From cfa5162ab48e6decd6481364b201a1af0103f5e8 Mon Sep 17 00:00:00 2001 From: fimac Date: Wed, 23 Oct 2024 07:55:42 +1100 Subject: [PATCH 1/2] copy over custom types --- .../postgresql/install.sql | 317 ++++++++++++++++++ .../postgresql/uninstall.sql | 20 ++ 2 files changed, 337 insertions(+) create mode 100644 sql/database-extensions/postgresql/install.sql create mode 100644 sql/database-extensions/postgresql/uninstall.sql diff --git a/sql/database-extensions/postgresql/install.sql b/sql/database-extensions/postgresql/install.sql new file mode 100644 index 000000000..9f2a6617a --- /dev/null +++ b/sql/database-extensions/postgresql/install.sql @@ -0,0 +1,317 @@ +CREATE EXTENSION IF NOT EXISTS pgcrypto; + +CREATE TYPE ore_64_8_v1_term AS ( + bytes bytea +); + +CREATE TYPE ore_64_8_v1 AS ( + terms ore_64_8_v1_term[] +); + +CREATE OR REPLACE FUNCTION compare_ore_64_8_v1_term(a ore_64_8_v1_term, b ore_64_8_v1_term) returns integer AS $$ + DECLARE + eq boolean := true; + unequal_block smallint := 0; + hash_key bytea; + target_block bytea; + + left_block_size CONSTANT smallint := 16; + right_block_size CONSTANT smallint := 32; + right_offset CONSTANT smallint := 136; -- 8 * 17 + + indicator smallint := 0; + BEGIN + IF a IS NULL AND b IS NULL THEN + RETURN 0; + END IF; + + IF a IS NULL THEN + RETURN -1; + END IF; + + IF b IS NULL THEN + RETURN 1; + END IF; + + IF bit_length(a.bytes) != bit_length(b.bytes) THEN + RAISE EXCEPTION 'Ciphertexts are different lengths'; + END IF; + + FOR block IN 0..7 LOOP + -- Compare each PRP (byte from the first 8 bytes) and PRF block (8 byte + -- chunks of the rest of the value). + -- NOTE: + -- * Substr is ordinally indexed (hence 1 and not 0, and 9 and not 8). + -- * We are not worrying about timing attacks here; don't fret about + -- the OR or !=. + IF + substr(a.bytes, 1 + block, 1) != substr(b.bytes, 1 + block, 1) + OR substr(a.bytes, 9 + left_block_size * block, left_block_size) != substr(b.bytes, 9 + left_block_size * BLOCK, left_block_size) + THEN + -- set the first unequal block we find + IF eq THEN + unequal_block := block; + END IF; + eq = false; + END IF; + END LOOP; + + IF eq THEN + RETURN 0::integer; + END IF; + + -- Hash key is the IV from the right CT of b + hash_key := substr(b.bytes, right_offset + 1, 16); + + -- first right block is at right offset + nonce_size (ordinally indexed) + target_block := substr(b.bytes, right_offset + 17 + (unequal_block * right_block_size), right_block_size); + + indicator := ( + get_bit( + encrypt( + substr(a.bytes, 9 + (left_block_size * unequal_block), left_block_size), + hash_key, + 'aes-ecb' + ), + 0 + ) + get_bit(target_block, get_byte(a.bytes, unequal_block))) % 2; + + IF indicator = 1 THEN + RETURN 1::integer; + ELSE + RETURN -1::integer; + END IF; + END; +$$ LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION ore_64_8_v1_term_eq(a ore_64_8_v1_term, b ore_64_8_v1_term) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1_term(a, b) = 0 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_term_neq(a ore_64_8_v1_term, b ore_64_8_v1_term) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1_term(a, b) <> 0 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_term_lt(a ore_64_8_v1_term, b ore_64_8_v1_term) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1_term(a, b) = -1 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_term_lte(a ore_64_8_v1_term, b ore_64_8_v1_term) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1_term(a, b) != 1 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_term_gt(a ore_64_8_v1_term, b ore_64_8_v1_term) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1_term(a, b) = 1 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_term_gte(a ore_64_8_v1_term, b ore_64_8_v1_term) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1_term(a, b) != -1 +$$ LANGUAGE SQL; + +CREATE OPERATOR = ( + PROCEDURE="ore_64_8_v1_term_eq", + LEFTARG=ore_64_8_v1_term, + RIGHTARG=ore_64_8_v1_term, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +CREATE OPERATOR <> ( + PROCEDURE="ore_64_8_v1_term_neq", + LEFTARG=ore_64_8_v1_term, + RIGHTARG=ore_64_8_v1_term, + NEGATOR = =, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +CREATE OPERATOR > ( + PROCEDURE="ore_64_8_v1_term_gt", + LEFTARG=ore_64_8_v1_term, + RIGHTARG=ore_64_8_v1_term, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR < ( + PROCEDURE="ore_64_8_v1_term_lt", + LEFTARG=ore_64_8_v1_term, + RIGHTARG=ore_64_8_v1_term, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR <= ( + PROCEDURE="ore_64_8_v1_term_lte", + LEFTARG=ore_64_8_v1_term, + RIGHTARG=ore_64_8_v1_term, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel +); + +CREATE OPERATOR >= ( + PROCEDURE="ore_64_8_v1_term_gte", + LEFTARG=ore_64_8_v1_term, + RIGHTARG=ore_64_8_v1_term, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel +); + +CREATE OPERATOR FAMILY ore_64_8_v1_term_btree_ops USING btree; +CREATE OPERATOR CLASS ore_64_8_v1_term_btree_ops DEFAULT FOR TYPE ore_64_8_v1_term USING btree FAMILY ore_64_8_v1_term_btree_ops AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 compare_ore_64_8_v1_term(a ore_64_8_v1_term, b ore_64_8_v1_term); + +-- Compare the "head" of each array and recurse if necessary +-- This function assumes an empty string is "less than" everything else +-- so if a is empty we return -1, if be is empty and a isn't, we return 1. +-- If both are empty we return 0. This cases probably isn't necessary as equality +-- 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. +CREATE OR REPLACE FUNCTION compare_ore_array(a ore_64_8_v1_term[], b ore_64_8_v1_term[]) returns integer AS $$ + DECLARE + cmp_result integer; + BEGIN + IF (array_length(a, 1) = 0 OR a IS NULL) AND (array_length(b, 1) = 0 OR b IS NULL) THEN + RETURN 0; + END IF; + IF array_length(a, 1) = 0 OR a IS NULL THEN + RETURN -1; + END IF; + IF array_length(b, 1) = 0 OR a IS NULL THEN + RETURN 1; + END IF; + + cmp_result := compare_ore_64_8_v1_term(a[1], b[1]); + IF cmp_result = 0 THEN + -- Removes the first element in the array, and calls this fn again to compare the next element/s in the array. + RETURN compare_ore_array(a[2:array_length(a,1)], b[2:array_length(b,1)]); + END IF; + + RETURN cmp_result; + END +$$ LANGUAGE plpgsql; + +-- This function uses lexicographic comparison +CREATE OR REPLACE FUNCTION compare_ore_64_8_v1(a ore_64_8_v1, b ore_64_8_v1) returns integer AS $$ + DECLARE + cmp_result integer; + BEGIN + -- Recursively compare blocks bailing as soon as we can make a decision + RETURN compare_ore_array(a.terms, b.terms); + END +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_eq(a ore_64_8_v1, b ore_64_8_v1) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1(a, b) = 0 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_neq(a ore_64_8_v1, b ore_64_8_v1) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1(a, b) <> 0 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_lt(a ore_64_8_v1, b ore_64_8_v1) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1(a, b) = -1 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_lte(a ore_64_8_v1, b ore_64_8_v1) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1(a, b) != 1 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_gt(a ore_64_8_v1, b ore_64_8_v1) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1(a, b) = 1 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_gte(a ore_64_8_v1, b ore_64_8_v1) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1(a, b) != -1 +$$ LANGUAGE SQL; + +CREATE OPERATOR = ( + PROCEDURE="ore_64_8_v1_eq", + LEFTARG=ore_64_8_v1, + RIGHTARG=ore_64_8_v1, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +CREATE OPERATOR <> ( + PROCEDURE="ore_64_8_v1_neq", + LEFTARG=ore_64_8_v1, + RIGHTARG=ore_64_8_v1, + NEGATOR = =, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +CREATE OPERATOR > ( + PROCEDURE="ore_64_8_v1_gt", + LEFTARG=ore_64_8_v1, + RIGHTARG=ore_64_8_v1, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR < ( + PROCEDURE="ore_64_8_v1_lt", + LEFTARG=ore_64_8_v1, + RIGHTARG=ore_64_8_v1, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR <= ( + PROCEDURE="ore_64_8_v1_lte", + LEFTARG=ore_64_8_v1, + RIGHTARG=ore_64_8_v1, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel +); + +CREATE OPERATOR >= ( + PROCEDURE="ore_64_8_v1_gte", + LEFTARG=ore_64_8_v1, + RIGHTARG=ore_64_8_v1, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel +); + +CREATE OPERATOR FAMILY ore_64_8_v1_btree_ops USING btree; +CREATE OPERATOR CLASS ore_64_8_v1_btree_ops DEFAULT FOR TYPE ore_64_8_v1 USING btree FAMILY ore_64_8_v1_btree_ops AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 compare_ore_64_8_v1(a ore_64_8_v1, b ore_64_8_v1); diff --git a/sql/database-extensions/postgresql/uninstall.sql b/sql/database-extensions/postgresql/uninstall.sql new file mode 100644 index 000000000..453ae68bc --- /dev/null +++ b/sql/database-extensions/postgresql/uninstall.sql @@ -0,0 +1,20 @@ +-- TODO: what happens if we try to uninstall a type which is in use? +DROP OPERATOR IF EXISTS = (ore_64_8_v1_term, ore_64_8_v1_term) CASCADE; +DROP OPERATOR IF EXISTS <> (ore_64_8_v1_term, ore_64_8_v1_term) CASCADE; +DROP OPERATOR IF EXISTS > (ore_64_8_v1_term, ore_64_8_v1_term) CASCADE; +DROP OPERATOR IF EXISTS < (ore_64_8_v1_term, ore_64_8_v1_term) CASCADE; +DROP OPERATOR IF EXISTS <= (ore_64_8_v1_term, ore_64_8_v1_term) CASCADE; +DROP OPERATOR IF EXISTS >= (ore_64_8_v1_term, ore_64_8_v1_term) CASCADE; +DROP OPERATOR CLASS IF EXISTS ore_64_8_v1_term_btree_ops USING btree CASCADE; +DROP OPERATOR FAMILY IF EXISTS ore_64_8_v1_term_btree_ops USING btree CASCADE; +DROP TYPE IF EXISTS ore_64_8_v1_term CASCADE; + +DROP OPERATOR IF EXISTS = (ore_64_8_v1, ore_64_8_v1) CASCADE; +DROP OPERATOR IF EXISTS <> (ore_64_8_v1, ore_64_8_v1) CASCADE; +DROP OPERATOR IF EXISTS > (ore_64_8_v1, ore_64_8_v1) CASCADE; +DROP OPERATOR IF EXISTS < (ore_64_8_v1, ore_64_8_v1) CASCADE; +DROP OPERATOR IF EXISTS <= (ore_64_8_v1, ore_64_8_v1) CASCADE; +DROP OPERATOR IF EXISTS >= (ore_64_8_v1, ore_64_8_v1) CASCADE; +DROP OPERATOR CLASS IF EXISTS ore_64_8_v1_btree_ops USING btree CASCADE; +DROP OPERATOR FAMILY IF EXISTS ore_64_8_v1_btree_ops USING btree CASCADE; +DROP TYPE IF EXISTS ore_64_8_v1 CASCADE; From 556c2359f9e3d33d63cf67aa0c969f6fa4e63e02 Mon Sep 17 00:00:00 2001 From: fimac Date: Wed, 23 Oct 2024 07:57:30 +1100 Subject: [PATCH 2/2] update just file to cat custom types sql --- justfile | 2 +- languages/go/xorm/migrations.go | 16 -- release/cipherstash-encrypt-dsl.sql | 317 ++++++++++++++++++++++++++++ 3 files changed, 318 insertions(+), 17 deletions(-) diff --git a/justfile b/justfile index 9ce3537eb..8d1013193 100644 --- a/justfile +++ b/justfile @@ -27,7 +27,7 @@ build: #!/usr/bin/env bash set -euxo pipefail - cat sql/dsl-core.sql sql/dsl-config-schema.sql sql/dsl-config-functions.sql sql/dsl-encryptindex.sql > release/cipherstash-encrypt-dsl.sql + cat sql/database-extensions/postgresql/install.sql sql/dsl-core.sql sql/dsl-config-schema.sql sql/dsl-config-functions.sql sql/dsl-encryptindex.sql > release/cipherstash-encrypt-dsl.sql psql: diff --git a/languages/go/xorm/migrations.go b/languages/go/xorm/migrations.go index a0b5c2d41..09245854d 100644 --- a/languages/go/xorm/migrations.go +++ b/languages/go/xorm/migrations.go @@ -8,26 +8,10 @@ import ( func InstallEql(engine *sql.DB) { log.Println("Start installing custom types and function") - installCsCustomTypes(engine) installDsl(engine) } -// These are our base custom types and functions for ore search. -func installCsCustomTypes(engine *sql.DB) { - path := "./custom-types.sql" - sql, err := os.ReadFile(path) - if err != nil { - log.Fatalf("Failed to read custom types SQL file: %v", err) - } - - _, err = engine.Exec(string(sql)) - if err != nil { - log.Fatalf("Failed to execute SQL query: %v", err) - } - log.Println("Custom types installed!") -} - // Installing EQL func installDsl(engine *sql.DB) { path := "../../../release/cipherstash-encrypt-dsl.sql" diff --git a/release/cipherstash-encrypt-dsl.sql b/release/cipherstash-encrypt-dsl.sql index 3655cfb58..4864adc29 100644 --- a/release/cipherstash-encrypt-dsl.sql +++ b/release/cipherstash-encrypt-dsl.sql @@ -1,3 +1,320 @@ +CREATE EXTENSION IF NOT EXISTS pgcrypto; + +CREATE TYPE ore_64_8_v1_term AS ( + bytes bytea +); + +CREATE TYPE ore_64_8_v1 AS ( + terms ore_64_8_v1_term[] +); + +CREATE OR REPLACE FUNCTION compare_ore_64_8_v1_term(a ore_64_8_v1_term, b ore_64_8_v1_term) returns integer AS $$ + DECLARE + eq boolean := true; + unequal_block smallint := 0; + hash_key bytea; + target_block bytea; + + left_block_size CONSTANT smallint := 16; + right_block_size CONSTANT smallint := 32; + right_offset CONSTANT smallint := 136; -- 8 * 17 + + indicator smallint := 0; + BEGIN + IF a IS NULL AND b IS NULL THEN + RETURN 0; + END IF; + + IF a IS NULL THEN + RETURN -1; + END IF; + + IF b IS NULL THEN + RETURN 1; + END IF; + + IF bit_length(a.bytes) != bit_length(b.bytes) THEN + RAISE EXCEPTION 'Ciphertexts are different lengths'; + END IF; + + FOR block IN 0..7 LOOP + -- Compare each PRP (byte from the first 8 bytes) and PRF block (8 byte + -- chunks of the rest of the value). + -- NOTE: + -- * Substr is ordinally indexed (hence 1 and not 0, and 9 and not 8). + -- * We are not worrying about timing attacks here; don't fret about + -- the OR or !=. + IF + substr(a.bytes, 1 + block, 1) != substr(b.bytes, 1 + block, 1) + OR substr(a.bytes, 9 + left_block_size * block, left_block_size) != substr(b.bytes, 9 + left_block_size * BLOCK, left_block_size) + THEN + -- set the first unequal block we find + IF eq THEN + unequal_block := block; + END IF; + eq = false; + END IF; + END LOOP; + + IF eq THEN + RETURN 0::integer; + END IF; + + -- Hash key is the IV from the right CT of b + hash_key := substr(b.bytes, right_offset + 1, 16); + + -- first right block is at right offset + nonce_size (ordinally indexed) + target_block := substr(b.bytes, right_offset + 17 + (unequal_block * right_block_size), right_block_size); + + indicator := ( + get_bit( + encrypt( + substr(a.bytes, 9 + (left_block_size * unequal_block), left_block_size), + hash_key, + 'aes-ecb' + ), + 0 + ) + get_bit(target_block, get_byte(a.bytes, unequal_block))) % 2; + + IF indicator = 1 THEN + RETURN 1::integer; + ELSE + RETURN -1::integer; + END IF; + END; +$$ LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION ore_64_8_v1_term_eq(a ore_64_8_v1_term, b ore_64_8_v1_term) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1_term(a, b) = 0 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_term_neq(a ore_64_8_v1_term, b ore_64_8_v1_term) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1_term(a, b) <> 0 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_term_lt(a ore_64_8_v1_term, b ore_64_8_v1_term) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1_term(a, b) = -1 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_term_lte(a ore_64_8_v1_term, b ore_64_8_v1_term) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1_term(a, b) != 1 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_term_gt(a ore_64_8_v1_term, b ore_64_8_v1_term) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1_term(a, b) = 1 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_term_gte(a ore_64_8_v1_term, b ore_64_8_v1_term) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1_term(a, b) != -1 +$$ LANGUAGE SQL; + +CREATE OPERATOR = ( + PROCEDURE="ore_64_8_v1_term_eq", + LEFTARG=ore_64_8_v1_term, + RIGHTARG=ore_64_8_v1_term, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +CREATE OPERATOR <> ( + PROCEDURE="ore_64_8_v1_term_neq", + LEFTARG=ore_64_8_v1_term, + RIGHTARG=ore_64_8_v1_term, + NEGATOR = =, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +CREATE OPERATOR > ( + PROCEDURE="ore_64_8_v1_term_gt", + LEFTARG=ore_64_8_v1_term, + RIGHTARG=ore_64_8_v1_term, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR < ( + PROCEDURE="ore_64_8_v1_term_lt", + LEFTARG=ore_64_8_v1_term, + RIGHTARG=ore_64_8_v1_term, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR <= ( + PROCEDURE="ore_64_8_v1_term_lte", + LEFTARG=ore_64_8_v1_term, + RIGHTARG=ore_64_8_v1_term, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel +); + +CREATE OPERATOR >= ( + PROCEDURE="ore_64_8_v1_term_gte", + LEFTARG=ore_64_8_v1_term, + RIGHTARG=ore_64_8_v1_term, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel +); + +CREATE OPERATOR FAMILY ore_64_8_v1_term_btree_ops USING btree; +CREATE OPERATOR CLASS ore_64_8_v1_term_btree_ops DEFAULT FOR TYPE ore_64_8_v1_term USING btree FAMILY ore_64_8_v1_term_btree_ops AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 compare_ore_64_8_v1_term(a ore_64_8_v1_term, b ore_64_8_v1_term); + +-- Compare the "head" of each array and recurse if necessary +-- This function assumes an empty string is "less than" everything else +-- so if a is empty we return -1, if be is empty and a isn't, we return 1. +-- If both are empty we return 0. This cases probably isn't necessary as equality +-- 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. +CREATE OR REPLACE FUNCTION compare_ore_array(a ore_64_8_v1_term[], b ore_64_8_v1_term[]) returns integer AS $$ + DECLARE + cmp_result integer; + BEGIN + IF (array_length(a, 1) = 0 OR a IS NULL) AND (array_length(b, 1) = 0 OR b IS NULL) THEN + RETURN 0; + END IF; + IF array_length(a, 1) = 0 OR a IS NULL THEN + RETURN -1; + END IF; + IF array_length(b, 1) = 0 OR a IS NULL THEN + RETURN 1; + END IF; + + cmp_result := compare_ore_64_8_v1_term(a[1], b[1]); + IF cmp_result = 0 THEN + -- Removes the first element in the array, and calls this fn again to compare the next element/s in the array. + RETURN compare_ore_array(a[2:array_length(a,1)], b[2:array_length(b,1)]); + END IF; + + RETURN cmp_result; + END +$$ LANGUAGE plpgsql; + +-- This function uses lexicographic comparison +CREATE OR REPLACE FUNCTION compare_ore_64_8_v1(a ore_64_8_v1, b ore_64_8_v1) returns integer AS $$ + DECLARE + cmp_result integer; + BEGIN + -- Recursively compare blocks bailing as soon as we can make a decision + RETURN compare_ore_array(a.terms, b.terms); + END +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_eq(a ore_64_8_v1, b ore_64_8_v1) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1(a, b) = 0 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_neq(a ore_64_8_v1, b ore_64_8_v1) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1(a, b) <> 0 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_lt(a ore_64_8_v1, b ore_64_8_v1) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1(a, b) = -1 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_lte(a ore_64_8_v1, b ore_64_8_v1) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1(a, b) != 1 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_gt(a ore_64_8_v1, b ore_64_8_v1) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1(a, b) = 1 +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION ore_64_8_v1_gte(a ore_64_8_v1, b ore_64_8_v1) RETURNS boolean AS $$ + SELECT compare_ore_64_8_v1(a, b) != -1 +$$ LANGUAGE SQL; + +CREATE OPERATOR = ( + PROCEDURE="ore_64_8_v1_eq", + LEFTARG=ore_64_8_v1, + RIGHTARG=ore_64_8_v1, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +CREATE OPERATOR <> ( + PROCEDURE="ore_64_8_v1_neq", + LEFTARG=ore_64_8_v1, + RIGHTARG=ore_64_8_v1, + NEGATOR = =, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +CREATE OPERATOR > ( + PROCEDURE="ore_64_8_v1_gt", + LEFTARG=ore_64_8_v1, + RIGHTARG=ore_64_8_v1, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR < ( + PROCEDURE="ore_64_8_v1_lt", + LEFTARG=ore_64_8_v1, + RIGHTARG=ore_64_8_v1, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR <= ( + PROCEDURE="ore_64_8_v1_lte", + LEFTARG=ore_64_8_v1, + RIGHTARG=ore_64_8_v1, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel +); + +CREATE OPERATOR >= ( + PROCEDURE="ore_64_8_v1_gte", + LEFTARG=ore_64_8_v1, + RIGHTARG=ore_64_8_v1, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel +); + +CREATE OPERATOR FAMILY ore_64_8_v1_btree_ops USING btree; +CREATE OPERATOR CLASS ore_64_8_v1_btree_ops DEFAULT FOR TYPE ore_64_8_v1 USING btree FAMILY ore_64_8_v1_btree_ops AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 compare_ore_64_8_v1(a ore_64_8_v1, b ore_64_8_v1); DROP CAST IF EXISTS (text AS ore_64_8_v1_term); DROP FUNCTION IF EXISTS cs_match_v1;