diff --git a/pom.xml b/pom.xml
index 32c8c29..7fe94b1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -8,7 +8,7 @@
0.2.5-SNAPSHOT
- 1.3.0
+ 1.3.21
0.9.17
diff --git a/squash-jdbc/src/org/jetbrains/squash/drivers/JDBCDataConversion.kt b/squash-jdbc/src/org/jetbrains/squash/drivers/JDBCDataConversion.kt
index 1d91b80..a4ab1e1 100644
--- a/squash-jdbc/src/org/jetbrains/squash/drivers/JDBCDataConversion.kt
+++ b/squash-jdbc/src/org/jetbrains/squash/drivers/JDBCDataConversion.kt
@@ -7,7 +7,7 @@ import java.time.*
import kotlin.reflect.*
open class JDBCDataConversion {
- open fun convertValueToDatabase(value: Any?): Any? {
+ open fun convertValueToDatabase(value: Any?, connection: Connection): Any? {
if (value == null)
return null
return when (value) {
diff --git a/squash-jdbc/src/org/jetbrains/squash/drivers/JDBCTransaction.kt b/squash-jdbc/src/org/jetbrains/squash/drivers/JDBCTransaction.kt
index 9ba9a05..8dc198f 100644
--- a/squash-jdbc/src/org/jetbrains/squash/drivers/JDBCTransaction.kt
+++ b/squash-jdbc/src/org/jetbrains/squash/drivers/JDBCTransaction.kt
@@ -92,7 +92,7 @@ open class JDBCTransaction(override val connection: JDBCConnection) : Transactio
prepareStatement(statementSQL.sql, arrayOf(connection.dialect.idSQL(returnColumn.name)))
statementSQL.arguments.forEach { arg ->
- preparedStatement.setObject(arg.index + 1, connection.conversion.convertValueToDatabase(arg.value))
+ preparedStatement.setObject(arg.index + 1, connection.conversion.convertValueToDatabase(arg.value, preparedStatement.connection))
}
return preparedStatement
}
diff --git a/squash-mysql/src/org/jetbrains/squash/dialects/mysql/MySqlDataConversion.kt b/squash-mysql/src/org/jetbrains/squash/dialects/mysql/MySqlDataConversion.kt
index cfe4184..f0431ac 100644
--- a/squash-mysql/src/org/jetbrains/squash/dialects/mysql/MySqlDataConversion.kt
+++ b/squash-mysql/src/org/jetbrains/squash/dialects/mysql/MySqlDataConversion.kt
@@ -3,19 +3,20 @@ package org.jetbrains.squash.dialects.mysql
import org.jetbrains.squash.connection.*
import org.jetbrains.squash.drivers.*
import java.nio.*
+import java.sql.Connection
import java.util.*
import kotlin.reflect.*
class MySqlDataConversion : JDBCDataConversion() {
- override fun convertValueToDatabase(value: Any?): Any? {
+ override fun convertValueToDatabase(value: Any?, connection: Connection): Any? {
if (value is UUID) {
val bb = ByteBuffer.wrap(ByteArray(16))
bb.putLong(value.mostSignificantBits)
bb.putLong(value.leastSignificantBits)
return bb.array()
}
- return super.convertValueToDatabase(value)
+ return super.convertValueToDatabase(value, connection)
}
override fun convertValueFromDatabase(value: Any?, type: KClass<*>): Any? {
diff --git a/squash-postgres/src/org/jetbrains/squash/definition/ColumnBuilder.kt b/squash-postgres/src/org/jetbrains/squash/definition/ColumnBuilder.kt
new file mode 100644
index 0000000..e2c2590
--- /dev/null
+++ b/squash-postgres/src/org/jetbrains/squash/definition/ColumnBuilder.kt
@@ -0,0 +1,32 @@
+package org.jetbrains.squash.definition
+
+import java.time.OffsetDateTime
+
+/**
+ * Creates a [OffsetDateTime] column
+ */
+fun TableDefinition.offsetDatetime(name: String): ColumnDefinition {
+ return createColumn(name, OffsetDateTimeColumnType)
+}
+
+/**
+ * Creates a [Int[]] column
+ */
+fun TableDefinition.intArray(name: String): ColumnDefinition> {
+ return createColumn(name, IntArrayColumnType)
+}
+
+/**
+ * Creates a [String[]] column
+ */
+fun TableDefinition.textArray(name: String): ColumnDefinition> {
+ return createColumn(name, TextArrayColumnType)
+}
+
+/**
+ * Creates a [Json] column
+ */
+fun TableDefinition.jsonb(name: String): ColumnDefinition {
+ return createColumn(name, JsonbColumnType)
+}
+
diff --git a/squash-postgres/src/org/jetbrains/squash/definition/ColumnType.kt b/squash-postgres/src/org/jetbrains/squash/definition/ColumnType.kt
new file mode 100644
index 0000000..7390f4b
--- /dev/null
+++ b/squash-postgres/src/org/jetbrains/squash/definition/ColumnType.kt
@@ -0,0 +1,8 @@
+package org.jetbrains.squash.definition
+
+import java.time.OffsetDateTime
+
+object OffsetDateTimeColumnType : ColumnType(OffsetDateTime::class)
+object IntArrayColumnType : ColumnType(Array::class)
+object TextArrayColumnType : ColumnType(Array::class)
+object JsonbColumnType : ColumnType(Json::class)
\ No newline at end of file
diff --git a/squash-postgres/src/org/jetbrains/squash/definition/Json.kt b/squash-postgres/src/org/jetbrains/squash/definition/Json.kt
new file mode 100644
index 0000000..6fb046a
--- /dev/null
+++ b/squash-postgres/src/org/jetbrains/squash/definition/Json.kt
@@ -0,0 +1,3 @@
+package org.jetbrains.squash.definition
+
+data class Json(val json: String?)
\ No newline at end of file
diff --git a/squash-postgres/src/org/jetbrains/squash/dialects/postgres/PgDataConversion.kt b/squash-postgres/src/org/jetbrains/squash/dialects/postgres/PgDataConversion.kt
index f0b6c79..4da592d 100644
--- a/squash-postgres/src/org/jetbrains/squash/dialects/postgres/PgDataConversion.kt
+++ b/squash-postgres/src/org/jetbrains/squash/dialects/postgres/PgDataConversion.kt
@@ -1,5 +1,63 @@
package org.jetbrains.squash.dialects.postgres
-import org.jetbrains.squash.drivers.*
+import org.jetbrains.squash.definition.Json
+import org.jetbrains.squash.drivers.JDBCDataConversion
+import org.jetbrains.squash.drivers.JDBCResponseColumn
+import org.postgresql.util.PGobject
+import java.sql.Connection
+import java.sql.ResultSet
+import java.time.OffsetDateTime
+import kotlin.reflect.KClass
-class PgDataConversion : JDBCDataConversion()
\ No newline at end of file
+class PgDataConversion : JDBCDataConversion() {
+ override fun convertValueToDatabase(value: Any?, connection: Connection): Any? {
+ return when {
+ value is OffsetDateTime -> { value }
+ Array::class.isInstance(value) -> {
+ val typedArray = value as Array
+ connection.createArrayOf("int4", typedArray)
+ }
+ Array::class.isInstance(value) -> {
+ val typedArray = value as Array
+ connection.createArrayOf("text", typedArray)
+ }
+ Json::class.isInstance(value) -> {
+ val json = value as Json
+ PGobject().apply {
+ this.type = "json"
+ this.value = json.json
+ }
+ }
+ else -> super.convertValueToDatabase(value, connection)
+ }
+ }
+
+ override fun convertValueFromDatabase(value: Any?, type: KClass<*>): Any? {
+ return when (value) {
+ type == java.time.OffsetDateTime::class && value is java.time.OffsetDateTime -> { value }
+ type == kotlin.Array::class && value is java.sql.Array -> {
+ val array = value as java.sql.Array
+ array.array as Array
+ }
+ type == kotlin.Array::class && value is java.sql.Array -> {
+ val array = value as java.sql.Array
+ array.array as Array
+ }
+ type == Json::class && value is Json -> {
+ val json = value as Json
+ json.json
+ }
+ else -> super.convertValueFromDatabase(value, type)
+ }
+ }
+
+ override fun fetch(resultSet: ResultSet, dbColumnIndex: Int, column: JDBCResponseColumn): Any? {
+ return when (column.databaseType) {
+ "_int4" -> resultSet.getArray(dbColumnIndex)?.array as Array?
+ "_text" -> resultSet.getArray(dbColumnIndex)?.array as Array?
+ "timestamptz" -> resultSet.getObject(dbColumnIndex, OffsetDateTime::class.java)
+ "json" -> Json(resultSet.getString(dbColumnIndex))
+ else -> super.fetch(resultSet, dbColumnIndex, column)
+ }
+ }
+}
\ No newline at end of file
diff --git a/squash-postgres/src/org/jetbrains/squash/dialects/postgres/PgDialect.kt b/squash-postgres/src/org/jetbrains/squash/dialects/postgres/PgDialect.kt
index 31faf5c..6f29246 100644
--- a/squash-postgres/src/org/jetbrains/squash/dialects/postgres/PgDialect.kt
+++ b/squash-postgres/src/org/jetbrains/squash/dialects/postgres/PgDialect.kt
@@ -1,7 +1,13 @@
package org.jetbrains.squash.dialects.postgres
import org.jetbrains.squash.definition.*
-import org.jetbrains.squash.dialect.*
+import org.jetbrains.squash.dialect.BaseDefinitionSQLDialect
+import org.jetbrains.squash.dialect.BaseSQLDialect
+import org.jetbrains.squash.dialect.DefinitionSQLDialect
+import org.jetbrains.squash.dialect.SQLStatementBuilder
+import org.jetbrains.squash.expressions.Expression
+import org.jetbrains.squash.expressions.ArrayInExpression
+import org.jetbrains.squash.expressions.ArrayOverlapExpression
object PgDialect : BaseSQLDialect("Postgres") {
override val definition: DefinitionSQLDialect = object : BaseDefinitionSQLDialect(this) {
@@ -32,8 +38,43 @@ object PgDialect : BaseSQLDialect("Postgres") {
is BlobColumnType -> builder.append("BYTEA")
is BinaryColumnType -> builder.append("BYTEA")
is DateTimeColumnType -> builder.append("TIMESTAMP")
+ is OffsetDateTimeColumnType -> builder.append("TIMESTAMP WITH TIME ZONE")
+ is IntArrayColumnType -> builder.append("INT[]")
+ is TextArrayColumnType -> builder.append("TEXT[]")
+ is JsonbColumnType -> builder.append("JSONB")
else -> super.columnTypeSQL(builder, type)
}
}
}
+
+ override fun appendExpression(builder: SQLStatementBuilder, expression: Expression): Unit = with(builder) {
+ when (expression) {
+ is ArrayInExpression<*> -> {
+ appendExpression(this, expression.value)
+ append(" @> ARRAY[")
+ expression.values.forEachIndexed { index, value ->
+ if (index > 0)
+ append(", ")
+ appendLiteralSQL(this, value)
+ }
+ append("]")
+ }
+ is ArrayOverlapExpression<*> -> {
+ appendExpression(this, expression.value)
+ append(" && ARRAY[")
+ var anyValueIsString = false
+ expression.values.forEachIndexed { index, value ->
+ if (index > 0)
+ append(", ")
+ appendLiteralSQL(this, value)
+ if (value is String)
+ anyValueIsString = true
+ }
+ append("]")
+ if (anyValueIsString)
+ append("::text[]")
+ }
+ else -> super.appendExpression(builder, expression)
+ }
+ }
}
\ No newline at end of file
diff --git a/squash-postgres/src/org/jetbrains/squash/expressions/PgExpression.kt b/squash-postgres/src/org/jetbrains/squash/expressions/PgExpression.kt
new file mode 100644
index 0000000..e18be7b
--- /dev/null
+++ b/squash-postgres/src/org/jetbrains/squash/expressions/PgExpression.kt
@@ -0,0 +1,4 @@
+package org.jetbrains.squash.expressions
+
+class ArrayInExpression(val value: Expression, val values: Collection) : Expression
+class ArrayOverlapExpression(val value: Expression, val values: Collection) : Expression
\ No newline at end of file
diff --git a/squash-postgres/src/org/jetbrains/squash/expressions/PgExpressionBuilder.kt b/squash-postgres/src/org/jetbrains/squash/expressions/PgExpressionBuilder.kt
new file mode 100644
index 0000000..11630ee
--- /dev/null
+++ b/squash-postgres/src/org/jetbrains/squash/expressions/PgExpressionBuilder.kt
@@ -0,0 +1,4 @@
+package org.jetbrains.squash.expressions
+
+infix fun Expression.contains(values: Collection): ArrayInExpression = ArrayInExpression(this, values)
+infix fun Expression.containsAny(values: Collection): ArrayOverlapExpression = ArrayOverlapExpression(this, values)
diff --git a/squash-postgres/test/org/jetbrains/squash/dialects/postgres/tests/PgDialectColumnTypes.kt b/squash-postgres/test/org/jetbrains/squash/dialects/postgres/tests/PgDialectColumnTypes.kt
new file mode 100644
index 0000000..fd494ae
--- /dev/null
+++ b/squash-postgres/test/org/jetbrains/squash/dialects/postgres/tests/PgDialectColumnTypes.kt
@@ -0,0 +1,14 @@
+package org.jetbrains.squash.dialects.postgres.tests
+
+import org.jetbrains.squash.definition.*
+
+object PgDialectColumnTypes : TableDefinition() {
+ val id = integer("id").autoIncrement().primaryKey()
+ val offsetdatetime = offsetDatetime("offsetdatetime")
+ val notnullIntarray = intArray("intarray")
+ val nullableIntarray = intArray("nullable_intarray").nullable()
+ val notnullTextarray = textArray("textarray")
+ val nullableTextarray = textArray("nullable_textarray").nullable()
+ val notnullJsonb = jsonb("notnull_jsonb")
+ val nullableJsonb = jsonb("nullable_jsonb").nullable()
+}
diff --git a/squash-postgres/test/org/jetbrains/squash/dialects/postgres/tests/PgDialectColumnTypesTests.kt b/squash-postgres/test/org/jetbrains/squash/dialects/postgres/tests/PgDialectColumnTypesTests.kt
new file mode 100644
index 0000000..2c3cc6c
--- /dev/null
+++ b/squash-postgres/test/org/jetbrains/squash/dialects/postgres/tests/PgDialectColumnTypesTests.kt
@@ -0,0 +1,89 @@
+package org.jetbrains.squash.dialects.postgres.tests
+
+import org.jetbrains.squash.connection.Transaction
+import org.jetbrains.squash.definition.IntColumnType
+import org.jetbrains.squash.definition.Json
+import org.jetbrains.squash.expressions.contains
+import org.jetbrains.squash.expressions.containsAny
+import org.jetbrains.squash.query.from
+import org.jetbrains.squash.query.where
+import org.jetbrains.squash.results.ResultRow
+import org.jetbrains.squash.results.get
+import org.jetbrains.squash.statements.insertInto
+import org.jetbrains.squash.statements.values
+import org.jetbrains.squash.tests.DatabaseTests
+import java.time.OffsetDateTime
+import java.time.ZoneOffset
+import java.util.*
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+
+class PgDialectColumnTypesTests: DatabaseTests by PgDatabaseTests() {
+ private val dialectColumnsTableSQL: String get() = "CREATE TABLE IF NOT EXISTS PgDialectColumnTypes (" +
+ "id ${getIdColumnType(IntColumnType)}, " +
+ "offsetdatetime TIMESTAMP WITH TIME ZONE NOT NULL, " +
+ "intarray INT[] NOT NULL, " +
+ "nullable_intarray INT[] NULL, " +
+ "textarray TEXT[] NOT NULL, " +
+ "nullable_textarray TEXT[] NULL, " +
+ "notnull_jsonb JSONB NOT NULL, " +
+ "nullable_jsonb JSONB NULL, " +
+ "CONSTRAINT PK_PgDialectColumnTypes PRIMARY KEY (id))"
+
+ @Test fun sql() {
+ withTransaction {
+ connection.dialect.definition.tableSQL(PgDialectColumnTypes).assertSQL { dialectColumnsTableSQL }
+ }
+ }
+
+ @Test fun insert() {
+ withTables(PgDialectColumnTypes) {
+ insertData()
+ }
+ }
+
+ private val offsetDate = OffsetDateTime.of(1976, 11, 24, 12, 1, 1, 0, ZoneOffset.ofHours(-6))
+ private val arrayOfInt = arrayOf(1, 2, 3, 4)
+ private val arrayOfString = arrayOf("see", "spot", "run")
+ private val jsonb = Json("{}")
+
+ @Test fun query() {
+ withTables(PgDialectColumnTypes) {
+ insertData()
+
+ fun checkRow(row: ResultRow) {
+ assertEquals(OffsetDateTime::class.java, row[PgDialectColumnTypes.offsetdatetime].javaClass)
+ assertEquals(offsetDate.toEpochSecond(), row[PgDialectColumnTypes.offsetdatetime].toEpochSecond())
+
+ assertEquals(Array::class.java, row[PgDialectColumnTypes.notnullIntarray].javaClass)
+ assertTrue { Arrays.equals(arrayOfInt, row[PgDialectColumnTypes.notnullIntarray]) }
+
+ assertEquals(Array::class.java, row[PgDialectColumnTypes.notnullTextarray].javaClass)
+ assertTrue { Arrays.equals(arrayOfString, row[PgDialectColumnTypes.notnullTextarray]) }
+ }
+
+ val containsRow = from(PgDialectColumnTypes).where { PgDialectColumnTypes.notnullIntarray contains arrayOfInt.take(2) }.execute().single()
+ checkRow(containsRow)
+
+ val containsAnyRow = from(PgDialectColumnTypes).where { PgDialectColumnTypes.notnullTextarray containsAny listOf(arrayOfString.first(), "other")}.execute().single()
+ checkRow(containsAnyRow)
+
+ val noRow = from(PgDialectColumnTypes).where { PgDialectColumnTypes.notnullIntarray contains listOf(9) }.execute().singleOrNull()
+ assertNull(noRow)
+ }
+ }
+
+ private fun Transaction.insertData() {
+ insertInto(PgDialectColumnTypes).values {
+ it[offsetdatetime] = offsetDate
+ it[notnullIntarray] = arrayOfInt
+ it[nullableIntarray] = null
+ it[notnullTextarray] = arrayOfString
+ it[nullableTextarray] = arrayOfString
+ it[notnullJsonb] = jsonb
+ it[nullableJsonb] = null
+ }.execute()
+ }
+}
\ No newline at end of file
diff --git a/squash.iml b/squash.iml
index 3af2333..a923e9c 100644
--- a/squash.iml
+++ b/squash.iml
@@ -1,6 +1,29 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -16,8 +39,19 @@
+
+
+
+
+
+
+
+
+
+
+
-
+
\ No newline at end of file