bun-query-builder 0.1.29 → 0.1.31
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/dist/bin/cli.js +196 -101
- package/dist/src/index.js +195 -100
- package/package.json +1 -1
package/dist/bin/cli.js
CHANGED
|
@@ -12391,6 +12391,63 @@ function validateIdentifier(name, context) {
|
|
|
12391
12391
|
throw new Error(`[query-builder] Invalid identifier${contextMsg}: '${name}'. Identifiers must start with a letter or underscore and contain only alphanumeric characters, underscores, and dots.`);
|
|
12392
12392
|
}
|
|
12393
12393
|
}
|
|
12394
|
+
function assertSafeWhereOperator(op, context) {
|
|
12395
|
+
if (typeof op !== "string")
|
|
12396
|
+
throw new TypeError(`[query-builder] ${context}: operator must be a string, got ${typeof op}`);
|
|
12397
|
+
const lower = op.toLowerCase();
|
|
12398
|
+
if (!SAFE_WHERE_OPERATORS.has(lower))
|
|
12399
|
+
throw new TypeError(`[query-builder] ${context}: refusing to use '${op}' as a SQL operator \u2014 not in the allowed set (${[...SAFE_WHERE_OPERATORS].join(", ")})`);
|
|
12400
|
+
return op;
|
|
12401
|
+
}
|
|
12402
|
+
function validateQualifiedIdentifier(value, context) {
|
|
12403
|
+
if (typeof value !== "string" || value.length === 0)
|
|
12404
|
+
throw new TypeError(`[query-builder] ${context}: identifier must be a non-empty string, got ${typeof value}`);
|
|
12405
|
+
if (value.length > 129)
|
|
12406
|
+
throw new TypeError(`[query-builder] ${context}: identifier '${value}' too long`);
|
|
12407
|
+
const parts = value.split(".");
|
|
12408
|
+
if (parts.length > 2)
|
|
12409
|
+
throw new TypeError(`[query-builder] ${context}: identifier '${value}' has more than one dot \u2014 only \`table.column\` is allowed`);
|
|
12410
|
+
for (const part of parts) {
|
|
12411
|
+
if (!/^[A-Z_][A-Z0-9_]*$/i.test(part))
|
|
12412
|
+
throw new TypeError(`[query-builder] ${context}: identifier segment '${part}' contains characters outside [A-Za-z0-9_]`);
|
|
12413
|
+
}
|
|
12414
|
+
}
|
|
12415
|
+
function assertSqlFragment(fragment, context) {
|
|
12416
|
+
if (fragment === null || fragment === undefined) {
|
|
12417
|
+
throw new TypeError(`[query-builder] ${context}: fragment must be a SqlFragment, got ${fragment}`);
|
|
12418
|
+
}
|
|
12419
|
+
if (typeof fragment === "string") {
|
|
12420
|
+
warnOnceBareSqlFragment(context);
|
|
12421
|
+
}
|
|
12422
|
+
}
|
|
12423
|
+
function warnOnceBareSqlFragment(context) {
|
|
12424
|
+
if (warnedSqlFragmentContexts.has(context))
|
|
12425
|
+
return;
|
|
12426
|
+
warnedSqlFragmentContexts.add(context);
|
|
12427
|
+
console.warn(`[query-builder] ${context}: bare string passed to a *Raw method. ` + `Prefer \`sql\`...\`\` tagged-template fragments so values are parameterised ` + `instead of concatenated \u2014 concatenating request input into SQL is an ` + `injection vector. This will become a hard error in a future release.`);
|
|
12428
|
+
}
|
|
12429
|
+
function formatSubqueryValue(val) {
|
|
12430
|
+
if (val === null)
|
|
12431
|
+
return "NULL";
|
|
12432
|
+
if (typeof val === "number" && Number.isFinite(val))
|
|
12433
|
+
return String(val);
|
|
12434
|
+
if (typeof val === "boolean")
|
|
12435
|
+
return val ? "1" : "0";
|
|
12436
|
+
if (typeof val === "string")
|
|
12437
|
+
return `'${val.replace(/'/g, "''")}'`;
|
|
12438
|
+
if (val instanceof Date)
|
|
12439
|
+
return `'${val.toISOString()}'`;
|
|
12440
|
+
throw new TypeError(`[query-builder] subquery condition: refusing to interpolate value of type ${typeof val}`);
|
|
12441
|
+
}
|
|
12442
|
+
function buildOverClause(partitionBy, orderBy) {
|
|
12443
|
+
const cols = Array.isArray(partitionBy) ? partitionBy : partitionBy ? [partitionBy] : [];
|
|
12444
|
+
const parts = [];
|
|
12445
|
+
if (cols.length)
|
|
12446
|
+
parts.push(`PARTITION BY ${cols.join(", ")}`);
|
|
12447
|
+
if (orderBy && orderBy.length)
|
|
12448
|
+
parts.push(`ORDER BY ${orderBy.map(([c, d]) => `${c} ${d === "desc" ? "DESC" : "ASC"}`).join(", ")}`);
|
|
12449
|
+
return parts.length ? `OVER (${parts.join(" ")})` : "OVER ()";
|
|
12450
|
+
}
|
|
12394
12451
|
|
|
12395
12452
|
class QueryCache {
|
|
12396
12453
|
cache = new Map;
|
|
@@ -12455,6 +12512,16 @@ function sleep(ms) {
|
|
|
12455
12512
|
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
12456
12513
|
}
|
|
12457
12514
|
function reorderSelectClauses(sql) {
|
|
12515
|
+
const hit = reorderCache.get(sql);
|
|
12516
|
+
if (hit !== undefined)
|
|
12517
|
+
return hit;
|
|
12518
|
+
const out = computeReorderedClauses(sql);
|
|
12519
|
+
if (reorderCache.size >= REORDER_CACHE_MAX)
|
|
12520
|
+
reorderCache.clear();
|
|
12521
|
+
reorderCache.set(sql, out);
|
|
12522
|
+
return out;
|
|
12523
|
+
}
|
|
12524
|
+
function computeReorderedClauses(sql) {
|
|
12458
12525
|
const KEYWORDS = [
|
|
12459
12526
|
{ key: "GROUP_BY", tokens: /^GROUP\s+BY\b/i },
|
|
12460
12527
|
{ key: "ORDER_BY", tokens: /^ORDER\s+BY\b/i },
|
|
@@ -12540,6 +12607,7 @@ function createQueryBuilder(state) {
|
|
|
12540
12607
|
const _sql = state?.sql ?? getOrCreateBunSql();
|
|
12541
12608
|
const meta = state?.meta;
|
|
12542
12609
|
const schema = state?.schema;
|
|
12610
|
+
const dynamicWhereColumnCache = new Map;
|
|
12543
12611
|
function applyCondition(expr) {
|
|
12544
12612
|
if (Array.isArray(expr)) {
|
|
12545
12613
|
const [col, op, val] = expr;
|
|
@@ -12858,65 +12926,9 @@ function createQueryBuilder(state) {
|
|
|
12858
12926
|
}
|
|
12859
12927
|
}
|
|
12860
12928
|
};
|
|
12861
|
-
const buildOverClause = (partitionBy, orderBy) => {
|
|
12862
|
-
const cols = Array.isArray(partitionBy) ? partitionBy : partitionBy ? [partitionBy] : [];
|
|
12863
|
-
const parts = [];
|
|
12864
|
-
if (cols.length)
|
|
12865
|
-
parts.push(`PARTITION BY ${cols.join(", ")}`);
|
|
12866
|
-
if (orderBy && orderBy.length)
|
|
12867
|
-
parts.push(`ORDER BY ${orderBy.map(([c, d]) => `${c} ${d === "desc" ? "DESC" : "ASC"}`).join(", ")}`);
|
|
12868
|
-
return parts.length ? `OVER (${parts.join(" ")})` : "OVER ()";
|
|
12869
|
-
};
|
|
12870
12929
|
const addWindowFunction = (fnExpr, alias, partitionBy, orderBy) => {
|
|
12871
12930
|
addToSelectClause(`${fnExpr} ${buildOverClause(partitionBy, orderBy)} AS ${alias}`);
|
|
12872
12931
|
};
|
|
12873
|
-
function assertSafeWhereOperator(op, context) {
|
|
12874
|
-
if (typeof op !== "string")
|
|
12875
|
-
throw new TypeError(`[query-builder] ${context}: operator must be a string, got ${typeof op}`);
|
|
12876
|
-
const lower = op.toLowerCase();
|
|
12877
|
-
if (!SAFE_WHERE_OPERATORS.has(lower))
|
|
12878
|
-
throw new TypeError(`[query-builder] ${context}: refusing to use '${op}' as a SQL operator \u2014 not in the allowed set (${[...SAFE_WHERE_OPERATORS].join(", ")})`);
|
|
12879
|
-
return op;
|
|
12880
|
-
}
|
|
12881
|
-
function validateQualifiedIdentifier(value, context) {
|
|
12882
|
-
if (typeof value !== "string" || value.length === 0)
|
|
12883
|
-
throw new TypeError(`[query-builder] ${context}: identifier must be a non-empty string, got ${typeof value}`);
|
|
12884
|
-
if (value.length > 129)
|
|
12885
|
-
throw new TypeError(`[query-builder] ${context}: identifier '${value}' too long`);
|
|
12886
|
-
const parts = value.split(".");
|
|
12887
|
-
if (parts.length > 2)
|
|
12888
|
-
throw new TypeError(`[query-builder] ${context}: identifier '${value}' has more than one dot \u2014 only \`table.column\` is allowed`);
|
|
12889
|
-
for (const part of parts) {
|
|
12890
|
-
if (!/^[A-Z_][A-Z0-9_]*$/i.test(part))
|
|
12891
|
-
throw new TypeError(`[query-builder] ${context}: identifier segment '${part}' contains characters outside [A-Za-z0-9_]`);
|
|
12892
|
-
}
|
|
12893
|
-
}
|
|
12894
|
-
function assertSqlFragment(fragment, context) {
|
|
12895
|
-
if (fragment === null || fragment === undefined) {
|
|
12896
|
-
throw new TypeError(`[query-builder] ${context}: fragment must be a SqlFragment, got ${fragment}`);
|
|
12897
|
-
}
|
|
12898
|
-
if (typeof fragment === "string") {
|
|
12899
|
-
warnOnceBareSqlFragment(context);
|
|
12900
|
-
}
|
|
12901
|
-
}
|
|
12902
|
-
const warnedSqlFragmentContexts = new Set;
|
|
12903
|
-
function warnOnceBareSqlFragment(context) {
|
|
12904
|
-
if (warnedSqlFragmentContexts.has(context))
|
|
12905
|
-
return;
|
|
12906
|
-
warnedSqlFragmentContexts.add(context);
|
|
12907
|
-
console.warn(`[query-builder] ${context}: bare string passed to a *Raw method. ` + `Prefer \`sql\`...\`\` tagged-template fragments so values are parameterised ` + `instead of concatenated \u2014 concatenating request input into SQL is an ` + `injection vector. This will become a hard error in a future release.`);
|
|
12908
|
-
}
|
|
12909
|
-
function formatSubqueryValue(val) {
|
|
12910
|
-
if (val === null)
|
|
12911
|
-
return "NULL";
|
|
12912
|
-
if (typeof val === "number" && Number.isFinite(val))
|
|
12913
|
-
return String(val);
|
|
12914
|
-
if (typeof val === "boolean")
|
|
12915
|
-
return val ? "1" : "0";
|
|
12916
|
-
if (typeof val === "string")
|
|
12917
|
-
return `'${val.replace(/'/g, "''")}'`;
|
|
12918
|
-
throw new TypeError(`[query-builder] subquery condition: refusing to interpolate value of type ${typeof val}`);
|
|
12919
|
-
}
|
|
12920
12932
|
const buildHasSubquery = (parentTable, targetTable, pk, callback) => {
|
|
12921
12933
|
validateIdentifier(parentTable, "relationship subquery (parent table)");
|
|
12922
12934
|
validateIdentifier(targetTable, "relationship subquery (target table)");
|
|
@@ -12949,7 +12961,7 @@ function createQueryBuilder(state) {
|
|
|
12949
12961
|
const subQb = {
|
|
12950
12962
|
where: (col, op, val) => {
|
|
12951
12963
|
validateIdentifier(col, "relationship subquery condition");
|
|
12952
|
-
return `${targetTable}.${col} ${op
|
|
12964
|
+
return `${targetTable}.${col} ${assertSafeWhereOperator(op, "whereHas callback")} ${formatSubqueryValue(val)}`;
|
|
12953
12965
|
}
|
|
12954
12966
|
};
|
|
12955
12967
|
const condition = callback(subQb);
|
|
@@ -12978,7 +12990,7 @@ function createQueryBuilder(state) {
|
|
|
12978
12990
|
const subQb = {
|
|
12979
12991
|
where: (col, op, val) => {
|
|
12980
12992
|
validateIdentifier(col, "relationship subquery condition");
|
|
12981
|
-
return `${targetTable}.${col} ${op
|
|
12993
|
+
return `${targetTable}.${col} ${assertSafeWhereOperator(op, "whereHas callback")} ${formatSubqueryValue(val)}`;
|
|
12982
12994
|
}
|
|
12983
12995
|
};
|
|
12984
12996
|
const condition = callback(subQb);
|
|
@@ -13282,6 +13294,54 @@ function createQueryBuilder(state) {
|
|
|
13282
13294
|
}
|
|
13283
13295
|
return "";
|
|
13284
13296
|
};
|
|
13297
|
+
const buildJoinConstraint = (targetTbl) => {
|
|
13298
|
+
if (!condition)
|
|
13299
|
+
return "";
|
|
13300
|
+
const frags = [];
|
|
13301
|
+
const addCmp = (col, op, val) => {
|
|
13302
|
+
validateIdentifier(String(col), "with() constraint column");
|
|
13303
|
+
const operator = assertSafeWhereOperator(op, "with() constraint operator");
|
|
13304
|
+
frags.push(`${targetTbl}.${String(col)} ${operator} ${formatSubqueryValue(val)}`);
|
|
13305
|
+
};
|
|
13306
|
+
const unsupported = (m) => () => {
|
|
13307
|
+
throw new Error(`[query-builder] with('${relationKey}', ...): ${m} is not supported inside a constraint callback on the JOIN-based builder \u2014 apply it to the outer query, or use the model layer's eager loading. (Silently ignoring it would return wrong data.)`);
|
|
13308
|
+
};
|
|
13309
|
+
const constraintQb = {
|
|
13310
|
+
where: (expr, op, val) => {
|
|
13311
|
+
if (Array.isArray(expr))
|
|
13312
|
+
addCmp(expr[0], expr[1], expr[2]);
|
|
13313
|
+
else if (expr && typeof expr === "object")
|
|
13314
|
+
for (const k of Object.keys(expr))
|
|
13315
|
+
addCmp(k, "=", expr[k]);
|
|
13316
|
+
else if (op !== undefined && val !== undefined)
|
|
13317
|
+
addCmp(expr, op, val);
|
|
13318
|
+
else if (op !== undefined)
|
|
13319
|
+
addCmp(expr, "=", op);
|
|
13320
|
+
return constraintQb;
|
|
13321
|
+
},
|
|
13322
|
+
whereIn: (col, vals) => {
|
|
13323
|
+
validateIdentifier(String(col), "with() constraint column");
|
|
13324
|
+
frags.push(`${targetTbl}.${String(col)} IN (${vals.map(formatSubqueryValue).join(", ")})`);
|
|
13325
|
+
return constraintQb;
|
|
13326
|
+
},
|
|
13327
|
+
whereNull: (col) => {
|
|
13328
|
+
validateIdentifier(String(col), "with() constraint column");
|
|
13329
|
+
frags.push(`${targetTbl}.${String(col)} IS NULL`);
|
|
13330
|
+
return constraintQb;
|
|
13331
|
+
},
|
|
13332
|
+
whereNotNull: (col) => {
|
|
13333
|
+
validateIdentifier(String(col), "with() constraint column");
|
|
13334
|
+
frags.push(`${targetTbl}.${String(col)} IS NOT NULL`);
|
|
13335
|
+
return constraintQb;
|
|
13336
|
+
},
|
|
13337
|
+
orderBy: unsupported("orderBy()"),
|
|
13338
|
+
limit: unsupported("limit()"),
|
|
13339
|
+
offset: unsupported("offset()"),
|
|
13340
|
+
take: unsupported("take()")
|
|
13341
|
+
};
|
|
13342
|
+
condition(constraintQb);
|
|
13343
|
+
return frags.length ? ` AND ${frags.join(" AND ")}` : "";
|
|
13344
|
+
};
|
|
13285
13345
|
const resolveTarget = () => {
|
|
13286
13346
|
const pick = (m) => {
|
|
13287
13347
|
const modelName = m?.[relationKey];
|
|
@@ -13326,7 +13386,7 @@ function createQueryBuilder(state) {
|
|
|
13326
13386
|
const throughPk = meta.primaryKeys[throughTable] ?? "id";
|
|
13327
13387
|
const fkInThrough = `${singularize(fromTable)}_id`;
|
|
13328
13388
|
const fkInFinal = `${singularize(throughTable)}_id`;
|
|
13329
|
-
|
|
13389
|
+
insertJoin(`LEFT JOIN ${throughTable} ON ${throughTable}.${fkInThrough} = ${fromTable}.${fromPk} LEFT JOIN ${finalTable} ON ${finalTable}.${fkInFinal} = ${throughTable}.${throughPk}`);
|
|
13330
13390
|
joinedTables.add(throughTable);
|
|
13331
13391
|
joinedTables.add(finalTable);
|
|
13332
13392
|
return finalTable;
|
|
@@ -13339,7 +13399,7 @@ function createQueryBuilder(state) {
|
|
|
13339
13399
|
const childPk = meta.primaryKeys[childTable] ?? "id";
|
|
13340
13400
|
const fkA = resolved?.fkParent ?? `${singularize(fromTable)}_id`;
|
|
13341
13401
|
const fkB = resolved?.fkRelated ?? `${singularize(childTable)}_id`;
|
|
13342
|
-
|
|
13402
|
+
insertJoin(`LEFT JOIN ${pivot} ON ${pivot}.${fkA} = ${fromTable}.${fromPk} LEFT JOIN ${childTable} ON ${childTable}.${childPk} = ${pivot}.${fkB}${buildJoinConstraint(childTable)}`);
|
|
13343
13403
|
joinedTables.add(pivot);
|
|
13344
13404
|
joinedTables.add(childTable);
|
|
13345
13405
|
return childTable;
|
|
@@ -13353,7 +13413,8 @@ function createQueryBuilder(state) {
|
|
|
13353
13413
|
const morphType = `${morphName}_type`;
|
|
13354
13414
|
const morphId = `${morphName}_id`;
|
|
13355
13415
|
const targetFk = `${singularize(childTable)}_id`;
|
|
13356
|
-
|
|
13416
|
+
const morphVal = formatSubqueryValue(meta.tableToModel[fromTable] || fromTable);
|
|
13417
|
+
insertJoin(`LEFT JOIN ${pivotTable} ON ${pivotTable}.${morphId} = ${fromTable}.${fromPk} AND ${pivotTable}.${morphType} = ${morphVal} LEFT JOIN ${childTable} ON ${childTable}.${childPk} = ${pivotTable}.${targetFk}`);
|
|
13357
13418
|
joinedTables.add(pivotTable);
|
|
13358
13419
|
joinedTables.add(childTable);
|
|
13359
13420
|
return childTable;
|
|
@@ -13369,7 +13430,8 @@ function createQueryBuilder(state) {
|
|
|
13369
13430
|
const morphType = `${morphName}_type`;
|
|
13370
13431
|
const morphId = `${morphName}_id`;
|
|
13371
13432
|
const relatedFk = `${singularize(relatedTable)}_id`;
|
|
13372
|
-
|
|
13433
|
+
const morphVal = formatSubqueryValue(meta.tableToModel[relatedTable] || relatedTable);
|
|
13434
|
+
insertJoin(`LEFT JOIN ${pivotTable} ON ${pivotTable}.${relatedFk} = ${fromTable}.${fromPk} LEFT JOIN ${relatedTable} ON ${relatedTable}.${relatedPk} = ${pivotTable}.${morphId} AND ${pivotTable}.${morphType} = ${morphVal}`);
|
|
13373
13435
|
joinedTables.add(pivotTable);
|
|
13374
13436
|
joinedTables.add(relatedTable);
|
|
13375
13437
|
return relatedTable;
|
|
@@ -13378,7 +13440,7 @@ function createQueryBuilder(state) {
|
|
|
13378
13440
|
if (isBt) {
|
|
13379
13441
|
const fkInParent = `${singularize(childTable)}_id`;
|
|
13380
13442
|
const childPk = meta.primaryKeys[childTable] ?? "id";
|
|
13381
|
-
|
|
13443
|
+
insertJoin(`LEFT JOIN ${childTable} ON ${fromTable}.${fkInParent} = ${childTable}.${childPk}${buildJoinConstraint(childTable)}`);
|
|
13382
13444
|
joinedTables.add(childTable);
|
|
13383
13445
|
return childTable;
|
|
13384
13446
|
}
|
|
@@ -13388,20 +13450,15 @@ function createQueryBuilder(state) {
|
|
|
13388
13450
|
const morphType = `${relationKey}_type`;
|
|
13389
13451
|
const morphId = `${relationKey}_id`;
|
|
13390
13452
|
const fromPk = meta.primaryKeys[fromTable] ?? "id";
|
|
13391
|
-
|
|
13453
|
+
const morphVal = formatSubqueryValue(meta.tableToModel[fromTable] || fromTable);
|
|
13454
|
+
insertJoin(`LEFT JOIN ${childTable} ON ${childTable}.${morphId} = ${fromTable}.${fromPk} AND ${childTable}.${morphType} = ${morphVal}`);
|
|
13392
13455
|
joinedTables.add(childTable);
|
|
13393
13456
|
return childTable;
|
|
13394
13457
|
}
|
|
13395
13458
|
const fkInChild = `${singularize(fromTable)}_id`;
|
|
13396
13459
|
const pk = meta.primaryKeys[fromTable] ?? "id";
|
|
13397
|
-
const
|
|
13398
|
-
|
|
13399
|
-
const currentSql = String(ensureBuilt());
|
|
13400
|
-
const joinCondition = `${childTable}.${fkInChild} = ${fromTable}.${pk}${softDeleteCheck}`;
|
|
13401
|
-
built = sql`${sql(currentSql)} LEFT JOIN ${sql(childTable)} ON ${sql(joinCondition)}`;
|
|
13402
|
-
} else {
|
|
13403
|
-
built = sql`${ensureBuilt()} LEFT JOIN ${sql(childTable)} ON ${sql(`${childTable}.${fkInChild}`)} = ${sql(`${fromTable}.${pk}`)}`;
|
|
13404
|
-
}
|
|
13460
|
+
const extraOn = `${addSoftDeleteCheck(childTable)}${buildJoinConstraint(childTable)}`;
|
|
13461
|
+
insertJoin(`LEFT JOIN ${childTable} ON ${childTable}.${fkInChild} = ${fromTable}.${pk}${extraOn}`);
|
|
13405
13462
|
joinedTables.add(childTable);
|
|
13406
13463
|
return childTable;
|
|
13407
13464
|
};
|
|
@@ -13438,7 +13495,7 @@ function createQueryBuilder(state) {
|
|
|
13438
13495
|
addToSelectClause(pivotColumnsStr);
|
|
13439
13496
|
}
|
|
13440
13497
|
}
|
|
13441
|
-
|
|
13498
|
+
built = null;
|
|
13442
13499
|
return this;
|
|
13443
13500
|
},
|
|
13444
13501
|
whereHas(relation, callback) {
|
|
@@ -14493,7 +14550,14 @@ function createQueryBuilder(state) {
|
|
|
14493
14550
|
return this;
|
|
14494
14551
|
},
|
|
14495
14552
|
toSQL() {
|
|
14496
|
-
|
|
14553
|
+
const sqlText = reorderSelectClauses(text);
|
|
14554
|
+
return {
|
|
14555
|
+
sql: sqlText,
|
|
14556
|
+
toString: () => sqlText,
|
|
14557
|
+
execute: () => ensureBuilt().execute(),
|
|
14558
|
+
values: () => ensureBuilt().values(),
|
|
14559
|
+
raw: () => ensureBuilt().raw()
|
|
14560
|
+
};
|
|
14497
14561
|
},
|
|
14498
14562
|
async value(column) {
|
|
14499
14563
|
const q = sql`${ensureBuilt()} LIMIT 1`;
|
|
@@ -14947,20 +15011,36 @@ function createQueryBuilder(state) {
|
|
|
14947
15011
|
if (typeof prop === "string" && (prop.startsWith("where") || prop.startsWith("orWhere") || prop.startsWith("andWhere"))) {
|
|
14948
15012
|
const isOr = prop.startsWith("orWhere");
|
|
14949
15013
|
const isAnd = prop.startsWith("andWhere");
|
|
14950
|
-
const
|
|
14951
|
-
|
|
15014
|
+
const cacheKey = `${String(table)}|${prop}`;
|
|
15015
|
+
let chosen = dynamicWhereColumnCache.get(cacheKey);
|
|
15016
|
+
if (chosen === undefined) {
|
|
15017
|
+
const raw = prop.replace(/^(?:or|and)?where/i, "");
|
|
15018
|
+
if (!raw) {
|
|
15019
|
+
dynamicWhereColumnCache.set(cacheKey, "");
|
|
15020
|
+
chosen = "";
|
|
15021
|
+
} else {
|
|
15022
|
+
const lowerFirst = raw.charAt(0).toLowerCase() + raw.slice(1);
|
|
15023
|
+
const snake = raw.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
|
|
15024
|
+
const available = schema ? Object.keys(schema[String(table)]?.columns ?? {}) : [];
|
|
15025
|
+
chosen = [snake, lowerFirst, lowerFirst.toLowerCase()].find((n) => available.includes(n)) ?? snake;
|
|
15026
|
+
dynamicWhereColumnCache.set(cacheKey, chosen);
|
|
15027
|
+
}
|
|
15028
|
+
}
|
|
15029
|
+
if (chosen === "")
|
|
14952
15030
|
return () => receiver;
|
|
14953
|
-
const
|
|
14954
|
-
const toSnake = (s) => s.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
|
|
14955
|
-
const snake = toSnake(raw);
|
|
14956
|
-
const tbl = String(table);
|
|
14957
|
-
const available = schema ? Object.keys(schema[tbl]?.columns ?? {}) : [];
|
|
14958
|
-
const chosen = [snake, lowerFirst, lowerFirst.toLowerCase()].find((n) => available.includes(n)) ?? snake;
|
|
15031
|
+
const column = chosen;
|
|
14959
15032
|
return (value) => {
|
|
14960
|
-
const expr = Array.isArray(value) ? sql`${sql(
|
|
15033
|
+
const expr = Array.isArray(value) ? sql`${sql(column)} IN ${sql(value)}` : sql`${sql(column)} = ${value}`;
|
|
14961
15034
|
built = isOr ? sql`${ensureBuilt()} OR ${expr}` : isAnd ? sql`${ensureBuilt()} AND ${expr}` : sql`${ensureBuilt()} WHERE ${expr}`;
|
|
14962
|
-
|
|
14963
|
-
|
|
15035
|
+
if (Array.isArray(value)) {
|
|
15036
|
+
const phs = getPlaceholders(value.length, whereParams.length + 1);
|
|
15037
|
+
addWhereText(isOr ? "OR" : isAnd ? "AND" : "WHERE", `${column} IN (${phs})`);
|
|
15038
|
+
whereParams.push(...value);
|
|
15039
|
+
} else {
|
|
15040
|
+
const ph = getPlaceholder(whereParams.length + 1);
|
|
15041
|
+
addWhereText(isOr ? "OR" : isAnd ? "AND" : "WHERE", `${column} = ${ph}`);
|
|
15042
|
+
whereParams.push(value);
|
|
15043
|
+
}
|
|
14964
15044
|
return receiver;
|
|
14965
15045
|
};
|
|
14966
15046
|
}
|
|
@@ -16208,7 +16288,7 @@ function clearQueryCache() {
|
|
|
16208
16288
|
function setQueryCacheMaxSize(size) {
|
|
16209
16289
|
queryCache.setMaxSize(size);
|
|
16210
16290
|
}
|
|
16211
|
-
var SQL_PATTERNS, SAFE_WHERE_OPERATORS, queryCache;
|
|
16291
|
+
var SQL_PATTERNS, SAFE_WHERE_OPERATORS, warnedSqlFragmentContexts, queryCache, reorderCache, REORDER_CACHE_MAX = 500;
|
|
16212
16292
|
var init_client = __esm(() => {
|
|
16213
16293
|
init_config();
|
|
16214
16294
|
init_db();
|
|
@@ -16243,7 +16323,9 @@ var init_client = __esm(() => {
|
|
|
16243
16323
|
"between",
|
|
16244
16324
|
"not between"
|
|
16245
16325
|
]);
|
|
16326
|
+
warnedSqlFragmentContexts = new Set;
|
|
16246
16327
|
queryCache = new QueryCache;
|
|
16328
|
+
reorderCache = new Map;
|
|
16247
16329
|
});
|
|
16248
16330
|
|
|
16249
16331
|
// src/actions/cache.ts
|
|
@@ -23821,8 +23903,15 @@ function getModelFromRegistry(name) {
|
|
|
23821
23903
|
return _getModel(name);
|
|
23822
23904
|
}
|
|
23823
23905
|
function toPostgresPlaceholders(sql2) {
|
|
23906
|
+
const hit = pgPlaceholderCache.get(sql2);
|
|
23907
|
+
if (hit !== undefined)
|
|
23908
|
+
return hit;
|
|
23824
23909
|
let i2 = 0;
|
|
23825
|
-
|
|
23910
|
+
const out = sql2.replace(/\?/g, () => `$${++i2}`);
|
|
23911
|
+
if (pgPlaceholderCache.size >= PG_PLACEHOLDER_CACHE_MAX)
|
|
23912
|
+
pgPlaceholderCache.clear();
|
|
23913
|
+
pgPlaceholderCache.set(sql2, out);
|
|
23914
|
+
return out;
|
|
23826
23915
|
}
|
|
23827
23916
|
function extractChanges(res) {
|
|
23828
23917
|
if (res == null)
|
|
@@ -23954,21 +24043,25 @@ function timestampsEnabled(definition) {
|
|
|
23954
24043
|
return Boolean(t2?.useTimestamps || t2?.timestampable);
|
|
23955
24044
|
}
|
|
23956
24045
|
function collectBelongsToManyKeys(definition) {
|
|
24046
|
+
const cached = btmKeysCache.get(definition);
|
|
24047
|
+
if (cached)
|
|
24048
|
+
return cached;
|
|
23957
24049
|
const keys = new Set;
|
|
23958
24050
|
const rel = definition.belongsToMany;
|
|
23959
|
-
if (
|
|
23960
|
-
|
|
23961
|
-
|
|
23962
|
-
|
|
23963
|
-
|
|
23964
|
-
|
|
23965
|
-
|
|
23966
|
-
|
|
23967
|
-
}
|
|
23968
|
-
|
|
23969
|
-
|
|
23970
|
-
|
|
24051
|
+
if (rel) {
|
|
24052
|
+
if (Array.isArray(rel)) {
|
|
24053
|
+
for (const item of rel) {
|
|
24054
|
+
if (typeof item === "string")
|
|
24055
|
+
keys.add(item.toLowerCase());
|
|
24056
|
+
else if (item && typeof item === "object" && item.model)
|
|
24057
|
+
keys.add(item.model.toLowerCase());
|
|
24058
|
+
}
|
|
24059
|
+
} else if (typeof rel === "object") {
|
|
24060
|
+
for (const k2 of Object.keys(rel))
|
|
24061
|
+
keys.add(k2);
|
|
24062
|
+
}
|
|
23971
24063
|
}
|
|
24064
|
+
btmKeysCache.set(definition, keys);
|
|
23972
24065
|
return keys;
|
|
23973
24066
|
}
|
|
23974
24067
|
|
|
@@ -25480,11 +25573,13 @@ async function seedModel(definition, count, faker) {
|
|
|
25480
25573
|
await exec.run(`INSERT INTO ${definition.table} (${columns.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`, Object.values(data));
|
|
25481
25574
|
}
|
|
25482
25575
|
}
|
|
25483
|
-
var SAFE_SQL_IDENTIFIER, _getModel = null, globalDb = null, _executor = null, _executorForDb = null, _executorDialect = null, _executorDatabase = null, SOFT_DELETE_COLUMN = "deleted_at", BTM_RELATED_ALIAS = "__btm_rel__", snakeCaseCache, tableNameCache, relationCache, RELATION_CACHE_MAX = 1000;
|
|
25576
|
+
var SAFE_SQL_IDENTIFIER, _getModel = null, pgPlaceholderCache, PG_PLACEHOLDER_CACHE_MAX = 500, globalDb = null, _executor = null, _executorForDb = null, _executorDialect = null, _executorDatabase = null, SOFT_DELETE_COLUMN = "deleted_at", BTM_RELATED_ALIAS = "__btm_rel__", btmKeysCache, snakeCaseCache, tableNameCache, relationCache, RELATION_CACHE_MAX = 1000;
|
|
25484
25577
|
var init_orm = __esm(() => {
|
|
25485
25578
|
init_config();
|
|
25486
25579
|
init_db();
|
|
25487
25580
|
SAFE_SQL_IDENTIFIER = /^[A-Z_][A-Z0-9_]*$/i;
|
|
25581
|
+
pgPlaceholderCache = new Map;
|
|
25582
|
+
btmKeysCache = new WeakMap;
|
|
25488
25583
|
snakeCaseCache = new Map;
|
|
25489
25584
|
tableNameCache = new Map;
|
|
25490
25585
|
relationCache = new Map;
|
|
@@ -29936,7 +30031,7 @@ function getPrefix() {
|
|
|
29936
30031
|
}
|
|
29937
30032
|
var prefix = getPrefix();
|
|
29938
30033
|
// package.json
|
|
29939
|
-
var version2 = "0.1.
|
|
30034
|
+
var version2 = "0.1.31";
|
|
29940
30035
|
|
|
29941
30036
|
// bin/cli.ts
|
|
29942
30037
|
init_actions();
|
package/dist/src/index.js
CHANGED
|
@@ -12391,6 +12391,63 @@ function validateIdentifier(name, context) {
|
|
|
12391
12391
|
throw new Error(`[query-builder] Invalid identifier${contextMsg}: '${name}'. Identifiers must start with a letter or underscore and contain only alphanumeric characters, underscores, and dots.`);
|
|
12392
12392
|
}
|
|
12393
12393
|
}
|
|
12394
|
+
function assertSafeWhereOperator(op, context) {
|
|
12395
|
+
if (typeof op !== "string")
|
|
12396
|
+
throw new TypeError(`[query-builder] ${context}: operator must be a string, got ${typeof op}`);
|
|
12397
|
+
const lower = op.toLowerCase();
|
|
12398
|
+
if (!SAFE_WHERE_OPERATORS.has(lower))
|
|
12399
|
+
throw new TypeError(`[query-builder] ${context}: refusing to use '${op}' as a SQL operator \u2014 not in the allowed set (${[...SAFE_WHERE_OPERATORS].join(", ")})`);
|
|
12400
|
+
return op;
|
|
12401
|
+
}
|
|
12402
|
+
function validateQualifiedIdentifier(value, context) {
|
|
12403
|
+
if (typeof value !== "string" || value.length === 0)
|
|
12404
|
+
throw new TypeError(`[query-builder] ${context}: identifier must be a non-empty string, got ${typeof value}`);
|
|
12405
|
+
if (value.length > 129)
|
|
12406
|
+
throw new TypeError(`[query-builder] ${context}: identifier '${value}' too long`);
|
|
12407
|
+
const parts = value.split(".");
|
|
12408
|
+
if (parts.length > 2)
|
|
12409
|
+
throw new TypeError(`[query-builder] ${context}: identifier '${value}' has more than one dot \u2014 only \`table.column\` is allowed`);
|
|
12410
|
+
for (const part of parts) {
|
|
12411
|
+
if (!/^[A-Z_][A-Z0-9_]*$/i.test(part))
|
|
12412
|
+
throw new TypeError(`[query-builder] ${context}: identifier segment '${part}' contains characters outside [A-Za-z0-9_]`);
|
|
12413
|
+
}
|
|
12414
|
+
}
|
|
12415
|
+
function assertSqlFragment(fragment, context) {
|
|
12416
|
+
if (fragment === null || fragment === undefined) {
|
|
12417
|
+
throw new TypeError(`[query-builder] ${context}: fragment must be a SqlFragment, got ${fragment}`);
|
|
12418
|
+
}
|
|
12419
|
+
if (typeof fragment === "string") {
|
|
12420
|
+
warnOnceBareSqlFragment(context);
|
|
12421
|
+
}
|
|
12422
|
+
}
|
|
12423
|
+
function warnOnceBareSqlFragment(context) {
|
|
12424
|
+
if (warnedSqlFragmentContexts.has(context))
|
|
12425
|
+
return;
|
|
12426
|
+
warnedSqlFragmentContexts.add(context);
|
|
12427
|
+
console.warn(`[query-builder] ${context}: bare string passed to a *Raw method. ` + `Prefer \`sql\`...\`\` tagged-template fragments so values are parameterised ` + `instead of concatenated \u2014 concatenating request input into SQL is an ` + `injection vector. This will become a hard error in a future release.`);
|
|
12428
|
+
}
|
|
12429
|
+
function formatSubqueryValue(val) {
|
|
12430
|
+
if (val === null)
|
|
12431
|
+
return "NULL";
|
|
12432
|
+
if (typeof val === "number" && Number.isFinite(val))
|
|
12433
|
+
return String(val);
|
|
12434
|
+
if (typeof val === "boolean")
|
|
12435
|
+
return val ? "1" : "0";
|
|
12436
|
+
if (typeof val === "string")
|
|
12437
|
+
return `'${val.replace(/'/g, "''")}'`;
|
|
12438
|
+
if (val instanceof Date)
|
|
12439
|
+
return `'${val.toISOString()}'`;
|
|
12440
|
+
throw new TypeError(`[query-builder] subquery condition: refusing to interpolate value of type ${typeof val}`);
|
|
12441
|
+
}
|
|
12442
|
+
function buildOverClause(partitionBy, orderBy) {
|
|
12443
|
+
const cols = Array.isArray(partitionBy) ? partitionBy : partitionBy ? [partitionBy] : [];
|
|
12444
|
+
const parts = [];
|
|
12445
|
+
if (cols.length)
|
|
12446
|
+
parts.push(`PARTITION BY ${cols.join(", ")}`);
|
|
12447
|
+
if (orderBy && orderBy.length)
|
|
12448
|
+
parts.push(`ORDER BY ${orderBy.map(([c, d]) => `${c} ${d === "desc" ? "DESC" : "ASC"}`).join(", ")}`);
|
|
12449
|
+
return parts.length ? `OVER (${parts.join(" ")})` : "OVER ()";
|
|
12450
|
+
}
|
|
12394
12451
|
|
|
12395
12452
|
class QueryCache {
|
|
12396
12453
|
cache = new Map;
|
|
@@ -12455,6 +12512,16 @@ function sleep(ms) {
|
|
|
12455
12512
|
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
12456
12513
|
}
|
|
12457
12514
|
function reorderSelectClauses(sql) {
|
|
12515
|
+
const hit = reorderCache.get(sql);
|
|
12516
|
+
if (hit !== undefined)
|
|
12517
|
+
return hit;
|
|
12518
|
+
const out = computeReorderedClauses(sql);
|
|
12519
|
+
if (reorderCache.size >= REORDER_CACHE_MAX)
|
|
12520
|
+
reorderCache.clear();
|
|
12521
|
+
reorderCache.set(sql, out);
|
|
12522
|
+
return out;
|
|
12523
|
+
}
|
|
12524
|
+
function computeReorderedClauses(sql) {
|
|
12458
12525
|
const KEYWORDS = [
|
|
12459
12526
|
{ key: "GROUP_BY", tokens: /^GROUP\s+BY\b/i },
|
|
12460
12527
|
{ key: "ORDER_BY", tokens: /^ORDER\s+BY\b/i },
|
|
@@ -12540,6 +12607,7 @@ function createQueryBuilder(state) {
|
|
|
12540
12607
|
const _sql = state?.sql ?? getOrCreateBunSql();
|
|
12541
12608
|
const meta = state?.meta;
|
|
12542
12609
|
const schema = state?.schema;
|
|
12610
|
+
const dynamicWhereColumnCache = new Map;
|
|
12543
12611
|
function applyCondition(expr) {
|
|
12544
12612
|
if (Array.isArray(expr)) {
|
|
12545
12613
|
const [col, op, val] = expr;
|
|
@@ -12858,65 +12926,9 @@ function createQueryBuilder(state) {
|
|
|
12858
12926
|
}
|
|
12859
12927
|
}
|
|
12860
12928
|
};
|
|
12861
|
-
const buildOverClause = (partitionBy, orderBy) => {
|
|
12862
|
-
const cols = Array.isArray(partitionBy) ? partitionBy : partitionBy ? [partitionBy] : [];
|
|
12863
|
-
const parts = [];
|
|
12864
|
-
if (cols.length)
|
|
12865
|
-
parts.push(`PARTITION BY ${cols.join(", ")}`);
|
|
12866
|
-
if (orderBy && orderBy.length)
|
|
12867
|
-
parts.push(`ORDER BY ${orderBy.map(([c, d]) => `${c} ${d === "desc" ? "DESC" : "ASC"}`).join(", ")}`);
|
|
12868
|
-
return parts.length ? `OVER (${parts.join(" ")})` : "OVER ()";
|
|
12869
|
-
};
|
|
12870
12929
|
const addWindowFunction = (fnExpr, alias, partitionBy, orderBy) => {
|
|
12871
12930
|
addToSelectClause(`${fnExpr} ${buildOverClause(partitionBy, orderBy)} AS ${alias}`);
|
|
12872
12931
|
};
|
|
12873
|
-
function assertSafeWhereOperator(op, context) {
|
|
12874
|
-
if (typeof op !== "string")
|
|
12875
|
-
throw new TypeError(`[query-builder] ${context}: operator must be a string, got ${typeof op}`);
|
|
12876
|
-
const lower = op.toLowerCase();
|
|
12877
|
-
if (!SAFE_WHERE_OPERATORS.has(lower))
|
|
12878
|
-
throw new TypeError(`[query-builder] ${context}: refusing to use '${op}' as a SQL operator \u2014 not in the allowed set (${[...SAFE_WHERE_OPERATORS].join(", ")})`);
|
|
12879
|
-
return op;
|
|
12880
|
-
}
|
|
12881
|
-
function validateQualifiedIdentifier(value, context) {
|
|
12882
|
-
if (typeof value !== "string" || value.length === 0)
|
|
12883
|
-
throw new TypeError(`[query-builder] ${context}: identifier must be a non-empty string, got ${typeof value}`);
|
|
12884
|
-
if (value.length > 129)
|
|
12885
|
-
throw new TypeError(`[query-builder] ${context}: identifier '${value}' too long`);
|
|
12886
|
-
const parts = value.split(".");
|
|
12887
|
-
if (parts.length > 2)
|
|
12888
|
-
throw new TypeError(`[query-builder] ${context}: identifier '${value}' has more than one dot \u2014 only \`table.column\` is allowed`);
|
|
12889
|
-
for (const part of parts) {
|
|
12890
|
-
if (!/^[A-Z_][A-Z0-9_]*$/i.test(part))
|
|
12891
|
-
throw new TypeError(`[query-builder] ${context}: identifier segment '${part}' contains characters outside [A-Za-z0-9_]`);
|
|
12892
|
-
}
|
|
12893
|
-
}
|
|
12894
|
-
function assertSqlFragment(fragment, context) {
|
|
12895
|
-
if (fragment === null || fragment === undefined) {
|
|
12896
|
-
throw new TypeError(`[query-builder] ${context}: fragment must be a SqlFragment, got ${fragment}`);
|
|
12897
|
-
}
|
|
12898
|
-
if (typeof fragment === "string") {
|
|
12899
|
-
warnOnceBareSqlFragment(context);
|
|
12900
|
-
}
|
|
12901
|
-
}
|
|
12902
|
-
const warnedSqlFragmentContexts = new Set;
|
|
12903
|
-
function warnOnceBareSqlFragment(context) {
|
|
12904
|
-
if (warnedSqlFragmentContexts.has(context))
|
|
12905
|
-
return;
|
|
12906
|
-
warnedSqlFragmentContexts.add(context);
|
|
12907
|
-
console.warn(`[query-builder] ${context}: bare string passed to a *Raw method. ` + `Prefer \`sql\`...\`\` tagged-template fragments so values are parameterised ` + `instead of concatenated \u2014 concatenating request input into SQL is an ` + `injection vector. This will become a hard error in a future release.`);
|
|
12908
|
-
}
|
|
12909
|
-
function formatSubqueryValue(val) {
|
|
12910
|
-
if (val === null)
|
|
12911
|
-
return "NULL";
|
|
12912
|
-
if (typeof val === "number" && Number.isFinite(val))
|
|
12913
|
-
return String(val);
|
|
12914
|
-
if (typeof val === "boolean")
|
|
12915
|
-
return val ? "1" : "0";
|
|
12916
|
-
if (typeof val === "string")
|
|
12917
|
-
return `'${val.replace(/'/g, "''")}'`;
|
|
12918
|
-
throw new TypeError(`[query-builder] subquery condition: refusing to interpolate value of type ${typeof val}`);
|
|
12919
|
-
}
|
|
12920
12932
|
const buildHasSubquery = (parentTable, targetTable, pk, callback) => {
|
|
12921
12933
|
validateIdentifier(parentTable, "relationship subquery (parent table)");
|
|
12922
12934
|
validateIdentifier(targetTable, "relationship subquery (target table)");
|
|
@@ -12949,7 +12961,7 @@ function createQueryBuilder(state) {
|
|
|
12949
12961
|
const subQb = {
|
|
12950
12962
|
where: (col, op, val) => {
|
|
12951
12963
|
validateIdentifier(col, "relationship subquery condition");
|
|
12952
|
-
return `${targetTable}.${col} ${op
|
|
12964
|
+
return `${targetTable}.${col} ${assertSafeWhereOperator(op, "whereHas callback")} ${formatSubqueryValue(val)}`;
|
|
12953
12965
|
}
|
|
12954
12966
|
};
|
|
12955
12967
|
const condition = callback(subQb);
|
|
@@ -12978,7 +12990,7 @@ function createQueryBuilder(state) {
|
|
|
12978
12990
|
const subQb = {
|
|
12979
12991
|
where: (col, op, val) => {
|
|
12980
12992
|
validateIdentifier(col, "relationship subquery condition");
|
|
12981
|
-
return `${targetTable}.${col} ${op
|
|
12993
|
+
return `${targetTable}.${col} ${assertSafeWhereOperator(op, "whereHas callback")} ${formatSubqueryValue(val)}`;
|
|
12982
12994
|
}
|
|
12983
12995
|
};
|
|
12984
12996
|
const condition = callback(subQb);
|
|
@@ -13282,6 +13294,54 @@ function createQueryBuilder(state) {
|
|
|
13282
13294
|
}
|
|
13283
13295
|
return "";
|
|
13284
13296
|
};
|
|
13297
|
+
const buildJoinConstraint = (targetTbl) => {
|
|
13298
|
+
if (!condition)
|
|
13299
|
+
return "";
|
|
13300
|
+
const frags = [];
|
|
13301
|
+
const addCmp = (col, op, val) => {
|
|
13302
|
+
validateIdentifier(String(col), "with() constraint column");
|
|
13303
|
+
const operator = assertSafeWhereOperator(op, "with() constraint operator");
|
|
13304
|
+
frags.push(`${targetTbl}.${String(col)} ${operator} ${formatSubqueryValue(val)}`);
|
|
13305
|
+
};
|
|
13306
|
+
const unsupported = (m) => () => {
|
|
13307
|
+
throw new Error(`[query-builder] with('${relationKey}', ...): ${m} is not supported inside a constraint callback on the JOIN-based builder \u2014 apply it to the outer query, or use the model layer's eager loading. (Silently ignoring it would return wrong data.)`);
|
|
13308
|
+
};
|
|
13309
|
+
const constraintQb = {
|
|
13310
|
+
where: (expr, op, val) => {
|
|
13311
|
+
if (Array.isArray(expr))
|
|
13312
|
+
addCmp(expr[0], expr[1], expr[2]);
|
|
13313
|
+
else if (expr && typeof expr === "object")
|
|
13314
|
+
for (const k of Object.keys(expr))
|
|
13315
|
+
addCmp(k, "=", expr[k]);
|
|
13316
|
+
else if (op !== undefined && val !== undefined)
|
|
13317
|
+
addCmp(expr, op, val);
|
|
13318
|
+
else if (op !== undefined)
|
|
13319
|
+
addCmp(expr, "=", op);
|
|
13320
|
+
return constraintQb;
|
|
13321
|
+
},
|
|
13322
|
+
whereIn: (col, vals) => {
|
|
13323
|
+
validateIdentifier(String(col), "with() constraint column");
|
|
13324
|
+
frags.push(`${targetTbl}.${String(col)} IN (${vals.map(formatSubqueryValue).join(", ")})`);
|
|
13325
|
+
return constraintQb;
|
|
13326
|
+
},
|
|
13327
|
+
whereNull: (col) => {
|
|
13328
|
+
validateIdentifier(String(col), "with() constraint column");
|
|
13329
|
+
frags.push(`${targetTbl}.${String(col)} IS NULL`);
|
|
13330
|
+
return constraintQb;
|
|
13331
|
+
},
|
|
13332
|
+
whereNotNull: (col) => {
|
|
13333
|
+
validateIdentifier(String(col), "with() constraint column");
|
|
13334
|
+
frags.push(`${targetTbl}.${String(col)} IS NOT NULL`);
|
|
13335
|
+
return constraintQb;
|
|
13336
|
+
},
|
|
13337
|
+
orderBy: unsupported("orderBy()"),
|
|
13338
|
+
limit: unsupported("limit()"),
|
|
13339
|
+
offset: unsupported("offset()"),
|
|
13340
|
+
take: unsupported("take()")
|
|
13341
|
+
};
|
|
13342
|
+
condition(constraintQb);
|
|
13343
|
+
return frags.length ? ` AND ${frags.join(" AND ")}` : "";
|
|
13344
|
+
};
|
|
13285
13345
|
const resolveTarget = () => {
|
|
13286
13346
|
const pick = (m) => {
|
|
13287
13347
|
const modelName = m?.[relationKey];
|
|
@@ -13326,7 +13386,7 @@ function createQueryBuilder(state) {
|
|
|
13326
13386
|
const throughPk = meta.primaryKeys[throughTable] ?? "id";
|
|
13327
13387
|
const fkInThrough = `${singularize(fromTable)}_id`;
|
|
13328
13388
|
const fkInFinal = `${singularize(throughTable)}_id`;
|
|
13329
|
-
|
|
13389
|
+
insertJoin(`LEFT JOIN ${throughTable} ON ${throughTable}.${fkInThrough} = ${fromTable}.${fromPk} LEFT JOIN ${finalTable} ON ${finalTable}.${fkInFinal} = ${throughTable}.${throughPk}`);
|
|
13330
13390
|
joinedTables.add(throughTable);
|
|
13331
13391
|
joinedTables.add(finalTable);
|
|
13332
13392
|
return finalTable;
|
|
@@ -13339,7 +13399,7 @@ function createQueryBuilder(state) {
|
|
|
13339
13399
|
const childPk = meta.primaryKeys[childTable] ?? "id";
|
|
13340
13400
|
const fkA = resolved?.fkParent ?? `${singularize(fromTable)}_id`;
|
|
13341
13401
|
const fkB = resolved?.fkRelated ?? `${singularize(childTable)}_id`;
|
|
13342
|
-
|
|
13402
|
+
insertJoin(`LEFT JOIN ${pivot} ON ${pivot}.${fkA} = ${fromTable}.${fromPk} LEFT JOIN ${childTable} ON ${childTable}.${childPk} = ${pivot}.${fkB}${buildJoinConstraint(childTable)}`);
|
|
13343
13403
|
joinedTables.add(pivot);
|
|
13344
13404
|
joinedTables.add(childTable);
|
|
13345
13405
|
return childTable;
|
|
@@ -13353,7 +13413,8 @@ function createQueryBuilder(state) {
|
|
|
13353
13413
|
const morphType = `${morphName}_type`;
|
|
13354
13414
|
const morphId = `${morphName}_id`;
|
|
13355
13415
|
const targetFk = `${singularize(childTable)}_id`;
|
|
13356
|
-
|
|
13416
|
+
const morphVal = formatSubqueryValue(meta.tableToModel[fromTable] || fromTable);
|
|
13417
|
+
insertJoin(`LEFT JOIN ${pivotTable} ON ${pivotTable}.${morphId} = ${fromTable}.${fromPk} AND ${pivotTable}.${morphType} = ${morphVal} LEFT JOIN ${childTable} ON ${childTable}.${childPk} = ${pivotTable}.${targetFk}`);
|
|
13357
13418
|
joinedTables.add(pivotTable);
|
|
13358
13419
|
joinedTables.add(childTable);
|
|
13359
13420
|
return childTable;
|
|
@@ -13369,7 +13430,8 @@ function createQueryBuilder(state) {
|
|
|
13369
13430
|
const morphType = `${morphName}_type`;
|
|
13370
13431
|
const morphId = `${morphName}_id`;
|
|
13371
13432
|
const relatedFk = `${singularize(relatedTable)}_id`;
|
|
13372
|
-
|
|
13433
|
+
const morphVal = formatSubqueryValue(meta.tableToModel[relatedTable] || relatedTable);
|
|
13434
|
+
insertJoin(`LEFT JOIN ${pivotTable} ON ${pivotTable}.${relatedFk} = ${fromTable}.${fromPk} LEFT JOIN ${relatedTable} ON ${relatedTable}.${relatedPk} = ${pivotTable}.${morphId} AND ${pivotTable}.${morphType} = ${morphVal}`);
|
|
13373
13435
|
joinedTables.add(pivotTable);
|
|
13374
13436
|
joinedTables.add(relatedTable);
|
|
13375
13437
|
return relatedTable;
|
|
@@ -13378,7 +13440,7 @@ function createQueryBuilder(state) {
|
|
|
13378
13440
|
if (isBt) {
|
|
13379
13441
|
const fkInParent = `${singularize(childTable)}_id`;
|
|
13380
13442
|
const childPk = meta.primaryKeys[childTable] ?? "id";
|
|
13381
|
-
|
|
13443
|
+
insertJoin(`LEFT JOIN ${childTable} ON ${fromTable}.${fkInParent} = ${childTable}.${childPk}${buildJoinConstraint(childTable)}`);
|
|
13382
13444
|
joinedTables.add(childTable);
|
|
13383
13445
|
return childTable;
|
|
13384
13446
|
}
|
|
@@ -13388,20 +13450,15 @@ function createQueryBuilder(state) {
|
|
|
13388
13450
|
const morphType = `${relationKey}_type`;
|
|
13389
13451
|
const morphId = `${relationKey}_id`;
|
|
13390
13452
|
const fromPk = meta.primaryKeys[fromTable] ?? "id";
|
|
13391
|
-
|
|
13453
|
+
const morphVal = formatSubqueryValue(meta.tableToModel[fromTable] || fromTable);
|
|
13454
|
+
insertJoin(`LEFT JOIN ${childTable} ON ${childTable}.${morphId} = ${fromTable}.${fromPk} AND ${childTable}.${morphType} = ${morphVal}`);
|
|
13392
13455
|
joinedTables.add(childTable);
|
|
13393
13456
|
return childTable;
|
|
13394
13457
|
}
|
|
13395
13458
|
const fkInChild = `${singularize(fromTable)}_id`;
|
|
13396
13459
|
const pk = meta.primaryKeys[fromTable] ?? "id";
|
|
13397
|
-
const
|
|
13398
|
-
|
|
13399
|
-
const currentSql = String(ensureBuilt());
|
|
13400
|
-
const joinCondition = `${childTable}.${fkInChild} = ${fromTable}.${pk}${softDeleteCheck}`;
|
|
13401
|
-
built = sql`${sql(currentSql)} LEFT JOIN ${sql(childTable)} ON ${sql(joinCondition)}`;
|
|
13402
|
-
} else {
|
|
13403
|
-
built = sql`${ensureBuilt()} LEFT JOIN ${sql(childTable)} ON ${sql(`${childTable}.${fkInChild}`)} = ${sql(`${fromTable}.${pk}`)}`;
|
|
13404
|
-
}
|
|
13460
|
+
const extraOn = `${addSoftDeleteCheck(childTable)}${buildJoinConstraint(childTable)}`;
|
|
13461
|
+
insertJoin(`LEFT JOIN ${childTable} ON ${childTable}.${fkInChild} = ${fromTable}.${pk}${extraOn}`);
|
|
13405
13462
|
joinedTables.add(childTable);
|
|
13406
13463
|
return childTable;
|
|
13407
13464
|
};
|
|
@@ -13438,7 +13495,7 @@ function createQueryBuilder(state) {
|
|
|
13438
13495
|
addToSelectClause(pivotColumnsStr);
|
|
13439
13496
|
}
|
|
13440
13497
|
}
|
|
13441
|
-
|
|
13498
|
+
built = null;
|
|
13442
13499
|
return this;
|
|
13443
13500
|
},
|
|
13444
13501
|
whereHas(relation, callback) {
|
|
@@ -14493,7 +14550,14 @@ function createQueryBuilder(state) {
|
|
|
14493
14550
|
return this;
|
|
14494
14551
|
},
|
|
14495
14552
|
toSQL() {
|
|
14496
|
-
|
|
14553
|
+
const sqlText = reorderSelectClauses(text);
|
|
14554
|
+
return {
|
|
14555
|
+
sql: sqlText,
|
|
14556
|
+
toString: () => sqlText,
|
|
14557
|
+
execute: () => ensureBuilt().execute(),
|
|
14558
|
+
values: () => ensureBuilt().values(),
|
|
14559
|
+
raw: () => ensureBuilt().raw()
|
|
14560
|
+
};
|
|
14497
14561
|
},
|
|
14498
14562
|
async value(column) {
|
|
14499
14563
|
const q = sql`${ensureBuilt()} LIMIT 1`;
|
|
@@ -14947,20 +15011,36 @@ function createQueryBuilder(state) {
|
|
|
14947
15011
|
if (typeof prop === "string" && (prop.startsWith("where") || prop.startsWith("orWhere") || prop.startsWith("andWhere"))) {
|
|
14948
15012
|
const isOr = prop.startsWith("orWhere");
|
|
14949
15013
|
const isAnd = prop.startsWith("andWhere");
|
|
14950
|
-
const
|
|
14951
|
-
|
|
15014
|
+
const cacheKey = `${String(table)}|${prop}`;
|
|
15015
|
+
let chosen = dynamicWhereColumnCache.get(cacheKey);
|
|
15016
|
+
if (chosen === undefined) {
|
|
15017
|
+
const raw = prop.replace(/^(?:or|and)?where/i, "");
|
|
15018
|
+
if (!raw) {
|
|
15019
|
+
dynamicWhereColumnCache.set(cacheKey, "");
|
|
15020
|
+
chosen = "";
|
|
15021
|
+
} else {
|
|
15022
|
+
const lowerFirst = raw.charAt(0).toLowerCase() + raw.slice(1);
|
|
15023
|
+
const snake = raw.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
|
|
15024
|
+
const available = schema ? Object.keys(schema[String(table)]?.columns ?? {}) : [];
|
|
15025
|
+
chosen = [snake, lowerFirst, lowerFirst.toLowerCase()].find((n) => available.includes(n)) ?? snake;
|
|
15026
|
+
dynamicWhereColumnCache.set(cacheKey, chosen);
|
|
15027
|
+
}
|
|
15028
|
+
}
|
|
15029
|
+
if (chosen === "")
|
|
14952
15030
|
return () => receiver;
|
|
14953
|
-
const
|
|
14954
|
-
const toSnake = (s) => s.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
|
|
14955
|
-
const snake = toSnake(raw);
|
|
14956
|
-
const tbl = String(table);
|
|
14957
|
-
const available = schema ? Object.keys(schema[tbl]?.columns ?? {}) : [];
|
|
14958
|
-
const chosen = [snake, lowerFirst, lowerFirst.toLowerCase()].find((n) => available.includes(n)) ?? snake;
|
|
15031
|
+
const column = chosen;
|
|
14959
15032
|
return (value) => {
|
|
14960
|
-
const expr = Array.isArray(value) ? sql`${sql(
|
|
15033
|
+
const expr = Array.isArray(value) ? sql`${sql(column)} IN ${sql(value)}` : sql`${sql(column)} = ${value}`;
|
|
14961
15034
|
built = isOr ? sql`${ensureBuilt()} OR ${expr}` : isAnd ? sql`${ensureBuilt()} AND ${expr}` : sql`${ensureBuilt()} WHERE ${expr}`;
|
|
14962
|
-
|
|
14963
|
-
|
|
15035
|
+
if (Array.isArray(value)) {
|
|
15036
|
+
const phs = getPlaceholders(value.length, whereParams.length + 1);
|
|
15037
|
+
addWhereText(isOr ? "OR" : isAnd ? "AND" : "WHERE", `${column} IN (${phs})`);
|
|
15038
|
+
whereParams.push(...value);
|
|
15039
|
+
} else {
|
|
15040
|
+
const ph = getPlaceholder(whereParams.length + 1);
|
|
15041
|
+
addWhereText(isOr ? "OR" : isAnd ? "AND" : "WHERE", `${column} = ${ph}`);
|
|
15042
|
+
whereParams.push(value);
|
|
15043
|
+
}
|
|
14964
15044
|
return receiver;
|
|
14965
15045
|
};
|
|
14966
15046
|
}
|
|
@@ -16208,7 +16288,7 @@ function clearQueryCache() {
|
|
|
16208
16288
|
function setQueryCacheMaxSize(size) {
|
|
16209
16289
|
queryCache.setMaxSize(size);
|
|
16210
16290
|
}
|
|
16211
|
-
var SQL_PATTERNS, SAFE_WHERE_OPERATORS, queryCache;
|
|
16291
|
+
var SQL_PATTERNS, SAFE_WHERE_OPERATORS, warnedSqlFragmentContexts, queryCache, reorderCache, REORDER_CACHE_MAX = 500;
|
|
16212
16292
|
var init_client = __esm(() => {
|
|
16213
16293
|
init_config();
|
|
16214
16294
|
init_db();
|
|
@@ -16243,7 +16323,9 @@ var init_client = __esm(() => {
|
|
|
16243
16323
|
"between",
|
|
16244
16324
|
"not between"
|
|
16245
16325
|
]);
|
|
16326
|
+
warnedSqlFragmentContexts = new Set;
|
|
16246
16327
|
queryCache = new QueryCache;
|
|
16328
|
+
reorderCache = new Map;
|
|
16247
16329
|
});
|
|
16248
16330
|
|
|
16249
16331
|
// src/actions/cache.ts
|
|
@@ -23821,8 +23903,15 @@ function getModelFromRegistry(name) {
|
|
|
23821
23903
|
return _getModel(name);
|
|
23822
23904
|
}
|
|
23823
23905
|
function toPostgresPlaceholders(sql2) {
|
|
23906
|
+
const hit = pgPlaceholderCache.get(sql2);
|
|
23907
|
+
if (hit !== undefined)
|
|
23908
|
+
return hit;
|
|
23824
23909
|
let i2 = 0;
|
|
23825
|
-
|
|
23910
|
+
const out = sql2.replace(/\?/g, () => `$${++i2}`);
|
|
23911
|
+
if (pgPlaceholderCache.size >= PG_PLACEHOLDER_CACHE_MAX)
|
|
23912
|
+
pgPlaceholderCache.clear();
|
|
23913
|
+
pgPlaceholderCache.set(sql2, out);
|
|
23914
|
+
return out;
|
|
23826
23915
|
}
|
|
23827
23916
|
function extractChanges(res) {
|
|
23828
23917
|
if (res == null)
|
|
@@ -23954,21 +24043,25 @@ function timestampsEnabled(definition) {
|
|
|
23954
24043
|
return Boolean(t2?.useTimestamps || t2?.timestampable);
|
|
23955
24044
|
}
|
|
23956
24045
|
function collectBelongsToManyKeys(definition) {
|
|
24046
|
+
const cached = btmKeysCache.get(definition);
|
|
24047
|
+
if (cached)
|
|
24048
|
+
return cached;
|
|
23957
24049
|
const keys = new Set;
|
|
23958
24050
|
const rel = definition.belongsToMany;
|
|
23959
|
-
if (
|
|
23960
|
-
|
|
23961
|
-
|
|
23962
|
-
|
|
23963
|
-
|
|
23964
|
-
|
|
23965
|
-
|
|
23966
|
-
|
|
23967
|
-
}
|
|
23968
|
-
|
|
23969
|
-
|
|
23970
|
-
|
|
24051
|
+
if (rel) {
|
|
24052
|
+
if (Array.isArray(rel)) {
|
|
24053
|
+
for (const item of rel) {
|
|
24054
|
+
if (typeof item === "string")
|
|
24055
|
+
keys.add(item.toLowerCase());
|
|
24056
|
+
else if (item && typeof item === "object" && item.model)
|
|
24057
|
+
keys.add(item.model.toLowerCase());
|
|
24058
|
+
}
|
|
24059
|
+
} else if (typeof rel === "object") {
|
|
24060
|
+
for (const k2 of Object.keys(rel))
|
|
24061
|
+
keys.add(k2);
|
|
24062
|
+
}
|
|
23971
24063
|
}
|
|
24064
|
+
btmKeysCache.set(definition, keys);
|
|
23972
24065
|
return keys;
|
|
23973
24066
|
}
|
|
23974
24067
|
|
|
@@ -25480,11 +25573,13 @@ async function seedModel(definition, count, faker) {
|
|
|
25480
25573
|
await exec.run(`INSERT INTO ${definition.table} (${columns.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`, Object.values(data));
|
|
25481
25574
|
}
|
|
25482
25575
|
}
|
|
25483
|
-
var SAFE_SQL_IDENTIFIER, _getModel = null, globalDb = null, _executor = null, _executorForDb = null, _executorDialect = null, _executorDatabase = null, SOFT_DELETE_COLUMN = "deleted_at", BTM_RELATED_ALIAS = "__btm_rel__", snakeCaseCache, tableNameCache, relationCache, RELATION_CACHE_MAX = 1000;
|
|
25576
|
+
var SAFE_SQL_IDENTIFIER, _getModel = null, pgPlaceholderCache, PG_PLACEHOLDER_CACHE_MAX = 500, globalDb = null, _executor = null, _executorForDb = null, _executorDialect = null, _executorDatabase = null, SOFT_DELETE_COLUMN = "deleted_at", BTM_RELATED_ALIAS = "__btm_rel__", btmKeysCache, snakeCaseCache, tableNameCache, relationCache, RELATION_CACHE_MAX = 1000;
|
|
25484
25577
|
var init_orm = __esm(() => {
|
|
25485
25578
|
init_config();
|
|
25486
25579
|
init_db();
|
|
25487
25580
|
SAFE_SQL_IDENTIFIER = /^[A-Z_][A-Z0-9_]*$/i;
|
|
25581
|
+
pgPlaceholderCache = new Map;
|
|
25582
|
+
btmKeysCache = new WeakMap;
|
|
25488
25583
|
snakeCaseCache = new Map;
|
|
25489
25584
|
tableNameCache = new Map;
|
|
25490
25585
|
relationCache = new Map;
|
package/package.json
CHANGED