From b654ec0fed6d6c4569c4b66b6bfc3345001a0908 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Wed, 18 Sep 2024 15:06:55 -0400 Subject: [PATCH 01/23] Initial work for peek for objects Signed-off-by: worksofliam --- src/database/schemas.ts | 19 ++++++- src/language/providers/index.ts | 4 +- src/language/providers/peekProvider.ts | 73 ++++++++++++++++++++++++++ src/views/schemaBrowser/index.ts | 20 +------ 4 files changed, 96 insertions(+), 20 deletions(-) create mode 100644 src/language/providers/peekProvider.ts diff --git a/src/database/schemas.ts b/src/database/schemas.ts index 6da419d4..a5a45438 100644 --- a/src/database/schemas.ts +++ b/src/database/schemas.ts @@ -1,4 +1,5 @@ +import { spec } from "node:test/reporters"; import { getInstance } from "../base"; import { JobManager } from "../config"; @@ -15,6 +16,22 @@ const typeMap = { export const AllSQLTypes: SQLType[] = ["tables", "views", "aliases", "constraints", "functions", "variables", "indexes", "procedures", "sequences", "packages", "triggers", "types", "logicals"]; +export const InternalTypes: {[t: string]: string} = { + "tables": `table`, + "views": `view`, + "aliases": `alias`, + "constraints": `constraint`, + "functions": `function`, + "variables": `variable`, + "indexes": `index`, + "procedures": `procedure`, + "sequences": `sequence`, + "packages": `package`, + "triggers": `trigger`, + "types": `type`, + "logicals": `logical` +} + export const SQL_ESCAPE_CHAR = `\\`; type BasicColumnType = string|number; @@ -211,7 +228,7 @@ export default class Schemas { schema: object.BASE_SCHEMA || undefined, name: object.BASE_OBJ || undefined } - })); + } as BasicSQLObject)); } /** diff --git a/src/language/providers/index.ts b/src/language/providers/index.ts index 07a54872..cf463170 100644 --- a/src/language/providers/index.ts +++ b/src/language/providers/index.ts @@ -2,6 +2,7 @@ import { completionProvider } from "./completionProvider"; import { formatProvider } from "./formatProvider"; import { hoverProvider, openProvider } from "./hoverProvider"; import { signatureProvider } from "./parameterProvider"; +import { peekProvider } from "./peekProvider"; export function languageInit() { let functionality = []; @@ -11,7 +12,8 @@ export function languageInit() { formatProvider, signatureProvider, hoverProvider, - openProvider + openProvider, + peekProvider ); return functionality; diff --git a/src/language/providers/peekProvider.ts b/src/language/providers/peekProvider.ts new file mode 100644 index 00000000..7882fa00 --- /dev/null +++ b/src/language/providers/peekProvider.ts @@ -0,0 +1,73 @@ +import { env, Hover, languages, MarkdownString, workspace } from "vscode"; +import { getSqlDocument } from "./logic/parse"; +import { DbCache, LookupResult, RoutineDetail } from "./logic/cache"; +import { JobManager } from "../../config"; +import Statement from "../../database/statement"; +import { getParmAttributes, prepareParamType } from "./logic/completion"; +import { StatementType } from "../sql/types"; +import { remoteAssistIsEnabled } from "./logic/available"; +import { getPositionData } from "./logic/callable"; +import { CallableSignature } from "../../database/callable"; +import Schemas, { AllSQLTypes, InternalTypes, SQLType } from "../../database/schemas"; + +const standardObjects: SQLType[] = AllSQLTypes.filter(type => ![`functions`, `procedures`].includes(type)); + +export const peekProvider = languages.registerDefinitionProvider({ language: `sql` }, { + async provideDefinition(document, position, token) { + if (!remoteAssistIsEnabled()) return; + console.log(`peekProvider`); + + const defaultSchema = getDefaultSchema(); + const sqlDoc = getSqlDocument(document); + const offset = document.offsetAt(position); + + const tokAt = sqlDoc.getTokenByOffset(offset); + const statementAt = sqlDoc.getStatementByOffset(offset); + + if (statementAt) { + const refs = statementAt.getObjectReferences(); + const possibleNames = refs.map(ref => ref.object.name).filter(name => name); + + const ref = refs.find(ref => ref.tokens[0].range.start && offset <= ref.tokens[ref.tokens.length - 1].range.end); + + if (ref) { + const name = Statement.noQuotes(Statement.delimName(ref.object.name, true)); + const schema = Statement.noQuotes(Statement.delimName(ref.object.schema || defaultSchema, true)); + + let types: SQLType[] = standardObjects; + + if (ref.isUDTF) { + types = [`functions`]; + } else if (statementAt.type === StatementType.Call) { + types = [`procedures`]; + } + + const possibleObjects = await Schemas.getObjects(schema, types, {filter: name}); + + if (possibleObjects.length) { + const lines: string[] = []; + for (const obj of possibleObjects) { + const type = InternalTypes[obj.type]; + if (type) { + const contents = await Schemas.generateSQL(obj.schema, obj.name, type.toUpperCase()); + lines.push(contents, ``, ``); + } + } + + const document = await workspace.openTextDocument({ content: lines.join(`\n`), language: `sql` }); + + return { + uri: document.uri, + range: document.lineAt(0).range, + }; + } + + } + } + } +}); + +const getDefaultSchema = (): string => { + const currentJob = JobManager.getSelection(); + return currentJob && currentJob.job.options.libraries[0] ? currentJob.job.options.libraries[0] : `QGPL`; +} \ No newline at end of file diff --git a/src/views/schemaBrowser/index.ts b/src/views/schemaBrowser/index.ts index 4503ced3..fb8d45e4 100644 --- a/src/views/schemaBrowser/index.ts +++ b/src/views/schemaBrowser/index.ts @@ -1,7 +1,7 @@ import { ThemeIcon, TreeItem } from "vscode" import * as vscode from "vscode" -import Schemas, { AllSQLTypes, SQL_ESCAPE_CHAR, SQLType } from "../../database/schemas"; +import Schemas, { AllSQLTypes, InternalTypes, SQL_ESCAPE_CHAR, SQLType } from "../../database/schemas"; import Table from "../../database/table"; import { getInstance, loadBase } from "../../base"; @@ -12,22 +12,6 @@ import Statement from "../../database/statement"; import { copyUI } from "./copyUI"; import { getAdvisedIndexesStatement, getIndexesStatement, getMTIStatement } from "./statements"; -const viewItem = { - "tables": `table`, - "views": `view`, - "aliases": `alias`, - "constraints": `constraint`, - "functions": `function`, - "variables": `variable`, - "indexes": `index`, - "procedures": `procedure`, - "sequences": `sequence`, - "packages": `package`, - "triggers": `trigger`, - "types": `type`, - "logicals": `logical` -} - const itemIcons = { "table": `split-horizontal`, "procedure": `run`, @@ -552,7 +536,7 @@ class SQLObject extends vscode.TreeItem { } constructor(item: BasicSQLObject) { - const type = viewItem[item.type]; + const type = InternalTypes[item.type]; super(Statement.prettyName(item.name), Types[type] ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None); this.contextValue = type; From 9f5ea2dc46290bba4304594545afd67af176a3b0 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 3 Dec 2024 15:03:01 -0500 Subject: [PATCH 02/23] Slim down definition and use formatter Signed-off-by: worksofliam --- src/database/schemas.ts | 25 ++++++++++++++++++++----- src/language/providers/peekProvider.ts | 6 +++--- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/database/schemas.ts b/src/database/schemas.ts index a5a45438..4e9b8ef5 100644 --- a/src/database/schemas.ts +++ b/src/database/schemas.ts @@ -1,5 +1,4 @@ -import { spec } from "node:test/reporters"; import { getInstance } from "../base"; import { JobManager } from "../config"; @@ -235,10 +234,26 @@ export default class Schemas { * @param schema Not user input * @param object Not user input */ - static async generateSQL(schema: string, object: string, internalType: string): Promise { - const lines = await JobManager.runSQL<{ SRCDTA: string }>([ - `CALL QSYS2.GENERATE_SQL(?, ?, ?, CREATE_OR_REPLACE_OPTION => '1', PRIVILEGES_OPTION => '0')` - ].join(` `), { parameters: [object, schema, internalType] }); + static async generateSQL(schema: string, object: string, internalType: string, basic?: boolean): Promise { + let statement = `CALL QSYS2.GENERATE_SQL(?, ?, ?, CREATE_OR_REPLACE_OPTION => '1', PRIVILEGES_OPTION => '0')`; + + if (basic) { + let options: string[] = [ + `CREATE_OR_REPLACE_OPTION => '0'`, + `PRIVILEGES_OPTION => '0'`, + `COMMENT_OPTION => '0'`, + `LABEL_OPTION => '0'`, + `HEADER_OPTION => '0'`, + `TRIGGER_OPTION => '0'`, + `CONSTRAINT_OPTION => '0'`, + // `PRIVILEGES_OPTION => '0'`, + // `ACTIVATE_ACCESS_CONTROL_OPTION => '0'`, + `MASK_AND_PERMISSION_OPTION => '0'`, + ]; + statement = `CALL QSYS2.GENERATE_SQL(?, ?, ?, ${options.join(`, `)})`; + } + + const lines = await JobManager.runSQL<{ SRCDTA: string }>(statement, { parameters: [object, schema, internalType] }); const generatedStatement = lines.map(line => line.SRCDTA).join(`\n`); diff --git a/src/language/providers/peekProvider.ts b/src/language/providers/peekProvider.ts index 7882fa00..9b171a82 100644 --- a/src/language/providers/peekProvider.ts +++ b/src/language/providers/peekProvider.ts @@ -45,12 +45,12 @@ export const peekProvider = languages.registerDefinitionProvider({ language: `sq const possibleObjects = await Schemas.getObjects(schema, types, {filter: name}); if (possibleObjects.length) { - const lines: string[] = []; + const lines: string[] = [`-- Condensed version of the object definition`]; for (const obj of possibleObjects) { const type = InternalTypes[obj.type]; if (type) { - const contents = await Schemas.generateSQL(obj.schema, obj.name, type.toUpperCase()); - lines.push(contents, ``, ``); + const contents = await Schemas.generateSQL(obj.schema, obj.name, type.toUpperCase(), true); + lines.push(Statement.format(contents), ``, ``); } } From d55a23baccb315f14ab3a27530b66441a36d802a Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 3 Dec 2024 15:05:06 -0500 Subject: [PATCH 03/23] Fix invalid comment references Signed-off-by: worksofliam --- .github/workflows/webpack_ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/webpack_ci.yaml b/.github/workflows/webpack_ci.yaml index 1156ea8a..61bff11a 100644 --- a/.github/workflows/webpack_ci.yaml +++ b/.github/workflows/webpack_ci.yaml @@ -57,7 +57,7 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: '👋 A new build is available for this PR based on ${{ github.event.pull_request.head.sha }}.\n * [Download here.](https://github.com/codefori/vscode-ibmi/actions/runs/${{ github.run_id }})\n* [Read more about how to test](https://github.com/codefori/vscode-ibmi/blob/master/.github/pr_testing_template.md)' + body: '👋 A new build is available for this PR based on ${{ github.event.pull_request.head.sha }}.\n * [Download here.](https://github.com/codefori/vscode-db2i/actions/runs/${{ github.run_id }})\n* [Read more about how to test](https://github.com/codefori/vscode-db2i/blob/master/.github/pr_testing_template.md)' }) - name: Update comment @@ -69,5 +69,5 @@ jobs: body: | 👋 A new build is available for this PR based on ${{ github.event.pull_request.head.sha }}. - * [Download here.](https://github.com/codefori/vscode-ibmi/actions/runs/${{ github.run_id }}) + * [Download here.](https://github.com/codefori/vscode-db2i/actions/runs/${{ github.run_id }}) * [Read more about how to test](https://github.com/codefori/vscode-ibmi/blob/master/.github/pr_testing_template.md) \ No newline at end of file From d6e93bf8959507a11fff04ea12858758a4ece3dd Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 4 Feb 2025 22:22:00 -0500 Subject: [PATCH 04/23] Remove formatter from peek provider Signed-off-by: worksofliam --- src/language/providers/peekProvider.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/language/providers/peekProvider.ts b/src/language/providers/peekProvider.ts index 9b171a82..714ad081 100644 --- a/src/language/providers/peekProvider.ts +++ b/src/language/providers/peekProvider.ts @@ -1,21 +1,16 @@ -import { env, Hover, languages, MarkdownString, workspace } from "vscode"; +import { languages, workspace } from "vscode"; import { getSqlDocument } from "./logic/parse"; -import { DbCache, LookupResult, RoutineDetail } from "./logic/cache"; import { JobManager } from "../../config"; import Statement from "../../database/statement"; -import { getParmAttributes, prepareParamType } from "./logic/completion"; import { StatementType } from "../sql/types"; import { remoteAssistIsEnabled } from "./logic/available"; -import { getPositionData } from "./logic/callable"; -import { CallableSignature } from "../../database/callable"; import Schemas, { AllSQLTypes, InternalTypes, SQLType } from "../../database/schemas"; -const standardObjects: SQLType[] = AllSQLTypes.filter(type => ![`functions`, `procedures`].includes(type)); export const peekProvider = languages.registerDefinitionProvider({ language: `sql` }, { async provideDefinition(document, position, token) { + const standardObjects: SQLType[] = AllSQLTypes.filter(type => ![`functions`, `procedures`].includes(type)); if (!remoteAssistIsEnabled()) return; - console.log(`peekProvider`); const defaultSchema = getDefaultSchema(); const sqlDoc = getSqlDocument(document); @@ -26,7 +21,6 @@ export const peekProvider = languages.registerDefinitionProvider({ language: `sq if (statementAt) { const refs = statementAt.getObjectReferences(); - const possibleNames = refs.map(ref => ref.object.name).filter(name => name); const ref = refs.find(ref => ref.tokens[0].range.start && offset <= ref.tokens[ref.tokens.length - 1].range.end); @@ -50,7 +44,7 @@ export const peekProvider = languages.registerDefinitionProvider({ language: `sq const type = InternalTypes[obj.type]; if (type) { const contents = await Schemas.generateSQL(obj.schema, obj.name, type.toUpperCase(), true); - lines.push(Statement.format(contents), ``, ``); + lines.push(contents, ``, ``); } } From f99b033b6b3255f1f186eee5af62db47aae826cb Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 4 Feb 2025 22:23:47 -0500 Subject: [PATCH 05/23] Upload artifact version bump Signed-off-by: worksofliam --- .github/workflows/webpack_ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/webpack_ci.yaml b/.github/workflows/webpack_ci.yaml index 1328643c..ca699a87 100644 --- a/.github/workflows/webpack_ci.yaml +++ b/.github/workflows/webpack_ci.yaml @@ -35,7 +35,7 @@ jobs: cat $GITHUB_ENV - name: Upload dist artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: test_vsix path: ${{env.vsix_name}} From 0db258cd37f4b32ac504269db89cf5975dd013b6 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 4 Feb 2025 22:35:38 -0500 Subject: [PATCH 06/23] Move peek result to second line to skip comment Signed-off-by: worksofliam --- src/language/providers/peekProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/language/providers/peekProvider.ts b/src/language/providers/peekProvider.ts index 714ad081..a91f697d 100644 --- a/src/language/providers/peekProvider.ts +++ b/src/language/providers/peekProvider.ts @@ -52,7 +52,7 @@ export const peekProvider = languages.registerDefinitionProvider({ language: `sq return { uri: document.uri, - range: document.lineAt(0).range, + range: document.lineAt(1).range, }; } From eedcba417f7b45a868be155cb4bc99b10ab3432a Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sat, 15 Mar 2025 17:16:43 -0400 Subject: [PATCH 07/23] Go to defintion now resolves based on library list Signed-off-by: worksofliam --- src/database/schemas.ts | 134 +++++++++++++------------ src/language/providers/peekProvider.ts | 17 +--- 2 files changed, 71 insertions(+), 80 deletions(-) diff --git a/src/database/schemas.ts b/src/database/schemas.ts index 99d1b2fe..3fc0a680 100644 --- a/src/database/schemas.ts +++ b/src/database/schemas.ts @@ -16,7 +16,7 @@ const typeMap = { export const AllSQLTypes: SQLType[] = ["tables", "views", "aliases", "constraints", "functions", "variables", "indexes", "procedures", "sequences", "packages", "triggers", "types", "logicals"]; -export const InternalTypes: {[t: string]: string} = { +export const InternalTypes: { [t: string]: string } = { "tables": `table`, "views": `view`, "aliases": `alias`, @@ -34,12 +34,12 @@ export const InternalTypes: {[t: string]: string} = { export const SQL_ESCAPE_CHAR = `\\`; -type BasicColumnType = string|number; -interface PartStatementInfo {clause: string, parameters: BasicColumnType[]}; +type BasicColumnType = string | number; +interface PartStatementInfo { clause: string, parameters: BasicColumnType[] }; function getFilterClause(againstColumn: string, filter: string, noAnd?: boolean): PartStatementInfo { if (!filter) { - return {clause: ``, parameters: []}; + return { clause: ``, parameters: [] }; } let clause = `${noAnd ? '' : 'AND'} UPPER(${againstColumn})`; @@ -63,7 +63,7 @@ function getFilterClause(againstColumn: string, filter: string, noAnd?: boolean) }; } -export interface ObjectReference {name: string, schema?: string}; +export interface ObjectReference { name: string, schema?: string }; const BASE_RESOLVE_SELECT = [ `select `, @@ -91,14 +91,7 @@ export default class Schemas { let resolvedObjects: ResolvedSqlObject[] = []; // We need to remove any duplicates from the list of objects to resolve - const uniqueObjects = new Set(); - sqlObjects = sqlObjects.filter((obj) => { - if (!uniqueObjects.has(obj.name)) { - uniqueObjects.add(obj.name); - return true; - } - return false; - }); + sqlObjects = sqlObjects.filter(o => sqlObjects.indexOf(o) === sqlObjects.findIndex(obj => obj.name === o.name && obj.schema === o.schema)); // First, we use OBJECT_STATISTICS to resolve the object based on the library list. // But, if the object is qualified with a schema, we need to use that schema to get the correct object. @@ -130,52 +123,64 @@ export default class Schemas { .map((obj) => obj.name); const qualified = sqlObjects.filter((obj) => obj.schema); - let baseStatement = [ - `select s.routine_name as name, l.schema_name as schema, s.ROUTINE_TYPE as sqlType`, - `from qsys2.library_list_info as l`, - `right join qsys2.sysroutines as s on l.schema_name = s.routine_schema`, - `where `, - ` l.schema_name is not null and`, - ` s.routine_name in (${unqualified.map(() => `?`).join(`, `)})`, - ].join(` `); - parameters.push(...unqualified); - - if (qualified.length > 0) { - const qualifiedClause = qualified - .map((obj) => `(s.routine_name = ? AND s.routine_schema = ?)`) - .join(` OR `); - baseStatement += ` and (${qualifiedClause})`; - parameters.push(...qualified.flatMap((obj) => [obj.name, obj.schema])); - } + if (qualified.length && unqualified.length) { + let baseStatement = [ + `select s.routine_name as name, l.schema_name as schema, s.ROUTINE_TYPE as sqlType`, + `from qsys2.library_list_info as l`, + `right join qsys2.sysroutines as s on l.schema_name = s.routine_schema`, + `where `, + ` l.schema_name is not null`, + ].join(` `); + + if (unqualified.length > 0) { + baseStatement += ` and s.routine_name in (${unqualified.map(() => `?`).join(`, `)})`; + parameters.push(...unqualified); + } - statements.push(baseStatement); + if (qualified.length > 0) { + const qualifiedClause = qualified + .map((obj) => `(s.routine_name = ? AND s.routine_schema = ?)`) + .join(` OR `); + baseStatement += ` and (${qualifiedClause})`; + parameters.push(...qualified.flatMap((obj) => [obj.name, obj.schema])); + } + + statements.push(baseStatement); + } if (statements.length === 0) { - return []; + return resolvedObjects; } const query = `${statements.join(" UNION ALL ")}`; - const objects: any[] = await JobManager.runSQL(query, { parameters }); - - resolvedObjects.push( - ...objects - .map((object) => ({ - name: object.NAME, - schema: object.SCHEMA, - sqlType: object.SQLTYPE, - })) - .filter((o) => o.sqlType) - ); - - // add reslved objects to to ReferenceCache - resolvedObjects.forEach((obj) => { - const key = this.buildReferenceCacheKey(obj); - if (!ReferenceCache.has(key)) { - ReferenceCache.set(key, obj); - } - }); - return resolvedObjects; + try { + const objects: any[] = await JobManager.runSQL(query, { parameters }); + + resolvedObjects.push( + ...objects + .map((object) => ({ + name: object.NAME, + schema: object.SCHEMA, + sqlType: object.SQLTYPE, + })) + .filter((o) => o.sqlType) + ); + + // add reslved objects to to ReferenceCache + resolvedObjects.forEach((obj) => { + const key = this.buildReferenceCacheKey(obj); + if (!ReferenceCache.has(key)) { + ReferenceCache.set(key, obj); + } + }); + + return resolvedObjects; + } catch (e) { + console.warn(`Error resolving objects: ${JSON.stringify(sqlObjects)}`); + console.warn(e); + return []; + } } static async getRelatedObjects( @@ -376,8 +381,7 @@ export default class Schemas { const objects: any[] = await JobManager.runSQL( [ query, - `${details.limit ? `limit ${details.limit}` : ``} ${ - details.offset ? `offset ${details.offset}` : `` + `${details.limit ? `limit ${details.limit}` : ``} ${details.offset ? `offset ${details.offset}` : `` }`, ].join(` `), { @@ -442,12 +446,12 @@ export default class Schemas { `CALL QSYS2.GENERATE_SQL( ${options.join(`, `)} )`, ].join(` `), { parameters: [object, schema, internalType] }); - // TODO: eventually .content -> .getContent(), it's not available yet - const contents = ( - await connection.content.downloadStreamfileRaw(tempFilePath) - ).toString(); - return contents; - } + // TODO: eventually .content -> .getContent(), it's not available yet + const contents = ( + await connection.content.downloadStreamfileRaw(tempFilePath) + ).toString(); + return contents; + } ); return result; @@ -458,9 +462,8 @@ export default class Schemas { name: string, type: string ): Promise { - const query = `DROP ${ - (this.isRoutineType(type) ? "SPECIFIC " : "") + type - } IF EXISTS ${schema}.${name}`; + const query = `DROP ${(this.isRoutineType(type) ? "SPECIFIC " : "") + type + } IF EXISTS ${schema}.${name}`; await getInstance().getContent().runSQL(query); } @@ -470,9 +473,8 @@ export default class Schemas { newName: string, type: string ): Promise { - const query = `RENAME ${ - type === "view" ? "table" : type - } ${schema}.${oldName} TO ${newName}`; + const query = `RENAME ${type === "view" ? "table" : type + } ${schema}.${oldName} TO ${newName}`; await getInstance().getContent().runSQL(query); } diff --git a/src/language/providers/peekProvider.ts b/src/language/providers/peekProvider.ts index a91f697d..7fd5c9fa 100644 --- a/src/language/providers/peekProvider.ts +++ b/src/language/providers/peekProvider.ts @@ -28,24 +28,13 @@ export const peekProvider = languages.registerDefinitionProvider({ language: `sq const name = Statement.noQuotes(Statement.delimName(ref.object.name, true)); const schema = Statement.noQuotes(Statement.delimName(ref.object.schema || defaultSchema, true)); - let types: SQLType[] = standardObjects; - - if (ref.isUDTF) { - types = [`functions`]; - } else if (statementAt.type === StatementType.Call) { - types = [`procedures`]; - } - - const possibleObjects = await Schemas.getObjects(schema, types, {filter: name}); + const possibleObjects = await Schemas.resolveObjects([{name, schema}]); if (possibleObjects.length) { const lines: string[] = [`-- Condensed version of the object definition`]; for (const obj of possibleObjects) { - const type = InternalTypes[obj.type]; - if (type) { - const contents = await Schemas.generateSQL(obj.schema, obj.name, type.toUpperCase(), true); - lines.push(contents, ``, ``); - } + const contents = await Schemas.generateSQL(obj.schema, obj.name, obj.sqlType, true); + lines.push(contents); } const document = await workspace.openTextDocument({ content: lines.join(`\n`), language: `sql` }); From c37c1192975597f43ad27430d9ca5eb79c17235b Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sat, 15 Mar 2025 17:26:19 -0400 Subject: [PATCH 08/23] Fix library list bug with caching Signed-off-by: worksofliam --- src/database/schemas.ts | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/database/schemas.ts b/src/database/schemas.ts index 3fc0a680..3fd5184d 100644 --- a/src/database/schemas.ts +++ b/src/database/schemas.ts @@ -75,11 +75,30 @@ const BASE_RESOLVE_SELECT = [ `end as sqlType`, ].join(` `); -let ReferenceCache: Map = new Map(); + export default class Schemas { + private static ReferenceCache: Map = new Map(); + private static buildReferenceCacheKey(obj: ObjectReference): string { return `${obj.schema}.${obj.name}`; } + + + static storeCachedReference(obj: ObjectReference, resolvedTo: ResolvedSqlObject): void { + if (obj.name && obj.schema) { + const key = Schemas.buildReferenceCacheKey(obj); + this.ReferenceCache.set(key, resolvedTo); + } + } + + static getCachedReference(obj: ObjectReference): ResolvedSqlObject | undefined { + if (obj.name && obj.schema) { + const key = Schemas.buildReferenceCacheKey(obj); + return this.ReferenceCache.get(key); + } + return undefined; + } + /** * Resolves to the following SQL types: SCHEMA, TABLE, VIEW, ALIAS, INDEX, FUNCTION and PROCEDURE */ @@ -96,12 +115,12 @@ export default class Schemas { // First, we use OBJECT_STATISTICS to resolve the object based on the library list. // But, if the object is qualified with a schema, we need to use that schema to get the correct object. for (const obj of sqlObjects) { - const key = this.buildReferenceCacheKey(obj); - // check if we have already resolved this object - if (ReferenceCache.has(key)) { - resolvedObjects.push(ReferenceCache.get(key!)); + const cached = this.getCachedReference(obj); + if (cached) { + resolvedObjects.push(cached); continue; } + if (obj.schema) { statements.push( `${BASE_RESOLVE_SELECT} from table(qsys2.object_statistics(?, '*ALL', object_name => ?))` @@ -169,10 +188,7 @@ export default class Schemas { // add reslved objects to to ReferenceCache resolvedObjects.forEach((obj) => { - const key = this.buildReferenceCacheKey(obj); - if (!ReferenceCache.has(key)) { - ReferenceCache.set(key, obj); - } + this.storeCachedReference(obj, obj); }); return resolvedObjects; From d93be8d62d97f3a0d21b01fce1160a15fc57dc5c Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sat, 15 Mar 2025 17:26:28 -0400 Subject: [PATCH 09/23] Simplify peek logic Signed-off-by: worksofliam --- src/language/providers/peekProvider.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/language/providers/peekProvider.ts b/src/language/providers/peekProvider.ts index 7fd5c9fa..aa944ef3 100644 --- a/src/language/providers/peekProvider.ts +++ b/src/language/providers/peekProvider.ts @@ -30,14 +30,11 @@ export const peekProvider = languages.registerDefinitionProvider({ language: `sq const possibleObjects = await Schemas.resolveObjects([{name, schema}]); - if (possibleObjects.length) { - const lines: string[] = [`-- Condensed version of the object definition`]; - for (const obj of possibleObjects) { - const contents = await Schemas.generateSQL(obj.schema, obj.name, obj.sqlType, true); - lines.push(contents); - } - - const document = await workspace.openTextDocument({ content: lines.join(`\n`), language: `sql` }); + if (possibleObjects.length === 1) { + const obj = possibleObjects[0]; + const content = await Schemas.generateSQL(obj.schema, obj.name, obj.sqlType, true); + + const document = await workspace.openTextDocument({ content, language: `sql` }); return { uri: document.uri, From d168244729ffb0044f1e70890a3326921b87f85d Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sun, 16 Mar 2025 13:14:51 -0400 Subject: [PATCH 10/23] Don't hardcode the current library Signed-off-by: worksofliam --- src/language/providers/peekProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/language/providers/peekProvider.ts b/src/language/providers/peekProvider.ts index aa944ef3..122b0dfa 100644 --- a/src/language/providers/peekProvider.ts +++ b/src/language/providers/peekProvider.ts @@ -26,7 +26,7 @@ export const peekProvider = languages.registerDefinitionProvider({ language: `sq if (ref) { const name = Statement.noQuotes(Statement.delimName(ref.object.name, true)); - const schema = Statement.noQuotes(Statement.delimName(ref.object.schema || defaultSchema, true)); + const schema = ref.object.schema ? Statement.noQuotes(Statement.delimName(ref.object.schema, true)) : undefined; const possibleObjects = await Schemas.resolveObjects([{name, schema}]); From 82cf8ab1d226a5758aa14077663600d9a748b8a3 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sun, 16 Mar 2025 21:47:19 -0400 Subject: [PATCH 11/23] Ability to configure naming for default job Signed-off-by: worksofliam --- package.json | 32 ++++++++++++----- src/connection/manager.ts | 11 ++++-- src/views/jobManager/contributes.json | 36 +++++++++++++++++-- src/views/jobManager/jobManagerView.ts | 4 +-- .../jobManager/selfCodes/contributes.json | 14 +------- 5 files changed, 68 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index 3836c67a..48d3af6f 100644 --- a/package.json +++ b/package.json @@ -186,12 +186,22 @@ } }, { - "id": "vscode-db2i.self", - "title": "SQL Error Logging Facility (SELF)", + "id": "vscode-db2i.jobManager", + "title": "SQL Job Manager", "properties": { - "vscode-db2i.jobSelfDefault": { + "vscode-db2i.jobManager.jobNamingDefault": { "type": "string", "order": 0, + "description": "Default naming mode for new jobs", + "default": "system", + "enum": [ + "system", + "sql" + ] + }, + "vscode-db2i.jobManager.jobSelfDefault": { + "type": "string", + "order": 1, "description": "Default SELF setting for new jobs", "default": "*NONE", "enum": [ @@ -200,7 +210,13 @@ "*ERROR", "*WARNING" ] - }, + } + } + }, + { + "id": "vscode-db2i.self", + "title": "SQL Error Logging Facility (SELF)", + "properties": { "vscode-db2i.jobSelfViewAutoRefresh": { "type": "boolean", "title": "Auto-refresh SELF Codes view", @@ -558,8 +574,8 @@ "icon": "$(edit)" }, { - "command": "vscode-db2i.jobManager.defaultSelfSettings", - "title": "SQL Error Logging Facility (SELF) - Default Setting", + "command": "vscode-db2i.jobManager.defaultSettings", + "title": "Default Job Settings", "category": "Db2 for i", "icon": "$(gear)" }, @@ -866,7 +882,7 @@ "when": "view == jobManager" }, { - "command": "vscode-db2i.jobManager.defaultSelfSettings", + "command": "vscode-db2i.jobManager.defaultSettings", "group": "navigation", "when": "view == jobManager" }, @@ -1268,7 +1284,7 @@ }, { "view": "vscode-db2i.self.nodes", - "contents": "🛠️ SELF Codes will appear here. You can set the SELF log level on specific jobs, or you can set the default for new jobs in the User Settings.\n\n[Set Default for New Jobs](command:vscode-db2i.jobManager.defaultSelfSettings)\n\n[Learn about SELF](command:vscode-db2i.self.help)" + "contents": "🛠️ SELF Codes will appear here. You can set the SELF log level on specific jobs, or you can set the default for new jobs in the User Settings.\n\n[Set Default for New Jobs](command:vscode-db2i.jobManager.defaultSettings)\n\n[Learn about SELF](command:vscode-db2i.self.help)" } ], "notebooks": [ diff --git a/src/connection/manager.ts b/src/connection/manager.ts index 9caf802e..65fb082b 100644 --- a/src/connection/manager.ts +++ b/src/connection/manager.ts @@ -27,11 +27,12 @@ export class SQLJobManager { if (ServerComponent.isInstalled()) { const instance = getInstance(); - const config = instance.getConfig(); + const connection = instance.getConnection()!; + const config = connection.getConfig(); const newJob = predefinedJob || (new OldSQLJob({ libraries: [config.currentLibrary, ...config.libraryList.filter((item) => item != config.currentLibrary)], - naming: `system`, + naming: SQLJobManager.getNamingDefault(), "full open": false, "transaction isolation": "none", "query optimize goal": "1", @@ -159,6 +160,10 @@ export class SQLJobManager { } static getSelfDefault(): SelfValue { - return Configuration.get(`jobSelfDefault`) || `*NONE`; + return Configuration.get(`jobManager.jobSelfDefault`) || `*NONE`; + } + + static getNamingDefault(): "sql"|"system" { + return (Configuration.get(`jobManager.jobNamingDefault`) || `system`) as "system" | "sql"; } } diff --git a/src/views/jobManager/contributes.json b/src/views/jobManager/contributes.json index d394df50..2545a0b6 100644 --- a/src/views/jobManager/contributes.json +++ b/src/views/jobManager/contributes.json @@ -1,5 +1,35 @@ { "contributes": { + "configuration": [ + { + "id": "vscode-db2i.jobManager", + "title": "SQL Job Manager", + "properties": { + "vscode-db2i.jobManager.jobNamingDefault": { + "type": "string", + "order": 0, + "description": "Default naming mode for new jobs", + "default": "system", + "enum": [ + "system", + "sql" + ] + }, + "vscode-db2i.jobManager.jobSelfDefault": { + "type": "string", + "order": 1, + "description": "Default SELF setting for new jobs", + "default": "*NONE", + "enum": [ + "*NONE", + "*ALL", + "*ERROR", + "*WARNING" + ] + } + } + } + ], "views": { "db2-explorer": [ { @@ -54,8 +84,8 @@ "icon": "$(edit)" }, { - "command": "vscode-db2i.jobManager.defaultSelfSettings", - "title": "SQL Error Logging Facility (SELF) - Default Setting", + "command": "vscode-db2i.jobManager.defaultSettings", + "title": "Default Job Settings", "category": "Db2 for i", "icon": "$(gear)" }, @@ -163,7 +193,7 @@ "when": "view == jobManager" }, { - "command": "vscode-db2i.jobManager.defaultSelfSettings", + "command": "vscode-db2i.jobManager.defaultSettings", "group": "navigation", "when": "view == jobManager" }, diff --git a/src/views/jobManager/jobManagerView.ts b/src/views/jobManager/jobManagerView.ts index 19c36dbc..4bbb771a 100644 --- a/src/views/jobManager/jobManagerView.ts +++ b/src/views/jobManager/jobManagerView.ts @@ -30,8 +30,8 @@ export class JobManagerView implements TreeDataProvider { ...ConfigManager.initialiseSaveCommands(), - vscode.commands.registerCommand(`vscode-db2i.jobManager.defaultSelfSettings`, () => { - vscode.commands.executeCommand('workbench.action.openSettings', 'vscode-db2i.jobSelf'); + vscode.commands.registerCommand(`vscode-db2i.jobManager.defaultSettings`, () => { + vscode.commands.executeCommand('workbench.action.openSettings', 'vscode-db2i.jobManager'); }), vscode.commands.registerCommand(`vscode-db2i.jobManager.newJob`, async (options?: JDBCOptions, name?: string) => { diff --git a/src/views/jobManager/selfCodes/contributes.json b/src/views/jobManager/selfCodes/contributes.json index 331ca331..33e71996 100644 --- a/src/views/jobManager/selfCodes/contributes.json +++ b/src/views/jobManager/selfCodes/contributes.json @@ -5,18 +5,6 @@ "id": "vscode-db2i.self", "title": "SQL Error Logging Facility (SELF)", "properties": { - "vscode-db2i.jobSelfDefault": { - "type": "string", - "order": 0, - "description": "Default SELF setting for new jobs", - "default": "*NONE", - "enum": [ - "*NONE", - "*ALL", - "*ERROR", - "*WARNING" - ] - }, "vscode-db2i.jobSelfViewAutoRefresh": { "type": "boolean", "title": "Auto-refresh SELF Codes view", @@ -40,7 +28,7 @@ "viewsWelcome": [ { "view": "vscode-db2i.self.nodes", - "contents": "🛠️ SELF Codes will appear here. You can set the SELF log level on specific jobs, or you can set the default for new jobs in the User Settings.\n\n[Set Default for New Jobs](command:vscode-db2i.jobManager.defaultSelfSettings)\n\n[Learn about SELF](command:vscode-db2i.self.help)" + "contents": "🛠️ SELF Codes will appear here. You can set the SELF log level on specific jobs, or you can set the default for new jobs in the User Settings.\n\n[Set Default for New Jobs](command:vscode-db2i.jobManager.defaultSettings)\n\n[Learn about SELF](command:vscode-db2i.self.help)" } ], "commands": [ From bb38ea07abed119211ea1334512a1aa00c2e01af Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sun, 16 Mar 2025 21:56:06 -0400 Subject: [PATCH 12/23] Peek to support SQL and system naming Signed-off-by: worksofliam --- src/connection/manager.ts | 6 ++++-- src/connection/sqlJob.ts | 9 +++++++++ src/language/providers/peekProvider.ts | 27 +++++++++++++++----------- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/connection/manager.ts b/src/connection/manager.ts index 65fb082b..329a9d17 100644 --- a/src/connection/manager.ts +++ b/src/connection/manager.ts @@ -13,6 +13,8 @@ export interface JobInfo { job: OldSQLJob; } +export type NamingFormats = "sql"|"system"; + const NO_SELECTED_JOB = -1; export class SQLJobManager { @@ -163,7 +165,7 @@ export class SQLJobManager { return Configuration.get(`jobManager.jobSelfDefault`) || `*NONE`; } - static getNamingDefault(): "sql"|"system" { - return (Configuration.get(`jobManager.jobNamingDefault`) || `system`) as "system" | "sql"; + static getNamingDefault(): NamingFormats { + return (Configuration.get(`jobManager.jobNamingDefault`) || `system`) as NamingFormats; } } diff --git a/src/connection/sqlJob.ts b/src/connection/sqlJob.ts index e8de01f0..68ecf388 100644 --- a/src/connection/sqlJob.ts +++ b/src/connection/sqlJob.ts @@ -5,6 +5,7 @@ import { SQLJob } from "@ibm/mapepire-js"; import { ConnectionResult, JobStatus, QueryResult, ServerRequest, ServerResponse } from "@ibm/mapepire-js/dist/src/types"; import { JobLogEntry } from "./types"; import Statement from "../database/statement"; +import { NamingFormats } from "./manager"; const DB2I_VERSION = (process.env[`DB2I_VERSION`] || ``) + ((process.env.DEV) ? ``:`-dev`); @@ -18,6 +19,14 @@ export class OldSQLJob extends SQLJob { return this.selfState; } + getCurrentSchema(): string { + return this.options.libraries[0] || `QGPL`; + } + + getNaming(): NamingFormats { + return this.options.naming; + } + public static async useExec() { let useExec = false; diff --git a/src/language/providers/peekProvider.ts b/src/language/providers/peekProvider.ts index 122b0dfa..772018fa 100644 --- a/src/language/providers/peekProvider.ts +++ b/src/language/providers/peekProvider.ts @@ -2,17 +2,21 @@ import { languages, workspace } from "vscode"; import { getSqlDocument } from "./logic/parse"; import { JobManager } from "../../config"; import Statement from "../../database/statement"; -import { StatementType } from "../sql/types"; import { remoteAssistIsEnabled } from "./logic/available"; -import Schemas, { AllSQLTypes, InternalTypes, SQLType } from "../../database/schemas"; +import Schemas, { } from "../../database/schemas"; export const peekProvider = languages.registerDefinitionProvider({ language: `sql` }, { async provideDefinition(document, position, token) { - const standardObjects: SQLType[] = AllSQLTypes.filter(type => ![`functions`, `procedures`].includes(type)); if (!remoteAssistIsEnabled()) return; - const defaultSchema = getDefaultSchema(); + const currentJob = JobManager.getSelection(); + + if (!currentJob) return; + + const defaultSchema = currentJob.job.getCurrentSchema(); + const naming = currentJob.job.getNaming(); + const sqlDoc = getSqlDocument(document); const offset = document.offsetAt(position); @@ -26,7 +30,13 @@ export const peekProvider = languages.registerDefinitionProvider({ language: `sq if (ref) { const name = Statement.noQuotes(Statement.delimName(ref.object.name, true)); - const schema = ref.object.schema ? Statement.noQuotes(Statement.delimName(ref.object.schema, true)) : undefined; + + // Schema is based on a few things: + // If it's a fully qualified path, use the schema path + // Otherwise: + // - if SQL naming is in use, then use the default schema + // - if system naming is in use, then don't pass a library and the library list will be used + const schema = ref.object.schema ? Statement.noQuotes(Statement.delimName(ref.object.schema, true)) : naming === `sql` ? defaultSchema : undefined; const possibleObjects = await Schemas.resolveObjects([{name, schema}]); @@ -45,9 +55,4 @@ export const peekProvider = languages.registerDefinitionProvider({ language: `sq } } } -}); - -const getDefaultSchema = (): string => { - const currentJob = JobManager.getSelection(); - return currentJob && currentJob.job.options.libraries[0] ? currentJob.job.options.libraries[0] : `QGPL`; -} \ No newline at end of file +}); \ No newline at end of file From c21673e89e6268341c40f9495cad94eca7294ed6 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sun, 16 Mar 2025 21:58:05 -0400 Subject: [PATCH 13/23] Change range line Signed-off-by: worksofliam --- src/language/providers/peekProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/language/providers/peekProvider.ts b/src/language/providers/peekProvider.ts index 772018fa..f2521ce1 100644 --- a/src/language/providers/peekProvider.ts +++ b/src/language/providers/peekProvider.ts @@ -48,7 +48,7 @@ export const peekProvider = languages.registerDefinitionProvider({ language: `sq return { uri: document.uri, - range: document.lineAt(1).range, + range: document.lineAt(0).range, }; } From 621860f0043d80efd2de825e7a6499f56b896e74 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sun, 16 Mar 2025 22:55:05 -0400 Subject: [PATCH 14/23] Add text on library list setting to clarify current schema Signed-off-by: worksofliam --- src/views/jobManager/editJob/systemTab.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/jobManager/editJob/systemTab.ts b/src/views/jobManager/editJob/systemTab.ts index be6f3d99..13334875 100644 --- a/src/views/jobManager/editJob/systemTab.ts +++ b/src/views/jobManager/editJob/systemTab.ts @@ -29,7 +29,7 @@ export default function getSystemTab(options: JDBCOptions) { .addInput( `libraries`, `Library list`, - `List of system libraries, separated by commas or spaces`, + `List of system libraries, separated by commas or spaces. First entry is the default library / current schema, and the remaining items make up the library list.`, { rows: 2, default: options.libraries ? options.libraries.join(`, `) : `QGPL` } ) .addSelect( From d4eda783dbc3f07327c6a550689c81bf3ab7c024 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sun, 16 Mar 2025 23:13:28 -0400 Subject: [PATCH 15/23] New API to get current schema Signed-off-by: worksofliam --- package.json | 10 ---------- src/connection/sqlJob.ts | 22 ++++++++-------------- src/views/schemaBrowser/contributes.json | 10 ---------- src/views/schemaBrowser/index.ts | 16 ---------------- 4 files changed, 8 insertions(+), 50 deletions(-) diff --git a/package.json b/package.json index 48d3af6f..98ad0d95 100644 --- a/package.json +++ b/package.json @@ -429,11 +429,6 @@ "category": "Db2 for i", "icon": "$(output)" }, - { - "command": "vscode-db2i.setCurrentSchema", - "title": "Set current schema", - "category": "Db2 for i" - }, { "command": "vscode-db2i.setSchemaFilter", "title": "Set filter", @@ -948,11 +943,6 @@ } ], "view/item/context": [ - { - "command": "vscode-db2i.setCurrentSchema", - "when": "viewItem == schema", - "group": "db2@1" - }, { "command": "vscode-db2i.setSchemaFilter", "when": "viewItem == schema", diff --git a/src/connection/sqlJob.ts b/src/connection/sqlJob.ts index 68ecf388..fc0c8d1e 100644 --- a/src/connection/sqlJob.ts +++ b/src/connection/sqlJob.ts @@ -19,7 +19,14 @@ export class OldSQLJob extends SQLJob { return this.selfState; } - getCurrentSchema(): string { + async getCurrentSchema(): Promise { + if (this.getNaming() === `sql`) { + const result = await this.execute<{'00001': string}>(`values (current schema)`); + if (result.success && result.data.length > 0) { + return result.data[0]['00001']; + } + } + return this.options.libraries[0] || `QGPL`; } @@ -178,19 +185,6 @@ export class OldSQLJob extends SQLJob { throw e; } } - - async setCurrentSchema(schema: string): Promise> { - if (schema) { - const upperSchema = Statement.delimName(schema, true); - const result = await this.execute(`set current schema = ?`, {parameters: [upperSchema]}); - if (result.success) { - this.options.libraries[0] = upperSchema; - } - - return result; - - } - } getJobLog(): Promise> { return this.query(`select * from table(qsys2.joblog_info('*')) a`).execute(); diff --git a/src/views/schemaBrowser/contributes.json b/src/views/schemaBrowser/contributes.json index c5dfb936..2d8780d9 100644 --- a/src/views/schemaBrowser/contributes.json +++ b/src/views/schemaBrowser/contributes.json @@ -95,11 +95,6 @@ "category": "Db2 for i", "icon": "$(output)" }, - { - "command": "vscode-db2i.setCurrentSchema", - "title": "Set current schema", - "category": "Db2 for i" - }, { "command": "vscode-db2i.setSchemaFilter", "title": "Set filter", @@ -127,11 +122,6 @@ } ], "view/item/context": [ - { - "command": "vscode-db2i.setCurrentSchema", - "when": "viewItem == schema", - "group": "db2@1" - }, { "command": "vscode-db2i.setSchemaFilter", "when": "viewItem == schema", diff --git a/src/views/schemaBrowser/index.ts b/src/views/schemaBrowser/index.ts index 8cbfdd28..55438214 100644 --- a/src/views/schemaBrowser/index.ts +++ b/src/views/schemaBrowser/index.ts @@ -350,22 +350,6 @@ export default class schemaBrowser { } }), - vscode.commands.registerCommand(`vscode-db2i.setCurrentSchema`, async (node: SchemaItem) => { - if (node && node.contextValue === `schema`) { - const schema = node.schema.toUpperCase(); - - const config = getInstance().getConfig(); - const currentLibrary = config.currentLibrary.toUpperCase(); - - if (schema && schema !== currentLibrary) { - config.currentLibrary = schema; - await getInstance().setConfig(config); - } - - vscode.window.showInformationMessage(`Current schema set to ${schema}.`); - } - }), - vscode.commands.registerCommand(`vscode-db2i.setSchemaFilter`, async (node: SchemaItem) => { if (node) { const value = await vscode.window.showInputBox({ From df336611623c369595e0ac515f6e73ca8f178a5a Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sun, 16 Mar 2025 23:34:00 -0400 Subject: [PATCH 16/23] Better support for SQL naming vs system naming Signed-off-by: worksofliam --- src/connection/manager.ts | 17 ++++++++++++++- src/connection/sqlJob.ts | 11 +++++++++- src/language/providers/completionProvider.ts | 22 +++++++++++++------- src/language/providers/hoverProvider.ts | 18 ++++++++-------- src/language/providers/peekProvider.ts | 2 +- src/views/jobManager/editJob/systemTab.ts | 2 +- 6 files changed, 51 insertions(+), 21 deletions(-) diff --git a/src/connection/manager.ts b/src/connection/manager.ts index 5da38913..de124ebe 100644 --- a/src/connection/manager.ts +++ b/src/connection/manager.ts @@ -118,6 +118,16 @@ export class SQLJobManager { return this.jobs[jobExists]; } + private resetCurrentSchema(query: string, job: OldSQLJob) { + if (query.toUpperCase().startsWith(`SET`)) { + const newSchema = query.split(` `)[2]; + if (newSchema) { + job.resetCurrentSchemaCache(); + } + } + return query; + } + async runSQL(query: string, opts?: QueryOptions, rowsToFetch = 2147483647): Promise { // 2147483647 is NOT arbitrary. On the server side, this is processed as a Java // int. This is the largest number available without overflow (Integer.MAX_VALUE) @@ -125,6 +135,8 @@ export class SQLJobManager { const statement = await this.getPagingStatement(query, opts); const results = await statement.execute(rowsToFetch); statement.close(); + + this.resetCurrentSchema(query, this.jobs[this.selectedJob].job); return results.data; } @@ -136,12 +148,15 @@ export class SQLJobManager { const results = await statement.execute(rowsToFetch); statement.close(); + this.resetCurrentSchema(query, this.jobs[this.selectedJob].job); return results; } async getPagingStatement(query: string, opts?: QueryOptions): Promise> { - const selected = this.jobs[this.selectedJob] + const selected = this.jobs[this.selectedJob]; if (ServerComponent.isInstalled() && selected) { + this.resetCurrentSchema(query, selected?.job); + return selected.job.query(query, opts); } else if (!ServerComponent.isInstalled()) { diff --git a/src/connection/sqlJob.ts b/src/connection/sqlJob.ts index fc0c8d1e..50993c6a 100644 --- a/src/connection/sqlJob.ts +++ b/src/connection/sqlJob.ts @@ -14,16 +14,25 @@ export class OldSQLJob extends SQLJob { private selfState: SelfValue = "*NONE"; id: string | undefined; + private currentSchemaStore: string | undefined; getSelfCode(): SelfValue { return this.selfState; } + resetCurrentSchemaCache() { + this.currentSchemaStore = undefined; + } + async getCurrentSchema(): Promise { if (this.getNaming() === `sql`) { + if (this.currentSchemaStore) + return this.currentSchemaStore; + const result = await this.execute<{'00001': string}>(`values (current schema)`); if (result.success && result.data.length > 0) { - return result.data[0]['00001']; + this.currentSchemaStore = result.data[0]['00001']; + return this.currentSchemaStore; } } diff --git a/src/language/providers/completionProvider.ts b/src/language/providers/completionProvider.ts index 43ead37e..e37f3e8d 100644 --- a/src/language/providers/completionProvider.ts +++ b/src/language/providers/completionProvider.ts @@ -219,6 +219,7 @@ async function getCompletionItemsForTriggerDot( offset: number, trigger: string ): Promise { + const defaultLibrary = await getDefaultSchema(); let list: CompletionItem[] = []; const curRef = currentStatement.getReferenceByOffset(offset); @@ -236,7 +237,7 @@ async function getCompletionItemsForTriggerDot( // Set the default schema for all references without one for (let ref of objectRefs) { if (!ref.object.schema) { - ref.object.schema = getDefaultSchema(); + ref.object.schema = defaultLibrary; } } @@ -284,7 +285,7 @@ async function getCompletionItemsForTriggerDot( } else { if (currentStatement.type === StatementType.Call) { - const procs = await getProcedures([curRef], getDefaultSchema()); + const procs = await getProcedures([curRef], defaultLibrary); list.push(...procs); } else { @@ -332,6 +333,7 @@ function createCompletionItemForAlias(ref: ObjectRef) { } async function getCompletionItemsForRefs(currentStatement: LanguageStatement.default, offset: number, cteColumns?: string[]) { + const defaultSchema = await getDefaultSchema(); const objectRefs = currentStatement.getObjectReferences(); const cteList = currentStatement.getCTEReferences(); @@ -350,7 +352,7 @@ async function getCompletionItemsForRefs(currentStatement: LanguageStatement.def // Set the default schema for all references without one for (let ref of objectRefs) { if (!ref.object.schema) { - ref.object.schema = getDefaultSchema(); + ref.object.schema = defaultSchema; } } @@ -395,7 +397,7 @@ async function getCompletionItemsForRefs(currentStatement: LanguageStatement.def if (tokenAtOffset === undefined && (emptyObjectRefs || curClause !== ClauseType.Unknown)) { // get all the completion items for objects in each referenced schema completionItems.push( - ...(await getObjectCompletions(getDefaultSchema(), completionTypes)) + ...(await getObjectCompletions(defaultSchema, completionTypes)) ); } else { // content assist invoked during incomplete reference @@ -453,7 +455,7 @@ async function getCompletionItems( currentStatement: LanguageStatement.default|undefined, offset?: number ) { - + const defaultSchema = await getDefaultSchema(); const s = currentStatement ? currentStatement.getTokenByOffset(offset) : null; if (trigger === "." || (s && s.type === `dot`) || trigger === "/") { @@ -479,7 +481,7 @@ async function getCompletionItems( if (currentStatement && currentStatement.type === StatementType.Call) { const curClause = currentStatement.getClauseForOffset(offset); if (curClause === ClauseType.Unknown) { - return getProcedures(currentStatement.getObjectReferences(), getDefaultSchema()); + return getProcedures(currentStatement.getObjectReferences(), defaultSchema); } } @@ -623,7 +625,11 @@ export const completionProvider = languages.registerCompletionItemProvider( "/" ); -const getDefaultSchema = (): string => { +const getDefaultSchema = () => { const currentJob = JobManager.getSelection(); - return currentJob && currentJob.job.options.libraries[0] ? currentJob.job.options.libraries[0] : `QGPL`; + if (currentJob) { + return currentJob.job.getCurrentSchema() + } else { + return Promise.resolve(`QGPL`); + } } \ No newline at end of file diff --git a/src/language/providers/hoverProvider.ts b/src/language/providers/hoverProvider.ts index 47037375..8ccfc1bf 100644 --- a/src/language/providers/hoverProvider.ts +++ b/src/language/providers/hoverProvider.ts @@ -15,12 +15,16 @@ import { CallableSignature } from "../../database/callable"; export const openProvider = workspace.onDidOpenTextDocument(async (document) => { if (document.languageId === `sql`) { - if (remoteAssistIsEnabled()) { + const selected = remoteAssistIsEnabled(); + + if (selected) { const sqlDoc = getSqlDocument(document); - const defaultSchema = getDefaultSchema(); + const defaultSchema = await selected.job.getCurrentSchema(); if (!sqlDoc) return; + // TODO: we need to stop hard coding default schema here!! + for (const statement of sqlDoc.statements) { const refs = statement.getObjectReferences(); if (refs.length) { @@ -60,9 +64,10 @@ export const openProvider = workspace.onDidOpenTextDocument(async (document) => export const hoverProvider = languages.registerHoverProvider({ language: `sql` }, { async provideHover(document, position, token) { - if (!remoteAssistIsEnabled(true)) return; + const selected = remoteAssistIsEnabled(true); + if (!selected) return; - const defaultSchema = getDefaultSchema(); + const defaultSchema = await selected.job.getCurrentSchema(); const sqlDoc = getSqlDocument(document); const offset = document.offsetAt(position); @@ -193,9 +198,4 @@ function lookupSymbol(name: string, schema: string | undefined, possibleNames: s schema = schema ? Statement.noQuotes(Statement.delimName(schema, true)) : undefined return DbCache.lookupSymbol(name, schema, possibleNames); -} - -const getDefaultSchema = (): string => { - const currentJob = JobManager.getSelection(); - return currentJob && currentJob.job.options.libraries[0] ? currentJob.job.options.libraries[0] : `QGPL`; } \ No newline at end of file diff --git a/src/language/providers/peekProvider.ts b/src/language/providers/peekProvider.ts index f2521ce1..0cac290b 100644 --- a/src/language/providers/peekProvider.ts +++ b/src/language/providers/peekProvider.ts @@ -14,7 +14,7 @@ export const peekProvider = languages.registerDefinitionProvider({ language: `sq if (!currentJob) return; - const defaultSchema = currentJob.job.getCurrentSchema(); + const defaultSchema = await currentJob.job.getCurrentSchema(); const naming = currentJob.job.getNaming(); const sqlDoc = getSqlDocument(document); diff --git a/src/views/jobManager/editJob/systemTab.ts b/src/views/jobManager/editJob/systemTab.ts index 13334875..63b8101a 100644 --- a/src/views/jobManager/editJob/systemTab.ts +++ b/src/views/jobManager/editJob/systemTab.ts @@ -29,7 +29,7 @@ export default function getSystemTab(options: JDBCOptions) { .addInput( `libraries`, `Library list`, - `List of system libraries, separated by commas or spaces. First entry is the default library / current schema, and the remaining items make up the library list.`, + `List of system libraries, separated by commas or spaces. First entry is the default library, and the remaining items make up the library list. The library list is typically not used when SQL naming is used.`, { rows: 2, default: options.libraries ? options.libraries.join(`, `) : `QGPL` } ) .addSelect( From d74172914499244962d18c5b3e970b4b2974d50e Mon Sep 17 00:00:00 2001 From: worksofliam Date: Wed, 19 Mar 2025 09:41:24 -0400 Subject: [PATCH 17/23] Fix for delimited names Signed-off-by: worksofliam --- src/database/schemas.ts | 18 +++++++++++++----- src/language/providers/peekProvider.ts | 6 +++--- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/database/schemas.ts b/src/database/schemas.ts index 3fd5184d..b4384d34 100644 --- a/src/database/schemas.ts +++ b/src/database/schemas.ts @@ -103,7 +103,8 @@ export default class Schemas { * Resolves to the following SQL types: SCHEMA, TABLE, VIEW, ALIAS, INDEX, FUNCTION and PROCEDURE */ static async resolveObjects( - sqlObjects: ObjectReference[] + sqlObjects: ObjectReference[], + ignoreSystemTypes: string[] = [] ): Promise { let statements: string[] = []; let parameters: BasicColumnType[] = []; @@ -114,6 +115,13 @@ export default class Schemas { // First, we use OBJECT_STATISTICS to resolve the object based on the library list. // But, if the object is qualified with a schema, we need to use that schema to get the correct object. + + let ignoreClause = ``; + if (ignoreSystemTypes.length > 0) { + ignoreSystemTypes = ignoreSystemTypes.map(i => i.toUpperCase()); + ignoreClause = `where objtype not in (${ignoreSystemTypes.map((i) => `?`).join(`, `)})`; + } + for (const obj of sqlObjects) { const cached = this.getCachedReference(obj); if (cached) { @@ -123,14 +131,14 @@ export default class Schemas { if (obj.schema) { statements.push( - `${BASE_RESOLVE_SELECT} from table(qsys2.object_statistics(?, '*ALL', object_name => ?))` + `${BASE_RESOLVE_SELECT} from table(qsys2.object_statistics(?, '*ALL', object_name => ?)) ${ignoreClause}` ); - parameters.push(obj.schema, obj.name); + parameters.push(obj.schema, obj.name, ...ignoreSystemTypes); } else { statements.push( - `${BASE_RESOLVE_SELECT} from table(qsys2.object_statistics('*LIBL', '*ALL', object_name => ?))` + `${BASE_RESOLVE_SELECT} from table(qsys2.object_statistics('*LIBL', '*ALL', object_name => ?)) ${ignoreClause}` ); - parameters.push(obj.name); + parameters.push(obj.name, ...ignoreSystemTypes); } } diff --git a/src/language/providers/peekProvider.ts b/src/language/providers/peekProvider.ts index 0cac290b..32b11c09 100644 --- a/src/language/providers/peekProvider.ts +++ b/src/language/providers/peekProvider.ts @@ -29,16 +29,16 @@ export const peekProvider = languages.registerDefinitionProvider({ language: `sq const ref = refs.find(ref => ref.tokens[0].range.start && offset <= ref.tokens[ref.tokens.length - 1].range.end); if (ref) { - const name = Statement.noQuotes(Statement.delimName(ref.object.name, true)); + const name = Statement.delimName(ref.object.name, true); // Schema is based on a few things: // If it's a fully qualified path, use the schema path // Otherwise: // - if SQL naming is in use, then use the default schema // - if system naming is in use, then don't pass a library and the library list will be used - const schema = ref.object.schema ? Statement.noQuotes(Statement.delimName(ref.object.schema, true)) : naming === `sql` ? defaultSchema : undefined; + const schema = ref.object.schema ? Statement.delimName(ref.object.schema, true) : naming === `sql` ? defaultSchema : undefined; - const possibleObjects = await Schemas.resolveObjects([{name, schema}]); + const possibleObjects = await Schemas.resolveObjects([{name, schema}], [`*LIB`]); if (possibleObjects.length === 1) { const obj = possibleObjects[0]; From b69014309b3d46f5fca78f9ff363b6a30d15aa29 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Mon, 24 Mar 2025 09:34:08 -0400 Subject: [PATCH 18/23] Replace config call Signed-off-by: worksofliam --- src/connection/syntaxChecker/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/connection/syntaxChecker/index.ts b/src/connection/syntaxChecker/index.ts index a5f939e6..1462b22c 100644 --- a/src/connection/syntaxChecker/index.ts +++ b/src/connection/syntaxChecker/index.ts @@ -47,7 +47,7 @@ export class SQLStatementChecker implements IBMiComponent { private getLibrary(connection: IBMi) { if (!this.library) { - this.library = connection?.config?.tempLibrary.toUpperCase() || `ILEDITOR`; + this.library = connection?.getConfig()?.tempLibrary.toUpperCase() || `ILEDITOR`; } return this.library; @@ -79,7 +79,7 @@ export class SQLStatementChecker implements IBMiComponent { return -1; } - async getRemoteState(connection: IBMi) { + async getRemoteState(connection: IBMi): Promise { const lib = this.getLibrary(connection); const wrapperVersion = await SQLStatementChecker.getVersionOf(connection, lib, WRAPPER_NAME); @@ -98,7 +98,7 @@ export class SQLStatementChecker implements IBMiComponent { update(connection: IBMi): ComponentState | Promise { return connection.withTempDirectory(async tempDir => { const tempSourcePath = posix.join(tempDir, `sqlchecker.sql`); - await connection.content.writeStreamfileRaw(tempSourcePath, Buffer.from(this.getSource(connection), "utf-8")); + await connection.getConfig().writeStreamfileRaw(tempSourcePath, Buffer.from(this.getSource(connection), "utf-8")); const result = await connection.runCommand({ command: `RUNSQLSTM SRCSTMF('${tempSourcePath}') COMMIT(*NONE) NAMING(*SYS)`, noLibList: true From a898a5caf5aa9df2d249343e0e0b81e29d5f8e4e Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 29 Apr 2025 11:14:37 -0400 Subject: [PATCH 19/23] Add additional info to hover for naming info Signed-off-by: worksofliam --- src/views/jobManager/statusBar.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/views/jobManager/statusBar.ts b/src/views/jobManager/statusBar.ts index 56ba91a7..cce6e044 100644 --- a/src/views/jobManager/statusBar.ts +++ b/src/views/jobManager/statusBar.ts @@ -14,7 +14,7 @@ export async function updateStatusBar(options: {newJob?: boolean, canceling?: bo let text; let backgroundColour: ThemeColor|undefined = undefined; - let toolTipItems = []; + let toolTipItems: string[] = []; if (options.executing) { text = `$(sync~spin) Executing...`; @@ -31,6 +31,18 @@ export async function updateStatusBar(options: {newJob?: boolean, canceling?: bo if (selected) { text = `$(database) ${selected.name}`; + const job = selected.job; + + if (job.getNaming() === `sql`) { + toolTipItems.push(`SQL Naming. Current schema: \`${await job.getCurrentSchema()}\``); + } else { + toolTipItems.push([ + `System Naming. Library list:`, + ``, + ...job.options.libraries.map((lib, i) => `${i+1}. \`${lib}\``) + ].join(`\n`)); + } + if (selected.job.underCommitControl()) { const pendingsTracts = await selected.job.getPendingTransactions(); if (pendingsTracts > 0) { From d78600753b09cac50a2639cee6753acb5a47ed54 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 29 Apr 2025 15:38:26 -0400 Subject: [PATCH 20/23] Improve status bar text Signed-off-by: worksofliam --- src/views/jobManager/statusBar.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/views/jobManager/statusBar.ts b/src/views/jobManager/statusBar.ts index cce6e044..ce45a592 100644 --- a/src/views/jobManager/statusBar.ts +++ b/src/views/jobManager/statusBar.ts @@ -34,10 +34,12 @@ export async function updateStatusBar(options: {newJob?: boolean, canceling?: bo const job = selected.job; if (job.getNaming() === `sql`) { - toolTipItems.push(`SQL Naming. Current schema: \`${await job.getCurrentSchema()}\``); + toolTipItems.push(`SQL Naming.\n\nCurrent schema: \`${await job.getCurrentSchema()}\``); } else { toolTipItems.push([ - `System Naming. Library list:`, + `System Naming.`, + ``, + `Configured user library list for job:`, ``, ...job.options.libraries.map((lib, i) => `${i+1}. \`${lib}\``) ].join(`\n`)); From d6707440338e0a6ef4bd35f5c39e4e5fe4a7638b Mon Sep 17 00:00:00 2001 From: worksofliam Date: Wed, 30 Apr 2025 08:07:17 -0400 Subject: [PATCH 21/23] Improve peek to include all matches Signed-off-by: worksofliam --- src/language/providers/peekProvider.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/language/providers/peekProvider.ts b/src/language/providers/peekProvider.ts index 32b11c09..ef8acda4 100644 --- a/src/language/providers/peekProvider.ts +++ b/src/language/providers/peekProvider.ts @@ -40,11 +40,21 @@ export const peekProvider = languages.registerDefinitionProvider({ language: `sq const possibleObjects = await Schemas.resolveObjects([{name, schema}], [`*LIB`]); - if (possibleObjects.length === 1) { - const obj = possibleObjects[0]; - const content = await Schemas.generateSQL(obj.schema, obj.name, obj.sqlType, true); + let totalBlocks: string[] = []; - const document = await workspace.openTextDocument({ content, language: `sql` }); + if (possibleObjects.length > 0) { + if (possibleObjects.length > 1) { + totalBlocks.push( + `-- Multiple objects found with the same name.` + ); + } + + for (const posObj of possibleObjects) { + const newContent = await Schemas.generateSQL(posObj.schema, posObj.name, posObj.sqlType, true); + totalBlocks.push(newContent); + } + + const document = await workspace.openTextDocument({ content: totalBlocks.join(`\n\n`), language: `sql` }); return { uri: document.uri, From 88355277e386e7c31da3a3d4d7e845960da05008 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 1 May 2025 08:38:27 -0400 Subject: [PATCH 22/23] Fix schema name from configuration Signed-off-by: worksofliam --- src/language/providers/peekProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/language/providers/peekProvider.ts b/src/language/providers/peekProvider.ts index ef8acda4..5a54d9da 100644 --- a/src/language/providers/peekProvider.ts +++ b/src/language/providers/peekProvider.ts @@ -36,7 +36,7 @@ export const peekProvider = languages.registerDefinitionProvider({ language: `sq // Otherwise: // - if SQL naming is in use, then use the default schema // - if system naming is in use, then don't pass a library and the library list will be used - const schema = ref.object.schema ? Statement.delimName(ref.object.schema, true) : naming === `sql` ? defaultSchema : undefined; + const schema = ref.object.schema ? Statement.delimName(ref.object.schema, true) : naming === `sql` ? Statement.delimName(defaultSchema) : undefined; const possibleObjects = await Schemas.resolveObjects([{name, schema}], [`*LIB`]); From 854d70bd80ed8a36bcf3e668efdd48053f200323 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 1 May 2025 08:52:50 -0400 Subject: [PATCH 23/23] Use delim name on status bar Signed-off-by: worksofliam --- src/views/jobManager/statusBar.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/views/jobManager/statusBar.ts b/src/views/jobManager/statusBar.ts index ce45a592..58366beb 100644 --- a/src/views/jobManager/statusBar.ts +++ b/src/views/jobManager/statusBar.ts @@ -2,6 +2,7 @@ import { MarkdownString, StatusBarAlignment, ThemeColor, languages, window } fro import { ServerComponent } from "../../connection/serverComponent"; import { JobManager } from "../../config"; import { getInstance } from "../../base"; +import Statement from "../../database/statement"; const item = window.createStatusBarItem(`sqlJob`, StatusBarAlignment.Left); @@ -34,7 +35,7 @@ export async function updateStatusBar(options: {newJob?: boolean, canceling?: bo const job = selected.job; if (job.getNaming() === `sql`) { - toolTipItems.push(`SQL Naming.\n\nCurrent schema: \`${await job.getCurrentSchema()}\``); + toolTipItems.push(`SQL Naming.\n\nCurrent schema: \`${Statement.delimName(await job.getCurrentSchema())}\``); } else { toolTipItems.push([ `System Naming.`,