├── .github ├── pr-labeler.yml ├── release-drafter.yml └── workflows │ ├── ci.yml │ ├── docs.yml │ ├── release-drafter.yml │ └── release.yml ├── .gitignore ├── .ruby-version ├── .scalafmt.conf ├── AUTHORS.md ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── NOTICE.md ├── README.md ├── build.sbt ├── docs ├── AUTHORS.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md └── NOTICE.md ├── project ├── build.properties └── plugins.sbt └── src ├── main ├── resources │ └── doobie.svg └── scala │ └── doobie │ ├── ConnectingToDatabaseSection.scala │ ├── DoobieLibrary.scala │ ├── DoobieUtils.scala │ ├── ErrorHandlingSection.scala │ ├── ErrorHandlingSectionHelpers.scala │ ├── Model.scala │ ├── MultiColumnQueriesSection.scala │ ├── ParameterizedQueriesSection.scala │ ├── ParameterizedQueryHelpers.scala │ ├── SelectingDataSection.scala │ ├── UpdatesSection.scala │ └── UpdatesSectionHelpers.scala └── test └── scala └── doobie ├── ConnectingToDatabaseSectionSpec.scala ├── ErrorHandlingSectionSpec.scala ├── MultiColumnQueriesSectionSpec.scala ├── ParameterizedQueriesSectionSpec.scala ├── SelectingDataSectionSpec.scala └── UpdatesSectionSpec.scala /.github/pr-labeler.yml: -------------------------------------------------------------------------------- 1 | # Don't edit this file! 2 | # It is automatically updated after every release of https://github.com/47degrees/.github 3 | # If you want to suggest a change, please open a PR or issue in that repository 4 | 5 | enhancement: ['enhancement/*', 'feature/*'] 6 | documentation: ['docs/*', 'doc/*'] 7 | breaking-change: ['breaking/*', 'break/*'] 8 | bug: ['bug/*', 'fix/*'] 9 | tests: ['test/*', 'tests/*'] 10 | dependency-update: ['dep/*', 'dependency/*', 'dependency-update/*'] 11 | scala-steward: ['update/*'] 12 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | # Don't edit this file! 2 | # It is automatically updated after every release of https://github.com/47degrees/.github 3 | # If you want to suggest a change, please open a PR or issue in that repository 4 | 5 | name-template: 'v$NEXT_PATCH_VERSION' 6 | tag-template: 'v$NEXT_PATCH_VERSION' 7 | exclude-labels: 8 | - 'auto-update' 9 | - 'auto-documentation' 10 | - 'auto-changelog' 11 | categories: 12 | - title: '⚠️ Breaking changes' 13 | label: 'breaking-change' 14 | - title: '🚀 Features' 15 | label: 'enhancement' 16 | - title: '📘 Documentation' 17 | label: 'documentation' 18 | - title: '🐛 Bug Fixes' 19 | label: 'bug' 20 | - title: '📈 Dependency updates' 21 | labels: 22 | - 'dependency-update' 23 | - 'scala-steward' 24 | template: | 25 | ## What's changed 26 | 27 | $CHANGES 28 | 29 | ## Contributors to this release 30 | 31 | $CONTRIBUTORS 32 | 33 | autolabeler: 34 | - label: "enhancement" 35 | branch: 36 | - '/enhancement\/.+/' 37 | - '/feature\/.+/' 38 | - label: "documentation" 39 | files: 40 | - "*.md" 41 | branch: 42 | - '/docs\/.+/' 43 | - '/doc\/.+/' 44 | - label: "breaking-change" 45 | branch: 46 | - '/breaking\/.+/' 47 | - '/break\/.+/' 48 | - label: "bug" 49 | branch: 50 | - '/bug\/.+/' 51 | - '/fix\/.+/' 52 | - label: "tests" 53 | branch: 54 | - '/test\/.+/' 55 | - '/tests\/.+/' 56 | - label: "dependency-update" 57 | branch: 58 | - '/dep\/.+/' 59 | - '/dependency\/.+/' 60 | - '/dependency-update\/.+/' 61 | - label: "scala-steward" 62 | branch: 63 | - '/update\/.+/' 64 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # Don't edit this file! 2 | # It is automatically updated after every release of https://github.com/47degrees/.github 3 | # If you want to suggest a change, please open a PR or issue in that repository 4 | 5 | name: Formatters & Tests 6 | 7 | on: 8 | push: 9 | branches: [main] 10 | pull_request: 11 | 12 | jobs: 13 | test: 14 | if: "!contains(github.event.head_commit.message, 'skip ci')" 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout project (pull-request) 18 | if: github.event_name == 'pull_request' 19 | uses: actions/checkout@v2.3.2 20 | with: 21 | repository: ${{ github.event.pull_request.head.repo.full_name }} 22 | ref: ${{ github.event.pull_request.head.ref }} 23 | - name: Checkout project (main) 24 | if: github.event_name == 'push' 25 | uses: actions/checkout@v2 26 | - name: Setup Scala 27 | uses: olafurpg/setup-scala@v11 28 | with: 29 | java-version: adopt@1.11 30 | - name: Setup Ruby 31 | uses: ruby/setup-ruby@v1 32 | with: 33 | ruby-version: .ruby-version 34 | - name: Setup yq 35 | run: sudo snap install yq 36 | - name: Run pre-conditions 37 | run: test -f .github/actions.yml && eval "$(yq e '.pre.ci // "true"' .github/actions.yml)" || true 38 | - name: Run scalafmt on Scala Steward PRs 39 | if: github.event.pull_request.user.login == '47erbot' && contains(github.event.pull_request.body, 'Scala Steward') 40 | run: sbt "scalafixEnable; fix" || sbt "scalafmtAll; scalafmtSbt" || true 41 | - name: Push changes 42 | uses: stefanzweifel/git-auto-commit-action@v4.5.1 43 | with: 44 | commit_message: Run formatter/linter 45 | - name: Run checks 46 | run: sbt ci-test 47 | env: 48 | GITHUB_TOKEN: ${{ secrets.ADMIN_GITHUB_TOKEN }} 49 | - name: Run post-conditions 50 | run: test -f .github/actions.yml && eval "$(yq e '.post.ci // "true"' .github/actions.yml)" || true 51 | - name: Automerge Scala Steward PRs 52 | if: success() && github.event_name == 'pull_request' && contains(github.event.pull_request.body, 'Scala Steward') 53 | uses: ridedott/merge-me-action@v1.1.36 54 | with: 55 | GITHUB_LOGIN: 47erbot 56 | GITHUB_TOKEN: ${{ secrets.ADMIN_GITHUB_TOKEN }} 57 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | # Don't edit this file! 2 | # It is automatically updated after every release of https://github.com/47degrees/.github 3 | # If you want to suggest a change, please open a PR or issue in that repository 4 | 5 | name: Update documentation 6 | 7 | on: 8 | release: 9 | types: [published] 10 | repository_dispatch: 11 | types: [docs] 12 | 13 | jobs: 14 | documentation: 15 | if: "!contains(github.event.head_commit.message, 'skip ci')" 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout project 19 | uses: actions/checkout@v2 20 | with: 21 | token: ${{ secrets.ADMIN_GITHUB_TOKEN }} 22 | ref: main 23 | - name: Fetch tags 24 | run: git fetch --tags 25 | - name: Setup Scala 26 | uses: olafurpg/setup-scala@v11 27 | with: 28 | java-version: adopt@1.11 29 | - name: Setup Ruby 30 | uses: ruby/setup-ruby@v1 31 | with: 32 | ruby-version: .ruby-version 33 | - name: Setup github-changelog-generator 34 | run: gem install github_changelog_generator -v 1.15.0 35 | - name: Setup yq 36 | run: sudo snap install yq 37 | - name: Run pre-conditions 38 | run: test -f .github/actions.yml && eval "$(yq e '.pre.docs // "true"' .github/actions.yml)" || true 39 | - name: Generate documentation 40 | run: sbt ci-docs 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | DOWNLOAD_INFO_FROM_GITHUB: true 44 | - name: Run post-conditions 45 | run: test -f .github/actions.yml && eval "$(yq e '.post.docs // "true"' .github/actions.yml)" || true 46 | - name: Push changes 47 | uses: stefanzweifel/git-auto-commit-action@v4.1.3 48 | with: 49 | commit_message: 'Update documentation, and other files [skip ci]' 50 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Drafts/updates the next repository release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | update_release_draft: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: release-drafter/release-drafter@v5 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.ADMIN_GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Don't edit this file! 2 | # It is automatically updated after every release of https://github.com/47degrees/.github 3 | # If you want to suggest a change, please open a PR or issue in that repository 4 | 5 | name: Release 6 | 7 | on: 8 | release: 9 | types: [published] 10 | push: 11 | branches: main 12 | 13 | jobs: 14 | release: 15 | if: "!contains(github.event.head_commit.message, 'skip ci')" 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout project 19 | uses: actions/checkout@v2 20 | with: 21 | fetch-depth: 0 22 | - name: Fetch tags 23 | run: git fetch --tags 24 | - name: Setup Scala 25 | uses: olafurpg/setup-scala@v11 26 | with: 27 | java-version: adopt@1.11 28 | - name: Setup Ruby 29 | uses: ruby/setup-ruby@v1 30 | with: 31 | ruby-version: .ruby-version 32 | - name: Setup GPG 33 | uses: olafurpg/setup-gpg@v3 34 | - name: Setup yq 35 | run: sudo snap install yq 36 | - name: Run pre-conditions 37 | run: test -f .github/actions.yml && eval "$(yq e '.pre.release // "true"' .github/actions.yml)" || true 38 | - name: Release new version 39 | run: sbt ci-publish 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 43 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 44 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 45 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 46 | - name: Run post-conditions 47 | run: test -f .github/actions.yml && eval "$(yq e '.post.release // "true"' .github/actions.yml)" || true 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don't edit this file! 2 | # It is automatically updated after every release of https://github.com/47degrees/.github 3 | # If you want to suggest a change, please open a PR or issue in that repository 4 | 5 | ### Intellij ### 6 | 7 | .idea 8 | out/ 9 | 10 | ### Java ### 11 | 12 | *.class 13 | *.log 14 | 15 | ### macOS ### 16 | 17 | .DS_Store 18 | 19 | ### SBT ### 20 | 21 | dist/* 22 | target/ 23 | lib_managed/ 24 | src_managed/ 25 | project/boot/ 26 | project/plugins/project/ 27 | .history 28 | .cache 29 | .lib/ 30 | .bsp 31 | 32 | ### Scala ### 33 | 34 | *.metals 35 | .bloop/ 36 | .metals/ 37 | metals.sbt 38 | .scala-build/ 39 | 40 | ### Vim ### 41 | 42 | # Swap 43 | [._]*.s[a-v][a-z] 44 | [._]*.sw[a-p] 45 | [._]s[a-rt-v][a-z] 46 | [._]ss[a-gi-z] 47 | [._]sw[a-p] 48 | 49 | # Session 50 | Session.vim 51 | Sessionx.vim 52 | 53 | # Temporary 54 | .netrwhist 55 | 56 | # Project local build artefacts 57 | .output 58 | 59 | # Auto-generated tag files 60 | tags 61 | 62 | # Persistent undo 63 | [._]*.un~ 64 | 65 | # Coc configuration directory 66 | .vim 67 | 68 | ### VisualStudioCode ### 69 | 70 | .vscode/ 71 | .vscode/* 72 | !.vscode/settings.json 73 | !.vscode/tasks.json 74 | !.vscode/launch.json 75 | !.vscode/extensions.json 76 | 77 | # Direnv 78 | 79 | .direnv 80 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.7.2 -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version=3.7.3 2 | style = defaultWithAlign 3 | maxColumn = 100 4 | runner.dialect = scala213 5 | 6 | continuationIndent.callSite = 2 7 | 8 | newlines { 9 | sometimesBeforeColonInMethodReturnType = false 10 | } 11 | 12 | align { 13 | arrowEnumeratorGenerator = false 14 | ifWhileOpenParen = false 15 | openParenCallSite = false 16 | openParenDefnSite = false 17 | } 18 | 19 | docstrings.style = Asterisk 20 | 21 | rewrite { 22 | rules = [SortImports, RedundantBraces] 23 | redundantBraces.maxLines = 1 24 | } 25 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | [comment]: <> (Don't edit this file!) 2 | [comment]: <> (It is automatically updated after every release of https://github.com/47degrees/.github) 3 | [comment]: <> (If you want to suggest a change, please open a PR or issue in that repository) 4 | 5 | # Authors 6 | 7 | ## Maintainers 8 | 9 | The maintainers of the project are: 10 | 11 | - [![47erbot](https://avatars1.githubusercontent.com/u/24799081?v=4&s=20) **47erbot**](https://github.com/47erbot) 12 | - [![alejandrohdezma](https://avatars0.githubusercontent.com/u/9027541?v=4&s=20) **Alejandro Hernández (alejandrohdezma)**](https://github.com/alejandrohdezma) 13 | - [![angoglez](https://avatars0.githubusercontent.com/u/10107285?v=4&s=20) **Ana Gómez González (angoglez)**](https://github.com/angoglez) 14 | - [![BenFradet](https://avatars2.githubusercontent.com/u/1737211?v=4&s=20) **Ben Fradet (BenFradet)**](https://github.com/BenFradet) 15 | - [![dominv](https://avatars1.githubusercontent.com/u/3943031?v=4&s=20) **Domingo Valera (dominv)**](https://github.com/dominv) 16 | - [![kiroco12](https://avatars1.githubusercontent.com/u/48894338?v=4&s=20) **Enrique Nieto (kiroco12)**](https://github.com/kiroco12) 17 | - [![franciscodr](https://avatars1.githubusercontent.com/u/1200151?v=4&s=20) **Francisco Diaz (franciscodr)**](https://github.com/franciscodr) 18 | - [![FRosner](https://avatars2.githubusercontent.com/u/3427394?v=4&s=20) **Frank Rosner (FRosner)**](https://github.com/FRosner) 19 | - [![jdesiloniz](https://avatars2.githubusercontent.com/u/2835739?v=4&s=20) **Javier de Silóniz Sandino (jdesiloniz)**](https://github.com/jdesiloniz) 20 | - [![juanpedromoreno](https://avatars2.githubusercontent.com/u/4879373?v=4&s=20) **Juan Pedro Moreno (juanpedromoreno)**](https://github.com/juanpedromoreno) 21 | - [![raulraja](https://avatars3.githubusercontent.com/u/456796?v=4&s=20) **Raúl Raja Martínez (raulraja)**](https://github.com/raulraja) 22 | 23 | ## Contributors 24 | 25 | These are the people that have contributed to the _exercises-doobie_ project: 26 | 27 | - [![juanpedromoreno](https://avatars2.githubusercontent.com/u/4879373?v=4&s=20) **juanpedromoreno**](https://github.com/juanpedromoreno) 28 | - [![franciscodr](https://avatars1.githubusercontent.com/u/1200151?v=4&s=20) **franciscodr**](https://github.com/franciscodr) 29 | - [![47erbot](https://avatars1.githubusercontent.com/u/24799081?v=4&s=20) **47erbot**](https://github.com/47erbot) 30 | - [![kiroco12](https://avatars1.githubusercontent.com/u/48894338?v=4&s=20) **kiroco12**](https://github.com/kiroco12) 31 | - [![jdesiloniz](https://avatars2.githubusercontent.com/u/2835739?v=4&s=20) **jdesiloniz**](https://github.com/jdesiloniz) 32 | - [![dominv](https://avatars1.githubusercontent.com/u/3943031?v=4&s=20) **dominv**](https://github.com/dominv) 33 | - [![BenFradet](https://avatars2.githubusercontent.com/u/1737211?v=4&s=20) **BenFradet**](https://github.com/BenFradet) 34 | - [![astorije](https://avatars1.githubusercontent.com/u/113730?v=4&s=20) **astorije**](https://github.com/astorije) 35 | - [![raulraja](https://avatars3.githubusercontent.com/u/456796?v=4&s=20) **raulraja**](https://github.com/raulraja) 36 | - [![tpolecat](https://avatars0.githubusercontent.com/u/1200131?v=4&s=20) **tpolecat**](https://github.com/tpolecat) 37 | - [![angoglez](https://avatars0.githubusercontent.com/u/10107285?v=4&s=20) **angoglez**](https://github.com/angoglez) 38 | - [![zarthross](https://avatars3.githubusercontent.com/u/1111592?v=4&s=20) **zarthross**](https://github.com/zarthross) 39 | - [![FRosner](https://avatars2.githubusercontent.com/u/3427394?v=4&s=20) **FRosner**](https://github.com/FRosner) 40 | - [![lloydmeta](https://avatars3.githubusercontent.com/u/914805?v=4&s=20) **lloydmeta**](https://github.com/lloydmeta) 41 | - [![etaty](https://avatars1.githubusercontent.com/u/461170?v=4&s=20) **etaty**](https://github.com/etaty) 42 | - [![alejandrohdezma](https://avatars0.githubusercontent.com/u/9027541?v=4&s=20) **alejandrohdezma**](https://github.com/alejandrohdezma) 43 | - [![zailushangde](https://avatars2.githubusercontent.com/u/12761009?v=4&s=20) **zailushangde**](https://github.com/zailushangde) -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v0.6.6](https://github.com/scala-exercises/exercises-doobie/tree/v0.6.6) (2020-08-10) 4 | 5 | [Full Changelog](https://github.com/scala-exercises/exercises-doobie/compare/v0.6.5...v0.6.6) 6 | 7 | 📈 **Dependency updates** 8 | 9 | - Update definitions, exercise-compiler, ... to 0.6.4 [\#87](https://github.com/scala-exercises/exercises-doobie/pull/87) ([47erbot](https://github.com/47erbot)) 10 | 11 | ## [v0.6.5](https://github.com/scala-exercises/exercises-doobie/tree/v0.6.5) (2020-08-10) 12 | 13 | [Full Changelog](https://github.com/scala-exercises/exercises-doobie/compare/v0.6.4...v0.6.5) 14 | 15 | 📈 **Dependency updates** 16 | 17 | - Update sbt-mdoc to 2.2.4 [\#86](https://github.com/scala-exercises/exercises-doobie/pull/86) ([47erbot](https://github.com/47erbot)) 18 | - Update scalacheck-1-14 to 3.2.1.0 [\#85](https://github.com/scala-exercises/exercises-doobie/pull/85) ([47erbot](https://github.com/47erbot)) 19 | - Update scalatest to 3.2.1 [\#84](https://github.com/scala-exercises/exercises-doobie/pull/84) ([47erbot](https://github.com/47erbot)) 20 | - Update sbt-scalafmt to 2.4.2 [\#83](https://github.com/scala-exercises/exercises-doobie/pull/83) ([47erbot](https://github.com/47erbot)) 21 | - Update scalafmt-core to 2.6.4 [\#82](https://github.com/scala-exercises/exercises-doobie/pull/82) ([47erbot](https://github.com/47erbot)) 22 | - Update scalafmt-core to 2.6.3 [\#81](https://github.com/scala-exercises/exercises-doobie/pull/81) ([scala-steward](https://github.com/scala-steward)) 23 | - Update scalafmt-core to 2.6.2 [\#79](https://github.com/scala-exercises/exercises-doobie/pull/79) ([scala-steward](https://github.com/scala-steward)) 24 | 25 | ## [v0.6.4](https://github.com/scala-exercises/exercises-doobie/tree/v0.6.4) (2020-06-30) 26 | 27 | [Full Changelog](https://github.com/scala-exercises/exercises-doobie/compare/v0.6.3...v0.6.4) 28 | 29 | ## [v0.6.3](https://github.com/scala-exercises/exercises-doobie/tree/v0.6.3) (2020-06-29) 30 | 31 | [Full Changelog](https://github.com/scala-exercises/exercises-doobie/compare/v0.6.2...v0.6.3) 32 | 33 | 📈 **Dependency updates** 34 | 35 | - Update sbt to 1.3.13 [\#76](https://github.com/scala-exercises/exercises-doobie/pull/76) ([scala-steward](https://github.com/scala-steward)) 36 | - Update sbt-mdoc to 2.2.3 [\#75](https://github.com/scala-exercises/exercises-doobie/pull/75) ([scala-steward](https://github.com/scala-steward)) 37 | - Update scalafmt-core to 2.6.1 [\#74](https://github.com/scala-exercises/exercises-doobie/pull/74) ([scala-steward](https://github.com/scala-steward)) 38 | - Update scalacheck-1-14 to 3.2.0.0 [\#73](https://github.com/scala-exercises/exercises-doobie/pull/73) ([scala-steward](https://github.com/scala-steward)) 39 | - Update scalatest to 3.2.0 [\#72](https://github.com/scala-exercises/exercises-doobie/pull/72) ([scala-steward](https://github.com/scala-steward)) 40 | - Update sbt-mdoc to 2.2.2 [\#66](https://github.com/scala-exercises/exercises-doobie/pull/66) ([scala-steward](https://github.com/scala-steward)) 41 | - Update sbt to 1.3.11 [\#64](https://github.com/scala-exercises/exercises-doobie/pull/64) ([scala-steward](https://github.com/scala-steward)) 42 | - Update scalatest to 3.1.2 [\#63](https://github.com/scala-exercises/exercises-doobie/pull/63) ([scala-steward](https://github.com/scala-steward)) 43 | 44 | **Merged pull requests:** 45 | 46 | - Update definitions, exercise-compiler, ... to 0.6.3 [\#77](https://github.com/scala-exercises/exercises-doobie/pull/77) ([scala-steward](https://github.com/scala-steward)) 47 | - Update scalafmt-core to 2.6.0 [\#70](https://github.com/scala-exercises/exercises-doobie/pull/70) ([BenFradet](https://github.com/BenFradet)) 48 | - Update scalafmt-core to 2.5.3 [\#68](https://github.com/scala-exercises/exercises-doobie/pull/68) ([BenFradet](https://github.com/BenFradet)) 49 | - Prepare repository for next release and SBT build improvements [\#65](https://github.com/scala-exercises/exercises-doobie/pull/65) ([juanpedromoreno](https://github.com/juanpedromoreno)) 50 | 51 | ## [v0.6.2](https://github.com/scala-exercises/exercises-doobie/tree/v0.6.2) (2020-04-27) 52 | 53 | [Full Changelog](https://github.com/scala-exercises/exercises-doobie/compare/v0.6.1...v0.6.2) 54 | 55 | ## [v0.6.1](https://github.com/scala-exercises/exercises-doobie/tree/v0.6.1) (2020-04-27) 56 | 57 | [Full Changelog](https://github.com/scala-exercises/exercises-doobie/compare/v0.6.0...v0.6.1) 58 | 59 | **Merged pull requests:** 60 | 61 | - Compile when Publish [\#58](https://github.com/scala-exercises/exercises-doobie/pull/58) ([juanpedromoreno](https://github.com/juanpedromoreno)) 62 | 63 | ## [v0.6.0](https://github.com/scala-exercises/exercises-doobie/tree/v0.6.0) (2020-04-24) 64 | 65 | [Full Changelog](https://github.com/scala-exercises/exercises-doobie/compare/v0.4.0...v0.6.0) 66 | 67 | **Merged pull requests:** 68 | 69 | - Updates Project [\#52](https://github.com/scala-exercises/exercises-doobie/pull/52) ([juanpedromoreno](https://github.com/juanpedromoreno)) 70 | - Update sbt to 1.3.10 [\#50](https://github.com/scala-exercises/exercises-doobie/pull/50) ([scala-steward](https://github.com/scala-steward)) 71 | - Update sbt to 1.3.9 [\#49](https://github.com/scala-exercises/exercises-doobie/pull/49) ([scala-steward](https://github.com/scala-steward)) 72 | - Update scalacheck-shapeless\_1.14 to 1.2.5 [\#48](https://github.com/scala-exercises/exercises-doobie/pull/48) ([scala-steward](https://github.com/scala-steward)) 73 | - Update scalatest to 3.1.1 [\#47](https://github.com/scala-exercises/exercises-doobie/pull/47) ([scala-steward](https://github.com/scala-steward)) 74 | - Mergify: configuration update [\#46](https://github.com/scala-exercises/exercises-doobie/pull/46) ([angoglez](https://github.com/angoglez)) 75 | - Update scalacheck-shapeless\_1.14 to 1.2.4 [\#45](https://github.com/scala-exercises/exercises-doobie/pull/45) ([scala-steward](https://github.com/scala-steward)) 76 | - Update sbt to 1.3.8 [\#44](https://github.com/scala-exercises/exercises-doobie/pull/44) ([scala-steward](https://github.com/scala-steward)) 77 | - Update to Scala 2.13.1 [\#43](https://github.com/scala-exercises/exercises-doobie/pull/43) ([kiroco12](https://github.com/kiroco12)) 78 | - Update scalacheck to 1.14.3 [\#39](https://github.com/scala-exercises/exercises-doobie/pull/39) ([scala-steward](https://github.com/scala-steward)) 79 | - Updated ScalaTest 3.1.0 [\#37](https://github.com/scala-exercises/exercises-doobie/pull/37) ([kiroco12](https://github.com/kiroco12)) 80 | - Fixed format and typos [\#36](https://github.com/scala-exercises/exercises-doobie/pull/36) ([kiroco12](https://github.com/kiroco12)) 81 | - Update to Scala 2.12.10 [\#35](https://github.com/scala-exercises/exercises-doobie/pull/35) ([kiroco12](https://github.com/kiroco12)) 82 | - Fix typo in SelectingDataSection.scala [\#32](https://github.com/scala-exercises/exercises-doobie/pull/32) ([zailushangde](https://github.com/zailushangde)) 83 | - Updating sbt org policies [\#30](https://github.com/scala-exercises/exercises-doobie/pull/30) ([dominv](https://github.com/dominv)) 84 | - Updating libraries versions [\#29](https://github.com/scala-exercises/exercises-doobie/pull/29) ([dominv](https://github.com/dominv)) 85 | - Add order by for section dealing with in clause [\#28](https://github.com/scala-exercises/exercises-doobie/pull/28) ([lloydmeta](https://github.com/lloydmeta)) 86 | - Fix some super minor typos [\#27](https://github.com/scala-exercises/exercises-doobie/pull/27) ([astorije](https://github.com/astorije)) 87 | - Fix typo [\#25](https://github.com/scala-exercises/exercises-doobie/pull/25) ([astorije](https://github.com/astorije)) 88 | 89 | ## [v0.4.0](https://github.com/scala-exercises/exercises-doobie/tree/v0.4.0) (2017-03-28) 90 | 91 | [Full Changelog](https://github.com/scala-exercises/exercises-doobie/compare/818c2d17a4980aa2343366c190a4c98291d99618...v0.4.0) 92 | 93 | **Closed issues:** 94 | 95 | - Exercise about shapeless Record was disabled [\#9](https://github.com/scala-exercises/exercises-doobie/issues/9) 96 | 97 | **Merged pull requests:** 98 | 99 | - Integrates sbt-org-policies plugin [\#24](https://github.com/scala-exercises/exercises-doobie/pull/24) ([juanpedromoreno](https://github.com/juanpedromoreno)) 100 | - Fix indentation for browser display [\#21](https://github.com/scala-exercises/exercises-doobie/pull/21) ([etaty](https://github.com/etaty)) 101 | - Update ErrorHandlingSection.scala [\#20](https://github.com/scala-exercises/exercises-doobie/pull/20) ([zarthross](https://github.com/zarthross)) 102 | - Updates PGP keys [\#18](https://github.com/scala-exercises/exercises-doobie/pull/18) ([juanpedromoreno](https://github.com/juanpedromoreno)) 103 | - SE-597 - Upgrade Libs [\#17](https://github.com/scala-exercises/exercises-doobie/pull/17) ([juanpedromoreno](https://github.com/juanpedromoreno)) 104 | - Added README to content repository [\#16](https://github.com/scala-exercises/exercises-doobie/pull/16) ([jdesiloniz](https://github.com/jdesiloniz)) 105 | - Included icon in library - bumped up version to 0.2.5 [\#15](https://github.com/scala-exercises/exercises-doobie/pull/15) ([jdesiloniz](https://github.com/jdesiloniz)) 106 | - Adds a new exercise with shapeless record [\#14](https://github.com/scala-exercises/exercises-doobie/pull/14) ([franciscodr](https://github.com/franciscodr)) 107 | - JP - Bumps Version [\#13](https://github.com/scala-exercises/exercises-doobie/pull/13) ([juanpedromoreno](https://github.com/juanpedromoreno)) 108 | - Adds Error handling section [\#12](https://github.com/scala-exercises/exercises-doobie/pull/12) ([franciscodr](https://github.com/franciscodr)) 109 | - Adds Inserting and Updating section [\#11](https://github.com/scala-exercises/exercises-doobie/pull/11) ([franciscodr](https://github.com/franciscodr)) 110 | - Adds Parameterized queries section [\#10](https://github.com/scala-exercises/exercises-doobie/pull/10) ([franciscodr](https://github.com/franciscodr)) 111 | - replace inDb with custom transactor [\#8](https://github.com/scala-exercises/exercises-doobie/pull/8) ([tpolecat](https://github.com/tpolecat)) 112 | - Adds MultiColumn queries section [\#7](https://github.com/scala-exercises/exercises-doobie/pull/7) ([franciscodr](https://github.com/franciscodr)) 113 | - Adds inDb method to perform data initialization and cleanup operations [\#6](https://github.com/scala-exercises/exercises-doobie/pull/6) ([franciscodr](https://github.com/franciscodr)) 114 | - Fixes conflict between different doobie versions [\#5](https://github.com/scala-exercises/exercises-doobie/pull/5) ([franciscodr](https://github.com/franciscodr)) 115 | - JP - SE-510 DRY Test.scala [\#4](https://github.com/scala-exercises/exercises-doobie/pull/4) ([juanpedromoreno](https://github.com/juanpedromoreno)) 116 | - Adds Selecting data section [\#3](https://github.com/scala-exercises/exercises-doobie/pull/3) ([franciscodr](https://github.com/franciscodr)) 117 | - Adds Connecting to database section [\#2](https://github.com/scala-exercises/exercises-doobie/pull/2) ([franciscodr](https://github.com/franciscodr)) 118 | - Adding the project structure for doobie exercises [\#1](https://github.com/scala-exercises/exercises-doobie/pull/1) ([franciscodr](https://github.com/franciscodr)) 119 | 120 | 121 | 122 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 123 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | [comment]: <> (Don't edit this file!) 2 | [comment]: <> (It is automatically updated after every release of https://github.com/47degrees/.github) 3 | [comment]: <> (If you want to suggest a change, please open a PR or issue in that repository) 4 | 5 | # Code of Conduct 6 | 7 | We are committed to providing a friendly, safe and welcoming 8 | environment for all, regardless of level of experience, gender, gender 9 | identity and expression, sexual orientation, disability, personal 10 | appearance, body size, race, ethnicity, age, religion, nationality, or 11 | other such characteristics. 12 | 13 | Everyone is expected to follow the 14 | [Scala Code of Conduct](https://www.scala-lang.org/conduct/) when 15 | discussing the project on the available communication channels. If you 16 | are being harassed, please contact us immediately so that we can 17 | support you. 18 | 19 | ## Moderation 20 | 21 | For any questions, concerns, or moderation requests please contact a 22 | [member of the project](AUTHORS.md#maintainers). -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | [comment]: <> (Don't edit this file!) 2 | [comment]: <> (It is automatically updated after every release of https://github.com/47degrees/.github) 3 | [comment]: <> (If you want to suggest a change, please open a PR or issue in that repository) 4 | 5 | # Contributing 6 | 7 | Discussion around _exercises-doobie_ happens in the [GitHub issues](https://github.com/scala-exercises/exercises-doobie/issues) and [pull requests](https://github.com/scala-exercises/exercises-doobie/pulls). 8 | 9 | Feel free to open an issue if you notice a bug, have an idea for a feature, or have a question about 10 | the code. Pull requests are also welcome. 11 | 12 | People are expected to follow the [Code of Conduct](CODE_OF_CONDUCT.md) when discussing _exercises-doobie_ on the Github page or other venues. 13 | 14 | If you are being harassed, please contact one of [us](AUTHORS.md#maintainers) immediately so that we can support you. In case you cannot get in touch with us please write an email to [47 Degrees Open Source](mailto:hello@47deg.com). 15 | 16 | ## How can I help? 17 | 18 | _exercises-doobie_ follows a standard [fork and pull](https://help.github.com/articles/using-pull-requests/) model for contributions via GitHub pull requests. 19 | 20 | The process is simple: 21 | 22 | 1. Find something you want to work on 23 | 2. Let us know you are working on it via GitHub issues/pull requests 24 | 3. Implement your contribution 25 | 4. Write tests 26 | 5. Update the documentation 27 | 6. Submit pull request 28 | 29 | You will be automatically included in the [AUTHORS.md](AUTHORS.md#contributors) file as contributor in the next release. 30 | 31 | If you encounter any confusion or frustration during the contribution process, please create a GitHub issue and we'll do our best to improve the process. -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright (C) 2016-2020 47 Degrees Open Source 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /NOTICE.md: -------------------------------------------------------------------------------- 1 | [comment]: <> (Don't edit this file!) 2 | [comment]: <> (It is automatically updated after every release of https://github.com/47degrees/.github) 3 | [comment]: <> (If you want to suggest a change, please open a PR or issue in that repository) 4 | 5 | exercises-doobie 6 | 7 | Copyright (c) 2016-2020 47 Degrees Open Source. All rights reserved. 8 | 9 | Licensed under Apache-2.0. See [LICENSE](LICENSE.md) for terms. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scala Exercises - Doobie library 2 | ------------------------ 3 | 4 | This repository hosts a content library for the [Scala Exercises](https://www.scala-exercises.org/) platform, including interactive exercises related to the [Doobie](https://github.com/tpolecat/doobie) functional JDBC layer. 5 | 6 | ## About Scala exercises 7 | 8 | "Scala Exercises" brings exercises for the Stdlib, Cats, Shapeless and many other great libraries for Scala to your browser. Offering hundreds of solvable exercises organized into several categories covering the basics of the Scala language and it's most important libraries. 9 | 10 | Scala Exercises is available at [scala-exercises.org](https://scala-exercises.org). 11 | 12 | ## Contributing 13 | 14 | Contributions welcome! Please join our [Gitter channel](https://gitter.im/scala-exercises/scala-exercises) 15 | to get involved, or visit our [GitHub site](https://github.com/scala-exercises). 16 | 17 | ## License 18 | 19 | Copyright (C) 2015-2016 47 Degrees, LLC. 20 | Reactive, scalable software solutions. 21 | http://47deg.com 22 | hello@47deg.com 23 | 24 | Licensed under the Apache License, Version 2.0 (the "License"); 25 | you may not use this file except in compliance with the License. 26 | You may obtain a copy of the License at 27 | 28 | http://www.apache.org/licenses/LICENSE-2.0 29 | 30 | Unless required by applicable law or agreed to in writing, software 31 | distributed under the License is distributed on an "AS IS" BASIS, 32 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | See the License for the specific language governing permissions and 34 | limitations under the License. -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import com.jsuereth.sbtpgp.PgpKeys.publishSigned 2 | 3 | ThisBuild / organization := "org.scala-exercises" 4 | ThisBuild / githubOrganization := "47degrees" 5 | ThisBuild / scalaVersion := "2.13.10" 6 | 7 | // This is required by the exercises compiler: 8 | publishLocal := (publishLocal dependsOn compile).value 9 | publishSigned := (publishSigned dependsOn compile).value 10 | 11 | addCommandAlias("ci-test", "scalafmtCheckAll; scalafmtSbtCheck; test") 12 | addCommandAlias("ci-docs", "github; documentation/mdoc; headerCreateAll") 13 | addCommandAlias("ci-publish", "github; ci-release") 14 | 15 | lazy val exercises = (project in file(".")) 16 | .settings(name := "exercises-doobie") 17 | .settings( 18 | libraryDependencies ++= Seq( 19 | "org.scala-exercises" %% "exercise-compiler" % "0.6.7", 20 | "org.scala-exercises" %% "definitions" % "0.6.7", 21 | "org.typelevel" %% "cats-core" % "2.9.0", 22 | "org.tpolecat" %% "doobie-core" % "0.13.4", 23 | "org.tpolecat" %% "doobie-h2" % "0.13.4", 24 | "com.chuusai" %% "shapeless" % "2.3.10", 25 | "org.scalatest" %% "scalatest" % "3.2.14", 26 | "org.scalacheck" %% "scalacheck" % "1.17.0", 27 | "org.scalatestplus" %% "scalacheck-1-14" % "3.2.2.0", 28 | "com.github.alexarchambault" %% "scalacheck-shapeless_1.15" % "1.3.0" 29 | ), 30 | dependencyOverrides += "org.scala-lang.modules" %% "scala-xml" % "1.3.0" 31 | ) 32 | .enablePlugins(ExerciseCompilerPlugin) 33 | 34 | lazy val documentation = project 35 | .settings(mdocOut := file(".")) 36 | .settings(publish / skip := true) 37 | .enablePlugins(MdocPlugin) 38 | -------------------------------------------------------------------------------- /docs/AUTHORS.md: -------------------------------------------------------------------------------- 1 | [comment]: <> (Don't edit this file!) 2 | [comment]: <> (It is automatically updated after every release of https://github.com/47degrees/.github) 3 | [comment]: <> (If you want to suggest a change, please open a PR or issue in that repository) 4 | 5 | # Authors 6 | 7 | ## Maintainers 8 | 9 | The maintainers of the project are: 10 | 11 | @COLLABORATORS@ 12 | 13 | ## Contributors 14 | 15 | These are the people that have contributed to the _@NAME@_ project: 16 | 17 | @CONTRIBUTORS@ -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | [comment]: <> (Don't edit this file!) 2 | [comment]: <> (It is automatically updated after every release of https://github.com/47degrees/.github) 3 | [comment]: <> (If you want to suggest a change, please open a PR or issue in that repository) 4 | 5 | # Code of Conduct 6 | 7 | We are committed to providing a friendly, safe and welcoming 8 | environment for all, regardless of level of experience, gender, gender 9 | identity and expression, sexual orientation, disability, personal 10 | appearance, body size, race, ethnicity, age, religion, nationality, or 11 | other such characteristics. 12 | 13 | Everyone is expected to follow the 14 | [Scala Code of Conduct](https://www.scala-lang.org/conduct/) when 15 | discussing the project on the available communication channels. If you 16 | are being harassed, please contact us immediately so that we can 17 | support you. 18 | 19 | ## Moderation 20 | 21 | For any questions, concerns, or moderation requests please contact a 22 | [member of the project](AUTHORS.md#maintainers). -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | [comment]: <> (Don't edit this file!) 2 | [comment]: <> (It is automatically updated after every release of https://github.com/47degrees/.github) 3 | [comment]: <> (If you want to suggest a change, please open a PR or issue in that repository) 4 | 5 | # Contributing 6 | 7 | Discussion around _@NAME@_ happens in the [GitHub issues](https://github.com/@REPO@/issues) and [pull requests](https://github.com/@REPO@/pulls). 8 | 9 | Feel free to open an issue if you notice a bug, have an idea for a feature, or have a question about 10 | the code. Pull requests are also welcome. 11 | 12 | People are expected to follow the [Code of Conduct](CODE_OF_CONDUCT.md) when discussing _@NAME@_ on the Github page or other venues. 13 | 14 | If you are being harassed, please contact one of [us](AUTHORS.md#maintainers) immediately so that we can support you. In case you cannot get in touch with us please write an email to [@ORG_NAME@](mailto:@ORG_EMAIL@). 15 | 16 | ## How can I help? 17 | 18 | _@NAME@_ follows a standard [fork and pull](https://help.github.com/articles/using-pull-requests/) model for contributions via GitHub pull requests. 19 | 20 | The process is simple: 21 | 22 | 1. Find something you want to work on 23 | 2. Let us know you are working on it via GitHub issues/pull requests 24 | 3. Implement your contribution 25 | 4. Write tests 26 | 5. Update the documentation 27 | 6. Submit pull request 28 | 29 | You will be automatically included in the [AUTHORS.md](AUTHORS.md#contributors) file as contributor in the next release. 30 | 31 | If you encounter any confusion or frustration during the contribution process, please create a GitHub issue and we'll do our best to improve the process. -------------------------------------------------------------------------------- /docs/LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright (C) @YEAR_RANGE@ @COPYRIGHT_OWNER@ 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /docs/NOTICE.md: -------------------------------------------------------------------------------- 1 | [comment]: <> (Don't edit this file!) 2 | [comment]: <> (It is automatically updated after every release of https://github.com/47degrees/.github) 3 | [comment]: <> (If you want to suggest a change, please open a PR or issue in that repository) 4 | 5 | @NAME@ 6 | 7 | Copyright (c) @YEAR_RANGE@ @ORG_NAME@. All rights reserved. 8 | 9 | Licensed under @LICENSE@. See [LICENSE](LICENSE.md) for terms. -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.8.2 -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-exercises" % "sbt-exercise" % "0.6.7") 2 | addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.11") 3 | addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.7") 4 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") 5 | addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.9.0") 6 | addSbtPlugin("com.alejandrohdezma" % "sbt-github" % "0.11.8") 7 | addSbtPlugin("com.alejandrohdezma" % "sbt-github-header" % "0.11.8") 8 | addSbtPlugin("com.alejandrohdezma" % "sbt-github-mdoc" % "0.11.8") 9 | addSbtPlugin("com.alejandrohdezma" % "sbt-remove-test-from-pom" % "0.1.0") 10 | -------------------------------------------------------------------------------- /src/main/resources/doobie.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Doobie 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/scala/doobie/ConnectingToDatabaseSection.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2020 47 Degrees Open Source 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package doobielib 18 | 19 | import cats.effect.IO 20 | import cats.implicits._ 21 | import DoobieUtils._ 22 | import doobie.implicits._ 23 | import doobie._ 24 | import org.scalaexercises.definitions.Section 25 | import org.scalatest.matchers.should.Matchers 26 | import org.scalatest.flatspec.AnyFlatSpec 27 | 28 | /** 29 | * ==Introduction== 30 | * doobie is a monadic API that provides a number of data types that all work the same way but 31 | * describe computations in different contexts. 32 | * 33 | * In the doobie high level API the most common types we will deal with have the form 34 | * ConnectionIO[A], specifying computations that take place in a context where a java.sql.Connection 35 | * is available, ultimately producing a value of type A. 36 | * 37 | * doobie programs are values. You can compose small programs to build larger programs. Once you 38 | * have constructed a program you wish to run, you interpret it into an effectful target monad of 39 | * your choice (Task or IO for example) and drop it into your main application wherever you like. 40 | * 41 | * ==First programs== 42 | * 43 | * Before we can use doobie we need to import some symbols. We will use package imports here as a 44 | * convenience; this will give us the most commonly-used symbols when working with the high-level 45 | * API. 46 | * 47 | * {{{ 48 | * import doobie._ 49 | * import doobie.implicits._ 50 | * }}} 51 | * 52 | * Let’s also bring in Cats. 53 | * 54 | * {{{ 55 | * import cats._ 56 | * import cats.effect._ 57 | * import cats.implicits._ 58 | * }}} 59 | * 60 | * In the doobie high level API the most common types we will deal with have the form 61 | * `ConnectionIO[A]`, specifying computations that take place in a context where a 62 | * `java.sql.Connection` is available, ultimately producing a value of type `A`. 63 | * 64 | * So let’s start with a `ConnectionIO` program that simply returns a constant. 65 | * 66 | * {{{ 67 | * val program1 = 42.pure[ConnectionIO] 68 | * // program1: ConnectionIO[Int] = Pure(42) 69 | * }}} 70 | * 71 | * This is a perfectly respectable doobie program, but we can’t run it as-is; we need a `Connection` 72 | * first. There are several ways to do this, but here let’s use a `Transactor`. 73 | * 74 | * '''Note''': DriverManagerTransactors have the advantage of no connection pooling and 75 | * configuration, so are perfect for testing. The main disadvantage is that it is slower than 76 | * pooling connection managers, no provides upper bound for concurrent connections and executes 77 | * blocking operations in an unbounded pool of threads. The `doobie-hikari` add-on provides a 78 | * `Transactor` implementation backed by a HikariCP connection pool. The connection pool is a 79 | * lifetime-managed object that must be shut down cleanly, so it is managed as a Resource. 80 | * 81 | * {{{ 82 | * import doobie.hikari._ 83 | * 84 | * // Resource yielding a transactor configured with a bounded connect EC and an unbounded 85 | * // transaction EC. Everything will be closed and shut down cleanly after use. 86 | * val transactor: Resource[IO, HikariTransactor[IO]] = 87 | * for { 88 | * ce <- ExecutionContexts.fixedThreadPool[IO] (32) // our connect EC 89 | * be <- Blocker[IO] // our blocking EC 90 | * xa <- HikariTransactor.newHikariTransactor[IO] ( 91 | * "org.h2.Driver", // driver classname 92 | * "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", // connect URL 93 | * "sa", // username 94 | * "", // password 95 | * ce, // await connection here 96 | * be // execute JDBC operations here 97 | * ) 98 | * } yield xa 99 | * }}} 100 | * 101 | * A `Transactor` is a data type that knows how to connect to a database, hand out connections, and 102 | * clean them up; and with this knowledge it can transform `ConnectionIO ~> IO`, which gives us a 103 | * program we can run. Specifically it gives us an `IO` that, when run, will connect to the database 104 | * and execute single transaction. 105 | * 106 | * We are using `cats.effect.IO` as our final effect type, but you can use any monad `M[_]` given 107 | * `cats.effect.Async[M]`. See Using Your Own Target Monad at the end of this chapter for more 108 | * details. 109 | * 110 | * @param name 111 | * connecting_to_database 112 | */ 113 | object ConnectingToDatabaseSection extends AnyFlatSpec with Matchers with Section { 114 | 115 | /** 116 | * And here we go. 117 | */ 118 | def constantValue(res0: Int) = 119 | transactor.use(42.pure[ConnectionIO].transact[IO]).unsafeRunSync() should be(res0) 120 | 121 | /** 122 | * We have computed a constant. It’s not very interesting because we never ask the database to 123 | * perform any work, but it’s a first step 124 | * 125 | * We are gonna connect to a database to compute a constant. Let’s use the sql string interpolator 126 | * to construct a query that asks the database to compute a constant. The meaning of this program 127 | * is “run the query, interpret the resultset as a stream of Int values, and yield its one and 128 | * only element.” 129 | */ 130 | def constantValueFromDatabase(res0: Int) = 131 | transactor.use(sql"select 42".query[Int].unique.transact[IO]).unsafeRunSync() should be(res0) 132 | 133 | /** 134 | * What if we want to do more than one thing in a transaction? Easy! `ConnectionIO` is a monad, so 135 | * we can use a for comprehension to compose two smaller programs into one larger program. 136 | */ 137 | def combineTwoPrograms(res0: (Int, Int)) = { 138 | val largerProgram = for { 139 | a <- sql"select 42".query[Int].unique 140 | b <- sql"select power(5, 2)".query[Int].unique 141 | } yield (a, b) 142 | 143 | transactor.use(largerProgram.transact[IO]).unsafeRunSync() should be(res0) 144 | } 145 | 146 | /** 147 | * The astute among you will note that we don’t actually need a monad to do this; an applicative 148 | * functor is all we need here. So we could also write the above program as: 149 | */ 150 | def combineTwoProgramsWithApplicative(res0: Int) = { 151 | val oneProgram = sql"select 42".query[Int].unique 152 | val anotherProgram = sql"select power(5, 2)".query[Int].unique 153 | 154 | transactor.use((oneProgram, anotherProgram).mapN(_ + _).transact[IO]).unsafeRunSync() should be( 155 | res0 156 | ) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/scala/doobie/DoobieLibrary.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2020 47 Degrees Open Source 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package doobielib 18 | 19 | import org.scalaexercises.definitions.{Library, Section} 20 | 21 | /** 22 | * doobie is a pure functional JDBC layer for Scala. 23 | * 24 | * @param name 25 | * doobie 26 | */ 27 | object DoobieLibrary extends Library { 28 | override def owner: String = "scala-exercises" 29 | override def repository: String = "exercises-doobie" 30 | 31 | override def color = Some("#E35E31") 32 | 33 | override def sections: List[Section] = 34 | List( 35 | ConnectingToDatabaseSection, 36 | SelectingDataSection, 37 | MultiColumnQueriesSection, 38 | ParameterizedQueriesSection, 39 | UpdatesSection, 40 | ErrorHandlingSection 41 | ) 42 | 43 | override def logoPath = "doobie" 44 | } 45 | -------------------------------------------------------------------------------- /src/main/scala/doobie/DoobieUtils.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2020 47 Degrees Open Source 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package doobielib 18 | 19 | import cats.effect._ 20 | import cats.implicits._ 21 | import doobie._ 22 | import doobie.free.connection.ConnectionIO 23 | import doobie.h2.H2Transactor 24 | import doobie.implicits._ 25 | import doobie.util.ExecutionContexts 26 | import Model._ 27 | 28 | object DoobieUtils { 29 | 30 | implicit val cs = IO.contextShift(ExecutionContexts.synchronous) 31 | 32 | val transactor: Resource[IO, H2Transactor[IO]] = { 33 | def url = "jdbc:h2:mem:" 34 | val user = "sa" 35 | val pass = "" 36 | 37 | for { 38 | ec <- ExecutionContexts.fixedThreadPool[IO](1) 39 | bc <- Blocker[IO] 40 | xa <- H2Transactor.newH2Transactor[IO](url, user, pass, ec, bc) 41 | } yield xa 42 | } 43 | 44 | object CountryTable { 45 | 46 | val createCountryTable: ConnectionIO[Int] = 47 | sql""" 48 | CREATE TABLE IF NOT EXISTS country ( 49 | code VARCHAR(64), 50 | name VARCHAR(255), 51 | population INT, 52 | gnp DECIMAL(10,2) 53 | ) 54 | """.update.run 55 | 56 | def insertCountries(countries: List[Country]): ConnectionIO[Int] = 57 | Update[Country]("insert into country (code, name, population, gnp) values (?,?,?,?)") 58 | .updateMany(countries) 59 | 60 | def transactorBlock[A](f: => ConnectionIO[A]): IO[A] = 61 | transactor.use((createCountryTable *> insertCountries(countries) *> f).transact[IO]) 62 | } 63 | 64 | object PersonTable { 65 | 66 | val createPersonTable: ConnectionIO[Int] = 67 | sql""" 68 | CREATE TABLE IF NOT EXISTS person ( 69 | id IDENTITY, 70 | name VARCHAR NOT NULL UNIQUE, 71 | age INT 72 | ) 73 | """.update.run 74 | 75 | def transactorBlock[A](f: => ConnectionIO[A]) = 76 | transactor.use((createPersonTable *> f).transact[IO]) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/scala/doobie/ErrorHandlingSection.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2020 47 Degrees Open Source 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package doobielib 18 | 19 | import DoobieUtils.PersonTable._ 20 | import ErrorHandlingSectionHelpers._ 21 | import doobie._ 22 | import doobie.implicits._ 23 | import org.scalaexercises.definitions.Section 24 | import org.scalatest.flatspec.AnyFlatSpec 25 | import org.scalatest.matchers.should.Matchers 26 | 27 | /** 28 | * ==About Exceptions== 29 | * Exceptions are a fact of life when interacting with databases, and they are largely 30 | * nondeterministic; whether an operation will succeed or not depends on unpredictable factors like 31 | * network health, the current contents of tables, locking state, and so on. So we must decide 32 | * whether to compute everything in a disjunction like `EitherT[ConnectionIO, Throwable, A]` or 33 | * allow exceptions to propagate until they are caught explicitly. doobie adopts the second 34 | * strategy: exceptions are allowed to propagate and escape unless handled explicitly (exactly as IO 35 | * works). This means when a doobie action (transformed to some target monad) is executed, 36 | * exceptions can escape. 37 | * 38 | * There are three main types of exceptions that are likely to arise: 39 | * 40 | * '''1.''' Various types of `IOException` can happen with any kind of I/O, and these exceptions 41 | * tend to be unrecoverable. 42 | * 43 | * '''2.''' Database exceptions, typically as a generic `SQLException` with a vendor-specific 44 | * `SQLState` identifying the specific error, are raised for common situations such as key 45 | * violations. Some vendors (PostgreSQL for instance) publish a table of error codes, and in these 46 | * cases doobie can provide a matching set of exception-handling combinators. However in most cases 47 | * the error codes must be passed down as folklore or discovered by experimentation. There exist the 48 | * XOPEN and SQL:2003 standards, but it seems that no vendor adheres closely to these 49 | * specifications. Some of these errors are recoverable and others aren’t. 50 | * 51 | * '''3.''' doobie will raise an `InvariantViolation` in response to invalid type mappings, unknown 52 | * JDBC constants returned by drivers, observed `NULL` values, and other violations of invariants 53 | * that doobie assumes. These exceptions indicate programmer error or driver non-compliance and are 54 | * generally unrecoverable. 55 | * 56 | * ==MonadError and Derived Combinators== 57 | * All '''doobie''' monads have associated instances of the `Async` instance, which extends 58 | * `MonadError[?[_], Throwable]`. This means `ConnectionIO`, etc., have the following primitive 59 | * operations: 60 | * 61 | * - `attempt` converts `M[A]` into `M[Either[Throwable, A]]` 62 | * - `fail` constructs an `M[A]` that fails with a provided `Throwable` 63 | * 64 | * So any '''doobie''' program can be lifted into a disjunction simply by adding `.attempt`. 65 | * 66 | * {{{ 67 | * val p = 42.pure[ConnectionIO] 68 | * // p: ConnectionIO[Int] = Pure(42) 69 | * 70 | * p.attempt 71 | * // res0: ConnectionIO[Either[Throwable, Int]] = Suspend( 72 | * // HandleErrorWith( 73 | * // FlatMapped(Pure(42), cats.Monad$$Lambda$8968/1197202183@4abff98c), 74 | * // cats.ApplicativeError$$Lambda$9059/715221672@694b43d5 75 | * // ) 76 | * // ) 77 | * }}} 78 | * 79 | * From the `.attempt` and `fail` combinators we can derive many other operations, as described in 80 | * the Cats documentation. In addition doobie provides the following specialized combinators that 81 | * only pay attention to `SQLException`: 82 | * 83 | * - `attemptSql` is like `attempt` but only traps `SQLException`. 84 | * - `attemptSomeSql` traps only specified `SQLException`s. 85 | * - `exceptSql` recovers from a SQLException with a new action. 86 | * - `onSqlException` executes an action on `SQLException` and discards its result. 87 | * 88 | * And finally we have a set of combinators that focus on SQLStates. 89 | * 90 | * - `attemptSqlState` is like `attemptSql` but yields `M[Either[SQLState, A]]`. 91 | * - `attemptSomeSqlState` traps only specified `SQLState`s. 92 | * - `exceptSqlState` recovers from a `SQLState` with a new action. 93 | * - `exceptSomeSqlState` recovers from specified `SQLState`s with a new action. 94 | * 95 | * @param name 96 | * error_handling 97 | */ 98 | object ErrorHandlingSection extends AnyFlatSpec with Matchers with Section { 99 | 100 | /** 101 | * Let's do some exercises where errors will happen and see how to deal with them. 102 | * 103 | * We're going to work with `person` table again, where the `name` column is marked as being 104 | * unique. 105 | * 106 | * {{{ 107 | * CREATE TABLE IF NOT EXISTS person ( 108 | * id IDENTITY, 109 | * name VARCHAR NOT NULL UNIQUE, 110 | * age INT 111 | * ) 112 | * }}} 113 | * 114 | * Alright, let’s define a way to insert instances. 115 | * 116 | * {{{ 117 | * def insert(n: String, a: Option[Int]): ConnectionIO[Long] = 118 | * sql"insert into person (name, age) values ($n, $a)" 119 | * .update 120 | * .withUniqueGeneratedKeys("id") 121 | * }}} 122 | * 123 | * The following exercises will try to insert two people with the same name. The second operation 124 | * will fail with a unique constraint violation. Let's see how we can avoid this error by using 125 | * several combinators. 126 | * 127 | * A first approach could be to specify the `Throwable` that we want to trap by using `attemptSql` 128 | * combinator. 129 | * 130 | * To make simpler the code we built a method which prepares the database, makes the query and 131 | * transacts it all: 132 | * 133 | * {{{ 134 | * def transactorBlock[A](f: => ConnectionIO[A]): IO[A] = 135 | * transactor.use((createPersonTable *> f).transact[IO]) 136 | * }}} 137 | */ 138 | def safeInsertWithAttemptSome(res0: Either[String, Long]) = { 139 | 140 | def safeInsert(name: String, age: Option[Int]): ConnectionIO[Either[String, Long]] = 141 | insert(name, age).attemptSql.map { 142 | case Left(_) => Left("Oops!") 143 | case Right(value) => Right(value) 144 | } 145 | 146 | val insertedRows = for { 147 | john <- safeInsert("John", Option(35)) 148 | otherJohn <- safeInsert("John", Option(20)) 149 | } yield otherJohn 150 | 151 | val result = transactorBlock(insertedRows).unsafeRunSync() 152 | 153 | result should be(res0) 154 | } 155 | 156 | /** 157 | * If we want to trap a specific `SqlState` like `unique constraint violation`, we'll use the 158 | * `attemptSomeSqlState`. We can specify several `SqlState` values and indicate what value we'll 159 | * return in each case. We can: 160 | * 161 | * - Use the `SqlState` values provided as constants in the contrib-postgresql add-on 162 | * - Create a new `SqlState` value by typing `val UNIQUE_VIOLATION = SqlState("23505")` 163 | */ 164 | def safeInsertWithAttemptSomeSqlState(res0: Either[String, Long]) = { 165 | 166 | def safeInsert(name: String, age: Option[Int]): ConnectionIO[Either[String, Long]] = 167 | insert(name, age) 168 | .attemptSomeSqlState { 169 | case FOREIGN_KEY_VIOLATION => "Another error" 170 | case UNIQUE_VIOLATION => "John is already here!" 171 | } 172 | 173 | val insertedRows = for { 174 | john <- safeInsert("John", Option(35)) 175 | otherJohn <- safeInsert("John", Option(20)) 176 | } yield otherJohn 177 | 178 | val result = transactorBlock(insertedRows).unsafeRunSync() 179 | 180 | result should be(res0) 181 | } 182 | 183 | /** 184 | * Finally we can recover from an error with a new action by using `exceptSqlState`. In this case, 185 | * if the name already exists, we'll insert the person with a different name. 186 | */ 187 | def safeInsertWithExceptSqlState(res0: String, res1: Option[Int]) = { 188 | 189 | def safeInsert(name: String, age: Option[Int]): ConnectionIO[Long] = 190 | insert(name, age) 191 | .exceptSqlState { case UNIQUE_VIOLATION => 192 | insert(name + "_20", age) 193 | } 194 | 195 | val insertedRows = for { 196 | john <- safeInsert("John", Option(35)) 197 | otherJohn <- safeInsert("John", Option(20)) 198 | info <- findPersonById(otherJohn) 199 | } yield info 200 | 201 | val result = transactorBlock(insertedRows).unsafeRunSync() 202 | 203 | result.name should be(res0) 204 | result.age should be(res1) 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/main/scala/doobie/ErrorHandlingSectionHelpers.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2020 47 Degrees Open Source 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package doobielib 18 | 19 | import doobie._ 20 | import doobie.implicits._ 21 | 22 | object ErrorHandlingSectionHelpers { 23 | 24 | val FOREIGN_KEY_VIOLATION = SqlState("23503") 25 | val UNIQUE_VIOLATION = SqlState("23505") 26 | 27 | case class PersonInfo(name: String, age: Option[Int]) 28 | 29 | def insert(s: String, a: Option[Int]): ConnectionIO[Long] = 30 | sql"insert into person (name, age) values ($s, $a)".update 31 | .withUniqueGeneratedKeys("id") 32 | 33 | def findPersonById(id: Long): ConnectionIO[PersonInfo] = 34 | sql"select name, age from person where id=$id" 35 | .query[PersonInfo] 36 | .unique 37 | } 38 | -------------------------------------------------------------------------------- /src/main/scala/doobie/Model.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2020 47 Degrees Open Source 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package doobielib 18 | 19 | object Model { 20 | 21 | case class Country(code: String, name: String, population: Long, gnp: Option[Double]) 22 | 23 | case class Code(code: String) 24 | 25 | case class CountryInfo(name: String, pop: Int, gnp: Option[Double]) 26 | 27 | val countries = List( 28 | Country("DEU", "Germany", 82164700, Option(2133367.00)), 29 | Country("ESP", "Spain", 39441700, None), 30 | Country("FRA", "France", 59225700, Option(1424285.00)), 31 | Country("GBR", "United Kingdom", 59623400, Option(1378330.00)), 32 | Country("USA", "United States of America", 278357000, Option(8510700.00)) 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /src/main/scala/doobie/MultiColumnQueriesSection.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2020 47 Degrees Open Source 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package doobielib 18 | 19 | import doobie.implicits._ 20 | import DoobieUtils.CountryTable._ 21 | import Model._ 22 | import org.scalaexercises.definitions.Section 23 | import org.scalatest.flatspec.AnyFlatSpec 24 | import org.scalatest.matchers.should.Matchers 25 | import shapeless._ 26 | import shapeless.record._ 27 | 28 | /** 29 | * So far, we have constructed queries that return single-column results. These results were mapped 30 | * to Scala types. But how can we deal with multi-column queries? 31 | * 32 | * In this section, we'll see what alternatives '''doobie''' offers us to work with multi-column 33 | * queries. 34 | * 35 | * As in previous sections, we'll keep working with the 'country' table: 36 | * {{{ 37 | * code name population gnp 38 | * "DEU" "Germany" 82164700 2133367.00 39 | * "ESP" "Spain" 39441700 null 40 | * "FRA" "France", 59225700 1424285.00 41 | * "GBR" "United Kingdom" 59623400 1378330.00 42 | * "USA" "United States of America" 278357000 8510700.00 43 | * }}} 44 | * 45 | * To make simpler the code we built a method which prepares the database, makes the query and 46 | * transacts it all: 47 | * 48 | * {{{ 49 | * def transactorBlock[A](f: => ConnectionIO[A]): IO[A] = 50 | * transactor.use((createCountryTable *> insertCountries(countries) *> f).transact[IO]) 51 | * }}} 52 | * 53 | * @param name 54 | * multi_column_queries 55 | */ 56 | object MultiColumnQueriesSection extends AnyFlatSpec with Matchers with Section { 57 | 58 | /** 59 | * We can select multiple columns and map them to a tuple. The `gnp` column in our table is 60 | * nullable so we’ll select that one into an `Option[Double]`. 61 | */ 62 | def selectMultipleColumnsUsingTuple(res0: Option[Double]) = { 63 | 64 | val (name, population, gnp) = 65 | transactorBlock { 66 | sql"select name, population, gnp from country where code = 'ESP'" 67 | .query[(String, Int, Option[Double])] 68 | .unique 69 | }.unsafeRunSync() 70 | 71 | gnp should be(res0) 72 | } 73 | 74 | /** 75 | * doobie automatically supports row mappings for atomic column types, as well as options, tuples, 76 | * `HList`s, shapeless records, and case classes thereof. So let’s try the same query with an 77 | * `HList` 78 | */ 79 | def selectMultipleColumnsUsingHList(res0: String) = { 80 | 81 | type CountryHListType = String :: Int :: Option[Double] :: HNil 82 | 83 | val hlist: CountryHListType = 84 | transactorBlock { 85 | sql"select name, population, gnp from country where code = 'FRA'" 86 | .query[CountryHListType] 87 | .unique 88 | }.unsafeRunSync() 89 | 90 | hlist.head should be(res0) 91 | } 92 | 93 | /** 94 | * And with a shapeless record: 95 | */ 96 | def selectMultipleColumnsUsingRecord(res0: Int) = { 97 | 98 | type Rec = Record.`'name -> String, 'pop -> Int, 'gnp -> Option[Double]`.T 99 | 100 | val record: Rec = 101 | transactorBlock { 102 | sql"select name, population, gnp from country where code = 'USA'" 103 | .query[Rec] 104 | .unique 105 | }.unsafeRunSync() 106 | 107 | record(Symbol("pop")) should be(res0) 108 | } 109 | 110 | /** 111 | * And again, mapping rows to a case class. 112 | * 113 | * {{{ 114 | * case class Country(code: String, name: String, population: Long, gnp: Option[Double]) 115 | * }}} 116 | */ 117 | def selectMultipleColumnsUsingCaseClass(res0: String) = { 118 | 119 | val country = 120 | transactorBlock { 121 | sql"select code, name, population, gnp from country where name = 'United Kingdom'" 122 | .query[Country] 123 | .unique 124 | }.unsafeRunSync() 125 | 126 | country.code should be(res0) 127 | } 128 | 129 | /** 130 | * You can also nest case classes, `HList`s, shapeless records, and/or tuples arbitrarily as long 131 | * as the eventual members are of supported columns types. For instance, here we map the same set 132 | * of columns to a tuple of two case classes: 133 | * 134 | * {{{ 135 | * case class Code(code: String) 136 | * case class CountryInfo(name: String, pop: Int, gnp: Option[Double]) 137 | * }}} 138 | */ 139 | def selectMultipleColumnsUsingNestedCaseClass(res0: String) = { 140 | 141 | val (code, country) = 142 | transactorBlock { 143 | sql"select code, name, population, gnp from country where code = 'ESP'" 144 | .query[(Code, CountryInfo)] 145 | .unique 146 | }.unsafeRunSync() 147 | 148 | country.name should be(res0) 149 | } 150 | 151 | /** 152 | * And just for fun, since the `Code` values are constructed from the primary key, let’s turn the 153 | * results into a `Map`. Trivial but useful. 154 | */ 155 | def selectMultipleColumnsUsingMap(res0: String, res1: Option[CountryInfo]) = { 156 | 157 | val notFoundCountry = CountryInfo("Not Found", 0, None) 158 | 159 | val countriesMap: Map[Code, CountryInfo] = 160 | transactorBlock { 161 | sql"select code, name, population, gnp from country" 162 | .query[(Code, CountryInfo)] 163 | .to[List] 164 | }.unsafeRunSync().toMap 165 | 166 | countriesMap.getOrElse(Code("DEU"), notFoundCountry).name should be(res0) 167 | countriesMap.get(Code("ITA")) should be(res1) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/main/scala/doobie/ParameterizedQueriesSection.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2020 47 Degrees Open Source 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package doobielib 18 | 19 | import cats.data.NonEmptyList 20 | import DoobieUtils.CountryTable._ 21 | import ParameterizedQueryHelpers._ 22 | import org.scalaexercises.definitions.Section 23 | import org.scalatest.flatspec.AnyFlatSpec 24 | import org.scalatest.matchers.should.Matchers 25 | 26 | /** 27 | * Previously we have worked with static SQL queries where the values used to filter data were 28 | * hard-coded and didn't change. 29 | * 30 | * {{{ 31 | * select code, name, population, gnp from country where code = "GBR" 32 | * }}} 33 | * 34 | * In this section, we'll learn how to construct parameterized queries. 35 | * 36 | * We’re still playing with the country table, shown here for reference. 37 | * {{{ 38 | * code name population gnp 39 | * "DEU" "Germany" 82164700 2133367.00 40 | * "ESP" "Spain" 39441700 null 41 | * "FRA" "France", 59225700 1424285.00 42 | * "GBR" "United Kingdom" 59623400 1378330.00 43 | * "USA" "United States of America" 278357000 8510700.00 44 | * }}} 45 | * 46 | * To make simpler the code we built a method which prepares the database, makes the query and 47 | * transacts it all: 48 | * {{{ 49 | * def transactorBlock[A](f: => ConnectionIO[A]): IO[A] = 50 | * transactor.use((createCountryTable *> insertCountries(countries) *> f).transact[IO]) 51 | * }}} 52 | * 53 | * @param name 54 | * parameterized_queries 55 | */ 56 | object ParameterizedQueriesSection extends AnyFlatSpec with Matchers with Section { 57 | 58 | /** 59 | * ==Adding a Parameter== 60 | * 61 | * Let’s factor our query into a method and add a parameter that selects only the countries with a 62 | * population larger than some value the user will provide. We insert the minPop argument into our 63 | * SQL statement as $minPop, just as if we were doing string interpolation. 64 | * {{{ 65 | * def biggerThan(minPop: Int) = 66 | * sql""" 67 | * select code, name, population, gnp 68 | * from country 69 | * where population > $minPop 70 | * order by population asc 71 | * """.query[Country] 72 | * }}} 73 | */ 74 | def addingAParameter(res0: String, res1: String) = { 75 | 76 | val countriesName = transactorBlock(biggerThan(75000000).to[List]) 77 | .unsafeRunSync() 78 | .map(_.name) 79 | 80 | countriesName should be(List(res0, res1)) 81 | } 82 | 83 | /** 84 | * So what’s going on? It looks like we’re just dropping a string literal into our SQL string, but 85 | * actually we’re constructing a proper parameterized PreparedStatement, and the minProp value is 86 | * ultimately set via a call to setInt 87 | * 88 | * '''doobie''' allows you to interpolate values of any type (and options thereof) with a `Put` 89 | * instance, which includes: 90 | * - any JVM type that has a target mapping defined by the JDBC specification, 91 | * - vendor-specific types defined by extension packages, 92 | * - custom column types that you define, and 93 | * - single-member products (case classes, typically) of any of the above. 94 | * 95 | * ==Multiple Parameters== 96 | * 97 | * Multiple parameters work the same way. 98 | * {{{ 99 | * def populationIn(range: Range) = 100 | * sql""" 101 | * select code, name, population, gnp 102 | * from country 103 | * where population > ${range.min} and population < ${range.max} 104 | * order by population asc 105 | * """.query[Country] 106 | * }}} 107 | */ 108 | def addingMultipleParameters(res0: String, res1: String, res2: String) = { 109 | 110 | val countriesName = transactorBlock(populationIn(25000000 to 75000000).to[List]) 111 | .unsafeRunSync() 112 | .map(_.name) 113 | 114 | countriesName should be(List(res0, res1, res2)) 115 | } 116 | 117 | /** 118 | * ==Dealing with IN Clauses== 119 | * 120 | * A common irritant when dealing with SQL literals is the desire to inline a sequence of 121 | * arguments into an IN clause, but SQL does not support this notion (nor does JDBC do anything to 122 | * assist). doobie supports this via statement fragments. 123 | * {{{ 124 | * def populationIn(range: Range, codes: NonEmptyList[String]) = { 125 | * val q = fr""" 126 | * select code, name, population, gnp 127 | * from country 128 | * where population > ${range.min} 129 | * and population < ${range.max} 130 | * and """ ++ Fragments.in(fr"code", codes) // code IN (...) 131 | * q.query[Country] 132 | * } 133 | * }}} 134 | * 135 | * Note that the `IN` clause must be non-empty, so `codes` is a `NonEmptyList`. 136 | */ 137 | def dealingWithInClause(res0: String, res1: String) = { 138 | 139 | val countriesName = transactorBlock( 140 | populationIn(25000000 to 75000000, NonEmptyList.of("ESP", "USA", "FRA")) 141 | .to[List] 142 | ).unsafeRunSync() 143 | .map(_.name) 144 | 145 | countriesName should be(List(res0, res1)) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/scala/doobie/ParameterizedQueryHelpers.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2020 47 Degrees Open Source 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package doobielib 18 | 19 | import cats.data._ 20 | import doobie._ 21 | import doobie.implicits._ 22 | import Model.Country 23 | 24 | object ParameterizedQueryHelpers { 25 | 26 | def biggerThan(minPop: Int) = 27 | sql"select code, name, population, gnp from country where population > $minPop order by population asc" 28 | .query[Country] 29 | 30 | def populationIn(range: Range) = 31 | sql"select code, name, population, gnp from country where population > ${range.min} and population < ${range.max} order by population asc" 32 | .query[Country] 33 | 34 | def populationIn(range: Range, codes: NonEmptyList[String]) = { 35 | val q = fr""" 36 | select code, name, population, gnp 37 | from country 38 | where population > ${range.min} 39 | and population < ${range.max} 40 | and """ ++ Fragments.in(fr"code", codes) // code IN (...) 41 | q.query[Country] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/scala/doobie/SelectingDataSection.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2020 47 Degrees Open Source 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package doobielib 18 | 19 | import doobie.implicits._ 20 | import DoobieUtils.CountryTable._ 21 | import org.scalaexercises.definitions.Section 22 | import org.scalatest.flatspec.AnyFlatSpec 23 | import org.scalatest.matchers.should.Matchers 24 | 25 | /** 26 | * We are going to construct some programs that retrieve data from the database and stream it back, 27 | * mapping to Scala types on the way. 28 | * 29 | * We will be playing with the country table that has the following structure: 30 | * {{{ 31 | * CREATE TABLE country ( 32 | * code character(3) NOT NULL, 33 | * name text NOT NULL, 34 | * population integer NOT NULL, 35 | * gnp numeric(10,2) 36 | * ) 37 | * }}} 38 | * 39 | * For the exercises, the ''country'' table will contain: 40 | * {{{ 41 | * code name population gnp 42 | * "DEU" "Germany" 82164700 2133367.00 43 | * "ESP" "Spain" 39441700 null 44 | * "FRA" "France" 59225700 1424285.00 45 | * "GBR" "United Kingdom" 59623400 1378330.00 46 | * "USA" "United States of America" 278357000 8510700.00 47 | * }}} 48 | * 49 | * ==How to select data== 50 | * 51 | * As we commented in the previous section, the `sql` string interpolator allows us to create a 52 | * query to select data from the database. 53 | * 54 | * For instance, `sql"select name from country".query[String]` defines a `Query0[String]`, which is 55 | * a one-column query that maps each returned row to a String. 56 | * 57 | * `.to[List]` is a convenience method that accumulates rows into a `List`, in this case yielding a 58 | * `ConnectionIO[List[String]]`. It works with any collection type that has a `CanBuildFrom`. 59 | * Similar methods are: 60 | * - `.unique` which returns a single value, raising an exception if there is not exactly one row 61 | * returned. 62 | * - `.option` which returns an `Option`, raising an exception if there is more than one row 63 | * returned. 64 | * - `.nel` which returns a `NonEmptyList`, raising an exception if there are no rows returned. 65 | * See the Scaladoc for Query0 for more information on these and other methods. 66 | * 67 | * @param name 68 | * selecting_data 69 | */ 70 | object SelectingDataSection extends AnyFlatSpec with Matchers with Section { 71 | 72 | /** 73 | * ==Getting info about the countries== 74 | * 75 | * To make simpler the code we built a method which prepares the database, makes the query and 76 | * transacts it all: 77 | * 78 | * {{{ 79 | * def transactorBlock[A](f: => ConnectionIO[A]): IO[A] = 80 | * transactor.use((createCountryTable *> insertCountries(countries) *> f).transact[IO]) 81 | * }}} 82 | * 83 | * We can use the `unique` method if we expect the query to return only one row 84 | */ 85 | def selectUniqueCountryName(res0: String) = { 86 | 87 | val countryName = 88 | transactorBlock(sql"select name from COUNTRY where code = 'ESP'".query[String].unique) 89 | .unsafeRunSync() 90 | 91 | countryName should be(res0) 92 | } 93 | 94 | /** 95 | * If we are not sure if the record exists, we can use the `option` method. 96 | */ 97 | def selectOptionalCountryName(res0: Option[String]) = { 98 | 99 | val maybeCountryName = 100 | transactorBlock(sql"select name from country where code = 'ITA'".query[String].option) 101 | .unsafeRunSync() 102 | 103 | maybeCountryName should be(res0) 104 | } 105 | 106 | /** 107 | * When the query can return more than one row, we can use the `list` to accumulate the results in 108 | * a List. 109 | */ 110 | def selectCountryNameList(res0: String) = { 111 | 112 | val countryNames = 113 | transactorBlock { 114 | sql"select name from country order by name".query[String].to[List] 115 | }.unsafeRunSync() 116 | 117 | countryNames.head should be(res0) 118 | } 119 | 120 | /** 121 | * This is ok, but there’s not much point reading all the results from the database when we only 122 | * want the first few rows. 123 | * 124 | * The difference here is that stream gives us an fs2 Stream[ConnectionIO, String] that emits rows 125 | * as they arrive from the database. By applying take(5) we instruct the stream to shut everything 126 | * down (and clean everything up) after five elements have been emitted. This is much more 127 | * efficient than pulling all 239 rows and then throwing most of them away. 128 | */ 129 | def selectCountryNameListByUsingProcess(res0: Int) = { 130 | 131 | val countryNames = 132 | transactorBlock { 133 | sql"select name from country order by name".query[String].stream.take(3).compile.toList 134 | }.unsafeRunSync() 135 | 136 | countryNames.size should be(res0) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/scala/doobie/UpdatesSection.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2020 47 Degrees Open Source 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package doobielib 18 | 19 | import cats.implicits._ 20 | import DoobieUtils.PersonTable._ 21 | import UpdatesSectionHelpers._ 22 | import doobie._ 23 | import doobie.implicits._ 24 | import UpdatesSectionHelpers.Person 25 | import org.scalaexercises.definitions.Section 26 | import org.scalatest.flatspec.AnyFlatSpec 27 | import org.scalatest.matchers.should.Matchers 28 | 29 | /** 30 | * In this section we examine operations that modify data in the database, and ways to retrieve the 31 | * results of these updates. 32 | * 33 | * ==Data Definition== 34 | * It is uncommon to define database structures at runtime, but '''doobie''' handles it just fine 35 | * and treats such operations like any other kind of update. And it happens to be useful here! 36 | * 37 | * Let’s create a new table, which we will use for the exercises to follow. This looks a lot like 38 | * our prior usage of the `sql` interpolator, but this time we’re using `update` rather than 39 | * `query`. The `.run` method gives a `ConnectionIO[Int]` that yields the total number of rows 40 | * modified. 41 | * 42 | * {{{ 43 | * val drop: Update0 = 44 | * sql""" 45 | * DROP TABLE IF EXISTS person 46 | * """.update 47 | * 48 | * val create: Update0 = 49 | * sql""" 50 | * CREATE TABLE person ( 51 | * id SERIAL, 52 | * name VARCHAR NOT NULL UNIQUE, 53 | * age SMALLINT 54 | * ) 55 | * """.update 56 | * }}} 57 | * 58 | * We can compose these and run them together. 59 | * {{{ 60 | * (drop, create).mapN(_ + _).transact(xa).unsafeRunSync 61 | * // res0: Int = 0 62 | * }}} 63 | * 64 | * ==Inserting== 65 | * Inserting is straightforward and works just as with selects. Here we define a method that 66 | * constructs an `Update0` that inserts a row into the `person` table. 67 | * 68 | * {{{ 69 | * def insert1(name: String, age: Option[Short]): Update0 = 70 | * sql"insert into person (name, age) values ($name, $age)".update 71 | * }}} 72 | * 73 | * @param name 74 | * inserting_and_updating 75 | */ 76 | object UpdatesSection extends AnyFlatSpec with Matchers with Section { 77 | 78 | /** 79 | * Let's insert a new row by using the recently defined `insert1` method. 80 | * 81 | * To make simpler the code we built a method which prepares the database, makes the query and 82 | * transacts it all: 83 | * 84 | * {{{ 85 | * def transactorBlock[A](f: => ConnectionIO[A]): IO[A] = 86 | * transactor.use((createPersonTable *> f).transact[IO]) 87 | * }}} 88 | */ 89 | def insertOneRow(res0: Int) = { 90 | 91 | val insertedRows = transactorBlock(insert1("John", Option(35)).run).unsafeRunSync() 92 | 93 | insertedRows should be(res0) 94 | } 95 | 96 | /** 97 | * On the contrary, if we want to insert several rows, there are different ways to do that. A 98 | * first try could be to use a `for-comprehension` to compose all the single operations. 99 | */ 100 | def insertSeveralRowsWithForComprehension(res0: Int) = { 101 | 102 | val rows = for { 103 | row1 <- insert1("Alice", Option(12)).run 104 | row2 <- insert1("Bob", None).run 105 | row3 <- insert1("John", Option(17)).run 106 | } yield row1 + row2 + row3 107 | 108 | val insertedRows = transactorBlock(rows).unsafeRunSync() 109 | 110 | insertedRows should be(res0) 111 | } 112 | 113 | /** 114 | * If there is no dependency between the SQL operations, it could be better to use an applicative 115 | * functor. 116 | */ 117 | def insertSeveralRowsWithApplicativeFunctor(res0: Int) = { 118 | 119 | val insertedOnePerson = insert1("Alice", Option(12)).run 120 | 121 | val insertedOtherPerson = insert1("Bob", None).run 122 | 123 | val insertedRows = 124 | transactorBlock((insertedOnePerson, insertedOtherPerson).mapN(_ + _)).unsafeRunSync() 125 | 126 | insertedRows should be(res0) 127 | } 128 | 129 | /** 130 | * If all the data to be inserted is represented by a `List`, other way could be by using the 131 | * Scalaz `traverse` method. 132 | */ 133 | def insertSeveralRowsWithTraverse(res0: Int) = { 134 | 135 | val people = 136 | List(("Alice", Option(12)), ("Bob", None), ("John", Option(17)), ("Mary", Option(16))) 137 | 138 | val insertedRows = 139 | transactorBlock(people.traverse(item => (insert1 _).tupled(item).run)).unsafeRunSync() 140 | 141 | insertedRows.sum should be(res0) 142 | } 143 | 144 | /** 145 | * ==Updating== 146 | * Updating follows the same pattern. For instance, we suppose that we want to modify the age of a 147 | * person. 148 | */ 149 | def updateExistingRow(res0: Int, res1: Int, res2: Int) = { 150 | 151 | val result = for { 152 | insertedRows <- insert1("Alice", Option(12)).run 153 | updatedRows <- sql"update person set age = 15 where name = 'Alice'".update.run 154 | person <- sql"select id, name, age from person where name = 'Alice'".query[Person].unique 155 | } yield (insertedRows, updatedRows, person) 156 | 157 | val (insertedRows, updatedRows, person) = transactorBlock(result).unsafeRunSync() 158 | 159 | insertedRows should be(res0) 160 | updatedRows should be(res1) 161 | person.age should be(Option(res2)) 162 | } 163 | 164 | /** 165 | * ==Retrieving info== 166 | * When we insert we usually want the new row back, so let’s do that. First we’ll do it the hard 167 | * way, by inserting, getting the last used key via `lastVal()`, then selecting the indicated row. 168 | * {{{ 169 | * def insert2(name: String, age: Option[Short]): ConnectionIO[Person] = 170 | * for { 171 | * _ <- sql"insert into person (name, age) values ($name, $age)".update.run 172 | * id <- sql"select lastval()".query[Long].unique 173 | * p <- sql"select id, name, age from person where id = $id".query[Person].unique 174 | * } yield p 175 | * }}} 176 | * 177 | * This is irritating but it is supported by all databases (although the “get the last used id” 178 | * function will vary by vendor). 179 | * 180 | * Some database (like H2) allow you to return [only] the inserted id, allowing the above 181 | * operation to be reduced to two statements (see below for an explanation of 182 | * `withUniqueGeneratedKeys`). 183 | */ 184 | def retrieveInfo(res0: String, res1: Int) = { 185 | 186 | def insert2_H2(name: String, age: Option[Int]): ConnectionIO[Person] = 187 | for { 188 | id <- 189 | sql"insert into person (name, age) values ($name, $age)".update 190 | .withUniqueGeneratedKeys[Int]("id") 191 | p <- sql"select id, name, age from person where id = $id".query[Person].unique 192 | } yield p 193 | 194 | val person = transactorBlock(insert2_H2("Ramone", Option(42))).unsafeRunSync() 195 | 196 | person.name should be(res0) 197 | person.age should be(Option(res1)) 198 | } 199 | 200 | /** 201 | * Other databases (including PostgreSQL) provide a way to do this in one shot by returning 202 | * multiple specified columns from the inserted row. 203 | * 204 | * {{{ 205 | * def insert3(name: String, age: Option[Int]): ConnectionIO[Person] = { 206 | * sql"insert into person (name, age) values ($name, $age)" 207 | * .update.withUniqueGeneratedKeys("id", "name", "age") 208 | * } 209 | * }}} 210 | * 211 | * The `withUniqueGeneratedKeys` specifies that we expect exactly one row back (otherwise an 212 | * exception will be thrown), and requires a list of columns to return. 213 | * 214 | * This mechanism also works for updates, for databases that support it. In the case of multiple 215 | * row updates we use `withGeneratedKeys` and get a `Stream[ConnectionIO, Person]` back. 216 | * 217 | * {{{ 218 | * val up = { 219 | * sql"update person set age = age + 1 where age is not null" 220 | * .update 221 | * .withGeneratedKeys[Person]("id", "name", "age") 222 | * } 223 | * }}} 224 | * 225 | * ==Batch Updates== 226 | * '''doobie''' supports batch updating via the `updateMany` and `updateManyWithGeneratedKeys` 227 | * operations on the `Update` data type. An `Update0`, which is the type of an sql"...".update 228 | * expression, represents a parameterized statement where the arguments are known. An `Update[A]` 229 | * is more general, and represents a parameterized statement where the composite argument of type 230 | * `A` is not known. 231 | */ 232 | def batchUpdates(res0: Int) = { 233 | type PersonInfo = (String, Option[Short]) 234 | 235 | def insertMany(ps: List[PersonInfo]): ConnectionIO[Int] = { 236 | val sql = "insert into person (name, age) values (?, ?)" 237 | Update[PersonInfo](sql).updateMany(ps) 238 | } 239 | 240 | // Some rows to insert 241 | val data = List[PersonInfo](("Frank", Some(12)), ("Daddy", None)) 242 | 243 | val insertedRows = transactorBlock(insertMany(data)).unsafeRunSync() 244 | 245 | insertedRows should be(res0) 246 | } 247 | 248 | } 249 | -------------------------------------------------------------------------------- /src/main/scala/doobie/UpdatesSectionHelpers.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2020 47 Degrees Open Source 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package doobielib 18 | 19 | import doobie._ 20 | import doobie.implicits._ 21 | 22 | object UpdatesSectionHelpers { 23 | 24 | case class Person(id: Long, name: String, age: Option[Short]) 25 | 26 | def insert1(name: String, age: Option[Int]): Update0 = 27 | sql""" 28 | INSERT INTO person (name, age) VALUES ($name, $age) 29 | """.update 30 | } 31 | -------------------------------------------------------------------------------- /src/test/scala/doobie/ConnectingToDatabaseSectionSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2020 47 Degrees Open Source 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package doobielib 18 | 19 | import org.scalacheck.ScalacheckShapeless._ 20 | import org.scalaexercises.Test 21 | import org.scalatest.refspec.RefSpec 22 | import org.scalatestplus.scalacheck.Checkers 23 | import shapeless.HNil 24 | 25 | class ConnectingToDatabaseSectionSpec extends RefSpec with Checkers { 26 | 27 | def `compute constant value` = { 28 | check( 29 | Test.testSuccess( 30 | ConnectingToDatabaseSection.constantValue _, 31 | 42 :: HNil 32 | ) 33 | ) 34 | } 35 | 36 | def `compute constant value from database` = { 37 | check( 38 | Test.testSuccess( 39 | ConnectingToDatabaseSection.constantValueFromDatabase _, 40 | 42 :: HNil 41 | ) 42 | ) 43 | } 44 | 45 | def `combine two small programs` = { 46 | check( 47 | Test.testSuccess( 48 | ConnectingToDatabaseSection.combineTwoPrograms _, 49 | (42, 25) :: HNil 50 | ) 51 | ) 52 | } 53 | 54 | def `combine two small programs with applicative` = { 55 | check( 56 | Test.testSuccess( 57 | ConnectingToDatabaseSection.combineTwoProgramsWithApplicative _, 58 | 67 :: HNil 59 | ) 60 | ) 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/test/scala/doobie/ErrorHandlingSectionSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2020 47 Degrees Open Source 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package doobielib 18 | 19 | import org.scalacheck.ScalacheckShapeless._ 20 | import org.scalaexercises.Test 21 | import org.scalatest.refspec.RefSpec 22 | import org.scalatestplus.scalacheck.Checkers 23 | import shapeless._ 24 | 25 | class ErrorHandlingSectionSpec extends RefSpec with Checkers { 26 | 27 | def `safe insert with attemptSome` = { 28 | check( 29 | Test.testSuccess( 30 | ErrorHandlingSection.safeInsertWithAttemptSome _, 31 | (Left("Oops!"): Either[String, Long]) :: HNil 32 | ) 33 | ) 34 | } 35 | 36 | def `safe insert with attemptSomeSqlState` = { 37 | check( 38 | Test.testSuccess( 39 | ErrorHandlingSection.safeInsertWithAttemptSomeSqlState _, 40 | (Left("John is already here!"): Either[String, Long]) :: HNil 41 | ) 42 | ) 43 | } 44 | 45 | def `safe insert with exceptSqlState` = { 46 | check( 47 | Test.testSuccess( 48 | ErrorHandlingSection.safeInsertWithExceptSqlState _, 49 | "John_20" :: Option(20) :: HNil 50 | ) 51 | ) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/scala/doobie/MultiColumnQueriesSectionSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2020 47 Degrees Open Source 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package doobielib 18 | 19 | import org.scalacheck.ScalacheckShapeless._ 20 | import Model.CountryInfo 21 | import org.scalacheck.{Arbitrary, Gen} 22 | import org.scalaexercises.Test 23 | import org.scalatest.refspec.RefSpec 24 | import org.scalatestplus.scalacheck.Checkers 25 | import shapeless.HNil 26 | 27 | class MultiColumnQueriesSectionSpec extends RefSpec with Checkers { 28 | 29 | implicit val countryInfoArbitrary: Arbitrary[CountryInfo] = Arbitrary { 30 | for { 31 | name <- Gen.identifier 32 | pop <- Gen.posNum[Int] 33 | gnp <- Gen.option(Gen.posNum[Double]) 34 | } yield CountryInfo(name, pop, gnp) 35 | } 36 | 37 | def `select multiple columns using tuple` = { 38 | val validResult: Option[Double] = None 39 | check( 40 | Test.testSuccess( 41 | MultiColumnQueriesSection.selectMultipleColumnsUsingTuple _, 42 | validResult :: HNil 43 | ) 44 | ) 45 | } 46 | 47 | def `select multiple columns using HList` = { 48 | check( 49 | Test.testSuccess( 50 | MultiColumnQueriesSection.selectMultipleColumnsUsingHList _, 51 | "France" :: HNil 52 | ) 53 | ) 54 | } 55 | 56 | def `select multiple columns using Record` = { 57 | check( 58 | Test.testSuccess( 59 | MultiColumnQueriesSection.selectMultipleColumnsUsingRecord _, 60 | 278357000 :: HNil 61 | ) 62 | ) 63 | } 64 | 65 | def `select multiple columns using case class` = { 66 | check( 67 | Test.testSuccess( 68 | MultiColumnQueriesSection.selectMultipleColumnsUsingCaseClass _, 69 | "GBR" :: HNil 70 | ) 71 | ) 72 | } 73 | 74 | def `select multiple columns using nested case class` = { 75 | check( 76 | Test.testSuccess( 77 | MultiColumnQueriesSection.selectMultipleColumnsUsingNestedCaseClass _, 78 | "Spain" :: HNil 79 | ) 80 | ) 81 | } 82 | 83 | def `select multiple columns using map` = { 84 | val validResult: Option[CountryInfo] = None 85 | check( 86 | Test.testSuccess( 87 | MultiColumnQueriesSection.selectMultipleColumnsUsingMap _, 88 | "Germany" :: validResult :: HNil 89 | ) 90 | ) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/test/scala/doobie/ParameterizedQueriesSectionSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2020 47 Degrees Open Source 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package doobielib 18 | 19 | import org.scalacheck.ScalacheckShapeless._ 20 | import org.scalaexercises.Test 21 | import org.scalatest.refspec.RefSpec 22 | import org.scalatestplus.scalacheck.Checkers 23 | import shapeless.HNil 24 | 25 | class ParameterizedQueriesSectionSpec extends RefSpec with Checkers { 26 | 27 | def `adding a parameter` = { 28 | check( 29 | Test.testSuccess( 30 | ParameterizedQueriesSection.addingAParameter _, 31 | "Germany" :: "United States of America" :: HNil 32 | ) 33 | ) 34 | } 35 | 36 | def `adding multiple parameters` = { 37 | check( 38 | Test.testSuccess( 39 | ParameterizedQueriesSection.addingMultipleParameters _, 40 | "Spain" :: "France" :: "United Kingdom" :: HNil 41 | ) 42 | ) 43 | } 44 | 45 | def `dealing with IN clause` = { 46 | check( 47 | Test.testSuccess( 48 | ParameterizedQueriesSection.dealingWithInClause _, 49 | "Spain" :: "France" :: HNil 50 | ) 51 | ) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/scala/doobie/SelectingDataSectionSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2020 47 Degrees Open Source 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package doobielib 18 | 19 | import org.scalacheck.ScalacheckShapeless._ 20 | import org.scalaexercises.Test 21 | import org.scalatest.BeforeAndAfterEach 22 | import org.scalatest.refspec.RefSpec 23 | import org.scalatestplus.scalacheck.Checkers 24 | import shapeless.HNil 25 | 26 | class SelectingDataSectionSpec extends RefSpec with Checkers with BeforeAndAfterEach { 27 | 28 | def `select country name list` = { 29 | check( 30 | Test.testSuccess( 31 | SelectingDataSection.selectCountryNameList _, 32 | "France" :: HNil 33 | ) 34 | ) 35 | } 36 | 37 | def `select country name list by using process` = { 38 | check( 39 | Test.testSuccess( 40 | SelectingDataSection.selectCountryNameListByUsingProcess _, 41 | 3 :: HNil 42 | ) 43 | ) 44 | } 45 | 46 | def `select optional country name` = { 47 | val value: Option[String] = None 48 | check( 49 | Test.testSuccess( 50 | SelectingDataSection.selectOptionalCountryName _, 51 | value :: HNil 52 | ) 53 | ) 54 | } 55 | 56 | def `select unique country name` = { 57 | check( 58 | Test.testSuccess( 59 | SelectingDataSection.selectUniqueCountryName _, 60 | "Spain" :: HNil 61 | ) 62 | ) 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/test/scala/doobie/UpdatesSectionSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2020 47 Degrees Open Source 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package doobielib 18 | 19 | import org.scalacheck.ScalacheckShapeless._ 20 | import org.scalaexercises.Test 21 | import org.scalatest.refspec.RefSpec 22 | import org.scalatestplus.scalacheck.Checkers 23 | import shapeless.HNil 24 | 25 | class UpdatesSectionSpec extends RefSpec with Checkers { 26 | 27 | def `insert one row` = { 28 | check( 29 | Test.testSuccess( 30 | UpdatesSection.insertOneRow _, 31 | 1 :: HNil 32 | ) 33 | ) 34 | } 35 | 36 | def `insert several rows by using for-comprehension` = { 37 | check( 38 | Test.testSuccess( 39 | UpdatesSection.insertSeveralRowsWithForComprehension _, 40 | 3 :: HNil 41 | ) 42 | ) 43 | } 44 | 45 | def `insert several rows by using applicative functor` = { 46 | check( 47 | Test.testSuccess( 48 | UpdatesSection.insertSeveralRowsWithApplicativeFunctor _, 49 | 2 :: HNil 50 | ) 51 | ) 52 | } 53 | 54 | def `insert several rows by using traverse` = { 55 | check( 56 | Test.testSuccess( 57 | UpdatesSection.insertSeveralRowsWithTraverse _, 58 | 4 :: HNil 59 | ) 60 | ) 61 | } 62 | 63 | def `update an existing row` = { 64 | check( 65 | Test.testSuccess( 66 | UpdatesSection.updateExistingRow _, 67 | 1 :: 1 :: 15 :: HNil 68 | ) 69 | ) 70 | } 71 | 72 | def `retrieve info` = { 73 | check( 74 | Test.testSuccess( 75 | UpdatesSection.retrieveInfo _, 76 | "Ramone" :: 42 :: HNil 77 | ) 78 | ) 79 | } 80 | 81 | def `batch updates` = { 82 | check( 83 | Test.testSuccess( 84 | UpdatesSection.batchUpdates _, 85 | 2 :: HNil 86 | ) 87 | ) 88 | } 89 | } 90 | --------------------------------------------------------------------------------