Skip to content

Commit ac6f693

Browse files
committed
Grab objects based on the library list
Signed-off-by: worksofliam <[email protected]>
1 parent fcc5e82 commit ac6f693

File tree

7 files changed

+162
-74
lines changed

7 files changed

+162
-74
lines changed

global.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ interface SQLParm {
3333
ROW_TYPE: "P" | "R",
3434
}
3535

36+
interface ResolvedSqlObject {
37+
schema: string;
38+
name: string;
39+
sqlType: string;
40+
}
41+
3642
interface BasicSQLObject {
3743
type: string;
3844
tableType: string;

package.json

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,14 +1296,6 @@
12961296
{
12971297
"name": "build",
12981298
"description": "Build an SQL statement"
1299-
},
1300-
{
1301-
"name": "activity",
1302-
"description": "Summarize the activity on the system"
1303-
},
1304-
{
1305-
"name": "set-schema",
1306-
"description": "Set the current schema"
13071299
}
13081300
]
13091301
}

src/aiProviders/context.ts

Lines changed: 87 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ interface MarkdownRef {
2525

2626
interface ContextDefinition {
2727
id: string;
28-
type: SQLType;
28+
type: string;
2929
content: any;
3030
}
3131

@@ -112,58 +112,106 @@ export async function buildSchemaDefinition(schema: string): Promise<Partial<Bas
112112
return compressedData;
113113
}
114114

115+
// hello my.world how/are you? -> [hello, my, ., world, how, /, are, you]
116+
function splitUpUserInput(input: string): string[] {
117+
input = input.replace(/[,!?$%\^&\*;:{}=\-`~()]/g, "")
118+
119+
let parts: string[] = [];
120+
121+
// Split the input string by spaces, dots and forward slash
122+
123+
let cPart = ``;
124+
let char: string;
125+
126+
const addPart = () => {
127+
if (cPart) {
128+
parts.push(cPart);
129+
cPart = ``;
130+
}
131+
}
132+
133+
for (let i = 0; i < input.length; i++) {
134+
char = input[i];
135+
136+
switch (char) {
137+
case ` `:
138+
addPart();
139+
break;
140+
141+
case `/`:
142+
case `.`:
143+
addPart();
144+
parts.push(char);
145+
break;
146+
147+
default:
148+
if ([`/`, `.`].includes(cPart)) {
149+
addPart();
150+
}
151+
152+
cPart += char;
153+
break;
154+
}
155+
}
156+
157+
addPart();
158+
159+
return parts;
160+
}
161+
115162
/**
116-
* Generates the SQL table definitions for the given schema and input words.
163+
* Generates the SQL object definitions for the given input string.
117164
*
118-
* This function parses the input words to identify potential table references,
119-
* filters them based on the schema's available tables, and generates the SQL
120-
* definitions for the identified tables.
165+
* This function parses the input words to identify potential SQL object references,
166+
* and generates the SQL definitions for the identified objects based on the library list.
121167
*
122-
* @param {string} schema - The schema name to search for tables.
123-
* @param {string[]} input - An array of words that may contain table references.
124-
* @returns {Promise<{[key: string]: string | undefined}>} A promise that resolves to an object
125-
* where the keys are table names and the values are their corresponding SQL definitions.
168+
* @param {string} input - A string that may contain table references.
126169
*/
127-
export async function generateTableDefinition(schema: string, input: string[]) {
128-
let tables: ContextDefinition[] = [];
170+
export async function getSqlContextItems(input: string): Promise<ContextDefinition[]> {
171+
let contextItems: ContextDefinition[] = [];
172+
129173
// Parse all SCHEMA.TABLE references first
130-
const schemaTableRefs = input.filter((word) => word.includes("."));
131-
const justWords = input.map((word) =>
132-
word.replace(/[,\/#!?$%\^&\*;:{}=\-_`~()]/g, "")
133-
);
174+
const tokens = splitUpUserInput(input);
134175

