bun-query-builder 0.1.25 → 0.1.27
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/actions/index.d.ts +1 -0
- package/dist/actions/introspect-db.d.ts +32 -0
- package/dist/actions/migrate-rollback.d.ts +14 -0
- package/dist/bin/cli.js +862 -216
- package/dist/client.d.ts +40 -10
- package/dist/config.d.ts +1 -15
- package/dist/db.d.ts +54 -0
- package/dist/orm.d.ts +30 -1
- package/dist/relation-utils.d.ts +27 -0
- package/dist/schema.d.ts +65 -3
- package/dist/src/index.js +858 -215
- package/dist/type-inference.d.ts +40 -0
- package/dist/types.d.ts +21 -0
- package/package.json +1 -1
package/dist/bin/cli.js
CHANGED
|
@@ -11694,7 +11694,7 @@ function setConfig(userConfig) {
|
|
|
11694
11694
|
Object.assign(_config, config5);
|
|
11695
11695
|
}
|
|
11696
11696
|
}
|
|
11697
|
-
var defaultConfig4, config5, _config = null, _lastConfiguredDialect = null, _warnedDialectConflicts;
|
|
11697
|
+
var defaultConfig4, CONFIG_SINGLETON_KEY, config5, _config = null, _lastConfiguredDialect = null, _warnedDialectConflicts;
|
|
11698
11698
|
var init_config = __esm(() => {
|
|
11699
11699
|
init_dist();
|
|
11700
11700
|
defaultConfig4 = {
|
|
@@ -11754,7 +11754,8 @@ var init_config = __esm(() => {
|
|
|
11754
11754
|
defaultFilter: true
|
|
11755
11755
|
}
|
|
11756
11756
|
};
|
|
11757
|
-
|
|
11757
|
+
CONFIG_SINGLETON_KEY = Symbol.for("bun-query-builder.config");
|
|
11758
|
+
config5 = globalThis[CONFIG_SINGLETON_KEY] ??= { ...defaultConfig4 };
|
|
11758
11759
|
_warnedDialectConflicts = new Set;
|
|
11759
11760
|
});
|
|
11760
11761
|
|
|
@@ -11975,6 +11976,16 @@ function createSQLiteSQL(filename) {
|
|
|
11975
11976
|
return sqlFunction;
|
|
11976
11977
|
}
|
|
11977
11978
|
function createConnectionString(dialect, dbConfig) {
|
|
11979
|
+
if ((dialect === "postgres" || dialect === "mysql") && process19.env.DB_CONNECTION === dialect) {
|
|
11980
|
+
const e = process19.env;
|
|
11981
|
+
const envDb = (e.DB_DATABASE || dbConfig.database || "").replace(/^['"]|['"]$/g, "");
|
|
11982
|
+
const envUser = e.DB_USERNAME || dbConfig.username;
|
|
11983
|
+
const envPass = e.DB_PASSWORD ?? dbConfig.password ?? "";
|
|
11984
|
+
const envHost = e.DB_HOST || dbConfig.host || "localhost";
|
|
11985
|
+
const envPort = e.DB_PORT || dbConfig.port;
|
|
11986
|
+
const scheme = dialect === "postgres" ? "postgres" : "mysql";
|
|
11987
|
+
return `${scheme}://${envUser}:${envPass}@${envHost}${envPort ? `:${envPort}` : ""}/${envDb}`;
|
|
11988
|
+
}
|
|
11978
11989
|
if (dbConfig.url) {
|
|
11979
11990
|
return dbConfig.url;
|
|
11980
11991
|
}
|
|
@@ -11993,6 +12004,21 @@ function createConnectionString(dialect, dbConfig) {
|
|
|
11993
12004
|
throw new Error(`Unsupported dialect: ${dialect}`);
|
|
11994
12005
|
}
|
|
11995
12006
|
}
|
|
12007
|
+
function resolvePoolOptions(pool) {
|
|
12008
|
+
if (!pool)
|
|
12009
|
+
return {};
|
|
12010
|
+
const out = {};
|
|
12011
|
+
const toSeconds = (ms) => Math.max(0, Math.round(ms / 1000));
|
|
12012
|
+
if (typeof pool.max === "number" && Number.isFinite(pool.max))
|
|
12013
|
+
out.max = pool.max;
|
|
12014
|
+
if (typeof pool.idleTimeoutMs === "number" && Number.isFinite(pool.idleTimeoutMs))
|
|
12015
|
+
out.idleTimeout = toSeconds(pool.idleTimeoutMs);
|
|
12016
|
+
if (typeof pool.acquireTimeoutMs === "number" && Number.isFinite(pool.acquireTimeoutMs))
|
|
12017
|
+
out.connectionTimeout = toSeconds(pool.acquireTimeoutMs);
|
|
12018
|
+
if (typeof pool.maxLifetimeMs === "number" && Number.isFinite(pool.maxLifetimeMs))
|
|
12019
|
+
out.maxLifetime = toSeconds(pool.maxLifetimeMs);
|
|
12020
|
+
return out;
|
|
12021
|
+
}
|
|
11996
12022
|
function getBunSql() {
|
|
11997
12023
|
const dialect = config5.dialect;
|
|
11998
12024
|
const connectionString = createConnectionString(dialect, config5.database);
|
|
@@ -12000,14 +12026,8 @@ function getBunSql() {
|
|
|
12000
12026
|
if (dialect === "sqlite") {
|
|
12001
12027
|
return createSQLiteSQL(connectionString);
|
|
12002
12028
|
}
|
|
12003
|
-
const
|
|
12004
|
-
|
|
12005
|
-
sql.catch((error) => {
|
|
12006
|
-
if (config5.verbose && !error.message.includes("database") && !error.message.includes("does not exist")) {
|
|
12007
|
-
console.warn(`[query-builder] Database connection error: ${error.message}`);
|
|
12008
|
-
}
|
|
12009
|
-
});
|
|
12010
|
-
}
|
|
12029
|
+
const poolOptions = resolvePoolOptions(config5.database.pool);
|
|
12030
|
+
const sql = Object.keys(poolOptions).length > 0 ? new SQL(connectionString, poolOptions) : new SQL(connectionString);
|
|
12011
12031
|
return sql;
|
|
12012
12032
|
} catch (error) {
|
|
12013
12033
|
console.error(`[query-builder] Failed to create database connection for dialect '${dialect}': ${error.message}`);
|
|
@@ -12017,19 +12037,31 @@ function getBunSql() {
|
|
|
12017
12037
|
throw error;
|
|
12018
12038
|
}
|
|
12019
12039
|
}
|
|
12040
|
+
function connectionSignature() {
|
|
12041
|
+
const d = config5.database;
|
|
12042
|
+
return JSON.stringify({
|
|
12043
|
+
dialect: config5.dialect,
|
|
12044
|
+
database: d.database,
|
|
12045
|
+
username: d.username,
|
|
12046
|
+
password: d.password,
|
|
12047
|
+
host: d.host,
|
|
12048
|
+
port: d.port,
|
|
12049
|
+
url: d.url,
|
|
12050
|
+
pool: resolvePoolOptions(d.pool)
|
|
12051
|
+
});
|
|
12052
|
+
}
|
|
12020
12053
|
function getOrCreateBunSql(forceNew = false) {
|
|
12021
|
-
const
|
|
12054
|
+
const signature = connectionSignature();
|
|
12055
|
+
const configChanged = _bunSqlInstance !== null && _currentSignature !== signature;
|
|
12022
12056
|
if (forceNew || configChanged || !_bunSqlInstance) {
|
|
12023
12057
|
_bunSqlInstance = getBunSql();
|
|
12024
|
-
|
|
12025
|
-
_currentDatabase = config5.database.database;
|
|
12058
|
+
_currentSignature = signature;
|
|
12026
12059
|
}
|
|
12027
12060
|
return _bunSqlInstance;
|
|
12028
12061
|
}
|
|
12029
12062
|
function resetConnection() {
|
|
12030
12063
|
_bunSqlInstance = null;
|
|
12031
|
-
|
|
12032
|
-
_currentDatabase = null;
|
|
12064
|
+
_currentSignature = null;
|
|
12033
12065
|
}
|
|
12034
12066
|
async function withFreshConnection(fn) {
|
|
12035
12067
|
try {
|
|
@@ -12044,7 +12076,7 @@ async function withFreshConnection(fn) {
|
|
|
12044
12076
|
}
|
|
12045
12077
|
}
|
|
12046
12078
|
function createLazyBunSql() {
|
|
12047
|
-
return new Proxy({}, {
|
|
12079
|
+
return new Proxy(function lazyBunSql() {}, {
|
|
12048
12080
|
get(_target, prop) {
|
|
12049
12081
|
const sql = getOrCreateBunSql();
|
|
12050
12082
|
const value = sql[prop];
|
|
@@ -12059,20 +12091,10 @@ function createLazyBunSql() {
|
|
|
12059
12091
|
}
|
|
12060
12092
|
});
|
|
12061
12093
|
}
|
|
12062
|
-
var _bunSqlInstance = null,
|
|
12094
|
+
var _bunSqlInstance = null, _currentSignature = null, bunSql;
|
|
12063
12095
|
var init_db = __esm(() => {
|
|
12064
12096
|
init_config();
|
|
12065
12097
|
bunSql = createLazyBunSql();
|
|
12066
|
-
if (typeof process19 !== "undefined" && process19.on) {
|
|
12067
|
-
const existingHandler = process19.listeners("unhandledRejection").find((h) => h.name === "sqlConnectionErrorHandler");
|
|
12068
|
-
if (!existingHandler) {
|
|
12069
|
-
let sqlConnectionErrorHandler = function(reason) {
|
|
12070
|
-
if (reason && (reason.message?.includes("database") || reason.message?.includes("does not exist") || reason.code === "ERR_POSTGRES_SERVER_ERROR" || reason.code === "3D000")) {}
|
|
12071
|
-
};
|
|
12072
|
-
Object.defineProperty(sqlConnectionErrorHandler, "name", { value: "sqlConnectionErrorHandler" });
|
|
12073
|
-
process19.on("unhandledRejection", sqlConnectionErrorHandler);
|
|
12074
|
-
}
|
|
12075
|
-
}
|
|
12076
12098
|
});
|
|
12077
12099
|
|
|
12078
12100
|
// src/actions/benchmark.ts
|
|
@@ -12322,6 +12344,47 @@ function* iterateAllPivots(meta, options = {}) {
|
|
|
12322
12344
|
function isRawExpression(expr) {
|
|
12323
12345
|
return typeof expr === "object" && expr !== null && "raw" in expr && typeof expr.raw === "string";
|
|
12324
12346
|
}
|
|
12347
|
+
function quoteInsertIdent(id) {
|
|
12348
|
+
return config5.dialect === "mysql" ? `\`${id.replace(/`/g, "``")}\`` : `"${id.replace(/"/g, '""')}"`;
|
|
12349
|
+
}
|
|
12350
|
+
function buildInsertClause(rows, startIndex = 1) {
|
|
12351
|
+
const cols = Object.keys(rows[0] ?? {});
|
|
12352
|
+
const params = [];
|
|
12353
|
+
let idx = startIndex;
|
|
12354
|
+
const tuples = rows.map((row) => {
|
|
12355
|
+
const phs = cols.map((c) => {
|
|
12356
|
+
params.push(row[c]);
|
|
12357
|
+
return getPlaceholder(idx++);
|
|
12358
|
+
});
|
|
12359
|
+
return `(${phs.join(", ")})`;
|
|
12360
|
+
});
|
|
12361
|
+
return {
|
|
12362
|
+
colsSql: cols.map(quoteInsertIdent).join(", "),
|
|
12363
|
+
valuesSql: tuples.join(", "),
|
|
12364
|
+
params,
|
|
12365
|
+
nextIndex: idx
|
|
12366
|
+
};
|
|
12367
|
+
}
|
|
12368
|
+
function hasSlowQueryHook(h) {
|
|
12369
|
+
return Boolean(h && (h.onSlowQuery || h.slowQueryThresholdMs != null && h.slowQueryThresholdMs >= 0));
|
|
12370
|
+
}
|
|
12371
|
+
function renderSelectColumn(col) {
|
|
12372
|
+
if (typeof col === "string")
|
|
12373
|
+
return col;
|
|
12374
|
+
if (isRawExpression(col))
|
|
12375
|
+
return col.raw;
|
|
12376
|
+
if (col && typeof col === "object") {
|
|
12377
|
+
const anyCol = col;
|
|
12378
|
+
if (typeof anyCol.raw === "function")
|
|
12379
|
+
return String(anyCol.raw());
|
|
12380
|
+
if (typeof anyCol.sql === "string")
|
|
12381
|
+
return anyCol.sql;
|
|
12382
|
+
const str = String(col);
|
|
12383
|
+
if (str !== "[object Object]")
|
|
12384
|
+
return str;
|
|
12385
|
+
}
|
|
12386
|
+
throw new TypeError(`[query-builder] select(): unsupported column ${String(col)} \u2014 pass a column name, a string[], or a SQL fragment (e.g. sql\`count(*) as c\`)`);
|
|
12387
|
+
}
|
|
12325
12388
|
function validateIdentifier(name, context) {
|
|
12326
12389
|
if (!SQL_PATTERNS.IDENTIFIER.test(name)) {
|
|
12327
12390
|
const contextMsg = context ? ` in ${context}` : "";
|
|
@@ -12340,13 +12403,29 @@ class QueryCache {
|
|
|
12340
12403
|
this.cache.delete(key);
|
|
12341
12404
|
return null;
|
|
12342
12405
|
}
|
|
12406
|
+
this.cache.delete(key);
|
|
12407
|
+
this.cache.set(key, entry);
|
|
12343
12408
|
return entry.data;
|
|
12344
12409
|
}
|
|
12345
12410
|
set(key, data, ttlMs) {
|
|
12411
|
+
if (this.cache.has(key))
|
|
12412
|
+
this.cache.delete(key);
|
|
12346
12413
|
if (this.cache.size >= this.maxSize) {
|
|
12347
|
-
const
|
|
12348
|
-
|
|
12349
|
-
|
|
12414
|
+
const now = Date.now();
|
|
12415
|
+
let evicted = false;
|
|
12416
|
+
for (const [k, v] of this.cache) {
|
|
12417
|
+
if (now > v.expiresAt) {
|
|
12418
|
+
this.cache.delete(k);
|
|
12419
|
+
evicted = true;
|
|
12420
|
+
if (this.cache.size < this.maxSize)
|
|
12421
|
+
break;
|
|
12422
|
+
}
|
|
12423
|
+
}
|
|
12424
|
+
if (!evicted) {
|
|
12425
|
+
const lruKey = this.cache.keys().next().value;
|
|
12426
|
+
if (lruKey !== undefined)
|
|
12427
|
+
this.cache.delete(lruKey);
|
|
12428
|
+
}
|
|
12350
12429
|
}
|
|
12351
12430
|
this.cache.set(key, {
|
|
12352
12431
|
data,
|
|
@@ -12417,6 +12496,9 @@ function reorderSelectClauses(sql) {
|
|
|
12417
12496
|
continue;
|
|
12418
12497
|
if (i === 0 || !/\s/.test(sql[i - 1]))
|
|
12419
12498
|
continue;
|
|
12499
|
+
const lead = sql.charCodeAt(i) & ~32;
|
|
12500
|
+
if (lead !== 71 && lead !== 79 && lead !== 72 && lead !== 76 && lead !== 87)
|
|
12501
|
+
continue;
|
|
12420
12502
|
const rest = sql.slice(i);
|
|
12421
12503
|
for (const { key, tokens } of KEYWORDS) {
|
|
12422
12504
|
const m = rest.match(tokens);
|
|
@@ -12536,20 +12618,35 @@ function createQueryBuilder(state) {
|
|
|
12536
12618
|
config5.debug.captureText = prev;
|
|
12537
12619
|
return s;
|
|
12538
12620
|
}
|
|
12621
|
+
function computeParams(q) {
|
|
12622
|
+
if (!q || typeof q !== "object")
|
|
12623
|
+
return;
|
|
12624
|
+
if (Array.isArray(q.values))
|
|
12625
|
+
return q.values;
|
|
12626
|
+
if (Array.isArray(q.parameters))
|
|
12627
|
+
return q.parameters;
|
|
12628
|
+
if (Array.isArray(q.params))
|
|
12629
|
+
return q.params;
|
|
12630
|
+
return;
|
|
12631
|
+
}
|
|
12539
12632
|
function runWithHooks(q, kind, opts) {
|
|
12540
12633
|
const hooks = config5.hooks;
|
|
12541
|
-
const
|
|
12634
|
+
const slowMs = hooks?.slowQueryThresholdMs;
|
|
12635
|
+
const slowEnabled = slowMs != null && slowMs >= 0;
|
|
12636
|
+
const hasSlowQuery = Boolean(hooks?.onSlowQuery || slowEnabled);
|
|
12637
|
+
const hasHooks = hooks && (hooks.onQueryStart || hooks.onQueryEnd || hooks.onQueryError || hooks.startSpan || hasSlowQuery);
|
|
12542
12638
|
const hasTimeoutOrSignal = opts?.timeoutMs && opts.timeoutMs > 0 || opts?.signal;
|
|
12543
12639
|
if (!hasHooks && !hasTimeoutOrSignal) {
|
|
12544
12640
|
return q.execute();
|
|
12545
12641
|
}
|
|
12546
12642
|
const text = computeSqlText(q);
|
|
12643
|
+
const params = computeParams(q);
|
|
12547
12644
|
const startAt = Date.now();
|
|
12548
12645
|
let span;
|
|
12549
12646
|
try {
|
|
12550
|
-
hooks?.onQueryStart?.({ sql: text, kind });
|
|
12647
|
+
hooks?.onQueryStart?.({ sql: text, params, kind });
|
|
12551
12648
|
if (hooks?.startSpan)
|
|
12552
|
-
span = hooks.startSpan({ sql: text, kind });
|
|
12649
|
+
span = hooks.startSpan({ sql: text, params, kind });
|
|
12553
12650
|
} catch {}
|
|
12554
12651
|
let finished = false;
|
|
12555
12652
|
const finish = (err, rowCount) => {
|
|
@@ -12559,9 +12656,15 @@ function createQueryBuilder(state) {
|
|
|
12559
12656
|
const durationMs = Date.now() - startAt;
|
|
12560
12657
|
try {
|
|
12561
12658
|
if (err) {
|
|
12562
|
-
hooks?.onQueryError?.({ sql: text, error: err, durationMs, kind });
|
|
12659
|
+
hooks?.onQueryError?.({ sql: text, params, error: err, durationMs, kind });
|
|
12563
12660
|
} else {
|
|
12564
|
-
hooks?.onQueryEnd?.({ sql: text, durationMs, rowCount, kind });
|
|
12661
|
+
hooks?.onQueryEnd?.({ sql: text, params, durationMs, rowCount, kind });
|
|
12662
|
+
if (slowEnabled && durationMs >= slowMs) {
|
|
12663
|
+
if (hooks?.onSlowQuery)
|
|
12664
|
+
hooks.onSlowQuery({ sql: text, params, durationMs, kind });
|
|
12665
|
+
else
|
|
12666
|
+
console.warn(`[query-builder] slow query (${durationMs}ms >= ${slowMs}ms): ${text}`);
|
|
12667
|
+
}
|
|
12565
12668
|
}
|
|
12566
12669
|
} catch {}
|
|
12567
12670
|
try {
|
|
@@ -12641,6 +12744,36 @@ function createQueryBuilder(state) {
|
|
|
12641
12744
|
const p = hasWhere ? prefix : "WHERE";
|
|
12642
12745
|
text = `${text} ${p} ${clause}`;
|
|
12643
12746
|
};
|
|
12747
|
+
const appendSetOp = (op, other) => {
|
|
12748
|
+
const st = other.__rawState?.();
|
|
12749
|
+
if (st) {
|
|
12750
|
+
const offset = whereParams.length;
|
|
12751
|
+
const otherSql = config5.dialect === "postgres" ? st.sql.replace(/\$(\d+)/g, (_m, n) => `$${Number(n) + offset}`) : st.sql;
|
|
12752
|
+
text += ` ${op} ${otherSql}`;
|
|
12753
|
+
whereParams.push(...st.params);
|
|
12754
|
+
} else {
|
|
12755
|
+
text += ` ${op} ${String(other.toSQL())}`;
|
|
12756
|
+
}
|
|
12757
|
+
built = null;
|
|
12758
|
+
};
|
|
12759
|
+
const insertJoin = (joinClause) => {
|
|
12760
|
+
const re = /\(|\)|\b(?:WHERE|GROUP BY|HAVING|ORDER BY|LIMIT|OFFSET|UNION)\b/gi;
|
|
12761
|
+
let depth = 0;
|
|
12762
|
+
let cut = -1;
|
|
12763
|
+
let mm;
|
|
12764
|
+
while (mm = re.exec(text)) {
|
|
12765
|
+
if (mm[0] === "(") {
|
|
12766
|
+
depth++;
|
|
12767
|
+
} else if (mm[0] === ")") {
|
|
12768
|
+
depth = Math.max(0, depth - 1);
|
|
12769
|
+
} else if (depth === 0) {
|
|
12770
|
+
cut = mm.index;
|
|
12771
|
+
break;
|
|
12772
|
+
}
|
|
12773
|
+
}
|
|
12774
|
+
text = cut >= 0 ? `${text.slice(0, cut)}${joinClause} ${text.slice(cut)}` : `${text} ${joinClause}`;
|
|
12775
|
+
built = null;
|
|
12776
|
+
};
|
|
12644
12777
|
const joinedTables = new Set;
|
|
12645
12778
|
let timeoutMs;
|
|
12646
12779
|
let abortSignal;
|
|
@@ -12725,6 +12858,18 @@ function createQueryBuilder(state) {
|
|
|
12725
12858
|
}
|
|
12726
12859
|
}
|
|
12727
12860
|
};
|
|
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
|
+
const addWindowFunction = (fnExpr, alias, partitionBy, orderBy) => {
|
|
12871
|
+
addToSelectClause(`${fnExpr} ${buildOverClause(partitionBy, orderBy)} AS ${alias}`);
|
|
12872
|
+
};
|
|
12728
12873
|
function assertSafeWhereOperator(op, context) {
|
|
12729
12874
|
if (typeof op !== "string")
|
|
12730
12875
|
throw new TypeError(`[query-builder] ${context}: operator must be a string, got ${typeof op}`);
|
|
@@ -12864,6 +13009,46 @@ function createQueryBuilder(state) {
|
|
|
12864
13009
|
validateIdentifier(fkA, "withCount (foreign key)");
|
|
12865
13010
|
return `(SELECT COUNT(*) FROM ${pivot} WHERE ${pivot}.${fkA} = ${parentTable}.${pk})`;
|
|
12866
13011
|
};
|
|
13012
|
+
const applyRelationAggregate = (fn, relation, column) => {
|
|
13013
|
+
if (!meta)
|
|
13014
|
+
return;
|
|
13015
|
+
validateIdentifier(column, `with${fn[0]}${fn.slice(1).toLowerCase()} (column)`);
|
|
13016
|
+
const parentTable = String(table);
|
|
13017
|
+
const rels = meta.relations?.[parentTable];
|
|
13018
|
+
if (!rels)
|
|
13019
|
+
return;
|
|
13020
|
+
const found = Object.entries(rels).find(([_t, relMap2]) => relMap2 && typeof relMap2 === "object" && (relation in relMap2));
|
|
13021
|
+
if (!found)
|
|
13022
|
+
return;
|
|
13023
|
+
const [type, relMap] = found;
|
|
13024
|
+
const entry = relMap[relation];
|
|
13025
|
+
const targetModel = typeof entry === "string" ? entry : entry?.model || entry?.target || entry;
|
|
13026
|
+
const targetTable = meta.modelToTable[targetModel] || targetModel;
|
|
13027
|
+
const pk = meta.primaryKeys[parentTable] ?? "id";
|
|
13028
|
+
validateIdentifier(targetTable, `with${fn} (target table)`);
|
|
13029
|
+
const aggExpr = `${fn}(${targetTable}.${column})`;
|
|
13030
|
+
let sub;
|
|
13031
|
+
if (type === "hasMany" || type === "hasOne") {
|
|
13032
|
+
const fk = `${parentTable.endsWith("s") ? parentTable.slice(0, -1) : parentTable}_id`;
|
|
13033
|
+
validateIdentifier(fk, `with${fn} (foreign key)`);
|
|
13034
|
+
sub = `(SELECT ${aggExpr} FROM ${targetTable} WHERE ${targetTable}.${fk} = ${parentTable}.${pk})`;
|
|
13035
|
+
} else if (type === "belongsToMany") {
|
|
13036
|
+
const resolved = meta ? resolvePivot(meta, parentTable, relation, { singularize, models: meta.models }) : null;
|
|
13037
|
+
const a = singularize(parentTable);
|
|
13038
|
+
const b = singularize(targetTable);
|
|
13039
|
+
const pivot = resolved?.pivotTable ?? [a, b].sort().join("_");
|
|
13040
|
+
const fkA = resolved?.fkParent ?? `${a}_id`;
|
|
13041
|
+
const fkB = resolved?.fkRelated ?? `${b}_id`;
|
|
13042
|
+
const targetPk = meta.primaryKeys[targetTable] ?? "id";
|
|
13043
|
+
validateIdentifier(pivot, `with${fn} (pivot table)`);
|
|
13044
|
+
validateIdentifier(fkA, `with${fn} (foreign key)`);
|
|
13045
|
+
validateIdentifier(fkB, `with${fn} (related key)`);
|
|
13046
|
+
sub = `(SELECT ${aggExpr} FROM ${pivot} JOIN ${targetTable} ON ${targetTable}.${targetPk} = ${pivot}.${fkB} WHERE ${pivot}.${fkA} = ${parentTable}.${pk})`;
|
|
13047
|
+
} else {
|
|
13048
|
+
return;
|
|
13049
|
+
}
|
|
13050
|
+
addToSelectClause(`${sub} AS ${relation}_${fn.toLowerCase()}_${column}`);
|
|
13051
|
+
};
|
|
12867
13052
|
const applyPivotColumnsToQuery = () => {
|
|
12868
13053
|
if (pivotColumns.size === 0)
|
|
12869
13054
|
return;
|
|
@@ -12958,6 +13143,52 @@ function createQueryBuilder(state) {
|
|
|
12958
13143
|
built = null;
|
|
12959
13144
|
return this;
|
|
12960
13145
|
},
|
|
13146
|
+
over(expression, alias, opts = {}) {
|
|
13147
|
+
addWindowFunction(expression, alias, opts.partitionBy, opts.orderBy);
|
|
13148
|
+
return this;
|
|
13149
|
+
},
|
|
13150
|
+
lag(column, opts = {}) {
|
|
13151
|
+
const args = [column, String(opts.offset ?? 1)];
|
|
13152
|
+
if (opts.defaultValue !== undefined)
|
|
13153
|
+
args.push(String(opts.defaultValue));
|
|
13154
|
+
addWindowFunction(`LAG(${args.join(", ")})`, opts.alias ?? `${column}_lag`, opts.partitionBy, opts.orderBy);
|
|
13155
|
+
return this;
|
|
13156
|
+
},
|
|
13157
|
+
lead(column, opts = {}) {
|
|
13158
|
+
const args = [column, String(opts.offset ?? 1)];
|
|
13159
|
+
if (opts.defaultValue !== undefined)
|
|
13160
|
+
args.push(String(opts.defaultValue));
|
|
13161
|
+
addWindowFunction(`LEAD(${args.join(", ")})`, opts.alias ?? `${column}_lead`, opts.partitionBy, opts.orderBy);
|
|
13162
|
+
return this;
|
|
13163
|
+
},
|
|
13164
|
+
sumOver(column, opts = {}) {
|
|
13165
|
+
addWindowFunction(`SUM(${column})`, opts.alias ?? `${column}_sum`, opts.partitionBy, opts.orderBy);
|
|
13166
|
+
return this;
|
|
13167
|
+
},
|
|
13168
|
+
avgOver(column, opts = {}) {
|
|
13169
|
+
addWindowFunction(`AVG(${column})`, opts.alias ?? `${column}_avg`, opts.partitionBy, opts.orderBy);
|
|
13170
|
+
return this;
|
|
13171
|
+
},
|
|
13172
|
+
countOver(column = "*", opts = {}) {
|
|
13173
|
+
addWindowFunction(`COUNT(${column})`, opts.alias ?? "count_over", opts.partitionBy, opts.orderBy);
|
|
13174
|
+
return this;
|
|
13175
|
+
},
|
|
13176
|
+
minOver(column, opts = {}) {
|
|
13177
|
+
addWindowFunction(`MIN(${column})`, opts.alias ?? `${column}_min`, opts.partitionBy, opts.orderBy);
|
|
13178
|
+
return this;
|
|
13179
|
+
},
|
|
13180
|
+
maxOver(column, opts = {}) {
|
|
13181
|
+
addWindowFunction(`MAX(${column})`, opts.alias ?? `${column}_max`, opts.partitionBy, opts.orderBy);
|
|
13182
|
+
return this;
|
|
13183
|
+
},
|
|
13184
|
+
firstValue(column, opts = {}) {
|
|
13185
|
+
addWindowFunction(`FIRST_VALUE(${column})`, opts.alias ?? `${column}_first`, opts.partitionBy, opts.orderBy);
|
|
13186
|
+
return this;
|
|
13187
|
+
},
|
|
13188
|
+
lastValue(column, opts = {}) {
|
|
13189
|
+
addWindowFunction(`LAST_VALUE(${column})`, opts.alias ?? `${column}_last`, opts.partitionBy, opts.orderBy);
|
|
13190
|
+
return this;
|
|
13191
|
+
},
|
|
12961
13192
|
selectAll() {
|
|
12962
13193
|
return this;
|
|
12963
13194
|
},
|
|
@@ -12967,22 +13198,24 @@ function createQueryBuilder(state) {
|
|
|
12967
13198
|
const cols = Array.isArray(columns2) ? columns2 : [columns2];
|
|
12968
13199
|
if (cols.length === 0)
|
|
12969
13200
|
return this;
|
|
13201
|
+
const rendered = cols.map(renderSelectColumn);
|
|
12970
13202
|
const fromIndex = text.indexOf(" FROM ");
|
|
12971
13203
|
if (fromIndex !== -1) {
|
|
12972
|
-
text = `SELECT ${
|
|
13204
|
+
text = `SELECT ${rendered.join(", ")}${text.substring(fromIndex)}`;
|
|
12973
13205
|
} else {
|
|
12974
|
-
text = `SELECT ${
|
|
13206
|
+
text = `SELECT ${rendered.join(", ")} FROM ${table}`;
|
|
12975
13207
|
}
|
|
12976
13208
|
return this;
|
|
12977
13209
|
},
|
|
12978
13210
|
addSelect(...columns2) {
|
|
12979
13211
|
if (!columns2.length)
|
|
12980
13212
|
return this;
|
|
13213
|
+
const rendered = columns2.map(renderSelectColumn);
|
|
12981
13214
|
const fromIdx = text.indexOf(" FROM ");
|
|
12982
13215
|
if (fromIdx !== -1) {
|
|
12983
|
-
text = `${text.substring(0, fromIdx)}, ${
|
|
13216
|
+
text = `${text.substring(0, fromIdx)}, ${rendered.join(", ")}${text.substring(fromIdx)}`;
|
|
12984
13217
|
} else {
|
|
12985
|
-
text += `, ${
|
|
13218
|
+
text += `, ${rendered.join(", ")}`;
|
|
12986
13219
|
}
|
|
12987
13220
|
built = null;
|
|
12988
13221
|
return this;
|
|
@@ -13042,28 +13275,6 @@ function createQueryBuilder(state) {
|
|
|
13042
13275
|
if (!rels) {
|
|
13043
13276
|
return fromTable;
|
|
13044
13277
|
}
|
|
13045
|
-
const _buildConditionalJoin = (baseJoinCondition, targetTable2) => {
|
|
13046
|
-
let joinCondition = baseJoinCondition;
|
|
13047
|
-
if (config5.softDeletes?.enabled && config5.softDeletes?.defaultFilter) {
|
|
13048
|
-
const softDeleteColumn = config5.softDeletes.column || "deleted_at";
|
|
13049
|
-
joinCondition = `${joinCondition} AND ${targetTable2}.${softDeleteColumn} IS NULL`;
|
|
13050
|
-
}
|
|
13051
|
-
if (!condition)
|
|
13052
|
-
return joinCondition;
|
|
13053
|
-
const conditionBuilder = {
|
|
13054
|
-
where: (col, op, val) => {
|
|
13055
|
-
const valStr = typeof val === "string" ? `'${val}'` : String(val);
|
|
13056
|
-
return `${targetTable2}.${col} ${op} ${valStr}`;
|
|
13057
|
-
}
|
|
13058
|
-
};
|
|
13059
|
-
try {
|
|
13060
|
-
const additionalCondition = condition(conditionBuilder);
|
|
13061
|
-
if (additionalCondition && typeof additionalCondition === "string") {
|
|
13062
|
-
return `${joinCondition} AND ${additionalCondition}`;
|
|
13063
|
-
}
|
|
13064
|
-
} catch {}
|
|
13065
|
-
return joinCondition;
|
|
13066
|
-
};
|
|
13067
13278
|
const addSoftDeleteCheck = (table2) => {
|
|
13068
13279
|
if (config5.softDeletes?.enabled && config5.softDeletes?.defaultFilter) {
|
|
13069
13280
|
const softDeleteColumn = config5.softDeletes.column || "deleted_at";
|
|
@@ -13399,6 +13610,22 @@ function createQueryBuilder(state) {
|
|
|
13399
13610
|
}
|
|
13400
13611
|
return this;
|
|
13401
13612
|
},
|
|
13613
|
+
withSum(relation, column) {
|
|
13614
|
+
applyRelationAggregate("SUM", relation, column);
|
|
13615
|
+
return this;
|
|
13616
|
+
},
|
|
13617
|
+
withAvg(relation, column) {
|
|
13618
|
+
applyRelationAggregate("AVG", relation, column);
|
|
13619
|
+
return this;
|
|
13620
|
+
},
|
|
13621
|
+
withMax(relation, column) {
|
|
13622
|
+
applyRelationAggregate("MAX", relation, column);
|
|
13623
|
+
return this;
|
|
13624
|
+
},
|
|
13625
|
+
withMin(relation, column) {
|
|
13626
|
+
applyRelationAggregate("MIN", relation, column);
|
|
13627
|
+
return this;
|
|
13628
|
+
},
|
|
13402
13629
|
applyPivotColumns() {
|
|
13403
13630
|
applyPivotColumnsToQuery();
|
|
13404
13631
|
return this;
|
|
@@ -13526,6 +13753,8 @@ function createQueryBuilder(state) {
|
|
|
13526
13753
|
where(expr, op, value) {
|
|
13527
13754
|
const getWhereKeyword = () => SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
|
|
13528
13755
|
if (typeof expr === "string" && op !== undefined) {
|
|
13756
|
+
validateIdentifier(String(expr), "where(column)");
|
|
13757
|
+
assertSafeWhereOperator(op, "where(operator)");
|
|
13529
13758
|
const operator = String(op).toLowerCase();
|
|
13530
13759
|
if (operator === "in" || operator === "not in") {
|
|
13531
13760
|
const values = Array.isArray(value) ? value : [value];
|
|
@@ -13547,7 +13776,8 @@ function createQueryBuilder(state) {
|
|
|
13547
13776
|
if (Array.isArray(expr)) {
|
|
13548
13777
|
const [col, op2, val] = expr;
|
|
13549
13778
|
const colName = String(col);
|
|
13550
|
-
|
|
13779
|
+
validateIdentifier(colName, "where(column)");
|
|
13780
|
+
const operator = assertSafeWhereOperator(op2, "where(operator)");
|
|
13551
13781
|
if (operator === "in" || operator === "not in") {
|
|
13552
13782
|
const values = Array.isArray(val) ? val : [val];
|
|
13553
13783
|
const placeholders = getPlaceholders(values.length, whereParams.length + 1);
|
|
@@ -13569,6 +13799,7 @@ function createQueryBuilder(state) {
|
|
|
13569
13799
|
const keys = Object.keys(whereObject);
|
|
13570
13800
|
const conditions = [];
|
|
13571
13801
|
for (const key of keys) {
|
|
13802
|
+
validateIdentifier(key, "where(column)");
|
|
13572
13803
|
const value2 = whereObject[key];
|
|
13573
13804
|
if (Array.isArray(value2)) {
|
|
13574
13805
|
const placeholders = getPlaceholders(value2.length, whereParams.length + 1);
|
|
@@ -13597,21 +13828,25 @@ function createQueryBuilder(state) {
|
|
|
13597
13828
|
return this;
|
|
13598
13829
|
},
|
|
13599
13830
|
whereNull(column) {
|
|
13831
|
+
validateIdentifier(String(column), "whereNull(column)");
|
|
13600
13832
|
const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
|
|
13601
13833
|
text = `${text} ${keyword} ${String(column)} IS NULL`;
|
|
13602
13834
|
built = null;
|
|
13603
13835
|
return this;
|
|
13604
13836
|
},
|
|
13605
13837
|
whereNotNull(column) {
|
|
13838
|
+
validateIdentifier(String(column), "whereNotNull(column)");
|
|
13606
13839
|
const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
|
|
13607
13840
|
text = `${text} ${keyword} ${String(column)} IS NOT NULL`;
|
|
13608
13841
|
built = null;
|
|
13609
13842
|
return this;
|
|
13610
13843
|
},
|
|
13611
13844
|
whereBetween(column, start, end) {
|
|
13845
|
+
validateIdentifier(String(column), "whereBetween(column)");
|
|
13612
13846
|
const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
|
|
13847
|
+
const i = whereParams.length + 1;
|
|
13848
|
+
text = `${text} ${keyword} ${String(column)} BETWEEN ${getPlaceholder(i)} AND ${getPlaceholder(i + 1)}`;
|
|
13613
13849
|
whereParams.push(start, end);
|
|
13614
|
-
text = `${text} ${keyword} ${String(column)} BETWEEN ? AND ?`;
|
|
13615
13850
|
built = null;
|
|
13616
13851
|
return this;
|
|
13617
13852
|
},
|
|
@@ -13622,10 +13857,32 @@ function createQueryBuilder(state) {
|
|
|
13622
13857
|
return this;
|
|
13623
13858
|
},
|
|
13624
13859
|
whereJsonContains(column, json) {
|
|
13860
|
+
validateIdentifier(String(column), "whereJsonContains(column)");
|
|
13625
13861
|
const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
|
|
13862
|
+
const dialect = config5.dialect;
|
|
13626
13863
|
const idx = whereParams.length + 1;
|
|
13627
|
-
|
|
13628
|
-
|
|
13864
|
+
if (dialect === "postgres") {
|
|
13865
|
+
if (config5.sql?.jsonContainsMode === "function")
|
|
13866
|
+
text += ` ${keyword} jsonb_contains(${column}, ${getPlaceholder(idx)})`;
|
|
13867
|
+
else
|
|
13868
|
+
text += ` ${keyword} ${column} @> ${getPlaceholder(idx)}`;
|
|
13869
|
+
whereParams.push(JSON.stringify(json));
|
|
13870
|
+
} else if (dialect === "mysql") {
|
|
13871
|
+
text += ` ${keyword} JSON_CONTAINS(${column}, ${getPlaceholder(idx)})`;
|
|
13872
|
+
whereParams.push(JSON.stringify(json));
|
|
13873
|
+
} else {
|
|
13874
|
+
if (Array.isArray(json)) {
|
|
13875
|
+
const conds = json.map((_, i) => `EXISTS (SELECT 1 FROM json_each(${column}) WHERE json_each.value = ${getPlaceholder(idx + i)})`);
|
|
13876
|
+
text += ` ${keyword} (${conds.join(" AND ")})`;
|
|
13877
|
+
for (const v of json)
|
|
13878
|
+
whereParams.push(v);
|
|
13879
|
+
} else if (json !== null && typeof json === "object") {
|
|
13880
|
+
throw new Error("[query-builder] whereJsonContains: object containment is not supported on SQLite \u2014 pass a scalar or array, or use whereJsonPath.");
|
|
13881
|
+
} else {
|
|
13882
|
+
text += ` ${keyword} EXISTS (SELECT 1 FROM json_each(${column}) WHERE json_each.value = ${getPlaceholder(idx)})`;
|
|
13883
|
+
whereParams.push(json);
|
|
13884
|
+
}
|
|
13885
|
+
}
|
|
13629
13886
|
built = null;
|
|
13630
13887
|
return this;
|
|
13631
13888
|
},
|
|
@@ -13652,69 +13909,85 @@ function createQueryBuilder(state) {
|
|
|
13652
13909
|
whereLike(column, pattern, caseSensitive = false) {
|
|
13653
13910
|
const expr = caseSensitive ? sql`${sql(String(column))} LIKE ${pattern}` : sql`LOWER(${sql(String(column))}) LIKE LOWER(${pattern})`;
|
|
13654
13911
|
built = sql`${ensureBuilt()} WHERE ${expr}`;
|
|
13655
|
-
|
|
13912
|
+
const ph = getPlaceholder(whereParams.length + 1);
|
|
13913
|
+
addWhereText("WHERE", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} LIKE ${caseSensitive ? ph : `LOWER(${ph})`}`);
|
|
13914
|
+
whereParams.push(pattern);
|
|
13656
13915
|
return this;
|
|
13657
13916
|
},
|
|
13658
13917
|
whereILike(column, pattern) {
|
|
13918
|
+
const ph = getPlaceholder(whereParams.length + 1);
|
|
13659
13919
|
if (config5.dialect === "postgres") {
|
|
13660
13920
|
built = sql`${ensureBuilt()} WHERE ${sql(String(column))} ILIKE ${pattern}`;
|
|
13661
|
-
addWhereText("WHERE", `${String(column)} ILIKE
|
|
13921
|
+
addWhereText("WHERE", `${String(column)} ILIKE ${ph}`);
|
|
13662
13922
|
} else {
|
|
13663
13923
|
const expr = sql`LOWER(${sql(String(column))}) LIKE LOWER(${pattern})`;
|
|
13664
13924
|
built = sql`${ensureBuilt()} WHERE ${expr}`;
|
|
13665
|
-
addWhereText("WHERE", `LOWER(${String(column)}) LIKE LOWER(
|
|
13925
|
+
addWhereText("WHERE", `LOWER(${String(column)}) LIKE LOWER(${ph})`);
|
|
13666
13926
|
}
|
|
13927
|
+
whereParams.push(pattern);
|
|
13667
13928
|
return this;
|
|
13668
13929
|
},
|
|
13669
13930
|
orWhereLike(column, pattern, caseSensitive = false) {
|
|
13670
13931
|
const expr = caseSensitive ? sql`${sql(String(column))} LIKE ${pattern}` : sql`LOWER(${sql(String(column))}) LIKE LOWER(${pattern})`;
|
|
13671
13932
|
built = sql`${ensureBuilt()} OR ${expr}`;
|
|
13672
|
-
|
|
13933
|
+
const ph = getPlaceholder(whereParams.length + 1);
|
|
13934
|
+
addWhereText("OR", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} LIKE ${caseSensitive ? ph : `LOWER(${ph})`}`);
|
|
13935
|
+
whereParams.push(pattern);
|
|
13673
13936
|
return this;
|
|
13674
13937
|
},
|
|
13675
13938
|
orWhereILike(column, pattern) {
|
|
13939
|
+
const ph = getPlaceholder(whereParams.length + 1);
|
|
13676
13940
|
if (config5.dialect === "postgres") {
|
|
13677
13941
|
built = sql`${ensureBuilt()} OR ${sql(String(column))} ILIKE ${pattern}`;
|
|
13678
|
-
addWhereText("OR", `${String(column)} ILIKE
|
|
13942
|
+
addWhereText("OR", `${String(column)} ILIKE ${ph}`);
|
|
13679
13943
|
} else {
|
|
13680
13944
|
const expr = sql`LOWER(${sql(String(column))}) LIKE LOWER(${pattern})`;
|
|
13681
13945
|
built = sql`${ensureBuilt()} OR ${expr}`;
|
|
13682
|
-
addWhereText("OR", `LOWER(${String(column)}) LIKE LOWER(
|
|
13946
|
+
addWhereText("OR", `LOWER(${String(column)}) LIKE LOWER(${ph})`);
|
|
13683
13947
|
}
|
|
13948
|
+
whereParams.push(pattern);
|
|
13684
13949
|
return this;
|
|
13685
13950
|
},
|
|
13686
13951
|
whereNotLike(column, pattern, caseSensitive = false) {
|
|
13687
13952
|
const expr = caseSensitive ? sql`${sql(String(column))} NOT LIKE ${pattern}` : sql`LOWER(${sql(String(column))}) NOT LIKE LOWER(${pattern})`;
|
|
13688
13953
|
built = sql`${ensureBuilt()} WHERE ${expr}`;
|
|
13689
|
-
|
|
13954
|
+
const ph = getPlaceholder(whereParams.length + 1);
|
|
13955
|
+
addWhereText("WHERE", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} NOT LIKE ${caseSensitive ? ph : `LOWER(${ph})`}`);
|
|
13956
|
+
whereParams.push(pattern);
|
|
13690
13957
|
return this;
|
|
13691
13958
|
},
|
|
13692
13959
|
whereNotILike(column, pattern) {
|
|
13960
|
+
const ph = getPlaceholder(whereParams.length + 1);
|
|
13693
13961
|
if (config5.dialect === "postgres") {
|
|
13694
13962
|
built = sql`${ensureBuilt()} WHERE ${sql(String(column))} NOT ILIKE ${pattern}`;
|
|
13695
|
-
addWhereText("WHERE", `${String(column)} NOT ILIKE
|
|
13963
|
+
addWhereText("WHERE", `${String(column)} NOT ILIKE ${ph}`);
|
|
13696
13964
|
} else {
|
|
13697
13965
|
const expr = sql`LOWER(${sql(String(column))}) NOT LIKE LOWER(${pattern})`;
|
|
13698
13966
|
built = sql`${ensureBuilt()} WHERE ${expr}`;
|
|
13699
|
-
addWhereText("WHERE", `LOWER(${String(column)}) NOT LIKE LOWER(
|
|
13967
|
+
addWhereText("WHERE", `LOWER(${String(column)}) NOT LIKE LOWER(${ph})`);
|
|
13700
13968
|
}
|
|
13969
|
+
whereParams.push(pattern);
|
|
13701
13970
|
return this;
|
|
13702
13971
|
},
|
|
13703
13972
|
orWhereNotLike(column, pattern, caseSensitive = false) {
|
|
13704
13973
|
const expr = caseSensitive ? sql`${sql(String(column))} NOT LIKE ${pattern}` : sql`LOWER(${sql(String(column))}) NOT LIKE LOWER(${pattern})`;
|
|
13705
13974
|
built = sql`${ensureBuilt()} OR ${expr}`;
|
|
13706
|
-
|
|
13975
|
+
const ph = getPlaceholder(whereParams.length + 1);
|
|
13976
|
+
addWhereText("OR", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} NOT LIKE ${caseSensitive ? ph : `LOWER(${ph})`}`);
|
|
13977
|
+
whereParams.push(pattern);
|
|
13707
13978
|
return this;
|
|
13708
13979
|
},
|
|
13709
13980
|
orWhereNotILike(column, pattern) {
|
|
13981
|
+
const ph = getPlaceholder(whereParams.length + 1);
|
|
13710
13982
|
if (config5.dialect === "postgres") {
|
|
13711
13983
|
built = sql`${ensureBuilt()} OR ${sql(String(column))} NOT ILIKE ${pattern}`;
|
|
13712
|
-
addWhereText("OR", `${String(column)} NOT ILIKE
|
|
13984
|
+
addWhereText("OR", `${String(column)} NOT ILIKE ${ph}`);
|
|
13713
13985
|
} else {
|
|
13714
13986
|
const expr = sql`LOWER(${sql(String(column))}) NOT LIKE LOWER(${pattern})`;
|
|
13715
13987
|
built = sql`${ensureBuilt()} OR ${expr}`;
|
|
13716
|
-
addWhereText("OR", `LOWER(${String(column)}) NOT LIKE LOWER(
|
|
13988
|
+
addWhereText("OR", `LOWER(${String(column)}) NOT LIKE LOWER(${ph})`);
|
|
13717
13989
|
}
|
|
13990
|
+
whereParams.push(pattern);
|
|
13718
13991
|
return this;
|
|
13719
13992
|
},
|
|
13720
13993
|
whereAny(cols, op, value) {
|
|
@@ -13754,8 +14027,10 @@ function createQueryBuilder(state) {
|
|
|
13754
14027
|
return this;
|
|
13755
14028
|
},
|
|
13756
14029
|
whereNotBetween(column, start, end) {
|
|
14030
|
+
validateIdentifier(String(column), "whereNotBetween(column)");
|
|
13757
14031
|
const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
|
|
13758
|
-
|
|
14032
|
+
const i = whereParams.length + 1;
|
|
14033
|
+
text += ` ${keyword} ${column} NOT BETWEEN ${getPlaceholder(i)} AND ${getPlaceholder(i + 1)}`;
|
|
13759
14034
|
whereParams.push(start, end);
|
|
13760
14035
|
built = null;
|
|
13761
14036
|
return this;
|
|
@@ -13795,6 +14070,7 @@ function createQueryBuilder(state) {
|
|
|
13795
14070
|
return this;
|
|
13796
14071
|
},
|
|
13797
14072
|
whereIn(column, values) {
|
|
14073
|
+
validateIdentifier(String(column), "whereIn(column)");
|
|
13798
14074
|
const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
|
|
13799
14075
|
if (Array.isArray(values)) {
|
|
13800
14076
|
const placeholders = getPlaceholders(values.length, whereParams.length + 1);
|
|
@@ -13807,6 +14083,7 @@ function createQueryBuilder(state) {
|
|
|
13807
14083
|
return this;
|
|
13808
14084
|
},
|
|
13809
14085
|
orWhereIn(column, values) {
|
|
14086
|
+
validateIdentifier(String(column), "orWhereIn(column)");
|
|
13810
14087
|
if (Array.isArray(values)) {
|
|
13811
14088
|
const placeholders = getPlaceholders(values.length, whereParams.length + 1);
|
|
13812
14089
|
text += ` OR ${column} IN (${placeholders})`;
|
|
@@ -13818,6 +14095,7 @@ function createQueryBuilder(state) {
|
|
|
13818
14095
|
return this;
|
|
13819
14096
|
},
|
|
13820
14097
|
whereNotIn(column, values) {
|
|
14098
|
+
validateIdentifier(String(column), "whereNotIn(column)");
|
|
13821
14099
|
const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
|
|
13822
14100
|
if (Array.isArray(values)) {
|
|
13823
14101
|
const placeholders = getPlaceholders(values.length, whereParams.length + 1);
|
|
@@ -13830,6 +14108,7 @@ function createQueryBuilder(state) {
|
|
|
13830
14108
|
return this;
|
|
13831
14109
|
},
|
|
13832
14110
|
orWhereNotIn(column, values) {
|
|
14111
|
+
validateIdentifier(String(column), "orWhereNotIn(column)");
|
|
13833
14112
|
if (Array.isArray(values)) {
|
|
13834
14113
|
const placeholders = getPlaceholders(values.length, whereParams.length + 1);
|
|
13835
14114
|
text += ` OR ${column} NOT IN (${placeholders})`;
|
|
@@ -13855,6 +14134,8 @@ function createQueryBuilder(state) {
|
|
|
13855
14134
|
},
|
|
13856
14135
|
andWhere(expr, op, value) {
|
|
13857
14136
|
if (typeof expr === "string" && op !== undefined) {
|
|
14137
|
+
validateIdentifier(String(expr), "andWhere(column)");
|
|
14138
|
+
assertSafeWhereOperator(op, "andWhere(operator)");
|
|
13858
14139
|
const paramIndex = whereParams.length + 1;
|
|
13859
14140
|
whereConditions.push(`${String(expr)} ${String(op)} ${getPlaceholder(paramIndex)}`);
|
|
13860
14141
|
whereParams.push(value);
|
|
@@ -13865,7 +14146,8 @@ function createQueryBuilder(state) {
|
|
|
13865
14146
|
if (Array.isArray(expr)) {
|
|
13866
14147
|
const [col, op2, val] = expr;
|
|
13867
14148
|
const colName = String(col);
|
|
13868
|
-
|
|
14149
|
+
validateIdentifier(colName, "andWhere(column)");
|
|
14150
|
+
const operator = assertSafeWhereOperator(op2, "andWhere(operator)");
|
|
13869
14151
|
if (operator === "in" || operator === "not in") {
|
|
13870
14152
|
const values = Array.isArray(val) ? val : [val];
|
|
13871
14153
|
const placeholders = getPlaceholders(values.length, whereParams.length + 1);
|
|
@@ -13915,6 +14197,8 @@ function createQueryBuilder(state) {
|
|
|
13915
14197
|
},
|
|
13916
14198
|
orWhere(expr, op, value) {
|
|
13917
14199
|
if (typeof expr === "string" && op !== undefined) {
|
|
14200
|
+
validateIdentifier(String(expr), "orWhere(column)");
|
|
14201
|
+
assertSafeWhereOperator(op, "orWhere(operator)");
|
|
13918
14202
|
const paramIndex = whereParams.length + 1;
|
|
13919
14203
|
whereConditions.push(`OR ${String(expr)} ${String(op)} ${getPlaceholder(paramIndex)}`);
|
|
13920
14204
|
whereParams.push(value);
|
|
@@ -13925,7 +14209,8 @@ function createQueryBuilder(state) {
|
|
|
13925
14209
|
if (Array.isArray(expr)) {
|
|
13926
14210
|
const [col, op2, val] = expr;
|
|
13927
14211
|
const colName = String(col);
|
|
13928
|
-
|
|
14212
|
+
validateIdentifier(colName, "orWhere(column)");
|
|
14213
|
+
const operator = assertSafeWhereOperator(op2, "orWhere(operator)");
|
|
13929
14214
|
if (operator === "in" || operator === "not in") {
|
|
13930
14215
|
const values = Array.isArray(val) ? val : [val];
|
|
13931
14216
|
const placeholders = getPlaceholders(values.length, whereParams.length + 1);
|
|
@@ -14023,7 +14308,11 @@ function createQueryBuilder(state) {
|
|
|
14023
14308
|
return this;
|
|
14024
14309
|
},
|
|
14025
14310
|
join(table2, onLeft, operator, onRight) {
|
|
14026
|
-
|
|
14311
|
+
validateIdentifier(table2, "join(table)");
|
|
14312
|
+
validateQualifiedIdentifier(onLeft, "join(onLeft)");
|
|
14313
|
+
validateQualifiedIdentifier(onRight, "join(onRight)");
|
|
14314
|
+
assertSafeWhereOperator(operator, "join(operator)");
|
|
14315
|
+
insertJoin(`JOIN ${table2} ON ${onLeft} ${operator} ${onRight}`);
|
|
14027
14316
|
joinedTables.add(table2);
|
|
14028
14317
|
return this;
|
|
14029
14318
|
},
|
|
@@ -14032,44 +14321,55 @@ function createQueryBuilder(state) {
|
|
|
14032
14321
|
validateQualifiedIdentifier(onLeft, "joinSub(onLeft)");
|
|
14033
14322
|
validateQualifiedIdentifier(onRight, "joinSub(onRight)");
|
|
14034
14323
|
assertSafeWhereOperator(operator, "joinSub(operator)");
|
|
14035
|
-
|
|
14036
|
-
built = null;
|
|
14324
|
+
insertJoin(`JOIN (${String(sub.toSQL())}) AS ${alias} ON ${onLeft} ${operator} ${onRight}`);
|
|
14037
14325
|
joinedTables.add(alias);
|
|
14038
14326
|
return this;
|
|
14039
14327
|
},
|
|
14040
14328
|
innerJoin(table2, onLeft, operator, onRight) {
|
|
14041
|
-
|
|
14042
|
-
|
|
14329
|
+
validateIdentifier(table2, "innerJoin(table)");
|
|
14330
|
+
validateQualifiedIdentifier(onLeft, "innerJoin(onLeft)");
|
|
14331
|
+
validateQualifiedIdentifier(onRight, "innerJoin(onRight)");
|
|
14332
|
+
assertSafeWhereOperator(operator, "innerJoin(operator)");
|
|
14333
|
+
insertJoin(`INNER JOIN ${table2} ON ${onLeft} ${operator} ${onRight}`);
|
|
14043
14334
|
joinedTables.add(table2);
|
|
14044
14335
|
return this;
|
|
14045
14336
|
},
|
|
14046
14337
|
leftJoin(table2, onLeft, operator, onRight) {
|
|
14047
|
-
|
|
14048
|
-
|
|
14338
|
+
validateIdentifier(table2, "leftJoin(table)");
|
|
14339
|
+
validateQualifiedIdentifier(onLeft, "leftJoin(onLeft)");
|
|
14340
|
+
validateQualifiedIdentifier(onRight, "leftJoin(onRight)");
|
|
14341
|
+
assertSafeWhereOperator(operator, "leftJoin(operator)");
|
|
14342
|
+
insertJoin(`LEFT JOIN ${table2} ON ${onLeft} ${operator} ${onRight}`);
|
|
14049
14343
|
joinedTables.add(table2);
|
|
14050
14344
|
return this;
|
|
14051
14345
|
},
|
|
14052
14346
|
leftJoinSub(sub, alias, onLeft, operator, onRight) {
|
|
14053
|
-
|
|
14054
|
-
|
|
14347
|
+
validateIdentifier(alias, "leftJoinSub(alias)");
|
|
14348
|
+
validateQualifiedIdentifier(onLeft, "leftJoinSub(onLeft)");
|
|
14349
|
+
validateQualifiedIdentifier(onRight, "leftJoinSub(onRight)");
|
|
14350
|
+
assertSafeWhereOperator(operator, "leftJoinSub(operator)");
|
|
14351
|
+
insertJoin(`LEFT JOIN (${String(sub.toSQL())}) AS ${alias} ON ${onLeft} ${operator} ${onRight}`);
|
|
14055
14352
|
joinedTables.add(alias);
|
|
14056
14353
|
return this;
|
|
14057
14354
|
},
|
|
14058
14355
|
rightJoin(table2, onLeft, operator, onRight) {
|
|
14059
|
-
|
|
14060
|
-
|
|
14356
|
+
validateIdentifier(table2, "rightJoin(table)");
|
|
14357
|
+
validateQualifiedIdentifier(onLeft, "rightJoin(onLeft)");
|
|
14358
|
+
validateQualifiedIdentifier(onRight, "rightJoin(onRight)");
|
|
14359
|
+
assertSafeWhereOperator(operator, "rightJoin(operator)");
|
|
14360
|
+
insertJoin(`RIGHT JOIN ${table2} ON ${onLeft} ${operator} ${onRight}`);
|
|
14061
14361
|
joinedTables.add(table2);
|
|
14062
14362
|
return this;
|
|
14063
14363
|
},
|
|
14064
14364
|
crossJoin(table2) {
|
|
14065
|
-
|
|
14066
|
-
|
|
14365
|
+
validateIdentifier(table2, "crossJoin(table)");
|
|
14366
|
+
insertJoin(`CROSS JOIN ${table2}`);
|
|
14067
14367
|
joinedTables.add(table2);
|
|
14068
14368
|
return this;
|
|
14069
14369
|
},
|
|
14070
14370
|
crossJoinSub(sub, alias) {
|
|
14071
|
-
|
|
14072
|
-
|
|
14371
|
+
validateIdentifier(alias, "crossJoinSub(alias)");
|
|
14372
|
+
insertJoin(`CROSS JOIN (${String(sub.toSQL())}) AS ${alias}`);
|
|
14073
14373
|
joinedTables.add(alias);
|
|
14074
14374
|
return this;
|
|
14075
14375
|
},
|
|
@@ -14122,9 +14422,10 @@ function createQueryBuilder(state) {
|
|
|
14122
14422
|
return this;
|
|
14123
14423
|
},
|
|
14124
14424
|
having(expr) {
|
|
14425
|
+
const kw = /\bHAVING\b/i.test(text) ? "AND" : "HAVING";
|
|
14125
14426
|
if (Array.isArray(expr)) {
|
|
14126
14427
|
const paramIdx = whereParams.length + 1;
|
|
14127
|
-
text = `${text}
|
|
14428
|
+
text = `${text} ${kw} ${expr[0]} ${expr[1]} ${getPlaceholder(paramIdx)}`;
|
|
14128
14429
|
whereParams.push(expr[2]);
|
|
14129
14430
|
built = null;
|
|
14130
14431
|
} else if (expr && typeof expr === "object" && !("raw" in expr)) {
|
|
@@ -14138,18 +14439,19 @@ function createQueryBuilder(state) {
|
|
|
14138
14439
|
conditions[i] = `${key} = ${getPlaceholder(baseIdx + i + 1)}`;
|
|
14139
14440
|
whereParams.push(expr[key]);
|
|
14140
14441
|
}
|
|
14141
|
-
text = `${text}
|
|
14442
|
+
text = `${text} ${kw} ${conditions.join(" AND ")}`;
|
|
14142
14443
|
built = null;
|
|
14143
14444
|
}
|
|
14144
14445
|
} else if (expr && typeof expr.raw !== "undefined") {
|
|
14145
|
-
text += `
|
|
14446
|
+
text += ` ${kw} ${expr.raw}`;
|
|
14146
14447
|
built = null;
|
|
14147
14448
|
}
|
|
14148
14449
|
return this;
|
|
14149
14450
|
},
|
|
14150
14451
|
havingRaw(fragment) {
|
|
14151
14452
|
assertSqlFragment(fragment, "havingRaw(fragment)");
|
|
14152
|
-
text
|
|
14453
|
+
const kw = /\bHAVING\b/i.test(text) ? "AND" : "HAVING";
|
|
14454
|
+
text += ` ${kw} ${String(fragment)}`;
|
|
14153
14455
|
built = null;
|
|
14154
14456
|
return this;
|
|
14155
14457
|
},
|
|
@@ -14160,13 +14462,27 @@ function createQueryBuilder(state) {
|
|
|
14160
14462
|
return this;
|
|
14161
14463
|
},
|
|
14162
14464
|
union(other) {
|
|
14163
|
-
|
|
14164
|
-
built = null;
|
|
14465
|
+
appendSetOp("UNION", other);
|
|
14165
14466
|
return this;
|
|
14166
14467
|
},
|
|
14167
14468
|
unionAll(other) {
|
|
14168
|
-
|
|
14169
|
-
|
|
14469
|
+
appendSetOp("UNION ALL", other);
|
|
14470
|
+
return this;
|
|
14471
|
+
},
|
|
14472
|
+
intersect(other) {
|
|
14473
|
+
appendSetOp("INTERSECT", other);
|
|
14474
|
+
return this;
|
|
14475
|
+
},
|
|
14476
|
+
intersectAll(other) {
|
|
14477
|
+
appendSetOp("INTERSECT ALL", other);
|
|
14478
|
+
return this;
|
|
14479
|
+
},
|
|
14480
|
+
except(other) {
|
|
14481
|
+
appendSetOp("EXCEPT", other);
|
|
14482
|
+
return this;
|
|
14483
|
+
},
|
|
14484
|
+
exceptAll(other) {
|
|
14485
|
+
appendSetOp("EXCEPT ALL", other);
|
|
14170
14486
|
return this;
|
|
14171
14487
|
},
|
|
14172
14488
|
forPage(page, perPage) {
|
|
@@ -14210,11 +14526,22 @@ function createQueryBuilder(state) {
|
|
|
14210
14526
|
const e = await this.exists();
|
|
14211
14527
|
return !e;
|
|
14212
14528
|
},
|
|
14213
|
-
async paginate(perPage, page = 1) {
|
|
14529
|
+
async paginate(perPage, page = 1, opts = {}) {
|
|
14214
14530
|
if (!Number.isFinite(perPage) || perPage <= 0 || !Number.isInteger(perPage))
|
|
14215
14531
|
throw new TypeError(`[query-builder] paginate(perPage): expected positive integer, got ${perPage}`);
|
|
14216
14532
|
if (!Number.isFinite(page) || page < 1 || !Number.isInteger(page))
|
|
14217
14533
|
throw new TypeError(`[query-builder] paginate(page): expected integer >= 1, got ${page}`);
|
|
14534
|
+
if (opts.tx) {
|
|
14535
|
+
const baseSql = reorderSelectClauses(text);
|
|
14536
|
+
const baseParams = [...whereParams];
|
|
14537
|
+
const cRows2 = await opts.tx.unsafe(`SELECT COUNT(*) as c FROM (${baseSql}) as sub`, baseParams);
|
|
14538
|
+
const total2 = Number(cRows2?.[0]?.c ?? 0);
|
|
14539
|
+
const lastPage2 = Math.max(1, Math.ceil(total2 / perPage));
|
|
14540
|
+
const p2 = Math.max(1, Math.min(page, lastPage2));
|
|
14541
|
+
const offset2 = (p2 - 1) * perPage;
|
|
14542
|
+
const data2 = await opts.tx.unsafe(`${baseSql} LIMIT ${perPage} OFFSET ${offset2}`, baseParams);
|
|
14543
|
+
return { data: data2, meta: { perPage, page: p2, total: total2, lastPage: lastPage2 } };
|
|
14544
|
+
}
|
|
14218
14545
|
const countQ = sql`SELECT COUNT(*) as c FROM (${ensureBuilt()}) as sub`;
|
|
14219
14546
|
const cRows = await runWithHooks(countQ, "select", { signal: abortSignal, timeoutMs });
|
|
14220
14547
|
const [cRow] = cRows;
|
|
@@ -14371,7 +14698,7 @@ function createQueryBuilder(state) {
|
|
|
14371
14698
|
},
|
|
14372
14699
|
async get() {
|
|
14373
14700
|
const hooks = config5.hooks;
|
|
14374
|
-
const hasQueryHooks = hooks && (hooks.onQueryStart || hooks.onQueryEnd || hooks.onQueryError || hooks.startSpan);
|
|
14701
|
+
const hasQueryHooks = hooks && (hooks.onQueryStart || hooks.onQueryEnd || hooks.onQueryError || hooks.startSpan || hasSlowQueryHook(hooks));
|
|
14375
14702
|
if (!config5.softDeletes?.enabled && !useCache && !timeoutMs && !abortSignal && !hasQueryHooks) {
|
|
14376
14703
|
const prepareFn = _sql._prepareStatement;
|
|
14377
14704
|
if (prepareFn) {
|
|
@@ -14426,7 +14753,7 @@ function createQueryBuilder(state) {
|
|
|
14426
14753
|
},
|
|
14427
14754
|
async first() {
|
|
14428
14755
|
const fHooks = config5.hooks;
|
|
14429
|
-
const fHasQueryHooks = fHooks && (fHooks.onQueryStart || fHooks.onQueryEnd || fHooks.onQueryError || fHooks.startSpan);
|
|
14756
|
+
const fHasQueryHooks = fHooks && (fHooks.onQueryStart || fHooks.onQueryEnd || fHooks.onQueryError || fHooks.startSpan || hasSlowQueryHook(fHooks));
|
|
14430
14757
|
if (!config5.softDeletes?.enabled && !useCache && !timeoutMs && !abortSignal && !fHasQueryHooks) {
|
|
14431
14758
|
const prepareFn = _sql._prepareStatement;
|
|
14432
14759
|
if (prepareFn) {
|
|
@@ -14508,7 +14835,7 @@ function createQueryBuilder(state) {
|
|
|
14508
14835
|
countText = `SELECT COUNT(*) as c FROM ${table}`;
|
|
14509
14836
|
}
|
|
14510
14837
|
const cHooks = config5.hooks;
|
|
14511
|
-
const cHasHooks = cHooks && (cHooks.onQueryStart || cHooks.onQueryEnd || cHooks.onQueryError || cHooks.startSpan);
|
|
14838
|
+
const cHasHooks = cHooks && (cHooks.onQueryStart || cHooks.onQueryEnd || cHooks.onQueryError || cHooks.startSpan || hasSlowQueryHook(cHooks));
|
|
14512
14839
|
if (!config5.softDeletes?.enabled && !useCache && !timeoutMs && !abortSignal && !cHasHooks) {
|
|
14513
14840
|
const prepareFn = _sql._prepareStatement;
|
|
14514
14841
|
if (prepareFn) {
|
|
@@ -14526,7 +14853,7 @@ function createQueryBuilder(state) {
|
|
|
14526
14853
|
const fromIdx = text.indexOf(" FROM ");
|
|
14527
14854
|
const avgText = fromIdx !== -1 ? `SELECT AVG(${column}) as a${text.substring(fromIdx)}` : `SELECT AVG(${column}) as a FROM ${table}`;
|
|
14528
14855
|
const aHooks = config5.hooks;
|
|
14529
|
-
const aHasHooks = aHooks && (aHooks.onQueryStart || aHooks.onQueryEnd || aHooks.onQueryError || aHooks.startSpan);
|
|
14856
|
+
const aHasHooks = aHooks && (aHooks.onQueryStart || aHooks.onQueryEnd || aHooks.onQueryError || aHooks.startSpan || hasSlowQueryHook(aHooks));
|
|
14530
14857
|
if (!config5.softDeletes?.enabled && !useCache && !timeoutMs && !abortSignal && !aHasHooks) {
|
|
14531
14858
|
const prepareFn = _sql._prepareStatement;
|
|
14532
14859
|
if (prepareFn) {
|
|
@@ -14596,6 +14923,9 @@ function createQueryBuilder(state) {
|
|
|
14596
14923
|
toParams() {
|
|
14597
14924
|
return ensureBuilt().values?.() ?? [];
|
|
14598
14925
|
},
|
|
14926
|
+
__rawState() {
|
|
14927
|
+
return { sql: reorderSelectClauses(text), params: [...whereParams] };
|
|
14928
|
+
},
|
|
14599
14929
|
raw() {
|
|
14600
14930
|
return ensureBuilt().raw();
|
|
14601
14931
|
},
|
|
@@ -14954,15 +15284,15 @@ function createQueryBuilder(state) {
|
|
|
14954
15284
|
params.length = totalParams;
|
|
14955
15285
|
if (rowCount === 1) {
|
|
14956
15286
|
if (!isPostgres) {
|
|
14957
|
-
let cols = keys[0];
|
|
15287
|
+
let cols = quoteId(keys[0]);
|
|
14958
15288
|
let placeholders = "?";
|
|
14959
15289
|
params[0] = firstRow[keys[0]];
|
|
14960
15290
|
for (let c = 1;c < colCount; c++) {
|
|
14961
|
-
cols += `,${keys[c]}`;
|
|
15291
|
+
cols += `,${quoteId(keys[c])}`;
|
|
14962
15292
|
placeholders += ",?";
|
|
14963
15293
|
params[c] = firstRow[keys[c]];
|
|
14964
15294
|
}
|
|
14965
|
-
sqlText = `INSERT INTO ${table}(${cols})VALUES(${placeholders})`;
|
|
15295
|
+
sqlText = `INSERT INTO ${quoteId(table)}(${cols})VALUES(${placeholders})`;
|
|
14966
15296
|
} else {
|
|
14967
15297
|
const columnList = keys.map((k) => quoteId(k)).join(",");
|
|
14968
15298
|
sqlText = `INSERT INTO ${quoteId(table)}(${columnList})VALUES(`;
|
|
@@ -15020,7 +15350,7 @@ function createQueryBuilder(state) {
|
|
|
15020
15350
|
},
|
|
15021
15351
|
execute() {
|
|
15022
15352
|
const hooks = config5.hooks;
|
|
15023
|
-
const hasHooks = hooks && (hooks.onQueryStart || hooks.onQueryEnd || hooks.onQueryError || hooks.startSpan || hooks.beforeCreate || hooks.afterCreate);
|
|
15353
|
+
const hasHooks = hooks && (hooks.onQueryStart || hooks.onQueryEnd || hooks.onQueryError || hooks.startSpan || hooks.beforeCreate || hooks.afterCreate || hasSlowQueryHook(hooks));
|
|
15024
15354
|
if (!hasHooks) {
|
|
15025
15355
|
const prepareFn = _sql._prepareStatement;
|
|
15026
15356
|
if (prepareFn) {
|
|
@@ -15494,55 +15824,72 @@ function createQueryBuilder(state) {
|
|
|
15494
15824
|
};
|
|
15495
15825
|
},
|
|
15496
15826
|
async insertOrIgnore(table, values) {
|
|
15497
|
-
|
|
15498
|
-
|
|
15499
|
-
return
|
|
15500
|
-
}
|
|
15501
|
-
const
|
|
15502
|
-
|
|
15827
|
+
const rows = Array.isArray(values) ? values : [values];
|
|
15828
|
+
if (!rows.length)
|
|
15829
|
+
return;
|
|
15830
|
+
const { colsSql, valuesSql, params } = buildInsertClause(rows);
|
|
15831
|
+
const tbl = quoteInsertIdent(String(table));
|
|
15832
|
+
const sqlText = config5.dialect === "mysql" ? `INSERT IGNORE INTO ${tbl} (${colsSql}) VALUES ${valuesSql}` : `INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql} ON CONFLICT DO NOTHING`;
|
|
15833
|
+
return bunSql.unsafe(sqlText, params).execute();
|
|
15503
15834
|
},
|
|
15504
15835
|
async insertGetId(table, values, idColumn = "id") {
|
|
15836
|
+
const { colsSql, valuesSql, params } = buildInsertClause([values]);
|
|
15837
|
+
const tbl = quoteInsertIdent(String(table));
|
|
15505
15838
|
if (config5.dialect === "mysql") {
|
|
15506
|
-
|
|
15507
|
-
const
|
|
15508
|
-
|
|
15509
|
-
|
|
15510
|
-
|
|
15511
|
-
const
|
|
15512
|
-
|
|
15513
|
-
|
|
15514
|
-
const
|
|
15515
|
-
|
|
15516
|
-
|
|
15517
|
-
}
|
|
15839
|
+
await bunSql.unsafe(`INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql}`, params).execute();
|
|
15840
|
+
const [row2] = await bunSql.unsafe(`SELECT LAST_INSERT_ID() as id`).execute();
|
|
15841
|
+
return row2?.id;
|
|
15842
|
+
}
|
|
15843
|
+
if (config5.dialect === "sqlite") {
|
|
15844
|
+
const res = await bunSql.unsafe(`INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql}`, params).execute();
|
|
15845
|
+
if (res?.lastInsertRowid != null)
|
|
15846
|
+
return res.lastInsertRowid;
|
|
15847
|
+
const [row2] = await bunSql.unsafe(`SELECT last_insert_rowid() as id`).execute();
|
|
15848
|
+
return row2?.id;
|
|
15849
|
+
}
|
|
15850
|
+
const [row] = await bunSql.unsafe(`INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql} RETURNING ${quoteInsertIdent(String(idColumn))} as id`, params).execute();
|
|
15851
|
+
return row?.id;
|
|
15518
15852
|
},
|
|
15519
15853
|
async updateOrInsert(table, match, values) {
|
|
15520
|
-
const
|
|
15521
|
-
const
|
|
15522
|
-
|
|
15854
|
+
const tbl = quoteInsertIdent(String(table));
|
|
15855
|
+
const matchKeys = Object.keys(match);
|
|
15856
|
+
let idx = 1;
|
|
15857
|
+
const whereSql = matchKeys.map((k) => `${quoteInsertIdent(k)} = ${getPlaceholder(idx++)}`).join(" AND ");
|
|
15858
|
+
const whereParams = matchKeys.map((k) => match[k]);
|
|
15859
|
+
const existsRows = await bunSql.unsafe(`SELECT 1 FROM ${tbl} WHERE ${whereSql} LIMIT 1`, whereParams).execute();
|
|
15523
15860
|
if (existsRows.length) {
|
|
15524
|
-
const
|
|
15525
|
-
|
|
15526
|
-
|
|
15527
|
-
|
|
15528
|
-
const
|
|
15529
|
-
await
|
|
15861
|
+
const setKeys = Object.keys(values);
|
|
15862
|
+
let i = 1;
|
|
15863
|
+
const setSql = setKeys.map((k) => `${quoteInsertIdent(k)} = ${getPlaceholder(i++)}`).join(", ");
|
|
15864
|
+
const whereSql2 = matchKeys.map((k) => `${quoteInsertIdent(k)} = ${getPlaceholder(i++)}`).join(" AND ");
|
|
15865
|
+
const params2 = [...setKeys.map((k) => values[k]), ...matchKeys.map((k) => match[k])];
|
|
15866
|
+
await bunSql.unsafe(`UPDATE ${tbl} SET ${setSql} WHERE ${whereSql2}`, params2).execute();
|
|
15530
15867
|
return true;
|
|
15531
15868
|
}
|
|
15869
|
+
const { colsSql, valuesSql, params } = buildInsertClause([{ ...match, ...values }]);
|
|
15870
|
+
await bunSql.unsafe(`INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql}`, params).execute();
|
|
15871
|
+
return true;
|
|
15532
15872
|
},
|
|
15533
15873
|
async upsert(table, rows, conflictColumns, mergeColumns) {
|
|
15534
15874
|
const targetCols = conflictColumns.map((c) => String(c));
|
|
15535
15875
|
const setCols = (mergeColumns ?? []).map((c) => String(c));
|
|
15876
|
+
const list = rows;
|
|
15877
|
+
if (!list.length)
|
|
15878
|
+
return;
|
|
15879
|
+
const { colsSql, valuesSql, params } = buildInsertClause(list);
|
|
15880
|
+
const tbl = quoteInsertIdent(String(table));
|
|
15881
|
+
const insert = `INSERT INTO ${tbl} (${colsSql}) VALUES ${valuesSql}`;
|
|
15536
15882
|
if (config5.dialect === "mysql") {
|
|
15537
|
-
|
|
15538
|
-
|
|
15539
|
-
|
|
15540
|
-
|
|
15541
|
-
|
|
15542
|
-
const
|
|
15543
|
-
|
|
15544
|
-
|
|
15545
|
-
|
|
15883
|
+
if (setCols.length === 0)
|
|
15884
|
+
return bunSql.unsafe(`INSERT IGNORE INTO ${tbl} (${colsSql}) VALUES ${valuesSql}`, params).execute();
|
|
15885
|
+
const updateList2 = setCols.map((c) => `${quoteInsertIdent(c)} = VALUES(${quoteInsertIdent(c)})`).join(", ");
|
|
15886
|
+
return bunSql.unsafe(`${insert} ON DUPLICATE KEY UPDATE ${updateList2}`, params).execute();
|
|
15887
|
+
}
|
|
15888
|
+
const targets = targetCols.map(quoteInsertIdent).join(", ");
|
|
15889
|
+
if (setCols.length === 0)
|
|
15890
|
+
return bunSql.unsafe(`${insert} ON CONFLICT (${targets}) DO NOTHING`, params).execute();
|
|
15891
|
+
const updateList = setCols.map((c) => `${quoteInsertIdent(c)} = EXCLUDED.${quoteInsertIdent(c)}`).join(", ");
|
|
15892
|
+
return bunSql.unsafe(`${insert} ON CONFLICT (${targets}) DO UPDATE SET ${updateList}`, params).execute();
|
|
15546
15893
|
},
|
|
15547
15894
|
async save(table, values) {
|
|
15548
15895
|
const pk = meta?.primaryKeys[String(table)] ?? "id";
|
|
@@ -15645,7 +15992,8 @@ function createQueryBuilder(state) {
|
|
|
15645
15992
|
const colCount = keys.length;
|
|
15646
15993
|
const rowCount = rows.length;
|
|
15647
15994
|
const params = Array.from({ length: rowCount * colCount });
|
|
15648
|
-
|
|
15995
|
+
const quoteId = config5.dialect === "mysql" ? (id) => `\`${String(id).replace(/`/g, "``")}\`` : (id) => `"${String(id).replace(/"/g, '""')}"`;
|
|
15996
|
+
let sql = `INSERT INTO ${quoteId(String(table))}(${keys.map(quoteId).join(",")})VALUES`;
|
|
15649
15997
|
let pidx = 0;
|
|
15650
15998
|
for (let r = 0;r < rowCount; r++) {
|
|
15651
15999
|
if (r > 0)
|
|
@@ -16865,6 +17213,126 @@ var init_introspect = __esm(() => {
|
|
|
16865
17213
|
init_src2();
|
|
16866
17214
|
});
|
|
16867
17215
|
|
|
17216
|
+
// src/actions/introspect-db.ts
|
|
17217
|
+
function sqlTypeToAttr(sqlType) {
|
|
17218
|
+
const t = sqlType.toLowerCase();
|
|
17219
|
+
if (/^bool|boolean|tinyint\(1\)/.test(t))
|
|
17220
|
+
return "boolean";
|
|
17221
|
+
if (/int|serial|numeric|decimal|double|real|float|money/.test(t))
|
|
17222
|
+
return "number";
|
|
17223
|
+
if (/timestamp|datetime|date|time/.test(t))
|
|
17224
|
+
return "datetime";
|
|
17225
|
+
if (/json/.test(t))
|
|
17226
|
+
return "json";
|
|
17227
|
+
return "string";
|
|
17228
|
+
}
|
|
17229
|
+
function singularize(name) {
|
|
17230
|
+
if (/ies$/i.test(name))
|
|
17231
|
+
return name.replace(/ies$/i, "y");
|
|
17232
|
+
if (/ses$/i.test(name))
|
|
17233
|
+
return name.replace(/es$/i, "");
|
|
17234
|
+
if (/s$/i.test(name) && !/ss$/i.test(name))
|
|
17235
|
+
return name.replace(/s$/i, "");
|
|
17236
|
+
return name;
|
|
17237
|
+
}
|
|
17238
|
+
function pascalCase(name) {
|
|
17239
|
+
return name.replace(/[-_\s]+/g, " ").split(" ").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
17240
|
+
}
|
|
17241
|
+
function modelNameForTable(table) {
|
|
17242
|
+
return pascalCase(singularize(table));
|
|
17243
|
+
}
|
|
17244
|
+
async function listTables(qb, dialect) {
|
|
17245
|
+
if (dialect === "postgres") {
|
|
17246
|
+
const rows2 = await qb.unsafe(`SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' ORDER BY table_name`);
|
|
17247
|
+
return rows2.map((r) => r.table_name);
|
|
17248
|
+
}
|
|
17249
|
+
if (dialect === "mysql") {
|
|
17250
|
+
const rows2 = await qb.unsafe(`SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() AND table_type = 'BASE TABLE' ORDER BY table_name`);
|
|
17251
|
+
return rows2.map((r) => r.table_name ?? r.TABLE_NAME);
|
|
17252
|
+
}
|
|
17253
|
+
const rows = await qb.unsafe(`SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name`);
|
|
17254
|
+
return rows.map((r) => r.name);
|
|
17255
|
+
}
|
|
17256
|
+
async function readColumns(qb, dialect, table) {
|
|
17257
|
+
if (dialect === "sqlite") {
|
|
17258
|
+
const rows2 = await qb.unsafe(`PRAGMA table_info(${table})`);
|
|
17259
|
+
return rows2.map((r) => ({
|
|
17260
|
+
name: r.name,
|
|
17261
|
+
sqlType: String(r.type || ""),
|
|
17262
|
+
nullable: Number(r.notnull) === 0,
|
|
17263
|
+
isPrimaryKey: Number(r.pk) > 0
|
|
17264
|
+
}));
|
|
17265
|
+
}
|
|
17266
|
+
if (dialect === "postgres") {
|
|
17267
|
+
const rows2 = await qb.unsafe(`SELECT c.column_name, c.data_type, c.is_nullable,
|
|
17268
|
+
(SELECT COUNT(*) FROM information_schema.table_constraints tc
|
|
17269
|
+
JOIN information_schema.key_column_usage k ON k.constraint_name = tc.constraint_name
|
|
17270
|
+
WHERE tc.constraint_type = 'PRIMARY KEY' AND tc.table_name = c.table_name AND k.column_name = c.column_name) AS is_pk
|
|
17271
|
+
FROM information_schema.columns c
|
|
17272
|
+
WHERE c.table_name = $1 AND c.table_schema = 'public'
|
|
17273
|
+
ORDER BY c.ordinal_position`, [table]);
|
|
17274
|
+
return rows2.map((r) => ({
|
|
17275
|
+
name: r.column_name,
|
|
17276
|
+
sqlType: String(r.data_type || ""),
|
|
17277
|
+
nullable: String(r.is_nullable).toUpperCase() === "YES",
|
|
17278
|
+
isPrimaryKey: Number(r.is_pk) > 0
|
|
17279
|
+
}));
|
|
17280
|
+
}
|
|
17281
|
+
const rows = await qb.unsafe(`SELECT column_name, data_type, is_nullable, column_key
|
|
17282
|
+
FROM information_schema.columns
|
|
17283
|
+
WHERE table_name = ? AND table_schema = DATABASE()
|
|
17284
|
+
ORDER BY ordinal_position`, [table]);
|
|
17285
|
+
return rows.map((r) => ({
|
|
17286
|
+
name: r.column_name ?? r.COLUMN_NAME,
|
|
17287
|
+
sqlType: String(r.data_type ?? r.DATA_TYPE ?? ""),
|
|
17288
|
+
nullable: String(r.is_nullable ?? r.IS_NULLABLE).toUpperCase() === "YES",
|
|
17289
|
+
isPrimaryKey: String(r.column_key ?? r.COLUMN_KEY).toUpperCase() === "PRI"
|
|
17290
|
+
}));
|
|
17291
|
+
}
|
|
17292
|
+
function generateModelSource(table, columns) {
|
|
17293
|
+
const modelName = modelNameForTable(table);
|
|
17294
|
+
const pk = columns.find((c) => c.isPrimaryKey)?.name ?? "id";
|
|
17295
|
+
const attrLines = columns.map((c) => {
|
|
17296
|
+
const parts = [`type: '${sqlTypeToAttr(c.sqlType)}'`];
|
|
17297
|
+
if (!c.nullable && !c.isPrimaryKey)
|
|
17298
|
+
parts.push("required: true");
|
|
17299
|
+
return ` ${c.name}: { ${parts.join(", ")} },`;
|
|
17300
|
+
}).join(`
|
|
17301
|
+
`);
|
|
17302
|
+
return `export const ${modelName} = defineModel({
|
|
17303
|
+
name: '${modelName}',
|
|
17304
|
+
table: '${table}',
|
|
17305
|
+
primaryKey: '${pk}',
|
|
17306
|
+
attributes: {
|
|
17307
|
+
${attrLines}
|
|
17308
|
+
},
|
|
17309
|
+
})
|
|
17310
|
+
`;
|
|
17311
|
+
}
|
|
17312
|
+
async function introspectDatabase(opts = {}) {
|
|
17313
|
+
const dialect = config5.dialect || "postgres";
|
|
17314
|
+
const qb = createQueryBuilder();
|
|
17315
|
+
const tables = opts.tables?.length ? opts.tables : await listTables(qb, dialect);
|
|
17316
|
+
const out = [];
|
|
17317
|
+
for (const table of tables) {
|
|
17318
|
+
const columns = await readColumns(qb, dialect, table);
|
|
17319
|
+
if (!columns.length)
|
|
17320
|
+
continue;
|
|
17321
|
+
out.push({
|
|
17322
|
+
table,
|
|
17323
|
+
modelName: modelNameForTable(table),
|
|
17324
|
+
primaryKey: columns.find((c) => c.isPrimaryKey)?.name ?? "id",
|
|
17325
|
+
columns,
|
|
17326
|
+
source: generateModelSource(table, columns)
|
|
17327
|
+
});
|
|
17328
|
+
}
|
|
17329
|
+
return out;
|
|
17330
|
+
}
|
|
17331
|
+
var init_introspect_db = __esm(() => {
|
|
17332
|
+
init_config();
|
|
17333
|
+
init_src2();
|
|
17334
|
+
});
|
|
17335
|
+
|
|
16868
17336
|
// src/actions/make-model.ts
|
|
16869
17337
|
import { existsSync as existsSync14, mkdirSync as mkdirSync5, writeFileSync as writeFileSync10 } from "fs";
|
|
16870
17338
|
import { dirname as dirname5, join as join7 } from "path";
|
|
@@ -17807,9 +18275,51 @@ var init_migrate_generate = __esm(() => {
|
|
|
17807
18275
|
});
|
|
17808
18276
|
|
|
17809
18277
|
// src/actions/migrate-rollback.ts
|
|
17810
|
-
import { existsSync as existsSync17, unlinkSync as unlinkSync2 } from "fs";
|
|
18278
|
+
import { existsSync as existsSync17, readFileSync as readFileSync4, unlinkSync as unlinkSync2 } from "fs";
|
|
17811
18279
|
import { dirname as dirname7, join as join10 } from "path";
|
|
17812
18280
|
import process26 from "process";
|
|
18281
|
+
function splitSqlStatements2(sql) {
|
|
18282
|
+
const out = [];
|
|
18283
|
+
let buf = "";
|
|
18284
|
+
let inString = false;
|
|
18285
|
+
const lines = sql.split(`
|
|
18286
|
+
`).filter((l) => !/^\s*--/.test(l));
|
|
18287
|
+
const text = lines.join(`
|
|
18288
|
+
`);
|
|
18289
|
+
for (let i = 0;i < text.length; i++) {
|
|
18290
|
+
const ch = text[i];
|
|
18291
|
+
if (ch === "'")
|
|
18292
|
+
inString = !inString;
|
|
18293
|
+
if (ch === ";" && !inString) {
|
|
18294
|
+
if (buf.trim())
|
|
18295
|
+
out.push(buf.trim());
|
|
18296
|
+
buf = "";
|
|
18297
|
+
} else {
|
|
18298
|
+
buf += ch;
|
|
18299
|
+
}
|
|
18300
|
+
}
|
|
18301
|
+
if (buf.trim())
|
|
18302
|
+
out.push(buf.trim());
|
|
18303
|
+
return out;
|
|
18304
|
+
}
|
|
18305
|
+
function deriveDownStatements(forwardSql, dialect = config5.dialect) {
|
|
18306
|
+
const q = (id) => dialect === "mysql" ? `\`${id}\`` : `"${id}"`;
|
|
18307
|
+
const down = [];
|
|
18308
|
+
const skipped = [];
|
|
18309
|
+
for (const stmt of splitSqlStatements2(forwardSql)) {
|
|
18310
|
+
let m;
|
|
18311
|
+
if (m = /^CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["`']?(\w+)["`']?/i.exec(stmt)) {
|
|
18312
|
+
down.push(`DROP TABLE IF EXISTS ${q(m[1])}`);
|
|
18313
|
+
} else if (m = /^ALTER\s+TABLE\s+["`']?(\w+)["`']?\s+ADD\s+(?:COLUMN\s+)?["`']?(\w+)["`']?/i.exec(stmt)) {
|
|
18314
|
+
down.push(`ALTER TABLE ${q(m[1])} DROP COLUMN ${q(m[2])}`);
|
|
18315
|
+
} else if (m = /^CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?["`']?(\w+)["`']?(?:\s+ON\s+["`']?(\w+)["`']?)?/i.exec(stmt)) {
|
|
18316
|
+
down.push(dialect === "mysql" && m[2] ? `DROP INDEX ${q(m[1])} ON ${q(m[2])}` : `DROP INDEX IF EXISTS ${q(m[1])}`);
|
|
18317
|
+
} else {
|
|
18318
|
+
skipped.push(stmt);
|
|
18319
|
+
}
|
|
18320
|
+
}
|
|
18321
|
+
return { down: down.reverse(), skipped };
|
|
18322
|
+
}
|
|
17813
18323
|
function findWorkspaceRoot2(startPath) {
|
|
17814
18324
|
let currentPath = startPath;
|
|
17815
18325
|
while (currentPath !== dirname7(currentPath)) {
|
|
@@ -17828,6 +18338,7 @@ function getSqlDirectory2(workspaceRoot) {
|
|
|
17828
18338
|
}
|
|
17829
18339
|
async function migrateRollback(options = {}) {
|
|
17830
18340
|
const steps = options.steps || 1;
|
|
18341
|
+
const reverseSchema = options.reverseSchema !== false;
|
|
17831
18342
|
console.log("-- Rolling back migrations");
|
|
17832
18343
|
console.log(`-- Steps: ${steps}`);
|
|
17833
18344
|
console.log();
|
|
@@ -17857,13 +18368,25 @@ async function migrateRollback(options = {}) {
|
|
|
17857
18368
|
console.log(` - ${migration.migration}`);
|
|
17858
18369
|
}
|
|
17859
18370
|
console.log();
|
|
18371
|
+
const sqlDir = getSqlDirectory2();
|
|
18372
|
+
let reversedAny = false;
|
|
17860
18373
|
for (const migration of migrationsToRollback) {
|
|
17861
18374
|
try {
|
|
18375
|
+
const filePath = join10(sqlDir, migration.migration);
|
|
18376
|
+
if (reverseSchema && existsSync17(filePath)) {
|
|
18377
|
+
const forwardSql = readFileSync4(filePath, "utf8");
|
|
18378
|
+
const { down, skipped } = deriveDownStatements(forwardSql);
|
|
18379
|
+
for (const stmt of down) {
|
|
18380
|
+
await qb.unsafe(stmt);
|
|
18381
|
+
console.log(`-- \u21A9 ${stmt}`);
|
|
18382
|
+
reversedAny = true;
|
|
18383
|
+
}
|
|
18384
|
+
if (skipped.length > 0)
|
|
18385
|
+
console.log(`-- \u26A0\uFE0F ${skipped.length} statement(s) in ${migration.migration} could not be auto-reversed (data/complex DDL) \u2014 reverse manually.`);
|
|
18386
|
+
}
|
|
17862
18387
|
const deleteSql = `DELETE FROM migrations WHERE migration = $1`;
|
|
17863
18388
|
await qb.unsafe(deleteSql, [migration.migration]);
|
|
17864
18389
|
console.log(`-- \u2713 Removed migration record: ${migration.migration}`);
|
|
17865
|
-
const sqlDir = getSqlDirectory2();
|
|
17866
|
-
const filePath = join10(sqlDir, migration.migration);
|
|
17867
18390
|
if (existsSync17(filePath)) {
|
|
17868
18391
|
unlinkSync2(filePath);
|
|
17869
18392
|
console.log(`-- \uD83D\uDDD1\uFE0F Deleted migration file: ${migration.migration}`);
|
|
@@ -17874,17 +18397,18 @@ async function migrateRollback(options = {}) {
|
|
|
17874
18397
|
}
|
|
17875
18398
|
}
|
|
17876
18399
|
console.log();
|
|
17877
|
-
|
|
17878
|
-
|
|
17879
|
-
|
|
17880
|
-
|
|
17881
|
-
|
|
18400
|
+
if (reverseSchema) {
|
|
18401
|
+
console.log(reversedAny ? "-- \u2713 Reverse DDL executed for the rolled-back migration(s)." : "-- \u26A0\uFE0F No reversible DDL found; only migration records were removed.");
|
|
18402
|
+
} else {
|
|
18403
|
+
console.log("-- \u26A0\uFE0F reverseSchema disabled: only migration records were removed.");
|
|
18404
|
+
}
|
|
17882
18405
|
} catch (err) {
|
|
17883
18406
|
console.error("-- Rollback failed:", err);
|
|
17884
18407
|
throw err;
|
|
17885
18408
|
}
|
|
17886
18409
|
}
|
|
17887
18410
|
var init_migrate_rollback = __esm(() => {
|
|
18411
|
+
init_config();
|
|
17888
18412
|
init_src2();
|
|
17889
18413
|
});
|
|
17890
18414
|
|
|
@@ -18119,7 +18643,7 @@ var init_ping = __esm(() => {
|
|
|
18119
18643
|
});
|
|
18120
18644
|
|
|
18121
18645
|
// src/actions/query-explain-all.ts
|
|
18122
|
-
import { readdirSync as readdirSync10, readFileSync as
|
|
18646
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
|
|
18123
18647
|
import { extname as extname3, join as join13 } from "path";
|
|
18124
18648
|
async function queryExplainAll(path, options = {}) {
|
|
18125
18649
|
const results = [];
|
|
@@ -18145,7 +18669,7 @@ async function queryExplainAll(path, options = {}) {
|
|
|
18145
18669
|
}
|
|
18146
18670
|
for (const file2 of files) {
|
|
18147
18671
|
try {
|
|
18148
|
-
const query =
|
|
18672
|
+
const query = readFileSync5(file2, "utf8").trim();
|
|
18149
18673
|
if (!query) {
|
|
18150
18674
|
if (options.verbose) {
|
|
18151
18675
|
console.log(`\u2298 ${file2}: empty file`);
|
|
@@ -18182,7 +18706,7 @@ async function queryExplainAll(path, options = {}) {
|
|
|
18182
18706
|
} catch (error) {
|
|
18183
18707
|
results.push({
|
|
18184
18708
|
file: file2,
|
|
18185
|
-
query:
|
|
18709
|
+
query: readFileSync5(file2, "utf8").trim(),
|
|
18186
18710
|
plan: [],
|
|
18187
18711
|
error: error.message
|
|
18188
18712
|
});
|
|
@@ -18796,6 +19320,7 @@ var init_actions = __esm(() => {
|
|
|
18796
19320
|
init_file();
|
|
18797
19321
|
init_inspect();
|
|
18798
19322
|
init_introspect();
|
|
19323
|
+
init_introspect_db();
|
|
18799
19324
|
init_make_model();
|
|
18800
19325
|
init_migrate();
|
|
18801
19326
|
init_migrate_generate();
|
|
@@ -23415,6 +23940,14 @@ function getDatabase() {
|
|
|
23415
23940
|
return exec.sqliteDb;
|
|
23416
23941
|
throw new Error(`[bun-query-builder] getDatabase() is only available for the sqlite dialect; ` + `the configured dialect is '${exec.dialect}'. Use the async model API instead.`);
|
|
23417
23942
|
}
|
|
23943
|
+
function softDeletesEnabled(definition) {
|
|
23944
|
+
const t2 = definition.traits;
|
|
23945
|
+
return Boolean(t2?.useSoftDeletes || t2?.softDeletable);
|
|
23946
|
+
}
|
|
23947
|
+
function timestampsEnabled(definition) {
|
|
23948
|
+
const t2 = definition.traits;
|
|
23949
|
+
return Boolean(t2?.useTimestamps || t2?.timestampable);
|
|
23950
|
+
}
|
|
23418
23951
|
function collectBelongsToManyKeys(definition) {
|
|
23419
23952
|
const keys = new Set;
|
|
23420
23953
|
const rel = definition.belongsToMany;
|
|
@@ -23584,7 +24117,7 @@ class ModelInstance {
|
|
|
23584
24117
|
if (changeKeys.length > 0) {
|
|
23585
24118
|
const sets = changeKeys.map((k2) => `${k2} = ?`).join(", ");
|
|
23586
24119
|
const values = [...Object.values(changes), this._attributes[pk]];
|
|
23587
|
-
if (this._definition
|
|
24120
|
+
if (timestampsEnabled(this._definition)) {
|
|
23588
24121
|
const now = formatNow();
|
|
23589
24122
|
await exec.run(`UPDATE ${this._definition.table} SET ${sets}, updated_at = ? WHERE ${pk} = ?`, [...Object.values(changes), now, this._attributes[pk]]);
|
|
23590
24123
|
} else {
|
|
@@ -23596,11 +24129,13 @@ class ModelInstance {
|
|
|
23596
24129
|
const attrs = this._definition.attributes;
|
|
23597
24130
|
const data = {};
|
|
23598
24131
|
for (const [key, attr] of Object.entries(attrs)) {
|
|
23599
|
-
if (attr.
|
|
24132
|
+
if (attr.guarded)
|
|
24133
|
+
continue;
|
|
24134
|
+
if (this._attributes[key] !== undefined) {
|
|
23600
24135
|
data[key] = this._attributes[key];
|
|
23601
24136
|
}
|
|
23602
24137
|
}
|
|
23603
|
-
if (this._definition
|
|
24138
|
+
if (timestampsEnabled(this._definition)) {
|
|
23604
24139
|
const now = formatNow();
|
|
23605
24140
|
data.created_at = now;
|
|
23606
24141
|
data.updated_at = now;
|
|
@@ -23646,14 +24181,31 @@ class ModelInstance {
|
|
|
23646
24181
|
if (!pkValue)
|
|
23647
24182
|
throw new Error("Cannot delete a model without a primary key");
|
|
23648
24183
|
await hooks?.beforeDelete?.(this);
|
|
23649
|
-
if (this._definition
|
|
23650
|
-
|
|
24184
|
+
if (softDeletesEnabled(this._definition)) {
|
|
24185
|
+
const now = formatNow();
|
|
24186
|
+
await exec.run(`UPDATE ${this._definition.table} SET deleted_at = ? WHERE ${pk} = ?`, [now, pkValue]);
|
|
24187
|
+
this._attributes[SOFT_DELETE_COLUMN] = now;
|
|
23651
24188
|
} else {
|
|
23652
24189
|
await exec.run(`DELETE FROM ${this._definition.table} WHERE ${pk} = ?`, [pkValue]);
|
|
23653
24190
|
}
|
|
23654
24191
|
await hooks?.afterDelete?.(this);
|
|
23655
24192
|
return true;
|
|
23656
24193
|
}
|
|
24194
|
+
async restore() {
|
|
24195
|
+
if (!softDeletesEnabled(this._definition))
|
|
24196
|
+
throw new Error(`[orm] restore() requires soft deletes on '${this._definition.name}'`);
|
|
24197
|
+
const pk = this._definition.primaryKey || "id";
|
|
24198
|
+
const pkValue = this._attributes[pk];
|
|
24199
|
+
if (!pkValue)
|
|
24200
|
+
throw new Error("Cannot restore a model without a primary key");
|
|
24201
|
+
await getExecutor().run(`UPDATE ${this._definition.table} SET ${SOFT_DELETE_COLUMN} = ? WHERE ${pk} = ?`, [null, pkValue]);
|
|
24202
|
+
this._attributes[SOFT_DELETE_COLUMN] = null;
|
|
24203
|
+
this._original = null;
|
|
24204
|
+
return this;
|
|
24205
|
+
}
|
|
24206
|
+
trashed() {
|
|
24207
|
+
return this._attributes[SOFT_DELETE_COLUMN] != null;
|
|
24208
|
+
}
|
|
23657
24209
|
async refresh() {
|
|
23658
24210
|
const exec = getExecutor();
|
|
23659
24211
|
const pk = this._definition.primaryKey || "id";
|
|
@@ -23958,9 +24510,23 @@ class BelongsToManyRelationBuilder {
|
|
|
23958
24510
|
this._offset = n2;
|
|
23959
24511
|
return this;
|
|
23960
24512
|
}
|
|
24513
|
+
relatedSelectColumns() {
|
|
24514
|
+
const cols = new Set([this.relatedPk, ...Object.keys(this._relatedDef.attributes ?? {})]);
|
|
24515
|
+
const t2 = this._relatedDef.traits;
|
|
24516
|
+
if (t2?.useTimestamps || t2?.timestampable) {
|
|
24517
|
+
cols.add("created_at");
|
|
24518
|
+
cols.add("updated_at");
|
|
24519
|
+
}
|
|
24520
|
+
if (t2?.useSoftDeletes || t2?.softDeletable)
|
|
24521
|
+
cols.add("deleted_at");
|
|
24522
|
+
if (t2?.useUuid)
|
|
24523
|
+
cols.add("uuid");
|
|
24524
|
+
return [...cols];
|
|
24525
|
+
}
|
|
23961
24526
|
buildSelect() {
|
|
23962
24527
|
const params = [];
|
|
23963
|
-
|
|
24528
|
+
const relatedSelect = this.relatedSelectColumns().map((c2) => `${this.relatedTable}.${c2} AS ${BTM_RELATED_ALIAS}${c2}`).join(", ");
|
|
24529
|
+
let sql2 = `SELECT ${relatedSelect}, ${this.pivotTable}.* FROM ${this.relatedTable}`;
|
|
23964
24530
|
sql2 += ` INNER JOIN ${this.pivotTable} ON ${this.pivotTable}.${this.fkRelated} = ${this.relatedTable}.${this.relatedPk}`;
|
|
23965
24531
|
sql2 += ` WHERE ${this.pivotTable}.${this.fkParent} = ?`;
|
|
23966
24532
|
params.push(this.parentId);
|
|
@@ -23981,25 +24547,17 @@ class BelongsToManyRelationBuilder {
|
|
|
23981
24547
|
return { sql: sql2, params };
|
|
23982
24548
|
}
|
|
23983
24549
|
hydrateRows(rows) {
|
|
23984
|
-
const relatedAttrs = new Set(Object.keys(this._relatedDef.attributes ?? {}));
|
|
23985
|
-
relatedAttrs.add(this.relatedPk);
|
|
23986
|
-
relatedAttrs.add("created_at");
|
|
23987
|
-
relatedAttrs.add("updated_at");
|
|
23988
|
-
relatedAttrs.add("deleted_at");
|
|
23989
24550
|
const fkParent = this.fkParent;
|
|
23990
24551
|
const fkRelated = this.fkRelated;
|
|
23991
24552
|
return rows.map((raw) => {
|
|
23992
24553
|
const relatedRow = {};
|
|
23993
24554
|
const pivotExtras = {};
|
|
23994
24555
|
for (const [k2, v2] of Object.entries(raw)) {
|
|
23995
|
-
if (k2
|
|
23996
|
-
|
|
23997
|
-
if (
|
|
23998
|
-
relatedRow[k2] = v2;
|
|
23999
|
-
else
|
|
24556
|
+
if (k2.startsWith(BTM_RELATED_ALIAS))
|
|
24557
|
+
relatedRow[k2.slice(BTM_RELATED_ALIAS.length)] = v2;
|
|
24558
|
+
else if (k2 !== fkParent && k2 !== fkRelated)
|
|
24000
24559
|
pivotExtras[k2] = v2;
|
|
24001
24560
|
}
|
|
24002
|
-
relatedRow[this.relatedPk] = raw[this.relatedPk];
|
|
24003
24561
|
const inst = new ModelInstance(this._relatedDef, relatedRow);
|
|
24004
24562
|
inst.pivot = pivotExtras;
|
|
24005
24563
|
return inst;
|
|
@@ -24146,9 +24704,18 @@ class ModelQueryBuilder {
|
|
|
24146
24704
|
_offset;
|
|
24147
24705
|
_select = ["*"];
|
|
24148
24706
|
_withRelations = [];
|
|
24707
|
+
_trashed = "exclude";
|
|
24149
24708
|
constructor(definition) {
|
|
24150
24709
|
this._definition = definition;
|
|
24151
24710
|
}
|
|
24711
|
+
withTrashed() {
|
|
24712
|
+
this._trashed = "include";
|
|
24713
|
+
return this;
|
|
24714
|
+
}
|
|
24715
|
+
onlyTrashed() {
|
|
24716
|
+
this._trashed = "only";
|
|
24717
|
+
return this;
|
|
24718
|
+
}
|
|
24152
24719
|
where(column, operatorOrValue, value) {
|
|
24153
24720
|
if (value === undefined) {
|
|
24154
24721
|
this._wheres.push({ column, operator: "=", value: operatorOrValue, boolean: "and" });
|
|
@@ -24326,11 +24893,24 @@ class ModelQueryBuilder {
|
|
|
24326
24893
|
}
|
|
24327
24894
|
return clauses.join(" ");
|
|
24328
24895
|
}
|
|
24896
|
+
softDeleteClause() {
|
|
24897
|
+
if (this._trashed === "include" || !softDeletesEnabled(this._definition))
|
|
24898
|
+
return "";
|
|
24899
|
+
return this._trashed === "only" ? `${SOFT_DELETE_COLUMN} IS NOT NULL` : `${SOFT_DELETE_COLUMN} IS NULL`;
|
|
24900
|
+
}
|
|
24901
|
+
composeWhere(params) {
|
|
24902
|
+
const userClause = this._wheres.length > 0 ? this.buildWhereClauses(params) : "";
|
|
24903
|
+
const sd = this.softDeleteClause();
|
|
24904
|
+
if (userClause && sd)
|
|
24905
|
+
return `(${userClause}) AND ${sd}`;
|
|
24906
|
+
return userClause || sd;
|
|
24907
|
+
}
|
|
24329
24908
|
buildQuery() {
|
|
24330
24909
|
const params = [];
|
|
24331
24910
|
let sql2 = `SELECT ${this._select.join(", ")} FROM ${this._definition.table}`;
|
|
24332
|
-
|
|
24333
|
-
|
|
24911
|
+
const whereBody = this.composeWhere(params);
|
|
24912
|
+
if (whereBody) {
|
|
24913
|
+
sql2 += ` WHERE ${whereBody}`;
|
|
24334
24914
|
}
|
|
24335
24915
|
if (this._orderBy.length > 0) {
|
|
24336
24916
|
sql2 += ` ORDER BY ${this._orderBy.map((o2) => `${o2.column} ${o2.direction.toUpperCase()}`).join(", ")}`;
|
|
@@ -24354,6 +24934,8 @@ class ModelQueryBuilder {
|
|
|
24354
24934
|
let rel = relationCache.get(cacheKey);
|
|
24355
24935
|
if (rel === undefined) {
|
|
24356
24936
|
rel = resolveRelation(this._definition, relationName);
|
|
24937
|
+
if (relationCache.size >= RELATION_CACHE_MAX)
|
|
24938
|
+
relationCache.clear();
|
|
24357
24939
|
relationCache.set(cacheKey, rel);
|
|
24358
24940
|
}
|
|
24359
24941
|
if (!rel)
|
|
@@ -24390,8 +24972,13 @@ class ModelQueryBuilder {
|
|
|
24390
24972
|
}
|
|
24391
24973
|
}
|
|
24392
24974
|
if (rel.type === "belongsTo") {
|
|
24393
|
-
const
|
|
24394
|
-
const
|
|
24975
|
+
const fkSet = new Set;
|
|
24976
|
+
for (const i2 of instances) {
|
|
24977
|
+
const v2 = i2._attributes[rel.foreignKey];
|
|
24978
|
+
if (v2 != null)
|
|
24979
|
+
fkSet.add(v2);
|
|
24980
|
+
}
|
|
24981
|
+
const uniqueFkValues = [...fkSet];
|
|
24395
24982
|
if (uniqueFkValues.length === 0)
|
|
24396
24983
|
continue;
|
|
24397
24984
|
const placeholders = uniqueFkValues.map(() => "?").join(", ");
|
|
@@ -24421,7 +25008,13 @@ class ModelQueryBuilder {
|
|
|
24421
25008
|
instance.setRelation(relationName, []);
|
|
24422
25009
|
continue;
|
|
24423
25010
|
}
|
|
24424
|
-
const
|
|
25011
|
+
const relatedIdSet = new Set;
|
|
25012
|
+
for (const p2 of pivotRows) {
|
|
25013
|
+
const v2 = p2[rel.pivotFkRelated];
|
|
25014
|
+
if (v2 != null)
|
|
25015
|
+
relatedIdSet.add(v2);
|
|
25016
|
+
}
|
|
25017
|
+
const relatedIds = [...relatedIdSet];
|
|
24425
25018
|
const relatedModelDef = getModelFromRegistry(rel.relatedModelName);
|
|
24426
25019
|
const relDef = relatedModelDef?.getDefinition?.() || relatedModelDef?.definition || this._definition;
|
|
24427
25020
|
const relatedPk = relDef?.primaryKey || "id";
|
|
@@ -24492,8 +25085,9 @@ class ModelQueryBuilder {
|
|
|
24492
25085
|
const exec = getExecutor();
|
|
24493
25086
|
const params = [];
|
|
24494
25087
|
let sql2 = `SELECT COUNT(*) as count FROM ${this._definition.table}`;
|
|
24495
|
-
|
|
24496
|
-
|
|
25088
|
+
const whereBody = this.composeWhere(params);
|
|
25089
|
+
if (whereBody) {
|
|
25090
|
+
sql2 += ` WHERE ${whereBody}`;
|
|
24497
25091
|
}
|
|
24498
25092
|
const row = await exec.get(sql2, params);
|
|
24499
25093
|
return Number(row?.count ?? 0);
|
|
@@ -24518,7 +25112,7 @@ class ModelQueryBuilder {
|
|
|
24518
25112
|
const exec = getExecutor();
|
|
24519
25113
|
const params = [amount];
|
|
24520
25114
|
let sql2 = `UPDATE ${this._definition.table} SET ${column} = ${column} + ?`;
|
|
24521
|
-
if (this._definition
|
|
25115
|
+
if (timestampsEnabled(this._definition)) {
|
|
24522
25116
|
sql2 += `, updated_at = ?`;
|
|
24523
25117
|
params.push(formatNow());
|
|
24524
25118
|
}
|
|
@@ -24539,6 +25133,7 @@ class ModelQueryBuilder {
|
|
|
24539
25133
|
builder._orderBy = [...this._orderBy];
|
|
24540
25134
|
builder._select = [...this._select];
|
|
24541
25135
|
builder._withRelations = [...this._withRelations];
|
|
25136
|
+
builder._trashed = this._trashed;
|
|
24542
25137
|
builder._limit = size;
|
|
24543
25138
|
builder._offset = page * size;
|
|
24544
25139
|
const results = await builder.get();
|
|
@@ -24575,8 +25170,9 @@ class ModelQueryBuilder {
|
|
|
24575
25170
|
const exec = getExecutor();
|
|
24576
25171
|
const params = [];
|
|
24577
25172
|
let sql2 = `SELECT ${column} FROM ${this._definition.table}`;
|
|
24578
|
-
|
|
24579
|
-
|
|
25173
|
+
const whereBody = this.composeWhere(params);
|
|
25174
|
+
if (whereBody) {
|
|
25175
|
+
sql2 += ` WHERE ${whereBody}`;
|
|
24580
25176
|
}
|
|
24581
25177
|
if (this._orderBy.length > 0) {
|
|
24582
25178
|
sql2 += ` ORDER BY ${this._orderBy.map((o2) => `${o2.column} ${o2.direction.toUpperCase()}`).join(", ")}`;
|
|
@@ -24593,8 +25189,9 @@ class ModelQueryBuilder {
|
|
|
24593
25189
|
const exec = getExecutor();
|
|
24594
25190
|
const params = [];
|
|
24595
25191
|
let sql2 = `SELECT ${fn}(${column}) as v FROM ${this._definition.table}`;
|
|
24596
|
-
|
|
24597
|
-
|
|
25192
|
+
const whereBody = this.composeWhere(params);
|
|
25193
|
+
if (whereBody) {
|
|
25194
|
+
sql2 += ` WHERE ${whereBody}`;
|
|
24598
25195
|
}
|
|
24599
25196
|
const row = await exec.get(sql2, params);
|
|
24600
25197
|
return row?.v == null ? null : Number(row.v);
|
|
@@ -24625,10 +25222,10 @@ class ModelQueryBuilder {
|
|
|
24625
25222
|
const entries = Object.entries(data);
|
|
24626
25223
|
const sets = entries.map(([k2]) => `${k2} = ?`).join(", ");
|
|
24627
25224
|
const params = entries.map(([, v2]) => v2);
|
|
24628
|
-
if (this._definition
|
|
25225
|
+
if (timestampsEnabled(this._definition)) {
|
|
24629
25226
|
params.push(formatNow());
|
|
24630
25227
|
}
|
|
24631
|
-
let sql2 = `UPDATE ${this._definition.table} SET ${sets}${this._definition
|
|
25228
|
+
let sql2 = `UPDATE ${this._definition.table} SET ${sets}${timestampsEnabled(this._definition) ? ", updated_at = ?" : ""}`;
|
|
24632
25229
|
if (this._wheres.length > 0) {
|
|
24633
25230
|
sql2 += ` WHERE ${this.buildWhereClauses(params)}`;
|
|
24634
25231
|
}
|
|
@@ -24674,10 +25271,13 @@ function createModel(definition) {
|
|
|
24674
25271
|
limit: (count) => new ModelQueryBuilder(definition).limit(count),
|
|
24675
25272
|
take: (count) => new ModelQueryBuilder(definition).take(count),
|
|
24676
25273
|
skip: (count) => new ModelQueryBuilder(definition).skip(count),
|
|
25274
|
+
withTrashed: () => new ModelQueryBuilder(definition).withTrashed(),
|
|
25275
|
+
onlyTrashed: () => new ModelQueryBuilder(definition).onlyTrashed(),
|
|
24677
25276
|
async find(id) {
|
|
24678
25277
|
const exec = getExecutor();
|
|
24679
25278
|
const pk = definition.primaryKey || "id";
|
|
24680
|
-
const
|
|
25279
|
+
const sd = softDeletesEnabled(definition) ? ` AND ${SOFT_DELETE_COLUMN} IS NULL` : "";
|
|
25280
|
+
const row = await exec.get(`SELECT * FROM ${definition.table} WHERE ${pk} = ?${sd}`, [id]);
|
|
24681
25281
|
return row ? new ModelInstance(definition, row) : undefined;
|
|
24682
25282
|
},
|
|
24683
25283
|
async findOrFail(id) {
|
|
@@ -24689,7 +25289,8 @@ function createModel(definition) {
|
|
|
24689
25289
|
async findMany(ids) {
|
|
24690
25290
|
const exec = getExecutor();
|
|
24691
25291
|
const pk = definition.primaryKey || "id";
|
|
24692
|
-
const
|
|
25292
|
+
const sd = softDeletesEnabled(definition) ? ` AND ${SOFT_DELETE_COLUMN} IS NULL` : "";
|
|
25293
|
+
const rows = await exec.all(`SELECT * FROM ${definition.table} WHERE ${pk} IN (${ids.map(() => "?").join(", ")})${sd}`, ids);
|
|
24693
25294
|
return rows.map((row) => new ModelInstance(definition, row));
|
|
24694
25295
|
},
|
|
24695
25296
|
all: () => new ModelQueryBuilder(definition).get(),
|
|
@@ -24804,10 +25405,10 @@ async function createTableFromModel(definition) {
|
|
|
24804
25405
|
}
|
|
24805
25406
|
columns.push(colDef);
|
|
24806
25407
|
}
|
|
24807
|
-
if (definition
|
|
25408
|
+
if (timestampsEnabled(definition)) {
|
|
24808
25409
|
columns.push("created_at TEXT", "updated_at TEXT");
|
|
24809
25410
|
}
|
|
24810
|
-
if (definition
|
|
25411
|
+
if (softDeletesEnabled(definition)) {
|
|
24811
25412
|
columns.push("deleted_at TEXT");
|
|
24812
25413
|
}
|
|
24813
25414
|
await exec.run(`CREATE TABLE IF NOT EXISTS ${definition.table} (${columns.join(", ")})`, []);
|
|
@@ -24852,7 +25453,7 @@ async function seedModel(definition, count, faker) {
|
|
|
24852
25453
|
if (attr.factory)
|
|
24853
25454
|
data[name] = attr.factory(faker);
|
|
24854
25455
|
}
|
|
24855
|
-
if (definition
|
|
25456
|
+
if (timestampsEnabled(definition)) {
|
|
24856
25457
|
const now = formatNow();
|
|
24857
25458
|
data.created_at = now;
|
|
24858
25459
|
data.updated_at = now;
|
|
@@ -24863,7 +25464,7 @@ async function seedModel(definition, count, faker) {
|
|
|
24863
25464
|
await exec.run(`INSERT INTO ${definition.table} (${columns.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`, Object.values(data));
|
|
24864
25465
|
}
|
|
24865
25466
|
}
|
|
24866
|
-
var SAFE_SQL_IDENTIFIER, _getModel = null, globalDb = null, _executor = null, _executorForDb = null, _executorDialect = null, _executorDatabase = null, snakeCaseCache, tableNameCache, relationCache;
|
|
25467
|
+
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;
|
|
24867
25468
|
var init_orm = __esm(() => {
|
|
24868
25469
|
init_config();
|
|
24869
25470
|
init_db();
|
|
@@ -26348,6 +26949,23 @@ async function loadModels(options) {
|
|
|
26348
26949
|
}
|
|
26349
26950
|
var init_loader = () => {};
|
|
26350
26951
|
|
|
26952
|
+
// src/relation-utils.ts
|
|
26953
|
+
function normalizeRelationEntry(entry) {
|
|
26954
|
+
if (typeof entry === "string")
|
|
26955
|
+
return { model: entry };
|
|
26956
|
+
if (entry && typeof entry === "object" && typeof entry.model === "string") {
|
|
26957
|
+
const e2 = entry;
|
|
26958
|
+
return { model: e2.model, foreignKey: e2.foreignKey, onDelete: e2.onDelete };
|
|
26959
|
+
}
|
|
26960
|
+
return null;
|
|
26961
|
+
}
|
|
26962
|
+
function normalizeRelationList(rel) {
|
|
26963
|
+
if (!rel)
|
|
26964
|
+
return [];
|
|
26965
|
+
const entries = Array.isArray(rel) ? rel : typeof rel === "object" ? Object.values(rel) : [];
|
|
26966
|
+
return entries.map(normalizeRelationEntry).filter((x2) => x2 !== null);
|
|
26967
|
+
}
|
|
26968
|
+
|
|
26351
26969
|
// src/meta.ts
|
|
26352
26970
|
function buildSchemaMeta(models) {
|
|
26353
26971
|
const modelToTable = {};
|
|
@@ -26365,13 +26983,24 @@ function buildSchemaMeta(models) {
|
|
|
26365
26983
|
const toRecord = (v2) => {
|
|
26366
26984
|
if (!v2)
|
|
26367
26985
|
return {};
|
|
26986
|
+
const rec = {};
|
|
26368
26987
|
if (Array.isArray(v2)) {
|
|
26369
|
-
const
|
|
26370
|
-
|
|
26371
|
-
|
|
26988
|
+
for (const item of v2) {
|
|
26989
|
+
const n2 = normalizeRelationEntry(item);
|
|
26990
|
+
if (n2)
|
|
26991
|
+
rec[n2.model] = n2.model;
|
|
26992
|
+
}
|
|
26372
26993
|
return rec;
|
|
26373
26994
|
}
|
|
26374
|
-
|
|
26995
|
+
if (typeof v2 === "object") {
|
|
26996
|
+
for (const [key, val] of Object.entries(v2)) {
|
|
26997
|
+
const n2 = normalizeRelationEntry(val);
|
|
26998
|
+
if (n2)
|
|
26999
|
+
rec[key] = n2.model;
|
|
27000
|
+
}
|
|
27001
|
+
return rec;
|
|
27002
|
+
}
|
|
27003
|
+
return {};
|
|
26375
27004
|
};
|
|
26376
27005
|
const toBelongsToManyRecord = (v2) => {
|
|
26377
27006
|
if (!v2)
|
|
@@ -26418,6 +27047,7 @@ function buildSchemaMeta(models) {
|
|
|
26418
27047
|
}
|
|
26419
27048
|
return { modelToTable, tableToModel, primaryKeys, relations, scopes: scopesByTable, models };
|
|
26420
27049
|
}
|
|
27050
|
+
var init_meta = () => {};
|
|
26421
27051
|
|
|
26422
27052
|
// src/migrations.ts
|
|
26423
27053
|
import { existsSync as existsSync21, mkdirSync as mkdirSync9, writeFileSync as writeFileSync14 } from "fs";
|
|
@@ -26580,15 +27210,6 @@ function detectTypeFromValidationRule(rule) {
|
|
|
26580
27210
|
}
|
|
26581
27211
|
return;
|
|
26582
27212
|
}
|
|
26583
|
-
function normalizeBelongsTo(belongsTo) {
|
|
26584
|
-
if (!belongsTo)
|
|
26585
|
-
return [];
|
|
26586
|
-
if (Array.isArray(belongsTo))
|
|
26587
|
-
return belongsTo;
|
|
26588
|
-
if (typeof belongsTo === "object")
|
|
26589
|
-
return Object.values(belongsTo);
|
|
26590
|
-
return [];
|
|
26591
|
-
}
|
|
26592
27213
|
function buildMigrationPlan2(models, options) {
|
|
26593
27214
|
const meta = buildSchemaMeta(models);
|
|
26594
27215
|
const tables = [];
|
|
@@ -26677,12 +27298,12 @@ function buildMigrationPlan2(models, options) {
|
|
|
26677
27298
|
}
|
|
26678
27299
|
columns.push(col);
|
|
26679
27300
|
}
|
|
26680
|
-
const
|
|
26681
|
-
for (const
|
|
26682
|
-
const fkColumnName = `${snakeCase(
|
|
27301
|
+
const belongsToRelations = normalizeRelationList(model.belongsTo);
|
|
27302
|
+
for (const rel of belongsToRelations) {
|
|
27303
|
+
const fkColumnName = rel.foreignKey ?? `${snakeCase(rel.model)}_id`;
|
|
26683
27304
|
if (columns.some((c2) => c2.name === fkColumnName))
|
|
26684
27305
|
continue;
|
|
26685
|
-
const refTable = meta.modelToTable[
|
|
27306
|
+
const refTable = meta.modelToTable[rel.model];
|
|
26686
27307
|
if (!refTable)
|
|
26687
27308
|
continue;
|
|
26688
27309
|
const refPk = meta.primaryKeys[refTable] ?? "id";
|
|
@@ -26693,7 +27314,7 @@ function buildMigrationPlan2(models, options) {
|
|
|
26693
27314
|
isUnique: false,
|
|
26694
27315
|
isNullable: true,
|
|
26695
27316
|
hasDefault: false,
|
|
26696
|
-
references: { table: refTable, column: refPk }
|
|
27317
|
+
references: { table: refTable, column: refPk, onDelete: rel.onDelete }
|
|
26697
27318
|
});
|
|
26698
27319
|
}
|
|
26699
27320
|
const traits = model.traits;
|
|
@@ -27010,6 +27631,13 @@ function columnsAreDifferent(col1, col2) {
|
|
|
27010
27631
|
}
|
|
27011
27632
|
return false;
|
|
27012
27633
|
}
|
|
27634
|
+
function referencesAreDifferent(r1, r2) {
|
|
27635
|
+
if (Boolean(r1) !== Boolean(r2))
|
|
27636
|
+
return true;
|
|
27637
|
+
if (!r1 || !r2)
|
|
27638
|
+
return false;
|
|
27639
|
+
return r1.table !== r2.table || r1.column !== r2.column || r1.onDelete !== r2.onDelete || r1.onUpdate !== r2.onUpdate;
|
|
27640
|
+
}
|
|
27013
27641
|
function mapIndexesByKey(indexes) {
|
|
27014
27642
|
const map = {};
|
|
27015
27643
|
for (const i2 of indexes) {
|
|
@@ -27153,6 +27781,13 @@ function generateDiffSql(previous, next) {
|
|
|
27153
27781
|
info2(`-- Detected column type change: ${curr.table}.${colName} (${prevCol.type} -> ${currCol.type})`);
|
|
27154
27782
|
hasChanges = true;
|
|
27155
27783
|
}
|
|
27784
|
+
if (referencesAreDifferent(prevCol.references, currCol.references) && currCol.references) {
|
|
27785
|
+
const addFkStatement = driver.addForeignKey(curr.table, currCol.name, currCol.references.table, currCol.references.column, currCol.references.onDelete, currCol.references.onUpdate);
|
|
27786
|
+
tableChanges.push(addFkStatement);
|
|
27787
|
+
chunks.push(addFkStatement);
|
|
27788
|
+
info2(`-- Detected foreign-key change: ${curr.table}.${colName} -> ${currCol.references.table}(${currCol.references.column})${currCol.references.onDelete ? ` ON DELETE ${currCol.references.onDelete}` : ""}`);
|
|
27789
|
+
hasChanges = true;
|
|
27790
|
+
}
|
|
27156
27791
|
}
|
|
27157
27792
|
}
|
|
27158
27793
|
for (const colName of Object.keys(currCols)) {
|
|
@@ -27206,6 +27841,7 @@ var migrationCounter = 0, migrationsCreatedCount = 0, migrationsUpdatedCount = 0
|
|
|
27206
27841
|
var init_migrations = __esm(() => {
|
|
27207
27842
|
init_config();
|
|
27208
27843
|
init_drivers();
|
|
27844
|
+
init_meta();
|
|
27209
27845
|
});
|
|
27210
27846
|
|
|
27211
27847
|
// src/schema.ts
|
|
@@ -27240,6 +27876,7 @@ var init_src2 = __esm(() => {
|
|
|
27240
27876
|
init_dynamodb_tooling_adapter();
|
|
27241
27877
|
init_dynamodb();
|
|
27242
27878
|
init_loader();
|
|
27879
|
+
init_meta();
|
|
27243
27880
|
init_migrations();
|
|
27244
27881
|
init_orm();
|
|
27245
27882
|
});
|
|
@@ -29283,7 +29920,7 @@ function getPrefix() {
|
|
|
29283
29920
|
}
|
|
29284
29921
|
var prefix = getPrefix();
|
|
29285
29922
|
// package.json
|
|
29286
|
-
var version2 = "0.1.
|
|
29923
|
+
var version2 = "0.1.27";
|
|
29287
29924
|
|
|
29288
29925
|
// bin/cli.ts
|
|
29289
29926
|
init_actions();
|
|
@@ -29309,6 +29946,15 @@ var cli = new CLI("query-builder");
|
|
|
29309
29946
|
cli.command("introspect <dir>", "Load models and print inferred schema").option("--verbose", "Enable verbose logging").example("query-builder introspect ./app/Models --verbose").action(async (dir, _options) => {
|
|
29310
29947
|
await introspect(dir, _options);
|
|
29311
29948
|
});
|
|
29949
|
+
cli.command("introspect:db", "Reverse-introspect the live database into defineModel() source (#1047)").option("--table <table>", "Limit to a single table (repeatable)").example("query-builder introspect:db > app/Models/generated.ts").action(async (_options) => {
|
|
29950
|
+
const t2 = _options?.table;
|
|
29951
|
+
const tables = t2 ? Array.isArray(t2) ? t2 : [t2] : undefined;
|
|
29952
|
+
const models = await introspectDatabase({ tables });
|
|
29953
|
+
console.log(`import { defineModel } from 'bun-query-builder'
|
|
29954
|
+
|
|
29955
|
+
${models.map((m2) => m2.source).join(`
|
|
29956
|
+
`)}`);
|
|
29957
|
+
});
|
|
29312
29958
|
cli.command("sql <dir> <table>", "Build a sample query for a table").option("--limit <n>", "Limit rows", { default: 10 }).example("query-builder sql ./app/Models users --limit 5").action(async (dir, table, opts) => {
|
|
29313
29959
|
await sql(dir, table, opts);
|
|
29314
29960
|
});
|