///|
// 解析阶段的中间结构,避免直接耦合到 @morm.*
struct FieldSpec {
label : String
field_type : String
nullable : Bool
attrs : @list.List[@moonbitlang/parser/attribute.Attribute]
}
///|
struct EntitySpec {
name : String
table_name : String
fields : Array[FieldSpec]
attrs : Array[ORMAttribute]
}
///|
struct EnumSpec {
name : String
values : FixedArray[String]
}
///|
struct ORMAttribute {
name : String
value : Map[String, String]
}
///|
/// 去除参数键名两端空白字符(空格/制表符)
fn trim_key(s : String) -> String {
let mut t = s
// 去除前缀空白
while t.has_prefix(" ") || t.has_prefix("\t") {
t = t[1:].to_owned()
}
// 去除后缀空白
while t.has_suffix(" ") || t.has_suffix("\t") {
let n = t.length()
if n == 0 {
break
}
t = t[0:n - 1].to_owned()
}
t
}
///|
fn is_ident_char(ch : Char) -> Bool {
(ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9') ||
ch == '_'
}
///|
fn parse_plain_enum_case_name(text : String) -> String? {
let trimmed = trim_key(text)
if trimmed == "" ||
trimmed.contains("(") ||
trimmed.contains("=") ||
trimmed.contains("{") {
return None
}
let name = if trimmed.find("//") is Some(idx) {
trim_key(trimmed[0:idx].to_owned())
} else {
trimmed
}
if name == "" {
return None
}
for ch in name {
if !is_ident_char(ch) {
return None
}
}
Some(name)
}
///|
fn parse_enum_case_segments(
segment_text : String,
values : Array[String],
) -> Bool {
let segments = segment_text.split(",").collect()
for segment in segments {
let trimmed = trim_key(segment.to_owned())
if trimmed == "" {
continue
}
if parse_plain_enum_case_name(trimmed) is Some(name) {
values.push(name)
} else {
return false
}
}
true
}
///|
fn parse_enum_name_from_decl(line : String) -> String? {
guard line.find("enum ") is Some(enum_idx) else { return None }
let mut rest = line[enum_idx + 5:].to_owned()
rest = trim_key(rest)
if rest == "" {
return None
}
let mut name = ""
for ch in rest {
if is_ident_char(ch) {
name = name + ch.to_string()
} else {
break
}
}
if name == "" {
None
} else {
Some(name)
}
}
///|
fn parse_enum_specs_from_source(source : String) -> Array[EnumSpec] {
let specs : Array[EnumSpec] = []
let lines = source.split("\n").collect()
let mut i = 0
while i < lines.length() {
let line = lines[i].to_owned()
if line.contains("enum ") &&
line.contains("{") &&
parse_enum_name_from_decl(line) is Some(name) {
let values : Array[String] = []
let mut supported = true
let mut block = if line.find("{") is Some(open_idx) {
line[open_idx + 1:].to_owned()
} else {
""
}
while true {
let mut current = block
let mut reached_end = false
if current.find("}") is Some(close_idx) {
current = current[0:close_idx].to_owned()
reached_end = true
}
if !parse_enum_case_segments(current, values) {
supported = false
}
if reached_end {
break
}
i = i + 1
if i >= lines.length() {
supported = false
break
}
block = lines[i].to_owned()
}
if supported && values.length() > 0 {
specs.push({ name, values: FixedArray::from_array(values), })
}
}
i = i + 1
}
specs
}
///|
fn find_enum_spec(enum_specs : Array[EnumSpec], name : String) -> EnumSpec? {
for spec in enum_specs {
if spec.name == name {
return Some(spec)
}
}
None
}
///|
fn split_attr_params(params_part : String) -> Array[String] {
let parts : Array[String] = []
let mut start = 0
let mut in_string = false
let mut i = 0
while i < params_part.length() {
let ch = params_part[i]
if ch == '"' && (i == 0 || params_part[i - 1] != '\\') {
in_string = !in_string
} else if ch == ',' && !in_string {
let part = trim_key(params_part[start:i].to_owned())
if part != "" {
parts.push(part)
}
start = i + 1
}
i = i + 1
}
let tail = trim_key(params_part[start:].to_owned())
if tail != "" {
parts.push(tail)
}
parts
}
///|
fn parse_attr_param_map(
name : String,
params_part : String,
) -> Map[String, String] {
let m : Map[String, String] = Map([])
let trimmed = trim_key(params_part)
if name == "#morm.default" && trimmed != "" {
m.set("value", trimmed)
return m
}
if !params_part.contains("=") && trimmed != "" {
m.set("value", trimmed)
return m
}
for pair in split_attr_params(params_part) {
if pair.find("=") is Some(eq_idx) {
let k = trim_key(pair[0:eq_idx].to_owned())
let vrest = trim_key(pair[eq_idx + 1:].to_owned())
if vrest.has_prefix("\"") && vrest.has_suffix("\"") && vrest.length() >= 2 {
m.set(k, vrest[1:vrest.length() - 1].to_owned())
} else if vrest != "" {
m.set(k, vrest)
}
} else if trim_key(pair) != "" {
m.set("value", trim_key(pair))
}
}
m
}
///|
/// 收集并解析所有 #morm.* attributes 为结构化 ORMAttribute
fn collect_orm_attributes(
attrs : @moonbitlang/core/list.List[@moonbitlang/parser/attribute.Attribute],
) -> Array[ORMAttribute] {
let results : Array[ORMAttribute] = []
attrs.each(attr => {
if attr.raw.has_prefix("#morm.") {
let raw = attr.raw
let mut name = raw
let mut params_part = ""
if raw.find("(") is Some(open_idx) {
name = raw[0:open_idx].to_owned()
if raw.has_suffix(")") && raw.length() > open_idx + 1 {
params_part = raw[open_idx + 1:raw.length() - 1].to_owned()
} else if raw.find(")") is Some(close_idx) {
params_part = raw[open_idx + 1:close_idx].to_owned()
}
}
let m = parse_attr_param_map(name, params_part)
results.push({ name, value: m, })
}
})
results
}
///|
/// 与 collect_orm_attributes 相同,但会尝试基于位置信息恢复完整 attribute 文本
fn collect_orm_attributes_from_source(
attrs : @moonbitlang/core/list.List[@moonbitlang/parser/attribute.Attribute],
source : String,
) -> Array[ORMAttribute] {
let results : Array[ORMAttribute] = []
attrs.each(attr => {
let mut raw = attr.raw
if raw.has_prefix("#morm.") && !raw.contains("(") {
let recovered = recover_attr_raw(source, attr)
if recovered != "" {
raw = recovered
}
}
if raw.has_prefix("#morm.") {
let mut name = raw
let mut params_part = ""
if raw.find("(") is Some(open_idx) {
name = raw[0:open_idx].to_owned()
if raw.has_suffix(")") && raw.length() > open_idx + 1 {
params_part = raw[open_idx + 1:raw.length() - 1].to_owned()
} else if raw.find(")") is Some(close_idx) {
params_part = raw[open_idx + 1:close_idx].to_owned()
}
}
let m = parse_attr_param_map(name, params_part)
results.push({ name, value: m, })
}
})
results
}
///|
/// Mapper 方法规格:方法名、SQL、参数与返回实体名
struct MapperMethodSpec {
name : String
sql : String? // 显式 SQL
builder_code : String? // 构建器代码
fetch_join : String? // 查询时追加 JOIN(JPA JOIN FETCH 风格)
params : Array[(String, String)] // (param_name, type_name)
return_type : String
return_kind : String
return_item_type : String?
return_map_key_type : String?
return_map_value_type : String?
returns_option : Bool
is_async : Bool
}
///|
fn long_ident_text(id : @moonbitlang/parser/syntax.LongIdent) -> String {
match id {
Ident(name~) => name
Dot(pkg~, id=target) => "@" + pkg + "." + target
}
}
///|
fn type_list_text(tys : @list.List[@moonbitlang/parser/syntax.Type]) -> String {
let parts : Array[String] = []
tys.each(ty => parts.push(type_expr_text("", ty)))
parts.join(",")
}
///|
fn type_expr_text(
_source : String,
ty : @moonbitlang/parser/syntax.Type,
) -> String {
match ty {
Name(constr_id~, tys~, ..) =>
if tys is Empty {
long_ident_text(constr_id.id)
} else {
"\{long_ident_text(constr_id.id)}[\{type_list_text(tys)}]"
}
Option(ty=inner, ..) => type_expr_text("", inner) + "?"
Tuple(tys~, ..) => "(\{type_list_text(tys)})"
_ => "String"
}
}
///|
fn analyze_return_type(
source : String,
ty : @moonbitlang/parser/syntax.Type,
) -> (String, String, String?, String?, String?, Bool) {
let full_type = type_expr_text(source, ty)
match ty {
Name(constr_id~, tys~, ..) =>
match constr_id.id {
Ident(name=n) | Dot(id=n, ..) if n
is ("FixedArray" | "Array" | "Set" | "List") =>
match tys.head() {
Some(inner) =>
(
full_type,
n,
Some(type_expr_text(source, inner)),
None,
None,
false,
)
None => (full_type, "value", None, None, None, false)
}
Ident(name="Map") | Dot(id="Map", ..) =>
match tys {
More(key_ty, tail=More(value_ty, ..)) =>
(
full_type,
"Map",
None,
Some(type_expr_text(source, key_ty)),
Some(type_expr_text(source, value_ty)),
false,
)
_ => (full_type, "value", None, None, None, false)
}
Ident(name="Page") | Dot(id="Page", ..) =>
match tys.head() {
Some(inner) =>
(
full_type,
"Page",
Some(type_expr_text(source, inner)),
None,
None,
false,
)
None => (full_type, "value", None, None, None, false)
}
_ => (full_type, "value", None, None, None, false)
}
Option(ty=inner, ..) =>
(
full_type,
"option",
Some(type_expr_text(source, inner)),
None,
None,
true,
)
_ => (full_type, "value", None, None, None, false)
}
}
///|
/// Mapper 规格:Trait 名称、实体名、方法列表
struct MapperSpec {
trait_name : String
entity_name : String?
table_name : String?
methods : Array[MapperMethodSpec]
}
///|
/// 提取 attribute 括号中的内容(不含引号处理)
fn extract_text_inside_parens(raw : String) -> String? {
if raw.find("(") is Some(open_idx) && raw.find(")") is Some(close_idx) {
return Some(raw[open_idx + 1:close_idx].to_owned())
}
None
}
///|
/// 提取 attribute 中首个引号包裹的字符串
fn extract_quoted_string_in_parens(raw : String) -> String? {
if raw.find("\"") is Some(start_idx) {
let tail = raw[start_idx + 1:].to_owned()
if tail.find("\"") is Some(end_rel) {
return Some(raw[start_idx + 1:start_idx + 1 + end_rel].to_owned())
}
}
None
}
///|
fn recover_attr_with_parens(
source : String,
attr : @moonbitlang/parser/attribute.Attribute,
raw : String,
) -> String {
if extract_text_inside_parens(raw) is Some(_) {
return raw
}
let recovered = recover_attr_raw(source, attr)
if recovered != "" {
recovered
} else {
raw
}
}
///|
fn recover_attr_with_quoted(
source : String,
attr : @moonbitlang/parser/attribute.Attribute,
raw : String,
) -> String {
if extract_quoted_string_in_parens(raw) is Some(_) {
return raw
}
let recovered = recover_attr_raw(source, attr)
if recovered != "" {
recovered
} else {
raw
}
}
///|
fn recover_attr_raw(
source : String,
attr : @moonbitlang/parser/attribute.Attribute,
) -> String {
let loc = attr.loc
let start_str = Show::to_string(loc.start)
let end_str = Show::to_string(loc.end)
let parse_pos = fn(s : String) -> (Int, Int) {
let mut s = s
if s.has_prefix("\"") && s.length() >= 2 {
s = s[1:s.length()].to_owned()
}
if s.has_suffix("\"") && s.length() >= 1 {
s = s[0:s.length() - 1].to_owned()
}
let parts = s.split(":").collect()
let len = parts.length()
if len >= 2 {
let line = @string.parse_int(parts[len - 2]) catch { _ => -1 }
let col = @string.parse_int(parts[len - 1]) catch { _ => -1 }
(line, col)
} else {
(-1, -1)
}
}
let (sl, sc) = parse_pos(start_str)
let (el, ec) = parse_pos(end_str)
let start_line = sl - 1
let start_col = sc - 1
let end_line = el - 1
let end_col = ec - 1
let lines = source.split("\n").collect()
if start_line == end_line && start_line >= 0 && start_line < lines.length() {
let line = lines[start_line]
let len = line.length()
if start_col >= 0 && end_col <= len && start_col <= end_col {
let res = line[start_col:end_col].to_owned()
return res
}
}
""
}
///|
/// 从方法名提取 `_by_` 后面的条件字段
fn derive_method_condition_fields(method_name : String) -> Array[String] {
let split_fields = fn(part : String) -> Array[String] {
let fields : Array[String] = []
for field in part.split("_and_") {
if field != "" {
fields.push(field.to_owned())
}
}
fields
}
let mut condition_part = ""
if method_name.strip_prefix("find_") is Some(rest) {
let parts = rest.split("_by_").collect()
if parts.length() >= 2 {
condition_part = parts[1].to_owned()
} else if rest.strip_prefix("by_") is Some(by_rest) {
condition_part = by_rest.to_owned()
}
} else if method_name.strip_prefix("count_") is Some(rest) {
let parts = rest.split("_by_").collect()
if parts.length() >= 2 {
condition_part = parts[1].to_owned()
} else if rest.strip_prefix("by_") is Some(by_rest) {
condition_part = by_rest.to_owned()
}
} else if method_name.strip_prefix("delete_by_") is Some(rest) {
condition_part = rest.to_owned()
} else if method_name.strip_prefix("update_") is Some(rest) {
let parts = rest.split("_by_").collect()
if parts.length() >= 2 {
condition_part = parts[1].to_owned()
}
}
if condition_part == "" {
return []
}
split_fields(condition_part)
}
///|
fn derive_update_method_fields(
method_name : String,
) -> (Array[String], Array[String]) {
let split_fields = fn(part : String) -> Array[String] {
let fields : Array[String] = []
for field in part.split("_and_") {
if field != "" {
fields.push(field.to_owned())
}
}
fields
}
if method_name.strip_prefix("update_") is Some(rest) {
let parts = rest.split("_by_").collect()
if parts.length() >= 2 {
let set_fields = split_fields(parts[0].to_owned())
let where_fields = split_fields(parts[1].to_owned())
return (set_fields, where_fields)
}
}
([], [])
}
///|
/// 为没有显式名字的 mapper 参数补上推导名
fn infer_mapper_param_names(method_name : String, count : Int) -> Array[String] {
let inferred : Array[String] = []
if count <= 0 {
return inferred
}
if method_name is ("save" | "update" | "delete" | "create") {
inferred.push("entity")
} else if method_name.has_prefix("update_") {
let (set_fields, where_fields) = derive_update_method_fields(method_name)
for field in set_fields {
inferred.push(field)
}
for field in where_fields {
inferred.push(field)
}
} else {
for field in derive_method_condition_fields(method_name) {
inferred.push(field.to_string())
}
}
while inferred.length() < count {
inferred.push("arg" + (inferred.length() + 1).to_string())
}
let trimmed : Array[String] = []
let mut i = 0
while i < count {
trimmed.push(inferred[i])
i = i + 1
}
trimmed
}
///|
fn resolve_mapper_param_name(
parsed_name : String,
inferred_names : Array[String],
index : Int,
) -> String {
if parsed_name is ("" | "_" | "?binder") {
return inferred_names[index]
}
parsed_name
}
///|
/// 从方法名推导构建器代码
fn derive_builder_code(
method_name : String,
entity_name : String,
table_name : String?,
) -> String? {
let table_expr = match table_name {
Some(t) => "\"\{t}\""
None => "\{entity_name}::table().name"
}
let is_count = method_name.has_prefix("count_")
// 识别动作并提取 by 条件片段(与 entity 解耦)
if method_name is ("find_all" | "all") {
return Some("@morm.select_from(\{table_expr})")
}
if method_name.strip_prefix("delete_by_") is Some(_) {
let fields = derive_method_condition_fields(method_name)
if fields.length() == 0 {
return None
}
let wheres_arr : Array[String] = []
fields.each(f => wheres_arr.push(".where_eq(\"\{f}\", \{f})"))
return Some("@morm.delete_from(\{table_expr})" + wheres_arr.join(""))
}
if method_name.has_prefix("update_") {
let (set_fields, where_fields) = derive_update_method_fields(method_name)
if set_fields.length() == 0 || where_fields.length() == 0 {
return None
}
let sets_arr : Array[String] = []
set_fields.each(f => sets_arr.push(".set(\"\{f}\", \{f})"))
let wheres_arr : Array[String] = []
where_fields.each(f => wheres_arr.push(".where_eq(\"\{f}\", \{f})"))
return Some(
"@morm.update(\{table_expr})" + sets_arr.join("") + wheres_arr.join(""),
)
}
let fields = derive_method_condition_fields(method_name)
if fields.length() == 0 {
return None
}
// 构建代码
let start = if is_count {
"@morm.select_raw(\{table_expr}, \"count(*)\")"
} else {
"@morm.select_from(\{table_expr})"
}
let wheres_arr : Array[String] = []
fields.each(f => wheres_arr.push(".where_eq(\"\{f}\", \{f})"))
let wheres = wheres_arr.join("")
Some("\{start}\{wheres}")
}
///|
/// 解析阶段:从 AST 收集 Mapper 规格
fn parse_mappers_from_ast(
ast : @moonbitlang/core/list.List[@moonbitlang/parser/syntax.Impl],
source : String,
entities : Array[EntitySpec],
) -> Array[MapperSpec] {
let specs : Array[MapperSpec] = []
// 建立实体名到表名的映射
let entity_table_map : Map[String, String] = Map([])
entities.each(e => entity_table_map.set(e.name, e.table_name))
ast.each(item => {
match item {
TopTrait(trait_decl) =>
if trait_decl is { name: { name: trait_name, .. }, methods, attrs, .. } {
// println("Found TopTrait: \{trait_name}")
// 读取 mapper 绑定信息(entity 可选,table 可选)
let mut entity_name_opt : String? = None
let mut table_name_opt : String? = None
attrs.each(attr => {
// println(" Attr: \{attr.raw}")
let raw_text = recover_attr_with_parens(source, attr, attr.raw)
if raw_text.has_prefix("#morm.mapper") {
if extract_text_inside_parens(raw_text) is Some(inner) {
if inner.contains("=") {
if inner =~ (re"entity=\"" + (re"[^\"]*" as entity) + re"\"") {
entity_name_opt = Some(entity.to_owned())
}
if inner =~ (re"table=\"" + (re"[^\"]*" as table) + re"\"") {
table_name_opt = Some(table.to_owned())
}
} else {
let plain = trim_key(inner)
// 兼容旧写法 #mapper(Student)
if entity_table_map.get(plain) is Some(tbl) {
entity_name_opt = Some(plain)
table_name_opt = Some(tbl)
} else {
table_name_opt = Some(plain)
}
}
}
}
})
if table_name_opt is None &&
entity_name_opt is Some(entity_name) &&
entity_table_map.get(entity_name) is Some(tbl) {
table_name_opt = Some(tbl)
}
if entity_name_opt is None && table_name_opt is Some(table_name) {
entities.each(e => {
if e.table_name == table_name {
entity_name_opt = Some(e.name)
}
})
}
// 收集方法
let mmethods : Array[MapperMethodSpec] = []
methods.each(m => {
if m is { name: { name: mname, .. }, params, attrs, is_async, .. } {
// 提取 SQL
let mut sql_opt : String? = None
let mut builder_code_opt : String? = None
let mut fetch_join_opt : String? = None
attrs.each(a => {
let raw_text = recover_attr_with_quoted(source, a, a.raw)
if raw_text.has_prefix("#morm.query") {
sql_opt = extract_quoted_string_in_parens(raw_text)
}
if raw_text.has_prefix("#morm.fetch") ||
raw_text.has_prefix("#morm.fetch_graph") ||
raw_text.has_prefix("#morm.load_graph") {
if extract_quoted_string_in_parens(raw_text) is Some(join_sql) {
fetch_join_opt = Some(join_sql)
} else if extract_text_inside_parens(raw_text) is Some(inner) {
if inner =~ (
re"join=\"" +
(re"[^\"]*" as join_sql) +
re"\""
) {
fetch_join_opt = Some(join_sql.to_owned())
} else if inner =~ (
re"joins=\"" +
(re"[^\"]*" as join_sql) +
re"\""
) {
fetch_join_opt = Some(join_sql.to_owned())
}
}
}
})
// 如果没有显式 query 注解,尝试从方法名推导构建器代码
if sql_opt is None {
builder_code_opt = derive_builder_code(
mname,
match entity_name_opt {
Some(n) => n
None => ""
},
table_name_opt,
)
}
if sql_opt is None &&
builder_code_opt is None &&
mname != "create" &&
mname != "save" &&
mname != "update" &&
mname != "delete" {
return
}
// 提取参数(基于 Parameter 变体,读取 binder.name 与显式类型)
let plist : Array[(String, String)] = []
let mut total_params = 0
params.each(_ => total_params = total_params + 1)
let inferred_param_names = infer_mapper_param_names(
mname, total_params,
)
let mut param_index = 0
params.each(p => {
match p {
Positional(binder={ name: pname, .. }, ty=Some(pty)) => {
let tname = type_expr_text(source, pty)
let resolved_name = resolve_mapper_param_name(
pname, inferred_param_names, param_index,
)
if tname != "Self" {
plist.push((resolved_name, tname))
param_index = param_index + 1
}
}
DiscardPositional(ty=Some(pty), ..) => {
let tname = type_expr_text(source, pty)
if tname != "Self" {
let pname = inferred_param_names[param_index]
plist.push((pname, tname))
param_index = param_index + 1
}
}
Labelled(binder={ name: pname, .. }, ty=Some(pty)) => {
let tname = type_expr_text(source, pty)
let resolved_name = resolve_mapper_param_name(
pname, inferred_param_names, param_index,
)
if tname != "Self" {
plist.push((resolved_name, tname))
param_index = param_index + 1
}
}
_ => ()
}
})
let mut return_type = "String?"
let mut return_kind = "value"
let mut return_item_type : String? = None
let mut return_map_key_type : String? = None
let mut return_map_value_type : String? = None
let mut returns_option = false
match m.return_type {
Some(ty) => {
let (
resolved_return_type,
resolved_return_kind,
resolved_return_item_type,
resolved_return_map_key_type,
resolved_return_map_value_type,
resolved_returns_option,
) = analyze_return_type(source, ty)
return_type = resolved_return_type
return_kind = resolved_return_kind
return_item_type = resolved_return_item_type
return_map_key_type = resolved_return_map_key_type
return_map_value_type = resolved_return_map_value_type
returns_option = resolved_returns_option
}
None => ()
}
mmethods.push({
name: mname,
sql: sql_opt,
builder_code: builder_code_opt,
fetch_join: fetch_join_opt,
params: plist,
return_type,
return_kind,
return_item_type,
return_map_key_type,
return_map_value_type,
returns_option,
is_async: is_async is Some(_),
})
}
})
specs.push({
trait_name,
entity_name: entity_name_opt,
table_name: table_name_opt,
methods: mmethods,
})
}
_ => ()
}
})
specs
}
///|
/// 解析字段上的外键属性(仅参数化声明),学习 GORM 风格,不依赖命名约定
fn parse_foreign_key_for_field(
current_table : String,
field_label : String,
field_type : String,
attrs : @moonbitlang/core/list.List[@moonbitlang/parser/attribute.Attribute],
source : String,
) -> @morm.ForeignKey? {
// 一次性收集并解析 attributes
let parsed = collect_orm_attributes_from_source(attrs, source)
// 查找 foreign_key 项
let mut has_foreign_key = false
let mut is_many_to_one = false
let mut fk_column_name = field_label
let mut fkargs_refs : String? = None
let mut fkargs_table : String? = None
let mut fkargs_column : String? = None
let mut fkargs_on_delete : String? = None
let mut fkargs_on_update : String? = None
parsed.each(a => {
if a.name == "#morm.foreign_key" {
has_foreign_key = true
if a.value.get("references") is Some(v) {
fkargs_refs = Some(v)
}
if a.value.get("table") is Some(v) {
fkargs_table = Some(v)
}
if a.value.get("column") is Some(v) {
fkargs_column = Some(v)
fk_column_name = v
}
if a.value.get("on_delete") is Some(v) {
fkargs_on_delete = Some(v)
}
if a.value.get("on_update") is Some(v) {
fkargs_on_update = Some(v)
}
}
if a.name == "#morm.many_to_one" {
has_foreign_key = true
is_many_to_one = true
if a.value.get("references") is Some(v) {
fkargs_refs = Some(v)
}
if a.value.get("table") is Some(v) {
fkargs_table = Some(v)
}
if a.value.get("column") is Some(v) {
fkargs_column = Some(v)
}
if a.value.get("fk") is Some(v) {
fk_column_name = v
}
if a.value.get("on_delete") is Some(v) {
fkargs_on_delete = Some(v)
}
if a.value.get("on_update") is Some(v) {
fkargs_on_update = Some(v)
}
}
if a.name == "#morm.on_delete_cascade" {
fkargs_on_delete = Some("CASCADE")
}
if a.name == "#morm.on_update_cascade" {
fkargs_on_update = Some("CASCADE")
}
})
if !has_foreign_key {
return None
}
if is_many_to_one && fk_column_name == field_label {
fk_column_name = field_label + "_id"
}
// 支持 references="Table.column" 或分别提供 table/column
let mut referenced_table = ""
let mut referenced_column = ""
if fkargs_refs is Some(r) && r.find(".") is Some(dot_index) {
let dot = dot_index
referenced_table = r[0:dot].to_owned()
referenced_column = r[dot + 1:].to_owned()
}
if referenced_table == "" && fkargs_table is Some(t) {
referenced_table = t
}
if referenced_column == "" && fkargs_column is Some(c) {
referenced_column = c
}
// 兜底:仅声明 #foreign_key 时,按常见命名推断 _id -> .id
if referenced_table == "" && referenced_column == "" {
if field_label.has_suffix("_id") && field_label.length() > 3 {
referenced_table = field_label[0:field_label.length() - 3].to_owned()
referenced_column = "id"
} else {
referenced_table = field_type.to_lower()
referenced_column = "id"
}
}
// 未提供足够信息则不生成外键
if referenced_table == "" || referenced_column == "" {
return None
}
let constraint_name = "fk_" + current_table + "_" + field_label
let mut fk = @morm.new_foreign_key(
constraint_name, fk_column_name, referenced_table, referenced_column,
)
// 可选 on_delete / on_update 参数(CASCADE/SET NULL/RESTRICT/NO ACTION)
if fkargs_on_delete is Some("CASCADE") {
fk = @morm.on_delete_cascade(fk)
}
if fkargs_on_update is Some("CASCADE") {
fk = @morm.on_update_cascade(fk)
}
Some(fk)
}
///|
fn field_is_one_to_many(
attrs : @moonbitlang/core/list.List[@moonbitlang/parser/attribute.Attribute],
source : String,
) -> Bool {
let parsed = collect_orm_attributes_from_source(attrs, source)
let mut hit = false
parsed.each(a => if a.name == "#morm.one_to_many" { hit = true })
hit
}
///|
fn field_is_transient(
attrs : @moonbitlang/core/list.List[@moonbitlang/parser/attribute.Attribute],
source : String,
) -> Bool {
let parsed = collect_orm_attributes_from_source(attrs, source)
let mut hit = false
parsed.each(a => if a.name == "#morm.transient" { hit = true })
hit
}
///|
fn field_many_to_one_fk_column(
field_label : String,
attrs : @moonbitlang/core/list.List[@moonbitlang/parser/attribute.Attribute],
source : String,
) -> String? {
let parsed = collect_orm_attributes_from_source(attrs, source)
for a in parsed {
if a.name == "#morm.many_to_one" {
if a.value.get("fk") is Some(v) {
return Some(v)
}
if a.value.get("column") is Some(v) {
return Some(v)
}
return Some(field_label + "_id")
}
}
None
}
///|
/// 查找 morm 类型属性
fn find_morm_type_attribute(
attrs : @moonbitlang/core/list.List[@moonbitlang/parser/attribute.Attribute],
) -> @morm.ColumnType? {
// 单次遍历 attrs,记录首个匹配并在遍历结束返回,避免多次线性扫描与闭包返回类型不匹配
let mut result : @morm.ColumnType? = None
attrs.each(attr => {
let raw = attr.raw
if raw.has_prefix("#morm.datetime") {
result = Some(DateTime)
} else if raw.has_prefix("#morm.date") {
result = Some(Date)
} else if raw.has_prefix("#morm.time") {
result = Some(Time)
} else if raw.has_prefix("#morm.timestamp") {
result = Some(Timestamp)
} else if raw.has_prefix("#morm.varchar") {
result = Some(VarChar(255))
} else if raw.has_prefix("#morm.char") {
result = Some(Char(1))
} else if raw.has_prefix("#morm.text") {
result = Some(Text)
} else if raw.has_prefix("#morm.mediumtext") {
result = Some(MediumText)
} else if raw.has_prefix("#morm.longtext") {
result = Some(LongText)
} else if raw.has_prefix("#morm.binary") {
result = Some(Binary(1))
} else if raw.has_prefix("#morm.varbinary") {
result = Some(VarBinary(255))
} else if raw.has_prefix("#morm.blob") {
result = Some(Blob)
} else if raw.has_prefix("#morm.tinyint") {
result = Some(TinyInt)
} else if raw.has_prefix("#morm.smallint") {
result = Some(SmallInt)
} else if raw.has_prefix("#morm.int") {
result = Some(Int)
} else if raw.has_prefix("#morm.bigint") {
result = Some(BigInt)
} else if raw.has_prefix("#morm.float") {
result = Some(Float)
} else if raw.has_prefix("#morm.double") {
result = Some(Double)
} else if raw.has_prefix("#morm.decimal") {
result = Some(Decimal(10, 2))
} else if raw.has_prefix("#morm.boolean") {
result = Some(Boolean)
} else if raw.has_prefix("#morm.json") {
result = Some(Json)
} else if raw.has_prefix("#morm.jsonb") {
result = Some(JsonB)
} else if raw.has_prefix("#morm.uuid") {
result = Some(VarChar(36))
}
})
result
}
///|
/// 单次遍历收集常用字段标志,避免重复扫描
fn collect_field_flags(
attrs : @moonbitlang/core/list.List[@moonbitlang/parser/attribute.Attribute],
source : String,
) -> (Bool, Bool, Bool, String?) {
let mut is_primary_key = false
let mut is_auto_increment = false
let mut has_not_null = false
let mut primary_key_strategy : String? = None
let parsed_attrs = collect_orm_attributes_from_source(attrs, source)
parsed_attrs.each(attr => {
match attr.name {
"#morm.primary_key" | "#morm.id" => {
is_primary_key = true
if attr.value.get("strategy") is Some(strategy) {
primary_key_strategy = Some(strategy)
}
}
"#morm.auto_increment" => is_auto_increment = true
"#morm.default" =>
if attr.value.get("value") is Some(value) {
let v = value.to_lower()
if v is ("autoincrement" | "autoincrement()" | "autoincrement( )") {
is_auto_increment = true
if !is_primary_key {
is_primary_key = true
}
if primary_key_strategy is None {
primary_key_strategy = Some("auto_increment")
}
}
}
"#morm.not_null" => has_not_null = true
_ => ()
}
})
if primary_key_strategy is Some(strategy) {
match strategy {
"auto_increment" => {
is_primary_key = true
is_auto_increment = true
}
"manual" => {
is_primary_key = true
is_auto_increment = false
}
_ => {
is_primary_key = true
is_auto_increment = false
}
}
}
(is_primary_key, is_auto_increment, has_not_null, primary_key_strategy)
}
///|
fn extract_type_name_and_nullable(
ty : @moonbitlang/parser/syntax.Type,
) -> (String, Bool) {
match ty {
Name(constr_id={ id: Ident(name~) | Dot(id=name, ..), .. }, ..) =>
(name, false)
Option(
ty=Name(constr_id={ id: Ident(name~) | Dot(id=name, ..), .. }, ..),
..
) => (name, true)
_ => ("String", false)
}
}
///|
/// 解析阶段:使用中间结构 EntitySpec(调用者负责提供 AST)
// 说明:避免在此函数中调用可能带错误类型的 API,保持纯解析逻辑。
fn parse_entities_from_ast(
ast : @moonbitlang/core/list.List[@moonbitlang/parser/syntax.Impl],
) -> Array[EntitySpec] {
let specs : Array[EntitySpec] = []
ast.each(item => {
if item is TopTypeDef(type_decl) {
if type_decl is { tycon: name, components: Record(fields~), attrs, .. } {
let orm_attrs = collect_orm_attributes(attrs)
let mut has_entity = false
let mut table_name = name.to_lower()
orm_attrs.each(a => {
if a.name is ("#morm.model" | "#morm.entity") {
has_entity = true
}
if a.name is ("#morm.model" | "#morm.entity" | "#morm.table") &&
a.value.get("name") is Some(custom_name) {
table_name = custom_name
}
})
if !has_entity {
return
}
let params : Array[FieldSpec] = []
fields.each(param => {
if param is { name: { label, .. }, ty, attrs, .. } {
let (field_type, is_nullable) = extract_type_name_and_nullable(ty)
params.push({ label, field_type, nullable: is_nullable, attrs, })
}
})
specs.push({ name, table_name, fields: params, attrs: orm_attrs, })
}
}
})
specs
}
///|
fn map_type_to_db_by_name(
field_type : String,
attrs : @moonbitlang/core/list.List[@moonbitlang/parser/attribute.Attribute],
enum_specs : Array[EnumSpec],
) -> @morm.ColumnType {
// 首先检查是否有特定的 morm 类型属性
// 使用 match 模式替代多个 if 判断,更接近 GORM 的风格
let attr_type = find_morm_type_attribute(attrs)
if attr_type is Some(ty) {
return ty
}
if find_enum_spec(enum_specs, field_type) is Some(enum_spec) {
return Enum(enum_spec.name, enum_spec.values)
}
// 默认类型映射表,类似 GORM 的 DataTypeMap
match field_type {
// 基本类型
"Int" => Int
"Int8" => TinyInt
"Int16" => SmallInt
"Int32" => Int
"Int64" => BigInt
"UInt" => Int
"UInt8" => Int
"UInt16" => Int
"UInt32" => Int
"UInt64" => Int
"Float" => Float
"Float32" => Float
"Float64" => Double
"Bool" => Boolean
"String" => VarChar(255)
"Char" => Char(1)
// 复杂类型
"Date" => Date
"DateTime" => DateTime
"PlainDateTime" => DateTime
"Time" => Time
"Timestamp" => Timestamp
"ZonedDateTime" => Timestamp
"Json" => Json
"Bytes" => Blob
"UUID" => VarChar(36)
// 默认情况
_ => VarChar(255)
}
}