diff --git a/README.md b/README.md index cffb3546e..011d54b2f 100644 --- a/README.md +++ b/README.md @@ -28,13 +28,13 @@ Connection pool | :white_check_mark: Feature | PostgreSQL | SQL Server | Oracle | MySQL :------------ | :-------------| :-------------|:-------------------| :------------- -Render Read | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +Render Read | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | Render Delete | :heavy_check_mark: | :heavy_check_mark: | :white_check_mark: | :white_check_mark: | -Render Update | :heavy_check_mark: | | :white_check_mark: | :white_check_mark: | -Render Insert | :heavy_check_mark: | | | +Render Update | :heavy_check_mark: | | :white_check_mark: | :white_check_mark: | +Render Insert | :heavy_check_mark: | | | :white_check_mark: | Functions | :heavy_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -Types | :white_check_mark: | | | :white_check_mark: | -Operators | | | | +Types | :white_check_mark: | | | :white_check_mark: | +Operators | | | | | ## What is ZIO SQL? ZIO SQL lets you write type-safe, type-inferred, and composable SQL queries in ordinary Scala, helping you prevent persistence bugs before they happen, and leverage your IDE to make writing SQL productive, safe, and fun. diff --git a/build.sbt b/build.sbt index 5c93dca58..9fd8a4c12 100644 --- a/build.sbt +++ b/build.sbt @@ -116,7 +116,7 @@ lazy val jdbc = project libraryDependencies ++= Seq( "dev.zio" %% "zio-test" % zioVersion % Test, "dev.zio" %% "zio-test-sbt" % zioVersion % Test, - "org.postgresql" % "postgresql" % "42.3.5" % Test, + "org.postgresql" % "postgresql" % "42.3.6" % Test, "com.dimafeng" %% "testcontainers-scala-postgresql" % testcontainersScalaVersion % Test ) ) @@ -178,7 +178,7 @@ lazy val postgres = project "org.testcontainers" % "database-commons" % testcontainersVersion % Test, "org.testcontainers" % "postgresql" % testcontainersVersion % Test, "org.testcontainers" % "jdbc" % testcontainersVersion % Test, - "org.postgresql" % "postgresql" % "42.3.5" % Compile, + "org.postgresql" % "postgresql" % "42.3.6" % Compile, "com.dimafeng" %% "testcontainers-scala-postgresql" % testcontainersScalaVersion % Test ) ) diff --git a/mysql/src/main/scala/zio/sql/mysql/MysqlSqlModule.scala b/mysql/src/main/scala/zio/sql/mysql/MysqlSqlModule.scala index cf32bb0c6..0e3b35625 100644 --- a/mysql/src/main/scala/zio/sql/mysql/MysqlSqlModule.scala +++ b/mysql/src/main/scala/zio/sql/mysql/MysqlSqlModule.scala @@ -41,6 +41,7 @@ trait MysqlSqlModule extends Sql { self => val MakeTime = FunctionDef[(Int, Int, Double), LocalTime](FunctionName("maketime")) val Now = FunctionDef[Any, ZonedDateTime](FunctionName("now")) val Pi = Expr.FunctionCall0[Double](FunctionDef[Any, Double](FunctionName("pi"))) + val Soundex = FunctionDef[String, String](FunctionName("soundex")) val Rand = FunctionDef[Int, Double](FunctionName("rand")) val RPad = FunctionDef[(String, Int, String), String](FunctionName("rpad")) val Uuid = Expr.FunctionCall0[UUID](FunctionDef[Any, UUID](FunctionName("uuid"))) diff --git a/mysql/src/test/scala/zio/sql/mysql/FunctionDefSpec.scala b/mysql/src/test/scala/zio/sql/mysql/FunctionDefSpec.scala index 030f42a25..3c5953820 100644 --- a/mysql/src/test/scala/zio/sql/mysql/FunctionDefSpec.scala +++ b/mysql/src/test/scala/zio/sql/mysql/FunctionDefSpec.scala @@ -121,6 +121,38 @@ object FunctionDefSpec extends MysqlRunnableSpec with ShopSchema { assertZIO(testResult.runHead.some)(equalTo(expected)) }, + test("soundex outputs should not match for non-similar-sounding strings") { + val queryForRobert = select(Soundex("Robert")) + val queryForTam = select(Soundex("Tam")) + + val resultForRobert = execute(queryForRobert) + val resultForTam = execute(queryForTam) + + for { + robertResult <- resultForRobert.runCollect + tamResult <- resultForTam.runCollect + } yield assert(robertResult.head.equals(tamResult.head))(equalTo(false)) + }, + test("soundex outputs should match for equivalent strings") { + val queryForRobert = select(Soundex("Robert")) + val queryForRupert = select(Soundex("Rupert")) + + val resultForRobert = execute(queryForRobert) + val resultForRupert = execute(queryForRupert) + + for { + robertResult <- resultForRobert.runCollect + rupertResult <- resultForRupert.runCollect + } yield assert(robertResult.head.equals(rupertResult.head))(equalTo(true)) + }, + test("soundex") { + val query = select(Soundex("Robert")) + val expected = "R163" + + val testResult = execute(query) + + assertZIO(testResult.runHead.some)(equalTo(expected)) + }, test("current_date") { val query = select(CurrentDate) diff --git a/postgres/src/main/scala/zio/sql/postgresql/PostgresSqlModule.scala b/postgres/src/main/scala/zio/sql/postgresql/PostgresSqlModule.scala index 817aa17e4..337147121 100644 --- a/postgres/src/main/scala/zio/sql/postgresql/PostgresSqlModule.scala +++ b/postgres/src/main/scala/zio/sql/postgresql/PostgresSqlModule.scala @@ -246,47 +246,36 @@ trait PostgresSqlModule extends Sql { self => object PostgresFunctionDef { import PostgresSpecific._ - val SplitPart = FunctionDef[(String, String, Int), String](FunctionName("split_part")) - val IsFinite = FunctionDef[Instant, Boolean](FunctionName("isfinite")) - val TimeOfDay = FunctionDef[Any, String](FunctionName("timeofday")) - val CurrentTime = Expr.ParenlessFunctionCall0[OffsetTime](FunctionName("current_time")) + val BitLength = FunctionDef[String, Int](FunctionName("bit_length")) + val CBRT = FunctionDef[Double, Double](FunctionName("cbrt")) val CharLength = FunctionDef[String, Int](FunctionName("character_length")) - val Localtime = Expr.ParenlessFunctionCall0[LocalTime](FunctionName("localtime")) - val LocaltimeWithPrecision = FunctionDef[Int, LocalTime](FunctionName("localtime")) - val Localtimestamp = Expr.ParenlessFunctionCall0[Instant](FunctionName("localtimestamp")) - val LocaltimestampWithPrecision = FunctionDef[Int, Instant](FunctionName("localtimestamp")) - val Md5 = FunctionDef[String, String](FunctionName("md5")) - val ParseIdent = FunctionDef[String, String](FunctionName("parse_ident")) val Chr = FunctionDef[Int, String](FunctionName("chr")) val CurrentDate = Expr.ParenlessFunctionCall0[LocalDate](FunctionName("current_date")) - val Initcap = FunctionDef[String, String](FunctionName("initcap")) - val Repeat = FunctionDef[(String, Int), String](FunctionName("repeat")) - val Reverse = FunctionDef[String, String](FunctionName("reverse")) - val TrimScale = FunctionDef[Double, Double](FunctionName("trim_scale")) - val Hex = FunctionDef[Int, String](FunctionName("to_hex")) - val Left = FunctionDef[(String, Int), String](FunctionName("left")) - val Length = FunctionDef[String, Int](FunctionName("length")) - val MinScale = FunctionDef[Double, Int](FunctionName("min_scale")) - val Radians = FunctionDef[Double, Double](FunctionName("radians")) - val Right = FunctionDef[(String, Int), String](FunctionName("right")) - val StartsWith = FunctionDef[(String, String), Boolean](FunctionName("starts_with")) - val Translate = FunctionDef[(String, String, String), String](FunctionName("translate")) - val Trunc = FunctionDef[Double, Double](FunctionName("trunc")) - val Sind = FunctionDef[Double, Double](FunctionName("sind")) - val GCD = FunctionDef[(Double, Double), Double](FunctionName("gcd")) - val LCM = FunctionDef[(Double, Double), Double](FunctionName("lcm")) - val CBRT = FunctionDef[Double, Double](FunctionName("cbrt")) + val CurrentTime = Expr.ParenlessFunctionCall0[OffsetTime](FunctionName("current_time")) + val Decode = FunctionDef[(String, String), Chunk[Byte]](FunctionName("decode")) val Degrees = FunctionDef[Double, Double](FunctionName("degrees")) val Div = FunctionDef[(Double, Double), Double](FunctionName("div")) + val Encode = FunctionDef[(Chunk[Byte], String), String](FunctionName("encode")) val Factorial = FunctionDef[Int, Int](FunctionName("factorial")) - val Random = FunctionDef[Any, Double](FunctionName("random")) + val Format0 = FunctionDef[String, String](FunctionName("format")) // TODO: varargs + val Format1 = FunctionDef[(String, Any), String](FunctionName("format")) + val Format2 = FunctionDef[(String, Any, Any), String](FunctionName("format")) + val Format3 = FunctionDef[(String, Any, Any, Any), String](FunctionName("format")) + val Format4 = FunctionDef[(String, Any, Any, Any, Any), String](FunctionName("format")) + val Format5 = FunctionDef[(String, Any, Any, Any, Any, Any), String](FunctionName("format")) + val GCD = FunctionDef[(Double, Double), Double](FunctionName("gcd")) + val GenRandomUuid = Expr.FunctionCall0[UUID](FunctionDef[Any, UUID](FunctionName("gen_random_uuid"))) + val Hex = FunctionDef[Int, String](FunctionName("to_hex")) + val Initcap = FunctionDef[String, String](FunctionName("initcap")) + val IsFinite = FunctionDef[Instant, Boolean](FunctionName("isfinite")) + val LCM = FunctionDef[(Double, Double), Double](FunctionName("lcm")) + val Left = FunctionDef[(String, Int), String](FunctionName("left")) + val Length = FunctionDef[String, Int](FunctionName("length")) + val Localtime = Expr.ParenlessFunctionCall0[LocalTime](FunctionName("localtime")) + val Localtimestamp = Expr.ParenlessFunctionCall0[Instant](FunctionName("localtimestamp")) + val LocaltimestampWithPrecision = FunctionDef[Int, Instant](FunctionName("localtimestamp")) + val LocaltimeWithPrecision = FunctionDef[Int, LocalTime](FunctionName("localtime")) val LPad = FunctionDef[(String, Int, String), String](FunctionName("lpad")) - val RPad = FunctionDef[(String, Int, String), String](FunctionName("rpad")) - val ToTimestamp = FunctionDef[Long, ZonedDateTime](FunctionName("to_timestamp")) - val PgClientEncoding = FunctionDef[Any, String](FunctionName("pg_client_encoding")) - val Now = FunctionDef[Any, ZonedDateTime](FunctionName("now")) - val StatementTimestamp = FunctionDef[Any, ZonedDateTime](FunctionName("statement_timestamp")) - val TransactionTimestamp = FunctionDef[Any, ZonedDateTime](FunctionName("transaction_timestamp")) val MakeDate = FunctionDef[(Int, Int, Int), LocalDate](FunctionName("make_date")) val MakeInterval = FunctionDef[Interval, Interval](FunctionName("make_interval")) val MakeTime = FunctionDef[(Int, Int, Double), LocalTime](FunctionName("make_time")) @@ -296,18 +285,29 @@ trait PostgresSqlModule extends Sql { self => ) val MakeTimestampz = FunctionDef[Timestampz, Timestampz](FunctionName("make_timestamptz")) - val Encode = FunctionDef[(Chunk[Byte], String), String](FunctionName("encode")) - val Decode = FunctionDef[(String, String), Chunk[Byte]](FunctionName("decode")) - val Format0 = FunctionDef[String, String](FunctionName("format")) // TODO: varargs - val Format1 = FunctionDef[(String, Any), String](FunctionName("format")) - val Format2 = FunctionDef[(String, Any, Any), String](FunctionName("format")) - val Format3 = FunctionDef[(String, Any, Any, Any), String](FunctionName("format")) - val Format4 = FunctionDef[(String, Any, Any, Any, Any), String](FunctionName("format")) - val Format5 = FunctionDef[(String, Any, Any, Any, Any, Any), String](FunctionName("format")) - val SetSeed = FunctionDef[Double, Unit](FunctionName("setseed")) - val BitLength = FunctionDef[String, Int](FunctionName("bit_length")) + val Md5 = FunctionDef[String, String](FunctionName("md5")) + val MinScale = FunctionDef[Double, Int](FunctionName("min_scale")) + val Now = FunctionDef[Any, ZonedDateTime](FunctionName("now")) + val ParseIdent = FunctionDef[String, String](FunctionName("parse_ident")) + val PgClientEncoding = FunctionDef[Any, String](FunctionName("pg_client_encoding")) val Pi = Expr.FunctionCall0[Double](FunctionDef[Any, Double](FunctionName("pi"))) - val GenRandomUuid = Expr.FunctionCall0[UUID](FunctionDef[Any, UUID](FunctionName("gen_random_uuid"))) + val Radians = FunctionDef[Double, Double](FunctionName("radians")) + val Random = FunctionDef[Any, Double](FunctionName("random")) + val Repeat = FunctionDef[(String, Int), String](FunctionName("repeat")) + val Reverse = FunctionDef[String, String](FunctionName("reverse")) + val Right = FunctionDef[(String, Int), String](FunctionName("right")) + val RPad = FunctionDef[(String, Int, String), String](FunctionName("rpad")) + val SetSeed = FunctionDef[Double, Unit](FunctionName("setseed")) + val Sind = FunctionDef[Double, Double](FunctionName("sind")) + val SplitPart = FunctionDef[(String, String, Int), String](FunctionName("split_part")) + val StartsWith = FunctionDef[(String, String), Boolean](FunctionName("starts_with")) + val StatementTimestamp = FunctionDef[Any, ZonedDateTime](FunctionName("statement_timestamp")) + val TimeOfDay = FunctionDef[Any, String](FunctionName("timeofday")) + val ToTimestamp = FunctionDef[Long, ZonedDateTime](FunctionName("to_timestamp")) + val TransactionTimestamp = FunctionDef[Any, ZonedDateTime](FunctionName("transaction_timestamp")) + val Translate = FunctionDef[(String, String, String), String](FunctionName("translate")) + val TrimScale = FunctionDef[Double, Double](FunctionName("trim_scale")) + val Trunc = FunctionDef[Double, Double](FunctionName("trunc")) } } diff --git a/postgres/src/test/scala/zio/sql/postgresql/FunctionDefSpec.scala b/postgres/src/test/scala/zio/sql/postgresql/FunctionDefSpec.scala index 457adfbdc..7d96f1b8e 100644 --- a/postgres/src/test/scala/zio/sql/postgresql/FunctionDefSpec.scala +++ b/postgres/src/test/scala/zio/sql/postgresql/FunctionDefSpec.scala @@ -30,15 +30,9 @@ object FunctionDefSpec extends PostgresRunnableSpec with DbSchema { import Expr._ // note: a plain number (3) would and should not compile - val query = select(ConcatWs4("+", "1", "2", "3")) from customers - - val expected = Seq( // note: one for each row - "1+2+3", - "1+2+3", - "1+2+3", - "1+2+3", - "1+2+3" - ) + val query = select(ConcatWs4("+", "1", "2", "3")) + + val expected = Seq("1+2+3") val testResult = execute(query) collectAndCompare(expected, testResult) @@ -124,15 +118,9 @@ object FunctionDefSpec extends PostgresRunnableSpec with DbSchema { test("format0") { import Expr._ - val query = select(Format0("Person")) from customers + val query = select(Format0("Person")) - val expected = Seq( - "Person", - "Person", - "Person", - "Person", - "Person" - ) + val expected = Seq("Person") val testResult = execute(query) collectAndCompare(expected, testResult) @@ -356,11 +344,7 @@ object FunctionDefSpec extends PostgresRunnableSpec with DbSchema { val testResult = execute(query) - val assertion = for { - r <- testResult.runCollect - } yield assert(r.head)(equalTo(expected)) - - assertion.mapErrorCause(cause => Cause.stackless(cause.untraced)) + assertZIO(testResult.runHead.some)(equalTo(expected)) }, suite("parseIdent")( test("parseIdent removes quoting of individual identifiers") { @@ -373,26 +357,18 @@ object FunctionDefSpec extends PostgresRunnableSpec with DbSchema { string2 <- someString } yield s""""${string1}".${string2}""" - val assertion = check(genTestString) { (testString) => + check(genTestString) { (testString) => val query = select(ParseIdent(testString)) val testResult = execute(query) - for { - r <- testResult.runCollect - } yield assert(r.head)(not(containsString("'")) && not(containsString("\""))) - + assertZIO(testResult.runHead.some)(not(containsString("'")) && not(containsString("\""))) } - assertion.mapErrorCause(cause => Cause.stackless(cause.untraced)) }, test("parseIdent fails with invalid identifier") { val query = select(ParseIdent("\'\"SomeSchema\".someTable.\'")) val testResult = execute(query) - val assertion = for { - r <- testResult.runCollect.exit - } yield assert(r)(fails(anything)) - - assertion.mapErrorCause(cause => Cause.stackless(cause.untraced)) + assertZIO(testResult.runCollect.exit)(fails(anything)) } ) @@ ignore, test("sqrt") { @@ -402,11 +378,7 @@ object FunctionDefSpec extends PostgresRunnableSpec with DbSchema { val testResult = execute(query) - val assertion = for { - r <- testResult.runCollect - } yield assert(r.head)(equalTo(expected)) - - assertion.mapErrorCause(cause => Cause.stackless(cause.untraced)) + assertZIO(testResult.runHead.some)(equalTo(expected)) }, test("chr") { val query = select(Chr(65)) @@ -876,16 +848,12 @@ object FunctionDefSpec extends PostgresRunnableSpec with DbSchema { assertion.mapErrorCause(cause => Cause.stackless(cause.untraced)) }, test("setseed") { - val query = select(SetSeed(0.12), Random(), Random()) from customers + val query = select(SetSeed(0.12), Random(), Random()) val randomTupleForSeed = (0.019967750719779076, 0.8378369929936333) val testResult = execute(query).map { case (_, b, c) => (b, c) } - val assertion = for { - r <- testResult.runCollect - } yield assert(r.take(2))(equalTo(Chunk(randomTupleForSeed, randomTupleForSeed))) - - assertion.mapErrorCause(cause => Cause.stackless(cause.untraced)) + assertZIO(testResult.runHead.some)(equalTo(randomTupleForSeed)) }, test("Can concat strings with concat function") {