From 52af9b6b7f17feb99313bbb5731f1556baea1c87 Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Wed, 5 May 2021 09:56:44 -0700 Subject: [PATCH 1/8] add more query fixing auto suggestions --- licenses.yaml | 2 +- web-console/package-lock.json | 8 ++-- web-console/package.json | 2 +- web-console/src/utils/druid-query.spec.ts | 45 +++++++++++++++++++++ web-console/src/utils/druid-query.ts | 49 +++++++++++++++++++++-- 5 files changed, 96 insertions(+), 10 deletions(-) diff --git a/licenses.yaml b/licenses.yaml index 77edbdfbb504..148def9b2ef0 100644 --- a/licenses.yaml +++ b/licenses.yaml @@ -5142,7 +5142,7 @@ license_category: binary module: web-console license_name: Apache License version 2.0 copyright: Imply Data -version: 0.10.7 +version: 0.10.11 --- diff --git a/web-console/package-lock.json b/web-console/package-lock.json index 9f848c918da9..fc87583fb6f6 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.10.11", + "resolved": "https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.10.11.tgz", + "integrity": "sha512-Fu+MN2slpSBvZwrBunJLzt6wiAjWn5GxlQcDaMVpK5/ggZ3+C0M/ozz1pFxi8TqG+I/oTPLTVfNylswOTW3TgA==", "requires": { - "tslib": "^2.0.2" + "tslib": "^2.2.0" } }, "duplexer": { diff --git a/web-console/package.json b/web-console/package.json index 0ae5ea013653..9ca6059cd293 100644 --- a/web-console/package.json +++ b/web-console/package.json @@ -67,7 +67,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.10.11", "file-saver": "^2.0.2", "fontsource-open-sans": "^3.0.9", "has-own-prop": "^2.0.0", diff --git a/web-console/src/utils/druid-query.spec.ts b/web-console/src/utils/druid-query.spec.ts index 55fd336920da..2738f733a7cb 100644 --- a/web-console/src/utils/druid-query.spec.ts +++ b/web-console/src/utils/druid-query.spec.ts @@ -111,6 +111,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 +144,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..78c9d311ad6c 100644 --- a/web-console/src/utils/druid-query.ts +++ b/web-console/src/utils/druid-query.ts @@ -144,12 +144,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 +172,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; } From cb92087603858a465547d79b4ab2b12a26745d3d Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Thu, 13 May 2021 09:29:23 -0700 Subject: [PATCH 2/8] update query gen --- licenses.yaml | 2 +- web-console/lib/keywords.js | 1 + web-console/package-lock.json | 6 +++--- web-console/package.json | 12 ++++++++---- .../number-menu-items/number-menu-items.tsx | 18 ++++++++++++------ .../string-menu-items/string-menu-items.tsx | 18 ++++++++++++------ .../time-menu-items/time-menu-items.tsx | 18 ++++++++++++------ .../query-view/column-tree/column-tree.tsx | 12 +++++++----- .../query-view/query-output/query-output.tsx | 11 +++-------- 9 files changed, 59 insertions(+), 39 deletions(-) diff --git a/licenses.yaml b/licenses.yaml index 148def9b2ef0..1b432610e4ca 100644 --- a/licenses.yaml +++ b/licenses.yaml @@ -5142,7 +5142,7 @@ license_category: binary module: web-console license_name: Apache License version 2.0 copyright: Imply Data -version: 0.10.11 +version: 0.11.2 --- diff --git a/web-console/lib/keywords.js b/web-console/lib/keywords.js index 4a8c249ed0eb..358623c61491 100644 --- a/web-console/lib/keywords.js +++ b/web-console/lib/keywords.js @@ -46,6 +46,7 @@ exports.SQL_KEYWORDS = [ 'OUTER', 'FULL', 'CROSS', + 'USING', ]; exports.SQL_EXPRESSION_PARTS = [ diff --git a/web-console/package-lock.json b/web-console/package-lock.json index fc87583fb6f6..076b184689cb 100644 --- a/web-console/package-lock.json +++ b/web-console/package-lock.json @@ -7905,9 +7905,9 @@ } }, "druid-query-toolkit": { - "version": "0.10.11", - "resolved": "https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.10.11.tgz", - "integrity": "sha512-Fu+MN2slpSBvZwrBunJLzt6wiAjWn5GxlQcDaMVpK5/ggZ3+C0M/ozz1pFxi8TqG+I/oTPLTVfNylswOTW3TgA==", + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.11.2.tgz", + "integrity": "sha512-mivF8CMOGc1Mkik2DT6xqJxs5Ai8Xz8Badg4Pt8rRY9aSYcGaHIkgP83R7B6RyfrTh2y+ZqCXwPpYyEn+gVllw==", "requires": { "tslib": "^2.2.0" } diff --git a/web-console/package.json b/web-console/package.json index 9ca6059cd293..efccb36bfa73 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.11", + "druid-query-toolkit": "^0.11.2", "file-saver": "^2.0.2", "fontsource-open-sans": "^3.0.9", "has-own-prop": "^2.0.0", 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 { - onQueryChange(parsedQuery.addToWhere(clause)); + onQueryChange(parsedQuery.addWhere(clause)); }} /> ); @@ -82,7 +82,13 @@ export const NumberMenuItems = React.memo(function NumberMenuItems(props: Number { - onQueryChange(parsedQuery.addToGroupBy(ex.as(alias)), true); + onQueryChange( + parsedQuery.addSelect(ex.as(alias), { + insertIndex: 'last-grouping', + addToGroupBy: 'end', + }), + true, + ); }} /> ); @@ -101,15 +107,15 @@ export const NumberMenuItems = React.memo(function NumberMenuItems(props: Number function renderRemoveGroupBy(): JSX.Element | undefined { const { columnName, parsedQuery, onQueryChange } = props; - const selectIndex = parsedQuery.getSelectIndexForColumn(columnName); - if (!parsedQuery.isGroupedSelectIndex(selectIndex)) return; + const groupedSelectIndexes = parsedQuery.getGroupedSelectIndexesForColumn(columnName); + if (!groupedSelectIndexes.length) return; return ( { - onQueryChange(parsedQuery.removeSelectIndex(selectIndex), true); + onQueryChange(parsedQuery.removeSelectIndexes(groupedSelectIndexes), true); }} /> ); @@ -125,7 +131,7 @@ export const NumberMenuItems = React.memo(function NumberMenuItems(props: Number { - onQueryChange(parsedQuery.addSelectExpression(ex.as(alias)), true); + onQueryChange(parsedQuery.addSelect(ex.as(alias)), true); }} /> ); diff --git a/web-console/src/views/query-view/column-tree/column-tree-menu/string-menu-items/string-menu-items.tsx b/web-console/src/views/query-view/column-tree/column-tree-menu/string-menu-items/string-menu-items.tsx index 9268e702bb4c..86ab8ffdf936 100644 --- a/web-console/src/views/query-view/column-tree/column-tree-menu/string-menu-items/string-menu-items.tsx +++ b/web-console/src/views/query-view/column-tree/column-tree-menu/string-menu-items/string-menu-items.tsx @@ -49,7 +49,7 @@ export const StringMenuItems = React.memo(function StringMenuItems(props: String { - onQueryChange(parsedQuery.addToWhere(clause), run); + onQueryChange(parsedQuery.addWhere(clause), run); }} /> ); @@ -82,15 +82,15 @@ export const StringMenuItems = React.memo(function StringMenuItems(props: String function renderRemoveGroupBy(): JSX.Element | undefined { const { columnName, parsedQuery, onQueryChange } = props; - const selectIndex = parsedQuery.getSelectIndexForColumn(columnName); - if (!parsedQuery.isGroupedSelectIndex(selectIndex)) return; + const groupedSelectIndexes = parsedQuery.getGroupedSelectIndexesForColumn(columnName); + if (!groupedSelectIndexes.length) return; return ( { - onQueryChange(parsedQuery.removeSelectIndex(selectIndex), true); + onQueryChange(parsedQuery.removeSelectIndexes(groupedSelectIndexes), true); }} /> ); @@ -105,7 +105,13 @@ export const StringMenuItems = React.memo(function StringMenuItems(props: String { - onQueryChange(parsedQuery.addToGroupBy(alias ? ex.as(alias) : ex), true); + onQueryChange( + parsedQuery.addSelect(alias ? ex.as(alias) : ex, { + insertIndex: 'last-grouping', + addToGroupBy: 'end', + }), + true, + ); }} /> ); @@ -143,7 +149,7 @@ export const StringMenuItems = React.memo(function StringMenuItems(props: String { - onQueryChange(parsedQuery.addSelectExpression(ex.as(alias)), run); + onQueryChange(parsedQuery.addSelect(ex.as(alias)), run); }} /> ); diff --git a/web-console/src/views/query-view/column-tree/column-tree-menu/time-menu-items/time-menu-items.tsx b/web-console/src/views/query-view/column-tree/column-tree-menu/time-menu-items/time-menu-items.tsx index c96108ed0883..b8a45b285c14 100644 --- a/web-console/src/views/query-view/column-tree/column-tree-menu/time-menu-items/time-menu-items.tsx +++ b/web-console/src/views/query-view/column-tree/column-tree-menu/time-menu-items/time-menu-items.tsx @@ -124,7 +124,7 @@ export const TimeMenuItems = React.memo(function TimeMenuItems(props: TimeMenuIt { - onQueryChange(parsedQuery.removeColumnFromWhere(columnName).addToWhere(clause), true); + onQueryChange(parsedQuery.removeColumnFromWhere(columnName).addWhere(clause), true); }} /> ); @@ -180,15 +180,15 @@ export const TimeMenuItems = React.memo(function TimeMenuItems(props: TimeMenuIt function renderRemoveGroupBy(): JSX.Element | undefined { const { columnName, parsedQuery, onQueryChange } = props; - const selectIndex = parsedQuery.getSelectIndexForColumn(columnName); - if (!parsedQuery.isGroupedSelectIndex(selectIndex)) return; + const groupedSelectIndexes = parsedQuery.getGroupedSelectIndexesForColumn(columnName); + if (!groupedSelectIndexes.length) return; return ( { - onQueryChange(parsedQuery.removeSelectIndex(selectIndex), true); + onQueryChange(parsedQuery.removeSelectIndexes(groupedSelectIndexes), true); }} /> ); @@ -204,7 +204,13 @@ export const TimeMenuItems = React.memo(function TimeMenuItems(props: TimeMenuIt { - onQueryChange(parsedQuery.addToGroupBy(ex.as(alias)), true); + onQueryChange( + parsedQuery.addSelect(ex.as(alias), { + insertIndex: 'last-grouping', + addToGroupBy: 'end', + }), + true, + ); }} /> ); @@ -259,7 +265,7 @@ export const TimeMenuItems = React.memo(function TimeMenuItems(props: TimeMenuIt { - onQueryChange(parsedQuery.addSelectExpression(ex.as(alias)), true); + onQueryChange(parsedQuery.addSelect(ex.as(alias)), true); }} /> ); diff --git a/web-console/src/views/query-view/column-tree/column-tree.tsx b/web-console/src/views/query-view/column-tree/column-tree.tsx index c1b6a463508a..87a43a14a4fb 100644 --- a/web-console/src/views/query-view/column-tree/column-tree.tsx +++ b/web-console/src/views/query-view/column-tree/column-tree.tsx @@ -62,7 +62,7 @@ interface HandleColumnClickOptions { onQueryChange: (query: SqlQuery, run: boolean) => void; } -function handleColumnClick(options: HandleColumnClickOptions): void { +function handleColumnShow(options: HandleColumnClickOptions): void { const { columnSchema, columnTable, columnName, columnType, parsedQuery, onQueryChange } = options; let query: SqlQuery; @@ -93,8 +93,10 @@ function handleColumnClick(options: HandleColumnClickOptions): void { } let newSelectExpressions = query.selectExpressions; - for (const aggregate of aggregates) { - newSelectExpressions = newSelectExpressions.append(aggregate); + if (newSelectExpressions) { + for (const aggregate of aggregates) { + newSelectExpressions = newSelectExpressions.append(aggregate); + } } onQueryChange( @@ -284,7 +286,7 @@ export class ColumnTree extends React.PureComponent - onQueryChange(parsedQuery.addSelectExpression(COUNT_STAR), true) + onQueryChange(parsedQuery.addSelect(COUNT_STAR), true) } /> )} @@ -328,7 +330,7 @@ export class ColumnTree extends React.PureComponent { - handleColumnClick({ + handleColumnShow({ columnSchema: schemaName, columnTable: tableName, columnName: columnData.COLUMN_NAME, diff --git a/web-console/src/views/query-view/query-output/query-output.tsx b/web-console/src/views/query-view/query-output/query-output.tsx index bd3622da7edf..d662adf666ef 100644 --- a/web-console/src/views/query-view/query-output/query-output.tsx +++ b/web-console/src/views/query-view/query-output/query-output.tsx @@ -257,7 +257,7 @@ export const QueryOutput = React.memo(function QueryOutput(props: QueryOutputPro text={`${having ? 'Having' : 'Filter on'}: ${prettyPrintSql(clause)}`} onClick={() => { onQueryChange( - having ? parsedQuery.addToHaving(clause) : parsedQuery.addToWhere(clause), + having ? parsedQuery.addHaving(clause) : parsedQuery.addWhere(clause), true, ); }} @@ -370,15 +370,10 @@ export const QueryOutput = React.memo(function QueryOutput(props: QueryOutputPro setRenamingColumn(-1); if (renameTo && parsedQuery) { if (parsedQuery.hasStarInSelect()) return; - const selectExpression = parsedQuery.selectExpressions.get(renamingColumn); + const selectExpression = parsedQuery.getSelectExpressionForIndex(renamingColumn); if (!selectExpression) return; onQueryChange( - parsedQuery.changeSelectExpressions( - parsedQuery.selectExpressions.change( - renamingColumn, - selectExpression.changeAliasName(renameTo), - ), - ), + parsedQuery.changeSelect(renamingColumn, selectExpression.changeAliasName(renameTo)), true, ); } From b16a76b426e07f5f0793379809bd19e4f8ac0462 Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Wed, 19 May 2021 10:42:50 -0700 Subject: [PATCH 3/8] update toolkit --- web-console/e2e-tests/tutorial-batch.spec.ts | 4 +- web-console/lib/keywords.js | 6 +++ web-console/package-lock.json | 6 +-- web-console/package.json | 2 +- .../views/datasource-view/datasource-view.tsx | 4 +- .../string-menu-items/string-menu-items.tsx | 7 +-- .../query-view/column-tree/column-tree.tsx | 49 +++++++++---------- .../query-view/query-output/query-output.tsx | 7 +-- 8 files changed, 44 insertions(+), 41 deletions(-) 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 358623c61491..bf7d9004cc07 100644 --- a/web-console/lib/keywords.js +++ b/web-console/lib/keywords.js @@ -47,6 +47,12 @@ exports.SQL_KEYWORDS = [ '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 076b184689cb..03550bac283b 100644 --- a/web-console/package-lock.json +++ b/web-console/package-lock.json @@ -7905,9 +7905,9 @@ } }, "druid-query-toolkit": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.11.2.tgz", - "integrity": "sha512-mivF8CMOGc1Mkik2DT6xqJxs5Ai8Xz8Badg4Pt8rRY9aSYcGaHIkgP83R7B6RyfrTh2y+ZqCXwPpYyEn+gVllw==", + "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.2.0" } diff --git a/web-console/package.json b/web-console/package.json index efccb36bfa73..4cedded80e71 100644 --- a/web-console/package.json +++ b/web-console/package.json @@ -71,7 +71,7 @@ "d3-axis": "^1.0.12", "d3-scale": "^3.2.0", "d3-selection": "^1.4.0", - "druid-query-toolkit": "^0.11.2", + "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/src/views/datasource-view/datasource-view.tsx b/web-console/src/views/datasource-view/datasource-view.tsx index 3397c4aa52a8..6ed388d0b308 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'; @@ -800,7 +800,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/string-menu-items/string-menu-items.tsx b/web-console/src/views/query-view/column-tree/column-tree-menu/string-menu-items/string-menu-items.tsx index 86ab8ffdf936..694691753625 100644 --- a/web-console/src/views/query-view/column-tree/column-tree-menu/string-menu-items/string-menu-items.tsx +++ b/web-console/src/views/query-view/column-tree/column-tree-menu/string-menu-items/string-menu-items.tsx @@ -25,6 +25,7 @@ import { SqlLiteral, SqlQuery, SqlRef, + SqlTableRef, } from 'druid-query-toolkit'; import React from 'react'; @@ -159,7 +160,7 @@ export const StringMenuItems = React.memo(function StringMenuItems(props: String {aggregateMenuItem(SqlFunction.decorated('COUNT', 'DISTINCT', [ref]), `dist_${columnName}`)} {aggregateMenuItem( - SqlFunction.simple('COUNT', [SqlRef.STAR], ref.equal(EMPTY_LITERAL)), + SqlFunction.COUNT_STAR.addWhereExpression(ref.equal(EMPTY_LITERAL)), `filtered_dist_${columnName}`, false, )} @@ -187,7 +188,7 @@ export const StringMenuItems = React.memo(function StringMenuItems(props: String parsedQuery.addJoin( SqlJoinPart.create( 'LEFT', - SqlRef.column(table, schema).upgrade(), + SqlTableRef.create(table, schema), SqlRef.column(columnName, table, 'lookup').equal( SqlRef.column( lookupColumn === columnName ? originalTableColumn : 'XXX', @@ -208,7 +209,7 @@ export const StringMenuItems = React.memo(function StringMenuItems(props: String parsedQuery.addJoin( SqlJoinPart.create( 'INNER', - SqlRef.column(table, schema).upgrade(), + SqlTableRef.create(table, schema), SqlRef.column(columnName, table, 'lookup').equal( SqlRef.column( lookupColumn === columnName ? originalTableColumn : 'XXX', diff --git a/web-console/src/views/query-view/column-tree/column-tree.tsx b/web-console/src/views/query-view/column-tree/column-tree.tsx index 87a43a14a4fb..ed09850eff4c 100644 --- a/web-console/src/views/query-view/column-tree/column-tree.tsx +++ b/web-console/src/views/query-view/column-tree/column-tree.tsx @@ -19,13 +19,13 @@ import { HTMLSelect, ITreeNode, Menu, MenuItem, Popover, Position, Tree } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import { - SqlAlias, SqlComparison, SqlExpression, SqlFunction, SqlJoinPart, SqlQuery, SqlRef, + SqlTableRef, } from 'druid-query-toolkit'; import React, { ChangeEvent } from 'react'; @@ -65,33 +65,32 @@ interface HandleColumnClickOptions { function handleColumnShow(options: HandleColumnClickOptions): void { const { columnSchema, columnTable, columnName, columnType, parsedQuery, onQueryChange } = options; - let query: SqlQuery; - const columnRef = SqlRef.column(columnName); - if (columnSchema === 'druid') { - if (columnType === 'TIMESTAMP') { - query = TIME_QUERY.fillPlaceholders([columnRef, SqlRef.table(columnTable)]) as SqlQuery; - } else { - query = STRING_QUERY.fillPlaceholders([columnRef, SqlRef.table(columnTable)]) as SqlQuery; - } - } else { - query = STRING_QUERY.fillPlaceholders([ - columnRef, - SqlRef.table(columnTable, columnSchema), - ]) as SqlQuery; - } - + let from: SqlExpression; let where: SqlExpression | undefined; - let aggregates: SqlAlias[] = []; + let aggregates: SqlExpression[] = []; if (parsedQuery && parsedQuery.getFirstTableName() === columnTable) { + from = parsedQuery.getFirstFromExpression()!; where = parsedQuery.getWhereExpression(); aggregates = parsedQuery.getAggregateSelectExpressions(); } else if (columnSchema === 'druid') { + from = SqlTableRef.create(columnTable); where = LAST_DAY; + } else { + from = SqlTableRef.create(columnTable, columnSchema); } + if (!aggregates.length) { aggregates.push(COUNT_STAR); } + const columnRef = SqlRef.column(columnName); + let query: SqlQuery; + if (columnSchema === 'druid' && columnType === 'TIMESTAMP') { + query = TIME_QUERY.fillPlaceholders([columnRef, from]) as SqlQuery; + } else { + query = STRING_QUERY.fillPlaceholders([columnRef, from]) as SqlQuery; + } + let newSelectExpressions = query.selectExpressions; if (newSelectExpressions) { for (const aggregate of aggregates) { @@ -128,11 +127,11 @@ export function getJoinColumns(parsedQuery: SqlQuery, _table: string) { const firstOnExpression = parsedQuery.fromClause.joinParts.first().onExpression; if (firstOnExpression instanceof SqlComparison && firstOnExpression.op === '=') { const { lhs, rhs } = firstOnExpression; - if (lhs instanceof SqlRef && lhs.namespace === 'lookup') { - lookupColumn = lhs.column; + if (lhs instanceof SqlRef && lhs.getNamespace() === 'lookup') { + lookupColumn = lhs.getColumn(); } if (rhs instanceof SqlRef) { - originalTableColumn = rhs.column; + originalTableColumn = rhs.getColumn(); } } } @@ -168,7 +167,7 @@ export class ColumnTree extends React.PureComponent { const parsedQuery = props.getParsedQuery(); - const tableRef = SqlRef.table(tableName).as(); + const tableRef = SqlTableRef.create(tableName); const prettyTableRef = prettyPrintSql(tableRef); return ( @@ -176,7 +175,7 @@ export class ColumnTree extends React.PureComponent { - const tableRef = SqlRef.table( + const tableRef = SqlTableRef.create( tableName, schemaName === 'druid' ? undefined : schemaName, ); @@ -191,7 +190,7 @@ export class ColumnTree extends React.PureComponent SqlRef.column(child.COLUMN_NAME).as()), + metadata.map(child => SqlRef.column(child.COLUMN_NAME)), ) .changeWhereExpression(where), true, @@ -230,7 +229,7 @@ export class ColumnTree extends React.PureComponent Date: Wed, 19 May 2021 10:49:11 -0700 Subject: [PATCH 4/8] update licenses --- licenses.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/licenses.yaml b/licenses.yaml index 1b432610e4ca..4bffa3043271 100644 --- a/licenses.yaml +++ b/licenses.yaml @@ -5142,7 +5142,7 @@ license_category: binary module: web-console license_name: Apache License version 2.0 copyright: Imply Data -version: 0.11.2 +version: 0.11.6 --- From 0e642c73d02511115f8ef6c8bcb72b7b020e185d Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Thu, 27 May 2021 18:17:14 -0700 Subject: [PATCH 5/8] fix funky quotes --- web-console/src/utils/druid-query.spec.ts | 39 +++++++++++++++++++++++ web-console/src/utils/druid-query.ts | 16 ++++++++++ 2 files changed, 55 insertions(+) diff --git a/web-console/src/utils/druid-query.spec.ts b/web-console/src/utils/druid-query.spec.ts index 2738f733a7cb..029b201a4cfc 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 funky 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 funky 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 funky 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 * diff --git a/web-console/src/utils/druid-query.ts b/web-console/src/utils/druid-query.ts index 78c9d311ad6c..b77b7ad381b8 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 funky 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( From 179bf25a7fecc8b39cd393dbe5636571b26a8890 Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Fri, 28 May 2021 12:25:55 -0700 Subject: [PATCH 6/8] funky => fancy --- web-console/src/utils/druid-query.spec.ts | 6 +++--- web-console/src/utils/druid-query.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web-console/src/utils/druid-query.spec.ts b/web-console/src/utils/druid-query.spec.ts index 029b201a4cfc..5b3f51936740 100644 --- a/web-console/src/utils/druid-query.spec.ts +++ b/web-console/src/utils/druid-query.spec.ts @@ -101,7 +101,7 @@ describe('DruidQuery', () => { const suggestion = DruidError.getSuggestion( 'Lexical error at line 6, column 60. Encountered: "\\u201c" (8220), after : ""', ); - expect(suggestion!.label).toEqual(`Replace funky quotes with ASCII quotes`); + expect(suggestion!.label).toEqual(`Replace fancy quotes with ASCII quotes`); expect(suggestion!.fn(sql)).toEqual(sane` SELECT * FROM "wikipedia" `); @@ -114,7 +114,7 @@ describe('DruidQuery', () => { const suggestion = DruidError.getSuggestion( 'Lexical error at line 6, column 60. Encountered: "\\u201d" (8221), after : ""', ); - expect(suggestion!.label).toEqual(`Replace funky quotes with ASCII quotes`); + expect(suggestion!.label).toEqual(`Replace fancy quotes with ASCII quotes`); expect(suggestion!.fn(sql)).toEqual(sane` SELECT * FROM "wikipedia" `); @@ -127,7 +127,7 @@ describe('DruidQuery', () => { const suggestion = DruidError.getSuggestion( 'Lexical error at line 1, column 45. Encountered: "\\u2018" (8216), after : ""', ); - expect(suggestion!.label).toEqual(`Replace funky quotes with ASCII quotes`); + expect(suggestion!.label).toEqual(`Replace fancy quotes with ASCII quotes`); expect(suggestion!.fn(sql)).toEqual(sane` SELECT * FROM "wikipedia" WHERE "channel" = 'lol' `); diff --git a/web-console/src/utils/druid-query.ts b/web-console/src/utils/druid-query.ts index b77b7ad381b8..fd6ec42ecfd1 100644 --- a/web-console/src/utils/druid-query.ts +++ b/web-console/src/utils/druid-query.ts @@ -128,7 +128,7 @@ export class DruidError extends Error { ); if (matchLexical) { return { - label: 'Replace funky quotes with ASCII quotes', + label: 'Replace fancy quotes with ASCII quotes', fn: str => { const newQuery = str .replace(/[\u2018-\u201b]/gim, `'`) From cc35eddf754a88dfc18b52b98c86a5e909d4bf78 Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Tue, 1 Jun 2021 09:43:09 -0700 Subject: [PATCH 7/8] revert engine change --- web-console/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web-console/package.json b/web-console/package.json index 4cedded80e71..580d85d9dc06 100644 --- a/web-console/package.json +++ b/web-console/package.json @@ -8,6 +8,9 @@ "type": "git", "url": "https://github.com/apache/druid" }, + "engines": { + "node": ">=10" + }, "jest": { "preset": "ts-jest", "testEnvironment": "jsdom", @@ -51,9 +54,6 @@ "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" From 66c86cd6d43f20613bf74e46247d03e68ea2ca41 Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Thu, 3 Jun 2021 17:15:06 -0700 Subject: [PATCH 8/8] separate web-console and website npm and node deps --- pom.xml | 2 -- web-console/package.json | 6 +++--- web-console/pom.xml | 2 ++ website/pom.xml | 2 ++ 4 files changed, 7 insertions(+), 5 deletions(-) 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/package.json b/web-console/package.json index 580d85d9dc06..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,9 @@ "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" 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/website/pom.xml b/website/pom.xml index a19699c1ffd3..4be642b60a00 100644 --- a/website/pom.xml +++ b/website/pom.xml @@ -39,6 +39,8 @@ to stage documentation for an unreleased version --> ${project.parent.version} + v10.24.1 + 6.14.12