bun-query-builder 0.1.30 → 0.1.32
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 +209 -68
- package/dist/client.d.ts +31 -0
- package/dist/src/index.js +202 -60
- package/package.json +1 -1
package/dist/bin/cli.js
CHANGED
|
@@ -12344,6 +12344,14 @@ function* iterateAllPivots(meta, options = {}) {
|
|
|
12344
12344
|
function isRawExpression(expr) {
|
|
12345
12345
|
return typeof expr === "object" && expr !== null && "raw" in expr && typeof expr.raw === "string";
|
|
12346
12346
|
}
|
|
12347
|
+
function raw(strings, ...values) {
|
|
12348
|
+
if (typeof strings === "string")
|
|
12349
|
+
return { raw: strings };
|
|
12350
|
+
let out = strings[0];
|
|
12351
|
+
for (let i = 0;i < values.length; i++)
|
|
12352
|
+
out += formatSubqueryValue(values[i]) + strings[i + 1];
|
|
12353
|
+
return { raw: out };
|
|
12354
|
+
}
|
|
12347
12355
|
function quoteInsertIdent(id) {
|
|
12348
12356
|
return config5.dialect === "mysql" ? `\`${id.replace(/`/g, "``")}\`` : `"${id.replace(/"/g, '""')}"`;
|
|
12349
12357
|
}
|
|
@@ -12412,13 +12420,31 @@ function validateQualifiedIdentifier(value, context) {
|
|
|
12412
12420
|
throw new TypeError(`[query-builder] ${context}: identifier segment '${part}' contains characters outside [A-Za-z0-9_]`);
|
|
12413
12421
|
}
|
|
12414
12422
|
}
|
|
12415
|
-
function
|
|
12416
|
-
if (fragment === null || fragment === undefined) {
|
|
12417
|
-
throw new TypeError(`[query-builder] ${context}: fragment must be a SqlFragment, got ${fragment}`);
|
|
12418
|
-
}
|
|
12423
|
+
function renderRawFragment(fragment, context) {
|
|
12419
12424
|
if (typeof fragment === "string") {
|
|
12420
12425
|
warnOnceBareSqlFragment(context);
|
|
12426
|
+
return fragment;
|
|
12421
12427
|
}
|
|
12428
|
+
if (fragment === null || fragment === undefined)
|
|
12429
|
+
throw new TypeError(`[query-builder] ${context}: fragment must be a SqlFragment, got ${fragment}`);
|
|
12430
|
+
if (isRawExpression(fragment))
|
|
12431
|
+
return fragment.raw;
|
|
12432
|
+
if (typeof fragment === "object") {
|
|
12433
|
+
const f = fragment;
|
|
12434
|
+
if (typeof f.raw === "string")
|
|
12435
|
+
return f.raw;
|
|
12436
|
+
if (typeof f.raw === "function") {
|
|
12437
|
+
const r = f.raw();
|
|
12438
|
+
if (typeof r === "string")
|
|
12439
|
+
return r;
|
|
12440
|
+
}
|
|
12441
|
+
if (typeof f.sql === "string")
|
|
12442
|
+
return f.sql;
|
|
12443
|
+
const s = String(fragment);
|
|
12444
|
+
if (s !== "[object Object]" && s !== "[object Promise]")
|
|
12445
|
+
return s;
|
|
12446
|
+
}
|
|
12447
|
+
throw new TypeError(`[query-builder] ${context}: cannot render this value as a SQL fragment. ` + `A Bun \`sql\`...\`\` query object cannot be converted to SQL text \u2014 pass a ` + `string, or use the exported \`raw\` helper: raw\`count(*) as c\` / raw('age > 18').`);
|
|
12422
12448
|
}
|
|
12423
12449
|
function warnOnceBareSqlFragment(context) {
|
|
12424
12450
|
if (warnedSqlFragmentContexts.has(context))
|
|
@@ -12435,6 +12461,8 @@ function formatSubqueryValue(val) {
|
|
|
12435
12461
|
return val ? "1" : "0";
|
|
12436
12462
|
if (typeof val === "string")
|
|
12437
12463
|
return `'${val.replace(/'/g, "''")}'`;
|
|
12464
|
+
if (val instanceof Date)
|
|
12465
|
+
return `'${val.toISOString()}'`;
|
|
12438
12466
|
throw new TypeError(`[query-builder] subquery condition: refusing to interpolate value of type ${typeof val}`);
|
|
12439
12467
|
}
|
|
12440
12468
|
function buildOverClause(partitionBy, orderBy) {
|
|
@@ -13091,12 +13119,12 @@ function createQueryBuilder(state) {
|
|
|
13091
13119
|
return this;
|
|
13092
13120
|
},
|
|
13093
13121
|
selectRaw(fragment) {
|
|
13094
|
-
|
|
13122
|
+
const frag = renderRawFragment(fragment, "selectRaw(fragment)");
|
|
13095
13123
|
const fromIdx = text.indexOf(" FROM ");
|
|
13096
13124
|
if (fromIdx !== -1) {
|
|
13097
|
-
text = `${text.substring(0, fromIdx)}, ${
|
|
13125
|
+
text = `${text.substring(0, fromIdx)}, ${frag}${text.substring(fromIdx)}`;
|
|
13098
13126
|
} else {
|
|
13099
|
-
text += `, ${
|
|
13127
|
+
text += `, ${frag}`;
|
|
13100
13128
|
}
|
|
13101
13129
|
built = null;
|
|
13102
13130
|
return this;
|
|
@@ -13209,12 +13237,15 @@ function createQueryBuilder(state) {
|
|
|
13209
13237
|
if (cols.length === 0)
|
|
13210
13238
|
return this;
|
|
13211
13239
|
const rendered = cols.map(renderSelectColumn);
|
|
13240
|
+
const distinctMatch = /^SELECT\s+(DISTINCT(?:\s+ON\s+\([^)]*\))?\s+)/i.exec(text);
|
|
13241
|
+
const distinctPrefix = distinctMatch ? distinctMatch[1] : "";
|
|
13212
13242
|
const fromIndex = text.indexOf(" FROM ");
|
|
13213
13243
|
if (fromIndex !== -1) {
|
|
13214
|
-
text = `SELECT ${rendered.join(", ")}${text.substring(fromIndex)}`;
|
|
13244
|
+
text = `SELECT ${distinctPrefix}${rendered.join(", ")}${text.substring(fromIndex)}`;
|
|
13215
13245
|
} else {
|
|
13216
|
-
text = `SELECT ${rendered.join(", ")} FROM ${table}`;
|
|
13246
|
+
text = `SELECT ${distinctPrefix}${rendered.join(", ")} FROM ${table}`;
|
|
13217
13247
|
}
|
|
13248
|
+
built = null;
|
|
13218
13249
|
return this;
|
|
13219
13250
|
},
|
|
13220
13251
|
addSelect(...columns2) {
|
|
@@ -13292,6 +13323,54 @@ function createQueryBuilder(state) {
|
|
|
13292
13323
|
}
|
|
13293
13324
|
return "";
|
|
13294
13325
|
};
|
|
13326
|
+
const buildJoinConstraint = (targetTbl) => {
|
|
13327
|
+
if (!condition)
|
|
13328
|
+
return "";
|
|
13329
|
+
const frags = [];
|
|
13330
|
+
const addCmp = (col, op, val) => {
|
|
13331
|
+
validateIdentifier(String(col), "with() constraint column");
|
|
13332
|
+
const operator = assertSafeWhereOperator(op, "with() constraint operator");
|
|
13333
|
+
frags.push(`${targetTbl}.${String(col)} ${operator} ${formatSubqueryValue(val)}`);
|
|
13334
|
+
};
|
|
13335
|
+
const unsupported = (m) => () => {
|
|
13336
|
+
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.)`);
|
|
13337
|
+
};
|
|
13338
|
+
const constraintQb = {
|
|
13339
|
+
where: (expr, op, val) => {
|
|
13340
|
+
if (Array.isArray(expr))
|
|
13341
|
+
addCmp(expr[0], expr[1], expr[2]);
|
|
13342
|
+
else if (expr && typeof expr === "object")
|
|
13343
|
+
for (const k of Object.keys(expr))
|
|
13344
|
+
addCmp(k, "=", expr[k]);
|
|
13345
|
+
else if (op !== undefined && val !== undefined)
|
|
13346
|
+
addCmp(expr, op, val);
|
|
13347
|
+
else if (op !== undefined)
|
|
13348
|
+
addCmp(expr, "=", op);
|
|
13349
|
+
return constraintQb;
|
|
13350
|
+
},
|
|
13351
|
+
whereIn: (col, vals) => {
|
|
13352
|
+
validateIdentifier(String(col), "with() constraint column");
|
|
13353
|
+
frags.push(`${targetTbl}.${String(col)} IN (${vals.map(formatSubqueryValue).join(", ")})`);
|
|
13354
|
+
return constraintQb;
|
|
13355
|
+
},
|
|
13356
|
+
whereNull: (col) => {
|
|
13357
|
+
validateIdentifier(String(col), "with() constraint column");
|
|
13358
|
+
frags.push(`${targetTbl}.${String(col)} IS NULL`);
|
|
13359
|
+
return constraintQb;
|
|
13360
|
+
},
|
|
13361
|
+
whereNotNull: (col) => {
|
|
13362
|
+
validateIdentifier(String(col), "with() constraint column");
|
|
13363
|
+
frags.push(`${targetTbl}.${String(col)} IS NOT NULL`);
|
|
13364
|
+
return constraintQb;
|
|
13365
|
+
},
|
|
13366
|
+
orderBy: unsupported("orderBy()"),
|
|
13367
|
+
limit: unsupported("limit()"),
|
|
13368
|
+
offset: unsupported("offset()"),
|
|
13369
|
+
take: unsupported("take()")
|
|
13370
|
+
};
|
|
13371
|
+
condition(constraintQb);
|
|
13372
|
+
return frags.length ? ` AND ${frags.join(" AND ")}` : "";
|
|
13373
|
+
};
|
|
13295
13374
|
const resolveTarget = () => {
|
|
13296
13375
|
const pick = (m) => {
|
|
13297
13376
|
const modelName = m?.[relationKey];
|
|
@@ -13336,7 +13415,7 @@ function createQueryBuilder(state) {
|
|
|
13336
13415
|
const throughPk = meta.primaryKeys[throughTable] ?? "id";
|
|
13337
13416
|
const fkInThrough = `${singularize(fromTable)}_id`;
|
|
13338
13417
|
const fkInFinal = `${singularize(throughTable)}_id`;
|
|
13339
|
-
|
|
13418
|
+
insertJoin(`LEFT JOIN ${throughTable} ON ${throughTable}.${fkInThrough} = ${fromTable}.${fromPk} LEFT JOIN ${finalTable} ON ${finalTable}.${fkInFinal} = ${throughTable}.${throughPk}`);
|
|
13340
13419
|
joinedTables.add(throughTable);
|
|
13341
13420
|
joinedTables.add(finalTable);
|
|
13342
13421
|
return finalTable;
|
|
@@ -13349,7 +13428,7 @@ function createQueryBuilder(state) {
|
|
|
13349
13428
|
const childPk = meta.primaryKeys[childTable] ?? "id";
|
|
13350
13429
|
const fkA = resolved?.fkParent ?? `${singularize(fromTable)}_id`;
|
|
13351
13430
|
const fkB = resolved?.fkRelated ?? `${singularize(childTable)}_id`;
|
|
13352
|
-
|
|
13431
|
+
insertJoin(`LEFT JOIN ${pivot} ON ${pivot}.${fkA} = ${fromTable}.${fromPk} LEFT JOIN ${childTable} ON ${childTable}.${childPk} = ${pivot}.${fkB}${buildJoinConstraint(childTable)}`);
|
|
13353
13432
|
joinedTables.add(pivot);
|
|
13354
13433
|
joinedTables.add(childTable);
|
|
13355
13434
|
return childTable;
|
|
@@ -13363,7 +13442,8 @@ function createQueryBuilder(state) {
|
|
|
13363
13442
|
const morphType = `${morphName}_type`;
|
|
13364
13443
|
const morphId = `${morphName}_id`;
|
|
13365
13444
|
const targetFk = `${singularize(childTable)}_id`;
|
|
13366
|
-
|
|
13445
|
+
const morphVal = formatSubqueryValue(meta.tableToModel[fromTable] || fromTable);
|
|
13446
|
+
insertJoin(`LEFT JOIN ${pivotTable} ON ${pivotTable}.${morphId} = ${fromTable}.${fromPk} AND ${pivotTable}.${morphType} = ${morphVal} LEFT JOIN ${childTable} ON ${childTable}.${childPk} = ${pivotTable}.${targetFk}`);
|
|
13367
13447
|
joinedTables.add(pivotTable);
|
|
13368
13448
|
joinedTables.add(childTable);
|
|
13369
13449
|
return childTable;
|
|
@@ -13379,7 +13459,8 @@ function createQueryBuilder(state) {
|
|
|
13379
13459
|
const morphType = `${morphName}_type`;
|
|
13380
13460
|
const morphId = `${morphName}_id`;
|
|
13381
13461
|
const relatedFk = `${singularize(relatedTable)}_id`;
|
|
13382
|
-
|
|
13462
|
+
const morphVal = formatSubqueryValue(meta.tableToModel[relatedTable] || relatedTable);
|
|
13463
|
+
insertJoin(`LEFT JOIN ${pivotTable} ON ${pivotTable}.${relatedFk} = ${fromTable}.${fromPk} LEFT JOIN ${relatedTable} ON ${relatedTable}.${relatedPk} = ${pivotTable}.${morphId} AND ${pivotTable}.${morphType} = ${morphVal}`);
|
|
13383
13464
|
joinedTables.add(pivotTable);
|
|
13384
13465
|
joinedTables.add(relatedTable);
|
|
13385
13466
|
return relatedTable;
|
|
@@ -13388,7 +13469,7 @@ function createQueryBuilder(state) {
|
|
|
13388
13469
|
if (isBt) {
|
|
13389
13470
|
const fkInParent = `${singularize(childTable)}_id`;
|
|
13390
13471
|
const childPk = meta.primaryKeys[childTable] ?? "id";
|
|
13391
|
-
|
|
13472
|
+
insertJoin(`LEFT JOIN ${childTable} ON ${fromTable}.${fkInParent} = ${childTable}.${childPk}${buildJoinConstraint(childTable)}`);
|
|
13392
13473
|
joinedTables.add(childTable);
|
|
13393
13474
|
return childTable;
|
|
13394
13475
|
}
|
|
@@ -13398,20 +13479,15 @@ function createQueryBuilder(state) {
|
|
|
13398
13479
|
const morphType = `${relationKey}_type`;
|
|
13399
13480
|
const morphId = `${relationKey}_id`;
|
|
13400
13481
|
const fromPk = meta.primaryKeys[fromTable] ?? "id";
|
|
13401
|
-
|
|
13482
|
+
const morphVal = formatSubqueryValue(meta.tableToModel[fromTable] || fromTable);
|
|
13483
|
+
insertJoin(`LEFT JOIN ${childTable} ON ${childTable}.${morphId} = ${fromTable}.${fromPk} AND ${childTable}.${morphType} = ${morphVal}`);
|
|
13402
13484
|
joinedTables.add(childTable);
|
|
13403
13485
|
return childTable;
|
|
13404
13486
|
}
|
|
13405
13487
|
const fkInChild = `${singularize(fromTable)}_id`;
|
|
13406
13488
|
const pk = meta.primaryKeys[fromTable] ?? "id";
|
|
13407
|
-
const
|
|
13408
|
-
|
|
13409
|
-
const currentSql = String(ensureBuilt());
|
|
13410
|
-
const joinCondition = `${childTable}.${fkInChild} = ${fromTable}.${pk}${softDeleteCheck}`;
|
|
13411
|
-
built = sql`${sql(currentSql)} LEFT JOIN ${sql(childTable)} ON ${sql(joinCondition)}`;
|
|
13412
|
-
} else {
|
|
13413
|
-
built = sql`${ensureBuilt()} LEFT JOIN ${sql(childTable)} ON ${sql(`${childTable}.${fkInChild}`)} = ${sql(`${fromTable}.${pk}`)}`;
|
|
13414
|
-
}
|
|
13489
|
+
const extraOn = `${addSoftDeleteCheck(childTable)}${buildJoinConstraint(childTable)}`;
|
|
13490
|
+
insertJoin(`LEFT JOIN ${childTable} ON ${childTable}.${fkInChild} = ${fromTable}.${pk}${extraOn}`);
|
|
13415
13491
|
joinedTables.add(childTable);
|
|
13416
13492
|
return childTable;
|
|
13417
13493
|
};
|
|
@@ -13448,7 +13524,7 @@ function createQueryBuilder(state) {
|
|
|
13448
13524
|
addToSelectClause(pivotColumnsStr);
|
|
13449
13525
|
}
|
|
13450
13526
|
}
|
|
13451
|
-
|
|
13527
|
+
built = null;
|
|
13452
13528
|
return this;
|
|
13453
13529
|
},
|
|
13454
13530
|
whereHas(relation, callback) {
|
|
@@ -14058,9 +14134,9 @@ function createQueryBuilder(state) {
|
|
|
14058
14134
|
return this;
|
|
14059
14135
|
},
|
|
14060
14136
|
whereRaw(fragment) {
|
|
14061
|
-
|
|
14137
|
+
const frag = renderRawFragment(fragment, "whereRaw(fragment)");
|
|
14062
14138
|
const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
|
|
14063
|
-
text += ` ${keyword} ${
|
|
14139
|
+
text += ` ${keyword} ${frag}`;
|
|
14064
14140
|
built = null;
|
|
14065
14141
|
return this;
|
|
14066
14142
|
},
|
|
@@ -14426,8 +14502,8 @@ function createQueryBuilder(state) {
|
|
|
14426
14502
|
return this;
|
|
14427
14503
|
},
|
|
14428
14504
|
groupByRaw(fragment) {
|
|
14429
|
-
|
|
14430
|
-
text = SQL_PATTERNS.GROUP_BY.test(text) ? `${text}, ${
|
|
14505
|
+
const frag = renderRawFragment(fragment, "groupByRaw(fragment)");
|
|
14506
|
+
text = SQL_PATTERNS.GROUP_BY.test(text) ? `${text}, ${frag}` : `${text} GROUP BY ${frag}`;
|
|
14431
14507
|
built = null;
|
|
14432
14508
|
return this;
|
|
14433
14509
|
},
|
|
@@ -14459,15 +14535,15 @@ function createQueryBuilder(state) {
|
|
|
14459
14535
|
return this;
|
|
14460
14536
|
},
|
|
14461
14537
|
havingRaw(fragment) {
|
|
14462
|
-
|
|
14538
|
+
const frag = renderRawFragment(fragment, "havingRaw(fragment)");
|
|
14463
14539
|
const kw = /\bHAVING\b/i.test(text) ? "AND" : "HAVING";
|
|
14464
|
-
text += ` ${kw} ${
|
|
14540
|
+
text += ` ${kw} ${frag}`;
|
|
14465
14541
|
built = null;
|
|
14466
14542
|
return this;
|
|
14467
14543
|
},
|
|
14468
14544
|
orderByRaw(fragment) {
|
|
14469
|
-
|
|
14470
|
-
text = SQL_PATTERNS.ORDER_BY.test(text) ? `${text}, ${
|
|
14545
|
+
const frag = renderRawFragment(fragment, "orderByRaw(fragment)");
|
|
14546
|
+
text = SQL_PATTERNS.ORDER_BY.test(text) ? `${text}, ${frag}` : `${text} ORDER BY ${frag}`;
|
|
14471
14547
|
built = null;
|
|
14472
14548
|
return this;
|
|
14473
14549
|
},
|
|
@@ -14650,20 +14726,20 @@ function createQueryBuilder(state) {
|
|
|
14650
14726
|
includeTrashed = true;
|
|
14651
14727
|
onlyTrashed = true;
|
|
14652
14728
|
const softDeleteColumn = config5.softDeletes?.column || "deleted_at";
|
|
14653
|
-
const splice = (
|
|
14654
|
-
const upper =
|
|
14729
|
+
const splice = (raw2, predicate2) => {
|
|
14730
|
+
const upper = raw2.toUpperCase();
|
|
14655
14731
|
let depth = 0;
|
|
14656
|
-
for (let i = 0;i <
|
|
14657
|
-
const c =
|
|
14732
|
+
for (let i = 0;i < raw2.length; i++) {
|
|
14733
|
+
const c = raw2[i];
|
|
14658
14734
|
if (c === "(")
|
|
14659
14735
|
depth++;
|
|
14660
14736
|
else if (c === ")")
|
|
14661
14737
|
depth--;
|
|
14662
|
-
else if (depth === 0 && upper.substring(i, i + 5) === "WHERE" && (i === 0 || /\s/.test(
|
|
14663
|
-
return `${
|
|
14738
|
+
else if (depth === 0 && upper.substring(i, i + 5) === "WHERE" && (i === 0 || /\s/.test(raw2[i - 1] ?? "")) && /\s/.test(raw2[i + 5] ?? "")) {
|
|
14739
|
+
return `${raw2.substring(0, i)}WHERE ${predicate2} AND ${raw2.substring(i + 6)}`;
|
|
14664
14740
|
}
|
|
14665
14741
|
}
|
|
14666
|
-
return `${
|
|
14742
|
+
return `${raw2} WHERE ${predicate2}`;
|
|
14667
14743
|
};
|
|
14668
14744
|
const predicate = `${table}.${softDeleteColumn} IS NOT NULL`;
|
|
14669
14745
|
text = splice(text, predicate);
|
|
@@ -14967,13 +15043,13 @@ function createQueryBuilder(state) {
|
|
|
14967
15043
|
const cacheKey = `${String(table)}|${prop}`;
|
|
14968
15044
|
let chosen = dynamicWhereColumnCache.get(cacheKey);
|
|
14969
15045
|
if (chosen === undefined) {
|
|
14970
|
-
const
|
|
14971
|
-
if (!
|
|
15046
|
+
const raw2 = prop.replace(/^(?:or|and)?where/i, "");
|
|
15047
|
+
if (!raw2) {
|
|
14972
15048
|
dynamicWhereColumnCache.set(cacheKey, "");
|
|
14973
15049
|
chosen = "";
|
|
14974
15050
|
} else {
|
|
14975
|
-
const lowerFirst =
|
|
14976
|
-
const snake =
|
|
15051
|
+
const lowerFirst = raw2.charAt(0).toLowerCase() + raw2.slice(1);
|
|
15052
|
+
const snake = raw2.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
|
|
14977
15053
|
const available = schema ? Object.keys(schema[String(table)]?.columns ?? {}) : [];
|
|
14978
15054
|
chosen = [snake, lowerFirst, lowerFirst.toLowerCase()].find((n) => available.includes(n)) ?? snake;
|
|
14979
15055
|
dynamicWhereColumnCache.set(cacheKey, chosen);
|
|
@@ -15363,6 +15439,10 @@ function createQueryBuilder(state) {
|
|
|
15363
15439
|
returning(...cols) {
|
|
15364
15440
|
const returningSql = `${sqlText} RETURNING ${cols.join(", ")}`;
|
|
15365
15441
|
const q = _sql.unsafe(returningSql, params);
|
|
15442
|
+
const runFirst = async () => {
|
|
15443
|
+
const rows = await runWithHooks(q, "insert");
|
|
15444
|
+
return Array.isArray(rows) ? rows[0] : rows;
|
|
15445
|
+
};
|
|
15366
15446
|
return {
|
|
15367
15447
|
where: () => this,
|
|
15368
15448
|
andWhere: () => this,
|
|
@@ -15371,7 +15451,22 @@ function createQueryBuilder(state) {
|
|
|
15371
15451
|
limit: () => this,
|
|
15372
15452
|
offset: () => this,
|
|
15373
15453
|
toSQL: () => makeExecutableQuery(q, returningSql),
|
|
15374
|
-
execute: () => runWithHooks(q, "insert")
|
|
15454
|
+
execute: () => runWithHooks(q, "insert"),
|
|
15455
|
+
get: () => runWithHooks(q, "insert"),
|
|
15456
|
+
first: runFirst,
|
|
15457
|
+
executeTakeFirst: runFirst,
|
|
15458
|
+
async firstOrFail() {
|
|
15459
|
+
const row = await runFirst();
|
|
15460
|
+
if (!row)
|
|
15461
|
+
throw new Error("Insert with RETURNING returned no rows");
|
|
15462
|
+
return row;
|
|
15463
|
+
},
|
|
15464
|
+
async executeTakeFirstOrThrow() {
|
|
15465
|
+
const row = await runFirst();
|
|
15466
|
+
if (!row)
|
|
15467
|
+
throw new Error("Insert with RETURNING returned no rows");
|
|
15468
|
+
return row;
|
|
15469
|
+
}
|
|
15375
15470
|
};
|
|
15376
15471
|
},
|
|
15377
15472
|
toSQL() {
|
|
@@ -15410,19 +15505,27 @@ function createQueryBuilder(state) {
|
|
|
15410
15505
|
returningAll() {
|
|
15411
15506
|
const returningSql = `${sqlText} RETURNING *`;
|
|
15412
15507
|
const q = _sql.unsafe(returningSql, params);
|
|
15508
|
+
const runFirst = async () => {
|
|
15509
|
+
const result = await runWithHooks(q, "insert");
|
|
15510
|
+
return Array.isArray(result) ? result[0] : result;
|
|
15511
|
+
};
|
|
15413
15512
|
return {
|
|
15414
15513
|
toSQL: () => makeExecutableQuery(q, returningSql),
|
|
15415
15514
|
execute: () => runWithHooks(q, "insert"),
|
|
15416
|
-
|
|
15417
|
-
|
|
15418
|
-
|
|
15515
|
+
get: () => runWithHooks(q, "insert"),
|
|
15516
|
+
first: runFirst,
|
|
15517
|
+
executeTakeFirst: runFirst,
|
|
15518
|
+
async firstOrFail() {
|
|
15519
|
+
const row = await runFirst();
|
|
15520
|
+
if (!row)
|
|
15521
|
+
throw new Error("Insert with RETURNING returned no rows");
|
|
15522
|
+
return row;
|
|
15419
15523
|
},
|
|
15420
15524
|
async executeTakeFirstOrThrow() {
|
|
15421
|
-
const
|
|
15422
|
-
|
|
15423
|
-
|
|
15424
|
-
|
|
15425
|
-
return first;
|
|
15525
|
+
const row = await runFirst();
|
|
15526
|
+
if (!row)
|
|
15527
|
+
throw new Error("Insert with RETURNING returned no rows");
|
|
15528
|
+
return row;
|
|
15426
15529
|
}
|
|
15427
15530
|
};
|
|
15428
15531
|
}
|
|
@@ -15486,6 +15589,10 @@ function createQueryBuilder(state) {
|
|
|
15486
15589
|
returning(...cols) {
|
|
15487
15590
|
const retText = `${sqlText} RETURNING ${cols.join(", ")}`;
|
|
15488
15591
|
const q = params.length > 0 ? _sql.unsafe(retText, params) : _sql.unsafe(retText);
|
|
15592
|
+
const runFirst = async () => {
|
|
15593
|
+
const rows = await runWithHooks(q, "update");
|
|
15594
|
+
return Array.isArray(rows) ? rows[0] : rows;
|
|
15595
|
+
};
|
|
15489
15596
|
const obj = {
|
|
15490
15597
|
where: () => obj,
|
|
15491
15598
|
andWhere: () => obj,
|
|
@@ -15494,7 +15601,22 @@ function createQueryBuilder(state) {
|
|
|
15494
15601
|
limit: () => obj,
|
|
15495
15602
|
offset: () => obj,
|
|
15496
15603
|
toSQL: () => makeExecutableQuery(q, retText),
|
|
15497
|
-
execute: () => runWithHooks(q, "update")
|
|
15604
|
+
execute: () => runWithHooks(q, "update"),
|
|
15605
|
+
get: () => runWithHooks(q, "update"),
|
|
15606
|
+
first: runFirst,
|
|
15607
|
+
executeTakeFirst: runFirst,
|
|
15608
|
+
async firstOrFail() {
|
|
15609
|
+
const row = await runFirst();
|
|
15610
|
+
if (!row)
|
|
15611
|
+
throw new Error("Update with RETURNING returned no rows");
|
|
15612
|
+
return row;
|
|
15613
|
+
},
|
|
15614
|
+
async executeTakeFirstOrThrow() {
|
|
15615
|
+
const row = await runFirst();
|
|
15616
|
+
if (!row)
|
|
15617
|
+
throw new Error("Update with RETURNING returned no rows");
|
|
15618
|
+
return row;
|
|
15619
|
+
}
|
|
15498
15620
|
};
|
|
15499
15621
|
return obj;
|
|
15500
15622
|
},
|
|
@@ -15593,6 +15715,10 @@ function createQueryBuilder(state) {
|
|
|
15593
15715
|
returning(...cols) {
|
|
15594
15716
|
const retText = `${sqlText} RETURNING ${cols.join(", ")}`;
|
|
15595
15717
|
const q = delParams.length > 0 ? _sql.unsafe(retText, delParams) : _sql.unsafe(retText);
|
|
15718
|
+
const runFirst = async () => {
|
|
15719
|
+
const rows = await runWithHooks(q, "delete");
|
|
15720
|
+
return Array.isArray(rows) ? rows[0] : rows;
|
|
15721
|
+
};
|
|
15596
15722
|
const obj = {
|
|
15597
15723
|
where: () => obj,
|
|
15598
15724
|
andWhere: () => obj,
|
|
@@ -15601,7 +15727,22 @@ function createQueryBuilder(state) {
|
|
|
15601
15727
|
limit: () => obj,
|
|
15602
15728
|
offset: () => obj,
|
|
15603
15729
|
toSQL: () => makeExecutableQuery(q, retText),
|
|
15604
|
-
execute: () => runWithHooks(q, "delete")
|
|
15730
|
+
execute: () => runWithHooks(q, "delete"),
|
|
15731
|
+
get: () => runWithHooks(q, "delete"),
|
|
15732
|
+
first: runFirst,
|
|
15733
|
+
executeTakeFirst: runFirst,
|
|
15734
|
+
async firstOrFail() {
|
|
15735
|
+
const row = await runFirst();
|
|
15736
|
+
if (!row)
|
|
15737
|
+
throw new Error("Delete with RETURNING returned no rows");
|
|
15738
|
+
return row;
|
|
15739
|
+
},
|
|
15740
|
+
async executeTakeFirstOrThrow() {
|
|
15741
|
+
const row = await runFirst();
|
|
15742
|
+
if (!row)
|
|
15743
|
+
throw new Error("Delete with RETURNING returned no rows");
|
|
15744
|
+
return row;
|
|
15745
|
+
}
|
|
15605
15746
|
};
|
|
15606
15747
|
return obj;
|
|
15607
15748
|
},
|
|
@@ -17953,8 +18094,8 @@ function loadPlanSnapshot(workspaceRoot, dialect) {
|
|
|
17953
18094
|
return;
|
|
17954
18095
|
}
|
|
17955
18096
|
try {
|
|
17956
|
-
const
|
|
17957
|
-
const parsed = JSON.parse(
|
|
18097
|
+
const raw2 = readFileSync3(snapshotPath, "utf8");
|
|
18098
|
+
const parsed = JSON.parse(raw2);
|
|
17958
18099
|
if (parsed?.plan && Array.isArray(parsed.plan.tables) && parsed.plan.dialect) {
|
|
17959
18100
|
return parsed.plan;
|
|
17960
18101
|
}
|
|
@@ -18013,8 +18154,8 @@ async function generateMigration(dir, opts = {}) {
|
|
|
18013
18154
|
const statePath = String(opts.state || defaultStatePath);
|
|
18014
18155
|
if (existsSync16(statePath)) {
|
|
18015
18156
|
try {
|
|
18016
|
-
const
|
|
18017
|
-
const parsed = JSON.parse(
|
|
18157
|
+
const raw2 = readFileSync3(statePath, "utf8");
|
|
18158
|
+
const parsed = JSON.parse(raw2);
|
|
18018
18159
|
previous = parsed?.plan && parsed.plan.tables ? parsed.plan : parsed?.tables ? parsed : undefined;
|
|
18019
18160
|
if (previous) {
|
|
18020
18161
|
info("-- Comparing with legacy state file (will migrate to new snapshot format)");
|
|
@@ -24604,10 +24745,10 @@ class BelongsToManyRelationBuilder {
|
|
|
24604
24745
|
hydrateRows(rows) {
|
|
24605
24746
|
const fkParent = this.fkParent;
|
|
24606
24747
|
const fkRelated = this.fkRelated;
|
|
24607
|
-
return rows.map((
|
|
24748
|
+
return rows.map((raw2) => {
|
|
24608
24749
|
const relatedRow = {};
|
|
24609
24750
|
const pivotExtras = {};
|
|
24610
|
-
for (const [k2, v2] of Object.entries(
|
|
24751
|
+
for (const [k2, v2] of Object.entries(raw2)) {
|
|
24611
24752
|
if (k2.startsWith(BTM_RELATED_ALIAS))
|
|
24612
24753
|
relatedRow[k2.slice(BTM_RELATED_ALIAS.length)] = v2;
|
|
24613
24754
|
else if (k2 !== fkParent && k2 !== fkRelated)
|
|
@@ -29073,11 +29214,11 @@ Received ${signal}, cleaning up...`);
|
|
|
29073
29214
|
argsAfterDoubleDashes = argv.slice(doubleDashesIndex + 1);
|
|
29074
29215
|
argv = argv.slice(0, doubleDashesIndex);
|
|
29075
29216
|
}
|
|
29076
|
-
const
|
|
29077
|
-
const parsed = { _:
|
|
29078
|
-
for (const name of Object.keys(
|
|
29217
|
+
const raw2 = parseArgv(argv, mriOptions);
|
|
29218
|
+
const parsed = { _: raw2._ };
|
|
29219
|
+
for (const name of Object.keys(raw2)) {
|
|
29079
29220
|
if (name !== "_") {
|
|
29080
|
-
parsed[camelcaseOptionName(name)] =
|
|
29221
|
+
parsed[camelcaseOptionName(name)] = raw2[name];
|
|
29081
29222
|
}
|
|
29082
29223
|
}
|
|
29083
29224
|
const args = parsed._;
|
|
@@ -29122,11 +29263,11 @@ Received ${signal}, cleaning up...`);
|
|
|
29122
29263
|
if (!isUsage)
|
|
29123
29264
|
return;
|
|
29124
29265
|
const e2 = err;
|
|
29125
|
-
const
|
|
29266
|
+
const raw2 = e2.message ?? "command-line error";
|
|
29126
29267
|
const label = this.name ? `${this.name}: ` : "";
|
|
29127
|
-
const suffix = /--help/.test(
|
|
29268
|
+
const suffix = /--help/.test(raw2) ? "" : `
|
|
29128
29269
|
Run \`${this.name ?? "cli"} --help\` for usage.`;
|
|
29129
|
-
process52.stderr.write(`${label}${
|
|
29270
|
+
process52.stderr.write(`${label}${raw2}${suffix}
|
|
29130
29271
|
`);
|
|
29131
29272
|
process52.exit(e2.exitCode ?? 2);
|
|
29132
29273
|
}
|
|
@@ -29984,7 +30125,7 @@ function getPrefix() {
|
|
|
29984
30125
|
}
|
|
29985
30126
|
var prefix = getPrefix();
|
|
29986
30127
|
// package.json
|
|
29987
|
-
var version2 = "0.1.
|
|
30128
|
+
var version2 = "0.1.32";
|
|
29988
30129
|
|
|
29989
30130
|
// bin/cli.ts
|
|
29990
30131
|
init_actions();
|
package/dist/client.d.ts
CHANGED
|
@@ -2,6 +2,33 @@ import { config } from './config';
|
|
|
2
2
|
import { resetConnection } from './db';
|
|
3
3
|
import type { DatabaseSchema } from './schema';
|
|
4
4
|
import type { SchemaMeta } from './meta';
|
|
5
|
+
/**
|
|
6
|
+
* # `raw`
|
|
7
|
+
*
|
|
8
|
+
* Build a raw SQL fragment for the `*Raw` builder methods (`whereRaw`,
|
|
9
|
+
* `selectRaw`, `orderByRaw`, `groupByRaw`, `havingRaw`) and `select()`.
|
|
10
|
+
*
|
|
11
|
+
* Use this INSTEAD of a Bun `sql\`...\`` tag: a Bun query object cannot be
|
|
12
|
+
* converted back to SQL text (it stringifies to "[object Promise]"), so it
|
|
13
|
+
* silently corrupts the generated SQL. `raw` returns a `{ raw: string }`
|
|
14
|
+
* fragment that the builder renders correctly and that satisfies the
|
|
15
|
+
* `SqlFragment` type (so it passes the bare-string injection guard).
|
|
16
|
+
*
|
|
17
|
+
* Interpolated values in the tagged-template form are SQL-escaped (strings
|
|
18
|
+
* single-quote-doubled, dates → ISO, numbers/booleans/null inlined) — the
|
|
19
|
+
* same escaping the relation-subquery builders use. For user input that must
|
|
20
|
+
* be parameterised, prefer the typed `where(...)` methods over `raw`.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* import { raw } from 'bun-query-builder'
|
|
25
|
+
* db.selectFrom('users').selectRaw(raw`count(*) as c`)
|
|
26
|
+
* db.selectFrom('users').whereRaw(raw('age > 18'))
|
|
27
|
+
* db.selectFrom('users').orderByRaw(raw`created_at desc`)
|
|
28
|
+
* db.selectFrom('orders').whereRaw(raw`status = ${userStatus}`) // value escaped
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function raw(strings: TemplateStringsArray | string, ...values: unknown[]): RawExpression;
|
|
5
32
|
// eslint-disable-next-line pickier/no-unused-vars
|
|
6
33
|
export declare function createQueryBuilder<DB extends DatabaseSchema<any>>(state?: Partial<InternalState>): QueryBuilder<DB>;
|
|
7
34
|
/**
|
|
@@ -26,6 +53,10 @@ export declare function clearQueryCache(): void;
|
|
|
26
53
|
* ```
|
|
27
54
|
*/
|
|
28
55
|
export declare function setQueryCacheMaxSize(size: number): void;
|
|
56
|
+
// Type guard for raw SQL expressions
|
|
57
|
+
declare interface RawExpression {
|
|
58
|
+
raw: string
|
|
59
|
+
}
|
|
29
60
|
export declare interface WhereRaw {
|
|
30
61
|
raw: any
|
|
31
62
|
}
|
package/dist/src/index.js
CHANGED
|
@@ -12344,6 +12344,14 @@ function* iterateAllPivots(meta, options = {}) {
|
|
|
12344
12344
|
function isRawExpression(expr) {
|
|
12345
12345
|
return typeof expr === "object" && expr !== null && "raw" in expr && typeof expr.raw === "string";
|
|
12346
12346
|
}
|
|
12347
|
+
function raw(strings, ...values) {
|
|
12348
|
+
if (typeof strings === "string")
|
|
12349
|
+
return { raw: strings };
|
|
12350
|
+
let out = strings[0];
|
|
12351
|
+
for (let i = 0;i < values.length; i++)
|
|
12352
|
+
out += formatSubqueryValue(values[i]) + strings[i + 1];
|
|
12353
|
+
return { raw: out };
|
|
12354
|
+
}
|
|
12347
12355
|
function quoteInsertIdent(id) {
|
|
12348
12356
|
return config5.dialect === "mysql" ? `\`${id.replace(/`/g, "``")}\`` : `"${id.replace(/"/g, '""')}"`;
|
|
12349
12357
|
}
|
|
@@ -12412,13 +12420,31 @@ function validateQualifiedIdentifier(value, context) {
|
|
|
12412
12420
|
throw new TypeError(`[query-builder] ${context}: identifier segment '${part}' contains characters outside [A-Za-z0-9_]`);
|
|
12413
12421
|
}
|
|
12414
12422
|
}
|
|
12415
|
-
function
|
|
12416
|
-
if (fragment === null || fragment === undefined) {
|
|
12417
|
-
throw new TypeError(`[query-builder] ${context}: fragment must be a SqlFragment, got ${fragment}`);
|
|
12418
|
-
}
|
|
12423
|
+
function renderRawFragment(fragment, context) {
|
|
12419
12424
|
if (typeof fragment === "string") {
|
|
12420
12425
|
warnOnceBareSqlFragment(context);
|
|
12426
|
+
return fragment;
|
|
12421
12427
|
}
|
|
12428
|
+
if (fragment === null || fragment === undefined)
|
|
12429
|
+
throw new TypeError(`[query-builder] ${context}: fragment must be a SqlFragment, got ${fragment}`);
|
|
12430
|
+
if (isRawExpression(fragment))
|
|
12431
|
+
return fragment.raw;
|
|
12432
|
+
if (typeof fragment === "object") {
|
|
12433
|
+
const f = fragment;
|
|
12434
|
+
if (typeof f.raw === "string")
|
|
12435
|
+
return f.raw;
|
|
12436
|
+
if (typeof f.raw === "function") {
|
|
12437
|
+
const r = f.raw();
|
|
12438
|
+
if (typeof r === "string")
|
|
12439
|
+
return r;
|
|
12440
|
+
}
|
|
12441
|
+
if (typeof f.sql === "string")
|
|
12442
|
+
return f.sql;
|
|
12443
|
+
const s = String(fragment);
|
|
12444
|
+
if (s !== "[object Object]" && s !== "[object Promise]")
|
|
12445
|
+
return s;
|
|
12446
|
+
}
|
|
12447
|
+
throw new TypeError(`[query-builder] ${context}: cannot render this value as a SQL fragment. ` + `A Bun \`sql\`...\`\` query object cannot be converted to SQL text \u2014 pass a ` + `string, or use the exported \`raw\` helper: raw\`count(*) as c\` / raw('age > 18').`);
|
|
12422
12448
|
}
|
|
12423
12449
|
function warnOnceBareSqlFragment(context) {
|
|
12424
12450
|
if (warnedSqlFragmentContexts.has(context))
|
|
@@ -12435,6 +12461,8 @@ function formatSubqueryValue(val) {
|
|
|
12435
12461
|
return val ? "1" : "0";
|
|
12436
12462
|
if (typeof val === "string")
|
|
12437
12463
|
return `'${val.replace(/'/g, "''")}'`;
|
|
12464
|
+
if (val instanceof Date)
|
|
12465
|
+
return `'${val.toISOString()}'`;
|
|
12438
12466
|
throw new TypeError(`[query-builder] subquery condition: refusing to interpolate value of type ${typeof val}`);
|
|
12439
12467
|
}
|
|
12440
12468
|
function buildOverClause(partitionBy, orderBy) {
|
|
@@ -13091,12 +13119,12 @@ function createQueryBuilder(state) {
|
|
|
13091
13119
|
return this;
|
|
13092
13120
|
},
|
|
13093
13121
|
selectRaw(fragment) {
|
|
13094
|
-
|
|
13122
|
+
const frag = renderRawFragment(fragment, "selectRaw(fragment)");
|
|
13095
13123
|
const fromIdx = text.indexOf(" FROM ");
|
|
13096
13124
|
if (fromIdx !== -1) {
|
|
13097
|
-
text = `${text.substring(0, fromIdx)}, ${
|
|
13125
|
+
text = `${text.substring(0, fromIdx)}, ${frag}${text.substring(fromIdx)}`;
|
|
13098
13126
|
} else {
|
|
13099
|
-
text += `, ${
|
|
13127
|
+
text += `, ${frag}`;
|
|
13100
13128
|
}
|
|
13101
13129
|
built = null;
|
|
13102
13130
|
return this;
|
|
@@ -13209,12 +13237,15 @@ function createQueryBuilder(state) {
|
|
|
13209
13237
|
if (cols.length === 0)
|
|
13210
13238
|
return this;
|
|
13211
13239
|
const rendered = cols.map(renderSelectColumn);
|
|
13240
|
+
const distinctMatch = /^SELECT\s+(DISTINCT(?:\s+ON\s+\([^)]*\))?\s+)/i.exec(text);
|
|
13241
|
+
const distinctPrefix = distinctMatch ? distinctMatch[1] : "";
|
|
13212
13242
|
const fromIndex = text.indexOf(" FROM ");
|
|
13213
13243
|
if (fromIndex !== -1) {
|
|
13214
|
-
text = `SELECT ${rendered.join(", ")}${text.substring(fromIndex)}`;
|
|
13244
|
+
text = `SELECT ${distinctPrefix}${rendered.join(", ")}${text.substring(fromIndex)}`;
|
|
13215
13245
|
} else {
|
|
13216
|
-
text = `SELECT ${rendered.join(", ")} FROM ${table}`;
|
|
13246
|
+
text = `SELECT ${distinctPrefix}${rendered.join(", ")} FROM ${table}`;
|
|
13217
13247
|
}
|
|
13248
|
+
built = null;
|
|
13218
13249
|
return this;
|
|
13219
13250
|
},
|
|
13220
13251
|
addSelect(...columns2) {
|
|
@@ -13292,6 +13323,54 @@ function createQueryBuilder(state) {
|
|
|
13292
13323
|
}
|
|
13293
13324
|
return "";
|
|
13294
13325
|
};
|
|
13326
|
+
const buildJoinConstraint = (targetTbl) => {
|
|
13327
|
+
if (!condition)
|
|
13328
|
+
return "";
|
|
13329
|
+
const frags = [];
|
|
13330
|
+
const addCmp = (col, op, val) => {
|
|
13331
|
+
validateIdentifier(String(col), "with() constraint column");
|
|
13332
|
+
const operator = assertSafeWhereOperator(op, "with() constraint operator");
|
|
13333
|
+
frags.push(`${targetTbl}.${String(col)} ${operator} ${formatSubqueryValue(val)}`);
|
|
13334
|
+
};
|
|
13335
|
+
const unsupported = (m) => () => {
|
|
13336
|
+
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.)`);
|
|
13337
|
+
};
|
|
13338
|
+
const constraintQb = {
|
|
13339
|
+
where: (expr, op, val) => {
|
|
13340
|
+
if (Array.isArray(expr))
|
|
13341
|
+
addCmp(expr[0], expr[1], expr[2]);
|
|
13342
|
+
else if (expr && typeof expr === "object")
|
|
13343
|
+
for (const k of Object.keys(expr))
|
|
13344
|
+
addCmp(k, "=", expr[k]);
|
|
13345
|
+
else if (op !== undefined && val !== undefined)
|
|
13346
|
+
addCmp(expr, op, val);
|
|
13347
|
+
else if (op !== undefined)
|
|
13348
|
+
addCmp(expr, "=", op);
|
|
13349
|
+
return constraintQb;
|
|
13350
|
+
},
|
|
13351
|
+
whereIn: (col, vals) => {
|
|
13352
|
+
validateIdentifier(String(col), "with() constraint column");
|
|
13353
|
+
frags.push(`${targetTbl}.${String(col)} IN (${vals.map(formatSubqueryValue).join(", ")})`);
|
|
13354
|
+
return constraintQb;
|
|
13355
|
+
},
|
|
13356
|
+
whereNull: (col) => {
|
|
13357
|
+
validateIdentifier(String(col), "with() constraint column");
|
|
13358
|
+
frags.push(`${targetTbl}.${String(col)} IS NULL`);
|
|
13359
|
+
return constraintQb;
|
|
13360
|
+
},
|
|
13361
|
+
whereNotNull: (col) => {
|
|
13362
|
+
validateIdentifier(String(col), "with() constraint column");
|
|
13363
|
+
frags.push(`${targetTbl}.${String(col)} IS NOT NULL`);
|
|
13364
|
+
return constraintQb;
|
|
13365
|
+
},
|
|
13366
|
+
orderBy: unsupported("orderBy()"),
|
|
13367
|
+
limit: unsupported("limit()"),
|
|
13368
|
+
offset: unsupported("offset()"),
|
|
13369
|
+
take: unsupported("take()")
|
|
13370
|
+
};
|
|
13371
|
+
condition(constraintQb);
|
|
13372
|
+
return frags.length ? ` AND ${frags.join(" AND ")}` : "";
|
|
13373
|
+
};
|
|
13295
13374
|
const resolveTarget = () => {
|
|
13296
13375
|
const pick = (m) => {
|
|
13297
13376
|
const modelName = m?.[relationKey];
|
|
@@ -13336,7 +13415,7 @@ function createQueryBuilder(state) {
|
|
|
13336
13415
|
const throughPk = meta.primaryKeys[throughTable] ?? "id";
|
|
13337
13416
|
const fkInThrough = `${singularize(fromTable)}_id`;
|
|
13338
13417
|
const fkInFinal = `${singularize(throughTable)}_id`;
|
|
13339
|
-
|
|
13418
|
+
insertJoin(`LEFT JOIN ${throughTable} ON ${throughTable}.${fkInThrough} = ${fromTable}.${fromPk} LEFT JOIN ${finalTable} ON ${finalTable}.${fkInFinal} = ${throughTable}.${throughPk}`);
|
|
13340
13419
|
joinedTables.add(throughTable);
|
|
13341
13420
|
joinedTables.add(finalTable);
|
|
13342
13421
|
return finalTable;
|
|
@@ -13349,7 +13428,7 @@ function createQueryBuilder(state) {
|
|
|
13349
13428
|
const childPk = meta.primaryKeys[childTable] ?? "id";
|
|
13350
13429
|
const fkA = resolved?.fkParent ?? `${singularize(fromTable)}_id`;
|
|
13351
13430
|
const fkB = resolved?.fkRelated ?? `${singularize(childTable)}_id`;
|
|
13352
|
-
|
|
13431
|
+
insertJoin(`LEFT JOIN ${pivot} ON ${pivot}.${fkA} = ${fromTable}.${fromPk} LEFT JOIN ${childTable} ON ${childTable}.${childPk} = ${pivot}.${fkB}${buildJoinConstraint(childTable)}`);
|
|
13353
13432
|
joinedTables.add(pivot);
|
|
13354
13433
|
joinedTables.add(childTable);
|
|
13355
13434
|
return childTable;
|
|
@@ -13363,7 +13442,8 @@ function createQueryBuilder(state) {
|
|
|
13363
13442
|
const morphType = `${morphName}_type`;
|
|
13364
13443
|
const morphId = `${morphName}_id`;
|
|
13365
13444
|
const targetFk = `${singularize(childTable)}_id`;
|
|
13366
|
-
|
|
13445
|
+
const morphVal = formatSubqueryValue(meta.tableToModel[fromTable] || fromTable);
|
|
13446
|
+
insertJoin(`LEFT JOIN ${pivotTable} ON ${pivotTable}.${morphId} = ${fromTable}.${fromPk} AND ${pivotTable}.${morphType} = ${morphVal} LEFT JOIN ${childTable} ON ${childTable}.${childPk} = ${pivotTable}.${targetFk}`);
|
|
13367
13447
|
joinedTables.add(pivotTable);
|
|
13368
13448
|
joinedTables.add(childTable);
|
|
13369
13449
|
return childTable;
|
|
@@ -13379,7 +13459,8 @@ function createQueryBuilder(state) {
|
|
|
13379
13459
|
const morphType = `${morphName}_type`;
|
|
13380
13460
|
const morphId = `${morphName}_id`;
|
|
13381
13461
|
const relatedFk = `${singularize(relatedTable)}_id`;
|
|
13382
|
-
|
|
13462
|
+
const morphVal = formatSubqueryValue(meta.tableToModel[relatedTable] || relatedTable);
|
|
13463
|
+
insertJoin(`LEFT JOIN ${pivotTable} ON ${pivotTable}.${relatedFk} = ${fromTable}.${fromPk} LEFT JOIN ${relatedTable} ON ${relatedTable}.${relatedPk} = ${pivotTable}.${morphId} AND ${pivotTable}.${morphType} = ${morphVal}`);
|
|
13383
13464
|
joinedTables.add(pivotTable);
|
|
13384
13465
|
joinedTables.add(relatedTable);
|
|
13385
13466
|
return relatedTable;
|
|
@@ -13388,7 +13469,7 @@ function createQueryBuilder(state) {
|
|
|
13388
13469
|
if (isBt) {
|
|
13389
13470
|
const fkInParent = `${singularize(childTable)}_id`;
|
|
13390
13471
|
const childPk = meta.primaryKeys[childTable] ?? "id";
|
|
13391
|
-
|
|
13472
|
+
insertJoin(`LEFT JOIN ${childTable} ON ${fromTable}.${fkInParent} = ${childTable}.${childPk}${buildJoinConstraint(childTable)}`);
|
|
13392
13473
|
joinedTables.add(childTable);
|
|
13393
13474
|
return childTable;
|
|
13394
13475
|
}
|
|
@@ -13398,20 +13479,15 @@ function createQueryBuilder(state) {
|
|
|
13398
13479
|
const morphType = `${relationKey}_type`;
|
|
13399
13480
|
const morphId = `${relationKey}_id`;
|
|
13400
13481
|
const fromPk = meta.primaryKeys[fromTable] ?? "id";
|
|
13401
|
-
|
|
13482
|
+
const morphVal = formatSubqueryValue(meta.tableToModel[fromTable] || fromTable);
|
|
13483
|
+
insertJoin(`LEFT JOIN ${childTable} ON ${childTable}.${morphId} = ${fromTable}.${fromPk} AND ${childTable}.${morphType} = ${morphVal}`);
|
|
13402
13484
|
joinedTables.add(childTable);
|
|
13403
13485
|
return childTable;
|
|
13404
13486
|
}
|
|
13405
13487
|
const fkInChild = `${singularize(fromTable)}_id`;
|
|
13406
13488
|
const pk = meta.primaryKeys[fromTable] ?? "id";
|
|
13407
|
-
const
|
|
13408
|
-
|
|
13409
|
-
const currentSql = String(ensureBuilt());
|
|
13410
|
-
const joinCondition = `${childTable}.${fkInChild} = ${fromTable}.${pk}${softDeleteCheck}`;
|
|
13411
|
-
built = sql`${sql(currentSql)} LEFT JOIN ${sql(childTable)} ON ${sql(joinCondition)}`;
|
|
13412
|
-
} else {
|
|
13413
|
-
built = sql`${ensureBuilt()} LEFT JOIN ${sql(childTable)} ON ${sql(`${childTable}.${fkInChild}`)} = ${sql(`${fromTable}.${pk}`)}`;
|
|
13414
|
-
}
|
|
13489
|
+
const extraOn = `${addSoftDeleteCheck(childTable)}${buildJoinConstraint(childTable)}`;
|
|
13490
|
+
insertJoin(`LEFT JOIN ${childTable} ON ${childTable}.${fkInChild} = ${fromTable}.${pk}${extraOn}`);
|
|
13415
13491
|
joinedTables.add(childTable);
|
|
13416
13492
|
return childTable;
|
|
13417
13493
|
};
|
|
@@ -13448,7 +13524,7 @@ function createQueryBuilder(state) {
|
|
|
13448
13524
|
addToSelectClause(pivotColumnsStr);
|
|
13449
13525
|
}
|
|
13450
13526
|
}
|
|
13451
|
-
|
|
13527
|
+
built = null;
|
|
13452
13528
|
return this;
|
|
13453
13529
|
},
|
|
13454
13530
|
whereHas(relation, callback) {
|
|
@@ -14058,9 +14134,9 @@ function createQueryBuilder(state) {
|
|
|
14058
14134
|
return this;
|
|
14059
14135
|
},
|
|
14060
14136
|
whereRaw(fragment) {
|
|
14061
|
-
|
|
14137
|
+
const frag = renderRawFragment(fragment, "whereRaw(fragment)");
|
|
14062
14138
|
const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
|
|
14063
|
-
text += ` ${keyword} ${
|
|
14139
|
+
text += ` ${keyword} ${frag}`;
|
|
14064
14140
|
built = null;
|
|
14065
14141
|
return this;
|
|
14066
14142
|
},
|
|
@@ -14426,8 +14502,8 @@ function createQueryBuilder(state) {
|
|
|
14426
14502
|
return this;
|
|
14427
14503
|
},
|
|
14428
14504
|
groupByRaw(fragment) {
|
|
14429
|
-
|
|
14430
|
-
text = SQL_PATTERNS.GROUP_BY.test(text) ? `${text}, ${
|
|
14505
|
+
const frag = renderRawFragment(fragment, "groupByRaw(fragment)");
|
|
14506
|
+
text = SQL_PATTERNS.GROUP_BY.test(text) ? `${text}, ${frag}` : `${text} GROUP BY ${frag}`;
|
|
14431
14507
|
built = null;
|
|
14432
14508
|
return this;
|
|
14433
14509
|
},
|
|
@@ -14459,15 +14535,15 @@ function createQueryBuilder(state) {
|
|
|
14459
14535
|
return this;
|
|
14460
14536
|
},
|
|
14461
14537
|
havingRaw(fragment) {
|
|
14462
|
-
|
|
14538
|
+
const frag = renderRawFragment(fragment, "havingRaw(fragment)");
|
|
14463
14539
|
const kw = /\bHAVING\b/i.test(text) ? "AND" : "HAVING";
|
|
14464
|
-
text += ` ${kw} ${
|
|
14540
|
+
text += ` ${kw} ${frag}`;
|
|
14465
14541
|
built = null;
|
|
14466
14542
|
return this;
|
|
14467
14543
|
},
|
|
14468
14544
|
orderByRaw(fragment) {
|
|
14469
|
-
|
|
14470
|
-
text = SQL_PATTERNS.ORDER_BY.test(text) ? `${text}, ${
|
|
14545
|
+
const frag = renderRawFragment(fragment, "orderByRaw(fragment)");
|
|
14546
|
+
text = SQL_PATTERNS.ORDER_BY.test(text) ? `${text}, ${frag}` : `${text} ORDER BY ${frag}`;
|
|
14471
14547
|
built = null;
|
|
14472
14548
|
return this;
|
|
14473
14549
|
},
|
|
@@ -14650,20 +14726,20 @@ function createQueryBuilder(state) {
|
|
|
14650
14726
|
includeTrashed = true;
|
|
14651
14727
|
onlyTrashed = true;
|
|
14652
14728
|
const softDeleteColumn = config5.softDeletes?.column || "deleted_at";
|
|
14653
|
-
const splice = (
|
|
14654
|
-
const upper =
|
|
14729
|
+
const splice = (raw2, predicate2) => {
|
|
14730
|
+
const upper = raw2.toUpperCase();
|
|
14655
14731
|
let depth = 0;
|
|
14656
|
-
for (let i = 0;i <
|
|
14657
|
-
const c =
|
|
14732
|
+
for (let i = 0;i < raw2.length; i++) {
|
|
14733
|
+
const c = raw2[i];
|
|
14658
14734
|
if (c === "(")
|
|
14659
14735
|
depth++;
|
|
14660
14736
|
else if (c === ")")
|
|
14661
14737
|
depth--;
|
|
14662
|
-
else if (depth === 0 && upper.substring(i, i + 5) === "WHERE" && (i === 0 || /\s/.test(
|
|
14663
|
-
return `${
|
|
14738
|
+
else if (depth === 0 && upper.substring(i, i + 5) === "WHERE" && (i === 0 || /\s/.test(raw2[i - 1] ?? "")) && /\s/.test(raw2[i + 5] ?? "")) {
|
|
14739
|
+
return `${raw2.substring(0, i)}WHERE ${predicate2} AND ${raw2.substring(i + 6)}`;
|
|
14664
14740
|
}
|
|
14665
14741
|
}
|
|
14666
|
-
return `${
|
|
14742
|
+
return `${raw2} WHERE ${predicate2}`;
|
|
14667
14743
|
};
|
|
14668
14744
|
const predicate = `${table}.${softDeleteColumn} IS NOT NULL`;
|
|
14669
14745
|
text = splice(text, predicate);
|
|
@@ -14967,13 +15043,13 @@ function createQueryBuilder(state) {
|
|
|
14967
15043
|
const cacheKey = `${String(table)}|${prop}`;
|
|
14968
15044
|
let chosen = dynamicWhereColumnCache.get(cacheKey);
|
|
14969
15045
|
if (chosen === undefined) {
|
|
14970
|
-
const
|
|
14971
|
-
if (!
|
|
15046
|
+
const raw2 = prop.replace(/^(?:or|and)?where/i, "");
|
|
15047
|
+
if (!raw2) {
|
|
14972
15048
|
dynamicWhereColumnCache.set(cacheKey, "");
|
|
14973
15049
|
chosen = "";
|
|
14974
15050
|
} else {
|
|
14975
|
-
const lowerFirst =
|
|
14976
|
-
const snake =
|
|
15051
|
+
const lowerFirst = raw2.charAt(0).toLowerCase() + raw2.slice(1);
|
|
15052
|
+
const snake = raw2.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
|
|
14977
15053
|
const available = schema ? Object.keys(schema[String(table)]?.columns ?? {}) : [];
|
|
14978
15054
|
chosen = [snake, lowerFirst, lowerFirst.toLowerCase()].find((n) => available.includes(n)) ?? snake;
|
|
14979
15055
|
dynamicWhereColumnCache.set(cacheKey, chosen);
|
|
@@ -15363,6 +15439,10 @@ function createQueryBuilder(state) {
|
|
|
15363
15439
|
returning(...cols) {
|
|
15364
15440
|
const returningSql = `${sqlText} RETURNING ${cols.join(", ")}`;
|
|
15365
15441
|
const q = _sql.unsafe(returningSql, params);
|
|
15442
|
+
const runFirst = async () => {
|
|
15443
|
+
const rows = await runWithHooks(q, "insert");
|
|
15444
|
+
return Array.isArray(rows) ? rows[0] : rows;
|
|
15445
|
+
};
|
|
15366
15446
|
return {
|
|
15367
15447
|
where: () => this,
|
|
15368
15448
|
andWhere: () => this,
|
|
@@ -15371,7 +15451,22 @@ function createQueryBuilder(state) {
|
|
|
15371
15451
|
limit: () => this,
|
|
15372
15452
|
offset: () => this,
|
|
15373
15453
|
toSQL: () => makeExecutableQuery(q, returningSql),
|
|
15374
|
-
execute: () => runWithHooks(q, "insert")
|
|
15454
|
+
execute: () => runWithHooks(q, "insert"),
|
|
15455
|
+
get: () => runWithHooks(q, "insert"),
|
|
15456
|
+
first: runFirst,
|
|
15457
|
+
executeTakeFirst: runFirst,
|
|
15458
|
+
async firstOrFail() {
|
|
15459
|
+
const row = await runFirst();
|
|
15460
|
+
if (!row)
|
|
15461
|
+
throw new Error("Insert with RETURNING returned no rows");
|
|
15462
|
+
return row;
|
|
15463
|
+
},
|
|
15464
|
+
async executeTakeFirstOrThrow() {
|
|
15465
|
+
const row = await runFirst();
|
|
15466
|
+
if (!row)
|
|
15467
|
+
throw new Error("Insert with RETURNING returned no rows");
|
|
15468
|
+
return row;
|
|
15469
|
+
}
|
|
15375
15470
|
};
|
|
15376
15471
|
},
|
|
15377
15472
|
toSQL() {
|
|
@@ -15410,19 +15505,27 @@ function createQueryBuilder(state) {
|
|
|
15410
15505
|
returningAll() {
|
|
15411
15506
|
const returningSql = `${sqlText} RETURNING *`;
|
|
15412
15507
|
const q = _sql.unsafe(returningSql, params);
|
|
15508
|
+
const runFirst = async () => {
|
|
15509
|
+
const result = await runWithHooks(q, "insert");
|
|
15510
|
+
return Array.isArray(result) ? result[0] : result;
|
|
15511
|
+
};
|
|
15413
15512
|
return {
|
|
15414
15513
|
toSQL: () => makeExecutableQuery(q, returningSql),
|
|
15415
15514
|
execute: () => runWithHooks(q, "insert"),
|
|
15416
|
-
|
|
15417
|
-
|
|
15418
|
-
|
|
15515
|
+
get: () => runWithHooks(q, "insert"),
|
|
15516
|
+
first: runFirst,
|
|
15517
|
+
executeTakeFirst: runFirst,
|
|
15518
|
+
async firstOrFail() {
|
|
15519
|
+
const row = await runFirst();
|
|
15520
|
+
if (!row)
|
|
15521
|
+
throw new Error("Insert with RETURNING returned no rows");
|
|
15522
|
+
return row;
|
|
15419
15523
|
},
|
|
15420
15524
|
async executeTakeFirstOrThrow() {
|
|
15421
|
-
const
|
|
15422
|
-
|
|
15423
|
-
|
|
15424
|
-
|
|
15425
|
-
return first;
|
|
15525
|
+
const row = await runFirst();
|
|
15526
|
+
if (!row)
|
|
15527
|
+
throw new Error("Insert with RETURNING returned no rows");
|
|
15528
|
+
return row;
|
|
15426
15529
|
}
|
|
15427
15530
|
};
|
|
15428
15531
|
}
|
|
@@ -15486,6 +15589,10 @@ function createQueryBuilder(state) {
|
|
|
15486
15589
|
returning(...cols) {
|
|
15487
15590
|
const retText = `${sqlText} RETURNING ${cols.join(", ")}`;
|
|
15488
15591
|
const q = params.length > 0 ? _sql.unsafe(retText, params) : _sql.unsafe(retText);
|
|
15592
|
+
const runFirst = async () => {
|
|
15593
|
+
const rows = await runWithHooks(q, "update");
|
|
15594
|
+
return Array.isArray(rows) ? rows[0] : rows;
|
|
15595
|
+
};
|
|
15489
15596
|
const obj = {
|
|
15490
15597
|
where: () => obj,
|
|
15491
15598
|
andWhere: () => obj,
|
|
@@ -15494,7 +15601,22 @@ function createQueryBuilder(state) {
|
|
|
15494
15601
|
limit: () => obj,
|
|
15495
15602
|
offset: () => obj,
|
|
15496
15603
|
toSQL: () => makeExecutableQuery(q, retText),
|
|
15497
|
-
execute: () => runWithHooks(q, "update")
|
|
15604
|
+
execute: () => runWithHooks(q, "update"),
|
|
15605
|
+
get: () => runWithHooks(q, "update"),
|
|
15606
|
+
first: runFirst,
|
|
15607
|
+
executeTakeFirst: runFirst,
|
|
15608
|
+
async firstOrFail() {
|
|
15609
|
+
const row = await runFirst();
|
|
15610
|
+
if (!row)
|
|
15611
|
+
throw new Error("Update with RETURNING returned no rows");
|
|
15612
|
+
return row;
|
|
15613
|
+
},
|
|
15614
|
+
async executeTakeFirstOrThrow() {
|
|
15615
|
+
const row = await runFirst();
|
|
15616
|
+
if (!row)
|
|
15617
|
+
throw new Error("Update with RETURNING returned no rows");
|
|
15618
|
+
return row;
|
|
15619
|
+
}
|
|
15498
15620
|
};
|
|
15499
15621
|
return obj;
|
|
15500
15622
|
},
|
|
@@ -15593,6 +15715,10 @@ function createQueryBuilder(state) {
|
|
|
15593
15715
|
returning(...cols) {
|
|
15594
15716
|
const retText = `${sqlText} RETURNING ${cols.join(", ")}`;
|
|
15595
15717
|
const q = delParams.length > 0 ? _sql.unsafe(retText, delParams) : _sql.unsafe(retText);
|
|
15718
|
+
const runFirst = async () => {
|
|
15719
|
+
const rows = await runWithHooks(q, "delete");
|
|
15720
|
+
return Array.isArray(rows) ? rows[0] : rows;
|
|
15721
|
+
};
|
|
15596
15722
|
const obj = {
|
|
15597
15723
|
where: () => obj,
|
|
15598
15724
|
andWhere: () => obj,
|
|
@@ -15601,7 +15727,22 @@ function createQueryBuilder(state) {
|
|
|
15601
15727
|
limit: () => obj,
|
|
15602
15728
|
offset: () => obj,
|
|
15603
15729
|
toSQL: () => makeExecutableQuery(q, retText),
|
|
15604
|
-
execute: () => runWithHooks(q, "delete")
|
|
15730
|
+
execute: () => runWithHooks(q, "delete"),
|
|
15731
|
+
get: () => runWithHooks(q, "delete"),
|
|
15732
|
+
first: runFirst,
|
|
15733
|
+
executeTakeFirst: runFirst,
|
|
15734
|
+
async firstOrFail() {
|
|
15735
|
+
const row = await runFirst();
|
|
15736
|
+
if (!row)
|
|
15737
|
+
throw new Error("Delete with RETURNING returned no rows");
|
|
15738
|
+
return row;
|
|
15739
|
+
},
|
|
15740
|
+
async executeTakeFirstOrThrow() {
|
|
15741
|
+
const row = await runFirst();
|
|
15742
|
+
if (!row)
|
|
15743
|
+
throw new Error("Delete with RETURNING returned no rows");
|
|
15744
|
+
return row;
|
|
15745
|
+
}
|
|
15605
15746
|
};
|
|
15606
15747
|
return obj;
|
|
15607
15748
|
},
|
|
@@ -17953,8 +18094,8 @@ function loadPlanSnapshot(workspaceRoot, dialect) {
|
|
|
17953
18094
|
return;
|
|
17954
18095
|
}
|
|
17955
18096
|
try {
|
|
17956
|
-
const
|
|
17957
|
-
const parsed = JSON.parse(
|
|
18097
|
+
const raw2 = readFileSync3(snapshotPath, "utf8");
|
|
18098
|
+
const parsed = JSON.parse(raw2);
|
|
17958
18099
|
if (parsed?.plan && Array.isArray(parsed.plan.tables) && parsed.plan.dialect) {
|
|
17959
18100
|
return parsed.plan;
|
|
17960
18101
|
}
|
|
@@ -18013,8 +18154,8 @@ async function generateMigration(dir, opts = {}) {
|
|
|
18013
18154
|
const statePath = String(opts.state || defaultStatePath);
|
|
18014
18155
|
if (existsSync16(statePath)) {
|
|
18015
18156
|
try {
|
|
18016
|
-
const
|
|
18017
|
-
const parsed = JSON.parse(
|
|
18157
|
+
const raw2 = readFileSync3(statePath, "utf8");
|
|
18158
|
+
const parsed = JSON.parse(raw2);
|
|
18018
18159
|
previous = parsed?.plan && parsed.plan.tables ? parsed.plan : parsed?.tables ? parsed : undefined;
|
|
18019
18160
|
if (previous) {
|
|
18020
18161
|
info("-- Comparing with legacy state file (will migrate to new snapshot format)");
|
|
@@ -24604,10 +24745,10 @@ class BelongsToManyRelationBuilder {
|
|
|
24604
24745
|
hydrateRows(rows) {
|
|
24605
24746
|
const fkParent = this.fkParent;
|
|
24606
24747
|
const fkRelated = this.fkRelated;
|
|
24607
|
-
return rows.map((
|
|
24748
|
+
return rows.map((raw2) => {
|
|
24608
24749
|
const relatedRow = {};
|
|
24609
24750
|
const pivotExtras = {};
|
|
24610
|
-
for (const [k2, v2] of Object.entries(
|
|
24751
|
+
for (const [k2, v2] of Object.entries(raw2)) {
|
|
24611
24752
|
if (k2.startsWith(BTM_RELATED_ALIAS))
|
|
24612
24753
|
relatedRow[k2.slice(BTM_RELATED_ALIAS.length)] = v2;
|
|
24613
24754
|
else if (k2 !== fkParent && k2 !== fkRelated)
|
|
@@ -27974,6 +28115,7 @@ export {
|
|
|
27974
28115
|
relationDiagram,
|
|
27975
28116
|
registerModel,
|
|
27976
28117
|
registerBrowserModels,
|
|
28118
|
+
raw,
|
|
27977
28119
|
queryExplainAll,
|
|
27978
28120
|
ping,
|
|
27979
28121
|
parseStacksModels,
|
package/package.json
CHANGED