From 88cb366553a8367bd223f73a72864a3ce058427d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Raddum=20Berg?= Date: Mon, 22 Apr 2024 19:54:21 +0200 Subject: [PATCH] Improve type flow (#89) - Prefer from other tables instead of from self - Add some recursion between inference of column types within a table, to support a table with a foreign key on its own pk --- init/data/test-tables.sql | 22 ++++- .../adventureworks/public/ShortText.scala | 38 ++++++++ .../public/flaff/FlaffFields.scala | 46 ++++++++++ .../adventureworks/public/flaff/FlaffId.scala | 43 ++++++++++ .../public/flaff/FlaffRepo.scala | 26 ++++++ .../public/flaff/FlaffRepoImpl.scala | 80 +++++++++++++++++ .../public/flaff/FlaffRepoMock.scala | 68 +++++++++++++++ .../public/flaff/FlaffRow.scala | 82 ++++++++++++++++++ .../adventureworks/testInsert.scala | 9 ++ .../adventureworks/public/ShortText.scala | 34 ++++++++ .../public/flaff/FlaffFields.scala | 46 ++++++++++ .../adventureworks/public/flaff/FlaffId.scala | 20 +++++ .../public/flaff/FlaffRepo.scala | 27 ++++++ .../public/flaff/FlaffRepoImpl.scala | 75 ++++++++++++++++ .../public/flaff/FlaffRepoMock.scala | 80 +++++++++++++++++ .../public/flaff/FlaffRow.scala | 63 ++++++++++++++ .../adventureworks/testInsert.scala | 9 ++ .../adventureworks/public/ShortText.scala | 36 ++++++++ .../public/flaff/FlaffFields.scala | 46 ++++++++++ .../adventureworks/public/flaff/FlaffId.scala | 46 ++++++++++ .../public/flaff/FlaffRepo.scala | 29 +++++++ .../public/flaff/FlaffRepoImpl.scala | 72 ++++++++++++++++ .../public/flaff/FlaffRepoMock.scala | 81 +++++++++++++++++ .../public/flaff/FlaffRow.scala | 86 +++++++++++++++++++ .../adventureworks/testInsert.scala | 9 ++ .../scala/typo/internal/ComputedTable.scala | 27 +++--- typo/src/scala/typo/internal/IdComputed.scala | 2 +- 27 files changed, 1187 insertions(+), 15 deletions(-) create mode 100644 typo-tester-anorm/generated-and-checked-in/adventureworks/public/ShortText.scala create mode 100644 typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffFields.scala create mode 100644 typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffId.scala create mode 100644 typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffRepo.scala create mode 100644 typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoImpl.scala create mode 100644 typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoMock.scala create mode 100644 typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffRow.scala create mode 100644 typo-tester-doobie/generated-and-checked-in/adventureworks/public/ShortText.scala create mode 100644 typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffFields.scala create mode 100644 typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffId.scala create mode 100644 typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffRepo.scala create mode 100644 typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoImpl.scala create mode 100644 typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoMock.scala create mode 100644 typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffRow.scala create mode 100644 typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/ShortText.scala create mode 100644 typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffFields.scala create mode 100644 typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffId.scala create mode 100644 typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffRepo.scala create mode 100644 typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoImpl.scala create mode 100644 typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoMock.scala create mode 100644 typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffRow.scala diff --git a/init/data/test-tables.sql b/init/data/test-tables.sql index 1e81854733..0d93b1f5b3 100644 --- a/init/data/test-tables.sql +++ b/init/data/test-tables.sql @@ -196,7 +196,23 @@ CREATE TABLE users CREATE TABLE "identity-test" ( - always_generated Integer NOT NULL GENERATED ALWAYS AS IDENTITY (INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1), - default_generated Integer NOT NULL GENERATED BY DEFAULT AS IDENTITY (INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1), - name Character varying(250) NOT NULL primary key + always_generated Integer NOT NULL GENERATED ALWAYS AS IDENTITY (INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1), + default_generated Integer NOT NULL GENERATED BY DEFAULT AS IDENTITY (INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1), + name Character varying(250) NOT NULL primary key ); + +create domain short_text as text + constraint short_text_check check (length(VALUE) <= 55); + +create table flaff +( + code short_text not null, + another_code varchar(20) not null, + some_number integer not null, + specifier short_text not null, + parentSpecifier short_text, + constraint flaff_pk primary key (code, another_code, some_number, specifier), + constraint flaff_parent_fk foreign key (code, another_code, some_number, parentSpecifier) references flaff +); + + diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/public/ShortText.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/ShortText.scala new file mode 100644 index 0000000000..4ff900cc1f --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/ShortText.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 public + +import adventureworks.Text +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 + +/** Domain `public.short_text` + * Constraint: CHECK ((length(VALUE) <= 55)) + */ +case class ShortText(value: String) +object ShortText { + implicit lazy val arrayColumn: Column[Array[ShortText]] = Column.columnToArray(column, implicitly) + implicit lazy val arrayToStatement: ToStatement[Array[ShortText]] = ToStatement.arrayToParameter(ParameterMetaData.StringParameterMetaData).contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[ShortText, String] = Bijection[ShortText, String](_.value)(ShortText.apply) + implicit lazy val column: Column[ShortText] = Column.columnToString.map(ShortText.apply) + implicit lazy val ordering: Ordering[ShortText] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[ShortText] = new ParameterMetaData[ShortText] { + override def sqlType: String = ParameterMetaData.StringParameterMetaData.sqlType + override def jdbcType: Int = ParameterMetaData.StringParameterMetaData.jdbcType + } + implicit lazy val reads: Reads[ShortText] = Reads.StringReads.map(ShortText.apply) + implicit lazy val text: Text[ShortText] = new Text[ShortText] { + override def unsafeEncode(v: ShortText, sb: StringBuilder) = Text.stringInstance.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: ShortText, sb: StringBuilder) = Text.stringInstance.unsafeArrayEncode(v.value, sb) + } + implicit lazy val toStatement: ToStatement[ShortText] = ToStatement.stringToStatement.contramap(_.value) + implicit lazy val writes: Writes[ShortText] = Writes.StringWrites.contramap(_.value) +} \ No newline at end of file diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffFields.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffFields.scala new file mode 100644 index 0000000000..11ce63aec6 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffFields.scala @@ -0,0 +1,46 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package public +package flaff + +import adventureworks.public.ShortText +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait FlaffFields[Row] { + val code: IdField[ShortText, Row] + val anotherCode: IdField[/* max 20 chars */ String, Row] + val someNumber: IdField[Int, Row] + val specifier: IdField[ShortText, Row] + val parentspecifier: OptField[ShortText, Row] +} + +object FlaffFields { + val structure: Relation[FlaffFields, FlaffRow, FlaffRow] = + new Impl(None, identity, (_, x) => x) + + private final class Impl[Row](val prefix: Option[String], val extract: Row => FlaffRow, val merge: (Row, FlaffRow) => Row) + extends Relation[FlaffFields, FlaffRow, Row] { + + override val fields: FlaffFields[Row] = new FlaffFields[Row] { + override val code = new IdField[ShortText, Row](prefix, "code", None, Some("text"))(x => extract(x).code, (row, value) => merge(row, extract(row).copy(code = value))) + override val anotherCode = new IdField[/* max 20 chars */ String, Row](prefix, "another_code", None, None)(x => extract(x).anotherCode, (row, value) => merge(row, extract(row).copy(anotherCode = value))) + override val someNumber = new IdField[Int, Row](prefix, "some_number", None, Some("int4"))(x => extract(x).someNumber, (row, value) => merge(row, extract(row).copy(someNumber = value))) + override val specifier = new IdField[ShortText, Row](prefix, "specifier", None, Some("text"))(x => extract(x).specifier, (row, value) => merge(row, extract(row).copy(specifier = value))) + override val parentspecifier = new OptField[ShortText, Row](prefix, "parentspecifier", None, Some("text"))(x => extract(x).parentspecifier, (row, value) => merge(row, extract(row).copy(parentspecifier = value))) + } + + override val columns: List[FieldLikeNoHkt[?, Row]] = + List[FieldLikeNoHkt[?, Row]](fields.code, fields.anotherCode, fields.someNumber, fields.specifier, fields.parentspecifier) + + override def copy[NewRow](prefix: Option[String], extract: NewRow => FlaffRow, merge: (NewRow, FlaffRow) => NewRow): Impl[NewRow] = + new Impl(prefix, extract, merge) + } + +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffId.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffId.scala new file mode 100644 index 0000000000..4b4e6e6697 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffId.scala @@ -0,0 +1,43 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package public +package flaff + +import adventureworks.public.ShortText +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 + +/** Type for the composite primary key of table `public.flaff` */ +case class FlaffId(code: ShortText, anotherCode: /* max 20 chars */ String, someNumber: Int, specifier: ShortText) +object FlaffId { + implicit lazy val ordering: Ordering[FlaffId] = Ordering.by(x => (x.code, x.anotherCode, x.someNumber, x.specifier)) + implicit lazy val reads: Reads[FlaffId] = Reads[FlaffId](json => JsResult.fromTry( + Try( + FlaffId( + code = json.\("code").as(ShortText.reads), + anotherCode = json.\("another_code").as(Reads.StringReads), + someNumber = json.\("some_number").as(Reads.IntReads), + specifier = json.\("specifier").as(ShortText.reads) + ) + ) + ), + ) + implicit lazy val writes: OWrites[FlaffId] = OWrites[FlaffId](o => + new JsObject(ListMap[String, JsValue]( + "code" -> ShortText.writes.writes(o.code), + "another_code" -> Writes.StringWrites.writes(o.anotherCode), + "some_number" -> Writes.IntWrites.writes(o.someNumber), + "specifier" -> ShortText.writes.writes(o.specifier) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffRepo.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffRepo.scala new file mode 100644 index 0000000000..856fe1fa98 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffRepo.scala @@ -0,0 +1,26 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package public +package flaff + +import java.sql.Connection +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait FlaffRepo { + def delete(compositeId: FlaffId)(implicit c: Connection): Boolean + def delete: DeleteBuilder[FlaffFields, FlaffRow] + def insert(unsaved: FlaffRow)(implicit c: Connection): FlaffRow + def insertStreaming(unsaved: Iterator[FlaffRow], batchSize: Int)(implicit c: Connection): Long + def select: SelectBuilder[FlaffFields, FlaffRow] + def selectAll(implicit c: Connection): List[FlaffRow] + def selectById(compositeId: FlaffId)(implicit c: Connection): Option[FlaffRow] + def update(row: FlaffRow)(implicit c: Connection): Boolean + def update: UpdateBuilder[FlaffFields, FlaffRow] + def upsert(unsaved: FlaffRow)(implicit c: Connection): FlaffRow +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoImpl.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoImpl.scala new file mode 100644 index 0000000000..96d33ad24d --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoImpl.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 public +package flaff + +import adventureworks.public.ShortText +import adventureworks.streamingInsert +import anorm.ParameterValue +import anorm.SqlStringInterpolation +import anorm.ToStatement +import java.sql.Connection +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class FlaffRepoImpl extends FlaffRepo { + override def delete(compositeId: FlaffId)(implicit c: Connection): Boolean = { + SQL"""delete from public.flaff where "code" = ${ParameterValue(compositeId.code, null, ShortText.toStatement)} AND "another_code" = ${ParameterValue(compositeId.anotherCode, null, ToStatement.stringToStatement)} AND "some_number" = ${ParameterValue(compositeId.someNumber, null, ToStatement.intToStatement)} AND "specifier" = ${ParameterValue(compositeId.specifier, null, ShortText.toStatement)}""".executeUpdate() > 0 + } + override def delete: DeleteBuilder[FlaffFields, FlaffRow] = { + DeleteBuilder("public.flaff", FlaffFields.structure) + } + override def insert(unsaved: FlaffRow)(implicit c: Connection): FlaffRow = { + SQL"""insert into public.flaff("code", "another_code", "some_number", "specifier", "parentspecifier") + values (${ParameterValue(unsaved.code, null, ShortText.toStatement)}::text, ${ParameterValue(unsaved.anotherCode, null, ToStatement.stringToStatement)}, ${ParameterValue(unsaved.someNumber, null, ToStatement.intToStatement)}::int4, ${ParameterValue(unsaved.specifier, null, ShortText.toStatement)}::text, ${ParameterValue(unsaved.parentspecifier, null, ToStatement.optionToStatement(ShortText.toStatement, ShortText.parameterMetadata))}::text) + returning "code", "another_code", "some_number", "specifier", "parentspecifier" + """ + .executeInsert(FlaffRow.rowParser(1).single) + + } + override def insertStreaming(unsaved: Iterator[FlaffRow], batchSize: Int)(implicit c: Connection): Long = { + streamingInsert(s"""COPY public.flaff("code", "another_code", "some_number", "specifier", "parentspecifier") FROM STDIN""", batchSize, unsaved)(FlaffRow.text, c) + } + override def select: SelectBuilder[FlaffFields, FlaffRow] = { + SelectBuilderSql("public.flaff", FlaffFields.structure, FlaffRow.rowParser) + } + override def selectAll(implicit c: Connection): List[FlaffRow] = { + SQL"""select "code", "another_code", "some_number", "specifier", "parentspecifier" + from public.flaff + """.as(FlaffRow.rowParser(1).*) + } + override def selectById(compositeId: FlaffId)(implicit c: Connection): Option[FlaffRow] = { + SQL"""select "code", "another_code", "some_number", "specifier", "parentspecifier" + from public.flaff + where "code" = ${ParameterValue(compositeId.code, null, ShortText.toStatement)} AND "another_code" = ${ParameterValue(compositeId.anotherCode, null, ToStatement.stringToStatement)} AND "some_number" = ${ParameterValue(compositeId.someNumber, null, ToStatement.intToStatement)} AND "specifier" = ${ParameterValue(compositeId.specifier, null, ShortText.toStatement)} + """.as(FlaffRow.rowParser(1).singleOpt) + } + override def update(row: FlaffRow)(implicit c: Connection): Boolean = { + val compositeId = row.compositeId + SQL"""update public.flaff + set "parentspecifier" = ${ParameterValue(row.parentspecifier, null, ToStatement.optionToStatement(ShortText.toStatement, ShortText.parameterMetadata))}::text + where "code" = ${ParameterValue(compositeId.code, null, ShortText.toStatement)} AND "another_code" = ${ParameterValue(compositeId.anotherCode, null, ToStatement.stringToStatement)} AND "some_number" = ${ParameterValue(compositeId.someNumber, null, ToStatement.intToStatement)} AND "specifier" = ${ParameterValue(compositeId.specifier, null, ShortText.toStatement)} + """.executeUpdate() > 0 + } + override def update: UpdateBuilder[FlaffFields, FlaffRow] = { + UpdateBuilder("public.flaff", FlaffFields.structure, FlaffRow.rowParser) + } + override def upsert(unsaved: FlaffRow)(implicit c: Connection): FlaffRow = { + SQL"""insert into public.flaff("code", "another_code", "some_number", "specifier", "parentspecifier") + values ( + ${ParameterValue(unsaved.code, null, ShortText.toStatement)}::text, + ${ParameterValue(unsaved.anotherCode, null, ToStatement.stringToStatement)}, + ${ParameterValue(unsaved.someNumber, null, ToStatement.intToStatement)}::int4, + ${ParameterValue(unsaved.specifier, null, ShortText.toStatement)}::text, + ${ParameterValue(unsaved.parentspecifier, null, ToStatement.optionToStatement(ShortText.toStatement, ShortText.parameterMetadata))}::text + ) + on conflict ("code", "another_code", "some_number", "specifier") + do update set + "parentspecifier" = EXCLUDED."parentspecifier" + returning "code", "another_code", "some_number", "specifier", "parentspecifier" + """ + .executeInsert(FlaffRow.rowParser(1).single) + + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoMock.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoMock.scala new file mode 100644 index 0000000000..10f2ded7b8 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoMock.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 public +package flaff + +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 FlaffRepoMock(map: scala.collection.mutable.Map[FlaffId, FlaffRow] = scala.collection.mutable.Map.empty) extends FlaffRepo { + override def delete(compositeId: FlaffId)(implicit c: Connection): Boolean = { + map.remove(compositeId).isDefined + } + override def delete: DeleteBuilder[FlaffFields, FlaffRow] = { + DeleteBuilderMock(DeleteParams.empty, FlaffFields.structure.fields, map) + } + override def insert(unsaved: FlaffRow)(implicit c: Connection): FlaffRow = { + 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[FlaffRow], batchSize: Int)(implicit c: Connection): Long = { + unsaved.foreach { row => + map += (row.compositeId -> row) + } + unsaved.size.toLong + } + override def select: SelectBuilder[FlaffFields, FlaffRow] = { + SelectBuilderMock(FlaffFields.structure, () => map.values.toList, SelectParams.empty) + } + override def selectAll(implicit c: Connection): List[FlaffRow] = { + map.values.toList + } + override def selectById(compositeId: FlaffId)(implicit c: Connection): Option[FlaffRow] = { + map.get(compositeId) + } + override def update(row: FlaffRow)(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 update: UpdateBuilder[FlaffFields, FlaffRow] = { + UpdateBuilderMock(UpdateParams.empty, FlaffFields.structure.fields, map) + } + override def upsert(unsaved: FlaffRow)(implicit c: Connection): FlaffRow = { + map.put(unsaved.compositeId, unsaved): @nowarn + unsaved + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffRow.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffRow.scala new file mode 100644 index 0000000000..868f512cdf --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/flaff/FlaffRow.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 public +package flaff + +import adventureworks.Text +import adventureworks.public.ShortText +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 + +case class FlaffRow( + /** Points to [[FlaffRow.code]] */ + code: ShortText, + /** Points to [[FlaffRow.anotherCode]] */ + anotherCode: /* max 20 chars */ String, + /** Points to [[FlaffRow.someNumber]] */ + someNumber: Int, + specifier: ShortText, + /** Points to [[FlaffRow.specifier]] */ + parentspecifier: Option[ShortText] +){ + val compositeId: FlaffId = FlaffId(code, anotherCode, someNumber, specifier) + } + +object FlaffRow { + implicit lazy val reads: Reads[FlaffRow] = Reads[FlaffRow](json => JsResult.fromTry( + Try( + FlaffRow( + code = json.\("code").as(ShortText.reads), + anotherCode = json.\("another_code").as(Reads.StringReads), + someNumber = json.\("some_number").as(Reads.IntReads), + specifier = json.\("specifier").as(ShortText.reads), + parentspecifier = json.\("parentspecifier").toOption.map(_.as(ShortText.reads)) + ) + ) + ), + ) + def rowParser(idx: Int): RowParser[FlaffRow] = RowParser[FlaffRow] { row => + Success( + FlaffRow( + code = row(idx + 0)(ShortText.column), + anotherCode = row(idx + 1)(Column.columnToString), + someNumber = row(idx + 2)(Column.columnToInt), + specifier = row(idx + 3)(ShortText.column), + parentspecifier = row(idx + 4)(Column.columnToOption(ShortText.column)) + ) + ) + } + implicit lazy val text: Text[FlaffRow] = Text.instance[FlaffRow]{ (row, sb) => + ShortText.text.unsafeEncode(row.code, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.anotherCode, sb) + sb.append(Text.DELIMETER) + Text.intInstance.unsafeEncode(row.someNumber, sb) + sb.append(Text.DELIMETER) + ShortText.text.unsafeEncode(row.specifier, sb) + sb.append(Text.DELIMETER) + Text.option(ShortText.text).unsafeEncode(row.parentspecifier, sb) + } + implicit lazy val writes: OWrites[FlaffRow] = OWrites[FlaffRow](o => + new JsObject(ListMap[String, JsValue]( + "code" -> ShortText.writes.writes(o.code), + "another_code" -> Writes.StringWrites.writes(o.anotherCode), + "some_number" -> Writes.IntWrites.writes(o.someNumber), + "specifier" -> ShortText.writes.writes(o.specifier), + "parentspecifier" -> Writes.OptionWrites(ShortText.writes).writes(o.parentspecifier) + )) + ) +} 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 678ad312f8..292a31681c 100644 --- a/typo-tester-anorm/generated-and-checked-in/adventureworks/testInsert.scala +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/testInsert.scala @@ -197,6 +197,9 @@ import adventureworks.public.Name import adventureworks.public.NameStyle import adventureworks.public.OrderNumber import adventureworks.public.Phone +import adventureworks.public.ShortText +import adventureworks.public.flaff.FlaffRepoImpl +import adventureworks.public.flaff.FlaffRow import adventureworks.public.identity_test.IdentityTestId import adventureworks.public.identity_test.IdentityTestRepoImpl import adventureworks.public.identity_test.IdentityTestRow @@ -620,6 +623,12 @@ class TestInsert(random: Random) { actualcost: Option[BigDecimal] = if (random.nextBoolean()) None else Some(BigDecimal.decimal(random.nextDouble())), modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault )(implicit c: Connection): WorkorderroutingRow = (new WorkorderroutingRepoImpl).insert(new WorkorderroutingRowUnsaved(workorderid = workorderid, locationid = locationid, productid = productid, operationsequence = operationsequence, scheduledstartdate = scheduledstartdate, scheduledenddate = scheduledenddate, actualstartdate = actualstartdate, actualenddate = actualenddate, actualresourcehrs = actualresourcehrs, plannedcost = plannedcost, actualcost = actualcost, modifieddate = modifieddate)) + def publicFlaff(code: ShortText = ShortText(random.alphanumeric.take(20).mkString), + anotherCode: /* max 20 chars */ String = random.alphanumeric.take(20).mkString, + someNumber: Int = random.nextInt(), + specifier: ShortText = ShortText(random.alphanumeric.take(20).mkString), + parentspecifier: Option[ShortText] = if (random.nextBoolean()) None else Some(ShortText(random.alphanumeric.take(20).mkString)) + )(implicit c: Connection): FlaffRow = (new FlaffRepoImpl).insert(new FlaffRow(code = code, anotherCode = anotherCode, someNumber = someNumber, specifier = specifier, parentspecifier = parentspecifier)) def publicIdentityTest(name: IdentityTestId, defaultGenerated: Defaulted[Int] = Defaulted.UseDefault)(implicit c: Connection): IdentityTestRow = (new IdentityTestRepoImpl).insert(new IdentityTestRowUnsaved(name = name, defaultGenerated = defaultGenerated)) def publicPgtest(box: TypoBox, bytea: TypoBytea, diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/public/ShortText.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/ShortText.scala new file mode 100644 index 0000000000..cf12d89cd8 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/ShortText.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 public + +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 `public.short_text` + * Constraint: CHECK ((length(VALUE) <= 55)) + */ +case class ShortText(value: String) +object ShortText { + implicit lazy val arrayGet: Get[Array[ShortText]] = adventureworks.StringArrayMeta.get.map(_.map(ShortText.apply)) + implicit lazy val arrayPut: Put[Array[ShortText]] = adventureworks.StringArrayMeta.put.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[ShortText, String] = Bijection[ShortText, String](_.value)(ShortText.apply) + implicit lazy val decoder: Decoder[ShortText] = Decoder.decodeString.map(ShortText.apply) + implicit lazy val encoder: Encoder[ShortText] = Encoder.encodeString.contramap(_.value) + implicit lazy val get: Get[ShortText] = Meta.StringMeta.get.map(ShortText.apply) + implicit lazy val ordering: Ordering[ShortText] = Ordering.by(_.value) + implicit lazy val put: Put[ShortText] = Meta.StringMeta.put.contramap(_.value) + implicit lazy val text: Text[ShortText] = new Text[ShortText] { + override def unsafeEncode(v: ShortText, sb: StringBuilder) = Text.stringInstance.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: ShortText, 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/public/flaff/FlaffFields.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffFields.scala new file mode 100644 index 0000000000..11ce63aec6 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffFields.scala @@ -0,0 +1,46 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package public +package flaff + +import adventureworks.public.ShortText +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait FlaffFields[Row] { + val code: IdField[ShortText, Row] + val anotherCode: IdField[/* max 20 chars */ String, Row] + val someNumber: IdField[Int, Row] + val specifier: IdField[ShortText, Row] + val parentspecifier: OptField[ShortText, Row] +} + +object FlaffFields { + val structure: Relation[FlaffFields, FlaffRow, FlaffRow] = + new Impl(None, identity, (_, x) => x) + + private final class Impl[Row](val prefix: Option[String], val extract: Row => FlaffRow, val merge: (Row, FlaffRow) => Row) + extends Relation[FlaffFields, FlaffRow, Row] { + + override val fields: FlaffFields[Row] = new FlaffFields[Row] { + override val code = new IdField[ShortText, Row](prefix, "code", None, Some("text"))(x => extract(x).code, (row, value) => merge(row, extract(row).copy(code = value))) + override val anotherCode = new IdField[/* max 20 chars */ String, Row](prefix, "another_code", None, None)(x => extract(x).anotherCode, (row, value) => merge(row, extract(row).copy(anotherCode = value))) + override val someNumber = new IdField[Int, Row](prefix, "some_number", None, Some("int4"))(x => extract(x).someNumber, (row, value) => merge(row, extract(row).copy(someNumber = value))) + override val specifier = new IdField[ShortText, Row](prefix, "specifier", None, Some("text"))(x => extract(x).specifier, (row, value) => merge(row, extract(row).copy(specifier = value))) + override val parentspecifier = new OptField[ShortText, Row](prefix, "parentspecifier", None, Some("text"))(x => extract(x).parentspecifier, (row, value) => merge(row, extract(row).copy(parentspecifier = value))) + } + + override val columns: List[FieldLikeNoHkt[?, Row]] = + List[FieldLikeNoHkt[?, Row]](fields.code, fields.anotherCode, fields.someNumber, fields.specifier, fields.parentspecifier) + + override def copy[NewRow](prefix: Option[String], extract: NewRow => FlaffRow, merge: (NewRow, FlaffRow) => NewRow): Impl[NewRow] = + new Impl(prefix, extract, merge) + } + +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffId.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffId.scala new file mode 100644 index 0000000000..78d1b33204 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffId.scala @@ -0,0 +1,20 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package public +package flaff + +import adventureworks.public.ShortText +import io.circe.Decoder +import io.circe.Encoder + +/** Type for the composite primary key of table `public.flaff` */ +case class FlaffId(code: ShortText, anotherCode: /* max 20 chars */ String, someNumber: Int, specifier: ShortText) +object FlaffId { + implicit lazy val decoder: Decoder[FlaffId] = Decoder.forProduct4[FlaffId, ShortText, /* max 20 chars */ String, Int, ShortText]("code", "another_code", "some_number", "specifier")(FlaffId.apply)(ShortText.decoder, Decoder.decodeString, Decoder.decodeInt, ShortText.decoder) + implicit lazy val encoder: Encoder[FlaffId] = Encoder.forProduct4[FlaffId, ShortText, /* max 20 chars */ String, Int, ShortText]("code", "another_code", "some_number", "specifier")(x => (x.code, x.anotherCode, x.someNumber, x.specifier))(ShortText.encoder, Encoder.encodeString, Encoder.encodeInt, ShortText.encoder) + implicit lazy val ordering: Ordering[FlaffId] = Ordering.by(x => (x.code, x.anotherCode, x.someNumber, x.specifier)) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffRepo.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffRepo.scala new file mode 100644 index 0000000000..9ef069b74f --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffRepo.scala @@ -0,0 +1,27 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package public +package flaff + +import doobie.free.connection.ConnectionIO +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait FlaffRepo { + def delete(compositeId: FlaffId): ConnectionIO[Boolean] + def delete: DeleteBuilder[FlaffFields, FlaffRow] + def insert(unsaved: FlaffRow): ConnectionIO[FlaffRow] + def insertStreaming(unsaved: Stream[ConnectionIO, FlaffRow], batchSize: Int): ConnectionIO[Long] + def select: SelectBuilder[FlaffFields, FlaffRow] + def selectAll: Stream[ConnectionIO, FlaffRow] + def selectById(compositeId: FlaffId): ConnectionIO[Option[FlaffRow]] + def update(row: FlaffRow): ConnectionIO[Boolean] + def update: UpdateBuilder[FlaffFields, FlaffRow] + def upsert(unsaved: FlaffRow): ConnectionIO[FlaffRow] +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoImpl.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoImpl.scala new file mode 100644 index 0000000000..a30983f180 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoImpl.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 public +package flaff + +import adventureworks.public.ShortText +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.meta.Meta +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.SelectBuilderSql +import typo.dsl.UpdateBuilder + +class FlaffRepoImpl extends FlaffRepo { + override def delete(compositeId: FlaffId): ConnectionIO[Boolean] = { + sql"""delete from public.flaff where "code" = ${fromWrite(compositeId.code)(Write.fromPut(ShortText.put))} AND "another_code" = ${fromWrite(compositeId.anotherCode)(Write.fromPut(Meta.StringMeta.put))} AND "some_number" = ${fromWrite(compositeId.someNumber)(Write.fromPut(Meta.IntMeta.put))} AND "specifier" = ${fromWrite(compositeId.specifier)(Write.fromPut(ShortText.put))}""".update.run.map(_ > 0) + } + override def delete: DeleteBuilder[FlaffFields, FlaffRow] = { + DeleteBuilder("public.flaff", FlaffFields.structure) + } + override def insert(unsaved: FlaffRow): ConnectionIO[FlaffRow] = { + sql"""insert into public.flaff("code", "another_code", "some_number", "specifier", "parentspecifier") + values (${fromWrite(unsaved.code)(Write.fromPut(ShortText.put))}::text, ${fromWrite(unsaved.anotherCode)(Write.fromPut(Meta.StringMeta.put))}, ${fromWrite(unsaved.someNumber)(Write.fromPut(Meta.IntMeta.put))}::int4, ${fromWrite(unsaved.specifier)(Write.fromPut(ShortText.put))}::text, ${fromWrite(unsaved.parentspecifier)(Write.fromPutOption(ShortText.put))}::text) + returning "code", "another_code", "some_number", "specifier", "parentspecifier" + """.query(using FlaffRow.read).unique + } + override def insertStreaming(unsaved: Stream[ConnectionIO, FlaffRow], batchSize: Int): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY public.flaff("code", "another_code", "some_number", "specifier", "parentspecifier") FROM STDIN""").copyIn(unsaved, batchSize)(using FlaffRow.text) + } + override def select: SelectBuilder[FlaffFields, FlaffRow] = { + SelectBuilderSql("public.flaff", FlaffFields.structure, FlaffRow.read) + } + override def selectAll: Stream[ConnectionIO, FlaffRow] = { + sql"""select "code", "another_code", "some_number", "specifier", "parentspecifier" from public.flaff""".query(using FlaffRow.read).stream + } + override def selectById(compositeId: FlaffId): ConnectionIO[Option[FlaffRow]] = { + sql"""select "code", "another_code", "some_number", "specifier", "parentspecifier" from public.flaff where "code" = ${fromWrite(compositeId.code)(Write.fromPut(ShortText.put))} AND "another_code" = ${fromWrite(compositeId.anotherCode)(Write.fromPut(Meta.StringMeta.put))} AND "some_number" = ${fromWrite(compositeId.someNumber)(Write.fromPut(Meta.IntMeta.put))} AND "specifier" = ${fromWrite(compositeId.specifier)(Write.fromPut(ShortText.put))}""".query(using FlaffRow.read).option + } + override def update(row: FlaffRow): ConnectionIO[Boolean] = { + val compositeId = row.compositeId + sql"""update public.flaff + set "parentspecifier" = ${fromWrite(row.parentspecifier)(Write.fromPutOption(ShortText.put))}::text + where "code" = ${fromWrite(compositeId.code)(Write.fromPut(ShortText.put))} AND "another_code" = ${fromWrite(compositeId.anotherCode)(Write.fromPut(Meta.StringMeta.put))} AND "some_number" = ${fromWrite(compositeId.someNumber)(Write.fromPut(Meta.IntMeta.put))} AND "specifier" = ${fromWrite(compositeId.specifier)(Write.fromPut(ShortText.put))}""" + .update + .run + .map(_ > 0) + } + override def update: UpdateBuilder[FlaffFields, FlaffRow] = { + UpdateBuilder("public.flaff", FlaffFields.structure, FlaffRow.read) + } + override def upsert(unsaved: FlaffRow): ConnectionIO[FlaffRow] = { + sql"""insert into public.flaff("code", "another_code", "some_number", "specifier", "parentspecifier") + values ( + ${fromWrite(unsaved.code)(Write.fromPut(ShortText.put))}::text, + ${fromWrite(unsaved.anotherCode)(Write.fromPut(Meta.StringMeta.put))}, + ${fromWrite(unsaved.someNumber)(Write.fromPut(Meta.IntMeta.put))}::int4, + ${fromWrite(unsaved.specifier)(Write.fromPut(ShortText.put))}::text, + ${fromWrite(unsaved.parentspecifier)(Write.fromPutOption(ShortText.put))}::text + ) + on conflict ("code", "another_code", "some_number", "specifier") + do update set + "parentspecifier" = EXCLUDED."parentspecifier" + returning "code", "another_code", "some_number", "specifier", "parentspecifier" + """.query(using FlaffRow.read).unique + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoMock.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoMock.scala new file mode 100644 index 0000000000..fad3e9a823 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoMock.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 public +package flaff + +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 FlaffRepoMock(map: scala.collection.mutable.Map[FlaffId, FlaffRow] = scala.collection.mutable.Map.empty) extends FlaffRepo { + override def delete(compositeId: FlaffId): ConnectionIO[Boolean] = { + delay(map.remove(compositeId).isDefined) + } + override def delete: DeleteBuilder[FlaffFields, FlaffRow] = { + DeleteBuilderMock(DeleteParams.empty, FlaffFields.structure.fields, map) + } + override def insert(unsaved: FlaffRow): ConnectionIO[FlaffRow] = { + 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, FlaffRow], batchSize: Int): ConnectionIO[Long] = { + unsaved.compile.toList.map { rows => + var num = 0L + rows.foreach { row => + map += (row.compositeId -> row) + num += 1 + } + num + } + } + override def select: SelectBuilder[FlaffFields, FlaffRow] = { + SelectBuilderMock(FlaffFields.structure, delay(map.values.toList), SelectParams.empty) + } + override def selectAll: Stream[ConnectionIO, FlaffRow] = { + Stream.emits(map.values.toList) + } + override def selectById(compositeId: FlaffId): ConnectionIO[Option[FlaffRow]] = { + delay(map.get(compositeId)) + } + override def update(row: FlaffRow): 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 update: UpdateBuilder[FlaffFields, FlaffRow] = { + UpdateBuilderMock(UpdateParams.empty, FlaffFields.structure.fields, map) + } + override def upsert(unsaved: FlaffRow): ConnectionIO[FlaffRow] = { + delay { + map.put(unsaved.compositeId, unsaved): @nowarn + unsaved + } + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffRow.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffRow.scala new file mode 100644 index 0000000000..2b4d0f7abd --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/flaff/FlaffRow.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 public +package flaff + +import adventureworks.public.ShortText +import doobie.enumerated.Nullability +import doobie.postgres.Text +import doobie.util.Read +import doobie.util.meta.Meta +import io.circe.Decoder +import io.circe.Encoder +import java.sql.ResultSet + +case class FlaffRow( + /** Points to [[FlaffRow.code]] */ + code: ShortText, + /** Points to [[FlaffRow.anotherCode]] */ + anotherCode: /* max 20 chars */ String, + /** Points to [[FlaffRow.someNumber]] */ + someNumber: Int, + specifier: ShortText, + /** Points to [[FlaffRow.specifier]] */ + parentspecifier: Option[ShortText] +){ + val compositeId: FlaffId = FlaffId(code, anotherCode, someNumber, specifier) + } + +object FlaffRow { + implicit lazy val decoder: Decoder[FlaffRow] = Decoder.forProduct5[FlaffRow, ShortText, /* max 20 chars */ String, Int, ShortText, Option[ShortText]]("code", "another_code", "some_number", "specifier", "parentspecifier")(FlaffRow.apply)(ShortText.decoder, Decoder.decodeString, Decoder.decodeInt, ShortText.decoder, Decoder.decodeOption(ShortText.decoder)) + implicit lazy val encoder: Encoder[FlaffRow] = Encoder.forProduct5[FlaffRow, ShortText, /* max 20 chars */ String, Int, ShortText, Option[ShortText]]("code", "another_code", "some_number", "specifier", "parentspecifier")(x => (x.code, x.anotherCode, x.someNumber, x.specifier, x.parentspecifier))(ShortText.encoder, Encoder.encodeString, Encoder.encodeInt, ShortText.encoder, Encoder.encodeOption(ShortText.encoder)) + implicit lazy val read: Read[FlaffRow] = new Read[FlaffRow]( + gets = List( + (ShortText.get, Nullability.NoNulls), + (Meta.StringMeta.get, Nullability.NoNulls), + (Meta.IntMeta.get, Nullability.NoNulls), + (ShortText.get, Nullability.NoNulls), + (ShortText.get, Nullability.Nullable) + ), + unsafeGet = (rs: ResultSet, i: Int) => FlaffRow( + code = ShortText.get.unsafeGetNonNullable(rs, i + 0), + anotherCode = Meta.StringMeta.get.unsafeGetNonNullable(rs, i + 1), + someNumber = Meta.IntMeta.get.unsafeGetNonNullable(rs, i + 2), + specifier = ShortText.get.unsafeGetNonNullable(rs, i + 3), + parentspecifier = ShortText.get.unsafeGetNullable(rs, i + 4) + ) + ) + implicit lazy val text: Text[FlaffRow] = Text.instance[FlaffRow]{ (row, sb) => + ShortText.text.unsafeEncode(row.code, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.anotherCode, sb) + sb.append(Text.DELIMETER) + Text.intInstance.unsafeEncode(row.someNumber, sb) + sb.append(Text.DELIMETER) + ShortText.text.unsafeEncode(row.specifier, sb) + sb.append(Text.DELIMETER) + Text.option(ShortText.text).unsafeEncode(row.parentspecifier, 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 6894191527..95238741c7 100644 --- a/typo-tester-doobie/generated-and-checked-in/adventureworks/testInsert.scala +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/testInsert.scala @@ -197,6 +197,9 @@ import adventureworks.public.Name import adventureworks.public.NameStyle import adventureworks.public.OrderNumber import adventureworks.public.Phone +import adventureworks.public.ShortText +import adventureworks.public.flaff.FlaffRepoImpl +import adventureworks.public.flaff.FlaffRow import adventureworks.public.identity_test.IdentityTestId import adventureworks.public.identity_test.IdentityTestRepoImpl import adventureworks.public.identity_test.IdentityTestRow @@ -620,6 +623,12 @@ class TestInsert(random: Random) { actualcost: Option[BigDecimal] = if (random.nextBoolean()) None else Some(BigDecimal.decimal(random.nextDouble())), modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ConnectionIO[WorkorderroutingRow] = (new WorkorderroutingRepoImpl).insert(new WorkorderroutingRowUnsaved(workorderid = workorderid, locationid = locationid, productid = productid, operationsequence = operationsequence, scheduledstartdate = scheduledstartdate, scheduledenddate = scheduledenddate, actualstartdate = actualstartdate, actualenddate = actualenddate, actualresourcehrs = actualresourcehrs, plannedcost = plannedcost, actualcost = actualcost, modifieddate = modifieddate)) + def publicFlaff(code: ShortText = ShortText(random.alphanumeric.take(20).mkString), + anotherCode: /* max 20 chars */ String = random.alphanumeric.take(20).mkString, + someNumber: Int = random.nextInt(), + specifier: ShortText = ShortText(random.alphanumeric.take(20).mkString), + parentspecifier: Option[ShortText] = if (random.nextBoolean()) None else Some(ShortText(random.alphanumeric.take(20).mkString)) + ): ConnectionIO[FlaffRow] = (new FlaffRepoImpl).insert(new FlaffRow(code = code, anotherCode = anotherCode, someNumber = someNumber, specifier = specifier, parentspecifier = parentspecifier)) def publicIdentityTest(name: IdentityTestId, defaultGenerated: Defaulted[Int] = Defaulted.UseDefault): ConnectionIO[IdentityTestRow] = (new IdentityTestRepoImpl).insert(new IdentityTestRowUnsaved(name = name, defaultGenerated = defaultGenerated)) def publicPgtest(box: TypoBox, bytea: TypoBytea, diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/ShortText.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/ShortText.scala new file mode 100644 index 0000000000..0bfd7caf4b --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/ShortText.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 public + +import adventureworks.Text +import typo.dsl.Bijection +import typo.dsl.ParameterMetaData +import zio.jdbc.JdbcDecoder +import zio.jdbc.JdbcEncoder +import zio.jdbc.SqlFragment.Setter +import zio.json.JsonDecoder +import zio.json.JsonEncoder + +/** Domain `public.short_text` + * Constraint: CHECK ((length(VALUE) <= 55)) + */ +case class ShortText(value: String) +object ShortText { + implicit lazy val arraySetter: Setter[Array[ShortText]] = adventureworks.StringArraySetter.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[ShortText, String] = Bijection[ShortText, String](_.value)(ShortText.apply) + implicit lazy val jdbcDecoder: JdbcDecoder[ShortText] = JdbcDecoder.stringDecoder.map(ShortText.apply) + implicit lazy val jdbcEncoder: JdbcEncoder[ShortText] = JdbcEncoder.stringEncoder.contramap(_.value) + implicit lazy val jsonDecoder: JsonDecoder[ShortText] = JsonDecoder.string.map(ShortText.apply) + implicit lazy val jsonEncoder: JsonEncoder[ShortText] = JsonEncoder.string.contramap(_.value) + implicit lazy val ordering: Ordering[ShortText] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[ShortText] = ParameterMetaData.instance[ShortText](ParameterMetaData.StringParameterMetaData.sqlType, ParameterMetaData.StringParameterMetaData.jdbcType) + implicit lazy val setter: Setter[ShortText] = Setter.stringSetter.contramap(_.value) + implicit lazy val text: Text[ShortText] = new Text[ShortText] { + override def unsafeEncode(v: ShortText, sb: StringBuilder) = Text.stringInstance.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: ShortText, 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/public/flaff/FlaffFields.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffFields.scala new file mode 100644 index 0000000000..11ce63aec6 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffFields.scala @@ -0,0 +1,46 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package public +package flaff + +import adventureworks.public.ShortText +import typo.dsl.SqlExpr.FieldLikeNoHkt +import typo.dsl.SqlExpr.IdField +import typo.dsl.SqlExpr.OptField +import typo.dsl.Structure.Relation + +trait FlaffFields[Row] { + val code: IdField[ShortText, Row] + val anotherCode: IdField[/* max 20 chars */ String, Row] + val someNumber: IdField[Int, Row] + val specifier: IdField[ShortText, Row] + val parentspecifier: OptField[ShortText, Row] +} + +object FlaffFields { + val structure: Relation[FlaffFields, FlaffRow, FlaffRow] = + new Impl(None, identity, (_, x) => x) + + private final class Impl[Row](val prefix: Option[String], val extract: Row => FlaffRow, val merge: (Row, FlaffRow) => Row) + extends Relation[FlaffFields, FlaffRow, Row] { + + override val fields: FlaffFields[Row] = new FlaffFields[Row] { + override val code = new IdField[ShortText, Row](prefix, "code", None, Some("text"))(x => extract(x).code, (row, value) => merge(row, extract(row).copy(code = value))) + override val anotherCode = new IdField[/* max 20 chars */ String, Row](prefix, "another_code", None, None)(x => extract(x).anotherCode, (row, value) => merge(row, extract(row).copy(anotherCode = value))) + override val someNumber = new IdField[Int, Row](prefix, "some_number", None, Some("int4"))(x => extract(x).someNumber, (row, value) => merge(row, extract(row).copy(someNumber = value))) + override val specifier = new IdField[ShortText, Row](prefix, "specifier", None, Some("text"))(x => extract(x).specifier, (row, value) => merge(row, extract(row).copy(specifier = value))) + override val parentspecifier = new OptField[ShortText, Row](prefix, "parentspecifier", None, Some("text"))(x => extract(x).parentspecifier, (row, value) => merge(row, extract(row).copy(parentspecifier = value))) + } + + override val columns: List[FieldLikeNoHkt[?, Row]] = + List[FieldLikeNoHkt[?, Row]](fields.code, fields.anotherCode, fields.someNumber, fields.specifier, fields.parentspecifier) + + override def copy[NewRow](prefix: Option[String], extract: NewRow => FlaffRow, merge: (NewRow, FlaffRow) => NewRow): Impl[NewRow] = + new Impl(prefix, extract, merge) + } + +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffId.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffId.scala new file mode 100644 index 0000000000..fd0adfe3b7 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffId.scala @@ -0,0 +1,46 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package public +package flaff + +import adventureworks.public.ShortText +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 `public.flaff` */ +case class FlaffId(code: ShortText, anotherCode: /* max 20 chars */ String, someNumber: Int, specifier: ShortText) +object FlaffId { + implicit lazy val jsonDecoder: JsonDecoder[FlaffId] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val code = jsonObj.get("code").toRight("Missing field 'code'").flatMap(_.as(ShortText.jsonDecoder)) + val anotherCode = jsonObj.get("another_code").toRight("Missing field 'another_code'").flatMap(_.as(JsonDecoder.string)) + val someNumber = jsonObj.get("some_number").toRight("Missing field 'some_number'").flatMap(_.as(JsonDecoder.int)) + val specifier = jsonObj.get("specifier").toRight("Missing field 'specifier'").flatMap(_.as(ShortText.jsonDecoder)) + if (code.isRight && anotherCode.isRight && someNumber.isRight && specifier.isRight) + Right(FlaffId(code = code.toOption.get, anotherCode = anotherCode.toOption.get, someNumber = someNumber.toOption.get, specifier = specifier.toOption.get)) + else Left(List[Either[String, Any]](code, anotherCode, someNumber, specifier).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[FlaffId] = new JsonEncoder[FlaffId] { + override def unsafeEncode(a: FlaffId, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""code":""") + ShortText.jsonEncoder.unsafeEncode(a.code, indent, out) + out.write(",") + out.write(""""another_code":""") + JsonEncoder.string.unsafeEncode(a.anotherCode, indent, out) + out.write(",") + out.write(""""some_number":""") + JsonEncoder.int.unsafeEncode(a.someNumber, indent, out) + out.write(",") + out.write(""""specifier":""") + ShortText.jsonEncoder.unsafeEncode(a.specifier, indent, out) + out.write("}") + } + } + implicit lazy val ordering: Ordering[FlaffId] = Ordering.by(x => (x.code, x.anotherCode, x.someNumber, x.specifier)) +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffRepo.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffRepo.scala new file mode 100644 index 0000000000..e69d7fb561 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffRepo.scala @@ -0,0 +1,29 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package public +package flaff + +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 FlaffRepo { + def delete(compositeId: FlaffId): ZIO[ZConnection, Throwable, Boolean] + def delete: DeleteBuilder[FlaffFields, FlaffRow] + def insert(unsaved: FlaffRow): ZIO[ZConnection, Throwable, FlaffRow] + def insertStreaming(unsaved: ZStream[ZConnection, Throwable, FlaffRow], batchSize: Int): ZIO[ZConnection, Throwable, Long] + def select: SelectBuilder[FlaffFields, FlaffRow] + def selectAll: ZStream[ZConnection, Throwable, FlaffRow] + def selectById(compositeId: FlaffId): ZIO[ZConnection, Throwable, Option[FlaffRow]] + def update(row: FlaffRow): ZIO[ZConnection, Throwable, Boolean] + def update: UpdateBuilder[FlaffFields, FlaffRow] + def upsert(unsaved: FlaffRow): ZIO[ZConnection, Throwable, UpdateResult[FlaffRow]] +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoImpl.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoImpl.scala new file mode 100644 index 0000000000..b981159db3 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoImpl.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 public +package flaff + +import adventureworks.public.ShortText +import adventureworks.streamingInsert +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.SqlFragment.Setter +import zio.jdbc.UpdateResult +import zio.jdbc.ZConnection +import zio.jdbc.sqlInterpolator +import zio.stream.ZStream + +class FlaffRepoImpl extends FlaffRepo { + override def delete(compositeId: FlaffId): ZIO[ZConnection, Throwable, Boolean] = { + sql"""delete from public.flaff where "code" = ${Segment.paramSegment(compositeId.code)(ShortText.setter)} AND "another_code" = ${Segment.paramSegment(compositeId.anotherCode)(Setter.stringSetter)} AND "some_number" = ${Segment.paramSegment(compositeId.someNumber)(Setter.intSetter)} AND "specifier" = ${Segment.paramSegment(compositeId.specifier)(ShortText.setter)}""".delete.map(_ > 0) + } + override def delete: DeleteBuilder[FlaffFields, FlaffRow] = { + DeleteBuilder("public.flaff", FlaffFields.structure) + } + override def insert(unsaved: FlaffRow): ZIO[ZConnection, Throwable, FlaffRow] = { + sql"""insert into public.flaff("code", "another_code", "some_number", "specifier", "parentspecifier") + values (${Segment.paramSegment(unsaved.code)(ShortText.setter)}::text, ${Segment.paramSegment(unsaved.anotherCode)(Setter.stringSetter)}, ${Segment.paramSegment(unsaved.someNumber)(Setter.intSetter)}::int4, ${Segment.paramSegment(unsaved.specifier)(ShortText.setter)}::text, ${Segment.paramSegment(unsaved.parentspecifier)(Setter.optionParamSetter(ShortText.setter))}::text) + returning "code", "another_code", "some_number", "specifier", "parentspecifier" + """.insertReturning(using FlaffRow.jdbcDecoder).map(_.updatedKeys.head) + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, FlaffRow], batchSize: Int): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY public.flaff("code", "another_code", "some_number", "specifier", "parentspecifier") FROM STDIN""", batchSize, unsaved)(FlaffRow.text) + } + override def select: SelectBuilder[FlaffFields, FlaffRow] = { + SelectBuilderSql("public.flaff", FlaffFields.structure, FlaffRow.jdbcDecoder) + } + override def selectAll: ZStream[ZConnection, Throwable, FlaffRow] = { + sql"""select "code", "another_code", "some_number", "specifier", "parentspecifier" from public.flaff""".query(using FlaffRow.jdbcDecoder).selectStream() + } + override def selectById(compositeId: FlaffId): ZIO[ZConnection, Throwable, Option[FlaffRow]] = { + sql"""select "code", "another_code", "some_number", "specifier", "parentspecifier" from public.flaff where "code" = ${Segment.paramSegment(compositeId.code)(ShortText.setter)} AND "another_code" = ${Segment.paramSegment(compositeId.anotherCode)(Setter.stringSetter)} AND "some_number" = ${Segment.paramSegment(compositeId.someNumber)(Setter.intSetter)} AND "specifier" = ${Segment.paramSegment(compositeId.specifier)(ShortText.setter)}""".query(using FlaffRow.jdbcDecoder).selectOne + } + override def update(row: FlaffRow): ZIO[ZConnection, Throwable, Boolean] = { + val compositeId = row.compositeId + sql"""update public.flaff + set "parentspecifier" = ${Segment.paramSegment(row.parentspecifier)(Setter.optionParamSetter(ShortText.setter))}::text + where "code" = ${Segment.paramSegment(compositeId.code)(ShortText.setter)} AND "another_code" = ${Segment.paramSegment(compositeId.anotherCode)(Setter.stringSetter)} AND "some_number" = ${Segment.paramSegment(compositeId.someNumber)(Setter.intSetter)} AND "specifier" = ${Segment.paramSegment(compositeId.specifier)(ShortText.setter)}""".update.map(_ > 0) + } + override def update: UpdateBuilder[FlaffFields, FlaffRow] = { + UpdateBuilder("public.flaff", FlaffFields.structure, FlaffRow.jdbcDecoder) + } + override def upsert(unsaved: FlaffRow): ZIO[ZConnection, Throwable, UpdateResult[FlaffRow]] = { + sql"""insert into public.flaff("code", "another_code", "some_number", "specifier", "parentspecifier") + values ( + ${Segment.paramSegment(unsaved.code)(ShortText.setter)}::text, + ${Segment.paramSegment(unsaved.anotherCode)(Setter.stringSetter)}, + ${Segment.paramSegment(unsaved.someNumber)(Setter.intSetter)}::int4, + ${Segment.paramSegment(unsaved.specifier)(ShortText.setter)}::text, + ${Segment.paramSegment(unsaved.parentspecifier)(Setter.optionParamSetter(ShortText.setter))}::text + ) + on conflict ("code", "another_code", "some_number", "specifier") + do update set + "parentspecifier" = EXCLUDED."parentspecifier" + returning "code", "another_code", "some_number", "specifier", "parentspecifier"""".insertReturning(using FlaffRow.jdbcDecoder) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoMock.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoMock.scala new file mode 100644 index 0000000000..9ce314dc62 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffRepoMock.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 public +package flaff + +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 FlaffRepoMock(map: scala.collection.mutable.Map[FlaffId, FlaffRow] = scala.collection.mutable.Map.empty) extends FlaffRepo { + override def delete(compositeId: FlaffId): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed(map.remove(compositeId).isDefined) + } + override def delete: DeleteBuilder[FlaffFields, FlaffRow] = { + DeleteBuilderMock(DeleteParams.empty, FlaffFields.structure.fields, map) + } + override def insert(unsaved: FlaffRow): ZIO[ZConnection, Throwable, FlaffRow] = { + 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, FlaffRow], batchSize: Int): 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[FlaffFields, FlaffRow] = { + SelectBuilderMock(FlaffFields.structure, ZIO.succeed(Chunk.fromIterable(map.values)), SelectParams.empty) + } + override def selectAll: ZStream[ZConnection, Throwable, FlaffRow] = { + ZStream.fromIterable(map.values) + } + override def selectById(compositeId: FlaffId): ZIO[ZConnection, Throwable, Option[FlaffRow]] = { + ZIO.succeed(map.get(compositeId)) + } + override def update(row: FlaffRow): 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 update: UpdateBuilder[FlaffFields, FlaffRow] = { + UpdateBuilderMock(UpdateParams.empty, FlaffFields.structure.fields, map) + } + override def upsert(unsaved: FlaffRow): ZIO[ZConnection, Throwable, UpdateResult[FlaffRow]] = { + ZIO.succeed { + map.put(unsaved.compositeId, unsaved): @nowarn + UpdateResult(1, Chunk.single(unsaved)) + } + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffRow.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffRow.scala new file mode 100644 index 0000000000..36867be899 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/flaff/FlaffRow.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 public +package flaff + +import adventureworks.Text +import adventureworks.public.ShortText +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 + +case class FlaffRow( + /** Points to [[FlaffRow.code]] */ + code: ShortText, + /** Points to [[FlaffRow.anotherCode]] */ + anotherCode: /* max 20 chars */ String, + /** Points to [[FlaffRow.someNumber]] */ + someNumber: Int, + specifier: ShortText, + /** Points to [[FlaffRow.specifier]] */ + parentspecifier: Option[ShortText] +){ + val compositeId: FlaffId = FlaffId(code, anotherCode, someNumber, specifier) + } + +object FlaffRow { + implicit lazy val jdbcDecoder: JdbcDecoder[FlaffRow] = new JdbcDecoder[FlaffRow] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, FlaffRow) = + columIndex + 4 -> + FlaffRow( + code = ShortText.jdbcDecoder.unsafeDecode(columIndex + 0, rs)._2, + anotherCode = JdbcDecoder.stringDecoder.unsafeDecode(columIndex + 1, rs)._2, + someNumber = JdbcDecoder.intDecoder.unsafeDecode(columIndex + 2, rs)._2, + specifier = ShortText.jdbcDecoder.unsafeDecode(columIndex + 3, rs)._2, + parentspecifier = JdbcDecoder.optionDecoder(ShortText.jdbcDecoder).unsafeDecode(columIndex + 4, rs)._2 + ) + } + implicit lazy val jsonDecoder: JsonDecoder[FlaffRow] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val code = jsonObj.get("code").toRight("Missing field 'code'").flatMap(_.as(ShortText.jsonDecoder)) + val anotherCode = jsonObj.get("another_code").toRight("Missing field 'another_code'").flatMap(_.as(JsonDecoder.string)) + val someNumber = jsonObj.get("some_number").toRight("Missing field 'some_number'").flatMap(_.as(JsonDecoder.int)) + val specifier = jsonObj.get("specifier").toRight("Missing field 'specifier'").flatMap(_.as(ShortText.jsonDecoder)) + val parentspecifier = jsonObj.get("parentspecifier").fold[Either[String, Option[ShortText]]](Right(None))(_.as(JsonDecoder.option(using ShortText.jsonDecoder))) + if (code.isRight && anotherCode.isRight && someNumber.isRight && specifier.isRight && parentspecifier.isRight) + Right(FlaffRow(code = code.toOption.get, anotherCode = anotherCode.toOption.get, someNumber = someNumber.toOption.get, specifier = specifier.toOption.get, parentspecifier = parentspecifier.toOption.get)) + else Left(List[Either[String, Any]](code, anotherCode, someNumber, specifier, parentspecifier).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[FlaffRow] = new JsonEncoder[FlaffRow] { + override def unsafeEncode(a: FlaffRow, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""code":""") + ShortText.jsonEncoder.unsafeEncode(a.code, indent, out) + out.write(",") + out.write(""""another_code":""") + JsonEncoder.string.unsafeEncode(a.anotherCode, indent, out) + out.write(",") + out.write(""""some_number":""") + JsonEncoder.int.unsafeEncode(a.someNumber, indent, out) + out.write(",") + out.write(""""specifier":""") + ShortText.jsonEncoder.unsafeEncode(a.specifier, indent, out) + out.write(",") + out.write(""""parentspecifier":""") + JsonEncoder.option(using ShortText.jsonEncoder).unsafeEncode(a.parentspecifier, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[FlaffRow] = Text.instance[FlaffRow]{ (row, sb) => + ShortText.text.unsafeEncode(row.code, sb) + sb.append(Text.DELIMETER) + Text.stringInstance.unsafeEncode(row.anotherCode, sb) + sb.append(Text.DELIMETER) + Text.intInstance.unsafeEncode(row.someNumber, sb) + sb.append(Text.DELIMETER) + ShortText.text.unsafeEncode(row.specifier, sb) + sb.append(Text.DELIMETER) + Text.option(ShortText.text).unsafeEncode(row.parentspecifier, 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 ce08bf8d2b..93b4a89edc 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 @@ -197,6 +197,9 @@ import adventureworks.public.Name import adventureworks.public.NameStyle import adventureworks.public.OrderNumber import adventureworks.public.Phone +import adventureworks.public.ShortText +import adventureworks.public.flaff.FlaffRepoImpl +import adventureworks.public.flaff.FlaffRow import adventureworks.public.identity_test.IdentityTestId import adventureworks.public.identity_test.IdentityTestRepoImpl import adventureworks.public.identity_test.IdentityTestRow @@ -621,6 +624,12 @@ class TestInsert(random: Random) { actualcost: Option[BigDecimal] = if (random.nextBoolean()) None else Some(BigDecimal.decimal(random.nextDouble())), modifieddate: Defaulted[TypoLocalDateTime] = Defaulted.UseDefault ): ZIO[ZConnection, Throwable, WorkorderroutingRow] = (new WorkorderroutingRepoImpl).insert(new WorkorderroutingRowUnsaved(workorderid = workorderid, locationid = locationid, productid = productid, operationsequence = operationsequence, scheduledstartdate = scheduledstartdate, scheduledenddate = scheduledenddate, actualstartdate = actualstartdate, actualenddate = actualenddate, actualresourcehrs = actualresourcehrs, plannedcost = plannedcost, actualcost = actualcost, modifieddate = modifieddate)) + def publicFlaff(code: ShortText = ShortText(random.alphanumeric.take(20).mkString), + anotherCode: /* max 20 chars */ String = random.alphanumeric.take(20).mkString, + someNumber: Int = random.nextInt(), + specifier: ShortText = ShortText(random.alphanumeric.take(20).mkString), + parentspecifier: Option[ShortText] = if (random.nextBoolean()) None else Some(ShortText(random.alphanumeric.take(20).mkString)) + ): ZIO[ZConnection, Throwable, FlaffRow] = (new FlaffRepoImpl).insert(new FlaffRow(code = code, anotherCode = anotherCode, someNumber = someNumber, specifier = specifier, parentspecifier = parentspecifier)) def publicIdentityTest(name: IdentityTestId, defaultGenerated: Defaulted[Int] = Defaulted.UseDefault): ZIO[ZConnection, Throwable, IdentityTestRow] = (new IdentityTestRepoImpl).insert(new IdentityTestRowUnsaved(name = name, defaultGenerated = defaultGenerated)) def publicPgtest(box: TypoBox, bytea: TypoBytea, diff --git a/typo/src/scala/typo/internal/ComputedTable.scala b/typo/src/scala/typo/internal/ComputedTable.scala index 839ae586db..2bb21d81d1 100644 --- a/typo/src/scala/typo/internal/ComputedTable.scala +++ b/typo/src/scala/typo/internal/ComputedTable.scala @@ -12,13 +12,13 @@ case class ComputedTable( eval: Eval[db.RelationName, HasSource] ) extends HasSource { override val source: Source.Table = Source.Table(dbTable.name) - val pointsTo: Map[db.ColName, (Source.Relation, db.ColName)] = - dbTable.foreignKeys.flatMap { fk => - val maybeOtherTable: Option[HasSource] = - if (fk.otherTable == dbTable.name) Some(this) - else eval(fk.otherTable).get + val pointsTo: Map[db.ColName, (Source.Relation, db.ColName)] = { + val (fkSelf, fkOther) = dbTable.foreignKeys.partition { fk => fk.otherTable == dbTable.name } - maybeOtherTable match { + val fromSelf = fkSelf.flatMap(fk => fk.cols.zip(fk.otherCols.map(cn => (source, cn))).toList).toMap + + val fromOthers = fkOther.flatMap { fk => + eval(fk.otherTable).get match { case None => options.logger.warn(s"Circular: ${dbTable.name.value} => ${fk.otherTable.value}") Nil @@ -28,6 +28,10 @@ case class ComputedTable( } }.toMap + // prefer inferring types from outside the table + fromSelf ++ fromOthers + } + val dbColsByName: Map[db.ColName, db.Col] = dbTable.cols.map(col => (col.name, col)).toMap @@ -87,11 +91,14 @@ case class ComputedTable( } } - def deriveType(dbCol: db.Col) = { + def deriveType(dbCol: db.Col): sc.Type = { // we let types flow through constraints down to this column, the point is to reuse id types downstream val typeFromFk: Option[sc.Type] = - pointsTo.get(dbCol.name) match { - case Some((otherTableSource, otherColName)) if otherTableSource.name != dbTable.name => + pointsTo.get(dbCol.name).flatMap { case (otherTableSource, otherColName) => + if (otherTableSource.name == dbTable.name) + if (otherColName == dbCol.name) None + else Some(deriveType(dbColsByName(otherColName))) + else eval(otherTableSource.name).get match { case Some(otherTable) => otherTable.cols.find(_.dbName == otherColName).map(_.tpe) @@ -99,8 +106,6 @@ case class ComputedTable( options.logger.warn(s"Unexpected circular dependency involving ${dbTable.name.value} => ${otherTableSource.name.value}") None } - - case _ => None } val typeFromId: Option[sc.Type] = diff --git a/typo/src/scala/typo/internal/IdComputed.scala b/typo/src/scala/typo/internal/IdComputed.scala index 5a7f5caeac..dec27c16fd 100644 --- a/typo/src/scala/typo/internal/IdComputed.scala +++ b/typo/src/scala/typo/internal/IdComputed.scala @@ -36,5 +36,5 @@ object IdComputed { override def paramName: sc.Ident = col.name } - case class Composite(cols: NonEmptyList[ComputedColumn], tpe: sc.Type.Qualified, paramName: sc.Ident) extends IdComputed + case class Composite(cols: NonEmptyList[ComputedColumn], tpe: sc.Type.Qualified, paramName: sc.Ident) extends IdComputed }