Skip to content

Commit 6ce4480

Browse files
authored
Merge pull request #79 from cipherstash/operators
Ensure EQL works with operators
2 parents d489193 + 06dd962 commit 6ce4480

17 files changed

+930
-260
lines changed

.github/workflows/test-eql.yml

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@ on:
66
paths:
77
- ".github/workflows/test-eql.yml"
88
- "sql/*.sql"
9+
- "tests/**/*"
10+
- "tasks/**/*"
911

1012
pull_request:
1113
branches:
1214
- main
1315
paths:
1416
- ".github/workflows/test-eql.yml"
1517
- "sql/*.sql"
18+
- "tests/**/*"
19+
- "tasks/**/*"
1620

1721
workflow_dispatch:
1822

@@ -23,29 +27,29 @@ defaults:
2327
jobs:
2428
test:
2529
name: "Test EQL SQL components"
26-
runs-on: ubuntu-24.04
30+
runs-on: ubuntu-latest-m
2731

2832
strategy:
2933
fail-fast: false
3034
matrix:
3135
postgres-version: [17, 16, 15, 14]
3236

3337
env:
34-
CS_DATABASE__PASSWORD:
35-
CS_DATABASE__PORT: 5432
36-
CS_DATABASE__NAME: test
38+
POSTGRES_VERSION: ${{ matrix.postgres-version }}
3739

3840
steps:
3941
- uses: actions/checkout@v4
4042

41-
- uses: extractions/setup-just@v1
42-
43-
- uses: ankane/setup-postgres@v1
43+
- uses: jdx/mise-action@v2
4444
with:
45-
postgres-version: ${{ matrix.postgres-version }}
46-
database: ${{ env.CS_DATABASE__NAME }}
45+
version: 2025.1.6 # [default: latest] mise version to install
46+
install: true # [default: true] run `mise install`
47+
cache: true # [default: true] cache mise using GitHub's cache
4748

48-
- name: Test EQL
49+
- name: Setup database (Postgres ${{ matrix.postgres-version }})
4950
run: |
50-
just build test
51+
mise run postgres:up postgres-${POSTGRES_VERSION} --extra-args "--detach --wait"
5152
53+
- name: Test EQL for Postgres ${{ matrix.postgres-version }}
54+
run: |
55+
mise run --output prefix test --postgres ${POSTGRES_VERSION}

.tool-versions

