diff --git a/sql/v2/expression/like_expression.go b/sql/v2/expression/like_expression.go index 236c15caf..5f557fa5a 100644 --- a/sql/v2/expression/like_expression.go +++ b/sql/v2/expression/like_expression.go @@ -70,6 +70,9 @@ func convertLikePatternToRegex(pattern string) (*regexp.Regexp, error) { chunk.Reset() i++ continue + } else { + // if there is an actual literal \ character, we need to include that in the string + chunk.WriteRune('\\') } } else if pattern[i] == '_' { // replace with . diff --git a/sql/v2/test/tck/README.md b/sql/v2/test/tck/README.md new file mode 100644 index 000000000..5852abcfe --- /dev/null +++ b/sql/v2/test/tck/README.md @@ -0,0 +1,28 @@ +# CloudEvents Expression Language TCK + +Each file of this TCK contains a set of test cases, testing one or more specific features of the language. + +The root file structure is composed by: + +* `name`: Name of the test suite contained in the file +* `tests`: List of tests + +Each test definition includes: + +* `name`: Name of the test case +* `expression`: Expression to test. +* `result`: Expected result (OPTIONAL). Can be a boolean, an integer or a string. +* `error`: Expected error (OPTIONAL). If absent, no error is expected. +* `event`: Input event (OPTIONAL). If present, this is a valid event serialized in JSON format. If absent, when testing + the expression, any valid event can be passed. +* `eventOverrides`: Overrides to the input event (OPTIONAL). This might be used when `event` is missing, in order to + define only some specific values, while the other (REQUIRED) attributes can be any value. + +The `error` values could be any of the following: + +* `parse`: Error while parsing the expression +* `math`: Math error while evaluating a math operator +* `cast`: Casting error +* `missingAttribute`: Addressed a missing attribute +* `missingFunction`: Addressed a missing function +* `functionEvaluation`: Error while evaluating a function diff --git a/sql/v2/test/tck/like_expression.yaml b/sql/v2/test/tck/like_expression.yaml index b8192b9e5..b6bc5a18b 100644 --- a/sql/v2/test/tck/like_expression.yaml +++ b/sql/v2/test/tck/like_expression.yaml @@ -1,8 +1,11 @@ name: Like expression tests: - - name: Exact match + - name: Exact match (1) expression: "'abc' LIKE 'abc'" result: true + - name: Exact match (2) + expression: "'ab\\c' LIKE 'ab\\c'" + result: true - name: Exact match (negate) expression: "'abc' NOT LIKE 'abc'" result: false @@ -25,6 +28,12 @@ tests: - name: Percentage operator (6) expression: "'' LIKE 'abc'" result: false + - name: Percentage operator (7) + expression: "'.ab.cde.' LIKE '.%.%.'" + result: true + - name: Percentage operator (8) + expression: "'ab.cde' LIKE '.%.%.'" + result: false - name: Underscore operator (1) expression: "'abc' LIKE 'a_b_c'" @@ -41,6 +50,12 @@ tests: - name: Underscore operator (5) expression: "'azbzc' LIKE 'a_b_c'" result: true + - name: Underscore operator (6) + expression: "'.a.b.' LIKE '._._.'" + result: true + - name: Underscore operator (7) + expression: "'abcd.' LIKE '._._.'" + result: false - name: Escaped underscore wildcards (1) expression: "'a_b_c' LIKE 'a\\_b\\_c'" @@ -78,3 +93,26 @@ tests: eventOverrides: myext: "abc123123%456_dzf" result: false + + - name: With type coercion from int (1) + expression: "234 LIKE '23_'" + result: true + - name: With type coercion from int (2) + expression: "2344 LIKE '23%'" + result: true + - name: With type coercion from int (3) + expression: "2344 LIKE '23_'" + result: false + + - name: With type coercion from bool (1) + expression: "TRUE LIKE 'tr%'" + result: true + - name: With type coercion from bool (2) + expression: "TRUE LIKE '%ue'" + result: true + - name: With type coercion from bool (3) + expression: "FALSE LIKE 'tr%'" + result: false + - name: With type coercion from bool (4) + expression: "FALSE LIKE 'fal%'" + result: true diff --git a/sql/v2/test/tck/subscriptions_api_recreations.yaml b/sql/v2/test/tck/subscriptions_api_recreations.yaml new file mode 100644 index 000000000..c56773b64 --- /dev/null +++ b/sql/v2/test/tck/subscriptions_api_recreations.yaml @@ -0,0 +1,168 @@ +name: SubscriptionsAPI Recreations +tests: + - name: Prefix filter (1) + expression: "source LIKE 'https://%'" + result: true + eventOverrides: + source: "https://example.com" + - name: Prefix filter (2) + expression: "source LIKE 'https://%'" + result: false + eventOverrides: + source: "http://example.com" + - name: Prefix filter on string extension + expression: "myext LIKE 'custom%'" + result: true + eventOverrides: + myext: "customext" + - name: Prefix filter on missing string extension + expression: "myext LIKE 'custom%'" + error: missingAttribute + + - name: Suffix filter (1) + expression: "type like '%.error'" + result: true + eventOverrides: + type: "com.github.error" + - name: Suffix filter (2) + expression: "type like '%.error'" + result: false + eventOverrides: + type: "com.github.success" + - name: Suffix filter on string extension + expression: "myext LIKE '%ext'" + result: true + eventOverrides: + myext: "customext" + - name: Suffix filter on missing string extension + expression: "myext LIKE '%ext'" + error: missingAttribute + + - name: Exact filter (1) + expression: "id = 'myId'" + result: true + eventOverrides: + id: "myId" + - name: Exact filter (2) + expression: "id = 'myId'" + result: false + eventOverrides: + id: "notmyId" + - name: Exact filter on string extension + expression: "myext = 'customext'" + result: true + eventOverrides: + myext: "customext" + - name: Exact filter on missing string extension + expression: "myext = 'customext'" + error: missingAttribute + + - name: Prefix filter AND Suffix filter (1) + expression: "id LIKE 'my%' AND source LIKE '%.ca'" + result: true + eventOverrides: + id: "myId" + source: "http://www.some-website.ca" + - name: Prefix filter AND Suffix filter (2) + expression: "id LIKE 'my%' AND source LIKE '%.ca'" + result: false + eventOverrides: + id: "myId" + source: "http://www.some-website.com" + - name: Prefix filter AND Suffix filter (3) + expression: "myext LIKE 'custom%' AND type LIKE '%.error'" + result: true + eventOverrides: + myext: "customext" + type: "com.github.error" + - name: Prefix AND Suffix filter (4) + expression: "type LIKE 'example.%' AND myext LIKE 'custom%'" + error: missingAttribute + eventOverrides: + type: "example.event.type" + + - name: Prefix OR Suffix filter (1) + expression: "id LIKE 'my%' OR source LIKE '%.ca'" + result: true + eventOverrides: + id: "myId" + source: "http://www.some-website.ca" + - name: Prefix OR Suffix filter (2) + expression: "id LIKE 'my%' OR source LIKE '%.ca'" + result: true + eventOverrides: + id: "myId" + source: "http://www.some-website.com" + - name: Prefix OR Suffix filter (3) + expression: "id LIKE 'my%' OR source LIKE '%.ca'" + result: true + eventOverrides: + id: "notmyId" + source: "http://www.some-website.ca" + - name: Prefix OR Suffix filter (4) + expression: "id LIKE 'my%' OR source LIKE '%.ca'" + result: false + eventOverrides: + id: "notmyId" + source: "http://www.some-website.com" + + - name: Disjunctive Normal Form (1) + expression: "(id = 'myId' AND type LIKE '%.success') OR (id = 'notmyId' AND source LIKE 'http://%' AND type LIKE '%.warning')" + result: true + eventOverrides: + id: "myId" + type: "example.event.success" + - name: Disjunctive Normal Form (2) + expression: "(id = 'myId' AND type LIKE '%.success') OR (id = 'notmyId' AND source LIKE 'http://%' AND type LIKE '%.warning')" + result: true + eventOverrides: + id: "notmyId" + type: "example.event.warning" + source: "http://localhost.localdomain" + - name: Disjunctive Normal Form (3) + expression: "(id = 'myId' AND type LIKE '%.success') OR (id = 'notmyId' AND source LIKE 'http://%' AND type LIKE '%.warning')" + result: false + eventOverrides: + id: "notmyId" + type: "example.event.warning" + source: "https://localhost.localdomain" + + - name: Conjunctive Normal Form (1) + expression: "(id = 'myId' OR type LIKE '%.success') AND (id = 'notmyId' OR source LIKE 'https://%' OR type LIKE '%.warning')" + result: true + eventOverrides: + id: "myId" + type: "example.event.warning" + source: "http://localhost.localdomain" + - name: Conjunctive Normal Form (2) + expression: "(id = 'myId' OR type LIKE '%.success') AND (id = 'notmyId' OR source LIKE 'https://%' OR type LIKE '%.warning')" + result: true + eventOverrides: + id: "notmyId" + type: "example.event.success" + source: "http://localhost.localdomain" + - name: Conjunctive Normal Form (3) + expression: "(id = 'myId' OR type LIKE '%.success') AND (id = 'notmyId' OR source LIKE 'https://%' OR type LIKE '%.warning')" + result: false + eventOverrides: + id: "notmyId" + type: "example.event.warning" + source: "http://localhost.localdomain" + - name: Conjunctive Normal Form (4) + expression: "(id = 'myId' OR type LIKE '%.success') AND (id = 'notmyId' OR source LIKE 'https://%' OR type LIKE '%.warning')" + result: false + eventOverrides: + id: "myId" + type: "example.event.success" + source: "http://localhost.localdomain" + - name: Conjunctive Normal Form (5) + expression: "(id = 'myId' OR type LIKE '%.success') AND (id = 'notmyId' OR source LIKE 'https://%' OR type LIKE '%.warning') AND (myext = 'customext')" + error: missingAttribute + eventOverrides: + id: "myId" + type: "example.event.warning" + source: "http://localhost.localdomain" + + + + diff --git a/sql/v2/test/tck_test.go b/sql/v2/test/tck_test.go index efa7ee7d2..f215c8db4 100644 --- a/sql/v2/test/tck_test.go +++ b/sql/v2/test/tck_test.go @@ -40,6 +40,7 @@ var TCKFileNames = []string{ "spec_examples", "string_builtin_functions", "sub_expression", + "subscriptions_api_recreations", } type ErrorType string @@ -93,6 +94,8 @@ func (tc TckTestCase) ExpectedResult() interface{} { return int32(tc.Result.(int)) case float64: return int32(tc.Result.(float64)) + case bool: + return tc.Result.(bool) } return tc.Result } @@ -125,6 +128,7 @@ func TestTCK(t *testing.T) { t.Run(file.Name, func(t *testing.T) { for j, testCase := range tckFiles[i].Tests { j := j + testCase := testCase t.Run(testCase.Name, func(t *testing.T) { t.Parallel() testCase := tckFiles[i].Tests[j]