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/main/scala/zio/sql/mysql/MysqlSqlModule.scala b/mysql/src/main/scala/zio/sql/mysql/MysqlSqlModule.scala index 6b116f2ce..fd8fd84f9 100644 --- a/mysql/src/main/scala/zio/sql/mysql/MysqlSqlModule.scala +++ b/mysql/src/main/scala/zio/sql/mysql/MysqlSqlModule.scala @@ -1,12 +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 => @@ -24,6 +26,17 @@ 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) + } + + 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/resources/shop_schema.sql b/mysql/src/test/resources/shop_schema.sql index 1ad8c80c9..c5ebea73c 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', 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 bfe405870..87038821c 100644 --- a/mysql/src/test/scala/zio/sql/mysql/CommonFunctionDefSpec.scala +++ b/mysql/src/test/scala/zio/sql/mysql/CommonFunctionDefSpec.scala @@ -39,6 +39,7 @@ object CommonFunctionDefSpec extends MysqlRunnableSpec with Jdbc { "RonaldRonaldRussell", "TerrenceTerrenceNoel", "MilaMilaPaterso", + "RobertRobertRupert", "AlanaAlanaMurray", "JoseJoseWiggins" ) @@ -54,6 +55,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" ) @@ -71,6 +73,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" ) @@ -95,7 +98,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 8347e7b9e..073aa6253 100644 --- a/mysql/src/test/scala/zio/sql/mysql/CustomFunctionDefSpec.scala +++ b/mysql/src/test/scala/zio/sql/mysql/CustomFunctionDefSpec.scala @@ -1,17 +1,20 @@ 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.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 { import MysqlFunctionDef._ + import MysqlSpecific._ case class Customers(id: UUID, dob: LocalDate, first_name: String, last_name: String, verified: Boolean) @@ -120,6 +123,51 @@ 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 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) + + 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("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) diff --git a/mysql/src/test/scala/zio/sql/mysql/MysqlModuleSpec.scala b/mysql/src/test/scala/zio/sql/mysql/MysqlModuleSpec.scala index 662ae3106..256c67c79 100644 --- a/mysql/src/test/scala/zio/sql/mysql/MysqlModuleSpec.scala +++ b/mysql/src/test/scala/zio/sql/mysql/MysqlModuleSpec.scala @@ -83,8 +83,6 @@ object MysqlModuleSpec extends MysqlRunnableSpec { val query = select(customerId, fName, lName, dob) from customers - println(renderRead(query)) - val expected = Seq( Customer( @@ -116,6 +114,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") ) ) @@ -186,6 +190,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") ) @@ -200,6 +205,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") ) @@ -215,7 +221,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 10e3f835e..479896522 100644 --- a/mysql/src/test/scala/zio/sql/mysql/TransactionSpec.scala +++ b/mysql/src/test/scala/zio/sql/mysql/TransactionSpec.scala @@ -32,7 +32,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)) @@ -49,7 +49,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 @@ -63,7 +63,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 }