uql-orm 0.35.0 → 0.36.1
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 +50 -51
- package/dist/migrate/builder/expressions.js +88 -85
- package/dist/migrate/builder/tableBuilder.d.ts +1 -0
- package/dist/migrate/builder/tableBuilder.js +5 -2
- package/dist/migrate/schemaGenerator.d.ts +3 -4
- package/dist/migrate/schemaGenerator.js +15 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
npm install uql-orm pg # or mysql2, mariadb, better-sqlite3, mongodb, @tursodatabase/serverless, @libsql/client
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
That is the whole install ([setup](https://uql-orm.dev/getting-started))
|
|
35
|
+
That is the whole install ([setup](https://uql-orm.dev/getting-started)). No compiler flags and no `reflect-metadata`; the decorators are the [TC39 standard spec](https://uql-orm.dev/entities/basic), and plain classes work too, via [`defineEntity`](https://uql-orm.dev/entities/imperative).
|
|
36
36
|
|
|
37
37
|
<a href="https://uql-orm.dev">
|
|
38
38
|
<picture>
|
|
@@ -1,66 +1,65 @@
|
|
|
1
|
+
import type { AbstractSqlDialect } from '../../dialect/index.js';
|
|
2
|
+
import type { SqlDialectName } from '../../type/index.js';
|
|
1
3
|
/**
|
|
2
|
-
* SQL
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
|
|
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
|
+
*/
|
|
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
|
+
};
|
|
17
|
+
/**
|
|
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 declare const DIALECT_DEFAULTS: Readonly<Record<SqlDialectName, DialectDefaults>>;
|
|
7
24
|
/**
|
|
8
|
-
*
|
|
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
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;
|
|
24
50
|
/**
|
|
25
|
-
*
|
|
26
|
-
|
|
27
|
-
now(): SqlExpression;
|
|
28
|
-
/**
|
|
29
|
-
* Current date (CURRENT_DATE).
|
|
30
|
-
*/
|
|
31
|
-
currentDate(): SqlExpression;
|
|
32
|
-
/**
|
|
33
|
-
* Current time (CURRENT_TIME).
|
|
34
|
-
*/
|
|
35
|
-
currentTime(): SqlExpression;
|
|
36
|
-
/**
|
|
37
|
-
* Generate a UUID. Postgres only - use `expr.mysqlUuid()` on MySQL/MariaDB.
|
|
38
|
-
*/
|
|
39
|
-
uuid(): SqlExpression;
|
|
40
|
-
/**
|
|
41
|
-
* Generate a UUID on MySQL/MariaDB.
|
|
42
|
-
*/
|
|
43
|
-
mysqlUuid(): SqlExpression;
|
|
44
|
-
/**
|
|
45
|
-
* Raw SQL expression.
|
|
46
|
-
* Use for custom expressions not covered by helpers.
|
|
47
|
-
*/
|
|
48
|
-
raw(sql: string): SqlExpression;
|
|
49
|
-
/**
|
|
50
|
-
* Empty JSON object. Postgres only - the `::jsonb` cast is not portable.
|
|
51
|
-
*/
|
|
52
|
-
emptyObject(): SqlExpression;
|
|
53
|
-
/**
|
|
54
|
-
* Empty JSON array. Postgres only - the `::jsonb` cast is not portable.
|
|
55
|
-
*/
|
|
56
|
-
emptyArray(): SqlExpression;
|
|
57
|
-
/**
|
|
58
|
-
* MySQL: ON UPDATE CURRENT_TIMESTAMP.
|
|
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.
|
|
59
53
|
*/
|
|
60
|
-
|
|
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;
|
|
61
59
|
};
|
|
62
60
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
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.
|
|
65
64
|
*/
|
|
66
|
-
export declare function formatDefaultValue(value: unknown): string;
|
|
65
|
+
export declare function formatDefaultValue(value: unknown, dialect: AbstractSqlDialect, columnType?: string): string;
|
|
@@ -1,117 +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
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'),
|
|
30
61
|
/**
|
|
31
|
-
*
|
|
32
|
-
|
|
33
|
-
now() {
|
|
34
|
-
return new SqlExpression('CURRENT_TIMESTAMP');
|
|
35
|
-
},
|
|
36
|
-
/**
|
|
37
|
-
* Current date (CURRENT_DATE).
|
|
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
|
-
* Generate a UUID. Postgres only - use `expr.mysqlUuid()` on MySQL/MariaDB.
|
|
50
|
-
*/
|
|
51
|
-
uuid() {
|
|
52
|
-
return new SqlExpression('gen_random_uuid()');
|
|
53
|
-
},
|
|
54
|
-
/**
|
|
55
|
-
* Generate a UUID on MySQL/MariaDB.
|
|
56
|
-
*/
|
|
57
|
-
mysqlUuid() {
|
|
58
|
-
return new SqlExpression('UUID()');
|
|
59
|
-
},
|
|
60
|
-
/**
|
|
61
|
-
* Raw SQL expression.
|
|
62
|
-
* Use for custom expressions not covered by helpers.
|
|
63
|
-
*/
|
|
64
|
-
raw(sql) {
|
|
65
|
-
return new SqlExpression(sql);
|
|
66
|
-
},
|
|
67
|
-
/**
|
|
68
|
-
* Empty JSON object. Postgres only - the `::jsonb` cast is not portable.
|
|
69
|
-
*/
|
|
70
|
-
emptyObject() {
|
|
71
|
-
return new SqlExpression("'{}'::jsonb");
|
|
72
|
-
},
|
|
73
|
-
/**
|
|
74
|
-
* Empty JSON array. Postgres only - the `::jsonb` cast is not portable.
|
|
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.
|
|
75
64
|
*/
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
|
|
81
|
-
*/
|
|
82
|
-
onUpdateNow() {
|
|
83
|
-
return new SqlExpression('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
|
|
84
|
-
},
|
|
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),
|
|
85
70
|
};
|
|
86
71
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
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.
|
|
89
75
|
*/
|
|
90
|
-
export function formatDefaultValue(value) {
|
|
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.
|
|
86
|
+
*/
|
|
87
|
+
function defaultLiteral(value, dialect) {
|
|
91
88
|
if (value === undefined || value === null) {
|
|
92
89
|
return 'NULL';
|
|
93
90
|
}
|
|
94
91
|
if (SqlExpression.isExpression(value)) {
|
|
95
|
-
return value
|
|
96
|
-
}
|
|
97
|
-
if (typeof value === 'string') {
|
|
98
|
-
// Escape single quotes for string literals
|
|
99
|
-
const escaped = value.replace(/'/g, "''");
|
|
100
|
-
return `'${escaped}'`;
|
|
101
|
-
}
|
|
102
|
-
if (typeof value === 'number') {
|
|
103
|
-
return String(value);
|
|
92
|
+
return expressionSql(value, dialect);
|
|
104
93
|
}
|
|
105
94
|
if (typeof value === 'boolean') {
|
|
106
|
-
return value ? 'TRUE' : 'FALSE';
|
|
95
|
+
return dialect.booleanLiteral === 'native' ? (value ? 'TRUE' : 'FALSE') : value ? '1' : '0';
|
|
107
96
|
}
|
|
108
97
|
if (value instanceof Date) {
|
|
109
|
-
return
|
|
98
|
+
return dialect.escape(ddlTimestamp(value));
|
|
110
99
|
}
|
|
111
100
|
if (typeof value === 'object') {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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`);
|
|
115
118
|
}
|
|
116
|
-
return
|
|
119
|
+
return sql;
|
|
117
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;
|
|
@@ -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();
|
|
@@ -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
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"homepage": "https://uql-orm.dev",
|
|
4
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.1",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=24"
|