Skip to content

Commit 47c72e8

Browse files
authored
Merge pull request #368 from janfh/feature/rpg-codegen
Generate RPG data structure from result metadata
2 parents 3d61ddf + 9029619 commit 47c72e8

File tree

4 files changed

+159
-4
lines changed

4 files changed

+159
-4
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
],
3333
"main": "./dist/extension.js",
3434
"scripts": {
35-
"language:test": "vitest",
35+
"test": "vitest",
3636
"dsc": "npx tsx src/dsc",
3737
"package": "vsce package",
3838
"vscode:prepublish": "rm -rf dist && npm run webpack && npm run dsc",

src/views/results/codegen.test.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { assert, expect, test } from 'vitest'
2+
import { columnToRpgDefinition, queryResultToRpgDs } from './codegen';
3+
import { QueryResult } from '@ibm/mapepire-js';
4+
5+
test('Column to RPG definition', () => {
6+
let rpgdef;
7+
8+
rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'NUMERIC', precision: 11, scale: 0});
9+
expect(rpgdef).toBe('zoned(11)');
10+
11+
rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'DECIMAL', precision: 13, scale: 2});
12+
expect(rpgdef).toBe('packed(13 : 2)');
13+
14+
rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'VARCHAR', precision: 60, scale: 0});
15+
expect(rpgdef).toBe('varchar(60)');
16+
17+
rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'CHAR', precision: 10, scale: 0});
18+
expect(rpgdef).toBe('char(10)');
19+
20+
rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'DATE', precision: 0, scale: 0});
21+
expect(rpgdef).toBe('date');
22+
23+
rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'TIME', precision: 0, scale: 0});
24+
expect(rpgdef).toBe('time');
25+
26+
rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'TIMESTAMP', precision: 0, scale: 0});
27+
expect(rpgdef).toBe('timestamp');
28+
29+
rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'SMALLINT', precision: 0, scale: 0});
30+
expect(rpgdef).toBe('int(5)');
31+
32+
rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'INTEGER', precision: 0, scale: 0});
33+
expect(rpgdef).toBe('int(10)');
34+
35+
rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'BIGINT', precision: 0, scale: 0});
36+
expect(rpgdef).toBe('int(20)');
37+
38+
rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'BOOLEAN', precision: 0, scale: 0});
39+
expect(rpgdef).toBe('ind');
40+
41+
rpgdef = columnToRpgDefinition({display_size: 0, label: '', name: '', type: 'SOME_UNKNOWN_TYPE', precision: 0, scale: 0});
42+
expect(rpgdef).toBe('// type:SOME_UNKNOWN_TYPE precision:0 scale:0');
43+
});
44+
45+
test('QueryResult to RPG data structure', () => {
46+
const queryResult: QueryResult<any> = {
47+
metadata: {
48+
column_count: 3,
49+
columns: [
50+
{
51+
display_size: 0,
52+
label: 'id',
53+
name: 'id',
54+
type: 'INTEGER',
55+
precision: 0,
56+
scale: 0
57+
},
58+
{
59+
display_size: 0,
60+
label: 'name',
61+
name: 'name',
62+
type: 'VARCHAR',
63+
precision: 80,
64+
scale: 0
65+
},
66+
{
67+
display_size: 0,
68+
label: 'salary',
69+
name: 'salary',
70+
type: 'DECIMAL',
71+
precision: 13,
72+
scale: 2
73+
},
74+
]
75+
},
76+
is_done: true,
77+
has_results: true,
78+
update_count: 0,
79+
data: [],
80+
id: '',
81+
success: true,
82+
sql_rc: 0,
83+
sql_state: '',
84+
execution_time: 0
85+
};
86+
const ds = queryResultToRpgDs(queryResult);
87+
const lines = ds.split('\n').filter(l => l !== '');
88+
expect(lines.length).toBe(5);
89+
expect(lines.at(0)).toBe('dcl-ds row_t qualified template;');
90+
expect(lines.at(1).trim()).toBe('id int(10);');
91+
expect(lines.at(2).trim()).toBe('name varchar(80);');
92+
expect(lines.at(3).trim()).toBe('salary packed(13 : 2);');
93+
expect(lines.at(4)).toBe('end-ds;');
94+
});
95+

