From dc03273aa000a49d6d2d1298e8479f3c80dfb5d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Raddum=20Berg?= Date: Fri, 16 Aug 2024 14:47:24 +0200 Subject: [PATCH] Support `GENERATED ALWAYS` (part 2) (#134) * Support `GENERATED ALWAYS` without `IDENTITY` * `Unsaved` rows for tables with generated always columns --- init/data/test-tables.sql | 33 +++- .../src/scala/scripts/GenHardcodedFiles.scala | 8 +- .../TableWithGeneratedColumnsFields.scala | 40 +++++ .../TableWithGeneratedColumnsId.scala | 36 +++++ .../TableWithGeneratedColumnsRepo.scala | 34 ++++ .../TableWithGeneratedColumnsRepoImpl.scala | 145 ++++++++++++++++++ .../TableWithGeneratedColumnsRepoMock.scala | 94 ++++++++++++ .../TableWithGeneratedColumnsRow.scala | 66 ++++++++ .../TableWithGeneratedColumnsRowUnsaved.scala | 45 ++++++ .../adventureworks/testInsert.scala | 5 + .../TableWithGeneratedColumnsFields.scala | 40 +++++ .../TableWithGeneratedColumnsId.scala | 33 ++++ .../TableWithGeneratedColumnsRepo.scala | 35 +++++ .../TableWithGeneratedColumnsRepoImpl.scala | 120 +++++++++++++++ .../TableWithGeneratedColumnsRepoMock.scala | 116 ++++++++++++++ .../TableWithGeneratedColumnsRow.scala | 62 ++++++++ .../TableWithGeneratedColumnsRowUnsaved.scala | 30 ++++ .../adventureworks/testInsert.scala | 5 + .../TableWithGeneratedColumnsFields.scala | 40 +++++ .../TableWithGeneratedColumnsId.scala | 36 +++++ .../TableWithGeneratedColumnsRepo.scala | 37 +++++ .../TableWithGeneratedColumnsRepoImpl.scala | 104 +++++++++++++ .../TableWithGeneratedColumnsRepoMock.scala | 105 +++++++++++++ .../TableWithGeneratedColumnsRow.scala | 64 ++++++++ .../TableWithGeneratedColumnsRowUnsaved.scala | 43 ++++++ .../adventureworks/testInsert.scala | 5 + typo/src/scala/typo/MetaDb.scala | 28 ++-- typo/src/scala/typo/db.scala | 57 ++++--- .../typo/internal/ComputedRowUnsaved.scala | 38 ++--- .../scala/typo/internal/ComputedSqlFile.scala | 2 +- .../scala/typo/internal/ComputedTable.scala | 4 +- .../typo/internal/ComputedTestInserts.scala | 2 +- typo/src/scala/typo/internal/FkAnalysis.scala | 2 +- .../typo/internal/codegen/DbLibAnorm.scala | 2 +- .../typo/internal/codegen/DbLibDoobie.scala | 4 +- .../internal/codegen/DbLibTextSupport.scala | 2 +- .../typo/internal/codegen/DbLibZioJdbc.scala | 2 +- .../typo/internal/codegen/FilesRelation.scala | 4 +- .../typo/internal/codegen/FilesTable.scala | 10 +- 39 files changed, 1464 insertions(+), 74 deletions(-) create mode 100644 typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsFields.scala create mode 100644 typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsId.scala create mode 100644 typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepo.scala create mode 100644 typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoImpl.scala create mode 100644 typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoMock.scala create mode 100644 typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRow.scala create mode 100644 typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRowUnsaved.scala create mode 100644 typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsFields.scala create mode 100644 typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsId.scala create mode 100644 typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepo.scala create mode 100644 typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoImpl.scala create mode 100644 typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoMock.scala create mode 100644 typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRow.scala create mode 100644 typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRowUnsaved.scala create mode 100644 typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsFields.scala create mode 100644 typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsId.scala create mode 100644 typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepo.scala create mode 100644 typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoImpl.scala create mode 100644 typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoMock.scala create mode 100644 typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRow.scala create mode 100644 typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRowUnsaved.scala diff --git a/init/data/test-tables.sql b/init/data/test-tables.sql index cda710cd7..4ed763979 100644 --- a/init/data/test-tables.sql +++ b/init/data/test-tables.sql @@ -217,11 +217,25 @@ create table flaff constraint flaff_parent_fk foreign key (code, another_code, some_number, parentSpecifier) references flaff ); -create table title (code text primary key); -insert into title (code) values ('mr'), ('ms'), ('dr'), ('phd'); +create table title +( + code text primary key +); +insert into title (code) +values ('mr'), + ('ms'), + ('dr'), + ('phd'); -create table title_domain (code short_text primary key); -insert into title_domain (code) values ('mr'), ('ms'), ('dr'), ('phd'); +create table title_domain +( + code short_text primary key +); +insert into title_domain (code) +values ('mr'), + ('ms'), + ('dr'), + ('phd'); create table titledperson ( @@ -229,3 +243,14 @@ create table titledperson title text not null references title, name text not null ); + +create table "table-with-generated-columns" +( + name text primary key, + "name-type-always" text NOT NULL GENERATED ALWAYS AS + (CASE + WHEN name IS NOT NULL THEN 'no-name' + WHEN name = 'a' THEN 'a-name' + ELSE 'some-name' + END) STORED +) \ No newline at end of file diff --git a/typo-scripts/src/scala/scripts/GenHardcodedFiles.scala b/typo-scripts/src/scala/scripts/GenHardcodedFiles.scala index 7562a47d0..f8d022b4d 100644 --- a/typo-scripts/src/scala/scripts/GenHardcodedFiles.scala +++ b/typo-scripts/src/scala/scripts/GenHardcodedFiles.scala @@ -24,7 +24,7 @@ object GenHardcodedFiles extends BleepCodegenScript("GenHardcodedFiles") { udtName = Some("varchar"), nullability = Nullability.NoNulls, columnDefault = None, - identity = None, + maybeGenerated = None, comment = None, constraints = Nil, jsonDescription = DebugJson.Empty @@ -41,7 +41,7 @@ object GenHardcodedFiles extends BleepCodegenScript("GenHardcodedFiles") { udtName = Some("varchar"), nullability = Nullability.NoNulls, columnDefault = Some("some-value"), - identity = None, + maybeGenerated = None, comment = None, constraints = Nil, jsonDescription = DebugJson.Empty @@ -53,7 +53,7 @@ object GenHardcodedFiles extends BleepCodegenScript("GenHardcodedFiles") { udtName = Some("myschema.sector"), nullability = Nullability.NoNulls, columnDefault = Some("PUBLIC"), - identity = Some(db.Identity("ALWAYS", None, None, None, None)), + maybeGenerated = Some(db.Generated.Identity("ALWAYS", None, None, None, None)), comment = None, constraints = Nil, jsonDescription = DebugJson.Empty @@ -64,7 +64,7 @@ object GenHardcodedFiles extends BleepCodegenScript("GenHardcodedFiles") { udtName = Some("myschema.number"), nullability = Nullability.NoNulls, columnDefault = Some("one"), - identity = None, + maybeGenerated = None, comment = None, constraints = Nil, jsonDescription = DebugJson.Empty diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsFields.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsFields.scala new file mode 100644 index 000000000..422245e0c --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsFields.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 public +package table_with_generated_columns + +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 TableWithGeneratedColumnsFields { + def name: IdField[TableWithGeneratedColumnsId, TableWithGeneratedColumnsRow] + def nameTypeAlways: Field[String, TableWithGeneratedColumnsRow] +} + +object TableWithGeneratedColumnsFields { + lazy val structure: Relation[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] { + + override lazy val fields: TableWithGeneratedColumnsFields = new TableWithGeneratedColumnsFields { + override def name = IdField[TableWithGeneratedColumnsId, TableWithGeneratedColumnsRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + override def nameTypeAlways = Field[String, TableWithGeneratedColumnsRow](_path, "name-type-always", None, None, x => x.nameTypeAlways, (row, value) => row.copy(nameTypeAlways = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, TableWithGeneratedColumnsRow]] = + List[FieldLikeNoHkt[?, TableWithGeneratedColumnsRow]](fields.name, fields.nameTypeAlways) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsId.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsId.scala new file mode 100644 index 000000000..265ca9078 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsId.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 +package table_with_generated_columns + +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 `public.table-with-generated-columns` */ +case class TableWithGeneratedColumnsId(value: String) extends AnyVal +object TableWithGeneratedColumnsId { + implicit lazy val arrayColumn: Column[Array[TableWithGeneratedColumnsId]] = Column.columnToArray(column, implicitly) + implicit lazy val arrayToStatement: ToStatement[Array[TableWithGeneratedColumnsId]] = ToStatement.arrayToParameter(ParameterMetaData.StringParameterMetaData).contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[TableWithGeneratedColumnsId, String] = Bijection[TableWithGeneratedColumnsId, String](_.value)(TableWithGeneratedColumnsId.apply) + implicit lazy val column: Column[TableWithGeneratedColumnsId] = Column.columnToString.map(TableWithGeneratedColumnsId.apply) + implicit lazy val ordering: Ordering[TableWithGeneratedColumnsId] = Ordering.by(_.value) + implicit lazy val parameterMetadata: ParameterMetaData[TableWithGeneratedColumnsId] = new ParameterMetaData[TableWithGeneratedColumnsId] { + override def sqlType: String = ParameterMetaData.StringParameterMetaData.sqlType + override def jdbcType: Int = ParameterMetaData.StringParameterMetaData.jdbcType + } + implicit lazy val reads: Reads[TableWithGeneratedColumnsId] = Reads.StringReads.map(TableWithGeneratedColumnsId.apply) + implicit lazy val text: Text[TableWithGeneratedColumnsId] = new Text[TableWithGeneratedColumnsId] { + override def unsafeEncode(v: TableWithGeneratedColumnsId, sb: StringBuilder) = Text.stringInstance.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: TableWithGeneratedColumnsId, sb: StringBuilder) = Text.stringInstance.unsafeArrayEncode(v.value, sb) + } + implicit lazy val toStatement: ToStatement[TableWithGeneratedColumnsId] = ToStatement.stringToStatement.contramap(_.value) + implicit lazy val writes: Writes[TableWithGeneratedColumnsId] = Writes.StringWrites.contramap(_.value) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepo.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepo.scala new file mode 100644 index 000000000..789d42b74 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepo.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 +package table_with_generated_columns + +import java.sql.Connection +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait TableWithGeneratedColumnsRepo { + def delete: DeleteBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] + def deleteById(name: TableWithGeneratedColumnsId)(implicit c: Connection): Boolean + def deleteByIds(names: Array[TableWithGeneratedColumnsId])(implicit c: Connection): Int + def insert(unsaved: TableWithGeneratedColumnsRow)(implicit c: Connection): TableWithGeneratedColumnsRow + def insert(unsaved: TableWithGeneratedColumnsRowUnsaved)(implicit c: Connection): TableWithGeneratedColumnsRow + def insertStreaming(unsaved: Iterator[TableWithGeneratedColumnsRow], batchSize: Int = 10000)(implicit c: Connection): Long + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Iterator[TableWithGeneratedColumnsRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long + def select: SelectBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] + def selectAll(implicit c: Connection): List[TableWithGeneratedColumnsRow] + def selectById(name: TableWithGeneratedColumnsId)(implicit c: Connection): Option[TableWithGeneratedColumnsRow] + def selectByIds(names: Array[TableWithGeneratedColumnsId])(implicit c: Connection): List[TableWithGeneratedColumnsRow] + def selectByIdsTracked(names: Array[TableWithGeneratedColumnsId])(implicit c: Connection): Map[TableWithGeneratedColumnsId, TableWithGeneratedColumnsRow] + def update: UpdateBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] + def upsert(unsaved: TableWithGeneratedColumnsRow)(implicit c: Connection): TableWithGeneratedColumnsRow + def upsertBatch(unsaved: Iterable[TableWithGeneratedColumnsRow])(implicit c: Connection): List[TableWithGeneratedColumnsRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Iterator[TableWithGeneratedColumnsRow], batchSize: Int = 10000)(implicit c: Connection): Int +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoImpl.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoImpl.scala new file mode 100644 index 000000000..91bf2d1e3 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoImpl.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 public +package table_with_generated_columns + +import anorm.BatchSql +import anorm.NamedParameter +import anorm.ParameterValue +import anorm.RowParser +import anorm.SQL +import anorm.SimpleSql +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 TableWithGeneratedColumnsRepoImpl extends TableWithGeneratedColumnsRepo { + override def delete: DeleteBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = { + DeleteBuilder(""""public"."table-with-generated-columns"""", TableWithGeneratedColumnsFields.structure) + } + override def deleteById(name: TableWithGeneratedColumnsId)(implicit c: Connection): Boolean = { + SQL"""delete from "public"."table-with-generated-columns" where "name" = ${ParameterValue(name, null, TableWithGeneratedColumnsId.toStatement)}""".executeUpdate() > 0 + } + override def deleteByIds(names: Array[TableWithGeneratedColumnsId])(implicit c: Connection): Int = { + SQL"""delete + from "public"."table-with-generated-columns" + where "name" = ANY(${names}) + """.executeUpdate() + + } + override def insert(unsaved: TableWithGeneratedColumnsRow)(implicit c: Connection): TableWithGeneratedColumnsRow = { + SQL"""insert into "public"."table-with-generated-columns"("name") + values (${ParameterValue(unsaved.name, null, TableWithGeneratedColumnsId.toStatement)}) + returning "name", "name-type-always" + """ + .executeInsert(TableWithGeneratedColumnsRow.rowParser(1).single) + + } + override def insert(unsaved: TableWithGeneratedColumnsRowUnsaved)(implicit c: Connection): TableWithGeneratedColumnsRow = { + val namedParameters = List( + Some((NamedParameter("name", ParameterValue(unsaved.name, null, TableWithGeneratedColumnsId.toStatement)), "")) + ).flatten + val quote = '"'.toString + if (namedParameters.isEmpty) { + SQL"""insert into "public"."table-with-generated-columns" default values + returning "name", "name-type-always" + """ + .executeInsert(TableWithGeneratedColumnsRow.rowParser(1).single) + } else { + val q = s"""insert into "public"."table-with-generated-columns"(${namedParameters.map{case (x, _) => quote + x.name + quote}.mkString(", ")}) + values (${namedParameters.map{ case (np, cast) => s"{${np.name}}$cast"}.mkString(", ")}) + returning "name", "name-type-always" + """ + SimpleSql(SQL(q), namedParameters.map { case (np, _) => np.tupled }.toMap, RowParser.successful) + .executeInsert(TableWithGeneratedColumnsRow.rowParser(1).single) + } + + } + override def insertStreaming(unsaved: Iterator[TableWithGeneratedColumnsRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "public"."table-with-generated-columns"("name") FROM STDIN""", batchSize, unsaved)(TableWithGeneratedColumnsRow.text, c) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[TableWithGeneratedColumnsRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + streamingInsert(s"""COPY "public"."table-with-generated-columns"("name") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(TableWithGeneratedColumnsRowUnsaved.text, c) + } + override def select: SelectBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = { + SelectBuilderSql(""""public"."table-with-generated-columns"""", TableWithGeneratedColumnsFields.structure, TableWithGeneratedColumnsRow.rowParser) + } + override def selectAll(implicit c: Connection): List[TableWithGeneratedColumnsRow] = { + SQL"""select "name", "name-type-always" + from "public"."table-with-generated-columns" + """.as(TableWithGeneratedColumnsRow.rowParser(1).*) + } + override def selectById(name: TableWithGeneratedColumnsId)(implicit c: Connection): Option[TableWithGeneratedColumnsRow] = { + SQL"""select "name", "name-type-always" + from "public"."table-with-generated-columns" + where "name" = ${ParameterValue(name, null, TableWithGeneratedColumnsId.toStatement)} + """.as(TableWithGeneratedColumnsRow.rowParser(1).singleOpt) + } + override def selectByIds(names: Array[TableWithGeneratedColumnsId])(implicit c: Connection): List[TableWithGeneratedColumnsRow] = { + SQL"""select "name", "name-type-always" + from "public"."table-with-generated-columns" + where "name" = ANY(${names}) + """.as(TableWithGeneratedColumnsRow.rowParser(1).*) + + } + override def selectByIdsTracked(names: Array[TableWithGeneratedColumnsId])(implicit c: Connection): Map[TableWithGeneratedColumnsId, TableWithGeneratedColumnsRow] = { + val byId = selectByIds(names).view.map(x => (x.name, x)).toMap + names.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = { + UpdateBuilder(""""public"."table-with-generated-columns"""", TableWithGeneratedColumnsFields.structure, TableWithGeneratedColumnsRow.rowParser) + } + override def upsert(unsaved: TableWithGeneratedColumnsRow)(implicit c: Connection): TableWithGeneratedColumnsRow = { + SQL"""insert into "public"."table-with-generated-columns"("name") + values ( + ${ParameterValue(unsaved.name, null, TableWithGeneratedColumnsId.toStatement)} + ) + on conflict ("name") + do nothing + returning "name", "name-type-always" + """ + .executeInsert(TableWithGeneratedColumnsRow.rowParser(1).single) + + } + override def upsertBatch(unsaved: Iterable[TableWithGeneratedColumnsRow])(implicit c: Connection): List[TableWithGeneratedColumnsRow] = { + def toNamedParameter(row: TableWithGeneratedColumnsRow): List[NamedParameter] = List( + NamedParameter("name", ParameterValue(row.name, null, TableWithGeneratedColumnsId.toStatement)) + ) + unsaved.toList match { + case Nil => Nil + case head :: rest => + new anorm.adventureworks.ExecuteReturningSyntax.Ops( + BatchSql( + s"""insert into "public"."table-with-generated-columns"("name") + values ({name}) + on conflict ("name") + do nothing + returning "name", "name-type-always" + """, + toNamedParameter(head), + rest.map(toNamedParameter)* + ) + ).executeReturning(TableWithGeneratedColumnsRow.rowParser(1).*) + } + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Iterator[TableWithGeneratedColumnsRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + SQL"""create temporary table table-with-generated-columns_TEMP (like "public"."table-with-generated-columns") on commit drop""".execute(): @nowarn + streamingInsert(s"""copy table-with-generated-columns_TEMP("name") from stdin""", batchSize, unsaved)(TableWithGeneratedColumnsRow.text, c): @nowarn + SQL"""insert into "public"."table-with-generated-columns"("name") + select * from table-with-generated-columns_TEMP + on conflict ("name") + do nothing + ; + drop table table-with-generated-columns_TEMP;""".executeUpdate() + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoMock.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoMock.scala new file mode 100644 index 000000000..0e0ffe4b2 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoMock.scala @@ -0,0 +1,94 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package public +package table_with_generated_columns + +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 TableWithGeneratedColumnsRepoMock(toRow: Function1[TableWithGeneratedColumnsRowUnsaved, TableWithGeneratedColumnsRow], + map: scala.collection.mutable.Map[TableWithGeneratedColumnsId, TableWithGeneratedColumnsRow] = scala.collection.mutable.Map.empty) extends TableWithGeneratedColumnsRepo { + override def delete: DeleteBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = { + DeleteBuilderMock(DeleteParams.empty, TableWithGeneratedColumnsFields.structure, map) + } + override def deleteById(name: TableWithGeneratedColumnsId)(implicit c: Connection): Boolean = { + map.remove(name).isDefined + } + override def deleteByIds(names: Array[TableWithGeneratedColumnsId])(implicit c: Connection): Int = { + names.map(id => map.remove(id)).count(_.isDefined) + } + override def insert(unsaved: TableWithGeneratedColumnsRow)(implicit c: Connection): TableWithGeneratedColumnsRow = { + val _ = if (map.contains(unsaved.name)) + sys.error(s"id ${unsaved.name} already exists") + else + map.put(unsaved.name, unsaved) + + unsaved + } + override def insert(unsaved: TableWithGeneratedColumnsRowUnsaved)(implicit c: Connection): TableWithGeneratedColumnsRow = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Iterator[TableWithGeneratedColumnsRow], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { row => + map += (row.name -> row) + } + unsaved.size.toLong + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Iterator[TableWithGeneratedColumnsRowUnsaved], batchSize: Int = 10000)(implicit c: Connection): Long = { + unsaved.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.name -> row) + } + unsaved.size.toLong + } + override def select: SelectBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = { + SelectBuilderMock(TableWithGeneratedColumnsFields.structure, () => map.values.toList, SelectParams.empty) + } + override def selectAll(implicit c: Connection): List[TableWithGeneratedColumnsRow] = { + map.values.toList + } + override def selectById(name: TableWithGeneratedColumnsId)(implicit c: Connection): Option[TableWithGeneratedColumnsRow] = { + map.get(name) + } + override def selectByIds(names: Array[TableWithGeneratedColumnsId])(implicit c: Connection): List[TableWithGeneratedColumnsRow] = { + names.flatMap(map.get).toList + } + override def selectByIdsTracked(names: Array[TableWithGeneratedColumnsId])(implicit c: Connection): Map[TableWithGeneratedColumnsId, TableWithGeneratedColumnsRow] = { + val byId = selectByIds(names).view.map(x => (x.name, x)).toMap + names.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + override def update: UpdateBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = { + UpdateBuilderMock(UpdateParams.empty, TableWithGeneratedColumnsFields.structure, map) + } + override def upsert(unsaved: TableWithGeneratedColumnsRow)(implicit c: Connection): TableWithGeneratedColumnsRow = { + map.put(unsaved.name, unsaved): @nowarn + unsaved + } + override def upsertBatch(unsaved: Iterable[TableWithGeneratedColumnsRow])(implicit c: Connection): List[TableWithGeneratedColumnsRow] = { + unsaved.map { row => + map += (row.name -> 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[TableWithGeneratedColumnsRow], batchSize: Int = 10000)(implicit c: Connection): Int = { + unsaved.foreach { row => + map += (row.name -> row) + } + unsaved.size + } +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRow.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRow.scala new file mode 100644 index 000000000..329403ba3 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRow.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 public +package table_with_generated_columns + +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: public.table-with-generated-columns + Primary key: name */ +case class TableWithGeneratedColumnsRow( + name: TableWithGeneratedColumnsId, + /** Generated ALWAYS, expression: + CASE + WHEN (name IS NOT NULL) THEN 'no-name'::text + WHEN (name = 'a'::text) THEN 'a-name'::text + ELSE 'some-name'::text + END */ + nameTypeAlways: String +){ + val id = name + def toUnsavedRow(): TableWithGeneratedColumnsRowUnsaved = + TableWithGeneratedColumnsRowUnsaved(name) + } + +object TableWithGeneratedColumnsRow { + implicit lazy val reads: Reads[TableWithGeneratedColumnsRow] = Reads[TableWithGeneratedColumnsRow](json => JsResult.fromTry( + Try( + TableWithGeneratedColumnsRow( + name = json.\("name").as(TableWithGeneratedColumnsId.reads), + nameTypeAlways = json.\("name-type-always").as(Reads.StringReads) + ) + ) + ), + ) + def rowParser(idx: Int): RowParser[TableWithGeneratedColumnsRow] = RowParser[TableWithGeneratedColumnsRow] { row => + Success( + TableWithGeneratedColumnsRow( + name = row(idx + 0)(TableWithGeneratedColumnsId.column), + nameTypeAlways = row(idx + 1)(Column.columnToString) + ) + ) + } + implicit lazy val text: Text[TableWithGeneratedColumnsRow] = Text.instance[TableWithGeneratedColumnsRow]{ (row, sb) => + TableWithGeneratedColumnsId.text.unsafeEncode(row.name, sb) + } + implicit lazy val writes: OWrites[TableWithGeneratedColumnsRow] = OWrites[TableWithGeneratedColumnsRow](o => + new JsObject(ListMap[String, JsValue]( + "name" -> TableWithGeneratedColumnsId.writes.writes(o.name), + "name-type-always" -> Writes.StringWrites.writes(o.nameTypeAlways) + )) + ) +} diff --git a/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRowUnsaved.scala b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRowUnsaved.scala new file mode 100644 index 000000000..03f8cbb61 --- /dev/null +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRowUnsaved.scala @@ -0,0 +1,45 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package public +package table_with_generated_columns + +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 + +/** This class corresponds to a row in table `public.table-with-generated-columns` which has not been persisted yet */ +case class TableWithGeneratedColumnsRowUnsaved( + name: TableWithGeneratedColumnsId +) { + def toRow(nameTypeAlwaysDefault: => String): TableWithGeneratedColumnsRow = + TableWithGeneratedColumnsRow( + name = name, + nameTypeAlways = nameTypeAlwaysDefault + ) +} +object TableWithGeneratedColumnsRowUnsaved { + implicit lazy val reads: Reads[TableWithGeneratedColumnsRowUnsaved] = Reads[TableWithGeneratedColumnsRowUnsaved](json => JsResult.fromTry( + Try( + TableWithGeneratedColumnsRowUnsaved( + name = json.\("name").as(TableWithGeneratedColumnsId.reads) + ) + ) + ), + ) + implicit lazy val text: Text[TableWithGeneratedColumnsRowUnsaved] = Text.instance[TableWithGeneratedColumnsRowUnsaved]{ (row, sb) => + TableWithGeneratedColumnsId.text.unsafeEncode(row.name, sb) + } + implicit lazy val writes: OWrites[TableWithGeneratedColumnsRowUnsaved] = OWrites[TableWithGeneratedColumnsRowUnsaved](o => + new JsObject(ListMap[String, JsValue]( + "name" -> TableWithGeneratedColumnsId.writes.writes(o.name) + )) + ) +} 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 a648fde62..29e8a454a 100644 --- a/typo-tester-anorm/generated-and-checked-in/adventureworks/testInsert.scala +++ b/typo-tester-anorm/generated-and-checked-in/adventureworks/testInsert.scala @@ -208,6 +208,10 @@ import adventureworks.public.pgtest.PgtestRepoImpl import adventureworks.public.pgtest.PgtestRow import adventureworks.public.pgtestnull.PgtestnullRepoImpl import adventureworks.public.pgtestnull.PgtestnullRow +import adventureworks.public.table_with_generated_columns.TableWithGeneratedColumnsId +import adventureworks.public.table_with_generated_columns.TableWithGeneratedColumnsRepoImpl +import adventureworks.public.table_with_generated_columns.TableWithGeneratedColumnsRow +import adventureworks.public.table_with_generated_columns.TableWithGeneratedColumnsRowUnsaved import adventureworks.public.title.TitleId import adventureworks.public.title.TitleRepoImpl import adventureworks.public.title.TitleRow @@ -781,6 +785,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { varchares: Option[Array[String]] = if (random.nextBoolean()) None else Some(Array.fill(random.nextInt(3))(random.alphanumeric.take(20).mkString)), xmles: Option[Array[TypoXml]] = None )(implicit c: Connection): PgtestnullRow = (new PgtestnullRepoImpl).insert(new PgtestnullRow(bool = bool, box = box, bpchar = bpchar, bytea = bytea, char = char, circle = circle, date = date, float4 = float4, float8 = float8, hstore = hstore, inet = inet, int2 = int2, int2vector = int2vector, int4 = int4, int8 = int8, interval = interval, json = json, jsonb = jsonb, line = line, lseg = lseg, money = money, mydomain = mydomain, myenum = myenum, name = name, numeric = numeric, path = path, point = point, polygon = polygon, text = text, time = time, timestamp = timestamp, timestampz = timestampz, timez = timez, uuid = uuid, varchar = varchar, vector = vector, xml = xml, boxes = boxes, bpchares = bpchares, chares = chares, circlees = circlees, datees = datees, float4es = float4es, float8es = float8es, inetes = inetes, int2es = int2es, int2vectores = int2vectores, int4es = int4es, int8es = int8es, intervales = intervales, jsones = jsones, jsonbes = jsonbes, linees = linees, lseges = lseges, moneyes = moneyes, mydomaines = mydomaines, myenumes = myenumes, namees = namees, numerices = numerices, pathes = pathes, pointes = pointes, polygones = polygones, textes = textes, timees = timees, timestampes = timestampes, timestampzes = timestampzes, timezes = timezes, uuides = uuides, varchares = varchares, xmles = xmles)) + def publicTableWithGeneratedColumns(name: TableWithGeneratedColumnsId = TableWithGeneratedColumnsId(random.alphanumeric.take(20).mkString))(implicit c: Connection): TableWithGeneratedColumnsRow = (new TableWithGeneratedColumnsRepoImpl).insert(new TableWithGeneratedColumnsRowUnsaved(name = name)) def publicTitle(code: TitleId = TitleId(random.alphanumeric.take(20).mkString))(implicit c: Connection): TitleRow = (new TitleRepoImpl).insert(new TitleRow(code = code)) def publicTitleDomain(code: TitleDomainId = TitleDomainId(domainInsert.publicShortText(random)))(implicit c: Connection): TitleDomainRow = (new TitleDomainRepoImpl).insert(new TitleDomainRow(code = code)) def publicTitledperson(titleShort: TitleDomainId = TitleDomainId.All(random.nextInt(4)), diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsFields.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsFields.scala new file mode 100644 index 000000000..422245e0c --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsFields.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 public +package table_with_generated_columns + +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 TableWithGeneratedColumnsFields { + def name: IdField[TableWithGeneratedColumnsId, TableWithGeneratedColumnsRow] + def nameTypeAlways: Field[String, TableWithGeneratedColumnsRow] +} + +object TableWithGeneratedColumnsFields { + lazy val structure: Relation[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] { + + override lazy val fields: TableWithGeneratedColumnsFields = new TableWithGeneratedColumnsFields { + override def name = IdField[TableWithGeneratedColumnsId, TableWithGeneratedColumnsRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + override def nameTypeAlways = Field[String, TableWithGeneratedColumnsRow](_path, "name-type-always", None, None, x => x.nameTypeAlways, (row, value) => row.copy(nameTypeAlways = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, TableWithGeneratedColumnsRow]] = + List[FieldLikeNoHkt[?, TableWithGeneratedColumnsRow]](fields.name, fields.nameTypeAlways) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsId.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsId.scala new file mode 100644 index 000000000..ecad47e9b --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsId.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 public +package table_with_generated_columns + +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 + +/** Type for the primary key of table `public.table-with-generated-columns` */ +case class TableWithGeneratedColumnsId(value: String) extends AnyVal +object TableWithGeneratedColumnsId { + implicit lazy val arrayGet: Get[Array[TableWithGeneratedColumnsId]] = adventureworks.StringArrayMeta.get.map(_.map(TableWithGeneratedColumnsId.apply)) + implicit lazy val arrayPut: Put[Array[TableWithGeneratedColumnsId]] = adventureworks.StringArrayMeta.put.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[TableWithGeneratedColumnsId, String] = Bijection[TableWithGeneratedColumnsId, String](_.value)(TableWithGeneratedColumnsId.apply) + implicit lazy val decoder: Decoder[TableWithGeneratedColumnsId] = Decoder.decodeString.map(TableWithGeneratedColumnsId.apply) + implicit lazy val encoder: Encoder[TableWithGeneratedColumnsId] = Encoder.encodeString.contramap(_.value) + implicit lazy val get: Get[TableWithGeneratedColumnsId] = Meta.StringMeta.get.map(TableWithGeneratedColumnsId.apply) + implicit lazy val ordering: Ordering[TableWithGeneratedColumnsId] = Ordering.by(_.value) + implicit lazy val put: Put[TableWithGeneratedColumnsId] = Meta.StringMeta.put.contramap(_.value) + implicit lazy val text: Text[TableWithGeneratedColumnsId] = new Text[TableWithGeneratedColumnsId] { + override def unsafeEncode(v: TableWithGeneratedColumnsId, sb: StringBuilder) = Text.stringInstance.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: TableWithGeneratedColumnsId, sb: StringBuilder) = Text.stringInstance.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepo.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepo.scala new file mode 100644 index 000000000..65a468aa5 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepo.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 public +package table_with_generated_columns + +import doobie.free.connection.ConnectionIO +import fs2.Stream +import typo.dsl.DeleteBuilder +import typo.dsl.SelectBuilder +import typo.dsl.UpdateBuilder + +trait TableWithGeneratedColumnsRepo { + def delete: DeleteBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] + def deleteById(name: TableWithGeneratedColumnsId): ConnectionIO[Boolean] + def deleteByIds(names: Array[TableWithGeneratedColumnsId]): ConnectionIO[Int] + def insert(unsaved: TableWithGeneratedColumnsRow): ConnectionIO[TableWithGeneratedColumnsRow] + def insert(unsaved: TableWithGeneratedColumnsRowUnsaved): ConnectionIO[TableWithGeneratedColumnsRow] + def insertStreaming(unsaved: Stream[ConnectionIO, TableWithGeneratedColumnsRow], batchSize: Int = 10000): ConnectionIO[Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, TableWithGeneratedColumnsRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] + def select: SelectBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] + def selectAll: Stream[ConnectionIO, TableWithGeneratedColumnsRow] + def selectById(name: TableWithGeneratedColumnsId): ConnectionIO[Option[TableWithGeneratedColumnsRow]] + def selectByIds(names: Array[TableWithGeneratedColumnsId]): Stream[ConnectionIO, TableWithGeneratedColumnsRow] + def selectByIdsTracked(names: Array[TableWithGeneratedColumnsId]): ConnectionIO[Map[TableWithGeneratedColumnsId, TableWithGeneratedColumnsRow]] + def update: UpdateBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] + def upsert(unsaved: TableWithGeneratedColumnsRow): ConnectionIO[TableWithGeneratedColumnsRow] + def upsertBatch(unsaved: List[TableWithGeneratedColumnsRow]): Stream[ConnectionIO, TableWithGeneratedColumnsRow] + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + def upsertStreaming(unsaved: Stream[ConnectionIO, TableWithGeneratedColumnsRow], batchSize: Int = 10000): ConnectionIO[Int] +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoImpl.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoImpl.scala new file mode 100644 index 000000000..00cd42dce --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoImpl.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 public +package table_with_generated_columns + +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 TableWithGeneratedColumnsRepoImpl extends TableWithGeneratedColumnsRepo { + override def delete: DeleteBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = { + DeleteBuilder(""""public"."table-with-generated-columns"""", TableWithGeneratedColumnsFields.structure) + } + override def deleteById(name: TableWithGeneratedColumnsId): ConnectionIO[Boolean] = { + sql"""delete from "public"."table-with-generated-columns" where "name" = ${fromWrite(name)(Write.fromPut(TableWithGeneratedColumnsId.put))}""".update.run.map(_ > 0) + } + override def deleteByIds(names: Array[TableWithGeneratedColumnsId]): ConnectionIO[Int] = { + sql"""delete from "public"."table-with-generated-columns" where "name" = ANY(${names})""".update.run + } + override def insert(unsaved: TableWithGeneratedColumnsRow): ConnectionIO[TableWithGeneratedColumnsRow] = { + sql"""insert into "public"."table-with-generated-columns"("name") + values (${fromWrite(unsaved.name)(Write.fromPut(TableWithGeneratedColumnsId.put))}) + returning "name", "name-type-always" + """.query(using TableWithGeneratedColumnsRow.read).unique + } + override def insert(unsaved: TableWithGeneratedColumnsRowUnsaved): ConnectionIO[TableWithGeneratedColumnsRow] = { + val fs = List( + Some((Fragment.const0(s""""name""""), fr"${fromWrite(unsaved.name)(Write.fromPut(TableWithGeneratedColumnsId.put))}")) + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "public"."table-with-generated-columns" default values + returning "name", "name-type-always" + """ + } else { + val CommaSeparate = Fragment.FragmentMonoid.intercalate(fr", ") + sql"""insert into "public"."table-with-generated-columns"(${CommaSeparate.combineAllOption(fs.map { case (n, _) => n }).get}) + values (${CommaSeparate.combineAllOption(fs.map { case (_, f) => f }).get}) + returning "name", "name-type-always" + """ + } + q.query(using TableWithGeneratedColumnsRow.read).unique + + } + override def insertStreaming(unsaved: Stream[ConnectionIO, TableWithGeneratedColumnsRow], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "public"."table-with-generated-columns"("name") FROM STDIN""").copyIn(unsaved, batchSize)(using TableWithGeneratedColumnsRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, TableWithGeneratedColumnsRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + new FragmentOps(sql"""COPY "public"."table-with-generated-columns"("name") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""").copyIn(unsaved, batchSize)(using TableWithGeneratedColumnsRowUnsaved.text) + } + override def select: SelectBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = { + SelectBuilderSql(""""public"."table-with-generated-columns"""", TableWithGeneratedColumnsFields.structure, TableWithGeneratedColumnsRow.read) + } + override def selectAll: Stream[ConnectionIO, TableWithGeneratedColumnsRow] = { + sql"""select "name", "name-type-always" from "public"."table-with-generated-columns"""".query(using TableWithGeneratedColumnsRow.read).stream + } + override def selectById(name: TableWithGeneratedColumnsId): ConnectionIO[Option[TableWithGeneratedColumnsRow]] = { + sql"""select "name", "name-type-always" from "public"."table-with-generated-columns" where "name" = ${fromWrite(name)(Write.fromPut(TableWithGeneratedColumnsId.put))}""".query(using TableWithGeneratedColumnsRow.read).option + } + override def selectByIds(names: Array[TableWithGeneratedColumnsId]): Stream[ConnectionIO, TableWithGeneratedColumnsRow] = { + sql"""select "name", "name-type-always" from "public"."table-with-generated-columns" where "name" = ANY(${names})""".query(using TableWithGeneratedColumnsRow.read).stream + } + override def selectByIdsTracked(names: Array[TableWithGeneratedColumnsId]): ConnectionIO[Map[TableWithGeneratedColumnsId, TableWithGeneratedColumnsRow]] = { + selectByIds(names).compile.toList.map { rows => + val byId = rows.view.map(x => (x.name, x)).toMap + names.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = { + UpdateBuilder(""""public"."table-with-generated-columns"""", TableWithGeneratedColumnsFields.structure, TableWithGeneratedColumnsRow.read) + } + override def upsert(unsaved: TableWithGeneratedColumnsRow): ConnectionIO[TableWithGeneratedColumnsRow] = { + sql"""insert into "public"."table-with-generated-columns"("name") + values ( + ${fromWrite(unsaved.name)(Write.fromPut(TableWithGeneratedColumnsId.put))} + ) + on conflict ("name") + do nothing + returning "name", "name-type-always" + """.query(using TableWithGeneratedColumnsRow.read).unique + } + override def upsertBatch(unsaved: List[TableWithGeneratedColumnsRow]): Stream[ConnectionIO, TableWithGeneratedColumnsRow] = { + Update[TableWithGeneratedColumnsRow]( + s"""insert into "public"."table-with-generated-columns"("name") + values (?) + on conflict ("name") + do nothing + returning "name", "name-type-always"""" + )(using TableWithGeneratedColumnsRow.write) + .updateManyWithGeneratedKeys[TableWithGeneratedColumnsRow]("name", "name-type-always")(unsaved)(using catsStdInstancesForList, TableWithGeneratedColumnsRow.read) + } + /* NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */ + override def upsertStreaming(unsaved: Stream[ConnectionIO, TableWithGeneratedColumnsRow], batchSize: Int = 10000): ConnectionIO[Int] = { + for { + _ <- sql"""create temporary table table-with-generated-columns_TEMP (like "public"."table-with-generated-columns") on commit drop""".update.run + _ <- new FragmentOps(sql"""copy table-with-generated-columns_TEMP("name") from stdin""").copyIn(unsaved, batchSize)(using TableWithGeneratedColumnsRow.text) + res <- sql"""insert into "public"."table-with-generated-columns"("name") + select * from table-with-generated-columns_TEMP + on conflict ("name") + do nothing + ; + drop table table-with-generated-columns_TEMP;""".update.run + } yield res + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoMock.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoMock.scala new file mode 100644 index 000000000..dd95ab550 --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoMock.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 public +package table_with_generated_columns + +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 TableWithGeneratedColumnsRepoMock(toRow: Function1[TableWithGeneratedColumnsRowUnsaved, TableWithGeneratedColumnsRow], + map: scala.collection.mutable.Map[TableWithGeneratedColumnsId, TableWithGeneratedColumnsRow] = scala.collection.mutable.Map.empty) extends TableWithGeneratedColumnsRepo { + override def delete: DeleteBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = { + DeleteBuilderMock(DeleteParams.empty, TableWithGeneratedColumnsFields.structure, map) + } + override def deleteById(name: TableWithGeneratedColumnsId): ConnectionIO[Boolean] = { + delay(map.remove(name).isDefined) + } + override def deleteByIds(names: Array[TableWithGeneratedColumnsId]): ConnectionIO[Int] = { + delay(names.map(id => map.remove(id)).count(_.isDefined)) + } + override def insert(unsaved: TableWithGeneratedColumnsRow): ConnectionIO[TableWithGeneratedColumnsRow] = { + delay { + val _ = if (map.contains(unsaved.name)) + sys.error(s"id ${unsaved.name} already exists") + else + map.put(unsaved.name, unsaved) + + unsaved + } + } + override def insert(unsaved: TableWithGeneratedColumnsRowUnsaved): ConnectionIO[TableWithGeneratedColumnsRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: Stream[ConnectionIO, TableWithGeneratedColumnsRow], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { rows => + var num = 0L + rows.foreach { row => + map += (row.name -> row) + num += 1 + } + num + } + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: Stream[ConnectionIO, TableWithGeneratedColumnsRowUnsaved], batchSize: Int = 10000): ConnectionIO[Long] = { + unsaved.compile.toList.map { unsavedRows => + var num = 0L + unsavedRows.foreach { unsavedRow => + val row = toRow(unsavedRow) + map += (row.name -> row) + num += 1 + } + num + } + } + override def select: SelectBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = { + SelectBuilderMock(TableWithGeneratedColumnsFields.structure, delay(map.values.toList), SelectParams.empty) + } + override def selectAll: Stream[ConnectionIO, TableWithGeneratedColumnsRow] = { + Stream.emits(map.values.toList) + } + override def selectById(name: TableWithGeneratedColumnsId): ConnectionIO[Option[TableWithGeneratedColumnsRow]] = { + delay(map.get(name)) + } + override def selectByIds(names: Array[TableWithGeneratedColumnsId]): Stream[ConnectionIO, TableWithGeneratedColumnsRow] = { + Stream.emits(names.flatMap(map.get).toList) + } + override def selectByIdsTracked(names: Array[TableWithGeneratedColumnsId]): ConnectionIO[Map[TableWithGeneratedColumnsId, TableWithGeneratedColumnsRow]] = { + selectByIds(names).compile.toList.map { rows => + val byId = rows.view.map(x => (x.name, x)).toMap + names.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = { + UpdateBuilderMock(UpdateParams.empty, TableWithGeneratedColumnsFields.structure, map) + } + override def upsert(unsaved: TableWithGeneratedColumnsRow): ConnectionIO[TableWithGeneratedColumnsRow] = { + delay { + map.put(unsaved.name, unsaved): @nowarn + unsaved + } + } + override def upsertBatch(unsaved: List[TableWithGeneratedColumnsRow]): Stream[ConnectionIO, TableWithGeneratedColumnsRow] = { + Stream.emits { + unsaved.map { row => + map += (row.name -> 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, TableWithGeneratedColumnsRow], batchSize: Int = 10000): ConnectionIO[Int] = { + unsaved.compile.toList.map { rows => + var num = 0 + rows.foreach { row => + map += (row.name -> row) + num += 1 + } + num + } + } +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRow.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRow.scala new file mode 100644 index 000000000..6d573d5df --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRow.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 public +package table_with_generated_columns + +import doobie.enumerated.Nullability +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 +import java.sql.ResultSet + +/** Table: public.table-with-generated-columns + Primary key: name */ +case class TableWithGeneratedColumnsRow( + name: TableWithGeneratedColumnsId, + /** Generated ALWAYS, expression: + CASE + WHEN (name IS NOT NULL) THEN 'no-name'::text + WHEN (name = 'a'::text) THEN 'a-name'::text + ELSE 'some-name'::text + END */ + nameTypeAlways: String +){ + val id = name + def toUnsavedRow(): TableWithGeneratedColumnsRowUnsaved = + TableWithGeneratedColumnsRowUnsaved(name) + } + +object TableWithGeneratedColumnsRow { + implicit lazy val decoder: Decoder[TableWithGeneratedColumnsRow] = Decoder.forProduct2[TableWithGeneratedColumnsRow, TableWithGeneratedColumnsId, String]("name", "name-type-always")(TableWithGeneratedColumnsRow.apply)(TableWithGeneratedColumnsId.decoder, Decoder.decodeString) + implicit lazy val encoder: Encoder[TableWithGeneratedColumnsRow] = Encoder.forProduct2[TableWithGeneratedColumnsRow, TableWithGeneratedColumnsId, String]("name", "name-type-always")(x => (x.name, x.nameTypeAlways))(TableWithGeneratedColumnsId.encoder, Encoder.encodeString) + implicit lazy val read: Read[TableWithGeneratedColumnsRow] = new Read[TableWithGeneratedColumnsRow]( + gets = List( + (TableWithGeneratedColumnsId.get, Nullability.NoNulls), + (Meta.StringMeta.get, Nullability.NoNulls) + ), + unsafeGet = (rs: ResultSet, i: Int) => TableWithGeneratedColumnsRow( + name = TableWithGeneratedColumnsId.get.unsafeGetNonNullable(rs, i + 0), + nameTypeAlways = Meta.StringMeta.get.unsafeGetNonNullable(rs, i + 1) + ) + ) + implicit lazy val text: Text[TableWithGeneratedColumnsRow] = Text.instance[TableWithGeneratedColumnsRow]{ (row, sb) => + TableWithGeneratedColumnsId.text.unsafeEncode(row.name, sb) + } + implicit lazy val write: Write[TableWithGeneratedColumnsRow] = new Write[TableWithGeneratedColumnsRow]( + puts = List((TableWithGeneratedColumnsId.put, Nullability.NoNulls)), + toList = x => List(x.name), + unsafeSet = (rs, i, a) => { + TableWithGeneratedColumnsId.put.unsafeSetNonNullable(rs, i + 0, a.name) + }, + unsafeUpdate = (ps, i, a) => { + TableWithGeneratedColumnsId.put.unsafeUpdateNonNullable(ps, i + 0, a.name) + } + ) +} diff --git a/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRowUnsaved.scala b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRowUnsaved.scala new file mode 100644 index 000000000..6300b02cc --- /dev/null +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRowUnsaved.scala @@ -0,0 +1,30 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package public +package table_with_generated_columns + +import doobie.postgres.Text +import io.circe.Decoder +import io.circe.Encoder + +/** This class corresponds to a row in table `public.table-with-generated-columns` which has not been persisted yet */ +case class TableWithGeneratedColumnsRowUnsaved( + name: TableWithGeneratedColumnsId +) { + def toRow(nameTypeAlwaysDefault: => String): TableWithGeneratedColumnsRow = + TableWithGeneratedColumnsRow( + name = name, + nameTypeAlways = nameTypeAlwaysDefault + ) +} +object TableWithGeneratedColumnsRowUnsaved { + implicit lazy val decoder: Decoder[TableWithGeneratedColumnsRowUnsaved] = Decoder.forProduct1[TableWithGeneratedColumnsRowUnsaved, TableWithGeneratedColumnsId]("name")(TableWithGeneratedColumnsRowUnsaved.apply)(TableWithGeneratedColumnsId.decoder) + implicit lazy val encoder: Encoder[TableWithGeneratedColumnsRowUnsaved] = Encoder.forProduct1[TableWithGeneratedColumnsRowUnsaved, TableWithGeneratedColumnsId]("name")(x => (x.name))(TableWithGeneratedColumnsId.encoder) + implicit lazy val text: Text[TableWithGeneratedColumnsRowUnsaved] = Text.instance[TableWithGeneratedColumnsRowUnsaved]{ (row, sb) => + TableWithGeneratedColumnsId.text.unsafeEncode(row.name, 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 43a30e32d..7f0e5f34b 100644 --- a/typo-tester-doobie/generated-and-checked-in/adventureworks/testInsert.scala +++ b/typo-tester-doobie/generated-and-checked-in/adventureworks/testInsert.scala @@ -208,6 +208,10 @@ import adventureworks.public.pgtest.PgtestRepoImpl import adventureworks.public.pgtest.PgtestRow import adventureworks.public.pgtestnull.PgtestnullRepoImpl import adventureworks.public.pgtestnull.PgtestnullRow +import adventureworks.public.table_with_generated_columns.TableWithGeneratedColumnsId +import adventureworks.public.table_with_generated_columns.TableWithGeneratedColumnsRepoImpl +import adventureworks.public.table_with_generated_columns.TableWithGeneratedColumnsRow +import adventureworks.public.table_with_generated_columns.TableWithGeneratedColumnsRowUnsaved import adventureworks.public.title.TitleId import adventureworks.public.title.TitleRepoImpl import adventureworks.public.title.TitleRow @@ -781,6 +785,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { varchares: Option[Array[String]] = if (random.nextBoolean()) None else Some(Array.fill(random.nextInt(3))(random.alphanumeric.take(20).mkString)), xmles: Option[Array[TypoXml]] = None ): ConnectionIO[PgtestnullRow] = (new PgtestnullRepoImpl).insert(new PgtestnullRow(bool = bool, box = box, bpchar = bpchar, bytea = bytea, char = char, circle = circle, date = date, float4 = float4, float8 = float8, hstore = hstore, inet = inet, int2 = int2, int2vector = int2vector, int4 = int4, int8 = int8, interval = interval, json = json, jsonb = jsonb, line = line, lseg = lseg, money = money, mydomain = mydomain, myenum = myenum, name = name, numeric = numeric, path = path, point = point, polygon = polygon, text = text, time = time, timestamp = timestamp, timestampz = timestampz, timez = timez, uuid = uuid, varchar = varchar, vector = vector, xml = xml, boxes = boxes, bpchares = bpchares, chares = chares, circlees = circlees, datees = datees, float4es = float4es, float8es = float8es, inetes = inetes, int2es = int2es, int2vectores = int2vectores, int4es = int4es, int8es = int8es, intervales = intervales, jsones = jsones, jsonbes = jsonbes, linees = linees, lseges = lseges, moneyes = moneyes, mydomaines = mydomaines, myenumes = myenumes, namees = namees, numerices = numerices, pathes = pathes, pointes = pointes, polygones = polygones, textes = textes, timees = timees, timestampes = timestampes, timestampzes = timestampzes, timezes = timezes, uuides = uuides, varchares = varchares, xmles = xmles)) + def publicTableWithGeneratedColumns(name: TableWithGeneratedColumnsId = TableWithGeneratedColumnsId(random.alphanumeric.take(20).mkString)): ConnectionIO[TableWithGeneratedColumnsRow] = (new TableWithGeneratedColumnsRepoImpl).insert(new TableWithGeneratedColumnsRowUnsaved(name = name)) def publicTitle(code: TitleId = TitleId(random.alphanumeric.take(20).mkString)): ConnectionIO[TitleRow] = (new TitleRepoImpl).insert(new TitleRow(code = code)) def publicTitleDomain(code: TitleDomainId = TitleDomainId(domainInsert.publicShortText(random))): ConnectionIO[TitleDomainRow] = (new TitleDomainRepoImpl).insert(new TitleDomainRow(code = code)) def publicTitledperson(titleShort: TitleDomainId = TitleDomainId.All(random.nextInt(4)), diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsFields.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsFields.scala new file mode 100644 index 000000000..422245e0c --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsFields.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 public +package table_with_generated_columns + +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 TableWithGeneratedColumnsFields { + def name: IdField[TableWithGeneratedColumnsId, TableWithGeneratedColumnsRow] + def nameTypeAlways: Field[String, TableWithGeneratedColumnsRow] +} + +object TableWithGeneratedColumnsFields { + lazy val structure: Relation[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = + new Impl(Nil) + + private final class Impl(val _path: List[Path]) + extends Relation[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] { + + override lazy val fields: TableWithGeneratedColumnsFields = new TableWithGeneratedColumnsFields { + override def name = IdField[TableWithGeneratedColumnsId, TableWithGeneratedColumnsRow](_path, "name", None, None, x => x.name, (row, value) => row.copy(name = value)) + override def nameTypeAlways = Field[String, TableWithGeneratedColumnsRow](_path, "name-type-always", None, None, x => x.nameTypeAlways, (row, value) => row.copy(nameTypeAlways = value)) + } + + override lazy val columns: List[FieldLikeNoHkt[?, TableWithGeneratedColumnsRow]] = + List[FieldLikeNoHkt[?, TableWithGeneratedColumnsRow]](fields.name, fields.nameTypeAlways) + + override def copy(path: List[Path]): Impl = + new Impl(path) + } + +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsId.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsId.scala new file mode 100644 index 000000000..5ffb9bb2e --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsId.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 +package table_with_generated_columns + +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 `public.table-with-generated-columns` */ +case class TableWithGeneratedColumnsId(value: String) extends AnyVal +object TableWithGeneratedColumnsId { + implicit lazy val arrayJdbcDecoder: JdbcDecoder[Array[TableWithGeneratedColumnsId]] = adventureworks.StringArrayDecoder.map(_.map(TableWithGeneratedColumnsId.apply)) + implicit lazy val arrayJdbcEncoder: JdbcEncoder[Array[TableWithGeneratedColumnsId]] = adventureworks.StringArrayEncoder.contramap(_.map(_.value)) + implicit lazy val arraySetter: Setter[Array[TableWithGeneratedColumnsId]] = adventureworks.StringArraySetter.contramap(_.map(_.value)) + implicit lazy val bijection: Bijection[TableWithGeneratedColumnsId, String] = Bijection[TableWithGeneratedColumnsId, String](_.value)(TableWithGeneratedColumnsId.apply) + implicit lazy val jdbcDecoder: JdbcDecoder[TableWithGeneratedColumnsId] = JdbcDecoder.stringDecoder.map(TableWithGeneratedColumnsId.apply) + implicit lazy val jdbcEncoder: JdbcEncoder[TableWithGeneratedColumnsId] = JdbcEncoder.stringEncoder.contramap(_.value) + implicit lazy val jsonDecoder: JsonDecoder[TableWithGeneratedColumnsId] = JsonDecoder.string.map(TableWithGeneratedColumnsId.apply) + implicit lazy val jsonEncoder: JsonEncoder[TableWithGeneratedColumnsId] = JsonEncoder.string.contramap(_.value) + implicit lazy val ordering: Ordering[TableWithGeneratedColumnsId] = Ordering.by(_.value) + implicit lazy val pgType: PGType[TableWithGeneratedColumnsId] = PGType.PGTypeString.as + implicit lazy val setter: Setter[TableWithGeneratedColumnsId] = Setter.stringSetter.contramap(_.value) + implicit lazy val text: Text[TableWithGeneratedColumnsId] = new Text[TableWithGeneratedColumnsId] { + override def unsafeEncode(v: TableWithGeneratedColumnsId, sb: StringBuilder) = Text.stringInstance.unsafeEncode(v.value, sb) + override def unsafeArrayEncode(v: TableWithGeneratedColumnsId, sb: StringBuilder) = Text.stringInstance.unsafeArrayEncode(v.value, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepo.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepo.scala new file mode 100644 index 000000000..7c5752631 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepo.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 public +package table_with_generated_columns + +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 TableWithGeneratedColumnsRepo { + def delete: DeleteBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] + def deleteById(name: TableWithGeneratedColumnsId): ZIO[ZConnection, Throwable, Boolean] + def deleteByIds(names: Array[TableWithGeneratedColumnsId]): ZIO[ZConnection, Throwable, Long] + def insert(unsaved: TableWithGeneratedColumnsRow): ZIO[ZConnection, Throwable, TableWithGeneratedColumnsRow] + def insert(unsaved: TableWithGeneratedColumnsRowUnsaved): ZIO[ZConnection, Throwable, TableWithGeneratedColumnsRow] + def insertStreaming(unsaved: ZStream[ZConnection, Throwable, TableWithGeneratedColumnsRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, TableWithGeneratedColumnsRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] + def select: SelectBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] + def selectAll: ZStream[ZConnection, Throwable, TableWithGeneratedColumnsRow] + def selectById(name: TableWithGeneratedColumnsId): ZIO[ZConnection, Throwable, Option[TableWithGeneratedColumnsRow]] + def selectByIds(names: Array[TableWithGeneratedColumnsId]): ZStream[ZConnection, Throwable, TableWithGeneratedColumnsRow] + def selectByIdsTracked(names: Array[TableWithGeneratedColumnsId]): ZIO[ZConnection, Throwable, Map[TableWithGeneratedColumnsId, TableWithGeneratedColumnsRow]] + def update: UpdateBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] + def upsert(unsaved: TableWithGeneratedColumnsRow): ZIO[ZConnection, Throwable, UpdateResult[TableWithGeneratedColumnsRow]] + // 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, TableWithGeneratedColumnsRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoImpl.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoImpl.scala new file mode 100644 index 000000000..232bf216f --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoImpl.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 public +package table_with_generated_columns + +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.UpdateResult +import zio.jdbc.ZConnection +import zio.jdbc.sqlInterpolator +import zio.stream.ZStream + +class TableWithGeneratedColumnsRepoImpl extends TableWithGeneratedColumnsRepo { + override def delete: DeleteBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = { + DeleteBuilder(""""public"."table-with-generated-columns"""", TableWithGeneratedColumnsFields.structure) + } + override def deleteById(name: TableWithGeneratedColumnsId): ZIO[ZConnection, Throwable, Boolean] = { + sql"""delete from "public"."table-with-generated-columns" where "name" = ${Segment.paramSegment(name)(TableWithGeneratedColumnsId.setter)}""".delete.map(_ > 0) + } + override def deleteByIds(names: Array[TableWithGeneratedColumnsId]): ZIO[ZConnection, Throwable, Long] = { + sql"""delete from "public"."table-with-generated-columns" where "name" = ANY(${names})""".delete + } + override def insert(unsaved: TableWithGeneratedColumnsRow): ZIO[ZConnection, Throwable, TableWithGeneratedColumnsRow] = { + sql"""insert into "public"."table-with-generated-columns"("name") + values (${Segment.paramSegment(unsaved.name)(TableWithGeneratedColumnsId.setter)}) + returning "name", "name-type-always" + """.insertReturning(using TableWithGeneratedColumnsRow.jdbcDecoder).map(_.updatedKeys.head) + } + override def insert(unsaved: TableWithGeneratedColumnsRowUnsaved): ZIO[ZConnection, Throwable, TableWithGeneratedColumnsRow] = { + val fs = List( + Some((sql""""name"""", sql"${Segment.paramSegment(unsaved.name)(TableWithGeneratedColumnsId.setter)}")) + ).flatten + + val q = if (fs.isEmpty) { + sql"""insert into "public"."table-with-generated-columns" default values + returning "name", "name-type-always" + """ + } else { + val names = fs.map { case (n, _) => n }.mkFragment(SqlFragment(", ")) + val values = fs.map { case (_, f) => f }.mkFragment(SqlFragment(", ")) + sql"""insert into "public"."table-with-generated-columns"($names) values ($values) returning "name", "name-type-always"""" + } + q.insertReturning(using TableWithGeneratedColumnsRow.jdbcDecoder).map(_.updatedKeys.head) + + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, TableWithGeneratedColumnsRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "public"."table-with-generated-columns"("name") FROM STDIN""", batchSize, unsaved)(TableWithGeneratedColumnsRow.text) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, TableWithGeneratedColumnsRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + streamingInsert(s"""COPY "public"."table-with-generated-columns"("name") FROM STDIN (DEFAULT '__DEFAULT_VALUE__')""", batchSize, unsaved)(TableWithGeneratedColumnsRowUnsaved.text) + } + override def select: SelectBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = { + SelectBuilderSql(""""public"."table-with-generated-columns"""", TableWithGeneratedColumnsFields.structure, TableWithGeneratedColumnsRow.jdbcDecoder) + } + override def selectAll: ZStream[ZConnection, Throwable, TableWithGeneratedColumnsRow] = { + sql"""select "name", "name-type-always" from "public"."table-with-generated-columns"""".query(using TableWithGeneratedColumnsRow.jdbcDecoder).selectStream() + } + override def selectById(name: TableWithGeneratedColumnsId): ZIO[ZConnection, Throwable, Option[TableWithGeneratedColumnsRow]] = { + sql"""select "name", "name-type-always" from "public"."table-with-generated-columns" where "name" = ${Segment.paramSegment(name)(TableWithGeneratedColumnsId.setter)}""".query(using TableWithGeneratedColumnsRow.jdbcDecoder).selectOne + } + override def selectByIds(names: Array[TableWithGeneratedColumnsId]): ZStream[ZConnection, Throwable, TableWithGeneratedColumnsRow] = { + sql"""select "name", "name-type-always" from "public"."table-with-generated-columns" where "name" = ANY(${Segment.paramSegment(names)(TableWithGeneratedColumnsId.arraySetter)})""".query(using TableWithGeneratedColumnsRow.jdbcDecoder).selectStream() + } + override def selectByIdsTracked(names: Array[TableWithGeneratedColumnsId]): ZIO[ZConnection, Throwable, Map[TableWithGeneratedColumnsId, TableWithGeneratedColumnsRow]] = { + selectByIds(names).runCollect.map { rows => + val byId = rows.view.map(x => (x.name, x)).toMap + names.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = { + UpdateBuilder(""""public"."table-with-generated-columns"""", TableWithGeneratedColumnsFields.structure, TableWithGeneratedColumnsRow.jdbcDecoder) + } + override def upsert(unsaved: TableWithGeneratedColumnsRow): ZIO[ZConnection, Throwable, UpdateResult[TableWithGeneratedColumnsRow]] = { + sql"""insert into "public"."table-with-generated-columns"("name") + values ( + ${Segment.paramSegment(unsaved.name)(TableWithGeneratedColumnsId.setter)} + ) + on conflict ("name") + do nothing + returning "name", "name-type-always"""".insertReturning(using TableWithGeneratedColumnsRow.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, TableWithGeneratedColumnsRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + val created = sql"""create temporary table table-with-generated-columns_TEMP (like "public"."table-with-generated-columns") on commit drop""".execute + val copied = streamingInsert(s"""copy table-with-generated-columns_TEMP("name") from stdin""", batchSize, unsaved)(TableWithGeneratedColumnsRow.text) + val merged = sql"""insert into "public"."table-with-generated-columns"("name") + select * from table-with-generated-columns_TEMP + on conflict ("name") + do nothing + ; + drop table table-with-generated-columns_TEMP;""".update + created *> copied *> merged + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoMock.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoMock.scala new file mode 100644 index 000000000..dc08a5491 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoMock.scala @@ -0,0 +1,105 @@ +/** + * File has been automatically generated by `typo`. + * + * IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN. + */ +package adventureworks +package public +package table_with_generated_columns + +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 TableWithGeneratedColumnsRepoMock(toRow: Function1[TableWithGeneratedColumnsRowUnsaved, TableWithGeneratedColumnsRow], + map: scala.collection.mutable.Map[TableWithGeneratedColumnsId, TableWithGeneratedColumnsRow] = scala.collection.mutable.Map.empty) extends TableWithGeneratedColumnsRepo { + override def delete: DeleteBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = { + DeleteBuilderMock(DeleteParams.empty, TableWithGeneratedColumnsFields.structure, map) + } + override def deleteById(name: TableWithGeneratedColumnsId): ZIO[ZConnection, Throwable, Boolean] = { + ZIO.succeed(map.remove(name).isDefined) + } + override def deleteByIds(names: Array[TableWithGeneratedColumnsId]): ZIO[ZConnection, Throwable, Long] = { + ZIO.succeed(names.map(id => map.remove(id)).count(_.isDefined).toLong) + } + override def insert(unsaved: TableWithGeneratedColumnsRow): ZIO[ZConnection, Throwable, TableWithGeneratedColumnsRow] = { + ZIO.succeed { + val _ = + if (map.contains(unsaved.name)) + sys.error(s"id ${unsaved.name} already exists") + else + map.put(unsaved.name, unsaved) + + unsaved + } + } + override def insert(unsaved: TableWithGeneratedColumnsRowUnsaved): ZIO[ZConnection, Throwable, TableWithGeneratedColumnsRow] = { + insert(toRow(unsaved)) + } + override def insertStreaming(unsaved: ZStream[ZConnection, Throwable, TableWithGeneratedColumnsRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.name -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + /* NOTE: this functionality requires PostgreSQL 16 or later! */ + override def insertUnsavedStreaming(unsaved: ZStream[ZConnection, Throwable, TableWithGeneratedColumnsRowUnsaved], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, unsavedRow) => + ZIO.succeed { + val row = toRow(unsavedRow) + map += (row.name -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } + override def select: SelectBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = { + SelectBuilderMock(TableWithGeneratedColumnsFields.structure, ZIO.succeed(Chunk.fromIterable(map.values)), SelectParams.empty) + } + override def selectAll: ZStream[ZConnection, Throwable, TableWithGeneratedColumnsRow] = { + ZStream.fromIterable(map.values) + } + override def selectById(name: TableWithGeneratedColumnsId): ZIO[ZConnection, Throwable, Option[TableWithGeneratedColumnsRow]] = { + ZIO.succeed(map.get(name)) + } + override def selectByIds(names: Array[TableWithGeneratedColumnsId]): ZStream[ZConnection, Throwable, TableWithGeneratedColumnsRow] = { + ZStream.fromIterable(names.flatMap(map.get)) + } + override def selectByIdsTracked(names: Array[TableWithGeneratedColumnsId]): ZIO[ZConnection, Throwable, Map[TableWithGeneratedColumnsId, TableWithGeneratedColumnsRow]] = { + selectByIds(names).runCollect.map { rows => + val byId = rows.view.map(x => (x.name, x)).toMap + names.view.flatMap(id => byId.get(id).map(x => (id, x))).toMap + } + } + override def update: UpdateBuilder[TableWithGeneratedColumnsFields, TableWithGeneratedColumnsRow] = { + UpdateBuilderMock(UpdateParams.empty, TableWithGeneratedColumnsFields.structure, map) + } + override def upsert(unsaved: TableWithGeneratedColumnsRow): ZIO[ZConnection, Throwable, UpdateResult[TableWithGeneratedColumnsRow]] = { + ZIO.succeed { + map.put(unsaved.name, 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, TableWithGeneratedColumnsRow], batchSize: Int = 10000): ZIO[ZConnection, Throwable, Long] = { + unsaved.scanZIO(0L) { case (acc, row) => + ZIO.succeed { + map += (row.name -> row) + acc + 1 + } + }.runLast.map(_.getOrElse(0L)) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRow.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRow.scala new file mode 100644 index 000000000..54a61ed58 --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRow.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 public +package table_with_generated_columns + +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: public.table-with-generated-columns + Primary key: name */ +case class TableWithGeneratedColumnsRow( + name: TableWithGeneratedColumnsId, + /** Generated ALWAYS, expression: + CASE + WHEN (name IS NOT NULL) THEN 'no-name'::text + WHEN (name = 'a'::text) THEN 'a-name'::text + ELSE 'some-name'::text + END */ + nameTypeAlways: String +){ + val id = name + def toUnsavedRow(): TableWithGeneratedColumnsRowUnsaved = + TableWithGeneratedColumnsRowUnsaved(name) + } + +object TableWithGeneratedColumnsRow { + implicit lazy val jdbcDecoder: JdbcDecoder[TableWithGeneratedColumnsRow] = new JdbcDecoder[TableWithGeneratedColumnsRow] { + override def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, TableWithGeneratedColumnsRow) = + columIndex + 1 -> + TableWithGeneratedColumnsRow( + name = TableWithGeneratedColumnsId.jdbcDecoder.unsafeDecode(columIndex + 0, rs)._2, + nameTypeAlways = JdbcDecoder.stringDecoder.unsafeDecode(columIndex + 1, rs)._2 + ) + } + implicit lazy val jsonDecoder: JsonDecoder[TableWithGeneratedColumnsRow] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val name = jsonObj.get("name").toRight("Missing field 'name'").flatMap(_.as(TableWithGeneratedColumnsId.jsonDecoder)) + val nameTypeAlways = jsonObj.get("name-type-always").toRight("Missing field 'name-type-always'").flatMap(_.as(JsonDecoder.string)) + if (name.isRight && nameTypeAlways.isRight) + Right(TableWithGeneratedColumnsRow(name = name.toOption.get, nameTypeAlways = nameTypeAlways.toOption.get)) + else Left(List[Either[String, Any]](name, nameTypeAlways).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[TableWithGeneratedColumnsRow] = new JsonEncoder[TableWithGeneratedColumnsRow] { + override def unsafeEncode(a: TableWithGeneratedColumnsRow, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""name":""") + TableWithGeneratedColumnsId.jsonEncoder.unsafeEncode(a.name, indent, out) + out.write(",") + out.write(""""name-type-always":""") + JsonEncoder.string.unsafeEncode(a.nameTypeAlways, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[TableWithGeneratedColumnsRow] = Text.instance[TableWithGeneratedColumnsRow]{ (row, sb) => + TableWithGeneratedColumnsId.text.unsafeEncode(row.name, sb) + } +} diff --git a/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRowUnsaved.scala b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRowUnsaved.scala new file mode 100644 index 000000000..942fb1d4d --- /dev/null +++ b/typo-tester-zio-jdbc/generated-and-checked-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRowUnsaved.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 table_with_generated_columns + +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 `public.table-with-generated-columns` which has not been persisted yet */ +case class TableWithGeneratedColumnsRowUnsaved( + name: TableWithGeneratedColumnsId +) { + def toRow(nameTypeAlwaysDefault: => String): TableWithGeneratedColumnsRow = + TableWithGeneratedColumnsRow( + name = name, + nameTypeAlways = nameTypeAlwaysDefault + ) +} +object TableWithGeneratedColumnsRowUnsaved { + implicit lazy val jsonDecoder: JsonDecoder[TableWithGeneratedColumnsRowUnsaved] = JsonDecoder[Json.Obj].mapOrFail { jsonObj => + val name = jsonObj.get("name").toRight("Missing field 'name'").flatMap(_.as(TableWithGeneratedColumnsId.jsonDecoder)) + if (name.isRight) + Right(TableWithGeneratedColumnsRowUnsaved(name = name.toOption.get)) + else Left(List[Either[String, Any]](name).flatMap(_.left.toOption).mkString(", ")) + } + implicit lazy val jsonEncoder: JsonEncoder[TableWithGeneratedColumnsRowUnsaved] = new JsonEncoder[TableWithGeneratedColumnsRowUnsaved] { + override def unsafeEncode(a: TableWithGeneratedColumnsRowUnsaved, indent: Option[Int], out: Write): Unit = { + out.write("{") + out.write(""""name":""") + TableWithGeneratedColumnsId.jsonEncoder.unsafeEncode(a.name, indent, out) + out.write("}") + } + } + implicit lazy val text: Text[TableWithGeneratedColumnsRowUnsaved] = Text.instance[TableWithGeneratedColumnsRowUnsaved]{ (row, sb) => + TableWithGeneratedColumnsId.text.unsafeEncode(row.name, 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 d38825a47..e255a307c 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 @@ -208,6 +208,10 @@ import adventureworks.public.pgtest.PgtestRepoImpl import adventureworks.public.pgtest.PgtestRow import adventureworks.public.pgtestnull.PgtestnullRepoImpl import adventureworks.public.pgtestnull.PgtestnullRow +import adventureworks.public.table_with_generated_columns.TableWithGeneratedColumnsId +import adventureworks.public.table_with_generated_columns.TableWithGeneratedColumnsRepoImpl +import adventureworks.public.table_with_generated_columns.TableWithGeneratedColumnsRow +import adventureworks.public.table_with_generated_columns.TableWithGeneratedColumnsRowUnsaved import adventureworks.public.title.TitleId import adventureworks.public.title.TitleRepoImpl import adventureworks.public.title.TitleRow @@ -782,6 +786,7 @@ class TestInsert(random: Random, domainInsert: TestDomainInsert) { varchares: Option[Array[String]] = if (random.nextBoolean()) None else Some(Array.fill(random.nextInt(3))(random.alphanumeric.take(20).mkString)), xmles: Option[Array[TypoXml]] = None ): ZIO[ZConnection, Throwable, PgtestnullRow] = (new PgtestnullRepoImpl).insert(new PgtestnullRow(bool = bool, box = box, bpchar = bpchar, bytea = bytea, char = char, circle = circle, date = date, float4 = float4, float8 = float8, hstore = hstore, inet = inet, int2 = int2, int2vector = int2vector, int4 = int4, int8 = int8, interval = interval, json = json, jsonb = jsonb, line = line, lseg = lseg, money = money, mydomain = mydomain, myenum = myenum, name = name, numeric = numeric, path = path, point = point, polygon = polygon, text = text, time = time, timestamp = timestamp, timestampz = timestampz, timez = timez, uuid = uuid, varchar = varchar, vector = vector, xml = xml, boxes = boxes, bpchares = bpchares, chares = chares, circlees = circlees, datees = datees, float4es = float4es, float8es = float8es, inetes = inetes, int2es = int2es, int2vectores = int2vectores, int4es = int4es, int8es = int8es, intervales = intervales, jsones = jsones, jsonbes = jsonbes, linees = linees, lseges = lseges, moneyes = moneyes, mydomaines = mydomaines, myenumes = myenumes, namees = namees, numerices = numerices, pathes = pathes, pointes = pointes, polygones = polygones, textes = textes, timees = timees, timestampes = timestampes, timestampzes = timestampzes, timezes = timezes, uuides = uuides, varchares = varchares, xmles = xmles)) + def publicTableWithGeneratedColumns(name: TableWithGeneratedColumnsId = TableWithGeneratedColumnsId(random.alphanumeric.take(20).mkString)): ZIO[ZConnection, Throwable, TableWithGeneratedColumnsRow] = (new TableWithGeneratedColumnsRepoImpl).insert(new TableWithGeneratedColumnsRowUnsaved(name = name)) def publicTitle(code: TitleId = TitleId(random.alphanumeric.take(20).mkString)): ZIO[ZConnection, Throwable, TitleRow] = (new TitleRepoImpl).insert(new TitleRow(code = code)) def publicTitleDomain(code: TitleDomainId = TitleDomainId(domainInsert.publicShortText(random))): ZIO[ZConnection, Throwable, TitleDomainRow] = (new TitleDomainRepoImpl).insert(new TitleDomainRow(code = code)) def publicTitledperson(titleShort: TitleDomainId = TitleDomainId.All(random.nextInt(4)), diff --git a/typo/src/scala/typo/MetaDb.scala b/typo/src/scala/typo/MetaDb.scala index 467a4c4dd..0963a94bc 100644 --- a/typo/src/scala/typo/MetaDb.scala +++ b/typo/src/scala/typo/MetaDb.scala @@ -230,7 +230,7 @@ object MetaDb { tpe = dbType, udtName = None, columnDefault = None, - identity = None, + maybeGenerated = None, comment = comments.get(coord), jsonDescription = DebugJson(mdCol), nullability = nullability, @@ -279,15 +279,21 @@ object MetaDb { logger.warn(s"Couldn't translate type from table ${relationName.value} column ${parsedName.name.value} with type ${c.udtName}. Falling back to text") } - val identity = c.identityGeneration.map { value => - db.Identity( - identityGeneration = value, - identityStart = c.identityStart, - identityIncrement = c.identityIncrement, - identityMaximum = c.identityMaximum, - identityMinimum = c.identityMinimum - ) - } + val generated = c.identityGeneration + .map { value => + db.Generated.Identity( + identityGeneration = value, + identityStart = c.identityStart, + identityIncrement = c.identityIncrement, + identityMaximum = c.identityMaximum, + identityMinimum = c.identityMinimum + ) + } + .orElse(c.isGenerated.flatMap { + case "NEVER" => None + case generated => Some(db.Generated.IsGenerated(generated, c.generationExpression)) + }) + val coord = (relationName, parsedName.name) db.Col( parsedName = parsedName, @@ -295,7 +301,7 @@ object MetaDb { udtName = c.udtName, nullability = nullability, columnDefault = c.columnDefault, - identity = identity, + maybeGenerated = generated, comment = comments.get(coord), constraints = constraints.getOrElse(coord, Nil) ++ deps.get(parsedName.name).flatMap(otherCoord => constraints.get(otherCoord)).getOrElse(Nil), jsonDescription = jsonDescription diff --git a/typo/src/scala/typo/db.scala b/typo/src/scala/typo/db.scala index 2fd1b6a31..e2ed72142 100644 --- a/typo/src/scala/typo/db.scala +++ b/typo/src/scala/typo/db.scala @@ -78,23 +78,42 @@ object db { case class Constraint(name: String, columns: SortedSet[ColName], checkClause: String) - case class Identity( - identityGeneration: String, - identityStart: Option[String], - identityIncrement: Option[String], - identityMaximum: Option[String], - identityMinimum: Option[String] - ) { - def ALWAYS = identityGeneration == "ALWAYS" - def `BY DEFAULT` = identityGeneration == "BY DEFAULT" - def asString: String = - List( - Some(s"Identity $identityGeneration"), - identityStart.map("identityStart: " + _), - identityIncrement.map("identityIncrement: " + _), - identityMaximum.map("identityMaximum: " + _), - identityMinimum.map("identityMinimum: " + _) - ).flatten.mkString(", ") + sealed trait Generated { + def ALWAYS: Boolean + def `BY DEFAULT`: Boolean + def asString: String + } + + object Generated { + case class IsGenerated(generation: String, expression: Option[String]) extends Generated { + override def ALWAYS: Boolean = generation == "ALWAYS" + override def `BY DEFAULT`: Boolean = generation == "BY DEFAULT" + + override def asString: String = + List( + Some(s"Generated $generation"), + expression.map("expression: " + _) + ).flatten.mkString(", ") + } + + case class Identity( + identityGeneration: String, + identityStart: Option[String], + identityIncrement: Option[String], + identityMaximum: Option[String], + identityMinimum: Option[String] + ) extends Generated { + override def ALWAYS: Boolean = identityGeneration == "ALWAYS" + override def `BY DEFAULT`: Boolean = identityGeneration == "BY DEFAULT" + override def asString: String = + List( + Some(s"Identity $identityGeneration"), + identityStart.map("identityStart: " + _), + identityIncrement.map("identityIncrement: " + _), + identityMaximum.map("identityMaximum: " + _), + identityMinimum.map("identityMinimum: " + _) + ).flatten.mkString(", ") + } } case class Col( @@ -103,12 +122,12 @@ object db { udtName: Option[String], nullability: Nullability, columnDefault: Option[String], - identity: Option[Identity], + maybeGenerated: Option[Generated], comment: Option[String], constraints: List[Constraint], jsonDescription: DebugJson ) { - def isDefaulted = columnDefault.nonEmpty || identity.exists(_.`BY DEFAULT`) + def isDefaulted = columnDefault.nonEmpty || maybeGenerated.exists(_.`BY DEFAULT`) def name = parsedName.name } case class RelationName(schema: Option[String], name: String) { diff --git a/typo/src/scala/typo/internal/ComputedRowUnsaved.scala b/typo/src/scala/typo/internal/ComputedRowUnsaved.scala index e8ab80cb5..963f3ca09 100644 --- a/typo/src/scala/typo/internal/ComputedRowUnsaved.scala +++ b/typo/src/scala/typo/internal/ComputedRowUnsaved.scala @@ -2,31 +2,31 @@ package typo package internal object ComputedRowUnsaved { - def apply(source: Source, cols: NonEmptyList[ComputedColumn], default: ComputedDefault, naming: Naming): Option[ComputedRowUnsaved] = { - val (alwaysGenerated, notAlwaysGenerated) = cols.toList.partition(c => c.dbCol.identity.exists(_.ALWAYS)) + def apply(source: Source, allCols: NonEmptyList[ComputedColumn], default: ComputedDefault, naming: Naming): Option[ComputedRowUnsaved] = { + val (alwaysGenerated, notAlwaysGenerated) = allCols.toList.partition(c => c.dbCol.maybeGenerated.exists(_.ALWAYS)) val (defaultCols, restCols) = notAlwaysGenerated.partition(c => c.dbCol.isDefaulted) - - NonEmptyList.fromList(defaultCols).map { nonEmpty => - val defaultCols = nonEmpty.map { col => - (col.copy(tpe = sc.Type.TApply(default.Defaulted, List(col.tpe))), col.tpe) - } - - new ComputedRowUnsaved( - defaultCols = defaultCols, - restCols = restCols, - alwaysGeneratedCols = alwaysGenerated, - tpe = sc.Type.Qualified(naming.rowUnsaved(source)) - ) + val defaultCols2 = defaultCols.map { col => + (col.copy(tpe = sc.Type.TApply(default.Defaulted, List(col.tpe))), col.tpe) + } + NonEmptyList.fromList(restCols ::: defaultCols2.map { case (col, _) => col }).collect { + case unsavedCols if defaultCols.nonEmpty || alwaysGenerated.nonEmpty => + new ComputedRowUnsaved( + allCols, + defaultCols = defaultCols2, + restCols = restCols, + alwaysGeneratedCols = alwaysGenerated, + unsavedCols = unsavedCols, + tpe = sc.Type.Qualified(naming.rowUnsaved(source)) + ) } } } case class ComputedRowUnsaved( - defaultCols: NonEmptyList[(ComputedColumn, sc.Type)], + allCols: NonEmptyList[ComputedColumn], + defaultCols: List[(ComputedColumn, sc.Type)], restCols: List[ComputedColumn], alwaysGeneratedCols: List[ComputedColumn], + unsavedCols: NonEmptyList[ComputedColumn], tpe: sc.Type.Qualified -) { - def allCols: NonEmptyList[ComputedColumn] = - restCols ::: defaultCols.map { case (col, _) => col } -} +) diff --git a/typo/src/scala/typo/internal/ComputedSqlFile.scala b/typo/src/scala/typo/internal/ComputedSqlFile.scala index 55f4e0246..0f2ecf853 100644 --- a/typo/src/scala/typo/internal/ComputedSqlFile.scala +++ b/typo/src/scala/typo/internal/ComputedSqlFile.scala @@ -54,7 +54,7 @@ case class ComputedSqlFile( udtName = None, nullability = nullability, columnDefault = None, - identity = None, + maybeGenerated = None, comment = None, constraints = Nil, jsonDescription = DebugJson(col) diff --git a/typo/src/scala/typo/internal/ComputedTable.scala b/typo/src/scala/typo/internal/ComputedTable.scala index d64379be7..68e14612e 100644 --- a/typo/src/scala/typo/internal/ComputedTable.scala +++ b/typo/src/scala/typo/internal/ComputedTable.scala @@ -130,12 +130,12 @@ case class ComputedTable( val writeableColumnsNotId: Option[NonEmptyList[ComputedColumn]] = colsNotId.flatMap { colsNotId => NonEmptyList.fromList( - colsNotId.toList.filterNot(_.dbCol.identity.exists(_.ALWAYS)) + colsNotId.toList.filterNot(_.dbCol.maybeGenerated.exists(_.ALWAYS)) ) } val writeableColumnsWithId: Option[NonEmptyList[ComputedColumn]] = - NonEmptyList.fromList(cols.toList.filterNot(c => c.dbCol.identity.exists(_.ALWAYS))) + NonEmptyList.fromList(cols.toList.filterNot(c => c.dbCol.maybeGenerated.exists(_.ALWAYS))) val maybeUnsavedRow: Option[ComputedRowUnsaved] = ComputedRowUnsaved(source, cols, default, naming) diff --git a/typo/src/scala/typo/internal/ComputedTestInserts.scala b/typo/src/scala/typo/internal/ComputedTestInserts.scala index 048e734fb..7c9cf9377 100644 --- a/typo/src/scala/typo/internal/ComputedTestInserts.scala +++ b/typo/src/scala/typo/internal/ComputedTestInserts.scala @@ -178,7 +178,7 @@ object ComputedTestInserts { case None => val cols: List[ComputedColumn] = table.maybeUnsavedRow match { - case Some(unsaved) => unsaved.allCols.toList + case Some(unsaved) => unsaved.unsavedCols.toList case None => table.cols.toList } val params: List[sc.Param] = defaultedParametersFor(cols) diff --git a/typo/src/scala/typo/internal/FkAnalysis.scala b/typo/src/scala/typo/internal/FkAnalysis.scala index be061be65..cf59cf9e7 100644 --- a/typo/src/scala/typo/internal/FkAnalysis.scala +++ b/typo/src/scala/typo/internal/FkAnalysis.scala @@ -8,7 +8,7 @@ class FkAnalysis(table: ComputedTable, candidateFks: List[FkAnalysis.CandidateFk lazy val createWithFkIdsRow: Option[FkAnalysis.CreateWithFkIds] = FkAnalysis.CreateWithFkIds.compute(candidateFks, table.cols) lazy val createWithFkIdsUnsavedRow: Option[FkAnalysis.CreateWithFkIds] = - table.maybeUnsavedRow.flatMap(unsaved => FkAnalysis.CreateWithFkIds.compute(candidateFks, unsaved.allCols)) + table.maybeUnsavedRow.flatMap(unsaved => FkAnalysis.CreateWithFkIds.compute(candidateFks, unsaved.unsavedCols)) lazy val createWithFkIdsUnsavedRowOrRow: Option[FkAnalysis.CreateWithFkIds] = createWithFkIdsUnsavedRow.orElse(createWithFkIdsRow) diff --git a/typo/src/scala/typo/internal/codegen/DbLibAnorm.scala b/typo/src/scala/typo/internal/codegen/DbLibAnorm.scala index cdce32176..dbfb7ce68 100644 --- a/typo/src/scala/typo/internal/codegen/DbLibAnorm.scala +++ b/typo/src/scala/typo/internal/codegen/DbLibAnorm.scala @@ -540,7 +540,7 @@ class DbLibAnorm(pkg: sc.QIdent, inlineImplicits: Boolean, default: ComputedDefa val sql = sc.s(code"COPY $relName(${dbNames(writeableColumnsWithId, isRead = false)}) FROM STDIN") code"${textSupport.get.streamingInsert}($sql, batchSize, unsaved)(${textSupport.get.lookupTextFor(rowType)}, c)" case RepoMethod.InsertUnsavedStreaming(relName, unsaved) => - val sql = sc.s(code"COPY $relName(${dbNames(unsaved.allCols, isRead = false)}) FROM STDIN (DEFAULT '${textSupport.get.DefaultValue}')") + val sql = sc.s(code"COPY $relName(${dbNames(unsaved.unsavedCols, isRead = false)}) FROM STDIN (DEFAULT '${textSupport.get.DefaultValue}')") code"${textSupport.get.streamingInsert}($sql, batchSize, unsaved)(${textSupport.get.lookupTextFor(unsaved.tpe)}, c)" case RepoMethod.DeleteBuilder(relName, fieldsType, _) => diff --git a/typo/src/scala/typo/internal/codegen/DbLibDoobie.scala b/typo/src/scala/typo/internal/codegen/DbLibDoobie.scala index 039567118..5bbbcac0c 100644 --- a/typo/src/scala/typo/internal/codegen/DbLibDoobie.scala +++ b/typo/src/scala/typo/internal/codegen/DbLibDoobie.scala @@ -294,7 +294,7 @@ class DbLibDoobie(pkg: sc.QIdent, inlineImplicits: Boolean, default: ComputedDef else code"new $FragmentOps($sql).copyIn[$rowType](unsaved, batchSize)" case RepoMethod.InsertUnsavedStreaming(relName, unsaved) => - val sql = SQL(code"COPY $relName(${dbNames(unsaved.allCols, isRead = false)}) FROM STDIN (DEFAULT '${textSupport.get.DefaultValue}')") + val sql = SQL(code"COPY $relName(${dbNames(unsaved.unsavedCols, isRead = false)}) FROM STDIN (DEFAULT '${textSupport.get.DefaultValue}')") if (fixVerySlowImplicit) code"new $FragmentOps($sql).copyIn(unsaved, batchSize)(using ${textSupport.get.lookupTextFor(unsaved.tpe)})" else code"new $FragmentOps($sql).copyIn[${unsaved.tpe}](unsaved, batchSize)" @@ -811,7 +811,7 @@ class DbLibDoobie(pkg: sc.QIdent, inlineImplicits: Boolean, default: ComputedDef } val write = { - val writeableColumnsWithId = cols.toList.filterNot(_.dbCol.identity.exists(_.ALWAYS)) + val writeableColumnsWithId = cols.toList.filterNot(_.dbCol.maybeGenerated.exists(_.ALWAYS)) val puts = { val all = writeableColumnsWithId.map { c => c.tpe match { diff --git a/typo/src/scala/typo/internal/codegen/DbLibTextSupport.scala b/typo/src/scala/typo/internal/codegen/DbLibTextSupport.scala index c953770cf..49ab9fd7a 100644 --- a/typo/src/scala/typo/internal/codegen/DbLibTextSupport.scala +++ b/typo/src/scala/typo/internal/codegen/DbLibTextSupport.scala @@ -70,7 +70,7 @@ class DbLibTextSupport(pkg: sc.QIdent, inlineImplicits: Boolean, externalText: O val row = sc.Ident("row") val sb = sc.Ident("sb") val textCols = cols.toList - .filterNot(_.dbCol.identity.exists(_.ALWAYS)) + .filterNot(_.dbCol.maybeGenerated.exists(_.ALWAYS)) .map { col => code"${lookupTextFor(col.tpe)}.unsafeEncode($row.${col.name}, $sb)" } val body = code"""|$Text.instance[$tpe]{ ($row, $sb) => diff --git a/typo/src/scala/typo/internal/codegen/DbLibZioJdbc.scala b/typo/src/scala/typo/internal/codegen/DbLibZioJdbc.scala index bf46931c0..ac3c09fd8 100644 --- a/typo/src/scala/typo/internal/codegen/DbLibZioJdbc.scala +++ b/typo/src/scala/typo/internal/codegen/DbLibZioJdbc.scala @@ -468,7 +468,7 @@ class DbLibZioJdbc(pkg: sc.QIdent, inlineImplicits: Boolean, dslEnabled: Boolean val sql = sc.s(code"COPY $relName(${dbNames(writeableColumnsWithId, isRead = false)}) FROM STDIN") code"${textSupport.get.streamingInsert}($sql, batchSize, unsaved)(${textSupport.get.lookupTextFor(rowType)})" case RepoMethod.InsertUnsavedStreaming(relName, unsaved) => - val sql = sc.s(code"COPY $relName(${dbNames(unsaved.allCols, isRead = false)}) FROM STDIN (DEFAULT '${textSupport.get.DefaultValue}')") + val sql = sc.s(code"COPY $relName(${dbNames(unsaved.unsavedCols, isRead = false)}) FROM STDIN (DEFAULT '${textSupport.get.DefaultValue}')") code"${textSupport.get.streamingInsert}($sql, batchSize, unsaved)(${textSupport.get.lookupTextFor(unsaved.tpe)})" case RepoMethod.DeleteBuilder(relName, fieldsType, _) => diff --git a/typo/src/scala/typo/internal/codegen/FilesRelation.scala b/typo/src/scala/typo/internal/codegen/FilesRelation.scala index df3ca1135..e83585d4d 100644 --- a/typo/src/scala/typo/internal/codegen/FilesRelation.scala +++ b/typo/src/scala/typo/internal/codegen/FilesRelation.scala @@ -46,7 +46,7 @@ case class FilesRelation( } val params = partOfIdParams ++ restParams code"""|def toUnsavedRow(${params.map(_.code).mkCode(", ")}): ${unsaved.tpe} = - | ${unsaved.tpe}(${unsaved.allCols.map(col => col.name.code).mkCode(", ")})""".stripMargin + | ${unsaved.tpe}(${unsaved.unsavedCols.map(col => col.name.code).mkCode(", ")})""".stripMargin } ) val formattedMembers = members.flatten match { @@ -60,7 +60,7 @@ case class FilesRelation( val commentPieces = List[Iterable[String]]( col.dbCol.comment, col.dbCol.columnDefault.map(x => s"Default: $x"), - col.dbCol.identity.map(_.asString), + col.dbCol.maybeGenerated.map(_.asString), col.pointsTo.map { case (relationName, columnName) => val shortened = sc.QIdent(dropCommonPrefix(naming.rowName(relationName).idents, names.RowName.value.idents)) s"Points to [[${shortened.dotName}.${naming.field(columnName).value}]]" diff --git a/typo/src/scala/typo/internal/codegen/FilesTable.scala b/typo/src/scala/typo/internal/codegen/FilesTable.scala index cbf7c2ed5..f717cc28c 100644 --- a/typo/src/scala/typo/internal/codegen/FilesTable.scala +++ b/typo/src/scala/typo/internal/codegen/FilesTable.scala @@ -21,7 +21,7 @@ case class FilesTable(table: ComputedTable, fkAnalysis: FkAnalysis, options: Int def mkDefaultParamName(col: ComputedColumn): sc.Ident = sc.Ident(col.name.value).appended("Default") - val params: NonEmptyList[sc.Param] = + val params: List[sc.Param] = unsaved.defaultCols.map { case (col, originalType) => sc.Param(mkDefaultParamName(col), sc.Type.ByName(originalType), None) } ++ unsaved.alwaysGeneratedCols.map(col => sc.Param(mkDefaultParamName(col), sc.Type.ByName(col.tpe), None)) @@ -53,10 +53,10 @@ case class FilesTable(table: ComputedTable, fkAnalysis: FkAnalysis, options: Int | )""".stripMargin } - val formattedCols = unsaved.allCols.map { col => + val formattedCols = unsaved.unsavedCols.map { col => val commentPieces = List[Iterable[String]]( col.dbCol.columnDefault.map(x => s"Default: $x"), - col.dbCol.identity.map(_.asString), + col.dbCol.maybeGenerated.map(_.asString), col.dbCol.comment, col.pointsTo map { case (relationName, columnName) => val shortened = sc.QIdent(relation.dropCommonPrefix(table.naming.rowName(relationName).idents, rowFile.tpe.value.idents)) @@ -84,8 +84,8 @@ case class FilesTable(table: ComputedTable, fkAnalysis: FkAnalysis, options: Int } val instances = - options.jsonLibs.flatMap(_.instances(unsaved.tpe, unsaved.allCols)) ++ - options.dbLib.toList.flatMap(_.rowInstances(unsaved.tpe, unsaved.allCols, rowType = DbLib.RowType.Writable)) + options.jsonLibs.flatMap(_.instances(unsaved.tpe, unsaved.unsavedCols)) ++ + options.dbLib.toList.flatMap(_.rowInstances(unsaved.tpe, unsaved.unsavedCols, rowType = DbLib.RowType.Writable)) sc.File( unsaved.tpe,