@bunnykit/orm 0.1.21 → 0.1.22
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.
|
@@ -111,6 +111,9 @@ export class Connection {
|
|
|
111
111
|
if (this.driverName !== "postgres") {
|
|
112
112
|
return await this.transaction(callback);
|
|
113
113
|
}
|
|
114
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(setting)) {
|
|
115
|
+
throw new Error(`Invalid PostgreSQL setting name: ${setting}`);
|
|
116
|
+
}
|
|
114
117
|
return await this.transaction(async (connection) => {
|
|
115
118
|
await connection.run(`SET LOCAL ${setting} = ${connection.getGrammar().placeholder(1)}`, [tenantId]);
|
|
116
119
|
return await callback(connection);
|
|
@@ -134,7 +134,7 @@ export declare class Builder<T = Record<string, any>> {
|
|
|
134
134
|
private addBinding;
|
|
135
135
|
private compileWhereClause;
|
|
136
136
|
private compileWheres;
|
|
137
|
-
private
|
|
137
|
+
private compileWhereClauses;
|
|
138
138
|
private compileOrders;
|
|
139
139
|
private compileGroups;
|
|
140
140
|
private compileHavings;
|
|
@@ -161,6 +161,8 @@ export declare class Builder<T = Record<string, any>> {
|
|
|
161
161
|
chunk(count: number, callback: (items: T[]) => void | Promise<void>): Promise<void>;
|
|
162
162
|
each(count: number, callback: (item: T) => void | Promise<void>): Promise<void>;
|
|
163
163
|
cursor(chunkSize?: number): AsyncGenerator<T>;
|
|
164
|
+
private getResultAccessColumn;
|
|
165
|
+
private compileCursorWheres;
|
|
164
166
|
lazy(count?: number): AsyncGenerator<T>;
|
|
165
167
|
insert(data: ModelAttributeInput<T> | ModelAttributeInput<T>[]): Promise<any>;
|
|
166
168
|
insertGetId(data: ModelAttributeInput<T>, idColumn?: ModelColumn<T>): Promise<any>;
|
|
@@ -65,8 +65,7 @@ export class Builder {
|
|
|
65
65
|
const nested = new Builder(this.connection, this.tableName);
|
|
66
66
|
callback(nested);
|
|
67
67
|
if (nested.wheres.length > 0) {
|
|
68
|
-
|
|
69
|
-
this.wheres.push({ type: "raw", column: `(${sql})`, boolean, scope: undefined });
|
|
68
|
+
this.wheres.push({ type: "nested", column: "", query: nested.wheres, boolean, scope: undefined });
|
|
70
69
|
}
|
|
71
70
|
return this;
|
|
72
71
|
}
|
|
@@ -484,6 +483,10 @@ export class Builder {
|
|
|
484
483
|
else if (where.type === "raw") {
|
|
485
484
|
return `${prefix} ${where.column}`;
|
|
486
485
|
}
|
|
486
|
+
else if (where.type === "nested") {
|
|
487
|
+
const sql = this.compileWhereClauses(where.query || [], "");
|
|
488
|
+
return `${prefix} (${sql})`;
|
|
489
|
+
}
|
|
487
490
|
else if (where.type === "like") {
|
|
488
491
|
const sql = this.grammar.compileLike(this.grammar.wrap(where.column), where.value, !!where.not, this.parameterize ? (v) => this.addBinding(v) : undefined);
|
|
489
492
|
return `${prefix} ${sql}`;
|
|
@@ -540,20 +543,15 @@ export class Builder {
|
|
|
540
543
|
return "";
|
|
541
544
|
}
|
|
542
545
|
compileWheres() {
|
|
543
|
-
|
|
544
|
-
return "";
|
|
545
|
-
const clauses = this.wheres.map((where, index) => {
|
|
546
|
-
const prefix = index === 0 ? "WHERE" : where.boolean.toUpperCase();
|
|
547
|
-
return this.compileWhereClause(where, prefix);
|
|
548
|
-
});
|
|
549
|
-
return clauses.join(" ");
|
|
546
|
+
return this.compileWhereClauses(this.wheres, "WHERE");
|
|
550
547
|
}
|
|
551
|
-
|
|
552
|
-
if (
|
|
548
|
+
compileWhereClauses(wheres, firstPrefix) {
|
|
549
|
+
if (wheres.length === 0)
|
|
553
550
|
return "";
|
|
554
|
-
const clauses =
|
|
555
|
-
const prefix = index === 0 ? "" : where.boolean.toUpperCase();
|
|
556
|
-
|
|
551
|
+
const clauses = wheres.map((where, index) => {
|
|
552
|
+
const prefix = index === 0 ? "WHERE" : where.boolean.toUpperCase();
|
|
553
|
+
const adjustedPrefix = index === 0 ? firstPrefix : prefix;
|
|
554
|
+
return this.compileWhereClause(where, adjustedPrefix);
|
|
557
555
|
});
|
|
558
556
|
return clauses.join(" ").trim();
|
|
559
557
|
}
|
|
@@ -781,9 +779,7 @@ export class Builder {
|
|
|
781
779
|
}
|
|
782
780
|
const orderColumn = this.orders[0]?.column || primaryKey;
|
|
783
781
|
const orderDirection = this.orders[0]?.direction || "asc";
|
|
784
|
-
|
|
785
|
-
const accessColumn = orderColumn.includes(".") ? orderColumn.split(".")[1] : orderColumn;
|
|
786
|
-
let lastValue = undefined;
|
|
782
|
+
let lastValues = undefined;
|
|
787
783
|
while (true) {
|
|
788
784
|
const builder = this.clone();
|
|
789
785
|
// Preserve multi-column ORDER BY, appending PK tie-breaker if not present
|
|
@@ -794,23 +790,15 @@ export class Builder {
|
|
|
794
790
|
}
|
|
795
791
|
builder.offsetValue = undefined;
|
|
796
792
|
builder.limitValue = chunkSize;
|
|
797
|
-
if (
|
|
798
|
-
const op = orderDirection === "asc" ? ">" : "<";
|
|
793
|
+
if (lastValues !== undefined) {
|
|
799
794
|
// Parenthesize existing wheres when appending cursor condition to preserve OR precedence
|
|
800
795
|
if (builder.wheres.length > 0) {
|
|
801
796
|
const hasOr = builder.wheres.some((w) => w.boolean === "or");
|
|
802
797
|
if (hasOr) {
|
|
803
|
-
builder.wheres = [{ type: "
|
|
798
|
+
builder.wheres = [{ type: "nested", column: "", query: builder.wheres, boolean: "and", scope: undefined }];
|
|
804
799
|
}
|
|
805
800
|
}
|
|
806
|
-
builder.wheres.push({
|
|
807
|
-
type: "basic",
|
|
808
|
-
column: orderColumn,
|
|
809
|
-
operator: op,
|
|
810
|
-
value: lastValue,
|
|
811
|
-
boolean: "and",
|
|
812
|
-
scope: undefined,
|
|
813
|
-
});
|
|
801
|
+
builder.wheres.push({ type: "nested", column: "", query: this.compileCursorWheres(builder.orders, lastValues), boolean: "and", scope: undefined });
|
|
814
802
|
}
|
|
815
803
|
const items = await builder.get();
|
|
816
804
|
if (items.length === 0)
|
|
@@ -821,11 +809,46 @@ export class Builder {
|
|
|
821
809
|
if (items.length < chunkSize)
|
|
822
810
|
break;
|
|
823
811
|
const lastItem = items[items.length - 1];
|
|
824
|
-
|
|
825
|
-
? lastItem[
|
|
812
|
+
lastValues = lastItem && typeof lastItem === "object"
|
|
813
|
+
? builder.orders.map((order) => lastItem[this.getResultAccessColumn(order.column)])
|
|
826
814
|
: undefined;
|
|
827
815
|
}
|
|
828
816
|
}
|
|
817
|
+
getResultAccessColumn(column) {
|
|
818
|
+
return column.includes(".") ? column.split(".").at(-1) : column;
|
|
819
|
+
}
|
|
820
|
+
compileCursorWheres(orders, values, index = 0) {
|
|
821
|
+
const order = orders[index];
|
|
822
|
+
const op = order.direction === "asc" ? ">" : "<";
|
|
823
|
+
const clauses = [{
|
|
824
|
+
type: "basic",
|
|
825
|
+
column: order.column,
|
|
826
|
+
operator: op,
|
|
827
|
+
value: values[index],
|
|
828
|
+
boolean: "and",
|
|
829
|
+
scope: undefined,
|
|
830
|
+
}];
|
|
831
|
+
if (index < orders.length - 1) {
|
|
832
|
+
clauses.push({
|
|
833
|
+
type: "nested",
|
|
834
|
+
column: "",
|
|
835
|
+
query: [
|
|
836
|
+
{
|
|
837
|
+
type: "basic",
|
|
838
|
+
column: order.column,
|
|
839
|
+
operator: "=",
|
|
840
|
+
value: values[index],
|
|
841
|
+
boolean: "and",
|
|
842
|
+
scope: undefined,
|
|
843
|
+
},
|
|
844
|
+
...this.compileCursorWheres(orders, values, index + 1),
|
|
845
|
+
],
|
|
846
|
+
boolean: "or",
|
|
847
|
+
scope: undefined,
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
return clauses;
|
|
851
|
+
}
|
|
829
852
|
async *lazy(count = 1000) {
|
|
830
853
|
let page = 1;
|
|
831
854
|
while (true) {
|
|
@@ -135,48 +135,57 @@ export class Schema {
|
|
|
135
135
|
const driver = connection.getDriverName();
|
|
136
136
|
const schema = connection.getSchema() || "public";
|
|
137
137
|
let sql;
|
|
138
|
+
let bindings = [];
|
|
138
139
|
if (driver === "sqlite") {
|
|
139
|
-
sql =
|
|
140
|
+
sql = "SELECT name FROM sqlite_master WHERE type='table' AND name = ?";
|
|
141
|
+
bindings = [table];
|
|
140
142
|
}
|
|
141
143
|
else if (driver === "mysql") {
|
|
142
|
-
sql =
|
|
144
|
+
sql = "SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?";
|
|
145
|
+
bindings = [table];
|
|
143
146
|
}
|
|
144
147
|
else {
|
|
145
|
-
sql =
|
|
148
|
+
sql = "SELECT table_name FROM information_schema.tables WHERE table_schema = $1 AND table_name = $2";
|
|
149
|
+
bindings = [schema, table];
|
|
146
150
|
}
|
|
147
|
-
const result = await connection.query(sql);
|
|
151
|
+
const result = await connection.query(sql, bindings);
|
|
148
152
|
return result.length > 0;
|
|
149
153
|
}
|
|
150
154
|
static async hasColumn(table, column) {
|
|
151
155
|
const connection = this.getConnection();
|
|
152
156
|
const driver = connection.getDriverName();
|
|
153
157
|
const schema = connection.getSchema() || "public";
|
|
158
|
+
const grammar = this.getGrammar();
|
|
154
159
|
let sql;
|
|
160
|
+
let bindings = [];
|
|
155
161
|
if (driver === "sqlite") {
|
|
156
|
-
sql = `PRAGMA table_info(${table})`;
|
|
162
|
+
sql = `PRAGMA table_info(${grammar.wrap(table)})`;
|
|
157
163
|
const result = await connection.query(sql);
|
|
158
164
|
return result.some((row) => row.name === column);
|
|
159
165
|
}
|
|
160
166
|
else if (driver === "mysql") {
|
|
161
|
-
sql =
|
|
167
|
+
sql = "SELECT column_name FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?";
|
|
168
|
+
bindings = [table, column];
|
|
162
169
|
}
|
|
163
170
|
else {
|
|
164
|
-
sql =
|
|
171
|
+
sql = "SELECT column_name FROM information_schema.columns WHERE table_schema = $1 AND table_name = $2 AND column_name = $3";
|
|
172
|
+
bindings = [schema, table, column];
|
|
165
173
|
}
|
|
166
|
-
const result = await connection.query(sql);
|
|
174
|
+
const result = await connection.query(sql, bindings);
|
|
167
175
|
return result.length > 0;
|
|
168
176
|
}
|
|
169
177
|
static async getColumn(table, column) {
|
|
170
178
|
const connection = this.getConnection();
|
|
171
179
|
const driver = connection.getDriverName();
|
|
172
180
|
const schema = connection.getSchema() || "public";
|
|
181
|
+
const grammar = this.getGrammar();
|
|
173
182
|
if (driver === "sqlite") {
|
|
174
|
-
const rows = await connection.query(`PRAGMA table_info(${table})`);
|
|
183
|
+
const rows = await connection.query(`PRAGMA table_info(${grammar.wrap(table)})`);
|
|
175
184
|
const row = rows.find((item) => item.name === column);
|
|
176
185
|
return row ? { name: row.name, type: row.type, primary: row.pk > 0, autoIncrement: false } : null;
|
|
177
186
|
}
|
|
178
187
|
if (driver === "mysql") {
|
|
179
|
-
const rows = await connection.query(`
|
|
188
|
+
const rows = await connection.query("SELECT column_name AS Field, column_type AS Type, column_key AS `Key`, extra AS Extra FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?", [table, column]);
|
|
180
189
|
const row = rows[0];
|
|
181
190
|
return row ? { name: row.Field, type: row.Type, primary: row.Key === "PRI", autoIncrement: String(row.Extra || "").toLowerCase().includes("auto_increment") } : null;
|
|
182
191
|
}
|
|
@@ -189,9 +198,9 @@ export class Schema {
|
|
|
189
198
|
LEFT JOIN information_schema.table_constraints tc
|
|
190
199
|
ON kcu.table_schema = tc.table_schema
|
|
191
200
|
AND kcu.constraint_name = tc.constraint_name
|
|
192
|
-
WHERE c.table_schema =
|
|
193
|
-
AND c.table_name =
|
|
194
|
-
AND c.column_name =
|
|
201
|
+
WHERE c.table_schema = $1
|
|
202
|
+
AND c.table_name = $2
|
|
203
|
+
AND c.column_name = $3`, [schema, table, column]);
|
|
195
204
|
const row = rows[0];
|
|
196
205
|
return row ? { name: row.column_name, type: row.data_type, primary: !!row.primary_key, autoIncrement: false } : null;
|
|
197
206
|
}
|
|
@@ -5,7 +5,7 @@ export class Grammar {
|
|
|
5
5
|
return value.split(".").map((v) => this.wrap(v)).join(".");
|
|
6
6
|
}
|
|
7
7
|
const { prefix, suffix } = this.wrappers;
|
|
8
|
-
return `${prefix}${value}${suffix}`;
|
|
8
|
+
return `${prefix}${value.replaceAll(suffix, `${suffix}${suffix}`)}${suffix}`;
|
|
9
9
|
}
|
|
10
10
|
wrapArray(values) {
|
|
11
11
|
return values.map((v) => this.wrap(v));
|
|
@@ -29,7 +29,7 @@ export interface ForeignKeyDefinition {
|
|
|
29
29
|
onUpdate?: string;
|
|
30
30
|
}
|
|
31
31
|
export interface WhereClause {
|
|
32
|
-
type: "basic" | "in" | "null" | "raw" | "between" | "column" | "exists" | "like" | "regexp" | "fulltext" | "json_contains" | "json_length" | "date" | "all" | "any";
|
|
32
|
+
type: "basic" | "in" | "null" | "raw" | "nested" | "between" | "column" | "exists" | "like" | "regexp" | "fulltext" | "json_contains" | "json_length" | "date" | "all" | "any";
|
|
33
33
|
column: string;
|
|
34
34
|
columns?: string[];
|
|
35
35
|
operator?: string;
|
|
@@ -38,6 +38,7 @@ export interface WhereClause {
|
|
|
38
38
|
scope?: string;
|
|
39
39
|
not?: boolean;
|
|
40
40
|
dateType?: string;
|
|
41
|
+
query?: WhereClause[];
|
|
41
42
|
}
|
|
42
43
|
export interface OrderClause {
|
|
43
44
|
column: string;
|
package/package.json
CHANGED