oyvindberg / typo

Typed Postgresql integration for Scala. Hopes to avoid typos
https://oyvindberg.github.io/typo/
MIT License
101 stars 11 forks source link

Is there a way to turn off the the creation of specific types for primary key fields? #77

Closed boggye closed 10 months ago

boggye commented 10 months ago

Hi,

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)
;

Typo generates the following case class & companion object:

case class SchoolId(value: Int) extends AnyVal
object SchoolId {
  implicit lazy val arrayColumn: Column[Array[SchoolId]] = Column.columnToArray(column, implicitly)
  implicit lazy val arrayToStatement: ToStatement[Array[SchoolId]] = typo.IntArrayToStatement.contramap(_.map(_.value))
  implicit lazy val column: Column[SchoolId] = Column.columnToInt.map(SchoolId.apply)
  implicit lazy val ordering: Ordering[SchoolId] = Ordering.by(_.value)
  implicit lazy val parameterMetadata: ParameterMetaData[SchoolId] = new ParameterMetaData[SchoolId] {
    override def sqlType: String = ParameterMetaData.IntParameterMetaData.sqlType
    override def jdbcType: Int = ParameterMetaData.IntParameterMetaData.jdbcType
  }
  implicit lazy val reads: Reads[SchoolId] = Reads.IntReads.map(SchoolId.apply)
  implicit lazy val text: Text[SchoolId] = new Text[SchoolId] {
    override def unsafeEncode(v: SchoolId, sb: StringBuilder) = Text.intInstance.unsafeEncode(v.value, sb)
    override def unsafeArrayEncode(v: SchoolId, sb: StringBuilder) = Text.intInstance.unsafeArrayEncode(v.value, sb)
  }
  implicit lazy val toStatement: ToStatement[SchoolId] = ToStatement.intToStatement.contramap(_.value)
  implicit lazy val writes: Writes[SchoolId] = Writes.IntWrites.contramap(_.value)
}

Is it possible to turn off the creation of this class and use only a straight Int?

If you can point me to an article that describes this approach in detail, that would be great.

Thanks

oyvindberg commented 10 months ago

You can use typeOverride, see https://oyvindberg.github.io/typo/docs/customization/customize-types/

val options = Options(
  // ...
  typeOverride = TypeOverride.relation {
    case ("public.identity-test", "name") => "String"
  }
)

What's the problem with the primary key types by the way? I consider it such a cool feature to get those for free

oyvindberg commented 10 months ago

I mean I could expose a better interface for this, similar to Options#readonlyRepo which uses a Selector to make a decision.

boggye commented 10 months ago

What's the problem with the primary key types by the way? I consider it such a cool feature to get those for free

I feel it adds a lot of unnecessary noise to the code. My app is a simple CRUD app, I am the only developer, and I don't think I am going to trip over myself if I use Int fields instead of value types fields. I know that there are other people that exult the benefits of using specific types and I am not negating those benefits in other contexts. I was wondering if there is an option to turn them off and use plain types.

boggye commented 10 months ago

as you per your suggestion I used the following setting and I was able to change the scala types of the these fields to Int:

import typo.db.RelationName

val rewriteMore = TypeOverride.of {
  case (RelationName(Some(schema), tableName), colName) if colName.value.endsWith("_id") => "Int"
}

Another question: is it possible to re-map a scala type by sql type name not by sql field name as it is done above? For instance, I want to map Date sql fields to LocalDate in scala and timestampz fields to ZonedDateTime regardless the name of the field. Put it another way, inside the partial function above is it possible to have access to the sql type of the column?

oyvindberg commented 10 months ago

I added the possibility of not generating id types for the relations you specify in #83 .

Another question: is it possible to re-map a scala type by sql type name not by sql field name as it is done above? For instance, I want to map Date sql fields to LocalDate in scala and timestampz fields to ZonedDateTime regardless the name of the field. Put it another way, inside the partial function above is it possible to have access to the sql type of the column?

No, not now. I also want to get rid of the TypoLocalDate and TypoInstant types, but it needs more work. it's a bit harder than it looks. I think the best way forward for now if you don't want these types leaking into your business layer is to duplicate the row types into proper domain model types and maintain explicit mapping. This is likely the best choice regardless of these types.

Also note that ZonedDateTime is the wrong data type - you lose the zone when you roundtrip it.

boggye commented 10 months ago

Thank you for the prompt response.

Also note that ZonedDateTime is the wrong data type - you lose the zone when you roundtrip it.

Do you have an example that shows what's wrong? From the little testing i've done the type seems ok. When you say "roundtrip" do you mean: get the value from the db and then save it to the database, and the process of saving removes the TZ from the value?

Thanks again!

oyvindberg commented 10 months ago

get the value from the db and then save it to the database, and the process of saving removes the TZ from the value?

Create it in code, persist it to pg and read it back. timestamptz stores the timestamp as UTC, so the original time zone is lost. Either postgres or the postgres driver may rewrite it back to its current time zone for you. if that's the case it'll look alright and you'll need to create the original with a time zone different than your current one to see it

boggye commented 10 months ago

ok, thank you for the explantions!