fdb2 1.0.37 → 1.0.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.md +41 -0
  2. package/dist/public/explorer.css +517 -478
  3. package/dist/public/explorer.js +1114 -872
  4. package/dist/public/index.css +87 -27
  5. package/dist/public/index.js +97 -49
  6. package/dist/public/layout.js +2 -2
  7. package/dist/public/vue.js +1 -1
  8. package/dist/server/core/schema/__tests__/canonical.test.ts +195 -0
  9. package/dist/server/core/schema/__tests__/ddl-emitter.test.ts +456 -0
  10. package/dist/server/core/schema/__tests__/diff.test.ts +242 -0
  11. package/dist/server/core/schema/__tests__/roundtrip.property.test.ts +403 -0
  12. package/dist/server/core/schema/canonical.d.ts +50 -0
  13. package/dist/server/core/schema/canonical.d.ts.map +1 -0
  14. package/dist/server/core/schema/canonical.js +212 -0
  15. package/dist/server/core/schema/canonical.js.map +1 -0
  16. package/dist/server/core/schema/canonical.ts +223 -0
  17. package/dist/server/core/schema/ddl-emitter.d.ts +68 -0
  18. package/dist/server/core/schema/ddl-emitter.d.ts.map +1 -0
  19. package/dist/server/core/schema/ddl-emitter.js +813 -0
  20. package/dist/server/core/schema/ddl-emitter.js.map +1 -0
  21. package/dist/server/core/schema/ddl-emitter.ts +925 -0
  22. package/dist/server/core/schema/diff.d.ts +16 -0
  23. package/dist/server/core/schema/diff.d.ts.map +1 -0
  24. package/dist/server/core/schema/diff.js +115 -0
  25. package/dist/server/core/schema/diff.js.map +1 -0
  26. package/dist/server/core/schema/diff.ts +134 -0
  27. package/dist/server/core/schema/type-defaults.d.ts +27 -0
  28. package/dist/server/core/schema/type-defaults.d.ts.map +1 -0
  29. package/dist/server/core/schema/type-defaults.js +61 -0
  30. package/dist/server/core/schema/type-defaults.js.map +1 -0
  31. package/dist/server/core/schema/type-defaults.ts +79 -0
  32. package/dist/server/index.d.ts +1 -1
  33. package/dist/server/index.d.ts.map +1 -1
  34. package/dist/server/index.js +347 -124
  35. package/dist/server/index.js.map +1 -1
  36. package/dist/server/index.ts +336 -161
  37. package/dist/server/model/ddl-spec.d.ts +163 -0
  38. package/dist/server/model/ddl-spec.d.ts.map +1 -0
  39. package/dist/server/model/ddl-spec.js +32 -0
  40. package/dist/server/model/ddl-spec.js.map +1 -0
  41. package/dist/server/model/ddl-spec.ts +175 -0
  42. package/dist/server/service/connection.service.d.ts +20 -0
  43. package/dist/server/service/connection.service.d.ts.map +1 -1
  44. package/dist/server/service/connection.service.js +99 -25
  45. package/dist/server/service/connection.service.js.map +1 -1
  46. package/dist/server/service/connection.service.ts +104 -25
  47. package/dist/server/service/database/base.service.d.ts +0 -4
  48. package/dist/server/service/database/base.service.d.ts.map +1 -1
  49. package/dist/server/service/database/base.service.js.map +1 -1
  50. package/dist/server/service/database/base.service.ts +0 -5
  51. package/dist/server/service/database/clickhouse.service.d.ts +0 -5
  52. package/dist/server/service/database/clickhouse.service.d.ts.map +1 -1
  53. package/dist/server/service/database/clickhouse.service.js +0 -49
  54. package/dist/server/service/database/clickhouse.service.js.map +1 -1
  55. package/dist/server/service/database/clickhouse.service.ts +0 -52
  56. package/dist/server/service/database/cockroachdb.service.d.ts +0 -5
  57. package/dist/server/service/database/cockroachdb.service.d.ts.map +1 -1
  58. package/dist/server/service/database/cockroachdb.service.js +11 -142
  59. package/dist/server/service/database/cockroachdb.service.js.map +1 -1
  60. package/dist/server/service/database/cockroachdb.service.ts +11 -161
  61. package/dist/server/service/database/database.service.d.ts +55 -2
  62. package/dist/server/service/database/database.service.d.ts.map +1 -1
  63. package/dist/server/service/database/database.service.js +246 -4
  64. package/dist/server/service/database/database.service.js.map +1 -1
  65. package/dist/server/service/database/database.service.ts +295 -7
  66. package/dist/server/service/database/mongodb.service.d.ts +0 -6
  67. package/dist/server/service/database/mongodb.service.d.ts.map +1 -1
  68. package/dist/server/service/database/mongodb.service.js +0 -8
  69. package/dist/server/service/database/mongodb.service.js.map +1 -1
  70. package/dist/server/service/database/mongodb.service.ts +0 -8
  71. package/dist/server/service/database/mssql.service.d.ts +8 -4
  72. package/dist/server/service/database/mssql.service.d.ts.map +1 -1
  73. package/dist/server/service/database/mssql.service.js +27 -101
  74. package/dist/server/service/database/mssql.service.js.map +1 -1
  75. package/dist/server/service/database/mssql.service.ts +28 -122
  76. package/dist/server/service/database/mysql.service.d.ts +0 -4
  77. package/dist/server/service/database/mysql.service.d.ts.map +1 -1
  78. package/dist/server/service/database/mysql.service.js +11 -137
  79. package/dist/server/service/database/mysql.service.js.map +1 -1
  80. package/dist/server/service/database/mysql.service.ts +11 -173
  81. package/dist/server/service/database/oracle.service.d.ts +0 -4
  82. package/dist/server/service/database/oracle.service.d.ts.map +1 -1
  83. package/dist/server/service/database/oracle.service.js +11 -104
  84. package/dist/server/service/database/oracle.service.js.map +1 -1
  85. package/dist/server/service/database/oracle.service.ts +11 -127
  86. package/dist/server/service/database/postgres.service.d.ts +0 -4
  87. package/dist/server/service/database/postgres.service.d.ts.map +1 -1
  88. package/dist/server/service/database/postgres.service.js +11 -112
  89. package/dist/server/service/database/postgres.service.js.map +1 -1
  90. package/dist/server/service/database/postgres.service.ts +11 -133
  91. package/dist/server/service/database/sap.service.d.ts +0 -4
  92. package/dist/server/service/database/sap.service.d.ts.map +1 -1
  93. package/dist/server/service/database/sap.service.js +11 -137
  94. package/dist/server/service/database/sap.service.js.map +1 -1
  95. package/dist/server/service/database/sap.service.ts +11 -158
  96. package/dist/server/service/database/sql-type.helper.d.ts.map +1 -1
  97. package/dist/server/service/database/sql-type.helper.js +9 -47
  98. package/dist/server/service/database/sql-type.helper.js.map +1 -1
  99. package/dist/server/service/database/sql-type.helper.ts +11 -53
  100. package/dist/server/service/database/sqlite.service.d.ts +0 -5
  101. package/dist/server/service/database/sqlite.service.d.ts.map +1 -1
  102. package/dist/server/service/database/sqlite.service.js +11 -147
  103. package/dist/server/service/database/sqlite.service.js.map +1 -1
  104. package/dist/server/service/database/sqlite.service.ts +11 -177
  105. package/package.json +1 -1
  106. package/server.js +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../../../../server/core/schema/canonical.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AA8BpG,8DAA8D;AAC9D,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAQzD;AAmBD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,OAAO,GAAG;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAwBvG;AAED,qGAAqG;AACrG,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;CAChC,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CA4B5E;AAOD,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AA4BvC,sDAAsD;AACtD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;CACzB,GAAG,cAAc,CAWjB;AAED,uCAAuC;AACvC,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,SAAS,GAAG,cAAc,CASrE;AAED,qCAAqC;AACrC,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,eAAe,EACvB,KAAK,EAAE,eAAe,GACrB,KAAK,CAAC,MAAM,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,eAAe,CAAC,CAiBtE"}
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeNumericLiteral = normalizeNumericLiteral;
4
+ exports.classifyDefault = classifyDefault;
5
+ exports.canonicalizeType = canonicalizeType;
6
+ exports.canonicalizeTableEntity = canonicalizeTableEntity;
7
+ exports.canonicalizeTableSpec = canonicalizeTableSpec;
8
+ exports.diffColumnFields = diffColumnFields;
9
+ /** 单参数即精度的类型(DECIMAL(10) → precision 而非 length) */
10
+ const PRECISION_TYPES = new Set(['DECIMAL', 'NUMERIC', 'DEC', 'NUMBER']);
11
+ /** 支持自增的整数族类型(首个 token 匹配) */
12
+ const AUTO_INCREMENT_TOKENS = new Set([
13
+ 'INT', 'INTEGER', 'BIGINT', 'SMALLINT', 'TINYINT', 'MEDIUMINT',
14
+ 'SERIAL', 'BIGSERIAL', 'SMALLSERIAL'
15
+ ]);
16
+ /** 无括号也按表达式处理的默认值关键字 */
17
+ const EXPRESSION_KEYWORDS = new Set([
18
+ 'CURRENT_TIMESTAMP', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_USER',
19
+ 'LOCALTIME', 'LOCALTIMESTAMP', 'SYSDATE', 'SYSGUID', 'SESSION_USER'
20
+ ]);
21
+ function trimmed(v) {
22
+ return String(v == null ? '' : v).trim();
23
+ }
24
+ /** ''/null/undefined → undefined;'0' → 0(假值不丢弃);非数字 → undefined */
25
+ function normalizeNumber(v) {
26
+ if (v === null || v === undefined)
27
+ return undefined;
28
+ if (typeof v === 'number')
29
+ return Number.isFinite(v) ? v : undefined;
30
+ const s = String(v).trim();
31
+ if (s === '')
32
+ return undefined;
33
+ if (!/^\d+$/.test(s))
34
+ return undefined;
35
+ const n = parseInt(s, 10);
36
+ return Number.isNaN(n) ? undefined : n;
37
+ }
38
+ /** 数值字面量归一:'007'→'7','0.50'→'0.5','-0'→'0'(字符串实现,避免大数精度损失) */
39
+ function normalizeNumericLiteral(s) {
40
+ const m = /^(-?)(\d+)(?:\.(\d+))?$/.exec(s);
41
+ if (!m)
42
+ return s;
43
+ const int = (m[2] ?? '').replace(/^0+(?=\d)/, '');
44
+ const frac = (m[3] || '').replace(/0+$/, '');
45
+ const text = frac === '' ? int : `${int}.${frac}`;
46
+ if (text === '0' && m[1] === '-')
47
+ return '0';
48
+ return m[1] === '-' && text !== '0' ? `-${text}` : text;
49
+ }
50
+ function normalizeLiteralText(s) {
51
+ const up = s.toUpperCase();
52
+ if (up === 'TRUE')
53
+ return 'true';
54
+ if (up === 'FALSE')
55
+ return 'false';
56
+ if (/^-?\d+(?:\.\d+)?$/.test(s))
57
+ return normalizeNumericLiteral(s);
58
+ return s;
59
+ }
60
+ function isExpressionText(s) {
61
+ if (EXPRESSION_KEYWORDS.has(s.toUpperCase()))
62
+ return true;
63
+ // 函数调用:now()、nextval('seq')、uuid()
64
+ if (/^[A-Za-z_][A-Za-z0-9_]*\s*\(/.test(s))
65
+ return true;
66
+ // PG 类型标注:'0'::character varying(整体在外层已按带引号前缀处理)
67
+ if (s.includes('::'))
68
+ return true;
69
+ return false;
70
+ }
71
+ /**
72
+ * 默认值三态+表达式分类。
73
+ * @param raw 原始值(实体侧:driver 原样,可能是 0/''/'NULL'/null;表单侧:文本)
74
+ * @param hasDefault 表单侧显式标记;实体侧不传(undefined 表示未知,按 raw 推断)
75
+ */
76
+ function classifyDefault(raw, hasDefault) {
77
+ if (hasDefault === false)
78
+ return { kind: 'none', text: '' };
79
+ if (raw === undefined || raw === null) {
80
+ // 表单声明有默认值但未给文本 → 按字面量空串(DEFAULT '')处理,绝不回落成 none
81
+ return hasDefault === true ? { kind: 'literal', text: '' } : { kind: 'none', text: '' };
82
+ }
83
+ if (typeof raw === 'boolean')
84
+ return { kind: 'literal', text: raw ? 'true' : 'false' };
85
+ if (typeof raw === 'number') {
86
+ return Number.isFinite(raw)
87
+ ? { kind: 'literal', text: normalizeNumericLiteral(String(raw)) }
88
+ : { kind: 'expression', text: String(raw) };
89
+ }
90
+ const s = String(raw).trim();
91
+ if (s === '')
92
+ return { kind: 'literal', text: '' }; // MySQL DEFAULT '' 与「无默认」必须可区分
93
+ if (s.toUpperCase() === 'NULL')
94
+ return { kind: 'null', text: '' };
95
+ // 带引号前缀:'abc' / '' / '0'::character varying
96
+ const q = /^'((?:[^']|'')*)'(.*)$/.exec(s);
97
+ if (q) {
98
+ const rest = (q[2] ?? '').trim();
99
+ if (rest === '')
100
+ return { kind: 'literal', text: (q[1] ?? '').replace(/''/g, "'") };
101
+ return { kind: 'expression', text: s.toUpperCase() }; // 'x'::type 等 PG 表达式
102
+ }
103
+ if (isExpressionText(s))
104
+ return { kind: 'expression', text: s.toUpperCase() };
105
+ return { kind: 'literal', text: normalizeLiteralText(s) };
106
+ }
107
+ /** 类型归一:接受 { type:'varchar(255)', length:255 } 或 { type:'VARCHAR', length:'255' } 或 customType 全串 */
108
+ function canonicalizeType(input) {
109
+ const custom = trimmed(input.customType);
110
+ if (custom)
111
+ return { baseType: custom }; // ENUM('a','b') 等整体原样比较
112
+ const raw = trimmed(input.type);
113
+ if (!raw)
114
+ return { baseType: '' };
115
+ const length = normalizeNumber(input.length);
116
+ const precision = normalizeNumber(input.precision);
117
+ const scale = normalizeNumber(input.scale);
118
+ const m = /^([A-Za-z0-9_ ]+?)\s*\(([^)]*)\)$/.exec(raw);
119
+ if (!m)
120
+ return { baseType: raw.toUpperCase(), length, precision, scale };
121
+ const base = (m[1] ?? '').trim().toUpperCase();
122
+ const params = (m[2] ?? '').split(',').map((p) => p.trim());
123
+ // 参数含非数字(ENUM('a','b')):整体按自定义串处理,避免拆坏
124
+ if (!params.every((p) => /^\d+$/.test(p)))
125
+ return { baseType: raw };
126
+ if (params.length >= 2) {
127
+ return {
128
+ baseType: base,
129
+ precision: precision ?? normalizeNumber(params[0] ?? ''),
130
+ scale: scale ?? normalizeNumber(params[1] ?? '')
131
+ };
132
+ }
133
+ if (length !== undefined)
134
+ return { baseType: base, length };
135
+ if (precision !== undefined)
136
+ return { baseType: base, precision, scale };
137
+ if (PRECISION_TYPES.has(base))
138
+ return { baseType: base, precision: normalizeNumber(params[0] ?? ''), scale };
139
+ return { baseType: base, length: normalizeNumber(params[0] ?? '') };
140
+ }
141
+ function autoIncrementCapable(baseType) {
142
+ const token = /^([A-Za-z0-9_]+)/.exec(baseType);
143
+ return !!token && AUTO_INCREMENT_TOKENS.has((token[1] ?? '').toUpperCase());
144
+ }
145
+ function buildColumn(src, primaryKey) {
146
+ const name = trimmed(src.name);
147
+ const t = canonicalizeType({
148
+ type: src.type,
149
+ customType: src.customType,
150
+ length: src.length,
151
+ precision: src.precision,
152
+ scale: src.scale
153
+ });
154
+ const d = classifyDefault(src.defaultValue, src.hasDefault);
155
+ const rawAutoIncrement = !!(src.isAutoIncrement ?? src.autoIncrement);
156
+ return {
157
+ name,
158
+ baseType: t.baseType,
159
+ length: t.length,
160
+ precision: t.precision,
161
+ scale: t.scale,
162
+ nullable: !!src.nullable,
163
+ defaultKind: d.kind,
164
+ defaultText: d.text,
165
+ rawAutoIncrement,
166
+ effectiveAutoIncrement: rawAutoIncrement && primaryKey.has(name) && autoIncrementCapable(t.baseType),
167
+ comment: trimmed(src.comment)
168
+ };
169
+ }
170
+ /** 数据库实体侧(live 结构 / getColumns 结果)→ CanonicalTable */
171
+ function canonicalizeTableEntity(table) {
172
+ const cols = table.columns || [];
173
+ const pk = new Set(cols.filter((c) => !!c.isPrimary).map((c) => trimmed(c.name)).filter(Boolean));
174
+ return {
175
+ name: trimmed(table.name),
176
+ comment: trimmed(table.comment),
177
+ columns: cols.map((c) => buildColumn(c, pk)),
178
+ primaryKey: [...pk]
179
+ };
180
+ }
181
+ /** 前端目标侧(TableSpec)→ CanonicalTable */
182
+ function canonicalizeTableSpec(spec) {
183
+ const pkNames = (spec.primaryKey || []).map((n) => trimmed(n)).filter(Boolean);
184
+ const pk = new Set(pkNames);
185
+ return {
186
+ name: trimmed(spec.name),
187
+ comment: trimmed(spec.comment),
188
+ columns: (spec.columns || []).map((c) => buildColumn(c, pk)),
189
+ primaryKey: pkNames
190
+ };
191
+ }
192
+ /** 列定义(不含列名、不含主键)是否等价;返回发生变化的维度集合 */
193
+ function diffColumnFields(before, after) {
194
+ const fields = [];
195
+ if (before.baseType !== after.baseType ||
196
+ before.length !== after.length ||
197
+ before.precision !== after.precision ||
198
+ before.scale !== after.scale) {
199
+ fields.push('type');
200
+ }
201
+ if (before.nullable !== after.nullable)
202
+ fields.push('nullable');
203
+ if (before.defaultKind !== after.defaultKind || before.defaultText !== after.defaultText) {
204
+ fields.push('default');
205
+ }
206
+ if (before.comment !== after.comment)
207
+ fields.push('comment');
208
+ if (before.effectiveAutoIncrement !== after.effectiveAutoIncrement)
209
+ fields.push('autoIncrement');
210
+ return fields;
211
+ }
212
+ //# sourceMappingURL=canonical.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical.js","sourceRoot":"","sources":["../../../../server/core/schema/canonical.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,223 @@
1
+ /**
2
+ * 归一化投影:把「数据库实体形状」和「前端表单形状」投影到同一个 CanonicalTable。
3
+ *
4
+ * 目标:表示差异不产生假 diff——
5
+ * 'varchar(255)' 与 { type:'VARCHAR', length:'255' } 等价;
6
+ * 0 / '0'、'' / DEFAULT ''、'NULL' / DEFAULT NULL / 无默认值 三态可区分且各自恒等;
7
+ * 注释前后空格、数值字面量的 '007'/'0.50' 等书写差异被消除。
8
+ *
9
+ * 不变式:canonicalize 是纯函数且幂等(对已是 canonical 形状的对象无副作用)。
10
+ * rawAutoIncrement 不做 clamp(保留原始意图,供"加主键时恢复自增"),
11
+ * effectiveAutoIncrement 按「自增必须依附主键」派生——两侧同一规则,比较恒稳。
12
+ */
13
+ import type { CanonicalColumn, CanonicalTable, DefaultKind, TableSpec } from '../../model/ddl-spec';
14
+
15
+ /** 单参数即精度的类型(DECIMAL(10) → precision 而非 length) */
16
+ const PRECISION_TYPES = new Set(['DECIMAL', 'NUMERIC', 'DEC', 'NUMBER']);
17
+ /** 支持自增的整数族类型(首个 token 匹配) */
18
+ const AUTO_INCREMENT_TOKENS = new Set([
19
+ 'INT', 'INTEGER', 'BIGINT', 'SMALLINT', 'TINYINT', 'MEDIUMINT',
20
+ 'SERIAL', 'BIGSERIAL', 'SMALLSERIAL'
21
+ ]);
22
+ /** 无括号也按表达式处理的默认值关键字 */
23
+ const EXPRESSION_KEYWORDS = new Set([
24
+ 'CURRENT_TIMESTAMP', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_USER',
25
+ 'LOCALTIME', 'LOCALTIMESTAMP', 'SYSDATE', 'SYSGUID', 'SESSION_USER'
26
+ ]);
27
+
28
+ function trimmed(v: unknown): string {
29
+ return String(v == null ? '' : v).trim();
30
+ }
31
+
32
+ /** ''/null/undefined → undefined;'0' → 0(假值不丢弃);非数字 → undefined */
33
+ function normalizeNumber(v: number | string | null | undefined): number | undefined {
34
+ if (v === null || v === undefined) return undefined;
35
+ if (typeof v === 'number') return Number.isFinite(v) ? v : undefined;
36
+ const s = String(v).trim();
37
+ if (s === '') return undefined;
38
+ if (!/^\d+$/.test(s)) return undefined;
39
+ const n = parseInt(s, 10);
40
+ return Number.isNaN(n) ? undefined : n;
41
+ }
42
+
43
+ /** 数值字面量归一:'007'→'7','0.50'→'0.5','-0'→'0'(字符串实现,避免大数精度损失) */
44
+ export function normalizeNumericLiteral(s: string): string {
45
+ const m = /^(-?)(\d+)(?:\.(\d+))?$/.exec(s);
46
+ if (!m) return s;
47
+ const int = (m[2] ?? '').replace(/^0+(?=\d)/, '');
48
+ const frac = (m[3] || '').replace(/0+$/, '');
49
+ const text = frac === '' ? int : `${int}.${frac}`;
50
+ if (text === '0' && m[1] === '-') return '0';
51
+ return m[1] === '-' && text !== '0' ? `-${text}` : text;
52
+ }
53
+
54
+ function normalizeLiteralText(s: string): string {
55
+ const up = s.toUpperCase();
56
+ if (up === 'TRUE') return 'true';
57
+ if (up === 'FALSE') return 'false';
58
+ if (/^-?\d+(?:\.\d+)?$/.test(s)) return normalizeNumericLiteral(s);
59
+ return s;
60
+ }
61
+
62
+ function isExpressionText(s: string): boolean {
63
+ if (EXPRESSION_KEYWORDS.has(s.toUpperCase())) return true;
64
+ // 函数调用:now()、nextval('seq')、uuid()
65
+ if (/^[A-Za-z_][A-Za-z0-9_]*\s*\(/.test(s)) return true;
66
+ // PG 类型标注:'0'::character varying(整体在外层已按带引号前缀处理)
67
+ if (s.includes('::')) return true;
68
+ return false;
69
+ }
70
+
71
+ /**
72
+ * 默认值三态+表达式分类。
73
+ * @param raw 原始值(实体侧:driver 原样,可能是 0/''/'NULL'/null;表单侧:文本)
74
+ * @param hasDefault 表单侧显式标记;实体侧不传(undefined 表示未知,按 raw 推断)
75
+ */
76
+ export function classifyDefault(raw: unknown, hasDefault?: boolean): { kind: DefaultKind; text: string } {
77
+ if (hasDefault === false) return { kind: 'none', text: '' };
78
+ if (raw === undefined || raw === null) {
79
+ // 表单声明有默认值但未给文本 → 按字面量空串(DEFAULT '')处理,绝不回落成 none
80
+ return hasDefault === true ? { kind: 'literal', text: '' } : { kind: 'none', text: '' };
81
+ }
82
+ if (typeof raw === 'boolean') return { kind: 'literal', text: raw ? 'true' : 'false' };
83
+ if (typeof raw === 'number') {
84
+ return Number.isFinite(raw)
85
+ ? { kind: 'literal', text: normalizeNumericLiteral(String(raw)) }
86
+ : { kind: 'expression', text: String(raw) };
87
+ }
88
+ const s = String(raw).trim();
89
+ if (s === '') return { kind: 'literal', text: '' }; // MySQL DEFAULT '' 与「无默认」必须可区分
90
+ if (s.toUpperCase() === 'NULL') return { kind: 'null', text: '' };
91
+ // 带引号前缀:'abc' / '' / '0'::character varying
92
+ const q = /^'((?:[^']|'')*)'(.*)$/.exec(s);
93
+ if (q) {
94
+ const rest = (q[2] ?? '').trim();
95
+ if (rest === '') return { kind: 'literal', text: (q[1] ?? '').replace(/''/g, "'") };
96
+ return { kind: 'expression', text: s.toUpperCase() }; // 'x'::type 等 PG 表达式
97
+ }
98
+ if (isExpressionText(s)) return { kind: 'expression', text: s.toUpperCase() };
99
+ return { kind: 'literal', text: normalizeLiteralText(s) };
100
+ }
101
+
102
+ /** 类型归一:接受 { type:'varchar(255)', length:255 } 或 { type:'VARCHAR', length:'255' } 或 customType 全串 */
103
+ export function canonicalizeType(input: {
104
+ type?: string;
105
+ customType?: string;
106
+ length?: number | string | null;
107
+ precision?: number | string | null;
108
+ scale?: number | string | null;
109
+ }): { baseType: string; length?: number; precision?: number; scale?: number } {
110
+ const custom = trimmed(input.customType);
111
+ if (custom) return { baseType: custom }; // ENUM('a','b') 等整体原样比较
112
+ const raw = trimmed(input.type);
113
+ if (!raw) return { baseType: '' };
114
+
115
+ const length = normalizeNumber(input.length);
116
+ const precision = normalizeNumber(input.precision);
117
+ const scale = normalizeNumber(input.scale);
118
+
119
+ const m = /^([A-Za-z0-9_ ]+?)\s*\(([^)]*)\)$/.exec(raw);
120
+ if (!m) return { baseType: raw.toUpperCase(), length, precision, scale };
121
+
122
+ const base = (m[1] ?? '').trim().toUpperCase();
123
+ const params = (m[2] ?? '').split(',').map((p) => p.trim());
124
+ // 参数含非数字(ENUM('a','b')):整体按自定义串处理,避免拆坏
125
+ if (!params.every((p) => /^\d+$/.test(p))) return { baseType: raw };
126
+ if (params.length >= 2) {
127
+ return {
128
+ baseType: base,
129
+ precision: precision ?? normalizeNumber(params[0] ?? ''),
130
+ scale: scale ?? normalizeNumber(params[1] ?? '')
131
+ };
132
+ }
133
+ if (length !== undefined) return { baseType: base, length };
134
+ if (precision !== undefined) return { baseType: base, precision, scale };
135
+ if (PRECISION_TYPES.has(base)) return { baseType: base, precision: normalizeNumber(params[0] ?? ''), scale };
136
+ return { baseType: base, length: normalizeNumber(params[0] ?? '') };
137
+ }
138
+
139
+ function autoIncrementCapable(baseType: string): boolean {
140
+ const token = /^([A-Za-z0-9_]+)/.exec(baseType);
141
+ return !!token && AUTO_INCREMENT_TOKENS.has((token[1] ?? '').toUpperCase());
142
+ }
143
+
144
+ type LooseColumn = Record<string, any>;
145
+
146
+ function buildColumn(src: LooseColumn, primaryKey: Set<string>): CanonicalColumn {
147
+ const name = trimmed(src.name);
148
+ const t = canonicalizeType({
149
+ type: src.type,
150
+ customType: src.customType,
151
+ length: src.length,
152
+ precision: src.precision,
153
+ scale: src.scale
154
+ });
155
+ const d = classifyDefault(src.defaultValue, src.hasDefault);
156
+ const rawAutoIncrement = !!(src.isAutoIncrement ?? src.autoIncrement);
157
+ return {
158
+ name,
159
+ baseType: t.baseType,
160
+ length: t.length,
161
+ precision: t.precision,
162
+ scale: t.scale,
163
+ nullable: !!src.nullable,
164
+ defaultKind: d.kind,
165
+ defaultText: d.text,
166
+ rawAutoIncrement,
167
+ effectiveAutoIncrement: rawAutoIncrement && primaryKey.has(name) && autoIncrementCapable(t.baseType),
168
+ comment: trimmed(src.comment)
169
+ };
170
+ }
171
+
172
+ /** 数据库实体侧(live 结构 / getColumns 结果)→ CanonicalTable */
173
+ export function canonicalizeTableEntity(table: {
174
+ name?: string;
175
+ comment?: string;
176
+ columns?: LooseColumn[];
177
+ }): CanonicalTable {
178
+ const cols = table.columns || [];
179
+ const pk = new Set(
180
+ cols.filter((c) => !!c.isPrimary).map((c) => trimmed(c.name)).filter(Boolean)
181
+ );
182
+ return {
183
+ name: trimmed(table.name),
184
+ comment: trimmed(table.comment),
185
+ columns: cols.map((c) => buildColumn(c, pk)),
186
+ primaryKey: [...pk]
187
+ };
188
+ }
189
+
190
+ /** 前端目标侧(TableSpec)→ CanonicalTable */
191
+ export function canonicalizeTableSpec(spec: TableSpec): CanonicalTable {
192
+ const pkNames = (spec.primaryKey || []).map((n) => trimmed(n)).filter(Boolean);
193
+ const pk = new Set(pkNames);
194
+ return {
195
+ name: trimmed(spec.name),
196
+ comment: trimmed(spec.comment),
197
+ columns: (spec.columns || []).map((c) => buildColumn(c, pk)),
198
+ primaryKey: pkNames
199
+ };
200
+ }
201
+
202
+ /** 列定义(不含列名、不含主键)是否等价;返回发生变化的维度集合 */
203
+ export function diffColumnFields(
204
+ before: CanonicalColumn,
205
+ after: CanonicalColumn
206
+ ): Array<'type' | 'nullable' | 'default' | 'comment' | 'autoIncrement'> {
207
+ const fields: Array<'type' | 'nullable' | 'default' | 'comment' | 'autoIncrement'> = [];
208
+ if (
209
+ before.baseType !== after.baseType ||
210
+ before.length !== after.length ||
211
+ before.precision !== after.precision ||
212
+ before.scale !== after.scale
213
+ ) {
214
+ fields.push('type');
215
+ }
216
+ if (before.nullable !== after.nullable) fields.push('nullable');
217
+ if (before.defaultKind !== after.defaultKind || before.defaultText !== after.defaultText) {
218
+ fields.push('default');
219
+ }
220
+ if (before.comment !== after.comment) fields.push('comment');
221
+ if (before.effectiveAutoIncrement !== after.effectiveAutoIncrement) fields.push('autoIncrement');
222
+ return fields;
223
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * 方言 DDL 生成器:ChangeSet → 各数据库的 DDL 语句序列。
3
+ *
4
+ * 与历史上"前端拼 SQL / 各方言 service 各自拼 ALTER"的关键差异:
5
+ * 1. 单一出口——预览与执行共用 emitAlterDdl,天然"所见即所得",
6
+ * 杜绝旧实现里"前端预览 A、后端执行 B"的分叉;
7
+ * 2. 渲染输入是 CanonicalColumn(defaultKind 三态 + effectiveAutoIncrement clamp),
8
+ * MySQL MODIFY 重建整列定义时不会把 DEFAULT '' / DEFAULT 0 写丢;
9
+ * 3. MySQL 主键/自增的鸡蛋问题有确定性编排:
10
+ * 去自增的 MODIFY → DROP PRIMARY KEY → ADD PRIMARY KEY → 其余列语句
11
+ * (AUTO_INCREMENT 列必须被索引;先 ADD PK 再 MODIFY 加自增);
12
+ * 4. SQLite rebuild 是纯函数:固定临时表名 + 先 DROP IF EXISTS,
13
+ * 同一输入永远得到同一语句序列(可测试、可幂等重试)。
14
+ *
15
+ * 纯函数不变式:emitAlterDdl(kind, c, c) === []。
16
+ */
17
+ import type { CanonicalColumn, CanonicalTable, DdlCapability } from '../../model/ddl-spec';
18
+ import type { DialectKind } from '../dialect';
19
+ /**
20
+ * CanonicalColumn → 类型串(canonical 化的逆投影;自定义类型全串原样)
21
+ *
22
+ * kind 必填:仅填了基础类型名(VARCHAR)而未填长度时,MySQL 等方言下必须补默认参数,
23
+ * 否则会生成 `VARCHAR` 这类非法类型(ER_PARSE_ERROR)。默认参数见 type-defaults。
24
+ */
25
+ export declare function renderType(col: CanonicalColumn, kind: DialectKind): string;
26
+ /** 默认值的 DEFAULT 子句片段(不带 DEFAULT 关键字);null 表示无默认值 */
27
+ export declare function renderDefault(col: CanonicalColumn): string | null;
28
+ export declare function ddlCapabilities(kind: DialectKind): DdlCapability;
29
+ /**
30
+ * 生成 ALTER 所需的、无法从变更集推断的运行时信息。
31
+ * mssqlPkConstraintName:SQL Server 主键约束名是自动生成的(PK__<table>__<hash>),
32
+ * 必须回查系统表获得;缺失时 DROP CONSTRAINT 必然失败,由调用方先判为错误。
33
+ */
34
+ export interface AlterDdlOptions {
35
+ mssqlPkConstraintName?: string;
36
+ }
37
+ /**
38
+ * 结构变更 DDL:内部完成 diff 并按方言渲染。
39
+ * 语句顺序有语义(MySQL PK/AI 编排、SQLite rebuild),调用方必须按序执行。
40
+ */
41
+ export declare function emitAlterDdl(kind: DialectKind, original: CanonicalTable, desired: CanonicalTable, opts?: AlterDdlOptions): string[];
42
+ /**
43
+ * 表名等价判断:按方言的大小写语义比较。
44
+ * PG/CockroachDB 会把裸标识符落成小写、Oracle/SAP 落成大写、SQLite/MSSQL 一般不敏感,
45
+ * 只有 ClickHouse/MongoDB 的标识符严格区分大小写——统一用 === 会漏判(PG 下 `Users` 与 `users` 冲突)。
46
+ */
47
+ export declare function tableNameEquals(kind: DialectKind, a: string, b: string): boolean;
48
+ /** emitCreateDdl 的可选参数 */
49
+ export interface EmitCreateOptions {
50
+ /** 输出 CREATE TABLE IF NOT EXISTS(导出/同步场景) */
51
+ ifNotExists?: boolean;
52
+ /** mssql 三段名前缀(数据库名)——对齐方言 createTable 的 [db].[dbo].[table] 行为 */
53
+ mssqlDatabase?: string;
54
+ /** clickhouse 表引擎(默认 MergeTree) */
55
+ engine?: string;
56
+ /** 表名覆盖(SQLite rebuild 复用同一渲染,固定临时表名) */
57
+ tableNameOverride?: string;
58
+ }
59
+ /**
60
+ * 建表 DDL:所有运行时建表(前端创建表 / syncTable 方言 createTable / SQLite rebuild 临时表)
61
+ * 的单一出口。输入 CanonicalTable(经 canonicalizeTableSpec / canonicalizeTableEntity 投影),
62
+ * 方言差异集中在此——顺带修复旧方言 createTable 的共知缺陷:
63
+ * - PG/CRDB 曾输出 `"col" int SERIAL` 与内联 `COMMENT`(均为语法错误);
64
+ * - SQLite 曾丢失主键、AUTOINCREMENT 脱离 PRIMARY KEY 单独输出;
65
+ * - 默认值用 `if (defaultValue)` 假值兜底(DEFAULT 0 丢失)且一律加引号(`DEFAULT '5'`)。
66
+ */
67
+ export declare function emitCreateDdl(kind: DialectKind, table: CanonicalTable, opts?: EmitCreateOptions): string[];
68
+ //# sourceMappingURL=ddl-emitter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ddl-emitter.d.ts","sourceRoot":"","sources":["../../../../server/core/schema/ddl-emitter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,aAAa,EAEd,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAiC9C;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,CAwB1E;AASD,oDAAoD;AACpD,wBAAgB,aAAa,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAajE;AAmED,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,GAAG,aAAa,CAEhE;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAID;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,cAAc,EACvB,IAAI,CAAC,EAAE,eAAe,GACrB,MAAM,EAAE,CAgCV;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAIhF;AAID,0BAA0B;AAC1B,MAAM,WAAW,iBAAiB;IAChC,6CAA6C;IAC7C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,kEAAkE;IAClE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,cAAc,EACrB,IAAI,GAAE,iBAAsB,GAC3B,MAAM,EAAE,CAuBV"}