Database Dialect Support

Built-in Database Dialects

Kronos chooses the SQL dialect and table-structure statements from KronosDataSourceWrapper.dbType.

val wrapper = KronosJdbcWrapper(dataSource)

Kronos.dataSource = { wrapper }

println(wrapper.dbType)
println(wrapper.sqlDialect)

The complete built-in dialects map to these DBType values:

DatabaseDBType
MySQLDBType.Mysql
PostgreSQLDBType.Postgres
SQLiteDBType.SQLite
H2DBType.H2
SQL ServerDBType.Mssql
OracleDBType.Oracle
DM8 (Dameng)DBType.DM8

For database connection and JDBC driver examples, see 📚Connect to DB. For table DDL workflows, see 📚Schema Sync and 📚Create Table As Select.

Behavior matrix

Use this page as the dialect behavior matrix. Each row links to the section or page that shows the Kotlin entry point and the SQL shape for the built-in dialects.

BehaviorWhere to check
PaginationSee Pagination for page(pageIndex, pageSize) SQL.
Upsert and onConflict()See Upsert and 📚Upsert for match-field upsert, uniqueness-conflict upsert, and dynamic conflict assignments.
Last insert idSee Last Insert Id and 📚Last Insert Id for identity key retrieval.
H2 and DM8See the dedicated sections below for H2 MERGE upserts, DM8 identity columns, and JDBC-generated identity keys.
DDL and schema syncSee Table Operations, 📚Table Operations, and 📚Schema Sync.
Column type renderingSee 📚Column Types for KColumnType examples and dialect output.
Identifier quotingSee Identifier Quoting for table and column quoting rules.
Functions and windowsSee Built-in Functions, 📚Functions, and 📚Sorting, Pagination, and Aggregation.

Identifier Quoting

The same query uses the identifier quoting rules of the active dialect.

kotlin

val users = User()
    .select { [it.id, it.name] }
    .toList()

Mysql

SELECT `id`, `name`
FROM `user`

PostgreSQL

SELECT "id", "name"
FROM "user"

SQLite

SELECT "id", "name"
FROM "user"

SQLServer

SELECT [id], [name]
FROM [user]

Oracle

SELECT "ID", "NAME"
FROM "USER"

Pagination

page(pageIndex, pageSize) starts from page 1. Add withTotal() after page(...) when a named PageResult with total metadata is required. Kronos renders the matching pagination syntax for the active database.

kotlin

val page = User()
    .select { [it.id, it.name] }
    .orderBy { it.id.asc() }
    .page(2, 20)
    .withTotal()
    .toList()

val users = page.records

Mysql

SELECT `id`, `name`
FROM `user`
ORDER BY `id` ASC
LIMIT 20 OFFSET 20

PostgreSQL

SELECT "id", "name"
FROM "user"
ORDER BY "id" ASC
LIMIT 20 OFFSET 20

SQLite

SELECT "id", "name"
FROM "user"
ORDER BY "id" ASC
LIMIT 20 OFFSET 20

SQLServer

SELECT [id], [name]
FROM [user]
ORDER BY [id] ASC
OFFSET 20 ROWS FETCH NEXT 20 ROWS ONLY

Oracle

SELECT "ID", "NAME"
FROM "USER"
ORDER BY "ID" ASC
OFFSET 20 ROWS FETCH NEXT 20 ROWS ONLY

Upsert

onConflict() uses database uniqueness conflicts and renders the matching SQL for the active dialect. When on { ... } is omitted, Kronos infers the conflict target from KPojo uniqueness metadata: primary-key values and declared unique indexes.

kotlin

User(id = 1, name = "Ada")
    .upsert { it.name }
    .onConflict()
    .execute()

Mysql

INSERT INTO `user` (`id`, `name`) VALUES (:id, :name)
ON DUPLICATE KEY UPDATE `name` = :name

PostgreSQL

INSERT INTO "user" ("id", "name") VALUES (:id, :name)
ON CONFLICT ("id") DO UPDATE SET "name" = :name

SQLite

INSERT INTO "user" ("id", "name") VALUES (:id, :name)
ON CONFLICT ("id") DO UPDATE SET "name" = :name

SQLServer

MERGE INTO [user] AS [t1]
USING (SELECT :id AS [id], :name AS [name]) AS [t2]
ON ([t1].[id] = [t2].[id])
WHEN MATCHED THEN UPDATE SET [t1].[name] = :name
WHEN NOT MATCHED THEN INSERT ([id], [name]) VALUES (:id, :name)

