pg-query-sdk 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/cjs/builders/QueryBuilder.d.ts +1 -1
- package/dist/cjs/core/ParamContext.d.ts +4 -1
- package/dist/cjs/core/ParamContext.js +6 -1
- package/dist/cjs/core/QueryExecutor.d.ts +1 -1
- package/dist/cjs/core/QueryExecutor.js +3 -2
- package/dist/cjs/query/ConditionBuilder.d.ts +10 -31
- package/dist/cjs/query/ConditionBuilder.js +79 -55
- package/dist/cjs/query/QueryBuilder.d.ts +5 -3
- package/dist/cjs/query/QueryBuilder.js +5 -1
- package/dist/esm/builders/QueryBuilder.d.ts +1 -1
- package/dist/esm/core/ParamContext.d.ts +4 -1
- package/dist/esm/core/ParamContext.js +6 -1
- package/dist/esm/core/QueryExecutor.d.ts +1 -1
- package/dist/esm/core/QueryExecutor.js +3 -2
- package/dist/esm/query/ConditionBuilder.d.ts +10 -31
- package/dist/esm/query/ConditionBuilder.js +76 -55
- package/dist/esm/query/QueryBuilder.d.ts +5 -3
- package/dist/esm/query/QueryBuilder.js +5 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ npm install pg-query-sdk
|
|
|
35
35
|
The `Database` class serves as the primary interface for all database interactions. Instantiate it with your PostgreSQL connection string.
|
|
36
36
|
|
|
37
37
|
```typescript
|
|
38
|
-
import Database from 'pg-query-sdk';
|
|
38
|
+
import {Database} from 'pg-query-sdk';
|
|
39
39
|
|
|
40
40
|
const db = new Database({
|
|
41
41
|
connectionString: 'postgres://user:pass@localhost:5432/your_database',
|
|
@@ -51,7 +51,7 @@ const db = new Database({
|
|
|
51
51
|
The `QueryBuilder` enables the programmatic construction of SQL `SELECT` statements, accessible via the `db.table()` method.
|
|
52
52
|
|
|
53
53
|
```typescript
|
|
54
|
-
import Database from 'pg-query-sdk';
|
|
54
|
+
import {Database} from 'pg-query-sdk';
|
|
55
55
|
|
|
56
56
|
const db = new Database({
|
|
57
57
|
connectionString: 'postgres://user:pass@localhost:5432/your_database',
|
|
@@ -76,7 +76,7 @@ selectExample();
|
|
|
76
76
|
The `ConditionBuilder` facilitates the creation of complex conditional logic within `WHERE` and `HAVING` clauses, typically used in conjunction with the `QueryBuilder`.
|
|
77
77
|
|
|
78
78
|
```typescript
|
|
79
|
-
import Database from 'pg-query-sdk';
|
|
79
|
+
import {Database} from 'pg-query-sdk';
|
|
80
80
|
|
|
81
81
|
const db = new Database({
|
|
82
82
|
connectionString: 'postgres://user:pass@localhost:5432/your_database',
|
|
@@ -129,7 +129,7 @@ directExecuteExample();
|
|
|
129
129
|
The SDK provides robust support for managing ACID-compliant transactions, ensuring data integrity.
|
|
130
130
|
|
|
131
131
|
```typescript
|
|
132
|
-
import Database from 'pg-query-sdk';
|
|
132
|
+
import {Database} from 'pg-query-sdk';
|
|
133
133
|
|
|
134
134
|
const db = new Database({
|
|
135
135
|
connectionString: 'postgres://user:pass@localhost:5432/your_database',
|
|
@@ -164,7 +164,7 @@ transactionExample();
|
|
|
164
164
|
The abstract `Repository<T>` class offers a foundational ORM layer, providing methods like `findById` and a pre-configured `QueryBuilder` (`qb()`). Custom DML operations (`insert`, `update`, `delete`) should be implemented in concrete repository classes.
|
|
165
165
|
|
|
166
166
|
```typescript
|
|
167
|
-
import Database,
|
|
167
|
+
import {Database, Repository } from 'pg-query-sdk';
|
|
168
168
|
import { QueryExecutor, Dialect } from 'pg-query-sdk';
|
|
169
169
|
|
|
170
170
|
interface User {
|
|
@@ -221,7 +221,7 @@ const db = new Database({ /* ... */ });
|
|
|
221
221
|
### ESM
|
|
222
222
|
|
|
223
223
|
```typescript
|
|
224
|
-
import Database from 'pg-query-sdk';
|
|
224
|
+
import {Database} from 'pg-query-sdk';
|
|
225
225
|
const db = new Database({ /* ... */ });
|
|
226
226
|
```
|
|
227
227
|
|
|
@@ -131,7 +131,7 @@ export default class QueryBuilder<T = any> {
|
|
|
131
131
|
*/
|
|
132
132
|
build(): {
|
|
133
133
|
query: string;
|
|
134
|
-
params: any[];
|
|
134
|
+
params: readonly (string | number | boolean | any[] | Date | Buffer<ArrayBufferLike> | null)[];
|
|
135
135
|
};
|
|
136
136
|
/**
|
|
137
137
|
* Executes the built SQL query and returns the results.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Dialect } from "../dialects/Dialect";
|
|
2
|
+
type SQLParam = string | number | boolean | Date | null | Buffer | any[];
|
|
2
3
|
/**
|
|
3
4
|
* Manages parameters for SQL queries, ensuring proper dialect-specific placeholders.
|
|
4
5
|
*/
|
|
@@ -20,5 +21,7 @@ export default class ParamContext {
|
|
|
20
21
|
* Retrieves the list of accumulated parameters.
|
|
21
22
|
* @returns An array of parameters.
|
|
22
23
|
*/
|
|
23
|
-
getParams():
|
|
24
|
+
getParams(): readonly SQLParam[];
|
|
25
|
+
clone(): ParamContext;
|
|
24
26
|
}
|
|
27
|
+
export {};
|
|
@@ -26,7 +26,12 @@ class ParamContext {
|
|
|
26
26
|
* @returns An array of parameters.
|
|
27
27
|
*/
|
|
28
28
|
getParams() {
|
|
29
|
-
return this.params;
|
|
29
|
+
return Object.freeze([...this.params]);
|
|
30
|
+
}
|
|
31
|
+
clone() {
|
|
32
|
+
const ctx = new ParamContext(this.dialect);
|
|
33
|
+
ctx.params = [...this.params];
|
|
34
|
+
return ctx;
|
|
30
35
|
}
|
|
31
36
|
}
|
|
32
37
|
exports.default = ParamContext;
|
|
@@ -21,7 +21,7 @@ export default class QueryExecutor {
|
|
|
21
21
|
* @param cacheTTL - Time-to-live for caching (not currently used in this implementation).
|
|
22
22
|
* @returns A Promise that resolves to the QueryResult.
|
|
23
23
|
*/
|
|
24
|
-
execute(query: string, params
|
|
24
|
+
execute(query: string, params?: readonly any[], cacheTTL?: number): Promise<QueryResult>;
|
|
25
25
|
/**
|
|
26
26
|
* Returns the underlying PostgreSQL Pool instance.
|
|
27
27
|
* @returns The Pool instance or undefined if not initialized with a connection string.
|
|
@@ -31,11 +31,12 @@ class QueryExecutor {
|
|
|
31
31
|
* @returns A Promise that resolves to the QueryResult.
|
|
32
32
|
*/
|
|
33
33
|
async execute(query, params = [], cacheTTL) {
|
|
34
|
+
const normalized = [...params];
|
|
34
35
|
if (this.client) {
|
|
35
|
-
return this.client.query(query,
|
|
36
|
+
return this.client.query(query, normalized);
|
|
36
37
|
}
|
|
37
38
|
if (this.pool) {
|
|
38
|
-
return this.pool.query(query,
|
|
39
|
+
return this.pool.query(query, normalized);
|
|
39
40
|
}
|
|
40
41
|
throw new Error('Executor not initialized');
|
|
41
42
|
}
|
|
@@ -1,44 +1,23 @@
|
|
|
1
1
|
import ParamContext from '../core/ParamContext';
|
|
2
|
+
type Operator = '=' | '>' | '<' | '>=' | '<=' | '!=' | '<>' | 'LIKE' | 'ILIKE' | 'IN' | 'NOT IN' | 'BETWEEN' | 'EXISTS';
|
|
3
|
+
type ConditionValue = any | {
|
|
4
|
+
op: Operator;
|
|
5
|
+
value: any;
|
|
6
|
+
};
|
|
2
7
|
/**
|
|
3
8
|
* A builder for constructing SQL WHERE and HAVING clauses.
|
|
4
9
|
*/
|
|
5
10
|
export default class ConditionBuilder {
|
|
6
11
|
private ctx;
|
|
7
12
|
private parts;
|
|
8
|
-
/**
|
|
9
|
-
* Creates an instance of ConditionBuilder.
|
|
10
|
-
* @param ctx - The ParamContext to manage query parameters.
|
|
11
|
-
*/
|
|
12
13
|
constructor(ctx: ParamContext);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
* @param obj - An object where keys are column names and values are the desired values or an object with `op` and `value`.
|
|
17
|
-
* @returns The current ConditionBuilder instance.
|
|
18
|
-
*/
|
|
19
|
-
where(obj: Record<string, any>): this;
|
|
20
|
-
/**
|
|
21
|
-
* Adds a raw SQL expression to the conditions.
|
|
22
|
-
* @param expression - The raw SQL expression.
|
|
23
|
-
* @returns The current ConditionBuilder instance.
|
|
24
|
-
*/
|
|
14
|
+
where(obj: Record<string, ConditionValue>): this;
|
|
15
|
+
private handleOperator;
|
|
16
|
+
private add;
|
|
25
17
|
raw(expression: string): this;
|
|
26
|
-
/**
|
|
27
|
-
* Adds a group of conditions connected by AND.
|
|
28
|
-
* @param cb - A callback function that receives a nested ConditionBuilder to define conditions within the group.
|
|
29
|
-
* @returns The current ConditionBuilder instance.
|
|
30
|
-
*/
|
|
31
18
|
andGroup(cb: (qb: ConditionBuilder) => void): this;
|
|
32
|
-
/**
|
|
33
|
-
* Adds a group of conditions connected by OR.
|
|
34
|
-
* @param cb - A callback function that receives a nested ConditionBuilder to define conditions within the group.
|
|
35
|
-
* @returns The current ConditionBuilder instance.
|
|
36
|
-
*/
|
|
37
19
|
orGroup(cb: (qb: ConditionBuilder) => void): this;
|
|
38
|
-
|
|
39
|
-
* Builds the SQL condition string.
|
|
40
|
-
* @param prefix - The prefix for the condition (e.g., 'WHERE', 'HAVING'). Defaults to 'WHERE'.
|
|
41
|
-
* @returns The built SQL condition string, or an empty string if no conditions were added.
|
|
42
|
-
*/
|
|
20
|
+
clone(): ConditionBuilder;
|
|
43
21
|
build(prefix?: string): string;
|
|
44
22
|
}
|
|
23
|
+
export {};
|
|
@@ -1,100 +1,124 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const QueryBuilder_1 = __importDefault(require("../builders/QueryBuilder"));
|
|
3
7
|
/**
|
|
4
8
|
* A builder for constructing SQL WHERE and HAVING clauses.
|
|
5
9
|
*/
|
|
6
10
|
class ConditionBuilder {
|
|
7
|
-
/**
|
|
8
|
-
* Creates an instance of ConditionBuilder.
|
|
9
|
-
* @param ctx - The ParamContext to manage query parameters.
|
|
10
|
-
*/
|
|
11
11
|
constructor(ctx) {
|
|
12
12
|
this.ctx = ctx;
|
|
13
13
|
this.parts = [];
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
16
|
-
* Adds one or more WHERE conditions based on an object.
|
|
17
|
-
* Supports direct equality, `null` checks, and custom operators.
|
|
18
|
-
* @param obj - An object where keys are column names and values are the desired values or an object with `op` and `value`.
|
|
19
|
-
* @returns The current ConditionBuilder instance.
|
|
20
|
-
*/
|
|
21
15
|
where(obj) {
|
|
22
|
-
Object.entries(obj).forEach(([key,
|
|
23
|
-
if (
|
|
24
|
-
this.
|
|
16
|
+
Object.entries(obj).forEach(([key, condition]) => {
|
|
17
|
+
if (condition === null) {
|
|
18
|
+
this.add(`${key} IS NULL`);
|
|
25
19
|
return;
|
|
26
20
|
}
|
|
27
|
-
if (typeof
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
if (typeof condition === 'object'
|
|
22
|
+
&& condition !== null
|
|
23
|
+
&& 'op' in condition) {
|
|
24
|
+
const { op, value } = condition;
|
|
25
|
+
this.handleOperator(key, op, value);
|
|
31
26
|
return;
|
|
32
27
|
}
|
|
33
|
-
const placeholder = this.ctx.add(
|
|
34
|
-
this.
|
|
28
|
+
const placeholder = this.ctx.add(condition);
|
|
29
|
+
this.add(`${key} = ${placeholder}`);
|
|
35
30
|
});
|
|
36
31
|
return this;
|
|
37
32
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
handleOperator(key, op, value) {
|
|
34
|
+
const allowed = [
|
|
35
|
+
'=', '>', '<', '>=', '<=', '!=', '<>',
|
|
36
|
+
'LIKE', 'ILIKE',
|
|
37
|
+
'IN', 'NOT IN',
|
|
38
|
+
'BETWEEN', 'EXISTS'
|
|
39
|
+
];
|
|
40
|
+
if (!allowed.includes(op)) {
|
|
41
|
+
throw new Error(`Invalid operator ${op}`);
|
|
42
|
+
}
|
|
43
|
+
switch (op) {
|
|
44
|
+
case 'IN':
|
|
45
|
+
case 'NOT IN': {
|
|
46
|
+
if (!Array.isArray(value)) {
|
|
47
|
+
throw new Error(`${op} expects array`);
|
|
48
|
+
}
|
|
49
|
+
const placeholders = value.map(v => this.ctx.add(v)).join(', ');
|
|
50
|
+
this.add(`${key} ${op} (${placeholders})`);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
case 'BETWEEN': {
|
|
54
|
+
if (!Array.isArray(value) || value.length !== 2) {
|
|
55
|
+
throw new Error('BETWEEN expects [min,max]');
|
|
56
|
+
}
|
|
57
|
+
const p1 = this.ctx.add(value[0]);
|
|
58
|
+
const p2 = this.ctx.add(value[1]);
|
|
59
|
+
this.add(`${key} BETWEEN ${p1} AND ${p2}`);
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case 'EXISTS': {
|
|
63
|
+
if (!(value instanceof QueryBuilder_1.default)) {
|
|
64
|
+
throw new Error('EXISTS expects QueryBuilder');
|
|
65
|
+
}
|
|
66
|
+
const { query, params } = value.build();
|
|
67
|
+
params.forEach(p => this.ctx.add(p));
|
|
68
|
+
this.add(`EXISTS (${query})`);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
default: {
|
|
72
|
+
const placeholder = this.ctx.add(value);
|
|
73
|
+
this.add(`${key} ${op} ${placeholder}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
add(expression, type = 'AND') {
|
|
78
|
+
this.parts.push({ type, expression });
|
|
79
|
+
}
|
|
43
80
|
raw(expression) {
|
|
44
|
-
this.
|
|
81
|
+
this.add(expression);
|
|
45
82
|
return this;
|
|
46
83
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Adds a group of conditions connected by AND.
|
|
49
|
-
* @param cb - A callback function that receives a nested ConditionBuilder to define conditions within the group.
|
|
50
|
-
* @returns The current ConditionBuilder instance.
|
|
51
|
-
*/
|
|
52
84
|
andGroup(cb) {
|
|
53
85
|
const nested = new ConditionBuilder(this.ctx);
|
|
54
86
|
cb(nested);
|
|
55
87
|
const built = nested.build();
|
|
56
|
-
if (built)
|
|
57
|
-
this
|
|
58
|
-
}
|
|
88
|
+
if (!built)
|
|
89
|
+
return this;
|
|
90
|
+
this.add(`(${built.replace(/^WHERE\s/, '')})`, 'AND');
|
|
59
91
|
return this;
|
|
60
92
|
}
|
|
61
|
-
/**
|
|
62
|
-
* Adds a group of conditions connected by OR.
|
|
63
|
-
* @param cb - A callback function that receives a nested ConditionBuilder to define conditions within the group.
|
|
64
|
-
* @returns The current ConditionBuilder instance.
|
|
65
|
-
*/
|
|
66
93
|
orGroup(cb) {
|
|
67
94
|
const nested = new ConditionBuilder(this.ctx);
|
|
68
95
|
cb(nested);
|
|
69
96
|
const built = nested.build();
|
|
70
|
-
if (built)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
97
|
+
if (!built)
|
|
98
|
+
return this;
|
|
99
|
+
this.add(`(${built.replace(/^WHERE\s/, '')})`, 'OR');
|
|
74
100
|
return this;
|
|
75
101
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
102
|
+
clone() {
|
|
103
|
+
const cloned = new ConditionBuilder(this.ctx);
|
|
104
|
+
cloned.parts = this.parts.map(p => ({
|
|
105
|
+
...p
|
|
106
|
+
}));
|
|
107
|
+
return cloned;
|
|
108
|
+
}
|
|
81
109
|
build(prefix = 'WHERE') {
|
|
82
110
|
if (!this.parts.length)
|
|
83
111
|
return '';
|
|
84
|
-
|
|
112
|
+
let sql = '';
|
|
85
113
|
this.parts.forEach((part, index) => {
|
|
86
114
|
if (index === 0) {
|
|
87
|
-
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
if (part.startsWith('OR ')) {
|
|
91
|
-
normalized.push(part);
|
|
115
|
+
sql += part.expression;
|
|
92
116
|
}
|
|
93
117
|
else {
|
|
94
|
-
|
|
118
|
+
sql += ` ${part.type} ${part.expression}`;
|
|
95
119
|
}
|
|
96
120
|
});
|
|
97
|
-
return `${prefix} ${
|
|
121
|
+
return `${prefix} ${sql}`;
|
|
98
122
|
}
|
|
99
123
|
}
|
|
100
124
|
exports.default = ConditionBuilder;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import ConditionBuilder from './ConditionBuilder';
|
|
2
2
|
import QueryExecutor from '../core/QueryExecutor';
|
|
3
3
|
import { Dialect } from '../dialects/Dialect';
|
|
4
|
+
type WhereInput<T> = Partial<T> | Record<string, any> | Array<Record<string, any>>;
|
|
4
5
|
/**
|
|
5
6
|
* A fluent SQL query builder for constructing and executing database queries.
|
|
6
7
|
* @template T The expected type of the results.
|
|
@@ -66,7 +67,7 @@ export default class QueryBuilder<T = any> {
|
|
|
66
67
|
* @param fields - An array of field names or keys of T.
|
|
67
68
|
* @returns The current QueryBuilder instance.
|
|
68
69
|
*/
|
|
69
|
-
select(fields: (keyof T | string)[]): this;
|
|
70
|
+
select(fields: (keyof T | string)[] | keyof T | string): this;
|
|
70
71
|
/**
|
|
71
72
|
* Adds a join clause to the query.
|
|
72
73
|
* @param type - The type of join (INNER, LEFT, RIGHT).
|
|
@@ -105,7 +106,7 @@ export default class QueryBuilder<T = any> {
|
|
|
105
106
|
* @param obj - An object where keys are column names and values are the desired values.
|
|
106
107
|
* @returns The current QueryBuilder instance.
|
|
107
108
|
*/
|
|
108
|
-
where(obj:
|
|
109
|
+
where(obj: WhereInput<T>): this;
|
|
109
110
|
/**
|
|
110
111
|
* Adds a raw WHERE expression.
|
|
111
112
|
* @param expression - The raw SQL expression for the WHERE clause.
|
|
@@ -187,7 +188,7 @@ export default class QueryBuilder<T = any> {
|
|
|
187
188
|
*/
|
|
188
189
|
build(): {
|
|
189
190
|
query: string;
|
|
190
|
-
params: any[];
|
|
191
|
+
params: readonly (string | number | boolean | any[] | Date | Buffer<ArrayBufferLike> | null)[];
|
|
191
192
|
};
|
|
192
193
|
/**
|
|
193
194
|
* Executes the built SQL query and returns the results.
|
|
@@ -195,3 +196,4 @@ export default class QueryBuilder<T = any> {
|
|
|
195
196
|
*/
|
|
196
197
|
execute(): Promise<T[]>;
|
|
197
198
|
}
|
|
199
|
+
export {};
|
|
@@ -52,7 +52,8 @@ class QueryBuilder {
|
|
|
52
52
|
* @returns The current QueryBuilder instance.
|
|
53
53
|
*/
|
|
54
54
|
select(fields) {
|
|
55
|
-
|
|
55
|
+
const normalized = Array.isArray(fields) ? fields : [fields];
|
|
56
|
+
this.fields = normalized.map(String);
|
|
56
57
|
return this;
|
|
57
58
|
}
|
|
58
59
|
/**
|
|
@@ -229,6 +230,9 @@ class QueryBuilder {
|
|
|
229
230
|
qb.limitCount = this.limitCount;
|
|
230
231
|
qb.offsetCount = this.offsetCount;
|
|
231
232
|
qb.ctes = [...this.ctes];
|
|
233
|
+
qb.condition = this.condition.clone();
|
|
234
|
+
qb.havingCondition = this.havingCondition.clone();
|
|
235
|
+
qb.ctx = this.ctx.clone();
|
|
232
236
|
return qb;
|
|
233
237
|
}
|
|
234
238
|
/**
|
|
@@ -131,7 +131,7 @@ export default class QueryBuilder<T = any> {
|
|
|
131
131
|
*/
|
|
132
132
|
build(): {
|
|
133
133
|
query: string;
|
|
134
|
-
params: any[];
|
|
134
|
+
params: readonly (string | number | boolean | any[] | Date | Buffer<ArrayBufferLike> | null)[];
|
|
135
135
|
};
|
|
136
136
|
/**
|
|
137
137
|
* Executes the built SQL query and returns the results.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Dialect } from "../dialects/Dialect";
|
|
2
|
+
type SQLParam = string | number | boolean | Date | null | Buffer | any[];
|
|
2
3
|
/**
|
|
3
4
|
* Manages parameters for SQL queries, ensuring proper dialect-specific placeholders.
|
|
4
5
|
*/
|
|
@@ -20,5 +21,7 @@ export default class ParamContext {
|
|
|
20
21
|
* Retrieves the list of accumulated parameters.
|
|
21
22
|
* @returns An array of parameters.
|
|
22
23
|
*/
|
|
23
|
-
getParams():
|
|
24
|
+
getParams(): readonly SQLParam[];
|
|
25
|
+
clone(): ParamContext;
|
|
24
26
|
}
|
|
27
|
+
export {};
|
|
@@ -24,6 +24,11 @@ export default class ParamContext {
|
|
|
24
24
|
* @returns An array of parameters.
|
|
25
25
|
*/
|
|
26
26
|
getParams() {
|
|
27
|
-
return this.params;
|
|
27
|
+
return Object.freeze([...this.params]);
|
|
28
|
+
}
|
|
29
|
+
clone() {
|
|
30
|
+
const ctx = new ParamContext(this.dialect);
|
|
31
|
+
ctx.params = [...this.params];
|
|
32
|
+
return ctx;
|
|
28
33
|
}
|
|
29
34
|
}
|
|
@@ -21,7 +21,7 @@ export default class QueryExecutor {
|
|
|
21
21
|
* @param cacheTTL - Time-to-live for caching (not currently used in this implementation).
|
|
22
22
|
* @returns A Promise that resolves to the QueryResult.
|
|
23
23
|
*/
|
|
24
|
-
execute(query: string, params
|
|
24
|
+
execute(query: string, params?: readonly any[], cacheTTL?: number): Promise<QueryResult>;
|
|
25
25
|
/**
|
|
26
26
|
* Returns the underlying PostgreSQL Pool instance.
|
|
27
27
|
* @returns The Pool instance or undefined if not initialized with a connection string.
|
|
@@ -29,11 +29,12 @@ export default class QueryExecutor {
|
|
|
29
29
|
* @returns A Promise that resolves to the QueryResult.
|
|
30
30
|
*/
|
|
31
31
|
async execute(query, params = [], cacheTTL) {
|
|
32
|
+
const normalized = [...params];
|
|
32
33
|
if (this.client) {
|
|
33
|
-
return this.client.query(query,
|
|
34
|
+
return this.client.query(query, normalized);
|
|
34
35
|
}
|
|
35
36
|
if (this.pool) {
|
|
36
|
-
return this.pool.query(query,
|
|
37
|
+
return this.pool.query(query, normalized);
|
|
37
38
|
}
|
|
38
39
|
throw new Error('Executor not initialized');
|
|
39
40
|
}
|
|
@@ -1,44 +1,23 @@
|
|
|
1
1
|
import ParamContext from '../core/ParamContext';
|
|
2
|
+
type Operator = '=' | '>' | '<' | '>=' | '<=' | '!=' | '<>' | 'LIKE' | 'ILIKE' | 'IN' | 'NOT IN' | 'BETWEEN' | 'EXISTS';
|
|
3
|
+
type ConditionValue = any | {
|
|
4
|
+
op: Operator;
|
|
5
|
+
value: any;
|
|
6
|
+
};
|
|
2
7
|
/**
|
|
3
8
|
* A builder for constructing SQL WHERE and HAVING clauses.
|
|
4
9
|
*/
|
|
5
10
|
export default class ConditionBuilder {
|
|
6
11
|
private ctx;
|
|
7
12
|
private parts;
|
|
8
|
-
/**
|
|
9
|
-
* Creates an instance of ConditionBuilder.
|
|
10
|
-
* @param ctx - The ParamContext to manage query parameters.
|
|
11
|
-
*/
|
|
12
13
|
constructor(ctx: ParamContext);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
* @param obj - An object where keys are column names and values are the desired values or an object with `op` and `value`.
|
|
17
|
-
* @returns The current ConditionBuilder instance.
|
|
18
|
-
*/
|
|
19
|
-
where(obj: Record<string, any>): this;
|
|
20
|
-
/**
|
|
21
|
-
* Adds a raw SQL expression to the conditions.
|
|
22
|
-
* @param expression - The raw SQL expression.
|
|
23
|
-
* @returns The current ConditionBuilder instance.
|
|
24
|
-
*/
|
|
14
|
+
where(obj: Record<string, ConditionValue>): this;
|
|
15
|
+
private handleOperator;
|
|
16
|
+
private add;
|
|
25
17
|
raw(expression: string): this;
|
|
26
|
-
/**
|
|
27
|
-
* Adds a group of conditions connected by AND.
|
|
28
|
-
* @param cb - A callback function that receives a nested ConditionBuilder to define conditions within the group.
|
|
29
|
-
* @returns The current ConditionBuilder instance.
|
|
30
|
-
*/
|
|
31
18
|
andGroup(cb: (qb: ConditionBuilder) => void): this;
|
|
32
|
-
/**
|
|
33
|
-
* Adds a group of conditions connected by OR.
|
|
34
|
-
* @param cb - A callback function that receives a nested ConditionBuilder to define conditions within the group.
|
|
35
|
-
* @returns The current ConditionBuilder instance.
|
|
36
|
-
*/
|
|
37
19
|
orGroup(cb: (qb: ConditionBuilder) => void): this;
|
|
38
|
-
|
|
39
|
-
* Builds the SQL condition string.
|
|
40
|
-
* @param prefix - The prefix for the condition (e.g., 'WHERE', 'HAVING'). Defaults to 'WHERE'.
|
|
41
|
-
* @returns The built SQL condition string, or an empty string if no conditions were added.
|
|
42
|
-
*/
|
|
20
|
+
clone(): ConditionBuilder;
|
|
43
21
|
build(prefix?: string): string;
|
|
44
22
|
}
|
|
23
|
+
export {};
|
|
@@ -1,97 +1,118 @@
|
|
|
1
|
+
import QueryBuilder from "../builders/QueryBuilder";
|
|
1
2
|
/**
|
|
2
3
|
* A builder for constructing SQL WHERE and HAVING clauses.
|
|
3
4
|
*/
|
|
4
5
|
export default class ConditionBuilder {
|
|
5
|
-
/**
|
|
6
|
-
* Creates an instance of ConditionBuilder.
|
|
7
|
-
* @param ctx - The ParamContext to manage query parameters.
|
|
8
|
-
*/
|
|
9
6
|
constructor(ctx) {
|
|
10
7
|
this.ctx = ctx;
|
|
11
8
|
this.parts = [];
|
|
12
9
|
}
|
|
13
|
-
/**
|
|
14
|
-
* Adds one or more WHERE conditions based on an object.
|
|
15
|
-
* Supports direct equality, `null` checks, and custom operators.
|
|
16
|
-
* @param obj - An object where keys are column names and values are the desired values or an object with `op` and `value`.
|
|
17
|
-
* @returns The current ConditionBuilder instance.
|
|
18
|
-
*/
|
|
19
10
|
where(obj) {
|
|
20
|
-
Object.entries(obj).forEach(([key,
|
|
21
|
-
if (
|
|
22
|
-
this.
|
|
11
|
+
Object.entries(obj).forEach(([key, condition]) => {
|
|
12
|
+
if (condition === null) {
|
|
13
|
+
this.add(`${key} IS NULL`);
|
|
23
14
|
return;
|
|
24
15
|
}
|
|
25
|
-
if (typeof
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
if (typeof condition === 'object'
|
|
17
|
+
&& condition !== null
|
|
18
|
+
&& 'op' in condition) {
|
|
19
|
+
const { op, value } = condition;
|
|
20
|
+
this.handleOperator(key, op, value);
|
|
29
21
|
return;
|
|
30
22
|
}
|
|
31
|
-
const placeholder = this.ctx.add(
|
|
32
|
-
this.
|
|
23
|
+
const placeholder = this.ctx.add(condition);
|
|
24
|
+
this.add(`${key} = ${placeholder}`);
|
|
33
25
|
});
|
|
34
26
|
return this;
|
|
35
27
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
28
|
+
handleOperator(key, op, value) {
|
|
29
|
+
const allowed = [
|
|
30
|
+
'=', '>', '<', '>=', '<=', '!=', '<>',
|
|
31
|
+
'LIKE', 'ILIKE',
|
|
32
|
+
'IN', 'NOT IN',
|
|
33
|
+
'BETWEEN', 'EXISTS'
|
|
34
|
+
];
|
|
35
|
+
if (!allowed.includes(op)) {
|
|
36
|
+
throw new Error(`Invalid operator ${op}`);
|
|
37
|
+
}
|
|
38
|
+
switch (op) {
|
|
39
|
+
case 'IN':
|
|
40
|
+
case 'NOT IN': {
|
|
41
|
+
if (!Array.isArray(value)) {
|
|
42
|
+
throw new Error(`${op} expects array`);
|
|
43
|
+
}
|
|
44
|
+
const placeholders = value.map(v => this.ctx.add(v)).join(', ');
|
|
45
|
+
this.add(`${key} ${op} (${placeholders})`);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case 'BETWEEN': {
|
|
49
|
+
if (!Array.isArray(value) || value.length !== 2) {
|
|
50
|
+
throw new Error('BETWEEN expects [min,max]');
|
|
51
|
+
}
|
|
52
|
+
const p1 = this.ctx.add(value[0]);
|
|
53
|
+
const p2 = this.ctx.add(value[1]);
|
|
54
|
+
this.add(`${key} BETWEEN ${p1} AND ${p2}`);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
case 'EXISTS': {
|
|
58
|
+
if (!(value instanceof QueryBuilder)) {
|
|
59
|
+
throw new Error('EXISTS expects QueryBuilder');
|
|
60
|
+
}
|
|
61
|
+
const { query, params } = value.build();
|
|
62
|
+
params.forEach(p => this.ctx.add(p));
|
|
63
|
+
this.add(`EXISTS (${query})`);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
default: {
|
|
67
|
+
const placeholder = this.ctx.add(value);
|
|
68
|
+
this.add(`${key} ${op} ${placeholder}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
add(expression, type = 'AND') {
|
|
73
|
+
this.parts.push({ type, expression });
|
|
74
|
+
}
|
|
41
75
|
raw(expression) {
|
|
42
|
-
this.
|
|
76
|
+
this.add(expression);
|
|
43
77
|
return this;
|
|
44
78
|
}
|
|
45
|
-
/**
|
|
46
|
-
* Adds a group of conditions connected by AND.
|
|
47
|
-
* @param cb - A callback function that receives a nested ConditionBuilder to define conditions within the group.
|
|
48
|
-
* @returns The current ConditionBuilder instance.
|
|
49
|
-
*/
|
|
50
79
|
andGroup(cb) {
|
|
51
80
|
const nested = new ConditionBuilder(this.ctx);
|
|
52
81
|
cb(nested);
|
|
53
82
|
const built = nested.build();
|
|
54
|
-
if (built)
|
|
55
|
-
this
|
|
56
|
-
}
|
|
83
|
+
if (!built)
|
|
84
|
+
return this;
|
|
85
|
+
this.add(`(${built.replace(/^WHERE\s/, '')})`, 'AND');
|
|
57
86
|
return this;
|
|
58
87
|
}
|
|
59
|
-
/**
|
|
60
|
-
* Adds a group of conditions connected by OR.
|
|
61
|
-
* @param cb - A callback function that receives a nested ConditionBuilder to define conditions within the group.
|
|
62
|
-
* @returns The current ConditionBuilder instance.
|
|
63
|
-
*/
|
|
64
88
|
orGroup(cb) {
|
|
65
89
|
const nested = new ConditionBuilder(this.ctx);
|
|
66
90
|
cb(nested);
|
|
67
91
|
const built = nested.build();
|
|
68
|
-
if (built)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
92
|
+
if (!built)
|
|
93
|
+
return this;
|
|
94
|
+
this.add(`(${built.replace(/^WHERE\s/, '')})`, 'OR');
|
|
72
95
|
return this;
|
|
73
96
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
97
|
+
clone() {
|
|
98
|
+
const cloned = new ConditionBuilder(this.ctx);
|
|
99
|
+
cloned.parts = this.parts.map(p => ({
|
|
100
|
+
...p
|
|
101
|
+
}));
|
|
102
|
+
return cloned;
|
|
103
|
+
}
|
|
79
104
|
build(prefix = 'WHERE') {
|
|
80
105
|
if (!this.parts.length)
|
|
81
106
|
return '';
|
|
82
|
-
|
|
107
|
+
let sql = '';
|
|
83
108
|
this.parts.forEach((part, index) => {
|
|
84
109
|
if (index === 0) {
|
|
85
|
-
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
if (part.startsWith('OR ')) {
|
|
89
|
-
normalized.push(part);
|
|
110
|
+
sql += part.expression;
|
|
90
111
|
}
|
|
91
112
|
else {
|
|
92
|
-
|
|
113
|
+
sql += ` ${part.type} ${part.expression}`;
|
|
93
114
|
}
|
|
94
115
|
});
|
|
95
|
-
return `${prefix} ${
|
|
116
|
+
return `${prefix} ${sql}`;
|
|
96
117
|
}
|
|
97
118
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import ConditionBuilder from './ConditionBuilder';
|
|
2
2
|
import QueryExecutor from '../core/QueryExecutor';
|
|
3
3
|
import { Dialect } from '../dialects/Dialect';
|
|
4
|
+
type WhereInput<T> = Partial<T> | Record<string, any> | Array<Record<string, any>>;
|
|
4
5
|
/**
|
|
5
6
|
* A fluent SQL query builder for constructing and executing database queries.
|
|
6
7
|
* @template T The expected type of the results.
|
|
@@ -66,7 +67,7 @@ export default class QueryBuilder<T = any> {
|
|
|
66
67
|
* @param fields - An array of field names or keys of T.
|
|
67
68
|
* @returns The current QueryBuilder instance.
|
|
68
69
|
*/
|
|
69
|
-
select(fields: (keyof T | string)[]): this;
|
|
70
|
+
select(fields: (keyof T | string)[] | keyof T | string): this;
|
|
70
71
|
/**
|
|
71
72
|
* Adds a join clause to the query.
|
|
72
73
|
* @param type - The type of join (INNER, LEFT, RIGHT).
|
|
@@ -105,7 +106,7 @@ export default class QueryBuilder<T = any> {
|
|
|
105
106
|
* @param obj - An object where keys are column names and values are the desired values.
|
|
106
107
|
* @returns The current QueryBuilder instance.
|
|
107
108
|
*/
|
|
108
|
-
where(obj:
|
|
109
|
+
where(obj: WhereInput<T>): this;
|
|
109
110
|
/**
|
|
110
111
|
* Adds a raw WHERE expression.
|
|
111
112
|
* @param expression - The raw SQL expression for the WHERE clause.
|
|
@@ -187,7 +188,7 @@ export default class QueryBuilder<T = any> {
|
|
|
187
188
|
*/
|
|
188
189
|
build(): {
|
|
189
190
|
query: string;
|
|
190
|
-
params: any[];
|
|
191
|
+
params: readonly (string | number | boolean | any[] | Date | Buffer<ArrayBufferLike> | null)[];
|
|
191
192
|
};
|
|
192
193
|
/**
|
|
193
194
|
* Executes the built SQL query and returns the results.
|
|
@@ -195,3 +196,4 @@ export default class QueryBuilder<T = any> {
|
|
|
195
196
|
*/
|
|
196
197
|
execute(): Promise<T[]>;
|
|
197
198
|
}
|
|
199
|
+
export {};
|
|
@@ -47,7 +47,8 @@ export default class QueryBuilder {
|
|
|
47
47
|
* @returns The current QueryBuilder instance.
|
|
48
48
|
*/
|
|
49
49
|
select(fields) {
|
|
50
|
-
|
|
50
|
+
const normalized = Array.isArray(fields) ? fields : [fields];
|
|
51
|
+
this.fields = normalized.map(String);
|
|
51
52
|
return this;
|
|
52
53
|
}
|
|
53
54
|
/**
|
|
@@ -224,6 +225,9 @@ export default class QueryBuilder {
|
|
|
224
225
|
qb.limitCount = this.limitCount;
|
|
225
226
|
qb.offsetCount = this.offsetCount;
|
|
226
227
|
qb.ctes = [...this.ctes];
|
|
228
|
+
qb.condition = this.condition.clone();
|
|
229
|
+
qb.havingCondition = this.havingCondition.clone();
|
|
230
|
+
qb.ctx = this.ctx.clone();
|
|
227
231
|
return qb;
|
|
228
232
|
}
|
|
229
233
|
/**
|