From 2d2eb92e28e68e74161d239bebe7a06d51265b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Frode=20H=C3=A5skjold?= <jan.frode.haskjold@outlook.com> Date: Fri, 11 Apr 2025 09:26:16 +0200 Subject: [PATCH 01/12] feat: Generate RPG data structure from result metadata --- package-lock.json | 4 +-- src/views/results/index.ts | 66 +++++++++++++++++++++++++++++++++++--- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9a1f8e28..ac65787e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-db2i", - "version": "1.9.4", + "version": "1.10.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-db2i", - "version": "1.9.4", + "version": "1.10.1", "dependencies": { "@ibm/mapepire-js": "^0.5.0", "@octokit/rest": "^21.1.1", diff --git a/src/views/results/index.ts b/src/views/results/index.ts index f4fc7a46..0cf5609c 100644 --- a/src/views/results/index.ts +++ b/src/views/results/index.ts @@ -18,7 +18,7 @@ import { updateStatusBar } from "../jobManager/statusBar"; import { DbCache } from "../../language/providers/logic/cache"; import { ExplainType } from "../../connection/types"; -export type StatementQualifier = "statement" | "update" | "explain" | "onlyexplain" | "json" | "csv" | "cl" | "sql"; +export type StatementQualifier = "statement" | "update" | "explain" | "onlyexplain" | "json" | "csv" | "cl" | "sql" | "rpg"; export interface StatementInfo { content: string, @@ -375,7 +375,65 @@ async function runHandler(options?: StatementInfo) { } else { vscode.window.showInformationMessage(`No job currently selected.`); } - + + } else if ([`rpg`].includes(statementDetail.qualifier)) { + chosenView.setLoadingText(`Executing SQL statement...`, false); + + setCancelButtonVisibility(true); + updateStatusBar({executing: true}); + const result = await JobManager.runSQLVerbose(statementDetail.content, undefined, 1); + setCancelButtonVisibility(false); + //let content = ``; + let content = `**free\n\n-- Row data structure\n` + + `dcl-ds row_t qualified template;\n`; + + for (let i = 0; i < result.metadata.column_count; i++) { + content += ` ${isNaN(+result.metadata.columns[i].label.charAt(0)) ? '' : 'col'}${result.metadata.columns[i].label.toLowerCase()} `; + switch (result.metadata.columns[i].type) { + case `NUMERIC`: + content += `zoned(${result.metadata.columns[i].precision}${result.metadata.columns[i].scale > 0 ? ' : ' + result.metadata.columns[i].scale : ''});\n`; + break; + case `DECIMAL`: + content += `packed(${result.metadata.columns[i].precision}${result.metadata.columns[i].scale > 0 ? ' : ' + result.metadata.columns[i].scale : ''});\n`; + break; + case `CHAR`: + content += `char(${result.metadata.columns[i].precision});\n`; + break; + case `VARCHAR`: + content += `varchar(${result.metadata.columns[i].precision});\n`; + break; + case `DATE`: + content += `date;\n`; + break; + case `TIME`: + content += `time;\n`; + break; + case `TIMESTAMP`: + content += `timestamp;\n`; + break; + case `SMALLINT`: + content += `int(5);\n`; + break; + case `INTEGER`: + content += `int(10);\n`; + break; + case `BIGINT`: + content += `int(20);\n`; + break; + case `BOOLEAN`: + content += `ind;\n`; + break; + default: + content += `// type:${result.metadata.columns[i].type} precision:${result.metadata.columns[i].precision} scale:${result.metadata.columns[i].scale}\n`; + break; + } + } + content += `end-ds;\n`; + const textDoc = await vscode.workspace.openTextDocument({ language: 'rpgle', content }); + await vscode.window.showTextDocument(textDoc); + updateStatusBar({executing: false}); + chosenView.setLoadingText(`RPG data structure generated.`, false); + } else { // Otherwise... it's a bit complicated. chosenView.setLoadingText(`Executing SQL statement...`, false); @@ -426,7 +484,7 @@ async function runHandler(options?: StatementInfo) { ]; content += insertStatement.join(`\n`) + `;\n`; } - break; + break; } const textDoc = await vscode.workspace.openTextDocument({ language: statementDetail.qualifier, content }); @@ -524,7 +582,7 @@ export function parseStatement(editor?: vscode.TextEditor, existingInfo?: Statem } if (statementInfo.content) { - [`cl`, `json`, `csv`, `sql`, `explain`, `update`].forEach(mode => { + [`cl`, `json`, `csv`, `sql`, `explain`, `update`, `rpg`].forEach(mode => { if (statementInfo.content.trim().toLowerCase().startsWith(mode + `:`)) { statementInfo.content = statementInfo.content.substring(mode.length + 1).trim(); From b82b981c6edbc442b4acd42faa6770f9238e525d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Frode=20H=C3=A5skjold?= <jan.frode.haskjold@outlook.com> Date: Fri, 11 Apr 2025 10:12:16 +0200 Subject: [PATCH 02/12] cleanup/formatting --- src/views/results/index.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/views/results/index.ts b/src/views/results/index.ts index 0cf5609c..492446dc 100644 --- a/src/views/results/index.ts +++ b/src/views/results/index.ts @@ -383,9 +383,7 @@ async function runHandler(options?: StatementInfo) { updateStatusBar({executing: true}); const result = await JobManager.runSQLVerbose(statementDetail.content, undefined, 1); setCancelButtonVisibility(false); - //let content = ``; - let content = `**free\n\n-- Row data structure\n` - + `dcl-ds row_t qualified template;\n`; + let content = `**free\n\n-- Row data structure\ndcl-ds row_t qualified template;\n`; for (let i = 0; i < result.metadata.column_count; i++) { content += ` ${isNaN(+result.metadata.columns[i].label.charAt(0)) ? '' : 'col'}${result.metadata.columns[i].label.toLowerCase()} `; @@ -484,7 +482,7 @@ async function runHandler(options?: StatementInfo) { ]; content += insertStatement.join(`\n`) + `;\n`; } - break; + break; } const textDoc = await vscode.workspace.openTextDocument({ language: statementDetail.qualifier, content }); From d2c4608ab2174e031ed0a2c401380ed66f66605c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Frode=20H=C3=A5skjold?= <jan.frode.haskjold@outlook.com> Date: Fri, 11 Apr 2025 18:59:13 +0200 Subject: [PATCH 03/12] Include statement in generated source as a comment --- src/views/results/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/views/results/index.ts b/src/views/results/index.ts index 492446dc..8db0455f 100644 --- a/src/views/results/index.ts +++ b/src/views/results/index.ts @@ -383,7 +383,9 @@ async function runHandler(options?: StatementInfo) { updateStatusBar({executing: true}); const result = await JobManager.runSQLVerbose(statementDetail.content, undefined, 1); setCancelButtonVisibility(false); - let content = `**free\n\n-- Row data structure\ndcl-ds row_t qualified template;\n`; + let content = `**free\n\n` + + `// statement: ${statementDetail.content}\n\n` + + `// Row data structure\ndcl-ds row_t qualified template;\n`; for (let i = 0; i < result.metadata.column_count; i++) { content += ` ${isNaN(+result.metadata.columns[i].label.charAt(0)) ? '' : 'col'}${result.metadata.columns[i].label.toLowerCase()} `; From 79d5820f8a88835abb4238fed3e7515bc88ac1e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Frode=20H=C3=A5skjold?= <jan.frode.haskjold@outlook.com> Date: Fri, 11 Apr 2025 19:15:31 +0200 Subject: [PATCH 04/12] RPG qualifier only allowed for select statements --- src/views/results/index.ts | 110 +++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 53 deletions(-) diff --git a/src/views/results/index.ts b/src/views/results/index.ts index 8db0455f..8991e7af 100644 --- a/src/views/results/index.ts +++ b/src/views/results/index.ts @@ -377,62 +377,66 @@ async function runHandler(options?: StatementInfo) { } } else if ([`rpg`].includes(statementDetail.qualifier)) { - chosenView.setLoadingText(`Executing SQL statement...`, false); + if (statementDetail.statement.type !== StatementType.Select) { + vscode.window.showErrorMessage('RPG 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); - let content = `**free\n\n` - + `// statement: ${statementDetail.content}\n\n` - + `// Row data structure\ndcl-ds row_t qualified template;\n`; - - for (let i = 0; i < result.metadata.column_count; i++) { - content += ` ${isNaN(+result.metadata.columns[i].label.charAt(0)) ? '' : 'col'}${result.metadata.columns[i].label.toLowerCase()} `; - switch (result.metadata.columns[i].type) { - case `NUMERIC`: - content += `zoned(${result.metadata.columns[i].precision}${result.metadata.columns[i].scale > 0 ? ' : ' + result.metadata.columns[i].scale : ''});\n`; - break; - case `DECIMAL`: - content += `packed(${result.metadata.columns[i].precision}${result.metadata.columns[i].scale > 0 ? ' : ' + result.metadata.columns[i].scale : ''});\n`; - break; - case `CHAR`: - content += `char(${result.metadata.columns[i].precision});\n`; - break; - case `VARCHAR`: - content += `varchar(${result.metadata.columns[i].precision});\n`; - break; - case `DATE`: - content += `date;\n`; - break; - case `TIME`: - content += `time;\n`; - break; - case `TIMESTAMP`: - content += `timestamp;\n`; - break; - case `SMALLINT`: - content += `int(5);\n`; - break; - case `INTEGER`: - content += `int(10);\n`; - break; - case `BIGINT`: - content += `int(20);\n`; - break; - case `BOOLEAN`: - content += `ind;\n`; - break; - default: - content += `// type:${result.metadata.columns[i].type} precision:${result.metadata.columns[i].precision} scale:${result.metadata.columns[i].scale}\n`; - break; + setCancelButtonVisibility(true); + updateStatusBar({executing: true}); + const result = await JobManager.runSQLVerbose(statementDetail.content, undefined, 1); + setCancelButtonVisibility(false); + let content = `**free\n\n` + + `// statement: ${statementDetail.content}\n\n` + + `// Row data structure\ndcl-ds row_t qualified template;\n`; + + for (let i = 0; i < result.metadata.column_count; i++) { + content += ` ${isNaN(+result.metadata.columns[i].label.charAt(0)) ? '' : 'col'}${result.metadata.columns[i].label.toLowerCase()} `; + switch (result.metadata.columns[i].type) { + case `NUMERIC`: + content += `zoned(${result.metadata.columns[i].precision}${result.metadata.columns[i].scale > 0 ? ' : ' + result.metadata.columns[i].scale : ''});\n`; + break; + case `DECIMAL`: + content += `packed(${result.metadata.columns[i].precision}${result.metadata.columns[i].scale > 0 ? ' : ' + result.metadata.columns[i].scale : ''});\n`; + break; + case `CHAR`: + content += `char(${result.metadata.columns[i].precision});\n`; + break; + case `VARCHAR`: + content += `varchar(${result.metadata.columns[i].precision});\n`; + break; + case `DATE`: + content += `date;\n`; + break; + case `TIME`: + content += `time;\n`; + break; + case `TIMESTAMP`: + content += `timestamp;\n`; + break; + case `SMALLINT`: + content += `int(5);\n`; + break; + case `INTEGER`: + content += `int(10);\n`; + break; + case `BIGINT`: + content += `int(20);\n`; + break; + case `BOOLEAN`: + content += `ind;\n`; + break; + default: + content += `// type:${result.metadata.columns[i].type} precision:${result.metadata.columns[i].precision} scale:${result.metadata.columns[i].scale}\n`; + break; + } } + content += `end-ds;\n`; + const textDoc = await vscode.workspace.openTextDocument({ language: 'rpgle', content }); + await vscode.window.showTextDocument(textDoc); + updateStatusBar({executing: false}); + chosenView.setLoadingText(`RPG data structure generated.`, false); } - content += `end-ds;\n`; - const textDoc = await vscode.workspace.openTextDocument({ language: 'rpgle', content }); - await vscode.window.showTextDocument(textDoc); - updateStatusBar({executing: false}); - chosenView.setLoadingText(`RPG data structure generated.`, false); } else { // Otherwise... it's a bit complicated. From 2515d80775e1ec05bfcd95003584aecf7a18f3b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Frode=20H=C3=A5skjold?= <jan.frode.haskjold@outlook.com> Date: Wed, 16 Apr 2025 22:03:03 +0200 Subject: [PATCH 05/12] Move RPG code generation to another function --- src/views/results/index.ts | 102 ++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 52 deletions(-) diff --git a/src/views/results/index.ts b/src/views/results/index.ts index 8991e7af..831dc879 100644 --- a/src/views/results/index.ts +++ b/src/views/results/index.ts @@ -17,6 +17,7 @@ import { generateSqlForAdvisedIndexes } from "./explain/advice"; import { updateStatusBar } from "../jobManager/statusBar"; import { DbCache } from "../../language/providers/logic/cache"; import { ExplainType } from "../../connection/types"; +import { ColumnMetaData } from "@ibm/mapepire-js"; export type StatementQualifier = "statement" | "update" | "explain" | "onlyexplain" | "json" | "csv" | "cl" | "sql" | "rpg"; @@ -376,62 +377,12 @@ async function runHandler(options?: StatementInfo) { vscode.window.showInformationMessage(`No job currently selected.`); } - } else if ([`rpg`].includes(statementDetail.qualifier)) { + } else if (statementDetail.qualifier === `rpg`) { if (statementDetail.statement.type !== StatementType.Select) { vscode.window.showErrorMessage('RPG 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); - let content = `**free\n\n` - + `// statement: ${statementDetail.content}\n\n` - + `// Row data structure\ndcl-ds row_t qualified template;\n`; - - for (let i = 0; i < result.metadata.column_count; i++) { - content += ` ${isNaN(+result.metadata.columns[i].label.charAt(0)) ? '' : 'col'}${result.metadata.columns[i].label.toLowerCase()} `; - switch (result.metadata.columns[i].type) { - case `NUMERIC`: - content += `zoned(${result.metadata.columns[i].precision}${result.metadata.columns[i].scale > 0 ? ' : ' + result.metadata.columns[i].scale : ''});\n`; - break; - case `DECIMAL`: - content += `packed(${result.metadata.columns[i].precision}${result.metadata.columns[i].scale > 0 ? ' : ' + result.metadata.columns[i].scale : ''});\n`; - break; - case `CHAR`: - content += `char(${result.metadata.columns[i].precision});\n`; - break; - case `VARCHAR`: - content += `varchar(${result.metadata.columns[i].precision});\n`; - break; - case `DATE`: - content += `date;\n`; - break; - case `TIME`: - content += `time;\n`; - break; - case `TIMESTAMP`: - content += `timestamp;\n`; - break; - case `SMALLINT`: - content += `int(5);\n`; - break; - case `INTEGER`: - content += `int(10);\n`; - break; - case `BIGINT`: - content += `int(20);\n`; - break; - case `BOOLEAN`: - content += `ind;\n`; - break; - default: - content += `// type:${result.metadata.columns[i].type} precision:${result.metadata.columns[i].precision} scale:${result.metadata.columns[i].scale}\n`; - break; - } - } - content += `end-ds;\n`; + let content: string = await statementToRpgDs(statementDetail); const textDoc = await vscode.workspace.openTextDocument({ language: 'rpgle', content }); await vscode.window.showTextDocument(textDoc); updateStatusBar({executing: false}); @@ -542,6 +493,53 @@ async function runHandler(options?: StatementInfo) { } } +async function statementToRpgDs(statement: ParsedStatementInfo) : Promise<string> { + setCancelButtonVisibility(true); + updateStatusBar({executing: true}); + const result = await JobManager.runSQLVerbose(statement.content, undefined, 1); + setCancelButtonVisibility(false); + + let content = `**free\n\n` + + `// statement: ${statement.content}\n\n` + + `// Row data structure\ndcl-ds row_t qualified template;\n`; + + for (let i = 0; i < result.metadata.column_count; i++) { + content += ` ${isNaN(+result.metadata.columns[i].label.charAt(0)) ? '' : 'col'}${result.metadata.columns[i].label.toLowerCase()} `; + content += columnToRpgDefinition(result.metadata.columns[i]); + } + content += `end-ds;\n`; + return content; +} + +function columnToRpgDefinition(column: ColumnMetaData) : string { + switch (column.type) { + case `NUMERIC`: + return `zoned(${column.precision}${column.scale > 0 ? ' : ' + column.scale : ''});\n`; + case `DECIMAL`: + return `packed(${column.precision}${column.scale > 0 ? ' : ' + column.scale : ''});\n`; + case `CHAR`: + return `char(${column.precision});\n`; + case `VARCHAR`: + return `varchar(${column.precision});\n`; + case `DATE`: + return `date;\n`; + case `TIME`: + return `time;\n`; + case `TIMESTAMP`: + return `timestamp;\n`; + case `SMALLINT`: + return `int(5);\n`; + case `INTEGER`: + return `int(10);\n`; + case `BIGINT`: + return `int(20);\n`; + case `BOOLEAN`: + return `ind;\n`; + default: + return `// type:${column.type} precision:${column.precision} scale:${column.scale}\n`; + } +} + export function parseStatement(editor?: vscode.TextEditor, existingInfo?: StatementInfo): ParsedStatementInfo { let statementInfo: ParsedStatementInfo = { content: ``, From 79d98c0a83ca56314a8a9097369da8711528cf25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Frode=20H=C3=A5skjold?= <jan.frode.haskjold@outlook.com> Date: Wed, 16 Apr 2025 22:20:49 +0200 Subject: [PATCH 06/12] Move statement execution and ui stuff outside of rpg codegen function --- src/views/results/index.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/views/results/index.ts b/src/views/results/index.ts index 831dc879..218a46f9 100644 --- a/src/views/results/index.ts +++ b/src/views/results/index.ts @@ -17,7 +17,7 @@ import { generateSqlForAdvisedIndexes } from "./explain/advice"; import { updateStatusBar } from "../jobManager/statusBar"; import { DbCache } from "../../language/providers/logic/cache"; import { ExplainType } from "../../connection/types"; -import { ColumnMetaData } from "@ibm/mapepire-js"; +import { ColumnMetaData, QueryResult } from "@ibm/mapepire-js"; export type StatementQualifier = "statement" | "update" | "explain" | "onlyexplain" | "json" | "csv" | "cl" | "sql" | "rpg"; @@ -382,7 +382,11 @@ async function runHandler(options?: StatementInfo) { vscode.window.showErrorMessage('RPG qualifier only supported for select statements'); } else { chosenView.setLoadingText(`Executing SQL statement...`, false); - let content: string = await statementToRpgDs(statementDetail); + setCancelButtonVisibility(true); + updateStatusBar({executing: true}); + const result = await JobManager.runSQLVerbose(statementDetail.content, undefined, 1); + setCancelButtonVisibility(false); + let content: string = await statementToRpgDs(result, statementDetail.content); const textDoc = await vscode.workspace.openTextDocument({ language: 'rpgle', content }); await vscode.window.showTextDocument(textDoc); updateStatusBar({executing: false}); @@ -493,14 +497,10 @@ async function runHandler(options?: StatementInfo) { } } -async function statementToRpgDs(statement: ParsedStatementInfo) : Promise<string> { - setCancelButtonVisibility(true); - updateStatusBar({executing: true}); - const result = await JobManager.runSQLVerbose(statement.content, undefined, 1); - setCancelButtonVisibility(false); +function statementToRpgDs(result: QueryResult<any>, statement: string) : string { let content = `**free\n\n` - + `// statement: ${statement.content}\n\n` + + `// statement: ${statement}\n\n` + `// Row data structure\ndcl-ds row_t qualified template;\n`; for (let i = 0; i < result.metadata.column_count; i++) { From c954d459969cd564b7b6ef5379ef2e6d0f244bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Frode=20H=C3=A5skjold?= <jan.frode.haskjold@outlook.com> Date: Wed, 16 Apr 2025 22:36:46 +0200 Subject: [PATCH 07/12] fix formatting --- src/views/results/index.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/views/results/index.ts b/src/views/results/index.ts index 218a46f9..7a251470 100644 --- a/src/views/results/index.ts +++ b/src/views/results/index.ts @@ -504,8 +504,8 @@ function statementToRpgDs(result: QueryResult<any>, statement: string) : string + `// Row data structure\ndcl-ds row_t qualified template;\n`; for (let i = 0; i < result.metadata.column_count; i++) { - content += ` ${isNaN(+result.metadata.columns[i].label.charAt(0)) ? '' : 'col'}${result.metadata.columns[i].label.toLowerCase()} `; - content += columnToRpgDefinition(result.metadata.columns[i]); + const name = `${isNaN(+result.metadata.columns[i].label.charAt(0)) ? '' : 'col'}${result.metadata.columns[i].label.toLowerCase()}` + content += ` ${name} ${columnToRpgDefinition(result.metadata.columns[i])};\n`; } content += `end-ds;\n`; return content; @@ -514,29 +514,29 @@ function statementToRpgDs(result: QueryResult<any>, statement: string) : string function columnToRpgDefinition(column: ColumnMetaData) : string { switch (column.type) { case `NUMERIC`: - return `zoned(${column.precision}${column.scale > 0 ? ' : ' + column.scale : ''});\n`; + return `zoned(${column.precision}${column.scale > 0 ? ' : ' + column.scale : ''})`; case `DECIMAL`: - return `packed(${column.precision}${column.scale > 0 ? ' : ' + column.scale : ''});\n`; + return `packed(${column.precision}${column.scale > 0 ? ' : ' + column.scale : ''})`; case `CHAR`: - return `char(${column.precision});\n`; + return `char(${column.precision})`; case `VARCHAR`: - return `varchar(${column.precision});\n`; + return `varchar(${column.precision})`; case `DATE`: - return `date;\n`; + return `date`; case `TIME`: - return `time;\n`; + return `time`; case `TIMESTAMP`: - return `timestamp;\n`; + return `timestamp`; case `SMALLINT`: - return `int(5);\n`; + return `int(5)`; case `INTEGER`: - return `int(10);\n`; + return `int(10)`; case `BIGINT`: - return `int(20);\n`; + return `int(20)`; case `BOOLEAN`: - return `ind;\n`; + return `ind`; default: - return `// type:${column.type} precision:${column.precision} scale:${column.scale}\n`; + return `// type:${column.type} precision:${column.precision} scale:${column.scale}`; } } From c797cede7565d1f3b557d0dac85279f49b003cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Frode=20H=C3=A5skjold?= <jan.frode.haskjold@outlook.com> Date: Thu, 17 Apr 2025 12:23:54 +0200 Subject: [PATCH 08/12] Refactoring --- src/views/results/codegen.test.ts | 8 +++++ src/views/results/codegen.ts | 40 ++++++++++++++++++++++++ src/views/results/index.ts | 52 ++++--------------------------- 3 files changed, 54 insertions(+), 46 deletions(-) create mode 100644 src/views/results/codegen.test.ts create mode 100644 src/views/results/codegen.ts diff --git a/src/views/results/codegen.test.ts b/src/views/results/codegen.test.ts new file mode 100644 index 00000000..c5044e22 --- /dev/null +++ b/src/views/results/codegen.test.ts @@ -0,0 +1,8 @@ +import { assert, expect, test } from 'vitest' +import { columnToRpgDefinition } from './codegen'; + +test('Basic tokens', () => { + const rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'VARCHAR', precision: 60, scale: 0}); + expect(rpgdef).toBe('varchar(60)'); +}); + diff --git a/src/views/results/codegen.ts b/src/views/results/codegen.ts new file mode 100644 index 00000000..9b488059 --- /dev/null +++ b/src/views/results/codegen.ts @@ -0,0 +1,40 @@ +import { ColumnMetaData, QueryResult } from "@ibm/mapepire-js"; + +export function queryResultToRpgDs(result: QueryResult<any>) : string { + let content = `dcl-ds row_t qualified template;\n`; + for (let i = 0; i < result.metadata.column_count; i++) { + const name = `${isNaN(+result.metadata.columns[i].label.charAt(0)) ? '' : 'col'}${result.metadata.columns[i].label.toLowerCase()}` + content += ` ${name} ${columnToRpgDefinition(result.metadata.columns[i])};\n`; + } + content += `end-ds;\n`; + return content; +} + +export function columnToRpgDefinition(column: ColumnMetaData) : string { + switch (column.type) { + case `NUMERIC`: + return `zoned(${column.precision}${column.scale > 0 ? ' : ' + column.scale : ''})`; + case `DECIMAL`: + return `packed(${column.precision}${column.scale > 0 ? ' : ' + 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 `int(5)`; + case `INTEGER`: + return `int(10)`; + case `BIGINT`: + return `int(20)`; + case `BOOLEAN`: + return `ind`; + default: + return `// type:${column.type} precision:${column.precision} scale:${column.scale}`; + } +} diff --git a/src/views/results/index.ts b/src/views/results/index.ts index 7a251470..82837aa1 100644 --- a/src/views/results/index.ts +++ b/src/views/results/index.ts @@ -17,7 +17,7 @@ import { generateSqlForAdvisedIndexes } from "./explain/advice"; import { updateStatusBar } from "../jobManager/statusBar"; import { DbCache } from "../../language/providers/logic/cache"; import { ExplainType } from "../../connection/types"; -import { ColumnMetaData, QueryResult } from "@ibm/mapepire-js"; +import { queryResultToRpgDs } from "./codegen"; export type StatementQualifier = "statement" | "update" | "explain" | "onlyexplain" | "json" | "csv" | "cl" | "sql" | "rpg"; @@ -386,10 +386,13 @@ async function runHandler(options?: StatementInfo) { updateStatusBar({executing: true}); const result = await JobManager.runSQLVerbose(statementDetail.content, undefined, 1); setCancelButtonVisibility(false); - let content: string = await statementToRpgDs(result, statementDetail.content); + updateStatusBar({executing: false}); + let content = `**free\n\n` + + `// statement: ${statementDetail.content}\n\n` + + `// Row data structure\n` + + queryResultToRpgDs(result); const textDoc = await vscode.workspace.openTextDocument({ language: 'rpgle', content }); await vscode.window.showTextDocument(textDoc); - updateStatusBar({executing: false}); chosenView.setLoadingText(`RPG data structure generated.`, false); } @@ -497,49 +500,6 @@ async function runHandler(options?: StatementInfo) { } } -function statementToRpgDs(result: QueryResult<any>, statement: string) : string { - - let content = `**free\n\n` - + `// statement: ${statement}\n\n` - + `// Row data structure\ndcl-ds row_t qualified template;\n`; - - for (let i = 0; i < result.metadata.column_count; i++) { - const name = `${isNaN(+result.metadata.columns[i].label.charAt(0)) ? '' : 'col'}${result.metadata.columns[i].label.toLowerCase()}` - content += ` ${name} ${columnToRpgDefinition(result.metadata.columns[i])};\n`; - } - content += `end-ds;\n`; - return content; -} - -function columnToRpgDefinition(column: ColumnMetaData) : string { - switch (column.type) { - case `NUMERIC`: - return `zoned(${column.precision}${column.scale > 0 ? ' : ' + column.scale : ''})`; - case `DECIMAL`: - return `packed(${column.precision}${column.scale > 0 ? ' : ' + 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 `int(5)`; - case `INTEGER`: - return `int(10)`; - case `BIGINT`: - return `int(20)`; - case `BOOLEAN`: - return `ind`; - default: - return `// type:${column.type} precision:${column.precision} scale:${column.scale}`; - } -} - export function parseStatement(editor?: vscode.TextEditor, existingInfo?: StatementInfo): ParsedStatementInfo { let statementInfo: ParsedStatementInfo = { content: ``, From 64ab6c15d5703e13f054072a9b16fccbfa987a99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Frode=20H=C3=A5skjold?= <jan.frode.haskjold@outlook.com> Date: Thu, 17 Apr 2025 13:07:48 +0200 Subject: [PATCH 09/12] Column to RPG definition test --- src/views/results/codegen.test.ts | 39 +++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/views/results/codegen.test.ts b/src/views/results/codegen.test.ts index c5044e22..6bc57efa 100644 --- a/src/views/results/codegen.test.ts +++ b/src/views/results/codegen.test.ts @@ -1,8 +1,43 @@ import { assert, expect, test } from 'vitest' import { columnToRpgDefinition } from './codegen'; -test('Basic tokens', () => { - const rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'VARCHAR', precision: 60, scale: 0}); +test('Column to RPG definition', () => { + let rpgdef; + + rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'NUMERIC', precision: 11, scale: 0}); + expect(rpgdef).toBe('zoned(11)'); + + rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'DECIMAL', precision: 13, scale: 2}); + expect(rpgdef).toBe('packed(13 : 2)'); + + rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'VARCHAR', precision: 60, scale: 0}); expect(rpgdef).toBe('varchar(60)'); + + rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'CHAR', precision: 10, scale: 0}); + expect(rpgdef).toBe('char(10)'); + + rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'DATE', precision: 0, scale: 0}); + expect(rpgdef).toBe('date'); + + rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'TIME', precision: 0, scale: 0}); + expect(rpgdef).toBe('time'); + + rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'TIMESTAMP', precision: 0, scale: 0}); + expect(rpgdef).toBe('timestamp'); + + rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'SMALLINT', precision: 0, scale: 0}); + expect(rpgdef).toBe('int(5)'); + + rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'INTEGER', precision: 0, scale: 0}); + expect(rpgdef).toBe('int(10)'); + + rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'BIGINT', precision: 0, scale: 0}); + expect(rpgdef).toBe('int(20)'); + + rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'BOOLEAN', precision: 0, scale: 0}); + expect(rpgdef).toBe('ind'); + + rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'SOME_UNKNOWN_TYPE', precision: 0, scale: 0}); + expect(rpgdef).toBe('// type:SOME_UNKNOWN_TYPE precision:0 scale:0'); }); From 51e4adc4e701b4ced12956c8045324661a1070d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Frode=20H=C3=A5skjold?= <jan.frode.haskjold@outlook.com> Date: Thu, 17 Apr 2025 13:25:40 +0200 Subject: [PATCH 10/12] Rename script "language:test" to "test" --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f5beb23e..07cde980 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ ], "main": "./dist/extension.js", "scripts": { - "language:test": "vitest", + "test": "vitest", "dsc": "npx tsx src/dsc", "package": "vsce package", "vscode:prepublish": "rm -rf dist && npm run webpack && npm run dsc", From 824e62b15e0999ac35fe3fbc600a5ced9fe5011d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Frode=20H=C3=A5skjold?= <jan.frode.haskjold@outlook.com> Date: Thu, 17 Apr 2025 13:40:08 +0200 Subject: [PATCH 11/12] QueryResult to RPG data structure test --- src/views/results/codegen.test.ts | 51 ++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/views/results/codegen.test.ts b/src/views/results/codegen.test.ts index 6bc57efa..c8298bde 100644 --- a/src/views/results/codegen.test.ts +++ b/src/views/results/codegen.test.ts @@ -1,5 +1,6 @@ import { assert, expect, test } from 'vitest' -import { columnToRpgDefinition } from './codegen'; +import { columnToRpgDefinition, queryResultToRpgDs } from './codegen'; +import { QueryResult } from '@ibm/mapepire-js'; test('Column to RPG definition', () => { let rpgdef; @@ -41,3 +42,51 @@ test('Column to RPG definition', () => { expect(rpgdef).toBe('// type:SOME_UNKNOWN_TYPE precision:0 scale:0'); }); +test('QueryResult to RPG data structure', () => { + const queryResult: QueryResult<any> = { + metadata: { + column_count: 3, + columns: [ + { + display_size: 0, + label: 'id', + name: 'id', + type: 'INTEGER', + precision: 0, + scale: 0 + }, + { + display_size: 0, + label: 'name', + name: 'name', + type: 'VARCHAR', + precision: 80, + scale: 0 + }, + { + display_size: 0, + label: 'salary', + name: 'salary', + type: 'DECIMAL', + precision: 13, + scale: 2 + }, + ] + }, + is_done: true, + has_results: true, + update_count: 0, + data: [], + id: '', + success: true, + sql_rc: 0, + sql_state: '', + execution_time: 0 + }; + const ds = queryResultToRpgDs(queryResult); + const lines = ds.split('\n').filter(l => l !== ''); + expect(lines.length).toBe(5); + expect(lines.at(0)).toBe('dcl-ds row_t qualified template;'); + expect(lines.at(4)).toBe('end-ds;'); +}); + From 3d3976b1db8ae4959d391806bea2b5e90bc1bd40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Frode=20H=C3=A5skjold?= <jan.frode.haskjold@outlook.com> Date: Thu, 17 Apr 2025 13:49:27 +0200 Subject: [PATCH 12/12] Added assertions to "QueryResult to RPG data structure" test --- src/views/results/codegen.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/views/results/codegen.test.ts b/src/views/results/codegen.test.ts index c8298bde..375c8ac6 100644 --- a/src/views/results/codegen.test.ts +++ b/src/views/results/codegen.test.ts @@ -87,6 +87,9 @@ test('QueryResult to RPG data structure', () => { const lines = ds.split('\n').filter(l => l !== ''); expect(lines.length).toBe(5); expect(lines.at(0)).toBe('dcl-ds row_t qualified template;'); + expect(lines.at(1).trim()).toBe('id int(10);'); + expect(lines.at(2).trim()).toBe('name varchar(80);'); + expect(lines.at(3).trim()).toBe('salary packed(13 : 2);'); expect(lines.at(4)).toBe('end-ds;'); });