JetBrains / Exposed

Kotlin SQL Framework
http://jetbrains.github.io/Exposed/
Apache License 2.0
8.05k stars 674 forks source link

feat: User defined types support (postgres enum/range, oracle varray) #2092

Closed obabichevjb closed 1 week ago

obabichevjb commented 1 month ago

[Not ready for merge] Here is an attempt to add support of User-defined types to Exposed. It was kind of experiment, and I cannot say that I am completely satisfied with the result, so it would be nice to discuss it and probably change the direction.

Concept

The problem I started with is the improvement of enumeration support. One part of it is native Postgres enumerations.

Postgres enum (and other user-defined types) can be created with sql command (create type <name> ...), has its own type name, and can be used among different tables. So the main conceptual idea of the feature is to make a ddl aware column type that should be responsible for being created inside a database.

I tried 2 options:

I stopped with the first variant because it looks less complex in general, and because ColumnTypes are also kind of just types without connection to certain table columns, and there is no problem to create a ColumnType and then register it in several tables.

From the usage perspective, it looks like:

enum class FooBar { FOO, BAR }

val fooBarEnumType = pgEnumerationType<FooBar>("test_postgres_enumeration_foobar") // PGEnumColumnType(name, T::class)

val tester = object : IntIdTable() {
    val value = enumeration("value", fooBarEnumType)
}

User can create a type and use it in different tables. SchmeUtils will create required types if it's necessary (but not delete after). For the table above the following sql would be created:

CREATE TYPE test_postgres_enumeration_foobar AS ENUM ('FOO', 'BAR')
CREATE TABLE IF NOT EXISTS "testpostgresenumeration$tester$1" (
  id SERIAL PRIMARY KEY, 
  "value" test_postgres_enumeration_foobar NOT NULL
)

in UserDefinedTypesTests class there are example of usage postgres enums, postgres ranges, and oracle varrays.

Concerns

My biggest concern now is that the feature is too vendor-specific and even for the current examples requires specific methods from underlying db drivers.

Even for the current example, I had to add to the core package postgres and oracle packages (libs.postgre, libs.oracle1). It's needed because for postgres types it was necessary to use PGEnum, and for oracle ARRAY (via createOracleArray() method of OracleConnection)

I had to pass through PreparedStatementApi interface the original statement (val statement: PreparedStatement) or connection of it, because creating oracle array requires unwrapped OracleConnection

Possible solution

The first option is to continue anyway. Even if it's not for 80% of users, it can be useful for some of them, and we can add support for more user-defined types.

It's also a variant to avoid creating of the types by ourself and expect that user will create it by himself (with exec() for example), and pass the name of a custom type to the column during describing table. But it will not solve the problem of specific drivers usage (we still need to convert values to PGObject and so on).

Make it inside vendor-specific package if we introduce them in the future. If the vendor-specific packages are created in the future, and these packages will know about driver interfaces, this feature can be implemented there too.

And we can say that such a feature is not the thing needed for the library on the API stabilization way.

Other user-defined types

There are several examples of custom types that are not covered with PR.

One of the most complicated ones that have own columns like tables. For example in Postgres it's composite types:

CREATE TYPE custom AS (
    value VARCHAR(15)
);

or object types in oracle:

CREATE TYPE custom AS OBJECT (
    value VARCHAR2(50)
);

Another examples: