Giter Club home page Giter Club logo

typo's Introduction

typo's People

Contributors

eirikm avatar guizmaii avatar mberndt123 avatar oyvindberg avatar sbrunk avatar wjoel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

typo's Issues

Derived Scala code fails compilation: Missing 'Ordering' instance for derived String Enumeration

First off, let me say how excited I am to test drive this project! We've got hundreds of tables and over 600 calls to ctx.run, and a mixture of Doobie and Quill throughout the codebase. Our compile times are abysmal, and I'm so excited, looking at the very efficient hand-drived typeclass instances for Doobie, for the compile-time benefits this project may bring us!

I have a String Enumeration named Permission which is used part of a primary key in another table:

// Permission.scala
sealed abstract class Permission(val value: String)

object Permission {
  def apply(str: String): Either[String, Permission] =
    ByName.get(str).toRight(s"'$str' does not match any of the following legal values: $Names")
  def force(str: String): Permission =
    apply(str) match {
      case Left(msg) => sys.error(msg)
      case Right(value) => value
    }
  case object invite extends Permission("invite")
  case object admin extends Permission("admin")
  case object owner extends Permission("owner")
  case object support extends Permission("support")
  case object explore extends Permission("explore")
  case object write extends Permission("write")
  val All: List[Permission] = List(invite, admin, owner, support, explore, write)
  val Names: String = All.map(_.value).mkString(", ")
  val ByName: Map[String, Permission] = All.map(x => (x.value, x)).toMap
              
  implicit lazy val arrayGet: Get[Array[Permission]] = pattern.sql.generated.StringArrayMeta.get.map(_.map(force))
  implicit lazy val arrayPut: Put[Array[Permission]] = pattern.sql.generated.StringArrayMeta.put.contramap(_.map(_.value))
  implicit lazy val get: Get[Permission] = Meta.StringMeta.get.temap(Permission.apply)
  implicit lazy val put: Put[Permission] = Meta.StringMeta.put.contramap(_.value)
  implicit lazy val read: Read[Permission] = Read.fromGet(get)
  implicit lazy val write: Write[Permission] = Write.fromPut(put)
}
...
// UserPermissionId.scala
case class UserPermissionId(userId: AppUserId, permission: Permission)
object UserPermissionId {
  implicit lazy val ordering: Ordering[UserPermissionId] = Ordering.by(x => (x.userId, x.permission))
}

The Ordering[UserPermissionId] fails to compile because Permission has no Ordering.

Additionally (I can file a separate issue if desired), I have a name collision on the local write inside the Permission object. write is one of the Permissions, as well as the name of the derived doobie Write instance. I suppose I could get around this with custom naming? Have not tried yet.

integrate with tapir

we could generate Schema if you want to export row types through your API. Not saying you should, but it could be very convenient for some cases

parse (some) constraints

the testInsert feature is awesome for inserting test data, but falls apart a little bit if you use a lot of constraints. many constraints are on the form column > 0, and the random generation could consider this information

Support for pgvector

@oyvindberg I continue to stumble over super interesting new libraries from you. Just did a quick test applying typo to a schema and some queries and it looks quite nice.

In that project, I'm using as Postgres extension called https://github.com/pgvector/pgvector, that defines its own vector type meant for fast similarity computations.

typo obviously doesn't know the type, so it generates a

case class TypoUnknownVector(value: String)

pgvector already has a JDBC integration, It defines a PGvector type that let's you map from and to arrays, i.e.

val insertStmt = conn.prepareStatement("INSERT INTO items (embedding) VALUES (?)")
insertStmt.setObject(1, new PGvector(Array[Float](1, 1, 1)))
insertStmt.executeUpdate()

See https://github.com/pgvector/pgvector-java/#jdbc-scala for details.

Is it possible to support such a custom type within typo and if so, what would be the recommended way to integrate it?

It was nice meeting you in person in Madrid BTW :)

UUID typecast issues in generated SQL queries

With the same user table and context as #30, doing queries with UUID type (i.g. user_id) doesn't typecast correctly in the sql query, for example

UsersRepoImpl.select.where(_.userId === UsersId(UUID.randomUUID())).toList

throws

Exception in thread "main" java.lang.ExceptionInInitializerError
        at Main.main(Main.scala)
Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: uuid = character varying
  Hint: No operator matches the given name and argument types. You might need to add explicit type casts.
  Position: 128

