Skip to content

Commit 30dec4b

Browse files
authored
Continuation of the work on the SET statement (#91)
* allow parsing of context only * more tools around adding context to random text * fix set quotes * add tests * changeset * add changeUnderlyingExpression * use it.each * use it.each everywhere * failing JSON_OBJECT tests * add key value parsing * add tests * rewrite more tests to use it.each * add more tests * typo * add more tests * add IDE setting * handle nested JSON_OBJECTS
1 parent 203e150 commit 30dec4b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2214
-429
lines changed

.changeset/lemon-hairs-appear.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'druid-query-toolkit': minor
3+
---
4+
5+
Allow parsing SET statements in an otherwise unparsable string and more integrations"

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99

1010
/node_modules/
1111
/coverage/
12+
/coverage_old/
1213
/build/
1314
/dist/
1415
/types/
1516
/src/sql/parser/index.ts
1617
/src/sql/parser/.DS_Store
18+
CLAUDE.md
1719

1820
# TypeScript cache
1921
*.tsbuildinfo
20-
21-
_old/

.idea/google-java-format.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,13 @@ ORDER BY 5 DESC
7575
*/
7676
```
7777

78-
For more examples check out the unit tests.
78+
For more examples, check out the unit tests.
7979

8080
#### ToDo
8181

8282
Not every valid DruidSQL construct can currently be parsed, the following snippets are not currently supported:
8383

8484
- `(a, b) IN (subquery)`
85-
- Support `FROM "wikipedia_k" USING (k)`
8685

8786
## License
8887

script/compile-peg.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ try {
2424
parser = peg.generate(header + '\n\n' + rules, {
2525
output: 'source',
2626
plugins: [tspegjs],
27+
allowedStartRules: ['Start', 'StartSetStatementsOnly'],
2728
});
2829
} catch (e) {
2930
console.error('Failed to compile');

src/filter-pattern/unify.spec.ts

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ function backAndForthNotCustom(expression: string): void {
3636
}
3737

3838
describe('filter-pattern', () => {
39-
it('fixed points', () => {
40-
const expressions: string[] = [
39+
describe('fixed point expressions', () => {
40+
it.each([
4141
`"lol" = 'hello'`,
4242
`"lol" <> 'hello'`,
4343
`"lol" IN ('hello', 'goodbye')`,
@@ -67,28 +67,19 @@ describe('filter-pattern', () => {
6767
`TIMESTAMP '2022-06-30 22:56:14.123' <= "__time" AND "__time" <= TIMESTAMP '2022-06-30 22:56:15.923'`,
6868
`TIMESTAMP '2022-06-30 22:56:14.123' < "__time" AND "__time" <= TIMESTAMP '2022-06-30 22:56:15.923'`,
6969
`(TIME_FLOOR(MAX_DATA_TIME(), 'P3M', NULL, 'Etc/UTC') <= "DIM:__time" AND "DIM:__time" < TIME_SHIFT(TIME_FLOOR(MAX_DATA_TIME(), 'P3M', NULL, 'Etc/UTC'), 'P1D', 1, 'Etc/UTC'))`,
70-
];
71-
72-
for (const expression of expressions) {
73-
try {
74-
backAndForthNotCustom(expression);
75-
} catch (e) {
76-
console.log(`Problem with: \`${expression}\``);
77-
throw e;
78-
}
79-
}
70+
])('correctly handles expression: %s', expression => {
71+
backAndForthNotCustom(expression);
72+
});
8073
});
8174

