diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/TestDomainInsert.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/TestDomainInsert.scala index a8e733e7a6..4d2a2dd08c 100644 --- a/typo-tester-anorm/generated-and-checked-in/adventureworks/TestDomainInsert.scala +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/TestDomainInsert.scala @@ -5,6 +5,7 @@ */ package adventureworks +import adventureworks.frontpage.Email import adventureworks.public.AccountNumber import adventureworks.public.Flag import adventureworks.public.Mydomain @@ -16,6 +17,10 @@ import adventureworks.public.ShortText import scala.util.Random trait TestDomainInsert { + /** Domain `frontpage.email` + * Constraint: CHECK ((VALUE ~ '^[^@]+@[^@]+\.[^@]+$'::text)) + */ + def frontpageEmail(random: Random): Email /** Domain `public.AccountNumber` * No constraint */ diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/Email.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/Email.scala new file mode 100644 index 0000000000..25316f3613 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/Email.scala @@ -0,0 +1,38 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage + +import anorm.Column +import anorm.ParameterMetaData +import anorm.ToStatement +import java.sql.Types +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import typo.dsl.Bijection + +/** Domain `frontpage.email` + * Constraint: CHECK ((VALUE ~ '^[^@]+@[^@]+\.[^@]+$'::text)) + */ +case class Email(value: String) +object Email { + implicit lazy val arrayColumn: Column[Array[Email]] = Column.columnToArray(column, implicitly) + implicit lazy val arrayToStatement: ToStatement[Array[Email]] = ToStatement.arrayToParameter(ParameterMetaData.StringParameterMetaData).contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[Email, String] = Bijection[Email, String](_.value)(Email.apply) + implicit lazy val column: Column[Email] = Column.columnToString.map(Email.apply) + implicit lazy val ordering: Ordering[Email] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[Email] = new ParameterMetaData[Email] { + override def sqlType: String = """"frontpage"."email"""" + override def jdbcType: Int = Types.OTHER + } + implicit lazy val reads: Reads[Email] = Reads.StringReads.map(Email.apply) + implicit lazy val text: Text[Email] = new Text[Email] { + override def unsafeEncode(v: Email, sb: StringBuilder) = Text.stringInstance.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: Email, sb: StringBuilder) = Text.stringInstance.unsafeArrayEncode(v.value, sb) + } + implicit lazy val toStatement: ToStatement[Email] = ToStatement.stringToStatement.contramap(_.value) + implicit lazy val writes: Writes[Email] = Writes.StringWrites.contramap(_.value) +} \ No newline at end of file diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/OrderStatus.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/OrderStatus.scala new file mode 100644 index 0000000000..0a078ed02a --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/OrderStatus.scala @@ -0,0 +1,59 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage + +import anorm.Column +import anorm.ParameterMetaData +import anorm.SqlMappingError +import anorm.ToStatement +import java.sql.Types +import play.api.libs.json.JsError +import play.api.libs.json.JsSuccess +import play.api.libs.json.JsValue +import play.api.libs.json.Reads +import play.api.libs.json.Writes + +/** Enum `frontpage.order_status` + * - pending + * - active + * - shipped + * - cancelled + */ +sealed abstract class OrderStatus(val value: String) + +object OrderStatus { + def apply(str: String): Either[String, OrderStatus] = + ByName.get(str).toRight(s"'$str' does not match any of the following legal values: $Names") + def force(str: String): OrderStatus = + apply(str) match { + case Left(msg) => sys.error(msg) + case Right(value) => value + } + case object pending extends OrderStatus("pending") + case object active extends OrderStatus("active") + case object shipped extends OrderStatus("shipped") + case object cancelled extends OrderStatus("cancelled") + val All: List[OrderStatus] = List(pending, active, shipped, cancelled) + val Names: String = All.map(_.value).mkString(", ") + val ByName: Map[String, OrderStatus] = All.map(x => (x.value, x)).toMap + + implicit lazy val arrayColumn: Column[Array[OrderStatus]] = Column.columnToArray[String](Column.columnToString, implicitly).map(_.map(OrderStatus.force)) + implicit lazy val arrayToStatement: ToStatement[Array[OrderStatus]] = ToStatement[Array[OrderStatus]]((ps, i, arr) => ps.setArray(i, ps.getConnection.createArrayOf("frontpage.order_status", arr.map[AnyRef](_.value)))) + implicit lazy val column: Column[OrderStatus] = Column.columnToString.mapResult(str => OrderStatus(str).left.map(SqlMappingError.apply)) + implicit lazy val ordering: Ordering[OrderStatus] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[OrderStatus] = new ParameterMetaData[OrderStatus] { + override def sqlType: String = "frontpage.order_status" + override def jdbcType: Int = Types.OTHER + } + implicit lazy val reads: Reads[OrderStatus] = Reads[OrderStatus]{(value: JsValue) => value.validate(Reads.StringReads).flatMap(str => OrderStatus(str).fold(JsError.apply, JsSuccess(_)))} + implicit lazy val text: Text[OrderStatus] = new Text[OrderStatus] { + override def unsafeEncode(v: OrderStatus, sb: StringBuilder) = Text.stringInstance.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: OrderStatus, sb: StringBuilder) = Text.stringInstance.unsafeArrayEncode(v.value, sb) + } + implicit lazy val toStatement: ToStatement[OrderStatus] = ToStatement.stringToStatement.contramap(_.value) + implicit lazy val writes: Writes[OrderStatus] = Writes[OrderStatus](value => Writes.StringWrites.writes(value.value)) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/UserRole.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/UserRole.scala new file mode 100644 index 0000000000..7e1612b91d --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/UserRole.scala @@ -0,0 +1,57 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage + +import anorm.Column +import anorm.ParameterMetaData +import anorm.SqlMappingError +import anorm.ToStatement +import java.sql.Types +import play.api.libs.json.JsError +import play.api.libs.json.JsSuccess +import play.api.libs.json.JsValue +import play.api.libs.json.Reads +import play.api.libs.json.Writes + +/** Enum `frontpage.user_role` + * - admin + * - manager + * - employee + */ +sealed abstract class UserRole(val value: String) + +object UserRole { + def apply(str: String): Either[String, UserRole] = + ByName.get(str).toRight(s"'$str' does not match any of the following legal values: $Names") + def force(str: String): UserRole = + apply(str) match { + case Left(msg) => sys.error(msg) + case Right(value) => value + } + case object admin extends UserRole("admin") + case object manager extends UserRole("manager") + case object employee extends UserRole("employee") + val All: List[UserRole] = List(admin, manager, employee) + val Names: String = All.map(_.value).mkString(", ") + val ByName: Map[String, UserRole] = All.map(x => (x.value, x)).toMap + + implicit lazy val arrayColumn: Column[Array[UserRole]] = Column.columnToArray[String](Column.columnToString, implicitly).map(_.map(UserRole.force)) + implicit lazy val arrayToStatement: ToStatement[Array[UserRole]] = ToStatement[Array[UserRole]]((ps, i, arr) => ps.setArray(i, ps.getConnection.createArrayOf("frontpage.user_role", arr.map[AnyRef](_.value)))) + implicit lazy val column: Column[UserRole] = Column.columnToString.mapResult(str => UserRole(str).left.map(SqlMappingError.apply)) + implicit lazy val ordering: Ordering[UserRole] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[UserRole] = new ParameterMetaData[UserRole] { + override def sqlType: String = "frontpage.user_role" + override def jdbcType: Int = Types.OTHER + } + implicit lazy val reads: Reads[UserRole] = Reads[UserRole]{(value: JsValue) => value.validate(Reads.StringReads).flatMap(str => UserRole(str).fold(JsError.apply, JsSuccess(_)))} + implicit lazy val text: Text[UserRole] = new Text[UserRole] { + override def unsafeEncode(v: UserRole, sb: StringBuilder) = Text.stringInstance.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: UserRole, sb: StringBuilder) = Text.stringInstance.unsafeArrayEncode(v.value, sb) + } + implicit lazy val toStatement: ToStatement[UserRole] = ToStatement.stringToStatement.contramap(_.value) + implicit lazy val writes: Writes[UserRole] = Writes[UserRole](value => Writes.StringWrites.writes(value.value)) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/UserStatus.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/UserStatus.scala new file mode 100644 index 0000000000..4da1338024 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/UserStatus.scala @@ -0,0 +1,57 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage + +import anorm.Column +import anorm.ParameterMetaData +import anorm.SqlMappingError +import anorm.ToStatement +import java.sql.Types +import play.api.libs.json.JsError +import play.api.libs.json.JsSuccess +import play.api.libs.json.JsValue +import play.api.libs.json.Reads +import play.api.libs.json.Writes + +/** Enum `frontpage.user_status` + * - active + * - inactive + * - suspended + */ +sealed abstract class UserStatus(val value: String) + +object UserStatus { + def apply(str: String): Either[String, UserStatus] = + ByName.get(str).toRight(s"'$str' does not match any of the following legal values: $Names") + def force(str: String): UserStatus = + apply(str) match { + case Left(msg) => sys.error(msg) + case Right(value) => value + } + case object active extends UserStatus("active") + case object inactive extends UserStatus("inactive") + case object suspended extends UserStatus("suspended") + val All: List[UserStatus] = List(active, inactive, suspended) + val Names: String = All.map(_.value).mkString(", ") + val ByName: Map[String, UserStatus] = All.map(x => (x.value, x)).toMap + + implicit lazy val arrayColumn: Column[Array[UserStatus]] = Column.columnToArray[String](Column.columnToString, implicitly).map(_.map(UserStatus.force)) + implicit lazy val arrayToStatement: ToStatement[Array[UserStatus]] = ToStatement[Array[UserStatus]]((ps, i, arr) => ps.setArray(i, ps.getConnection.createArrayOf("frontpage.user_status", arr.map[AnyRef](_.value)))) + implicit lazy val column: Column[UserStatus] = Column.columnToString.mapResult(str => UserStatus(str).left.map(SqlMappingError.apply)) + implicit lazy val ordering: Ordering[UserStatus] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[UserStatus] = new ParameterMetaData[UserStatus] { + override def sqlType: String = "frontpage.user_status" + override def jdbcType: Int = Types.OTHER + } + implicit lazy val reads: Reads[UserStatus] = Reads[UserStatus]{(value: JsValue) => value.validate(Reads.StringReads).flatMap(str => UserStatus(str).fold(JsError.apply, JsSuccess(_)))} + implicit lazy val text: Text[UserStatus] = new Text[UserStatus] { + override def unsafeEncode(v: UserStatus, sb: StringBuilder) = Text.stringInstance.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: UserStatus, sb: StringBuilder) = Text.stringInstance.unsafeArrayEncode(v.value, sb) + } + implicit lazy val toStatement: ToStatement[UserStatus] = ToStatement.stringToStatement.contramap(_.value) + implicit lazy val writes: Writes[UserStatus] = Writes[UserStatus](value => Writes.StringWrites.writes(value.value)) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressFields.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressFields.scala new file mode 100644 index 0000000000..ad9a616933 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressFields.scala @@ -0,0 +1,42 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.Structure.Relation + +trait AddressFields { + def id: IdField[AddressId, AddressRow] + def city: Field[String, AddressRow] + def country: Field[String, AddressRow] +} + +object AddressFields { + lazy val structure: Relation[AddressFields, AddressRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[AddressFields, AddressRow] { + + override lazy val fields: AddressFields = new AddressFields { + override def id = IdField[AddressId, AddressRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def city = Field[String, AddressRow](_path, "city", None, None, x => x.city, (row, value) => row.copy(city = value)) + override def country = Field[String, AddressRow](_path, "country", None, None, x => x.country, (row, value) => row.copy(country = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, AddressRow]] = + List[FieldLikeNoHkt[?, AddressRow]](fields.id, fields.city, fields.country) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressId.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressId.scala new file mode 100644 index 0000000000..a9e60c16c7 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import adventureworks.customtypes.TypoUUID +import anorm.Column +import anorm.ParameterMetaData +import anorm.ToStatement +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.address` */ +case class AddressId(value: TypoUUID) extends AnyVal +object AddressId { + implicit lazy val arrayColumn: Column[Array[AddressId]] = Column.columnToArray(column, implicitly) + implicit lazy val arrayToStatement: ToStatement[Array[AddressId]] = TypoUUID.arrayToStatement.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[AddressId, TypoUUID] = Bijection[AddressId, TypoUUID](_.value)(AddressId.apply) + implicit lazy val column: Column[AddressId] = TypoUUID.column.map(AddressId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[AddressId] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[AddressId] = new ParameterMetaData[AddressId] { + override def sqlType: String = TypoUUID.parameterMetadata.sqlType + override def jdbcType: Int = TypoUUID.parameterMetadata.jdbcType + } + implicit lazy val reads: Reads[AddressId] = TypoUUID.reads.map(AddressId.apply) + implicit lazy val text: Text[AddressId] = new Text[AddressId] { + override def unsafeEncode(v: AddressId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: AddressId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } + implicit lazy val toStatement: ToStatement[AddressId] = TypoUUID.toStatement.contramap(_.value) + implicit lazy val writes: Writes[AddressId] = TypoUUID.writes.contramap(_.value) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressRepo.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressRepo.scala new file mode 100644 index 0000000000..50d815842d --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressRepo.scala @@ -0,0 +1,35 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import java.sql.Connection +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait AddressRepo { + def delete: DeleteBuilder[AddressFields, AddressRow] + def deleteById(id: AddressId)(implicit c: Connection): Boolean + def deleteByIds(ids: Array[AddressId])(implicit c: Connection): Int + def insert(unsaved: AddressRow)(implicit c: Connection): AddressRow + def insert(unsaved: AddressRowUnsaved)(implicit c: Connection): AddressRow + def insertStreaming(unsaved: Iterator[AddressRow], batchSize: Int = 10000)(implicit c: Connection): Long + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Iterator[AddressRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long + def select: SelectBuilder[AddressFields, AddressRow] + def selectAll(implicit c: Connection): List[AddressRow] + def selectById(id: AddressId)(implicit c: Connection): Option[AddressRow] + def selectByIds(ids: Array[AddressId])(implicit c: Connection): List[AddressRow] + def selectByIdsTracked(ids: Array[AddressId])(implicit c: Connection): Map[AddressId, AddressRow] + def update: UpdateBuilder[AddressFields, AddressRow] + def update(row: AddressRow)(implicit c: Connection): Boolean + def upsert(unsaved: AddressRow)(implicit c: Connection): AddressRow + def upsertBatch(unsaved: Iterable[AddressRow])(implicit c: Connection): List[AddressRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Iterator[AddressRow], batchSize: Int = 10000)(implicit c: Connection): Int +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressRepoImpl.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressRepoImpl.scala new file mode 100644 index 0000000000..b198b076e3 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressRepoImpl.scala @@ -0,0 +1,170 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import adventureworks.customtypes.Defaulted +import anorm.BatchSql +import anorm.NamedParameter +import anorm.ParameterValue +import anorm.RowParser +import anorm.SQL +import anorm.SimpleSql +import anorm.SqlStringInterpolation +import anorm.ToStatement +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class AddressRepoImpl extends AddressRepo { + override def delete: DeleteBuilder[AddressFields, AddressRow] = { + DeleteBuilder(""""frontpage"."address"""", AddressFields.structure) + } + override def deleteById(id: AddressId)(implicit c: Connection): Boolean = { + SQL"""delete from "frontpage"."address" where "id" = ${ParameterValue(id, null, AddressId.toStatement)}""".executeUpdate() > 0 + } + override def deleteByIds(ids: Array[AddressId])(implicit c: Connection): Int = { + SQL"""delete + from "frontpage"."address" + where "id" = ANY(${ids}) + """.executeUpdate() + + } + override def insert(unsaved: AddressRow)(implicit c: Connection): AddressRow = { + SQL"""insert into "frontpage"."address"("id", "city", "country") + values (${ParameterValue(unsaved.id, null, AddressId.toStatement)}::uuid, ${ParameterValue(unsaved.city, null, ToStatement.stringToStatement)}, ${ParameterValue(unsaved.country, null, ToStatement.stringToStatement)}) + returning "id", "city", "country" + """ + .executeInsert(AddressRow.rowParser(1).single) + + } + override def insert(unsaved: AddressRowUnsaved)(implicit c: Connection): AddressRow = { + val namedParameters = List( + Some((NamedParameter("city", ParameterValue(unsaved.city, null, ToStatement.stringToStatement)), "")), + Some((NamedParameter("country", ParameterValue(unsaved.country, null, ToStatement.stringToStatement)), "")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("id", ParameterValue(value, null, AddressId.toStatement)), "::uuid")) + } + ).flatten + val quote = '"'.toString + if (namedParameters.isEmpty) { + SQL"""insert into "frontpage"."address" default values + returning "id", "city", "country" + """ + .executeInsert(AddressRow.rowParser(1).single) + } else { + val q = s"""insert into "frontpage"."address"(${namedParameters.map{case (x, _) => quote + x.name + quote}.mkString(", ")}) + values (${namedParameters.map{ case (np, cast) => s"{${np.name}}$cast"}.mkString(", ")}) + returning "id", "city", "country" + """ + SimpleSql(SQL(q), namedParameters.map { case (np, _) => np.tupled }.toMap, RowParser.successful) + .executeInsert(AddressRow.rowParser(1).single) + } + + } + override def insertStreaming(unsaved: Iterator[AddressRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."address"("id", "city", "country") FROM STDIN""", batchSize, unsaved)(AddressRow.text, c) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[AddressRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."address"("city", "country", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(AddressRowUnsaved.text, c) + } + override def select: SelectBuilder[AddressFields, AddressRow] = { + SelectBuilderSql(""""frontpage"."address"""", AddressFields.structure, AddressRow.rowParser) + } + override def selectAll(implicit c: Connection): List[AddressRow] = { + SQL"""select "id", "city", "country" + from "frontpage"."address" + """.as(AddressRow.rowParser(1).*) + } + override def selectById(id: AddressId)(implicit c: Connection): Option[AddressRow] = { + SQL"""select "id", "city", "country" + from "frontpage"."address" + where "id" = ${ParameterValue(id, null, AddressId.toStatement)} + """.as(AddressRow.rowParser(1).singleOpt) + } + override def selectByIds(ids: Array[AddressId])(implicit c: Connection): List[AddressRow] = { + SQL"""select "id", "city", "country" + from "frontpage"."address" + where "id" = ANY(${ids}) + """.as(AddressRow.rowParser(1).*) + + } + override def selectByIdsTracked(ids: Array[AddressId])(implicit c: Connection): Map[AddressId, AddressRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[AddressFields, AddressRow] = { + UpdateBuilder(""""frontpage"."address"""", AddressFields.structure, AddressRow.rowParser) + } + override def update(row: AddressRow)(implicit c: Connection): Boolean = { + val id = row.id + SQL"""update "frontpage"."address" + set "city" = ${ParameterValue(row.city, null, ToStatement.stringToStatement)}, + "country" = ${ParameterValue(row.country, null, ToStatement.stringToStatement)} + where "id" = ${ParameterValue(id, null, AddressId.toStatement)} + """.executeUpdate() > 0 + } + override def upsert(unsaved: AddressRow)(implicit c: Connection): AddressRow = { + SQL"""insert into "frontpage"."address"("id", "city", "country") + values ( + ${ParameterValue(unsaved.id, null, AddressId.toStatement)}::uuid, + ${ParameterValue(unsaved.city, null, ToStatement.stringToStatement)}, + ${ParameterValue(unsaved.country, null, ToStatement.stringToStatement)} + ) + on conflict ("id") + do update set + "city" = EXCLUDED."city", + "country" = EXCLUDED."country" + returning "id", "city", "country" + """ + .executeInsert(AddressRow.rowParser(1).single) + + } + override def upsertBatch(unsaved: Iterable[AddressRow])(implicit c: Connection): List[AddressRow] = { + def toNamedParameter(row: AddressRow): List[NamedParameter] = List( + NamedParameter("id", ParameterValue(row.id, null, AddressId.toStatement)), + NamedParameter("city", ParameterValue(row.city, null, ToStatement.stringToStatement)), + NamedParameter("country", ParameterValue(row.country, null, ToStatement.stringToStatement)) + ) + unsaved.toList match { + case Nil => Nil + case head :: rest => + new anorm.adventureworks.ExecuteReturningSyntax.Ops( + BatchSql( + s"""insert into "frontpage"."address"("id", "city", "country") + values ({id}::uuid, {city}, {country}) + on conflict ("id") + do update set + "city" = EXCLUDED."city", + "country" = EXCLUDED."country" + returning "id", "city", "country" + """, + toNamedParameter(head), + rest.map(toNamedParameter)* + ) + ).executeReturning(AddressRow.rowParser(1).*) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[AddressRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + SQL"""create temporary table address_TEMP (like "frontpage"."address") on commit drop""".execute(): @nowarn + streamingInsert(s"""copy address_TEMP("id", "city", "country") from stdin""", batchSize, unsaved)(AddressRow.text, c): @nowarn + SQL"""insert into "frontpage"."address"("id", "city", "country") + select * from address_TEMP + on conflict ("id") + do update set + "city" = EXCLUDED."city", + "country" = EXCLUDED."country" + ; + drop table address_TEMP;""".executeUpdate() + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressRepoMock.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressRepoMock.scala new file mode 100644 index 0000000000..ace310a0d2 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressRepoMock.scala @@ -0,0 +1,103 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class AddressRepoMock(toRow: Function1[AddressRowUnsaved, AddressRow], + map: scala.collection.mutable.Map[AddressId, AddressRow] = scala.collection.mutable.Map.empty) extends AddressRepo { + override def delete: DeleteBuilder[AddressFields, AddressRow] = { + DeleteBuilderMock(DeleteParams.empty, AddressFields.structure, map) + } + override def deleteById(id: AddressId)(implicit c: Connection): Boolean = { + map.remove(id).isDefined + } + override def deleteByIds(ids: Array[AddressId])(implicit c: Connection): Int = { + ids.map(id => map.remove(id)).count(_.isDefined) + } + override def insert(unsaved: AddressRow)(implicit c: Connection): AddressRow = { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + override def insert(unsaved: AddressRowUnsaved)(implicit c: Connection): AddressRow = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Iterator[AddressRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size.toLong + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[AddressRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + } + unsaved.size.toLong + } + override def select: SelectBuilder[AddressFields, AddressRow] = { + SelectBuilderMock(AddressFields.structure, () => map.values.toList, SelectParams.empty) + } + override def selectAll(implicit c: Connection): List[AddressRow] = { + map.values.toList + } + override def selectById(id: AddressId)(implicit c: Connection): Option[AddressRow] = { + map.get(id) + } + override def selectByIds(ids: Array[AddressId])(implicit c: Connection): List[AddressRow] = { + ids.flatMap(map.get).toList + } + override def selectByIdsTracked(ids: Array[AddressId])(implicit c: Connection): Map[AddressId, AddressRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[AddressFields, AddressRow] = { + UpdateBuilderMock(UpdateParams.empty, AddressFields.structure, map) + } + override def update(row: AddressRow)(implicit c: Connection): Boolean = { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + override def upsert(unsaved: AddressRow)(implicit c: Connection): AddressRow = { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + override def upsertBatch(unsaved: Iterable[AddressRow])(implicit c: Connection): List[AddressRow] = { + unsaved.map { row => + map += (row.id -> row) + row + }.toList + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[AddressRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressRow.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressRow.scala new file mode 100644 index 0000000000..1567d7d880 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressRow.scala @@ -0,0 +1,69 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import adventureworks.customtypes.Defaulted +import anorm.Column +import anorm.RowParser +import anorm.Success +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** Table: frontpage.address + Primary key: id */ +case class AddressRow( + /** Default: gen_random_uuid() */ + id: AddressId, + city: String, + country: String +){ + def toUnsavedRow(id: Defaulted[AddressId]): AddressRowUnsaved = + AddressRowUnsaved(city, country, id) + } + +object AddressRow { + implicit lazy val reads: Reads[AddressRow] = Reads[AddressRow](json => JsResult.fromTry( + Try( + AddressRow( + id = json.\("id").as(AddressId.reads), + city = json.\("city").as(Reads.StringReads), + country = json.\("country").as(Reads.StringReads) + ) + ) + ), + ) + def rowParser(idx: Int): RowParser[AddressRow] = RowParser[AddressRow] { row => + Success( + AddressRow( + id = row(idx + 0)(AddressId.column), + city = row(idx + 1)(Column.columnToString), + country = row(idx + 2)(Column.columnToString) + ) + ) + } + implicit lazy val text: Text[AddressRow] = Text.instance[AddressRow]{ (row, sb) => + AddressId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.city, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.country, sb) + } + implicit lazy val writes: OWrites[AddressRow] = OWrites[AddressRow](o => + new JsObject(ListMap[String, JsValue]( + "id" -> AddressId.writes.writes(o.id), + "city" -> Writes.StringWrites.writes(o.city), + "country" -> Writes.StringWrites.writes(o.country) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressRowUnsaved.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressRowUnsaved.scala new file mode 100644 index 0000000000..1349e7eafb --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/address/AddressRowUnsaved.scala @@ -0,0 +1,62 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import adventureworks.customtypes.Defaulted +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** This class corresponds to a row in table `frontpage.address` which has not been persisted yet */ +case class AddressRowUnsaved( + city: String, + country: String, + /** Default: gen_random_uuid() */ + id: Defaulted[AddressId] = Defaulted.UseDefault +) { + def toRow(idDefault: => AddressId): AddressRow = + AddressRow( + city = city, + country = country, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object AddressRowUnsaved { + implicit lazy val reads: Reads[AddressRowUnsaved] = Reads[AddressRowUnsaved](json => JsResult.fromTry( + Try( + AddressRowUnsaved( + city = json.\("city").as(Reads.StringReads), + country = json.\("country").as(Reads.StringReads), + id = json.\("id").as(Defaulted.reads(AddressId.reads)) + ) + ) + ), + ) + implicit lazy val text: Text[AddressRowUnsaved] = Text.instance[AddressRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.city, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.country, sb) + sb.append(Text.DELIMETER) + Defaulted.text(AddressId.text).unsafeEncode(row.id, sb) + } + implicit lazy val writes: OWrites[AddressRowUnsaved] = OWrites[AddressRowUnsaved](o => + new JsObject(ListMap[String, JsValue]( + "city" -> Writes.StringWrites.writes(o.city), + "country" -> Writes.StringWrites.writes(o.country), + "id" -> Defaulted.writes(AddressId.writes).writes(o.id) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryFields.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryFields.scala new file mode 100644 index 0000000000..258c0d3f56 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryFields.scala @@ -0,0 +1,40 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.Structure.Relation + +trait CategoryFields { + def id: IdField[CategoryId, CategoryRow] + def name: Field[String, CategoryRow] +} + +object CategoryFields { + lazy val structure: Relation[CategoryFields, CategoryRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[CategoryFields, CategoryRow] { + + override lazy val fields: CategoryFields = new CategoryFields { + override def id = IdField[CategoryId, CategoryRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, CategoryRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, CategoryRow]] = + List[FieldLikeNoHkt[?, CategoryRow]](fields.id, fields.name) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryId.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryId.scala new file mode 100644 index 0000000000..b620df9e33 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import adventureworks.customtypes.TypoUUID +import anorm.Column +import anorm.ParameterMetaData +import anorm.ToStatement +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.category` */ +case class CategoryId(value: TypoUUID) extends AnyVal +object CategoryId { + implicit lazy val arrayColumn: Column[Array[CategoryId]] = Column.columnToArray(column, implicitly) + implicit lazy val arrayToStatement: ToStatement[Array[CategoryId]] = TypoUUID.arrayToStatement.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[CategoryId, TypoUUID] = Bijection[CategoryId, TypoUUID](_.value)(CategoryId.apply) + implicit lazy val column: Column[CategoryId] = TypoUUID.column.map(CategoryId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[CategoryId] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[CategoryId] = new ParameterMetaData[CategoryId] { + override def sqlType: String = TypoUUID.parameterMetadata.sqlType + override def jdbcType: Int = TypoUUID.parameterMetadata.jdbcType + } + implicit lazy val reads: Reads[CategoryId] = TypoUUID.reads.map(CategoryId.apply) + implicit lazy val text: Text[CategoryId] = new Text[CategoryId] { + override def unsafeEncode(v: CategoryId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: CategoryId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } + implicit lazy val toStatement: ToStatement[CategoryId] = TypoUUID.toStatement.contramap(_.value) + implicit lazy val writes: Writes[CategoryId] = TypoUUID.writes.contramap(_.value) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepo.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepo.scala new file mode 100644 index 0000000000..d1445cfcfb --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepo.scala @@ -0,0 +1,35 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import java.sql.Connection +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait CategoryRepo { + def delete: DeleteBuilder[CategoryFields, CategoryRow] + def deleteById(id: CategoryId)(implicit c: Connection): Boolean + def deleteByIds(ids: Array[CategoryId])(implicit c: Connection): Int + def insert(unsaved: CategoryRow)(implicit c: Connection): CategoryRow + def insert(unsaved: CategoryRowUnsaved)(implicit c: Connection): CategoryRow + def insertStreaming(unsaved: Iterator[CategoryRow], batchSize: Int = 10000)(implicit c: Connection): Long + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Iterator[CategoryRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long + def select: SelectBuilder[CategoryFields, CategoryRow] + def selectAll(implicit c: Connection): List[CategoryRow] + def selectById(id: CategoryId)(implicit c: Connection): Option[CategoryRow] + def selectByIds(ids: Array[CategoryId])(implicit c: Connection): List[CategoryRow] + def selectByIdsTracked(ids: Array[CategoryId])(implicit c: Connection): Map[CategoryId, CategoryRow] + def update: UpdateBuilder[CategoryFields, CategoryRow] + def update(row: CategoryRow)(implicit c: Connection): Boolean + def upsert(unsaved: CategoryRow)(implicit c: Connection): CategoryRow + def upsertBatch(unsaved: Iterable[CategoryRow])(implicit c: Connection): List[CategoryRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Iterator[CategoryRow], batchSize: Int = 10000)(implicit c: Connection): Int +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepoImpl.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepoImpl.scala new file mode 100644 index 0000000000..4c6fd54a35 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepoImpl.scala @@ -0,0 +1,163 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import adventureworks.customtypes.Defaulted +import anorm.BatchSql +import anorm.NamedParameter +import anorm.ParameterValue +import anorm.RowParser +import anorm.SQL +import anorm.SimpleSql +import anorm.SqlStringInterpolation +import anorm.ToStatement +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class CategoryRepoImpl extends CategoryRepo { + override def delete: DeleteBuilder[CategoryFields, CategoryRow] = { + DeleteBuilder(""""frontpage"."category"""", CategoryFields.structure) + } + override def deleteById(id: CategoryId)(implicit c: Connection): Boolean = { + SQL"""delete from "frontpage"."category" where "id" = ${ParameterValue(id, null, CategoryId.toStatement)}""".executeUpdate() > 0 + } + override def deleteByIds(ids: Array[CategoryId])(implicit c: Connection): Int = { + SQL"""delete + from "frontpage"."category" + where "id" = ANY(${ids}) + """.executeUpdate() + + } + override def insert(unsaved: CategoryRow)(implicit c: Connection): CategoryRow = { + SQL"""insert into "frontpage"."category"("id", "name") + values (${ParameterValue(unsaved.id, null, CategoryId.toStatement)}::uuid, ${ParameterValue(unsaved.name, null, ToStatement.stringToStatement)}) + returning "id", "name" + """ + .executeInsert(CategoryRow.rowParser(1).single) + + } + override def insert(unsaved: CategoryRowUnsaved)(implicit c: Connection): CategoryRow = { + val namedParameters = List( + Some((NamedParameter("name", ParameterValue(unsaved.name, null, ToStatement.stringToStatement)), "")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("id", ParameterValue(value, null, CategoryId.toStatement)), "::uuid")) + } + ).flatten + val quote = '"'.toString + if (namedParameters.isEmpty) { + SQL"""insert into "frontpage"."category" default values + returning "id", "name" + """ + .executeInsert(CategoryRow.rowParser(1).single) + } else { + val q = s"""insert into "frontpage"."category"(${namedParameters.map{case (x, _) => quote + x.name + quote}.mkString(", ")}) + values (${namedParameters.map{ case (np, cast) => s"{${np.name}}$cast"}.mkString(", ")}) + returning "id", "name" + """ + SimpleSql(SQL(q), namedParameters.map { case (np, _) => np.tupled }.toMap, RowParser.successful) + .executeInsert(CategoryRow.rowParser(1).single) + } + + } + override def insertStreaming(unsaved: Iterator[CategoryRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."category"("id", "name") FROM STDIN""", batchSize, unsaved)(CategoryRow.text, c) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[CategoryRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."category"("name", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(CategoryRowUnsaved.text, c) + } + override def select: SelectBuilder[CategoryFields, CategoryRow] = { + SelectBuilderSql(""""frontpage"."category"""", CategoryFields.structure, CategoryRow.rowParser) + } + override def selectAll(implicit c: Connection): List[CategoryRow] = { + SQL"""select "id", "name" + from "frontpage"."category" + """.as(CategoryRow.rowParser(1).*) + } + override def selectById(id: CategoryId)(implicit c: Connection): Option[CategoryRow] = { + SQL"""select "id", "name" + from "frontpage"."category" + where "id" = ${ParameterValue(id, null, CategoryId.toStatement)} + """.as(CategoryRow.rowParser(1).singleOpt) + } + override def selectByIds(ids: Array[CategoryId])(implicit c: Connection): List[CategoryRow] = { + SQL"""select "id", "name" + from "frontpage"."category" + where "id" = ANY(${ids}) + """.as(CategoryRow.rowParser(1).*) + + } + override def selectByIdsTracked(ids: Array[CategoryId])(implicit c: Connection): Map[CategoryId, CategoryRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[CategoryFields, CategoryRow] = { + UpdateBuilder(""""frontpage"."category"""", CategoryFields.structure, CategoryRow.rowParser) + } + override def update(row: CategoryRow)(implicit c: Connection): Boolean = { + val id = row.id + SQL"""update "frontpage"."category" + set "name" = ${ParameterValue(row.name, null, ToStatement.stringToStatement)} + where "id" = ${ParameterValue(id, null, CategoryId.toStatement)} + """.executeUpdate() > 0 + } + override def upsert(unsaved: CategoryRow)(implicit c: Connection): CategoryRow = { + SQL"""insert into "frontpage"."category"("id", "name") + values ( + ${ParameterValue(unsaved.id, null, CategoryId.toStatement)}::uuid, + ${ParameterValue(unsaved.name, null, ToStatement.stringToStatement)} + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name" + returning "id", "name" + """ + .executeInsert(CategoryRow.rowParser(1).single) + + } + override def upsertBatch(unsaved: Iterable[CategoryRow])(implicit c: Connection): List[CategoryRow] = { + def toNamedParameter(row: CategoryRow): List[NamedParameter] = List( + NamedParameter("id", ParameterValue(row.id, null, CategoryId.toStatement)), + NamedParameter("name", ParameterValue(row.name, null, ToStatement.stringToStatement)) + ) + unsaved.toList match { + case Nil => Nil + case head :: rest => + new anorm.adventureworks.ExecuteReturningSyntax.Ops( + BatchSql( + s"""insert into "frontpage"."category"("id", "name") + values ({id}::uuid, {name}) + on conflict ("id") + do update set + "name" = EXCLUDED."name" + returning "id", "name" + """, + toNamedParameter(head), + rest.map(toNamedParameter)* + ) + ).executeReturning(CategoryRow.rowParser(1).*) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[CategoryRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + SQL"""create temporary table category_TEMP (like "frontpage"."category") on commit drop""".execute(): @nowarn + streamingInsert(s"""copy category_TEMP("id", "name") from stdin""", batchSize, unsaved)(CategoryRow.text, c): @nowarn + SQL"""insert into "frontpage"."category"("id", "name") + select * from category_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name" + ; + drop table category_TEMP;""".executeUpdate() + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepoMock.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepoMock.scala new file mode 100644 index 0000000000..2b12ce88f2 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepoMock.scala @@ -0,0 +1,103 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class CategoryRepoMock(toRow: Function1[CategoryRowUnsaved, CategoryRow], + map: scala.collection.mutable.Map[CategoryId, CategoryRow] = scala.collection.mutable.Map.empty) extends CategoryRepo { + override def delete: DeleteBuilder[CategoryFields, CategoryRow] = { + DeleteBuilderMock(DeleteParams.empty, CategoryFields.structure, map) + } + override def deleteById(id: CategoryId)(implicit c: Connection): Boolean = { + map.remove(id).isDefined + } + override def deleteByIds(ids: Array[CategoryId])(implicit c: Connection): Int = { + ids.map(id => map.remove(id)).count(_.isDefined) + } + override def insert(unsaved: CategoryRow)(implicit c: Connection): CategoryRow = { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + override def insert(unsaved: CategoryRowUnsaved)(implicit c: Connection): CategoryRow = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Iterator[CategoryRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size.toLong + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[CategoryRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + } + unsaved.size.toLong + } + override def select: SelectBuilder[CategoryFields, CategoryRow] = { + SelectBuilderMock(CategoryFields.structure, () => map.values.toList, SelectParams.empty) + } + override def selectAll(implicit c: Connection): List[CategoryRow] = { + map.values.toList + } + override def selectById(id: CategoryId)(implicit c: Connection): Option[CategoryRow] = { + map.get(id) + } + override def selectByIds(ids: Array[CategoryId])(implicit c: Connection): List[CategoryRow] = { + ids.flatMap(map.get).toList + } + override def selectByIdsTracked(ids: Array[CategoryId])(implicit c: Connection): Map[CategoryId, CategoryRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[CategoryFields, CategoryRow] = { + UpdateBuilderMock(UpdateParams.empty, CategoryFields.structure, map) + } + override def update(row: CategoryRow)(implicit c: Connection): Boolean = { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + override def upsert(unsaved: CategoryRow)(implicit c: Connection): CategoryRow = { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + override def upsertBatch(unsaved: Iterable[CategoryRow])(implicit c: Connection): List[CategoryRow] = { + unsaved.map { row => + map += (row.id -> row) + row + }.toList + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[CategoryRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryRow.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryRow.scala new file mode 100644 index 0000000000..524dbefe55 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryRow.scala @@ -0,0 +1,63 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import adventureworks.customtypes.Defaulted +import anorm.Column +import anorm.RowParser +import anorm.Success +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** Table: frontpage.category + Primary key: id */ +case class CategoryRow( + /** Default: gen_random_uuid() */ + id: CategoryId, + name: String +){ + def toUnsavedRow(id: Defaulted[CategoryId]): CategoryRowUnsaved = + CategoryRowUnsaved(name, id) + } + +object CategoryRow { + implicit lazy val reads: Reads[CategoryRow] = Reads[CategoryRow](json => JsResult.fromTry( + Try( + CategoryRow( + id = json.\("id").as(CategoryId.reads), + name = json.\("name").as(Reads.StringReads) + ) + ) + ), + ) + def rowParser(idx: Int): RowParser[CategoryRow] = RowParser[CategoryRow] { row => + Success( + CategoryRow( + id = row(idx + 0)(CategoryId.column), + name = row(idx + 1)(Column.columnToString) + ) + ) + } + implicit lazy val text: Text[CategoryRow] = Text.instance[CategoryRow]{ (row, sb) => + CategoryId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + } + implicit lazy val writes: OWrites[CategoryRow] = OWrites[CategoryRow](o => + new JsObject(ListMap[String, JsValue]( + "id" -> CategoryId.writes.writes(o.id), + "name" -> Writes.StringWrites.writes(o.name) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryRowUnsaved.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryRowUnsaved.scala new file mode 100644 index 0000000000..1994cfb66d --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/category/CategoryRowUnsaved.scala @@ -0,0 +1,56 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import adventureworks.customtypes.Defaulted +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** This class corresponds to a row in table `frontpage.category` which has not been persisted yet */ +case class CategoryRowUnsaved( + name: String, + /** Default: gen_random_uuid() */ + id: Defaulted[CategoryId] = Defaulted.UseDefault +) { + def toRow(idDefault: => CategoryId): CategoryRow = + CategoryRow( + name = name, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object CategoryRowUnsaved { + implicit lazy val reads: Reads[CategoryRowUnsaved] = Reads[CategoryRowUnsaved](json => JsResult.fromTry( + Try( + CategoryRowUnsaved( + name = json.\("name").as(Reads.StringReads), + id = json.\("id").as(Defaulted.reads(CategoryId.reads)) + ) + ) + ), + ) + implicit lazy val text: Text[CategoryRowUnsaved] = Text.instance[CategoryRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Defaulted.text(CategoryId.text).unsafeEncode(row.id, sb) + } + implicit lazy val writes: OWrites[CategoryRowUnsaved] = OWrites[CategoryRowUnsaved](o => + new JsObject(ListMap[String, JsValue]( + "name" -> Writes.StringWrites.writes(o.name), + "id" -> Defaulted.writes(CategoryId.writes).writes(o.id) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyFields.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyFields.scala new file mode 100644 index 0000000000..e6777fa969 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyFields.scala @@ -0,0 +1,40 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.Structure.Relation + +trait CompanyFields { + def id: IdField[CompanyId, CompanyRow] + def name: Field[String, CompanyRow] +} + +object CompanyFields { + lazy val structure: Relation[CompanyFields, CompanyRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[CompanyFields, CompanyRow] { + + override lazy val fields: CompanyFields = new CompanyFields { + override def id = IdField[CompanyId, CompanyRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, CompanyRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, CompanyRow]] = + List[FieldLikeNoHkt[?, CompanyRow]](fields.id, fields.name) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyId.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyId.scala new file mode 100644 index 0000000000..a4070f9462 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import adventureworks.customtypes.TypoUUID +import anorm.Column +import anorm.ParameterMetaData +import anorm.ToStatement +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.company` */ +case class CompanyId(value: TypoUUID) extends AnyVal +object CompanyId { + implicit lazy val arrayColumn: Column[Array[CompanyId]] = Column.columnToArray(column, implicitly) + implicit lazy val arrayToStatement: ToStatement[Array[CompanyId]] = TypoUUID.arrayToStatement.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[CompanyId, TypoUUID] = Bijection[CompanyId, TypoUUID](_.value)(CompanyId.apply) + implicit lazy val column: Column[CompanyId] = TypoUUID.column.map(CompanyId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[CompanyId] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[CompanyId] = new ParameterMetaData[CompanyId] { + override def sqlType: String = TypoUUID.parameterMetadata.sqlType + override def jdbcType: Int = TypoUUID.parameterMetadata.jdbcType + } + implicit lazy val reads: Reads[CompanyId] = TypoUUID.reads.map(CompanyId.apply) + implicit lazy val text: Text[CompanyId] = new Text[CompanyId] { + override def unsafeEncode(v: CompanyId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: CompanyId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } + implicit lazy val toStatement: ToStatement[CompanyId] = TypoUUID.toStatement.contramap(_.value) + implicit lazy val writes: Writes[CompanyId] = TypoUUID.writes.contramap(_.value) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepo.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepo.scala new file mode 100644 index 0000000000..049c815b6d --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepo.scala @@ -0,0 +1,35 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import java.sql.Connection +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait CompanyRepo { + def delete: DeleteBuilder[CompanyFields, CompanyRow] + def deleteById(id: CompanyId)(implicit c: Connection): Boolean + def deleteByIds(ids: Array[CompanyId])(implicit c: Connection): Int + def insert(unsaved: CompanyRow)(implicit c: Connection): CompanyRow + def insert(unsaved: CompanyRowUnsaved)(implicit c: Connection): CompanyRow + def insertStreaming(unsaved: Iterator[CompanyRow], batchSize: Int = 10000)(implicit c: Connection): Long + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Iterator[CompanyRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long + def select: SelectBuilder[CompanyFields, CompanyRow] + def selectAll(implicit c: Connection): List[CompanyRow] + def selectById(id: CompanyId)(implicit c: Connection): Option[CompanyRow] + def selectByIds(ids: Array[CompanyId])(implicit c: Connection): List[CompanyRow] + def selectByIdsTracked(ids: Array[CompanyId])(implicit c: Connection): Map[CompanyId, CompanyRow] + def update: UpdateBuilder[CompanyFields, CompanyRow] + def update(row: CompanyRow)(implicit c: Connection): Boolean + def upsert(unsaved: CompanyRow)(implicit c: Connection): CompanyRow + def upsertBatch(unsaved: Iterable[CompanyRow])(implicit c: Connection): List[CompanyRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Iterator[CompanyRow], batchSize: Int = 10000)(implicit c: Connection): Int +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepoImpl.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepoImpl.scala new file mode 100644 index 0000000000..684194d120 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepoImpl.scala @@ -0,0 +1,163 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import adventureworks.customtypes.Defaulted +import anorm.BatchSql +import anorm.NamedParameter +import anorm.ParameterValue +import anorm.RowParser +import anorm.SQL +import anorm.SimpleSql +import anorm.SqlStringInterpolation +import anorm.ToStatement +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class CompanyRepoImpl extends CompanyRepo { + override def delete: DeleteBuilder[CompanyFields, CompanyRow] = { + DeleteBuilder(""""frontpage"."company"""", CompanyFields.structure) + } + override def deleteById(id: CompanyId)(implicit c: Connection): Boolean = { + SQL"""delete from "frontpage"."company" where "id" = ${ParameterValue(id, null, CompanyId.toStatement)}""".executeUpdate() > 0 + } + override def deleteByIds(ids: Array[CompanyId])(implicit c: Connection): Int = { + SQL"""delete + from "frontpage"."company" + where "id" = ANY(${ids}) + """.executeUpdate() + + } + override def insert(unsaved: CompanyRow)(implicit c: Connection): CompanyRow = { + SQL"""insert into "frontpage"."company"("id", "name") + values (${ParameterValue(unsaved.id, null, CompanyId.toStatement)}::uuid, ${ParameterValue(unsaved.name, null, ToStatement.stringToStatement)}) + returning "id", "name" + """ + .executeInsert(CompanyRow.rowParser(1).single) + + } + override def insert(unsaved: CompanyRowUnsaved)(implicit c: Connection): CompanyRow = { + val namedParameters = List( + Some((NamedParameter("name", ParameterValue(unsaved.name, null, ToStatement.stringToStatement)), "")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("id", ParameterValue(value, null, CompanyId.toStatement)), "::uuid")) + } + ).flatten + val quote = '"'.toString + if (namedParameters.isEmpty) { + SQL"""insert into "frontpage"."company" default values + returning "id", "name" + """ + .executeInsert(CompanyRow.rowParser(1).single) + } else { + val q = s"""insert into "frontpage"."company"(${namedParameters.map{case (x, _) => quote + x.name + quote}.mkString(", ")}) + values (${namedParameters.map{ case (np, cast) => s"{${np.name}}$cast"}.mkString(", ")}) + returning "id", "name" + """ + SimpleSql(SQL(q), namedParameters.map { case (np, _) => np.tupled }.toMap, RowParser.successful) + .executeInsert(CompanyRow.rowParser(1).single) + } + + } + override def insertStreaming(unsaved: Iterator[CompanyRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."company"("id", "name") FROM STDIN""", batchSize, unsaved)(CompanyRow.text, c) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[CompanyRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."company"("name", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(CompanyRowUnsaved.text, c) + } + override def select: SelectBuilder[CompanyFields, CompanyRow] = { + SelectBuilderSql(""""frontpage"."company"""", CompanyFields.structure, CompanyRow.rowParser) + } + override def selectAll(implicit c: Connection): List[CompanyRow] = { + SQL"""select "id", "name" + from "frontpage"."company" + """.as(CompanyRow.rowParser(1).*) + } + override def selectById(id: CompanyId)(implicit c: Connection): Option[CompanyRow] = { + SQL"""select "id", "name" + from "frontpage"."company" + where "id" = ${ParameterValue(id, null, CompanyId.toStatement)} + """.as(CompanyRow.rowParser(1).singleOpt) + } + override def selectByIds(ids: Array[CompanyId])(implicit c: Connection): List[CompanyRow] = { + SQL"""select "id", "name" + from "frontpage"."company" + where "id" = ANY(${ids}) + """.as(CompanyRow.rowParser(1).*) + + } + override def selectByIdsTracked(ids: Array[CompanyId])(implicit c: Connection): Map[CompanyId, CompanyRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[CompanyFields, CompanyRow] = { + UpdateBuilder(""""frontpage"."company"""", CompanyFields.structure, CompanyRow.rowParser) + } + override def update(row: CompanyRow)(implicit c: Connection): Boolean = { + val id = row.id + SQL"""update "frontpage"."company" + set "name" = ${ParameterValue(row.name, null, ToStatement.stringToStatement)} + where "id" = ${ParameterValue(id, null, CompanyId.toStatement)} + """.executeUpdate() > 0 + } + override def upsert(unsaved: CompanyRow)(implicit c: Connection): CompanyRow = { + SQL"""insert into "frontpage"."company"("id", "name") + values ( + ${ParameterValue(unsaved.id, null, CompanyId.toStatement)}::uuid, + ${ParameterValue(unsaved.name, null, ToStatement.stringToStatement)} + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name" + returning "id", "name" + """ + .executeInsert(CompanyRow.rowParser(1).single) + + } + override def upsertBatch(unsaved: Iterable[CompanyRow])(implicit c: Connection): List[CompanyRow] = { + def toNamedParameter(row: CompanyRow): List[NamedParameter] = List( + NamedParameter("id", ParameterValue(row.id, null, CompanyId.toStatement)), + NamedParameter("name", ParameterValue(row.name, null, ToStatement.stringToStatement)) + ) + unsaved.toList match { + case Nil => Nil + case head :: rest => + new anorm.adventureworks.ExecuteReturningSyntax.Ops( + BatchSql( + s"""insert into "frontpage"."company"("id", "name") + values ({id}::uuid, {name}) + on conflict ("id") + do update set + "name" = EXCLUDED."name" + returning "id", "name" + """, + toNamedParameter(head), + rest.map(toNamedParameter)* + ) + ).executeReturning(CompanyRow.rowParser(1).*) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[CompanyRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + SQL"""create temporary table company_TEMP (like "frontpage"."company") on commit drop""".execute(): @nowarn + streamingInsert(s"""copy company_TEMP("id", "name") from stdin""", batchSize, unsaved)(CompanyRow.text, c): @nowarn + SQL"""insert into "frontpage"."company"("id", "name") + select * from company_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name" + ; + drop table company_TEMP;""".executeUpdate() + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepoMock.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepoMock.scala new file mode 100644 index 0000000000..e51091befc --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepoMock.scala @@ -0,0 +1,103 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class CompanyRepoMock(toRow: Function1[CompanyRowUnsaved, CompanyRow], + map: scala.collection.mutable.Map[CompanyId, CompanyRow] = scala.collection.mutable.Map.empty) extends CompanyRepo { + override def delete: DeleteBuilder[CompanyFields, CompanyRow] = { + DeleteBuilderMock(DeleteParams.empty, CompanyFields.structure, map) + } + override def deleteById(id: CompanyId)(implicit c: Connection): Boolean = { + map.remove(id).isDefined + } + override def deleteByIds(ids: Array[CompanyId])(implicit c: Connection): Int = { + ids.map(id => map.remove(id)).count(_.isDefined) + } + override def insert(unsaved: CompanyRow)(implicit c: Connection): CompanyRow = { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + override def insert(unsaved: CompanyRowUnsaved)(implicit c: Connection): CompanyRow = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Iterator[CompanyRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size.toLong + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[CompanyRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + } + unsaved.size.toLong + } + override def select: SelectBuilder[CompanyFields, CompanyRow] = { + SelectBuilderMock(CompanyFields.structure, () => map.values.toList, SelectParams.empty) + } + override def selectAll(implicit c: Connection): List[CompanyRow] = { + map.values.toList + } + override def selectById(id: CompanyId)(implicit c: Connection): Option[CompanyRow] = { + map.get(id) + } + override def selectByIds(ids: Array[CompanyId])(implicit c: Connection): List[CompanyRow] = { + ids.flatMap(map.get).toList + } + override def selectByIdsTracked(ids: Array[CompanyId])(implicit c: Connection): Map[CompanyId, CompanyRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[CompanyFields, CompanyRow] = { + UpdateBuilderMock(UpdateParams.empty, CompanyFields.structure, map) + } + override def update(row: CompanyRow)(implicit c: Connection): Boolean = { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + override def upsert(unsaved: CompanyRow)(implicit c: Connection): CompanyRow = { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + override def upsertBatch(unsaved: Iterable[CompanyRow])(implicit c: Connection): List[CompanyRow] = { + unsaved.map { row => + map += (row.id -> row) + row + }.toList + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[CompanyRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyRow.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyRow.scala new file mode 100644 index 0000000000..53150b3443 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyRow.scala @@ -0,0 +1,63 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import adventureworks.customtypes.Defaulted +import anorm.Column +import anorm.RowParser +import anorm.Success +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** Table: frontpage.company + Primary key: id */ +case class CompanyRow( + /** Default: gen_random_uuid() */ + id: CompanyId, + name: String +){ + def toUnsavedRow(id: Defaulted[CompanyId]): CompanyRowUnsaved = + CompanyRowUnsaved(name, id) + } + +object CompanyRow { + implicit lazy val reads: Reads[CompanyRow] = Reads[CompanyRow](json => JsResult.fromTry( + Try( + CompanyRow( + id = json.\("id").as(CompanyId.reads), + name = json.\("name").as(Reads.StringReads) + ) + ) + ), + ) + def rowParser(idx: Int): RowParser[CompanyRow] = RowParser[CompanyRow] { row => + Success( + CompanyRow( + id = row(idx + 0)(CompanyId.column), + name = row(idx + 1)(Column.columnToString) + ) + ) + } + implicit lazy val text: Text[CompanyRow] = Text.instance[CompanyRow]{ (row, sb) => + CompanyId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + } + implicit lazy val writes: OWrites[CompanyRow] = OWrites[CompanyRow](o => + new JsObject(ListMap[String, JsValue]( + "id" -> CompanyId.writes.writes(o.id), + "name" -> Writes.StringWrites.writes(o.name) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyRowUnsaved.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyRowUnsaved.scala new file mode 100644 index 0000000000..9d1ecd1c66 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/company/CompanyRowUnsaved.scala @@ -0,0 +1,56 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import adventureworks.customtypes.Defaulted +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** This class corresponds to a row in table `frontpage.company` which has not been persisted yet */ +case class CompanyRowUnsaved( + name: String, + /** Default: gen_random_uuid() */ + id: Defaulted[CompanyId] = Defaulted.UseDefault +) { + def toRow(idDefault: => CompanyId): CompanyRow = + CompanyRow( + name = name, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object CompanyRowUnsaved { + implicit lazy val reads: Reads[CompanyRowUnsaved] = Reads[CompanyRowUnsaved](json => JsResult.fromTry( + Try( + CompanyRowUnsaved( + name = json.\("name").as(Reads.StringReads), + id = json.\("id").as(Defaulted.reads(CompanyId.reads)) + ) + ) + ), + ) + implicit lazy val text: Text[CompanyRowUnsaved] = Text.instance[CompanyRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Defaulted.text(CompanyId.text).unsafeEncode(row.id, sb) + } + implicit lazy val writes: OWrites[CompanyRowUnsaved] = OWrites[CompanyRowUnsaved](o => + new JsObject(ListMap[String, JsValue]( + "name" -> Writes.StringWrites.writes(o.name), + "id" -> Defaulted.writes(CompanyId.writes).writes(o.id) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerFields.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerFields.scala new file mode 100644 index 0000000000..50841ec7c7 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerFields.scala @@ -0,0 +1,53 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import adventureworks.frontpage.user.UserFields +import adventureworks.frontpage.user.UserId +import adventureworks.frontpage.user.UserRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait CustomerFields { + def id: IdField[CustomerId, CustomerRow] + def userId: OptField[UserId, CustomerRow] + def companyName: OptField[String, CustomerRow] + def creditLimit: OptField[BigDecimal, CustomerRow] + def verified: OptField[Boolean, CustomerRow] + def fkUser: ForeignKey[UserFields, UserRow] = + ForeignKey[UserFields, UserRow]("frontpage.customer_user_id_fkey", Nil) + .withColumnPair(userId, _.id) +} + +object CustomerFields { + lazy val structure: Relation[CustomerFields, CustomerRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[CustomerFields, CustomerRow] { + + override lazy val fields: CustomerFields = new CustomerFields { + override def id = IdField[CustomerId, CustomerRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def userId = OptField[UserId, CustomerRow](_path, "user_id", None, Some("uuid"), x => x.userId, (row, value) => row.copy(userId = value)) + override def companyName = OptField[String, CustomerRow](_path, "company_name", None, None, x => x.companyName, (row, value) => row.copy(companyName = value)) + override def creditLimit = OptField[BigDecimal, CustomerRow](_path, "credit_limit", None, Some("numeric"), x => x.creditLimit, (row, value) => row.copy(creditLimit = value)) + override def verified = OptField[Boolean, CustomerRow](_path, "verified", None, None, x => x.verified, (row, value) => row.copy(verified = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, CustomerRow]] = + List[FieldLikeNoHkt[?, CustomerRow]](fields.id, fields.userId, fields.companyName, fields.creditLimit, fields.verified) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerId.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerId.scala new file mode 100644 index 0000000000..a377b519e5 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import adventureworks.customtypes.TypoUUID +import anorm.Column +import anorm.ParameterMetaData +import anorm.ToStatement +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.customer` */ +case class CustomerId(value: TypoUUID) extends AnyVal +object CustomerId { + implicit lazy val arrayColumn: Column[Array[CustomerId]] = Column.columnToArray(column, implicitly) + implicit lazy val arrayToStatement: ToStatement[Array[CustomerId]] = TypoUUID.arrayToStatement.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[CustomerId, TypoUUID] = Bijection[CustomerId, TypoUUID](_.value)(CustomerId.apply) + implicit lazy val column: Column[CustomerId] = TypoUUID.column.map(CustomerId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[CustomerId] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[CustomerId] = new ParameterMetaData[CustomerId] { + override def sqlType: String = TypoUUID.parameterMetadata.sqlType + override def jdbcType: Int = TypoUUID.parameterMetadata.jdbcType + } + implicit lazy val reads: Reads[CustomerId] = TypoUUID.reads.map(CustomerId.apply) + implicit lazy val text: Text[CustomerId] = new Text[CustomerId] { + override def unsafeEncode(v: CustomerId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: CustomerId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } + implicit lazy val toStatement: ToStatement[CustomerId] = TypoUUID.toStatement.contramap(_.value) + implicit lazy val writes: Writes[CustomerId] = TypoUUID.writes.contramap(_.value) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepo.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepo.scala new file mode 100644 index 0000000000..700d622abe --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepo.scala @@ -0,0 +1,35 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import java.sql.Connection +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait CustomerRepo { + def delete: DeleteBuilder[CustomerFields, CustomerRow] + def deleteById(id: CustomerId)(implicit c: Connection): Boolean + def deleteByIds(ids: Array[CustomerId])(implicit c: Connection): Int + def insert(unsaved: CustomerRow)(implicit c: Connection): CustomerRow + def insert(unsaved: CustomerRowUnsaved)(implicit c: Connection): CustomerRow + def insertStreaming(unsaved: Iterator[CustomerRow], batchSize: Int = 10000)(implicit c: Connection): Long + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Iterator[CustomerRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long + def select: SelectBuilder[CustomerFields, CustomerRow] + def selectAll(implicit c: Connection): List[CustomerRow] + def selectById(id: CustomerId)(implicit c: Connection): Option[CustomerRow] + def selectByIds(ids: Array[CustomerId])(implicit c: Connection): List[CustomerRow] + def selectByIdsTracked(ids: Array[CustomerId])(implicit c: Connection): Map[CustomerId, CustomerRow] + def update: UpdateBuilder[CustomerFields, CustomerRow] + def update(row: CustomerRow)(implicit c: Connection): Boolean + def upsert(unsaved: CustomerRow)(implicit c: Connection): CustomerRow + def upsertBatch(unsaved: Iterable[CustomerRow])(implicit c: Connection): List[CustomerRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Iterator[CustomerRow], batchSize: Int = 10000)(implicit c: Connection): Int +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepoImpl.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepoImpl.scala new file mode 100644 index 0000000000..8a0746993d --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepoImpl.scala @@ -0,0 +1,189 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.user.UserId +import anorm.BatchSql +import anorm.NamedParameter +import anorm.ParameterMetaData +import anorm.ParameterValue +import anorm.RowParser +import anorm.SQL +import anorm.SimpleSql +import anorm.SqlStringInterpolation +import anorm.ToStatement +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class CustomerRepoImpl extends CustomerRepo { + override def delete: DeleteBuilder[CustomerFields, CustomerRow] = { + DeleteBuilder(""""frontpage"."customer"""", CustomerFields.structure) + } + override def deleteById(id: CustomerId)(implicit c: Connection): Boolean = { + SQL"""delete from "frontpage"."customer" where "id" = ${ParameterValue(id, null, CustomerId.toStatement)}""".executeUpdate() > 0 + } + override def deleteByIds(ids: Array[CustomerId])(implicit c: Connection): Int = { + SQL"""delete + from "frontpage"."customer" + where "id" = ANY(${ids}) + """.executeUpdate() + + } + override def insert(unsaved: CustomerRow)(implicit c: Connection): CustomerRow = { + SQL"""insert into "frontpage"."customer"("id", "user_id", "company_name", "credit_limit", "verified") + values (${ParameterValue(unsaved.id, null, CustomerId.toStatement)}::uuid, ${ParameterValue(unsaved.userId, null, ToStatement.optionToStatement(UserId.toStatement, UserId.parameterMetadata))}::uuid, ${ParameterValue(unsaved.companyName, null, ToStatement.optionToStatement(ToStatement.stringToStatement, ParameterMetaData.StringParameterMetaData))}, ${ParameterValue(unsaved.creditLimit, null, ToStatement.optionToStatement(ToStatement.scalaBigDecimalToStatement, ParameterMetaData.BigDecimalParameterMetaData))}::numeric, ${ParameterValue(unsaved.verified, null, ToStatement.optionToStatement(ToStatement.booleanToStatement, ParameterMetaData.BooleanParameterMetaData))}) + returning "id", "user_id", "company_name", "credit_limit", "verified" + """ + .executeInsert(CustomerRow.rowParser(1).single) + + } + override def insert(unsaved: CustomerRowUnsaved)(implicit c: Connection): CustomerRow = { + val namedParameters = List( + Some((NamedParameter("user_id", ParameterValue(unsaved.userId, null, ToStatement.optionToStatement(UserId.toStatement, UserId.parameterMetadata))), "::uuid")), + Some((NamedParameter("company_name", ParameterValue(unsaved.companyName, null, ToStatement.optionToStatement(ToStatement.stringToStatement, ParameterMetaData.StringParameterMetaData))), "")), + Some((NamedParameter("credit_limit", ParameterValue(unsaved.creditLimit, null, ToStatement.optionToStatement(ToStatement.scalaBigDecimalToStatement, ParameterMetaData.BigDecimalParameterMetaData))), "::numeric")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("id", ParameterValue(value, null, CustomerId.toStatement)), "::uuid")) + }, + unsaved.verified match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("verified", ParameterValue(value, null, ToStatement.optionToStatement(ToStatement.booleanToStatement, ParameterMetaData.BooleanParameterMetaData))), "")) + } + ).flatten + val quote = '"'.toString + if (namedParameters.isEmpty) { + SQL"""insert into "frontpage"."customer" default values + returning "id", "user_id", "company_name", "credit_limit", "verified" + """ + .executeInsert(CustomerRow.rowParser(1).single) + } else { + val q = s"""insert into "frontpage"."customer"(${namedParameters.map{case (x, _) => quote + x.name + quote}.mkString(", ")}) + values (${namedParameters.map{ case (np, cast) => s"{${np.name}}$cast"}.mkString(", ")}) + returning "id", "user_id", "company_name", "credit_limit", "verified" + """ + SimpleSql(SQL(q), namedParameters.map { case (np, _) => np.tupled }.toMap, RowParser.successful) + .executeInsert(CustomerRow.rowParser(1).single) + } + + } + override def insertStreaming(unsaved: Iterator[CustomerRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."customer"("id", "user_id", "company_name", "credit_limit", "verified") FROM STDIN""", batchSize, unsaved)(CustomerRow.text, c) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[CustomerRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."customer"("user_id", "company_name", "credit_limit", "id", "verified") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(CustomerRowUnsaved.text, c) + } + override def select: SelectBuilder[CustomerFields, CustomerRow] = { + SelectBuilderSql(""""frontpage"."customer"""", CustomerFields.structure, CustomerRow.rowParser) + } + override def selectAll(implicit c: Connection): List[CustomerRow] = { + SQL"""select "id", "user_id", "company_name", "credit_limit", "verified" + from "frontpage"."customer" + """.as(CustomerRow.rowParser(1).*) + } + override def selectById(id: CustomerId)(implicit c: Connection): Option[CustomerRow] = { + SQL"""select "id", "user_id", "company_name", "credit_limit", "verified" + from "frontpage"."customer" + where "id" = ${ParameterValue(id, null, CustomerId.toStatement)} + """.as(CustomerRow.rowParser(1).singleOpt) + } + override def selectByIds(ids: Array[CustomerId])(implicit c: Connection): List[CustomerRow] = { + SQL"""select "id", "user_id", "company_name", "credit_limit", "verified" + from "frontpage"."customer" + where "id" = ANY(${ids}) + """.as(CustomerRow.rowParser(1).*) + + } + override def selectByIdsTracked(ids: Array[CustomerId])(implicit c: Connection): Map[CustomerId, CustomerRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[CustomerFields, CustomerRow] = { + UpdateBuilder(""""frontpage"."customer"""", CustomerFields.structure, CustomerRow.rowParser) + } + override def update(row: CustomerRow)(implicit c: Connection): Boolean = { + val id = row.id + SQL"""update "frontpage"."customer" + set "user_id" = ${ParameterValue(row.userId, null, ToStatement.optionToStatement(UserId.toStatement, UserId.parameterMetadata))}::uuid, + "company_name" = ${ParameterValue(row.companyName, null, ToStatement.optionToStatement(ToStatement.stringToStatement, ParameterMetaData.StringParameterMetaData))}, + "credit_limit" = ${ParameterValue(row.creditLimit, null, ToStatement.optionToStatement(ToStatement.scalaBigDecimalToStatement, ParameterMetaData.BigDecimalParameterMetaData))}::numeric, + "verified" = ${ParameterValue(row.verified, null, ToStatement.optionToStatement(ToStatement.booleanToStatement, ParameterMetaData.BooleanParameterMetaData))} + where "id" = ${ParameterValue(id, null, CustomerId.toStatement)} + """.executeUpdate() > 0 + } + override def upsert(unsaved: CustomerRow)(implicit c: Connection): CustomerRow = { + SQL"""insert into "frontpage"."customer"("id", "user_id", "company_name", "credit_limit", "verified") + values ( + ${ParameterValue(unsaved.id, null, CustomerId.toStatement)}::uuid, + ${ParameterValue(unsaved.userId, null, ToStatement.optionToStatement(UserId.toStatement, UserId.parameterMetadata))}::uuid, + ${ParameterValue(unsaved.companyName, null, ToStatement.optionToStatement(ToStatement.stringToStatement, ParameterMetaData.StringParameterMetaData))}, + ${ParameterValue(unsaved.creditLimit, null, ToStatement.optionToStatement(ToStatement.scalaBigDecimalToStatement, ParameterMetaData.BigDecimalParameterMetaData))}::numeric, + ${ParameterValue(unsaved.verified, null, ToStatement.optionToStatement(ToStatement.booleanToStatement, ParameterMetaData.BooleanParameterMetaData))} + ) + on conflict ("id") + do update set + "user_id" = EXCLUDED."user_id", + "company_name" = EXCLUDED."company_name", + "credit_limit" = EXCLUDED."credit_limit", + "verified" = EXCLUDED."verified" + returning "id", "user_id", "company_name", "credit_limit", "verified" + """ + .executeInsert(CustomerRow.rowParser(1).single) + + } + override def upsertBatch(unsaved: Iterable[CustomerRow])(implicit c: Connection): List[CustomerRow] = { + def toNamedParameter(row: CustomerRow): List[NamedParameter] = List( + NamedParameter("id", ParameterValue(row.id, null, CustomerId.toStatement)), + NamedParameter("user_id", ParameterValue(row.userId, null, ToStatement.optionToStatement(UserId.toStatement, UserId.parameterMetadata))), + NamedParameter("company_name", ParameterValue(row.companyName, null, ToStatement.optionToStatement(ToStatement.stringToStatement, ParameterMetaData.StringParameterMetaData))), + NamedParameter("credit_limit", ParameterValue(row.creditLimit, null, ToStatement.optionToStatement(ToStatement.scalaBigDecimalToStatement, ParameterMetaData.BigDecimalParameterMetaData))), + NamedParameter("verified", ParameterValue(row.verified, null, ToStatement.optionToStatement(ToStatement.booleanToStatement, ParameterMetaData.BooleanParameterMetaData))) + ) + unsaved.toList match { + case Nil => Nil + case head :: rest => + new anorm.adventureworks.ExecuteReturningSyntax.Ops( + BatchSql( + s"""insert into "frontpage"."customer"("id", "user_id", "company_name", "credit_limit", "verified") + values ({id}::uuid, {user_id}::uuid, {company_name}, {credit_limit}::numeric, {verified}) + on conflict ("id") + do update set + "user_id" = EXCLUDED."user_id", + "company_name" = EXCLUDED."company_name", + "credit_limit" = EXCLUDED."credit_limit", + "verified" = EXCLUDED."verified" + returning "id", "user_id", "company_name", "credit_limit", "verified" + """, + toNamedParameter(head), + rest.map(toNamedParameter)* + ) + ).executeReturning(CustomerRow.rowParser(1).*) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[CustomerRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + SQL"""create temporary table customer_TEMP (like "frontpage"."customer") on commit drop""".execute(): @nowarn + streamingInsert(s"""copy customer_TEMP("id", "user_id", "company_name", "credit_limit", "verified") from stdin""", batchSize, unsaved)(CustomerRow.text, c): @nowarn + SQL"""insert into "frontpage"."customer"("id", "user_id", "company_name", "credit_limit", "verified") + select * from customer_TEMP + on conflict ("id") + do update set + "user_id" = EXCLUDED."user_id", + "company_name" = EXCLUDED."company_name", + "credit_limit" = EXCLUDED."credit_limit", + "verified" = EXCLUDED."verified" + ; + drop table customer_TEMP;""".executeUpdate() + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepoMock.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepoMock.scala new file mode 100644 index 0000000000..6131302ab0 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepoMock.scala @@ -0,0 +1,103 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class CustomerRepoMock(toRow: Function1[CustomerRowUnsaved, CustomerRow], + map: scala.collection.mutable.Map[CustomerId, CustomerRow] = scala.collection.mutable.Map.empty) extends CustomerRepo { + override def delete: DeleteBuilder[CustomerFields, CustomerRow] = { + DeleteBuilderMock(DeleteParams.empty, CustomerFields.structure, map) + } + override def deleteById(id: CustomerId)(implicit c: Connection): Boolean = { + map.remove(id).isDefined + } + override def deleteByIds(ids: Array[CustomerId])(implicit c: Connection): Int = { + ids.map(id => map.remove(id)).count(_.isDefined) + } + override def insert(unsaved: CustomerRow)(implicit c: Connection): CustomerRow = { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + override def insert(unsaved: CustomerRowUnsaved)(implicit c: Connection): CustomerRow = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Iterator[CustomerRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size.toLong + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[CustomerRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + } + unsaved.size.toLong + } + override def select: SelectBuilder[CustomerFields, CustomerRow] = { + SelectBuilderMock(CustomerFields.structure, () => map.values.toList, SelectParams.empty) + } + override def selectAll(implicit c: Connection): List[CustomerRow] = { + map.values.toList + } + override def selectById(id: CustomerId)(implicit c: Connection): Option[CustomerRow] = { + map.get(id) + } + override def selectByIds(ids: Array[CustomerId])(implicit c: Connection): List[CustomerRow] = { + ids.flatMap(map.get).toList + } + override def selectByIdsTracked(ids: Array[CustomerId])(implicit c: Connection): Map[CustomerId, CustomerRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[CustomerFields, CustomerRow] = { + UpdateBuilderMock(UpdateParams.empty, CustomerFields.structure, map) + } + override def update(row: CustomerRow)(implicit c: Connection): Boolean = { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + override def upsert(unsaved: CustomerRow)(implicit c: Connection): CustomerRow = { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + override def upsertBatch(unsaved: Iterable[CustomerRow])(implicit c: Connection): List[CustomerRow] = { + unsaved.map { row => + map += (row.id -> row) + row + }.toList + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[CustomerRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRow.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRow.scala new file mode 100644 index 0000000000..86dc5167a8 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRow.scala @@ -0,0 +1,84 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.user.UserId +import anorm.Column +import anorm.RowParser +import anorm.Success +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** Table: frontpage.customer + Primary key: id */ +case class CustomerRow( + /** Default: gen_random_uuid() */ + id: CustomerId, + /** Points to [[user.UserRow.id]] */ + userId: Option[UserId], + companyName: Option[String], + creditLimit: Option[BigDecimal], + /** Default: false */ + verified: Option[Boolean] +){ + def toUnsavedRow(id: Defaulted[CustomerId], verified: Defaulted[Option[Boolean]] = Defaulted.Provided(this.verified)): CustomerRowUnsaved = + CustomerRowUnsaved(userId, companyName, creditLimit, id, verified) + } + +object CustomerRow { + implicit lazy val reads: Reads[CustomerRow] = Reads[CustomerRow](json => JsResult.fromTry( + Try( + CustomerRow( + id = json.\("id").as(CustomerId.reads), + userId = json.\("user_id").toOption.map(_.as(UserId.reads)), + companyName = json.\("company_name").toOption.map(_.as(Reads.StringReads)), + creditLimit = json.\("credit_limit").toOption.map(_.as(Reads.bigDecReads)), + verified = json.\("verified").toOption.map(_.as(Reads.BooleanReads)) + ) + ) + ), + ) + def rowParser(idx: Int): RowParser[CustomerRow] = RowParser[CustomerRow] { row => + Success( + CustomerRow( + id = row(idx + 0)(CustomerId.column), + userId = row(idx + 1)(Column.columnToOption(UserId.column)), + companyName = row(idx + 2)(Column.columnToOption(Column.columnToString)), + creditLimit = row(idx + 3)(Column.columnToOption(Column.columnToScalaBigDecimal)), + verified = row(idx + 4)(Column.columnToOption(Column.columnToBoolean)) + ) + ) + } + implicit lazy val text: Text[CustomerRow] = Text.instance[CustomerRow]{ (row, sb) => + CustomerId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.option(UserId.text).unsafeEncode(row.userId, sb) + sb.append(Text.DELIMETER) + Text.option(Text.stringInstance).unsafeEncode(row.companyName, sb) + sb.append(Text.DELIMETER) + Text.option(Text.bigDecimalInstance).unsafeEncode(row.creditLimit, sb) + sb.append(Text.DELIMETER) + Text.option(Text.booleanInstance).unsafeEncode(row.verified, sb) + } + implicit lazy val writes: OWrites[CustomerRow] = OWrites[CustomerRow](o => + new JsObject(ListMap[String, JsValue]( + "id" -> CustomerId.writes.writes(o.id), + "user_id" -> Writes.OptionWrites(UserId.writes).writes(o.userId), + "company_name" -> Writes.OptionWrites(Writes.StringWrites).writes(o.companyName), + "credit_limit" -> Writes.OptionWrites(Writes.BigDecimalWrites).writes(o.creditLimit), + "verified" -> Writes.OptionWrites(Writes.BooleanWrites).writes(o.verified) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRowUnsaved.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRowUnsaved.scala new file mode 100644 index 0000000000..57f2d2708e --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRowUnsaved.scala @@ -0,0 +1,80 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.user.UserId +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** This class corresponds to a row in table `frontpage.customer` which has not been persisted yet */ +case class CustomerRowUnsaved( + /** Points to [[user.UserRow.id]] */ + userId: Option[UserId], + companyName: Option[String], + creditLimit: Option[BigDecimal], + /** Default: gen_random_uuid() */ + id: Defaulted[CustomerId] = Defaulted.UseDefault, + /** Default: false */ + verified: Defaulted[Option[Boolean]] = Defaulted.UseDefault +) { + def toRow(idDefault: => CustomerId, verifiedDefault: => Option[Boolean]): CustomerRow = + CustomerRow( + userId = userId, + companyName = companyName, + creditLimit = creditLimit, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + }, + verified = verified match { + case Defaulted.UseDefault => verifiedDefault + case Defaulted.Provided(value) => value + } + ) +} +object CustomerRowUnsaved { + implicit lazy val reads: Reads[CustomerRowUnsaved] = Reads[CustomerRowUnsaved](json => JsResult.fromTry( + Try( + CustomerRowUnsaved( + userId = json.\("user_id").toOption.map(_.as(UserId.reads)), + companyName = json.\("company_name").toOption.map(_.as(Reads.StringReads)), + creditLimit = json.\("credit_limit").toOption.map(_.as(Reads.bigDecReads)), + id = json.\("id").as(Defaulted.reads(CustomerId.reads)), + verified = json.\("verified").as(Defaulted.readsOpt(Reads.BooleanReads)) + ) + ) + ), + ) + implicit lazy val text: Text[CustomerRowUnsaved] = Text.instance[CustomerRowUnsaved]{ (row, sb) => + Text.option(UserId.text).unsafeEncode(row.userId, sb) + sb.append(Text.DELIMETER) + Text.option(Text.stringInstance).unsafeEncode(row.companyName, sb) + sb.append(Text.DELIMETER) + Text.option(Text.bigDecimalInstance).unsafeEncode(row.creditLimit, sb) + sb.append(Text.DELIMETER) + Defaulted.text(CustomerId.text).unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text.booleanInstance)).unsafeEncode(row.verified, sb) + } + implicit lazy val writes: OWrites[CustomerRowUnsaved] = OWrites[CustomerRowUnsaved](o => + new JsObject(ListMap[String, JsValue]( + "user_id" -> Writes.OptionWrites(UserId.writes).writes(o.userId), + "company_name" -> Writes.OptionWrites(Writes.StringWrites).writes(o.companyName), + "credit_limit" -> Writes.OptionWrites(Writes.BigDecimalWrites).writes(o.creditLimit), + "id" -> Defaulted.writes(CustomerId.writes).writes(o.id), + "verified" -> Defaulted.writes(Writes.OptionWrites(Writes.BooleanWrites)).writes(o.verified) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentFields.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentFields.scala new file mode 100644 index 0000000000..deae7d4799 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentFields.scala @@ -0,0 +1,52 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import adventureworks.frontpage.company.CompanyFields +import adventureworks.frontpage.company.CompanyId +import adventureworks.frontpage.company.CompanyRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait DepartmentFields { + def id: IdField[DepartmentId, DepartmentRow] + def name: Field[String, DepartmentRow] + def budget: OptField[BigDecimal, DepartmentRow] + def companyId: OptField[CompanyId, DepartmentRow] + def fkCompany: ForeignKey[CompanyFields, CompanyRow] = + ForeignKey[CompanyFields, CompanyRow]("frontpage.department_company_id_fkey", Nil) + .withColumnPair(companyId, _.id) +} + +object DepartmentFields { + lazy val structure: Relation[DepartmentFields, DepartmentRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[DepartmentFields, DepartmentRow] { + + override lazy val fields: DepartmentFields = new DepartmentFields { + override def id = IdField[DepartmentId, DepartmentRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, DepartmentRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + override def budget = OptField[BigDecimal, DepartmentRow](_path, "budget", None, Some("numeric"), x => x.budget, (row, value) => row.copy(budget = value)) + override def companyId = OptField[CompanyId, DepartmentRow](_path, "company_id", None, Some("uuid"), x => x.companyId, (row, value) => row.copy(companyId = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, DepartmentRow]] = + List[FieldLikeNoHkt[?, DepartmentRow]](fields.id, fields.name, fields.budget, fields.companyId) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentId.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentId.scala new file mode 100644 index 0000000000..bf612c3234 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import adventureworks.customtypes.TypoUUID +import anorm.Column +import anorm.ParameterMetaData +import anorm.ToStatement +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.department` */ +case class DepartmentId(value: TypoUUID) extends AnyVal +object DepartmentId { + implicit lazy val arrayColumn: Column[Array[DepartmentId]] = Column.columnToArray(column, implicitly) + implicit lazy val arrayToStatement: ToStatement[Array[DepartmentId]] = TypoUUID.arrayToStatement.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[DepartmentId, TypoUUID] = Bijection[DepartmentId, TypoUUID](_.value)(DepartmentId.apply) + implicit lazy val column: Column[DepartmentId] = TypoUUID.column.map(DepartmentId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[DepartmentId] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[DepartmentId] = new ParameterMetaData[DepartmentId] { + override def sqlType: String = TypoUUID.parameterMetadata.sqlType + override def jdbcType: Int = TypoUUID.parameterMetadata.jdbcType + } + implicit lazy val reads: Reads[DepartmentId] = TypoUUID.reads.map(DepartmentId.apply) + implicit lazy val text: Text[DepartmentId] = new Text[DepartmentId] { + override def unsafeEncode(v: DepartmentId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: DepartmentId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } + implicit lazy val toStatement: ToStatement[DepartmentId] = TypoUUID.toStatement.contramap(_.value) + implicit lazy val writes: Writes[DepartmentId] = TypoUUID.writes.contramap(_.value) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepo.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepo.scala new file mode 100644 index 0000000000..7b9de199d9 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepo.scala @@ -0,0 +1,35 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import java.sql.Connection +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait DepartmentRepo { + def delete: DeleteBuilder[DepartmentFields, DepartmentRow] + def deleteById(id: DepartmentId)(implicit c: Connection): Boolean + def deleteByIds(ids: Array[DepartmentId])(implicit c: Connection): Int + def insert(unsaved: DepartmentRow)(implicit c: Connection): DepartmentRow + def insert(unsaved: DepartmentRowUnsaved)(implicit c: Connection): DepartmentRow + def insertStreaming(unsaved: Iterator[DepartmentRow], batchSize: Int = 10000)(implicit c: Connection): Long + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Iterator[DepartmentRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long + def select: SelectBuilder[DepartmentFields, DepartmentRow] + def selectAll(implicit c: Connection): List[DepartmentRow] + def selectById(id: DepartmentId)(implicit c: Connection): Option[DepartmentRow] + def selectByIds(ids: Array[DepartmentId])(implicit c: Connection): List[DepartmentRow] + def selectByIdsTracked(ids: Array[DepartmentId])(implicit c: Connection): Map[DepartmentId, DepartmentRow] + def update: UpdateBuilder[DepartmentFields, DepartmentRow] + def update(row: DepartmentRow)(implicit c: Connection): Boolean + def upsert(unsaved: DepartmentRow)(implicit c: Connection): DepartmentRow + def upsertBatch(unsaved: Iterable[DepartmentRow])(implicit c: Connection): List[DepartmentRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Iterator[DepartmentRow], batchSize: Int = 10000)(implicit c: Connection): Int +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepoImpl.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepoImpl.scala new file mode 100644 index 0000000000..f0d5a09eaf --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepoImpl.scala @@ -0,0 +1,179 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.company.CompanyId +import anorm.BatchSql +import anorm.NamedParameter +import anorm.ParameterMetaData +import anorm.ParameterValue +import anorm.RowParser +import anorm.SQL +import anorm.SimpleSql +import anorm.SqlStringInterpolation +import anorm.ToStatement +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class DepartmentRepoImpl extends DepartmentRepo { + override def delete: DeleteBuilder[DepartmentFields, DepartmentRow] = { + DeleteBuilder(""""frontpage"."department"""", DepartmentFields.structure) + } + override def deleteById(id: DepartmentId)(implicit c: Connection): Boolean = { + SQL"""delete from "frontpage"."department" where "id" = ${ParameterValue(id, null, DepartmentId.toStatement)}""".executeUpdate() > 0 + } + override def deleteByIds(ids: Array[DepartmentId])(implicit c: Connection): Int = { + SQL"""delete + from "frontpage"."department" + where "id" = ANY(${ids}) + """.executeUpdate() + + } + override def insert(unsaved: DepartmentRow)(implicit c: Connection): DepartmentRow = { + SQL"""insert into "frontpage"."department"("id", "name", "budget", "company_id") + values (${ParameterValue(unsaved.id, null, DepartmentId.toStatement)}::uuid, ${ParameterValue(unsaved.name, null, ToStatement.stringToStatement)}, ${ParameterValue(unsaved.budget, null, ToStatement.optionToStatement(ToStatement.scalaBigDecimalToStatement, ParameterMetaData.BigDecimalParameterMetaData))}::numeric, ${ParameterValue(unsaved.companyId, null, ToStatement.optionToStatement(CompanyId.toStatement, CompanyId.parameterMetadata))}::uuid) + returning "id", "name", "budget", "company_id" + """ + .executeInsert(DepartmentRow.rowParser(1).single) + + } + override def insert(unsaved: DepartmentRowUnsaved)(implicit c: Connection): DepartmentRow = { + val namedParameters = List( + Some((NamedParameter("name", ParameterValue(unsaved.name, null, ToStatement.stringToStatement)), "")), + Some((NamedParameter("budget", ParameterValue(unsaved.budget, null, ToStatement.optionToStatement(ToStatement.scalaBigDecimalToStatement, ParameterMetaData.BigDecimalParameterMetaData))), "::numeric")), + Some((NamedParameter("company_id", ParameterValue(unsaved.companyId, null, ToStatement.optionToStatement(CompanyId.toStatement, CompanyId.parameterMetadata))), "::uuid")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("id", ParameterValue(value, null, DepartmentId.toStatement)), "::uuid")) + } + ).flatten + val quote = '"'.toString + if (namedParameters.isEmpty) { + SQL"""insert into "frontpage"."department" default values + returning "id", "name", "budget", "company_id" + """ + .executeInsert(DepartmentRow.rowParser(1).single) + } else { + val q = s"""insert into "frontpage"."department"(${namedParameters.map{case (x, _) => quote + x.name + quote}.mkString(", ")}) + values (${namedParameters.map{ case (np, cast) => s"{${np.name}}$cast"}.mkString(", ")}) + returning "id", "name", "budget", "company_id" + """ + SimpleSql(SQL(q), namedParameters.map { case (np, _) => np.tupled }.toMap, RowParser.successful) + .executeInsert(DepartmentRow.rowParser(1).single) + } + + } + override def insertStreaming(unsaved: Iterator[DepartmentRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."department"("id", "name", "budget", "company_id") FROM STDIN""", batchSize, unsaved)(DepartmentRow.text, c) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[DepartmentRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."department"("name", "budget", "company_id", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(DepartmentRowUnsaved.text, c) + } + override def select: SelectBuilder[DepartmentFields, DepartmentRow] = { + SelectBuilderSql(""""frontpage"."department"""", DepartmentFields.structure, DepartmentRow.rowParser) + } + override def selectAll(implicit c: Connection): List[DepartmentRow] = { + SQL"""select "id", "name", "budget", "company_id" + from "frontpage"."department" + """.as(DepartmentRow.rowParser(1).*) + } + override def selectById(id: DepartmentId)(implicit c: Connection): Option[DepartmentRow] = { + SQL"""select "id", "name", "budget", "company_id" + from "frontpage"."department" + where "id" = ${ParameterValue(id, null, DepartmentId.toStatement)} + """.as(DepartmentRow.rowParser(1).singleOpt) + } + override def selectByIds(ids: Array[DepartmentId])(implicit c: Connection): List[DepartmentRow] = { + SQL"""select "id", "name", "budget", "company_id" + from "frontpage"."department" + where "id" = ANY(${ids}) + """.as(DepartmentRow.rowParser(1).*) + + } + override def selectByIdsTracked(ids: Array[DepartmentId])(implicit c: Connection): Map[DepartmentId, DepartmentRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[DepartmentFields, DepartmentRow] = { + UpdateBuilder(""""frontpage"."department"""", DepartmentFields.structure, DepartmentRow.rowParser) + } + override def update(row: DepartmentRow)(implicit c: Connection): Boolean = { + val id = row.id + SQL"""update "frontpage"."department" + set "name" = ${ParameterValue(row.name, null, ToStatement.stringToStatement)}, + "budget" = ${ParameterValue(row.budget, null, ToStatement.optionToStatement(ToStatement.scalaBigDecimalToStatement, ParameterMetaData.BigDecimalParameterMetaData))}::numeric, + "company_id" = ${ParameterValue(row.companyId, null, ToStatement.optionToStatement(CompanyId.toStatement, CompanyId.parameterMetadata))}::uuid + where "id" = ${ParameterValue(id, null, DepartmentId.toStatement)} + """.executeUpdate() > 0 + } + override def upsert(unsaved: DepartmentRow)(implicit c: Connection): DepartmentRow = { + SQL"""insert into "frontpage"."department"("id", "name", "budget", "company_id") + values ( + ${ParameterValue(unsaved.id, null, DepartmentId.toStatement)}::uuid, + ${ParameterValue(unsaved.name, null, ToStatement.stringToStatement)}, + ${ParameterValue(unsaved.budget, null, ToStatement.optionToStatement(ToStatement.scalaBigDecimalToStatement, ParameterMetaData.BigDecimalParameterMetaData))}::numeric, + ${ParameterValue(unsaved.companyId, null, ToStatement.optionToStatement(CompanyId.toStatement, CompanyId.parameterMetadata))}::uuid + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "budget" = EXCLUDED."budget", + "company_id" = EXCLUDED."company_id" + returning "id", "name", "budget", "company_id" + """ + .executeInsert(DepartmentRow.rowParser(1).single) + + } + override def upsertBatch(unsaved: Iterable[DepartmentRow])(implicit c: Connection): List[DepartmentRow] = { + def toNamedParameter(row: DepartmentRow): List[NamedParameter] = List( + NamedParameter("id", ParameterValue(row.id, null, DepartmentId.toStatement)), + NamedParameter("name", ParameterValue(row.name, null, ToStatement.stringToStatement)), + NamedParameter("budget", ParameterValue(row.budget, null, ToStatement.optionToStatement(ToStatement.scalaBigDecimalToStatement, ParameterMetaData.BigDecimalParameterMetaData))), + NamedParameter("company_id", ParameterValue(row.companyId, null, ToStatement.optionToStatement(CompanyId.toStatement, CompanyId.parameterMetadata))) + ) + unsaved.toList match { + case Nil => Nil + case head :: rest => + new anorm.adventureworks.ExecuteReturningSyntax.Ops( + BatchSql( + s"""insert into "frontpage"."department"("id", "name", "budget", "company_id") + values ({id}::uuid, {name}, {budget}::numeric, {company_id}::uuid) + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "budget" = EXCLUDED."budget", + "company_id" = EXCLUDED."company_id" + returning "id", "name", "budget", "company_id" + """, + toNamedParameter(head), + rest.map(toNamedParameter)* + ) + ).executeReturning(DepartmentRow.rowParser(1).*) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[DepartmentRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + SQL"""create temporary table department_TEMP (like "frontpage"."department") on commit drop""".execute(): @nowarn + streamingInsert(s"""copy department_TEMP("id", "name", "budget", "company_id") from stdin""", batchSize, unsaved)(DepartmentRow.text, c): @nowarn + SQL"""insert into "frontpage"."department"("id", "name", "budget", "company_id") + select * from department_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "budget" = EXCLUDED."budget", + "company_id" = EXCLUDED."company_id" + ; + drop table department_TEMP;""".executeUpdate() + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepoMock.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepoMock.scala new file mode 100644 index 0000000000..fd805314e9 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepoMock.scala @@ -0,0 +1,103 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class DepartmentRepoMock(toRow: Function1[DepartmentRowUnsaved, DepartmentRow], + map: scala.collection.mutable.Map[DepartmentId, DepartmentRow] = scala.collection.mutable.Map.empty) extends DepartmentRepo { + override def delete: DeleteBuilder[DepartmentFields, DepartmentRow] = { + DeleteBuilderMock(DeleteParams.empty, DepartmentFields.structure, map) + } + override def deleteById(id: DepartmentId)(implicit c: Connection): Boolean = { + map.remove(id).isDefined + } + override def deleteByIds(ids: Array[DepartmentId])(implicit c: Connection): Int = { + ids.map(id => map.remove(id)).count(_.isDefined) + } + override def insert(unsaved: DepartmentRow)(implicit c: Connection): DepartmentRow = { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + override def insert(unsaved: DepartmentRowUnsaved)(implicit c: Connection): DepartmentRow = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Iterator[DepartmentRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size.toLong + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[DepartmentRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + } + unsaved.size.toLong + } + override def select: SelectBuilder[DepartmentFields, DepartmentRow] = { + SelectBuilderMock(DepartmentFields.structure, () => map.values.toList, SelectParams.empty) + } + override def selectAll(implicit c: Connection): List[DepartmentRow] = { + map.values.toList + } + override def selectById(id: DepartmentId)(implicit c: Connection): Option[DepartmentRow] = { + map.get(id) + } + override def selectByIds(ids: Array[DepartmentId])(implicit c: Connection): List[DepartmentRow] = { + ids.flatMap(map.get).toList + } + override def selectByIdsTracked(ids: Array[DepartmentId])(implicit c: Connection): Map[DepartmentId, DepartmentRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[DepartmentFields, DepartmentRow] = { + UpdateBuilderMock(UpdateParams.empty, DepartmentFields.structure, map) + } + override def update(row: DepartmentRow)(implicit c: Connection): Boolean = { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + override def upsert(unsaved: DepartmentRow)(implicit c: Connection): DepartmentRow = { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + override def upsertBatch(unsaved: Iterable[DepartmentRow])(implicit c: Connection): List[DepartmentRow] = { + unsaved.map { row => + map += (row.id -> row) + row + }.toList + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[DepartmentRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRow.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRow.scala new file mode 100644 index 0000000000..c5c4283478 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRow.scala @@ -0,0 +1,77 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.company.CompanyId +import anorm.Column +import anorm.RowParser +import anorm.Success +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** Table: frontpage.department + Primary key: id */ +case class DepartmentRow( + /** Default: gen_random_uuid() */ + id: DepartmentId, + name: String, + budget: Option[BigDecimal], + /** Points to [[company.CompanyRow.id]] */ + companyId: Option[CompanyId] +){ + def toUnsavedRow(id: Defaulted[DepartmentId]): DepartmentRowUnsaved = + DepartmentRowUnsaved(name, budget, companyId, id) + } + +object DepartmentRow { + implicit lazy val reads: Reads[DepartmentRow] = Reads[DepartmentRow](json => JsResult.fromTry( + Try( + DepartmentRow( + id = json.\("id").as(DepartmentId.reads), + name = json.\("name").as(Reads.StringReads), + budget = json.\("budget").toOption.map(_.as(Reads.bigDecReads)), + companyId = json.\("company_id").toOption.map(_.as(CompanyId.reads)) + ) + ) + ), + ) + def rowParser(idx: Int): RowParser[DepartmentRow] = RowParser[DepartmentRow] { row => + Success( + DepartmentRow( + id = row(idx + 0)(DepartmentId.column), + name = row(idx + 1)(Column.columnToString), + budget = row(idx + 2)(Column.columnToOption(Column.columnToScalaBigDecimal)), + companyId = row(idx + 3)(Column.columnToOption(CompanyId.column)) + ) + ) + } + implicit lazy val text: Text[DepartmentRow] = Text.instance[DepartmentRow]{ (row, sb) => + DepartmentId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(Text.bigDecimalInstance).unsafeEncode(row.budget, sb) + sb.append(Text.DELIMETER) + Text.option(CompanyId.text).unsafeEncode(row.companyId, sb) + } + implicit lazy val writes: OWrites[DepartmentRow] = OWrites[DepartmentRow](o => + new JsObject(ListMap[String, JsValue]( + "id" -> DepartmentId.writes.writes(o.id), + "name" -> Writes.StringWrites.writes(o.name), + "budget" -> Writes.OptionWrites(Writes.BigDecimalWrites).writes(o.budget), + "company_id" -> Writes.OptionWrites(CompanyId.writes).writes(o.companyId) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRowUnsaved.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRowUnsaved.scala new file mode 100644 index 0000000000..11e7ab7088 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRowUnsaved.scala @@ -0,0 +1,70 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.company.CompanyId +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** This class corresponds to a row in table `frontpage.department` which has not been persisted yet */ +case class DepartmentRowUnsaved( + name: String, + budget: Option[BigDecimal], + /** Points to [[company.CompanyRow.id]] */ + companyId: Option[CompanyId], + /** Default: gen_random_uuid() */ + id: Defaulted[DepartmentId] = Defaulted.UseDefault +) { + def toRow(idDefault: => DepartmentId): DepartmentRow = + DepartmentRow( + name = name, + budget = budget, + companyId = companyId, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object DepartmentRowUnsaved { + implicit lazy val reads: Reads[DepartmentRowUnsaved] = Reads[DepartmentRowUnsaved](json => JsResult.fromTry( + Try( + DepartmentRowUnsaved( + name = json.\("name").as(Reads.StringReads), + budget = json.\("budget").toOption.map(_.as(Reads.bigDecReads)), + companyId = json.\("company_id").toOption.map(_.as(CompanyId.reads)), + id = json.\("id").as(Defaulted.reads(DepartmentId.reads)) + ) + ) + ), + ) + implicit lazy val text: Text[DepartmentRowUnsaved] = Text.instance[DepartmentRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(Text.bigDecimalInstance).unsafeEncode(row.budget, sb) + sb.append(Text.DELIMETER) + Text.option(CompanyId.text).unsafeEncode(row.companyId, sb) + sb.append(Text.DELIMETER) + Defaulted.text(DepartmentId.text).unsafeEncode(row.id, sb) + } + implicit lazy val writes: OWrites[DepartmentRowUnsaved] = OWrites[DepartmentRowUnsaved](o => + new JsObject(ListMap[String, JsValue]( + "name" -> Writes.StringWrites.writes(o.name), + "budget" -> Writes.OptionWrites(Writes.BigDecimalWrites).writes(o.budget), + "company_id" -> Writes.OptionWrites(CompanyId.writes).writes(o.companyId), + "id" -> Defaulted.writes(DepartmentId.writes).writes(o.id) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeFields.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeFields.scala new file mode 100644 index 0000000000..8ac2329269 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeFields.scala @@ -0,0 +1,50 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.frontpage.person.PersonFields +import adventureworks.frontpage.person.PersonId +import adventureworks.frontpage.person.PersonRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait EmployeeFields { + def id: IdField[EmployeeId, EmployeeRow] + def personId: Field[PersonId, EmployeeRow] + def salary: OptField[BigDecimal, EmployeeRow] + def fkPerson: ForeignKey[PersonFields, PersonRow] = + ForeignKey[PersonFields, PersonRow]("frontpage.fk_person", Nil) + .withColumnPair(personId, _.id) +} + +object EmployeeFields { + lazy val structure: Relation[EmployeeFields, EmployeeRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[EmployeeFields, EmployeeRow] { + + override lazy val fields: EmployeeFields = new EmployeeFields { + override def id = IdField[EmployeeId, EmployeeRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def personId = Field[PersonId, EmployeeRow](_path, "person_id", None, Some("uuid"), x => x.personId, (row, value) => row.copy(personId = value)) + override def salary = OptField[BigDecimal, EmployeeRow](_path, "salary", None, Some("numeric"), x => x.salary, (row, value) => row.copy(salary = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, EmployeeRow]] = + List[FieldLikeNoHkt[?, EmployeeRow]](fields.id, fields.personId, fields.salary) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeId.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeId.scala new file mode 100644 index 0000000000..3ed83b55e2 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.customtypes.TypoUUID +import anorm.Column +import anorm.ParameterMetaData +import anorm.ToStatement +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.employee` */ +case class EmployeeId(value: TypoUUID) extends AnyVal +object EmployeeId { + implicit lazy val arrayColumn: Column[Array[EmployeeId]] = Column.columnToArray(column, implicitly) + implicit lazy val arrayToStatement: ToStatement[Array[EmployeeId]] = TypoUUID.arrayToStatement.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[EmployeeId, TypoUUID] = Bijection[EmployeeId, TypoUUID](_.value)(EmployeeId.apply) + implicit lazy val column: Column[EmployeeId] = TypoUUID.column.map(EmployeeId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[EmployeeId] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[EmployeeId] = new ParameterMetaData[EmployeeId] { + override def sqlType: String = TypoUUID.parameterMetadata.sqlType + override def jdbcType: Int = TypoUUID.parameterMetadata.jdbcType + } + implicit lazy val reads: Reads[EmployeeId] = TypoUUID.reads.map(EmployeeId.apply) + implicit lazy val text: Text[EmployeeId] = new Text[EmployeeId] { + override def unsafeEncode(v: EmployeeId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: EmployeeId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } + implicit lazy val toStatement: ToStatement[EmployeeId] = TypoUUID.toStatement.contramap(_.value) + implicit lazy val writes: Writes[EmployeeId] = TypoUUID.writes.contramap(_.value) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepo.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepo.scala new file mode 100644 index 0000000000..0244d072f7 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepo.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.frontpage.person.PersonId +import java.sql.Connection +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait EmployeeRepo { + def delete: DeleteBuilder[EmployeeFields, EmployeeRow] + def deleteById(id: EmployeeId)(implicit c: Connection): Boolean + def deleteByIds(ids: Array[EmployeeId])(implicit c: Connection): Int + def insert(unsaved: EmployeeRow)(implicit c: Connection): EmployeeRow + def insert(unsaved: EmployeeRowUnsaved)(implicit c: Connection): EmployeeRow + def insertStreaming(unsaved: Iterator[EmployeeRow], batchSize: Int = 10000)(implicit c: Connection): Long + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Iterator[EmployeeRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long + def select: SelectBuilder[EmployeeFields, EmployeeRow] + def selectAll(implicit c: Connection): List[EmployeeRow] + def selectById(id: EmployeeId)(implicit c: Connection): Option[EmployeeRow] + def selectByIds(ids: Array[EmployeeId])(implicit c: Connection): List[EmployeeRow] + def selectByIdsTracked(ids: Array[EmployeeId])(implicit c: Connection): Map[EmployeeId, EmployeeRow] + def selectByUniquePersonId(personId: PersonId)(implicit c: Connection): Option[EmployeeRow] + def update: UpdateBuilder[EmployeeFields, EmployeeRow] + def update(row: EmployeeRow)(implicit c: Connection): Boolean + def upsert(unsaved: EmployeeRow)(implicit c: Connection): EmployeeRow + def upsertBatch(unsaved: Iterable[EmployeeRow])(implicit c: Connection): List[EmployeeRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Iterator[EmployeeRow], batchSize: Int = 10000)(implicit c: Connection): Int +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepoImpl.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepoImpl.scala new file mode 100644 index 0000000000..a126649344 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepoImpl.scala @@ -0,0 +1,179 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.person.PersonId +import anorm.BatchSql +import anorm.NamedParameter +import anorm.ParameterMetaData +import anorm.ParameterValue +import anorm.RowParser +import anorm.SQL +import anorm.SimpleSql +import anorm.SqlStringInterpolation +import anorm.ToStatement +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class EmployeeRepoImpl extends EmployeeRepo { + override def delete: DeleteBuilder[EmployeeFields, EmployeeRow] = { + DeleteBuilder(""""frontpage"."employee"""", EmployeeFields.structure) + } + override def deleteById(id: EmployeeId)(implicit c: Connection): Boolean = { + SQL"""delete from "frontpage"."employee" where "id" = ${ParameterValue(id, null, EmployeeId.toStatement)}""".executeUpdate() > 0 + } + override def deleteByIds(ids: Array[EmployeeId])(implicit c: Connection): Int = { + SQL"""delete + from "frontpage"."employee" + where "id" = ANY(${ids}) + """.executeUpdate() + + } + override def insert(unsaved: EmployeeRow)(implicit c: Connection): EmployeeRow = { + SQL"""insert into "frontpage"."employee"("id", "person_id", "salary") + values (${ParameterValue(unsaved.id, null, EmployeeId.toStatement)}::uuid, ${ParameterValue(unsaved.personId, null, PersonId.toStatement)}::uuid, ${ParameterValue(unsaved.salary, null, ToStatement.optionToStatement(ToStatement.scalaBigDecimalToStatement, ParameterMetaData.BigDecimalParameterMetaData))}::numeric) + returning "id", "person_id", "salary" + """ + .executeInsert(EmployeeRow.rowParser(1).single) + + } + override def insert(unsaved: EmployeeRowUnsaved)(implicit c: Connection): EmployeeRow = { + val namedParameters = List( + Some((NamedParameter("person_id", ParameterValue(unsaved.personId, null, PersonId.toStatement)), "::uuid")), + Some((NamedParameter("salary", ParameterValue(unsaved.salary, null, ToStatement.optionToStatement(ToStatement.scalaBigDecimalToStatement, ParameterMetaData.BigDecimalParameterMetaData))), "::numeric")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("id", ParameterValue(value, null, EmployeeId.toStatement)), "::uuid")) + } + ).flatten + val quote = '"'.toString + if (namedParameters.isEmpty) { + SQL"""insert into "frontpage"."employee" default values + returning "id", "person_id", "salary" + """ + .executeInsert(EmployeeRow.rowParser(1).single) + } else { + val q = s"""insert into "frontpage"."employee"(${namedParameters.map{case (x, _) => quote + x.name + quote}.mkString(", ")}) + values (${namedParameters.map{ case (np, cast) => s"{${np.name}}$cast"}.mkString(", ")}) + returning "id", "person_id", "salary" + """ + SimpleSql(SQL(q), namedParameters.map { case (np, _) => np.tupled }.toMap, RowParser.successful) + .executeInsert(EmployeeRow.rowParser(1).single) + } + + } + override def insertStreaming(unsaved: Iterator[EmployeeRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."employee"("id", "person_id", "salary") FROM STDIN""", batchSize, unsaved)(EmployeeRow.text, c) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[EmployeeRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."employee"("person_id", "salary", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(EmployeeRowUnsaved.text, c) + } + override def select: SelectBuilder[EmployeeFields, EmployeeRow] = { + SelectBuilderSql(""""frontpage"."employee"""", EmployeeFields.structure, EmployeeRow.rowParser) + } + override def selectAll(implicit c: Connection): List[EmployeeRow] = { + SQL"""select "id", "person_id", "salary" + from "frontpage"."employee" + """.as(EmployeeRow.rowParser(1).*) + } + override def selectById(id: EmployeeId)(implicit c: Connection): Option[EmployeeRow] = { + SQL"""select "id", "person_id", "salary" + from "frontpage"."employee" + where "id" = ${ParameterValue(id, null, EmployeeId.toStatement)} + """.as(EmployeeRow.rowParser(1).singleOpt) + } + override def selectByIds(ids: Array[EmployeeId])(implicit c: Connection): List[EmployeeRow] = { + SQL"""select "id", "person_id", "salary" + from "frontpage"."employee" + where "id" = ANY(${ids}) + """.as(EmployeeRow.rowParser(1).*) + + } + override def selectByIdsTracked(ids: Array[EmployeeId])(implicit c: Connection): Map[EmployeeId, EmployeeRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def selectByUniquePersonId(personId: PersonId)(implicit c: Connection): Option[EmployeeRow] = { + SQL"""select "id", "person_id", "salary" + from "frontpage"."employee" + where "person_id" = ${ParameterValue(personId, null, PersonId.toStatement)} + """.as(EmployeeRow.rowParser(1).singleOpt) + + } + override def update: UpdateBuilder[EmployeeFields, EmployeeRow] = { + UpdateBuilder(""""frontpage"."employee"""", EmployeeFields.structure, EmployeeRow.rowParser) + } + override def update(row: EmployeeRow)(implicit c: Connection): Boolean = { + val id = row.id + SQL"""update "frontpage"."employee" + set "person_id" = ${ParameterValue(row.personId, null, PersonId.toStatement)}::uuid, + "salary" = ${ParameterValue(row.salary, null, ToStatement.optionToStatement(ToStatement.scalaBigDecimalToStatement, ParameterMetaData.BigDecimalParameterMetaData))}::numeric + where "id" = ${ParameterValue(id, null, EmployeeId.toStatement)} + """.executeUpdate() > 0 + } + override def upsert(unsaved: EmployeeRow)(implicit c: Connection): EmployeeRow = { + SQL"""insert into "frontpage"."employee"("id", "person_id", "salary") + values ( + ${ParameterValue(unsaved.id, null, EmployeeId.toStatement)}::uuid, + ${ParameterValue(unsaved.personId, null, PersonId.toStatement)}::uuid, + ${ParameterValue(unsaved.salary, null, ToStatement.optionToStatement(ToStatement.scalaBigDecimalToStatement, ParameterMetaData.BigDecimalParameterMetaData))}::numeric + ) + on conflict ("id") + do update set + "person_id" = EXCLUDED."person_id", + "salary" = EXCLUDED."salary" + returning "id", "person_id", "salary" + """ + .executeInsert(EmployeeRow.rowParser(1).single) + + } + override def upsertBatch(unsaved: Iterable[EmployeeRow])(implicit c: Connection): List[EmployeeRow] = { + def toNamedParameter(row: EmployeeRow): List[NamedParameter] = List( + NamedParameter("id", ParameterValue(row.id, null, EmployeeId.toStatement)), + NamedParameter("person_id", ParameterValue(row.personId, null, PersonId.toStatement)), + NamedParameter("salary", ParameterValue(row.salary, null, ToStatement.optionToStatement(ToStatement.scalaBigDecimalToStatement, ParameterMetaData.BigDecimalParameterMetaData))) + ) + unsaved.toList match { + case Nil => Nil + case head :: rest => + new anorm.adventureworks.ExecuteReturningSyntax.Ops( + BatchSql( + s"""insert into "frontpage"."employee"("id", "person_id", "salary") + values ({id}::uuid, {person_id}::uuid, {salary}::numeric) + on conflict ("id") + do update set + "person_id" = EXCLUDED."person_id", + "salary" = EXCLUDED."salary" + returning "id", "person_id", "salary" + """, + toNamedParameter(head), + rest.map(toNamedParameter)* + ) + ).executeReturning(EmployeeRow.rowParser(1).*) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[EmployeeRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + SQL"""create temporary table employee_TEMP (like "frontpage"."employee") on commit drop""".execute(): @nowarn + streamingInsert(s"""copy employee_TEMP("id", "person_id", "salary") from stdin""", batchSize, unsaved)(EmployeeRow.text, c): @nowarn + SQL"""insert into "frontpage"."employee"("id", "person_id", "salary") + select * from employee_TEMP + on conflict ("id") + do update set + "person_id" = EXCLUDED."person_id", + "salary" = EXCLUDED."salary" + ; + drop table employee_TEMP;""".executeUpdate() + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepoMock.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepoMock.scala new file mode 100644 index 0000000000..042738bfe7 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepoMock.scala @@ -0,0 +1,107 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.frontpage.person.PersonId +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class EmployeeRepoMock(toRow: Function1[EmployeeRowUnsaved, EmployeeRow], + map: scala.collection.mutable.Map[EmployeeId, EmployeeRow] = scala.collection.mutable.Map.empty) extends EmployeeRepo { + override def delete: DeleteBuilder[EmployeeFields, EmployeeRow] = { + DeleteBuilderMock(DeleteParams.empty, EmployeeFields.structure, map) + } + override def deleteById(id: EmployeeId)(implicit c: Connection): Boolean = { + map.remove(id).isDefined + } + override def deleteByIds(ids: Array[EmployeeId])(implicit c: Connection): Int = { + ids.map(id => map.remove(id)).count(_.isDefined) + } + override def insert(unsaved: EmployeeRow)(implicit c: Connection): EmployeeRow = { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + override def insert(unsaved: EmployeeRowUnsaved)(implicit c: Connection): EmployeeRow = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Iterator[EmployeeRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size.toLong + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[EmployeeRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + } + unsaved.size.toLong + } + override def select: SelectBuilder[EmployeeFields, EmployeeRow] = { + SelectBuilderMock(EmployeeFields.structure, () => map.values.toList, SelectParams.empty) + } + override def selectAll(implicit c: Connection): List[EmployeeRow] = { + map.values.toList + } + override def selectById(id: EmployeeId)(implicit c: Connection): Option[EmployeeRow] = { + map.get(id) + } + override def selectByIds(ids: Array[EmployeeId])(implicit c: Connection): List[EmployeeRow] = { + ids.flatMap(map.get).toList + } + override def selectByIdsTracked(ids: Array[EmployeeId])(implicit c: Connection): Map[EmployeeId, EmployeeRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def selectByUniquePersonId(personId: PersonId)(implicit c: Connection): Option[EmployeeRow] = { + map.values.find(v => personId == v.personId) + } + override def update: UpdateBuilder[EmployeeFields, EmployeeRow] = { + UpdateBuilderMock(UpdateParams.empty, EmployeeFields.structure, map) + } + override def update(row: EmployeeRow)(implicit c: Connection): Boolean = { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + override def upsert(unsaved: EmployeeRow)(implicit c: Connection): EmployeeRow = { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + override def upsertBatch(unsaved: Iterable[EmployeeRow])(implicit c: Connection): List[EmployeeRow] = { + unsaved.map { row => + map += (row.id -> row) + row + }.toList + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[EmployeeRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRow.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRow.scala new file mode 100644 index 0000000000..21420a28fa --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRow.scala @@ -0,0 +1,71 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.person.PersonId +import anorm.Column +import anorm.RowParser +import anorm.Success +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** Table: frontpage.employee + Primary key: id */ +case class EmployeeRow( + /** Default: gen_random_uuid() */ + id: EmployeeId, + /** Points to [[person.PersonRow.id]] */ + personId: PersonId, + salary: Option[BigDecimal] +){ + def toUnsavedRow(id: Defaulted[EmployeeId]): EmployeeRowUnsaved = + EmployeeRowUnsaved(personId, salary, id) + } + +object EmployeeRow { + implicit lazy val reads: Reads[EmployeeRow] = Reads[EmployeeRow](json => JsResult.fromTry( + Try( + EmployeeRow( + id = json.\("id").as(EmployeeId.reads), + personId = json.\("person_id").as(PersonId.reads), + salary = json.\("salary").toOption.map(_.as(Reads.bigDecReads)) + ) + ) + ), + ) + def rowParser(idx: Int): RowParser[EmployeeRow] = RowParser[EmployeeRow] { row => + Success( + EmployeeRow( + id = row(idx + 0)(EmployeeId.column), + personId = row(idx + 1)(PersonId.column), + salary = row(idx + 2)(Column.columnToOption(Column.columnToScalaBigDecimal)) + ) + ) + } + implicit lazy val text: Text[EmployeeRow] = Text.instance[EmployeeRow]{ (row, sb) => + EmployeeId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + PersonId.text.unsafeEncode(row.personId, sb) + sb.append(Text.DELIMETER) + Text.option(Text.bigDecimalInstance).unsafeEncode(row.salary, sb) + } + implicit lazy val writes: OWrites[EmployeeRow] = OWrites[EmployeeRow](o => + new JsObject(ListMap[String, JsValue]( + "id" -> EmployeeId.writes.writes(o.id), + "person_id" -> PersonId.writes.writes(o.personId), + "salary" -> Writes.OptionWrites(Writes.BigDecimalWrites).writes(o.salary) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRowUnsaved.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRowUnsaved.scala new file mode 100644 index 0000000000..505cba8ff0 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRowUnsaved.scala @@ -0,0 +1,64 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.person.PersonId +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** This class corresponds to a row in table `frontpage.employee` which has not been persisted yet */ +case class EmployeeRowUnsaved( + /** Points to [[person.PersonRow.id]] */ + personId: PersonId, + salary: Option[BigDecimal], + /** Default: gen_random_uuid() */ + id: Defaulted[EmployeeId] = Defaulted.UseDefault +) { + def toRow(idDefault: => EmployeeId): EmployeeRow = + EmployeeRow( + personId = personId, + salary = salary, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object EmployeeRowUnsaved { + implicit lazy val reads: Reads[EmployeeRowUnsaved] = Reads[EmployeeRowUnsaved](json => JsResult.fromTry( + Try( + EmployeeRowUnsaved( + personId = json.\("person_id").as(PersonId.reads), + salary = json.\("salary").toOption.map(_.as(Reads.bigDecReads)), + id = json.\("id").as(Defaulted.reads(EmployeeId.reads)) + ) + ) + ), + ) + implicit lazy val text: Text[EmployeeRowUnsaved] = Text.instance[EmployeeRowUnsaved]{ (row, sb) => + PersonId.text.unsafeEncode(row.personId, sb) + sb.append(Text.DELIMETER) + Text.option(Text.bigDecimalInstance).unsafeEncode(row.salary, sb) + sb.append(Text.DELIMETER) + Defaulted.text(EmployeeId.text).unsafeEncode(row.id, sb) + } + implicit lazy val writes: OWrites[EmployeeRowUnsaved] = OWrites[EmployeeRowUnsaved](o => + new JsObject(ListMap[String, JsValue]( + "person_id" -> PersonId.writes.writes(o.personId), + "salary" -> Writes.OptionWrites(Writes.BigDecimalWrites).writes(o.salary), + "id" -> Defaulted.writes(EmployeeId.writes).writes(o.id) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationFields.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationFields.scala new file mode 100644 index 0000000000..c4fb82d377 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationFields.scala @@ -0,0 +1,53 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import adventureworks.customtypes.TypoInet +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoPoint +import adventureworks.customtypes.TypoPolygon +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait LocationFields { + def id: IdField[LocationId, LocationRow] + def name: Field[String, LocationRow] + def position: OptField[TypoPoint, LocationRow] + def area: OptField[TypoPolygon, LocationRow] + def ipRange: OptField[TypoInet, LocationRow] + def metadata: OptField[TypoJsonb, LocationRow] +} + +object LocationFields { + lazy val structure: Relation[LocationFields, LocationRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[LocationFields, LocationRow] { + + override lazy val fields: LocationFields = new LocationFields { + override def id = IdField[LocationId, LocationRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, LocationRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + override def position = OptField[TypoPoint, LocationRow](_path, "position", None, Some("point"), x => x.position, (row, value) => row.copy(position = value)) + override def area = OptField[TypoPolygon, LocationRow](_path, "area", None, Some("polygon"), x => x.area, (row, value) => row.copy(area = value)) + override def ipRange = OptField[TypoInet, LocationRow](_path, "ip_range", None, Some("inet"), x => x.ipRange, (row, value) => row.copy(ipRange = value)) + override def metadata = OptField[TypoJsonb, LocationRow](_path, "metadata", None, Some("jsonb"), x => x.metadata, (row, value) => row.copy(metadata = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, LocationRow]] = + List[FieldLikeNoHkt[?, LocationRow]](fields.id, fields.name, fields.position, fields.area, fields.ipRange, fields.metadata) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationId.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationId.scala new file mode 100644 index 0000000000..ec2c8d2413 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import adventureworks.customtypes.TypoUUID +import anorm.Column +import anorm.ParameterMetaData +import anorm.ToStatement +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.location` */ +case class LocationId(value: TypoUUID) extends AnyVal +object LocationId { + implicit lazy val arrayColumn: Column[Array[LocationId]] = Column.columnToArray(column, implicitly) + implicit lazy val arrayToStatement: ToStatement[Array[LocationId]] = TypoUUID.arrayToStatement.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[LocationId, TypoUUID] = Bijection[LocationId, TypoUUID](_.value)(LocationId.apply) + implicit lazy val column: Column[LocationId] = TypoUUID.column.map(LocationId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[LocationId] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[LocationId] = new ParameterMetaData[LocationId] { + override def sqlType: String = TypoUUID.parameterMetadata.sqlType + override def jdbcType: Int = TypoUUID.parameterMetadata.jdbcType + } + implicit lazy val reads: Reads[LocationId] = TypoUUID.reads.map(LocationId.apply) + implicit lazy val text: Text[LocationId] = new Text[LocationId] { + override def unsafeEncode(v: LocationId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: LocationId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } + implicit lazy val toStatement: ToStatement[LocationId] = TypoUUID.toStatement.contramap(_.value) + implicit lazy val writes: Writes[LocationId] = TypoUUID.writes.contramap(_.value) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationRepo.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationRepo.scala new file mode 100644 index 0000000000..c17a297b63 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationRepo.scala @@ -0,0 +1,35 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import java.sql.Connection +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait LocationRepo { + def delete: DeleteBuilder[LocationFields, LocationRow] + def deleteById(id: LocationId)(implicit c: Connection): Boolean + def deleteByIds(ids: Array[LocationId])(implicit c: Connection): Int + def insert(unsaved: LocationRow)(implicit c: Connection): LocationRow + def insert(unsaved: LocationRowUnsaved)(implicit c: Connection): LocationRow + def insertStreaming(unsaved: Iterator[LocationRow], batchSize: Int = 10000)(implicit c: Connection): Long + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Iterator[LocationRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long + def select: SelectBuilder[LocationFields, LocationRow] + def selectAll(implicit c: Connection): List[LocationRow] + def selectById(id: LocationId)(implicit c: Connection): Option[LocationRow] + def selectByIds(ids: Array[LocationId])(implicit c: Connection): List[LocationRow] + def selectByIdsTracked(ids: Array[LocationId])(implicit c: Connection): Map[LocationId, LocationRow] + def update: UpdateBuilder[LocationFields, LocationRow] + def update(row: LocationRow)(implicit c: Connection): Boolean + def upsert(unsaved: LocationRow)(implicit c: Connection): LocationRow + def upsertBatch(unsaved: Iterable[LocationRow])(implicit c: Connection): List[LocationRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Iterator[LocationRow], batchSize: Int = 10000)(implicit c: Connection): Int +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationRepoImpl.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationRepoImpl.scala new file mode 100644 index 0000000000..0e43dfffcc --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationRepoImpl.scala @@ -0,0 +1,198 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoInet +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoPoint +import adventureworks.customtypes.TypoPolygon +import anorm.BatchSql +import anorm.NamedParameter +import anorm.ParameterValue +import anorm.RowParser +import anorm.SQL +import anorm.SimpleSql +import anorm.SqlStringInterpolation +import anorm.ToStatement +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class LocationRepoImpl extends LocationRepo { + override def delete: DeleteBuilder[LocationFields, LocationRow] = { + DeleteBuilder(""""frontpage"."location"""", LocationFields.structure) + } + override def deleteById(id: LocationId)(implicit c: Connection): Boolean = { + SQL"""delete from "frontpage"."location" where "id" = ${ParameterValue(id, null, LocationId.toStatement)}""".executeUpdate() > 0 + } + override def deleteByIds(ids: Array[LocationId])(implicit c: Connection): Int = { + SQL"""delete + from "frontpage"."location" + where "id" = ANY(${ids}) + """.executeUpdate() + + } + override def insert(unsaved: LocationRow)(implicit c: Connection): LocationRow = { + SQL"""insert into "frontpage"."location"("id", "name", "position", "area", "ip_range", "metadata") + values (${ParameterValue(unsaved.id, null, LocationId.toStatement)}::uuid, ${ParameterValue(unsaved.name, null, ToStatement.stringToStatement)}, ${ParameterValue(unsaved.position, null, ToStatement.optionToStatement(TypoPoint.toStatement, TypoPoint.parameterMetadata))}::point, ${ParameterValue(unsaved.area, null, ToStatement.optionToStatement(TypoPolygon.toStatement, TypoPolygon.parameterMetadata))}::polygon, ${ParameterValue(unsaved.ipRange, null, ToStatement.optionToStatement(TypoInet.toStatement, TypoInet.parameterMetadata))}::inet, ${ParameterValue(unsaved.metadata, null, ToStatement.optionToStatement(TypoJsonb.toStatement, TypoJsonb.parameterMetadata))}::jsonb) + returning "id", "name", "position", "area", "ip_range", "metadata" + """ + .executeInsert(LocationRow.rowParser(1).single) + + } + override def insert(unsaved: LocationRowUnsaved)(implicit c: Connection): LocationRow = { + val namedParameters = List( + Some((NamedParameter("name", ParameterValue(unsaved.name, null, ToStatement.stringToStatement)), "")), + Some((NamedParameter("position", ParameterValue(unsaved.position, null, ToStatement.optionToStatement(TypoPoint.toStatement, TypoPoint.parameterMetadata))), "::point")), + Some((NamedParameter("area", ParameterValue(unsaved.area, null, ToStatement.optionToStatement(TypoPolygon.toStatement, TypoPolygon.parameterMetadata))), "::polygon")), + Some((NamedParameter("ip_range", ParameterValue(unsaved.ipRange, null, ToStatement.optionToStatement(TypoInet.toStatement, TypoInet.parameterMetadata))), "::inet")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("id", ParameterValue(value, null, LocationId.toStatement)), "::uuid")) + }, + unsaved.metadata match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("metadata", ParameterValue(value, null, ToStatement.optionToStatement(TypoJsonb.toStatement, TypoJsonb.parameterMetadata))), "::jsonb")) + } + ).flatten + val quote = '"'.toString + if (namedParameters.isEmpty) { + SQL"""insert into "frontpage"."location" default values + returning "id", "name", "position", "area", "ip_range", "metadata" + """ + .executeInsert(LocationRow.rowParser(1).single) + } else { + val q = s"""insert into "frontpage"."location"(${namedParameters.map{case (x, _) => quote + x.name + quote}.mkString(", ")}) + values (${namedParameters.map{ case (np, cast) => s"{${np.name}}$cast"}.mkString(", ")}) + returning "id", "name", "position", "area", "ip_range", "metadata" + """ + SimpleSql(SQL(q), namedParameters.map { case (np, _) => np.tupled }.toMap, RowParser.successful) + .executeInsert(LocationRow.rowParser(1).single) + } + + } + override def insertStreaming(unsaved: Iterator[LocationRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."location"("id", "name", "position", "area", "ip_range", "metadata") FROM STDIN""", batchSize, unsaved)(LocationRow.text, c) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[LocationRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."location"("name", "position", "area", "ip_range", "id", "metadata") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(LocationRowUnsaved.text, c) + } + override def select: SelectBuilder[LocationFields, LocationRow] = { + SelectBuilderSql(""""frontpage"."location"""", LocationFields.structure, LocationRow.rowParser) + } + override def selectAll(implicit c: Connection): List[LocationRow] = { + SQL"""select "id", "name", "position", "area", "ip_range", "metadata" + from "frontpage"."location" + """.as(LocationRow.rowParser(1).*) + } + override def selectById(id: LocationId)(implicit c: Connection): Option[LocationRow] = { + SQL"""select "id", "name", "position", "area", "ip_range", "metadata" + from "frontpage"."location" + where "id" = ${ParameterValue(id, null, LocationId.toStatement)} + """.as(LocationRow.rowParser(1).singleOpt) + } + override def selectByIds(ids: Array[LocationId])(implicit c: Connection): List[LocationRow] = { + SQL"""select "id", "name", "position", "area", "ip_range", "metadata" + from "frontpage"."location" + where "id" = ANY(${ids}) + """.as(LocationRow.rowParser(1).*) + + } + override def selectByIdsTracked(ids: Array[LocationId])(implicit c: Connection): Map[LocationId, LocationRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[LocationFields, LocationRow] = { + UpdateBuilder(""""frontpage"."location"""", LocationFields.structure, LocationRow.rowParser) + } + override def update(row: LocationRow)(implicit c: Connection): Boolean = { + val id = row.id + SQL"""update "frontpage"."location" + set "name" = ${ParameterValue(row.name, null, ToStatement.stringToStatement)}, + "position" = ${ParameterValue(row.position, null, ToStatement.optionToStatement(TypoPoint.toStatement, TypoPoint.parameterMetadata))}::point, + "area" = ${ParameterValue(row.area, null, ToStatement.optionToStatement(TypoPolygon.toStatement, TypoPolygon.parameterMetadata))}::polygon, + "ip_range" = ${ParameterValue(row.ipRange, null, ToStatement.optionToStatement(TypoInet.toStatement, TypoInet.parameterMetadata))}::inet, + "metadata" = ${ParameterValue(row.metadata, null, ToStatement.optionToStatement(TypoJsonb.toStatement, TypoJsonb.parameterMetadata))}::jsonb + where "id" = ${ParameterValue(id, null, LocationId.toStatement)} + """.executeUpdate() > 0 + } + override def upsert(unsaved: LocationRow)(implicit c: Connection): LocationRow = { + SQL"""insert into "frontpage"."location"("id", "name", "position", "area", "ip_range", "metadata") + values ( + ${ParameterValue(unsaved.id, null, LocationId.toStatement)}::uuid, + ${ParameterValue(unsaved.name, null, ToStatement.stringToStatement)}, + ${ParameterValue(unsaved.position, null, ToStatement.optionToStatement(TypoPoint.toStatement, TypoPoint.parameterMetadata))}::point, + ${ParameterValue(unsaved.area, null, ToStatement.optionToStatement(TypoPolygon.toStatement, TypoPolygon.parameterMetadata))}::polygon, + ${ParameterValue(unsaved.ipRange, null, ToStatement.optionToStatement(TypoInet.toStatement, TypoInet.parameterMetadata))}::inet, + ${ParameterValue(unsaved.metadata, null, ToStatement.optionToStatement(TypoJsonb.toStatement, TypoJsonb.parameterMetadata))}::jsonb + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "position" = EXCLUDED."position", + "area" = EXCLUDED."area", + "ip_range" = EXCLUDED."ip_range", + "metadata" = EXCLUDED."metadata" + returning "id", "name", "position", "area", "ip_range", "metadata" + """ + .executeInsert(LocationRow.rowParser(1).single) + + } + override def upsertBatch(unsaved: Iterable[LocationRow])(implicit c: Connection): List[LocationRow] = { + def toNamedParameter(row: LocationRow): List[NamedParameter] = List( + NamedParameter("id", ParameterValue(row.id, null, LocationId.toStatement)), + NamedParameter("name", ParameterValue(row.name, null, ToStatement.stringToStatement)), + NamedParameter("position", ParameterValue(row.position, null, ToStatement.optionToStatement(TypoPoint.toStatement, TypoPoint.parameterMetadata))), + NamedParameter("area", ParameterValue(row.area, null, ToStatement.optionToStatement(TypoPolygon.toStatement, TypoPolygon.parameterMetadata))), + NamedParameter("ip_range", ParameterValue(row.ipRange, null, ToStatement.optionToStatement(TypoInet.toStatement, TypoInet.parameterMetadata))), + NamedParameter("metadata", ParameterValue(row.metadata, null, ToStatement.optionToStatement(TypoJsonb.toStatement, TypoJsonb.parameterMetadata))) + ) + unsaved.toList match { + case Nil => Nil + case head :: rest => + new anorm.adventureworks.ExecuteReturningSyntax.Ops( + BatchSql( + s"""insert into "frontpage"."location"("id", "name", "position", "area", "ip_range", "metadata") + values ({id}::uuid, {name}, {position}::point, {area}::polygon, {ip_range}::inet, {metadata}::jsonb) + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "position" = EXCLUDED."position", + "area" = EXCLUDED."area", + "ip_range" = EXCLUDED."ip_range", + "metadata" = EXCLUDED."metadata" + returning "id", "name", "position", "area", "ip_range", "metadata" + """, + toNamedParameter(head), + rest.map(toNamedParameter)* + ) + ).executeReturning(LocationRow.rowParser(1).*) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[LocationRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + SQL"""create temporary table location_TEMP (like "frontpage"."location") on commit drop""".execute(): @nowarn + streamingInsert(s"""copy location_TEMP("id", "name", "position", "area", "ip_range", "metadata") from stdin""", batchSize, unsaved)(LocationRow.text, c): @nowarn + SQL"""insert into "frontpage"."location"("id", "name", "position", "area", "ip_range", "metadata") + select * from location_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "position" = EXCLUDED."position", + "area" = EXCLUDED."area", + "ip_range" = EXCLUDED."ip_range", + "metadata" = EXCLUDED."metadata" + ; + drop table location_TEMP;""".executeUpdate() + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationRepoMock.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationRepoMock.scala new file mode 100644 index 0000000000..0f7ecd65d0 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationRepoMock.scala @@ -0,0 +1,103 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class LocationRepoMock(toRow: Function1[LocationRowUnsaved, LocationRow], + map: scala.collection.mutable.Map[LocationId, LocationRow] = scala.collection.mutable.Map.empty) extends LocationRepo { + override def delete: DeleteBuilder[LocationFields, LocationRow] = { + DeleteBuilderMock(DeleteParams.empty, LocationFields.structure, map) + } + override def deleteById(id: LocationId)(implicit c: Connection): Boolean = { + map.remove(id).isDefined + } + override def deleteByIds(ids: Array[LocationId])(implicit c: Connection): Int = { + ids.map(id => map.remove(id)).count(_.isDefined) + } + override def insert(unsaved: LocationRow)(implicit c: Connection): LocationRow = { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + override def insert(unsaved: LocationRowUnsaved)(implicit c: Connection): LocationRow = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Iterator[LocationRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size.toLong + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[LocationRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + } + unsaved.size.toLong + } + override def select: SelectBuilder[LocationFields, LocationRow] = { + SelectBuilderMock(LocationFields.structure, () => map.values.toList, SelectParams.empty) + } + override def selectAll(implicit c: Connection): List[LocationRow] = { + map.values.toList + } + override def selectById(id: LocationId)(implicit c: Connection): Option[LocationRow] = { + map.get(id) + } + override def selectByIds(ids: Array[LocationId])(implicit c: Connection): List[LocationRow] = { + ids.flatMap(map.get).toList + } + override def selectByIdsTracked(ids: Array[LocationId])(implicit c: Connection): Map[LocationId, LocationRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[LocationFields, LocationRow] = { + UpdateBuilderMock(UpdateParams.empty, LocationFields.structure, map) + } + override def update(row: LocationRow)(implicit c: Connection): Boolean = { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + override def upsert(unsaved: LocationRow)(implicit c: Connection): LocationRow = { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + override def upsertBatch(unsaved: Iterable[LocationRow])(implicit c: Connection): List[LocationRow] = { + unsaved.map { row => + map += (row.id -> row) + row + }.toList + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[LocationRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationRow.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationRow.scala new file mode 100644 index 0000000000..8c946828cd --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationRow.scala @@ -0,0 +1,92 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoInet +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoPoint +import adventureworks.customtypes.TypoPolygon +import anorm.Column +import anorm.RowParser +import anorm.Success +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** Table: frontpage.location + Primary key: id */ +case class LocationRow( + /** Default: gen_random_uuid() */ + id: LocationId, + name: String, + position: Option[TypoPoint], + area: Option[TypoPolygon], + ipRange: Option[TypoInet], + /** Default: '{}'::jsonb */ + metadata: Option[TypoJsonb] +){ + def toUnsavedRow(id: Defaulted[LocationId], metadata: Defaulted[Option[TypoJsonb]] = Defaulted.Provided(this.metadata)): LocationRowUnsaved = + LocationRowUnsaved(name, position, area, ipRange, id, metadata) + } + +object LocationRow { + implicit lazy val reads: Reads[LocationRow] = Reads[LocationRow](json => JsResult.fromTry( + Try( + LocationRow( + id = json.\("id").as(LocationId.reads), + name = json.\("name").as(Reads.StringReads), + position = json.\("position").toOption.map(_.as(TypoPoint.reads)), + area = json.\("area").toOption.map(_.as(TypoPolygon.reads)), + ipRange = json.\("ip_range").toOption.map(_.as(TypoInet.reads)), + metadata = json.\("metadata").toOption.map(_.as(TypoJsonb.reads)) + ) + ) + ), + ) + def rowParser(idx: Int): RowParser[LocationRow] = RowParser[LocationRow] { row => + Success( + LocationRow( + id = row(idx + 0)(LocationId.column), + name = row(idx + 1)(Column.columnToString), + position = row(idx + 2)(Column.columnToOption(TypoPoint.column)), + area = row(idx + 3)(Column.columnToOption(TypoPolygon.column)), + ipRange = row(idx + 4)(Column.columnToOption(TypoInet.column)), + metadata = row(idx + 5)(Column.columnToOption(TypoJsonb.column)) + ) + ) + } + implicit lazy val text: Text[LocationRow] = Text.instance[LocationRow]{ (row, sb) => + LocationId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(TypoPoint.text).unsafeEncode(row.position, sb) + sb.append(Text.DELIMETER) + Text.option(TypoPolygon.text).unsafeEncode(row.area, sb) + sb.append(Text.DELIMETER) + Text.option(TypoInet.text).unsafeEncode(row.ipRange, sb) + sb.append(Text.DELIMETER) + Text.option(TypoJsonb.text).unsafeEncode(row.metadata, sb) + } + implicit lazy val writes: OWrites[LocationRow] = OWrites[LocationRow](o => + new JsObject(ListMap[String, JsValue]( + "id" -> LocationId.writes.writes(o.id), + "name" -> Writes.StringWrites.writes(o.name), + "position" -> Writes.OptionWrites(TypoPoint.writes).writes(o.position), + "area" -> Writes.OptionWrites(TypoPolygon.writes).writes(o.area), + "ip_range" -> Writes.OptionWrites(TypoInet.writes).writes(o.ipRange), + "metadata" -> Writes.OptionWrites(TypoJsonb.writes).writes(o.metadata) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationRowUnsaved.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationRowUnsaved.scala new file mode 100644 index 0000000000..161d3e2e09 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/location/LocationRowUnsaved.scala @@ -0,0 +1,88 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoInet +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoPoint +import adventureworks.customtypes.TypoPolygon +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** This class corresponds to a row in table `frontpage.location` which has not been persisted yet */ +case class LocationRowUnsaved( + name: String, + position: Option[TypoPoint], + area: Option[TypoPolygon], + ipRange: Option[TypoInet], + /** Default: gen_random_uuid() */ + id: Defaulted[LocationId] = Defaulted.UseDefault, + /** Default: '{}'::jsonb */ + metadata: Defaulted[Option[TypoJsonb]] = Defaulted.UseDefault +) { + def toRow(idDefault: => LocationId, metadataDefault: => Option[TypoJsonb]): LocationRow = + LocationRow( + name = name, + position = position, + area = area, + ipRange = ipRange, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + }, + metadata = metadata match { + case Defaulted.UseDefault => metadataDefault + case Defaulted.Provided(value) => value + } + ) +} +object LocationRowUnsaved { + implicit lazy val reads: Reads[LocationRowUnsaved] = Reads[LocationRowUnsaved](json => JsResult.fromTry( + Try( + LocationRowUnsaved( + name = json.\("name").as(Reads.StringReads), + position = json.\("position").toOption.map(_.as(TypoPoint.reads)), + area = json.\("area").toOption.map(_.as(TypoPolygon.reads)), + ipRange = json.\("ip_range").toOption.map(_.as(TypoInet.reads)), + id = json.\("id").as(Defaulted.reads(LocationId.reads)), + metadata = json.\("metadata").as(Defaulted.readsOpt(TypoJsonb.reads)) + ) + ) + ), + ) + implicit lazy val text: Text[LocationRowUnsaved] = Text.instance[LocationRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(TypoPoint.text).unsafeEncode(row.position, sb) + sb.append(Text.DELIMETER) + Text.option(TypoPolygon.text).unsafeEncode(row.area, sb) + sb.append(Text.DELIMETER) + Text.option(TypoInet.text).unsafeEncode(row.ipRange, sb) + sb.append(Text.DELIMETER) + Defaulted.text(LocationId.text).unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(TypoJsonb.text)).unsafeEncode(row.metadata, sb) + } + implicit lazy val writes: OWrites[LocationRowUnsaved] = OWrites[LocationRowUnsaved](o => + new JsObject(ListMap[String, JsValue]( + "name" -> Writes.StringWrites.writes(o.name), + "position" -> Writes.OptionWrites(TypoPoint.writes).writes(o.position), + "area" -> Writes.OptionWrites(TypoPolygon.writes).writes(o.area), + "ip_range" -> Writes.OptionWrites(TypoInet.writes).writes(o.ipRange), + "id" -> Defaulted.writes(LocationId.writes).writes(o.id), + "metadata" -> Defaulted.writes(Writes.OptionWrites(TypoJsonb.writes)).writes(o.metadata) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderFields.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderFields.scala new file mode 100644 index 0000000000..ba6d63651e --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderFields.scala @@ -0,0 +1,65 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.product.ProductFields +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.product.ProductRow +import adventureworks.frontpage.user.UserFields +import adventureworks.frontpage.user.UserId +import adventureworks.frontpage.user.UserRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait OrderFields { + def id: IdField[OrderId, OrderRow] + def userId: OptField[UserId, OrderRow] + def productId: OptField[ProductId, OrderRow] + def status: OptField[OrderStatus, OrderRow] + def total: Field[BigDecimal, OrderRow] + def createdAt: OptField[TypoLocalDateTime, OrderRow] + def shippedAt: OptField[TypoLocalDateTime, OrderRow] + def fkProduct: ForeignKey[ProductFields, ProductRow] = + ForeignKey[ProductFields, ProductRow]("frontpage.order_product_id_fkey", Nil) + .withColumnPair(productId, _.id) + def fkUser: ForeignKey[UserFields, UserRow] = + ForeignKey[UserFields, UserRow]("frontpage.order_user_id_fkey", Nil) + .withColumnPair(userId, _.id) +} + +object OrderFields { + lazy val structure: Relation[OrderFields, OrderRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[OrderFields, OrderRow] { + + override lazy val fields: OrderFields = new OrderFields { + override def id = IdField[OrderId, OrderRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def userId = OptField[UserId, OrderRow](_path, "user_id", None, Some("uuid"), x => x.userId, (row, value) => row.copy(userId = value)) + override def productId = OptField[ProductId, OrderRow](_path, "product_id", None, Some("uuid"), x => x.productId, (row, value) => row.copy(productId = value)) + override def status = OptField[OrderStatus, OrderRow](_path, "status", None, Some("frontpage.order_status"), x => x.status, (row, value) => row.copy(status = value)) + override def total = Field[BigDecimal, OrderRow](_path, "total", None, Some("numeric"), x => x.total, (row, value) => row.copy(total = value)) + override def createdAt = OptField[TypoLocalDateTime, OrderRow](_path, "created_at", Some("text"), Some("timestamp"), x => x.createdAt, (row, value) => row.copy(createdAt = value)) + override def shippedAt = OptField[TypoLocalDateTime, OrderRow](_path, "shipped_at", Some("text"), Some("timestamp"), x => x.shippedAt, (row, value) => row.copy(shippedAt = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, OrderRow]] = + List[FieldLikeNoHkt[?, OrderRow]](fields.id, fields.userId, fields.productId, fields.status, fields.total, fields.createdAt, fields.shippedAt) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderId.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderId.scala new file mode 100644 index 0000000000..892b4713f8 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import adventureworks.customtypes.TypoUUID +import anorm.Column +import anorm.ParameterMetaData +import anorm.ToStatement +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.order` */ +case class OrderId(value: TypoUUID) extends AnyVal +object OrderId { + implicit lazy val arrayColumn: Column[Array[OrderId]] = Column.columnToArray(column, implicitly) + implicit lazy val arrayToStatement: ToStatement[Array[OrderId]] = TypoUUID.arrayToStatement.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[OrderId, TypoUUID] = Bijection[OrderId, TypoUUID](_.value)(OrderId.apply) + implicit lazy val column: Column[OrderId] = TypoUUID.column.map(OrderId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[OrderId] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[OrderId] = new ParameterMetaData[OrderId] { + override def sqlType: String = TypoUUID.parameterMetadata.sqlType + override def jdbcType: Int = TypoUUID.parameterMetadata.jdbcType + } + implicit lazy val reads: Reads[OrderId] = TypoUUID.reads.map(OrderId.apply) + implicit lazy val text: Text[OrderId] = new Text[OrderId] { + override def unsafeEncode(v: OrderId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: OrderId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } + implicit lazy val toStatement: ToStatement[OrderId] = TypoUUID.toStatement.contramap(_.value) + implicit lazy val writes: Writes[OrderId] = TypoUUID.writes.contramap(_.value) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderRepo.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderRepo.scala new file mode 100644 index 0000000000..bc3a490fdc --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderRepo.scala @@ -0,0 +1,35 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import java.sql.Connection +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait OrderRepo { + def delete: DeleteBuilder[OrderFields, OrderRow] + def deleteById(id: OrderId)(implicit c: Connection): Boolean + def deleteByIds(ids: Array[OrderId])(implicit c: Connection): Int + def insert(unsaved: OrderRow)(implicit c: Connection): OrderRow + def insert(unsaved: OrderRowUnsaved)(implicit c: Connection): OrderRow + def insertStreaming(unsaved: Iterator[OrderRow], batchSize: Int = 10000)(implicit c: Connection): Long + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Iterator[OrderRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long + def select: SelectBuilder[OrderFields, OrderRow] + def selectAll(implicit c: Connection): List[OrderRow] + def selectById(id: OrderId)(implicit c: Connection): Option[OrderRow] + def selectByIds(ids: Array[OrderId])(implicit c: Connection): List[OrderRow] + def selectByIdsTracked(ids: Array[OrderId])(implicit c: Connection): Map[OrderId, OrderRow] + def update: UpdateBuilder[OrderFields, OrderRow] + def update(row: OrderRow)(implicit c: Connection): Boolean + def upsert(unsaved: OrderRow)(implicit c: Connection): OrderRow + def upsertBatch(unsaved: Iterable[OrderRow])(implicit c: Connection): List[OrderRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Iterator[OrderRow], batchSize: Int = 10000)(implicit c: Connection): Int +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderRepoImpl.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderRepoImpl.scala new file mode 100644 index 0000000000..c48100a107 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderRepoImpl.scala @@ -0,0 +1,207 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.user.UserId +import anorm.BatchSql +import anorm.NamedParameter +import anorm.ParameterValue +import anorm.RowParser +import anorm.SQL +import anorm.SimpleSql +import anorm.SqlStringInterpolation +import anorm.ToStatement +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class OrderRepoImpl extends OrderRepo { + override def delete: DeleteBuilder[OrderFields, OrderRow] = { + DeleteBuilder(""""frontpage"."order"""", OrderFields.structure) + } + override def deleteById(id: OrderId)(implicit c: Connection): Boolean = { + SQL"""delete from "frontpage"."order" where "id" = ${ParameterValue(id, null, OrderId.toStatement)}""".executeUpdate() > 0 + } + override def deleteByIds(ids: Array[OrderId])(implicit c: Connection): Int = { + SQL"""delete + from "frontpage"."order" + where "id" = ANY(${ids}) + """.executeUpdate() + + } + override def insert(unsaved: OrderRow)(implicit c: Connection): OrderRow = { + SQL"""insert into "frontpage"."order"("id", "user_id", "product_id", "status", "total", "created_at", "shipped_at") + values (${ParameterValue(unsaved.id, null, OrderId.toStatement)}::uuid, ${ParameterValue(unsaved.userId, null, ToStatement.optionToStatement(UserId.toStatement, UserId.parameterMetadata))}::uuid, ${ParameterValue(unsaved.productId, null, ToStatement.optionToStatement(ProductId.toStatement, ProductId.parameterMetadata))}::uuid, ${ParameterValue(unsaved.status, null, ToStatement.optionToStatement(OrderStatus.toStatement, OrderStatus.parameterMetadata))}::frontpage.order_status, ${ParameterValue(unsaved.total, null, ToStatement.scalaBigDecimalToStatement)}::numeric, ${ParameterValue(unsaved.createdAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp, ${ParameterValue(unsaved.shippedAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp) + returning "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text + """ + .executeInsert(OrderRow.rowParser(1).single) + + } + override def insert(unsaved: OrderRowUnsaved)(implicit c: Connection): OrderRow = { + val namedParameters = List( + Some((NamedParameter("user_id", ParameterValue(unsaved.userId, null, ToStatement.optionToStatement(UserId.toStatement, UserId.parameterMetadata))), "::uuid")), + Some((NamedParameter("product_id", ParameterValue(unsaved.productId, null, ToStatement.optionToStatement(ProductId.toStatement, ProductId.parameterMetadata))), "::uuid")), + Some((NamedParameter("total", ParameterValue(unsaved.total, null, ToStatement.scalaBigDecimalToStatement)), "::numeric")), + Some((NamedParameter("shipped_at", ParameterValue(unsaved.shippedAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))), "::timestamp")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("id", ParameterValue(value, null, OrderId.toStatement)), "::uuid")) + }, + unsaved.status match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("status", ParameterValue(value, null, ToStatement.optionToStatement(OrderStatus.toStatement, OrderStatus.parameterMetadata))), "::frontpage.order_status")) + }, + unsaved.createdAt match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("created_at", ParameterValue(value, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))), "::timestamp")) + } + ).flatten + val quote = '"'.toString + if (namedParameters.isEmpty) { + SQL"""insert into "frontpage"."order" default values + returning "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text + """ + .executeInsert(OrderRow.rowParser(1).single) + } else { + val q = s"""insert into "frontpage"."order"(${namedParameters.map{case (x, _) => quote + x.name + quote}.mkString(", ")}) + values (${namedParameters.map{ case (np, cast) => s"{${np.name}}$cast"}.mkString(", ")}) + returning "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text + """ + SimpleSql(SQL(q), namedParameters.map { case (np, _) => np.tupled }.toMap, RowParser.successful) + .executeInsert(OrderRow.rowParser(1).single) + } + + } + override def insertStreaming(unsaved: Iterator[OrderRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."order"("id", "user_id", "product_id", "status", "total", "created_at", "shipped_at") FROM STDIN""", batchSize, unsaved)(OrderRow.text, c) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[OrderRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."order"("user_id", "product_id", "total", "shipped_at", "id", "status", "created_at") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(OrderRowUnsaved.text, c) + } + override def select: SelectBuilder[OrderFields, OrderRow] = { + SelectBuilderSql(""""frontpage"."order"""", OrderFields.structure, OrderRow.rowParser) + } + override def selectAll(implicit c: Connection): List[OrderRow] = { + SQL"""select "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text + from "frontpage"."order" + """.as(OrderRow.rowParser(1).*) + } + override def selectById(id: OrderId)(implicit c: Connection): Option[OrderRow] = { + SQL"""select "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text + from "frontpage"."order" + where "id" = ${ParameterValue(id, null, OrderId.toStatement)} + """.as(OrderRow.rowParser(1).singleOpt) + } + override def selectByIds(ids: Array[OrderId])(implicit c: Connection): List[OrderRow] = { + SQL"""select "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text + from "frontpage"."order" + where "id" = ANY(${ids}) + """.as(OrderRow.rowParser(1).*) + + } + override def selectByIdsTracked(ids: Array[OrderId])(implicit c: Connection): Map[OrderId, OrderRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[OrderFields, OrderRow] = { + UpdateBuilder(""""frontpage"."order"""", OrderFields.structure, OrderRow.rowParser) + } + override def update(row: OrderRow)(implicit c: Connection): Boolean = { + val id = row.id + SQL"""update "frontpage"."order" + set "user_id" = ${ParameterValue(row.userId, null, ToStatement.optionToStatement(UserId.toStatement, UserId.parameterMetadata))}::uuid, + "product_id" = ${ParameterValue(row.productId, null, ToStatement.optionToStatement(ProductId.toStatement, ProductId.parameterMetadata))}::uuid, + "status" = ${ParameterValue(row.status, null, ToStatement.optionToStatement(OrderStatus.toStatement, OrderStatus.parameterMetadata))}::frontpage.order_status, + "total" = ${ParameterValue(row.total, null, ToStatement.scalaBigDecimalToStatement)}::numeric, + "created_at" = ${ParameterValue(row.createdAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp, + "shipped_at" = ${ParameterValue(row.shippedAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp + where "id" = ${ParameterValue(id, null, OrderId.toStatement)} + """.executeUpdate() > 0 + } + override def upsert(unsaved: OrderRow)(implicit c: Connection): OrderRow = { + SQL"""insert into "frontpage"."order"("id", "user_id", "product_id", "status", "total", "created_at", "shipped_at") + values ( + ${ParameterValue(unsaved.id, null, OrderId.toStatement)}::uuid, + ${ParameterValue(unsaved.userId, null, ToStatement.optionToStatement(UserId.toStatement, UserId.parameterMetadata))}::uuid, + ${ParameterValue(unsaved.productId, null, ToStatement.optionToStatement(ProductId.toStatement, ProductId.parameterMetadata))}::uuid, + ${ParameterValue(unsaved.status, null, ToStatement.optionToStatement(OrderStatus.toStatement, OrderStatus.parameterMetadata))}::frontpage.order_status, + ${ParameterValue(unsaved.total, null, ToStatement.scalaBigDecimalToStatement)}::numeric, + ${ParameterValue(unsaved.createdAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp, + ${ParameterValue(unsaved.shippedAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp + ) + on conflict ("id") + do update set + "user_id" = EXCLUDED."user_id", + "product_id" = EXCLUDED."product_id", + "status" = EXCLUDED."status", + "total" = EXCLUDED."total", + "created_at" = EXCLUDED."created_at", + "shipped_at" = EXCLUDED."shipped_at" + returning "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text + """ + .executeInsert(OrderRow.rowParser(1).single) + + } + override def upsertBatch(unsaved: Iterable[OrderRow])(implicit c: Connection): List[OrderRow] = { + def toNamedParameter(row: OrderRow): List[NamedParameter] = List( + NamedParameter("id", ParameterValue(row.id, null, OrderId.toStatement)), + NamedParameter("user_id", ParameterValue(row.userId, null, ToStatement.optionToStatement(UserId.toStatement, UserId.parameterMetadata))), + NamedParameter("product_id", ParameterValue(row.productId, null, ToStatement.optionToStatement(ProductId.toStatement, ProductId.parameterMetadata))), + NamedParameter("status", ParameterValue(row.status, null, ToStatement.optionToStatement(OrderStatus.toStatement, OrderStatus.parameterMetadata))), + NamedParameter("total", ParameterValue(row.total, null, ToStatement.scalaBigDecimalToStatement)), + NamedParameter("created_at", ParameterValue(row.createdAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))), + NamedParameter("shipped_at", ParameterValue(row.shippedAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))) + ) + unsaved.toList match { + case Nil => Nil + case head :: rest => + new anorm.adventureworks.ExecuteReturningSyntax.Ops( + BatchSql( + s"""insert into "frontpage"."order"("id", "user_id", "product_id", "status", "total", "created_at", "shipped_at") + values ({id}::uuid, {user_id}::uuid, {product_id}::uuid, {status}::frontpage.order_status, {total}::numeric, {created_at}::timestamp, {shipped_at}::timestamp) + on conflict ("id") + do update set + "user_id" = EXCLUDED."user_id", + "product_id" = EXCLUDED."product_id", + "status" = EXCLUDED."status", + "total" = EXCLUDED."total", + "created_at" = EXCLUDED."created_at", + "shipped_at" = EXCLUDED."shipped_at" + returning "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text + """, + toNamedParameter(head), + rest.map(toNamedParameter)* + ) + ).executeReturning(OrderRow.rowParser(1).*) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[OrderRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + SQL"""create temporary table order_TEMP (like "frontpage"."order") on commit drop""".execute(): @nowarn + streamingInsert(s"""copy order_TEMP("id", "user_id", "product_id", "status", "total", "created_at", "shipped_at") from stdin""", batchSize, unsaved)(OrderRow.text, c): @nowarn + SQL"""insert into "frontpage"."order"("id", "user_id", "product_id", "status", "total", "created_at", "shipped_at") + select * from order_TEMP + on conflict ("id") + do update set + "user_id" = EXCLUDED."user_id", + "product_id" = EXCLUDED."product_id", + "status" = EXCLUDED."status", + "total" = EXCLUDED."total", + "created_at" = EXCLUDED."created_at", + "shipped_at" = EXCLUDED."shipped_at" + ; + drop table order_TEMP;""".executeUpdate() + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderRepoMock.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderRepoMock.scala new file mode 100644 index 0000000000..bed8964ca8 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderRepoMock.scala @@ -0,0 +1,103 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class OrderRepoMock(toRow: Function1[OrderRowUnsaved, OrderRow], + map: scala.collection.mutable.Map[OrderId, OrderRow] = scala.collection.mutable.Map.empty) extends OrderRepo { + override def delete: DeleteBuilder[OrderFields, OrderRow] = { + DeleteBuilderMock(DeleteParams.empty, OrderFields.structure, map) + } + override def deleteById(id: OrderId)(implicit c: Connection): Boolean = { + map.remove(id).isDefined + } + override def deleteByIds(ids: Array[OrderId])(implicit c: Connection): Int = { + ids.map(id => map.remove(id)).count(_.isDefined) + } + override def insert(unsaved: OrderRow)(implicit c: Connection): OrderRow = { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + override def insert(unsaved: OrderRowUnsaved)(implicit c: Connection): OrderRow = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Iterator[OrderRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size.toLong + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[OrderRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + } + unsaved.size.toLong + } + override def select: SelectBuilder[OrderFields, OrderRow] = { + SelectBuilderMock(OrderFields.structure, () => map.values.toList, SelectParams.empty) + } + override def selectAll(implicit c: Connection): List[OrderRow] = { + map.values.toList + } + override def selectById(id: OrderId)(implicit c: Connection): Option[OrderRow] = { + map.get(id) + } + override def selectByIds(ids: Array[OrderId])(implicit c: Connection): List[OrderRow] = { + ids.flatMap(map.get).toList + } + override def selectByIdsTracked(ids: Array[OrderId])(implicit c: Connection): Map[OrderId, OrderRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[OrderFields, OrderRow] = { + UpdateBuilderMock(UpdateParams.empty, OrderFields.structure, map) + } + override def update(row: OrderRow)(implicit c: Connection): Boolean = { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + override def upsert(unsaved: OrderRow)(implicit c: Connection): OrderRow = { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + override def upsertBatch(unsaved: Iterable[OrderRow])(implicit c: Connection): List[OrderRow] = { + unsaved.map { row => + map += (row.id -> row) + row + }.toList + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[OrderRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderRow.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderRow.scala new file mode 100644 index 0000000000..4918372756 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderRow.scala @@ -0,0 +1,100 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.user.UserId +import anorm.Column +import anorm.RowParser +import anorm.Success +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** Table: frontpage.order + Primary key: id */ +case class OrderRow( + /** Default: gen_random_uuid() */ + id: OrderId, + /** Points to [[user.UserRow.id]] */ + userId: Option[UserId], + /** Points to [[product.ProductRow.id]] */ + productId: Option[ProductId], + /** Default: 'pending'::frontpage.order_status */ + status: Option[OrderStatus], + total: BigDecimal, + /** Default: now() */ + createdAt: Option[TypoLocalDateTime], + shippedAt: Option[TypoLocalDateTime] +){ + def toUnsavedRow(id: Defaulted[OrderId], status: Defaulted[Option[OrderStatus]] = Defaulted.Provided(this.status), createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.Provided(this.createdAt)): OrderRowUnsaved = + OrderRowUnsaved(userId, productId, total, shippedAt, id, status, createdAt) + } + +object OrderRow { + implicit lazy val reads: Reads[OrderRow] = Reads[OrderRow](json => JsResult.fromTry( + Try( + OrderRow( + id = json.\("id").as(OrderId.reads), + userId = json.\("user_id").toOption.map(_.as(UserId.reads)), + productId = json.\("product_id").toOption.map(_.as(ProductId.reads)), + status = json.\("status").toOption.map(_.as(OrderStatus.reads)), + total = json.\("total").as(Reads.bigDecReads), + createdAt = json.\("created_at").toOption.map(_.as(TypoLocalDateTime.reads)), + shippedAt = json.\("shipped_at").toOption.map(_.as(TypoLocalDateTime.reads)) + ) + ) + ), + ) + def rowParser(idx: Int): RowParser[OrderRow] = RowParser[OrderRow] { row => + Success( + OrderRow( + id = row(idx + 0)(OrderId.column), + userId = row(idx + 1)(Column.columnToOption(UserId.column)), + productId = row(idx + 2)(Column.columnToOption(ProductId.column)), + status = row(idx + 3)(Column.columnToOption(OrderStatus.column)), + total = row(idx + 4)(Column.columnToScalaBigDecimal), + createdAt = row(idx + 5)(Column.columnToOption(TypoLocalDateTime.column)), + shippedAt = row(idx + 6)(Column.columnToOption(TypoLocalDateTime.column)) + ) + ) + } + implicit lazy val text: Text[OrderRow] = Text.instance[OrderRow]{ (row, sb) => + OrderId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.option(UserId.text).unsafeEncode(row.userId, sb) + sb.append(Text.DELIMETER) + Text.option(ProductId.text).unsafeEncode(row.productId, sb) + sb.append(Text.DELIMETER) + Text.option(OrderStatus.text).unsafeEncode(row.status, sb) + sb.append(Text.DELIMETER) + Text.bigDecimalInstance.unsafeEncode(row.total, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.createdAt, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.shippedAt, sb) + } + implicit lazy val writes: OWrites[OrderRow] = OWrites[OrderRow](o => + new JsObject(ListMap[String, JsValue]( + "id" -> OrderId.writes.writes(o.id), + "user_id" -> Writes.OptionWrites(UserId.writes).writes(o.userId), + "product_id" -> Writes.OptionWrites(ProductId.writes).writes(o.productId), + "status" -> Writes.OptionWrites(OrderStatus.writes).writes(o.status), + "total" -> Writes.BigDecimalWrites.writes(o.total), + "created_at" -> Writes.OptionWrites(TypoLocalDateTime.writes).writes(o.createdAt), + "shipped_at" -> Writes.OptionWrites(TypoLocalDateTime.writes).writes(o.shippedAt) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderRowUnsaved.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderRowUnsaved.scala new file mode 100644 index 0000000000..dbf82a5ef5 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order/OrderRowUnsaved.scala @@ -0,0 +1,99 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.user.UserId +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** This class corresponds to a row in table `frontpage.order` which has not been persisted yet */ +case class OrderRowUnsaved( + /** Points to [[user.UserRow.id]] */ + userId: Option[UserId], + /** Points to [[product.ProductRow.id]] */ + productId: Option[ProductId], + total: BigDecimal, + shippedAt: Option[TypoLocalDateTime], + /** Default: gen_random_uuid() */ + id: Defaulted[OrderId] = Defaulted.UseDefault, + /** Default: 'pending'::frontpage.order_status */ + status: Defaulted[Option[OrderStatus]] = Defaulted.UseDefault, + /** Default: now() */ + createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault +) { + def toRow(idDefault: => OrderId, statusDefault: => Option[OrderStatus], createdAtDefault: => Option[TypoLocalDateTime]): OrderRow = + OrderRow( + userId = userId, + productId = productId, + total = total, + shippedAt = shippedAt, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + }, + status = status match { + case Defaulted.UseDefault => statusDefault + case Defaulted.Provided(value) => value + }, + createdAt = createdAt match { + case Defaulted.UseDefault => createdAtDefault + case Defaulted.Provided(value) => value + } + ) +} +object OrderRowUnsaved { + implicit lazy val reads: Reads[OrderRowUnsaved] = Reads[OrderRowUnsaved](json => JsResult.fromTry( + Try( + OrderRowUnsaved( + userId = json.\("user_id").toOption.map(_.as(UserId.reads)), + productId = json.\("product_id").toOption.map(_.as(ProductId.reads)), + total = json.\("total").as(Reads.bigDecReads), + shippedAt = json.\("shipped_at").toOption.map(_.as(TypoLocalDateTime.reads)), + id = json.\("id").as(Defaulted.reads(OrderId.reads)), + status = json.\("status").as(Defaulted.readsOpt(OrderStatus.reads)), + createdAt = json.\("created_at").as(Defaulted.readsOpt(TypoLocalDateTime.reads)) + ) + ) + ), + ) + implicit lazy val text: Text[OrderRowUnsaved] = Text.instance[OrderRowUnsaved]{ (row, sb) => + Text.option(UserId.text).unsafeEncode(row.userId, sb) + sb.append(Text.DELIMETER) + Text.option(ProductId.text).unsafeEncode(row.productId, sb) + sb.append(Text.DELIMETER) + Text.bigDecimalInstance.unsafeEncode(row.total, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.shippedAt, sb) + sb.append(Text.DELIMETER) + Defaulted.text(OrderId.text).unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(OrderStatus.text)).unsafeEncode(row.status, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(TypoLocalDateTime.text)).unsafeEncode(row.createdAt, sb) + } + implicit lazy val writes: OWrites[OrderRowUnsaved] = OWrites[OrderRowUnsaved](o => + new JsObject(ListMap[String, JsValue]( + "user_id" -> Writes.OptionWrites(UserId.writes).writes(o.userId), + "product_id" -> Writes.OptionWrites(ProductId.writes).writes(o.productId), + "total" -> Writes.BigDecimalWrites.writes(o.total), + "shipped_at" -> Writes.OptionWrites(TypoLocalDateTime.writes).writes(o.shippedAt), + "id" -> Defaulted.writes(OrderId.writes).writes(o.id), + "status" -> Defaulted.writes(Writes.OptionWrites(OrderStatus.writes)).writes(o.status), + "created_at" -> Defaulted.writes(Writes.OptionWrites(TypoLocalDateTime.writes)).writes(o.createdAt) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemFields.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemFields.scala new file mode 100644 index 0000000000..d86c231793 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemFields.scala @@ -0,0 +1,63 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.order.OrderFields +import adventureworks.frontpage.order.OrderId +import adventureworks.frontpage.order.OrderRow +import adventureworks.frontpage.product.ProductFields +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.product.ProductRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait OrderItemFields { + def id: IdField[OrderItemId, OrderItemRow] + def orderId: OptField[OrderId, OrderItemRow] + def productId: OptField[ProductId, OrderItemRow] + def quantity: Field[Int, OrderItemRow] + def price: Field[BigDecimal, OrderItemRow] + def shippedAt: OptField[TypoLocalDateTime, OrderItemRow] + def fkOrder: ForeignKey[OrderFields, OrderRow] = + ForeignKey[OrderFields, OrderRow]("frontpage.order_item_order_id_fkey", Nil) + .withColumnPair(orderId, _.id) + def fkProduct: ForeignKey[ProductFields, ProductRow] = + ForeignKey[ProductFields, ProductRow]("frontpage.order_item_product_id_fkey", Nil) + .withColumnPair(productId, _.id) +} + +object OrderItemFields { + lazy val structure: Relation[OrderItemFields, OrderItemRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[OrderItemFields, OrderItemRow] { + + override lazy val fields: OrderItemFields = new OrderItemFields { + override def id = IdField[OrderItemId, OrderItemRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def orderId = OptField[OrderId, OrderItemRow](_path, "order_id", None, Some("uuid"), x => x.orderId, (row, value) => row.copy(orderId = value)) + override def productId = OptField[ProductId, OrderItemRow](_path, "product_id", None, Some("uuid"), x => x.productId, (row, value) => row.copy(productId = value)) + override def quantity = Field[Int, OrderItemRow](_path, "quantity", None, Some("int4"), x => x.quantity, (row, value) => row.copy(quantity = value)) + override def price = Field[BigDecimal, OrderItemRow](_path, "price", None, Some("numeric"), x => x.price, (row, value) => row.copy(price = value)) + override def shippedAt = OptField[TypoLocalDateTime, OrderItemRow](_path, "shipped_at", Some("text"), Some("timestamp"), x => x.shippedAt, (row, value) => row.copy(shippedAt = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, OrderItemRow]] = + List[FieldLikeNoHkt[?, OrderItemRow]](fields.id, fields.orderId, fields.productId, fields.quantity, fields.price, fields.shippedAt) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemId.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemId.scala new file mode 100644 index 0000000000..30f4dc9963 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import adventureworks.customtypes.TypoUUID +import anorm.Column +import anorm.ParameterMetaData +import anorm.ToStatement +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.order_item` */ +case class OrderItemId(value: TypoUUID) extends AnyVal +object OrderItemId { + implicit lazy val arrayColumn: Column[Array[OrderItemId]] = Column.columnToArray(column, implicitly) + implicit lazy val arrayToStatement: ToStatement[Array[OrderItemId]] = TypoUUID.arrayToStatement.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[OrderItemId, TypoUUID] = Bijection[OrderItemId, TypoUUID](_.value)(OrderItemId.apply) + implicit lazy val column: Column[OrderItemId] = TypoUUID.column.map(OrderItemId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[OrderItemId] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[OrderItemId] = new ParameterMetaData[OrderItemId] { + override def sqlType: String = TypoUUID.parameterMetadata.sqlType + override def jdbcType: Int = TypoUUID.parameterMetadata.jdbcType + } + implicit lazy val reads: Reads[OrderItemId] = TypoUUID.reads.map(OrderItemId.apply) + implicit lazy val text: Text[OrderItemId] = new Text[OrderItemId] { + override def unsafeEncode(v: OrderItemId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: OrderItemId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } + implicit lazy val toStatement: ToStatement[OrderItemId] = TypoUUID.toStatement.contramap(_.value) + implicit lazy val writes: Writes[OrderItemId] = TypoUUID.writes.contramap(_.value) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepo.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepo.scala new file mode 100644 index 0000000000..6020dbe3dc --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepo.scala @@ -0,0 +1,35 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import java.sql.Connection +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait OrderItemRepo { + def delete: DeleteBuilder[OrderItemFields, OrderItemRow] + def deleteById(id: OrderItemId)(implicit c: Connection): Boolean + def deleteByIds(ids: Array[OrderItemId])(implicit c: Connection): Int + def insert(unsaved: OrderItemRow)(implicit c: Connection): OrderItemRow + def insert(unsaved: OrderItemRowUnsaved)(implicit c: Connection): OrderItemRow + def insertStreaming(unsaved: Iterator[OrderItemRow], batchSize: Int = 10000)(implicit c: Connection): Long + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Iterator[OrderItemRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long + def select: SelectBuilder[OrderItemFields, OrderItemRow] + def selectAll(implicit c: Connection): List[OrderItemRow] + def selectById(id: OrderItemId)(implicit c: Connection): Option[OrderItemRow] + def selectByIds(ids: Array[OrderItemId])(implicit c: Connection): List[OrderItemRow] + def selectByIdsTracked(ids: Array[OrderItemId])(implicit c: Connection): Map[OrderItemId, OrderItemRow] + def update: UpdateBuilder[OrderItemFields, OrderItemRow] + def update(row: OrderItemRow)(implicit c: Connection): Boolean + def upsert(unsaved: OrderItemRow)(implicit c: Connection): OrderItemRow + def upsertBatch(unsaved: Iterable[OrderItemRow])(implicit c: Connection): List[OrderItemRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Iterator[OrderItemRow], batchSize: Int = 10000)(implicit c: Connection): Int +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepoImpl.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepoImpl.scala new file mode 100644 index 0000000000..399d1f9b34 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepoImpl.scala @@ -0,0 +1,194 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.order.OrderId +import adventureworks.frontpage.product.ProductId +import anorm.BatchSql +import anorm.NamedParameter +import anorm.ParameterValue +import anorm.RowParser +import anorm.SQL +import anorm.SimpleSql +import anorm.SqlStringInterpolation +import anorm.ToStatement +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class OrderItemRepoImpl extends OrderItemRepo { + override def delete: DeleteBuilder[OrderItemFields, OrderItemRow] = { + DeleteBuilder(""""frontpage"."order_item"""", OrderItemFields.structure) + } + override def deleteById(id: OrderItemId)(implicit c: Connection): Boolean = { + SQL"""delete from "frontpage"."order_item" where "id" = ${ParameterValue(id, null, OrderItemId.toStatement)}""".executeUpdate() > 0 + } + override def deleteByIds(ids: Array[OrderItemId])(implicit c: Connection): Int = { + SQL"""delete + from "frontpage"."order_item" + where "id" = ANY(${ids}) + """.executeUpdate() + + } + override def insert(unsaved: OrderItemRow)(implicit c: Connection): OrderItemRow = { + SQL"""insert into "frontpage"."order_item"("id", "order_id", "product_id", "quantity", "price", "shipped_at") + values (${ParameterValue(unsaved.id, null, OrderItemId.toStatement)}::uuid, ${ParameterValue(unsaved.orderId, null, ToStatement.optionToStatement(OrderId.toStatement, OrderId.parameterMetadata))}::uuid, ${ParameterValue(unsaved.productId, null, ToStatement.optionToStatement(ProductId.toStatement, ProductId.parameterMetadata))}::uuid, ${ParameterValue(unsaved.quantity, null, ToStatement.intToStatement)}::int4, ${ParameterValue(unsaved.price, null, ToStatement.scalaBigDecimalToStatement)}::numeric, ${ParameterValue(unsaved.shippedAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp) + returning "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text + """ + .executeInsert(OrderItemRow.rowParser(1).single) + + } + override def insert(unsaved: OrderItemRowUnsaved)(implicit c: Connection): OrderItemRow = { + val namedParameters = List( + Some((NamedParameter("order_id", ParameterValue(unsaved.orderId, null, ToStatement.optionToStatement(OrderId.toStatement, OrderId.parameterMetadata))), "::uuid")), + Some((NamedParameter("product_id", ParameterValue(unsaved.productId, null, ToStatement.optionToStatement(ProductId.toStatement, ProductId.parameterMetadata))), "::uuid")), + Some((NamedParameter("quantity", ParameterValue(unsaved.quantity, null, ToStatement.intToStatement)), "::int4")), + Some((NamedParameter("price", ParameterValue(unsaved.price, null, ToStatement.scalaBigDecimalToStatement)), "::numeric")), + Some((NamedParameter("shipped_at", ParameterValue(unsaved.shippedAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))), "::timestamp")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("id", ParameterValue(value, null, OrderItemId.toStatement)), "::uuid")) + } + ).flatten + val quote = '"'.toString + if (namedParameters.isEmpty) { + SQL"""insert into "frontpage"."order_item" default values + returning "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text + """ + .executeInsert(OrderItemRow.rowParser(1).single) + } else { + val q = s"""insert into "frontpage"."order_item"(${namedParameters.map{case (x, _) => quote + x.name + quote}.mkString(", ")}) + values (${namedParameters.map{ case (np, cast) => s"{${np.name}}$cast"}.mkString(", ")}) + returning "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text + """ + SimpleSql(SQL(q), namedParameters.map { case (np, _) => np.tupled }.toMap, RowParser.successful) + .executeInsert(OrderItemRow.rowParser(1).single) + } + + } + override def insertStreaming(unsaved: Iterator[OrderItemRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."order_item"("id", "order_id", "product_id", "quantity", "price", "shipped_at") FROM STDIN""", batchSize, unsaved)(OrderItemRow.text, c) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[OrderItemRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."order_item"("order_id", "product_id", "quantity", "price", "shipped_at", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(OrderItemRowUnsaved.text, c) + } + override def select: SelectBuilder[OrderItemFields, OrderItemRow] = { + SelectBuilderSql(""""frontpage"."order_item"""", OrderItemFields.structure, OrderItemRow.rowParser) + } + override def selectAll(implicit c: Connection): List[OrderItemRow] = { + SQL"""select "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text + from "frontpage"."order_item" + """.as(OrderItemRow.rowParser(1).*) + } + override def selectById(id: OrderItemId)(implicit c: Connection): Option[OrderItemRow] = { + SQL"""select "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text + from "frontpage"."order_item" + where "id" = ${ParameterValue(id, null, OrderItemId.toStatement)} + """.as(OrderItemRow.rowParser(1).singleOpt) + } + override def selectByIds(ids: Array[OrderItemId])(implicit c: Connection): List[OrderItemRow] = { + SQL"""select "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text + from "frontpage"."order_item" + where "id" = ANY(${ids}) + """.as(OrderItemRow.rowParser(1).*) + + } + override def selectByIdsTracked(ids: Array[OrderItemId])(implicit c: Connection): Map[OrderItemId, OrderItemRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[OrderItemFields, OrderItemRow] = { + UpdateBuilder(""""frontpage"."order_item"""", OrderItemFields.structure, OrderItemRow.rowParser) + } + override def update(row: OrderItemRow)(implicit c: Connection): Boolean = { + val id = row.id + SQL"""update "frontpage"."order_item" + set "order_id" = ${ParameterValue(row.orderId, null, ToStatement.optionToStatement(OrderId.toStatement, OrderId.parameterMetadata))}::uuid, + "product_id" = ${ParameterValue(row.productId, null, ToStatement.optionToStatement(ProductId.toStatement, ProductId.parameterMetadata))}::uuid, + "quantity" = ${ParameterValue(row.quantity, null, ToStatement.intToStatement)}::int4, + "price" = ${ParameterValue(row.price, null, ToStatement.scalaBigDecimalToStatement)}::numeric, + "shipped_at" = ${ParameterValue(row.shippedAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp + where "id" = ${ParameterValue(id, null, OrderItemId.toStatement)} + """.executeUpdate() > 0 + } + override def upsert(unsaved: OrderItemRow)(implicit c: Connection): OrderItemRow = { + SQL"""insert into "frontpage"."order_item"("id", "order_id", "product_id", "quantity", "price", "shipped_at") + values ( + ${ParameterValue(unsaved.id, null, OrderItemId.toStatement)}::uuid, + ${ParameterValue(unsaved.orderId, null, ToStatement.optionToStatement(OrderId.toStatement, OrderId.parameterMetadata))}::uuid, + ${ParameterValue(unsaved.productId, null, ToStatement.optionToStatement(ProductId.toStatement, ProductId.parameterMetadata))}::uuid, + ${ParameterValue(unsaved.quantity, null, ToStatement.intToStatement)}::int4, + ${ParameterValue(unsaved.price, null, ToStatement.scalaBigDecimalToStatement)}::numeric, + ${ParameterValue(unsaved.shippedAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp + ) + on conflict ("id") + do update set + "order_id" = EXCLUDED."order_id", + "product_id" = EXCLUDED."product_id", + "quantity" = EXCLUDED."quantity", + "price" = EXCLUDED."price", + "shipped_at" = EXCLUDED."shipped_at" + returning "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text + """ + .executeInsert(OrderItemRow.rowParser(1).single) + + } + override def upsertBatch(unsaved: Iterable[OrderItemRow])(implicit c: Connection): List[OrderItemRow] = { + def toNamedParameter(row: OrderItemRow): List[NamedParameter] = List( + NamedParameter("id", ParameterValue(row.id, null, OrderItemId.toStatement)), + NamedParameter("order_id", ParameterValue(row.orderId, null, ToStatement.optionToStatement(OrderId.toStatement, OrderId.parameterMetadata))), + NamedParameter("product_id", ParameterValue(row.productId, null, ToStatement.optionToStatement(ProductId.toStatement, ProductId.parameterMetadata))), + NamedParameter("quantity", ParameterValue(row.quantity, null, ToStatement.intToStatement)), + NamedParameter("price", ParameterValue(row.price, null, ToStatement.scalaBigDecimalToStatement)), + NamedParameter("shipped_at", ParameterValue(row.shippedAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))) + ) + unsaved.toList match { + case Nil => Nil + case head :: rest => + new anorm.adventureworks.ExecuteReturningSyntax.Ops( + BatchSql( + s"""insert into "frontpage"."order_item"("id", "order_id", "product_id", "quantity", "price", "shipped_at") + values ({id}::uuid, {order_id}::uuid, {product_id}::uuid, {quantity}::int4, {price}::numeric, {shipped_at}::timestamp) + on conflict ("id") + do update set + "order_id" = EXCLUDED."order_id", + "product_id" = EXCLUDED."product_id", + "quantity" = EXCLUDED."quantity", + "price" = EXCLUDED."price", + "shipped_at" = EXCLUDED."shipped_at" + returning "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text + """, + toNamedParameter(head), + rest.map(toNamedParameter)* + ) + ).executeReturning(OrderItemRow.rowParser(1).*) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[OrderItemRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + SQL"""create temporary table order_item_TEMP (like "frontpage"."order_item") on commit drop""".execute(): @nowarn + streamingInsert(s"""copy order_item_TEMP("id", "order_id", "product_id", "quantity", "price", "shipped_at") from stdin""", batchSize, unsaved)(OrderItemRow.text, c): @nowarn + SQL"""insert into "frontpage"."order_item"("id", "order_id", "product_id", "quantity", "price", "shipped_at") + select * from order_item_TEMP + on conflict ("id") + do update set + "order_id" = EXCLUDED."order_id", + "product_id" = EXCLUDED."product_id", + "quantity" = EXCLUDED."quantity", + "price" = EXCLUDED."price", + "shipped_at" = EXCLUDED."shipped_at" + ; + drop table order_item_TEMP;""".executeUpdate() + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepoMock.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepoMock.scala new file mode 100644 index 0000000000..742828d9ad --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepoMock.scala @@ -0,0 +1,103 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class OrderItemRepoMock(toRow: Function1[OrderItemRowUnsaved, OrderItemRow], + map: scala.collection.mutable.Map[OrderItemId, OrderItemRow] = scala.collection.mutable.Map.empty) extends OrderItemRepo { + override def delete: DeleteBuilder[OrderItemFields, OrderItemRow] = { + DeleteBuilderMock(DeleteParams.empty, OrderItemFields.structure, map) + } + override def deleteById(id: OrderItemId)(implicit c: Connection): Boolean = { + map.remove(id).isDefined + } + override def deleteByIds(ids: Array[OrderItemId])(implicit c: Connection): Int = { + ids.map(id => map.remove(id)).count(_.isDefined) + } + override def insert(unsaved: OrderItemRow)(implicit c: Connection): OrderItemRow = { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + override def insert(unsaved: OrderItemRowUnsaved)(implicit c: Connection): OrderItemRow = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Iterator[OrderItemRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size.toLong + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[OrderItemRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + } + unsaved.size.toLong + } + override def select: SelectBuilder[OrderItemFields, OrderItemRow] = { + SelectBuilderMock(OrderItemFields.structure, () => map.values.toList, SelectParams.empty) + } + override def selectAll(implicit c: Connection): List[OrderItemRow] = { + map.values.toList + } + override def selectById(id: OrderItemId)(implicit c: Connection): Option[OrderItemRow] = { + map.get(id) + } + override def selectByIds(ids: Array[OrderItemId])(implicit c: Connection): List[OrderItemRow] = { + ids.flatMap(map.get).toList + } + override def selectByIdsTracked(ids: Array[OrderItemId])(implicit c: Connection): Map[OrderItemId, OrderItemRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[OrderItemFields, OrderItemRow] = { + UpdateBuilderMock(UpdateParams.empty, OrderItemFields.structure, map) + } + override def update(row: OrderItemRow)(implicit c: Connection): Boolean = { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + override def upsert(unsaved: OrderItemRow)(implicit c: Connection): OrderItemRow = { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + override def upsertBatch(unsaved: Iterable[OrderItemRow])(implicit c: Connection): List[OrderItemRow] = { + unsaved.map { row => + map += (row.id -> row) + row + }.toList + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[OrderItemRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRow.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRow.scala new file mode 100644 index 0000000000..e452dc38c3 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRow.scala @@ -0,0 +1,92 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.order.OrderId +import adventureworks.frontpage.product.ProductId +import anorm.Column +import anorm.RowParser +import anorm.Success +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** Table: frontpage.order_item + Primary key: id */ +case class OrderItemRow( + /** Default: gen_random_uuid() */ + id: OrderItemId, + /** Points to [[order.OrderRow.id]] */ + orderId: Option[OrderId], + /** Points to [[product.ProductRow.id]] */ + productId: Option[ProductId], + quantity: Int, + price: BigDecimal, + shippedAt: Option[TypoLocalDateTime] +){ + def toUnsavedRow(id: Defaulted[OrderItemId]): OrderItemRowUnsaved = + OrderItemRowUnsaved(orderId, productId, quantity, price, shippedAt, id) + } + +object OrderItemRow { + implicit lazy val reads: Reads[OrderItemRow] = Reads[OrderItemRow](json => JsResult.fromTry( + Try( + OrderItemRow( + id = json.\("id").as(OrderItemId.reads), + orderId = json.\("order_id").toOption.map(_.as(OrderId.reads)), + productId = json.\("product_id").toOption.map(_.as(ProductId.reads)), + quantity = json.\("quantity").as(Reads.IntReads), + price = json.\("price").as(Reads.bigDecReads), + shippedAt = json.\("shipped_at").toOption.map(_.as(TypoLocalDateTime.reads)) + ) + ) + ), + ) + def rowParser(idx: Int): RowParser[OrderItemRow] = RowParser[OrderItemRow] { row => + Success( + OrderItemRow( + id = row(idx + 0)(OrderItemId.column), + orderId = row(idx + 1)(Column.columnToOption(OrderId.column)), + productId = row(idx + 2)(Column.columnToOption(ProductId.column)), + quantity = row(idx + 3)(Column.columnToInt), + price = row(idx + 4)(Column.columnToScalaBigDecimal), + shippedAt = row(idx + 5)(Column.columnToOption(TypoLocalDateTime.column)) + ) + ) + } + implicit lazy val text: Text[OrderItemRow] = Text.instance[OrderItemRow]{ (row, sb) => + OrderItemId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.option(OrderId.text).unsafeEncode(row.orderId, sb) + sb.append(Text.DELIMETER) + Text.option(ProductId.text).unsafeEncode(row.productId, sb) + sb.append(Text.DELIMETER) + Text.intInstance.unsafeEncode(row.quantity, sb) + sb.append(Text.DELIMETER) + Text.bigDecimalInstance.unsafeEncode(row.price, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.shippedAt, sb) + } + implicit lazy val writes: OWrites[OrderItemRow] = OWrites[OrderItemRow](o => + new JsObject(ListMap[String, JsValue]( + "id" -> OrderItemId.writes.writes(o.id), + "order_id" -> Writes.OptionWrites(OrderId.writes).writes(o.orderId), + "product_id" -> Writes.OptionWrites(ProductId.writes).writes(o.productId), + "quantity" -> Writes.IntWrites.writes(o.quantity), + "price" -> Writes.BigDecimalWrites.writes(o.price), + "shipped_at" -> Writes.OptionWrites(TypoLocalDateTime.writes).writes(o.shippedAt) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRowUnsaved.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRowUnsaved.scala new file mode 100644 index 0000000000..d3641d1613 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRowUnsaved.scala @@ -0,0 +1,85 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.order.OrderId +import adventureworks.frontpage.product.ProductId +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** This class corresponds to a row in table `frontpage.order_item` which has not been persisted yet */ +case class OrderItemRowUnsaved( + /** Points to [[order.OrderRow.id]] */ + orderId: Option[OrderId], + /** Points to [[product.ProductRow.id]] */ + productId: Option[ProductId], + quantity: Int, + price: BigDecimal, + shippedAt: Option[TypoLocalDateTime], + /** Default: gen_random_uuid() */ + id: Defaulted[OrderItemId] = Defaulted.UseDefault +) { + def toRow(idDefault: => OrderItemId): OrderItemRow = + OrderItemRow( + orderId = orderId, + productId = productId, + quantity = quantity, + price = price, + shippedAt = shippedAt, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object OrderItemRowUnsaved { + implicit lazy val reads: Reads[OrderItemRowUnsaved] = Reads[OrderItemRowUnsaved](json => JsResult.fromTry( + Try( + OrderItemRowUnsaved( + orderId = json.\("order_id").toOption.map(_.as(OrderId.reads)), + productId = json.\("product_id").toOption.map(_.as(ProductId.reads)), + quantity = json.\("quantity").as(Reads.IntReads), + price = json.\("price").as(Reads.bigDecReads), + shippedAt = json.\("shipped_at").toOption.map(_.as(TypoLocalDateTime.reads)), + id = json.\("id").as(Defaulted.reads(OrderItemId.reads)) + ) + ) + ), + ) + implicit lazy val text: Text[OrderItemRowUnsaved] = Text.instance[OrderItemRowUnsaved]{ (row, sb) => + Text.option(OrderId.text).unsafeEncode(row.orderId, sb) + sb.append(Text.DELIMETER) + Text.option(ProductId.text).unsafeEncode(row.productId, sb) + sb.append(Text.DELIMETER) + Text.intInstance.unsafeEncode(row.quantity, sb) + sb.append(Text.DELIMETER) + Text.bigDecimalInstance.unsafeEncode(row.price, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.shippedAt, sb) + sb.append(Text.DELIMETER) + Defaulted.text(OrderItemId.text).unsafeEncode(row.id, sb) + } + implicit lazy val writes: OWrites[OrderItemRowUnsaved] = OWrites[OrderItemRowUnsaved](o => + new JsObject(ListMap[String, JsValue]( + "order_id" -> Writes.OptionWrites(OrderId.writes).writes(o.orderId), + "product_id" -> Writes.OptionWrites(ProductId.writes).writes(o.productId), + "quantity" -> Writes.IntWrites.writes(o.quantity), + "price" -> Writes.BigDecimalWrites.writes(o.price), + "shipped_at" -> Writes.OptionWrites(TypoLocalDateTime.writes).writes(o.shippedAt), + "id" -> Defaulted.writes(OrderItemId.writes).writes(o.id) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionFields.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionFields.scala new file mode 100644 index 0000000000..856b5aae7e --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionFields.scala @@ -0,0 +1,40 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.Structure.Relation + +trait PermissionFields { + def id: IdField[PermissionId, PermissionRow] + def name: Field[String, PermissionRow] +} + +object PermissionFields { + lazy val structure: Relation[PermissionFields, PermissionRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[PermissionFields, PermissionRow] { + + override lazy val fields: PermissionFields = new PermissionFields { + override def id = IdField[PermissionId, PermissionRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, PermissionRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, PermissionRow]] = + List[FieldLikeNoHkt[?, PermissionRow]](fields.id, fields.name) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionId.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionId.scala new file mode 100644 index 0000000000..bea5b5092b --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import adventureworks.customtypes.TypoUUID +import anorm.Column +import anorm.ParameterMetaData +import anorm.ToStatement +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.permission` */ +case class PermissionId(value: TypoUUID) extends AnyVal +object PermissionId { + implicit lazy val arrayColumn: Column[Array[PermissionId]] = Column.columnToArray(column, implicitly) + implicit lazy val arrayToStatement: ToStatement[Array[PermissionId]] = TypoUUID.arrayToStatement.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[PermissionId, TypoUUID] = Bijection[PermissionId, TypoUUID](_.value)(PermissionId.apply) + implicit lazy val column: Column[PermissionId] = TypoUUID.column.map(PermissionId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[PermissionId] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[PermissionId] = new ParameterMetaData[PermissionId] { + override def sqlType: String = TypoUUID.parameterMetadata.sqlType + override def jdbcType: Int = TypoUUID.parameterMetadata.jdbcType + } + implicit lazy val reads: Reads[PermissionId] = TypoUUID.reads.map(PermissionId.apply) + implicit lazy val text: Text[PermissionId] = new Text[PermissionId] { + override def unsafeEncode(v: PermissionId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: PermissionId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } + implicit lazy val toStatement: ToStatement[PermissionId] = TypoUUID.toStatement.contramap(_.value) + implicit lazy val writes: Writes[PermissionId] = TypoUUID.writes.contramap(_.value) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepo.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepo.scala new file mode 100644 index 0000000000..638beba427 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepo.scala @@ -0,0 +1,36 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import java.sql.Connection +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait PermissionRepo { + def delete: DeleteBuilder[PermissionFields, PermissionRow] + def deleteById(id: PermissionId)(implicit c: Connection): Boolean + def deleteByIds(ids: Array[PermissionId])(implicit c: Connection): Int + def insert(unsaved: PermissionRow)(implicit c: Connection): PermissionRow + def insert(unsaved: PermissionRowUnsaved)(implicit c: Connection): PermissionRow + def insertStreaming(unsaved: Iterator[PermissionRow], batchSize: Int = 10000)(implicit c: Connection): Long + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Iterator[PermissionRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long + def select: SelectBuilder[PermissionFields, PermissionRow] + def selectAll(implicit c: Connection): List[PermissionRow] + def selectById(id: PermissionId)(implicit c: Connection): Option[PermissionRow] + def selectByIds(ids: Array[PermissionId])(implicit c: Connection): List[PermissionRow] + def selectByIdsTracked(ids: Array[PermissionId])(implicit c: Connection): Map[PermissionId, PermissionRow] + def selectByUniqueName(name: String)(implicit c: Connection): Option[PermissionRow] + def update: UpdateBuilder[PermissionFields, PermissionRow] + def update(row: PermissionRow)(implicit c: Connection): Boolean + def upsert(unsaved: PermissionRow)(implicit c: Connection): PermissionRow + def upsertBatch(unsaved: Iterable[PermissionRow])(implicit c: Connection): List[PermissionRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Iterator[PermissionRow], batchSize: Int = 10000)(implicit c: Connection): Int +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepoImpl.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepoImpl.scala new file mode 100644 index 0000000000..4c07d28a28 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepoImpl.scala @@ -0,0 +1,170 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import adventureworks.customtypes.Defaulted +import anorm.BatchSql +import anorm.NamedParameter +import anorm.ParameterValue +import anorm.RowParser +import anorm.SQL +import anorm.SimpleSql +import anorm.SqlStringInterpolation +import anorm.ToStatement +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class PermissionRepoImpl extends PermissionRepo { + override def delete: DeleteBuilder[PermissionFields, PermissionRow] = { + DeleteBuilder(""""frontpage"."permission"""", PermissionFields.structure) + } + override def deleteById(id: PermissionId)(implicit c: Connection): Boolean = { + SQL"""delete from "frontpage"."permission" where "id" = ${ParameterValue(id, null, PermissionId.toStatement)}""".executeUpdate() > 0 + } + override def deleteByIds(ids: Array[PermissionId])(implicit c: Connection): Int = { + SQL"""delete + from "frontpage"."permission" + where "id" = ANY(${ids}) + """.executeUpdate() + + } + override def insert(unsaved: PermissionRow)(implicit c: Connection): PermissionRow = { + SQL"""insert into "frontpage"."permission"("id", "name") + values (${ParameterValue(unsaved.id, null, PermissionId.toStatement)}::uuid, ${ParameterValue(unsaved.name, null, ToStatement.stringToStatement)}) + returning "id", "name" + """ + .executeInsert(PermissionRow.rowParser(1).single) + + } + override def insert(unsaved: PermissionRowUnsaved)(implicit c: Connection): PermissionRow = { + val namedParameters = List( + Some((NamedParameter("name", ParameterValue(unsaved.name, null, ToStatement.stringToStatement)), "")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("id", ParameterValue(value, null, PermissionId.toStatement)), "::uuid")) + } + ).flatten + val quote = '"'.toString + if (namedParameters.isEmpty) { + SQL"""insert into "frontpage"."permission" default values + returning "id", "name" + """ + .executeInsert(PermissionRow.rowParser(1).single) + } else { + val q = s"""insert into "frontpage"."permission"(${namedParameters.map{case (x, _) => quote + x.name + quote}.mkString(", ")}) + values (${namedParameters.map{ case (np, cast) => s"{${np.name}}$cast"}.mkString(", ")}) + returning "id", "name" + """ + SimpleSql(SQL(q), namedParameters.map { case (np, _) => np.tupled }.toMap, RowParser.successful) + .executeInsert(PermissionRow.rowParser(1).single) + } + + } + override def insertStreaming(unsaved: Iterator[PermissionRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."permission"("id", "name") FROM STDIN""", batchSize, unsaved)(PermissionRow.text, c) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[PermissionRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."permission"("name", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(PermissionRowUnsaved.text, c) + } + override def select: SelectBuilder[PermissionFields, PermissionRow] = { + SelectBuilderSql(""""frontpage"."permission"""", PermissionFields.structure, PermissionRow.rowParser) + } + override def selectAll(implicit c: Connection): List[PermissionRow] = { + SQL"""select "id", "name" + from "frontpage"."permission" + """.as(PermissionRow.rowParser(1).*) + } + override def selectById(id: PermissionId)(implicit c: Connection): Option[PermissionRow] = { + SQL"""select "id", "name" + from "frontpage"."permission" + where "id" = ${ParameterValue(id, null, PermissionId.toStatement)} + """.as(PermissionRow.rowParser(1).singleOpt) + } + override def selectByIds(ids: Array[PermissionId])(implicit c: Connection): List[PermissionRow] = { + SQL"""select "id", "name" + from "frontpage"."permission" + where "id" = ANY(${ids}) + """.as(PermissionRow.rowParser(1).*) + + } + override def selectByIdsTracked(ids: Array[PermissionId])(implicit c: Connection): Map[PermissionId, PermissionRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def selectByUniqueName(name: String)(implicit c: Connection): Option[PermissionRow] = { + SQL"""select "id", "name" + from "frontpage"."permission" + where "name" = ${ParameterValue(name, null, ToStatement.stringToStatement)} + """.as(PermissionRow.rowParser(1).singleOpt) + + } + override def update: UpdateBuilder[PermissionFields, PermissionRow] = { + UpdateBuilder(""""frontpage"."permission"""", PermissionFields.structure, PermissionRow.rowParser) + } + override def update(row: PermissionRow)(implicit c: Connection): Boolean = { + val id = row.id + SQL"""update "frontpage"."permission" + set "name" = ${ParameterValue(row.name, null, ToStatement.stringToStatement)} + where "id" = ${ParameterValue(id, null, PermissionId.toStatement)} + """.executeUpdate() > 0 + } + override def upsert(unsaved: PermissionRow)(implicit c: Connection): PermissionRow = { + SQL"""insert into "frontpage"."permission"("id", "name") + values ( + ${ParameterValue(unsaved.id, null, PermissionId.toStatement)}::uuid, + ${ParameterValue(unsaved.name, null, ToStatement.stringToStatement)} + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name" + returning "id", "name" + """ + .executeInsert(PermissionRow.rowParser(1).single) + + } + override def upsertBatch(unsaved: Iterable[PermissionRow])(implicit c: Connection): List[PermissionRow] = { + def toNamedParameter(row: PermissionRow): List[NamedParameter] = List( + NamedParameter("id", ParameterValue(row.id, null, PermissionId.toStatement)), + NamedParameter("name", ParameterValue(row.name, null, ToStatement.stringToStatement)) + ) + unsaved.toList match { + case Nil => Nil + case head :: rest => + new anorm.adventureworks.ExecuteReturningSyntax.Ops( + BatchSql( + s"""insert into "frontpage"."permission"("id", "name") + values ({id}::uuid, {name}) + on conflict ("id") + do update set + "name" = EXCLUDED."name" + returning "id", "name" + """, + toNamedParameter(head), + rest.map(toNamedParameter)* + ) + ).executeReturning(PermissionRow.rowParser(1).*) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[PermissionRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + SQL"""create temporary table permission_TEMP (like "frontpage"."permission") on commit drop""".execute(): @nowarn + streamingInsert(s"""copy permission_TEMP("id", "name") from stdin""", batchSize, unsaved)(PermissionRow.text, c): @nowarn + SQL"""insert into "frontpage"."permission"("id", "name") + select * from permission_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name" + ; + drop table permission_TEMP;""".executeUpdate() + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepoMock.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepoMock.scala new file mode 100644 index 0000000000..831f7907c7 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepoMock.scala @@ -0,0 +1,106 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class PermissionRepoMock(toRow: Function1[PermissionRowUnsaved, PermissionRow], + map: scala.collection.mutable.Map[PermissionId, PermissionRow] = scala.collection.mutable.Map.empty) extends PermissionRepo { + override def delete: DeleteBuilder[PermissionFields, PermissionRow] = { + DeleteBuilderMock(DeleteParams.empty, PermissionFields.structure, map) + } + override def deleteById(id: PermissionId)(implicit c: Connection): Boolean = { + map.remove(id).isDefined + } + override def deleteByIds(ids: Array[PermissionId])(implicit c: Connection): Int = { + ids.map(id => map.remove(id)).count(_.isDefined) + } + override def insert(unsaved: PermissionRow)(implicit c: Connection): PermissionRow = { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + override def insert(unsaved: PermissionRowUnsaved)(implicit c: Connection): PermissionRow = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Iterator[PermissionRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size.toLong + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[PermissionRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + } + unsaved.size.toLong + } + override def select: SelectBuilder[PermissionFields, PermissionRow] = { + SelectBuilderMock(PermissionFields.structure, () => map.values.toList, SelectParams.empty) + } + override def selectAll(implicit c: Connection): List[PermissionRow] = { + map.values.toList + } + override def selectById(id: PermissionId)(implicit c: Connection): Option[PermissionRow] = { + map.get(id) + } + override def selectByIds(ids: Array[PermissionId])(implicit c: Connection): List[PermissionRow] = { + ids.flatMap(map.get).toList + } + override def selectByIdsTracked(ids: Array[PermissionId])(implicit c: Connection): Map[PermissionId, PermissionRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def selectByUniqueName(name: String)(implicit c: Connection): Option[PermissionRow] = { + map.values.find(v => name == v.name) + } + override def update: UpdateBuilder[PermissionFields, PermissionRow] = { + UpdateBuilderMock(UpdateParams.empty, PermissionFields.structure, map) + } + override def update(row: PermissionRow)(implicit c: Connection): Boolean = { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + override def upsert(unsaved: PermissionRow)(implicit c: Connection): PermissionRow = { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + override def upsertBatch(unsaved: Iterable[PermissionRow])(implicit c: Connection): List[PermissionRow] = { + unsaved.map { row => + map += (row.id -> row) + row + }.toList + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[PermissionRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRow.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRow.scala new file mode 100644 index 0000000000..7a1dc94087 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRow.scala @@ -0,0 +1,63 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import adventureworks.customtypes.Defaulted +import anorm.Column +import anorm.RowParser +import anorm.Success +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** Table: frontpage.permission + Primary key: id */ +case class PermissionRow( + /** Default: gen_random_uuid() */ + id: PermissionId, + name: String +){ + def toUnsavedRow(id: Defaulted[PermissionId]): PermissionRowUnsaved = + PermissionRowUnsaved(name, id) + } + +object PermissionRow { + implicit lazy val reads: Reads[PermissionRow] = Reads[PermissionRow](json => JsResult.fromTry( + Try( + PermissionRow( + id = json.\("id").as(PermissionId.reads), + name = json.\("name").as(Reads.StringReads) + ) + ) + ), + ) + def rowParser(idx: Int): RowParser[PermissionRow] = RowParser[PermissionRow] { row => + Success( + PermissionRow( + id = row(idx + 0)(PermissionId.column), + name = row(idx + 1)(Column.columnToString) + ) + ) + } + implicit lazy val text: Text[PermissionRow] = Text.instance[PermissionRow]{ (row, sb) => + PermissionId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + } + implicit lazy val writes: OWrites[PermissionRow] = OWrites[PermissionRow](o => + new JsObject(ListMap[String, JsValue]( + "id" -> PermissionId.writes.writes(o.id), + "name" -> Writes.StringWrites.writes(o.name) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRowUnsaved.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRowUnsaved.scala new file mode 100644 index 0000000000..28a2e45d09 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRowUnsaved.scala @@ -0,0 +1,56 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import adventureworks.customtypes.Defaulted +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** This class corresponds to a row in table `frontpage.permission` which has not been persisted yet */ +case class PermissionRowUnsaved( + name: String, + /** Default: gen_random_uuid() */ + id: Defaulted[PermissionId] = Defaulted.UseDefault +) { + def toRow(idDefault: => PermissionId): PermissionRow = + PermissionRow( + name = name, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object PermissionRowUnsaved { + implicit lazy val reads: Reads[PermissionRowUnsaved] = Reads[PermissionRowUnsaved](json => JsResult.fromTry( + Try( + PermissionRowUnsaved( + name = json.\("name").as(Reads.StringReads), + id = json.\("id").as(Defaulted.reads(PermissionId.reads)) + ) + ) + ), + ) + implicit lazy val text: Text[PermissionRowUnsaved] = Text.instance[PermissionRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Defaulted.text(PermissionId.text).unsafeEncode(row.id, sb) + } + implicit lazy val writes: OWrites[PermissionRowUnsaved] = OWrites[PermissionRowUnsaved](o => + new JsObject(ListMap[String, JsValue]( + "name" -> Writes.StringWrites.writes(o.name), + "id" -> Defaulted.writes(PermissionId.writes).writes(o.id) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonFields.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonFields.scala new file mode 100644 index 0000000000..c547be34fd --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonFields.scala @@ -0,0 +1,53 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.address.AddressFields +import adventureworks.frontpage.address.AddressId +import adventureworks.frontpage.address.AddressRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait PersonFields { + def id: IdField[PersonId, PersonRow] + def name: Field[String, PersonRow] + def addressId: OptField[AddressId, PersonRow] + def createdAt: OptField[TypoLocalDateTime, PersonRow] + def fkAddress: ForeignKey[AddressFields, AddressRow] = + ForeignKey[AddressFields, AddressRow]("frontpage.fk_address", Nil) + .withColumnPair(addressId, _.id) +} + +object PersonFields { + lazy val structure: Relation[PersonFields, PersonRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[PersonFields, PersonRow] { + + override lazy val fields: PersonFields = new PersonFields { + override def id = IdField[PersonId, PersonRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, PersonRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + override def addressId = OptField[AddressId, PersonRow](_path, "address_id", None, Some("uuid"), x => x.addressId, (row, value) => row.copy(addressId = value)) + override def createdAt = OptField[TypoLocalDateTime, PersonRow](_path, "created_at", Some("text"), Some("timestamp"), x => x.createdAt, (row, value) => row.copy(createdAt = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, PersonRow]] = + List[FieldLikeNoHkt[?, PersonRow]](fields.id, fields.name, fields.addressId, fields.createdAt) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonId.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonId.scala new file mode 100644 index 0000000000..d75c5bee91 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import adventureworks.customtypes.TypoUUID +import anorm.Column +import anorm.ParameterMetaData +import anorm.ToStatement +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.person` */ +case class PersonId(value: TypoUUID) extends AnyVal +object PersonId { + implicit lazy val arrayColumn: Column[Array[PersonId]] = Column.columnToArray(column, implicitly) + implicit lazy val arrayToStatement: ToStatement[Array[PersonId]] = TypoUUID.arrayToStatement.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[PersonId, TypoUUID] = Bijection[PersonId, TypoUUID](_.value)(PersonId.apply) + implicit lazy val column: Column[PersonId] = TypoUUID.column.map(PersonId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[PersonId] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[PersonId] = new ParameterMetaData[PersonId] { + override def sqlType: String = TypoUUID.parameterMetadata.sqlType + override def jdbcType: Int = TypoUUID.parameterMetadata.jdbcType + } + implicit lazy val reads: Reads[PersonId] = TypoUUID.reads.map(PersonId.apply) + implicit lazy val text: Text[PersonId] = new Text[PersonId] { + override def unsafeEncode(v: PersonId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: PersonId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } + implicit lazy val toStatement: ToStatement[PersonId] = TypoUUID.toStatement.contramap(_.value) + implicit lazy val writes: Writes[PersonId] = TypoUUID.writes.contramap(_.value) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonRepo.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonRepo.scala new file mode 100644 index 0000000000..d4557a914c --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonRepo.scala @@ -0,0 +1,35 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import java.sql.Connection +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait PersonRepo { + def delete: DeleteBuilder[PersonFields, PersonRow] + def deleteById(id: PersonId)(implicit c: Connection): Boolean + def deleteByIds(ids: Array[PersonId])(implicit c: Connection): Int + def insert(unsaved: PersonRow)(implicit c: Connection): PersonRow + def insert(unsaved: PersonRowUnsaved)(implicit c: Connection): PersonRow + def insertStreaming(unsaved: Iterator[PersonRow], batchSize: Int = 10000)(implicit c: Connection): Long + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Iterator[PersonRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long + def select: SelectBuilder[PersonFields, PersonRow] + def selectAll(implicit c: Connection): List[PersonRow] + def selectById(id: PersonId)(implicit c: Connection): Option[PersonRow] + def selectByIds(ids: Array[PersonId])(implicit c: Connection): List[PersonRow] + def selectByIdsTracked(ids: Array[PersonId])(implicit c: Connection): Map[PersonId, PersonRow] + def update: UpdateBuilder[PersonFields, PersonRow] + def update(row: PersonRow)(implicit c: Connection): Boolean + def upsert(unsaved: PersonRow)(implicit c: Connection): PersonRow + def upsertBatch(unsaved: Iterable[PersonRow])(implicit c: Connection): List[PersonRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Iterator[PersonRow], batchSize: Int = 10000)(implicit c: Connection): Int +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonRepoImpl.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonRepoImpl.scala new file mode 100644 index 0000000000..8039a6e992 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonRepoImpl.scala @@ -0,0 +1,182 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.address.AddressId +import anorm.BatchSql +import anorm.NamedParameter +import anorm.ParameterValue +import anorm.RowParser +import anorm.SQL +import anorm.SimpleSql +import anorm.SqlStringInterpolation +import anorm.ToStatement +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class PersonRepoImpl extends PersonRepo { + override def delete: DeleteBuilder[PersonFields, PersonRow] = { + DeleteBuilder(""""frontpage"."person"""", PersonFields.structure) + } + override def deleteById(id: PersonId)(implicit c: Connection): Boolean = { + SQL"""delete from "frontpage"."person" where "id" = ${ParameterValue(id, null, PersonId.toStatement)}""".executeUpdate() > 0 + } + override def deleteByIds(ids: Array[PersonId])(implicit c: Connection): Int = { + SQL"""delete + from "frontpage"."person" + where "id" = ANY(${ids}) + """.executeUpdate() + + } + override def insert(unsaved: PersonRow)(implicit c: Connection): PersonRow = { + SQL"""insert into "frontpage"."person"("id", "name", "address_id", "created_at") + values (${ParameterValue(unsaved.id, null, PersonId.toStatement)}::uuid, ${ParameterValue(unsaved.name, null, ToStatement.stringToStatement)}, ${ParameterValue(unsaved.addressId, null, ToStatement.optionToStatement(AddressId.toStatement, AddressId.parameterMetadata))}::uuid, ${ParameterValue(unsaved.createdAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp) + returning "id", "name", "address_id", "created_at"::text + """ + .executeInsert(PersonRow.rowParser(1).single) + + } + override def insert(unsaved: PersonRowUnsaved)(implicit c: Connection): PersonRow = { + val namedParameters = List( + Some((NamedParameter("name", ParameterValue(unsaved.name, null, ToStatement.stringToStatement)), "")), + Some((NamedParameter("address_id", ParameterValue(unsaved.addressId, null, ToStatement.optionToStatement(AddressId.toStatement, AddressId.parameterMetadata))), "::uuid")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("id", ParameterValue(value, null, PersonId.toStatement)), "::uuid")) + }, + unsaved.createdAt match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("created_at", ParameterValue(value, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))), "::timestamp")) + } + ).flatten + val quote = '"'.toString + if (namedParameters.isEmpty) { + SQL"""insert into "frontpage"."person" default values + returning "id", "name", "address_id", "created_at"::text + """ + .executeInsert(PersonRow.rowParser(1).single) + } else { + val q = s"""insert into "frontpage"."person"(${namedParameters.map{case (x, _) => quote + x.name + quote}.mkString(", ")}) + values (${namedParameters.map{ case (np, cast) => s"{${np.name}}$cast"}.mkString(", ")}) + returning "id", "name", "address_id", "created_at"::text + """ + SimpleSql(SQL(q), namedParameters.map { case (np, _) => np.tupled }.toMap, RowParser.successful) + .executeInsert(PersonRow.rowParser(1).single) + } + + } + override def insertStreaming(unsaved: Iterator[PersonRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."person"("id", "name", "address_id", "created_at") FROM STDIN""", batchSize, unsaved)(PersonRow.text, c) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[PersonRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."person"("name", "address_id", "id", "created_at") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(PersonRowUnsaved.text, c) + } + override def select: SelectBuilder[PersonFields, PersonRow] = { + SelectBuilderSql(""""frontpage"."person"""", PersonFields.structure, PersonRow.rowParser) + } + override def selectAll(implicit c: Connection): List[PersonRow] = { + SQL"""select "id", "name", "address_id", "created_at"::text + from "frontpage"."person" + """.as(PersonRow.rowParser(1).*) + } + override def selectById(id: PersonId)(implicit c: Connection): Option[PersonRow] = { + SQL"""select "id", "name", "address_id", "created_at"::text + from "frontpage"."person" + where "id" = ${ParameterValue(id, null, PersonId.toStatement)} + """.as(PersonRow.rowParser(1).singleOpt) + } + override def selectByIds(ids: Array[PersonId])(implicit c: Connection): List[PersonRow] = { + SQL"""select "id", "name", "address_id", "created_at"::text + from "frontpage"."person" + where "id" = ANY(${ids}) + """.as(PersonRow.rowParser(1).*) + + } + override def selectByIdsTracked(ids: Array[PersonId])(implicit c: Connection): Map[PersonId, PersonRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[PersonFields, PersonRow] = { + UpdateBuilder(""""frontpage"."person"""", PersonFields.structure, PersonRow.rowParser) + } + override def update(row: PersonRow)(implicit c: Connection): Boolean = { + val id = row.id + SQL"""update "frontpage"."person" + set "name" = ${ParameterValue(row.name, null, ToStatement.stringToStatement)}, + "address_id" = ${ParameterValue(row.addressId, null, ToStatement.optionToStatement(AddressId.toStatement, AddressId.parameterMetadata))}::uuid, + "created_at" = ${ParameterValue(row.createdAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp + where "id" = ${ParameterValue(id, null, PersonId.toStatement)} + """.executeUpdate() > 0 + } + override def upsert(unsaved: PersonRow)(implicit c: Connection): PersonRow = { + SQL"""insert into "frontpage"."person"("id", "name", "address_id", "created_at") + values ( + ${ParameterValue(unsaved.id, null, PersonId.toStatement)}::uuid, + ${ParameterValue(unsaved.name, null, ToStatement.stringToStatement)}, + ${ParameterValue(unsaved.addressId, null, ToStatement.optionToStatement(AddressId.toStatement, AddressId.parameterMetadata))}::uuid, + ${ParameterValue(unsaved.createdAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "address_id" = EXCLUDED."address_id", + "created_at" = EXCLUDED."created_at" + returning "id", "name", "address_id", "created_at"::text + """ + .executeInsert(PersonRow.rowParser(1).single) + + } + override def upsertBatch(unsaved: Iterable[PersonRow])(implicit c: Connection): List[PersonRow] = { + def toNamedParameter(row: PersonRow): List[NamedParameter] = List( + NamedParameter("id", ParameterValue(row.id, null, PersonId.toStatement)), + NamedParameter("name", ParameterValue(row.name, null, ToStatement.stringToStatement)), + NamedParameter("address_id", ParameterValue(row.addressId, null, ToStatement.optionToStatement(AddressId.toStatement, AddressId.parameterMetadata))), + NamedParameter("created_at", ParameterValue(row.createdAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))) + ) + unsaved.toList match { + case Nil => Nil + case head :: rest => + new anorm.adventureworks.ExecuteReturningSyntax.Ops( + BatchSql( + s"""insert into "frontpage"."person"("id", "name", "address_id", "created_at") + values ({id}::uuid, {name}, {address_id}::uuid, {created_at}::timestamp) + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "address_id" = EXCLUDED."address_id", + "created_at" = EXCLUDED."created_at" + returning "id", "name", "address_id", "created_at"::text + """, + toNamedParameter(head), + rest.map(toNamedParameter)* + ) + ).executeReturning(PersonRow.rowParser(1).*) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[PersonRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + SQL"""create temporary table person_TEMP (like "frontpage"."person") on commit drop""".execute(): @nowarn + streamingInsert(s"""copy person_TEMP("id", "name", "address_id", "created_at") from stdin""", batchSize, unsaved)(PersonRow.text, c): @nowarn + SQL"""insert into "frontpage"."person"("id", "name", "address_id", "created_at") + select * from person_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "address_id" = EXCLUDED."address_id", + "created_at" = EXCLUDED."created_at" + ; + drop table person_TEMP;""".executeUpdate() + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonRepoMock.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonRepoMock.scala new file mode 100644 index 0000000000..deee4569a0 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonRepoMock.scala @@ -0,0 +1,103 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class PersonRepoMock(toRow: Function1[PersonRowUnsaved, PersonRow], + map: scala.collection.mutable.Map[PersonId, PersonRow] = scala.collection.mutable.Map.empty) extends PersonRepo { + override def delete: DeleteBuilder[PersonFields, PersonRow] = { + DeleteBuilderMock(DeleteParams.empty, PersonFields.structure, map) + } + override def deleteById(id: PersonId)(implicit c: Connection): Boolean = { + map.remove(id).isDefined + } + override def deleteByIds(ids: Array[PersonId])(implicit c: Connection): Int = { + ids.map(id => map.remove(id)).count(_.isDefined) + } + override def insert(unsaved: PersonRow)(implicit c: Connection): PersonRow = { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + override def insert(unsaved: PersonRowUnsaved)(implicit c: Connection): PersonRow = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Iterator[PersonRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size.toLong + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[PersonRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + } + unsaved.size.toLong + } + override def select: SelectBuilder[PersonFields, PersonRow] = { + SelectBuilderMock(PersonFields.structure, () => map.values.toList, SelectParams.empty) + } + override def selectAll(implicit c: Connection): List[PersonRow] = { + map.values.toList + } + override def selectById(id: PersonId)(implicit c: Connection): Option[PersonRow] = { + map.get(id) + } + override def selectByIds(ids: Array[PersonId])(implicit c: Connection): List[PersonRow] = { + ids.flatMap(map.get).toList + } + override def selectByIdsTracked(ids: Array[PersonId])(implicit c: Connection): Map[PersonId, PersonRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[PersonFields, PersonRow] = { + UpdateBuilderMock(UpdateParams.empty, PersonFields.structure, map) + } + override def update(row: PersonRow)(implicit c: Connection): Boolean = { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + override def upsert(unsaved: PersonRow)(implicit c: Connection): PersonRow = { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + override def upsertBatch(unsaved: Iterable[PersonRow])(implicit c: Connection): List[PersonRow] = { + unsaved.map { row => + map += (row.id -> row) + row + }.toList + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[PersonRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonRow.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonRow.scala new file mode 100644 index 0000000000..7913929065 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonRow.scala @@ -0,0 +1,79 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.address.AddressId +import anorm.Column +import anorm.RowParser +import anorm.Success +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** Table: frontpage.person + Primary key: id */ +case class PersonRow( + /** Default: gen_random_uuid() */ + id: PersonId, + name: String, + /** Points to [[address.AddressRow.id]] */ + addressId: Option[AddressId], + /** Default: now() */ + createdAt: Option[TypoLocalDateTime] +){ + def toUnsavedRow(id: Defaulted[PersonId], createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.Provided(this.createdAt)): PersonRowUnsaved = + PersonRowUnsaved(name, addressId, id, createdAt) + } + +object PersonRow { + implicit lazy val reads: Reads[PersonRow] = Reads[PersonRow](json => JsResult.fromTry( + Try( + PersonRow( + id = json.\("id").as(PersonId.reads), + name = json.\("name").as(Reads.StringReads), + addressId = json.\("address_id").toOption.map(_.as(AddressId.reads)), + createdAt = json.\("created_at").toOption.map(_.as(TypoLocalDateTime.reads)) + ) + ) + ), + ) + def rowParser(idx: Int): RowParser[PersonRow] = RowParser[PersonRow] { row => + Success( + PersonRow( + id = row(idx + 0)(PersonId.column), + name = row(idx + 1)(Column.columnToString), + addressId = row(idx + 2)(Column.columnToOption(AddressId.column)), + createdAt = row(idx + 3)(Column.columnToOption(TypoLocalDateTime.column)) + ) + ) + } + implicit lazy val text: Text[PersonRow] = Text.instance[PersonRow]{ (row, sb) => + PersonId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(AddressId.text).unsafeEncode(row.addressId, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.createdAt, sb) + } + implicit lazy val writes: OWrites[PersonRow] = OWrites[PersonRow](o => + new JsObject(ListMap[String, JsValue]( + "id" -> PersonId.writes.writes(o.id), + "name" -> Writes.StringWrites.writes(o.name), + "address_id" -> Writes.OptionWrites(AddressId.writes).writes(o.addressId), + "created_at" -> Writes.OptionWrites(TypoLocalDateTime.writes).writes(o.createdAt) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonRowUnsaved.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonRowUnsaved.scala new file mode 100644 index 0000000000..84a1fa3c35 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/person/PersonRowUnsaved.scala @@ -0,0 +1,75 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.address.AddressId +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** This class corresponds to a row in table `frontpage.person` which has not been persisted yet */ +case class PersonRowUnsaved( + name: String, + /** Points to [[address.AddressRow.id]] */ + addressId: Option[AddressId], + /** Default: gen_random_uuid() */ + id: Defaulted[PersonId] = Defaulted.UseDefault, + /** Default: now() */ + createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault +) { + def toRow(idDefault: => PersonId, createdAtDefault: => Option[TypoLocalDateTime]): PersonRow = + PersonRow( + name = name, + addressId = addressId, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + }, + createdAt = createdAt match { + case Defaulted.UseDefault => createdAtDefault + case Defaulted.Provided(value) => value + } + ) +} +object PersonRowUnsaved { + implicit lazy val reads: Reads[PersonRowUnsaved] = Reads[PersonRowUnsaved](json => JsResult.fromTry( + Try( + PersonRowUnsaved( + name = json.\("name").as(Reads.StringReads), + addressId = json.\("address_id").toOption.map(_.as(AddressId.reads)), + id = json.\("id").as(Defaulted.reads(PersonId.reads)), + createdAt = json.\("created_at").as(Defaulted.readsOpt(TypoLocalDateTime.reads)) + ) + ) + ), + ) + implicit lazy val text: Text[PersonRowUnsaved] = Text.instance[PersonRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(AddressId.text).unsafeEncode(row.addressId, sb) + sb.append(Text.DELIMETER) + Defaulted.text(PersonId.text).unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(TypoLocalDateTime.text)).unsafeEncode(row.createdAt, sb) + } + implicit lazy val writes: OWrites[PersonRowUnsaved] = OWrites[PersonRowUnsaved](o => + new JsObject(ListMap[String, JsValue]( + "name" -> Writes.StringWrites.writes(o.name), + "address_id" -> Writes.OptionWrites(AddressId.writes).writes(o.addressId), + "id" -> Defaulted.writes(PersonId.writes).writes(o.id), + "created_at" -> Defaulted.writes(Writes.OptionWrites(TypoLocalDateTime.writes)).writes(o.createdAt) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductFields.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductFields.scala new file mode 100644 index 0000000000..d2c7d735b1 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductFields.scala @@ -0,0 +1,61 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoLocalDateTime +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait ProductFields { + def id: IdField[ProductId, ProductRow] + def name: Field[String, ProductRow] + def price: Field[BigDecimal, ProductRow] + def inStock: OptField[Boolean, ProductRow] + def quantity: OptField[Int, ProductRow] + def lastRestocked: OptField[TypoLocalDateTime, ProductRow] + def lastModified: OptField[TypoLocalDateTime, ProductRow] + def tags: OptField[Array[String], ProductRow] + def categories: OptField[Array[Int], ProductRow] + def prices: OptField[Array[BigDecimal], ProductRow] + def attributes: OptField[Array[TypoJsonb], ProductRow] +} + +object ProductFields { + lazy val structure: Relation[ProductFields, ProductRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[ProductFields, ProductRow] { + + override lazy val fields: ProductFields = new ProductFields { + override def id = IdField[ProductId, ProductRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, ProductRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + override def price = Field[BigDecimal, ProductRow](_path, "price", None, Some("numeric"), x => x.price, (row, value) => row.copy(price = value)) + override def inStock = OptField[Boolean, ProductRow](_path, "in_stock", None, None, x => x.inStock, (row, value) => row.copy(inStock = value)) + override def quantity = OptField[Int, ProductRow](_path, "quantity", None, Some("int4"), x => x.quantity, (row, value) => row.copy(quantity = value)) + override def lastRestocked = OptField[TypoLocalDateTime, ProductRow](_path, "last_restocked", Some("text"), Some("timestamp"), x => x.lastRestocked, (row, value) => row.copy(lastRestocked = value)) + override def lastModified = OptField[TypoLocalDateTime, ProductRow](_path, "last_modified", Some("text"), Some("timestamp"), x => x.lastModified, (row, value) => row.copy(lastModified = value)) + override def tags = OptField[Array[String], ProductRow](_path, "tags", None, Some("text[]"), x => x.tags, (row, value) => row.copy(tags = value)) + override def categories = OptField[Array[Int], ProductRow](_path, "categories", None, Some("int4[]"), x => x.categories, (row, value) => row.copy(categories = value)) + override def prices = OptField[Array[BigDecimal], ProductRow](_path, "prices", None, Some("numeric[]"), x => x.prices, (row, value) => row.copy(prices = value)) + override def attributes = OptField[Array[TypoJsonb], ProductRow](_path, "attributes", None, Some("jsonb[]"), x => x.attributes, (row, value) => row.copy(attributes = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, ProductRow]] = + List[FieldLikeNoHkt[?, ProductRow]](fields.id, fields.name, fields.price, fields.inStock, fields.quantity, fields.lastRestocked, fields.lastModified, fields.tags, fields.categories, fields.prices, fields.attributes) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductId.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductId.scala new file mode 100644 index 0000000000..758b935f8d --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import adventureworks.customtypes.TypoUUID +import anorm.Column +import anorm.ParameterMetaData +import anorm.ToStatement +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.product` */ +case class ProductId(value: TypoUUID) extends AnyVal +object ProductId { + implicit lazy val arrayColumn: Column[Array[ProductId]] = Column.columnToArray(column, implicitly) + implicit lazy val arrayToStatement: ToStatement[Array[ProductId]] = TypoUUID.arrayToStatement.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[ProductId, TypoUUID] = Bijection[ProductId, TypoUUID](_.value)(ProductId.apply) + implicit lazy val column: Column[ProductId] = TypoUUID.column.map(ProductId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[ProductId] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[ProductId] = new ParameterMetaData[ProductId] { + override def sqlType: String = TypoUUID.parameterMetadata.sqlType + override def jdbcType: Int = TypoUUID.parameterMetadata.jdbcType + } + implicit lazy val reads: Reads[ProductId] = TypoUUID.reads.map(ProductId.apply) + implicit lazy val text: Text[ProductId] = new Text[ProductId] { + override def unsafeEncode(v: ProductId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: ProductId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } + implicit lazy val toStatement: ToStatement[ProductId] = TypoUUID.toStatement.contramap(_.value) + implicit lazy val writes: Writes[ProductId] = TypoUUID.writes.contramap(_.value) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductRepo.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductRepo.scala new file mode 100644 index 0000000000..8d362175b1 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductRepo.scala @@ -0,0 +1,35 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import java.sql.Connection +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait ProductRepo { + def delete: DeleteBuilder[ProductFields, ProductRow] + def deleteById(id: ProductId)(implicit c: Connection): Boolean + def deleteByIds(ids: Array[ProductId])(implicit c: Connection): Int + def insert(unsaved: ProductRow)(implicit c: Connection): ProductRow + def insert(unsaved: ProductRowUnsaved)(implicit c: Connection): ProductRow + def insertStreaming(unsaved: Iterator[ProductRow], batchSize: Int = 10000)(implicit c: Connection): Long + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Iterator[ProductRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long + def select: SelectBuilder[ProductFields, ProductRow] + def selectAll(implicit c: Connection): List[ProductRow] + def selectById(id: ProductId)(implicit c: Connection): Option[ProductRow] + def selectByIds(ids: Array[ProductId])(implicit c: Connection): List[ProductRow] + def selectByIdsTracked(ids: Array[ProductId])(implicit c: Connection): Map[ProductId, ProductRow] + def update: UpdateBuilder[ProductFields, ProductRow] + def update(row: ProductRow)(implicit c: Connection): Boolean + def upsert(unsaved: ProductRow)(implicit c: Connection): ProductRow + def upsertBatch(unsaved: Iterable[ProductRow])(implicit c: Connection): List[ProductRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Iterator[ProductRow], batchSize: Int = 10000)(implicit c: Connection): Int +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductRepoImpl.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductRepoImpl.scala new file mode 100644 index 0000000000..b9968aec94 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductRepoImpl.scala @@ -0,0 +1,250 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoLocalDateTime +import anorm.BatchSql +import anorm.NamedParameter +import anorm.ParameterMetaData +import anorm.ParameterValue +import anorm.RowParser +import anorm.SQL +import anorm.SimpleSql +import anorm.SqlStringInterpolation +import anorm.ToStatement +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class ProductRepoImpl extends ProductRepo { + override def delete: DeleteBuilder[ProductFields, ProductRow] = { + DeleteBuilder(""""frontpage"."product"""", ProductFields.structure) + } + override def deleteById(id: ProductId)(implicit c: Connection): Boolean = { + SQL"""delete from "frontpage"."product" where "id" = ${ParameterValue(id, null, ProductId.toStatement)}""".executeUpdate() > 0 + } + override def deleteByIds(ids: Array[ProductId])(implicit c: Connection): Int = { + SQL"""delete + from "frontpage"."product" + where "id" = ANY(${ids}) + """.executeUpdate() + + } + override def insert(unsaved: ProductRow)(implicit c: Connection): ProductRow = { + SQL"""insert into "frontpage"."product"("id", "name", "price", "in_stock", "quantity", "last_restocked", "last_modified", "tags", "categories", "prices", "attributes") + values (${ParameterValue(unsaved.id, null, ProductId.toStatement)}::uuid, ${ParameterValue(unsaved.name, null, ToStatement.stringToStatement)}, ${ParameterValue(unsaved.price, null, ToStatement.scalaBigDecimalToStatement)}::numeric, ${ParameterValue(unsaved.inStock, null, ToStatement.optionToStatement(ToStatement.booleanToStatement, ParameterMetaData.BooleanParameterMetaData))}, ${ParameterValue(unsaved.quantity, null, ToStatement.optionToStatement(ToStatement.intToStatement, ParameterMetaData.IntParameterMetaData))}::int4, ${ParameterValue(unsaved.lastRestocked, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp, ${ParameterValue(unsaved.lastModified, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp, ${ParameterValue(unsaved.tags, null, ToStatement.optionToStatement(ToStatement.arrayToParameter(ParameterMetaData.StringParameterMetaData), adventureworks.arrayParameterMetaData(ParameterMetaData.StringParameterMetaData)))}::text[], ${ParameterValue(unsaved.categories, null, ToStatement.optionToStatement(adventureworks.IntArrayToStatement, adventureworks.arrayParameterMetaData(ParameterMetaData.IntParameterMetaData)))}::int4[], ${ParameterValue(unsaved.prices, null, ToStatement.optionToStatement(adventureworks.BigDecimalArrayToStatement, adventureworks.arrayParameterMetaData(ParameterMetaData.BigDecimalParameterMetaData)))}::numeric[], ${ParameterValue(unsaved.attributes, null, ToStatement.optionToStatement(TypoJsonb.arrayToStatement, adventureworks.arrayParameterMetaData(TypoJsonb.parameterMetadata)))}::jsonb[]) + returning "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes" + """ + .executeInsert(ProductRow.rowParser(1).single) + + } + override def insert(unsaved: ProductRowUnsaved)(implicit c: Connection): ProductRow = { + val namedParameters = List( + Some((NamedParameter("name", ParameterValue(unsaved.name, null, ToStatement.stringToStatement)), "")), + Some((NamedParameter("price", ParameterValue(unsaved.price, null, ToStatement.scalaBigDecimalToStatement)), "::numeric")), + Some((NamedParameter("last_restocked", ParameterValue(unsaved.lastRestocked, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))), "::timestamp")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("id", ParameterValue(value, null, ProductId.toStatement)), "::uuid")) + }, + unsaved.inStock match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("in_stock", ParameterValue(value, null, ToStatement.optionToStatement(ToStatement.booleanToStatement, ParameterMetaData.BooleanParameterMetaData))), "")) + }, + unsaved.quantity match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("quantity", ParameterValue(value, null, ToStatement.optionToStatement(ToStatement.intToStatement, ParameterMetaData.IntParameterMetaData))), "::int4")) + }, + unsaved.lastModified match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("last_modified", ParameterValue(value, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))), "::timestamp")) + }, + unsaved.tags match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("tags", ParameterValue(value, null, ToStatement.optionToStatement(ToStatement.arrayToParameter(ParameterMetaData.StringParameterMetaData), adventureworks.arrayParameterMetaData(ParameterMetaData.StringParameterMetaData)))), "::text[]")) + }, + unsaved.categories match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("categories", ParameterValue(value, null, ToStatement.optionToStatement(adventureworks.IntArrayToStatement, adventureworks.arrayParameterMetaData(ParameterMetaData.IntParameterMetaData)))), "::int4[]")) + }, + unsaved.prices match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("prices", ParameterValue(value, null, ToStatement.optionToStatement(adventureworks.BigDecimalArrayToStatement, adventureworks.arrayParameterMetaData(ParameterMetaData.BigDecimalParameterMetaData)))), "::numeric[]")) + }, + unsaved.attributes match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("attributes", ParameterValue(value, null, ToStatement.optionToStatement(TypoJsonb.arrayToStatement, adventureworks.arrayParameterMetaData(TypoJsonb.parameterMetadata)))), "::jsonb[]")) + } + ).flatten + val quote = '"'.toString + if (namedParameters.isEmpty) { + SQL"""insert into "frontpage"."product" default values + returning "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes" + """ + .executeInsert(ProductRow.rowParser(1).single) + } else { + val q = s"""insert into "frontpage"."product"(${namedParameters.map{case (x, _) => quote + x.name + quote}.mkString(", ")}) + values (${namedParameters.map{ case (np, cast) => s"{${np.name}}$cast"}.mkString(", ")}) + returning "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes" + """ + SimpleSql(SQL(q), namedParameters.map { case (np, _) => np.tupled }.toMap, RowParser.successful) + .executeInsert(ProductRow.rowParser(1).single) + } + + } + override def insertStreaming(unsaved: Iterator[ProductRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."product"("id", "name", "price", "in_stock", "quantity", "last_restocked", "last_modified", "tags", "categories", "prices", "attributes") FROM STDIN""", batchSize, unsaved)(ProductRow.text, c) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[ProductRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."product"("name", "price", "last_restocked", "id", "in_stock", "quantity", "last_modified", "tags", "categories", "prices", "attributes") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(ProductRowUnsaved.text, c) + } + override def select: SelectBuilder[ProductFields, ProductRow] = { + SelectBuilderSql(""""frontpage"."product"""", ProductFields.structure, ProductRow.rowParser) + } + override def selectAll(implicit c: Connection): List[ProductRow] = { + SQL"""select "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes" + from "frontpage"."product" + """.as(ProductRow.rowParser(1).*) + } + override def selectById(id: ProductId)(implicit c: Connection): Option[ProductRow] = { + SQL"""select "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes" + from "frontpage"."product" + where "id" = ${ParameterValue(id, null, ProductId.toStatement)} + """.as(ProductRow.rowParser(1).singleOpt) + } + override def selectByIds(ids: Array[ProductId])(implicit c: Connection): List[ProductRow] = { + SQL"""select "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes" + from "frontpage"."product" + where "id" = ANY(${ids}) + """.as(ProductRow.rowParser(1).*) + + } + override def selectByIdsTracked(ids: Array[ProductId])(implicit c: Connection): Map[ProductId, ProductRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[ProductFields, ProductRow] = { + UpdateBuilder(""""frontpage"."product"""", ProductFields.structure, ProductRow.rowParser) + } + override def update(row: ProductRow)(implicit c: Connection): Boolean = { + val id = row.id + SQL"""update "frontpage"."product" + set "name" = ${ParameterValue(row.name, null, ToStatement.stringToStatement)}, + "price" = ${ParameterValue(row.price, null, ToStatement.scalaBigDecimalToStatement)}::numeric, + "in_stock" = ${ParameterValue(row.inStock, null, ToStatement.optionToStatement(ToStatement.booleanToStatement, ParameterMetaData.BooleanParameterMetaData))}, + "quantity" = ${ParameterValue(row.quantity, null, ToStatement.optionToStatement(ToStatement.intToStatement, ParameterMetaData.IntParameterMetaData))}::int4, + "last_restocked" = ${ParameterValue(row.lastRestocked, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp, + "last_modified" = ${ParameterValue(row.lastModified, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp, + "tags" = ${ParameterValue(row.tags, null, ToStatement.optionToStatement(ToStatement.arrayToParameter(ParameterMetaData.StringParameterMetaData), adventureworks.arrayParameterMetaData(ParameterMetaData.StringParameterMetaData)))}::text[], + "categories" = ${ParameterValue(row.categories, null, ToStatement.optionToStatement(adventureworks.IntArrayToStatement, adventureworks.arrayParameterMetaData(ParameterMetaData.IntParameterMetaData)))}::int4[], + "prices" = ${ParameterValue(row.prices, null, ToStatement.optionToStatement(adventureworks.BigDecimalArrayToStatement, adventureworks.arrayParameterMetaData(ParameterMetaData.BigDecimalParameterMetaData)))}::numeric[], + "attributes" = ${ParameterValue(row.attributes, null, ToStatement.optionToStatement(TypoJsonb.arrayToStatement, adventureworks.arrayParameterMetaData(TypoJsonb.parameterMetadata)))}::jsonb[] + where "id" = ${ParameterValue(id, null, ProductId.toStatement)} + """.executeUpdate() > 0 + } + override def upsert(unsaved: ProductRow)(implicit c: Connection): ProductRow = { + SQL"""insert into "frontpage"."product"("id", "name", "price", "in_stock", "quantity", "last_restocked", "last_modified", "tags", "categories", "prices", "attributes") + values ( + ${ParameterValue(unsaved.id, null, ProductId.toStatement)}::uuid, + ${ParameterValue(unsaved.name, null, ToStatement.stringToStatement)}, + ${ParameterValue(unsaved.price, null, ToStatement.scalaBigDecimalToStatement)}::numeric, + ${ParameterValue(unsaved.inStock, null, ToStatement.optionToStatement(ToStatement.booleanToStatement, ParameterMetaData.BooleanParameterMetaData))}, + ${ParameterValue(unsaved.quantity, null, ToStatement.optionToStatement(ToStatement.intToStatement, ParameterMetaData.IntParameterMetaData))}::int4, + ${ParameterValue(unsaved.lastRestocked, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp, + ${ParameterValue(unsaved.lastModified, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp, + ${ParameterValue(unsaved.tags, null, ToStatement.optionToStatement(ToStatement.arrayToParameter(ParameterMetaData.StringParameterMetaData), adventureworks.arrayParameterMetaData(ParameterMetaData.StringParameterMetaData)))}::text[], + ${ParameterValue(unsaved.categories, null, ToStatement.optionToStatement(adventureworks.IntArrayToStatement, adventureworks.arrayParameterMetaData(ParameterMetaData.IntParameterMetaData)))}::int4[], + ${ParameterValue(unsaved.prices, null, ToStatement.optionToStatement(adventureworks.BigDecimalArrayToStatement, adventureworks.arrayParameterMetaData(ParameterMetaData.BigDecimalParameterMetaData)))}::numeric[], + ${ParameterValue(unsaved.attributes, null, ToStatement.optionToStatement(TypoJsonb.arrayToStatement, adventureworks.arrayParameterMetaData(TypoJsonb.parameterMetadata)))}::jsonb[] + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "price" = EXCLUDED."price", + "in_stock" = EXCLUDED."in_stock", + "quantity" = EXCLUDED."quantity", + "last_restocked" = EXCLUDED."last_restocked", + "last_modified" = EXCLUDED."last_modified", + "tags" = EXCLUDED."tags", + "categories" = EXCLUDED."categories", + "prices" = EXCLUDED."prices", + "attributes" = EXCLUDED."attributes" + returning "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes" + """ + .executeInsert(ProductRow.rowParser(1).single) + + } + override def upsertBatch(unsaved: Iterable[ProductRow])(implicit c: Connection): List[ProductRow] = { + def toNamedParameter(row: ProductRow): List[NamedParameter] = List( + NamedParameter("id", ParameterValue(row.id, null, ProductId.toStatement)), + NamedParameter("name", ParameterValue(row.name, null, ToStatement.stringToStatement)), + NamedParameter("price", ParameterValue(row.price, null, ToStatement.scalaBigDecimalToStatement)), + NamedParameter("in_stock", ParameterValue(row.inStock, null, ToStatement.optionToStatement(ToStatement.booleanToStatement, ParameterMetaData.BooleanParameterMetaData))), + NamedParameter("quantity", ParameterValue(row.quantity, null, ToStatement.optionToStatement(ToStatement.intToStatement, ParameterMetaData.IntParameterMetaData))), + NamedParameter("last_restocked", ParameterValue(row.lastRestocked, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))), + NamedParameter("last_modified", ParameterValue(row.lastModified, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))), + NamedParameter("tags", ParameterValue(row.tags, null, ToStatement.optionToStatement(ToStatement.arrayToParameter(ParameterMetaData.StringParameterMetaData), adventureworks.arrayParameterMetaData(ParameterMetaData.StringParameterMetaData)))), + NamedParameter("categories", ParameterValue(row.categories, null, ToStatement.optionToStatement(adventureworks.IntArrayToStatement, adventureworks.arrayParameterMetaData(ParameterMetaData.IntParameterMetaData)))), + NamedParameter("prices", ParameterValue(row.prices, null, ToStatement.optionToStatement(adventureworks.BigDecimalArrayToStatement, adventureworks.arrayParameterMetaData(ParameterMetaData.BigDecimalParameterMetaData)))), + NamedParameter("attributes", ParameterValue(row.attributes, null, ToStatement.optionToStatement(TypoJsonb.arrayToStatement, adventureworks.arrayParameterMetaData(TypoJsonb.parameterMetadata)))) + ) + unsaved.toList match { + case Nil => Nil + case head :: rest => + new anorm.adventureworks.ExecuteReturningSyntax.Ops( + BatchSql( + s"""insert into "frontpage"."product"("id", "name", "price", "in_stock", "quantity", "last_restocked", "last_modified", "tags", "categories", "prices", "attributes") + values ({id}::uuid, {name}, {price}::numeric, {in_stock}, {quantity}::int4, {last_restocked}::timestamp, {last_modified}::timestamp, {tags}::text[], {categories}::int4[], {prices}::numeric[], {attributes}::jsonb[]) + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "price" = EXCLUDED."price", + "in_stock" = EXCLUDED."in_stock", + "quantity" = EXCLUDED."quantity", + "last_restocked" = EXCLUDED."last_restocked", + "last_modified" = EXCLUDED."last_modified", + "tags" = EXCLUDED."tags", + "categories" = EXCLUDED."categories", + "prices" = EXCLUDED."prices", + "attributes" = EXCLUDED."attributes" + returning "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes" + """, + toNamedParameter(head), + rest.map(toNamedParameter)* + ) + ).executeReturning(ProductRow.rowParser(1).*) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[ProductRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + SQL"""create temporary table product_TEMP (like "frontpage"."product") on commit drop""".execute(): @nowarn + streamingInsert(s"""copy product_TEMP("id", "name", "price", "in_stock", "quantity", "last_restocked", "last_modified", "tags", "categories", "prices", "attributes") from stdin""", batchSize, unsaved)(ProductRow.text, c): @nowarn + SQL"""insert into "frontpage"."product"("id", "name", "price", "in_stock", "quantity", "last_restocked", "last_modified", "tags", "categories", "prices", "attributes") + select * from product_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "price" = EXCLUDED."price", + "in_stock" = EXCLUDED."in_stock", + "quantity" = EXCLUDED."quantity", + "last_restocked" = EXCLUDED."last_restocked", + "last_modified" = EXCLUDED."last_modified", + "tags" = EXCLUDED."tags", + "categories" = EXCLUDED."categories", + "prices" = EXCLUDED."prices", + "attributes" = EXCLUDED."attributes" + ; + drop table product_TEMP;""".executeUpdate() + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductRepoMock.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductRepoMock.scala new file mode 100644 index 0000000000..b6c91f8f41 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductRepoMock.scala @@ -0,0 +1,103 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class ProductRepoMock(toRow: Function1[ProductRowUnsaved, ProductRow], + map: scala.collection.mutable.Map[ProductId, ProductRow] = scala.collection.mutable.Map.empty) extends ProductRepo { + override def delete: DeleteBuilder[ProductFields, ProductRow] = { + DeleteBuilderMock(DeleteParams.empty, ProductFields.structure, map) + } + override def deleteById(id: ProductId)(implicit c: Connection): Boolean = { + map.remove(id).isDefined + } + override def deleteByIds(ids: Array[ProductId])(implicit c: Connection): Int = { + ids.map(id => map.remove(id)).count(_.isDefined) + } + override def insert(unsaved: ProductRow)(implicit c: Connection): ProductRow = { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + override def insert(unsaved: ProductRowUnsaved)(implicit c: Connection): ProductRow = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Iterator[ProductRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size.toLong + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[ProductRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + } + unsaved.size.toLong + } + override def select: SelectBuilder[ProductFields, ProductRow] = { + SelectBuilderMock(ProductFields.structure, () => map.values.toList, SelectParams.empty) + } + override def selectAll(implicit c: Connection): List[ProductRow] = { + map.values.toList + } + override def selectById(id: ProductId)(implicit c: Connection): Option[ProductRow] = { + map.get(id) + } + override def selectByIds(ids: Array[ProductId])(implicit c: Connection): List[ProductRow] = { + ids.flatMap(map.get).toList + } + override def selectByIdsTracked(ids: Array[ProductId])(implicit c: Connection): Map[ProductId, ProductRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[ProductFields, ProductRow] = { + UpdateBuilderMock(UpdateParams.empty, ProductFields.structure, map) + } + override def update(row: ProductRow)(implicit c: Connection): Boolean = { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + override def upsert(unsaved: ProductRow)(implicit c: Connection): ProductRow = { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + override def upsertBatch(unsaved: Iterable[ProductRow])(implicit c: Connection): List[ProductRow] = { + unsaved.map { row => + map += (row.id -> row) + row + }.toList + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[ProductRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductRow.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductRow.scala new file mode 100644 index 0000000000..fdefe68123 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductRow.scala @@ -0,0 +1,126 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoLocalDateTime +import anorm.Column +import anorm.RowParser +import anorm.Success +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** Table: frontpage.product + Primary key: id */ +case class ProductRow( + /** Default: gen_random_uuid() */ + id: ProductId, + name: String, + price: BigDecimal, + /** Default: true */ + inStock: Option[Boolean], + /** Default: 0 */ + quantity: Option[Int], + lastRestocked: Option[TypoLocalDateTime], + /** Default: now() */ + lastModified: Option[TypoLocalDateTime], + /** Default: '{}'::text[] */ + tags: Option[Array[String]], + /** Default: '{}'::integer[] */ + categories: Option[Array[Int]], + /** Default: '{}'::numeric[] */ + prices: Option[Array[BigDecimal]], + /** Default: '{}'::jsonb[] */ + attributes: Option[Array[TypoJsonb]] +){ + def toUnsavedRow(id: Defaulted[ProductId], inStock: Defaulted[Option[Boolean]] = Defaulted.Provided(this.inStock), quantity: Defaulted[Option[Int]] = Defaulted.Provided(this.quantity), lastModified: Defaulted[Option[TypoLocalDateTime]] = Defaulted.Provided(this.lastModified), tags: Defaulted[Option[Array[String]]] = Defaulted.Provided(this.tags), categories: Defaulted[Option[Array[Int]]] = Defaulted.Provided(this.categories), prices: Defaulted[Option[Array[BigDecimal]]] = Defaulted.Provided(this.prices), attributes: Defaulted[Option[Array[TypoJsonb]]] = Defaulted.Provided(this.attributes)): ProductRowUnsaved = + ProductRowUnsaved(name, price, lastRestocked, id, inStock, quantity, lastModified, tags, categories, prices, attributes) + } + +object ProductRow { + implicit lazy val reads: Reads[ProductRow] = Reads[ProductRow](json => JsResult.fromTry( + Try( + ProductRow( + id = json.\("id").as(ProductId.reads), + name = json.\("name").as(Reads.StringReads), + price = json.\("price").as(Reads.bigDecReads), + inStock = json.\("in_stock").toOption.map(_.as(Reads.BooleanReads)), + quantity = json.\("quantity").toOption.map(_.as(Reads.IntReads)), + lastRestocked = json.\("last_restocked").toOption.map(_.as(TypoLocalDateTime.reads)), + lastModified = json.\("last_modified").toOption.map(_.as(TypoLocalDateTime.reads)), + tags = json.\("tags").toOption.map(_.as(Reads.ArrayReads[String](using Reads.StringReads, implicitly))), + categories = json.\("categories").toOption.map(_.as(Reads.ArrayReads[Int](using Reads.IntReads, implicitly))), + prices = json.\("prices").toOption.map(_.as(Reads.ArrayReads[BigDecimal](using Reads.bigDecReads, implicitly))), + attributes = json.\("attributes").toOption.map(_.as(Reads.ArrayReads[TypoJsonb](using TypoJsonb.reads, implicitly))) + ) + ) + ), + ) + def rowParser(idx: Int): RowParser[ProductRow] = RowParser[ProductRow] { row => + Success( + ProductRow( + id = row(idx + 0)(ProductId.column), + name = row(idx + 1)(Column.columnToString), + price = row(idx + 2)(Column.columnToScalaBigDecimal), + inStock = row(idx + 3)(Column.columnToOption(Column.columnToBoolean)), + quantity = row(idx + 4)(Column.columnToOption(Column.columnToInt)), + lastRestocked = row(idx + 5)(Column.columnToOption(TypoLocalDateTime.column)), + lastModified = row(idx + 6)(Column.columnToOption(TypoLocalDateTime.column)), + tags = row(idx + 7)(Column.columnToOption(Column.columnToArray[String](Column.columnToString, implicitly))), + categories = row(idx + 8)(Column.columnToOption(Column.columnToArray[Int](Column.columnToInt, implicitly))), + prices = row(idx + 9)(Column.columnToOption(Column.columnToArray[BigDecimal](Column.columnToScalaBigDecimal, implicitly))), + attributes = row(idx + 10)(Column.columnToOption(TypoJsonb.arrayColumn)) + ) + ) + } + implicit lazy val text: Text[ProductRow] = Text.instance[ProductRow]{ (row, sb) => + ProductId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.bigDecimalInstance.unsafeEncode(row.price, sb) + sb.append(Text.DELIMETER) + Text.option(Text.booleanInstance).unsafeEncode(row.inStock, sb) + sb.append(Text.DELIMETER) + Text.option(Text.intInstance).unsafeEncode(row.quantity, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.lastRestocked, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.lastModified, sb) + sb.append(Text.DELIMETER) + Text.option(Text[Array[String]]).unsafeEncode(row.tags, sb) + sb.append(Text.DELIMETER) + Text.option(Text[Array[Int]]).unsafeEncode(row.categories, sb) + sb.append(Text.DELIMETER) + Text.option(Text[Array[BigDecimal]]).unsafeEncode(row.prices, sb) + sb.append(Text.DELIMETER) + Text.option(Text.iterableInstance[Array, TypoJsonb](TypoJsonb.text, implicitly)).unsafeEncode(row.attributes, sb) + } + implicit lazy val writes: OWrites[ProductRow] = OWrites[ProductRow](o => + new JsObject(ListMap[String, JsValue]( + "id" -> ProductId.writes.writes(o.id), + "name" -> Writes.StringWrites.writes(o.name), + "price" -> Writes.BigDecimalWrites.writes(o.price), + "in_stock" -> Writes.OptionWrites(Writes.BooleanWrites).writes(o.inStock), + "quantity" -> Writes.OptionWrites(Writes.IntWrites).writes(o.quantity), + "last_restocked" -> Writes.OptionWrites(TypoLocalDateTime.writes).writes(o.lastRestocked), + "last_modified" -> Writes.OptionWrites(TypoLocalDateTime.writes).writes(o.lastModified), + "tags" -> Writes.OptionWrites(Writes.arrayWrites[String](using implicitly, Writes.StringWrites)).writes(o.tags), + "categories" -> Writes.OptionWrites(Writes.arrayWrites[Int](using implicitly, Writes.IntWrites)).writes(o.categories), + "prices" -> Writes.OptionWrites(Writes.arrayWrites[BigDecimal](using implicitly, Writes.BigDecimalWrites)).writes(o.prices), + "attributes" -> Writes.OptionWrites(Writes.arrayWrites[TypoJsonb](using implicitly, TypoJsonb.writes)).writes(o.attributes) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductRowUnsaved.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductRowUnsaved.scala new file mode 100644 index 0000000000..e5ab989002 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product/ProductRowUnsaved.scala @@ -0,0 +1,140 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoLocalDateTime +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** This class corresponds to a row in table `frontpage.product` which has not been persisted yet */ +case class ProductRowUnsaved( + name: String, + price: BigDecimal, + lastRestocked: Option[TypoLocalDateTime], + /** Default: gen_random_uuid() */ + id: Defaulted[ProductId] = Defaulted.UseDefault, + /** Default: true */ + inStock: Defaulted[Option[Boolean]] = Defaulted.UseDefault, + /** Default: 0 */ + quantity: Defaulted[Option[Int]] = Defaulted.UseDefault, + /** Default: now() */ + lastModified: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault, + /** Default: '{}'::text[] */ + tags: Defaulted[Option[Array[String]]] = Defaulted.UseDefault, + /** Default: '{}'::integer[] */ + categories: Defaulted[Option[Array[Int]]] = Defaulted.UseDefault, + /** Default: '{}'::numeric[] */ + prices: Defaulted[Option[Array[BigDecimal]]] = Defaulted.UseDefault, + /** Default: '{}'::jsonb[] */ + attributes: Defaulted[Option[Array[TypoJsonb]]] = Defaulted.UseDefault +) { + def toRow(idDefault: => ProductId, inStockDefault: => Option[Boolean], quantityDefault: => Option[Int], lastModifiedDefault: => Option[TypoLocalDateTime], tagsDefault: => Option[Array[String]], categoriesDefault: => Option[Array[Int]], pricesDefault: => Option[Array[BigDecimal]], attributesDefault: => Option[Array[TypoJsonb]]): ProductRow = + ProductRow( + name = name, + price = price, + lastRestocked = lastRestocked, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + }, + inStock = inStock match { + case Defaulted.UseDefault => inStockDefault + case Defaulted.Provided(value) => value + }, + quantity = quantity match { + case Defaulted.UseDefault => quantityDefault + case Defaulted.Provided(value) => value + }, + lastModified = lastModified match { + case Defaulted.UseDefault => lastModifiedDefault + case Defaulted.Provided(value) => value + }, + tags = tags match { + case Defaulted.UseDefault => tagsDefault + case Defaulted.Provided(value) => value + }, + categories = categories match { + case Defaulted.UseDefault => categoriesDefault + case Defaulted.Provided(value) => value + }, + prices = prices match { + case Defaulted.UseDefault => pricesDefault + case Defaulted.Provided(value) => value + }, + attributes = attributes match { + case Defaulted.UseDefault => attributesDefault + case Defaulted.Provided(value) => value + } + ) +} +object ProductRowUnsaved { + implicit lazy val reads: Reads[ProductRowUnsaved] = Reads[ProductRowUnsaved](json => JsResult.fromTry( + Try( + ProductRowUnsaved( + name = json.\("name").as(Reads.StringReads), + price = json.\("price").as(Reads.bigDecReads), + lastRestocked = json.\("last_restocked").toOption.map(_.as(TypoLocalDateTime.reads)), + id = json.\("id").as(Defaulted.reads(ProductId.reads)), + inStock = json.\("in_stock").as(Defaulted.readsOpt(Reads.BooleanReads)), + quantity = json.\("quantity").as(Defaulted.readsOpt(Reads.IntReads)), + lastModified = json.\("last_modified").as(Defaulted.readsOpt(TypoLocalDateTime.reads)), + tags = json.\("tags").as(Defaulted.readsOpt(Reads.ArrayReads[String](using Reads.StringReads, implicitly))), + categories = json.\("categories").as(Defaulted.readsOpt(Reads.ArrayReads[Int](using Reads.IntReads, implicitly))), + prices = json.\("prices").as(Defaulted.readsOpt(Reads.ArrayReads[BigDecimal](using Reads.bigDecReads, implicitly))), + attributes = json.\("attributes").as(Defaulted.readsOpt(Reads.ArrayReads[TypoJsonb](using TypoJsonb.reads, implicitly))) + ) + ) + ), + ) + implicit lazy val text: Text[ProductRowUnsaved] = Text.instance[ProductRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.bigDecimalInstance.unsafeEncode(row.price, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.lastRestocked, sb) + sb.append(Text.DELIMETER) + Defaulted.text(ProductId.text).unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text.booleanInstance)).unsafeEncode(row.inStock, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text.intInstance)).unsafeEncode(row.quantity, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(TypoLocalDateTime.text)).unsafeEncode(row.lastModified, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text[Array[String]])).unsafeEncode(row.tags, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text[Array[Int]])).unsafeEncode(row.categories, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text[Array[BigDecimal]])).unsafeEncode(row.prices, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text.iterableInstance[Array, TypoJsonb](TypoJsonb.text, implicitly))).unsafeEncode(row.attributes, sb) + } + implicit lazy val writes: OWrites[ProductRowUnsaved] = OWrites[ProductRowUnsaved](o => + new JsObject(ListMap[String, JsValue]( + "name" -> Writes.StringWrites.writes(o.name), + "price" -> Writes.BigDecimalWrites.writes(o.price), + "last_restocked" -> Writes.OptionWrites(TypoLocalDateTime.writes).writes(o.lastRestocked), + "id" -> Defaulted.writes(ProductId.writes).writes(o.id), + "in_stock" -> Defaulted.writes(Writes.OptionWrites(Writes.BooleanWrites)).writes(o.inStock), + "quantity" -> Defaulted.writes(Writes.OptionWrites(Writes.IntWrites)).writes(o.quantity), + "last_modified" -> Defaulted.writes(Writes.OptionWrites(TypoLocalDateTime.writes)).writes(o.lastModified), + "tags" -> Defaulted.writes(Writes.OptionWrites(Writes.arrayWrites[String](using implicitly, Writes.StringWrites))).writes(o.tags), + "categories" -> Defaulted.writes(Writes.OptionWrites(Writes.arrayWrites[Int](using implicitly, Writes.IntWrites))).writes(o.categories), + "prices" -> Defaulted.writes(Writes.OptionWrites(Writes.arrayWrites[BigDecimal](using implicitly, Writes.BigDecimalWrites))).writes(o.prices), + "attributes" -> Defaulted.writes(Writes.OptionWrites(Writes.arrayWrites[TypoJsonb](using implicitly, TypoJsonb.writes))).writes(o.attributes) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryFields.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryFields.scala new file mode 100644 index 0000000000..bfbb0997fc --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryFields.scala @@ -0,0 +1,61 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product_category + +import adventureworks.frontpage.category.CategoryFields +import adventureworks.frontpage.category.CategoryId +import adventureworks.frontpage.category.CategoryRow +import adventureworks.frontpage.product.ProductFields +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.product.ProductRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.Required +import typo.dsl.SqlExpr +import typo.dsl.SqlExpr.CompositeIn +import typo.dsl.SqlExpr.CompositeIn.TuplePart +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.Structure.Relation + +trait ProductCategoryFields { + def productId: IdField[ProductId, ProductCategoryRow] + def categoryId: IdField[CategoryId, ProductCategoryRow] + def fkCategory: ForeignKey[CategoryFields, CategoryRow] = + ForeignKey[CategoryFields, CategoryRow]("frontpage.product_category_category_id_fkey", Nil) + .withColumnPair(categoryId, _.id) + def fkProduct: ForeignKey[ProductFields, ProductRow] = + ForeignKey[ProductFields, ProductRow]("frontpage.product_category_product_id_fkey", Nil) + .withColumnPair(productId, _.id) + def compositeIdIs(compositeId: ProductCategoryId): SqlExpr[Boolean, Required] = + productId.isEqual(compositeId.productId).and(categoryId.isEqual(compositeId.categoryId)) + def compositeIdIn(compositeIds: Array[ProductCategoryId]): SqlExpr[Boolean, Required] = + new CompositeIn(compositeIds)(TuplePart(productId)(_.productId), TuplePart(categoryId)(_.categoryId)) + +} + +object ProductCategoryFields { + lazy val structure: Relation[ProductCategoryFields, ProductCategoryRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[ProductCategoryFields, ProductCategoryRow] { + + override lazy val fields: ProductCategoryFields = new ProductCategoryFields { + override def productId = IdField[ProductId, ProductCategoryRow](_path, "product_id", None, Some("uuid"), x => x.productId, (row, value) => row.copy(productId = value)) + override def categoryId = IdField[CategoryId, ProductCategoryRow](_path, "category_id", None, Some("uuid"), x => x.categoryId, (row, value) => row.copy(categoryId = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, ProductCategoryRow]] = + List[FieldLikeNoHkt[?, ProductCategoryRow]](fields.productId, fields.categoryId) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryId.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryId.scala new file mode 100644 index 0000000000..1620800f46 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryId.scala @@ -0,0 +1,42 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product_category + +import adventureworks.frontpage.category.CategoryId +import adventureworks.frontpage.product.ProductId +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import scala.collection.immutable.ListMap +import scala.util.Try + +/** Type for the composite primary key of table `frontpage.product_category` */ +case class ProductCategoryId( + productId: ProductId, + categoryId: CategoryId +) +object ProductCategoryId { + implicit lazy val ordering: Ordering[ProductCategoryId] = Ordering.by(x => (x.productId, x.categoryId)) + implicit lazy val reads: Reads[ProductCategoryId] = Reads[ProductCategoryId](json => JsResult.fromTry( + Try( + ProductCategoryId( + productId = json.\("product_id").as(ProductId.reads), + categoryId = json.\("category_id").as(CategoryId.reads) + ) + ) + ), + ) + implicit lazy val writes: OWrites[ProductCategoryId] = OWrites[ProductCategoryId](o => + new JsObject(ListMap[String, JsValue]( + "product_id" -> ProductId.writes.writes(o.productId), + "category_id" -> CategoryId.writes.writes(o.categoryId) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepo.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepo.scala new file mode 100644 index 0000000000..bd5421bb75 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepo.scala @@ -0,0 +1,31 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product_category + +import java.sql.Connection +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait ProductCategoryRepo { + def delete: DeleteBuilder[ProductCategoryFields, ProductCategoryRow] + def deleteById(compositeId: ProductCategoryId)(implicit c: Connection): Boolean + def deleteByIds(compositeIds: Array[ProductCategoryId])(implicit c: Connection): Int + def insert(unsaved: ProductCategoryRow)(implicit c: Connection): ProductCategoryRow + def insertStreaming(unsaved: Iterator[ProductCategoryRow], batchSize: Int = 10000)(implicit c: Connection): Long + def select: SelectBuilder[ProductCategoryFields, ProductCategoryRow] + def selectAll(implicit c: Connection): List[ProductCategoryRow] + def selectById(compositeId: ProductCategoryId)(implicit c: Connection): Option[ProductCategoryRow] + def selectByIds(compositeIds: Array[ProductCategoryId])(implicit c: Connection): List[ProductCategoryRow] + def selectByIdsTracked(compositeIds: Array[ProductCategoryId])(implicit c: Connection): Map[ProductCategoryId, ProductCategoryRow] + def update: UpdateBuilder[ProductCategoryFields, ProductCategoryRow] + def upsert(unsaved: ProductCategoryRow)(implicit c: Connection): ProductCategoryRow + def upsertBatch(unsaved: Iterable[ProductCategoryRow])(implicit c: Connection): List[ProductCategoryRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Iterator[ProductCategoryRow], batchSize: Int = 10000)(implicit c: Connection): Int +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepoImpl.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepoImpl.scala new file mode 100644 index 0000000000..4f9c3afb9b --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepoImpl.scala @@ -0,0 +1,128 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product_category + +import adventureworks.frontpage.category.CategoryId +import adventureworks.frontpage.product.ProductId +import anorm.BatchSql +import anorm.NamedParameter +import anorm.ParameterValue +import anorm.SqlStringInterpolation +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class ProductCategoryRepoImpl extends ProductCategoryRepo { + override def delete: DeleteBuilder[ProductCategoryFields, ProductCategoryRow] = { + DeleteBuilder(""""frontpage"."product_category"""", ProductCategoryFields.structure) + } + override def deleteById(compositeId: ProductCategoryId)(implicit c: Connection): Boolean = { + SQL"""delete from "frontpage"."product_category" where "product_id" = ${ParameterValue(compositeId.productId, null, ProductId.toStatement)} AND "category_id" = ${ParameterValue(compositeId.categoryId, null, CategoryId.toStatement)}""".executeUpdate() > 0 + } + override def deleteByIds(compositeIds: Array[ProductCategoryId])(implicit c: Connection): Int = { + val productId = compositeIds.map(_.productId) + val categoryId = compositeIds.map(_.categoryId) + SQL"""delete + from "frontpage"."product_category" + where ("product_id", "category_id") + in (select unnest(${productId}), unnest(${categoryId})) + """.executeUpdate() + + } + override def insert(unsaved: ProductCategoryRow)(implicit c: Connection): ProductCategoryRow = { + SQL"""insert into "frontpage"."product_category"("product_id", "category_id") + values (${ParameterValue(unsaved.productId, null, ProductId.toStatement)}::uuid, ${ParameterValue(unsaved.categoryId, null, CategoryId.toStatement)}::uuid) + returning "product_id", "category_id" + """ + .executeInsert(ProductCategoryRow.rowParser(1).single) + + } + override def insertStreaming(unsaved: Iterator[ProductCategoryRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."product_category"("product_id", "category_id") FROM STDIN""", batchSize, unsaved)(ProductCategoryRow.text, c) + } + override def select: SelectBuilder[ProductCategoryFields, ProductCategoryRow] = { + SelectBuilderSql(""""frontpage"."product_category"""", ProductCategoryFields.structure, ProductCategoryRow.rowParser) + } + override def selectAll(implicit c: Connection): List[ProductCategoryRow] = { + SQL"""select "product_id", "category_id" + from "frontpage"."product_category" + """.as(ProductCategoryRow.rowParser(1).*) + } + override def selectById(compositeId: ProductCategoryId)(implicit c: Connection): Option[ProductCategoryRow] = { + SQL"""select "product_id", "category_id" + from "frontpage"."product_category" + where "product_id" = ${ParameterValue(compositeId.productId, null, ProductId.toStatement)} AND "category_id" = ${ParameterValue(compositeId.categoryId, null, CategoryId.toStatement)} + """.as(ProductCategoryRow.rowParser(1).singleOpt) + } + override def selectByIds(compositeIds: Array[ProductCategoryId])(implicit c: Connection): List[ProductCategoryRow] = { + val productId = compositeIds.map(_.productId) + val categoryId = compositeIds.map(_.categoryId) + SQL"""select "product_id", "category_id" + from "frontpage"."product_category" + where ("product_id", "category_id") + in (select unnest(${productId}), unnest(${categoryId})) + """.as(ProductCategoryRow.rowParser(1).*) + + } + override def selectByIdsTracked(compositeIds: Array[ProductCategoryId])(implicit c: Connection): Map[ProductCategoryId, ProductCategoryRow] = { + val byId = selectByIds(compositeIds).view.map(x => (x.compositeId, x)).toMap + compositeIds.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[ProductCategoryFields, ProductCategoryRow] = { + UpdateBuilder(""""frontpage"."product_category"""", ProductCategoryFields.structure, ProductCategoryRow.rowParser) + } + override def upsert(unsaved: ProductCategoryRow)(implicit c: Connection): ProductCategoryRow = { + SQL"""insert into "frontpage"."product_category"("product_id", "category_id") + values ( + ${ParameterValue(unsaved.productId, null, ProductId.toStatement)}::uuid, + ${ParameterValue(unsaved.categoryId, null, CategoryId.toStatement)}::uuid + ) + on conflict ("product_id", "category_id") + do update set "product_id" = EXCLUDED."product_id" + returning "product_id", "category_id" + """ + .executeInsert(ProductCategoryRow.rowParser(1).single) + + } + override def upsertBatch(unsaved: Iterable[ProductCategoryRow])(implicit c: Connection): List[ProductCategoryRow] = { + def toNamedParameter(row: ProductCategoryRow): List[NamedParameter] = List( + NamedParameter("product_id", ParameterValue(row.productId, null, ProductId.toStatement)), + NamedParameter("category_id", ParameterValue(row.categoryId, null, CategoryId.toStatement)) + ) + unsaved.toList match { + case Nil => Nil + case head :: rest => + new anorm.adventureworks.ExecuteReturningSyntax.Ops( + BatchSql( + s"""insert into "frontpage"."product_category"("product_id", "category_id") + values ({product_id}::uuid, {category_id}::uuid) + on conflict ("product_id", "category_id") + do nothing + returning "product_id", "category_id" + """, + toNamedParameter(head), + rest.map(toNamedParameter)* + ) + ).executeReturning(ProductCategoryRow.rowParser(1).*) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[ProductCategoryRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + SQL"""create temporary table product_category_TEMP (like "frontpage"."product_category") on commit drop""".execute(): @nowarn + streamingInsert(s"""copy product_category_TEMP("product_id", "category_id") from stdin""", batchSize, unsaved)(ProductCategoryRow.text, c): @nowarn + SQL"""insert into "frontpage"."product_category"("product_id", "category_id") + select * from product_category_TEMP + on conflict ("product_id", "category_id") + do nothing + ; + drop table product_category_TEMP;""".executeUpdate() + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepoMock.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepoMock.scala new file mode 100644 index 0000000000..68ea553d66 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepoMock.scala @@ -0,0 +1,82 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product_category + +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class ProductCategoryRepoMock(map: scala.collection.mutable.Map[ProductCategoryId, ProductCategoryRow] = scala.collection.mutable.Map.empty) extends ProductCategoryRepo { + override def delete: DeleteBuilder[ProductCategoryFields, ProductCategoryRow] = { + DeleteBuilderMock(DeleteParams.empty, ProductCategoryFields.structure, map) + } + override def deleteById(compositeId: ProductCategoryId)(implicit c: Connection): Boolean = { + map.remove(compositeId).isDefined + } + override def deleteByIds(compositeIds: Array[ProductCategoryId])(implicit c: Connection): Int = { + compositeIds.map(id => map.remove(id)).count(_.isDefined) + } + override def insert(unsaved: ProductCategoryRow)(implicit c: Connection): ProductCategoryRow = { + val _ = if (map.contains(unsaved.compositeId)) + sys.error(s"id ${unsaved.compositeId} already exists") + else + map.put(unsaved.compositeId, unsaved) + + unsaved + } + override def insertStreaming(unsaved: Iterator[ProductCategoryRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { row => + map += (row.compositeId -> row) + } + unsaved.size.toLong + } + override def select: SelectBuilder[ProductCategoryFields, ProductCategoryRow] = { + SelectBuilderMock(ProductCategoryFields.structure, () => map.values.toList, SelectParams.empty) + } + override def selectAll(implicit c: Connection): List[ProductCategoryRow] = { + map.values.toList + } + override def selectById(compositeId: ProductCategoryId)(implicit c: Connection): Option[ProductCategoryRow] = { + map.get(compositeId) + } + override def selectByIds(compositeIds: Array[ProductCategoryId])(implicit c: Connection): List[ProductCategoryRow] = { + compositeIds.flatMap(map.get).toList + } + override def selectByIdsTracked(compositeIds: Array[ProductCategoryId])(implicit c: Connection): Map[ProductCategoryId, ProductCategoryRow] = { + val byId = selectByIds(compositeIds).view.map(x => (x.compositeId, x)).toMap + compositeIds.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[ProductCategoryFields, ProductCategoryRow] = { + UpdateBuilderMock(UpdateParams.empty, ProductCategoryFields.structure, map) + } + override def upsert(unsaved: ProductCategoryRow)(implicit c: Connection): ProductCategoryRow = { + map.put(unsaved.compositeId, unsaved): @nowarn + unsaved + } + override def upsertBatch(unsaved: Iterable[ProductCategoryRow])(implicit c: Connection): List[ProductCategoryRow] = { + unsaved.map { row => + map += (row.compositeId -> row) + row + }.toList + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[ProductCategoryRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + unsaved.foreach { row => + map += (row.compositeId -> row) + } + unsaved.size + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRow.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRow.scala new file mode 100644 index 0000000000..bbe90b7a7f --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRow.scala @@ -0,0 +1,65 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product_category + +import adventureworks.frontpage.category.CategoryId +import adventureworks.frontpage.product.ProductId +import anorm.RowParser +import anorm.Success +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import scala.collection.immutable.ListMap +import scala.util.Try + +/** Table: frontpage.product_category + Composite primary key: product_id, category_id */ +case class ProductCategoryRow( + /** Points to [[product.ProductRow.id]] */ + productId: ProductId, + /** Points to [[category.CategoryRow.id]] */ + categoryId: CategoryId +){ + val compositeId: ProductCategoryId = ProductCategoryId(productId, categoryId) + val id = compositeId + } + +object ProductCategoryRow { + def apply(compositeId: ProductCategoryId) = + new ProductCategoryRow(compositeId.productId, compositeId.categoryId) + implicit lazy val reads: Reads[ProductCategoryRow] = Reads[ProductCategoryRow](json => JsResult.fromTry( + Try( + ProductCategoryRow( + productId = json.\("product_id").as(ProductId.reads), + categoryId = json.\("category_id").as(CategoryId.reads) + ) + ) + ), + ) + def rowParser(idx: Int): RowParser[ProductCategoryRow] = RowParser[ProductCategoryRow] { row => + Success( + ProductCategoryRow( + productId = row(idx + 0)(ProductId.column), + categoryId = row(idx + 1)(CategoryId.column) + ) + ) + } + implicit lazy val text: Text[ProductCategoryRow] = Text.instance[ProductCategoryRow]{ (row, sb) => + ProductId.text.unsafeEncode(row.productId, sb) + sb.append(Text.DELIMETER) + CategoryId.text.unsafeEncode(row.categoryId, sb) + } + implicit lazy val writes: OWrites[ProductCategoryRow] = OWrites[ProductCategoryRow](o => + new JsObject(ListMap[String, JsValue]( + "product_id" -> ProductId.writes.writes(o.productId), + "category_id" -> CategoryId.writes.writes(o.categoryId) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleFields.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleFields.scala new file mode 100644 index 0000000000..5eb3c827f7 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleFields.scala @@ -0,0 +1,40 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.Structure.Relation + +trait RoleFields { + def id: IdField[RoleId, RoleRow] + def name: Field[String, RoleRow] +} + +object RoleFields { + lazy val structure: Relation[RoleFields, RoleRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[RoleFields, RoleRow] { + + override lazy val fields: RoleFields = new RoleFields { + override def id = IdField[RoleId, RoleRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, RoleRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, RoleRow]] = + List[FieldLikeNoHkt[?, RoleRow]](fields.id, fields.name) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleId.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleId.scala new file mode 100644 index 0000000000..a7e165e34a --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import adventureworks.customtypes.TypoUUID +import anorm.Column +import anorm.ParameterMetaData +import anorm.ToStatement +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.role` */ +case class RoleId(value: TypoUUID) extends AnyVal +object RoleId { + implicit lazy val arrayColumn: Column[Array[RoleId]] = Column.columnToArray(column, implicitly) + implicit lazy val arrayToStatement: ToStatement[Array[RoleId]] = TypoUUID.arrayToStatement.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[RoleId, TypoUUID] = Bijection[RoleId, TypoUUID](_.value)(RoleId.apply) + implicit lazy val column: Column[RoleId] = TypoUUID.column.map(RoleId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[RoleId] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[RoleId] = new ParameterMetaData[RoleId] { + override def sqlType: String = TypoUUID.parameterMetadata.sqlType + override def jdbcType: Int = TypoUUID.parameterMetadata.jdbcType + } + implicit lazy val reads: Reads[RoleId] = TypoUUID.reads.map(RoleId.apply) + implicit lazy val text: Text[RoleId] = new Text[RoleId] { + override def unsafeEncode(v: RoleId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: RoleId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } + implicit lazy val toStatement: ToStatement[RoleId] = TypoUUID.toStatement.contramap(_.value) + implicit lazy val writes: Writes[RoleId] = TypoUUID.writes.contramap(_.value) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleRepo.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleRepo.scala new file mode 100644 index 0000000000..fe17fbe2a4 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleRepo.scala @@ -0,0 +1,36 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import java.sql.Connection +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait RoleRepo { + def delete: DeleteBuilder[RoleFields, RoleRow] + def deleteById(id: RoleId)(implicit c: Connection): Boolean + def deleteByIds(ids: Array[RoleId])(implicit c: Connection): Int + def insert(unsaved: RoleRow)(implicit c: Connection): RoleRow + def insert(unsaved: RoleRowUnsaved)(implicit c: Connection): RoleRow + def insertStreaming(unsaved: Iterator[RoleRow], batchSize: Int = 10000)(implicit c: Connection): Long + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Iterator[RoleRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long + def select: SelectBuilder[RoleFields, RoleRow] + def selectAll(implicit c: Connection): List[RoleRow] + def selectById(id: RoleId)(implicit c: Connection): Option[RoleRow] + def selectByIds(ids: Array[RoleId])(implicit c: Connection): List[RoleRow] + def selectByIdsTracked(ids: Array[RoleId])(implicit c: Connection): Map[RoleId, RoleRow] + def selectByUniqueName(name: String)(implicit c: Connection): Option[RoleRow] + def update: UpdateBuilder[RoleFields, RoleRow] + def update(row: RoleRow)(implicit c: Connection): Boolean + def upsert(unsaved: RoleRow)(implicit c: Connection): RoleRow + def upsertBatch(unsaved: Iterable[RoleRow])(implicit c: Connection): List[RoleRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Iterator[RoleRow], batchSize: Int = 10000)(implicit c: Connection): Int +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleRepoImpl.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleRepoImpl.scala new file mode 100644 index 0000000000..c8f4f6f53d --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleRepoImpl.scala @@ -0,0 +1,170 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import adventureworks.customtypes.Defaulted +import anorm.BatchSql +import anorm.NamedParameter +import anorm.ParameterValue +import anorm.RowParser +import anorm.SQL +import anorm.SimpleSql +import anorm.SqlStringInterpolation +import anorm.ToStatement +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class RoleRepoImpl extends RoleRepo { + override def delete: DeleteBuilder[RoleFields, RoleRow] = { + DeleteBuilder(""""frontpage"."role"""", RoleFields.structure) + } + override def deleteById(id: RoleId)(implicit c: Connection): Boolean = { + SQL"""delete from "frontpage"."role" where "id" = ${ParameterValue(id, null, RoleId.toStatement)}""".executeUpdate() > 0 + } + override def deleteByIds(ids: Array[RoleId])(implicit c: Connection): Int = { + SQL"""delete + from "frontpage"."role" + where "id" = ANY(${ids}) + """.executeUpdate() + + } + override def insert(unsaved: RoleRow)(implicit c: Connection): RoleRow = { + SQL"""insert into "frontpage"."role"("id", "name") + values (${ParameterValue(unsaved.id, null, RoleId.toStatement)}::uuid, ${ParameterValue(unsaved.name, null, ToStatement.stringToStatement)}) + returning "id", "name" + """ + .executeInsert(RoleRow.rowParser(1).single) + + } + override def insert(unsaved: RoleRowUnsaved)(implicit c: Connection): RoleRow = { + val namedParameters = List( + Some((NamedParameter("name", ParameterValue(unsaved.name, null, ToStatement.stringToStatement)), "")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("id", ParameterValue(value, null, RoleId.toStatement)), "::uuid")) + } + ).flatten + val quote = '"'.toString + if (namedParameters.isEmpty) { + SQL"""insert into "frontpage"."role" default values + returning "id", "name" + """ + .executeInsert(RoleRow.rowParser(1).single) + } else { + val q = s"""insert into "frontpage"."role"(${namedParameters.map{case (x, _) => quote + x.name + quote}.mkString(", ")}) + values (${namedParameters.map{ case (np, cast) => s"{${np.name}}$cast"}.mkString(", ")}) + returning "id", "name" + """ + SimpleSql(SQL(q), namedParameters.map { case (np, _) => np.tupled }.toMap, RowParser.successful) + .executeInsert(RoleRow.rowParser(1).single) + } + + } + override def insertStreaming(unsaved: Iterator[RoleRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."role"("id", "name") FROM STDIN""", batchSize, unsaved)(RoleRow.text, c) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[RoleRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."role"("name", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(RoleRowUnsaved.text, c) + } + override def select: SelectBuilder[RoleFields, RoleRow] = { + SelectBuilderSql(""""frontpage"."role"""", RoleFields.structure, RoleRow.rowParser) + } + override def selectAll(implicit c: Connection): List[RoleRow] = { + SQL"""select "id", "name" + from "frontpage"."role" + """.as(RoleRow.rowParser(1).*) + } + override def selectById(id: RoleId)(implicit c: Connection): Option[RoleRow] = { + SQL"""select "id", "name" + from "frontpage"."role" + where "id" = ${ParameterValue(id, null, RoleId.toStatement)} + """.as(RoleRow.rowParser(1).singleOpt) + } + override def selectByIds(ids: Array[RoleId])(implicit c: Connection): List[RoleRow] = { + SQL"""select "id", "name" + from "frontpage"."role" + where "id" = ANY(${ids}) + """.as(RoleRow.rowParser(1).*) + + } + override def selectByIdsTracked(ids: Array[RoleId])(implicit c: Connection): Map[RoleId, RoleRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def selectByUniqueName(name: String)(implicit c: Connection): Option[RoleRow] = { + SQL"""select "id", "name" + from "frontpage"."role" + where "name" = ${ParameterValue(name, null, ToStatement.stringToStatement)} + """.as(RoleRow.rowParser(1).singleOpt) + + } + override def update: UpdateBuilder[RoleFields, RoleRow] = { + UpdateBuilder(""""frontpage"."role"""", RoleFields.structure, RoleRow.rowParser) + } + override def update(row: RoleRow)(implicit c: Connection): Boolean = { + val id = row.id + SQL"""update "frontpage"."role" + set "name" = ${ParameterValue(row.name, null, ToStatement.stringToStatement)} + where "id" = ${ParameterValue(id, null, RoleId.toStatement)} + """.executeUpdate() > 0 + } + override def upsert(unsaved: RoleRow)(implicit c: Connection): RoleRow = { + SQL"""insert into "frontpage"."role"("id", "name") + values ( + ${ParameterValue(unsaved.id, null, RoleId.toStatement)}::uuid, + ${ParameterValue(unsaved.name, null, ToStatement.stringToStatement)} + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name" + returning "id", "name" + """ + .executeInsert(RoleRow.rowParser(1).single) + + } + override def upsertBatch(unsaved: Iterable[RoleRow])(implicit c: Connection): List[RoleRow] = { + def toNamedParameter(row: RoleRow): List[NamedParameter] = List( + NamedParameter("id", ParameterValue(row.id, null, RoleId.toStatement)), + NamedParameter("name", ParameterValue(row.name, null, ToStatement.stringToStatement)) + ) + unsaved.toList match { + case Nil => Nil + case head :: rest => + new anorm.adventureworks.ExecuteReturningSyntax.Ops( + BatchSql( + s"""insert into "frontpage"."role"("id", "name") + values ({id}::uuid, {name}) + on conflict ("id") + do update set + "name" = EXCLUDED."name" + returning "id", "name" + """, + toNamedParameter(head), + rest.map(toNamedParameter)* + ) + ).executeReturning(RoleRow.rowParser(1).*) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[RoleRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + SQL"""create temporary table role_TEMP (like "frontpage"."role") on commit drop""".execute(): @nowarn + streamingInsert(s"""copy role_TEMP("id", "name") from stdin""", batchSize, unsaved)(RoleRow.text, c): @nowarn + SQL"""insert into "frontpage"."role"("id", "name") + select * from role_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name" + ; + drop table role_TEMP;""".executeUpdate() + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleRepoMock.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleRepoMock.scala new file mode 100644 index 0000000000..581df58959 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleRepoMock.scala @@ -0,0 +1,106 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class RoleRepoMock(toRow: Function1[RoleRowUnsaved, RoleRow], + map: scala.collection.mutable.Map[RoleId, RoleRow] = scala.collection.mutable.Map.empty) extends RoleRepo { + override def delete: DeleteBuilder[RoleFields, RoleRow] = { + DeleteBuilderMock(DeleteParams.empty, RoleFields.structure, map) + } + override def deleteById(id: RoleId)(implicit c: Connection): Boolean = { + map.remove(id).isDefined + } + override def deleteByIds(ids: Array[RoleId])(implicit c: Connection): Int = { + ids.map(id => map.remove(id)).count(_.isDefined) + } + override def insert(unsaved: RoleRow)(implicit c: Connection): RoleRow = { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + override def insert(unsaved: RoleRowUnsaved)(implicit c: Connection): RoleRow = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Iterator[RoleRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size.toLong + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[RoleRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + } + unsaved.size.toLong + } + override def select: SelectBuilder[RoleFields, RoleRow] = { + SelectBuilderMock(RoleFields.structure, () => map.values.toList, SelectParams.empty) + } + override def selectAll(implicit c: Connection): List[RoleRow] = { + map.values.toList + } + override def selectById(id: RoleId)(implicit c: Connection): Option[RoleRow] = { + map.get(id) + } + override def selectByIds(ids: Array[RoleId])(implicit c: Connection): List[RoleRow] = { + ids.flatMap(map.get).toList + } + override def selectByIdsTracked(ids: Array[RoleId])(implicit c: Connection): Map[RoleId, RoleRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def selectByUniqueName(name: String)(implicit c: Connection): Option[RoleRow] = { + map.values.find(v => name == v.name) + } + override def update: UpdateBuilder[RoleFields, RoleRow] = { + UpdateBuilderMock(UpdateParams.empty, RoleFields.structure, map) + } + override def update(row: RoleRow)(implicit c: Connection): Boolean = { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + override def upsert(unsaved: RoleRow)(implicit c: Connection): RoleRow = { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + override def upsertBatch(unsaved: Iterable[RoleRow])(implicit c: Connection): List[RoleRow] = { + unsaved.map { row => + map += (row.id -> row) + row + }.toList + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[RoleRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleRow.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleRow.scala new file mode 100644 index 0000000000..c392d2e670 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleRow.scala @@ -0,0 +1,63 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import adventureworks.customtypes.Defaulted +import anorm.Column +import anorm.RowParser +import anorm.Success +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** Table: frontpage.role + Primary key: id */ +case class RoleRow( + /** Default: gen_random_uuid() */ + id: RoleId, + name: String +){ + def toUnsavedRow(id: Defaulted[RoleId]): RoleRowUnsaved = + RoleRowUnsaved(name, id) + } + +object RoleRow { + implicit lazy val reads: Reads[RoleRow] = Reads[RoleRow](json => JsResult.fromTry( + Try( + RoleRow( + id = json.\("id").as(RoleId.reads), + name = json.\("name").as(Reads.StringReads) + ) + ) + ), + ) + def rowParser(idx: Int): RowParser[RoleRow] = RowParser[RoleRow] { row => + Success( + RoleRow( + id = row(idx + 0)(RoleId.column), + name = row(idx + 1)(Column.columnToString) + ) + ) + } + implicit lazy val text: Text[RoleRow] = Text.instance[RoleRow]{ (row, sb) => + RoleId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + } + implicit lazy val writes: OWrites[RoleRow] = OWrites[RoleRow](o => + new JsObject(ListMap[String, JsValue]( + "id" -> RoleId.writes.writes(o.id), + "name" -> Writes.StringWrites.writes(o.name) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleRowUnsaved.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleRowUnsaved.scala new file mode 100644 index 0000000000..f3d89cf5f3 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/role/RoleRowUnsaved.scala @@ -0,0 +1,56 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import adventureworks.customtypes.Defaulted +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** This class corresponds to a row in table `frontpage.role` which has not been persisted yet */ +case class RoleRowUnsaved( + name: String, + /** Default: gen_random_uuid() */ + id: Defaulted[RoleId] = Defaulted.UseDefault +) { + def toRow(idDefault: => RoleId): RoleRow = + RoleRow( + name = name, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object RoleRowUnsaved { + implicit lazy val reads: Reads[RoleRowUnsaved] = Reads[RoleRowUnsaved](json => JsResult.fromTry( + Try( + RoleRowUnsaved( + name = json.\("name").as(Reads.StringReads), + id = json.\("id").as(Defaulted.reads(RoleId.reads)) + ) + ) + ), + ) + implicit lazy val text: Text[RoleRowUnsaved] = Text.instance[RoleRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Defaulted.text(RoleId.text).unsafeEncode(row.id, sb) + } + implicit lazy val writes: OWrites[RoleRowUnsaved] = OWrites[RoleRowUnsaved](o => + new JsObject(ListMap[String, JsValue]( + "name" -> Writes.StringWrites.writes(o.name), + "id" -> Defaulted.writes(RoleId.writes).writes(o.id) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserFields.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserFields.scala new file mode 100644 index 0000000000..032b7e96b1 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserFields.scala @@ -0,0 +1,66 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.department.DepartmentFields +import adventureworks.frontpage.department.DepartmentId +import adventureworks.frontpage.department.DepartmentRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait UserFields { + def id: IdField[UserId, UserRow] + def email: Field[Email, UserRow] + def name: Field[String, UserRow] + def createdAt: OptField[TypoLocalDateTime, UserRow] + def departmentId: OptField[DepartmentId, UserRow] + def status: OptField[UserStatus, UserRow] + def verified: OptField[Boolean, UserRow] + def managerId: OptField[UserId, UserRow] + def role: OptField[UserRole, UserRow] + def fkDepartment: ForeignKey[DepartmentFields, DepartmentRow] = + ForeignKey[DepartmentFields, DepartmentRow]("frontpage.user_department_id_fkey", Nil) + .withColumnPair(departmentId, _.id) + def fkUser: ForeignKey[UserFields, UserRow] = + ForeignKey[UserFields, UserRow]("frontpage.user_manager_id_fkey", Nil) + .withColumnPair(managerId, _.id) +} + +object UserFields { + lazy val structure: Relation[UserFields, UserRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[UserFields, UserRow] { + + override lazy val fields: UserFields = new UserFields { + override def id = IdField[UserId, UserRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def email = Field[Email, UserRow](_path, "email", None, Some("text"), x => x.email, (row, value) => row.copy(email = value)) + override def name = Field[String, UserRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + override def createdAt = OptField[TypoLocalDateTime, UserRow](_path, "created_at", Some("text"), Some("timestamp"), x => x.createdAt, (row, value) => row.copy(createdAt = value)) + override def departmentId = OptField[DepartmentId, UserRow](_path, "department_id", None, Some("uuid"), x => x.departmentId, (row, value) => row.copy(departmentId = value)) + override def status = OptField[UserStatus, UserRow](_path, "status", None, Some("frontpage.user_status"), x => x.status, (row, value) => row.copy(status = value)) + override def verified = OptField[Boolean, UserRow](_path, "verified", None, None, x => x.verified, (row, value) => row.copy(verified = value)) + override def managerId = OptField[UserId, UserRow](_path, "manager_id", None, Some("uuid"), x => x.managerId, (row, value) => row.copy(managerId = value)) + override def role = OptField[UserRole, UserRow](_path, "role", None, Some("frontpage.user_role"), x => x.role, (row, value) => row.copy(role = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, UserRow]] = + List[FieldLikeNoHkt[?, UserRow]](fields.id, fields.email, fields.name, fields.createdAt, fields.departmentId, fields.status, fields.verified, fields.managerId, fields.role) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserId.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserId.scala new file mode 100644 index 0000000000..f777d5e84b --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import adventureworks.customtypes.TypoUUID +import anorm.Column +import anorm.ParameterMetaData +import anorm.ToStatement +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.user` */ +case class UserId(value: TypoUUID) extends AnyVal +object UserId { + implicit lazy val arrayColumn: Column[Array[UserId]] = Column.columnToArray(column, implicitly) + implicit lazy val arrayToStatement: ToStatement[Array[UserId]] = TypoUUID.arrayToStatement.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[UserId, TypoUUID] = Bijection[UserId, TypoUUID](_.value)(UserId.apply) + implicit lazy val column: Column[UserId] = TypoUUID.column.map(UserId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[UserId] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[UserId] = new ParameterMetaData[UserId] { + override def sqlType: String = TypoUUID.parameterMetadata.sqlType + override def jdbcType: Int = TypoUUID.parameterMetadata.jdbcType + } + implicit lazy val reads: Reads[UserId] = TypoUUID.reads.map(UserId.apply) + implicit lazy val text: Text[UserId] = new Text[UserId] { + override def unsafeEncode(v: UserId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: UserId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } + implicit lazy val toStatement: ToStatement[UserId] = TypoUUID.toStatement.contramap(_.value) + implicit lazy val writes: Writes[UserId] = TypoUUID.writes.contramap(_.value) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserRepo.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserRepo.scala new file mode 100644 index 0000000000..b4374262eb --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserRepo.scala @@ -0,0 +1,36 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import java.sql.Connection +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait UserRepo { + def delete: DeleteBuilder[UserFields, UserRow] + def deleteById(id: UserId)(implicit c: Connection): Boolean + def deleteByIds(ids: Array[UserId])(implicit c: Connection): Int + def insert(unsaved: UserRow)(implicit c: Connection): UserRow + def insert(unsaved: UserRowUnsaved)(implicit c: Connection): UserRow + def insertStreaming(unsaved: Iterator[UserRow], batchSize: Int = 10000)(implicit c: Connection): Long + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Iterator[UserRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long + def select: SelectBuilder[UserFields, UserRow] + def selectAll(implicit c: Connection): List[UserRow] + def selectById(id: UserId)(implicit c: Connection): Option[UserRow] + def selectByIds(ids: Array[UserId])(implicit c: Connection): List[UserRow] + def selectByIdsTracked(ids: Array[UserId])(implicit c: Connection): Map[UserId, UserRow] + def selectByUniqueEmail(email: Email)(implicit c: Connection): Option[UserRow] + def update: UpdateBuilder[UserFields, UserRow] + def update(row: UserRow)(implicit c: Connection): Boolean + def upsert(unsaved: UserRow)(implicit c: Connection): UserRow + def upsertBatch(unsaved: Iterable[UserRow])(implicit c: Connection): List[UserRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Iterator[UserRow], batchSize: Int = 10000)(implicit c: Connection): Int +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserRepoImpl.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserRepoImpl.scala new file mode 100644 index 0000000000..93084983a0 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserRepoImpl.scala @@ -0,0 +1,234 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.department.DepartmentId +import anorm.BatchSql +import anorm.NamedParameter +import anorm.ParameterMetaData +import anorm.ParameterValue +import anorm.RowParser +import anorm.SQL +import anorm.SimpleSql +import anorm.SqlStringInterpolation +import anorm.ToStatement +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class UserRepoImpl extends UserRepo { + override def delete: DeleteBuilder[UserFields, UserRow] = { + DeleteBuilder(""""frontpage"."user"""", UserFields.structure) + } + override def deleteById(id: UserId)(implicit c: Connection): Boolean = { + SQL"""delete from "frontpage"."user" where "id" = ${ParameterValue(id, null, UserId.toStatement)}""".executeUpdate() > 0 + } + override def deleteByIds(ids: Array[UserId])(implicit c: Connection): Int = { + SQL"""delete + from "frontpage"."user" + where "id" = ANY(${ids}) + """.executeUpdate() + + } + override def insert(unsaved: UserRow)(implicit c: Connection): UserRow = { + SQL"""insert into "frontpage"."user"("id", "email", "name", "created_at", "department_id", "status", "verified", "manager_id", "role") + values (${ParameterValue(unsaved.id, null, UserId.toStatement)}::uuid, ${ParameterValue(unsaved.email, null, Email.toStatement)}::text, ${ParameterValue(unsaved.name, null, ToStatement.stringToStatement)}, ${ParameterValue(unsaved.createdAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp, ${ParameterValue(unsaved.departmentId, null, ToStatement.optionToStatement(DepartmentId.toStatement, DepartmentId.parameterMetadata))}::uuid, ${ParameterValue(unsaved.status, null, ToStatement.optionToStatement(UserStatus.toStatement, UserStatus.parameterMetadata))}::frontpage.user_status, ${ParameterValue(unsaved.verified, null, ToStatement.optionToStatement(ToStatement.booleanToStatement, ParameterMetaData.BooleanParameterMetaData))}, ${ParameterValue(unsaved.managerId, null, ToStatement.optionToStatement(UserId.toStatement, UserId.parameterMetadata))}::uuid, ${ParameterValue(unsaved.role, null, ToStatement.optionToStatement(UserRole.toStatement, UserRole.parameterMetadata))}::frontpage.user_role) + returning "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" + """ + .executeInsert(UserRow.rowParser(1).single) + + } + override def insert(unsaved: UserRowUnsaved)(implicit c: Connection): UserRow = { + val namedParameters = List( + Some((NamedParameter("email", ParameterValue(unsaved.email, null, Email.toStatement)), "::text")), + Some((NamedParameter("name", ParameterValue(unsaved.name, null, ToStatement.stringToStatement)), "")), + Some((NamedParameter("department_id", ParameterValue(unsaved.departmentId, null, ToStatement.optionToStatement(DepartmentId.toStatement, DepartmentId.parameterMetadata))), "::uuid")), + Some((NamedParameter("manager_id", ParameterValue(unsaved.managerId, null, ToStatement.optionToStatement(UserId.toStatement, UserId.parameterMetadata))), "::uuid")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("id", ParameterValue(value, null, UserId.toStatement)), "::uuid")) + }, + unsaved.createdAt match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("created_at", ParameterValue(value, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))), "::timestamp")) + }, + unsaved.status match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("status", ParameterValue(value, null, ToStatement.optionToStatement(UserStatus.toStatement, UserStatus.parameterMetadata))), "::frontpage.user_status")) + }, + unsaved.verified match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("verified", ParameterValue(value, null, ToStatement.optionToStatement(ToStatement.booleanToStatement, ParameterMetaData.BooleanParameterMetaData))), "")) + }, + unsaved.role match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("role", ParameterValue(value, null, ToStatement.optionToStatement(UserRole.toStatement, UserRole.parameterMetadata))), "::frontpage.user_role")) + } + ).flatten + val quote = '"'.toString + if (namedParameters.isEmpty) { + SQL"""insert into "frontpage"."user" default values + returning "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" + """ + .executeInsert(UserRow.rowParser(1).single) + } else { + val q = s"""insert into "frontpage"."user"(${namedParameters.map{case (x, _) => quote + x.name + quote}.mkString(", ")}) + values (${namedParameters.map{ case (np, cast) => s"{${np.name}}$cast"}.mkString(", ")}) + returning "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" + """ + SimpleSql(SQL(q), namedParameters.map { case (np, _) => np.tupled }.toMap, RowParser.successful) + .executeInsert(UserRow.rowParser(1).single) + } + + } + override def insertStreaming(unsaved: Iterator[UserRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."user"("id", "email", "name", "created_at", "department_id", "status", "verified", "manager_id", "role") FROM STDIN""", batchSize, unsaved)(UserRow.text, c) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[UserRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."user"("email", "name", "department_id", "manager_id", "id", "created_at", "status", "verified", "role") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(UserRowUnsaved.text, c) + } + override def select: SelectBuilder[UserFields, UserRow] = { + SelectBuilderSql(""""frontpage"."user"""", UserFields.structure, UserRow.rowParser) + } + override def selectAll(implicit c: Connection): List[UserRow] = { + SQL"""select "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" + from "frontpage"."user" + """.as(UserRow.rowParser(1).*) + } + override def selectById(id: UserId)(implicit c: Connection): Option[UserRow] = { + SQL"""select "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" + from "frontpage"."user" + where "id" = ${ParameterValue(id, null, UserId.toStatement)} + """.as(UserRow.rowParser(1).singleOpt) + } + override def selectByIds(ids: Array[UserId])(implicit c: Connection): List[UserRow] = { + SQL"""select "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" + from "frontpage"."user" + where "id" = ANY(${ids}) + """.as(UserRow.rowParser(1).*) + + } + override def selectByIdsTracked(ids: Array[UserId])(implicit c: Connection): Map[UserId, UserRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def selectByUniqueEmail(email: Email)(implicit c: Connection): Option[UserRow] = { + SQL"""select "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" + from "frontpage"."user" + where "email" = ${ParameterValue(email, null, Email.toStatement)} + """.as(UserRow.rowParser(1).singleOpt) + + } + override def update: UpdateBuilder[UserFields, UserRow] = { + UpdateBuilder(""""frontpage"."user"""", UserFields.structure, UserRow.rowParser) + } + override def update(row: UserRow)(implicit c: Connection): Boolean = { + val id = row.id + SQL"""update "frontpage"."user" + set "email" = ${ParameterValue(row.email, null, Email.toStatement)}::text, + "name" = ${ParameterValue(row.name, null, ToStatement.stringToStatement)}, + "created_at" = ${ParameterValue(row.createdAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp, + "department_id" = ${ParameterValue(row.departmentId, null, ToStatement.optionToStatement(DepartmentId.toStatement, DepartmentId.parameterMetadata))}::uuid, + "status" = ${ParameterValue(row.status, null, ToStatement.optionToStatement(UserStatus.toStatement, UserStatus.parameterMetadata))}::frontpage.user_status, + "verified" = ${ParameterValue(row.verified, null, ToStatement.optionToStatement(ToStatement.booleanToStatement, ParameterMetaData.BooleanParameterMetaData))}, + "manager_id" = ${ParameterValue(row.managerId, null, ToStatement.optionToStatement(UserId.toStatement, UserId.parameterMetadata))}::uuid, + "role" = ${ParameterValue(row.role, null, ToStatement.optionToStatement(UserRole.toStatement, UserRole.parameterMetadata))}::frontpage.user_role + where "id" = ${ParameterValue(id, null, UserId.toStatement)} + """.executeUpdate() > 0 + } + override def upsert(unsaved: UserRow)(implicit c: Connection): UserRow = { + SQL"""insert into "frontpage"."user"("id", "email", "name", "created_at", "department_id", "status", "verified", "manager_id", "role") + values ( + ${ParameterValue(unsaved.id, null, UserId.toStatement)}::uuid, + ${ParameterValue(unsaved.email, null, Email.toStatement)}::text, + ${ParameterValue(unsaved.name, null, ToStatement.stringToStatement)}, + ${ParameterValue(unsaved.createdAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp, + ${ParameterValue(unsaved.departmentId, null, ToStatement.optionToStatement(DepartmentId.toStatement, DepartmentId.parameterMetadata))}::uuid, + ${ParameterValue(unsaved.status, null, ToStatement.optionToStatement(UserStatus.toStatement, UserStatus.parameterMetadata))}::frontpage.user_status, + ${ParameterValue(unsaved.verified, null, ToStatement.optionToStatement(ToStatement.booleanToStatement, ParameterMetaData.BooleanParameterMetaData))}, + ${ParameterValue(unsaved.managerId, null, ToStatement.optionToStatement(UserId.toStatement, UserId.parameterMetadata))}::uuid, + ${ParameterValue(unsaved.role, null, ToStatement.optionToStatement(UserRole.toStatement, UserRole.parameterMetadata))}::frontpage.user_role + ) + on conflict ("id") + do update set + "email" = EXCLUDED."email", + "name" = EXCLUDED."name", + "created_at" = EXCLUDED."created_at", + "department_id" = EXCLUDED."department_id", + "status" = EXCLUDED."status", + "verified" = EXCLUDED."verified", + "manager_id" = EXCLUDED."manager_id", + "role" = EXCLUDED."role" + returning "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" + """ + .executeInsert(UserRow.rowParser(1).single) + + } + override def upsertBatch(unsaved: Iterable[UserRow])(implicit c: Connection): List[UserRow] = { + def toNamedParameter(row: UserRow): List[NamedParameter] = List( + NamedParameter("id", ParameterValue(row.id, null, UserId.toStatement)), + NamedParameter("email", ParameterValue(row.email, null, Email.toStatement)), + NamedParameter("name", ParameterValue(row.name, null, ToStatement.stringToStatement)), + NamedParameter("created_at", ParameterValue(row.createdAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))), + NamedParameter("department_id", ParameterValue(row.departmentId, null, ToStatement.optionToStatement(DepartmentId.toStatement, DepartmentId.parameterMetadata))), + NamedParameter("status", ParameterValue(row.status, null, ToStatement.optionToStatement(UserStatus.toStatement, UserStatus.parameterMetadata))), + NamedParameter("verified", ParameterValue(row.verified, null, ToStatement.optionToStatement(ToStatement.booleanToStatement, ParameterMetaData.BooleanParameterMetaData))), + NamedParameter("manager_id", ParameterValue(row.managerId, null, ToStatement.optionToStatement(UserId.toStatement, UserId.parameterMetadata))), + NamedParameter("role", ParameterValue(row.role, null, ToStatement.optionToStatement(UserRole.toStatement, UserRole.parameterMetadata))) + ) + unsaved.toList match { + case Nil => Nil + case head :: rest => + new anorm.adventureworks.ExecuteReturningSyntax.Ops( + BatchSql( + s"""insert into "frontpage"."user"("id", "email", "name", "created_at", "department_id", "status", "verified", "manager_id", "role") + values ({id}::uuid, {email}::text, {name}, {created_at}::timestamp, {department_id}::uuid, {status}::frontpage.user_status, {verified}, {manager_id}::uuid, {role}::frontpage.user_role) + on conflict ("id") + do update set + "email" = EXCLUDED."email", + "name" = EXCLUDED."name", + "created_at" = EXCLUDED."created_at", + "department_id" = EXCLUDED."department_id", + "status" = EXCLUDED."status", + "verified" = EXCLUDED."verified", + "manager_id" = EXCLUDED."manager_id", + "role" = EXCLUDED."role" + returning "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" + """, + toNamedParameter(head), + rest.map(toNamedParameter)* + ) + ).executeReturning(UserRow.rowParser(1).*) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[UserRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + SQL"""create temporary table user_TEMP (like "frontpage"."user") on commit drop""".execute(): @nowarn + streamingInsert(s"""copy user_TEMP("id", "email", "name", "created_at", "department_id", "status", "verified", "manager_id", "role") from stdin""", batchSize, unsaved)(UserRow.text, c): @nowarn + SQL"""insert into "frontpage"."user"("id", "email", "name", "created_at", "department_id", "status", "verified", "manager_id", "role") + select * from user_TEMP + on conflict ("id") + do update set + "email" = EXCLUDED."email", + "name" = EXCLUDED."name", + "created_at" = EXCLUDED."created_at", + "department_id" = EXCLUDED."department_id", + "status" = EXCLUDED."status", + "verified" = EXCLUDED."verified", + "manager_id" = EXCLUDED."manager_id", + "role" = EXCLUDED."role" + ; + drop table user_TEMP;""".executeUpdate() + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserRepoMock.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserRepoMock.scala new file mode 100644 index 0000000000..45bdc8977d --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserRepoMock.scala @@ -0,0 +1,106 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class UserRepoMock(toRow: Function1[UserRowUnsaved, UserRow], + map: scala.collection.mutable.Map[UserId, UserRow] = scala.collection.mutable.Map.empty) extends UserRepo { + override def delete: DeleteBuilder[UserFields, UserRow] = { + DeleteBuilderMock(DeleteParams.empty, UserFields.structure, map) + } + override def deleteById(id: UserId)(implicit c: Connection): Boolean = { + map.remove(id).isDefined + } + override def deleteByIds(ids: Array[UserId])(implicit c: Connection): Int = { + ids.map(id => map.remove(id)).count(_.isDefined) + } + override def insert(unsaved: UserRow)(implicit c: Connection): UserRow = { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + override def insert(unsaved: UserRowUnsaved)(implicit c: Connection): UserRow = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Iterator[UserRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size.toLong + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[UserRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + } + unsaved.size.toLong + } + override def select: SelectBuilder[UserFields, UserRow] = { + SelectBuilderMock(UserFields.structure, () => map.values.toList, SelectParams.empty) + } + override def selectAll(implicit c: Connection): List[UserRow] = { + map.values.toList + } + override def selectById(id: UserId)(implicit c: Connection): Option[UserRow] = { + map.get(id) + } + override def selectByIds(ids: Array[UserId])(implicit c: Connection): List[UserRow] = { + ids.flatMap(map.get).toList + } + override def selectByIdsTracked(ids: Array[UserId])(implicit c: Connection): Map[UserId, UserRow] = { + val byId = selectByIds(ids).view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def selectByUniqueEmail(email: Email)(implicit c: Connection): Option[UserRow] = { + map.values.find(v => email == v.email) + } + override def update: UpdateBuilder[UserFields, UserRow] = { + UpdateBuilderMock(UpdateParams.empty, UserFields.structure, map) + } + override def update(row: UserRow)(implicit c: Connection): Boolean = { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + override def upsert(unsaved: UserRow)(implicit c: Connection): UserRow = { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + override def upsertBatch(unsaved: Iterable[UserRow])(implicit c: Connection): List[UserRow] = { + unsaved.map { row => + map += (row.id -> row) + row + }.toList + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[UserRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + unsaved.foreach { row => + map += (row.id -> row) + } + unsaved.size + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserRow.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserRow.scala new file mode 100644 index 0000000000..dae79b2280 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserRow.scala @@ -0,0 +1,113 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.department.DepartmentId +import anorm.Column +import anorm.RowParser +import anorm.Success +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** Table: frontpage.user + Primary key: id */ +case class UserRow( + /** Default: gen_random_uuid() */ + id: UserId, + email: Email, + name: String, + /** Default: now() */ + createdAt: Option[TypoLocalDateTime], + /** Points to [[department.DepartmentRow.id]] */ + departmentId: Option[DepartmentId], + /** Default: 'active'::frontpage.user_status */ + status: Option[UserStatus], + /** Default: false */ + verified: Option[Boolean], + /** Points to [[UserRow.id]] */ + managerId: Option[UserId], + /** Default: 'employee'::frontpage.user_role */ + role: Option[UserRole] +){ + def toUnsavedRow(id: Defaulted[UserId], createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.Provided(this.createdAt), status: Defaulted[Option[UserStatus]] = Defaulted.Provided(this.status), verified: Defaulted[Option[Boolean]] = Defaulted.Provided(this.verified), role: Defaulted[Option[UserRole]] = Defaulted.Provided(this.role)): UserRowUnsaved = + UserRowUnsaved(email, name, departmentId, managerId, id, createdAt, status, verified, role) + } + +object UserRow { + implicit lazy val reads: Reads[UserRow] = Reads[UserRow](json => JsResult.fromTry( + Try( + UserRow( + id = json.\("id").as(UserId.reads), + email = json.\("email").as(Email.reads), + name = json.\("name").as(Reads.StringReads), + createdAt = json.\("created_at").toOption.map(_.as(TypoLocalDateTime.reads)), + departmentId = json.\("department_id").toOption.map(_.as(DepartmentId.reads)), + status = json.\("status").toOption.map(_.as(UserStatus.reads)), + verified = json.\("verified").toOption.map(_.as(Reads.BooleanReads)), + managerId = json.\("manager_id").toOption.map(_.as(UserId.reads)), + role = json.\("role").toOption.map(_.as(UserRole.reads)) + ) + ) + ), + ) + def rowParser(idx: Int): RowParser[UserRow] = RowParser[UserRow] { row => + Success( + UserRow( + id = row(idx + 0)(UserId.column), + email = row(idx + 1)(Email.column), + name = row(idx + 2)(Column.columnToString), + createdAt = row(idx + 3)(Column.columnToOption(TypoLocalDateTime.column)), + departmentId = row(idx + 4)(Column.columnToOption(DepartmentId.column)), + status = row(idx + 5)(Column.columnToOption(UserStatus.column)), + verified = row(idx + 6)(Column.columnToOption(Column.columnToBoolean)), + managerId = row(idx + 7)(Column.columnToOption(UserId.column)), + role = row(idx + 8)(Column.columnToOption(UserRole.column)) + ) + ) + } + implicit lazy val text: Text[UserRow] = Text.instance[UserRow]{ (row, sb) => + UserId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Email.text.unsafeEncode(row.email, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.createdAt, sb) + sb.append(Text.DELIMETER) + Text.option(DepartmentId.text).unsafeEncode(row.departmentId, sb) + sb.append(Text.DELIMETER) + Text.option(UserStatus.text).unsafeEncode(row.status, sb) + sb.append(Text.DELIMETER) + Text.option(Text.booleanInstance).unsafeEncode(row.verified, sb) + sb.append(Text.DELIMETER) + Text.option(UserId.text).unsafeEncode(row.managerId, sb) + sb.append(Text.DELIMETER) + Text.option(UserRole.text).unsafeEncode(row.role, sb) + } + implicit lazy val writes: OWrites[UserRow] = OWrites[UserRow](o => + new JsObject(ListMap[String, JsValue]( + "id" -> UserId.writes.writes(o.id), + "email" -> Email.writes.writes(o.email), + "name" -> Writes.StringWrites.writes(o.name), + "created_at" -> Writes.OptionWrites(TypoLocalDateTime.writes).writes(o.createdAt), + "department_id" -> Writes.OptionWrites(DepartmentId.writes).writes(o.departmentId), + "status" -> Writes.OptionWrites(UserStatus.writes).writes(o.status), + "verified" -> Writes.OptionWrites(Writes.BooleanWrites).writes(o.verified), + "manager_id" -> Writes.OptionWrites(UserId.writes).writes(o.managerId), + "role" -> Writes.OptionWrites(UserRole.writes).writes(o.role) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserRowUnsaved.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserRowUnsaved.scala new file mode 100644 index 0000000000..ce9d31baa4 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user/UserRowUnsaved.scala @@ -0,0 +1,118 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.department.DepartmentId +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** This class corresponds to a row in table `frontpage.user` which has not been persisted yet */ +case class UserRowUnsaved( + email: Email, + name: String, + /** Points to [[department.DepartmentRow.id]] */ + departmentId: Option[DepartmentId], + /** Points to [[UserRow.id]] */ + managerId: Option[UserId], + /** Default: gen_random_uuid() */ + id: Defaulted[UserId] = Defaulted.UseDefault, + /** Default: now() */ + createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault, + /** Default: 'active'::frontpage.user_status */ + status: Defaulted[Option[UserStatus]] = Defaulted.UseDefault, + /** Default: false */ + verified: Defaulted[Option[Boolean]] = Defaulted.UseDefault, + /** Default: 'employee'::frontpage.user_role */ + role: Defaulted[Option[UserRole]] = Defaulted.UseDefault +) { + def toRow(idDefault: => UserId, createdAtDefault: => Option[TypoLocalDateTime], statusDefault: => Option[UserStatus], verifiedDefault: => Option[Boolean], roleDefault: => Option[UserRole]): UserRow = + UserRow( + email = email, + name = name, + departmentId = departmentId, + managerId = managerId, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + }, + createdAt = createdAt match { + case Defaulted.UseDefault => createdAtDefault + case Defaulted.Provided(value) => value + }, + status = status match { + case Defaulted.UseDefault => statusDefault + case Defaulted.Provided(value) => value + }, + verified = verified match { + case Defaulted.UseDefault => verifiedDefault + case Defaulted.Provided(value) => value + }, + role = role match { + case Defaulted.UseDefault => roleDefault + case Defaulted.Provided(value) => value + } + ) +} +object UserRowUnsaved { + implicit lazy val reads: Reads[UserRowUnsaved] = Reads[UserRowUnsaved](json => JsResult.fromTry( + Try( + UserRowUnsaved( + email = json.\("email").as(Email.reads), + name = json.\("name").as(Reads.StringReads), + departmentId = json.\("department_id").toOption.map(_.as(DepartmentId.reads)), + managerId = json.\("manager_id").toOption.map(_.as(UserId.reads)), + id = json.\("id").as(Defaulted.reads(UserId.reads)), + createdAt = json.\("created_at").as(Defaulted.readsOpt(TypoLocalDateTime.reads)), + status = json.\("status").as(Defaulted.readsOpt(UserStatus.reads)), + verified = json.\("verified").as(Defaulted.readsOpt(Reads.BooleanReads)), + role = json.\("role").as(Defaulted.readsOpt(UserRole.reads)) + ) + ) + ), + ) + implicit lazy val text: Text[UserRowUnsaved] = Text.instance[UserRowUnsaved]{ (row, sb) => + Email.text.unsafeEncode(row.email, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(DepartmentId.text).unsafeEncode(row.departmentId, sb) + sb.append(Text.DELIMETER) + Text.option(UserId.text).unsafeEncode(row.managerId, sb) + sb.append(Text.DELIMETER) + Defaulted.text(UserId.text).unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(TypoLocalDateTime.text)).unsafeEncode(row.createdAt, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(UserStatus.text)).unsafeEncode(row.status, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text.booleanInstance)).unsafeEncode(row.verified, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(UserRole.text)).unsafeEncode(row.role, sb) + } + implicit lazy val writes: OWrites[UserRowUnsaved] = OWrites[UserRowUnsaved](o => + new JsObject(ListMap[String, JsValue]( + "email" -> Email.writes.writes(o.email), + "name" -> Writes.StringWrites.writes(o.name), + "department_id" -> Writes.OptionWrites(DepartmentId.writes).writes(o.departmentId), + "manager_id" -> Writes.OptionWrites(UserId.writes).writes(o.managerId), + "id" -> Defaulted.writes(UserId.writes).writes(o.id), + "created_at" -> Defaulted.writes(Writes.OptionWrites(TypoLocalDateTime.writes)).writes(o.createdAt), + "status" -> Defaulted.writes(Writes.OptionWrites(UserStatus.writes)).writes(o.status), + "verified" -> Defaulted.writes(Writes.OptionWrites(Writes.BooleanWrites)).writes(o.verified), + "role" -> Defaulted.writes(Writes.OptionWrites(UserRole.writes)).writes(o.role) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionFields.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionFields.scala new file mode 100644 index 0000000000..51eba08eed --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionFields.scala @@ -0,0 +1,65 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.permission.PermissionFields +import adventureworks.frontpage.permission.PermissionId +import adventureworks.frontpage.permission.PermissionRow +import adventureworks.frontpage.user.UserFields +import adventureworks.frontpage.user.UserId +import adventureworks.frontpage.user.UserRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.Required +import typo.dsl.SqlExpr +import typo.dsl.SqlExpr.CompositeIn +import typo.dsl.SqlExpr.CompositeIn.TuplePart +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait UserPermissionFields { + def userId: IdField[UserId, UserPermissionRow] + def permissionId: IdField[PermissionId, UserPermissionRow] + def grantedAt: OptField[TypoLocalDateTime, UserPermissionRow] + def fkPermission: ForeignKey[PermissionFields, PermissionRow] = + ForeignKey[PermissionFields, PermissionRow]("frontpage.user_permission_permission_id_fkey", Nil) + .withColumnPair(permissionId, _.id) + def fkUser: ForeignKey[UserFields, UserRow] = + ForeignKey[UserFields, UserRow]("frontpage.user_permission_user_id_fkey", Nil) + .withColumnPair(userId, _.id) + def compositeIdIs(compositeId: UserPermissionId): SqlExpr[Boolean, Required] = + userId.isEqual(compositeId.userId).and(permissionId.isEqual(compositeId.permissionId)) + def compositeIdIn(compositeIds: Array[UserPermissionId]): SqlExpr[Boolean, Required] = + new CompositeIn(compositeIds)(TuplePart(userId)(_.userId), TuplePart(permissionId)(_.permissionId)) + +} + +object UserPermissionFields { + lazy val structure: Relation[UserPermissionFields, UserPermissionRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[UserPermissionFields, UserPermissionRow] { + + override lazy val fields: UserPermissionFields = new UserPermissionFields { + override def userId = IdField[UserId, UserPermissionRow](_path, "user_id", None, Some("uuid"), x => x.userId, (row, value) => row.copy(userId = value)) + override def permissionId = IdField[PermissionId, UserPermissionRow](_path, "permission_id", None, Some("uuid"), x => x.permissionId, (row, value) => row.copy(permissionId = value)) + override def grantedAt = OptField[TypoLocalDateTime, UserPermissionRow](_path, "granted_at", Some("text"), Some("timestamp"), x => x.grantedAt, (row, value) => row.copy(grantedAt = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, UserPermissionRow]] = + List[FieldLikeNoHkt[?, UserPermissionRow]](fields.userId, fields.permissionId, fields.grantedAt) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionId.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionId.scala new file mode 100644 index 0000000000..6c2878e1a0 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionId.scala @@ -0,0 +1,42 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import adventureworks.frontpage.permission.PermissionId +import adventureworks.frontpage.user.UserId +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import scala.collection.immutable.ListMap +import scala.util.Try + +/** Type for the composite primary key of table `frontpage.user_permission` */ +case class UserPermissionId( + userId: UserId, + permissionId: PermissionId +) +object UserPermissionId { + implicit lazy val ordering: Ordering[UserPermissionId] = Ordering.by(x => (x.userId, x.permissionId)) + implicit lazy val reads: Reads[UserPermissionId] = Reads[UserPermissionId](json => JsResult.fromTry( + Try( + UserPermissionId( + userId = json.\("user_id").as(UserId.reads), + permissionId = json.\("permission_id").as(PermissionId.reads) + ) + ) + ), + ) + implicit lazy val writes: OWrites[UserPermissionId] = OWrites[UserPermissionId](o => + new JsObject(ListMap[String, JsValue]( + "user_id" -> UserId.writes.writes(o.userId), + "permission_id" -> PermissionId.writes.writes(o.permissionId) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepo.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepo.scala new file mode 100644 index 0000000000..a95297615a --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepo.scala @@ -0,0 +1,35 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import java.sql.Connection +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait UserPermissionRepo { + def delete: DeleteBuilder[UserPermissionFields, UserPermissionRow] + def deleteById(compositeId: UserPermissionId)(implicit c: Connection): Boolean + def deleteByIds(compositeIds: Array[UserPermissionId])(implicit c: Connection): Int + def insert(unsaved: UserPermissionRow)(implicit c: Connection): UserPermissionRow + def insert(unsaved: UserPermissionRowUnsaved)(implicit c: Connection): UserPermissionRow + def insertStreaming(unsaved: Iterator[UserPermissionRow], batchSize: Int = 10000)(implicit c: Connection): Long + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Iterator[UserPermissionRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long + def select: SelectBuilder[UserPermissionFields, UserPermissionRow] + def selectAll(implicit c: Connection): List[UserPermissionRow] + def selectById(compositeId: UserPermissionId)(implicit c: Connection): Option[UserPermissionRow] + def selectByIds(compositeIds: Array[UserPermissionId])(implicit c: Connection): List[UserPermissionRow] + def selectByIdsTracked(compositeIds: Array[UserPermissionId])(implicit c: Connection): Map[UserPermissionId, UserPermissionRow] + def update: UpdateBuilder[UserPermissionFields, UserPermissionRow] + def update(row: UserPermissionRow)(implicit c: Connection): Boolean + def upsert(unsaved: UserPermissionRow)(implicit c: Connection): UserPermissionRow + def upsertBatch(unsaved: Iterable[UserPermissionRow])(implicit c: Connection): List[UserPermissionRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Iterator[UserPermissionRow], batchSize: Int = 10000)(implicit c: Connection): Int +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepoImpl.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepoImpl.scala new file mode 100644 index 0000000000..b4c20a2c56 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepoImpl.scala @@ -0,0 +1,175 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.permission.PermissionId +import adventureworks.frontpage.user.UserId +import anorm.BatchSql +import anorm.NamedParameter +import anorm.ParameterValue +import anorm.RowParser +import anorm.SQL +import anorm.SimpleSql +import anorm.SqlStringInterpolation +import anorm.ToStatement +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class UserPermissionRepoImpl extends UserPermissionRepo { + override def delete: DeleteBuilder[UserPermissionFields, UserPermissionRow] = { + DeleteBuilder(""""frontpage"."user_permission"""", UserPermissionFields.structure) + } + override def deleteById(compositeId: UserPermissionId)(implicit c: Connection): Boolean = { + SQL"""delete from "frontpage"."user_permission" where "user_id" = ${ParameterValue(compositeId.userId, null, UserId.toStatement)} AND "permission_id" = ${ParameterValue(compositeId.permissionId, null, PermissionId.toStatement)}""".executeUpdate() > 0 + } + override def deleteByIds(compositeIds: Array[UserPermissionId])(implicit c: Connection): Int = { + val userId = compositeIds.map(_.userId) + val permissionId = compositeIds.map(_.permissionId) + SQL"""delete + from "frontpage"."user_permission" + where ("user_id", "permission_id") + in (select unnest(${userId}), unnest(${permissionId})) + """.executeUpdate() + + } + override def insert(unsaved: UserPermissionRow)(implicit c: Connection): UserPermissionRow = { + SQL"""insert into "frontpage"."user_permission"("user_id", "permission_id", "granted_at") + values (${ParameterValue(unsaved.userId, null, UserId.toStatement)}::uuid, ${ParameterValue(unsaved.permissionId, null, PermissionId.toStatement)}::uuid, ${ParameterValue(unsaved.grantedAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp) + returning "user_id", "permission_id", "granted_at"::text + """ + .executeInsert(UserPermissionRow.rowParser(1).single) + + } + override def insert(unsaved: UserPermissionRowUnsaved)(implicit c: Connection): UserPermissionRow = { + val namedParameters = List( + Some((NamedParameter("user_id", ParameterValue(unsaved.userId, null, UserId.toStatement)), "::uuid")), + Some((NamedParameter("permission_id", ParameterValue(unsaved.permissionId, null, PermissionId.toStatement)), "::uuid")), + unsaved.grantedAt match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((NamedParameter("granted_at", ParameterValue(value, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))), "::timestamp")) + } + ).flatten + val quote = '"'.toString + if (namedParameters.isEmpty) { + SQL"""insert into "frontpage"."user_permission" default values + returning "user_id", "permission_id", "granted_at"::text + """ + .executeInsert(UserPermissionRow.rowParser(1).single) + } else { + val q = s"""insert into "frontpage"."user_permission"(${namedParameters.map{case (x, _) => quote + x.name + quote}.mkString(", ")}) + values (${namedParameters.map{ case (np, cast) => s"{${np.name}}$cast"}.mkString(", ")}) + returning "user_id", "permission_id", "granted_at"::text + """ + SimpleSql(SQL(q), namedParameters.map { case (np, _) => np.tupled }.toMap, RowParser.successful) + .executeInsert(UserPermissionRow.rowParser(1).single) + } + + } + override def insertStreaming(unsaved: Iterator[UserPermissionRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."user_permission"("user_id", "permission_id", "granted_at") FROM STDIN""", batchSize, unsaved)(UserPermissionRow.text, c) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[UserPermissionRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "frontpage"."user_permission"("user_id", "permission_id", "granted_at") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(UserPermissionRowUnsaved.text, c) + } + override def select: SelectBuilder[UserPermissionFields, UserPermissionRow] = { + SelectBuilderSql(""""frontpage"."user_permission"""", UserPermissionFields.structure, UserPermissionRow.rowParser) + } + override def selectAll(implicit c: Connection): List[UserPermissionRow] = { + SQL"""select "user_id", "permission_id", "granted_at"::text + from "frontpage"."user_permission" + """.as(UserPermissionRow.rowParser(1).*) + } + override def selectById(compositeId: UserPermissionId)(implicit c: Connection): Option[UserPermissionRow] = { + SQL"""select "user_id", "permission_id", "granted_at"::text + from "frontpage"."user_permission" + where "user_id" = ${ParameterValue(compositeId.userId, null, UserId.toStatement)} AND "permission_id" = ${ParameterValue(compositeId.permissionId, null, PermissionId.toStatement)} + """.as(UserPermissionRow.rowParser(1).singleOpt) + } + override def selectByIds(compositeIds: Array[UserPermissionId])(implicit c: Connection): List[UserPermissionRow] = { + val userId = compositeIds.map(_.userId) + val permissionId = compositeIds.map(_.permissionId) + SQL"""select "user_id", "permission_id", "granted_at"::text + from "frontpage"."user_permission" + where ("user_id", "permission_id") + in (select unnest(${userId}), unnest(${permissionId})) + """.as(UserPermissionRow.rowParser(1).*) + + } + override def selectByIdsTracked(compositeIds: Array[UserPermissionId])(implicit c: Connection): Map[UserPermissionId, UserPermissionRow] = { + val byId = selectByIds(compositeIds).view.map(x => (x.compositeId, x)).toMap + compositeIds.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[UserPermissionFields, UserPermissionRow] = { + UpdateBuilder(""""frontpage"."user_permission"""", UserPermissionFields.structure, UserPermissionRow.rowParser) + } + override def update(row: UserPermissionRow)(implicit c: Connection): Boolean = { + val compositeId = row.compositeId + SQL"""update "frontpage"."user_permission" + set "granted_at" = ${ParameterValue(row.grantedAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp + where "user_id" = ${ParameterValue(compositeId.userId, null, UserId.toStatement)} AND "permission_id" = ${ParameterValue(compositeId.permissionId, null, PermissionId.toStatement)} + """.executeUpdate() > 0 + } + override def upsert(unsaved: UserPermissionRow)(implicit c: Connection): UserPermissionRow = { + SQL"""insert into "frontpage"."user_permission"("user_id", "permission_id", "granted_at") + values ( + ${ParameterValue(unsaved.userId, null, UserId.toStatement)}::uuid, + ${ParameterValue(unsaved.permissionId, null, PermissionId.toStatement)}::uuid, + ${ParameterValue(unsaved.grantedAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))}::timestamp + ) + on conflict ("user_id", "permission_id") + do update set + "granted_at" = EXCLUDED."granted_at" + returning "user_id", "permission_id", "granted_at"::text + """ + .executeInsert(UserPermissionRow.rowParser(1).single) + + } + override def upsertBatch(unsaved: Iterable[UserPermissionRow])(implicit c: Connection): List[UserPermissionRow] = { + def toNamedParameter(row: UserPermissionRow): List[NamedParameter] = List( + NamedParameter("user_id", ParameterValue(row.userId, null, UserId.toStatement)), + NamedParameter("permission_id", ParameterValue(row.permissionId, null, PermissionId.toStatement)), + NamedParameter("granted_at", ParameterValue(row.grantedAt, null, ToStatement.optionToStatement(TypoLocalDateTime.toStatement, TypoLocalDateTime.parameterMetadata))) + ) + unsaved.toList match { + case Nil => Nil + case head :: rest => + new anorm.adventureworks.ExecuteReturningSyntax.Ops( + BatchSql( + s"""insert into "frontpage"."user_permission"("user_id", "permission_id", "granted_at") + values ({user_id}::uuid, {permission_id}::uuid, {granted_at}::timestamp) + on conflict ("user_id", "permission_id") + do update set + "granted_at" = EXCLUDED."granted_at" + returning "user_id", "permission_id", "granted_at"::text + """, + toNamedParameter(head), + rest.map(toNamedParameter)* + ) + ).executeReturning(UserPermissionRow.rowParser(1).*) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[UserPermissionRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + SQL"""create temporary table user_permission_TEMP (like "frontpage"."user_permission") on commit drop""".execute(): @nowarn + streamingInsert(s"""copy user_permission_TEMP("user_id", "permission_id", "granted_at") from stdin""", batchSize, unsaved)(UserPermissionRow.text, c): @nowarn + SQL"""insert into "frontpage"."user_permission"("user_id", "permission_id", "granted_at") + select * from user_permission_TEMP + on conflict ("user_id", "permission_id") + do update set + "granted_at" = EXCLUDED."granted_at" + ; + drop table user_permission_TEMP;""".executeUpdate() + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepoMock.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepoMock.scala new file mode 100644 index 0000000000..e49f0b6784 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepoMock.scala @@ -0,0 +1,103 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import java.sql.Connection +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class UserPermissionRepoMock(toRow: Function1[UserPermissionRowUnsaved, UserPermissionRow], + map: scala.collection.mutable.Map[UserPermissionId, UserPermissionRow] = scala.collection.mutable.Map.empty) extends UserPermissionRepo { + override def delete: DeleteBuilder[UserPermissionFields, UserPermissionRow] = { + DeleteBuilderMock(DeleteParams.empty, UserPermissionFields.structure, map) + } + override def deleteById(compositeId: UserPermissionId)(implicit c: Connection): Boolean = { + map.remove(compositeId).isDefined + } + override def deleteByIds(compositeIds: Array[UserPermissionId])(implicit c: Connection): Int = { + compositeIds.map(id => map.remove(id)).count(_.isDefined) + } + override def insert(unsaved: UserPermissionRow)(implicit c: Connection): UserPermissionRow = { + val _ = if (map.contains(unsaved.compositeId)) + sys.error(s"id ${unsaved.compositeId} already exists") + else + map.put(unsaved.compositeId, unsaved) + + unsaved + } + override def insert(unsaved: UserPermissionRowUnsaved)(implicit c: Connection): UserPermissionRow = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Iterator[UserPermissionRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { row => + map += (row.compositeId -> row) + } + unsaved.size.toLong + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[UserPermissionRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.compositeId -> row) + } + unsaved.size.toLong + } + override def select: SelectBuilder[UserPermissionFields, UserPermissionRow] = { + SelectBuilderMock(UserPermissionFields.structure, () => map.values.toList, SelectParams.empty) + } + override def selectAll(implicit c: Connection): List[UserPermissionRow] = { + map.values.toList + } + override def selectById(compositeId: UserPermissionId)(implicit c: Connection): Option[UserPermissionRow] = { + map.get(compositeId) + } + override def selectByIds(compositeIds: Array[UserPermissionId])(implicit c: Connection): List[UserPermissionRow] = { + compositeIds.flatMap(map.get).toList + } + override def selectByIdsTracked(compositeIds: Array[UserPermissionId])(implicit c: Connection): Map[UserPermissionId, UserPermissionRow] = { + val byId = selectByIds(compositeIds).view.map(x => (x.compositeId, x)).toMap + compositeIds.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[UserPermissionFields, UserPermissionRow] = { + UpdateBuilderMock(UpdateParams.empty, UserPermissionFields.structure, map) + } + override def update(row: UserPermissionRow)(implicit c: Connection): Boolean = { + map.get(row.compositeId) match { + case Some(`row`) => false + case Some(_) => + map.put(row.compositeId, row): @nowarn + true + case None => false + } + } + override def upsert(unsaved: UserPermissionRow)(implicit c: Connection): UserPermissionRow = { + map.put(unsaved.compositeId, unsaved): @nowarn + unsaved + } + override def upsertBatch(unsaved: Iterable[UserPermissionRow])(implicit c: Connection): List[UserPermissionRow] = { + unsaved.map { row => + map += (row.compositeId -> row) + row + }.toList + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[UserPermissionRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + unsaved.foreach { row => + map += (row.compositeId -> row) + } + unsaved.size + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRow.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRow.scala new file mode 100644 index 0000000000..3ad384ae97 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRow.scala @@ -0,0 +1,78 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.permission.PermissionId +import adventureworks.frontpage.user.UserId +import anorm.Column +import anorm.RowParser +import anorm.Success +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** Table: frontpage.user_permission + Composite primary key: user_id, permission_id */ +case class UserPermissionRow( + /** Points to [[user.UserRow.id]] */ + userId: UserId, + /** Points to [[permission.PermissionRow.id]] */ + permissionId: PermissionId, + /** Default: now() */ + grantedAt: Option[TypoLocalDateTime] +){ + val compositeId: UserPermissionId = UserPermissionId(userId, permissionId) + val id = compositeId + def toUnsavedRow(grantedAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.Provided(this.grantedAt)): UserPermissionRowUnsaved = + UserPermissionRowUnsaved(userId, permissionId, grantedAt) + } + +object UserPermissionRow { + def apply(compositeId: UserPermissionId, grantedAt: Option[TypoLocalDateTime]) = + new UserPermissionRow(compositeId.userId, compositeId.permissionId, grantedAt) + implicit lazy val reads: Reads[UserPermissionRow] = Reads[UserPermissionRow](json => JsResult.fromTry( + Try( + UserPermissionRow( + userId = json.\("user_id").as(UserId.reads), + permissionId = json.\("permission_id").as(PermissionId.reads), + grantedAt = json.\("granted_at").toOption.map(_.as(TypoLocalDateTime.reads)) + ) + ) + ), + ) + def rowParser(idx: Int): RowParser[UserPermissionRow] = RowParser[UserPermissionRow] { row => + Success( + UserPermissionRow( + userId = row(idx + 0)(UserId.column), + permissionId = row(idx + 1)(PermissionId.column), + grantedAt = row(idx + 2)(Column.columnToOption(TypoLocalDateTime.column)) + ) + ) + } + implicit lazy val text: Text[UserPermissionRow] = Text.instance[UserPermissionRow]{ (row, sb) => + UserId.text.unsafeEncode(row.userId, sb) + sb.append(Text.DELIMETER) + PermissionId.text.unsafeEncode(row.permissionId, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.grantedAt, sb) + } + implicit lazy val writes: OWrites[UserPermissionRow] = OWrites[UserPermissionRow](o => + new JsObject(ListMap[String, JsValue]( + "user_id" -> UserId.writes.writes(o.userId), + "permission_id" -> PermissionId.writes.writes(o.permissionId), + "granted_at" -> Writes.OptionWrites(TypoLocalDateTime.writes).writes(o.grantedAt) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRowUnsaved.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRowUnsaved.scala new file mode 100644 index 0000000000..a3b9d0dc8f --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRowUnsaved.scala @@ -0,0 +1,67 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.permission.PermissionId +import adventureworks.frontpage.user.UserId +import play.api.libs.json.JsObject +import play.api.libs.json.JsResult +import play.api.libs.json.JsValue +import play.api.libs.json.OWrites +import play.api.libs.json.Reads +import play.api.libs.json.Writes +import scala.collection.immutable.ListMap +import scala.util.Try + +/** This class corresponds to a row in table `frontpage.user_permission` which has not been persisted yet */ +case class UserPermissionRowUnsaved( + /** Points to [[user.UserRow.id]] */ + userId: UserId, + /** Points to [[permission.PermissionRow.id]] */ + permissionId: PermissionId, + /** Default: now() */ + grantedAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault +) { + def toRow(grantedAtDefault: => Option[TypoLocalDateTime]): UserPermissionRow = + UserPermissionRow( + userId = userId, + permissionId = permissionId, + grantedAt = grantedAt match { + case Defaulted.UseDefault => grantedAtDefault + case Defaulted.Provided(value) => value + } + ) +} +object UserPermissionRowUnsaved { + implicit lazy val reads: Reads[UserPermissionRowUnsaved] = Reads[UserPermissionRowUnsaved](json => JsResult.fromTry( + Try( + UserPermissionRowUnsaved( + userId = json.\("user_id").as(UserId.reads), + permissionId = json.\("permission_id").as(PermissionId.reads), + grantedAt = json.\("granted_at").as(Defaulted.readsOpt(TypoLocalDateTime.reads)) + ) + ) + ), + ) + implicit lazy val text: Text[UserPermissionRowUnsaved] = Text.instance[UserPermissionRowUnsaved]{ (row, sb) => + UserId.text.unsafeEncode(row.userId, sb) + sb.append(Text.DELIMETER) + PermissionId.text.unsafeEncode(row.permissionId, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(TypoLocalDateTime.text)).unsafeEncode(row.grantedAt, sb) + } + implicit lazy val writes: OWrites[UserPermissionRowUnsaved] = OWrites[UserPermissionRowUnsaved](o => + new JsObject(ListMap[String, JsValue]( + "user_id" -> UserId.writes.writes(o.userId), + "permission_id" -> PermissionId.writes.writes(o.permissionId), + "granted_at" -> Defaulted.writes(Writes.OptionWrites(TypoLocalDateTime.writes)).writes(o.grantedAt) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/testInsert.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/testInsert.scala index e6b1bdd743..1d2b8582f9 100644 --- a/typo-tester-anorm/generated-and-checked-in/adventureworks/testInsert.scala +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/testInsert.scala @@ -31,13 +31,71 @@ import adventureworks.customtypes.TypoUUID import adventureworks.customtypes.TypoUnknownCitext import adventureworks.customtypes.TypoVector import adventureworks.customtypes.TypoXml -import adventureworks.humanresources.department.DepartmentId -import adventureworks.humanresources.department.DepartmentRepoImpl -import adventureworks.humanresources.department.DepartmentRow -import adventureworks.humanresources.department.DepartmentRowUnsaved -import adventureworks.humanresources.employee.EmployeeRepoImpl -import adventureworks.humanresources.employee.EmployeeRow -import adventureworks.humanresources.employee.EmployeeRowUnsaved +import adventureworks.frontpage.Email +import adventureworks.frontpage.OrderStatus +import adventureworks.frontpage.UserRole +import adventureworks.frontpage.UserStatus +import adventureworks.frontpage.address.AddressId +import adventureworks.frontpage.address.AddressRepoImpl +import adventureworks.frontpage.address.AddressRow +import adventureworks.frontpage.address.AddressRowUnsaved +import adventureworks.frontpage.category.CategoryId +import adventureworks.frontpage.category.CategoryRepoImpl +import adventureworks.frontpage.category.CategoryRow +import adventureworks.frontpage.category.CategoryRowUnsaved +import adventureworks.frontpage.company.CompanyId +import adventureworks.frontpage.company.CompanyRepoImpl +import adventureworks.frontpage.company.CompanyRow +import adventureworks.frontpage.company.CompanyRowUnsaved +import adventureworks.frontpage.customer.CustomerId +import adventureworks.frontpage.customer.CustomerRepoImpl +import adventureworks.frontpage.customer.CustomerRow +import adventureworks.frontpage.customer.CustomerRowUnsaved +import adventureworks.frontpage.department.DepartmentId +import adventureworks.frontpage.department.DepartmentRepoImpl +import adventureworks.frontpage.department.DepartmentRow +import adventureworks.frontpage.department.DepartmentRowUnsaved +import adventureworks.frontpage.employee.EmployeeId +import adventureworks.frontpage.employee.EmployeeRepoImpl +import adventureworks.frontpage.employee.EmployeeRow +import adventureworks.frontpage.employee.EmployeeRowUnsaved +import adventureworks.frontpage.location.LocationId +import adventureworks.frontpage.location.LocationRepoImpl +import adventureworks.frontpage.location.LocationRow +import adventureworks.frontpage.location.LocationRowUnsaved +import adventureworks.frontpage.order.OrderId +import adventureworks.frontpage.order.OrderRepoImpl +import adventureworks.frontpage.order.OrderRow +import adventureworks.frontpage.order.OrderRowUnsaved +import adventureworks.frontpage.order_item.OrderItemId +import adventureworks.frontpage.order_item.OrderItemRepoImpl +import adventureworks.frontpage.order_item.OrderItemRow +import adventureworks.frontpage.order_item.OrderItemRowUnsaved +import adventureworks.frontpage.permission.PermissionId +import adventureworks.frontpage.permission.PermissionRepoImpl +import adventureworks.frontpage.permission.PermissionRow +import adventureworks.frontpage.permission.PermissionRowUnsaved +import adventureworks.frontpage.person.PersonId +import adventureworks.frontpage.person.PersonRepoImpl +import adventureworks.frontpage.person.PersonRow +import adventureworks.frontpage.person.PersonRowUnsaved +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.product.ProductRepoImpl +import adventureworks.frontpage.product.ProductRow +import adventureworks.frontpage.product.ProductRowUnsaved +import adventureworks.frontpage.product_category.ProductCategoryRepoImpl +import adventureworks.frontpage.product_category.ProductCategoryRow +import adventureworks.frontpage.role.RoleId +import adventureworks.frontpage.role.RoleRepoImpl +import adventureworks.frontpage.role.RoleRow +import adventureworks.frontpage.role.RoleRowUnsaved +import adventureworks.frontpage.user.UserId +import adventureworks.frontpage.user.UserRepoImpl +import adventureworks.frontpage.user.UserRow +import adventureworks.frontpage.user.UserRowUnsaved +import adventureworks.frontpage.user_permission.UserPermissionRepoImpl +import adventureworks.frontpage.user_permission.UserPermissionRow +import adventureworks.frontpage.user_permission.UserPermissionRowUnsaved import adventureworks.humanresources.employeedepartmenthistory.EmployeedepartmenthistoryRepoImpl import adventureworks.humanresources.employeedepartmenthistory.EmployeedepartmenthistoryRow import adventureworks.humanresources.employeedepartmenthistory.EmployeedepartmenthistoryRowUnsaved @@ -52,10 +110,6 @@ import adventureworks.humanresources.shift.ShiftId import adventureworks.humanresources.shift.ShiftRepoImpl import adventureworks.humanresources.shift.ShiftRow import adventureworks.humanresources.shift.ShiftRowUnsaved -import adventureworks.person.address.AddressId -import adventureworks.person.address.AddressRepoImpl -import adventureworks.person.address.AddressRow -import adventureworks.person.address.AddressRowUnsaved import adventureworks.person.addresstype.AddresstypeId import adventureworks.person.addresstype.AddresstypeRepoImpl import adventureworks.person.addresstype.AddresstypeRow @@ -84,9 +138,6 @@ import adventureworks.person.emailaddress.EmailaddressRowUnsaved import adventureworks.person.password.PasswordRepoImpl import adventureworks.person.password.PasswordRow import adventureworks.person.password.PasswordRowUnsaved -import adventureworks.person.person.PersonRepoImpl -import adventureworks.person.person.PersonRow -import adventureworks.person.person.PersonRowUnsaved import adventureworks.person.personphone.PersonphoneRepoImpl import adventureworks.person.personphone.PersonphoneRow import adventureworks.person.personphone.PersonphoneRowUnsaved @@ -113,14 +164,6 @@ import adventureworks.production.illustration.IllustrationId import adventureworks.production.illustration.IllustrationRepoImpl import adventureworks.production.illustration.IllustrationRow import adventureworks.production.illustration.IllustrationRowUnsaved -import adventureworks.production.location.LocationId -import adventureworks.production.location.LocationRepoImpl -import adventureworks.production.location.LocationRow -import adventureworks.production.location.LocationRowUnsaved -import adventureworks.production.product.ProductId -import adventureworks.production.product.ProductRepoImpl -import adventureworks.production.product.ProductRow -import adventureworks.production.product.ProductRowUnsaved import adventureworks.production.productcategory.ProductcategoryId import adventureworks.production.productcategory.ProductcategoryRepoImpl import adventureworks.production.productcategory.ProductcategoryRow @@ -267,10 +310,6 @@ import adventureworks.sales.currencyrate.CurrencyrateId import adventureworks.sales.currencyrate.CurrencyrateRepoImpl import adventureworks.sales.currencyrate.CurrencyrateRow import adventureworks.sales.currencyrate.CurrencyrateRowUnsaved -import adventureworks.sales.customer.CustomerId -import adventureworks.sales.customer.CustomerRepoImpl -import adventureworks.sales.customer.CustomerRow -import adventureworks.sales.customer.CustomerRowUnsaved import adventureworks.sales.personcreditcard.PersoncreditcardRepoImpl import adventureworks.sales.personcreditcard.PersoncreditcardRow import adventureworks.sales.personcreditcard.PersoncreditcardRowUnsaved @@ -331,11 +370,88 @@ import java.time.ZoneOffset import scala.util.Random class TestInsert(random: Random, domainInsert: TestDomainInsert) { + def frontpageAddress(city: String = random.alphanumeric.take(20).mkString, + country: String = random.alphanumeric.take(20).mkString, + id: Defaulted[AddressId] = Defaulted.UseDefault + )(implicit c: Connection): AddressRow = (new AddressRepoImpl).insert(new AddressRowUnsaved(city = city, country = country, id = id)) + def frontpageCategory(name: String = random.alphanumeric.take(20).mkString, id: Defaulted[CategoryId] = Defaulted.UseDefault)(implicit c: Connection): CategoryRow = (new CategoryRepoImpl).insert(new CategoryRowUnsaved(name = name, id = id)) + def frontpageCompany(name: String = random.alphanumeric.take(20).mkString, id: Defaulted[CompanyId] = Defaulted.UseDefault)(implicit c: Connection): CompanyRow = (new CompanyRepoImpl).insert(new CompanyRowUnsaved(name = name, id = id)) + def frontpageCustomer(userId: Option[UserId] = None, + companyName: Option[String] = if (random.nextBoolean()) None else Some(random.alphanumeric.take(20).mkString), + creditLimit: Option[BigDecimal] = if (random.nextBoolean()) None else Some(BigDecimal.decimal(random.nextDouble())), + id: Defaulted[CustomerId] = Defaulted.UseDefault, + verified: Defaulted[Option[Boolean]] = Defaulted.UseDefault + )(implicit c: Connection): CustomerRow = (new CustomerRepoImpl).insert(new CustomerRowUnsaved(userId = userId, companyName = companyName, creditLimit = creditLimit, id = id, verified = verified)) + def frontpageDepartment(name: String = random.alphanumeric.take(20).mkString, + budget: Option[BigDecimal] = if (random.nextBoolean()) None else Some(BigDecimal.decimal(random.nextDouble())), + companyId: Option[CompanyId] = None, + id: Defaulted[DepartmentId] = Defaulted.UseDefault + )(implicit c: Connection): DepartmentRow = (new DepartmentRepoImpl).insert(new DepartmentRowUnsaved(name = name, budget = budget, companyId = companyId, id = id)) + def frontpageEmployee(personId: PersonId, + salary: Option[BigDecimal] = if (random.nextBoolean()) None else Some(BigDecimal.decimal(random.nextDouble())), + id: Defaulted[EmployeeId] = Defaulted.UseDefault + )(implicit c: Connection): EmployeeRow = (new EmployeeRepoImpl).insert(new EmployeeRowUnsaved(personId = personId, salary = salary, id = id)) + def frontpageLocation(name: String = random.alphanumeric.take(20).mkString, + position: Option[TypoPoint] = None, + area: Option[TypoPolygon] = None, + ipRange: Option[TypoInet] = None, + id: Defaulted[LocationId] = Defaulted.UseDefault, + metadata: Defaulted[Option[TypoJsonb]] = Defaulted.UseDefault + )(implicit c: Connection): LocationRow = (new LocationRepoImpl).insert(new LocationRowUnsaved(name = name, position = position, area = area, ipRange = ipRange, id = id, metadata = metadata)) + def frontpageOrder(userId: Option[UserId] = None, + productId: Option[ProductId] = None, + total: BigDecimal = BigDecimal.decimal(random.nextDouble()), + shippedAt: Option[TypoLocalDateTime] = if (random.nextBoolean()) None else Some(TypoLocalDateTime(LocalDateTime.of(LocalDate.ofEpochDay(random.nextInt(30000).toLong), LocalTime.ofSecondOfDay(random.nextInt(24 * 60 * 60).toLong)))), + id: Defaulted[OrderId] = Defaulted.UseDefault, + status: Defaulted[Option[OrderStatus]] = Defaulted.UseDefault, + createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault + )(implicit c: Connection): OrderRow = (new OrderRepoImpl).insert(new OrderRowUnsaved(userId = userId, productId = productId, total = total, shippedAt = shippedAt, id = id, status = status, createdAt = createdAt)) + def frontpageOrderItem(orderId: Option[OrderId] = None, + productId: Option[ProductId] = None, + quantity: Int = random.nextInt(), + price: BigDecimal = BigDecimal.decimal(random.nextDouble()), + shippedAt: Option[TypoLocalDateTime] = if (random.nextBoolean()) None else Some(TypoLocalDateTime(LocalDateTime.of(LocalDate.ofEpochDay(random.nextInt(30000).toLong), LocalTime.ofSecondOfDay(random.nextInt(24 * 60 * 60).toLong)))), + id: Defaulted[OrderItemId] = Defaulted.UseDefault + )(implicit c: Connection): OrderItemRow = (new OrderItemRepoImpl).insert(new OrderItemRowUnsaved(orderId = orderId, productId = productId, quantity = quantity, price = price, shippedAt = shippedAt, id = id)) + def frontpagePermission(name: String = random.alphanumeric.take(20).mkString, id: Defaulted[PermissionId] = Defaulted.UseDefault)(implicit c: Connection): PermissionRow = (new PermissionRepoImpl).insert(new PermissionRowUnsaved(name = name, id = id)) + def frontpagePerson(name: String = random.alphanumeric.take(20).mkString, + addressId: Option[AddressId] = None, + id: Defaulted[PersonId] = Defaulted.UseDefault, + createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault + )(implicit c: Connection): PersonRow = (new PersonRepoImpl).insert(new PersonRowUnsaved(name = name, addressId = addressId, id = id, createdAt = createdAt)) + def frontpageProduct(name: String = random.alphanumeric.take(20).mkString, + price: BigDecimal = BigDecimal.decimal(random.nextDouble()), + lastRestocked: Option[TypoLocalDateTime] = if (random.nextBoolean()) None else Some(TypoLocalDateTime(LocalDateTime.of(LocalDate.ofEpochDay(random.nextInt(30000).toLong), LocalTime.ofSecondOfDay(random.nextInt(24 * 60 * 60).toLong)))), + id: Defaulted[ProductId] = Defaulted.UseDefault, + inStock: Defaulted[Option[Boolean]] = Defaulted.UseDefault, + quantity: Defaulted[Option[Int]] = Defaulted.UseDefault, + lastModified: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault, + tags: Defaulted[Option[Array[String]]] = Defaulted.UseDefault, + categories: Defaulted[Option[Array[Int]]] = Defaulted.UseDefault, + prices: Defaulted[Option[Array[BigDecimal]]] = Defaulted.UseDefault, + attributes: Defaulted[Option[Array[TypoJsonb]]] = Defaulted.UseDefault + )(implicit c: Connection): ProductRow = (new ProductRepoImpl).insert(new ProductRowUnsaved(name = name, price = price, lastRestocked = lastRestocked, id = id, inStock = inStock, quantity = quantity, lastModified = lastModified, tags = tags, categories = categories, prices = prices, attributes = attributes)) + def frontpageProductCategory(productId: ProductId, categoryId: CategoryId)(implicit c: Connection): ProductCategoryRow = (new ProductCategoryRepoImpl).insert(new ProductCategoryRow(productId = productId, categoryId = categoryId)) + def frontpageRole(name: String = random.alphanumeric.take(20).mkString, id: Defaulted[RoleId] = Defaulted.UseDefault)(implicit c: Connection): RoleRow = (new RoleRepoImpl).insert(new RoleRowUnsaved(name = name, id = id)) + def frontpageUser(email: Email = domainInsert.frontpageEmail(random), + name: String = random.alphanumeric.take(20).mkString, + departmentId: Option[DepartmentId] = None, + managerId: Option[UserId] = None, + id: Defaulted[UserId] = Defaulted.UseDefault, + createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault, + status: Defaulted[Option[UserStatus]] = Defaulted.UseDefault, + verified: Defaulted[Option[Boolean]] = Defaulted.UseDefault, + role: Defaulted[Option[UserRole]] = Defaulted.UseDefault + )(implicit c: Connection): UserRow = (new UserRepoImpl).insert(new UserRowUnsaved(email = email, name = name, departmentId = departmentId, managerId = managerId, id = id, createdAt = createdAt, status = status, verified = verified, role = role)) + def frontpageUserPermission(userId: UserId, + permissionId: PermissionId, + grantedAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault + )(implicit c: Connection): UserPermissionRow = (new UserPermissionRepoImpl).insert(new UserPermissionRowUnsaved(userId = userId, permissionId = permissionId, grantedAt = grantedAt)) def humanresourcesDepartment(name: Name = domainInsert.publicName(random), groupname: Name = domainInsert.publicName(random), - departmentid: Defaulted[DepartmentId] = Defaulted.UseDefault, + departmentid: Defaulted[adventureworks.humanresources.department.DepartmentId] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault - )(implicit c: Connection): DepartmentRow = (new DepartmentRepoImpl).insert(new DepartmentRowUnsaved(name = name, groupname = groupname, departmentid = departmentid, modifieddate = modifieddate)) + )(implicit c: Connection): adventureworks.humanresources.department.DepartmentRow = (new adventureworks.humanresources.department.DepartmentRepoImpl).insert(new adventureworks.humanresources.department.DepartmentRowUnsaved(name = name, groupname = groupname, departmentid = departmentid, modifieddate = modifieddate)) def humanresourcesEmployee(businessentityid: BusinessentityId, birthdate: TypoLocalDate, maritalstatus: /* bpchar, max 1 chars */ String, @@ -351,9 +467,9 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault, organizationnode: Defaulted[Option[String]] = Defaulted.UseDefault - )(implicit c: Connection): EmployeeRow = (new EmployeeRepoImpl).insert(new EmployeeRowUnsaved(businessentityid = businessentityid, birthdate = birthdate, maritalstatus = maritalstatus, gender = gender, hiredate = hiredate, nationalidnumber = nationalidnumber, loginid = loginid, jobtitle = jobtitle, salariedflag = salariedflag, vacationhours = vacationhours, sickleavehours = sickleavehours, currentflag = currentflag, rowguid = rowguid, modifieddate = modifieddate, organizationnode = organizationnode)) + )(implicit c: Connection): adventureworks.humanresources.employee.EmployeeRow = (new adventureworks.humanresources.employee.EmployeeRepoImpl).insert(new adventureworks.humanresources.employee.EmployeeRowUnsaved(businessentityid = businessentityid, birthdate = birthdate, maritalstatus = maritalstatus, gender = gender, hiredate = hiredate, nationalidnumber = nationalidnumber, loginid = loginid, jobtitle = jobtitle, salariedflag = salariedflag, vacationhours = vacationhours, sickleavehours = sickleavehours, currentflag = currentflag, rowguid = rowguid, modifieddate = modifieddate, organizationnode = organizationnode)) def humanresourcesEmployeedepartmenthistory(businessentityid: BusinessentityId, - departmentid: DepartmentId, + departmentid: adventureworks.humanresources.department.DepartmentId, shiftid: ShiftId, startdate: TypoLocalDate, enddate: Option[TypoLocalDate] = None, @@ -382,10 +498,10 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { city: /* max 30 chars */ String = random.alphanumeric.take(20).mkString, postalcode: /* max 15 chars */ String = random.alphanumeric.take(15).mkString, spatiallocation: Option[TypoBytea] = None, - addressid: Defaulted[AddressId] = Defaulted.UseDefault, + addressid: Defaulted[adventureworks.person.address.AddressId] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault - )(implicit c: Connection): AddressRow = (new AddressRepoImpl).insert(new AddressRowUnsaved(stateprovinceid = stateprovinceid, addressline1 = addressline1, addressline2 = addressline2, city = city, postalcode = postalcode, spatiallocation = spatiallocation, addressid = addressid, rowguid = rowguid, modifieddate = modifieddate)) + )(implicit c: Connection): adventureworks.person.address.AddressRow = (new adventureworks.person.address.AddressRepoImpl).insert(new adventureworks.person.address.AddressRowUnsaved(stateprovinceid = stateprovinceid, addressline1 = addressline1, addressline2 = addressline2, city = city, postalcode = postalcode, spatiallocation = spatiallocation, addressid = addressid, rowguid = rowguid, modifieddate = modifieddate)) def personAddresstype(name: Name = domainInsert.publicName(random), addresstypeid: Defaulted[AddresstypeId] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, @@ -396,7 +512,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault )(implicit c: Connection): BusinessentityRow = (new BusinessentityRepoImpl).insert(new BusinessentityRowUnsaved(businessentityid = businessentityid, rowguid = rowguid, modifieddate = modifieddate)) def personBusinessentityaddress(businessentityid: BusinessentityId, - addressid: AddressId, + addressid: adventureworks.person.address.AddressId, addresstypeid: AddresstypeId, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault @@ -440,7 +556,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { emailpromotion: Defaulted[Int] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault - )(implicit c: Connection): PersonRow = (new PersonRepoImpl).insert(new PersonRowUnsaved(businessentityid = businessentityid, persontype = persontype, firstname = firstname, title = title, middlename = middlename, lastname = lastname, suffix = suffix, additionalcontactinfo = additionalcontactinfo, demographics = demographics, namestyle = namestyle, emailpromotion = emailpromotion, rowguid = rowguid, modifieddate = modifieddate)) + )(implicit c: Connection): adventureworks.person.person.PersonRow = (new adventureworks.person.person.PersonRepoImpl).insert(new adventureworks.person.person.PersonRowUnsaved(businessentityid = businessentityid, persontype = persontype, firstname = firstname, title = title, middlename = middlename, lastname = lastname, suffix = suffix, additionalcontactinfo = additionalcontactinfo, demographics = demographics, namestyle = namestyle, emailpromotion = emailpromotion, rowguid = rowguid, modifieddate = modifieddate)) def personPersonphone(businessentityid: BusinessentityId, phonenumbertypeid: PhonenumbertypeId, phonenumber: Phone = domainInsert.publicPhone(random), @@ -459,10 +575,10 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault )(implicit c: Connection): StateprovinceRow = (new StateprovinceRepoImpl).insert(new StateprovinceRowUnsaved(countryregioncode = countryregioncode, territoryid = territoryid, stateprovincecode = stateprovincecode, name = name, stateprovinceid = stateprovinceid, isonlystateprovinceflag = isonlystateprovinceflag, rowguid = rowguid, modifieddate = modifieddate)) - def productionBillofmaterials(componentid: ProductId, + def productionBillofmaterials(componentid: adventureworks.production.product.ProductId, unitmeasurecode: UnitmeasureId, bomlevel: TypoShort, - productassemblyid: Option[ProductId] = None, + productassemblyid: Option[adventureworks.production.product.ProductId] = None, enddate: Option[TypoLocalDateTime] = None, billofmaterialsid: Defaulted[Int] = Defaulted.UseDefault, startdate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault, @@ -492,11 +608,11 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault )(implicit c: Connection): IllustrationRow = (new IllustrationRepoImpl).insert(new IllustrationRowUnsaved(diagram = diagram, illustrationid = illustrationid, modifieddate = modifieddate)) def productionLocation(name: Name = domainInsert.publicName(random), - locationid: Defaulted[LocationId] = Defaulted.UseDefault, + locationid: Defaulted[adventureworks.production.location.LocationId] = Defaulted.UseDefault, costrate: Defaulted[BigDecimal] = Defaulted.UseDefault, availability: Defaulted[BigDecimal] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault - )(implicit c: Connection): LocationRow = (new LocationRepoImpl).insert(new LocationRowUnsaved(name = name, locationid = locationid, costrate = costrate, availability = availability, modifieddate = modifieddate)) + )(implicit c: Connection): adventureworks.production.location.LocationRow = (new adventureworks.production.location.LocationRepoImpl).insert(new adventureworks.production.location.LocationRowUnsaved(name = name, locationid = locationid, costrate = costrate, availability = availability, modifieddate = modifieddate)) def productionProduct(safetystocklevel: TypoShort, reorderpoint: TypoShort, standardcost: BigDecimal, @@ -517,18 +633,18 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { productmodelid: Option[ProductmodelId] = None, sellenddate: Option[TypoLocalDateTime] = None, discontinueddate: Option[TypoLocalDateTime] = if (random.nextBoolean()) None else Some(TypoLocalDateTime(LocalDateTime.of(LocalDate.ofEpochDay(random.nextInt(30000).toLong), LocalTime.ofSecondOfDay(random.nextInt(24 * 60 * 60).toLong)))), - productid: Defaulted[ProductId] = Defaulted.UseDefault, + productid: Defaulted[adventureworks.production.product.ProductId] = Defaulted.UseDefault, makeflag: Defaulted[Flag] = Defaulted.UseDefault, finishedgoodsflag: Defaulted[Flag] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault - )(implicit c: Connection): ProductRow = (new ProductRepoImpl).insert(new ProductRowUnsaved(safetystocklevel = safetystocklevel, reorderpoint = reorderpoint, standardcost = standardcost, listprice = listprice, daystomanufacture = daystomanufacture, sellstartdate = sellstartdate, name = name, productnumber = productnumber, color = color, size = size, sizeunitmeasurecode = sizeunitmeasurecode, weightunitmeasurecode = weightunitmeasurecode, weight = weight, productline = productline, `class` = `class`, style = style, productsubcategoryid = productsubcategoryid, productmodelid = productmodelid, sellenddate = sellenddate, discontinueddate = discontinueddate, productid = productid, makeflag = makeflag, finishedgoodsflag = finishedgoodsflag, rowguid = rowguid, modifieddate = modifieddate)) + )(implicit c: Connection): adventureworks.production.product.ProductRow = (new adventureworks.production.product.ProductRepoImpl).insert(new adventureworks.production.product.ProductRowUnsaved(safetystocklevel = safetystocklevel, reorderpoint = reorderpoint, standardcost = standardcost, listprice = listprice, daystomanufacture = daystomanufacture, sellstartdate = sellstartdate, name = name, productnumber = productnumber, color = color, size = size, sizeunitmeasurecode = sizeunitmeasurecode, weightunitmeasurecode = weightunitmeasurecode, weight = weight, productline = productline, `class` = `class`, style = style, productsubcategoryid = productsubcategoryid, productmodelid = productmodelid, sellenddate = sellenddate, discontinueddate = discontinueddate, productid = productid, makeflag = makeflag, finishedgoodsflag = finishedgoodsflag, rowguid = rowguid, modifieddate = modifieddate)) def productionProductcategory(name: Name = domainInsert.publicName(random), productcategoryid: Defaulted[ProductcategoryId] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault )(implicit c: Connection): ProductcategoryRow = (new ProductcategoryRepoImpl).insert(new ProductcategoryRowUnsaved(name = name, productcategoryid = productcategoryid, rowguid = rowguid, modifieddate = modifieddate)) - def productionProductcosthistory(productid: ProductId, + def productionProductcosthistory(productid: adventureworks.production.product.ProductId, startdate: TypoLocalDateTime, standardcost: BigDecimal, enddate: Option[TypoLocalDateTime] = None, @@ -539,19 +655,19 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault )(implicit c: Connection): ProductdescriptionRow = (new ProductdescriptionRepoImpl).insert(new ProductdescriptionRowUnsaved(description = description, productdescriptionid = productdescriptionid, rowguid = rowguid, modifieddate = modifieddate)) - def productionProductdocument(productid: ProductId, + def productionProductdocument(productid: adventureworks.production.product.ProductId, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault, documentnode: Defaulted[DocumentId] = Defaulted.UseDefault )(implicit c: Connection): ProductdocumentRow = (new ProductdocumentRepoImpl).insert(new ProductdocumentRowUnsaved(productid = productid, modifieddate = modifieddate, documentnode = documentnode)) - def productionProductinventory(productid: ProductId, - locationid: LocationId, + def productionProductinventory(productid: adventureworks.production.product.ProductId, + locationid: adventureworks.production.location.LocationId, bin: TypoShort, shelf: /* max 10 chars */ String = random.alphanumeric.take(10).mkString, quantity: Defaulted[TypoShort] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault )(implicit c: Connection): ProductinventoryRow = (new ProductinventoryRepoImpl).insert(new ProductinventoryRowUnsaved(productid = productid, locationid = locationid, bin = bin, shelf = shelf, quantity = quantity, rowguid = rowguid, modifieddate = modifieddate)) - def productionProductlistpricehistory(productid: ProductId, + def productionProductlistpricehistory(productid: adventureworks.production.product.ProductId, startdate: TypoLocalDateTime, listprice: BigDecimal, enddate: Option[TypoLocalDateTime] = None, @@ -580,12 +696,12 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { productphotoid: Defaulted[ProductphotoId] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault )(implicit c: Connection): ProductphotoRow = (new ProductphotoRepoImpl).insert(new ProductphotoRowUnsaved(thumbnailphoto = thumbnailphoto, thumbnailphotofilename = thumbnailphotofilename, largephoto = largephoto, largephotofilename = largephotofilename, productphotoid = productphotoid, modifieddate = modifieddate)) - def productionProductproductphoto(productid: ProductId, + def productionProductproductphoto(productid: adventureworks.production.product.ProductId, productphotoid: ProductphotoId, primary: Defaulted[Flag] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault )(implicit c: Connection): ProductproductphotoRow = (new ProductproductphotoRepoImpl).insert(new ProductproductphotoRowUnsaved(productid = productid, productphotoid = productphotoid, primary = primary, modifieddate = modifieddate)) - def productionProductreview(productid: ProductId, + def productionProductreview(productid: adventureworks.production.product.ProductId, rating: Int, reviewername: Name = domainInsert.publicName(random), emailaddress: /* max 50 chars */ String = random.alphanumeric.take(20).mkString, @@ -604,7 +720,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { scrapreasonid: Defaulted[ScrapreasonId] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault )(implicit c: Connection): ScrapreasonRow = (new ScrapreasonRepoImpl).insert(new ScrapreasonRowUnsaved(name = name, scrapreasonid = scrapreasonid, modifieddate = modifieddate)) - def productionTransactionhistory(productid: ProductId, + def productionTransactionhistory(productid: adventureworks.production.product.ProductId, transactiontype: /* bpchar, max 1 chars */ String, referenceorderid: Int = random.nextInt(), quantity: Int = random.nextInt(), @@ -628,7 +744,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { name: Name = domainInsert.publicName(random), modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault )(implicit c: Connection): UnitmeasureRow = (new UnitmeasureRepoImpl).insert(new UnitmeasureRowUnsaved(unitmeasurecode = unitmeasurecode, name = name, modifieddate = modifieddate)) - def productionWorkorder(productid: ProductId, + def productionWorkorder(productid: adventureworks.production.product.ProductId, orderqty: Int, scrappedqty: TypoShort, startdate: TypoLocalDateTime, @@ -639,7 +755,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault )(implicit c: Connection): WorkorderRow = (new WorkorderRepoImpl).insert(new WorkorderRowUnsaved(productid = productid, orderqty = orderqty, scrappedqty = scrappedqty, startdate = startdate, enddate = enddate, duedate = duedate, scrapreasonid = scrapreasonid, workorderid = workorderid, modifieddate = modifieddate)) def productionWorkorderrouting(workorderid: WorkorderId, - locationid: LocationId, + locationid: adventureworks.production.location.LocationId, scheduledstartdate: TypoLocalDateTime, scheduledenddate: TypoLocalDateTime, plannedcost: BigDecimal, @@ -821,7 +937,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { verifiedOn: Option[TypoInstant] = if (random.nextBoolean()) None else Some(TypoInstant(Instant.ofEpochMilli(1000000000000L + random.nextLong(1000000000000L)))), createdAt: Defaulted[TypoInstant] = Defaulted.UseDefault )(implicit c: Connection): UsersRow = (new UsersRepoImpl).insert(new UsersRowUnsaved(email = email, userId = userId, name = name, lastName = lastName, password = password, verifiedOn = verifiedOn, createdAt = createdAt)) - def purchasingProductvendor(productid: ProductId, + def purchasingProductvendor(productid: adventureworks.production.product.ProductId, businessentityid: BusinessentityId, averageleadtime: Int, standardprice: BigDecimal, @@ -888,10 +1004,10 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { def salesCustomer(personid: Option[BusinessentityId] = None, storeid: Option[BusinessentityId] = None, territoryid: Option[SalesterritoryId] = None, - customerid: Defaulted[CustomerId] = Defaulted.UseDefault, + customerid: Defaulted[adventureworks.sales.customer.CustomerId] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault - )(implicit c: Connection): CustomerRow = (new CustomerRepoImpl).insert(new CustomerRowUnsaved(personid = personid, storeid = storeid, territoryid = territoryid, customerid = customerid, rowguid = rowguid, modifieddate = modifieddate)) + )(implicit c: Connection): adventureworks.sales.customer.CustomerRow = (new adventureworks.sales.customer.CustomerRepoImpl).insert(new adventureworks.sales.customer.CustomerRowUnsaved(personid = personid, storeid = storeid, territoryid = territoryid, customerid = customerid, rowguid = rowguid, modifieddate = modifieddate)) def salesPersoncreditcard(businessentityid: BusinessentityId, creditcardid: /* user-picked */ CustomCreditcardId, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault @@ -907,9 +1023,9 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault )(implicit c: Connection): SalesorderdetailRow = (new SalesorderdetailRepoImpl).insert(new SalesorderdetailRowUnsaved(salesorderid = salesorderid, carriertrackingnumber = carriertrackingnumber, orderqty = orderqty, productid = SpecialofferproductId.productid, specialofferid = SpecialofferproductId.specialofferid, unitprice = unitprice, salesorderdetailid = salesorderdetailid, unitpricediscount = unitpricediscount, rowguid = rowguid, modifieddate = modifieddate)) def salesSalesorderheader(duedate: TypoLocalDateTime, - customerid: CustomerId, - billtoaddressid: AddressId, - shiptoaddressid: AddressId, + customerid: adventureworks.sales.customer.CustomerId, + billtoaddressid: adventureworks.person.address.AddressId, + shiptoaddressid: adventureworks.person.address.AddressId, shipmethodid: ShipmethodId, shipdate: Option[TypoLocalDateTime] = None, purchaseordernumber: Option[OrderNumber] = if (random.nextBoolean()) None else Some(domainInsert.publicOrderNumber(random)), @@ -983,7 +1099,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault )(implicit c: Connection): SalesterritoryhistoryRow = (new SalesterritoryhistoryRepoImpl).insert(new SalesterritoryhistoryRowUnsaved(businessentityid = businessentityid, territoryid = territoryid, startdate = startdate, enddate = enddate, rowguid = rowguid, modifieddate = modifieddate)) - def salesShoppingcartitem(productid: ProductId, + def salesShoppingcartitem(productid: adventureworks.production.product.ProductId, shoppingcartid: /* max 50 chars */ String = random.alphanumeric.take(20).mkString, shoppingcartitemid: Defaulted[ShoppingcartitemId] = Defaulted.UseDefault, quantity: Defaulted[Int] = Defaulted.UseDefault, @@ -1003,7 +1119,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault )(implicit c: Connection): SpecialofferRow = (new SpecialofferRepoImpl).insert(new SpecialofferRowUnsaved(startdate = startdate, enddate = enddate, description = description, `type` = `type`, category = category, maxqty = maxqty, specialofferid = specialofferid, discountpct = discountpct, minqty = minqty, rowguid = rowguid, modifieddate = modifieddate)) def salesSpecialofferproduct(specialofferid: SpecialofferId, - productid: ProductId, + productid: adventureworks.production.product.ProductId, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault )(implicit c: Connection): SpecialofferproductRow = (new SpecialofferproductRepoImpl).insert(new SpecialofferproductRowUnsaved(specialofferid = specialofferid, productid = productid, rowguid = rowguid, modifieddate = modifieddate)) diff --git a/typo-tester-anorm/src/scala/adventureworks/DomainInsert.scala b/typo-tester-anorm/src/scala/adventureworks/DomainInsert.scala index 3906b94e03..8e5b3d1464 100644 --- a/typo-tester-anorm/src/scala/adventureworks/DomainInsert.scala +++ b/typo-tester-anorm/src/scala/adventureworks/DomainInsert.scala @@ -13,4 +13,5 @@ object DomainInsert extends TestDomainInsert { override def publicPhone(random: Random): Phone = Phone(random.nextString(10)) override def publicShortText(random: Random): ShortText = ShortText(random.nextString(10)) override def publicOrderNumber(random: Random): OrderNumber = OrderNumber(random.nextString(10)) + override def frontpageEmail(random: Random): frontpage.Email = frontpage.Email(s"user${random.nextInt(1000)}@example.com") } diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/TestDomainInsert.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/TestDomainInsert.scala index a8e733e7a6..4d2a2dd08c 100644 --- a/typo-tester-doobie/generated-and-checked-in/adventureworks/TestDomainInsert.scala +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/TestDomainInsert.scala @@ -5,6 +5,7 @@ */ package adventureworks +import adventureworks.frontpage.Email import adventureworks.public.AccountNumber import adventureworks.public.Flag import adventureworks.public.Mydomain @@ -16,6 +17,10 @@ import adventureworks.public.ShortText import scala.util.Random trait TestDomainInsert { + /** Domain `frontpage.email` + * Constraint: CHECK ((VALUE ~ '^[^@]+@[^@]+\.[^@]+$'::text)) + */ + def frontpageEmail(random: Random): Email /** Domain `public.AccountNumber` * No constraint */ diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/Email.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/Email.scala new file mode 100644 index 0000000000..4059adeede --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/Email.scala @@ -0,0 +1,34 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage + +import doobie.postgres.Text +import doobie.util.Get +import doobie.util.Put +import doobie.util.meta.Meta +import io.circe.Decoder +import io.circe.Encoder +import typo.dsl.Bijection + +/** Domain `frontpage.email` + * Constraint: CHECK ((VALUE ~ '^[^@]+@[^@]+\.[^@]+$'::text)) + */ +case class Email(value: String) +object Email { + implicit lazy val arrayGet: Get[Array[Email]] = adventureworks.StringArrayMeta.get.map(_.map(Email.apply)) + implicit lazy val arrayPut: Put[Array[Email]] = adventureworks.StringArrayMeta.put.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[Email, String] = Bijection[Email, String](_.value)(Email.apply) + implicit lazy val decoder: Decoder[Email] = Decoder.decodeString.map(Email.apply) + implicit lazy val encoder: Encoder[Email] = Encoder.encodeString.contramap(_.value) + implicit lazy val get: Get[Email] = Meta.StringMeta.get.map(Email.apply) + implicit lazy val ordering: Ordering[Email] = Ordering.by(_.value) + implicit lazy val put: Put[Email] = Meta.StringMeta.put.contramap(_.value) + implicit lazy val text: Text[Email] = new Text[Email] { + override def unsafeEncode(v: Email, sb: StringBuilder) = Text.stringInstance.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: Email, sb: StringBuilder) = Text.stringInstance.unsafeArrayEncode(v.value, sb) + } +} \ No newline at end of file diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/OrderStatus.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/OrderStatus.scala new file mode 100644 index 0000000000..651a149f26 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/OrderStatus.scala @@ -0,0 +1,57 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage + +import cats.data.NonEmptyList +import doobie.enumerated.JdbcType +import doobie.postgres.Text +import doobie.util.Get +import doobie.util.Put +import doobie.util.Read +import doobie.util.Write +import doobie.util.meta.Meta +import io.circe.Decoder +import io.circe.Encoder + +/** Enum `frontpage.order_status` + * - pending + * - active + * - shipped + * - cancelled + */ +sealed abstract class OrderStatus(val value: String) + +object OrderStatus { + def apply(str: String): Either[String, OrderStatus] = + ByName.get(str).toRight(s"'$str' does not match any of the following legal values: $Names") + def force(str: String): OrderStatus = + apply(str) match { + case Left(msg) => sys.error(msg) + case Right(value) => value + } + case object pending extends OrderStatus("pending") + case object active extends OrderStatus("active") + case object shipped extends OrderStatus("shipped") + case object cancelled extends OrderStatus("cancelled") + val All: List[OrderStatus] = List(pending, active, shipped, cancelled) + val Names: String = All.map(_.value).mkString(", ") + val ByName: Map[String, OrderStatus] = All.map(x => (x.value, x)).toMap + + implicit lazy val arrayGet: Get[Array[OrderStatus]] = adventureworks.StringArrayMeta.get.map(_.map(force)) + implicit lazy val arrayPut: Put[Array[OrderStatus]] = Put.Advanced.array[AnyRef](NonEmptyList.one("frontpage.order_status[]"), "frontpage.order_status").contramap(_.map(_.value)) + implicit lazy val decoder: Decoder[OrderStatus] = Decoder.decodeString.emap(OrderStatus.apply) + implicit lazy val encoder: Encoder[OrderStatus] = Encoder.encodeString.contramap(_.value) + implicit lazy val get: Get[OrderStatus] = Meta.StringMeta.get.temap(OrderStatus.apply) + implicit lazy val ordering: Ordering[OrderStatus] = Ordering.by(_.value) + implicit lazy val put: Put[OrderStatus] = Put.Advanced.one[OrderStatus](JdbcType.Other, NonEmptyList.one("frontpage.order_status"), (ps, i, a) => ps.setString(i, a.value), (rs, i, a) => rs.updateString(i, a.value)) + implicit lazy val read: Read[OrderStatus] = new Read.Single(get) + implicit lazy val text: Text[OrderStatus] = new Text[OrderStatus] { + override def unsafeEncode(v: OrderStatus, sb: StringBuilder) = Text.stringInstance.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: OrderStatus, sb: StringBuilder) = Text.stringInstance.unsafeArrayEncode(v.value, sb) + } + implicit lazy val write: Write[OrderStatus] = new Write.Single(put) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/UserRole.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/UserRole.scala new file mode 100644 index 0000000000..1ba89acce4 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/UserRole.scala @@ -0,0 +1,55 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage + +import cats.data.NonEmptyList +import doobie.enumerated.JdbcType +import doobie.postgres.Text +import doobie.util.Get +import doobie.util.Put +import doobie.util.Read +import doobie.util.Write +import doobie.util.meta.Meta +import io.circe.Decoder +import io.circe.Encoder + +/** Enum `frontpage.user_role` + * - admin + * - manager + * - employee + */ +sealed abstract class UserRole(val value: String) + +object UserRole { + def apply(str: String): Either[String, UserRole] = + ByName.get(str).toRight(s"'$str' does not match any of the following legal values: $Names") + def force(str: String): UserRole = + apply(str) match { + case Left(msg) => sys.error(msg) + case Right(value) => value + } + case object admin extends UserRole("admin") + case object manager extends UserRole("manager") + case object employee extends UserRole("employee") + val All: List[UserRole] = List(admin, manager, employee) + val Names: String = All.map(_.value).mkString(", ") + val ByName: Map[String, UserRole] = All.map(x => (x.value, x)).toMap + + implicit lazy val arrayGet: Get[Array[UserRole]] = adventureworks.StringArrayMeta.get.map(_.map(force)) + implicit lazy val arrayPut: Put[Array[UserRole]] = Put.Advanced.array[AnyRef](NonEmptyList.one("frontpage.user_role[]"), "frontpage.user_role").contramap(_.map(_.value)) + implicit lazy val decoder: Decoder[UserRole] = Decoder.decodeString.emap(UserRole.apply) + implicit lazy val encoder: Encoder[UserRole] = Encoder.encodeString.contramap(_.value) + implicit lazy val get: Get[UserRole] = Meta.StringMeta.get.temap(UserRole.apply) + implicit lazy val ordering: Ordering[UserRole] = Ordering.by(_.value) + implicit lazy val put: Put[UserRole] = Put.Advanced.one[UserRole](JdbcType.Other, NonEmptyList.one("frontpage.user_role"), (ps, i, a) => ps.setString(i, a.value), (rs, i, a) => rs.updateString(i, a.value)) + implicit lazy val read: Read[UserRole] = new Read.Single(get) + implicit lazy val text: Text[UserRole] = new Text[UserRole] { + override def unsafeEncode(v: UserRole, sb: StringBuilder) = Text.stringInstance.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: UserRole, sb: StringBuilder) = Text.stringInstance.unsafeArrayEncode(v.value, sb) + } + implicit lazy val write: Write[UserRole] = new Write.Single(put) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/UserStatus.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/UserStatus.scala new file mode 100644 index 0000000000..7b4fed2113 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/UserStatus.scala @@ -0,0 +1,55 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage + +import cats.data.NonEmptyList +import doobie.enumerated.JdbcType +import doobie.postgres.Text +import doobie.util.Get +import doobie.util.Put +import doobie.util.Read +import doobie.util.Write +import doobie.util.meta.Meta +import io.circe.Decoder +import io.circe.Encoder + +/** Enum `frontpage.user_status` + * - active + * - inactive + * - suspended + */ +sealed abstract class UserStatus(val value: String) + +object UserStatus { + def apply(str: String): Either[String, UserStatus] = + ByName.get(str).toRight(s"'$str' does not match any of the following legal values: $Names") + def force(str: String): UserStatus = + apply(str) match { + case Left(msg) => sys.error(msg) + case Right(value) => value + } + case object active extends UserStatus("active") + case object inactive extends UserStatus("inactive") + case object suspended extends UserStatus("suspended") + val All: List[UserStatus] = List(active, inactive, suspended) + val Names: String = All.map(_.value).mkString(", ") + val ByName: Map[String, UserStatus] = All.map(x => (x.value, x)).toMap + + implicit lazy val arrayGet: Get[Array[UserStatus]] = adventureworks.StringArrayMeta.get.map(_.map(force)) + implicit lazy val arrayPut: Put[Array[UserStatus]] = Put.Advanced.array[AnyRef](NonEmptyList.one("frontpage.user_status[]"), "frontpage.user_status").contramap(_.map(_.value)) + implicit lazy val decoder: Decoder[UserStatus] = Decoder.decodeString.emap(UserStatus.apply) + implicit lazy val encoder: Encoder[UserStatus] = Encoder.encodeString.contramap(_.value) + implicit lazy val get: Get[UserStatus] = Meta.StringMeta.get.temap(UserStatus.apply) + implicit lazy val ordering: Ordering[UserStatus] = Ordering.by(_.value) + implicit lazy val put: Put[UserStatus] = Put.Advanced.one[UserStatus](JdbcType.Other, NonEmptyList.one("frontpage.user_status"), (ps, i, a) => ps.setString(i, a.value), (rs, i, a) => rs.updateString(i, a.value)) + implicit lazy val read: Read[UserStatus] = new Read.Single(get) + implicit lazy val text: Text[UserStatus] = new Text[UserStatus] { + override def unsafeEncode(v: UserStatus, sb: StringBuilder) = Text.stringInstance.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: UserStatus, sb: StringBuilder) = Text.stringInstance.unsafeArrayEncode(v.value, sb) + } + implicit lazy val write: Write[UserStatus] = new Write.Single(put) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressFields.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressFields.scala new file mode 100644 index 0000000000..ad9a616933 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressFields.scala @@ -0,0 +1,42 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.Structure.Relation + +trait AddressFields { + def id: IdField[AddressId, AddressRow] + def city: Field[String, AddressRow] + def country: Field[String, AddressRow] +} + +object AddressFields { + lazy val structure: Relation[AddressFields, AddressRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[AddressFields, AddressRow] { + + override lazy val fields: AddressFields = new AddressFields { + override def id = IdField[AddressId, AddressRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def city = Field[String, AddressRow](_path, "city", None, None, x => x.city, (row, value) => row.copy(city = value)) + override def country = Field[String, AddressRow](_path, "country", None, None, x => x.country, (row, value) => row.copy(country = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, AddressRow]] = + List[FieldLikeNoHkt[?, AddressRow]](fields.id, fields.city, fields.country) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressId.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressId.scala new file mode 100644 index 0000000000..96eb8dc6bd --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressId.scala @@ -0,0 +1,33 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import adventureworks.customtypes.TypoUUID +import doobie.postgres.Text +import doobie.util.Get +import doobie.util.Put +import io.circe.Decoder +import io.circe.Encoder +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.address` */ +case class AddressId(value: TypoUUID) extends AnyVal +object AddressId { + implicit lazy val arrayGet: Get[Array[AddressId]] = TypoUUID.arrayGet.map(_.map(AddressId.apply)) + implicit lazy val arrayPut: Put[Array[AddressId]] = TypoUUID.arrayPut.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[AddressId, TypoUUID] = Bijection[AddressId, TypoUUID](_.value)(AddressId.apply) + implicit lazy val decoder: Decoder[AddressId] = TypoUUID.decoder.map(AddressId.apply) + implicit lazy val encoder: Encoder[AddressId] = TypoUUID.encoder.contramap(_.value) + implicit lazy val get: Get[AddressId] = TypoUUID.get.map(AddressId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[AddressId] = Ordering.by(_.value) + implicit lazy val put: Put[AddressId] = TypoUUID.put.contramap(_.value) + implicit lazy val text: Text[AddressId] = new Text[AddressId] { + override def unsafeEncode(v: AddressId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: AddressId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressRepo.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressRepo.scala new file mode 100644 index 0000000000..b9f63c2121 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressRepo.scala @@ -0,0 +1,36 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import doobie.free.connection.ConnectionIO +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait AddressRepo { + def delete: DeleteBuilder[AddressFields, AddressRow] + def deleteById(id: AddressId): ConnectionIO[Boolean] + def deleteByIds(ids: Array[AddressId]): ConnectionIO[Int] + def insert(unsaved: AddressRow): ConnectionIO[AddressRow] + def insert(unsaved: AddressRowUnsaved): ConnectionIO[AddressRow] + def insertStreaming(unsaved: Stream[ConnectionIO, AddressRow], batchSize: Int = 10000): ConnectionIO[Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, AddressRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] + def select: SelectBuilder[AddressFields, AddressRow] + def selectAll: Stream[ConnectionIO, AddressRow] + def selectById(id: AddressId): ConnectionIO[Option[AddressRow]] + def selectByIds(ids: Array[AddressId]): Stream[ConnectionIO, AddressRow] + def selectByIdsTracked(ids: Array[AddressId]): ConnectionIO[Map[AddressId, AddressRow]] + def update: UpdateBuilder[AddressFields, AddressRow] + def update(row: AddressRow): ConnectionIO[Boolean] + def upsert(unsaved: AddressRow): ConnectionIO[AddressRow] + def upsertBatch(unsaved: List[AddressRow]): Stream[ConnectionIO, AddressRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Stream[ConnectionIO, AddressRow], batchSize: Int = 10000): ConnectionIO[Int] +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressRepoImpl.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressRepoImpl.scala new file mode 100644 index 0000000000..cd30bd0e0d --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressRepoImpl.scala @@ -0,0 +1,145 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import adventureworks.customtypes.Defaulted +import cats.instances.list.catsStdInstancesForList +import doobie.free.connection.ConnectionIO +import doobie.postgres.syntax.FragmentOps +import doobie.syntax.SqlInterpolator.SingleFragment.fromWrite +import doobie.syntax.string.toSqlInterpolator +import doobie.util.Write +import doobie.util.fragment.Fragment +import doobie.util.meta.Meta +import doobie.util.update.Update +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class AddressRepoImpl extends AddressRepo { + override def delete: DeleteBuilder[AddressFields, AddressRow] = { + DeleteBuilder(""""frontpage"."address"""", AddressFields.structure) + } + override def deleteById(id: AddressId): ConnectionIO[Boolean] = { + sql"""delete from "frontpage"."address" where "id" = ${fromWrite(id)(new Write.Single(AddressId.put))}""".update.run.map(_ > 0) + } + override def deleteByIds(ids: Array[AddressId]): ConnectionIO[Int] = { + sql"""delete from "frontpage"."address" where "id" = ANY(${ids})""".update.run + } + override def insert(unsaved: AddressRow): ConnectionIO[AddressRow] = { + sql"""insert into "frontpage"."address"("id", "city", "country") + values (${fromWrite(unsaved.id)(new Write.Single(AddressId.put))}::uuid, ${fromWrite(unsaved.city)(new Write.Single(Meta.StringMeta.put))}, ${fromWrite(unsaved.country)(new Write.Single(Meta.StringMeta.put))}) + returning "id", "city", "country" + """.query(using AddressRow.read).unique + } + override def insert(unsaved: AddressRowUnsaved): ConnectionIO[AddressRow] = { + val fs = List( + Some((Fragment.const0(s""""city""""), fr"${fromWrite(unsaved.city)(new Write.Single(Meta.StringMeta.put))}")), + Some((Fragment.const0(s""""country""""), fr"${fromWrite(unsaved.country)(new Write.Single(Meta.StringMeta.put))}")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""id""""), fr"${fromWrite(value: AddressId)(new Write.Single(AddressId.put))}::uuid")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."address" default values + returning "id", "city", "country" + """ + } else { + val CommaSeparate = Fragment.FragmentMonoid.intercalate(fr", ") + sql"""insert into "frontpage"."address"(${CommaSeparate.combineAllOption(fs.map { case (n, _) => n }).get}) + values (${CommaSeparate.combineAllOption(fs.map { case (_, f) => f }).get}) + returning "id", "city", "country" + """ + } + q.query(using AddressRow.read).unique + + } + override def insertStreaming(unsaved: Stream[ConnectionIO, AddressRow], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."address"("id", "city", "country") FROM STDIN""").copyIn(unsaved, batchSize)(using AddressRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, AddressRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."address"("city", "country", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""").copyIn(unsaved, batchSize)(using AddressRowUnsaved.text) + } + override def select: SelectBuilder[AddressFields, AddressRow] = { + SelectBuilderSql(""""frontpage"."address"""", AddressFields.structure, AddressRow.read) + } + override def selectAll: Stream[ConnectionIO, AddressRow] = { + sql"""select "id", "city", "country" from "frontpage"."address"""".query(using AddressRow.read).stream + } + override def selectById(id: AddressId): ConnectionIO[Option[AddressRow]] = { + sql"""select "id", "city", "country" from "frontpage"."address" where "id" = ${fromWrite(id)(new Write.Single(AddressId.put))}""".query(using AddressRow.read).option + } + override def selectByIds(ids: Array[AddressId]): Stream[ConnectionIO, AddressRow] = { + sql"""select "id", "city", "country" from "frontpage"."address" where "id" = ANY(${ids})""".query(using AddressRow.read).stream + } + override def selectByIdsTracked(ids: Array[AddressId]): ConnectionIO[Map[AddressId, AddressRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[AddressFields, AddressRow] = { + UpdateBuilder(""""frontpage"."address"""", AddressFields.structure, AddressRow.read) + } + override def update(row: AddressRow): ConnectionIO[Boolean] = { + val id = row.id + sql"""update "frontpage"."address" + set "city" = ${fromWrite(row.city)(new Write.Single(Meta.StringMeta.put))}, + "country" = ${fromWrite(row.country)(new Write.Single(Meta.StringMeta.put))} + where "id" = ${fromWrite(id)(new Write.Single(AddressId.put))}""" + .update + .run + .map(_ > 0) + } + override def upsert(unsaved: AddressRow): ConnectionIO[AddressRow] = { + sql"""insert into "frontpage"."address"("id", "city", "country") + values ( + ${fromWrite(unsaved.id)(new Write.Single(AddressId.put))}::uuid, + ${fromWrite(unsaved.city)(new Write.Single(Meta.StringMeta.put))}, + ${fromWrite(unsaved.country)(new Write.Single(Meta.StringMeta.put))} + ) + on conflict ("id") + do update set + "city" = EXCLUDED."city", + "country" = EXCLUDED."country" + returning "id", "city", "country" + """.query(using AddressRow.read).unique + } + override def upsertBatch(unsaved: List[AddressRow]): Stream[ConnectionIO, AddressRow] = { + Update[AddressRow]( + s"""insert into "frontpage"."address"("id", "city", "country") + values (?::uuid,?,?) + on conflict ("id") + do update set + "city" = EXCLUDED."city", + "country" = EXCLUDED."country" + returning "id", "city", "country"""" + )(using AddressRow.write) + .updateManyWithGeneratedKeys[AddressRow]("id", "city", "country")(unsaved)(using catsStdInstancesForList, AddressRow.read) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, AddressRow], batchSize: Int = 10000): ConnectionIO[Int] = { + for { + _ <- sql"""create temporary table address_TEMP (like "frontpage"."address") on commit drop""".update.run + _ <- new FragmentOps(sql"""copy address_TEMP("id", "city", "country") from stdin""").copyIn(unsaved, batchSize)(using AddressRow.text) + res <- sql"""insert into "frontpage"."address"("id", "city", "country") + select * from address_TEMP + on conflict ("id") + do update set + "city" = EXCLUDED."city", + "country" = EXCLUDED."country" + ; + drop table address_TEMP;""".update.run + } yield res + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressRepoMock.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressRepoMock.scala new file mode 100644 index 0000000000..70e1ddda3c --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressRepoMock.scala @@ -0,0 +1,127 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import doobie.free.connection.ConnectionIO +import doobie.free.connection.delay +import fs2.Stream +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class AddressRepoMock(toRow: Function1[AddressRowUnsaved, AddressRow], + map: scala.collection.mutable.Map[AddressId, AddressRow] = scala.collection.mutable.Map.empty) extends AddressRepo { + override def delete: DeleteBuilder[AddressFields, AddressRow] = { + DeleteBuilderMock(DeleteParams.empty, AddressFields.structure, map) + } + override def deleteById(id: AddressId): ConnectionIO[Boolean] = { + delay(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[AddressId]): ConnectionIO[Int] = { + delay(ids.map(id => map.remove(id)).count(_.isDefined)) + } + override def insert(unsaved: AddressRow): ConnectionIO[AddressRow] = { + delay { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: AddressRowUnsaved): ConnectionIO[AddressRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Stream[ConnectionIO, AddressRow], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { rows => + var num = 0L + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, AddressRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { unsavedRows => + var num = 0L + unsavedRows.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + num += 1 + } + num + } + } + override def select: SelectBuilder[AddressFields, AddressRow] = { + SelectBuilderMock(AddressFields.structure, delay(map.values.toList), SelectParams.empty) + } + override def selectAll: Stream[ConnectionIO, AddressRow] = { + Stream.emits(map.values.toList) + } + override def selectById(id: AddressId): ConnectionIO[Option[AddressRow]] = { + delay(map.get(id)) + } + override def selectByIds(ids: Array[AddressId]): Stream[ConnectionIO, AddressRow] = { + Stream.emits(ids.flatMap(map.get).toList) + } + override def selectByIdsTracked(ids: Array[AddressId]): ConnectionIO[Map[AddressId, AddressRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[AddressFields, AddressRow] = { + UpdateBuilderMock(UpdateParams.empty, AddressFields.structure, map) + } + override def update(row: AddressRow): ConnectionIO[Boolean] = { + delay { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: AddressRow): ConnectionIO[AddressRow] = { + delay { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + } + override def upsertBatch(unsaved: List[AddressRow]): Stream[ConnectionIO, AddressRow] = { + Stream.emits { + unsaved.map { row => + map += (row.id -> row) + row + } + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, AddressRow], batchSize: Int = 10000): ConnectionIO[Int] = { + unsaved.compile.toList.map { rows => + var num = 0 + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressRow.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressRow.scala new file mode 100644 index 0000000000..36c123baec --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressRow.scala @@ -0,0 +1,57 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import adventureworks.customtypes.Defaulted +import doobie.postgres.Text +import doobie.util.Read +import doobie.util.Write +import doobie.util.meta.Meta +import io.circe.Decoder +import io.circe.Encoder + +/** Table: frontpage.address + Primary key: id */ +case class AddressRow( + /** Default: gen_random_uuid() */ + id: AddressId, + city: String, + country: String +){ + def toUnsavedRow(id: Defaulted[AddressId]): AddressRowUnsaved = + AddressRowUnsaved(city, country, id) + } + +object AddressRow { + implicit lazy val decoder: Decoder[AddressRow] = Decoder.forProduct3[AddressRow, AddressId, String, String]("id", "city", "country")(AddressRow.apply)(AddressId.decoder, Decoder.decodeString, Decoder.decodeString) + implicit lazy val encoder: Encoder[AddressRow] = Encoder.forProduct3[AddressRow, AddressId, String, String]("id", "city", "country")(x => (x.id, x.city, x.country))(AddressId.encoder, Encoder.encodeString, Encoder.encodeString) + implicit lazy val read: Read[AddressRow] = new Read.CompositeOfInstances(Array( + new Read.Single(AddressId.get).asInstanceOf[Read[Any]], + new Read.Single(Meta.StringMeta.get).asInstanceOf[Read[Any]], + new Read.Single(Meta.StringMeta.get).asInstanceOf[Read[Any]] + ))(using scala.reflect.ClassTag.Any).map { arr => + AddressRow( + id = arr(0).asInstanceOf[AddressId], + city = arr(1).asInstanceOf[String], + country = arr(2).asInstanceOf[String] + ) + } + implicit lazy val text: Text[AddressRow] = Text.instance[AddressRow]{ (row, sb) => + AddressId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.city, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.country, sb) + } + implicit lazy val write: Write[AddressRow] = new Write.Composite[AddressRow]( + List(new Write.Single(AddressId.put), + new Write.Single(Meta.StringMeta.put), + new Write.Single(Meta.StringMeta.put)), + a => List(a.id, a.city, a.country) + ) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressRowUnsaved.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressRowUnsaved.scala new file mode 100644 index 0000000000..2e935f101b --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/address/AddressRowUnsaved.scala @@ -0,0 +1,42 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import adventureworks.customtypes.Defaulted +import doobie.postgres.Text +import io.circe.Decoder +import io.circe.Encoder + +/** This class corresponds to a row in table `frontpage.address` which has not been persisted yet */ +case class AddressRowUnsaved( + city: String, + country: String, + /** Default: gen_random_uuid() */ + id: Defaulted[AddressId] = Defaulted.UseDefault +) { + def toRow(idDefault: => AddressId): AddressRow = + AddressRow( + city = city, + country = country, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object AddressRowUnsaved { + implicit lazy val decoder: Decoder[AddressRowUnsaved] = Decoder.forProduct3[AddressRowUnsaved, String, String, Defaulted[AddressId]]("city", "country", "id")(AddressRowUnsaved.apply)(Decoder.decodeString, Decoder.decodeString, Defaulted.decoder(AddressId.decoder)) + implicit lazy val encoder: Encoder[AddressRowUnsaved] = Encoder.forProduct3[AddressRowUnsaved, String, String, Defaulted[AddressId]]("city", "country", "id")(x => (x.city, x.country, x.id))(Encoder.encodeString, Encoder.encodeString, Defaulted.encoder(AddressId.encoder)) + implicit lazy val text: Text[AddressRowUnsaved] = Text.instance[AddressRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.city, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.country, sb) + sb.append(Text.DELIMETER) + Defaulted.text(AddressId.text).unsafeEncode(row.id, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryFields.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryFields.scala new file mode 100644 index 0000000000..258c0d3f56 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryFields.scala @@ -0,0 +1,40 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.Structure.Relation + +trait CategoryFields { + def id: IdField[CategoryId, CategoryRow] + def name: Field[String, CategoryRow] +} + +object CategoryFields { + lazy val structure: Relation[CategoryFields, CategoryRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[CategoryFields, CategoryRow] { + + override lazy val fields: CategoryFields = new CategoryFields { + override def id = IdField[CategoryId, CategoryRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, CategoryRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, CategoryRow]] = + List[FieldLikeNoHkt[?, CategoryRow]](fields.id, fields.name) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryId.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryId.scala new file mode 100644 index 0000000000..965af68a6c --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryId.scala @@ -0,0 +1,33 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import adventureworks.customtypes.TypoUUID +import doobie.postgres.Text +import doobie.util.Get +import doobie.util.Put +import io.circe.Decoder +import io.circe.Encoder +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.category` */ +case class CategoryId(value: TypoUUID) extends AnyVal +object CategoryId { + implicit lazy val arrayGet: Get[Array[CategoryId]] = TypoUUID.arrayGet.map(_.map(CategoryId.apply)) + implicit lazy val arrayPut: Put[Array[CategoryId]] = TypoUUID.arrayPut.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[CategoryId, TypoUUID] = Bijection[CategoryId, TypoUUID](_.value)(CategoryId.apply) + implicit lazy val decoder: Decoder[CategoryId] = TypoUUID.decoder.map(CategoryId.apply) + implicit lazy val encoder: Encoder[CategoryId] = TypoUUID.encoder.contramap(_.value) + implicit lazy val get: Get[CategoryId] = TypoUUID.get.map(CategoryId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[CategoryId] = Ordering.by(_.value) + implicit lazy val put: Put[CategoryId] = TypoUUID.put.contramap(_.value) + implicit lazy val text: Text[CategoryId] = new Text[CategoryId] { + override def unsafeEncode(v: CategoryId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: CategoryId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepo.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepo.scala new file mode 100644 index 0000000000..5866b188bb --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepo.scala @@ -0,0 +1,36 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import doobie.free.connection.ConnectionIO +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait CategoryRepo { + def delete: DeleteBuilder[CategoryFields, CategoryRow] + def deleteById(id: CategoryId): ConnectionIO[Boolean] + def deleteByIds(ids: Array[CategoryId]): ConnectionIO[Int] + def insert(unsaved: CategoryRow): ConnectionIO[CategoryRow] + def insert(unsaved: CategoryRowUnsaved): ConnectionIO[CategoryRow] + def insertStreaming(unsaved: Stream[ConnectionIO, CategoryRow], batchSize: Int = 10000): ConnectionIO[Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, CategoryRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] + def select: SelectBuilder[CategoryFields, CategoryRow] + def selectAll: Stream[ConnectionIO, CategoryRow] + def selectById(id: CategoryId): ConnectionIO[Option[CategoryRow]] + def selectByIds(ids: Array[CategoryId]): Stream[ConnectionIO, CategoryRow] + def selectByIdsTracked(ids: Array[CategoryId]): ConnectionIO[Map[CategoryId, CategoryRow]] + def update: UpdateBuilder[CategoryFields, CategoryRow] + def update(row: CategoryRow): ConnectionIO[Boolean] + def upsert(unsaved: CategoryRow): ConnectionIO[CategoryRow] + def upsertBatch(unsaved: List[CategoryRow]): Stream[ConnectionIO, CategoryRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Stream[ConnectionIO, CategoryRow], batchSize: Int = 10000): ConnectionIO[Int] +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepoImpl.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepoImpl.scala new file mode 100644 index 0000000000..3bcb144bf8 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepoImpl.scala @@ -0,0 +1,139 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import adventureworks.customtypes.Defaulted +import cats.instances.list.catsStdInstancesForList +import doobie.free.connection.ConnectionIO +import doobie.postgres.syntax.FragmentOps +import doobie.syntax.SqlInterpolator.SingleFragment.fromWrite +import doobie.syntax.string.toSqlInterpolator +import doobie.util.Write +import doobie.util.fragment.Fragment +import doobie.util.meta.Meta +import doobie.util.update.Update +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class CategoryRepoImpl extends CategoryRepo { + override def delete: DeleteBuilder[CategoryFields, CategoryRow] = { + DeleteBuilder(""""frontpage"."category"""", CategoryFields.structure) + } + override def deleteById(id: CategoryId): ConnectionIO[Boolean] = { + sql"""delete from "frontpage"."category" where "id" = ${fromWrite(id)(new Write.Single(CategoryId.put))}""".update.run.map(_ > 0) + } + override def deleteByIds(ids: Array[CategoryId]): ConnectionIO[Int] = { + sql"""delete from "frontpage"."category" where "id" = ANY(${ids})""".update.run + } + override def insert(unsaved: CategoryRow): ConnectionIO[CategoryRow] = { + sql"""insert into "frontpage"."category"("id", "name") + values (${fromWrite(unsaved.id)(new Write.Single(CategoryId.put))}::uuid, ${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}) + returning "id", "name" + """.query(using CategoryRow.read).unique + } + override def insert(unsaved: CategoryRowUnsaved): ConnectionIO[CategoryRow] = { + val fs = List( + Some((Fragment.const0(s""""name""""), fr"${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""id""""), fr"${fromWrite(value: CategoryId)(new Write.Single(CategoryId.put))}::uuid")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."category" default values + returning "id", "name" + """ + } else { + val CommaSeparate = Fragment.FragmentMonoid.intercalate(fr", ") + sql"""insert into "frontpage"."category"(${CommaSeparate.combineAllOption(fs.map { case (n, _) => n }).get}) + values (${CommaSeparate.combineAllOption(fs.map { case (_, f) => f }).get}) + returning "id", "name" + """ + } + q.query(using CategoryRow.read).unique + + } + override def insertStreaming(unsaved: Stream[ConnectionIO, CategoryRow], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."category"("id", "name") FROM STDIN""").copyIn(unsaved, batchSize)(using CategoryRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, CategoryRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."category"("name", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""").copyIn(unsaved, batchSize)(using CategoryRowUnsaved.text) + } + override def select: SelectBuilder[CategoryFields, CategoryRow] = { + SelectBuilderSql(""""frontpage"."category"""", CategoryFields.structure, CategoryRow.read) + } + override def selectAll: Stream[ConnectionIO, CategoryRow] = { + sql"""select "id", "name" from "frontpage"."category"""".query(using CategoryRow.read).stream + } + override def selectById(id: CategoryId): ConnectionIO[Option[CategoryRow]] = { + sql"""select "id", "name" from "frontpage"."category" where "id" = ${fromWrite(id)(new Write.Single(CategoryId.put))}""".query(using CategoryRow.read).option + } + override def selectByIds(ids: Array[CategoryId]): Stream[ConnectionIO, CategoryRow] = { + sql"""select "id", "name" from "frontpage"."category" where "id" = ANY(${ids})""".query(using CategoryRow.read).stream + } + override def selectByIdsTracked(ids: Array[CategoryId]): ConnectionIO[Map[CategoryId, CategoryRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[CategoryFields, CategoryRow] = { + UpdateBuilder(""""frontpage"."category"""", CategoryFields.structure, CategoryRow.read) + } + override def update(row: CategoryRow): ConnectionIO[Boolean] = { + val id = row.id + sql"""update "frontpage"."category" + set "name" = ${fromWrite(row.name)(new Write.Single(Meta.StringMeta.put))} + where "id" = ${fromWrite(id)(new Write.Single(CategoryId.put))}""" + .update + .run + .map(_ > 0) + } + override def upsert(unsaved: CategoryRow): ConnectionIO[CategoryRow] = { + sql"""insert into "frontpage"."category"("id", "name") + values ( + ${fromWrite(unsaved.id)(new Write.Single(CategoryId.put))}::uuid, + ${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))} + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name" + returning "id", "name" + """.query(using CategoryRow.read).unique + } + override def upsertBatch(unsaved: List[CategoryRow]): Stream[ConnectionIO, CategoryRow] = { + Update[CategoryRow]( + s"""insert into "frontpage"."category"("id", "name") + values (?::uuid,?) + on conflict ("id") + do update set + "name" = EXCLUDED."name" + returning "id", "name"""" + )(using CategoryRow.write) + .updateManyWithGeneratedKeys[CategoryRow]("id", "name")(unsaved)(using catsStdInstancesForList, CategoryRow.read) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, CategoryRow], batchSize: Int = 10000): ConnectionIO[Int] = { + for { + _ <- sql"""create temporary table category_TEMP (like "frontpage"."category") on commit drop""".update.run + _ <- new FragmentOps(sql"""copy category_TEMP("id", "name") from stdin""").copyIn(unsaved, batchSize)(using CategoryRow.text) + res <- sql"""insert into "frontpage"."category"("id", "name") + select * from category_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name" + ; + drop table category_TEMP;""".update.run + } yield res + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepoMock.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepoMock.scala new file mode 100644 index 0000000000..0b950dabe2 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepoMock.scala @@ -0,0 +1,127 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import doobie.free.connection.ConnectionIO +import doobie.free.connection.delay +import fs2.Stream +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class CategoryRepoMock(toRow: Function1[CategoryRowUnsaved, CategoryRow], + map: scala.collection.mutable.Map[CategoryId, CategoryRow] = scala.collection.mutable.Map.empty) extends CategoryRepo { + override def delete: DeleteBuilder[CategoryFields, CategoryRow] = { + DeleteBuilderMock(DeleteParams.empty, CategoryFields.structure, map) + } + override def deleteById(id: CategoryId): ConnectionIO[Boolean] = { + delay(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[CategoryId]): ConnectionIO[Int] = { + delay(ids.map(id => map.remove(id)).count(_.isDefined)) + } + override def insert(unsaved: CategoryRow): ConnectionIO[CategoryRow] = { + delay { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: CategoryRowUnsaved): ConnectionIO[CategoryRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Stream[ConnectionIO, CategoryRow], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { rows => + var num = 0L + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, CategoryRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { unsavedRows => + var num = 0L + unsavedRows.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + num += 1 + } + num + } + } + override def select: SelectBuilder[CategoryFields, CategoryRow] = { + SelectBuilderMock(CategoryFields.structure, delay(map.values.toList), SelectParams.empty) + } + override def selectAll: Stream[ConnectionIO, CategoryRow] = { + Stream.emits(map.values.toList) + } + override def selectById(id: CategoryId): ConnectionIO[Option[CategoryRow]] = { + delay(map.get(id)) + } + override def selectByIds(ids: Array[CategoryId]): Stream[ConnectionIO, CategoryRow] = { + Stream.emits(ids.flatMap(map.get).toList) + } + override def selectByIdsTracked(ids: Array[CategoryId]): ConnectionIO[Map[CategoryId, CategoryRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[CategoryFields, CategoryRow] = { + UpdateBuilderMock(UpdateParams.empty, CategoryFields.structure, map) + } + override def update(row: CategoryRow): ConnectionIO[Boolean] = { + delay { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: CategoryRow): ConnectionIO[CategoryRow] = { + delay { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + } + override def upsertBatch(unsaved: List[CategoryRow]): Stream[ConnectionIO, CategoryRow] = { + Stream.emits { + unsaved.map { row => + map += (row.id -> row) + row + } + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, CategoryRow], batchSize: Int = 10000): ConnectionIO[Int] = { + unsaved.compile.toList.map { rows => + var num = 0 + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryRow.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryRow.scala new file mode 100644 index 0000000000..fd0c44a027 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryRow.scala @@ -0,0 +1,51 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import adventureworks.customtypes.Defaulted +import doobie.postgres.Text +import doobie.util.Read +import doobie.util.Write +import doobie.util.meta.Meta +import io.circe.Decoder +import io.circe.Encoder + +/** Table: frontpage.category + Primary key: id */ +case class CategoryRow( + /** Default: gen_random_uuid() */ + id: CategoryId, + name: String +){ + def toUnsavedRow(id: Defaulted[CategoryId]): CategoryRowUnsaved = + CategoryRowUnsaved(name, id) + } + +object CategoryRow { + implicit lazy val decoder: Decoder[CategoryRow] = Decoder.forProduct2[CategoryRow, CategoryId, String]("id", "name")(CategoryRow.apply)(CategoryId.decoder, Decoder.decodeString) + implicit lazy val encoder: Encoder[CategoryRow] = Encoder.forProduct2[CategoryRow, CategoryId, String]("id", "name")(x => (x.id, x.name))(CategoryId.encoder, Encoder.encodeString) + implicit lazy val read: Read[CategoryRow] = new Read.CompositeOfInstances(Array( + new Read.Single(CategoryId.get).asInstanceOf[Read[Any]], + new Read.Single(Meta.StringMeta.get).asInstanceOf[Read[Any]] + ))(using scala.reflect.ClassTag.Any).map { arr => + CategoryRow( + id = arr(0).asInstanceOf[CategoryId], + name = arr(1).asInstanceOf[String] + ) + } + implicit lazy val text: Text[CategoryRow] = Text.instance[CategoryRow]{ (row, sb) => + CategoryId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + } + implicit lazy val write: Write[CategoryRow] = new Write.Composite[CategoryRow]( + List(new Write.Single(CategoryId.put), + new Write.Single(Meta.StringMeta.put)), + a => List(a.id, a.name) + ) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryRowUnsaved.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryRowUnsaved.scala new file mode 100644 index 0000000000..4e749e9914 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/category/CategoryRowUnsaved.scala @@ -0,0 +1,38 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import adventureworks.customtypes.Defaulted +import doobie.postgres.Text +import io.circe.Decoder +import io.circe.Encoder + +/** This class corresponds to a row in table `frontpage.category` which has not been persisted yet */ +case class CategoryRowUnsaved( + name: String, + /** Default: gen_random_uuid() */ + id: Defaulted[CategoryId] = Defaulted.UseDefault +) { + def toRow(idDefault: => CategoryId): CategoryRow = + CategoryRow( + name = name, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object CategoryRowUnsaved { + implicit lazy val decoder: Decoder[CategoryRowUnsaved] = Decoder.forProduct2[CategoryRowUnsaved, String, Defaulted[CategoryId]]("name", "id")(CategoryRowUnsaved.apply)(Decoder.decodeString, Defaulted.decoder(CategoryId.decoder)) + implicit lazy val encoder: Encoder[CategoryRowUnsaved] = Encoder.forProduct2[CategoryRowUnsaved, String, Defaulted[CategoryId]]("name", "id")(x => (x.name, x.id))(Encoder.encodeString, Defaulted.encoder(CategoryId.encoder)) + implicit lazy val text: Text[CategoryRowUnsaved] = Text.instance[CategoryRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Defaulted.text(CategoryId.text).unsafeEncode(row.id, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyFields.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyFields.scala new file mode 100644 index 0000000000..e6777fa969 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyFields.scala @@ -0,0 +1,40 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.Structure.Relation + +trait CompanyFields { + def id: IdField[CompanyId, CompanyRow] + def name: Field[String, CompanyRow] +} + +object CompanyFields { + lazy val structure: Relation[CompanyFields, CompanyRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[CompanyFields, CompanyRow] { + + override lazy val fields: CompanyFields = new CompanyFields { + override def id = IdField[CompanyId, CompanyRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, CompanyRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, CompanyRow]] = + List[FieldLikeNoHkt[?, CompanyRow]](fields.id, fields.name) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyId.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyId.scala new file mode 100644 index 0000000000..1ab7d084f1 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyId.scala @@ -0,0 +1,33 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import adventureworks.customtypes.TypoUUID +import doobie.postgres.Text +import doobie.util.Get +import doobie.util.Put +import io.circe.Decoder +import io.circe.Encoder +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.company` */ +case class CompanyId(value: TypoUUID) extends AnyVal +object CompanyId { + implicit lazy val arrayGet: Get[Array[CompanyId]] = TypoUUID.arrayGet.map(_.map(CompanyId.apply)) + implicit lazy val arrayPut: Put[Array[CompanyId]] = TypoUUID.arrayPut.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[CompanyId, TypoUUID] = Bijection[CompanyId, TypoUUID](_.value)(CompanyId.apply) + implicit lazy val decoder: Decoder[CompanyId] = TypoUUID.decoder.map(CompanyId.apply) + implicit lazy val encoder: Encoder[CompanyId] = TypoUUID.encoder.contramap(_.value) + implicit lazy val get: Get[CompanyId] = TypoUUID.get.map(CompanyId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[CompanyId] = Ordering.by(_.value) + implicit lazy val put: Put[CompanyId] = TypoUUID.put.contramap(_.value) + implicit lazy val text: Text[CompanyId] = new Text[CompanyId] { + override def unsafeEncode(v: CompanyId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: CompanyId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepo.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepo.scala new file mode 100644 index 0000000000..b1367df895 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepo.scala @@ -0,0 +1,36 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import doobie.free.connection.ConnectionIO +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait CompanyRepo { + def delete: DeleteBuilder[CompanyFields, CompanyRow] + def deleteById(id: CompanyId): ConnectionIO[Boolean] + def deleteByIds(ids: Array[CompanyId]): ConnectionIO[Int] + def insert(unsaved: CompanyRow): ConnectionIO[CompanyRow] + def insert(unsaved: CompanyRowUnsaved): ConnectionIO[CompanyRow] + def insertStreaming(unsaved: Stream[ConnectionIO, CompanyRow], batchSize: Int = 10000): ConnectionIO[Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, CompanyRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] + def select: SelectBuilder[CompanyFields, CompanyRow] + def selectAll: Stream[ConnectionIO, CompanyRow] + def selectById(id: CompanyId): ConnectionIO[Option[CompanyRow]] + def selectByIds(ids: Array[CompanyId]): Stream[ConnectionIO, CompanyRow] + def selectByIdsTracked(ids: Array[CompanyId]): ConnectionIO[Map[CompanyId, CompanyRow]] + def update: UpdateBuilder[CompanyFields, CompanyRow] + def update(row: CompanyRow): ConnectionIO[Boolean] + def upsert(unsaved: CompanyRow): ConnectionIO[CompanyRow] + def upsertBatch(unsaved: List[CompanyRow]): Stream[ConnectionIO, CompanyRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Stream[ConnectionIO, CompanyRow], batchSize: Int = 10000): ConnectionIO[Int] +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepoImpl.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepoImpl.scala new file mode 100644 index 0000000000..3045d676bc --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepoImpl.scala @@ -0,0 +1,139 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import adventureworks.customtypes.Defaulted +import cats.instances.list.catsStdInstancesForList +import doobie.free.connection.ConnectionIO +import doobie.postgres.syntax.FragmentOps +import doobie.syntax.SqlInterpolator.SingleFragment.fromWrite +import doobie.syntax.string.toSqlInterpolator +import doobie.util.Write +import doobie.util.fragment.Fragment +import doobie.util.meta.Meta +import doobie.util.update.Update +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class CompanyRepoImpl extends CompanyRepo { + override def delete: DeleteBuilder[CompanyFields, CompanyRow] = { + DeleteBuilder(""""frontpage"."company"""", CompanyFields.structure) + } + override def deleteById(id: CompanyId): ConnectionIO[Boolean] = { + sql"""delete from "frontpage"."company" where "id" = ${fromWrite(id)(new Write.Single(CompanyId.put))}""".update.run.map(_ > 0) + } + override def deleteByIds(ids: Array[CompanyId]): ConnectionIO[Int] = { + sql"""delete from "frontpage"."company" where "id" = ANY(${ids})""".update.run + } + override def insert(unsaved: CompanyRow): ConnectionIO[CompanyRow] = { + sql"""insert into "frontpage"."company"("id", "name") + values (${fromWrite(unsaved.id)(new Write.Single(CompanyId.put))}::uuid, ${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}) + returning "id", "name" + """.query(using CompanyRow.read).unique + } + override def insert(unsaved: CompanyRowUnsaved): ConnectionIO[CompanyRow] = { + val fs = List( + Some((Fragment.const0(s""""name""""), fr"${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""id""""), fr"${fromWrite(value: CompanyId)(new Write.Single(CompanyId.put))}::uuid")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."company" default values + returning "id", "name" + """ + } else { + val CommaSeparate = Fragment.FragmentMonoid.intercalate(fr", ") + sql"""insert into "frontpage"."company"(${CommaSeparate.combineAllOption(fs.map { case (n, _) => n }).get}) + values (${CommaSeparate.combineAllOption(fs.map { case (_, f) => f }).get}) + returning "id", "name" + """ + } + q.query(using CompanyRow.read).unique + + } + override def insertStreaming(unsaved: Stream[ConnectionIO, CompanyRow], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."company"("id", "name") FROM STDIN""").copyIn(unsaved, batchSize)(using CompanyRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, CompanyRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."company"("name", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""").copyIn(unsaved, batchSize)(using CompanyRowUnsaved.text) + } + override def select: SelectBuilder[CompanyFields, CompanyRow] = { + SelectBuilderSql(""""frontpage"."company"""", CompanyFields.structure, CompanyRow.read) + } + override def selectAll: Stream[ConnectionIO, CompanyRow] = { + sql"""select "id", "name" from "frontpage"."company"""".query(using CompanyRow.read).stream + } + override def selectById(id: CompanyId): ConnectionIO[Option[CompanyRow]] = { + sql"""select "id", "name" from "frontpage"."company" where "id" = ${fromWrite(id)(new Write.Single(CompanyId.put))}""".query(using CompanyRow.read).option + } + override def selectByIds(ids: Array[CompanyId]): Stream[ConnectionIO, CompanyRow] = { + sql"""select "id", "name" from "frontpage"."company" where "id" = ANY(${ids})""".query(using CompanyRow.read).stream + } + override def selectByIdsTracked(ids: Array[CompanyId]): ConnectionIO[Map[CompanyId, CompanyRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[CompanyFields, CompanyRow] = { + UpdateBuilder(""""frontpage"."company"""", CompanyFields.structure, CompanyRow.read) + } + override def update(row: CompanyRow): ConnectionIO[Boolean] = { + val id = row.id + sql"""update "frontpage"."company" + set "name" = ${fromWrite(row.name)(new Write.Single(Meta.StringMeta.put))} + where "id" = ${fromWrite(id)(new Write.Single(CompanyId.put))}""" + .update + .run + .map(_ > 0) + } + override def upsert(unsaved: CompanyRow): ConnectionIO[CompanyRow] = { + sql"""insert into "frontpage"."company"("id", "name") + values ( + ${fromWrite(unsaved.id)(new Write.Single(CompanyId.put))}::uuid, + ${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))} + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name" + returning "id", "name" + """.query(using CompanyRow.read).unique + } + override def upsertBatch(unsaved: List[CompanyRow]): Stream[ConnectionIO, CompanyRow] = { + Update[CompanyRow]( + s"""insert into "frontpage"."company"("id", "name") + values (?::uuid,?) + on conflict ("id") + do update set + "name" = EXCLUDED."name" + returning "id", "name"""" + )(using CompanyRow.write) + .updateManyWithGeneratedKeys[CompanyRow]("id", "name")(unsaved)(using catsStdInstancesForList, CompanyRow.read) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, CompanyRow], batchSize: Int = 10000): ConnectionIO[Int] = { + for { + _ <- sql"""create temporary table company_TEMP (like "frontpage"."company") on commit drop""".update.run + _ <- new FragmentOps(sql"""copy company_TEMP("id", "name") from stdin""").copyIn(unsaved, batchSize)(using CompanyRow.text) + res <- sql"""insert into "frontpage"."company"("id", "name") + select * from company_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name" + ; + drop table company_TEMP;""".update.run + } yield res + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepoMock.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepoMock.scala new file mode 100644 index 0000000000..c5fcb7d3ff --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepoMock.scala @@ -0,0 +1,127 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import doobie.free.connection.ConnectionIO +import doobie.free.connection.delay +import fs2.Stream +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class CompanyRepoMock(toRow: Function1[CompanyRowUnsaved, CompanyRow], + map: scala.collection.mutable.Map[CompanyId, CompanyRow] = scala.collection.mutable.Map.empty) extends CompanyRepo { + override def delete: DeleteBuilder[CompanyFields, CompanyRow] = { + DeleteBuilderMock(DeleteParams.empty, CompanyFields.structure, map) + } + override def deleteById(id: CompanyId): ConnectionIO[Boolean] = { + delay(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[CompanyId]): ConnectionIO[Int] = { + delay(ids.map(id => map.remove(id)).count(_.isDefined)) + } + override def insert(unsaved: CompanyRow): ConnectionIO[CompanyRow] = { + delay { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: CompanyRowUnsaved): ConnectionIO[CompanyRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Stream[ConnectionIO, CompanyRow], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { rows => + var num = 0L + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, CompanyRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { unsavedRows => + var num = 0L + unsavedRows.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + num += 1 + } + num + } + } + override def select: SelectBuilder[CompanyFields, CompanyRow] = { + SelectBuilderMock(CompanyFields.structure, delay(map.values.toList), SelectParams.empty) + } + override def selectAll: Stream[ConnectionIO, CompanyRow] = { + Stream.emits(map.values.toList) + } + override def selectById(id: CompanyId): ConnectionIO[Option[CompanyRow]] = { + delay(map.get(id)) + } + override def selectByIds(ids: Array[CompanyId]): Stream[ConnectionIO, CompanyRow] = { + Stream.emits(ids.flatMap(map.get).toList) + } + override def selectByIdsTracked(ids: Array[CompanyId]): ConnectionIO[Map[CompanyId, CompanyRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[CompanyFields, CompanyRow] = { + UpdateBuilderMock(UpdateParams.empty, CompanyFields.structure, map) + } + override def update(row: CompanyRow): ConnectionIO[Boolean] = { + delay { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: CompanyRow): ConnectionIO[CompanyRow] = { + delay { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + } + override def upsertBatch(unsaved: List[CompanyRow]): Stream[ConnectionIO, CompanyRow] = { + Stream.emits { + unsaved.map { row => + map += (row.id -> row) + row + } + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, CompanyRow], batchSize: Int = 10000): ConnectionIO[Int] = { + unsaved.compile.toList.map { rows => + var num = 0 + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyRow.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyRow.scala new file mode 100644 index 0000000000..136cd6e55c --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyRow.scala @@ -0,0 +1,51 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import adventureworks.customtypes.Defaulted +import doobie.postgres.Text +import doobie.util.Read +import doobie.util.Write +import doobie.util.meta.Meta +import io.circe.Decoder +import io.circe.Encoder + +/** Table: frontpage.company + Primary key: id */ +case class CompanyRow( + /** Default: gen_random_uuid() */ + id: CompanyId, + name: String +){ + def toUnsavedRow(id: Defaulted[CompanyId]): CompanyRowUnsaved = + CompanyRowUnsaved(name, id) + } + +object CompanyRow { + implicit lazy val decoder: Decoder[CompanyRow] = Decoder.forProduct2[CompanyRow, CompanyId, String]("id", "name")(CompanyRow.apply)(CompanyId.decoder, Decoder.decodeString) + implicit lazy val encoder: Encoder[CompanyRow] = Encoder.forProduct2[CompanyRow, CompanyId, String]("id", "name")(x => (x.id, x.name))(CompanyId.encoder, Encoder.encodeString) + implicit lazy val read: Read[CompanyRow] = new Read.CompositeOfInstances(Array( + new Read.Single(CompanyId.get).asInstanceOf[Read[Any]], + new Read.Single(Meta.StringMeta.get).asInstanceOf[Read[Any]] + ))(using scala.reflect.ClassTag.Any).map { arr => + CompanyRow( + id = arr(0).asInstanceOf[CompanyId], + name = arr(1).asInstanceOf[String] + ) + } + implicit lazy val text: Text[CompanyRow] = Text.instance[CompanyRow]{ (row, sb) => + CompanyId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + } + implicit lazy val write: Write[CompanyRow] = new Write.Composite[CompanyRow]( + List(new Write.Single(CompanyId.put), + new Write.Single(Meta.StringMeta.put)), + a => List(a.id, a.name) + ) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyRowUnsaved.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyRowUnsaved.scala new file mode 100644 index 0000000000..8c59b21a52 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/company/CompanyRowUnsaved.scala @@ -0,0 +1,38 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import adventureworks.customtypes.Defaulted +import doobie.postgres.Text +import io.circe.Decoder +import io.circe.Encoder + +/** This class corresponds to a row in table `frontpage.company` which has not been persisted yet */ +case class CompanyRowUnsaved( + name: String, + /** Default: gen_random_uuid() */ + id: Defaulted[CompanyId] = Defaulted.UseDefault +) { + def toRow(idDefault: => CompanyId): CompanyRow = + CompanyRow( + name = name, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object CompanyRowUnsaved { + implicit lazy val decoder: Decoder[CompanyRowUnsaved] = Decoder.forProduct2[CompanyRowUnsaved, String, Defaulted[CompanyId]]("name", "id")(CompanyRowUnsaved.apply)(Decoder.decodeString, Defaulted.decoder(CompanyId.decoder)) + implicit lazy val encoder: Encoder[CompanyRowUnsaved] = Encoder.forProduct2[CompanyRowUnsaved, String, Defaulted[CompanyId]]("name", "id")(x => (x.name, x.id))(Encoder.encodeString, Defaulted.encoder(CompanyId.encoder)) + implicit lazy val text: Text[CompanyRowUnsaved] = Text.instance[CompanyRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Defaulted.text(CompanyId.text).unsafeEncode(row.id, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerFields.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerFields.scala new file mode 100644 index 0000000000..50841ec7c7 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerFields.scala @@ -0,0 +1,53 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import adventureworks.frontpage.user.UserFields +import adventureworks.frontpage.user.UserId +import adventureworks.frontpage.user.UserRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait CustomerFields { + def id: IdField[CustomerId, CustomerRow] + def userId: OptField[UserId, CustomerRow] + def companyName: OptField[String, CustomerRow] + def creditLimit: OptField[BigDecimal, CustomerRow] + def verified: OptField[Boolean, CustomerRow] + def fkUser: ForeignKey[UserFields, UserRow] = + ForeignKey[UserFields, UserRow]("frontpage.customer_user_id_fkey", Nil) + .withColumnPair(userId, _.id) +} + +object CustomerFields { + lazy val structure: Relation[CustomerFields, CustomerRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[CustomerFields, CustomerRow] { + + override lazy val fields: CustomerFields = new CustomerFields { + override def id = IdField[CustomerId, CustomerRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def userId = OptField[UserId, CustomerRow](_path, "user_id", None, Some("uuid"), x => x.userId, (row, value) => row.copy(userId = value)) + override def companyName = OptField[String, CustomerRow](_path, "company_name", None, None, x => x.companyName, (row, value) => row.copy(companyName = value)) + override def creditLimit = OptField[BigDecimal, CustomerRow](_path, "credit_limit", None, Some("numeric"), x => x.creditLimit, (row, value) => row.copy(creditLimit = value)) + override def verified = OptField[Boolean, CustomerRow](_path, "verified", None, None, x => x.verified, (row, value) => row.copy(verified = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, CustomerRow]] = + List[FieldLikeNoHkt[?, CustomerRow]](fields.id, fields.userId, fields.companyName, fields.creditLimit, fields.verified) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerId.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerId.scala new file mode 100644 index 0000000000..bbd1d89a73 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerId.scala @@ -0,0 +1,33 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import adventureworks.customtypes.TypoUUID +import doobie.postgres.Text +import doobie.util.Get +import doobie.util.Put +import io.circe.Decoder +import io.circe.Encoder +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.customer` */ +case class CustomerId(value: TypoUUID) extends AnyVal +object CustomerId { + implicit lazy val arrayGet: Get[Array[CustomerId]] = TypoUUID.arrayGet.map(_.map(CustomerId.apply)) + implicit lazy val arrayPut: Put[Array[CustomerId]] = TypoUUID.arrayPut.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[CustomerId, TypoUUID] = Bijection[CustomerId, TypoUUID](_.value)(CustomerId.apply) + implicit lazy val decoder: Decoder[CustomerId] = TypoUUID.decoder.map(CustomerId.apply) + implicit lazy val encoder: Encoder[CustomerId] = TypoUUID.encoder.contramap(_.value) + implicit lazy val get: Get[CustomerId] = TypoUUID.get.map(CustomerId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[CustomerId] = Ordering.by(_.value) + implicit lazy val put: Put[CustomerId] = TypoUUID.put.contramap(_.value) + implicit lazy val text: Text[CustomerId] = new Text[CustomerId] { + override def unsafeEncode(v: CustomerId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: CustomerId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepo.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepo.scala new file mode 100644 index 0000000000..7ffae12cc1 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepo.scala @@ -0,0 +1,36 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import doobie.free.connection.ConnectionIO +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait CustomerRepo { + def delete: DeleteBuilder[CustomerFields, CustomerRow] + def deleteById(id: CustomerId): ConnectionIO[Boolean] + def deleteByIds(ids: Array[CustomerId]): ConnectionIO[Int] + def insert(unsaved: CustomerRow): ConnectionIO[CustomerRow] + def insert(unsaved: CustomerRowUnsaved): ConnectionIO[CustomerRow] + def insertStreaming(unsaved: Stream[ConnectionIO, CustomerRow], batchSize: Int = 10000): ConnectionIO[Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, CustomerRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] + def select: SelectBuilder[CustomerFields, CustomerRow] + def selectAll: Stream[ConnectionIO, CustomerRow] + def selectById(id: CustomerId): ConnectionIO[Option[CustomerRow]] + def selectByIds(ids: Array[CustomerId]): Stream[ConnectionIO, CustomerRow] + def selectByIdsTracked(ids: Array[CustomerId]): ConnectionIO[Map[CustomerId, CustomerRow]] + def update: UpdateBuilder[CustomerFields, CustomerRow] + def update(row: CustomerRow): ConnectionIO[Boolean] + def upsert(unsaved: CustomerRow): ConnectionIO[CustomerRow] + def upsertBatch(unsaved: List[CustomerRow]): Stream[ConnectionIO, CustomerRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Stream[ConnectionIO, CustomerRow], batchSize: Int = 10000): ConnectionIO[Int] +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepoImpl.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepoImpl.scala new file mode 100644 index 0000000000..e79fc759b7 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepoImpl.scala @@ -0,0 +1,161 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.user.UserId +import cats.instances.list.catsStdInstancesForList +import doobie.free.connection.ConnectionIO +import doobie.postgres.syntax.FragmentOps +import doobie.syntax.SqlInterpolator.SingleFragment.fromWrite +import doobie.syntax.string.toSqlInterpolator +import doobie.util.Write +import doobie.util.fragment.Fragment +import doobie.util.meta.Meta +import doobie.util.update.Update +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class CustomerRepoImpl extends CustomerRepo { + override def delete: DeleteBuilder[CustomerFields, CustomerRow] = { + DeleteBuilder(""""frontpage"."customer"""", CustomerFields.structure) + } + override def deleteById(id: CustomerId): ConnectionIO[Boolean] = { + sql"""delete from "frontpage"."customer" where "id" = ${fromWrite(id)(new Write.Single(CustomerId.put))}""".update.run.map(_ > 0) + } + override def deleteByIds(ids: Array[CustomerId]): ConnectionIO[Int] = { + sql"""delete from "frontpage"."customer" where "id" = ANY(${ids})""".update.run + } + override def insert(unsaved: CustomerRow): ConnectionIO[CustomerRow] = { + sql"""insert into "frontpage"."customer"("id", "user_id", "company_name", "credit_limit", "verified") + values (${fromWrite(unsaved.id)(new Write.Single(CustomerId.put))}::uuid, ${fromWrite(unsaved.userId)(new Write.SingleOpt(UserId.put))}::uuid, ${fromWrite(unsaved.companyName)(new Write.SingleOpt(Meta.StringMeta.put))}, ${fromWrite(unsaved.creditLimit)(new Write.SingleOpt(Meta.ScalaBigDecimalMeta.put))}::numeric, ${fromWrite(unsaved.verified)(new Write.SingleOpt(Meta.BooleanMeta.put))}) + returning "id", "user_id", "company_name", "credit_limit", "verified" + """.query(using CustomerRow.read).unique + } + override def insert(unsaved: CustomerRowUnsaved): ConnectionIO[CustomerRow] = { + val fs = List( + Some((Fragment.const0(s""""user_id""""), fr"${fromWrite(unsaved.userId)(new Write.SingleOpt(UserId.put))}::uuid")), + Some((Fragment.const0(s""""company_name""""), fr"${fromWrite(unsaved.companyName)(new Write.SingleOpt(Meta.StringMeta.put))}")), + Some((Fragment.const0(s""""credit_limit""""), fr"${fromWrite(unsaved.creditLimit)(new Write.SingleOpt(Meta.ScalaBigDecimalMeta.put))}::numeric")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""id""""), fr"${fromWrite(value: CustomerId)(new Write.Single(CustomerId.put))}::uuid")) + }, + unsaved.verified match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""verified""""), fr"${fromWrite(value: Option[Boolean])(new Write.SingleOpt(Meta.BooleanMeta.put))}")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."customer" default values + returning "id", "user_id", "company_name", "credit_limit", "verified" + """ + } else { + val CommaSeparate = Fragment.FragmentMonoid.intercalate(fr", ") + sql"""insert into "frontpage"."customer"(${CommaSeparate.combineAllOption(fs.map { case (n, _) => n }).get}) + values (${CommaSeparate.combineAllOption(fs.map { case (_, f) => f }).get}) + returning "id", "user_id", "company_name", "credit_limit", "verified" + """ + } + q.query(using CustomerRow.read).unique + + } + override def insertStreaming(unsaved: Stream[ConnectionIO, CustomerRow], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."customer"("id", "user_id", "company_name", "credit_limit", "verified") FROM STDIN""").copyIn(unsaved, batchSize)(using CustomerRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, CustomerRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."customer"("user_id", "company_name", "credit_limit", "id", "verified") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""").copyIn(unsaved, batchSize)(using CustomerRowUnsaved.text) + } + override def select: SelectBuilder[CustomerFields, CustomerRow] = { + SelectBuilderSql(""""frontpage"."customer"""", CustomerFields.structure, CustomerRow.read) + } + override def selectAll: Stream[ConnectionIO, CustomerRow] = { + sql"""select "id", "user_id", "company_name", "credit_limit", "verified" from "frontpage"."customer"""".query(using CustomerRow.read).stream + } + override def selectById(id: CustomerId): ConnectionIO[Option[CustomerRow]] = { + sql"""select "id", "user_id", "company_name", "credit_limit", "verified" from "frontpage"."customer" where "id" = ${fromWrite(id)(new Write.Single(CustomerId.put))}""".query(using CustomerRow.read).option + } + override def selectByIds(ids: Array[CustomerId]): Stream[ConnectionIO, CustomerRow] = { + sql"""select "id", "user_id", "company_name", "credit_limit", "verified" from "frontpage"."customer" where "id" = ANY(${ids})""".query(using CustomerRow.read).stream + } + override def selectByIdsTracked(ids: Array[CustomerId]): ConnectionIO[Map[CustomerId, CustomerRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[CustomerFields, CustomerRow] = { + UpdateBuilder(""""frontpage"."customer"""", CustomerFields.structure, CustomerRow.read) + } + override def update(row: CustomerRow): ConnectionIO[Boolean] = { + val id = row.id + sql"""update "frontpage"."customer" + set "user_id" = ${fromWrite(row.userId)(new Write.SingleOpt(UserId.put))}::uuid, + "company_name" = ${fromWrite(row.companyName)(new Write.SingleOpt(Meta.StringMeta.put))}, + "credit_limit" = ${fromWrite(row.creditLimit)(new Write.SingleOpt(Meta.ScalaBigDecimalMeta.put))}::numeric, + "verified" = ${fromWrite(row.verified)(new Write.SingleOpt(Meta.BooleanMeta.put))} + where "id" = ${fromWrite(id)(new Write.Single(CustomerId.put))}""" + .update + .run + .map(_ > 0) + } + override def upsert(unsaved: CustomerRow): ConnectionIO[CustomerRow] = { + sql"""insert into "frontpage"."customer"("id", "user_id", "company_name", "credit_limit", "verified") + values ( + ${fromWrite(unsaved.id)(new Write.Single(CustomerId.put))}::uuid, + ${fromWrite(unsaved.userId)(new Write.SingleOpt(UserId.put))}::uuid, + ${fromWrite(unsaved.companyName)(new Write.SingleOpt(Meta.StringMeta.put))}, + ${fromWrite(unsaved.creditLimit)(new Write.SingleOpt(Meta.ScalaBigDecimalMeta.put))}::numeric, + ${fromWrite(unsaved.verified)(new Write.SingleOpt(Meta.BooleanMeta.put))} + ) + on conflict ("id") + do update set + "user_id" = EXCLUDED."user_id", + "company_name" = EXCLUDED."company_name", + "credit_limit" = EXCLUDED."credit_limit", + "verified" = EXCLUDED."verified" + returning "id", "user_id", "company_name", "credit_limit", "verified" + """.query(using CustomerRow.read).unique + } + override def upsertBatch(unsaved: List[CustomerRow]): Stream[ConnectionIO, CustomerRow] = { + Update[CustomerRow]( + s"""insert into "frontpage"."customer"("id", "user_id", "company_name", "credit_limit", "verified") + values (?::uuid,?::uuid,?,?::numeric,?) + on conflict ("id") + do update set + "user_id" = EXCLUDED."user_id", + "company_name" = EXCLUDED."company_name", + "credit_limit" = EXCLUDED."credit_limit", + "verified" = EXCLUDED."verified" + returning "id", "user_id", "company_name", "credit_limit", "verified"""" + )(using CustomerRow.write) + .updateManyWithGeneratedKeys[CustomerRow]("id", "user_id", "company_name", "credit_limit", "verified")(unsaved)(using catsStdInstancesForList, CustomerRow.read) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, CustomerRow], batchSize: Int = 10000): ConnectionIO[Int] = { + for { + _ <- sql"""create temporary table customer_TEMP (like "frontpage"."customer") on commit drop""".update.run + _ <- new FragmentOps(sql"""copy customer_TEMP("id", "user_id", "company_name", "credit_limit", "verified") from stdin""").copyIn(unsaved, batchSize)(using CustomerRow.text) + res <- sql"""insert into "frontpage"."customer"("id", "user_id", "company_name", "credit_limit", "verified") + select * from customer_TEMP + on conflict ("id") + do update set + "user_id" = EXCLUDED."user_id", + "company_name" = EXCLUDED."company_name", + "credit_limit" = EXCLUDED."credit_limit", + "verified" = EXCLUDED."verified" + ; + drop table customer_TEMP;""".update.run + } yield res + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepoMock.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepoMock.scala new file mode 100644 index 0000000000..7ec02aad82 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepoMock.scala @@ -0,0 +1,127 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import doobie.free.connection.ConnectionIO +import doobie.free.connection.delay +import fs2.Stream +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class CustomerRepoMock(toRow: Function1[CustomerRowUnsaved, CustomerRow], + map: scala.collection.mutable.Map[CustomerId, CustomerRow] = scala.collection.mutable.Map.empty) extends CustomerRepo { + override def delete: DeleteBuilder[CustomerFields, CustomerRow] = { + DeleteBuilderMock(DeleteParams.empty, CustomerFields.structure, map) + } + override def deleteById(id: CustomerId): ConnectionIO[Boolean] = { + delay(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[CustomerId]): ConnectionIO[Int] = { + delay(ids.map(id => map.remove(id)).count(_.isDefined)) + } + override def insert(unsaved: CustomerRow): ConnectionIO[CustomerRow] = { + delay { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: CustomerRowUnsaved): ConnectionIO[CustomerRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Stream[ConnectionIO, CustomerRow], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { rows => + var num = 0L + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, CustomerRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { unsavedRows => + var num = 0L + unsavedRows.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + num += 1 + } + num + } + } + override def select: SelectBuilder[CustomerFields, CustomerRow] = { + SelectBuilderMock(CustomerFields.structure, delay(map.values.toList), SelectParams.empty) + } + override def selectAll: Stream[ConnectionIO, CustomerRow] = { + Stream.emits(map.values.toList) + } + override def selectById(id: CustomerId): ConnectionIO[Option[CustomerRow]] = { + delay(map.get(id)) + } + override def selectByIds(ids: Array[CustomerId]): Stream[ConnectionIO, CustomerRow] = { + Stream.emits(ids.flatMap(map.get).toList) + } + override def selectByIdsTracked(ids: Array[CustomerId]): ConnectionIO[Map[CustomerId, CustomerRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[CustomerFields, CustomerRow] = { + UpdateBuilderMock(UpdateParams.empty, CustomerFields.structure, map) + } + override def update(row: CustomerRow): ConnectionIO[Boolean] = { + delay { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: CustomerRow): ConnectionIO[CustomerRow] = { + delay { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + } + override def upsertBatch(unsaved: List[CustomerRow]): Stream[ConnectionIO, CustomerRow] = { + Stream.emits { + unsaved.map { row => + map += (row.id -> row) + row + } + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, CustomerRow], batchSize: Int = 10000): ConnectionIO[Int] = { + unsaved.compile.toList.map { rows => + var num = 0 + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRow.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRow.scala new file mode 100644 index 0000000000..778b24e7fc --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRow.scala @@ -0,0 +1,72 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.user.UserId +import doobie.postgres.Text +import doobie.util.Read +import doobie.util.Write +import doobie.util.meta.Meta +import io.circe.Decoder +import io.circe.Encoder + +/** Table: frontpage.customer + Primary key: id */ +case class CustomerRow( + /** Default: gen_random_uuid() */ + id: CustomerId, + /** Points to [[user.UserRow.id]] */ + userId: Option[UserId], + companyName: Option[String], + creditLimit: Option[BigDecimal], + /** Default: false */ + verified: Option[Boolean] +){ + def toUnsavedRow(id: Defaulted[CustomerId], verified: Defaulted[Option[Boolean]] = Defaulted.Provided(this.verified)): CustomerRowUnsaved = + CustomerRowUnsaved(userId, companyName, creditLimit, id, verified) + } + +object CustomerRow { + implicit lazy val decoder: Decoder[CustomerRow] = Decoder.forProduct5[CustomerRow, CustomerId, Option[UserId], Option[String], Option[BigDecimal], Option[Boolean]]("id", "user_id", "company_name", "credit_limit", "verified")(CustomerRow.apply)(CustomerId.decoder, Decoder.decodeOption(UserId.decoder), Decoder.decodeOption(Decoder.decodeString), Decoder.decodeOption(Decoder.decodeBigDecimal), Decoder.decodeOption(Decoder.decodeBoolean)) + implicit lazy val encoder: Encoder[CustomerRow] = Encoder.forProduct5[CustomerRow, CustomerId, Option[UserId], Option[String], Option[BigDecimal], Option[Boolean]]("id", "user_id", "company_name", "credit_limit", "verified")(x => (x.id, x.userId, x.companyName, x.creditLimit, x.verified))(CustomerId.encoder, Encoder.encodeOption(UserId.encoder), Encoder.encodeOption(Encoder.encodeString), Encoder.encodeOption(Encoder.encodeBigDecimal), Encoder.encodeOption(Encoder.encodeBoolean)) + implicit lazy val read: Read[CustomerRow] = new Read.CompositeOfInstances(Array( + new Read.Single(CustomerId.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(UserId.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(Meta.StringMeta.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(Meta.ScalaBigDecimalMeta.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(Meta.BooleanMeta.get).asInstanceOf[Read[Any]] + ))(using scala.reflect.ClassTag.Any).map { arr => + CustomerRow( + id = arr(0).asInstanceOf[CustomerId], + userId = arr(1).asInstanceOf[Option[UserId]], + companyName = arr(2).asInstanceOf[Option[String]], + creditLimit = arr(3).asInstanceOf[Option[BigDecimal]], + verified = arr(4).asInstanceOf[Option[Boolean]] + ) + } + implicit lazy val text: Text[CustomerRow] = Text.instance[CustomerRow]{ (row, sb) => + CustomerId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.option(UserId.text).unsafeEncode(row.userId, sb) + sb.append(Text.DELIMETER) + Text.option(Text.stringInstance).unsafeEncode(row.companyName, sb) + sb.append(Text.DELIMETER) + Text.option(Text.bigDecimalInstance).unsafeEncode(row.creditLimit, sb) + sb.append(Text.DELIMETER) + Text.option(Text.booleanInstance).unsafeEncode(row.verified, sb) + } + implicit lazy val write: Write[CustomerRow] = new Write.Composite[CustomerRow]( + List(new Write.Single(CustomerId.put), + new Write.Single(UserId.put).toOpt, + new Write.Single(Meta.StringMeta.put).toOpt, + new Write.Single(Meta.ScalaBigDecimalMeta.put).toOpt, + new Write.Single(Meta.BooleanMeta.put).toOpt), + a => List(a.id, a.userId, a.companyName, a.creditLimit, a.verified) + ) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRowUnsaved.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRowUnsaved.scala new file mode 100644 index 0000000000..a0a8a42d04 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRowUnsaved.scala @@ -0,0 +1,56 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.user.UserId +import doobie.postgres.Text +import io.circe.Decoder +import io.circe.Encoder + +/** This class corresponds to a row in table `frontpage.customer` which has not been persisted yet */ +case class CustomerRowUnsaved( + /** Points to [[user.UserRow.id]] */ + userId: Option[UserId], + companyName: Option[String], + creditLimit: Option[BigDecimal], + /** Default: gen_random_uuid() */ + id: Defaulted[CustomerId] = Defaulted.UseDefault, + /** Default: false */ + verified: Defaulted[Option[Boolean]] = Defaulted.UseDefault +) { + def toRow(idDefault: => CustomerId, verifiedDefault: => Option[Boolean]): CustomerRow = + CustomerRow( + userId = userId, + companyName = companyName, + creditLimit = creditLimit, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + }, + verified = verified match { + case Defaulted.UseDefault => verifiedDefault + case Defaulted.Provided(value) => value + } + ) +} +object CustomerRowUnsaved { + implicit lazy val decoder: Decoder[CustomerRowUnsaved] = Decoder.forProduct5[CustomerRowUnsaved, Option[UserId], Option[String], Option[BigDecimal], Defaulted[CustomerId], Defaulted[Option[Boolean]]]("user_id", "company_name", "credit_limit", "id", "verified")(CustomerRowUnsaved.apply)(Decoder.decodeOption(UserId.decoder), Decoder.decodeOption(Decoder.decodeString), Decoder.decodeOption(Decoder.decodeBigDecimal), Defaulted.decoder(CustomerId.decoder), Defaulted.decoder(Decoder.decodeOption(Decoder.decodeBoolean))) + implicit lazy val encoder: Encoder[CustomerRowUnsaved] = Encoder.forProduct5[CustomerRowUnsaved, Option[UserId], Option[String], Option[BigDecimal], Defaulted[CustomerId], Defaulted[Option[Boolean]]]("user_id", "company_name", "credit_limit", "id", "verified")(x => (x.userId, x.companyName, x.creditLimit, x.id, x.verified))(Encoder.encodeOption(UserId.encoder), Encoder.encodeOption(Encoder.encodeString), Encoder.encodeOption(Encoder.encodeBigDecimal), Defaulted.encoder(CustomerId.encoder), Defaulted.encoder(Encoder.encodeOption(Encoder.encodeBoolean))) + implicit lazy val text: Text[CustomerRowUnsaved] = Text.instance[CustomerRowUnsaved]{ (row, sb) => + Text.option(UserId.text).unsafeEncode(row.userId, sb) + sb.append(Text.DELIMETER) + Text.option(Text.stringInstance).unsafeEncode(row.companyName, sb) + sb.append(Text.DELIMETER) + Text.option(Text.bigDecimalInstance).unsafeEncode(row.creditLimit, sb) + sb.append(Text.DELIMETER) + Defaulted.text(CustomerId.text).unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text.booleanInstance)).unsafeEncode(row.verified, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentFields.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentFields.scala new file mode 100644 index 0000000000..deae7d4799 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentFields.scala @@ -0,0 +1,52 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import adventureworks.frontpage.company.CompanyFields +import adventureworks.frontpage.company.CompanyId +import adventureworks.frontpage.company.CompanyRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait DepartmentFields { + def id: IdField[DepartmentId, DepartmentRow] + def name: Field[String, DepartmentRow] + def budget: OptField[BigDecimal, DepartmentRow] + def companyId: OptField[CompanyId, DepartmentRow] + def fkCompany: ForeignKey[CompanyFields, CompanyRow] = + ForeignKey[CompanyFields, CompanyRow]("frontpage.department_company_id_fkey", Nil) + .withColumnPair(companyId, _.id) +} + +object DepartmentFields { + lazy val structure: Relation[DepartmentFields, DepartmentRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[DepartmentFields, DepartmentRow] { + + override lazy val fields: DepartmentFields = new DepartmentFields { + override def id = IdField[DepartmentId, DepartmentRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, DepartmentRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + override def budget = OptField[BigDecimal, DepartmentRow](_path, "budget", None, Some("numeric"), x => x.budget, (row, value) => row.copy(budget = value)) + override def companyId = OptField[CompanyId, DepartmentRow](_path, "company_id", None, Some("uuid"), x => x.companyId, (row, value) => row.copy(companyId = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, DepartmentRow]] = + List[FieldLikeNoHkt[?, DepartmentRow]](fields.id, fields.name, fields.budget, fields.companyId) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentId.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentId.scala new file mode 100644 index 0000000000..f9c7dc6d1c --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentId.scala @@ -0,0 +1,33 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import adventureworks.customtypes.TypoUUID +import doobie.postgres.Text +import doobie.util.Get +import doobie.util.Put +import io.circe.Decoder +import io.circe.Encoder +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.department` */ +case class DepartmentId(value: TypoUUID) extends AnyVal +object DepartmentId { + implicit lazy val arrayGet: Get[Array[DepartmentId]] = TypoUUID.arrayGet.map(_.map(DepartmentId.apply)) + implicit lazy val arrayPut: Put[Array[DepartmentId]] = TypoUUID.arrayPut.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[DepartmentId, TypoUUID] = Bijection[DepartmentId, TypoUUID](_.value)(DepartmentId.apply) + implicit lazy val decoder: Decoder[DepartmentId] = TypoUUID.decoder.map(DepartmentId.apply) + implicit lazy val encoder: Encoder[DepartmentId] = TypoUUID.encoder.contramap(_.value) + implicit lazy val get: Get[DepartmentId] = TypoUUID.get.map(DepartmentId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[DepartmentId] = Ordering.by(_.value) + implicit lazy val put: Put[DepartmentId] = TypoUUID.put.contramap(_.value) + implicit lazy val text: Text[DepartmentId] = new Text[DepartmentId] { + override def unsafeEncode(v: DepartmentId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: DepartmentId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepo.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepo.scala new file mode 100644 index 0000000000..b83152aae3 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepo.scala @@ -0,0 +1,36 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import doobie.free.connection.ConnectionIO +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait DepartmentRepo { + def delete: DeleteBuilder[DepartmentFields, DepartmentRow] + def deleteById(id: DepartmentId): ConnectionIO[Boolean] + def deleteByIds(ids: Array[DepartmentId]): ConnectionIO[Int] + def insert(unsaved: DepartmentRow): ConnectionIO[DepartmentRow] + def insert(unsaved: DepartmentRowUnsaved): ConnectionIO[DepartmentRow] + def insertStreaming(unsaved: Stream[ConnectionIO, DepartmentRow], batchSize: Int = 10000): ConnectionIO[Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, DepartmentRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] + def select: SelectBuilder[DepartmentFields, DepartmentRow] + def selectAll: Stream[ConnectionIO, DepartmentRow] + def selectById(id: DepartmentId): ConnectionIO[Option[DepartmentRow]] + def selectByIds(ids: Array[DepartmentId]): Stream[ConnectionIO, DepartmentRow] + def selectByIdsTracked(ids: Array[DepartmentId]): ConnectionIO[Map[DepartmentId, DepartmentRow]] + def update: UpdateBuilder[DepartmentFields, DepartmentRow] + def update(row: DepartmentRow): ConnectionIO[Boolean] + def upsert(unsaved: DepartmentRow): ConnectionIO[DepartmentRow] + def upsertBatch(unsaved: List[DepartmentRow]): Stream[ConnectionIO, DepartmentRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Stream[ConnectionIO, DepartmentRow], batchSize: Int = 10000): ConnectionIO[Int] +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepoImpl.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepoImpl.scala new file mode 100644 index 0000000000..4da68dfafd --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepoImpl.scala @@ -0,0 +1,152 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.company.CompanyId +import cats.instances.list.catsStdInstancesForList +import doobie.free.connection.ConnectionIO +import doobie.postgres.syntax.FragmentOps +import doobie.syntax.SqlInterpolator.SingleFragment.fromWrite +import doobie.syntax.string.toSqlInterpolator +import doobie.util.Write +import doobie.util.fragment.Fragment +import doobie.util.meta.Meta +import doobie.util.update.Update +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class DepartmentRepoImpl extends DepartmentRepo { + override def delete: DeleteBuilder[DepartmentFields, DepartmentRow] = { + DeleteBuilder(""""frontpage"."department"""", DepartmentFields.structure) + } + override def deleteById(id: DepartmentId): ConnectionIO[Boolean] = { + sql"""delete from "frontpage"."department" where "id" = ${fromWrite(id)(new Write.Single(DepartmentId.put))}""".update.run.map(_ > 0) + } + override def deleteByIds(ids: Array[DepartmentId]): ConnectionIO[Int] = { + sql"""delete from "frontpage"."department" where "id" = ANY(${ids})""".update.run + } + override def insert(unsaved: DepartmentRow): ConnectionIO[DepartmentRow] = { + sql"""insert into "frontpage"."department"("id", "name", "budget", "company_id") + values (${fromWrite(unsaved.id)(new Write.Single(DepartmentId.put))}::uuid, ${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}, ${fromWrite(unsaved.budget)(new Write.SingleOpt(Meta.ScalaBigDecimalMeta.put))}::numeric, ${fromWrite(unsaved.companyId)(new Write.SingleOpt(CompanyId.put))}::uuid) + returning "id", "name", "budget", "company_id" + """.query(using DepartmentRow.read).unique + } + override def insert(unsaved: DepartmentRowUnsaved): ConnectionIO[DepartmentRow] = { + val fs = List( + Some((Fragment.const0(s""""name""""), fr"${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}")), + Some((Fragment.const0(s""""budget""""), fr"${fromWrite(unsaved.budget)(new Write.SingleOpt(Meta.ScalaBigDecimalMeta.put))}::numeric")), + Some((Fragment.const0(s""""company_id""""), fr"${fromWrite(unsaved.companyId)(new Write.SingleOpt(CompanyId.put))}::uuid")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""id""""), fr"${fromWrite(value: DepartmentId)(new Write.Single(DepartmentId.put))}::uuid")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."department" default values + returning "id", "name", "budget", "company_id" + """ + } else { + val CommaSeparate = Fragment.FragmentMonoid.intercalate(fr", ") + sql"""insert into "frontpage"."department"(${CommaSeparate.combineAllOption(fs.map { case (n, _) => n }).get}) + values (${CommaSeparate.combineAllOption(fs.map { case (_, f) => f }).get}) + returning "id", "name", "budget", "company_id" + """ + } + q.query(using DepartmentRow.read).unique + + } + override def insertStreaming(unsaved: Stream[ConnectionIO, DepartmentRow], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."department"("id", "name", "budget", "company_id") FROM STDIN""").copyIn(unsaved, batchSize)(using DepartmentRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, DepartmentRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."department"("name", "budget", "company_id", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""").copyIn(unsaved, batchSize)(using DepartmentRowUnsaved.text) + } + override def select: SelectBuilder[DepartmentFields, DepartmentRow] = { + SelectBuilderSql(""""frontpage"."department"""", DepartmentFields.structure, DepartmentRow.read) + } + override def selectAll: Stream[ConnectionIO, DepartmentRow] = { + sql"""select "id", "name", "budget", "company_id" from "frontpage"."department"""".query(using DepartmentRow.read).stream + } + override def selectById(id: DepartmentId): ConnectionIO[Option[DepartmentRow]] = { + sql"""select "id", "name", "budget", "company_id" from "frontpage"."department" where "id" = ${fromWrite(id)(new Write.Single(DepartmentId.put))}""".query(using DepartmentRow.read).option + } + override def selectByIds(ids: Array[DepartmentId]): Stream[ConnectionIO, DepartmentRow] = { + sql"""select "id", "name", "budget", "company_id" from "frontpage"."department" where "id" = ANY(${ids})""".query(using DepartmentRow.read).stream + } + override def selectByIdsTracked(ids: Array[DepartmentId]): ConnectionIO[Map[DepartmentId, DepartmentRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[DepartmentFields, DepartmentRow] = { + UpdateBuilder(""""frontpage"."department"""", DepartmentFields.structure, DepartmentRow.read) + } + override def update(row: DepartmentRow): ConnectionIO[Boolean] = { + val id = row.id + sql"""update "frontpage"."department" + set "name" = ${fromWrite(row.name)(new Write.Single(Meta.StringMeta.put))}, + "budget" = ${fromWrite(row.budget)(new Write.SingleOpt(Meta.ScalaBigDecimalMeta.put))}::numeric, + "company_id" = ${fromWrite(row.companyId)(new Write.SingleOpt(CompanyId.put))}::uuid + where "id" = ${fromWrite(id)(new Write.Single(DepartmentId.put))}""" + .update + .run + .map(_ > 0) + } + override def upsert(unsaved: DepartmentRow): ConnectionIO[DepartmentRow] = { + sql"""insert into "frontpage"."department"("id", "name", "budget", "company_id") + values ( + ${fromWrite(unsaved.id)(new Write.Single(DepartmentId.put))}::uuid, + ${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}, + ${fromWrite(unsaved.budget)(new Write.SingleOpt(Meta.ScalaBigDecimalMeta.put))}::numeric, + ${fromWrite(unsaved.companyId)(new Write.SingleOpt(CompanyId.put))}::uuid + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "budget" = EXCLUDED."budget", + "company_id" = EXCLUDED."company_id" + returning "id", "name", "budget", "company_id" + """.query(using DepartmentRow.read).unique + } + override def upsertBatch(unsaved: List[DepartmentRow]): Stream[ConnectionIO, DepartmentRow] = { + Update[DepartmentRow]( + s"""insert into "frontpage"."department"("id", "name", "budget", "company_id") + values (?::uuid,?,?::numeric,?::uuid) + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "budget" = EXCLUDED."budget", + "company_id" = EXCLUDED."company_id" + returning "id", "name", "budget", "company_id"""" + )(using DepartmentRow.write) + .updateManyWithGeneratedKeys[DepartmentRow]("id", "name", "budget", "company_id")(unsaved)(using catsStdInstancesForList, DepartmentRow.read) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, DepartmentRow], batchSize: Int = 10000): ConnectionIO[Int] = { + for { + _ <- sql"""create temporary table department_TEMP (like "frontpage"."department") on commit drop""".update.run + _ <- new FragmentOps(sql"""copy department_TEMP("id", "name", "budget", "company_id") from stdin""").copyIn(unsaved, batchSize)(using DepartmentRow.text) + res <- sql"""insert into "frontpage"."department"("id", "name", "budget", "company_id") + select * from department_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "budget" = EXCLUDED."budget", + "company_id" = EXCLUDED."company_id" + ; + drop table department_TEMP;""".update.run + } yield res + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepoMock.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepoMock.scala new file mode 100644 index 0000000000..288c1ee2b9 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepoMock.scala @@ -0,0 +1,127 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import doobie.free.connection.ConnectionIO +import doobie.free.connection.delay +import fs2.Stream +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class DepartmentRepoMock(toRow: Function1[DepartmentRowUnsaved, DepartmentRow], + map: scala.collection.mutable.Map[DepartmentId, DepartmentRow] = scala.collection.mutable.Map.empty) extends DepartmentRepo { + override def delete: DeleteBuilder[DepartmentFields, DepartmentRow] = { + DeleteBuilderMock(DeleteParams.empty, DepartmentFields.structure, map) + } + override def deleteById(id: DepartmentId): ConnectionIO[Boolean] = { + delay(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[DepartmentId]): ConnectionIO[Int] = { + delay(ids.map(id => map.remove(id)).count(_.isDefined)) + } + override def insert(unsaved: DepartmentRow): ConnectionIO[DepartmentRow] = { + delay { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: DepartmentRowUnsaved): ConnectionIO[DepartmentRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Stream[ConnectionIO, DepartmentRow], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { rows => + var num = 0L + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, DepartmentRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { unsavedRows => + var num = 0L + unsavedRows.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + num += 1 + } + num + } + } + override def select: SelectBuilder[DepartmentFields, DepartmentRow] = { + SelectBuilderMock(DepartmentFields.structure, delay(map.values.toList), SelectParams.empty) + } + override def selectAll: Stream[ConnectionIO, DepartmentRow] = { + Stream.emits(map.values.toList) + } + override def selectById(id: DepartmentId): ConnectionIO[Option[DepartmentRow]] = { + delay(map.get(id)) + } + override def selectByIds(ids: Array[DepartmentId]): Stream[ConnectionIO, DepartmentRow] = { + Stream.emits(ids.flatMap(map.get).toList) + } + override def selectByIdsTracked(ids: Array[DepartmentId]): ConnectionIO[Map[DepartmentId, DepartmentRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[DepartmentFields, DepartmentRow] = { + UpdateBuilderMock(UpdateParams.empty, DepartmentFields.structure, map) + } + override def update(row: DepartmentRow): ConnectionIO[Boolean] = { + delay { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: DepartmentRow): ConnectionIO[DepartmentRow] = { + delay { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + } + override def upsertBatch(unsaved: List[DepartmentRow]): Stream[ConnectionIO, DepartmentRow] = { + Stream.emits { + unsaved.map { row => + map += (row.id -> row) + row + } + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, DepartmentRow], batchSize: Int = 10000): ConnectionIO[Int] = { + unsaved.compile.toList.map { rows => + var num = 0 + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRow.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRow.scala new file mode 100644 index 0000000000..e13dc5c289 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRow.scala @@ -0,0 +1,65 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.company.CompanyId +import doobie.postgres.Text +import doobie.util.Read +import doobie.util.Write +import doobie.util.meta.Meta +import io.circe.Decoder +import io.circe.Encoder + +/** Table: frontpage.department + Primary key: id */ +case class DepartmentRow( + /** Default: gen_random_uuid() */ + id: DepartmentId, + name: String, + budget: Option[BigDecimal], + /** Points to [[company.CompanyRow.id]] */ + companyId: Option[CompanyId] +){ + def toUnsavedRow(id: Defaulted[DepartmentId]): DepartmentRowUnsaved = + DepartmentRowUnsaved(name, budget, companyId, id) + } + +object DepartmentRow { + implicit lazy val decoder: Decoder[DepartmentRow] = Decoder.forProduct4[DepartmentRow, DepartmentId, String, Option[BigDecimal], Option[CompanyId]]("id", "name", "budget", "company_id")(DepartmentRow.apply)(DepartmentId.decoder, Decoder.decodeString, Decoder.decodeOption(Decoder.decodeBigDecimal), Decoder.decodeOption(CompanyId.decoder)) + implicit lazy val encoder: Encoder[DepartmentRow] = Encoder.forProduct4[DepartmentRow, DepartmentId, String, Option[BigDecimal], Option[CompanyId]]("id", "name", "budget", "company_id")(x => (x.id, x.name, x.budget, x.companyId))(DepartmentId.encoder, Encoder.encodeString, Encoder.encodeOption(Encoder.encodeBigDecimal), Encoder.encodeOption(CompanyId.encoder)) + implicit lazy val read: Read[DepartmentRow] = new Read.CompositeOfInstances(Array( + new Read.Single(DepartmentId.get).asInstanceOf[Read[Any]], + new Read.Single(Meta.StringMeta.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(Meta.ScalaBigDecimalMeta.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(CompanyId.get).asInstanceOf[Read[Any]] + ))(using scala.reflect.ClassTag.Any).map { arr => + DepartmentRow( + id = arr(0).asInstanceOf[DepartmentId], + name = arr(1).asInstanceOf[String], + budget = arr(2).asInstanceOf[Option[BigDecimal]], + companyId = arr(3).asInstanceOf[Option[CompanyId]] + ) + } + implicit lazy val text: Text[DepartmentRow] = Text.instance[DepartmentRow]{ (row, sb) => + DepartmentId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(Text.bigDecimalInstance).unsafeEncode(row.budget, sb) + sb.append(Text.DELIMETER) + Text.option(CompanyId.text).unsafeEncode(row.companyId, sb) + } + implicit lazy val write: Write[DepartmentRow] = new Write.Composite[DepartmentRow]( + List(new Write.Single(DepartmentId.put), + new Write.Single(Meta.StringMeta.put), + new Write.Single(Meta.ScalaBigDecimalMeta.put).toOpt, + new Write.Single(CompanyId.put).toOpt), + a => List(a.id, a.name, a.budget, a.companyId) + ) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRowUnsaved.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRowUnsaved.scala new file mode 100644 index 0000000000..c6f62aed51 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRowUnsaved.scala @@ -0,0 +1,48 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.company.CompanyId +import doobie.postgres.Text +import io.circe.Decoder +import io.circe.Encoder + +/** This class corresponds to a row in table `frontpage.department` which has not been persisted yet */ +case class DepartmentRowUnsaved( + name: String, + budget: Option[BigDecimal], + /** Points to [[company.CompanyRow.id]] */ + companyId: Option[CompanyId], + /** Default: gen_random_uuid() */ + id: Defaulted[DepartmentId] = Defaulted.UseDefault +) { + def toRow(idDefault: => DepartmentId): DepartmentRow = + DepartmentRow( + name = name, + budget = budget, + companyId = companyId, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object DepartmentRowUnsaved { + implicit lazy val decoder: Decoder[DepartmentRowUnsaved] = Decoder.forProduct4[DepartmentRowUnsaved, String, Option[BigDecimal], Option[CompanyId], Defaulted[DepartmentId]]("name", "budget", "company_id", "id")(DepartmentRowUnsaved.apply)(Decoder.decodeString, Decoder.decodeOption(Decoder.decodeBigDecimal), Decoder.decodeOption(CompanyId.decoder), Defaulted.decoder(DepartmentId.decoder)) + implicit lazy val encoder: Encoder[DepartmentRowUnsaved] = Encoder.forProduct4[DepartmentRowUnsaved, String, Option[BigDecimal], Option[CompanyId], Defaulted[DepartmentId]]("name", "budget", "company_id", "id")(x => (x.name, x.budget, x.companyId, x.id))(Encoder.encodeString, Encoder.encodeOption(Encoder.encodeBigDecimal), Encoder.encodeOption(CompanyId.encoder), Defaulted.encoder(DepartmentId.encoder)) + implicit lazy val text: Text[DepartmentRowUnsaved] = Text.instance[DepartmentRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(Text.bigDecimalInstance).unsafeEncode(row.budget, sb) + sb.append(Text.DELIMETER) + Text.option(CompanyId.text).unsafeEncode(row.companyId, sb) + sb.append(Text.DELIMETER) + Defaulted.text(DepartmentId.text).unsafeEncode(row.id, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeFields.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeFields.scala new file mode 100644 index 0000000000..8ac2329269 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeFields.scala @@ -0,0 +1,50 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.frontpage.person.PersonFields +import adventureworks.frontpage.person.PersonId +import adventureworks.frontpage.person.PersonRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait EmployeeFields { + def id: IdField[EmployeeId, EmployeeRow] + def personId: Field[PersonId, EmployeeRow] + def salary: OptField[BigDecimal, EmployeeRow] + def fkPerson: ForeignKey[PersonFields, PersonRow] = + ForeignKey[PersonFields, PersonRow]("frontpage.fk_person", Nil) + .withColumnPair(personId, _.id) +} + +object EmployeeFields { + lazy val structure: Relation[EmployeeFields, EmployeeRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[EmployeeFields, EmployeeRow] { + + override lazy val fields: EmployeeFields = new EmployeeFields { + override def id = IdField[EmployeeId, EmployeeRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def personId = Field[PersonId, EmployeeRow](_path, "person_id", None, Some("uuid"), x => x.personId, (row, value) => row.copy(personId = value)) + override def salary = OptField[BigDecimal, EmployeeRow](_path, "salary", None, Some("numeric"), x => x.salary, (row, value) => row.copy(salary = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, EmployeeRow]] = + List[FieldLikeNoHkt[?, EmployeeRow]](fields.id, fields.personId, fields.salary) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeId.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeId.scala new file mode 100644 index 0000000000..d680cb64f6 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeId.scala @@ -0,0 +1,33 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.customtypes.TypoUUID +import doobie.postgres.Text +import doobie.util.Get +import doobie.util.Put +import io.circe.Decoder +import io.circe.Encoder +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.employee` */ +case class EmployeeId(value: TypoUUID) extends AnyVal +object EmployeeId { + implicit lazy val arrayGet: Get[Array[EmployeeId]] = TypoUUID.arrayGet.map(_.map(EmployeeId.apply)) + implicit lazy val arrayPut: Put[Array[EmployeeId]] = TypoUUID.arrayPut.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[EmployeeId, TypoUUID] = Bijection[EmployeeId, TypoUUID](_.value)(EmployeeId.apply) + implicit lazy val decoder: Decoder[EmployeeId] = TypoUUID.decoder.map(EmployeeId.apply) + implicit lazy val encoder: Encoder[EmployeeId] = TypoUUID.encoder.contramap(_.value) + implicit lazy val get: Get[EmployeeId] = TypoUUID.get.map(EmployeeId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[EmployeeId] = Ordering.by(_.value) + implicit lazy val put: Put[EmployeeId] = TypoUUID.put.contramap(_.value) + implicit lazy val text: Text[EmployeeId] = new Text[EmployeeId] { + override def unsafeEncode(v: EmployeeId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: EmployeeId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepo.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepo.scala new file mode 100644 index 0000000000..7ae5679104 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepo.scala @@ -0,0 +1,38 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.frontpage.person.PersonId +import doobie.free.connection.ConnectionIO +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait EmployeeRepo { + def delete: DeleteBuilder[EmployeeFields, EmployeeRow] + def deleteById(id: EmployeeId): ConnectionIO[Boolean] + def deleteByIds(ids: Array[EmployeeId]): ConnectionIO[Int] + def insert(unsaved: EmployeeRow): ConnectionIO[EmployeeRow] + def insert(unsaved: EmployeeRowUnsaved): ConnectionIO[EmployeeRow] + def insertStreaming(unsaved: Stream[ConnectionIO, EmployeeRow], batchSize: Int = 10000): ConnectionIO[Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, EmployeeRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] + def select: SelectBuilder[EmployeeFields, EmployeeRow] + def selectAll: Stream[ConnectionIO, EmployeeRow] + def selectById(id: EmployeeId): ConnectionIO[Option[EmployeeRow]] + def selectByIds(ids: Array[EmployeeId]): Stream[ConnectionIO, EmployeeRow] + def selectByIdsTracked(ids: Array[EmployeeId]): ConnectionIO[Map[EmployeeId, EmployeeRow]] + def selectByUniquePersonId(personId: PersonId): ConnectionIO[Option[EmployeeRow]] + def update: UpdateBuilder[EmployeeFields, EmployeeRow] + def update(row: EmployeeRow): ConnectionIO[Boolean] + def upsert(unsaved: EmployeeRow): ConnectionIO[EmployeeRow] + def upsertBatch(unsaved: List[EmployeeRow]): Stream[ConnectionIO, EmployeeRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Stream[ConnectionIO, EmployeeRow], batchSize: Int = 10000): ConnectionIO[Int] +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepoImpl.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepoImpl.scala new file mode 100644 index 0000000000..834d815d32 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepoImpl.scala @@ -0,0 +1,152 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.person.PersonId +import cats.instances.list.catsStdInstancesForList +import doobie.free.connection.ConnectionIO +import doobie.postgres.syntax.FragmentOps +import doobie.syntax.SqlInterpolator.SingleFragment.fromWrite +import doobie.syntax.string.toSqlInterpolator +import doobie.util.Write +import doobie.util.fragment.Fragment +import doobie.util.meta.Meta +import doobie.util.update.Update +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class EmployeeRepoImpl extends EmployeeRepo { + override def delete: DeleteBuilder[EmployeeFields, EmployeeRow] = { + DeleteBuilder(""""frontpage"."employee"""", EmployeeFields.structure) + } + override def deleteById(id: EmployeeId): ConnectionIO[Boolean] = { + sql"""delete from "frontpage"."employee" where "id" = ${fromWrite(id)(new Write.Single(EmployeeId.put))}""".update.run.map(_ > 0) + } + override def deleteByIds(ids: Array[EmployeeId]): ConnectionIO[Int] = { + sql"""delete from "frontpage"."employee" where "id" = ANY(${ids})""".update.run + } + override def insert(unsaved: EmployeeRow): ConnectionIO[EmployeeRow] = { + sql"""insert into "frontpage"."employee"("id", "person_id", "salary") + values (${fromWrite(unsaved.id)(new Write.Single(EmployeeId.put))}::uuid, ${fromWrite(unsaved.personId)(new Write.Single(PersonId.put))}::uuid, ${fromWrite(unsaved.salary)(new Write.SingleOpt(Meta.ScalaBigDecimalMeta.put))}::numeric) + returning "id", "person_id", "salary" + """.query(using EmployeeRow.read).unique + } + override def insert(unsaved: EmployeeRowUnsaved): ConnectionIO[EmployeeRow] = { + val fs = List( + Some((Fragment.const0(s""""person_id""""), fr"${fromWrite(unsaved.personId)(new Write.Single(PersonId.put))}::uuid")), + Some((Fragment.const0(s""""salary""""), fr"${fromWrite(unsaved.salary)(new Write.SingleOpt(Meta.ScalaBigDecimalMeta.put))}::numeric")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""id""""), fr"${fromWrite(value: EmployeeId)(new Write.Single(EmployeeId.put))}::uuid")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."employee" default values + returning "id", "person_id", "salary" + """ + } else { + val CommaSeparate = Fragment.FragmentMonoid.intercalate(fr", ") + sql"""insert into "frontpage"."employee"(${CommaSeparate.combineAllOption(fs.map { case (n, _) => n }).get}) + values (${CommaSeparate.combineAllOption(fs.map { case (_, f) => f }).get}) + returning "id", "person_id", "salary" + """ + } + q.query(using EmployeeRow.read).unique + + } + override def insertStreaming(unsaved: Stream[ConnectionIO, EmployeeRow], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."employee"("id", "person_id", "salary") FROM STDIN""").copyIn(unsaved, batchSize)(using EmployeeRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, EmployeeRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."employee"("person_id", "salary", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""").copyIn(unsaved, batchSize)(using EmployeeRowUnsaved.text) + } + override def select: SelectBuilder[EmployeeFields, EmployeeRow] = { + SelectBuilderSql(""""frontpage"."employee"""", EmployeeFields.structure, EmployeeRow.read) + } + override def selectAll: Stream[ConnectionIO, EmployeeRow] = { + sql"""select "id", "person_id", "salary" from "frontpage"."employee"""".query(using EmployeeRow.read).stream + } + override def selectById(id: EmployeeId): ConnectionIO[Option[EmployeeRow]] = { + sql"""select "id", "person_id", "salary" from "frontpage"."employee" where "id" = ${fromWrite(id)(new Write.Single(EmployeeId.put))}""".query(using EmployeeRow.read).option + } + override def selectByIds(ids: Array[EmployeeId]): Stream[ConnectionIO, EmployeeRow] = { + sql"""select "id", "person_id", "salary" from "frontpage"."employee" where "id" = ANY(${ids})""".query(using EmployeeRow.read).stream + } + override def selectByIdsTracked(ids: Array[EmployeeId]): ConnectionIO[Map[EmployeeId, EmployeeRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def selectByUniquePersonId(personId: PersonId): ConnectionIO[Option[EmployeeRow]] = { + sql"""select "id", "person_id", "salary" + from "frontpage"."employee" + where "person_id" = ${fromWrite(personId)(new Write.Single(PersonId.put))} + """.query(using EmployeeRow.read).option + } + override def update: UpdateBuilder[EmployeeFields, EmployeeRow] = { + UpdateBuilder(""""frontpage"."employee"""", EmployeeFields.structure, EmployeeRow.read) + } + override def update(row: EmployeeRow): ConnectionIO[Boolean] = { + val id = row.id + sql"""update "frontpage"."employee" + set "person_id" = ${fromWrite(row.personId)(new Write.Single(PersonId.put))}::uuid, + "salary" = ${fromWrite(row.salary)(new Write.SingleOpt(Meta.ScalaBigDecimalMeta.put))}::numeric + where "id" = ${fromWrite(id)(new Write.Single(EmployeeId.put))}""" + .update + .run + .map(_ > 0) + } + override def upsert(unsaved: EmployeeRow): ConnectionIO[EmployeeRow] = { + sql"""insert into "frontpage"."employee"("id", "person_id", "salary") + values ( + ${fromWrite(unsaved.id)(new Write.Single(EmployeeId.put))}::uuid, + ${fromWrite(unsaved.personId)(new Write.Single(PersonId.put))}::uuid, + ${fromWrite(unsaved.salary)(new Write.SingleOpt(Meta.ScalaBigDecimalMeta.put))}::numeric + ) + on conflict ("id") + do update set + "person_id" = EXCLUDED."person_id", + "salary" = EXCLUDED."salary" + returning "id", "person_id", "salary" + """.query(using EmployeeRow.read).unique + } + override def upsertBatch(unsaved: List[EmployeeRow]): Stream[ConnectionIO, EmployeeRow] = { + Update[EmployeeRow]( + s"""insert into "frontpage"."employee"("id", "person_id", "salary") + values (?::uuid,?::uuid,?::numeric) + on conflict ("id") + do update set + "person_id" = EXCLUDED."person_id", + "salary" = EXCLUDED."salary" + returning "id", "person_id", "salary"""" + )(using EmployeeRow.write) + .updateManyWithGeneratedKeys[EmployeeRow]("id", "person_id", "salary")(unsaved)(using catsStdInstancesForList, EmployeeRow.read) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, EmployeeRow], batchSize: Int = 10000): ConnectionIO[Int] = { + for { + _ <- sql"""create temporary table employee_TEMP (like "frontpage"."employee") on commit drop""".update.run + _ <- new FragmentOps(sql"""copy employee_TEMP("id", "person_id", "salary") from stdin""").copyIn(unsaved, batchSize)(using EmployeeRow.text) + res <- sql"""insert into "frontpage"."employee"("id", "person_id", "salary") + select * from employee_TEMP + on conflict ("id") + do update set + "person_id" = EXCLUDED."person_id", + "salary" = EXCLUDED."salary" + ; + drop table employee_TEMP;""".update.run + } yield res + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepoMock.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepoMock.scala new file mode 100644 index 0000000000..294024b4c9 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepoMock.scala @@ -0,0 +1,131 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.frontpage.person.PersonId +import doobie.free.connection.ConnectionIO +import doobie.free.connection.delay +import fs2.Stream +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class EmployeeRepoMock(toRow: Function1[EmployeeRowUnsaved, EmployeeRow], + map: scala.collection.mutable.Map[EmployeeId, EmployeeRow] = scala.collection.mutable.Map.empty) extends EmployeeRepo { + override def delete: DeleteBuilder[EmployeeFields, EmployeeRow] = { + DeleteBuilderMock(DeleteParams.empty, EmployeeFields.structure, map) + } + override def deleteById(id: EmployeeId): ConnectionIO[Boolean] = { + delay(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[EmployeeId]): ConnectionIO[Int] = { + delay(ids.map(id => map.remove(id)).count(_.isDefined)) + } + override def insert(unsaved: EmployeeRow): ConnectionIO[EmployeeRow] = { + delay { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: EmployeeRowUnsaved): ConnectionIO[EmployeeRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Stream[ConnectionIO, EmployeeRow], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { rows => + var num = 0L + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, EmployeeRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { unsavedRows => + var num = 0L + unsavedRows.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + num += 1 + } + num + } + } + override def select: SelectBuilder[EmployeeFields, EmployeeRow] = { + SelectBuilderMock(EmployeeFields.structure, delay(map.values.toList), SelectParams.empty) + } + override def selectAll: Stream[ConnectionIO, EmployeeRow] = { + Stream.emits(map.values.toList) + } + override def selectById(id: EmployeeId): ConnectionIO[Option[EmployeeRow]] = { + delay(map.get(id)) + } + override def selectByIds(ids: Array[EmployeeId]): Stream[ConnectionIO, EmployeeRow] = { + Stream.emits(ids.flatMap(map.get).toList) + } + override def selectByIdsTracked(ids: Array[EmployeeId]): ConnectionIO[Map[EmployeeId, EmployeeRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def selectByUniquePersonId(personId: PersonId): ConnectionIO[Option[EmployeeRow]] = { + delay(map.values.find(v => personId == v.personId)) + } + override def update: UpdateBuilder[EmployeeFields, EmployeeRow] = { + UpdateBuilderMock(UpdateParams.empty, EmployeeFields.structure, map) + } + override def update(row: EmployeeRow): ConnectionIO[Boolean] = { + delay { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: EmployeeRow): ConnectionIO[EmployeeRow] = { + delay { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + } + override def upsertBatch(unsaved: List[EmployeeRow]): Stream[ConnectionIO, EmployeeRow] = { + Stream.emits { + unsaved.map { row => + map += (row.id -> row) + row + } + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, EmployeeRow], batchSize: Int = 10000): ConnectionIO[Int] = { + unsaved.compile.toList.map { rows => + var num = 0 + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRow.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRow.scala new file mode 100644 index 0000000000..6f1bb233ff --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRow.scala @@ -0,0 +1,59 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.person.PersonId +import doobie.postgres.Text +import doobie.util.Read +import doobie.util.Write +import doobie.util.meta.Meta +import io.circe.Decoder +import io.circe.Encoder + +/** Table: frontpage.employee + Primary key: id */ +case class EmployeeRow( + /** Default: gen_random_uuid() */ + id: EmployeeId, + /** Points to [[person.PersonRow.id]] */ + personId: PersonId, + salary: Option[BigDecimal] +){ + def toUnsavedRow(id: Defaulted[EmployeeId]): EmployeeRowUnsaved = + EmployeeRowUnsaved(personId, salary, id) + } + +object EmployeeRow { + implicit lazy val decoder: Decoder[EmployeeRow] = Decoder.forProduct3[EmployeeRow, EmployeeId, PersonId, Option[BigDecimal]]("id", "person_id", "salary")(EmployeeRow.apply)(EmployeeId.decoder, PersonId.decoder, Decoder.decodeOption(Decoder.decodeBigDecimal)) + implicit lazy val encoder: Encoder[EmployeeRow] = Encoder.forProduct3[EmployeeRow, EmployeeId, PersonId, Option[BigDecimal]]("id", "person_id", "salary")(x => (x.id, x.personId, x.salary))(EmployeeId.encoder, PersonId.encoder, Encoder.encodeOption(Encoder.encodeBigDecimal)) + implicit lazy val read: Read[EmployeeRow] = new Read.CompositeOfInstances(Array( + new Read.Single(EmployeeId.get).asInstanceOf[Read[Any]], + new Read.Single(PersonId.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(Meta.ScalaBigDecimalMeta.get).asInstanceOf[Read[Any]] + ))(using scala.reflect.ClassTag.Any).map { arr => + EmployeeRow( + id = arr(0).asInstanceOf[EmployeeId], + personId = arr(1).asInstanceOf[PersonId], + salary = arr(2).asInstanceOf[Option[BigDecimal]] + ) + } + implicit lazy val text: Text[EmployeeRow] = Text.instance[EmployeeRow]{ (row, sb) => + EmployeeId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + PersonId.text.unsafeEncode(row.personId, sb) + sb.append(Text.DELIMETER) + Text.option(Text.bigDecimalInstance).unsafeEncode(row.salary, sb) + } + implicit lazy val write: Write[EmployeeRow] = new Write.Composite[EmployeeRow]( + List(new Write.Single(EmployeeId.put), + new Write.Single(PersonId.put), + new Write.Single(Meta.ScalaBigDecimalMeta.put).toOpt), + a => List(a.id, a.personId, a.salary) + ) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRowUnsaved.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRowUnsaved.scala new file mode 100644 index 0000000000..c2706224ca --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRowUnsaved.scala @@ -0,0 +1,44 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.person.PersonId +import doobie.postgres.Text +import io.circe.Decoder +import io.circe.Encoder + +/** This class corresponds to a row in table `frontpage.employee` which has not been persisted yet */ +case class EmployeeRowUnsaved( + /** Points to [[person.PersonRow.id]] */ + personId: PersonId, + salary: Option[BigDecimal], + /** Default: gen_random_uuid() */ + id: Defaulted[EmployeeId] = Defaulted.UseDefault +) { + def toRow(idDefault: => EmployeeId): EmployeeRow = + EmployeeRow( + personId = personId, + salary = salary, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object EmployeeRowUnsaved { + implicit lazy val decoder: Decoder[EmployeeRowUnsaved] = Decoder.forProduct3[EmployeeRowUnsaved, PersonId, Option[BigDecimal], Defaulted[EmployeeId]]("person_id", "salary", "id")(EmployeeRowUnsaved.apply)(PersonId.decoder, Decoder.decodeOption(Decoder.decodeBigDecimal), Defaulted.decoder(EmployeeId.decoder)) + implicit lazy val encoder: Encoder[EmployeeRowUnsaved] = Encoder.forProduct3[EmployeeRowUnsaved, PersonId, Option[BigDecimal], Defaulted[EmployeeId]]("person_id", "salary", "id")(x => (x.personId, x.salary, x.id))(PersonId.encoder, Encoder.encodeOption(Encoder.encodeBigDecimal), Defaulted.encoder(EmployeeId.encoder)) + implicit lazy val text: Text[EmployeeRowUnsaved] = Text.instance[EmployeeRowUnsaved]{ (row, sb) => + PersonId.text.unsafeEncode(row.personId, sb) + sb.append(Text.DELIMETER) + Text.option(Text.bigDecimalInstance).unsafeEncode(row.salary, sb) + sb.append(Text.DELIMETER) + Defaulted.text(EmployeeId.text).unsafeEncode(row.id, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationFields.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationFields.scala new file mode 100644 index 0000000000..c4fb82d377 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationFields.scala @@ -0,0 +1,53 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import adventureworks.customtypes.TypoInet +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoPoint +import adventureworks.customtypes.TypoPolygon +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait LocationFields { + def id: IdField[LocationId, LocationRow] + def name: Field[String, LocationRow] + def position: OptField[TypoPoint, LocationRow] + def area: OptField[TypoPolygon, LocationRow] + def ipRange: OptField[TypoInet, LocationRow] + def metadata: OptField[TypoJsonb, LocationRow] +} + +object LocationFields { + lazy val structure: Relation[LocationFields, LocationRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[LocationFields, LocationRow] { + + override lazy val fields: LocationFields = new LocationFields { + override def id = IdField[LocationId, LocationRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, LocationRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + override def position = OptField[TypoPoint, LocationRow](_path, "position", None, Some("point"), x => x.position, (row, value) => row.copy(position = value)) + override def area = OptField[TypoPolygon, LocationRow](_path, "area", None, Some("polygon"), x => x.area, (row, value) => row.copy(area = value)) + override def ipRange = OptField[TypoInet, LocationRow](_path, "ip_range", None, Some("inet"), x => x.ipRange, (row, value) => row.copy(ipRange = value)) + override def metadata = OptField[TypoJsonb, LocationRow](_path, "metadata", None, Some("jsonb"), x => x.metadata, (row, value) => row.copy(metadata = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, LocationRow]] = + List[FieldLikeNoHkt[?, LocationRow]](fields.id, fields.name, fields.position, fields.area, fields.ipRange, fields.metadata) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationId.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationId.scala new file mode 100644 index 0000000000..64cdfcab27 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationId.scala @@ -0,0 +1,33 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import adventureworks.customtypes.TypoUUID +import doobie.postgres.Text +import doobie.util.Get +import doobie.util.Put +import io.circe.Decoder +import io.circe.Encoder +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.location` */ +case class LocationId(value: TypoUUID) extends AnyVal +object LocationId { + implicit lazy val arrayGet: Get[Array[LocationId]] = TypoUUID.arrayGet.map(_.map(LocationId.apply)) + implicit lazy val arrayPut: Put[Array[LocationId]] = TypoUUID.arrayPut.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[LocationId, TypoUUID] = Bijection[LocationId, TypoUUID](_.value)(LocationId.apply) + implicit lazy val decoder: Decoder[LocationId] = TypoUUID.decoder.map(LocationId.apply) + implicit lazy val encoder: Encoder[LocationId] = TypoUUID.encoder.contramap(_.value) + implicit lazy val get: Get[LocationId] = TypoUUID.get.map(LocationId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[LocationId] = Ordering.by(_.value) + implicit lazy val put: Put[LocationId] = TypoUUID.put.contramap(_.value) + implicit lazy val text: Text[LocationId] = new Text[LocationId] { + override def unsafeEncode(v: LocationId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: LocationId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationRepo.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationRepo.scala new file mode 100644 index 0000000000..d68dba4413 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationRepo.scala @@ -0,0 +1,36 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import doobie.free.connection.ConnectionIO +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait LocationRepo { + def delete: DeleteBuilder[LocationFields, LocationRow] + def deleteById(id: LocationId): ConnectionIO[Boolean] + def deleteByIds(ids: Array[LocationId]): ConnectionIO[Int] + def insert(unsaved: LocationRow): ConnectionIO[LocationRow] + def insert(unsaved: LocationRowUnsaved): ConnectionIO[LocationRow] + def insertStreaming(unsaved: Stream[ConnectionIO, LocationRow], batchSize: Int = 10000): ConnectionIO[Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, LocationRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] + def select: SelectBuilder[LocationFields, LocationRow] + def selectAll: Stream[ConnectionIO, LocationRow] + def selectById(id: LocationId): ConnectionIO[Option[LocationRow]] + def selectByIds(ids: Array[LocationId]): Stream[ConnectionIO, LocationRow] + def selectByIdsTracked(ids: Array[LocationId]): ConnectionIO[Map[LocationId, LocationRow]] + def update: UpdateBuilder[LocationFields, LocationRow] + def update(row: LocationRow): ConnectionIO[Boolean] + def upsert(unsaved: LocationRow): ConnectionIO[LocationRow] + def upsertBatch(unsaved: List[LocationRow]): Stream[ConnectionIO, LocationRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Stream[ConnectionIO, LocationRow], batchSize: Int = 10000): ConnectionIO[Int] +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationRepoImpl.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationRepoImpl.scala new file mode 100644 index 0000000000..c836930fd2 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationRepoImpl.scala @@ -0,0 +1,170 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoInet +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoPoint +import adventureworks.customtypes.TypoPolygon +import cats.instances.list.catsStdInstancesForList +import doobie.free.connection.ConnectionIO +import doobie.postgres.syntax.FragmentOps +import doobie.syntax.SqlInterpolator.SingleFragment.fromWrite +import doobie.syntax.string.toSqlInterpolator +import doobie.util.Write +import doobie.util.fragment.Fragment +import doobie.util.meta.Meta +import doobie.util.update.Update +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class LocationRepoImpl extends LocationRepo { + override def delete: DeleteBuilder[LocationFields, LocationRow] = { + DeleteBuilder(""""frontpage"."location"""", LocationFields.structure) + } + override def deleteById(id: LocationId): ConnectionIO[Boolean] = { + sql"""delete from "frontpage"."location" where "id" = ${fromWrite(id)(new Write.Single(LocationId.put))}""".update.run.map(_ > 0) + } + override def deleteByIds(ids: Array[LocationId]): ConnectionIO[Int] = { + sql"""delete from "frontpage"."location" where "id" = ANY(${ids})""".update.run + } + override def insert(unsaved: LocationRow): ConnectionIO[LocationRow] = { + sql"""insert into "frontpage"."location"("id", "name", "position", "area", "ip_range", "metadata") + values (${fromWrite(unsaved.id)(new Write.Single(LocationId.put))}::uuid, ${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}, ${fromWrite(unsaved.position)(new Write.SingleOpt(TypoPoint.put))}::point, ${fromWrite(unsaved.area)(new Write.SingleOpt(TypoPolygon.put))}::polygon, ${fromWrite(unsaved.ipRange)(new Write.SingleOpt(TypoInet.put))}::inet, ${fromWrite(unsaved.metadata)(new Write.SingleOpt(TypoJsonb.put))}::jsonb) + returning "id", "name", "position", "area", "ip_range", "metadata" + """.query(using LocationRow.read).unique + } + override def insert(unsaved: LocationRowUnsaved): ConnectionIO[LocationRow] = { + val fs = List( + Some((Fragment.const0(s""""name""""), fr"${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}")), + Some((Fragment.const0(s""""position""""), fr"${fromWrite(unsaved.position)(new Write.SingleOpt(TypoPoint.put))}::point")), + Some((Fragment.const0(s""""area""""), fr"${fromWrite(unsaved.area)(new Write.SingleOpt(TypoPolygon.put))}::polygon")), + Some((Fragment.const0(s""""ip_range""""), fr"${fromWrite(unsaved.ipRange)(new Write.SingleOpt(TypoInet.put))}::inet")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""id""""), fr"${fromWrite(value: LocationId)(new Write.Single(LocationId.put))}::uuid")) + }, + unsaved.metadata match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""metadata""""), fr"${fromWrite(value: Option[TypoJsonb])(new Write.SingleOpt(TypoJsonb.put))}::jsonb")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."location" default values + returning "id", "name", "position", "area", "ip_range", "metadata" + """ + } else { + val CommaSeparate = Fragment.FragmentMonoid.intercalate(fr", ") + sql"""insert into "frontpage"."location"(${CommaSeparate.combineAllOption(fs.map { case (n, _) => n }).get}) + values (${CommaSeparate.combineAllOption(fs.map { case (_, f) => f }).get}) + returning "id", "name", "position", "area", "ip_range", "metadata" + """ + } + q.query(using LocationRow.read).unique + + } + override def insertStreaming(unsaved: Stream[ConnectionIO, LocationRow], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."location"("id", "name", "position", "area", "ip_range", "metadata") FROM STDIN""").copyIn(unsaved, batchSize)(using LocationRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, LocationRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."location"("name", "position", "area", "ip_range", "id", "metadata") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""").copyIn(unsaved, batchSize)(using LocationRowUnsaved.text) + } + override def select: SelectBuilder[LocationFields, LocationRow] = { + SelectBuilderSql(""""frontpage"."location"""", LocationFields.structure, LocationRow.read) + } + override def selectAll: Stream[ConnectionIO, LocationRow] = { + sql"""select "id", "name", "position", "area", "ip_range", "metadata" from "frontpage"."location"""".query(using LocationRow.read).stream + } + override def selectById(id: LocationId): ConnectionIO[Option[LocationRow]] = { + sql"""select "id", "name", "position", "area", "ip_range", "metadata" from "frontpage"."location" where "id" = ${fromWrite(id)(new Write.Single(LocationId.put))}""".query(using LocationRow.read).option + } + override def selectByIds(ids: Array[LocationId]): Stream[ConnectionIO, LocationRow] = { + sql"""select "id", "name", "position", "area", "ip_range", "metadata" from "frontpage"."location" where "id" = ANY(${ids})""".query(using LocationRow.read).stream + } + override def selectByIdsTracked(ids: Array[LocationId]): ConnectionIO[Map[LocationId, LocationRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[LocationFields, LocationRow] = { + UpdateBuilder(""""frontpage"."location"""", LocationFields.structure, LocationRow.read) + } + override def update(row: LocationRow): ConnectionIO[Boolean] = { + val id = row.id + sql"""update "frontpage"."location" + set "name" = ${fromWrite(row.name)(new Write.Single(Meta.StringMeta.put))}, + "position" = ${fromWrite(row.position)(new Write.SingleOpt(TypoPoint.put))}::point, + "area" = ${fromWrite(row.area)(new Write.SingleOpt(TypoPolygon.put))}::polygon, + "ip_range" = ${fromWrite(row.ipRange)(new Write.SingleOpt(TypoInet.put))}::inet, + "metadata" = ${fromWrite(row.metadata)(new Write.SingleOpt(TypoJsonb.put))}::jsonb + where "id" = ${fromWrite(id)(new Write.Single(LocationId.put))}""" + .update + .run + .map(_ > 0) + } + override def upsert(unsaved: LocationRow): ConnectionIO[LocationRow] = { + sql"""insert into "frontpage"."location"("id", "name", "position", "area", "ip_range", "metadata") + values ( + ${fromWrite(unsaved.id)(new Write.Single(LocationId.put))}::uuid, + ${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}, + ${fromWrite(unsaved.position)(new Write.SingleOpt(TypoPoint.put))}::point, + ${fromWrite(unsaved.area)(new Write.SingleOpt(TypoPolygon.put))}::polygon, + ${fromWrite(unsaved.ipRange)(new Write.SingleOpt(TypoInet.put))}::inet, + ${fromWrite(unsaved.metadata)(new Write.SingleOpt(TypoJsonb.put))}::jsonb + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "position" = EXCLUDED."position", + "area" = EXCLUDED."area", + "ip_range" = EXCLUDED."ip_range", + "metadata" = EXCLUDED."metadata" + returning "id", "name", "position", "area", "ip_range", "metadata" + """.query(using LocationRow.read).unique + } + override def upsertBatch(unsaved: List[LocationRow]): Stream[ConnectionIO, LocationRow] = { + Update[LocationRow]( + s"""insert into "frontpage"."location"("id", "name", "position", "area", "ip_range", "metadata") + values (?::uuid,?,?::point,?::polygon,?::inet,?::jsonb) + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "position" = EXCLUDED."position", + "area" = EXCLUDED."area", + "ip_range" = EXCLUDED."ip_range", + "metadata" = EXCLUDED."metadata" + returning "id", "name", "position", "area", "ip_range", "metadata"""" + )(using LocationRow.write) + .updateManyWithGeneratedKeys[LocationRow]("id", "name", "position", "area", "ip_range", "metadata")(unsaved)(using catsStdInstancesForList, LocationRow.read) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, LocationRow], batchSize: Int = 10000): ConnectionIO[Int] = { + for { + _ <- sql"""create temporary table location_TEMP (like "frontpage"."location") on commit drop""".update.run + _ <- new FragmentOps(sql"""copy location_TEMP("id", "name", "position", "area", "ip_range", "metadata") from stdin""").copyIn(unsaved, batchSize)(using LocationRow.text) + res <- sql"""insert into "frontpage"."location"("id", "name", "position", "area", "ip_range", "metadata") + select * from location_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "position" = EXCLUDED."position", + "area" = EXCLUDED."area", + "ip_range" = EXCLUDED."ip_range", + "metadata" = EXCLUDED."metadata" + ; + drop table location_TEMP;""".update.run + } yield res + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationRepoMock.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationRepoMock.scala new file mode 100644 index 0000000000..688cf54db4 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationRepoMock.scala @@ -0,0 +1,127 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import doobie.free.connection.ConnectionIO +import doobie.free.connection.delay +import fs2.Stream +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class LocationRepoMock(toRow: Function1[LocationRowUnsaved, LocationRow], + map: scala.collection.mutable.Map[LocationId, LocationRow] = scala.collection.mutable.Map.empty) extends LocationRepo { + override def delete: DeleteBuilder[LocationFields, LocationRow] = { + DeleteBuilderMock(DeleteParams.empty, LocationFields.structure, map) + } + override def deleteById(id: LocationId): ConnectionIO[Boolean] = { + delay(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[LocationId]): ConnectionIO[Int] = { + delay(ids.map(id => map.remove(id)).count(_.isDefined)) + } + override def insert(unsaved: LocationRow): ConnectionIO[LocationRow] = { + delay { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: LocationRowUnsaved): ConnectionIO[LocationRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Stream[ConnectionIO, LocationRow], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { rows => + var num = 0L + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, LocationRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { unsavedRows => + var num = 0L + unsavedRows.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + num += 1 + } + num + } + } + override def select: SelectBuilder[LocationFields, LocationRow] = { + SelectBuilderMock(LocationFields.structure, delay(map.values.toList), SelectParams.empty) + } + override def selectAll: Stream[ConnectionIO, LocationRow] = { + Stream.emits(map.values.toList) + } + override def selectById(id: LocationId): ConnectionIO[Option[LocationRow]] = { + delay(map.get(id)) + } + override def selectByIds(ids: Array[LocationId]): Stream[ConnectionIO, LocationRow] = { + Stream.emits(ids.flatMap(map.get).toList) + } + override def selectByIdsTracked(ids: Array[LocationId]): ConnectionIO[Map[LocationId, LocationRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[LocationFields, LocationRow] = { + UpdateBuilderMock(UpdateParams.empty, LocationFields.structure, map) + } + override def update(row: LocationRow): ConnectionIO[Boolean] = { + delay { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: LocationRow): ConnectionIO[LocationRow] = { + delay { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + } + override def upsertBatch(unsaved: List[LocationRow]): Stream[ConnectionIO, LocationRow] = { + Stream.emits { + unsaved.map { row => + map += (row.id -> row) + row + } + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, LocationRow], batchSize: Int = 10000): ConnectionIO[Int] = { + unsaved.compile.toList.map { rows => + var num = 0 + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationRow.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationRow.scala new file mode 100644 index 0000000000..fb2703eda5 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationRow.scala @@ -0,0 +1,80 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoInet +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoPoint +import adventureworks.customtypes.TypoPolygon +import doobie.postgres.Text +import doobie.util.Read +import doobie.util.Write +import doobie.util.meta.Meta +import io.circe.Decoder +import io.circe.Encoder + +/** Table: frontpage.location + Primary key: id */ +case class LocationRow( + /** Default: gen_random_uuid() */ + id: LocationId, + name: String, + position: Option[TypoPoint], + area: Option[TypoPolygon], + ipRange: Option[TypoInet], + /** Default: '{}'::jsonb */ + metadata: Option[TypoJsonb] +){ + def toUnsavedRow(id: Defaulted[LocationId], metadata: Defaulted[Option[TypoJsonb]] = Defaulted.Provided(this.metadata)): LocationRowUnsaved = + LocationRowUnsaved(name, position, area, ipRange, id, metadata) + } + +object LocationRow { + implicit lazy val decoder: Decoder[LocationRow] = Decoder.forProduct6[LocationRow, LocationId, String, Option[TypoPoint], Option[TypoPolygon], Option[TypoInet], Option[TypoJsonb]]("id", "name", "position", "area", "ip_range", "metadata")(LocationRow.apply)(LocationId.decoder, Decoder.decodeString, Decoder.decodeOption(TypoPoint.decoder), Decoder.decodeOption(TypoPolygon.decoder), Decoder.decodeOption(TypoInet.decoder), Decoder.decodeOption(TypoJsonb.decoder)) + implicit lazy val encoder: Encoder[LocationRow] = Encoder.forProduct6[LocationRow, LocationId, String, Option[TypoPoint], Option[TypoPolygon], Option[TypoInet], Option[TypoJsonb]]("id", "name", "position", "area", "ip_range", "metadata")(x => (x.id, x.name, x.position, x.area, x.ipRange, x.metadata))(LocationId.encoder, Encoder.encodeString, Encoder.encodeOption(TypoPoint.encoder), Encoder.encodeOption(TypoPolygon.encoder), Encoder.encodeOption(TypoInet.encoder), Encoder.encodeOption(TypoJsonb.encoder)) + implicit lazy val read: Read[LocationRow] = new Read.CompositeOfInstances(Array( + new Read.Single(LocationId.get).asInstanceOf[Read[Any]], + new Read.Single(Meta.StringMeta.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(TypoPoint.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(TypoPolygon.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(TypoInet.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(TypoJsonb.get).asInstanceOf[Read[Any]] + ))(using scala.reflect.ClassTag.Any).map { arr => + LocationRow( + id = arr(0).asInstanceOf[LocationId], + name = arr(1).asInstanceOf[String], + position = arr(2).asInstanceOf[Option[TypoPoint]], + area = arr(3).asInstanceOf[Option[TypoPolygon]], + ipRange = arr(4).asInstanceOf[Option[TypoInet]], + metadata = arr(5).asInstanceOf[Option[TypoJsonb]] + ) + } + implicit lazy val text: Text[LocationRow] = Text.instance[LocationRow]{ (row, sb) => + LocationId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(TypoPoint.text).unsafeEncode(row.position, sb) + sb.append(Text.DELIMETER) + Text.option(TypoPolygon.text).unsafeEncode(row.area, sb) + sb.append(Text.DELIMETER) + Text.option(TypoInet.text).unsafeEncode(row.ipRange, sb) + sb.append(Text.DELIMETER) + Text.option(TypoJsonb.text).unsafeEncode(row.metadata, sb) + } + implicit lazy val write: Write[LocationRow] = new Write.Composite[LocationRow]( + List(new Write.Single(LocationId.put), + new Write.Single(Meta.StringMeta.put), + new Write.Single(TypoPoint.put).toOpt, + new Write.Single(TypoPolygon.put).toOpt, + new Write.Single(TypoInet.put).toOpt, + new Write.Single(TypoJsonb.put).toOpt), + a => List(a.id, a.name, a.position, a.area, a.ipRange, a.metadata) + ) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationRowUnsaved.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationRowUnsaved.scala new file mode 100644 index 0000000000..ac432c84ee --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/location/LocationRowUnsaved.scala @@ -0,0 +1,62 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoInet +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoPoint +import adventureworks.customtypes.TypoPolygon +import doobie.postgres.Text +import io.circe.Decoder +import io.circe.Encoder + +/** This class corresponds to a row in table `frontpage.location` which has not been persisted yet */ +case class LocationRowUnsaved( + name: String, + position: Option[TypoPoint], + area: Option[TypoPolygon], + ipRange: Option[TypoInet], + /** Default: gen_random_uuid() */ + id: Defaulted[LocationId] = Defaulted.UseDefault, + /** Default: '{}'::jsonb */ + metadata: Defaulted[Option[TypoJsonb]] = Defaulted.UseDefault +) { + def toRow(idDefault: => LocationId, metadataDefault: => Option[TypoJsonb]): LocationRow = + LocationRow( + name = name, + position = position, + area = area, + ipRange = ipRange, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + }, + metadata = metadata match { + case Defaulted.UseDefault => metadataDefault + case Defaulted.Provided(value) => value + } + ) +} +object LocationRowUnsaved { + implicit lazy val decoder: Decoder[LocationRowUnsaved] = Decoder.forProduct6[LocationRowUnsaved, String, Option[TypoPoint], Option[TypoPolygon], Option[TypoInet], Defaulted[LocationId], Defaulted[Option[TypoJsonb]]]("name", "position", "area", "ip_range", "id", "metadata")(LocationRowUnsaved.apply)(Decoder.decodeString, Decoder.decodeOption(TypoPoint.decoder), Decoder.decodeOption(TypoPolygon.decoder), Decoder.decodeOption(TypoInet.decoder), Defaulted.decoder(LocationId.decoder), Defaulted.decoder(Decoder.decodeOption(TypoJsonb.decoder))) + implicit lazy val encoder: Encoder[LocationRowUnsaved] = Encoder.forProduct6[LocationRowUnsaved, String, Option[TypoPoint], Option[TypoPolygon], Option[TypoInet], Defaulted[LocationId], Defaulted[Option[TypoJsonb]]]("name", "position", "area", "ip_range", "id", "metadata")(x => (x.name, x.position, x.area, x.ipRange, x.id, x.metadata))(Encoder.encodeString, Encoder.encodeOption(TypoPoint.encoder), Encoder.encodeOption(TypoPolygon.encoder), Encoder.encodeOption(TypoInet.encoder), Defaulted.encoder(LocationId.encoder), Defaulted.encoder(Encoder.encodeOption(TypoJsonb.encoder))) + implicit lazy val text: Text[LocationRowUnsaved] = Text.instance[LocationRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(TypoPoint.text).unsafeEncode(row.position, sb) + sb.append(Text.DELIMETER) + Text.option(TypoPolygon.text).unsafeEncode(row.area, sb) + sb.append(Text.DELIMETER) + Text.option(TypoInet.text).unsafeEncode(row.ipRange, sb) + sb.append(Text.DELIMETER) + Defaulted.text(LocationId.text).unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(TypoJsonb.text)).unsafeEncode(row.metadata, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderFields.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderFields.scala new file mode 100644 index 0000000000..ba6d63651e --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderFields.scala @@ -0,0 +1,65 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.product.ProductFields +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.product.ProductRow +import adventureworks.frontpage.user.UserFields +import adventureworks.frontpage.user.UserId +import adventureworks.frontpage.user.UserRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait OrderFields { + def id: IdField[OrderId, OrderRow] + def userId: OptField[UserId, OrderRow] + def productId: OptField[ProductId, OrderRow] + def status: OptField[OrderStatus, OrderRow] + def total: Field[BigDecimal, OrderRow] + def createdAt: OptField[TypoLocalDateTime, OrderRow] + def shippedAt: OptField[TypoLocalDateTime, OrderRow] + def fkProduct: ForeignKey[ProductFields, ProductRow] = + ForeignKey[ProductFields, ProductRow]("frontpage.order_product_id_fkey", Nil) + .withColumnPair(productId, _.id) + def fkUser: ForeignKey[UserFields, UserRow] = + ForeignKey[UserFields, UserRow]("frontpage.order_user_id_fkey", Nil) + .withColumnPair(userId, _.id) +} + +object OrderFields { + lazy val structure: Relation[OrderFields, OrderRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[OrderFields, OrderRow] { + + override lazy val fields: OrderFields = new OrderFields { + override def id = IdField[OrderId, OrderRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def userId = OptField[UserId, OrderRow](_path, "user_id", None, Some("uuid"), x => x.userId, (row, value) => row.copy(userId = value)) + override def productId = OptField[ProductId, OrderRow](_path, "product_id", None, Some("uuid"), x => x.productId, (row, value) => row.copy(productId = value)) + override def status = OptField[OrderStatus, OrderRow](_path, "status", None, Some("frontpage.order_status"), x => x.status, (row, value) => row.copy(status = value)) + override def total = Field[BigDecimal, OrderRow](_path, "total", None, Some("numeric"), x => x.total, (row, value) => row.copy(total = value)) + override def createdAt = OptField[TypoLocalDateTime, OrderRow](_path, "created_at", Some("text"), Some("timestamp"), x => x.createdAt, (row, value) => row.copy(createdAt = value)) + override def shippedAt = OptField[TypoLocalDateTime, OrderRow](_path, "shipped_at", Some("text"), Some("timestamp"), x => x.shippedAt, (row, value) => row.copy(shippedAt = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, OrderRow]] = + List[FieldLikeNoHkt[?, OrderRow]](fields.id, fields.userId, fields.productId, fields.status, fields.total, fields.createdAt, fields.shippedAt) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderId.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderId.scala new file mode 100644 index 0000000000..0bd7485c9c --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderId.scala @@ -0,0 +1,33 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import adventureworks.customtypes.TypoUUID +import doobie.postgres.Text +import doobie.util.Get +import doobie.util.Put +import io.circe.Decoder +import io.circe.Encoder +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.order` */ +case class OrderId(value: TypoUUID) extends AnyVal +object OrderId { + implicit lazy val arrayGet: Get[Array[OrderId]] = TypoUUID.arrayGet.map(_.map(OrderId.apply)) + implicit lazy val arrayPut: Put[Array[OrderId]] = TypoUUID.arrayPut.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[OrderId, TypoUUID] = Bijection[OrderId, TypoUUID](_.value)(OrderId.apply) + implicit lazy val decoder: Decoder[OrderId] = TypoUUID.decoder.map(OrderId.apply) + implicit lazy val encoder: Encoder[OrderId] = TypoUUID.encoder.contramap(_.value) + implicit lazy val get: Get[OrderId] = TypoUUID.get.map(OrderId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[OrderId] = Ordering.by(_.value) + implicit lazy val put: Put[OrderId] = TypoUUID.put.contramap(_.value) + implicit lazy val text: Text[OrderId] = new Text[OrderId] { + override def unsafeEncode(v: OrderId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: OrderId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderRepo.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderRepo.scala new file mode 100644 index 0000000000..f0e1258f73 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderRepo.scala @@ -0,0 +1,36 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import doobie.free.connection.ConnectionIO +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait OrderRepo { + def delete: DeleteBuilder[OrderFields, OrderRow] + def deleteById(id: OrderId): ConnectionIO[Boolean] + def deleteByIds(ids: Array[OrderId]): ConnectionIO[Int] + def insert(unsaved: OrderRow): ConnectionIO[OrderRow] + def insert(unsaved: OrderRowUnsaved): ConnectionIO[OrderRow] + def insertStreaming(unsaved: Stream[ConnectionIO, OrderRow], batchSize: Int = 10000): ConnectionIO[Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, OrderRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] + def select: SelectBuilder[OrderFields, OrderRow] + def selectAll: Stream[ConnectionIO, OrderRow] + def selectById(id: OrderId): ConnectionIO[Option[OrderRow]] + def selectByIds(ids: Array[OrderId]): Stream[ConnectionIO, OrderRow] + def selectByIdsTracked(ids: Array[OrderId]): ConnectionIO[Map[OrderId, OrderRow]] + def update: UpdateBuilder[OrderFields, OrderRow] + def update(row: OrderRow): ConnectionIO[Boolean] + def upsert(unsaved: OrderRow): ConnectionIO[OrderRow] + def upsertBatch(unsaved: List[OrderRow]): Stream[ConnectionIO, OrderRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Stream[ConnectionIO, OrderRow], batchSize: Int = 10000): ConnectionIO[Int] +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderRepoImpl.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderRepoImpl.scala new file mode 100644 index 0000000000..34c959472c --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderRepoImpl.scala @@ -0,0 +1,178 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.user.UserId +import cats.instances.list.catsStdInstancesForList +import doobie.free.connection.ConnectionIO +import doobie.postgres.syntax.FragmentOps +import doobie.syntax.SqlInterpolator.SingleFragment.fromWrite +import doobie.syntax.string.toSqlInterpolator +import doobie.util.Write +import doobie.util.fragment.Fragment +import doobie.util.meta.Meta +import doobie.util.update.Update +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class OrderRepoImpl extends OrderRepo { + override def delete: DeleteBuilder[OrderFields, OrderRow] = { + DeleteBuilder(""""frontpage"."order"""", OrderFields.structure) + } + override def deleteById(id: OrderId): ConnectionIO[Boolean] = { + sql"""delete from "frontpage"."order" where "id" = ${fromWrite(id)(new Write.Single(OrderId.put))}""".update.run.map(_ > 0) + } + override def deleteByIds(ids: Array[OrderId]): ConnectionIO[Int] = { + sql"""delete from "frontpage"."order" where "id" = ANY(${ids})""".update.run + } + override def insert(unsaved: OrderRow): ConnectionIO[OrderRow] = { + sql"""insert into "frontpage"."order"("id", "user_id", "product_id", "status", "total", "created_at", "shipped_at") + values (${fromWrite(unsaved.id)(new Write.Single(OrderId.put))}::uuid, ${fromWrite(unsaved.userId)(new Write.SingleOpt(UserId.put))}::uuid, ${fromWrite(unsaved.productId)(new Write.SingleOpt(ProductId.put))}::uuid, ${fromWrite(unsaved.status)(new Write.SingleOpt(OrderStatus.put))}::frontpage.order_status, ${fromWrite(unsaved.total)(new Write.Single(Meta.ScalaBigDecimalMeta.put))}::numeric, ${fromWrite(unsaved.createdAt)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp, ${fromWrite(unsaved.shippedAt)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp) + returning "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text + """.query(using OrderRow.read).unique + } + override def insert(unsaved: OrderRowUnsaved): ConnectionIO[OrderRow] = { + val fs = List( + Some((Fragment.const0(s""""user_id""""), fr"${fromWrite(unsaved.userId)(new Write.SingleOpt(UserId.put))}::uuid")), + Some((Fragment.const0(s""""product_id""""), fr"${fromWrite(unsaved.productId)(new Write.SingleOpt(ProductId.put))}::uuid")), + Some((Fragment.const0(s""""total""""), fr"${fromWrite(unsaved.total)(new Write.Single(Meta.ScalaBigDecimalMeta.put))}::numeric")), + Some((Fragment.const0(s""""shipped_at""""), fr"${fromWrite(unsaved.shippedAt)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""id""""), fr"${fromWrite(value: OrderId)(new Write.Single(OrderId.put))}::uuid")) + }, + unsaved.status match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""status""""), fr"${fromWrite(value: Option[OrderStatus])(new Write.SingleOpt(OrderStatus.put))}::frontpage.order_status")) + }, + unsaved.createdAt match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""created_at""""), fr"${fromWrite(value: Option[TypoLocalDateTime])(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."order" default values + returning "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text + """ + } else { + val CommaSeparate = Fragment.FragmentMonoid.intercalate(fr", ") + sql"""insert into "frontpage"."order"(${CommaSeparate.combineAllOption(fs.map { case (n, _) => n }).get}) + values (${CommaSeparate.combineAllOption(fs.map { case (_, f) => f }).get}) + returning "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text + """ + } + q.query(using OrderRow.read).unique + + } + override def insertStreaming(unsaved: Stream[ConnectionIO, OrderRow], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."order"("id", "user_id", "product_id", "status", "total", "created_at", "shipped_at") FROM STDIN""").copyIn(unsaved, batchSize)(using OrderRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, OrderRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."order"("user_id", "product_id", "total", "shipped_at", "id", "status", "created_at") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""").copyIn(unsaved, batchSize)(using OrderRowUnsaved.text) + } + override def select: SelectBuilder[OrderFields, OrderRow] = { + SelectBuilderSql(""""frontpage"."order"""", OrderFields.structure, OrderRow.read) + } + override def selectAll: Stream[ConnectionIO, OrderRow] = { + sql"""select "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text from "frontpage"."order"""".query(using OrderRow.read).stream + } + override def selectById(id: OrderId): ConnectionIO[Option[OrderRow]] = { + sql"""select "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text from "frontpage"."order" where "id" = ${fromWrite(id)(new Write.Single(OrderId.put))}""".query(using OrderRow.read).option + } + override def selectByIds(ids: Array[OrderId]): Stream[ConnectionIO, OrderRow] = { + sql"""select "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text from "frontpage"."order" where "id" = ANY(${ids})""".query(using OrderRow.read).stream + } + override def selectByIdsTracked(ids: Array[OrderId]): ConnectionIO[Map[OrderId, OrderRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[OrderFields, OrderRow] = { + UpdateBuilder(""""frontpage"."order"""", OrderFields.structure, OrderRow.read) + } + override def update(row: OrderRow): ConnectionIO[Boolean] = { + val id = row.id + sql"""update "frontpage"."order" + set "user_id" = ${fromWrite(row.userId)(new Write.SingleOpt(UserId.put))}::uuid, + "product_id" = ${fromWrite(row.productId)(new Write.SingleOpt(ProductId.put))}::uuid, + "status" = ${fromWrite(row.status)(new Write.SingleOpt(OrderStatus.put))}::frontpage.order_status, + "total" = ${fromWrite(row.total)(new Write.Single(Meta.ScalaBigDecimalMeta.put))}::numeric, + "created_at" = ${fromWrite(row.createdAt)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp, + "shipped_at" = ${fromWrite(row.shippedAt)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp + where "id" = ${fromWrite(id)(new Write.Single(OrderId.put))}""" + .update + .run + .map(_ > 0) + } + override def upsert(unsaved: OrderRow): ConnectionIO[OrderRow] = { + sql"""insert into "frontpage"."order"("id", "user_id", "product_id", "status", "total", "created_at", "shipped_at") + values ( + ${fromWrite(unsaved.id)(new Write.Single(OrderId.put))}::uuid, + ${fromWrite(unsaved.userId)(new Write.SingleOpt(UserId.put))}::uuid, + ${fromWrite(unsaved.productId)(new Write.SingleOpt(ProductId.put))}::uuid, + ${fromWrite(unsaved.status)(new Write.SingleOpt(OrderStatus.put))}::frontpage.order_status, + ${fromWrite(unsaved.total)(new Write.Single(Meta.ScalaBigDecimalMeta.put))}::numeric, + ${fromWrite(unsaved.createdAt)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp, + ${fromWrite(unsaved.shippedAt)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp + ) + on conflict ("id") + do update set + "user_id" = EXCLUDED."user_id", + "product_id" = EXCLUDED."product_id", + "status" = EXCLUDED."status", + "total" = EXCLUDED."total", + "created_at" = EXCLUDED."created_at", + "shipped_at" = EXCLUDED."shipped_at" + returning "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text + """.query(using OrderRow.read).unique + } + override def upsertBatch(unsaved: List[OrderRow]): Stream[ConnectionIO, OrderRow] = { + Update[OrderRow]( + s"""insert into "frontpage"."order"("id", "user_id", "product_id", "status", "total", "created_at", "shipped_at") + values (?::uuid,?::uuid,?::uuid,?::frontpage.order_status,?::numeric,?::timestamp,?::timestamp) + on conflict ("id") + do update set + "user_id" = EXCLUDED."user_id", + "product_id" = EXCLUDED."product_id", + "status" = EXCLUDED."status", + "total" = EXCLUDED."total", + "created_at" = EXCLUDED."created_at", + "shipped_at" = EXCLUDED."shipped_at" + returning "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text""" + )(using OrderRow.write) + .updateManyWithGeneratedKeys[OrderRow]("id", "user_id", "product_id", "status", "total", "created_at", "shipped_at")(unsaved)(using catsStdInstancesForList, OrderRow.read) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, OrderRow], batchSize: Int = 10000): ConnectionIO[Int] = { + for { + _ <- sql"""create temporary table order_TEMP (like "frontpage"."order") on commit drop""".update.run + _ <- new FragmentOps(sql"""copy order_TEMP("id", "user_id", "product_id", "status", "total", "created_at", "shipped_at") from stdin""").copyIn(unsaved, batchSize)(using OrderRow.text) + res <- sql"""insert into "frontpage"."order"("id", "user_id", "product_id", "status", "total", "created_at", "shipped_at") + select * from order_TEMP + on conflict ("id") + do update set + "user_id" = EXCLUDED."user_id", + "product_id" = EXCLUDED."product_id", + "status" = EXCLUDED."status", + "total" = EXCLUDED."total", + "created_at" = EXCLUDED."created_at", + "shipped_at" = EXCLUDED."shipped_at" + ; + drop table order_TEMP;""".update.run + } yield res + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderRepoMock.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderRepoMock.scala new file mode 100644 index 0000000000..7435f49ea5 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderRepoMock.scala @@ -0,0 +1,127 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import doobie.free.connection.ConnectionIO +import doobie.free.connection.delay +import fs2.Stream +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class OrderRepoMock(toRow: Function1[OrderRowUnsaved, OrderRow], + map: scala.collection.mutable.Map[OrderId, OrderRow] = scala.collection.mutable.Map.empty) extends OrderRepo { + override def delete: DeleteBuilder[OrderFields, OrderRow] = { + DeleteBuilderMock(DeleteParams.empty, OrderFields.structure, map) + } + override def deleteById(id: OrderId): ConnectionIO[Boolean] = { + delay(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[OrderId]): ConnectionIO[Int] = { + delay(ids.map(id => map.remove(id)).count(_.isDefined)) + } + override def insert(unsaved: OrderRow): ConnectionIO[OrderRow] = { + delay { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: OrderRowUnsaved): ConnectionIO[OrderRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Stream[ConnectionIO, OrderRow], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { rows => + var num = 0L + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, OrderRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { unsavedRows => + var num = 0L + unsavedRows.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + num += 1 + } + num + } + } + override def select: SelectBuilder[OrderFields, OrderRow] = { + SelectBuilderMock(OrderFields.structure, delay(map.values.toList), SelectParams.empty) + } + override def selectAll: Stream[ConnectionIO, OrderRow] = { + Stream.emits(map.values.toList) + } + override def selectById(id: OrderId): ConnectionIO[Option[OrderRow]] = { + delay(map.get(id)) + } + override def selectByIds(ids: Array[OrderId]): Stream[ConnectionIO, OrderRow] = { + Stream.emits(ids.flatMap(map.get).toList) + } + override def selectByIdsTracked(ids: Array[OrderId]): ConnectionIO[Map[OrderId, OrderRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[OrderFields, OrderRow] = { + UpdateBuilderMock(UpdateParams.empty, OrderFields.structure, map) + } + override def update(row: OrderRow): ConnectionIO[Boolean] = { + delay { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: OrderRow): ConnectionIO[OrderRow] = { + delay { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + } + override def upsertBatch(unsaved: List[OrderRow]): Stream[ConnectionIO, OrderRow] = { + Stream.emits { + unsaved.map { row => + map += (row.id -> row) + row + } + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, OrderRow], batchSize: Int = 10000): ConnectionIO[Int] = { + unsaved.compile.toList.map { rows => + var num = 0 + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderRow.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderRow.scala new file mode 100644 index 0000000000..48908af9df --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderRow.scala @@ -0,0 +1,88 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.user.UserId +import doobie.postgres.Text +import doobie.util.Read +import doobie.util.Write +import doobie.util.meta.Meta +import io.circe.Decoder +import io.circe.Encoder + +/** Table: frontpage.order + Primary key: id */ +case class OrderRow( + /** Default: gen_random_uuid() */ + id: OrderId, + /** Points to [[user.UserRow.id]] */ + userId: Option[UserId], + /** Points to [[product.ProductRow.id]] */ + productId: Option[ProductId], + /** Default: 'pending'::frontpage.order_status */ + status: Option[OrderStatus], + total: BigDecimal, + /** Default: now() */ + createdAt: Option[TypoLocalDateTime], + shippedAt: Option[TypoLocalDateTime] +){ + def toUnsavedRow(id: Defaulted[OrderId], status: Defaulted[Option[OrderStatus]] = Defaulted.Provided(this.status), createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.Provided(this.createdAt)): OrderRowUnsaved = + OrderRowUnsaved(userId, productId, total, shippedAt, id, status, createdAt) + } + +object OrderRow { + implicit lazy val decoder: Decoder[OrderRow] = Decoder.forProduct7[OrderRow, OrderId, Option[UserId], Option[ProductId], Option[OrderStatus], BigDecimal, Option[TypoLocalDateTime], Option[TypoLocalDateTime]]("id", "user_id", "product_id", "status", "total", "created_at", "shipped_at")(OrderRow.apply)(OrderId.decoder, Decoder.decodeOption(UserId.decoder), Decoder.decodeOption(ProductId.decoder), Decoder.decodeOption(OrderStatus.decoder), Decoder.decodeBigDecimal, Decoder.decodeOption(TypoLocalDateTime.decoder), Decoder.decodeOption(TypoLocalDateTime.decoder)) + implicit lazy val encoder: Encoder[OrderRow] = Encoder.forProduct7[OrderRow, OrderId, Option[UserId], Option[ProductId], Option[OrderStatus], BigDecimal, Option[TypoLocalDateTime], Option[TypoLocalDateTime]]("id", "user_id", "product_id", "status", "total", "created_at", "shipped_at")(x => (x.id, x.userId, x.productId, x.status, x.total, x.createdAt, x.shippedAt))(OrderId.encoder, Encoder.encodeOption(UserId.encoder), Encoder.encodeOption(ProductId.encoder), Encoder.encodeOption(OrderStatus.encoder), Encoder.encodeBigDecimal, Encoder.encodeOption(TypoLocalDateTime.encoder), Encoder.encodeOption(TypoLocalDateTime.encoder)) + implicit lazy val read: Read[OrderRow] = new Read.CompositeOfInstances(Array( + new Read.Single(OrderId.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(UserId.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(ProductId.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(OrderStatus.get).asInstanceOf[Read[Any]], + new Read.Single(Meta.ScalaBigDecimalMeta.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(TypoLocalDateTime.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(TypoLocalDateTime.get).asInstanceOf[Read[Any]] + ))(using scala.reflect.ClassTag.Any).map { arr => + OrderRow( + id = arr(0).asInstanceOf[OrderId], + userId = arr(1).asInstanceOf[Option[UserId]], + productId = arr(2).asInstanceOf[Option[ProductId]], + status = arr(3).asInstanceOf[Option[OrderStatus]], + total = arr(4).asInstanceOf[BigDecimal], + createdAt = arr(5).asInstanceOf[Option[TypoLocalDateTime]], + shippedAt = arr(6).asInstanceOf[Option[TypoLocalDateTime]] + ) + } + implicit lazy val text: Text[OrderRow] = Text.instance[OrderRow]{ (row, sb) => + OrderId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.option(UserId.text).unsafeEncode(row.userId, sb) + sb.append(Text.DELIMETER) + Text.option(ProductId.text).unsafeEncode(row.productId, sb) + sb.append(Text.DELIMETER) + Text.option(OrderStatus.text).unsafeEncode(row.status, sb) + sb.append(Text.DELIMETER) + Text.bigDecimalInstance.unsafeEncode(row.total, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.createdAt, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.shippedAt, sb) + } + implicit lazy val write: Write[OrderRow] = new Write.Composite[OrderRow]( + List(new Write.Single(OrderId.put), + new Write.Single(UserId.put).toOpt, + new Write.Single(ProductId.put).toOpt, + new Write.Single(OrderStatus.put).toOpt, + new Write.Single(Meta.ScalaBigDecimalMeta.put), + new Write.Single(TypoLocalDateTime.put).toOpt, + new Write.Single(TypoLocalDateTime.put).toOpt), + a => List(a.id, a.userId, a.productId, a.status, a.total, a.createdAt, a.shippedAt) + ) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderRowUnsaved.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderRowUnsaved.scala new file mode 100644 index 0000000000..e4a33aaad4 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order/OrderRowUnsaved.scala @@ -0,0 +1,71 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.user.UserId +import doobie.postgres.Text +import io.circe.Decoder +import io.circe.Encoder + +/** This class corresponds to a row in table `frontpage.order` which has not been persisted yet */ +case class OrderRowUnsaved( + /** Points to [[user.UserRow.id]] */ + userId: Option[UserId], + /** Points to [[product.ProductRow.id]] */ + productId: Option[ProductId], + total: BigDecimal, + shippedAt: Option[TypoLocalDateTime], + /** Default: gen_random_uuid() */ + id: Defaulted[OrderId] = Defaulted.UseDefault, + /** Default: 'pending'::frontpage.order_status */ + status: Defaulted[Option[OrderStatus]] = Defaulted.UseDefault, + /** Default: now() */ + createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault +) { + def toRow(idDefault: => OrderId, statusDefault: => Option[OrderStatus], createdAtDefault: => Option[TypoLocalDateTime]): OrderRow = + OrderRow( + userId = userId, + productId = productId, + total = total, + shippedAt = shippedAt, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + }, + status = status match { + case Defaulted.UseDefault => statusDefault + case Defaulted.Provided(value) => value + }, + createdAt = createdAt match { + case Defaulted.UseDefault => createdAtDefault + case Defaulted.Provided(value) => value + } + ) +} +object OrderRowUnsaved { + implicit lazy val decoder: Decoder[OrderRowUnsaved] = Decoder.forProduct7[OrderRowUnsaved, Option[UserId], Option[ProductId], BigDecimal, Option[TypoLocalDateTime], Defaulted[OrderId], Defaulted[Option[OrderStatus]], Defaulted[Option[TypoLocalDateTime]]]("user_id", "product_id", "total", "shipped_at", "id", "status", "created_at")(OrderRowUnsaved.apply)(Decoder.decodeOption(UserId.decoder), Decoder.decodeOption(ProductId.decoder), Decoder.decodeBigDecimal, Decoder.decodeOption(TypoLocalDateTime.decoder), Defaulted.decoder(OrderId.decoder), Defaulted.decoder(Decoder.decodeOption(OrderStatus.decoder)), Defaulted.decoder(Decoder.decodeOption(TypoLocalDateTime.decoder))) + implicit lazy val encoder: Encoder[OrderRowUnsaved] = Encoder.forProduct7[OrderRowUnsaved, Option[UserId], Option[ProductId], BigDecimal, Option[TypoLocalDateTime], Defaulted[OrderId], Defaulted[Option[OrderStatus]], Defaulted[Option[TypoLocalDateTime]]]("user_id", "product_id", "total", "shipped_at", "id", "status", "created_at")(x => (x.userId, x.productId, x.total, x.shippedAt, x.id, x.status, x.createdAt))(Encoder.encodeOption(UserId.encoder), Encoder.encodeOption(ProductId.encoder), Encoder.encodeBigDecimal, Encoder.encodeOption(TypoLocalDateTime.encoder), Defaulted.encoder(OrderId.encoder), Defaulted.encoder(Encoder.encodeOption(OrderStatus.encoder)), Defaulted.encoder(Encoder.encodeOption(TypoLocalDateTime.encoder))) + implicit lazy val text: Text[OrderRowUnsaved] = Text.instance[OrderRowUnsaved]{ (row, sb) => + Text.option(UserId.text).unsafeEncode(row.userId, sb) + sb.append(Text.DELIMETER) + Text.option(ProductId.text).unsafeEncode(row.productId, sb) + sb.append(Text.DELIMETER) + Text.bigDecimalInstance.unsafeEncode(row.total, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.shippedAt, sb) + sb.append(Text.DELIMETER) + Defaulted.text(OrderId.text).unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(OrderStatus.text)).unsafeEncode(row.status, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(TypoLocalDateTime.text)).unsafeEncode(row.createdAt, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemFields.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemFields.scala new file mode 100644 index 0000000000..d86c231793 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemFields.scala @@ -0,0 +1,63 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.order.OrderFields +import adventureworks.frontpage.order.OrderId +import adventureworks.frontpage.order.OrderRow +import adventureworks.frontpage.product.ProductFields +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.product.ProductRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait OrderItemFields { + def id: IdField[OrderItemId, OrderItemRow] + def orderId: OptField[OrderId, OrderItemRow] + def productId: OptField[ProductId, OrderItemRow] + def quantity: Field[Int, OrderItemRow] + def price: Field[BigDecimal, OrderItemRow] + def shippedAt: OptField[TypoLocalDateTime, OrderItemRow] + def fkOrder: ForeignKey[OrderFields, OrderRow] = + ForeignKey[OrderFields, OrderRow]("frontpage.order_item_order_id_fkey", Nil) + .withColumnPair(orderId, _.id) + def fkProduct: ForeignKey[ProductFields, ProductRow] = + ForeignKey[ProductFields, ProductRow]("frontpage.order_item_product_id_fkey", Nil) + .withColumnPair(productId, _.id) +} + +object OrderItemFields { + lazy val structure: Relation[OrderItemFields, OrderItemRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[OrderItemFields, OrderItemRow] { + + override lazy val fields: OrderItemFields = new OrderItemFields { + override def id = IdField[OrderItemId, OrderItemRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def orderId = OptField[OrderId, OrderItemRow](_path, "order_id", None, Some("uuid"), x => x.orderId, (row, value) => row.copy(orderId = value)) + override def productId = OptField[ProductId, OrderItemRow](_path, "product_id", None, Some("uuid"), x => x.productId, (row, value) => row.copy(productId = value)) + override def quantity = Field[Int, OrderItemRow](_path, "quantity", None, Some("int4"), x => x.quantity, (row, value) => row.copy(quantity = value)) + override def price = Field[BigDecimal, OrderItemRow](_path, "price", None, Some("numeric"), x => x.price, (row, value) => row.copy(price = value)) + override def shippedAt = OptField[TypoLocalDateTime, OrderItemRow](_path, "shipped_at", Some("text"), Some("timestamp"), x => x.shippedAt, (row, value) => row.copy(shippedAt = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, OrderItemRow]] = + List[FieldLikeNoHkt[?, OrderItemRow]](fields.id, fields.orderId, fields.productId, fields.quantity, fields.price, fields.shippedAt) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemId.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemId.scala new file mode 100644 index 0000000000..f7c5a9da0d --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemId.scala @@ -0,0 +1,33 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import adventureworks.customtypes.TypoUUID +import doobie.postgres.Text +import doobie.util.Get +import doobie.util.Put +import io.circe.Decoder +import io.circe.Encoder +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.order_item` */ +case class OrderItemId(value: TypoUUID) extends AnyVal +object OrderItemId { + implicit lazy val arrayGet: Get[Array[OrderItemId]] = TypoUUID.arrayGet.map(_.map(OrderItemId.apply)) + implicit lazy val arrayPut: Put[Array[OrderItemId]] = TypoUUID.arrayPut.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[OrderItemId, TypoUUID] = Bijection[OrderItemId, TypoUUID](_.value)(OrderItemId.apply) + implicit lazy val decoder: Decoder[OrderItemId] = TypoUUID.decoder.map(OrderItemId.apply) + implicit lazy val encoder: Encoder[OrderItemId] = TypoUUID.encoder.contramap(_.value) + implicit lazy val get: Get[OrderItemId] = TypoUUID.get.map(OrderItemId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[OrderItemId] = Ordering.by(_.value) + implicit lazy val put: Put[OrderItemId] = TypoUUID.put.contramap(_.value) + implicit lazy val text: Text[OrderItemId] = new Text[OrderItemId] { + override def unsafeEncode(v: OrderItemId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: OrderItemId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepo.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepo.scala new file mode 100644 index 0000000000..1c0e5e5e88 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepo.scala @@ -0,0 +1,36 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import doobie.free.connection.ConnectionIO +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait OrderItemRepo { + def delete: DeleteBuilder[OrderItemFields, OrderItemRow] + def deleteById(id: OrderItemId): ConnectionIO[Boolean] + def deleteByIds(ids: Array[OrderItemId]): ConnectionIO[Int] + def insert(unsaved: OrderItemRow): ConnectionIO[OrderItemRow] + def insert(unsaved: OrderItemRowUnsaved): ConnectionIO[OrderItemRow] + def insertStreaming(unsaved: Stream[ConnectionIO, OrderItemRow], batchSize: Int = 10000): ConnectionIO[Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, OrderItemRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] + def select: SelectBuilder[OrderItemFields, OrderItemRow] + def selectAll: Stream[ConnectionIO, OrderItemRow] + def selectById(id: OrderItemId): ConnectionIO[Option[OrderItemRow]] + def selectByIds(ids: Array[OrderItemId]): Stream[ConnectionIO, OrderItemRow] + def selectByIdsTracked(ids: Array[OrderItemId]): ConnectionIO[Map[OrderItemId, OrderItemRow]] + def update: UpdateBuilder[OrderItemFields, OrderItemRow] + def update(row: OrderItemRow): ConnectionIO[Boolean] + def upsert(unsaved: OrderItemRow): ConnectionIO[OrderItemRow] + def upsertBatch(unsaved: List[OrderItemRow]): Stream[ConnectionIO, OrderItemRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Stream[ConnectionIO, OrderItemRow], batchSize: Int = 10000): ConnectionIO[Int] +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepoImpl.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepoImpl.scala new file mode 100644 index 0000000000..4692b7dd65 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepoImpl.scala @@ -0,0 +1,166 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.order.OrderId +import adventureworks.frontpage.product.ProductId +import cats.instances.list.catsStdInstancesForList +import doobie.free.connection.ConnectionIO +import doobie.postgres.syntax.FragmentOps +import doobie.syntax.SqlInterpolator.SingleFragment.fromWrite +import doobie.syntax.string.toSqlInterpolator +import doobie.util.Write +import doobie.util.fragment.Fragment +import doobie.util.meta.Meta +import doobie.util.update.Update +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class OrderItemRepoImpl extends OrderItemRepo { + override def delete: DeleteBuilder[OrderItemFields, OrderItemRow] = { + DeleteBuilder(""""frontpage"."order_item"""", OrderItemFields.structure) + } + override def deleteById(id: OrderItemId): ConnectionIO[Boolean] = { + sql"""delete from "frontpage"."order_item" where "id" = ${fromWrite(id)(new Write.Single(OrderItemId.put))}""".update.run.map(_ > 0) + } + override def deleteByIds(ids: Array[OrderItemId]): ConnectionIO[Int] = { + sql"""delete from "frontpage"."order_item" where "id" = ANY(${ids})""".update.run + } + override def insert(unsaved: OrderItemRow): ConnectionIO[OrderItemRow] = { + sql"""insert into "frontpage"."order_item"("id", "order_id", "product_id", "quantity", "price", "shipped_at") + values (${fromWrite(unsaved.id)(new Write.Single(OrderItemId.put))}::uuid, ${fromWrite(unsaved.orderId)(new Write.SingleOpt(OrderId.put))}::uuid, ${fromWrite(unsaved.productId)(new Write.SingleOpt(ProductId.put))}::uuid, ${fromWrite(unsaved.quantity)(new Write.Single(Meta.IntMeta.put))}::int4, ${fromWrite(unsaved.price)(new Write.Single(Meta.ScalaBigDecimalMeta.put))}::numeric, ${fromWrite(unsaved.shippedAt)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp) + returning "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text + """.query(using OrderItemRow.read).unique + } + override def insert(unsaved: OrderItemRowUnsaved): ConnectionIO[OrderItemRow] = { + val fs = List( + Some((Fragment.const0(s""""order_id""""), fr"${fromWrite(unsaved.orderId)(new Write.SingleOpt(OrderId.put))}::uuid")), + Some((Fragment.const0(s""""product_id""""), fr"${fromWrite(unsaved.productId)(new Write.SingleOpt(ProductId.put))}::uuid")), + Some((Fragment.const0(s""""quantity""""), fr"${fromWrite(unsaved.quantity)(new Write.Single(Meta.IntMeta.put))}::int4")), + Some((Fragment.const0(s""""price""""), fr"${fromWrite(unsaved.price)(new Write.Single(Meta.ScalaBigDecimalMeta.put))}::numeric")), + Some((Fragment.const0(s""""shipped_at""""), fr"${fromWrite(unsaved.shippedAt)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""id""""), fr"${fromWrite(value: OrderItemId)(new Write.Single(OrderItemId.put))}::uuid")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."order_item" default values + returning "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text + """ + } else { + val CommaSeparate = Fragment.FragmentMonoid.intercalate(fr", ") + sql"""insert into "frontpage"."order_item"(${CommaSeparate.combineAllOption(fs.map { case (n, _) => n }).get}) + values (${CommaSeparate.combineAllOption(fs.map { case (_, f) => f }).get}) + returning "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text + """ + } + q.query(using OrderItemRow.read).unique + + } + override def insertStreaming(unsaved: Stream[ConnectionIO, OrderItemRow], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."order_item"("id", "order_id", "product_id", "quantity", "price", "shipped_at") FROM STDIN""").copyIn(unsaved, batchSize)(using OrderItemRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, OrderItemRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."order_item"("order_id", "product_id", "quantity", "price", "shipped_at", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""").copyIn(unsaved, batchSize)(using OrderItemRowUnsaved.text) + } + override def select: SelectBuilder[OrderItemFields, OrderItemRow] = { + SelectBuilderSql(""""frontpage"."order_item"""", OrderItemFields.structure, OrderItemRow.read) + } + override def selectAll: Stream[ConnectionIO, OrderItemRow] = { + sql"""select "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text from "frontpage"."order_item"""".query(using OrderItemRow.read).stream + } + override def selectById(id: OrderItemId): ConnectionIO[Option[OrderItemRow]] = { + sql"""select "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text from "frontpage"."order_item" where "id" = ${fromWrite(id)(new Write.Single(OrderItemId.put))}""".query(using OrderItemRow.read).option + } + override def selectByIds(ids: Array[OrderItemId]): Stream[ConnectionIO, OrderItemRow] = { + sql"""select "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text from "frontpage"."order_item" where "id" = ANY(${ids})""".query(using OrderItemRow.read).stream + } + override def selectByIdsTracked(ids: Array[OrderItemId]): ConnectionIO[Map[OrderItemId, OrderItemRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[OrderItemFields, OrderItemRow] = { + UpdateBuilder(""""frontpage"."order_item"""", OrderItemFields.structure, OrderItemRow.read) + } + override def update(row: OrderItemRow): ConnectionIO[Boolean] = { + val id = row.id + sql"""update "frontpage"."order_item" + set "order_id" = ${fromWrite(row.orderId)(new Write.SingleOpt(OrderId.put))}::uuid, + "product_id" = ${fromWrite(row.productId)(new Write.SingleOpt(ProductId.put))}::uuid, + "quantity" = ${fromWrite(row.quantity)(new Write.Single(Meta.IntMeta.put))}::int4, + "price" = ${fromWrite(row.price)(new Write.Single(Meta.ScalaBigDecimalMeta.put))}::numeric, + "shipped_at" = ${fromWrite(row.shippedAt)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp + where "id" = ${fromWrite(id)(new Write.Single(OrderItemId.put))}""" + .update + .run + .map(_ > 0) + } + override def upsert(unsaved: OrderItemRow): ConnectionIO[OrderItemRow] = { + sql"""insert into "frontpage"."order_item"("id", "order_id", "product_id", "quantity", "price", "shipped_at") + values ( + ${fromWrite(unsaved.id)(new Write.Single(OrderItemId.put))}::uuid, + ${fromWrite(unsaved.orderId)(new Write.SingleOpt(OrderId.put))}::uuid, + ${fromWrite(unsaved.productId)(new Write.SingleOpt(ProductId.put))}::uuid, + ${fromWrite(unsaved.quantity)(new Write.Single(Meta.IntMeta.put))}::int4, + ${fromWrite(unsaved.price)(new Write.Single(Meta.ScalaBigDecimalMeta.put))}::numeric, + ${fromWrite(unsaved.shippedAt)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp + ) + on conflict ("id") + do update set + "order_id" = EXCLUDED."order_id", + "product_id" = EXCLUDED."product_id", + "quantity" = EXCLUDED."quantity", + "price" = EXCLUDED."price", + "shipped_at" = EXCLUDED."shipped_at" + returning "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text + """.query(using OrderItemRow.read).unique + } + override def upsertBatch(unsaved: List[OrderItemRow]): Stream[ConnectionIO, OrderItemRow] = { + Update[OrderItemRow]( + s"""insert into "frontpage"."order_item"("id", "order_id", "product_id", "quantity", "price", "shipped_at") + values (?::uuid,?::uuid,?::uuid,?::int4,?::numeric,?::timestamp) + on conflict ("id") + do update set + "order_id" = EXCLUDED."order_id", + "product_id" = EXCLUDED."product_id", + "quantity" = EXCLUDED."quantity", + "price" = EXCLUDED."price", + "shipped_at" = EXCLUDED."shipped_at" + returning "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text""" + )(using OrderItemRow.write) + .updateManyWithGeneratedKeys[OrderItemRow]("id", "order_id", "product_id", "quantity", "price", "shipped_at")(unsaved)(using catsStdInstancesForList, OrderItemRow.read) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, OrderItemRow], batchSize: Int = 10000): ConnectionIO[Int] = { + for { + _ <- sql"""create temporary table order_item_TEMP (like "frontpage"."order_item") on commit drop""".update.run + _ <- new FragmentOps(sql"""copy order_item_TEMP("id", "order_id", "product_id", "quantity", "price", "shipped_at") from stdin""").copyIn(unsaved, batchSize)(using OrderItemRow.text) + res <- sql"""insert into "frontpage"."order_item"("id", "order_id", "product_id", "quantity", "price", "shipped_at") + select * from order_item_TEMP + on conflict ("id") + do update set + "order_id" = EXCLUDED."order_id", + "product_id" = EXCLUDED."product_id", + "quantity" = EXCLUDED."quantity", + "price" = EXCLUDED."price", + "shipped_at" = EXCLUDED."shipped_at" + ; + drop table order_item_TEMP;""".update.run + } yield res + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepoMock.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepoMock.scala new file mode 100644 index 0000000000..644c575d6a --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepoMock.scala @@ -0,0 +1,127 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import doobie.free.connection.ConnectionIO +import doobie.free.connection.delay +import fs2.Stream +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class OrderItemRepoMock(toRow: Function1[OrderItemRowUnsaved, OrderItemRow], + map: scala.collection.mutable.Map[OrderItemId, OrderItemRow] = scala.collection.mutable.Map.empty) extends OrderItemRepo { + override def delete: DeleteBuilder[OrderItemFields, OrderItemRow] = { + DeleteBuilderMock(DeleteParams.empty, OrderItemFields.structure, map) + } + override def deleteById(id: OrderItemId): ConnectionIO[Boolean] = { + delay(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[OrderItemId]): ConnectionIO[Int] = { + delay(ids.map(id => map.remove(id)).count(_.isDefined)) + } + override def insert(unsaved: OrderItemRow): ConnectionIO[OrderItemRow] = { + delay { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: OrderItemRowUnsaved): ConnectionIO[OrderItemRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Stream[ConnectionIO, OrderItemRow], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { rows => + var num = 0L + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, OrderItemRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { unsavedRows => + var num = 0L + unsavedRows.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + num += 1 + } + num + } + } + override def select: SelectBuilder[OrderItemFields, OrderItemRow] = { + SelectBuilderMock(OrderItemFields.structure, delay(map.values.toList), SelectParams.empty) + } + override def selectAll: Stream[ConnectionIO, OrderItemRow] = { + Stream.emits(map.values.toList) + } + override def selectById(id: OrderItemId): ConnectionIO[Option[OrderItemRow]] = { + delay(map.get(id)) + } + override def selectByIds(ids: Array[OrderItemId]): Stream[ConnectionIO, OrderItemRow] = { + Stream.emits(ids.flatMap(map.get).toList) + } + override def selectByIdsTracked(ids: Array[OrderItemId]): ConnectionIO[Map[OrderItemId, OrderItemRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[OrderItemFields, OrderItemRow] = { + UpdateBuilderMock(UpdateParams.empty, OrderItemFields.structure, map) + } + override def update(row: OrderItemRow): ConnectionIO[Boolean] = { + delay { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: OrderItemRow): ConnectionIO[OrderItemRow] = { + delay { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + } + override def upsertBatch(unsaved: List[OrderItemRow]): Stream[ConnectionIO, OrderItemRow] = { + Stream.emits { + unsaved.map { row => + map += (row.id -> row) + row + } + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, OrderItemRow], batchSize: Int = 10000): ConnectionIO[Int] = { + unsaved.compile.toList.map { rows => + var num = 0 + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRow.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRow.scala new file mode 100644 index 0000000000..d9b1fd0e3d --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRow.scala @@ -0,0 +1,80 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.order.OrderId +import adventureworks.frontpage.product.ProductId +import doobie.postgres.Text +import doobie.util.Read +import doobie.util.Write +import doobie.util.meta.Meta +import io.circe.Decoder +import io.circe.Encoder + +/** Table: frontpage.order_item + Primary key: id */ +case class OrderItemRow( + /** Default: gen_random_uuid() */ + id: OrderItemId, + /** Points to [[order.OrderRow.id]] */ + orderId: Option[OrderId], + /** Points to [[product.ProductRow.id]] */ + productId: Option[ProductId], + quantity: Int, + price: BigDecimal, + shippedAt: Option[TypoLocalDateTime] +){ + def toUnsavedRow(id: Defaulted[OrderItemId]): OrderItemRowUnsaved = + OrderItemRowUnsaved(orderId, productId, quantity, price, shippedAt, id) + } + +object OrderItemRow { + implicit lazy val decoder: Decoder[OrderItemRow] = Decoder.forProduct6[OrderItemRow, OrderItemId, Option[OrderId], Option[ProductId], Int, BigDecimal, Option[TypoLocalDateTime]]("id", "order_id", "product_id", "quantity", "price", "shipped_at")(OrderItemRow.apply)(OrderItemId.decoder, Decoder.decodeOption(OrderId.decoder), Decoder.decodeOption(ProductId.decoder), Decoder.decodeInt, Decoder.decodeBigDecimal, Decoder.decodeOption(TypoLocalDateTime.decoder)) + implicit lazy val encoder: Encoder[OrderItemRow] = Encoder.forProduct6[OrderItemRow, OrderItemId, Option[OrderId], Option[ProductId], Int, BigDecimal, Option[TypoLocalDateTime]]("id", "order_id", "product_id", "quantity", "price", "shipped_at")(x => (x.id, x.orderId, x.productId, x.quantity, x.price, x.shippedAt))(OrderItemId.encoder, Encoder.encodeOption(OrderId.encoder), Encoder.encodeOption(ProductId.encoder), Encoder.encodeInt, Encoder.encodeBigDecimal, Encoder.encodeOption(TypoLocalDateTime.encoder)) + implicit lazy val read: Read[OrderItemRow] = new Read.CompositeOfInstances(Array( + new Read.Single(OrderItemId.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(OrderId.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(ProductId.get).asInstanceOf[Read[Any]], + new Read.Single(Meta.IntMeta.get).asInstanceOf[Read[Any]], + new Read.Single(Meta.ScalaBigDecimalMeta.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(TypoLocalDateTime.get).asInstanceOf[Read[Any]] + ))(using scala.reflect.ClassTag.Any).map { arr => + OrderItemRow( + id = arr(0).asInstanceOf[OrderItemId], + orderId = arr(1).asInstanceOf[Option[OrderId]], + productId = arr(2).asInstanceOf[Option[ProductId]], + quantity = arr(3).asInstanceOf[Int], + price = arr(4).asInstanceOf[BigDecimal], + shippedAt = arr(5).asInstanceOf[Option[TypoLocalDateTime]] + ) + } + implicit lazy val text: Text[OrderItemRow] = Text.instance[OrderItemRow]{ (row, sb) => + OrderItemId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.option(OrderId.text).unsafeEncode(row.orderId, sb) + sb.append(Text.DELIMETER) + Text.option(ProductId.text).unsafeEncode(row.productId, sb) + sb.append(Text.DELIMETER) + Text.intInstance.unsafeEncode(row.quantity, sb) + sb.append(Text.DELIMETER) + Text.bigDecimalInstance.unsafeEncode(row.price, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.shippedAt, sb) + } + implicit lazy val write: Write[OrderItemRow] = new Write.Composite[OrderItemRow]( + List(new Write.Single(OrderItemId.put), + new Write.Single(OrderId.put).toOpt, + new Write.Single(ProductId.put).toOpt, + new Write.Single(Meta.IntMeta.put), + new Write.Single(Meta.ScalaBigDecimalMeta.put), + new Write.Single(TypoLocalDateTime.put).toOpt), + a => List(a.id, a.orderId, a.productId, a.quantity, a.price, a.shippedAt) + ) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRowUnsaved.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRowUnsaved.scala new file mode 100644 index 0000000000..73d82b005a --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRowUnsaved.scala @@ -0,0 +1,59 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.order.OrderId +import adventureworks.frontpage.product.ProductId +import doobie.postgres.Text +import io.circe.Decoder +import io.circe.Encoder + +/** This class corresponds to a row in table `frontpage.order_item` which has not been persisted yet */ +case class OrderItemRowUnsaved( + /** Points to [[order.OrderRow.id]] */ + orderId: Option[OrderId], + /** Points to [[product.ProductRow.id]] */ + productId: Option[ProductId], + quantity: Int, + price: BigDecimal, + shippedAt: Option[TypoLocalDateTime], + /** Default: gen_random_uuid() */ + id: Defaulted[OrderItemId] = Defaulted.UseDefault +) { + def toRow(idDefault: => OrderItemId): OrderItemRow = + OrderItemRow( + orderId = orderId, + productId = productId, + quantity = quantity, + price = price, + shippedAt = shippedAt, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object OrderItemRowUnsaved { + implicit lazy val decoder: Decoder[OrderItemRowUnsaved] = Decoder.forProduct6[OrderItemRowUnsaved, Option[OrderId], Option[ProductId], Int, BigDecimal, Option[TypoLocalDateTime], Defaulted[OrderItemId]]("order_id", "product_id", "quantity", "price", "shipped_at", "id")(OrderItemRowUnsaved.apply)(Decoder.decodeOption(OrderId.decoder), Decoder.decodeOption(ProductId.decoder), Decoder.decodeInt, Decoder.decodeBigDecimal, Decoder.decodeOption(TypoLocalDateTime.decoder), Defaulted.decoder(OrderItemId.decoder)) + implicit lazy val encoder: Encoder[OrderItemRowUnsaved] = Encoder.forProduct6[OrderItemRowUnsaved, Option[OrderId], Option[ProductId], Int, BigDecimal, Option[TypoLocalDateTime], Defaulted[OrderItemId]]("order_id", "product_id", "quantity", "price", "shipped_at", "id")(x => (x.orderId, x.productId, x.quantity, x.price, x.shippedAt, x.id))(Encoder.encodeOption(OrderId.encoder), Encoder.encodeOption(ProductId.encoder), Encoder.encodeInt, Encoder.encodeBigDecimal, Encoder.encodeOption(TypoLocalDateTime.encoder), Defaulted.encoder(OrderItemId.encoder)) + implicit lazy val text: Text[OrderItemRowUnsaved] = Text.instance[OrderItemRowUnsaved]{ (row, sb) => + Text.option(OrderId.text).unsafeEncode(row.orderId, sb) + sb.append(Text.DELIMETER) + Text.option(ProductId.text).unsafeEncode(row.productId, sb) + sb.append(Text.DELIMETER) + Text.intInstance.unsafeEncode(row.quantity, sb) + sb.append(Text.DELIMETER) + Text.bigDecimalInstance.unsafeEncode(row.price, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.shippedAt, sb) + sb.append(Text.DELIMETER) + Defaulted.text(OrderItemId.text).unsafeEncode(row.id, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionFields.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionFields.scala new file mode 100644 index 0000000000..856b5aae7e --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionFields.scala @@ -0,0 +1,40 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.Structure.Relation + +trait PermissionFields { + def id: IdField[PermissionId, PermissionRow] + def name: Field[String, PermissionRow] +} + +object PermissionFields { + lazy val structure: Relation[PermissionFields, PermissionRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[PermissionFields, PermissionRow] { + + override lazy val fields: PermissionFields = new PermissionFields { + override def id = IdField[PermissionId, PermissionRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, PermissionRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, PermissionRow]] = + List[FieldLikeNoHkt[?, PermissionRow]](fields.id, fields.name) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionId.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionId.scala new file mode 100644 index 0000000000..983726ddc2 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionId.scala @@ -0,0 +1,33 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import adventureworks.customtypes.TypoUUID +import doobie.postgres.Text +import doobie.util.Get +import doobie.util.Put +import io.circe.Decoder +import io.circe.Encoder +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.permission` */ +case class PermissionId(value: TypoUUID) extends AnyVal +object PermissionId { + implicit lazy val arrayGet: Get[Array[PermissionId]] = TypoUUID.arrayGet.map(_.map(PermissionId.apply)) + implicit lazy val arrayPut: Put[Array[PermissionId]] = TypoUUID.arrayPut.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[PermissionId, TypoUUID] = Bijection[PermissionId, TypoUUID](_.value)(PermissionId.apply) + implicit lazy val decoder: Decoder[PermissionId] = TypoUUID.decoder.map(PermissionId.apply) + implicit lazy val encoder: Encoder[PermissionId] = TypoUUID.encoder.contramap(_.value) + implicit lazy val get: Get[PermissionId] = TypoUUID.get.map(PermissionId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[PermissionId] = Ordering.by(_.value) + implicit lazy val put: Put[PermissionId] = TypoUUID.put.contramap(_.value) + implicit lazy val text: Text[PermissionId] = new Text[PermissionId] { + override def unsafeEncode(v: PermissionId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: PermissionId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepo.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepo.scala new file mode 100644 index 0000000000..c79d9aa01e --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepo.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import doobie.free.connection.ConnectionIO +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait PermissionRepo { + def delete: DeleteBuilder[PermissionFields, PermissionRow] + def deleteById(id: PermissionId): ConnectionIO[Boolean] + def deleteByIds(ids: Array[PermissionId]): ConnectionIO[Int] + def insert(unsaved: PermissionRow): ConnectionIO[PermissionRow] + def insert(unsaved: PermissionRowUnsaved): ConnectionIO[PermissionRow] + def insertStreaming(unsaved: Stream[ConnectionIO, PermissionRow], batchSize: Int = 10000): ConnectionIO[Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, PermissionRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] + def select: SelectBuilder[PermissionFields, PermissionRow] + def selectAll: Stream[ConnectionIO, PermissionRow] + def selectById(id: PermissionId): ConnectionIO[Option[PermissionRow]] + def selectByIds(ids: Array[PermissionId]): Stream[ConnectionIO, PermissionRow] + def selectByIdsTracked(ids: Array[PermissionId]): ConnectionIO[Map[PermissionId, PermissionRow]] + def selectByUniqueName(name: String): ConnectionIO[Option[PermissionRow]] + def update: UpdateBuilder[PermissionFields, PermissionRow] + def update(row: PermissionRow): ConnectionIO[Boolean] + def upsert(unsaved: PermissionRow): ConnectionIO[PermissionRow] + def upsertBatch(unsaved: List[PermissionRow]): Stream[ConnectionIO, PermissionRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Stream[ConnectionIO, PermissionRow], batchSize: Int = 10000): ConnectionIO[Int] +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepoImpl.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepoImpl.scala new file mode 100644 index 0000000000..bd33cb1bf1 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepoImpl.scala @@ -0,0 +1,145 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import adventureworks.customtypes.Defaulted +import cats.instances.list.catsStdInstancesForList +import doobie.free.connection.ConnectionIO +import doobie.postgres.syntax.FragmentOps +import doobie.syntax.SqlInterpolator.SingleFragment.fromWrite +import doobie.syntax.string.toSqlInterpolator +import doobie.util.Write +import doobie.util.fragment.Fragment +import doobie.util.meta.Meta +import doobie.util.update.Update +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class PermissionRepoImpl extends PermissionRepo { + override def delete: DeleteBuilder[PermissionFields, PermissionRow] = { + DeleteBuilder(""""frontpage"."permission"""", PermissionFields.structure) + } + override def deleteById(id: PermissionId): ConnectionIO[Boolean] = { + sql"""delete from "frontpage"."permission" where "id" = ${fromWrite(id)(new Write.Single(PermissionId.put))}""".update.run.map(_ > 0) + } + override def deleteByIds(ids: Array[PermissionId]): ConnectionIO[Int] = { + sql"""delete from "frontpage"."permission" where "id" = ANY(${ids})""".update.run + } + override def insert(unsaved: PermissionRow): ConnectionIO[PermissionRow] = { + sql"""insert into "frontpage"."permission"("id", "name") + values (${fromWrite(unsaved.id)(new Write.Single(PermissionId.put))}::uuid, ${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}) + returning "id", "name" + """.query(using PermissionRow.read).unique + } + override def insert(unsaved: PermissionRowUnsaved): ConnectionIO[PermissionRow] = { + val fs = List( + Some((Fragment.const0(s""""name""""), fr"${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""id""""), fr"${fromWrite(value: PermissionId)(new Write.Single(PermissionId.put))}::uuid")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."permission" default values + returning "id", "name" + """ + } else { + val CommaSeparate = Fragment.FragmentMonoid.intercalate(fr", ") + sql"""insert into "frontpage"."permission"(${CommaSeparate.combineAllOption(fs.map { case (n, _) => n }).get}) + values (${CommaSeparate.combineAllOption(fs.map { case (_, f) => f }).get}) + returning "id", "name" + """ + } + q.query(using PermissionRow.read).unique + + } + override def insertStreaming(unsaved: Stream[ConnectionIO, PermissionRow], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."permission"("id", "name") FROM STDIN""").copyIn(unsaved, batchSize)(using PermissionRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, PermissionRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."permission"("name", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""").copyIn(unsaved, batchSize)(using PermissionRowUnsaved.text) + } + override def select: SelectBuilder[PermissionFields, PermissionRow] = { + SelectBuilderSql(""""frontpage"."permission"""", PermissionFields.structure, PermissionRow.read) + } + override def selectAll: Stream[ConnectionIO, PermissionRow] = { + sql"""select "id", "name" from "frontpage"."permission"""".query(using PermissionRow.read).stream + } + override def selectById(id: PermissionId): ConnectionIO[Option[PermissionRow]] = { + sql"""select "id", "name" from "frontpage"."permission" where "id" = ${fromWrite(id)(new Write.Single(PermissionId.put))}""".query(using PermissionRow.read).option + } + override def selectByIds(ids: Array[PermissionId]): Stream[ConnectionIO, PermissionRow] = { + sql"""select "id", "name" from "frontpage"."permission" where "id" = ANY(${ids})""".query(using PermissionRow.read).stream + } + override def selectByIdsTracked(ids: Array[PermissionId]): ConnectionIO[Map[PermissionId, PermissionRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def selectByUniqueName(name: String): ConnectionIO[Option[PermissionRow]] = { + sql"""select "id", "name" + from "frontpage"."permission" + where "name" = ${fromWrite(name)(new Write.Single(Meta.StringMeta.put))} + """.query(using PermissionRow.read).option + } + override def update: UpdateBuilder[PermissionFields, PermissionRow] = { + UpdateBuilder(""""frontpage"."permission"""", PermissionFields.structure, PermissionRow.read) + } + override def update(row: PermissionRow): ConnectionIO[Boolean] = { + val id = row.id + sql"""update "frontpage"."permission" + set "name" = ${fromWrite(row.name)(new Write.Single(Meta.StringMeta.put))} + where "id" = ${fromWrite(id)(new Write.Single(PermissionId.put))}""" + .update + .run + .map(_ > 0) + } + override def upsert(unsaved: PermissionRow): ConnectionIO[PermissionRow] = { + sql"""insert into "frontpage"."permission"("id", "name") + values ( + ${fromWrite(unsaved.id)(new Write.Single(PermissionId.put))}::uuid, + ${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))} + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name" + returning "id", "name" + """.query(using PermissionRow.read).unique + } + override def upsertBatch(unsaved: List[PermissionRow]): Stream[ConnectionIO, PermissionRow] = { + Update[PermissionRow]( + s"""insert into "frontpage"."permission"("id", "name") + values (?::uuid,?) + on conflict ("id") + do update set + "name" = EXCLUDED."name" + returning "id", "name"""" + )(using PermissionRow.write) + .updateManyWithGeneratedKeys[PermissionRow]("id", "name")(unsaved)(using catsStdInstancesForList, PermissionRow.read) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, PermissionRow], batchSize: Int = 10000): ConnectionIO[Int] = { + for { + _ <- sql"""create temporary table permission_TEMP (like "frontpage"."permission") on commit drop""".update.run + _ <- new FragmentOps(sql"""copy permission_TEMP("id", "name") from stdin""").copyIn(unsaved, batchSize)(using PermissionRow.text) + res <- sql"""insert into "frontpage"."permission"("id", "name") + select * from permission_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name" + ; + drop table permission_TEMP;""".update.run + } yield res + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepoMock.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepoMock.scala new file mode 100644 index 0000000000..d2d1550b15 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepoMock.scala @@ -0,0 +1,130 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import doobie.free.connection.ConnectionIO +import doobie.free.connection.delay +import fs2.Stream +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class PermissionRepoMock(toRow: Function1[PermissionRowUnsaved, PermissionRow], + map: scala.collection.mutable.Map[PermissionId, PermissionRow] = scala.collection.mutable.Map.empty) extends PermissionRepo { + override def delete: DeleteBuilder[PermissionFields, PermissionRow] = { + DeleteBuilderMock(DeleteParams.empty, PermissionFields.structure, map) + } + override def deleteById(id: PermissionId): ConnectionIO[Boolean] = { + delay(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[PermissionId]): ConnectionIO[Int] = { + delay(ids.map(id => map.remove(id)).count(_.isDefined)) + } + override def insert(unsaved: PermissionRow): ConnectionIO[PermissionRow] = { + delay { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: PermissionRowUnsaved): ConnectionIO[PermissionRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Stream[ConnectionIO, PermissionRow], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { rows => + var num = 0L + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, PermissionRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { unsavedRows => + var num = 0L + unsavedRows.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + num += 1 + } + num + } + } + override def select: SelectBuilder[PermissionFields, PermissionRow] = { + SelectBuilderMock(PermissionFields.structure, delay(map.values.toList), SelectParams.empty) + } + override def selectAll: Stream[ConnectionIO, PermissionRow] = { + Stream.emits(map.values.toList) + } + override def selectById(id: PermissionId): ConnectionIO[Option[PermissionRow]] = { + delay(map.get(id)) + } + override def selectByIds(ids: Array[PermissionId]): Stream[ConnectionIO, PermissionRow] = { + Stream.emits(ids.flatMap(map.get).toList) + } + override def selectByIdsTracked(ids: Array[PermissionId]): ConnectionIO[Map[PermissionId, PermissionRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def selectByUniqueName(name: String): ConnectionIO[Option[PermissionRow]] = { + delay(map.values.find(v => name == v.name)) + } + override def update: UpdateBuilder[PermissionFields, PermissionRow] = { + UpdateBuilderMock(UpdateParams.empty, PermissionFields.structure, map) + } + override def update(row: PermissionRow): ConnectionIO[Boolean] = { + delay { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: PermissionRow): ConnectionIO[PermissionRow] = { + delay { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + } + override def upsertBatch(unsaved: List[PermissionRow]): Stream[ConnectionIO, PermissionRow] = { + Stream.emits { + unsaved.map { row => + map += (row.id -> row) + row + } + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, PermissionRow], batchSize: Int = 10000): ConnectionIO[Int] = { + unsaved.compile.toList.map { rows => + var num = 0 + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRow.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRow.scala new file mode 100644 index 0000000000..521cb557e7 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRow.scala @@ -0,0 +1,51 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import adventureworks.customtypes.Defaulted +import doobie.postgres.Text +import doobie.util.Read +import doobie.util.Write +import doobie.util.meta.Meta +import io.circe.Decoder +import io.circe.Encoder + +/** Table: frontpage.permission + Primary key: id */ +case class PermissionRow( + /** Default: gen_random_uuid() */ + id: PermissionId, + name: String +){ + def toUnsavedRow(id: Defaulted[PermissionId]): PermissionRowUnsaved = + PermissionRowUnsaved(name, id) + } + +object PermissionRow { + implicit lazy val decoder: Decoder[PermissionRow] = Decoder.forProduct2[PermissionRow, PermissionId, String]("id", "name")(PermissionRow.apply)(PermissionId.decoder, Decoder.decodeString) + implicit lazy val encoder: Encoder[PermissionRow] = Encoder.forProduct2[PermissionRow, PermissionId, String]("id", "name")(x => (x.id, x.name))(PermissionId.encoder, Encoder.encodeString) + implicit lazy val read: Read[PermissionRow] = new Read.CompositeOfInstances(Array( + new Read.Single(PermissionId.get).asInstanceOf[Read[Any]], + new Read.Single(Meta.StringMeta.get).asInstanceOf[Read[Any]] + ))(using scala.reflect.ClassTag.Any).map { arr => + PermissionRow( + id = arr(0).asInstanceOf[PermissionId], + name = arr(1).asInstanceOf[String] + ) + } + implicit lazy val text: Text[PermissionRow] = Text.instance[PermissionRow]{ (row, sb) => + PermissionId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + } + implicit lazy val write: Write[PermissionRow] = new Write.Composite[PermissionRow]( + List(new Write.Single(PermissionId.put), + new Write.Single(Meta.StringMeta.put)), + a => List(a.id, a.name) + ) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRowUnsaved.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRowUnsaved.scala new file mode 100644 index 0000000000..f79f213a76 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRowUnsaved.scala @@ -0,0 +1,38 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import adventureworks.customtypes.Defaulted +import doobie.postgres.Text +import io.circe.Decoder +import io.circe.Encoder + +/** This class corresponds to a row in table `frontpage.permission` which has not been persisted yet */ +case class PermissionRowUnsaved( + name: String, + /** Default: gen_random_uuid() */ + id: Defaulted[PermissionId] = Defaulted.UseDefault +) { + def toRow(idDefault: => PermissionId): PermissionRow = + PermissionRow( + name = name, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object PermissionRowUnsaved { + implicit lazy val decoder: Decoder[PermissionRowUnsaved] = Decoder.forProduct2[PermissionRowUnsaved, String, Defaulted[PermissionId]]("name", "id")(PermissionRowUnsaved.apply)(Decoder.decodeString, Defaulted.decoder(PermissionId.decoder)) + implicit lazy val encoder: Encoder[PermissionRowUnsaved] = Encoder.forProduct2[PermissionRowUnsaved, String, Defaulted[PermissionId]]("name", "id")(x => (x.name, x.id))(Encoder.encodeString, Defaulted.encoder(PermissionId.encoder)) + implicit lazy val text: Text[PermissionRowUnsaved] = Text.instance[PermissionRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Defaulted.text(PermissionId.text).unsafeEncode(row.id, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonFields.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonFields.scala new file mode 100644 index 0000000000..c547be34fd --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonFields.scala @@ -0,0 +1,53 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.address.AddressFields +import adventureworks.frontpage.address.AddressId +import adventureworks.frontpage.address.AddressRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait PersonFields { + def id: IdField[PersonId, PersonRow] + def name: Field[String, PersonRow] + def addressId: OptField[AddressId, PersonRow] + def createdAt: OptField[TypoLocalDateTime, PersonRow] + def fkAddress: ForeignKey[AddressFields, AddressRow] = + ForeignKey[AddressFields, AddressRow]("frontpage.fk_address", Nil) + .withColumnPair(addressId, _.id) +} + +object PersonFields { + lazy val structure: Relation[PersonFields, PersonRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[PersonFields, PersonRow] { + + override lazy val fields: PersonFields = new PersonFields { + override def id = IdField[PersonId, PersonRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, PersonRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + override def addressId = OptField[AddressId, PersonRow](_path, "address_id", None, Some("uuid"), x => x.addressId, (row, value) => row.copy(addressId = value)) + override def createdAt = OptField[TypoLocalDateTime, PersonRow](_path, "created_at", Some("text"), Some("timestamp"), x => x.createdAt, (row, value) => row.copy(createdAt = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, PersonRow]] = + List[FieldLikeNoHkt[?, PersonRow]](fields.id, fields.name, fields.addressId, fields.createdAt) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonId.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonId.scala new file mode 100644 index 0000000000..6c437ea7c6 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonId.scala @@ -0,0 +1,33 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import adventureworks.customtypes.TypoUUID +import doobie.postgres.Text +import doobie.util.Get +import doobie.util.Put +import io.circe.Decoder +import io.circe.Encoder +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.person` */ +case class PersonId(value: TypoUUID) extends AnyVal +object PersonId { + implicit lazy val arrayGet: Get[Array[PersonId]] = TypoUUID.arrayGet.map(_.map(PersonId.apply)) + implicit lazy val arrayPut: Put[Array[PersonId]] = TypoUUID.arrayPut.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[PersonId, TypoUUID] = Bijection[PersonId, TypoUUID](_.value)(PersonId.apply) + implicit lazy val decoder: Decoder[PersonId] = TypoUUID.decoder.map(PersonId.apply) + implicit lazy val encoder: Encoder[PersonId] = TypoUUID.encoder.contramap(_.value) + implicit lazy val get: Get[PersonId] = TypoUUID.get.map(PersonId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[PersonId] = Ordering.by(_.value) + implicit lazy val put: Put[PersonId] = TypoUUID.put.contramap(_.value) + implicit lazy val text: Text[PersonId] = new Text[PersonId] { + override def unsafeEncode(v: PersonId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: PersonId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonRepo.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonRepo.scala new file mode 100644 index 0000000000..10740e9a3f --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonRepo.scala @@ -0,0 +1,36 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import doobie.free.connection.ConnectionIO +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait PersonRepo { + def delete: DeleteBuilder[PersonFields, PersonRow] + def deleteById(id: PersonId): ConnectionIO[Boolean] + def deleteByIds(ids: Array[PersonId]): ConnectionIO[Int] + def insert(unsaved: PersonRow): ConnectionIO[PersonRow] + def insert(unsaved: PersonRowUnsaved): ConnectionIO[PersonRow] + def insertStreaming(unsaved: Stream[ConnectionIO, PersonRow], batchSize: Int = 10000): ConnectionIO[Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, PersonRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] + def select: SelectBuilder[PersonFields, PersonRow] + def selectAll: Stream[ConnectionIO, PersonRow] + def selectById(id: PersonId): ConnectionIO[Option[PersonRow]] + def selectByIds(ids: Array[PersonId]): Stream[ConnectionIO, PersonRow] + def selectByIdsTracked(ids: Array[PersonId]): ConnectionIO[Map[PersonId, PersonRow]] + def update: UpdateBuilder[PersonFields, PersonRow] + def update(row: PersonRow): ConnectionIO[Boolean] + def upsert(unsaved: PersonRow): ConnectionIO[PersonRow] + def upsertBatch(unsaved: List[PersonRow]): Stream[ConnectionIO, PersonRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Stream[ConnectionIO, PersonRow], batchSize: Int = 10000): ConnectionIO[Int] +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonRepoImpl.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonRepoImpl.scala new file mode 100644 index 0000000000..c297fcb7b2 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonRepoImpl.scala @@ -0,0 +1,156 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.address.AddressId +import cats.instances.list.catsStdInstancesForList +import doobie.free.connection.ConnectionIO +import doobie.postgres.syntax.FragmentOps +import doobie.syntax.SqlInterpolator.SingleFragment.fromWrite +import doobie.syntax.string.toSqlInterpolator +import doobie.util.Write +import doobie.util.fragment.Fragment +import doobie.util.meta.Meta +import doobie.util.update.Update +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class PersonRepoImpl extends PersonRepo { + override def delete: DeleteBuilder[PersonFields, PersonRow] = { + DeleteBuilder(""""frontpage"."person"""", PersonFields.structure) + } + override def deleteById(id: PersonId): ConnectionIO[Boolean] = { + sql"""delete from "frontpage"."person" where "id" = ${fromWrite(id)(new Write.Single(PersonId.put))}""".update.run.map(_ > 0) + } + override def deleteByIds(ids: Array[PersonId]): ConnectionIO[Int] = { + sql"""delete from "frontpage"."person" where "id" = ANY(${ids})""".update.run + } + override def insert(unsaved: PersonRow): ConnectionIO[PersonRow] = { + sql"""insert into "frontpage"."person"("id", "name", "address_id", "created_at") + values (${fromWrite(unsaved.id)(new Write.Single(PersonId.put))}::uuid, ${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}, ${fromWrite(unsaved.addressId)(new Write.SingleOpt(AddressId.put))}::uuid, ${fromWrite(unsaved.createdAt)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp) + returning "id", "name", "address_id", "created_at"::text + """.query(using PersonRow.read).unique + } + override def insert(unsaved: PersonRowUnsaved): ConnectionIO[PersonRow] = { + val fs = List( + Some((Fragment.const0(s""""name""""), fr"${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}")), + Some((Fragment.const0(s""""address_id""""), fr"${fromWrite(unsaved.addressId)(new Write.SingleOpt(AddressId.put))}::uuid")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""id""""), fr"${fromWrite(value: PersonId)(new Write.Single(PersonId.put))}::uuid")) + }, + unsaved.createdAt match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""created_at""""), fr"${fromWrite(value: Option[TypoLocalDateTime])(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."person" default values + returning "id", "name", "address_id", "created_at"::text + """ + } else { + val CommaSeparate = Fragment.FragmentMonoid.intercalate(fr", ") + sql"""insert into "frontpage"."person"(${CommaSeparate.combineAllOption(fs.map { case (n, _) => n }).get}) + values (${CommaSeparate.combineAllOption(fs.map { case (_, f) => f }).get}) + returning "id", "name", "address_id", "created_at"::text + """ + } + q.query(using PersonRow.read).unique + + } + override def insertStreaming(unsaved: Stream[ConnectionIO, PersonRow], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."person"("id", "name", "address_id", "created_at") FROM STDIN""").copyIn(unsaved, batchSize)(using PersonRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, PersonRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."person"("name", "address_id", "id", "created_at") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""").copyIn(unsaved, batchSize)(using PersonRowUnsaved.text) + } + override def select: SelectBuilder[PersonFields, PersonRow] = { + SelectBuilderSql(""""frontpage"."person"""", PersonFields.structure, PersonRow.read) + } + override def selectAll: Stream[ConnectionIO, PersonRow] = { + sql"""select "id", "name", "address_id", "created_at"::text from "frontpage"."person"""".query(using PersonRow.read).stream + } + override def selectById(id: PersonId): ConnectionIO[Option[PersonRow]] = { + sql"""select "id", "name", "address_id", "created_at"::text from "frontpage"."person" where "id" = ${fromWrite(id)(new Write.Single(PersonId.put))}""".query(using PersonRow.read).option + } + override def selectByIds(ids: Array[PersonId]): Stream[ConnectionIO, PersonRow] = { + sql"""select "id", "name", "address_id", "created_at"::text from "frontpage"."person" where "id" = ANY(${ids})""".query(using PersonRow.read).stream + } + override def selectByIdsTracked(ids: Array[PersonId]): ConnectionIO[Map[PersonId, PersonRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[PersonFields, PersonRow] = { + UpdateBuilder(""""frontpage"."person"""", PersonFields.structure, PersonRow.read) + } + override def update(row: PersonRow): ConnectionIO[Boolean] = { + val id = row.id + sql"""update "frontpage"."person" + set "name" = ${fromWrite(row.name)(new Write.Single(Meta.StringMeta.put))}, + "address_id" = ${fromWrite(row.addressId)(new Write.SingleOpt(AddressId.put))}::uuid, + "created_at" = ${fromWrite(row.createdAt)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp + where "id" = ${fromWrite(id)(new Write.Single(PersonId.put))}""" + .update + .run + .map(_ > 0) + } + override def upsert(unsaved: PersonRow): ConnectionIO[PersonRow] = { + sql"""insert into "frontpage"."person"("id", "name", "address_id", "created_at") + values ( + ${fromWrite(unsaved.id)(new Write.Single(PersonId.put))}::uuid, + ${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}, + ${fromWrite(unsaved.addressId)(new Write.SingleOpt(AddressId.put))}::uuid, + ${fromWrite(unsaved.createdAt)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "address_id" = EXCLUDED."address_id", + "created_at" = EXCLUDED."created_at" + returning "id", "name", "address_id", "created_at"::text + """.query(using PersonRow.read).unique + } + override def upsertBatch(unsaved: List[PersonRow]): Stream[ConnectionIO, PersonRow] = { + Update[PersonRow]( + s"""insert into "frontpage"."person"("id", "name", "address_id", "created_at") + values (?::uuid,?,?::uuid,?::timestamp) + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "address_id" = EXCLUDED."address_id", + "created_at" = EXCLUDED."created_at" + returning "id", "name", "address_id", "created_at"::text""" + )(using PersonRow.write) + .updateManyWithGeneratedKeys[PersonRow]("id", "name", "address_id", "created_at")(unsaved)(using catsStdInstancesForList, PersonRow.read) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, PersonRow], batchSize: Int = 10000): ConnectionIO[Int] = { + for { + _ <- sql"""create temporary table person_TEMP (like "frontpage"."person") on commit drop""".update.run + _ <- new FragmentOps(sql"""copy person_TEMP("id", "name", "address_id", "created_at") from stdin""").copyIn(unsaved, batchSize)(using PersonRow.text) + res <- sql"""insert into "frontpage"."person"("id", "name", "address_id", "created_at") + select * from person_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "address_id" = EXCLUDED."address_id", + "created_at" = EXCLUDED."created_at" + ; + drop table person_TEMP;""".update.run + } yield res + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonRepoMock.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonRepoMock.scala new file mode 100644 index 0000000000..3ddc53a50f --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonRepoMock.scala @@ -0,0 +1,127 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import doobie.free.connection.ConnectionIO +import doobie.free.connection.delay +import fs2.Stream +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class PersonRepoMock(toRow: Function1[PersonRowUnsaved, PersonRow], + map: scala.collection.mutable.Map[PersonId, PersonRow] = scala.collection.mutable.Map.empty) extends PersonRepo { + override def delete: DeleteBuilder[PersonFields, PersonRow] = { + DeleteBuilderMock(DeleteParams.empty, PersonFields.structure, map) + } + override def deleteById(id: PersonId): ConnectionIO[Boolean] = { + delay(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[PersonId]): ConnectionIO[Int] = { + delay(ids.map(id => map.remove(id)).count(_.isDefined)) + } + override def insert(unsaved: PersonRow): ConnectionIO[PersonRow] = { + delay { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: PersonRowUnsaved): ConnectionIO[PersonRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Stream[ConnectionIO, PersonRow], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { rows => + var num = 0L + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, PersonRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { unsavedRows => + var num = 0L + unsavedRows.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + num += 1 + } + num + } + } + override def select: SelectBuilder[PersonFields, PersonRow] = { + SelectBuilderMock(PersonFields.structure, delay(map.values.toList), SelectParams.empty) + } + override def selectAll: Stream[ConnectionIO, PersonRow] = { + Stream.emits(map.values.toList) + } + override def selectById(id: PersonId): ConnectionIO[Option[PersonRow]] = { + delay(map.get(id)) + } + override def selectByIds(ids: Array[PersonId]): Stream[ConnectionIO, PersonRow] = { + Stream.emits(ids.flatMap(map.get).toList) + } + override def selectByIdsTracked(ids: Array[PersonId]): ConnectionIO[Map[PersonId, PersonRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[PersonFields, PersonRow] = { + UpdateBuilderMock(UpdateParams.empty, PersonFields.structure, map) + } + override def update(row: PersonRow): ConnectionIO[Boolean] = { + delay { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: PersonRow): ConnectionIO[PersonRow] = { + delay { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + } + override def upsertBatch(unsaved: List[PersonRow]): Stream[ConnectionIO, PersonRow] = { + Stream.emits { + unsaved.map { row => + map += (row.id -> row) + row + } + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, PersonRow], batchSize: Int = 10000): ConnectionIO[Int] = { + unsaved.compile.toList.map { rows => + var num = 0 + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonRow.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonRow.scala new file mode 100644 index 0000000000..dcc9a3c086 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonRow.scala @@ -0,0 +1,67 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.address.AddressId +import doobie.postgres.Text +import doobie.util.Read +import doobie.util.Write +import doobie.util.meta.Meta +import io.circe.Decoder +import io.circe.Encoder + +/** Table: frontpage.person + Primary key: id */ +case class PersonRow( + /** Default: gen_random_uuid() */ + id: PersonId, + name: String, + /** Points to [[address.AddressRow.id]] */ + addressId: Option[AddressId], + /** Default: now() */ + createdAt: Option[TypoLocalDateTime] +){ + def toUnsavedRow(id: Defaulted[PersonId], createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.Provided(this.createdAt)): PersonRowUnsaved = + PersonRowUnsaved(name, addressId, id, createdAt) + } + +object PersonRow { + implicit lazy val decoder: Decoder[PersonRow] = Decoder.forProduct4[PersonRow, PersonId, String, Option[AddressId], Option[TypoLocalDateTime]]("id", "name", "address_id", "created_at")(PersonRow.apply)(PersonId.decoder, Decoder.decodeString, Decoder.decodeOption(AddressId.decoder), Decoder.decodeOption(TypoLocalDateTime.decoder)) + implicit lazy val encoder: Encoder[PersonRow] = Encoder.forProduct4[PersonRow, PersonId, String, Option[AddressId], Option[TypoLocalDateTime]]("id", "name", "address_id", "created_at")(x => (x.id, x.name, x.addressId, x.createdAt))(PersonId.encoder, Encoder.encodeString, Encoder.encodeOption(AddressId.encoder), Encoder.encodeOption(TypoLocalDateTime.encoder)) + implicit lazy val read: Read[PersonRow] = new Read.CompositeOfInstances(Array( + new Read.Single(PersonId.get).asInstanceOf[Read[Any]], + new Read.Single(Meta.StringMeta.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(AddressId.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(TypoLocalDateTime.get).asInstanceOf[Read[Any]] + ))(using scala.reflect.ClassTag.Any).map { arr => + PersonRow( + id = arr(0).asInstanceOf[PersonId], + name = arr(1).asInstanceOf[String], + addressId = arr(2).asInstanceOf[Option[AddressId]], + createdAt = arr(3).asInstanceOf[Option[TypoLocalDateTime]] + ) + } + implicit lazy val text: Text[PersonRow] = Text.instance[PersonRow]{ (row, sb) => + PersonId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(AddressId.text).unsafeEncode(row.addressId, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.createdAt, sb) + } + implicit lazy val write: Write[PersonRow] = new Write.Composite[PersonRow]( + List(new Write.Single(PersonId.put), + new Write.Single(Meta.StringMeta.put), + new Write.Single(AddressId.put).toOpt, + new Write.Single(TypoLocalDateTime.put).toOpt), + a => List(a.id, a.name, a.addressId, a.createdAt) + ) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonRowUnsaved.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonRowUnsaved.scala new file mode 100644 index 0000000000..82a25ff251 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/person/PersonRowUnsaved.scala @@ -0,0 +1,53 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.address.AddressId +import doobie.postgres.Text +import io.circe.Decoder +import io.circe.Encoder + +/** This class corresponds to a row in table `frontpage.person` which has not been persisted yet */ +case class PersonRowUnsaved( + name: String, + /** Points to [[address.AddressRow.id]] */ + addressId: Option[AddressId], + /** Default: gen_random_uuid() */ + id: Defaulted[PersonId] = Defaulted.UseDefault, + /** Default: now() */ + createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault +) { + def toRow(idDefault: => PersonId, createdAtDefault: => Option[TypoLocalDateTime]): PersonRow = + PersonRow( + name = name, + addressId = addressId, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + }, + createdAt = createdAt match { + case Defaulted.UseDefault => createdAtDefault + case Defaulted.Provided(value) => value + } + ) +} +object PersonRowUnsaved { + implicit lazy val decoder: Decoder[PersonRowUnsaved] = Decoder.forProduct4[PersonRowUnsaved, String, Option[AddressId], Defaulted[PersonId], Defaulted[Option[TypoLocalDateTime]]]("name", "address_id", "id", "created_at")(PersonRowUnsaved.apply)(Decoder.decodeString, Decoder.decodeOption(AddressId.decoder), Defaulted.decoder(PersonId.decoder), Defaulted.decoder(Decoder.decodeOption(TypoLocalDateTime.decoder))) + implicit lazy val encoder: Encoder[PersonRowUnsaved] = Encoder.forProduct4[PersonRowUnsaved, String, Option[AddressId], Defaulted[PersonId], Defaulted[Option[TypoLocalDateTime]]]("name", "address_id", "id", "created_at")(x => (x.name, x.addressId, x.id, x.createdAt))(Encoder.encodeString, Encoder.encodeOption(AddressId.encoder), Defaulted.encoder(PersonId.encoder), Defaulted.encoder(Encoder.encodeOption(TypoLocalDateTime.encoder))) + implicit lazy val text: Text[PersonRowUnsaved] = Text.instance[PersonRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(AddressId.text).unsafeEncode(row.addressId, sb) + sb.append(Text.DELIMETER) + Defaulted.text(PersonId.text).unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(TypoLocalDateTime.text)).unsafeEncode(row.createdAt, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductFields.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductFields.scala new file mode 100644 index 0000000000..d2c7d735b1 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductFields.scala @@ -0,0 +1,61 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoLocalDateTime +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait ProductFields { + def id: IdField[ProductId, ProductRow] + def name: Field[String, ProductRow] + def price: Field[BigDecimal, ProductRow] + def inStock: OptField[Boolean, ProductRow] + def quantity: OptField[Int, ProductRow] + def lastRestocked: OptField[TypoLocalDateTime, ProductRow] + def lastModified: OptField[TypoLocalDateTime, ProductRow] + def tags: OptField[Array[String], ProductRow] + def categories: OptField[Array[Int], ProductRow] + def prices: OptField[Array[BigDecimal], ProductRow] + def attributes: OptField[Array[TypoJsonb], ProductRow] +} + +object ProductFields { + lazy val structure: Relation[ProductFields, ProductRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[ProductFields, ProductRow] { + + override lazy val fields: ProductFields = new ProductFields { + override def id = IdField[ProductId, ProductRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, ProductRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + override def price = Field[BigDecimal, ProductRow](_path, "price", None, Some("numeric"), x => x.price, (row, value) => row.copy(price = value)) + override def inStock = OptField[Boolean, ProductRow](_path, "in_stock", None, None, x => x.inStock, (row, value) => row.copy(inStock = value)) + override def quantity = OptField[Int, ProductRow](_path, "quantity", None, Some("int4"), x => x.quantity, (row, value) => row.copy(quantity = value)) + override def lastRestocked = OptField[TypoLocalDateTime, ProductRow](_path, "last_restocked", Some("text"), Some("timestamp"), x => x.lastRestocked, (row, value) => row.copy(lastRestocked = value)) + override def lastModified = OptField[TypoLocalDateTime, ProductRow](_path, "last_modified", Some("text"), Some("timestamp"), x => x.lastModified, (row, value) => row.copy(lastModified = value)) + override def tags = OptField[Array[String], ProductRow](_path, "tags", None, Some("text[]"), x => x.tags, (row, value) => row.copy(tags = value)) + override def categories = OptField[Array[Int], ProductRow](_path, "categories", None, Some("int4[]"), x => x.categories, (row, value) => row.copy(categories = value)) + override def prices = OptField[Array[BigDecimal], ProductRow](_path, "prices", None, Some("numeric[]"), x => x.prices, (row, value) => row.copy(prices = value)) + override def attributes = OptField[Array[TypoJsonb], ProductRow](_path, "attributes", None, Some("jsonb[]"), x => x.attributes, (row, value) => row.copy(attributes = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, ProductRow]] = + List[FieldLikeNoHkt[?, ProductRow]](fields.id, fields.name, fields.price, fields.inStock, fields.quantity, fields.lastRestocked, fields.lastModified, fields.tags, fields.categories, fields.prices, fields.attributes) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductId.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductId.scala new file mode 100644 index 0000000000..9e8147e063 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductId.scala @@ -0,0 +1,33 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import adventureworks.customtypes.TypoUUID +import doobie.postgres.Text +import doobie.util.Get +import doobie.util.Put +import io.circe.Decoder +import io.circe.Encoder +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.product` */ +case class ProductId(value: TypoUUID) extends AnyVal +object ProductId { + implicit lazy val arrayGet: Get[Array[ProductId]] = TypoUUID.arrayGet.map(_.map(ProductId.apply)) + implicit lazy val arrayPut: Put[Array[ProductId]] = TypoUUID.arrayPut.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[ProductId, TypoUUID] = Bijection[ProductId, TypoUUID](_.value)(ProductId.apply) + implicit lazy val decoder: Decoder[ProductId] = TypoUUID.decoder.map(ProductId.apply) + implicit lazy val encoder: Encoder[ProductId] = TypoUUID.encoder.contramap(_.value) + implicit lazy val get: Get[ProductId] = TypoUUID.get.map(ProductId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[ProductId] = Ordering.by(_.value) + implicit lazy val put: Put[ProductId] = TypoUUID.put.contramap(_.value) + implicit lazy val text: Text[ProductId] = new Text[ProductId] { + override def unsafeEncode(v: ProductId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: ProductId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductRepo.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductRepo.scala new file mode 100644 index 0000000000..da1a424630 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductRepo.scala @@ -0,0 +1,36 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import doobie.free.connection.ConnectionIO +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait ProductRepo { + def delete: DeleteBuilder[ProductFields, ProductRow] + def deleteById(id: ProductId): ConnectionIO[Boolean] + def deleteByIds(ids: Array[ProductId]): ConnectionIO[Int] + def insert(unsaved: ProductRow): ConnectionIO[ProductRow] + def insert(unsaved: ProductRowUnsaved): ConnectionIO[ProductRow] + def insertStreaming(unsaved: Stream[ConnectionIO, ProductRow], batchSize: Int = 10000): ConnectionIO[Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, ProductRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] + def select: SelectBuilder[ProductFields, ProductRow] + def selectAll: Stream[ConnectionIO, ProductRow] + def selectById(id: ProductId): ConnectionIO[Option[ProductRow]] + def selectByIds(ids: Array[ProductId]): Stream[ConnectionIO, ProductRow] + def selectByIdsTracked(ids: Array[ProductId]): ConnectionIO[Map[ProductId, ProductRow]] + def update: UpdateBuilder[ProductFields, ProductRow] + def update(row: ProductRow): ConnectionIO[Boolean] + def upsert(unsaved: ProductRow): ConnectionIO[ProductRow] + def upsertBatch(unsaved: List[ProductRow]): Stream[ConnectionIO, ProductRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Stream[ConnectionIO, ProductRow], batchSize: Int = 10000): ConnectionIO[Int] +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductRepoImpl.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductRepoImpl.scala new file mode 100644 index 0000000000..fe5997ebf3 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductRepoImpl.scala @@ -0,0 +1,216 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoLocalDateTime +import cats.instances.list.catsStdInstancesForList +import doobie.free.connection.ConnectionIO +import doobie.postgres.syntax.FragmentOps +import doobie.syntax.SqlInterpolator.SingleFragment.fromWrite +import doobie.syntax.string.toSqlInterpolator +import doobie.util.Write +import doobie.util.fragment.Fragment +import doobie.util.meta.Meta +import doobie.util.update.Update +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class ProductRepoImpl extends ProductRepo { + override def delete: DeleteBuilder[ProductFields, ProductRow] = { + DeleteBuilder(""""frontpage"."product"""", ProductFields.structure) + } + override def deleteById(id: ProductId): ConnectionIO[Boolean] = { + sql"""delete from "frontpage"."product" where "id" = ${fromWrite(id)(new Write.Single(ProductId.put))}""".update.run.map(_ > 0) + } + override def deleteByIds(ids: Array[ProductId]): ConnectionIO[Int] = { + sql"""delete from "frontpage"."product" where "id" = ANY(${ids})""".update.run + } + override def insert(unsaved: ProductRow): ConnectionIO[ProductRow] = { + sql"""insert into "frontpage"."product"("id", "name", "price", "in_stock", "quantity", "last_restocked", "last_modified", "tags", "categories", "prices", "attributes") + values (${fromWrite(unsaved.id)(new Write.Single(ProductId.put))}::uuid, ${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}, ${fromWrite(unsaved.price)(new Write.Single(Meta.ScalaBigDecimalMeta.put))}::numeric, ${fromWrite(unsaved.inStock)(new Write.SingleOpt(Meta.BooleanMeta.put))}, ${fromWrite(unsaved.quantity)(new Write.SingleOpt(Meta.IntMeta.put))}::int4, ${fromWrite(unsaved.lastRestocked)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp, ${fromWrite(unsaved.lastModified)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp, ${fromWrite(unsaved.tags)(new Write.SingleOpt(adventureworks.StringArrayMeta.put))}::text[], ${fromWrite(unsaved.categories)(new Write.SingleOpt(adventureworks.IntegerArrayMeta.put))}::int4[], ${fromWrite(unsaved.prices)(new Write.SingleOpt(adventureworks.BigDecimalMeta.put))}::numeric[], ${fromWrite(unsaved.attributes)(new Write.SingleOpt(TypoJsonb.arrayPut))}::jsonb[]) + returning "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes" + """.query(using ProductRow.read).unique + } + override def insert(unsaved: ProductRowUnsaved): ConnectionIO[ProductRow] = { + val fs = List( + Some((Fragment.const0(s""""name""""), fr"${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}")), + Some((Fragment.const0(s""""price""""), fr"${fromWrite(unsaved.price)(new Write.Single(Meta.ScalaBigDecimalMeta.put))}::numeric")), + Some((Fragment.const0(s""""last_restocked""""), fr"${fromWrite(unsaved.lastRestocked)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""id""""), fr"${fromWrite(value: ProductId)(new Write.Single(ProductId.put))}::uuid")) + }, + unsaved.inStock match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""in_stock""""), fr"${fromWrite(value: Option[Boolean])(new Write.SingleOpt(Meta.BooleanMeta.put))}")) + }, + unsaved.quantity match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""quantity""""), fr"${fromWrite(value: Option[Int])(new Write.SingleOpt(Meta.IntMeta.put))}::int4")) + }, + unsaved.lastModified match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""last_modified""""), fr"${fromWrite(value: Option[TypoLocalDateTime])(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp")) + }, + unsaved.tags match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""tags""""), fr"${fromWrite(value: Option[Array[String]])(new Write.SingleOpt(adventureworks.StringArrayMeta.put))}::text[]")) + }, + unsaved.categories match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""categories""""), fr"${fromWrite(value: Option[Array[Int]])(new Write.SingleOpt(adventureworks.IntegerArrayMeta.put))}::int4[]")) + }, + unsaved.prices match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""prices""""), fr"${fromWrite(value: Option[Array[BigDecimal]])(new Write.SingleOpt(adventureworks.BigDecimalMeta.put))}::numeric[]")) + }, + unsaved.attributes match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""attributes""""), fr"${fromWrite(value: Option[Array[TypoJsonb]])(new Write.SingleOpt(TypoJsonb.arrayPut))}::jsonb[]")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."product" default values + returning "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes" + """ + } else { + val CommaSeparate = Fragment.FragmentMonoid.intercalate(fr", ") + sql"""insert into "frontpage"."product"(${CommaSeparate.combineAllOption(fs.map { case (n, _) => n }).get}) + values (${CommaSeparate.combineAllOption(fs.map { case (_, f) => f }).get}) + returning "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes" + """ + } + q.query(using ProductRow.read).unique + + } + override def insertStreaming(unsaved: Stream[ConnectionIO, ProductRow], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."product"("id", "name", "price", "in_stock", "quantity", "last_restocked", "last_modified", "tags", "categories", "prices", "attributes") FROM STDIN""").copyIn(unsaved, batchSize)(using ProductRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, ProductRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."product"("name", "price", "last_restocked", "id", "in_stock", "quantity", "last_modified", "tags", "categories", "prices", "attributes") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""").copyIn(unsaved, batchSize)(using ProductRowUnsaved.text) + } + override def select: SelectBuilder[ProductFields, ProductRow] = { + SelectBuilderSql(""""frontpage"."product"""", ProductFields.structure, ProductRow.read) + } + override def selectAll: Stream[ConnectionIO, ProductRow] = { + sql"""select "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes" from "frontpage"."product"""".query(using ProductRow.read).stream + } + override def selectById(id: ProductId): ConnectionIO[Option[ProductRow]] = { + sql"""select "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes" from "frontpage"."product" where "id" = ${fromWrite(id)(new Write.Single(ProductId.put))}""".query(using ProductRow.read).option + } + override def selectByIds(ids: Array[ProductId]): Stream[ConnectionIO, ProductRow] = { + sql"""select "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes" from "frontpage"."product" where "id" = ANY(${ids})""".query(using ProductRow.read).stream + } + override def selectByIdsTracked(ids: Array[ProductId]): ConnectionIO[Map[ProductId, ProductRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[ProductFields, ProductRow] = { + UpdateBuilder(""""frontpage"."product"""", ProductFields.structure, ProductRow.read) + } + override def update(row: ProductRow): ConnectionIO[Boolean] = { + val id = row.id + sql"""update "frontpage"."product" + set "name" = ${fromWrite(row.name)(new Write.Single(Meta.StringMeta.put))}, + "price" = ${fromWrite(row.price)(new Write.Single(Meta.ScalaBigDecimalMeta.put))}::numeric, + "in_stock" = ${fromWrite(row.inStock)(new Write.SingleOpt(Meta.BooleanMeta.put))}, + "quantity" = ${fromWrite(row.quantity)(new Write.SingleOpt(Meta.IntMeta.put))}::int4, + "last_restocked" = ${fromWrite(row.lastRestocked)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp, + "last_modified" = ${fromWrite(row.lastModified)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp, + "tags" = ${fromWrite(row.tags)(new Write.SingleOpt(adventureworks.StringArrayMeta.put))}::text[], + "categories" = ${fromWrite(row.categories)(new Write.SingleOpt(adventureworks.IntegerArrayMeta.put))}::int4[], + "prices" = ${fromWrite(row.prices)(new Write.SingleOpt(adventureworks.BigDecimalMeta.put))}::numeric[], + "attributes" = ${fromWrite(row.attributes)(new Write.SingleOpt(TypoJsonb.arrayPut))}::jsonb[] + where "id" = ${fromWrite(id)(new Write.Single(ProductId.put))}""" + .update + .run + .map(_ > 0) + } + override def upsert(unsaved: ProductRow): ConnectionIO[ProductRow] = { + sql"""insert into "frontpage"."product"("id", "name", "price", "in_stock", "quantity", "last_restocked", "last_modified", "tags", "categories", "prices", "attributes") + values ( + ${fromWrite(unsaved.id)(new Write.Single(ProductId.put))}::uuid, + ${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}, + ${fromWrite(unsaved.price)(new Write.Single(Meta.ScalaBigDecimalMeta.put))}::numeric, + ${fromWrite(unsaved.inStock)(new Write.SingleOpt(Meta.BooleanMeta.put))}, + ${fromWrite(unsaved.quantity)(new Write.SingleOpt(Meta.IntMeta.put))}::int4, + ${fromWrite(unsaved.lastRestocked)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp, + ${fromWrite(unsaved.lastModified)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp, + ${fromWrite(unsaved.tags)(new Write.SingleOpt(adventureworks.StringArrayMeta.put))}::text[], + ${fromWrite(unsaved.categories)(new Write.SingleOpt(adventureworks.IntegerArrayMeta.put))}::int4[], + ${fromWrite(unsaved.prices)(new Write.SingleOpt(adventureworks.BigDecimalMeta.put))}::numeric[], + ${fromWrite(unsaved.attributes)(new Write.SingleOpt(TypoJsonb.arrayPut))}::jsonb[] + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "price" = EXCLUDED."price", + "in_stock" = EXCLUDED."in_stock", + "quantity" = EXCLUDED."quantity", + "last_restocked" = EXCLUDED."last_restocked", + "last_modified" = EXCLUDED."last_modified", + "tags" = EXCLUDED."tags", + "categories" = EXCLUDED."categories", + "prices" = EXCLUDED."prices", + "attributes" = EXCLUDED."attributes" + returning "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes" + """.query(using ProductRow.read).unique + } + override def upsertBatch(unsaved: List[ProductRow]): Stream[ConnectionIO, ProductRow] = { + Update[ProductRow]( + s"""insert into "frontpage"."product"("id", "name", "price", "in_stock", "quantity", "last_restocked", "last_modified", "tags", "categories", "prices", "attributes") + values (?::uuid,?,?::numeric,?,?::int4,?::timestamp,?::timestamp,?::text[],?::int4[],?::numeric[],?::jsonb[]) + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "price" = EXCLUDED."price", + "in_stock" = EXCLUDED."in_stock", + "quantity" = EXCLUDED."quantity", + "last_restocked" = EXCLUDED."last_restocked", + "last_modified" = EXCLUDED."last_modified", + "tags" = EXCLUDED."tags", + "categories" = EXCLUDED."categories", + "prices" = EXCLUDED."prices", + "attributes" = EXCLUDED."attributes" + returning "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes"""" + )(using ProductRow.write) + .updateManyWithGeneratedKeys[ProductRow]("id", "name", "price", "in_stock", "quantity", "last_restocked", "last_modified", "tags", "categories", "prices", "attributes")(unsaved)(using catsStdInstancesForList, ProductRow.read) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, ProductRow], batchSize: Int = 10000): ConnectionIO[Int] = { + for { + _ <- sql"""create temporary table product_TEMP (like "frontpage"."product") on commit drop""".update.run + _ <- new FragmentOps(sql"""copy product_TEMP("id", "name", "price", "in_stock", "quantity", "last_restocked", "last_modified", "tags", "categories", "prices", "attributes") from stdin""").copyIn(unsaved, batchSize)(using ProductRow.text) + res <- sql"""insert into "frontpage"."product"("id", "name", "price", "in_stock", "quantity", "last_restocked", "last_modified", "tags", "categories", "prices", "attributes") + select * from product_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "price" = EXCLUDED."price", + "in_stock" = EXCLUDED."in_stock", + "quantity" = EXCLUDED."quantity", + "last_restocked" = EXCLUDED."last_restocked", + "last_modified" = EXCLUDED."last_modified", + "tags" = EXCLUDED."tags", + "categories" = EXCLUDED."categories", + "prices" = EXCLUDED."prices", + "attributes" = EXCLUDED."attributes" + ; + drop table product_TEMP;""".update.run + } yield res + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductRepoMock.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductRepoMock.scala new file mode 100644 index 0000000000..122195f48a --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductRepoMock.scala @@ -0,0 +1,127 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import doobie.free.connection.ConnectionIO +import doobie.free.connection.delay +import fs2.Stream +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class ProductRepoMock(toRow: Function1[ProductRowUnsaved, ProductRow], + map: scala.collection.mutable.Map[ProductId, ProductRow] = scala.collection.mutable.Map.empty) extends ProductRepo { + override def delete: DeleteBuilder[ProductFields, ProductRow] = { + DeleteBuilderMock(DeleteParams.empty, ProductFields.structure, map) + } + override def deleteById(id: ProductId): ConnectionIO[Boolean] = { + delay(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[ProductId]): ConnectionIO[Int] = { + delay(ids.map(id => map.remove(id)).count(_.isDefined)) + } + override def insert(unsaved: ProductRow): ConnectionIO[ProductRow] = { + delay { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: ProductRowUnsaved): ConnectionIO[ProductRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Stream[ConnectionIO, ProductRow], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { rows => + var num = 0L + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, ProductRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { unsavedRows => + var num = 0L + unsavedRows.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + num += 1 + } + num + } + } + override def select: SelectBuilder[ProductFields, ProductRow] = { + SelectBuilderMock(ProductFields.structure, delay(map.values.toList), SelectParams.empty) + } + override def selectAll: Stream[ConnectionIO, ProductRow] = { + Stream.emits(map.values.toList) + } + override def selectById(id: ProductId): ConnectionIO[Option[ProductRow]] = { + delay(map.get(id)) + } + override def selectByIds(ids: Array[ProductId]): Stream[ConnectionIO, ProductRow] = { + Stream.emits(ids.flatMap(map.get).toList) + } + override def selectByIdsTracked(ids: Array[ProductId]): ConnectionIO[Map[ProductId, ProductRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[ProductFields, ProductRow] = { + UpdateBuilderMock(UpdateParams.empty, ProductFields.structure, map) + } + override def update(row: ProductRow): ConnectionIO[Boolean] = { + delay { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: ProductRow): ConnectionIO[ProductRow] = { + delay { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + } + override def upsertBatch(unsaved: List[ProductRow]): Stream[ConnectionIO, ProductRow] = { + Stream.emits { + unsaved.map { row => + map += (row.id -> row) + row + } + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, ProductRow], batchSize: Int = 10000): ConnectionIO[Int] = { + unsaved.compile.toList.map { rows => + var num = 0 + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductRow.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductRow.scala new file mode 100644 index 0000000000..08ec64e560 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductRow.scala @@ -0,0 +1,114 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoLocalDateTime +import doobie.postgres.Text +import doobie.util.Read +import doobie.util.Write +import doobie.util.meta.Meta +import io.circe.Decoder +import io.circe.Encoder + +/** Table: frontpage.product + Primary key: id */ +case class ProductRow( + /** Default: gen_random_uuid() */ + id: ProductId, + name: String, + price: BigDecimal, + /** Default: true */ + inStock: Option[Boolean], + /** Default: 0 */ + quantity: Option[Int], + lastRestocked: Option[TypoLocalDateTime], + /** Default: now() */ + lastModified: Option[TypoLocalDateTime], + /** Default: '{}'::text[] */ + tags: Option[Array[String]], + /** Default: '{}'::integer[] */ + categories: Option[Array[Int]], + /** Default: '{}'::numeric[] */ + prices: Option[Array[BigDecimal]], + /** Default: '{}'::jsonb[] */ + attributes: Option[Array[TypoJsonb]] +){ + def toUnsavedRow(id: Defaulted[ProductId], inStock: Defaulted[Option[Boolean]] = Defaulted.Provided(this.inStock), quantity: Defaulted[Option[Int]] = Defaulted.Provided(this.quantity), lastModified: Defaulted[Option[TypoLocalDateTime]] = Defaulted.Provided(this.lastModified), tags: Defaulted[Option[Array[String]]] = Defaulted.Provided(this.tags), categories: Defaulted[Option[Array[Int]]] = Defaulted.Provided(this.categories), prices: Defaulted[Option[Array[BigDecimal]]] = Defaulted.Provided(this.prices), attributes: Defaulted[Option[Array[TypoJsonb]]] = Defaulted.Provided(this.attributes)): ProductRowUnsaved = + ProductRowUnsaved(name, price, lastRestocked, id, inStock, quantity, lastModified, tags, categories, prices, attributes) + } + +object ProductRow { + implicit lazy val decoder: Decoder[ProductRow] = Decoder.forProduct11[ProductRow, ProductId, String, BigDecimal, Option[Boolean], Option[Int], Option[TypoLocalDateTime], Option[TypoLocalDateTime], Option[Array[String]], Option[Array[Int]], Option[Array[BigDecimal]], Option[Array[TypoJsonb]]]("id", "name", "price", "in_stock", "quantity", "last_restocked", "last_modified", "tags", "categories", "prices", "attributes")(ProductRow.apply)(ProductId.decoder, Decoder.decodeString, Decoder.decodeBigDecimal, Decoder.decodeOption(Decoder.decodeBoolean), Decoder.decodeOption(Decoder.decodeInt), Decoder.decodeOption(TypoLocalDateTime.decoder), Decoder.decodeOption(TypoLocalDateTime.decoder), Decoder.decodeOption(Decoder.decodeArray[String](Decoder.decodeString, implicitly)), Decoder.decodeOption(Decoder.decodeArray[Int](Decoder.decodeInt, implicitly)), Decoder.decodeOption(Decoder.decodeArray[BigDecimal](Decoder.decodeBigDecimal, implicitly)), Decoder.decodeOption(Decoder.decodeArray[TypoJsonb](TypoJsonb.decoder, implicitly))) + implicit lazy val encoder: Encoder[ProductRow] = Encoder.forProduct11[ProductRow, ProductId, String, BigDecimal, Option[Boolean], Option[Int], Option[TypoLocalDateTime], Option[TypoLocalDateTime], Option[Array[String]], Option[Array[Int]], Option[Array[BigDecimal]], Option[Array[TypoJsonb]]]("id", "name", "price", "in_stock", "quantity", "last_restocked", "last_modified", "tags", "categories", "prices", "attributes")(x => (x.id, x.name, x.price, x.inStock, x.quantity, x.lastRestocked, x.lastModified, x.tags, x.categories, x.prices, x.attributes))(ProductId.encoder, Encoder.encodeString, Encoder.encodeBigDecimal, Encoder.encodeOption(Encoder.encodeBoolean), Encoder.encodeOption(Encoder.encodeInt), Encoder.encodeOption(TypoLocalDateTime.encoder), Encoder.encodeOption(TypoLocalDateTime.encoder), Encoder.encodeOption(Encoder.encodeIterable[String, Array](Encoder.encodeString, implicitly)), Encoder.encodeOption(Encoder.encodeIterable[Int, Array](Encoder.encodeInt, implicitly)), Encoder.encodeOption(Encoder.encodeIterable[BigDecimal, Array](Encoder.encodeBigDecimal, implicitly)), Encoder.encodeOption(Encoder.encodeIterable[TypoJsonb, Array](TypoJsonb.encoder, implicitly))) + implicit lazy val read: Read[ProductRow] = new Read.CompositeOfInstances(Array( + new Read.Single(ProductId.get).asInstanceOf[Read[Any]], + new Read.Single(Meta.StringMeta.get).asInstanceOf[Read[Any]], + new Read.Single(Meta.ScalaBigDecimalMeta.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(Meta.BooleanMeta.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(Meta.IntMeta.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(TypoLocalDateTime.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(TypoLocalDateTime.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(adventureworks.StringArrayMeta.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(adventureworks.IntegerArrayMeta.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(adventureworks.BigDecimalMeta.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(TypoJsonb.arrayGet).asInstanceOf[Read[Any]] + ))(using scala.reflect.ClassTag.Any).map { arr => + ProductRow( + id = arr(0).asInstanceOf[ProductId], + name = arr(1).asInstanceOf[String], + price = arr(2).asInstanceOf[BigDecimal], + inStock = arr(3).asInstanceOf[Option[Boolean]], + quantity = arr(4).asInstanceOf[Option[Int]], + lastRestocked = arr(5).asInstanceOf[Option[TypoLocalDateTime]], + lastModified = arr(6).asInstanceOf[Option[TypoLocalDateTime]], + tags = arr(7).asInstanceOf[Option[Array[String]]], + categories = arr(8).asInstanceOf[Option[Array[Int]]], + prices = arr(9).asInstanceOf[Option[Array[BigDecimal]]], + attributes = arr(10).asInstanceOf[Option[Array[TypoJsonb]]] + ) + } + implicit lazy val text: Text[ProductRow] = Text.instance[ProductRow]{ (row, sb) => + ProductId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.bigDecimalInstance.unsafeEncode(row.price, sb) + sb.append(Text.DELIMETER) + Text.option(Text.booleanInstance).unsafeEncode(row.inStock, sb) + sb.append(Text.DELIMETER) + Text.option(Text.intInstance).unsafeEncode(row.quantity, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.lastRestocked, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.lastModified, sb) + sb.append(Text.DELIMETER) + Text.option(Text[Array[String]]).unsafeEncode(row.tags, sb) + sb.append(Text.DELIMETER) + Text.option(Text[Array[Int]]).unsafeEncode(row.categories, sb) + sb.append(Text.DELIMETER) + Text.option(Text[Array[BigDecimal]]).unsafeEncode(row.prices, sb) + sb.append(Text.DELIMETER) + Text.option(Text.iterableInstance[Array, TypoJsonb](TypoJsonb.text, implicitly)).unsafeEncode(row.attributes, sb) + } + implicit lazy val write: Write[ProductRow] = new Write.Composite[ProductRow]( + List(new Write.Single(ProductId.put), + new Write.Single(Meta.StringMeta.put), + new Write.Single(Meta.ScalaBigDecimalMeta.put), + new Write.Single(Meta.BooleanMeta.put).toOpt, + new Write.Single(Meta.IntMeta.put).toOpt, + new Write.Single(TypoLocalDateTime.put).toOpt, + new Write.Single(TypoLocalDateTime.put).toOpt, + new Write.Single(adventureworks.StringArrayMeta.put).toOpt, + new Write.Single(adventureworks.IntegerArrayMeta.put).toOpt, + new Write.Single(adventureworks.BigDecimalMeta.put).toOpt, + new Write.Single(TypoJsonb.arrayPut).toOpt), + a => List(a.id, a.name, a.price, a.inStock, a.quantity, a.lastRestocked, a.lastModified, a.tags, a.categories, a.prices, a.attributes) + ) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductRowUnsaved.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductRowUnsaved.scala new file mode 100644 index 0000000000..6381477336 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product/ProductRowUnsaved.scala @@ -0,0 +1,104 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoLocalDateTime +import doobie.postgres.Text +import io.circe.Decoder +import io.circe.Encoder + +/** This class corresponds to a row in table `frontpage.product` which has not been persisted yet */ +case class ProductRowUnsaved( + name: String, + price: BigDecimal, + lastRestocked: Option[TypoLocalDateTime], + /** Default: gen_random_uuid() */ + id: Defaulted[ProductId] = Defaulted.UseDefault, + /** Default: true */ + inStock: Defaulted[Option[Boolean]] = Defaulted.UseDefault, + /** Default: 0 */ + quantity: Defaulted[Option[Int]] = Defaulted.UseDefault, + /** Default: now() */ + lastModified: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault, + /** Default: '{}'::text[] */ + tags: Defaulted[Option[Array[String]]] = Defaulted.UseDefault, + /** Default: '{}'::integer[] */ + categories: Defaulted[Option[Array[Int]]] = Defaulted.UseDefault, + /** Default: '{}'::numeric[] */ + prices: Defaulted[Option[Array[BigDecimal]]] = Defaulted.UseDefault, + /** Default: '{}'::jsonb[] */ + attributes: Defaulted[Option[Array[TypoJsonb]]] = Defaulted.UseDefault +) { + def toRow(idDefault: => ProductId, inStockDefault: => Option[Boolean], quantityDefault: => Option[Int], lastModifiedDefault: => Option[TypoLocalDateTime], tagsDefault: => Option[Array[String]], categoriesDefault: => Option[Array[Int]], pricesDefault: => Option[Array[BigDecimal]], attributesDefault: => Option[Array[TypoJsonb]]): ProductRow = + ProductRow( + name = name, + price = price, + lastRestocked = lastRestocked, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + }, + inStock = inStock match { + case Defaulted.UseDefault => inStockDefault + case Defaulted.Provided(value) => value + }, + quantity = quantity match { + case Defaulted.UseDefault => quantityDefault + case Defaulted.Provided(value) => value + }, + lastModified = lastModified match { + case Defaulted.UseDefault => lastModifiedDefault + case Defaulted.Provided(value) => value + }, + tags = tags match { + case Defaulted.UseDefault => tagsDefault + case Defaulted.Provided(value) => value + }, + categories = categories match { + case Defaulted.UseDefault => categoriesDefault + case Defaulted.Provided(value) => value + }, + prices = prices match { + case Defaulted.UseDefault => pricesDefault + case Defaulted.Provided(value) => value + }, + attributes = attributes match { + case Defaulted.UseDefault => attributesDefault + case Defaulted.Provided(value) => value + } + ) +} +object ProductRowUnsaved { + implicit lazy val decoder: Decoder[ProductRowUnsaved] = Decoder.forProduct11[ProductRowUnsaved, String, BigDecimal, Option[TypoLocalDateTime], Defaulted[ProductId], Defaulted[Option[Boolean]], Defaulted[Option[Int]], Defaulted[Option[TypoLocalDateTime]], Defaulted[Option[Array[String]]], Defaulted[Option[Array[Int]]], Defaulted[Option[Array[BigDecimal]]], Defaulted[Option[Array[TypoJsonb]]]]("name", "price", "last_restocked", "id", "in_stock", "quantity", "last_modified", "tags", "categories", "prices", "attributes")(ProductRowUnsaved.apply)(Decoder.decodeString, Decoder.decodeBigDecimal, Decoder.decodeOption(TypoLocalDateTime.decoder), Defaulted.decoder(ProductId.decoder), Defaulted.decoder(Decoder.decodeOption(Decoder.decodeBoolean)), Defaulted.decoder(Decoder.decodeOption(Decoder.decodeInt)), Defaulted.decoder(Decoder.decodeOption(TypoLocalDateTime.decoder)), Defaulted.decoder(Decoder.decodeOption(Decoder.decodeArray[String](Decoder.decodeString, implicitly))), Defaulted.decoder(Decoder.decodeOption(Decoder.decodeArray[Int](Decoder.decodeInt, implicitly))), Defaulted.decoder(Decoder.decodeOption(Decoder.decodeArray[BigDecimal](Decoder.decodeBigDecimal, implicitly))), Defaulted.decoder(Decoder.decodeOption(Decoder.decodeArray[TypoJsonb](TypoJsonb.decoder, implicitly)))) + implicit lazy val encoder: Encoder[ProductRowUnsaved] = Encoder.forProduct11[ProductRowUnsaved, String, BigDecimal, Option[TypoLocalDateTime], Defaulted[ProductId], Defaulted[Option[Boolean]], Defaulted[Option[Int]], Defaulted[Option[TypoLocalDateTime]], Defaulted[Option[Array[String]]], Defaulted[Option[Array[Int]]], Defaulted[Option[Array[BigDecimal]]], Defaulted[Option[Array[TypoJsonb]]]]("name", "price", "last_restocked", "id", "in_stock", "quantity", "last_modified", "tags", "categories", "prices", "attributes")(x => (x.name, x.price, x.lastRestocked, x.id, x.inStock, x.quantity, x.lastModified, x.tags, x.categories, x.prices, x.attributes))(Encoder.encodeString, Encoder.encodeBigDecimal, Encoder.encodeOption(TypoLocalDateTime.encoder), Defaulted.encoder(ProductId.encoder), Defaulted.encoder(Encoder.encodeOption(Encoder.encodeBoolean)), Defaulted.encoder(Encoder.encodeOption(Encoder.encodeInt)), Defaulted.encoder(Encoder.encodeOption(TypoLocalDateTime.encoder)), Defaulted.encoder(Encoder.encodeOption(Encoder.encodeIterable[String, Array](Encoder.encodeString, implicitly))), Defaulted.encoder(Encoder.encodeOption(Encoder.encodeIterable[Int, Array](Encoder.encodeInt, implicitly))), Defaulted.encoder(Encoder.encodeOption(Encoder.encodeIterable[BigDecimal, Array](Encoder.encodeBigDecimal, implicitly))), Defaulted.encoder(Encoder.encodeOption(Encoder.encodeIterable[TypoJsonb, Array](TypoJsonb.encoder, implicitly)))) + implicit lazy val text: Text[ProductRowUnsaved] = Text.instance[ProductRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.bigDecimalInstance.unsafeEncode(row.price, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.lastRestocked, sb) + sb.append(Text.DELIMETER) + Defaulted.text(ProductId.text).unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text.booleanInstance)).unsafeEncode(row.inStock, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text.intInstance)).unsafeEncode(row.quantity, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(TypoLocalDateTime.text)).unsafeEncode(row.lastModified, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text[Array[String]])).unsafeEncode(row.tags, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text[Array[Int]])).unsafeEncode(row.categories, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text[Array[BigDecimal]])).unsafeEncode(row.prices, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text.iterableInstance[Array, TypoJsonb](TypoJsonb.text, implicitly))).unsafeEncode(row.attributes, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryFields.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryFields.scala new file mode 100644 index 0000000000..bfbb0997fc --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryFields.scala @@ -0,0 +1,61 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product_category + +import adventureworks.frontpage.category.CategoryFields +import adventureworks.frontpage.category.CategoryId +import adventureworks.frontpage.category.CategoryRow +import adventureworks.frontpage.product.ProductFields +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.product.ProductRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.Required +import typo.dsl.SqlExpr +import typo.dsl.SqlExpr.CompositeIn +import typo.dsl.SqlExpr.CompositeIn.TuplePart +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.Structure.Relation + +trait ProductCategoryFields { + def productId: IdField[ProductId, ProductCategoryRow] + def categoryId: IdField[CategoryId, ProductCategoryRow] + def fkCategory: ForeignKey[CategoryFields, CategoryRow] = + ForeignKey[CategoryFields, CategoryRow]("frontpage.product_category_category_id_fkey", Nil) + .withColumnPair(categoryId, _.id) + def fkProduct: ForeignKey[ProductFields, ProductRow] = + ForeignKey[ProductFields, ProductRow]("frontpage.product_category_product_id_fkey", Nil) + .withColumnPair(productId, _.id) + def compositeIdIs(compositeId: ProductCategoryId): SqlExpr[Boolean, Required] = + productId.isEqual(compositeId.productId).and(categoryId.isEqual(compositeId.categoryId)) + def compositeIdIn(compositeIds: Array[ProductCategoryId]): SqlExpr[Boolean, Required] = + new CompositeIn(compositeIds)(TuplePart(productId)(_.productId), TuplePart(categoryId)(_.categoryId)) + +} + +object ProductCategoryFields { + lazy val structure: Relation[ProductCategoryFields, ProductCategoryRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[ProductCategoryFields, ProductCategoryRow] { + + override lazy val fields: ProductCategoryFields = new ProductCategoryFields { + override def productId = IdField[ProductId, ProductCategoryRow](_path, "product_id", None, Some("uuid"), x => x.productId, (row, value) => row.copy(productId = value)) + override def categoryId = IdField[CategoryId, ProductCategoryRow](_path, "category_id", None, Some("uuid"), x => x.categoryId, (row, value) => row.copy(categoryId = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, ProductCategoryRow]] = + List[FieldLikeNoHkt[?, ProductCategoryRow]](fields.productId, fields.categoryId) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryId.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryId.scala new file mode 100644 index 0000000000..9fe68eff41 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryId.scala @@ -0,0 +1,24 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product_category + +import adventureworks.frontpage.category.CategoryId +import adventureworks.frontpage.product.ProductId +import io.circe.Decoder +import io.circe.Encoder + +/** Type for the composite primary key of table `frontpage.product_category` */ +case class ProductCategoryId( + productId: ProductId, + categoryId: CategoryId +) +object ProductCategoryId { + implicit lazy val decoder: Decoder[ProductCategoryId] = Decoder.forProduct2[ProductCategoryId, ProductId, CategoryId]("product_id", "category_id")(ProductCategoryId.apply)(ProductId.decoder, CategoryId.decoder) + implicit lazy val encoder: Encoder[ProductCategoryId] = Encoder.forProduct2[ProductCategoryId, ProductId, CategoryId]("product_id", "category_id")(x => (x.productId, x.categoryId))(ProductId.encoder, CategoryId.encoder) + implicit lazy val ordering: Ordering[ProductCategoryId] = Ordering.by(x => (x.productId, x.categoryId)) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepo.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepo.scala new file mode 100644 index 0000000000..1648f2acb0 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepo.scala @@ -0,0 +1,32 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product_category + +import doobie.free.connection.ConnectionIO +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait ProductCategoryRepo { + def delete: DeleteBuilder[ProductCategoryFields, ProductCategoryRow] + def deleteById(compositeId: ProductCategoryId): ConnectionIO[Boolean] + def deleteByIds(compositeIds: Array[ProductCategoryId]): ConnectionIO[Int] + def insert(unsaved: ProductCategoryRow): ConnectionIO[ProductCategoryRow] + def insertStreaming(unsaved: Stream[ConnectionIO, ProductCategoryRow], batchSize: Int = 10000): ConnectionIO[Long] + def select: SelectBuilder[ProductCategoryFields, ProductCategoryRow] + def selectAll: Stream[ConnectionIO, ProductCategoryRow] + def selectById(compositeId: ProductCategoryId): ConnectionIO[Option[ProductCategoryRow]] + def selectByIds(compositeIds: Array[ProductCategoryId]): Stream[ConnectionIO, ProductCategoryRow] + def selectByIdsTracked(compositeIds: Array[ProductCategoryId]): ConnectionIO[Map[ProductCategoryId, ProductCategoryRow]] + def update: UpdateBuilder[ProductCategoryFields, ProductCategoryRow] + def upsert(unsaved: ProductCategoryRow): ConnectionIO[ProductCategoryRow] + def upsertBatch(unsaved: List[ProductCategoryRow]): Stream[ConnectionIO, ProductCategoryRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Stream[ConnectionIO, ProductCategoryRow], batchSize: Int = 10000): ConnectionIO[Int] +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepoImpl.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepoImpl.scala new file mode 100644 index 0000000000..0743d66a8a --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepoImpl.scala @@ -0,0 +1,113 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product_category + +import adventureworks.frontpage.category.CategoryId +import adventureworks.frontpage.product.ProductId +import cats.instances.list.catsStdInstancesForList +import doobie.free.connection.ConnectionIO +import doobie.postgres.syntax.FragmentOps +import doobie.syntax.SqlInterpolator.SingleFragment.fromWrite +import doobie.syntax.string.toSqlInterpolator +import doobie.util.Write +import doobie.util.update.Update +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class ProductCategoryRepoImpl extends ProductCategoryRepo { + override def delete: DeleteBuilder[ProductCategoryFields, ProductCategoryRow] = { + DeleteBuilder(""""frontpage"."product_category"""", ProductCategoryFields.structure) + } + override def deleteById(compositeId: ProductCategoryId): ConnectionIO[Boolean] = { + sql"""delete from "frontpage"."product_category" where "product_id" = ${fromWrite(compositeId.productId)(new Write.Single(ProductId.put))} AND "category_id" = ${fromWrite(compositeId.categoryId)(new Write.Single(CategoryId.put))}""".update.run.map(_ > 0) + } + override def deleteByIds(compositeIds: Array[ProductCategoryId]): ConnectionIO[Int] = { + val productId = compositeIds.map(_.productId) + val categoryId = compositeIds.map(_.categoryId) + sql"""delete + from "frontpage"."product_category" + where ("product_id", "category_id") + in (select unnest(${productId}), unnest(${categoryId})) + """.update.run + + } + override def insert(unsaved: ProductCategoryRow): ConnectionIO[ProductCategoryRow] = { + sql"""insert into "frontpage"."product_category"("product_id", "category_id") + values (${fromWrite(unsaved.productId)(new Write.Single(ProductId.put))}::uuid, ${fromWrite(unsaved.categoryId)(new Write.Single(CategoryId.put))}::uuid) + returning "product_id", "category_id" + """.query(using ProductCategoryRow.read).unique + } + override def insertStreaming(unsaved: Stream[ConnectionIO, ProductCategoryRow], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."product_category"("product_id", "category_id") FROM STDIN""").copyIn(unsaved, batchSize)(using ProductCategoryRow.text) + } + override def select: SelectBuilder[ProductCategoryFields, ProductCategoryRow] = { + SelectBuilderSql(""""frontpage"."product_category"""", ProductCategoryFields.structure, ProductCategoryRow.read) + } + override def selectAll: Stream[ConnectionIO, ProductCategoryRow] = { + sql"""select "product_id", "category_id" from "frontpage"."product_category"""".query(using ProductCategoryRow.read).stream + } + override def selectById(compositeId: ProductCategoryId): ConnectionIO[Option[ProductCategoryRow]] = { + sql"""select "product_id", "category_id" from "frontpage"."product_category" where "product_id" = ${fromWrite(compositeId.productId)(new Write.Single(ProductId.put))} AND "category_id" = ${fromWrite(compositeId.categoryId)(new Write.Single(CategoryId.put))}""".query(using ProductCategoryRow.read).option + } + override def selectByIds(compositeIds: Array[ProductCategoryId]): Stream[ConnectionIO, ProductCategoryRow] = { + val productId = compositeIds.map(_.productId) + val categoryId = compositeIds.map(_.categoryId) + sql"""select "product_id", "category_id" + from "frontpage"."product_category" + where ("product_id", "category_id") + in (select unnest(${productId}), unnest(${categoryId})) + """.query(using ProductCategoryRow.read).stream + + } + override def selectByIdsTracked(compositeIds: Array[ProductCategoryId]): ConnectionIO[Map[ProductCategoryId, ProductCategoryRow]] = { + selectByIds(compositeIds).compile.toList.map { rows => + val byId = rows.view.map(x => (x.compositeId, x)).toMap + compositeIds.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[ProductCategoryFields, ProductCategoryRow] = { + UpdateBuilder(""""frontpage"."product_category"""", ProductCategoryFields.structure, ProductCategoryRow.read) + } + override def upsert(unsaved: ProductCategoryRow): ConnectionIO[ProductCategoryRow] = { + sql"""insert into "frontpage"."product_category"("product_id", "category_id") + values ( + ${fromWrite(unsaved.productId)(new Write.Single(ProductId.put))}::uuid, + ${fromWrite(unsaved.categoryId)(new Write.Single(CategoryId.put))}::uuid + ) + on conflict ("product_id", "category_id") + do update set "product_id" = EXCLUDED."product_id" + returning "product_id", "category_id" + """.query(using ProductCategoryRow.read).unique + } + override def upsertBatch(unsaved: List[ProductCategoryRow]): Stream[ConnectionIO, ProductCategoryRow] = { + Update[ProductCategoryRow]( + s"""insert into "frontpage"."product_category"("product_id", "category_id") + values (?::uuid,?::uuid) + on conflict ("product_id", "category_id") + do nothing + returning "product_id", "category_id"""" + )(using ProductCategoryRow.write) + .updateManyWithGeneratedKeys[ProductCategoryRow]("product_id", "category_id")(unsaved)(using catsStdInstancesForList, ProductCategoryRow.read) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, ProductCategoryRow], batchSize: Int = 10000): ConnectionIO[Int] = { + for { + _ <- sql"""create temporary table product_category_TEMP (like "frontpage"."product_category") on commit drop""".update.run + _ <- new FragmentOps(sql"""copy product_category_TEMP("product_id", "category_id") from stdin""").copyIn(unsaved, batchSize)(using ProductCategoryRow.text) + res <- sql"""insert into "frontpage"."product_category"("product_id", "category_id") + select * from product_category_TEMP + on conflict ("product_id", "category_id") + do nothing + ; + drop table product_category_TEMP;""".update.run + } yield res + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepoMock.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepoMock.scala new file mode 100644 index 0000000000..6dff577337 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepoMock.scala @@ -0,0 +1,100 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product_category + +import doobie.free.connection.ConnectionIO +import doobie.free.connection.delay +import fs2.Stream +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class ProductCategoryRepoMock(map: scala.collection.mutable.Map[ProductCategoryId, ProductCategoryRow] = scala.collection.mutable.Map.empty) extends ProductCategoryRepo { + override def delete: DeleteBuilder[ProductCategoryFields, ProductCategoryRow] = { + DeleteBuilderMock(DeleteParams.empty, ProductCategoryFields.structure, map) + } + override def deleteById(compositeId: ProductCategoryId): ConnectionIO[Boolean] = { + delay(map.remove(compositeId).isDefined) + } + override def deleteByIds(compositeIds: Array[ProductCategoryId]): ConnectionIO[Int] = { + delay(compositeIds.map(id => map.remove(id)).count(_.isDefined)) + } + override def insert(unsaved: ProductCategoryRow): ConnectionIO[ProductCategoryRow] = { + delay { + val _ = if (map.contains(unsaved.compositeId)) + sys.error(s"id ${unsaved.compositeId} already exists") + else + map.put(unsaved.compositeId, unsaved) + + unsaved + } + } + override def insertStreaming(unsaved: Stream[ConnectionIO, ProductCategoryRow], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { rows => + var num = 0L + rows.foreach { row => + map += (row.compositeId -> row) + num += 1 + } + num + } + } + override def select: SelectBuilder[ProductCategoryFields, ProductCategoryRow] = { + SelectBuilderMock(ProductCategoryFields.structure, delay(map.values.toList), SelectParams.empty) + } + override def selectAll: Stream[ConnectionIO, ProductCategoryRow] = { + Stream.emits(map.values.toList) + } + override def selectById(compositeId: ProductCategoryId): ConnectionIO[Option[ProductCategoryRow]] = { + delay(map.get(compositeId)) + } + override def selectByIds(compositeIds: Array[ProductCategoryId]): Stream[ConnectionIO, ProductCategoryRow] = { + Stream.emits(compositeIds.flatMap(map.get).toList) + } + override def selectByIdsTracked(compositeIds: Array[ProductCategoryId]): ConnectionIO[Map[ProductCategoryId, ProductCategoryRow]] = { + selectByIds(compositeIds).compile.toList.map { rows => + val byId = rows.view.map(x => (x.compositeId, x)).toMap + compositeIds.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[ProductCategoryFields, ProductCategoryRow] = { + UpdateBuilderMock(UpdateParams.empty, ProductCategoryFields.structure, map) + } + override def upsert(unsaved: ProductCategoryRow): ConnectionIO[ProductCategoryRow] = { + delay { + map.put(unsaved.compositeId, unsaved): @nowarn + unsaved + } + } + override def upsertBatch(unsaved: List[ProductCategoryRow]): Stream[ConnectionIO, ProductCategoryRow] = { + Stream.emits { + unsaved.map { row => + map += (row.compositeId -> row) + row + } + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, ProductCategoryRow], batchSize: Int = 10000): ConnectionIO[Int] = { + unsaved.compile.toList.map { rows => + var num = 0 + rows.foreach { row => + map += (row.compositeId -> row) + num += 1 + } + num + } + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRow.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRow.scala new file mode 100644 index 0000000000..a492efba22 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRow.scala @@ -0,0 +1,54 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product_category + +import adventureworks.frontpage.category.CategoryId +import adventureworks.frontpage.product.ProductId +import doobie.postgres.Text +import doobie.util.Read +import doobie.util.Write +import io.circe.Decoder +import io.circe.Encoder + +/** Table: frontpage.product_category + Composite primary key: product_id, category_id */ +case class ProductCategoryRow( + /** Points to [[product.ProductRow.id]] */ + productId: ProductId, + /** Points to [[category.CategoryRow.id]] */ + categoryId: CategoryId +){ + val compositeId: ProductCategoryId = ProductCategoryId(productId, categoryId) + val id = compositeId + } + +object ProductCategoryRow { + def apply(compositeId: ProductCategoryId) = + new ProductCategoryRow(compositeId.productId, compositeId.categoryId) + implicit lazy val decoder: Decoder[ProductCategoryRow] = Decoder.forProduct2[ProductCategoryRow, ProductId, CategoryId]("product_id", "category_id")(ProductCategoryRow.apply)(ProductId.decoder, CategoryId.decoder) + implicit lazy val encoder: Encoder[ProductCategoryRow] = Encoder.forProduct2[ProductCategoryRow, ProductId, CategoryId]("product_id", "category_id")(x => (x.productId, x.categoryId))(ProductId.encoder, CategoryId.encoder) + implicit lazy val read: Read[ProductCategoryRow] = new Read.CompositeOfInstances(Array( + new Read.Single(ProductId.get).asInstanceOf[Read[Any]], + new Read.Single(CategoryId.get).asInstanceOf[Read[Any]] + ))(using scala.reflect.ClassTag.Any).map { arr => + ProductCategoryRow( + productId = arr(0).asInstanceOf[ProductId], + categoryId = arr(1).asInstanceOf[CategoryId] + ) + } + implicit lazy val text: Text[ProductCategoryRow] = Text.instance[ProductCategoryRow]{ (row, sb) => + ProductId.text.unsafeEncode(row.productId, sb) + sb.append(Text.DELIMETER) + CategoryId.text.unsafeEncode(row.categoryId, sb) + } + implicit lazy val write: Write[ProductCategoryRow] = new Write.Composite[ProductCategoryRow]( + List(new Write.Single(ProductId.put), + new Write.Single(CategoryId.put)), + a => List(a.productId, a.categoryId) + ) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleFields.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleFields.scala new file mode 100644 index 0000000000..5eb3c827f7 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleFields.scala @@ -0,0 +1,40 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.Structure.Relation + +trait RoleFields { + def id: IdField[RoleId, RoleRow] + def name: Field[String, RoleRow] +} + +object RoleFields { + lazy val structure: Relation[RoleFields, RoleRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[RoleFields, RoleRow] { + + override lazy val fields: RoleFields = new RoleFields { + override def id = IdField[RoleId, RoleRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, RoleRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, RoleRow]] = + List[FieldLikeNoHkt[?, RoleRow]](fields.id, fields.name) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleId.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleId.scala new file mode 100644 index 0000000000..85d118d436 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleId.scala @@ -0,0 +1,33 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import adventureworks.customtypes.TypoUUID +import doobie.postgres.Text +import doobie.util.Get +import doobie.util.Put +import io.circe.Decoder +import io.circe.Encoder +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.role` */ +case class RoleId(value: TypoUUID) extends AnyVal +object RoleId { + implicit lazy val arrayGet: Get[Array[RoleId]] = TypoUUID.arrayGet.map(_.map(RoleId.apply)) + implicit lazy val arrayPut: Put[Array[RoleId]] = TypoUUID.arrayPut.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[RoleId, TypoUUID] = Bijection[RoleId, TypoUUID](_.value)(RoleId.apply) + implicit lazy val decoder: Decoder[RoleId] = TypoUUID.decoder.map(RoleId.apply) + implicit lazy val encoder: Encoder[RoleId] = TypoUUID.encoder.contramap(_.value) + implicit lazy val get: Get[RoleId] = TypoUUID.get.map(RoleId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[RoleId] = Ordering.by(_.value) + implicit lazy val put: Put[RoleId] = TypoUUID.put.contramap(_.value) + implicit lazy val text: Text[RoleId] = new Text[RoleId] { + override def unsafeEncode(v: RoleId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: RoleId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleRepo.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleRepo.scala new file mode 100644 index 0000000000..2555bf1826 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleRepo.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import doobie.free.connection.ConnectionIO +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait RoleRepo { + def delete: DeleteBuilder[RoleFields, RoleRow] + def deleteById(id: RoleId): ConnectionIO[Boolean] + def deleteByIds(ids: Array[RoleId]): ConnectionIO[Int] + def insert(unsaved: RoleRow): ConnectionIO[RoleRow] + def insert(unsaved: RoleRowUnsaved): ConnectionIO[RoleRow] + def insertStreaming(unsaved: Stream[ConnectionIO, RoleRow], batchSize: Int = 10000): ConnectionIO[Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, RoleRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] + def select: SelectBuilder[RoleFields, RoleRow] + def selectAll: Stream[ConnectionIO, RoleRow] + def selectById(id: RoleId): ConnectionIO[Option[RoleRow]] + def selectByIds(ids: Array[RoleId]): Stream[ConnectionIO, RoleRow] + def selectByIdsTracked(ids: Array[RoleId]): ConnectionIO[Map[RoleId, RoleRow]] + def selectByUniqueName(name: String): ConnectionIO[Option[RoleRow]] + def update: UpdateBuilder[RoleFields, RoleRow] + def update(row: RoleRow): ConnectionIO[Boolean] + def upsert(unsaved: RoleRow): ConnectionIO[RoleRow] + def upsertBatch(unsaved: List[RoleRow]): Stream[ConnectionIO, RoleRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Stream[ConnectionIO, RoleRow], batchSize: Int = 10000): ConnectionIO[Int] +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleRepoImpl.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleRepoImpl.scala new file mode 100644 index 0000000000..ff9ded751f --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleRepoImpl.scala @@ -0,0 +1,145 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import adventureworks.customtypes.Defaulted +import cats.instances.list.catsStdInstancesForList +import doobie.free.connection.ConnectionIO +import doobie.postgres.syntax.FragmentOps +import doobie.syntax.SqlInterpolator.SingleFragment.fromWrite +import doobie.syntax.string.toSqlInterpolator +import doobie.util.Write +import doobie.util.fragment.Fragment +import doobie.util.meta.Meta +import doobie.util.update.Update +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class RoleRepoImpl extends RoleRepo { + override def delete: DeleteBuilder[RoleFields, RoleRow] = { + DeleteBuilder(""""frontpage"."role"""", RoleFields.structure) + } + override def deleteById(id: RoleId): ConnectionIO[Boolean] = { + sql"""delete from "frontpage"."role" where "id" = ${fromWrite(id)(new Write.Single(RoleId.put))}""".update.run.map(_ > 0) + } + override def deleteByIds(ids: Array[RoleId]): ConnectionIO[Int] = { + sql"""delete from "frontpage"."role" where "id" = ANY(${ids})""".update.run + } + override def insert(unsaved: RoleRow): ConnectionIO[RoleRow] = { + sql"""insert into "frontpage"."role"("id", "name") + values (${fromWrite(unsaved.id)(new Write.Single(RoleId.put))}::uuid, ${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}) + returning "id", "name" + """.query(using RoleRow.read).unique + } + override def insert(unsaved: RoleRowUnsaved): ConnectionIO[RoleRow] = { + val fs = List( + Some((Fragment.const0(s""""name""""), fr"${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""id""""), fr"${fromWrite(value: RoleId)(new Write.Single(RoleId.put))}::uuid")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."role" default values + returning "id", "name" + """ + } else { + val CommaSeparate = Fragment.FragmentMonoid.intercalate(fr", ") + sql"""insert into "frontpage"."role"(${CommaSeparate.combineAllOption(fs.map { case (n, _) => n }).get}) + values (${CommaSeparate.combineAllOption(fs.map { case (_, f) => f }).get}) + returning "id", "name" + """ + } + q.query(using RoleRow.read).unique + + } + override def insertStreaming(unsaved: Stream[ConnectionIO, RoleRow], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."role"("id", "name") FROM STDIN""").copyIn(unsaved, batchSize)(using RoleRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, RoleRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."role"("name", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""").copyIn(unsaved, batchSize)(using RoleRowUnsaved.text) + } + override def select: SelectBuilder[RoleFields, RoleRow] = { + SelectBuilderSql(""""frontpage"."role"""", RoleFields.structure, RoleRow.read) + } + override def selectAll: Stream[ConnectionIO, RoleRow] = { + sql"""select "id", "name" from "frontpage"."role"""".query(using RoleRow.read).stream + } + override def selectById(id: RoleId): ConnectionIO[Option[RoleRow]] = { + sql"""select "id", "name" from "frontpage"."role" where "id" = ${fromWrite(id)(new Write.Single(RoleId.put))}""".query(using RoleRow.read).option + } + override def selectByIds(ids: Array[RoleId]): Stream[ConnectionIO, RoleRow] = { + sql"""select "id", "name" from "frontpage"."role" where "id" = ANY(${ids})""".query(using RoleRow.read).stream + } + override def selectByIdsTracked(ids: Array[RoleId]): ConnectionIO[Map[RoleId, RoleRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def selectByUniqueName(name: String): ConnectionIO[Option[RoleRow]] = { + sql"""select "id", "name" + from "frontpage"."role" + where "name" = ${fromWrite(name)(new Write.Single(Meta.StringMeta.put))} + """.query(using RoleRow.read).option + } + override def update: UpdateBuilder[RoleFields, RoleRow] = { + UpdateBuilder(""""frontpage"."role"""", RoleFields.structure, RoleRow.read) + } + override def update(row: RoleRow): ConnectionIO[Boolean] = { + val id = row.id + sql"""update "frontpage"."role" + set "name" = ${fromWrite(row.name)(new Write.Single(Meta.StringMeta.put))} + where "id" = ${fromWrite(id)(new Write.Single(RoleId.put))}""" + .update + .run + .map(_ > 0) + } + override def upsert(unsaved: RoleRow): ConnectionIO[RoleRow] = { + sql"""insert into "frontpage"."role"("id", "name") + values ( + ${fromWrite(unsaved.id)(new Write.Single(RoleId.put))}::uuid, + ${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))} + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name" + returning "id", "name" + """.query(using RoleRow.read).unique + } + override def upsertBatch(unsaved: List[RoleRow]): Stream[ConnectionIO, RoleRow] = { + Update[RoleRow]( + s"""insert into "frontpage"."role"("id", "name") + values (?::uuid,?) + on conflict ("id") + do update set + "name" = EXCLUDED."name" + returning "id", "name"""" + )(using RoleRow.write) + .updateManyWithGeneratedKeys[RoleRow]("id", "name")(unsaved)(using catsStdInstancesForList, RoleRow.read) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, RoleRow], batchSize: Int = 10000): ConnectionIO[Int] = { + for { + _ <- sql"""create temporary table role_TEMP (like "frontpage"."role") on commit drop""".update.run + _ <- new FragmentOps(sql"""copy role_TEMP("id", "name") from stdin""").copyIn(unsaved, batchSize)(using RoleRow.text) + res <- sql"""insert into "frontpage"."role"("id", "name") + select * from role_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name" + ; + drop table role_TEMP;""".update.run + } yield res + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleRepoMock.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleRepoMock.scala new file mode 100644 index 0000000000..63bc6405e6 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleRepoMock.scala @@ -0,0 +1,130 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import doobie.free.connection.ConnectionIO +import doobie.free.connection.delay +import fs2.Stream +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class RoleRepoMock(toRow: Function1[RoleRowUnsaved, RoleRow], + map: scala.collection.mutable.Map[RoleId, RoleRow] = scala.collection.mutable.Map.empty) extends RoleRepo { + override def delete: DeleteBuilder[RoleFields, RoleRow] = { + DeleteBuilderMock(DeleteParams.empty, RoleFields.structure, map) + } + override def deleteById(id: RoleId): ConnectionIO[Boolean] = { + delay(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[RoleId]): ConnectionIO[Int] = { + delay(ids.map(id => map.remove(id)).count(_.isDefined)) + } + override def insert(unsaved: RoleRow): ConnectionIO[RoleRow] = { + delay { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: RoleRowUnsaved): ConnectionIO[RoleRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Stream[ConnectionIO, RoleRow], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { rows => + var num = 0L + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, RoleRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { unsavedRows => + var num = 0L + unsavedRows.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + num += 1 + } + num + } + } + override def select: SelectBuilder[RoleFields, RoleRow] = { + SelectBuilderMock(RoleFields.structure, delay(map.values.toList), SelectParams.empty) + } + override def selectAll: Stream[ConnectionIO, RoleRow] = { + Stream.emits(map.values.toList) + } + override def selectById(id: RoleId): ConnectionIO[Option[RoleRow]] = { + delay(map.get(id)) + } + override def selectByIds(ids: Array[RoleId]): Stream[ConnectionIO, RoleRow] = { + Stream.emits(ids.flatMap(map.get).toList) + } + override def selectByIdsTracked(ids: Array[RoleId]): ConnectionIO[Map[RoleId, RoleRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def selectByUniqueName(name: String): ConnectionIO[Option[RoleRow]] = { + delay(map.values.find(v => name == v.name)) + } + override def update: UpdateBuilder[RoleFields, RoleRow] = { + UpdateBuilderMock(UpdateParams.empty, RoleFields.structure, map) + } + override def update(row: RoleRow): ConnectionIO[Boolean] = { + delay { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: RoleRow): ConnectionIO[RoleRow] = { + delay { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + } + override def upsertBatch(unsaved: List[RoleRow]): Stream[ConnectionIO, RoleRow] = { + Stream.emits { + unsaved.map { row => + map += (row.id -> row) + row + } + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, RoleRow], batchSize: Int = 10000): ConnectionIO[Int] = { + unsaved.compile.toList.map { rows => + var num = 0 + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleRow.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleRow.scala new file mode 100644 index 0000000000..5b0363cf29 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleRow.scala @@ -0,0 +1,51 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import adventureworks.customtypes.Defaulted +import doobie.postgres.Text +import doobie.util.Read +import doobie.util.Write +import doobie.util.meta.Meta +import io.circe.Decoder +import io.circe.Encoder + +/** Table: frontpage.role + Primary key: id */ +case class RoleRow( + /** Default: gen_random_uuid() */ + id: RoleId, + name: String +){ + def toUnsavedRow(id: Defaulted[RoleId]): RoleRowUnsaved = + RoleRowUnsaved(name, id) + } + +object RoleRow { + implicit lazy val decoder: Decoder[RoleRow] = Decoder.forProduct2[RoleRow, RoleId, String]("id", "name")(RoleRow.apply)(RoleId.decoder, Decoder.decodeString) + implicit lazy val encoder: Encoder[RoleRow] = Encoder.forProduct2[RoleRow, RoleId, String]("id", "name")(x => (x.id, x.name))(RoleId.encoder, Encoder.encodeString) + implicit lazy val read: Read[RoleRow] = new Read.CompositeOfInstances(Array( + new Read.Single(RoleId.get).asInstanceOf[Read[Any]], + new Read.Single(Meta.StringMeta.get).asInstanceOf[Read[Any]] + ))(using scala.reflect.ClassTag.Any).map { arr => + RoleRow( + id = arr(0).asInstanceOf[RoleId], + name = arr(1).asInstanceOf[String] + ) + } + implicit lazy val text: Text[RoleRow] = Text.instance[RoleRow]{ (row, sb) => + RoleId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + } + implicit lazy val write: Write[RoleRow] = new Write.Composite[RoleRow]( + List(new Write.Single(RoleId.put), + new Write.Single(Meta.StringMeta.put)), + a => List(a.id, a.name) + ) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleRowUnsaved.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleRowUnsaved.scala new file mode 100644 index 0000000000..6caaa35aa7 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/role/RoleRowUnsaved.scala @@ -0,0 +1,38 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import adventureworks.customtypes.Defaulted +import doobie.postgres.Text +import io.circe.Decoder +import io.circe.Encoder + +/** This class corresponds to a row in table `frontpage.role` which has not been persisted yet */ +case class RoleRowUnsaved( + name: String, + /** Default: gen_random_uuid() */ + id: Defaulted[RoleId] = Defaulted.UseDefault +) { + def toRow(idDefault: => RoleId): RoleRow = + RoleRow( + name = name, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object RoleRowUnsaved { + implicit lazy val decoder: Decoder[RoleRowUnsaved] = Decoder.forProduct2[RoleRowUnsaved, String, Defaulted[RoleId]]("name", "id")(RoleRowUnsaved.apply)(Decoder.decodeString, Defaulted.decoder(RoleId.decoder)) + implicit lazy val encoder: Encoder[RoleRowUnsaved] = Encoder.forProduct2[RoleRowUnsaved, String, Defaulted[RoleId]]("name", "id")(x => (x.name, x.id))(Encoder.encodeString, Defaulted.encoder(RoleId.encoder)) + implicit lazy val text: Text[RoleRowUnsaved] = Text.instance[RoleRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Defaulted.text(RoleId.text).unsafeEncode(row.id, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserFields.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserFields.scala new file mode 100644 index 0000000000..032b7e96b1 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserFields.scala @@ -0,0 +1,66 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.department.DepartmentFields +import adventureworks.frontpage.department.DepartmentId +import adventureworks.frontpage.department.DepartmentRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait UserFields { + def id: IdField[UserId, UserRow] + def email: Field[Email, UserRow] + def name: Field[String, UserRow] + def createdAt: OptField[TypoLocalDateTime, UserRow] + def departmentId: OptField[DepartmentId, UserRow] + def status: OptField[UserStatus, UserRow] + def verified: OptField[Boolean, UserRow] + def managerId: OptField[UserId, UserRow] + def role: OptField[UserRole, UserRow] + def fkDepartment: ForeignKey[DepartmentFields, DepartmentRow] = + ForeignKey[DepartmentFields, DepartmentRow]("frontpage.user_department_id_fkey", Nil) + .withColumnPair(departmentId, _.id) + def fkUser: ForeignKey[UserFields, UserRow] = + ForeignKey[UserFields, UserRow]("frontpage.user_manager_id_fkey", Nil) + .withColumnPair(managerId, _.id) +} + +object UserFields { + lazy val structure: Relation[UserFields, UserRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[UserFields, UserRow] { + + override lazy val fields: UserFields = new UserFields { + override def id = IdField[UserId, UserRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def email = Field[Email, UserRow](_path, "email", None, Some("text"), x => x.email, (row, value) => row.copy(email = value)) + override def name = Field[String, UserRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + override def createdAt = OptField[TypoLocalDateTime, UserRow](_path, "created_at", Some("text"), Some("timestamp"), x => x.createdAt, (row, value) => row.copy(createdAt = value)) + override def departmentId = OptField[DepartmentId, UserRow](_path, "department_id", None, Some("uuid"), x => x.departmentId, (row, value) => row.copy(departmentId = value)) + override def status = OptField[UserStatus, UserRow](_path, "status", None, Some("frontpage.user_status"), x => x.status, (row, value) => row.copy(status = value)) + override def verified = OptField[Boolean, UserRow](_path, "verified", None, None, x => x.verified, (row, value) => row.copy(verified = value)) + override def managerId = OptField[UserId, UserRow](_path, "manager_id", None, Some("uuid"), x => x.managerId, (row, value) => row.copy(managerId = value)) + override def role = OptField[UserRole, UserRow](_path, "role", None, Some("frontpage.user_role"), x => x.role, (row, value) => row.copy(role = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, UserRow]] = + List[FieldLikeNoHkt[?, UserRow]](fields.id, fields.email, fields.name, fields.createdAt, fields.departmentId, fields.status, fields.verified, fields.managerId, fields.role) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserId.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserId.scala new file mode 100644 index 0000000000..b9394587f2 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserId.scala @@ -0,0 +1,33 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import adventureworks.customtypes.TypoUUID +import doobie.postgres.Text +import doobie.util.Get +import doobie.util.Put +import io.circe.Decoder +import io.circe.Encoder +import typo.dsl.Bijection + +/** Type for the primary key of table `frontpage.user` */ +case class UserId(value: TypoUUID) extends AnyVal +object UserId { + implicit lazy val arrayGet: Get[Array[UserId]] = TypoUUID.arrayGet.map(_.map(UserId.apply)) + implicit lazy val arrayPut: Put[Array[UserId]] = TypoUUID.arrayPut.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[UserId, TypoUUID] = Bijection[UserId, TypoUUID](_.value)(UserId.apply) + implicit lazy val decoder: Decoder[UserId] = TypoUUID.decoder.map(UserId.apply) + implicit lazy val encoder: Encoder[UserId] = TypoUUID.encoder.contramap(_.value) + implicit lazy val get: Get[UserId] = TypoUUID.get.map(UserId.apply) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[UserId] = Ordering.by(_.value) + implicit lazy val put: Put[UserId] = TypoUUID.put.contramap(_.value) + implicit lazy val text: Text[UserId] = new Text[UserId] { + override def unsafeEncode(v: UserId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: UserId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserRepo.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserRepo.scala new file mode 100644 index 0000000000..863df9d101 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserRepo.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import doobie.free.connection.ConnectionIO +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait UserRepo { + def delete: DeleteBuilder[UserFields, UserRow] + def deleteById(id: UserId): ConnectionIO[Boolean] + def deleteByIds(ids: Array[UserId]): ConnectionIO[Int] + def insert(unsaved: UserRow): ConnectionIO[UserRow] + def insert(unsaved: UserRowUnsaved): ConnectionIO[UserRow] + def insertStreaming(unsaved: Stream[ConnectionIO, UserRow], batchSize: Int = 10000): ConnectionIO[Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, UserRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] + def select: SelectBuilder[UserFields, UserRow] + def selectAll: Stream[ConnectionIO, UserRow] + def selectById(id: UserId): ConnectionIO[Option[UserRow]] + def selectByIds(ids: Array[UserId]): Stream[ConnectionIO, UserRow] + def selectByIdsTracked(ids: Array[UserId]): ConnectionIO[Map[UserId, UserRow]] + def selectByUniqueEmail(email: Email): ConnectionIO[Option[UserRow]] + def update: UpdateBuilder[UserFields, UserRow] + def update(row: UserRow): ConnectionIO[Boolean] + def upsert(unsaved: UserRow): ConnectionIO[UserRow] + def upsertBatch(unsaved: List[UserRow]): Stream[ConnectionIO, UserRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Stream[ConnectionIO, UserRow], batchSize: Int = 10000): ConnectionIO[Int] +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserRepoImpl.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserRepoImpl.scala new file mode 100644 index 0000000000..9b3b443333 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserRepoImpl.scala @@ -0,0 +1,201 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.department.DepartmentId +import cats.instances.list.catsStdInstancesForList +import doobie.free.connection.ConnectionIO +import doobie.postgres.syntax.FragmentOps +import doobie.syntax.SqlInterpolator.SingleFragment.fromWrite +import doobie.syntax.string.toSqlInterpolator +import doobie.util.Write +import doobie.util.fragment.Fragment +import doobie.util.meta.Meta +import doobie.util.update.Update +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class UserRepoImpl extends UserRepo { + override def delete: DeleteBuilder[UserFields, UserRow] = { + DeleteBuilder(""""frontpage"."user"""", UserFields.structure) + } + override def deleteById(id: UserId): ConnectionIO[Boolean] = { + sql"""delete from "frontpage"."user" where "id" = ${fromWrite(id)(new Write.Single(UserId.put))}""".update.run.map(_ > 0) + } + override def deleteByIds(ids: Array[UserId]): ConnectionIO[Int] = { + sql"""delete from "frontpage"."user" where "id" = ANY(${ids})""".update.run + } + override def insert(unsaved: UserRow): ConnectionIO[UserRow] = { + sql"""insert into "frontpage"."user"("id", "email", "name", "created_at", "department_id", "status", "verified", "manager_id", "role") + values (${fromWrite(unsaved.id)(new Write.Single(UserId.put))}::uuid, ${fromWrite(unsaved.email)(new Write.Single(Email.put))}::text, ${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}, ${fromWrite(unsaved.createdAt)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp, ${fromWrite(unsaved.departmentId)(new Write.SingleOpt(DepartmentId.put))}::uuid, ${fromWrite(unsaved.status)(new Write.SingleOpt(UserStatus.put))}::frontpage.user_status, ${fromWrite(unsaved.verified)(new Write.SingleOpt(Meta.BooleanMeta.put))}, ${fromWrite(unsaved.managerId)(new Write.SingleOpt(UserId.put))}::uuid, ${fromWrite(unsaved.role)(new Write.SingleOpt(UserRole.put))}::frontpage.user_role) + returning "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" + """.query(using UserRow.read).unique + } + override def insert(unsaved: UserRowUnsaved): ConnectionIO[UserRow] = { + val fs = List( + Some((Fragment.const0(s""""email""""), fr"${fromWrite(unsaved.email)(new Write.Single(Email.put))}::text")), + Some((Fragment.const0(s""""name""""), fr"${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}")), + Some((Fragment.const0(s""""department_id""""), fr"${fromWrite(unsaved.departmentId)(new Write.SingleOpt(DepartmentId.put))}::uuid")), + Some((Fragment.const0(s""""manager_id""""), fr"${fromWrite(unsaved.managerId)(new Write.SingleOpt(UserId.put))}::uuid")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""id""""), fr"${fromWrite(value: UserId)(new Write.Single(UserId.put))}::uuid")) + }, + unsaved.createdAt match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""created_at""""), fr"${fromWrite(value: Option[TypoLocalDateTime])(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp")) + }, + unsaved.status match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""status""""), fr"${fromWrite(value: Option[UserStatus])(new Write.SingleOpt(UserStatus.put))}::frontpage.user_status")) + }, + unsaved.verified match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""verified""""), fr"${fromWrite(value: Option[Boolean])(new Write.SingleOpt(Meta.BooleanMeta.put))}")) + }, + unsaved.role match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""role""""), fr"${fromWrite(value: Option[UserRole])(new Write.SingleOpt(UserRole.put))}::frontpage.user_role")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."user" default values + returning "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" + """ + } else { + val CommaSeparate = Fragment.FragmentMonoid.intercalate(fr", ") + sql"""insert into "frontpage"."user"(${CommaSeparate.combineAllOption(fs.map { case (n, _) => n }).get}) + values (${CommaSeparate.combineAllOption(fs.map { case (_, f) => f }).get}) + returning "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" + """ + } + q.query(using UserRow.read).unique + + } + override def insertStreaming(unsaved: Stream[ConnectionIO, UserRow], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."user"("id", "email", "name", "created_at", "department_id", "status", "verified", "manager_id", "role") FROM STDIN""").copyIn(unsaved, batchSize)(using UserRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, UserRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."user"("email", "name", "department_id", "manager_id", "id", "created_at", "status", "verified", "role") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""").copyIn(unsaved, batchSize)(using UserRowUnsaved.text) + } + override def select: SelectBuilder[UserFields, UserRow] = { + SelectBuilderSql(""""frontpage"."user"""", UserFields.structure, UserRow.read) + } + override def selectAll: Stream[ConnectionIO, UserRow] = { + sql"""select "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" from "frontpage"."user"""".query(using UserRow.read).stream + } + override def selectById(id: UserId): ConnectionIO[Option[UserRow]] = { + sql"""select "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" from "frontpage"."user" where "id" = ${fromWrite(id)(new Write.Single(UserId.put))}""".query(using UserRow.read).option + } + override def selectByIds(ids: Array[UserId]): Stream[ConnectionIO, UserRow] = { + sql"""select "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" from "frontpage"."user" where "id" = ANY(${ids})""".query(using UserRow.read).stream + } + override def selectByIdsTracked(ids: Array[UserId]): ConnectionIO[Map[UserId, UserRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def selectByUniqueEmail(email: Email): ConnectionIO[Option[UserRow]] = { + sql"""select "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" + from "frontpage"."user" + where "email" = ${fromWrite(email)(new Write.Single(Email.put))} + """.query(using UserRow.read).option + } + override def update: UpdateBuilder[UserFields, UserRow] = { + UpdateBuilder(""""frontpage"."user"""", UserFields.structure, UserRow.read) + } + override def update(row: UserRow): ConnectionIO[Boolean] = { + val id = row.id + sql"""update "frontpage"."user" + set "email" = ${fromWrite(row.email)(new Write.Single(Email.put))}::text, + "name" = ${fromWrite(row.name)(new Write.Single(Meta.StringMeta.put))}, + "created_at" = ${fromWrite(row.createdAt)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp, + "department_id" = ${fromWrite(row.departmentId)(new Write.SingleOpt(DepartmentId.put))}::uuid, + "status" = ${fromWrite(row.status)(new Write.SingleOpt(UserStatus.put))}::frontpage.user_status, + "verified" = ${fromWrite(row.verified)(new Write.SingleOpt(Meta.BooleanMeta.put))}, + "manager_id" = ${fromWrite(row.managerId)(new Write.SingleOpt(UserId.put))}::uuid, + "role" = ${fromWrite(row.role)(new Write.SingleOpt(UserRole.put))}::frontpage.user_role + where "id" = ${fromWrite(id)(new Write.Single(UserId.put))}""" + .update + .run + .map(_ > 0) + } + override def upsert(unsaved: UserRow): ConnectionIO[UserRow] = { + sql"""insert into "frontpage"."user"("id", "email", "name", "created_at", "department_id", "status", "verified", "manager_id", "role") + values ( + ${fromWrite(unsaved.id)(new Write.Single(UserId.put))}::uuid, + ${fromWrite(unsaved.email)(new Write.Single(Email.put))}::text, + ${fromWrite(unsaved.name)(new Write.Single(Meta.StringMeta.put))}, + ${fromWrite(unsaved.createdAt)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp, + ${fromWrite(unsaved.departmentId)(new Write.SingleOpt(DepartmentId.put))}::uuid, + ${fromWrite(unsaved.status)(new Write.SingleOpt(UserStatus.put))}::frontpage.user_status, + ${fromWrite(unsaved.verified)(new Write.SingleOpt(Meta.BooleanMeta.put))}, + ${fromWrite(unsaved.managerId)(new Write.SingleOpt(UserId.put))}::uuid, + ${fromWrite(unsaved.role)(new Write.SingleOpt(UserRole.put))}::frontpage.user_role + ) + on conflict ("id") + do update set + "email" = EXCLUDED."email", + "name" = EXCLUDED."name", + "created_at" = EXCLUDED."created_at", + "department_id" = EXCLUDED."department_id", + "status" = EXCLUDED."status", + "verified" = EXCLUDED."verified", + "manager_id" = EXCLUDED."manager_id", + "role" = EXCLUDED."role" + returning "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" + """.query(using UserRow.read).unique + } + override def upsertBatch(unsaved: List[UserRow]): Stream[ConnectionIO, UserRow] = { + Update[UserRow]( + s"""insert into "frontpage"."user"("id", "email", "name", "created_at", "department_id", "status", "verified", "manager_id", "role") + values (?::uuid,?::text,?,?::timestamp,?::uuid,?::frontpage.user_status,?,?::uuid,?::frontpage.user_role) + on conflict ("id") + do update set + "email" = EXCLUDED."email", + "name" = EXCLUDED."name", + "created_at" = EXCLUDED."created_at", + "department_id" = EXCLUDED."department_id", + "status" = EXCLUDED."status", + "verified" = EXCLUDED."verified", + "manager_id" = EXCLUDED."manager_id", + "role" = EXCLUDED."role" + returning "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role"""" + )(using UserRow.write) + .updateManyWithGeneratedKeys[UserRow]("id", "email", "name", "created_at", "department_id", "status", "verified", "manager_id", "role")(unsaved)(using catsStdInstancesForList, UserRow.read) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, UserRow], batchSize: Int = 10000): ConnectionIO[Int] = { + for { + _ <- sql"""create temporary table user_TEMP (like "frontpage"."user") on commit drop""".update.run + _ <- new FragmentOps(sql"""copy user_TEMP("id", "email", "name", "created_at", "department_id", "status", "verified", "manager_id", "role") from stdin""").copyIn(unsaved, batchSize)(using UserRow.text) + res <- sql"""insert into "frontpage"."user"("id", "email", "name", "created_at", "department_id", "status", "verified", "manager_id", "role") + select * from user_TEMP + on conflict ("id") + do update set + "email" = EXCLUDED."email", + "name" = EXCLUDED."name", + "created_at" = EXCLUDED."created_at", + "department_id" = EXCLUDED."department_id", + "status" = EXCLUDED."status", + "verified" = EXCLUDED."verified", + "manager_id" = EXCLUDED."manager_id", + "role" = EXCLUDED."role" + ; + drop table user_TEMP;""".update.run + } yield res + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserRepoMock.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserRepoMock.scala new file mode 100644 index 0000000000..17fedee5e4 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserRepoMock.scala @@ -0,0 +1,130 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import doobie.free.connection.ConnectionIO +import doobie.free.connection.delay +import fs2.Stream +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class UserRepoMock(toRow: Function1[UserRowUnsaved, UserRow], + map: scala.collection.mutable.Map[UserId, UserRow] = scala.collection.mutable.Map.empty) extends UserRepo { + override def delete: DeleteBuilder[UserFields, UserRow] = { + DeleteBuilderMock(DeleteParams.empty, UserFields.structure, map) + } + override def deleteById(id: UserId): ConnectionIO[Boolean] = { + delay(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[UserId]): ConnectionIO[Int] = { + delay(ids.map(id => map.remove(id)).count(_.isDefined)) + } + override def insert(unsaved: UserRow): ConnectionIO[UserRow] = { + delay { + val _ = if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: UserRowUnsaved): ConnectionIO[UserRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Stream[ConnectionIO, UserRow], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { rows => + var num = 0L + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, UserRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { unsavedRows => + var num = 0L + unsavedRows.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.id -> row) + num += 1 + } + num + } + } + override def select: SelectBuilder[UserFields, UserRow] = { + SelectBuilderMock(UserFields.structure, delay(map.values.toList), SelectParams.empty) + } + override def selectAll: Stream[ConnectionIO, UserRow] = { + Stream.emits(map.values.toList) + } + override def selectById(id: UserId): ConnectionIO[Option[UserRow]] = { + delay(map.get(id)) + } + override def selectByIds(ids: Array[UserId]): Stream[ConnectionIO, UserRow] = { + Stream.emits(ids.flatMap(map.get).toList) + } + override def selectByIdsTracked(ids: Array[UserId]): ConnectionIO[Map[UserId, UserRow]] = { + selectByIds(ids).compile.toList.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def selectByUniqueEmail(email: Email): ConnectionIO[Option[UserRow]] = { + delay(map.values.find(v => email == v.email)) + } + override def update: UpdateBuilder[UserFields, UserRow] = { + UpdateBuilderMock(UpdateParams.empty, UserFields.structure, map) + } + override def update(row: UserRow): ConnectionIO[Boolean] = { + delay { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: UserRow): ConnectionIO[UserRow] = { + delay { + map.put(unsaved.id, unsaved): @nowarn + unsaved + } + } + override def upsertBatch(unsaved: List[UserRow]): Stream[ConnectionIO, UserRow] = { + Stream.emits { + unsaved.map { row => + map += (row.id -> row) + row + } + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, UserRow], batchSize: Int = 10000): ConnectionIO[Int] = { + unsaved.compile.toList.map { rows => + var num = 0 + rows.foreach { row => + map += (row.id -> row) + num += 1 + } + num + } + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserRow.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserRow.scala new file mode 100644 index 0000000000..83e6a28e80 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserRow.scala @@ -0,0 +1,101 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.department.DepartmentId +import doobie.postgres.Text +import doobie.util.Read +import doobie.util.Write +import doobie.util.meta.Meta +import io.circe.Decoder +import io.circe.Encoder + +/** Table: frontpage.user + Primary key: id */ +case class UserRow( + /** Default: gen_random_uuid() */ + id: UserId, + email: Email, + name: String, + /** Default: now() */ + createdAt: Option[TypoLocalDateTime], + /** Points to [[department.DepartmentRow.id]] */ + departmentId: Option[DepartmentId], + /** Default: 'active'::frontpage.user_status */ + status: Option[UserStatus], + /** Default: false */ + verified: Option[Boolean], + /** Points to [[UserRow.id]] */ + managerId: Option[UserId], + /** Default: 'employee'::frontpage.user_role */ + role: Option[UserRole] +){ + def toUnsavedRow(id: Defaulted[UserId], createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.Provided(this.createdAt), status: Defaulted[Option[UserStatus]] = Defaulted.Provided(this.status), verified: Defaulted[Option[Boolean]] = Defaulted.Provided(this.verified), role: Defaulted[Option[UserRole]] = Defaulted.Provided(this.role)): UserRowUnsaved = + UserRowUnsaved(email, name, departmentId, managerId, id, createdAt, status, verified, role) + } + +object UserRow { + implicit lazy val decoder: Decoder[UserRow] = Decoder.forProduct9[UserRow, UserId, Email, String, Option[TypoLocalDateTime], Option[DepartmentId], Option[UserStatus], Option[Boolean], Option[UserId], Option[UserRole]]("id", "email", "name", "created_at", "department_id", "status", "verified", "manager_id", "role")(UserRow.apply)(UserId.decoder, Email.decoder, Decoder.decodeString, Decoder.decodeOption(TypoLocalDateTime.decoder), Decoder.decodeOption(DepartmentId.decoder), Decoder.decodeOption(UserStatus.decoder), Decoder.decodeOption(Decoder.decodeBoolean), Decoder.decodeOption(UserId.decoder), Decoder.decodeOption(UserRole.decoder)) + implicit lazy val encoder: Encoder[UserRow] = Encoder.forProduct9[UserRow, UserId, Email, String, Option[TypoLocalDateTime], Option[DepartmentId], Option[UserStatus], Option[Boolean], Option[UserId], Option[UserRole]]("id", "email", "name", "created_at", "department_id", "status", "verified", "manager_id", "role")(x => (x.id, x.email, x.name, x.createdAt, x.departmentId, x.status, x.verified, x.managerId, x.role))(UserId.encoder, Email.encoder, Encoder.encodeString, Encoder.encodeOption(TypoLocalDateTime.encoder), Encoder.encodeOption(DepartmentId.encoder), Encoder.encodeOption(UserStatus.encoder), Encoder.encodeOption(Encoder.encodeBoolean), Encoder.encodeOption(UserId.encoder), Encoder.encodeOption(UserRole.encoder)) + implicit lazy val read: Read[UserRow] = new Read.CompositeOfInstances(Array( + new Read.Single(UserId.get).asInstanceOf[Read[Any]], + new Read.Single(Email.get).asInstanceOf[Read[Any]], + new Read.Single(Meta.StringMeta.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(TypoLocalDateTime.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(DepartmentId.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(UserStatus.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(Meta.BooleanMeta.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(UserId.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(UserRole.get).asInstanceOf[Read[Any]] + ))(using scala.reflect.ClassTag.Any).map { arr => + UserRow( + id = arr(0).asInstanceOf[UserId], + email = arr(1).asInstanceOf[Email], + name = arr(2).asInstanceOf[String], + createdAt = arr(3).asInstanceOf[Option[TypoLocalDateTime]], + departmentId = arr(4).asInstanceOf[Option[DepartmentId]], + status = arr(5).asInstanceOf[Option[UserStatus]], + verified = arr(6).asInstanceOf[Option[Boolean]], + managerId = arr(7).asInstanceOf[Option[UserId]], + role = arr(8).asInstanceOf[Option[UserRole]] + ) + } + implicit lazy val text: Text[UserRow] = Text.instance[UserRow]{ (row, sb) => + UserId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Email.text.unsafeEncode(row.email, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.createdAt, sb) + sb.append(Text.DELIMETER) + Text.option(DepartmentId.text).unsafeEncode(row.departmentId, sb) + sb.append(Text.DELIMETER) + Text.option(UserStatus.text).unsafeEncode(row.status, sb) + sb.append(Text.DELIMETER) + Text.option(Text.booleanInstance).unsafeEncode(row.verified, sb) + sb.append(Text.DELIMETER) + Text.option(UserId.text).unsafeEncode(row.managerId, sb) + sb.append(Text.DELIMETER) + Text.option(UserRole.text).unsafeEncode(row.role, sb) + } + implicit lazy val write: Write[UserRow] = new Write.Composite[UserRow]( + List(new Write.Single(UserId.put), + new Write.Single(Email.put), + new Write.Single(Meta.StringMeta.put), + new Write.Single(TypoLocalDateTime.put).toOpt, + new Write.Single(DepartmentId.put).toOpt, + new Write.Single(UserStatus.put).toOpt, + new Write.Single(Meta.BooleanMeta.put).toOpt, + new Write.Single(UserId.put).toOpt, + new Write.Single(UserRole.put).toOpt), + a => List(a.id, a.email, a.name, a.createdAt, a.departmentId, a.status, a.verified, a.managerId, a.role) + ) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserRowUnsaved.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserRowUnsaved.scala new file mode 100644 index 0000000000..e4536b9a21 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user/UserRowUnsaved.scala @@ -0,0 +1,86 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.department.DepartmentId +import doobie.postgres.Text +import io.circe.Decoder +import io.circe.Encoder + +/** This class corresponds to a row in table `frontpage.user` which has not been persisted yet */ +case class UserRowUnsaved( + email: Email, + name: String, + /** Points to [[department.DepartmentRow.id]] */ + departmentId: Option[DepartmentId], + /** Points to [[UserRow.id]] */ + managerId: Option[UserId], + /** Default: gen_random_uuid() */ + id: Defaulted[UserId] = Defaulted.UseDefault, + /** Default: now() */ + createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault, + /** Default: 'active'::frontpage.user_status */ + status: Defaulted[Option[UserStatus]] = Defaulted.UseDefault, + /** Default: false */ + verified: Defaulted[Option[Boolean]] = Defaulted.UseDefault, + /** Default: 'employee'::frontpage.user_role */ + role: Defaulted[Option[UserRole]] = Defaulted.UseDefault +) { + def toRow(idDefault: => UserId, createdAtDefault: => Option[TypoLocalDateTime], statusDefault: => Option[UserStatus], verifiedDefault: => Option[Boolean], roleDefault: => Option[UserRole]): UserRow = + UserRow( + email = email, + name = name, + departmentId = departmentId, + managerId = managerId, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + }, + createdAt = createdAt match { + case Defaulted.UseDefault => createdAtDefault + case Defaulted.Provided(value) => value + }, + status = status match { + case Defaulted.UseDefault => statusDefault + case Defaulted.Provided(value) => value + }, + verified = verified match { + case Defaulted.UseDefault => verifiedDefault + case Defaulted.Provided(value) => value + }, + role = role match { + case Defaulted.UseDefault => roleDefault + case Defaulted.Provided(value) => value + } + ) +} +object UserRowUnsaved { + implicit lazy val decoder: Decoder[UserRowUnsaved] = Decoder.forProduct9[UserRowUnsaved, Email, String, Option[DepartmentId], Option[UserId], Defaulted[UserId], Defaulted[Option[TypoLocalDateTime]], Defaulted[Option[UserStatus]], Defaulted[Option[Boolean]], Defaulted[Option[UserRole]]]("email", "name", "department_id", "manager_id", "id", "created_at", "status", "verified", "role")(UserRowUnsaved.apply)(Email.decoder, Decoder.decodeString, Decoder.decodeOption(DepartmentId.decoder), Decoder.decodeOption(UserId.decoder), Defaulted.decoder(UserId.decoder), Defaulted.decoder(Decoder.decodeOption(TypoLocalDateTime.decoder)), Defaulted.decoder(Decoder.decodeOption(UserStatus.decoder)), Defaulted.decoder(Decoder.decodeOption(Decoder.decodeBoolean)), Defaulted.decoder(Decoder.decodeOption(UserRole.decoder))) + implicit lazy val encoder: Encoder[UserRowUnsaved] = Encoder.forProduct9[UserRowUnsaved, Email, String, Option[DepartmentId], Option[UserId], Defaulted[UserId], Defaulted[Option[TypoLocalDateTime]], Defaulted[Option[UserStatus]], Defaulted[Option[Boolean]], Defaulted[Option[UserRole]]]("email", "name", "department_id", "manager_id", "id", "created_at", "status", "verified", "role")(x => (x.email, x.name, x.departmentId, x.managerId, x.id, x.createdAt, x.status, x.verified, x.role))(Email.encoder, Encoder.encodeString, Encoder.encodeOption(DepartmentId.encoder), Encoder.encodeOption(UserId.encoder), Defaulted.encoder(UserId.encoder), Defaulted.encoder(Encoder.encodeOption(TypoLocalDateTime.encoder)), Defaulted.encoder(Encoder.encodeOption(UserStatus.encoder)), Defaulted.encoder(Encoder.encodeOption(Encoder.encodeBoolean)), Defaulted.encoder(Encoder.encodeOption(UserRole.encoder))) + implicit lazy val text: Text[UserRowUnsaved] = Text.instance[UserRowUnsaved]{ (row, sb) => + Email.text.unsafeEncode(row.email, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(DepartmentId.text).unsafeEncode(row.departmentId, sb) + sb.append(Text.DELIMETER) + Text.option(UserId.text).unsafeEncode(row.managerId, sb) + sb.append(Text.DELIMETER) + Defaulted.text(UserId.text).unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(TypoLocalDateTime.text)).unsafeEncode(row.createdAt, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(UserStatus.text)).unsafeEncode(row.status, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text.booleanInstance)).unsafeEncode(row.verified, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(UserRole.text)).unsafeEncode(row.role, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionFields.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionFields.scala new file mode 100644 index 0000000000..51eba08eed --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionFields.scala @@ -0,0 +1,65 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.permission.PermissionFields +import adventureworks.frontpage.permission.PermissionId +import adventureworks.frontpage.permission.PermissionRow +import adventureworks.frontpage.user.UserFields +import adventureworks.frontpage.user.UserId +import adventureworks.frontpage.user.UserRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.Required +import typo.dsl.SqlExpr +import typo.dsl.SqlExpr.CompositeIn +import typo.dsl.SqlExpr.CompositeIn.TuplePart +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait UserPermissionFields { + def userId: IdField[UserId, UserPermissionRow] + def permissionId: IdField[PermissionId, UserPermissionRow] + def grantedAt: OptField[TypoLocalDateTime, UserPermissionRow] + def fkPermission: ForeignKey[PermissionFields, PermissionRow] = + ForeignKey[PermissionFields, PermissionRow]("frontpage.user_permission_permission_id_fkey", Nil) + .withColumnPair(permissionId, _.id) + def fkUser: ForeignKey[UserFields, UserRow] = + ForeignKey[UserFields, UserRow]("frontpage.user_permission_user_id_fkey", Nil) + .withColumnPair(userId, _.id) + def compositeIdIs(compositeId: UserPermissionId): SqlExpr[Boolean, Required] = + userId.isEqual(compositeId.userId).and(permissionId.isEqual(compositeId.permissionId)) + def compositeIdIn(compositeIds: Array[UserPermissionId]): SqlExpr[Boolean, Required] = + new CompositeIn(compositeIds)(TuplePart(userId)(_.userId), TuplePart(permissionId)(_.permissionId)) + +} + +object UserPermissionFields { + lazy val structure: Relation[UserPermissionFields, UserPermissionRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[UserPermissionFields, UserPermissionRow] { + + override lazy val fields: UserPermissionFields = new UserPermissionFields { + override def userId = IdField[UserId, UserPermissionRow](_path, "user_id", None, Some("uuid"), x => x.userId, (row, value) => row.copy(userId = value)) + override def permissionId = IdField[PermissionId, UserPermissionRow](_path, "permission_id", None, Some("uuid"), x => x.permissionId, (row, value) => row.copy(permissionId = value)) + override def grantedAt = OptField[TypoLocalDateTime, UserPermissionRow](_path, "granted_at", Some("text"), Some("timestamp"), x => x.grantedAt, (row, value) => row.copy(grantedAt = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, UserPermissionRow]] = + List[FieldLikeNoHkt[?, UserPermissionRow]](fields.userId, fields.permissionId, fields.grantedAt) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionId.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionId.scala new file mode 100644 index 0000000000..f7f48ac1dd --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionId.scala @@ -0,0 +1,24 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import adventureworks.frontpage.permission.PermissionId +import adventureworks.frontpage.user.UserId +import io.circe.Decoder +import io.circe.Encoder + +/** Type for the composite primary key of table `frontpage.user_permission` */ +case class UserPermissionId( + userId: UserId, + permissionId: PermissionId +) +object UserPermissionId { + implicit lazy val decoder: Decoder[UserPermissionId] = Decoder.forProduct2[UserPermissionId, UserId, PermissionId]("user_id", "permission_id")(UserPermissionId.apply)(UserId.decoder, PermissionId.decoder) + implicit lazy val encoder: Encoder[UserPermissionId] = Encoder.forProduct2[UserPermissionId, UserId, PermissionId]("user_id", "permission_id")(x => (x.userId, x.permissionId))(UserId.encoder, PermissionId.encoder) + implicit lazy val ordering: Ordering[UserPermissionId] = Ordering.by(x => (x.userId, x.permissionId)) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepo.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepo.scala new file mode 100644 index 0000000000..98442a1caa --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepo.scala @@ -0,0 +1,36 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import doobie.free.connection.ConnectionIO +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait UserPermissionRepo { + def delete: DeleteBuilder[UserPermissionFields, UserPermissionRow] + def deleteById(compositeId: UserPermissionId): ConnectionIO[Boolean] + def deleteByIds(compositeIds: Array[UserPermissionId]): ConnectionIO[Int] + def insert(unsaved: UserPermissionRow): ConnectionIO[UserPermissionRow] + def insert(unsaved: UserPermissionRowUnsaved): ConnectionIO[UserPermissionRow] + def insertStreaming(unsaved: Stream[ConnectionIO, UserPermissionRow], batchSize: Int = 10000): ConnectionIO[Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, UserPermissionRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] + def select: SelectBuilder[UserPermissionFields, UserPermissionRow] + def selectAll: Stream[ConnectionIO, UserPermissionRow] + def selectById(compositeId: UserPermissionId): ConnectionIO[Option[UserPermissionRow]] + def selectByIds(compositeIds: Array[UserPermissionId]): Stream[ConnectionIO, UserPermissionRow] + def selectByIdsTracked(compositeIds: Array[UserPermissionId]): ConnectionIO[Map[UserPermissionId, UserPermissionRow]] + def update: UpdateBuilder[UserPermissionFields, UserPermissionRow] + def update(row: UserPermissionRow): ConnectionIO[Boolean] + def upsert(unsaved: UserPermissionRow): ConnectionIO[UserPermissionRow] + def upsertBatch(unsaved: List[UserPermissionRow]): Stream[ConnectionIO, UserPermissionRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Stream[ConnectionIO, UserPermissionRow], batchSize: Int = 10000): ConnectionIO[Int] +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepoImpl.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepoImpl.scala new file mode 100644 index 0000000000..f0cabae962 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepoImpl.scala @@ -0,0 +1,157 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.permission.PermissionId +import adventureworks.frontpage.user.UserId +import cats.instances.list.catsStdInstancesForList +import doobie.free.connection.ConnectionIO +import doobie.postgres.syntax.FragmentOps +import doobie.syntax.SqlInterpolator.SingleFragment.fromWrite +import doobie.syntax.string.toSqlInterpolator +import doobie.util.Write +import doobie.util.fragment.Fragment +import doobie.util.update.Update +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class UserPermissionRepoImpl extends UserPermissionRepo { + override def delete: DeleteBuilder[UserPermissionFields, UserPermissionRow] = { + DeleteBuilder(""""frontpage"."user_permission"""", UserPermissionFields.structure) + } + override def deleteById(compositeId: UserPermissionId): ConnectionIO[Boolean] = { + sql"""delete from "frontpage"."user_permission" where "user_id" = ${fromWrite(compositeId.userId)(new Write.Single(UserId.put))} AND "permission_id" = ${fromWrite(compositeId.permissionId)(new Write.Single(PermissionId.put))}""".update.run.map(_ > 0) + } + override def deleteByIds(compositeIds: Array[UserPermissionId]): ConnectionIO[Int] = { + val userId = compositeIds.map(_.userId) + val permissionId = compositeIds.map(_.permissionId) + sql"""delete + from "frontpage"."user_permission" + where ("user_id", "permission_id") + in (select unnest(${userId}), unnest(${permissionId})) + """.update.run + + } + override def insert(unsaved: UserPermissionRow): ConnectionIO[UserPermissionRow] = { + sql"""insert into "frontpage"."user_permission"("user_id", "permission_id", "granted_at") + values (${fromWrite(unsaved.userId)(new Write.Single(UserId.put))}::uuid, ${fromWrite(unsaved.permissionId)(new Write.Single(PermissionId.put))}::uuid, ${fromWrite(unsaved.grantedAt)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp) + returning "user_id", "permission_id", "granted_at"::text + """.query(using UserPermissionRow.read).unique + } + override def insert(unsaved: UserPermissionRowUnsaved): ConnectionIO[UserPermissionRow] = { + val fs = List( + Some((Fragment.const0(s""""user_id""""), fr"${fromWrite(unsaved.userId)(new Write.Single(UserId.put))}::uuid")), + Some((Fragment.const0(s""""permission_id""""), fr"${fromWrite(unsaved.permissionId)(new Write.Single(PermissionId.put))}::uuid")), + unsaved.grantedAt match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((Fragment.const0(s""""granted_at""""), fr"${fromWrite(value: Option[TypoLocalDateTime])(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."user_permission" default values + returning "user_id", "permission_id", "granted_at"::text + """ + } else { + val CommaSeparate = Fragment.FragmentMonoid.intercalate(fr", ") + sql"""insert into "frontpage"."user_permission"(${CommaSeparate.combineAllOption(fs.map { case (n, _) => n }).get}) + values (${CommaSeparate.combineAllOption(fs.map { case (_, f) => f }).get}) + returning "user_id", "permission_id", "granted_at"::text + """ + } + q.query(using UserPermissionRow.read).unique + + } + override def insertStreaming(unsaved: Stream[ConnectionIO, UserPermissionRow], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."user_permission"("user_id", "permission_id", "granted_at") FROM STDIN""").copyIn(unsaved, batchSize)(using UserPermissionRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, UserPermissionRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "frontpage"."user_permission"("user_id", "permission_id", "granted_at") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""").copyIn(unsaved, batchSize)(using UserPermissionRowUnsaved.text) + } + override def select: SelectBuilder[UserPermissionFields, UserPermissionRow] = { + SelectBuilderSql(""""frontpage"."user_permission"""", UserPermissionFields.structure, UserPermissionRow.read) + } + override def selectAll: Stream[ConnectionIO, UserPermissionRow] = { + sql"""select "user_id", "permission_id", "granted_at"::text from "frontpage"."user_permission"""".query(using UserPermissionRow.read).stream + } + override def selectById(compositeId: UserPermissionId): ConnectionIO[Option[UserPermissionRow]] = { + sql"""select "user_id", "permission_id", "granted_at"::text from "frontpage"."user_permission" where "user_id" = ${fromWrite(compositeId.userId)(new Write.Single(UserId.put))} AND "permission_id" = ${fromWrite(compositeId.permissionId)(new Write.Single(PermissionId.put))}""".query(using UserPermissionRow.read).option + } + override def selectByIds(compositeIds: Array[UserPermissionId]): Stream[ConnectionIO, UserPermissionRow] = { + val userId = compositeIds.map(_.userId) + val permissionId = compositeIds.map(_.permissionId) + sql"""select "user_id", "permission_id", "granted_at"::text + from "frontpage"."user_permission" + where ("user_id", "permission_id") + in (select unnest(${userId}), unnest(${permissionId})) + """.query(using UserPermissionRow.read).stream + + } + override def selectByIdsTracked(compositeIds: Array[UserPermissionId]): ConnectionIO[Map[UserPermissionId, UserPermissionRow]] = { + selectByIds(compositeIds).compile.toList.map { rows => + val byId = rows.view.map(x => (x.compositeId, x)).toMap + compositeIds.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[UserPermissionFields, UserPermissionRow] = { + UpdateBuilder(""""frontpage"."user_permission"""", UserPermissionFields.structure, UserPermissionRow.read) + } + override def update(row: UserPermissionRow): ConnectionIO[Boolean] = { + val compositeId = row.compositeId + sql"""update "frontpage"."user_permission" + set "granted_at" = ${fromWrite(row.grantedAt)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp + where "user_id" = ${fromWrite(compositeId.userId)(new Write.Single(UserId.put))} AND "permission_id" = ${fromWrite(compositeId.permissionId)(new Write.Single(PermissionId.put))}""" + .update + .run + .map(_ > 0) + } + override def upsert(unsaved: UserPermissionRow): ConnectionIO[UserPermissionRow] = { + sql"""insert into "frontpage"."user_permission"("user_id", "permission_id", "granted_at") + values ( + ${fromWrite(unsaved.userId)(new Write.Single(UserId.put))}::uuid, + ${fromWrite(unsaved.permissionId)(new Write.Single(PermissionId.put))}::uuid, + ${fromWrite(unsaved.grantedAt)(new Write.SingleOpt(TypoLocalDateTime.put))}::timestamp + ) + on conflict ("user_id", "permission_id") + do update set + "granted_at" = EXCLUDED."granted_at" + returning "user_id", "permission_id", "granted_at"::text + """.query(using UserPermissionRow.read).unique + } + override def upsertBatch(unsaved: List[UserPermissionRow]): Stream[ConnectionIO, UserPermissionRow] = { + Update[UserPermissionRow]( + s"""insert into "frontpage"."user_permission"("user_id", "permission_id", "granted_at") + values (?::uuid,?::uuid,?::timestamp) + on conflict ("user_id", "permission_id") + do update set + "granted_at" = EXCLUDED."granted_at" + returning "user_id", "permission_id", "granted_at"::text""" + )(using UserPermissionRow.write) + .updateManyWithGeneratedKeys[UserPermissionRow]("user_id", "permission_id", "granted_at")(unsaved)(using catsStdInstancesForList, UserPermissionRow.read) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, UserPermissionRow], batchSize: Int = 10000): ConnectionIO[Int] = { + for { + _ <- sql"""create temporary table user_permission_TEMP (like "frontpage"."user_permission") on commit drop""".update.run + _ <- new FragmentOps(sql"""copy user_permission_TEMP("user_id", "permission_id", "granted_at") from stdin""").copyIn(unsaved, batchSize)(using UserPermissionRow.text) + res <- sql"""insert into "frontpage"."user_permission"("user_id", "permission_id", "granted_at") + select * from user_permission_TEMP + on conflict ("user_id", "permission_id") + do update set + "granted_at" = EXCLUDED."granted_at" + ; + drop table user_permission_TEMP;""".update.run + } yield res + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepoMock.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepoMock.scala new file mode 100644 index 0000000000..7bc64f1d9e --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepoMock.scala @@ -0,0 +1,127 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import doobie.free.connection.ConnectionIO +import doobie.free.connection.delay +import fs2.Stream +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams + +class UserPermissionRepoMock(toRow: Function1[UserPermissionRowUnsaved, UserPermissionRow], + map: scala.collection.mutable.Map[UserPermissionId, UserPermissionRow] = scala.collection.mutable.Map.empty) extends UserPermissionRepo { + override def delete: DeleteBuilder[UserPermissionFields, UserPermissionRow] = { + DeleteBuilderMock(DeleteParams.empty, UserPermissionFields.structure, map) + } + override def deleteById(compositeId: UserPermissionId): ConnectionIO[Boolean] = { + delay(map.remove(compositeId).isDefined) + } + override def deleteByIds(compositeIds: Array[UserPermissionId]): ConnectionIO[Int] = { + delay(compositeIds.map(id => map.remove(id)).count(_.isDefined)) + } + override def insert(unsaved: UserPermissionRow): ConnectionIO[UserPermissionRow] = { + delay { + val _ = if (map.contains(unsaved.compositeId)) + sys.error(s"id ${unsaved.compositeId} already exists") + else + map.put(unsaved.compositeId, unsaved) + + unsaved + } + } + override def insert(unsaved: UserPermissionRowUnsaved): ConnectionIO[UserPermissionRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Stream[ConnectionIO, UserPermissionRow], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { rows => + var num = 0L + rows.foreach { row => + map += (row.compositeId -> row) + num += 1 + } + num + } + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, UserPermissionRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { unsavedRows => + var num = 0L + unsavedRows.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.compositeId -> row) + num += 1 + } + num + } + } + override def select: SelectBuilder[UserPermissionFields, UserPermissionRow] = { + SelectBuilderMock(UserPermissionFields.structure, delay(map.values.toList), SelectParams.empty) + } + override def selectAll: Stream[ConnectionIO, UserPermissionRow] = { + Stream.emits(map.values.toList) + } + override def selectById(compositeId: UserPermissionId): ConnectionIO[Option[UserPermissionRow]] = { + delay(map.get(compositeId)) + } + override def selectByIds(compositeIds: Array[UserPermissionId]): Stream[ConnectionIO, UserPermissionRow] = { + Stream.emits(compositeIds.flatMap(map.get).toList) + } + override def selectByIdsTracked(compositeIds: Array[UserPermissionId]): ConnectionIO[Map[UserPermissionId, UserPermissionRow]] = { + selectByIds(compositeIds).compile.toList.map { rows => + val byId = rows.view.map(x => (x.compositeId, x)).toMap + compositeIds.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[UserPermissionFields, UserPermissionRow] = { + UpdateBuilderMock(UpdateParams.empty, UserPermissionFields.structure, map) + } + override def update(row: UserPermissionRow): ConnectionIO[Boolean] = { + delay { + map.get(row.compositeId) match { + case Some(`row`) => false + case Some(_) => + map.put(row.compositeId, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: UserPermissionRow): ConnectionIO[UserPermissionRow] = { + delay { + map.put(unsaved.compositeId, unsaved): @nowarn + unsaved + } + } + override def upsertBatch(unsaved: List[UserPermissionRow]): Stream[ConnectionIO, UserPermissionRow] = { + Stream.emits { + unsaved.map { row => + map += (row.compositeId -> row) + row + } + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, UserPermissionRow], batchSize: Int = 10000): ConnectionIO[Int] = { + unsaved.compile.toList.map { rows => + var num = 0 + rows.foreach { row => + map += (row.compositeId -> row) + num += 1 + } + num + } + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRow.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRow.scala new file mode 100644 index 0000000000..f27199eb6e --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRow.scala @@ -0,0 +1,65 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.permission.PermissionId +import adventureworks.frontpage.user.UserId +import doobie.postgres.Text +import doobie.util.Read +import doobie.util.Write +import io.circe.Decoder +import io.circe.Encoder + +/** Table: frontpage.user_permission + Composite primary key: user_id, permission_id */ +case class UserPermissionRow( + /** Points to [[user.UserRow.id]] */ + userId: UserId, + /** Points to [[permission.PermissionRow.id]] */ + permissionId: PermissionId, + /** Default: now() */ + grantedAt: Option[TypoLocalDateTime] +){ + val compositeId: UserPermissionId = UserPermissionId(userId, permissionId) + val id = compositeId + def toUnsavedRow(grantedAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.Provided(this.grantedAt)): UserPermissionRowUnsaved = + UserPermissionRowUnsaved(userId, permissionId, grantedAt) + } + +object UserPermissionRow { + def apply(compositeId: UserPermissionId, grantedAt: Option[TypoLocalDateTime]) = + new UserPermissionRow(compositeId.userId, compositeId.permissionId, grantedAt) + implicit lazy val decoder: Decoder[UserPermissionRow] = Decoder.forProduct3[UserPermissionRow, UserId, PermissionId, Option[TypoLocalDateTime]]("user_id", "permission_id", "granted_at")(UserPermissionRow.apply)(UserId.decoder, PermissionId.decoder, Decoder.decodeOption(TypoLocalDateTime.decoder)) + implicit lazy val encoder: Encoder[UserPermissionRow] = Encoder.forProduct3[UserPermissionRow, UserId, PermissionId, Option[TypoLocalDateTime]]("user_id", "permission_id", "granted_at")(x => (x.userId, x.permissionId, x.grantedAt))(UserId.encoder, PermissionId.encoder, Encoder.encodeOption(TypoLocalDateTime.encoder)) + implicit lazy val read: Read[UserPermissionRow] = new Read.CompositeOfInstances(Array( + new Read.Single(UserId.get).asInstanceOf[Read[Any]], + new Read.Single(PermissionId.get).asInstanceOf[Read[Any]], + new Read.SingleOpt(TypoLocalDateTime.get).asInstanceOf[Read[Any]] + ))(using scala.reflect.ClassTag.Any).map { arr => + UserPermissionRow( + userId = arr(0).asInstanceOf[UserId], + permissionId = arr(1).asInstanceOf[PermissionId], + grantedAt = arr(2).asInstanceOf[Option[TypoLocalDateTime]] + ) + } + implicit lazy val text: Text[UserPermissionRow] = Text.instance[UserPermissionRow]{ (row, sb) => + UserId.text.unsafeEncode(row.userId, sb) + sb.append(Text.DELIMETER) + PermissionId.text.unsafeEncode(row.permissionId, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.grantedAt, sb) + } + implicit lazy val write: Write[UserPermissionRow] = new Write.Composite[UserPermissionRow]( + List(new Write.Single(UserId.put), + new Write.Single(PermissionId.put), + new Write.Single(TypoLocalDateTime.put).toOpt), + a => List(a.userId, a.permissionId, a.grantedAt) + ) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRowUnsaved.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRowUnsaved.scala new file mode 100644 index 0000000000..3f53a442f3 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRowUnsaved.scala @@ -0,0 +1,47 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.permission.PermissionId +import adventureworks.frontpage.user.UserId +import doobie.postgres.Text +import io.circe.Decoder +import io.circe.Encoder + +/** This class corresponds to a row in table `frontpage.user_permission` which has not been persisted yet */ +case class UserPermissionRowUnsaved( + /** Points to [[user.UserRow.id]] */ + userId: UserId, + /** Points to [[permission.PermissionRow.id]] */ + permissionId: PermissionId, + /** Default: now() */ + grantedAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault +) { + def toRow(grantedAtDefault: => Option[TypoLocalDateTime]): UserPermissionRow = + UserPermissionRow( + userId = userId, + permissionId = permissionId, + grantedAt = grantedAt match { + case Defaulted.UseDefault => grantedAtDefault + case Defaulted.Provided(value) => value + } + ) +} +object UserPermissionRowUnsaved { + implicit lazy val decoder: Decoder[UserPermissionRowUnsaved] = Decoder.forProduct3[UserPermissionRowUnsaved, UserId, PermissionId, Defaulted[Option[TypoLocalDateTime]]]("user_id", "permission_id", "granted_at")(UserPermissionRowUnsaved.apply)(UserId.decoder, PermissionId.decoder, Defaulted.decoder(Decoder.decodeOption(TypoLocalDateTime.decoder))) + implicit lazy val encoder: Encoder[UserPermissionRowUnsaved] = Encoder.forProduct3[UserPermissionRowUnsaved, UserId, PermissionId, Defaulted[Option[TypoLocalDateTime]]]("user_id", "permission_id", "granted_at")(x => (x.userId, x.permissionId, x.grantedAt))(UserId.encoder, PermissionId.encoder, Defaulted.encoder(Encoder.encodeOption(TypoLocalDateTime.encoder))) + implicit lazy val text: Text[UserPermissionRowUnsaved] = Text.instance[UserPermissionRowUnsaved]{ (row, sb) => + UserId.text.unsafeEncode(row.userId, sb) + sb.append(Text.DELIMETER) + PermissionId.text.unsafeEncode(row.permissionId, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(TypoLocalDateTime.text)).unsafeEncode(row.grantedAt, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/testInsert.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/testInsert.scala index 9167f86be0..8d89d9fa52 100644 --- a/typo-tester-doobie/generated-and-checked-in/adventureworks/testInsert.scala +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/testInsert.scala @@ -31,13 +31,71 @@ import adventureworks.customtypes.TypoUUID import adventureworks.customtypes.TypoUnknownCitext import adventureworks.customtypes.TypoVector import adventureworks.customtypes.TypoXml -import adventureworks.humanresources.department.DepartmentId -import adventureworks.humanresources.department.DepartmentRepoImpl -import adventureworks.humanresources.department.DepartmentRow -import adventureworks.humanresources.department.DepartmentRowUnsaved -import adventureworks.humanresources.employee.EmployeeRepoImpl -import adventureworks.humanresources.employee.EmployeeRow -import adventureworks.humanresources.employee.EmployeeRowUnsaved +import adventureworks.frontpage.Email +import adventureworks.frontpage.OrderStatus +import adventureworks.frontpage.UserRole +import adventureworks.frontpage.UserStatus +import adventureworks.frontpage.address.AddressId +import adventureworks.frontpage.address.AddressRepoImpl +import adventureworks.frontpage.address.AddressRow +import adventureworks.frontpage.address.AddressRowUnsaved +import adventureworks.frontpage.category.CategoryId +import adventureworks.frontpage.category.CategoryRepoImpl +import adventureworks.frontpage.category.CategoryRow +import adventureworks.frontpage.category.CategoryRowUnsaved +import adventureworks.frontpage.company.CompanyId +import adventureworks.frontpage.company.CompanyRepoImpl +import adventureworks.frontpage.company.CompanyRow +import adventureworks.frontpage.company.CompanyRowUnsaved +import adventureworks.frontpage.customer.CustomerId +import adventureworks.frontpage.customer.CustomerRepoImpl +import adventureworks.frontpage.customer.CustomerRow +import adventureworks.frontpage.customer.CustomerRowUnsaved +import adventureworks.frontpage.department.DepartmentId +import adventureworks.frontpage.department.DepartmentRepoImpl +import adventureworks.frontpage.department.DepartmentRow +import adventureworks.frontpage.department.DepartmentRowUnsaved +import adventureworks.frontpage.employee.EmployeeId +import adventureworks.frontpage.employee.EmployeeRepoImpl +import adventureworks.frontpage.employee.EmployeeRow +import adventureworks.frontpage.employee.EmployeeRowUnsaved +import adventureworks.frontpage.location.LocationId +import adventureworks.frontpage.location.LocationRepoImpl +import adventureworks.frontpage.location.LocationRow +import adventureworks.frontpage.location.LocationRowUnsaved +import adventureworks.frontpage.order.OrderId +import adventureworks.frontpage.order.OrderRepoImpl +import adventureworks.frontpage.order.OrderRow +import adventureworks.frontpage.order.OrderRowUnsaved +import adventureworks.frontpage.order_item.OrderItemId +import adventureworks.frontpage.order_item.OrderItemRepoImpl +import adventureworks.frontpage.order_item.OrderItemRow +import adventureworks.frontpage.order_item.OrderItemRowUnsaved +import adventureworks.frontpage.permission.PermissionId +import adventureworks.frontpage.permission.PermissionRepoImpl +import adventureworks.frontpage.permission.PermissionRow +import adventureworks.frontpage.permission.PermissionRowUnsaved +import adventureworks.frontpage.person.PersonId +import adventureworks.frontpage.person.PersonRepoImpl +import adventureworks.frontpage.person.PersonRow +import adventureworks.frontpage.person.PersonRowUnsaved +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.product.ProductRepoImpl +import adventureworks.frontpage.product.ProductRow +import adventureworks.frontpage.product.ProductRowUnsaved +import adventureworks.frontpage.product_category.ProductCategoryRepoImpl +import adventureworks.frontpage.product_category.ProductCategoryRow +import adventureworks.frontpage.role.RoleId +import adventureworks.frontpage.role.RoleRepoImpl +import adventureworks.frontpage.role.RoleRow +import adventureworks.frontpage.role.RoleRowUnsaved +import adventureworks.frontpage.user.UserId +import adventureworks.frontpage.user.UserRepoImpl +import adventureworks.frontpage.user.UserRow +import adventureworks.frontpage.user.UserRowUnsaved +import adventureworks.frontpage.user_permission.UserPermissionRepoImpl +import adventureworks.frontpage.user_permission.UserPermissionRow +import adventureworks.frontpage.user_permission.UserPermissionRowUnsaved import adventureworks.humanresources.employeedepartmenthistory.EmployeedepartmenthistoryRepoImpl import adventureworks.humanresources.employeedepartmenthistory.EmployeedepartmenthistoryRow import adventureworks.humanresources.employeedepartmenthistory.EmployeedepartmenthistoryRowUnsaved @@ -52,10 +110,6 @@ import adventureworks.humanresources.shift.ShiftId import adventureworks.humanresources.shift.ShiftRepoImpl import adventureworks.humanresources.shift.ShiftRow import adventureworks.humanresources.shift.ShiftRowUnsaved -import adventureworks.person.address.AddressId -import adventureworks.person.address.AddressRepoImpl -import adventureworks.person.address.AddressRow -import adventureworks.person.address.AddressRowUnsaved import adventureworks.person.addresstype.AddresstypeId import adventureworks.person.addresstype.AddresstypeRepoImpl import adventureworks.person.addresstype.AddresstypeRow @@ -84,9 +138,6 @@ import adventureworks.person.emailaddress.EmailaddressRowUnsaved import adventureworks.person.password.PasswordRepoImpl import adventureworks.person.password.PasswordRow import adventureworks.person.password.PasswordRowUnsaved -import adventureworks.person.person.PersonRepoImpl -import adventureworks.person.person.PersonRow -import adventureworks.person.person.PersonRowUnsaved import adventureworks.person.personphone.PersonphoneRepoImpl import adventureworks.person.personphone.PersonphoneRow import adventureworks.person.personphone.PersonphoneRowUnsaved @@ -113,14 +164,6 @@ import adventureworks.production.illustration.IllustrationId import adventureworks.production.illustration.IllustrationRepoImpl import adventureworks.production.illustration.IllustrationRow import adventureworks.production.illustration.IllustrationRowUnsaved -import adventureworks.production.location.LocationId -import adventureworks.production.location.LocationRepoImpl -import adventureworks.production.location.LocationRow -import adventureworks.production.location.LocationRowUnsaved -import adventureworks.production.product.ProductId -import adventureworks.production.product.ProductRepoImpl -import adventureworks.production.product.ProductRow -import adventureworks.production.product.ProductRowUnsaved import adventureworks.production.productcategory.ProductcategoryId import adventureworks.production.productcategory.ProductcategoryRepoImpl import adventureworks.production.productcategory.ProductcategoryRow @@ -267,10 +310,6 @@ import adventureworks.sales.currencyrate.CurrencyrateId import adventureworks.sales.currencyrate.CurrencyrateRepoImpl import adventureworks.sales.currencyrate.CurrencyrateRow import adventureworks.sales.currencyrate.CurrencyrateRowUnsaved -import adventureworks.sales.customer.CustomerId -import adventureworks.sales.customer.CustomerRepoImpl -import adventureworks.sales.customer.CustomerRow -import adventureworks.sales.customer.CustomerRowUnsaved import adventureworks.sales.personcreditcard.PersoncreditcardRepoImpl import adventureworks.sales.personcreditcard.PersoncreditcardRow import adventureworks.sales.personcreditcard.PersoncreditcardRowUnsaved @@ -331,11 +370,88 @@ import java.time.ZoneOffset import scala.util.Random class TestInsert(random: Random, domainInsert: TestDomainInsert) { + def frontpageAddress(city: String = random.alphanumeric.take(20).mkString, + country: String = random.alphanumeric.take(20).mkString, + id: Defaulted[AddressId] = Defaulted.UseDefault + ): ConnectionIO[AddressRow] = (new AddressRepoImpl).insert(new AddressRowUnsaved(city = city, country = country, id = id)) + def frontpageCategory(name: String = random.alphanumeric.take(20).mkString, id: Defaulted[CategoryId] = Defaulted.UseDefault): ConnectionIO[CategoryRow] = (new CategoryRepoImpl).insert(new CategoryRowUnsaved(name = name, id = id)) + def frontpageCompany(name: String = random.alphanumeric.take(20).mkString, id: Defaulted[CompanyId] = Defaulted.UseDefault): ConnectionIO[CompanyRow] = (new CompanyRepoImpl).insert(new CompanyRowUnsaved(name = name, id = id)) + def frontpageCustomer(userId: Option[UserId] = None, + companyName: Option[String] = if (random.nextBoolean()) None else Some(random.alphanumeric.take(20).mkString), + creditLimit: Option[BigDecimal] = if (random.nextBoolean()) None else Some(BigDecimal.decimal(random.nextDouble())), + id: Defaulted[CustomerId] = Defaulted.UseDefault, + verified: Defaulted[Option[Boolean]] = Defaulted.UseDefault + ): ConnectionIO[CustomerRow] = (new CustomerRepoImpl).insert(new CustomerRowUnsaved(userId = userId, companyName = companyName, creditLimit = creditLimit, id = id, verified = verified)) + def frontpageDepartment(name: String = random.alphanumeric.take(20).mkString, + budget: Option[BigDecimal] = if (random.nextBoolean()) None else Some(BigDecimal.decimal(random.nextDouble())), + companyId: Option[CompanyId] = None, + id: Defaulted[DepartmentId] = Defaulted.UseDefault + ): ConnectionIO[DepartmentRow] = (new DepartmentRepoImpl).insert(new DepartmentRowUnsaved(name = name, budget = budget, companyId = companyId, id = id)) + def frontpageEmployee(personId: PersonId, + salary: Option[BigDecimal] = if (random.nextBoolean()) None else Some(BigDecimal.decimal(random.nextDouble())), + id: Defaulted[EmployeeId] = Defaulted.UseDefault + ): ConnectionIO[EmployeeRow] = (new EmployeeRepoImpl).insert(new EmployeeRowUnsaved(personId = personId, salary = salary, id = id)) + def frontpageLocation(name: String = random.alphanumeric.take(20).mkString, + position: Option[TypoPoint] = None, + area: Option[TypoPolygon] = None, + ipRange: Option[TypoInet] = None, + id: Defaulted[LocationId] = Defaulted.UseDefault, + metadata: Defaulted[Option[TypoJsonb]] = Defaulted.UseDefault + ): ConnectionIO[LocationRow] = (new LocationRepoImpl).insert(new LocationRowUnsaved(name = name, position = position, area = area, ipRange = ipRange, id = id, metadata = metadata)) + def frontpageOrder(userId: Option[UserId] = None, + productId: Option[ProductId] = None, + total: BigDecimal = BigDecimal.decimal(random.nextDouble()), + shippedAt: Option[TypoLocalDateTime] = if (random.nextBoolean()) None else Some(TypoLocalDateTime(LocalDateTime.of(LocalDate.ofEpochDay(random.nextInt(30000).toLong), LocalTime.ofSecondOfDay(random.nextInt(24 * 60 * 60).toLong)))), + id: Defaulted[OrderId] = Defaulted.UseDefault, + status: Defaulted[Option[OrderStatus]] = Defaulted.UseDefault, + createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault + ): ConnectionIO[OrderRow] = (new OrderRepoImpl).insert(new OrderRowUnsaved(userId = userId, productId = productId, total = total, shippedAt = shippedAt, id = id, status = status, createdAt = createdAt)) + def frontpageOrderItem(orderId: Option[OrderId] = None, + productId: Option[ProductId] = None, + quantity: Int = random.nextInt(), + price: BigDecimal = BigDecimal.decimal(random.nextDouble()), + shippedAt: Option[TypoLocalDateTime] = if (random.nextBoolean()) None else Some(TypoLocalDateTime(LocalDateTime.of(LocalDate.ofEpochDay(random.nextInt(30000).toLong), LocalTime.ofSecondOfDay(random.nextInt(24 * 60 * 60).toLong)))), + id: Defaulted[OrderItemId] = Defaulted.UseDefault + ): ConnectionIO[OrderItemRow] = (new OrderItemRepoImpl).insert(new OrderItemRowUnsaved(orderId = orderId, productId = productId, quantity = quantity, price = price, shippedAt = shippedAt, id = id)) + def frontpagePermission(name: String = random.alphanumeric.take(20).mkString, id: Defaulted[PermissionId] = Defaulted.UseDefault): ConnectionIO[PermissionRow] = (new PermissionRepoImpl).insert(new PermissionRowUnsaved(name = name, id = id)) + def frontpagePerson(name: String = random.alphanumeric.take(20).mkString, + addressId: Option[AddressId] = None, + id: Defaulted[PersonId] = Defaulted.UseDefault, + createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault + ): ConnectionIO[PersonRow] = (new PersonRepoImpl).insert(new PersonRowUnsaved(name = name, addressId = addressId, id = id, createdAt = createdAt)) + def frontpageProduct(name: String = random.alphanumeric.take(20).mkString, + price: BigDecimal = BigDecimal.decimal(random.nextDouble()), + lastRestocked: Option[TypoLocalDateTime] = if (random.nextBoolean()) None else Some(TypoLocalDateTime(LocalDateTime.of(LocalDate.ofEpochDay(random.nextInt(30000).toLong), LocalTime.ofSecondOfDay(random.nextInt(24 * 60 * 60).toLong)))), + id: Defaulted[ProductId] = Defaulted.UseDefault, + inStock: Defaulted[Option[Boolean]] = Defaulted.UseDefault, + quantity: Defaulted[Option[Int]] = Defaulted.UseDefault, + lastModified: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault, + tags: Defaulted[Option[Array[String]]] = Defaulted.UseDefault, + categories: Defaulted[Option[Array[Int]]] = Defaulted.UseDefault, + prices: Defaulted[Option[Array[BigDecimal]]] = Defaulted.UseDefault, + attributes: Defaulted[Option[Array[TypoJsonb]]] = Defaulted.UseDefault + ): ConnectionIO[ProductRow] = (new ProductRepoImpl).insert(new ProductRowUnsaved(name = name, price = price, lastRestocked = lastRestocked, id = id, inStock = inStock, quantity = quantity, lastModified = lastModified, tags = tags, categories = categories, prices = prices, attributes = attributes)) + def frontpageProductCategory(productId: ProductId, categoryId: CategoryId): ConnectionIO[ProductCategoryRow] = (new ProductCategoryRepoImpl).insert(new ProductCategoryRow(productId = productId, categoryId = categoryId)) + def frontpageRole(name: String = random.alphanumeric.take(20).mkString, id: Defaulted[RoleId] = Defaulted.UseDefault): ConnectionIO[RoleRow] = (new RoleRepoImpl).insert(new RoleRowUnsaved(name = name, id = id)) + def frontpageUser(email: Email = domainInsert.frontpageEmail(random), + name: String = random.alphanumeric.take(20).mkString, + departmentId: Option[DepartmentId] = None, + managerId: Option[UserId] = None, + id: Defaulted[UserId] = Defaulted.UseDefault, + createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault, + status: Defaulted[Option[UserStatus]] = Defaulted.UseDefault, + verified: Defaulted[Option[Boolean]] = Defaulted.UseDefault, + role: Defaulted[Option[UserRole]] = Defaulted.UseDefault + ): ConnectionIO[UserRow] = (new UserRepoImpl).insert(new UserRowUnsaved(email = email, name = name, departmentId = departmentId, managerId = managerId, id = id, createdAt = createdAt, status = status, verified = verified, role = role)) + def frontpageUserPermission(userId: UserId, + permissionId: PermissionId, + grantedAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault + ): ConnectionIO[UserPermissionRow] = (new UserPermissionRepoImpl).insert(new UserPermissionRowUnsaved(userId = userId, permissionId = permissionId, grantedAt = grantedAt)) def humanresourcesDepartment(name: Name = domainInsert.publicName(random), groupname: Name = domainInsert.publicName(random), - departmentid: Defaulted[DepartmentId] = Defaulted.UseDefault, + departmentid: Defaulted[adventureworks.humanresources.department.DepartmentId] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault - ): ConnectionIO[DepartmentRow] = (new DepartmentRepoImpl).insert(new DepartmentRowUnsaved(name = name, groupname = groupname, departmentid = departmentid, modifieddate = modifieddate)) + ): ConnectionIO[adventureworks.humanresources.department.DepartmentRow] = (new adventureworks.humanresources.department.DepartmentRepoImpl).insert(new adventureworks.humanresources.department.DepartmentRowUnsaved(name = name, groupname = groupname, departmentid = departmentid, modifieddate = modifieddate)) def humanresourcesEmployee(businessentityid: BusinessentityId, birthdate: TypoLocalDate, maritalstatus: /* bpchar, max 1 chars */ String, @@ -351,9 +467,9 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault, organizationnode: Defaulted[Option[String]] = Defaulted.UseDefault - ): ConnectionIO[EmployeeRow] = (new EmployeeRepoImpl).insert(new EmployeeRowUnsaved(businessentityid = businessentityid, birthdate = birthdate, maritalstatus = maritalstatus, gender = gender, hiredate = hiredate, nationalidnumber = nationalidnumber, loginid = loginid, jobtitle = jobtitle, salariedflag = salariedflag, vacationhours = vacationhours, sickleavehours = sickleavehours, currentflag = currentflag, rowguid = rowguid, modifieddate = modifieddate, organizationnode = organizationnode)) + ): ConnectionIO[adventureworks.humanresources.employee.EmployeeRow] = (new adventureworks.humanresources.employee.EmployeeRepoImpl).insert(new adventureworks.humanresources.employee.EmployeeRowUnsaved(businessentityid = businessentityid, birthdate = birthdate, maritalstatus = maritalstatus, gender = gender, hiredate = hiredate, nationalidnumber = nationalidnumber, loginid = loginid, jobtitle = jobtitle, salariedflag = salariedflag, vacationhours = vacationhours, sickleavehours = sickleavehours, currentflag = currentflag, rowguid = rowguid, modifieddate = modifieddate, organizationnode = organizationnode)) def humanresourcesEmployeedepartmenthistory(businessentityid: BusinessentityId, - departmentid: DepartmentId, + departmentid: adventureworks.humanresources.department.DepartmentId, shiftid: ShiftId, startdate: TypoLocalDate, enddate: Option[TypoLocalDate] = None, @@ -382,10 +498,10 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { city: /* max 30 chars */ String = random.alphanumeric.take(20).mkString, postalcode: /* max 15 chars */ String = random.alphanumeric.take(15).mkString, spatiallocation: Option[TypoBytea] = None, - addressid: Defaulted[AddressId] = Defaulted.UseDefault, + addressid: Defaulted[adventureworks.person.address.AddressId] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault - ): ConnectionIO[AddressRow] = (new AddressRepoImpl).insert(new AddressRowUnsaved(stateprovinceid = stateprovinceid, addressline1 = addressline1, addressline2 = addressline2, city = city, postalcode = postalcode, spatiallocation = spatiallocation, addressid = addressid, rowguid = rowguid, modifieddate = modifieddate)) + ): ConnectionIO[adventureworks.person.address.AddressRow] = (new adventureworks.person.address.AddressRepoImpl).insert(new adventureworks.person.address.AddressRowUnsaved(stateprovinceid = stateprovinceid, addressline1 = addressline1, addressline2 = addressline2, city = city, postalcode = postalcode, spatiallocation = spatiallocation, addressid = addressid, rowguid = rowguid, modifieddate = modifieddate)) def personAddresstype(name: Name = domainInsert.publicName(random), addresstypeid: Defaulted[AddresstypeId] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, @@ -396,7 +512,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ConnectionIO[BusinessentityRow] = (new BusinessentityRepoImpl).insert(new BusinessentityRowUnsaved(businessentityid = businessentityid, rowguid = rowguid, modifieddate = modifieddate)) def personBusinessentityaddress(businessentityid: BusinessentityId, - addressid: AddressId, + addressid: adventureworks.person.address.AddressId, addresstypeid: AddresstypeId, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault @@ -440,7 +556,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { emailpromotion: Defaulted[Int] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault - ): ConnectionIO[PersonRow] = (new PersonRepoImpl).insert(new PersonRowUnsaved(businessentityid = businessentityid, persontype = persontype, firstname = firstname, title = title, middlename = middlename, lastname = lastname, suffix = suffix, additionalcontactinfo = additionalcontactinfo, demographics = demographics, namestyle = namestyle, emailpromotion = emailpromotion, rowguid = rowguid, modifieddate = modifieddate)) + ): ConnectionIO[adventureworks.person.person.PersonRow] = (new adventureworks.person.person.PersonRepoImpl).insert(new adventureworks.person.person.PersonRowUnsaved(businessentityid = businessentityid, persontype = persontype, firstname = firstname, title = title, middlename = middlename, lastname = lastname, suffix = suffix, additionalcontactinfo = additionalcontactinfo, demographics = demographics, namestyle = namestyle, emailpromotion = emailpromotion, rowguid = rowguid, modifieddate = modifieddate)) def personPersonphone(businessentityid: BusinessentityId, phonenumbertypeid: PhonenumbertypeId, phonenumber: Phone = domainInsert.publicPhone(random), @@ -459,10 +575,10 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ConnectionIO[StateprovinceRow] = (new StateprovinceRepoImpl).insert(new StateprovinceRowUnsaved(countryregioncode = countryregioncode, territoryid = territoryid, stateprovincecode = stateprovincecode, name = name, stateprovinceid = stateprovinceid, isonlystateprovinceflag = isonlystateprovinceflag, rowguid = rowguid, modifieddate = modifieddate)) - def productionBillofmaterials(componentid: ProductId, + def productionBillofmaterials(componentid: adventureworks.production.product.ProductId, unitmeasurecode: UnitmeasureId, bomlevel: TypoShort, - productassemblyid: Option[ProductId] = None, + productassemblyid: Option[adventureworks.production.product.ProductId] = None, enddate: Option[TypoLocalDateTime] = None, billofmaterialsid: Defaulted[Int] = Defaulted.UseDefault, startdate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault, @@ -492,11 +608,11 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ConnectionIO[IllustrationRow] = (new IllustrationRepoImpl).insert(new IllustrationRowUnsaved(diagram = diagram, illustrationid = illustrationid, modifieddate = modifieddate)) def productionLocation(name: Name = domainInsert.publicName(random), - locationid: Defaulted[LocationId] = Defaulted.UseDefault, + locationid: Defaulted[adventureworks.production.location.LocationId] = Defaulted.UseDefault, costrate: Defaulted[BigDecimal] = Defaulted.UseDefault, availability: Defaulted[BigDecimal] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault - ): ConnectionIO[LocationRow] = (new LocationRepoImpl).insert(new LocationRowUnsaved(name = name, locationid = locationid, costrate = costrate, availability = availability, modifieddate = modifieddate)) + ): ConnectionIO[adventureworks.production.location.LocationRow] = (new adventureworks.production.location.LocationRepoImpl).insert(new adventureworks.production.location.LocationRowUnsaved(name = name, locationid = locationid, costrate = costrate, availability = availability, modifieddate = modifieddate)) def productionProduct(safetystocklevel: TypoShort, reorderpoint: TypoShort, standardcost: BigDecimal, @@ -517,18 +633,18 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { productmodelid: Option[ProductmodelId] = None, sellenddate: Option[TypoLocalDateTime] = None, discontinueddate: Option[TypoLocalDateTime] = if (random.nextBoolean()) None else Some(TypoLocalDateTime(LocalDateTime.of(LocalDate.ofEpochDay(random.nextInt(30000).toLong), LocalTime.ofSecondOfDay(random.nextInt(24 * 60 * 60).toLong)))), - productid: Defaulted[ProductId] = Defaulted.UseDefault, + productid: Defaulted[adventureworks.production.product.ProductId] = Defaulted.UseDefault, makeflag: Defaulted[Flag] = Defaulted.UseDefault, finishedgoodsflag: Defaulted[Flag] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault - ): ConnectionIO[ProductRow] = (new ProductRepoImpl).insert(new ProductRowUnsaved(safetystocklevel = safetystocklevel, reorderpoint = reorderpoint, standardcost = standardcost, listprice = listprice, daystomanufacture = daystomanufacture, sellstartdate = sellstartdate, name = name, productnumber = productnumber, color = color, size = size, sizeunitmeasurecode = sizeunitmeasurecode, weightunitmeasurecode = weightunitmeasurecode, weight = weight, productline = productline, `class` = `class`, style = style, productsubcategoryid = productsubcategoryid, productmodelid = productmodelid, sellenddate = sellenddate, discontinueddate = discontinueddate, productid = productid, makeflag = makeflag, finishedgoodsflag = finishedgoodsflag, rowguid = rowguid, modifieddate = modifieddate)) + ): ConnectionIO[adventureworks.production.product.ProductRow] = (new adventureworks.production.product.ProductRepoImpl).insert(new adventureworks.production.product.ProductRowUnsaved(safetystocklevel = safetystocklevel, reorderpoint = reorderpoint, standardcost = standardcost, listprice = listprice, daystomanufacture = daystomanufacture, sellstartdate = sellstartdate, name = name, productnumber = productnumber, color = color, size = size, sizeunitmeasurecode = sizeunitmeasurecode, weightunitmeasurecode = weightunitmeasurecode, weight = weight, productline = productline, `class` = `class`, style = style, productsubcategoryid = productsubcategoryid, productmodelid = productmodelid, sellenddate = sellenddate, discontinueddate = discontinueddate, productid = productid, makeflag = makeflag, finishedgoodsflag = finishedgoodsflag, rowguid = rowguid, modifieddate = modifieddate)) def productionProductcategory(name: Name = domainInsert.publicName(random), productcategoryid: Defaulted[ProductcategoryId] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ConnectionIO[ProductcategoryRow] = (new ProductcategoryRepoImpl).insert(new ProductcategoryRowUnsaved(name = name, productcategoryid = productcategoryid, rowguid = rowguid, modifieddate = modifieddate)) - def productionProductcosthistory(productid: ProductId, + def productionProductcosthistory(productid: adventureworks.production.product.ProductId, startdate: TypoLocalDateTime, standardcost: BigDecimal, enddate: Option[TypoLocalDateTime] = None, @@ -539,19 +655,19 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ConnectionIO[ProductdescriptionRow] = (new ProductdescriptionRepoImpl).insert(new ProductdescriptionRowUnsaved(description = description, productdescriptionid = productdescriptionid, rowguid = rowguid, modifieddate = modifieddate)) - def productionProductdocument(productid: ProductId, + def productionProductdocument(productid: adventureworks.production.product.ProductId, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault, documentnode: Defaulted[DocumentId] = Defaulted.UseDefault ): ConnectionIO[ProductdocumentRow] = (new ProductdocumentRepoImpl).insert(new ProductdocumentRowUnsaved(productid = productid, modifieddate = modifieddate, documentnode = documentnode)) - def productionProductinventory(productid: ProductId, - locationid: LocationId, + def productionProductinventory(productid: adventureworks.production.product.ProductId, + locationid: adventureworks.production.location.LocationId, bin: TypoShort, shelf: /* max 10 chars */ String = random.alphanumeric.take(10).mkString, quantity: Defaulted[TypoShort] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ConnectionIO[ProductinventoryRow] = (new ProductinventoryRepoImpl).insert(new ProductinventoryRowUnsaved(productid = productid, locationid = locationid, bin = bin, shelf = shelf, quantity = quantity, rowguid = rowguid, modifieddate = modifieddate)) - def productionProductlistpricehistory(productid: ProductId, + def productionProductlistpricehistory(productid: adventureworks.production.product.ProductId, startdate: TypoLocalDateTime, listprice: BigDecimal, enddate: Option[TypoLocalDateTime] = None, @@ -580,12 +696,12 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { productphotoid: Defaulted[ProductphotoId] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ConnectionIO[ProductphotoRow] = (new ProductphotoRepoImpl).insert(new ProductphotoRowUnsaved(thumbnailphoto = thumbnailphoto, thumbnailphotofilename = thumbnailphotofilename, largephoto = largephoto, largephotofilename = largephotofilename, productphotoid = productphotoid, modifieddate = modifieddate)) - def productionProductproductphoto(productid: ProductId, + def productionProductproductphoto(productid: adventureworks.production.product.ProductId, productphotoid: ProductphotoId, primary: Defaulted[Flag] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ConnectionIO[ProductproductphotoRow] = (new ProductproductphotoRepoImpl).insert(new ProductproductphotoRowUnsaved(productid = productid, productphotoid = productphotoid, primary = primary, modifieddate = modifieddate)) - def productionProductreview(productid: ProductId, + def productionProductreview(productid: adventureworks.production.product.ProductId, rating: Int, reviewername: Name = domainInsert.publicName(random), emailaddress: /* max 50 chars */ String = random.alphanumeric.take(20).mkString, @@ -604,7 +720,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { scrapreasonid: Defaulted[ScrapreasonId] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ConnectionIO[ScrapreasonRow] = (new ScrapreasonRepoImpl).insert(new ScrapreasonRowUnsaved(name = name, scrapreasonid = scrapreasonid, modifieddate = modifieddate)) - def productionTransactionhistory(productid: ProductId, + def productionTransactionhistory(productid: adventureworks.production.product.ProductId, transactiontype: /* bpchar, max 1 chars */ String, referenceorderid: Int = random.nextInt(), quantity: Int = random.nextInt(), @@ -628,7 +744,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { name: Name = domainInsert.publicName(random), modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ConnectionIO[UnitmeasureRow] = (new UnitmeasureRepoImpl).insert(new UnitmeasureRowUnsaved(unitmeasurecode = unitmeasurecode, name = name, modifieddate = modifieddate)) - def productionWorkorder(productid: ProductId, + def productionWorkorder(productid: adventureworks.production.product.ProductId, orderqty: Int, scrappedqty: TypoShort, startdate: TypoLocalDateTime, @@ -639,7 +755,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ConnectionIO[WorkorderRow] = (new WorkorderRepoImpl).insert(new WorkorderRowUnsaved(productid = productid, orderqty = orderqty, scrappedqty = scrappedqty, startdate = startdate, enddate = enddate, duedate = duedate, scrapreasonid = scrapreasonid, workorderid = workorderid, modifieddate = modifieddate)) def productionWorkorderrouting(workorderid: WorkorderId, - locationid: LocationId, + locationid: adventureworks.production.location.LocationId, scheduledstartdate: TypoLocalDateTime, scheduledenddate: TypoLocalDateTime, plannedcost: BigDecimal, @@ -821,7 +937,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { verifiedOn: Option[TypoInstant] = if (random.nextBoolean()) None else Some(TypoInstant(Instant.ofEpochMilli(1000000000000L + random.nextLong(1000000000000L)))), createdAt: Defaulted[TypoInstant] = Defaulted.UseDefault ): ConnectionIO[UsersRow] = (new UsersRepoImpl).insert(new UsersRowUnsaved(email = email, userId = userId, name = name, lastName = lastName, password = password, verifiedOn = verifiedOn, createdAt = createdAt)) - def purchasingProductvendor(productid: ProductId, + def purchasingProductvendor(productid: adventureworks.production.product.ProductId, businessentityid: BusinessentityId, averageleadtime: Int, standardprice: BigDecimal, @@ -888,10 +1004,10 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { def salesCustomer(personid: Option[BusinessentityId] = None, storeid: Option[BusinessentityId] = None, territoryid: Option[SalesterritoryId] = None, - customerid: Defaulted[CustomerId] = Defaulted.UseDefault, + customerid: Defaulted[adventureworks.sales.customer.CustomerId] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault - ): ConnectionIO[CustomerRow] = (new CustomerRepoImpl).insert(new CustomerRowUnsaved(personid = personid, storeid = storeid, territoryid = territoryid, customerid = customerid, rowguid = rowguid, modifieddate = modifieddate)) + ): ConnectionIO[adventureworks.sales.customer.CustomerRow] = (new adventureworks.sales.customer.CustomerRepoImpl).insert(new adventureworks.sales.customer.CustomerRowUnsaved(personid = personid, storeid = storeid, territoryid = territoryid, customerid = customerid, rowguid = rowguid, modifieddate = modifieddate)) def salesPersoncreditcard(businessentityid: BusinessentityId, creditcardid: /* user-picked */ CustomCreditcardId, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault @@ -907,9 +1023,9 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ConnectionIO[SalesorderdetailRow] = (new SalesorderdetailRepoImpl).insert(new SalesorderdetailRowUnsaved(salesorderid = salesorderid, carriertrackingnumber = carriertrackingnumber, orderqty = orderqty, productid = SpecialofferproductId.productid, specialofferid = SpecialofferproductId.specialofferid, unitprice = unitprice, salesorderdetailid = salesorderdetailid, unitpricediscount = unitpricediscount, rowguid = rowguid, modifieddate = modifieddate)) def salesSalesorderheader(duedate: TypoLocalDateTime, - customerid: CustomerId, - billtoaddressid: AddressId, - shiptoaddressid: AddressId, + customerid: adventureworks.sales.customer.CustomerId, + billtoaddressid: adventureworks.person.address.AddressId, + shiptoaddressid: adventureworks.person.address.AddressId, shipmethodid: ShipmethodId, shipdate: Option[TypoLocalDateTime] = None, purchaseordernumber: Option[OrderNumber] = if (random.nextBoolean()) None else Some(domainInsert.publicOrderNumber(random)), @@ -983,7 +1099,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ConnectionIO[SalesterritoryhistoryRow] = (new SalesterritoryhistoryRepoImpl).insert(new SalesterritoryhistoryRowUnsaved(businessentityid = businessentityid, territoryid = territoryid, startdate = startdate, enddate = enddate, rowguid = rowguid, modifieddate = modifieddate)) - def salesShoppingcartitem(productid: ProductId, + def salesShoppingcartitem(productid: adventureworks.production.product.ProductId, shoppingcartid: /* max 50 chars */ String = random.alphanumeric.take(20).mkString, shoppingcartitemid: Defaulted[ShoppingcartitemId] = Defaulted.UseDefault, quantity: Defaulted[Int] = Defaulted.UseDefault, @@ -1003,7 +1119,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ConnectionIO[SpecialofferRow] = (new SpecialofferRepoImpl).insert(new SpecialofferRowUnsaved(startdate = startdate, enddate = enddate, description = description, `type` = `type`, category = category, maxqty = maxqty, specialofferid = specialofferid, discountpct = discountpct, minqty = minqty, rowguid = rowguid, modifieddate = modifieddate)) def salesSpecialofferproduct(specialofferid: SpecialofferId, - productid: ProductId, + productid: adventureworks.production.product.ProductId, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ConnectionIO[SpecialofferproductRow] = (new SpecialofferproductRepoImpl).insert(new SpecialofferproductRowUnsaved(specialofferid = specialofferid, productid = productid, rowguid = rowguid, modifieddate = modifieddate)) diff --git a/typo-tester-doobie/src/scala/adventureworks/DomainInsert.scala b/typo-tester-doobie/src/scala/adventureworks/DomainInsert.scala index 3906b94e03..8e5b3d1464 100644 --- a/typo-tester-doobie/src/scala/adventureworks/DomainInsert.scala +++ b/typo-tester-doobie/src/scala/adventureworks/DomainInsert.scala @@ -13,4 +13,5 @@ object DomainInsert extends TestDomainInsert { override def publicPhone(random: Random): Phone = Phone(random.nextString(10)) override def publicShortText(random: Random): ShortText = ShortText(random.nextString(10)) override def publicOrderNumber(random: Random): OrderNumber = OrderNumber(random.nextString(10)) + override def frontpageEmail(random: Random): frontpage.Email = frontpage.Email(s"user${random.nextInt(1000)}@example.com") } diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/TestDomainInsert.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/TestDomainInsert.scala index a8e733e7a6..4d2a2dd08c 100644 --- a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/TestDomainInsert.scala +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/TestDomainInsert.scala @@ -5,6 +5,7 @@ */ package adventureworks +import adventureworks.frontpage.Email import adventureworks.public.AccountNumber import adventureworks.public.Flag import adventureworks.public.Mydomain @@ -16,6 +17,10 @@ import adventureworks.public.ShortText import scala.util.Random trait TestDomainInsert { + /** Domain `frontpage.email` + * Constraint: CHECK ((VALUE ~ '^[^@]+@[^@]+\.[^@]+$'::text)) + */ + def frontpageEmail(random: Random): Email /** Domain `public.AccountNumber` * No constraint */ diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/Email.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/Email.scala new file mode 100644 index 0000000000..2bf3b6be2d --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/Email.scala @@ -0,0 +1,38 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage + +import java.sql.Types +import typo.dsl.Bijection +import typo.dsl.PGType +import zio.jdbc.JdbcDecoder +import zio.jdbc.JdbcEncoder +import zio.jdbc.SqlFragment.Setter +import zio.json.JsonDecoder +import zio.json.JsonEncoder + +/** Domain `frontpage.email` + * Constraint: CHECK ((VALUE ~ '^[^@]+@[^@]+\.[^@]+$'::text)) + */ +case class Email(value: String) +object Email { + implicit lazy val arrayJdbcDecoder: JdbcDecoder[Array[Email]] = adventureworks.StringArrayDecoder.map(_.map(Email.apply)) + implicit lazy val arrayJdbcEncoder: JdbcEncoder[Array[Email]] = adventureworks.StringArrayEncoder.contramap(_.map(_.value)) + implicit lazy val arraySetter: Setter[Array[Email]] = adventureworks.StringArraySetter.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[Email, String] = Bijection[Email, String](_.value)(Email.apply) + implicit lazy val jdbcDecoder: JdbcDecoder[Email] = JdbcDecoder.stringDecoder.map(Email.apply) + implicit lazy val jdbcEncoder: JdbcEncoder[Email] = JdbcEncoder.stringEncoder.contramap(_.value) + implicit lazy val jsonDecoder: JsonDecoder[Email] = JsonDecoder.string.map(Email.apply) + implicit lazy val jsonEncoder: JsonEncoder[Email] = JsonEncoder.string.contramap(_.value) + implicit lazy val ordering: Ordering[Email] = Ordering.by(_.value) + implicit lazy val pgType: PGType[Email] = PGType.instance(""""frontpage"."email"""", Types.OTHER) + implicit lazy val setter: Setter[Email] = Setter.stringSetter.contramap(_.value) + implicit lazy val text: Text[Email] = new Text[Email] { + override def unsafeEncode(v: Email, sb: StringBuilder) = Text.stringInstance.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: Email, sb: StringBuilder) = Text.stringInstance.unsafeArrayEncode(v.value, sb) + } +} \ No newline at end of file diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/OrderStatus.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/OrderStatus.scala new file mode 100644 index 0000000000..03c670f6ab --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/OrderStatus.scala @@ -0,0 +1,74 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage + +import java.sql.ResultSet +import java.sql.Types +import typo.dsl.PGType +import zio.jdbc.JdbcDecoder +import zio.jdbc.JdbcDecoderError +import zio.jdbc.JdbcEncoder +import zio.jdbc.SqlFragment.Setter +import zio.json.JsonDecoder +import zio.json.JsonEncoder + +/** Enum `frontpage.order_status` + * - pending + * - active + * - shipped + * - cancelled + */ +sealed abstract class OrderStatus(val value: String) + +object OrderStatus { + def apply(str: String): Either[String, OrderStatus] = + ByName.get(str).toRight(s"'$str' does not match any of the following legal values: $Names") + def force(str: String): OrderStatus = + apply(str) match { + case Left(msg) => sys.error(msg) + case Right(value) => value + } + case object pending extends OrderStatus("pending") + case object active extends OrderStatus("active") + case object shipped extends OrderStatus("shipped") + case object cancelled extends OrderStatus("cancelled") + val All: List[OrderStatus] = List(pending, active, shipped, cancelled) + val Names: String = All.map(_.value).mkString(", ") + val ByName: Map[String, OrderStatus] = All.map(x => (x.value, x)).toMap + + implicit lazy val arrayJdbcDecoder: JdbcDecoder[Array[OrderStatus]] = adventureworks.StringArrayDecoder.map(a => if (a == null) null else a.map(force)) + implicit lazy val arrayJdbcEncoder: JdbcEncoder[Array[OrderStatus]] = JdbcEncoder.singleParamEncoder(using arraySetter) + implicit lazy val arraySetter: Setter[Array[OrderStatus]] = Setter.forSqlType[Array[OrderStatus]]( + (ps, i, v) => ps.setArray(i, ps.getConnection.createArrayOf("frontpage.order_status", v.map(x => x.value))), + java.sql.Types.ARRAY + ) + implicit lazy val jdbcDecoder: JdbcDecoder[OrderStatus] = JdbcDecoder.stringDecoder.flatMap { s => + new JdbcDecoder[OrderStatus] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, OrderStatus) = { + def error(msg: String): JdbcDecoderError = + JdbcDecoderError( + message = s"Error decoding OrderStatus from ResultSet", + cause = new RuntimeException(msg), + metadata = rs.getMetaData, + row = rs.getRow + ) + + OrderStatus.apply(s).fold(e => throw error(e), (columIndex, _)) + } + } + } + implicit lazy val jdbcEncoder: JdbcEncoder[OrderStatus] = JdbcEncoder.stringEncoder.contramap(_.value) + implicit lazy val jsonDecoder: JsonDecoder[OrderStatus] = JsonDecoder.string.mapOrFail(OrderStatus.apply) + implicit lazy val jsonEncoder: JsonEncoder[OrderStatus] = JsonEncoder.string.contramap(_.value) + implicit lazy val ordering: Ordering[OrderStatus] = Ordering.by(_.value) + implicit lazy val pgType: PGType[OrderStatus] = PGType.instance[OrderStatus]("frontpage.order_status", Types.OTHER) + implicit lazy val setter: Setter[OrderStatus] = Setter.stringSetter.contramap(_.value) + implicit lazy val text: Text[OrderStatus] = new Text[OrderStatus] { + override def unsafeEncode(v: OrderStatus, sb: StringBuilder) = Text.stringInstance.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: OrderStatus, sb: StringBuilder) = Text.stringInstance.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/UserRole.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/UserRole.scala new file mode 100644 index 0000000000..fa8cf7f3c2 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/UserRole.scala @@ -0,0 +1,72 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage + +import java.sql.ResultSet +import java.sql.Types +import typo.dsl.PGType +import zio.jdbc.JdbcDecoder +import zio.jdbc.JdbcDecoderError +import zio.jdbc.JdbcEncoder +import zio.jdbc.SqlFragment.Setter +import zio.json.JsonDecoder +import zio.json.JsonEncoder + +/** Enum `frontpage.user_role` + * - admin + * - manager + * - employee + */ +sealed abstract class UserRole(val value: String) + +object UserRole { + def apply(str: String): Either[String, UserRole] = + ByName.get(str).toRight(s"'$str' does not match any of the following legal values: $Names") + def force(str: String): UserRole = + apply(str) match { + case Left(msg) => sys.error(msg) + case Right(value) => value + } + case object admin extends UserRole("admin") + case object manager extends UserRole("manager") + case object employee extends UserRole("employee") + val All: List[UserRole] = List(admin, manager, employee) + val Names: String = All.map(_.value).mkString(", ") + val ByName: Map[String, UserRole] = All.map(x => (x.value, x)).toMap + + implicit lazy val arrayJdbcDecoder: JdbcDecoder[Array[UserRole]] = adventureworks.StringArrayDecoder.map(a => if (a == null) null else a.map(force)) + implicit lazy val arrayJdbcEncoder: JdbcEncoder[Array[UserRole]] = JdbcEncoder.singleParamEncoder(using arraySetter) + implicit lazy val arraySetter: Setter[Array[UserRole]] = Setter.forSqlType[Array[UserRole]]( + (ps, i, v) => ps.setArray(i, ps.getConnection.createArrayOf("frontpage.user_role", v.map(x => x.value))), + java.sql.Types.ARRAY + ) + implicit lazy val jdbcDecoder: JdbcDecoder[UserRole] = JdbcDecoder.stringDecoder.flatMap { s => + new JdbcDecoder[UserRole] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, UserRole) = { + def error(msg: String): JdbcDecoderError = + JdbcDecoderError( + message = s"Error decoding UserRole from ResultSet", + cause = new RuntimeException(msg), + metadata = rs.getMetaData, + row = rs.getRow + ) + + UserRole.apply(s).fold(e => throw error(e), (columIndex, _)) + } + } + } + implicit lazy val jdbcEncoder: JdbcEncoder[UserRole] = JdbcEncoder.stringEncoder.contramap(_.value) + implicit lazy val jsonDecoder: JsonDecoder[UserRole] = JsonDecoder.string.mapOrFail(UserRole.apply) + implicit lazy val jsonEncoder: JsonEncoder[UserRole] = JsonEncoder.string.contramap(_.value) + implicit lazy val ordering: Ordering[UserRole] = Ordering.by(_.value) + implicit lazy val pgType: PGType[UserRole] = PGType.instance[UserRole]("frontpage.user_role", Types.OTHER) + implicit lazy val setter: Setter[UserRole] = Setter.stringSetter.contramap(_.value) + implicit lazy val text: Text[UserRole] = new Text[UserRole] { + override def unsafeEncode(v: UserRole, sb: StringBuilder) = Text.stringInstance.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: UserRole, sb: StringBuilder) = Text.stringInstance.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/UserStatus.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/UserStatus.scala new file mode 100644 index 0000000000..981c2180bb --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/UserStatus.scala @@ -0,0 +1,72 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage + +import java.sql.ResultSet +import java.sql.Types +import typo.dsl.PGType +import zio.jdbc.JdbcDecoder +import zio.jdbc.JdbcDecoderError +import zio.jdbc.JdbcEncoder +import zio.jdbc.SqlFragment.Setter +import zio.json.JsonDecoder +import zio.json.JsonEncoder + +/** Enum `frontpage.user_status` + * - active + * - inactive + * - suspended + */ +sealed abstract class UserStatus(val value: String) + +object UserStatus { + def apply(str: String): Either[String, UserStatus] = + ByName.get(str).toRight(s"'$str' does not match any of the following legal values: $Names") + def force(str: String): UserStatus = + apply(str) match { + case Left(msg) => sys.error(msg) + case Right(value) => value + } + case object active extends UserStatus("active") + case object inactive extends UserStatus("inactive") + case object suspended extends UserStatus("suspended") + val All: List[UserStatus] = List(active, inactive, suspended) + val Names: String = All.map(_.value).mkString(", ") + val ByName: Map[String, UserStatus] = All.map(x => (x.value, x)).toMap + + implicit lazy val arrayJdbcDecoder: JdbcDecoder[Array[UserStatus]] = adventureworks.StringArrayDecoder.map(a => if (a == null) null else a.map(force)) + implicit lazy val arrayJdbcEncoder: JdbcEncoder[Array[UserStatus]] = JdbcEncoder.singleParamEncoder(using arraySetter) + implicit lazy val arraySetter: Setter[Array[UserStatus]] = Setter.forSqlType[Array[UserStatus]]( + (ps, i, v) => ps.setArray(i, ps.getConnection.createArrayOf("frontpage.user_status", v.map(x => x.value))), + java.sql.Types.ARRAY + ) + implicit lazy val jdbcDecoder: JdbcDecoder[UserStatus] = JdbcDecoder.stringDecoder.flatMap { s => + new JdbcDecoder[UserStatus] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, UserStatus) = { + def error(msg: String): JdbcDecoderError = + JdbcDecoderError( + message = s"Error decoding UserStatus from ResultSet", + cause = new RuntimeException(msg), + metadata = rs.getMetaData, + row = rs.getRow + ) + + UserStatus.apply(s).fold(e => throw error(e), (columIndex, _)) + } + } + } + implicit lazy val jdbcEncoder: JdbcEncoder[UserStatus] = JdbcEncoder.stringEncoder.contramap(_.value) + implicit lazy val jsonDecoder: JsonDecoder[UserStatus] = JsonDecoder.string.mapOrFail(UserStatus.apply) + implicit lazy val jsonEncoder: JsonEncoder[UserStatus] = JsonEncoder.string.contramap(_.value) + implicit lazy val ordering: Ordering[UserStatus] = Ordering.by(_.value) + implicit lazy val pgType: PGType[UserStatus] = PGType.instance[UserStatus]("frontpage.user_status", Types.OTHER) + implicit lazy val setter: Setter[UserStatus] = Setter.stringSetter.contramap(_.value) + implicit lazy val text: Text[UserStatus] = new Text[UserStatus] { + override def unsafeEncode(v: UserStatus, sb: StringBuilder) = Text.stringInstance.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: UserStatus, sb: StringBuilder) = Text.stringInstance.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressFields.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressFields.scala new file mode 100644 index 0000000000..ad9a616933 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressFields.scala @@ -0,0 +1,42 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.Structure.Relation + +trait AddressFields { + def id: IdField[AddressId, AddressRow] + def city: Field[String, AddressRow] + def country: Field[String, AddressRow] +} + +object AddressFields { + lazy val structure: Relation[AddressFields, AddressRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[AddressFields, AddressRow] { + + override lazy val fields: AddressFields = new AddressFields { + override def id = IdField[AddressId, AddressRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def city = Field[String, AddressRow](_path, "city", None, None, x => x.city, (row, value) => row.copy(city = value)) + override def country = Field[String, AddressRow](_path, "country", None, None, x => x.country, (row, value) => row.copy(country = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, AddressRow]] = + List[FieldLikeNoHkt[?, AddressRow]](fields.id, fields.city, fields.country) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressId.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressId.scala new file mode 100644 index 0000000000..95017d979f --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import adventureworks.customtypes.TypoUUID +import typo.dsl.Bijection +import typo.dsl.PGType +import zio.jdbc.JdbcDecoder +import zio.jdbc.JdbcEncoder +import zio.jdbc.SqlFragment.Setter +import zio.json.JsonDecoder +import zio.json.JsonEncoder + +/** Type for the primary key of table `frontpage.address` */ +case class AddressId(value: TypoUUID) extends AnyVal +object AddressId { + implicit lazy val arrayJdbcDecoder: JdbcDecoder[Array[AddressId]] = JdbcDecoder[Array[TypoUUID]].map(_.map(AddressId.apply)) + implicit lazy val arrayJdbcEncoder: JdbcEncoder[Array[AddressId]] = JdbcEncoder[Array[TypoUUID]].contramap(_.map(_.value)) + implicit lazy val arraySetter: Setter[Array[AddressId]] = TypoUUID.arraySetter.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[AddressId, TypoUUID] = Bijection[AddressId, TypoUUID](_.value)(AddressId.apply) + implicit lazy val jdbcDecoder: JdbcDecoder[AddressId] = TypoUUID.jdbcDecoder.map(AddressId.apply) + implicit lazy val jdbcEncoder: JdbcEncoder[AddressId] = TypoUUID.jdbcEncoder.contramap(_.value) + implicit lazy val jsonDecoder: JsonDecoder[AddressId] = TypoUUID.jsonDecoder.map(AddressId.apply) + implicit lazy val jsonEncoder: JsonEncoder[AddressId] = TypoUUID.jsonEncoder.contramap(_.value) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[AddressId] = Ordering.by(_.value) + implicit lazy val pgType: PGType[AddressId] = TypoUUID.pgType.as + implicit lazy val setter: Setter[AddressId] = TypoUUID.setter.contramap(_.value) + implicit lazy val text: Text[AddressId] = new Text[AddressId] { + override def unsafeEncode(v: AddressId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: AddressId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressRepo.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressRepo.scala new file mode 100644 index 0000000000..acff037e28 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressRepo.scala @@ -0,0 +1,38 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +trait AddressRepo { + def delete: DeleteBuilder[AddressFields, AddressRow] + def deleteById(id: AddressId): ZIO[ZConnection, Throwable, Boolean] + def deleteByIds(ids: Array[AddressId]): ZIO[ZConnection, Throwable, Long] + def insert(unsaved: AddressRow): ZIO[ZConnection, Throwable, AddressRow] + def insert(unsaved: AddressRowUnsaved): ZIO[ZConnection, Throwable, AddressRow] + def insertStreaming(unsaved: ZStream[ZConnection, Throwable, AddressRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, AddressRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + def select: SelectBuilder[AddressFields, AddressRow] + def selectAll: ZStream[ZConnection, Throwable, AddressRow] + def selectById(id: AddressId): ZIO[ZConnection, Throwable, Option[AddressRow]] + def selectByIds(ids: Array[AddressId]): ZStream[ZConnection, Throwable, AddressRow] + def selectByIdsTracked(ids: Array[AddressId]): ZIO[ZConnection, Throwable, Map[AddressId, AddressRow]] + def update: UpdateBuilder[AddressFields, AddressRow] + def update(row: AddressRow): ZIO[ZConnection, Throwable, Boolean] + def upsert(unsaved: AddressRow): ZIO[ZConnection, Throwable, UpdateResult[AddressRow]] + // Not implementable for zio-jdbc: upsertBatch + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, AddressRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressRepoImpl.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressRepoImpl.scala new file mode 100644 index 0000000000..15940f72ec --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressRepoImpl.scala @@ -0,0 +1,124 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import adventureworks.customtypes.Defaulted +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.SqlFragment +import zio.jdbc.SqlFragment.Segment +import zio.jdbc.SqlFragment.Setter +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.jdbc.sqlInterpolator +import zio.stream.ZStream + +class AddressRepoImpl extends AddressRepo { + override def delete: DeleteBuilder[AddressFields, AddressRow] = { + DeleteBuilder(""""frontpage"."address"""", AddressFields.structure) + } + override def deleteById(id: AddressId): ZIO[ZConnection, Throwable, Boolean] = { + sql"""delete from "frontpage"."address" where "id" = ${Segment.paramSegment(id)(AddressId.setter)}""".delete.map(_ > 0) + } + override def deleteByIds(ids: Array[AddressId]): ZIO[ZConnection, Throwable, Long] = { + sql"""delete from "frontpage"."address" where "id" = ANY(${ids})""".delete + } + override def insert(unsaved: AddressRow): ZIO[ZConnection, Throwable, AddressRow] = { + sql"""insert into "frontpage"."address"("id", "city", "country") + values (${Segment.paramSegment(unsaved.id)(AddressId.setter)}::uuid, ${Segment.paramSegment(unsaved.city)(Setter.stringSetter)}, ${Segment.paramSegment(unsaved.country)(Setter.stringSetter)}) + returning "id", "city", "country" + """.insertReturning(using AddressRow.jdbcDecoder).map(_.updatedKeys.head) + } + override def insert(unsaved: AddressRowUnsaved): ZIO[ZConnection, Throwable, AddressRow] = { + val fs = List( + Some((sql""""city"""", sql"${Segment.paramSegment(unsaved.city)(Setter.stringSetter)}")), + Some((sql""""country"""", sql"${Segment.paramSegment(unsaved.country)(Setter.stringSetter)}")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""id"""", sql"${Segment.paramSegment(value: AddressId)(AddressId.setter)}::uuid")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."address" default values + returning "id", "city", "country" + """ + } else { + val names = fs.map { case (n, _) => n }.mkFragment(SqlFragment(", ")) + val values = fs.map { case (_, f) => f }.mkFragment(SqlFragment(", ")) + sql"""insert into "frontpage"."address"($names) values ($values) returning "id", "city", "country"""" + } + q.insertReturning(using AddressRow.jdbcDecoder).map(_.updatedKeys.head) + + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, AddressRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."address"("id", "city", "country") FROM STDIN""", batchSize, unsaved)(AddressRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, AddressRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."address"("city", "country", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(AddressRowUnsaved.text) + } + override def select: SelectBuilder[AddressFields, AddressRow] = { + SelectBuilderSql(""""frontpage"."address"""", AddressFields.structure, AddressRow.jdbcDecoder) + } + override def selectAll: ZStream[ZConnection, Throwable, AddressRow] = { + sql"""select "id", "city", "country" from "frontpage"."address"""".query(using AddressRow.jdbcDecoder).selectStream() + } + override def selectById(id: AddressId): ZIO[ZConnection, Throwable, Option[AddressRow]] = { + sql"""select "id", "city", "country" from "frontpage"."address" where "id" = ${Segment.paramSegment(id)(AddressId.setter)}""".query(using AddressRow.jdbcDecoder).selectOne + } + override def selectByIds(ids: Array[AddressId]): ZStream[ZConnection, Throwable, AddressRow] = { + sql"""select "id", "city", "country" from "frontpage"."address" where "id" = ANY(${Segment.paramSegment(ids)(AddressId.arraySetter)})""".query(using AddressRow.jdbcDecoder).selectStream() + } + override def selectByIdsTracked(ids: Array[AddressId]): ZIO[ZConnection, Throwable, Map[AddressId, AddressRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[AddressFields, AddressRow] = { + UpdateBuilder(""""frontpage"."address"""", AddressFields.structure, AddressRow.jdbcDecoder) + } + override def update(row: AddressRow): ZIO[ZConnection, Throwable, Boolean] = { + val id = row.id + sql"""update "frontpage"."address" + set "city" = ${Segment.paramSegment(row.city)(Setter.stringSetter)}, + "country" = ${Segment.paramSegment(row.country)(Setter.stringSetter)} + where "id" = ${Segment.paramSegment(id)(AddressId.setter)}""".update.map(_ > 0) + } + override def upsert(unsaved: AddressRow): ZIO[ZConnection, Throwable, UpdateResult[AddressRow]] = { + sql"""insert into "frontpage"."address"("id", "city", "country") + values ( + ${Segment.paramSegment(unsaved.id)(AddressId.setter)}::uuid, + ${Segment.paramSegment(unsaved.city)(Setter.stringSetter)}, + ${Segment.paramSegment(unsaved.country)(Setter.stringSetter)} + ) + on conflict ("id") + do update set + "city" = EXCLUDED."city", + "country" = EXCLUDED."country" + returning "id", "city", "country"""".insertReturning(using AddressRow.jdbcDecoder) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, AddressRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + val created = sql"""create temporary table address_TEMP (like "frontpage"."address") on commit drop""".execute + val copied = streamingInsert(s"""copy address_TEMP("id", "city", "country") from stdin""", batchSize, unsaved)(AddressRow.text) + val merged = sql"""insert into "frontpage"."address"("id", "city", "country") + select * from address_TEMP + on conflict ("id") + do update set + "city" = EXCLUDED."city", + "country" = EXCLUDED."country" + ; + drop table address_TEMP;""".update + created *> copied *> merged + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressRepoMock.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressRepoMock.scala new file mode 100644 index 0000000000..825d23f353 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressRepoMock.scala @@ -0,0 +1,116 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams +import zio.Chunk +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +class AddressRepoMock(toRow: Function1[AddressRowUnsaved, AddressRow], + map: scala.collection.mutable.Map[AddressId, AddressRow] = scala.collection.mutable.Map.empty) extends AddressRepo { + override def delete: DeleteBuilder[AddressFields, AddressRow] = { + DeleteBuilderMock(DeleteParams.empty, AddressFields.structure, map) + } + override def deleteById(id: AddressId): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[AddressId]): ZIO[ZConnection, Throwable, Long] = { + ZIO.succeed(ids.map(id => map.remove(id)).count(_.isDefined).toLong) + } + override def insert(unsaved: AddressRow): ZIO[ZConnection, Throwable, AddressRow] = { + ZIO.succeed { + val _ = + if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: AddressRowUnsaved): ZIO[ZConnection, Throwable, AddressRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, AddressRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, AddressRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, unsavedRow) => + ZIO.succeed { + val row = toRow(unsavedRow) + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + override def select: SelectBuilder[AddressFields, AddressRow] = { + SelectBuilderMock(AddressFields.structure, ZIO.succeed(Chunk.fromIterable(map.values)), SelectParams.empty) + } + override def selectAll: ZStream[ZConnection, Throwable, AddressRow] = { + ZStream.fromIterable(map.values) + } + override def selectById(id: AddressId): ZIO[ZConnection, Throwable, Option[AddressRow]] = { + ZIO.succeed(map.get(id)) + } + override def selectByIds(ids: Array[AddressId]): ZStream[ZConnection, Throwable, AddressRow] = { + ZStream.fromIterable(ids.flatMap(map.get)) + } + override def selectByIdsTracked(ids: Array[AddressId]): ZIO[ZConnection, Throwable, Map[AddressId, AddressRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[AddressFields, AddressRow] = { + UpdateBuilderMock(UpdateParams.empty, AddressFields.structure, map) + } + override def update(row: AddressRow): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: AddressRow): ZIO[ZConnection, Throwable, UpdateResult[AddressRow]] = { + ZIO.succeed { + map.put(unsaved.id, unsaved): @nowarn + UpdateResult(1, Chunk.single(unsaved)) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, AddressRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressRow.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressRow.scala new file mode 100644 index 0000000000..7288fcbfb4 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressRow.scala @@ -0,0 +1,69 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import adventureworks.customtypes.Defaulted +import java.sql.ResultSet +import zio.jdbc.JdbcDecoder +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** Table: frontpage.address + Primary key: id */ +case class AddressRow( + /** Default: gen_random_uuid() */ + id: AddressId, + city: String, + country: String +){ + def toUnsavedRow(id: Defaulted[AddressId]): AddressRowUnsaved = + AddressRowUnsaved(city, country, id) + } + +object AddressRow { + implicit lazy val jdbcDecoder: JdbcDecoder[AddressRow] = new JdbcDecoder[AddressRow] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, AddressRow) = + columIndex + 2 -> + AddressRow( + id = AddressId.jdbcDecoder.unsafeDecode(columIndex + 0, rs)._2, + city = JdbcDecoder.stringDecoder.unsafeDecode(columIndex + 1, rs)._2, + country = JdbcDecoder.stringDecoder.unsafeDecode(columIndex + 2, rs)._2 + ) + } + implicit lazy val jsonDecoder: JsonDecoder[AddressRow] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(AddressId.jsonDecoder)) + val city = jsonObj.get("city").toRight("Missing field 'city'").flatMap(_.as(JsonDecoder.string)) + val country = jsonObj.get("country").toRight("Missing field 'country'").flatMap(_.as(JsonDecoder.string)) + if (id.isRight && city.isRight && country.isRight) + Right(AddressRow(id = id.toOption.get, city = city.toOption.get, country = country.toOption.get)) + else Left(List[Either[String, Any]](id, city, country).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[AddressRow] = new JsonEncoder[AddressRow] { + override def unsafeEncode(a: AddressRow, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""id":""") + AddressId.jsonEncoder.unsafeEncode(a.id, indent, out) + out.write(",") + out.write(""""city":""") + JsonEncoder.string.unsafeEncode(a.city, indent, out) + out.write(",") + out.write(""""country":""") + JsonEncoder.string.unsafeEncode(a.country, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[AddressRow] = Text.instance[AddressRow]{ (row, sb) => + AddressId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.city, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.country, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressRowUnsaved.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressRowUnsaved.scala new file mode 100644 index 0000000000..76d4c627cb --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/address/AddressRowUnsaved.scala @@ -0,0 +1,63 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package address + +import adventureworks.customtypes.Defaulted +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** This class corresponds to a row in table `frontpage.address` which has not been persisted yet */ +case class AddressRowUnsaved( + city: String, + country: String, + /** Default: gen_random_uuid() */ + id: Defaulted[AddressId] = Defaulted.UseDefault +) { + def toRow(idDefault: => AddressId): AddressRow = + AddressRow( + city = city, + country = country, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object AddressRowUnsaved { + implicit lazy val jsonDecoder: JsonDecoder[AddressRowUnsaved] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val city = jsonObj.get("city").toRight("Missing field 'city'").flatMap(_.as(JsonDecoder.string)) + val country = jsonObj.get("country").toRight("Missing field 'country'").flatMap(_.as(JsonDecoder.string)) + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(Defaulted.jsonDecoder(AddressId.jsonDecoder))) + if (city.isRight && country.isRight && id.isRight) + Right(AddressRowUnsaved(city = city.toOption.get, country = country.toOption.get, id = id.toOption.get)) + else Left(List[Either[String, Any]](city, country, id).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[AddressRowUnsaved] = new JsonEncoder[AddressRowUnsaved] { + override def unsafeEncode(a: AddressRowUnsaved, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""city":""") + JsonEncoder.string.unsafeEncode(a.city, indent, out) + out.write(",") + out.write(""""country":""") + JsonEncoder.string.unsafeEncode(a.country, indent, out) + out.write(",") + out.write(""""id":""") + Defaulted.jsonEncoder(AddressId.jsonEncoder).unsafeEncode(a.id, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[AddressRowUnsaved] = Text.instance[AddressRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.city, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.country, sb) + sb.append(Text.DELIMETER) + Defaulted.text(AddressId.text).unsafeEncode(row.id, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryFields.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryFields.scala new file mode 100644 index 0000000000..258c0d3f56 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryFields.scala @@ -0,0 +1,40 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.Structure.Relation + +trait CategoryFields { + def id: IdField[CategoryId, CategoryRow] + def name: Field[String, CategoryRow] +} + +object CategoryFields { + lazy val structure: Relation[CategoryFields, CategoryRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[CategoryFields, CategoryRow] { + + override lazy val fields: CategoryFields = new CategoryFields { + override def id = IdField[CategoryId, CategoryRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, CategoryRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, CategoryRow]] = + List[FieldLikeNoHkt[?, CategoryRow]](fields.id, fields.name) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryId.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryId.scala new file mode 100644 index 0000000000..e68a7f9b23 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import adventureworks.customtypes.TypoUUID +import typo.dsl.Bijection +import typo.dsl.PGType +import zio.jdbc.JdbcDecoder +import zio.jdbc.JdbcEncoder +import zio.jdbc.SqlFragment.Setter +import zio.json.JsonDecoder +import zio.json.JsonEncoder + +/** Type for the primary key of table `frontpage.category` */ +case class CategoryId(value: TypoUUID) extends AnyVal +object CategoryId { + implicit lazy val arrayJdbcDecoder: JdbcDecoder[Array[CategoryId]] = JdbcDecoder[Array[TypoUUID]].map(_.map(CategoryId.apply)) + implicit lazy val arrayJdbcEncoder: JdbcEncoder[Array[CategoryId]] = JdbcEncoder[Array[TypoUUID]].contramap(_.map(_.value)) + implicit lazy val arraySetter: Setter[Array[CategoryId]] = TypoUUID.arraySetter.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[CategoryId, TypoUUID] = Bijection[CategoryId, TypoUUID](_.value)(CategoryId.apply) + implicit lazy val jdbcDecoder: JdbcDecoder[CategoryId] = TypoUUID.jdbcDecoder.map(CategoryId.apply) + implicit lazy val jdbcEncoder: JdbcEncoder[CategoryId] = TypoUUID.jdbcEncoder.contramap(_.value) + implicit lazy val jsonDecoder: JsonDecoder[CategoryId] = TypoUUID.jsonDecoder.map(CategoryId.apply) + implicit lazy val jsonEncoder: JsonEncoder[CategoryId] = TypoUUID.jsonEncoder.contramap(_.value) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[CategoryId] = Ordering.by(_.value) + implicit lazy val pgType: PGType[CategoryId] = TypoUUID.pgType.as + implicit lazy val setter: Setter[CategoryId] = TypoUUID.setter.contramap(_.value) + implicit lazy val text: Text[CategoryId] = new Text[CategoryId] { + override def unsafeEncode(v: CategoryId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: CategoryId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepo.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepo.scala new file mode 100644 index 0000000000..3eb3c56e13 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepo.scala @@ -0,0 +1,38 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +trait CategoryRepo { + def delete: DeleteBuilder[CategoryFields, CategoryRow] + def deleteById(id: CategoryId): ZIO[ZConnection, Throwable, Boolean] + def deleteByIds(ids: Array[CategoryId]): ZIO[ZConnection, Throwable, Long] + def insert(unsaved: CategoryRow): ZIO[ZConnection, Throwable, CategoryRow] + def insert(unsaved: CategoryRowUnsaved): ZIO[ZConnection, Throwable, CategoryRow] + def insertStreaming(unsaved: ZStream[ZConnection, Throwable, CategoryRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, CategoryRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + def select: SelectBuilder[CategoryFields, CategoryRow] + def selectAll: ZStream[ZConnection, Throwable, CategoryRow] + def selectById(id: CategoryId): ZIO[ZConnection, Throwable, Option[CategoryRow]] + def selectByIds(ids: Array[CategoryId]): ZStream[ZConnection, Throwable, CategoryRow] + def selectByIdsTracked(ids: Array[CategoryId]): ZIO[ZConnection, Throwable, Map[CategoryId, CategoryRow]] + def update: UpdateBuilder[CategoryFields, CategoryRow] + def update(row: CategoryRow): ZIO[ZConnection, Throwable, Boolean] + def upsert(unsaved: CategoryRow): ZIO[ZConnection, Throwable, UpdateResult[CategoryRow]] + // Not implementable for zio-jdbc: upsertBatch + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, CategoryRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepoImpl.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepoImpl.scala new file mode 100644 index 0000000000..70a1c598e6 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepoImpl.scala @@ -0,0 +1,119 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import adventureworks.customtypes.Defaulted +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.SqlFragment +import zio.jdbc.SqlFragment.Segment +import zio.jdbc.SqlFragment.Setter +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.jdbc.sqlInterpolator +import zio.stream.ZStream + +class CategoryRepoImpl extends CategoryRepo { + override def delete: DeleteBuilder[CategoryFields, CategoryRow] = { + DeleteBuilder(""""frontpage"."category"""", CategoryFields.structure) + } + override def deleteById(id: CategoryId): ZIO[ZConnection, Throwable, Boolean] = { + sql"""delete from "frontpage"."category" where "id" = ${Segment.paramSegment(id)(CategoryId.setter)}""".delete.map(_ > 0) + } + override def deleteByIds(ids: Array[CategoryId]): ZIO[ZConnection, Throwable, Long] = { + sql"""delete from "frontpage"."category" where "id" = ANY(${ids})""".delete + } + override def insert(unsaved: CategoryRow): ZIO[ZConnection, Throwable, CategoryRow] = { + sql"""insert into "frontpage"."category"("id", "name") + values (${Segment.paramSegment(unsaved.id)(CategoryId.setter)}::uuid, ${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}) + returning "id", "name" + """.insertReturning(using CategoryRow.jdbcDecoder).map(_.updatedKeys.head) + } + override def insert(unsaved: CategoryRowUnsaved): ZIO[ZConnection, Throwable, CategoryRow] = { + val fs = List( + Some((sql""""name"""", sql"${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""id"""", sql"${Segment.paramSegment(value: CategoryId)(CategoryId.setter)}::uuid")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."category" default values + returning "id", "name" + """ + } else { + val names = fs.map { case (n, _) => n }.mkFragment(SqlFragment(", ")) + val values = fs.map { case (_, f) => f }.mkFragment(SqlFragment(", ")) + sql"""insert into "frontpage"."category"($names) values ($values) returning "id", "name"""" + } + q.insertReturning(using CategoryRow.jdbcDecoder).map(_.updatedKeys.head) + + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, CategoryRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."category"("id", "name") FROM STDIN""", batchSize, unsaved)(CategoryRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, CategoryRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."category"("name", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(CategoryRowUnsaved.text) + } + override def select: SelectBuilder[CategoryFields, CategoryRow] = { + SelectBuilderSql(""""frontpage"."category"""", CategoryFields.structure, CategoryRow.jdbcDecoder) + } + override def selectAll: ZStream[ZConnection, Throwable, CategoryRow] = { + sql"""select "id", "name" from "frontpage"."category"""".query(using CategoryRow.jdbcDecoder).selectStream() + } + override def selectById(id: CategoryId): ZIO[ZConnection, Throwable, Option[CategoryRow]] = { + sql"""select "id", "name" from "frontpage"."category" where "id" = ${Segment.paramSegment(id)(CategoryId.setter)}""".query(using CategoryRow.jdbcDecoder).selectOne + } + override def selectByIds(ids: Array[CategoryId]): ZStream[ZConnection, Throwable, CategoryRow] = { + sql"""select "id", "name" from "frontpage"."category" where "id" = ANY(${Segment.paramSegment(ids)(CategoryId.arraySetter)})""".query(using CategoryRow.jdbcDecoder).selectStream() + } + override def selectByIdsTracked(ids: Array[CategoryId]): ZIO[ZConnection, Throwable, Map[CategoryId, CategoryRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[CategoryFields, CategoryRow] = { + UpdateBuilder(""""frontpage"."category"""", CategoryFields.structure, CategoryRow.jdbcDecoder) + } + override def update(row: CategoryRow): ZIO[ZConnection, Throwable, Boolean] = { + val id = row.id + sql"""update "frontpage"."category" + set "name" = ${Segment.paramSegment(row.name)(Setter.stringSetter)} + where "id" = ${Segment.paramSegment(id)(CategoryId.setter)}""".update.map(_ > 0) + } + override def upsert(unsaved: CategoryRow): ZIO[ZConnection, Throwable, UpdateResult[CategoryRow]] = { + sql"""insert into "frontpage"."category"("id", "name") + values ( + ${Segment.paramSegment(unsaved.id)(CategoryId.setter)}::uuid, + ${Segment.paramSegment(unsaved.name)(Setter.stringSetter)} + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name" + returning "id", "name"""".insertReturning(using CategoryRow.jdbcDecoder) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, CategoryRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + val created = sql"""create temporary table category_TEMP (like "frontpage"."category") on commit drop""".execute + val copied = streamingInsert(s"""copy category_TEMP("id", "name") from stdin""", batchSize, unsaved)(CategoryRow.text) + val merged = sql"""insert into "frontpage"."category"("id", "name") + select * from category_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name" + ; + drop table category_TEMP;""".update + created *> copied *> merged + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepoMock.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepoMock.scala new file mode 100644 index 0000000000..9dc514651a --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryRepoMock.scala @@ -0,0 +1,116 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams +import zio.Chunk +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +class CategoryRepoMock(toRow: Function1[CategoryRowUnsaved, CategoryRow], + map: scala.collection.mutable.Map[CategoryId, CategoryRow] = scala.collection.mutable.Map.empty) extends CategoryRepo { + override def delete: DeleteBuilder[CategoryFields, CategoryRow] = { + DeleteBuilderMock(DeleteParams.empty, CategoryFields.structure, map) + } + override def deleteById(id: CategoryId): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[CategoryId]): ZIO[ZConnection, Throwable, Long] = { + ZIO.succeed(ids.map(id => map.remove(id)).count(_.isDefined).toLong) + } + override def insert(unsaved: CategoryRow): ZIO[ZConnection, Throwable, CategoryRow] = { + ZIO.succeed { + val _ = + if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: CategoryRowUnsaved): ZIO[ZConnection, Throwable, CategoryRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, CategoryRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, CategoryRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, unsavedRow) => + ZIO.succeed { + val row = toRow(unsavedRow) + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + override def select: SelectBuilder[CategoryFields, CategoryRow] = { + SelectBuilderMock(CategoryFields.structure, ZIO.succeed(Chunk.fromIterable(map.values)), SelectParams.empty) + } + override def selectAll: ZStream[ZConnection, Throwable, CategoryRow] = { + ZStream.fromIterable(map.values) + } + override def selectById(id: CategoryId): ZIO[ZConnection, Throwable, Option[CategoryRow]] = { + ZIO.succeed(map.get(id)) + } + override def selectByIds(ids: Array[CategoryId]): ZStream[ZConnection, Throwable, CategoryRow] = { + ZStream.fromIterable(ids.flatMap(map.get)) + } + override def selectByIdsTracked(ids: Array[CategoryId]): ZIO[ZConnection, Throwable, Map[CategoryId, CategoryRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[CategoryFields, CategoryRow] = { + UpdateBuilderMock(UpdateParams.empty, CategoryFields.structure, map) + } + override def update(row: CategoryRow): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: CategoryRow): ZIO[ZConnection, Throwable, UpdateResult[CategoryRow]] = { + ZIO.succeed { + map.put(unsaved.id, unsaved): @nowarn + UpdateResult(1, Chunk.single(unsaved)) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, CategoryRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryRow.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryRow.scala new file mode 100644 index 0000000000..94a5c7bcfb --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryRow.scala @@ -0,0 +1,61 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import adventureworks.customtypes.Defaulted +import java.sql.ResultSet +import zio.jdbc.JdbcDecoder +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** Table: frontpage.category + Primary key: id */ +case class CategoryRow( + /** Default: gen_random_uuid() */ + id: CategoryId, + name: String +){ + def toUnsavedRow(id: Defaulted[CategoryId]): CategoryRowUnsaved = + CategoryRowUnsaved(name, id) + } + +object CategoryRow { + implicit lazy val jdbcDecoder: JdbcDecoder[CategoryRow] = new JdbcDecoder[CategoryRow] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, CategoryRow) = + columIndex + 1 -> + CategoryRow( + id = CategoryId.jdbcDecoder.unsafeDecode(columIndex + 0, rs)._2, + name = JdbcDecoder.stringDecoder.unsafeDecode(columIndex + 1, rs)._2 + ) + } + implicit lazy val jsonDecoder: JsonDecoder[CategoryRow] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(CategoryId.jsonDecoder)) + val name = jsonObj.get("name").toRight("Missing field 'name'").flatMap(_.as(JsonDecoder.string)) + if (id.isRight && name.isRight) + Right(CategoryRow(id = id.toOption.get, name = name.toOption.get)) + else Left(List[Either[String, Any]](id, name).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[CategoryRow] = new JsonEncoder[CategoryRow] { + override def unsafeEncode(a: CategoryRow, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""id":""") + CategoryId.jsonEncoder.unsafeEncode(a.id, indent, out) + out.write(",") + out.write(""""name":""") + JsonEncoder.string.unsafeEncode(a.name, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[CategoryRow] = Text.instance[CategoryRow]{ (row, sb) => + CategoryId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryRowUnsaved.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryRowUnsaved.scala new file mode 100644 index 0000000000..b6174c3365 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/category/CategoryRowUnsaved.scala @@ -0,0 +1,55 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package category + +import adventureworks.customtypes.Defaulted +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** This class corresponds to a row in table `frontpage.category` which has not been persisted yet */ +case class CategoryRowUnsaved( + name: String, + /** Default: gen_random_uuid() */ + id: Defaulted[CategoryId] = Defaulted.UseDefault +) { + def toRow(idDefault: => CategoryId): CategoryRow = + CategoryRow( + name = name, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object CategoryRowUnsaved { + implicit lazy val jsonDecoder: JsonDecoder[CategoryRowUnsaved] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val name = jsonObj.get("name").toRight("Missing field 'name'").flatMap(_.as(JsonDecoder.string)) + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(Defaulted.jsonDecoder(CategoryId.jsonDecoder))) + if (name.isRight && id.isRight) + Right(CategoryRowUnsaved(name = name.toOption.get, id = id.toOption.get)) + else Left(List[Either[String, Any]](name, id).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[CategoryRowUnsaved] = new JsonEncoder[CategoryRowUnsaved] { + override def unsafeEncode(a: CategoryRowUnsaved, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""name":""") + JsonEncoder.string.unsafeEncode(a.name, indent, out) + out.write(",") + out.write(""""id":""") + Defaulted.jsonEncoder(CategoryId.jsonEncoder).unsafeEncode(a.id, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[CategoryRowUnsaved] = Text.instance[CategoryRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Defaulted.text(CategoryId.text).unsafeEncode(row.id, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyFields.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyFields.scala new file mode 100644 index 0000000000..e6777fa969 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyFields.scala @@ -0,0 +1,40 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.Structure.Relation + +trait CompanyFields { + def id: IdField[CompanyId, CompanyRow] + def name: Field[String, CompanyRow] +} + +object CompanyFields { + lazy val structure: Relation[CompanyFields, CompanyRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[CompanyFields, CompanyRow] { + + override lazy val fields: CompanyFields = new CompanyFields { + override def id = IdField[CompanyId, CompanyRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, CompanyRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, CompanyRow]] = + List[FieldLikeNoHkt[?, CompanyRow]](fields.id, fields.name) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyId.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyId.scala new file mode 100644 index 0000000000..91d0d2642d --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import adventureworks.customtypes.TypoUUID +import typo.dsl.Bijection +import typo.dsl.PGType +import zio.jdbc.JdbcDecoder +import zio.jdbc.JdbcEncoder +import zio.jdbc.SqlFragment.Setter +import zio.json.JsonDecoder +import zio.json.JsonEncoder + +/** Type for the primary key of table `frontpage.company` */ +case class CompanyId(value: TypoUUID) extends AnyVal +object CompanyId { + implicit lazy val arrayJdbcDecoder: JdbcDecoder[Array[CompanyId]] = JdbcDecoder[Array[TypoUUID]].map(_.map(CompanyId.apply)) + implicit lazy val arrayJdbcEncoder: JdbcEncoder[Array[CompanyId]] = JdbcEncoder[Array[TypoUUID]].contramap(_.map(_.value)) + implicit lazy val arraySetter: Setter[Array[CompanyId]] = TypoUUID.arraySetter.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[CompanyId, TypoUUID] = Bijection[CompanyId, TypoUUID](_.value)(CompanyId.apply) + implicit lazy val jdbcDecoder: JdbcDecoder[CompanyId] = TypoUUID.jdbcDecoder.map(CompanyId.apply) + implicit lazy val jdbcEncoder: JdbcEncoder[CompanyId] = TypoUUID.jdbcEncoder.contramap(_.value) + implicit lazy val jsonDecoder: JsonDecoder[CompanyId] = TypoUUID.jsonDecoder.map(CompanyId.apply) + implicit lazy val jsonEncoder: JsonEncoder[CompanyId] = TypoUUID.jsonEncoder.contramap(_.value) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[CompanyId] = Ordering.by(_.value) + implicit lazy val pgType: PGType[CompanyId] = TypoUUID.pgType.as + implicit lazy val setter: Setter[CompanyId] = TypoUUID.setter.contramap(_.value) + implicit lazy val text: Text[CompanyId] = new Text[CompanyId] { + override def unsafeEncode(v: CompanyId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: CompanyId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepo.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepo.scala new file mode 100644 index 0000000000..e2fb09f4e9 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepo.scala @@ -0,0 +1,38 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +trait CompanyRepo { + def delete: DeleteBuilder[CompanyFields, CompanyRow] + def deleteById(id: CompanyId): ZIO[ZConnection, Throwable, Boolean] + def deleteByIds(ids: Array[CompanyId]): ZIO[ZConnection, Throwable, Long] + def insert(unsaved: CompanyRow): ZIO[ZConnection, Throwable, CompanyRow] + def insert(unsaved: CompanyRowUnsaved): ZIO[ZConnection, Throwable, CompanyRow] + def insertStreaming(unsaved: ZStream[ZConnection, Throwable, CompanyRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, CompanyRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + def select: SelectBuilder[CompanyFields, CompanyRow] + def selectAll: ZStream[ZConnection, Throwable, CompanyRow] + def selectById(id: CompanyId): ZIO[ZConnection, Throwable, Option[CompanyRow]] + def selectByIds(ids: Array[CompanyId]): ZStream[ZConnection, Throwable, CompanyRow] + def selectByIdsTracked(ids: Array[CompanyId]): ZIO[ZConnection, Throwable, Map[CompanyId, CompanyRow]] + def update: UpdateBuilder[CompanyFields, CompanyRow] + def update(row: CompanyRow): ZIO[ZConnection, Throwable, Boolean] + def upsert(unsaved: CompanyRow): ZIO[ZConnection, Throwable, UpdateResult[CompanyRow]] + // Not implementable for zio-jdbc: upsertBatch + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, CompanyRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepoImpl.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepoImpl.scala new file mode 100644 index 0000000000..3aeb2c1c02 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepoImpl.scala @@ -0,0 +1,119 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import adventureworks.customtypes.Defaulted +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.SqlFragment +import zio.jdbc.SqlFragment.Segment +import zio.jdbc.SqlFragment.Setter +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.jdbc.sqlInterpolator +import zio.stream.ZStream + +class CompanyRepoImpl extends CompanyRepo { + override def delete: DeleteBuilder[CompanyFields, CompanyRow] = { + DeleteBuilder(""""frontpage"."company"""", CompanyFields.structure) + } + override def deleteById(id: CompanyId): ZIO[ZConnection, Throwable, Boolean] = { + sql"""delete from "frontpage"."company" where "id" = ${Segment.paramSegment(id)(CompanyId.setter)}""".delete.map(_ > 0) + } + override def deleteByIds(ids: Array[CompanyId]): ZIO[ZConnection, Throwable, Long] = { + sql"""delete from "frontpage"."company" where "id" = ANY(${ids})""".delete + } + override def insert(unsaved: CompanyRow): ZIO[ZConnection, Throwable, CompanyRow] = { + sql"""insert into "frontpage"."company"("id", "name") + values (${Segment.paramSegment(unsaved.id)(CompanyId.setter)}::uuid, ${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}) + returning "id", "name" + """.insertReturning(using CompanyRow.jdbcDecoder).map(_.updatedKeys.head) + } + override def insert(unsaved: CompanyRowUnsaved): ZIO[ZConnection, Throwable, CompanyRow] = { + val fs = List( + Some((sql""""name"""", sql"${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""id"""", sql"${Segment.paramSegment(value: CompanyId)(CompanyId.setter)}::uuid")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."company" default values + returning "id", "name" + """ + } else { + val names = fs.map { case (n, _) => n }.mkFragment(SqlFragment(", ")) + val values = fs.map { case (_, f) => f }.mkFragment(SqlFragment(", ")) + sql"""insert into "frontpage"."company"($names) values ($values) returning "id", "name"""" + } + q.insertReturning(using CompanyRow.jdbcDecoder).map(_.updatedKeys.head) + + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, CompanyRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."company"("id", "name") FROM STDIN""", batchSize, unsaved)(CompanyRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, CompanyRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."company"("name", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(CompanyRowUnsaved.text) + } + override def select: SelectBuilder[CompanyFields, CompanyRow] = { + SelectBuilderSql(""""frontpage"."company"""", CompanyFields.structure, CompanyRow.jdbcDecoder) + } + override def selectAll: ZStream[ZConnection, Throwable, CompanyRow] = { + sql"""select "id", "name" from "frontpage"."company"""".query(using CompanyRow.jdbcDecoder).selectStream() + } + override def selectById(id: CompanyId): ZIO[ZConnection, Throwable, Option[CompanyRow]] = { + sql"""select "id", "name" from "frontpage"."company" where "id" = ${Segment.paramSegment(id)(CompanyId.setter)}""".query(using CompanyRow.jdbcDecoder).selectOne + } + override def selectByIds(ids: Array[CompanyId]): ZStream[ZConnection, Throwable, CompanyRow] = { + sql"""select "id", "name" from "frontpage"."company" where "id" = ANY(${Segment.paramSegment(ids)(CompanyId.arraySetter)})""".query(using CompanyRow.jdbcDecoder).selectStream() + } + override def selectByIdsTracked(ids: Array[CompanyId]): ZIO[ZConnection, Throwable, Map[CompanyId, CompanyRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[CompanyFields, CompanyRow] = { + UpdateBuilder(""""frontpage"."company"""", CompanyFields.structure, CompanyRow.jdbcDecoder) + } + override def update(row: CompanyRow): ZIO[ZConnection, Throwable, Boolean] = { + val id = row.id + sql"""update "frontpage"."company" + set "name" = ${Segment.paramSegment(row.name)(Setter.stringSetter)} + where "id" = ${Segment.paramSegment(id)(CompanyId.setter)}""".update.map(_ > 0) + } + override def upsert(unsaved: CompanyRow): ZIO[ZConnection, Throwable, UpdateResult[CompanyRow]] = { + sql"""insert into "frontpage"."company"("id", "name") + values ( + ${Segment.paramSegment(unsaved.id)(CompanyId.setter)}::uuid, + ${Segment.paramSegment(unsaved.name)(Setter.stringSetter)} + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name" + returning "id", "name"""".insertReturning(using CompanyRow.jdbcDecoder) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, CompanyRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + val created = sql"""create temporary table company_TEMP (like "frontpage"."company") on commit drop""".execute + val copied = streamingInsert(s"""copy company_TEMP("id", "name") from stdin""", batchSize, unsaved)(CompanyRow.text) + val merged = sql"""insert into "frontpage"."company"("id", "name") + select * from company_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name" + ; + drop table company_TEMP;""".update + created *> copied *> merged + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepoMock.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepoMock.scala new file mode 100644 index 0000000000..35f11184ca --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyRepoMock.scala @@ -0,0 +1,116 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams +import zio.Chunk +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +class CompanyRepoMock(toRow: Function1[CompanyRowUnsaved, CompanyRow], + map: scala.collection.mutable.Map[CompanyId, CompanyRow] = scala.collection.mutable.Map.empty) extends CompanyRepo { + override def delete: DeleteBuilder[CompanyFields, CompanyRow] = { + DeleteBuilderMock(DeleteParams.empty, CompanyFields.structure, map) + } + override def deleteById(id: CompanyId): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[CompanyId]): ZIO[ZConnection, Throwable, Long] = { + ZIO.succeed(ids.map(id => map.remove(id)).count(_.isDefined).toLong) + } + override def insert(unsaved: CompanyRow): ZIO[ZConnection, Throwable, CompanyRow] = { + ZIO.succeed { + val _ = + if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: CompanyRowUnsaved): ZIO[ZConnection, Throwable, CompanyRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, CompanyRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, CompanyRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, unsavedRow) => + ZIO.succeed { + val row = toRow(unsavedRow) + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + override def select: SelectBuilder[CompanyFields, CompanyRow] = { + SelectBuilderMock(CompanyFields.structure, ZIO.succeed(Chunk.fromIterable(map.values)), SelectParams.empty) + } + override def selectAll: ZStream[ZConnection, Throwable, CompanyRow] = { + ZStream.fromIterable(map.values) + } + override def selectById(id: CompanyId): ZIO[ZConnection, Throwable, Option[CompanyRow]] = { + ZIO.succeed(map.get(id)) + } + override def selectByIds(ids: Array[CompanyId]): ZStream[ZConnection, Throwable, CompanyRow] = { + ZStream.fromIterable(ids.flatMap(map.get)) + } + override def selectByIdsTracked(ids: Array[CompanyId]): ZIO[ZConnection, Throwable, Map[CompanyId, CompanyRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[CompanyFields, CompanyRow] = { + UpdateBuilderMock(UpdateParams.empty, CompanyFields.structure, map) + } + override def update(row: CompanyRow): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: CompanyRow): ZIO[ZConnection, Throwable, UpdateResult[CompanyRow]] = { + ZIO.succeed { + map.put(unsaved.id, unsaved): @nowarn + UpdateResult(1, Chunk.single(unsaved)) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, CompanyRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyRow.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyRow.scala new file mode 100644 index 0000000000..0f0a996f41 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyRow.scala @@ -0,0 +1,61 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import adventureworks.customtypes.Defaulted +import java.sql.ResultSet +import zio.jdbc.JdbcDecoder +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** Table: frontpage.company + Primary key: id */ +case class CompanyRow( + /** Default: gen_random_uuid() */ + id: CompanyId, + name: String +){ + def toUnsavedRow(id: Defaulted[CompanyId]): CompanyRowUnsaved = + CompanyRowUnsaved(name, id) + } + +object CompanyRow { + implicit lazy val jdbcDecoder: JdbcDecoder[CompanyRow] = new JdbcDecoder[CompanyRow] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, CompanyRow) = + columIndex + 1 -> + CompanyRow( + id = CompanyId.jdbcDecoder.unsafeDecode(columIndex + 0, rs)._2, + name = JdbcDecoder.stringDecoder.unsafeDecode(columIndex + 1, rs)._2 + ) + } + implicit lazy val jsonDecoder: JsonDecoder[CompanyRow] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(CompanyId.jsonDecoder)) + val name = jsonObj.get("name").toRight("Missing field 'name'").flatMap(_.as(JsonDecoder.string)) + if (id.isRight && name.isRight) + Right(CompanyRow(id = id.toOption.get, name = name.toOption.get)) + else Left(List[Either[String, Any]](id, name).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[CompanyRow] = new JsonEncoder[CompanyRow] { + override def unsafeEncode(a: CompanyRow, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""id":""") + CompanyId.jsonEncoder.unsafeEncode(a.id, indent, out) + out.write(",") + out.write(""""name":""") + JsonEncoder.string.unsafeEncode(a.name, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[CompanyRow] = Text.instance[CompanyRow]{ (row, sb) => + CompanyId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyRowUnsaved.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyRowUnsaved.scala new file mode 100644 index 0000000000..7d1a56aae1 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/company/CompanyRowUnsaved.scala @@ -0,0 +1,55 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package company + +import adventureworks.customtypes.Defaulted +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** This class corresponds to a row in table `frontpage.company` which has not been persisted yet */ +case class CompanyRowUnsaved( + name: String, + /** Default: gen_random_uuid() */ + id: Defaulted[CompanyId] = Defaulted.UseDefault +) { + def toRow(idDefault: => CompanyId): CompanyRow = + CompanyRow( + name = name, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object CompanyRowUnsaved { + implicit lazy val jsonDecoder: JsonDecoder[CompanyRowUnsaved] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val name = jsonObj.get("name").toRight("Missing field 'name'").flatMap(_.as(JsonDecoder.string)) + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(Defaulted.jsonDecoder(CompanyId.jsonDecoder))) + if (name.isRight && id.isRight) + Right(CompanyRowUnsaved(name = name.toOption.get, id = id.toOption.get)) + else Left(List[Either[String, Any]](name, id).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[CompanyRowUnsaved] = new JsonEncoder[CompanyRowUnsaved] { + override def unsafeEncode(a: CompanyRowUnsaved, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""name":""") + JsonEncoder.string.unsafeEncode(a.name, indent, out) + out.write(",") + out.write(""""id":""") + Defaulted.jsonEncoder(CompanyId.jsonEncoder).unsafeEncode(a.id, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[CompanyRowUnsaved] = Text.instance[CompanyRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Defaulted.text(CompanyId.text).unsafeEncode(row.id, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerFields.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerFields.scala new file mode 100644 index 0000000000..50841ec7c7 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerFields.scala @@ -0,0 +1,53 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import adventureworks.frontpage.user.UserFields +import adventureworks.frontpage.user.UserId +import adventureworks.frontpage.user.UserRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait CustomerFields { + def id: IdField[CustomerId, CustomerRow] + def userId: OptField[UserId, CustomerRow] + def companyName: OptField[String, CustomerRow] + def creditLimit: OptField[BigDecimal, CustomerRow] + def verified: OptField[Boolean, CustomerRow] + def fkUser: ForeignKey[UserFields, UserRow] = + ForeignKey[UserFields, UserRow]("frontpage.customer_user_id_fkey", Nil) + .withColumnPair(userId, _.id) +} + +object CustomerFields { + lazy val structure: Relation[CustomerFields, CustomerRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[CustomerFields, CustomerRow] { + + override lazy val fields: CustomerFields = new CustomerFields { + override def id = IdField[CustomerId, CustomerRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def userId = OptField[UserId, CustomerRow](_path, "user_id", None, Some("uuid"), x => x.userId, (row, value) => row.copy(userId = value)) + override def companyName = OptField[String, CustomerRow](_path, "company_name", None, None, x => x.companyName, (row, value) => row.copy(companyName = value)) + override def creditLimit = OptField[BigDecimal, CustomerRow](_path, "credit_limit", None, Some("numeric"), x => x.creditLimit, (row, value) => row.copy(creditLimit = value)) + override def verified = OptField[Boolean, CustomerRow](_path, "verified", None, None, x => x.verified, (row, value) => row.copy(verified = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, CustomerRow]] = + List[FieldLikeNoHkt[?, CustomerRow]](fields.id, fields.userId, fields.companyName, fields.creditLimit, fields.verified) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerId.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerId.scala new file mode 100644 index 0000000000..1e31ce9a1b --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import adventureworks.customtypes.TypoUUID +import typo.dsl.Bijection +import typo.dsl.PGType +import zio.jdbc.JdbcDecoder +import zio.jdbc.JdbcEncoder +import zio.jdbc.SqlFragment.Setter +import zio.json.JsonDecoder +import zio.json.JsonEncoder + +/** Type for the primary key of table `frontpage.customer` */ +case class CustomerId(value: TypoUUID) extends AnyVal +object CustomerId { + implicit lazy val arrayJdbcDecoder: JdbcDecoder[Array[CustomerId]] = JdbcDecoder[Array[TypoUUID]].map(_.map(CustomerId.apply)) + implicit lazy val arrayJdbcEncoder: JdbcEncoder[Array[CustomerId]] = JdbcEncoder[Array[TypoUUID]].contramap(_.map(_.value)) + implicit lazy val arraySetter: Setter[Array[CustomerId]] = TypoUUID.arraySetter.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[CustomerId, TypoUUID] = Bijection[CustomerId, TypoUUID](_.value)(CustomerId.apply) + implicit lazy val jdbcDecoder: JdbcDecoder[CustomerId] = TypoUUID.jdbcDecoder.map(CustomerId.apply) + implicit lazy val jdbcEncoder: JdbcEncoder[CustomerId] = TypoUUID.jdbcEncoder.contramap(_.value) + implicit lazy val jsonDecoder: JsonDecoder[CustomerId] = TypoUUID.jsonDecoder.map(CustomerId.apply) + implicit lazy val jsonEncoder: JsonEncoder[CustomerId] = TypoUUID.jsonEncoder.contramap(_.value) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[CustomerId] = Ordering.by(_.value) + implicit lazy val pgType: PGType[CustomerId] = TypoUUID.pgType.as + implicit lazy val setter: Setter[CustomerId] = TypoUUID.setter.contramap(_.value) + implicit lazy val text: Text[CustomerId] = new Text[CustomerId] { + override def unsafeEncode(v: CustomerId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: CustomerId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepo.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepo.scala new file mode 100644 index 0000000000..cc274df793 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepo.scala @@ -0,0 +1,38 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +trait CustomerRepo { + def delete: DeleteBuilder[CustomerFields, CustomerRow] + def deleteById(id: CustomerId): ZIO[ZConnection, Throwable, Boolean] + def deleteByIds(ids: Array[CustomerId]): ZIO[ZConnection, Throwable, Long] + def insert(unsaved: CustomerRow): ZIO[ZConnection, Throwable, CustomerRow] + def insert(unsaved: CustomerRowUnsaved): ZIO[ZConnection, Throwable, CustomerRow] + def insertStreaming(unsaved: ZStream[ZConnection, Throwable, CustomerRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, CustomerRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + def select: SelectBuilder[CustomerFields, CustomerRow] + def selectAll: ZStream[ZConnection, Throwable, CustomerRow] + def selectById(id: CustomerId): ZIO[ZConnection, Throwable, Option[CustomerRow]] + def selectByIds(ids: Array[CustomerId]): ZStream[ZConnection, Throwable, CustomerRow] + def selectByIdsTracked(ids: Array[CustomerId]): ZIO[ZConnection, Throwable, Map[CustomerId, CustomerRow]] + def update: UpdateBuilder[CustomerFields, CustomerRow] + def update(row: CustomerRow): ZIO[ZConnection, Throwable, Boolean] + def upsert(unsaved: CustomerRow): ZIO[ZConnection, Throwable, UpdateResult[CustomerRow]] + // Not implementable for zio-jdbc: upsertBatch + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, CustomerRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepoImpl.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepoImpl.scala new file mode 100644 index 0000000000..306e820b80 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepoImpl.scala @@ -0,0 +1,138 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.user.UserId +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.SqlFragment +import zio.jdbc.SqlFragment.Segment +import zio.jdbc.SqlFragment.Setter +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.jdbc.sqlInterpolator +import zio.stream.ZStream + +class CustomerRepoImpl extends CustomerRepo { + override def delete: DeleteBuilder[CustomerFields, CustomerRow] = { + DeleteBuilder(""""frontpage"."customer"""", CustomerFields.structure) + } + override def deleteById(id: CustomerId): ZIO[ZConnection, Throwable, Boolean] = { + sql"""delete from "frontpage"."customer" where "id" = ${Segment.paramSegment(id)(CustomerId.setter)}""".delete.map(_ > 0) + } + override def deleteByIds(ids: Array[CustomerId]): ZIO[ZConnection, Throwable, Long] = { + sql"""delete from "frontpage"."customer" where "id" = ANY(${ids})""".delete + } + override def insert(unsaved: CustomerRow): ZIO[ZConnection, Throwable, CustomerRow] = { + sql"""insert into "frontpage"."customer"("id", "user_id", "company_name", "credit_limit", "verified") + values (${Segment.paramSegment(unsaved.id)(CustomerId.setter)}::uuid, ${Segment.paramSegment(unsaved.userId)(Setter.optionParamSetter(UserId.setter))}::uuid, ${Segment.paramSegment(unsaved.companyName)(Setter.optionParamSetter(Setter.stringSetter))}, ${Segment.paramSegment(unsaved.creditLimit)(Setter.optionParamSetter(Setter.bigDecimalScalaSetter))}::numeric, ${Segment.paramSegment(unsaved.verified)(Setter.optionParamSetter(Setter.booleanSetter))}) + returning "id", "user_id", "company_name", "credit_limit", "verified" + """.insertReturning(using CustomerRow.jdbcDecoder).map(_.updatedKeys.head) + } + override def insert(unsaved: CustomerRowUnsaved): ZIO[ZConnection, Throwable, CustomerRow] = { + val fs = List( + Some((sql""""user_id"""", sql"${Segment.paramSegment(unsaved.userId)(Setter.optionParamSetter(UserId.setter))}::uuid")), + Some((sql""""company_name"""", sql"${Segment.paramSegment(unsaved.companyName)(Setter.optionParamSetter(Setter.stringSetter))}")), + Some((sql""""credit_limit"""", sql"${Segment.paramSegment(unsaved.creditLimit)(Setter.optionParamSetter(Setter.bigDecimalScalaSetter))}::numeric")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""id"""", sql"${Segment.paramSegment(value: CustomerId)(CustomerId.setter)}::uuid")) + }, + unsaved.verified match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""verified"""", sql"${Segment.paramSegment(value: Option[Boolean])(Setter.optionParamSetter(Setter.booleanSetter))}")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."customer" default values + returning "id", "user_id", "company_name", "credit_limit", "verified" + """ + } else { + val names = fs.map { case (n, _) => n }.mkFragment(SqlFragment(", ")) + val values = fs.map { case (_, f) => f }.mkFragment(SqlFragment(", ")) + sql"""insert into "frontpage"."customer"($names) values ($values) returning "id", "user_id", "company_name", "credit_limit", "verified"""" + } + q.insertReturning(using CustomerRow.jdbcDecoder).map(_.updatedKeys.head) + + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, CustomerRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."customer"("id", "user_id", "company_name", "credit_limit", "verified") FROM STDIN""", batchSize, unsaved)(CustomerRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, CustomerRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."customer"("user_id", "company_name", "credit_limit", "id", "verified") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(CustomerRowUnsaved.text) + } + override def select: SelectBuilder[CustomerFields, CustomerRow] = { + SelectBuilderSql(""""frontpage"."customer"""", CustomerFields.structure, CustomerRow.jdbcDecoder) + } + override def selectAll: ZStream[ZConnection, Throwable, CustomerRow] = { + sql"""select "id", "user_id", "company_name", "credit_limit", "verified" from "frontpage"."customer"""".query(using CustomerRow.jdbcDecoder).selectStream() + } + override def selectById(id: CustomerId): ZIO[ZConnection, Throwable, Option[CustomerRow]] = { + sql"""select "id", "user_id", "company_name", "credit_limit", "verified" from "frontpage"."customer" where "id" = ${Segment.paramSegment(id)(CustomerId.setter)}""".query(using CustomerRow.jdbcDecoder).selectOne + } + override def selectByIds(ids: Array[CustomerId]): ZStream[ZConnection, Throwable, CustomerRow] = { + sql"""select "id", "user_id", "company_name", "credit_limit", "verified" from "frontpage"."customer" where "id" = ANY(${Segment.paramSegment(ids)(CustomerId.arraySetter)})""".query(using CustomerRow.jdbcDecoder).selectStream() + } + override def selectByIdsTracked(ids: Array[CustomerId]): ZIO[ZConnection, Throwable, Map[CustomerId, CustomerRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[CustomerFields, CustomerRow] = { + UpdateBuilder(""""frontpage"."customer"""", CustomerFields.structure, CustomerRow.jdbcDecoder) + } + override def update(row: CustomerRow): ZIO[ZConnection, Throwable, Boolean] = { + val id = row.id + sql"""update "frontpage"."customer" + set "user_id" = ${Segment.paramSegment(row.userId)(Setter.optionParamSetter(UserId.setter))}::uuid, + "company_name" = ${Segment.paramSegment(row.companyName)(Setter.optionParamSetter(Setter.stringSetter))}, + "credit_limit" = ${Segment.paramSegment(row.creditLimit)(Setter.optionParamSetter(Setter.bigDecimalScalaSetter))}::numeric, + "verified" = ${Segment.paramSegment(row.verified)(Setter.optionParamSetter(Setter.booleanSetter))} + where "id" = ${Segment.paramSegment(id)(CustomerId.setter)}""".update.map(_ > 0) + } + override def upsert(unsaved: CustomerRow): ZIO[ZConnection, Throwable, UpdateResult[CustomerRow]] = { + sql"""insert into "frontpage"."customer"("id", "user_id", "company_name", "credit_limit", "verified") + values ( + ${Segment.paramSegment(unsaved.id)(CustomerId.setter)}::uuid, + ${Segment.paramSegment(unsaved.userId)(Setter.optionParamSetter(UserId.setter))}::uuid, + ${Segment.paramSegment(unsaved.companyName)(Setter.optionParamSetter(Setter.stringSetter))}, + ${Segment.paramSegment(unsaved.creditLimit)(Setter.optionParamSetter(Setter.bigDecimalScalaSetter))}::numeric, + ${Segment.paramSegment(unsaved.verified)(Setter.optionParamSetter(Setter.booleanSetter))} + ) + on conflict ("id") + do update set + "user_id" = EXCLUDED."user_id", + "company_name" = EXCLUDED."company_name", + "credit_limit" = EXCLUDED."credit_limit", + "verified" = EXCLUDED."verified" + returning "id", "user_id", "company_name", "credit_limit", "verified"""".insertReturning(using CustomerRow.jdbcDecoder) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, CustomerRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + val created = sql"""create temporary table customer_TEMP (like "frontpage"."customer") on commit drop""".execute + val copied = streamingInsert(s"""copy customer_TEMP("id", "user_id", "company_name", "credit_limit", "verified") from stdin""", batchSize, unsaved)(CustomerRow.text) + val merged = sql"""insert into "frontpage"."customer"("id", "user_id", "company_name", "credit_limit", "verified") + select * from customer_TEMP + on conflict ("id") + do update set + "user_id" = EXCLUDED."user_id", + "company_name" = EXCLUDED."company_name", + "credit_limit" = EXCLUDED."credit_limit", + "verified" = EXCLUDED."verified" + ; + drop table customer_TEMP;""".update + created *> copied *> merged + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepoMock.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepoMock.scala new file mode 100644 index 0000000000..6ec5732c04 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRepoMock.scala @@ -0,0 +1,116 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams +import zio.Chunk +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +class CustomerRepoMock(toRow: Function1[CustomerRowUnsaved, CustomerRow], + map: scala.collection.mutable.Map[CustomerId, CustomerRow] = scala.collection.mutable.Map.empty) extends CustomerRepo { + override def delete: DeleteBuilder[CustomerFields, CustomerRow] = { + DeleteBuilderMock(DeleteParams.empty, CustomerFields.structure, map) + } + override def deleteById(id: CustomerId): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[CustomerId]): ZIO[ZConnection, Throwable, Long] = { + ZIO.succeed(ids.map(id => map.remove(id)).count(_.isDefined).toLong) + } + override def insert(unsaved: CustomerRow): ZIO[ZConnection, Throwable, CustomerRow] = { + ZIO.succeed { + val _ = + if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: CustomerRowUnsaved): ZIO[ZConnection, Throwable, CustomerRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, CustomerRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, CustomerRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, unsavedRow) => + ZIO.succeed { + val row = toRow(unsavedRow) + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + override def select: SelectBuilder[CustomerFields, CustomerRow] = { + SelectBuilderMock(CustomerFields.structure, ZIO.succeed(Chunk.fromIterable(map.values)), SelectParams.empty) + } + override def selectAll: ZStream[ZConnection, Throwable, CustomerRow] = { + ZStream.fromIterable(map.values) + } + override def selectById(id: CustomerId): ZIO[ZConnection, Throwable, Option[CustomerRow]] = { + ZIO.succeed(map.get(id)) + } + override def selectByIds(ids: Array[CustomerId]): ZStream[ZConnection, Throwable, CustomerRow] = { + ZStream.fromIterable(ids.flatMap(map.get)) + } + override def selectByIdsTracked(ids: Array[CustomerId]): ZIO[ZConnection, Throwable, Map[CustomerId, CustomerRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[CustomerFields, CustomerRow] = { + UpdateBuilderMock(UpdateParams.empty, CustomerFields.structure, map) + } + override def update(row: CustomerRow): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: CustomerRow): ZIO[ZConnection, Throwable, UpdateResult[CustomerRow]] = { + ZIO.succeed { + map.put(unsaved.id, unsaved): @nowarn + UpdateResult(1, Chunk.single(unsaved)) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, CustomerRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRow.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRow.scala new file mode 100644 index 0000000000..b4bb5c66e1 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRow.scala @@ -0,0 +1,88 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.user.UserId +import java.sql.ResultSet +import zio.jdbc.JdbcDecoder +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** Table: frontpage.customer + Primary key: id */ +case class CustomerRow( + /** Default: gen_random_uuid() */ + id: CustomerId, + /** Points to [[user.UserRow.id]] */ + userId: Option[UserId], + companyName: Option[String], + creditLimit: Option[BigDecimal], + /** Default: false */ + verified: Option[Boolean] +){ + def toUnsavedRow(id: Defaulted[CustomerId], verified: Defaulted[Option[Boolean]] = Defaulted.Provided(this.verified)): CustomerRowUnsaved = + CustomerRowUnsaved(userId, companyName, creditLimit, id, verified) + } + +object CustomerRow { + implicit lazy val jdbcDecoder: JdbcDecoder[CustomerRow] = new JdbcDecoder[CustomerRow] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, CustomerRow) = + columIndex + 4 -> + CustomerRow( + id = CustomerId.jdbcDecoder.unsafeDecode(columIndex + 0, rs)._2, + userId = JdbcDecoder.optionDecoder(UserId.jdbcDecoder).unsafeDecode(columIndex + 1, rs)._2, + companyName = JdbcDecoder.optionDecoder(JdbcDecoder.stringDecoder).unsafeDecode(columIndex + 2, rs)._2, + creditLimit = JdbcDecoder.optionDecoder(JdbcDecoder.bigDecimalDecoderScala).unsafeDecode(columIndex + 3, rs)._2, + verified = JdbcDecoder.optionDecoder(JdbcDecoder.booleanDecoder).unsafeDecode(columIndex + 4, rs)._2 + ) + } + implicit lazy val jsonDecoder: JsonDecoder[CustomerRow] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(CustomerId.jsonDecoder)) + val userId = jsonObj.get("user_id").fold[Either[String, Option[UserId]]](Right(None))(_.as(JsonDecoder.option(using UserId.jsonDecoder))) + val companyName = jsonObj.get("company_name").fold[Either[String, Option[String]]](Right(None))(_.as(JsonDecoder.option(using JsonDecoder.string))) + val creditLimit = jsonObj.get("credit_limit").fold[Either[String, Option[BigDecimal]]](Right(None))(_.as(JsonDecoder.option(using JsonDecoder.scalaBigDecimal))) + val verified = jsonObj.get("verified").fold[Either[String, Option[Boolean]]](Right(None))(_.as(JsonDecoder.option(using JsonDecoder.boolean))) + if (id.isRight && userId.isRight && companyName.isRight && creditLimit.isRight && verified.isRight) + Right(CustomerRow(id = id.toOption.get, userId = userId.toOption.get, companyName = companyName.toOption.get, creditLimit = creditLimit.toOption.get, verified = verified.toOption.get)) + else Left(List[Either[String, Any]](id, userId, companyName, creditLimit, verified).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[CustomerRow] = new JsonEncoder[CustomerRow] { + override def unsafeEncode(a: CustomerRow, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""id":""") + CustomerId.jsonEncoder.unsafeEncode(a.id, indent, out) + out.write(",") + out.write(""""user_id":""") + JsonEncoder.option(using UserId.jsonEncoder).unsafeEncode(a.userId, indent, out) + out.write(",") + out.write(""""company_name":""") + JsonEncoder.option(using JsonEncoder.string).unsafeEncode(a.companyName, indent, out) + out.write(",") + out.write(""""credit_limit":""") + JsonEncoder.option(using JsonEncoder.scalaBigDecimal).unsafeEncode(a.creditLimit, indent, out) + out.write(",") + out.write(""""verified":""") + JsonEncoder.option(using JsonEncoder.boolean).unsafeEncode(a.verified, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[CustomerRow] = Text.instance[CustomerRow]{ (row, sb) => + CustomerId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.option(UserId.text).unsafeEncode(row.userId, sb) + sb.append(Text.DELIMETER) + Text.option(Text.stringInstance).unsafeEncode(row.companyName, sb) + sb.append(Text.DELIMETER) + Text.option(Text.bigDecimalInstance).unsafeEncode(row.creditLimit, sb) + sb.append(Text.DELIMETER) + Text.option(Text.booleanInstance).unsafeEncode(row.verified, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRowUnsaved.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRowUnsaved.scala new file mode 100644 index 0000000000..9649a3b72e --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/customer/CustomerRowUnsaved.scala @@ -0,0 +1,85 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package customer + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.user.UserId +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** This class corresponds to a row in table `frontpage.customer` which has not been persisted yet */ +case class CustomerRowUnsaved( + /** Points to [[user.UserRow.id]] */ + userId: Option[UserId], + companyName: Option[String], + creditLimit: Option[BigDecimal], + /** Default: gen_random_uuid() */ + id: Defaulted[CustomerId] = Defaulted.UseDefault, + /** Default: false */ + verified: Defaulted[Option[Boolean]] = Defaulted.UseDefault +) { + def toRow(idDefault: => CustomerId, verifiedDefault: => Option[Boolean]): CustomerRow = + CustomerRow( + userId = userId, + companyName = companyName, + creditLimit = creditLimit, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + }, + verified = verified match { + case Defaulted.UseDefault => verifiedDefault + case Defaulted.Provided(value) => value + } + ) +} +object CustomerRowUnsaved { + implicit lazy val jsonDecoder: JsonDecoder[CustomerRowUnsaved] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val userId = jsonObj.get("user_id").fold[Either[String, Option[UserId]]](Right(None))(_.as(JsonDecoder.option(using UserId.jsonDecoder))) + val companyName = jsonObj.get("company_name").fold[Either[String, Option[String]]](Right(None))(_.as(JsonDecoder.option(using JsonDecoder.string))) + val creditLimit = jsonObj.get("credit_limit").fold[Either[String, Option[BigDecimal]]](Right(None))(_.as(JsonDecoder.option(using JsonDecoder.scalaBigDecimal))) + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(Defaulted.jsonDecoder(CustomerId.jsonDecoder))) + val verified = jsonObj.get("verified").toRight("Missing field 'verified'").flatMap(_.as(Defaulted.jsonDecoder(JsonDecoder.option(using JsonDecoder.boolean)))) + if (userId.isRight && companyName.isRight && creditLimit.isRight && id.isRight && verified.isRight) + Right(CustomerRowUnsaved(userId = userId.toOption.get, companyName = companyName.toOption.get, creditLimit = creditLimit.toOption.get, id = id.toOption.get, verified = verified.toOption.get)) + else Left(List[Either[String, Any]](userId, companyName, creditLimit, id, verified).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[CustomerRowUnsaved] = new JsonEncoder[CustomerRowUnsaved] { + override def unsafeEncode(a: CustomerRowUnsaved, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""user_id":""") + JsonEncoder.option(using UserId.jsonEncoder).unsafeEncode(a.userId, indent, out) + out.write(",") + out.write(""""company_name":""") + JsonEncoder.option(using JsonEncoder.string).unsafeEncode(a.companyName, indent, out) + out.write(",") + out.write(""""credit_limit":""") + JsonEncoder.option(using JsonEncoder.scalaBigDecimal).unsafeEncode(a.creditLimit, indent, out) + out.write(",") + out.write(""""id":""") + Defaulted.jsonEncoder(CustomerId.jsonEncoder).unsafeEncode(a.id, indent, out) + out.write(",") + out.write(""""verified":""") + Defaulted.jsonEncoder(JsonEncoder.option(using JsonEncoder.boolean)).unsafeEncode(a.verified, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[CustomerRowUnsaved] = Text.instance[CustomerRowUnsaved]{ (row, sb) => + Text.option(UserId.text).unsafeEncode(row.userId, sb) + sb.append(Text.DELIMETER) + Text.option(Text.stringInstance).unsafeEncode(row.companyName, sb) + sb.append(Text.DELIMETER) + Text.option(Text.bigDecimalInstance).unsafeEncode(row.creditLimit, sb) + sb.append(Text.DELIMETER) + Defaulted.text(CustomerId.text).unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text.booleanInstance)).unsafeEncode(row.verified, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentFields.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentFields.scala new file mode 100644 index 0000000000..deae7d4799 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentFields.scala @@ -0,0 +1,52 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import adventureworks.frontpage.company.CompanyFields +import adventureworks.frontpage.company.CompanyId +import adventureworks.frontpage.company.CompanyRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait DepartmentFields { + def id: IdField[DepartmentId, DepartmentRow] + def name: Field[String, DepartmentRow] + def budget: OptField[BigDecimal, DepartmentRow] + def companyId: OptField[CompanyId, DepartmentRow] + def fkCompany: ForeignKey[CompanyFields, CompanyRow] = + ForeignKey[CompanyFields, CompanyRow]("frontpage.department_company_id_fkey", Nil) + .withColumnPair(companyId, _.id) +} + +object DepartmentFields { + lazy val structure: Relation[DepartmentFields, DepartmentRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[DepartmentFields, DepartmentRow] { + + override lazy val fields: DepartmentFields = new DepartmentFields { + override def id = IdField[DepartmentId, DepartmentRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, DepartmentRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + override def budget = OptField[BigDecimal, DepartmentRow](_path, "budget", None, Some("numeric"), x => x.budget, (row, value) => row.copy(budget = value)) + override def companyId = OptField[CompanyId, DepartmentRow](_path, "company_id", None, Some("uuid"), x => x.companyId, (row, value) => row.copy(companyId = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, DepartmentRow]] = + List[FieldLikeNoHkt[?, DepartmentRow]](fields.id, fields.name, fields.budget, fields.companyId) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentId.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentId.scala new file mode 100644 index 0000000000..c7d4839e8f --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import adventureworks.customtypes.TypoUUID +import typo.dsl.Bijection +import typo.dsl.PGType +import zio.jdbc.JdbcDecoder +import zio.jdbc.JdbcEncoder +import zio.jdbc.SqlFragment.Setter +import zio.json.JsonDecoder +import zio.json.JsonEncoder + +/** Type for the primary key of table `frontpage.department` */ +case class DepartmentId(value: TypoUUID) extends AnyVal +object DepartmentId { + implicit lazy val arrayJdbcDecoder: JdbcDecoder[Array[DepartmentId]] = JdbcDecoder[Array[TypoUUID]].map(_.map(DepartmentId.apply)) + implicit lazy val arrayJdbcEncoder: JdbcEncoder[Array[DepartmentId]] = JdbcEncoder[Array[TypoUUID]].contramap(_.map(_.value)) + implicit lazy val arraySetter: Setter[Array[DepartmentId]] = TypoUUID.arraySetter.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[DepartmentId, TypoUUID] = Bijection[DepartmentId, TypoUUID](_.value)(DepartmentId.apply) + implicit lazy val jdbcDecoder: JdbcDecoder[DepartmentId] = TypoUUID.jdbcDecoder.map(DepartmentId.apply) + implicit lazy val jdbcEncoder: JdbcEncoder[DepartmentId] = TypoUUID.jdbcEncoder.contramap(_.value) + implicit lazy val jsonDecoder: JsonDecoder[DepartmentId] = TypoUUID.jsonDecoder.map(DepartmentId.apply) + implicit lazy val jsonEncoder: JsonEncoder[DepartmentId] = TypoUUID.jsonEncoder.contramap(_.value) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[DepartmentId] = Ordering.by(_.value) + implicit lazy val pgType: PGType[DepartmentId] = TypoUUID.pgType.as + implicit lazy val setter: Setter[DepartmentId] = TypoUUID.setter.contramap(_.value) + implicit lazy val text: Text[DepartmentId] = new Text[DepartmentId] { + override def unsafeEncode(v: DepartmentId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: DepartmentId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepo.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepo.scala new file mode 100644 index 0000000000..c3e18a628a --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepo.scala @@ -0,0 +1,38 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +trait DepartmentRepo { + def delete: DeleteBuilder[DepartmentFields, DepartmentRow] + def deleteById(id: DepartmentId): ZIO[ZConnection, Throwable, Boolean] + def deleteByIds(ids: Array[DepartmentId]): ZIO[ZConnection, Throwable, Long] + def insert(unsaved: DepartmentRow): ZIO[ZConnection, Throwable, DepartmentRow] + def insert(unsaved: DepartmentRowUnsaved): ZIO[ZConnection, Throwable, DepartmentRow] + def insertStreaming(unsaved: ZStream[ZConnection, Throwable, DepartmentRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, DepartmentRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + def select: SelectBuilder[DepartmentFields, DepartmentRow] + def selectAll: ZStream[ZConnection, Throwable, DepartmentRow] + def selectById(id: DepartmentId): ZIO[ZConnection, Throwable, Option[DepartmentRow]] + def selectByIds(ids: Array[DepartmentId]): ZStream[ZConnection, Throwable, DepartmentRow] + def selectByIdsTracked(ids: Array[DepartmentId]): ZIO[ZConnection, Throwable, Map[DepartmentId, DepartmentRow]] + def update: UpdateBuilder[DepartmentFields, DepartmentRow] + def update(row: DepartmentRow): ZIO[ZConnection, Throwable, Boolean] + def upsert(unsaved: DepartmentRow): ZIO[ZConnection, Throwable, UpdateResult[DepartmentRow]] + // Not implementable for zio-jdbc: upsertBatch + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, DepartmentRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepoImpl.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepoImpl.scala new file mode 100644 index 0000000000..5ea441d0da --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepoImpl.scala @@ -0,0 +1,130 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.company.CompanyId +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.SqlFragment +import zio.jdbc.SqlFragment.Segment +import zio.jdbc.SqlFragment.Setter +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.jdbc.sqlInterpolator +import zio.stream.ZStream + +class DepartmentRepoImpl extends DepartmentRepo { + override def delete: DeleteBuilder[DepartmentFields, DepartmentRow] = { + DeleteBuilder(""""frontpage"."department"""", DepartmentFields.structure) + } + override def deleteById(id: DepartmentId): ZIO[ZConnection, Throwable, Boolean] = { + sql"""delete from "frontpage"."department" where "id" = ${Segment.paramSegment(id)(DepartmentId.setter)}""".delete.map(_ > 0) + } + override def deleteByIds(ids: Array[DepartmentId]): ZIO[ZConnection, Throwable, Long] = { + sql"""delete from "frontpage"."department" where "id" = ANY(${ids})""".delete + } + override def insert(unsaved: DepartmentRow): ZIO[ZConnection, Throwable, DepartmentRow] = { + sql"""insert into "frontpage"."department"("id", "name", "budget", "company_id") + values (${Segment.paramSegment(unsaved.id)(DepartmentId.setter)}::uuid, ${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}, ${Segment.paramSegment(unsaved.budget)(Setter.optionParamSetter(Setter.bigDecimalScalaSetter))}::numeric, ${Segment.paramSegment(unsaved.companyId)(Setter.optionParamSetter(CompanyId.setter))}::uuid) + returning "id", "name", "budget", "company_id" + """.insertReturning(using DepartmentRow.jdbcDecoder).map(_.updatedKeys.head) + } + override def insert(unsaved: DepartmentRowUnsaved): ZIO[ZConnection, Throwable, DepartmentRow] = { + val fs = List( + Some((sql""""name"""", sql"${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}")), + Some((sql""""budget"""", sql"${Segment.paramSegment(unsaved.budget)(Setter.optionParamSetter(Setter.bigDecimalScalaSetter))}::numeric")), + Some((sql""""company_id"""", sql"${Segment.paramSegment(unsaved.companyId)(Setter.optionParamSetter(CompanyId.setter))}::uuid")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""id"""", sql"${Segment.paramSegment(value: DepartmentId)(DepartmentId.setter)}::uuid")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."department" default values + returning "id", "name", "budget", "company_id" + """ + } else { + val names = fs.map { case (n, _) => n }.mkFragment(SqlFragment(", ")) + val values = fs.map { case (_, f) => f }.mkFragment(SqlFragment(", ")) + sql"""insert into "frontpage"."department"($names) values ($values) returning "id", "name", "budget", "company_id"""" + } + q.insertReturning(using DepartmentRow.jdbcDecoder).map(_.updatedKeys.head) + + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, DepartmentRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."department"("id", "name", "budget", "company_id") FROM STDIN""", batchSize, unsaved)(DepartmentRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, DepartmentRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."department"("name", "budget", "company_id", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(DepartmentRowUnsaved.text) + } + override def select: SelectBuilder[DepartmentFields, DepartmentRow] = { + SelectBuilderSql(""""frontpage"."department"""", DepartmentFields.structure, DepartmentRow.jdbcDecoder) + } + override def selectAll: ZStream[ZConnection, Throwable, DepartmentRow] = { + sql"""select "id", "name", "budget", "company_id" from "frontpage"."department"""".query(using DepartmentRow.jdbcDecoder).selectStream() + } + override def selectById(id: DepartmentId): ZIO[ZConnection, Throwable, Option[DepartmentRow]] = { + sql"""select "id", "name", "budget", "company_id" from "frontpage"."department" where "id" = ${Segment.paramSegment(id)(DepartmentId.setter)}""".query(using DepartmentRow.jdbcDecoder).selectOne + } + override def selectByIds(ids: Array[DepartmentId]): ZStream[ZConnection, Throwable, DepartmentRow] = { + sql"""select "id", "name", "budget", "company_id" from "frontpage"."department" where "id" = ANY(${Segment.paramSegment(ids)(DepartmentId.arraySetter)})""".query(using DepartmentRow.jdbcDecoder).selectStream() + } + override def selectByIdsTracked(ids: Array[DepartmentId]): ZIO[ZConnection, Throwable, Map[DepartmentId, DepartmentRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[DepartmentFields, DepartmentRow] = { + UpdateBuilder(""""frontpage"."department"""", DepartmentFields.structure, DepartmentRow.jdbcDecoder) + } + override def update(row: DepartmentRow): ZIO[ZConnection, Throwable, Boolean] = { + val id = row.id + sql"""update "frontpage"."department" + set "name" = ${Segment.paramSegment(row.name)(Setter.stringSetter)}, + "budget" = ${Segment.paramSegment(row.budget)(Setter.optionParamSetter(Setter.bigDecimalScalaSetter))}::numeric, + "company_id" = ${Segment.paramSegment(row.companyId)(Setter.optionParamSetter(CompanyId.setter))}::uuid + where "id" = ${Segment.paramSegment(id)(DepartmentId.setter)}""".update.map(_ > 0) + } + override def upsert(unsaved: DepartmentRow): ZIO[ZConnection, Throwable, UpdateResult[DepartmentRow]] = { + sql"""insert into "frontpage"."department"("id", "name", "budget", "company_id") + values ( + ${Segment.paramSegment(unsaved.id)(DepartmentId.setter)}::uuid, + ${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}, + ${Segment.paramSegment(unsaved.budget)(Setter.optionParamSetter(Setter.bigDecimalScalaSetter))}::numeric, + ${Segment.paramSegment(unsaved.companyId)(Setter.optionParamSetter(CompanyId.setter))}::uuid + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "budget" = EXCLUDED."budget", + "company_id" = EXCLUDED."company_id" + returning "id", "name", "budget", "company_id"""".insertReturning(using DepartmentRow.jdbcDecoder) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, DepartmentRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + val created = sql"""create temporary table department_TEMP (like "frontpage"."department") on commit drop""".execute + val copied = streamingInsert(s"""copy department_TEMP("id", "name", "budget", "company_id") from stdin""", batchSize, unsaved)(DepartmentRow.text) + val merged = sql"""insert into "frontpage"."department"("id", "name", "budget", "company_id") + select * from department_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "budget" = EXCLUDED."budget", + "company_id" = EXCLUDED."company_id" + ; + drop table department_TEMP;""".update + created *> copied *> merged + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepoMock.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepoMock.scala new file mode 100644 index 0000000000..ae7bfb8f6e --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRepoMock.scala @@ -0,0 +1,116 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams +import zio.Chunk +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +class DepartmentRepoMock(toRow: Function1[DepartmentRowUnsaved, DepartmentRow], + map: scala.collection.mutable.Map[DepartmentId, DepartmentRow] = scala.collection.mutable.Map.empty) extends DepartmentRepo { + override def delete: DeleteBuilder[DepartmentFields, DepartmentRow] = { + DeleteBuilderMock(DeleteParams.empty, DepartmentFields.structure, map) + } + override def deleteById(id: DepartmentId): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[DepartmentId]): ZIO[ZConnection, Throwable, Long] = { + ZIO.succeed(ids.map(id => map.remove(id)).count(_.isDefined).toLong) + } + override def insert(unsaved: DepartmentRow): ZIO[ZConnection, Throwable, DepartmentRow] = { + ZIO.succeed { + val _ = + if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: DepartmentRowUnsaved): ZIO[ZConnection, Throwable, DepartmentRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, DepartmentRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, DepartmentRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, unsavedRow) => + ZIO.succeed { + val row = toRow(unsavedRow) + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + override def select: SelectBuilder[DepartmentFields, DepartmentRow] = { + SelectBuilderMock(DepartmentFields.structure, ZIO.succeed(Chunk.fromIterable(map.values)), SelectParams.empty) + } + override def selectAll: ZStream[ZConnection, Throwable, DepartmentRow] = { + ZStream.fromIterable(map.values) + } + override def selectById(id: DepartmentId): ZIO[ZConnection, Throwable, Option[DepartmentRow]] = { + ZIO.succeed(map.get(id)) + } + override def selectByIds(ids: Array[DepartmentId]): ZStream[ZConnection, Throwable, DepartmentRow] = { + ZStream.fromIterable(ids.flatMap(map.get)) + } + override def selectByIdsTracked(ids: Array[DepartmentId]): ZIO[ZConnection, Throwable, Map[DepartmentId, DepartmentRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[DepartmentFields, DepartmentRow] = { + UpdateBuilderMock(UpdateParams.empty, DepartmentFields.structure, map) + } + override def update(row: DepartmentRow): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: DepartmentRow): ZIO[ZConnection, Throwable, UpdateResult[DepartmentRow]] = { + ZIO.succeed { + map.put(unsaved.id, unsaved): @nowarn + UpdateResult(1, Chunk.single(unsaved)) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, DepartmentRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRow.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRow.scala new file mode 100644 index 0000000000..78c01e3e29 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRow.scala @@ -0,0 +1,79 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.company.CompanyId +import java.sql.ResultSet +import zio.jdbc.JdbcDecoder +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** Table: frontpage.department + Primary key: id */ +case class DepartmentRow( + /** Default: gen_random_uuid() */ + id: DepartmentId, + name: String, + budget: Option[BigDecimal], + /** Points to [[company.CompanyRow.id]] */ + companyId: Option[CompanyId] +){ + def toUnsavedRow(id: Defaulted[DepartmentId]): DepartmentRowUnsaved = + DepartmentRowUnsaved(name, budget, companyId, id) + } + +object DepartmentRow { + implicit lazy val jdbcDecoder: JdbcDecoder[DepartmentRow] = new JdbcDecoder[DepartmentRow] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, DepartmentRow) = + columIndex + 3 -> + DepartmentRow( + id = DepartmentId.jdbcDecoder.unsafeDecode(columIndex + 0, rs)._2, + name = JdbcDecoder.stringDecoder.unsafeDecode(columIndex + 1, rs)._2, + budget = JdbcDecoder.optionDecoder(JdbcDecoder.bigDecimalDecoderScala).unsafeDecode(columIndex + 2, rs)._2, + companyId = JdbcDecoder.optionDecoder(CompanyId.jdbcDecoder).unsafeDecode(columIndex + 3, rs)._2 + ) + } + implicit lazy val jsonDecoder: JsonDecoder[DepartmentRow] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(DepartmentId.jsonDecoder)) + val name = jsonObj.get("name").toRight("Missing field 'name'").flatMap(_.as(JsonDecoder.string)) + val budget = jsonObj.get("budget").fold[Either[String, Option[BigDecimal]]](Right(None))(_.as(JsonDecoder.option(using JsonDecoder.scalaBigDecimal))) + val companyId = jsonObj.get("company_id").fold[Either[String, Option[CompanyId]]](Right(None))(_.as(JsonDecoder.option(using CompanyId.jsonDecoder))) + if (id.isRight && name.isRight && budget.isRight && companyId.isRight) + Right(DepartmentRow(id = id.toOption.get, name = name.toOption.get, budget = budget.toOption.get, companyId = companyId.toOption.get)) + else Left(List[Either[String, Any]](id, name, budget, companyId).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[DepartmentRow] = new JsonEncoder[DepartmentRow] { + override def unsafeEncode(a: DepartmentRow, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""id":""") + DepartmentId.jsonEncoder.unsafeEncode(a.id, indent, out) + out.write(",") + out.write(""""name":""") + JsonEncoder.string.unsafeEncode(a.name, indent, out) + out.write(",") + out.write(""""budget":""") + JsonEncoder.option(using JsonEncoder.scalaBigDecimal).unsafeEncode(a.budget, indent, out) + out.write(",") + out.write(""""company_id":""") + JsonEncoder.option(using CompanyId.jsonEncoder).unsafeEncode(a.companyId, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[DepartmentRow] = Text.instance[DepartmentRow]{ (row, sb) => + DepartmentId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(Text.bigDecimalInstance).unsafeEncode(row.budget, sb) + sb.append(Text.DELIMETER) + Text.option(CompanyId.text).unsafeEncode(row.companyId, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRowUnsaved.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRowUnsaved.scala new file mode 100644 index 0000000000..449bf4623e --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/department/DepartmentRowUnsaved.scala @@ -0,0 +1,73 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package department + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.company.CompanyId +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** This class corresponds to a row in table `frontpage.department` which has not been persisted yet */ +case class DepartmentRowUnsaved( + name: String, + budget: Option[BigDecimal], + /** Points to [[company.CompanyRow.id]] */ + companyId: Option[CompanyId], + /** Default: gen_random_uuid() */ + id: Defaulted[DepartmentId] = Defaulted.UseDefault +) { + def toRow(idDefault: => DepartmentId): DepartmentRow = + DepartmentRow( + name = name, + budget = budget, + companyId = companyId, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object DepartmentRowUnsaved { + implicit lazy val jsonDecoder: JsonDecoder[DepartmentRowUnsaved] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val name = jsonObj.get("name").toRight("Missing field 'name'").flatMap(_.as(JsonDecoder.string)) + val budget = jsonObj.get("budget").fold[Either[String, Option[BigDecimal]]](Right(None))(_.as(JsonDecoder.option(using JsonDecoder.scalaBigDecimal))) + val companyId = jsonObj.get("company_id").fold[Either[String, Option[CompanyId]]](Right(None))(_.as(JsonDecoder.option(using CompanyId.jsonDecoder))) + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(Defaulted.jsonDecoder(DepartmentId.jsonDecoder))) + if (name.isRight && budget.isRight && companyId.isRight && id.isRight) + Right(DepartmentRowUnsaved(name = name.toOption.get, budget = budget.toOption.get, companyId = companyId.toOption.get, id = id.toOption.get)) + else Left(List[Either[String, Any]](name, budget, companyId, id).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[DepartmentRowUnsaved] = new JsonEncoder[DepartmentRowUnsaved] { + override def unsafeEncode(a: DepartmentRowUnsaved, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""name":""") + JsonEncoder.string.unsafeEncode(a.name, indent, out) + out.write(",") + out.write(""""budget":""") + JsonEncoder.option(using JsonEncoder.scalaBigDecimal).unsafeEncode(a.budget, indent, out) + out.write(",") + out.write(""""company_id":""") + JsonEncoder.option(using CompanyId.jsonEncoder).unsafeEncode(a.companyId, indent, out) + out.write(",") + out.write(""""id":""") + Defaulted.jsonEncoder(DepartmentId.jsonEncoder).unsafeEncode(a.id, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[DepartmentRowUnsaved] = Text.instance[DepartmentRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(Text.bigDecimalInstance).unsafeEncode(row.budget, sb) + sb.append(Text.DELIMETER) + Text.option(CompanyId.text).unsafeEncode(row.companyId, sb) + sb.append(Text.DELIMETER) + Defaulted.text(DepartmentId.text).unsafeEncode(row.id, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeFields.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeFields.scala new file mode 100644 index 0000000000..8ac2329269 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeFields.scala @@ -0,0 +1,50 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.frontpage.person.PersonFields +import adventureworks.frontpage.person.PersonId +import adventureworks.frontpage.person.PersonRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait EmployeeFields { + def id: IdField[EmployeeId, EmployeeRow] + def personId: Field[PersonId, EmployeeRow] + def salary: OptField[BigDecimal, EmployeeRow] + def fkPerson: ForeignKey[PersonFields, PersonRow] = + ForeignKey[PersonFields, PersonRow]("frontpage.fk_person", Nil) + .withColumnPair(personId, _.id) +} + +object EmployeeFields { + lazy val structure: Relation[EmployeeFields, EmployeeRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[EmployeeFields, EmployeeRow] { + + override lazy val fields: EmployeeFields = new EmployeeFields { + override def id = IdField[EmployeeId, EmployeeRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def personId = Field[PersonId, EmployeeRow](_path, "person_id", None, Some("uuid"), x => x.personId, (row, value) => row.copy(personId = value)) + override def salary = OptField[BigDecimal, EmployeeRow](_path, "salary", None, Some("numeric"), x => x.salary, (row, value) => row.copy(salary = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, EmployeeRow]] = + List[FieldLikeNoHkt[?, EmployeeRow]](fields.id, fields.personId, fields.salary) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeId.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeId.scala new file mode 100644 index 0000000000..20035f6fe0 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.customtypes.TypoUUID +import typo.dsl.Bijection +import typo.dsl.PGType +import zio.jdbc.JdbcDecoder +import zio.jdbc.JdbcEncoder +import zio.jdbc.SqlFragment.Setter +import zio.json.JsonDecoder +import zio.json.JsonEncoder + +/** Type for the primary key of table `frontpage.employee` */ +case class EmployeeId(value: TypoUUID) extends AnyVal +object EmployeeId { + implicit lazy val arrayJdbcDecoder: JdbcDecoder[Array[EmployeeId]] = JdbcDecoder[Array[TypoUUID]].map(_.map(EmployeeId.apply)) + implicit lazy val arrayJdbcEncoder: JdbcEncoder[Array[EmployeeId]] = JdbcEncoder[Array[TypoUUID]].contramap(_.map(_.value)) + implicit lazy val arraySetter: Setter[Array[EmployeeId]] = TypoUUID.arraySetter.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[EmployeeId, TypoUUID] = Bijection[EmployeeId, TypoUUID](_.value)(EmployeeId.apply) + implicit lazy val jdbcDecoder: JdbcDecoder[EmployeeId] = TypoUUID.jdbcDecoder.map(EmployeeId.apply) + implicit lazy val jdbcEncoder: JdbcEncoder[EmployeeId] = TypoUUID.jdbcEncoder.contramap(_.value) + implicit lazy val jsonDecoder: JsonDecoder[EmployeeId] = TypoUUID.jsonDecoder.map(EmployeeId.apply) + implicit lazy val jsonEncoder: JsonEncoder[EmployeeId] = TypoUUID.jsonEncoder.contramap(_.value) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[EmployeeId] = Ordering.by(_.value) + implicit lazy val pgType: PGType[EmployeeId] = TypoUUID.pgType.as + implicit lazy val setter: Setter[EmployeeId] = TypoUUID.setter.contramap(_.value) + implicit lazy val text: Text[EmployeeId] = new Text[EmployeeId] { + override def unsafeEncode(v: EmployeeId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: EmployeeId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepo.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepo.scala new file mode 100644 index 0000000000..58c4267bcb --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepo.scala @@ -0,0 +1,40 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.frontpage.person.PersonId +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +trait EmployeeRepo { + def delete: DeleteBuilder[EmployeeFields, EmployeeRow] + def deleteById(id: EmployeeId): ZIO[ZConnection, Throwable, Boolean] + def deleteByIds(ids: Array[EmployeeId]): ZIO[ZConnection, Throwable, Long] + def insert(unsaved: EmployeeRow): ZIO[ZConnection, Throwable, EmployeeRow] + def insert(unsaved: EmployeeRowUnsaved): ZIO[ZConnection, Throwable, EmployeeRow] + def insertStreaming(unsaved: ZStream[ZConnection, Throwable, EmployeeRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, EmployeeRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + def select: SelectBuilder[EmployeeFields, EmployeeRow] + def selectAll: ZStream[ZConnection, Throwable, EmployeeRow] + def selectById(id: EmployeeId): ZIO[ZConnection, Throwable, Option[EmployeeRow]] + def selectByIds(ids: Array[EmployeeId]): ZStream[ZConnection, Throwable, EmployeeRow] + def selectByIdsTracked(ids: Array[EmployeeId]): ZIO[ZConnection, Throwable, Map[EmployeeId, EmployeeRow]] + def selectByUniquePersonId(personId: PersonId): ZIO[ZConnection, Throwable, Option[EmployeeRow]] + def update: UpdateBuilder[EmployeeFields, EmployeeRow] + def update(row: EmployeeRow): ZIO[ZConnection, Throwable, Boolean] + def upsert(unsaved: EmployeeRow): ZIO[ZConnection, Throwable, UpdateResult[EmployeeRow]] + // Not implementable for zio-jdbc: upsertBatch + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, EmployeeRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepoImpl.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepoImpl.scala new file mode 100644 index 0000000000..e9e775018b --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepoImpl.scala @@ -0,0 +1,131 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.person.PersonId +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.SqlFragment +import zio.jdbc.SqlFragment.Segment +import zio.jdbc.SqlFragment.Setter +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.jdbc.sqlInterpolator +import zio.stream.ZStream + +class EmployeeRepoImpl extends EmployeeRepo { + override def delete: DeleteBuilder[EmployeeFields, EmployeeRow] = { + DeleteBuilder(""""frontpage"."employee"""", EmployeeFields.structure) + } + override def deleteById(id: EmployeeId): ZIO[ZConnection, Throwable, Boolean] = { + sql"""delete from "frontpage"."employee" where "id" = ${Segment.paramSegment(id)(EmployeeId.setter)}""".delete.map(_ > 0) + } + override def deleteByIds(ids: Array[EmployeeId]): ZIO[ZConnection, Throwable, Long] = { + sql"""delete from "frontpage"."employee" where "id" = ANY(${ids})""".delete + } + override def insert(unsaved: EmployeeRow): ZIO[ZConnection, Throwable, EmployeeRow] = { + sql"""insert into "frontpage"."employee"("id", "person_id", "salary") + values (${Segment.paramSegment(unsaved.id)(EmployeeId.setter)}::uuid, ${Segment.paramSegment(unsaved.personId)(PersonId.setter)}::uuid, ${Segment.paramSegment(unsaved.salary)(Setter.optionParamSetter(Setter.bigDecimalScalaSetter))}::numeric) + returning "id", "person_id", "salary" + """.insertReturning(using EmployeeRow.jdbcDecoder).map(_.updatedKeys.head) + } + override def insert(unsaved: EmployeeRowUnsaved): ZIO[ZConnection, Throwable, EmployeeRow] = { + val fs = List( + Some((sql""""person_id"""", sql"${Segment.paramSegment(unsaved.personId)(PersonId.setter)}::uuid")), + Some((sql""""salary"""", sql"${Segment.paramSegment(unsaved.salary)(Setter.optionParamSetter(Setter.bigDecimalScalaSetter))}::numeric")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""id"""", sql"${Segment.paramSegment(value: EmployeeId)(EmployeeId.setter)}::uuid")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."employee" default values + returning "id", "person_id", "salary" + """ + } else { + val names = fs.map { case (n, _) => n }.mkFragment(SqlFragment(", ")) + val values = fs.map { case (_, f) => f }.mkFragment(SqlFragment(", ")) + sql"""insert into "frontpage"."employee"($names) values ($values) returning "id", "person_id", "salary"""" + } + q.insertReturning(using EmployeeRow.jdbcDecoder).map(_.updatedKeys.head) + + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, EmployeeRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."employee"("id", "person_id", "salary") FROM STDIN""", batchSize, unsaved)(EmployeeRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, EmployeeRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."employee"("person_id", "salary", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(EmployeeRowUnsaved.text) + } + override def select: SelectBuilder[EmployeeFields, EmployeeRow] = { + SelectBuilderSql(""""frontpage"."employee"""", EmployeeFields.structure, EmployeeRow.jdbcDecoder) + } + override def selectAll: ZStream[ZConnection, Throwable, EmployeeRow] = { + sql"""select "id", "person_id", "salary" from "frontpage"."employee"""".query(using EmployeeRow.jdbcDecoder).selectStream() + } + override def selectById(id: EmployeeId): ZIO[ZConnection, Throwable, Option[EmployeeRow]] = { + sql"""select "id", "person_id", "salary" from "frontpage"."employee" where "id" = ${Segment.paramSegment(id)(EmployeeId.setter)}""".query(using EmployeeRow.jdbcDecoder).selectOne + } + override def selectByIds(ids: Array[EmployeeId]): ZStream[ZConnection, Throwable, EmployeeRow] = { + sql"""select "id", "person_id", "salary" from "frontpage"."employee" where "id" = ANY(${Segment.paramSegment(ids)(EmployeeId.arraySetter)})""".query(using EmployeeRow.jdbcDecoder).selectStream() + } + override def selectByIdsTracked(ids: Array[EmployeeId]): ZIO[ZConnection, Throwable, Map[EmployeeId, EmployeeRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def selectByUniquePersonId(personId: PersonId): ZIO[ZConnection, Throwable, Option[EmployeeRow]] = { + sql"""select "id", "person_id", "salary" + from "frontpage"."employee" + where "person_id" = ${Segment.paramSegment(personId)(PersonId.setter)} + """.query(using EmployeeRow.jdbcDecoder).selectOne + } + override def update: UpdateBuilder[EmployeeFields, EmployeeRow] = { + UpdateBuilder(""""frontpage"."employee"""", EmployeeFields.structure, EmployeeRow.jdbcDecoder) + } + override def update(row: EmployeeRow): ZIO[ZConnection, Throwable, Boolean] = { + val id = row.id + sql"""update "frontpage"."employee" + set "person_id" = ${Segment.paramSegment(row.personId)(PersonId.setter)}::uuid, + "salary" = ${Segment.paramSegment(row.salary)(Setter.optionParamSetter(Setter.bigDecimalScalaSetter))}::numeric + where "id" = ${Segment.paramSegment(id)(EmployeeId.setter)}""".update.map(_ > 0) + } + override def upsert(unsaved: EmployeeRow): ZIO[ZConnection, Throwable, UpdateResult[EmployeeRow]] = { + sql"""insert into "frontpage"."employee"("id", "person_id", "salary") + values ( + ${Segment.paramSegment(unsaved.id)(EmployeeId.setter)}::uuid, + ${Segment.paramSegment(unsaved.personId)(PersonId.setter)}::uuid, + ${Segment.paramSegment(unsaved.salary)(Setter.optionParamSetter(Setter.bigDecimalScalaSetter))}::numeric + ) + on conflict ("id") + do update set + "person_id" = EXCLUDED."person_id", + "salary" = EXCLUDED."salary" + returning "id", "person_id", "salary"""".insertReturning(using EmployeeRow.jdbcDecoder) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, EmployeeRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + val created = sql"""create temporary table employee_TEMP (like "frontpage"."employee") on commit drop""".execute + val copied = streamingInsert(s"""copy employee_TEMP("id", "person_id", "salary") from stdin""", batchSize, unsaved)(EmployeeRow.text) + val merged = sql"""insert into "frontpage"."employee"("id", "person_id", "salary") + select * from employee_TEMP + on conflict ("id") + do update set + "person_id" = EXCLUDED."person_id", + "salary" = EXCLUDED."salary" + ; + drop table employee_TEMP;""".update + created *> copied *> merged + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepoMock.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepoMock.scala new file mode 100644 index 0000000000..5d818a99d7 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRepoMock.scala @@ -0,0 +1,120 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.frontpage.person.PersonId +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams +import zio.Chunk +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +class EmployeeRepoMock(toRow: Function1[EmployeeRowUnsaved, EmployeeRow], + map: scala.collection.mutable.Map[EmployeeId, EmployeeRow] = scala.collection.mutable.Map.empty) extends EmployeeRepo { + override def delete: DeleteBuilder[EmployeeFields, EmployeeRow] = { + DeleteBuilderMock(DeleteParams.empty, EmployeeFields.structure, map) + } + override def deleteById(id: EmployeeId): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[EmployeeId]): ZIO[ZConnection, Throwable, Long] = { + ZIO.succeed(ids.map(id => map.remove(id)).count(_.isDefined).toLong) + } + override def insert(unsaved: EmployeeRow): ZIO[ZConnection, Throwable, EmployeeRow] = { + ZIO.succeed { + val _ = + if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: EmployeeRowUnsaved): ZIO[ZConnection, Throwable, EmployeeRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, EmployeeRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, EmployeeRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, unsavedRow) => + ZIO.succeed { + val row = toRow(unsavedRow) + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + override def select: SelectBuilder[EmployeeFields, EmployeeRow] = { + SelectBuilderMock(EmployeeFields.structure, ZIO.succeed(Chunk.fromIterable(map.values)), SelectParams.empty) + } + override def selectAll: ZStream[ZConnection, Throwable, EmployeeRow] = { + ZStream.fromIterable(map.values) + } + override def selectById(id: EmployeeId): ZIO[ZConnection, Throwable, Option[EmployeeRow]] = { + ZIO.succeed(map.get(id)) + } + override def selectByIds(ids: Array[EmployeeId]): ZStream[ZConnection, Throwable, EmployeeRow] = { + ZStream.fromIterable(ids.flatMap(map.get)) + } + override def selectByIdsTracked(ids: Array[EmployeeId]): ZIO[ZConnection, Throwable, Map[EmployeeId, EmployeeRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def selectByUniquePersonId(personId: PersonId): ZIO[ZConnection, Throwable, Option[EmployeeRow]] = { + ZIO.succeed(map.values.find(v => personId == v.personId)) + } + override def update: UpdateBuilder[EmployeeFields, EmployeeRow] = { + UpdateBuilderMock(UpdateParams.empty, EmployeeFields.structure, map) + } + override def update(row: EmployeeRow): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: EmployeeRow): ZIO[ZConnection, Throwable, UpdateResult[EmployeeRow]] = { + ZIO.succeed { + map.put(unsaved.id, unsaved): @nowarn + UpdateResult(1, Chunk.single(unsaved)) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, EmployeeRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRow.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRow.scala new file mode 100644 index 0000000000..4cf5cff8bc --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRow.scala @@ -0,0 +1,71 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.person.PersonId +import java.sql.ResultSet +import zio.jdbc.JdbcDecoder +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** Table: frontpage.employee + Primary key: id */ +case class EmployeeRow( + /** Default: gen_random_uuid() */ + id: EmployeeId, + /** Points to [[person.PersonRow.id]] */ + personId: PersonId, + salary: Option[BigDecimal] +){ + def toUnsavedRow(id: Defaulted[EmployeeId]): EmployeeRowUnsaved = + EmployeeRowUnsaved(personId, salary, id) + } + +object EmployeeRow { + implicit lazy val jdbcDecoder: JdbcDecoder[EmployeeRow] = new JdbcDecoder[EmployeeRow] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, EmployeeRow) = + columIndex + 2 -> + EmployeeRow( + id = EmployeeId.jdbcDecoder.unsafeDecode(columIndex + 0, rs)._2, + personId = PersonId.jdbcDecoder.unsafeDecode(columIndex + 1, rs)._2, + salary = JdbcDecoder.optionDecoder(JdbcDecoder.bigDecimalDecoderScala).unsafeDecode(columIndex + 2, rs)._2 + ) + } + implicit lazy val jsonDecoder: JsonDecoder[EmployeeRow] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(EmployeeId.jsonDecoder)) + val personId = jsonObj.get("person_id").toRight("Missing field 'person_id'").flatMap(_.as(PersonId.jsonDecoder)) + val salary = jsonObj.get("salary").fold[Either[String, Option[BigDecimal]]](Right(None))(_.as(JsonDecoder.option(using JsonDecoder.scalaBigDecimal))) + if (id.isRight && personId.isRight && salary.isRight) + Right(EmployeeRow(id = id.toOption.get, personId = personId.toOption.get, salary = salary.toOption.get)) + else Left(List[Either[String, Any]](id, personId, salary).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[EmployeeRow] = new JsonEncoder[EmployeeRow] { + override def unsafeEncode(a: EmployeeRow, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""id":""") + EmployeeId.jsonEncoder.unsafeEncode(a.id, indent, out) + out.write(",") + out.write(""""person_id":""") + PersonId.jsonEncoder.unsafeEncode(a.personId, indent, out) + out.write(",") + out.write(""""salary":""") + JsonEncoder.option(using JsonEncoder.scalaBigDecimal).unsafeEncode(a.salary, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[EmployeeRow] = Text.instance[EmployeeRow]{ (row, sb) => + EmployeeId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + PersonId.text.unsafeEncode(row.personId, sb) + sb.append(Text.DELIMETER) + Text.option(Text.bigDecimalInstance).unsafeEncode(row.salary, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRowUnsaved.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRowUnsaved.scala new file mode 100644 index 0000000000..9295b63646 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/employee/EmployeeRowUnsaved.scala @@ -0,0 +1,65 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package employee + +import adventureworks.customtypes.Defaulted +import adventureworks.frontpage.person.PersonId +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** This class corresponds to a row in table `frontpage.employee` which has not been persisted yet */ +case class EmployeeRowUnsaved( + /** Points to [[person.PersonRow.id]] */ + personId: PersonId, + salary: Option[BigDecimal], + /** Default: gen_random_uuid() */ + id: Defaulted[EmployeeId] = Defaulted.UseDefault +) { + def toRow(idDefault: => EmployeeId): EmployeeRow = + EmployeeRow( + personId = personId, + salary = salary, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object EmployeeRowUnsaved { + implicit lazy val jsonDecoder: JsonDecoder[EmployeeRowUnsaved] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val personId = jsonObj.get("person_id").toRight("Missing field 'person_id'").flatMap(_.as(PersonId.jsonDecoder)) + val salary = jsonObj.get("salary").fold[Either[String, Option[BigDecimal]]](Right(None))(_.as(JsonDecoder.option(using JsonDecoder.scalaBigDecimal))) + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(Defaulted.jsonDecoder(EmployeeId.jsonDecoder))) + if (personId.isRight && salary.isRight && id.isRight) + Right(EmployeeRowUnsaved(personId = personId.toOption.get, salary = salary.toOption.get, id = id.toOption.get)) + else Left(List[Either[String, Any]](personId, salary, id).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[EmployeeRowUnsaved] = new JsonEncoder[EmployeeRowUnsaved] { + override def unsafeEncode(a: EmployeeRowUnsaved, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""person_id":""") + PersonId.jsonEncoder.unsafeEncode(a.personId, indent, out) + out.write(",") + out.write(""""salary":""") + JsonEncoder.option(using JsonEncoder.scalaBigDecimal).unsafeEncode(a.salary, indent, out) + out.write(",") + out.write(""""id":""") + Defaulted.jsonEncoder(EmployeeId.jsonEncoder).unsafeEncode(a.id, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[EmployeeRowUnsaved] = Text.instance[EmployeeRowUnsaved]{ (row, sb) => + PersonId.text.unsafeEncode(row.personId, sb) + sb.append(Text.DELIMETER) + Text.option(Text.bigDecimalInstance).unsafeEncode(row.salary, sb) + sb.append(Text.DELIMETER) + Defaulted.text(EmployeeId.text).unsafeEncode(row.id, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationFields.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationFields.scala new file mode 100644 index 0000000000..c4fb82d377 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationFields.scala @@ -0,0 +1,53 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import adventureworks.customtypes.TypoInet +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoPoint +import adventureworks.customtypes.TypoPolygon +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait LocationFields { + def id: IdField[LocationId, LocationRow] + def name: Field[String, LocationRow] + def position: OptField[TypoPoint, LocationRow] + def area: OptField[TypoPolygon, LocationRow] + def ipRange: OptField[TypoInet, LocationRow] + def metadata: OptField[TypoJsonb, LocationRow] +} + +object LocationFields { + lazy val structure: Relation[LocationFields, LocationRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[LocationFields, LocationRow] { + + override lazy val fields: LocationFields = new LocationFields { + override def id = IdField[LocationId, LocationRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, LocationRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + override def position = OptField[TypoPoint, LocationRow](_path, "position", None, Some("point"), x => x.position, (row, value) => row.copy(position = value)) + override def area = OptField[TypoPolygon, LocationRow](_path, "area", None, Some("polygon"), x => x.area, (row, value) => row.copy(area = value)) + override def ipRange = OptField[TypoInet, LocationRow](_path, "ip_range", None, Some("inet"), x => x.ipRange, (row, value) => row.copy(ipRange = value)) + override def metadata = OptField[TypoJsonb, LocationRow](_path, "metadata", None, Some("jsonb"), x => x.metadata, (row, value) => row.copy(metadata = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, LocationRow]] = + List[FieldLikeNoHkt[?, LocationRow]](fields.id, fields.name, fields.position, fields.area, fields.ipRange, fields.metadata) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationId.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationId.scala new file mode 100644 index 0000000000..993affd7a7 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import adventureworks.customtypes.TypoUUID +import typo.dsl.Bijection +import typo.dsl.PGType +import zio.jdbc.JdbcDecoder +import zio.jdbc.JdbcEncoder +import zio.jdbc.SqlFragment.Setter +import zio.json.JsonDecoder +import zio.json.JsonEncoder + +/** Type for the primary key of table `frontpage.location` */ +case class LocationId(value: TypoUUID) extends AnyVal +object LocationId { + implicit lazy val arrayJdbcDecoder: JdbcDecoder[Array[LocationId]] = JdbcDecoder[Array[TypoUUID]].map(_.map(LocationId.apply)) + implicit lazy val arrayJdbcEncoder: JdbcEncoder[Array[LocationId]] = JdbcEncoder[Array[TypoUUID]].contramap(_.map(_.value)) + implicit lazy val arraySetter: Setter[Array[LocationId]] = TypoUUID.arraySetter.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[LocationId, TypoUUID] = Bijection[LocationId, TypoUUID](_.value)(LocationId.apply) + implicit lazy val jdbcDecoder: JdbcDecoder[LocationId] = TypoUUID.jdbcDecoder.map(LocationId.apply) + implicit lazy val jdbcEncoder: JdbcEncoder[LocationId] = TypoUUID.jdbcEncoder.contramap(_.value) + implicit lazy val jsonDecoder: JsonDecoder[LocationId] = TypoUUID.jsonDecoder.map(LocationId.apply) + implicit lazy val jsonEncoder: JsonEncoder[LocationId] = TypoUUID.jsonEncoder.contramap(_.value) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[LocationId] = Ordering.by(_.value) + implicit lazy val pgType: PGType[LocationId] = TypoUUID.pgType.as + implicit lazy val setter: Setter[LocationId] = TypoUUID.setter.contramap(_.value) + implicit lazy val text: Text[LocationId] = new Text[LocationId] { + override def unsafeEncode(v: LocationId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: LocationId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationRepo.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationRepo.scala new file mode 100644 index 0000000000..30fba071a8 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationRepo.scala @@ -0,0 +1,38 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +trait LocationRepo { + def delete: DeleteBuilder[LocationFields, LocationRow] + def deleteById(id: LocationId): ZIO[ZConnection, Throwable, Boolean] + def deleteByIds(ids: Array[LocationId]): ZIO[ZConnection, Throwable, Long] + def insert(unsaved: LocationRow): ZIO[ZConnection, Throwable, LocationRow] + def insert(unsaved: LocationRowUnsaved): ZIO[ZConnection, Throwable, LocationRow] + def insertStreaming(unsaved: ZStream[ZConnection, Throwable, LocationRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, LocationRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + def select: SelectBuilder[LocationFields, LocationRow] + def selectAll: ZStream[ZConnection, Throwable, LocationRow] + def selectById(id: LocationId): ZIO[ZConnection, Throwable, Option[LocationRow]] + def selectByIds(ids: Array[LocationId]): ZStream[ZConnection, Throwable, LocationRow] + def selectByIdsTracked(ids: Array[LocationId]): ZIO[ZConnection, Throwable, Map[LocationId, LocationRow]] + def update: UpdateBuilder[LocationFields, LocationRow] + def update(row: LocationRow): ZIO[ZConnection, Throwable, Boolean] + def upsert(unsaved: LocationRow): ZIO[ZConnection, Throwable, UpdateResult[LocationRow]] + // Not implementable for zio-jdbc: upsertBatch + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, LocationRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationRepoImpl.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationRepoImpl.scala new file mode 100644 index 0000000000..57a2186ff0 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationRepoImpl.scala @@ -0,0 +1,146 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoInet +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoPoint +import adventureworks.customtypes.TypoPolygon +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.SqlFragment +import zio.jdbc.SqlFragment.Segment +import zio.jdbc.SqlFragment.Setter +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.jdbc.sqlInterpolator +import zio.stream.ZStream + +class LocationRepoImpl extends LocationRepo { + override def delete: DeleteBuilder[LocationFields, LocationRow] = { + DeleteBuilder(""""frontpage"."location"""", LocationFields.structure) + } + override def deleteById(id: LocationId): ZIO[ZConnection, Throwable, Boolean] = { + sql"""delete from "frontpage"."location" where "id" = ${Segment.paramSegment(id)(LocationId.setter)}""".delete.map(_ > 0) + } + override def deleteByIds(ids: Array[LocationId]): ZIO[ZConnection, Throwable, Long] = { + sql"""delete from "frontpage"."location" where "id" = ANY(${ids})""".delete + } + override def insert(unsaved: LocationRow): ZIO[ZConnection, Throwable, LocationRow] = { + sql"""insert into "frontpage"."location"("id", "name", "position", "area", "ip_range", "metadata") + values (${Segment.paramSegment(unsaved.id)(LocationId.setter)}::uuid, ${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}, ${Segment.paramSegment(unsaved.position)(Setter.optionParamSetter(TypoPoint.setter))}::point, ${Segment.paramSegment(unsaved.area)(Setter.optionParamSetter(TypoPolygon.setter))}::polygon, ${Segment.paramSegment(unsaved.ipRange)(Setter.optionParamSetter(TypoInet.setter))}::inet, ${Segment.paramSegment(unsaved.metadata)(Setter.optionParamSetter(TypoJsonb.setter))}::jsonb) + returning "id", "name", "position", "area", "ip_range", "metadata" + """.insertReturning(using LocationRow.jdbcDecoder).map(_.updatedKeys.head) + } + override def insert(unsaved: LocationRowUnsaved): ZIO[ZConnection, Throwable, LocationRow] = { + val fs = List( + Some((sql""""name"""", sql"${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}")), + Some((sql""""position"""", sql"${Segment.paramSegment(unsaved.position)(Setter.optionParamSetter(TypoPoint.setter))}::point")), + Some((sql""""area"""", sql"${Segment.paramSegment(unsaved.area)(Setter.optionParamSetter(TypoPolygon.setter))}::polygon")), + Some((sql""""ip_range"""", sql"${Segment.paramSegment(unsaved.ipRange)(Setter.optionParamSetter(TypoInet.setter))}::inet")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""id"""", sql"${Segment.paramSegment(value: LocationId)(LocationId.setter)}::uuid")) + }, + unsaved.metadata match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""metadata"""", sql"${Segment.paramSegment(value: Option[TypoJsonb])(Setter.optionParamSetter(TypoJsonb.setter))}::jsonb")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."location" default values + returning "id", "name", "position", "area", "ip_range", "metadata" + """ + } else { + val names = fs.map { case (n, _) => n }.mkFragment(SqlFragment(", ")) + val values = fs.map { case (_, f) => f }.mkFragment(SqlFragment(", ")) + sql"""insert into "frontpage"."location"($names) values ($values) returning "id", "name", "position", "area", "ip_range", "metadata"""" + } + q.insertReturning(using LocationRow.jdbcDecoder).map(_.updatedKeys.head) + + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, LocationRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."location"("id", "name", "position", "area", "ip_range", "metadata") FROM STDIN""", batchSize, unsaved)(LocationRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, LocationRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."location"("name", "position", "area", "ip_range", "id", "metadata") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(LocationRowUnsaved.text) + } + override def select: SelectBuilder[LocationFields, LocationRow] = { + SelectBuilderSql(""""frontpage"."location"""", LocationFields.structure, LocationRow.jdbcDecoder) + } + override def selectAll: ZStream[ZConnection, Throwable, LocationRow] = { + sql"""select "id", "name", "position", "area", "ip_range", "metadata" from "frontpage"."location"""".query(using LocationRow.jdbcDecoder).selectStream() + } + override def selectById(id: LocationId): ZIO[ZConnection, Throwable, Option[LocationRow]] = { + sql"""select "id", "name", "position", "area", "ip_range", "metadata" from "frontpage"."location" where "id" = ${Segment.paramSegment(id)(LocationId.setter)}""".query(using LocationRow.jdbcDecoder).selectOne + } + override def selectByIds(ids: Array[LocationId]): ZStream[ZConnection, Throwable, LocationRow] = { + sql"""select "id", "name", "position", "area", "ip_range", "metadata" from "frontpage"."location" where "id" = ANY(${Segment.paramSegment(ids)(LocationId.arraySetter)})""".query(using LocationRow.jdbcDecoder).selectStream() + } + override def selectByIdsTracked(ids: Array[LocationId]): ZIO[ZConnection, Throwable, Map[LocationId, LocationRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[LocationFields, LocationRow] = { + UpdateBuilder(""""frontpage"."location"""", LocationFields.structure, LocationRow.jdbcDecoder) + } + override def update(row: LocationRow): ZIO[ZConnection, Throwable, Boolean] = { + val id = row.id + sql"""update "frontpage"."location" + set "name" = ${Segment.paramSegment(row.name)(Setter.stringSetter)}, + "position" = ${Segment.paramSegment(row.position)(Setter.optionParamSetter(TypoPoint.setter))}::point, + "area" = ${Segment.paramSegment(row.area)(Setter.optionParamSetter(TypoPolygon.setter))}::polygon, + "ip_range" = ${Segment.paramSegment(row.ipRange)(Setter.optionParamSetter(TypoInet.setter))}::inet, + "metadata" = ${Segment.paramSegment(row.metadata)(Setter.optionParamSetter(TypoJsonb.setter))}::jsonb + where "id" = ${Segment.paramSegment(id)(LocationId.setter)}""".update.map(_ > 0) + } + override def upsert(unsaved: LocationRow): ZIO[ZConnection, Throwable, UpdateResult[LocationRow]] = { + sql"""insert into "frontpage"."location"("id", "name", "position", "area", "ip_range", "metadata") + values ( + ${Segment.paramSegment(unsaved.id)(LocationId.setter)}::uuid, + ${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}, + ${Segment.paramSegment(unsaved.position)(Setter.optionParamSetter(TypoPoint.setter))}::point, + ${Segment.paramSegment(unsaved.area)(Setter.optionParamSetter(TypoPolygon.setter))}::polygon, + ${Segment.paramSegment(unsaved.ipRange)(Setter.optionParamSetter(TypoInet.setter))}::inet, + ${Segment.paramSegment(unsaved.metadata)(Setter.optionParamSetter(TypoJsonb.setter))}::jsonb + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "position" = EXCLUDED."position", + "area" = EXCLUDED."area", + "ip_range" = EXCLUDED."ip_range", + "metadata" = EXCLUDED."metadata" + returning "id", "name", "position", "area", "ip_range", "metadata"""".insertReturning(using LocationRow.jdbcDecoder) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, LocationRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + val created = sql"""create temporary table location_TEMP (like "frontpage"."location") on commit drop""".execute + val copied = streamingInsert(s"""copy location_TEMP("id", "name", "position", "area", "ip_range", "metadata") from stdin""", batchSize, unsaved)(LocationRow.text) + val merged = sql"""insert into "frontpage"."location"("id", "name", "position", "area", "ip_range", "metadata") + select * from location_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "position" = EXCLUDED."position", + "area" = EXCLUDED."area", + "ip_range" = EXCLUDED."ip_range", + "metadata" = EXCLUDED."metadata" + ; + drop table location_TEMP;""".update + created *> copied *> merged + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationRepoMock.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationRepoMock.scala new file mode 100644 index 0000000000..195fc10741 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationRepoMock.scala @@ -0,0 +1,116 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams +import zio.Chunk +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +class LocationRepoMock(toRow: Function1[LocationRowUnsaved, LocationRow], + map: scala.collection.mutable.Map[LocationId, LocationRow] = scala.collection.mutable.Map.empty) extends LocationRepo { + override def delete: DeleteBuilder[LocationFields, LocationRow] = { + DeleteBuilderMock(DeleteParams.empty, LocationFields.structure, map) + } + override def deleteById(id: LocationId): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[LocationId]): ZIO[ZConnection, Throwable, Long] = { + ZIO.succeed(ids.map(id => map.remove(id)).count(_.isDefined).toLong) + } + override def insert(unsaved: LocationRow): ZIO[ZConnection, Throwable, LocationRow] = { + ZIO.succeed { + val _ = + if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: LocationRowUnsaved): ZIO[ZConnection, Throwable, LocationRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, LocationRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, LocationRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, unsavedRow) => + ZIO.succeed { + val row = toRow(unsavedRow) + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + override def select: SelectBuilder[LocationFields, LocationRow] = { + SelectBuilderMock(LocationFields.structure, ZIO.succeed(Chunk.fromIterable(map.values)), SelectParams.empty) + } + override def selectAll: ZStream[ZConnection, Throwable, LocationRow] = { + ZStream.fromIterable(map.values) + } + override def selectById(id: LocationId): ZIO[ZConnection, Throwable, Option[LocationRow]] = { + ZIO.succeed(map.get(id)) + } + override def selectByIds(ids: Array[LocationId]): ZStream[ZConnection, Throwable, LocationRow] = { + ZStream.fromIterable(ids.flatMap(map.get)) + } + override def selectByIdsTracked(ids: Array[LocationId]): ZIO[ZConnection, Throwable, Map[LocationId, LocationRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[LocationFields, LocationRow] = { + UpdateBuilderMock(UpdateParams.empty, LocationFields.structure, map) + } + override def update(row: LocationRow): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: LocationRow): ZIO[ZConnection, Throwable, UpdateResult[LocationRow]] = { + ZIO.succeed { + map.put(unsaved.id, unsaved): @nowarn + UpdateResult(1, Chunk.single(unsaved)) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, LocationRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationRow.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationRow.scala new file mode 100644 index 0000000000..99114b6a55 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationRow.scala @@ -0,0 +1,98 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoInet +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoPoint +import adventureworks.customtypes.TypoPolygon +import java.sql.ResultSet +import zio.jdbc.JdbcDecoder +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** Table: frontpage.location + Primary key: id */ +case class LocationRow( + /** Default: gen_random_uuid() */ + id: LocationId, + name: String, + position: Option[TypoPoint], + area: Option[TypoPolygon], + ipRange: Option[TypoInet], + /** Default: '{}'::jsonb */ + metadata: Option[TypoJsonb] +){ + def toUnsavedRow(id: Defaulted[LocationId], metadata: Defaulted[Option[TypoJsonb]] = Defaulted.Provided(this.metadata)): LocationRowUnsaved = + LocationRowUnsaved(name, position, area, ipRange, id, metadata) + } + +object LocationRow { + implicit lazy val jdbcDecoder: JdbcDecoder[LocationRow] = new JdbcDecoder[LocationRow] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, LocationRow) = + columIndex + 5 -> + LocationRow( + id = LocationId.jdbcDecoder.unsafeDecode(columIndex + 0, rs)._2, + name = JdbcDecoder.stringDecoder.unsafeDecode(columIndex + 1, rs)._2, + position = JdbcDecoder.optionDecoder(TypoPoint.jdbcDecoder).unsafeDecode(columIndex + 2, rs)._2, + area = JdbcDecoder.optionDecoder(TypoPolygon.jdbcDecoder).unsafeDecode(columIndex + 3, rs)._2, + ipRange = JdbcDecoder.optionDecoder(TypoInet.jdbcDecoder).unsafeDecode(columIndex + 4, rs)._2, + metadata = JdbcDecoder.optionDecoder(TypoJsonb.jdbcDecoder).unsafeDecode(columIndex + 5, rs)._2 + ) + } + implicit lazy val jsonDecoder: JsonDecoder[LocationRow] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(LocationId.jsonDecoder)) + val name = jsonObj.get("name").toRight("Missing field 'name'").flatMap(_.as(JsonDecoder.string)) + val position = jsonObj.get("position").fold[Either[String, Option[TypoPoint]]](Right(None))(_.as(JsonDecoder.option(using TypoPoint.jsonDecoder))) + val area = jsonObj.get("area").fold[Either[String, Option[TypoPolygon]]](Right(None))(_.as(JsonDecoder.option(using TypoPolygon.jsonDecoder))) + val ipRange = jsonObj.get("ip_range").fold[Either[String, Option[TypoInet]]](Right(None))(_.as(JsonDecoder.option(using TypoInet.jsonDecoder))) + val metadata = jsonObj.get("metadata").fold[Either[String, Option[TypoJsonb]]](Right(None))(_.as(JsonDecoder.option(using TypoJsonb.jsonDecoder))) + if (id.isRight && name.isRight && position.isRight && area.isRight && ipRange.isRight && metadata.isRight) + Right(LocationRow(id = id.toOption.get, name = name.toOption.get, position = position.toOption.get, area = area.toOption.get, ipRange = ipRange.toOption.get, metadata = metadata.toOption.get)) + else Left(List[Either[String, Any]](id, name, position, area, ipRange, metadata).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[LocationRow] = new JsonEncoder[LocationRow] { + override def unsafeEncode(a: LocationRow, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""id":""") + LocationId.jsonEncoder.unsafeEncode(a.id, indent, out) + out.write(",") + out.write(""""name":""") + JsonEncoder.string.unsafeEncode(a.name, indent, out) + out.write(",") + out.write(""""position":""") + JsonEncoder.option(using TypoPoint.jsonEncoder).unsafeEncode(a.position, indent, out) + out.write(",") + out.write(""""area":""") + JsonEncoder.option(using TypoPolygon.jsonEncoder).unsafeEncode(a.area, indent, out) + out.write(",") + out.write(""""ip_range":""") + JsonEncoder.option(using TypoInet.jsonEncoder).unsafeEncode(a.ipRange, indent, out) + out.write(",") + out.write(""""metadata":""") + JsonEncoder.option(using TypoJsonb.jsonEncoder).unsafeEncode(a.metadata, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[LocationRow] = Text.instance[LocationRow]{ (row, sb) => + LocationId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(TypoPoint.text).unsafeEncode(row.position, sb) + sb.append(Text.DELIMETER) + Text.option(TypoPolygon.text).unsafeEncode(row.area, sb) + sb.append(Text.DELIMETER) + Text.option(TypoInet.text).unsafeEncode(row.ipRange, sb) + sb.append(Text.DELIMETER) + Text.option(TypoJsonb.text).unsafeEncode(row.metadata, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationRowUnsaved.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationRowUnsaved.scala new file mode 100644 index 0000000000..121f69381b --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/location/LocationRowUnsaved.scala @@ -0,0 +1,95 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package location + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoInet +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoPoint +import adventureworks.customtypes.TypoPolygon +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** This class corresponds to a row in table `frontpage.location` which has not been persisted yet */ +case class LocationRowUnsaved( + name: String, + position: Option[TypoPoint], + area: Option[TypoPolygon], + ipRange: Option[TypoInet], + /** Default: gen_random_uuid() */ + id: Defaulted[LocationId] = Defaulted.UseDefault, + /** Default: '{}'::jsonb */ + metadata: Defaulted[Option[TypoJsonb]] = Defaulted.UseDefault +) { + def toRow(idDefault: => LocationId, metadataDefault: => Option[TypoJsonb]): LocationRow = + LocationRow( + name = name, + position = position, + area = area, + ipRange = ipRange, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + }, + metadata = metadata match { + case Defaulted.UseDefault => metadataDefault + case Defaulted.Provided(value) => value + } + ) +} +object LocationRowUnsaved { + implicit lazy val jsonDecoder: JsonDecoder[LocationRowUnsaved] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val name = jsonObj.get("name").toRight("Missing field 'name'").flatMap(_.as(JsonDecoder.string)) + val position = jsonObj.get("position").fold[Either[String, Option[TypoPoint]]](Right(None))(_.as(JsonDecoder.option(using TypoPoint.jsonDecoder))) + val area = jsonObj.get("area").fold[Either[String, Option[TypoPolygon]]](Right(None))(_.as(JsonDecoder.option(using TypoPolygon.jsonDecoder))) + val ipRange = jsonObj.get("ip_range").fold[Either[String, Option[TypoInet]]](Right(None))(_.as(JsonDecoder.option(using TypoInet.jsonDecoder))) + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(Defaulted.jsonDecoder(LocationId.jsonDecoder))) + val metadata = jsonObj.get("metadata").toRight("Missing field 'metadata'").flatMap(_.as(Defaulted.jsonDecoder(JsonDecoder.option(using TypoJsonb.jsonDecoder)))) + if (name.isRight && position.isRight && area.isRight && ipRange.isRight && id.isRight && metadata.isRight) + Right(LocationRowUnsaved(name = name.toOption.get, position = position.toOption.get, area = area.toOption.get, ipRange = ipRange.toOption.get, id = id.toOption.get, metadata = metadata.toOption.get)) + else Left(List[Either[String, Any]](name, position, area, ipRange, id, metadata).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[LocationRowUnsaved] = new JsonEncoder[LocationRowUnsaved] { + override def unsafeEncode(a: LocationRowUnsaved, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""name":""") + JsonEncoder.string.unsafeEncode(a.name, indent, out) + out.write(",") + out.write(""""position":""") + JsonEncoder.option(using TypoPoint.jsonEncoder).unsafeEncode(a.position, indent, out) + out.write(",") + out.write(""""area":""") + JsonEncoder.option(using TypoPolygon.jsonEncoder).unsafeEncode(a.area, indent, out) + out.write(",") + out.write(""""ip_range":""") + JsonEncoder.option(using TypoInet.jsonEncoder).unsafeEncode(a.ipRange, indent, out) + out.write(",") + out.write(""""id":""") + Defaulted.jsonEncoder(LocationId.jsonEncoder).unsafeEncode(a.id, indent, out) + out.write(",") + out.write(""""metadata":""") + Defaulted.jsonEncoder(JsonEncoder.option(using TypoJsonb.jsonEncoder)).unsafeEncode(a.metadata, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[LocationRowUnsaved] = Text.instance[LocationRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(TypoPoint.text).unsafeEncode(row.position, sb) + sb.append(Text.DELIMETER) + Text.option(TypoPolygon.text).unsafeEncode(row.area, sb) + sb.append(Text.DELIMETER) + Text.option(TypoInet.text).unsafeEncode(row.ipRange, sb) + sb.append(Text.DELIMETER) + Defaulted.text(LocationId.text).unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(TypoJsonb.text)).unsafeEncode(row.metadata, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderFields.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderFields.scala new file mode 100644 index 0000000000..ba6d63651e --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderFields.scala @@ -0,0 +1,65 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.product.ProductFields +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.product.ProductRow +import adventureworks.frontpage.user.UserFields +import adventureworks.frontpage.user.UserId +import adventureworks.frontpage.user.UserRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait OrderFields { + def id: IdField[OrderId, OrderRow] + def userId: OptField[UserId, OrderRow] + def productId: OptField[ProductId, OrderRow] + def status: OptField[OrderStatus, OrderRow] + def total: Field[BigDecimal, OrderRow] + def createdAt: OptField[TypoLocalDateTime, OrderRow] + def shippedAt: OptField[TypoLocalDateTime, OrderRow] + def fkProduct: ForeignKey[ProductFields, ProductRow] = + ForeignKey[ProductFields, ProductRow]("frontpage.order_product_id_fkey", Nil) + .withColumnPair(productId, _.id) + def fkUser: ForeignKey[UserFields, UserRow] = + ForeignKey[UserFields, UserRow]("frontpage.order_user_id_fkey", Nil) + .withColumnPair(userId, _.id) +} + +object OrderFields { + lazy val structure: Relation[OrderFields, OrderRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[OrderFields, OrderRow] { + + override lazy val fields: OrderFields = new OrderFields { + override def id = IdField[OrderId, OrderRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def userId = OptField[UserId, OrderRow](_path, "user_id", None, Some("uuid"), x => x.userId, (row, value) => row.copy(userId = value)) + override def productId = OptField[ProductId, OrderRow](_path, "product_id", None, Some("uuid"), x => x.productId, (row, value) => row.copy(productId = value)) + override def status = OptField[OrderStatus, OrderRow](_path, "status", None, Some("frontpage.order_status"), x => x.status, (row, value) => row.copy(status = value)) + override def total = Field[BigDecimal, OrderRow](_path, "total", None, Some("numeric"), x => x.total, (row, value) => row.copy(total = value)) + override def createdAt = OptField[TypoLocalDateTime, OrderRow](_path, "created_at", Some("text"), Some("timestamp"), x => x.createdAt, (row, value) => row.copy(createdAt = value)) + override def shippedAt = OptField[TypoLocalDateTime, OrderRow](_path, "shipped_at", Some("text"), Some("timestamp"), x => x.shippedAt, (row, value) => row.copy(shippedAt = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, OrderRow]] = + List[FieldLikeNoHkt[?, OrderRow]](fields.id, fields.userId, fields.productId, fields.status, fields.total, fields.createdAt, fields.shippedAt) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderId.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderId.scala new file mode 100644 index 0000000000..0ceca7ee94 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import adventureworks.customtypes.TypoUUID +import typo.dsl.Bijection +import typo.dsl.PGType +import zio.jdbc.JdbcDecoder +import zio.jdbc.JdbcEncoder +import zio.jdbc.SqlFragment.Setter +import zio.json.JsonDecoder +import zio.json.JsonEncoder + +/** Type for the primary key of table `frontpage.order` */ +case class OrderId(value: TypoUUID) extends AnyVal +object OrderId { + implicit lazy val arrayJdbcDecoder: JdbcDecoder[Array[OrderId]] = JdbcDecoder[Array[TypoUUID]].map(_.map(OrderId.apply)) + implicit lazy val arrayJdbcEncoder: JdbcEncoder[Array[OrderId]] = JdbcEncoder[Array[TypoUUID]].contramap(_.map(_.value)) + implicit lazy val arraySetter: Setter[Array[OrderId]] = TypoUUID.arraySetter.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[OrderId, TypoUUID] = Bijection[OrderId, TypoUUID](_.value)(OrderId.apply) + implicit lazy val jdbcDecoder: JdbcDecoder[OrderId] = TypoUUID.jdbcDecoder.map(OrderId.apply) + implicit lazy val jdbcEncoder: JdbcEncoder[OrderId] = TypoUUID.jdbcEncoder.contramap(_.value) + implicit lazy val jsonDecoder: JsonDecoder[OrderId] = TypoUUID.jsonDecoder.map(OrderId.apply) + implicit lazy val jsonEncoder: JsonEncoder[OrderId] = TypoUUID.jsonEncoder.contramap(_.value) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[OrderId] = Ordering.by(_.value) + implicit lazy val pgType: PGType[OrderId] = TypoUUID.pgType.as + implicit lazy val setter: Setter[OrderId] = TypoUUID.setter.contramap(_.value) + implicit lazy val text: Text[OrderId] = new Text[OrderId] { + override def unsafeEncode(v: OrderId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: OrderId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderRepo.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderRepo.scala new file mode 100644 index 0000000000..23bdced560 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderRepo.scala @@ -0,0 +1,38 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +trait OrderRepo { + def delete: DeleteBuilder[OrderFields, OrderRow] + def deleteById(id: OrderId): ZIO[ZConnection, Throwable, Boolean] + def deleteByIds(ids: Array[OrderId]): ZIO[ZConnection, Throwable, Long] + def insert(unsaved: OrderRow): ZIO[ZConnection, Throwable, OrderRow] + def insert(unsaved: OrderRowUnsaved): ZIO[ZConnection, Throwable, OrderRow] + def insertStreaming(unsaved: ZStream[ZConnection, Throwable, OrderRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, OrderRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + def select: SelectBuilder[OrderFields, OrderRow] + def selectAll: ZStream[ZConnection, Throwable, OrderRow] + def selectById(id: OrderId): ZIO[ZConnection, Throwable, Option[OrderRow]] + def selectByIds(ids: Array[OrderId]): ZStream[ZConnection, Throwable, OrderRow] + def selectByIdsTracked(ids: Array[OrderId]): ZIO[ZConnection, Throwable, Map[OrderId, OrderRow]] + def update: UpdateBuilder[OrderFields, OrderRow] + def update(row: OrderRow): ZIO[ZConnection, Throwable, Boolean] + def upsert(unsaved: OrderRow): ZIO[ZConnection, Throwable, UpdateResult[OrderRow]] + // Not implementable for zio-jdbc: upsertBatch + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, OrderRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderRepoImpl.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderRepoImpl.scala new file mode 100644 index 0000000000..6bd829f1bf --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderRepoImpl.scala @@ -0,0 +1,153 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.user.UserId +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.SqlFragment +import zio.jdbc.SqlFragment.Segment +import zio.jdbc.SqlFragment.Setter +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.jdbc.sqlInterpolator +import zio.stream.ZStream + +class OrderRepoImpl extends OrderRepo { + override def delete: DeleteBuilder[OrderFields, OrderRow] = { + DeleteBuilder(""""frontpage"."order"""", OrderFields.structure) + } + override def deleteById(id: OrderId): ZIO[ZConnection, Throwable, Boolean] = { + sql"""delete from "frontpage"."order" where "id" = ${Segment.paramSegment(id)(OrderId.setter)}""".delete.map(_ > 0) + } + override def deleteByIds(ids: Array[OrderId]): ZIO[ZConnection, Throwable, Long] = { + sql"""delete from "frontpage"."order" where "id" = ANY(${ids})""".delete + } + override def insert(unsaved: OrderRow): ZIO[ZConnection, Throwable, OrderRow] = { + sql"""insert into "frontpage"."order"("id", "user_id", "product_id", "status", "total", "created_at", "shipped_at") + values (${Segment.paramSegment(unsaved.id)(OrderId.setter)}::uuid, ${Segment.paramSegment(unsaved.userId)(Setter.optionParamSetter(UserId.setter))}::uuid, ${Segment.paramSegment(unsaved.productId)(Setter.optionParamSetter(ProductId.setter))}::uuid, ${Segment.paramSegment(unsaved.status)(Setter.optionParamSetter(OrderStatus.setter))}::frontpage.order_status, ${Segment.paramSegment(unsaved.total)(Setter.bigDecimalScalaSetter)}::numeric, ${Segment.paramSegment(unsaved.createdAt)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp, ${Segment.paramSegment(unsaved.shippedAt)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp) + returning "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text + """.insertReturning(using OrderRow.jdbcDecoder).map(_.updatedKeys.head) + } + override def insert(unsaved: OrderRowUnsaved): ZIO[ZConnection, Throwable, OrderRow] = { + val fs = List( + Some((sql""""user_id"""", sql"${Segment.paramSegment(unsaved.userId)(Setter.optionParamSetter(UserId.setter))}::uuid")), + Some((sql""""product_id"""", sql"${Segment.paramSegment(unsaved.productId)(Setter.optionParamSetter(ProductId.setter))}::uuid")), + Some((sql""""total"""", sql"${Segment.paramSegment(unsaved.total)(Setter.bigDecimalScalaSetter)}::numeric")), + Some((sql""""shipped_at"""", sql"${Segment.paramSegment(unsaved.shippedAt)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""id"""", sql"${Segment.paramSegment(value: OrderId)(OrderId.setter)}::uuid")) + }, + unsaved.status match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""status"""", sql"${Segment.paramSegment(value: Option[OrderStatus])(Setter.optionParamSetter(OrderStatus.setter))}::frontpage.order_status")) + }, + unsaved.createdAt match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""created_at"""", sql"${Segment.paramSegment(value: Option[TypoLocalDateTime])(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."order" default values + returning "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text + """ + } else { + val names = fs.map { case (n, _) => n }.mkFragment(SqlFragment(", ")) + val values = fs.map { case (_, f) => f }.mkFragment(SqlFragment(", ")) + sql"""insert into "frontpage"."order"($names) values ($values) returning "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text""" + } + q.insertReturning(using OrderRow.jdbcDecoder).map(_.updatedKeys.head) + + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, OrderRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."order"("id", "user_id", "product_id", "status", "total", "created_at", "shipped_at") FROM STDIN""", batchSize, unsaved)(OrderRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, OrderRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."order"("user_id", "product_id", "total", "shipped_at", "id", "status", "created_at") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(OrderRowUnsaved.text) + } + override def select: SelectBuilder[OrderFields, OrderRow] = { + SelectBuilderSql(""""frontpage"."order"""", OrderFields.structure, OrderRow.jdbcDecoder) + } + override def selectAll: ZStream[ZConnection, Throwable, OrderRow] = { + sql"""select "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text from "frontpage"."order"""".query(using OrderRow.jdbcDecoder).selectStream() + } + override def selectById(id: OrderId): ZIO[ZConnection, Throwable, Option[OrderRow]] = { + sql"""select "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text from "frontpage"."order" where "id" = ${Segment.paramSegment(id)(OrderId.setter)}""".query(using OrderRow.jdbcDecoder).selectOne + } + override def selectByIds(ids: Array[OrderId]): ZStream[ZConnection, Throwable, OrderRow] = { + sql"""select "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text from "frontpage"."order" where "id" = ANY(${Segment.paramSegment(ids)(OrderId.arraySetter)})""".query(using OrderRow.jdbcDecoder).selectStream() + } + override def selectByIdsTracked(ids: Array[OrderId]): ZIO[ZConnection, Throwable, Map[OrderId, OrderRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[OrderFields, OrderRow] = { + UpdateBuilder(""""frontpage"."order"""", OrderFields.structure, OrderRow.jdbcDecoder) + } + override def update(row: OrderRow): ZIO[ZConnection, Throwable, Boolean] = { + val id = row.id + sql"""update "frontpage"."order" + set "user_id" = ${Segment.paramSegment(row.userId)(Setter.optionParamSetter(UserId.setter))}::uuid, + "product_id" = ${Segment.paramSegment(row.productId)(Setter.optionParamSetter(ProductId.setter))}::uuid, + "status" = ${Segment.paramSegment(row.status)(Setter.optionParamSetter(OrderStatus.setter))}::frontpage.order_status, + "total" = ${Segment.paramSegment(row.total)(Setter.bigDecimalScalaSetter)}::numeric, + "created_at" = ${Segment.paramSegment(row.createdAt)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp, + "shipped_at" = ${Segment.paramSegment(row.shippedAt)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp + where "id" = ${Segment.paramSegment(id)(OrderId.setter)}""".update.map(_ > 0) + } + override def upsert(unsaved: OrderRow): ZIO[ZConnection, Throwable, UpdateResult[OrderRow]] = { + sql"""insert into "frontpage"."order"("id", "user_id", "product_id", "status", "total", "created_at", "shipped_at") + values ( + ${Segment.paramSegment(unsaved.id)(OrderId.setter)}::uuid, + ${Segment.paramSegment(unsaved.userId)(Setter.optionParamSetter(UserId.setter))}::uuid, + ${Segment.paramSegment(unsaved.productId)(Setter.optionParamSetter(ProductId.setter))}::uuid, + ${Segment.paramSegment(unsaved.status)(Setter.optionParamSetter(OrderStatus.setter))}::frontpage.order_status, + ${Segment.paramSegment(unsaved.total)(Setter.bigDecimalScalaSetter)}::numeric, + ${Segment.paramSegment(unsaved.createdAt)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp, + ${Segment.paramSegment(unsaved.shippedAt)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp + ) + on conflict ("id") + do update set + "user_id" = EXCLUDED."user_id", + "product_id" = EXCLUDED."product_id", + "status" = EXCLUDED."status", + "total" = EXCLUDED."total", + "created_at" = EXCLUDED."created_at", + "shipped_at" = EXCLUDED."shipped_at" + returning "id", "user_id", "product_id", "status", "total", "created_at"::text, "shipped_at"::text""".insertReturning(using OrderRow.jdbcDecoder) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, OrderRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + val created = sql"""create temporary table order_TEMP (like "frontpage"."order") on commit drop""".execute + val copied = streamingInsert(s"""copy order_TEMP("id", "user_id", "product_id", "status", "total", "created_at", "shipped_at") from stdin""", batchSize, unsaved)(OrderRow.text) + val merged = sql"""insert into "frontpage"."order"("id", "user_id", "product_id", "status", "total", "created_at", "shipped_at") + select * from order_TEMP + on conflict ("id") + do update set + "user_id" = EXCLUDED."user_id", + "product_id" = EXCLUDED."product_id", + "status" = EXCLUDED."status", + "total" = EXCLUDED."total", + "created_at" = EXCLUDED."created_at", + "shipped_at" = EXCLUDED."shipped_at" + ; + drop table order_TEMP;""".update + created *> copied *> merged + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderRepoMock.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderRepoMock.scala new file mode 100644 index 0000000000..bf4e719f84 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderRepoMock.scala @@ -0,0 +1,116 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams +import zio.Chunk +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +class OrderRepoMock(toRow: Function1[OrderRowUnsaved, OrderRow], + map: scala.collection.mutable.Map[OrderId, OrderRow] = scala.collection.mutable.Map.empty) extends OrderRepo { + override def delete: DeleteBuilder[OrderFields, OrderRow] = { + DeleteBuilderMock(DeleteParams.empty, OrderFields.structure, map) + } + override def deleteById(id: OrderId): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[OrderId]): ZIO[ZConnection, Throwable, Long] = { + ZIO.succeed(ids.map(id => map.remove(id)).count(_.isDefined).toLong) + } + override def insert(unsaved: OrderRow): ZIO[ZConnection, Throwable, OrderRow] = { + ZIO.succeed { + val _ = + if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: OrderRowUnsaved): ZIO[ZConnection, Throwable, OrderRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, OrderRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, OrderRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, unsavedRow) => + ZIO.succeed { + val row = toRow(unsavedRow) + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + override def select: SelectBuilder[OrderFields, OrderRow] = { + SelectBuilderMock(OrderFields.structure, ZIO.succeed(Chunk.fromIterable(map.values)), SelectParams.empty) + } + override def selectAll: ZStream[ZConnection, Throwable, OrderRow] = { + ZStream.fromIterable(map.values) + } + override def selectById(id: OrderId): ZIO[ZConnection, Throwable, Option[OrderRow]] = { + ZIO.succeed(map.get(id)) + } + override def selectByIds(ids: Array[OrderId]): ZStream[ZConnection, Throwable, OrderRow] = { + ZStream.fromIterable(ids.flatMap(map.get)) + } + override def selectByIdsTracked(ids: Array[OrderId]): ZIO[ZConnection, Throwable, Map[OrderId, OrderRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[OrderFields, OrderRow] = { + UpdateBuilderMock(UpdateParams.empty, OrderFields.structure, map) + } + override def update(row: OrderRow): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: OrderRow): ZIO[ZConnection, Throwable, UpdateResult[OrderRow]] = { + ZIO.succeed { + map.put(unsaved.id, unsaved): @nowarn + UpdateResult(1, Chunk.single(unsaved)) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, OrderRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderRow.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderRow.scala new file mode 100644 index 0000000000..fe26b99e3a --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderRow.scala @@ -0,0 +1,108 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.user.UserId +import java.sql.ResultSet +import zio.jdbc.JdbcDecoder +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** Table: frontpage.order + Primary key: id */ +case class OrderRow( + /** Default: gen_random_uuid() */ + id: OrderId, + /** Points to [[user.UserRow.id]] */ + userId: Option[UserId], + /** Points to [[product.ProductRow.id]] */ + productId: Option[ProductId], + /** Default: 'pending'::frontpage.order_status */ + status: Option[OrderStatus], + total: BigDecimal, + /** Default: now() */ + createdAt: Option[TypoLocalDateTime], + shippedAt: Option[TypoLocalDateTime] +){ + def toUnsavedRow(id: Defaulted[OrderId], status: Defaulted[Option[OrderStatus]] = Defaulted.Provided(this.status), createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.Provided(this.createdAt)): OrderRowUnsaved = + OrderRowUnsaved(userId, productId, total, shippedAt, id, status, createdAt) + } + +object OrderRow { + implicit lazy val jdbcDecoder: JdbcDecoder[OrderRow] = new JdbcDecoder[OrderRow] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, OrderRow) = + columIndex + 6 -> + OrderRow( + id = OrderId.jdbcDecoder.unsafeDecode(columIndex + 0, rs)._2, + userId = JdbcDecoder.optionDecoder(UserId.jdbcDecoder).unsafeDecode(columIndex + 1, rs)._2, + productId = JdbcDecoder.optionDecoder(ProductId.jdbcDecoder).unsafeDecode(columIndex + 2, rs)._2, + status = JdbcDecoder.optionDecoder(OrderStatus.jdbcDecoder).unsafeDecode(columIndex + 3, rs)._2, + total = JdbcDecoder.bigDecimalDecoderScala.unsafeDecode(columIndex + 4, rs)._2, + createdAt = JdbcDecoder.optionDecoder(TypoLocalDateTime.jdbcDecoder).unsafeDecode(columIndex + 5, rs)._2, + shippedAt = JdbcDecoder.optionDecoder(TypoLocalDateTime.jdbcDecoder).unsafeDecode(columIndex + 6, rs)._2 + ) + } + implicit lazy val jsonDecoder: JsonDecoder[OrderRow] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(OrderId.jsonDecoder)) + val userId = jsonObj.get("user_id").fold[Either[String, Option[UserId]]](Right(None))(_.as(JsonDecoder.option(using UserId.jsonDecoder))) + val productId = jsonObj.get("product_id").fold[Either[String, Option[ProductId]]](Right(None))(_.as(JsonDecoder.option(using ProductId.jsonDecoder))) + val status = jsonObj.get("status").fold[Either[String, Option[OrderStatus]]](Right(None))(_.as(JsonDecoder.option(using OrderStatus.jsonDecoder))) + val total = jsonObj.get("total").toRight("Missing field 'total'").flatMap(_.as(JsonDecoder.scalaBigDecimal)) + val createdAt = jsonObj.get("created_at").fold[Either[String, Option[TypoLocalDateTime]]](Right(None))(_.as(JsonDecoder.option(using TypoLocalDateTime.jsonDecoder))) + val shippedAt = jsonObj.get("shipped_at").fold[Either[String, Option[TypoLocalDateTime]]](Right(None))(_.as(JsonDecoder.option(using TypoLocalDateTime.jsonDecoder))) + if (id.isRight && userId.isRight && productId.isRight && status.isRight && total.isRight && createdAt.isRight && shippedAt.isRight) + Right(OrderRow(id = id.toOption.get, userId = userId.toOption.get, productId = productId.toOption.get, status = status.toOption.get, total = total.toOption.get, createdAt = createdAt.toOption.get, shippedAt = shippedAt.toOption.get)) + else Left(List[Either[String, Any]](id, userId, productId, status, total, createdAt, shippedAt).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[OrderRow] = new JsonEncoder[OrderRow] { + override def unsafeEncode(a: OrderRow, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""id":""") + OrderId.jsonEncoder.unsafeEncode(a.id, indent, out) + out.write(",") + out.write(""""user_id":""") + JsonEncoder.option(using UserId.jsonEncoder).unsafeEncode(a.userId, indent, out) + out.write(",") + out.write(""""product_id":""") + JsonEncoder.option(using ProductId.jsonEncoder).unsafeEncode(a.productId, indent, out) + out.write(",") + out.write(""""status":""") + JsonEncoder.option(using OrderStatus.jsonEncoder).unsafeEncode(a.status, indent, out) + out.write(",") + out.write(""""total":""") + JsonEncoder.scalaBigDecimal.unsafeEncode(a.total, indent, out) + out.write(",") + out.write(""""created_at":""") + JsonEncoder.option(using TypoLocalDateTime.jsonEncoder).unsafeEncode(a.createdAt, indent, out) + out.write(",") + out.write(""""shipped_at":""") + JsonEncoder.option(using TypoLocalDateTime.jsonEncoder).unsafeEncode(a.shippedAt, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[OrderRow] = Text.instance[OrderRow]{ (row, sb) => + OrderId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.option(UserId.text).unsafeEncode(row.userId, sb) + sb.append(Text.DELIMETER) + Text.option(ProductId.text).unsafeEncode(row.productId, sb) + sb.append(Text.DELIMETER) + Text.option(OrderStatus.text).unsafeEncode(row.status, sb) + sb.append(Text.DELIMETER) + Text.bigDecimalInstance.unsafeEncode(row.total, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.createdAt, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.shippedAt, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderRowUnsaved.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderRowUnsaved.scala new file mode 100644 index 0000000000..ed85affadb --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order/OrderRowUnsaved.scala @@ -0,0 +1,108 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.user.UserId +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** This class corresponds to a row in table `frontpage.order` which has not been persisted yet */ +case class OrderRowUnsaved( + /** Points to [[user.UserRow.id]] */ + userId: Option[UserId], + /** Points to [[product.ProductRow.id]] */ + productId: Option[ProductId], + total: BigDecimal, + shippedAt: Option[TypoLocalDateTime], + /** Default: gen_random_uuid() */ + id: Defaulted[OrderId] = Defaulted.UseDefault, + /** Default: 'pending'::frontpage.order_status */ + status: Defaulted[Option[OrderStatus]] = Defaulted.UseDefault, + /** Default: now() */ + createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault +) { + def toRow(idDefault: => OrderId, statusDefault: => Option[OrderStatus], createdAtDefault: => Option[TypoLocalDateTime]): OrderRow = + OrderRow( + userId = userId, + productId = productId, + total = total, + shippedAt = shippedAt, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + }, + status = status match { + case Defaulted.UseDefault => statusDefault + case Defaulted.Provided(value) => value + }, + createdAt = createdAt match { + case Defaulted.UseDefault => createdAtDefault + case Defaulted.Provided(value) => value + } + ) +} +object OrderRowUnsaved { + implicit lazy val jsonDecoder: JsonDecoder[OrderRowUnsaved] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val userId = jsonObj.get("user_id").fold[Either[String, Option[UserId]]](Right(None))(_.as(JsonDecoder.option(using UserId.jsonDecoder))) + val productId = jsonObj.get("product_id").fold[Either[String, Option[ProductId]]](Right(None))(_.as(JsonDecoder.option(using ProductId.jsonDecoder))) + val total = jsonObj.get("total").toRight("Missing field 'total'").flatMap(_.as(JsonDecoder.scalaBigDecimal)) + val shippedAt = jsonObj.get("shipped_at").fold[Either[String, Option[TypoLocalDateTime]]](Right(None))(_.as(JsonDecoder.option(using TypoLocalDateTime.jsonDecoder))) + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(Defaulted.jsonDecoder(OrderId.jsonDecoder))) + val status = jsonObj.get("status").toRight("Missing field 'status'").flatMap(_.as(Defaulted.jsonDecoder(JsonDecoder.option(using OrderStatus.jsonDecoder)))) + val createdAt = jsonObj.get("created_at").toRight("Missing field 'created_at'").flatMap(_.as(Defaulted.jsonDecoder(JsonDecoder.option(using TypoLocalDateTime.jsonDecoder)))) + if (userId.isRight && productId.isRight && total.isRight && shippedAt.isRight && id.isRight && status.isRight && createdAt.isRight) + Right(OrderRowUnsaved(userId = userId.toOption.get, productId = productId.toOption.get, total = total.toOption.get, shippedAt = shippedAt.toOption.get, id = id.toOption.get, status = status.toOption.get, createdAt = createdAt.toOption.get)) + else Left(List[Either[String, Any]](userId, productId, total, shippedAt, id, status, createdAt).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[OrderRowUnsaved] = new JsonEncoder[OrderRowUnsaved] { + override def unsafeEncode(a: OrderRowUnsaved, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""user_id":""") + JsonEncoder.option(using UserId.jsonEncoder).unsafeEncode(a.userId, indent, out) + out.write(",") + out.write(""""product_id":""") + JsonEncoder.option(using ProductId.jsonEncoder).unsafeEncode(a.productId, indent, out) + out.write(",") + out.write(""""total":""") + JsonEncoder.scalaBigDecimal.unsafeEncode(a.total, indent, out) + out.write(",") + out.write(""""shipped_at":""") + JsonEncoder.option(using TypoLocalDateTime.jsonEncoder).unsafeEncode(a.shippedAt, indent, out) + out.write(",") + out.write(""""id":""") + Defaulted.jsonEncoder(OrderId.jsonEncoder).unsafeEncode(a.id, indent, out) + out.write(",") + out.write(""""status":""") + Defaulted.jsonEncoder(JsonEncoder.option(using OrderStatus.jsonEncoder)).unsafeEncode(a.status, indent, out) + out.write(",") + out.write(""""created_at":""") + Defaulted.jsonEncoder(JsonEncoder.option(using TypoLocalDateTime.jsonEncoder)).unsafeEncode(a.createdAt, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[OrderRowUnsaved] = Text.instance[OrderRowUnsaved]{ (row, sb) => + Text.option(UserId.text).unsafeEncode(row.userId, sb) + sb.append(Text.DELIMETER) + Text.option(ProductId.text).unsafeEncode(row.productId, sb) + sb.append(Text.DELIMETER) + Text.bigDecimalInstance.unsafeEncode(row.total, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.shippedAt, sb) + sb.append(Text.DELIMETER) + Defaulted.text(OrderId.text).unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(OrderStatus.text)).unsafeEncode(row.status, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(TypoLocalDateTime.text)).unsafeEncode(row.createdAt, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemFields.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemFields.scala new file mode 100644 index 0000000000..d86c231793 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemFields.scala @@ -0,0 +1,63 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.order.OrderFields +import adventureworks.frontpage.order.OrderId +import adventureworks.frontpage.order.OrderRow +import adventureworks.frontpage.product.ProductFields +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.product.ProductRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait OrderItemFields { + def id: IdField[OrderItemId, OrderItemRow] + def orderId: OptField[OrderId, OrderItemRow] + def productId: OptField[ProductId, OrderItemRow] + def quantity: Field[Int, OrderItemRow] + def price: Field[BigDecimal, OrderItemRow] + def shippedAt: OptField[TypoLocalDateTime, OrderItemRow] + def fkOrder: ForeignKey[OrderFields, OrderRow] = + ForeignKey[OrderFields, OrderRow]("frontpage.order_item_order_id_fkey", Nil) + .withColumnPair(orderId, _.id) + def fkProduct: ForeignKey[ProductFields, ProductRow] = + ForeignKey[ProductFields, ProductRow]("frontpage.order_item_product_id_fkey", Nil) + .withColumnPair(productId, _.id) +} + +object OrderItemFields { + lazy val structure: Relation[OrderItemFields, OrderItemRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[OrderItemFields, OrderItemRow] { + + override lazy val fields: OrderItemFields = new OrderItemFields { + override def id = IdField[OrderItemId, OrderItemRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def orderId = OptField[OrderId, OrderItemRow](_path, "order_id", None, Some("uuid"), x => x.orderId, (row, value) => row.copy(orderId = value)) + override def productId = OptField[ProductId, OrderItemRow](_path, "product_id", None, Some("uuid"), x => x.productId, (row, value) => row.copy(productId = value)) + override def quantity = Field[Int, OrderItemRow](_path, "quantity", None, Some("int4"), x => x.quantity, (row, value) => row.copy(quantity = value)) + override def price = Field[BigDecimal, OrderItemRow](_path, "price", None, Some("numeric"), x => x.price, (row, value) => row.copy(price = value)) + override def shippedAt = OptField[TypoLocalDateTime, OrderItemRow](_path, "shipped_at", Some("text"), Some("timestamp"), x => x.shippedAt, (row, value) => row.copy(shippedAt = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, OrderItemRow]] = + List[FieldLikeNoHkt[?, OrderItemRow]](fields.id, fields.orderId, fields.productId, fields.quantity, fields.price, fields.shippedAt) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemId.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemId.scala new file mode 100644 index 0000000000..d3ef622369 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import adventureworks.customtypes.TypoUUID +import typo.dsl.Bijection +import typo.dsl.PGType +import zio.jdbc.JdbcDecoder +import zio.jdbc.JdbcEncoder +import zio.jdbc.SqlFragment.Setter +import zio.json.JsonDecoder +import zio.json.JsonEncoder + +/** Type for the primary key of table `frontpage.order_item` */ +case class OrderItemId(value: TypoUUID) extends AnyVal +object OrderItemId { + implicit lazy val arrayJdbcDecoder: JdbcDecoder[Array[OrderItemId]] = JdbcDecoder[Array[TypoUUID]].map(_.map(OrderItemId.apply)) + implicit lazy val arrayJdbcEncoder: JdbcEncoder[Array[OrderItemId]] = JdbcEncoder[Array[TypoUUID]].contramap(_.map(_.value)) + implicit lazy val arraySetter: Setter[Array[OrderItemId]] = TypoUUID.arraySetter.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[OrderItemId, TypoUUID] = Bijection[OrderItemId, TypoUUID](_.value)(OrderItemId.apply) + implicit lazy val jdbcDecoder: JdbcDecoder[OrderItemId] = TypoUUID.jdbcDecoder.map(OrderItemId.apply) + implicit lazy val jdbcEncoder: JdbcEncoder[OrderItemId] = TypoUUID.jdbcEncoder.contramap(_.value) + implicit lazy val jsonDecoder: JsonDecoder[OrderItemId] = TypoUUID.jsonDecoder.map(OrderItemId.apply) + implicit lazy val jsonEncoder: JsonEncoder[OrderItemId] = TypoUUID.jsonEncoder.contramap(_.value) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[OrderItemId] = Ordering.by(_.value) + implicit lazy val pgType: PGType[OrderItemId] = TypoUUID.pgType.as + implicit lazy val setter: Setter[OrderItemId] = TypoUUID.setter.contramap(_.value) + implicit lazy val text: Text[OrderItemId] = new Text[OrderItemId] { + override def unsafeEncode(v: OrderItemId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: OrderItemId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepo.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepo.scala new file mode 100644 index 0000000000..9893bb3d2e --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepo.scala @@ -0,0 +1,38 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +trait OrderItemRepo { + def delete: DeleteBuilder[OrderItemFields, OrderItemRow] + def deleteById(id: OrderItemId): ZIO[ZConnection, Throwable, Boolean] + def deleteByIds(ids: Array[OrderItemId]): ZIO[ZConnection, Throwable, Long] + def insert(unsaved: OrderItemRow): ZIO[ZConnection, Throwable, OrderItemRow] + def insert(unsaved: OrderItemRowUnsaved): ZIO[ZConnection, Throwable, OrderItemRow] + def insertStreaming(unsaved: ZStream[ZConnection, Throwable, OrderItemRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, OrderItemRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + def select: SelectBuilder[OrderItemFields, OrderItemRow] + def selectAll: ZStream[ZConnection, Throwable, OrderItemRow] + def selectById(id: OrderItemId): ZIO[ZConnection, Throwable, Option[OrderItemRow]] + def selectByIds(ids: Array[OrderItemId]): ZStream[ZConnection, Throwable, OrderItemRow] + def selectByIdsTracked(ids: Array[OrderItemId]): ZIO[ZConnection, Throwable, Map[OrderItemId, OrderItemRow]] + def update: UpdateBuilder[OrderItemFields, OrderItemRow] + def update(row: OrderItemRow): ZIO[ZConnection, Throwable, Boolean] + def upsert(unsaved: OrderItemRow): ZIO[ZConnection, Throwable, UpdateResult[OrderItemRow]] + // Not implementable for zio-jdbc: upsertBatch + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, OrderItemRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepoImpl.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepoImpl.scala new file mode 100644 index 0000000000..0b87e99b61 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepoImpl.scala @@ -0,0 +1,142 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.order.OrderId +import adventureworks.frontpage.product.ProductId +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.SqlFragment +import zio.jdbc.SqlFragment.Segment +import zio.jdbc.SqlFragment.Setter +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.jdbc.sqlInterpolator +import zio.stream.ZStream + +class OrderItemRepoImpl extends OrderItemRepo { + override def delete: DeleteBuilder[OrderItemFields, OrderItemRow] = { + DeleteBuilder(""""frontpage"."order_item"""", OrderItemFields.structure) + } + override def deleteById(id: OrderItemId): ZIO[ZConnection, Throwable, Boolean] = { + sql"""delete from "frontpage"."order_item" where "id" = ${Segment.paramSegment(id)(OrderItemId.setter)}""".delete.map(_ > 0) + } + override def deleteByIds(ids: Array[OrderItemId]): ZIO[ZConnection, Throwable, Long] = { + sql"""delete from "frontpage"."order_item" where "id" = ANY(${ids})""".delete + } + override def insert(unsaved: OrderItemRow): ZIO[ZConnection, Throwable, OrderItemRow] = { + sql"""insert into "frontpage"."order_item"("id", "order_id", "product_id", "quantity", "price", "shipped_at") + values (${Segment.paramSegment(unsaved.id)(OrderItemId.setter)}::uuid, ${Segment.paramSegment(unsaved.orderId)(Setter.optionParamSetter(OrderId.setter))}::uuid, ${Segment.paramSegment(unsaved.productId)(Setter.optionParamSetter(ProductId.setter))}::uuid, ${Segment.paramSegment(unsaved.quantity)(Setter.intSetter)}::int4, ${Segment.paramSegment(unsaved.price)(Setter.bigDecimalScalaSetter)}::numeric, ${Segment.paramSegment(unsaved.shippedAt)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp) + returning "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text + """.insertReturning(using OrderItemRow.jdbcDecoder).map(_.updatedKeys.head) + } + override def insert(unsaved: OrderItemRowUnsaved): ZIO[ZConnection, Throwable, OrderItemRow] = { + val fs = List( + Some((sql""""order_id"""", sql"${Segment.paramSegment(unsaved.orderId)(Setter.optionParamSetter(OrderId.setter))}::uuid")), + Some((sql""""product_id"""", sql"${Segment.paramSegment(unsaved.productId)(Setter.optionParamSetter(ProductId.setter))}::uuid")), + Some((sql""""quantity"""", sql"${Segment.paramSegment(unsaved.quantity)(Setter.intSetter)}::int4")), + Some((sql""""price"""", sql"${Segment.paramSegment(unsaved.price)(Setter.bigDecimalScalaSetter)}::numeric")), + Some((sql""""shipped_at"""", sql"${Segment.paramSegment(unsaved.shippedAt)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""id"""", sql"${Segment.paramSegment(value: OrderItemId)(OrderItemId.setter)}::uuid")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."order_item" default values + returning "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text + """ + } else { + val names = fs.map { case (n, _) => n }.mkFragment(SqlFragment(", ")) + val values = fs.map { case (_, f) => f }.mkFragment(SqlFragment(", ")) + sql"""insert into "frontpage"."order_item"($names) values ($values) returning "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text""" + } + q.insertReturning(using OrderItemRow.jdbcDecoder).map(_.updatedKeys.head) + + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, OrderItemRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."order_item"("id", "order_id", "product_id", "quantity", "price", "shipped_at") FROM STDIN""", batchSize, unsaved)(OrderItemRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, OrderItemRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."order_item"("order_id", "product_id", "quantity", "price", "shipped_at", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(OrderItemRowUnsaved.text) + } + override def select: SelectBuilder[OrderItemFields, OrderItemRow] = { + SelectBuilderSql(""""frontpage"."order_item"""", OrderItemFields.structure, OrderItemRow.jdbcDecoder) + } + override def selectAll: ZStream[ZConnection, Throwable, OrderItemRow] = { + sql"""select "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text from "frontpage"."order_item"""".query(using OrderItemRow.jdbcDecoder).selectStream() + } + override def selectById(id: OrderItemId): ZIO[ZConnection, Throwable, Option[OrderItemRow]] = { + sql"""select "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text from "frontpage"."order_item" where "id" = ${Segment.paramSegment(id)(OrderItemId.setter)}""".query(using OrderItemRow.jdbcDecoder).selectOne + } + override def selectByIds(ids: Array[OrderItemId]): ZStream[ZConnection, Throwable, OrderItemRow] = { + sql"""select "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text from "frontpage"."order_item" where "id" = ANY(${Segment.paramSegment(ids)(OrderItemId.arraySetter)})""".query(using OrderItemRow.jdbcDecoder).selectStream() + } + override def selectByIdsTracked(ids: Array[OrderItemId]): ZIO[ZConnection, Throwable, Map[OrderItemId, OrderItemRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[OrderItemFields, OrderItemRow] = { + UpdateBuilder(""""frontpage"."order_item"""", OrderItemFields.structure, OrderItemRow.jdbcDecoder) + } + override def update(row: OrderItemRow): ZIO[ZConnection, Throwable, Boolean] = { + val id = row.id + sql"""update "frontpage"."order_item" + set "order_id" = ${Segment.paramSegment(row.orderId)(Setter.optionParamSetter(OrderId.setter))}::uuid, + "product_id" = ${Segment.paramSegment(row.productId)(Setter.optionParamSetter(ProductId.setter))}::uuid, + "quantity" = ${Segment.paramSegment(row.quantity)(Setter.intSetter)}::int4, + "price" = ${Segment.paramSegment(row.price)(Setter.bigDecimalScalaSetter)}::numeric, + "shipped_at" = ${Segment.paramSegment(row.shippedAt)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp + where "id" = ${Segment.paramSegment(id)(OrderItemId.setter)}""".update.map(_ > 0) + } + override def upsert(unsaved: OrderItemRow): ZIO[ZConnection, Throwable, UpdateResult[OrderItemRow]] = { + sql"""insert into "frontpage"."order_item"("id", "order_id", "product_id", "quantity", "price", "shipped_at") + values ( + ${Segment.paramSegment(unsaved.id)(OrderItemId.setter)}::uuid, + ${Segment.paramSegment(unsaved.orderId)(Setter.optionParamSetter(OrderId.setter))}::uuid, + ${Segment.paramSegment(unsaved.productId)(Setter.optionParamSetter(ProductId.setter))}::uuid, + ${Segment.paramSegment(unsaved.quantity)(Setter.intSetter)}::int4, + ${Segment.paramSegment(unsaved.price)(Setter.bigDecimalScalaSetter)}::numeric, + ${Segment.paramSegment(unsaved.shippedAt)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp + ) + on conflict ("id") + do update set + "order_id" = EXCLUDED."order_id", + "product_id" = EXCLUDED."product_id", + "quantity" = EXCLUDED."quantity", + "price" = EXCLUDED."price", + "shipped_at" = EXCLUDED."shipped_at" + returning "id", "order_id", "product_id", "quantity", "price", "shipped_at"::text""".insertReturning(using OrderItemRow.jdbcDecoder) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, OrderItemRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + val created = sql"""create temporary table order_item_TEMP (like "frontpage"."order_item") on commit drop""".execute + val copied = streamingInsert(s"""copy order_item_TEMP("id", "order_id", "product_id", "quantity", "price", "shipped_at") from stdin""", batchSize, unsaved)(OrderItemRow.text) + val merged = sql"""insert into "frontpage"."order_item"("id", "order_id", "product_id", "quantity", "price", "shipped_at") + select * from order_item_TEMP + on conflict ("id") + do update set + "order_id" = EXCLUDED."order_id", + "product_id" = EXCLUDED."product_id", + "quantity" = EXCLUDED."quantity", + "price" = EXCLUDED."price", + "shipped_at" = EXCLUDED."shipped_at" + ; + drop table order_item_TEMP;""".update + created *> copied *> merged + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepoMock.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepoMock.scala new file mode 100644 index 0000000000..ebe8e7fea3 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRepoMock.scala @@ -0,0 +1,116 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams +import zio.Chunk +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +class OrderItemRepoMock(toRow: Function1[OrderItemRowUnsaved, OrderItemRow], + map: scala.collection.mutable.Map[OrderItemId, OrderItemRow] = scala.collection.mutable.Map.empty) extends OrderItemRepo { + override def delete: DeleteBuilder[OrderItemFields, OrderItemRow] = { + DeleteBuilderMock(DeleteParams.empty, OrderItemFields.structure, map) + } + override def deleteById(id: OrderItemId): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[OrderItemId]): ZIO[ZConnection, Throwable, Long] = { + ZIO.succeed(ids.map(id => map.remove(id)).count(_.isDefined).toLong) + } + override def insert(unsaved: OrderItemRow): ZIO[ZConnection, Throwable, OrderItemRow] = { + ZIO.succeed { + val _ = + if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: OrderItemRowUnsaved): ZIO[ZConnection, Throwable, OrderItemRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, OrderItemRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, OrderItemRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, unsavedRow) => + ZIO.succeed { + val row = toRow(unsavedRow) + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + override def select: SelectBuilder[OrderItemFields, OrderItemRow] = { + SelectBuilderMock(OrderItemFields.structure, ZIO.succeed(Chunk.fromIterable(map.values)), SelectParams.empty) + } + override def selectAll: ZStream[ZConnection, Throwable, OrderItemRow] = { + ZStream.fromIterable(map.values) + } + override def selectById(id: OrderItemId): ZIO[ZConnection, Throwable, Option[OrderItemRow]] = { + ZIO.succeed(map.get(id)) + } + override def selectByIds(ids: Array[OrderItemId]): ZStream[ZConnection, Throwable, OrderItemRow] = { + ZStream.fromIterable(ids.flatMap(map.get)) + } + override def selectByIdsTracked(ids: Array[OrderItemId]): ZIO[ZConnection, Throwable, Map[OrderItemId, OrderItemRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[OrderItemFields, OrderItemRow] = { + UpdateBuilderMock(UpdateParams.empty, OrderItemFields.structure, map) + } + override def update(row: OrderItemRow): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: OrderItemRow): ZIO[ZConnection, Throwable, UpdateResult[OrderItemRow]] = { + ZIO.succeed { + map.put(unsaved.id, unsaved): @nowarn + UpdateResult(1, Chunk.single(unsaved)) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, OrderItemRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRow.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRow.scala new file mode 100644 index 0000000000..e78cd8bb0d --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRow.scala @@ -0,0 +1,98 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.order.OrderId +import adventureworks.frontpage.product.ProductId +import java.sql.ResultSet +import zio.jdbc.JdbcDecoder +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** Table: frontpage.order_item + Primary key: id */ +case class OrderItemRow( + /** Default: gen_random_uuid() */ + id: OrderItemId, + /** Points to [[order.OrderRow.id]] */ + orderId: Option[OrderId], + /** Points to [[product.ProductRow.id]] */ + productId: Option[ProductId], + quantity: Int, + price: BigDecimal, + shippedAt: Option[TypoLocalDateTime] +){ + def toUnsavedRow(id: Defaulted[OrderItemId]): OrderItemRowUnsaved = + OrderItemRowUnsaved(orderId, productId, quantity, price, shippedAt, id) + } + +object OrderItemRow { + implicit lazy val jdbcDecoder: JdbcDecoder[OrderItemRow] = new JdbcDecoder[OrderItemRow] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, OrderItemRow) = + columIndex + 5 -> + OrderItemRow( + id = OrderItemId.jdbcDecoder.unsafeDecode(columIndex + 0, rs)._2, + orderId = JdbcDecoder.optionDecoder(OrderId.jdbcDecoder).unsafeDecode(columIndex + 1, rs)._2, + productId = JdbcDecoder.optionDecoder(ProductId.jdbcDecoder).unsafeDecode(columIndex + 2, rs)._2, + quantity = JdbcDecoder.intDecoder.unsafeDecode(columIndex + 3, rs)._2, + price = JdbcDecoder.bigDecimalDecoderScala.unsafeDecode(columIndex + 4, rs)._2, + shippedAt = JdbcDecoder.optionDecoder(TypoLocalDateTime.jdbcDecoder).unsafeDecode(columIndex + 5, rs)._2 + ) + } + implicit lazy val jsonDecoder: JsonDecoder[OrderItemRow] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(OrderItemId.jsonDecoder)) + val orderId = jsonObj.get("order_id").fold[Either[String, Option[OrderId]]](Right(None))(_.as(JsonDecoder.option(using OrderId.jsonDecoder))) + val productId = jsonObj.get("product_id").fold[Either[String, Option[ProductId]]](Right(None))(_.as(JsonDecoder.option(using ProductId.jsonDecoder))) + val quantity = jsonObj.get("quantity").toRight("Missing field 'quantity'").flatMap(_.as(JsonDecoder.int)) + val price = jsonObj.get("price").toRight("Missing field 'price'").flatMap(_.as(JsonDecoder.scalaBigDecimal)) + val shippedAt = jsonObj.get("shipped_at").fold[Either[String, Option[TypoLocalDateTime]]](Right(None))(_.as(JsonDecoder.option(using TypoLocalDateTime.jsonDecoder))) + if (id.isRight && orderId.isRight && productId.isRight && quantity.isRight && price.isRight && shippedAt.isRight) + Right(OrderItemRow(id = id.toOption.get, orderId = orderId.toOption.get, productId = productId.toOption.get, quantity = quantity.toOption.get, price = price.toOption.get, shippedAt = shippedAt.toOption.get)) + else Left(List[Either[String, Any]](id, orderId, productId, quantity, price, shippedAt).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[OrderItemRow] = new JsonEncoder[OrderItemRow] { + override def unsafeEncode(a: OrderItemRow, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""id":""") + OrderItemId.jsonEncoder.unsafeEncode(a.id, indent, out) + out.write(",") + out.write(""""order_id":""") + JsonEncoder.option(using OrderId.jsonEncoder).unsafeEncode(a.orderId, indent, out) + out.write(",") + out.write(""""product_id":""") + JsonEncoder.option(using ProductId.jsonEncoder).unsafeEncode(a.productId, indent, out) + out.write(",") + out.write(""""quantity":""") + JsonEncoder.int.unsafeEncode(a.quantity, indent, out) + out.write(",") + out.write(""""price":""") + JsonEncoder.scalaBigDecimal.unsafeEncode(a.price, indent, out) + out.write(",") + out.write(""""shipped_at":""") + JsonEncoder.option(using TypoLocalDateTime.jsonEncoder).unsafeEncode(a.shippedAt, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[OrderItemRow] = Text.instance[OrderItemRow]{ (row, sb) => + OrderItemId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.option(OrderId.text).unsafeEncode(row.orderId, sb) + sb.append(Text.DELIMETER) + Text.option(ProductId.text).unsafeEncode(row.productId, sb) + sb.append(Text.DELIMETER) + Text.intInstance.unsafeEncode(row.quantity, sb) + sb.append(Text.DELIMETER) + Text.bigDecimalInstance.unsafeEncode(row.price, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.shippedAt, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRowUnsaved.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRowUnsaved.scala new file mode 100644 index 0000000000..d9a9fc9103 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/order_item/OrderItemRowUnsaved.scala @@ -0,0 +1,92 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package order_item + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.order.OrderId +import adventureworks.frontpage.product.ProductId +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** This class corresponds to a row in table `frontpage.order_item` which has not been persisted yet */ +case class OrderItemRowUnsaved( + /** Points to [[order.OrderRow.id]] */ + orderId: Option[OrderId], + /** Points to [[product.ProductRow.id]] */ + productId: Option[ProductId], + quantity: Int, + price: BigDecimal, + shippedAt: Option[TypoLocalDateTime], + /** Default: gen_random_uuid() */ + id: Defaulted[OrderItemId] = Defaulted.UseDefault +) { + def toRow(idDefault: => OrderItemId): OrderItemRow = + OrderItemRow( + orderId = orderId, + productId = productId, + quantity = quantity, + price = price, + shippedAt = shippedAt, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object OrderItemRowUnsaved { + implicit lazy val jsonDecoder: JsonDecoder[OrderItemRowUnsaved] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val orderId = jsonObj.get("order_id").fold[Either[String, Option[OrderId]]](Right(None))(_.as(JsonDecoder.option(using OrderId.jsonDecoder))) + val productId = jsonObj.get("product_id").fold[Either[String, Option[ProductId]]](Right(None))(_.as(JsonDecoder.option(using ProductId.jsonDecoder))) + val quantity = jsonObj.get("quantity").toRight("Missing field 'quantity'").flatMap(_.as(JsonDecoder.int)) + val price = jsonObj.get("price").toRight("Missing field 'price'").flatMap(_.as(JsonDecoder.scalaBigDecimal)) + val shippedAt = jsonObj.get("shipped_at").fold[Either[String, Option[TypoLocalDateTime]]](Right(None))(_.as(JsonDecoder.option(using TypoLocalDateTime.jsonDecoder))) + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(Defaulted.jsonDecoder(OrderItemId.jsonDecoder))) + if (orderId.isRight && productId.isRight && quantity.isRight && price.isRight && shippedAt.isRight && id.isRight) + Right(OrderItemRowUnsaved(orderId = orderId.toOption.get, productId = productId.toOption.get, quantity = quantity.toOption.get, price = price.toOption.get, shippedAt = shippedAt.toOption.get, id = id.toOption.get)) + else Left(List[Either[String, Any]](orderId, productId, quantity, price, shippedAt, id).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[OrderItemRowUnsaved] = new JsonEncoder[OrderItemRowUnsaved] { + override def unsafeEncode(a: OrderItemRowUnsaved, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""order_id":""") + JsonEncoder.option(using OrderId.jsonEncoder).unsafeEncode(a.orderId, indent, out) + out.write(",") + out.write(""""product_id":""") + JsonEncoder.option(using ProductId.jsonEncoder).unsafeEncode(a.productId, indent, out) + out.write(",") + out.write(""""quantity":""") + JsonEncoder.int.unsafeEncode(a.quantity, indent, out) + out.write(",") + out.write(""""price":""") + JsonEncoder.scalaBigDecimal.unsafeEncode(a.price, indent, out) + out.write(",") + out.write(""""shipped_at":""") + JsonEncoder.option(using TypoLocalDateTime.jsonEncoder).unsafeEncode(a.shippedAt, indent, out) + out.write(",") + out.write(""""id":""") + Defaulted.jsonEncoder(OrderItemId.jsonEncoder).unsafeEncode(a.id, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[OrderItemRowUnsaved] = Text.instance[OrderItemRowUnsaved]{ (row, sb) => + Text.option(OrderId.text).unsafeEncode(row.orderId, sb) + sb.append(Text.DELIMETER) + Text.option(ProductId.text).unsafeEncode(row.productId, sb) + sb.append(Text.DELIMETER) + Text.intInstance.unsafeEncode(row.quantity, sb) + sb.append(Text.DELIMETER) + Text.bigDecimalInstance.unsafeEncode(row.price, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.shippedAt, sb) + sb.append(Text.DELIMETER) + Defaulted.text(OrderItemId.text).unsafeEncode(row.id, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionFields.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionFields.scala new file mode 100644 index 0000000000..856b5aae7e --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionFields.scala @@ -0,0 +1,40 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.Structure.Relation + +trait PermissionFields { + def id: IdField[PermissionId, PermissionRow] + def name: Field[String, PermissionRow] +} + +object PermissionFields { + lazy val structure: Relation[PermissionFields, PermissionRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[PermissionFields, PermissionRow] { + + override lazy val fields: PermissionFields = new PermissionFields { + override def id = IdField[PermissionId, PermissionRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, PermissionRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, PermissionRow]] = + List[FieldLikeNoHkt[?, PermissionRow]](fields.id, fields.name) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionId.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionId.scala new file mode 100644 index 0000000000..15fac86386 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import adventureworks.customtypes.TypoUUID +import typo.dsl.Bijection +import typo.dsl.PGType +import zio.jdbc.JdbcDecoder +import zio.jdbc.JdbcEncoder +import zio.jdbc.SqlFragment.Setter +import zio.json.JsonDecoder +import zio.json.JsonEncoder + +/** Type for the primary key of table `frontpage.permission` */ +case class PermissionId(value: TypoUUID) extends AnyVal +object PermissionId { + implicit lazy val arrayJdbcDecoder: JdbcDecoder[Array[PermissionId]] = JdbcDecoder[Array[TypoUUID]].map(_.map(PermissionId.apply)) + implicit lazy val arrayJdbcEncoder: JdbcEncoder[Array[PermissionId]] = JdbcEncoder[Array[TypoUUID]].contramap(_.map(_.value)) + implicit lazy val arraySetter: Setter[Array[PermissionId]] = TypoUUID.arraySetter.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[PermissionId, TypoUUID] = Bijection[PermissionId, TypoUUID](_.value)(PermissionId.apply) + implicit lazy val jdbcDecoder: JdbcDecoder[PermissionId] = TypoUUID.jdbcDecoder.map(PermissionId.apply) + implicit lazy val jdbcEncoder: JdbcEncoder[PermissionId] = TypoUUID.jdbcEncoder.contramap(_.value) + implicit lazy val jsonDecoder: JsonDecoder[PermissionId] = TypoUUID.jsonDecoder.map(PermissionId.apply) + implicit lazy val jsonEncoder: JsonEncoder[PermissionId] = TypoUUID.jsonEncoder.contramap(_.value) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[PermissionId] = Ordering.by(_.value) + implicit lazy val pgType: PGType[PermissionId] = TypoUUID.pgType.as + implicit lazy val setter: Setter[PermissionId] = TypoUUID.setter.contramap(_.value) + implicit lazy val text: Text[PermissionId] = new Text[PermissionId] { + override def unsafeEncode(v: PermissionId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: PermissionId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepo.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepo.scala new file mode 100644 index 0000000000..6cb669e84d --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepo.scala @@ -0,0 +1,39 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +trait PermissionRepo { + def delete: DeleteBuilder[PermissionFields, PermissionRow] + def deleteById(id: PermissionId): ZIO[ZConnection, Throwable, Boolean] + def deleteByIds(ids: Array[PermissionId]): ZIO[ZConnection, Throwable, Long] + def insert(unsaved: PermissionRow): ZIO[ZConnection, Throwable, PermissionRow] + def insert(unsaved: PermissionRowUnsaved): ZIO[ZConnection, Throwable, PermissionRow] + def insertStreaming(unsaved: ZStream[ZConnection, Throwable, PermissionRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, PermissionRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + def select: SelectBuilder[PermissionFields, PermissionRow] + def selectAll: ZStream[ZConnection, Throwable, PermissionRow] + def selectById(id: PermissionId): ZIO[ZConnection, Throwable, Option[PermissionRow]] + def selectByIds(ids: Array[PermissionId]): ZStream[ZConnection, Throwable, PermissionRow] + def selectByIdsTracked(ids: Array[PermissionId]): ZIO[ZConnection, Throwable, Map[PermissionId, PermissionRow]] + def selectByUniqueName(name: String): ZIO[ZConnection, Throwable, Option[PermissionRow]] + def update: UpdateBuilder[PermissionFields, PermissionRow] + def update(row: PermissionRow): ZIO[ZConnection, Throwable, Boolean] + def upsert(unsaved: PermissionRow): ZIO[ZConnection, Throwable, UpdateResult[PermissionRow]] + // Not implementable for zio-jdbc: upsertBatch + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, PermissionRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepoImpl.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepoImpl.scala new file mode 100644 index 0000000000..208af3002f --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepoImpl.scala @@ -0,0 +1,125 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import adventureworks.customtypes.Defaulted +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.SqlFragment +import zio.jdbc.SqlFragment.Segment +import zio.jdbc.SqlFragment.Setter +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.jdbc.sqlInterpolator +import zio.stream.ZStream + +class PermissionRepoImpl extends PermissionRepo { + override def delete: DeleteBuilder[PermissionFields, PermissionRow] = { + DeleteBuilder(""""frontpage"."permission"""", PermissionFields.structure) + } + override def deleteById(id: PermissionId): ZIO[ZConnection, Throwable, Boolean] = { + sql"""delete from "frontpage"."permission" where "id" = ${Segment.paramSegment(id)(PermissionId.setter)}""".delete.map(_ > 0) + } + override def deleteByIds(ids: Array[PermissionId]): ZIO[ZConnection, Throwable, Long] = { + sql"""delete from "frontpage"."permission" where "id" = ANY(${ids})""".delete + } + override def insert(unsaved: PermissionRow): ZIO[ZConnection, Throwable, PermissionRow] = { + sql"""insert into "frontpage"."permission"("id", "name") + values (${Segment.paramSegment(unsaved.id)(PermissionId.setter)}::uuid, ${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}) + returning "id", "name" + """.insertReturning(using PermissionRow.jdbcDecoder).map(_.updatedKeys.head) + } + override def insert(unsaved: PermissionRowUnsaved): ZIO[ZConnection, Throwable, PermissionRow] = { + val fs = List( + Some((sql""""name"""", sql"${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""id"""", sql"${Segment.paramSegment(value: PermissionId)(PermissionId.setter)}::uuid")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."permission" default values + returning "id", "name" + """ + } else { + val names = fs.map { case (n, _) => n }.mkFragment(SqlFragment(", ")) + val values = fs.map { case (_, f) => f }.mkFragment(SqlFragment(", ")) + sql"""insert into "frontpage"."permission"($names) values ($values) returning "id", "name"""" + } + q.insertReturning(using PermissionRow.jdbcDecoder).map(_.updatedKeys.head) + + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, PermissionRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."permission"("id", "name") FROM STDIN""", batchSize, unsaved)(PermissionRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, PermissionRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."permission"("name", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(PermissionRowUnsaved.text) + } + override def select: SelectBuilder[PermissionFields, PermissionRow] = { + SelectBuilderSql(""""frontpage"."permission"""", PermissionFields.structure, PermissionRow.jdbcDecoder) + } + override def selectAll: ZStream[ZConnection, Throwable, PermissionRow] = { + sql"""select "id", "name" from "frontpage"."permission"""".query(using PermissionRow.jdbcDecoder).selectStream() + } + override def selectById(id: PermissionId): ZIO[ZConnection, Throwable, Option[PermissionRow]] = { + sql"""select "id", "name" from "frontpage"."permission" where "id" = ${Segment.paramSegment(id)(PermissionId.setter)}""".query(using PermissionRow.jdbcDecoder).selectOne + } + override def selectByIds(ids: Array[PermissionId]): ZStream[ZConnection, Throwable, PermissionRow] = { + sql"""select "id", "name" from "frontpage"."permission" where "id" = ANY(${Segment.paramSegment(ids)(PermissionId.arraySetter)})""".query(using PermissionRow.jdbcDecoder).selectStream() + } + override def selectByIdsTracked(ids: Array[PermissionId]): ZIO[ZConnection, Throwable, Map[PermissionId, PermissionRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def selectByUniqueName(name: String): ZIO[ZConnection, Throwable, Option[PermissionRow]] = { + sql"""select "id", "name" + from "frontpage"."permission" + where "name" = ${Segment.paramSegment(name)(Setter.stringSetter)} + """.query(using PermissionRow.jdbcDecoder).selectOne + } + override def update: UpdateBuilder[PermissionFields, PermissionRow] = { + UpdateBuilder(""""frontpage"."permission"""", PermissionFields.structure, PermissionRow.jdbcDecoder) + } + override def update(row: PermissionRow): ZIO[ZConnection, Throwable, Boolean] = { + val id = row.id + sql"""update "frontpage"."permission" + set "name" = ${Segment.paramSegment(row.name)(Setter.stringSetter)} + where "id" = ${Segment.paramSegment(id)(PermissionId.setter)}""".update.map(_ > 0) + } + override def upsert(unsaved: PermissionRow): ZIO[ZConnection, Throwable, UpdateResult[PermissionRow]] = { + sql"""insert into "frontpage"."permission"("id", "name") + values ( + ${Segment.paramSegment(unsaved.id)(PermissionId.setter)}::uuid, + ${Segment.paramSegment(unsaved.name)(Setter.stringSetter)} + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name" + returning "id", "name"""".insertReturning(using PermissionRow.jdbcDecoder) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, PermissionRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + val created = sql"""create temporary table permission_TEMP (like "frontpage"."permission") on commit drop""".execute + val copied = streamingInsert(s"""copy permission_TEMP("id", "name") from stdin""", batchSize, unsaved)(PermissionRow.text) + val merged = sql"""insert into "frontpage"."permission"("id", "name") + select * from permission_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name" + ; + drop table permission_TEMP;""".update + created *> copied *> merged + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepoMock.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepoMock.scala new file mode 100644 index 0000000000..8772625398 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRepoMock.scala @@ -0,0 +1,119 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams +import zio.Chunk +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +class PermissionRepoMock(toRow: Function1[PermissionRowUnsaved, PermissionRow], + map: scala.collection.mutable.Map[PermissionId, PermissionRow] = scala.collection.mutable.Map.empty) extends PermissionRepo { + override def delete: DeleteBuilder[PermissionFields, PermissionRow] = { + DeleteBuilderMock(DeleteParams.empty, PermissionFields.structure, map) + } + override def deleteById(id: PermissionId): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[PermissionId]): ZIO[ZConnection, Throwable, Long] = { + ZIO.succeed(ids.map(id => map.remove(id)).count(_.isDefined).toLong) + } + override def insert(unsaved: PermissionRow): ZIO[ZConnection, Throwable, PermissionRow] = { + ZIO.succeed { + val _ = + if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: PermissionRowUnsaved): ZIO[ZConnection, Throwable, PermissionRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, PermissionRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, PermissionRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, unsavedRow) => + ZIO.succeed { + val row = toRow(unsavedRow) + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + override def select: SelectBuilder[PermissionFields, PermissionRow] = { + SelectBuilderMock(PermissionFields.structure, ZIO.succeed(Chunk.fromIterable(map.values)), SelectParams.empty) + } + override def selectAll: ZStream[ZConnection, Throwable, PermissionRow] = { + ZStream.fromIterable(map.values) + } + override def selectById(id: PermissionId): ZIO[ZConnection, Throwable, Option[PermissionRow]] = { + ZIO.succeed(map.get(id)) + } + override def selectByIds(ids: Array[PermissionId]): ZStream[ZConnection, Throwable, PermissionRow] = { + ZStream.fromIterable(ids.flatMap(map.get)) + } + override def selectByIdsTracked(ids: Array[PermissionId]): ZIO[ZConnection, Throwable, Map[PermissionId, PermissionRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def selectByUniqueName(name: String): ZIO[ZConnection, Throwable, Option[PermissionRow]] = { + ZIO.succeed(map.values.find(v => name == v.name)) + } + override def update: UpdateBuilder[PermissionFields, PermissionRow] = { + UpdateBuilderMock(UpdateParams.empty, PermissionFields.structure, map) + } + override def update(row: PermissionRow): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: PermissionRow): ZIO[ZConnection, Throwable, UpdateResult[PermissionRow]] = { + ZIO.succeed { + map.put(unsaved.id, unsaved): @nowarn + UpdateResult(1, Chunk.single(unsaved)) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, PermissionRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRow.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRow.scala new file mode 100644 index 0000000000..4c692503e7 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRow.scala @@ -0,0 +1,61 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import adventureworks.customtypes.Defaulted +import java.sql.ResultSet +import zio.jdbc.JdbcDecoder +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** Table: frontpage.permission + Primary key: id */ +case class PermissionRow( + /** Default: gen_random_uuid() */ + id: PermissionId, + name: String +){ + def toUnsavedRow(id: Defaulted[PermissionId]): PermissionRowUnsaved = + PermissionRowUnsaved(name, id) + } + +object PermissionRow { + implicit lazy val jdbcDecoder: JdbcDecoder[PermissionRow] = new JdbcDecoder[PermissionRow] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, PermissionRow) = + columIndex + 1 -> + PermissionRow( + id = PermissionId.jdbcDecoder.unsafeDecode(columIndex + 0, rs)._2, + name = JdbcDecoder.stringDecoder.unsafeDecode(columIndex + 1, rs)._2 + ) + } + implicit lazy val jsonDecoder: JsonDecoder[PermissionRow] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(PermissionId.jsonDecoder)) + val name = jsonObj.get("name").toRight("Missing field 'name'").flatMap(_.as(JsonDecoder.string)) + if (id.isRight && name.isRight) + Right(PermissionRow(id = id.toOption.get, name = name.toOption.get)) + else Left(List[Either[String, Any]](id, name).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[PermissionRow] = new JsonEncoder[PermissionRow] { + override def unsafeEncode(a: PermissionRow, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""id":""") + PermissionId.jsonEncoder.unsafeEncode(a.id, indent, out) + out.write(",") + out.write(""""name":""") + JsonEncoder.string.unsafeEncode(a.name, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[PermissionRow] = Text.instance[PermissionRow]{ (row, sb) => + PermissionId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRowUnsaved.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRowUnsaved.scala new file mode 100644 index 0000000000..ae25e5bee4 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/permission/PermissionRowUnsaved.scala @@ -0,0 +1,55 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package permission + +import adventureworks.customtypes.Defaulted +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** This class corresponds to a row in table `frontpage.permission` which has not been persisted yet */ +case class PermissionRowUnsaved( + name: String, + /** Default: gen_random_uuid() */ + id: Defaulted[PermissionId] = Defaulted.UseDefault +) { + def toRow(idDefault: => PermissionId): PermissionRow = + PermissionRow( + name = name, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object PermissionRowUnsaved { + implicit lazy val jsonDecoder: JsonDecoder[PermissionRowUnsaved] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val name = jsonObj.get("name").toRight("Missing field 'name'").flatMap(_.as(JsonDecoder.string)) + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(Defaulted.jsonDecoder(PermissionId.jsonDecoder))) + if (name.isRight && id.isRight) + Right(PermissionRowUnsaved(name = name.toOption.get, id = id.toOption.get)) + else Left(List[Either[String, Any]](name, id).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[PermissionRowUnsaved] = new JsonEncoder[PermissionRowUnsaved] { + override def unsafeEncode(a: PermissionRowUnsaved, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""name":""") + JsonEncoder.string.unsafeEncode(a.name, indent, out) + out.write(",") + out.write(""""id":""") + Defaulted.jsonEncoder(PermissionId.jsonEncoder).unsafeEncode(a.id, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[PermissionRowUnsaved] = Text.instance[PermissionRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Defaulted.text(PermissionId.text).unsafeEncode(row.id, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonFields.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonFields.scala new file mode 100644 index 0000000000..c547be34fd --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonFields.scala @@ -0,0 +1,53 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.address.AddressFields +import adventureworks.frontpage.address.AddressId +import adventureworks.frontpage.address.AddressRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait PersonFields { + def id: IdField[PersonId, PersonRow] + def name: Field[String, PersonRow] + def addressId: OptField[AddressId, PersonRow] + def createdAt: OptField[TypoLocalDateTime, PersonRow] + def fkAddress: ForeignKey[AddressFields, AddressRow] = + ForeignKey[AddressFields, AddressRow]("frontpage.fk_address", Nil) + .withColumnPair(addressId, _.id) +} + +object PersonFields { + lazy val structure: Relation[PersonFields, PersonRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[PersonFields, PersonRow] { + + override lazy val fields: PersonFields = new PersonFields { + override def id = IdField[PersonId, PersonRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, PersonRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + override def addressId = OptField[AddressId, PersonRow](_path, "address_id", None, Some("uuid"), x => x.addressId, (row, value) => row.copy(addressId = value)) + override def createdAt = OptField[TypoLocalDateTime, PersonRow](_path, "created_at", Some("text"), Some("timestamp"), x => x.createdAt, (row, value) => row.copy(createdAt = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, PersonRow]] = + List[FieldLikeNoHkt[?, PersonRow]](fields.id, fields.name, fields.addressId, fields.createdAt) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonId.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonId.scala new file mode 100644 index 0000000000..f3da3ac3a5 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import adventureworks.customtypes.TypoUUID +import typo.dsl.Bijection +import typo.dsl.PGType +import zio.jdbc.JdbcDecoder +import zio.jdbc.JdbcEncoder +import zio.jdbc.SqlFragment.Setter +import zio.json.JsonDecoder +import zio.json.JsonEncoder + +/** Type for the primary key of table `frontpage.person` */ +case class PersonId(value: TypoUUID) extends AnyVal +object PersonId { + implicit lazy val arrayJdbcDecoder: JdbcDecoder[Array[PersonId]] = JdbcDecoder[Array[TypoUUID]].map(_.map(PersonId.apply)) + implicit lazy val arrayJdbcEncoder: JdbcEncoder[Array[PersonId]] = JdbcEncoder[Array[TypoUUID]].contramap(_.map(_.value)) + implicit lazy val arraySetter: Setter[Array[PersonId]] = TypoUUID.arraySetter.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[PersonId, TypoUUID] = Bijection[PersonId, TypoUUID](_.value)(PersonId.apply) + implicit lazy val jdbcDecoder: JdbcDecoder[PersonId] = TypoUUID.jdbcDecoder.map(PersonId.apply) + implicit lazy val jdbcEncoder: JdbcEncoder[PersonId] = TypoUUID.jdbcEncoder.contramap(_.value) + implicit lazy val jsonDecoder: JsonDecoder[PersonId] = TypoUUID.jsonDecoder.map(PersonId.apply) + implicit lazy val jsonEncoder: JsonEncoder[PersonId] = TypoUUID.jsonEncoder.contramap(_.value) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[PersonId] = Ordering.by(_.value) + implicit lazy val pgType: PGType[PersonId] = TypoUUID.pgType.as + implicit lazy val setter: Setter[PersonId] = TypoUUID.setter.contramap(_.value) + implicit lazy val text: Text[PersonId] = new Text[PersonId] { + override def unsafeEncode(v: PersonId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: PersonId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonRepo.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonRepo.scala new file mode 100644 index 0000000000..72ad2767ed --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonRepo.scala @@ -0,0 +1,38 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +trait PersonRepo { + def delete: DeleteBuilder[PersonFields, PersonRow] + def deleteById(id: PersonId): ZIO[ZConnection, Throwable, Boolean] + def deleteByIds(ids: Array[PersonId]): ZIO[ZConnection, Throwable, Long] + def insert(unsaved: PersonRow): ZIO[ZConnection, Throwable, PersonRow] + def insert(unsaved: PersonRowUnsaved): ZIO[ZConnection, Throwable, PersonRow] + def insertStreaming(unsaved: ZStream[ZConnection, Throwable, PersonRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, PersonRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + def select: SelectBuilder[PersonFields, PersonRow] + def selectAll: ZStream[ZConnection, Throwable, PersonRow] + def selectById(id: PersonId): ZIO[ZConnection, Throwable, Option[PersonRow]] + def selectByIds(ids: Array[PersonId]): ZStream[ZConnection, Throwable, PersonRow] + def selectByIdsTracked(ids: Array[PersonId]): ZIO[ZConnection, Throwable, Map[PersonId, PersonRow]] + def update: UpdateBuilder[PersonFields, PersonRow] + def update(row: PersonRow): ZIO[ZConnection, Throwable, Boolean] + def upsert(unsaved: PersonRow): ZIO[ZConnection, Throwable, UpdateResult[PersonRow]] + // Not implementable for zio-jdbc: upsertBatch + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, PersonRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonRepoImpl.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonRepoImpl.scala new file mode 100644 index 0000000000..fe8ca56b00 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonRepoImpl.scala @@ -0,0 +1,134 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.address.AddressId +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.SqlFragment +import zio.jdbc.SqlFragment.Segment +import zio.jdbc.SqlFragment.Setter +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.jdbc.sqlInterpolator +import zio.stream.ZStream + +class PersonRepoImpl extends PersonRepo { + override def delete: DeleteBuilder[PersonFields, PersonRow] = { + DeleteBuilder(""""frontpage"."person"""", PersonFields.structure) + } + override def deleteById(id: PersonId): ZIO[ZConnection, Throwable, Boolean] = { + sql"""delete from "frontpage"."person" where "id" = ${Segment.paramSegment(id)(PersonId.setter)}""".delete.map(_ > 0) + } + override def deleteByIds(ids: Array[PersonId]): ZIO[ZConnection, Throwable, Long] = { + sql"""delete from "frontpage"."person" where "id" = ANY(${ids})""".delete + } + override def insert(unsaved: PersonRow): ZIO[ZConnection, Throwable, PersonRow] = { + sql"""insert into "frontpage"."person"("id", "name", "address_id", "created_at") + values (${Segment.paramSegment(unsaved.id)(PersonId.setter)}::uuid, ${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}, ${Segment.paramSegment(unsaved.addressId)(Setter.optionParamSetter(AddressId.setter))}::uuid, ${Segment.paramSegment(unsaved.createdAt)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp) + returning "id", "name", "address_id", "created_at"::text + """.insertReturning(using PersonRow.jdbcDecoder).map(_.updatedKeys.head) + } + override def insert(unsaved: PersonRowUnsaved): ZIO[ZConnection, Throwable, PersonRow] = { + val fs = List( + Some((sql""""name"""", sql"${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}")), + Some((sql""""address_id"""", sql"${Segment.paramSegment(unsaved.addressId)(Setter.optionParamSetter(AddressId.setter))}::uuid")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""id"""", sql"${Segment.paramSegment(value: PersonId)(PersonId.setter)}::uuid")) + }, + unsaved.createdAt match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""created_at"""", sql"${Segment.paramSegment(value: Option[TypoLocalDateTime])(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."person" default values + returning "id", "name", "address_id", "created_at"::text + """ + } else { + val names = fs.map { case (n, _) => n }.mkFragment(SqlFragment(", ")) + val values = fs.map { case (_, f) => f }.mkFragment(SqlFragment(", ")) + sql"""insert into "frontpage"."person"($names) values ($values) returning "id", "name", "address_id", "created_at"::text""" + } + q.insertReturning(using PersonRow.jdbcDecoder).map(_.updatedKeys.head) + + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, PersonRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."person"("id", "name", "address_id", "created_at") FROM STDIN""", batchSize, unsaved)(PersonRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, PersonRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."person"("name", "address_id", "id", "created_at") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(PersonRowUnsaved.text) + } + override def select: SelectBuilder[PersonFields, PersonRow] = { + SelectBuilderSql(""""frontpage"."person"""", PersonFields.structure, PersonRow.jdbcDecoder) + } + override def selectAll: ZStream[ZConnection, Throwable, PersonRow] = { + sql"""select "id", "name", "address_id", "created_at"::text from "frontpage"."person"""".query(using PersonRow.jdbcDecoder).selectStream() + } + override def selectById(id: PersonId): ZIO[ZConnection, Throwable, Option[PersonRow]] = { + sql"""select "id", "name", "address_id", "created_at"::text from "frontpage"."person" where "id" = ${Segment.paramSegment(id)(PersonId.setter)}""".query(using PersonRow.jdbcDecoder).selectOne + } + override def selectByIds(ids: Array[PersonId]): ZStream[ZConnection, Throwable, PersonRow] = { + sql"""select "id", "name", "address_id", "created_at"::text from "frontpage"."person" where "id" = ANY(${Segment.paramSegment(ids)(PersonId.arraySetter)})""".query(using PersonRow.jdbcDecoder).selectStream() + } + override def selectByIdsTracked(ids: Array[PersonId]): ZIO[ZConnection, Throwable, Map[PersonId, PersonRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[PersonFields, PersonRow] = { + UpdateBuilder(""""frontpage"."person"""", PersonFields.structure, PersonRow.jdbcDecoder) + } + override def update(row: PersonRow): ZIO[ZConnection, Throwable, Boolean] = { + val id = row.id + sql"""update "frontpage"."person" + set "name" = ${Segment.paramSegment(row.name)(Setter.stringSetter)}, + "address_id" = ${Segment.paramSegment(row.addressId)(Setter.optionParamSetter(AddressId.setter))}::uuid, + "created_at" = ${Segment.paramSegment(row.createdAt)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp + where "id" = ${Segment.paramSegment(id)(PersonId.setter)}""".update.map(_ > 0) + } + override def upsert(unsaved: PersonRow): ZIO[ZConnection, Throwable, UpdateResult[PersonRow]] = { + sql"""insert into "frontpage"."person"("id", "name", "address_id", "created_at") + values ( + ${Segment.paramSegment(unsaved.id)(PersonId.setter)}::uuid, + ${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}, + ${Segment.paramSegment(unsaved.addressId)(Setter.optionParamSetter(AddressId.setter))}::uuid, + ${Segment.paramSegment(unsaved.createdAt)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "address_id" = EXCLUDED."address_id", + "created_at" = EXCLUDED."created_at" + returning "id", "name", "address_id", "created_at"::text""".insertReturning(using PersonRow.jdbcDecoder) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, PersonRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + val created = sql"""create temporary table person_TEMP (like "frontpage"."person") on commit drop""".execute + val copied = streamingInsert(s"""copy person_TEMP("id", "name", "address_id", "created_at") from stdin""", batchSize, unsaved)(PersonRow.text) + val merged = sql"""insert into "frontpage"."person"("id", "name", "address_id", "created_at") + select * from person_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "address_id" = EXCLUDED."address_id", + "created_at" = EXCLUDED."created_at" + ; + drop table person_TEMP;""".update + created *> copied *> merged + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonRepoMock.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonRepoMock.scala new file mode 100644 index 0000000000..db0bf20a58 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonRepoMock.scala @@ -0,0 +1,116 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams +import zio.Chunk +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +class PersonRepoMock(toRow: Function1[PersonRowUnsaved, PersonRow], + map: scala.collection.mutable.Map[PersonId, PersonRow] = scala.collection.mutable.Map.empty) extends PersonRepo { + override def delete: DeleteBuilder[PersonFields, PersonRow] = { + DeleteBuilderMock(DeleteParams.empty, PersonFields.structure, map) + } + override def deleteById(id: PersonId): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[PersonId]): ZIO[ZConnection, Throwable, Long] = { + ZIO.succeed(ids.map(id => map.remove(id)).count(_.isDefined).toLong) + } + override def insert(unsaved: PersonRow): ZIO[ZConnection, Throwable, PersonRow] = { + ZIO.succeed { + val _ = + if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: PersonRowUnsaved): ZIO[ZConnection, Throwable, PersonRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, PersonRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, PersonRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, unsavedRow) => + ZIO.succeed { + val row = toRow(unsavedRow) + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + override def select: SelectBuilder[PersonFields, PersonRow] = { + SelectBuilderMock(PersonFields.structure, ZIO.succeed(Chunk.fromIterable(map.values)), SelectParams.empty) + } + override def selectAll: ZStream[ZConnection, Throwable, PersonRow] = { + ZStream.fromIterable(map.values) + } + override def selectById(id: PersonId): ZIO[ZConnection, Throwable, Option[PersonRow]] = { + ZIO.succeed(map.get(id)) + } + override def selectByIds(ids: Array[PersonId]): ZStream[ZConnection, Throwable, PersonRow] = { + ZStream.fromIterable(ids.flatMap(map.get)) + } + override def selectByIdsTracked(ids: Array[PersonId]): ZIO[ZConnection, Throwable, Map[PersonId, PersonRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[PersonFields, PersonRow] = { + UpdateBuilderMock(UpdateParams.empty, PersonFields.structure, map) + } + override def update(row: PersonRow): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: PersonRow): ZIO[ZConnection, Throwable, UpdateResult[PersonRow]] = { + ZIO.succeed { + map.put(unsaved.id, unsaved): @nowarn + UpdateResult(1, Chunk.single(unsaved)) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, PersonRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonRow.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonRow.scala new file mode 100644 index 0000000000..11aae65b78 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonRow.scala @@ -0,0 +1,81 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.address.AddressId +import java.sql.ResultSet +import zio.jdbc.JdbcDecoder +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** Table: frontpage.person + Primary key: id */ +case class PersonRow( + /** Default: gen_random_uuid() */ + id: PersonId, + name: String, + /** Points to [[address.AddressRow.id]] */ + addressId: Option[AddressId], + /** Default: now() */ + createdAt: Option[TypoLocalDateTime] +){ + def toUnsavedRow(id: Defaulted[PersonId], createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.Provided(this.createdAt)): PersonRowUnsaved = + PersonRowUnsaved(name, addressId, id, createdAt) + } + +object PersonRow { + implicit lazy val jdbcDecoder: JdbcDecoder[PersonRow] = new JdbcDecoder[PersonRow] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, PersonRow) = + columIndex + 3 -> + PersonRow( + id = PersonId.jdbcDecoder.unsafeDecode(columIndex + 0, rs)._2, + name = JdbcDecoder.stringDecoder.unsafeDecode(columIndex + 1, rs)._2, + addressId = JdbcDecoder.optionDecoder(AddressId.jdbcDecoder).unsafeDecode(columIndex + 2, rs)._2, + createdAt = JdbcDecoder.optionDecoder(TypoLocalDateTime.jdbcDecoder).unsafeDecode(columIndex + 3, rs)._2 + ) + } + implicit lazy val jsonDecoder: JsonDecoder[PersonRow] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(PersonId.jsonDecoder)) + val name = jsonObj.get("name").toRight("Missing field 'name'").flatMap(_.as(JsonDecoder.string)) + val addressId = jsonObj.get("address_id").fold[Either[String, Option[AddressId]]](Right(None))(_.as(JsonDecoder.option(using AddressId.jsonDecoder))) + val createdAt = jsonObj.get("created_at").fold[Either[String, Option[TypoLocalDateTime]]](Right(None))(_.as(JsonDecoder.option(using TypoLocalDateTime.jsonDecoder))) + if (id.isRight && name.isRight && addressId.isRight && createdAt.isRight) + Right(PersonRow(id = id.toOption.get, name = name.toOption.get, addressId = addressId.toOption.get, createdAt = createdAt.toOption.get)) + else Left(List[Either[String, Any]](id, name, addressId, createdAt).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[PersonRow] = new JsonEncoder[PersonRow] { + override def unsafeEncode(a: PersonRow, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""id":""") + PersonId.jsonEncoder.unsafeEncode(a.id, indent, out) + out.write(",") + out.write(""""name":""") + JsonEncoder.string.unsafeEncode(a.name, indent, out) + out.write(",") + out.write(""""address_id":""") + JsonEncoder.option(using AddressId.jsonEncoder).unsafeEncode(a.addressId, indent, out) + out.write(",") + out.write(""""created_at":""") + JsonEncoder.option(using TypoLocalDateTime.jsonEncoder).unsafeEncode(a.createdAt, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[PersonRow] = Text.instance[PersonRow]{ (row, sb) => + PersonId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(AddressId.text).unsafeEncode(row.addressId, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.createdAt, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonRowUnsaved.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonRowUnsaved.scala new file mode 100644 index 0000000000..3fcf4abe17 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/person/PersonRowUnsaved.scala @@ -0,0 +1,78 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package person + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.address.AddressId +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** This class corresponds to a row in table `frontpage.person` which has not been persisted yet */ +case class PersonRowUnsaved( + name: String, + /** Points to [[address.AddressRow.id]] */ + addressId: Option[AddressId], + /** Default: gen_random_uuid() */ + id: Defaulted[PersonId] = Defaulted.UseDefault, + /** Default: now() */ + createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault +) { + def toRow(idDefault: => PersonId, createdAtDefault: => Option[TypoLocalDateTime]): PersonRow = + PersonRow( + name = name, + addressId = addressId, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + }, + createdAt = createdAt match { + case Defaulted.UseDefault => createdAtDefault + case Defaulted.Provided(value) => value + } + ) +} +object PersonRowUnsaved { + implicit lazy val jsonDecoder: JsonDecoder[PersonRowUnsaved] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val name = jsonObj.get("name").toRight("Missing field 'name'").flatMap(_.as(JsonDecoder.string)) + val addressId = jsonObj.get("address_id").fold[Either[String, Option[AddressId]]](Right(None))(_.as(JsonDecoder.option(using AddressId.jsonDecoder))) + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(Defaulted.jsonDecoder(PersonId.jsonDecoder))) + val createdAt = jsonObj.get("created_at").toRight("Missing field 'created_at'").flatMap(_.as(Defaulted.jsonDecoder(JsonDecoder.option(using TypoLocalDateTime.jsonDecoder)))) + if (name.isRight && addressId.isRight && id.isRight && createdAt.isRight) + Right(PersonRowUnsaved(name = name.toOption.get, addressId = addressId.toOption.get, id = id.toOption.get, createdAt = createdAt.toOption.get)) + else Left(List[Either[String, Any]](name, addressId, id, createdAt).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[PersonRowUnsaved] = new JsonEncoder[PersonRowUnsaved] { + override def unsafeEncode(a: PersonRowUnsaved, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""name":""") + JsonEncoder.string.unsafeEncode(a.name, indent, out) + out.write(",") + out.write(""""address_id":""") + JsonEncoder.option(using AddressId.jsonEncoder).unsafeEncode(a.addressId, indent, out) + out.write(",") + out.write(""""id":""") + Defaulted.jsonEncoder(PersonId.jsonEncoder).unsafeEncode(a.id, indent, out) + out.write(",") + out.write(""""created_at":""") + Defaulted.jsonEncoder(JsonEncoder.option(using TypoLocalDateTime.jsonEncoder)).unsafeEncode(a.createdAt, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[PersonRowUnsaved] = Text.instance[PersonRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(AddressId.text).unsafeEncode(row.addressId, sb) + sb.append(Text.DELIMETER) + Defaulted.text(PersonId.text).unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(TypoLocalDateTime.text)).unsafeEncode(row.createdAt, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductFields.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductFields.scala new file mode 100644 index 0000000000..d2c7d735b1 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductFields.scala @@ -0,0 +1,61 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoLocalDateTime +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait ProductFields { + def id: IdField[ProductId, ProductRow] + def name: Field[String, ProductRow] + def price: Field[BigDecimal, ProductRow] + def inStock: OptField[Boolean, ProductRow] + def quantity: OptField[Int, ProductRow] + def lastRestocked: OptField[TypoLocalDateTime, ProductRow] + def lastModified: OptField[TypoLocalDateTime, ProductRow] + def tags: OptField[Array[String], ProductRow] + def categories: OptField[Array[Int], ProductRow] + def prices: OptField[Array[BigDecimal], ProductRow] + def attributes: OptField[Array[TypoJsonb], ProductRow] +} + +object ProductFields { + lazy val structure: Relation[ProductFields, ProductRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[ProductFields, ProductRow] { + + override lazy val fields: ProductFields = new ProductFields { + override def id = IdField[ProductId, ProductRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, ProductRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + override def price = Field[BigDecimal, ProductRow](_path, "price", None, Some("numeric"), x => x.price, (row, value) => row.copy(price = value)) + override def inStock = OptField[Boolean, ProductRow](_path, "in_stock", None, None, x => x.inStock, (row, value) => row.copy(inStock = value)) + override def quantity = OptField[Int, ProductRow](_path, "quantity", None, Some("int4"), x => x.quantity, (row, value) => row.copy(quantity = value)) + override def lastRestocked = OptField[TypoLocalDateTime, ProductRow](_path, "last_restocked", Some("text"), Some("timestamp"), x => x.lastRestocked, (row, value) => row.copy(lastRestocked = value)) + override def lastModified = OptField[TypoLocalDateTime, ProductRow](_path, "last_modified", Some("text"), Some("timestamp"), x => x.lastModified, (row, value) => row.copy(lastModified = value)) + override def tags = OptField[Array[String], ProductRow](_path, "tags", None, Some("text[]"), x => x.tags, (row, value) => row.copy(tags = value)) + override def categories = OptField[Array[Int], ProductRow](_path, "categories", None, Some("int4[]"), x => x.categories, (row, value) => row.copy(categories = value)) + override def prices = OptField[Array[BigDecimal], ProductRow](_path, "prices", None, Some("numeric[]"), x => x.prices, (row, value) => row.copy(prices = value)) + override def attributes = OptField[Array[TypoJsonb], ProductRow](_path, "attributes", None, Some("jsonb[]"), x => x.attributes, (row, value) => row.copy(attributes = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, ProductRow]] = + List[FieldLikeNoHkt[?, ProductRow]](fields.id, fields.name, fields.price, fields.inStock, fields.quantity, fields.lastRestocked, fields.lastModified, fields.tags, fields.categories, fields.prices, fields.attributes) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductId.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductId.scala new file mode 100644 index 0000000000..6d8fef3669 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import adventureworks.customtypes.TypoUUID +import typo.dsl.Bijection +import typo.dsl.PGType +import zio.jdbc.JdbcDecoder +import zio.jdbc.JdbcEncoder +import zio.jdbc.SqlFragment.Setter +import zio.json.JsonDecoder +import zio.json.JsonEncoder + +/** Type for the primary key of table `frontpage.product` */ +case class ProductId(value: TypoUUID) extends AnyVal +object ProductId { + implicit lazy val arrayJdbcDecoder: JdbcDecoder[Array[ProductId]] = JdbcDecoder[Array[TypoUUID]].map(_.map(ProductId.apply)) + implicit lazy val arrayJdbcEncoder: JdbcEncoder[Array[ProductId]] = JdbcEncoder[Array[TypoUUID]].contramap(_.map(_.value)) + implicit lazy val arraySetter: Setter[Array[ProductId]] = TypoUUID.arraySetter.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[ProductId, TypoUUID] = Bijection[ProductId, TypoUUID](_.value)(ProductId.apply) + implicit lazy val jdbcDecoder: JdbcDecoder[ProductId] = TypoUUID.jdbcDecoder.map(ProductId.apply) + implicit lazy val jdbcEncoder: JdbcEncoder[ProductId] = TypoUUID.jdbcEncoder.contramap(_.value) + implicit lazy val jsonDecoder: JsonDecoder[ProductId] = TypoUUID.jsonDecoder.map(ProductId.apply) + implicit lazy val jsonEncoder: JsonEncoder[ProductId] = TypoUUID.jsonEncoder.contramap(_.value) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[ProductId] = Ordering.by(_.value) + implicit lazy val pgType: PGType[ProductId] = TypoUUID.pgType.as + implicit lazy val setter: Setter[ProductId] = TypoUUID.setter.contramap(_.value) + implicit lazy val text: Text[ProductId] = new Text[ProductId] { + override def unsafeEncode(v: ProductId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: ProductId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductRepo.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductRepo.scala new file mode 100644 index 0000000000..ba2a6b28f7 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductRepo.scala @@ -0,0 +1,38 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +trait ProductRepo { + def delete: DeleteBuilder[ProductFields, ProductRow] + def deleteById(id: ProductId): ZIO[ZConnection, Throwable, Boolean] + def deleteByIds(ids: Array[ProductId]): ZIO[ZConnection, Throwable, Long] + def insert(unsaved: ProductRow): ZIO[ZConnection, Throwable, ProductRow] + def insert(unsaved: ProductRowUnsaved): ZIO[ZConnection, Throwable, ProductRow] + def insertStreaming(unsaved: ZStream[ZConnection, Throwable, ProductRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, ProductRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + def select: SelectBuilder[ProductFields, ProductRow] + def selectAll: ZStream[ZConnection, Throwable, ProductRow] + def selectById(id: ProductId): ZIO[ZConnection, Throwable, Option[ProductRow]] + def selectByIds(ids: Array[ProductId]): ZStream[ZConnection, Throwable, ProductRow] + def selectByIdsTracked(ids: Array[ProductId]): ZIO[ZConnection, Throwable, Map[ProductId, ProductRow]] + def update: UpdateBuilder[ProductFields, ProductRow] + def update(row: ProductRow): ZIO[ZConnection, Throwable, Boolean] + def upsert(unsaved: ProductRow): ZIO[ZConnection, Throwable, UpdateResult[ProductRow]] + // Not implementable for zio-jdbc: upsertBatch + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, ProductRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductRepoImpl.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductRepoImpl.scala new file mode 100644 index 0000000000..cb24ae9723 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductRepoImpl.scala @@ -0,0 +1,187 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoLocalDateTime +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.SqlFragment +import zio.jdbc.SqlFragment.Segment +import zio.jdbc.SqlFragment.Setter +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.jdbc.sqlInterpolator +import zio.stream.ZStream + +class ProductRepoImpl extends ProductRepo { + override def delete: DeleteBuilder[ProductFields, ProductRow] = { + DeleteBuilder(""""frontpage"."product"""", ProductFields.structure) + } + override def deleteById(id: ProductId): ZIO[ZConnection, Throwable, Boolean] = { + sql"""delete from "frontpage"."product" where "id" = ${Segment.paramSegment(id)(ProductId.setter)}""".delete.map(_ > 0) + } + override def deleteByIds(ids: Array[ProductId]): ZIO[ZConnection, Throwable, Long] = { + sql"""delete from "frontpage"."product" where "id" = ANY(${ids})""".delete + } + override def insert(unsaved: ProductRow): ZIO[ZConnection, Throwable, ProductRow] = { + sql"""insert into "frontpage"."product"("id", "name", "price", "in_stock", "quantity", "last_restocked", "last_modified", "tags", "categories", "prices", "attributes") + values (${Segment.paramSegment(unsaved.id)(ProductId.setter)}::uuid, ${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}, ${Segment.paramSegment(unsaved.price)(Setter.bigDecimalScalaSetter)}::numeric, ${Segment.paramSegment(unsaved.inStock)(Setter.optionParamSetter(Setter.booleanSetter))}, ${Segment.paramSegment(unsaved.quantity)(Setter.optionParamSetter(Setter.intSetter))}::int4, ${Segment.paramSegment(unsaved.lastRestocked)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp, ${Segment.paramSegment(unsaved.lastModified)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp, ${Segment.paramSegment(unsaved.tags)(Setter.optionParamSetter(adventureworks.StringArraySetter))}::text[], ${Segment.paramSegment(unsaved.categories)(Setter.optionParamSetter(adventureworks.IntArraySetter))}::int4[], ${Segment.paramSegment(unsaved.prices)(Setter.optionParamSetter(adventureworks.ScalaBigDecimalArraySetter))}::numeric[], ${Segment.paramSegment(unsaved.attributes)(Setter.optionParamSetter(TypoJsonb.arraySetter))}::jsonb[]) + returning "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes" + """.insertReturning(using ProductRow.jdbcDecoder).map(_.updatedKeys.head) + } + override def insert(unsaved: ProductRowUnsaved): ZIO[ZConnection, Throwable, ProductRow] = { + val fs = List( + Some((sql""""name"""", sql"${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}")), + Some((sql""""price"""", sql"${Segment.paramSegment(unsaved.price)(Setter.bigDecimalScalaSetter)}::numeric")), + Some((sql""""last_restocked"""", sql"${Segment.paramSegment(unsaved.lastRestocked)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""id"""", sql"${Segment.paramSegment(value: ProductId)(ProductId.setter)}::uuid")) + }, + unsaved.inStock match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""in_stock"""", sql"${Segment.paramSegment(value: Option[Boolean])(Setter.optionParamSetter(Setter.booleanSetter))}")) + }, + unsaved.quantity match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""quantity"""", sql"${Segment.paramSegment(value: Option[Int])(Setter.optionParamSetter(Setter.intSetter))}::int4")) + }, + unsaved.lastModified match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""last_modified"""", sql"${Segment.paramSegment(value: Option[TypoLocalDateTime])(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp")) + }, + unsaved.tags match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""tags"""", sql"${Segment.paramSegment(value: Option[Array[String]])(Setter.optionParamSetter(adventureworks.StringArraySetter))}::text[]")) + }, + unsaved.categories match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""categories"""", sql"${Segment.paramSegment(value: Option[Array[Int]])(Setter.optionParamSetter(adventureworks.IntArraySetter))}::int4[]")) + }, + unsaved.prices match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""prices"""", sql"${Segment.paramSegment(value: Option[Array[BigDecimal]])(Setter.optionParamSetter(adventureworks.ScalaBigDecimalArraySetter))}::numeric[]")) + }, + unsaved.attributes match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""attributes"""", sql"${Segment.paramSegment(value: Option[Array[TypoJsonb]])(Setter.optionParamSetter(TypoJsonb.arraySetter))}::jsonb[]")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."product" default values + returning "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes" + """ + } else { + val names = fs.map { case (n, _) => n }.mkFragment(SqlFragment(", ")) + val values = fs.map { case (_, f) => f }.mkFragment(SqlFragment(", ")) + sql"""insert into "frontpage"."product"($names) values ($values) returning "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes"""" + } + q.insertReturning(using ProductRow.jdbcDecoder).map(_.updatedKeys.head) + + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, ProductRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."product"("id", "name", "price", "in_stock", "quantity", "last_restocked", "last_modified", "tags", "categories", "prices", "attributes") FROM STDIN""", batchSize, unsaved)(ProductRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, ProductRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."product"("name", "price", "last_restocked", "id", "in_stock", "quantity", "last_modified", "tags", "categories", "prices", "attributes") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(ProductRowUnsaved.text) + } + override def select: SelectBuilder[ProductFields, ProductRow] = { + SelectBuilderSql(""""frontpage"."product"""", ProductFields.structure, ProductRow.jdbcDecoder) + } + override def selectAll: ZStream[ZConnection, Throwable, ProductRow] = { + sql"""select "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes" from "frontpage"."product"""".query(using ProductRow.jdbcDecoder).selectStream() + } + override def selectById(id: ProductId): ZIO[ZConnection, Throwable, Option[ProductRow]] = { + sql"""select "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes" from "frontpage"."product" where "id" = ${Segment.paramSegment(id)(ProductId.setter)}""".query(using ProductRow.jdbcDecoder).selectOne + } + override def selectByIds(ids: Array[ProductId]): ZStream[ZConnection, Throwable, ProductRow] = { + sql"""select "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes" from "frontpage"."product" where "id" = ANY(${Segment.paramSegment(ids)(ProductId.arraySetter)})""".query(using ProductRow.jdbcDecoder).selectStream() + } + override def selectByIdsTracked(ids: Array[ProductId]): ZIO[ZConnection, Throwable, Map[ProductId, ProductRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[ProductFields, ProductRow] = { + UpdateBuilder(""""frontpage"."product"""", ProductFields.structure, ProductRow.jdbcDecoder) + } + override def update(row: ProductRow): ZIO[ZConnection, Throwable, Boolean] = { + val id = row.id + sql"""update "frontpage"."product" + set "name" = ${Segment.paramSegment(row.name)(Setter.stringSetter)}, + "price" = ${Segment.paramSegment(row.price)(Setter.bigDecimalScalaSetter)}::numeric, + "in_stock" = ${Segment.paramSegment(row.inStock)(Setter.optionParamSetter(Setter.booleanSetter))}, + "quantity" = ${Segment.paramSegment(row.quantity)(Setter.optionParamSetter(Setter.intSetter))}::int4, + "last_restocked" = ${Segment.paramSegment(row.lastRestocked)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp, + "last_modified" = ${Segment.paramSegment(row.lastModified)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp, + "tags" = ${Segment.paramSegment(row.tags)(Setter.optionParamSetter(adventureworks.StringArraySetter))}::text[], + "categories" = ${Segment.paramSegment(row.categories)(Setter.optionParamSetter(adventureworks.IntArraySetter))}::int4[], + "prices" = ${Segment.paramSegment(row.prices)(Setter.optionParamSetter(adventureworks.ScalaBigDecimalArraySetter))}::numeric[], + "attributes" = ${Segment.paramSegment(row.attributes)(Setter.optionParamSetter(TypoJsonb.arraySetter))}::jsonb[] + where "id" = ${Segment.paramSegment(id)(ProductId.setter)}""".update.map(_ > 0) + } + override def upsert(unsaved: ProductRow): ZIO[ZConnection, Throwable, UpdateResult[ProductRow]] = { + sql"""insert into "frontpage"."product"("id", "name", "price", "in_stock", "quantity", "last_restocked", "last_modified", "tags", "categories", "prices", "attributes") + values ( + ${Segment.paramSegment(unsaved.id)(ProductId.setter)}::uuid, + ${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}, + ${Segment.paramSegment(unsaved.price)(Setter.bigDecimalScalaSetter)}::numeric, + ${Segment.paramSegment(unsaved.inStock)(Setter.optionParamSetter(Setter.booleanSetter))}, + ${Segment.paramSegment(unsaved.quantity)(Setter.optionParamSetter(Setter.intSetter))}::int4, + ${Segment.paramSegment(unsaved.lastRestocked)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp, + ${Segment.paramSegment(unsaved.lastModified)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp, + ${Segment.paramSegment(unsaved.tags)(Setter.optionParamSetter(adventureworks.StringArraySetter))}::text[], + ${Segment.paramSegment(unsaved.categories)(Setter.optionParamSetter(adventureworks.IntArraySetter))}::int4[], + ${Segment.paramSegment(unsaved.prices)(Setter.optionParamSetter(adventureworks.ScalaBigDecimalArraySetter))}::numeric[], + ${Segment.paramSegment(unsaved.attributes)(Setter.optionParamSetter(TypoJsonb.arraySetter))}::jsonb[] + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "price" = EXCLUDED."price", + "in_stock" = EXCLUDED."in_stock", + "quantity" = EXCLUDED."quantity", + "last_restocked" = EXCLUDED."last_restocked", + "last_modified" = EXCLUDED."last_modified", + "tags" = EXCLUDED."tags", + "categories" = EXCLUDED."categories", + "prices" = EXCLUDED."prices", + "attributes" = EXCLUDED."attributes" + returning "id", "name", "price", "in_stock", "quantity", "last_restocked"::text, "last_modified"::text, "tags", "categories", "prices", "attributes"""".insertReturning(using ProductRow.jdbcDecoder) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, ProductRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + val created = sql"""create temporary table product_TEMP (like "frontpage"."product") on commit drop""".execute + val copied = streamingInsert(s"""copy product_TEMP("id", "name", "price", "in_stock", "quantity", "last_restocked", "last_modified", "tags", "categories", "prices", "attributes") from stdin""", batchSize, unsaved)(ProductRow.text) + val merged = sql"""insert into "frontpage"."product"("id", "name", "price", "in_stock", "quantity", "last_restocked", "last_modified", "tags", "categories", "prices", "attributes") + select * from product_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name", + "price" = EXCLUDED."price", + "in_stock" = EXCLUDED."in_stock", + "quantity" = EXCLUDED."quantity", + "last_restocked" = EXCLUDED."last_restocked", + "last_modified" = EXCLUDED."last_modified", + "tags" = EXCLUDED."tags", + "categories" = EXCLUDED."categories", + "prices" = EXCLUDED."prices", + "attributes" = EXCLUDED."attributes" + ; + drop table product_TEMP;""".update + created *> copied *> merged + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductRepoMock.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductRepoMock.scala new file mode 100644 index 0000000000..71f043fcac --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductRepoMock.scala @@ -0,0 +1,116 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams +import zio.Chunk +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +class ProductRepoMock(toRow: Function1[ProductRowUnsaved, ProductRow], + map: scala.collection.mutable.Map[ProductId, ProductRow] = scala.collection.mutable.Map.empty) extends ProductRepo { + override def delete: DeleteBuilder[ProductFields, ProductRow] = { + DeleteBuilderMock(DeleteParams.empty, ProductFields.structure, map) + } + override def deleteById(id: ProductId): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[ProductId]): ZIO[ZConnection, Throwable, Long] = { + ZIO.succeed(ids.map(id => map.remove(id)).count(_.isDefined).toLong) + } + override def insert(unsaved: ProductRow): ZIO[ZConnection, Throwable, ProductRow] = { + ZIO.succeed { + val _ = + if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: ProductRowUnsaved): ZIO[ZConnection, Throwable, ProductRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, ProductRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, ProductRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, unsavedRow) => + ZIO.succeed { + val row = toRow(unsavedRow) + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + override def select: SelectBuilder[ProductFields, ProductRow] = { + SelectBuilderMock(ProductFields.structure, ZIO.succeed(Chunk.fromIterable(map.values)), SelectParams.empty) + } + override def selectAll: ZStream[ZConnection, Throwable, ProductRow] = { + ZStream.fromIterable(map.values) + } + override def selectById(id: ProductId): ZIO[ZConnection, Throwable, Option[ProductRow]] = { + ZIO.succeed(map.get(id)) + } + override def selectByIds(ids: Array[ProductId]): ZStream[ZConnection, Throwable, ProductRow] = { + ZStream.fromIterable(ids.flatMap(map.get)) + } + override def selectByIdsTracked(ids: Array[ProductId]): ZIO[ZConnection, Throwable, Map[ProductId, ProductRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[ProductFields, ProductRow] = { + UpdateBuilderMock(UpdateParams.empty, ProductFields.structure, map) + } + override def update(row: ProductRow): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: ProductRow): ZIO[ZConnection, Throwable, UpdateResult[ProductRow]] = { + ZIO.succeed { + map.put(unsaved.id, unsaved): @nowarn + UpdateResult(1, Chunk.single(unsaved)) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, ProductRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductRow.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductRow.scala new file mode 100644 index 0000000000..87bffbab08 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductRow.scala @@ -0,0 +1,142 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoLocalDateTime +import java.sql.ResultSet +import zio.jdbc.JdbcDecoder +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** Table: frontpage.product + Primary key: id */ +case class ProductRow( + /** Default: gen_random_uuid() */ + id: ProductId, + name: String, + price: BigDecimal, + /** Default: true */ + inStock: Option[Boolean], + /** Default: 0 */ + quantity: Option[Int], + lastRestocked: Option[TypoLocalDateTime], + /** Default: now() */ + lastModified: Option[TypoLocalDateTime], + /** Default: '{}'::text[] */ + tags: Option[Array[String]], + /** Default: '{}'::integer[] */ + categories: Option[Array[Int]], + /** Default: '{}'::numeric[] */ + prices: Option[Array[BigDecimal]], + /** Default: '{}'::jsonb[] */ + attributes: Option[Array[TypoJsonb]] +){ + def toUnsavedRow(id: Defaulted[ProductId], inStock: Defaulted[Option[Boolean]] = Defaulted.Provided(this.inStock), quantity: Defaulted[Option[Int]] = Defaulted.Provided(this.quantity), lastModified: Defaulted[Option[TypoLocalDateTime]] = Defaulted.Provided(this.lastModified), tags: Defaulted[Option[Array[String]]] = Defaulted.Provided(this.tags), categories: Defaulted[Option[Array[Int]]] = Defaulted.Provided(this.categories), prices: Defaulted[Option[Array[BigDecimal]]] = Defaulted.Provided(this.prices), attributes: Defaulted[Option[Array[TypoJsonb]]] = Defaulted.Provided(this.attributes)): ProductRowUnsaved = + ProductRowUnsaved(name, price, lastRestocked, id, inStock, quantity, lastModified, tags, categories, prices, attributes) + } + +object ProductRow { + implicit lazy val jdbcDecoder: JdbcDecoder[ProductRow] = new JdbcDecoder[ProductRow] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, ProductRow) = + columIndex + 10 -> + ProductRow( + id = ProductId.jdbcDecoder.unsafeDecode(columIndex + 0, rs)._2, + name = JdbcDecoder.stringDecoder.unsafeDecode(columIndex + 1, rs)._2, + price = JdbcDecoder.bigDecimalDecoderScala.unsafeDecode(columIndex + 2, rs)._2, + inStock = JdbcDecoder.optionDecoder(JdbcDecoder.booleanDecoder).unsafeDecode(columIndex + 3, rs)._2, + quantity = JdbcDecoder.optionDecoder(JdbcDecoder.intDecoder).unsafeDecode(columIndex + 4, rs)._2, + lastRestocked = JdbcDecoder.optionDecoder(TypoLocalDateTime.jdbcDecoder).unsafeDecode(columIndex + 5, rs)._2, + lastModified = JdbcDecoder.optionDecoder(TypoLocalDateTime.jdbcDecoder).unsafeDecode(columIndex + 6, rs)._2, + tags = JdbcDecoder.optionDecoder(adventureworks.StringArrayDecoder).unsafeDecode(columIndex + 7, rs)._2, + categories = JdbcDecoder.optionDecoder(adventureworks.IntArrayDecoder).unsafeDecode(columIndex + 8, rs)._2, + prices = JdbcDecoder.optionDecoder(adventureworks.ScalaBigDecimalArrayDecoder).unsafeDecode(columIndex + 9, rs)._2, + attributes = JdbcDecoder.optionDecoder(JdbcDecoder[Array[TypoJsonb]]).unsafeDecode(columIndex + 10, rs)._2 + ) + } + implicit lazy val jsonDecoder: JsonDecoder[ProductRow] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(ProductId.jsonDecoder)) + val name = jsonObj.get("name").toRight("Missing field 'name'").flatMap(_.as(JsonDecoder.string)) + val price = jsonObj.get("price").toRight("Missing field 'price'").flatMap(_.as(JsonDecoder.scalaBigDecimal)) + val inStock = jsonObj.get("in_stock").fold[Either[String, Option[Boolean]]](Right(None))(_.as(JsonDecoder.option(using JsonDecoder.boolean))) + val quantity = jsonObj.get("quantity").fold[Either[String, Option[Int]]](Right(None))(_.as(JsonDecoder.option(using JsonDecoder.int))) + val lastRestocked = jsonObj.get("last_restocked").fold[Either[String, Option[TypoLocalDateTime]]](Right(None))(_.as(JsonDecoder.option(using TypoLocalDateTime.jsonDecoder))) + val lastModified = jsonObj.get("last_modified").fold[Either[String, Option[TypoLocalDateTime]]](Right(None))(_.as(JsonDecoder.option(using TypoLocalDateTime.jsonDecoder))) + val tags = jsonObj.get("tags").fold[Either[String, Option[Array[String]]]](Right(None))(_.as(JsonDecoder.option(using JsonDecoder.array[String](using JsonDecoder.string, implicitly)))) + val categories = jsonObj.get("categories").fold[Either[String, Option[Array[Int]]]](Right(None))(_.as(JsonDecoder.option(using JsonDecoder.array[Int](using JsonDecoder.int, implicitly)))) + val prices = jsonObj.get("prices").fold[Either[String, Option[Array[BigDecimal]]]](Right(None))(_.as(JsonDecoder.option(using JsonDecoder.array[BigDecimal](using JsonDecoder.scalaBigDecimal, implicitly)))) + val attributes = jsonObj.get("attributes").fold[Either[String, Option[Array[TypoJsonb]]]](Right(None))(_.as(JsonDecoder.option(using JsonDecoder.array[TypoJsonb](using TypoJsonb.jsonDecoder, implicitly)))) + if (id.isRight && name.isRight && price.isRight && inStock.isRight && quantity.isRight && lastRestocked.isRight && lastModified.isRight && tags.isRight && categories.isRight && prices.isRight && attributes.isRight) + Right(ProductRow(id = id.toOption.get, name = name.toOption.get, price = price.toOption.get, inStock = inStock.toOption.get, quantity = quantity.toOption.get, lastRestocked = lastRestocked.toOption.get, lastModified = lastModified.toOption.get, tags = tags.toOption.get, categories = categories.toOption.get, prices = prices.toOption.get, attributes = attributes.toOption.get)) + else Left(List[Either[String, Any]](id, name, price, inStock, quantity, lastRestocked, lastModified, tags, categories, prices, attributes).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[ProductRow] = new JsonEncoder[ProductRow] { + override def unsafeEncode(a: ProductRow, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""id":""") + ProductId.jsonEncoder.unsafeEncode(a.id, indent, out) + out.write(",") + out.write(""""name":""") + JsonEncoder.string.unsafeEncode(a.name, indent, out) + out.write(",") + out.write(""""price":""") + JsonEncoder.scalaBigDecimal.unsafeEncode(a.price, indent, out) + out.write(",") + out.write(""""in_stock":""") + JsonEncoder.option(using JsonEncoder.boolean).unsafeEncode(a.inStock, indent, out) + out.write(",") + out.write(""""quantity":""") + JsonEncoder.option(using JsonEncoder.int).unsafeEncode(a.quantity, indent, out) + out.write(",") + out.write(""""last_restocked":""") + JsonEncoder.option(using TypoLocalDateTime.jsonEncoder).unsafeEncode(a.lastRestocked, indent, out) + out.write(",") + out.write(""""last_modified":""") + JsonEncoder.option(using TypoLocalDateTime.jsonEncoder).unsafeEncode(a.lastModified, indent, out) + out.write(",") + out.write(""""tags":""") + JsonEncoder.option(using JsonEncoder.array[String](using JsonEncoder.string, implicitly)).unsafeEncode(a.tags, indent, out) + out.write(",") + out.write(""""categories":""") + JsonEncoder.option(using JsonEncoder.array[Int](using JsonEncoder.int, implicitly)).unsafeEncode(a.categories, indent, out) + out.write(",") + out.write(""""prices":""") + JsonEncoder.option(using JsonEncoder.array[BigDecimal](using JsonEncoder.scalaBigDecimal, implicitly)).unsafeEncode(a.prices, indent, out) + out.write(",") + out.write(""""attributes":""") + JsonEncoder.option(using JsonEncoder.array[TypoJsonb](using TypoJsonb.jsonEncoder, implicitly)).unsafeEncode(a.attributes, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[ProductRow] = Text.instance[ProductRow]{ (row, sb) => + ProductId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.bigDecimalInstance.unsafeEncode(row.price, sb) + sb.append(Text.DELIMETER) + Text.option(Text.booleanInstance).unsafeEncode(row.inStock, sb) + sb.append(Text.DELIMETER) + Text.option(Text.intInstance).unsafeEncode(row.quantity, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.lastRestocked, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.lastModified, sb) + sb.append(Text.DELIMETER) + Text.option(Text[Array[String]]).unsafeEncode(row.tags, sb) + sb.append(Text.DELIMETER) + Text.option(Text[Array[Int]]).unsafeEncode(row.categories, sb) + sb.append(Text.DELIMETER) + Text.option(Text[Array[BigDecimal]]).unsafeEncode(row.prices, sb) + sb.append(Text.DELIMETER) + Text.option(Text.iterableInstance[Array, TypoJsonb](TypoJsonb.text, implicitly)).unsafeEncode(row.attributes, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductRowUnsaved.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductRowUnsaved.scala new file mode 100644 index 0000000000..e21e2bcb7c --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product/ProductRowUnsaved.scala @@ -0,0 +1,157 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoJsonb +import adventureworks.customtypes.TypoLocalDateTime +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** This class corresponds to a row in table `frontpage.product` which has not been persisted yet */ +case class ProductRowUnsaved( + name: String, + price: BigDecimal, + lastRestocked: Option[TypoLocalDateTime], + /** Default: gen_random_uuid() */ + id: Defaulted[ProductId] = Defaulted.UseDefault, + /** Default: true */ + inStock: Defaulted[Option[Boolean]] = Defaulted.UseDefault, + /** Default: 0 */ + quantity: Defaulted[Option[Int]] = Defaulted.UseDefault, + /** Default: now() */ + lastModified: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault, + /** Default: '{}'::text[] */ + tags: Defaulted[Option[Array[String]]] = Defaulted.UseDefault, + /** Default: '{}'::integer[] */ + categories: Defaulted[Option[Array[Int]]] = Defaulted.UseDefault, + /** Default: '{}'::numeric[] */ + prices: Defaulted[Option[Array[BigDecimal]]] = Defaulted.UseDefault, + /** Default: '{}'::jsonb[] */ + attributes: Defaulted[Option[Array[TypoJsonb]]] = Defaulted.UseDefault +) { + def toRow(idDefault: => ProductId, inStockDefault: => Option[Boolean], quantityDefault: => Option[Int], lastModifiedDefault: => Option[TypoLocalDateTime], tagsDefault: => Option[Array[String]], categoriesDefault: => Option[Array[Int]], pricesDefault: => Option[Array[BigDecimal]], attributesDefault: => Option[Array[TypoJsonb]]): ProductRow = + ProductRow( + name = name, + price = price, + lastRestocked = lastRestocked, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + }, + inStock = inStock match { + case Defaulted.UseDefault => inStockDefault + case Defaulted.Provided(value) => value + }, + quantity = quantity match { + case Defaulted.UseDefault => quantityDefault + case Defaulted.Provided(value) => value + }, + lastModified = lastModified match { + case Defaulted.UseDefault => lastModifiedDefault + case Defaulted.Provided(value) => value + }, + tags = tags match { + case Defaulted.UseDefault => tagsDefault + case Defaulted.Provided(value) => value + }, + categories = categories match { + case Defaulted.UseDefault => categoriesDefault + case Defaulted.Provided(value) => value + }, + prices = prices match { + case Defaulted.UseDefault => pricesDefault + case Defaulted.Provided(value) => value + }, + attributes = attributes match { + case Defaulted.UseDefault => attributesDefault + case Defaulted.Provided(value) => value + } + ) +} +object ProductRowUnsaved { + implicit lazy val jsonDecoder: JsonDecoder[ProductRowUnsaved] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val name = jsonObj.get("name").toRight("Missing field 'name'").flatMap(_.as(JsonDecoder.string)) + val price = jsonObj.get("price").toRight("Missing field 'price'").flatMap(_.as(JsonDecoder.scalaBigDecimal)) + val lastRestocked = jsonObj.get("last_restocked").fold[Either[String, Option[TypoLocalDateTime]]](Right(None))(_.as(JsonDecoder.option(using TypoLocalDateTime.jsonDecoder))) + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(Defaulted.jsonDecoder(ProductId.jsonDecoder))) + val inStock = jsonObj.get("in_stock").toRight("Missing field 'in_stock'").flatMap(_.as(Defaulted.jsonDecoder(JsonDecoder.option(using JsonDecoder.boolean)))) + val quantity = jsonObj.get("quantity").toRight("Missing field 'quantity'").flatMap(_.as(Defaulted.jsonDecoder(JsonDecoder.option(using JsonDecoder.int)))) + val lastModified = jsonObj.get("last_modified").toRight("Missing field 'last_modified'").flatMap(_.as(Defaulted.jsonDecoder(JsonDecoder.option(using TypoLocalDateTime.jsonDecoder)))) + val tags = jsonObj.get("tags").toRight("Missing field 'tags'").flatMap(_.as(Defaulted.jsonDecoder(JsonDecoder.option(using JsonDecoder.array[String](using JsonDecoder.string, implicitly))))) + val categories = jsonObj.get("categories").toRight("Missing field 'categories'").flatMap(_.as(Defaulted.jsonDecoder(JsonDecoder.option(using JsonDecoder.array[Int](using JsonDecoder.int, implicitly))))) + val prices = jsonObj.get("prices").toRight("Missing field 'prices'").flatMap(_.as(Defaulted.jsonDecoder(JsonDecoder.option(using JsonDecoder.array[BigDecimal](using JsonDecoder.scalaBigDecimal, implicitly))))) + val attributes = jsonObj.get("attributes").toRight("Missing field 'attributes'").flatMap(_.as(Defaulted.jsonDecoder(JsonDecoder.option(using JsonDecoder.array[TypoJsonb](using TypoJsonb.jsonDecoder, implicitly))))) + if (name.isRight && price.isRight && lastRestocked.isRight && id.isRight && inStock.isRight && quantity.isRight && lastModified.isRight && tags.isRight && categories.isRight && prices.isRight && attributes.isRight) + Right(ProductRowUnsaved(name = name.toOption.get, price = price.toOption.get, lastRestocked = lastRestocked.toOption.get, id = id.toOption.get, inStock = inStock.toOption.get, quantity = quantity.toOption.get, lastModified = lastModified.toOption.get, tags = tags.toOption.get, categories = categories.toOption.get, prices = prices.toOption.get, attributes = attributes.toOption.get)) + else Left(List[Either[String, Any]](name, price, lastRestocked, id, inStock, quantity, lastModified, tags, categories, prices, attributes).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[ProductRowUnsaved] = new JsonEncoder[ProductRowUnsaved] { + override def unsafeEncode(a: ProductRowUnsaved, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""name":""") + JsonEncoder.string.unsafeEncode(a.name, indent, out) + out.write(",") + out.write(""""price":""") + JsonEncoder.scalaBigDecimal.unsafeEncode(a.price, indent, out) + out.write(",") + out.write(""""last_restocked":""") + JsonEncoder.option(using TypoLocalDateTime.jsonEncoder).unsafeEncode(a.lastRestocked, indent, out) + out.write(",") + out.write(""""id":""") + Defaulted.jsonEncoder(ProductId.jsonEncoder).unsafeEncode(a.id, indent, out) + out.write(",") + out.write(""""in_stock":""") + Defaulted.jsonEncoder(JsonEncoder.option(using JsonEncoder.boolean)).unsafeEncode(a.inStock, indent, out) + out.write(",") + out.write(""""quantity":""") + Defaulted.jsonEncoder(JsonEncoder.option(using JsonEncoder.int)).unsafeEncode(a.quantity, indent, out) + out.write(",") + out.write(""""last_modified":""") + Defaulted.jsonEncoder(JsonEncoder.option(using TypoLocalDateTime.jsonEncoder)).unsafeEncode(a.lastModified, indent, out) + out.write(",") + out.write(""""tags":""") + Defaulted.jsonEncoder(JsonEncoder.option(using JsonEncoder.array[String](using JsonEncoder.string, implicitly))).unsafeEncode(a.tags, indent, out) + out.write(",") + out.write(""""categories":""") + Defaulted.jsonEncoder(JsonEncoder.option(using JsonEncoder.array[Int](using JsonEncoder.int, implicitly))).unsafeEncode(a.categories, indent, out) + out.write(",") + out.write(""""prices":""") + Defaulted.jsonEncoder(JsonEncoder.option(using JsonEncoder.array[BigDecimal](using JsonEncoder.scalaBigDecimal, implicitly))).unsafeEncode(a.prices, indent, out) + out.write(",") + out.write(""""attributes":""") + Defaulted.jsonEncoder(JsonEncoder.option(using JsonEncoder.array[TypoJsonb](using TypoJsonb.jsonEncoder, implicitly))).unsafeEncode(a.attributes, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[ProductRowUnsaved] = Text.instance[ProductRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.bigDecimalInstance.unsafeEncode(row.price, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.lastRestocked, sb) + sb.append(Text.DELIMETER) + Defaulted.text(ProductId.text).unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text.booleanInstance)).unsafeEncode(row.inStock, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text.intInstance)).unsafeEncode(row.quantity, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(TypoLocalDateTime.text)).unsafeEncode(row.lastModified, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text[Array[String]])).unsafeEncode(row.tags, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text[Array[Int]])).unsafeEncode(row.categories, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text[Array[BigDecimal]])).unsafeEncode(row.prices, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text.iterableInstance[Array, TypoJsonb](TypoJsonb.text, implicitly))).unsafeEncode(row.attributes, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryFields.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryFields.scala new file mode 100644 index 0000000000..bfbb0997fc --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryFields.scala @@ -0,0 +1,61 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product_category + +import adventureworks.frontpage.category.CategoryFields +import adventureworks.frontpage.category.CategoryId +import adventureworks.frontpage.category.CategoryRow +import adventureworks.frontpage.product.ProductFields +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.product.ProductRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.Required +import typo.dsl.SqlExpr +import typo.dsl.SqlExpr.CompositeIn +import typo.dsl.SqlExpr.CompositeIn.TuplePart +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.Structure.Relation + +trait ProductCategoryFields { + def productId: IdField[ProductId, ProductCategoryRow] + def categoryId: IdField[CategoryId, ProductCategoryRow] + def fkCategory: ForeignKey[CategoryFields, CategoryRow] = + ForeignKey[CategoryFields, CategoryRow]("frontpage.product_category_category_id_fkey", Nil) + .withColumnPair(categoryId, _.id) + def fkProduct: ForeignKey[ProductFields, ProductRow] = + ForeignKey[ProductFields, ProductRow]("frontpage.product_category_product_id_fkey", Nil) + .withColumnPair(productId, _.id) + def compositeIdIs(compositeId: ProductCategoryId): SqlExpr[Boolean, Required] = + productId.isEqual(compositeId.productId).and(categoryId.isEqual(compositeId.categoryId)) + def compositeIdIn(compositeIds: Array[ProductCategoryId]): SqlExpr[Boolean, Required] = + new CompositeIn(compositeIds)(TuplePart(productId)(_.productId), TuplePart(categoryId)(_.categoryId)) + +} + +object ProductCategoryFields { + lazy val structure: Relation[ProductCategoryFields, ProductCategoryRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[ProductCategoryFields, ProductCategoryRow] { + + override lazy val fields: ProductCategoryFields = new ProductCategoryFields { + override def productId = IdField[ProductId, ProductCategoryRow](_path, "product_id", None, Some("uuid"), x => x.productId, (row, value) => row.copy(productId = value)) + override def categoryId = IdField[CategoryId, ProductCategoryRow](_path, "category_id", None, Some("uuid"), x => x.categoryId, (row, value) => row.copy(categoryId = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, ProductCategoryRow]] = + List[FieldLikeNoHkt[?, ProductCategoryRow]](fields.productId, fields.categoryId) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryId.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryId.scala new file mode 100644 index 0000000000..199bbbee6c --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryId.scala @@ -0,0 +1,42 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product_category + +import adventureworks.frontpage.category.CategoryId +import adventureworks.frontpage.product.ProductId +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** Type for the composite primary key of table `frontpage.product_category` */ +case class ProductCategoryId( + productId: ProductId, + categoryId: CategoryId +) +object ProductCategoryId { + implicit lazy val jsonDecoder: JsonDecoder[ProductCategoryId] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val productId = jsonObj.get("product_id").toRight("Missing field 'product_id'").flatMap(_.as(ProductId.jsonDecoder)) + val categoryId = jsonObj.get("category_id").toRight("Missing field 'category_id'").flatMap(_.as(CategoryId.jsonDecoder)) + if (productId.isRight && categoryId.isRight) + Right(ProductCategoryId(productId = productId.toOption.get, categoryId = categoryId.toOption.get)) + else Left(List[Either[String, Any]](productId, categoryId).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[ProductCategoryId] = new JsonEncoder[ProductCategoryId] { + override def unsafeEncode(a: ProductCategoryId, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""product_id":""") + ProductId.jsonEncoder.unsafeEncode(a.productId, indent, out) + out.write(",") + out.write(""""category_id":""") + CategoryId.jsonEncoder.unsafeEncode(a.categoryId, indent, out) + out.write("}") + } + } + implicit lazy val ordering: Ordering[ProductCategoryId] = Ordering.by(x => (x.productId, x.categoryId)) +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepo.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepo.scala new file mode 100644 index 0000000000..f1da38710b --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepo.scala @@ -0,0 +1,34 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product_category + +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +trait ProductCategoryRepo { + def delete: DeleteBuilder[ProductCategoryFields, ProductCategoryRow] + def deleteById(compositeId: ProductCategoryId): ZIO[ZConnection, Throwable, Boolean] + def deleteByIds(compositeIds: Array[ProductCategoryId]): ZIO[ZConnection, Throwable, Long] + def insert(unsaved: ProductCategoryRow): ZIO[ZConnection, Throwable, ProductCategoryRow] + def insertStreaming(unsaved: ZStream[ZConnection, Throwable, ProductCategoryRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + def select: SelectBuilder[ProductCategoryFields, ProductCategoryRow] + def selectAll: ZStream[ZConnection, Throwable, ProductCategoryRow] + def selectById(compositeId: ProductCategoryId): ZIO[ZConnection, Throwable, Option[ProductCategoryRow]] + def selectByIds(compositeIds: Array[ProductCategoryId]): ZStream[ZConnection, Throwable, ProductCategoryRow] + def selectByIdsTracked(compositeIds: Array[ProductCategoryId]): ZIO[ZConnection, Throwable, Map[ProductCategoryId, ProductCategoryRow]] + def update: UpdateBuilder[ProductCategoryFields, ProductCategoryRow] + def upsert(unsaved: ProductCategoryRow): ZIO[ZConnection, Throwable, UpdateResult[ProductCategoryRow]] + // Not implementable for zio-jdbc: upsertBatch + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, ProductCategoryRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepoImpl.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepoImpl.scala new file mode 100644 index 0000000000..0fbe1986dc --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepoImpl.scala @@ -0,0 +1,99 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product_category + +import adventureworks.frontpage.category.CategoryId +import adventureworks.frontpage.product.ProductId +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.SqlFragment.Segment +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.jdbc.sqlInterpolator +import zio.stream.ZStream + +class ProductCategoryRepoImpl extends ProductCategoryRepo { + override def delete: DeleteBuilder[ProductCategoryFields, ProductCategoryRow] = { + DeleteBuilder(""""frontpage"."product_category"""", ProductCategoryFields.structure) + } + override def deleteById(compositeId: ProductCategoryId): ZIO[ZConnection, Throwable, Boolean] = { + sql"""delete from "frontpage"."product_category" where "product_id" = ${Segment.paramSegment(compositeId.productId)(ProductId.setter)} AND "category_id" = ${Segment.paramSegment(compositeId.categoryId)(CategoryId.setter)}""".delete.map(_ > 0) + } + override def deleteByIds(compositeIds: Array[ProductCategoryId]): ZIO[ZConnection, Throwable, Long] = { + val productId = compositeIds.map(_.productId) + val categoryId = compositeIds.map(_.categoryId) + sql"""delete + from "frontpage"."product_category" + where ("product_id", "category_id") + in (select unnest(${productId}), unnest(${categoryId})) + """.delete + + } + override def insert(unsaved: ProductCategoryRow): ZIO[ZConnection, Throwable, ProductCategoryRow] = { + sql"""insert into "frontpage"."product_category"("product_id", "category_id") + values (${Segment.paramSegment(unsaved.productId)(ProductId.setter)}::uuid, ${Segment.paramSegment(unsaved.categoryId)(CategoryId.setter)}::uuid) + returning "product_id", "category_id" + """.insertReturning(using ProductCategoryRow.jdbcDecoder).map(_.updatedKeys.head) + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, ProductCategoryRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."product_category"("product_id", "category_id") FROM STDIN""", batchSize, unsaved)(ProductCategoryRow.text) + } + override def select: SelectBuilder[ProductCategoryFields, ProductCategoryRow] = { + SelectBuilderSql(""""frontpage"."product_category"""", ProductCategoryFields.structure, ProductCategoryRow.jdbcDecoder) + } + override def selectAll: ZStream[ZConnection, Throwable, ProductCategoryRow] = { + sql"""select "product_id", "category_id" from "frontpage"."product_category"""".query(using ProductCategoryRow.jdbcDecoder).selectStream() + } + override def selectById(compositeId: ProductCategoryId): ZIO[ZConnection, Throwable, Option[ProductCategoryRow]] = { + sql"""select "product_id", "category_id" from "frontpage"."product_category" where "product_id" = ${Segment.paramSegment(compositeId.productId)(ProductId.setter)} AND "category_id" = ${Segment.paramSegment(compositeId.categoryId)(CategoryId.setter)}""".query(using ProductCategoryRow.jdbcDecoder).selectOne + } + override def selectByIds(compositeIds: Array[ProductCategoryId]): ZStream[ZConnection, Throwable, ProductCategoryRow] = { + val productId = compositeIds.map(_.productId) + val categoryId = compositeIds.map(_.categoryId) + sql"""select "product_id", "category_id" + from "frontpage"."product_category" + where ("product_id", "category_id") + in (select unnest(${productId}), unnest(${categoryId})) + """.query(using ProductCategoryRow.jdbcDecoder).selectStream() + + } + override def selectByIdsTracked(compositeIds: Array[ProductCategoryId]): ZIO[ZConnection, Throwable, Map[ProductCategoryId, ProductCategoryRow]] = { + selectByIds(compositeIds).runCollect.map { rows => + val byId = rows.view.map(x => (x.compositeId, x)).toMap + compositeIds.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[ProductCategoryFields, ProductCategoryRow] = { + UpdateBuilder(""""frontpage"."product_category"""", ProductCategoryFields.structure, ProductCategoryRow.jdbcDecoder) + } + override def upsert(unsaved: ProductCategoryRow): ZIO[ZConnection, Throwable, UpdateResult[ProductCategoryRow]] = { + sql"""insert into "frontpage"."product_category"("product_id", "category_id") + values ( + ${Segment.paramSegment(unsaved.productId)(ProductId.setter)}::uuid, + ${Segment.paramSegment(unsaved.categoryId)(CategoryId.setter)}::uuid + ) + on conflict ("product_id", "category_id") + do update set "product_id" = EXCLUDED."product_id" + returning "product_id", "category_id"""".insertReturning(using ProductCategoryRow.jdbcDecoder) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, ProductCategoryRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + val created = sql"""create temporary table product_category_TEMP (like "frontpage"."product_category") on commit drop""".execute + val copied = streamingInsert(s"""copy product_category_TEMP("product_id", "category_id") from stdin""", batchSize, unsaved)(ProductCategoryRow.text) + val merged = sql"""insert into "frontpage"."product_category"("product_id", "category_id") + select * from product_category_TEMP + on conflict ("product_id", "category_id") + do nothing + ; + drop table product_category_TEMP;""".update + created *> copied *> merged + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepoMock.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepoMock.scala new file mode 100644 index 0000000000..74fbb61bb1 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRepoMock.scala @@ -0,0 +1,91 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product_category + +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams +import zio.Chunk +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +class ProductCategoryRepoMock(map: scala.collection.mutable.Map[ProductCategoryId, ProductCategoryRow] = scala.collection.mutable.Map.empty) extends ProductCategoryRepo { + override def delete: DeleteBuilder[ProductCategoryFields, ProductCategoryRow] = { + DeleteBuilderMock(DeleteParams.empty, ProductCategoryFields.structure, map) + } + override def deleteById(compositeId: ProductCategoryId): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed(map.remove(compositeId).isDefined) + } + override def deleteByIds(compositeIds: Array[ProductCategoryId]): ZIO[ZConnection, Throwable, Long] = { + ZIO.succeed(compositeIds.map(id => map.remove(id)).count(_.isDefined).toLong) + } + override def insert(unsaved: ProductCategoryRow): ZIO[ZConnection, Throwable, ProductCategoryRow] = { + ZIO.succeed { + val _ = + if (map.contains(unsaved.compositeId)) + sys.error(s"id ${unsaved.compositeId} already exists") + else + map.put(unsaved.compositeId, unsaved) + + unsaved + } + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, ProductCategoryRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.compositeId -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + override def select: SelectBuilder[ProductCategoryFields, ProductCategoryRow] = { + SelectBuilderMock(ProductCategoryFields.structure, ZIO.succeed(Chunk.fromIterable(map.values)), SelectParams.empty) + } + override def selectAll: ZStream[ZConnection, Throwable, ProductCategoryRow] = { + ZStream.fromIterable(map.values) + } + override def selectById(compositeId: ProductCategoryId): ZIO[ZConnection, Throwable, Option[ProductCategoryRow]] = { + ZIO.succeed(map.get(compositeId)) + } + override def selectByIds(compositeIds: Array[ProductCategoryId]): ZStream[ZConnection, Throwable, ProductCategoryRow] = { + ZStream.fromIterable(compositeIds.flatMap(map.get)) + } + override def selectByIdsTracked(compositeIds: Array[ProductCategoryId]): ZIO[ZConnection, Throwable, Map[ProductCategoryId, ProductCategoryRow]] = { + selectByIds(compositeIds).runCollect.map { rows => + val byId = rows.view.map(x => (x.compositeId, x)).toMap + compositeIds.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[ProductCategoryFields, ProductCategoryRow] = { + UpdateBuilderMock(UpdateParams.empty, ProductCategoryFields.structure, map) + } + override def upsert(unsaved: ProductCategoryRow): ZIO[ZConnection, Throwable, UpdateResult[ProductCategoryRow]] = { + ZIO.succeed { + map.put(unsaved.compositeId, unsaved): @nowarn + UpdateResult(1, Chunk.single(unsaved)) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, ProductCategoryRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.compositeId -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRow.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRow.scala new file mode 100644 index 0000000000..707646a160 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/product_category/ProductCategoryRow.scala @@ -0,0 +1,65 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package product_category + +import adventureworks.frontpage.category.CategoryId +import adventureworks.frontpage.product.ProductId +import java.sql.ResultSet +import zio.jdbc.JdbcDecoder +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** Table: frontpage.product_category + Composite primary key: product_id, category_id */ +case class ProductCategoryRow( + /** Points to [[product.ProductRow.id]] */ + productId: ProductId, + /** Points to [[category.CategoryRow.id]] */ + categoryId: CategoryId +){ + val compositeId: ProductCategoryId = ProductCategoryId(productId, categoryId) + val id = compositeId + } + +object ProductCategoryRow { + def apply(compositeId: ProductCategoryId) = + new ProductCategoryRow(compositeId.productId, compositeId.categoryId) + implicit lazy val jdbcDecoder: JdbcDecoder[ProductCategoryRow] = new JdbcDecoder[ProductCategoryRow] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, ProductCategoryRow) = + columIndex + 1 -> + ProductCategoryRow( + productId = ProductId.jdbcDecoder.unsafeDecode(columIndex + 0, rs)._2, + categoryId = CategoryId.jdbcDecoder.unsafeDecode(columIndex + 1, rs)._2 + ) + } + implicit lazy val jsonDecoder: JsonDecoder[ProductCategoryRow] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val productId = jsonObj.get("product_id").toRight("Missing field 'product_id'").flatMap(_.as(ProductId.jsonDecoder)) + val categoryId = jsonObj.get("category_id").toRight("Missing field 'category_id'").flatMap(_.as(CategoryId.jsonDecoder)) + if (productId.isRight && categoryId.isRight) + Right(ProductCategoryRow(productId = productId.toOption.get, categoryId = categoryId.toOption.get)) + else Left(List[Either[String, Any]](productId, categoryId).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[ProductCategoryRow] = new JsonEncoder[ProductCategoryRow] { + override def unsafeEncode(a: ProductCategoryRow, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""product_id":""") + ProductId.jsonEncoder.unsafeEncode(a.productId, indent, out) + out.write(",") + out.write(""""category_id":""") + CategoryId.jsonEncoder.unsafeEncode(a.categoryId, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[ProductCategoryRow] = Text.instance[ProductCategoryRow]{ (row, sb) => + ProductId.text.unsafeEncode(row.productId, sb) + sb.append(Text.DELIMETER) + CategoryId.text.unsafeEncode(row.categoryId, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleFields.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleFields.scala new file mode 100644 index 0000000000..5eb3c827f7 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleFields.scala @@ -0,0 +1,40 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.Structure.Relation + +trait RoleFields { + def id: IdField[RoleId, RoleRow] + def name: Field[String, RoleRow] +} + +object RoleFields { + lazy val structure: Relation[RoleFields, RoleRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[RoleFields, RoleRow] { + + override lazy val fields: RoleFields = new RoleFields { + override def id = IdField[RoleId, RoleRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def name = Field[String, RoleRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, RoleRow]] = + List[FieldLikeNoHkt[?, RoleRow]](fields.id, fields.name) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleId.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleId.scala new file mode 100644 index 0000000000..dc69244713 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import adventureworks.customtypes.TypoUUID +import typo.dsl.Bijection +import typo.dsl.PGType +import zio.jdbc.JdbcDecoder +import zio.jdbc.JdbcEncoder +import zio.jdbc.SqlFragment.Setter +import zio.json.JsonDecoder +import zio.json.JsonEncoder + +/** Type for the primary key of table `frontpage.role` */ +case class RoleId(value: TypoUUID) extends AnyVal +object RoleId { + implicit lazy val arrayJdbcDecoder: JdbcDecoder[Array[RoleId]] = JdbcDecoder[Array[TypoUUID]].map(_.map(RoleId.apply)) + implicit lazy val arrayJdbcEncoder: JdbcEncoder[Array[RoleId]] = JdbcEncoder[Array[TypoUUID]].contramap(_.map(_.value)) + implicit lazy val arraySetter: Setter[Array[RoleId]] = TypoUUID.arraySetter.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[RoleId, TypoUUID] = Bijection[RoleId, TypoUUID](_.value)(RoleId.apply) + implicit lazy val jdbcDecoder: JdbcDecoder[RoleId] = TypoUUID.jdbcDecoder.map(RoleId.apply) + implicit lazy val jdbcEncoder: JdbcEncoder[RoleId] = TypoUUID.jdbcEncoder.contramap(_.value) + implicit lazy val jsonDecoder: JsonDecoder[RoleId] = TypoUUID.jsonDecoder.map(RoleId.apply) + implicit lazy val jsonEncoder: JsonEncoder[RoleId] = TypoUUID.jsonEncoder.contramap(_.value) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[RoleId] = Ordering.by(_.value) + implicit lazy val pgType: PGType[RoleId] = TypoUUID.pgType.as + implicit lazy val setter: Setter[RoleId] = TypoUUID.setter.contramap(_.value) + implicit lazy val text: Text[RoleId] = new Text[RoleId] { + override def unsafeEncode(v: RoleId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: RoleId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleRepo.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleRepo.scala new file mode 100644 index 0000000000..d5a50b9f50 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleRepo.scala @@ -0,0 +1,39 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +trait RoleRepo { + def delete: DeleteBuilder[RoleFields, RoleRow] + def deleteById(id: RoleId): ZIO[ZConnection, Throwable, Boolean] + def deleteByIds(ids: Array[RoleId]): ZIO[ZConnection, Throwable, Long] + def insert(unsaved: RoleRow): ZIO[ZConnection, Throwable, RoleRow] + def insert(unsaved: RoleRowUnsaved): ZIO[ZConnection, Throwable, RoleRow] + def insertStreaming(unsaved: ZStream[ZConnection, Throwable, RoleRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, RoleRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + def select: SelectBuilder[RoleFields, RoleRow] + def selectAll: ZStream[ZConnection, Throwable, RoleRow] + def selectById(id: RoleId): ZIO[ZConnection, Throwable, Option[RoleRow]] + def selectByIds(ids: Array[RoleId]): ZStream[ZConnection, Throwable, RoleRow] + def selectByIdsTracked(ids: Array[RoleId]): ZIO[ZConnection, Throwable, Map[RoleId, RoleRow]] + def selectByUniqueName(name: String): ZIO[ZConnection, Throwable, Option[RoleRow]] + def update: UpdateBuilder[RoleFields, RoleRow] + def update(row: RoleRow): ZIO[ZConnection, Throwable, Boolean] + def upsert(unsaved: RoleRow): ZIO[ZConnection, Throwable, UpdateResult[RoleRow]] + // Not implementable for zio-jdbc: upsertBatch + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, RoleRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleRepoImpl.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleRepoImpl.scala new file mode 100644 index 0000000000..5ce704793b --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleRepoImpl.scala @@ -0,0 +1,125 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import adventureworks.customtypes.Defaulted +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.SqlFragment +import zio.jdbc.SqlFragment.Segment +import zio.jdbc.SqlFragment.Setter +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.jdbc.sqlInterpolator +import zio.stream.ZStream + +class RoleRepoImpl extends RoleRepo { + override def delete: DeleteBuilder[RoleFields, RoleRow] = { + DeleteBuilder(""""frontpage"."role"""", RoleFields.structure) + } + override def deleteById(id: RoleId): ZIO[ZConnection, Throwable, Boolean] = { + sql"""delete from "frontpage"."role" where "id" = ${Segment.paramSegment(id)(RoleId.setter)}""".delete.map(_ > 0) + } + override def deleteByIds(ids: Array[RoleId]): ZIO[ZConnection, Throwable, Long] = { + sql"""delete from "frontpage"."role" where "id" = ANY(${ids})""".delete + } + override def insert(unsaved: RoleRow): ZIO[ZConnection, Throwable, RoleRow] = { + sql"""insert into "frontpage"."role"("id", "name") + values (${Segment.paramSegment(unsaved.id)(RoleId.setter)}::uuid, ${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}) + returning "id", "name" + """.insertReturning(using RoleRow.jdbcDecoder).map(_.updatedKeys.head) + } + override def insert(unsaved: RoleRowUnsaved): ZIO[ZConnection, Throwable, RoleRow] = { + val fs = List( + Some((sql""""name"""", sql"${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""id"""", sql"${Segment.paramSegment(value: RoleId)(RoleId.setter)}::uuid")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."role" default values + returning "id", "name" + """ + } else { + val names = fs.map { case (n, _) => n }.mkFragment(SqlFragment(", ")) + val values = fs.map { case (_, f) => f }.mkFragment(SqlFragment(", ")) + sql"""insert into "frontpage"."role"($names) values ($values) returning "id", "name"""" + } + q.insertReturning(using RoleRow.jdbcDecoder).map(_.updatedKeys.head) + + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, RoleRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."role"("id", "name") FROM STDIN""", batchSize, unsaved)(RoleRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, RoleRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."role"("name", "id") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(RoleRowUnsaved.text) + } + override def select: SelectBuilder[RoleFields, RoleRow] = { + SelectBuilderSql(""""frontpage"."role"""", RoleFields.structure, RoleRow.jdbcDecoder) + } + override def selectAll: ZStream[ZConnection, Throwable, RoleRow] = { + sql"""select "id", "name" from "frontpage"."role"""".query(using RoleRow.jdbcDecoder).selectStream() + } + override def selectById(id: RoleId): ZIO[ZConnection, Throwable, Option[RoleRow]] = { + sql"""select "id", "name" from "frontpage"."role" where "id" = ${Segment.paramSegment(id)(RoleId.setter)}""".query(using RoleRow.jdbcDecoder).selectOne + } + override def selectByIds(ids: Array[RoleId]): ZStream[ZConnection, Throwable, RoleRow] = { + sql"""select "id", "name" from "frontpage"."role" where "id" = ANY(${Segment.paramSegment(ids)(RoleId.arraySetter)})""".query(using RoleRow.jdbcDecoder).selectStream() + } + override def selectByIdsTracked(ids: Array[RoleId]): ZIO[ZConnection, Throwable, Map[RoleId, RoleRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def selectByUniqueName(name: String): ZIO[ZConnection, Throwable, Option[RoleRow]] = { + sql"""select "id", "name" + from "frontpage"."role" + where "name" = ${Segment.paramSegment(name)(Setter.stringSetter)} + """.query(using RoleRow.jdbcDecoder).selectOne + } + override def update: UpdateBuilder[RoleFields, RoleRow] = { + UpdateBuilder(""""frontpage"."role"""", RoleFields.structure, RoleRow.jdbcDecoder) + } + override def update(row: RoleRow): ZIO[ZConnection, Throwable, Boolean] = { + val id = row.id + sql"""update "frontpage"."role" + set "name" = ${Segment.paramSegment(row.name)(Setter.stringSetter)} + where "id" = ${Segment.paramSegment(id)(RoleId.setter)}""".update.map(_ > 0) + } + override def upsert(unsaved: RoleRow): ZIO[ZConnection, Throwable, UpdateResult[RoleRow]] = { + sql"""insert into "frontpage"."role"("id", "name") + values ( + ${Segment.paramSegment(unsaved.id)(RoleId.setter)}::uuid, + ${Segment.paramSegment(unsaved.name)(Setter.stringSetter)} + ) + on conflict ("id") + do update set + "name" = EXCLUDED."name" + returning "id", "name"""".insertReturning(using RoleRow.jdbcDecoder) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, RoleRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + val created = sql"""create temporary table role_TEMP (like "frontpage"."role") on commit drop""".execute + val copied = streamingInsert(s"""copy role_TEMP("id", "name") from stdin""", batchSize, unsaved)(RoleRow.text) + val merged = sql"""insert into "frontpage"."role"("id", "name") + select * from role_TEMP + on conflict ("id") + do update set + "name" = EXCLUDED."name" + ; + drop table role_TEMP;""".update + created *> copied *> merged + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleRepoMock.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleRepoMock.scala new file mode 100644 index 0000000000..b85b33c01f --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleRepoMock.scala @@ -0,0 +1,119 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams +import zio.Chunk +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +class RoleRepoMock(toRow: Function1[RoleRowUnsaved, RoleRow], + map: scala.collection.mutable.Map[RoleId, RoleRow] = scala.collection.mutable.Map.empty) extends RoleRepo { + override def delete: DeleteBuilder[RoleFields, RoleRow] = { + DeleteBuilderMock(DeleteParams.empty, RoleFields.structure, map) + } + override def deleteById(id: RoleId): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[RoleId]): ZIO[ZConnection, Throwable, Long] = { + ZIO.succeed(ids.map(id => map.remove(id)).count(_.isDefined).toLong) + } + override def insert(unsaved: RoleRow): ZIO[ZConnection, Throwable, RoleRow] = { + ZIO.succeed { + val _ = + if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: RoleRowUnsaved): ZIO[ZConnection, Throwable, RoleRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, RoleRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, RoleRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, unsavedRow) => + ZIO.succeed { + val row = toRow(unsavedRow) + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + override def select: SelectBuilder[RoleFields, RoleRow] = { + SelectBuilderMock(RoleFields.structure, ZIO.succeed(Chunk.fromIterable(map.values)), SelectParams.empty) + } + override def selectAll: ZStream[ZConnection, Throwable, RoleRow] = { + ZStream.fromIterable(map.values) + } + override def selectById(id: RoleId): ZIO[ZConnection, Throwable, Option[RoleRow]] = { + ZIO.succeed(map.get(id)) + } + override def selectByIds(ids: Array[RoleId]): ZStream[ZConnection, Throwable, RoleRow] = { + ZStream.fromIterable(ids.flatMap(map.get)) + } + override def selectByIdsTracked(ids: Array[RoleId]): ZIO[ZConnection, Throwable, Map[RoleId, RoleRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def selectByUniqueName(name: String): ZIO[ZConnection, Throwable, Option[RoleRow]] = { + ZIO.succeed(map.values.find(v => name == v.name)) + } + override def update: UpdateBuilder[RoleFields, RoleRow] = { + UpdateBuilderMock(UpdateParams.empty, RoleFields.structure, map) + } + override def update(row: RoleRow): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: RoleRow): ZIO[ZConnection, Throwable, UpdateResult[RoleRow]] = { + ZIO.succeed { + map.put(unsaved.id, unsaved): @nowarn + UpdateResult(1, Chunk.single(unsaved)) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, RoleRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleRow.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleRow.scala new file mode 100644 index 0000000000..fcc1149fe6 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleRow.scala @@ -0,0 +1,61 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import adventureworks.customtypes.Defaulted +import java.sql.ResultSet +import zio.jdbc.JdbcDecoder +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** Table: frontpage.role + Primary key: id */ +case class RoleRow( + /** Default: gen_random_uuid() */ + id: RoleId, + name: String +){ + def toUnsavedRow(id: Defaulted[RoleId]): RoleRowUnsaved = + RoleRowUnsaved(name, id) + } + +object RoleRow { + implicit lazy val jdbcDecoder: JdbcDecoder[RoleRow] = new JdbcDecoder[RoleRow] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, RoleRow) = + columIndex + 1 -> + RoleRow( + id = RoleId.jdbcDecoder.unsafeDecode(columIndex + 0, rs)._2, + name = JdbcDecoder.stringDecoder.unsafeDecode(columIndex + 1, rs)._2 + ) + } + implicit lazy val jsonDecoder: JsonDecoder[RoleRow] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(RoleId.jsonDecoder)) + val name = jsonObj.get("name").toRight("Missing field 'name'").flatMap(_.as(JsonDecoder.string)) + if (id.isRight && name.isRight) + Right(RoleRow(id = id.toOption.get, name = name.toOption.get)) + else Left(List[Either[String, Any]](id, name).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[RoleRow] = new JsonEncoder[RoleRow] { + override def unsafeEncode(a: RoleRow, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""id":""") + RoleId.jsonEncoder.unsafeEncode(a.id, indent, out) + out.write(",") + out.write(""""name":""") + JsonEncoder.string.unsafeEncode(a.name, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[RoleRow] = Text.instance[RoleRow]{ (row, sb) => + RoleId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleRowUnsaved.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleRowUnsaved.scala new file mode 100644 index 0000000000..6d6415b588 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/role/RoleRowUnsaved.scala @@ -0,0 +1,55 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package role + +import adventureworks.customtypes.Defaulted +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** This class corresponds to a row in table `frontpage.role` which has not been persisted yet */ +case class RoleRowUnsaved( + name: String, + /** Default: gen_random_uuid() */ + id: Defaulted[RoleId] = Defaulted.UseDefault +) { + def toRow(idDefault: => RoleId): RoleRow = + RoleRow( + name = name, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + } + ) +} +object RoleRowUnsaved { + implicit lazy val jsonDecoder: JsonDecoder[RoleRowUnsaved] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val name = jsonObj.get("name").toRight("Missing field 'name'").flatMap(_.as(JsonDecoder.string)) + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(Defaulted.jsonDecoder(RoleId.jsonDecoder))) + if (name.isRight && id.isRight) + Right(RoleRowUnsaved(name = name.toOption.get, id = id.toOption.get)) + else Left(List[Either[String, Any]](name, id).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[RoleRowUnsaved] = new JsonEncoder[RoleRowUnsaved] { + override def unsafeEncode(a: RoleRowUnsaved, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""name":""") + JsonEncoder.string.unsafeEncode(a.name, indent, out) + out.write(",") + out.write(""""id":""") + Defaulted.jsonEncoder(RoleId.jsonEncoder).unsafeEncode(a.id, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[RoleRowUnsaved] = Text.instance[RoleRowUnsaved]{ (row, sb) => + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Defaulted.text(RoleId.text).unsafeEncode(row.id, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserFields.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserFields.scala new file mode 100644 index 0000000000..032b7e96b1 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserFields.scala @@ -0,0 +1,66 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.department.DepartmentFields +import adventureworks.frontpage.department.DepartmentId +import adventureworks.frontpage.department.DepartmentRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.SqlExpr.Field +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait UserFields { + def id: IdField[UserId, UserRow] + def email: Field[Email, UserRow] + def name: Field[String, UserRow] + def createdAt: OptField[TypoLocalDateTime, UserRow] + def departmentId: OptField[DepartmentId, UserRow] + def status: OptField[UserStatus, UserRow] + def verified: OptField[Boolean, UserRow] + def managerId: OptField[UserId, UserRow] + def role: OptField[UserRole, UserRow] + def fkDepartment: ForeignKey[DepartmentFields, DepartmentRow] = + ForeignKey[DepartmentFields, DepartmentRow]("frontpage.user_department_id_fkey", Nil) + .withColumnPair(departmentId, _.id) + def fkUser: ForeignKey[UserFields, UserRow] = + ForeignKey[UserFields, UserRow]("frontpage.user_manager_id_fkey", Nil) + .withColumnPair(managerId, _.id) +} + +object UserFields { + lazy val structure: Relation[UserFields, UserRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[UserFields, UserRow] { + + override lazy val fields: UserFields = new UserFields { + override def id = IdField[UserId, UserRow](_path, "id", None, Some("uuid"), x => x.id, (row, value) => row.copy(id = value)) + override def email = Field[Email, UserRow](_path, "email", None, Some("text"), x => x.email, (row, value) => row.copy(email = value)) + override def name = Field[String, UserRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + override def createdAt = OptField[TypoLocalDateTime, UserRow](_path, "created_at", Some("text"), Some("timestamp"), x => x.createdAt, (row, value) => row.copy(createdAt = value)) + override def departmentId = OptField[DepartmentId, UserRow](_path, "department_id", None, Some("uuid"), x => x.departmentId, (row, value) => row.copy(departmentId = value)) + override def status = OptField[UserStatus, UserRow](_path, "status", None, Some("frontpage.user_status"), x => x.status, (row, value) => row.copy(status = value)) + override def verified = OptField[Boolean, UserRow](_path, "verified", None, None, x => x.verified, (row, value) => row.copy(verified = value)) + override def managerId = OptField[UserId, UserRow](_path, "manager_id", None, Some("uuid"), x => x.managerId, (row, value) => row.copy(managerId = value)) + override def role = OptField[UserRole, UserRow](_path, "role", None, Some("frontpage.user_role"), x => x.role, (row, value) => row.copy(role = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, UserRow]] = + List[FieldLikeNoHkt[?, UserRow]](fields.id, fields.email, fields.name, fields.createdAt, fields.departmentId, fields.status, fields.verified, fields.managerId, fields.role) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserId.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserId.scala new file mode 100644 index 0000000000..75e2c31245 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserId.scala @@ -0,0 +1,37 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import adventureworks.customtypes.TypoUUID +import typo.dsl.Bijection +import typo.dsl.PGType +import zio.jdbc.JdbcDecoder +import zio.jdbc.JdbcEncoder +import zio.jdbc.SqlFragment.Setter +import zio.json.JsonDecoder +import zio.json.JsonEncoder + +/** Type for the primary key of table `frontpage.user` */ +case class UserId(value: TypoUUID) extends AnyVal +object UserId { + implicit lazy val arrayJdbcDecoder: JdbcDecoder[Array[UserId]] = JdbcDecoder[Array[TypoUUID]].map(_.map(UserId.apply)) + implicit lazy val arrayJdbcEncoder: JdbcEncoder[Array[UserId]] = JdbcEncoder[Array[TypoUUID]].contramap(_.map(_.value)) + implicit lazy val arraySetter: Setter[Array[UserId]] = TypoUUID.arraySetter.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[UserId, TypoUUID] = Bijection[UserId, TypoUUID](_.value)(UserId.apply) + implicit lazy val jdbcDecoder: JdbcDecoder[UserId] = TypoUUID.jdbcDecoder.map(UserId.apply) + implicit lazy val jdbcEncoder: JdbcEncoder[UserId] = TypoUUID.jdbcEncoder.contramap(_.value) + implicit lazy val jsonDecoder: JsonDecoder[UserId] = TypoUUID.jsonDecoder.map(UserId.apply) + implicit lazy val jsonEncoder: JsonEncoder[UserId] = TypoUUID.jsonEncoder.contramap(_.value) + implicit def ordering(implicit O0: Ordering[TypoUUID]): Ordering[UserId] = Ordering.by(_.value) + implicit lazy val pgType: PGType[UserId] = TypoUUID.pgType.as + implicit lazy val setter: Setter[UserId] = TypoUUID.setter.contramap(_.value) + implicit lazy val text: Text[UserId] = new Text[UserId] { + override def unsafeEncode(v: UserId, sb: StringBuilder) = TypoUUID.text.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: UserId, sb: StringBuilder) = TypoUUID.text.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserRepo.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserRepo.scala new file mode 100644 index 0000000000..124c8d89a5 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserRepo.scala @@ -0,0 +1,39 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +trait UserRepo { + def delete: DeleteBuilder[UserFields, UserRow] + def deleteById(id: UserId): ZIO[ZConnection, Throwable, Boolean] + def deleteByIds(ids: Array[UserId]): ZIO[ZConnection, Throwable, Long] + def insert(unsaved: UserRow): ZIO[ZConnection, Throwable, UserRow] + def insert(unsaved: UserRowUnsaved): ZIO[ZConnection, Throwable, UserRow] + def insertStreaming(unsaved: ZStream[ZConnection, Throwable, UserRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, UserRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + def select: SelectBuilder[UserFields, UserRow] + def selectAll: ZStream[ZConnection, Throwable, UserRow] + def selectById(id: UserId): ZIO[ZConnection, Throwable, Option[UserRow]] + def selectByIds(ids: Array[UserId]): ZStream[ZConnection, Throwable, UserRow] + def selectByIdsTracked(ids: Array[UserId]): ZIO[ZConnection, Throwable, Map[UserId, UserRow]] + def selectByUniqueEmail(email: Email): ZIO[ZConnection, Throwable, Option[UserRow]] + def update: UpdateBuilder[UserFields, UserRow] + def update(row: UserRow): ZIO[ZConnection, Throwable, Boolean] + def upsert(unsaved: UserRow): ZIO[ZConnection, Throwable, UpdateResult[UserRow]] + // Not implementable for zio-jdbc: upsertBatch + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, UserRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserRepoImpl.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserRepoImpl.scala new file mode 100644 index 0000000000..4e3917474e --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserRepoImpl.scala @@ -0,0 +1,174 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.department.DepartmentId +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.SqlFragment +import zio.jdbc.SqlFragment.Segment +import zio.jdbc.SqlFragment.Setter +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.jdbc.sqlInterpolator +import zio.stream.ZStream + +class UserRepoImpl extends UserRepo { + override def delete: DeleteBuilder[UserFields, UserRow] = { + DeleteBuilder(""""frontpage"."user"""", UserFields.structure) + } + override def deleteById(id: UserId): ZIO[ZConnection, Throwable, Boolean] = { + sql"""delete from "frontpage"."user" where "id" = ${Segment.paramSegment(id)(UserId.setter)}""".delete.map(_ > 0) + } + override def deleteByIds(ids: Array[UserId]): ZIO[ZConnection, Throwable, Long] = { + sql"""delete from "frontpage"."user" where "id" = ANY(${ids})""".delete + } + override def insert(unsaved: UserRow): ZIO[ZConnection, Throwable, UserRow] = { + sql"""insert into "frontpage"."user"("id", "email", "name", "created_at", "department_id", "status", "verified", "manager_id", "role") + values (${Segment.paramSegment(unsaved.id)(UserId.setter)}::uuid, ${Segment.paramSegment(unsaved.email)(Email.setter)}::text, ${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}, ${Segment.paramSegment(unsaved.createdAt)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp, ${Segment.paramSegment(unsaved.departmentId)(Setter.optionParamSetter(DepartmentId.setter))}::uuid, ${Segment.paramSegment(unsaved.status)(Setter.optionParamSetter(UserStatus.setter))}::frontpage.user_status, ${Segment.paramSegment(unsaved.verified)(Setter.optionParamSetter(Setter.booleanSetter))}, ${Segment.paramSegment(unsaved.managerId)(Setter.optionParamSetter(UserId.setter))}::uuid, ${Segment.paramSegment(unsaved.role)(Setter.optionParamSetter(UserRole.setter))}::frontpage.user_role) + returning "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" + """.insertReturning(using UserRow.jdbcDecoder).map(_.updatedKeys.head) + } + override def insert(unsaved: UserRowUnsaved): ZIO[ZConnection, Throwable, UserRow] = { + val fs = List( + Some((sql""""email"""", sql"${Segment.paramSegment(unsaved.email)(Email.setter)}::text")), + Some((sql""""name"""", sql"${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}")), + Some((sql""""department_id"""", sql"${Segment.paramSegment(unsaved.departmentId)(Setter.optionParamSetter(DepartmentId.setter))}::uuid")), + Some((sql""""manager_id"""", sql"${Segment.paramSegment(unsaved.managerId)(Setter.optionParamSetter(UserId.setter))}::uuid")), + unsaved.id match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""id"""", sql"${Segment.paramSegment(value: UserId)(UserId.setter)}::uuid")) + }, + unsaved.createdAt match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""created_at"""", sql"${Segment.paramSegment(value: Option[TypoLocalDateTime])(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp")) + }, + unsaved.status match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""status"""", sql"${Segment.paramSegment(value: Option[UserStatus])(Setter.optionParamSetter(UserStatus.setter))}::frontpage.user_status")) + }, + unsaved.verified match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""verified"""", sql"${Segment.paramSegment(value: Option[Boolean])(Setter.optionParamSetter(Setter.booleanSetter))}")) + }, + unsaved.role match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""role"""", sql"${Segment.paramSegment(value: Option[UserRole])(Setter.optionParamSetter(UserRole.setter))}::frontpage.user_role")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."user" default values + returning "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" + """ + } else { + val names = fs.map { case (n, _) => n }.mkFragment(SqlFragment(", ")) + val values = fs.map { case (_, f) => f }.mkFragment(SqlFragment(", ")) + sql"""insert into "frontpage"."user"($names) values ($values) returning "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role"""" + } + q.insertReturning(using UserRow.jdbcDecoder).map(_.updatedKeys.head) + + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, UserRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."user"("id", "email", "name", "created_at", "department_id", "status", "verified", "manager_id", "role") FROM STDIN""", batchSize, unsaved)(UserRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, UserRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."user"("email", "name", "department_id", "manager_id", "id", "created_at", "status", "verified", "role") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(UserRowUnsaved.text) + } + override def select: SelectBuilder[UserFields, UserRow] = { + SelectBuilderSql(""""frontpage"."user"""", UserFields.structure, UserRow.jdbcDecoder) + } + override def selectAll: ZStream[ZConnection, Throwable, UserRow] = { + sql"""select "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" from "frontpage"."user"""".query(using UserRow.jdbcDecoder).selectStream() + } + override def selectById(id: UserId): ZIO[ZConnection, Throwable, Option[UserRow]] = { + sql"""select "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" from "frontpage"."user" where "id" = ${Segment.paramSegment(id)(UserId.setter)}""".query(using UserRow.jdbcDecoder).selectOne + } + override def selectByIds(ids: Array[UserId]): ZStream[ZConnection, Throwable, UserRow] = { + sql"""select "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" from "frontpage"."user" where "id" = ANY(${Segment.paramSegment(ids)(UserId.arraySetter)})""".query(using UserRow.jdbcDecoder).selectStream() + } + override def selectByIdsTracked(ids: Array[UserId]): ZIO[ZConnection, Throwable, Map[UserId, UserRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def selectByUniqueEmail(email: Email): ZIO[ZConnection, Throwable, Option[UserRow]] = { + sql"""select "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role" + from "frontpage"."user" + where "email" = ${Segment.paramSegment(email)(Email.setter)} + """.query(using UserRow.jdbcDecoder).selectOne + } + override def update: UpdateBuilder[UserFields, UserRow] = { + UpdateBuilder(""""frontpage"."user"""", UserFields.structure, UserRow.jdbcDecoder) + } + override def update(row: UserRow): ZIO[ZConnection, Throwable, Boolean] = { + val id = row.id + sql"""update "frontpage"."user" + set "email" = ${Segment.paramSegment(row.email)(Email.setter)}::text, + "name" = ${Segment.paramSegment(row.name)(Setter.stringSetter)}, + "created_at" = ${Segment.paramSegment(row.createdAt)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp, + "department_id" = ${Segment.paramSegment(row.departmentId)(Setter.optionParamSetter(DepartmentId.setter))}::uuid, + "status" = ${Segment.paramSegment(row.status)(Setter.optionParamSetter(UserStatus.setter))}::frontpage.user_status, + "verified" = ${Segment.paramSegment(row.verified)(Setter.optionParamSetter(Setter.booleanSetter))}, + "manager_id" = ${Segment.paramSegment(row.managerId)(Setter.optionParamSetter(UserId.setter))}::uuid, + "role" = ${Segment.paramSegment(row.role)(Setter.optionParamSetter(UserRole.setter))}::frontpage.user_role + where "id" = ${Segment.paramSegment(id)(UserId.setter)}""".update.map(_ > 0) + } + override def upsert(unsaved: UserRow): ZIO[ZConnection, Throwable, UpdateResult[UserRow]] = { + sql"""insert into "frontpage"."user"("id", "email", "name", "created_at", "department_id", "status", "verified", "manager_id", "role") + values ( + ${Segment.paramSegment(unsaved.id)(UserId.setter)}::uuid, + ${Segment.paramSegment(unsaved.email)(Email.setter)}::text, + ${Segment.paramSegment(unsaved.name)(Setter.stringSetter)}, + ${Segment.paramSegment(unsaved.createdAt)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp, + ${Segment.paramSegment(unsaved.departmentId)(Setter.optionParamSetter(DepartmentId.setter))}::uuid, + ${Segment.paramSegment(unsaved.status)(Setter.optionParamSetter(UserStatus.setter))}::frontpage.user_status, + ${Segment.paramSegment(unsaved.verified)(Setter.optionParamSetter(Setter.booleanSetter))}, + ${Segment.paramSegment(unsaved.managerId)(Setter.optionParamSetter(UserId.setter))}::uuid, + ${Segment.paramSegment(unsaved.role)(Setter.optionParamSetter(UserRole.setter))}::frontpage.user_role + ) + on conflict ("id") + do update set + "email" = EXCLUDED."email", + "name" = EXCLUDED."name", + "created_at" = EXCLUDED."created_at", + "department_id" = EXCLUDED."department_id", + "status" = EXCLUDED."status", + "verified" = EXCLUDED."verified", + "manager_id" = EXCLUDED."manager_id", + "role" = EXCLUDED."role" + returning "id", "email", "name", "created_at"::text, "department_id", "status", "verified", "manager_id", "role"""".insertReturning(using UserRow.jdbcDecoder) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, UserRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + val created = sql"""create temporary table user_TEMP (like "frontpage"."user") on commit drop""".execute + val copied = streamingInsert(s"""copy user_TEMP("id", "email", "name", "created_at", "department_id", "status", "verified", "manager_id", "role") from stdin""", batchSize, unsaved)(UserRow.text) + val merged = sql"""insert into "frontpage"."user"("id", "email", "name", "created_at", "department_id", "status", "verified", "manager_id", "role") + select * from user_TEMP + on conflict ("id") + do update set + "email" = EXCLUDED."email", + "name" = EXCLUDED."name", + "created_at" = EXCLUDED."created_at", + "department_id" = EXCLUDED."department_id", + "status" = EXCLUDED."status", + "verified" = EXCLUDED."verified", + "manager_id" = EXCLUDED."manager_id", + "role" = EXCLUDED."role" + ; + drop table user_TEMP;""".update + created *> copied *> merged + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserRepoMock.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserRepoMock.scala new file mode 100644 index 0000000000..e94a5c1786 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserRepoMock.scala @@ -0,0 +1,119 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams +import zio.Chunk +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +class UserRepoMock(toRow: Function1[UserRowUnsaved, UserRow], + map: scala.collection.mutable.Map[UserId, UserRow] = scala.collection.mutable.Map.empty) extends UserRepo { + override def delete: DeleteBuilder[UserFields, UserRow] = { + DeleteBuilderMock(DeleteParams.empty, UserFields.structure, map) + } + override def deleteById(id: UserId): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed(map.remove(id).isDefined) + } + override def deleteByIds(ids: Array[UserId]): ZIO[ZConnection, Throwable, Long] = { + ZIO.succeed(ids.map(id => map.remove(id)).count(_.isDefined).toLong) + } + override def insert(unsaved: UserRow): ZIO[ZConnection, Throwable, UserRow] = { + ZIO.succeed { + val _ = + if (map.contains(unsaved.id)) + sys.error(s"id ${unsaved.id} already exists") + else + map.put(unsaved.id, unsaved) + + unsaved + } + } + override def insert(unsaved: UserRowUnsaved): ZIO[ZConnection, Throwable, UserRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, UserRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, UserRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, unsavedRow) => + ZIO.succeed { + val row = toRow(unsavedRow) + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + override def select: SelectBuilder[UserFields, UserRow] = { + SelectBuilderMock(UserFields.structure, ZIO.succeed(Chunk.fromIterable(map.values)), SelectParams.empty) + } + override def selectAll: ZStream[ZConnection, Throwable, UserRow] = { + ZStream.fromIterable(map.values) + } + override def selectById(id: UserId): ZIO[ZConnection, Throwable, Option[UserRow]] = { + ZIO.succeed(map.get(id)) + } + override def selectByIds(ids: Array[UserId]): ZStream[ZConnection, Throwable, UserRow] = { + ZStream.fromIterable(ids.flatMap(map.get)) + } + override def selectByIdsTracked(ids: Array[UserId]): ZIO[ZConnection, Throwable, Map[UserId, UserRow]] = { + selectByIds(ids).runCollect.map { rows => + val byId = rows.view.map(x => (x.id, x)).toMap + ids.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def selectByUniqueEmail(email: Email): ZIO[ZConnection, Throwable, Option[UserRow]] = { + ZIO.succeed(map.values.find(v => email == v.email)) + } + override def update: UpdateBuilder[UserFields, UserRow] = { + UpdateBuilderMock(UpdateParams.empty, UserFields.structure, map) + } + override def update(row: UserRow): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed { + map.get(row.id) match { + case Some(`row`) => false + case Some(_) => + map.put(row.id, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: UserRow): ZIO[ZConnection, Throwable, UpdateResult[UserRow]] = { + ZIO.succeed { + map.put(unsaved.id, unsaved): @nowarn + UpdateResult(1, Chunk.single(unsaved)) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, UserRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.id -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserRow.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserRow.scala new file mode 100644 index 0000000000..ea0ac6895b --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserRow.scala @@ -0,0 +1,125 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.department.DepartmentId +import java.sql.ResultSet +import zio.jdbc.JdbcDecoder +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** Table: frontpage.user + Primary key: id */ +case class UserRow( + /** Default: gen_random_uuid() */ + id: UserId, + email: Email, + name: String, + /** Default: now() */ + createdAt: Option[TypoLocalDateTime], + /** Points to [[department.DepartmentRow.id]] */ + departmentId: Option[DepartmentId], + /** Default: 'active'::frontpage.user_status */ + status: Option[UserStatus], + /** Default: false */ + verified: Option[Boolean], + /** Points to [[UserRow.id]] */ + managerId: Option[UserId], + /** Default: 'employee'::frontpage.user_role */ + role: Option[UserRole] +){ + def toUnsavedRow(id: Defaulted[UserId], createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.Provided(this.createdAt), status: Defaulted[Option[UserStatus]] = Defaulted.Provided(this.status), verified: Defaulted[Option[Boolean]] = Defaulted.Provided(this.verified), role: Defaulted[Option[UserRole]] = Defaulted.Provided(this.role)): UserRowUnsaved = + UserRowUnsaved(email, name, departmentId, managerId, id, createdAt, status, verified, role) + } + +object UserRow { + implicit lazy val jdbcDecoder: JdbcDecoder[UserRow] = new JdbcDecoder[UserRow] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, UserRow) = + columIndex + 8 -> + UserRow( + id = UserId.jdbcDecoder.unsafeDecode(columIndex + 0, rs)._2, + email = Email.jdbcDecoder.unsafeDecode(columIndex + 1, rs)._2, + name = JdbcDecoder.stringDecoder.unsafeDecode(columIndex + 2, rs)._2, + createdAt = JdbcDecoder.optionDecoder(TypoLocalDateTime.jdbcDecoder).unsafeDecode(columIndex + 3, rs)._2, + departmentId = JdbcDecoder.optionDecoder(DepartmentId.jdbcDecoder).unsafeDecode(columIndex + 4, rs)._2, + status = JdbcDecoder.optionDecoder(UserStatus.jdbcDecoder).unsafeDecode(columIndex + 5, rs)._2, + verified = JdbcDecoder.optionDecoder(JdbcDecoder.booleanDecoder).unsafeDecode(columIndex + 6, rs)._2, + managerId = JdbcDecoder.optionDecoder(UserId.jdbcDecoder).unsafeDecode(columIndex + 7, rs)._2, + role = JdbcDecoder.optionDecoder(UserRole.jdbcDecoder).unsafeDecode(columIndex + 8, rs)._2 + ) + } + implicit lazy val jsonDecoder: JsonDecoder[UserRow] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(UserId.jsonDecoder)) + val email = jsonObj.get("email").toRight("Missing field 'email'").flatMap(_.as(Email.jsonDecoder)) + val name = jsonObj.get("name").toRight("Missing field 'name'").flatMap(_.as(JsonDecoder.string)) + val createdAt = jsonObj.get("created_at").fold[Either[String, Option[TypoLocalDateTime]]](Right(None))(_.as(JsonDecoder.option(using TypoLocalDateTime.jsonDecoder))) + val departmentId = jsonObj.get("department_id").fold[Either[String, Option[DepartmentId]]](Right(None))(_.as(JsonDecoder.option(using DepartmentId.jsonDecoder))) + val status = jsonObj.get("status").fold[Either[String, Option[UserStatus]]](Right(None))(_.as(JsonDecoder.option(using UserStatus.jsonDecoder))) + val verified = jsonObj.get("verified").fold[Either[String, Option[Boolean]]](Right(None))(_.as(JsonDecoder.option(using JsonDecoder.boolean))) + val managerId = jsonObj.get("manager_id").fold[Either[String, Option[UserId]]](Right(None))(_.as(JsonDecoder.option(using UserId.jsonDecoder))) + val role = jsonObj.get("role").fold[Either[String, Option[UserRole]]](Right(None))(_.as(JsonDecoder.option(using UserRole.jsonDecoder))) + if (id.isRight && email.isRight && name.isRight && createdAt.isRight && departmentId.isRight && status.isRight && verified.isRight && managerId.isRight && role.isRight) + Right(UserRow(id = id.toOption.get, email = email.toOption.get, name = name.toOption.get, createdAt = createdAt.toOption.get, departmentId = departmentId.toOption.get, status = status.toOption.get, verified = verified.toOption.get, managerId = managerId.toOption.get, role = role.toOption.get)) + else Left(List[Either[String, Any]](id, email, name, createdAt, departmentId, status, verified, managerId, role).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[UserRow] = new JsonEncoder[UserRow] { + override def unsafeEncode(a: UserRow, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""id":""") + UserId.jsonEncoder.unsafeEncode(a.id, indent, out) + out.write(",") + out.write(""""email":""") + Email.jsonEncoder.unsafeEncode(a.email, indent, out) + out.write(",") + out.write(""""name":""") + JsonEncoder.string.unsafeEncode(a.name, indent, out) + out.write(",") + out.write(""""created_at":""") + JsonEncoder.option(using TypoLocalDateTime.jsonEncoder).unsafeEncode(a.createdAt, indent, out) + out.write(",") + out.write(""""department_id":""") + JsonEncoder.option(using DepartmentId.jsonEncoder).unsafeEncode(a.departmentId, indent, out) + out.write(",") + out.write(""""status":""") + JsonEncoder.option(using UserStatus.jsonEncoder).unsafeEncode(a.status, indent, out) + out.write(",") + out.write(""""verified":""") + JsonEncoder.option(using JsonEncoder.boolean).unsafeEncode(a.verified, indent, out) + out.write(",") + out.write(""""manager_id":""") + JsonEncoder.option(using UserId.jsonEncoder).unsafeEncode(a.managerId, indent, out) + out.write(",") + out.write(""""role":""") + JsonEncoder.option(using UserRole.jsonEncoder).unsafeEncode(a.role, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[UserRow] = Text.instance[UserRow]{ (row, sb) => + UserId.text.unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Email.text.unsafeEncode(row.email, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.createdAt, sb) + sb.append(Text.DELIMETER) + Text.option(DepartmentId.text).unsafeEncode(row.departmentId, sb) + sb.append(Text.DELIMETER) + Text.option(UserStatus.text).unsafeEncode(row.status, sb) + sb.append(Text.DELIMETER) + Text.option(Text.booleanInstance).unsafeEncode(row.verified, sb) + sb.append(Text.DELIMETER) + Text.option(UserId.text).unsafeEncode(row.managerId, sb) + sb.append(Text.DELIMETER) + Text.option(UserRole.text).unsafeEncode(row.role, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserRowUnsaved.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserRowUnsaved.scala new file mode 100644 index 0000000000..c3423d8fa7 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user/UserRowUnsaved.scala @@ -0,0 +1,131 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.department.DepartmentId +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** This class corresponds to a row in table `frontpage.user` which has not been persisted yet */ +case class UserRowUnsaved( + email: Email, + name: String, + /** Points to [[department.DepartmentRow.id]] */ + departmentId: Option[DepartmentId], + /** Points to [[UserRow.id]] */ + managerId: Option[UserId], + /** Default: gen_random_uuid() */ + id: Defaulted[UserId] = Defaulted.UseDefault, + /** Default: now() */ + createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault, + /** Default: 'active'::frontpage.user_status */ + status: Defaulted[Option[UserStatus]] = Defaulted.UseDefault, + /** Default: false */ + verified: Defaulted[Option[Boolean]] = Defaulted.UseDefault, + /** Default: 'employee'::frontpage.user_role */ + role: Defaulted[Option[UserRole]] = Defaulted.UseDefault +) { + def toRow(idDefault: => UserId, createdAtDefault: => Option[TypoLocalDateTime], statusDefault: => Option[UserStatus], verifiedDefault: => Option[Boolean], roleDefault: => Option[UserRole]): UserRow = + UserRow( + email = email, + name = name, + departmentId = departmentId, + managerId = managerId, + id = id match { + case Defaulted.UseDefault => idDefault + case Defaulted.Provided(value) => value + }, + createdAt = createdAt match { + case Defaulted.UseDefault => createdAtDefault + case Defaulted.Provided(value) => value + }, + status = status match { + case Defaulted.UseDefault => statusDefault + case Defaulted.Provided(value) => value + }, + verified = verified match { + case Defaulted.UseDefault => verifiedDefault + case Defaulted.Provided(value) => value + }, + role = role match { + case Defaulted.UseDefault => roleDefault + case Defaulted.Provided(value) => value + } + ) +} +object UserRowUnsaved { + implicit lazy val jsonDecoder: JsonDecoder[UserRowUnsaved] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val email = jsonObj.get("email").toRight("Missing field 'email'").flatMap(_.as(Email.jsonDecoder)) + val name = jsonObj.get("name").toRight("Missing field 'name'").flatMap(_.as(JsonDecoder.string)) + val departmentId = jsonObj.get("department_id").fold[Either[String, Option[DepartmentId]]](Right(None))(_.as(JsonDecoder.option(using DepartmentId.jsonDecoder))) + val managerId = jsonObj.get("manager_id").fold[Either[String, Option[UserId]]](Right(None))(_.as(JsonDecoder.option(using UserId.jsonDecoder))) + val id = jsonObj.get("id").toRight("Missing field 'id'").flatMap(_.as(Defaulted.jsonDecoder(UserId.jsonDecoder))) + val createdAt = jsonObj.get("created_at").toRight("Missing field 'created_at'").flatMap(_.as(Defaulted.jsonDecoder(JsonDecoder.option(using TypoLocalDateTime.jsonDecoder)))) + val status = jsonObj.get("status").toRight("Missing field 'status'").flatMap(_.as(Defaulted.jsonDecoder(JsonDecoder.option(using UserStatus.jsonDecoder)))) + val verified = jsonObj.get("verified").toRight("Missing field 'verified'").flatMap(_.as(Defaulted.jsonDecoder(JsonDecoder.option(using JsonDecoder.boolean)))) + val role = jsonObj.get("role").toRight("Missing field 'role'").flatMap(_.as(Defaulted.jsonDecoder(JsonDecoder.option(using UserRole.jsonDecoder)))) + if (email.isRight && name.isRight && departmentId.isRight && managerId.isRight && id.isRight && createdAt.isRight && status.isRight && verified.isRight && role.isRight) + Right(UserRowUnsaved(email = email.toOption.get, name = name.toOption.get, departmentId = departmentId.toOption.get, managerId = managerId.toOption.get, id = id.toOption.get, createdAt = createdAt.toOption.get, status = status.toOption.get, verified = verified.toOption.get, role = role.toOption.get)) + else Left(List[Either[String, Any]](email, name, departmentId, managerId, id, createdAt, status, verified, role).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[UserRowUnsaved] = new JsonEncoder[UserRowUnsaved] { + override def unsafeEncode(a: UserRowUnsaved, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""email":""") + Email.jsonEncoder.unsafeEncode(a.email, indent, out) + out.write(",") + out.write(""""name":""") + JsonEncoder.string.unsafeEncode(a.name, indent, out) + out.write(",") + out.write(""""department_id":""") + JsonEncoder.option(using DepartmentId.jsonEncoder).unsafeEncode(a.departmentId, indent, out) + out.write(",") + out.write(""""manager_id":""") + JsonEncoder.option(using UserId.jsonEncoder).unsafeEncode(a.managerId, indent, out) + out.write(",") + out.write(""""id":""") + Defaulted.jsonEncoder(UserId.jsonEncoder).unsafeEncode(a.id, indent, out) + out.write(",") + out.write(""""created_at":""") + Defaulted.jsonEncoder(JsonEncoder.option(using TypoLocalDateTime.jsonEncoder)).unsafeEncode(a.createdAt, indent, out) + out.write(",") + out.write(""""status":""") + Defaulted.jsonEncoder(JsonEncoder.option(using UserStatus.jsonEncoder)).unsafeEncode(a.status, indent, out) + out.write(",") + out.write(""""verified":""") + Defaulted.jsonEncoder(JsonEncoder.option(using JsonEncoder.boolean)).unsafeEncode(a.verified, indent, out) + out.write(",") + out.write(""""role":""") + Defaulted.jsonEncoder(JsonEncoder.option(using UserRole.jsonEncoder)).unsafeEncode(a.role, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[UserRowUnsaved] = Text.instance[UserRowUnsaved]{ (row, sb) => + Email.text.unsafeEncode(row.email, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.name, sb) + sb.append(Text.DELIMETER) + Text.option(DepartmentId.text).unsafeEncode(row.departmentId, sb) + sb.append(Text.DELIMETER) + Text.option(UserId.text).unsafeEncode(row.managerId, sb) + sb.append(Text.DELIMETER) + Defaulted.text(UserId.text).unsafeEncode(row.id, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(TypoLocalDateTime.text)).unsafeEncode(row.createdAt, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(UserStatus.text)).unsafeEncode(row.status, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(Text.booleanInstance)).unsafeEncode(row.verified, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(UserRole.text)).unsafeEncode(row.role, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionFields.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionFields.scala new file mode 100644 index 0000000000..51eba08eed --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionFields.scala @@ -0,0 +1,65 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.permission.PermissionFields +import adventureworks.frontpage.permission.PermissionId +import adventureworks.frontpage.permission.PermissionRow +import adventureworks.frontpage.user.UserFields +import adventureworks.frontpage.user.UserId +import adventureworks.frontpage.user.UserRow +import typo.dsl.ForeignKey +import typo.dsl.Path +import typo.dsl.Required +import typo.dsl.SqlExpr +import typo.dsl.SqlExpr.CompositeIn +import typo.dsl.SqlExpr.CompositeIn.TuplePart +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait UserPermissionFields { + def userId: IdField[UserId, UserPermissionRow] + def permissionId: IdField[PermissionId, UserPermissionRow] + def grantedAt: OptField[TypoLocalDateTime, UserPermissionRow] + def fkPermission: ForeignKey[PermissionFields, PermissionRow] = + ForeignKey[PermissionFields, PermissionRow]("frontpage.user_permission_permission_id_fkey", Nil) + .withColumnPair(permissionId, _.id) + def fkUser: ForeignKey[UserFields, UserRow] = + ForeignKey[UserFields, UserRow]("frontpage.user_permission_user_id_fkey", Nil) + .withColumnPair(userId, _.id) + def compositeIdIs(compositeId: UserPermissionId): SqlExpr[Boolean, Required] = + userId.isEqual(compositeId.userId).and(permissionId.isEqual(compositeId.permissionId)) + def compositeIdIn(compositeIds: Array[UserPermissionId]): SqlExpr[Boolean, Required] = + new CompositeIn(compositeIds)(TuplePart(userId)(_.userId), TuplePart(permissionId)(_.permissionId)) + +} + +object UserPermissionFields { + lazy val structure: Relation[UserPermissionFields, UserPermissionRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[UserPermissionFields, UserPermissionRow] { + + override lazy val fields: UserPermissionFields = new UserPermissionFields { + override def userId = IdField[UserId, UserPermissionRow](_path, "user_id", None, Some("uuid"), x => x.userId, (row, value) => row.copy(userId = value)) + override def permissionId = IdField[PermissionId, UserPermissionRow](_path, "permission_id", None, Some("uuid"), x => x.permissionId, (row, value) => row.copy(permissionId = value)) + override def grantedAt = OptField[TypoLocalDateTime, UserPermissionRow](_path, "granted_at", Some("text"), Some("timestamp"), x => x.grantedAt, (row, value) => row.copy(grantedAt = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, UserPermissionRow]] = + List[FieldLikeNoHkt[?, UserPermissionRow]](fields.userId, fields.permissionId, fields.grantedAt) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionId.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionId.scala new file mode 100644 index 0000000000..a50c2a0fa6 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionId.scala @@ -0,0 +1,42 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import adventureworks.frontpage.permission.PermissionId +import adventureworks.frontpage.user.UserId +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** Type for the composite primary key of table `frontpage.user_permission` */ +case class UserPermissionId( + userId: UserId, + permissionId: PermissionId +) +object UserPermissionId { + implicit lazy val jsonDecoder: JsonDecoder[UserPermissionId] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val userId = jsonObj.get("user_id").toRight("Missing field 'user_id'").flatMap(_.as(UserId.jsonDecoder)) + val permissionId = jsonObj.get("permission_id").toRight("Missing field 'permission_id'").flatMap(_.as(PermissionId.jsonDecoder)) + if (userId.isRight && permissionId.isRight) + Right(UserPermissionId(userId = userId.toOption.get, permissionId = permissionId.toOption.get)) + else Left(List[Either[String, Any]](userId, permissionId).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[UserPermissionId] = new JsonEncoder[UserPermissionId] { + override def unsafeEncode(a: UserPermissionId, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""user_id":""") + UserId.jsonEncoder.unsafeEncode(a.userId, indent, out) + out.write(",") + out.write(""""permission_id":""") + PermissionId.jsonEncoder.unsafeEncode(a.permissionId, indent, out) + out.write("}") + } + } + implicit lazy val ordering: Ordering[UserPermissionId] = Ordering.by(x => (x.userId, x.permissionId)) +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepo.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepo.scala new file mode 100644 index 0000000000..f82b70d3f1 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepo.scala @@ -0,0 +1,38 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +trait UserPermissionRepo { + def delete: DeleteBuilder[UserPermissionFields, UserPermissionRow] + def deleteById(compositeId: UserPermissionId): ZIO[ZConnection, Throwable, Boolean] + def deleteByIds(compositeIds: Array[UserPermissionId]): ZIO[ZConnection, Throwable, Long] + def insert(unsaved: UserPermissionRow): ZIO[ZConnection, Throwable, UserPermissionRow] + def insert(unsaved: UserPermissionRowUnsaved): ZIO[ZConnection, Throwable, UserPermissionRow] + def insertStreaming(unsaved: ZStream[ZConnection, Throwable, UserPermissionRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, UserPermissionRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + def select: SelectBuilder[UserPermissionFields, UserPermissionRow] + def selectAll: ZStream[ZConnection, Throwable, UserPermissionRow] + def selectById(compositeId: UserPermissionId): ZIO[ZConnection, Throwable, Option[UserPermissionRow]] + def selectByIds(compositeIds: Array[UserPermissionId]): ZStream[ZConnection, Throwable, UserPermissionRow] + def selectByIdsTracked(compositeIds: Array[UserPermissionId]): ZIO[ZConnection, Throwable, Map[UserPermissionId, UserPermissionRow]] + def update: UpdateBuilder[UserPermissionFields, UserPermissionRow] + def update(row: UserPermissionRow): ZIO[ZConnection, Throwable, Boolean] + def upsert(unsaved: UserPermissionRow): ZIO[ZConnection, Throwable, UpdateResult[UserPermissionRow]] + // Not implementable for zio-jdbc: upsertBatch + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, UserPermissionRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepoImpl.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepoImpl.scala new file mode 100644 index 0000000000..c820c4eaeb --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepoImpl.scala @@ -0,0 +1,138 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.permission.PermissionId +import adventureworks.frontpage.user.UserId +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder +import zio.ZIO +import zio.jdbc.SqlFragment +import zio.jdbc.SqlFragment.Segment +import zio.jdbc.SqlFragment.Setter +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.jdbc.sqlInterpolator +import zio.stream.ZStream + +class UserPermissionRepoImpl extends UserPermissionRepo { + override def delete: DeleteBuilder[UserPermissionFields, UserPermissionRow] = { + DeleteBuilder(""""frontpage"."user_permission"""", UserPermissionFields.structure) + } + override def deleteById(compositeId: UserPermissionId): ZIO[ZConnection, Throwable, Boolean] = { + sql"""delete from "frontpage"."user_permission" where "user_id" = ${Segment.paramSegment(compositeId.userId)(UserId.setter)} AND "permission_id" = ${Segment.paramSegment(compositeId.permissionId)(PermissionId.setter)}""".delete.map(_ > 0) + } + override def deleteByIds(compositeIds: Array[UserPermissionId]): ZIO[ZConnection, Throwable, Long] = { + val userId = compositeIds.map(_.userId) + val permissionId = compositeIds.map(_.permissionId) + sql"""delete + from "frontpage"."user_permission" + where ("user_id", "permission_id") + in (select unnest(${userId}), unnest(${permissionId})) + """.delete + + } + override def insert(unsaved: UserPermissionRow): ZIO[ZConnection, Throwable, UserPermissionRow] = { + sql"""insert into "frontpage"."user_permission"("user_id", "permission_id", "granted_at") + values (${Segment.paramSegment(unsaved.userId)(UserId.setter)}::uuid, ${Segment.paramSegment(unsaved.permissionId)(PermissionId.setter)}::uuid, ${Segment.paramSegment(unsaved.grantedAt)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp) + returning "user_id", "permission_id", "granted_at"::text + """.insertReturning(using UserPermissionRow.jdbcDecoder).map(_.updatedKeys.head) + } + override def insert(unsaved: UserPermissionRowUnsaved): ZIO[ZConnection, Throwable, UserPermissionRow] = { + val fs = List( + Some((sql""""user_id"""", sql"${Segment.paramSegment(unsaved.userId)(UserId.setter)}::uuid")), + Some((sql""""permission_id"""", sql"${Segment.paramSegment(unsaved.permissionId)(PermissionId.setter)}::uuid")), + unsaved.grantedAt match { + case Defaulted.UseDefault => None + case Defaulted.Provided(value) => Some((sql""""granted_at"""", sql"${Segment.paramSegment(value: Option[TypoLocalDateTime])(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp")) + } + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "frontpage"."user_permission" default values + returning "user_id", "permission_id", "granted_at"::text + """ + } else { + val names = fs.map { case (n, _) => n }.mkFragment(SqlFragment(", ")) + val values = fs.map { case (_, f) => f }.mkFragment(SqlFragment(", ")) + sql"""insert into "frontpage"."user_permission"($names) values ($values) returning "user_id", "permission_id", "granted_at"::text""" + } + q.insertReturning(using UserPermissionRow.jdbcDecoder).map(_.updatedKeys.head) + + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, UserPermissionRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."user_permission"("user_id", "permission_id", "granted_at") FROM STDIN""", batchSize, unsaved)(UserPermissionRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, UserPermissionRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "frontpage"."user_permission"("user_id", "permission_id", "granted_at") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(UserPermissionRowUnsaved.text) + } + override def select: SelectBuilder[UserPermissionFields, UserPermissionRow] = { + SelectBuilderSql(""""frontpage"."user_permission"""", UserPermissionFields.structure, UserPermissionRow.jdbcDecoder) + } + override def selectAll: ZStream[ZConnection, Throwable, UserPermissionRow] = { + sql"""select "user_id", "permission_id", "granted_at"::text from "frontpage"."user_permission"""".query(using UserPermissionRow.jdbcDecoder).selectStream() + } + override def selectById(compositeId: UserPermissionId): ZIO[ZConnection, Throwable, Option[UserPermissionRow]] = { + sql"""select "user_id", "permission_id", "granted_at"::text from "frontpage"."user_permission" where "user_id" = ${Segment.paramSegment(compositeId.userId)(UserId.setter)} AND "permission_id" = ${Segment.paramSegment(compositeId.permissionId)(PermissionId.setter)}""".query(using UserPermissionRow.jdbcDecoder).selectOne + } + override def selectByIds(compositeIds: Array[UserPermissionId]): ZStream[ZConnection, Throwable, UserPermissionRow] = { + val userId = compositeIds.map(_.userId) + val permissionId = compositeIds.map(_.permissionId) + sql"""select "user_id", "permission_id", "granted_at"::text + from "frontpage"."user_permission" + where ("user_id", "permission_id") + in (select unnest(${userId}), unnest(${permissionId})) + """.query(using UserPermissionRow.jdbcDecoder).selectStream() + + } + override def selectByIdsTracked(compositeIds: Array[UserPermissionId]): ZIO[ZConnection, Throwable, Map[UserPermissionId, UserPermissionRow]] = { + selectByIds(compositeIds).runCollect.map { rows => + val byId = rows.view.map(x => (x.compositeId, x)).toMap + compositeIds.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[UserPermissionFields, UserPermissionRow] = { + UpdateBuilder(""""frontpage"."user_permission"""", UserPermissionFields.structure, UserPermissionRow.jdbcDecoder) + } + override def update(row: UserPermissionRow): ZIO[ZConnection, Throwable, Boolean] = { + val compositeId = row.compositeId + sql"""update "frontpage"."user_permission" + set "granted_at" = ${Segment.paramSegment(row.grantedAt)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp + where "user_id" = ${Segment.paramSegment(compositeId.userId)(UserId.setter)} AND "permission_id" = ${Segment.paramSegment(compositeId.permissionId)(PermissionId.setter)}""".update.map(_ > 0) + } + override def upsert(unsaved: UserPermissionRow): ZIO[ZConnection, Throwable, UpdateResult[UserPermissionRow]] = { + sql"""insert into "frontpage"."user_permission"("user_id", "permission_id", "granted_at") + values ( + ${Segment.paramSegment(unsaved.userId)(UserId.setter)}::uuid, + ${Segment.paramSegment(unsaved.permissionId)(PermissionId.setter)}::uuid, + ${Segment.paramSegment(unsaved.grantedAt)(Setter.optionParamSetter(TypoLocalDateTime.setter))}::timestamp + ) + on conflict ("user_id", "permission_id") + do update set + "granted_at" = EXCLUDED."granted_at" + returning "user_id", "permission_id", "granted_at"::text""".insertReturning(using UserPermissionRow.jdbcDecoder) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, UserPermissionRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + val created = sql"""create temporary table user_permission_TEMP (like "frontpage"."user_permission") on commit drop""".execute + val copied = streamingInsert(s"""copy user_permission_TEMP("user_id", "permission_id", "granted_at") from stdin""", batchSize, unsaved)(UserPermissionRow.text) + val merged = sql"""insert into "frontpage"."user_permission"("user_id", "permission_id", "granted_at") + select * from user_permission_TEMP + on conflict ("user_id", "permission_id") + do update set + "granted_at" = EXCLUDED."granted_at" + ; + drop table user_permission_TEMP;""".update + created *> copied *> merged + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepoMock.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepoMock.scala new file mode 100644 index 0000000000..6a917bc97c --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRepoMock.scala @@ -0,0 +1,116 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import scala.annotation.nowarn +import typo.dsl.DeleteBuilder +import typo.dsl.DeleteBuilder.DeleteBuilderMock +import typo.dsl.DeleteParams +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderMock +import typo.dsl.SelectParams +import typo.dsl.UpdateBuilder +import typo.dsl.UpdateBuilder.UpdateBuilderMock +import typo.dsl.UpdateParams +import zio.Chunk +import zio.ZIO +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.stream.ZStream + +class UserPermissionRepoMock(toRow: Function1[UserPermissionRowUnsaved, UserPermissionRow], + map: scala.collection.mutable.Map[UserPermissionId, UserPermissionRow] = scala.collection.mutable.Map.empty) extends UserPermissionRepo { + override def delete: DeleteBuilder[UserPermissionFields, UserPermissionRow] = { + DeleteBuilderMock(DeleteParams.empty, UserPermissionFields.structure, map) + } + override def deleteById(compositeId: UserPermissionId): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed(map.remove(compositeId).isDefined) + } + override def deleteByIds(compositeIds: Array[UserPermissionId]): ZIO[ZConnection, Throwable, Long] = { + ZIO.succeed(compositeIds.map(id => map.remove(id)).count(_.isDefined).toLong) + } + override def insert(unsaved: UserPermissionRow): ZIO[ZConnection, Throwable, UserPermissionRow] = { + ZIO.succeed { + val _ = + if (map.contains(unsaved.compositeId)) + sys.error(s"id ${unsaved.compositeId} already exists") + else + map.put(unsaved.compositeId, unsaved) + + unsaved + } + } + override def insert(unsaved: UserPermissionRowUnsaved): ZIO[ZConnection, Throwable, UserPermissionRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, UserPermissionRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.compositeId -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, UserPermissionRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, unsavedRow) => + ZIO.succeed { + val row = toRow(unsavedRow) + map += (row.compositeId -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + override def select: SelectBuilder[UserPermissionFields, UserPermissionRow] = { + SelectBuilderMock(UserPermissionFields.structure, ZIO.succeed(Chunk.fromIterable(map.values)), SelectParams.empty) + } + override def selectAll: ZStream[ZConnection, Throwable, UserPermissionRow] = { + ZStream.fromIterable(map.values) + } + override def selectById(compositeId: UserPermissionId): ZIO[ZConnection, Throwable, Option[UserPermissionRow]] = { + ZIO.succeed(map.get(compositeId)) + } + override def selectByIds(compositeIds: Array[UserPermissionId]): ZStream[ZConnection, Throwable, UserPermissionRow] = { + ZStream.fromIterable(compositeIds.flatMap(map.get)) + } + override def selectByIdsTracked(compositeIds: Array[UserPermissionId]): ZIO[ZConnection, Throwable, Map[UserPermissionId, UserPermissionRow]] = { + selectByIds(compositeIds).runCollect.map { rows => + val byId = rows.view.map(x => (x.compositeId, x)).toMap + compositeIds.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[UserPermissionFields, UserPermissionRow] = { + UpdateBuilderMock(UpdateParams.empty, UserPermissionFields.structure, map) + } + override def update(row: UserPermissionRow): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed { + map.get(row.compositeId) match { + case Some(`row`) => false + case Some(_) => + map.put(row.compositeId, row): @nowarn + true + case None => false + } + } + } + override def upsert(unsaved: UserPermissionRow): ZIO[ZConnection, Throwable, UpdateResult[UserPermissionRow]] = { + ZIO.succeed { + map.put(unsaved.compositeId, unsaved): @nowarn + UpdateResult(1, Chunk.single(unsaved)) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: ZStream[ZConnection, Throwable, UserPermissionRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.compositeId -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRow.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRow.scala new file mode 100644 index 0000000000..0eb66b987f --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRow.scala @@ -0,0 +1,78 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.permission.PermissionId +import adventureworks.frontpage.user.UserId +import java.sql.ResultSet +import zio.jdbc.JdbcDecoder +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** Table: frontpage.user_permission + Composite primary key: user_id, permission_id */ +case class UserPermissionRow( + /** Points to [[user.UserRow.id]] */ + userId: UserId, + /** Points to [[permission.PermissionRow.id]] */ + permissionId: PermissionId, + /** Default: now() */ + grantedAt: Option[TypoLocalDateTime] +){ + val compositeId: UserPermissionId = UserPermissionId(userId, permissionId) + val id = compositeId + def toUnsavedRow(grantedAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.Provided(this.grantedAt)): UserPermissionRowUnsaved = + UserPermissionRowUnsaved(userId, permissionId, grantedAt) + } + +object UserPermissionRow { + def apply(compositeId: UserPermissionId, grantedAt: Option[TypoLocalDateTime]) = + new UserPermissionRow(compositeId.userId, compositeId.permissionId, grantedAt) + implicit lazy val jdbcDecoder: JdbcDecoder[UserPermissionRow] = new JdbcDecoder[UserPermissionRow] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, UserPermissionRow) = + columIndex + 2 -> + UserPermissionRow( + userId = UserId.jdbcDecoder.unsafeDecode(columIndex + 0, rs)._2, + permissionId = PermissionId.jdbcDecoder.unsafeDecode(columIndex + 1, rs)._2, + grantedAt = JdbcDecoder.optionDecoder(TypoLocalDateTime.jdbcDecoder).unsafeDecode(columIndex + 2, rs)._2 + ) + } + implicit lazy val jsonDecoder: JsonDecoder[UserPermissionRow] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val userId = jsonObj.get("user_id").toRight("Missing field 'user_id'").flatMap(_.as(UserId.jsonDecoder)) + val permissionId = jsonObj.get("permission_id").toRight("Missing field 'permission_id'").flatMap(_.as(PermissionId.jsonDecoder)) + val grantedAt = jsonObj.get("granted_at").fold[Either[String, Option[TypoLocalDateTime]]](Right(None))(_.as(JsonDecoder.option(using TypoLocalDateTime.jsonDecoder))) + if (userId.isRight && permissionId.isRight && grantedAt.isRight) + Right(UserPermissionRow(userId = userId.toOption.get, permissionId = permissionId.toOption.get, grantedAt = grantedAt.toOption.get)) + else Left(List[Either[String, Any]](userId, permissionId, grantedAt).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[UserPermissionRow] = new JsonEncoder[UserPermissionRow] { + override def unsafeEncode(a: UserPermissionRow, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""user_id":""") + UserId.jsonEncoder.unsafeEncode(a.userId, indent, out) + out.write(",") + out.write(""""permission_id":""") + PermissionId.jsonEncoder.unsafeEncode(a.permissionId, indent, out) + out.write(",") + out.write(""""granted_at":""") + JsonEncoder.option(using TypoLocalDateTime.jsonEncoder).unsafeEncode(a.grantedAt, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[UserPermissionRow] = Text.instance[UserPermissionRow]{ (row, sb) => + UserId.text.unsafeEncode(row.userId, sb) + sb.append(Text.DELIMETER) + PermissionId.text.unsafeEncode(row.permissionId, sb) + sb.append(Text.DELIMETER) + Text.option(TypoLocalDateTime.text).unsafeEncode(row.grantedAt, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRowUnsaved.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRowUnsaved.scala new file mode 100644 index 0000000000..5cd70945ef --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/frontpage/user_permission/UserPermissionRowUnsaved.scala @@ -0,0 +1,68 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package frontpage +package user_permission + +import adventureworks.customtypes.Defaulted +import adventureworks.customtypes.TypoLocalDateTime +import adventureworks.frontpage.permission.PermissionId +import adventureworks.frontpage.user.UserId +import zio.json.JsonDecoder +import zio.json.JsonEncoder +import zio.json.ast.Json +import zio.json.internal.Write + +/** This class corresponds to a row in table `frontpage.user_permission` which has not been persisted yet */ +case class UserPermissionRowUnsaved( + /** Points to [[user.UserRow.id]] */ + userId: UserId, + /** Points to [[permission.PermissionRow.id]] */ + permissionId: PermissionId, + /** Default: now() */ + grantedAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault +) { + def toRow(grantedAtDefault: => Option[TypoLocalDateTime]): UserPermissionRow = + UserPermissionRow( + userId = userId, + permissionId = permissionId, + grantedAt = grantedAt match { + case Defaulted.UseDefault => grantedAtDefault + case Defaulted.Provided(value) => value + } + ) +} +object UserPermissionRowUnsaved { + implicit lazy val jsonDecoder: JsonDecoder[UserPermissionRowUnsaved] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val userId = jsonObj.get("user_id").toRight("Missing field 'user_id'").flatMap(_.as(UserId.jsonDecoder)) + val permissionId = jsonObj.get("permission_id").toRight("Missing field 'permission_id'").flatMap(_.as(PermissionId.jsonDecoder)) + val grantedAt = jsonObj.get("granted_at").toRight("Missing field 'granted_at'").flatMap(_.as(Defaulted.jsonDecoder(JsonDecoder.option(using TypoLocalDateTime.jsonDecoder)))) + if (userId.isRight && permissionId.isRight && grantedAt.isRight) + Right(UserPermissionRowUnsaved(userId = userId.toOption.get, permissionId = permissionId.toOption.get, grantedAt = grantedAt.toOption.get)) + else Left(List[Either[String, Any]](userId, permissionId, grantedAt).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[UserPermissionRowUnsaved] = new JsonEncoder[UserPermissionRowUnsaved] { + override def unsafeEncode(a: UserPermissionRowUnsaved, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""user_id":""") + UserId.jsonEncoder.unsafeEncode(a.userId, indent, out) + out.write(",") + out.write(""""permission_id":""") + PermissionId.jsonEncoder.unsafeEncode(a.permissionId, indent, out) + out.write(",") + out.write(""""granted_at":""") + Defaulted.jsonEncoder(JsonEncoder.option(using TypoLocalDateTime.jsonEncoder)).unsafeEncode(a.grantedAt, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[UserPermissionRowUnsaved] = Text.instance[UserPermissionRowUnsaved]{ (row, sb) => + UserId.text.unsafeEncode(row.userId, sb) + sb.append(Text.DELIMETER) + PermissionId.text.unsafeEncode(row.permissionId, sb) + sb.append(Text.DELIMETER) + Defaulted.text(Text.option(TypoLocalDateTime.text)).unsafeEncode(row.grantedAt, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/testInsert.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/testInsert.scala index a379077615..4737a49cbb 100644 --- a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/testInsert.scala +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/testInsert.scala @@ -31,13 +31,71 @@ import adventureworks.customtypes.TypoUUID import adventureworks.customtypes.TypoUnknownCitext import adventureworks.customtypes.TypoVector import adventureworks.customtypes.TypoXml -import adventureworks.humanresources.department.DepartmentId -import adventureworks.humanresources.department.DepartmentRepoImpl -import adventureworks.humanresources.department.DepartmentRow -import adventureworks.humanresources.department.DepartmentRowUnsaved -import adventureworks.humanresources.employee.EmployeeRepoImpl -import adventureworks.humanresources.employee.EmployeeRow -import adventureworks.humanresources.employee.EmployeeRowUnsaved +import adventureworks.frontpage.Email +import adventureworks.frontpage.OrderStatus +import adventureworks.frontpage.UserRole +import adventureworks.frontpage.UserStatus +import adventureworks.frontpage.address.AddressId +import adventureworks.frontpage.address.AddressRepoImpl +import adventureworks.frontpage.address.AddressRow +import adventureworks.frontpage.address.AddressRowUnsaved +import adventureworks.frontpage.category.CategoryId +import adventureworks.frontpage.category.CategoryRepoImpl +import adventureworks.frontpage.category.CategoryRow +import adventureworks.frontpage.category.CategoryRowUnsaved +import adventureworks.frontpage.company.CompanyId +import adventureworks.frontpage.company.CompanyRepoImpl +import adventureworks.frontpage.company.CompanyRow +import adventureworks.frontpage.company.CompanyRowUnsaved +import adventureworks.frontpage.customer.CustomerId +import adventureworks.frontpage.customer.CustomerRepoImpl +import adventureworks.frontpage.customer.CustomerRow +import adventureworks.frontpage.customer.CustomerRowUnsaved +import adventureworks.frontpage.department.DepartmentId +import adventureworks.frontpage.department.DepartmentRepoImpl +import adventureworks.frontpage.department.DepartmentRow +import adventureworks.frontpage.department.DepartmentRowUnsaved +import adventureworks.frontpage.employee.EmployeeId +import adventureworks.frontpage.employee.EmployeeRepoImpl +import adventureworks.frontpage.employee.EmployeeRow +import adventureworks.frontpage.employee.EmployeeRowUnsaved +import adventureworks.frontpage.location.LocationId +import adventureworks.frontpage.location.LocationRepoImpl +import adventureworks.frontpage.location.LocationRow +import adventureworks.frontpage.location.LocationRowUnsaved +import adventureworks.frontpage.order.OrderId +import adventureworks.frontpage.order.OrderRepoImpl +import adventureworks.frontpage.order.OrderRow +import adventureworks.frontpage.order.OrderRowUnsaved +import adventureworks.frontpage.order_item.OrderItemId +import adventureworks.frontpage.order_item.OrderItemRepoImpl +import adventureworks.frontpage.order_item.OrderItemRow +import adventureworks.frontpage.order_item.OrderItemRowUnsaved +import adventureworks.frontpage.permission.PermissionId +import adventureworks.frontpage.permission.PermissionRepoImpl +import adventureworks.frontpage.permission.PermissionRow +import adventureworks.frontpage.permission.PermissionRowUnsaved +import adventureworks.frontpage.person.PersonId +import adventureworks.frontpage.person.PersonRepoImpl +import adventureworks.frontpage.person.PersonRow +import adventureworks.frontpage.person.PersonRowUnsaved +import adventureworks.frontpage.product.ProductId +import adventureworks.frontpage.product.ProductRepoImpl +import adventureworks.frontpage.product.ProductRow +import adventureworks.frontpage.product.ProductRowUnsaved +import adventureworks.frontpage.product_category.ProductCategoryRepoImpl +import adventureworks.frontpage.product_category.ProductCategoryRow +import adventureworks.frontpage.role.RoleId +import adventureworks.frontpage.role.RoleRepoImpl +import adventureworks.frontpage.role.RoleRow +import adventureworks.frontpage.role.RoleRowUnsaved +import adventureworks.frontpage.user.UserId +import adventureworks.frontpage.user.UserRepoImpl +import adventureworks.frontpage.user.UserRow +import adventureworks.frontpage.user.UserRowUnsaved +import adventureworks.frontpage.user_permission.UserPermissionRepoImpl +import adventureworks.frontpage.user_permission.UserPermissionRow +import adventureworks.frontpage.user_permission.UserPermissionRowUnsaved import adventureworks.humanresources.employeedepartmenthistory.EmployeedepartmenthistoryRepoImpl import adventureworks.humanresources.employeedepartmenthistory.EmployeedepartmenthistoryRow import adventureworks.humanresources.employeedepartmenthistory.EmployeedepartmenthistoryRowUnsaved @@ -52,10 +110,6 @@ import adventureworks.humanresources.shift.ShiftId import adventureworks.humanresources.shift.ShiftRepoImpl import adventureworks.humanresources.shift.ShiftRow import adventureworks.humanresources.shift.ShiftRowUnsaved -import adventureworks.person.address.AddressId -import adventureworks.person.address.AddressRepoImpl -import adventureworks.person.address.AddressRow -import adventureworks.person.address.AddressRowUnsaved import adventureworks.person.addresstype.AddresstypeId import adventureworks.person.addresstype.AddresstypeRepoImpl import adventureworks.person.addresstype.AddresstypeRow @@ -84,9 +138,6 @@ import adventureworks.person.emailaddress.EmailaddressRowUnsaved import adventureworks.person.password.PasswordRepoImpl import adventureworks.person.password.PasswordRow import adventureworks.person.password.PasswordRowUnsaved -import adventureworks.person.person.PersonRepoImpl -import adventureworks.person.person.PersonRow -import adventureworks.person.person.PersonRowUnsaved import adventureworks.person.personphone.PersonphoneRepoImpl import adventureworks.person.personphone.PersonphoneRow import adventureworks.person.personphone.PersonphoneRowUnsaved @@ -113,14 +164,6 @@ import adventureworks.production.illustration.IllustrationId import adventureworks.production.illustration.IllustrationRepoImpl import adventureworks.production.illustration.IllustrationRow import adventureworks.production.illustration.IllustrationRowUnsaved -import adventureworks.production.location.LocationId -import adventureworks.production.location.LocationRepoImpl -import adventureworks.production.location.LocationRow -import adventureworks.production.location.LocationRowUnsaved -import adventureworks.production.product.ProductId -import adventureworks.production.product.ProductRepoImpl -import adventureworks.production.product.ProductRow -import adventureworks.production.product.ProductRowUnsaved import adventureworks.production.productcategory.ProductcategoryId import adventureworks.production.productcategory.ProductcategoryRepoImpl import adventureworks.production.productcategory.ProductcategoryRow @@ -267,10 +310,6 @@ import adventureworks.sales.currencyrate.CurrencyrateId import adventureworks.sales.currencyrate.CurrencyrateRepoImpl import adventureworks.sales.currencyrate.CurrencyrateRow import adventureworks.sales.currencyrate.CurrencyrateRowUnsaved -import adventureworks.sales.customer.CustomerId -import adventureworks.sales.customer.CustomerRepoImpl -import adventureworks.sales.customer.CustomerRow -import adventureworks.sales.customer.CustomerRowUnsaved import adventureworks.sales.personcreditcard.PersoncreditcardRepoImpl import adventureworks.sales.personcreditcard.PersoncreditcardRow import adventureworks.sales.personcreditcard.PersoncreditcardRowUnsaved @@ -332,11 +371,88 @@ import zio.ZIO import zio.jdbc.ZConnection class TestInsert(random: Random, domainInsert: TestDomainInsert) { + def frontpageAddress(city: String = random.alphanumeric.take(20).mkString, + country: String = random.alphanumeric.take(20).mkString, + id: Defaulted[AddressId] = Defaulted.UseDefault + ): ZIO[ZConnection, Throwable, AddressRow] = (new AddressRepoImpl).insert(new AddressRowUnsaved(city = city, country = country, id = id)) + def frontpageCategory(name: String = random.alphanumeric.take(20).mkString, id: Defaulted[CategoryId] = Defaulted.UseDefault): ZIO[ZConnection, Throwable, CategoryRow] = (new CategoryRepoImpl).insert(new CategoryRowUnsaved(name = name, id = id)) + def frontpageCompany(name: String = random.alphanumeric.take(20).mkString, id: Defaulted[CompanyId] = Defaulted.UseDefault): ZIO[ZConnection, Throwable, CompanyRow] = (new CompanyRepoImpl).insert(new CompanyRowUnsaved(name = name, id = id)) + def frontpageCustomer(userId: Option[UserId] = None, + companyName: Option[String] = if (random.nextBoolean()) None else Some(random.alphanumeric.take(20).mkString), + creditLimit: Option[BigDecimal] = if (random.nextBoolean()) None else Some(BigDecimal.decimal(random.nextDouble())), + id: Defaulted[CustomerId] = Defaulted.UseDefault, + verified: Defaulted[Option[Boolean]] = Defaulted.UseDefault + ): ZIO[ZConnection, Throwable, CustomerRow] = (new CustomerRepoImpl).insert(new CustomerRowUnsaved(userId = userId, companyName = companyName, creditLimit = creditLimit, id = id, verified = verified)) + def frontpageDepartment(name: String = random.alphanumeric.take(20).mkString, + budget: Option[BigDecimal] = if (random.nextBoolean()) None else Some(BigDecimal.decimal(random.nextDouble())), + companyId: Option[CompanyId] = None, + id: Defaulted[DepartmentId] = Defaulted.UseDefault + ): ZIO[ZConnection, Throwable, DepartmentRow] = (new DepartmentRepoImpl).insert(new DepartmentRowUnsaved(name = name, budget = budget, companyId = companyId, id = id)) + def frontpageEmployee(personId: PersonId, + salary: Option[BigDecimal] = if (random.nextBoolean()) None else Some(BigDecimal.decimal(random.nextDouble())), + id: Defaulted[EmployeeId] = Defaulted.UseDefault + ): ZIO[ZConnection, Throwable, EmployeeRow] = (new EmployeeRepoImpl).insert(new EmployeeRowUnsaved(personId = personId, salary = salary, id = id)) + def frontpageLocation(name: String = random.alphanumeric.take(20).mkString, + position: Option[TypoPoint] = None, + area: Option[TypoPolygon] = None, + ipRange: Option[TypoInet] = None, + id: Defaulted[LocationId] = Defaulted.UseDefault, + metadata: Defaulted[Option[TypoJsonb]] = Defaulted.UseDefault + ): ZIO[ZConnection, Throwable, LocationRow] = (new LocationRepoImpl).insert(new LocationRowUnsaved(name = name, position = position, area = area, ipRange = ipRange, id = id, metadata = metadata)) + def frontpageOrder(userId: Option[UserId] = None, + productId: Option[ProductId] = None, + total: BigDecimal = BigDecimal.decimal(random.nextDouble()), + shippedAt: Option[TypoLocalDateTime] = if (random.nextBoolean()) None else Some(TypoLocalDateTime(LocalDateTime.of(LocalDate.ofEpochDay(random.nextInt(30000).toLong), LocalTime.ofSecondOfDay(random.nextInt(24 * 60 * 60).toLong)))), + id: Defaulted[OrderId] = Defaulted.UseDefault, + status: Defaulted[Option[OrderStatus]] = Defaulted.UseDefault, + createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault + ): ZIO[ZConnection, Throwable, OrderRow] = (new OrderRepoImpl).insert(new OrderRowUnsaved(userId = userId, productId = productId, total = total, shippedAt = shippedAt, id = id, status = status, createdAt = createdAt)) + def frontpageOrderItem(orderId: Option[OrderId] = None, + productId: Option[ProductId] = None, + quantity: Int = random.nextInt(), + price: BigDecimal = BigDecimal.decimal(random.nextDouble()), + shippedAt: Option[TypoLocalDateTime] = if (random.nextBoolean()) None else Some(TypoLocalDateTime(LocalDateTime.of(LocalDate.ofEpochDay(random.nextInt(30000).toLong), LocalTime.ofSecondOfDay(random.nextInt(24 * 60 * 60).toLong)))), + id: Defaulted[OrderItemId] = Defaulted.UseDefault + ): ZIO[ZConnection, Throwable, OrderItemRow] = (new OrderItemRepoImpl).insert(new OrderItemRowUnsaved(orderId = orderId, productId = productId, quantity = quantity, price = price, shippedAt = shippedAt, id = id)) + def frontpagePermission(name: String = random.alphanumeric.take(20).mkString, id: Defaulted[PermissionId] = Defaulted.UseDefault): ZIO[ZConnection, Throwable, PermissionRow] = (new PermissionRepoImpl).insert(new PermissionRowUnsaved(name = name, id = id)) + def frontpagePerson(name: String = random.alphanumeric.take(20).mkString, + addressId: Option[AddressId] = None, + id: Defaulted[PersonId] = Defaulted.UseDefault, + createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault + ): ZIO[ZConnection, Throwable, PersonRow] = (new PersonRepoImpl).insert(new PersonRowUnsaved(name = name, addressId = addressId, id = id, createdAt = createdAt)) + def frontpageProduct(name: String = random.alphanumeric.take(20).mkString, + price: BigDecimal = BigDecimal.decimal(random.nextDouble()), + lastRestocked: Option[TypoLocalDateTime] = if (random.nextBoolean()) None else Some(TypoLocalDateTime(LocalDateTime.of(LocalDate.ofEpochDay(random.nextInt(30000).toLong), LocalTime.ofSecondOfDay(random.nextInt(24 * 60 * 60).toLong)))), + id: Defaulted[ProductId] = Defaulted.UseDefault, + inStock: Defaulted[Option[Boolean]] = Defaulted.UseDefault, + quantity: Defaulted[Option[Int]] = Defaulted.UseDefault, + lastModified: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault, + tags: Defaulted[Option[Array[String]]] = Defaulted.UseDefault, + categories: Defaulted[Option[Array[Int]]] = Defaulted.UseDefault, + prices: Defaulted[Option[Array[BigDecimal]]] = Defaulted.UseDefault, + attributes: Defaulted[Option[Array[TypoJsonb]]] = Defaulted.UseDefault + ): ZIO[ZConnection, Throwable, ProductRow] = (new ProductRepoImpl).insert(new ProductRowUnsaved(name = name, price = price, lastRestocked = lastRestocked, id = id, inStock = inStock, quantity = quantity, lastModified = lastModified, tags = tags, categories = categories, prices = prices, attributes = attributes)) + def frontpageProductCategory(productId: ProductId, categoryId: CategoryId): ZIO[ZConnection, Throwable, ProductCategoryRow] = (new ProductCategoryRepoImpl).insert(new ProductCategoryRow(productId = productId, categoryId = categoryId)) + def frontpageRole(name: String = random.alphanumeric.take(20).mkString, id: Defaulted[RoleId] = Defaulted.UseDefault): ZIO[ZConnection, Throwable, RoleRow] = (new RoleRepoImpl).insert(new RoleRowUnsaved(name = name, id = id)) + def frontpageUser(email: Email = domainInsert.frontpageEmail(random), + name: String = random.alphanumeric.take(20).mkString, + departmentId: Option[DepartmentId] = None, + managerId: Option[UserId] = None, + id: Defaulted[UserId] = Defaulted.UseDefault, + createdAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault, + status: Defaulted[Option[UserStatus]] = Defaulted.UseDefault, + verified: Defaulted[Option[Boolean]] = Defaulted.UseDefault, + role: Defaulted[Option[UserRole]] = Defaulted.UseDefault + ): ZIO[ZConnection, Throwable, UserRow] = (new UserRepoImpl).insert(new UserRowUnsaved(email = email, name = name, departmentId = departmentId, managerId = managerId, id = id, createdAt = createdAt, status = status, verified = verified, role = role)) + def frontpageUserPermission(userId: UserId, + permissionId: PermissionId, + grantedAt: Defaulted[Option[TypoLocalDateTime]] = Defaulted.UseDefault + ): ZIO[ZConnection, Throwable, UserPermissionRow] = (new UserPermissionRepoImpl).insert(new UserPermissionRowUnsaved(userId = userId, permissionId = permissionId, grantedAt = grantedAt)) def humanresourcesDepartment(name: Name = domainInsert.publicName(random), groupname: Name = domainInsert.publicName(random), - departmentid: Defaulted[DepartmentId] = Defaulted.UseDefault, + departmentid: Defaulted[adventureworks.humanresources.department.DepartmentId] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault - ): ZIO[ZConnection, Throwable, DepartmentRow] = (new DepartmentRepoImpl).insert(new DepartmentRowUnsaved(name = name, groupname = groupname, departmentid = departmentid, modifieddate = modifieddate)) + ): ZIO[ZConnection, Throwable, adventureworks.humanresources.department.DepartmentRow] = (new adventureworks.humanresources.department.DepartmentRepoImpl).insert(new adventureworks.humanresources.department.DepartmentRowUnsaved(name = name, groupname = groupname, departmentid = departmentid, modifieddate = modifieddate)) def humanresourcesEmployee(businessentityid: BusinessentityId, birthdate: TypoLocalDate, maritalstatus: /* bpchar, max 1 chars */ String, @@ -352,9 +468,9 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault, organizationnode: Defaulted[Option[String]] = Defaulted.UseDefault - ): ZIO[ZConnection, Throwable, EmployeeRow] = (new EmployeeRepoImpl).insert(new EmployeeRowUnsaved(businessentityid = businessentityid, birthdate = birthdate, maritalstatus = maritalstatus, gender = gender, hiredate = hiredate, nationalidnumber = nationalidnumber, loginid = loginid, jobtitle = jobtitle, salariedflag = salariedflag, vacationhours = vacationhours, sickleavehours = sickleavehours, currentflag = currentflag, rowguid = rowguid, modifieddate = modifieddate, organizationnode = organizationnode)) + ): ZIO[ZConnection, Throwable, adventureworks.humanresources.employee.EmployeeRow] = (new adventureworks.humanresources.employee.EmployeeRepoImpl).insert(new adventureworks.humanresources.employee.EmployeeRowUnsaved(businessentityid = businessentityid, birthdate = birthdate, maritalstatus = maritalstatus, gender = gender, hiredate = hiredate, nationalidnumber = nationalidnumber, loginid = loginid, jobtitle = jobtitle, salariedflag = salariedflag, vacationhours = vacationhours, sickleavehours = sickleavehours, currentflag = currentflag, rowguid = rowguid, modifieddate = modifieddate, organizationnode = organizationnode)) def humanresourcesEmployeedepartmenthistory(businessentityid: BusinessentityId, - departmentid: DepartmentId, + departmentid: adventureworks.humanresources.department.DepartmentId, shiftid: ShiftId, startdate: TypoLocalDate, enddate: Option[TypoLocalDate] = None, @@ -383,10 +499,10 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { city: /* max 30 chars */ String = random.alphanumeric.take(20).mkString, postalcode: /* max 15 chars */ String = random.alphanumeric.take(15).mkString, spatiallocation: Option[TypoBytea] = None, - addressid: Defaulted[AddressId] = Defaulted.UseDefault, + addressid: Defaulted[adventureworks.person.address.AddressId] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault - ): ZIO[ZConnection, Throwable, AddressRow] = (new AddressRepoImpl).insert(new AddressRowUnsaved(stateprovinceid = stateprovinceid, addressline1 = addressline1, addressline2 = addressline2, city = city, postalcode = postalcode, spatiallocation = spatiallocation, addressid = addressid, rowguid = rowguid, modifieddate = modifieddate)) + ): ZIO[ZConnection, Throwable, adventureworks.person.address.AddressRow] = (new adventureworks.person.address.AddressRepoImpl).insert(new adventureworks.person.address.AddressRowUnsaved(stateprovinceid = stateprovinceid, addressline1 = addressline1, addressline2 = addressline2, city = city, postalcode = postalcode, spatiallocation = spatiallocation, addressid = addressid, rowguid = rowguid, modifieddate = modifieddate)) def personAddresstype(name: Name = domainInsert.publicName(random), addresstypeid: Defaulted[AddresstypeId] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, @@ -397,7 +513,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ZIO[ZConnection, Throwable, BusinessentityRow] = (new BusinessentityRepoImpl).insert(new BusinessentityRowUnsaved(businessentityid = businessentityid, rowguid = rowguid, modifieddate = modifieddate)) def personBusinessentityaddress(businessentityid: BusinessentityId, - addressid: AddressId, + addressid: adventureworks.person.address.AddressId, addresstypeid: AddresstypeId, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault @@ -441,7 +557,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { emailpromotion: Defaulted[Int] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault - ): ZIO[ZConnection, Throwable, PersonRow] = (new PersonRepoImpl).insert(new PersonRowUnsaved(businessentityid = businessentityid, persontype = persontype, firstname = firstname, title = title, middlename = middlename, lastname = lastname, suffix = suffix, additionalcontactinfo = additionalcontactinfo, demographics = demographics, namestyle = namestyle, emailpromotion = emailpromotion, rowguid = rowguid, modifieddate = modifieddate)) + ): ZIO[ZConnection, Throwable, adventureworks.person.person.PersonRow] = (new adventureworks.person.person.PersonRepoImpl).insert(new adventureworks.person.person.PersonRowUnsaved(businessentityid = businessentityid, persontype = persontype, firstname = firstname, title = title, middlename = middlename, lastname = lastname, suffix = suffix, additionalcontactinfo = additionalcontactinfo, demographics = demographics, namestyle = namestyle, emailpromotion = emailpromotion, rowguid = rowguid, modifieddate = modifieddate)) def personPersonphone(businessentityid: BusinessentityId, phonenumbertypeid: PhonenumbertypeId, phonenumber: Phone = domainInsert.publicPhone(random), @@ -460,10 +576,10 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ZIO[ZConnection, Throwable, StateprovinceRow] = (new StateprovinceRepoImpl).insert(new StateprovinceRowUnsaved(countryregioncode = countryregioncode, territoryid = territoryid, stateprovincecode = stateprovincecode, name = name, stateprovinceid = stateprovinceid, isonlystateprovinceflag = isonlystateprovinceflag, rowguid = rowguid, modifieddate = modifieddate)) - def productionBillofmaterials(componentid: ProductId, + def productionBillofmaterials(componentid: adventureworks.production.product.ProductId, unitmeasurecode: UnitmeasureId, bomlevel: TypoShort, - productassemblyid: Option[ProductId] = None, + productassemblyid: Option[adventureworks.production.product.ProductId] = None, enddate: Option[TypoLocalDateTime] = None, billofmaterialsid: Defaulted[Int] = Defaulted.UseDefault, startdate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault, @@ -493,11 +609,11 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ZIO[ZConnection, Throwable, IllustrationRow] = (new IllustrationRepoImpl).insert(new IllustrationRowUnsaved(diagram = diagram, illustrationid = illustrationid, modifieddate = modifieddate)) def productionLocation(name: Name = domainInsert.publicName(random), - locationid: Defaulted[LocationId] = Defaulted.UseDefault, + locationid: Defaulted[adventureworks.production.location.LocationId] = Defaulted.UseDefault, costrate: Defaulted[BigDecimal] = Defaulted.UseDefault, availability: Defaulted[BigDecimal] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault - ): ZIO[ZConnection, Throwable, LocationRow] = (new LocationRepoImpl).insert(new LocationRowUnsaved(name = name, locationid = locationid, costrate = costrate, availability = availability, modifieddate = modifieddate)) + ): ZIO[ZConnection, Throwable, adventureworks.production.location.LocationRow] = (new adventureworks.production.location.LocationRepoImpl).insert(new adventureworks.production.location.LocationRowUnsaved(name = name, locationid = locationid, costrate = costrate, availability = availability, modifieddate = modifieddate)) def productionProduct(safetystocklevel: TypoShort, reorderpoint: TypoShort, standardcost: BigDecimal, @@ -518,18 +634,18 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { productmodelid: Option[ProductmodelId] = None, sellenddate: Option[TypoLocalDateTime] = None, discontinueddate: Option[TypoLocalDateTime] = if (random.nextBoolean()) None else Some(TypoLocalDateTime(LocalDateTime.of(LocalDate.ofEpochDay(random.nextInt(30000).toLong), LocalTime.ofSecondOfDay(random.nextInt(24 * 60 * 60).toLong)))), - productid: Defaulted[ProductId] = Defaulted.UseDefault, + productid: Defaulted[adventureworks.production.product.ProductId] = Defaulted.UseDefault, makeflag: Defaulted[Flag] = Defaulted.UseDefault, finishedgoodsflag: Defaulted[Flag] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault - ): ZIO[ZConnection, Throwable, ProductRow] = (new ProductRepoImpl).insert(new ProductRowUnsaved(safetystocklevel = safetystocklevel, reorderpoint = reorderpoint, standardcost = standardcost, listprice = listprice, daystomanufacture = daystomanufacture, sellstartdate = sellstartdate, name = name, productnumber = productnumber, color = color, size = size, sizeunitmeasurecode = sizeunitmeasurecode, weightunitmeasurecode = weightunitmeasurecode, weight = weight, productline = productline, `class` = `class`, style = style, productsubcategoryid = productsubcategoryid, productmodelid = productmodelid, sellenddate = sellenddate, discontinueddate = discontinueddate, productid = productid, makeflag = makeflag, finishedgoodsflag = finishedgoodsflag, rowguid = rowguid, modifieddate = modifieddate)) + ): ZIO[ZConnection, Throwable, adventureworks.production.product.ProductRow] = (new adventureworks.production.product.ProductRepoImpl).insert(new adventureworks.production.product.ProductRowUnsaved(safetystocklevel = safetystocklevel, reorderpoint = reorderpoint, standardcost = standardcost, listprice = listprice, daystomanufacture = daystomanufacture, sellstartdate = sellstartdate, name = name, productnumber = productnumber, color = color, size = size, sizeunitmeasurecode = sizeunitmeasurecode, weightunitmeasurecode = weightunitmeasurecode, weight = weight, productline = productline, `class` = `class`, style = style, productsubcategoryid = productsubcategoryid, productmodelid = productmodelid, sellenddate = sellenddate, discontinueddate = discontinueddate, productid = productid, makeflag = makeflag, finishedgoodsflag = finishedgoodsflag, rowguid = rowguid, modifieddate = modifieddate)) def productionProductcategory(name: Name = domainInsert.publicName(random), productcategoryid: Defaulted[ProductcategoryId] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ZIO[ZConnection, Throwable, ProductcategoryRow] = (new ProductcategoryRepoImpl).insert(new ProductcategoryRowUnsaved(name = name, productcategoryid = productcategoryid, rowguid = rowguid, modifieddate = modifieddate)) - def productionProductcosthistory(productid: ProductId, + def productionProductcosthistory(productid: adventureworks.production.product.ProductId, startdate: TypoLocalDateTime, standardcost: BigDecimal, enddate: Option[TypoLocalDateTime] = None, @@ -540,19 +656,19 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ZIO[ZConnection, Throwable, ProductdescriptionRow] = (new ProductdescriptionRepoImpl).insert(new ProductdescriptionRowUnsaved(description = description, productdescriptionid = productdescriptionid, rowguid = rowguid, modifieddate = modifieddate)) - def productionProductdocument(productid: ProductId, + def productionProductdocument(productid: adventureworks.production.product.ProductId, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault, documentnode: Defaulted[DocumentId] = Defaulted.UseDefault ): ZIO[ZConnection, Throwable, ProductdocumentRow] = (new ProductdocumentRepoImpl).insert(new ProductdocumentRowUnsaved(productid = productid, modifieddate = modifieddate, documentnode = documentnode)) - def productionProductinventory(productid: ProductId, - locationid: LocationId, + def productionProductinventory(productid: adventureworks.production.product.ProductId, + locationid: adventureworks.production.location.LocationId, bin: TypoShort, shelf: /* max 10 chars */ String = random.alphanumeric.take(10).mkString, quantity: Defaulted[TypoShort] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ZIO[ZConnection, Throwable, ProductinventoryRow] = (new ProductinventoryRepoImpl).insert(new ProductinventoryRowUnsaved(productid = productid, locationid = locationid, bin = bin, shelf = shelf, quantity = quantity, rowguid = rowguid, modifieddate = modifieddate)) - def productionProductlistpricehistory(productid: ProductId, + def productionProductlistpricehistory(productid: adventureworks.production.product.ProductId, startdate: TypoLocalDateTime, listprice: BigDecimal, enddate: Option[TypoLocalDateTime] = None, @@ -581,12 +697,12 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { productphotoid: Defaulted[ProductphotoId] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ZIO[ZConnection, Throwable, ProductphotoRow] = (new ProductphotoRepoImpl).insert(new ProductphotoRowUnsaved(thumbnailphoto = thumbnailphoto, thumbnailphotofilename = thumbnailphotofilename, largephoto = largephoto, largephotofilename = largephotofilename, productphotoid = productphotoid, modifieddate = modifieddate)) - def productionProductproductphoto(productid: ProductId, + def productionProductproductphoto(productid: adventureworks.production.product.ProductId, productphotoid: ProductphotoId, primary: Defaulted[Flag] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ZIO[ZConnection, Throwable, ProductproductphotoRow] = (new ProductproductphotoRepoImpl).insert(new ProductproductphotoRowUnsaved(productid = productid, productphotoid = productphotoid, primary = primary, modifieddate = modifieddate)) - def productionProductreview(productid: ProductId, + def productionProductreview(productid: adventureworks.production.product.ProductId, rating: Int, reviewername: Name = domainInsert.publicName(random), emailaddress: /* max 50 chars */ String = random.alphanumeric.take(20).mkString, @@ -605,7 +721,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { scrapreasonid: Defaulted[ScrapreasonId] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ZIO[ZConnection, Throwable, ScrapreasonRow] = (new ScrapreasonRepoImpl).insert(new ScrapreasonRowUnsaved(name = name, scrapreasonid = scrapreasonid, modifieddate = modifieddate)) - def productionTransactionhistory(productid: ProductId, + def productionTransactionhistory(productid: adventureworks.production.product.ProductId, transactiontype: /* bpchar, max 1 chars */ String, referenceorderid: Int = random.nextInt(), quantity: Int = random.nextInt(), @@ -629,7 +745,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { name: Name = domainInsert.publicName(random), modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ZIO[ZConnection, Throwable, UnitmeasureRow] = (new UnitmeasureRepoImpl).insert(new UnitmeasureRowUnsaved(unitmeasurecode = unitmeasurecode, name = name, modifieddate = modifieddate)) - def productionWorkorder(productid: ProductId, + def productionWorkorder(productid: adventureworks.production.product.ProductId, orderqty: Int, scrappedqty: TypoShort, startdate: TypoLocalDateTime, @@ -640,7 +756,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ZIO[ZConnection, Throwable, WorkorderRow] = (new WorkorderRepoImpl).insert(new WorkorderRowUnsaved(productid = productid, orderqty = orderqty, scrappedqty = scrappedqty, startdate = startdate, enddate = enddate, duedate = duedate, scrapreasonid = scrapreasonid, workorderid = workorderid, modifieddate = modifieddate)) def productionWorkorderrouting(workorderid: WorkorderId, - locationid: LocationId, + locationid: adventureworks.production.location.LocationId, scheduledstartdate: TypoLocalDateTime, scheduledenddate: TypoLocalDateTime, plannedcost: BigDecimal, @@ -822,7 +938,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { verifiedOn: Option[TypoInstant] = if (random.nextBoolean()) None else Some(TypoInstant(Instant.ofEpochMilli(1000000000000L + random.nextLong(1000000000000L)))), createdAt: Defaulted[TypoInstant] = Defaulted.UseDefault ): ZIO[ZConnection, Throwable, UsersRow] = (new UsersRepoImpl).insert(new UsersRowUnsaved(email = email, userId = userId, name = name, lastName = lastName, password = password, verifiedOn = verifiedOn, createdAt = createdAt)) - def purchasingProductvendor(productid: ProductId, + def purchasingProductvendor(productid: adventureworks.production.product.ProductId, businessentityid: BusinessentityId, averageleadtime: Int, standardprice: BigDecimal, @@ -889,10 +1005,10 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { def salesCustomer(personid: Option[BusinessentityId] = None, storeid: Option[BusinessentityId] = None, territoryid: Option[SalesterritoryId] = None, - customerid: Defaulted[CustomerId] = Defaulted.UseDefault, + customerid: Defaulted[adventureworks.sales.customer.CustomerId] = Defaulted.UseDefault, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault - ): ZIO[ZConnection, Throwable, CustomerRow] = (new CustomerRepoImpl).insert(new CustomerRowUnsaved(personid = personid, storeid = storeid, territoryid = territoryid, customerid = customerid, rowguid = rowguid, modifieddate = modifieddate)) + ): ZIO[ZConnection, Throwable, adventureworks.sales.customer.CustomerRow] = (new adventureworks.sales.customer.CustomerRepoImpl).insert(new adventureworks.sales.customer.CustomerRowUnsaved(personid = personid, storeid = storeid, territoryid = territoryid, customerid = customerid, rowguid = rowguid, modifieddate = modifieddate)) def salesPersoncreditcard(businessentityid: BusinessentityId, creditcardid: /* user-picked */ CustomCreditcardId, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault @@ -908,9 +1024,9 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ZIO[ZConnection, Throwable, SalesorderdetailRow] = (new SalesorderdetailRepoImpl).insert(new SalesorderdetailRowUnsaved(salesorderid = salesorderid, carriertrackingnumber = carriertrackingnumber, orderqty = orderqty, productid = SpecialofferproductId.productid, specialofferid = SpecialofferproductId.specialofferid, unitprice = unitprice, salesorderdetailid = salesorderdetailid, unitpricediscount = unitpricediscount, rowguid = rowguid, modifieddate = modifieddate)) def salesSalesorderheader(duedate: TypoLocalDateTime, - customerid: CustomerId, - billtoaddressid: AddressId, - shiptoaddressid: AddressId, + customerid: adventureworks.sales.customer.CustomerId, + billtoaddressid: adventureworks.person.address.AddressId, + shiptoaddressid: adventureworks.person.address.AddressId, shipmethodid: ShipmethodId, shipdate: Option[TypoLocalDateTime] = None, purchaseordernumber: Option[OrderNumber] = if (random.nextBoolean()) None else Some(domainInsert.publicOrderNumber(random)), @@ -984,7 +1100,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ZIO[ZConnection, Throwable, SalesterritoryhistoryRow] = (new SalesterritoryhistoryRepoImpl).insert(new SalesterritoryhistoryRowUnsaved(businessentityid = businessentityid, territoryid = territoryid, startdate = startdate, enddate = enddate, rowguid = rowguid, modifieddate = modifieddate)) - def salesShoppingcartitem(productid: ProductId, + def salesShoppingcartitem(productid: adventureworks.production.product.ProductId, shoppingcartid: /* max 50 chars */ String = random.alphanumeric.take(20).mkString, shoppingcartitemid: Defaulted[ShoppingcartitemId] = Defaulted.UseDefault, quantity: Defaulted[Int] = Defaulted.UseDefault, @@ -1004,7 +1120,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ZIO[ZConnection, Throwable, SpecialofferRow] = (new SpecialofferRepoImpl).insert(new SpecialofferRowUnsaved(startdate = startdate, enddate = enddate, description = description, `type` = `type`, category = category, maxqty = maxqty, specialofferid = specialofferid, discountpct = discountpct, minqty = minqty, rowguid = rowguid, modifieddate = modifieddate)) def salesSpecialofferproduct(specialofferid: SpecialofferId, - productid: ProductId, + productid: adventureworks.production.product.ProductId, rowguid: Defaulted[TypoUUID] = Defaulted.UseDefault, modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ZIO[ZConnection, Throwable, SpecialofferproductRow] = (new SpecialofferproductRepoImpl).insert(new SpecialofferproductRowUnsaved(specialofferid = specialofferid, productid = productid, rowguid = rowguid, modifieddate = modifieddate)) diff --git a/typo-tester-zio-jdbc/src/scala/adventureworks/DomainInsert.scala b/typo-tester-zio-jdbc/src/scala/adventureworks/DomainInsert.scala index 3906b94e03..8e5b3d1464 100644 --- a/typo-tester-zio-jdbc/src/scala/adventureworks/DomainInsert.scala +++ b/typo-tester-zio-jdbc/src/scala/adventureworks/DomainInsert.scala @@ -13,4 +13,5 @@ object DomainInsert extends TestDomainInsert { override def publicPhone(random: Random): Phone = Phone(random.nextString(10)) override def publicShortText(random: Random): ShortText = ShortText(random.nextString(10)) override def publicOrderNumber(random: Random): OrderNumber = OrderNumber(random.nextString(10)) + override def frontpageEmail(random: Random): frontpage.Email = frontpage.Email(s"user${random.nextInt(1000)}@example.com") }