ormbridge 0.3.0 → 0.5.0
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.
- package/README.md +239 -37
- package/dist/cliRunner.js +14 -1
- package/dist/cliRunner.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +9 -0
- package/dist/config.js.map +1 -1
- package/dist/emitters/drizzle.d.ts +19 -0
- package/dist/emitters/drizzle.js +1337 -0
- package/dist/emitters/drizzle.js.map +1 -0
- package/dist/emitters/jsonSchema.d.ts +20 -0
- package/dist/emitters/jsonSchema.js +503 -0
- package/dist/emitters/jsonSchema.js.map +1 -0
- package/dist/emitters/sqlDdl.d.ts +29 -0
- package/dist/emitters/sqlDdl.js +1741 -0
- package/dist/emitters/sqlDdl.js.map +1 -0
- package/dist/emitters/sqlalchemy.d.ts +30 -0
- package/dist/emitters/sqlalchemy.js +1725 -0
- package/dist/emitters/sqlalchemy.js.map +1 -0
- package/dist/emitters/typeorm.js +53 -1
- package/dist/emitters/typeorm.js.map +1 -1
- package/dist/emitters/zod.d.ts +40 -0
- package/dist/emitters/zod.js +498 -0
- package/dist/emitters/zod.js.map +1 -0
- package/dist/formats.d.ts +11 -1
- package/dist/formats.js +84 -0
- package/dist/formats.js.map +1 -1
- package/dist/index.d.ts +18 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/io.js +82 -8
- package/dist/io.js.map +1 -1
- package/dist/parsers/drizzle.d.ts +12 -0
- package/dist/parsers/drizzle.js +2040 -0
- package/dist/parsers/drizzle.js.map +1 -0
- package/dist/parsers/jsonSchema.d.ts +21 -0
- package/dist/parsers/jsonSchema.js +1432 -0
- package/dist/parsers/jsonSchema.js.map +1 -0
- package/dist/parsers/pythonSyntax.d.ts +7 -1
- package/dist/parsers/pythonSyntax.js +47 -8
- package/dist/parsers/pythonSyntax.js.map +1 -1
- package/dist/parsers/sqlDdl.d.ts +18 -0
- package/dist/parsers/sqlDdl.js +2302 -0
- package/dist/parsers/sqlDdl.js.map +1 -0
- package/dist/parsers/sqlSyntax.d.ts +79 -0
- package/dist/parsers/sqlSyntax.js +698 -0
- package/dist/parsers/sqlSyntax.js.map +1 -0
- package/dist/parsers/sqlTypes.d.ts +53 -0
- package/dist/parsers/sqlTypes.js +742 -0
- package/dist/parsers/sqlTypes.js.map +1 -0
- package/dist/parsers/sqlalchemy.d.ts +12 -0
- package/dist/parsers/sqlalchemy.js +3191 -0
- package/dist/parsers/sqlalchemy.js.map +1 -0
- package/dist/parsers/typeorm.js +22 -7
- package/dist/parsers/typeorm.js.map +1 -1
- package/dist/prismaOnlyConstructs.d.ts +2 -2
- package/dist/prismaOnlyConstructs.js +5 -5
- package/dist/prismaOnlyConstructs.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
import { sourceOf } from './sqlSyntax.js';
|
|
2
|
+
/** Words that may follow the first word of a type name, by that first word. */
|
|
3
|
+
const TYPE_CONTINUATIONS = {
|
|
4
|
+
double: new Set(['precision']),
|
|
5
|
+
character: new Set(['varying', 'large', 'object']),
|
|
6
|
+
char: new Set(['varying', 'large', 'object']),
|
|
7
|
+
nchar: new Set(['varying', 'large', 'object']),
|
|
8
|
+
national: new Set([
|
|
9
|
+
'character',
|
|
10
|
+
'char',
|
|
11
|
+
'varchar',
|
|
12
|
+
'varying',
|
|
13
|
+
'large',
|
|
14
|
+
'object',
|
|
15
|
+
]),
|
|
16
|
+
native: new Set(['character']),
|
|
17
|
+
varying: new Set(['character']),
|
|
18
|
+
bit: new Set(['varying']),
|
|
19
|
+
binary: new Set(['varying', 'large', 'object']),
|
|
20
|
+
long: new Set(['varchar', 'raw', 'varbinary']),
|
|
21
|
+
unsigned: new Set(['big', 'int', 'integer']),
|
|
22
|
+
big: new Set(['int', 'integer']),
|
|
23
|
+
interval: new Set(['year', 'month', 'day', 'hour', 'minute', 'second', 'to']),
|
|
24
|
+
};
|
|
25
|
+
/** Second words that may continue a multi-word type after another continuation word. */
|
|
26
|
+
const CHAINED_CONTINUATIONS = new Set([
|
|
27
|
+
'varying',
|
|
28
|
+
'character',
|
|
29
|
+
'char',
|
|
30
|
+
'large',
|
|
31
|
+
'object',
|
|
32
|
+
'int',
|
|
33
|
+
'integer',
|
|
34
|
+
'to',
|
|
35
|
+
'month',
|
|
36
|
+
'day',
|
|
37
|
+
'hour',
|
|
38
|
+
'minute',
|
|
39
|
+
'second',
|
|
40
|
+
'year',
|
|
41
|
+
'precision',
|
|
42
|
+
'varchar',
|
|
43
|
+
]);
|
|
44
|
+
/**
|
|
45
|
+
* Reads a column type. Never fails: a cursor at something that is not a type gives an empty name, which the
|
|
46
|
+
* mapping reports as an unknown type.
|
|
47
|
+
*/
|
|
48
|
+
export function parseType(cursor, text, dialect) {
|
|
49
|
+
const startToken = cursor.peek();
|
|
50
|
+
const startPosition = cursor.position;
|
|
51
|
+
const consumed = [];
|
|
52
|
+
const nameWords = [];
|
|
53
|
+
let userName;
|
|
54
|
+
let args = [];
|
|
55
|
+
let enumValues;
|
|
56
|
+
let setValues;
|
|
57
|
+
let arrayDepth = 0;
|
|
58
|
+
let unsigned = false;
|
|
59
|
+
let hasArgs = false;
|
|
60
|
+
if (startToken.kind === 'ident') {
|
|
61
|
+
// A quoted type name, possibly schema-qualified: "public"."post_status".
|
|
62
|
+
let last = cursor.next();
|
|
63
|
+
consumed.push(last);
|
|
64
|
+
while (cursor.isPunct('.')) {
|
|
65
|
+
cursor.next();
|
|
66
|
+
const part = cursor.peek();
|
|
67
|
+
if (part.kind === 'ident' || part.kind === 'word') {
|
|
68
|
+
last = cursor.next();
|
|
69
|
+
consumed.push(last);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
userName = last.value;
|
|
76
|
+
nameWords.push(last.value.toLowerCase());
|
|
77
|
+
}
|
|
78
|
+
else if (startToken.kind === 'word') {
|
|
79
|
+
cursor.next();
|
|
80
|
+
consumed.push(startToken);
|
|
81
|
+
if (cursor.isPunct('.')) {
|
|
82
|
+
// schema.type
|
|
83
|
+
let last = startToken;
|
|
84
|
+
while (cursor.isPunct('.')) {
|
|
85
|
+
cursor.next();
|
|
86
|
+
const part = cursor.peek();
|
|
87
|
+
if (part.kind === 'ident' || part.kind === 'word') {
|
|
88
|
+
last = cursor.next();
|
|
89
|
+
consumed.push(last);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
userName = last.value;
|
|
96
|
+
nameWords.push(last.value.toLowerCase());
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
userName = startToken.value;
|
|
100
|
+
nameWords.push(startToken.value.toLowerCase());
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
return {
|
|
105
|
+
name: '',
|
|
106
|
+
args: [],
|
|
107
|
+
arrayDepth: 0,
|
|
108
|
+
unsigned: false,
|
|
109
|
+
text: '',
|
|
110
|
+
userName: '',
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const first = nameWords[0] ?? '';
|
|
114
|
+
const isBuiltInStart = startToken.kind === 'word' && nameWords.length === 1;
|
|
115
|
+
for (;;) {
|
|
116
|
+
const token = cursor.peek();
|
|
117
|
+
if (token.kind === 'word') {
|
|
118
|
+
const word = token.value.toLowerCase();
|
|
119
|
+
if (word === 'unsigned' || word === 'signed' || word === 'zerofill') {
|
|
120
|
+
if (word === 'unsigned') {
|
|
121
|
+
unsigned = true;
|
|
122
|
+
}
|
|
123
|
+
consumed.push(cursor.next());
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (word === 'array') {
|
|
127
|
+
arrayDepth += 1;
|
|
128
|
+
consumed.push(cursor.next());
|
|
129
|
+
if (cursor.isPunct('[')) {
|
|
130
|
+
while (!cursor.atEnd && !cursor.isPunct(']')) {
|
|
131
|
+
consumed.push(cursor.next());
|
|
132
|
+
}
|
|
133
|
+
if (cursor.isPunct(']')) {
|
|
134
|
+
consumed.push(cursor.next());
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if ((word === 'with' || word === 'without') &&
|
|
140
|
+
(cursor.isKeyword('TIME', 1) || cursor.isKeyword('LOCAL', 1))) {
|
|
141
|
+
consumed.push(cursor.next());
|
|
142
|
+
if (cursor.isKeyword('LOCAL')) {
|
|
143
|
+
consumed.push(cursor.next());
|
|
144
|
+
}
|
|
145
|
+
if (cursor.isKeyword('TIME')) {
|
|
146
|
+
consumed.push(cursor.next());
|
|
147
|
+
}
|
|
148
|
+
if (cursor.isKeyword('ZONE')) {
|
|
149
|
+
consumed.push(cursor.next());
|
|
150
|
+
}
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const allowed = isBuiltInStart
|
|
154
|
+
? TYPE_CONTINUATIONS[first]
|
|
155
|
+
: undefined;
|
|
156
|
+
const chained = nameWords.length > 1 && CHAINED_CONTINUATIONS.has(word);
|
|
157
|
+
if (isBuiltInStart &&
|
|
158
|
+
(allowed?.has(word) === true || chained) &&
|
|
159
|
+
// `varchar CHARACTER SET utf8` is a column option, not a type word.
|
|
160
|
+
!(word === 'character' && cursor.isKeyword('SET', 1))) {
|
|
161
|
+
nameWords.push(word);
|
|
162
|
+
consumed.push(cursor.next());
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
if (token.kind === 'punct' && token.text === '(' && !hasArgs) {
|
|
168
|
+
hasArgs = true;
|
|
169
|
+
const group = cursor.readGroup() ?? [];
|
|
170
|
+
consumed.push(token);
|
|
171
|
+
for (const inner of group) {
|
|
172
|
+
consumed.push(inner);
|
|
173
|
+
}
|
|
174
|
+
consumed.push(cursor.tokens[cursor.position - 1] ?? token);
|
|
175
|
+
args = group
|
|
176
|
+
.filter((inner) => inner.kind === 'number' ||
|
|
177
|
+
inner.kind === 'word' ||
|
|
178
|
+
inner.kind === 'ident')
|
|
179
|
+
.map((inner) => inner.value);
|
|
180
|
+
const strings = group
|
|
181
|
+
.filter((inner) => inner.kind === 'string')
|
|
182
|
+
.map((inner) => inner.value);
|
|
183
|
+
if (first === 'enum' && strings.length > 0) {
|
|
184
|
+
enumValues = strings;
|
|
185
|
+
}
|
|
186
|
+
else if (first === 'set' && strings.length > 0) {
|
|
187
|
+
setValues = strings;
|
|
188
|
+
}
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (token.kind === 'punct' && token.text === '[') {
|
|
192
|
+
// PostgreSQL array suffix: [] or [3].
|
|
193
|
+
consumed.push(cursor.next());
|
|
194
|
+
while (!cursor.atEnd && !cursor.isPunct(']')) {
|
|
195
|
+
consumed.push(cursor.next());
|
|
196
|
+
}
|
|
197
|
+
if (cursor.isPunct(']')) {
|
|
198
|
+
consumed.push(cursor.next());
|
|
199
|
+
}
|
|
200
|
+
arrayDepth += 1;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
if (cursor.position === startPosition) {
|
|
206
|
+
cursor.next();
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
name: nameWords.join(' '),
|
|
210
|
+
args,
|
|
211
|
+
arrayDepth,
|
|
212
|
+
unsigned,
|
|
213
|
+
...(enumValues === undefined ? {} : { enumValues }),
|
|
214
|
+
...(setValues === undefined ? {} : { setValues }),
|
|
215
|
+
text: dialect === 'postgresql'
|
|
216
|
+
? sourceOf(text, consumed)
|
|
217
|
+
: sourceOf(text, consumed).replace(/[[\]`]/g, ''),
|
|
218
|
+
userName,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function toInteger(value) {
|
|
222
|
+
if (value === undefined || !/^\d{1,9}$/.test(value)) {
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
return Number(value);
|
|
226
|
+
}
|
|
227
|
+
const INT_NAMES = new Set([
|
|
228
|
+
'int',
|
|
229
|
+
'integer',
|
|
230
|
+
'int2',
|
|
231
|
+
'int4',
|
|
232
|
+
'smallint',
|
|
233
|
+
'mediumint',
|
|
234
|
+
'int3',
|
|
235
|
+
'tinyint',
|
|
236
|
+
'int1',
|
|
237
|
+
'year',
|
|
238
|
+
]);
|
|
239
|
+
const BIG_INT_NAMES = new Set([
|
|
240
|
+
'bigint',
|
|
241
|
+
'int8',
|
|
242
|
+
'big int',
|
|
243
|
+
'unsigned big int',
|
|
244
|
+
]);
|
|
245
|
+
const SERIAL_NAMES = {
|
|
246
|
+
serial: 'int',
|
|
247
|
+
serial4: 'int',
|
|
248
|
+
serial2: 'int',
|
|
249
|
+
smallserial: 'int',
|
|
250
|
+
bigserial: 'bigInt',
|
|
251
|
+
serial8: 'bigInt',
|
|
252
|
+
};
|
|
253
|
+
const FLOAT_NAMES = new Set([
|
|
254
|
+
'real',
|
|
255
|
+
'float',
|
|
256
|
+
'float4',
|
|
257
|
+
'float8',
|
|
258
|
+
'double',
|
|
259
|
+
'double precision',
|
|
260
|
+
'binary_float',
|
|
261
|
+
'binary_double',
|
|
262
|
+
]);
|
|
263
|
+
const DECIMAL_NAMES = new Set([
|
|
264
|
+
'numeric',
|
|
265
|
+
'decimal',
|
|
266
|
+
'dec',
|
|
267
|
+
'fixed',
|
|
268
|
+
'number',
|
|
269
|
+
]);
|
|
270
|
+
const STRING_NAMES = new Set([
|
|
271
|
+
'varchar',
|
|
272
|
+
'character varying',
|
|
273
|
+
'varying character',
|
|
274
|
+
'nvarchar',
|
|
275
|
+
'national varchar',
|
|
276
|
+
'national character varying',
|
|
277
|
+
'national char varying',
|
|
278
|
+
'nchar varying',
|
|
279
|
+
'varchar2',
|
|
280
|
+
'nvarchar2',
|
|
281
|
+
'bpchar',
|
|
282
|
+
'name',
|
|
283
|
+
'native character',
|
|
284
|
+
]);
|
|
285
|
+
const FIXED_CHAR_NAMES = new Set([
|
|
286
|
+
'char',
|
|
287
|
+
'character',
|
|
288
|
+
'nchar',
|
|
289
|
+
'national char',
|
|
290
|
+
'national character',
|
|
291
|
+
]);
|
|
292
|
+
const TEXT_NAMES = new Set([
|
|
293
|
+
'text',
|
|
294
|
+
'tinytext',
|
|
295
|
+
'mediumtext',
|
|
296
|
+
'longtext',
|
|
297
|
+
'ntext',
|
|
298
|
+
'clob',
|
|
299
|
+
'nclob',
|
|
300
|
+
'long',
|
|
301
|
+
'long varchar',
|
|
302
|
+
'citext',
|
|
303
|
+
'character large object',
|
|
304
|
+
'char large object',
|
|
305
|
+
'string',
|
|
306
|
+
]);
|
|
307
|
+
const DATE_TIME_NAMES = new Set([
|
|
308
|
+
'timestamp',
|
|
309
|
+
'timestamptz',
|
|
310
|
+
'datetime',
|
|
311
|
+
'datetime2',
|
|
312
|
+
'smalldatetime',
|
|
313
|
+
'datetimeoffset',
|
|
314
|
+
]);
|
|
315
|
+
const BYTES_NAMES = new Set([
|
|
316
|
+
'bytea',
|
|
317
|
+
'blob',
|
|
318
|
+
'tinyblob',
|
|
319
|
+
'mediumblob',
|
|
320
|
+
'longblob',
|
|
321
|
+
'binary',
|
|
322
|
+
'varbinary',
|
|
323
|
+
'binary varying',
|
|
324
|
+
'image',
|
|
325
|
+
'raw',
|
|
326
|
+
'long raw',
|
|
327
|
+
'long varbinary',
|
|
328
|
+
'binary large object',
|
|
329
|
+
]);
|
|
330
|
+
const RANGE_NAMES = {
|
|
331
|
+
int4range: 'int',
|
|
332
|
+
int8range: 'bigInt',
|
|
333
|
+
numrange: 'decimal',
|
|
334
|
+
daterange: 'date',
|
|
335
|
+
tsrange: 'dateTime',
|
|
336
|
+
tstzrange: 'dateTime',
|
|
337
|
+
};
|
|
338
|
+
/** SQLite assigns a type affinity from substrings of the declared type. */
|
|
339
|
+
function mapSqliteAffinity(name) {
|
|
340
|
+
if (name.includes('int')) {
|
|
341
|
+
return { type: 'int' };
|
|
342
|
+
}
|
|
343
|
+
if (name.includes('char') || name.includes('clob') || name.includes('text')) {
|
|
344
|
+
return { type: 'text' };
|
|
345
|
+
}
|
|
346
|
+
if (name.includes('blob')) {
|
|
347
|
+
return { type: 'bytes' };
|
|
348
|
+
}
|
|
349
|
+
if (name.includes('real') || name.includes('floa') || name.includes('doub')) {
|
|
350
|
+
return { type: 'float' };
|
|
351
|
+
}
|
|
352
|
+
return undefined;
|
|
353
|
+
}
|
|
354
|
+
/** Maps a column type to the shared model. Unknown names come back as `isUserType` (possibly an enum). */
|
|
355
|
+
export function mapType(raw, dialect) {
|
|
356
|
+
const name = raw.name;
|
|
357
|
+
const first = toInteger(raw.args[0]);
|
|
358
|
+
const second = toInteger(raw.args[1]);
|
|
359
|
+
if (raw.enumValues !== undefined) {
|
|
360
|
+
return { type: 'string' };
|
|
361
|
+
}
|
|
362
|
+
if (raw.setValues !== undefined) {
|
|
363
|
+
return { type: 'text' };
|
|
364
|
+
}
|
|
365
|
+
const serial = SERIAL_NAMES[name];
|
|
366
|
+
if (serial !== undefined && dialect !== 'sqlite') {
|
|
367
|
+
return {
|
|
368
|
+
type: dialect === 'mysql' && name === 'serial' ? 'bigInt' : serial,
|
|
369
|
+
autoIncrement: true,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
if (name === 'bool' || name === 'boolean') {
|
|
373
|
+
return { type: 'boolean' };
|
|
374
|
+
}
|
|
375
|
+
if (name === 'bit') {
|
|
376
|
+
if (raw.args.length === 0 || first === 1) {
|
|
377
|
+
return { type: 'boolean' };
|
|
378
|
+
}
|
|
379
|
+
return { type: 'unsupported', unsupported: true };
|
|
380
|
+
}
|
|
381
|
+
if (name === 'tinyint' && dialect === 'mysql' && first === 1) {
|
|
382
|
+
return { type: 'boolean' };
|
|
383
|
+
}
|
|
384
|
+
if (INT_NAMES.has(name)) {
|
|
385
|
+
return { type: 'int' };
|
|
386
|
+
}
|
|
387
|
+
if (BIG_INT_NAMES.has(name)) {
|
|
388
|
+
return { type: 'bigInt' };
|
|
389
|
+
}
|
|
390
|
+
if (FLOAT_NAMES.has(name)) {
|
|
391
|
+
return { type: 'float' };
|
|
392
|
+
}
|
|
393
|
+
if (DECIMAL_NAMES.has(name)) {
|
|
394
|
+
return {
|
|
395
|
+
type: 'decimal',
|
|
396
|
+
...(first === undefined ? {} : { maxDigits: first }),
|
|
397
|
+
...(first === undefined ? {} : { decimalPlaces: second ?? 0 }),
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
if (name === 'money' || name === 'smallmoney') {
|
|
401
|
+
if (dialect === 'sqlserver') {
|
|
402
|
+
return {
|
|
403
|
+
type: 'decimal',
|
|
404
|
+
maxDigits: name === 'money' ? 19 : 10,
|
|
405
|
+
decimalPlaces: 4,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
return { type: 'unsupported', unsupported: true };
|
|
409
|
+
}
|
|
410
|
+
if (STRING_NAMES.has(name)) {
|
|
411
|
+
if (raw.args[0]?.toLowerCase() === 'max' || first === undefined) {
|
|
412
|
+
return { type: 'text' };
|
|
413
|
+
}
|
|
414
|
+
return { type: 'string', maxLength: first };
|
|
415
|
+
}
|
|
416
|
+
if (FIXED_CHAR_NAMES.has(name)) {
|
|
417
|
+
if (raw.args[0]?.toLowerCase() === 'max') {
|
|
418
|
+
return { type: 'text' };
|
|
419
|
+
}
|
|
420
|
+
if (first === undefined) {
|
|
421
|
+
return dialect === 'sqlite'
|
|
422
|
+
? { type: 'text' }
|
|
423
|
+
: { type: 'string', maxLength: 1 };
|
|
424
|
+
}
|
|
425
|
+
return { type: 'string', maxLength: first };
|
|
426
|
+
}
|
|
427
|
+
if (TEXT_NAMES.has(name)) {
|
|
428
|
+
return { type: 'text' };
|
|
429
|
+
}
|
|
430
|
+
if (name === 'json' || name === 'jsonb') {
|
|
431
|
+
return { type: 'json' };
|
|
432
|
+
}
|
|
433
|
+
if (name === 'timestamp' && dialect === 'sqlserver') {
|
|
434
|
+
return { type: 'unsupported', unsupported: true };
|
|
435
|
+
}
|
|
436
|
+
if (name === 'rowversion') {
|
|
437
|
+
return { type: 'unsupported', unsupported: true };
|
|
438
|
+
}
|
|
439
|
+
if (DATE_TIME_NAMES.has(name)) {
|
|
440
|
+
return { type: 'dateTime' };
|
|
441
|
+
}
|
|
442
|
+
if (name === 'date') {
|
|
443
|
+
return { type: 'date' };
|
|
444
|
+
}
|
|
445
|
+
if (name === 'time' || name === 'timetz') {
|
|
446
|
+
return { type: 'time' };
|
|
447
|
+
}
|
|
448
|
+
if (name === 'interval' || name.startsWith('interval ')) {
|
|
449
|
+
return { type: 'duration' };
|
|
450
|
+
}
|
|
451
|
+
if (name === 'uuid' || name === 'uniqueidentifier') {
|
|
452
|
+
return { type: 'uuid' };
|
|
453
|
+
}
|
|
454
|
+
if (BYTES_NAMES.has(name)) {
|
|
455
|
+
return { type: 'bytes' };
|
|
456
|
+
}
|
|
457
|
+
if (name === 'inet' || name === 'cidr') {
|
|
458
|
+
return { type: 'ipAddress' };
|
|
459
|
+
}
|
|
460
|
+
if (name === 'hstore') {
|
|
461
|
+
return { type: 'hstore' };
|
|
462
|
+
}
|
|
463
|
+
const rangeOf = RANGE_NAMES[name];
|
|
464
|
+
if (rangeOf !== undefined) {
|
|
465
|
+
return { type: 'range', rangeOf };
|
|
466
|
+
}
|
|
467
|
+
if (dialect === 'sqlite') {
|
|
468
|
+
const affinity = mapSqliteAffinity(name);
|
|
469
|
+
if (affinity !== undefined) {
|
|
470
|
+
return affinity;
|
|
471
|
+
}
|
|
472
|
+
if (name === '') {
|
|
473
|
+
return { type: 'bytes' };
|
|
474
|
+
}
|
|
475
|
+
return { type: 'decimal' };
|
|
476
|
+
}
|
|
477
|
+
return { type: 'unsupported', unsupported: true, isUserType: true };
|
|
478
|
+
}
|
|
479
|
+
// ---------------------------------------------------------------------------
|
|
480
|
+
// Defaults
|
|
481
|
+
// ---------------------------------------------------------------------------
|
|
482
|
+
const NOW_FUNCTIONS = new Set([
|
|
483
|
+
'CURRENT_TIMESTAMP',
|
|
484
|
+
'CURRENT_DATE',
|
|
485
|
+
'CURRENT_TIME',
|
|
486
|
+
'LOCALTIMESTAMP',
|
|
487
|
+
'LOCALTIME',
|
|
488
|
+
'NOW',
|
|
489
|
+
'GETDATE',
|
|
490
|
+
'GETUTCDATE',
|
|
491
|
+
'SYSDATETIME',
|
|
492
|
+
'SYSUTCDATETIME',
|
|
493
|
+
'SYSDATETIMEOFFSET',
|
|
494
|
+
'TRANSACTION_TIMESTAMP',
|
|
495
|
+
'STATEMENT_TIMESTAMP',
|
|
496
|
+
'CLOCK_TIMESTAMP',
|
|
497
|
+
]);
|
|
498
|
+
const UUID_FUNCTIONS = new Set([
|
|
499
|
+
'GEN_RANDOM_UUID',
|
|
500
|
+
'UUID_GENERATE_V4',
|
|
501
|
+
'UUID_GENERATE_V1',
|
|
502
|
+
'UUID_GENERATE_V1MC',
|
|
503
|
+
'NEWID',
|
|
504
|
+
'NEWSEQUENTIALID',
|
|
505
|
+
'UUID',
|
|
506
|
+
'UUID_V4',
|
|
507
|
+
'UUIDV4',
|
|
508
|
+
]);
|
|
509
|
+
const SQLITE_NOW_FUNCTIONS = new Set([
|
|
510
|
+
'DATETIME',
|
|
511
|
+
'DATE',
|
|
512
|
+
'TIME',
|
|
513
|
+
'JULIANDAY',
|
|
514
|
+
]);
|
|
515
|
+
/** Layers of wrapping parentheses removed from a default; more than this is never a real default. */
|
|
516
|
+
const MAX_STRIPPED_LAYERS = 32;
|
|
517
|
+
/** Removes parentheses that wrap the whole expression: `((0))` becomes `0`. */
|
|
518
|
+
function stripOuterParens(tokens) {
|
|
519
|
+
let from = 0;
|
|
520
|
+
let to = tokens.length;
|
|
521
|
+
for (let layer = 0; layer < MAX_STRIPPED_LAYERS; layer += 1) {
|
|
522
|
+
const first = tokens[from];
|
|
523
|
+
const last = tokens[to - 1];
|
|
524
|
+
if (to - from < 2 ||
|
|
525
|
+
first === undefined ||
|
|
526
|
+
last === undefined ||
|
|
527
|
+
first.text !== '(' ||
|
|
528
|
+
first.kind !== 'punct' ||
|
|
529
|
+
last.text !== ')' ||
|
|
530
|
+
last.kind !== 'punct') {
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
let depth = 0;
|
|
534
|
+
let wrapsAll = true;
|
|
535
|
+
for (let at = from; at < to; at += 1) {
|
|
536
|
+
const token = tokens[at];
|
|
537
|
+
if (token?.kind === 'punct') {
|
|
538
|
+
if (token.text === '(') {
|
|
539
|
+
depth += 1;
|
|
540
|
+
}
|
|
541
|
+
else if (token.text === ')') {
|
|
542
|
+
depth -= 1;
|
|
543
|
+
if (depth === 0 && at < to - 1) {
|
|
544
|
+
wrapsAll = false;
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
if (!wrapsAll) {
|
|
551
|
+
break;
|
|
552
|
+
}
|
|
553
|
+
from += 1;
|
|
554
|
+
to -= 1;
|
|
555
|
+
}
|
|
556
|
+
return from === 0 && to === tokens.length ? tokens : tokens.slice(from, to);
|
|
557
|
+
}
|
|
558
|
+
/** True when what follows a literal is only a type cast: `::character varying`, `::text[]`, `::"public"."x"`. */
|
|
559
|
+
function isCastOnly(tokens) {
|
|
560
|
+
const first = tokens[0];
|
|
561
|
+
if (first === undefined) {
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
if (first.kind !== 'punct' || first.text !== '::') {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
return tokens.every((token) => token.kind === 'word' ||
|
|
568
|
+
token.kind === 'ident' ||
|
|
569
|
+
token.kind === 'number' ||
|
|
570
|
+
(token.kind === 'punct' && /^(::|\(|\)|,|\.|\[|\])$/.test(token.text)));
|
|
571
|
+
}
|
|
572
|
+
function numberLiteral(text) {
|
|
573
|
+
const value = Number(text);
|
|
574
|
+
if (Number.isFinite(value) &&
|
|
575
|
+
String(value) !== '' &&
|
|
576
|
+
/^[+-]?\d+(\.\d+)?$/.test(text)) {
|
|
577
|
+
const normalized = text
|
|
578
|
+
.replace(/^\+/, '')
|
|
579
|
+
.replace(/^(-?)0+(?=\d)/, '$1');
|
|
580
|
+
const canonical = String(value);
|
|
581
|
+
if (canonical === normalized ||
|
|
582
|
+
(normalized.includes('.') &&
|
|
583
|
+
canonical === normalized.replace(/0+$/, '').replace(/\.$/, ''))) {
|
|
584
|
+
return value;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return text;
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Reads a DEFAULT expression. Returns `null` for `DEFAULT NULL` (no default at all), and `undefined` for an
|
|
591
|
+
* expression the model cannot describe, which the caller keeps as a raw database expression.
|
|
592
|
+
*/
|
|
593
|
+
export function interpretDefault(allTokens, target, dialect) {
|
|
594
|
+
const tokens = stripOuterParens(allTokens);
|
|
595
|
+
const first = tokens[0];
|
|
596
|
+
if (first === undefined) {
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
const stringish = target.type === 'string' ||
|
|
600
|
+
target.type === 'text' ||
|
|
601
|
+
target.type === 'uuid' ||
|
|
602
|
+
target.type === 'json' ||
|
|
603
|
+
target.type === 'dateTime' ||
|
|
604
|
+
target.type === 'date' ||
|
|
605
|
+
target.type === 'time' ||
|
|
606
|
+
target.type === 'duration' ||
|
|
607
|
+
target.type === 'ipAddress';
|
|
608
|
+
if (first.kind === 'word' && first.up === 'NULL' && tokens.length === 1) {
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
// Booleans and numbers.
|
|
612
|
+
if (first.kind === 'word' && (first.up === 'TRUE' || first.up === 'FALSE')) {
|
|
613
|
+
if (tokens.length === 1 && !target.isArray) {
|
|
614
|
+
if (target.type === 'boolean') {
|
|
615
|
+
return { kind: 'literal', value: first.up === 'TRUE' };
|
|
616
|
+
}
|
|
617
|
+
if (target.type === 'int' || target.type === 'bigInt') {
|
|
618
|
+
return { kind: 'literal', value: first.up === 'TRUE' ? 1 : 0 };
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return undefined;
|
|
622
|
+
}
|
|
623
|
+
let signed = '';
|
|
624
|
+
let numberToken;
|
|
625
|
+
if (first.kind === 'number' && tokens.length === 1) {
|
|
626
|
+
numberToken = first;
|
|
627
|
+
}
|
|
628
|
+
else if (first.kind === 'punct' &&
|
|
629
|
+
(first.text === '-' || first.text === '+') &&
|
|
630
|
+
tokens.length === 2 &&
|
|
631
|
+
tokens[1]?.kind === 'number') {
|
|
632
|
+
signed = first.text === '-' ? '-' : '';
|
|
633
|
+
numberToken = tokens[1];
|
|
634
|
+
}
|
|
635
|
+
if (numberToken !== undefined && !target.isArray) {
|
|
636
|
+
const text = `${signed}${numberToken.value}`;
|
|
637
|
+
if (target.type === 'boolean') {
|
|
638
|
+
if (text === '1') {
|
|
639
|
+
return { kind: 'literal', value: true };
|
|
640
|
+
}
|
|
641
|
+
if (text === '0') {
|
|
642
|
+
return { kind: 'literal', value: false };
|
|
643
|
+
}
|
|
644
|
+
return undefined;
|
|
645
|
+
}
|
|
646
|
+
if (target.type === 'int' ||
|
|
647
|
+
target.type === 'bigInt' ||
|
|
648
|
+
target.type === 'float' ||
|
|
649
|
+
target.type === 'decimal') {
|
|
650
|
+
return { kind: 'literal', value: numberLiteral(text) };
|
|
651
|
+
}
|
|
652
|
+
if (stringish && target.enumType === undefined) {
|
|
653
|
+
return { kind: 'literal', value: text };
|
|
654
|
+
}
|
|
655
|
+
return undefined;
|
|
656
|
+
}
|
|
657
|
+
// String literals, optionally cast.
|
|
658
|
+
if (first.kind === 'string' && !target.isArray) {
|
|
659
|
+
const rest = tokens.slice(1);
|
|
660
|
+
if (isCastOnly(rest)) {
|
|
661
|
+
const value = first.value;
|
|
662
|
+
if (target.enumType !== undefined) {
|
|
663
|
+
const member = target.enumType.values.find((candidate) => candidate.dbValue === value);
|
|
664
|
+
if (member !== undefined) {
|
|
665
|
+
return { kind: 'enumValue', value: member.name };
|
|
666
|
+
}
|
|
667
|
+
return undefined;
|
|
668
|
+
}
|
|
669
|
+
if (target.type === 'boolean') {
|
|
670
|
+
const lowered = value.toLowerCase();
|
|
671
|
+
if (['t', 'true', '1', 'y', 'yes', 'on'].includes(lowered)) {
|
|
672
|
+
return { kind: 'literal', value: true };
|
|
673
|
+
}
|
|
674
|
+
if (['f', 'false', '0', 'n', 'no', 'off'].includes(lowered)) {
|
|
675
|
+
return { kind: 'literal', value: false };
|
|
676
|
+
}
|
|
677
|
+
return undefined;
|
|
678
|
+
}
|
|
679
|
+
if (target.type === 'int' ||
|
|
680
|
+
target.type === 'bigInt' ||
|
|
681
|
+
target.type === 'float' ||
|
|
682
|
+
target.type === 'decimal') {
|
|
683
|
+
if (/^[+-]?\d+(\.\d+)?$/.test(value.trim())) {
|
|
684
|
+
return { kind: 'literal', value: numberLiteral(value.trim()) };
|
|
685
|
+
}
|
|
686
|
+
return undefined;
|
|
687
|
+
}
|
|
688
|
+
if ((target.type === 'dateTime' ||
|
|
689
|
+
target.type === 'date' ||
|
|
690
|
+
target.type === 'time') &&
|
|
691
|
+
value.toLowerCase() === 'now') {
|
|
692
|
+
return { kind: 'now' };
|
|
693
|
+
}
|
|
694
|
+
if (stringish) {
|
|
695
|
+
return { kind: 'literal', value };
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return undefined;
|
|
699
|
+
}
|
|
700
|
+
// Function calls and keywords.
|
|
701
|
+
if (first.kind === 'word') {
|
|
702
|
+
const callGroup = tokens.length === 1 ||
|
|
703
|
+
(tokens[1]?.kind === 'punct' &&
|
|
704
|
+
tokens[1].text === '(' &&
|
|
705
|
+
tokens[tokens.length - 1]?.text === ')' &&
|
|
706
|
+
tokens[tokens.length - 1]?.kind === 'punct');
|
|
707
|
+
if (callGroup && NOW_FUNCTIONS.has(first.up)) {
|
|
708
|
+
if (target.type === 'dateTime' ||
|
|
709
|
+
target.type === 'date' ||
|
|
710
|
+
target.type === 'time') {
|
|
711
|
+
return { kind: 'now' };
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if (callGroup &&
|
|
715
|
+
UUID_FUNCTIONS.has(first.up) &&
|
|
716
|
+
(target.type === 'uuid' || target.type === 'string')) {
|
|
717
|
+
return { kind: 'uuid' };
|
|
718
|
+
}
|
|
719
|
+
if (callGroup &&
|
|
720
|
+
first.up === 'NEXTVAL' &&
|
|
721
|
+
(target.type === 'int' || target.type === 'bigInt')) {
|
|
722
|
+
return { kind: 'autoIncrement' };
|
|
723
|
+
}
|
|
724
|
+
if (dialect === 'sqlite' &&
|
|
725
|
+
SQLITE_NOW_FUNCTIONS.has(first.up) &&
|
|
726
|
+
tokens[1]?.text === '(' &&
|
|
727
|
+
tokens[2]?.kind === 'string' &&
|
|
728
|
+
tokens[2].value.toLowerCase() === 'now' &&
|
|
729
|
+
tokens.slice(3).every((token) => token.kind !== 'word') &&
|
|
730
|
+
(target.type === 'dateTime' ||
|
|
731
|
+
target.type === 'date' ||
|
|
732
|
+
target.type === 'time')) {
|
|
733
|
+
return { kind: 'now' };
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
return undefined;
|
|
737
|
+
}
|
|
738
|
+
/** The raw expression of a default that is kept as written (parentheses around the whole text removed). */
|
|
739
|
+
export function defaultExpression(allTokens, text) {
|
|
740
|
+
return sourceOf(text, stripOuterParens(allTokens));
|
|
741
|
+
}
|
|
742
|
+
//# sourceMappingURL=sqlTypes.js.map
|