@malloydata/malloy 0.0.242 → 0.0.243-dev250314032737

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.
@@ -243,9 +243,18 @@ class Dialect {
243
243
  escapeClause = true;
244
244
  }
245
245
  else {
246
- if (escapeActive && (c === '%' || c === '_')) {
247
- escaped += this.likeEscape ? '^' : '\\';
248
- escapeClause = this.likeEscape;
246
+ if (escapeActive) {
247
+ if (this.likeEscape) {
248
+ if (c === '%' || c === '_') {
249
+ escaped += '^';
250
+ escapeClause = true;
251
+ }
252
+ }
253
+ else {
254
+ if (c === '%' || c === '_' || c === '\\') {
255
+ escaped += '\\';
256
+ }
257
+ }
249
258
  }
250
259
  escaped += c;
251
260
  escapeActive = false;
@@ -25,6 +25,7 @@ export declare class PostgresDialect extends PostgresBase {
25
25
  readsNestedData: boolean;
26
26
  supportsComplexFilteredSources: boolean;
27
27
  compoundObjectInSchema: boolean;
28
+ likeEscape: boolean;
28
29
  quoteTablePath(tablePath: string): string;
29
30
  sqlGroupSetTable(groupSetCount: number): string;
30
31
  sqlAnyValue(groupSet: number, fieldName: string): string;
@@ -94,6 +94,7 @@ class PostgresDialect extends pg_impl_1.PostgresBase {
94
94
  this.readsNestedData = false;
95
95
  this.supportsComplexFilteredSources = false;
96
96
  this.compoundObjectInSchema = false;
97
+ this.likeEscape = false;
97
98
  }
98
99
  quoteTablePath(tablePath) {
99
100
  return tablePath
@@ -26,33 +26,47 @@ class ExprFilterExpression extends expression_def_1.ExpressionDef {
26
26
  if (matchExpr.type === 'error') {
27
27
  return matchExpr;
28
28
  }
29
- if (matchExpr.type === 'string') {
30
- const fParse = malloy_filter_1.StringFilterExpression.parse(this.filterText);
31
- if (fParse.log.length > 0) {
32
- for (const _err of fParse.log) {
33
- // mtoy todo actuall get error and report correct position and error type
34
- return this.loggedErrorExpr('filter-expression-type', 'FHJKL:DSHJKL error in parsing filter expression');
35
- }
36
- }
37
- if (!fParse.parsed) {
38
- return this.loggedErrorExpr('filter-expression-type', 'FJKLD:JDKSL: expression parsed to null');
39
- }
40
- const filterMatch = {
41
- node: 'filterMatch',
42
- dataType: matchExpr.type,
43
- filter: fParse.parsed,
44
- e: matchExpr.value,
45
- };
46
- if (op === '!~') {
47
- filterMatch.notMatch = true;
29
+ let fParse;
30
+ switch (matchExpr.type) {
31
+ case 'string':
32
+ fParse = malloy_filter_1.StringFilterExpression.parse(this.filterText);
33
+ break;
34
+ case 'number':
35
+ fParse = malloy_filter_1.NumberFilterExpression.parse(this.filterText);
36
+ break;
37
+ case 'boolean':
38
+ fParse = malloy_filter_1.BooleanFilterExpression.parse(this.filterText);
39
+ break;
40
+ case 'date':
41
+ case 'timestamp':
42
+ fParse = malloy_filter_1.TemporalFilterExpression.parse(this.filterText);
43
+ break;
44
+ default:
45
+ return left.loggedErrorExpr('filter-expression-type', `Cannot use filter expressions with type '${matchExpr.type}'`);
46
+ }
47
+ if (fParse.log.length > 0) {
48
+ for (const _err of fParse.log) {
49
+ // mtoy todo actuall get error and report correct position and error type
50
+ return this.loggedErrorExpr('filter-expression-type', `Filter parse error: ${fParse.log[0].message}`);
48
51
  }
49
- return (0, expr_value_1.computedExprValue)({
50
- value: filterMatch,
51
- dataType: { type: 'boolean' },
52
- from: [matchExpr],
53
- });
54
52
  }
55
- return this.loggedErrorExpr('filter-expression-type', `CUISAO)VEFG Filter expressions for ${matchExpr.type} not available`);
53
+ if (!fParse.parsed) {
54
+ return this.loggedErrorExpr('filter-expression-type', 'FJKLD:JDKSL: expression parsed to null');
55
+ }
56
+ const filterMatch = {
57
+ node: 'filterMatch',
58
+ dataType: matchExpr.type,
59
+ filter: fParse.parsed,
60
+ e: matchExpr.value,
61
+ };
62
+ if (op === '!~') {
63
+ filterMatch.notMatch = true;
64
+ }
65
+ return (0, expr_value_1.computedExprValue)({
66
+ value: filterMatch,
67
+ dataType: { type: 'boolean' },
68
+ from: [matchExpr],
69
+ });
56
70
  }
57
71
  return this.loggedErrorExpr('filter-expression-type', `Cannot use the '${op}' operator with a filter expression`);
58
72
  }
@@ -1,5 +1,9 @@
1
- import { StringClause } from '@malloydata/malloy-filter';
1
+ import { BooleanClause, ClauseBase, NumberClause, StringClause, TemporalClause } from '@malloydata/malloy-filter';
2
2
  import { Dialect } from '../dialect';
3
3
  export declare const FilterCompilers: {
4
+ compile(t: string, c: ClauseBase, x: string, d: Dialect): string;
5
+ numberCompile(nc: NumberClause, x: string, d: Dialect): string;
6
+ booleanCompile(bc: BooleanClause, x: string, _d: Dialect): string;
7
+ temporalCompile(tc: TemporalClause, x: string, d: Dialect): string;
4
8
  stringCompile(sc: StringClause, x: string, d: Dialect): string;
5
9
  };
@@ -7,15 +7,25 @@
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.FilterCompilers = void 0;
10
- function likeSafe(v) {
11
- return v.replace(/([_%])g/, '\\$1');
10
+ const malloy_filter_1 = require("@malloydata/malloy-filter");
11
+ function escapeForLike(v) {
12
+ return v.replace(/([%_\\])/g, '\\$1');
13
+ }
14
+ function invertCompare(no) {
15
+ if (no === '>')
16
+ return '<=';
17
+ else if (no === '<')
18
+ return '>=';
19
+ else if (no === '>=')
20
+ return '<';
21
+ return '>';
12
22
  }
13
23
  function unlike(disLiked, x) {
14
- const orNull = `OR ${x} IS NULL`;
24
+ const orNull = ` OR ${x} IS NULL`;
15
25
  if (disLiked.length === 1) {
16
- return `${disLiked[0]} ${orNull}`;
26
+ return `${disLiked[0]}${orNull}`;
17
27
  }
18
- return `(${disLiked.join(' and ')}) $orNULL`;
28
+ return `(${disLiked.join(' AND ')})${orNull}`;
19
29
  }
20
30
  /*
21
31
  * These compilers from filter expression to SQL actually belong in malloy-filters but
@@ -25,6 +35,83 @@ function unlike(disLiked, x) {
25
35
  * XXXXFilterCompiler.compile() will move to XXXFilterExpression.compile()
26
36
  */
27
37
  exports.FilterCompilers = {
38
+ compile(t, c, x, d) {
39
+ if (t === 'string' && (0, malloy_filter_1.isStringClause)(c)) {
40
+ return exports.FilterCompilers.stringCompile(c, x, d);
41
+ }
42
+ else if (t === 'number' && (0, malloy_filter_1.isNumberClause)(c)) {
43
+ return exports.FilterCompilers.numberCompile(c, x, d);
44
+ }
45
+ else if (t === 'boolean' && (0, malloy_filter_1.isBooleanClause)(c)) {
46
+ return exports.FilterCompilers.booleanCompile(c, x, d);
47
+ }
48
+ else if ((t === 'date' || t === 'timestamp') && (0, malloy_filter_1.isTemporalClause)(c)) {
49
+ return exports.FilterCompilers.temporalCompile(c, x, d);
50
+ }
51
+ throw new Error('INTERNAL ERROR: No filter compiler for ' + t);
52
+ },
53
+ numberCompile(nc, x, d) {
54
+ switch (nc.operator) {
55
+ case '!=':
56
+ case '=': {
57
+ const notEqual = (nc.operator === '=' && nc.not) || (nc.operator === '!=' && !nc.not);
58
+ const optList = nc.values.join(', ');
59
+ if (nc.values.length === 1) {
60
+ if (notEqual)
61
+ return `${x} != ${optList} OR ${x} IS NULL`;
62
+ return `${x} = ${optList}`;
63
+ }
64
+ if (notEqual)
65
+ return `${x} NOT IN (${optList}) OR ${x} IS NULL`;
66
+ return `${x} IN (${optList})`;
67
+ }
68
+ case '>':
69
+ case '<':
70
+ case '>=':
71
+ case '<=':
72
+ return nc.values
73
+ .map(v => `${x} ${nc.operator} ${v}`)
74
+ .join(nc.not ? ' AND ' : ' OR ');
75
+ case 'range': {
76
+ let startOp = nc.startOperator;
77
+ let endOp = nc.endOperator;
78
+ let plus = ' AND ';
79
+ if (nc.not) {
80
+ startOp = invertCompare(startOp);
81
+ endOp = invertCompare(endOp);
82
+ plus = ' OR ';
83
+ }
84
+ return `${x} ${startOp} ${nc.startValue} ${plus} ${x} ${endOp} ${nc.endValue}`;
85
+ }
86
+ case 'null':
87
+ return nc.not ? `${x} IS NOT NULL` : `${x} IS NULL`;
88
+ case '()': {
89
+ const wrapped = '(' + exports.FilterCompilers.numberCompile(nc.expr, x, d) + ')';
90
+ return nc.not ? `NOT ${wrapped}` : wrapped;
91
+ }
92
+ case 'and':
93
+ case 'or':
94
+ return nc.members
95
+ .map(m => exports.FilterCompilers.numberCompile(m, x, d))
96
+ .join(` ${nc.operator.toUpperCase()}`);
97
+ }
98
+ },
99
+ booleanCompile(bc, x, _d) {
100
+ switch (bc.operator) {
101
+ case 'false':
102
+ return `${x} = false`;
103
+ case 'false_or_null':
104
+ return `${x} IS NULL OR ${x} = false`;
105
+ case 'null':
106
+ return bc.not ? `${x} IS NOT NULL` : `${x} IS NULL`;
107
+ case 'true':
108
+ return x;
109
+ }
110
+ },
111
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
112
+ temporalCompile(tc, x, d) {
113
+ return 'false';
114
+ },
28
115
  stringCompile(sc, x, d) {
29
116
  switch (sc.operator) {
30
117
  case 'null':
@@ -47,38 +134,38 @@ exports.FilterCompilers = {
47
134
  return sc.not ? `not ${wrapped}` : wrapped;
48
135
  }
49
136
  case 'contains': {
50
- const matches = sc.values.map(v => '%' + likeSafe(v) + '%');
137
+ const matches = sc.values.map(v => '%' + escapeForLike(v) + '%');
51
138
  if (sc.not) {
52
139
  return unlike(matches.map(m => d.sqlLike('NOT LIKE', x, m)), x);
53
140
  }
54
- return matches.map(m => d.sqlLike('LIKE', x, m)).join(' or ');
141
+ return matches.map(m => d.sqlLike('LIKE', x, m)).join(' OR ');
55
142
  }
56
143
  case 'starts': {
57
- const matches = sc.values.map(v => likeSafe(v) + '%');
144
+ const matches = sc.values.map(v => escapeForLike(v) + '%');
58
145
  if (sc.not) {
59
146
  return unlike(matches.map(m => d.sqlLike('NOT LIKE', x, m)), x);
60
147
  }
61
- return matches.map(m => d.sqlLike('LIKE', x, m)).join(' or ');
148
+ return matches.map(m => d.sqlLike('LIKE', x, m)).join(' OR ');
62
149
  }
63
150
  case 'ends': {
64
- const matches = sc.values.map(v => '%' + likeSafe(v));
151
+ const matches = sc.values.map(v => '%' + escapeForLike(v));
65
152
  if (sc.not) {
66
153
  return unlike(matches.map(m => d.sqlLike('NOT LIKE', x, m)), x);
67
154
  }
68
- return matches.map(m => d.sqlLike('LIKE', x, m)).join(' or ');
155
+ return matches.map(m => d.sqlLike('LIKE', x, m)).join(' OR ');
69
156
  }
70
157
  case '~':
71
158
  if (sc.not) {
72
159
  return unlike(sc.escaped_values.map(m => d.sqlLike('NOT LIKE', x, m)), x);
73
160
  }
74
- return sc.escaped_values.map(m => d.sqlLike('LIKE', x, m)).join(' or ');
161
+ return sc.escaped_values.map(m => d.sqlLike('LIKE', x, m)).join(' OR ');
75
162
  case 'and': {
76
163
  const clauses = sc.members.map(c => exports.FilterCompilers.stringCompile(c, x, d));
77
- return clauses.join(' and ');
164
+ return clauses.join(' AND ');
78
165
  }
79
166
  case 'or': {
80
167
  const clauses = sc.members.map(c => exports.FilterCompilers.stringCompile(c, x, d));
81
- return clauses.join(' or ');
168
+ return clauses.join(' AND ');
82
169
  }
83
170
  case ',': {
84
171
  /*
@@ -134,7 +221,7 @@ exports.FilterCompilers = {
134
221
  if (includeNull) {
135
222
  includeExprs.push(`${x} IS NULL`);
136
223
  }
137
- includeSQL = includeExprs.join(' or ');
224
+ includeSQL = includeExprs.join(' OR ');
138
225
  }
139
226
  let excludeSQL = '';
140
227
  if (excludes.length > 0 || excludeEmpty || excludeNull) {
@@ -145,11 +232,11 @@ exports.FilterCompilers = {
145
232
  if (excludeNull) {
146
233
  excludeExprs.push(`${x} IS NOT NULL`);
147
234
  }
148
- excludeSQL = excludeExprs.join(' and ');
235
+ excludeSQL = excludeExprs.join(' AND ');
149
236
  }
150
237
  if (includeSQL) {
151
238
  return excludeSQL !== ''
152
- ? includeSQL + ' and ' + excludeSQL
239
+ ? `(${includeSQL}) AND (${excludeSQL})`
153
240
  : includeSQL;
154
241
  }
155
242
  return excludeSQL !== '' ? excludeSQL : 'true';
@@ -883,10 +883,12 @@ class QueryField extends QueryNode {
883
883
  case 'compositeField':
884
884
  return '{COMPOSITE_FIELD}';
885
885
  case 'filterMatch':
886
- if (expr.dataType === 'string') {
887
- return filter_compilers_1.FilterCompilers.stringCompile(expr.filter, expr.e.sql || '', this.parent.dialect);
886
+ if (expr.dataType === 'string' ||
887
+ expr.dataType === 'number' ||
888
+ expr.dataType === 'boolean') {
889
+ return filter_compilers_1.FilterCompilers.compile(expr.dataType, expr.filter, expr.e.sql || '', this.parent.dialect);
888
890
  }
889
- throw new Error(`Internal Error: FCU ${expr.dataType}`);
891
+ throw new Error(`Internal Error: Filter Compiler Undefined (FCU) ${expr.dataType}`);
890
892
  default:
891
893
  throw new Error(`Internal Error: Unknown expression node '${expr.node}' ${JSON.stringify(expr, undefined, 2)}`);
892
894
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const MALLOY_VERSION = "0.0.242";
1
+ export declare const MALLOY_VERSION = "0.0.243";
package/dist/version.js CHANGED
@@ -2,5 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MALLOY_VERSION = void 0;
4
4
  // generated with 'generate-version-file' script; do not edit manually
5
- exports.MALLOY_VERSION = '0.0.242';
5
+ exports.MALLOY_VERSION = '0.0.243';
6
6
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/malloy",
3
- "version": "0.0.242",
3
+ "version": "0.0.243-dev250314032737",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./dist/index.js",
@@ -41,9 +41,9 @@
41
41
  "generate-version-file": "VERSION=$(npm pkg get version --workspaces=false | tr -d \\\")\necho \"// generated with 'generate-version-file' script; do not edit manually\\nexport const MALLOY_VERSION = '$VERSION';\" > src/version.ts"
42
42
  },
43
43
  "dependencies": {
44
- "@malloydata/malloy-filter": "^0.0.242",
45
- "@malloydata/malloy-interfaces": "^0.0.242",
46
- "@malloydata/malloy-tag": "^0.0.242",
44
+ "@malloydata/malloy-filter": "^0.0.243-dev250314032737",
45
+ "@malloydata/malloy-interfaces": "^0.0.243-dev250314032737",
46
+ "@malloydata/malloy-tag": "^0.0.243-dev250314032737",
47
47
  "antlr4ts": "^0.5.0-alpha.4",
48
48
  "assert": "^2.0.0",
49
49
  "jest-diff": "^29.6.2",