82-
it('invalid expressions', () => {
83-
const expressions: string[] = [
75+
describe('invalid expressions', () => {
76+
it.each([
8477
`"__time" >= TIMESTAMP '2022-06-30 22:56:15.923' AND TIMESTAMP '2021-06-30 22:56:14.123' >= "__time"`,
8578
`TIMESTAMP '2021-06-30 22:56:14.123' >= "__time" AND "__time" >= TIMESTAMP '2022-06-30 22:56:15.923'`,
86-
];
87-
88-
for (const expression of expressions) {
79+
])('correctly handles invalid expression: %s', expression => {
8980
const pattern = fitFilterPattern(SqlExpression.parse(expression));
9081
expect(pattern.type).toEqual('custom');
91-
}
82+
});
9283
});
9384

9485
describe('fitFilterPattern', () => {

src/introspect/introspect.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export class Introspect {
4040
}
4141

4242
static getQueryColumnIntrospectionQuery(query: SqlQuery | SqlTable): SqlQuery {
43-
return SqlQuery.create(query).changeLimitValue(0);
43+
return SqlQuery.selectStarFrom(query).changeLimitValue(0);
4444
}
4545

4646
static getQueryColumnIntrospectionPayload(

src/sql/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export * from './sql-case/sql-when-then-part';
3838
export * from './sql-case/sql-case';
3939
export * from './sql-alias/sql-alias';
4040
export * from './sql-labeled-expression/sql-labeled-expression';
41+
export * from './sql-key-value/sql-key-value';
4142
export * from './sql-window-spec/sql-window-spec';
4243
export * from './sql-window-spec/sql-frame-bound';
4344
export * from './sql-set-statement/sql-set-statement';

src/sql/parser/druidsql.pegjs

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,31 @@
1212
* limitations under the License.
1313
*/
1414

15-
Start = initial:_? thing:(SqlQueryWithPossibleContext / SqlAlias) final:_sc?
15+
Start = initial:_ thing:(SqlQueryWithPossibleContext / SqlAlias) final:_sc
1616
{
1717
if (initial) thing = thing.changeSpace('initial', initial);
1818
if (final) thing = thing.changeSpace('final', final);
1919
return thing;
2020
}
2121

22+
StartSetStatementsOnly = spaceBefore:_ statements:(SqlSetStatement _sc)* rest:$(.*)
23+
{
24+
let ret = {
25+
spaceBefore: spaceBefore,
26+
rest: rest
27+
}
28+
29+
if (statements.length) {
30+
ret.contextStatements = new S.SeparatedArray(
31+
statements.map(function(x) { return x[0] }),
32+
statements.map(function(x) { return x[1] }).slice(0, statements.length - 1)
33+
);
34+
ret.spaceAfter = statements[statements.length - 1][1];
35+
}
36+
37+
return ret;
38+
}
39+
2240
// ------------------------------
2341

2442
SqlAlias = expression:Expression alias:((_ AsToken)? _ RefNameAlias)? columns:(_ SqlColumnList)?
@@ -62,6 +80,38 @@ SqlLabeledExpression = label:RefNameAlias preArrow:_ "=>" postArrow:_ expression
6280
});
6381
}
6482

83+
SqlKeyValue = LongKeyValueForm / ShortKeyValueForm
84+
85+
LongKeyValueForm = keyToken:KeyToken postKey:_ key:Expression postKeyExpression:_ valueToken:ValueToken preValueExpression:_ value:Expression
86+
{
87+
return new S.SqlKeyValue({
88+
key: key,
89+
value: value,
90+
spacing: {
91+
postKey: postKey,
92+
postKeyExpression: postKeyExpression,
93+
preValueExpression: preValueExpression
94+
},
95+
keywords: {
96+
key: keyToken,
97+
value: valueToken
98+
}
99+
});
100+
}
101+
102+
ShortKeyValueForm = key:Expression postKeyExpression:_ ":" preValueExpression:_ value:Expression
103+
{
104+
return new S.SqlKeyValue({
105+
key: key,
106+
value: value,
107+
short: true,
108+
spacing: {
109+
postKeyExpression: postKeyExpression,
110+
preValueExpression: preValueExpression
111+
}
112+
});
113+
}
114+
65115
SqlExtendClause =
66116
extend:(ExtendToken _)?
67117
OpenParen
@@ -1010,6 +1060,7 @@ Function =
10101060
/ TimestampAddDiffFunction
10111061
/ PositionFunction
10121062
/ JsonValueReturningFunction
1063+
/ JsonObjectFunction
10131064
/ ArrayFunction
10141065
/ NakedFunction
10151066

@@ -1171,6 +1222,32 @@ JsonValueReturningFunction =
11711222
});
11721223
}
11731224

