diff --git a/diagrams/overview-insert.drawio.svg b/diagrams/overview-insert.drawio.svg deleted file mode 100644 index cef7acf..0000000 --- a/diagrams/overview-insert.drawio.svg +++ /dev/null @@ -1,457 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
- - INSERT INTO - - - users - - - ( - - - name - - - ) - -
-
-
- - - VALUES ( - - - {"p": "plaintext"} - - - ) - - -
-
-
-
-
-
- - INSERT INTO users (name)... - -
-
- - - - - - - - - - - - - - - - - - - - - - - -
-
-
- Cipherstash Proxy -
-
-
-
- - Cipherstash Proxy - -
-
- - - - -
-
-
- PostgreSQL -
-
-
-
- - PostgreSQL - -
-
- - - - -
-
-
- Encrypt -
-
-
-
- - Encrypt - -
-
- - - - - - - - - - - - - - - - - - users - - - - - - -
-
-
- 1 -
-
-
-
- - 1 - -
-
- - - - -
-
-
- ******** -
-
-
-
- - ******** - -
-
- - - - -
-
-
- 2 -
-
-
-
- - 2 - -
-
- - - - -
-
-
- ******** -
-
-
-
- - ******** - -
-
- - - - -
-
-
- 3 -
-
-
-
- - 3 - -
-
- - - - -
-
-
- - ******** - -
-
-
-
- - ******** - -
-
- - - -
-
-
-
-
-
-
- - -
-
- - - - -
-
-
- - 1 - -
-
-
-
- - 1 - -
-
- - - -
-
-
- - Plaintext - -
-
-
-
- - Plaintext - -
-
- - - - -
-
-
- - 2 - -
-
-
-
- - 2 - -
-
- - - -
-
-
- - Encryption - -
-
-
-
- - Encryption - -
-
- - - - -
-
-
- - 3 - -
-
-
-
- - 3 - -
-
- - - -
-
-
- - Storage - -
-
-
-
- - Storage - -
-
- - - - -
-
-
- - 1 - -
-
-
-
- - 1 - -
-
- - - - -
-
-
- - 2 - -
-
-
-
- - 2 - -
-
- - - - -
-
-
- - 3 - -
-
-
-
- - 3 - -
-
-
- - - - - Text is not SVG - cannot display - - - -
\ No newline at end of file diff --git a/diagrams/overview-select.drawio.svg b/diagrams/overview-select.drawio.svg deleted file mode 100644 index 1effb5a..0000000 --- a/diagrams/overview-select.drawio.svg +++ /dev/null @@ -1,552 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- Cipherstash Proxy -
-
-
-
- - Cipherstash Proxy - -
-
- - - - -
-
-
- PostgreSQL -
-
-
-
- - PostgreSQL - -
-
- - - - -
-
-
- Encrypt -
-
-
-
- - Encrypt - -
-
- - - - - - - - - - - - - - - - - - users - - - - - - -
-
-
- 1 -
-
-
-
- - 1 - -
-
- - - - -
-
-
- ******** -
-
-
-
- - ******** - -
-
- - - - -
-
-
- 2 -
-
-
-
- - 2 - -
-
- - - - -
-
-
- ******** -
-
-
-
- - ******** - -
-
- - - - -
-
-
- 3 -
-
-
-
- - 3 - -
-
- - - - -
-
-
- - ******** - -
-
-
-
- - ******** - -
-
- - - -
-
-
-
-
-
-
- - -
-
- - - - -
-
-
- - 1 - -
-
-
-
- - 1 - -
-
- - - -
-
-
- - Wrap - -
-
-
-
- - Wrap - -
-
- - - - -
-
-
- - 2 - -
-
-
-
- - 2 - -
-
- - - -
-
-
- - Encryption - -
-
-
-
- - Encryption - -
-
- - - - -
-
-
- - 3 - -
-
-
-
- - 3 - -
-
- - - -
-
-
- - Execute - -
-
-
-
- - Execute - -
-
- - - - -
-
-
- - 1 - -
-
-
-
- - 1 - -
-
- - - - -
-
-
- - 3 - -
-
-
-
- - 3 - -
-
- - - -
-
-
-
-
- - - SELECT - - name - - - -
-
- - - FROM - - users - - - -
-
-
- - - - WHERE - - - - cs_match_v1( - - - - name - - - - ) - - - @> cs - - - _match_v1 - - - ($1); - - -
-
-
-
-
-
-
- - SELECT name... - -
-
- - - - -
-
-
- - 4 - -
-
-
-
- - 4 - -
-
- - - -
-
-
- - Decrypt - -
-
-
-
- - Decrypt - -
-
- - - - - - - - -
-
-
- - 2 - -
-
-
-
- - 2 - -
-
- - - - -
-
-
- - 4 - -
-
-
-
- - 4 - -
-
- - - -
-
-
- Decrypt -
-
-
-
- - Decrypt - -
-
-
- - - - - Text is not SVG - cannot display - - - -
diff --git a/src/blake3/functions.sql b/src/blake3/functions.sql new file mode 100644 index 0000000..3682c84 --- /dev/null +++ b/src/blake3/functions.sql @@ -0,0 +1,38 @@ +-- REQUIRE: src/schema.sql + +-- extracts ste_vec index from a jsonb value +-- DROP FUNCTION IF EXISTS eql_v1.blake3(val jsonb); + +-- extracts blake3 index from a jsonb value +-- DROP FUNCTION IF EXISTS eql_v1.blake3(val jsonb); + +CREATE FUNCTION eql_v1.blake3(val jsonb) + RETURNS eql_v1.blake3 + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + BEGIN + + IF NOT (val ? 'b') NULL THEN + RAISE 'Expected a blake3 index (b) value in json: %', val; + END IF; + + IF val->>'b' IS NULL THEN + RETURN NULL; + END IF; + + RETURN val->>'b'; + END; +$$ LANGUAGE plpgsql; + + +-- extracts blake3 index from an eql_v1_encrypted value +-- DROP FUNCTION IF EXISTS eql_v1.blake3(val eql_v1_encrypted); + +CREATE FUNCTION eql_v1.blake3(val eql_v1_encrypted) + RETURNS eql_v1.blake3 + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + BEGIN + RETURN (SELECT eql_v1.blake3(val.data)); + END; +$$ LANGUAGE plpgsql; diff --git a/src/blake3/types.sql b/src/blake3/types.sql new file mode 100644 index 0000000..ab6f965 --- /dev/null +++ b/src/blake3/types.sql @@ -0,0 +1,4 @@ +-- REQUIRE: src/schema.sql + +-- DROP DOMAIN IF EXISTS eql_v1.blake3; +CREATE DOMAIN eql_v1.blake3 AS text; diff --git a/src/common.sql b/src/common.sql index ff587e0..3e4ed9e 100644 --- a/src/common.sql +++ b/src/common.sql @@ -2,11 +2,65 @@ -- REQUIRE: src/schema.sql +-- Constant time comparison of 2 bytea values + + + + + + +-- DROP FUNCTION IF EXISTS eql_v1.bytea_eq(a bytea, b bytea); + +CREATE FUNCTION eql_v1.bytea_eq(a bytea, b bytea) RETURNS boolean AS $$ +DECLARE + result boolean; + differing bytea; +BEGIN + + -- Check if the bytea values are the same length + IF LENGTH(a) != LENGTH(b) THEN + RETURN false; + END IF; + + -- Compare each byte in the bytea values + result := true; + FOR i IN 1..LENGTH(a) LOOP + IF SUBSTRING(a FROM i FOR 1) != SUBSTRING(b FROM i FOR 1) THEN + result := result AND false; + END IF; + END LOOP; + + RETURN result; +END; +$$ LANGUAGE plpgsql; + + +-- DROP FUNCTION IF EXISTS eql_v1.jsonb_array_to_bytea_array(val jsonb); + +-- Casts a jsonb array of hex-encoded strings to an array of bytea. +CREATE FUNCTION eql_v1.jsonb_array_to_bytea_array(val jsonb) +RETURNS bytea[] AS $$ +DECLARE + terms_arr bytea[]; +BEGIN + IF jsonb_typeof(val) = 'null' THEN + RETURN NULL; + END IF; + + SELECT array_agg(decode(value::text, 'hex')::bytea) + INTO terms_arr + FROM jsonb_array_elements_text(val) AS value; + + RETURN terms_arr; +END; +$$ LANGUAGE plpgsql; + + -- -- Convenience function to log a message -- -DROP FUNCTION IF EXISTS eql_v1.log(text); +-- DROP FUNCTION IF EXISTS eql_v1.log(text); CREATE FUNCTION eql_v1.log(s text) RETURNS void AS $$ @@ -19,7 +73,7 @@ $$ LANGUAGE plpgsql; -- -- Convenience function to describe a test -- -DROP FUNCTION IF EXISTS eql_v1.log(text, text); +-- DROP FUNCTION IF EXISTS eql_v1.log(text, text); CREATE FUNCTION eql_v1.log(ctx text, s text) RETURNS void AS $$ diff --git a/src/config/config_test.sql b/src/config/config_test.sql index 500e9ca..227e9c9 100644 --- a/src/config/config_test.sql +++ b/src/config/config_test.sql @@ -4,7 +4,7 @@ -- -- Helper function for assertions -- -DROP FUNCTION IF EXISTS _index_exists(text, text, text, text); +-- DROP FUNCTION IF EXISTS _index_exists(text, text, text, text); CREATE FUNCTION _index_exists(table_name text, column_name text, index_name text, state text DEFAULT 'pending') RETURNS boolean LANGUAGE sql STRICT PARALLEL SAFE diff --git a/src/config/constraints.sql b/src/config/constraints.sql index a5ad001..b69349a 100644 --- a/src/config/constraints.sql +++ b/src/config/constraints.sql @@ -5,7 +5,7 @@ -- -- Used by the eql_v1.config_check_indexes as part of the configuration_data_v1 constraint -- -DROP FUNCTION IF EXISTS eql_v1.config_get_indexes(jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.config_get_indexes(jsonb); CREATE FUNCTION eql_v1.config_get_indexes(val jsonb) RETURNS SETOF text LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE @@ -18,7 +18,7 @@ END; -- -- Used by the cs_configuration_data_v1_check constraint -- -DROP FUNCTION IF EXISTS eql_v1.config_check_indexes(jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.config_check_indexes(jsonb); CREATE FUNCTION eql_v1.config_check_indexes(val jsonb) RETURNS BOOLEAN IMMUTABLE STRICT PARALLEL SAFE @@ -36,7 +36,7 @@ AS $$ $$ LANGUAGE plpgsql; -DROP FUNCTION IF EXISTS eql_v1.config_check_cast(jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.config_check_cast(jsonb); CREATE FUNCTION eql_v1.config_check_cast(val jsonb) RETURNS BOOLEAN @@ -52,7 +52,7 @@ $$ LANGUAGE plpgsql; -- -- Should include a tables field -- Tables should not be empty -DROP FUNCTION IF EXISTS eql_v1.config_check_tables(jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.config_check_tables(jsonb); CREATE FUNCTION eql_v1.config_check_tables(val jsonb) RETURNS boolean AS $$ @@ -65,7 +65,7 @@ AS $$ $$ LANGUAGE plpgsql; -- Should include a version field -DROP FUNCTION IF EXISTS eql_v1.config_check_version(jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.config_check_version(jsonb); CREATE FUNCTION eql_v1.config_check_version(val jsonb) RETURNS boolean AS $$ diff --git a/src/config/functions.sql b/src/config/functions.sql index ec948e6..100d00a 100644 --- a/src/config/functions.sql +++ b/src/config/functions.sql @@ -4,7 +4,7 @@ -- -- -DROP FUNCTION IF EXISTS eql_v1.config_default(config jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.config_default(config jsonb); CREATE FUNCTION eql_v1.config_default(config jsonb) RETURNS jsonb @@ -19,7 +19,7 @@ AS $$ $$ LANGUAGE plpgsql; -DROP FUNCTION IF EXISTS eql_v1.config_add_table(table_name text, config jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.config_add_table(table_name text, config jsonb); CREATE FUNCTION eql_v1.config_add_table(table_name text, config jsonb) RETURNS jsonb @@ -37,7 +37,7 @@ $$ LANGUAGE plpgsql; -- Add the column if it doesn't exist -DROP FUNCTION IF EXISTS eql_v1.config_add_column(table_name text, column_name text, config jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.config_add_column(table_name text, column_name text, config jsonb); CREATE FUNCTION eql_v1.config_add_column(table_name text, column_name text, config jsonb) RETURNS jsonb @@ -56,7 +56,7 @@ $$ LANGUAGE plpgsql; -- Set the cast -DROP FUNCTION IF EXISTS eql_v1.config_add_cast(table_name text, column_name text, cast_as text, config jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.config_add_cast(table_name text, column_name text, cast_as text, config jsonb); CREATE FUNCTION eql_v1.config_add_cast(table_name text, column_name text, cast_as text, config jsonb) RETURNS jsonb @@ -70,7 +70,7 @@ $$ LANGUAGE plpgsql; -- Add the column if it doesn't exist -DROP FUNCTION IF EXISTS eql_v1.config_add_index(table_name text, column_name text, index_name text, opts jsonb, config jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.config_add_index(table_name text, column_name text, index_name text, opts jsonb, config jsonb); CREATE FUNCTION eql_v1.config_add_index(table_name text, column_name text, index_name text, opts jsonb, config jsonb) RETURNS jsonb @@ -86,7 +86,7 @@ $$ LANGUAGE plpgsql; -- -- Default options for match index -- -DROP FUNCTION IF EXISTS eql_v1.config_match_default(); +-- DROP FUNCTION IF EXISTS eql_v1.config_match_default(); CREATE FUNCTION eql_v1.config_match_default() RETURNS jsonb @@ -103,7 +103,7 @@ END; -- -- Adds an index term to the configuration -- -DROP FUNCTION IF EXISTS eql_v1.add_index(table_name text, column_name text, index_name text, cast_as text, opts jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.add_index(table_name text, column_name text, index_name text, cast_as text, opts jsonb); CREATE FUNCTION eql_v1.add_index(table_name text, column_name text, index_name text, cast_as text DEFAULT 'text', opts jsonb DEFAULT '{}') RETURNS jsonb @@ -155,7 +155,7 @@ AS $$ $$ LANGUAGE plpgsql; -DROP FUNCTION IF EXISTS eql_v1.remove_index(table_name text, column_name text, index_name text); +-- DROP FUNCTION IF EXISTS eql_v1.remove_index(table_name text, column_name text, index_name text); CREATE FUNCTION eql_v1.remove_index(table_name text, column_name text, index_name text) RETURNS jsonb @@ -216,7 +216,7 @@ AS $$ $$ LANGUAGE plpgsql; -DROP FUNCTION IF EXISTS eql_v1.modify_index(table_name text, column_name text, index_name text, cast_as text, opts jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.modify_index(table_name text, column_name text, index_name text, cast_as text, opts jsonb); CREATE FUNCTION eql_v1.modify_index(table_name text, column_name text, index_name text, cast_as text DEFAULT 'text', opts jsonb DEFAULT '{}') RETURNS jsonb @@ -240,7 +240,7 @@ $$ LANGUAGE plpgsql; -- -- Raises an exception if the configuration is already `encrypting` or if there is no `pending` configuration to encrypt. -- -DROP FUNCTION IF EXISTS eql_v1.encrypt(); +-- DROP FUNCTION IF EXISTS eql_v1.encrypt(); CREATE FUNCTION eql_v1.encrypt(force boolean DEFAULT false) RETURNS boolean @@ -267,7 +267,7 @@ AS $$ $$ LANGUAGE plpgsql; -DROP FUNCTION IF EXISTS eql_v1.activate(); +-- DROP FUNCTION IF EXISTS eql_v1.activate(); CREATE FUNCTION eql_v1.activate() RETURNS boolean @@ -285,7 +285,7 @@ AS $$ $$ LANGUAGE plpgsql; -DROP FUNCTION IF EXISTS eql_v1.discard(); +-- DROP FUNCTION IF EXISTS eql_v1.discard(); CREATE FUNCTION eql_v1.discard() RETURNS boolean @@ -301,7 +301,7 @@ AS $$ $$ LANGUAGE plpgsql; -DROP FUNCTION IF EXISTS eql_v1.add_column(table_name text, column_name text, cast_as text); +-- DROP FUNCTION IF EXISTS eql_v1.add_column(table_name text, column_name text, cast_as text); CREATE FUNCTION eql_v1.add_column(table_name text, column_name text, cast_as text DEFAULT 'text') RETURNS jsonb @@ -340,7 +340,7 @@ AS $$ $$ LANGUAGE plpgsql; -DROP FUNCTION IF EXISTS eql_v1.remove_column(table_name text, column_name text); +-- DROP FUNCTION IF EXISTS eql_v1.remove_column(table_name text, column_name text); CREATE FUNCTION eql_v1.remove_column(table_name text, column_name text) RETURNS jsonb @@ -396,7 +396,7 @@ AS $$ $$ LANGUAGE plpgsql; -DROP FUNCTION IF EXISTS eql_v1.reload_config(); +-- DROP FUNCTION IF EXISTS eql_v1.reload_config(); CREATE FUNCTION eql_v1.reload_config() RETURNS void @@ -405,7 +405,7 @@ BEGIN ATOMIC RETURN NULL; END; -DROP FUNCTION IF EXISTS eql_v1.config(); +-- DROP FUNCTION IF EXISTS eql_v1.config(); -- A convenience function to return the configuration in a tabular format, allowing for easier filtering, and querying. -- Query using `SELECT * FROM cs_config();` diff --git a/src/config/tables.sql b/src/config/tables.sql index 43fd3c9..c22e800 100644 --- a/src/config/tables.sql +++ b/src/config/tables.sql @@ -2,7 +2,7 @@ -- -- --- CREATE the cs_configuration_v1 TABLE +-- CREATE the eql_v1_configuration TABLE -- CREATE TABLE IF NOT EXISTS public.eql_v1_configuration ( diff --git a/src/encrypted/aggregates.sql b/src/encrypted/aggregates.sql index 9e2dff1..78a2c20 100644 --- a/src/encrypted/aggregates.sql +++ b/src/encrypted/aggregates.sql @@ -3,8 +3,8 @@ -- REQUIRE: src/ore/functions.sql -- Aggregate functions for ORE -DROP AGGREGATE IF EXISTS eql_v1.min(eql_v1_encrypted); -DROP FUNCTION IF EXISTS eql_v1.min(a eql_v1_encrypted, b eql_v1_encrypted); +-- DROP AGGREGATE IF EXISTS eql_v1.min(eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1.min(a eql_v1_encrypted, b eql_v1_encrypted); CREATE FUNCTION eql_v1.min(a eql_v1_encrypted, b eql_v1_encrypted) RETURNS eql_v1_encrypted @@ -27,8 +27,8 @@ CREATE AGGREGATE eql_v1.min(eql_v1_encrypted) stype = eql_v1_encrypted ); -DROP AGGREGATE IF EXISTS eql_v1.max(eql_v1_encrypted); -DROP FUNCTION IF EXISTS eql_v1.max(a eql_v1_encrypted, b eql_v1_encrypted); +-- DROP AGGREGATE IF EXISTS eql_v1.max(eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1.max(a eql_v1_encrypted, b eql_v1_encrypted); CREATE FUNCTION eql_v1.max(a eql_v1_encrypted, b eql_v1_encrypted) RETURNS eql_v1_encrypted diff --git a/src/encrypted/aggregates_test.sql b/src/encrypted/aggregates_test.sql index 4872ca8..285d2fa 100644 --- a/src/encrypted/aggregates_test.sql +++ b/src/encrypted/aggregates_test.sql @@ -1,7 +1,7 @@ \set ON_ERROR_STOP on -- create table -DROP TABLE IF EXISTS agg_test; +-- DROP TABLE IF EXISTS agg_test; CREATE TABLE agg_test ( plain_int integer, diff --git a/src/encrypted/casts.sql b/src/encrypted/casts.sql index 0c63c5b..2b6470b 100644 --- a/src/encrypted/casts.sql +++ b/src/encrypted/casts.sql @@ -5,7 +5,7 @@ -- -- Convert jsonb to eql_v1.encrypted -- -DROP FUNCTION IF EXISTS eql_v1.to_encrypted(data jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.to_encrypted(data jsonb); CREATE FUNCTION eql_v1.to_encrypted(data jsonb) RETURNS public.eql_v1_encrypted AS $$ @@ -17,7 +17,7 @@ $$ LANGUAGE plpgsql; -- -- Cast jsonb to eql_v1.encrypted -- -DROP CAST IF EXISTS (jsonb AS public.eql_v1_encrypted); +-- DROP CAST IF EXISTS (jsonb AS public.eql_v1_encrypted); CREATE CAST (jsonb AS public.eql_v1_encrypted) WITH FUNCTION eql_v1.to_encrypted(jsonb) AS IMPLICIT; @@ -26,7 +26,7 @@ CREATE CAST (jsonb AS public.eql_v1_encrypted) -- -- Convert text to eql_v1.encrypted -- -DROP FUNCTION IF EXISTS eql_v1.to_encrypted(data text); +-- DROP FUNCTION IF EXISTS eql_v1.to_encrypted(data text); CREATE FUNCTION eql_v1.to_encrypted(data text) RETURNS public.eql_v1_encrypted AS $$ @@ -38,7 +38,7 @@ $$ LANGUAGE plpgsql; -- -- Cast text to eql_v1.encrypted -- -DROP CAST IF EXISTS (text AS public.eql_v1_encrypted); +-- DROP CAST IF EXISTS (text AS public.eql_v1_encrypted); CREATE CAST (text AS public.eql_v1_encrypted) WITH FUNCTION eql_v1.to_encrypted(text) AS IMPLICIT; @@ -48,7 +48,7 @@ CREATE CAST (text AS public.eql_v1_encrypted) -- -- Convert eql_v1.encrypted to jsonb -- -DROP FUNCTION IF EXISTS eql_v1.to_jsonb(e public.eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1.to_jsonb(e public.eql_v1_encrypted); CREATE FUNCTION eql_v1.to_jsonb(e public.eql_v1_encrypted) RETURNS jsonb AS $$ @@ -60,7 +60,7 @@ $$ LANGUAGE plpgsql; -- -- Cast eql_v1.encrypted to jsonb -- -DROP CAST IF EXISTS (public.eql_v1_encrypted AS jsonb); +-- DROP CAST IF EXISTS (public.eql_v1_encrypted AS jsonb); CREATE CAST (public.eql_v1_encrypted AS jsonb) WITH FUNCTION eql_v1.to_jsonb(public.eql_v1_encrypted) AS ASSIGNMENT; diff --git a/src/encrypted/constraints.sql b/src/encrypted/constraints.sql index 9103212..3296b25 100644 --- a/src/encrypted/constraints.sql +++ b/src/encrypted/constraints.sql @@ -76,7 +76,7 @@ -- $$ LANGUAGE plpgsql; -- Should include an ident field -DROP FUNCTION IF EXISTS eql_v1._encrypted_check_i(jsonb); +-- DROP FUNCTION IF EXISTS eql_v1._encrypted_check_i(jsonb); CREATE FUNCTION eql_v1._encrypted_check_i(val jsonb) RETURNS boolean AS $$ @@ -106,7 +106,7 @@ $$ LANGUAGE plpgsql; -- $$ LANGUAGE plpgsql; -- Ident field should include table and column -DROP FUNCTION IF EXISTS eql_v1._encrypted_check_i_ct(jsonb); +-- DROP FUNCTION IF EXISTS eql_v1._encrypted_check_i_ct(jsonb); CREATE FUNCTION eql_v1._encrypted_check_i_ct(val jsonb) RETURNS boolean AS $$ @@ -132,7 +132,7 @@ $$ LANGUAGE plpgsql; -- $$ LANGUAGE plpgsql; -DROP FUNCTION IF EXISTS eql_v1.check_encrypted(val jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.check_encrypted(val jsonb); CREATE FUNCTION eql_v1.check_encrypted(val jsonb) RETURNS BOOLEAN diff --git a/src/encrypted/functions.sql b/src/encrypted/functions.sql index fb09bf4..f200943 100644 --- a/src/encrypted/functions.sql +++ b/src/encrypted/functions.sql @@ -4,7 +4,7 @@ -- REQUIRE: src/unique/types.sql -DROP FUNCTION IF EXISTS eql_v1.ciphertext(val jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.ciphertext(val jsonb); CREATE FUNCTION eql_v1.ciphertext(val jsonb) RETURNS text @@ -19,26 +19,26 @@ AS $$ $$ LANGUAGE plpgsql; -DROP FUNCTION IF EXISTS eql_v1.to_jsonb(val eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1.ciphertext(val eql_v1_encrypted); -CREATE FUNCTION eql_v1.to_jsonb(val eql_v1_encrypted) - RETURNS jsonb +CREATE FUNCTION eql_v1.ciphertext(val eql_v1_encrypted) + RETURNS text IMMUTABLE STRICT PARALLEL SAFE AS $$ BEGIN - RETURN val.data; + RETURN eql_v1.ciphertext(val.data); END; $$ LANGUAGE plpgsql; -DROP FUNCTION IF EXISTS eql_v1._first_grouped_value(jsonb, jsonb); +-- DROP FUNCTION IF EXISTS eql_v1._first_grouped_value(jsonb, jsonb); CREATE FUNCTION eql_v1._first_grouped_value(jsonb, jsonb) RETURNS jsonb AS $$ SELECT COALESCE($1, $2); $$ LANGUAGE sql IMMUTABLE; -DROP AGGREGATE IF EXISTS eql_v1.cs_grouped_value(jsonb); +-- DROP AGGREGATE IF EXISTS eql_v1.cs_grouped_value(jsonb); CREATE AGGREGATE eql_v1.cs_grouped_value(jsonb) ( SFUNC = eql_v1._first_grouped_value, diff --git a/src/encryptindex/functions.sql b/src/encryptindex/functions.sql index 448f102..4aafd53 100644 --- a/src/encryptindex/functions.sql +++ b/src/encryptindex/functions.sql @@ -1,7 +1,7 @@ -- Return the diff of two configurations -- Returns the set of keys in a that have different values to b -- The json comparison is on object values held by the key -DROP FUNCTION IF EXISTS eql_v1.diff_config(a JSONB, b JSONB); +-- DROP FUNCTION IF EXISTS eql_v1.diff_config(a JSONB, b JSONB); CREATE FUNCTION eql_v1.diff_config(a JSONB, b JSONB) RETURNS TABLE(table_name TEXT, column_name TEXT) @@ -34,7 +34,7 @@ $$ LANGUAGE plpgsql; -- Returns the set of columns with pending configuration changes -- Compares the columns in pending configuration that do not match the active config -DROP FUNCTION IF EXISTS eql_v1.select_pending_columns(); +-- DROP FUNCTION IF EXISTS eql_v1.select_pending_columns(); CREATE FUNCTION eql_v1.select_pending_columns() RETURNS TABLE(table_name TEXT, column_name TEXT) @@ -72,7 +72,7 @@ $$ LANGUAGE plpgsql; -- On initial encryption from plaintext the target column will be `{column_name}_encrypted ` -- OR NULL if the column does not exist -- -DROP FUNCTION IF EXISTS eql_v1.select_target_columns(); +-- DROP FUNCTION IF EXISTS eql_v1.select_target_columns(); CREATE FUNCTION eql_v1.select_target_columns() RETURNS TABLE(table_name TEXT, column_name TEXT, target_column TEXT) @@ -93,7 +93,7 @@ $$ LANGUAGE sql; -- -- Returns true if all pending columns have a target (encrypted) column -DROP FUNCTION IF EXISTS eql_v1.ready_for_encryption(); +-- DROP FUNCTION IF EXISTS eql_v1.ready_for_encryption(); CREATE FUNCTION eql_v1.ready_for_encryption() RETURNS BOOLEAN @@ -113,7 +113,7 @@ $$ LANGUAGE sql; -- Executes the ALTER TABLE statement -- `ALTER TABLE {target_table} ADD COLUMN {column_name}_encrypted eql_v1_encrypted;` -- -DROP FUNCTION IF EXISTS eql_v1.create_encrypted_columns(); +-- DROP FUNCTION IF EXISTS eql_v1.create_encrypted_columns(); CREATE FUNCTION eql_v1.create_encrypted_columns() RETURNS TABLE(table_name TEXT, column_name TEXT) @@ -138,7 +138,7 @@ $$ LANGUAGE plpgsql; -- `ALTER TABLE {target_table} RENAME COLUMN {column_name} TO {column_name}_plaintext; -- `ALTER TABLE {target_table} RENAME COLUMN {column_name}_encrypted TO {column_name};` -- -DROP FUNCTION IF EXISTS eql_v1.rename_encrypted_columns(); +-- DROP FUNCTION IF EXISTS eql_v1.rename_encrypted_columns(); CREATE FUNCTION eql_v1.rename_encrypted_columns() RETURNS TABLE(table_name TEXT, column_name TEXT, target_column TEXT) @@ -155,7 +155,7 @@ AS $$ $$ LANGUAGE plpgsql; -DROP FUNCTION IF EXISTS eql_v1.count_encrypted_with_active_config(table_name TEXT, column_name TEXT); +-- DROP FUNCTION IF EXISTS eql_v1.count_encrypted_with_active_config(table_name TEXT, column_name TEXT); CREATE FUNCTION eql_v1.count_encrypted_with_active_config(table_name TEXT, column_name TEXT) RETURNS BIGINT diff --git a/src/encryptindex/functions_test.sql b/src/encryptindex/functions_test.sql index 5e89d19..9dd7cc7 100644 --- a/src/encryptindex/functions_test.sql +++ b/src/encryptindex/functions_test.sql @@ -8,7 +8,7 @@ TRUNCATE TABLE eql_v1_configuration; -- Create a table with a plaintext column -DROP TABLE IF EXISTS users; +-- DROP TABLE IF EXISTS users; CREATE TABLE users ( id bigint GENERATED ALWAYS AS IDENTITY, @@ -63,7 +63,7 @@ $$ LANGUAGE plpgsql; TRUNCATE TABLE eql_v1_configuration; -- Create a table with multiple plaintext columns -DROP TABLE IF EXISTS users; +-- DROP TABLE IF EXISTS users; CREATE TABLE users ( id bigint GENERATED ALWAYS AS IDENTITY, @@ -119,7 +119,7 @@ $$ LANGUAGE plpgsql; -- The schema should be validated first. -- Users table does not exist, so should fail. -- ----------------------------------------------- -DROP TABLE IF EXISTS users; +-- DROP TABLE IF EXISTS users; TRUNCATE TABLE eql_v1_configuration; @@ -148,7 +148,7 @@ $$ LANGUAGE plpgsql; -- -- Schema validation is skipped -- ----------------------------------------------- -DROP TABLE IF EXISTS users; +-- DROP TABLE IF EXISTS users; TRUNCATE TABLE eql_v1_configuration; DO $$ @@ -194,7 +194,7 @@ INSERT INTO eql_v1_configuration (state, data) VALUES ( ); -- Create a table with plaintext and encrypted columns -DROP TABLE IF EXISTS users; +-- DROP TABLE IF EXISTS users; CREATE TABLE users ( id bigint GENERATED ALWAYS AS IDENTITY, @@ -244,7 +244,7 @@ INSERT INTO eql_v1_configuration (state, data) VALUES ( ); -- Create a table with plaintext and jsonb column -DROP TABLE IF EXISTS users; +-- DROP TABLE IF EXISTS users; CREATE TABLE users ( id bigint GENERATED ALWAYS AS IDENTITY, @@ -295,7 +295,7 @@ INSERT INTO eql_v1_configuration (state, data) VALUES ( -- Create a table with multiple plaintext columns -DROP TABLE IF EXISTS users; +-- DROP TABLE IF EXISTS users; CREATE TABLE users ( id bigint GENERATED ALWAYS AS IDENTITY, diff --git a/src/jsonb/functions.sql b/src/jsonb/functions.sql new file mode 100644 index 0000000..ade1b35 --- /dev/null +++ b/src/jsonb/functions.sql @@ -0,0 +1,291 @@ +-- REQUIRE: src/schema.sql +-- REQUIRE: src/encrypted/types.sql + +-- The jsonpath operators @? and @@ suppress the following errors: +-- missing object field or array element, +-- unexpected JSON item type, +-- datetime and numeric errors. +-- The jsonpath-related functions described below can also be told to suppress these types of errors. +-- This behavior might be helpful when searching JSON document collections of varying structure. + + + +-- +-- +-- Returns the stevec encrypted element matching the selector +-- +-- If the selector is not found, the function returns NULL +-- If the selector is found, the function returns the matching element +-- +-- Array elements use the same selector +-- Multiple matching elements are wrapped into an eql_v1_encrypted with an array flag +-- +-- +-- DROP FUNCTION IF EXISTS eql_v1.jsonb_path_query(val jsonb, selector text); + +CREATE FUNCTION eql_v1.jsonb_path_query(val jsonb, selector text) + RETURNS SETOF eql_v1_encrypted + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + DECLARE + sv eql_v1_encrypted[]; + found jsonb[]; + e jsonb; + ary boolean; + BEGIN + + IF val IS NULL THEN + RETURN NEXT NULL; + END IF; + + sv := eql_v1.ste_vec(val); + + FOR idx IN 1..array_length(sv, 1) LOOP + e := sv[idx]; + + IF eql_v1.selector(e) = selector THEN + found := array_append(found, e); + IF eql_v1.is_ste_vec_array(e) THEN + ary := true; + END IF; + + END IF; + END LOOP; + + IF found IS NOT NULL THEN + + IF ary THEN + + -- Wrapp found array elements as eql_v1_encrypted + RETURN NEXT jsonb_build_object( + 'sv', found, + 'a', 1 + )::eql_v1_encrypted; + + ELSE + RETURN NEXT found[1]::eql_v1_encrypted; + END IF; + + END IF; + + RETURN; + END; +$$ LANGUAGE plpgsql; + + +-- DROP FUNCTION IF EXISTS eql_v1.jsonb_path_query(val eql_v1_encrypted, selector text); + +CREATE FUNCTION eql_v1.jsonb_path_query(val eql_v1_encrypted, selector text) + RETURNS SETOF eql_v1_encrypted + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + BEGIN + RETURN QUERY + SELECT * FROM eql_v1.jsonb_path_query(val.data, selector); + END; +$$ LANGUAGE plpgsql; + + +-- DROP FUNCTION IF EXISTS eql_v1.jsonb_path_exists(val jsonb, selector text); + +CREATE FUNCTION eql_v1.jsonb_path_exists(val jsonb, selector text) + RETURNS boolean + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + BEGIN + RETURN EXISTS ( + SELECT eql_v1.jsonb_path_query(val, selector) + ); + END; +$$ LANGUAGE plpgsql; + + +-- DROP FUNCTION IF EXISTS eql_v1.jsonb_path_exists(val eql_v1_encrypted, selector text); + +CREATE FUNCTION eql_v1.jsonb_path_exists(val eql_v1_encrypted, selector text) + RETURNS boolean + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + BEGIN + RETURN EXISTS ( + SELECT eql_v1.jsonb_path_query(val, selector) + ); + END; +$$ LANGUAGE plpgsql; + + +-- +-- +-- DROP FUNCTION IF EXISTS eql_v1.jsonb_path_query_first(val jsonb, selector text); + +CREATE FUNCTION eql_v1.jsonb_path_query_first(val jsonb, selector text) + RETURNS eql_v1_encrypted + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + BEGIN + RETURN ( + SELECT ( + SELECT e + FROM eql_v1.jsonb_path_query(val.data, selector) AS e + LIMIT 1 + ) + ); + END; +$$ LANGUAGE plpgsql; + + +-- DROP FUNCTION IF EXISTS eql_v1.jsonb_path_query_first(val eql_v1_encrypted, selector text); + +CREATE FUNCTION eql_v1.jsonb_path_query_first(val eql_v1_encrypted, selector text) + RETURNS eql_v1_encrypted + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + BEGIN + RETURN ( + SELECT e + FROM eql_v1.jsonb_path_query(val.data, selector) as e + LIMIT 1 + ); + END; +$$ LANGUAGE plpgsql; + + + +-- + + +-- ===================================================================== +-- +-- Returns the length of an encrypted jsonb array +--- +-- An encrypted is a jsonb array if it contains an "a" field/attribute with a truthy value +-- +-- DROP FUNCTION IF EXISTS eql_v1.jsonb_array_length(val jsonb); + +CREATE FUNCTION eql_v1.jsonb_array_length(val jsonb) + RETURNS integer + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + DECLARE + sv eql_v1_encrypted[]; + found eql_v1_encrypted[]; + BEGIN + + IF val IS NULL THEN + RETURN NULL; + END IF; + + IF eql_v1.is_ste_vec_array(val) THEN + sv := eql_v1.ste_vec(val); + RETURN array_length(sv, 1); + END IF; + + RAISE 'cannot get array length of a non-array'; + END; +$$ LANGUAGE plpgsql; + + +-- DROP FUNCTION IF EXISTS eql_v1.jsonb_array_length(val eql_v1_encrypted); + +CREATE FUNCTION eql_v1.jsonb_array_length(val eql_v1_encrypted) + RETURNS integer + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + BEGIN + RETURN ( + SELECT eql_v1.jsonb_array_length(val.data) + ); + END; +$$ LANGUAGE plpgsql; + + + + +-- ===================================================================== +-- +-- Returns the length of an encrypted jsonb array +--- +-- An encrypted is a jsonb array if it contains an "a" field/attribute with a truthy value +-- +-- DROP FUNCTION IF EXISTS eql_v1.jsonb_array_elements(val jsonb); + +CREATE FUNCTION eql_v1.jsonb_array_elements(val jsonb) + RETURNS SETOF eql_v1_encrypted + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + DECLARE + sv eql_v1_encrypted[]; + found eql_v1_encrypted[]; + BEGIN + + IF NOT eql_v1.is_ste_vec_array(val) THEN + RAISE 'cannot extract elements from non-array'; + END IF; + + sv := eql_v1.ste_vec(val); + + FOR idx IN 1..array_length(sv, 1) LOOP + RETURN NEXT sv[idx]; + END LOOP; + + RETURN; + END; +$$ LANGUAGE plpgsql; + + +-- DROP FUNCTION IF EXISTS eql_v1.jsonb_array_elements(val eql_v1_encrypted); + +CREATE FUNCTION eql_v1.jsonb_array_elements(val eql_v1_encrypted) + RETURNS SETOF eql_v1_encrypted + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + BEGIN + RETURN QUERY + SELECT * FROM eql_v1.jsonb_array_elements(val.data); + END; +$$ LANGUAGE plpgsql; + + + +-- ===================================================================== +-- +-- Returns the length of an encrypted jsonb array +--- +-- An encrypted is a jsonb array if it contains an "a" field/attribute with a truthy value +-- +-- DROP FUNCTION IF EXISTS eql_v1.jsonb_array_elements_text(val jsonb); + +CREATE FUNCTION eql_v1.jsonb_array_elements_text(val jsonb) + RETURNS SETOF text + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + DECLARE + sv eql_v1_encrypted[]; + found eql_v1_encrypted[]; + BEGIN + IF NOT eql_v1.is_ste_vec_array(val) THEN + RAISE 'cannot extract elements from non-array'; + END IF; + + sv := eql_v1.ste_vec(val); + + FOR idx IN 1..array_length(sv, 1) LOOP + RETURN NEXT eql_v1.ciphertext(sv[idx]); + END LOOP; + + RETURN; + END; +$$ LANGUAGE plpgsql; + + +-- DROP FUNCTION IF EXISTS eql_v1.jsonb_array_elements_text(val eql_v1_encrypted); + +CREATE FUNCTION eql_v1.jsonb_array_elements_text(val eql_v1_encrypted) + RETURNS SETOF text + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + BEGIN + RETURN QUERY + SELECT * FROM eql_v1.jsonb_array_elements_text(val.data); + END; +$$ LANGUAGE plpgsql; diff --git a/src/jsonb/functions_test.sql b/src/jsonb/functions_test.sql new file mode 100644 index 0000000..ca99302 --- /dev/null +++ b/src/jsonb/functions_test.sql @@ -0,0 +1,333 @@ +\set ON_ERROR_STOP on + + +SELECT create_table_with_encrypted(); +SELECT seed_encrypted_json(); + + +-- DROP TABLE IF EXISTS unencrypted; +-- CREATE TABLE unencrypted +-- ( +-- id bigint GENERATED ALWAYS AS IDENTITY, +-- u jsonb, +-- PRIMARY KEY(id) +-- ); +-- INSERT INTO unencrypted (u) +-- VALUES +-- ('{"a": [1, 2, 3] }'), +-- ('{"a": [1, 2, 3, 4] }'), +-- ('{"a": [1, 2, 3, 4, 5] }'); + +-- SELECT * +-- FROM unencrypted +-- WHERE EXISTS ( +-- SELECT 1 +-- FROM jsonb_array_elements(u->'a') AS elem +-- WHERE elem::int < 2 +-- ); + +-- SELECT seed_encrypted(get_array_ste_vec()::eql_v1_encrypted); +-- SELECT * +-- FROM encrypted +-- WHERE EXISTS ( +-- SELECT 1 +-- FROM eql_v1.jsonb_array_elements(e->'f510853730e1c3dbd31b86963f029dd5') AS elem +-- WHERE elem > '{"ocf": "b0c0a7385cb2f7dfe32a2649a9d8294794b8fc05585a240c1315f1e45ee7d9012616db3f01b43fa94351618670a29c24fc75df1392d52764c757b34495888b1c"}'::jsonb +-- ); + +-- SELECT eql_v1.jsonb_path_query_first(e, '33743aed3ae636f6bf05cff11ac4b519') as e +-- FROM encrypted +-- WHERE eql_v1.jsonb_path_query(e, '33743aed3ae636f6bf05cff11ac4b519') IS NOT NULL; + + + +-- "ocf": "b0c0a7385cb2f7dfe32a2649a9d8294794b8fc05585a240c1315f1e45ee7d9012616db3f01b43fa94351618670a29c24fc75df1392d52764c757b34495888b1c", + +-- SELECT eql_v1.jsonb_array_elements(eql_v1.jsonb_path_query(e, 'f510853730e1c3dbd31b86963f029dd5')) as e FROM encrypted ; + + + + +-- -- SELECT eql_v1.jsonb_path_exists(e, ''f510853730e1c3dbd31b86963f029dd5'') FROM encrypted; +-- -- SELECT eql_v1.jsonb_path_query(e, 'f510853730e1c3dbd31b86963f029dd5') FROM encrypted; + +-- -- SELECT eql_v1.jsonb_path_query(e, 'f510853730e1c3dbd31b86963f029dd5') as e FROM encrypted; +-- -- SELECT eql_v1.jsonb_array_length(eql_v1.jsonb_path_query(e, 'f510853730e1c3dbd31b86963f029dd5')) as e FROM encrypted LIMIT 1; +-- -- SELECT eql_v1.jsonb_array_elements(eql_v1.jsonb_path_query(e, 'f510853730e1c3dbd31b86963f029dd5')) as e FROM encrypted ; +-- -- SELECT eql_v1.jsonb_array_elements_text(eql_v1.jsonb_path_query(e, 'f510853730e1c3dbd31b86963f029dd5')) as e FROM encrypted ; +-- -- SELECT eql_v1.jsonb_array_length(eql_v1.jsonb_path_query(e, 'f510853730e1c3dbd31b86963f029dd5')) as e FROM encrypted LIMIT 1; +-- -- SELECT eql_v1.jsonb_path_query(e, 'f510853730e1c3dbd31b86963f029dd5') as e FROM encrypted; + + + + +-- ======================================================================== +-- +-- Selector &.a[*] +-- -> 33743aed3ae636f6bf05cff11ac4b519 +-- +DO $$ +DECLARE + sv eql_v1_encrypted; + results eql_v1_encrypted[]; + BEGIN + + PERFORM seed_encrypted_json(); + PERFORM seed_encrypted(get_array_ste_vec()::eql_v1_encrypted); + + PERFORM assert_result( + 'jsonb_array_elements returns array elements from jsonb_path_query result', + 'SELECT eql_v1.jsonb_array_elements(eql_v1.jsonb_path_query(e, ''f510853730e1c3dbd31b86963f029dd5'')) as e FROM encrypted;'); + + PERFORM assert_count( + 'jsonb_array_elements returns the correct number of array elements from jsonb_path_query result', + 'SELECT eql_v1.jsonb_array_elements(eql_v1.jsonb_path_query(e, ''f510853730e1c3dbd31b86963f029dd5'')) as e FROM encrypted;', + 5); + + PERFORM assert_exception( + 'jsonb_array_elements exception if input is not an array', + 'SELECT eql_v1.jsonb_array_elements(eql_v1.jsonb_path_query(e, ''33743aed3ae636f6bf05cff11ac4b519'')) as e FROM encrypted LIMIT 1;'); + + END; +$$ LANGUAGE plpgsql; + + +-- -- ======================================================================== +-- -- +-- -- Selector &.a[*] +-- -- -> 33743aed3ae636f6bf05cff11ac4b519 +-- -- +DO $$ +DECLARE + sv eql_v1_encrypted; + results eql_v1_encrypted[]; + BEGIN + + PERFORM seed_encrypted_json(); + PERFORM seed_encrypted(get_array_ste_vec()::eql_v1_encrypted); + + PERFORM assert_result( + 'jsonb_array_elements_text returns array elements from jsonb_path_query result', + 'SELECT eql_v1.jsonb_array_elements_text(eql_v1.jsonb_path_query(e, ''f510853730e1c3dbd31b86963f029dd5'')) as e FROM encrypted;'); + + PERFORM assert_count( + 'jsonb_array_elements_text returns the correct number of array elements from jsonb_path_query result', + 'SELECT eql_v1.jsonb_array_elements_text(eql_v1.jsonb_path_query(e, ''f510853730e1c3dbd31b86963f029dd5'')) as e FROM encrypted;', + 5); + + PERFORM assert_exception( + 'jsonb_array_elements_text exception if input is not an array', + 'SELECT eql_v1.jsonb_array_elements_text(eql_v1.jsonb_path_query(e, ''33743aed3ae636f6bf05cff11ac4b519'')) as e FROM encrypted LIMIT 1;'); + + END; +$$ LANGUAGE plpgsql; + + +-- ======================================================================== +-- +-- Selector &.a[*] +-- -> 33743aed3ae636f6bf05cff11ac4b519 +-- +DO $$ +DECLARE + sv eql_v1_encrypted; + results eql_v1_encrypted[]; + BEGIN + + PERFORM seed_encrypted_json(); + PERFORM seed_encrypted(get_array_ste_vec()::eql_v1_encrypted); + + PERFORM assert_result( + 'jsonb_array_length returns array length of jsonb_path_query result', + 'SELECT eql_v1.jsonb_array_length(eql_v1.jsonb_path_query(e, ''f510853730e1c3dbd31b86963f029dd5'')) as e FROM encrypted LIMIT 1;', + '5'); + + PERFORM assert_exception( + 'jsonb_array_length exception if input is not an array', + 'SELECT eql_v1.jsonb_array_length(eql_v1.jsonb_path_query(e, ''33743aed3ae636f6bf05cff11ac4b519'')) as e FROM encrypted LIMIT 1;'); + + END; +$$ LANGUAGE plpgsql; + + + +-- -- ======================================================================== +-- +-- -- "{\"hello\": \"four\", \"n\": 20, \"a\": [1, 2, 3, 4, 5] }", +-- +-- Selector &.a[*] +-- -> 33743aed3ae636f6bf05cff11ac4b519 +-- +DO $$ +DECLARE + sv eql_v1_encrypted; + results eql_v1_encrypted[]; + BEGIN + + PERFORM seed_encrypted_json(); + + -- Insert a row with array selector + sv := get_array_ste_vec()::eql_v1_encrypted; + PERFORM seed_encrypted(sv); + + PERFORM assert_count( + 'jsonb_path_query with array selector returns count', + 'SELECT eql_v1.jsonb_path_query_first(e, ''33743aed3ae636f6bf05cff11ac4b519'') as e FROM encrypted;', + 4 + ); + + PERFORM assert_count( + 'jsonb_path_query with array selector returns count', + 'SELECT eql_v1.jsonb_path_query_first(e, ''33743aed3ae636f6bf05cff11ac4b519'') as e FROM encrypted WHERE eql_v1.jsonb_path_query_first(e, ''33743aed3ae636f6bf05cff11ac4b519'') IS NOT NULL;', + 1 + ); + + END; +$$ LANGUAGE plpgsql; + + +-- ------------------------------------------------------------------------ + + + +-- ------------------------------------------------------------------------ +-- +-- jsonb_path_query +-- + +-- Paths +-- $ -> bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- +-- +DO $$ + BEGIN + PERFORM seed_encrypted_json(); + + PERFORM assert_result( + 'jsonb_path_query', + 'SELECT eql_v1.jsonb_path_query(e, ''2517068c0d1f9d4d41d2c666211f785e'') FROM encrypted LIMIT 1;'); + + PERFORM assert_count( + 'jsonb_path_query returns count', + 'SELECT eql_v1.jsonb_path_query(e, ''2517068c0d1f9d4d41d2c666211f785e'') FROM encrypted;', + 3); + + END; +$$ LANGUAGE plpgsql; + + +DO $$ + BEGIN + + PERFORM seed_encrypted_json(); + + PERFORM assert_result( + 'jsonb_path_exists returns true', + 'SELECT eql_v1.jsonb_path_exists(e, ''2517068c0d1f9d4d41d2c666211f785e'') FROM encrypted LIMIT 1;', + 'true'); + + PERFORM assert_result( + 'jsonb_path_exists returns false', + 'SELECT eql_v1.jsonb_path_exists(e, ''blahvtha'') FROM encrypted LIMIT 1;', + 'false'); + + PERFORM assert_count( + 'jsonb_path_exists returns count', + 'SELECT eql_v1.jsonb_path_exists(e, ''2517068c0d1f9d4d41d2c666211f785e'') FROM encrypted;', + 3); + END; +$$ LANGUAGE plpgsql; + + + +-- -- +-- -- Selector &.a[*] +-- -- -> 33743aed3ae636f6bf05cff11ac4b519 +-- -- +DO $$ +DECLARE + sv eql_v1_encrypted; + results eql_v1_encrypted[]; + BEGIN + + -- Insert a row with array selector + sv := get_array_ste_vec()::eql_v1_encrypted; + PERFORM seed_encrypted(sv); + + PERFORM assert_result( + 'jsonb_path_query with array selector', + 'SELECT eql_v1.jsonb_path_query(e, ''f510853730e1c3dbd31b86963f029dd5'') FROM encrypted;'); + + -- An array should be wrapped and returned as a single element + PERFORM assert_count( + 'jsonb_path_query with array selector returns one result', + 'SELECT eql_v1.jsonb_path_query(e, ''f510853730e1c3dbd31b86963f029dd5'') FROM encrypted;', + 1); + END; +$$ LANGUAGE plpgsql; + + + +-- -- +-- -- Selector &.a[*] +-- -- -> 33743aed3ae636f6bf05cff11ac4b519 +-- -- +DO $$ + DECLARE + sv eql_v1_encrypted; + results eql_v1_encrypted[]; + BEGIN + + PERFORM seed_encrypted_json(); + + -- Insert a row with array selector + sv := get_array_ste_vec()::eql_v1_encrypted; + PERFORM seed_encrypted(sv); + + PERFORM assert_result( + 'jsonb_path_exists with array selector', + 'SELECT eql_v1.jsonb_path_exists(e, ''f510853730e1c3dbd31b86963f029dd5'') FROM encrypted;'); + + PERFORM assert_count( + 'jsonb_path_exists with array selector returns correct number of records', + 'SELECT eql_v1.jsonb_path_exists(e, ''f510853730e1c3dbd31b86963f029dd5'') FROM encrypted;', + 4); + END; +$$ LANGUAGE plpgsql; + + + +-- -- +-- -- Selector &.a[*] +-- -- -> 33743aed3ae636f6bf05cff11ac4b519 +-- -- +DO $$ +DECLARE + sv eql_v1_encrypted; + results eql_v1_encrypted[]; + BEGIN + + PERFORM seed_encrypted_json(); + + -- Insert a row with array selector + sv := get_array_ste_vec()::eql_v1_encrypted; + PERFORM seed_encrypted(sv); + + PERFORM assert_count( + 'jsonb_path_query with array selector returns count', + 'SELECT eql_v1.jsonb_path_query_first(e, ''33743aed3ae636f6bf05cff11ac4b519'') as e FROM encrypted;', + 4 + ); + + PERFORM assert_count( + 'jsonb_path_query with array selector returns count', + 'SELECT eql_v1.jsonb_path_query_first(e, ''33743aed3ae636f6bf05cff11ac4b519'') as e FROM encrypted WHERE eql_v1.jsonb_path_query_first(e, ''33743aed3ae636f6bf05cff11ac4b519'') IS NOT NULL;', + 1 + ); + + END; +$$ LANGUAGE plpgsql; + + diff --git a/src/match/functions.sql b/src/match/functions.sql index 0febe46..0df79c0 100644 --- a/src/match/functions.sql +++ b/src/match/functions.sql @@ -2,7 +2,7 @@ -- extracts match index from an emcrypted column -DROP FUNCTION IF EXISTS eql_v1.match(val jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.match(val jsonb); CREATE FUNCTION eql_v1.match(val jsonb) RETURNS eql_v1.match_index @@ -18,7 +18,7 @@ $$ LANGUAGE plpgsql; -- extracts unique index from an encrypted column -DROP FUNCTION IF EXISTS eql_v1.match(val eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1.match(val eql_v1_encrypted); CREATE FUNCTION eql_v1.match(val eql_v1_encrypted) RETURNS eql_v1.match_index @@ -28,5 +28,3 @@ AS $$ RETURN (SELECT eql_v1.match(val.data)); END; $$ LANGUAGE plpgsql; - - diff --git a/src/match/types.sql b/src/match/types.sql index c89e406..0b72631 100644 --- a/src/match/types.sql +++ b/src/match/types.sql @@ -1,5 +1,5 @@ -- REQUIRE: src/schema.sql -DROP DOMAIN IF EXISTS eql_v1.match_index; +-- DROP DOMAIN IF EXISTS eql_v1.match_index; CREATE DOMAIN eql_v1.match_index AS smallint[]; diff --git a/src/operators/->.sql b/src/operators/->.sql index 904373d..8c997e6 100644 --- a/src/operators/->.sql +++ b/src/operators/->.sql @@ -1,51 +1,81 @@ +-- REQUIRE: src/schema.sql -- REQUIRE: src/encrypted/types.sql -- REQUIRE: src/encrypted/functions.sql - -DROP OPERATOR IF EXISTS -> (eql_v1_encrypted, text); -DROP FUNCTION IF EXISTS eql_v1."->"(e eql_v1_encrypted, selector text); - -- --- Returns +-- The -> operator returns an encrypted matching the selector +-- Encyprted JSON is represented as an array of `eql_v1_encrypted`. +-- Each `eql_v1_encrypted` value has a selector, ciphertext, and an index term of +-- - blake3 +-- - ore_cllw_u64_8 +-- - ore_cllw_var_8 -- +-- { +-- "sv": [ {"c": "", "s": "", "b": "" } ] +-- } +-- +-- DROP OPERATOR IF EXISTS -> (eql_v1_encrypted, text); + +-- DROP FUNCTION IF EXISTS eql_v1."->"(e eql_v1_encrypted, selector text); + CREATE FUNCTION eql_v1."->"(e eql_v1_encrypted, selector text) RETURNS eql_v1_encrypted IMMUTABLE STRICT PARALLEL SAFE AS $$ - -- DECLARE - -- j jsonb; - -- found: text; - -- ignored: text; + DECLARE + sv eql_v1_encrypted[]; + found eql_v1_encrypted; + BEGIN + + IF e IS NULL THEN + RETURN NULL; + END IF; + + sv := eql_v1.ste_vec(e); + + FOR idx IN 1..array_length(sv, 1) LOOP + if eql_v1.selector(sv[idx]) = selector THEN + found := sv[idx]; + END IF; + END LOOP; + + RETURN found; + END; +$$ LANGUAGE plpgsql; + + +-- + + +-- DROP OPERATOR IF EXISTS -> (eql_v1_encrypted, integer); + +-- DROP FUNCTION IF EXISTS eql_v1."->"(e eql_v1_encrypted, selector integer); + +CREATE FUNCTION eql_v1."->"(e eql_v1_encrypted, selector integer) + RETURNS eql_v1_encrypted + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + DECLARE + sv eql_v1_encrypted[]; + found eql_v1_encrypted; BEGIN + IF NOT eql_v1.is_ste_vec_array(e) THEN + RETURN NULL; + END IF; + + sv := eql_v1.ste_vec(e); - -- j := e->'j'; - -- PERFORM eql_v1.log(j); - - -- FOR i IN 1..jsonb_array_length(j, 1) LOOP - -- -- -- The ELSE part is to help ensure constant time operation. - -- -- -- The result is thrown away. - -- IF j[i]->'s' = selector THEN - -- found := j[i]->'c'; - -- ELSE - -- ignored := j[i]->'c'; - -- END IF; - -- END LOOP; - - -- IF found IS NOT NULL THEN - -- RETURN found; - -- ELSE - -- RETURN NULL; - -- END IF; - - RETURN ( - SELECT elem::eql_v1_encrypted - FROM jsonb_array_elements(e.data->'j') AS elem - WHERE elem->>'s' = selector - LIMIT 1 - ); + -- PostgreSQL arrays are 1-based + -- JSONB arrays are 0-based and so the selector is 0-based + FOR idx IN 1..array_length(sv, 1) LOOP + if (idx-1) = selector THEN + found := sv[idx]; + END IF; + END LOOP; + RETURN found; END; $$ LANGUAGE plpgsql; @@ -57,34 +87,9 @@ CREATE OPERATOR ->( ); --- ste_vec_index := eql_v1.ste_vec(col); - --- IF ste_vec_index IS NULL THEN --- RETURN NULL; --- END IF; - --- target_selector := selector->>'svs'; - --- FOR i IN 1..array_length(ste_vec_index.entries, 1) LOOP --- -- The ELSE part is to help ensure constant time operation. --- -- The result is thrown away. --- IF ste_vec_index.entries[i].tokenized_selector = target_selector THEN --- found := ste_vec_index.entries[i].ciphertext; --- ELSE --- ignored := ste_vec_index.entries[i].ciphertext; --- END IF; --- END LOOP; - --- IF found IS NOT NULL THEN --- RETURN jsonb_build_object( --- 'k', 'ct', --- 'c', found, --- 'o', NULL, --- 'm', NULL, --- 'u', NULL, --- 'i', col->'i', --- 'v', 1 --- ); --- ELSE --- RETURN NULL; --- END IF; +CREATE OPERATOR ->( + FUNCTION=eql_v1."->", + LEFTARG=eql_v1_encrypted, + RIGHTARG=integer +); + diff --git a/src/operators/->>.sql b/src/operators/->>.sql index 2e7ed8e..7a49b7a 100644 --- a/src/operators/->>.sql +++ b/src/operators/->>.sql @@ -1,46 +1,22 @@ +-- REQUIRE: src/schema.sql -- REQUIRE: src/encrypted/types.sql -- REQUIRE: src/encrypted/functions.sql - -DROP OPERATOR IF EXISTS ->> (eql_v1_encrypted, text); -DROP FUNCTION IF EXISTS eql_v1."->>"(e eql_v1_encrypted, selector text); +-- DROP OPERATOR IF EXISTS ->> (eql_v1_encrypted, text); +-- DROP FUNCTION IF EXISTS eql_v1."->>"(e eql_v1_encrypted, selector text); CREATE FUNCTION eql_v1."->>"(e eql_v1_encrypted, selector text) RETURNS text - IMMUTABLE STRICT PARALLEL SAFE +IMMUTABLE STRICT PARALLEL SAFE AS $$ DECLARE - j jsonb; - found text; - ignored text; + found eql_v1_encrypted; BEGIN - -- j := e.data->'j'; - -- -- PERFORM eql_v1.log(j::text); - -- PERFORM eql_v1.log('jsonb_array_length(j)'); - -- PERFORM eql_v1.log(jsonb_array_length(j)::text); - - -- FOR i IN 0..jsonb_array_length(j) LOOP - -- -- The ELSE part is to help ensure constant time operation. - -- -- The result is thrown away. - -- IF j[i]->>'s' = selector THEN - -- found := eql_v1.ciphertext(j->i); - -- ELSE - -- ignored := eql_v1.ciphertext(j->i); - -- END IF; - -- END LOOP; - - -- IF found IS NOT NULL THEN - -- RETURN found; - -- ELSE - -- RETURN NULL; - -- END IF; - RETURN ( - SELECT eql_v1.ciphertext(elem) - FROM jsonb_array_elements(e.data->'j') AS elem - WHERE elem->>'s' = selector - LIMIT 1 - ); + + found = eql_v1."->"(e, selector); + + RETURN eql_v1.ciphertext(found); END; $$ LANGUAGE plpgsql; diff --git a/src/operators/->>_test.sql b/src/operators/->>_test.sql new file mode 100644 index 0000000..afae58d --- /dev/null +++ b/src/operators/->>_test.sql @@ -0,0 +1,45 @@ +\set ON_ERROR_STOP on + +SELECT create_table_with_encrypted(); +SELECT seed_encrypted_json(); + +-- +-- The ->> operator returns ciphertext matching the selector +DO $$ + BEGIN + PERFORM assert_result( + 'Selector ->> returns at least one eql_v1_encrypted', + 'SELECT e->>''bca213de9ccce676fa849ff9c4807963'' FROM encrypted;'); + + PERFORM assert_count( + 'Selector ->> returns all eql_v1_encrypted', + 'SELECT e->>''bca213de9ccce676fa849ff9c4807963'' FROM encrypted;', + 3); + END; +$$ LANGUAGE plpgsql; + + +-- +-- The ->> operator returns NULL if no matching selector +DO $$ + BEGIN + PERFORM assert_no_result( + 'Unknown selector -> returns null', + 'SELECT e->>''blahvtha'' FROM encrypted;'); + + END; +$$ LANGUAGE plpgsql; + + +-- +-- The ->> operator returns ciphertext matching the selector +DO $$ + BEGIN + + PERFORM assert_result( + 'Selector ->> returns all eql_v1_encrypted', + 'SELECT e->>''bca213de9ccce676fa849ff9c4807963'' FROM encrypted LIMIT 1;', + 'mBbLGB9xHAGzLvUj-`@Wmf=IhD87n7r3ir3n!Sk6AKir_YawR=0c>pk(OydB;ntIEXK~c>V&4>)rNkf_test.sql b/src/operators/->_test.sql new file mode 100644 index 0000000..34983f5 --- /dev/null +++ b/src/operators/->_test.sql @@ -0,0 +1,79 @@ +\set ON_ERROR_STOP on + + +SELECT create_table_with_encrypted(); +SELECT seed_encrypted_json(); + + +-- +-- The -> operator returns an encrypted matching the selector +DO $$ + BEGIN + PERFORM assert_result( + 'Selector -> returns at least one eql_v1_encrypted', + 'SELECT e->''bca213de9ccce676fa849ff9c4807963'' FROM encrypted;'); + + PERFORM assert_count( + 'Selector -> returns all eql_v1_encrypted', + 'SELECT e->''bca213de9ccce676fa849ff9c4807963'' FROM encrypted;', + 3); + END; +$$ LANGUAGE plpgsql; + + +-- +-- The -> operator returns NULL if no matching selector +DO $$ + BEGIN + PERFORM assert_no_result( + 'Unknown selector -> returns null', + 'SELECT e->''blahvtha'' FROM encrypted;'); + + END; +$$ LANGUAGE plpgsql; + + + +-- +-- encrypted returned from -> operator expression called via eql_v1.ciphertext +-- +DO $$ + DECLARE + result eql_v1_encrypted; + BEGIN + PERFORM assert_result( + 'Fetch ciphertext via selector', + 'SELECT eql_v1.ciphertext(e->''2517068c0d1f9d4d41d2c666211f785e'') FROM encrypted;'); + + PERFORM assert_count( + 'Fetch ciphertext via selector returns all eql_v1_encrypted', + 'SELECT eql_v1.ciphertext(e->''2517068c0d1f9d4d41d2c666211f785e'') FROM encrypted;', + 3); + END; +$$ LANGUAGE plpgsql; + + + +-- +-- encrypted returned from -> operator expression called via eql_v1.ciphertext +-- +DO $$ + DECLARE + result eql_v1_encrypted; + BEGIN + + PERFORM truncate_table_with_encrypted(); + PERFORM seed_encrypted(get_array_ste_vec()::eql_v1_encrypted); + + PERFORM assert_result( + '-> operator with integer returns array', + 'SELECT eql_v1.jsonb_path_query_first(e, ''f510853730e1c3dbd31b86963f029dd5'')->0 as e FROM encrypted'); + + PERFORM assert_count( + '-> operator with integer returns array', + 'SELECT eql_v1.jsonb_path_query_first(e, ''f510853730e1c3dbd31b86963f029dd5'')->0 as e FROM encrypted', + 1); + + END; +$$ LANGUAGE plpgsql; + diff --git a/src/operators/<.sql b/src/operators/<.sql index 89a1835..9421c22 100644 --- a/src/operators/<.sql +++ b/src/operators/<.sql @@ -20,22 +20,39 @@ -- -- -DROP OPERATOR CLASS IF EXISTS eql_v1.encrypted_operator USING btree; -DROP OPERATOR FAMILY IF EXISTS eql_v1.encrypted_operator USING btree; - -DROP FUNCTION IF EXISTS eql_v1.lt(a eql_v1_encrypted, b eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1.lt(a eql_v1_encrypted, b eql_v1_encrypted); CREATE FUNCTION eql_v1.lt(a eql_v1_encrypted, b eql_v1_encrypted) RETURNS boolean AS $$ BEGIN - RETURN eql_v1.ore_64_8_v1(a) < eql_v1.ore_64_8_v1(b); + + BEGIN + RETURN eql_v1.ore_cllw_u64_8(a) < eql_v1.ore_cllw_u64_8(b); + -- RETURN (eql_v1.unique(a) = eql_v1.unique(b)); + EXCEPTION WHEN OTHERS THEN + -- PERFORM eql_v1.log('eql_v1.lt no ore_cllw_u64_8 index'); + END; + + BEGIN + RETURN eql_v1.ore_cllw_var_8(a) < eql_v1.ore_cllw_var_8(b); + EXCEPTION WHEN OTHERS THEN + -- PERFORM eql_v1.log('eql_v1.lt no ore_cllw_var_8 index'); + END; + + BEGIN + RETURN eql_v1.ore_64_8_v1(a) < eql_v1.ore_64_8_v1(b); + EXCEPTION WHEN OTHERS THEN + -- PERFORM eql_v1.log('eql_v1.lt no ore_64_8_v1 index'); + END; + + RETURN false; END; $$ LANGUAGE plpgsql; -DROP OPERATOR IF EXISTS < (eql_v1_encrypted, eql_v1_encrypted); -DROP FUNCTION IF EXISTS eql_v1."<"(a eql_v1_encrypted, b eql_v1_encrypted); +-- DROP OPERATOR IF EXISTS < (eql_v1_encrypted, eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1."<"(a eql_v1_encrypted, b eql_v1_encrypted); CREATE FUNCTION eql_v1."<"(a eql_v1_encrypted, b eql_v1_encrypted) RETURNS boolean @@ -56,8 +73,8 @@ CREATE OPERATOR <( ); -DROP OPERATOR IF EXISTS < (eql_v1_encrypted, jsonb); -DROP FUNCTION IF EXISTS eql_v1."<"(a eql_v1_encrypted, b jsonb); +-- DROP OPERATOR IF EXISTS < (eql_v1_encrypted, jsonb); +-- DROP FUNCTION IF EXISTS eql_v1."<"(a eql_v1_encrypted, b jsonb); CREATE FUNCTION eql_v1."<"(a eql_v1_encrypted, b jsonb) RETURNS boolean @@ -78,8 +95,8 @@ CREATE OPERATOR <( ); -DROP OPERATOR IF EXISTS < (jsonb, eql_v1_encrypted); -DROP FUNCTION IF EXISTS eql_v1."<"(a jsonb, b eql_v1_encrypted); +-- DROP OPERATOR IF EXISTS < (jsonb, eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1."<"(a jsonb, b eql_v1_encrypted); CREATE FUNCTION eql_v1."<"(a jsonb, b eql_v1_encrypted) RETURNS boolean diff --git a/src/operators/<=.sql b/src/operators/<=.sql index af6a280..75e4cad 100644 --- a/src/operators/<=.sql +++ b/src/operators/<=.sql @@ -20,19 +20,38 @@ -- -- -DROP FUNCTION IF EXISTS eql_v1.lte(a eql_v1_encrypted, b eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1.lte(a eql_v1_encrypted, b eql_v1_encrypted); CREATE FUNCTION eql_v1.lte(a eql_v1_encrypted, b eql_v1_encrypted) RETURNS boolean AS $$ BEGIN - RETURN eql_v1.ore_64_8_v1(a) >= eql_v1.ore_64_8_v1(b); + + BEGIN + RETURN eql_v1.ore_cllw_u64_8(a) <= eql_v1.ore_cllw_u64_8(b); + EXCEPTION WHEN OTHERS THEN + -- PERFORM eql_v1.log('eql_v1.lte no ore_cllw_u64_8 index'); + END; + + BEGIN + RETURN eql_v1.ore_cllw_var_8(a) <= eql_v1.ore_cllw_var_8(b); + EXCEPTION WHEN OTHERS THEN + -- PERFORM eql_v1.log('eql_v1.lte no ore_cllw_var_8 index'); + END; + + BEGIN + RETURN eql_v1.ore_64_8_v1(a) <= eql_v1.ore_64_8_v1(b); + EXCEPTION WHEN OTHERS THEN + -- PERFORM eql_v1.log('eql_v1.lte no ore_64_8_v1 index'); + END; + + RETURN false; END; $$ LANGUAGE plpgsql; -DROP OPERATOR IF EXISTS <= (eql_v1_encrypted, eql_v1_encrypted); -DROP FUNCTION IF EXISTS eql_v1."<="(a eql_v1_encrypted, b eql_v1_encrypted); +-- DROP OPERATOR IF EXISTS <= (eql_v1_encrypted, eql_v1_encrypted) CASCADE; +-- DROP FUNCTION IF EXISTS eql_v1."<="(a eql_v1_encrypted, b eql_v1_encrypted); CREATE FUNCTION eql_v1."<="(a eql_v1_encrypted, b eql_v1_encrypted) RETURNS boolean @@ -53,8 +72,8 @@ CREATE OPERATOR <=( ); -DROP OPERATOR IF EXISTS <= (eql_v1_encrypted, jsonb); -DROP FUNCTION IF EXISTS eql_v1."<="(a eql_v1_encrypted, b jsonb); +-- DROP OPERATOR IF EXISTS <= (eql_v1_encrypted, jsonb); +-- DROP FUNCTION IF EXISTS eql_v1."<="(a eql_v1_encrypted, b jsonb); CREATE FUNCTION eql_v1."<="(a eql_v1_encrypted, b jsonb) RETURNS boolean @@ -75,8 +94,8 @@ CREATE OPERATOR <=( ); -DROP OPERATOR IF EXISTS <= (jsonb, eql_v1_encrypted); -DROP FUNCTION IF EXISTS eql_v1."<="(a jsonb, b eql_v1_encrypted); +-- DROP OPERATOR IF EXISTS <= (jsonb, eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1."<="(a jsonb, b eql_v1_encrypted); CREATE FUNCTION eql_v1."<="(a jsonb, b eql_v1_encrypted) RETURNS boolean diff --git a/src/operators/<=_test.sql b/src/operators/<=_test.sql index e091a6b..dd7b136 100644 --- a/src/operators/<=_test.sql +++ b/src/operators/<=_test.sql @@ -3,6 +3,99 @@ SELECT create_table_with_encrypted(); SELECT seed_encrypted_json(); +SELECT e FROM encrypted WHERE e->'a7cea93975ed8c01f861ccb6bd082784' <= '("{""c"": ""mBbM0#UZON2jQ3@LiWcvns2Yf6y3L;hykEh`}*fX#aF;n*=>+*o5Uarod39C7TF-SiCD-NgkG)l%Vw=l!tX>H*P bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- +-- -- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- This extracts the data associated with the field from the test eql_v1_encrypted + -- json n: 30 + sv := get_numeric_ste_vec_30()::eql_v1_encrypted; + -- extract the term at $.n returned as eql_v1_encrypted + term := sv->'2517068c0d1f9d4d41d2c666211f785e'; + + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted <= eql_v1_encrypted with ore_cllw_u64_8 index term'), + format('SELECT e FROM encrypted WHERE e->''2517068c0d1f9d4d41d2c666211f785e'' <= %L::eql_v1_encrypted', term)); + + PERFORM assert_count( + format('eql_v1_encrypted <= eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE e->''2517068c0d1f9d4d41d2c666211f785e'' <= %L::eql_v1_encrypted', term), + 3); + + -- -- Check the $.hello path + -- -- Returned encrypted does not have ore_cllw_u64_8 + PERFORM assert_no_result( + format('eql_v1_encrypted <= eql_v1_encrypted with ore_cllw_u64_8 index term'), + format('SELECT e FROM encrypted WHERE e->''a7cea93975ed8c01f861ccb6bd082784'' <= %L::eql_v1_encrypted', term)); + + END; +$$ LANGUAGE plpgsql; + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- eql_v1_encrypted < eql_v1_encrypted with ore_cllw_var_8 index +-- +-- Test data is in form '{"hello": "{one | two | three}", "n": {10 | 20 | 30} }' +-- +-- Paths +-- $ -> bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- +-- -- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- This extracts the data associated with the field from the test eql_v1_encrypted + -- json n: 30 + sv := get_numeric_ste_vec_30()::eql_v1_encrypted; + -- extract the term at $.n returned as eql_v1_encrypted + term := sv->'a7cea93975ed8c01f861ccb6bd082784'; + + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted <= eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE e->''a7cea93975ed8c01f861ccb6bd082784'' <= %L::eql_v1_encrypted', term)); + + PERFORM assert_count( + format('eql_v1_encrypted <= eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE e->''a7cea93975ed8c01f861ccb6bd082784'' <= %L::eql_v1_encrypted', term), + 2); + + -- -- Check the $.n path + -- -- Returned encrypted does not have ore_cllw_u64_8 + PERFORM assert_no_result( + format('eql_v1_encrypted <= eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE e->''2517068c0d1f9d4d41d2c666211f785e'' <= %L::eql_v1_encrypted', term)); + + END; +$$ LANGUAGE plpgsql; + + + -- -- ORE - eql_v1_encrypted <= eql_v1_encrypted -- @@ -12,32 +105,29 @@ DECLARE ore_term jsonb; BEGIN - -- Create a record with HIGH ore - e := create_encrypted_json()::jsonb || get_high_ore(); + -- Record with a Numeric ORE term of 42 + e := create_encrypted_ore_json(42); PERFORM seed_encrypted(e); - -- Default has LOW ore - e := create_encrypted_json(); - PERFORM assert_result( - 'eql_v1_encrypted >= eql_v1_encrypted', + 'eql_v1_encrypted <= eql_v1_encrypted', format('SELECT e FROM encrypted WHERE e <= %L::eql_v1_encrypted', e)); - - for i in 1..3 loop - e := create_encrypted_json(i); - - PERFORM assert_result( - format('eql_v1_encrypted >= eql_v1_encrypted %s of 3', i), - format('SELECT e FROM encrypted WHERE e <= %L;', e)); - - PERFORM assert_count( - format('eql_v1_encrypted >= eql_v1_encrypted %s of 3', i), + PERFORM assert_count( + format('eql_v1_encrypted <= eql_v1_encrypted'), format('SELECT e FROM encrypted WHERE e <= %L;', e), 4); - end loop; + e := create_encrypted_ore_json(20); + + PERFORM assert_result( + 'eql_v1_encrypted <= eql_v1_encrypted', + format('SELECT e FROM encrypted WHERE e <= %L::eql_v1_encrypted', e)); + PERFORM assert_count( + format('eql_v1_encrypted <= eql_v1_encrypted'), + format('SELECT e FROM encrypted WHERE e <= %L;', e), + 2); END; $$ LANGUAGE plpgsql; @@ -50,13 +140,25 @@ DECLARE e eql_v1_encrypted; ore_term jsonb; BEGIN + -- Reset data + PERFORM seed_encrypted_json(); - -- Create a record with HIGH ore - e := create_encrypted_json()::jsonb || get_high_ore(); + -- Record with a Numeric ORE term of 42 + e := create_encrypted_ore_json(42); PERFORM seed_encrypted(e); - -- Default has LOW ore - e := create_encrypted_json(); + PERFORM assert_result( + 'eql_v1.lte(a eql_v1_encrypted, b eql_v1_encrypted)', + format('SELECT e FROM encrypted WHERE eql_v1.lte(e, %L)', e)); + + -- include + PERFORM assert_count( + 'eql_v1.lte(a eql_v1_encrypted, b eql_v1_encrypted)', + format('SELECT e FROM encrypted WHERE eql_v1.lte(e, %L)', e), + 4); + + -- Record with a Numeric ORE term of 30 + e := create_encrypted_ore_json(30); PERFORM assert_result( 'eql_v1.get(a eql_v1_encrypted, b eql_v1_encrypted)', @@ -65,7 +167,7 @@ DECLARE PERFORM assert_count( 'eql_v1.get(a eql_v1_encrypted, b eql_v1_encrypted)', format('SELECT e FROM encrypted WHERE eql_v1.lte(e, %L)', e), - 5); + 3); END; $$ LANGUAGE plpgsql; diff --git a/src/operators/<>.sql b/src/operators/<>.sql index 57cbfdd..de55a65 100644 --- a/src/operators/<>.sql +++ b/src/operators/<>.sql @@ -21,7 +21,7 @@ -- We check these index terms in this order and use the first one that exists for both parameters -- -- -DROP FUNCTION IF EXISTS eql_v1.neq(a eql_v1_encrypted, b eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1.neq(a eql_v1_encrypted, b eql_v1_encrypted); CREATE FUNCTION eql_v1.neq(a eql_v1_encrypted, b eql_v1_encrypted) RETURNS boolean @@ -33,8 +33,8 @@ AS $$ $$ LANGUAGE plpgsql; -DROP OPERATOR IF EXISTS <> (eql_v1_encrypted, eql_v1_encrypted); -DROP FUNCTION IF EXISTS eql_v1."<>"(a eql_v1_encrypted, b eql_v1_encrypted); +-- DROP OPERATOR IF EXISTS <> (eql_v1_encrypted, eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1."<>"(a eql_v1_encrypted, b eql_v1_encrypted); CREATE FUNCTION eql_v1."<>"(a eql_v1_encrypted, b eql_v1_encrypted) RETURNS boolean @@ -57,8 +57,8 @@ CREATE OPERATOR <> ( MERGES ); -DROP OPERATOR IF EXISTS <> (eql_v1_encrypted, jsonb); -DROP FUNCTION IF EXISTS eql_v1."<>"(a eql_v1_encrypted, b jsonb); +-- DROP OPERATOR IF EXISTS <> (eql_v1_encrypted, jsonb); +-- DROP FUNCTION IF EXISTS eql_v1."<>"(a eql_v1_encrypted, b jsonb); CREATE FUNCTION eql_v1."<>"(a eql_v1_encrypted, b jsonb) RETURNS boolean @@ -81,8 +81,8 @@ CREATE OPERATOR <> ( ); -DROP OPERATOR IF EXISTS <> (jsonb, eql_v1_encrypted); -DROP FUNCTION IF EXISTS eql_v1."<>"(a jsonb, b eql_v1_encrypted); +-- DROP OPERATOR IF EXISTS <> (jsonb, eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1."<>"(a jsonb, b eql_v1_encrypted); CREATE FUNCTION eql_v1."<>"(a jsonb, b eql_v1_encrypted) RETURNS boolean diff --git a/src/operators/<>_ore_cllw_u64_8_test.sql b/src/operators/<>_ore_cllw_u64_8_test.sql new file mode 100644 index 0000000..1a950a2 --- /dev/null +++ b/src/operators/<>_ore_cllw_u64_8_test.sql @@ -0,0 +1,57 @@ +\set ON_ERROR_STOP on + +SELECT create_table_with_encrypted(); +SELECT seed_encrypted_json(); + + +-- ======================================================================== + + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- ore_cllw_u64_8 equality +-- +-- Test data is '{"hello": "world", "n": 42}' + +-- Paths +-- $ -> bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- +-- + +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- This extracts the data associated with the field from the test eql_v1_encrypted + -- json n: 10 + sv := get_numeric_ste_vec_10()::eql_v1_encrypted; + -- extract the term at $.n returned as eql_v1_encrypted + term := sv->'2517068c0d1f9d4d41d2c666211f785e'; + + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted <> eql_v1_encrypted with ore_cllw_u64_8 index term'), + format('SELECT e FROM encrypted WHERE (e->''2517068c0d1f9d4d41d2c666211f785e'') <> %L::eql_v1_encrypted', term)); + + PERFORM assert_count( + format('eql_v1_encrypted <> eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE e->''2517068c0d1f9d4d41d2c666211f785e'' <> %L::eql_v1_encrypted', term), + 2); + + -- -- Check the $.hello path + -- -- Returned encrypted does not have ore_cllw_u64_8 + PERFORM assert_result( + format('eql_v1_encrypted <> eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE e->''a7cea93975ed8c01f861ccb6bd082784'' <> %L::eql_v1_encrypted', term)); + + END; +$$ LANGUAGE plpgsql; + + + +SELECT drop_table_with_encrypted(); \ No newline at end of file diff --git a/src/operators/<>_ore_cllw_var_8_test.sql b/src/operators/<>_ore_cllw_var_8_test.sql new file mode 100644 index 0000000..6cc0438 --- /dev/null +++ b/src/operators/<>_ore_cllw_var_8_test.sql @@ -0,0 +1,56 @@ +\set ON_ERROR_STOP on + +SELECT create_table_with_encrypted(); +SELECT seed_encrypted_json(); + + +-- ======================================================================== + + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- ore_cllw_u64_8 equality +-- +-- Test data is '{"hello": "world", "n": 42}' + +-- Paths +-- $ -> bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- +-- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- This extracts the data associated with the field from the test eql_v1_encrypted + -- json n: 10 + sv := get_numeric_ste_vec_10()::eql_v1_encrypted; + -- extract the term at $.n returned as eql_v1_encrypted + term := sv->'a7cea93975ed8c01f861ccb6bd082784'; + + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted <> eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE (e->''a7cea93975ed8c01f861ccb6bd082784'') <> %L::eql_v1_encrypted', term)); + + PERFORM assert_count( + format('eql_v1_encrypted <> eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE e->''a7cea93975ed8c01f861ccb6bd082784'' <> %L::eql_v1_encrypted', term), + 2); + + -- -- Check the $.n path + -- -- Returned encrypted does not have ore_cllw_var_8 + PERFORM assert_result( + format('eql_v1_encrypted <> eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE e->''2517068c0d1f9d4d41d2c666211f785e'' <> %L::eql_v1_encrypted', term)); + + END; +$$ LANGUAGE plpgsql; + + + +SELECT drop_table_with_encrypted(); \ No newline at end of file diff --git a/src/operators/<>_ore_test.sql b/src/operators/<>_ore_test.sql new file mode 100644 index 0000000..eaddf83 --- /dev/null +++ b/src/operators/<>_ore_test.sql @@ -0,0 +1,87 @@ +\set ON_ERROR_STOP on + +SELECT create_table_with_encrypted(); +SELECT seed_encrypted_json(); + + +-- +-- ORE - eql_v1_encrypted <> eql_v1_encrypted +-- +DO $$ +DECLARE + e eql_v1_encrypted; + ore_term jsonb; + BEGIN + + -- Record with a Numeric ORE term of 42 + e := create_encrypted_ore_json(42); + PERFORM seed_encrypted(e); + + PERFORM assert_result( + 'eql_v1_encrypted <> eql_v1_encrypted', + format('SELECT e FROM encrypted WHERE e <> %L::eql_v1_encrypted', e)); + + PERFORM assert_count( + format('eql_v1_encrypted <> eql_v1_encrypted'), + format('SELECT e FROM encrypted WHERE e <> %L;', e), + 3); + + e := create_encrypted_ore_json(20); + + PERFORM assert_result( + 'eql_v1_encrypted <> eql_v1_encrypted', + format('SELECT e FROM encrypted WHERE e <> %L::eql_v1_encrypted', e)); + + PERFORM assert_count( + format('eql_v1_encrypted <> eql_v1_encrypted'), + format('SELECT e FROM encrypted WHERE e <> %L;', e), + 3); + END; +$$ LANGUAGE plpgsql; + + +-- +-- ORE - eql_v1.gte(a eql_v1_encrypted, b eql_v1_encrypted) +-- +DO $$ +DECLARE + e eql_v1_encrypted; + ore_term jsonb; + BEGIN + -- Reset data + PERFORM seed_encrypted_json(); + + -- Record with a Numeric ORE term of 20 + e := create_encrypted_ore_json(20); + PERFORM seed_encrypted(e); + + PERFORM assert_result( + 'eql_v1.neq(a eql_v1_encrypted, b eql_v1_encrypted)', + format('SELECT e FROM encrypted WHERE eql_v1.neq(e, %L)', e)); + + -- include + PERFORM assert_count( + 'eql_v1.neq(a eql_v1_encrypted, b eql_v1_encrypted)', + format('SELECT e FROM encrypted WHERE eql_v1.neq(e, %L)', e), + 2); + + -- Record with a Numeric ORE term of 30 + e := create_encrypted_ore_json(30); + + PERFORM assert_result( + 'eql_v1.get(a eql_v1_encrypted, b eql_v1_encrypted)', + format('SELECT e FROM encrypted WHERE eql_v1.neq(e, %L)', e)); + + PERFORM assert_count( + 'eql_v1.get(a eql_v1_encrypted, b eql_v1_encrypted)', + format('SELECT e FROM encrypted WHERE eql_v1.neq(e, %L)', e), + 3); + END; +$$ LANGUAGE plpgsql; + + + +-- ======================================================================== + + +SELECT drop_table_with_encrypted(); \ No newline at end of file diff --git a/src/operators/<>_test.sql b/src/operators/<>_test.sql index e4fa58d..844650f 100644 --- a/src/operators/<>_test.sql +++ b/src/operators/<>_test.sql @@ -3,8 +3,9 @@ SELECT create_table_with_encrypted(); SELECT seed_encrypted_json(); + -- --- Unique inequality - eql_v1_encrypted <> eql_v1_encrypted +-- Unique equality - eql_v1_encrypted <> eql_v1_encrypted -- DO $$ DECLARE @@ -12,36 +13,30 @@ DECLARE BEGIN for i in 1..3 loop - e := create_encrypted_json(i)::jsonb-'o'; - - PERFORM assert_result( - format('eql_v1_encrypted <> eql_v1_encrypted with unique index term %s of 3', i), - format('SELECT e FROM encrypted WHERE e <> %L;', e)); + e := create_encrypted_json(i, 'u'); PERFORM assert_count( - format('eql_v1_encrypted <> eql_v1_encrypted with ore index term'), - format('SELECT e FROM encrypted WHERE e <> %L', e), + format('eql_v1_encrypted <> eql_v1_encrypted with unique index term %s of 3', i), + format('SELECT e FROM encrypted WHERE e <> %L;', e), 2); + end loop; - -- remove the ore index term - e := create_encrypted_json(91347)::jsonb-'o'; + -- record not in database + e := create_encrypted_json(91347, 'u'); - PERFORM assert_result( + PERFORM assert_no_result( 'eql_v1_encrypted <> eql_v1_encrypted with no matching record', format('SELECT e FROM encrypted WHERE e <> %L;', e)); - PERFORM assert_count( - 'eql_v1_encrypted <> eql_v1_encrypted with no matching record', - format('SELECT e FROM encrypted WHERE e <> %L;', e), - 3); - END; $$ LANGUAGE plpgsql; +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ -- --- Unique inequality - eql_v1.neq(eql_v1_encrypted, eql_v1_encrypted) +-- Unique equality - eql_v1.neq(eql_v1_encrypted, eql_v1_encrypted) -- DO $$ DECLARE @@ -49,204 +44,122 @@ DECLARE BEGIN for i in 1..3 loop - e := create_encrypted_json(i)::jsonb-'o'; - - PERFORM assert_result( - format('eql_v1.neq(eql_v1_encrypted, eql_v1_encrypted) with unique index term %s of 3', i), - format('SELECT e FROM encrypted WHERE eql_v1.neq(e, %L);', e)); - + e := create_encrypted_json(i, 'u'); PERFORM assert_count( - format('eql_v1_encrypted <> eql_v1_encrypted with ore index term %s of 3', i), + format('eql_v1.neq(eql_v1_encrypted, eql_v1_encrypted) with unique index term %s of 3', i), format('SELECT e FROM encrypted WHERE eql_v1.neq(e, %L);', e), 2); end loop; - -- remove the ore index term - e := create_encrypted_json(91347)::jsonb-'o'; - - PERFORM assert_result( - 'eql_v1.eq(eql_v1_encrypted, eql_v1_encrypted) with no matching record', - format('SELECT e FROM encrypted WHERE eql_v1.neq(e, %L);', e)); + -- record not in database + e := create_encrypted_json(91347, 'u'); - PERFORM assert_count( - 'eql_v1_encrypted <> eql_v1_encrypted with ore index term', - format('SELECT e FROM encrypted WHERE eql_v1.neq(e, %L);', e), - 3); + PERFORM assert_no_result( + 'eql_v1_encrypted <> eql_v1_encrypted with no matching record', + format('SELECT e FROM encrypted WHERE e <> %L;', e)); END; $$ LANGUAGE plpgsql; +-- ======================================================================== + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ -- --- Unique equality - eql_v1_encrypted <> jsonb +-- Blake equality - eql_v1_encrypted <> eql_v1_encrypted -- DO $$ DECLARE - e jsonb; + e eql_v1_encrypted; BEGIN + for i in 1..3 loop - e := create_encrypted_json(i)::jsonb-'o'; + e := create_encrypted_json(i, 'b'); PERFORM assert_result( - format('eql_v1_encrypted <> eql_v1_encrypted with unique index term %s of 3', i), - format('SELECT e FROM encrypted WHERE e <> %L::jsonb;', e)); + format('eql_v1_encrypted <> eql_v1_encrypted with blake3 index term %s of 3', i), + format('SELECT e FROM encrypted WHERE e <> %L;', e)); - PERFORM assert_count( - format('eql_v1_encrypted <> eql_v1_encrypted with ore index term %s of 3', i), - format('SELECT e FROM encrypted WHERE e <> %L::jsonb', e), - 2); end loop; -- remove the ore index term - e := create_encrypted_json(91347)::jsonb-'o'; - - PERFORM assert_result( - 'eql_v1_encrypted <> eql_v1_encrypted with no matching record', - format('SELECT e FROM encrypted WHERE e <> %L::jsonb;', e)); + e := create_encrypted_json(91347, 'b'); - PERFORM assert_count( + PERFORM assert_no_result( 'eql_v1_encrypted <> eql_v1_encrypted with no matching record', - format('SELECT e FROM encrypted WHERE e <> %L::jsonb;', e), - 3); + format('SELECT e FROM encrypted WHERE e <> %L;', e)); END; $$ LANGUAGE plpgsql; - --- --- ORE inequality eql_v1_encrypted <> eql_v1_encrypted --- --- --- Example ORE values are generated from an array in the form `vec![0, 1, 2, 3, 4, 5]`; --- --- JSON values are JSON escaped on top of a PostgreSQL escaped Record --- --- PostgreSQL value is ("{""(\\""\\\\\\\\x000102030405\\"")""}") +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ -- +-- Blake3 equality - eql_v1.neq(eql_v1_encrypted, eql_v1_encrypted) -- DO $$ DECLARE e eql_v1_encrypted; - ore_term jsonb; BEGIN - -- remove the unique index term - e := create_encrypted_json()::jsonb-'u'; - - -- Same ORE value for all items so no results - PERFORM assert_no_result( - 'eql_v1_encrypted <> eql_v1_encrypted with ore index term', - format('SELECT e FROM encrypted WHERE e <> %L', e)); - - - -- -- not the same ore term - ore_term := '{"o": ["1212121212125932e28282d03415e7714fccd69eb7eb476c70743e485e20331f59cbc1c848dcdeda716f351eb20588c406a7df5fb8917ebf816739aa1414ac3b8498e493bf0badea5c9fdb3cc34da8b152b995957591880c523beb1d3f12487c38d18f62dd26209a727674e5a5fe3a3e3037860839afd8011f94b49eaa5fa5a60e1e2adccde4185a7d6c7f83088500b677f897d4ffc276016d614708488f407c01bd3ccf2be653269062cb97f8945a621d049277d19b1c248611f25d047038928d2efeb4323c402af4c19288c7b36911dc06639af5bb34367519b66c1f525bbd3828c12067c9c579aeeb4fb3ae0918125dc1dad5fd518019a5ae67894ce1a7f7bed1a591ba8edda2fdf4cd403761fd981fb1ea5eb0bf806f919350ee60cac16d0a39a491a4d79301781f95ea3870aea82e9946053537360b2fb415b18b61aed0af81d461ad6b923f10c0df79daddc4e279ff543a282bb3a37f9fa03238348b3dac51a453b04bced1f5bd318ddd829bdfe5f37abdbeda730e21441b818302f3c5c2c4d5657accfca4c53d7a80eb3db43946d38965be5f796b"]}'::jsonb; - - -- remove the unique index term and add the ore term - e := create_encrypted_json()::jsonb-'u' || ore_term; + for i in 1..3 loop + e := create_encrypted_json(i, 'b'); PERFORM assert_result( - 'eql_v1_encrypted <> eql_v1_encrypted with ore index term', - format('SELECT e FROM encrypted WHERE e <> %L', e)); - - PERFORM assert_count( - 'eql_v1_encrypted <> eql_v1_encrypted with no matching record', - format('SELECT e FROM encrypted WHERE e <> %L;', e), - 3); - - END; -$$ LANGUAGE plpgsql; - - - --- -- --- -- ORE equality using the `eql_v1.ore_64_8_v1(eql_v1_encrypted)` function calls --- -- --- -- Example ORE values are generated from an array in the form `vec![0, 1, 2, 3, 4, 5]`; --- -- --- -- JSON values are JSON escaped on top of a PostgreSQL escaped Record --- -- --- -- PostgreSQL value is ("{""(\\""\\\\\\\\x000102030405\\"")""}") --- -- --- -- -DO $$ -DECLARE - e eql_v1_encrypted; - ore_term jsonb; - BEGIN - - -- remove the unique index term - e := create_encrypted_json()::jsonb-'u'; - - PERFORM assert_no_result( - 'eql_v1.ore_64_8_v1(eql_v1_encrypted) <> eql_v1.ore_64_8_v1(eql_v1_encrypted)', - format('SELECT e FROM encrypted WHERE eql_v1.ore_64_8_v1(e) <> eql_v1.ore_64_8_v1(%L::eql_v1_encrypted)', e)); - - -- new ore term - ore_term := '{"o": ["1212121212125932e28282d03415e7714fccd69eb7eb476c70743e485e20331f59cbc1c848dcdeda716f351eb20588c406a7df5fb8917ebf816739aa1414ac3b8498e493bf0badea5c9fdb3cc34da8b152b995957591880c523beb1d3f12487c38d18f62dd26209a727674e5a5fe3a3e3037860839afd8011f94b49eaa5fa5a60e1e2adccde4185a7d6c7f83088500b677f897d4ffc276016d614708488f407c01bd3ccf2be653269062cb97f8945a621d049277d19b1c248611f25d047038928d2efeb4323c402af4c19288c7b36911dc06639af5bb34367519b66c1f525bbd3828c12067c9c579aeeb4fb3ae0918125dc1dad5fd518019a5ae67894ce1a7f7bed1a591ba8edda2fdf4cd403761fd981fb1ea5eb0bf806f919350ee60cac16d0a39a491a4d79301781f95ea3870aea82e9946053537360b2fb415b18b61aed0af81d461ad6b923f10c0df79daddc4e279ff543a282bb3a37f9fa03238348b3dac51a453b04bced1f5bd318ddd829bdfe5f37abdbeda730e21441b818302f3c5c2c4d5657accfca4c53d7a80eb3db43946d38965be5f796b"]}'::jsonb; - - -- remove the unique index term and add the ore term - e := create_encrypted_json()::jsonb-'u' || ore_term; - -- -- PERFORM eql_v1.log('e', e::text); + format('eql_v1.neq(eql_v1_encrypted, eql_v1_encrypted) with unique index term %s of 3', i), + format('SELECT e FROM encrypted WHERE eql_v1.neq(e, %L);', e)); + end loop; - PERFORM assert_result( - 'eql_v1.ore_64_8_v1(eql_v1_encrypted) <> eql_v1.ore_64_8_v1(eql_v1_encrypted)', - format('SELECT e FROM encrypted WHERE eql_v1.ore_64_8_v1(e) <> eql_v1.ore_64_8_v1(%L::eql_v1_encrypted)', e)); + -- remove the ore index term + e := create_encrypted_json(91347, 'b'); - PERFORM assert_count( - 'eql_v1.ore_64_8_v1(eql_v1_encrypted) <> eql_v1.ore_64_8_v1(eql_v1_encrypted)', - format('SELECT e FROM encrypted WHERE eql_v1.ore_64_8_v1(e) <> eql_v1.ore_64_8_v1(%L::eql_v1_encrypted)', e), - 3); + PERFORM assert_no_result( + 'eql_v1.neq(eql_v1_encrypted, eql_v1_encrypted) with no matching record', + format('SELECT e FROM encrypted WHERE eql_v1.neq(e, %L);', e)); END; $$ LANGUAGE plpgsql; - --- --- ORE equality using the `eql_v1.neq(eql_v1_encrypted, eql_v1_encrypted)` function calls --- --- Example ORE values are generated from an array in the form `vec![0, 1, 2, 3, 4, 5]`; --- --- JSON values are JSON escaped on top of a PostgreSQL escaped Record --- --- PostgreSQL value is ("{""(\\""\\\\\\\\x000102030405\\"")""}") +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ -- +-- Blake3 equality - eql_v1_encrypted = jsonb -- DO $$ DECLARE - e eql_v1_encrypted; - ore_term jsonb; + e jsonb; BEGIN + for i in 1..3 loop - -- remove the unique index term - e := create_encrypted_json()::jsonb-'u'; + -- remove the default + e := create_encrypted_json(i, 'b'); - PERFORM assert_no_result( - 'eql_v1.neq(eql_v1_encrypted, eql_v1_encrypted)', - format('SELECT e FROM encrypted WHERE eql_v1.neq(e, %L);', e)); + PERFORM assert_result( + format('eql_v1_encrypted = jsonb with unique index term %s of 3', i), + format('SELECT e FROM encrypted WHERE e <> %L::jsonb;', e)); - -- new ore term - ore_term := '{"o": ["1212121212125932e28282d03415e7714fccd69eb7eb476c70743e485e20331f59cbc1c848dcdeda716f351eb20588c406a7df5fb8917ebf816739aa1414ac3b8498e493bf0badea5c9fdb3cc34da8b152b995957591880c523beb1d3f12487c38d18f62dd26209a727674e5a5fe3a3e3037860839afd8011f94b49eaa5fa5a60e1e2adccde4185a7d6c7f83088500b677f897d4ffc276016d614708488f407c01bd3ccf2be653269062cb97f8945a621d049277d19b1c248611f25d047038928d2efeb4323c402af4c19288c7b36911dc06639af5bb34367519b66c1f525bbd3828c12067c9c579aeeb4fb3ae0918125dc1dad5fd518019a5ae67894ce1a7f7bed1a591ba8edda2fdf4cd403761fd981fb1ea5eb0bf806f919350ee60cac16d0a39a491a4d79301781f95ea3870aea82e9946053537360b2fb415b18b61aed0af81d461ad6b923f10c0df79daddc4e279ff543a282bb3a37f9fa03238348b3dac51a453b04bced1f5bd318ddd829bdfe5f37abdbeda730e21441b818302f3c5c2c4d5657accfca4c53d7a80eb3db43946d38965be5f796b"]}'::jsonb; + PERFORM assert_result( + format('jsonb = eql_v1_encrypted with unique index term %s of 3', i), + format('SELECT e FROM encrypted WHERE %L::jsonb = e', e)); + end loop; - -- remove the unique index term and add the ore term - e := create_encrypted_json()::jsonb-'u' || ore_term; + e := create_encrypted_json(91347, 'b'); + PERFORM assert_no_result( + 'eql_v1_encrypted = jsonb with no matching record', + format('SELECT e FROM encrypted WHERE e <> %L::jsonb', e)); - PERFORM assert_result( - 'eql_v1.neq(eql_v1_encrypted, eql_v1_encrypted)', - format('SELECT e FROM encrypted WHERE eql_v1.neq(e, %L);', e)); - + PERFORM assert_no_result( + 'jsonb = eql_v1_encrypted with no matching record', + format('SELECT e FROM encrypted WHERE %L::jsonb = e', e)); END; $$ LANGUAGE plpgsql; - - - - SELECT drop_table_with_encrypted(); \ No newline at end of file diff --git a/src/operators/<@.sql b/src/operators/<@.sql index e69de29..cbe76bf 100644 --- a/src/operators/<@.sql +++ b/src/operators/<@.sql @@ -0,0 +1,19 @@ +-- REQUIRE: src/schema.sql +-- REQUIRE: src/encrypted/types.sql +-- REQUIRE: src/ste_vec/functions.sql + + +-- DROP OPERATOR IF EXISTS <@ (eql_v1_encrypted, eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1."<@"(e eql_v1_encrypted, b eql_v1_encrypted); + +CREATE FUNCTION eql_v1."<@"(a eql_v1_encrypted, b eql_v1_encrypted) +RETURNS boolean AS $$ + -- Contains with reversed arguments + SELECT eql_v1.ste_vec_contains(b, a) +$$ LANGUAGE SQL; + +CREATE OPERATOR <@( + FUNCTION=eql_v1."<@", + LEFTARG=eql_v1_encrypted, + RIGHTARG=eql_v1_encrypted +); diff --git a/src/operators/<@_test.sql b/src/operators/<@_test.sql new file mode 100644 index 0000000..8012576 --- /dev/null +++ b/src/operators/<@_test.sql @@ -0,0 +1,44 @@ +\set ON_ERROR_STOP on + +SELECT create_table_with_encrypted(); +SELECT seed_encrypted_json(); + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- ore_cllw_u64_8 equality +-- +-- Test data is in form '{"hello": "{one | two | three}", "n": {10 | 20 | 30} }' +-- +-- Paths +-- $ -> bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- +-- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- This extracts the data associated with the field from the test eql_v1_encrypted + sv := get_numeric_ste_vec_10()::eql_v1_encrypted; + -- extract the term at $.n returned as eql_v1_encrypted + term := sv->'a7cea93975ed8c01f861ccb6bd082784'; + + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted = eql_v1_encrypted with ore_cllw_u64_8 index term'), + format('SELECT e FROM encrypted WHERE %L::eql_v1_encrypted <@ e', term)); + + PERFORM assert_count( + format('eql_v1_encrypted = eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE %L::eql_v1_encrypted <@ e', term), + 1); + + END; +$$ LANGUAGE plpgsql; + + +SELECT drop_table_with_encrypted(); \ No newline at end of file diff --git a/src/operators/<_test.sql b/src/operators/<_test.sql index 1d51f6a..6da744c 100644 --- a/src/operators/<_test.sql +++ b/src/operators/<_test.sql @@ -3,6 +3,97 @@ SELECT create_table_with_encrypted(); SELECT seed_encrypted_json(); + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- eql_v1_encrypted < eql_v1_encrypted with ore_cllw_u64_8 index +-- +-- Test data is in form '{"hello": "{one | two | three}", "n": {10 | 20 | 30} }' +-- +-- Paths +-- $ -> bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- +-- -- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- This extracts the data associated with the field from the test eql_v1_encrypted + -- json n: 30 + sv := get_numeric_ste_vec_30()::eql_v1_encrypted; + -- extract the term at $.n returned as eql_v1_encrypted + term := sv->'2517068c0d1f9d4d41d2c666211f785e'; + + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted < eql_v1_encrypted with ore_cllw_u64_8 index term'), + format('SELECT e FROM encrypted WHERE e->''2517068c0d1f9d4d41d2c666211f785e'' < %L::eql_v1_encrypted', term)); + + PERFORM assert_count( + format('eql_v1_encrypted < eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE e->''2517068c0d1f9d4d41d2c666211f785e'' < %L::eql_v1_encrypted', term), + 2); + + -- -- Check the $.hello path + -- -- Returned encrypted does not have ore_cllw_u64_8 + PERFORM assert_no_result( + format('eql_v1_encrypted < eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE e->''a7cea93975ed8c01f861ccb6bd082784'' < %L::eql_v1_encrypted', term)); + + END; +$$ LANGUAGE plpgsql; + + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- eql_v1_encrypted < eql_v1_encrypted with ore_cllw_var_8 index +-- +-- Test data is in form '{"hello": "{one | two | three}", "n": {10 | 20 | 30} }' +-- +-- Paths +-- $ -> bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- +-- -- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- This extracts the data associated with the field from the test eql_v1_encrypted + sv := get_numeric_ste_vec_30()::eql_v1_encrypted; + -- extract the term at $.n returned as eql_v1_encrypted + term := sv->'a7cea93975ed8c01f861ccb6bd082784'; + + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted < eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE e->''a7cea93975ed8c01f861ccb6bd082784'' < %L::eql_v1_encrypted', term)); + + PERFORM assert_count( + format('eql_v1_encrypted < eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE e->''a7cea93975ed8c01f861ccb6bd082784'' < %L::eql_v1_encrypted', term), + 1); + + -- -- Check the $.n path + -- -- Returned encrypted does not have ore_cllw_var_8 + PERFORM assert_no_result( + format('eql_v1_encrypted < eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE e->''2517068c0d1f9d4d41d2c666211f785e'' < %L::eql_v1_encrypted', term)); + + END; +$$ LANGUAGE plpgsql; + + + -- -- ORE - eql_v1_encrypted < eql_v1_encrypted -- @@ -11,7 +102,6 @@ DECLARE e eql_v1_encrypted; ore_term eql_v1_encrypted; BEGIN - SELECT ore.e FROM ore WHERE id = 42 INTO ore_term; PERFORM assert_count( @@ -19,19 +109,15 @@ DECLARE format('SELECT id FROM ore WHERE e < %L ORDER BY e DESC', ore_term), 41); - for i in 1..3 loop - e := create_encrypted_json(i); + -- Record with a Numeric ORE term of 1 + e := create_encrypted_ore_json(1); - PERFORM assert_no_result( - format('eql_v1_encrypted < eql_v1_encrypted %s of 3', i), - format('SELECT e FROM encrypted WHERE e < %L;', e)); - end loop; + PERFORM assert_no_result( + format('eql_v1_encrypted < eql_v1_encrypted'), + format('SELECT e FROM encrypted WHERE e < %L;', e)); - -- "HIGH" ORE - ore_term := '{"o": ["1212121212125932e28282d03415e7714fccd69eb7eb476c70743e485e20331f59cbc1c848dcdeda716f351eb20588c406a7df5fb8917ebf816739aa1414ac3b8498e493bf0badea5c9fdb3cc34da8b152b995957591880c523beb1d3f12487c38d18f62dd26209a727674e5a5fe3a3e3037860839afd8011f94b49eaa5fa5a60e1e2adccde4185a7d6c7f83088500b677f897d4ffc276016d614708488f407c01bd3ccf2be653269062cb97f8945a621d049277d19b1c248611f25d047038928d2efeb4323c402af4c19288c7b36911dc06639af5bb34367519b66c1f525bbd3828c12067c9c579aeeb4fb3ae0918125dc1dad5fd518019a5ae67894ce1a7f7bed1a591ba8edda2fdf4cd403761fd981fb1ea5eb0bf806f919350ee60cac16d0a39a491a4d79301781f95ea3870aea82e9946053537360b2fb415b18b61aed0af81d461ad6b923f10c0df79daddc4e279ff543a282bb3a37f9fa03238348b3dac51a453b04bced1f5bd318ddd829bdfe5f37abdbeda730e21441b818302f3c5c2c4d5657accfca4c53d7a80eb3db43946d38965be5f796b"]}'::jsonb; - - -- add the ore term - e := (create_encrypted_json()::jsonb || ore_term::jsonb)::eql_v1_encrypted; + -- Record with a Numeric ORE term of 42 + e := create_encrypted_ore_json(42); PERFORM assert_result( 'eql_v1_encrypted < eql_v1_encrypted', @@ -53,11 +139,8 @@ DECLARE e eql_v1_encrypted; ore_term jsonb; BEGIN - -- "HIGH" ORE - ore_term := '{"o": ["1212121212125932e28282d03415e7714fccd69eb7eb476c70743e485e20331f59cbc1c848dcdeda716f351eb20588c406a7df5fb8917ebf816739aa1414ac3b8498e493bf0badea5c9fdb3cc34da8b152b995957591880c523beb1d3f12487c38d18f62dd26209a727674e5a5fe3a3e3037860839afd8011f94b49eaa5fa5a60e1e2adccde4185a7d6c7f83088500b677f897d4ffc276016d614708488f407c01bd3ccf2be653269062cb97f8945a621d049277d19b1c248611f25d047038928d2efeb4323c402af4c19288c7b36911dc06639af5bb34367519b66c1f525bbd3828c12067c9c579aeeb4fb3ae0918125dc1dad5fd518019a5ae67894ce1a7f7bed1a591ba8edda2fdf4cd403761fd981fb1ea5eb0bf806f919350ee60cac16d0a39a491a4d79301781f95ea3870aea82e9946053537360b2fb415b18b61aed0af81d461ad6b923f10c0df79daddc4e279ff543a282bb3a37f9fa03238348b3dac51a453b04bced1f5bd318ddd829bdfe5f37abdbeda730e21441b818302f3c5c2c4d5657accfca4c53d7a80eb3db43946d38965be5f796b"]}'::jsonb; - - -- add the ore term - e := (create_encrypted_json()::jsonb || ore_term)::eql_v1_encrypted; + -- Record with a Numeric ORE term of 42 + e := create_encrypted_ore_json(42); PERFORM assert_result( 'eql_v1.lt(a eql_v1_encrypted, b eql_v1_encrypted)', diff --git a/src/operators/=.sql b/src/operators/=.sql index 5337275..8ef9f27 100644 --- a/src/operators/=.sql +++ b/src/operators/=.sql @@ -3,7 +3,12 @@ -- REQUIRE: src/unique/functions.sql -- REQUIRE: src/ore/types.sql -- REQUIRE: src/ore/functions.sql - +-- REQUIRE: src/ore/operators.sql +-- REQUIRE: src/blake3/types.sql +-- REQUIRE: src/blake3/functions.sql +-- REQUIRE: src/ore_cllw_u64_8/types.sql +-- REQUIRE: src/ore_cllw_u64_8/functions.sql +-- REQUIRE: src/ore_cllw_u64_8/operators.sql -- Operators for equality comparisons of eql_v1_encrypted types -- @@ -16,43 +21,57 @@ -- There are multiple index terms that provide equality comparisons -- - unique -- - ore_64_8_v1 --- - ore_cllw_8_v1 +-- -- -- We check these index terms in this order and use the first one that exists for both parameters -- -- -DROP FUNCTION IF EXISTS eql_v1.eq(a eql_v1_encrypted, b eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1.eq(a eql_v1_encrypted, b eql_v1_encrypted); CREATE FUNCTION eql_v1.eq(a eql_v1_encrypted, b eql_v1_encrypted) RETURNS boolean IMMUTABLE STRICT PARALLEL SAFE AS $$ - DECLARE - u boolean; - o boolean; BEGIN + BEGIN - u := (SELECT eql_v1.unique(a) = eql_v1.unique(b)); + RETURN eql_v1.unique(a) = eql_v1.unique(b); + EXCEPTION WHEN OTHERS THEN + -- PERFORM eql_v1.log('No unique index'); + END; + BEGIN + RETURN eql_v1.blake3(a) = eql_v1.blake3(b); EXCEPTION WHEN OTHERS THEN + -- PERFORM eql_v1.log('No blake3 index'); + END; - u := false; + BEGIN + RETURN eql_v1.ore_cllw_u64_8(a) = eql_v1.ore_cllw_u64_8(b); + EXCEPTION WHEN OTHERS THEN + -- PERFORM eql_v1.log('No ore_cllw_u64_8 index'); + END; + + BEGIN + RETURN eql_v1.ore_cllw_var_8(a) = eql_v1.ore_cllw_var_8(b); + EXCEPTION WHEN OTHERS THEN + -- PERFORM eql_v1.log('No ore_cllw_u64_8 index'); END; BEGIN - o := (SELECT eql_v1.ore_64_8_v1(a) = eql_v1.ore_64_8_v1(b)); + RETURN eql_v1.ore_64_8_v1(a) = eql_v1.ore_64_8_v1(b); EXCEPTION WHEN OTHERS THEN - o := false; + -- PERFORM eql_v1.log('No ore_64_8_v1 index'); END; - RETURN u OR o; + RETURN false; END; $$ LANGUAGE plpgsql; -DROP OPERATOR IF EXISTS = (eql_v1_encrypted, eql_v1_encrypted); -DROP FUNCTION IF EXISTS eql_v1."="(a eql_v1_encrypted, b eql_v1_encrypted); +-- DROP OPERATOR IF EXISTS = (eql_v1_encrypted, eql_v1_encrypted) CASCADE; +-- DROP FUNCTION IF EXISTS eql_v1."="(a eql_v1_encrypted, b eql_v1_encrypted); CREATE FUNCTION eql_v1."="(a eql_v1_encrypted, b eql_v1_encrypted) RETURNS boolean @@ -74,8 +93,8 @@ CREATE OPERATOR = ( MERGES ); -DROP OPERATOR IF EXISTS = (eql_v1_encrypted, jsonb); -DROP FUNCTION IF EXISTS eql_v1."="(a eql_v1_encrypted, b jsonb); +-- DROP OPERATOR IF EXISTS = (eql_v1_encrypted, jsonb); +-- DROP FUNCTION IF EXISTS eql_v1."="(a eql_v1_encrypted, b jsonb); CREATE FUNCTION eql_v1."="(a eql_v1_encrypted, b jsonb) RETURNS boolean @@ -97,8 +116,8 @@ CREATE OPERATOR = ( MERGES ); -DROP OPERATOR IF EXISTS = (jsonb, eql_v1_encrypted); -DROP FUNCTION IF EXISTS eql_v1."="(a jsonb, b eql_v1_encrypted); +-- DROP OPERATOR IF EXISTS = (jsonb, eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1."="(a jsonb, b eql_v1_encrypted); CREATE FUNCTION eql_v1."="(a jsonb, b eql_v1_encrypted) RETURNS boolean diff --git a/src/operators/=_ore_cllw_u64_8_test.sql b/src/operators/=_ore_cllw_u64_8_test.sql new file mode 100644 index 0000000..7f52203 --- /dev/null +++ b/src/operators/=_ore_cllw_u64_8_test.sql @@ -0,0 +1,56 @@ +\set ON_ERROR_STOP on + +SELECT create_table_with_encrypted(); +SELECT seed_encrypted_json(); + + +-- ======================================================================== + + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- ore_cllw_u64_8 equality +-- +-- Test data is in form '{"hello": "{one | two | three}", "n": {10 | 20 | 30} }' +-- +-- Paths +-- $ -> bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- +-- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- This extracts the data associated with the field from the test eql_v1_encrypted + -- json n: 10 + sv := get_numeric_ste_vec_10()::eql_v1_encrypted; + -- extract the term at $.n returned as eql_v1_encrypted + term := sv->'2517068c0d1f9d4d41d2c666211f785e'; + + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted = eql_v1_encrypted with ore_cllw_u64_8 index term'), + format('SELECT e FROM encrypted WHERE (e->''2517068c0d1f9d4d41d2c666211f785e'') = %L::eql_v1_encrypted', term)); + + PERFORM assert_count( + format('eql_v1_encrypted = eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE e->''2517068c0d1f9d4d41d2c666211f785e'' = %L::eql_v1_encrypted', term), + 1); + + -- -- Check the $.hello path + -- -- Returned encrypted does not have ore_cllw_u64_8 + PERFORM assert_no_result( + format('eql_v1_encrypted = eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE e->''a7cea93975ed8c01f861ccb6bd082784'' = %L::eql_v1_encrypted', term)); + + END; +$$ LANGUAGE plpgsql; + + + +SELECT drop_table_with_encrypted(); \ No newline at end of file diff --git a/src/operators/=_ore_cllw_var_8_test.sql b/src/operators/=_ore_cllw_var_8_test.sql new file mode 100644 index 0000000..8ef38ab --- /dev/null +++ b/src/operators/=_ore_cllw_var_8_test.sql @@ -0,0 +1,53 @@ +\set ON_ERROR_STOP on + +SELECT create_table_with_encrypted(); +SELECT seed_encrypted_json(); + + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- ore_cllw_u64_8 equality +-- +-- Test data is in form '{"hello": "{one | two | three}", "n": {10 | 20 | 30} }' +-- +-- Paths +-- $ -> bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- +-- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- This extracts the data associated with the field from the test eql_v1_encrypted + -- json n: 10 + sv := get_numeric_ste_vec_10()::eql_v1_encrypted; + -- extract the term at $.n returned as eql_v1_encrypted + term := sv->'a7cea93975ed8c01f861ccb6bd082784'; + + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted = eql_v1_encrypted with ore_cllw_u64_8 index term'), + format('SELECT e FROM encrypted WHERE (e->''a7cea93975ed8c01f861ccb6bd082784'') = %L::eql_v1_encrypted', term)); + + PERFORM assert_count( + format('eql_v1_encrypted = eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE e->''a7cea93975ed8c01f861ccb6bd082784'' = %L::eql_v1_encrypted', term), + 1); + + -- -- Check the $.n path + -- -- Returned encrypted does not have ore_cllw_u64_8 + PERFORM assert_no_result( + format('eql_v1_encrypted = eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE e->''2517068c0d1f9d4d41d2c666211f785e'' = %L::eql_v1_encrypted', term)); + + END; +$$ LANGUAGE plpgsql; + + + +SELECT drop_table_with_encrypted(); \ No newline at end of file diff --git a/src/operators/=_ore_test.sql b/src/operators/=_ore_test.sql new file mode 100644 index 0000000..1b06c6b --- /dev/null +++ b/src/operators/=_ore_test.sql @@ -0,0 +1,87 @@ +\set ON_ERROR_STOP on + +SELECT create_table_with_encrypted(); +SELECT seed_encrypted_json(); + + +-- +-- ORE - eql_v1_encrypted = eql_v1_encrypted +-- +DO $$ +DECLARE + e eql_v1_encrypted; + ore_term jsonb; + BEGIN + + -- Record with a Numeric ORE term of 42 + e := create_encrypted_ore_json(42); + PERFORM seed_encrypted(e); + + PERFORM assert_result( + 'eql_v1_encrypted = eql_v1_encrypted', + format('SELECT e FROM encrypted WHERE e = %L::eql_v1_encrypted', e)); + + PERFORM assert_count( + format('eql_v1_encrypted = eql_v1_encrypted'), + format('SELECT e FROM encrypted WHERE e = %L;', e), + 1); + + e := create_encrypted_ore_json(20); + + PERFORM assert_result( + 'eql_v1_encrypted = eql_v1_encrypted', + format('SELECT e FROM encrypted WHERE e = %L::eql_v1_encrypted', e)); + + PERFORM assert_count( + format('eql_v1_encrypted = eql_v1_encrypted'), + format('SELECT e FROM encrypted WHERE e = %L;', e), + 1); + END; +$$ LANGUAGE plpgsql; + + +-- +-- ORE - eql_v1.gte(a eql_v1_encrypted, b eql_v1_encrypted) +-- +DO $$ +DECLARE + e eql_v1_encrypted; + ore_term jsonb; + BEGIN + -- Reset data + PERFORM seed_encrypted_json(); + + -- Record with a Numeric ORE term of 20 + e := create_encrypted_ore_json(20); + PERFORM seed_encrypted(e); + + PERFORM assert_result( + 'eql_v1.eq(a eql_v1_encrypted, b eql_v1_encrypted)', + format('SELECT e FROM encrypted WHERE eql_v1.eq(e, %L)', e)); + + -- include + PERFORM assert_count( + 'eql_v1.eq(a eql_v1_encrypted, b eql_v1_encrypted)', + format('SELECT e FROM encrypted WHERE eql_v1.eq(e, %L)', e), + 2); + + -- Record with a Numeric ORE term of 30 + e := create_encrypted_ore_json(30); + + PERFORM assert_result( + 'eql_v1.get(a eql_v1_encrypted, b eql_v1_encrypted)', + format('SELECT e FROM encrypted WHERE eql_v1.eq(e, %L)', e)); + + PERFORM assert_count( + 'eql_v1.get(a eql_v1_encrypted, b eql_v1_encrypted)', + format('SELECT e FROM encrypted WHERE eql_v1.eq(e, %L)', e), + 1); + END; +$$ LANGUAGE plpgsql; + + + +-- ======================================================================== + + +SELECT drop_table_with_encrypted(); \ No newline at end of file diff --git a/src/operators/=_test.sql b/src/operators/=_test.sql index 7251253..d50267c 100644 --- a/src/operators/=_test.sql +++ b/src/operators/=_test.sql @@ -3,7 +3,6 @@ SELECT create_table_with_encrypted(); SELECT seed_encrypted_json(); -SELECT e FROM encrypted WHERE e = '("{""c"": ""ciphertext"", ""i"": {""c"": ""e"", ""t"": ""encrypted""}, ""j"": [{""c"": ""ciphertext.1"", ""s"": ""selector.1"", ""t"": ""term.1""}], ""m"": [10, 11, 12, 13, 14, 15], ""u"": ""unique.1"", ""75d1219a941e4853572b60f51"": ""902cd835193393f41315d2e00""}")'; -- -- Unique equality - eql_v1_encrypted = eql_v1_encrypted @@ -14,7 +13,7 @@ DECLARE BEGIN for i in 1..3 loop - e := create_encrypted_json(i)::jsonb-'o'; + e := create_encrypted_json(i, 'u'); PERFORM assert_result( format('eql_v1_encrypted = eql_v1_encrypted with unique index term %s of 3', i), @@ -23,7 +22,7 @@ DECLARE end loop; -- remove the ore index term - e := create_encrypted_json(91347)::jsonb-'o'; + e := create_encrypted_json(91347, 'u'); PERFORM assert_no_result( 'eql_v1_encrypted = eql_v1_encrypted with no matching record', @@ -95,136 +94,103 @@ DECLARE $$ LANGUAGE plpgsql; + +-- ======================================================================== + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ -- --- Example ORE values are generated from an array in the form `vec![0, 1, 2, 3, 4, 5]`; --- --- JSON values are JSON escaped on top of a PostgreSQL escaped Record --- --- PostgreSQL value is ("{""(\\""\\\\\\\\x000102030405\\"")""}") --- +-- Blake equality - eql_v1_encrypted = eql_v1_encrypted -- DO $$ DECLARE e eql_v1_encrypted; - ore_term jsonb; BEGIN - -- remove the unique index term - e := create_encrypted_json()::jsonb-'u'; + for i in 1..3 loop + e := create_encrypted_json(i, 'b'); PERFORM assert_result( - format('eql_v1_encrypted = eql_v1_encrypted with ore index term'), - format('SELECT e FROM encrypted WHERE e = %L', e)); - - PERFORM assert_count( - format('eql_v1_encrypted = eql_v1_encrypted with ore index term'), - format('SELECT e FROM encrypted WHERE e = %L', e), - 3); - + format('eql_v1_encrypted = eql_v1_encrypted with unique index term %s of 3', i), + format('SELECT e FROM encrypted WHERE e = %L;', e)); - -- -- not the same ore term - ore_term := '{"o": ["1212121212125932e28282d03415e7714fccd69eb7eb476c70743e485e20331f59cbc1c848dcdeda716f351eb20588c406a7df5fb8917ebf816739aa1414ac3b8498e493bf0badea5c9fdb3cc34da8b152b995957591880c523beb1d3f12487c38d18f62dd26209a727674e5a5fe3a3e3037860839afd8011f94b49eaa5fa5a60e1e2adccde4185a7d6c7f83088500b677f897d4ffc276016d614708488f407c01bd3ccf2be653269062cb97f8945a621d049277d19b1c248611f25d047038928d2efeb4323c402af4c19288c7b36911dc06639af5bb34367519b66c1f525bbd3828c12067c9c579aeeb4fb3ae0918125dc1dad5fd518019a5ae67894ce1a7f7bed1a591ba8edda2fdf4cd403761fd981fb1ea5eb0bf806f919350ee60cac16d0a39a491a4d79301781f95ea3870aea82e9946053537360b2fb415b18b61aed0af81d461ad6b923f10c0df79daddc4e279ff543a282bb3a37f9fa03238348b3dac51a453b04bced1f5bd318ddd829bdfe5f37abdbeda730e21441b818302f3c5c2c4d5657accfca4c53d7a80eb3db43946d38965be5f796b"]}'::jsonb; + end loop; - -- remove the unique index term and add the ore term - e := create_encrypted_json()::jsonb-'u' || ore_term; + -- remove the ore index term + e := create_encrypted_json(91347, 'b'); - PERFORM assert_no_result( - format('eql_v1_encrypted = eql_v1_encrypted with ore index term'), - format('SELECT e FROM encrypted WHERE e = %L', e)); + PERFORM assert_no_result( + 'eql_v1_encrypted = eql_v1_encrypted with no matching record', + format('SELECT e FROM encrypted WHERE e = %L;', e)); END; $$ LANGUAGE plpgsql; - --- --- ORE equality using the `eql_v1.ore_64_8_v1(eql_v1_encrypted)` function calls --- --- Example ORE values are generated from an array in the form `vec![0, 1, 2, 3, 4, 5]`; --- --- JSON values are JSON escaped on top of a PostgreSQL escaped Record --- --- PostgreSQL value is ("{""(\\""\\\\\\\\x000102030405\\"")""}") +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ -- +-- Blake3 equality - eql_v1.eq(eql_v1_encrypted, eql_v1_encrypted) -- DO $$ DECLARE e eql_v1_encrypted; - ore_term jsonb; BEGIN - -- remove the unique index term - e := create_encrypted_json()::jsonb-'u'; + for i in 1..3 loop + e := create_encrypted_json(i, 'b'); PERFORM assert_result( - format('eql_v1.ore_64_8_v1(eql_v1_encrypted) = eql_v1.ore_64_8_v1(eql_v1_encrypted)'), - format('SELECT e FROM encrypted WHERE eql_v1.ore_64_8_v1(e) = eql_v1.ore_64_8_v1(%L::eql_v1_encrypted)', e)); - - -- all seed values have the same ore term - PERFORM assert_count( - format('eql_v1.ore_64_8_v1(eql_v1_encrypted) = eql_v1.ore_64_8_v1(eql_v1_encrypted)'), - format('SELECT e FROM encrypted WHERE eql_v1.ore_64_8_v1(e) = eql_v1.ore_64_8_v1(%L::eql_v1_encrypted)', e), - 3); - - - -- new ore term - ore_term := '{"o": ["1212121212125932e28282d03415e7714fccd69eb7eb476c70743e485e20331f59cbc1c848dcdeda716f351eb20588c406a7df5fb8917ebf816739aa1414ac3b8498e493bf0badea5c9fdb3cc34da8b152b995957591880c523beb1d3f12487c38d18f62dd26209a727674e5a5fe3a3e3037860839afd8011f94b49eaa5fa5a60e1e2adccde4185a7d6c7f83088500b677f897d4ffc276016d614708488f407c01bd3ccf2be653269062cb97f8945a621d049277d19b1c248611f25d047038928d2efeb4323c402af4c19288c7b36911dc06639af5bb34367519b66c1f525bbd3828c12067c9c579aeeb4fb3ae0918125dc1dad5fd518019a5ae67894ce1a7f7bed1a591ba8edda2fdf4cd403761fd981fb1ea5eb0bf806f919350ee60cac16d0a39a491a4d79301781f95ea3870aea82e9946053537360b2fb415b18b61aed0af81d461ad6b923f10c0df79daddc4e279ff543a282bb3a37f9fa03238348b3dac51a453b04bced1f5bd318ddd829bdfe5f37abdbeda730e21441b818302f3c5c2c4d5657accfca4c53d7a80eb3db43946d38965be5f796b"]}'::jsonb; + format('eql_v1.eq(eql_v1_encrypted, eql_v1_encrypted) with unique index term %s of 3', i), + format('SELECT e FROM encrypted WHERE eql_v1.eq(e, %L);', e)); + end loop; - -- remove the unique index term and add the ore term - e := create_encrypted_json()::jsonb-'u' || ore_term; - -- -- PERFORM eql_v1.log('e', e::text); + -- remove the ore index term + e := create_encrypted_json(91347, 'b'); - PERFORM assert_no_result( - format('eql_v1.ore_64_8_v1(eql_v1_encrypted) = eql_v1.ore_64_8_v1(eql_v1_encrypted)'), - format('SELECT e FROM encrypted WHERE eql_v1.ore_64_8_v1(e) = eql_v1.ore_64_8_v1(%L::eql_v1_encrypted)', e)); + PERFORM assert_no_result( + 'eql_v1.eq(eql_v1_encrypted, eql_v1_encrypted) with no matching record', + format('SELECT e FROM encrypted WHERE eql_v1.eq(e, %L);', e)); END; $$ LANGUAGE plpgsql; - --- --- ORE equality using the `eql_v1.eq(eql_v1_encrypted, eql_v1_encrypted)' --- --- Example ORE values are generated from an array in the form `vec![0, 1, 2, 3, 4, 5]`; --- --- JSON values are JSON escaped on top of a PostgreSQL escaped Record --- --- PostgreSQL value is ("{""(\\""\\\\\\\\x000102030405\\"")""}") +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ -- +-- Blake3 equality - eql_v1_encrypted = jsonb -- DO $$ DECLARE - e eql_v1_encrypted; - ore_term jsonb; + e jsonb; BEGIN + for i in 1..3 loop - -- remove the unique index term - e := create_encrypted_json()::jsonb-'u'; + -- remove the default + e := create_encrypted_json(i, 'b'); PERFORM assert_result( - 'eql_v1.eq(eql_v1_encrypted, eql_v1_encrypted)', - format('SELECT e FROM encrypted WHERE eql_v1.eq(e, %L);', e)); + format('eql_v1_encrypted = jsonb with unique index term %s of 3', i), + format('SELECT e FROM encrypted WHERE e = %L::jsonb;', e)); - -- all seed values have the same ore term - PERFORM assert_count( - 'eql_v1.eq(eql_v1_encrypted, eql_v1_encrypted)', - format('SELECT e FROM encrypted WHERE eql_v1.eq(e, %L);', e), - 3); + PERFORM assert_result( + format('jsonb = eql_v1_encrypted with unique index term %s of 3', i), + format('SELECT e FROM encrypted WHERE %L::jsonb = e', e)); + end loop; + e := create_encrypted_json(91347, 'b'); - -- new ore term - ore_term := '{"o": ["1212121212125932e28282d03415e7714fccd69eb7eb476c70743e485e20331f59cbc1c848dcdeda716f351eb20588c406a7df5fb8917ebf816739aa1414ac3b8498e493bf0badea5c9fdb3cc34da8b152b995957591880c523beb1d3f12487c38d18f62dd26209a727674e5a5fe3a3e3037860839afd8011f94b49eaa5fa5a60e1e2adccde4185a7d6c7f83088500b677f897d4ffc276016d614708488f407c01bd3ccf2be653269062cb97f8945a621d049277d19b1c248611f25d047038928d2efeb4323c402af4c19288c7b36911dc06639af5bb34367519b66c1f525bbd3828c12067c9c579aeeb4fb3ae0918125dc1dad5fd518019a5ae67894ce1a7f7bed1a591ba8edda2fdf4cd403761fd981fb1ea5eb0bf806f919350ee60cac16d0a39a491a4d79301781f95ea3870aea82e9946053537360b2fb415b18b61aed0af81d461ad6b923f10c0df79daddc4e279ff543a282bb3a37f9fa03238348b3dac51a453b04bced1f5bd318ddd829bdfe5f37abdbeda730e21441b818302f3c5c2c4d5657accfca4c53d7a80eb3db43946d38965be5f796b"]}'::jsonb; + PERFORM assert_no_result( + 'eql_v1_encrypted = jsonb with no matching record', + format('SELECT e FROM encrypted WHERE e = %L::jsonb', e)); - -- remove the unique index term and add the ore term - e := create_encrypted_json()::jsonb-'u' || ore_term; + PERFORM assert_no_result( + 'jsonb = eql_v1_encrypted with no matching record', + format('SELECT e FROM encrypted WHERE %L::jsonb = e', e)); - PERFORM assert_no_result( - 'eql_v1.eq(eql_v1_encrypted, eql_v1_encrypted)', - format('SELECT e FROM encrypted WHERE eql_v1.eq(e, %L);', e)); END; $$ LANGUAGE plpgsql; - SELECT drop_table_with_encrypted(); \ No newline at end of file diff --git a/src/operators/>.sql b/src/operators/>.sql index 86d4600..279e3ec 100644 --- a/src/operators/>.sql +++ b/src/operators/>.sql @@ -20,19 +20,43 @@ -- -- -DROP FUNCTION IF EXISTS eql_v1.gt(a eql_v1_encrypted, b eql_v1_encrypted); +-- WHERE a > b +-- WHERE eql_v1.gt(a, b) +-- + + +-- DROP FUNCTION IF EXISTS eql_v1.gt(a eql_v1_encrypted, b eql_v1_encrypted); CREATE FUNCTION eql_v1.gt(a eql_v1_encrypted, b eql_v1_encrypted) RETURNS boolean AS $$ BEGIN - RETURN eql_v1.ore_64_8_v1(a) > eql_v1.ore_64_8_v1(b); + + BEGIN + RETURN eql_v1.ore_cllw_u64_8(a) > eql_v1.ore_cllw_u64_8(b); + EXCEPTION WHEN OTHERS THEN + -- PERFORM eql_v1.log('eql_v1.gt no ore_cllw_u64_8 index'); + END; + + BEGIN + RETURN eql_v1.ore_cllw_var_8(a) > eql_v1.ore_cllw_var_8(b); + EXCEPTION WHEN OTHERS THEN + -- PERFORM eql_v1.log('eql_v1.gt no ore_cllw_var_8 index'); + END; + + BEGIN + RETURN eql_v1.ore_64_8_v1(a) > eql_v1.ore_64_8_v1(b); + EXCEPTION WHEN OTHERS THEN + -- PERFORM eql_v1.log('eql_v1.gt no ore_64_8_v1 index'); + END; + + RETURN false; END; $$ LANGUAGE plpgsql; -DROP OPERATOR IF EXISTS > (eql_v1_encrypted, eql_v1_encrypted); -DROP FUNCTION IF EXISTS eql_v1.">"(a eql_v1_encrypted, b eql_v1_encrypted); +-- DROP OPERATOR IF EXISTS > (eql_v1_encrypted, eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1.">"(a eql_v1_encrypted, b eql_v1_encrypted); CREATE FUNCTION eql_v1.">"(a eql_v1_encrypted, b eql_v1_encrypted) RETURNS boolean @@ -53,8 +77,8 @@ CREATE OPERATOR >( ); -DROP OPERATOR IF EXISTS > (eql_v1_encrypted, jsonb); -DROP FUNCTION IF EXISTS eql_v1.">"(a eql_v1_encrypted, b jsonb); +-- DROP OPERATOR IF EXISTS > (eql_v1_encrypted, jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.">"(a eql_v1_encrypted, b jsonb); CREATE FUNCTION eql_v1.">"(a eql_v1_encrypted, b jsonb) RETURNS boolean @@ -75,8 +99,8 @@ CREATE OPERATOR >( ); -DROP OPERATOR IF EXISTS > (jsonb, eql_v1_encrypted); -DROP FUNCTION IF EXISTS eql_v1.">"(a jsonb, b eql_v1_encrypted); +-- DROP OPERATOR IF EXISTS > (jsonb, eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1.">"(a jsonb, b eql_v1_encrypted); CREATE FUNCTION eql_v1.">"(a jsonb, b eql_v1_encrypted) RETURNS boolean diff --git a/src/operators/>=.sql b/src/operators/>=.sql index 20e6d89..100d969 100644 --- a/src/operators/>=.sql +++ b/src/operators/>=.sql @@ -20,19 +20,38 @@ -- -- -DROP FUNCTION IF EXISTS eql_v1.gte(a eql_v1_encrypted, b eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1.gte(a eql_v1_encrypted, b eql_v1_encrypted); CREATE FUNCTION eql_v1.gte(a eql_v1_encrypted, b eql_v1_encrypted) RETURNS boolean AS $$ BEGIN - RETURN eql_v1.ore_64_8_v1(a) >= eql_v1.ore_64_8_v1(b); + + BEGIN + RETURN eql_v1.ore_cllw_u64_8(a) >= eql_v1.ore_cllw_u64_8(b); + EXCEPTION WHEN OTHERS THEN + -- PERFORM eql_v1.log('eql_v1.gte no ore_cllw_u64_8 index'); + END; + + BEGIN + RETURN eql_v1.ore_cllw_var_8(a) >= eql_v1.ore_cllw_var_8(b); + EXCEPTION WHEN OTHERS THEN + -- PERFORM eql_v1.log('eql_v1.gte no ore_cllw_var_8 index'); + END; + + BEGIN + RETURN eql_v1.ore_64_8_v1(a) >= eql_v1.ore_64_8_v1(b); + EXCEPTION WHEN OTHERS THEN + -- PERFORM eql_v1.log('eql_v1.gte no ore_64_8_v1 index'); + END; + + RETURN false; END; $$ LANGUAGE plpgsql; -DROP OPERATOR IF EXISTS >= (eql_v1_encrypted, eql_v1_encrypted); -DROP FUNCTION IF EXISTS eql_v1.">="(a eql_v1_encrypted, b eql_v1_encrypted); +-- DROP OPERATOR IF EXISTS >= (eql_v1_encrypted, eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1.">="(a eql_v1_encrypted, b eql_v1_encrypted); CREATE FUNCTION eql_v1.">="(a eql_v1_encrypted, b eql_v1_encrypted) RETURNS boolean @@ -54,8 +73,8 @@ CREATE OPERATOR >=( ); -DROP OPERATOR IF EXISTS >= (eql_v1_encrypted, jsonb); -DROP FUNCTION IF EXISTS eql_v1.">="(a eql_v1_encrypted, b jsonb); +-- DROP OPERATOR IF EXISTS >= (eql_v1_encrypted, jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.">="(a eql_v1_encrypted, b jsonb); CREATE FUNCTION eql_v1.">="(a eql_v1_encrypted, b jsonb) RETURNS boolean @@ -76,8 +95,8 @@ CREATE OPERATOR >=( ); -DROP OPERATOR IF EXISTS >= (jsonb, eql_v1_encrypted); -DROP FUNCTION IF EXISTS eql_v1.">="(a jsonb, b eql_v1_encrypted); +-- DROP OPERATOR IF EXISTS >= (jsonb, eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1.">="(a jsonb, b eql_v1_encrypted); CREATE FUNCTION eql_v1.">="(a jsonb, b eql_v1_encrypted) RETURNS boolean diff --git a/src/operators/>=_test.sql b/src/operators/>=_test.sql index 01c060b..8e7635a 100644 --- a/src/operators/>=_test.sql +++ b/src/operators/>=_test.sql @@ -3,6 +3,97 @@ SELECT create_table_with_encrypted(); SELECT seed_encrypted_json(); + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- eql_v1_encrypted < eql_v1_encrypted with ore_cllw_var_8 index +-- +-- Test data is in form '{"hello": "{one | two | three}", "n": {10 | 20 | 30} }' +-- +-- Paths +-- $ -> bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- +-- -- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- This extracts the data associated with the field from the test eql_v1_encrypted + -- json n: 30 + sv := get_numeric_ste_vec_10()::eql_v1_encrypted; + -- extract the term at $.n returned as eql_v1_encrypted + term := sv->'2517068c0d1f9d4d41d2c666211f785e'; + + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted >= eql_v1_encrypted with ore_cllw_u64_8 index term'), + format('SELECT e FROM encrypted WHERE e->''2517068c0d1f9d4d41d2c666211f785e'' >= %L::eql_v1_encrypted', term)); + + PERFORM assert_count( + format('eql_v1_encrypted >= eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE e->''2517068c0d1f9d4d41d2c666211f785e'' >= %L::eql_v1_encrypted', term), + 3); + + -- -- Check the $.hello path + -- -- Returned encrypted does not have ore_cllw_u64_8 + PERFORM assert_no_result( + format('eql_v1_encrypted >= eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE e->''a7cea93975ed8c01f861ccb6bd082784'' >= %L::eql_v1_encrypted', term)); + + END; +$$ LANGUAGE plpgsql; + + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- eql_v1_encrypted < eql_v1_encrypted with ore_cllw_var_8 index +-- +-- Test data is in form '{"hello": "{one | two | three}", "n": {10 | 20 | 30} }' +-- +-- Paths +-- $ -> bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- +-- -- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- This extracts the data associated with the field from the test eql_v1_encrypted + -- json n: 30 + sv := get_numeric_ste_vec_10()::eql_v1_encrypted; + -- extract the term at $.n returned as eql_v1_encrypted + term := sv->'a7cea93975ed8c01f861ccb6bd082784'; + + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted >= eql_v1_encrypted with ore_cllw_u64_8 index term'), + format('SELECT e FROM encrypted WHERE e->''a7cea93975ed8c01f861ccb6bd082784'' >= %L::eql_v1_encrypted', term)); + + PERFORM assert_count( + format('eql_v1_encrypted >= eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE e->''a7cea93975ed8c01f861ccb6bd082784'' >= %L::eql_v1_encrypted', term), + 3); + + -- -- Check the $.hello path + -- -- Returned encrypted does not have ore_cllw_u64_8 + PERFORM assert_no_result( + format('eql_v1_encrypted >= eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE e->''2517068c0d1f9d4d41d2c666211f785e'' >= %L::eql_v1_encrypted', term)); + + END; +$$ LANGUAGE plpgsql; + + -- -- ORE - eql_v1_encrypted >= eql_v1_encrypted -- @@ -12,32 +103,29 @@ DECLARE ore_term jsonb; BEGIN - -- Create a record with HIGH ore - e := create_encrypted_json()::jsonb || get_high_ore(); + -- Record with a Numeric ORE term of 12 + e := create_encrypted_ore_json(12); PERFORM seed_encrypted(e); - -- Default has LOW ore - e := create_encrypted_json(); - PERFORM assert_result( 'eql_v1_encrypted >= eql_v1_encrypted', - format('SELECT e FROM encrypted WHERE e >= %L::eql_v1_encrypted', e)); + format('SELECT e FROM encrypted WHERE e >= %L', e)); - - for i in 1..3 loop - e := create_encrypted_json(i); - - PERFORM assert_result( - format('eql_v1_encrypted >= eql_v1_encrypted %s of 3', i), - format('SELECT e FROM encrypted WHERE e >= %L;', e)); - - PERFORM assert_count( - format('eql_v1_encrypted >= eql_v1_encrypted %s of 3', i), + PERFORM assert_count( + format('eql_v1_encrypted >= eql_v1_encrypted'), format('SELECT e FROM encrypted WHERE e >= %L;', e), - 4); - end loop; + 3); + e := create_encrypted_ore_json(20); + + PERFORM assert_result( + 'eql_v1_encrypted >= eql_v1_encrypted', + format('SELECT e FROM encrypted WHERE e >= %L::eql_v1_encrypted', e)); + PERFORM assert_count( + format('eql_v1_encrypted >= eql_v1_encrypted'), + format('SELECT e FROM encrypted WHERE e >= %L;', e), + 2); END; $$ LANGUAGE plpgsql; @@ -50,13 +138,25 @@ DECLARE e eql_v1_encrypted; ore_term jsonb; BEGIN + -- Reset data + PERFORM seed_encrypted_json(); - -- Create a record with HIGH ore - e := create_encrypted_json()::jsonb || get_high_ore(); + -- Record with a Numeric ORE term of 42 + e := create_encrypted_ore_json(42); PERFORM seed_encrypted(e); - -- Default has LOW ore - e := create_encrypted_json(); + PERFORM assert_result( + 'eql_v1.gte(a eql_v1_encrypted, b eql_v1_encrypted)', + format('SELECT e FROM encrypted WHERE eql_v1.gte(e, %L)', e)); + + -- include + PERFORM assert_count( + 'eql_v1.gte(a eql_v1_encrypted, b eql_v1_encrypted)', + format('SELECT e FROM encrypted WHERE eql_v1.gte(e, %L)', e), + 1); + + -- Record with a Numeric ORE term of 30 + e := create_encrypted_ore_json(30); PERFORM assert_result( 'eql_v1.get(a eql_v1_encrypted, b eql_v1_encrypted)', @@ -65,7 +165,7 @@ DECLARE PERFORM assert_count( 'eql_v1.get(a eql_v1_encrypted, b eql_v1_encrypted)', format('SELECT e FROM encrypted WHERE eql_v1.gte(e, %L)', e), - 5); + 2); END; $$ LANGUAGE plpgsql; diff --git a/src/operators/>_test.sql b/src/operators/>_test.sql index 89b6766..55f73f5 100644 --- a/src/operators/>_test.sql +++ b/src/operators/>_test.sql @@ -3,62 +3,153 @@ SELECT create_table_with_encrypted(); SELECT seed_encrypted_json(); + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ -- --- ORE - eql_v1_encrypted > eql_v1_encrypted +-- eql_v1_encrypted < eql_v1_encrypted with ore_cllw_u64_8 index +-- +-- Test data is in form '{"hello": "{one | two | three}", "n": {10 | 20 | 30} }' -- +-- Paths +-- $ -> bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- +-- -- DO $$ DECLARE - e eql_v1_encrypted; - ore_term jsonb; + sv eql_v1_encrypted; + term eql_v1_encrypted; BEGIN - -- Create a record with HIGH ore - e := create_encrypted_json()::jsonb || get_high_ore(); - PERFORM seed_encrypted(e); + -- This extracts the data associated with the field from the test eql_v1_encrypted + -- json n: 30 + sv := get_numeric_ste_vec_20()::eql_v1_encrypted; + -- extract the term at $.n returned as eql_v1_encrypted + term := sv->'2517068c0d1f9d4d41d2c666211f785e'; - -- Default has LOW ore - e := create_encrypted_json(); + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted > eql_v1_encrypted with ore_cllw_u64_8 index term'), + format('SELECT e FROM encrypted WHERE e->''2517068c0d1f9d4d41d2c666211f785e'' > %L::eql_v1_encrypted', term)); + + PERFORM assert_count( + format('eql_v1_encrypted > eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE e->''2517068c0d1f9d4d41d2c666211f785e'' > %L::eql_v1_encrypted', term), + 1); + + -- -- Check the $.hello path + -- -- Returned encrypted does not have ore_cllw_u64_8 + PERFORM assert_no_result( + format('eql_v1_encrypted > eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE e->''a7cea93975ed8c01f861ccb6bd082784'' > %L::eql_v1_encrypted', term)); + + END; +$$ LANGUAGE plpgsql; + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- eql_v1_encrypted < eql_v1_encrypted with ore_cllw_var_8 index +-- +-- Test data is in form '{"hello": "{one | two | three}", "n": {10 | 20 | 30} }' +-- +-- Paths +-- $ -> bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- +-- -- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- This extracts the data associated with the field from the test eql_v1_encrypted + -- json n: 30 + sv := get_numeric_ste_vec_30()::eql_v1_encrypted; + -- extract the term at $.n returned as eql_v1_encrypted + term := sv->'a7cea93975ed8c01f861ccb6bd082784'; + + -- -- -- -- $.n PERFORM assert_result( - 'eql_v1_encrypted > eql_v1_encrypted', - format('SELECT e FROM encrypted WHERE e > %L::eql_v1_encrypted', e)); + format('eql_v1_encrypted > eql_v1_encrypted with ore_cllw_u64_8 index term'), + format('SELECT e FROM encrypted WHERE e->''a7cea93975ed8c01f861ccb6bd082784'' > %L::eql_v1_encrypted', term)); - for i in 1..3 loop - e := create_encrypted_json(i); + PERFORM assert_count( + format('eql_v1_encrypted > eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE e->''a7cea93975ed8c01f861ccb6bd082784'' > %L::eql_v1_encrypted', term), + 1); - PERFORM assert_result( - format('eql_v1_encrypted > eql_v1_encrypted %s of 3', i), - format('SELECT e FROM encrypted WHERE e > %L;', e)); - end loop; + -- -- Check the $.hello path + -- -- Returned encrypted does not have ore_cllw_var_8 + PERFORM assert_no_result( + format('eql_v1_encrypted > eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE e->''2517068c0d1f9d4d41d2c666211f785e'' > %L::eql_v1_encrypted', term)); END; $$ LANGUAGE plpgsql; -- --- ORE - eql_v1.gt(a eql_v1_encrypted, b eql_v1_encrypted) +-- ORE - eql_v1_encrypted > eql_v1_encrypted -- DO $$ DECLARE e eql_v1_encrypted; - ore_term jsonb; + ore_term eql_v1_encrypted; BEGIN + SELECT ore.e FROM ore WHERE id = 42 INTO ore_term; + + PERFORM assert_count( + 'eql_v1_encrypted > eql_v1_encrypted', + format('SELECT id FROM ore WHERE e > %L ORDER BY e DESC', ore_term), + 57); + + -- Record with a Numeric ORE term of 1 + e := create_encrypted_ore_json(99); + + PERFORM assert_no_result( + format('eql_v1_encrypted > eql_v1_encrypted'), + format('SELECT e FROM encrypted WHERE e > %L;', e)); + + -- Record with a Numeric ORE term of 42 + e := create_encrypted_ore_json(17); + + PERFORM assert_result( + 'eql_v1_encrypted > eql_v1_encrypted', + format('SELECT e FROM encrypted WHERE e > %L', e)); + + PERFORM assert_count( + 'eql_v1_encrypted > eql_v1_encrypted', + format('SELECT e FROM encrypted WHERE e > %L', e), + 2); + END; +$$ LANGUAGE plpgsql; - -- Create a record with HIGH ore - e := create_encrypted_json()::jsonb || get_high_ore(); - PERFORM seed_encrypted(e); - -- Default has LOW ore - e := create_encrypted_json(); +-- -- +-- -- ORE - eql_v1.lt(a eql_v1_encrypted, b eql_v1_encrypted) +-- -- +DO $$ +DECLARE + e eql_v1_encrypted; + ore_term jsonb; + BEGIN + -- Record with a Numeric ORE term of 42 + e := create_encrypted_ore_json(21); PERFORM assert_result( - 'eql_v1.get(a eql_v1_encrypted, b eql_v1_encrypted)', + 'eql_v1.lt(a eql_v1_encrypted, b eql_v1_encrypted)', format('SELECT e FROM encrypted WHERE eql_v1.gt(e, %L)', e)); PERFORM assert_count( - 'eql_v1.get(a eql_v1_encrypted, b eql_v1_encrypted)', + 'eql_v1.lt(a eql_v1_encrypted, b eql_v1_encrypted)', format('SELECT e FROM encrypted WHERE eql_v1.gt(e, %L)', e), - 2); + 1); END; $$ LANGUAGE plpgsql; diff --git a/src/operators/@>.sql b/src/operators/@>.sql index 32932a5..daf540d 100644 --- a/src/operators/@>.sql +++ b/src/operators/@>.sql @@ -1,94 +1,18 @@ +-- REQUIRE: src/schema.sql -- REQUIRE: src/encrypted/types.sql --- REQUIRE: src/encrypted/functions.sql +-- REQUIRE: src/ste_vec/functions.sql -DROP OPERATOR IF EXISTS @> (eql_v1_encrypted, eql_v1_encrypted); -DROP FUNCTION IF EXISTS eql_v1."@>"(e eql_v1_encrypted, b eql_v1_encrypted); +-- DROP OPERATOR IF EXISTS @> (eql_v1_encrypted, eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1."@>"(e eql_v1_encrypted, b eql_v1_encrypted); --- --- Returns the element that --- CREATE FUNCTION eql_v1."@>"(a eql_v1_encrypted, b eql_v1_encrypted) - RETURNS boolean - IMMUTABLE STRICT PARALLEL SAFE -AS $$ - DECLARE - selector text; - term text; - BEGIN - selector := b.data->>'s'; - term := b.data->>'t'; - - IF selector IS NULL THEN - RETURN false; - END IF; - - IF term IS NULL THEN - RETURN false; - END IF; - - RETURN ( - SELECT exists ( - SELECT elem::eql_v1_encrypted - FROM jsonb_array_elements(a.data->'j') AS elem - WHERE elem->>'s' = selector AND - elem->>'t' = term - LIMIT 1 - ) - ); - - END; -$$ LANGUAGE plpgsql; - +RETURNS boolean AS $$ + SELECT eql_v1.ste_vec_contains(a, b) +$$ LANGUAGE SQL; CREATE OPERATOR @>( FUNCTION=eql_v1."@>", LEFTARG=eql_v1_encrypted, RIGHTARG=eql_v1_encrypted ); - - - - --- -- Determine if a contains b (ignoring ciphertext values) --- DROP FUNCTION IF EXISTS eql_v1.ste_vec_logical_contains(a eql_v1.ste_vec_index, b eql_v1.ste_vec_index); - --- CREATE FUNCTION eql_v1.ste_vec_logical_contains(a eql_v1.ste_vec_index, b eql_v1.ste_vec_index) --- RETURNS boolean AS $$ --- DECLARE --- result boolean; --- intermediate_result boolean; --- BEGIN --- result := true; --- IF array_length(b.entries, 1) IS NULL THEN --- RETURN result; --- END IF; --- FOR i IN 1..array_length(b.entries, 1) LOOP --- intermediate_result := eql_v1.ste_vec_entry_array_contains_entry(a.entries, b.entries[i]); --- result := result AND intermediate_result; --- END LOOP; --- RETURN result; --- END; --- $$ LANGUAGE plpgsql; - --- -- Determine if a contains b (ignoring ciphertext values) --- DROP FUNCTION IF EXISTS eql_v1.ste_vec_entry_array_contains_entry(a eql_v1.ste_vec_entry[], b eql_v1.ste_vec_entry); - --- CREATE FUNCTION eql_v1.ste_vec_entry_array_contains_entry(a eql_v1.ste_vec_entry[], b eql_v1.ste_vec_entry) --- RETURNS boolean AS $$ --- DECLARE --- result boolean; --- intermediate_result boolean; --- BEGIN --- IF array_length(a, 1) IS NULL THEN --- RETURN false; --- END IF; - --- result := false; --- FOR i IN 1..array_length(a, 1) LOOP --- intermediate_result := a[i].tokenized_selector = b.tokenized_selector AND a[i].term = b.term; --- result := result OR intermediate_result; --- END LOOP; --- RETURN result; --- END; --- $$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/src/operators/@>_test.sql b/src/operators/@>_test.sql new file mode 100644 index 0000000..c3d6405 --- /dev/null +++ b/src/operators/@>_test.sql @@ -0,0 +1,94 @@ +\set ON_ERROR_STOP on + +SELECT create_table_with_encrypted(); +SELECT seed_encrypted_json(); + + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- eql_v1_encrypted contains itself +-- +-- +DO $$ + DECLARE + a eql_v1_encrypted; + b eql_v1_encrypted; + BEGIN + + a := get_numeric_ste_vec_10()::eql_v1_encrypted; + b := get_numeric_ste_vec_10()::eql_v1_encrypted; + + ASSERT a @> b; + ASSERT b @> a; + END; +$$ LANGUAGE plpgsql; + + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- eql_v1_encrypted contains a term +-- +-- +DO $$ + DECLARE + a eql_v1_encrypted; + b eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + a := get_numeric_ste_vec_10()::eql_v1_encrypted; + b := get_numeric_ste_vec_10()::eql_v1_encrypted; + + -- $.n + term := b->'2517068c0d1f9d4d41d2c666211f785e'; + + ASSERT a @> term; + + ASSERT NOT term @> a; + END; +$$ LANGUAGE plpgsql; + + + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- ore_cllw_u64_8 equality +-- +-- Test data is in form '{"hello": "{one | two | three}", "n": {10 | 20 | 30} }' +-- +-- Paths +-- $ -> bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- +-- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- This extracts the data associated with the field from the test eql_v1_encrypted + sv := get_numeric_ste_vec_10()::eql_v1_encrypted; + -- extract the term at $.n returned as eql_v1_encrypted + term := sv->'a7cea93975ed8c01f861ccb6bd082784'; + + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted = eql_v1_encrypted with ore_cllw_u64_8 index term'), + format('SELECT e FROM encrypted WHERE e @> %L::eql_v1_encrypted', term)); + + PERFORM assert_count( + format('eql_v1_encrypted = eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE e @> %L::eql_v1_encrypted', term), + 1); + + END; +$$ LANGUAGE plpgsql; + + + +SELECT drop_table_with_encrypted(); \ No newline at end of file diff --git a/src/operators/class.sql b/src/operators/class.sql index 17a15e5..652bc57 100644 --- a/src/operators/class.sql +++ b/src/operators/class.sql @@ -11,10 +11,10 @@ -- DROP ORERATOR CLASS & FAMILY BEFORE FUNCTION -DROP OPERATOR CLASS IF EXISTS eql_v1.encrypted_operator USING btree; -DROP OPERATOR FAMILY IF EXISTS eql_v1.encrypted_operator USING btree; +-- DROP OPERATOR CLASS IF EXISTS eql_v1.encrypted_operator USING btree; +-- DROP OPERATOR FAMILY IF EXISTS eql_v1.encrypted_operator USING btree; -DROP FUNCTION IF EXISTS eql_v1.compare(a eql_v1_encrypted, b eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1.compare(a eql_v1_encrypted, b eql_v1_encrypted); -- -- Comparison function for eql_v1_encrypted diff --git a/src/operators/~~.sql b/src/operators/~~.sql index 4e48406..59b4e20 100644 --- a/src/operators/~~.sql +++ b/src/operators/~~.sql @@ -1,3 +1,4 @@ +-- REQUIRE: src/schema.sql -- REQUIRE: src/encrypted/types.sql -- REQUIRE: src/match/types.sql -- REQUIRE: src/match/functions.sql @@ -12,7 +13,7 @@ -- -DROP FUNCTION IF EXISTS eql_v1.match(a eql_v1_encrypted, b eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1.match(a eql_v1_encrypted, b eql_v1_encrypted); CREATE FUNCTION eql_v1.match(a eql_v1_encrypted, b eql_v1_encrypted) RETURNS boolean AS $$ @@ -21,10 +22,10 @@ $$ LANGUAGE SQL; -- DROP OPERATOR BEFORE FUNCTION -DROP OPERATOR IF EXISTS ~~ (eql_v1_encrypted, eql_v1_encrypted); -DROP OPERATOR IF EXISTS ~~* (eql_v1_encrypted, eql_v1_encrypted); +-- DROP OPERATOR IF EXISTS ~~ (eql_v1_encrypted, eql_v1_encrypted); +-- DROP OPERATOR IF EXISTS ~~* (eql_v1_encrypted, eql_v1_encrypted); -DROP FUNCTION IF EXISTS eql_v1."~~"(a eql_v1_encrypted, b eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1."~~"(a eql_v1_encrypted, b eql_v1_encrypted); CREATE FUNCTION eql_v1."~~"(a eql_v1_encrypted, b eql_v1_encrypted) RETURNS boolean @@ -55,10 +56,10 @@ CREATE OPERATOR ~~*( MERGES ); -DROP OPERATOR IF EXISTS ~~ (eql_v1_encrypted, jsonb); -DROP OPERATOR IF EXISTS ~~* (eql_v1_encrypted, jsonb); +-- DROP OPERATOR IF EXISTS ~~ (eql_v1_encrypted, jsonb); +-- DROP OPERATOR IF EXISTS ~~* (eql_v1_encrypted, jsonb); -DROP FUNCTION IF EXISTS eql_v1."~~"(a eql_v1_encrypted, b jsonb); +-- DROP FUNCTION IF EXISTS eql_v1."~~"(a eql_v1_encrypted, b jsonb); CREATE FUNCTION eql_v1."~~"(a eql_v1_encrypted, b jsonb) RETURNS boolean @@ -90,10 +91,10 @@ CREATE OPERATOR ~~*( ); -DROP OPERATOR IF EXISTS ~~ (jsonb, eql_v1_encrypted); -DROP OPERATOR IF EXISTS ~~* (jsonb, eql_v1_encrypted); +-- DROP OPERATOR IF EXISTS ~~ (jsonb, eql_v1_encrypted); +-- DROP OPERATOR IF EXISTS ~~* (jsonb, eql_v1_encrypted); -DROP FUNCTION IF EXISTS eql_v1."~~"(a jsonb, b eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1."~~"(a jsonb, b eql_v1_encrypted); CREATE FUNCTION eql_v1."~~"(a jsonb, b eql_v1_encrypted) RETURNS boolean @@ -126,34 +127,3 @@ CREATE OPERATOR ~~*( -- ----------------------------------------------------------------------------- - - - - --- DROP OPERATOR IF EXISTS ~~ (eql_v1.match_index, eql_v1.match_index); --- DROP FUNCTION IF EXISTS eql_v1.encrypted_match(a eql_v1.match_index, b eql_v1.match_index); - --- CREATE FUNCTION eql_v1.encrypted_match(a eql_v1.match_index, b eql_v1.match_index) --- RETURNS boolean AS $$ --- SELECT a @> b; --- $$ LANGUAGE SQL; - --- CREATE OPERATOR ~~( --- FUNCTION=eql_v1.encrypted_match, --- LEFTARG=eql_v1.match_index, --- RIGHTARG=eql_v1.match_index, --- RESTRICT = eqsel, --- JOIN = eqjoinsel, --- HASHES, --- MERGES --- ); - --- CREATE OPERATOR ~~*( --- FUNCTION=eql_v1.encrypted_match, --- LEFTARG=eql_v1.match_index, --- RIGHTARG=eql_v1.match_index, --- RESTRICT = eqsel, --- JOIN = eqjoinsel, --- HASHES, --- MERGES --- ); diff --git a/src/operators/~~_test.sql b/src/operators/~~_test.sql index efc2d5a..48f1a35 100644 --- a/src/operators/~~_test.sql +++ b/src/operators/~~_test.sql @@ -25,7 +25,7 @@ DECLARE end loop; -- Partial match - e := create_encrypted_json()::jsonb || '{"m": [10, 11]}'; + e := create_encrypted_json('m')::jsonb || '{"m": [10, 11]}'; PERFORM assert_result( 'eql_v1_encrypted ~~ eql_v1_encrypted with partial match', @@ -48,7 +48,7 @@ DECLARE BEGIN for i in 1..3 loop - e := create_encrypted_json(i); + e := create_encrypted_json(i, 'm'); PERFORM assert_result( format('eql_v1_encrypted ~~* eql_v1_encrypted %s of 3', i), @@ -61,7 +61,7 @@ DECLARE end loop; -- Partial match - e := create_encrypted_json()::jsonb || '{"m": [10, 11]}'; + e := create_encrypted_json('m')::jsonb || '{"m": [10, 11]}'; PERFORM assert_result( 'eql_v1_encrypted ~~* eql_v1_encrypted with partial match', @@ -84,7 +84,7 @@ DECLARE BEGIN for i in 1..3 loop - e := create_encrypted_json(i); + e := create_encrypted_json(i, 'm'); PERFORM assert_result( format('eql_v1.match(eql_v1_encrypted, eql_v1_encrypted)', i), @@ -93,7 +93,7 @@ DECLARE end loop; -- Partial match - e := create_encrypted_json()::jsonb || '{"m": [10, 11]}'; + e := create_encrypted_json('m')::jsonb || '{"m": [10, 11]}'; PERFORM assert_result( 'eql_v1.match(eql_v1_encrypted, eql_v1_encrypted)', diff --git a/src/ore-cllw/operators.sql b/src/ore-cllw/operators.sql deleted file mode 100644 index e69de29..0000000 diff --git a/src/ore-cllw/types.sql b/src/ore-cllw/types.sql deleted file mode 100644 index d8e449c..0000000 --- a/src/ore-cllw/types.sql +++ /dev/null @@ -1 +0,0 @@ --- REQUIRE: src/schema.sql diff --git a/src/ore/casts.sql b/src/ore/casts.sql index 1c80dc5..82465cc 100644 --- a/src/ore/casts.sql +++ b/src/ore/casts.sql @@ -2,7 +2,7 @@ -- REQUIRE: src/ore/types.sql -- casts text to ore_64_8_v1_term (bytea) -DROP FUNCTION IF EXISTS eql_v1.text_to_ore_64_8_v1_term(t text); +-- DROP FUNCTION IF EXISTS eql_v1.text_to_ore_64_8_v1_term(t text); CREATE FUNCTION eql_v1.text_to_ore_64_8_v1_term(t text) RETURNS eql_v1.ore_64_8_v1_term @@ -12,7 +12,7 @@ BEGIN ATOMIC END; -- cast to cleanup ore_64_8_v1 extraction -DROP CAST IF EXISTS (text AS eql_v1.ore_64_8_v1_term); +-- DROP CAST IF EXISTS (text AS eql_v1.ore_64_8_v1_term); CREATE CAST (text AS eql_v1.ore_64_8_v1_term) WITH FUNCTION eql_v1.text_to_ore_64_8_v1_term(text) AS IMPLICIT; diff --git a/src/ore/functions.sql b/src/ore/functions.sql index 899a6e1..79afb91 100644 --- a/src/ore/functions.sql +++ b/src/ore/functions.sql @@ -4,32 +4,52 @@ -- REQUIRE: src/ore/types.sql -DROP FUNCTION IF EXISTS eql_v1.jsonb_array_to_ore_64_8_v1(val jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.jsonb_array_to_ore_64_8_v1(val jsonb); -- Casts a jsonb array of hex-encoded strings to the `ore_64_8_v1` composite type. -- In other words, this function takes the ORE index format sent through in the -- EQL payload from Proxy and decodes it as the composite type that we use for -- ORE operations on the Postgres side. +-- CREATE FUNCTION eql_v1.jsonb_array_to_ore_64_8_v1(val jsonb) +-- RETURNS eql_v1.ore_64_8_v1 AS $$ +-- DECLARE +-- terms_arr eql_v1.ore_64_8_v1_term[]; +-- BEGIN +-- IF jsonb_typeof(val) = 'null' THEN +-- RETURN NULL; +-- END IF; + +-- SELECT array_agg(ROW(decode(value::text, 'hex'))::eql_v1.ore_64_8_v1_term) +-- INTO terms_arr +-- FROM jsonb_array_elements_text(val) AS value; + +-- PERFORM eql_v1.log('terms', terms_arr::text); + +-- RETURN ROW(terms_arr)::eql_v1.ore_64_8_v1; +-- END; +-- $$ LANGUAGE plpgsql; + + CREATE FUNCTION eql_v1.jsonb_array_to_ore_64_8_v1(val jsonb) RETURNS eql_v1.ore_64_8_v1 AS $$ DECLARE - terms_arr eql_v1.ore_64_8_v1_term[]; + terms eql_v1.ore_64_8_v1_term[]; BEGIN IF jsonb_typeof(val) = 'null' THEN RETURN NULL; END IF; - SELECT array_agg(ROW(decode(value::text, 'hex'))::eql_v1.ore_64_8_v1_term) - INTO terms_arr - FROM jsonb_array_elements_text(val) AS value; + SELECT array_agg(ROW(b)::eql_v1.ore_64_8_v1_term) + INTO terms + FROM unnest(eql_v1.jsonb_array_to_bytea_array(val)) AS b; - RETURN ROW(terms_arr)::eql_v1.ore_64_8_v1; + RETURN ROW(terms)::eql_v1.ore_64_8_v1; END; $$ LANGUAGE plpgsql; -- extracts ore index from jsonb -DROP FUNCTION IF EXISTS eql_v1.ore_64_8_v1(val jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.ore_64_8_v1(val jsonb); CREATE FUNCTION eql_v1.ore_64_8_v1(val jsonb) RETURNS eql_v1.ore_64_8_v1 @@ -45,7 +65,7 @@ $$ LANGUAGE plpgsql; -- extracts ore index from an encrypted column -DROP FUNCTION IF EXISTS eql_v1.ore_64_8_v1(val eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1.ore_64_8_v1(val eql_v1_encrypted); CREATE FUNCTION eql_v1.ore_64_8_v1(val eql_v1_encrypted) RETURNS eql_v1.ore_64_8_v1 @@ -58,7 +78,7 @@ $$ LANGUAGE plpgsql; -- This function uses lexicographic comparison -DROP FUNCTION IF EXISTS eql_v1.compare_ore_64_8_v1(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1); +-- DROP FUNCTION IF EXISTS eql_v1.compare_ore_64_8_v1(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1); CREATE FUNCTION eql_v1.compare_ore_64_8_v1(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1) RETURNS integer AS $$ @@ -69,7 +89,7 @@ RETURNS integer AS $$ $$ LANGUAGE plpgsql; -DROP FUNCTION IF EXISTS eql_v1.compare_ore_64_8_v1_term(a eql_v1.ore_64_8_v1_term, b eql_v1.ore_64_8_v1_term); +-- DROP FUNCTION IF EXISTS eql_v1.compare_ore_64_8_v1_term(a eql_v1.ore_64_8_v1_term, b eql_v1.ore_64_8_v1_term); CREATE FUNCTION eql_v1.compare_ore_64_8_v1_term(a eql_v1.ore_64_8_v1_term, b eql_v1.ore_64_8_v1_term) RETURNS integer @@ -157,7 +177,7 @@ $$ LANGUAGE plpgsql; -- doesn't always make sense but it's here for completeness. -- If both are non-empty, we compare the first element. If they are equal -- we need to consider the next block so we recurse, otherwise we return the comparison result. -DROP FUNCTION IF EXISTS eql_v1.compare_ore_array(a eql_v1.ore_64_8_v1_term[], b eql_v1.ore_64_8_v1_term[]); +-- DROP FUNCTION IF EXISTS eql_v1.compare_ore_array(a eql_v1.ore_64_8_v1_term[], b eql_v1.ore_64_8_v1_term[]); CREATE FUNCTION eql_v1.compare_ore_array(a eql_v1.ore_64_8_v1_term[], b eql_v1.ore_64_8_v1_term[]) RETURNS integer AS $$ diff --git a/src/ore/operators.sql b/src/ore/operators.sql index 99772e1..7c249a3 100644 --- a/src/ore/operators.sql +++ b/src/ore/operators.sql @@ -4,7 +4,7 @@ -- REQUIRE: src/ore/functions.sql -DROP FUNCTION IF EXISTS eql_v1.ore_64_8_v1_eq(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1); +-- DROP FUNCTION IF EXISTS eql_v1.ore_64_8_v1_eq(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1); CREATE FUNCTION eql_v1.ore_64_8_v1_eq(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1) RETURNS boolean AS $$ @@ -12,7 +12,7 @@ RETURNS boolean AS $$ $$ LANGUAGE SQL; -DROP FUNCTION IF EXISTS eql_v1.ore_64_8_v1_neq(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1); +-- DROP FUNCTION IF EXISTS eql_v1.ore_64_8_v1_neq(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1); CREATE FUNCTION eql_v1.ore_64_8_v1_neq(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1) RETURNS boolean AS $$ @@ -20,7 +20,7 @@ RETURNS boolean AS $$ $$ LANGUAGE SQL; -DROP FUNCTION IF EXISTS eql_v1.ore_64_8_v1_lt(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1); +-- DROP FUNCTION IF EXISTS eql_v1.ore_64_8_v1_lt(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1); CREATE FUNCTION eql_v1.ore_64_8_v1_lt(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1) RETURNS boolean AS $$ @@ -28,7 +28,7 @@ RETURNS boolean AS $$ $$ LANGUAGE SQL; -DROP FUNCTION IF EXISTS eql_v1.ore_64_8_v1_lte(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1); +-- DROP FUNCTION IF EXISTS eql_v1.ore_64_8_v1_lte(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1); CREATE FUNCTION eql_v1.ore_64_8_v1_lte(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1) RETURNS boolean AS $$ @@ -36,7 +36,7 @@ RETURNS boolean AS $$ $$ LANGUAGE SQL; -DROP FUNCTION IF EXISTS eql_v1.ore_64_8_v1_gt(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1); +-- DROP FUNCTION IF EXISTS eql_v1.ore_64_8_v1_gt(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1); CREATE FUNCTION eql_v1.ore_64_8_v1_gt(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1) RETURNS boolean AS $$ @@ -44,7 +44,7 @@ RETURNS boolean AS $$ $$ LANGUAGE SQL; -DROP FUNCTION IF EXISTS eql_v1.ore_64_8_v1_gte(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1); +-- DROP FUNCTION IF EXISTS eql_v1.ore_64_8_v1_gte(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1); CREATE FUNCTION eql_v1.ore_64_8_v1_gte(a eql_v1.ore_64_8_v1, b eql_v1.ore_64_8_v1) RETURNS boolean AS $$ @@ -52,7 +52,7 @@ RETURNS boolean AS $$ $$ LANGUAGE SQL; -DROP OPERATOR IF EXISTS = (eql_v1.ore_64_8_v1, eql_v1.ore_64_8_v1); +-- DROP OPERATOR IF EXISTS = (eql_v1.ore_64_8_v1, eql_v1.ore_64_8_v1); CREATE OPERATOR = ( FUNCTION=eql_v1.ore_64_8_v1_eq, @@ -66,7 +66,7 @@ CREATE OPERATOR = ( ); -DROP OPERATOR IF EXISTS <> (eql_v1.ore_64_8_v1, eql_v1.ore_64_8_v1); +-- DROP OPERATOR IF EXISTS <> (eql_v1.ore_64_8_v1, eql_v1.ore_64_8_v1); CREATE OPERATOR <> ( FUNCTION=eql_v1.ore_64_8_v1_neq, @@ -79,7 +79,7 @@ CREATE OPERATOR <> ( MERGES ); -DROP OPERATOR IF EXISTS > (eql_v1.ore_64_8_v1, eql_v1.ore_64_8_v1); +-- DROP OPERATOR IF EXISTS > (eql_v1.ore_64_8_v1, eql_v1.ore_64_8_v1); CREATE OPERATOR > ( FUNCTION=eql_v1.ore_64_8_v1_gt, @@ -92,7 +92,7 @@ CREATE OPERATOR > ( ); -DROP OPERATOR IF EXISTS < (eql_v1.ore_64_8_v1, eql_v1.ore_64_8_v1); +-- DROP OPERATOR IF EXISTS < (eql_v1.ore_64_8_v1, eql_v1.ore_64_8_v1); CREATE OPERATOR < ( FUNCTION=eql_v1.ore_64_8_v1_lt, @@ -105,7 +105,7 @@ CREATE OPERATOR < ( ); -DROP OPERATOR IF EXISTS <= (eql_v1.ore_64_8_v1, eql_v1.ore_64_8_v1); +-- DROP OPERATOR IF EXISTS <= (eql_v1.ore_64_8_v1, eql_v1.ore_64_8_v1); CREATE OPERATOR <= ( FUNCTION=eql_v1.ore_64_8_v1_lte, @@ -118,7 +118,7 @@ CREATE OPERATOR <= ( ); -DROP OPERATOR IF EXISTS >= (eql_v1.ore_64_8_v1, eql_v1.ore_64_8_v1); +-- DROP OPERATOR IF EXISTS >= (eql_v1.ore_64_8_v1, eql_v1.ore_64_8_v1); CREATE OPERATOR >= ( FUNCTION=eql_v1.ore_64_8_v1_gte, @@ -131,12 +131,12 @@ CREATE OPERATOR >= ( ); -DROP OPERATOR FAMILY IF EXISTS eql_v1.ore_64_8_v1_btree_ops USING btree; +-- DROP OPERATOR FAMILY IF EXISTS eql_v1.ore_64_8_v1_btree_ops USING btree; CREATE OPERATOR FAMILY eql_v1.ore_64_8_v1_btree_ops USING btree; -DROP OPERATOR CLASS IF EXISTS eql_v1.ore_64_8_v1_btree_ops USING btree; +-- DROP OPERATOR CLASS IF EXISTS eql_v1.ore_64_8_v1_btree_ops USING btree; CREATE OPERATOR CLASS eql_v1.ore_64_8_v1_btree_ops DEFAULT FOR TYPE eql_v1.ore_64_8_v1 USING btree FAMILY eql_v1.ore_64_8_v1_btree_ops AS OPERATOR 1 <, diff --git a/src/ore/types.sql b/src/ore/types.sql index cb86d09..eba6ea9 100644 --- a/src/ore/types.sql +++ b/src/ore/types.sql @@ -1,12 +1,12 @@ -- REQUIRE: src/schema.sql -DROP TYPE IF EXISTS eql_v1.ore_64_8_v1_term; +-- DROP TYPE IF EXISTS eql_v1.ore_64_8_v1_term; CREATE TYPE eql_v1.ore_64_8_v1_term AS ( bytes bytea ); -DROP TYPE IF EXISTS eql_v1.ore_64_8_v1; +-- DROP TYPE IF EXISTS eql_v1.ore_64_8_v1; CREATE TYPE eql_v1.ore_64_8_v1 AS ( terms eql_v1.ore_64_8_v1_term[] diff --git a/src/ore_cllw_u64_8/class.sql b/src/ore_cllw_u64_8/class.sql new file mode 100644 index 0000000..f1f8b39 --- /dev/null +++ b/src/ore_cllw_u64_8/class.sql @@ -0,0 +1,19 @@ +-- REQUIRE: src/schema.sql +-- REQUIRE: src/ore_cllw_u64_8/types.sql +-- REQUIRE: src/ore_cllw_u64_8/functions.sql +-- REQUIRE: src/ore_cllw_u64_8/operators.sql + + +-- DROP OPERATOR FAMILY IF EXISTS eql_v1.ore_cllw_u64_8_btree_ops USING btree; + +CREATE OPERATOR FAMILY eql_v1.ore_cllw_u64_8_btree_ops USING btree; + +-- DROP OPERATOR CLASS IF EXISTS eql_v1.ore_cllw_u64_8_btree_ops USING btree; + +CREATE OPERATOR CLASS eql_v1.ore_cllw_u64_8_btree_ops DEFAULT FOR TYPE eql_v1.ore_cllw_u64_8 USING btree FAMILY eql_v1.ore_cllw_u64_8_btree_ops AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 eql_v1.compare_ore_cllw_u64_8(a eql_v1.ore_cllw_u64_8, b eql_v1.ore_cllw_u64_8); diff --git a/src/ore_cllw_u64_8/functions.sql b/src/ore_cllw_u64_8/functions.sql new file mode 100644 index 0000000..706c754 --- /dev/null +++ b/src/ore_cllw_u64_8/functions.sql @@ -0,0 +1,114 @@ +-- REQUIRE: src/schema.sql +-- REQUIRE: src/common.sql +-- REQUIRE: src/ore_cllw_u64_8/types.sql + + + +-- extracts ste_vec index from a jsonb value +-- DROP FUNCTION IF EXISTS eql_v1.ste_vec(val jsonb); + +-- extracts ore_cllw_u64_8 index from a jsonb value +-- DROP FUNCTION IF EXISTS eql_v1.ore_cllw_u64_8(val jsonb); + +CREATE FUNCTION eql_v1.ore_cllw_u64_8(val jsonb) + RETURNS eql_v1.ore_cllw_u64_8 + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + BEGIN + + IF NOT (val ? 'ocf') THEN + RAISE 'Expected a ore_cllw_u64_8 index (ocf) value in json: %', val; + END IF; + + IF val->>'ocf' IS NULL THEN + RETURN NULL; + END IF; + + RETURN ROW(decode(val->>'ocf', 'hex')); + END; +$$ LANGUAGE plpgsql; + + +-- extracts ore_cllw_u64_8 index from an eql_v1_encrypted value +-- DROP FUNCTION IF EXISTS eql_v1.ore_cllw_u64_8(val eql_v1_encrypted); + +CREATE FUNCTION eql_v1.ore_cllw_u64_8(val eql_v1_encrypted) + RETURNS eql_v1.ore_cllw_u64_8 + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + BEGIN + RETURN (SELECT eql_v1.ore_cllw_u64_8(val.data)); + END; +$$ LANGUAGE plpgsql; + + + +-- +-- Compare ore cllw bytes +-- Used by both fixed and variable ore cllw variants +-- + +-- DROP FUNCTION IF EXISTS eql_v1.__compare_inner_ore_cllw_u64_8(a bytea, b bytea); +-- DROP FUNCTION IF EXISTS eql_v1.compare_ore_cllw(a bytea, b bytea); +CREATE FUNCTION eql_v1.compare_ore_cllw(a bytea, b bytea) +RETURNS int AS $$ +DECLARE + len_a INT; + x BYTEA; + y BYTEA; + i INT; + differing RECORD; +BEGIN + len_a := LENGTH(a); + + -- Iterate over each byte and compare them + FOR i IN 1..len_a LOOP + x := SUBSTRING(a FROM i FOR 1); + y := SUBSTRING(b FROM i FOR 1); + + -- Check if there's a difference + IF x != y THEN + differing := (x, y); + EXIT; + END IF; + END LOOP; + + -- If a difference is found, compare the bytes as in Rust logic + IF differing IS NOT NULL THEN + IF (get_byte(y, 0) + 1) % 256 = get_byte(x, 0) THEN + RETURN 1; + ELSE + RETURN -1; + END IF; + ELSE + RETURN 0; + END IF; +END; +$$ LANGUAGE plpgsql; + + + +-- DROP FUNCTION IF EXISTS eql_v1.compare_ore_cllw_u64_8(a eql_v1.ore_cllw_u64_8, b eql_v1.ore_cllw_u64_8); + +CREATE FUNCTION eql_v1.compare_ore_cllw_u64_8(a eql_v1.ore_cllw_u64_8, b eql_v1.ore_cllw_u64_8) +RETURNS int AS $$ +DECLARE + len_a INT; + len_b INT; +BEGIN + IF a IS NULL OR b IS NULL THEN + RETURN NULL; + END IF; + + -- Check if the lengths of the two bytea arguments are the same + len_a := LENGTH(a.bytes); + len_b := LENGTH(b.bytes); + + IF len_a != len_b THEN + RAISE EXCEPTION 'ore_cllw_u64_8 index terms are not the same length'; + END IF; + + RETURN eql_v1.compare_ore_cllw(a.bytes, b.bytes); +END; +$$ LANGUAGE plpgsql; + diff --git a/src/ore_cllw_u64_8/operators.sql b/src/ore_cllw_u64_8/operators.sql new file mode 100644 index 0000000..e4c6cce --- /dev/null +++ b/src/ore_cllw_u64_8/operators.sql @@ -0,0 +1,141 @@ +-- REQUIRE: src/schema.sql +-- REQUIRE: src/common.sql +-- REQUIRE: src/ore_cllw_u64_8/types.sql +-- REQUIRE: src/ore_cllw_u64_8/functions.sql + +-- DROP FUNCTION IF EXISTS eql_v1.ore_cllw_u64_8_eq(a eql_v1.ore_cllw_u64_8, b eql_v1.ore_cllw_u64_8); + +CREATE FUNCTION eql_v1.ore_cllw_u64_8_eq(a eql_v1.ore_cllw_u64_8, b eql_v1.ore_cllw_u64_8) +RETURNS boolean AS $$ + SELECT eql_v1.bytea_eq(a.bytes, b.bytes) +$$ LANGUAGE SQL; + + +-- DROP FUNCTION IF EXISTS eql_v1.ore_cllw_u64_8_neq(a eql_v1.ore_cllw_u64_8, b eql_v1.ore_cllw_u64_8); + +CREATE FUNCTION eql_v1.ore_cllw_u64_8_neq(a eql_v1.ore_cllw_u64_8, b eql_v1.ore_cllw_u64_8) +RETURNS boolean AS $$ + SELECT NOT eql_v1.bytea_eq(a.bytes, b.bytes) +$$ LANGUAGE SQL; + + +-- DROP FUNCTION IF EXISTS eql_v1.ore_cllw_u64_8_lt(a eql_v1.ore_cllw_u64_8, b eql_v1.ore_cllw_u64_8); + +CREATE FUNCTION eql_v1.ore_cllw_u64_8_lt(a eql_v1.ore_cllw_u64_8, b eql_v1.ore_cllw_u64_8) +RETURNS boolean +AS $$ + BEGIN + RETURN eql_v1.compare_ore_cllw_u64_8(a, b) = -1; + END +$$ LANGUAGE plpgsql; + + +-- DROP FUNCTION IF EXISTS eql_v1.ore_cllw_u64_8_lte(a eql_v1.ore_cllw_u64_8, b eql_v1.ore_cllw_u64_8); + +CREATE FUNCTION eql_v1.ore_cllw_u64_8_lte(a eql_v1.ore_cllw_u64_8, b eql_v1.ore_cllw_u64_8) +RETURNS boolean AS $$ + SELECT eql_v1.compare_ore_cllw_u64_8(a, b) != 1 +$$ LANGUAGE SQL; + + +-- DROP FUNCTION IF EXISTS eql_v1.ore_cllw_u64_8_gt(a eql_v1.ore_cllw_u64_8, b eql_v1.ore_cllw_u64_8); + +CREATE FUNCTION eql_v1.ore_cllw_u64_8_gt(a eql_v1.ore_cllw_u64_8, b eql_v1.ore_cllw_u64_8) +RETURNS boolean AS $$ + SELECT eql_v1.compare_ore_cllw_u64_8(a, b) = 1 +$$ LANGUAGE SQL; + + +-- DROP FUNCTION IF EXISTS eql_v1.ore_cllw_u64_8_gte(a eql_v1.ore_cllw_u64_8, b eql_v1.ore_cllw_u64_8); + +CREATE FUNCTION eql_v1.ore_cllw_u64_8_gte(a eql_v1.ore_cllw_u64_8, b eql_v1.ore_cllw_u64_8) +RETURNS boolean AS $$ + SELECT eql_v1.compare_ore_cllw_u64_8(a, b) != -1 +$$ LANGUAGE SQL; + + +-- DROP OPERATOR IF EXISTS = (eql_v1.ore_cllw_u64_8, eql_v1.ore_cllw_u64_8); + +CREATE OPERATOR = ( + FUNCTION=eql_v1.ore_cllw_u64_8_eq, + LEFTARG=eql_v1.ore_cllw_u64_8, + RIGHTARG=eql_v1.ore_cllw_u64_8, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + + +-- DROP OPERATOR IF EXISTS <> (eql_v1.ore_cllw_u64_8, eql_v1.ore_cllw_u64_8); + +CREATE OPERATOR <> ( + FUNCTION=eql_v1.ore_cllw_u64_8_neq, + LEFTARG=eql_v1.ore_cllw_u64_8, + RIGHTARG=eql_v1.ore_cllw_u64_8, + NEGATOR = =, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + + +-- DROP OPERATOR IF EXISTS > (eql_v1.ore_cllw_u64_8, eql_v1.ore_cllw_u64_8); + +CREATE OPERATOR > ( + FUNCTION=eql_v1.ore_cllw_u64_8_gt, + LEFTARG=eql_v1.ore_cllw_u64_8, + RIGHTARG=eql_v1.ore_cllw_u64_8, + NEGATOR = <=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel, + HASHES, + MERGES +); + + +-- DROP OPERATOR IF EXISTS < (eql_v1.ore_cllw_u64_8, eql_v1.ore_cllw_u64_8); + +CREATE OPERATOR < ( + FUNCTION=eql_v1.ore_cllw_u64_8_lt, + LEFTARG=eql_v1.ore_cllw_u64_8, + RIGHTARG=eql_v1.ore_cllw_u64_8, + NEGATOR = >=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel, + HASHES, + MERGES +); + + +-- DROP OPERATOR IF EXISTS >= (eql_v1.ore_cllw_u64_8, eql_v1.ore_cllw_u64_8); + +CREATE OPERATOR >= ( + FUNCTION=eql_v1.ore_cllw_u64_8_gte, + LEFTARG=eql_v1.ore_cllw_u64_8, + RIGHTARG=eql_v1.ore_cllw_u64_8, + NEGATOR = <, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel, + HASHES, + MERGES +); + + +-- DROP OPERATOR IF EXISTS <= (eql_v1.ore_cllw_u64_8, eql_v1.ore_cllw_u64_8); + +CREATE OPERATOR <= ( + FUNCTION=eql_v1.ore_cllw_u64_8_lte, + LEFTARG=eql_v1.ore_cllw_u64_8, + RIGHTARG=eql_v1.ore_cllw_u64_8, + NEGATOR = >, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel, + HASHES, + MERGES +); + + + diff --git a/src/ore_cllw_u64_8/operators_test.sql b/src/ore_cllw_u64_8/operators_test.sql new file mode 100644 index 0000000..9bc130e --- /dev/null +++ b/src/ore_cllw_u64_8/operators_test.sql @@ -0,0 +1,234 @@ +\set ON_ERROR_STOP on + +SELECT create_table_with_encrypted(); +SELECT seed_encrypted_json(); + + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- ore_cllw_u64_8 < ore_cllw_u64_8 +-- +-- Test data is '{"hello": "world", "n": 42}' + +-- Paths +-- $ -> bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- +-- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- json n: 20 + sv := get_numeric_ste_vec_20()::eql_v1_encrypted; + -- extract the term at $.n + term := sv->'2517068c0d1f9d4d41d2c666211f785e'; + + PERFORM eql_v1.log('term', term::text); + + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted < eql_v1_encrypted with ore_cllw_u64_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_u64_8(e->''2517068c0d1f9d4d41d2c666211f785e'') < eql_v1.ore_cllw_u64_8(%L::eql_v1_encrypted)', term)); + + PERFORM assert_count( + format('eql_v1_encrypted < eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_u64_8(e->''2517068c0d1f9d4d41d2c666211f785e'') < eql_v1.ore_cllw_u64_8(%L::eql_v1_encrypted)', term), + 1); + + -- Check the $.hello path + -- Returned encrypted does not have ore_cllw_u64_8 + PERFORM assert_exception( + format('eql_v1_encrypted < eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_u64_8(e->''a7cea93975ed8c01f861ccb6bd082784'') < eql_v1.ore_cllw_u64_8(%L::eql_v1_encrypted)', term)); + + END; +$$ LANGUAGE plpgsql; + + + + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- ore_cllw_u64_8 <= ore_cllw_u64_8 +-- +-- Test data is '{"hello": "world", "n": 42}' + +-- Paths +-- $ -> bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- +-- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- json n: 20 + sv := get_numeric_ste_vec_20()::eql_v1_encrypted; + + -- extract the term at $.n + term := sv->'2517068c0d1f9d4d41d2c666211f785e'; + + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted <= eql_v1_encrypted with ore_cllw_u64_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_u64_8(e->''2517068c0d1f9d4d41d2c666211f785e'') <= eql_v1.ore_cllw_u64_8(%L::eql_v1_encrypted)', term)); + + PERFORM assert_count( + format('eql_v1_encrypted <= eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_u64_8(e->''2517068c0d1f9d4d41d2c666211f785e'') <= eql_v1.ore_cllw_u64_8(%L::eql_v1_encrypted)', term), + 2); + + -- Check the $.hello path + -- Returned encrypted does not have ore_cllw_u64_8 + PERFORM assert_exception( + format('eql_v1_encrypted <= eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_u64_8(e->''a7cea93975ed8c01f861ccb6bd082784'') <= eql_v1.ore_cllw_u64_8(%L::eql_v1_encrypted)', term)); + + END; +$$ LANGUAGE plpgsql; + + + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- ore_cllw_u64_8 >= ore_cllw_u64_8 +-- +-- Test data is '{"hello": "world", "n": 42}' + +-- Paths +-- $ -> bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- +-- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- json n: 30 + sv := get_numeric_ste_vec_30()::eql_v1_encrypted; + + -- extract the term at $.n + term := sv->'2517068c0d1f9d4d41d2c666211f785e'; + + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted >= eql_v1_encrypted with ore_cllw_u64_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_u64_8(e->''2517068c0d1f9d4d41d2c666211f785e'') >= eql_v1.ore_cllw_u64_8(%L::eql_v1_encrypted)', term)); + + PERFORM assert_count( + format('eql_v1_encrypted >= eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_u64_8(e->''2517068c0d1f9d4d41d2c666211f785e'') >= eql_v1.ore_cllw_u64_8(%L::eql_v1_encrypted)', term), + 1); + + -- Check the $ path + -- Returned encrypted does not have ore_cllw_u64_8 + PERFORM assert_exception( + format('eql_v1_encrypted >= eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_u64_8(e->''bca213de9ccce676fa849ff9c4807963'') >= eql_v1.ore_cllw_u64_8(%L::eql_v1_encrypted)', term)); + + END; +$$ LANGUAGE plpgsql; + + + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- ore_cllw_u64_8 > ore_cllw_u64_8 +-- +-- Test data is '{"hello": "world", "n": 42}' + +-- Paths +-- $ -> bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- +-- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- json n: 20 + sv := get_numeric_ste_vec_20()::eql_v1_encrypted; + + -- extract the term at $.n + term := sv->'2517068c0d1f9d4d41d2c666211f785e'; + + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted > eql_v1_encrypted with ore_cllw_u64_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_u64_8(e->''2517068c0d1f9d4d41d2c666211f785e'') > eql_v1.ore_cllw_u64_8(%L::eql_v1_encrypted)', term)); + + PERFORM assert_count( + format('eql_v1_encrypted > eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_u64_8(e->''2517068c0d1f9d4d41d2c666211f785e'') > eql_v1.ore_cllw_u64_8(%L::eql_v1_encrypted)', term), + 1); + + -- Check the $ path + -- Returned encrypted does not have ore_cllw_u64_8 + PERFORM assert_exception( + format('eql_v1_encrypted > eql_v1_encrypted with ore index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_u64_8(e->''bca213de9ccce676fa849ff9c4807963'') > eql_v1.ore_cllw_u64_8(%L::eql_v1_encrypted)', term)); + + END; +$$ LANGUAGE plpgsql; + + + + +-- -- ------------------------------------------------------------------------ +-- -- ------------------------------------------------------------------------ +-- -- +-- -- ore_cllw_u64_8 = ore_cllw_u64_8 +-- -- +-- -- Test data is '{"hello": "world", "n": 42}' + +-- -- Paths +-- -- $ -> bca213de9ccce676fa849ff9c4807963 +-- -- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- -- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- -- +-- -- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- json n: 20 + sv := get_numeric_ste_vec_10()::eql_v1_encrypted; + -- extract the term at $.n + term := sv->'2517068c0d1f9d4d41d2c666211f785e'; + + PERFORM assert_result( + format('eql_v1_encrypted = eql_v1_encrypted with ore_cllw_u64_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_u64_8(e->''2517068c0d1f9d4d41d2c666211f785e'') = eql_v1.ore_cllw_u64_8(%L::eql_v1_encrypted)', term)); + + PERFORM assert_count( + format('eql_v1_encrypted = eql_v1_encrypted with ore_cllw_u64_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_u64_8(e->''2517068c0d1f9d4d41d2c666211f785e'') = eql_v1.ore_cllw_u64_8(%L::eql_v1_encrypted)', term), + 1); + + -- Check the $.n path + -- Returned encrypted does not have ore_cllw_u64_8 and raises exception + PERFORM assert_exception( + format('eql_v1_encrypted = eql_v1_encrypted with ore_cllw_u64_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_u64_8(e->''a7cea93975ed8c01f861ccb6bd082784'') = eql_v1.ore_cllw_u64_8(%L::eql_v1_encrypted)', term)); + + END; +$$ LANGUAGE plpgsql; diff --git a/src/ore_cllw_u64_8/types.sql b/src/ore_cllw_u64_8/types.sql new file mode 100644 index 0000000..01f600c --- /dev/null +++ b/src/ore_cllw_u64_8/types.sql @@ -0,0 +1,7 @@ +-- REQUIRE: src/schema.sql + +-- Represents a ciphertext encrypted with the CLLW ORE scheme for a fixed output size +-- Each output block is 8-bits +CREATE TYPE eql_v1.ore_cllw_u64_8 AS ( + bytes bytea +); diff --git a/src/ore_cllw_var_8/class.sql b/src/ore_cllw_var_8/class.sql new file mode 100644 index 0000000..3f9965a --- /dev/null +++ b/src/ore_cllw_var_8/class.sql @@ -0,0 +1,19 @@ +-- REQUIRE: src/schema.sql +-- REQUIRE: src/ore_cllw_var_8/types.sql +-- REQUIRE: src/ore_cllw_var_8/functions.sql +-- REQUIRE: src/ore_cllw_var_8/operators.sql + + +-- DROP OPERATOR FAMILY IF EXISTS eql_v1.ore_cllw_var_8_variable_btree_ops USING btree; + +CREATE OPERATOR FAMILY eql_v1.ore_cllw_var_8_variable_btree_ops USING btree; + +-- DROP OPERATOR CLASS IF EXISTS eql_v1.ore_cllw_var_8_variable_btree_ops USING btree; + +CREATE OPERATOR CLASS eql_v1.ore_cllw_var_8_variable_btree_ops DEFAULT FOR TYPE eql_v1.ore_cllw_var_8 USING btree FAMILY eql_v1.ore_cllw_var_8_variable_btree_ops AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 eql_v1.compare_ore_cllw_var_8(a eql_v1.ore_cllw_var_8, b eql_v1.ore_cllw_var_8); diff --git a/src/ore_cllw_var_8/functions.sql b/src/ore_cllw_var_8/functions.sql new file mode 100644 index 0000000..8cf61de --- /dev/null +++ b/src/ore_cllw_var_8/functions.sql @@ -0,0 +1,100 @@ +-- REQUIRE: src/schema.sql +-- REQUIRE: src/common.sql +-- REQUIRE: src/ore_cllw_var_8/types.sql +-- REQUIRE: src/ore_cllw_u64_8/functions.sql + + + +-- extracts ore_cllw_var_8 index from a jsonb value +-- DROP FUNCTION IF EXISTS eql_v1.ore_cllw_var_8(val jsonb); + +CREATE FUNCTION eql_v1.ore_cllw_var_8(val jsonb) + RETURNS eql_v1.ore_cllw_var_8 + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + BEGIN + + IF NOT (val ? 'ocv') THEN + RAISE 'Expected a ore_cllw_var_8 index (ocv) value in json: %', val; + END IF; + + IF val->>'ocv' IS NULL THEN + RETURN NULL; + END IF; + + RETURN ROW(decode(val->>'ocv', 'hex')); + END; +$$ LANGUAGE plpgsql; + + +-- extracts ore_cllw_var_8 index from an eql_v1_encrypted value +-- DROP FUNCTION IF EXISTS eql_v1.ore_cllw_var_8(val eql_v1_encrypted); + +CREATE FUNCTION eql_v1.ore_cllw_var_8(val eql_v1_encrypted) + RETURNS eql_v1.ore_cllw_var_8 + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + BEGIN + RETURN (SELECT eql_v1.ore_cllw_var_8(val.data)); + END; +$$ LANGUAGE plpgsql; + + +-- DROP FUNCTION IF EXISTS eql_v1.compare_ore_cllw_var_8(a eql_v1.ore_cllw_var_8, b eql_v1.ore_cllw_var_8); + +CREATE FUNCTION eql_v1.compare_ore_cllw_var_8(a eql_v1.ore_cllw_var_8, b eql_v1.ore_cllw_var_8) +RETURNS int AS $$ +DECLARE + len_a INT; + len_b INT; + -- length of the common part of the two bytea values + common_len INT; + cmp_result INT; +BEGIN + IF a IS NULL OR b IS NULL THEN + RETURN NULL; + END IF; + + -- Get the lengths of both bytea inputs + len_a := LENGTH(a.bytes); + len_b := LENGTH(b.bytes); + + -- Handle empty cases + IF len_a = 0 AND len_b = 0 THEN + RETURN 0; + ELSIF len_a = 0 THEN + RETURN -1; + ELSIF len_b = 0 THEN + RETURN 1; + END IF; + + -- Find the length of the shorter bytea + IF len_a < len_b THEN + common_len := len_a; + ELSE + common_len := len_b; + END IF; + + -- Use the compare_bytea function to compare byte by byte + cmp_result := eql_v1.compare_ore_cllw( + SUBSTRING(a.bytes FROM 1 FOR common_len), + SUBSTRING(b.bytes FROM 1 FOR common_len) + ); + + -- If the comparison returns 'less' or 'greater', return that result + IF cmp_result = -1 THEN + RETURN -1; + ELSIF cmp_result = 1 THEN + RETURN 1; + END IF; + + -- If the bytea comparison is 'equal', compare lengths + IF len_a < len_b THEN + RETURN -1; + ELSIF len_a > len_b THEN + RETURN 1; + ELSE + RETURN 0; + END IF; +END; +$$ LANGUAGE plpgsql; diff --git a/src/ore-cllw/functions.sql b/src/ore_cllw_var_8/functions_test.sql similarity index 100% rename from src/ore-cllw/functions.sql rename to src/ore_cllw_var_8/functions_test.sql diff --git a/src/ore_cllw_var_8/operators.sql b/src/ore_cllw_var_8/operators.sql new file mode 100644 index 0000000..584a76d --- /dev/null +++ b/src/ore_cllw_var_8/operators.sql @@ -0,0 +1,127 @@ +-- REQUIRE: src/ore_cllw_var_8/types.sql +-- REQUIRE: src/ore_cllw_var_8/functions.sql + + + +-- Lexical comparison operators + +-- DROP FUNCTION IF EXISTS eql_v1.ore_cllw_var_8_eq(a eql_v1.ore_cllw_var_8, b eql_v1.ore_cllw_var_8); + +CREATE OR REPLACE FUNCTION eql_v1.ore_cllw_var_8_eq(a eql_v1.ore_cllw_var_8, b eql_v1.ore_cllw_var_8) RETURNS boolean AS $$ + SELECT eql_v1.bytea_eq(a.bytes, b.bytes) +$$ LANGUAGE SQL; + +-- DROP FUNCTION IF EXISTS eql_v1.ore_cllw_var_8_neq(a eql_v1.ore_cllw_var_8, b eql_v1.ore_cllw_var_8); + +CREATE OR REPLACE FUNCTION eql_v1.ore_cllw_var_8_neq(a eql_v1.ore_cllw_var_8, b eql_v1.ore_cllw_var_8) RETURNS boolean AS $$ + SELECT NOT eql_v1.bytea_eq(a.bytes, b.bytes) +$$ LANGUAGE SQL; + +-- DROP FUNCTION IF EXISTS eql_v1.ore_cllw_var_8_lt(a eql_v1.ore_cllw_var_8, b eql_v1.ore_cllw_var_8); + +CREATE OR REPLACE FUNCTION eql_v1.ore_cllw_var_8_lt(a eql_v1.ore_cllw_var_8, b eql_v1.ore_cllw_var_8) +RETURNS boolean +-- AS $$ +-- SELECT eql_v1.compare_ore_cllw_var_8(a, b) = -1 +-- $$ LANGUAGE SQL; +AS $$ + BEGIN + RETURN eql_v1.compare_ore_cllw_var_8(a, b) = -1; + END; +$$ LANGUAGE plpgsql; + +-- DROP FUNCTION IF EXISTS eql_v1.ore_cllw_var_8_lte(a eql_v1.ore_cllw_var_8, b eql_v1.ore_cllw_var_8); + +CREATE OR REPLACE FUNCTION eql_v1.ore_cllw_var_8_lte(a eql_v1.ore_cllw_var_8, b eql_v1.ore_cllw_var_8) RETURNS boolean AS $$ + SELECT eql_v1.compare_ore_cllw_var_8(a, b) != 1 +$$ LANGUAGE SQL; + +-- DROP FUNCTION IF EXISTS eql_v1.ore_cllw_var_8_gt(a eql_v1.ore_cllw_var_8, b eql_v1.ore_cllw_var_8); + +CREATE OR REPLACE FUNCTION eql_v1.ore_cllw_var_8_gt(a eql_v1.ore_cllw_var_8, b eql_v1.ore_cllw_var_8) RETURNS boolean AS $$ + SELECT eql_v1.compare_ore_cllw_var_8(a, b) = 1 +$$ LANGUAGE SQL; + +-- DROP FUNCTION IF EXISTS eql_v1.ore_cllw_var_8_gte(a eql_v1.ore_cllw_var_8, b eql_v1.ore_cllw_var_8); + +CREATE OR REPLACE FUNCTION eql_v1.ore_cllw_var_8_gte(a eql_v1.ore_cllw_var_8, b eql_v1.ore_cllw_var_8) RETURNS boolean AS $$ + SELECT eql_v1.compare_ore_cllw_var_8(a, b) != -1 +$$ LANGUAGE SQL; + +-- DROP OPERATOR IF EXISTS = (eql_v1.ore_cllw_var_8, eql_v1.ore_cllw_var_8); + +CREATE OPERATOR = ( + FUNCTION=eql_v1.ore_cllw_var_8_eq, + LEFTARG=eql_v1.ore_cllw_var_8, + RIGHTARG=eql_v1.ore_cllw_var_8, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +-- DROP OPERATOR IF EXISTS <> (eql_v1.ore_cllw_var_8, eql_v1.ore_cllw_var_8); + +CREATE OPERATOR <> ( + FUNCTION=eql_v1.ore_cllw_var_8_neq, + LEFTARG=eql_v1.ore_cllw_var_8, + RIGHTARG=eql_v1.ore_cllw_var_8, + NEGATOR = =, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +-- DROP OPERATOR IF EXISTS > (eql_v1.ore_cllw_var_8, eql_v1.ore_cllw_var_8); + +CREATE OPERATOR > ( + FUNCTION=eql_v1.ore_cllw_var_8_gt, + LEFTARG=eql_v1.ore_cllw_var_8, + RIGHTARG=eql_v1.ore_cllw_var_8, + NEGATOR = <=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel, + HASHES, + MERGES +); + +-- DROP OPERATOR IF EXISTS < (eql_v1.ore_cllw_var_8, eql_v1.ore_cllw_var_8); + +CREATE OPERATOR < ( + FUNCTION=eql_v1.ore_cllw_var_8_lt, + LEFTARG=eql_v1.ore_cllw_var_8, + RIGHTARG=eql_v1.ore_cllw_var_8, + NEGATOR = >=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel, + HASHES, + MERGES +); + +-- DROP OPERATOR IF EXISTS >= (eql_v1.ore_cllw_var_8, eql_v1.ore_cllw_var_8); + +CREATE OPERATOR >= ( + FUNCTION=eql_v1.ore_cllw_var_8_gte, + LEFTARG=eql_v1.ore_cllw_var_8, + RIGHTARG=eql_v1.ore_cllw_var_8, + NEGATOR = <, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel, + HASHES, + MERGES +); + +-- DROP OPERATOR IF EXISTS <= (eql_v1.ore_cllw_var_8, eql_v1.ore_cllw_var_8); + +CREATE OPERATOR <= ( + FUNCTION=eql_v1.ore_cllw_var_8_lte, + LEFTARG=eql_v1.ore_cllw_var_8, + RIGHTARG=eql_v1.ore_cllw_var_8, + NEGATOR = >, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel, + HASHES, + MERGES +); diff --git a/src/ore_cllw_var_8/operators_test.sql b/src/ore_cllw_var_8/operators_test.sql new file mode 100644 index 0000000..5b89813 --- /dev/null +++ b/src/ore_cllw_var_8/operators_test.sql @@ -0,0 +1,234 @@ +\set ON_ERROR_STOP on + +SELECT create_table_with_encrypted(); +SELECT seed_encrypted_json(); + + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- ore_cllw_var_8 < ore_cllw_var_8 +-- +-- Test data is '{"hello": "world", "n": 42}' + +-- Paths +-- $ -> bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- +-- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- json n: 20 + sv := get_numeric_ste_vec_20()::eql_v1_encrypted; + + -- extract the term at $.n + term := sv->'a7cea93975ed8c01f861ccb6bd082784'; + + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted < eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_var_8(e->''a7cea93975ed8c01f861ccb6bd082784'') < eql_v1.ore_cllw_var_8(%L::eql_v1_encrypted)', term)); + + -- other values are "one" and "three" + PERFORM assert_count( + format('eql_v1_encrypted < eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_var_8(e->''a7cea93975ed8c01f861ccb6bd082784'') < eql_v1.ore_cllw_var_8(%L::eql_v1_encrypted)', term), + 2); + + -- Check the $.n path + -- Returned encrypted does not have ore_cllw_var_8 and raises an exception + PERFORM assert_exception( + format('eql_v1_encrypted < eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_var_8(e->''2517068c0d1f9d4d41d2c666211f785e'') < eql_v1.ore_cllw_var_8(%L::eql_v1_encrypted)', term)); + + END; +$$ LANGUAGE plpgsql; + + + + +-- -- ------------------------------------------------------------------------ +-- -- ------------------------------------------------------------------------ +-- -- +-- -- ore_cllw_var_8 <= ore_cllw_var_8 +-- -- +-- -- Test data is '{"hello": "world", "n": 42}' + +-- -- Paths +-- -- $ -> bca213de9ccce676fa849ff9c4807963 +-- -- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- -- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- -- +-- -- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- json n: 20 + sv := get_numeric_ste_vec_20()::eql_v1_encrypted; + + -- extract the term at $.n + term := sv->'a7cea93975ed8c01f861ccb6bd082784'; + + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted <= eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_var_8(e->''a7cea93975ed8c01f861ccb6bd082784'') <= eql_v1.ore_cllw_var_8(%L::eql_v1_encrypted)', term)); + + PERFORM assert_count( + format('eql_v1_encrypted <= eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_var_8(e->''a7cea93975ed8c01f861ccb6bd082784'') <= eql_v1.ore_cllw_var_8(%L::eql_v1_encrypted)', term), + 3); + + -- Check the $.hello path + -- Returned encrypted does not have ore_cllw_var_8 + PERFORM assert_exception( + format('eql_v1_encrypted <= eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_var_8(e->''2517068c0d1f9d4d41d2c666211f785e'') <= eql_v1.ore_cllw_var_8(%L::eql_v1_encrypted)', term)); + + END; +$$ LANGUAGE plpgsql; + + + +-- -- ------------------------------------------------------------------------ +-- -- ------------------------------------------------------------------------ +-- -- +-- -- ore_cllw_var_8 >= ore_cllw_var_8 +-- -- +-- -- Test data is '{"hello": "world", "n": 42}' + +-- -- Paths +-- -- $ -> bca213de9ccce676fa849ff9c4807963 +-- -- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- -- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- -- +-- -- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- json n: 30 + sv := get_numeric_ste_vec_30()::eql_v1_encrypted; + + -- extract the term at $.n + term := sv->'a7cea93975ed8c01f861ccb6bd082784'; + + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted >= eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_var_8(e->''a7cea93975ed8c01f861ccb6bd082784'') >= eql_v1.ore_cllw_var_8(%L::eql_v1_encrypted)', term)); + + PERFORM assert_count( + format('eql_v1_encrypted >= eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_var_8(e->''a7cea93975ed8c01f861ccb6bd082784'') >= eql_v1.ore_cllw_var_8(%L::eql_v1_encrypted)', term), + 2); + + -- Check the $ path + -- Returned encrypted does not have ore_cllw_var_8 + PERFORM assert_exception( + format('eql_v1_encrypted >= eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_var_8(e->''bca213de9ccce676fa849ff9c4807963'') >= eql_v1.ore_cllw_var_8(%L::eql_v1_encrypted)', term)); + + END; +$$ LANGUAGE plpgsql; + + + +-- -- ------------------------------------------------------------------------ +-- -- ------------------------------------------------------------------------ +-- -- +-- -- ore_cllw_var_8 > ore_cllw_var_8 +-- -- +-- -- Test data is '{"hello": "world", "n": 42}' + +-- -- Paths +-- -- $ -> bca213de9ccce676fa849ff9c4807963 +-- -- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- -- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- -- +-- -- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- json hello: one + sv := get_numeric_ste_vec_10()::eql_v1_encrypted; + + -- extract the term at $.n + term := sv->'a7cea93975ed8c01f861ccb6bd082784'; + + -- -- -- -- $.n + PERFORM assert_result( + format('eql_v1_encrypted > eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_var_8(e->''a7cea93975ed8c01f861ccb6bd082784'') > eql_v1.ore_cllw_var_8(%L::eql_v1_encrypted)', term)); + + PERFORM assert_count( + format('eql_v1_encrypted > eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_var_8(e->''a7cea93975ed8c01f861ccb6bd082784'') > eql_v1.ore_cllw_var_8(%L::eql_v1_encrypted)', term), + 2); + + -- Check the $ path + -- Returned encrypted does not have ore_cllw_var_8 + PERFORM assert_exception( + format('eql_v1_encrypted > eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_var_8(e->''bca213de9ccce676fa849ff9c4807963'') > eql_v1.ore_cllw_var_8(%L::eql_v1_encrypted)', term)); + + END; +$$ LANGUAGE plpgsql; + + + + +-- -- ------------------------------------------------------------------------ +-- -- ------------------------------------------------------------------------ +-- -- +-- -- ore_cllw_var_8 = ore_cllw_var_8 +-- -- +-- -- Test data is '{"hello": "world", "n": 42}' + +-- -- Paths +-- -- $ -> bca213de9ccce676fa849ff9c4807963 +-- -- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- -- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- -- +-- -- +DO $$ +DECLARE + sv eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + -- json n: 20 + sv := get_numeric_ste_vec_10()::eql_v1_encrypted; + -- extract the term at $.n + term := sv->'a7cea93975ed8c01f861ccb6bd082784'; + + PERFORM assert_result( + format('eql_v1_encrypted = eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_var_8(e->''a7cea93975ed8c01f861ccb6bd082784'') = eql_v1.ore_cllw_var_8(%L::eql_v1_encrypted)', term)); + + PERFORM assert_count( + format('eql_v1_encrypted = eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_var_8(e->''a7cea93975ed8c01f861ccb6bd082784'') = eql_v1.ore_cllw_var_8(%L::eql_v1_encrypted)', term), + 1); + + -- Check the $.n path + -- Returned encrypted does not have ore_cllw_var_8 and raises exception + PERFORM assert_exception( + format('eql_v1_encrypted = eql_v1_encrypted with ore_cllw_var_8 index term'), + format('SELECT e FROM encrypted WHERE eql_v1.ore_cllw_var_8(e->''2517068c0d1f9d4d41d2c666211f785e'') = eql_v1.ore_cllw_var_8(%L::eql_v1_encrypted)', term)); + + END; +$$ LANGUAGE plpgsql; diff --git a/src/ore_cllw_var_8/types.sql b/src/ore_cllw_var_8/types.sql new file mode 100644 index 0000000..145432a --- /dev/null +++ b/src/ore_cllw_var_8/types.sql @@ -0,0 +1,7 @@ +-- REQUIRE: src/schema.sql + +-- Represents a ciphertext encrypted with the CLLW ORE scheme for a variable output size +-- Each output block is 8-bits +CREATE TYPE eql_v1.ore_cllw_var_8 AS ( + bytes bytea +); diff --git a/src/ste_vec/functions.sql b/src/ste_vec/functions.sql new file mode 100644 index 0000000..f3ecd01 --- /dev/null +++ b/src/ste_vec/functions.sql @@ -0,0 +1,162 @@ +-- REQUIRE: src/schema.sql +-- REQUIRE: src/encrypted/types.sql + + +-- DROP FUNCTION IF EXISTS eql_v1.ste_vec(val jsonb); +-- +CREATE FUNCTION eql_v1.ste_vec(val jsonb) + RETURNS eql_v1_encrypted[] + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + DECLARE + sv jsonb; + ary eql_v1_encrypted[]; + BEGIN + + IF val ? 'sv' THEN + sv := val->'sv'; + ELSE + sv := jsonb_build_array(val); + END IF; + + SELECT array_agg(elem::eql_v1_encrypted) + INTO ary + FROM jsonb_array_elements(sv) AS elem; + + RETURN ary; + END; +$$ LANGUAGE plpgsql; + + +-- extracts ste_vec index from an eql_v1_encrypted value +-- DROP FUNCTION IF EXISTS eql_v1.ste_vec(val eql_v1_encrypted); + +CREATE FUNCTION eql_v1.ste_vec(val eql_v1_encrypted) + RETURNS eql_v1_encrypted[] + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + BEGIN + RETURN (SELECT eql_v1.ste_vec(val.data)); + END; +$$ LANGUAGE plpgsql; + + +-- DROP FUNCTION IF EXISTS eql_v1.selector(val jsonb); + +CREATE FUNCTION eql_v1.selector(val jsonb) + RETURNS text + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + BEGIN + IF val ? 's' THEN + RETURN val->>'s'; + END IF; + RAISE 'Expected a selector index (s) value in json: %', val; + END; +$$ LANGUAGE plpgsql; + + +-- extracts ste_vec index from an eql_v1_encrypted value +-- DROP FUNCTION IF EXISTS eql_v1.selector(val eql_v1_encrypted); + +CREATE FUNCTION eql_v1.selector(val eql_v1_encrypted) + RETURNS text + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + BEGIN + RETURN (SELECT eql_v1.selector(val.data)); + END; +$$ LANGUAGE plpgsql; + + +-- DROP FUNCTION IF EXISTS eql_v1.is_ste_vec_array(val jsonb); + +CREATE FUNCTION eql_v1.is_ste_vec_array(val jsonb) + RETURNS boolean + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + BEGIN + IF val ? 'a' THEN + RETURN (val->>'a')::boolean; + END IF; + + RETURN false; + END; +$$ LANGUAGE plpgsql; + + +-- extracts ste_vec index from an eql_v1_encrypted value +-- DROP FUNCTION IF EXISTS eql_v1.is_ste_vec_array(val eql_v1_encrypted); + +CREATE FUNCTION eql_v1.is_ste_vec_array(val eql_v1_encrypted) + RETURNS boolean + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + BEGIN + RETURN (SELECT eql_v1.is_ste_vec_array(val.data)); + END; +$$ LANGUAGE plpgsql; + + + +-- Returns true if b is contained in any element of a +-- DROP FUNCTION IF EXISTS eql_v1.ste_vec_contains(a eql_v1_encrypted[], b eql_v1_encrypted); +CREATE FUNCTION eql_v1.ste_vec_contains(a eql_v1_encrypted[], b eql_v1_encrypted) + RETURNS boolean + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + DECLARE + result boolean; + _a eql_v1_encrypted; + BEGIN + + result := false; + + FOR idx IN 1..array_length(a, 1) LOOP + _a := a[idx]; + result := result OR (eql_v1.selector(_a) = eql_v1.selector(b) AND _a = b); + END LOOP; + + RETURN result; + END; +$$ LANGUAGE plpgsql; + + +-- Returns truy if a contains b +-- All values of b must be in a +-- DROP FUNCTION IF EXISTS eql_v1.ste_vec_contains(a eql_v1_encrypted, b eql_v1_encrypted); +CREATE FUNCTION eql_v1.ste_vec_contains(a eql_v1_encrypted, b eql_v1_encrypted) + RETURNS boolean + IMMUTABLE STRICT PARALLEL SAFE +AS $$ + DECLARE + result boolean; + sv_a eql_v1_encrypted[]; + sv_b eql_v1_encrypted[]; + _b eql_v1_encrypted; + BEGIN + + -- jsonb arrays of ste_vec encrypted values + sv_a := eql_v1.ste_vec(a); + sv_b := eql_v1.ste_vec(b); + + -- an empty b is always contained in a + IF array_length(sv_b, 1) IS NULL THEN + RETURN true; + END IF; + + IF array_length(sv_a, 1) IS NULL THEN + RETURN false; + END IF; + + result := true; + + -- for each element of b check if it is in a + FOR idx IN 1..array_length(sv_b, 1) LOOP + _b := sv_b[idx]; + result := result AND eql_v1.ste_vec_contains(sv_a, _b); + END LOOP; + + RETURN result; + END; +$$ LANGUAGE plpgsql; diff --git a/src/ste_vec/functions_test.sql b/src/ste_vec/functions_test.sql new file mode 100644 index 0000000..2b40318 --- /dev/null +++ b/src/ste_vec/functions_test.sql @@ -0,0 +1,92 @@ +\set ON_ERROR_STOP on + +SELECT create_table_with_encrypted(); +SELECT seed_encrypted_json(); + + +DO $$ + DECLARE + e eql_v1_encrypted; + sv eql_v1_encrypted[]; + BEGIN + + SELECT encrypted.e FROM encrypted LIMIT 1 INTO e; + + sv := eql_v1.ste_vec(e); + ASSERT array_length(sv, 1) = 3; + + -- eql_v1_encrypted that IS a ste_vec element + e := get_numeric_ste_vec_10()::eql_v1_encrypted; + + sv := eql_v1.ste_vec(e); + ASSERT array_length(sv, 1) = 3; + + END; +$$ LANGUAGE plpgsql; + + +DO $$ + DECLARE + e eql_v1_encrypted; + sv eql_v1_encrypted[]; + BEGIN + e := '{ "a": 1 }'::jsonb::eql_v1_encrypted; + ASSERT eql_v1.is_ste_vec_array(e); + + + e := '{ "a": 0 }'::jsonb::eql_v1_encrypted; + ASSERT NOT eql_v1.is_ste_vec_array(e); + + e := '{ }'::jsonb::eql_v1_encrypted; + ASSERT NOT eql_v1.is_ste_vec_array(e); + END; +$$ LANGUAGE plpgsql; + + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- eql_v1_encrypted contains itself +-- +-- +DO $$ + DECLARE + a eql_v1_encrypted; + b eql_v1_encrypted; + BEGIN + + a := get_numeric_ste_vec_10()::eql_v1_encrypted; + b := get_numeric_ste_vec_10()::eql_v1_encrypted; + + ASSERT eql_v1.ste_vec_contains(a, b); + ASSERT eql_v1.ste_vec_contains(b, a); + + END; +$$ LANGUAGE plpgsql; + + +-- ------------------------------------------------------------------------ +-- ------------------------------------------------------------------------ +-- +-- eql_v1_encrypted contains a term +-- +-- +DO $$ + DECLARE + a eql_v1_encrypted; + b eql_v1_encrypted; + term eql_v1_encrypted; + BEGIN + + a := get_numeric_ste_vec_10()::eql_v1_encrypted; + b := get_numeric_ste_vec_10()::eql_v1_encrypted; + + -- $.n + term := b->'2517068c0d1f9d4d41d2c666211f785e'; + + ASSERT eql_v1.ste_vec_contains(a, term); + + ASSERT NOT eql_v1.ste_vec_contains(term, a); + END; +$$ LANGUAGE plpgsql; + diff --git a/src/unique/functions.sql b/src/unique/functions.sql index 301dbb7..4d18f67 100644 --- a/src/unique/functions.sql +++ b/src/unique/functions.sql @@ -1,7 +1,8 @@ +-- REQUIRE: src/schema.sql -- REQUIRE: src/unique/types.sql -- extracts unique index from an encrypted column -DROP FUNCTION IF EXISTS eql_v1.unique(val jsonb); +-- DROP FUNCTION IF EXISTS eql_v1.unique(val jsonb); CREATE FUNCTION eql_v1.unique(val jsonb) RETURNS eql_v1.unique_index @@ -17,7 +18,7 @@ $$ LANGUAGE plpgsql; -- extracts unique index from an encrypted column -DROP FUNCTION IF EXISTS eql_v1.unique(val eql_v1_encrypted); +-- DROP FUNCTION IF EXISTS eql_v1.unique(val eql_v1_encrypted); CREATE FUNCTION eql_v1.unique(val eql_v1_encrypted) RETURNS eql_v1.unique_index diff --git a/src/unique/types.sql b/src/unique/types.sql index aa3890a..df496c6 100644 --- a/src/unique/types.sql +++ b/src/unique/types.sql @@ -1,4 +1,4 @@ -- REQUIRE: src/schema.sql -DROP DOMAIN IF EXISTS eql_v1.unique_index; +-- DROP DOMAIN IF EXISTS eql_v1.unique_index; CREATE DOMAIN eql_v1.unique_index AS text; diff --git a/tasks/build.sh b/tasks/build.sh index b861898..7293615 100755 --- a/tasks/build.sh +++ b/tasks/build.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash #MISE description="Build SQL into single release file" #MISE alias="b" -#MISE sources=["sql/*.sql"] +#MISE sources=["src/**/*.sql"] #MISE outputs=["release/cipherstash-encrypt.sql","release/cipherstash-encrypt-uninstall.sql"] #USAGE flag "--version " help="Specify release version of EQL" default="DEV" diff --git a/tasks/test.sh b/tasks/test.sh index 4a45e47..c86909c 100755 --- a/tasks/test.sh +++ b/tasks/test.sh @@ -30,11 +30,7 @@ run_test () { echo '###############################################' echo - - cat $1 | docker exec -i ${container_name} psql --variable ON_ERROR_STOP=1 $connection_url -f- - - } # setup @@ -42,7 +38,10 @@ fail_if_postgres_not_running mise run build --force mise run reset --force --postgres ${POSTGRES_VERSION} +echo '/////////////////////////////////////////////////////////' cat release/cipherstash-encrypt.sql +echo '/////////////////////////////////////////////////////////' + # Install # cat release/cipherstash-encrypt.sql | docker exec -i ${container_name} psql ${connection_url} -f- diff --git a/tasks/uninstall.sql b/tasks/uninstall.sql index d79c4d6..0f35210 100644 --- a/tasks/uninstall.sql +++ b/tasks/uninstall.sql @@ -5,4 +5,6 @@ BEGIN END $$; -DROP SCHEMA IF EXISTS eql_v1 CASCADE; \ No newline at end of file +ALTER TABLE public.eql_v1_configuration DROP CONSTRAINT IF EXISTS eql_v1_configuration_data_check; + +DROP SCHEMA IF EXISTS eql_v1 CASCADE; diff --git a/tests/old/@>_test.sql b/tests/old/@>_test.sql new file mode 100644 index 0000000..1147314 --- /dev/null +++ b/tests/old/@>_test.sql @@ -0,0 +1,98 @@ +\set ON_ERROR_STOP on + +SELECT create_table_with_encrypted(); +SELECT seed_encrypted_json(); + +SELECT * FROM encrypted; + +SELECT e @> '{ + "c": "ciphertext", + "i": { + "t": "encrypted", + "c": "e" + }, + "s": "selector.1", + "t": "term.1" + }'::jsonb::eql_v1_encrypted FROM encrypted; + + +\set ON_ERROR_STOP on + + +SELECT create_table_with_encrypted(); + + +DO $$ + BEGIN + PERFORM assert_result( + 'Fetch ciphertext from encrypted column', + 'SELECT e->>''selector.1'' FROM encrypted;'); + END; +$$ LANGUAGE plpgsql; + + +DO $$ + BEGIN + PERFORM assert_result( + 'Fetch ciphertext from encrypted column', + 'SELECT e->>''selector.1'' FROM encrypted;'); + END; +$$ LANGUAGE plpgsql; + + +DO $$ + BEGIN + PERFORM assert_result( + 'Fetch encrypted using selected', + 'SELECT e->''selector.1'' FROM encrypted;'); + + + PERFORM assert_no_result( + '-> operator only works on top level of encrypted column', + 'SELECT e->''selector.1''->''blah'' FROM encrypted;'); + END; +$$ LANGUAGE plpgsql; + + +SELECT drop_table_with_encrypted(); + +-- DO $$ +-- DECLARE +-- e eql_v1_encrypted; +-- BEGIN +-- e := '{ +-- "c": "ciphertext", +-- "i": { +-- "t": "encrypted", +-- "c": "e" +-- }, +-- "s": "selector.1", +-- "t": "term.1" +-- }'::jsonb::eql_v1_encrypted; + +-- PERFORM eql_v1.log('Encrypted', e::text); + + +-- PERFORM assert_result( +-- 'Fetch ciphertext from encrypted column', +-- format('SELECT e @> %s FROM encrypted;', e)); + +-- END; +-- $$ LANGUAGE plpgsql; + + +-- -- DO $$ +-- -- BEGIN +-- -- PERFORM assert_result( +-- -- 'Fetch encrypted using selected', +-- -- 'SELECT e->''selector.1'' FROM encrypted;'); + + +-- -- PERFORM assert_no_result( +-- -- '-> operator only works on top level of encrypted column', +-- -- 'SELECT e->''selector.1''->''blah'' FROM encrypted;'); +-- -- END; +-- -- $$ LANGUAGE plpgsql; + + +SELECT drop_table_with_encrypted(); \ No newline at end of file diff --git a/tests/old/core.sql b/tests/old/core.sql new file mode 100644 index 0000000..04a5e2c --- /dev/null +++ b/tests/old/core.sql @@ -0,0 +1,219 @@ +\set ON_ERROR_STOP on + +-- Create a table with a plaintext column +DROP TABLE IF EXISTS users; +CREATE TABLE users +( + id bigint GENERATED ALWAYS AS IDENTITY, + name_encrypted eql_v1_encrypted, + PRIMARY KEY(id) +); + + +TRUNCATE TABLE users; + + +INSERT INTO users (name_encrypted) VALUES ( + '{ + "v": 1, + "k": "ct", + "c": "ciphertext", + "i": { + "t": "users", + "c": "name" + }, + "m": [1, 1], + "u": "text", + "o": ["a"] + }'::jsonb +); + +DO $$ + BEGIN + + ASSERT (SELECT EXISTS (SELECT id FROM users WHERE eql_v1.ciphertext(name_encrypted) = 'ciphertext')); + + ASSERT (SELECT EXISTS (SELECT id FROM users WHERE eql_v1.match(name_encrypted) = '{1,1}')); + + ASSERT (SELECT EXISTS (SELECT id FROM users WHERE eql_v1.unique(name_encrypted) = 'text')); + + -- ORE PAYLOAD ABOUT TO CHANGE + -- ASSERT (SELECT EXISTS (SELECT id FROM users WHERE eql_v1.ore_64_8_v1(name_encrypted) = '{a}')); + + END; +$$ LANGUAGE plpgsql; + + +TRUNCATE TABLE users; + +INSERT INTO users DEFAULT VALUES; + +SELECT id FROM users; + +DO $$ + BEGIN + ASSERT (SELECT EXISTS (SELECT id FROM users)); + END; +$$ LANGUAGE plpgsql; + + +-- ----------------------------------------------- +--- +-- eql_v1_encrypted type +-- Validate configuration schema +-- Try and insert many invalid configurations +-- None should exist +-- +-- ----------------------------------------------- +TRUNCATE TABLE users; + +\set ON_ERROR_STOP off +\set ON_ERROR_ROLLBACK on + +DO $$ + BEGIN + RAISE NOTICE 'eql_v1_encrypted constraint tests: 10 errors expected here'; + END; +$$ LANGUAGE plpgsql; + + +-- no version +INSERT INTO users (name_encrypted) VALUES ( + '{ + "v": 1, + "k": "ct", + "c": "ciphertext", + "i": { + "t": "users", + "c": "name" + } + }'::jsonb +); + +-- no ident details +INSERT INTO users (name_encrypted) VALUES ( + '{ + "v": 1, + "k": "ct", + "c": "ciphertext" + }'::jsonb +); + +-- no kind +INSERT INTO users (name_encrypted) VALUES ( + '{ + "v": 1, + "c": "ciphertext", + "i": { + "t": "users", + "c": "name" + } + }'::jsonb +); + + + +-- bad kind +INSERT INTO users (name_encrypted) VALUES ( + '{ + "v": 1, + "k": "vtha", + "c": "ciphertext", + "i": { + "t": "users", + "c": "name" + } + }'::jsonb +); + +-- pt +INSERT INTO users (name_encrypted) VALUES ( + '{ + "v": 1, + "k": "pt", + "i": { + "t": "users", + "c": "name" + } + }'::jsonb +); + +--pt with ciphertext +INSERT INTO users (name_encrypted) VALUES ( + '{ + "v": 1, + "k": "pt", + "c": "ciphertext", + "i": { + "t": "users", + "c": "name" + } + }'::jsonb +); + +-- ct without ciphertext +INSERT INTO users (name_encrypted) VALUES ( + '{ + "v": 1, + "k": "ct", + "i": { + "t": "users", + "c": "name" + } + }'::jsonb +); + + +-- ct with plaintext +INSERT INTO users (name_encrypted) VALUES ( + '{ + "v": 1, + "k": "ct", + "p": "plaintext", + "i": { + "t": "users", + "c": "name" + } + }'::jsonb +); + + +-- ciphertext without ct +INSERT INTO users (name_encrypted) VALUES ( + '{ + "v": 1, + "c": "ciphertext", + "i": { + "t": "users", + "c": "name" + } + }'::jsonb +); + +-- ciphertext with invalid q +INSERT INTO users (name_encrypted) VALUES ( + '{ + "v": 1, + "c": "ciphertext", + "i": { + "t": "users", + "c": "name" + }, + "q": "invalid" + }'::jsonb +); + +-- Nothing should be in the DB +DO $$ + BEGIN + ASSERT (SELECT NOT EXISTS (SELECT * FROM users c)); + END; +$$ LANGUAGE plpgsql; + + +\set ON_ERROR_STOP on +\set ON_ERROR_ROLLBACK off + + + + diff --git a/tests/test_helpers.sql b/tests/test_helpers.sql index 5707348..eb806c0 100644 --- a/tests/test_helpers.sql +++ b/tests/test_helpers.sql @@ -25,25 +25,249 @@ AS $$ END; $$ LANGUAGE plpgsql; +-- +-- Creates a table with an encrypted column for testing +-- +DROP FUNCTION IF EXISTS truncate_table_with_encrypted(); +CREATE FUNCTION truncate_table_with_encrypted() + RETURNS void +AS $$ + BEGIN + TRUNCATE encrypted; + END; +$$ LANGUAGE plpgsql; + + -DROP FUNCTION IF EXISTS get_high_ore(); -CREATE FUNCTION get_high_ore() +DROP FUNCTION IF EXISTS get_numeric_ste_vec_10(); +CREATE FUNCTION get_numeric_ste_vec_10() RETURNS jsonb AS $$ BEGIN - RETURN '{"o": ["1212121212125932e28282d03415e7714fccd69eb7eb476c70743e485e20331f59cbc1c848dcdeda716f351eb20588c406a7df5fb8917ebf816739aa1414ac3b8498e493bf0badea5c9fdb3cc34da8b152b995957591880c523beb1d3f12487c38d18f62dd26209a727674e5a5fe3a3e3037860839afd8011f94b49eaa5fa5a60e1e2adccde4185a7d6c7f83088500b677f897d4ffc276016d614708488f407c01bd3ccf2be653269062cb97f8945a621d049277d19b1c248611f25d047038928d2efeb4323c402af4c19288c7b36911dc06639af5bb34367519b66c1f525bbd3828c12067c9c579aeeb4fb3ae0918125dc1dad5fd518019a5ae67894ce1a7f7bed1a591ba8edda2fdf4cd403761fd981fb1ea5eb0bf806f919350ee60cac16d0a39a491a4d79301781f95ea3870aea82e9946053537360b2fb415b18b61aed0af81d461ad6b923f10c0df79daddc4e279ff543a282bb3a37f9fa03238348b3dac51a453b04bced1f5bd318ddd829bdfe5f37abdbeda730e21441b818302f3c5c2c4d5657accfca4c53d7a80eb3db43946d38965be5f796b"]}'::jsonb; -END; + RETURN '{ + "sv": [ + { + "b": "7b4ffe5d60e4e4300dc3e28d9c300c87", + "c": "mBbLGB9xHAGzLvUj-`@Wmf=IhD87n7r3ir3n!Sk6AKir_YawR=0c>pk(OydB;ntIEXK~c>V&4>)rNkfF eql_v1_encrypted[] +-- a [ +-- 1 +-- ] +-- + +-- ORIGINAL $.a encoding +-- { +-- "b": "8258356162d2415d55244abf49e40da3", +-- "c": "mBbL9j9(QoRD)R+z?=Fvn#=FR9iI)K4Nzk-ea`~#Lx@wBSDPSmkp-h+tNEHoo@T@#vwh?Ejvk%78G}b+je+xufQA5mSwHSid)iEOkg@>mpuh", +-- "s": "f510853730e1c3dbd31b86963f029dd5" +-- }, + +DROP FUNCTION IF EXISTS get_array_ste_vec(); +CREATE FUNCTION get_array_ste_vec() + RETURNS jsonb +AS $$ + BEGIN + RETURN '{ + "sv": [ + { + "b": "7b4ffe5d60e4e4300dc3e28d9c300c87", + "c": "mBbL9j9(QoRD)R+z?=Fvn#=FRIg79JJM`MCq+nE0*U^ca-cViL884d-TInfY&E9HW@X>!U&lkYne2!EecKG8xwLYb0X#y7|05rrPvwh?Ejvk%78G}b+je+xufQA5mSwHSid)iEOkg@>mpuh", + "s": "bca213de9ccce676fa849ff9c4807963" + }, + + + + { + + "c": "mBbL9j9(QoRD)R+z?=Fvn#=FR6Z{(4c^$CD^7q>z{xl^%5S4=m#2~YMW7y15TC<^_oBO-6ni$TotY#2~YMz{xl^%5S4=m#2~YMW7y15TC<^_oBO-6ni$TotY#2~YMoG#B*Y-IedG9!9-X`ygGXYGf%A%hh5&w9KkiR^+DvtjvHG(8jjwtt=6Wr{!%WJ?vt(v&0~?edG9!9-X`ygGXYGf%A%hh5&w9KkiR^+DvtjvHH*Pjq@SFR7iRajU#?{(K%x=#2^Zs|F~fm*&w!wSjZQIUaj-XX01=c??f8cq8*Vf?zEu5", + "ocv": "af96e1dabbec581f36d71e3a48ffb427f54832851b4fefa6989887ccaf7e038f66f8cb40e6959458", + "s": "a7cea93975ed8c01f861ccb6bd082784" + }, + { + "c": "mBbM0#UZON2jQ3@LiWcvns2Yf6y3L;hykEh`}*fX#aF;n*=>+*o5Uarod39C7TF-SiCD-NgkG)l%Vw=l!tX>H*PkiFH&De7C+d}sDugsc?JuI*>$AsG83nsXvrND0-(S", + "b": "7b4ffe5d60e4e4300dc3e28d9c300c87", + "s": "bca213de9ccce676fa849ff9c4807963" + }, + { + "c": "mBbK0Cob5dQ5Jki69vRd75f9k8Rn)lVSgZ9Q3jQYu)}sv8};==6AExb8MwqC=TCnxQeQ_FKiJQxgbDw71`CJTb)@Vv6Q`bN$sH2{puh", + "ocv": "af96e1dabbec5913707844664eb160923982fdec75bda4bcd063e26b4254a9f334ce7ebc2612713c", + "s": "a7cea93975ed8c01f861ccb6bd082784" + }, + { + "c": "mBbK0Cob5dQ5Jki69vRd75f9k6yc;BV`COqamPOX6P`g5TMr)AeZ(N=Pk%%2`Uq=={*w3hh3IBNp3y0Ztr0g;ir=DoZ9TNhezy", + "ocf": "b0c13d4a4a9ffcb2ef8629d60d5e32db453fad8792b2450d02f37ec5fe207b42da30093fd14c4975c9b192ecbf939b2d5a56a7ae2db1254e6532aa7569971462", + "s": "2517068c0d1f9d4d41d2c666211f785e" + } + ] + }'::jsonb; + END; +$$ LANGUAGE plpgsql; -- -- @@ -51,6 +275,13 @@ $$ LANGUAGE plpgsql; -- -- Creates a table with an encrypted column for testing -- +-- JSON -- '{"hello": "world", "n": 42}' +-- +-- Paths +-- $ -> bca213de9ccce676fa849ff9c4807963 +-- $.hello -> a7cea93975ed8c01f861ccb6bd082784 +-- $.n -> 2517068c0d1f9d4d41d2c666211f785e +-- -- -- -- -- DROP FUNCTION IF EXISTS create_encrypted_json(integer); @@ -64,6 +295,8 @@ AS $$ stop integer; random_key text; random_val text; + sv jsonb; + ore_term jsonb; BEGIN start := (10 * id); @@ -73,6 +306,22 @@ AS $$ select substr(md5(random()::text), 1, 25) INTO random_key; select substr(md5(random()::text), 1, 25) INTO random_val; + CASE id + WHEN 1 THEN + sv := get_numeric_ste_vec_10(); + WHEN 2 THEN + sv := get_numeric_ste_vec_20(); + WHEN 3 THEN + sv := get_numeric_ste_vec_30(); + ELSE + sv := get_numeric_ste_vec_42(); + END CASE; + + + SELECT ore.e FROM ore WHERE ore.id = start INTO ore_term; + + -- PERFORM eql_v1.log('ore_term: ', ore_term::text); + s := format( '{ "%s": "%s", @@ -82,23 +331,80 @@ AS $$ "c": "e" }, "u": "unique.%s", - "m": %s, - "o": ["12121212121259bfe28282d03415e7714fccd69eb7eb476c70743e485e20331f59cbc1c848dcdeda716f351eb20588c406a7df5fb8917ebf816739aa1414ac3b8498e493bf0badea5c9fdb3cc34da8b152b995957591880c523beb1d3f12487c38d18f62dd26209a727674e5a5fe3a3e3037860839afd801ff4a28b714e4cde8df10625dce72602fdbdcc53d515857f1119f5912804ce09c6cf6c2d37393a27a465134523b512664582f834e15003b7216cb668480bc3e7d1c069f2572ece7c848b9eb9a28b4e62bfc2b97c93e61b2054154e621c5bbb7bed37de3d7c343bd3dbcf7b4af20128c961351bf55910a855f08a8587c2059a5f05ca8d7a082e695b3dd4ff3ce86694d4fe98972220eea1ab90f5de493ef3a502b74a569f103ee2897ebc9ae9b16a17e7be67415ee830519beb3058ffc1c1eb0e574d66c8b365919f27eb00aa7bce475d7bdaad4ed800f8fc3d626e0eb842e312b0cc22a1ccf89847ebb2cd0a6e18aec21bd2deeec1c47301fc687f7f764bb882b50f553c246a6da5816b78b3530119ea68b08a8403a90e063e58502670563bd4d"], - "j": [ - { - "c": "ciphertext.%s", - "s": "selector.%s", - "t": "term.%s" - } - ] + "b": "blake3.%s", + "m": %s }', random_key, random_val, - id, m, id, id, id); + id, id, m); + + s := s::jsonb || sv || ore_term; + + -- PERFORM eql_v1.log('json: %', s); RETURN s::eql_v1_encrypted; + END; +$$ LANGUAGE plpgsql; + + +DROP FUNCTION IF EXISTS create_encrypted_json(integer, VARIADIC indexes text[]); +CREATE FUNCTION create_encrypted_json(id integer, VARIADIC indexes text[]) + RETURNS eql_v1_encrypted +AS $$ + DECLARE + j jsonb; + BEGIN + j := create_encrypted_json(id); + + j := ( + SELECT jsonb_object_agg(key, value) + FROM jsonb_each(j) + WHERE key = ANY(indexes) + ); + + RETURN j::eql_v1_encrypted; + + END; +$$ LANGUAGE plpgsql; + + +DROP FUNCTION IF EXISTS create_encrypted_json(VARIADIC indexes text[]); +CREATE FUNCTION create_encrypted_json(VARIADIC indexes text[]) + RETURNS eql_v1_encrypted +AS $$ + DECLARE + default_indexes text[]; + j jsonb; + BEGIN + + default_indexes := ARRAY['c', 'i', 'v']; + + j := create_encrypted_json(1); + + j := ( + SELECT jsonb_object_agg(key, value) + FROM jsonb_each(j) + WHERE key = ANY(indexes || default_indexes) + ); + + RETURN j::eql_v1_encrypted; + + END; +$$ LANGUAGE plpgsql; + +DROP FUNCTION IF EXISTS create_encrypted_ore_json(val integer); +CREATE FUNCTION create_encrypted_ore_json(val integer) + RETURNS eql_v1_encrypted +AS $$ + DECLARE + e eql_v1_encrypted; + ore_term jsonb; + BEGIN + EXECUTE format('SELECT ore.e FROM ore WHERE id = %s', val) INTO ore_term; + e := create_encrypted_json('o')::jsonb || ore_term; + RETURN e::eql_v1_encrypted; END; $$ LANGUAGE plpgsql; @@ -107,8 +413,13 @@ DROP FUNCTION IF EXISTS create_encrypted_json(); CREATE FUNCTION create_encrypted_json() RETURNS eql_v1_encrypted AS $$ + DECLARE + id integer; + j jsonb; BEGIN - RETURN (create_encrypted_json(1)); + id := trunc(random() * 1000 + 1); + j := create_encrypted_json(id); + RETURN j::eql_v1_encrypted; END; $$ LANGUAGE plpgsql; @@ -124,13 +435,14 @@ $$ LANGUAGE plpgsql; -- --- Creates a table with an encrypted column for testing +-- Truncates and creates base test data -- DROP FUNCTION IF EXISTS seed_encrypted_json(); CREATE FUNCTION seed_encrypted_json() RETURNS void AS $$ BEGIN + PERFORM truncate_table_with_encrypted(); PERFORM seed_encrypted(create_encrypted_json(1)); PERFORM seed_encrypted(create_encrypted_json(2)); PERFORM seed_encrypted(create_encrypted_json(3)); @@ -179,7 +491,7 @@ AS $$ EXECUTE sql into result; if result IS NULL THEN - RAISE NOTICE 'ASSERTION FAILED'; + RAISE NOTICE 'ASSERT RESULT FAILED'; RAISE NOTICE '%', regexp_replace(sql, '^\s+|\s*$', '', 'g'); ASSERT false; END IF; @@ -188,6 +500,31 @@ AS $$ $$ LANGUAGE plpgsql; +-- +-- Assert the the provided SQL statement returns a non-null result +-- +DROP FUNCTION IF EXISTS assert_result(describe text, sql text, result text); + +CREATE FUNCTION assert_result(describe text, sql text, expected text) + RETURNS void +AS $$ + DECLARE + result text; + BEGIN + RAISE NOTICE '%', describe; + EXECUTE sql into result; + + if result <> expected THEN + RAISE NOTICE 'ASSERT EXPECTED RESULT FAILED'; + RAISE NOTICE 'Expected: %', expected; + RAISE NOTICE 'Result: %', result; + RAISE NOTICE '%', regexp_replace(sql, '^\s+|\s*$', '', 'g'); + ASSERT false; + END IF; + + END; +$$ LANGUAGE plpgsql; + -- -- Assert the the provided SQL statement returns a non-null result -- @@ -203,7 +540,7 @@ AS $$ EXECUTE sql into result_id; IF result_id <> id THEN - RAISE NOTICE 'ASSERTION FAILED'; + RAISE NOTICE 'ASSERT ID FAILED'; RAISE NOTICE 'Expected row with id % but returned %', id, result_id; RAISE NOTICE '%', regexp_replace(sql, '^\s+|\s*$', '', 'g'); ASSERT false; @@ -228,7 +565,7 @@ AS $$ EXECUTE sql into result; IF result IS NOT NULL THEN - RAISE NOTICE 'ASSERTION FAILED'; + RAISE NOTICE 'ASSERT NO RESULT FAILED'; RAISE NOTICE '%', regexp_replace(sql, '^\s+|\s*$', '', 'g'); ASSERT false; END IF; @@ -257,7 +594,7 @@ AS $$ EXECUTE format('SELECT COUNT(*) FROM (%s) as q', sql) INTO result; if result <> expected THEN - RAISE NOTICE 'ASSERTION FAILED'; + RAISE NOTICE 'ASSERT COUNT FAILED'; RAISE NOTICE 'Expected % rows and returned %', expected, result; RAISE NOTICE '%', regexp_replace(sql, '^\s+|\s*$', '', 'g'); ASSERT false; @@ -281,7 +618,7 @@ AS $$ BEGIN EXECUTE sql; - RAISE NOTICE 'ASSERTION FAILED'; + RAISE NOTICE 'ASSERT EXCEPTION FAILED'; RAISE NOTICE 'EXPECTED STATEMENT TO RAISE EXCEPTION'; RAISE NOTICE '%', regexp_replace(sql, '^\s+|\s*$', '', 'g'); ASSERT false;