diff --git a/package.json b/package.json
index 99e82f13..6184c65c 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",
diff --git a/src/views/results/codegen.test.ts b/src/views/results/codegen.test.ts
new file mode 100644
index 00000000..375c8ac6
--- /dev/null
+++ b/src/views/results/codegen.test.ts
@@ -0,0 +1,95 @@
+import { assert, expect, test } from 'vitest'
+import { columnToRpgDefinition, queryResultToRpgDs } from './codegen';
+import { QueryResult } from '@ibm/mapepire-js';
+
+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');
+});
+
+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(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;');
+});
+
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 f4fc7a46..82837aa1 100644
--- a/src/views/results/index.ts
+++ b/src/views/results/index.ts
@@ -17,8 +17,9 @@ import { generateSqlForAdvisedIndexes } from "./explain/advice";
 import { updateStatusBar } from "../jobManager/statusBar";
 import { DbCache } from "../../language/providers/logic/cache";
 import { ExplainType } from "../../connection/types";
+import { queryResultToRpgDs } from "./codegen";
 
-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 +376,26 @@ async function runHandler(options?: StatementInfo) {
           } else {
             vscode.window.showInformationMessage(`No job currently selected.`);
           }
-        
+
+        } 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);
+            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);
+            chosenView.setLoadingText(`RPG data structure generated.`, false);
+          }
+
         } else {
           // Otherwise... it's a bit complicated.
           chosenView.setLoadingText(`Executing SQL statement...`, false);
@@ -524,7 +544,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();