1225+
JsonObjectFunction =
1226+
functionName:JsonObjectToken
1227+
preLeftParen:_
1228+
OpenParen
1229+
postLeftParen:_
1230+
head:SqlKeyValue?
1231+
tail:(CommaSeparator SqlKeyValue)*
1232+
postArguments:_
1233+
CloseParen
1234+
{
1235+
var value = {
1236+
functionName: makeFunctionName(functionName)
1237+
};
1238+
var spacing = value.spacing = {
1239+
preLeftParen: preLeftParen,
1240+
postLeftParen: postLeftParen
1241+
};
1242+
1243+
if (head) {
1244+
value.args = makeSeparatedArray(head, tail);
1245+
spacing.postArguments = postArguments;
1246+
}
1247+
1248+
return new S.SqlFunction(value);
1249+
}
1250+
11741251
ExtractFunction =
11751252
functionName:(ExtractToken / ('"' ExtractToken '"'))
11761253
preLeftParen:_
@@ -1881,6 +1958,9 @@ IntoToken = $("INTO"i !IdentifierPart)
18811958
IsToken = $("IS"i !IdentifierPart)
18821959
JoinToken = $("JOIN"i !IdentifierPart)
18831960
JsonValueToken = $("JSON_VALUE"i !IdentifierPart)
1961+
JsonObjectToken = $("JSON_OBJECT"i !IdentifierPart)
1962+
KeyToken = $("KEY"i !IdentifierPart)
1963+
ValueToken = $("VALUE"i !IdentifierPart)
18841964
LeadingToken = $("LEADING"i !IdentifierPart)
18851965
LikeToken = $("LIKE"i !IdentifierPart)
18861966
LimitToken = $("LIMIT"i !IdentifierPart)

src/sql/sql-alias/sql-alias.spec.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,49 @@ describe('SqlAlias', () => {
172172
});
173173

174174
describe('.create', () => {
175-
expect(
176-
SqlAlias.create(SqlAlias.create(SqlColumn.create('X'), 'name1'), 'name2').toString(),
177-
).toEqual('"X" AS "name2"');
175+
it('overwrites existing alias when aliasing an already aliased expression', () => {
176+
expect(
177+
SqlAlias.create(SqlAlias.create(SqlColumn.create('X'), 'name1'), 'name2').toString(),
178+
).toEqual('"X" AS "name2"');
179+
});
180+
181+
it('creates a simple alias with string column and string alias', () => {
182+
expect(SqlAlias.create(SqlColumn.create('col1'), 'alias1').toString()).toEqual(
183+
'"col1" AS "alias1"',
184+
);
185+
});
186+
187+
it('creates an alias with RefName object as alias', () => {
188+
const refName = RefName.create('myAlias', true);
189+
expect(SqlAlias.create(SqlColumn.create('col1'), refName).toString()).toEqual(
190+
'"col1" AS "myAlias"',
191+
);
192+
});
193+
194+
it('auto-quotes aliases that are reserved keywords', () => {
195+
expect(SqlAlias.create(SqlColumn.create('col1'), 'select').toString()).toEqual(
196+
'"col1" AS "select"',
197+
);
198+
});
199+
200+
it('forces quotes when forceQuotes is true', () => {
201+
expect(SqlAlias.create(SqlColumn.create('col1'), 'normal', true).toString()).toEqual(
202+
'"col1" AS "normal"',
203+
);
204+
});
205+
206+
it('adds parentheses to SqlQuery expressions', () => {
207+
const query = SqlQuery.create('tbl');
208+
const aliasedQuery = SqlAlias.create(query, 'subq');
209+
const result = aliasedQuery.toString();
210+
211+
// Check that the result contains the main components rather than exact formatting
212+
expect(result).toContain('(');
213+
expect(result).toContain(')');
214+
expect(result).toContain('SELECT');
215+
expect(result).toContain('FROM "tbl"');
216+
expect(result).toContain('AS "subq"');
217+
});
178218
});
179219

180220
describe('#changeAlias', () => {

0 commit comments

Comments
 (0)