SQL fragment

UsersRepoImpl.select.where(_.userId === UsersId(UUID.randomUUID())).sql

Some(Fragment(List(NamedParameter(param1,ParameterValue(UsersId(aedb7b6c-c474-44e6-b09a-3f251edb7b46)))),select "user_id","name","last_name","email","password","created_at"::text,"verified_on"::text from public.users
 where user_id = {param1}::VARCHAR
))

make codegen faster

it currently analyzes all views sequentially, even if they are not needed. in most cases the views in the postgres schemas are not needed. can support this by introducing some lazyness, and spreading across multiple transactions. can also analyze files in parallel at that point

Instant anorm Column parser exception

With a basic database

CREATE TABLE users(
  user_id UUID NOT NULL,
  created_at TIMESTAMPTZ NOT NULL
)

and override created_at type to java.time.Instant

val options = Options(
      pkg = "org.foo.generated",
      dbLib = Some(DbLibName.Anorm),
      jsonLibs = List(JsonLibName.PlayJson),
      enableDsl = true,
      typeOverride = TypeOverride
        .of {
          case (
                RelationName(Some("public"), "users"),
                ColName("created_at")
              ) =>
            "java.time.Instant"
        }
}

I get an AnormException when I try to do something in the UsersRepoImpl

UsersRepoImpl.insert(
    UsersRow(
      userId = TypoUUID.randomUUID,
      createdAt = Instant.now()
    )
  )

Exception in thread "main" java.lang.ExceptionInInitializerError
        at Main.main(Main.scala)
Caused by: anorm.AnormException: TypeDoesNotMatch(Cannot convert 2023-09-13 17:42:16.253765-07: java.lang.String to Java8 Instant for column ColumnName(.created_at,Some(created_at)))

Libs using scala-cli

//> using scala "3.3.0"

//> using lib "com.olvind.typo::typo:0.3.0"
//> using lib "com.olvind.typo::typo-dsl-anorm:0.3.0"
//> using lib "com.typesafe.play::play-json::2.10.0-RC9"

ship watch mode in typo

for now you have to code a watch mode in your script, which is obviously not a great thing to demand of users

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

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

Error running the generator script

Script:

//> using dep "com.olvind.typo::typo:0.3.0"
//> using dep "com.olvind.typo::typo-dsl-doobie:0.3.0"
//> using scala "3.3.1"

import typo.*

// adapt to your instance and credentials
implicit val c: java.sql.Connection =
  java.sql.DriverManager.getConnection("jdbc:postgresql://localhost:5432/postgres?user=postgres&password=***")

val options = Options(
  // customize package name for generated code
  pkg = "com.prx.db",
  // pick your database library
  dbLib = Some(DbLibName.Doobie),
  jsonLibs = Nil,
  // many more possibilities for customization here
  // ...
)

// current folder, where you run the script from
val location = java.nio.file.Path.of(sys.props("user.dir"))

// destination folder. All files in this dir will be overwritten!
val targetDir = location.resolve("generator")

// where typo will look for sql files
val scriptsFolder = location.resolve("sql")

generateFromDb(options, scriptsPaths = List(scriptsFolder))
  .overwriteFolder(folder = targetDir)

The error I've got is:

