uql-orm 0.50.0 → 0.52.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/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +2 -2
- package/dist/bunSql/bunSql.util.d.ts +33 -10
- package/dist/bunSql/bunSql.util.js +57 -42
- package/dist/bunSql/bunSqlQuerier.d.ts +13 -8
- package/dist/bunSql/bunSqlQuerier.js +17 -8
- package/dist/bunSql/bunSqlQuerierPool.d.ts +10 -2
- package/dist/bunSql/bunSqlQuerierPool.js +38 -25
- package/dist/bunSql/index.d.ts +1 -3
- package/dist/bunSql/index.js +0 -3
- package/dist/dialect/abstractSqlDialect.d.ts +61 -7
- package/dist/dialect/abstractSqlDialect.js +88 -27
- package/dist/dialect/aliases.d.ts +2 -0
- package/dist/dialect/aliases.js +2 -0
- package/dist/dialect/jsonSql.d.ts +2 -2
- package/dist/dialect/jsonSql.js +2 -2
- package/dist/dialect/mergeSqlDialect.d.ts +45 -0
- package/dist/dialect/mergeSqlDialect.js +89 -0
- package/dist/dialect/mysqlLikeSqlDialect.js +4 -1
- package/dist/dialect/pgLikeSqlDialect.d.ts +3 -3
- package/dist/dialect/pgLikeSqlDialect.js +15 -12
- package/dist/migrate/builder/expressions.js +5 -0
- package/dist/migrate/introspection/index.d.ts +2 -0
- package/dist/migrate/introspection/index.js +2 -0
- package/dist/migrate/introspection/mssqlIntrospector.d.ts +63 -0
- package/dist/migrate/introspection/mssqlIntrospector.js +198 -0
- package/dist/migrate/introspection/postgresIntrospector.js +3 -3
- package/dist/migrate/introspection/registry.d.ts +3 -0
- package/dist/migrate/introspection/registry.js +28 -0
- package/dist/migrate/migrator.js +2 -21
- package/dist/mongo/mongoDialect.js +4 -1
- package/dist/mongo/mongodbQuerier.d.ts +2 -2
- package/dist/mongo/mongodbQuerier.js +8 -6
- package/dist/mssql/index.d.ts +3 -0
- package/dist/mssql/index.js +3 -0
- package/dist/mssql/mssqlDialect.d.ts +144 -0
- package/dist/mssql/mssqlDialect.js +328 -0
- package/dist/mssql/mssqlQuerier.d.ts +23 -0
- package/dist/mssql/mssqlQuerier.js +137 -0
- package/dist/mssql/mssqlQuerierPool.d.ts +17 -0
- package/dist/mssql/mssqlQuerierPool.js +32 -0
- package/dist/mssql/mssqlWireTypes.d.ts +23 -0
- package/dist/mssql/mssqlWireTypes.js +44 -0
- package/dist/pglite/pgliteQuerier.d.ts +4 -2
- package/dist/pglite/pgliteQuerier.js +7 -2
- package/dist/postgres/pgCursorStream.d.ts +20 -0
- package/dist/postgres/pgCursorStream.js +49 -0
- package/dist/postgres/pgDialect.d.ts +1 -1
- package/dist/postgres/pgDialect.js +1 -1
- package/dist/postgres/postgresWireDriverCapabilities.d.ts +12 -10
- package/dist/postgres/postgresWireDriverCapabilities.js +12 -10
- package/dist/querier/abstractQuerier.d.ts +3 -7
- package/dist/querier/abstractQuerier.js +22 -2
- package/dist/querier/abstractQuerierPool.d.ts +3 -3
- package/dist/schema/canonicalType.js +96 -113
- package/dist/sqlite/sqliteDialect.d.ts +7 -7
- package/dist/sqlite/sqliteDialect.js +21 -18
- package/dist/type/dialect.d.ts +31 -4
- package/dist/type/migratorDialect.d.ts +1 -1
- package/dist/type/migratorDialect.js +1 -0
- package/dist/type/query.d.ts +29 -6
- package/dist/type/universalQuerier.d.ts +3 -3
- package/package.json +13 -3
- package/dist/bunSql/bunSqlCockroachDialect.d.ts +0 -12
- package/dist/bunSql/bunSqlCockroachDialect.js +0 -15
- package/dist/bunSql/bunSqlPostgresDialect.d.ts +0 -11
- package/dist/bunSql/bunSqlPostgresDialect.js +0 -14
- package/dist/bunSql/bunSqliteDialect.d.ts +0 -6
- package/dist/bunSql/bunSqliteDialect.js +0 -6
|
@@ -44,6 +44,9 @@ const SQL_TO_CANONICAL = {
|
|
|
44
44
|
char: { category: 'string' },
|
|
45
45
|
character: { category: 'string' },
|
|
46
46
|
varchar: { category: 'string' },
|
|
47
|
+
varchar2: { category: 'string' },
|
|
48
|
+
nvarchar: { category: 'string' },
|
|
49
|
+
nchar: { category: 'string' },
|
|
47
50
|
'character varying': { category: 'string' },
|
|
48
51
|
text: { category: 'string', size: 'small' },
|
|
49
52
|
tinytext: { category: 'string', size: 'tiny' },
|
|
@@ -64,11 +67,15 @@ const SQL_TO_CANONICAL = {
|
|
|
64
67
|
'timestamp with time zone': { category: 'timestamp', withTimezone: true },
|
|
65
68
|
timestamptz: { category: 'timestamp', withTimezone: true },
|
|
66
69
|
datetime: { category: 'timestamp' },
|
|
70
|
+
datetime2: { category: 'timestamp' },
|
|
71
|
+
smalldatetime: { category: 'timestamp' },
|
|
72
|
+
datetimeoffset: { category: 'timestamp', withTimezone: true },
|
|
67
73
|
// === JSON ===
|
|
68
74
|
json: { category: 'json' },
|
|
69
75
|
jsonb: { category: 'json' },
|
|
70
76
|
// === UUID ===
|
|
71
77
|
uuid: { category: 'uuid' },
|
|
78
|
+
uniqueidentifier: { category: 'uuid' },
|
|
72
79
|
// === Binary ===
|
|
73
80
|
blob: { category: 'blob' },
|
|
74
81
|
bytea: { category: 'blob' },
|
|
@@ -77,15 +84,16 @@ const SQL_TO_CANONICAL = {
|
|
|
77
84
|
tinyblob: { category: 'blob', size: 'tiny' },
|
|
78
85
|
mediumblob: { category: 'blob', size: 'medium' },
|
|
79
86
|
longblob: { category: 'blob', size: 'big' },
|
|
87
|
+
image: { category: 'blob', size: 'big' },
|
|
80
88
|
// === Vector (for AI/embeddings) ===
|
|
81
89
|
vector: { category: 'vector' },
|
|
82
90
|
halfvec: { category: 'halfvec' },
|
|
83
91
|
sparsevec: { category: 'sparsevec' },
|
|
84
92
|
};
|
|
85
93
|
/**
|
|
86
|
-
* pgvector is the only engine with three vector column types, so every other
|
|
87
|
-
* canonical categories onto the single type it does have (see `hasNarrowVectorTypes` on the dialect,
|
|
88
|
-
* dialect-side half of the same fact).
|
|
94
|
+
* pgvector is the only engine with three vector column types, so every other engine maps all three
|
|
95
|
+
* canonical categories onto the single type it does have (see `hasNarrowVectorTypes` on the dialect,
|
|
96
|
+
* the dialect-side half of the same fact).
|
|
89
97
|
*/
|
|
90
98
|
function withVectorType(scalars, vector) {
|
|
91
99
|
return { ...scalars, vector, halfvec: vector, sparsevec: vector };
|
|
@@ -103,11 +111,9 @@ const PG_SCALAR_MAP = {
|
|
|
103
111
|
uuid: 'UUID',
|
|
104
112
|
blob: 'BYTEA',
|
|
105
113
|
};
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
halfvec: 'HALFVEC',
|
|
110
|
-
sparsevec: 'SPARSEVEC',
|
|
114
|
+
const PG_SIZES = {
|
|
115
|
+
integer: { tiny: 'SMALLINT', small: 'SMALLINT', medium: 'INTEGER', big: 'BIGINT' },
|
|
116
|
+
float: { tiny: 'REAL', small: 'REAL', medium: 'DOUBLE PRECISION', big: 'DOUBLE PRECISION' },
|
|
111
117
|
};
|
|
112
118
|
const MYSQL_SCALAR_MAP = {
|
|
113
119
|
integer: 'INT',
|
|
@@ -122,6 +128,13 @@ const MYSQL_SCALAR_MAP = {
|
|
|
122
128
|
uuid: 'CHAR(36)',
|
|
123
129
|
blob: 'BLOB',
|
|
124
130
|
};
|
|
131
|
+
/** MariaDB is a MySQL fork and spells every one of these the same way, so both take the one map. */
|
|
132
|
+
const MYSQL_SIZES = {
|
|
133
|
+
integer: { tiny: 'TINYINT', small: 'SMALLINT', medium: 'MEDIUMINT', big: 'BIGINT' },
|
|
134
|
+
float: { tiny: 'FLOAT', small: 'FLOAT', medium: 'DOUBLE', big: 'DOUBLE' },
|
|
135
|
+
string: { tiny: 'TINYTEXT', small: 'TEXT', medium: 'MEDIUMTEXT', big: 'LONGTEXT' },
|
|
136
|
+
blob: { tiny: 'TINYBLOB', small: 'BLOB', medium: 'MEDIUMBLOB', big: 'LONGBLOB' },
|
|
137
|
+
};
|
|
125
138
|
const SQLITE_SCALAR_MAP = {
|
|
126
139
|
integer: 'INTEGER',
|
|
127
140
|
float: 'REAL',
|
|
@@ -135,6 +148,35 @@ const SQLITE_SCALAR_MAP = {
|
|
|
135
148
|
uuid: 'TEXT',
|
|
136
149
|
blob: 'BLOB',
|
|
137
150
|
};
|
|
151
|
+
/**
|
|
152
|
+
* Every string column is `NVARCHAR`: `VARCHAR` is a codepage type on SQL Server and silently destroys
|
|
153
|
+
* anything outside it on write. UQL never exposes the choice, so there is nothing to weigh per column.
|
|
154
|
+
*/
|
|
155
|
+
const MSSQL_SCALAR_MAP = {
|
|
156
|
+
integer: 'INT',
|
|
157
|
+
float: 'REAL',
|
|
158
|
+
decimal: 'DECIMAL',
|
|
159
|
+
string: 'NVARCHAR',
|
|
160
|
+
boolean: 'BIT',
|
|
161
|
+
date: 'DATE',
|
|
162
|
+
time: 'TIME',
|
|
163
|
+
timestamp: 'DATETIME2',
|
|
164
|
+
json: 'NVARCHAR(MAX)',
|
|
165
|
+
uuid: 'UNIQUEIDENTIFIER',
|
|
166
|
+
blob: 'VARBINARY(MAX)',
|
|
167
|
+
};
|
|
168
|
+
/** `FLOAT` is eight bytes here and `REAL` four - the opposite of the MySQL family's spelling. */
|
|
169
|
+
const MSSQL_SIZES = {
|
|
170
|
+
integer: { tiny: 'TINYINT', small: 'SMALLINT', medium: 'INT', big: 'BIGINT' },
|
|
171
|
+
float: { tiny: 'REAL', small: 'REAL', medium: 'FLOAT', big: 'FLOAT' },
|
|
172
|
+
string: { tiny: 'NVARCHAR(255)', small: 'NVARCHAR(MAX)', medium: 'NVARCHAR(MAX)', big: 'NVARCHAR(MAX)' },
|
|
173
|
+
blob: {
|
|
174
|
+
tiny: 'VARBINARY(255)',
|
|
175
|
+
small: 'VARBINARY(MAX)',
|
|
176
|
+
medium: 'VARBINARY(MAX)',
|
|
177
|
+
big: 'VARBINARY(MAX)',
|
|
178
|
+
},
|
|
179
|
+
};
|
|
138
180
|
/** MongoDB uses BSON types, not SQL types. These are placeholders for compatibility. */
|
|
139
181
|
const MONGO_SCALAR_MAP = {
|
|
140
182
|
integer: 'int',
|
|
@@ -149,73 +191,31 @@ const MONGO_SCALAR_MAP = {
|
|
|
149
191
|
uuid: 'binData',
|
|
150
192
|
blob: 'binData',
|
|
151
193
|
};
|
|
152
|
-
/** Every engine's
|
|
153
|
-
const
|
|
154
|
-
postgres:
|
|
194
|
+
/** Every engine's spelling of the canonical types. A new engine is one entry here and nothing else. */
|
|
195
|
+
const ENGINE_TYPES = {
|
|
196
|
+
postgres: {
|
|
197
|
+
scalars: { ...PG_SCALAR_MAP, vector: 'VECTOR', halfvec: 'HALFVEC', sparsevec: 'SPARSEVEC' },
|
|
198
|
+
sizes: PG_SIZES,
|
|
199
|
+
},
|
|
155
200
|
// CockroachDB's VECTOR is native, no extension needed.
|
|
156
|
-
cockroachdb: withVectorType(PG_SCALAR_MAP, 'VECTOR'),
|
|
201
|
+
cockroachdb: { scalars: withVectorType(PG_SCALAR_MAP, 'VECTOR'), sizes: PG_SIZES },
|
|
157
202
|
// MySQL does have a `VECTOR` type (26.7), but no distance function outside HeatWave and no vector
|
|
158
203
|
// index, so JSON keeps the column queryable with the JSON operators and needs no conversion.
|
|
159
|
-
mysql:
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
};
|
|
164
|
-
/**
|
|
165
|
-
* Size variant modifiers for SQL types.
|
|
166
|
-
*/
|
|
167
|
-
const PG_SIZE_MODIFIERS = {
|
|
168
|
-
integer: {
|
|
169
|
-
tiny: 'SMALLINT',
|
|
170
|
-
small: 'SMALLINT',
|
|
171
|
-
medium: 'INTEGER',
|
|
172
|
-
big: 'BIGINT',
|
|
173
|
-
},
|
|
174
|
-
float: {
|
|
175
|
-
tiny: 'REAL',
|
|
176
|
-
small: 'REAL',
|
|
177
|
-
medium: 'DOUBLE PRECISION',
|
|
178
|
-
big: 'DOUBLE PRECISION',
|
|
179
|
-
},
|
|
180
|
-
};
|
|
181
|
-
/**
|
|
182
|
-
* MariaDB is a MySQL fork and spells every one of these the same way, so both take the one map.
|
|
183
|
-
* Listed per-dialect, MariaDB's had only `integer`: a `double` column was created `FLOAT` there, four
|
|
184
|
-
* bytes where the entity asked for eight, and a `big` string or blob lost its `LONG` prefix.
|
|
185
|
-
*/
|
|
186
|
-
const MYSQL_SIZE_MODIFIERS = {
|
|
187
|
-
integer: {
|
|
188
|
-
tiny: 'TINYINT',
|
|
189
|
-
small: 'SMALLINT',
|
|
190
|
-
medium: 'MEDIUMINT',
|
|
191
|
-
big: 'BIGINT',
|
|
204
|
+
mysql: {
|
|
205
|
+
scalars: withVectorType(MYSQL_SCALAR_MAP, 'JSON'),
|
|
206
|
+
sizes: MYSQL_SIZES,
|
|
207
|
+
decimal: { precision: 10, scale: 2 },
|
|
192
208
|
},
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
big: 'DOUBLE',
|
|
209
|
+
mariadb: {
|
|
210
|
+
scalars: withVectorType(MYSQL_SCALAR_MAP, 'VECTOR'),
|
|
211
|
+
sizes: MYSQL_SIZES,
|
|
212
|
+
decimal: { precision: 10, scale: 2 },
|
|
198
213
|
},
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
},
|
|
205
|
-
blob: {
|
|
206
|
-
tiny: 'TINYBLOB',
|
|
207
|
-
small: 'BLOB',
|
|
208
|
-
medium: 'MEDIUMBLOB',
|
|
209
|
-
big: 'LONGBLOB',
|
|
210
|
-
},
|
|
211
|
-
};
|
|
212
|
-
const SIZE_MODIFIERS = {
|
|
213
|
-
postgres: PG_SIZE_MODIFIERS,
|
|
214
|
-
cockroachdb: PG_SIZE_MODIFIERS,
|
|
215
|
-
mysql: MYSQL_SIZE_MODIFIERS,
|
|
216
|
-
sqlite: {}, // SQLite uses affinity, no size modifiers
|
|
217
|
-
mariadb: MYSQL_SIZE_MODIFIERS,
|
|
218
|
-
mongodb: {},
|
|
214
|
+
// SQLite uses affinity, so no size variants.
|
|
215
|
+
sqlite: { scalars: withVectorType(SQLITE_SCALAR_MAP, 'TEXT') },
|
|
216
|
+
// A 2025 server has a native `VECTOR`; below that the column is JSON text, which stays queryable.
|
|
217
|
+
mssql: { scalars: withVectorType(MSSQL_SCALAR_MAP, 'NVARCHAR(MAX)'), sizes: MSSQL_SIZES },
|
|
218
|
+
mongodb: { scalars: withVectorType(MONGO_SCALAR_MAP, 'array') },
|
|
219
219
|
};
|
|
220
220
|
/**
|
|
221
221
|
* Maps canonical types to TypeScript types for entity generation.
|
|
@@ -317,61 +317,44 @@ export function canonicalColumnType(sqlType, reported = {}) {
|
|
|
317
317
|
export function canonicalToSql(type, dialect) {
|
|
318
318
|
if (type.raw)
|
|
319
319
|
return type.raw;
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
320
|
+
const engine = ENGINE_TYPES[dialect.dialectName];
|
|
321
|
+
const { features } = dialect;
|
|
322
|
+
// A `size` the engine spells out wins outright; anything else falls through to the rules below.
|
|
323
|
+
// `TEXT` canonicalizes to `size: 'small'`, so a string counts as unsized only where the engine
|
|
324
|
+
// declares no variant for it, which is how Postgres reaches `TEXT` rather than its base `VARCHAR`.
|
|
325
|
+
const sized = engine.sizes?.[type.category]?.[type.size];
|
|
326
|
+
let sqlType = sized ?? engine.scalars[type.category];
|
|
327
|
+
if (type.category === 'string' && !sized) {
|
|
328
|
+
sqlType = formatStringSqlType(type, engine.scalars.string, features.stringSizing);
|
|
324
329
|
}
|
|
325
330
|
else if (type.category === 'decimal') {
|
|
326
|
-
sqlType = formatDecimalSqlType(type,
|
|
331
|
+
sqlType = formatDecimalSqlType(type, engine.decimal, sqlType);
|
|
327
332
|
}
|
|
328
|
-
else if (isVectorCategory(type.category) && type.length &&
|
|
333
|
+
else if (isVectorCategory(type.category) && type.length && features.vectorSupportsLength) {
|
|
329
334
|
sqlType = `${sqlType}(${type.length})`;
|
|
330
335
|
}
|
|
331
|
-
if (type.category === 'timestamp' && type.withTimezone &&
|
|
336
|
+
if (type.category === 'timestamp' && type.withTimezone && features.supportsTimestamptz) {
|
|
332
337
|
sqlType = 'TIMESTAMPTZ';
|
|
333
338
|
}
|
|
334
|
-
|
|
335
|
-
sqlType = `${sqlType} UNSIGNED`;
|
|
336
|
-
}
|
|
337
|
-
return sqlType;
|
|
339
|
+
return type.unsigned && features.supportsUnsigned ? `${sqlType} UNSIGNED` : sqlType;
|
|
338
340
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
if (
|
|
342
|
-
|
|
343
|
-
if (sizeMap?.[type.size]) {
|
|
344
|
-
sqlType = sizeMap[type.size];
|
|
345
|
-
}
|
|
341
|
+
/** See {@link EngineFeatures.stringSizing} for what each mode means. */
|
|
342
|
+
function formatStringSqlType(type, base, sizing) {
|
|
343
|
+
if (sizing === 'text') {
|
|
344
|
+
return base;
|
|
346
345
|
}
|
|
347
|
-
|
|
348
|
-
}
|
|
349
|
-
function formatStringSqlType(type, dialect) {
|
|
350
|
-
const { dialectName, features } = dialect;
|
|
351
|
-
if (dialectName === 'sqlite')
|
|
352
|
-
return 'TEXT';
|
|
353
|
-
if (features.defaultStringAsText)
|
|
354
|
-
return type.length ? `VARCHAR(${type.length})` : 'TEXT';
|
|
355
|
-
if (dialectName === 'mysql' || dialectName === 'mariadb') {
|
|
356
|
-
if (type.size === 'tiny')
|
|
357
|
-
return 'TINYTEXT';
|
|
358
|
-
if (type.size === 'small')
|
|
359
|
-
return 'TEXT';
|
|
360
|
-
if (type.size === 'medium')
|
|
361
|
-
return 'MEDIUMTEXT';
|
|
362
|
-
if (type.size === 'big')
|
|
363
|
-
return 'LONGTEXT';
|
|
364
|
-
return type.length ? `VARCHAR(${type.length})` : 'VARCHAR(255)';
|
|
346
|
+
if (type.length) {
|
|
347
|
+
return `${base}(${type.length})`;
|
|
365
348
|
}
|
|
366
|
-
return
|
|
349
|
+
return sizing === 'bounded-text' ? 'TEXT' : `${base}(255)`;
|
|
367
350
|
}
|
|
368
|
-
function formatDecimalSqlType(type,
|
|
369
|
-
const p = type.precision ??
|
|
370
|
-
const s = type.scale ??
|
|
371
|
-
if (p
|
|
372
|
-
return
|
|
351
|
+
function formatDecimalSqlType(type, fallback, baseType) {
|
|
352
|
+
const p = type.precision ?? fallback?.precision;
|
|
353
|
+
const s = type.scale ?? fallback?.scale;
|
|
354
|
+
if (p === undefined) {
|
|
355
|
+
return baseType;
|
|
373
356
|
}
|
|
374
|
-
return baseType
|
|
357
|
+
return s === undefined ? `${baseType}(${p})` : `${baseType}(${p}, ${s})`;
|
|
375
358
|
}
|
|
376
359
|
/**
|
|
377
360
|
* Convert a canonical type to a TypeScript type string.
|
|
@@ -46,24 +46,24 @@ export declare class SqliteDialect extends AbstractSqlDialect {
|
|
|
46
46
|
* element includes these keys" - `$elemMatch` always expands to per-field conditions.
|
|
47
47
|
*/
|
|
48
48
|
protected readonly jsonContainmentIsPartial = false;
|
|
49
|
-
/** `
|
|
49
|
+
/** `JSON_EACH` exposes a JSON boolean as `0`/`1` and a number as a number - already comparable. */
|
|
50
50
|
protected readonly jsonScalarElemKeepsType = true;
|
|
51
51
|
/**
|
|
52
52
|
* Each element is read back as JSON text through `->` at its own `fullkey`, so it compares
|
|
53
|
-
* correctly whatever its type. `
|
|
53
|
+
* correctly whatever its type. `JSON_EACH`'s `value` column would not: it unquotes strings (`a`
|
|
54
54
|
* vs `"a"`), flattens booleans to 0/1, and stringifies objects.
|
|
55
55
|
*/
|
|
56
56
|
protected jsonAll(ctx: QueryContext, jsonField: string, value: unknown): string;
|
|
57
57
|
protected jsonSize(ctx: QueryContext, jsonField: string, value: number | QuerySizeComparisonOps): string;
|
|
58
|
-
/** `
|
|
58
|
+
/** `JSON_EACH` yields both scalar and object elements, so one form covers each case. */
|
|
59
59
|
protected jsonElemFrom(jsonField: string, _fields: readonly string[], alias: string): string;
|
|
60
60
|
protected jsonElemRef(alias: string, field?: string, asJson?: boolean): string;
|
|
61
61
|
protected getJsonPathScalarExpr(escapedColumn: string, jsonPathStr: string): string;
|
|
62
62
|
protected numericCast(expr: string): string;
|
|
63
63
|
protected jsonCast(operand: string): string;
|
|
64
64
|
/**
|
|
65
|
-
* `
|
|
66
|
-
* through `->` at their own `fullkey` so each keeps its JSON type - `
|
|
65
|
+
* `JSON_REPLACE` leaves an absent key (and a NULL column) untouched. Elements are read back
|
|
66
|
+
* through `->` at their own `fullkey` so each keeps its JSON type - `JSON_EACH`'s `value` would
|
|
67
67
|
* flatten booleans to 0/1 and stringify objects.
|
|
68
68
|
*/
|
|
69
69
|
protected jsonPullKey(ctx: QueryContext, expr: string, escapedCol: string, key: string, value: unknown): string;
|
|
@@ -71,8 +71,8 @@ export declare class SqliteDialect extends AbstractSqlDialect {
|
|
|
71
71
|
/**
|
|
72
72
|
* `[#]` appends, creating the array when the key is absent.
|
|
73
73
|
*
|
|
74
|
-
* @remarks `
|
|
75
|
-
* resolves past the end of the array, and Turso's engine implements `
|
|
74
|
+
* @remarks `JSON_SET` rather than `JSON_INSERT`: the two are equivalent here because `[#]` always
|
|
75
|
+
* resolves past the end of the array, and Turso's engine implements `JSON_INSERT` as create-only,
|
|
76
76
|
* so it silently drops the element when the array already exists.
|
|
77
77
|
*/
|
|
78
78
|
protected jsonPush(ctx: QueryContext, expr: string, push: Record<string, unknown>): string;
|
|
@@ -19,7 +19,10 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
19
19
|
vectorIndexRequiresNotNull: false,
|
|
20
20
|
vectorSupportsLength: false,
|
|
21
21
|
supportsTimestamptz: false,
|
|
22
|
-
|
|
22
|
+
stringSizing: 'text',
|
|
23
|
+
supportsUnsigned: false,
|
|
24
|
+
multipleCascadePaths: true,
|
|
25
|
+
serverSideCursors: false,
|
|
23
26
|
};
|
|
24
27
|
dialectName = 'sqlite';
|
|
25
28
|
escapeIdChar = '`';
|
|
@@ -94,11 +97,11 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
94
97
|
* element includes these keys" - `$elemMatch` always expands to per-field conditions.
|
|
95
98
|
*/
|
|
96
99
|
jsonContainmentIsPartial = false;
|
|
97
|
-
/** `
|
|
100
|
+
/** `JSON_EACH` exposes a JSON boolean as `0`/`1` and a number as a number - already comparable. */
|
|
98
101
|
jsonScalarElemKeepsType = true;
|
|
99
102
|
/**
|
|
100
103
|
* Each element is read back as JSON text through `->` at its own `fullkey`, so it compares
|
|
101
|
-
* correctly whatever its type. `
|
|
104
|
+
* correctly whatever its type. `JSON_EACH`'s `value` column would not: it unquotes strings (`a`
|
|
102
105
|
* vs `"a"`), flattens booleans to 0/1, and stringifies objects.
|
|
103
106
|
*/
|
|
104
107
|
jsonAll(ctx, jsonField, value) {
|
|
@@ -108,52 +111,52 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
108
111
|
return `(${conditions.join(' AND ')})`;
|
|
109
112
|
}
|
|
110
113
|
jsonSize(ctx, jsonField, value) {
|
|
111
|
-
return this.buildFragment(ctx, (fragmentCtx) => this.buildSizeComparison(fragmentCtx, () => fragmentCtx.append(`
|
|
114
|
+
return this.buildFragment(ctx, (fragmentCtx) => this.buildSizeComparison(fragmentCtx, () => fragmentCtx.append(`JSON_ARRAY_LENGTH(${jsonField})`), value));
|
|
112
115
|
}
|
|
113
|
-
/** `
|
|
116
|
+
/** `JSON_EACH` yields both scalar and object elements, so one form covers each case. */
|
|
114
117
|
jsonElemFrom(jsonField, _fields, alias) {
|
|
115
|
-
return `
|
|
118
|
+
return `JSON_EACH(${jsonField}) ${alias}`;
|
|
116
119
|
}
|
|
117
120
|
jsonElemRef(alias, field, asJson = false) {
|
|
118
121
|
if (field === undefined) {
|
|
119
122
|
return `${alias}.value`;
|
|
120
123
|
}
|
|
121
|
-
return asJson ? `${alias}.value -> ${jsonPath(field)}` : `
|
|
124
|
+
return asJson ? `${alias}.value -> ${jsonPath(field)}` : `JSON_EXTRACT(${alias}.value, ${jsonPath(field)})`;
|
|
122
125
|
}
|
|
123
126
|
getJsonPathScalarExpr(escapedColumn, jsonPathStr) {
|
|
124
|
-
return `
|
|
127
|
+
return `JSON_EXTRACT(${escapedColumn}, ${jsonPath(jsonPathStr)})`;
|
|
125
128
|
}
|
|
126
129
|
numericCast(expr) {
|
|
127
130
|
return `CAST(${expr} AS REAL)`;
|
|
128
131
|
}
|
|
129
132
|
jsonCast(operand) {
|
|
130
|
-
return `
|
|
133
|
+
return `JSON(${operand})`;
|
|
131
134
|
}
|
|
132
135
|
/**
|
|
133
|
-
* `
|
|
134
|
-
* through `->` at their own `fullkey` so each keeps its JSON type - `
|
|
136
|
+
* `JSON_REPLACE` leaves an absent key (and a NULL column) untouched. Elements are read back
|
|
137
|
+
* through `->` at their own `fullkey` so each keeps its JSON type - `JSON_EACH`'s `value` would
|
|
135
138
|
* flatten booleans to 0/1 and stringify objects.
|
|
136
139
|
*/
|
|
137
140
|
jsonPullKey(ctx, expr, escapedCol, key, value) {
|
|
138
141
|
const path = jsonPath(key);
|
|
139
142
|
const elem = `${escapedCol} -> ${JSON_PULL_ALIAS}.fullkey`;
|
|
140
|
-
const kept = `SELECT
|
|
141
|
-
return `
|
|
143
|
+
const kept = `SELECT JSON_GROUP_ARRAY(JSON(${elem})) FROM JSON_EACH(${escapedCol}, ${path}) ${JSON_PULL_ALIAS} WHERE ${elem} <> ${this.jsonScalarParam(ctx, value)}`;
|
|
144
|
+
return `JSON_REPLACE(${expr}, ${path}, (${kept}))`;
|
|
142
145
|
}
|
|
143
146
|
jsonSet(ctx, expr, set, field) {
|
|
144
|
-
return jsonAssignCall((value) => this.jsonScalarParam(ctx, value), '
|
|
147
|
+
return jsonAssignCall((value) => this.jsonScalarParam(ctx, value), 'JSON_SET', jsonSetTarget(expr, field, `'{}'`), set);
|
|
145
148
|
}
|
|
146
149
|
/**
|
|
147
150
|
* `[#]` appends, creating the array when the key is absent.
|
|
148
151
|
*
|
|
149
|
-
* @remarks `
|
|
150
|
-
* resolves past the end of the array, and Turso's engine implements `
|
|
152
|
+
* @remarks `JSON_SET` rather than `JSON_INSERT`: the two are equivalent here because `[#]` always
|
|
153
|
+
* resolves past the end of the array, and Turso's engine implements `JSON_INSERT` as create-only,
|
|
151
154
|
* so it silently drops the element when the array already exists.
|
|
152
155
|
*/
|
|
153
156
|
jsonPush(ctx, expr, push) {
|
|
154
|
-
return jsonAssignCall((value) => this.jsonScalarParam(ctx, value), '
|
|
157
|
+
return jsonAssignCall((value) => this.jsonScalarParam(ctx, value), 'JSON_SET', expr, push, '[#]');
|
|
155
158
|
}
|
|
156
159
|
jsonUnset(_ctx, expr, unset) {
|
|
157
|
-
return jsonRemoveCall('
|
|
160
|
+
return jsonRemoveCall('JSON_REMOVE', expr, unset);
|
|
158
161
|
}
|
|
159
162
|
}
|
package/dist/type/dialect.d.ts
CHANGED
|
@@ -128,8 +128,35 @@ export interface EngineFeatures {
|
|
|
128
128
|
readonly vectorSupportsLength: boolean;
|
|
129
129
|
/** Whether the dialect natively supports the TIMESTAMPTZ alias/type. */
|
|
130
130
|
readonly supportsTimestamptz: boolean;
|
|
131
|
-
/**
|
|
132
|
-
|
|
131
|
+
/**
|
|
132
|
+
* How a `string` column is sized: `text` is always the unbounded type whatever length was asked
|
|
133
|
+
* for (SQLite, whose affinity makes a bound meaningless), `bounded-text` takes the bound when there
|
|
134
|
+
* is one and the unbounded type otherwise (the Postgres family), `varchar` is always bounded and
|
|
135
|
+
* falls back to 255 (the MySQL family, SQL Server).
|
|
136
|
+
*
|
|
137
|
+
* Three-way for the reason {@link commentSyntax} is: a boolean puts SQLite and Postgres on the
|
|
138
|
+
* same branch, and they need different answers.
|
|
139
|
+
*/
|
|
140
|
+
readonly stringSizing: 'text' | 'bounded-text' | 'varchar';
|
|
141
|
+
/** Whether the engine has unsigned integers, so `@Field({ unsigned: true })` reaches the column. */
|
|
142
|
+
readonly supportsUnsigned: boolean;
|
|
143
|
+
/**
|
|
144
|
+
* Whether one table can be reached by two cascading foreign-key paths. SQL Server refuses the
|
|
145
|
+
* constraint outright ("may cause cycles or multiple cascade paths", error 1785) and Oracle
|
|
146
|
+
* likewise, so a cascade is downgraded to `NO ACTION` there rather than emitting DDL the engine
|
|
147
|
+
* rejects - which would otherwise fail on any diamond-shaped schema at create time.
|
|
148
|
+
*/
|
|
149
|
+
readonly multipleCascadePaths: boolean;
|
|
150
|
+
/**
|
|
151
|
+
* Whether the engine has SQL-level cursors (`DECLARE`/`FETCH FORWARD`/`CLOSE`), which is how a
|
|
152
|
+
* driver with no cursor API of its own still streams a result set instead of buffering it - see
|
|
153
|
+
* `postgres/pgCursorStream.ts`. True across the Postgres wire family, whose spelling that helper
|
|
154
|
+
* speaks; SQL Server and Oracle have cursors of their own but not this syntax.
|
|
155
|
+
*
|
|
156
|
+
* The node-`pg` family reports `true` and goes on using `pg-query-stream`: the flag says the engine
|
|
157
|
+
* has cursors, not that the driver needs them.
|
|
158
|
+
*/
|
|
159
|
+
readonly serverSideCursors: boolean;
|
|
133
160
|
}
|
|
134
161
|
export interface DialectFeatures extends EngineFeatures, DriverCapabilities {
|
|
135
162
|
}
|
|
@@ -219,13 +246,13 @@ export interface QueryDialect {
|
|
|
219
246
|
/**
|
|
220
247
|
* Supported SQL dialect identifiers.
|
|
221
248
|
*/
|
|
222
|
-
export type SqlDialectName = 'postgres' | 'cockroachdb' | 'mysql' | 'mariadb' | 'sqlite';
|
|
249
|
+
export type SqlDialectName = 'postgres' | 'cockroachdb' | 'mysql' | 'mariadb' | 'sqlite' | 'mssql';
|
|
223
250
|
/**
|
|
224
251
|
* Minimal dialect interface exposing escapeIdChar for SQL operations
|
|
225
252
|
*/
|
|
226
253
|
export interface SqlQueryDialect extends QueryDialect {
|
|
227
254
|
/**
|
|
228
|
-
* The SQL dialect name (postgres, mysql, mariadb, sqlite).
|
|
255
|
+
* The SQL dialect name (postgres, mysql, mariadb, sqlite, mssql).
|
|
229
256
|
*/
|
|
230
257
|
readonly dialectName: SqlDialectName;
|
|
231
258
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DialectName } from './querier.js';
|
|
2
|
-
declare const KNOWN_MIGRATOR_DIALECTS: readonly ["postgres", "cockroachdb", "mysql", "mariadb", "sqlite", "mongodb"];
|
|
2
|
+
declare const KNOWN_MIGRATOR_DIALECTS: readonly ["postgres", "cockroachdb", "mysql", "mariadb", "sqlite", "mssql", "mongodb"];
|
|
3
3
|
export type KnownMigratorDialect = (typeof KNOWN_MIGRATOR_DIALECTS)[number];
|
|
4
4
|
/**
|
|
5
5
|
* Whether `d` is supported by built-in migrator introspection / schema generators.
|
package/dist/type/query.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FieldKey, IdKey, JsonFieldPaths, RelationKey, RelationTarget } from './entity.js';
|
|
1
|
+
import type { FieldKey, IdKey, JsonFieldPaths, RelationKey, RelationTarget, WrittenId } from './entity.js';
|
|
2
2
|
import type { QueryLock } from './queryLock.js';
|
|
3
3
|
import type { QueryRaw } from './queryRaw.js';
|
|
4
4
|
import type { QueryWhere } from './queryWhere.js';
|
|
@@ -407,7 +407,29 @@ export type QueryStringified = {
|
|
|
407
407
|
[K in keyof Query<unknown>]?: string;
|
|
408
408
|
};
|
|
409
409
|
/**
|
|
410
|
-
*
|
|
410
|
+
* What upserting one row reports, against the entity rather than the driver.
|
|
411
|
+
*
|
|
412
|
+
* `created` is here and not on {@link QueryUpsertManyResult} because it is only ever knowable for a
|
|
413
|
+
* single statement: a batch's `affectedRows` is a weighted sum on the dialects that report one at
|
|
414
|
+
* all, and a batch of mixed shapes is several statements.
|
|
415
|
+
*/
|
|
416
|
+
export type QueryUpsertOneResult<E> = {
|
|
417
|
+
readonly id?: WrittenId<E>;
|
|
418
|
+
readonly changes?: number;
|
|
419
|
+
/** Whether the record was created (`true`) or updated (`false`), where the dialect can tell. */
|
|
420
|
+
readonly created?: boolean;
|
|
421
|
+
};
|
|
422
|
+
/**
|
|
423
|
+
* What upserting many rows reports. `ids` is payload-aligned like an insert's, so it zips with the
|
|
424
|
+
* rows that were passed, and carries a composite key as the map naming it.
|
|
425
|
+
*/
|
|
426
|
+
export type QueryUpsertManyResult<E> = {
|
|
427
|
+
readonly ids: (WrittenId<E> | undefined)[];
|
|
428
|
+
readonly changes?: number;
|
|
429
|
+
};
|
|
430
|
+
/**
|
|
431
|
+
* result of an update operation, as the driver reports it - which is what `run` hands back, where
|
|
432
|
+
* there is no entity to name the ids against. The `QueryUpsert*Result` pair is the entity-level shape.
|
|
411
433
|
*/
|
|
412
434
|
export type QueryUpdateResult = {
|
|
413
435
|
/**
|
|
@@ -415,11 +437,12 @@ export type QueryUpdateResult = {
|
|
|
415
437
|
*/
|
|
416
438
|
changes?: number;
|
|
417
439
|
/**
|
|
418
|
-
* the
|
|
419
|
-
*
|
|
420
|
-
*
|
|
440
|
+
* the IDs the statement reported, in payload order, `undefined` where it reported none for that
|
|
441
|
+
* row - a MongoDB upsert names only the documents it inserted. Exact on `'returning'` dialects;
|
|
442
|
+
* inferred from the driver header on the others (see {@link InsertIdSource}), and absent
|
|
443
|
+
* altogether when the header reports nothing.
|
|
421
444
|
*/
|
|
422
|
-
ids?: PrimaryKey[];
|
|
445
|
+
ids?: (PrimaryKey | undefined)[];
|
|
423
446
|
/**
|
|
424
447
|
* first inserted ID.
|
|
425
448
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EntityData, EntityId, FieldKey, RelationKey, UpdatePayload, WrittenId } from './entity.js';
|
|
2
|
-
import type { QueryConflictPaths, QueryFilter, QueryFindResult, QueryOneProjected, QueryOptions, QueryPage, QueryProjected, QuerySearch, QueryStreamProjected,
|
|
2
|
+
import type { QueryConflictPaths, QueryFilter, QueryFindResult, QueryOneProjected, QueryOptions, QueryPage, QueryProjected, QuerySearch, QueryStreamProjected, QueryUpsertOneResult, QueryUpsertManyResult } from './query.js';
|
|
3
3
|
import type { QueryAggMap, QueryAggregate, QueryAggregateResult, QueryGroupMap } from './queryAggregate.js';
|
|
4
4
|
import type { Type } from './utility.js';
|
|
5
5
|
import type { QuerierCountedResult, QuerierResult, QuerierTransport } from './wire.js';
|
|
@@ -139,7 +139,7 @@ export interface UniversalQuerier extends SharedQuerier<'server', QueryOptions>
|
|
|
139
139
|
* @param payload the data to be persisted
|
|
140
140
|
* @return operation metadata; see {@link QueryUpdateResult}
|
|
141
141
|
*/
|
|
142
|
-
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<
|
|
142
|
+
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpsertOneResult<E>>;
|
|
143
143
|
/**
|
|
144
144
|
* Insert or update many records based on the conflict paths.
|
|
145
145
|
* @param entity the entity to persist on
|
|
@@ -147,7 +147,7 @@ export interface UniversalQuerier extends SharedQuerier<'server', QueryOptions>
|
|
|
147
147
|
* @param payload the data to be persisted
|
|
148
148
|
* @return operation metadata; see {@link QueryUpdateResult}
|
|
149
149
|
*/
|
|
150
|
-
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<
|
|
150
|
+
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpsertManyResult<E>>;
|
|
151
151
|
/**
|
|
152
152
|
* insert or update a record.
|
|
153
153
|
* @param entity the entity to persist on
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uql-orm",
|
|
3
3
|
"homepage": "https://uql-orm.dev",
|
|
4
|
-
"description": "The JSON-native TypeScript ORM for Bun, Browsers, Edge, Deno, Node, Workers. Supports PostgreSQL, PGlite, MySQL, MariaDB, SQLite, CockroachDB, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.",
|
|
4
|
+
"description": "The JSON-native TypeScript ORM for Bun, Browsers, Edge, Deno, Node, Workers. Supports PostgreSQL, PGlite, MySQL, MariaDB, SQLite, CockroachDB, SQL Server, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.52.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=24"
|
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
"./pglite": "./dist/pglite/index.js",
|
|
50
50
|
"./d1": "./dist/d1/index.js",
|
|
51
51
|
"./bunSql": "./dist/bunSql/index.js",
|
|
52
|
-
"./package.json": "./package.json"
|
|
52
|
+
"./package.json": "./package.json",
|
|
53
|
+
"./mssql": "./dist/mssql/index.js"
|
|
53
54
|
},
|
|
54
55
|
"files": [
|
|
55
56
|
"dist",
|
|
@@ -75,6 +76,7 @@
|
|
|
75
76
|
"express": ">=5.0.0",
|
|
76
77
|
"mariadb": ">=3.0.0",
|
|
77
78
|
"mongodb": ">=6.0.0",
|
|
79
|
+
"mssql": ">=11.0.0",
|
|
78
80
|
"mysql2": ">=3.0.0",
|
|
79
81
|
"pg": ">=8.0.0",
|
|
80
82
|
"pg-query-stream": ">=4.0.0",
|
|
@@ -125,6 +127,9 @@
|
|
|
125
127
|
},
|
|
126
128
|
"rxjs": {
|
|
127
129
|
"optional": true
|
|
130
|
+
},
|
|
131
|
+
"mssql": {
|
|
132
|
+
"optional": true
|
|
128
133
|
}
|
|
129
134
|
},
|
|
130
135
|
"devDependencies": {
|
|
@@ -139,12 +144,14 @@
|
|
|
139
144
|
"@tursodatabase/serverless": "^1.4.0",
|
|
140
145
|
"@types/better-sqlite3": "^9.6.0",
|
|
141
146
|
"@types/express": "^5.0.6",
|
|
147
|
+
"@types/mssql": "^12.3.0",
|
|
142
148
|
"@types/pg": "^8.23.1",
|
|
143
149
|
"@types/ws": "^8.18.1",
|
|
144
150
|
"better-sqlite3": "^13.0.3",
|
|
145
151
|
"express": "^5.2.1",
|
|
146
152
|
"mariadb": "^3.5.4",
|
|
147
153
|
"mongodb": "^7.6.0",
|
|
154
|
+
"mssql": "^11",
|
|
148
155
|
"mysql2": "^3.24.4",
|
|
149
156
|
"pg": "^8.23.0",
|
|
150
157
|
"pg-query-stream": "^4.17.0",
|
|
@@ -185,6 +192,9 @@
|
|
|
185
192
|
"sqlite",
|
|
186
193
|
"sqlite3",
|
|
187
194
|
"cockroachdb",
|
|
195
|
+
"mssql",
|
|
196
|
+
"sqlserver",
|
|
197
|
+
"sql-server",
|
|
188
198
|
"mongodb",
|
|
189
199
|
"mongo",
|
|
190
200
|
"libsql",
|