Skip to content

Commit

Permalink
Web console: support for the export execution state (#15969) (#16058)
Browse files Browse the repository at this point in the history
* init

* add CSV keyword

Co-authored-by: Vadim Ogievetsky <vadim@ogievetsky.com>
  • Loading branch information
cryptoe and vogievetsky committed Mar 8, 2024
1 parent daf2c48 commit dd3aff5
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 131 deletions.
1 change: 1 addition & 0 deletions web-console/lib/keywords.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ exports.SQL_EXPRESSION_PARTS = [
'YEAR',
'TIMESTAMP',
'INTERVAL',
'CSV',
];

exports.SQL_CONSTANTS = ['NULL', 'FALSE', 'TRUE'];
Expand Down
2 changes: 1 addition & 1 deletion web-console/src/druid-models/execution/execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ export class Execution {
return this.destination?.numTotalRows;
}

public isSuccessfulInsert(): boolean {
public isSuccessfulIngest(): boolean {
return Boolean(this.status === 'SUCCESS' && this.getIngestDatasource());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,100 +455,6 @@ describe('WorkbenchQuery', () => {
});
});

describe('#getIngestDatasource', () => {
it('works with INSERT', () => {
const sql = sane`
-- Some comment
INSERT INTO trips2
SELECT
TIME_PARSE(pickup_datetime) AS __time,
*
FROM TABLE(
EXTERN(
'{"type": "local", ...}',
'{"type":"csv", ...}'
)
) EXTEND (cab_type, VARCHAR)
CLUSTERED BY trip_id
`;

const workbenchQuery = WorkbenchQuery.blank().changeQueryString(sql);
expect(workbenchQuery.getIngestDatasource()).toEqual('trips2');
expect(workbenchQuery.changeEngine('sql-native').getIngestDatasource()).toBeUndefined();
});

it('works with INSERT (unparsable)', () => {
const sql = sane`
-- Some comment
INSERT into trips2
SELECT
TIME_PARSE(pickup_datetime) AS __time,
*
FROM TABLE(
`;

const workbenchQuery = WorkbenchQuery.blank().changeQueryString(sql);
expect(workbenchQuery.getIngestDatasource()).toEqual('trips2');
expect(workbenchQuery.changeEngine('sql-native').getIngestDatasource()).toBeUndefined();
});

it('works with INSERT (unparsable with paren)', () => {
const sql = sane`
-- Some comment
INSERT into trips2
(SELECT TIME_PARSE(pickup_datetime) AS __time,
`;

const workbenchQuery = WorkbenchQuery.blank().changeQueryString(sql);
expect(workbenchQuery.getIngestDatasource()).toEqual('trips2');
expect(workbenchQuery.changeEngine('sql-native').getIngestDatasource()).toBeUndefined();
});

it('works with REPLACE', () => {
const sql = sane`
REPLACE INTO trips2 OVERWRITE ALL
SELECT
TIME_PARSE(pickup_datetime) AS __time,
*
FROM TABLE(
EXTERN(
'{"type": "local", ...}',
'{"type":"csv", ...}'
)
) EXTEND (cab_type, VARCHAR)
CLUSTERED BY trip_id
`;

const workbenchQuery = WorkbenchQuery.blank().changeQueryString(sql);
expect(workbenchQuery.getIngestDatasource()).toEqual('trips2');
expect(workbenchQuery.changeEngine('sql-native').getIngestDatasource()).toBeUndefined();
});

it('works with REPLACE (unparsable)', () => {
const sql = sane`
REPLACE INTO trips2 OVERWRITE ALL
WITH kttm_data AS (SELECT *
`;

const workbenchQuery = WorkbenchQuery.blank().changeQueryString(sql);
expect(workbenchQuery.getIngestDatasource()).toEqual('trips2');
expect(workbenchQuery.changeEngine('sql-native').getIngestDatasource()).toBeUndefined();
});

it('works with REPLACE (unparsable with comment at start)', () => {
const sql = sane`
-- Hello world SELECT
REPLACE INTO trips2 OVERWRITE ALL
WITH kttm_data AS (SELECT *
`;

const workbenchQuery = WorkbenchQuery.blank().changeQueryString(sql);
expect(workbenchQuery.getIngestDatasource()).toEqual('trips2');
expect(workbenchQuery.changeEngine('sql-native').getIngestDatasource()).toBeUndefined();
});
});

describe('#getIssue', () => {
it('works', () => {
expect(
Expand Down
33 changes: 6 additions & 27 deletions web-console/src/druid-models/workbench-query/workbench-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export class WorkbenchQuery {
.changeQueryString(queryString)
.changeQueryContext(cleanContext);

if (noSqlOuterLimit && !retQuery.getIngestDatasource()) {
if (noSqlOuterLimit && !retQuery.isIngestQuery()) {
retQuery = retQuery.changeUnlimited(true);
}

Expand Down Expand Up @@ -221,23 +221,6 @@ export class WorkbenchQuery {
return /EXTERN\s*\(|(?:INSERT|REPLACE)\s+INTO/im.test(queryString);
}

static getIngestDatasourceFromQueryFragment(queryFragment: string): string | undefined {
// Assuming the queryFragment is no parsable find the prefix that look like:
// REPLACE<space>INTO<space><whatever><space>SELECT<space or EOF>
const matchInsertReplaceIndex = queryFragment.match(/(?:INSERT|REPLACE)\s+INTO/i)?.index;
if (typeof matchInsertReplaceIndex !== 'number') return;

const queryStartingWithInsertOrReplace = queryFragment.substring(matchInsertReplaceIndex);

const matchEnd = queryStartingWithInsertOrReplace.match(/\(|\b(?:SELECT|WITH)\b|$/i);
const fragmentQuery = SqlQuery.maybeParse(
queryStartingWithInsertOrReplace.substring(0, matchEnd?.index) + ' SELECT * FROM t',
);
if (!fragmentQuery) return;

return fragmentQuery.getIngestTable()?.getName();
}

public readonly queryString: string;
public readonly queryContext: QueryContext;
public readonly queryParameters?: QueryParameter[];
Expand Down Expand Up @@ -409,21 +392,17 @@ export class WorkbenchQuery {
}
}

public getIngestDatasource(): string | undefined {
if (this.getEffectiveEngine() !== 'sql-msq-task') return;
public isIngestQuery(): boolean {
if (this.getEffectiveEngine() !== 'sql-msq-task') return false;

const { queryString, parsedQuery } = this;
if (parsedQuery) {
return parsedQuery.getIngestTable()?.getName();
return Boolean(parsedQuery.getIngestTable());
}

if (this.isJsonLike()) return;

return WorkbenchQuery.getIngestDatasourceFromQueryFragment(queryString);
}
if (this.isJsonLike()) return false;

public isIngestQuery(): boolean {
return Boolean(this.getIngestDatasource());
return /(?:INSERT|REPLACE)\s+INTO/i.test(queryString);
}

public toggleUnlimited(): WorkbenchQuery {
Expand Down
20 changes: 20 additions & 0 deletions web-console/src/views/workbench-view/query-tab/query-tab.scss
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,25 @@ $vertical-gap: 6px;
right: 0;
}
}

.generic-status-container {
position: relative;

.generic-status-container-info {
position: absolute;
top: 5px;
left: 5px;
right: 5px;
height: 30px;
}

.execution-stages-pane {
position: absolute;
top: 40px;
bottom: 0;
left: 0;
right: 0;
}
}
}
}
28 changes: 19 additions & 9 deletions web-console/src/views/workbench-view/query-tab/query-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -286,11 +286,11 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) {
useCallback(state => state.increment, []),
);
useEffect(() => {
if (execution?.isSuccessfulInsert()) {
if (execution?.isSuccessfulIngest()) {
incrementMetadataVersion();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [Boolean(execution?.isSuccessfulInsert())]);
}, [Boolean(execution?.isSuccessfulIngest())]);

function moveToPosition(position: RowColumn) {
const currentQueryInput = queryInputRef.current;
Expand Down Expand Up @@ -434,12 +434,6 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) {
queryResult={execution.result}
onQueryAction={handleQueryAction}
/>
) : execution.isSuccessfulInsert() ? (
<IngestSuccessPane
execution={execution}
onDetails={onDetails}
onQueryTab={onQueryTab}
/>
) : execution.error ? (
<div className="error-container">
<ExecutionErrorPane execution={execution} />
Expand All @@ -452,8 +446,24 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) {
/>
)}
</div>
) : execution.isSuccessfulIngest() ? (
<IngestSuccessPane
execution={execution}
onDetails={onDetails}
onQueryTab={onQueryTab}
/>
) : (
<div>Unknown query execution state</div>
<div className="generic-status-container">
<div className="generic-status-container-info">
{`Execution completed with status: ${execution.status}`}
</div>
<ExecutionStagesPane
execution={execution}
onErrorClick={() => onDetails(statsTaskId!, 'error')}
onWarningClick={() => onDetails(statsTaskId!, 'warnings')}
goToTask={goToTask}
/>
</div>
))}
{executionState.error && (
<QueryErrorPane
Expand Down

0 comments on commit dd3aff5

Please sign in to comment.