typo: fetched tableConstraints from PG in 192ms
typo: fetched keyColumnUsage from PG in 9ms
typo: fetched referentialConstraints from PG in 25ms
typo: fetched pgEnums from PG in 5ms
typo: fetched tables from PG in 49ms
typo: fetched columns from PG in 258ms
Exception in thread "main" org.postgresql.util.PSQLException: ERROR: IN could not convert type character to "char"
  Position: 650
        at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2713)
        at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2401)
        at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:368)
        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.executeQuery(PgPreparedStatement.java:134)
        at anorm.Sql.resultSet$$anonfun$1$$anonfun$1(Anorm.scala:97)
        at resource.DefaultManagedResource.open(AbstractManagedResource.scala:107)
        at resource.AbstractManagedResource.acquireFor(AbstractManagedResource.scala:84)
        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.WithResult.asTry(SqlResult.scala:215)
        at anorm.WithResult.asTry$(SqlResult.scala:47)
        at anorm.SimpleSql.asTry(SimpleSql.scala:6)
        at anorm.WithResult.as(SqlResult.scala:205)
        at anorm.WithResult.as$(SqlResult.scala:47)
        at anorm.SimpleSql.as(SimpleSql.scala:6)
        at typo.generated.custom.view_find_all.ViewFindAllSqlRepoImpl$.apply(ViewFindAllSqlRepoImpl.scala:33)
        at typo.MetaDb$Input$.fromDb$$anonfun$7(MetaDb.scala:57)
        at typo.MetaDb$Input$.timed$1(MetaDb.scala:44)
        at typo.MetaDb$Input$.fromDb(MetaDb.scala:57)
        at typo.MetaDb$.fromDb(MetaDb.scala:66)
        at typo.generateFromDb$.apply(generateFromDb.scala:12)
        at GenDb$_.<init>(GenDb.sc:30)
        at GenDb_sc$.script$lzyINIT1(GenDb.sc:47)
        at GenDb_sc$.script(GenDb.sc:47)
        at GenDb_sc$.main(GenDb.sc:51)
        at GenDb_sc.main(GenDb.sc)

It would have been very nice if it showed more information about the current context.

typo generator wipes out directory where it puts the generated code (targetDir)

Hi,

I have the following generator:

// remember to give the project a github star if you like it <3
//
//> using dep "com.olvind.typo::typo:0.6.0"
//> using scala "3.3.0"

import typo.*

// adapt to your instance and credentials
implicit val c: java.sql.Connection =
  java.sql.DriverManager.getConnection("jdbc:postgresql://localhost:5432/somedb?user=postgres&password=password")

val options = Options(
  // customize package name for generated code
  pkg = "typo.models",
  // pick your database library
  dbLib = Some(DbLibName.Anorm),
  jsonLibs = List(JsonLibName.PlayJson),
  // many more possibilities for customization here
  // ...
)

// current folder, where you run the script from
val location = java.nio.file.Path.of(sys.props("user.dir"))

// destination folder. All files in this dir will be overwritten!
val targetDir = location.resolve("app")

// where Typo will look for sql files
val scriptsFolder = location.resolve("sql")

// you can use this to customize which relations you want to generate code for, see below
val selector = Selector.ExcludePostgresInternal

generateFromDb(options, folder = targetDir, selector = selector, scriptsPaths = List(scriptsFolder))
  .overwriteFolder()

// add changed files to git, so you can keep them under control
//scala.sys.process.Process(List("git", "add", targetDir.toString)).!!

I have a play framework project and I want to place the generated code in a package under app. As is, the generator wiped out the content of the app folder (it included the controllers code and others) which was somewhat unexpected. I thought the generator would remove the typo folder recursively. Luckily, I use IntelliJ and I was able to restore the deleted code. It is a brand new project and I didn't add the code yet to git until now after this accident.

Are there any options that allow me to tell the generator not to wipe out the content of the app folder, i.e. the content of targetDir?

For now, I am going to add the generated code somewhere under: target/scala-3.3.1\typo.

Thanks

derive object types for json columns

we already inspect the verbose query plan, and it's actually possible to construct object types for (some) json columns. this would be very, very cool to actually do. see for instance this simple example:

EXPLAIN (VERBOSE, FORMAT JSON)
SELECT jsonb_build_object(
     p.title,
     p.firstname,
     p.middlename)
FROM person.person p;
[
  {
    "Plan": {
      "Node Type": "Seq Scan",
      "Parallel Aware": false,
      "Async Capable": false,
      "Relation Name": "person",
      "Schema": "person",
      "Alias": "p",
      "Startup Cost": 0.00,
      "Total Cost": 1.32,
      "Plan Rows": 26,
      "Plan Width": 32,
      "Output": ["jsonb_build_object(title, firstname, middlename)"]
    }
  }
]

support skunk

it would be so wonderful to get rid of jdbc altogether.

There is likely a bunch of jdbc assumptions in typo, so this is a long-term goal and likely a significant refactoring

UUID row type SQL typecast error

With the database

CREATE TABLE users(
  user_id UUID NOT NULL,
  created_at TIMESTAMPTZ NOT NULL
)

and override user_id to type UUID

