diff --git a/src/views/results/codegen.ts b/src/views/results/codegen.ts index b90ceb75..8b865c31 100644 --- a/src/views/results/codegen.ts +++ b/src/views/results/codegen.ts @@ -1,6 +1,6 @@ import { ColumnMetaData, QueryResult } from "@ibm/mapepire-js"; -export function queryResultToRpgDs(result: QueryResult, source: string = 'Name') : string { +export function queryResultToRpgDs(result: QueryResult, source: string = 'Name'): string { let content = `dcl-ds row_t qualified template;\n`; for (let i = 0; i < result.metadata.column_count; i++) { const name = columnToRpgFieldName(result.metadata.columns[i], source); @@ -10,7 +10,7 @@ export function queryResultToRpgDs(result: QueryResult, source: string = 'N return content; } -export function columnToRpgFieldName(column: ColumnMetaData, source: string = 'Name') : string { +export function columnToRpgFieldName(column: ColumnMetaData, source: string = 'Name'): string { let name = source === 'Label' ? column.label.toLowerCase().trim() : column.name.toLowerCase().trim(); name = name.replace(/\u00fc/g, "u") // ü -> u .replace(/\u00e4/g, "a") // ä -> a @@ -24,14 +24,14 @@ export function columnToRpgFieldName(column: ColumnMetaData, source: string = 'N .replace(/\s+/g, "_") // remaining whitespaces to underscore .replace(/[^a-zA-Z0-9_]/g, "") // remove non-alphanumeric chars .replace(/\_+/i, "_") // replace multiple underscores with single underscore - .trim(); + .trim(); if (!isNaN(+name.charAt(0))) { name = `col` + name; } return name; } -export function columnToRpgDefinition(column: ColumnMetaData) : string { +export function columnToRpgDefinition(column: ColumnMetaData): string { switch (column.type) { case `NUMERIC`: return `zoned(${column.precision}${column.scale > 0 ? ' : ' + column.scale : ''})`; @@ -59,3 +59,59 @@ export function columnToRpgDefinition(column: ColumnMetaData) : string { return `// type:${column.type} precision:${column.precision} scale:${column.scale}`; } } + +export function queryResultToUdtf(result: QueryResult, sqlStatement: string): string { + let columnDefinitions = ''; + for (let i = 0; i < result.metadata.column_count; i++) { + const column = result.metadata.columns[i]; + columnDefinitions += ` ${column.name} ${columnToSqlDefinition(column)}`; + if (i < result.metadata.column_count - 1) { + columnDefinitions += ',\n'; + } else { + columnDefinitions += '\n'; + } + } + + return `CREATE OR REPLACE FUNCTION MyFunction()\n` + + ` RETURNS TABLE (\n` + + columnDefinitions + + ` )\n` + + ` NOT DETERMINISTIC\n` + + ` NO EXTERNAL ACTION\n` + + ` READS SQL DATA\n` + + ` SET OPTION COMMIT = *NONE,\n` + + ` DYNUSRPRF = *USER,\n` + + ` USRPRF = *USER\n` + + ` BEGIN\n` + + ` RETURN ${sqlStatement};\n` + + ` END;`; +} + +export function columnToSqlDefinition(column: ColumnMetaData): string { + switch (column.type) { + case 'NUMERIC': + return `NUMERIC(${column.precision},${column.scale})`; + case 'DECIMAL': + return `DECIMAL(${column.precision},${column.scale})`; + case 'CHAR': + return `CHAR(${column.precision})`; + case 'VARCHAR': + return `VARCHAR(${column.precision})`; + case 'DATE': + return `DATE`; + case 'TIME': + return `TIME`; + case 'TIMESTAMP': + return `TIMESTAMP`; + case 'SMALLINT': + return `INTEGER`; + case 'INTEGER': + return `INTEGER`; + case 'BIGINT': + return `BIGINT`; + case 'BOOLEAN': + return `BOOLEAN`; + default: + return `-- type:${column.type} precision:${column.precision} scale:${column.scale} */`; + } +} \ No newline at end of file diff --git a/src/views/results/index.ts b/src/views/results/index.ts index ee1b17f6..90456c77 100644 --- a/src/views/results/index.ts +++ b/src/views/results/index.ts @@ -13,7 +13,7 @@ import Statement from "../../language/sql/statement"; import { ObjectRef, ParsedEmbeddedStatement, StatementGroup, StatementType } from "../../language/sql/types"; import { updateStatusBar } from "../jobManager/statusBar"; import { getLiteralsFromStatement, getPriorBindableStatement } from "./binding"; -import { queryResultToRpgDs } from "./codegen"; +import { queryResultToRpgDs, queryResultToUdtf } from "./codegen"; import { registerRunStatement } from "./editorUi"; import { generateSqlForAdvisedIndexes } from "./explain/advice"; import { DoveNodeView, PropertyNode } from "./explain/doveNodeView"; @@ -22,7 +22,7 @@ import { DoveTreeDecorationProvider } from "./explain/doveTreeDecorationProvider import { ExplainTree } from "./explain/nodes"; import { ResultSetPanelProvider, SqlParameter } from "./resultSetPanelProvider"; -export type StatementQualifier = "statement" | "bind" | "update" | "explain" | "onlyexplain" | "json" | "csv" | "cl" | "sql" | "rpg"; +export type StatementQualifier = "statement" | "bind" | "update" | "explain" | "onlyexplain" | "json" | "csv" | "cl" | "sql" | "rpg" | "udtf"; export interface StatementInfo { content: string, @@ -440,6 +440,26 @@ async function runHandler(options?: StatementInfo) { await vscode.window.showTextDocument(textDoc); chosenView.setLoadingText(`RPG data structure generated.`, false); } + + } else if (statementDetail.qualifier === `udtf`) { + if (statementDetail.statement.type !== StatementType.Select) { + vscode.window.showErrorMessage('UDTF qualifier only supported for select statements'); + } else { + chosenView.setLoadingText(`Executing SQL statement...`, false); + setCancelButtonVisibility(true); + updateStatusBar({executing: true}); + const result = await JobManager.runSQLVerbose(statementDetail.content, undefined, 1); + setCancelButtonVisibility(false); + updateStatusBar({executing: false}); + let content = `-- statement:\n` + + `-- ${statementDetail.content.replace(/(\r\n|\r|\n)/g, '\n-- ') }\n\n` + + `-- User-defined table function\n` + + queryResultToUdtf(result, statementDetail.content); + + const textDoc = await vscode.workspace.openTextDocument({ language: 'sql', content }); + await vscode.window.showTextDocument(textDoc); + chosenView.setLoadingText(`User-defined table function generated.`, false); + } } else { // Otherwise... it's a bit complicated. @@ -592,7 +612,7 @@ export function parseStatement(editor?: vscode.TextEditor, existingInfo?: Statem } if (statementInfo.content) { - [`cl`, `json`, `csv`, `sql`, `explain`, `update`, `rpg`, `bind`].forEach(mode => { + [`cl`, `json`, `csv`, `sql`, `explain`, `update`, `rpg`, `udtf`, `bind`].forEach(mode => { if (statementInfo.content.trim().toLowerCase().startsWith(mode + `:`)) { statementInfo.content = statementInfo.content.substring(mode.length + 1).trim();