From b116754d5fff7df086c5983ad433f6b915d07ec9 Mon Sep 17 00:00:00 2001 From: Grigorii Berezin Date: Sun, 28 Apr 2024 22:21:53 +0300 Subject: [PATCH 1/4] feat: implement sounds like function for mysql --- .../src/main/scala/zio/sql/expr/Expr.scala | 5 ++++ .../src/main/scala/zio/sql/ops/Operator.scala | 7 ++++- mysql/src/test/resources/shop_schema.sql | 3 +- .../zio/sql/mysql/CustomFunctionDefSpec.scala | 29 +++++++++++++++---- 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/core/jvm/src/main/scala/zio/sql/expr/Expr.scala b/core/jvm/src/main/scala/zio/sql/expr/Expr.scala index 178e58b20..415097e3b 100644 --- a/core/jvm/src/main/scala/zio/sql/expr/Expr.scala +++ b/core/jvm/src/main/scala/zio/sql/expr/Expr.scala @@ -67,6 +67,11 @@ sealed trait Expr[-F, -A, +B] { self => ): Expr[F with F2, A1, Boolean] = Expr.Relational(self, that, RelationalOp.LessThanEqual) + def soundsLike[F2, A1 <: A](that: Expr[F2, A1, String])(implicit + ev: B <:< String + ): Expr[F with F2, A1, Boolean] = + Expr.Relational(self, that, RelationalOp.MySqlExtensions.SoundsLike) + def like[F2, A1 <: A](that: Expr[F2, A1, String])(implicit ev: B <:< String): Expr[F with F2, A1, Boolean] = Expr.Relational(self, that, RelationalOp.Like) diff --git a/core/jvm/src/main/scala/zio/sql/ops/Operator.scala b/core/jvm/src/main/scala/zio/sql/ops/Operator.scala index 50cdd0031..847bd8d96 100644 --- a/core/jvm/src/main/scala/zio/sql/ops/Operator.scala +++ b/core/jvm/src/main/scala/zio/sql/ops/Operator.scala @@ -52,7 +52,6 @@ object Operator { final case class OrBit[A: IsIntegral]() extends BinaryOp[A] { def isIntegral: IsIntegral[A] = implicitly[IsIntegral[A]] override val symbol: String = "|" - } } @@ -97,6 +96,12 @@ object Operator { case object Like extends RelationalOp { override val symbol: String = "like" } + + object MySqlExtensions { + case object SoundsLike extends RelationalOp { + override val symbol: String = "sounds like" + } + } } sealed trait UnaryOp[A] extends Operator diff --git a/mysql/src/test/resources/shop_schema.sql b/mysql/src/test/resources/shop_schema.sql index 1ad8c80c9..40a323f66 100644 --- a/mysql/src/test/resources/shop_schema.sql +++ b/mysql/src/test/resources/shop_schema.sql @@ -48,7 +48,8 @@ values ('f76c9ace-be07-4bf3-bd4c-4a9c62882e64', 'Terrence', 'Noel', true, '1999-11-02'), ('784426a5-b90a-4759-afbb-571b7a0ba35e', 'Mila', 'Paterso', true, '1990-11-16'), ('df8215a2-d5fd-4c6c-9984-801a1b3a2a0b', 'Alana', 'Murray', true, '1995-11-12'), - ('636ae137-5b1a-4c8c-b11f-c47c624d9cdc', 'Jose', 'Wiggins', false, '1987-03-23'); + ('636ae137-5b1a-4c8c-b11f-c47c624d9cdc', 'Jose', 'Wiggins', false, '1987-03-23'), + ('d4f6c156-20ac-4d27-8ced-535bf4315ebc', 'Robert', 'Rupert', false, '1998-06-11'); insert into products (id, name, description, image_url) diff --git a/mysql/src/test/scala/zio/sql/mysql/CustomFunctionDefSpec.scala b/mysql/src/test/scala/zio/sql/mysql/CustomFunctionDefSpec.scala index 34f512f73..a2fdbb2db 100644 --- a/mysql/src/test/scala/zio/sql/mysql/CustomFunctionDefSpec.scala +++ b/mysql/src/test/scala/zio/sql/mysql/CustomFunctionDefSpec.scala @@ -1,13 +1,16 @@ package zio.sql.mysql -import zio.test._ -import zio.test.Assertion._ +import zio.Chunk import zio.schema._ -import java.time.{ LocalDate, LocalTime, ZoneId } -import java.time.format.DateTimeFormatter import zio.sql.Jdbc -import java.util.UUID +import zio.sql.expr.Expr.literal import zio.sql.table._ +import zio.test.Assertion._ +import zio.test._ + +import java.time.format.DateTimeFormatter +import java.time.{LocalDate, LocalTime, ZoneId} +import java.util.UUID object CustomFunctionDefSpec extends MysqlRunnableSpec with Jdbc { @@ -119,6 +122,22 @@ object CustomFunctionDefSpec extends MysqlRunnableSpec with Jdbc { assertZIO(testResult.runHead.some)(equalTo(expected)) }, + test("sounds like") { + val query = select("Robert".soundsLike("Rupert")) + + val testResult = execute(query) + + assertZIO(testResult.runHead.some)(equalTo(true)) + }, + test("sounds like on column") { + val query = select(customerId).from(customers).where(fName.soundsLike(lName)) + + for { + result <- execute(query).runCollect + } yield assertTrue( + result == Chunk(UUID.fromString("d4f6c156-20ac-4d27-8ced-535bf4315ebc")) + ) + }, test("current_date") { val query = select(CurrentDate) From 81a8dba83dc1ec8ea85fbf3b23f67ab13e9dfe85 Mon Sep 17 00:00:00 2001 From: Grigorii Berezin Date: Mon, 29 Apr 2024 08:15:39 +0300 Subject: [PATCH 2/4] test: correct tests --- mysql/src/test/resources/shop_schema.sql | 2 +- .../scala/zio/sql/mysql/CommonFunctionDefSpec.scala | 5 ++++- .../scala/zio/sql/mysql/CustomFunctionDefSpec.scala | 2 +- .../test/scala/zio/sql/mysql/MysqlModuleSpec.scala | 12 +++++++++--- .../test/scala/zio/sql/mysql/TransactionSpec.scala | 6 +++--- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/mysql/src/test/resources/shop_schema.sql b/mysql/src/test/resources/shop_schema.sql index 40a323f66..c5ebea73c 100644 --- a/mysql/src/test/resources/shop_schema.sql +++ b/mysql/src/test/resources/shop_schema.sql @@ -49,7 +49,7 @@ values ('784426a5-b90a-4759-afbb-571b7a0ba35e', 'Mila', 'Paterso', true, '1990-11-16'), ('df8215a2-d5fd-4c6c-9984-801a1b3a2a0b', 'Alana', 'Murray', true, '1995-11-12'), ('636ae137-5b1a-4c8c-b11f-c47c624d9cdc', 'Jose', 'Wiggins', false, '1987-03-23'), - ('d4f6c156-20ac-4d27-8ced-535bf4315ebc', 'Robert', 'Rupert', false, '1998-06-11'); + ('d4f6c156-20ac-4d27-8ced-535bf4315ebc', 'Robert', 'Rupert', true, '1998-06-11'); insert into products (id, name, description, image_url) diff --git a/mysql/src/test/scala/zio/sql/mysql/CommonFunctionDefSpec.scala b/mysql/src/test/scala/zio/sql/mysql/CommonFunctionDefSpec.scala index 953588441..807cd1ed9 100644 --- a/mysql/src/test/scala/zio/sql/mysql/CommonFunctionDefSpec.scala +++ b/mysql/src/test/scala/zio/sql/mysql/CommonFunctionDefSpec.scala @@ -38,6 +38,7 @@ object CommonFunctionDefSpec extends MysqlRunnableSpec with Jdbc { "RonaldRonaldRussell", "TerrenceTerrenceNoel", "MilaMilaPaterso", + "RobertRobertRupert", "AlanaAlanaMurray", "JoseJoseWiggins" ) @@ -53,6 +54,7 @@ object CommonFunctionDefSpec extends MysqlRunnableSpec with Jdbc { "Person: Ronald Russell", "Person: Terrence Noel", "Person: Mila Paterso", + "Person: Robert Rupert", "Person: Alana Murray", "Person: Jose Wiggins" ) @@ -70,6 +72,7 @@ object CommonFunctionDefSpec extends MysqlRunnableSpec with Jdbc { "Name: Ronald and Surname: Russell", "Name: Terrence and Surname: Noel", "Name: Mila and Surname: Paterso", + "Name: Robert and Surname: Rupert", "Name: Alana and Surname: Murray", "Name: Jose and Surname: Wiggins" ) @@ -94,7 +97,7 @@ object CommonFunctionDefSpec extends MysqlRunnableSpec with Jdbc { val query = select(Concat(fName, lName) as "fullname") from customers - val expected = Seq("RonaldRussell", "TerrenceNoel", "MilaPaterso", "AlanaMurray", "JoseWiggins") + val expected = Seq("RonaldRussell", "TerrenceNoel", "MilaPaterso", "RobertRupert", "AlanaMurray", "JoseWiggins") val result = execute(query) diff --git a/mysql/src/test/scala/zio/sql/mysql/CustomFunctionDefSpec.scala b/mysql/src/test/scala/zio/sql/mysql/CustomFunctionDefSpec.scala index a2fdbb2db..3d17afc95 100644 --- a/mysql/src/test/scala/zio/sql/mysql/CustomFunctionDefSpec.scala +++ b/mysql/src/test/scala/zio/sql/mysql/CustomFunctionDefSpec.scala @@ -9,7 +9,7 @@ import zio.test.Assertion._ import zio.test._ import java.time.format.DateTimeFormatter -import java.time.{LocalDate, LocalTime, ZoneId} +import java.time.{ LocalDate, LocalTime, ZoneId } import java.util.UUID object CustomFunctionDefSpec extends MysqlRunnableSpec with Jdbc { diff --git a/mysql/src/test/scala/zio/sql/mysql/MysqlModuleSpec.scala b/mysql/src/test/scala/zio/sql/mysql/MysqlModuleSpec.scala index d3a423b6a..a5d39e588 100644 --- a/mysql/src/test/scala/zio/sql/mysql/MysqlModuleSpec.scala +++ b/mysql/src/test/scala/zio/sql/mysql/MysqlModuleSpec.scala @@ -79,8 +79,6 @@ object MysqlModuleSpec extends MysqlRunnableSpec { val query = select(customerId, fName, lName, dob) from customers - println(renderRead(query)) - val expected = Seq( Customer( @@ -112,6 +110,12 @@ object MysqlModuleSpec extends MysqlRunnableSpec { "Jose", "Wiggins", LocalDate.parse("1987-03-23") + ), + Customer( + UUID.fromString("d4f6c156-20ac-4d27-8ced-535bf4315ebc"), + "Robert", + "Rupert", + LocalDate.parse("1998-06-11") ) ) @@ -182,6 +186,7 @@ object MysqlModuleSpec extends MysqlRunnableSpec { UUID.fromString("60b01fc9-c902-4468-8d49-3c0f989def37"), UUID.fromString("f76c9ace-be07-4bf3-bd4c-4a9c62882e64"), UUID.fromString("784426a5-b90a-4759-afbb-571b7a0ba35e"), + UUID.fromString("d4f6c156-20ac-4d27-8ced-535bf4315ebc"), UUID.fromString("df8215a2-d5fd-4c6c-9984-801a1b3a2a0b"), UUID.fromString("636ae137-5b1a-4c8c-b11f-c47c624d9cdc") ) @@ -196,6 +201,7 @@ object MysqlModuleSpec extends MysqlRunnableSpec { UUID.fromString("60b01fc9-c902-4468-8d49-3c0f989def37"), UUID.fromString("f76c9ace-be07-4bf3-bd4c-4a9c62882e64"), UUID.fromString("784426a5-b90a-4759-afbb-571b7a0ba35e"), + UUID.fromString("d4f6c156-20ac-4d27-8ced-535bf4315ebc"), UUID.fromString("df8215a2-d5fd-4c6c-9984-801a1b3a2a0b"), UUID.fromString("636ae137-5b1a-4c8c-b11f-c47c624d9cdc") ) @@ -211,7 +217,7 @@ object MysqlModuleSpec extends MysqlRunnableSpec { for { r <- execute(query).runCollect - } yield assertTrue(r.head == 5L) + } yield assertTrue(r.head == 6L) }, test("Can select from joined tables (inner join)") { val query = select(fName, lName, orderDate) from (customers join orders).on( diff --git a/mysql/src/test/scala/zio/sql/mysql/TransactionSpec.scala b/mysql/src/test/scala/zio/sql/mysql/TransactionSpec.scala index b22d8ca8b..2750e914b 100644 --- a/mysql/src/test/scala/zio/sql/mysql/TransactionSpec.scala +++ b/mysql/src/test/scala/zio/sql/mysql/TransactionSpec.scala @@ -31,7 +31,7 @@ object TransactionSpec extends MysqlRunnableSpec with Jdbc { val assertion = result - .map(count => assertTrue(count == 5)) + .map(count => assertTrue(count == 6)) .orDie assertion.mapErrorCause(cause => Cause.stackless(cause.untraced)) @@ -48,7 +48,7 @@ object TransactionSpec extends MysqlRunnableSpec with Jdbc { remainingCustomersCount <- execute(query).map(identity[UUID](_)).runCount } yield (allCustomersCount, remainingCustomersCount)) - assertZIO(result)(equalTo((5L, 5L))).mapErrorCause(cause => Cause.stackless(cause.untraced)) + assertZIO(result)(equalTo((6L, 6L))).mapErrorCause(cause => Cause.stackless(cause.untraced)) }, test("Transaction succeeded and deleted rows") { val query = select(customerId) from customers @@ -62,7 +62,7 @@ object TransactionSpec extends MysqlRunnableSpec with Jdbc { remainingCustomersCount <- execute(query).map(identity[UUID](_)).runCount } yield (allCustomersCount, remainingCustomersCount)) - assertZIO(result)(equalTo((5L, 4L))).mapErrorCause(cause => Cause.stackless(cause.untraced)) + assertZIO(result)(equalTo((6L, 5L))).mapErrorCause(cause => Cause.stackless(cause.untraced)) } ) @@ sequential } From bc13907c9740a585dbf8596513301857f9746af0 Mon Sep 17 00:00:00 2001 From: Grigorii Berezin Date: Mon, 29 Apr 2024 08:27:22 +0300 Subject: [PATCH 3/4] feat: move soundsLike to Mysql module --- core/jvm/src/main/scala/zio/sql/expr/Expr.scala | 5 ----- mysql/src/main/scala/zio/sql/mysql/MysqlSqlModule.scala | 6 ++++++ .../test/scala/zio/sql/mysql/CustomFunctionDefSpec.scala | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/core/jvm/src/main/scala/zio/sql/expr/Expr.scala b/core/jvm/src/main/scala/zio/sql/expr/Expr.scala index 415097e3b..178e58b20 100644 --- a/core/jvm/src/main/scala/zio/sql/expr/Expr.scala +++ b/core/jvm/src/main/scala/zio/sql/expr/Expr.scala @@ -67,11 +67,6 @@ sealed trait Expr[-F, -A, +B] { self => ): Expr[F with F2, A1, Boolean] = Expr.Relational(self, that, RelationalOp.LessThanEqual) - def soundsLike[F2, A1 <: A](that: Expr[F2, A1, String])(implicit - ev: B <:< String - ): Expr[F with F2, A1, Boolean] = - Expr.Relational(self, that, RelationalOp.MySqlExtensions.SoundsLike) - def like[F2, A1 <: A](that: Expr[F2, A1, String])(implicit ev: B <:< String): Expr[F with F2, A1, Boolean] = Expr.Relational(self, that, RelationalOp.Like) diff --git a/mysql/src/main/scala/zio/sql/mysql/MysqlSqlModule.scala b/mysql/src/main/scala/zio/sql/mysql/MysqlSqlModule.scala index 6b116f2ce..975f68305 100644 --- a/mysql/src/main/scala/zio/sql/mysql/MysqlSqlModule.scala +++ b/mysql/src/main/scala/zio/sql/mysql/MysqlSqlModule.scala @@ -6,6 +6,7 @@ import java.util.UUID import zio.sql.Sql import zio.sql.select._ import zio.sql.expr._ +import zio.sql.ops.Operator.RelationalOp import zio.sql.typetag._ trait MysqlSqlModule extends Sql { self => @@ -24,6 +25,11 @@ trait MysqlSqlModule extends Sql { self => ) } } + + implicit class ExprOps[F1, A1, B](expr: Expr[F1, A1, B]) { + def soundsLike[F2, A2 <: A1](that: Expr[F2, A2, B])(implicit ev: B <:< String): Expr[F1 with F2, A2, Boolean] = + Expr.Relational(expr, that, RelationalOp.MySqlExtensions.SoundsLike) + } } object MysqlFunctionDef { diff --git a/mysql/src/test/scala/zio/sql/mysql/CustomFunctionDefSpec.scala b/mysql/src/test/scala/zio/sql/mysql/CustomFunctionDefSpec.scala index 3d17afc95..bcf4dad1c 100644 --- a/mysql/src/test/scala/zio/sql/mysql/CustomFunctionDefSpec.scala +++ b/mysql/src/test/scala/zio/sql/mysql/CustomFunctionDefSpec.scala @@ -15,6 +15,7 @@ import java.util.UUID object CustomFunctionDefSpec extends MysqlRunnableSpec with Jdbc { import MysqlFunctionDef._ + import MysqlSpecific._ case class Customers(id: UUID, dob: LocalDate, first_name: String, last_name: String, verified: Boolean) @@ -123,7 +124,7 @@ object CustomFunctionDefSpec extends MysqlRunnableSpec with Jdbc { assertZIO(testResult.runHead.some)(equalTo(expected)) }, test("sounds like") { - val query = select("Robert".soundsLike("Rupert")) + val query = select(literal("Robert").soundsLike("Rupert")) val testResult = execute(query) From b71d4c97f8578d3db459b95b280f1e74454c203a Mon Sep 17 00:00:00 2001 From: Grigorii Berezin Date: Mon, 29 Apr 2024 09:01:54 +0300 Subject: [PATCH 4/4] feat: improvement for sounds like on literal --- .../scala/zio/sql/mysql/MysqlSqlModule.scala | 17 +++++++--- .../zio/sql/mysql/CustomFunctionDefSpec.scala | 32 +++++++++++++++++-- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/mysql/src/main/scala/zio/sql/mysql/MysqlSqlModule.scala b/mysql/src/main/scala/zio/sql/mysql/MysqlSqlModule.scala index 975f68305..fd8fd84f9 100644 --- a/mysql/src/main/scala/zio/sql/mysql/MysqlSqlModule.scala +++ b/mysql/src/main/scala/zio/sql/mysql/MysqlSqlModule.scala @@ -1,13 +1,14 @@ package zio.sql.mysql -import java.time._ -import java.sql.ResultSet -import java.util.UUID -import zio.sql.Sql -import zio.sql.select._ import zio.sql.expr._ import zio.sql.ops.Operator.RelationalOp +import zio.sql.select._ import zio.sql.typetag._ +import zio.sql.{ Features, Sql } + +import java.sql.ResultSet +import java.time._ +import java.util.UUID trait MysqlSqlModule extends Sql { self => @@ -30,6 +31,12 @@ trait MysqlSqlModule extends Sql { self => def soundsLike[F2, A2 <: A1](that: Expr[F2, A2, B])(implicit ev: B <:< String): Expr[F1 with F2, A2, Boolean] = Expr.Relational(expr, that, RelationalOp.MySqlExtensions.SoundsLike) } + + implicit class LiteralOps[B](line: B)(implicit literal: B => Expr[Features.Literal, Any, B]) { + def soundsLike[F, A](that: Expr[F, A, B])(implicit + ev: B <:< String + ): Expr[Features.Literal with F, A, Boolean] = literal(line).soundsLike(that) + } } object MysqlFunctionDef { diff --git a/mysql/src/test/scala/zio/sql/mysql/CustomFunctionDefSpec.scala b/mysql/src/test/scala/zio/sql/mysql/CustomFunctionDefSpec.scala index bcf4dad1c..32ba491e2 100644 --- a/mysql/src/test/scala/zio/sql/mysql/CustomFunctionDefSpec.scala +++ b/mysql/src/test/scala/zio/sql/mysql/CustomFunctionDefSpec.scala @@ -3,7 +3,6 @@ package zio.sql.mysql import zio.Chunk import zio.schema._ import zio.sql.Jdbc -import zio.sql.expr.Expr.literal import zio.sql.table._ import zio.test.Assertion._ import zio.test._ @@ -124,7 +123,21 @@ object CustomFunctionDefSpec extends MysqlRunnableSpec with Jdbc { assertZIO(testResult.runHead.some)(equalTo(expected)) }, test("sounds like") { - val query = select(literal("Robert").soundsLike("Rupert")) + val query = select("Robert".soundsLike("Rupert")) + + val testResult = execute(query) + + assertZIO(testResult.runHead.some)(equalTo(true)) + }, + test("sounds like don't match") { + val query = select("Grisha".soundsLike("Berezin")) + + val testResult = execute(query) + + assertZIO(testResult.runHead.some)(equalTo(false)) + }, + test("sounds like don't match inverse") { + val query = select("Grisha".soundsLike("Berezin").isNotTrue) val testResult = execute(query) @@ -139,6 +152,21 @@ object CustomFunctionDefSpec extends MysqlRunnableSpec with Jdbc { result == Chunk(UUID.fromString("d4f6c156-20ac-4d27-8ced-535bf4315ebc")) ) }, + test("sounds like on column inverse") { + val query = select(customerId).from(customers).where(fName.soundsLike(lName).isNotTrue) + + for { + result <- execute(query).runCollect + } yield assertTrue( + result == Chunk( + UUID.fromString("60b01fc9-c902-4468-8d49-3c0f989def37"), + UUID.fromString("636ae137-5b1a-4c8c-b11f-c47c624d9cdc"), + UUID.fromString("784426a5-b90a-4759-afbb-571b7a0ba35e"), + UUID.fromString("df8215a2-d5fd-4c6c-9984-801a1b3a2a0b"), + UUID.fromString("f76c9ace-be07-4bf3-bd4c-4a9c62882e64") + ) + ) + }, test("current_date") { val query = select(CurrentDate)