From d4657990da43782c332eec1f0e3dc7a49e6e4dc8 Mon Sep 17 00:00:00 2001 From: Grigorii Berezin Date: Mon, 29 Apr 2024 15:55:59 +0300 Subject: [PATCH 1/2] - add `show` method for statements - add tests on `show` for all sql dialects - add `show` info in README --- README.md | 10 +-- core/jvm/src/main/scala/zio/sql/Sql.scala | 16 ++++ .../zio/sql/mysql/MySqlQueryShowSpec.scala | 86 +++++++++++++++++++ .../sql/oracle/OracleSqlQueryShowSpec.scala | 86 +++++++++++++++++++ .../postgresql/PostgresSqlQueryShowSpec.scala | 86 +++++++++++++++++++ .../sqlserver/SqlServerQueryShowSpec.scala | 86 +++++++++++++++++++ 6 files changed, 365 insertions(+), 5 deletions(-) create mode 100644 mysql/src/test/scala/zio/sql/mysql/MySqlQueryShowSpec.scala create mode 100644 oracle/src/test/scala/zio/sql/oracle/OracleSqlQueryShowSpec.scala create mode 100644 postgres/src/test/scala/zio/sql/postgresql/PostgresSqlQueryShowSpec.scala create mode 100644 sqlserver/src/test/scala/zio/sql/sqlserver/SqlServerQueryShowSpec.scala diff --git a/README.md b/README.md index e4f545e0b..dc084e574 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ 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. -[![Development](https://img.shields.io/badge/Project%20Stage-Development-green.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-sql/workflows/CI/badge.svg) [![Sonatype Releases](https://img.shields.io/nexus/r/https/oss.sonatype.org/dev.zio/zio-sql_2.13.svg?label=Sonatype%20Release)](https://oss.sonatype.org/content/repositories/releases/dev/zio/zio-sql_2.13/) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-sql_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-sql_2.13/) [![javadoc](https://javadoc.io/badge2/dev.zio/zio-sql-docs_2.13/javadoc.svg)](https://javadoc.io/doc/dev.zio/zio-sql-docs_2.13) [![ZIO SQL](https://img.shields.io/github/stars/zio/zio-sql?style=social)](https://github.com/zio/zio-sql) +[![Development](https://img.shields.io/badge/Project%20Stage-Development-green.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-sql/workflows/CI/badge.svg) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-sql_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-sql_2.13/) [![ZIO SQL](https://img.shields.io/github/stars/zio/zio-sql?style=social)](https://github.com/zio/zio-sql) ## Introduction @@ -63,16 +63,16 @@ ZIO SQL is packaged into separate modules for different databases. Depending on ```scala //PostgreSQL -libraryDependencies += "dev.zio" %% "zio-sql-postgres" % "0.1.2" +libraryDependencies += "dev.zio" %% "zio-sql-postgres" % "" //MySQL -libraryDependencies += "dev.zio" %% "zio-sql-mysql" % "0.1.2" +libraryDependencies += "dev.zio" %% "zio-sql-mysql" % "" //Oracle -libraryDependencies += "dev.zio" %% "zio-sql-oracle" % "0.1.2" +libraryDependencies += "dev.zio" %% "zio-sql-oracle" % "" //SQL Server -libraryDependencies += "dev.zio" %% "zio-sql-sqlserver" % "0.1.2" +libraryDependencies += "dev.zio" %% "zio-sql-sqlserver" % "" ``` ## Imports and modules diff --git a/core/jvm/src/main/scala/zio/sql/Sql.scala b/core/jvm/src/main/scala/zio/sql/Sql.scala index 7e1b9cd67..463003597 100644 --- a/core/jvm/src/main/scala/zio/sql/Sql.scala +++ b/core/jvm/src/main/scala/zio/sql/Sql.scala @@ -54,6 +54,22 @@ trait Sql { def renderInsert[A: Schema](insert: Insert[_, A]): SqlStatement + implicit class readOps(read: Read[_]) { + def show: String = renderRead(read) + } + + implicit class updateOps(update: Update[_]) { + def show: String = renderUpdate(update) + } + + implicit class insertOps[A: Schema](insert: Insert[_, A]) { + def show: String = renderInsert(insert).query + } + + implicit class deleteOps(delete: Delete[_]) { + def show: String = renderDelete(delete) + } + // TODO don't know where to put it now implicit def convertOptionToSome[A](implicit op: Schema[Option[A]]): Schema[Some[A]] = op.transformOrFail[Some[A]]( diff --git a/mysql/src/test/scala/zio/sql/mysql/MySqlQueryShowSpec.scala b/mysql/src/test/scala/zio/sql/mysql/MySqlQueryShowSpec.scala new file mode 100644 index 000000000..ae5f40504 --- /dev/null +++ b/mysql/src/test/scala/zio/sql/mysql/MySqlQueryShowSpec.scala @@ -0,0 +1,86 @@ +package zio.sql.mysql + +import zio.Scope +import zio.schema.DeriveSchema +import zio.sql.table.Table._ +import zio.test._ + +import java.time._ +import java.util.UUID + +object MySqlQueryShowSpec extends ZIOSpecDefault with MysqlRenderModule { + final case class Product(id: UUID, name: String, price: Double) + + object Product { + implicit val productSchema = DeriveSchema.gen[Product] + val products = defineTableSmart[Product] + val (id, name, price) = products.columns + } + + final case class Order(id: UUID, productId: UUID, quantity: Int, orderDate: LocalDate) + + object Order { + implicit val orderSchema = DeriveSchema.gen[Order] + val orders = defineTable[Order] + val (orderId, productId, quantity, date) = orders.columns + } + + override def spec: Spec[TestEnvironment with Scope, Any] = suite("MySqlQueryShow")( + test("rendering select") { + import Order._ + import Product._ + + val selectQueryRender = + select(orderId, name) + .from( + products + .join(orders) + .on(productId === id) + ) + .limit(5) + .offset(10) + .show + + val expectedQuery = + "SELECT order.id, products.name FROM products INNER JOIN order ON order.product_id = products.id LIMIT 5 OFFSET 10" + + assertTrue(selectQueryRender == expectedQuery) + }, + test("rendering insert") { + import Product._ + + def insertProduct(uuid: UUID) = + insertInto(products)(id, name, price) + .values((uuid, "Zionomicon", 10.5)) + + val expectedQuery = "INSERT INTO products (id, name, price) VALUES (?, ?, ?);" + + assertTrue(insertProduct(UUID.fromString("dd5a7ae7-de19-446a-87a4-576d79de5c83")).show == expectedQuery) + }, + test("rendering update") { + import Product._ + + def updateProduct(uuid: UUID) = + update(products) + .set(name, "foo") + .set(price, price * 1.1) + .where(id === uuid) + + val expectedQuery = + "UPDATE products SET products.name = 'foo', products.price = products.price * 1.1 WHERE products.id = 'f1e69839-964f-44b7-b90d-bd5f51700540'" + + assertTrue(updateProduct(UUID.fromString("f1e69839-964f-44b7-b90d-bd5f51700540")).show == expectedQuery) + }, + test("rendering delete") { + import Product._ + + def deleteProduct(uuid: UUID) = + deleteFrom(products) + .where(id === uuid) + + val expectedQuery = "DELETE FROM products WHERE products.id = '95625b37-e785-4b4f-86b1-69affaf5f848'" + + assertTrue(deleteProduct(UUID.fromString("95625b37-e785-4b4f-86b1-69affaf5f848")).show == expectedQuery) + } + ) +} diff --git a/oracle/src/test/scala/zio/sql/oracle/OracleSqlQueryShowSpec.scala b/oracle/src/test/scala/zio/sql/oracle/OracleSqlQueryShowSpec.scala new file mode 100644 index 000000000..7f2ee7951 --- /dev/null +++ b/oracle/src/test/scala/zio/sql/oracle/OracleSqlQueryShowSpec.scala @@ -0,0 +1,86 @@ +package zio.sql.oracle + +import zio.Scope +import zio.schema.DeriveSchema +import zio.sql.table.Table._ +import zio.test._ + +import java.time._ +import java.util.UUID + +object OracleSqlQueryShowSpec extends ZIOSpecDefault with OracleRenderModule { + final case class Product(id: UUID, name: String, price: Double) + + object Product { + implicit val productSchema = DeriveSchema.gen[Product] + val products = defineTableSmart[Product] + val (id, name, price) = products.columns + } + + final case class Order(id: UUID, productId: UUID, quantity: Int, orderDate: LocalDate) + + object Order { + implicit val orderSchema = DeriveSchema.gen[Order] + val orders = defineTable[Order] + val (orderId, productId, quantity, date) = orders.columns + } + + override def spec: Spec[TestEnvironment with Scope, Any] = suite("OracleSqlQueryShow")( + test("rendering select") { + import Order._ + import Product._ + + val selectQueryRender = + select(orderId, name) + .from( + products + .join(orders) + .on(productId === id) + ) + .limit(5) + .offset(10) + .show + + val expectedQuery = + "SELECT order.id, products.name FROM products INNER JOIN order ON order.product_id = products.id WHERE rownum <= 5" + + assertTrue(selectQueryRender == expectedQuery) + }, + test("rendering insert") { + import Product._ + + def insertProduct(uuid: UUID) = + insertInto(products)(id, name, price) + .values((uuid, "Zionomicon", 10.5)) + + val expectedQuery = "INSERT INTO products (id, name, price) VALUES (?, ?, ?)" + + assertTrue(insertProduct(UUID.fromString("dd5a7ae7-de19-446a-87a4-576d79de5c83")).show == expectedQuery) + }, + test("rendering update") { + import Product._ + + def updateProduct(uuid: UUID) = + update(products) + .set(name, "foo") + .set(price, price * 1.1) + .where(id === uuid) + + val expectedQuery = + "UPDATE products SET products.name = N'foo', products.price = products.price * 1.1 WHERE products.id = 'f1e69839-964f-44b7-b90d-bd5f51700540'" + + assertTrue(updateProduct(UUID.fromString("f1e69839-964f-44b7-b90d-bd5f51700540")).show == expectedQuery) + }, + test("rendering delete") { + import Product._ + + def deleteProduct(uuid: UUID) = + deleteFrom(products) + .where(id === uuid) + + val expectedQuery = "DELETE FROM products WHERE products.id = '95625b37-e785-4b4f-86b1-69affaf5f848'" + + assertTrue(deleteProduct(UUID.fromString("95625b37-e785-4b4f-86b1-69affaf5f848")).show == expectedQuery) + } + ) +} diff --git a/postgres/src/test/scala/zio/sql/postgresql/PostgresSqlQueryShowSpec.scala b/postgres/src/test/scala/zio/sql/postgresql/PostgresSqlQueryShowSpec.scala new file mode 100644 index 000000000..ee4faa1ab --- /dev/null +++ b/postgres/src/test/scala/zio/sql/postgresql/PostgresSqlQueryShowSpec.scala @@ -0,0 +1,86 @@ +package zio.sql.postgresql + +import zio.Scope +import zio.schema.DeriveSchema +import zio.sql.table.Table._ +import zio.test._ + +import java.time._ +import java.util.UUID + +object PostgresSqlQueryShowSpec extends ZIOSpecDefault with PostgresRenderModule { + final case class Product(id: UUID, name: String, price: Double) + + object Product { + implicit val productSchema = DeriveSchema.gen[Product] + val products = defineTableSmart[Product] + val (id, name, price) = products.columns + } + + final case class Order(id: UUID, productId: UUID, quantity: Int, orderDate: LocalDate) + + object Order { + implicit val orderSchema = DeriveSchema.gen[Order] + val orders = defineTable[Order] + val (orderId, productId, quantity, date) = orders.columns + } + + override def spec: Spec[TestEnvironment with Scope, Any] = suite("PostgresSqlQueryShow")( + test("rendering select") { + import Order._ + import Product._ + + val selectQueryRender = + select(orderId, name) + .from( + products + .join(orders) + .on(productId === id) + ) + .limit(5) + .offset(10) + .show + + val expectedQuery = + "SELECT \"order\".\"id\", \"products\".\"name\" FROM \"products\" INNER JOIN \"order\" ON \"order\".\"product_id\" = \"products\".\"id\" LIMIT 5 OFFSET 10" + + assertTrue(selectQueryRender == expectedQuery) + }, + test("rendering insert") { + import Product._ + + def insertProduct(uuid: UUID) = + insertInto(products)(id, name, price) + .values((uuid, "Zionomicon", 10.5)) + + val expectedQuery = "INSERT INTO \"products\" (\"id\", \"name\", \"price\") VALUES (?, ?, ?);" + + assertTrue(insertProduct(UUID.fromString("dd5a7ae7-de19-446a-87a4-576d79de5c83")).show == expectedQuery) + }, + test("rendering update") { + import Product._ + + def updateProduct(uuid: UUID) = + update(products) + .set(name, "foo") + .set(price, price * 1.1) + .where(id === uuid) + + val expectedQuery = + "UPDATE \"products\" SET \"name\" = 'foo', \"price\" = \"products\".\"price\" * 1.1 WHERE \"products\".\"id\" = 'f1e69839-964f-44b7-b90d-bd5f51700540'" + + assertTrue(updateProduct(UUID.fromString("f1e69839-964f-44b7-b90d-bd5f51700540")).show == expectedQuery) + }, + test("rendering delete") { + import Product._ + + def deleteProduct(uuid: UUID) = + deleteFrom(products) + .where(id === uuid) + + val expectedQuery = "DELETE FROM \"products\" WHERE \"products\".\"id\" = '95625b37-e785-4b4f-86b1-69affaf5f848'" + + assertTrue(deleteProduct(UUID.fromString("95625b37-e785-4b4f-86b1-69affaf5f848")).show == expectedQuery) + } + ) +} diff --git a/sqlserver/src/test/scala/zio/sql/sqlserver/SqlServerQueryShowSpec.scala b/sqlserver/src/test/scala/zio/sql/sqlserver/SqlServerQueryShowSpec.scala new file mode 100644 index 000000000..214a67858 --- /dev/null +++ b/sqlserver/src/test/scala/zio/sql/sqlserver/SqlServerQueryShowSpec.scala @@ -0,0 +1,86 @@ +package zio.sql.sqlserver + +import zio.Scope +import zio.schema.DeriveSchema +import zio.sql.table.Table._ +import zio.test._ + +import java.time._ +import java.util.UUID + +object SqlServerQueryShowSpec extends ZIOSpecDefault with SqlServerRenderModule { + final case class Product(id: UUID, name: String, price: Double) + + object Product { + implicit val productSchema = DeriveSchema.gen[Product] + val products = defineTableSmart[Product] + val (id, name, price) = products.columns + } + + final case class Order(id: UUID, productId: UUID, quantity: Int, orderDate: LocalDate) + + object Order { + implicit val orderSchema = DeriveSchema.gen[Order] + val orders = defineTable[Order] + val (orderId, productId, quantity, date) = orders.columns + } + + override def spec: Spec[TestEnvironment with Scope, Any] = suite("SqlServerQueryShow")( + test("rendering select") { + import Order._ + import Product._ + + val selectQueryRender = + select(orderId, name) + .from( + products + .join(orders) + .on(productId === id) + ) + .limit(5) + .offset(10) + .show + + val expectedQuery = + "SELECT TOP 5 order.id, products.name FROM products inner join order on order.product_id = products.id " + + assertTrue(selectQueryRender == expectedQuery) + }, + test("rendering insert") { + import Product._ + + def insertProduct(uuid: UUID) = + insertInto(products)(id, name, price) + .values((uuid, "Zionomicon", 10.5)) + + val expectedQuery = "INSERT INTO products (id, name, price) VALUES (?, ?, ?);" + + assertTrue(insertProduct(UUID.fromString("dd5a7ae7-de19-446a-87a4-576d79de5c83")).show == expectedQuery) + }, + test("rendering update") { + import Product._ + + def updateProduct(uuid: UUID) = + update(products) + .set(name, "foo") + .set(price, price * 1.1) + .where(id === uuid) + + val expectedQuery = + "UPDATE products SET products.name = N'foo', products.price = products.price * 1.1 WHERE products.id = 'f1e69839-964f-44b7-b90d-bd5f51700540'" + + assertTrue(updateProduct(UUID.fromString("f1e69839-964f-44b7-b90d-bd5f51700540")).show == expectedQuery) + }, + test("rendering delete") { + import Product._ + + def deleteProduct(uuid: UUID) = + deleteFrom(products) + .where(id === uuid) + + val expectedQuery = "DELETE FROM products WHERE products.id = '95625b37-e785-4b4f-86b1-69affaf5f848'" + + assertTrue(deleteProduct(UUID.fromString("95625b37-e785-4b4f-86b1-69affaf5f848")).show == expectedQuery) + } + ) +} From 80f7afd7e1e3f5e4b2fc65d496a0ead3c37bc7d0 Mon Sep 17 00:00:00 2001 From: Grigorii Berezin Date: Mon, 29 Apr 2024 16:11:29 +0300 Subject: [PATCH 2/2] - add `show` method for statements - add tests on `show` for all sql dialects - add `show` info in README --- README.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index dc084e574..27584e922 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ 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. -[![Development](https://img.shields.io/badge/Project%20Stage-Development-green.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-sql/workflows/CI/badge.svg) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-sql_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-sql_2.13/) [![ZIO SQL](https://img.shields.io/github/stars/zio/zio-sql?style=social)](https://github.com/zio/zio-sql) +[![Development](https://img.shields.io/badge/Project%20Stage-Development-green.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-sql/workflows/CI/badge.svg) [![Sonatype Releases](https://img.shields.io/nexus/r/https/oss.sonatype.org/dev.zio/zio-sql_2.13.svg?label=Sonatype%20Release)](https://oss.sonatype.org/content/repositories/releases/dev/zio/zio-sql_2.13/) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-sql_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-sql_2.13/) [![javadoc](https://javadoc.io/badge2/dev.zio/zio-sql-docs_2.13/javadoc.svg)](https://javadoc.io/doc/dev.zio/zio-sql-docs_2.13) [![ZIO SQL](https://img.shields.io/github/stars/zio/zio-sql?style=social)](https://github.com/zio/zio-sql) ## Introduction @@ -63,16 +63,16 @@ ZIO SQL is packaged into separate modules for different databases. Depending on ```scala //PostgreSQL -libraryDependencies += "dev.zio" %% "zio-sql-postgres" % "" +libraryDependencies += "dev.zio" %% "zio-sql-postgres" % "0.1.2" //MySQL -libraryDependencies += "dev.zio" %% "zio-sql-mysql" % "" +libraryDependencies += "dev.zio" %% "zio-sql-mysql" % "0.1.2" //Oracle -libraryDependencies += "dev.zio" %% "zio-sql-oracle" % "" +libraryDependencies += "dev.zio" %% "zio-sql-oracle" % "0.1.2" //SQL Server -libraryDependencies += "dev.zio" %% "zio-sql-sqlserver" % "" +libraryDependencies += "dev.zio" %% "zio-sql-sqlserver" % "0.1.2" ``` ## Imports and modules @@ -225,7 +225,52 @@ TODO: details ## Printing queries -TODO: details +### Select + +```scala +select(orderId, name) + .from(products.join(orders) + .on(productId === id)) + .limit(5) + .offset(10) + .show +// val res0: String = SELECT "order"."id", "products"."name" FROM "products" INNER JOIN "order" ON "order"."product_id" = "products"."id" LIMIT 5 OFFSET 10 +``` + +### Insert + +```scala +def insertProduct(uuid: UUID) = + insertInto(products)(id, name, price) + .values((uuid, "Zionomicon", 10.5)) + +insertProduct(UUID.fromString("dd5a7ae7-de19-446a-87a4-576d79de5c83")).show +// val res0: String = INSERT INTO "products" ("id", "name", "price") VALUES (?, ?, ?); +``` + +### Update + +```scala +def updateProduct(uuid: UUID) = + update(products) + .set(name, "foo") + .set(price, price * 1.1) + .where(id === uuid) + +updateProduct(UUID.fromString("f1e69839-964f-44b7-b90d-bd5f51700540")).show +// val res0: String = UPDATE "products" SET "name" = 'foo', "price" = "products"."price" * 1.1 WHERE "products"."id" = 'f1e69839-964f-44b7-b90d-bd5f51700540' +``` + +### Delete + +```scala +def deleteProduct(uuid: UUID) = + deleteFrom(products) + .where(id === uuid) + +deleteProduct(UUID.fromString("95625b37-e785-4b4f-86b1-69affaf5f848")).show +// val res0: String = DELETE FROM "products" WHERE "products"."id" = '95625b37-e785-4b4f-86b1-69affaf5f848' +``` ## Running queries