数据库方言支持

内置数据库方言

Kronos 会通过 KronosDataSourceWrapper.dbType 选择 SQL 方言和表结构语句。

val wrapper = KronosJdbcWrapper(dataSource)

Kronos.dataSource = { wrapper }

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

内置完整方言对应以下 DBType:

数据库DBType
MySQLDBType.Mysql
PostgreSQLDBType.Postgres
SQLiteDBType.SQLite
H2DBType.H2
SQL ServerDBType.Mssql
OracleDBType.Oracle
DM8(达梦)DBType.DM8

连接数据库和 JDBC driver 示例请参考📚连接到数据库。 表 DDL 工作流请参考📚表结构同步和📚基于查询创建表。

方言行为矩阵

本页作为方言行为矩阵入口。每一行都指向包含 Kotlin 入口和内置方言 SQL 形态的章节或页面。

行为查阅位置
分页见分页,查看 page(pageIndex, pageSize) 生成的 SQL。
Upsert 和 onConflict()见 Upsert 与 📚Upsert,查看按匹配字段 upsert、唯一约束冲突 upsert 和动态冲突赋值。
自增主键读取见 Last Insert Id 与 📚Last Insert Id。
H2 和 DM8见下方独立章节,了解 H2 MERGE upsert、DM8 自增列和 JDBC generated key。
DDL 和表结构同步见表结构操作、📚表操作和📚表结构同步。
字段类型渲染见 📚字段类型 中的 KColumnType 示例和方言输出。
标识符引用见标识符引用,查看表名和字段名引用规则。
函数和窗口函数见内置函数、📚函数和📚排序、分页和聚合。

标识符引用

同一段查询会按当前方言引用表名和字段名。

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"

分页

page(pageIndex, pageSize) 的页码从 1 开始。需要总数元数据时,在 page(...) 后调用 withTotal(),得到命名的 PageResult。Kronos 会把相同 DSL 渲染为当前数据库的分页语法。

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() 按数据库唯一约束冲突处理更新,并按当前方言渲染对应 SQL。省略 on { ... } 时,Kronos 会根据 KPojo 唯一性元数据推导冲突目标,包括主键值和已声明的唯一索引。

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

自增主键 insert 调用 .withId() 后可以读取 lastInsertId。内置 KronosJdbcWrapper 会在 insert 执行时读取 JDBC generated keys;使用后续查询读取生成 ID 的 wrapper 会使用下面的方言 SQL。

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.createTable(...)、table.syncTable(...)、table.dropTable(...) 和 table.truncateTable(...) 会使用当前数据库的 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)

表结构同步会先读取当前表结构,再生成需要执行的 DDL。

kotlin

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

result

changed == false  // Kronos 创建目标表
changed == true   // Kronos 按差异同步已有表

内置函数

函数 DSL 保持相同的 Kotlin 调用方式,由当前方言选择对应 SQL 形式。函数可用范围和参数语义见📚函数。

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 使用双引号引用标识符,并通过 H2 MERGE 语法处理唯一约束冲突的 upsert。内存库和服务端连接配置见📚连接到数据库。

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 自增字段使用 H2 的 identity 列定义。带 .withId() 的插入任务会返回 JDBC generated key。

DM8(达梦)

DM8 的查询使用 Oracle 兼容语法,表结构使用 DM8 原生自增列。JDBC Driver 配置见📚连接到数据库。

kotlin

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

Kronos 会将该字段渲染为 DM8 DDL 中的 INT IDENTITY(1,1)。带 .withId() 的插入任务会返回 JDBC generated key。

数据库枚举与扩展

DBType 还包含 DB2、Sybase、OceanBase、GaussDB 等枚举值,用于连接识别和方言扩展入口。

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

添加新的数据库方言请参考📚创建数据库方言。

自定义数据库连接请参考📚概念 - 数据源包装器。