2026-08-19 09:14:01 +08:00
|
|
|
|
/*
|
|
|
|
|
|
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
|
|
|
|
|
* 迁移基础设施:Migration 基类 + MigrationBuilder 声明式操作 + DDL SQL 生成。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 用法(对齐 EF Core 迁移):
|
|
|
|
|
|
* public class InitialCreate <: Migration {
|
|
|
|
|
|
* public init() { super("20250701000000_InitialCreate", "创建用户表") }
|
|
|
|
|
|
* public override func up(builder: MigrationBuilder): Unit {
|
|
|
|
|
|
* builder.createTable("users") { tb =>
|
|
|
|
|
|
* tb.column("id", ColumnTypes.BigIntCol).primary().autoInc().notNull()
|
|
|
|
|
|
* tb.column("name", ColumnTypes.TextCol).withMaxLength(100).notNull()
|
|
|
|
|
|
* }
|
|
|
|
|
|
* builder.createIndex("ix_users_name", "users", ["name"])
|
|
|
|
|
|
* }
|
|
|
|
|
|
* public override func down(builder: MigrationBuilder): Unit {
|
|
|
|
|
|
* builder.dropTable("users")
|
|
|
|
|
|
* }
|
|
|
|
|
|
* }
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
package simcu::orm.migrations
|
|
|
|
|
|
|
|
|
|
|
|
import std.collection.*
|
|
|
|
|
|
import std.time.*
|
|
|
|
|
|
import simcu::orm.sql.*
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 列定义(表结构/迁移用)。列类型 ColumnTypes 定义于 simcu::orm.sql,DDL 映射由方言决定。
|
|
|
|
|
|
*/
|
|
|
|
|
|
public class ColumnDefinition {
|
|
|
|
|
|
public var name: String = ""
|
|
|
|
|
|
public var columnType: ColumnTypes = ColumnTypes.TextCol
|
|
|
|
|
|
public var primaryKey: Bool = false
|
|
|
|
|
|
public var autoIncrement: Bool = false
|
|
|
|
|
|
public var nullable: Bool = true
|
|
|
|
|
|
public var unique: Bool = false
|
|
|
|
|
|
public var maxLength: Int64 = 255
|
|
|
|
|
|
public var defaultValue: ?Any = None
|
|
|
|
|
|
|
|
|
|
|
|
public init(name: String, columnType: ColumnTypes) {
|
|
|
|
|
|
this.name = name
|
|
|
|
|
|
this.columnType = columnType
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 标记主键
|
|
|
|
|
|
public func primary(): ColumnDefinition {
|
|
|
|
|
|
this.primaryKey = true
|
|
|
|
|
|
this
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 标记自增(仅整型列有效)
|
|
|
|
|
|
public func autoInc(): ColumnDefinition {
|
|
|
|
|
|
this.autoIncrement = true
|
|
|
|
|
|
this
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 标记非空
|
|
|
|
|
|
public func notNull(): ColumnDefinition {
|
|
|
|
|
|
this.nullable = false
|
|
|
|
|
|
this
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 标记唯一
|
|
|
|
|
|
public func withUnique(): ColumnDefinition {
|
|
|
|
|
|
this.unique = true
|
|
|
|
|
|
this
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 指定字符串最大长度
|
|
|
|
|
|
public func withMaxLength(n: Int64): ColumnDefinition {
|
|
|
|
|
|
this.maxLength = n
|
|
|
|
|
|
this
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 指定默认值
|
|
|
|
|
|
public func withDefault(value: Any): ColumnDefinition {
|
|
|
|
|
|
this.defaultValue = Some(value)
|
|
|
|
|
|
this
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 迁移操作类型。
|
|
|
|
|
|
*/
|
|
|
|
|
|
public enum MigrationOperationKind {
|
|
|
|
|
|
| CreateTable
|
|
|
|
|
|
| DropTable
|
|
|
|
|
|
| AddColumn
|
|
|
|
|
|
| DropColumn
|
|
|
|
|
|
| AlterColumn
|
|
|
|
|
|
| RenameColumn
|
|
|
|
|
|
| CreateIndex
|
|
|
|
|
|
| DropIndex
|
|
|
|
|
|
| RawSql
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 一条迁移操作(不同 kind 使用对应字段)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
public class MigrationOperation {
|
|
|
|
|
|
public let kind: MigrationOperationKind
|
|
|
|
|
|
public var tableName: String = ""
|
|
|
|
|
|
public var columnName: String = ""
|
|
|
|
|
|
public var newColumnName: String = ""
|
|
|
|
|
|
public var column: ?ColumnDefinition = None
|
|
|
|
|
|
public var columnDefs: ArrayList<ColumnDefinition> = ArrayList()
|
|
|
|
|
|
public var indexName: String = ""
|
|
|
|
|
|
public var columnNames: ArrayList<String> = ArrayList()
|
|
|
|
|
|
public var unique: Bool = false
|
|
|
|
|
|
public var sql: String = ""
|
|
|
|
|
|
|
|
|
|
|
|
public init(kind: MigrationOperationKind) {
|
|
|
|
|
|
this.kind = kind
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 建表辅助(createTable 的闭包参数)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
public class TableBuilder {
|
|
|
|
|
|
public let tableName: String
|
|
|
|
|
|
internal let columns = ArrayList<ColumnDefinition>()
|
|
|
|
|
|
|
|
|
|
|
|
public init(tableName: String) {
|
|
|
|
|
|
this.tableName = tableName
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 声明一列(返回列定义以支持链式标记)
|
|
|
|
|
|
public func column(name: String, columnType: ColumnTypes): ColumnDefinition {
|
|
|
|
|
|
let c = ColumnDefinition(name, columnType)
|
|
|
|
|
|
columns.add(c)
|
|
|
|
|
|
c
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 迁移构建器:声明式累积操作(对齐 EF Core MigrationBuilder)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
public class MigrationBuilder {
|
|
|
|
|
|
internal let operations = ArrayList<MigrationOperation>()
|
|
|
|
|
|
|
|
|
|
|
|
public init() {}
|
|
|
|
|
|
|
|
|
|
|
|
/// 当前累积的操作列表(供执行器/测试读取)
|
|
|
|
|
|
public func getOperations(): ArrayList<MigrationOperation> {
|
|
|
|
|
|
operations
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public func createTable(name: String, build: (TableBuilder) -> Unit): MigrationBuilder {
|
|
|
|
|
|
let tb = TableBuilder(name)
|
|
|
|
|
|
build(tb)
|
|
|
|
|
|
let op = MigrationOperation(MigrationOperationKind.CreateTable)
|
|
|
|
|
|
op.tableName = name
|
|
|
|
|
|
op.columnDefs = tb.columns
|
|
|
|
|
|
operations.add(op)
|
|
|
|
|
|
this
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public func dropTable(name: String): MigrationBuilder {
|
|
|
|
|
|
let op = MigrationOperation(MigrationOperationKind.DropTable)
|
|
|
|
|
|
op.tableName = name
|
|
|
|
|
|
operations.add(op)
|
|
|
|
|
|
this
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public func addColumn(table: String, column: ColumnDefinition): MigrationBuilder {
|
|
|
|
|
|
let op = MigrationOperation(MigrationOperationKind.AddColumn)
|
|
|
|
|
|
op.tableName = table
|
|
|
|
|
|
op.column = Some(column)
|
|
|
|
|
|
operations.add(op)
|
|
|
|
|
|
this
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public func dropColumn(table: String, columnName: String): MigrationBuilder {
|
|
|
|
|
|
let op = MigrationOperation(MigrationOperationKind.DropColumn)
|
|
|
|
|
|
op.tableName = table
|
|
|
|
|
|
op.columnName = columnName
|
|
|
|
|
|
operations.add(op)
|
|
|
|
|
|
this
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public func alterColumn(table: String, column: ColumnDefinition): MigrationBuilder {
|
|
|
|
|
|
let op = MigrationOperation(MigrationOperationKind.AlterColumn)
|
|
|
|
|
|
op.tableName = table
|
|
|
|
|
|
op.column = Some(column)
|
|
|
|
|
|
operations.add(op)
|
|
|
|
|
|
this
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public func renameColumn(table: String, oldName: String, newName: String): MigrationBuilder {
|
|
|
|
|
|
let op = MigrationOperation(MigrationOperationKind.RenameColumn)
|
|
|
|
|
|
op.tableName = table
|
|
|
|
|
|
op.columnName = oldName
|
|
|
|
|
|
op.newColumnName = newName
|
|
|
|
|
|
operations.add(op)
|
|
|
|
|
|
this
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public func createIndex(indexName: String, table: String, columns: ArrayList<String>): MigrationBuilder {
|
|
|
|
|
|
createIndex(indexName, table, columns, false)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public func createIndex(indexName: String, table: String, columns: ArrayList<String>,
|
|
|
|
|
|
unique: Bool): MigrationBuilder {
|
|
|
|
|
|
let op = MigrationOperation(MigrationOperationKind.CreateIndex)
|
|
|
|
|
|
op.indexName = indexName
|
|
|
|
|
|
op.tableName = table
|
|
|
|
|
|
op.columnNames = columns
|
|
|
|
|
|
op.unique = unique
|
|
|
|
|
|
operations.add(op)
|
|
|
|
|
|
this
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public func dropIndex(indexName: String, table: String): MigrationBuilder {
|
|
|
|
|
|
let op = MigrationOperation(MigrationOperationKind.DropIndex)
|
|
|
|
|
|
op.indexName = indexName
|
|
|
|
|
|
op.tableName = table
|
|
|
|
|
|
operations.add(op)
|
|
|
|
|
|
this
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public func rawSql(sql: String): MigrationBuilder {
|
|
|
|
|
|
let op = MigrationOperation(MigrationOperationKind.RawSql)
|
|
|
|
|
|
op.sql = sql
|
|
|
|
|
|
operations.add(op)
|
|
|
|
|
|
this
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 迁移基类:migrationId 形如 "20250701000000_InitialCreate"(时间戳_名称),按字典序应用。
|
|
|
|
|
|
* 两种使用方式:
|
|
|
|
|
|
* 1. 子类 override up/down 手写声明式操作(MigrationBuilder);
|
|
|
|
|
|
* 2. 由 MigrationGenerator 从模型生成时注入 preset 操作,基类 up/down 自动重放。
|
|
|
|
|
|
*/
|
|
|
|
|
|
public open class Migration {
|
|
|
|
|
|
public let migrationId: String
|
|
|
|
|
|
public let description: String
|
|
|
|
|
|
|
|
|
|
|
|
/// 由 MigrationGenerator 注入的 up 操作(按序执行)
|
|
|
|
|
|
internal var presetOperations = ArrayList<MigrationOperation>()
|
|
|
|
|
|
/// 由 MigrationGenerator 注入的 down 操作(按序执行,已按 up 逆序反转)
|
|
|
|
|
|
internal var presetDownOperations = ArrayList<MigrationOperation>()
|
|
|
|
|
|
|
|
|
|
|
|
public init(migrationId: String, description: String) {
|
|
|
|
|
|
this.migrationId = migrationId
|
|
|
|
|
|
this.description = description
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public init(migrationId: String) {
|
|
|
|
|
|
this.migrationId = migrationId
|
|
|
|
|
|
this.description = ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public open func up(builder: MigrationBuilder): Unit {
|
|
|
|
|
|
for (op in presetOperations) {
|
|
|
|
|
|
builder.operations.add(op)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public open func down(builder: MigrationBuilder): Unit {
|
|
|
|
|
|
for (op in presetDownOperations) {
|
|
|
|
|
|
builder.operations.add(op)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* DDL SQL 工厂:MigrationOperation → SQL(类型映射与差异语法委托给 ISqlDialect)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
public class DdlFactory {
|
|
|
|
|
|
public init() {}
|
|
|
|
|
|
|
|
|
|
|
|
public func toSql(op: MigrationOperation, dialect: ISqlDialect): String {
|
|
|
|
|
|
match (op.kind) {
|
|
|
|
|
|
case MigrationOperationKind.CreateTable => createTableSql(op, dialect)
|
|
|
|
|
|
case MigrationOperationKind.DropTable => "DROP TABLE IF EXISTS ${dialect.quoteName(op.tableName)}"
|
|
|
|
|
|
case MigrationOperationKind.AddColumn =>
|
|
|
|
|
|
"ALTER TABLE ${dialect.quoteName(op.tableName)} ADD COLUMN ${columnDefSql(op.column.getOrThrow(), dialect)}"
|
|
|
|
|
|
case MigrationOperationKind.DropColumn =>
|
|
|
|
|
|
"ALTER TABLE ${dialect.quoteName(op.tableName)} DROP COLUMN ${dialect.quoteName(op.columnName)}"
|
|
|
|
|
|
case MigrationOperationKind.AlterColumn =>
|
|
|
|
|
|
dialect.buildAlterColumn(op.tableName, columnDefSql(op.column.getOrThrow(), dialect))
|
|
|
|
|
|
case MigrationOperationKind.RenameColumn =>
|
|
|
|
|
|
"ALTER TABLE ${dialect.quoteName(op.tableName)} RENAME COLUMN ${dialect.quoteName(op.columnName)} TO ${dialect.quoteName(op.newColumnName)}"
|
|
|
|
|
|
case MigrationOperationKind.CreateIndex => createIndexSql(op, dialect)
|
|
|
|
|
|
case MigrationOperationKind.DropIndex => dialect.buildDropIndex(op.indexName, op.tableName)
|
|
|
|
|
|
case MigrationOperationKind.RawSql => op.sql
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-21 03:41:53 +08:00
|
|
|
|
/// 解析 AddColumn/AlterColumn 为可执行 SQL 列表(兼容"同名列修改数据结构"场景):
|
|
|
|
|
|
/// - 库中无同名列 → 单条 ADD COLUMN
|
|
|
|
|
|
/// - 有同名列且类型一致 → AddColumn 幂等跳过(空列表);AlterColumn 保留原 SQL(处理非空/长度等变化)
|
|
|
|
|
|
/// - 有同名列但类型不同 → 先 DROP COLUMN 再 ADD COLUMN(重建该字段)
|
|
|
|
|
|
/// existingType 来自 information_schema.data_type;None 表示列不存在。
|
|
|
|
|
|
public func resolveColumnSqls(op: MigrationOperation, dialect: ISqlDialect,
|
|
|
|
|
|
existingType: ?String): ArrayList<String> {
|
|
|
|
|
|
let result = ArrayList<String>()
|
|
|
|
|
|
let col = op.column.getOrThrow()
|
|
|
|
|
|
match (existingType) {
|
|
|
|
|
|
case None => result.add(toSql(op, dialect))
|
|
|
|
|
|
case Some(dbType) =>
|
|
|
|
|
|
let target = dialect.columnTypeSql(col.columnType, col.autoIncrement, col.maxLength)
|
|
|
|
|
|
if (normalizeTypeName(dbType) == normalizeTypeName(target)) {
|
|
|
|
|
|
// 类型一致:AddColumn 视为幂等跳过;AlterColumn 保留原 SQL
|
|
|
|
|
|
let isAlter = match (op.kind) {
|
|
|
|
|
|
case MigrationOperationKind.AlterColumn => true
|
|
|
|
|
|
case _ => false
|
|
|
|
|
|
}
|
|
|
|
|
|
if (isAlter) {
|
|
|
|
|
|
result.add(toSql(op, dialect))
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 类型不同:先删除老字段,再新增新字段
|
|
|
|
|
|
let drop = MigrationOperation(MigrationOperationKind.DropColumn)
|
|
|
|
|
|
drop.tableName = op.tableName
|
|
|
|
|
|
drop.columnName = col.name
|
|
|
|
|
|
result.add(toSql(drop, dialect))
|
|
|
|
|
|
result.add(toSql(op, dialect))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
result
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 类型名归一化:方言 SQL 类型 ↔ information_schema.data_type,统一为小写基础类型(忽略长度/精度括号)。
|
|
|
|
|
|
public static func normalizeTypeName(t: String): String {
|
|
|
|
|
|
let lower = asciiLower(t)
|
|
|
|
|
|
let base = if (let Some(i) <- lower.indexOf("(")) { lower[0..i] } else { lower }
|
|
|
|
|
|
match (base) {
|
|
|
|
|
|
case "bigserial" | "bigint" | "int8" => "bigint"
|
|
|
|
|
|
case "serial" | "integer" | "int" | "int4" => "integer"
|
|
|
|
|
|
case "smallint" | "int2" => "smallint"
|
|
|
|
|
|
case "varchar" | "character varying" => "varchar"
|
|
|
|
|
|
case "character" | "char" | "bpchar" => "char"
|
|
|
|
|
|
case "text" => "text"
|
|
|
|
|
|
case "boolean" | "bool" => "boolean"
|
|
|
|
|
|
case "double precision" | "float8" => "double precision"
|
|
|
|
|
|
case "real" | "float4" => "real"
|
|
|
|
|
|
case "timestamp" | "timestamp without time zone" | "timestamp with time zone" => "timestamp"
|
|
|
|
|
|
case "decimal" | "numeric" => "decimal"
|
|
|
|
|
|
case "bytea" => "bytea"
|
|
|
|
|
|
case _ => base
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// ASCII 大写转小写(A-Z → a-z,其余字符不变;仅依赖切片与 indexOf,避免 std String 缺失 API)
|
|
|
|
|
|
private static func asciiLower(s: String): String {
|
|
|
|
|
|
let upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
|
|
|
|
let lower = "abcdefghijklmnopqrstuvwxyz"
|
|
|
|
|
|
let sb = StringBuilder()
|
|
|
|
|
|
for (i in 0..s.size) {
|
|
|
|
|
|
let c = s[i..i + 1]
|
|
|
|
|
|
if (let Some(idx) <- upper.indexOf(c)) {
|
|
|
|
|
|
sb.append(lower[idx..idx + 1])
|
|
|
|
|
|
} else {
|
|
|
|
|
|
sb.append(c)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
sb.toString()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-19 09:14:01 +08:00
|
|
|
|
private func createTableSql(op: MigrationOperation, dialect: ISqlDialect): String {
|
|
|
|
|
|
let sb = StringBuilder()
|
|
|
|
|
|
sb.append("CREATE TABLE IF NOT EXISTS ${dialect.quoteName(op.tableName)} (\n")
|
|
|
|
|
|
var first = true
|
|
|
|
|
|
for (c in op.columnDefs) {
|
|
|
|
|
|
if (!first) {
|
|
|
|
|
|
sb.append(",\n")
|
|
|
|
|
|
}
|
|
|
|
|
|
sb.append(" ${columnDefSql(c, dialect)}")
|
|
|
|
|
|
first = false
|
|
|
|
|
|
}
|
|
|
|
|
|
sb.append("\n)")
|
|
|
|
|
|
sb.toString()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private func createIndexSql(op: MigrationOperation, dialect: ISqlDialect): String {
|
|
|
|
|
|
let sb = StringBuilder()
|
|
|
|
|
|
sb.append("CREATE ")
|
|
|
|
|
|
if (op.unique) {
|
|
|
|
|
|
sb.append("UNIQUE ")
|
|
|
|
|
|
}
|
|
|
|
|
|
sb.append("INDEX ${dialect.quoteName(op.indexName)} ON ${dialect.quoteName(op.tableName)} (")
|
|
|
|
|
|
var first = true
|
|
|
|
|
|
for (c in op.columnNames) {
|
|
|
|
|
|
if (!first) {
|
|
|
|
|
|
sb.append(", ")
|
|
|
|
|
|
}
|
|
|
|
|
|
sb.append(dialect.quoteName(c))
|
|
|
|
|
|
first = false
|
|
|
|
|
|
}
|
|
|
|
|
|
sb.append(")")
|
|
|
|
|
|
sb.toString()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private func columnDefSql(col: ColumnDefinition, dialect: ISqlDialect): String {
|
|
|
|
|
|
let sb = StringBuilder()
|
|
|
|
|
|
sb.append(dialect.quoteName(col.name))
|
|
|
|
|
|
sb.append(" ${dialect.columnTypeSql(col.columnType, col.autoIncrement, col.maxLength)}")
|
|
|
|
|
|
if (!col.nullable || col.primaryKey) {
|
|
|
|
|
|
sb.append(" NOT NULL")
|
|
|
|
|
|
}
|
|
|
|
|
|
if (col.primaryKey) {
|
|
|
|
|
|
sb.append(" PRIMARY KEY")
|
|
|
|
|
|
}
|
|
|
|
|
|
if (col.unique) {
|
|
|
|
|
|
sb.append(" UNIQUE")
|
|
|
|
|
|
}
|
|
|
|
|
|
if (let Some(dv) <- col.defaultValue) {
|
|
|
|
|
|
sb.append(" DEFAULT ${defaultValueSql(dv)}")
|
|
|
|
|
|
}
|
|
|
|
|
|
sb.toString()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private func defaultValueSql(v: Any): String {
|
|
|
|
|
|
if (let s: String <- v) {
|
|
|
|
|
|
return "'${escapeQuotes(s)}'"
|
|
|
|
|
|
}
|
|
|
|
|
|
if (let b: Bool <- v) {
|
|
|
|
|
|
return if (b) { "TRUE" } else { "FALSE" }
|
|
|
|
|
|
}
|
|
|
|
|
|
if (let i64: Int64 <- v) {
|
|
|
|
|
|
return "${i64}"
|
|
|
|
|
|
}
|
|
|
|
|
|
if (let i32: Int32 <- v) {
|
|
|
|
|
|
return "${i32}"
|
|
|
|
|
|
}
|
|
|
|
|
|
if (let i16: Int16 <- v) {
|
|
|
|
|
|
return "${i16}"
|
|
|
|
|
|
}
|
|
|
|
|
|
if (let i8: Int8 <- v) {
|
|
|
|
|
|
return "${i8}"
|
|
|
|
|
|
}
|
|
|
|
|
|
if (let u64: UInt64 <- v) {
|
|
|
|
|
|
return "${u64}"
|
|
|
|
|
|
}
|
|
|
|
|
|
if (let u32: UInt32 <- v) {
|
|
|
|
|
|
return "${u32}"
|
|
|
|
|
|
}
|
|
|
|
|
|
if (let u16: UInt16 <- v) {
|
|
|
|
|
|
return "${u16}"
|
|
|
|
|
|
}
|
|
|
|
|
|
if (let u8: UInt8 <- v) {
|
|
|
|
|
|
return "${u8}"
|
|
|
|
|
|
}
|
|
|
|
|
|
if (let f64: Float64 <- v) {
|
|
|
|
|
|
return "${f64}"
|
|
|
|
|
|
}
|
|
|
|
|
|
if (let f32: Float32 <- v) {
|
|
|
|
|
|
return "${f32}"
|
|
|
|
|
|
}
|
|
|
|
|
|
if (let r: Rune <- v) {
|
|
|
|
|
|
return "'${r}'"
|
|
|
|
|
|
}
|
|
|
|
|
|
if (let d: DateTime <- v) {
|
|
|
|
|
|
return "'${d.toString()}'"
|
|
|
|
|
|
}
|
|
|
|
|
|
throw Exception("simorm: 不支持的默认值类型,请使用 String/Bool/数值/Rune/DateTime")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private func escapeQuotes(s: String): String {
|
|
|
|
|
|
var sb = StringBuilder()
|
|
|
|
|
|
for (c in s.runes()) {
|
|
|
|
|
|
if (c == Rune(0x27)) {
|
|
|
|
|
|
sb.append("''")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
sb.append(c)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
sb.toString()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|