val options = Options(
      pkg = "org.foo.generated",
      dbLib = Some(DbLibName.Anorm),
      jsonLibs = List(JsonLibName.PlayJson),
      enableDsl = true,
      typeOverride = TypeOverride
        .of {
          case (RelationName(Some("public"), "users"), ColName("user_id")) =>
            "java.util.UUID"
          case (RelationName(Some("public"), "users"), ColName("created_at")) =>
            "java.time.Instant"
        }
}

// Generates
case class UsersRow(
    userId: /* user-picked */ UUID,
    createdAt: /* user-picked */ Instant
)

If I try to do a conditional query I get an SQL typecast error

UsersRepoImpl.select
    .where(
      _.userId === UUID.fromString("e471b369-8330-4b5c-89e4-f4cc40682fa3")
    )
    .toList

Exception in thread "main" java.lang.ExceptionInInitializerError
        at Main.main(Main.scala)
Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: uuid = character varying
  Hint: No operator matches the given name and argument types. You might need to add explicit type casts.

SQL Fragment

UsersRepoImpl.select
      .where(
        _.userId === UUID.fromString("e471b369-8330-4b5c-89e4-f4cc40682fa3")
      )
      .sql

Some(Fragment(List(NamedParameter(param1,ParameterValue(e471b369-8330-4b5c-89e4-f4cc40682fa3))),select "user_id","created_at"::text from public.users
 where user_id = {param1}::VARCHAR
))

increase test coverage

ideally should have property tests for all supported column types, among many other things

autogenerated identity fields and typo generated code

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

Document Selector and filtering by selector

I just realized that it's already quite simple to filter out certain schemas/tables etc. via Selector. I.e.

val selector = 
  (Selector.schemas("public") or
  (Selector.schemas("other") and Selector.relationNames("table_a", "table_b", "table_c")))

  generateFromDb(options, selector=selector, scriptsPaths = List(scriptsFolder))
    .overwriteFolder(folder = targetDir)

I think it could be useful to have that in the docs with a few examples. Especially as using this can reduce compile times on dbs with many tables :)

Add support to CITEXT module

Following the next table, I try to generate the DSL files

CREATE TABLE users(
  user_id UUID NOT NULL,
  name TEXT NOT NULL,
  last_name TEXT NULL,
  email CITEXT NOT NULL,
  password TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  verified_on TIMESTAMPTZ NULL,
  CONSTRAINT users_user_id_pk PRIMARY KEY (user_id),
  CONSTRAINT users_email_unique UNIQUE (email)
);

With the options

val options = Options(
      pkg = "org.foo.generated",
      dbLib = Some(DbLibName.Anorm),
      jsonLibs = List(JsonLibName.PlayJson),
      enableDsl = true
    )

When generating the DSL files, the script logs Couldn't translate type from relation public.users column email with type email. Falling back to text generating the UsersRepoImpl, but if I do a select, Anorm throws the error

  println(UsersRepoImpl.selectAll)
Exception in thread "main" java.lang.ExceptionInInitializerError
        at Main.main(Main.scala)
Caused by: anorm.AnormException: TypeDoesNotMatch(Cannot convert email@email.net: org.postgresql.util.PGobject to String for column ColumnName(users.email,Some(email)))

I'm using scala-cli with libs

//> using lib "com.olvind.typo::typo:0.2.0"
//> using lib "com.olvind.typo::typo-dsl-anorm:0.2.0"
//> using lib "com.typesafe.play::play-json::2.10.0-RC9"

selectByUnique only selects input column

I just stumbled upon an error when using selectByUnique, which promises to return the full row in the type signature, but only selects the input column in the generated SQL.

You can see that in the Users example:

override def selectByUnique(email: TypoUnknownCitext): ConnectionIO[Option[UsersRow]] = {
sql"""select "email"::text
from public.users
where "email" = ${fromWrite(email)(Write.fromPut(TypoUnknownCitext.put))}
""".query(UsersRow.read).option
}

selectById in comparison selects the full row as expected:

override def selectById(userId: UsersId): ConnectionIO[Option[UsersRow]] = {
sql"""select "user_id", "name", "last_name", "email"::text, "password", "created_at"::text, "verified_on"::text from public.users where "user_id" = ${fromWrite(userId)(Write.fromPut(UsersId.put))}""".query(UsersRow.read).option
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.