Oracle

MERGE INTO "USER" "T1"
USING (SELECT :id AS "ID", :name AS "NAME") "T2"
ON ("T1"."ID" = "T2"."ID")
WHEN MATCHED THEN UPDATE SET "T1"."NAME" = :name
WHEN NOT MATCHED THEN INSERT ("ID", "NAME") VALUES (:id, :name)

Last Insert Id

An insert on an identity primary key can read lastInsertId when the insert uses .withId(). The built-in KronosJdbcWrapper reads JDBC generated keys during insert execution; wrappers that use a follow-up query use the dialect SQL below.

kotlin

val id = User(name = "Kronos")
    .insert()
    .withId()
    .execute()
    .lastInsertId

Mysql

SELECT LAST_INSERT_ID()

PostgreSQL

SELECT LASTVAL()

SQLite

SELECT last_insert_rowid()

SQLServer

SELECT SCOPE_IDENTITY()

Oracle

SELECT MAX("ID") FROM "USER"

Table Operations

table.createTable(...), table.syncTable(...), table.dropTable(...), and table.truncateTable(...) use the active database's DatabaseStatements.

kotlin

val table = Kronos.dataSource.table

table.createTable(Account())
table.syncTable(Account())
table.truncateTable(Account())
table.dropTable(Account())

Mysql

CREATE TABLE `account` (`id` SIGNED NOT NULL, `name` CHAR(64), `created_at` DATETIME)

PostgreSQL

CREATE TABLE "account" ("id" INTEGER NOT NULL, "name" VARCHAR(64), "created_at" TIMESTAMP)

SQLite

CREATE TABLE "account" ("id" INTEGER NOT NULL, "name" VARCHAR(64), "created_at" TIMESTAMP)

SQLServer

CREATE TABLE [account] ([name] NVARCHAR(32), [created_at] DATETIME2)

Oracle

CREATE TABLE "ACCOUNT" ("NAME" VARCHAR(4000), "SCORE" INT)

Schema sync reads the current table structure first, then executes the required DDL.

kotlin

val changed = Kronos.dataSource.table.syncTable(User())

result

changed == false  // Kronos creates the target table
changed == true   // Kronos synchronizes the existing table by diff

Built-in Functions

The function DSL keeps the same Kotlin call shape while the active dialect selects its SQL form. Function availability and argument semantics are covered in 📚Functions.

kotlin

val rows = User()
    .select { f.rand().alias("rand") }
    .toList()

Mysql

SELECT RAND() AS rand
FROM `tb_user`

PostgreSQL

SELECT RANDOM() AS rand
FROM "tb_user"

SQLite

SELECT (RANDOM() / 9223372036854775808.0 + 0.5) AS rand
FROM "tb_user"

H2

SELECT RAND() AS rand
FROM "tb_user"

SQLServer

SELECT RAND() AS rand
FROM [tb_user]

Oracle

SELECT DBMS_RANDOM.VALUE AS RAND
FROM "TB_USER"

DM8

SELECT DBMS_RANDOM.VALUE AS RAND
FROM "TB_USER"

H2

H2 uses quoted identifiers and H2 MERGE syntax for conflict-based upserts. Use the H2 JDBC configuration in 📚Connect to DB for an in-memory or server connection.

kotlin

User(id = 1, name = "Ada")
    .upsert { it.name }
    .onConflict()
    .execute()

H2

MERGE INTO "user" AS "t1"
USING (VALUES (:id, :name)) AS "t2" ("id", "name")
ON ("t1"."id" = "t2"."id")
WHEN MATCHED THEN UPDATE SET "name" = "t2"."name"
WHEN NOT MATCHED THEN INSERT ("id", "name") VALUES ("t2"."id", "t2"."name")

H2 identity fields use H2 identity columns. Insert tasks with .withId() return the JDBC-generated key.

DM8

DM8 uses Oracle-compatible query syntax and DM8-native identity columns. Configure its JDBC driver as shown in 📚Connect to DB.

kotlin

@PrimaryKey(identity = true)
var id: Int? = null

Kronos renders this field as INT IDENTITY(1,1) in DM8 table DDL. Insert tasks with .withId() return the JDBC-generated key.

Database Enum Values and Extension

DBType also contains DB2, Sybase, OceanBase, and GaussDB for connection identification and dialect extension entry points.

val dbType = DBType.fromName(connection.metaData.databaseProductName)

For adding a new database dialect, see 📚Create Database Dialect.

For custom database connections, see 📚concept - data-source-wrapper.