|
| 1 | +package com.metaobjects.integration.kotlin.tables |
| 2 | + |
| 3 | +import org.jetbrains.exposed.sql.Column |
| 4 | +import org.jetbrains.exposed.sql.ColumnType |
| 5 | +import org.jetbrains.exposed.sql.Table |
| 6 | +import org.jetbrains.exposed.sql.statements.api.PreparedStatementApi |
| 7 | +import org.jetbrains.exposed.sql.statements.jdbc.JdbcPreparedStatementImpl |
| 8 | +import org.jetbrains.exposed.sql.vendors.currentDialect |
| 9 | +import java.net.URI |
| 10 | +import java.sql.ResultSet |
| 11 | +import java.sql.Types |
| 12 | + |
| 13 | +/** |
| 14 | + * Hand-written reference Exposed column types for `field.uri` / `field.inet` (ADR-0036/0037 |
| 15 | + * Wave 3) — the test-harness analogue of the per-package `MetaInetUriColumnType.kt` the |
| 16 | + * [com.metaobjects.generator.kotlin.KotlinExposedTableGenerator] emits. |
| 17 | + * |
| 18 | + * `field.uri` mirrors the generated `MetaUriColumnType` 1:1 (a `Column<java.net.URI>` over plain |
| 19 | + * `text`; persists/round-trips the verbatim URI string). |
| 20 | + * |
| 21 | + * `field.inet` DEVIATES from the generated `MetaInetColumnType` on the READ side ON PURPOSE. |
| 22 | + * The generator's column is a `Column<java.net.InetAddress>` whose `valueFromDB` resolves the |
| 23 | + * JDBC value to an `InetAddress` — and `InetAddress.getHostAddress()` returns the UNcompressed |
| 24 | + * IPv6 form (`2001:db8:0:0:0:8a2e:370:7334`). The persistence-conformance `op:roundtrip` |
| 25 | + * expectation is the canonical COMPRESSED form (`2001:db8::8a2e:370:7334`) — which is exactly |
| 26 | + * what Postgres' native `inet` wire text already is. So the reference column here is a |
| 27 | + * `Column<String>` over the native `inet` type that READS the driver's native inet string |
| 28 | + * directly via `ResultSet.getString` (compressed, bare host — NO `::text` cast, so no `/32`|`/128` |
| 29 | + * mask is appended) and WRITES the bare address string via the driver's `inet` coercion. This is |
| 30 | + * the "read the inet column as the driver's string wire value" rule from the corpus |
| 31 | + * (`roundtrip-all-types.yaml`): it keeps the read-back byte-identical to the cross-port wire form |
| 32 | + * without re-deriving the host literal from an `InetAddress`. |
| 33 | + */ |
| 34 | + |
| 35 | +/** Custom column type for `field.uri`: a `Column<java.net.URI>` over plain `text`. */ |
| 36 | +internal class MetaUriColumnType : ColumnType<URI>() { |
| 37 | + override fun sqlType(): String = currentDialect.dataTypeProvider.textType() |
| 38 | + override fun valueFromDB(value: Any): URI = when (value) { |
| 39 | + is URI -> value |
| 40 | + else -> URI.create(value.toString()) |
| 41 | + } |
| 42 | + override fun notNullValueToDB(value: URI): Any = value.toString() |
| 43 | + override fun nonNullValueToString(value: URI): String = "'$value'" |
| 44 | +} |
| 45 | + |
| 46 | +/** Column builder for `field.uri`: a `Column<java.net.URI>` over a `text` column. */ |
| 47 | +internal fun Table.uriColumn(name: String): Column<URI> = |
| 48 | + registerColumn(name, MetaUriColumnType()) |
| 49 | + |
| 50 | +/** |
| 51 | + * Custom column type for `field.inet`: a `Column<String>` over the Postgres-native `inet` type |
| 52 | + * that round-trips the bare host-literal string. The READ asks the driver for the native inet |
| 53 | + * wire text (`ResultSet.getString`), which Postgres returns WITHOUT a CIDR mask (host address, |
| 54 | + * no `/32`|`/128`) and in CANONICAL COMPRESSED form for IPv6 — so the read-back matches the |
| 55 | + * corpus expectation without going through `InetAddress.getHostAddress()` (which would emit the |
| 56 | + * uncompressed IPv6). The WRITE binds the address string and lets the JDBC driver coerce it to |
| 57 | + * the native `inet` column. |
| 58 | + */ |
| 59 | +internal class MetaInetStringColumnType : ColumnType<String>() { |
| 60 | + override fun sqlType(): String = "inet" |
| 61 | + override fun valueFromDB(value: Any): String = value.toString() |
| 62 | + override fun notNullValueToDB(value: String): Any = value |
| 63 | + override fun nonNullValueToString(value: String): String = "'$value'" |
| 64 | + /** Read the native inet wire string (compressed IPv6, bare host — no `::text` cast). */ |
| 65 | + override fun readObject(rs: ResultSet, index: Int): Any? = rs.getString(index) |
| 66 | + override fun setParameter(stmt: PreparedStatementApi, index: Int, value: Any?) { |
| 67 | + // Bind the bare address string via setObject(.., Types.OTHER) so the Postgres JDBC driver |
| 68 | + // coerces it to the native `inet` column. Exposed's plain `stmt[index] = value` binds the |
| 69 | + // String as VARCHAR (default stringtype), which Postgres rejects ("column is of type inet |
| 70 | + // but expression is of type character varying"). Reach the raw java.sql.PreparedStatement |
| 71 | + // and bind with Types.OTHER — the exact pattern OMDB's InetCodec uses on the JVM. |
| 72 | + val raw = (stmt as JdbcPreparedStatementImpl).statement |
| 73 | + if (value == null) raw.setNull(index, Types.OTHER) |
| 74 | + else raw.setObject(index, value.toString(), Types.OTHER) |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +/** Column builder for `field.inet`: a `Column<String>` (native inet wire string) over an `inet` column. */ |
| 79 | +internal fun Table.inetColumn(name: String): Column<String> = |
| 80 | + registerColumn(name, MetaInetStringColumnType()) |
0 commit comments