uql-orm 0.34.1 → 0.36.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 +1 -1
- package/dist/migrate/builder/expressions.d.ts +51 -72
- package/dist/migrate/builder/expressions.js +89 -118
- package/dist/migrate/builder/tableBuilder.d.ts +1 -0
- package/dist/migrate/builder/tableBuilder.js +6 -3
- package/dist/migrate/migrator.d.ts +4 -4
- package/dist/migrate/migrator.js +4 -4
- package/dist/migrate/schemaGenerator.d.ts +3 -4
- package/dist/migrate/schemaGenerator.js +15 -16
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,86 +1,65 @@
|
|
|
1
|
+
import type { AbstractSqlDialect } from '../../dialect/index.js';
|
|
2
|
+
import type { SqlDialectName } from '../../type/index.js';
|
|
1
3
|
/**
|
|
2
|
-
* SQL
|
|
3
|
-
*
|
|
4
|
-
* Type-safe SQL expressions for default values and other uses.
|
|
5
|
-
* Provides a `t` helper with common expressions.
|
|
4
|
+
* A {@link ColumnSchema.defaultValue} that is SQL rather than a literal. Kinds are symbolic: the
|
|
5
|
+
* dialect names the spelling and {@link formatDefaultValue} renders one at DDL time.
|
|
6
6
|
*/
|
|
7
|
+
export type SqlExpressionKind = 'now' | 'currentDate' | 'currentTime' | 'uuid' | 'uuidv7' | 'onUpdateNow' | 'raw';
|
|
8
|
+
/** Every kind's DDL spelling, `null` where an engine has none. `raw` carries its own text instead. */
|
|
9
|
+
export type SqlExpressionMap = Readonly<Record<Exclude<SqlExpressionKind, 'raw'>, string | null>>;
|
|
10
|
+
/** How one engine renders a DDL default. A new per-dialect rule is a field here, not a second table. */
|
|
11
|
+
export type DialectDefaults = {
|
|
12
|
+
/** Spelling of each kind, `null` where the engine has none. */
|
|
13
|
+
readonly expressions: SqlExpressionMap;
|
|
14
|
+
/** Column types whose `DEFAULT` this engine takes only as a parenthesized expression. */
|
|
15
|
+
readonly wrapTypes?: RegExp;
|
|
16
|
+
};
|
|
7
17
|
/**
|
|
8
|
-
*
|
|
18
|
+
* Looked up by name rather than carried on the dialect, which keeps DDL data out of the query
|
|
19
|
+
* bundle - the same split that keeps `CANONICAL_TO_SQL` in `schema/canonicalType.ts`. `uuidv7()` is
|
|
20
|
+
* Postgres 18+ and `UUID_v7()` MariaDB 11.7+; a server below those rejects it itself, the version
|
|
21
|
+
* not being knowable here.
|
|
22
|
+
*/
|
|
23
|
+
export declare const DIALECT_DEFAULTS: Readonly<Record<SqlDialectName, DialectDefaults>>;
|
|
24
|
+
/**
|
|
25
|
+
* A DDL default that is SQL rather than a literal. A class, not a plain object, so a JSON default
|
|
26
|
+
* cannot masquerade as one: `defaultValue` accepts `unknown`.
|
|
9
27
|
*/
|
|
10
28
|
export declare class SqlExpression {
|
|
11
|
-
readonly
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* Check if a value is a SQL expression.
|
|
16
|
-
*/
|
|
29
|
+
readonly kind: SqlExpressionKind;
|
|
30
|
+
readonly sql?: string | undefined;
|
|
31
|
+
/** `sql` is set only for the `raw` kind, which carries its own text verbatim. */
|
|
32
|
+
constructor(kind: SqlExpressionKind, sql?: string | undefined);
|
|
17
33
|
static isExpression(value: unknown): value is SqlExpression;
|
|
18
34
|
}
|
|
19
35
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
36
|
+
* Symbolic DDL defaults. Each builds a token the dialect spells at DDL time, so `expr.uuid()` is
|
|
37
|
+
* `gen_random_uuid()` on Postgres and `UUID()` on MySQL from one migration.
|
|
38
|
+
*
|
|
39
|
+
* Use in migrations: `t.timestamp('createdAt', { defaultValue: expr.now() })`
|
|
22
40
|
*/
|
|
23
|
-
export declare const
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Current time (CURRENT_TIME).
|
|
34
|
-
*/
|
|
35
|
-
currentTime(): SqlExpression;
|
|
36
|
-
/**
|
|
37
|
-
* NULL value.
|
|
38
|
-
*/
|
|
39
|
-
null(): SqlExpression;
|
|
40
|
-
/**
|
|
41
|
-
* Boolean true.
|
|
42
|
-
*/
|
|
43
|
-
true(): SqlExpression;
|
|
44
|
-
/**
|
|
45
|
-
* Boolean false.
|
|
46
|
-
*/
|
|
47
|
-
false(): SqlExpression;
|
|
48
|
-
/**
|
|
49
|
-
* Generate UUID (Postgres: gen_random_uuid(), MySQL: UUID()).
|
|
50
|
-
*/
|
|
51
|
-
uuid(): SqlExpression;
|
|
52
|
-
/**
|
|
53
|
-
* Auto-generate UUID for MySQL.
|
|
54
|
-
*/
|
|
55
|
-
mysqlUuid(): SqlExpression;
|
|
56
|
-
/**
|
|
57
|
-
* Raw SQL expression.
|
|
58
|
-
* Use for custom expressions not covered by helpers.
|
|
59
|
-
*/
|
|
60
|
-
raw(sql: string): SqlExpression;
|
|
61
|
-
/**
|
|
62
|
-
* String literal with proper escaping.
|
|
63
|
-
*/
|
|
64
|
-
literal(value: string): SqlExpression;
|
|
65
|
-
/**
|
|
66
|
-
* Numeric literal.
|
|
67
|
-
*/
|
|
68
|
-
number(value: number): SqlExpression;
|
|
69
|
-
/**
|
|
70
|
-
* Empty JSON object.
|
|
71
|
-
*/
|
|
72
|
-
emptyObject(): SqlExpression;
|
|
73
|
-
/**
|
|
74
|
-
* Empty JSON array.
|
|
75
|
-
*/
|
|
76
|
-
emptyArray(): SqlExpression;
|
|
41
|
+
export declare const expr: {
|
|
42
|
+
/** Current timestamp. */
|
|
43
|
+
now: () => SqlExpression;
|
|
44
|
+
/** Current date. */
|
|
45
|
+
currentDate: () => SqlExpression;
|
|
46
|
+
/** Current time. */
|
|
47
|
+
currentTime: () => SqlExpression;
|
|
48
|
+
/** Generated UUID. SQLite has no built-in one and throws; pass `expr.raw` there. */
|
|
49
|
+
uuid: () => SqlExpression;
|
|
77
50
|
/**
|
|
78
|
-
*
|
|
51
|
+
* Time-ordered UUID, which indexes far better than a random one as a key. Postgres 18+ and
|
|
52
|
+
* MariaDB 11.7+ only; MySQL, SQLite and CockroachDB have no such function and throw.
|
|
79
53
|
*/
|
|
80
|
-
|
|
54
|
+
uuidv7: () => SqlExpression;
|
|
55
|
+
/** MySQL's `ON UPDATE CURRENT_TIMESTAMP`. Throws on dialects without it. */
|
|
56
|
+
onUpdateNow: () => SqlExpression;
|
|
57
|
+
/** SQL taken verbatim, for anything the kinds above do not cover. Not portable by definition. */
|
|
58
|
+
raw: (sql: string) => SqlExpression;
|
|
81
59
|
};
|
|
82
60
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
61
|
+
* The one place a DDL default becomes SQL: an expression through the dialect's own spelling, anything
|
|
62
|
+
* else as a literal, so `true` is `1` where booleans are integers. `columnType` decides only whether
|
|
63
|
+
* the result needs wrapping, which MySQL demands on its large types whatever the value.
|
|
85
64
|
*/
|
|
86
|
-
export declare function formatDefaultValue(value: unknown): string;
|
|
65
|
+
export declare function formatDefaultValue(value: unknown, dialect: AbstractSqlDialect, columnType?: string): string;
|
|
@@ -1,149 +1,120 @@
|
|
|
1
|
+
const ANSI = {
|
|
2
|
+
now: 'CURRENT_TIMESTAMP',
|
|
3
|
+
currentDate: 'CURRENT_DATE',
|
|
4
|
+
currentTime: 'CURRENT_TIME',
|
|
5
|
+
uuid: null,
|
|
6
|
+
uuidv7: null,
|
|
7
|
+
onUpdateNow: null,
|
|
8
|
+
};
|
|
9
|
+
const PG = { ...ANSI, uuid: 'gen_random_uuid()' };
|
|
10
|
+
const MYSQL = {
|
|
11
|
+
...ANSI,
|
|
12
|
+
uuid: 'UUID()',
|
|
13
|
+
onUpdateNow: 'CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
|
|
14
|
+
};
|
|
15
|
+
/** MySQL 8.0.13+ rejects `DEFAULT 'x'` on these but accepts `DEFAULT ('x')`, whatever the value. */
|
|
16
|
+
const MYSQL_LARGE_TYPES = /^\s*(TINY|MEDIUM|LONG)?(TEXT|BLOB)|^\s*(JSON|GEOMETRY)\b/i;
|
|
1
17
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
18
|
+
* Looked up by name rather than carried on the dialect, which keeps DDL data out of the query
|
|
19
|
+
* bundle - the same split that keeps `CANONICAL_TO_SQL` in `schema/canonicalType.ts`. `uuidv7()` is
|
|
20
|
+
* Postgres 18+ and `UUID_v7()` MariaDB 11.7+; a server below those rejects it itself, the version
|
|
21
|
+
* not being knowable here.
|
|
6
22
|
*/
|
|
23
|
+
export const DIALECT_DEFAULTS = {
|
|
24
|
+
postgres: { expressions: { ...PG, uuidv7: 'uuidv7()' } },
|
|
25
|
+
cockroachdb: { expressions: PG },
|
|
26
|
+
mysql: { expressions: MYSQL, wrapTypes: MYSQL_LARGE_TYPES },
|
|
27
|
+
mariadb: { expressions: { ...MYSQL, uuidv7: 'UUID_v7()' }, wrapTypes: MYSQL_LARGE_TYPES },
|
|
28
|
+
sqlite: { expressions: ANSI },
|
|
29
|
+
};
|
|
7
30
|
/**
|
|
8
|
-
*
|
|
31
|
+
* A DDL default that is SQL rather than a literal. A class, not a plain object, so a JSON default
|
|
32
|
+
* cannot masquerade as one: `defaultValue` accepts `unknown`.
|
|
9
33
|
*/
|
|
10
34
|
export class SqlExpression {
|
|
35
|
+
kind;
|
|
11
36
|
sql;
|
|
12
|
-
|
|
37
|
+
/** `sql` is set only for the `raw` kind, which carries its own text verbatim. */
|
|
38
|
+
constructor(kind, sql) {
|
|
39
|
+
this.kind = kind;
|
|
13
40
|
this.sql = sql;
|
|
14
41
|
}
|
|
15
|
-
toString() {
|
|
16
|
-
return this.sql;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Check if a value is a SQL expression.
|
|
20
|
-
*/
|
|
21
42
|
static isExpression(value) {
|
|
22
43
|
return value instanceof SqlExpression;
|
|
23
44
|
}
|
|
24
45
|
}
|
|
25
46
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
47
|
+
* Symbolic DDL defaults. Each builds a token the dialect spells at DDL time, so `expr.uuid()` is
|
|
48
|
+
* `gen_random_uuid()` on Postgres and `UUID()` on MySQL from one migration.
|
|
49
|
+
*
|
|
50
|
+
* Use in migrations: `t.timestamp('createdAt', { defaultValue: expr.now() })`
|
|
28
51
|
*/
|
|
29
|
-
export const
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
*/
|
|
39
|
-
currentDate() {
|
|
40
|
-
return new SqlExpression('CURRENT_DATE');
|
|
41
|
-
},
|
|
42
|
-
/**
|
|
43
|
-
* Current time (CURRENT_TIME).
|
|
44
|
-
*/
|
|
45
|
-
currentTime() {
|
|
46
|
-
return new SqlExpression('CURRENT_TIME');
|
|
47
|
-
},
|
|
48
|
-
/**
|
|
49
|
-
* NULL value.
|
|
50
|
-
*/
|
|
51
|
-
null() {
|
|
52
|
-
return new SqlExpression('NULL');
|
|
53
|
-
},
|
|
54
|
-
/**
|
|
55
|
-
* Boolean true.
|
|
56
|
-
*/
|
|
57
|
-
true() {
|
|
58
|
-
return new SqlExpression('TRUE');
|
|
59
|
-
},
|
|
60
|
-
/**
|
|
61
|
-
* Boolean false.
|
|
62
|
-
*/
|
|
63
|
-
false() {
|
|
64
|
-
return new SqlExpression('FALSE');
|
|
65
|
-
},
|
|
66
|
-
/**
|
|
67
|
-
* Generate UUID (Postgres: gen_random_uuid(), MySQL: UUID()).
|
|
68
|
-
*/
|
|
69
|
-
uuid() {
|
|
70
|
-
return new SqlExpression('gen_random_uuid()');
|
|
71
|
-
},
|
|
52
|
+
export const expr = {
|
|
53
|
+
/** Current timestamp. */
|
|
54
|
+
now: () => new SqlExpression('now'),
|
|
55
|
+
/** Current date. */
|
|
56
|
+
currentDate: () => new SqlExpression('currentDate'),
|
|
57
|
+
/** Current time. */
|
|
58
|
+
currentTime: () => new SqlExpression('currentTime'),
|
|
59
|
+
/** Generated UUID. SQLite has no built-in one and throws; pass `expr.raw` there. */
|
|
60
|
+
uuid: () => new SqlExpression('uuid'),
|
|
72
61
|
/**
|
|
73
|
-
*
|
|
62
|
+
* Time-ordered UUID, which indexes far better than a random one as a key. Postgres 18+ and
|
|
63
|
+
* MariaDB 11.7+ only; MySQL, SQLite and CockroachDB have no such function and throw.
|
|
74
64
|
*/
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
|
|
80
|
-
* Use for custom expressions not covered by helpers.
|
|
81
|
-
*/
|
|
82
|
-
raw(sql) {
|
|
83
|
-
return new SqlExpression(sql);
|
|
84
|
-
},
|
|
85
|
-
/**
|
|
86
|
-
* String literal with proper escaping.
|
|
87
|
-
*/
|
|
88
|
-
literal(value) {
|
|
89
|
-
// Escape single quotes by doubling them
|
|
90
|
-
const escaped = value.replace(/'/g, "''");
|
|
91
|
-
return new SqlExpression(`'${escaped}'`);
|
|
92
|
-
},
|
|
93
|
-
/**
|
|
94
|
-
* Numeric literal.
|
|
95
|
-
*/
|
|
96
|
-
number(value) {
|
|
97
|
-
return new SqlExpression(String(value));
|
|
98
|
-
},
|
|
99
|
-
/**
|
|
100
|
-
* Empty JSON object.
|
|
101
|
-
*/
|
|
102
|
-
emptyObject() {
|
|
103
|
-
return new SqlExpression("'{}'::jsonb");
|
|
104
|
-
},
|
|
105
|
-
/**
|
|
106
|
-
* Empty JSON array.
|
|
107
|
-
*/
|
|
108
|
-
emptyArray() {
|
|
109
|
-
return new SqlExpression("'[]'::jsonb");
|
|
110
|
-
},
|
|
111
|
-
/**
|
|
112
|
-
* MySQL: ON UPDATE CURRENT_TIMESTAMP.
|
|
113
|
-
*/
|
|
114
|
-
onUpdateNow() {
|
|
115
|
-
return new SqlExpression('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
|
|
116
|
-
},
|
|
65
|
+
uuidv7: () => new SqlExpression('uuidv7'),
|
|
66
|
+
/** MySQL's `ON UPDATE CURRENT_TIMESTAMP`. Throws on dialects without it. */
|
|
67
|
+
onUpdateNow: () => new SqlExpression('onUpdateNow'),
|
|
68
|
+
/** SQL taken verbatim, for anything the kinds above do not cover. Not portable by definition. */
|
|
69
|
+
raw: (sql) => new SqlExpression('raw', sql),
|
|
117
70
|
};
|
|
118
71
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
72
|
+
* The one place a DDL default becomes SQL: an expression through the dialect's own spelling, anything
|
|
73
|
+
* else as a literal, so `true` is `1` where booleans are integers. `columnType` decides only whether
|
|
74
|
+
* the result needs wrapping, which MySQL demands on its large types whatever the value.
|
|
75
|
+
*/
|
|
76
|
+
export function formatDefaultValue(value, dialect, columnType) {
|
|
77
|
+
const sql = defaultLiteral(value, dialect);
|
|
78
|
+
const { wrapTypes } = DIALECT_DEFAULTS[dialect.dialectName];
|
|
79
|
+
return columnType !== undefined && wrapTypes?.test(columnType) ? `(${sql})` : sql;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Quoting is the dialect's `escape`, so a backslash in a default is escaped the way the engine reads
|
|
83
|
+
* it - MySQL takes `'a\b'` as a backspace where Postgres takes it literally. Only the cases `escape`
|
|
84
|
+
* cannot serve stay here: a boolean is `1` where booleans are integers, and a plain object or array
|
|
85
|
+
* is JSON rather than the throw and the IN-list `escape` gives them.
|
|
121
86
|
*/
|
|
122
|
-
|
|
87
|
+
function defaultLiteral(value, dialect) {
|
|
123
88
|
if (value === undefined || value === null) {
|
|
124
89
|
return 'NULL';
|
|
125
90
|
}
|
|
126
91
|
if (SqlExpression.isExpression(value)) {
|
|
127
|
-
return value
|
|
128
|
-
}
|
|
129
|
-
if (typeof value === 'string') {
|
|
130
|
-
// Escape single quotes for string literals
|
|
131
|
-
const escaped = value.replace(/'/g, "''");
|
|
132
|
-
return `'${escaped}'`;
|
|
133
|
-
}
|
|
134
|
-
if (typeof value === 'number') {
|
|
135
|
-
return String(value);
|
|
92
|
+
return expressionSql(value, dialect);
|
|
136
93
|
}
|
|
137
94
|
if (typeof value === 'boolean') {
|
|
138
|
-
return value ? 'TRUE' : 'FALSE';
|
|
95
|
+
return dialect.booleanLiteral === 'native' ? (value ? 'TRUE' : 'FALSE') : value ? '1' : '0';
|
|
139
96
|
}
|
|
140
97
|
if (value instanceof Date) {
|
|
141
|
-
return
|
|
98
|
+
return dialect.escape(ddlTimestamp(value));
|
|
142
99
|
}
|
|
143
100
|
if (typeof value === 'object') {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
101
|
+
return dialect.escape(JSON.stringify(value));
|
|
102
|
+
}
|
|
103
|
+
return dialect.escape(value);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* `YYYY-MM-DD HH:mm:ss.SSS` in UTC. Not `toISOString`, whose `T` and `Z` MySQL rejects outright
|
|
107
|
+
* ("Invalid default value"), and not `escape`'s local-time form, which would make the DDL depend on
|
|
108
|
+
* the machine that generated it.
|
|
109
|
+
*/
|
|
110
|
+
function ddlTimestamp(date) {
|
|
111
|
+
return date.toISOString().replace('T', ' ').replace('Z', '');
|
|
112
|
+
}
|
|
113
|
+
function expressionSql(expression, dialect) {
|
|
114
|
+
const { expressions } = DIALECT_DEFAULTS[dialect.dialectName];
|
|
115
|
+
const sql = expression.kind === 'raw' ? expression.sql : expressions[expression.kind];
|
|
116
|
+
if (sql == null) {
|
|
117
|
+
throw new TypeError(`${dialect.dialectName} has no '${expression.kind}' default; pass expr.raw(...) with SQL this engine accepts`);
|
|
147
118
|
}
|
|
148
|
-
return
|
|
119
|
+
return sql;
|
|
149
120
|
}
|
|
@@ -40,6 +40,7 @@ export declare class TableBuilder implements ITableBuilder {
|
|
|
40
40
|
private add;
|
|
41
41
|
createdAt(): IColumnBuilder;
|
|
42
42
|
updatedAt(): IColumnBuilder;
|
|
43
|
+
private timestampNow;
|
|
43
44
|
timestamps(): void;
|
|
44
45
|
primaryKey(columns: string[]): this;
|
|
45
46
|
unique(columns: readonly IndexColumnInput[], options?: string | IndexOptions): this;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { normalizeIndexColumn } from '../../util/index.js';
|
|
7
7
|
import { derivedIndexName } from '../../util/sql.util.js';
|
|
8
8
|
import { ColumnBuilder } from './columnBuilder.js';
|
|
9
|
-
import {
|
|
9
|
+
import { expr } from './expressions.js';
|
|
10
10
|
/**
|
|
11
11
|
* Builder for table-level foreign keys.
|
|
12
12
|
*/
|
|
@@ -137,10 +137,13 @@ export class TableBuilder {
|
|
|
137
137
|
return column;
|
|
138
138
|
}
|
|
139
139
|
createdAt() {
|
|
140
|
-
return this.
|
|
140
|
+
return this.timestampNow('createdAt');
|
|
141
141
|
}
|
|
142
142
|
updatedAt() {
|
|
143
|
-
return this.
|
|
143
|
+
return this.timestampNow('updatedAt');
|
|
144
|
+
}
|
|
145
|
+
timestampNow(name) {
|
|
146
|
+
return this.add(name, { category: 'timestamp' }, { defaultValue: expr.now() });
|
|
144
147
|
}
|
|
145
148
|
timestamps() {
|
|
146
149
|
this.createdAt();
|
|
@@ -198,10 +198,10 @@ export interface BuilderMigrationDefinition {
|
|
|
198
198
|
* ```ts
|
|
199
199
|
* export default defineBuilderMigration({
|
|
200
200
|
* async up(m) {
|
|
201
|
-
* await m.createTable('users', (
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
201
|
+
* await m.createTable('users', (t) => {
|
|
202
|
+
* t.id();
|
|
203
|
+
* t.string('email', { length: 255 }).unique();
|
|
204
|
+
* t.timestamps();
|
|
205
205
|
* });
|
|
206
206
|
* },
|
|
207
207
|
* async down(m) {
|
package/dist/migrate/migrator.js
CHANGED
|
@@ -571,10 +571,10 @@ export function defineMigration(migration) {
|
|
|
571
571
|
* ```ts
|
|
572
572
|
* export default defineBuilderMigration({
|
|
573
573
|
* async up(m) {
|
|
574
|
-
* await m.createTable('users', (
|
|
575
|
-
*
|
|
576
|
-
*
|
|
577
|
-
*
|
|
574
|
+
* await m.createTable('users', (t) => {
|
|
575
|
+
* t.id();
|
|
576
|
+
* t.string('email', { length: 255 }).unique();
|
|
577
|
+
* t.timestamps();
|
|
578
578
|
* });
|
|
579
579
|
* },
|
|
580
580
|
* async down(m) {
|
|
@@ -76,6 +76,9 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
76
76
|
* rules are the same everywhere, and having them written twice is how the two paths drifted.
|
|
77
77
|
*/
|
|
78
78
|
private renderColumn;
|
|
79
|
+
/** ` DEFAULT <sql>`, or nothing where the column declares none. Empty rather than `DEFAULT NULL`
|
|
80
|
+
* so an absent default stays absent - `defaultValue: null` is the way to ask for one. */
|
|
81
|
+
private defaultClause;
|
|
79
82
|
getSqlType(field: FieldOptions, fieldType?: unknown): string;
|
|
80
83
|
/**
|
|
81
84
|
* Generate ALTER COLUMN statements (database-specific)
|
|
@@ -85,10 +88,6 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
85
88
|
* Generate column comment clause (if supported)
|
|
86
89
|
*/
|
|
87
90
|
generateColumnComment(columnName: string, comment: string): string;
|
|
88
|
-
/**
|
|
89
|
-
* Format a default value for SQL
|
|
90
|
-
*/
|
|
91
|
-
formatDefaultValue(value: unknown): string;
|
|
92
91
|
/**
|
|
93
92
|
* Compare an entity with a database table node and return the differences.
|
|
94
93
|
*/
|
|
@@ -4,7 +4,7 @@ import { areTypesEqual, canonicalToSql, fieldOptionsToCanonical, isVectorCategor
|
|
|
4
4
|
import { buildSchemaAST } from '../schema/schemaASTBuilder.js';
|
|
5
5
|
import { getKeys, isAutoIncrement, qualifyName } from '../util/index.js';
|
|
6
6
|
import { derivedForeignKeyName } from '../util/sql.util.js';
|
|
7
|
-
import { formatDefaultValue } from './builder/expressions.js';
|
|
7
|
+
import { formatDefaultValue, SqlExpression } from './builder/expressions.js';
|
|
8
8
|
import { fullColumnDefinitionToNode, tableDefinitionToNode } from './generator/definitionToNode.js';
|
|
9
9
|
import { indexNodeToSchema } from './generator/indexNodeToSchema.js';
|
|
10
10
|
/**
|
|
@@ -243,14 +243,19 @@ export class SqlSchemaGenerator {
|
|
|
243
243
|
if (column.isUnique && !column.isPrimaryKey) {
|
|
244
244
|
def += ' UNIQUE';
|
|
245
245
|
}
|
|
246
|
-
|
|
247
|
-
def += ` DEFAULT ${this.formatDefaultValue(column.defaultValue)}`;
|
|
248
|
-
}
|
|
246
|
+
def += this.defaultClause(column);
|
|
249
247
|
if (column.comment) {
|
|
250
248
|
def += this.generateColumnComment(column.name, column.comment);
|
|
251
249
|
}
|
|
252
250
|
return def;
|
|
253
251
|
}
|
|
252
|
+
/** ` DEFAULT <sql>`, or nothing where the column declares none. Empty rather than `DEFAULT NULL`
|
|
253
|
+
* so an absent default stays absent - `defaultValue: null` is the way to ask for one. */
|
|
254
|
+
defaultClause(column) {
|
|
255
|
+
return column.defaultValue === undefined
|
|
256
|
+
? ''
|
|
257
|
+
: ` DEFAULT ${formatDefaultValue(column.defaultValue, this.dialect, column.type)}`;
|
|
258
|
+
}
|
|
254
259
|
getSqlType(field, fieldType) {
|
|
255
260
|
// If field has a reference, inherit type from the target primary key
|
|
256
261
|
if (field.references) {
|
|
@@ -288,7 +293,7 @@ export class SqlSchemaGenerator {
|
|
|
288
293
|
statements.push(`ALTER TABLE ${table} ALTER COLUMN ${colName} SET NOT NULL;`);
|
|
289
294
|
}
|
|
290
295
|
if (column.defaultValue !== undefined) {
|
|
291
|
-
statements.push(`ALTER TABLE ${table} ALTER COLUMN ${colName} SET
|
|
296
|
+
statements.push(`ALTER TABLE ${table} ALTER COLUMN ${colName} SET${this.defaultClause(column)};`);
|
|
292
297
|
}
|
|
293
298
|
else {
|
|
294
299
|
statements.push(`ALTER TABLE ${table} ALTER COLUMN ${colName} DROP DEFAULT;`);
|
|
@@ -307,15 +312,6 @@ export class SqlSchemaGenerator {
|
|
|
307
312
|
}
|
|
308
313
|
return '';
|
|
309
314
|
}
|
|
310
|
-
/**
|
|
311
|
-
* Format a default value for SQL
|
|
312
|
-
*/
|
|
313
|
-
formatDefaultValue(value) {
|
|
314
|
-
if (this.dialect.booleanLiteral === 'integer' && typeof value === 'boolean') {
|
|
315
|
-
return value ? '1' : '0';
|
|
316
|
-
}
|
|
317
|
-
return formatDefaultValue(value);
|
|
318
|
-
}
|
|
319
315
|
/**
|
|
320
316
|
* Compare an entity with a database table node and return the differences.
|
|
321
317
|
*/
|
|
@@ -467,9 +463,12 @@ export class SqlSchemaGenerator {
|
|
|
467
463
|
return true;
|
|
468
464
|
if (current === undefined || desired === undefined)
|
|
469
465
|
return current === desired;
|
|
470
|
-
const normalize = (
|
|
471
|
-
if (
|
|
466
|
+
const normalize = (value) => {
|
|
467
|
+
if (value === null)
|
|
472
468
|
return 'null';
|
|
469
|
+
// Render first: the desired side may be a symbolic expression, the current side is always the
|
|
470
|
+
// engine's own text, and `{"kind":"now"}` matches no spelling of `CURRENT_TIMESTAMP`.
|
|
471
|
+
const val = SqlExpression.isExpression(value) ? formatDefaultValue(value, this.dialect) : value;
|
|
473
472
|
if (typeof val === 'string') {
|
|
474
473
|
let s = val.replace(/::[a-z_]+(\s+[a-z_]+)*(\[\])?$/i, '');
|
|
475
474
|
s = s.replace(/^'(.*)'$/, '$1');
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uql-orm",
|
|
3
3
|
"homepage": "https://uql-orm.dev",
|
|
4
|
-
"description": "JSON-native
|
|
4
|
+
"description": "JSON-native ORM for Node.js, Bun and Deno. Supports PostgreSQL, PGlite, MySQL, MariaDB, SQLite, CockroachDB, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.36.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=24"
|