Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion licenses.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

---

Expand Down
2 changes: 0 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,6 @@
<netty3.version>3.10.6.Final</netty3.version>
<resilience4j.version>1.3.1</resilience4j.version>
<netty4.version>4.1.63.Final</netty4.version>
<node.version>v10.24.0</node.version>
<npm.version>6.14.11</npm.version>
<postgresql.version>42.2.14</postgresql.version>
<protobuf.version>3.11.0</protobuf.version>
<slf4j.version>1.7.12</slf4j.version>
Expand Down
4 changes: 2 additions & 2 deletions web-console/e2e-tests/tutorial-batch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down
7 changes: 7 additions & 0 deletions web-console/lib/keywords.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ exports.SQL_KEYWORDS = [
'OUTER',
'FULL',
'CROSS',
'USING',
'FETCH',
'FIRST',
'NEXT',
'ROW',
'ROWS',
'ONLY',
];

exports.SQL_EXPRESSION_PARTS = [
Expand Down
8 changes: 4 additions & 4 deletions web-console/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 8 additions & 4 deletions web-console/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
"type": "git",
"url": "https://github.com/apache/druid"
},
"engines": {
"node": ">=10"
},
"jest": {
"preset": "ts-jest",
"testEnvironment": "jsdom",
Expand Down Expand Up @@ -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"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this stuff mean? do we need to update the nodejs version we use to build this when using maven? https://github.com/apache/druid/blob/master/pom.xml#L103 (this is also used by website builds afaik)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is too complex for me to think about... with the website interaction that is. I added it to inform the local dev tools. I think if you are developing on the web console locally you really want node > 10 for some of the dev dependancies. I will remove the engines entry.

},
"volta": {
"node": "14.16.1",
"npm": "6.14.12"
},
"dependencies": {
"@blueprintjs/core": "^3.33.0",
"@blueprintjs/datetime": "^3.19.2",
Expand All @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions web-console/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
<properties>
<resources.directory>${project.build.directory}/resources</resources.directory>
<druid.console.skip>false</druid.console.skip> <!-- this property is overidden in Travis CI to skip the javascript-related work -->
<node.version>v14.16.1</node.version>
<npm.version>6.14.12</npm.version>
</properties>

<build>
Expand Down
84 changes: 84 additions & 0 deletions web-console/src/utils/druid-query.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
Expand All @@ -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" ...`,
Expand All @@ -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: <EOF> "EXCEPT" ...`,
Expand Down
65 changes: 61 additions & 4 deletions web-console/src/utils/druid-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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;
Expand All @@ -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;
}

Expand Down
4 changes: 2 additions & 2 deletions web-console/src/views/datasource-view/datasource-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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()),
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const NumberMenuItems = React.memo(function NumberMenuItems(props: Number
<MenuItem
text={prettyPrintSql(clause)}
onClick={() => {
onQueryChange(parsedQuery.addToWhere(clause));
onQueryChange(parsedQuery.addWhere(clause));
}}
/>
);
Expand Down Expand Up @@ -82,7 +82,13 @@ export const NumberMenuItems = React.memo(function NumberMenuItems(props: Number
<MenuItem
text={prettyPrintSql(ex)}
onClick={() => {
onQueryChange(parsedQuery.addToGroupBy(ex.as(alias)), true);
onQueryChange(
parsedQuery.addSelect(ex.as(alias), {
insertIndex: 'last-grouping',
addToGroupBy: 'end',
}),
true,
);
}}
/>
);
Expand All @@ -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 (
<MenuItem
icon={IconNames.UNGROUP_OBJECTS}
text="Remove group by"
onClick={() => {
onQueryChange(parsedQuery.removeSelectIndex(selectIndex), true);
onQueryChange(parsedQuery.removeSelectIndexes(groupedSelectIndexes), true);
}}
/>
);
Expand All @@ -125,7 +131,7 @@ export const NumberMenuItems = React.memo(function NumberMenuItems(props: Number
<MenuItem
text={prettyPrintSql(ex)}
onClick={() => {
onQueryChange(parsedQuery.addSelectExpression(ex.as(alias)), true);
onQueryChange(parsedQuery.addSelect(ex.as(alias)), true);
}}
/>
);
Expand Down
Loading