search-input-query-parser 0.1.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/dist/cjs/first-pass-parser.js +77 -0
- package/dist/cjs/lexer.js +322 -0
- package/dist/cjs/parse-in-values.js +65 -0
- package/dist/cjs/parse-primary.js +154 -0
- package/dist/cjs/parse-range-expression.js +174 -0
- package/dist/cjs/parser.js +85 -0
- package/dist/cjs/search-query-to-sql.js +346 -0
- package/dist/cjs/transform-to-expression.js +130 -0
- package/dist/cjs/validate-expression-fields.js +244 -0
- package/dist/cjs/validate-in-expression.js +33 -0
- package/dist/cjs/validate-string.js +65 -0
- package/dist/cjs/validate-wildcard.js +40 -0
- package/dist/cjs/validator.js +34 -0
- package/dist/esm/first-pass-parser.js +73 -0
- package/dist/esm/lexer.js +315 -0
- package/dist/esm/parse-in-values.js +61 -0
- package/dist/esm/parse-primary.js +147 -0
- package/dist/esm/parse-range-expression.js +170 -0
- package/dist/esm/parser.js +81 -0
- package/dist/esm/search-query-to-sql.js +341 -0
- package/dist/esm/transform-to-expression.js +126 -0
- package/dist/esm/validate-expression-fields.js +240 -0
- package/dist/esm/validate-in-expression.js +29 -0
- package/dist/esm/validate-string.js +61 -0
- package/dist/esm/validate-wildcard.js +36 -0
- package/dist/esm/validator.js +30 -0
- package/dist/types/first-pass-parser.d.ts +40 -0
- package/dist/types/lexer.d.ts +27 -0
- package/dist/types/parse-in-values.d.ts +3 -0
- package/dist/types/parse-primary.d.ts +6 -0
- package/dist/types/parse-range-expression.d.ts +2 -0
- package/dist/types/parser.d.ts +68 -0
- package/dist/types/search-query-to-sql.d.ts +18 -0
- package/dist/types/transform-to-expression.d.ts +3 -0
- package/dist/types/validate-expression-fields.d.ts +4 -0
- package/dist/types/validate-in-expression.d.ts +3 -0
- package/dist/types/validate-string.d.ts +3 -0
- package/dist/types/validate-wildcard.d.ts +3 -0
- package/dist/types/validator.d.ts +8 -0
- package/package.json +52 -0
- package/src/first-pass-parser.test.ts +441 -0
- package/src/first-pass-parser.ts +144 -0
- package/src/lexer.test.ts +439 -0
- package/src/lexer.ts +387 -0
- package/src/parse-in-values.ts +74 -0
- package/src/parse-primary.ts +179 -0
- package/src/parse-range-expression.ts +187 -0
- package/src/parser.test.ts +982 -0
- package/src/parser.ts +219 -0
- package/src/search-query-to-sql.test.ts +503 -0
- package/src/search-query-to-sql.ts +506 -0
- package/src/transform-to-expression.ts +153 -0
- package/src/validate-expression-fields.ts +296 -0
- package/src/validate-in-expression.ts +36 -0
- package/src/validate-string.ts +73 -0
- package/src/validate-wildcard.ts +45 -0
- package/src/validator.test.ts +192 -0
- package/src/validator.ts +53 -0
package/src/parser.ts
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { tokenize, createStream, currentToken, TokenType } from "./lexer";
|
|
2
|
+
import {
|
|
3
|
+
parseExpression,
|
|
4
|
+
PositionLength,
|
|
5
|
+
WildcardPattern as FirstPassWildcard,
|
|
6
|
+
} from "./first-pass-parser";
|
|
7
|
+
import { validateSearchQuery, ValidationError } from "./validator";
|
|
8
|
+
import { validateExpressionFields } from "./validate-expression-fields";
|
|
9
|
+
import { transformToExpression } from "./transform-to-expression";
|
|
10
|
+
|
|
11
|
+
// Schema types for range queries
|
|
12
|
+
interface FieldSchema {
|
|
13
|
+
name: string;
|
|
14
|
+
type: "string" | "number" | "date" | "boolean";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Second Pass AST types (semantic analysis)
|
|
18
|
+
type SearchTerm = {
|
|
19
|
+
readonly type: "SEARCH_TERM";
|
|
20
|
+
readonly value: string;
|
|
21
|
+
} & PositionLength;
|
|
22
|
+
|
|
23
|
+
type WildcardPattern = {
|
|
24
|
+
readonly type: "WILDCARD";
|
|
25
|
+
readonly prefix: string;
|
|
26
|
+
readonly quoted: boolean;
|
|
27
|
+
} & PositionLength;
|
|
28
|
+
|
|
29
|
+
type Field = {
|
|
30
|
+
readonly type: "FIELD";
|
|
31
|
+
readonly value: string;
|
|
32
|
+
} & PositionLength;
|
|
33
|
+
|
|
34
|
+
type Value = {
|
|
35
|
+
readonly type: "VALUE";
|
|
36
|
+
readonly value: string;
|
|
37
|
+
} & PositionLength;
|
|
38
|
+
|
|
39
|
+
type RangeOperator = ">=" | ">" | "<=" | "<" | "BETWEEN";
|
|
40
|
+
|
|
41
|
+
type RangeExpression = {
|
|
42
|
+
readonly type: "RANGE";
|
|
43
|
+
readonly field: Field;
|
|
44
|
+
readonly operator: RangeOperator;
|
|
45
|
+
readonly value: Value;
|
|
46
|
+
readonly value2?: Value; // For BETWEEN
|
|
47
|
+
} & PositionLength;
|
|
48
|
+
|
|
49
|
+
export type FieldValue = {
|
|
50
|
+
readonly type: "FIELD_VALUE";
|
|
51
|
+
readonly field: Field;
|
|
52
|
+
readonly value: Value;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type And = {
|
|
56
|
+
readonly type: "AND";
|
|
57
|
+
readonly left: Expression;
|
|
58
|
+
readonly right: Expression;
|
|
59
|
+
} & PositionLength;
|
|
60
|
+
|
|
61
|
+
type Or = {
|
|
62
|
+
readonly type: "OR";
|
|
63
|
+
readonly left: Expression;
|
|
64
|
+
readonly right: Expression;
|
|
65
|
+
} & PositionLength;
|
|
66
|
+
|
|
67
|
+
type Not = {
|
|
68
|
+
readonly type: "NOT";
|
|
69
|
+
readonly expression: Expression;
|
|
70
|
+
} & PositionLength;
|
|
71
|
+
|
|
72
|
+
type InExpression = {
|
|
73
|
+
readonly type: "IN";
|
|
74
|
+
readonly field: Field;
|
|
75
|
+
readonly values: Value[];
|
|
76
|
+
} & PositionLength;
|
|
77
|
+
|
|
78
|
+
type Expression =
|
|
79
|
+
| SearchTerm
|
|
80
|
+
| WildcardPattern
|
|
81
|
+
| FieldValue
|
|
82
|
+
| RangeExpression
|
|
83
|
+
| And
|
|
84
|
+
| Or
|
|
85
|
+
| Not
|
|
86
|
+
| InExpression;
|
|
87
|
+
|
|
88
|
+
type SearchQuery = {
|
|
89
|
+
readonly type: "SEARCH_QUERY";
|
|
90
|
+
readonly expression: Expression | null;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
type SearchQueryError = {
|
|
94
|
+
readonly type: "SEARCH_QUERY_ERROR";
|
|
95
|
+
readonly expression: null;
|
|
96
|
+
readonly errors: ValidationError[];
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Helper function to stringify expressions
|
|
100
|
+
const stringify = (expr: Expression): string => {
|
|
101
|
+
switch (expr.type) {
|
|
102
|
+
case "SEARCH_TERM":
|
|
103
|
+
return expr.value;
|
|
104
|
+
case "WILDCARD":
|
|
105
|
+
return `${expr.prefix}*`;
|
|
106
|
+
case "FIELD_VALUE":
|
|
107
|
+
return `${expr.field.value}:${expr.value.value}`;
|
|
108
|
+
case "RANGE":
|
|
109
|
+
if (expr.operator === "BETWEEN") {
|
|
110
|
+
return `${expr.field.value}:${expr.value.value}..${expr.value2?.value}`;
|
|
111
|
+
}
|
|
112
|
+
return `${expr.field.value}:${expr.operator}${expr.value.value}`;
|
|
113
|
+
case "NOT":
|
|
114
|
+
return `NOT (${stringify(expr.expression)})`;
|
|
115
|
+
case "AND":
|
|
116
|
+
return `(${stringify(expr.left)} AND ${stringify(expr.right)})`;
|
|
117
|
+
case "OR":
|
|
118
|
+
return `(${stringify(expr.left)} OR ${stringify(expr.right)})`;
|
|
119
|
+
case "IN": {
|
|
120
|
+
const values = expr.values.map((v: { value: string }) => v.value).join(",");
|
|
121
|
+
return `${expr.field.value}:IN(${values})`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Main parse function
|
|
127
|
+
export const parseSearchInputQuery = (
|
|
128
|
+
input: string,
|
|
129
|
+
fieldSchemas: FieldSchema[] = []
|
|
130
|
+
): SearchQuery | SearchQueryError => {
|
|
131
|
+
try {
|
|
132
|
+
const tokens = tokenize(input);
|
|
133
|
+
const stream = createStream(tokens);
|
|
134
|
+
|
|
135
|
+
if (currentToken(stream).type === TokenType.EOF) {
|
|
136
|
+
return { type: "SEARCH_QUERY", expression: null };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const result = parseExpression(stream);
|
|
140
|
+
|
|
141
|
+
const finalToken = currentToken(result.stream);
|
|
142
|
+
if (finalToken.type !== TokenType.EOF) {
|
|
143
|
+
throw {
|
|
144
|
+
message: 'Unexpected ")"',
|
|
145
|
+
position: finalToken.position,
|
|
146
|
+
length: finalToken.length,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const errors = validateSearchQuery(result.result);
|
|
151
|
+
const fieldErrors: ValidationError[] = [];
|
|
152
|
+
|
|
153
|
+
const allowedFields = fieldSchemas.map((s) => s.name.toLowerCase());
|
|
154
|
+
|
|
155
|
+
if (allowedFields.length > 0) {
|
|
156
|
+
const columnSet = new Set(allowedFields.map((col) => col.toLowerCase()));
|
|
157
|
+
const schemaMap = new Map(
|
|
158
|
+
fieldSchemas.map((s) => [s.name.toLowerCase(), s])
|
|
159
|
+
);
|
|
160
|
+
validateExpressionFields(
|
|
161
|
+
result.result,
|
|
162
|
+
columnSet,
|
|
163
|
+
fieldErrors,
|
|
164
|
+
schemaMap
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const fieldErrorKeys = fieldErrors.map(
|
|
169
|
+
({ position, length }) => `${position}-${length}`
|
|
170
|
+
);
|
|
171
|
+
const errorsToRemove = errors.filter(({ position, length }) =>
|
|
172
|
+
fieldErrorKeys.includes(`${position}-${length}`)
|
|
173
|
+
);
|
|
174
|
+
const fieldErrorsFiltered = fieldErrors.filter(
|
|
175
|
+
({ position, length }) =>
|
|
176
|
+
!errorsToRemove.some(
|
|
177
|
+
(error) => error.position === position && error.length === length
|
|
178
|
+
)
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const allErrors = [...errors, ...fieldErrorsFiltered].sort(
|
|
182
|
+
(a, b) => a.position - b.position
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
if (allErrors.length > 0) {
|
|
186
|
+
return {
|
|
187
|
+
type: "SEARCH_QUERY_ERROR",
|
|
188
|
+
expression: null,
|
|
189
|
+
errors: allErrors,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const schemaMap = new Map(
|
|
194
|
+
fieldSchemas.map((s) => [s.name.toLowerCase(), s])
|
|
195
|
+
);
|
|
196
|
+
const expression = transformToExpression(result.result, schemaMap);
|
|
197
|
+
|
|
198
|
+
return { type: "SEARCH_QUERY", expression };
|
|
199
|
+
} catch (error: any) {
|
|
200
|
+
return {
|
|
201
|
+
type: "SEARCH_QUERY_ERROR",
|
|
202
|
+
expression: null,
|
|
203
|
+
errors: [error],
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export {
|
|
209
|
+
type SearchQuery,
|
|
210
|
+
type SearchQueryError,
|
|
211
|
+
type Expression,
|
|
212
|
+
type ValidationError,
|
|
213
|
+
type FieldSchema,
|
|
214
|
+
type RangeOperator,
|
|
215
|
+
type RangeExpression,
|
|
216
|
+
type WildcardPattern,
|
|
217
|
+
type Value,
|
|
218
|
+
stringify,
|
|
219
|
+
};
|