src/views/results/codegen.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { ColumnMetaData, QueryResult } from "@ibm/mapepire-js";
2+
3+
export function queryResultToRpgDs(result: QueryResult<any>) : string {
4+
let content = `dcl-ds row_t qualified template;\n`;
5+
for (let i = 0; i < result.metadata.column_count; i++) {
6+
const name = `${isNaN(+result.metadata.columns[i].label.charAt(0)) ? '' : 'col'}${result.metadata.columns[i].label.toLowerCase()}`
7+
content += ` ${name} ${columnToRpgDefinition(result.metadata.columns[i])};\n`;
8+
}
9+
content += `end-ds;\n`;
10+
return content;
11+
}
12+
13+
export function columnToRpgDefinition(column: ColumnMetaData) : string {
14+
switch (column.type) {
15+
case `NUMERIC`:
16+
return `zoned(${column.precision}${column.scale > 0 ? ' : ' + column.scale : ''})`;
17+
case `DECIMAL`:
18+
return `packed(${column.precision}${column.scale > 0 ? ' : ' + column.scale : ''})`;
19+
case `CHAR`:
20+
return `char(${column.precision})`;
21+
case `VARCHAR`:
22+
return `varchar(${column.precision})`;
23+
case `DATE`:
24+
return `date`;
25+
case `TIME`:
26+
return `time`;
27+
case `TIMESTAMP`:
28+
return `timestamp`;
29+
case `SMALLINT`:
30+
return `int(5)`;
31+
case `INTEGER`:
32+
return `int(10)`;
33+
case `BIGINT`:
34+
return `int(20)`;
35+
case `BOOLEAN`:
36+
return `ind`;
37+
default:
38+
return `// type:${column.type} precision:${column.precision} scale:${column.scale}`;
39+
}
40+
}

src/views/results/index.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ import { generateSqlForAdvisedIndexes } from "./explain/advice";
1717
import { updateStatusBar } from "../jobManager/statusBar";
1818
import { DbCache } from "../../language/providers/logic/cache";
1919
import { ExplainType } from "../../connection/types";
20+
import { queryResultToRpgDs } from "./codegen";
2021

21-
export type StatementQualifier = "statement" | "update" | "explain" | "onlyexplain" | "json" | "csv" | "cl" | "sql";
22+
export type StatementQualifier = "statement" | "update" | "explain" | "onlyexplain" | "json" | "csv" | "cl" | "sql" | "rpg";
2223

2324
export interface StatementInfo {
2425
content: string,
@@ -375,7 +376,26 @@ async function runHandler(options?: StatementInfo) {
375376
} else {
376377
vscode.window.showInformationMessage(`No job currently selected.`);
377378
}
378-
379+
380+
} else if (statementDetail.qualifier === `rpg`) {
381+
if (statementDetail.statement.type !== StatementType.Select) {
382+
vscode.window.showErrorMessage('RPG qualifier only supported for select statements');
383+
} else {
384+
chosenView.setLoadingText(`Executing SQL statement...`, false);
385+
setCancelButtonVisibility(true);
386+
updateStatusBar({executing: true});
387+
const result = await JobManager.runSQLVerbose(statementDetail.content, undefined, 1);
388+
setCancelButtonVisibility(false);
389+
updateStatusBar({executing: false});
390+
let content = `**free\n\n`
391+
+ `// statement: ${statementDetail.content}\n\n`
392+
+ `// Row data structure\n`
393+
+ queryResultToRpgDs(result);
394+
const textDoc = await vscode.workspace.openTextDocument({ language: 'rpgle', content });
395+
await vscode.window.showTextDocument(textDoc);
396+
chosenView.setLoadingText(`RPG data structure generated.`, false);
397+
}
398+
379399
} else {
380400
// Otherwise... it's a bit complicated.
381401
chosenView.setLoadingText(`Executing SQL statement...`, false);
@@ -524,7 +544,7 @@ export function parseStatement(editor?: vscode.TextEditor, existingInfo?: Statem
524544
}
525545

526546
if (statementInfo.content) {
527-
[`cl`, `json`, `csv`, `sql`, `explain`, `update`].forEach(mode => {
547+
[`cl`, `json`, `csv`, `sql`, `explain`, `update`, `rpg`].forEach(mode => {
528548
if (statementInfo.content.trim().toLowerCase().startsWith(mode + `:`)) {
529549
statementInfo.content = statementInfo.content.substring(mode.length + 1).trim();
530550

0 commit comments

Comments
 (0)