@mst003/milql 1.1.1
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/.github/copilot-instructions.md +37 -0
- package/.gitlab-ci.yml +29 -0
- package/README.md +37 -0
- package/index.js +18 -0
- package/lib/ASTListener.js +51 -0
- package/lib/BaseASTListener.js +7 -0
- package/lib/HQLTransform.js +206 -0
- package/lib/Operator.js +27 -0
- package/lib/Parser.js +278 -0
- package/lib/SortDirection.js +6 -0
- package/lib/internal/CSTListener.js +97 -0
- package/package.json +14 -0
- package/src/MILQLLexer.g +203 -0
- package/src/MILQLParser.g +102 -0
- package/tempTest.js +6 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<!-- Use this file to provide workspace-specific custom instructions to Copilot. For more details, visit https://code.visualstudio.com/docs/copilot/copilot-customization#_use-a-githubcopilotinstructionsmd-file -->
|
|
2
|
+
- [x] Verify that the copilot-instructions.md file in the .github directory is created. - File created successfully.
|
|
3
|
+
|
|
4
|
+
- [x] Clarify Project Requirements - Node.js npm package project for refactoring a plugin specified.
|
|
5
|
+
|
|
6
|
+
- [x] Scaffold the Project
|
|
7
|
+
<!--
|
|
8
|
+
Ensure that the previous step has been marked as completed.
|
|
9
|
+
Call project setup tool with projectType parameter.
|
|
10
|
+
Run scaffolding command to create project files and folders.
|
|
11
|
+
Use '.' as the working directory.
|
|
12
|
+
If no appropriate projectType is available, search documentation using available tools.
|
|
13
|
+
Otherwise, create the project structure manually using available file creation tools.
|
|
14
|
+
-->
|
|
15
|
+
- Created package.json, index.js, README.md, .gitignore manually.
|
|
16
|
+
|
|
17
|
+
- [x] Customize the Project
|
|
18
|
+
- Plugin logic ported from Java/Groovy to Node.js, ANTLR grammars adapted for JS, classes implemented in lib/
|
|
19
|
+
|
|
20
|
+
- [x] Install Required Extensions
|
|
21
|
+
- No extensions needed.
|
|
22
|
+
|
|
23
|
+
- [x] Compile the Project
|
|
24
|
+
- No compilation required for Node.js project.
|
|
25
|
+
|
|
26
|
+
- [x] Create and Run Task
|
|
27
|
+
- No tasks required.
|
|
28
|
+
|
|
29
|
+
- [x] Launch the Project
|
|
30
|
+
- Not applicable for npm package.
|
|
31
|
+
|
|
32
|
+
- [x] Ensure Documentation is Complete
|
|
33
|
+
- README.md created, HTML comments removed from copilot-instructions.md.
|
|
34
|
+
|
|
35
|
+
- Work through each checklist item systematically.
|
|
36
|
+
- Keep communication concise and focused.
|
|
37
|
+
- Follow development best practices.
|
package/.gitlab-ci.yml
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
stages:
|
|
2
|
+
#- test
|
|
3
|
+
- publish
|
|
4
|
+
|
|
5
|
+
variables:
|
|
6
|
+
NPM_REGISTRY: "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/npm/"
|
|
7
|
+
|
|
8
|
+
cache:
|
|
9
|
+
paths:
|
|
10
|
+
- node_modules/
|
|
11
|
+
|
|
12
|
+
# test:
|
|
13
|
+
# image: node:20
|
|
14
|
+
# stage: test
|
|
15
|
+
# script:
|
|
16
|
+
# - npm ci
|
|
17
|
+
# - npm test
|
|
18
|
+
|
|
19
|
+
publish:
|
|
20
|
+
image: node:20
|
|
21
|
+
stage: publish
|
|
22
|
+
script:
|
|
23
|
+
- echo "Publishing package to GitLab NPM registry..."
|
|
24
|
+
- npm config set //gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/npm/:_authToken $GROUP_REGISTRY_TOKEN
|
|
25
|
+
- npm publish --registry "$NPM_REGISTRY"
|
|
26
|
+
rules:
|
|
27
|
+
- if: '$CI_COMMIT_TAG'
|
|
28
|
+
when: always
|
|
29
|
+
- when: never
|
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# mil-ql
|
|
2
|
+
|
|
3
|
+
An implementation of the MIL Query Language for Node.js.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install mil-ql
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
This package includes a built-in MILQL parser, so no ANTLR file generation is required.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
const { Parser, HQLTransform } = require('mil-ql');
|
|
19
|
+
|
|
20
|
+
const hql = new HQLTransform('Entity');
|
|
21
|
+
Parser.parse('filters=name::eq::"Alice"&sort=-createdAt&limit=10', hql);
|
|
22
|
+
|
|
23
|
+
console.log(hql.query);
|
|
24
|
+
console.log(hql.parameters);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## API
|
|
28
|
+
|
|
29
|
+
- `HQLTransform(entityName)`: Creates an HQL transformer for the given entity.
|
|
30
|
+
- `BaseASTListener`: Base class for implementing AST listeners.
|
|
31
|
+
- `ASTListener`: Interface for AST listeners.
|
|
32
|
+
- `Operator`: Enum for filter operators.
|
|
33
|
+
- `SortDirection`: Enum for sort directions.
|
|
34
|
+
|
|
35
|
+
## License
|
|
36
|
+
|
|
37
|
+
ISC
|
package/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Main entry point for the mil-ql npm package
|
|
2
|
+
// MIL Query Language implementation for Node.js
|
|
3
|
+
|
|
4
|
+
const HQLTransform = require('./lib/HQLTransform');
|
|
5
|
+
const BaseASTListener = require('./lib/BaseASTListener');
|
|
6
|
+
const ASTListener = require('./lib/ASTListener');
|
|
7
|
+
const Operator = require('./lib/Operator');
|
|
8
|
+
const SortDirection = require('./lib/SortDirection');
|
|
9
|
+
const Parser = require('./lib/Parser');
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
HQLTransform,
|
|
13
|
+
BaseASTListener,
|
|
14
|
+
ASTListener,
|
|
15
|
+
Operator,
|
|
16
|
+
SortDirection,
|
|
17
|
+
Parser
|
|
18
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
class ASTListener {
|
|
2
|
+
enterLimit() {}
|
|
3
|
+
exitLimit(value) {}
|
|
4
|
+
|
|
5
|
+
enterOffset() {}
|
|
6
|
+
exitOffset(value) {}
|
|
7
|
+
|
|
8
|
+
enterSort() {}
|
|
9
|
+
exitSort() {}
|
|
10
|
+
enterSortTerm() {}
|
|
11
|
+
exitSortTerm(sortDirection, sortKey) {}
|
|
12
|
+
|
|
13
|
+
enterFullTextSearch() {}
|
|
14
|
+
exitFullTextSearch(searchString) {}
|
|
15
|
+
|
|
16
|
+
enterEmbedPath() {}
|
|
17
|
+
exitEmbedPath() {}
|
|
18
|
+
enterEmbedDepth() {}
|
|
19
|
+
exitEmbedDepth() {}
|
|
20
|
+
enterEmbedPathExpression() {}
|
|
21
|
+
exitEmbedPathExpression(path) {}
|
|
22
|
+
|
|
23
|
+
enterDepthExpression() {}
|
|
24
|
+
exitDepthExpression(value) {}
|
|
25
|
+
|
|
26
|
+
enterFilter() {}
|
|
27
|
+
exitFilter() {}
|
|
28
|
+
enterAndPredicate() {}
|
|
29
|
+
exitAndPredicate() {}
|
|
30
|
+
enterOrPredicate() {}
|
|
31
|
+
exitOrPredicate() {}
|
|
32
|
+
enterPredicateTerm() {}
|
|
33
|
+
exitPredicateTerm(operator, lhs) {}
|
|
34
|
+
enterPredicateRightIntegerOperand() {}
|
|
35
|
+
exitPredicateRightIntegerOperand(value) {}
|
|
36
|
+
enterPredicateRightDecimalOperand() {}
|
|
37
|
+
exitPredicateRightDecimalOperand(value) {}
|
|
38
|
+
enterPredicateRightDateOperand() {}
|
|
39
|
+
exitPredicateRightDateOperand(value) {}
|
|
40
|
+
enterPredicateRightBlankOperand() {}
|
|
41
|
+
exitPredicateRightBlankOperand() {}
|
|
42
|
+
enterPredicateRightStringOperand() {}
|
|
43
|
+
exitPredicateRightStringOperand(value) {}
|
|
44
|
+
|
|
45
|
+
enterField() {}
|
|
46
|
+
exitField() {}
|
|
47
|
+
enterFieldTerm() {}
|
|
48
|
+
exitFieldTerm(fieldKey) {}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = ASTListener;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
const BaseASTListener = require('./BaseASTListener');
|
|
2
|
+
const SortDirection = require('./SortDirection');
|
|
3
|
+
const Operator = require('./Operator');
|
|
4
|
+
|
|
5
|
+
class HQLTransform extends BaseASTListener {
|
|
6
|
+
static ENTITY_ALIAS = 'e';
|
|
7
|
+
|
|
8
|
+
constructor(entity) {
|
|
9
|
+
super();
|
|
10
|
+
this.limit = 25;
|
|
11
|
+
this.offset = 0;
|
|
12
|
+
this.selectClause = "";
|
|
13
|
+
this.joinClause = "";
|
|
14
|
+
this.orderByClause = "";
|
|
15
|
+
this.whereClause = "";
|
|
16
|
+
this.parameters = {};
|
|
17
|
+
this.entity = entity;
|
|
18
|
+
|
|
19
|
+
this.orderByTerms = [];
|
|
20
|
+
this.selectTerms = [];
|
|
21
|
+
this.joinTerms = [];
|
|
22
|
+
this.predicates = [];
|
|
23
|
+
this.operands = [];
|
|
24
|
+
this.parameterId = 1;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getRecordParser() {
|
|
28
|
+
// TODO: implement HQLRecordParser
|
|
29
|
+
// return new HQLRecordParser(this.selectTerms);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get query() {
|
|
33
|
+
return `SELECT * FROM ${this.entity} ${HQLTransform.ENTITY_ALIAS} ${this.joinClause} ${this.whereClause} ${this.orderByClause}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get countQuery() {
|
|
37
|
+
return `select count(*) from ${this.entity} ${HQLTransform.ENTITY_ALIAS} ${this.whereClause}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Pagination parsing
|
|
41
|
+
|
|
42
|
+
exitLimit(value) {
|
|
43
|
+
this.limit = value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
exitOffset(value) {
|
|
47
|
+
this.offset = value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Ordering parsing
|
|
51
|
+
|
|
52
|
+
exitSort() {
|
|
53
|
+
this.orderByClause = `order by ${this.orderByTerms.join(', ')}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
exitSortTerm(sortDirection, sortKey) {
|
|
57
|
+
this.orderByTerms.push(`${sortKey.join('.')} ${this.asHQLSortDirection(sortDirection)}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Projection parsing
|
|
61
|
+
|
|
62
|
+
exitField() {
|
|
63
|
+
this.selectClause = `select ${this.selectTerms.map(term => `${HQLTransform.ENTITY_ALIAS}.${term.join('.')}`).join(', ')}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
exitFieldTerm(fieldTerm) {
|
|
67
|
+
this.selectTerms.push(fieldTerm);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Embed parsing
|
|
71
|
+
|
|
72
|
+
exitEmbedPath() {
|
|
73
|
+
if (this.joinTerms.length > 0) {
|
|
74
|
+
this.joinClause = this.joinTerms.map(term => `left join fetch ${HQLTransform.ENTITY_ALIAS}.${term.join('.')}`).join(' ');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
exitEmbedPathExpression(path) {
|
|
79
|
+
this.joinTerms.push(path);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Filtering parsing
|
|
83
|
+
|
|
84
|
+
exitFilter() {
|
|
85
|
+
this.whereClause = `where ${this.predicates.pop()}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
exitAndPredicate() {
|
|
89
|
+
const rhs = this.predicates.pop();
|
|
90
|
+
const lhs = this.predicates.pop();
|
|
91
|
+
this.predicates.push(`(${lhs} and ${rhs})`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
exitOrPredicate() {
|
|
95
|
+
const rhs = this.predicates.pop();
|
|
96
|
+
const lhs = this.predicates.pop();
|
|
97
|
+
this.predicates.push(`(${lhs} or ${rhs})`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
exitPredicateTerm(operator, lhs) {
|
|
101
|
+
const rhs = this.operands.pop();
|
|
102
|
+
const paramName = rhs.slice(1);
|
|
103
|
+
this.applyOperatorParameter(operator, paramName);
|
|
104
|
+
|
|
105
|
+
const fieldExpression = this.asHQLExpression(operator, lhs.join('.'));
|
|
106
|
+
this.predicates.push(`(${fieldExpression} ${this.asHQLOperator(operator)} ${rhs})`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
exitPredicateRightIntegerOperand(value) {
|
|
110
|
+
this.addParameter(value);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
exitPredicateRightDecimalOperand(value) {
|
|
114
|
+
this.addParameter(value);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
exitPredicateRightDateOperand(value) {
|
|
118
|
+
this.addParameter(value);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
exitPredicateRightBlankOperand() {
|
|
122
|
+
this.addParameter(null);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
exitPredicateRightStringOperand(value) {
|
|
126
|
+
this.addParameter(value);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
addParameter(value) {
|
|
130
|
+
const paramName = `param_${this.parameterId++}`;
|
|
131
|
+
this.parameters[paramName] = value;
|
|
132
|
+
this.operands.push(`:${paramName}`);
|
|
133
|
+
return paramName;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
applyOperatorParameter(operator, paramName) {
|
|
137
|
+
let value = this.parameters[paramName];
|
|
138
|
+
if (value == null) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
switch (operator) {
|
|
143
|
+
case Operator.CONTAINS_SUBSTRING:
|
|
144
|
+
case Operator.NOT_CONTAINS_SUBSTRING:
|
|
145
|
+
value = `%${value}%`;
|
|
146
|
+
break;
|
|
147
|
+
case Operator.STARTS_WITH:
|
|
148
|
+
case Operator.STARTS_WITH_LOWCASE_IGNOREACCENT:
|
|
149
|
+
value = `${value}%`;
|
|
150
|
+
break;
|
|
151
|
+
case Operator.ENDS_WITH:
|
|
152
|
+
value = `%${value}`;
|
|
153
|
+
break;
|
|
154
|
+
case Operator.EQUALS_WITH_LOWCASE_IGNOREACCENT:
|
|
155
|
+
value = `${value}`.toLowerCase();
|
|
156
|
+
break;
|
|
157
|
+
case Operator.STARTS_WITH_LOWCASE_IGNOREACCENT:
|
|
158
|
+
value = `${value}`.toLowerCase();
|
|
159
|
+
break;
|
|
160
|
+
default:
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this.parameters[paramName] = value;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
asHQLExpression(operator, field) {
|
|
168
|
+
switch (operator) {
|
|
169
|
+
case Operator.EQUALS_WITH_LOWCASE_IGNOREACCENT:
|
|
170
|
+
case Operator.STARTS_WITH_LOWCASE_IGNOREACCENT:
|
|
171
|
+
return `lower(${field})`;
|
|
172
|
+
default:
|
|
173
|
+
return field;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
asHQLSortDirection(sortDirection) {
|
|
178
|
+
switch (sortDirection) {
|
|
179
|
+
case SortDirection.DESC: return 'desc';
|
|
180
|
+
case SortDirection.ASC: return 'asc';
|
|
181
|
+
default: throw new Error("Invalid sort direction");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
asHQLOperator(operator) {
|
|
186
|
+
switch (operator) {
|
|
187
|
+
case Operator.EQUALS: return '=';
|
|
188
|
+
case Operator.NOT_EQUALS: return '!=';
|
|
189
|
+
case Operator.GREATER_THAN: return '>';
|
|
190
|
+
case Operator.GREATER_OR_EQUAL_TO: return '>=';
|
|
191
|
+
case Operator.LESS_THAN: return '<';
|
|
192
|
+
case Operator.LESS_THAN_OR_EQUAL_TO: return '<=';
|
|
193
|
+
case Operator.CONTAINS_SUBSTRING: return 'like';
|
|
194
|
+
case Operator.NOT_CONTAINS_SUBSTRING: return 'not like';
|
|
195
|
+
case Operator.STARTS_WITH: return 'like';
|
|
196
|
+
case Operator.ENDS_WITH: return 'like';
|
|
197
|
+
case Operator.STARTS_WITH_LOWCASE_IGNOREACCENT: return 'like';
|
|
198
|
+
case Operator.EQUALS_WITH_LOWCASE_IGNOREACCENT: return '=';
|
|
199
|
+
case Operator.MATCHES_REGEX: return 'regexp';
|
|
200
|
+
case Operator.NOT_MATCHES_REGEX: return 'not regexp';
|
|
201
|
+
default: throw new Error('Unsupported operator.');
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
module.exports = HQLTransform;
|
package/lib/Operator.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const Operator = {
|
|
2
|
+
EQUALS: 'eq',
|
|
3
|
+
NOT_EQUALS: 'neq',
|
|
4
|
+
GREATER_THAN: 'gt',
|
|
5
|
+
GREATER_OR_EQUAL_TO: 'gte',
|
|
6
|
+
LESS_THAN: 'lt',
|
|
7
|
+
LESS_THAN_OR_EQUAL_TO: 'lte',
|
|
8
|
+
CONTAINS_SUBSTRING: 'ct',
|
|
9
|
+
NOT_CONTAINS_SUBSTRING: 'nct',
|
|
10
|
+
STARTS_WITH: 'sw',
|
|
11
|
+
ENDS_WITH: 'ew',
|
|
12
|
+
MATCHES_REGEX: 'mre',
|
|
13
|
+
NOT_MATCHES_REGEX: 'nmre',
|
|
14
|
+
STARTS_WITH_LOWCASE_IGNOREACCENT: 'swlcia',
|
|
15
|
+
EQUALS_WITH_LOWCASE_IGNOREACCENT: 'eqlcia'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
Operator.from = function(test) {
|
|
19
|
+
for (let key in Operator) {
|
|
20
|
+
if (Operator[key] === test) {
|
|
21
|
+
return key;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
module.exports = Operator;
|
package/lib/Parser.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
const Operator = require('./Operator');
|
|
2
|
+
const SortDirection = require('./SortDirection');
|
|
3
|
+
|
|
4
|
+
const OPERATOR_MAP = {
|
|
5
|
+
eq: Operator.EQUALS,
|
|
6
|
+
neq: Operator.NOT_EQUALS,
|
|
7
|
+
gt: Operator.GREATER_THAN,
|
|
8
|
+
gte: Operator.GREATER_OR_EQUAL_TO,
|
|
9
|
+
lt: Operator.LESS_THAN,
|
|
10
|
+
lte: Operator.LESS_THAN_OR_EQUAL_TO,
|
|
11
|
+
ct: Operator.CONTAINS_SUBSTRING,
|
|
12
|
+
nct: Operator.NOT_CONTAINS_SUBSTRING,
|
|
13
|
+
sw: Operator.STARTS_WITH,
|
|
14
|
+
ew: Operator.ENDS_WITH,
|
|
15
|
+
mre: Operator.MATCHES_REGEX,
|
|
16
|
+
nmre: Operator.NOT_MATCHES_REGEX,
|
|
17
|
+
swlcia: Operator.STARTS_WITH_LOWCASE_IGNOREACCENT,
|
|
18
|
+
eqlcia: Operator.EQUALS_WITH_LOWCASE_IGNOREACCENT
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
class Parser {
|
|
22
|
+
static parse(input, astListener) {
|
|
23
|
+
const query = input.startsWith('?') ? input.slice(1) : input;
|
|
24
|
+
const params = new URLSearchParams(query);
|
|
25
|
+
|
|
26
|
+
for (const [name, value] of params) {
|
|
27
|
+
switch (name) {
|
|
28
|
+
case 'limit':
|
|
29
|
+
astListener.enterLimit();
|
|
30
|
+
astListener.exitLimit(parseInt(value, 10));
|
|
31
|
+
break;
|
|
32
|
+
case 'offset':
|
|
33
|
+
astListener.enterOffset();
|
|
34
|
+
astListener.exitOffset(parseInt(value, 10));
|
|
35
|
+
break;
|
|
36
|
+
case 'sort':
|
|
37
|
+
Parser.parseSort(value, astListener);
|
|
38
|
+
break;
|
|
39
|
+
case 'fields':
|
|
40
|
+
Parser.parseFields(value, astListener);
|
|
41
|
+
break;
|
|
42
|
+
case 'embed':
|
|
43
|
+
Parser.parseEmbed(value, astListener);
|
|
44
|
+
break;
|
|
45
|
+
case 'filters':
|
|
46
|
+
Parser.parseFilters(value, astListener);
|
|
47
|
+
break;
|
|
48
|
+
case 'q':
|
|
49
|
+
astListener.enterFullTextSearch();
|
|
50
|
+
astListener.exitFullTextSearch(value);
|
|
51
|
+
break;
|
|
52
|
+
default:
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return astListener;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static parseSort(value, astListener) {
|
|
61
|
+
astListener.enterSort();
|
|
62
|
+
const terms = value.split(',').filter(Boolean);
|
|
63
|
+
for (const term of terms) {
|
|
64
|
+
astListener.enterSortTerm();
|
|
65
|
+
const direction = term.startsWith('-') ? SortDirection.DESC : SortDirection.ASC;
|
|
66
|
+
const path = term.replace(/^-/, '').split('.').filter(Boolean);
|
|
67
|
+
astListener.exitSortTerm(direction, path);
|
|
68
|
+
}
|
|
69
|
+
astListener.exitSort();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static parseFields(value, astListener) {
|
|
73
|
+
astListener.enterField();
|
|
74
|
+
const fields = value.split(',').filter(Boolean);
|
|
75
|
+
for (const field of fields) {
|
|
76
|
+
const path = field.split('.').filter(Boolean);
|
|
77
|
+
astListener.enterFieldTerm();
|
|
78
|
+
astListener.exitFieldTerm(path);
|
|
79
|
+
}
|
|
80
|
+
astListener.exitField();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static parseEmbed(value, astListener) {
|
|
84
|
+
astListener.enterEmbedPath();
|
|
85
|
+
const paths = value.split(',').filter(Boolean);
|
|
86
|
+
for (const pathText of paths) {
|
|
87
|
+
const path = pathText.split('.').filter(Boolean);
|
|
88
|
+
astListener.enterEmbedPathExpression();
|
|
89
|
+
astListener.exitEmbedPathExpression(path);
|
|
90
|
+
}
|
|
91
|
+
astListener.exitEmbedPath();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static parseFilters(value, astListener) {
|
|
95
|
+
astListener.enterFilter();
|
|
96
|
+
const tokens = Parser.tokenizeFilter(value);
|
|
97
|
+
const ast = Parser.parseFilterExpression(tokens);
|
|
98
|
+
Parser.walkFilter(ast, astListener);
|
|
99
|
+
astListener.exitFilter();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static tokenizeFilter(value) {
|
|
103
|
+
const tokens = [];
|
|
104
|
+
let text = value.trim();
|
|
105
|
+
|
|
106
|
+
while (text.length > 0) {
|
|
107
|
+
if (text.startsWith('(')) {
|
|
108
|
+
tokens.push({ type: 'lparen' });
|
|
109
|
+
text = text.slice(1).trimStart();
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (text.startsWith(')')) {
|
|
113
|
+
tokens.push({ type: 'rparen' });
|
|
114
|
+
text = text.slice(1).trimStart();
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (text.startsWith(';')) {
|
|
118
|
+
tokens.push({ type: 'and' });
|
|
119
|
+
text = text.slice(1).trimStart();
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (text.startsWith(',')) {
|
|
123
|
+
tokens.push({ type: 'or' });
|
|
124
|
+
text = text.slice(1).trimStart();
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const operatorMatch = text.match(/^([A-Za-z0-9_]+(?:\.[A-Za-z0-9_]+)*)::(eq|neq|gt|gte|lt|lte|ct|nct|sw|ew|mre|nmre|eqlcia|swlcia)::/);
|
|
129
|
+
if (operatorMatch) {
|
|
130
|
+
tokens.push({ type: 'term', lhs: operatorMatch[1].split('.'), operator: operatorMatch[2] });
|
|
131
|
+
text = text.slice(operatorMatch[0].length).trimStart();
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const blankMatch = text.match(/^BLANK\b/);
|
|
136
|
+
if (blankMatch) {
|
|
137
|
+
tokens.push({ type: 'blank' });
|
|
138
|
+
text = text.slice(blankMatch[0].length).trimStart();
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const quotedMatch = text.match(/^"([^"\\]*(?:\\.[^"\\]*)*)"/);
|
|
143
|
+
if (quotedMatch) {
|
|
144
|
+
tokens.push({ type: 'string', value: quotedMatch[1].replace(/\\"/g, '"') });
|
|
145
|
+
text = text.slice(quotedMatch[0].length).trimStart();
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const numberMatch = text.match(/^([0-9]+(?:\.[0-9]+)?)/);
|
|
150
|
+
if (numberMatch) {
|
|
151
|
+
tokens.push({ type: 'number', value: numberMatch[1] });
|
|
152
|
+
text = text.slice(numberMatch[0].length).trimStart();
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
throw new Error(`Unable to parse filters near: ${text}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return tokens;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
static parseFilterExpression(tokens, minPrec = 0) {
|
|
163
|
+
let node = Parser.parseFilterPrimary(tokens);
|
|
164
|
+
|
|
165
|
+
while (tokens.length > 0 && Parser.getPrecedence(tokens[0]) >= minPrec) {
|
|
166
|
+
const opToken = tokens.shift();
|
|
167
|
+
const precedence = Parser.getPrecedence(opToken);
|
|
168
|
+
const nextMinPrec = precedence + 1;
|
|
169
|
+
const rhs = Parser.parseFilterExpression(tokens, nextMinPrec);
|
|
170
|
+
node = { type: opToken.type, left: node, right: rhs };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return node;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
static parseFilterPrimary(tokens) {
|
|
177
|
+
const token = tokens.shift();
|
|
178
|
+
if (!token) {
|
|
179
|
+
throw new Error('Unexpected end of filter expression');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (token.type === 'lparen') {
|
|
183
|
+
const node = Parser.parseFilterExpression(tokens);
|
|
184
|
+
const next = tokens.shift();
|
|
185
|
+
if (!next || next.type !== 'rparen') {
|
|
186
|
+
throw new Error('Missing closing parenthesis in filter expression');
|
|
187
|
+
}
|
|
188
|
+
return node;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (token.type === 'term') {
|
|
192
|
+
const rhs = Parser.parseFilterValue(tokens);
|
|
193
|
+
return {
|
|
194
|
+
type: 'term',
|
|
195
|
+
lhs: token.lhs,
|
|
196
|
+
operator: token.operator,
|
|
197
|
+
rhs
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
throw new Error(`Unexpected token in filter expression: ${JSON.stringify(token)}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
static parseFilterValue(tokens) {
|
|
205
|
+
const token = tokens.shift();
|
|
206
|
+
if (!token) {
|
|
207
|
+
throw new Error('Filter value expected');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (token.type === 'blank') {
|
|
211
|
+
return { type: 'blank', value: null };
|
|
212
|
+
}
|
|
213
|
+
if (token.type === 'string') {
|
|
214
|
+
const dateValue = /^\d{4}-\d{2}-\d{2}$/.test(token.value) ? new Date(token.value) : token.value;
|
|
215
|
+
return { type: dateValue instanceof Date ? 'date' : 'string', value: dateValue };
|
|
216
|
+
}
|
|
217
|
+
if (token.type === 'number') {
|
|
218
|
+
return token.value.includes('.')
|
|
219
|
+
? { type: 'decimal', value: parseFloat(token.value) }
|
|
220
|
+
: { type: 'integer', value: parseInt(token.value, 10) };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
throw new Error(`Invalid filter value token: ${JSON.stringify(token)}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
static getPrecedence(token) {
|
|
227
|
+
if (!token) return -1;
|
|
228
|
+
if (token.type === 'and') return 2;
|
|
229
|
+
if (token.type === 'or') return 1;
|
|
230
|
+
return -1;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
static walkFilter(node, astListener) {
|
|
234
|
+
if (!node) return;
|
|
235
|
+
|
|
236
|
+
if (node.type === 'and') {
|
|
237
|
+
astListener.enterAndPredicate();
|
|
238
|
+
Parser.walkFilter(node.left, astListener);
|
|
239
|
+
Parser.walkFilter(node.right, astListener);
|
|
240
|
+
astListener.exitAndPredicate();
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (node.type === 'or') {
|
|
245
|
+
astListener.enterOrPredicate();
|
|
246
|
+
Parser.walkFilter(node.left, astListener);
|
|
247
|
+
Parser.walkFilter(node.right, astListener);
|
|
248
|
+
astListener.exitOrPredicate();
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
astListener.enterPredicateTerm();
|
|
253
|
+
|
|
254
|
+
if (node.rhs.type === 'integer') {
|
|
255
|
+
astListener.enterPredicateRightIntegerOperand();
|
|
256
|
+
astListener.exitPredicateRightIntegerOperand(node.rhs.value);
|
|
257
|
+
} else if (node.rhs.type === 'decimal') {
|
|
258
|
+
astListener.enterPredicateRightDecimalOperand();
|
|
259
|
+
astListener.exitPredicateRightDecimalOperand(node.rhs.value);
|
|
260
|
+
} else if (node.rhs.type === 'date') {
|
|
261
|
+
astListener.enterPredicateRightDateOperand();
|
|
262
|
+
astListener.exitPredicateRightDateOperand(node.rhs.value);
|
|
263
|
+
} else if (node.rhs.type === 'blank') {
|
|
264
|
+
astListener.enterPredicateRightBlankOperand();
|
|
265
|
+
astListener.exitPredicateRightBlankOperand();
|
|
266
|
+
} else if (node.rhs.type === 'string') {
|
|
267
|
+
astListener.enterPredicateRightStringOperand();
|
|
268
|
+
astListener.exitPredicateRightStringOperand(node.rhs.value);
|
|
269
|
+
} else {
|
|
270
|
+
throw new Error(`Unsupported filter rhs type: ${node.rhs.type}`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
astListener.exitPredicateTerm(OPERATOR_MAP[node.operator], node.lhs);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
module.exports = Parser;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const antlr4 = require('antlr4');
|
|
2
|
+
|
|
3
|
+
class CSTListener extends require('../MILQLParserListener') {
|
|
4
|
+
constructor(astListener) {
|
|
5
|
+
super();
|
|
6
|
+
this.astListener = astListener;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Limit
|
|
10
|
+
enterLimitParameter(ctx) {
|
|
11
|
+
this.astListener.enterLimit();
|
|
12
|
+
}
|
|
13
|
+
exitLimitParameter(ctx) {
|
|
14
|
+
this.astListener.exitLimit(this.asInteger(ctx.Integer));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Offset
|
|
18
|
+
enterOffsetParameter(ctx) {
|
|
19
|
+
this.astListener.enterOffset();
|
|
20
|
+
}
|
|
21
|
+
exitOffsetParameter(ctx) {
|
|
22
|
+
this.astListener.exitOffset(this.asInteger(ctx.Integer));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Sort
|
|
26
|
+
enterSortParameter(ctx) {
|
|
27
|
+
this.astListener.enterSort();
|
|
28
|
+
}
|
|
29
|
+
exitSortParameter(ctx) {
|
|
30
|
+
this.astListener.exitSort();
|
|
31
|
+
}
|
|
32
|
+
enterSortExpression(ctx) {
|
|
33
|
+
this.astListener.enterSortTerm();
|
|
34
|
+
}
|
|
35
|
+
sortIdentifierPath = [];
|
|
36
|
+
enterSortIdentifierPath(ctx) {
|
|
37
|
+
this.sortIdentifierPath = [];
|
|
38
|
+
}
|
|
39
|
+
exitSortIdentifierStep(ctx) {
|
|
40
|
+
this.sortIdentifierPath.push(this.decode(ctx.SortIdentifier.getText()));
|
|
41
|
+
}
|
|
42
|
+
exitSortExpression(ctx) {
|
|
43
|
+
const direction = ctx.ReverseSort ? require('../SortDirection').DESC : require('../SortDirection').ASC;
|
|
44
|
+
this.astListener.exitSortTerm(direction, [...this.sortIdentifierPath]);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Field
|
|
48
|
+
enterFieldParameter(ctx) {
|
|
49
|
+
this.astListener.enterField();
|
|
50
|
+
}
|
|
51
|
+
exitFieldParameter(ctx) {
|
|
52
|
+
this.astListener.exitField();
|
|
53
|
+
}
|
|
54
|
+
enterFieldExpression(ctx) {
|
|
55
|
+
this.astListener.enterFieldTerm();
|
|
56
|
+
}
|
|
57
|
+
fieldIdentifierPath = [];
|
|
58
|
+
enterFieldIdentifierPath(ctx) {
|
|
59
|
+
this.fieldIdentifierPath = [];
|
|
60
|
+
}
|
|
61
|
+
exitFieldIdentifierStep(ctx) {
|
|
62
|
+
this.fieldIdentifierPath.push(this.decode(ctx.FieldIdentifier.getText()));
|
|
63
|
+
}
|
|
64
|
+
exitFieldExpression(ctx) {
|
|
65
|
+
this.astListener.exitFieldTerm([...this.fieldIdentifierPath]);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Embed
|
|
69
|
+
exitEmbedPathExpression(ctx) {
|
|
70
|
+
// Collect path - simplified
|
|
71
|
+
const path = []; // TODO: implement path collection
|
|
72
|
+
this.astListener.exitEmbedPathExpression(path);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Filter - TODO: implement complex filter parsing
|
|
76
|
+
exitFilter(ctx) {
|
|
77
|
+
this.astListener.exitFilter();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Search
|
|
81
|
+
enterSearchParameter(ctx) {
|
|
82
|
+
this.astListener.enterFullTextSearch();
|
|
83
|
+
}
|
|
84
|
+
exitSearchParameter(ctx) {
|
|
85
|
+
this.astListener.exitFullTextSearch(this.decode(ctx.ParameterValue.getText()));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Helpers
|
|
89
|
+
asInteger(token) {
|
|
90
|
+
return parseInt(token.getText());
|
|
91
|
+
}
|
|
92
|
+
decode(str) {
|
|
93
|
+
return decodeURIComponent(str);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = CSTListener;
|
package/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mst003/milql",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "An implementation of the MIL Query Language for Node.js",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node tempTest.js"
|
|
8
|
+
},
|
|
9
|
+
"keywords": ["milql", "query", "language", "hql"],
|
|
10
|
+
"author": "",
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"type": "commonjs"
|
|
13
|
+
|
|
14
|
+
}
|
package/src/MILQLLexer.g
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
lexer grammar MILQLLexer;
|
|
2
|
+
|
|
3
|
+
// @header {
|
|
4
|
+
// }
|
|
5
|
+
|
|
6
|
+
// ----------------------------------------------------------------------------
|
|
7
|
+
// mode default;
|
|
8
|
+
|
|
9
|
+
Field
|
|
10
|
+
: 'fields' Equals -> pushMode(FieldMode)
|
|
11
|
+
;
|
|
12
|
+
|
|
13
|
+
Filter
|
|
14
|
+
: 'filters' Equals -> pushMode(FilterMode)
|
|
15
|
+
;
|
|
16
|
+
|
|
17
|
+
Embed
|
|
18
|
+
: 'embed' Equals -> pushMode(EmbedMode)
|
|
19
|
+
;
|
|
20
|
+
|
|
21
|
+
Limit
|
|
22
|
+
: 'limit' Equals
|
|
23
|
+
;
|
|
24
|
+
|
|
25
|
+
Offset
|
|
26
|
+
: 'offset' Equals
|
|
27
|
+
;
|
|
28
|
+
|
|
29
|
+
Search
|
|
30
|
+
: 'q' Equals
|
|
31
|
+
;
|
|
32
|
+
|
|
33
|
+
Sort
|
|
34
|
+
: 'sort' Equals -> pushMode(SortMode)
|
|
35
|
+
;
|
|
36
|
+
|
|
37
|
+
Equals
|
|
38
|
+
: '='
|
|
39
|
+
;
|
|
40
|
+
|
|
41
|
+
Ampersand
|
|
42
|
+
: '&'
|
|
43
|
+
;
|
|
44
|
+
|
|
45
|
+
Integer
|
|
46
|
+
: ( [0-9] )+ ( 'L' )?
|
|
47
|
+
;
|
|
48
|
+
|
|
49
|
+
Decimal
|
|
50
|
+
: ( [0-9] )+ '.' ( [0-9] )+ ( 'D' )?
|
|
51
|
+
;
|
|
52
|
+
|
|
53
|
+
Identifier
|
|
54
|
+
: ( [a-z] | [A-Z] | [0-9] | '_' )+
|
|
55
|
+
;
|
|
56
|
+
|
|
57
|
+
ParameterValue
|
|
58
|
+
: ( [a-z] | [A-Z] | [0-9]| ',' | '-' | '.' | '%' | '+' )+
|
|
59
|
+
;
|
|
60
|
+
|
|
61
|
+
// ----------------------------------------------------------------------------
|
|
62
|
+
mode EmbedMode;
|
|
63
|
+
|
|
64
|
+
Depth
|
|
65
|
+
: 'DEPTH.'
|
|
66
|
+
;
|
|
67
|
+
|
|
68
|
+
PathExpressionSeparator
|
|
69
|
+
: '%2C' | ','
|
|
70
|
+
;
|
|
71
|
+
|
|
72
|
+
PathStepSeparator
|
|
73
|
+
: '.'
|
|
74
|
+
;
|
|
75
|
+
|
|
76
|
+
DepthValue
|
|
77
|
+
: Integer
|
|
78
|
+
;
|
|
79
|
+
|
|
80
|
+
PathStepIdentifier
|
|
81
|
+
: Identifier
|
|
82
|
+
;
|
|
83
|
+
|
|
84
|
+
EndEmbedMode
|
|
85
|
+
: '&' -> popMode
|
|
86
|
+
;
|
|
87
|
+
|
|
88
|
+
// ----------------------------------------------------------------------------
|
|
89
|
+
mode SortMode;
|
|
90
|
+
|
|
91
|
+
ReverseSort
|
|
92
|
+
: '-'
|
|
93
|
+
;
|
|
94
|
+
|
|
95
|
+
SortExpressionSeparator
|
|
96
|
+
: '%2C' | ','
|
|
97
|
+
;
|
|
98
|
+
|
|
99
|
+
SortIdentifierStepSeparator
|
|
100
|
+
: '.'
|
|
101
|
+
;
|
|
102
|
+
|
|
103
|
+
SortIdentifier
|
|
104
|
+
: Identifier
|
|
105
|
+
;
|
|
106
|
+
|
|
107
|
+
EndSortMode
|
|
108
|
+
: '&' -> popMode
|
|
109
|
+
;
|
|
110
|
+
|
|
111
|
+
// ----------------------------------------------------------------------------
|
|
112
|
+
mode FieldMode;
|
|
113
|
+
|
|
114
|
+
FieldExpressionSeparator
|
|
115
|
+
: '%2C' | ','
|
|
116
|
+
;
|
|
117
|
+
|
|
118
|
+
FieldIdentifierStepSeparator
|
|
119
|
+
: '.'
|
|
120
|
+
;
|
|
121
|
+
|
|
122
|
+
EndFieldMode
|
|
123
|
+
: '&' -> popMode
|
|
124
|
+
;
|
|
125
|
+
|
|
126
|
+
FieldIdentifier
|
|
127
|
+
: Identifier
|
|
128
|
+
;
|
|
129
|
+
|
|
130
|
+
// ----------------------------------------------------------------------------
|
|
131
|
+
mode FilterMode;
|
|
132
|
+
|
|
133
|
+
AndFilter
|
|
134
|
+
: '%3B' | ';'
|
|
135
|
+
;
|
|
136
|
+
|
|
137
|
+
OrFilter
|
|
138
|
+
: '%2C' | ','
|
|
139
|
+
;
|
|
140
|
+
|
|
141
|
+
FilterOperation
|
|
142
|
+
: ('%3A%3A' | '::')
|
|
143
|
+
( 'eq'
|
|
144
|
+
| 'neq'
|
|
145
|
+
| 'gt'
|
|
146
|
+
| 'gte'
|
|
147
|
+
| 'lt'
|
|
148
|
+
| 'lte'
|
|
149
|
+
| 'ct'
|
|
150
|
+
| 'nct'
|
|
151
|
+
| 'sw'
|
|
152
|
+
| 'ew'
|
|
153
|
+
| 'mre'
|
|
154
|
+
| 'nmre'
|
|
155
|
+
| 'eqlcia'
|
|
156
|
+
| 'swlcia'
|
|
157
|
+
)
|
|
158
|
+
('%3A%3A' | '::')
|
|
159
|
+
;
|
|
160
|
+
|
|
161
|
+
FilterLeftParens
|
|
162
|
+
: '%28' | '('
|
|
163
|
+
;
|
|
164
|
+
|
|
165
|
+
FilterRightParens
|
|
166
|
+
: '%29' | ')'
|
|
167
|
+
;
|
|
168
|
+
|
|
169
|
+
FilterBlank
|
|
170
|
+
: 'BLANK'
|
|
171
|
+
;
|
|
172
|
+
|
|
173
|
+
FilterInteger
|
|
174
|
+
: Integer
|
|
175
|
+
;
|
|
176
|
+
|
|
177
|
+
FilterDecimal
|
|
178
|
+
: Decimal
|
|
179
|
+
;
|
|
180
|
+
|
|
181
|
+
FilterIdentifierStepSeparator
|
|
182
|
+
: '.'
|
|
183
|
+
;
|
|
184
|
+
|
|
185
|
+
FilterIdentifier
|
|
186
|
+
: Identifier
|
|
187
|
+
;
|
|
188
|
+
|
|
189
|
+
Digit
|
|
190
|
+
: [0-9]
|
|
191
|
+
;
|
|
192
|
+
|
|
193
|
+
HexDigit
|
|
194
|
+
: [0-9] | [A-F]
|
|
195
|
+
;
|
|
196
|
+
|
|
197
|
+
FilterDate
|
|
198
|
+
: '%22' Digit Digit Digit Digit '-' Digit Digit '-' Digit Digit '%22'
|
|
199
|
+
;
|
|
200
|
+
|
|
201
|
+
FilterString
|
|
202
|
+
: '"' ( ~'"' )* '"'
|
|
203
|
+
;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
parser grammar MILQLParser;
|
|
2
|
+
|
|
3
|
+
options { tokenVocab=MILQLLexer; }
|
|
4
|
+
|
|
5
|
+
// @header {
|
|
6
|
+
// }
|
|
7
|
+
|
|
8
|
+
parameterList
|
|
9
|
+
: ( parameter
|
|
10
|
+
( ( Ampersand
|
|
11
|
+
| EndEmbedMode
|
|
12
|
+
| EndSortMode
|
|
13
|
+
| EndFilterMode
|
|
14
|
+
| EndFieldMode
|
|
15
|
+
)
|
|
16
|
+
parameter
|
|
17
|
+
)*
|
|
18
|
+
)?
|
|
19
|
+
EOF
|
|
20
|
+
;
|
|
21
|
+
|
|
22
|
+
parameter
|
|
23
|
+
: Embed pathExpressions # EmbedPathParameter
|
|
24
|
+
| Embed depthExpression # EmbedDepthParameter
|
|
25
|
+
| Field fieldExpressions # FieldParameter
|
|
26
|
+
| Filter filterExpression # FilterParameter
|
|
27
|
+
| Limit Integer # LimitParameter
|
|
28
|
+
| Offset Integer # OffsetParameter
|
|
29
|
+
| Search ParameterValue # SearchParameter
|
|
30
|
+
| Sort sortExpressions # SortParameter
|
|
31
|
+
;
|
|
32
|
+
|
|
33
|
+
pathExpressions
|
|
34
|
+
: pathExpression ( PathExpressionSeparator pathExpression )*
|
|
35
|
+
;
|
|
36
|
+
|
|
37
|
+
pathExpression
|
|
38
|
+
: pathStepExpression ( PathStepSeparator pathStepExpression )*
|
|
39
|
+
;
|
|
40
|
+
|
|
41
|
+
pathStepExpression
|
|
42
|
+
: PathStepIdentifier
|
|
43
|
+
;
|
|
44
|
+
|
|
45
|
+
depthExpression
|
|
46
|
+
: Depth DepthValue
|
|
47
|
+
;
|
|
48
|
+
|
|
49
|
+
fieldExpressions
|
|
50
|
+
: fieldExpression ( FieldExpressionSeparator fieldExpression )*
|
|
51
|
+
;
|
|
52
|
+
|
|
53
|
+
fieldExpression
|
|
54
|
+
: fieldIdentifierPath
|
|
55
|
+
;
|
|
56
|
+
|
|
57
|
+
fieldIdentifierPath
|
|
58
|
+
: fieldIdentifierStep ( FieldIdentifierStepSeparator fieldIdentifierStep )*
|
|
59
|
+
;
|
|
60
|
+
|
|
61
|
+
fieldIdentifierStep
|
|
62
|
+
: FieldIdentifier
|
|
63
|
+
;
|
|
64
|
+
|
|
65
|
+
filterExpression
|
|
66
|
+
: filterExpression AndFilter filterExpression # AndFilterExpression
|
|
67
|
+
| filterExpression OrFilter filterExpression # OrFilterExpression
|
|
68
|
+
| filterIdentifierPath FilterOperation filterTestValue # FilterTermExpression
|
|
69
|
+
| FilterLeftParens filterExpression FilterRightParens # FilterGroup
|
|
70
|
+
;
|
|
71
|
+
|
|
72
|
+
filterIdentifierPath
|
|
73
|
+
: filterIdentifierStep ( FilterIdentifierStepSeparator filterIdentifierStep )*
|
|
74
|
+
;
|
|
75
|
+
|
|
76
|
+
filterIdentifierStep
|
|
77
|
+
: FilterIdentifier
|
|
78
|
+
;
|
|
79
|
+
|
|
80
|
+
filterTestValue
|
|
81
|
+
: FilterInteger # FilterInteger
|
|
82
|
+
| FilterDecimal # FilterDecimal
|
|
83
|
+
| FilterDate # FilterDate
|
|
84
|
+
| FilterBlank # FilterBlank
|
|
85
|
+
| FilterString # FilterString
|
|
86
|
+
;
|
|
87
|
+
|
|
88
|
+
sortExpressions
|
|
89
|
+
: sortExpression ( SortExpressionSeparator sortExpression )*
|
|
90
|
+
;
|
|
91
|
+
|
|
92
|
+
sortExpression
|
|
93
|
+
: ReverseSort? sortIdentifierPath
|
|
94
|
+
;
|
|
95
|
+
|
|
96
|
+
sortIdentifierPath
|
|
97
|
+
: sortIdentifierStep ( SortIdentifierStepSeparator sortIdentifierStep )*
|
|
98
|
+
;
|
|
99
|
+
|
|
100
|
+
sortIdentifierStep
|
|
101
|
+
: SortIdentifier
|
|
102
|
+
;
|
package/tempTest.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
const { Parser, HQLTransform } = require('./index');
|
|
2
|
+
const hql = new HQLTransform('User');
|
|
3
|
+
Parser.parse('filters=name::eq::"Alice"&sort=-createdAt&limit=10&offset=5&q=hello', hql);
|
|
4
|
+
console.log(hql.query);
|
|
5
|
+
console.log(JSON.stringify(hql.parameters));
|
|
6
|
+
console.log(hql.limit, hql.offset);
|