@sqb/builder 5.0.7 → 6.0.0
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 +33 -17
- package/package.json +1 -1
- package/serialize-context.d.ts +1 -1
- package/serialize-context.js +23 -3
- package/sql/elements/field.js +1 -1
- package/sql/elements/min.d.ts +0 -1
- package/sql/elements/min.js +1 -4
- package/sql/insert.d.ts +1 -0
- package/sql/insert.js +4 -1
- package/sql/operators/in.js +7 -2
- package/sql/operators/logical-operator.js +6 -3
- package/sql/update.d.ts +1 -0
- package/sql/update.js +4 -1
package/README.md
CHANGED
|
@@ -6,16 +6,40 @@
|
|
|
6
6
|
|
|
7
7
|
[![NPM Version][npm-image]][npm-url]
|
|
8
8
|
[![NPM Downloads][downloads-image]][downloads-url]
|
|
9
|
-
[![
|
|
9
|
+
[![CI Tests][ci-test-image]][ci-test-url]
|
|
10
10
|
[![Test Coverage][coveralls-image]][coveralls-url]
|
|
11
|
-
[![Dependencies][dependencies-image]][dependencies-url]
|
|
12
|
-
[![DevDependencies][devdependencies-image]][devdependencies-url]
|
|
13
|
-
[![Package Quality][quality-image]][quality-url]
|
|
14
11
|
|
|
15
12
|
## About SQB
|
|
16
13
|
|
|
17
14
|
SQB is an extensible, multi-dialect SQL query builder and Database connection wrapper for NodeJS.
|
|
18
15
|
|
|
16
|
+
## About @sqb/builder
|
|
17
|
+
|
|
18
|
+
`@sqb/builder` is the query construction and serialization core the whole SQB stack is built on.
|
|
19
|
+
It lets you compose `Select`, `Insert`, `Update` and `Delete` statements as plain JavaScript/
|
|
20
|
+
TypeScript objects — with type-checked columns, joins, conditions and parameters — and turns them
|
|
21
|
+
into the correct SQL text for whichever database you're targeting, without depending on any driver
|
|
22
|
+
or network connection itself.
|
|
23
|
+
|
|
24
|
+
Serialization is dialect-driven: each target database (Postgres, MySQL, MariaDB, Oracle, SQL
|
|
25
|
+
Server, SQLite, ...) is a pluggable `SerializerExtension` that can override how any part of a query
|
|
26
|
+
is rendered — identifier quoting, `LIMIT`/`OFFSET` syntax, boolean literals, `RETURNING` support,
|
|
27
|
+
and more — while everything it doesn't override falls through to sensible defaults. This is what
|
|
28
|
+
lets [`@sqb/connect`](../connect) and the various dialect/adapter packages share one query-building
|
|
29
|
+
API across every supported database.
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { Select, Eq } from '@sqb/builder';
|
|
33
|
+
|
|
34
|
+
const query = Select('id', 'given_name', 'family_name')
|
|
35
|
+
.from('customers')
|
|
36
|
+
.where(Eq('active', true))
|
|
37
|
+
.orderBy('id')
|
|
38
|
+
.limit(10);
|
|
39
|
+
|
|
40
|
+
const { sql, params } = query.generate({ dialect: 'postgres' });
|
|
41
|
+
```
|
|
42
|
+
|
|
19
43
|
## Main goals
|
|
20
44
|
|
|
21
45
|
- Single code base for any sql based database
|
|
@@ -39,7 +63,7 @@ $ npm install @sqb/builder --save
|
|
|
39
63
|
|
|
40
64
|
## Node Compatibility
|
|
41
65
|
|
|
42
|
-
- node >=
|
|
66
|
+
- node >= 20.x
|
|
43
67
|
|
|
44
68
|
### License
|
|
45
69
|
|
|
@@ -47,17 +71,9 @@ SQB is available under [MIT](LICENSE) license.
|
|
|
47
71
|
|
|
48
72
|
[npm-image]: https://img.shields.io/npm/v/@sqb/builder.svg
|
|
49
73
|
[npm-url]: https://npmjs.org/package/@sqb/builder
|
|
50
|
-
[travis-image]: https://img.shields.io/travis/sqbjs/@sqb/builder/master.svg
|
|
51
|
-
[travis-url]: https://travis-ci.org/sqbjs/@sqb/builder
|
|
52
|
-
[coveralls-image]: https://img.shields.io/coveralls/sqbjs/@sqb/builder/master.svg
|
|
53
|
-
[coveralls-url]: https://coveralls.io/r/sqbjs/@sqb/builder
|
|
54
74
|
[downloads-image]: https://img.shields.io/npm/dm/@sqb/builder.svg
|
|
55
75
|
[downloads-url]: https://npmjs.org/package/@sqb/builder
|
|
56
|
-
[
|
|
57
|
-
[
|
|
58
|
-
[
|
|
59
|
-
[
|
|
60
|
-
[devdependencies-image]: https://david-dm.org/sqbjs/@sqb/builder/dev-status.svg
|
|
61
|
-
[devdependencies-url]: https://david-dm.org/sqbjs/@sqb/builder?type=dev
|
|
62
|
-
[quality-image]: http://npm.packagequality.com/shield/@sqb/builder.png
|
|
63
|
-
[quality-url]: http://packagequality.com/#?package=@sqb/builder
|
|
76
|
+
[ci-test-image]: https://github.com/panates/sqb/actions/workflows/test.yml/badge.svg
|
|
77
|
+
[ci-test-url]: https://github.com/panates/sqb/actions/workflows/test.yml
|
|
78
|
+
[coveralls-image]: https://coveralls.io/repos/github/sqbjs/sqb/badge.svg?branch=master
|
|
79
|
+
[coveralls-url]: https://coveralls.io/github/sqbjs/sqb?branch=master
|
package/package.json
CHANGED
package/serialize-context.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Query } from './sql/index.js';
|
|
|
2
2
|
import type { DefaultSerializeFunction, GenerateOptions, ParamOptions } from './types.js';
|
|
3
3
|
export declare class SerializeContext implements GenerateOptions {
|
|
4
4
|
readonly rootQuery: Query;
|
|
5
|
-
readonly reservedWords: string
|
|
5
|
+
readonly reservedWords: Set<string>;
|
|
6
6
|
dialect?: string;
|
|
7
7
|
prettyPrint?: boolean;
|
|
8
8
|
params?: Record<string, any>;
|
package/serialize-context.js
CHANGED
|
@@ -4,7 +4,7 @@ import { Query } from './sql/index.js';
|
|
|
4
4
|
import { isLogicalOperator, isQuery, isSqlElement } from './type-guards.js';
|
|
5
5
|
export class SerializeContext {
|
|
6
6
|
rootQuery;
|
|
7
|
-
reservedWords = [
|
|
7
|
+
reservedWords = new Set([
|
|
8
8
|
'schema',
|
|
9
9
|
'table',
|
|
10
10
|
'field',
|
|
@@ -61,7 +61,27 @@ export class SerializeContext {
|
|
|
61
61
|
'foreign',
|
|
62
62
|
'user',
|
|
63
63
|
'password',
|
|
64
|
-
|
|
64
|
+
'all',
|
|
65
|
+
'as',
|
|
66
|
+
'asc',
|
|
67
|
+
'case',
|
|
68
|
+
'cast',
|
|
69
|
+
'check',
|
|
70
|
+
'column',
|
|
71
|
+
'desc',
|
|
72
|
+
'else',
|
|
73
|
+
'end',
|
|
74
|
+
'for',
|
|
75
|
+
'in',
|
|
76
|
+
'into',
|
|
77
|
+
'is',
|
|
78
|
+
'on',
|
|
79
|
+
'then',
|
|
80
|
+
'to',
|
|
81
|
+
'union',
|
|
82
|
+
'unique',
|
|
83
|
+
'when',
|
|
84
|
+
]);
|
|
65
85
|
dialect;
|
|
66
86
|
prettyPrint;
|
|
67
87
|
params;
|
|
@@ -182,7 +202,7 @@ export class SerializeContext {
|
|
|
182
202
|
isReservedWord(s) {
|
|
183
203
|
if (!s)
|
|
184
204
|
return false;
|
|
185
|
-
if (this.reservedWords.
|
|
205
|
+
if (this.reservedWords.has(s.toLowerCase()))
|
|
186
206
|
return true;
|
|
187
207
|
for (const ext of SerializerRegistry.items()) {
|
|
188
208
|
if (ext.dialect === this.dialect && ext.isReservedWord) {
|
package/sql/elements/field.js
CHANGED
|
@@ -19,7 +19,7 @@ class FieldClass extends BaseField {
|
|
|
19
19
|
const prefix = ctx.escapeReserved(this._schema ? this._schema + '.' : '') +
|
|
20
20
|
(this._table ? this._table + '.' : '');
|
|
21
21
|
return (prefix +
|
|
22
|
-
(
|
|
22
|
+
(o.isReservedWord ? '"' + this._field + '"' : this._field) +
|
|
23
23
|
(this._alias ? ' as ' + this._alias : ''));
|
|
24
24
|
});
|
|
25
25
|
}
|
package/sql/elements/min.d.ts
CHANGED
package/sql/elements/min.js
CHANGED
|
@@ -4,10 +4,6 @@ import { SerializeContext } from '../../serialize-context.js';
|
|
|
4
4
|
class MinClass extends SqlElement {
|
|
5
5
|
_expression;
|
|
6
6
|
_alias;
|
|
7
|
-
constructor(expression) {
|
|
8
|
-
super();
|
|
9
|
-
this._expression = expression;
|
|
10
|
-
}
|
|
11
7
|
get _type() {
|
|
12
8
|
return SerializationType.MIN_STATEMENT;
|
|
13
9
|
}
|
|
@@ -38,6 +34,7 @@ class MinClass extends SqlElement {
|
|
|
38
34
|
export const Min = function (expression) {
|
|
39
35
|
if (!(this instanceof Min))
|
|
40
36
|
return new Min(expression);
|
|
37
|
+
SqlElement.call(this);
|
|
41
38
|
this._expression = expression;
|
|
42
39
|
};
|
|
43
40
|
Min.prototype = MinClass.prototype;
|
package/sql/insert.d.ts
CHANGED
package/sql/insert.js
CHANGED
|
@@ -23,6 +23,9 @@ class InsertClass extends ReturningQuery {
|
|
|
23
23
|
values: this.__serializeValues(ctx),
|
|
24
24
|
returning: this.__serializeReturning(ctx),
|
|
25
25
|
};
|
|
26
|
+
return ctx.serialize(this._type, o, () => this.__defaultSerialize(ctx, o));
|
|
27
|
+
}
|
|
28
|
+
__defaultSerialize(ctx, o) {
|
|
26
29
|
let out = 'insert into ' +
|
|
27
30
|
o.table +
|
|
28
31
|
'\n\t(' +
|
|
@@ -53,7 +56,7 @@ class InsertClass extends ReturningQuery {
|
|
|
53
56
|
}
|
|
54
57
|
else
|
|
55
58
|
arr = Object.keys(this._input);
|
|
56
|
-
return ctx.serialize(SerializationType.INSERT_QUERY_COLUMNS, arr, () => printArray(arr));
|
|
59
|
+
return ctx.serialize(SerializationType.INSERT_QUERY_COLUMNS, arr, () => printArray(arr.map(c => ctx.escapeReserved(c))));
|
|
57
60
|
}
|
|
58
61
|
/**
|
|
59
62
|
*
|
package/sql/operators/in.js
CHANGED
|
@@ -4,8 +4,13 @@ import { isSqlElement } from '../../type-guards.js';
|
|
|
4
4
|
import { CompOperator } from './comp-operator.js';
|
|
5
5
|
class InClass extends CompOperator {
|
|
6
6
|
_serialize(ctx) {
|
|
7
|
-
if (Array.isArray(this._right) && !this._right.length)
|
|
8
|
-
|
|
7
|
+
if (Array.isArray(this._right) && !this._right.length) {
|
|
8
|
+
// Nothing can be IN an empty list (always false), and everything is
|
|
9
|
+
// NOT IN an empty list (always true). Emit a self-contained literal
|
|
10
|
+
// instead of dropping the condition, which would silently invert its
|
|
11
|
+
// meaning into "no filter at all".
|
|
12
|
+
return this._operatorType === OperatorType.notIn ? '1=1' : '1=0';
|
|
13
|
+
}
|
|
9
14
|
return super._serialize(ctx);
|
|
10
15
|
}
|
|
11
16
|
}
|
|
@@ -29,10 +29,13 @@ function wrapObject(obj) {
|
|
|
29
29
|
const m = n.match(COMPARE_LEFT_PATTERN);
|
|
30
30
|
if (!m)
|
|
31
31
|
throw new TypeError(`"${n}" is not a valid expression definition`);
|
|
32
|
-
|
|
32
|
+
// A bare array value with no explicit operator (e.g. {status: [...]})
|
|
33
|
+
// means "one of these values", not literal equality to an array.
|
|
34
|
+
const opKey = m[2] || (Array.isArray(v) ? 'in' : 'eq');
|
|
35
|
+
fn = registeredOperators[opKey];
|
|
33
36
|
if (!fn)
|
|
34
|
-
throw new Error(`Unknown operator "${
|
|
35
|
-
const inst = fn(m[1],
|
|
37
|
+
throw new Error(`Unknown operator "${opKey}"`);
|
|
38
|
+
const inst = fn(m[1], v);
|
|
36
39
|
result.push(inst);
|
|
37
40
|
}
|
|
38
41
|
}
|
package/sql/update.d.ts
CHANGED
package/sql/update.js
CHANGED
|
@@ -33,6 +33,9 @@ class UpdateClass extends ReturningQuery {
|
|
|
33
33
|
where: this.__serializeWhere(ctx),
|
|
34
34
|
returning: this.__serializeReturning(ctx),
|
|
35
35
|
};
|
|
36
|
+
return ctx.serialize(this._type, o, () => this.__defaultSerialize(ctx, o));
|
|
37
|
+
}
|
|
38
|
+
__defaultSerialize(ctx, o) {
|
|
36
39
|
let out = 'update ' + o.table + ' set \n\t' + o.values + '\b';
|
|
37
40
|
if (o.where)
|
|
38
41
|
out += '\n' + o.where;
|
|
@@ -54,7 +57,7 @@ class UpdateClass extends ReturningQuery {
|
|
|
54
57
|
});
|
|
55
58
|
}
|
|
56
59
|
return ctx.serialize(SerializationType.UPDATE_QUERY_VALUES, arr, () => {
|
|
57
|
-
const a = arr.map(o => o.field + ' = ' + o.value);
|
|
60
|
+
const a = arr.map(o => ctx.escapeReserved(o.field) + ' = ' + o.value);
|
|
58
61
|
return printArray(a, ',');
|
|
59
62
|
});
|
|
60
63
|
}
|