Skip to content

Commit dc8bf22

Browse files
authored
Merge pull request #335 from codefori/feature/singular_prompt_builder
Refactor and enhance Db2 for i context provider and SQL prompt guidelines
2 parents ceb4537 + 825288a commit dc8bf22

File tree

11 files changed

+574
-339
lines changed

11 files changed

+574
-339
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: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,18 +1296,35 @@
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
}
13101302
],
1303+
"languageModelTools": [
1304+
{
1305+
"name": "vscode-db2i-chat-sqlRunnerTool",
1306+
"tags": [
1307+
"sql"
1308+
],
1309+
"canBeReferencedInPrompt": true,
1310+
"toolReferenceName": "result",
1311+
"displayName": "Run SQL statement",
1312+
"icon": "$(play)",
1313+
"modelDescription": "Run an SQL statement and return the result",
1314+
"inputSchema": {
1315+
"type": "object",
1316+
"properties": {
1317+
"statement": {
1318+
"type": "string",
1319+
"description": "The statement to execute"
1320+
}
1321+
},
1322+
"required": [
1323+
"statement"
1324+
]
1325+
}
1326+
}
1327+
],
13111328
"snippets": [
13121329
{
13131330
"language": "sql",

src/aiProviders/context.ts

Lines changed: 112 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import * as vscode from "vscode";
33
import { JobManager } from "../config";
44
import Schemas, { AllSQLTypes, SQLType } from "../database/schemas";
55
import Statement from "../database/statement";
6-
import { DB2_SYSTEM_PROMPT } from "./continue/prompts";
6+
import { DB2_SYSTEM_PROMPT } from "./prompts";
7+
import { Db2ContextItems } from "./prompt";
78

89
export function canTalkToDb() {
910
return JobManager.getSelection() !== undefined;
@@ -23,12 +24,6 @@ interface MarkdownRef {
2324
SCHMEA?: string;
2425
}
2526

26-
interface ContextDefinition {
27-
id: string;
28-
type: SQLType;
29-
content: any;
30-
}
31-
3227
/**
3328
* Builds a semantic representation of a database schema by fetching all objects
3429
* associated with the schema, cleaning them, and filtering out unnecessary properties.
@@ -112,165 +107,135 @@ export async function buildSchemaDefinition(schema: string): Promise<Partial<Bas
112107
return compressedData;
113108
}
114109

110+
// hello my.world how/are you? -> [hello, my, ., world, how, /, are, you]
111+
function splitUpUserInput(input: string): string[] {
112+
input = input.replace(/[,!?$%\^&\*;:{}=\-`~()]/g, "")
113+
114+
let parts: string[] = [];
115+
116+
// Split the input string by spaces, dots and forward slash
117+
118+
let cPart = ``;
119+
let char: string;
120+
121+
const addPart = () => {
122+
if (cPart) {
123+
parts.push(cPart);
124+
cPart = ``;
125+
}
126+
}
127+
128+
for (let i = 0; i < input.length; i++) {
129+
char = input[i];
130+
131+
switch (char) {
132+
case ` `:
133+
addPart();
134+
break;
135+
136+
case `/`:
137+
case `.`:
138+
addPart();
139+
parts.push(char);
140+
break;
141+
142+
default:
143+
if ([`/`, `.`].includes(cPart)) {
144+
addPart();
145+
}
146+
147+
cPart += char;
148+
break;
149+
}
150+
}
151+
152+
addPart();
153+
154+
return parts;
155+
}
156+
115157
/**
116-
* Generates the SQL table definitions for the given schema and input words.
158+
* Generates the SQL object definitions for the given input string.
117159
*
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.
160+
* This function parses the input words to identify potential SQL object references,
161+
* and generates the SQL definitions for the identified objects based on the library list.
121162
*
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.
163+
* @param {string} input - A string that may contain table references.
126164
*/
127-
export async function generateTableDefinition(schema: string, input: string[]) {
128-
let tables: ContextDefinition[] = [];
165+
export async function getSqlContextItems(input: string): Promise<{items: Db2ContextItems[], refs: ResolvedSqlObject[]}> {
129166
// 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-
);
167+
const tokens = splitUpUserInput(input);
134168

135169
// Remove plurals from words
136-
justWords.push(
137-
...justWords
170+
tokens.push(
171+
...tokens
138172
.filter((word) => word.endsWith("s"))
139173
.map((word) => word.slice(0, -1))
140174
);
141175

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-
);
176+
let possibleRefs: {name: string, schema?: string}[] = [];
177+
for (let i = 0; i < tokens.length; i++) {
178+
const token = tokens[i];
179+
180+
if (token[i+1] && [`.`, `/`].includes(token[i+1]) && tokens[i + 2]) {
181+
const nextToken = tokens[i + 2];
158182

159-
await Promise.all(
160-
filteredTables.map(async (token) => {
161-
try {
162-
const content = await Schemas.generateSQL(schema, token, `TABLE`);
163-
if (content) {
164-
tables.push({
165-
id: token,
166-
type: `tables`,
167-
content: content,
168-
});
169-
}
170-
} catch (e) {
171-
// ignore
172-
}
173-
})
174-
);
183+
possibleRefs.push({
184+
name: Statement.delimName(nextToken, true),
185+
schema: Statement.delimName(token, true),
186+
});
187+
188+
i += 2; // Skip the next token as it's already processed
189+
190+
} else {
191+
possibleRefs.push({
192+
name: Statement.delimName(token, true),
193+
});
194+
}
195+
}
175196

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;
197+
const allObjects = await Schemas.resolveObjects(possibleRefs);
198+
const contextItems = await getContentItemsForRefs(allObjects);
199+
200+
return {
201+
items: contextItems,
202+
refs: allObjects,
203+
};
189204
}
190205

191-
// depreciated
192-
export async function findPossibleTables(
193-
stream: vscode.ChatResponseStream,
194-
schema: string,
195-
words: string[]
196-
) {
197-
let tables: TableRefs = {};
206+
export async function getContentItemsForRefs(allObjects: ResolvedSqlObject[]): Promise<Db2ContextItems[]> {
207+
const items: (Db2ContextItems|undefined)[] = await Promise.all(
208+
allObjects.map(async (o) => {
209+
try {
210+
if (o.sqlType === `SCHEMA`) {
211+
const schemaSemantic = await buildSchemaDefinition(o.name);
212+
if (schemaSemantic) {
213+
return {
214+
name: `SCHEMA Definition`,
215+
description: `${o.name} definition`,
216+
content: JSON.stringify(schemaSemantic)
217+
};
218+
}
219+
220+
return undefined;
198221

199-
// Parse all SCHEMA.TABLE references first
200-
const schemaTableRefs = words.filter((word) => word.includes("."));
201-
const justWords = words.map((word) =>
202-
word.replace(/[,\/#!?$%\^&\*;:{}=\-_`~()]/g, "")
203-
);
222+
} else {
223+
const content = await Schemas.generateSQL(o.schema, o.name, o.sqlType);
204224

205-
// Remove plurals from words
206-
justWords.push(
207-
...justWords
208-
.filter((word) => word.endsWith("s"))
209-
.map((word) => word.slice(0, -1))
225+
return {
226+
name: `${o.sqlType.toLowerCase()} definition for ${o.name}`,
227+
description: `${o.sqlType} definition`,
228+
content: content
229+
};
230+
}
231+
232+
} catch (e) {
233+
return undefined;
234+
}
235+
})
210236
);
211237

212-
// Filter prompt for possible refs to tables
213-
const validWords = justWords
214-
.filter(
215-
(word) => word.length > 2 && !word.endsWith("s") && !word.includes(`'`)
216-
)
217-
.map((word) => `'${Statement.delimName(word, true)}'`);
218-
219-
const objectFindStatement = [
220-
`SELECT `,
221-
` column.TABLE_SCHEMA,`,
222-
` column.TABLE_NAME,`,
223-
` column.COLUMN_NAME,`,
224-
` key.CONSTRAINT_NAME,`,
225-
` column.DATA_TYPE, `,
226-
` column.CHARACTER_MAXIMUM_LENGTH,`,
227-
` column.NUMERIC_SCALE, `,
228-
` column.NUMERIC_PRECISION,`,
229-
` column.IS_NULLABLE, `,
230-
// ` column.HAS_DEFAULT, `,
231-
// ` column.COLUMN_DEFAULT, `,
232-
` column.COLUMN_TEXT, `,
233-
` column.IS_IDENTITY`,
234-
`FROM QSYS2.SYSCOLUMNS2 as column`,
235-
`LEFT JOIN QSYS2.syskeycst as key`,
236-
` on `,
237-
` column.table_schema = key.table_schema and`,
238-
` column.table_name = key.table_name and`,
239-
` column.column_name = key.column_name`,
240-
`WHERE column.TABLE_SCHEMA = '${Statement.delimName(schema, true)}'`,
241-
...[
242-
schemaTableRefs.length > 0
243-
? `AND (column.TABLE_NAME in (${validWords.join(
244-
`, `
245-
)}) OR (${schemaTableRefs
246-
.map((ref) => {
247-
const [schema, table] = ref.split(".");
248-
const cleanedTable = table.replace(
249-
/[,\/#!?$%\^&\*;:{}=\-_`~()]/g,
250-
""
251-
);
252-
return `(column.TABLE_SCHEMA = '${Statement.delimName(
253-
schema,
254-
true
255-
)}' AND column.TABLE_NAME = '${Statement.delimName(
256-
cleanedTable,
257-
true
258-
)}')`;
259-
})
260-
.join(" OR ")}))`
261-
: `AND column.TABLE_NAME in (${validWords.join(`, `)})`,
262-
],
263-
`ORDER BY column.ORDINAL_POSITION`,
264-
].join(` `);
265-
// TODO
266-
const result: TableColumn[] = await JobManager.runSQL(objectFindStatement);
267-
268-
result.forEach((row) => {
269-
const tableName = row.TABLE_NAME.toLowerCase();
270-
if (!tables[tableName]) tables[tableName] = [];
271-
tables[tableName].push(row);
272-
});
273-
return tables;
238+
return items.filter((item) => item !== undefined);
274239
}
275240

276241
/**

0 commit comments

Comments
 (0)