Skip to content

Commit b8098f8

Browse files
committed
updating hsqldb example to match Kotlin/dataframe#1710
1 parent 45c0e16 commit b8098f8

3 files changed

Lines changed: 104 additions & 33 deletions

File tree

src/main/kotlin/customdb/Example_1_Simple_Example.kt

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,43 @@
11
package org.jetbrains.kotlinx.dataframe.examples.jdbc.customdb
22

3+
import kotlinx.datetime.LocalDate
34
import org.jetbrains.kotlinx.dataframe.annotations.DataSchema
4-
import org.jetbrains.kotlinx.dataframe.api.*
5+
import org.jetbrains.kotlinx.dataframe.api.cast
6+
import org.jetbrains.kotlinx.dataframe.api.filter
7+
import org.jetbrains.kotlinx.dataframe.api.print
8+
import org.jetbrains.kotlinx.dataframe.api.renameToCamelCase
59
import org.jetbrains.kotlinx.dataframe.io.readDataFrame
610
import java.sql.DriverManager
7-
import java.util.*
811

912
@DataSchema
1013
interface Orders {
1114
val id: Int
1215
val item: String
1316
val price: Double
14-
val orderDate: java.util.Date
17+
val orderDate: LocalDate
1518
}
1619

17-
//https://www.tutorialspoint.com/hsqldb/hsqldb_quick_guide.htm
18-
// CMD> java -classpath lib/hsqldb.jar org.hsqldb.server.Server --database.0 file:hsqldb/demodb --dbname.0 testdb
20+
21+
/**
22+
* Following https://www.tutorialspoint.com/hsqldb/hsqldb_quick_guide.htm:
23+
*
24+
* Run `CMD> java -classpath lib/hsqldb.jar org.hsqldb.server.Server --database.0 file:hsqldb/demodb --dbname.0 testdb`
25+
*
26+
* Use `url = `[URL] to connect to the running server.
27+
*
28+
* Alternatively, use `url = `[URL_IN_MEMORY] to connect to an in-memory database for testing purposes.
29+
*/
1930
fun main() {
20-
DriverManager.getConnection(URL, USER_NAME, PASSWORD).use { con ->
31+
val url = URL
32+
DriverManager.getConnection(url, USER_NAME, PASSWORD).use { con ->
2133
createAndPopulateTable(con)
2234

2335
val df = con
2436
.readDataFrame("SELECT * FROM orders", dbType = HSQLDB)
2537
.renameToCamelCase()
2638
.cast<Orders>(verify = true)
2739

28-
// df.filter { it.price > 800 }.print()
40+
df.filter { it.price > 800 }.print()
2941

3042
removeTable(con)
3143
}

src/main/kotlin/customdb/HSQLDB.kt

Lines changed: 83 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,103 @@
11
package org.jetbrains.kotlinx.dataframe.examples.jdbc.customdb
22

3-
import org.jetbrains.kotlinx.dataframe.io.TableColumnMetadata
4-
import org.jetbrains.kotlinx.dataframe.io.TableMetadata
3+
import kotlinx.datetime.LocalDate
4+
import kotlinx.datetime.toKotlinLocalDate
5+
import org.jetbrains.kotlinx.dataframe.DataColumn
6+
import org.jetbrains.kotlinx.dataframe.examples.jdbc.customdb.HSQLDB.getPreprocessedValueType
7+
import org.jetbrains.kotlinx.dataframe.examples.jdbc.customdb.HSQLDB.preprocessValue
58
import org.jetbrains.kotlinx.dataframe.io.db.DbType
6-
import org.jetbrains.kotlinx.dataframe.schema.ColumnSchema
9+
import org.jetbrains.kotlinx.dataframe.io.db.TableColumnMetadata
10+
import org.jetbrains.kotlinx.dataframe.io.db.TableMetadata
711
import java.sql.ResultSet
8-
import java.util.Locale
12+
import java.sql.Types
913
import kotlin.reflect.KType
14+
import kotlin.reflect.full.isSubtypeOf
15+
import kotlin.reflect.full.withNullability
16+
import kotlin.reflect.typeOf
17+
import java.sql.Date as SqlDate
1018

1119
/**
1220
* Represents the HSQLDB database type.
1321
*
14-
* This class provides methods to convert data from a ResultSet to the appropriate type for HSQLDB,
15-
* and to generate the corresponding column schema.
22+
* This object provides all functions to read data from a HSQLDB [ResultSet],
23+
* preprocess the values, and build [columns][DataColumn].
1624
*/
17-
public object HSQLDB : DbType("hsqldb") {
18-
override val driverClassName: String
19-
get() = "org.hsqldb.jdbcDriver"
25+
object HSQLDB : DbType("hsqldb") {
2026

21-
override fun convertSqlTypeToColumnSchemaValue(tableColumnMetadata: TableColumnMetadata): ColumnSchema? {
22-
return null
27+
/** The JDBC driver class name. */
28+
override val driverClassName: String = "org.hsqldb.jdbcDriver"
29+
30+
/**
31+
* This function should return the correct type of the value returned by [ResultSet.getObject] from JDBC
32+
* for the column with the given [tableColumnMetadata].
33+
* [DbType] has a good default type-map, but your database type might deviate.
34+
*
35+
* Supplying these types helps you and DataFrame to correctly interpret and handle data from the database.
36+
*/
37+
override fun getExpectedJdbcType(tableColumnMetadata: TableColumnMetadata): KType =
38+
when (tableColumnMetadata.jdbcType) {
39+
// For example, here we say that we expect .getObject() to return a Java SQL Date
40+
// when the given JDBC SQL type is DATE
41+
Types.DATE -> typeOf<SqlDate>().withNullability(tableColumnMetadata.isNullable)
42+
43+
// TODO this list is likely incomplete for HSQLDB
44+
45+
// Else, we follow the default behavior
46+
else -> super.getExpectedJdbcType(tableColumnMetadata)
47+
}
48+
49+
/**
50+
* If you want to preprocess certain values before creating a [DataColumn], you can override this function.
51+
* [DbType] already has a few types of values being preprocessed, but you can customize this behavior.
52+
*
53+
* This function just specifies the type-behavior, [preprocessValue] actually does the preprocessing.
54+
*/
55+
override fun getPreprocessedValueType(
56+
tableColumnMetadata: TableColumnMetadata,
57+
expectedJdbcType: KType
58+
): KType =
59+
when {
60+
// Let's say we want to convert java.sql.Date to kotlinx.datetime.LocalDate (taking nullability into account)
61+
expectedJdbcType.isSubtypeOf(typeOf<SqlDate?>()) ->
62+
typeOf<LocalDate>().withNullability(tableColumnMetadata.isNullable)
63+
64+
// Else, we follow the default behavior
65+
else ->
66+
super.getPreprocessedValueType(tableColumnMetadata, expectedJdbcType)
67+
}
68+
69+
/**
70+
* This function actually preprocesses the values returned by [ResultSet.getObject], following the
71+
* [getPreprocessedValueType] type-behavior.
72+
*/
73+
override fun <J, D> preprocessValue(
74+
value: J,
75+
tableColumnMetadata: TableColumnMetadata,
76+
expectedJdbcType: KType,
77+
expectedPreprocessedValueType: KType
78+
): D = when {
79+
// Here we actually perform the conversion from java.sql.Date to kotlinx.datetime.LocalDate
80+
expectedJdbcType.isSubtypeOf(typeOf<SqlDate?>()) ->
81+
(value as SqlDate?)?.toLocalDate()?.toKotlinLocalDate() as D
82+
83+
// Else, we follow the default behavior
84+
else ->
85+
super.preprocessValue(value, tableColumnMetadata, expectedJdbcType, expectedPreprocessedValueType)
2386
}
2487

2588
override fun isSystemTable(tableMetadata: TableMetadata): Boolean {
26-
val locale = Locale.getDefault()
27-
fun String?.containsWithLowercase(substr: String) = this?.lowercase(locale)?.contains(substr) == true
28-
val schemaName = tableMetadata.schemaName
29-
val name = tableMetadata.name
30-
return schemaName.containsWithLowercase("information_schema") ||
31-
schemaName.containsWithLowercase("system") ||
32-
name.containsWithLowercase("system_")
89+
val schemaName = tableMetadata.schemaName.orEmpty().lowercase()
90+
val name = tableMetadata.name.lowercase()
91+
92+
return "information_schema" in schemaName ||
93+
"system" in schemaName ||
94+
"system_" in name
3395
}
3496

3597
override fun buildTableMetadata(tables: ResultSet): TableMetadata =
3698
TableMetadata(
37-
tables.getString("TABLE_NAME"),
38-
tables.getString("TABLE_SCHEM"),
39-
tables.getString("TABLE_CAT"),
99+
name = tables.getString("TABLE_NAME"),
100+
schemaName = tables.getString("TABLE_SCHEM"),
101+
catalogue = tables.getString("TABLE_CAT"),
40102
)
41-
42-
override fun convertSqlTypeToKType(tableColumnMetadata: TableColumnMetadata): KType? {
43-
return null
44-
}
45103
}

src/main/kotlin/customdb/hsqldbUtils.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.jetbrains.kotlinx.dataframe.examples.jdbc.customdb
33
import java.sql.Connection
44

55
const val URL = "jdbc:hsqldb:hsql://localhost/testdb"
6+
const val URL_IN_MEMORY = "jdbc:hsqldb:hsql:mem:testdb"
67
const val USER_NAME = "SA"
78
const val PASSWORD = ""
89

@@ -13,7 +14,7 @@ fun removeTable(con: Connection): Int {
1314
}
1415

1516
fun createAndPopulateTable(con: Connection) {
16-
var stmt = con.createStatement()
17+
val stmt = con.createStatement()
1718
stmt.executeUpdate(
1819
"""CREATE TABLE IF NOT EXISTS orders (
1920
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,

0 commit comments

Comments
 (0)