oyvindberg / typo

Typed Postgresql integration for Scala. Hopes to avoid typos

Home Page:https://oyvindberg.github.io/typo/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

autogenerated identity fields and typo generated code

boggye opened this issue · comments

Hi,

TLDR; Unless I am missing some settings, Typo is not aware of identity auto generated fields.

I use anorm and I have the following table:

CREATE TABLE school
(
  school_id Integer NOT NULL GENERATED ALWAYS AS IDENTITY
    (INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1),
  school_name Character varying(250) NOT NULL,
  created_dt Timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
  last_modified_dt Timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
)
WITH (
  autovacuum_enabled=true)
;

ALTER TABLE school ADD CONSTRAINT pk_school PRIMARY KEY (school_id)
;

As you can see the values in the school_id field are autogenerated sequentially. The GENERATED ALWAYS indicates the field is always populated by the database. This is reflected in the data returned by the information_schema.columns table as well.
select * from information_schema.columns c where c.table_name = 'school'
image

The insert method code generated by Typo is:

  override def insert(unsaved: SchoolRow)(implicit c: Connection): SchoolRow = {
    SQL"""insert into public.school("school_id", "school_name", "created_dt", "last_modified_dt")
          values (${ParameterValue(unsaved.schoolId, null, SchoolId.toStatement)}::int4, ${ParameterValue(unsaved.schoolName, null, ToStatement.stringToStatement)}, ${ParameterValue(unsaved.createdDt, null, TypoInstant.toStatement)}::timestamptz, ${ParameterValue(unsaved.lastModifiedDt, null, TypoInstant.toStatement)}::timestamptz)
          returning "school_id", "school_name", "created_dt"::text, "last_modified_dt"::text
       """
      .executeInsert(SchoolRow.rowParser(1).single)
   
  }

When I execute:

    val repo = new schooldb.public.school.SchoolRepoImpl
    val schoolRowUnsaved = SchoolRowUnsaved(SchoolId(100), "Hogwarts 2")
//    repo.insert(schoolRowUnsaved)(cn)

    repo.insert(SchoolRow(SchoolId(115), "Hogwarts 3", TypoInstant(Instant.now()), TypoInstant(Instant.now())))

I get this exception:

  Detail: Column "school_id" is an identity column defined as GENERATED ALWAYS.
  Hint: Use OVERRIDING SYSTEM VALUE to override.
	at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2712)
	at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2400)
	at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:367)
	at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:498)
	at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:415)
	at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:190)
	at org.postgresql.jdbc.PgPreparedStatement.executeUpdate(PgPreparedStatement.java:152)
	at anorm.Sql.execInsert$$anonfun$1(Anorm.scala:217)
	at resource.ManagedResourceOperations$$anon$1.$anonfun$acquireFor$1(ManagedResourceOperations.scala:47)
	at resource.AbstractManagedResource.$anonfun$acquireFor$1(AbstractManagedResource.scala:85)
	at scala.util.control.Exception$Catch.$anonfun$either$1(Exception.scala:251)
	at scala.util.control.Exception$Catch.apply(Exception.scala:227)
	at scala.util.control.Exception$Catch.either(Exception.scala:251)
	at resource.AbstractManagedResource.acquireFor(AbstractManagedResource.scala:85)
	at resource.ManagedResourceOperations$$anon$1.acquireFor(ManagedResourceOperations.scala:47)
	at resource.DeferredExtractableManagedResource.acquireFor(AbstractManagedResource.scala:25)
	at resource.ManagedResourceOperations.apply(ManagedResourceOperations.scala:31)
	at resource.ManagedResourceOperations.apply$(ManagedResourceOperations.scala:31)
	at resource.DeferredExtractableManagedResource.apply(AbstractManagedResource.scala:22)
	at resource.ManagedResourceOperations.acquireAndGet(ManagedResourceOperations.scala:29)
	at resource.ManagedResourceOperations.acquireAndGet$(ManagedResourceOperations.scala:29)
	at resource.DeferredExtractableManagedResource.acquireAndGet(AbstractManagedResource.scala:22)
	at anorm.Sql$.asTry$$anonfun$1(Anorm.scala:263)
	at scala.util.Try$.apply(Try.scala:210)
	at anorm.Sql$.asTry(Anorm.scala:263)
	at anorm.Sql.execInsert(Anorm.scala:221)
	at anorm.Sql.executeInsert(Anorm.scala:150)
	at anorm.Sql.executeInsert$(Anorm.scala:57)
	at anorm.SimpleSql.executeInsert(SimpleSql.scala:6)
	at schooldb.public.school.SchoolRepoImpl.insert(SchoolRepoImpl.scala:31)

Imo, Typo should generate code that doesn't insert into autogenerated fields. The insert(SchoolRowUnaved) fails as well.

There is also the variant:

  period_id Integer NOT NULL GENERATED BY DEFAULT AS IDENTITY
    (INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1),

This allows both ways, i.e. server generated sequence or manually provided value.

Thanks

TIL i learned - I had never heard about it before. I gave it a quick shot and implemented it.

If the identity column is always generated it now won't appear in the unsaved row types, while if it's generated by default it'll behave like column with default

Thank you for the prompt fix!