diff --git a/licenses.yaml b/licenses.yaml
index 2eb60d9600f8..4d9770c418b2 100644
--- a/licenses.yaml
+++ b/licenses.yaml
@@ -5143,7 +5143,7 @@ license_category: binary
module: web-console
license_name: Apache License version 2.0
copyright: Imply Data
-version: 0.10.7
+version: 0.11.6
---
diff --git a/pom.xml b/pom.xml
index af6676465699..9ef32061c73b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -100,8 +100,6 @@
3.10.6.Final
1.3.1
4.1.63.Final
- v10.24.0
- 6.14.11
42.2.14
3.11.0
1.7.12
diff --git a/web-console/e2e-tests/tutorial-batch.spec.ts b/web-console/e2e-tests/tutorial-batch.spec.ts
index ebb95448cab3..32ff73c83e42 100644
--- a/web-console/e2e-tests/tutorial-batch.spec.ts
+++ b/web-console/e2e-tests/tutorial-batch.spec.ts
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-import { SqlRef } from 'druid-query-toolkit';
+import { SqlTableRef } from 'druid-query-toolkit';
import * as playwright from 'playwright-chromium';
import { DatasourcesOverview } from './component/datasources/overview';
@@ -167,7 +167,7 @@ async function validateDatasourceStatus(page: playwright.Page, datasourceName: s
async function validateQuery(page: playwright.Page, datasourceName: string) {
const queryOverview = new QueryOverview(page, UNIFIED_CONSOLE_URL);
- const query = `SELECT * FROM ${SqlRef.table(datasourceName)} ORDER BY __time`;
+ const query = `SELECT * FROM ${SqlTableRef.create(datasourceName)} ORDER BY __time`;
const results = await queryOverview.runQuery(query);
expect(results).toBeDefined();
expect(results.length).toBeGreaterThan(0);
diff --git a/web-console/lib/keywords.js b/web-console/lib/keywords.js
index 4a8c249ed0eb..bf7d9004cc07 100644
--- a/web-console/lib/keywords.js
+++ b/web-console/lib/keywords.js
@@ -46,6 +46,13 @@ exports.SQL_KEYWORDS = [
'OUTER',
'FULL',
'CROSS',
+ 'USING',
+ 'FETCH',
+ 'FIRST',
+ 'NEXT',
+ 'ROW',
+ 'ROWS',
+ 'ONLY',
];
exports.SQL_EXPRESSION_PARTS = [
diff --git a/web-console/package-lock.json b/web-console/package-lock.json
index 9f848c918da9..03550bac283b 100644
--- a/web-console/package-lock.json
+++ b/web-console/package-lock.json
@@ -7905,11 +7905,11 @@
}
},
"druid-query-toolkit": {
- "version": "0.10.7",
- "resolved": "https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.10.7.tgz",
- "integrity": "sha512-+1f8LrGTi0FML8H5Rfou6fJGlEtex7gWwsS9fDgxL1fs7eJkdaTrgvCUT/sFxfR3HmcEBktbmS4mShiBZztLoQ==",
+ "version": "0.11.6",
+ "resolved": "https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.11.6.tgz",
+ "integrity": "sha512-ThOhXW0CCEf08be+qpc4GwbSIewXZPoJViEAr0qx4s9B57vTIlz8VTdfrC0ei/r2PjfGNg+lx1RZAmvsbfo2tA==",
"requires": {
- "tslib": "^2.0.2"
+ "tslib": "^2.2.0"
}
},
"duplexer": {
diff --git a/web-console/package.json b/web-console/package.json
index 0ae5ea013653..4cedded80e71 100644
--- a/web-console/package.json
+++ b/web-console/package.json
@@ -8,9 +8,6 @@
"type": "git",
"url": "https://github.com/apache/druid"
},
- "engines": {
- "node": ">=10"
- },
"jest": {
"preset": "ts-jest",
"testEnvironment": "jsdom",
@@ -54,6 +51,13 @@
"check-licenses": "license-checker --production --onlyAllow 'Apache-1.1;Apache-2.0;BSD-2-Clause;BSD-3-Clause;0BSD;MIT;CC0-1.0' --summary",
"start": "webpack serve --hot --open"
},
+ "engines": {
+ "node": ">=14"
+ },
+ "volta": {
+ "node": "14.16.1",
+ "npm": "6.14.12"
+ },
"dependencies": {
"@blueprintjs/core": "^3.33.0",
"@blueprintjs/datetime": "^3.19.2",
@@ -67,7 +71,7 @@
"d3-axis": "^1.0.12",
"d3-scale": "^3.2.0",
"d3-selection": "^1.4.0",
- "druid-query-toolkit": "^0.10.7",
+ "druid-query-toolkit": "^0.11.6",
"file-saver": "^2.0.2",
"fontsource-open-sans": "^3.0.9",
"has-own-prop": "^2.0.0",
diff --git a/web-console/pom.xml b/web-console/pom.xml
index 599b34c9eb4e..11ff0eae14a5 100644
--- a/web-console/pom.xml
+++ b/web-console/pom.xml
@@ -34,6 +34,8 @@
${project.build.directory}/resources
false
+ v14.16.1
+ 6.14.12
diff --git a/web-console/src/utils/druid-query.spec.ts b/web-console/src/utils/druid-query.spec.ts
index 55fd336920da..5b3f51936740 100644
--- a/web-console/src/utils/druid-query.spec.ts
+++ b/web-console/src/utils/druid-query.spec.ts
@@ -94,6 +94,45 @@ describe('DruidQuery', () => {
`);
});
+ it('works for bad double quotes 1', () => {
+ const sql = sane`
+ SELECT * FROM “wikipedia”
+ `;
+ const suggestion = DruidError.getSuggestion(
+ 'Lexical error at line 6, column 60. Encountered: "\\u201c" (8220), after : ""',
+ );
+ expect(suggestion!.label).toEqual(`Replace fancy quotes with ASCII quotes`);
+ expect(suggestion!.fn(sql)).toEqual(sane`
+ SELECT * FROM "wikipedia"
+ `);
+ });
+
+ it('works for bad double quotes 2', () => {
+ const sql = sane`
+ SELECT * FROM ”wikipedia”
+ `;
+ const suggestion = DruidError.getSuggestion(
+ 'Lexical error at line 6, column 60. Encountered: "\\u201d" (8221), after : ""',
+ );
+ expect(suggestion!.label).toEqual(`Replace fancy quotes with ASCII quotes`);
+ expect(suggestion!.fn(sql)).toEqual(sane`
+ SELECT * FROM "wikipedia"
+ `);
+ });
+
+ it('works for bad double quotes 3', () => {
+ const sql = sane`
+ SELECT * FROM "wikipedia" WHERE "channel" = ‘lol‘
+ `;
+ const suggestion = DruidError.getSuggestion(
+ 'Lexical error at line 1, column 45. Encountered: "\\u2018" (8216), after : ""',
+ );
+ expect(suggestion!.label).toEqual(`Replace fancy quotes with ASCII quotes`);
+ expect(suggestion!.fn(sql)).toEqual(sane`
+ SELECT * FROM "wikipedia" WHERE "channel" = 'lol'
+ `);
+ });
+
it('works for incorrectly quoted literal', () => {
const sql = sane`
SELECT *
@@ -111,6 +150,29 @@ describe('DruidQuery', () => {
`);
});
+ it('works for incorrectly quoted AS alias', () => {
+ const sql = sane`
+ SELECT
+ channel,
+ COUNT(*) AS 'Count'
+ FROM wikipedia
+ GROUP BY 1
+ ORDER BY 2 DESC
+ `;
+ const suggestion = DruidError.getSuggestion(
+ `Encountered "\\'Count\\'" at line 3, column 15...`,
+ );
+ expect(suggestion!.label).toEqual(`Replace 'Count' with "Count"`);
+ expect(suggestion!.fn(sql)).toEqual(sane`
+ SELECT
+ channel,
+ COUNT(*) AS "Count"
+ FROM wikipedia
+ GROUP BY 1
+ ORDER BY 2 DESC
+ `);
+ });
+
it('removes comma (,) before FROM', () => {
const suggestion = DruidError.getSuggestion(
`Encountered "FROM" at line 1, column 14. Was expecting one of: "ABS" ...`,
@@ -121,6 +183,28 @@ describe('DruidQuery', () => {
);
});
+ it('removes comma (,) before ORDER', () => {
+ const suggestion = DruidError.getSuggestion(
+ `Encountered ", ORDER" at line 1, column 14. Was expecting one of: "ABS" ...`,
+ );
+ expect(suggestion!.label).toEqual(`Remove , before ORDER`);
+ expect(
+ suggestion!.fn(
+ `SELECT page FROM wikipedia WHERE channel = '#ar.wikipedia' GROUP BY 1, ORDER BY 1`,
+ ),
+ ).toEqual(`SELECT page FROM wikipedia WHERE channel = '#ar.wikipedia' GROUP BY 1 ORDER BY 1`);
+ });
+
+ it('removes trailing semicolon (;)', () => {
+ const suggestion = DruidError.getSuggestion(
+ `Encountered ";" at line 1, column 14. Was expecting one of: "ABS" ...`,
+ );
+ expect(suggestion!.label).toEqual(`Remove trailing ;`);
+ expect(suggestion!.fn(`SELECT page FROM wikipedia WHERE channel = '#ar.wikipedia';`)).toEqual(
+ `SELECT page FROM wikipedia WHERE channel = '#ar.wikipedia'`,
+ );
+ });
+
it('does nothing there there is nothing to do', () => {
const suggestion = DruidError.getSuggestion(
`Encountered "channel" at line 1, column 35. Was expecting one of: "EXCEPT" ...`,
diff --git a/web-console/src/utils/druid-query.ts b/web-console/src/utils/druid-query.ts
index 3b1fde1bb727..fd6ec42ecfd1 100644
--- a/web-console/src/utils/druid-query.ts
+++ b/web-console/src/utils/druid-query.ts
@@ -123,6 +123,22 @@ export class DruidError extends Error {
};
}
+ const matchLexical = /Lexical error at line (\d+), column (\d+).\s+Encountered: "\\u201\w"/.exec(
+ errorMessage,
+ );
+ if (matchLexical) {
+ return {
+ label: 'Replace fancy quotes with ASCII quotes',
+ fn: str => {
+ const newQuery = str
+ .replace(/[\u2018-\u201b]/gim, `'`)
+ .replace(/[\u201c-\u201f]/gim, `"`);
+ if (newQuery === str) return;
+ return newQuery;
+ },
+ };
+ }
+
// Incorrect quoting on table
// ex: org.apache.calcite.runtime.CalciteContextException: From line 3, column 17 to line 3, column 31: Column '#ar.wikipedia' not found in any table
const matchQuotes = /org.apache.calcite.runtime.CalciteContextException: From line (\d+), column (\d+) to line \d+, column \d+: Column '([^']+)' not found in any table/.exec(
@@ -144,12 +160,26 @@ export class DruidError extends Error {
};
}
+ // Single quotes on AS alias
+ const matchSingleQuotesAlias = /Encountered "\\'([\w-]+)\\'" at/i.exec(errorMessage);
+ if (matchSingleQuotesAlias) {
+ const alias = matchSingleQuotesAlias[1];
+ return {
+ label: `Replace '${alias}' with "${alias}"`,
+ fn: str => {
+ const newQuery = str.replace(new RegExp(`(AS\\s*)'(${alias})'`, 'gim'), '$1"$2"');
+ if (newQuery === str) return;
+ return newQuery;
+ },
+ };
+ }
+
// , before FROM
- const matchComma = /Encountered "(FROM)" at/i.exec(errorMessage);
- if (matchComma) {
- const fromKeyword = matchComma[1];
+ const matchCommaFrom = /Encountered "(FROM)" at/i.exec(errorMessage);
+ if (matchCommaFrom) {
+ const keyword = matchCommaFrom[1];
return {
- label: `Remove , before ${fromKeyword}`,
+ label: `Remove , before ${keyword}`,
fn: str => {
const newQuery = str.replace(/,(\s+FROM)/gim, '$1');
if (newQuery === str) return;
@@ -158,6 +188,33 @@ export class DruidError extends Error {
};
}
+ // , before GROUP, ORDER, or LIMIT
+ const matchComma = /Encountered ", (GROUP|ORDER|LIMIT)" at/i.exec(errorMessage);
+ if (matchComma) {
+ const keyword = matchComma[1];
+ return {
+ label: `Remove , before ${keyword}`,
+ fn: str => {
+ const newQuery = str.replace(new RegExp(`,(\\s+${keyword})`, 'gim'), '$1');
+ if (newQuery === str) return;
+ return newQuery;
+ },
+ };
+ }
+
+ // ; at the end
+ const matchSemicolon = /Encountered ";" at/i.exec(errorMessage);
+ if (matchSemicolon) {
+ return {
+ label: `Remove trailing ;`,
+ fn: str => {
+ const newQuery = str.replace(/;+(\s*)$/m, '$1');
+ if (newQuery === str) return;
+ return newQuery;
+ },
+ };
+ }
+
return;
}
diff --git a/web-console/src/views/datasource-view/datasource-view.tsx b/web-console/src/views/datasource-view/datasource-view.tsx
index 8657a9a46587..1d4d27df45db 100644
--- a/web-console/src/views/datasource-view/datasource-view.tsx
+++ b/web-console/src/views/datasource-view/datasource-view.tsx
@@ -19,7 +19,7 @@
import { FormGroup, InputGroup, Intent, MenuItem, Switch } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import classNames from 'classnames';
-import { SqlQuery, SqlRef } from 'druid-query-toolkit';
+import { SqlQuery, SqlTableRef } from 'druid-query-toolkit';
import React from 'react';
import ReactTable, { Filter } from 'react-table';
@@ -824,7 +824,7 @@ ORDER BY 1`;
goToActions.push({
icon: IconNames.APPLICATION,
title: 'Query with SQL',
- onAction: () => goToQuery(SqlQuery.create(SqlRef.table(datasource)).toString()),
+ onAction: () => goToQuery(SqlQuery.create(SqlTableRef.create(datasource)).toString()),
});
}
diff --git a/web-console/src/views/query-view/column-tree/column-tree-menu/number-menu-items/number-menu-items.tsx b/web-console/src/views/query-view/column-tree/column-tree-menu/number-menu-items/number-menu-items.tsx
index e1ecd9b7663a..c85ddbe9d391 100644
--- a/web-console/src/views/query-view/column-tree/column-tree-menu/number-menu-items/number-menu-items.tsx
+++ b/web-console/src/views/query-view/column-tree/column-tree-menu/number-menu-items/number-menu-items.tsx
@@ -43,7 +43,7 @@ export const NumberMenuItems = React.memo(function NumberMenuItems(props: Number