Skip to content

Commit 4bafac9

Browse files
authored
Haskell acceptance tests and admin database parser changes (#1354)
### Tests Added a short Haskell integration tests suite. ### Admin database Changed how we parse commands (and created a bit of a mess). We first attempt to detect `SET` and `SHOW` using the pg parser. If that fails, we fallback to string interpolation. This allows us to respond to common commands executed by database drivers, e.g., `SHOW server_version` (which now returns the parser version we get from `pg_raw_parse`). This also allows us to parse and respond to multi-statement `SET` queries, e.g.: ```sql SET application_name TO 'persistent'; SET client_encoding TO 'utf8'; ``` without those getting mangled by our "string parser". We don't do anything if we see a variable we don't recognize, by the way. To add to the complexity, we have some commands that would match the `show` branch but not be valid `SHOW` commands, e.g., `SHOW CLIENTS <column list>`. Handling for these is duplicated because apparently that's the best I can come up with today.
1 parent 3ee07ab commit 4bafac9

13 files changed

Lines changed: 379 additions & 27 deletions

File tree

.github/workflows/ci.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ jobs:
7777
- { name: ruby, script: integration/ruby/run.sh }
7878
- { name: java, script: integration/java/run.sh }
7979
- { name: elixir, script: integration/elixir/run.sh, needs_beam: true }
80+
- { name: haskell, script: integration/haskell/run.sh, needs_haskell: true }
8081
- { name: mirror, script: integration/mirror/run.sh }
8182
- { name: sql, script: integration/sql/run.sh }
8283
- { name: toxi, script: integration/toxi/run.sh }
@@ -135,6 +136,21 @@ jobs:
135136
with:
136137
otp-version: "28"
137138
elixir-version: "1.20.3"
139+
- name: Install GHC/Cabal
140+
if: matrix.needs_haskell
141+
id: setup-haskell
142+
uses: haskell-actions/setup@v2
143+
with:
144+
ghc-version: "9.12.4"
145+
cabal-version: "latest"
146+
- name: Cache Cabal dependencies
147+
if: matrix.needs_haskell
148+
uses: actions/cache@v5
149+
with:
150+
path: ${{ steps.setup-haskell.outputs.cabal-store }}
151+
key: cabal-${{ runner.os }}-${{ steps.setup-haskell.outputs.ghc-version }}-${{ hashFiles('integration/haskell/*.cabal', 'integration/haskell/cabal.project', 'integration/haskell/**/*.hs') }}
152+
restore-keys: |
153+
cabal-${{ runner.os }}-${{ steps.setup-haskell.outputs.ghc-version }}-
138154
- name: Setup dependencies
139155
run: bash integration/ci/setup.sh --with-toxi
140156
- name: Run ${{ matrix.name }}

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ edition = "2024"
2222
pgdog-plugin = { path = "./pgdog-plugin", version = "0.4.0", default-features = false }
2323
pgdog-config = { path = "./pgdog-config", version = "0.1.0" }
2424
pgdog-postgres-types = { path = "./pgdog-postgres-types"}
25-
pg_raw_parse = { git = "https://github.com/pgdogdev/pg_raw_parse.git", rev = "8758803" }
25+
pg_raw_parse = { git = "https://github.com/pgdogdev/pg_raw_parse.git", rev = "6870860" }
2626
bon = "3.9"
2727
schemars = { version = "1.2.1", features = ["uuid1"] }
2828
serde_json = "1.0"

integration/haskell/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist-newstyle/

integration/haskell/cabal.project

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
packages: .
2+
3+
tests: true

integration/haskell/dev.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
5+
6+
pushd "${SCRIPT_DIR}"
7+
8+
has_required_version() {
9+
cabal list persistent-postgresql --simple-output |
10+
grep -Fqx 'persistent-postgresql 2.14.3.0'
11+
}
12+
13+
for attempt in 1 2 3; do
14+
if has_required_version; then
15+
break
16+
fi
17+
18+
echo "Hackage index is stale; refreshing (attempt ${attempt}/3)" >&2
19+
cabal update || true
20+
sleep $((attempt * 5))
21+
done
22+
23+
if ! has_required_version; then
24+
echo "Hackage index does not contain persistent-postgresql 2.14.3.0 after 3 refresh attempts" >&2
25+
exit 1
26+
fi
27+
28+
cabal test --test-show-details=direct
29+
popd
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
cabal-version: 3.0
2+
name: pgdog-haskell-integration
3+
version: 0.1.0.0
4+
build-type: Simple
5+
6+
test-suite persistent-integration
7+
type: exitcode-stdio-1.0
8+
main-is: Main.hs
9+
hs-source-dirs: test
10+
default-language: GHC2021
11+
ghc-options: -Wall
12+
build-depends:
13+
base >=4.18 && <5,
14+
hspec >=2.11 && <3,
15+
monad-logger >=0.3 && <0.4,
16+
persistent >=2.18.1 && <2.19,
17+
persistent-postgresql >=2.14.3 && <2.15,
18+
text >=1.2 && <3,
19+
transformers >=0.5 && <0.7

integration/haskell/run.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
5+
source "${SCRIPT_DIR}/../common.sh"
6+
7+
bash "${SCRIPT_DIR}/../ci/apt.sh" libpq-dev pkg-config
8+
9+
if ! command -v cabal >/dev/null 2>&1; then
10+
echo "cabal not found. Install GHC and Cabal, then re-run." >&2
11+
exit 1
12+
fi
13+
14+
run_pgdog
15+
wait_for_pgdog
16+
17+
bash ${SCRIPT_DIR}/dev.sh
18+
19+
stop_pgdog

integration/haskell/test/Main.hs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
{-# LANGUAGE DataKinds #-}
2+
{-# LANGUAGE DerivingStrategies #-}
3+
{-# LANGUAGE FlexibleInstances #-}
4+
{-# LANGUAGE GADTs #-}
5+
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
6+
{-# LANGUAGE MultiParamTypeClasses #-}
7+
{-# LANGUAGE OverloadedStrings #-}
8+
{-# LANGUAGE QuasiQuotes #-}
9+
{-# LANGUAGE StandaloneDeriving #-}
10+
{-# LANGUAGE TemplateHaskell #-}
11+
{-# LANGUAGE TypeFamilies #-}
12+
{-# LANGUAGE UndecidableInstances #-}
13+
14+
module Main (main) where
15+
16+
import Control.Exception (bracket_)
17+
import Control.Monad.IO.Class (liftIO)
18+
import Control.Monad.Logger (runNoLoggingT)
19+
import Control.Monad.Trans.Reader (runReaderT)
20+
import Data.Text (Text)
21+
import Database.Persist
22+
import Database.Persist.Postgresql
23+
import Database.Persist.TH
24+
import Test.Hspec
25+
26+
share
27+
[mkPersist sqlSettings, mkMigrate "migrateAll"]
28+
[persistLowerCase|
29+
PersistentThing sql=haskell_persistent
30+
value Text
31+
deriving Eq Show
32+
|]
33+
34+
connectionString :: ConnectionString
35+
connectionString = "host=127.0.0.1 port=6432 user=pgdog password=pgdog dbname=pgdog sslmode=disable"
36+
37+
adminConnectionString :: ConnectionString
38+
adminConnectionString = "host=127.0.0.1 port=6432 user=admin password=pgdog dbname=admin sslmode=disable"
39+
40+
main :: IO ()
41+
main =
42+
bracket_
43+
(runAdminCommand "SET read_write_strategy TO 'conservative'")
44+
(runAdminCommand "RELOAD")
45+
runTests
46+
47+
runAdminCommand :: Text -> IO ()
48+
runAdminCommand command = runNoLoggingT $
49+
withPostgresqlConn adminConnectionString $
50+
runReaderT (rawExecute command [])
51+
52+
runTests :: IO ()
53+
runTests = runNoLoggingT $
54+
withPostgresqlPool connectionString 2 $ \pool ->
55+
liftIO $ do
56+
runSqlPool (runMigration migrateAll) pool
57+
hspec $
58+
before_ (runSqlPool clearTestRows pool) $
59+
describe "Persistent through PgDog" $ do
60+
it "connects and runs a typed query" $ do
61+
result <- runSqlPool selectOne pool
62+
result `shouldBe` [Single 1]
63+
64+
it "creates, reads, updates, and deletes a row" $
65+
runSqlPool basicCrud pool
66+
67+
clearTestRows :: SqlPersistT IO ()
68+
clearTestRows = deleteWhere ([] :: [Filter PersistentThing])
69+
70+
selectOne :: SqlPersistT IO [Single Int]
71+
selectOne = rawSql "SELECT 1" []
72+
73+
basicCrud :: SqlPersistT IO ()
74+
basicCrud = do
75+
key <- insert (PersistentThing "created")
76+
77+
created <- get key
78+
liftIO $ created `shouldBe` Just (PersistentThing "created")
79+
80+
update key [PersistentThingValue =. "updated"]
81+
updated <- get key
82+
liftIO $ updated `shouldBe` Just (PersistentThing "updated")
83+
84+
delete key
85+
deleted <- get key
86+
liftIO $ deleted `shouldBe` Nothing

pgdog/src/admin/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub mod show_bans;
2929
pub mod show_client_memory;
3030
pub mod show_clients;
3131
pub mod show_config;
32+
pub mod show_guc;
3233
pub mod show_instance_id;
3334
pub mod show_listeners;
3435
pub mod show_lists;
@@ -74,6 +75,7 @@ pub use show_bans::*;
7475
pub use show_client_memory::*;
7576
pub use show_clients::*;
7677
pub use show_config::*;
78+
pub use show_guc::*;
7779
pub use show_instance_id::*;
7880
pub use show_listeners::*;
7981
pub use show_lists::*;

0 commit comments

Comments
 (0)