mirror of https://github.com/apache/druid.git
Web console: support for the export execution state (#15969)
* init * add CSV keyword
This commit is contained in:
parent
28b3e117cf
commit
bf3139562c
|
@ -108,6 +108,7 @@ exports.SQL_EXPRESSION_PARTS = [
|
|||
'YEAR',
|
||||
'TIMESTAMP',
|
||||
'INTERVAL',
|
||||
'CSV',
|
||||
];
|
||||
|
||||
exports.SQL_CONSTANTS = ['NULL', 'FALSE', 'TRUE'];
|
||||
|
|
|
@ -567,7 +567,7 @@ export class Execution {
|
|||
return this.destination?.numTotalRows;
|
||||
}
|
||||
|
||||
public isSuccessfulInsert(): boolean {
|
||||
public isSuccessfulIngest(): boolean {
|
||||
return Boolean(this.status === 'SUCCESS' && this.getIngestDatasource());
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue