Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ buildscript {
hsqlVersion = '2.3.4'
postgresVersion = '9.4.1208'
mysqlVersion = '5.1.38'
mssqlVersion = '6.3.6.jre8-preview'
jerseyVersion = '2.23.1'
dropwizardVersion = '1.0.0'
jacksonVersion = '2.7.0'
Expand Down Expand Up @@ -143,6 +144,7 @@ project(':core') {
"org.postgresql:postgresql:$postgresVersion",
"mysql:mysql-connector-java:$mysqlVersion",
"org.xerial:sqlite-jdbc:3.8.11.2",
"com.microsoft.sqlserver:mssql-jdbc:$mssqlVersion",
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,12 @@ class DefaultSession(override val connection: Connection,
return DefaultTransaction(this)
}

private fun <R> withPreparedStatement(sql: String, parameters: List<Map<String, Any?>> = listOf(), options: StatementOptions,
f: (ExecutingStatement, PreparedStatement) -> Pair<ExecutingStatement, R>): R {
private fun <R> withPreparedStatement(
sql: String,
parameters: List<Map<String, Any?>> = listOf(),
options: StatementOptions,
f: (ExecutingStatement, PreparedStatement) -> Pair<ExecutingStatement, R>
): R {
var statement = ExecutingStatement(this, hashMapOf(), sql, parameters, options)
try {
statement = interceptor.construct(statement)
Expand Down Expand Up @@ -263,10 +267,13 @@ class DefaultSession(override val connection: Connection,
}
}

private fun createStatementCacheKey(options: StatementOptions, sql: String, statement: ExecutingStatement): StatementCacheKey {
return StatementCacheKey(sql, statement.inClauseSizes, if (options.applyNameToQuery) options.name else null,
options.limit != null, options.offset != null)
}
private fun createStatementCacheKey(options: StatementOptions, sql: String, statement: ExecutingStatement) = StatementCacheKey(
sql = sql,
collections = statement.inClauseSizes,
name = if (options.applyNameToQuery) options.name else null,
limit = options.limit != null,
offset = options.offset != null
)

private fun prepareStatement(sql: String, options: StatementOptions): PreparedStatement {
val statement = if (options.useGeneratedKeys || options.generatedKeyColumns.isNotEmpty()) {
Expand Down Expand Up @@ -358,5 +365,10 @@ data class ExecutingStatement(
/**
* StatementCacheKey contains the sql and any options that modify the generated prepared statement
*/
data class StatementCacheKey(val sql: String, val collections: Map<String, Int>, val name: String?,
val limit: Boolean, val offset: Boolean)
data class StatementCacheKey(
val sql: String,
val collections: Map<String, Int>,
val name: String?,
val limit: Boolean,
val offset: Boolean
)
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ interface Dialect {
val supportsFetchingGeneratedKeysByName: Boolean

fun arrayBasedIn(name: String): String

fun arrayBasedIn(paramName: String, values: Collection<Any>): String = throw TODO("Not Implemented")
fun allocateIds(count: Int, sequence: String, columnName: String): String

fun applyLimitAndOffset(limit: Int?, offset: Int?, sql: String): String {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.github.andrewoma.kwery.core.dialect

import java.sql.Date
import java.sql.Time
import java.sql.Timestamp


open class SqlServerDialect : Dialect {
override fun bind(value: Any, limit: Int) = when (value) {
is String -> escapeSingleQuotedString(value.truncate(limit))
is Timestamp -> timestampFormat.get().format(value)
is Date -> "'$value'"
is Time -> "'$value'"
else -> value.toString()
}

override fun arrayBasedIn(name: String) = TODO("Fail")

override val supportsArrayBasedIn = false

override val supportsAllocateIds = false

override fun allocateIds(count: Int, sequence: String, columnName: String) = throw UnsupportedOperationException()

override val supportsFetchingGeneratedKeysByName = false

override fun applyLimitAndOffset(limit: Int?, offset: Int?, sql: String) = when {
limit != null && offset != null -> "$sql OFFSET $offset ROWS FETCH NEXT $limit ROWS ONLY"
offset != null -> "$sql OFFSET $offset ROWS"
limit != null -> "$sql OFFSET 0 ROWS FETCH NEXT $limit ROWS ONLY"
else -> sql
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@

package com.github.andrewoma.kwery.core

import com.github.andrewoma.kwery.core.dialect.Dialect
import com.github.andrewoma.kwery.core.dialect.MysqlDialect
import com.github.andrewoma.kwery.core.dialect.PostgresDialect
import com.github.andrewoma.kwery.core.dialect.SqliteDialect
import com.github.andrewoma.kwery.core.dialect.*
import com.zaxxer.hikari.pool.ProxyConnection
import org.junit.Test
import org.postgresql.largeobject.LargeObjectManager
Expand All @@ -52,8 +49,16 @@ abstract class AbstractDialectTest(dataSource: DataSource, dialect: Dialect) : A
session.update("delete from test")
}

data class Value(val time: Time, val date: Date, val timestamp: Timestamp, val binary: String,
val varchar: String, val blob: String, val clob: String, val ints: List<Int>)
data class Value(
val time: Time,
val date: Date,
val timestamp: Timestamp,
val binary: String,
val varchar: String,
val blob: String,
val clob: String,
val ints: List<Int>
)

@Test fun `Array based select should work inlined`() {
if (!dialect.supportsArrayBasedIn) return
Expand All @@ -74,16 +79,20 @@ abstract class AbstractDialectTest(dataSource: DataSource, dialect: Dialect) : A
}

@Test fun `Bindings to blobs and clobs via streams`() {
if (session.dialect is PostgresDialect || session.dialect is SqliteDialect) return
if (
session.dialect is PostgresDialect ||
session.dialect is SqliteDialect ||
session.dialect is SqlServerDialect
) return

val now = System.currentTimeMillis()
val value = Value(Time(now), Date(now), Timestamp(now), "binary",
"var'char", "blob", "clob", listOf(1, 2, 3))
"var'char", "blob", "clob", listOf(1, 2, 3))

val params = createParams(value) + mapOf(
"id" to "streams",
"blob_col" to ByteArrayInputStream(value.blob.toByteArray(Charsets.UTF_8)),
"clob_col" to StringReader(value.clob)
"id" to "streams",
"blob_col" to ByteArrayInputStream(value.blob.toByteArray(Charsets.UTF_8)),
"clob_col" to StringReader(value.clob)
)

assertEquals(1, session.update(insertSql, params))
Expand All @@ -105,7 +114,7 @@ abstract class AbstractDialectTest(dataSource: DataSource, dialect: Dialect) : A
@Test fun `Bindings to literals should return the same values when fetched`() {
val now = System.currentTimeMillis()
val value = Value(Time(now), Date(now), Timestamp(now), "binary",
"var'char", "blob", "clob", listOf(1, 2, 3))
"var'char", "blob", "clob", listOf(1, 2, 3))

session.update(insertSql, createParams(value) + mapOf("id" to "params"))

Expand All @@ -120,14 +129,14 @@ abstract class AbstractDialectTest(dataSource: DataSource, dialect: Dialect) : A
}

private fun createParams(value: Value) = mapOf(
"time_col" to value.time,
"date_col" to value.date,
"timestamp_col" to value.timestamp,
"binary_col" to value.binary.toByteArray(Charsets.UTF_8),
"varchar_col" to value.varchar,
"blob_col" to toBlob(value.blob),
"clob_col" to toClob(value.clob),
"array_col" to if (dialect is MysqlDialect) "" else session.connection.createArrayOf("int", value.ints.toTypedArray())
"time_col" to value.time,
"date_col" to value.date,
"timestamp_col" to value.timestamp,
"binary_col" to value.binary.toByteArray(Charsets.UTF_8),
"varchar_col" to value.varchar,
"blob_col" to toBlob(value.blob),
"clob_col" to toClob(value.clob),
"array_col" to if (dialect is MysqlDialect || dialect is SqlServerDialect) "" else session.connection.createArrayOf("int", value.ints.toTypedArray())
)

@Test fun `Allocate ids should contain a unique sequence of ids`() {
Expand Down Expand Up @@ -197,13 +206,13 @@ abstract class AbstractDialectTest(dataSource: DataSource, dialect: Dialect) : A
private fun findById(id: String): Value {
return session.select("select * from dialect_test where id = '$id'") { row ->
Value(row.time("time_col"),
row.date("date_col"),
row.timestamp("timestamp_col"),
String(row.bytes("binary_col"), Charsets.UTF_8),
row.string("varchar_col"),
fromBlob(row, "blob_col"),
fromClob(row, "clob_col"),
if (dialect is MysqlDialect || dialect is SqliteDialect) listOf() else row.array<Int>("array_col"))
row.date("date_col"),
row.timestamp("timestamp_col"),
String(row.bytes("binary_col"), Charsets.UTF_8),
row.string("varchar_col"),
fromBlob(row, "blob_col"),
fromClob(row, "clob_col"),
if (dialect is MysqlDialect || dialect is SqliteDialect) listOf() else row.array<Int>("array_col"))
}.single()
}

Expand Down Expand Up @@ -258,4 +267,4 @@ abstract class AbstractDialectTest(dataSource: DataSource, dialect: Dialect) : A
assertEquals(expected.timestamp.toString(), actual.timestamp.toString()) // travis doesn't support millis
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ val mysqlDataSource = HikariDataSource().apply {
password = "kwery"
}

val sqlserverDataSource = HikariDataSource().apply {
jdbcUrl = "jdbc:sqlserver://localhost:1433;databaseName=model"
username = "sa"
password = "yourStrong(!)Password"
}


val sqliteDataSource = HikariDataSource().apply {
jdbcUrl = "jdbc:sqlite::memory:"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.github.andrewoma.kwery.core

import com.github.andrewoma.kwery.core.dialect.SqlServerDialect

class SqlServerDialectTest : AbstractDialectTest(sqlserverDataSource, SqlServerDialect()) {
//language=tsql
override val sql = """
drop table if exists dialect_test;

create table dialect_test (
id varchar(255),
time_col time,
date_col date,
timestamp_col timestamp,
-- timestamp_col timestamp(3), Waiting on travis to support this
binary_col binary(50),
varchar_col varchar(1000),
blob_col binary(50),
clob_col text,
array_col text -- Not supported
);

drop table if exists test;

create table test (
id varchar(255),
value varchar(255)
)
""".trimIndent()

}
27 changes: 27 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
version: '3.6'

services:
mysql:
image: mysql
environment:
MYSQL_DATABASE: kwery
MYSQL_ROOT_PASSWORD: kwery
ports:
- 3306:3306

postgres:
image: postgres
environment:
POSTGRES_DB: kwery
POSTGRES_USER: postgres
POSTGRES_PASSWORD: kwery
ports:
- 5432:5432

sqlserver:
image: mcr.microsoft.com/mssql/server
environment:
ACCEPT_EULA: Y
SA_PASSWORD: yourStrong(!)Password
ports:
- 1433:1433
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,16 @@ abstract class AbstractDao<T : Any, ID : Any>(
id(table.rowMapper(table.idColumns, nf)(row))
}
}

private fun Session.createArrayOf(key: String, sqlType: String?, array: kotlin.Array<Any>): Array {
return connection.createArrayOf(sqlType, array)
}

private fun Session.createWheresOf(key: String, sqlType: String?, array: kotlin.Array<Any>): Map<String, Any?> {
return array.mapIndexed { index, value ->
"$key$index" to value
}.toMap()
}
}

enum class IdStrategy {
Expand Down