Skip to content

Commit

Permalink
Merge branch 'master' into 155
Browse files Browse the repository at this point in the history
  • Loading branch information
sviezypan authored May 27, 2022
2 parents f58375b + 894f6b1 commit 1532031
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 39 deletions.
133 changes: 131 additions & 2 deletions mysql/src/main/scala/zio/sql/mysql/MysqlRenderModule.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package zio.sql.mysql

import java.time._
import java.time.format.DateTimeFormatter

import zio.Chunk
import zio.schema.Schema
import zio.schema._
import zio.schema.StandardType._
import zio.sql.driver.Renderer

trait MysqlRenderModule extends MysqlSqlModule { self =>
Expand All @@ -12,7 +16,11 @@ trait MysqlRenderModule extends MysqlSqlModule { self =>
render.toString
}

override def renderInsert[A: Schema](insert: self.Insert[_, A]): String = ???
override def renderInsert[A: Schema](insert: self.Insert[_, A]): String = {
implicit val render: Renderer = Renderer()
MysqlRenderer.renderInsertImpl(insert)
render.toString
}

override def renderDelete(delete: self.Delete[_]): String = {
implicit val render: Renderer = Renderer()
Expand All @@ -27,6 +35,17 @@ trait MysqlRenderModule extends MysqlSqlModule { self =>
}

object MysqlRenderer {
def renderInsertImpl[A](insert: Insert[_, A])(implicit render: Renderer, schema: Schema[A]) = {
render("INSERT INTO ")
renderTable(insert.table)

render(" (")
renderColumnNames(insert.sources)
render(") VALUES ")

renderInsertValues(insert.values)
}

def renderDeleteImpl(delete: Delete[_])(implicit render: Renderer) = {
render("DELETE FROM ")
renderTable(delete.table)
Expand Down Expand Up @@ -114,6 +133,114 @@ trait MysqlRenderModule extends MysqlSqlModule { self =>
render(" (", values.mkString(","), ") ") // todo fix needs escaping
}

private def renderInsertValues[A](col: Seq[A])(implicit render: Renderer, schema: Schema[A]): Unit =
col.toList match {
case head :: Nil =>
render("(")
renderInsertValue(head)
render(");")
case head :: next =>
render("(")
renderInsertValue(head)(render, schema)
render(" ),")
renderInsertValues(next)
case Nil => ()
}

private def renderInsertValue[Z](z: Z)(implicit render: Renderer, schema: Schema[Z]): Unit =
schema.toDynamic(z) match {
case DynamicValue.Record(listMap) =>
listMap.values.toList match {
case head :: Nil => renderDynamicValue(head)
case head :: next =>
renderDynamicValue(head)
render(", ")
renderDynamicValues(next)
case Nil => ()
}
case value => renderDynamicValue(value)
}

private def renderDynamicValues(dynValues: List[DynamicValue])(implicit render: Renderer): Unit =
dynValues match {
case head :: Nil => renderDynamicValue(head)
case head :: tail =>
renderDynamicValue(head)
render(", ")
renderDynamicValues(tail)
case Nil => ()
}

def renderDynamicValue(dynValue: DynamicValue)(implicit render: Renderer): Unit =
dynValue match {
case DynamicValue.Primitive(value, typeTag) =>
StandardType.fromString(typeTag.tag) match {
case Some(v) =>
v match {
case BigDecimalType =>
render(value)
case StandardType.InstantType(formatter) =>
render(s"'${formatter.format(value.asInstanceOf[Instant])}'")
case CharType => render(s"'${value}'")
case IntType => render(value)
case StandardType.MonthDayType => render(s"'${value}'")
case BinaryType => render(s"'${value}'")
case StandardType.MonthType => render(s"'${value}'")
case StandardType.LocalDateTimeType(formatter) =>
render(s"'${formatter.format(value.asInstanceOf[LocalDateTime])}'")
case UnitType => render("null") // None is encoded as Schema[Unit].transform(_ => None, _ => ())
case StandardType.YearMonthType => render(s"'${value}'")
case DoubleType => render(value)
case StandardType.YearType => render(s"'${value}'")
case StandardType.OffsetDateTimeType(formatter) =>
render(s"'${formatter.format(value.asInstanceOf[OffsetDateTime])}'")
case StandardType.ZonedDateTimeType(_) =>
render(s"'${DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(value.asInstanceOf[ZonedDateTime])}'")
case BigIntegerType => render(s"'${value}'")
case UUIDType => render(s"'${value}'")
case StandardType.ZoneOffsetType => render(s"'${value}'")
case ShortType => render(value)
case StandardType.LocalTimeType(formatter) =>
render(s"'${formatter.format(value.asInstanceOf[LocalTime])}'")
case StandardType.OffsetTimeType(formatter) =>
render(s"'${formatter.format(value.asInstanceOf[OffsetTime])}'")
case LongType => render(value)
case StringType => render(s"'${value}'")
case StandardType.PeriodType => render(s"'${value}'")
case StandardType.ZoneIdType => render(s"'${value}'")
case StandardType.LocalDateType(formatter) =>
render(s"'${formatter.format(value.asInstanceOf[LocalDate])}'")
case BoolType => render(value)
case DayOfWeekType => render(s"'${value}'")
case FloatType => render(value)
case StandardType.DurationType => render(s"'${value}'")
}
case None => ()
}
case DynamicValue.Tuple(left, right) =>
renderDynamicValue(left)
render(", ")
renderDynamicValue(right)
case DynamicValue.SomeValue(value) => renderDynamicValue(value)
case DynamicValue.NoneValue => render("null")
case _ => ()
}

private def renderColumnNames(sources: SelectionSet[_])(implicit render: Renderer): Unit =
sources match {
case SelectionSet.Empty => ()
case SelectionSet.Cons(columnSelection, tail) =>
val _ = columnSelection.name.map { name =>
render(name)
}
tail.asInstanceOf[SelectionSet[_]] match {
case SelectionSet.Empty => ()
case next @ SelectionSet.Cons(_, _) =>
render(", ")
renderColumnNames(next.asInstanceOf[SelectionSet[_]])(render)
}
}

private def renderSet(set: List[Set[_, _]])(implicit render: Renderer): Unit =
set match {
case head :: tail =>
Expand Down Expand Up @@ -154,6 +281,8 @@ trait MysqlRenderModule extends MysqlSqlModule { self =>
render(" ")
}

private[zio] def quoted(name: String): String = "\"" + name + "\""

private def renderExpr[A, B](expr: self.Expr[_, A, B])(implicit render: Renderer): Unit = expr match {
case Expr.Subselect(subselect) =>
render(" (")
Expand Down
12 changes: 11 additions & 1 deletion mysql/src/main/scala/zio/sql/mysql/MysqlSqlModule.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package zio.sql.mysql

import java.time._
import java.sql.ResultSet
import java.time.{ LocalDate, LocalTime, OffsetTime, Year, ZonedDateTime }
import java.time.format.DateTimeFormatter
import java.util.UUID

import zio.schema.Schema
import zio.sql.Sql

trait MysqlSqlModule extends Sql { self =>
Expand Down Expand Up @@ -43,4 +46,11 @@ trait MysqlSqlModule extends Sql { self =>
val Uuid = Expr.FunctionCall0[UUID](FunctionDef[Any, UUID](FunctionName("uuid")))
val Radians = FunctionDef[Double, Double](FunctionName("radians"))
}

implicit val localDateSchema =
Schema.primitive[LocalDate](zio.schema.StandardType.LocalDateType(DateTimeFormatter.ISO_DATE))

implicit val localDateTimeSchema =
Schema.primitive[LocalDateTime](zio.schema.StandardType.LocalDateTimeType(DateTimeFormatter.ISO_DATE_TIME))

}
55 changes: 28 additions & 27 deletions mysql/src/test/resources/shop_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ create table orders
(
id varchar(36) not null primary key,
customer_id varchar(36) not null,
order_date date not null
order_date date not null,
deleted_at datetime
);

create table products
Expand Down Expand Up @@ -78,33 +79,33 @@ values
('D5137D3A-894A-4109-9986-E982541B434F', '2020-01-01', 55.00);

insert into orders
(id, customer_id, order_date)
(id, customer_id, order_date, deleted_at)
values
('04912093-cc2e-46ac-b64c-1bd7bb7758c3', '60b01fc9-c902-4468-8d49-3c0f989def37', '2019-03-25'),
('a243fa42-817a-44ec-8b67-22193d212d82', '60b01fc9-c902-4468-8d49-3c0f989def37', '2018-06-04'),
('9022dd0d-06d6-4a43-9121-2993fc7712a1', 'df8215a2-d5fd-4c6c-9984-801a1b3a2a0b', '2019-08-19'),
('38d66d44-3cfa-488a-ac77-30277751418f', '636ae137-5b1a-4c8c-b11f-c47c624d9cdc', '2019-08-30'),
('7b2627d5-0150-44df-9171-3462e20797ee', '636ae137-5b1a-4c8c-b11f-c47c624d9cdc', '2019-03-07'),
('62cd4109-3e5d-40cc-8188-3899fc1f8bdf', '60b01fc9-c902-4468-8d49-3c0f989def37', '2020-03-19'),
('9473a0bc-396a-4936-96b0-3eea922af36b', 'df8215a2-d5fd-4c6c-9984-801a1b3a2a0b', '2020-05-11'),
('b8bac18d-769f-48ed-809d-4b6c0e4d1795', 'df8215a2-d5fd-4c6c-9984-801a1b3a2a0b', '2019-02-21'),
('852e2dc9-4ec3-4225-a6f7-4f42f8ff728e', '60b01fc9-c902-4468-8d49-3c0f989def37', '2018-05-06'),
('bebbfe4d-4ec3-4389-bdc2-50e9eac2b15b', '784426a5-b90a-4759-afbb-571b7a0ba35e', '2019-02-11'),
('742d45a0-e81a-41ce-95ad-55b4cabba258', 'f76c9ace-be07-4bf3-bd4c-4a9c62882e64', '2019-10-12'),
('618aa21f-700b-4ca7-933c-67066cf4cd97', '60b01fc9-c902-4468-8d49-3c0f989def37', '2019-01-29'),
('606da090-dd33-4a77-8746-6ed0e8443ab2', 'f76c9ace-be07-4bf3-bd4c-4a9c62882e64', '2019-02-10'),
('4914028d-2e28-4033-a5f2-8f4fcdee8206', '60b01fc9-c902-4468-8d49-3c0f989def37', '2019-09-27'),
('d4e77298-d829-4e36-a6a0-902403f4b7d3', 'df8215a2-d5fd-4c6c-9984-801a1b3a2a0b', '2018-11-13'),
('fd0fa8d4-e1a0-4369-be07-945450db5d36', '636ae137-5b1a-4c8c-b11f-c47c624d9cdc', '2020-01-15'),
('d6d8dddc-4b0b-4d74-8edc-a54e9b7f35f7', 'f76c9ace-be07-4bf3-bd4c-4a9c62882e64', '2018-07-10'),
('876b6034-b33c-4497-81ee-b4e8742164c2', '784426a5-b90a-4759-afbb-571b7a0ba35e', '2019-08-01'),
('91caa28a-a5fe-40d7-979c-bd6a128d0418', 'df8215a2-d5fd-4c6c-9984-801a1b3a2a0b', '2019-12-08'),
('401c7ab1-41cf-4756-8af5-be25cf2ae67b', '784426a5-b90a-4759-afbb-571b7a0ba35e', '2019-11-04'),
('2c3fc180-d0df-4d7b-a271-e6ccd2440393', '784426a5-b90a-4759-afbb-571b7a0ba35e', '2018-10-14'),
('763a7c39-833f-4ee8-9939-e80dfdbfc0fc', 'f76c9ace-be07-4bf3-bd4c-4a9c62882e64', '2020-04-05'),
('5011d206-8eff-42c4-868e-f1a625e1f186', '636ae137-5b1a-4c8c-b11f-c47c624d9cdc', '2019-01-23'),
('0a48ffb0-ec61-4147-af56-fc4dbca8de0a', 'f76c9ace-be07-4bf3-bd4c-4a9c62882e64', '2019-05-14'),
('5883cb62-d792-4ee3-acbc-fe85b6baa998', '784426a5-b90a-4759-afbb-571b7a0ba35e', '2020-04-30');
('04912093-cc2e-46ac-b64c-1bd7bb7758c3', '60b01fc9-c902-4468-8d49-3c0f989def37', '2019-03-25', null),
('a243fa42-817a-44ec-8b67-22193d212d82', '60b01fc9-c902-4468-8d49-3c0f989def37', '2018-06-04', null),
('9022dd0d-06d6-4a43-9121-2993fc7712a1', 'df8215a2-d5fd-4c6c-9984-801a1b3a2a0b', '2019-08-19', null),
('38d66d44-3cfa-488a-ac77-30277751418f', '636ae137-5b1a-4c8c-b11f-c47c624d9cdc', '2019-08-30', null),
('7b2627d5-0150-44df-9171-3462e20797ee', '636ae137-5b1a-4c8c-b11f-c47c624d9cdc', '2019-03-07', null),
('62cd4109-3e5d-40cc-8188-3899fc1f8bdf', '60b01fc9-c902-4468-8d49-3c0f989def37', '2020-03-19', null),
('9473a0bc-396a-4936-96b0-3eea922af36b', 'df8215a2-d5fd-4c6c-9984-801a1b3a2a0b', '2020-05-11', null),
('b8bac18d-769f-48ed-809d-4b6c0e4d1795', 'df8215a2-d5fd-4c6c-9984-801a1b3a2a0b', '2019-02-21', null),
('852e2dc9-4ec3-4225-a6f7-4f42f8ff728e', '60b01fc9-c902-4468-8d49-3c0f989def37', '2018-05-06', null),
('bebbfe4d-4ec3-4389-bdc2-50e9eac2b15b', '784426a5-b90a-4759-afbb-571b7a0ba35e', '2019-02-11', null),
('742d45a0-e81a-41ce-95ad-55b4cabba258', 'f76c9ace-be07-4bf3-bd4c-4a9c62882e64', '2019-10-12', null),
('618aa21f-700b-4ca7-933c-67066cf4cd97', '60b01fc9-c902-4468-8d49-3c0f989def37', '2019-01-29', null),
('606da090-dd33-4a77-8746-6ed0e8443ab2', 'f76c9ace-be07-4bf3-bd4c-4a9c62882e64', '2019-02-10', null),
('4914028d-2e28-4033-a5f2-8f4fcdee8206', '60b01fc9-c902-4468-8d49-3c0f989def37', '2019-09-27', null),
('d4e77298-d829-4e36-a6a0-902403f4b7d3', 'df8215a2-d5fd-4c6c-9984-801a1b3a2a0b', '2018-11-13', null),
('fd0fa8d4-e1a0-4369-be07-945450db5d36', '636ae137-5b1a-4c8c-b11f-c47c624d9cdc', '2020-01-15', null),
('d6d8dddc-4b0b-4d74-8edc-a54e9b7f35f7', 'f76c9ace-be07-4bf3-bd4c-4a9c62882e64', '2018-07-10', null),
('876b6034-b33c-4497-81ee-b4e8742164c2', '784426a5-b90a-4759-afbb-571b7a0ba35e', '2019-08-01', null),
('91caa28a-a5fe-40d7-979c-bd6a128d0418', 'df8215a2-d5fd-4c6c-9984-801a1b3a2a0b', '2019-12-08', null),
('401c7ab1-41cf-4756-8af5-be25cf2ae67b', '784426a5-b90a-4759-afbb-571b7a0ba35e', '2019-11-04', null),
('2c3fc180-d0df-4d7b-a271-e6ccd2440393', '784426a5-b90a-4759-afbb-571b7a0ba35e', '2018-10-14', null),
('763a7c39-833f-4ee8-9939-e80dfdbfc0fc', 'f76c9ace-be07-4bf3-bd4c-4a9c62882e64', '2020-04-05', null),
('5011d206-8eff-42c4-868e-f1a625e1f186', '636ae137-5b1a-4c8c-b11f-c47c624d9cdc', '2019-01-23', null),
('0a48ffb0-ec61-4147-af56-fc4dbca8de0a', 'f76c9ace-be07-4bf3-bd4c-4a9c62882e64', '2019-05-14', null),
('5883cb62-d792-4ee3-acbc-fe85b6baa998', '784426a5-b90a-4759-afbb-571b7a0ba35e', '2020-04-30', '2020-05-01T09:00:00');

insert into order_details
(order_id, product_id, quantity, unit_price)
Expand Down
85 changes: 80 additions & 5 deletions mysql/src/test/scala/zio/sql/mysql/MysqlModuleSpec.scala
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
package zio.sql.mysql

import java.time.LocalDate
import java.time._
import java.time.format.DateTimeFormatter
import java.util.UUID

import zio.Cause
import scala.language.postfixOps

import zio._
import zio.schema._
import zio.test._
import zio.test.Assertion._
import scala.language.postfixOps

object MysqlModuleSpec extends MysqlRunnableSpec with ShopSchema {

import this.Customers._
import this.Orders._
import Customers._
import Orders._

override def specLayered = suite("Mysql module")(
test("Can select from single table") {
Expand Down Expand Up @@ -161,6 +164,78 @@ object MysqlModuleSpec extends MysqlRunnableSpec with ShopSchema {

assertion.mapErrorCause(cause => Cause.stackless(cause.untraced))
},
test("Can insert rows") {
final case class CustomerRow(
id: UUID,
dateOfBirth: LocalDate,
firstName: String,
lastName: String,
verified: Boolean
)
implicit val customerRowSchema =
Schema.CaseClass5[UUID, LocalDate, String, String, Boolean, CustomerRow](
Schema.Field("id", Schema.primitive[UUID](zio.schema.StandardType.UUIDType)),
Schema.Field(
"dateOfBirth",
Schema.primitive[LocalDate](zio.schema.StandardType.LocalDateType(DateTimeFormatter.ISO_DATE))
),
Schema.Field("firstName", Schema.primitive[String](zio.schema.StandardType.StringType)),
Schema.Field("lastName", Schema.primitive[String](zio.schema.StandardType.StringType)),
Schema.Field("verified", Schema.primitive[Boolean](zio.schema.StandardType.BoolType)),
CustomerRow.apply,
_.id,
_.dateOfBirth,
_.firstName,
_.lastName,
_.verified
)

val rows = List(
CustomerRow(UUID.randomUUID(), LocalDate.ofYearDay(2001, 8), "Peter", "Parker", true),
CustomerRow(UUID.randomUUID(), LocalDate.ofYearDay(1980, 2), "Stephen", "Strange", false)
)

val command = insertInto(customers)(
customerId,
dob,
fName,
lName,
verified
).values(rows)

println(renderInsert(command))

assertZIO(execute(command))(equalTo(2))
},
test("Can insert tuples") {
implicit val optionLocalDateTimeSchema = Schema.option[LocalDateTime]

val rows = List(
(
UUID.randomUUID(),
UUID.randomUUID(),
LocalDate.of(2022, 1, 1),
None
),
(
UUID.randomUUID(),
UUID.randomUUID(),
LocalDate.of(2022, 1, 5),
Some(LocalDateTime.of(2022, 1, 10, 3, 20))
)
)

val command = insertInto(orders)(
orderId,
fkCustomerId,
orderDate,
deleted_at
).values(rows)

println(renderInsert(command))

assertZIO(execute(command))(equalTo(2))
},
test("Can update rows") {
val query = update(customers).set(fName, "Roland").where(fName === "Ronald")

Expand Down
12 changes: 8 additions & 4 deletions mysql/src/test/scala/zio/sql/mysql/ShopSchema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,21 @@ import zio.sql.Jdbc
trait ShopSchema extends Jdbc { self =>
import self.ColumnSet._

object Customers {
object Customers {
val customers =
(uuid("id") ++ localDate("dob") ++ string("first_name") ++ string("last_name") ++ boolean("verified"))
.table("customers")

val (customerId, dob, fName, lName, verified) = customers.columns
}
object Orders {
val orders = (uuid("id") ++ uuid("customer_id") ++ localDate("order_date")).table("orders")
object Orders {

val (orderId, fkCustomerId, orderDate) = orders.columns
import ColumnSetAspect._

val orders = (uuid("id") ++ uuid("customer_id") ++ localDate("order_date") ++
localDateTime("deleted_at") @@ nullable).table("orders")

val (orderId, fkCustomerId, orderDate, deleted_at) = orders.columns
}
object Products {
val products =
Expand Down

0 comments on commit 1532031

Please sign in to comment.