@malloydata/malloy-filter 0.0.237-dev250222025222 → 0.0.237-dev250222205057

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.
Files changed (69) hide show
  1. package/dist/a_simple_parser.js +12 -9
  2. package/dist/a_simple_parser.js.map +1 -1
  3. package/dist/base_parser.d.ts +0 -2
  4. package/dist/base_parser.js.map +1 -1
  5. package/dist/boolean_parser.d.ts +2 -2
  6. package/dist/boolean_parser.js +3 -1
  7. package/dist/boolean_parser.js.map +1 -1
  8. package/dist/boolean_serializer.d.ts +4 -4
  9. package/dist/boolean_serializer.js +12 -4
  10. package/dist/boolean_serializer.js.map +1 -1
  11. package/dist/clause_types.d.ts +40 -47
  12. package/dist/date_parser.d.ts +13 -11
  13. package/dist/date_parser.js +192 -131
  14. package/dist/date_parser.js.map +1 -1
  15. package/dist/date_serializer.d.ts +6 -5
  16. package/dist/date_serializer.js +65 -77
  17. package/dist/date_serializer.js.map +1 -1
  18. package/dist/date_types.d.ts +75 -0
  19. package/dist/{filter_types.js → date_types.js} +1 -1
  20. package/dist/date_types.js.map +1 -0
  21. package/dist/generate_samples.js +162 -87
  22. package/dist/generate_samples.js.map +1 -1
  23. package/dist/number_parser.d.ts +2 -5
  24. package/dist/number_parser.js +10 -51
  25. package/dist/number_parser.js.map +1 -1
  26. package/dist/number_serializer.d.ts +4 -4
  27. package/dist/number_serializer.js +17 -14
  28. package/dist/number_serializer.js.map +1 -1
  29. package/dist/string_parser.d.ts +2 -2
  30. package/dist/string_parser.js +21 -21
  31. package/dist/string_parser.js.map +1 -1
  32. package/dist/string_serializer.d.ts +6 -5
  33. package/dist/string_serializer.js +38 -24
  34. package/dist/string_serializer.js.map +1 -1
  35. package/package.json +1 -2
  36. package/src/DEVELOPING.md +2 -5
  37. package/src/a_simple_parser.ts +12 -9
  38. package/src/base_parser.ts +0 -3
  39. package/src/boolean_parser.ts +10 -5
  40. package/src/boolean_serializer.ts +14 -7
  41. package/src/clause_types.ts +65 -108
  42. package/src/date_parser.ts +229 -192
  43. package/src/date_serializer.ts +59 -87
  44. package/src/date_types.ts +159 -0
  45. package/src/generate_samples.ts +178 -109
  46. package/src/number_parser.ts +14 -63
  47. package/src/number_serializer.ts +19 -20
  48. package/src/string_parser.ts +40 -27
  49. package/src/string_serializer.ts +58 -32
  50. package/tsconfig.json +1 -6
  51. package/dist/a_simple_serializer.d.ts +0 -1
  52. package/dist/a_simple_serializer.js +0 -31
  53. package/dist/a_simple_serializer.js.map +0 -1
  54. package/dist/base_serializer.d.ts +0 -6
  55. package/dist/base_serializer.js +0 -11
  56. package/dist/base_serializer.js.map +0 -1
  57. package/dist/filter_parser.d.ts +0 -12
  58. package/dist/filter_parser.js +0 -66
  59. package/dist/filter_parser.js.map +0 -1
  60. package/dist/filter_serializer.d.ts +0 -13
  61. package/dist/filter_serializer.js +0 -43
  62. package/dist/filter_serializer.js.map +0 -1
  63. package/dist/filter_types.d.ts +0 -10
  64. package/dist/filter_types.js.map +0 -1
  65. package/src/a_simple_serializer.ts +0 -40
  66. package/src/base_serializer.ts +0 -9
  67. package/src/filter_parser.ts +0 -68
  68. package/src/filter_serializer.ts +0 -49
  69. package/src/filter_types.ts +0 -12
