Skip to content

Commit

Permalink
Improve type flow (#89)
Browse files Browse the repository at this point in the history
- Prefer from other tables instead of from self
- Add some recursion between inference of column types within a table, to support a table with a foreign key on its own pk
  • Loading branch information
oyvindberg authored Apr 22, 2024
1 parent b649c96 commit 88cb366
Show file tree
Hide file tree
Showing 27 changed files with 1,187 additions and 15 deletions.
22 changes: 19 additions & 3 deletions init/data/test-tables.sql
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,23 @@ CREATE TABLE users

CREATE TABLE "identity-test"
(
always_generated Integer NOT NULL GENERATED ALWAYS AS IDENTITY (INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1),
default_generated Integer NOT NULL GENERATED BY DEFAULT AS IDENTITY (INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1),
name Character varying(250) NOT NULL primary key
always_generated Integer NOT NULL GENERATED ALWAYS AS IDENTITY (INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1),
default_generated Integer NOT NULL GENERATED BY DEFAULT AS IDENTITY (INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1),
name Character varying(250) NOT NULL primary key
);

create domain short_text as text
constraint short_text_check check (length(VALUE) <= 55);

create table flaff
(
code short_text not null,
another_code varchar(20) not null,
some_number integer not null,
specifier short_text not null,
parentSpecifier short_text,
constraint flaff_pk primary key (code, another_code, some_number, specifier),
constraint flaff_parent_fk foreign key (code, another_code, some_number, parentSpecifier) references flaff
);


Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* File has been automatically generated by `typo`.
*
* IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN.
*/
package adventureworks
package public

import adventureworks.Text
import anorm.Column
import anorm.ParameterMetaData
import anorm.ToStatement
import play.api.libs.json.Reads
import play.api.libs.json.Writes
import typo.dsl.Bijection

/** Domain `public.short_text`
* Constraint: CHECK ((length(VALUE) <= 55))
*/
case class ShortText(value: String)
object ShortText {
implicit lazy val arrayColumn: Column[Array[ShortText]] = Column.columnToArray(column, implicitly)
implicit lazy val arrayToStatement: ToStatement[Array[ShortText]] = ToStatement.arrayToParameter(ParameterMetaData.StringParameterMetaData).contramap(_.map(_.value))
implicit lazy val bijection: Bijection[ShortText, String] = Bijection[ShortText, String](_.value)(ShortText.apply)
implicit lazy val column: Column[ShortText] = Column.columnToString.map(ShortText.apply)
implicit lazy val ordering: Ordering[ShortText] = Ordering.by(_.value)
implicit lazy val parameterMetadata: ParameterMetaData[ShortText] = new ParameterMetaData[ShortText] {
override def sqlType: String = ParameterMetaData.StringParameterMetaData.sqlType
override def jdbcType: Int = ParameterMetaData.StringParameterMetaData.jdbcType
}
implicit lazy val reads: Reads[ShortText] = Reads.StringReads.map(ShortText.apply)
implicit lazy val text: Text[ShortText] = new Text[ShortText] {
override def unsafeEncode(v: ShortText, sb: StringBuilder) = Text.stringInstance.unsafeEncode(v.value, sb)
override def unsafeArrayEncode(v: ShortText, sb: StringBuilder) = Text.stringInstance.unsafeArrayEncode(v.value, sb)
}
implicit lazy val toStatement: ToStatement[ShortText] = ToStatement.stringToStatement.contramap(_.value)
implicit lazy val writes: Writes[ShortText] = Writes.StringWrites.contramap(_.value)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* File has been automatically generated by `typo`.
*
* IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN.
*/
package adventureworks
package public
package flaff

import adventureworks.public.ShortText
import typo.dsl.SqlExpr.FieldLikeNoHkt
import typo.dsl.SqlExpr.IdField
import typo.dsl.SqlExpr.OptField
import typo.dsl.Structure.Relation

trait FlaffFields[Row] {
val code: IdField[ShortText, Row]
val anotherCode: IdField[/* max 20 chars */ String, Row]
val someNumber: IdField[Int, Row]
val specifier: IdField[ShortText, Row]
val parentspecifier: OptField[ShortText, Row]
}

object FlaffFields {
val structure: Relation[FlaffFields, FlaffRow, FlaffRow] =
new Impl(None, identity, (_, x) => x)

private final class Impl[Row](val prefix: Option[String], val extract: Row => FlaffRow, val merge: (Row, FlaffRow) => Row)
extends Relation[FlaffFields, FlaffRow, Row] {

override val fields: FlaffFields[Row] = new FlaffFields[Row] {
override val code = new IdField[ShortText, Row](prefix, "code", None, Some("text"))(x => extract(x).code, (row, value) => merge(row, extract(row).copy(code = value)))
override val anotherCode = new IdField[/* max 20 chars */ String, Row](prefix, "another_code", None, None)(x => extract(x).anotherCode, (row, value) => merge(row, extract(row).copy(anotherCode = value)))
override val someNumber = new IdField[Int, Row](prefix, "some_number", None, Some("int4"))(x => extract(x).someNumber, (row, value) => merge(row, extract(row).copy(someNumber = value)))
override val specifier = new IdField[ShortText, Row](prefix, "specifier", None, Some("text"))(x => extract(x).specifier, (row, value) => merge(row, extract(row).copy(specifier = value)))
override val parentspecifier = new OptField[ShortText, Row](prefix, "parentspecifier", None, Some("text"))(x => extract(x).parentspecifier, (row, value) => merge(row, extract(row).copy(parentspecifier = value)))
}

override val columns: List[FieldLikeNoHkt[?, Row]] =
List[FieldLikeNoHkt[?, Row]](fields.code, fields.anotherCode, fields.someNumber, fields.specifier, fields.parentspecifier)

override def copy[NewRow](prefix: Option[String], extract: NewRow => FlaffRow, merge: (NewRow, FlaffRow) => NewRow): Impl[NewRow] =
new Impl(prefix, extract, merge)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* File has been automatically generated by `typo`.
*
* IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN.
*/
package adventureworks
package public
package flaff

import adventureworks.public.ShortText
import play.api.libs.json.JsObject
import play.api.libs.json.JsResult
import play.api.libs.json.JsValue
import play.api.libs.json.OWrites
import play.api.libs.json.Reads
import play.api.libs.json.Writes
import scala.collection.immutable.ListMap
import scala.util.Try

/** Type for the composite primary key of table `public.flaff` */
case class FlaffId(code: ShortText, anotherCode: /* max 20 chars */ String, someNumber: Int, specifier: ShortText)
object FlaffId {
implicit lazy val ordering: Ordering[FlaffId] = Ordering.by(x => (x.code, x.anotherCode, x.someNumber, x.specifier))
implicit lazy val reads: Reads[FlaffId] = Reads[FlaffId](json => JsResult.fromTry(
Try(
FlaffId(
code = json.\("code").as(ShortText.reads),
anotherCode = json.\("another_code").as(Reads.StringReads),
someNumber = json.\("some_number").as(Reads.IntReads),
specifier = json.\("specifier").as(ShortText.reads)
)
)
),
)
implicit lazy val writes: OWrites[FlaffId] = OWrites[FlaffId](o =>
new JsObject(ListMap[String, JsValue](
"code" -> ShortText.writes.writes(o.code),
"another_code" -> Writes.StringWrites.writes(o.anotherCode),
"some_number" -> Writes.IntWrites.writes(o.someNumber),
"specifier" -> ShortText.writes.writes(o.specifier)
))
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* File has been automatically generated by `typo`.
*
* IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN.
*/
package adventureworks
package public
package flaff

import java.sql.Connection
import typo.dsl.DeleteBuilder
import typo.dsl.SelectBuilder
import typo.dsl.UpdateBuilder

trait FlaffRepo {
def delete(compositeId: FlaffId)(implicit c: Connection): Boolean
def delete: DeleteBuilder[FlaffFields, FlaffRow]
def insert(unsaved: FlaffRow)(implicit c: Connection): FlaffRow
def insertStreaming(unsaved: Iterator[FlaffRow], batchSize: Int)(implicit c: Connection): Long
def select: SelectBuilder[FlaffFields, FlaffRow]
def selectAll(implicit c: Connection): List[FlaffRow]
def selectById(compositeId: FlaffId)(implicit c: Connection): Option[FlaffRow]
def update(row: FlaffRow)(implicit c: Connection): Boolean
def update: UpdateBuilder[FlaffFields, FlaffRow]
def upsert(unsaved: FlaffRow)(implicit c: Connection): FlaffRow
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* File has been automatically generated by `typo`.
*
* IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN.
*/
package adventureworks
package public
package flaff

import adventureworks.public.ShortText
import adventureworks.streamingInsert
import anorm.ParameterValue
import anorm.SqlStringInterpolation
import anorm.ToStatement
import java.sql.Connection
import typo.dsl.DeleteBuilder
import typo.dsl.SelectBuilder
import typo.dsl.SelectBuilderSql
import typo.dsl.UpdateBuilder

class FlaffRepoImpl extends FlaffRepo {
override def delete(compositeId: FlaffId)(implicit c: Connection): Boolean = {
SQL"""delete from public.flaff where "code" = ${ParameterValue(compositeId.code, null, ShortText.toStatement)} AND "another_code" = ${ParameterValue(compositeId.anotherCode, null, ToStatement.stringToStatement)} AND "some_number" = ${ParameterValue(compositeId.someNumber, null, ToStatement.intToStatement)} AND "specifier" = ${ParameterValue(compositeId.specifier, null, ShortText.toStatement)}""".executeUpdate() > 0
}
override def delete: DeleteBuilder[FlaffFields, FlaffRow] = {
DeleteBuilder("public.flaff", FlaffFields.structure)
}
override def insert(unsaved: FlaffRow)(implicit c: Connection): FlaffRow = {
SQL"""insert into public.flaff("code", "another_code", "some_number", "specifier", "parentspecifier")
values (${ParameterValue(unsaved.code, null, ShortText.toStatement)}::text, ${ParameterValue(unsaved.anotherCode, null, ToStatement.stringToStatement)}, ${ParameterValue(unsaved.someNumber, null, ToStatement.intToStatement)}::int4, ${ParameterValue(unsaved.specifier, null, ShortText.toStatement)}::text, ${ParameterValue(unsaved.parentspecifier, null, ToStatement.optionToStatement(ShortText.toStatement, ShortText.parameterMetadata))}::text)
returning "code", "another_code", "some_number", "specifier", "parentspecifier"
"""
.executeInsert(FlaffRow.rowParser(1).single)

}
override def insertStreaming(unsaved: Iterator[FlaffRow], batchSize: Int)(implicit c: Connection): Long = {
streamingInsert(s"""COPY public.flaff("code", "another_code", "some_number", "specifier", "parentspecifier") FROM STDIN""", batchSize, unsaved)(FlaffRow.text, c)
}
override def select: SelectBuilder[FlaffFields, FlaffRow] = {
SelectBuilderSql("public.flaff", FlaffFields.structure, FlaffRow.rowParser)
}
override def selectAll(implicit c: Connection): List[FlaffRow] = {
SQL"""select "code", "another_code", "some_number", "specifier", "parentspecifier"
from public.flaff
""".as(FlaffRow.rowParser(1).*)
}
override def selectById(compositeId: FlaffId)(implicit c: Connection): Option[FlaffRow] = {
SQL"""select "code", "another_code", "some_number", "specifier", "parentspecifier"
from public.flaff
where "code" = ${ParameterValue(compositeId.code, null, ShortText.toStatement)} AND "another_code" = ${ParameterValue(compositeId.anotherCode, null, ToStatement.stringToStatement)} AND "some_number" = ${ParameterValue(compositeId.someNumber, null, ToStatement.intToStatement)} AND "specifier" = ${ParameterValue(compositeId.specifier, null, ShortText.toStatement)}
""".as(FlaffRow.rowParser(1).singleOpt)
}
override def update(row: FlaffRow)(implicit c: Connection): Boolean = {
val compositeId = row.compositeId
SQL"""update public.flaff
set "parentspecifier" = ${ParameterValue(row.parentspecifier, null, ToStatement.optionToStatement(ShortText.toStatement, ShortText.parameterMetadata))}::text
where "code" = ${ParameterValue(compositeId.code, null, ShortText.toStatement)} AND "another_code" = ${ParameterValue(compositeId.anotherCode, null, ToStatement.stringToStatement)} AND "some_number" = ${ParameterValue(compositeId.someNumber, null, ToStatement.intToStatement)} AND "specifier" = ${ParameterValue(compositeId.specifier, null, ShortText.toStatement)}
""".executeUpdate() > 0
}
override def update: UpdateBuilder[FlaffFields, FlaffRow] = {
UpdateBuilder("public.flaff", FlaffFields.structure, FlaffRow.rowParser)
}
override def upsert(unsaved: FlaffRow)(implicit c: Connection): FlaffRow = {
SQL"""insert into public.flaff("code", "another_code", "some_number", "specifier", "parentspecifier")
values (
${ParameterValue(unsaved.code, null, ShortText.toStatement)}::text,
${ParameterValue(unsaved.anotherCode, null, ToStatement.stringToStatement)},
${ParameterValue(unsaved.someNumber, null, ToStatement.intToStatement)}::int4,
${ParameterValue(unsaved.specifier, null, ShortText.toStatement)}::text,
${ParameterValue(unsaved.parentspecifier, null, ToStatement.optionToStatement(ShortText.toStatement, ShortText.parameterMetadata))}::text
)
on conflict ("code", "another_code", "some_number", "specifier")
do update set
"parentspecifier" = EXCLUDED."parentspecifier"
returning "code", "another_code", "some_number", "specifier", "parentspecifier"
"""
.executeInsert(FlaffRow.rowParser(1).single)

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* File has been automatically generated by `typo`.
*
* IF YOU CHANGE THIS FILE YOUR CHANGES WILL BE OVERWRITTEN.
*/
package adventureworks
package public
package flaff

import java.sql.Connection
import scala.annotation.nowarn
import typo.dsl.DeleteBuilder
import typo.dsl.DeleteBuilder.DeleteBuilderMock
import typo.dsl.DeleteParams
import typo.dsl.SelectBuilder
import typo.dsl.SelectBuilderMock
import typo.dsl.SelectParams
import typo.dsl.UpdateBuilder
import typo.dsl.UpdateBuilder.UpdateBuilderMock
import typo.dsl.UpdateParams

class FlaffRepoMock(map: scala.collection.mutable.Map[FlaffId, FlaffRow] = scala.collection.mutable.Map.empty) extends FlaffRepo {
override def delete(compositeId: FlaffId)(implicit c: Connection): Boolean = {
map.remove(compositeId).isDefined
}
override def delete: DeleteBuilder[FlaffFields, FlaffRow] = {
DeleteBuilderMock(DeleteParams.empty, FlaffFields.structure.fields, map)
}
override def insert(unsaved: FlaffRow)(implicit c: Connection): FlaffRow = {
val _ = if (map.contains(unsaved.compositeId))
sys.error(s"id ${unsaved.compositeId} already exists")
else
map.put(unsaved.compositeId, unsaved)

unsaved
}
override def insertStreaming(unsaved: Iterator[FlaffRow], batchSize: Int)(implicit c: Connection): Long = {
unsaved.foreach { row =>
map += (row.compositeId -> row)
}
unsaved.size.toLong
}
override def select: SelectBuilder[FlaffFields, FlaffRow] = {
SelectBuilderMock(FlaffFields.structure, () => map.values.toList, SelectParams.empty)
}
override def selectAll(implicit c: Connection): List[FlaffRow] = {
map.values.toList
}
override def selectById(compositeId: FlaffId)(implicit c: Connection): Option[FlaffRow] = {
map.get(compositeId)
}
override def update(row: FlaffRow)(implicit c: Connection): Boolean = {
map.get(row.compositeId) match {
case Some(`row`) => false
case Some(_) =>
map.put(row.compositeId, row): @nowarn
true
case None => false
}
}
override def update: UpdateBuilder[FlaffFields, FlaffRow] = {
UpdateBuilderMock(UpdateParams.empty, FlaffFields.structure.fields, map)
}
override def upsert(unsaved: FlaffRow)(implicit c: Connection): FlaffRow = {
map.put(unsaved.compositeId, unsaved): @nowarn
unsaved
}
}
Loading

0 comments on commit 88cb366

Please sign in to comment.