Skip to content

Commit 278e730

Browse files
Merge pull request #459 from codefori/feature/udtf-sql-prefix
Add `udtf` SQL prefix
2 parents e7b35e4 + 226e6b2 commit 278e730

File tree

2 files changed

+132
-7
lines changed

2 files changed

+132
-7
lines changed

src/views/results/codegen.ts

Lines changed: 109 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { ColumnMetaData, QueryResult } from "@ibm/mapepire-js";
2+
import { Token } from "../../language/sql/types";
3+
import { tokenIs } from "../../language/sql/statement";
24

3-
export function queryResultToRpgDs(result: QueryResult<any>, source: string = 'Name') : string {
5+
export function queryResultToRpgDs(result: QueryResult<any>, source: string = 'Name'): string {
46
let content = `dcl-ds row_t qualified template;\n`;
57
for (let i = 0; i < result.metadata.column_count; i++) {
68
const name = columnToRpgFieldName(result.metadata.columns[i], source);
@@ -10,7 +12,7 @@ export function queryResultToRpgDs(result: QueryResult<any>, source: string = 'N
1012
return content;
1113
}
1214

13-
export function columnToRpgFieldName(column: ColumnMetaData, source: string = 'Name') : string {
15+
export function columnToRpgFieldName(column: ColumnMetaData, source: string = 'Name'): string {
1416
let name = source === 'Label' ? column.label.toLowerCase().trim() : column.name.toLowerCase().trim();
1517
name = name.replace(/\u00fc/g, "u") // ü -> u
1618
.replace(/\u00e4/g, "a") // ä -> a
@@ -24,14 +26,14 @@ export function columnToRpgFieldName(column: ColumnMetaData, source: string = 'N
2426
.replace(/\s+/g, "_") // remaining whitespaces to underscore
2527
.replace(/[^a-zA-Z0-9_]/g, "") // remove non-alphanumeric chars
2628
.replace(/\_+/i, "_") // replace multiple underscores with single underscore
27-
.trim();
29+
.trim();
2830
if (!isNaN(+name.charAt(0))) {
2931
name = `col` + name;
3032
}
3133
return name;
3234
}
3335

34-
export function columnToRpgDefinition(column: ColumnMetaData) : string {
36+
export function columnToRpgDefinition(column: ColumnMetaData): string {
3537
switch (column.type) {
3638
case `NUMERIC`:
3739
return `zoned(${column.precision}${column.scale > 0 ? ' : ' + column.scale : ''})`;
@@ -59,3 +61,106 @@ export function columnToRpgDefinition(column: ColumnMetaData) : string {
5961
return `// type:${column.type} precision:${column.precision} scale:${column.scale}`;
6062
}
6163
}
64+
65+
export function queryResultToUdtf(result: QueryResult<any>, sqlStatement: string, tokens: Token[]): string {
66+
let columnDefinitions = '';
67+
for (let i = 0; i < result.metadata.column_count; i++) {
68+
const column = result.metadata.columns[i];
69+
columnDefinitions += ` ${column.name} ${columnToSqlDefinition(column)}`;
70+
if (i < result.metadata.column_count - 1) {
71+
columnDefinitions += ',\n';
72+
} else {
73+
columnDefinitions += '\n';
74+
}
75+
}
76+
77+
if (tokens.length > 4 &&
78+
tokenIs(tokens[0], `word`, `UDTF`) &&
79+
tokenIs(tokens[1], `colon`, `:`) &&
80+
tokenIs(tokens[2], `statementType`, `SELECT`) &&
81+
tokenIs(tokens[3], `asterisk`, `*`)) {
82+
const prefixEnd = (tokens[3].range.start - tokens[0].range.start) - (tokens[1].range.start - tokens[0].range.start) - 2;
83+
const suffixStart = (tokens[3].range.start - tokens[0].range.start) - (tokens[1].range.start - tokens[0].range.start);
84+
const columns = result.metadata.columns.map(column => column.name).join(`,\n `)
85+
sqlStatement = `${sqlStatement.substring(0, prefixEnd)}${columns}\n ${sqlStatement.substring(suffixStart)}`;
86+
}
87+
88+
return `CREATE OR REPLACE FUNCTION MyFunction()\n`
89+
+ ` RETURNS TABLE (\n`
90+
+ columnDefinitions
91+
+ ` )\n`
92+
+ ` NOT DETERMINISTIC\n`
93+
+ ` NO EXTERNAL ACTION\n`
94+
+ ` READS SQL DATA\n`
95+
+ ` SET OPTION COMMIT = *NONE,\n`
96+
+ ` DBGVIEW = *SOURCE,\n`
97+
+ ` DYNUSRPRF = *USER,\n`
98+
+ ` USRPRF = *USER\n`
99+
+ ` BEGIN\n`
100+
+ ` RETURN ${sqlStatement};\n`
101+
+ ` END;`;
102+
}
103+
104+
/**
105+
* Based on built-in types: https://www.ibm.com/docs/en/i/7.6.0?topic=statements-create-table
106+
*/
107+
export function columnToSqlDefinition(column: ColumnMetaData): string {
108+
switch (column.type) {
109+
case 'SMALLINT':
110+
return `SMALLINT`;
111+
case 'INTEGER':
112+
return `INTEGER`;
113+
case 'BIGINT':
114+
return `BIGINT`;
115+
case 'DECIMAL':
116+
return `DECIMAL(${column.precision},${column.scale})`;
117+
case 'NUMERIC':
118+
return `NUMERIC(${column.precision},${column.scale})`;
119+
case 'REAL':
120+
return `REAL`;
121+
case 'DOUBLE':
122+
return `DOUBLE`;
123+
case 'DECFLOAT':
124+
return `DECFLOAT(${column.precision})`;
125+
case 'CHAR':
126+
return `CHAR(${column.precision})`;
127+
case 'VARCHAR':
128+
return `VARCHAR(${column.precision})`;
129+
case 'CLOB':
130+
return `CLOB(${column.precision})`;
131+
case 'GRAPHIC':
132+
return `GRAPHIC(${column.precision})`;
133+
case 'VARGRAPHIC':
134+
return `VARGRAPHIC(${column.precision})`;
135+
case 'DBCLOB':
136+
return `DBCLOB(${column.precision})`;
137+
case 'NCHAR':
138+
return `NCHAR(${column.precision})`;
139+
case 'NVARCHAR':
140+
return `NVARCHAR(${column.precision})`;
141+
case 'NCLOB':
142+
return `NCLOB(${column.precision})`;
143+
case 'BINARY':
144+
return `BINARY(${column.precision})`;
145+
case 'VARBINARY':
146+
return `VARBINARY(${column.precision})`;
147+
case 'BLOB':
148+
return `BLOB(${column.precision})`;
149+
case 'DATE':
150+
return `DATE`;
151+
case 'TIME':
152+
return `TIME`;
153+
case 'TIMESTAMP':
154+
return `TIMESTAMP(${column.scale})`;
155+
case 'DATALINK':
156+
return `DATALINK(${column.precision})`;
157+
case 'ROWID':
158+
return `ROWID`;
159+
case 'XML':
160+
return `XML`;
161+
case 'BOOLEAN':
162+
return `BOOLEAN`;
163+
default:
164+
return `-- type:${column.type} precision:${column.precision} scale:${column.scale} */`;
165+
}
166+
}

src/views/results/index.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import Statement from "../../language/sql/statement";
1313
import { ObjectRef, ParsedEmbeddedStatement, StatementGroup, StatementType } from "../../language/sql/types";
1414
import { updateStatusBar } from "../jobManager/statusBar";
1515
import { getLiteralsFromStatement, getPriorBindableStatement } from "./binding";
16-
import { queryResultToRpgDs } from "./codegen";
16+
import { queryResultToRpgDs, queryResultToUdtf } from "./codegen";
1717
import { registerRunStatement } from "./editorUi";
1818
import { generateSqlForAdvisedIndexes } from "./explain/advice";
1919
import { DoveNodeView, PropertyNode } from "./explain/doveNodeView";
@@ -22,7 +22,7 @@ import { DoveTreeDecorationProvider } from "./explain/doveTreeDecorationProvider
2222
import { ExplainTree } from "./explain/nodes";
2323
import { ResultSetPanelProvider, SqlParameter } from "./resultSetPanelProvider";
2424

25-
export type StatementQualifier = "statement" | "bind" | "update" | "explain" | "onlyexplain" | "json" | "csv" | "cl" | "sql" | "rpg";
25+
export type StatementQualifier = "statement" | "bind" | "update" | "explain" | "onlyexplain" | "json" | "csv" | "cl" | "sql" | "rpg" | "udtf";
2626

2727
export interface StatementInfo {
2828
content: string,
@@ -440,6 +440,26 @@ async function runHandler(options?: StatementInfo) {
440440
await vscode.window.showTextDocument(textDoc);
441441
chosenView.setLoadingText(`RPG data structure generated.`, false);
442442
}
443+
444+
} else if (statementDetail.qualifier === `udtf`) {
445+
if (statementDetail.statement.type !== StatementType.Select) {
446+
vscode.window.showErrorMessage('UDTF qualifier only supported for select statements');
447+
} else {
448+
chosenView.setLoadingText(`Executing SQL statement...`, false);
449+
setCancelButtonVisibility(true);
450+
updateStatusBar({executing: true});
451+
const result = await JobManager.runSQLVerbose(statementDetail.content, undefined, 1);
452+
setCancelButtonVisibility(false);
453+
updateStatusBar({executing: false});
454+
let content = `-- statement:\n`
455+
+ `-- ${statementDetail.content.replace(/(\r\n|\r|\n)/g, '\n-- ') }\n\n`
456+
+ `-- User-defined table function\n`
457+
+ queryResultToUdtf(result, statementDetail.content, statementDetail.statement.tokens);
458+
459+
const textDoc = await vscode.workspace.openTextDocument({ language: 'sql', content });
460+
await vscode.window.showTextDocument(textDoc);
461+
chosenView.setLoadingText(`User-defined table function generated.`, false);
462+
}
443463

444464
} else {
445465
// Otherwise... it's a bit complicated.
@@ -592,7 +612,7 @@ export function parseStatement(editor?: vscode.TextEditor, existingInfo?: Statem
592612
}
593613

594614
if (statementInfo.content) {
595-
[`cl`, `json`, `csv`, `sql`, `explain`, `update`, `rpg`, `bind`].forEach(mode => {
615+
[`cl`, `json`, `csv`, `sql`, `explain`, `update`, `rpg`, `udtf`, `bind`].forEach(mode => {
596616
if (statementInfo.content.trim().toLowerCase().startsWith(mode + `:`)) {
597617
statementInfo.content = statementInfo.content.substring(mode.length + 1).trim();
598618

0 commit comments

Comments
 (0)