Skip to content

Commit

Permalink
Improvements to signature support
Browse files Browse the repository at this point in the history
Signed-off-by: worksofliam <[email protected]>
  • Loading branch information
worksofliam committed Sep 17, 2024
1 parent 86fb7bd commit 4d9dde6
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 85 deletions.
1 change: 1 addition & 0 deletions global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ interface SQLParm {
DEFAULT?: string,
LONG_COMMENT?: string,
ORDINAL_POSITION: number,
ROW_TYPE: "P" | "R",
}

interface BasicSQLObject {
Expand Down
44 changes: 4 additions & 40 deletions src/database/callable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface CallableRoutine {
export interface CallableSignature {
specificName: string;
parms: SQLParm[];
returns: SQLParm[];
}

export default class Callable {
Expand Down Expand Up @@ -45,7 +46,7 @@ export default class Callable {
const results = await JobManager.runSQL<SQLParm>(
[
`SELECT * FROM QSYS2.SYSPARMS`,
`WHERE SPECIFIC_SCHEMA = ? AND ROW_TYPE = 'P' AND SPECIFIC_NAME in (${specificNames.map(n => `?`).join(`, `)})`,
`WHERE SPECIFIC_SCHEMA = ? AND ROW_TYPE in ('P', 'R') AND SPECIFIC_NAME in (${specificNames.map(n => `?`).join(`, `)})`,
`ORDER BY ORDINAL_POSITION`
].join(` `),
{
Expand All @@ -60,48 +61,11 @@ export default class Callable {
const groupedResults: CallableSignature[] = uniqueSpecificNames.map(name => {
return {
specificName: name,
parms: results.filter(row => row.SPECIFIC_NAME === name)
parms: results.filter(row => row.SPECIFIC_NAME === name && row.ROW_TYPE === `P`),
returns: results.filter(row => row.SPECIFIC_NAME === name && row.ROW_TYPE === `R`)
}
});

return groupedResults;
}

/**
* @param schema Not user input
* @param specificName Not user input
* @returns
*/
static getParms(schema: string, specificName: string, resolveName: boolean = false): Promise<SQLParm[]> {
const rowType = `P`; // Parameter
return Callable.getFromSysParms(schema, specificName, rowType, resolveName);
}

static getResultColumns(schema: string, specificName: string, resolveName: boolean = false) {
const rowType = `R`; // Row
return Callable.getFromSysParms(schema, specificName, rowType, resolveName);
}

static getFromSysParms(schema: string, name: string, rowType: "P"|"R", resolveName: boolean = false): Promise<SQLParm[]> {
let parameters = [schema, rowType];

let specificNameClause = undefined;

if (resolveName) {
specificNameClause = `SPECIFIC_NAME = (select specific_name from qsys2.sysroutines where specific_schema = ? and routine_name = ?)`;
parameters.push(schema, name);
} else {
specificNameClause = `SPECIFIC_NAME = ?`;
parameters.push(name);
}

return JobManager.runSQL<SQLParm>(
[
`SELECT * FROM QSYS2.SYSPARMS`,
`WHERE SPECIFIC_SCHEMA = ? AND ROW_TYPE = ? AND ${specificNameClause}`,
`ORDER BY ORDINAL_POSITION`
].join(` `),
{ parameters }
);
}
}
8 changes: 5 additions & 3 deletions src/language/providers/completionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,15 @@ async function getObjectColumns(
name = Statement.noQuotes(Statement.delimName(name, true));

if (isUDTF) {
const resultSet = await DbCache.getRoutineResultColumns(schema, name, true);
const resultSet = await DbCache.getCachedSignatures(schema, name);

if (!resultSet?.length ? true : false) {
return [];
}

const chosenSig = resultSet[resultSet.length-1];

completionItems = resultSet.map((i) =>
completionItems = chosenSig.returns.map((i) =>
createCompletionItem(
Statement.prettyName(i.PARAMETER_NAME),
CompletionItemKind.Field,
Expand All @@ -111,7 +113,7 @@ async function getObjectColumns(
);

} else {
const columns = await DbCache.getColumns(schema, name);
const columns = await DbCache.getObjectColumns(schema, name);

if (!columns?.length ? true : false) {
return [];
Expand Down
29 changes: 19 additions & 10 deletions src/language/providers/hoverProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export const openProvider = workspace.onDidOpenTextDocument(async (document) =>
const schema = Statement.noQuotes(Statement.delimName(first.object.schema || defaultSchema, true));
const result = await DbCache.getType(schema, name, `PROCEDURE`);
if (result) {
await DbCache.getRoutineResultColumns(schema, name, true);
await DbCache.getSignaturesFor(schema, name, result.specificNames);
}
}
Expand All @@ -40,11 +39,10 @@ export const openProvider = workspace.onDidOpenTextDocument(async (document) =>
if (ref.isUDTF) {
const result = await DbCache.getType(schema, name, `FUNCTION`);
if (result) {
await DbCache.getRoutineResultColumns(schema, name, true);
await DbCache.getSignaturesFor(schema, name, result.specificNames);
}
} else {
await DbCache.getColumns(schema, name);
await DbCache.getObjectColumns(schema, name);
}
}
}
Expand All @@ -69,21 +67,23 @@ export const hoverProvider = languages.registerHoverProvider({ language: `sql` }

if (statementAt) {
const refs = statementAt.getObjectReferences();
const possibleNames = refs.map(ref => ref.object.name).filter(name => name);

for (const ref of refs) {
const atRef = offset >= ref.tokens[0].range.start && offset <= ref.tokens[ref.tokens.length - 1].range.end;

if (atRef) {
const schema = ref.object.schema || defaultSchema;
const result = lookupSymbol(ref.object.name, schema);
const result = lookupSymbol(ref.object.name, schema, possibleNames);
if (result) {
addSymbol(md, result);
}

if (systemSchemas.includes(schema.toUpperCase())) {
addSearch(md, ref.object.name, result !== undefined);
}
} else if (tokAt && tokAt.value) {
const result = lookupSymbol(tokAt.value, defaultSchema);
} else if (tokAt && tokAt.type === `word` && tokAt.value) {
const result = lookupSymbol(tokAt.value, defaultSchema, possibleNames);
if (result) {
addSymbol(md, result);
}
Expand Down Expand Up @@ -118,8 +118,17 @@ function addSymbol(base: MarkdownString, symbol: LookupResult) {

if ('routine' in symbol) {
const routineName = Statement.prettyName(symbol.routine.name);
for (const signature of symbol.signatures) {
base.appendCodeblock(`${routineName}(\n${signature.parms.map(p => ` ${Statement.prettyName(p.PARAMETER_NAME)} => ${prepareParamType(p)}`).join(',\n')}\n)`, `sql`);
for (let i = 0; i < symbol.signatures.length; i++) {
const signature = symbol.signatures[i];
const returns = signature.returns.length > 0 ? `: ${signature.returns.length} column${signature.returns.length === 1 ? `` : `s`}` : '';

base.appendCodeblock(`${routineName}(\n${signature.parms.map((p, pI) => {
return ` ${Statement.prettyName(p.PARAMETER_NAME || `parm${pI}`)} => ${prepareParamType(p)}`
}).join(',\n')}\n)${returns}`, `sql`);

if (i < symbol.signatures.length - 1) {
base.appendMarkdown(`\n---\n`);
}
}
}
else if ('PARAMETER_NAME' in symbol) {
Expand All @@ -135,11 +144,11 @@ function addSymbol(base: MarkdownString, symbol: LookupResult) {
}
}

function lookupSymbol(name: string, schema: string) {
function lookupSymbol(name: string, schema: string, possibleNames: string[]) {
name = Statement.noQuotes(Statement.delimName(name, true));
schema = Statement.noQuotes(Statement.delimName(schema, true));

return DbCache.lookupSymbol(name, schema);
return DbCache.lookupSymbol(name, schema, possibleNames);
}

const getDefaultSchema = (): string => {
Expand Down
53 changes: 21 additions & 32 deletions src/language/providers/logic/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,19 @@ import Table from "../../../database/table";
interface RoutineDetail {
routine: CallableRoutine;
signatures: CallableSignature[];
resultingColumns: SQLParm[];
}

export type LookupResult = RoutineDetail | SQLParm | BasicSQLObject | TableColumn;

export class DbCache {
private static schemaObjects: Map<string, BasicSQLObject[]> = new Map();
private static routineResultColumns: Map<string, SQLParm[]> = new Map();
private static objectColumns: Map<string, TableColumn[]> = new Map();
private static routines: Map<string, CallableRoutine|false> = new Map();
private static routineSignatures: Map<string, CallableSignature[]> = new Map();

private static toReset: string[] = [];

static async resetCache() {
this.routineResultColumns.clear();
this.objectColumns.clear();
this.schemaObjects.clear();
this.toReset = [];
Expand All @@ -43,17 +40,25 @@ export class DbCache {
return false;
}

static lookupSymbol(name: string, schema: string): LookupResult {
static lookupSymbol(name: string, schema: string, objectFilter: string[]): LookupResult {
const routine = this.getCachedType(schema, name, `FUNCTION`) || this.getCachedType(schema, name, `PROCEDURE`);
if (routine) {
const signatures = this.getCachedSignatures(schema, name);
const resultingColumns = this.getCachedRoutineResultColumns(schema, name);
return { routine, signatures, resultingColumns } as RoutineDetail;
return { routine, signatures } as RoutineDetail;
}

objectFilter = objectFilter.map(o => o.toLowerCase());

const included = (name: string) => {
if (objectFilter) {
return objectFilter.includes(name.toLowerCase());
}
return true;
}

// Search objects
for (const currentSchema of this.schemaObjects.values()) {
const chosenObject = currentSchema.find(column => column.name === name && column.schema === schema);
const chosenObject = currentSchema.find(sqlObject => included(sqlObject.name) && sqlObject.name === name && sqlObject.schema === schema);
if (chosenObject) {
return chosenObject;
}
Expand All @@ -63,43 +68,27 @@ export class DbCache {

// First object columns
for (const currentObject of this.objectColumns.values()) {
const chosenColumn = currentObject.find(column => column.COLUMN_NAME.toLowerCase() === name.toLowerCase());
const chosenColumn = currentObject.find(column => included(column.TABLE_NAME) && column.COLUMN_NAME.toLowerCase() === name.toLowerCase());
if (chosenColumn) {
return chosenColumn;
}
}

// Then by routine result columns
for (const currentRoutine of this.routineResultColumns.values()) {
const chosenColumn = currentRoutine.find(column => column.PARAMETER_NAME.toLowerCase() === name.toLowerCase());
if (chosenColumn) {
return chosenColumn;
for (const currentRoutineSig of this.routineSignatures.values()) {
for (const signature of currentRoutineSig) {
const chosenColumn = signature.returns.find(column => column.PARAMETER_NAME.toLowerCase() === name.toLowerCase());
if (chosenColumn) {
return chosenColumn;
}
}
}
}

static async getRoutineResultColumns(schema: string, name: string, resolveName?: boolean) {
const key = getKey(`routine`, schema, name);

if (!this.routineResultColumns.has(key) || this.shouldReset(name)) {
const result = await Callable.getResultColumns(schema, name, resolveName);
if (result) {
this.routineResultColumns.set(key, result);
}
}

return this.routineResultColumns.get(key) || [];
}

static getCachedRoutineResultColumns(schema: string, name: string) {
const key = getKey(`routine`, schema, name);
return this.routineResultColumns.get(key) || [];
}

static async getColumns(schema: string, name: string) {
static async getObjectColumns(schema: string, name: string) {
const key = getKey(`columns`, schema, name);

if (!this.routineResultColumns.has(key) || this.shouldReset(name)) {
if (!this.objectColumns.has(key) || this.shouldReset(name)) {
const result = await Table.getItems(schema, name);
if (result) {
this.objectColumns.set(key, result);
Expand Down

0 comments on commit 4d9dde6

Please sign in to comment.