135176
// Remove plurals from words
136-
justWords.push(
137-
...justWords
177+
tokens.push(
178+
...tokens
138179
.filter((word) => word.endsWith("s"))
139180
.map((word) => word.slice(0, -1))
140181
);
141182

142-
// Filter prompt for possible refs to tables
143-
const validWords = justWords
144-
.filter(
145-
(word) => word.length > 2 && !word.endsWith("s") && !word.includes(`'`)
146-
)
147-
.map((word) => `${Statement.delimName(word, true)}`);
148-
149-
const allTables: BasicSQLObject[] = await Schemas.getObjects(schema, [
150-
`tables`,
151-
]);
152-
153-
const filteredTables = Array.from(
154-
new Set(
155-
validWords.filter((word) => allTables.some((table) => table.name == word))
156-
)
157-
);
183+
let possibleRefs: {name: string, schema?: string}[] = [];
184+
for (let i = 0; i < tokens.length; i++) {
185+
const token = tokens[i];
186+
187+
if (token[i+1] && [`.`, `/`].includes(token[i+1]) && tokens[i + 2]) {
188+
const nextToken = tokens[i + 2];
189+
190+
possibleRefs.push({
191+
name: Statement.delimName(nextToken, true),
192+
schema: Statement.delimName(token, true),
193+
});
194+
195+
i += 2; // Skip the next token as it's already processed
196+
197+
} else {
198+
possibleRefs.push({
199+
name: Statement.delimName(token, true),
200+
});
201+
}
202+
}
203+
204+
const allObjects = await Schemas.resolveObjects(possibleRefs);
158205

159206
await Promise.all(
160-
filteredTables.map(async (token) => {
207+
allObjects.map(async (o) => {
161208
try {
162-
const content = await Schemas.generateSQL(schema, token, `TABLE`);
209+
const content = await Schemas.generateSQL(o.schema, o.name, o.sqlType);
210+
163211
if (content) {
164-
tables.push({
165-
id: token,
166-
type: `tables`,
212+
contextItems.push({
213+
id: o.name,
214+
type: o.sqlType,
167215
content: content,
168216
});
169217
}
@@ -173,19 +221,7 @@ export async function generateTableDefinition(schema: string, input: string[]) {
173221
})
174222
);
175223

176-
// check for QSYS2
177-
// for (const item of schemaTableRefs) {
178-
// const [curSchema, table] = item.split(`.`);
179-
// if (curSchema.toUpperCase() === `QSYS2`) {
180-
// try {
181-
// const content = await Schemas.getObjects
182-
// } catch (e) {
183-
// continue
184-
// }
185-
// }
186-
// }
187-
188-
return tables;
224+
return contextItems;
189225
}
190226

191227
/**

src/aiProviders/continue/continueContextProvider.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { SelfCodeNode } from "../../views/jobManager/selfCodes/nodes";
1313
import {
1414
buildSchemaDefinition,
1515
canTalkToDb,
16-
generateTableDefinition
16+
getSqlContextItems
1717
} from "../context";
1818
import { DB2_SELF_PROMPT, DB2_SYSTEM_PROMPT } from "../prompts";
1919
import Configuration from "../../configuration";
@@ -179,10 +179,7 @@ export class db2ContextProvider implements IContextProvider {
179179
break;
180180
default:
181181
// 2. TABLE References
182-
const tablesRefs = await generateTableDefinition(
183-
schema,
184-
fullInput.split(` `)
185-
);
182+
const tablesRefs = await getSqlContextItems(fullInput);
186183
for (const table of tablesRefs) {
187184
contextItems.push({
188185
name: `table definition for ${table.id}`,

src/aiProviders/continue/listTablesContextProvider.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import Schemas from "../../database/schemas";
1111
import Table from "../../database/table";
1212
import {
1313
buildSchemaDefinition,
14-
generateTableDefinition
14+
getSqlContextItems
1515
} from "../context";
1616
import Configuration from "../../configuration";
1717

@@ -82,10 +82,7 @@ class ListDb2iTables implements IContextProvider {
8282
}
8383

8484
} else {
85-
const tablesRefs = await generateTableDefinition(
86-
this.schema,
87-
extras.fullInput.split(` `)
88-
);
85+
const tablesRefs = await getSqlContextItems(extras.fullInput);
8986
for (const table of tablesRefs) {
9087
contextItems.push({
9188
name: `table definition for ${table.id}`,

src/aiProviders/prompt.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { JobManager } from "../config";
22
import Configuration from "../configuration";
33
import { JobInfo } from "../connection/manager";
4-
import { buildSchemaDefinition, canTalkToDb, generateTableDefinition } from "./context";
4+
import { buildSchemaDefinition, canTalkToDb, getSqlContextItems } from "./context";
55
import { DB2_SYSTEM_PROMPT } from "./prompts";
66

77
export interface PromptOptions {
@@ -48,10 +48,7 @@ export async function buildPrompt(input: string, options: PromptOptions = {}): P
4848
// TODO: self?
4949

5050
progress(`Building table definition for ${currentSchema}...`);
51-
const refs = await generateTableDefinition(
52-
currentSchema,
53-
input.split(` `)
54-
);
51+
const refs = await getSqlContextItems(input);
5552

5653
if (options.history) {
5754
contextItems.push(...options.history);

src/database/schemas.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,69 @@ function getFilterClause(againstColumn: string, filter: string, noAnd?: boolean)
4747
}
4848

4949
export default class Schemas {
50+
/**
51+
* Resolves to the following SQL types: TABLE, VIEW, ALIAS, INDEX, FUNCTION and PROCEDURE
52+
*/
53+
static async resolveObjects(sqlObjects: {name: string, schema?: string}[]): Promise<ResolvedSqlObject[]> {
54+
let statements: string[] = [];
55+
let parameters: BasicColumnType[] = [];
56+
57+
// First, we use OBJECT_STATISTICS to resolve the object based on the library list.
58+
// But, if the object is qualified with a schema, we need to use that schema to get the correct object.
59+
for (const obj of sqlObjects) {
60+
if (obj.schema) {
61+
statements.push(
62+
`select OBJLONGNAME as name, OBJLONGSCHEMA as schema, SQL_OBJECT_TYPE as sqlType from table(qsys2.object_statistics(?, '*ALL', object_name => ?)) where SQL_OBJECT_TYPE IS NOT NULL`
63+
);
64+
parameters.push(obj.schema, obj.name);
65+
} else {
66+
statements.push(
67+
`select OBJLONGNAME as name, OBJLONGSCHEMA as schema, SQL_OBJECT_TYPE as sqlType from table(qsys2.object_statistics('*LIBL', '*ALL', object_name => ?)) where SQL_OBJECT_TYPE IS NOT NULL`
68+
);
69+
parameters.push(obj.name);
70+
}
71+
}
72+
73+
// We have to do a little bit more logic for routines, because they are not included properly in OBJECT_STATISTICS.
74+
// So we do a join against the library list view and SYSROUTINES (which has a list of all routines in a given schema)
75+
// to get the correct schema and name.
76+
const unqualified = sqlObjects.filter(obj => !obj.schema).map(obj => obj.name);
77+
const qualified = sqlObjects.filter(obj => obj.schema);
78+
const qualifiedClause = qualified.map(obj => `(s.routine_name = ? AND s.routine_schema = ?)`).join(` OR `);
79+
80+
let baseStatement = [
81+
`select s.routine_name as name, l.schema_name as schema, s.ROUTINE_TYPE as sqlType`,
82+
`from qsys2.library_list_info as l`,
83+
`right join qsys2.sysroutines as s on l.schema_name = s.routine_schema`,
84+
`where `,
85+
` l.schema_name is not null and`,
86+
` s.routine_name in (${sqlObjects.map(() => `?`).join(`, `)})`,
87+
].join(` `);
88+
parameters.push(...unqualified);
89+
90+
if (qualified.length > 0) {
91+
baseStatement += ` and (${qualifiedClause})`;
92+
parameters.push(...qualified.flatMap(obj => [obj.name, obj.schema]));
93+
}
94+
95+
statements.push(baseStatement);
96+
97+
if (statements.length === 0) {
98+
return [];
99+
}
100+
101+
const query = `${statements.join(" UNION ALL ")}`;
102+
const objects: any[] = await JobManager.runSQL(query, { parameters });
103+
104+
const resolvedObjects: ResolvedSqlObject[] = objects.map(object => ({
105+
name: object.NAME,
106+
schema: object.SCHEMA,
107+
sqlType: object.SQLTYPE
108+
}));
109+
110+
return resolvedObjects;
111+
}
112+
50113
/**
51114
* @param schema Not user input
52115
*/

0 commit comments

Comments
 (0)