@sqb/mssql-dialect 5.0.6 → 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 CHANGED
@@ -6,30 +6,25 @@
6
6
 
7
7
  [![NPM Version][npm-image]][npm-url]
8
8
  [![NPM Downloads][downloads-image]][downloads-url]
9
- [![Build Status][travis-image]][travis-url]
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
 
19
- ## Main goals
16
+ ## About @sqb/mssql-dialect
20
17
 
21
- - Single code base for any sql based database
22
- - Powerful and simplified query coding scheme
23
- - Fast applications with low memory requirements
24
- - Let applications work with large data tables efficiently
25
- - Support latest JavaScript language standards
26
- - Lightweight and extensible framework.
18
+ This package registers the `mssql` SQL serialization dialect (a `SerializerExtension` for
19
+ [`@sqb/builder`](../builder)) used by [`@sqb/mssql`](../mssql). It is loaded automatically when
20
+ `@sqb/mssql` is imported, so you normally don't need to depend on it directly.
27
21
 
28
- You can report bugs and discuss features on the [GitHub issues](https://github.com/sqbjs/sqb/issues) page
29
-
30
- Thanks to all of the great [contributions](https://github.com/sqbjs/sqb/graphs/contributors) to the project.
31
-
32
- You may want to check detailed [DOCUMENTATION](https://sqbjs.github.io/sqb/)
22
+ It adapts query output to T-SQL: `@name` parameter placeholders instead of `:name`, `OFFSET ...
23
+ ROWS [FETCH NEXT ... ROWS ONLY]` instead of `LIMIT`/`OFFSET` (SQL Server requires an `ORDER BY`
24
+ before `OFFSET`, which the dialect adds automatically when a query needs pagination but has none),
25
+ and SQL Server's own reserved-word list. `RETURNING` is rewritten into SQL Server's native `OUTPUT
26
+ INSERTED.col` / `OUTPUT DELETED.col` clause rather than being stripped, since SQL Server has no
27
+ `RETURNING` keyword but its `OUTPUT` clause achieves the same thing without a follow-up `SELECT`.
33
28
 
34
29
  ## Installation
35
30
 
@@ -39,7 +34,7 @@ $ npm install @sqb/mssql-dialect --save
39
34
 
40
35
  ## Node Compatibility
41
36
 
42
- - node >= 16.x
37
+ - node >= 20.x
43
38
 
44
39
  ### License
45
40
 
@@ -47,17 +42,9 @@ SQB is available under [MIT](LICENSE) license.
47
42
 
48
43
  [npm-image]: https://img.shields.io/npm/v/@sqb/mssql-dialect.svg
49
44
  [npm-url]: https://npmjs.org/package/@sqb/mssql-dialect
50
- [travis-image]: https://img.shields.io/travis/sqbjs/@sqb/mssql-dialect/master.svg
51
- [travis-url]: https://travis-ci.org/sqbjs/@sqb/mssql-dialect
52
- [coveralls-image]: https://img.shields.io/coveralls/sqbjs/@sqb/mssql-dialect/master.svg
53
- [coveralls-url]: https://coveralls.io/r/sqbjs/@sqb/mssql-dialect
54
45
  [downloads-image]: https://img.shields.io/npm/dm/@sqb/mssql-dialect.svg
55
46
  [downloads-url]: https://npmjs.org/package/@sqb/mssql-dialect
56
- [gitter-image]: https://badges.gitter.im/sqbjs/@sqb/mssql-dialect.svg
57
- [gitter-url]: https://gitter.im/sqbjs/@sqb/mssql-dialect?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
58
- [dependencies-image]: https://david-dm.org/sqbjs/@sqb/mssql-dialect/status.svg
59
- [dependencies-url]: https://david-dm.org/sqbjs/@sqb/mssql-dialect
60
- [devdependencies-image]: https://david-dm.org/sqbjs/@sqb/mssql-dialect/dev-status.svg
61
- [devdependencies-url]: https://david-dm.org/sqbjs/@sqb/mssql-dialect?type=dev
62
- [quality-image]: http://npm.packagequality.com/shield/@sqb/mssql-dialect.png
63
- [quality-url]: http://packagequality.com/#?package=@sqb/mssql-dialect
47
+ [ci-test-image]: https://github.com/panates/sqb/actions/workflows/test.yml/badge.svg
48
+ [ci-test-url]: https://github.com/panates/sqb/actions/workflows/test.yml
49
+ [coveralls-image]: https://coveralls.io/repos/github/sqbjs/sqb/badge.svg?branch=master
50
+ [coveralls-url]: https://coveralls.io/github/sqbjs/sqb?branch=master
@@ -1,8 +1,13 @@
1
1
  import { type DefaultSerializeFunction, SerializationType, SerializeContext, type SerializerExtension } from '@sqb/builder';
2
2
  export declare class MSSqlSerializer implements SerializerExtension {
3
3
  dialect: string;
4
- isReservedWord(ctx: any, s: any): any;
4
+ reservedWords: Set<string>;
5
+ isReservedWord(_: any, s: any): any;
5
6
  serialize(ctx: SerializeContext, type: SerializationType | string, o: any, defFn: DefaultSerializeFunction): string | undefined;
6
7
  private _serializeSelect;
8
+ private _serializeComparison;
9
+ private _serializeBooleanValue;
10
+ private _serializeStringValue;
11
+ private _serializeReturning;
7
12
  private _serializeParameter;
8
13
  }
@@ -1,14 +1,151 @@
1
- import { SerializationType, SerializeContext, } from '@sqb/builder';
2
- const reservedWords = ['comment'];
1
+ import { isParam, OperatorType, SerializationType, SerializeContext, } from '@sqb/builder';
2
+ // T-SQL reserved keywords (Reserved Keywords (Transact-SQL), learn.microsoft.com)
3
+ // that are not already covered by SerializeContext's base reservedWords list.
4
+ const reservedWords = new Set([
5
+ 'any',
6
+ 'backup',
7
+ 'begin',
8
+ 'break',
9
+ 'browse',
10
+ 'bulk',
11
+ 'checkpoint',
12
+ 'close',
13
+ 'clustered',
14
+ 'coalesce',
15
+ 'collate',
16
+ 'comment',
17
+ 'commit',
18
+ 'compute',
19
+ 'contains',
20
+ 'containstable',
21
+ 'continue',
22
+ 'convert',
23
+ 'cross',
24
+ 'current',
25
+ 'current_date',
26
+ 'current_time',
27
+ 'current_timestamp',
28
+ 'current_user',
29
+ 'cursor',
30
+ 'database',
31
+ 'dbcc',
32
+ 'deallocate',
33
+ 'declare',
34
+ 'deny',
35
+ 'disk',
36
+ 'distributed',
37
+ 'double',
38
+ 'dump',
39
+ 'errlvl',
40
+ 'escape',
41
+ 'except',
42
+ 'exec',
43
+ 'execute',
44
+ 'exists',
45
+ 'exit',
46
+ 'external',
47
+ 'fetch',
48
+ 'file',
49
+ 'fillfactor',
50
+ 'freetext',
51
+ 'freetexttable',
52
+ 'function',
53
+ 'goto',
54
+ 'grant',
55
+ 'holdlock',
56
+ 'identity',
57
+ 'identity_insert',
58
+ 'identitycol',
59
+ 'if',
60
+ 'intersect',
61
+ 'kill',
62
+ 'lineno',
63
+ 'load',
64
+ 'national',
65
+ 'nocheck',
66
+ 'nonclustered',
67
+ 'nullif',
68
+ 'of',
69
+ 'off',
70
+ 'offsets',
71
+ 'open',
72
+ 'opendatasource',
73
+ 'openquery',
74
+ 'openrowset',
75
+ 'openxml',
76
+ 'option',
77
+ 'over',
78
+ 'percent',
79
+ 'pivot',
80
+ 'plan',
81
+ 'precision',
82
+ 'print',
83
+ 'proc',
84
+ 'procedure',
85
+ 'public',
86
+ 'raiserror',
87
+ 'read',
88
+ 'readtext',
89
+ 'reconfigure',
90
+ 'replication',
91
+ 'restore',
92
+ 'restrict',
93
+ 'return',
94
+ 'revert',
95
+ 'revoke',
96
+ 'rollback',
97
+ 'rowcount',
98
+ 'rowguidcol',
99
+ 'rule',
100
+ 'save',
101
+ 'securityaudit',
102
+ 'semantickeyphrasetable',
103
+ 'semanticsimilaritydetailstable',
104
+ 'semanticsimilaritytable',
105
+ 'session_user',
106
+ 'set',
107
+ 'setuser',
108
+ 'shutdown',
109
+ 'some',
110
+ 'statistics',
111
+ 'system_user',
112
+ 'tablesample',
113
+ 'textsize',
114
+ 'top',
115
+ 'tran',
116
+ 'transaction',
117
+ 'trigger',
118
+ 'truncate',
119
+ 'try_convert',
120
+ 'tsequal',
121
+ 'unpivot',
122
+ 'updatetext',
123
+ 'use',
124
+ 'values',
125
+ 'varying',
126
+ 'view',
127
+ 'waitfor',
128
+ 'while',
129
+ 'writetext',
130
+ ]);
3
131
  export class MSSqlSerializer {
4
132
  dialect = 'mssql';
5
- isReservedWord(ctx, s) {
6
- return (s && typeof s === 'string' && reservedWords.includes(s.toLowerCase()));
133
+ reservedWords = reservedWords;
134
+ isReservedWord(_, s) {
135
+ return s && typeof s === 'string' && reservedWords.has(s.toLowerCase());
7
136
  }
8
137
  serialize(ctx, type, o, defFn) {
9
138
  switch (type) {
10
139
  case SerializationType.SELECT_QUERY:
11
140
  return this._serializeSelect(ctx, o, defFn);
141
+ case SerializationType.COMPARISON_EXPRESSION:
142
+ return this._serializeComparison(ctx, o, defFn);
143
+ case SerializationType.BOOLEAN_VALUE:
144
+ return this._serializeBooleanValue(ctx, o);
145
+ case SerializationType.STRING_VALUE:
146
+ return this._serializeStringValue(ctx, o, defFn);
147
+ case SerializationType.RETURNING_BLOCK:
148
+ return this._serializeReturning();
12
149
  case SerializationType.EXTERNAL_PARAMETER:
13
150
  return this._serializeParameter(ctx, o, defFn);
14
151
  default:
@@ -19,13 +156,95 @@ export class MSSqlSerializer {
19
156
  let out = defFn(ctx, o);
20
157
  const limit = o.limit || 0;
21
158
  const offset = Math.max(o.offset || 0, 0);
22
- if (offset)
159
+ if (limit || offset) {
160
+ // OFFSET/FETCH is only valid when preceded by an ORDER BY clause.
161
+ if (!o.orderBy)
162
+ out += '\nORDER BY (SELECT NULL)';
23
163
  out += '\nOFFSET ' + offset + ' ROWS';
24
- if (limit)
25
- out += (!offset ? '\n' : ' ') + 'FETCH NEXT ' + limit + ' ROWS ONLY';
164
+ if (limit)
165
+ out += ' FETCH NEXT ' + limit + ' ROWS ONLY';
166
+ }
26
167
  return out;
27
168
  }
169
+ _serializeComparison(ctx, o, defFn) {
170
+ if (isParam(o.orgRight)) {
171
+ const n = o.orgRight._name;
172
+ const v = ctx.orgParams?.[n];
173
+ if (Array.isArray(v)) {
174
+ o.right.isParam = false;
175
+ if (o.operatorType === 'eq')
176
+ return defFn(ctx, {
177
+ ...o,
178
+ operatorType: OperatorType.in,
179
+ symbol: 'in',
180
+ });
181
+ if (o.operatorType === 'ne')
182
+ return defFn(ctx, {
183
+ ...o,
184
+ operatorType: OperatorType.notIn,
185
+ symbol: 'not in',
186
+ });
187
+ }
188
+ }
189
+ if ((o.right?.expression && o.right?.expression === 'null') ||
190
+ (o.right &&
191
+ o.right?.value == null &&
192
+ (!o.right.expression || o.right.expression.startsWith('@')))) {
193
+ if (o.right.expression?.startsWith('@')) {
194
+ const s = o.right.expression.substring(1);
195
+ if (ctx.params)
196
+ delete ctx.params[s];
197
+ if (ctx.preparedParams)
198
+ delete ctx.preparedParams[s];
199
+ if (ctx.paramOptions)
200
+ delete ctx.paramOptions[s];
201
+ o.right.expression = 'null';
202
+ o.right.isParam = false;
203
+ }
204
+ if (o.operatorType === 'eq')
205
+ return defFn(ctx, {
206
+ ...o,
207
+ operatorType: OperatorType.is,
208
+ symbol: 'is',
209
+ });
210
+ if (o.operatorType === 'ne')
211
+ return defFn(ctx, {
212
+ ...o,
213
+ operatorType: OperatorType.isNot,
214
+ symbol: 'is not',
215
+ });
216
+ }
217
+ return defFn(ctx, o);
218
+ }
219
+ _serializeBooleanValue(_ctx, o) {
220
+ return o == null ? 'null' : o ? '1' : '0';
221
+ }
222
+ _serializeStringValue(ctx, o, defFn) {
223
+ // T-SQL's implicit conversion of ISO 8601 strings carrying a timezone
224
+ // offset (e.g. from JSON test fixtures) into `datetime`/`date` is
225
+ // unreliable, so normalize to 'yyyy-mm-dd hh:mm:ss' ourselves.
226
+ if (typeof o === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(o)) {
227
+ const d = new Date(o);
228
+ if (!isNaN(d.getTime()))
229
+ return ctx.dateToSQL(d);
230
+ }
231
+ return defFn(ctx, o);
232
+ }
233
+ _serializeReturning() {
234
+ // T-SQL has no RETURNING clause; the OUTPUT clause it uses instead
235
+ // must be positioned before WHERE/VALUES rather than at the end of
236
+ // the statement, so it is injected at the connection layer instead.
237
+ return '';
238
+ }
28
239
  _serializeParameter(ctx, o, defFn) {
240
+ if (ctx.rootQuery._type === SerializationType.SELECT_QUERY ||
241
+ ctx.rootQuery._type === SerializationType.DELETE_QUERY) {
242
+ const v = ctx.params?.[o.name];
243
+ if (Array.isArray(v)) {
244
+ delete ctx.params?.[o.name];
245
+ return ctx.anyToSQL(v);
246
+ }
247
+ }
29
248
  defFn(ctx, o);
30
249
  return '@' + o.name;
31
250
  }
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@sqb/mssql-dialect",
3
3
  "description": "SQB serialization extension for MS-SQL database",
4
- "version": "5.0.6",
4
+ "version": "6.0.0",
5
5
  "author": "Panates",
6
6
  "license": "Apache-2.0",
7
7
  "dependencies": {
8
8
  "tslib": "^2.8.1"
9
9
  },
10
10
  "peerDependencies": {
11
- "@sqb/builder": "^5.0.6"
11
+ "@sqb/builder": "^6.0.0"
12
12
  },
13
13
  "type": "module",
14
14
  "module": "./index.js",