///|
using @dialect {type Dialect}
///|
pub(open) trait Entity: ToJson + @json.FromJson {
table(Self) -> Table
}
///|
// 外键约束
pub(all) struct ForeignKey {
name : String
column : String
referenced_table : String
referenced_column : String
on_delete : String // CASCADE, SET NULL, RESTRICT, NO ACTION
on_update : String // CASCADE, SET NULL, RESTRICT, NO ACTION
} derive(Show, ToJson, FromJson, Eq, Compare, Hash)
///|
pub fn add_column(table : Table, col : Column) -> Table {
let new_columns = Array::new()
for column in table.columns {
new_columns.push(column)
}
new_columns.push(col)
{ ..table, columns: FixedArray::from_array(new_columns) }
}
///|
pub fn add_index(table : Table, idx : Index) -> Table {
let new_indexes = Array::new()
for index in table.indexes {
new_indexes.push(index)
}
new_indexes.push(idx)
{ ..table, indexes: FixedArray::from_array(new_indexes) }
}
///|
pub fn add_foreign_key(table : Table, fk : ForeignKey) -> Table {
let new_fks = Array::new()
for foreign_key in table.foreign_keys {
new_fks.push(foreign_key)
}
new_fks.push(fk)
{ ..table, foreign_keys: FixedArray::from_array(new_fks) }
}
///|
pub fn table_comment(table : Table, text : String) -> Table {
{ ..table, comment: text }
}
///|
pub fn table_engine(table : Table, engine : String) -> Table {
{ ..table, engine, }
}
///|
// ---- 索引构建器函数 ----
pub fn Index::new(name : String, columns : FixedArray[String]) -> Index {
{ name, index_type: Index, columns, comment: "" }
}
///|
pub fn Index::new_unique(name : String, columns : FixedArray[String]) -> Index {
{ name, index_type: Unique, columns, comment: "" }
}
///|
pub fn Index::new_primary(columns : FixedArray[String]) -> Index {
{ name: "PRIMARY", index_type: Primary, columns, comment: "" }
}
///|
// ---- 外键构建器函数 ----
pub fn ForeignKey::new(
name : String,
column : String,
referenced_table : String,
referenced_column : String,
) -> ForeignKey {
{
name,
column,
referenced_table,
referenced_column,
on_delete: "RESTRICT",
on_update: "RESTRICT",
}
}
///|
pub fn on_delete_cascade(fk : ForeignKey) -> ForeignKey {
{
name: fk.name,
column: fk.column,
referenced_table: fk.referenced_table,
referenced_column: fk.referenced_column,
on_delete: "CASCADE",
on_update: fk.on_update,
}
}
///|
pub fn on_update_cascade(fk : ForeignKey) -> ForeignKey {
{
name: fk.name,
column: fk.column,
referenced_table: fk.referenced_table,
referenced_column: fk.referenced_column,
on_delete: fk.on_delete,
on_update: "CASCADE",
}
}
///|
// ---- 类型推断辅助函数 ----
pub fn infer_column_type(value : Json) -> ColumnType {
match value {
Number(n, ..) => if n.to_int().to_double() == n { Int } else { Double }
String(_) => VarChar(255)
_ => Text
}
}
///|
// ---- DB/Driver error & result types ----
pub(all) struct DbError {
message : String
} derive(Show, ToJson)
///|
pub type DbResult[T] = Result[T, DbError]
///|
// ---- Driver 接口定义 ----
pub(open) trait Driver {
open(Self, dsn : String) -> DbResult[Self]
close(Self) -> DbResult[Self]
ping(Self) -> DbResult[Unit]
exec(Self, sql : String, args : FixedArray[Json]) -> DbResult[Unit]
query(Self, sql : String, args : FixedArray[Json]) -> DbResult[
FixedArray[Json],
]
}
///|
// ---- 简易查询构建器 ----
pub struct Query {
select : String
from : String
where_ : FixedArray[Where]
order_by : FixedArray[OrderBy]
limit : Int?
offset : Int?
} derive(Eq, Show, ToJson, FromJson)
///|
pub(all) enum OrderBy {
Asc(String)
Desc(String)
} derive(Eq, Show, ToJson, FromJson)
///|
pub struct Where {
col : String
value : Json
ty : WhereType
} derive(Eq, Show, ToJson, FromJson)
///|
pub enum WhereType {
Eq
Gt
Lt
Gte
Lte
} derive(Eq, Show, ToJson, FromJson)
///|
pub fn select_from(table : String) -> Query {
{
select: "*",
from: table,
where_: [],
order_by: [],
limit: None,
offset: None,
}
}
///|
pub fn select_raw(table : String, cols_expr : String) -> Query {
{
select: cols_expr,
from: table,
where_: [],
order_by: [],
limit: None,
offset: None,
}
}
///|
pub fn Query::where_eq(self : Query, col : String, value : Json) -> Query {
let where_ = self.where_ + [{ col, value, ty: Eq }]
{ ..self, where_, }
}
///|
pub fn Query::where_gt(self : Query, col : String, value : Json) -> Query {
let where_ = self.where_ + [{ col, value, ty: Gt }]
{ ..self, where_, }
}
///|
pub fn Query::where_lt(self : Query, col : String, value : Json) -> Query {
let where_ = self.where_ + [{ col, value, ty: Lt }]
{ ..self, where_, }
}
///|
pub fn Query::where_gte(self : Query, col : String, value : Json) -> Query {
let where_ = self.where_ + [{ col, value, ty: Gte }]
{ ..self, where_, }
}
///|
pub fn Query::where_lte(self : Query, col : String, value : Json) -> Query {
let where_ = self.where_ + [{ col, value, ty: Lte }]
{ ..self, where_, }
}
///|
pub fn Query::order_by(self : Query, order_by : OrderBy) -> Query {
{ ..self, order_by: self.order_by + [order_by] }
}
///|
pub fn Query::limit(self : Query, n : Int) -> Query {
{ ..self, limit: Some(n) }
}
///|
pub fn Query::offset(self : Query, n : Int) -> Query {
{ ..self, offset: Some(n) }
}
///|
pub fn Query::page(self : Query, page : Int, size : Int) -> Query {
{ ..self, offset: Some((page - 1) * size), limit: Some(size) }
}
///|
fn param_placeholder(dialect : Dialect, idx : Int) -> String {
match dialect {
PgSQL => "$" + idx.to_string()
Oracle => ":" + idx.to_string()
_ => "?"
}
}
///|
fn placeholders(dialect : Dialect, count : Int, start_index : Int) -> String {
let parts : Array[String] = []
let mut i = 0
while i < count {
parts.push(param_placeholder(dialect, start_index + i))
i = i + 1
}
parts.join(", ")
}
///|
fn format_where_sql(
where_ : FixedArray[Where],
dialect : Dialect,
start_index : Int,
) -> String {
where_
.mapi((i, w) => {
let ty = match w.ty {
Eq => "="
Gt => ">"
Lt => "<"
Gte => ">="
Lte => "<="
}
[w.col, ty, param_placeholder(dialect, start_index + i)].join(" ")
})
.join(" AND ")
}
///|
fn format_set_sql(
sets : FixedArray[Set],
dialect : Dialect,
start_index : Int,
) -> String {
let parts : Array[String] = []
let mut i = 0
for s in sets {
parts.push(
[s.col, "=", param_placeholder(dialect, start_index + i)].join(" "),
)
i = i + 1
}
parts.join(", ")
}
///|
pub fn Query::to(self : Query, dialect : Dialect) -> (String, FixedArray[Json]) {
let buf = @buffer.new()
buf.write_string("SELECT ")
if self.select.is_empty() {
buf.write_string("*")
} else {
buf.write_string(self.select)
}
buf.write_string(" FROM ")
buf.write_string(self.from)
if !self.where_.is_empty() {
buf.write_string(" WHERE ")
buf.write_string(format_where_sql(self.where_, dialect, 1))
}
if !self.order_by.is_empty() {
buf.write_string(" ORDER BY ")
buf.write_string(
self.order_by
.map(ob => {
match ob {
Asc(col) => "\{col} ASC"
Desc(col) => "\{col} DESC"
}
})
.join(", "),
)
}
match dialect {
Oracle | SqlServer => {
if self.offset is Some(offset) {
buf.write_string(" OFFSET ")
buf.write_string(offset.to_string())
buf.write_string(" ROWS")
}
if self.limit is Some(limit) {
buf.write_string(" FETCH NEXT ")
buf.write_string(limit.to_string())
buf.write_string(" ROWS ONLY")
}
}
_ => {
if self.limit is Some(limit) {
buf.write_string(" LIMIT ")
buf.write_string(limit.to_string())
}
if self.offset is Some(offset) {
buf.write_string(" OFFSET ")
buf.write_string(offset.to_string())
}
}
}
(buf.to_string(), self.where_.map(w => w.value))
}
///|
// ---- 事务语句(BEGIN/COMMIT/ROLLBACK) ----
pub fn begin_tx(dialect : Dialect) -> String {
match dialect {
MySQL => "START TRANSACTION"
PgSQL => "BEGIN"
Sqlite3 => "BEGIN"
SqlServer => "BEGIN TRANSACTION"
Oracle => "" // Oracle 事务隐式开始:在首个 DML 执行时开始
}
}
///|
pub fn commit_tx(dialect : Dialect) -> String {
match dialect {
_ => "COMMIT"
}
}
///|
pub fn rollback_tx(dialect : Dialect) -> String {
match dialect {
_ => "ROLLBACK"
}
}
///|
// ---- DDL 类型与列定义映射 ----
fn map_column_type(dialect : Dialect, ct : ColumnType) -> String {
match ct {
TinyInt =>
match dialect {
PgSQL => "SMALLINT"
SqlServer => "TINYINT"
Oracle => "NUMBER(3)"
_ => "TINYINT"
}
SmallInt =>
match dialect {
Oracle => "NUMBER(5)"
PgSQL => "SMALLINT"
_ => "SMALLINT"
}
MediumInt =>
match dialect {
MySQL => "MEDIUMINT"
Oracle => "NUMBER(7)"
Sqlite3 => "INTEGER"
_ => "INT"
}
Int =>
match dialect {
Sqlite3 | PgSQL => "INTEGER"
Oracle => "NUMBER(10)"
_ => "INT"
}
BigInt =>
match dialect {
Oracle => "NUMBER(19)"
_ => "BIGINT"
}
Float =>
match dialect {
Sqlite3 => "REAL"
Oracle => "BINARY_FLOAT"
_ => "FLOAT"
}
Double =>
match dialect {
Sqlite3 => "REAL"
PgSQL => "DOUBLE PRECISION"
Oracle => "BINARY_DOUBLE"
_ => "DOUBLE"
}
Decimal(p, s) =>
match dialect {
Sqlite3 => "NUMERIC"
Oracle => "NUMBER(\{p}, \{s})"
_ => "DECIMAL(\{p}, \{s})"
}
Char(len) =>
match dialect {
Sqlite3 => "TEXT"
_ => "CHAR(\{len})"
}
VarChar(len) =>
match dialect {
PgSQL | MySQL | SqlServer => "VARCHAR(\{len})"
Sqlite3 => "TEXT"
Oracle => "VARCHAR2(\{len})"
}
Text =>
match dialect {
Oracle => "CLOB"
PgSQL => "TEXT"
SqlServer => "NVARCHAR(MAX)"
_ => "TEXT"
}
MediumText =>
match dialect {
MySQL => "MEDIUMTEXT"
_ => "TEXT"
}
LongText =>
match dialect {
MySQL => "LONGTEXT"
Oracle => "CLOB"
_ => "TEXT"
}
Binary(len) =>
match dialect {
MySQL => "BINARY(\{len})"
SqlServer => "BINARY(\{len})"
Oracle => "RAW(\{len})"
PgSQL => "BYTEA"
Sqlite3 => "BLOB"
}
VarBinary(len) =>
match dialect {
MySQL => "VARBINARY(\{len})"
SqlServer => "VARBINARY(\{len})"
PgSQL => "BYTEA"
Oracle => "RAW(\{len})"
Sqlite3 => "BLOB"
}
Blob =>
match dialect {
PgSQL => "BYTEA"
SqlServer => "VARBINARY(MAX)"
Oracle => "BLOB"
_ => "BLOB"
}
MediumBlob =>
match dialect {
MySQL => "MEDIUMBLOB"
Oracle => "BLOB"
_ => "BLOB"
}
LongBlob =>
match dialect {
MySQL => "LONGBLOB"
Oracle => "BLOB"
_ => "BLOB"
}
Date =>
match dialect {
Sqlite3 => "TEXT"
_ => "DATE"
}
Time =>
match dialect {
SqlServer => "TIME"
Oracle => "DATE"
Sqlite3 => "TEXT"
_ => "TIME"
}
DateTime =>
match dialect {
Sqlite3 => "TEXT"
PgSQL => "TIMESTAMP"
SqlServer => "DATETIME2"
Oracle => "TIMESTAMP"
_ => "DATETIME"
}
Timestamp =>
match dialect {
Sqlite3 => "TEXT"
PgSQL => "TIMESTAMP"
SqlServer => "DATETIME2"
Oracle => "TIMESTAMP"
_ => "TIMESTAMP"
}
Year =>
match dialect {
MySQL => "YEAR"
Oracle => "NUMBER(4)"
Sqlite3 => "INTEGER"
_ => "INT"
}
Json =>
match dialect {
PgSQL => "JSON"
MySQL => "JSON"
SqlServer => "NVARCHAR(MAX)"
Oracle => "CLOB"
_ => "TEXT"
}
JsonB =>
match dialect {
PgSQL => "JSONB"
Sqlite3 => "TEXT"
_ => "JSON"
}
Boolean =>
match dialect {
PgSQL => "BOOLEAN"
MySQL => "TINYINT(1)"
Sqlite3 => "INTEGER"
SqlServer => "BIT"
Oracle => "NUMBER(1)"
}
}
}
///|
fn format_column_def(dialect : Dialect, col : Column) -> String {
let ty = map_column_type(dialect, col.column_type)
let buf = @buffer.new()
// PostgreSQL 的自增类型用 SERIAL/BIGSERIAL
let ty_final = if col.auto_increment {
match dialect {
PgSQL =>
match col.column_type {
BigInt => "BIGSERIAL"
_ => "SERIAL"
}
_ => ty
}
} else {
ty
}
buf.write_string(col.name)
buf.write_string(" ")
buf.write_string(ty_final)
// SQL Server/Oracle/SQLite 的自增修饰
if col.auto_increment {
match dialect {
MySQL => buf.write_string(" AUTO_INCREMENT")
SqlServer => buf.write_string(" IDENTITY(1,1)")
Oracle => buf.write_string(" GENERATED BY DEFAULT AS IDENTITY")
Sqlite3 => () // SQLite: 需要 INTEGER PRIMARY KEY AUTOINCREMENT */ }
_ => ()
}
}
if !col.nullable {
buf.write_string(" NOT NULL")
}
if col.primary_key {
match dialect {
Sqlite3 => buf.write_string(" PRIMARY KEY") // SQLite 允许内联主键
_ => buf.write_string(" PRIMARY KEY")
}
}
if col.unique {
buf.write_string(" UNIQUE")
}
if col.default_value is Some(d) {
// 保守策略:默认值按原样拼接,避免误加引号破坏表达式
buf.write_string(" DEFAULT ")
buf.write_string(d)
}
// MySQL 列注释
if col.comment != "" {
match dialect {
MySQL => {
buf.write_string(" COMMENT ")
buf.write_string("'\{col.comment}'")
}
_ => ()
}
}
// SQLite 的 AUTOINCREMENT 必须与 INTEGER PRIMARY KEY 联用
if col.auto_increment && dialect == Sqlite3 {
buf.write_string(" AUTOINCREMENT")
}
buf.to_string()
}
///|
// ---- CREATE TABLE 生成 ----
pub fn Table::to_create_sql(self : Table, dialect : Dialect) -> String {
let buf = @buffer.new()
buf.write_string("CREATE TABLE ")
buf.write_string(self.name)
buf.write_string(" (\n ")
// 列定义
let col_sqls = self.columns.map(c => format_column_def(dialect, c))
buf.write_string(col_sqls.join(",\n "))
// 表级索引/约束
for idx in self.indexes {
match idx.index_type {
Primary => {
buf.write_string(",\n PRIMARY KEY (")
buf.write_string(idx.columns.join(", "))
buf.write_string(")")
}
Unique =>
match dialect {
MySQL => {
buf.write_string(",\n UNIQUE KEY ")
buf.write_string(idx.name)
buf.write_string(" (")
buf.write_string(idx.columns.join(", "))
buf.write_string(")")
}
_ => {
buf.write_string(",\n UNIQUE (")
buf.write_string(idx.columns.join(", "))
buf.write_string(")")
}
}
Index | FullText | Spatial => ()
// 普通索引/全文/空间索引:部分方言不支持在 CREATE TABLE 中声明
// 这里保守不在表定义中加入,交由后续 ALTER/CREATE INDEX 完成
}
}
// 外键
for fk in self.foreign_keys {
buf.write_string(",\n CONSTRAINT ")
buf.write_string(fk.name)
buf.write_string(" FOREIGN KEY (")
buf.write_string(fk.column)
buf.write_string(") REFERENCES ")
buf.write_string(fk.referenced_table)
buf.write_string(" (")
buf.write_string(fk.referenced_column)
buf.write_string(")")
if fk.on_delete != "" {
buf.write_string(" ON DELETE ")
buf.write_string(fk.on_delete)
}
if fk.on_update != "" {
buf.write_string(" ON UPDATE ")
buf.write_string(fk.on_update)
}
}
buf.write_string("\n)")
// MySQL 表选项
match dialect {
MySQL => {
buf.write_string(" ENGINE=")
buf.write_string(self.engine)
buf.write_string(" DEFAULT CHARSET=")
buf.write_string(self.charset)
buf.write_string(" COLLATE=")
buf.write_string(self.collation)
if self.comment != "" {
buf.write_string(" COMMENT=")
buf.write_string(["'", self.comment, "'"].join(""))
}
if self.auto_increment > 1 {
buf.write_string(" AUTO_INCREMENT=")
buf.write_string(self.auto_increment.to_string())
}
}
_ => ()
}
buf.to_string()
}
///|
// ---- ALTER TABLE 构建器 ----
pub enum AlterOp {
AddColumn(Column)
DropColumn(String)
ModifyColumn(Column)
AddIndex(Index)
DropIndex(String)
AddForeignKey(ForeignKey)
DropForeignKey(String)
RenameTable(String)
TableComment(String)
} derive(Show, ToJson, FromJson)
///|
pub struct AlterTable {
table : String
ops : FixedArray[AlterOp]
} derive(Show, ToJson, FromJson)
///|
pub fn alter_table(name : String) -> AlterTable {
{ table: name, ops: [] }
}
///|
pub fn AlterTable::add_column(self : AlterTable, col : Column) -> AlterTable {
{ ..self, ops: self.ops + [AddColumn(col)] }
}
///|
pub fn AlterTable::drop_column(self : AlterTable, name : String) -> AlterTable {
{ ..self, ops: self.ops + [DropColumn(name)] }
}
///|
pub fn AlterTable::modify_column(self : AlterTable, col : Column) -> AlterTable {
{ ..self, ops: self.ops + [ModifyColumn(col)] }
}
///|
pub fn AlterTable::add_index(self : AlterTable, idx : Index) -> AlterTable {
{ ..self, ops: self.ops + [AddIndex(idx)] }
}
///|
pub fn AlterTable::drop_index(self : AlterTable, name : String) -> AlterTable {
{ ..self, ops: self.ops + [DropIndex(name)] }
}
///|
pub fn AlterTable::add_foreign_key(
self : AlterTable,
fk : ForeignKey,
) -> AlterTable {
{ ..self, ops: self.ops + [AddForeignKey(fk)] }
}
///|
pub fn AlterTable::drop_foreign_key(
self : AlterTable,
name : String,
) -> AlterTable {
{ ..self, ops: self.ops + [DropForeignKey(name)] }
}
///|
pub fn AlterTable::rename(self : AlterTable, new_name : String) -> AlterTable {
{ ..self, ops: self.ops + [RenameTable(new_name)] }
}
///|
pub fn AlterTable::comment(self : AlterTable, text : String) -> AlterTable {
{ ..self, ops: self.ops + [TableComment(text)] }
}
///|
pub fn AlterTable::to(
self : AlterTable,
dialect : Dialect,
) -> (String, FixedArray[Json]) {
let stmts = Array::new()
for op in self.ops {
match op {
AddColumn(col) =>
stmts.push(
"ALTER TABLE \{self.table} ADD COLUMN \{format_column_def(dialect, col)}",
)
DropColumn(name) => {
let s = match dialect {
SqlServer => "ALTER TABLE \{self.table} DROP COLUMN \{name}"
_ => "ALTER TABLE \{self.table} DROP COLUMN \{name}"
}
stmts.push(s)
}
ModifyColumn(col) => {
let s = match dialect {
MySQL =>
[
"ALTER TABLE ",
self.table,
" MODIFY COLUMN ",
format_column_def(dialect, col),
].join("")
PgSQL =>
[
"ALTER TABLE ",
self.table,
" ALTER COLUMN ",
col.name,
" TYPE ",
map_column_type(dialect, col.column_type),
].join("")
SqlServer =>
[
"ALTER TABLE ",
self.table,
" ALTER COLUMN ",
format_column_def(dialect, col),
].join("")
Oracle =>
[
"ALTER TABLE ",
self.table,
" MODIFY (",
format_column_def(dialect, col),
")",
].join("")
Sqlite3 =>
[
"ALTER TABLE ",
self.table,
" RENAME COLUMN ",
col.name,
" TO ",
col.name,
].join("") // SQLite 对修改列类型支持有限
}
stmts.push(s)
}
AddIndex(idx) => {
let cols = ["(", idx.columns.join(", "), ")"].join("")
let s = match dialect {
MySQL =>
match idx.index_type {
Unique =>
"ALTER TABLE \{self.table} ADD UNIQUE INDEX \{idx.name} \{cols}"
_ => "ALTER TABLE \{self.table} ADD INDEX \{idx.name} \{cols}"
}
PgSQL =>
match idx.index_type {
Unique =>
"CREATE UNIQUE INDEX \{idx.name} ON \{self.table} \{cols}"
_ => "CREATE INDEX \{idx.name} ON \{self.table} \{cols}"
}
Sqlite3 =>
match idx.index_type {
Primary | Index | FullText | Spatial =>
abort(
"Sqlite3 does not support primary, index, fulltext, or spatial indexes",
)
Unique =>
if idx.index_type == Unique {
"CREATE UNIQUE INDEX \{idx.name} ON \{self.table} \{cols}"
} else {
"CREATE INDEX \{idx.name} ON \{self.table} \{cols}"
}
}
SqlServer =>
match idx.index_type {
Unique =>
"CREATE UNIQUE INDEX \{idx.name} ON \{self.table} \{cols}"
_ => "CREATE INDEX \{idx.name} ON \{self.table} \{cols}"
}
Oracle =>
match idx.index_type {
Unique =>
"CREATE UNIQUE INDEX \{idx.name} ON \{self.table} \{cols}"
_ => "CREATE INDEX \{idx.name} ON \{self.table} \{cols}"
}
}
stmts.push(s)
}
DropIndex(name) => {
let s = match dialect {
MySQL => "ALTER TABLE \{self.table} DROP INDEX \{name}"
SqlServer => "DROP INDEX \{name} ON \{self.table}"
Oracle | Sqlite3 | PgSQL => "DROP INDEX \{name}"
}
stmts.push(s)
}
AddForeignKey(fk) => {
let s = @buffer.new()
s.write_string("ALTER TABLE ")
s.write_string(self.table)
s.write_string(" ADD CONSTRAINT ")
s.write_string(fk.name)
s.write_string(" FOREIGN KEY (")
s.write_string(fk.column)
s.write_string(") REFERENCES ")
s.write_string(fk.referenced_table)
s.write_string(" (")
s.write_string(fk.referenced_column)
s.write_string(")")
if fk.on_delete != "" {
s.write_string([" ON DELETE ", fk.on_delete].join(""))
}
if fk.on_update != "" {
s.write_string([" ON UPDATE ", fk.on_update].join(""))
}
stmts.push(s.to_string())
}
DropForeignKey(name) => {
let s = match dialect {
MySQL => "ALTER TABLE \{self.table} DROP FOREIGN KEY \{name}"
PgSQL => "ALTER TABLE \{self.table} DROP CONSTRAINT \{name}"
_ => "ALTER TABLE \{self.table} DROP CONSTRAINT \{name}"
}
stmts.push(s)
}
RenameTable(new_name) => {
let s = match dialect {
MySQL => "RENAME TABLE \{self.table} TO \{new_name}"
PgSQL => "ALTER TABLE \{self.table} RENAME TO \{new_name}"
_ => "ALTER TABLE \{self.table} RENAME TO \{new_name}"
}
stmts.push(s)
}
TableComment(text) => {
let s = match dialect {
MySQL => "ALTER TABLE \{self.table} COMMENT = '\{text}'"
PgSQL => "COMMENT ON TABLE \{self.table} IS '\{text}'"
Oracle => "COMMENT ON TABLE \{self.table} IS '\{text}'"
_ => ""
}
if !s.is_empty() {
stmts.push(s)
}
}
}
}
(stmts.join("; "), [])
}
///|
// ---- INSERT 构建器 ----
pub struct InsertQuery {
into : String
columns : FixedArray[String]
values : FixedArray[Json]
} derive(Eq, Show, ToJson, FromJson)
///|
pub fn insert_into(table : String) -> InsertQuery {
{ into: table, columns: [], values: [] }
}
///|
pub fn[E : Entity] InsertQuery::from(
self : InsertQuery,
entity : E,
) -> InsertQuery {
let json = entity.to_json()
let columns = entity.table().columns.map(c => c.name)
let values = if json is Object(obj) {
FixedArray::from_iter(obj.values())
} else {
[]
}
{ ..self, columns, values }
}
///|
pub fn InsertQuery::columns(
self : InsertQuery,
columns : FixedArray[String],
) -> InsertQuery {
{ ..self, columns, }
}
///|
pub fn InsertQuery::values(
self : InsertQuery,
values : FixedArray[Json],
) -> InsertQuery {
{ ..self, values, }
}
///|
pub fn InsertQuery::to(
self : InsertQuery,
dialect : Dialect,
) -> (String, FixedArray[Json]) {
let buf = @buffer.new()
buf.write_string("INSERT INTO ")
buf.write_string(self.into)
if !self.columns.is_empty() {
buf.write_string(" (")
buf.write_string(self.columns.join(", "))
buf.write_string(")")
}
buf.write_string(" VALUES (")
buf.write_string(placeholders(dialect, self.values.length(), 1))
buf.write_string(")")
(buf.to_string(), self.values)
}
///|
// ---- UPSERT 构建器 ----
pub struct UpsertQuery {
table : String
sets : FixedArray[Set]
conflict_target : FixedArray[String] // 冲突检测列(主键或唯一索引列)
update_sets : FixedArray[Set] // 冲突时更新的列
} derive(Eq, Show, ToJson, FromJson)
///|
pub fn upsert_into(table : String) -> UpsertQuery {
{ table, sets: [], conflict_target: [], update_sets: [] }
}
///|
pub fn[E : Entity] UpsertQuery::from(
self : UpsertQuery,
entity : E,
) -> UpsertQuery {
let json = entity.to_json()
let sets = if json is Object(obj) {
obj.map((col, value) => { col, value })
} else {
{}
}
let table = entity.table()
let mut conflict_cols : FixedArray[String] = []
match table.primary_key() {
Some(pk) => conflict_cols = [pk]
None =>
for idx in table.indexes {
match idx.index_type {
Unique => {
conflict_cols = idx.columns
break
}
_ => ()
}
}
}
{
..self,
sets: FixedArray::from_iter(sets.values()),
conflict_target: conflict_cols,
}
}
///|
pub fn UpsertQuery::set(
self : UpsertQuery,
col : String,
value : Json,
) -> UpsertQuery {
{ ..self, sets: self.sets + [{ col, value }] }
}
///|
pub fn UpsertQuery::on_conflict(
self : UpsertQuery,
columns : FixedArray[String],
) -> UpsertQuery {
{ ..self, conflict_target: columns }
}
///|
pub fn UpsertQuery::do_update_set(
self : UpsertQuery,
col : String,
value : Json,
) -> UpsertQuery {
{ ..self, update_sets: self.update_sets + [{ col, value }] }
}
///|
pub fn UpsertQuery::to(
self : UpsertQuery,
dialect : Dialect,
) -> (String, FixedArray[Json]) {
let buf = @buffer.new()
// INSERT 部分
buf.write_string("INSERT INTO ")
buf.write_string(self.table)
buf.write_string(" (")
buf.write_string(self.sets.map(s => s.col).join(", "))
buf.write_string(") VALUES (")
buf.write_string(placeholders(dialect, self.sets.length(), 1))
buf.write_string(")")
// 冲突处理部分
match dialect {
PgSQL => {
if !self.conflict_target.is_empty() {
buf.write_string(" ON CONFLICT (")
buf.write_string(self.conflict_target.join(", "))
buf.write_string(")")
}
if !self.update_sets.is_empty() {
buf.write_string(" DO UPDATE SET ")
let parts : Array[String] = []
let mut i = 0
for s in self.update_sets {
parts.push(
[s.col, "=", param_placeholder(dialect, self.sets.length() + 1 + i)].join(
" ",
),
)
i = i + 1
}
buf.write_string(parts.join(", "))
} else {
buf.write_string(" DO NOTHING")
}
buf.write_string(" RETURNING *")
}
MySQL =>
if !self.update_sets.is_empty() {
buf.write_string(" ON DUPLICATE KEY UPDATE ")
let parts : Array[String] = []
let mut i = 0
for s in self.update_sets {
parts.push(
[s.col, "=", param_placeholder(dialect, self.sets.length() + 1 + i)].join(
" ",
),
)
i = i + 1
}
buf.write_string(parts.join(", "))
}
Sqlite3 => {
if !self.conflict_target.is_empty() {
buf.write_string(" ON CONFLICT (")
buf.write_string(self.conflict_target.join(", "))
buf.write_string(")")
}
if !self.update_sets.is_empty() {
buf.write_string(" DO UPDATE SET ")
let parts : Array[String] = []
let mut i = 0
for s in self.update_sets {
parts.push(
[s.col, "=", param_placeholder(dialect, self.sets.length() + 1 + i)].join(
" ",
),
)
i = i + 1
}
buf.write_string(parts.join(", "))
} else {
buf.write_string(" DO NOTHING")
}
buf.write_string(" RETURNING *")
}
_ => abort("UpsertQuery::to: dialect not supported")
}
let mut args : FixedArray[Json] = self.sets.map(s => s.value)
if !self.update_sets.is_empty() {
args = args + self.update_sets.map(s => s.value)
}
(buf.to_string(), FixedArray::from_array(args))
}
///|
// ---- UPDATE 构建器 ----
pub struct Set {
col : String
value : Json
} derive(Eq, Show, ToJson, FromJson)
///|
pub struct UpdateQuery {
table : String
sets : FixedArray[Set]
where_ : FixedArray[Where]
} derive(Eq, Show, ToJson, FromJson)
///|
pub fn update(table : String) -> UpdateQuery {
{ table, sets: [], where_: [] }
}
///|
pub fn[E : Entity] UpdateQuery::from(
self : UpdateQuery,
entity : E,
) -> UpdateQuery {
let json = entity.to_json()
let values = FixedArray::from_iter(
if json is Object(obj) {
obj.map((k, v) => Set::{ col: k, value: v }).values()
} else {
[].iter()
},
)
{ ..self, sets: values }
}
///|
pub fn UpdateQuery::set(
self : UpdateQuery,
col : String,
value : Json,
) -> UpdateQuery {
{ ..self, sets: self.sets + [{ col, value }] }
}
///|
pub fn UpdateQuery::where_eq(
self : UpdateQuery,
col : String,
value : Json,
) -> UpdateQuery {
{ ..self, where_: self.where_ + [{ col, value, ty: Eq }] }
}
///|
pub fn UpdateQuery::where_gt(
self : UpdateQuery,
col : String,
value : Json,
) -> UpdateQuery {
{ ..self, where_: self.where_ + [{ col, value, ty: Gt }] }
}
///|
pub fn UpdateQuery::where_lt(
self : UpdateQuery,
col : String,
value : Json,
) -> UpdateQuery {
{ ..self, where_: self.where_ + [{ col, value, ty: Lt }] }
}
///|
pub fn UpdateQuery::where_gte(
self : UpdateQuery,
col : String,
value : Json,
) -> UpdateQuery {
{ ..self, where_: self.where_ + [{ col, value, ty: Gte }] }
}
///|
pub fn UpdateQuery::where_lte(
self : UpdateQuery,
col : String,
value : Json,
) -> UpdateQuery {
{ ..self, where_: self.where_ + [{ col, value, ty: Lte }] }
}
///|
pub fn UpdateQuery::to_mysql_sql(self : UpdateQuery) -> String {
let buf = @buffer.new()
buf.write_string("UPDATE ")
buf.write_string(self.table)
buf.write_string(" SET ")
buf.write_string(
self.sets.map(s => [s.col, "=", s.value.stringify()].join(" ")).join(", "),
)
if !self.where_.is_empty() {
buf.write_string(" WHERE ")
buf.write_string(
self.where_
.map(w => {
let ty = match w.ty {
Eq => "="
Gt => ">"
Lt => "<"
Gte => ">="
Lte => "<="
}
[w.col, ty, w.value.stringify()].join(" ")
})
.join(" AND "),
)
}
buf.to_string()
}
///|
pub fn UpdateQuery::to(
self : UpdateQuery,
dialect : Dialect,
) -> (String, FixedArray[Json]) {
let buf = @buffer.new()
buf.write_string("UPDATE ")
buf.write_string(self.table)
buf.write_string(" SET ")
buf.write_string(format_set_sql(self.sets, dialect, 1))
if !self.where_.is_empty() {
buf.write_string(" WHERE ")
buf.write_string(
format_where_sql(self.where_, dialect, self.sets.length() + 1),
)
}
(buf.to_string(), self.sets.map(s => s.value) + self.where_.map(w => w.value))
}
///|
// ---- DELETE 构建器 ----
pub struct DeleteQuery {
from : String
where_ : FixedArray[Where]
} derive(Eq, Show, ToJson, FromJson)
///|
pub fn delete_from(table : String) -> DeleteQuery {
{ from: table, where_: [] }
}
///|
pub fn DeleteQuery::where_eq(
self : DeleteQuery,
col : String,
value : Json,
) -> DeleteQuery {
{ ..self, where_: self.where_ + [{ col, value, ty: Eq }] }
}
///|
pub fn DeleteQuery::where_gt(
self : DeleteQuery,
col : String,
value : Json,
) -> DeleteQuery {
{ ..self, where_: self.where_ + [{ col, value, ty: Gt }] }
}
///|
pub fn DeleteQuery::where_lt(
self : DeleteQuery,
col : String,
value : Json,
) -> DeleteQuery {
{ ..self, where_: self.where_ + [{ col, value, ty: Lt }] }
}
///|
pub fn DeleteQuery::where_gte(
self : DeleteQuery,
col : String,
value : Json,
) -> DeleteQuery {
{ ..self, where_: self.where_ + [{ col, value, ty: Gte }] }
}
///|
pub fn DeleteQuery::where_lte(
self : DeleteQuery,
col : String,
value : Json,
) -> DeleteQuery {
{ ..self, where_: self.where_ + [{ col, value, ty: Lte }] }
}
///|
pub fn DeleteQuery::to(
self : DeleteQuery,
dialect : Dialect,
) -> (String, FixedArray[Json]) {
let buf = @buffer.new()
buf.write_string("DELETE FROM ")
buf.write_string(self.from)
if !self.where_.is_empty() {
buf.write_string(" WHERE ")
buf.write_string(format_where_sql(self.where_, dialect, 1))
}
(buf.to_string(), self.where_.map(w => w.value))
}
///|
pub fn[E : Entity] DeleteQuery::from(
self : DeleteQuery,
entity : E,
) -> DeleteQuery {
let table = entity.table()
let pk_opt = table.primary_key()
match pk_opt {
Some(pk) => {
let obj = if entity.to_json() is Object(obj) { obj } else { {} }
match obj.get(pk) {
Some(v) =>
{ ..self, where_: self.where_ + [{ col: pk, value: v, ty: Eq }] }
None => self
}
}
None => self
}
}