From a407d5c462824886bac504e9c3fb4526f3f3b21b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?griff=20=D1=96=E2=8A=99?= <346896+griffio@users.noreply.github.com> Date: Mon, 8 Jul 2024 18:47:24 +0100 Subject: [PATCH] Fix 5330 add Postgresql ILIKE operator (#5333) * override binary_like_operator Add `ILIKE` to `binary_like_operator` Postgresql doesn't use `GLOB | REGEXP | MATCH` keywords It would require rewriting the mixin just to add `ILIKE` Add like and ilike patterns to regex_match_operator regex_match_operator is ordered by shortest matching characters first as the parser gets confused * Add tests fixture test integration tests --- .../postgresql/grammar/PostgreSql.bnf | 13 ++++++- .../fixtures_postgresql/like-operators/Test.s | 17 +++++++++ .../sqldelight/postgresql/integration/Like.sq | 24 +++++++++++++ .../postgresql/integration/PostgreSqlTest.kt | 36 +++++++++++++++++++ 4 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 dialects/postgresql/src/testFixtures/resources/fixtures_postgresql/like-operators/Test.s create mode 100644 sqldelight-gradle-plugin/src/test/integration-postgresql/src/main/sqldelight/app/cash/sqldelight/postgresql/integration/Like.sq diff --git a/dialects/postgresql/src/main/kotlin/app/cash/sqldelight/dialects/postgresql/grammar/PostgreSql.bnf b/dialects/postgresql/src/main/kotlin/app/cash/sqldelight/dialects/postgresql/grammar/PostgreSql.bnf index 1590622aa23..b76edb749c6 100644 --- a/dialects/postgresql/src/main/kotlin/app/cash/sqldelight/dialects/postgresql/grammar/PostgreSql.bnf +++ b/dialects/postgresql/src/main/kotlin/app/cash/sqldelight/dialects/postgresql/grammar/PostgreSql.bnf @@ -44,6 +44,7 @@ "static com.alecstrong.sql.psi.core.psi.SqlTypes.FOREIGN" "static com.alecstrong.sql.psi.core.psi.SqlTypes.FROM" "static com.alecstrong.sql.psi.core.psi.SqlTypes.GENERATED" + "static com.alecstrong.sql.psi.core.psi.SqlTypes.GLOB" "static com.alecstrong.sql.psi.core.psi.SqlTypes.GROUP" "static com.alecstrong.sql.psi.core.psi.SqlTypes.HAVING" "static com.alecstrong.sql.psi.core.psi.SqlTypes.ID" @@ -54,8 +55,10 @@ "static com.alecstrong.sql.psi.core.psi.SqlTypes.INSERT" "static com.alecstrong.sql.psi.core.psi.SqlTypes.INTO" "static com.alecstrong.sql.psi.core.psi.SqlTypes.KEY" + "static com.alecstrong.sql.psi.core.psi.SqlTypes.LIKE" "static com.alecstrong.sql.psi.core.psi.SqlTypes.LIMIT" "static com.alecstrong.sql.psi.core.psi.SqlTypes.LP" + "static com.alecstrong.sql.psi.core.psi.SqlTypes.MATCH" "static com.alecstrong.sql.psi.core.psi.SqlTypes.MINUS" "static com.alecstrong.sql.psi.core.psi.SqlTypes.MULTIPLY" "static com.alecstrong.sql.psi.core.psi.SqlTypes.NO" @@ -69,6 +72,7 @@ "static com.alecstrong.sql.psi.core.psi.SqlTypes.PARTITION" "static com.alecstrong.sql.psi.core.psi.SqlTypes.PLUS" "static com.alecstrong.sql.psi.core.psi.SqlTypes.PRIMARY" + "static com.alecstrong.sql.psi.core.psi.SqlTypes.REGEXP" "static com.alecstrong.sql.psi.core.psi.SqlTypes.RENAME" "static com.alecstrong.sql.psi.core.psi.SqlTypes.REPLACE" "static com.alecstrong.sql.psi.core.psi.SqlTypes.ROLLBACK" @@ -109,6 +113,7 @@ overrides ::= type_name | create_index_stmt | select_stmt | ordering_term + | binary_like_operator | literal_value column_constraint ::= [ CONSTRAINT {identifier} ] ( @@ -192,6 +197,12 @@ create_index_stmt ::= CREATE [ UNIQUE ] INDEX [ 'CONCURRENTLY' ] [ IF NOT EXISTS pin = 6 } +binary_like_operator ::= ( 'ILIKE' | LIKE | GLOB | REGEXP | MATCH ) { + extends = "com.alecstrong.sql.psi.core.psi.impl.SqlBinaryLikeOperatorImpl" + implements = "com.alecstrong.sql.psi.core.psi.SqlBinaryLikeOperator" + override = true +} + identity_clause ::= 'IDENTITY' [ LP [ 'SEQUENCE' 'NAME' sequence_name ] [ sequence_parameters* ] RP ] generated_clause ::= GENERATED ( (ALWAYS AS LP <> RP 'STORED') | ( (ALWAYS | BY DEFAULT) AS identity_clause ) ) { @@ -445,7 +456,7 @@ match_operator_expression ::= ( {function_expr} | {column_expr} ) match_operator pin = 2 } -regex_match_operator ::= '~*' | '~' | '!~*' | '!~' +regex_match_operator ::= '~~*' | '~*' | '!~~*' | '!~*' | '~~' | '~' | '!~~' | '!~' regex_match_operator_expression ::= ( {function_expr} | {column_expr} ) regex_match_operator <> { mixin = "app.cash.sqldelight.dialects.postgresql.grammar.mixins.RegExMatchOperatorExpressionMixin" diff --git a/dialects/postgresql/src/testFixtures/resources/fixtures_postgresql/like-operators/Test.s b/dialects/postgresql/src/testFixtures/resources/fixtures_postgresql/like-operators/Test.s new file mode 100644 index 00000000000..16e796062f8 --- /dev/null +++ b/dialects/postgresql/src/testFixtures/resources/fixtures_postgresql/like-operators/Test.s @@ -0,0 +1,17 @@ +CREATE TABLE Test ( + txt TEXT NOT NULL +); + +SELECT * FROM Test WHERE txt LIKE 'testing%'; + +SELECT * FROM Test WHERE txt ILIKE 'test%'; + +SELECT * FROM Test WHERE txt ~~ 'testin%'; + +SELECT * FROM Test WHERE txt ~~* '%esting%'; + +SELECT txt !~~ 'testing%' FROM Test; + +SELECT txt !~~* 'testing%' FROM Test; + +SELECT txt ILIKE 'test%' FROM Test; diff --git a/sqldelight-gradle-plugin/src/test/integration-postgresql/src/main/sqldelight/app/cash/sqldelight/postgresql/integration/Like.sq b/sqldelight-gradle-plugin/src/test/integration-postgresql/src/main/sqldelight/app/cash/sqldelight/postgresql/integration/Like.sq new file mode 100644 index 00000000000..91982c8af74 --- /dev/null +++ b/sqldelight-gradle-plugin/src/test/integration-postgresql/src/main/sqldelight/app/cash/sqldelight/postgresql/integration/Like.sq @@ -0,0 +1,24 @@ +CREATE TABLE Test_Like ( + txt TEXT NOT NULL +); + +insert: +INSERT INTO Test_Like (txt) VALUES(?); + +selectWhereLike: +SELECT * FROM Test_Like WHERE txt LIKE ?; + +selectWhereILike: +SELECT * FROM Test_Like WHERE txt ILIKE ?; + +selectWhereLikeRegex: +SELECT * FROM Test_Like WHERE txt ~~ 'testin%'; + +selectWhereILikeRegex: +SELECT * FROM Test_Like WHERE txt ~~* '%esting%'; + +selectLikeRegex: +SELECT txt ~~ 'testing%', txt !~~ 'testing%' FROM Test_Like; + +selectILikeRegex: +SELECT txt ~~* 'testing%', txt !~~* 'testing%' FROM Test_Like; diff --git a/sqldelight-gradle-plugin/src/test/integration-postgresql/src/test/kotlin/app/cash/sqldelight/postgresql/integration/PostgreSqlTest.kt b/sqldelight-gradle-plugin/src/test/integration-postgresql/src/test/kotlin/app/cash/sqldelight/postgresql/integration/PostgreSqlTest.kt index e09f4518b4d..4d8e86618a2 100644 --- a/sqldelight-gradle-plugin/src/test/integration-postgresql/src/test/kotlin/app/cash/sqldelight/postgresql/integration/PostgreSqlTest.kt +++ b/sqldelight-gradle-plugin/src/test/integration-postgresql/src/test/kotlin/app/cash/sqldelight/postgresql/integration/PostgreSqlTest.kt @@ -866,6 +866,42 @@ class PostgreSqlTest { } } + @Test + fun testLike() { + database.likeQueries.insert("testing") + + with(database.likeQueries.selectWhereLike("test%").executeAsList()) { + assertThat(first()).isEqualTo("testing") + } + + with(database.likeQueries.selectWhereLikeRegex().executeAsList()) { + assertThat(first()).isEqualTo("testing") + } + + with(database.likeQueries.selectLikeRegex().executeAsList()) { + assertThat(first().expr).isTrue() + assertThat(first().expr_).isFalse() + } + } + + @Test + fun testILike() { + database.likeQueries.insert("TESTING") + + with(database.likeQueries.selectWhereILike("test%").executeAsList()) { + assertThat(first()).isEqualTo("TESTING") + } + + with(database.likeQueries.selectWhereILikeRegex().executeAsList()) { + assertThat(first()).isEqualTo("TESTING") + } + + with(database.likeQueries.selectILikeRegex().executeAsList()) { + assertThat(first().expr).isTrue() + assertThat(first().expr_).isFalse() + } + } + @Test fun testRankOver() { database.windowFunctionsQueries.insert("t", 2)