diff --git a/languages/go/goeql/goeql.go b/languages/go/goeql/goeql.go index 2a2de479..2ba1a71e 100644 --- a/languages/go/goeql/goeql.go +++ b/languages/go/goeql/goeql.go @@ -29,6 +29,7 @@ type EncryptedColumn struct { P string `json:"p"` I TableColumn `json:"i"` V int `json:"v"` + Q string `json:"q"` } // EncryptedText is a string value to be encrypted @@ -149,9 +150,29 @@ func (eb *EncryptedBool) Deserialize(data []byte) (EncryptedBool, error) { return false, fmt.Errorf("invalid format: missing 'p' field") } -// SerializeQuery produces a jsonb payload used by EQL query functions to perform search operations like equality checks, range queries, and unique constraints. -func SerializeQuery(value any, table string, column string) ([]byte, error) { - query, err := ToEncryptedColumn(value, table, column) +// MatchQuery serializes a plaintext value used in a match query +func MatchQuery(value any, table string, column string) ([]byte, error) { + return serializeQuery(value, table, column, "match") +} + +// OreQuery serializes a plaintext value used in an ore query +func OreQuery(value any, table string, column string) ([]byte, error) { + return serializeQuery(value, table, column, "ore") +} + +// UniqueQuery serializes a plaintext value used in a unique query +func UniqueQuery(value any, table string, column string) ([]byte, error) { + return serializeQuery(value, table, column, "unique") +} + +// JsonbQuery serializes a plaintext value used in a jsonb query +func JsonbQuery(value any, table string, column string) ([]byte, error) { + return serializeQuery(value, table, column, "ste_vec") +} + +// serializeQuery produces a jsonb payload used by EQL query functions to perform search operations like equality checks, range queries, and unique constraints. +func serializeQuery(value any, table string, column string, queryType string) ([]byte, error) { + query, err := ToEncryptedColumn(value, table, column, queryType) if err != nil { return nil, fmt.Errorf("error converting to EncryptedColumn: %v", err) } @@ -165,14 +186,15 @@ func SerializeQuery(value any, table string, column string) ([]byte, error) { } // ToEncryptedColumn converts a plaintext value to a string, and returns the EncryptedColumn struct for inserting into a database. -func ToEncryptedColumn(value any, table string, column string) (EncryptedColumn, error) { +func ToEncryptedColumn(value any, table string, column string, queryType ...string) (EncryptedColumn, error) { str, err := convertToString(value) if err != nil { return EncryptedColumn{}, fmt.Errorf("error: %v", err) } - data := EncryptedColumn{K: "pt", P: str, I: TableColumn{T: table, C: column}, V: 1} - + if queryType != nil { + data.Q = queryType[0] + } return data, nil } diff --git a/languages/go/goeql/goeql_test.go b/languages/go/goeql/goeql_test.go index 89086ee4..7dcba214 100644 --- a/languages/go/goeql/goeql_test.go +++ b/languages/go/goeql/goeql_test.go @@ -210,34 +210,105 @@ func TestEncryptedBool_Deserialize(t *testing.T) { } } -// Test SerializeQuery Function -func TestSerializeQuery(t *testing.T) { - tests := []struct { - value interface{} - table string - column string - expectedP string - }{ - {value: "test_string", table: "table1", column: "column1", expectedP: "test_string"}, - {value: 123, table: "table2", column: "column2", expectedP: "123"}, - {value: true, table: "table3", column: "column3", expectedP: "true"}, - {value: map[string]interface{}{"key": "value"}, table: "table4", column: "column4", expectedP: `{"key":"value"}`}, +func TestMatchQuerySerialization(t *testing.T) { + value := "test_string" + table := "table1" + column := "column1" + expectedP := "test_string" + expectedQ := "match" + + serializedData, err := MatchQuery(value, table, column) + if err != nil { + t.Fatalf("SerializeQuery returned error: %v", err) } - for _, tt := range tests { - serializedData, err := SerializeQuery(tt.value, tt.table, tt.column) - if err != nil { - t.Fatalf("SerializeQuery returned error: %v", err) - } + var ec EncryptedColumn + if err := json.Unmarshal(serializedData, &ec); err != nil { + t.Fatalf("Error unmarshaling serialized data: %v", err) + } - var ec EncryptedColumn - if err := json.Unmarshal(serializedData, &ec); err != nil { - t.Fatalf("Error unmarshaling serialized data: %v", err) - } + if ec.P != expectedP { + t.Errorf("Expected P to be '%s', got '%s'", expectedP, ec.P) + } - if ec.P != tt.expectedP { - t.Errorf("Expected P to be '%s', got '%s'", tt.expectedP, ec.P) - } + if ec.Q != expectedQ { + t.Errorf("Expected Q to be '%s', got '%s'", expectedQ, ec.Q) + } +} +func TestOreQuerySerialization(t *testing.T) { + value := 123 + table := "table1" + column := "column1" + expectedP := "123" + expectedQ := "ore" + + serializedData, err := OreQuery(value, table, column) + if err != nil { + t.Fatalf("SerializeQuery returned error: %v", err) + } + + var ec EncryptedColumn + if err := json.Unmarshal(serializedData, &ec); err != nil { + t.Fatalf("Error unmarshaling serialized data: %v", err) + } + + if ec.P != expectedP { + t.Errorf("Expected P to be '%s', got '%s'", expectedP, ec.P) + } + + if ec.Q != expectedQ { + t.Errorf("Expected Q to be '%s', got '%s'", expectedQ, ec.Q) + } +} + +func TestUniqueQuerySerialization(t *testing.T) { + value := true + table := "table1" + column := "column1" + expectedP := "true" + expectedQ := "unique" + + serializedData, err := UniqueQuery(value, table, column) + if err != nil { + t.Fatalf("SerializeQuery returned error: %v", err) + } + + var ec EncryptedColumn + if err := json.Unmarshal(serializedData, &ec); err != nil { + t.Fatalf("Error unmarshaling serialized data: %v", err) + } + + if ec.P != expectedP { + t.Errorf("Expected P to be '%s', got '%s'", expectedP, ec.P) + } + + if ec.Q != expectedQ { + t.Errorf("Expected Q to be '%s', got '%s'", expectedQ, ec.Q) + } +} + +func TestJsonbQuerySerialization(t *testing.T) { + value := map[string]interface{}{"key": "value"} + table := "table1" + column := "column1" + expectedP := `{"key":"value"}` + expectedQ := "ste_vec" + + serializedData, err := JsonbQuery(value, table, column) + if err != nil { + t.Fatalf("SerializeQuery returned error: %v", err) + } + + var ec EncryptedColumn + if err := json.Unmarshal(serializedData, &ec); err != nil { + t.Fatalf("Error unmarshaling serialized data: %v", err) + } + + if ec.P != expectedP { + t.Errorf("Expected P to be '%s', got '%s'", expectedP, ec.P) + } + if ec.Q != expectedQ { + t.Errorf("Expected Q to be '%s', got '%s'", expectedQ, ec.Q) } } diff --git a/languages/go/xorm/e2e_test.go b/languages/go/xorm/e2e_test.go index fc570057..e48da964 100644 --- a/languages/go/xorm/e2e_test.go +++ b/languages/go/xorm/e2e_test.go @@ -51,10 +51,10 @@ func TestWhereQueryOnUnencryptedColumn(t *testing.T) { t.Fatalf("Could not insert new example: %v", err) } - var example Example + var returnedExample Example text := "sydney" - has, err := engine.Where("non_encrypted_field = ?", text).Get(&example) + has, err := engine.Where("non_encrypted_field = ?", text).Get(&returnedExample) if err != nil { t.Fatalf("Could not retrieve example: %v", err) } @@ -63,10 +63,10 @@ func TestWhereQueryOnUnencryptedColumn(t *testing.T) { t.Errorf("Expected has to equal true, got: %v", has) } - assert.Equal(t, newExample.NonEncryptedField, example.NonEncryptedField, "NonEncryptedField does not match") - assert.Equal(t, newExample.EncryptedIntField, example.EncryptedIntField, "EncryptedIntField does not match") - assert.Equal(t, newExample.EncryptedTextField, example.EncryptedTextField, "EncryptedTextField does not match") - assert.Equal(t, newExample.EncryptedJsonbField, example.EncryptedJsonbField, "EncryptedJsonbField does not match") + assert.Equal(t, newExample.NonEncryptedField, returnedExample.NonEncryptedField, "NonEncryptedField does not match") + assert.Equal(t, newExample.EncryptedIntField, returnedExample.EncryptedIntField, "EncryptedIntField does not match") + assert.Equal(t, newExample.EncryptedTextField, returnedExample.EncryptedTextField, "EncryptedTextField does not match") + assert.Equal(t, newExample.EncryptedJsonbField, returnedExample.EncryptedJsonbField, "EncryptedJsonbField does not match") } func TestMatchQueryLongString(t *testing.T) { @@ -82,6 +82,12 @@ func TestMatchQueryLongString(t *testing.T) { } examples := []Example{ + { + NonEncryptedField: "brisbane", + EncryptedIntField: 23, + EncryptedTextField: "another string that shouldn't be returned", + EncryptedJsonbField: jsonData, + }, { NonEncryptedField: "sydney", EncryptedIntField: 23, @@ -102,9 +108,9 @@ func TestMatchQueryLongString(t *testing.T) { t.Errorf("Error inserting examples: %v", err) } - assert.Equal(t, int64(2), inserted, "Expected to insert 2 rows") + assert.Equal(t, int64(3), inserted, "Expected to insert 2 rows") - query, err := goeql.SerializeQuery("this", "examples", "encrypted_text_field") + query, err := goeql.MatchQuery("this", "examples", "encrypted_text_field") if err != nil { log.Fatalf("Error marshaling encrypted_text_field query: %v", err) } @@ -119,7 +125,7 @@ func TestMatchQueryLongString(t *testing.T) { t.Errorf("Expected has to equal true, got: %v", has) } - assert.Equal(t, returnedExample.EncryptedTextField, EncryptedTextField("this is a long string"), "EncryptedTextField should match") + assert.Equal(t, EncryptedTextField("this is a long string"), returnedExample.EncryptedTextField, "EncryptedTextField should match") } func TestMatchQueryEmail(t *testing.T) { @@ -157,7 +163,7 @@ func TestMatchQueryEmail(t *testing.T) { assert.Equal(t, int64(2), inserted, "Expected to insert 2 rows") - query, err := goeql.SerializeQuery("test", "examples", "encrypted_text_field") + query, err := goeql.MatchQuery("test", "examples", "encrypted_text_field") if err != nil { log.Fatalf("Error marshaling encrypted_text_field query: %v", err) } @@ -172,7 +178,7 @@ func TestMatchQueryEmail(t *testing.T) { t.Errorf("Expected has to equal true, got: %v", has) } - assert.Equal(t, returnedExample.EncryptedTextField, EncryptedTextField("testemail@test.com"), "EncryptedTextField should match") + assert.Equal(t, EncryptedTextField("testemail@test.com"), returnedExample.EncryptedTextField, "EncryptedTextField should match") } func TestJsonbQuerySimple(t *testing.T) { @@ -218,7 +224,7 @@ func TestJsonbQuerySimple(t *testing.T) { }, } - query, errTwo := goeql.SerializeQuery(jsonbQuery, "examples", "encrypted_jsonb_field") + query, errTwo := goeql.JsonbQuery(jsonbQuery, "examples", "encrypted_jsonb_field") if errTwo != nil { log.Fatalf("Error marshaling encrypted_jsonb_field: %v", errTwo) } @@ -233,7 +239,7 @@ func TestJsonbQuerySimple(t *testing.T) { t.Errorf("Expected has to equal true, got: %v", has) } - assert.Equal(t, returnedExample.EncryptedJsonbField, EncryptedJsonbField(expectedJson), "EncryptedJsonb field should match") + assert.Equal(t, EncryptedJsonbField(expectedJson), returnedExample.EncryptedJsonbField, "EncryptedJsonb field should match") } func TestJsonbQueryNested(t *testing.T) { @@ -284,7 +290,7 @@ func TestJsonbQueryNested(t *testing.T) { }, } - query, errTwo := goeql.SerializeQuery(jsonbQuery, "examples", "encrypted_jsonb_field") + query, errTwo := goeql.JsonbQuery(jsonbQuery, "examples", "encrypted_jsonb_field") if errTwo != nil { log.Fatalf("Error marshaling encrypted_jsonb_field: %v", errTwo) } @@ -299,7 +305,7 @@ func TestJsonbQueryNested(t *testing.T) { t.Errorf("Expected has to equal true, got: %v", has) } - assert.Equal(t, returnedExample.EncryptedJsonbField, EncryptedJsonbField(expectedJson), "EncryptedJsonb field should match") + assert.Equal(t, EncryptedJsonbField(expectedJson), returnedExample.EncryptedJsonbField, "EncryptedJsonb field should match") } func TestOreStringRangeQuery(t *testing.T) { @@ -331,7 +337,7 @@ func TestOreStringRangeQuery(t *testing.T) { assert.Equal(t, int64(2), inserted, "Expected to insert 2 rows") // Query - query, errQuery := goeql.SerializeQuery("tree", "examples", "encrypted_text_field") + query, errQuery := goeql.OreQuery("tree", "examples", "encrypted_text_field") if errQuery != nil { log.Fatalf("err: %v", errQuery) } @@ -346,7 +352,7 @@ func TestOreStringRangeQuery(t *testing.T) { t.Errorf("Expected has to equal true, got: %v", has) } - assert.Equal(t, returnedExample.EncryptedTextField, expected, "EncryptedText field should match") + assert.Equal(t, expected, returnedExample.EncryptedTextField, "EncryptedText field should match") } func TestOreIntRangeQuery(t *testing.T) { @@ -378,7 +384,7 @@ func TestOreIntRangeQuery(t *testing.T) { assert.Equal(t, int64(2), inserted, "Expected to insert 2 rows") // Query - query, errQuery := goeql.SerializeQuery(32, "examples", "encrypted_int_field") + query, errQuery := goeql.OreQuery(32, "examples", "encrypted_int_field") if errQuery != nil { log.Fatalf("err: %v", errQuery) } @@ -393,7 +399,7 @@ func TestOreIntRangeQuery(t *testing.T) { t.Errorf("Expected has to equal true, got: %v", has) } - assert.Equal(t, returnedExample.EncryptedIntField, expected, "EncryptedInt field should match") + assert.Equal(t, expected, returnedExample.EncryptedIntField, "EncryptedInt field should match") } func TestOreBoolRangeQuery(t *testing.T) { @@ -434,7 +440,7 @@ func TestOreBoolRangeQuery(t *testing.T) { assert.Equal(t, int64(3), inserted, "Expected to insert 3 rows") // Query - query, errQuery := goeql.SerializeQuery(false, "examples", "encrypted_bool_field") + query, errQuery := goeql.OreQuery(false, "examples", "encrypted_bool_field") if errQuery != nil { log.Fatalf("err: %v", errQuery) } @@ -449,7 +455,7 @@ func TestOreBoolRangeQuery(t *testing.T) { t.Errorf("Expected has to equal true, got: %v", has) } - assert.Equal(t, returnedExample.EncryptedBoolField, expected, "EncryptedBool field should match") + assert.Equal(t, expected, returnedExample.EncryptedBoolField, "EncryptedBool field should match") } func TestUniqueStringQuery(t *testing.T) { @@ -490,7 +496,7 @@ func TestUniqueStringQuery(t *testing.T) { assert.Equal(t, int64(3), inserted, "Expected to insert 3 rows") // Query - query, errQuery := goeql.SerializeQuery("testing two", "examples", "encrypted_text_field") + query, errQuery := goeql.UniqueQuery("testing two", "examples", "encrypted_text_field") if errQuery != nil { log.Fatalf("err: %v", errQuery) } @@ -505,5 +511,5 @@ func TestUniqueStringQuery(t *testing.T) { t.Errorf("Expected has to equal true, got: %v", has) } - assert.Equal(t, returnedExample.EncryptedTextField, expected, "EncryptedText field should match") + assert.Equal(t, expected, returnedExample.EncryptedTextField, "EncryptedText field should match") } diff --git a/languages/go/xorm/example_queries.go b/languages/go/xorm/example_queries.go index 41497b4b..af74eee0 100644 --- a/languages/go/xorm/example_queries.go +++ b/languages/go/xorm/example_queries.go @@ -60,7 +60,7 @@ func MatchQueryLongString(engine *xorm.Engine) { fmt.Printf("Example one inserted: %+v\n", newExample) fmt.Println("") - query, err := goeql.SerializeQuery("this", "examples", "encrypted_text_field") + query, err := goeql.MatchQuery("this", "examples", "encrypted_text_field") if err != nil { log.Fatalf("Error marshaling encrypted_text_field: %v", err) } @@ -93,7 +93,7 @@ func MatchQueryEmail(engine *xorm.Engine) { fmt.Printf("Example two inserted!: %+v\n", newExampleTwo) fmt.Println("") - query, errTwo := goeql.SerializeQuery("some", "examples", "encrypted_text_field") + query, errTwo := goeql.MatchQuery("some", "examples", "encrypted_text_field") if errTwo != nil { log.Fatalf("Error marshaling encrypted_text_field: %v", errTwo) } @@ -141,7 +141,7 @@ func JsonbQuerySimple(engine *xorm.Engine) { }, } - query, errTwo := goeql.SerializeQuery(jsonbQuery, "examples", "encrypted_jsonb_field") + query, errTwo := goeql.JsonbQuery(jsonbQuery, "examples", "encrypted_jsonb_field") if errTwo != nil { log.Fatalf("Error marshaling encrypted_jsonb_field: %v", errTwo) } @@ -201,7 +201,7 @@ func JsonbQueryDeepNested(engine *xorm.Engine) { }, } - jsonbQuery, errQuery := goeql.SerializeQuery(query, "examples", "encrypted_jsonb_field") + jsonbQuery, errQuery := goeql.JsonbQuery(query, "examples", "encrypted_jsonb_field") if errQuery != nil { log.Fatalf("err: %v", errQuery) } @@ -238,7 +238,7 @@ func OreStringRangeQuery(engine *xorm.Engine) { fmt.Println("Examples inserted!") // Query - query, errQuery := goeql.SerializeQuery("tree", "examples", "encrypted_text_field") + query, errQuery := goeql.OreQuery("tree", "examples", "encrypted_text_field") if errQuery != nil { log.Fatalf("err: %v", errQuery) } @@ -276,7 +276,7 @@ func OreIntRangeQuery(engine *xorm.Engine) { fmt.Println("Examples inserted!", example1) fmt.Println("Examples inserted!", example2) - serializedOreIntQuery, errQuery := goeql.SerializeQuery(32, "examples", "encrypted_int_field") + serializedOreIntQuery, errQuery := goeql.OreQuery(32, "examples", "encrypted_int_field") if errQuery != nil { log.Fatalf("err: %v", errQuery) } @@ -322,7 +322,7 @@ func OreBoolQuery(engine *xorm.Engine) { fmt.Println("Example3 inserted!", example3) // Query - query, errQuery := goeql.SerializeQuery(false, "examples", "encrypted_bool_field") + query, errQuery := goeql.OreQuery(false, "examples", "encrypted_bool_field") if errQuery != nil { log.Fatalf("err: %v", errQuery) } @@ -364,7 +364,7 @@ func UniqueStringQuery(engine *xorm.Engine) { fmt.Println("Example3 inserted!", example3) var allExamples []Example - query, errQuery := goeql.SerializeQuery("test two", "examples", "encrypted_text_field") + query, errQuery := goeql.UniqueQuery("test two", "examples", "encrypted_text_field") if errQuery != nil { log.Fatalf("err: %v", errQuery) } diff --git a/languages/go/xorm/main.go b/languages/go/xorm/main.go index 7c92c77c..830b9134 100644 --- a/languages/go/xorm/main.go +++ b/languages/go/xorm/main.go @@ -13,11 +13,6 @@ import ( "xorm.io/xorm/names" ) -// To setup postgres: -// Run: docker compose up -// To run examples -// Run: go run . - // Create a separate custom type for each field that is being encrypted, using the relevant go type. // This custom type can then be used to access the conversion interface to use toDB and fromDb. type EncryptedTextField string diff --git a/languages/go/xorm/migrations.go b/languages/go/xorm/migrations.go index e0ac3fb8..a0b5c2d4 100644 --- a/languages/go/xorm/migrations.go +++ b/languages/go/xorm/migrations.go @@ -30,7 +30,7 @@ func installCsCustomTypes(engine *sql.DB) { // Installing EQL func installDsl(engine *sql.DB) { - path := "./cipherstash-encrypt-dsl.sql" + path := "../../../release/cipherstash-encrypt-dsl.sql" sql, err := os.ReadFile(path) if err != nil { log.Fatalf("Failed to read SQL file: %v", err)