git.iko.soy/typeable/postgresql-simple
on master help sign in
.ghcideleted−16
since splitsnapshots
unifiedsplit
@@ -1,16 +0,0 @@
1−:set -isrc -itest -optP-includedist/build/autogen/cabal_macros.h
2−:set -Wall -fno-warn-name-shadowing
3−
4−:set -XDoAndIfThenElse
5−:set -XOverloadedStrings
6−:set -XBangPatterns
7−:set -XViewPatterns
8−:set -XTypeOperators
9−
10−-- for tests
11−:set -XNamedFieldPuns
12−:set -XRank2Types
13−:set -XRecordWildCards
14−:set -XPatternGuards
15−:set -XScopedTypeVariables
16−:set -fno-warn-unused-do-bind
.github/workflows/haskell-ci.ymlnew file+256
@@ -0,0 +1,256 @@
1+# This GitHub workflow config has been generated by a script via
2+#
3+# haskell-ci 'github' 'cabal.project'
4+#
5+# To regenerate the script (for example after adjusting tested-with) run
6+#
7+# haskell-ci regenerate
8+#
9+# For more information, see https://github.com/haskell-CI/haskell-ci
10+#
11+# version: 0.17.20231110
12+#
13+# REGENDATA ("0.17.20231110",["github","cabal.project"])
14+#
15+name: Haskell-CI
16+on:
17+ push:
18+ branches:
19+ - master
20+ pull_request:
21+ branches:
22+ - master
23+jobs:
24+ linux:
25+ name: Haskell-CI - Linux - ${{ matrix.compiler }}
26+ runs-on: ubuntu-20.04
27+ timeout-minutes:
28+ 60
29+ container:
30+ image: buildpack-deps:bionic
31+ services:
32+ postgres:
33+ image: postgres:14
34+ env:
35+ POSTGRES_PASSWORD: postgres
36+ options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
37+ continue-on-error: ${{ matrix.allow-failure }}
38+ strategy:
39+ matrix:
40+ include:
41+ - compiler: ghc-9.8.1
42+ compilerKind: ghc
43+ compilerVersion: 9.8.1
44+ setup-method: ghcup
45+ allow-failure: false
46+ - compiler: ghc-9.6.3
47+ compilerKind: ghc
48+ compilerVersion: 9.6.3
49+ setup-method: ghcup
50+ allow-failure: false
51+ - compiler: ghc-9.4.8
52+ compilerKind: ghc
53+ compilerVersion: 9.4.8
54+ setup-method: ghcup
55+ allow-failure: false
56+ - compiler: ghc-9.2.8
57+ compilerKind: ghc
58+ compilerVersion: 9.2.8
59+ setup-method: ghcup
60+ allow-failure: false
61+ - compiler: ghc-9.0.2
62+ compilerKind: ghc
63+ compilerVersion: 9.0.2
64+ setup-method: ghcup
65+ allow-failure: false
66+ - compiler: ghc-8.10.7
67+ compilerKind: ghc
68+ compilerVersion: 8.10.7
69+ setup-method: ghcup
70+ allow-failure: false
71+ - compiler: ghc-8.8.4
72+ compilerKind: ghc
73+ compilerVersion: 8.8.4
74+ setup-method: hvr-ppa
75+ allow-failure: false
76+ - compiler: ghc-8.6.5
77+ compilerKind: ghc
78+ compilerVersion: 8.6.5
79+ setup-method: hvr-ppa
80+ allow-failure: false
81+ fail-fast: false
82+ steps:
83+ - name: apt
84+ run: |
85+ apt-get update
86+ apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5
87+ if [ "${{ matrix.setup-method }}" = ghcup ]; then
88+ mkdir -p "$HOME/.ghcup/bin"
89+ curl -sL https://downloads.haskell.org/ghcup/0.1.20.0/x86_64-linux-ghcup-0.1.20.0 > "$HOME/.ghcup/bin/ghcup"
90+ chmod a+x "$HOME/.ghcup/bin/ghcup"
91+ "$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" || (cat "$HOME"/.ghcup/logs/*.* && false)
92+ "$HOME/.ghcup/bin/ghcup" install cabal 3.10.2.0 || (cat "$HOME"/.ghcup/logs/*.* && false)
93+ else
94+ apt-add-repository -y 'ppa:hvr/ghc'
95+ apt-get update
96+ apt-get install -y "$HCNAME"
97+ mkdir -p "$HOME/.ghcup/bin"
98+ curl -sL https://downloads.haskell.org/ghcup/0.1.20.0/x86_64-linux-ghcup-0.1.20.0 > "$HOME/.ghcup/bin/ghcup"
99+ chmod a+x "$HOME/.ghcup/bin/ghcup"
100+ "$HOME/.ghcup/bin/ghcup" install cabal 3.10.2.0 || (cat "$HOME"/.ghcup/logs/*.* && false)
101+ fi
102+ env:
103+ HCKIND: ${{ matrix.compilerKind }}
104+ HCNAME: ${{ matrix.compiler }}
105+ HCVER: ${{ matrix.compilerVersion }}
106+ - name: Set PATH and environment variables
107+ run: |
108+ echo "$HOME/.cabal/bin" >> $GITHUB_PATH
109+ echo "LANG=C.UTF-8" >> "$GITHUB_ENV"
110+ echo "CABAL_DIR=$HOME/.cabal" >> "$GITHUB_ENV"
111+ echo "CABAL_CONFIG=$HOME/.cabal/config" >> "$GITHUB_ENV"
112+ HCDIR=/opt/$HCKIND/$HCVER
113+ if [ "${{ matrix.setup-method }}" = ghcup ]; then
114+ HC=$("$HOME/.ghcup/bin/ghcup" whereis ghc "$HCVER")
115+ HCPKG=$(echo "$HC" | sed 's#ghc$#ghc-pkg#')
116+ HADDOCK=$(echo "$HC" | sed 's#ghc$#haddock#')
117+ echo "HC=$HC" >> "$GITHUB_ENV"
118+ echo "HCPKG=$HCPKG" >> "$GITHUB_ENV"
119+ echo "HADDOCK=$HADDOCK" >> "$GITHUB_ENV"
120+ echo "CABAL=$HOME/.ghcup/bin/cabal-3.10.2.0 -vnormal+nowrap" >> "$GITHUB_ENV"
121+ else
122+ HC=$HCDIR/bin/$HCKIND
123+ echo "HC=$HC" >> "$GITHUB_ENV"
124+ echo "HCPKG=$HCDIR/bin/$HCKIND-pkg" >> "$GITHUB_ENV"
125+ echo "HADDOCK=$HCDIR/bin/haddock" >> "$GITHUB_ENV"
126+ echo "CABAL=$HOME/.ghcup/bin/cabal-3.10.2.0 -vnormal+nowrap" >> "$GITHUB_ENV"
127+ fi
128+
129+ HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')
130+ echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV"
131+ echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV"
132+ echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV"
133+ echo "HEADHACKAGE=false" >> "$GITHUB_ENV"
134+ echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV"
135+ echo "GHCJSARITH=0" >> "$GITHUB_ENV"
136+ env:
137+ HCKIND: ${{ matrix.compilerKind }}
138+ HCNAME: ${{ matrix.compiler }}
139+ HCVER: ${{ matrix.compilerVersion }}
140+ - name: env
141+ run: |
142+ env
143+ - name: write cabal config
144+ run: |
145+ mkdir -p $CABAL_DIR
146+ cat >> $CABAL_CONFIG <<EOF
147+ remote-build-reporting: anonymous
148+ write-ghc-environment-files: never
149+ remote-repo-cache: $CABAL_DIR/packages
150+ logs-dir: $CABAL_DIR/logs
151+ world-file: $CABAL_DIR/world
152+ extra-prog-path: $CABAL_DIR/bin
153+ symlink-bindir: $CABAL_DIR/bin
154+ installdir: $CABAL_DIR/bin
155+ build-summary: $CABAL_DIR/logs/build.log
156+ store-dir: $CABAL_DIR/store
157+ install-dirs user
158+ prefix: $CABAL_DIR
159+ repository hackage.haskell.org
160+ url: http://hackage.haskell.org/
161+ EOF
162+ cat >> $CABAL_CONFIG <<EOF
163+ program-default-options
164+ ghc-options: $GHCJOBS +RTS -M3G -RTS
165+ EOF
166+ cat $CABAL_CONFIG
167+ - name: versions
168+ run: |
169+ $HC --version || true
170+ $HC --print-project-git-commit-id || true
171+ $CABAL --version || true
172+ - name: update cabal index
173+ run: |
174+ $CABAL v2-update -v
175+ - name: install cabal-plan
176+ run: |
177+ mkdir -p $HOME/.cabal/bin
178+ curl -sL https://github.com/haskell-hvr/cabal-plan/releases/download/v0.7.3.0/cabal-plan-0.7.3.0-x86_64-linux.xz > cabal-plan.xz
179+ echo 'f62ccb2971567a5f638f2005ad3173dba14693a45154c1508645c52289714cb2 cabal-plan.xz' | sha256sum -c -
180+ xz -d < cabal-plan.xz > $HOME/.cabal/bin/cabal-plan
181+ rm -f cabal-plan.xz
182+ chmod a+x $HOME/.cabal/bin/cabal-plan
183+ cabal-plan --version
184+ - name: checkout
185+ uses: actions/checkout@v3
186+ with:
187+ path: source
188+ - name: initial cabal.project for sdist
189+ run: |
190+ touch cabal.project
191+ echo "packages: $GITHUB_WORKSPACE/source/." >> cabal.project
192+ cat cabal.project
193+ - name: sdist
194+ run: |
195+ mkdir -p sdist
196+ $CABAL sdist all --output-dir $GITHUB_WORKSPACE/sdist
197+ - name: unpack
198+ run: |
199+ mkdir -p unpacked
200+ find sdist -maxdepth 1 -type f -name '*.tar.gz' -exec tar -C $GITHUB_WORKSPACE/unpacked -xzvf {} \;
201+ - name: generate cabal.project
202+ run: |
203+ PKGDIR_postgresql_simple="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/postgresql-simple-[0-9.]*')"
204+ echo "PKGDIR_postgresql_simple=${PKGDIR_postgresql_simple}" >> "$GITHUB_ENV"
205+ rm -f cabal.project cabal.project.local
206+ touch cabal.project
207+ touch cabal.project.local
208+ echo "packages: ${PKGDIR_postgresql_simple}" >> cabal.project
209+ echo "package postgresql-simple" >> cabal.project
210+ echo " ghc-options: -Werror=missing-methods" >> cabal.project
211+ cat >> cabal.project <<EOF
212+ allow-newer: cryptohash-md5-0.11.100.1:base
213+ EOF
214+ $HCPKG list --simple-output --names-only | perl -ne 'for (split /\s+/) { print "constraints: $_ installed\n" unless /^(postgresql-simple)$/; }' >> cabal.project.local
215+ cat cabal.project
216+ cat cabal.project.local
217+ - name: dump install plan
218+ run: |
219+ $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dry-run all
220+ cabal-plan
221+ - name: restore cache
222+ uses: actions/cache/restore@v3
223+ with:
224+ key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }}
225+ path: ~/.cabal/store
226+ restore-keys: ${{ runner.os }}-${{ matrix.compiler }}-
227+ - name: install dependencies
228+ run: |
229+ $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks --dependencies-only -j2 all
230+ $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dependencies-only -j2 all
231+ - name: build w/o tests
232+ run: |
233+ $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all
234+ - name: build
235+ run: |
236+ $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --write-ghc-environment-files=always
237+ - name: tests
238+ run: |
239+ $CABAL v2-test $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --test-show-details=direct
240+ - name: cabal check
241+ run: |
242+ cd ${PKGDIR_postgresql_simple} || false
243+ ${CABAL} -vnormal check
244+ - name: haddock
245+ run: |
246+ $CABAL v2-haddock --disable-documentation --haddock-all $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all
247+ - name: unconstrained build
248+ run: |
249+ rm -f cabal.project.local
250+ $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all
251+ - name: save cache
252+ uses: actions/cache/save@v3
253+ if: always()
254+ with:
255+ key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }}
256+ path: ~/.cabal/store
.github/workflows/simple.ymlnew file+58
@@ -0,0 +1,58 @@
1+name: Simple
2+on:
3+ push:
4+ branches:
5+ - master
6+ pull_request:
7+ branches:
8+ - master
9+
10+jobs:
11+ native:
12+ name: "Simple: GHC ${{ matrix.ghc }} on ${{ matrix.os }}"
13+ runs-on: ${{ matrix.os }}
14+ strategy:
15+ matrix:
16+ os: [macos-latest, windows-latest]
17+ ghc: ['8.10','9.0','9.2','9.4.8','9.6.3','9.8.1']
18+ fail-fast: false
19+ timeout-minutes:
20+ 60
21+ steps:
22+ - name: Set git to use LF
23+ run: |
24+ git config --global core.autocrlf false
25+ git config --global core.eol lf
26+
27+ - name: Set up Haskell
28+ id: setup-haskell
29+ uses: haskell/actions/setup@v2
30+ with:
31+ ghc-version: ${{ matrix.ghc }}
32+ cabal-version: '3.10.2.0'
33+
34+ - name: Set up PostgreSQL
35+ uses: ikalnytskyi/action-setup-postgres@v3
36+ id: postgres
37+ with:
38+ username: ci
39+ password: sw0rdfish
40+ database: test
41+
42+ - name: Checkout
43+ uses: actions/checkout@v3.0.2
44+
45+ - name: Cache
46+ uses: actions/cache@v2.1.3
47+ with:
48+ path: ${{ steps.setup-haskell.outputs.cabal-store }}
49+ key: ${{ runner.os }}-${{ matrix.ghc }}-${{ github.sha }}
50+ restore-keys: ${{ runner.os }}-${{ matrix.ghc }}-
51+
52+ - name: Build
53+ run: cabal build all --enable-tests
54+
55+ - name: Test
56+ run: cabal test all --enable-tests --test-show-details=direct
57+ env:
58+ DATABASE_CONNSTRING: ${{ steps.postgres.outputs.connection-uri }}
.travis.ymldeleted−129
@@ -1,129 +0,0 @@
1−# This Travis job script has been generated by a script via
2−#
3−# haskell-ci '--config' 'cabal.haskell-ci' '--output' '.travis.yml' 'postgresql-simple.cabal'
4−#
5−# For more information, see https://github.com/haskell-CI/haskell-ci
6−#
7−# version: 0.1.0.0
8−#
9−language: c
10−dist: xenial
11−
12−git:
13− submodules: false # whether to recursively clone submodules
14−
15−branches:
16− only:
17− - master
18−
19−cache:
20− directories:
21− - $HOME/.cabal/packages
22− - $HOME/.cabal/store
23−
24−services:
25−- postgresql
26−addons:
27− postgresql: '10'
28−
29−before_cache:
30− - rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log
31− # remove files that are regenerated by 'cabal update'
32− - rm -fv $CABALHOME/packages/hackage.haskell.org/00-index.*
33− - rm -fv $CABALHOME/packages/hackage.haskell.org/*.json
34− - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.cache
35− - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar
36− - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar.idx
37−
38− - rm -rfv $CABALHOME/packages/head.hackage
39−
40−matrix:
41− include:
42− - compiler: "ghc-8.6.3"
43− addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.3], sources: [hvr-ghc]}}
44− - compiler: "ghc-8.4.4"
45− addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.4], sources: [hvr-ghc]}}
46− - compiler: "ghc-8.2.2"
47− addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.2.2], sources: [hvr-ghc]}}
48− - compiler: "ghc-8.0.2"
49− addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.0.2], sources: [hvr-ghc]}}
50− - compiler: "ghc-7.10.3"
51− addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.10.3], sources: [hvr-ghc]}}
52− - compiler: "ghc-7.8.4"
53− addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.4], sources: [hvr-ghc]}}
54− - compiler: "ghc-7.6.3"
55− addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.6.3], sources: [hvr-ghc]}}
56−
57−before_install:
58− - HC=/opt/ghc/bin/${CC}
59− - HCPKG=${HC/ghc/ghc-pkg}
60− - unset CC
61− - CABAL=/opt/ghc/bin/cabal
62− - CABALHOME=$HOME/.cabal
63− - export PATH="$CABALHOME/bin:$PATH"
64− - ROOTDIR=$(pwd)
65− - HCNUMVER=$(( $(${HC} --numeric-version|sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\1 * 10000 + \2 * 100 + \3/') ))
66− - echo $HCNUMVER
67−
68−install:
69− - ${CABAL} --version
70− - echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]"
71− - TEST=--enable-tests
72− - BENCH=--enable-benchmarks
73− - GHCHEAD=${GHCHEAD-false}
74− - travis_retry ${CABAL} update -v
75− - sed -i.bak 's/^jobs:/-- jobs:/' $CABALHOME/config
76− - rm -fv cabal.project cabal.project.local
77− - grep -Ev -- '^\s*--' $CABALHOME/config | grep -Ev '^\s*$'
78− - rm -f cabal.project
79− - touch cabal.project
80− - "printf 'packages: \".\"\\n' >> cabal.project"
81− - "printf 'write-ghc-environment-files: always\\n' >> cabal.project"
82− - touch cabal.project.local
83− - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | grep -vE -- '^(postgresql-simple)$' | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
84− - cat cabal.project || true
85− - cat cabal.project.local || true
86− - if [ -f "./configure.ac" ]; then (cd "." && autoreconf -i); fi
87− - rm -f cabal.project.freeze
88− - ${CABAL} new-freeze -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project" --dry
89− - "cat \"cabal.project.freeze\" | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
90− - rm "cabal.project.freeze"
91− - ${CABAL} new-build -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project" --dep -j2 all
92− - ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks --project-file="cabal.project" --dep -j2 all
93− - rm -rf .ghc.environment.* "."/dist
94− - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
95−
96−# Here starts the actual work to be performed for the package under test;
97−# any command which exits with a non-zero exit code causes the build to fail.
98−script:
99− # test that source-distributions can be generated
100− - ${CABAL} new-sdist all
101− - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/
102− - cd ${DISTDIR} || false
103− - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;
104− - rm -f cabal.project
105− - touch cabal.project
106− - "printf 'packages: \"postgresql-simple-*/*.cabal\"\\n' >> cabal.project"
107− - "printf 'write-ghc-environment-files: always\\n' >> cabal.project"
108− - touch cabal.project.local
109− - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | grep -vE -- '^(postgresql-simple)$' | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
110− - cat cabal.project || true
111− - cat cabal.project.local || true
112− # this builds all libraries and executables (without tests/benchmarks)
113− - ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all
114−
115− # build & run tests, build benchmarks
116− - ${CABAL} new-build -w ${HC} ${TEST} ${BENCH} all
117− - if [ "x$TEST" = "x--enable-tests" ]; then ${CABAL} new-test -w ${HC} ${TEST} ${BENCH} all; fi
118−
119− # cabal check
120− - (cd postgresql-simple-* && ${CABAL} check)
121−
122− # haddock
123− - ${CABAL} new-haddock -w ${HC} ${TEST} ${BENCH} all
124−
125− # Build without installed constraints for packages in global-db
126− - rm -f cabal.project.local; ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all;
127−
128−# REGENDATA ["--config","cabal.haskell-ci","--output",".travis.yml","postgresql-simple.cabal"]
129−# EOF
CHANGES.md+58−4
@@ -1,3 +1,57 @@
1+### Version 0.7.0.0 (2023-07-31)
2+
3+ * Remove Eq Null instance. (Future `base` may break it, we remove it profilacticly).
4+ * Use `postgresql-libpq >=0.10.0.0`. It includes critical fixes,
5+ by using `postgresql-simple >=0.7` you won't get older `postgresql-libpq`.
6+ * Drop support for GHC prior 8.6
7+ * Added a class for `postgresql-simple` exceptions: `SomePostgreSqlException`.
8+ This allows to catch all `postgresql-simple` exceptions at once.
9+ (c.f. `AsyncException` in `base).
10+
11+### Version 0.6.5.1 (2023-07-09)
12+
13+ * Support `aeson-2.2.0.0`
14+
15+### Version 0.6.5 (2022-10-30)
16+
17+ * Add `withConnect`
18+
19+### Version 0.6.4 (2021-01-06)
20+
21+ * Add foldCopyData helper function
22+ Thanks to Sebastián Estrella for the implementation
23+ https://github.com/haskellari/postgresql-simple/pull/56
24+ * Implement support for postgresql 'interval' type
25+ Thanks to Andre Marques Lee for the implementation
26+ https://github.com/haskellari/postgresql-simple/pull/60
27+ * Depend on `time-compat` to provide uniform `time` related interface.
28+
29+### Version 0.6.3 (2020-11-15)
30+
31+ * Add `fromFieldJSONByteString`
32+ Thanks to tomjaguarpaw for the implementation
33+ https://github.com/haskellari/postgresql-simple/pull/47
34+ * Add `attoFieldParser`
35+ Thanks to Victor Nawothnig for the implementation
36+ https://github.com/haskellari/postgresql-simple/pull/45
37+ * Add `Identity` and `Const` instance
38+ Thanks to Cary Robbins for the implementation
39+ https://github.com/haskellari/postgresql-simple/pull/46
40+ * Add `withTransactionModeRetry'`, a variant of `withTransactionModeRetry`
41+ for all exception types.
42+ Thanks to Elliot Cameron for the implementation
43+ https://github.com/haskellari/postgresql-simple/pull/42
44+ * Fix spurious aborts when retrying transactions
45+ Thanks to Elliot Cameron for the implementation
46+ https://github.com/haskellari/postgresql-simple/pull/34
47+ * Add `Database.PostgreSQL.Simple.Newtypes` module
48+ with `Aeson` newtype.
49+ https://github.com/haskellari/postgresql-simple/pull/55
50+
51+### Version 0.6.2 (2019-04-26)
52+
53+ * Define `MonadFail Ok`.
54+
1### Version 0.6.1 (2019-03-04)
55### Version 0.6.1 (2019-03-04)
2
56
3 * Escape double '??' to a literal '?
57 * Escape double '??' to a literal '?
@@ -217,7 +271,7 @@
217 support UTF8 except for Mule Internal Code, the Multilingual
271 support UTF8 except for Mule Internal Code, the Multilingual
218 Extensions for Emacs. An exception should be raised upon
272 Extensions for Emacs. An exception should be raised upon
219 connecting to a database by the backend if the backend cannot
273 connecting to a database by the backend if the backend cannot
220− accomodate this requirement.
274+ accommodate this requirement.
221
275
222 * Added `Eq` and `Typeable` instances for `Connection`.
276 * Added `Eq` and `Typeable` instances for `Connection`.
223
277
@@ -355,7 +409,7 @@
355
409
356 * Changed the Identifier and QualifiedIdentifier to use Text in
410 * Changed the Identifier and QualifiedIdentifier to use Text in
357 order to avoid encoding errors. Technically this requires a
411 order to avoid encoding errors. Technically this requires a
358− major verson bump, but let's pretend 0.4.1.0 didn't happen.
412+ major version bump, but let's pretend 0.4.1.0 didn't happen.
359
413
360 * Removed non-exhaustive cases in the ToField instance for Values,
414 * Removed non-exhaustive cases in the ToField instance for Values,
361 and tweaked error messages.
415 and tweaked error messages.
@@ -515,7 +569,7 @@
515
569
516 * Added a brand new TypeInfo system that gives FromField instances
570 * Added a brand new TypeInfo system that gives FromField instances
517 convenient and efficient access to the pg_type metatable. This
571 convenient and efficient access to the pg_type metatable. This
518− replaced the older typename cache, and was neccesary to properly
572+ replaced the older typename cache, and was necessary to properly
519 support postgres array types. Thanks to Bas van Dijk for his
573 support postgres array types. Thanks to Bas van Dijk for his
520 work on this feature.
574 work on this feature.
521
575
@@ -607,7 +661,7 @@
607 now the preferred way of dealing with `timestamp` (without time zone).
661 now the preferred way of dealing with `timestamp` (without time zone).
608
662
609 * `Database.PostgreSQL.Simple.Time` is a new module that offers types
663 * `Database.PostgreSQL.Simple.Time` is a new module that offers types
610− that accomodate PostgreSQL's infinities.
664+ that accommodate PostgreSQL's infinities.
611
665
612 * All time-related `FromField`/`ToField` instances are now based on new,
666 * All time-related `FromField`/`ToField` instances are now based on new,
613 higher-speed parsers and printers instead of those provided by the
667 higher-speed parsers and printers instead of those provided by the
CONTRIBUTING.mdnew file+13
@@ -0,0 +1,13 @@
1+Patches welcome!
2+
3+- If you are only going to bump bounds:
4+ - If it's really **only bounds**, please simply open an issue (so you'll have a URL to refer to). I have a semi-automated process to make revisions, pull requests only disturb it.
5+ - If patch includes **source code change** (i.e. I'll need to make a release), and it's a patch to support **newer base/GHC version**:
6+ - Amend `tested-with` to include that GHC
7+ - Regenerate `.github/workflows/haskell-ci.yml` with `haskell-ci regenerate` (get the latest from [GitHub haskell-ci/haskell-ci](https://github.com/haskell-ci/haskell-ci))
8+
9+- Don't edit `CHANGELOG.md`, rather include a copyable entry in your pull request description. I often process pull requests in bulk, and everyone editing the `CHANGELOG.md` causes unnecessary conflicts.
10+- For the same reason, do not edit `version` or `x-revision`
11+
12+- I use [`stylish-haskell`](https://github.com/jaspervdj/stylish-haskell) to format imports. I encourage you to use it too, when contributing.
13+- General code style is 4 spaces, just look around how it looks, it's not so strict.
README.md−3
@@ -1,7 +1,4 @@
1−[![Continuous Integration status][status-png]][status]
2[![Hackage page (downloads and API reference)][hackage-png]][hackage]
1[![Hackage page (downloads and API reference)][hackage-png]][hackage]
3
2
4− [status-png]: https://api.travis-ci.org/lpsmith/postgresql-simple.svg
5− [status]: http://travis-ci.org/lpsmith/postgresql-simple?branch=master
6 [hackage-png]: http://img.shields.io/hackage/v/postgresql-simple.svg
3 [hackage-png]: http://img.shields.io/hackage/v/postgresql-simple.svg
7 [hackage]: http://hackage.haskell.org/package/postgresql-simple
4 [hackage]: http://hackage.haskell.org/package/postgresql-simple
cabal.project+2
@@ -1,2 +1,4 @@
1packages: .
1packages: .
2tests: true
2tests: true
3+
4+allow-newer: cryptohash-md5-0.11.100.1:base
cabal.project.local.winnew file+4
@@ -0,0 +1,4 @@
1+constraints: these -assoc
2+
3+package postgresql-libpq
4+ flags: -use-pkg-config
postgresql-simple.cabal+129−146
@@ -1,192 +1,175 @@
1−Cabal-version: 1.12
1+cabal-version: 1.12
2−Name: postgresql-simple
2+name: postgresql-simple
3−Version: 0.6.1
3+version: 0.7.0.0
4−
4+x-revision: 1
5−Synopsis: Mid-Level PostgreSQL client library
5+synopsis: Mid-Level PostgreSQL client library
6−Description:
6+description:
7− Mid-Level PostgreSQL client library, forked from mysql-simple.
7+ Mid-Level PostgreSQL client library, forked from mysql-simple.
8−License: BSD3
8+
9+license: BSD3
9−License-file: LICENSE
10+license-file: LICENSE
10−Author: Bryan O'Sullivan, Leon P Smith
11+author: Bryan O'Sullivan, Leon P Smith
11−Maintainer: Oleg Grenrus <oleg.grenrus@iki.fi>
12+maintainer: Oleg Grenrus <oleg.grenrus@iki.fi>
13+copyright:
12−Copyright: (c) 2011 MailRank, Inc.
14+ (c) 2011 MailRank, Inc.
13− (c) 2011-2018 Leon P Smith
15+ (c) 2011-2018 Leon P Smith
14− (c) 2018-2019 Oleg Grenrus
16+ (c) 2018-2020 Oleg Grenrus
17+
15−Category: Database
18+category: Database
16−Build-type: Simple
19+build-type: Simple
17−
18extra-source-files:
20extra-source-files:
19− CONTRIBUTORS
20 CHANGES.md
21 CHANGES.md
22+ CONTRIBUTORS
21 test/results/malformed-input.expected
23 test/results/malformed-input.expected
22 test/results/unique-constraint-violation.expected
24 test/results/unique-constraint-violation.expected
23
25
24tested-with:
26tested-with:
25− GHC == 7.6.3
27+ GHC ==8.6.5
26− || == 7.8.4
28+ || ==8.8.4
27− || == 7.10.3
29+ || ==8.10.7
28− || == 8.0.2
30+ || ==9.0.2
29− || == 8.2.2
31+ || ==9.2.8
30− || == 8.4.4
32+ || ==9.4.8
31− || == 8.6.3
33+ || ==9.6.3
32−
34+ || ==9.8.1
33−Library
35+
36+library
34− default-language: Haskell2010
37+ default-language: Haskell2010
35− hs-source-dirs: src
38+ hs-source-dirs: src
36− Exposed-modules:
39+ exposed-modules:
37− Database.PostgreSQL.Simple
40+ Database.PostgreSQL.Simple
38− Database.PostgreSQL.Simple.Arrays
41+ Database.PostgreSQL.Simple.Arrays
39− Database.PostgreSQL.Simple.Copy
42+ Database.PostgreSQL.Simple.Copy
40− Database.PostgreSQL.Simple.Cursor
43+ Database.PostgreSQL.Simple.Cursor
44+ Database.PostgreSQL.Simple.Errors
41− Database.PostgreSQL.Simple.FromField
45+ Database.PostgreSQL.Simple.FromField
42− Database.PostgreSQL.Simple.FromRow
46+ Database.PostgreSQL.Simple.FromRow
47+ Database.PostgreSQL.Simple.HStore
48+ Database.PostgreSQL.Simple.HStore.Internal
49+ Database.PostgreSQL.Simple.Internal
43− Database.PostgreSQL.Simple.LargeObjects
50+ Database.PostgreSQL.Simple.LargeObjects
44− Database.PostgreSQL.Simple.HStore
45− Database.PostgreSQL.Simple.HStore.Internal
51+ Database.PostgreSQL.Simple.Newtypes
46− Database.PostgreSQL.Simple.Notification
52+ Database.PostgreSQL.Simple.Notification
47− Database.PostgreSQL.Simple.Ok
53+ Database.PostgreSQL.Simple.Ok
48− Database.PostgreSQL.Simple.Range
54+ Database.PostgreSQL.Simple.Range
49− Database.PostgreSQL.Simple.SqlQQ
55+ Database.PostgreSQL.Simple.SqlQQ
50− Database.PostgreSQL.Simple.Time
56+ Database.PostgreSQL.Simple.Time
51− Database.PostgreSQL.Simple.Time.Internal
57+ Database.PostgreSQL.Simple.Time.Internal
52− Database.PostgreSQL.Simple.ToField
58+ Database.PostgreSQL.Simple.ToField
53− Database.PostgreSQL.Simple.ToRow
59+ Database.PostgreSQL.Simple.ToRow
54− Database.PostgreSQL.Simple.Transaction
60+ Database.PostgreSQL.Simple.Transaction
55− Database.PostgreSQL.Simple.TypeInfo
61+ Database.PostgreSQL.Simple.TypeInfo
56− Database.PostgreSQL.Simple.TypeInfo.Macro
62+ Database.PostgreSQL.Simple.TypeInfo.Macro
57− Database.PostgreSQL.Simple.TypeInfo.Static
63+ Database.PostgreSQL.Simple.TypeInfo.Static
58− Database.PostgreSQL.Simple.Types
64+ Database.PostgreSQL.Simple.Types
59− Database.PostgreSQL.Simple.Errors
60− Database.PostgreSQL.Simple.Vector
65+ Database.PostgreSQL.Simple.Vector
61− Database.PostgreSQL.Simple.Vector.Unboxed
66+ Database.PostgreSQL.Simple.Vector.Unboxed
62−
67+
63−-- Other-modules:
68+ -- Other-modules:
64− Database.PostgreSQL.Simple.Internal
65−
66− Other-modules:
69+ other-modules:
67− Database.PostgreSQL.Simple.Compat
70+ Database.PostgreSQL.Simple.Compat
68− Database.PostgreSQL.Simple.HStore.Implementation
71+ Database.PostgreSQL.Simple.HStore.Implementation
69− Database.PostgreSQL.Simple.Internal.PQResultUtils
72+ Database.PostgreSQL.Simple.Internal.PQResultUtils
70− Database.PostgreSQL.Simple.Time.Implementation
73+ Database.PostgreSQL.Simple.Time.Implementation
71− Database.PostgreSQL.Simple.Time.Internal.Parser
74+ Database.PostgreSQL.Simple.Time.Internal.Parser
72− Database.PostgreSQL.Simple.Time.Internal.Printer
75+ Database.PostgreSQL.Simple.Time.Internal.Printer
73− Database.PostgreSQL.Simple.TypeInfo.Types
76+ Database.PostgreSQL.Simple.TypeInfo.Types
74
77
75 -- GHC bundled libs
78 -- GHC bundled libs
76− Build-depends:
79+ build-depends:
77− base >=4.6.0.0 && <4.13
80+ base >=4.12.0.0 && <4.20
78− , bytestring >=0.10.0.0 && <0.11
81+ , bytestring >=0.10.8.2 && <0.13
79− , containers >=0.5.0.0 && <0.7
82+ , containers >=0.6.0.1 && <0.7
80− , time >=1.4.0.1 && <1.9
81− , transformers >=0.3.0.0 && <0.6
82− , template-haskell >=2.8.0.0 && <2.15
83+ , template-haskell >=2.14.0.0 && <2.22
83− , text >=1.2.3.0 && <1.3
84+ , text >=1.2.3.0 && <1.3 || >=2.0 && <2.2
85+ , time-compat >=1.9.5 && <1.12
86+ , transformers >=0.5.6.2 && <0.7
84
87
85 -- Other dependencies
88 -- Other dependencies
86− Build-depends:
89+ build-depends:
87− aeson >=1.4.1.0 && <1.5
90+ aeson >=2.1.2.1 && <2.3
88− , attoparsec >=0.13.2.2 && <0.14
91+ , attoparsec >=0.14.4 && <0.15
89− , bytestring-builder >=0.10.8.1.0 && <0.11
90− , case-insensitive >=1.2.0.11 && <1.3
92+ , case-insensitive >=1.2.1.0 && <1.3
91− , hashable >=1.2.7.0 && <1.3
93+ , hashable >=1.4.3.0 && <1.5
92− , Only >=0.1 && <0.1.1
94+ , Only >=0.1 && <0.1.1
93− , postgresql-libpq >=0.9.4.2 && < 0.10
95+ , postgresql-libpq >=0.10.0.0 && <0.11
96+ , scientific >=0.3.7.0 && <0.4
94− , uuid-types >=1.0.3 && <1.1
97+ , uuid-types >=1.0.5 && <1.1
95− , scientific >=0.3.6.2 && <0.4
96− , vector >=0.12.0.1 && <0.13
98+ , vector >=0.13.0.0 && <0.14
97−
98− if !impl(ghc >= 8.0)
99− Build-depends:
100− semigroups >=0.18.5 && <0.19
101−
102− if !impl(ghc >= 7.6)
103− Build-depends:
104− ghc-prim
105
99
106 default-extensions:
100 default-extensions:
101+ BangPatterns
107 DoAndIfThenElse
102 DoAndIfThenElse
108 OverloadedStrings
103 OverloadedStrings
109− BangPatterns
110− ViewPatterns
111 TypeOperators
104 TypeOperators
105+ ViewPatterns
112
106
113− ghc-options: -Wall -fno-warn-name-shadowing
107+ ghc-options: -Wall -fno-warn-name-shadowing
114
108
115source-repository head
109source-repository head
116 type: git
110 type: git
117− location: http://github.com/phadej/postgresql-simple
111+ location: http://github.com/haskellari/postgresql-simple
118−
119−source-repository this
120− type: git
121− location: http://github.com/phadej/postgresql-simple
122− tag: v0.6
123
112
124test-suite inspection
113test-suite inspection
125− if !impl(ghc >= 8.0)
126− buildable: False
127−
128 default-language: Haskell2010
114 default-language: Haskell2010
129− type: exitcode-stdio-1.0
115+ type: exitcode-stdio-1.0
130− hs-source-dirs: test
116+ hs-source-dirs: test
131− main-is: Inspection.hs
117+ main-is: Inspection.hs
132 build-depends:
118 build-depends:
133 base
119 base
134− , inspection-testing >=0.4.1.1 && <0.5
120+ , inspection-testing >=0.4.1.1 && <0.6
135− , postgresql-simple
136 , postgresql-libpq
121 , postgresql-libpq
122+ , postgresql-simple
137 , tasty
123 , tasty
138 , tasty-hunit
124 , tasty-hunit
139
125
140test-suite test
126test-suite test
141− default-language: Haskell2010
127+ default-language: Haskell2010
142− type: exitcode-stdio-1.0
128+ type: exitcode-stdio-1.0
143−
144− hs-source-dirs: test
129+ hs-source-dirs: test
145− main-is: Main.hs
130+ main-is: Main.hs
146 other-modules:
131 other-modules:
147 Common
132 Common
133+ Exception
134+ Interval
148 Notify
135 Notify
149 Serializable
136 Serializable
150 Time
137 Time
151
138
139+ ghc-options: -threaded
152− ghc-options: -Wall -fno-warn-name-shadowing -fno-warn-unused-do-bind
140+ ghc-options: -Wall -fno-warn-name-shadowing -fno-warn-unused-do-bind
153−
154 default-extensions:
141 default-extensions:
155 NamedFieldPuns
142 NamedFieldPuns
156 OverloadedStrings
143 OverloadedStrings
144+ PatternGuards
157 Rank2Types
145 Rank2Types
158 RecordWildCards
146 RecordWildCards
159− PatternGuards
160 ScopedTypeVariables
147 ScopedTypeVariables
161
148
162− build-depends: base
149+ build-depends:
163− , aeson
150+ aeson
151+ , base
164− , base16-bytestring
152+ , base16-bytestring
165− , bytestring
153+ , bytestring
154+ , case-insensitive
166− , containers
155+ , containers
167− , cryptohash-md5 >= 0.11.100.1 && <0.12
156+ , cryptohash-md5 >=0.11.100.1 && <0.12
168− , filepath
157+ , filepath
169− , tasty
170− , tasty-hunit
171− , tasty-golden
172− , HUnit
158+ , HUnit
173− , postgresql-simple
159+ , postgresql-simple
160+ , tasty
161+ , tasty-golden
162+ , tasty-hunit
174− , text
163+ , text
175− , time
164+ , time-compat
176− , vector
165+ , vector
177− , case-insensitive
178−
179− if !impl(ghc >= 7.6)
180− build-depends:
181− ghc-prim
182
166
183benchmark select
167benchmark select
184 default-language: Haskell2010
168 default-language: Haskell2010
185 type: exitcode-stdio-1.0
169 type: exitcode-stdio-1.0
186−
187− hs-source-dirs: bench
170+ hs-source-dirs: bench
188− main-is: Select.hs
171+ main-is: Select.hs
189−
190− build-depends: base
172+ build-depends:
173+ base
191− , postgresql-simple
174+ , postgresql-simple
192− , vector
175+ , vector
src/Database/PostgreSQL/Simple.hs+4−2
@@ -64,6 +64,7 @@ module Database.PostgreSQL.Simple
64 , Only(..)
64 , Only(..)
65 , (:.)(..)
65 , (:.)(..)
66 -- ** Exceptions
66 -- ** Exceptions
67+ , SomePostgreSqlException(..)
67 , SqlError(..)
68 , SqlError(..)
68 , PQ.ExecStatus(..)
69 , PQ.ExecStatus(..)
69 , FormatError(..)
70 , FormatError(..)
@@ -73,6 +74,7 @@ module Database.PostgreSQL.Simple
73 , Base.connectPostgreSQL
74 , Base.connectPostgreSQL
74 , Base.close
75 , Base.close
75 , Base.connect
76 , Base.connect
77+ , Base.withConnect
76 , Base.ConnectInfo(..)
78 , Base.ConnectInfo(..)
77 , Base.defaultConnectInfo
79 , Base.defaultConnectInfo
78 , Base.postgreSQLConnectionString
80 , Base.postgreSQLConnectionString
@@ -338,7 +340,7 @@ execute conn template qs = do
338-- @
340-- @
339-- executeMany c [sql|
341-- executeMany c [sql|
340-- UPDATE sometable
342-- UPDATE sometable
341−-- SET sometable.y = upd.y
343+-- SET y = upd.y
342-- FROM (VALUES (?,?)) as upd(x,y)
344-- FROM (VALUES (?,?)) as upd(x,y)
343-- WHERE sometable.x = upd.x
345-- WHERE sometable.x = upd.x
344-- |] [(1, \"hello\"),(2, \"world\")]
346-- |] [(1, \"hello\"),(2, \"world\")]
@@ -900,7 +902,7 @@ forEachWith_ parser conn template = foldWith_ parser conn template () . const
900-- all values of the given PostgreSQL type is considered \"compatible\".
902-- all values of the given PostgreSQL type is considered \"compatible\".
901-- For instance, you can always extract a PostgreSQL 16-bit @SMALLINT@
903-- For instance, you can always extract a PostgreSQL 16-bit @SMALLINT@
902-- column to a Haskell 'Int'. The Haskell 'Float' type can accurately
904-- column to a Haskell 'Int'. The Haskell 'Float' type can accurately
903−-- represent a @SMALLINT@, so it is considered compatble with those types.
905+-- represent a @SMALLINT@, so it is considered compatible with those types.
904--
906--
905-- * A numeric compatibility check is based only on the type of a
907-- * A numeric compatibility check is based only on the type of a
906-- column, /not/ on its values. For instance, a PostgreSQL 64-bit
908-- column, /not/ on its values. For instance, a PostgreSQL 64-bit
src/Database/PostgreSQL/Simple/Compat.hs−53
@@ -12,36 +12,13 @@ module Database.PostgreSQL.Simple.Compat
12 ) where
12 ) where
13
13
14import qualified Control.Exception as E
14import qualified Control.Exception as E
15−import Data.Monoid
16import Data.ByteString (ByteString)
15import Data.ByteString (ByteString)
17−#if MIN_VERSION_bytestring(0,10,0)
18import Data.ByteString.Lazy (toStrict)
16import Data.ByteString.Lazy (toStrict)
19−#else
20−import qualified Data.ByteString as B
21−import Data.ByteString.Lazy (toChunks)
22−#endif
23import Data.ByteString.Builder (Builder, toLazyByteString)
17import Data.ByteString.Builder (Builder, toLazyByteString)
24−
25−#if MIN_VERSION_scientific(0,3,0)
26import Data.Text.Lazy.Builder.Scientific (scientificBuilder)
18import Data.Text.Lazy.Builder.Scientific (scientificBuilder)
27−#else
28−import Data.Scientific (scientificBuilder)
29−#endif
30−
31−#if __GLASGOW_HASKELL__ >= 702
32import System.IO.Unsafe (unsafeDupablePerformIO)
19import System.IO.Unsafe (unsafeDupablePerformIO)
33−#elif __GLASGOW_HASKELL__ >= 611
34−import GHC.IO (unsafeDupablePerformIO)
35−#else
36−import GHC.IOBase (unsafeDupablePerformIO)
37−#endif
38−
39import Data.Fixed (Pico)
20import Data.Fixed (Pico)
40−#if MIN_VERSION_base(4,7,0)
41import Data.Fixed (Fixed(MkFixed))
21import Data.Fixed (Fixed(MkFixed))
42−#else
43−import Unsafe.Coerce (unsafeCoerce)
44−#endif
45
22
46-- | Like 'E.mask', but backported to base before version 4.3.0.
23-- | Like 'E.mask', but backported to base before version 4.3.0.
47--
24--
@@ -51,44 +28,14 @@ import Unsafe.Coerce (unsafeCoerce)
51-- 'withTransactionMode' function calls the restore callback only once, so we
28-- 'withTransactionMode' function calls the restore callback only once, so we
52-- don't need that polymorphism.
29-- don't need that polymorphism.
53mask :: ((IO a -> IO a) -> IO b) -> IO b
30mask :: ((IO a -> IO a) -> IO b) -> IO b
54−#if MIN_VERSION_base(4,3,0)
55mask io = E.mask $ \restore -> io restore
31mask io = E.mask $ \restore -> io restore
56−#else
57−mask io = do
58− b <- E.blocked
59− E.block $ io $ \m -> if b then m else E.unblock m
60−#endif
61{-# INLINE mask #-}
32{-# INLINE mask #-}
62
33
63−#if !MIN_VERSION_base(4,5,0)
64−infixr 6 <>
65−
66−(<>) :: Monoid m => m -> m -> m
67−(<>) = mappend
68−{-# INLINE (<>) #-}
69−#endif
70−
71toByteString :: Builder -> ByteString
34toByteString :: Builder -> ByteString
72−#if MIN_VERSION_bytestring(0,10,0)
73toByteString x = toStrict (toLazyByteString x)
35toByteString x = toStrict (toLazyByteString x)
74−#else
75−toByteString x = B.concat (toChunks (toLazyByteString x))
76−#endif
77−
78−#if MIN_VERSION_base(4,7,0)
79
36
80toPico :: Integer -> Pico
37toPico :: Integer -> Pico
81toPico = MkFixed
38toPico = MkFixed
82
39
83fromPico :: Pico -> Integer
40fromPico :: Pico -> Integer
84fromPico (MkFixed i) = i
41fromPico (MkFixed i) = i
85−
86−#else
87−
88−toPico :: Integer -> Pico
89−toPico = unsafeCoerce
90−
91−fromPico :: Pico -> Integer
92−fromPico = unsafeCoerce
93−
94−#endif
src/Database/PostgreSQL/Simple/Copy.hs+26−5
@@ -32,6 +32,7 @@ module Database.PostgreSQL.Simple.Copy
32 ( copy
32 ( copy
33 , copy_
33 , copy_
34 , CopyOutResult(..)
34 , CopyOutResult(..)
35+ , foldCopyData
35 , getCopyData
36 , getCopyData
36 , putCopyData
37 , putCopyData
37 , putCopyEnd
38 , putCopyEnd
@@ -54,7 +55,7 @@ import Database.PostgreSQL.Simple.Internal hiding (result, row)
54-- | Issue a @COPY FROM STDIN@ or @COPY TO STDOUT@ query. In the former
55-- | Issue a @COPY FROM STDIN@ or @COPY TO STDOUT@ query. In the former
55-- case, the connection's state will change to @CopyIn@; in the latter,
56-- case, the connection's state will change to @CopyIn@; in the latter,
56-- @CopyOut@. The connection must be in the ready state in order
57-- @CopyOut@. The connection must be in the ready state in order
57−-- to call this function. Performs parameter subsitution.
58+-- to call this function. Performs parameter substitution.
58
59
59copy :: ( ToRow params ) => Connection -> Query -> params -> IO ()
60copy :: ( ToRow params ) => Connection -> Query -> params -> IO ()
60copy conn template qs = do
61copy conn template qs = do
@@ -85,12 +86,8 @@ doCopy funcName conn template q = do
85 PQ.TuplesOk -> err
86 PQ.TuplesOk -> err
86 PQ.CopyOut -> return ()
87 PQ.CopyOut -> return ()
87 PQ.CopyIn -> return ()
88 PQ.CopyIn -> return ()
88−#if MIN_VERSION_postgresql_libpq(0,9,3)
89 PQ.CopyBoth -> errMsg "COPY BOTH is not supported"
89 PQ.CopyBoth -> errMsg "COPY BOTH is not supported"
90−#endif
91−#if MIN_VERSION_postgresql_libpq(0,9,2)
92 PQ.SingleTuple -> errMsg "single-row mode is not supported"
90 PQ.SingleTuple -> errMsg "single-row mode is not supported"
93−#endif
94 PQ.BadResponse -> throwResultError funcName result status
91 PQ.BadResponse -> throwResultError funcName result status
95 PQ.NonfatalError -> throwResultError funcName result status
92 PQ.NonfatalError -> throwResultError funcName result status
96 PQ.FatalError -> throwResultError funcName result status
93 PQ.FatalError -> throwResultError funcName result status
@@ -104,6 +101,30 @@ data CopyOutResult
104 deriving (Eq, Typeable, Show)
101 deriving (Eq, Typeable, Show)
105
102
106
103
104+-- | Fold over @COPY TO STDOUT@ query passing each copied row to an accumulator
105+-- and calling a post-process at the end. A connection must be in the
106+-- @CopyOut@ state in order to call this function.
107+--
108+-- __Example__
109+--
110+-- > (acc, count) <- foldCopyData conn
111+-- > (\acc row -> return (row:acc))
112+-- > (\acc count -> return (acc, count))
113+-- > []
114+
115+foldCopyData
116+ :: Connection -- ^ Database connection
117+ -> (a -> B.ByteString -> IO a) -- ^ Accumulate one row of the result
118+ -> (a -> Int64 -> IO b) -- ^ Post-process accumulator with a count of rows
119+ -> a -- ^ Initial accumulator
120+ -> IO b -- ^ Result
121+foldCopyData conn f g !acc = do
122+ result <- getCopyData conn
123+ case result of
124+ CopyOutRow row -> f acc row >>= foldCopyData conn f g
125+ CopyOutDone count -> g acc count
126+
127+
107-- | Retrieve some data from a @COPY TO STDOUT@ query. A connection
128-- | Retrieve some data from a @COPY TO STDOUT@ query. A connection
108-- must be in the @CopyOut@ state in order to call this function. If this
129-- must be in the @CopyOut@ state in order to call this function. If this
109-- returns a 'CopyOutRow', the connection remains in the @CopyOut@ state,
130-- returns a 'CopyOutRow', the connection remains in the @CopyOut@ state,
src/Database/PostgreSQL/Simple/Errors.hs+4−3
@@ -25,7 +25,6 @@ module Database.PostgreSQL.Simple.Errors
25 )
25 )
26 where
26 where
27
27
28−import Control.Applicative
29import Control.Exception as E
28import Control.Exception as E
30
29
31import Data.Attoparsec.ByteString.Char8
30import Data.Attoparsec.ByteString.Char8
@@ -60,11 +59,13 @@ data ConstraintViolation
60 deriving (Show, Eq, Ord, Typeable)
59 deriving (Show, Eq, Ord, Typeable)
61
60
62-- Default instance should be enough
61-- Default instance should be enough
63−instance Exception ConstraintViolation
62+instance Exception ConstraintViolation where
63+ toException = postgresqlExceptionToException
64+ fromException = postgresqlExceptionFromException
64
65
65
66
66-- | Tries to convert 'SqlError' to 'ConstrainViolation', checks sqlState and
67-- | Tries to convert 'SqlError' to 'ConstrainViolation', checks sqlState and
67−-- succeedes only if able to parse sqlErrorMsg.
68+-- succeeds only if able to parse sqlErrorMsg.
68--
69--
69-- > createUser = handleJust constraintViolation handler $ execute conn ...
70-- > createUser = handleJust constraintViolation handler $ execute conn ...
70-- > where
71-- > where
src/Database/PostgreSQL/Simple/FromField.hs+61−24
@@ -2,6 +2,7 @@
2{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}
2{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}
3{-# LANGUAGE PatternGuards, ScopedTypeVariables #-}
3{-# LANGUAGE PatternGuards, ScopedTypeVariables #-}
4{-# LANGUAGE RecordWildCards #-}
4{-# LANGUAGE RecordWildCards #-}
5+{-# LANGUAGE PolyKinds #-}
5
6
6{- |
7{- |
7Module: Database.PostgreSQL.Simple.FromField
8Module: Database.PostgreSQL.Simple.FromField
@@ -106,26 +107,27 @@ module Database.PostgreSQL.Simple.FromField
106 , PQ.Oid(..)
107 , PQ.Oid(..)
107 , PQ.Format(..)
108 , PQ.Format(..)
108 , pgArrayFieldParser
109 , pgArrayFieldParser
110+ , attoFieldParser
109
111
110 , optionalField
112 , optionalField
111 , fromJSONField
113 , fromJSONField
114+ , fromFieldJSONByteString
112 ) where
115 ) where
113
116
114#include "MachDeps.h"
117#include "MachDeps.h"
115
118
116−import Control.Applicative ( (<|>), (<$>), pure, (*>), (<*) )
119+import Control.Applicative ( Const(Const), (<|>), (<$>), pure, (*>), (<*) )
117import Control.Concurrent.MVar (MVar, newMVar)
120import Control.Concurrent.MVar (MVar, newMVar)
118−import Control.Exception (Exception)
121+import Control.Exception (Exception (toException, fromException))
119import qualified Data.Aeson as JSON
122import qualified Data.Aeson as JSON
120−import qualified Data.Aeson.Internal as JSON
121−import qualified Data.Aeson.Parser as JSON (value')
122import Data.Attoparsec.ByteString.Char8 hiding (Result)
123import Data.Attoparsec.ByteString.Char8 hiding (Result)
123import Data.ByteString (ByteString)
124import Data.ByteString (ByteString)
124import qualified Data.ByteString.Char8 as B
125import qualified Data.ByteString.Char8 as B
126+import Data.Functor.Identity (Identity(Identity))
125import Data.Int (Int16, Int32, Int64)
127import Data.Int (Int16, Int32, Int64)
126import Data.IORef (IORef, newIORef)
128import Data.IORef (IORef, newIORef)
127import Data.Ratio (Ratio)
129import Data.Ratio (Ratio)
128−import Data.Time ( UTCTime, ZonedTime, LocalTime, Day, TimeOfDay )
130+import Data.Time.Compat ( UTCTime, ZonedTime, LocalTime, Day, TimeOfDay, CalendarDiffTime )
129import Data.Typeable (Typeable, typeOf)
131import Data.Typeable (Typeable, typeOf)
130import Data.Vector (Vector)
132import Data.Vector (Vector)
131import Data.Vector.Mutable (IOVector)
133import Data.Vector.Mutable (IOVector)
@@ -152,6 +154,8 @@ import qualified Data.UUID.Types as UUID
152import Data.Scientific (Scientific)
154import Data.Scientific (Scientific)
153import GHC.Real (infinity, notANumber)
155import GHC.Real (infinity, notANumber)
154
156
157+import qualified Data.Aeson.Types as JSON
158+
155-- | Exception thrown if conversion from a SQL value to a Haskell
159-- | Exception thrown if conversion from a SQL value to a Haskell
156-- value fails.
160-- value fails.
157data ResultError = Incompatible { errSQLType :: String
161data ResultError = Incompatible { errSQLType :: String
@@ -178,7 +182,9 @@ data ResultError = Incompatible { errSQLType :: String
178 -- between metadata and actual data in a row).
182 -- between metadata and actual data in a row).
179 deriving (Eq, Show, Typeable)
183 deriving (Eq, Show, Typeable)
180
184
181−instance Exception ResultError
185+instance Exception ResultError where
186+ toException = postgresqlExceptionToException
187+ fromException = postgresqlExceptionFromException
182
188
183left :: Exception a => a -> Conversion b
189left :: Exception a => a -> Conversion b
184left = conversionError
190left = conversionError
@@ -244,9 +250,11 @@ tableOid Field{..} = toMaybeOid (unsafeDupablePerformIO (PQ.ftable result column
244 then Nothing
250 then Nothing
245 else Just x
251 else Just x
246
252
247−-- | If the column has a table associated with it, this returns the number
253+-- | If the column has a table associated with it, this returns the
248−-- off the associated table column. Numbering starts from 0. Analogous
254+-- number of the associated table column. Table columns have
255+-- nonzero numbers. Zero is returned if the specified column is not
256+-- a simple reference to a table column, or when using pre-3.0
249−-- to libpq's @PQftablecol@.
257+-- protocol. Analogous to libpq's @PQftablecol@.
250
258
251tableColumn :: Field -> Int
259tableColumn :: Field -> Int
252tableColumn Field{..} = fromCol (unsafeDupablePerformIO (PQ.ftablecol result column))
260tableColumn Field{..} = fromCol (unsafeDupablePerformIO (PQ.ftablecol result column))
@@ -265,6 +273,12 @@ instance FromField () where
265 | typeOid f /= TI.voidOid = returnError Incompatible f ""
273 | typeOid f /= TI.voidOid = returnError Incompatible f ""
266 | otherwise = pure ()
274 | otherwise = pure ()
267
275
276+instance (FromField a) => FromField (Const a b) where
277+ fromField f bs = Const <$> fromField f bs
278+
279+instance (FromField a) => FromField (Identity a) where
280+ fromField f bs = Identity <$> fromField f bs
281+
268-- | For dealing with null values. Compatible with any postgresql type
282-- | For dealing with null values. Compatible with any postgresql type
269-- compatible with type @a@. Note that the type is not checked if
283-- compatible with type @a@. Note that the type is not checked if
270-- the value is null, although it is inadvisable to rely on this
284-- the value is null, although it is inadvisable to rely on this
@@ -311,11 +325,11 @@ instance FromField Char where
311
325
312-- | int2
326-- | int2
313instance FromField Int16 where
327instance FromField Int16 where
314− fromField = atto ok16 $ signed decimal
328+ fromField = attoFieldParser ok16 $ signed decimal
315
329
316-- | int2, int4
330-- | int2, int4
317instance FromField Int32 where
331instance FromField Int32 where
318− fromField = atto ok32 $ signed decimal
332+ fromField = attoFieldParser ok32 $ signed decimal
319
333
320#if WORD_SIZE_IN_BITS < 64
334#if WORD_SIZE_IN_BITS < 64
321-- | int2, int4, and if compiled as 64-bit code, int8 as well.
335-- | int2, int4, and if compiled as 64-bit code, int8 as well.
@@ -325,36 +339,36 @@ instance FromField Int32 where
325-- This library was compiled as 64-bit code.
339-- This library was compiled as 64-bit code.
326#endif
340#endif
327instance FromField Int where
341instance FromField Int where
328− fromField = atto okInt $ signed decimal
342+ fromField = attoFieldParser okInt $ signed decimal
329
343
330-- | int2, int4, int8
344-- | int2, int4, int8
331instance FromField Int64 where
345instance FromField Int64 where
332− fromField = atto ok64 $ signed decimal
346+ fromField = attoFieldParser ok64 $ signed decimal
333
347
334-- | int2, int4, int8
348-- | int2, int4, int8
335instance FromField Integer where
349instance FromField Integer where
336− fromField = atto ok64 $ signed decimal
350+ fromField = attoFieldParser ok64 $ signed decimal
337
351
338-- | int2, float4 (Uses attoparsec's 'double' routine, for
352-- | int2, float4 (Uses attoparsec's 'double' routine, for
339-- better accuracy convert to 'Scientific' or 'Rational' first)
353-- better accuracy convert to 'Scientific' or 'Rational' first)
340instance FromField Float where
354instance FromField Float where
341− fromField = atto ok (realToFrac <$> pg_double)
355+ fromField = attoFieldParser ok (realToFrac <$> pg_double)
342 where ok = eq TI.float4Oid \/ eq TI.int2Oid
356 where ok = eq TI.float4Oid \/ eq TI.int2Oid
343
357
344-- | int2, int4, float4, float8 (Uses attoparsec's 'double' routine, for
358-- | int2, int4, float4, float8 (Uses attoparsec's 'double' routine, for
345-- better accuracy convert to 'Scientific' or 'Rational' first)
359-- better accuracy convert to 'Scientific' or 'Rational' first)
346instance FromField Double where
360instance FromField Double where
347− fromField = atto ok pg_double
361+ fromField = attoFieldParser ok pg_double
348 where ok = eq TI.float4Oid \/ eq TI.float8Oid \/ eq TI.int2Oid \/ eq TI.int4Oid
362 where ok = eq TI.float4Oid \/ eq TI.float8Oid \/ eq TI.int2Oid \/ eq TI.int4Oid
349
363
350-- | int2, int4, int8, float4, float8, numeric
364-- | int2, int4, int8, float4, float8, numeric
351instance FromField (Ratio Integer) where
365instance FromField (Ratio Integer) where
352− fromField = atto ok pg_rational
366+ fromField = attoFieldParser ok pg_rational
353 where ok = eq TI.float4Oid \/ eq TI.float8Oid \/ eq TI.int2Oid \/ eq TI.int4Oid \/ eq TI.int8Oid \/ eq TI.numericOid
367 where ok = eq TI.float4Oid \/ eq TI.float8Oid \/ eq TI.int2Oid \/ eq TI.int4Oid \/ eq TI.int8Oid \/ eq TI.numericOid
354
368
355-- | int2, int4, int8, float4, float8, numeric
369-- | int2, int4, int8, float4, float8, numeric
356instance FromField Scientific where
370instance FromField Scientific where
357− fromField = atto ok rational
371+ fromField = attoFieldParser ok rational
358 where ok = eq TI.float4Oid \/ eq TI.float8Oid \/ eq TI.int2Oid \/ eq TI.int4Oid \/ eq TI.int8Oid \/ eq TI.numericOid
372 where ok = eq TI.float4Oid \/ eq TI.float8Oid \/ eq TI.int2Oid \/ eq TI.int4Oid \/ eq TI.int8Oid \/ eq TI.numericOid
359
373
360unBinary :: Binary t -> t
374unBinary :: Binary t -> t
@@ -382,7 +396,7 @@ instance FromField SB.ByteString where
382
396
383-- | oid
397-- | oid
384instance FromField PQ.Oid where
398instance FromField PQ.Oid where
385− fromField f dat = PQ.Oid <$> atto (== TI.oidOid) decimal f dat
399+ fromField f dat = PQ.Oid <$> attoFieldParser (== TI.oidOid) decimal f dat
386
400
387-- | bytea, name, text, \"char\", bpchar, varchar, unknown
401-- | bytea, name, text, \"char\", bpchar, varchar, unknown
388instance FromField LB.ByteString where
402instance FromField LB.ByteString where
@@ -475,6 +489,15 @@ instance FromField LocalTimestamp where
475instance FromField Date where
489instance FromField Date where
476 fromField = ff TI.dateOid "Date" parseDate
490 fromField = ff TI.dateOid "Date" parseDate
477
491
492+-- | interval. Requires you to configure intervalstyle as @iso_8601@.
493+--
494+-- You can configure intervalstyle on every connection with a @SET@ command,
495+-- but for better performance you may want to configure it permanently in the
496+-- file found with @SHOW config_file;@ .
497+--
498+instance FromField CalendarDiffTime where
499+ fromField = ff TI.intervalOid "CalendarDiffTime" parseCalendarDiffTime
500+
478ff :: PQ.Oid -> String -> (B8.ByteString -> Either String a)
501ff :: PQ.Oid -> String -> (B8.ByteString -> Either String a)
479 -> Field -> Maybe B8.ByteString -> Conversion a
502 -> Field -> Maybe B8.ByteString -> Conversion a
480ff compatOid hsType parseBS f mstr =
503ff compatOid hsType parseBS f mstr =
@@ -553,11 +576,13 @@ instance FromField UUID where
553-- | json, jsonb
576-- | json, jsonb
554instance FromField JSON.Value where
577instance FromField JSON.Value where
555 fromField f mbs = parseBS =<< fromFieldJSONByteString f mbs
578 fromField f mbs = parseBS =<< fromFieldJSONByteString f mbs
556− where parseBS bs = case parseOnly (JSON.value' <* endOfInput) bs of
579+ where parseBS bs = case JSON.eitherDecodeStrict' bs of
557 Left err -> returnError ConversionFailed f err
580 Left err -> returnError ConversionFailed f err
558 Right val -> pure val
581 Right val -> pure val
559
582
560-- | Return the JSON ByteString directly
583-- | Return the JSON ByteString directly
584+--
585+-- @since 0.6.3
561fromFieldJSONByteString :: Field -> Maybe ByteString -> Conversion ByteString
586fromFieldJSONByteString :: Field -> Maybe ByteString -> Conversion ByteString
562fromFieldJSONByteString f mbs =
587fromFieldJSONByteString f mbs =
563 if typeOid f /= TI.jsonOid && typeOid f /= TI.jsonbOid
588 if typeOid f /= TI.jsonOid && typeOid f /= TI.jsonbOid
@@ -659,10 +684,22 @@ returnError mkErr f msg = do
659 (show (typeOf (undefined :: a)))
684 (show (typeOf (undefined :: a)))
660 msg
685 msg
661
686
687+-- | Construct a field parser from an attoparsec parser. An 'Incompatible' error is thrown if the
688+-- PostgreSQL oid does not match the specified predicate.
689+--
690+-- @
691+-- instance FromField Int16 where
692+-- fromField = attoFieldParser ok16 (signed decimal)
693+-- @
694+--
695+-- @since 0.6.3
662−atto :: forall a. (Typeable a)
696+attoFieldParser :: forall a. (Typeable a)
663− => Compat -> Parser a -> Field -> Maybe ByteString
697+ => (PQ.Oid -> Bool)
664− -> Conversion a
698+ -- ^ Predicate for whether the postgresql type oid is compatible with this parser
699+ -> Parser a
700+ -- ^ An attoparsec parser.
701+ -> FieldParser a
665−atto types p0 f dat = doFromField f types (go p0) dat
702+attoFieldParser types p0 f dat = doFromField f types (go p0) dat
666 where
703 where
667 go :: Parser a -> ByteString -> Conversion a
704 go :: Parser a -> ByteString -> Conversion a
668 go p s =
705 go p s =
src/Database/PostgreSQL/Simple/HStore/Implementation.hs−6
@@ -22,9 +22,6 @@ import Data.ByteString.Builder (Builder, byteString, char8)
22import qualified Data.ByteString.Builder as BU
22import qualified Data.ByteString.Builder as BU
23import Data.ByteString.Internal (c2w, w2c)
23import Data.ByteString.Internal (c2w, w2c)
24import qualified Data.ByteString.Lazy as BL
24import qualified Data.ByteString.Lazy as BL
25−#if !MIN_VERSION_bytestring(0,10,0)
26−import qualified Data.ByteString.Lazy.Internal as BL (foldrChunks)
27−#endif
28import Data.Map(Map)
25import Data.Map(Map)
29import qualified Data.Map as Map
26import qualified Data.Map as Map
30import Data.Text(Text)
27import Data.Text(Text)
@@ -69,9 +66,6 @@ instance Semigroup HStoreBuilder where
69
66
70instance Monoid HStoreBuilder where
67instance Monoid HStoreBuilder where
71 mempty = Empty
68 mempty = Empty
72−#if !(MIN_VERSION_base(4,11,0))
73− mappend = (<>)
74−#endif
75
69
76class ToHStoreText a where
70class ToHStoreText a where
77 toHStoreText :: a -> HStoreText
71 toHStoreText :: a -> HStoreText
src/Database/PostgreSQL/Simple/Internal.hs+37−7
@@ -1,6 +1,8 @@
1{-# LANGUAGE CPP, BangPatterns, DoAndIfThenElse, RecordWildCards #-}
1{-# LANGUAGE CPP, BangPatterns, DoAndIfThenElse, RecordWildCards #-}
2{-# LANGUAGE DeriveDataTypeable, DeriveGeneric #-}
2{-# LANGUAGE DeriveDataTypeable, DeriveGeneric #-}
3{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+{-# LANGUAGE ExistentialQuantification #-}
5+{-# LANGUAGE InstanceSigs #-}
4
6
5------------------------------------------------------------------------------
7------------------------------------------------------------------------------
6-- |
8-- |
@@ -34,7 +36,6 @@ import Data.Int (Int64)
34import qualified Data.IntMap as IntMap
36import qualified Data.IntMap as IntMap
35import Data.IORef
37import Data.IORef
36import Data.Maybe(fromMaybe)
38import Data.Maybe(fromMaybe)
37−import Data.Monoid
38import Data.String
39import Data.String
39import qualified Data.Text as T
40import qualified Data.Text as T
40import qualified Data.Text.Encoding as TE
41import qualified Data.Text.Encoding as TE
@@ -82,6 +83,25 @@ data Connection = Connection {
82instance Eq Connection where
83instance Eq Connection where
83 x == y = connectionHandle x == connectionHandle y
84 x == y = connectionHandle x == connectionHandle y
84
85
86+-- | Superclass for postgresql exceptions
87+data SomePostgreSqlException = forall e. Exception e => SomePostgreSqlException e
88+ deriving Typeable
89+
90+postgresqlExceptionToException :: Exception e => e -> SomeException
91+postgresqlExceptionToException = toException . SomePostgreSqlException
92+
93+postgresqlExceptionFromException :: Exception e => SomeException -> Maybe e
94+postgresqlExceptionFromException x = do
95+ SomePostgreSqlException a <- fromException x
96+ cast a
97+
98+instance Show SomePostgreSqlException where
99+ showsPrec :: Int -> SomePostgreSqlException -> ShowS
100+ showsPrec p (SomePostgreSqlException e) = showsPrec p e
101+
102+instance Exception SomePostgreSqlException where
103+ displayException (SomePostgreSqlException e) = displayException e
104+
85data SqlError = SqlError {
105data SqlError = SqlError {
86 sqlState :: ByteString
106 sqlState :: ByteString
87 , sqlExecStatus :: ExecStatus
107 , sqlExecStatus :: ExecStatus
@@ -93,7 +113,10 @@ data SqlError = SqlError {
93fatalError :: ByteString -> SqlError
113fatalError :: ByteString -> SqlError
94fatalError msg = SqlError "" FatalError msg "" ""
114fatalError msg = SqlError "" FatalError msg "" ""
95
115
96−instance Exception SqlError
116+instance Exception SqlError where
117+ toException = postgresqlExceptionToException
118+ fromException = postgresqlExceptionFromException
119+
97
120
98-- | Exception thrown if 'query' is used to perform an @INSERT@-like
121-- | Exception thrown if 'query' is used to perform an @INSERT@-like
99-- operation, or 'execute' is used to perform a @SELECT@-like operation.
122-- operation, or 'execute' is used to perform a @SELECT@-like operation.
@@ -102,7 +125,9 @@ data QueryError = QueryError {
102 , qeQuery :: Query
125 , qeQuery :: Query
103 } deriving (Eq, Show, Typeable)
126 } deriving (Eq, Show, Typeable)
104
127
105−instance Exception QueryError
128+instance Exception QueryError where
129+ toException = postgresqlExceptionToException
130+ fromException = postgresqlExceptionFromException
106
131
107-- | Exception thrown if a 'Query' could not be formatted correctly.
132-- | Exception thrown if a 'Query' could not be formatted correctly.
108-- This may occur if the number of \'@?@\' characters in the query
133-- This may occur if the number of \'@?@\' characters in the query
@@ -113,7 +138,9 @@ data FormatError = FormatError {
113 , fmtParams :: [ByteString]
138 , fmtParams :: [ByteString]
114 } deriving (Eq, Show, Typeable)
139 } deriving (Eq, Show, Typeable)
115
140
116−instance Exception FormatError
141+instance Exception FormatError where
142+ toException = postgresqlExceptionToException
143+ fromException = postgresqlExceptionFromException
117
144
118data ConnectInfo = ConnectInfo {
145data ConnectInfo = ConnectInfo {
119 connectHost :: String
146 connectHost :: String
@@ -155,6 +182,12 @@ defaultConnectInfo = ConnectInfo {
155connect :: ConnectInfo -> IO Connection
182connect :: ConnectInfo -> IO Connection
156connect = connectPostgreSQL . postgreSQLConnectionString
183connect = connectPostgreSQL . postgreSQLConnectionString
157
184
185+-- | Memory bracket around 'connect' and 'close'.
186+--
187+-- @since 0.6.5
188+withConnect :: ConnectInfo -> (Connection -> IO c) -> IO c
189+withConnect connInfo = bracket (connect connInfo) close
190+
158-- | Attempt to make a connection based on a libpq connection string.
191-- | Attempt to make a connection based on a libpq connection string.
159-- See <https://www.postgresql.org/docs/9.5/static/libpq-connect.html#LIBPQ-CONNSTRING>
192-- See <https://www.postgresql.org/docs/9.5/static/libpq-connect.html#LIBPQ-CONNSTRING>
160-- for more information. Also note that environment variables also affect
193-- for more information. Also note that environment variables also affect
@@ -484,9 +517,6 @@ instance Alternative Conversion where
484 Errors _ -> (oka <|>) <$> runConversion mb conn
517 Errors _ -> (oka <|>) <$> runConversion mb conn
485
518
486instance Monad Conversion where
519instance Monad Conversion where
487−#if !(MIN_VERSION_base(4,8,0))
488− return = pure
489−#endif
490 m >>= f = Conversion $ \conn -> do
520 m >>= f = Conversion $ \conn -> do
491 oka <- runConversion m conn
521 oka <- runConversion m conn
492 case oka of
522 case oka of
src/Database/PostgreSQL/Simple/Internal/PQResultUtils.hs−4
@@ -76,12 +76,8 @@ finishQueryWith' q result k = do
76 PQ.CommandOk -> queryErr "query resulted in a command response (did you mean to use `execute` or forget a RETURNING?)"
76 PQ.CommandOk -> queryErr "query resulted in a command response (did you mean to use `execute` or forget a RETURNING?)"
77 PQ.CopyOut -> queryErr "query: COPY TO is not supported"
77 PQ.CopyOut -> queryErr "query: COPY TO is not supported"
78 PQ.CopyIn -> queryErr "query: COPY FROM is not supported"
78 PQ.CopyIn -> queryErr "query: COPY FROM is not supported"
79−#if MIN_VERSION_postgresql_libpq(0,9,3)
80 PQ.CopyBoth -> queryErr "query: COPY BOTH is not supported"
79 PQ.CopyBoth -> queryErr "query: COPY BOTH is not supported"
81−#endif
82−#if MIN_VERSION_postgresql_libpq(0,9,2)
83 PQ.SingleTuple -> queryErr "query: single-row mode is not supported"
80 PQ.SingleTuple -> queryErr "query: single-row mode is not supported"
84−#endif
85 PQ.BadResponse -> throwResultError "query" result status
81 PQ.BadResponse -> throwResultError "query" result status
86 PQ.NonfatalError -> throwResultError "query" result status
82 PQ.NonfatalError -> throwResultError "query" result status
87 PQ.FatalError -> throwResultError "query" result status
83 PQ.FatalError -> throwResultError "query" result status
src/Database/PostgreSQL/Simple/Newtypes.hsnew file+49
@@ -0,0 +1,49 @@
1+{-# LANGUAGE DeriveFunctor #-}
2+{-# LANGUAGE DeriveDataTypeable #-}
3+-- | Module with newtypes suitable to usage with @DerivingVia@ or standalone.
4+--
5+-- The newtypes are named after packages they wrap.
6+module Database.PostgreSQL.Simple.Newtypes (
7+ Aeson (..), getAeson,
8+) where
9+
10+import Data.Typeable (Typeable)
11+import Database.PostgreSQL.Simple.ToField (ToField (..))
12+import Database.PostgreSQL.Simple.FromField (FromField (..), fromJSONField)
13+
14+import qualified Data.Aeson as Aeson
15+
16+-------------------------------------------------------------------------------
17+-- aeson
18+-------------------------------------------------------------------------------
19+
20+-- | A newtype wrapper with 'ToField' and 'FromField' instances
21+-- based on 'Aeson.ToJSON' and 'Aeson.FromJSON' type classes from @aeson@.
22+--
23+-- Example using @DerivingVia@:
24+--
25+-- @
26+-- data Foo = Foo Int String
27+-- deriving stock (Eq, Show, Generic) -- GHC built int
28+-- deriving anyclass ('Aeson.FromJSON', 'Aeson.ToJSON') -- Derived using GHC Generics
29+-- deriving ('ToField', 'FromField') via 'Aeson' Foo -- DerivingVia
30+-- @
31+--
32+-- Example using 'Aeson' newtype directly, for more ad-hoc queries
33+--
34+-- @
35+-- execute conn "INSERT INTO tbl (fld) VALUES (?)" (Only ('Aeson' x))
36+-- @
37+--
38+-- @since 0.6.3
39+newtype Aeson a = Aeson a
40+ deriving (Eq, Show, Read, Typeable, Functor)
41+
42+getAeson :: Aeson a -> a
43+getAeson (Aeson a) = a
44+
45+instance Aeson.ToJSON a => ToField (Aeson a) where
46+ toField = toField . Aeson.encode . getAeson
47+
48+instance (Aeson.FromJSON a, Typeable a) => FromField (Aeson a) where
49+ fromField f bs = fmap Aeson (fromJSONField f bs)
src/Database/PostgreSQL/Simple/Notification.hs−17
@@ -47,8 +47,6 @@ import GHC.IO.Exception ( ioe_location )
47
47
48#if defined(mingw32_HOST_OS)
48#if defined(mingw32_HOST_OS)
49import Control.Concurrent ( threadDelay )
49import Control.Concurrent ( threadDelay )
50−#elif !MIN_VERSION_base(4,7,0)
51−import Control.Concurrent ( threadWaitRead )
52#else
50#else
53import GHC.Conc ( atomically )
51import GHC.Conc ( atomically )
54import Control.Concurrent ( threadWaitReadSTM )
52import Control.Concurrent ( threadWaitReadSTM )
@@ -94,21 +92,6 @@ getNotification conn = join $ withConnection conn fetch
94 -- with async exceptions, whereas threadDelay can.
92 -- with async exceptions, whereas threadDelay can.
95 Just _fd -> do
93 Just _fd -> do
96 return (threadDelay 1000000 >> loop)
94 return (threadDelay 1000000 >> loop)
97−#elif !MIN_VERSION_base(4,7,0)
98− -- Technically there's a race condition that is usually benign.
99− -- If the connection is closed or reset after we drop the
100− -- lock, and then the fd index is reallocated to a new
101− -- descriptor before we call threadWaitRead, then
102− -- we could end up waiting on the wrong descriptor.
103− --
104− -- Now, if the descriptor becomes readable promptly, then
105− -- it's no big deal as we'll wake up and notice the change
106− -- on the next iteration of the loop. But if are very
107− -- unlucky, then we could end up waiting a long time.
108− Just fd -> do
109− return $ do
110− threadWaitRead fd `catch` (throwIO . setIOErrorLocation)
111− loop
112#else
95#else
113 -- This case fixes the race condition above. By registering
96 -- This case fixes the race condition above. By registering
114 -- our interest in the descriptor before we drop the lock,
97 -- our interest in the descriptor before we drop the lock,
src/Database/PostgreSQL/Simple/Ok.hs+7−4
@@ -37,6 +37,8 @@ import Control.Exception
37import Control.Monad(MonadPlus(..))
37import Control.Monad(MonadPlus(..))
38import Data.Typeable
38import Data.Typeable
39
39
40+import qualified Control.Monad.Fail as Fail
41+
40-- FIXME: [SomeException] should probably be something else, maybe
42-- FIXME: [SomeException] should probably be something else, maybe
41-- a difference list (or a tree?)
43-- a difference list (or a tree?)
42
44
@@ -70,13 +72,14 @@ instance MonadPlus Ok where
70 mplus = (<|>)
72 mplus = (<|>)
71
73
72instance Monad Ok where
74instance Monad Ok where
73−#if !(MIN_VERSION_base(4,8,0))
74− return = pure
75−#endif
76−
77 Errors es >>= _ = Errors es
75 Errors es >>= _ = Errors es
78 Ok a >>= f = f a
76 Ok a >>= f = f a
79
77
78+#if !(MIN_VERSION_base(4,13,0))
79+ fail = Fail.fail
80+#endif
81+
82+instance Fail.MonadFail Ok where
80 fail str = Errors [SomeException (ErrorCall str)]
83 fail str = Errors [SomeException (ErrorCall str)]
81
84
82-- | a way to reify a list of exceptions into a single exception
85-- | a way to reify a list of exceptions into a single exception
src/Database/PostgreSQL/Simple/Range.hs+1−1
@@ -38,7 +38,7 @@ import Data.Monoid (mempty)
38import Data.Scientific (Scientific)
38import Data.Scientific (Scientific)
39import qualified Data.Text.Lazy.Builder as LT
39import qualified Data.Text.Lazy.Builder as LT
40import qualified Data.Text.Lazy.Encoding as LT
40import qualified Data.Text.Lazy.Encoding as LT
41−import Data.Time (Day, LocalTime,
41+import Data.Time.Compat (Day, LocalTime,
42 NominalDiffTime,
42 NominalDiffTime,
43 TimeOfDay, UTCTime,
43 TimeOfDay, UTCTime,
44 ZonedTime,
44 ZonedTime,
src/Database/PostgreSQL/Simple/SqlQQ.hs+1−1
@@ -35,7 +35,7 @@ import Data.String
35-- > (beginTime,endTime,string)
35-- > (beginTime,endTime,string)
36--
36--
37-- This quasiquoter returns a literal string expression of type 'Query',
37-- This quasiquoter returns a literal string expression of type 'Query',
38−-- and attempts to mimimize whitespace; otherwise the above query would
38+-- and attempts to minimize whitespace; otherwise the above query would
39-- consist of approximately half whitespace when sent to the database
39-- consist of approximately half whitespace when sent to the database
40-- backend. It also recognizes and strips out standard sql comments "--".
40-- backend. It also recognizes and strips out standard sql comments "--".
41--
41--
src/Database/PostgreSQL/Simple/Time.hs+2
@@ -227,6 +227,7 @@ module Database.PostgreSQL.Simple.Time
227 , parseUTCTimestamp
227 , parseUTCTimestamp
228 , parseZonedTimestamp
228 , parseZonedTimestamp
229 , parseLocalTimestamp
229 , parseLocalTimestamp
230+ , parseCalendarDiffTime
230 , dayToBuilder
231 , dayToBuilder
231 , utcTimeToBuilder
232 , utcTimeToBuilder
232 , zonedTimeToBuilder
233 , zonedTimeToBuilder
@@ -239,6 +240,7 @@ module Database.PostgreSQL.Simple.Time
239 , localTimestampToBuilder
240 , localTimestampToBuilder
240 , unboundedToBuilder
241 , unboundedToBuilder
241 , nominalDiffTimeToBuilder
242 , nominalDiffTimeToBuilder
243+ , calendarDiffTimeToBuilder
242 ) where
244 ) where
243
245
244import Database.PostgreSQL.Simple.Time.Implementation
246import Database.PostgreSQL.Simple.Time.Implementation
src/Database/PostgreSQL/Simple/Time/Implementation.hs+11−1
@@ -18,7 +18,8 @@ import Data.ByteString.Builder.Prim(primBounded)
18import Control.Arrow((***))
18import Control.Arrow((***))
19import Control.Applicative
19import Control.Applicative
20import qualified Data.ByteString as B
20import qualified Data.ByteString as B
21−import Data.Time hiding (getTimeZone, getZonedTime)
21+import Data.Time.Compat (LocalTime, UTCTime, ZonedTime, Day, TimeOfDay, TimeZone, NominalDiffTime, utc)
22+import Data.Time.LocalTime.Compat (CalendarDiffTime)
22import Data.Typeable
23import Data.Typeable
23import Data.Maybe (fromMaybe)
24import Data.Maybe (fromMaybe)
24import qualified Data.Attoparsec.ByteString.Char8 as A
25import qualified Data.Attoparsec.ByteString.Char8 as A
@@ -77,6 +78,9 @@ parseLocalTimestamp = A.parseOnly (getLocalTimestamp <* A.endOfInput)
77parseDate :: B.ByteString -> Either String Date
78parseDate :: B.ByteString -> Either String Date
78parseDate = A.parseOnly (getDate <* A.endOfInput)
79parseDate = A.parseOnly (getDate <* A.endOfInput)
79
80
81+parseCalendarDiffTime :: B.ByteString -> Either String CalendarDiffTime
82+parseCalendarDiffTime = A.parseOnly (getCalendarDiffTime <* A.endOfInput)
83+
80getUnbounded :: A.Parser a -> A.Parser (Unbounded a)
84getUnbounded :: A.Parser a -> A.Parser (Unbounded a)
81getUnbounded getFinite
85getUnbounded getFinite
82 = (pure NegInfinity <* A.string "-infinity")
86 = (pure NegInfinity <* A.string "-infinity")
@@ -125,6 +129,9 @@ getUTCTime = TP.utcTime
125getUTCTimestamp :: A.Parser UTCTimestamp
129getUTCTimestamp :: A.Parser UTCTimestamp
126getUTCTimestamp = getUnbounded getUTCTime
130getUTCTimestamp = getUnbounded getUTCTime
127
131
132+getCalendarDiffTime :: A.Parser CalendarDiffTime
133+getCalendarDiffTime = TP.calendarDiffTime
134+
128dayToBuilder :: Day -> Builder
135dayToBuilder :: Day -> Builder
129dayToBuilder = primBounded TPP.day
136dayToBuilder = primBounded TPP.day
130
137
@@ -164,3 +171,6 @@ dateToBuilder = unboundedToBuilder dayToBuilder
164
171
165nominalDiffTimeToBuilder :: NominalDiffTime -> Builder
172nominalDiffTimeToBuilder :: NominalDiffTime -> Builder
166nominalDiffTimeToBuilder = TPP.nominalDiffTime
173nominalDiffTimeToBuilder = TPP.nominalDiffTime
174+
175+calendarDiffTimeToBuilder :: CalendarDiffTime -> Builder
176+calendarDiffTimeToBuilder = TPP.calendarDiffTime
src/Database/PostgreSQL/Simple/Time/Internal/Parser.hs+12−3
@@ -21,20 +21,24 @@ module Database.PostgreSQL.Simple.Time.Internal.Parser
21 , localToUTCTimeOfDayHMS
21 , localToUTCTimeOfDayHMS
22 , utcTime
22 , utcTime
23 , zonedTime
23 , zonedTime
24+ , calendarDiffTime
24 ) where
25 ) where
25
26
26import Control.Applicative ((<$>), (<*>), (<*), (*>))
27import Control.Applicative ((<$>), (<*>), (<*), (*>))
27import Database.PostgreSQL.Simple.Compat (toPico)
28import Database.PostgreSQL.Simple.Compat (toPico)
28import Data.Attoparsec.ByteString.Char8 as A
29import Data.Attoparsec.ByteString.Char8 as A
29import Data.Bits ((.&.))
30import Data.Bits ((.&.))
31+import Data.ByteString (ByteString)
30import Data.Char (ord)
32import Data.Char (ord)
31import Data.Fixed (Pico)
33import Data.Fixed (Pico)
32import Data.Int (Int64)
34import Data.Int (Int64)
33import Data.Maybe (fromMaybe)
35import Data.Maybe (fromMaybe)
34−import Data.Time.Calendar (Day, fromGregorianValid, addDays)
36+import Data.Time.Calendar.Compat (Day, fromGregorianValid, addDays)
35−import Data.Time.Clock (UTCTime(..))
37+import Data.Time.Clock.Compat (UTCTime(..))
38+import Data.Time.Format.ISO8601.Compat (iso8601ParseM)
39+import Data.Time.LocalTime.Compat (CalendarDiffTime)
36import qualified Data.ByteString.Char8 as B8
40import qualified Data.ByteString.Char8 as B8
37−import qualified Data.Time.LocalTime as Local
41+import qualified Data.Time.LocalTime.Compat as Local
38
42
39-- | Parse a date of the form @YYYY-MM-DD@.
43-- | Parse a date of the form @YYYY-MM-DD@.
40day :: Parser Day
44day :: Parser Day
@@ -193,3 +197,8 @@ zonedTime = Local.ZonedTime <$> localTime <*> (fromMaybe utc <$> timeZone)
193
197
194utc :: Local.TimeZone
198utc :: Local.TimeZone
195utc = Local.TimeZone 0 False ""
199utc = Local.TimeZone 0 False ""
200+
201+calendarDiffTime :: Parser CalendarDiffTime
202+calendarDiffTime = do
203+ contents <- takeByteString
204+ iso8601ParseM $ B8.unpack contents
src/Database/PostgreSQL/Simple/Time/Internal/Printer.hs+14−2
@@ -17,19 +17,23 @@ module Database.PostgreSQL.Simple.Time.Internal.Printer
17 , localTime
17 , localTime
18 , zonedTime
18 , zonedTime
19 , nominalDiffTime
19 , nominalDiffTime
20+ , calendarDiffTime
20 ) where
21 ) where
21
22
22import Control.Arrow ((>>>))
23import Control.Arrow ((>>>))
23−import Data.ByteString.Builder (Builder, integerDec)
24+import Data.ByteString.Builder (Builder, byteString, integerDec)
24import Data.ByteString.Builder.Prim
25import Data.ByteString.Builder.Prim
25 ( liftFixedToBounded, (>$<), (>*<)
26 ( liftFixedToBounded, (>$<), (>*<)
26 , BoundedPrim, primBounded, condB, emptyB, FixedPrim, char8, int32Dec)
27 , BoundedPrim, primBounded, condB, emptyB, FixedPrim, char8, int32Dec)
27import Data.Char ( chr )
28import Data.Char ( chr )
28import Data.Int ( Int32, Int64 )
29import Data.Int ( Int32, Int64 )
29−import Data.Time
30+import Data.String (fromString)
31+import Data.Time.Compat
30 ( UTCTime(..), ZonedTime(..), LocalTime(..), NominalDiffTime
32 ( UTCTime(..), ZonedTime(..), LocalTime(..), NominalDiffTime
31 , Day, toGregorian, TimeOfDay(..), timeToTimeOfDay
33 , Day, toGregorian, TimeOfDay(..), timeToTimeOfDay
32 , TimeZone, timeZoneMinutes )
34 , TimeZone, timeZoneMinutes )
35+import Data.Time.Format.ISO8601.Compat (iso8601Show)
36+import Data.Time.LocalTime.Compat (CalendarDiffTime)
33import Database.PostgreSQL.Simple.Compat ((<>), fromPico)
37import Database.PostgreSQL.Simple.Compat ((<>), fromPico)
34import Unsafe.Coerce (unsafeCoerce)
38import Unsafe.Coerce (unsafeCoerce)
35
39
@@ -121,3 +125,11 @@ nominalDiffTime :: NominalDiffTime -> Builder
121nominalDiffTime xy = integerDec x <> primBounded frac (abs (fromIntegral y))
125nominalDiffTime xy = integerDec x <> primBounded frac (abs (fromIntegral y))
122 where
126 where
123 (x,y) = fromPico (unsafeCoerce xy) `quotRem` 1000000000000
127 (x,y) = fromPico (unsafeCoerce xy) `quotRem` 1000000000000
128+
129+calendarDiffTime :: CalendarDiffTime -> Builder
130+calendarDiffTime = byteString
131+ . fromString
132+ -- from the docs: "Beware: fromString truncates multi-byte characters to octets".
133+ -- However, I think this is a safe usage, because ISO8601-encoding seems restricted
134+ -- to ASCII output.
135+ . iso8601Show
src/Database/PostgreSQL/Simple/ToField.hs+19−8
@@ -1,5 +1,6 @@
1{-# LANGUAGE CPP, DeriveDataTypeable, DeriveFunctor #-}
1{-# LANGUAGE CPP, DeriveDataTypeable, DeriveFunctor #-}
2{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}
2{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}
3+{-# LANGUAGE PolyKinds #-}
3
4
4------------------------------------------------------------------------------
5------------------------------------------------------------------------------
5-- |
6-- |
@@ -22,6 +23,7 @@ module Database.PostgreSQL.Simple.ToField
22 , inQuotes
23 , inQuotes
23 ) where
24 ) where
24
25
26+import Control.Applicative (Const(Const))
25import qualified Data.Aeson as JSON
27import qualified Data.Aeson as JSON
26import Data.ByteString (ByteString)
28import Data.ByteString (ByteString)
27import Data.ByteString.Builder
29import Data.ByteString.Builder
@@ -30,10 +32,12 @@ import Data.ByteString.Builder
30 , wordDec, word8Dec, word16Dec, word32Dec, word64Dec
32 , wordDec, word8Dec, word16Dec, word32Dec, word64Dec
31 , floatDec, doubleDec
33 , floatDec, doubleDec
32 )
34 )
35+import Data.Functor.Identity (Identity(Identity))
33import Data.Int (Int8, Int16, Int32, Int64)
36import Data.Int (Int8, Int16, Int32, Int64)
34import Data.List (intersperse)
37import Data.List (intersperse)
35import Data.Monoid (mappend)
38import Data.Monoid (mappend)
36−import Data.Time (Day, TimeOfDay, LocalTime, UTCTime, ZonedTime, NominalDiffTime)
39+import Data.Time.Compat (Day, TimeOfDay, LocalTime, UTCTime, ZonedTime, NominalDiffTime)
40+import Data.Time.LocalTime.Compat (CalendarDiffTime)
37import Data.Typeable (Typeable)
41import Data.Typeable (Typeable)
38import Data.Word (Word, Word8, Word16, Word32, Word64)
42import Data.Word (Word, Word8, Word16, Word32, Word64)
39import {-# SOURCE #-} Database.PostgreSQL.Simple.ToRow
43import {-# SOURCE #-} Database.PostgreSQL.Simple.ToRow
@@ -55,11 +59,7 @@ import qualified Data.Vector as V
55import qualified Database.PostgreSQL.LibPQ as PQ
59import qualified Database.PostgreSQL.LibPQ as PQ
56import Database.PostgreSQL.Simple.Time
60import Database.PostgreSQL.Simple.Time
57import Data.Scientific (Scientific)
61import Data.Scientific (Scientific)
58−#if MIN_VERSION_scientific(0,3,0)
59import Data.Text.Lazy.Builder.Scientific (scientificBuilder)
62import Data.Text.Lazy.Builder.Scientific (scientificBuilder)
60−#else
61−import Data.Scientific (scientificBuilder)
62−#endif
63import Foreign.C.Types (CUInt(..))
63import Foreign.C.Types (CUInt(..))
64
64
65-- | How to render an element when substituting it into a query.
65-- | How to render an element when substituting it into a query.
@@ -100,6 +100,14 @@ instance ToField Action where
100 toField a = a
100 toField a = a
101 {-# INLINE toField #-}
101 {-# INLINE toField #-}
102
102
103+instance (ToField a) => ToField (Const a b) where
104+ toField (Const a) = toField a
105+ {-# INLINE toField #-}
106+
107+instance (ToField a) => ToField (Identity a) where
108+ toField (Identity a) = toField a
109+ {-# INLINE toField #-}
110+
103instance (ToField a) => ToField (Maybe a) where
111instance (ToField a) => ToField (Maybe a) where
104 toField Nothing = renderNull
112 toField Nothing = renderNull
105 toField (Just a) = toField a
113 toField (Just a) = toField a
@@ -282,6 +290,10 @@ instance ToField NominalDiffTime where
282 toField = Plain . inQuotes . nominalDiffTimeToBuilder
290 toField = Plain . inQuotes . nominalDiffTimeToBuilder
283 {-# INLINE toField #-}
291 {-# INLINE toField #-}
284
292
293+instance ToField CalendarDiffTime where
294+ toField = Plain . inQuotes . calendarDiffTimeToBuilder
295+ {-# INLINE toField #-}
296+
285instance (ToField a) => ToField (PGArray a) where
297instance (ToField a) => ToField (PGArray a) where
286 toField pgArray =
298 toField pgArray =
287 case fromPGArray pgArray of
299 case fromPGArray pgArray of
@@ -302,14 +314,13 @@ instance ToField UUID where
302instance ToField JSON.Value where
314instance ToField JSON.Value where
303 toField = toField . JSON.encode
315 toField = toField . JSON.encode
304
316
305−-- | Convert a Haskell value to a JSON 'JSON.Value' using
317+-- | Convert a Haskell value to JSON using 'JSON.toEncoding'.
306−-- 'JSON.toJSON' and convert that to a field using 'toField'.
307--
318--
308-- This can be used as the default implementation for the 'toField'
319-- This can be used as the default implementation for the 'toField'
309-- method for Haskell types that have a JSON representation in
320-- method for Haskell types that have a JSON representation in
310-- PostgreSQL.
321-- PostgreSQL.
311toJSONField :: JSON.ToJSON a => a -> Action
322toJSONField :: JSON.ToJSON a => a -> Action
312−toJSONField = toField . JSON.toJSON
323+toJSONField = toField . JSON.encode
313
324
314-- | Surround a string with single-quote characters: \"@'@\"
325-- | Surround a string with single-quote characters: \"@'@\"
315--
326--
src/Database/PostgreSQL/Simple/Transaction.hs+16−12
@@ -17,6 +17,7 @@ module Database.PostgreSQL.Simple.Transaction
17 , withTransactionLevel
17 , withTransactionLevel
18 , withTransactionMode
18 , withTransactionMode
19 , withTransactionModeRetry
19 , withTransactionModeRetry
20+ , withTransactionModeRetry'
20 , withTransactionSerializable
21 , withTransactionSerializable
21 , TransactionMode(..)
22 , TransactionMode(..)
22 , IsolationLevel(..)
23 , IsolationLevel(..)
@@ -146,31 +147,34 @@ withTransactionMode mode conn act =
146 commit conn
147 commit conn
147 return r
148 return r
148
149
150+-- | 'withTransactionModeRetry'' but with the exception type pinned to 'SqlError'.
151+withTransactionModeRetry :: TransactionMode -> (SqlError -> Bool) -> Connection -> IO a -> IO a
152+withTransactionModeRetry = withTransactionModeRetry'
153+
149-- | Like 'withTransactionMode', but also takes a custom callback to
154-- | Like 'withTransactionMode', but also takes a custom callback to
150−-- determine if a transaction should be retried if an 'SqlError' occurs.
155+-- determine if a transaction should be retried if an exception occurs.
151−-- If the callback returns True, then the transaction will be retried.
156+-- If the callback returns 'True', then the transaction will be retried.
152−-- If the callback returns False, or an exception other than an 'SqlError'
157+-- If the callback returns 'False', or an exception other than an @e@
153-- occurs then the transaction will be rolled back and the exception rethrown.
158-- occurs then the transaction will be rolled back and the exception rethrown.
154--
159--
155-- This is used to implement 'withTransactionSerializable'.
160-- This is used to implement 'withTransactionSerializable'.
156−withTransactionModeRetry :: TransactionMode -> (SqlError -> Bool) -> Connection -> IO a -> IO a
161+withTransactionModeRetry' :: forall a e. E.Exception e => TransactionMode -> (e -> Bool) -> Connection -> IO a -> IO a
157−withTransactionModeRetry mode shouldRetry conn act =
162+withTransactionModeRetry' mode shouldRetry conn act =
158 mask $ \restore ->
163 mask $ \restore ->
159 retryLoop $ E.try $ do
164 retryLoop $ E.try $ do
160− a <- restore act
165+ a <- restore act `E.onException` rollback_ conn
161 commit conn
166 commit conn
162 return a
167 return a
163 where
168 where
164− retryLoop :: IO (Either E.SomeException a) -> IO a
169+ retryLoop :: IO (Either e a) -> IO a
165 retryLoop act' = do
170 retryLoop act' = do
166 beginMode mode conn
171 beginMode mode conn
167 r <- act'
172 r <- act'
168 case r of
173 case r of
169− Left e -> do
174+ Left e ->
170− rollback_ conn
171− case fmap shouldRetry (E.fromException e) of
175+ case shouldRetry e of
172− Just True -> retryLoop act'
176+ True -> retryLoop act'
173− _ -> E.throwIO e
177+ False -> E.throwIO e
174 Right a ->
178 Right a ->
175 return a
179 return a
176
180
src/Database/PostgreSQL/Simple/Types.hs−7
@@ -50,10 +50,6 @@ import Database.PostgreSQL.Simple.Compat (toByteString)
50data Null = Null
50data Null = Null
51 deriving (Read, Show, Typeable)
51 deriving (Read, Show, Typeable)
52
52
53−instance Eq Null where
54− _ == _ = False
55− _ /= _ = False
56−
57-- | A placeholder for the PostgreSQL @DEFAULT@ value.
53-- | A placeholder for the PostgreSQL @DEFAULT@ value.
58data Default = Default
54data Default = Default
59 deriving (Read, Show, Typeable)
55 deriving (Read, Show, Typeable)
@@ -97,9 +93,6 @@ instance Semigroup Query where
97
93
98instance Monoid Query where
94instance Monoid Query where
99 mempty = Query B.empty
95 mempty = Query B.empty
100−#if !(MIN_VERSION_base(4,11,0))
101− mappend = (<>)
102−#endif
103
96
104-- | Wrap a list of values for use in an @IN@ clause. Replaces a
97-- | Wrap a list of values for use in an @IN@ clause. Replaces a
105-- single \"@?@\" character with a parenthesized list of rendered
98-- single \"@?@\" character with a parenthesized list of rendered
test/Exception.hsnew file+70
@@ -0,0 +1,70 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+
5+module Exception (testExceptions) where
6+
7+import Database.PostgreSQL.Simple
8+import Test.Tasty.HUnit (Assertion, assertBool)
9+import Common (TestEnv)
10+import Control.Exception (Exception (..), SomeException)
11+import Data.Maybe (isJust)
12+import Data.Either (isLeft)
13+import Control.Exception (throwIO, try)
14+
15+testExceptions :: TestEnv -> Assertion
16+testExceptions _ = do
17+ let sqlError = SqlError
18+ { sqlState = ""
19+ , sqlExecStatus = FatalError
20+ , sqlErrorMsg = ""
21+ , sqlErrorDetail = ""
22+ , sqlErrorHint = ""
23+ }
24+ let sqlEx :: SomeException = toException sqlError
25+ assertBool "SqlError is SomePostgreSqlException" $ isJust (fromException sqlEx :: Maybe SomePostgreSqlException)
26+ assertBool "SqlError is SqlError" $ isJust (fromException sqlEx :: Maybe SqlError)
27+ eSqlError :: Either SqlError () <- try $ throwIO sqlEx
28+ assertBool "Can catch SqlError" $ isLeft eSqlError
29+ eSqlPostgreSqlEx :: Either SomePostgreSqlException () <- try $ throwIO sqlEx
30+ assertBool "Can catch SomePostgreSqlException from SqlError" $ isLeft eSqlPostgreSqlEx
31+
32+ let formatError = FormatError
33+ { fmtMessage = ""
34+ , fmtQuery = ""
35+ , fmtParams = []
36+ }
37+ let formatEx :: SomeException = toException formatError
38+ assertBool "FormatError is SomePostgreSqlException" $ isJust (fromException formatEx :: Maybe SomePostgreSqlException)
39+ assertBool "FormatError is FormatError" $ isJust (fromException formatEx :: Maybe FormatError)
40+ eFormatError :: Either FormatError () <- try $ throwIO formatEx
41+ assertBool "Can catch FormatError" $ isLeft eFormatError
42+ eFormatPostreSqlEx :: Either SomePostgreSqlException () <- try $ throwIO formatEx
43+ assertBool "Can catch SomePostgreSqlException from FormatError" $ isLeft eFormatPostreSqlEx
44+
45+ let queryError = QueryError
46+ { qeMessage = ""
47+ , qeQuery = ""
48+ }
49+ let queryEx :: SomeException = toException queryError
50+ assertBool "QueryError is SomePostgreSqlException" $ isJust (fromException queryEx :: Maybe SomePostgreSqlException)
51+ assertBool "QueryError is QueryError" $ isJust (fromException queryEx :: Maybe QueryError)
52+ eQueryError :: Either QueryError () <- try $ throwIO queryEx
53+ assertBool "Can catch QueryError" $ isLeft eQueryError
54+ eQueryPostgreSqlEx :: Either SomePostgreSqlException () <- try $ throwIO queryEx
55+ assertBool "Can catch SomePostgreSqlException from QueryError" $ isLeft eQueryPostgreSqlEx
56+
57+ let resultError = Incompatible
58+ { errSQLType = ""
59+ , errSQLTableOid = Nothing
60+ , errSQLField = ""
61+ , errHaskellType = ""
62+ , errMessage = ""
63+ }
64+ let resultEx :: SomeException = toException resultError
65+ assertBool "ResultError is SomePostgreSqlException" $ isJust (fromException resultEx :: Maybe SomePostgreSqlException)
66+ assertBool "ResultError is ResultError" $ isJust (fromException resultEx :: Maybe ResultError)
67+ eResultEx :: Either ResultError () <- try $ throwIO resultEx
68+ assertBool "Can catch ResultError" $ isLeft eResultEx
69+ eResultPostgreSqlEx :: Either SomePostgreSqlException () <- try $ throwIO resultEx
70+ assertBool "Can catch SomePostgreSqlException from ResultError" $ isLeft eResultPostgreSqlEx
test/Inspection.hs+6−1
@@ -53,7 +53,12 @@ inspectionTests = testGroup "inspection"
53
53
54 -- byteaOid isn't inlined?
54 -- byteaOid isn't inlined?
55 , testCase "inlineTypoid" $
55 , testCase "inlineTypoid" $
56+#if __GLASGOW_HASKELL__ >= 808
57+ assertSuccess
58+#else
59+ assertFailure'
60+#endif
56− assertFailure' $(inspectTest $ 'lhs02 ==- 'rhs02)
61+ $(inspectTest $ 'lhs02 ==- 'rhs02)
57 ]
62 ]
58
63
59assertSuccess :: Result -> IO ()
64assertSuccess :: Result -> IO ()
test/Interval.hsnew file+176
@@ -0,0 +1,176 @@
1+{-# LANGUAGE QuasiQuotes #-}
2+
3+{-
4+
5+Testing strategies:
6+
7+fromString . toString == id ** Todo?
8+
9+toString . fromString == almost id ** Todo?
10+
11+postgresql -> haskell -> postgresql * Done
12+
13+haskell -> postgresql -> haskell ** Todo?
14+
15+But still, what we really want to establish is that the two values
16+correspond; for example, a conversion that consistently added hour
17+when printed to a string and subtracted an hour when parsed from string
18+would still pass these tests.
19+
20+
21+Right now, we are checking that 1400+ timestamps in the range of 1860 to
22+2060 round trip from postgresql to haskell and back in 5 different timezones.
23+In addition to UTC, the four timezones were selected so that 2 have a positive
24+offset, and 2 have a negative offset, and that 2 have an offset of a
25+whole number of hours, while the other two do not.
26+
27+It may be worth adding a few more timezones to ensure better test coverage.
28+
29+We are checking a handful of selected timestamps to ensure we hit
30+various corner-cases in the code, in addition to 1400 timestamps randomly
31+generated with granularity of seconds down to microseconds in powers of ten.
32+
33+-}
34+
35+module Interval (testInterval) where
36+
37+import Common
38+import Control.Monad(forM_, replicateM_)
39+import Data.Time.Compat
40+import Data.Time.LocalTime.Compat (CalendarDiffTime(..))
41+import Data.ByteString(ByteString)
42+import Database.PostgreSQL.Simple.SqlQQ
43+
44+data IntervalTestCase = IntervalTestCase
45+ { label :: String
46+ , inputMonths :: Integer
47+ , inputSeconds :: NominalDiffTime
48+ , asText :: String
49+ }
50+ deriving (Eq, Show)
51+
52+testInterval :: TestEnv -> Assertion
53+testInterval env@TestEnv{..} = do
54+
55+ initializeTable env
56+
57+ let milliseconds = 0.001
58+ seconds = 1
59+ minutes = 60 * seconds
60+ hours = 60 * minutes
61+ days = 24 * hours
62+ weeks = 7 * days
63+ months = 1
64+ years = 12 * months
65+
66+ mapM (checkRoundTrip env)
67+ [ IntervalTestCase
68+ { label = "zero"
69+ , inputMonths = 0
70+ , inputSeconds = 0
71+ , asText = "PT0"
72+ }
73+ , IntervalTestCase
74+ { label = "1 year"
75+ , inputMonths = 1 * years
76+ , inputSeconds = 0
77+ , asText = "P1Y"
78+ }
79+ , IntervalTestCase
80+ { label = "2 months"
81+ , inputMonths = 2 * months
82+ , inputSeconds = 0
83+ , asText = "P2M"
84+ }
85+ , IntervalTestCase
86+ { label = "3 weeks"
87+ , inputMonths = 0
88+ , inputSeconds = 3 * weeks
89+ , asText = "P3W"
90+ }
91+ , IntervalTestCase
92+ { label = "4 days"
93+ , inputMonths = 0
94+ , inputSeconds = 4 * days
95+ , asText = "P4D"
96+ }
97+ , IntervalTestCase
98+ { label = "5 hours"
99+ , inputMonths = 0
100+ , inputSeconds = 5 * hours
101+ , asText = "PT5H"
102+ }
103+ , IntervalTestCase
104+ { label = "6 minutes"
105+ , inputMonths = 0
106+ , inputSeconds = 6 * minutes
107+ , asText = "PT6M"
108+ }
109+ , IntervalTestCase
110+ { label = "7 seconds"
111+ , inputMonths = 0
112+ , inputSeconds = 7 * seconds
113+ , asText = "PT7S"
114+ }
115+ , IntervalTestCase
116+ { label = "8 milliseconds"
117+ , inputMonths = 0
118+ , inputSeconds = 8 * milliseconds
119+ , asText = "PT0.008S"
120+ }
121+ , IntervalTestCase
122+ { label = "combination of intervals (day-size or bigger)"
123+ , inputMonths = 2 * years + 4 * months
124+ , inputSeconds = 3 * weeks + 5 * days
125+ , asText = "P2Y4M3W5D"
126+ }
127+ , IntervalTestCase
128+ { label = "combination of intervals (smaller than day-size)"
129+ , inputMonths = 0
130+ , inputSeconds = 18 * hours + 56 * minutes + 23 * seconds + 563 * milliseconds
131+ , asText = "PT18H56M23.563S"
132+ }
133+ , IntervalTestCase
134+ { label = "full combination of intervals"
135+ , inputMonths = 2 * years + 4 * months
136+ , inputSeconds = 3 * weeks + 5 * days + 18 * hours + 56 * minutes + 23 * seconds + 563 * milliseconds
137+ , asText = "P2Y4M3W5DT18H56M23.563S"
138+ }
139+ ]
140+
141+ return ()
142+
143+initializeTable :: TestEnv -> IO ()
144+initializeTable TestEnv{..} = withTransaction conn $ do
145+ execute_ conn
146+ [sql| CREATE TEMPORARY TABLE testinterval
147+ ( id serial, sample interval, PRIMARY KEY(id) ) |]
148+
149+ return ()
150+
151+checkRoundTrip :: TestEnv -> IntervalTestCase -> IO ()
152+checkRoundTrip TestEnv{..} IntervalTestCase{..} = do
153+
154+ let input = CalendarDiffTime
155+ { ctMonths = inputMonths
156+ , ctTime = inputSeconds
157+ }
158+
159+ [(returnedId :: Int, output :: CalendarDiffTime)] <- query conn [sql|
160+ INSERT INTO testinterval (sample)
161+ VALUES (?)
162+ RETURNING id, sample
163+ |] (Only input)
164+
165+ assertBool ("CalendarDiffTime did not round-trip from Haskell to SQL and back (" ++ label ++ ")") $
166+ output == input
167+
168+ [(Only isExpectedIso)] <- query conn [sql|
169+ SELECT sample = (?)::interval
170+ FROM testinterval
171+ WHERE id = ?
172+ |] (asText, returnedId)
173+
174+ assertBool ("CalendarDiffTime inserted did not match ISO8601 equivalent \"" ++ asText ++ "\". (" ++ label ++ ")")
175+ isExpectedIso
176+
test/Main.hs+95−10
@@ -3,10 +3,19 @@
3{-# LANGUAGE DeriveDataTypeable #-}
3{-# LANGUAGE DeriveDataTypeable #-}
4{-# LANGUAGE DoAndIfThenElse #-}
4{-# LANGUAGE DoAndIfThenElse #-}
5{-# LANGUAGE ScopedTypeVariables #-}
5{-# LANGUAGE ScopedTypeVariables #-}
6+#if __GLASGOW_HASKELL__ >= 806
7+{-# LANGUAGE DerivingStrategies #-}
8+{-# LANGUAGE DerivingVia #-}
9+{-# LANGUAGE DeriveAnyClass #-}
10+#endif
11+module Main (main) where
12+
6import Common
13import Common
7import Database.PostgreSQL.Simple.Copy
14import Database.PostgreSQL.Simple.Copy
15+import Database.PostgreSQL.Simple.ToField (ToField)
8import Database.PostgreSQL.Simple.FromField (FromField)
16import Database.PostgreSQL.Simple.FromField (FromField)
9import Database.PostgreSQL.Simple.HStore
17import Database.PostgreSQL.Simple.HStore
18+import Database.PostgreSQL.Simple.Newtypes
10import Database.PostgreSQL.Simple.Internal (breakOnSingleQuestionMark)
19import Database.PostgreSQL.Simple.Internal (breakOnSingleQuestionMark)
11import Database.PostgreSQL.Simple.Types(Query(..),Values(..), PGArray(..))
20import Database.PostgreSQL.Simple.Types(Query(..),Values(..), PGArray(..))
12import qualified Database.PostgreSQL.Simple.Transaction as ST
21import qualified Database.PostgreSQL.Simple.Transaction as ST
@@ -15,6 +24,7 @@ import Control.Applicative
15import Control.Exception as E
24import Control.Exception as E
16import Control.Monad
25import Control.Monad
17import Data.Char
26import Data.Char
27+import Data.Foldable (toList)
18import Data.List (concat, sort)
28import Data.List (concat, sort)
19import Data.IORef
29import Data.IORef
20import Data.Monoid ((<>))
30import Data.Monoid ((<>))
@@ -25,6 +35,7 @@ import GHC.Generics (Generic)
25import Data.Aeson
35import Data.Aeson
26import Data.ByteString (ByteString)
36import Data.ByteString (ByteString)
27import qualified Data.ByteString as B
37import qualified Data.ByteString as B
38+import qualified Data.ByteString.Char8 as BS8
28import qualified Data.ByteString.Lazy.Char8 as BL
39import qualified Data.ByteString.Lazy.Char8 as BL
29import Data.CaseInsensitive (CI)
40import Data.CaseInsensitive (CI)
30import qualified Data.CaseInsensitive as CI
41import qualified Data.CaseInsensitive as CI
@@ -35,13 +46,16 @@ import qualified Data.Text.Encoding as T
35import qualified Data.Vector as V
46import qualified Data.Vector as V
36import System.FilePath
47import System.FilePath
37import System.Timeout(timeout)
48import System.Timeout(timeout)
38−import Data.Time(getCurrentTime, diffUTCTime)
49+import Data.Time.Compat (getCurrentTime, diffUTCTime)
50+import System.Environment (getEnvironment)
39
51
40import Test.Tasty
52import Test.Tasty
41import Test.Tasty.Golden
53import Test.Tasty.Golden
42import Notify
54import Notify
43import Serializable
55import Serializable
44import Time
56import Time
57+import Interval
58+import Exception (testExceptions)
45
59
46tests :: TestEnv -> TestTree
60tests :: TestEnv -> TestTree
47tests env = testGroup "tests"
61tests env = testGroup "tests"
@@ -52,11 +66,14 @@ tests env = testGroup "tests"
52 , testCase "Notify" . testNotify
66 , testCase "Notify" . testNotify
53 , testCase "Serializable" . testSerializable
67 , testCase "Serializable" . testSerializable
54 , testCase "Time" . testTime
68 , testCase "Time" . testTime
69+ , testCase "Interval" . testInterval
55 , testCase "Array" . testArray
70 , testCase "Array" . testArray
56 , testCase "Array of nullables" . testNullableArray
71 , testCase "Array of nullables" . testNullableArray
57 , testCase "HStore" . testHStore
72 , testCase "HStore" . testHStore
58 , testCase "citext" . testCIText
73 , testCase "citext" . testCIText
59 , testCase "JSON" . testJSON
74 , testCase "JSON" . testJSON
75+ , testCase "Aeson newtype" . testAeson
76+ , testCase "DerivingVia" . testDerivingVia
60 , testCase "Question mark escape" . testQM
77 , testCase "Question mark escape" . testQM
61 , testCase "Savepoint" . testSavepoint
78 , testCase "Savepoint" . testSavepoint
62 , testCase "Unicode" . testUnicode
79 , testCase "Unicode" . testUnicode
@@ -68,6 +85,7 @@ tests env = testGroup "tests"
68 , testCase "2-ary generic" . testGeneric2
85 , testCase "2-ary generic" . testGeneric2
69 , testCase "3-ary generic" . testGeneric3
86 , testCase "3-ary generic" . testGeneric3
70 , testCase "Timeout" . testTimeout
87 , testCase "Timeout" . testTimeout
88+ , testCase "Exceptions" . testExceptions
71 ]
89 ]
72
90
73testBytea :: TestEnv -> TestTree
91testBytea :: TestEnv -> TestTree
@@ -121,7 +139,7 @@ testFold TestEnv{..} = do
121 xs <- readIORef ref
139 xs <- readIORef ref
122 writeIORef ref $! (a,b):xs
140 writeIORef ref $! (a,b):xs
123 xs <- readIORef ref
141 xs <- readIORef ref
124− reverse xs @?= [(a,b) | a <- [1..100], b <- [1..50]]
142+ reverse xs @?= [(a,b) | b <- [1..50], a <- [1..100]]
125
143
126 -- Make sure fold propagates our exception.
144 -- Make sure fold propagates our exception.
127 ref <- newIORef []
145 ref <- newIORef []
@@ -238,6 +256,40 @@ testJSON TestEnv{..} = do
238 js' <- query conn "SELECT ?::json" js
256 js' <- query conn "SELECT ?::json" js
239 [js] @?= js'
257 [js] @?= js'
240
258
259+testAeson :: TestEnv -> Assertion
260+testAeson TestEnv{..} = do
261+ roundTrip (Map.fromList [] :: Map Text Text)
262+ roundTrip (Map.fromList [("foo","bar"),("bar","baz"),("baz","hello")] :: Map Text Text)
263+ roundTrip (Map.fromList [("fo\"o","bar"),("b\\ar","baz"),("baz","\"value\\with\"escapes")] :: Map Text Text)
264+ roundTrip (V.fromList [1,2,3,4,5::Int])
265+ roundTrip ("foo" :: Text)
266+ roundTrip (42 :: Int)
267+ where
268+ roundTrip :: (Eq a, Show a, Typeable a, ToJSON a, FromJSON a)=> a -> Assertion
269+ roundTrip x = do
270+ y <- query conn "SELECT ?::json" (Only (Aeson x))
271+ [Only (Aeson x)] @?= y
272+
273+testDerivingVia :: TestEnv -> Assertion
274+testDerivingVia TestEnv{..} = do
275+#if __GLASGOW_HASKELL__ <806
276+ return ()
277+#else
278+ roundTrip $ DerivingVia1 42 "Meaning of Life"
279+ where
280+ roundTrip :: (Eq a, Show a, Typeable a, ToField a, FromField a)=> a -> Assertion
281+ roundTrip x = do
282+ y <- query conn "SELECT ?::json" (Only x)
283+ [Only x] @?= y
284+
285+data DerivingVia1 = DerivingVia1 Int String
286+ deriving stock (Eq, Show, Generic)
287+ deriving anyclass (FromJSON, ToJSON)
288+ deriving (ToField, FromField) via Aeson DerivingVia1
289+
290+#endif
291+
292+
241testQM :: TestEnv -> Assertion
293testQM :: TestEnv -> Assertion
242testQM TestEnv{..} = do
294testQM TestEnv{..} = do
243 -- Just test on a single string
295 -- Just test on a single string
@@ -395,6 +447,14 @@ testCopy TestEnv{..} = do
395 -- so that we can issue more queries:
447 -- so that we can issue more queries:
396 [Only (x::Int)] <- query_ conn "SELECT 2 + 2"
448 [Only (x::Int)] <- query_ conn "SELECT 2 + 2"
397 x @?= 4
449 x @?= 4
450+ -- foldCopyData
451+ copy_ conn "COPY copy_test TO STDOUT (FORMAT CSV)"
452+ (acc, count) <- foldCopyData conn
453+ (\acc row -> return (row:acc))
454+ (\acc count -> return (acc, count))
455+ []
456+ sort acc @?= sort copyRows
457+ count @?= 2
398 where
458 where
399 copyRows = ["1,foo\n"
459 copyRows = ["1,foo\n"
400 ,"2,bar\n"]
460 ,"2,bar\n"]
@@ -414,12 +474,13 @@ testCopyFailures env = testGroup "Copy failures"
414
474
415goldenTest :: TestName -> IO BL.ByteString -> TestTree
475goldenTest :: TestName -> IO BL.ByteString -> TestTree
416goldenTest testName =
476goldenTest testName =
417− goldenVsString testName (resultsDir </> fileName<.>"expected")
477+ goldenVsStringDiff testName diff (resultsDir </> fileName<.>"expected")
418 where
478 where
419 resultsDir = "test" </> "results"
479 resultsDir = "test" </> "results"
420 fileName = map normalize testName
480 fileName = map normalize testName
421 normalize c | not (isAlpha c) = '-'
481 normalize c | not (isAlpha c) = '-'
422 | otherwise = c
482 | otherwise = c
483+ diff ref new = ["diff", "-u", ref, new]
423
484
424-- | Test that we provide a sensible error message on failure
485-- | Test that we provide a sensible error message on failure
425testCopyUniqueConstraintError :: TestEnv -> TestTree
486testCopyUniqueConstraintError :: TestEnv -> TestTree
@@ -546,18 +607,42 @@ isFormatError i FormatError{..}
546--
607--
547-- Note that some tests, such as Notify, use multiple connections, and assume
608-- Note that some tests, such as Notify, use multiple connections, and assume
548-- that 'testConnect' connects to the same database every time it is called.
609-- that 'testConnect' connects to the same database every time it is called.
549−testConnect :: IO Connection
610+withTestEnv :: ByteString -> (TestEnv -> IO a) -> IO a
550−testConnect = connectPostgreSQL ""
611+withTestEnv connstr cb =
612+ withConn $ \conn -> do
613+ -- currently required for interval to work.
614+ -- we also test that this doesn't interfere with anything else
615+ execute_ conn "SET intervalstyle TO 'iso_8601'"
551
616
552−withTestEnv :: (TestEnv -> IO a) -> IO a
553−withTestEnv cb =
554− withConn $ \conn ->
555 cb TestEnv
617 cb TestEnv
556 { conn = conn
618 { conn = conn
557 , withConn = withConn
619 , withConn = withConn
558 }
620 }
559 where
621 where
560− withConn = bracket testConnect close
622+ withConn = bracket (connectPostgreSQL connstr) close
561
623
562main :: IO ()
624main :: IO ()
625+main = withConnstring $ \connstring -> do
563−main = withTestEnv $ defaultMain . tests
626+ withTestEnv connstring (defaultMain . tests)
627+
628+withConnstring :: (BS8.ByteString -> IO ()) -> IO ()
629+withConnstring kont = do
630+ env <- getEnvironment
631+ case lookup "DATABASE_CONNSTRING" env of
632+ Just s -> kont (BS8.pack (special s))
633+ Nothing -> case lookup "GITHUB_ACTIONS" env of
634+ Just "true" -> kont (BS8.pack gha)
635+ _ -> putStrLn "Set DATABASE_CONNSTRING environment variable"
636+ where
637+ -- https://www.appveyor.com/docs/services-databases/
638+ special "appveyor" = "dbname='TestDb' user='postgres' password='Password12!'"
639+ special "travis" = ""
640+ special s = s
641+
642+ gha = unwords
643+ [ "dbname='postgres'"
644+ , "user='postgres'"
645+ , "password='postgres'"
646+ , "host='postgres'"
647+ , "port=5432"
648+ ]
test/Time.hs+1−1
@@ -36,7 +36,7 @@ module Time (testTime) where
36
36
37import Common
37import Common
38import Control.Monad(forM_, replicateM_)
38import Control.Monad(forM_, replicateM_)
39−import Data.Time
39+import Data.Time.Compat
40import Data.ByteString(ByteString)
40import Data.ByteString(ByteString)
41import Database.PostgreSQL.Simple.SqlQQ
41import Database.PostgreSQL.Simple.SqlQQ
42
42
test/results/malformed-input.expected+1−1
@@ -1,4 +1,4 @@
1user error (Database.PostgreSQL.Simple.Copy.putCopyEnd: failed to parse command status
1user error (Database.PostgreSQL.Simple.Copy.putCopyEnd: failed to parse command status
2−Connection error: ERROR: invalid input syntax for integer: "z"
2+Connection error: ERROR: invalid input syntax for type integer: "z"
3CONTEXT: COPY copy_unique_constraint_error_test, line 3, column x: "z"
3CONTEXT: COPY copy_unique_constraint_error_test, line 3, column x: "z"
4)
4)