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;');
 });