Web console: support for the export execution state (#15969)

* init

* add CSV keyword
This commit is contained in:
Vadim Ogievetsky 2024-02-26 11:28:25 -08:00 committed by GitHub
parent 28b3e117cf
commit bf3139562c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 47 additions and 131 deletions

View File

@ -108,6 +108,7 @@ exports.SQL_EXPRESSION_PARTS = [
'YEAR',
'TIMESTAMP',
'INTERVAL',
'CSV',
];
exports.SQL_CONSTANTS = ['NULL', 'FALSE', 'TRUE'];

View File

@ -567,7 +567,7 @@ export class Execution {
return this.destination?.numTotalRows;
}
public isSuccessfulInsert(): boolean {
public isSuccessfulIngest(): boolean {
return Boolean(this.status === 'SUCCESS' && this.getIngestDatasource());
}

View File

@ -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(

View File

@ -158,7 +158,7 @@ export class WorkbenchQuery {
.changeQueryString(queryString)
.changeQueryContext(cleanContext);
if (noSqlOuterLimit && !retQuery.getIngestDatasource()) {
if (noSqlOuterLimit && !retQuery.isIngestQuery()) {
retQuery = retQuery.changeUnlimited(true);
}
@ -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[];
@ -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;
if (this.isJsonLike()) return false;
return WorkbenchQuery.getIngestDatasourceFromQueryFragment(queryString);
}
public isIngestQuery(): boolean {
return Boolean(this.getIngestDatasource());
return /(?:INSERT|REPLACE)\s+INTO/i.test(queryString);
}
public toggleUnlimited(): WorkbenchQuery {

View File

@ -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;
}
}
}
}

View File

@ -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;
@ -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} />
@ -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