@@ -1,20 +1,21 @@
1
- import {Clause} from './clause_types';
2
1
  import {SpecialToken, Tokenizer, TokenizerParams} from './tokenizer';
3
2
  import {BooleanParser} from './boolean_parser';
4
3
  import {StringParser} from './string_parser';
5
4
  import {NumberParser} from './number_parser';
6
5
  import {DateParser} from './date_parser';
7
- import {BaseParser} from './base_parser';
8
6
  import {BooleanSerializer} from './boolean_serializer';
9
7
  import {StringSerializer} from './string_serializer';
10
8
  import {NumberSerializer} from './number_serializer';
11
9
  import {DateSerializer} from './date_serializer';
12
- import {BaseSerializer} from './base_serializer';
10
+ import {BooleanClause, NumberClause, StringClause} from './clause_types';
11
+ import {DateClause} from './date_types';
12
+
13
+ type Clause = BooleanClause | DateClause | NumberClause | StringClause;
13
14
 
14
15
  const numberExamples = [
15
16
  '5',
16
17
  '!=5',
17
- '1, 3, null , 7',
18
+ '1, 3, 5, null',
18
19
  '<1, >=100 ',
19
20
  '>=1',
20
21
  ' <= 10 ',
@@ -69,9 +70,10 @@ const stringExamples = [
69
70
  const booleanExamples = [
70
71
  'true',
71
72
  'FALSE',
73
+ '=false',
72
74
  'null',
73
75
  '-NULL',
74
- ' True , faLSE,NULl,-null',
76
+ ' True , faLSE,=false,NULl,-null',
75
77
  "-'null'",
76
78
  '10',
77
79
  'nnull',
@@ -88,7 +90,7 @@ const dateExamples = [
88
90
  'before 3 days ago',
89
91
  'before 2025-08-30 08:30:20',
90
92
  'after 2025-10-05',
91
- '2025-08-30 12:00:00 to 2025-09-18 14:00:00',
93
+ '2025-08-30 12:00 to 2025-09-18 14:30',
92
94
  'this year',
93
95
  'next tuesday',
94
96
  '7 years from now',
@@ -103,11 +105,14 @@ const dateExamples = [
103
105
  'next week',
104
106
  'now',
105
107
  'now to next month',
108
+ 'null',
109
+ '-null,',
106
110
  ' yyesterday ', // Typo
107
111
  'before', // Bad syntax
108
112
  'for', // Bad syntax
109
113
  '7', // Bad syntax
110
114
  'from now', // Bad syntax
115
+ '2025-12-25 12:32:', // Bad syntax
111
116
  '',
112
117
  ];
113
118
 
@@ -207,77 +212,89 @@ function testTokenizerNumber() {
207
212
  }
208
213
  }
209
214
 
210
- function testParserSingle(
215
+ function testNumberParserSingle(
211
216
  str: string,
212
- parser: BaseParser,
213
217
  outputFormatter?: (clauses: Clause[]) => string
214
218
  ): void {
215
219
  console.log('Input: ', str);
216
- try {
217
- const response = parser.parse();
218
- // console.log('Tokens: ', parser.getTokens());
219
- if (response.clauses && response.clauses.length > 0) {
220
- if (outputFormatter) {
221
- console.log('Output: ', outputFormatter(response.clauses));
222
- } else {
223
- console.log('Output: ', ...response.clauses);
224
- }
225
- }
226
- if (response.errors && response.errors.length > 0) {
227
- console.log('Errors: ', ...response.errors);
228
- }
229
- } catch (ex: Error | unknown) {
230
- if (ex instanceof Error) console.error('Thrown Error: ', ex.message);
231
- else {
232
- console.error('Thrown Unknown error: ', ex);
220
+ const parser = new NumberParser(str);
221
+ const response = parser.parse();
222
+ // console.log('Tokens: ', parser.getTokens());
223
+ if (response.clauses && response.clauses.length > 0) {
224
+ if (outputFormatter) {
225
+ console.log('Output: ', outputFormatter(response.clauses));
226
+ } else {
227
+ console.log('Output: ', ...response.clauses);
233
228
  }
234
229
  }
230
+ if (response.errors && response.errors.length > 0) {
231
+ console.log('Errors: ', ...response.errors);
232
+ }
235
233
  console.log('');
236
234
  }
237
235
 
238
- function testSerializerRoundtrip(
236
+ function testNumberParser() {
237
+ for (const example of numberExamples) {
238
+ testNumberParserSingle(example);
239
+ }
240
+ }
241
+
242
+ function testStringParserSingle(
239
243
  str: string,
240
- parser: BaseParser,
241
- serializerFunc: (clauses: Clause[]) => BaseSerializer
244
+ outputFormatter?: (clauses: Clause[]) => string
242
245
  ): void {
243
- console.log('Input: ' + str);
244
- try {
245
- const response = parser.parse();
246
- // console.log('Clause: ', ...response.clauses, '\n');
247
- if (response.clauses && response.clauses.length > 0) {
248
- const result = serializerFunc(response.clauses || []).serialize();
249
- console.log('Output: ' + result);
250
- }
251
- if (response.errors && response.errors.length > 0) {
252
- console.log('Errors: ', ...response.errors);
253
- }
254
- } catch (ex: Error | unknown) {
255
- if (ex instanceof Error) console.error('Thrown Error: ', ex.message);
256
- else {
257
- console.error('Thrown Unknown error: ', ex);
246
+ console.log('Input: ', str);
247
+ const parser = new StringParser(str);
248
+ const response = parser.parse();
249
+ // console.log('Tokens: ', parser.getTokens());
250
+ if (response.clauses && response.clauses.length > 0) {
251
+ if (outputFormatter) {
252
+ console.log('Output: ', outputFormatter(response.clauses));
253
+ } else {
254
+ console.log('Output: ', ...response.clauses);
258
255
  }
259
256
  }
260
- console.log('');
261
- }
262
-
263
- function testNumberParser() {
264
- for (const example of numberExamples) {
265
- const parser = new NumberParser(example);
266
- testParserSingle(example, parser);
257
+ if (response.quotes) {
258
+ console.log('Quotes: ', ...response.quotes);
259
+ }
260
+ if (response.errors && response.errors.length > 0) {
261
+ console.log('Errors: ', ...response.errors);
267
262
  }
263
+ console.log('');
268
264
  }
269
265
 
270
266
  function testStringParser() {
271
267
  for (const example of stringExamples) {
272
268
  const parser = new StringParser(example);
273
- testParserSingle(example, parser);
269
+ testStringParserSingle(example);
270
+ }
271
+ }
272
+
273
+ function testBooleanParserSingle(
274
+ str: string,
275
+ outputFormatter?: (clauses: Clause[]) => string
276
+ ): void {
277
+ console.log('Input: ', str);
278
+ const parser = new BooleanParser(str);
279
+ const response = parser.parse();
280
+ // console.log('Tokens: ', parser.getTokens());
281
+ if (response.clauses && response.clauses.length > 0) {
282
+ if (outputFormatter) {
283
+ console.log('Output: ', outputFormatter(response.clauses));
284
+ } else {
285
+ console.log('Output: ', ...response.clauses);
286
+ }
287
+ }
288
+ if (response.errors && response.errors.length > 0) {
289
+ console.log('Errors: ', ...response.errors);
274
290
  }
291
+ console.log('');
275
292
  }
276
293
 
277
294
  function testBooleanParser() {
278
295
  for (const example of booleanExamples) {
279
296
  const parser = new BooleanParser(example);
280
- testParserSingle(example, parser);
297
+ testBooleanParserSingle(example);
281
298
  }
282
299
  }
283
300
 
@@ -289,67 +306,112 @@ function jsonFormatter(clauses: Clause[]): string {
289
306
  return str;
290
307
  }
291
308
 
309
+ function testDateParserSingle(
310
+ str: string,
311
+ outputFormatter?: (clauses: Clause[]) => string
312
+ ): void {
313
+ console.log('Input: ', str);
314
+ const parser = new DateParser(str);
315
+ const response = parser.parse();
316
+ // console.log('Tokens: ', parser.getTokens());
317
+ if (response.clauses && response.clauses.length > 0) {
318
+ if (outputFormatter) {
319
+ console.log('Output: ', outputFormatter(response.clauses));
320
+ } else {
321
+ console.log('Output: ', ...response.clauses);
322
+ }
323
+ }
324
+ if (response.errors && response.errors.length > 0) {
325
+ console.log('Errors: ', ...response.errors);
326
+ }
327
+ console.log('');
328
+ }
329
+
292
330
  function testDateParser() {
293
331
  for (const example of dateExamples) {
294
332
  const parser = new DateParser(example);
295
- testParserSingle(example, parser);
296
- //testParserSingle(example, parser, jsonFormatter);
333
+ testDateParserSingle(example);
334
+ }
335
+ }
336
+
337
+ function testNumberRoundtrip(str: string): void {
338
+ console.log('Input: ' + str);
339
+ const response = new NumberParser(str).parse();
340
+ // console.log('Clause: ', ...response.clauses, '\n');
341
+ if (response.clauses && response.clauses.length > 0) {
342
+ const result = new NumberSerializer(response.clauses || []).serialize();
343
+ console.log('Output: ' + result);
344
+ }
345
+ if (response.errors && response.errors.length > 0) {
346
+ console.log('Errors: ', ...response.errors);
297
347
  }
348
+ console.log('');
298
349
  }
299
350
 
300
351
  function testNumberSerializer(): void {
301
- const examples = [
302
- [{operator: '>', value: 10}],
303
- [{startOperator: '>=', startValue: 20, endOperator: '<=', endValue: 30}],
304
- ];
305
352
  for (const example of numberExamples) {
306
- testSerializerRoundtrip(
307
- example,
308
- new NumberParser(example),
309
- clauses => new NumberSerializer(clauses)
310
- );
353
+ testNumberRoundtrip(example);
311
354
  }
312
355
  }
313
356
 
357
+ function testStringRoundtrip(str: string): void {
358
+ console.log('Input: ' + str);
359
+ const response = new StringParser(str).parse();
360
+ // console.log('Clause: ', ...response.clauses, '\n');
361
+ if (response.clauses && response.clauses.length > 0) {
362
+ const result = new StringSerializer(response.clauses || []).serialize();
363
+ console.log('Output: ' + result);
364
+ }
365
+ if (response.errors && response.errors.length > 0) {
366
+ console.log('Errors: ', ...response.errors);
367
+ }
368
+ console.log('');
369
+ }
370
+
314
371
  function testStringSerializer(): void {
315
372
  for (const example of stringExamples) {
316
- testSerializerRoundtrip(
317
- example,
318
- new StringParser(example),
319
- clauses => new StringSerializer(clauses)
320
- );
373
+ testStringRoundtrip(example);
321
374
  }
322
375
  }
323
376
 
377
+ function testBooleanRoundtrip(str: string): void {
378
+ console.log('Input: ' + str);
379
+ const response = new BooleanParser(str).parse();
380
+ // console.log('Clause: ', ...response.clauses, '\n');
381
+ if (response.clauses && response.clauses.length > 0) {
382
+ const result = new BooleanSerializer(response.clauses || []).serialize();
383
+ console.log('Output: ' + result);
384
+ }
385
+ if (response.errors && response.errors.length > 0) {
386
+ console.log('Errors: ', ...response.errors);
387
+ }
388
+ console.log('');
389
+ }
390
+
324
391
  function testBooleanSerializer(): void {
325
392
  const examples = [[{operator: 'TRUE'}]];
326
393
  for (const example of booleanExamples) {
327
- testSerializerRoundtrip(
328
- example,
329
- new BooleanParser(example),
330
- clauses => new BooleanSerializer(clauses)
331
- );
394
+ testBooleanRoundtrip(example);
332
395
  }
333
396
  }
334
397
 
398
+ function testDateRoundtrip(str: string): void {
399
+ console.log('Input: ' + str);
400
+ const response = new DateParser(str).parse();
401
+ // console.log('Clause: ', ...response.clauses, '\n');
402
+ if (response.clauses && response.clauses.length > 0) {
403
+ const result = new DateSerializer(response.clauses || []).serialize();
404
+ console.log('Output: ' + result);
405
+ }
406
+ if (response.errors && response.errors.length > 0) {
407
+ console.log('Errors: ', ...response.errors);
408
+ }
409
+ console.log('');
410
+ }
411
+
335
412
  function testDateSerializer(): void {
336
- const examples = [
337
- [{prefix: 'BEFORE', values: [{type: 'day', value: 'yesterday'}]}],
338
- [
339
- {
340
- start: [{type: 'day', value: 'today'}],
341
- operator: 'TO',
342
- end: [{type: 'day', value: 'tomorrow'}],
343
- },
344
- ],
345
- [{type: 'day', value: 'today'}],
346
- ];
347
413
  for (const example of dateExamples) {
348
- testSerializerRoundtrip(
349
- example,
350
- new DateParser(example),
351
- clauses => new DateSerializer(clauses)
352
- );
414
+ testDateRoundtrip(example);
353
415
  }
354
416
  }
355
417
 
@@ -362,26 +424,33 @@ function printHeader(title: string): void {
362
424
 
363
425
  // Comment or uncomment the following function calls to disable/enable examples.
364
426
  function generateSamples() {
365
- //printHeader('Tokenizer');
366
- //testTokenizerString();
367
- //testTokenizerNumber();
368
- //testTokenizerMatchTypes();
369
- //printHeader('Numbers');
370
- //testNumberParser();
371
- //printHeader('Strings');
372
- //testStringParser();
373
- //printHeader('Booleans');
374
- //testBooleanParser();
375
- //printHeader('Dates and Times');
376
- //testDateParser();
377
- printHeader('Number Serializer');
378
- testNumberSerializer();
379
- printHeader('String Serializer');
380
- testStringSerializer();
381
- printHeader('Boolean Serializer');
382
- testBooleanSerializer();
383
- printHeader('Date and Time Serializer');
384
- testDateSerializer();
427
+ try {
428
+ //printHeader('Tokenizer');
429
+ //testTokenizerString();
430
+ //testTokenizerNumber();
431
+ //testTokenizerMatchTypes();
432
+ printHeader('Numbers');
433
+ testNumberParser();
434
+ printHeader('Strings');
435
+ testStringParser();
436
+ printHeader('Booleans');
437
+ testBooleanParser();
438
+ printHeader('Dates and Times');
439
+ testDateParser();
440
+ printHeader('Number Serializer');
441
+ testNumberSerializer();
442
+ printHeader('String Serializer');
443
+ testStringSerializer();
444
+ printHeader('Boolean Serializer');
445
+ testBooleanSerializer();
446
+ printHeader('Date and Time Serializer');
447
+ testDateSerializer();
448
+ } catch (ex: Error | unknown) {
449
+ if (ex instanceof Error) console.error('Thrown Error: ', ex.message);
450
+ else {
451
+ console.error('Thrown Unknown error: ', ex);
452
+ }
453
+ }
385
454
  }
386
455
 
387
456
  generateSamples();
@@ -5,11 +5,11 @@ import {
5
5
  NumberOperator,
6
6
  NumberRangeOperator,
7
7
  NumberClause,
8
- Clause,
8
+ NumberParserResponse,
9
+ FilterError,
9
10
  } from './clause_types';
10
11
  import {BaseParser} from './base_parser';
11
12
  import {Token} from './token_types';
12
- import {FilterParserResponse, FilterError} from './filter_types';
13
13
 
14
14
  export class NumberParser extends BaseParser {
15
15
  constructor(input: string) {
@@ -46,7 +46,7 @@ export class NumberParser extends BaseParser {
46
46
  this.tokens = tokenizer.parse();
47
47
  }
48
48
 
49
- public parse(): FilterParserResponse {
49
+ public parse(): NumberParserResponse {
50
50
  this.index = 0;
51
51
  this.tokenize();
52
52
  let clauses: NumberClause[] = [];
@@ -98,7 +98,9 @@ export class NumberParser extends BaseParser {
98
98
  outputs.push(clause);
99
99
  } else if (
100
100
  previous !== undefined &&
101
- previous.operator === clause.operator
101
+ previous.operator === clause.operator &&
102
+ 'values' in previous &&
103
+ 'values' in clause
102
104
  ) {
103
105
  previous.values.push(...clause.values);
104
106
  } else {
@@ -204,59 +206,6 @@ export class NumberParser extends BaseParser {
204
206
  return clauses;
205
207
  }
206
208
 
207
- private static getNegatedType(tokenType: NumberOperator): NumberOperator {
208
- switch (tokenType) {
209
- case '<=':
210
- return '>';
211
- case '>=':
212
- return '<';
213
- case '<':
214
- return '>';
215
- case '>':
216
- return '<';
217
- case '=':
218
- return '!=';
219
- case '!=':
220
- return '=';
221
- }
222
- }
223
-
224
- private static getNumberOperator(
225
- tokenType: string
226
- ): NumberOperator | undefined {
227
- switch (tokenType) {
228
- case '<=':
229
- return '<=';
230
- case '>=':
231
- return '>=';
232
- case '<':
233
- return '<';
234
- case '>':
235
- return '>';
236
- case '=':
237
- return '=';
238
- case '!=':
239
- return '!=';
240
- }
241
- return undefined;
242
- }
243
-
244
- private static getNumberRnageOperator(
245
- tokenType: string
246
- ): NumberRangeOperator | undefined {
247
- switch (tokenType) {
248
- case '<=':
249
- return '<=';
250
- case '>=':
251
- return '>=';
252
- case '<':
253
- return '<';
254
- case '>':
255
- return '>';
256
- }
257
- return undefined;
258
- }
259
-
260
209
  private checkNumericExpression(
261
210
  tokenType: NumberOperator,
262
211
  clauses: NumberClause[]
@@ -281,7 +230,7 @@ export class NumberParser extends BaseParser {
281
230
  return false;
282
231
  }
283
232
 
284
- private checkSimpleNumber(clauses: Clause[]): boolean {
233
+ private checkSimpleNumber(clauses: NumberClause[]): boolean {
285
234
  if (this.getNext().type === 'word') {
286
235
  const numericValue: number = NumberParser.parseNumber(
287
236
  this.getAt(this.index).value
@@ -296,11 +245,13 @@ export class NumberParser extends BaseParser {
296
245
  return false;
297
246
  }
298
247
 
299
- private checkNull(clauses: Clause[]): boolean {
300
- if (this.getNext().type === 'NULL' || this.getNext().type === 'NOTNULL') {
301
- const tokenType = this.getNext().type === 'NULL' ? '=' : '!=';
302
- const clause: NumberCondition = {operator: tokenType, values: [null]};
303
- clauses.push(clause);
248
+ private checkNull(clauses: NumberClause[]): boolean {
249
+ const type = this.getNext().type;
250
+ if (type === 'NULL') {
251
+ clauses.push({operator: 'NULL'});
252
+ return true;
253
+ } else if (type === 'NOTNULL') {
254
+ clauses.push({operator: 'NOTNULL'});
304
255
  return true;
305
256
  }
306
257
  return false;
@@ -3,13 +3,12 @@ import {
3
3
  NumberRange,
4
4
  NumberOperator,
5
5
  NumberRangeOperator,
6
- Clause,
6
+ NumberClause,
7
7
  } from './clause_types';
8
- import {BaseSerializer} from './base_serializer';
9
8
 
10
- export class NumberSerializer extends BaseSerializer {
11
- constructor(clauses: Clause[]) {
12
- super(clauses);
9
+ export class NumberSerializer {
10
+ constructor(private clauses: NumberClause[]) {
11
+ this.clauses = clauses;
13
12
  }
14
13
 
15
14
  public serialize(): string {
@@ -17,15 +16,13 @@ export class NumberSerializer extends BaseSerializer {
17
16
  return result.trim().replace(/,$/, '');
18
17
  }
19
18
 
20
- // NumberOperator = '<=' | '>=' | '!=' | '=' | '>' | '<';
19
+ // NumberOperator = '<=' | '>=' | '!=' | '=' | '>' | '<'
21
20
  private static numberConditionToString(
22
21
  operator: NumberOperator,
23
- value: number | null
22
+ value: number
24
23
  ): string {
25
- if (value === null) {
26
- return operator === '=' ? 'NULL' : '-NULL';
27
- }
28
- const operatorString = operator === '=' ? '' : operator; // Remove operator for eg "5, 7, 9"
24
+ // Remove operator for eg "5, 7, 9".
25
+ const operatorString = operator === '=' ? '' : operator;
29
26
  return operatorString + value;
30
27
  }
31
28
 
@@ -44,7 +41,7 @@ export class NumberSerializer extends BaseSerializer {
44
41
  }
45
42
  }
46
43
 
47
- private static isNumberOperator(value: string): value is NumberOperator {
44
+ private static isNumberOperator(value: string): boolean {
48
45
  return ['<=', '>=', '!=', '=', '>', '<'].includes(value);
49
46
  }
50
47
 
@@ -69,16 +66,20 @@ export class NumberSerializer extends BaseSerializer {
69
66
  );
70
67
  }
71
68
 
72
- private static clauseToString(clauses: Clause[]): string {
69
+ private static clauseToString(clauses: NumberClause[]): string {
73
70
  let result = '';
74
71
  for (const clause of clauses) {
75
- if ('operator' in clause && clause.operator === 'range') {
72
+ if (!('operator' in clause)) {
73
+ throw new Error('Invalid number clause ' + JSON.stringify(clause));
74
+ }
75
+ if (clause.operator === 'range') {
76
76
  result += NumberSerializer.rangeToString(clause);
77
77
  result += ', ';
78
- } else if (
79
- 'operator' in clause &&
80
- NumberSerializer.isNumberOperator(clause.operator)
81
- ) {
78
+ } else if (clause.operator === 'NULL') {
79
+ result += 'NULL, ';
80
+ } else if (clause.operator === 'NOTNULL') {
81
+ result += '-NULL, ';
82
+ } else if (NumberSerializer.isNumberOperator(clause.operator)) {
82
83
  const numberClause: NumberCondition = clause as NumberCondition;
83
84
  for (const value of numberClause.values) {
84
85
  result += NumberSerializer.numberConditionToString(
@@ -87,8 +88,6 @@ export class NumberSerializer extends BaseSerializer {
87
88
  );
88
89
  result += ', ';
89
90
  }
90
- } else {
91
- throw new Error('Invalid number clause ' + JSON.stringify(clause));
92
91
  }
93
92
  }
94
93
  return result;