-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support
GENERATED ALWAYS
(part 2) (#134)
* Support `GENERATED ALWAYS` without `IDENTITY` * `Unsaved` rows for tables with generated always columns
- Loading branch information
1 parent
35c1757
commit dc03273
Showing
39 changed files
with
1,464 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
.../adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsFields.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
|
||
} |
36 changes: 36 additions & 0 deletions
36
...d-in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsId.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
34 changes: 34 additions & 0 deletions
34
...in/adventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepo.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
145 changes: 145 additions & 0 deletions
145
...dventureworks/public/table_with_generated_columns/TableWithGeneratedColumnsRepoImpl.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() | ||
} | ||
} |
Oops, something went wrong.