Lines changed: 0 additions & 1 deletion
This file was deleted.

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,3 +392,49 @@ To cut a [release](https://github.com/cipherstash/encrypt-query-language/release
392392
1. Click `Publish release`.
393393

394394
This will trigger the [Release EQL](https://github.com/cipherstash/encrypt-query-language/actions/workflows/release-eql.yml) workflow, which will build and attach artifacts to [the release](https://github.com/cipherstash/encrypt-query-language/releases/).
395+
396+
## Testing
397+
398+
There are tests for EQL for PostgreSQL versions 14–17.
399+
400+
They easiest way to run them is in [GitHub Actions](https://github.com/cipherstash/encrypt-query-language/actions/workflows/test-eql.yml).
401+
402+
### Running tests locally
403+
404+
> [!IMPORTANT]
405+
> **Before you run the tests** you need to have this software installed:
406+
> - [mise](https://mise.jdx.dev/) — see the [installing mise](#installing-mise) instructions
407+
> - [Docker](https://www.docker.com/) — see Docker's [documentation for installing](https://docs.docker.com/get-started/get-docker/)
408+
409+
To run tests locally:
410+
411+
``` shell
412+
# Clone the repo
413+
git clone https://github.com/cipherstash/encrypt-query-language
414+
cd encrypt-query-language
415+
416+
# Install dependencies
417+
mise trust --yes
418+
419+
# Start a postgres instance
420+
mise run postgres:up postgres-17 --extra-args "--detach --wait"
421+
422+
# Run the tests
423+
mise run test --postgres 17
424+
425+
# Stop and remove all containers and networks
426+
mise run postgres:down
427+
```
428+
429+
You can run the same tasks for Postgres 14, 15, 16, and 17.
430+
431+
The configuration for the Postgres containers in `tests/docker-compose.yml`.
432+
433+
Limitations:
434+
435+
- **Volumes for Postgres containers are not persistent.**
436+
If you need to look at data in the container, uncomment a volume in
437+
`tests/docker-compose.yml`
438+
- **You can't run multiple Postgres containers at the same time.**
439+
All the containers bind to the same port (`7543`). If you want to run
440+
multiple containers at the same time, you'll have to change the ports.

justfile

Lines changed: 0 additions & 84 deletions
This file was deleted.

mise.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[settings]
2+
# Config for test environments
3+
# Can be invoked with: mise --env tcp run <task>
4+
# trusted_config_paths = [
5+
# "./tests/mise.toml",
6+
# "./tests/mise.tcp.toml",
7+
# "./tests/mise.tls.toml",
8+
# ]
9+
[task_config]
10+
includes = [
11+
"tasks",
12+
"tasks/postgres.toml"
13+
]
14+
15+
[env]
16+
POSTGRES_DB = "cipherstash"
17+
POSTGRES_USER = "cipherstash"
18+
POSTGRES_PASSWORD = "password"
19+
POSTGRES_HOST = "localhost"
20+
POSTGRES_PORT = "7432"

sql/010-core-domain-types.sql

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
DROP DOMAIN IF EXISTS cs_match_index_v1;
2+
CREATE DOMAIN cs_match_index_v1 AS smallint[];
3+
4+
DROP DOMAIN IF EXISTS cs_unique_index_v1;
5+
CREATE DOMAIN cs_unique_index_v1 AS text;
6+
7+
8+
-- cs_encrypted_v1 is a column type and cannot be dropped if in use
9+
DO $$
10+
BEGIN
11+
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'cs_encrypted_v1') THEN
12+
CREATE DOMAIN cs_encrypted_v1 AS JSONB;
13+
END IF;
14+
END
15+
$$;
16+
17+
18+
-- Should include a kind field
19+
DROP FUNCTION IF EXISTS _cs_encrypted_check_k(jsonb);
20+
CREATE FUNCTION _cs_encrypted_check_k(val jsonb)
21+
RETURNS boolean
22+
AS $$
23+
BEGIN
24+
IF (val->>'k' = ANY('{ct, sv}')) THEN
25+
RETURN true;
26+
END IF;
27+
RAISE 'Invalid kind (%) in Encrypted column. Kind should be one of {ct, sv}', val;
28+
END;
29+
$$ LANGUAGE plpgsql;
30+
31+
32+
--
33+
-- CT payload should include a c field
34+
--
35+
DROP FUNCTION IF EXISTS _cs_encrypted_check_k_ct(jsonb);
36+
CREATE FUNCTION _cs_encrypted_check_k_ct(val jsonb)
37+
RETURNS boolean
38+
AS $$
39+
BEGIN
40+
IF (val->>'k' = 'ct') THEN
41+
IF (val ? 'c') THEN
42+
RETURN true;
43+
END IF;
44+
RAISE 'Encrypted column kind (k) of "ct" missing data field (c): %', val;
45+
END IF;
46+
RETURN true;
47+
END;
48+
$$ LANGUAGE plpgsql;
49+
50+
51+
--
52+
-- SV payload should include an sv field
53+
--
54+
DROP FUNCTION IF EXISTS _cs_encrypted_check_k_sv(jsonb);
55+
CREATE FUNCTION _cs_encrypted_check_k_sv(val jsonb)
56+
RETURNS boolean
57+
AS $$
58+
BEGIN
59+
IF (val->>'k' = 'sv') THEN
60+
IF (val ? 'sv') THEN
61+
RETURN true;
62+
END IF;
63+
RAISE 'Encrypted column kind (k) of "sv" missing data field (sv): %', val;
64+
END IF;
65+
RETURN true;
66+
END;
67+
$$ LANGUAGE plpgsql;
68+
69+
70+
-- Plaintext field should never be present in an encrypted column
71+
DROP FUNCTION IF EXISTS _cs_encrypted_check_p(jsonb);
72+
CREATE FUNCTION _cs_encrypted_check_p(val jsonb)
73+
RETURNS boolean
74+
AS $$
75+
BEGIN
76+
IF NOT val ? 'p' THEN
77+
RETURN true;
78+
END IF;
79+
RAISE 'Encrypted column includes plaintext (p) field: %', val;
80+
END;
81+
$$ LANGUAGE plpgsql;
82+
83+
-- Should include an ident field
84+
DROP FUNCTION IF EXISTS _cs_encrypted_check_i(jsonb);
85+
CREATE FUNCTION _cs_encrypted_check_i(val jsonb)
86+
RETURNS boolean
87+
AS $$
88+
BEGIN
89+
IF val ? 'i' THEN
90+
RETURN true;
91+
END IF;
92+
RAISE 'Encrypted column missing ident (i) field: %', val;
93+
END;
94+
$$ LANGUAGE plpgsql;
95+
96+
-- Query field should never be present in an encrypted column
97+
DROP FUNCTION IF EXISTS _cs_encrypted_check_q(jsonb);
98+
CREATE FUNCTION _cs_encrypted_check_q(val jsonb)
99+
RETURNS boolean
100+
AS $$
101+
BEGIN
102+
IF val ? 'q' THEN
103+
RAISE 'Encrypted column includes query (q) field: %', val;
104+
END IF;
105+
RETURN true;
106+
END;
107+
$$ LANGUAGE plpgsql;
108+
109+
-- Ident field should include table and column
110+
DROP FUNCTION IF EXISTS _cs_encrypted_check_i_ct(jsonb);
111+
CREATE FUNCTION _cs_encrypted_check_i_ct(val jsonb)
112+
RETURNS boolean
113+
AS $$
114+
BEGIN
115+
IF (val->'i' ?& array['t', 'c']) THEN
116+
RETURN true;
117+
END IF;
118+
RAISE 'Encrypted column ident (i) missing table (t) or column (c) fields: %', val;
119+
END;
120+
$$ LANGUAGE plpgsql;
121+
122+
-- Should include a version field
123+
DROP FUNCTION IF EXISTS _cs_encrypted_check_v(jsonb);
124+
CREATE FUNCTION _cs_encrypted_check_v(val jsonb)
125+
RETURNS boolean
126+
AS $$
127+
BEGIN
128+
IF (val ? 'v') THEN
129+
RETURN true;
130+
END IF;
131+
RAISE 'Encrypted column missing version (v) field: %', val;
132+
END;
133+
$$ LANGUAGE plpgsql;
134+
135+
136+
DROP FUNCTION IF EXISTS cs_check_encrypted_v1(val jsonb);
137+
138+
CREATE FUNCTION cs_check_encrypted_v1(val jsonb)
139+
RETURNS BOOLEAN
140+
LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE
141+
BEGIN ATOMIC
142+
RETURN (
143+
_cs_encrypted_check_v(val) AND
144+
_cs_encrypted_check_i(val) AND
145+
_cs_encrypted_check_k(val) AND
146+
_cs_encrypted_check_k_ct(val) AND
147+
_cs_encrypted_check_k_sv(val) AND
148+
_cs_encrypted_check_q(val) AND
149+
_cs_encrypted_check_p(val)
150+
);
151+
END;
152+
153+
ALTER DOMAIN cs_encrypted_v1 DROP CONSTRAINT IF EXISTS cs_encrypted_v1_check;
154+
155+
ALTER DOMAIN cs_encrypted_v1
156+
ADD CONSTRAINT cs_encrypted_v1_check CHECK (
157+
cs_check_encrypted_v1(VALUE)
158+
);
159+

0 commit comments

Comments
 (0)