diff --git a/package.json b/package.json index 6184c65c..dc4a38a9 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", @@ -423,11 +439,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", @@ -588,8 +599,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)" }, @@ -948,7 +959,7 @@ "when": "view == jobManager" }, { - "command": "vscode-db2i.jobManager.defaultSelfSettings", + "command": "vscode-db2i.jobManager.defaultSettings", "group": "navigation", "when": "view == jobManager" }, @@ -1014,11 +1025,6 @@ } ], "view/item/context": [ - { - "command": "vscode-db2i.setCurrentSchema", - "when": "viewItem == schema", - "group": "db2@1" - }, { "command": "vscode-db2i.setSchemaFilter", "when": "viewItem == schema", @@ -1375,7 +1381,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 3f4ead4e..2806f8f5 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 { @@ -27,11 +29,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", @@ -117,6 +120,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) @@ -128,10 +141,7 @@ export class SQLJobManager { const results = await statement.execute(rowsToFetch); statement.close(); - // const e = performance.now() - // console.log(`Statement executed in ${e - s} ms. ${results.data.length} rows returned.`); - // console.log(`\t${query.padEnd(40).substring(0, 40)}`) - + this.resetCurrentSchema(query, this.jobs[this.selectedJob].job); return results.data; } @@ -143,12 +153,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()) { @@ -171,6 +184,10 @@ export class SQLJobManager { } static getSelfDefault(): SelfValue { - return Configuration.get(`jobSelfDefault`) || `*NONE`; + return Configuration.get(`jobManager.jobSelfDefault`) || `*NONE`; + } + + static getNamingDefault(): NamingFormats { + return (Configuration.get(`jobManager.jobNamingDefault`) || `system`) as NamingFormats; } } diff --git a/src/connection/sqlJob.ts b/src/connection/sqlJob.ts index 447e4319..a1d6a821 100644 --- a/src/connection/sqlJob.ts +++ b/src/connection/sqlJob.ts @@ -5,6 +5,7 @@ import { SQLJob } from "@ibm/mapepire-js"; import { ConnectionResult, QueryResult, ServerRequest, ServerResponse } from "@ibm/mapepire-js/dist/src/types"; import { JobLogEntry, JobStatus } from "./types"; import Statement from "../database/statement"; +import { NamingFormats } from "./manager"; const DB2I_VERSION = (process.env[`DB2I_VERSION`] || ``) + ((process.env.DEV) ? ``:`-dev`); @@ -13,11 +14,35 @@ 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) { + this.currentSchemaStore = result.data[0]['00001']; + return this.currentSchemaStore; + } + } + + return this.options.libraries[0] || `QGPL`; + } + + getNaming(): NamingFormats { + return this.options.naming; + } + public static async useExec() { let useExec = false; @@ -169,19 +194,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/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 diff --git a/src/database/schemas.ts b/src/database/schemas.ts index 25dc09ce..40e37d34 100644 --- a/src/database/schemas.ts +++ b/src/database/schemas.ts @@ -16,14 +16,30 @@ 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; -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})`; @@ -47,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 `, @@ -59,50 +75,70 @@ 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 */ static async resolveObjects( - sqlObjects: ObjectReference[] + sqlObjects: ObjectReference[], + ignoreSystemTypes: string[] = [] ): Promise { let statements: string[] = []; let parameters: BasicColumnType[] = []; 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. + + 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 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 => ?))` + `${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); } } @@ -114,52 +150,61 @@ 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) => { + this.storeCachedReference(obj, obj); + }); + + return resolvedObjects; + } catch (e) { + console.warn(`Error resolving objects: ${JSON.stringify(sqlObjects)}`); + console.warn(e); + return []; + } } static async getRelatedObjects( @@ -367,8 +412,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(` `), { @@ -398,36 +442,47 @@ export default class Schemas { * @param schema Not user input * @param object Not user input */ - static async generateSQL( - schema: string, - object: string, - internalType: string - ): Promise { + static async generateSQL(schema: string, object: string, internalType: string, isBasic?: boolean): Promise { const instance = getInstance(); const connection = instance.getConnection(); - const result = await connection.withTempDirectory( - async (tempDir) => { - const tempFilePath = path.posix.join(tempDir, `generatedSql.sql`); - await JobManager.runSQL<{ SRCDTA: string }>( - [ - `CALL QSYS2.GENERATE_SQL( DATABASE_OBJECT_NAME => ?, DATABASE_OBJECT_LIBRARY_NAME => ?, DATABASE_OBJECT_TYPE => ? - , CREATE_OR_REPLACE_OPTION => '1', PRIVILEGES_OPTION => '0' - , DATABASE_SOURCE_FILE_NAME => '*STMF' - , STATEMENT_FORMATTING_OPTION => '0' - , SOURCE_STREAM_FILE => '${tempFilePath}' - , SOURCE_STREAM_FILE_END_OF_LINE => 'LF' - , SOURCE_STREAM_FILE_CCSID => 1208 )`, - ].join(` `), - { parameters: [object, schema, internalType] } + const result = await connection.withTempDirectory(async (tempDir) => { + const tempFilePath = path.posix.join(tempDir, `generatedSql.sql`); + + let options = [ + `DATABASE_OBJECT_NAME => ?`, + `DATABASE_OBJECT_LIBRARY_NAME => ?`, + `DATABASE_OBJECT_TYPE => ?`, + `DATABASE_SOURCE_FILE_NAME => '*STMF'`, + `STATEMENT_FORMATTING_OPTION => '1'`, + `SOURCE_STREAM_FILE => '${tempFilePath}'`, + `SOURCE_STREAM_FILE_END_OF_LINE => 'LF'`, + `SOURCE_STREAM_FILE_CCSID => 1208` + ]; + + if (isBasic) { + options.push( + `CREATE_OR_REPLACE_OPTION => '0'`, + `PRIVILEGES_OPTION => '0'`, + `COMMENT_OPTION => '0'`, + `LABEL_OPTION => '0'`, + `HEADER_OPTION => '0'`, + `TRIGGER_OPTION => '0'`, + `CONSTRAINT_OPTION => '0'`, + `MASK_AND_PERMISSION_OPTION => '0'`, ); - - // TODO: eventually .content -> .getContent(), it's not available yet - const contents = ( - await connection.content.downloadStreamfileRaw(tempFilePath) - ).toString(); - return contents; } + + await JobManager.runSQL<{ SRCDTA: string }>([ + `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; + } ); return result; @@ -438,9 +493,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); } @@ -450,9 +504,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/completionProvider.ts b/src/language/providers/completionProvider.ts index 6460d664..be0c34e0 100644 --- a/src/language/providers/completionProvider.ts +++ b/src/language/providers/completionProvider.ts @@ -212,6 +212,7 @@ async function getCompletionItemsForTriggerDot( offset: number, trigger: string ): Promise { + const defaultLibrary = await getDefaultSchema(); let list: CompletionItem[] = []; const curRef = currentStatement.getReferenceByOffset(offset); @@ -229,7 +230,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; } } @@ -278,7 +279,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 { @@ -326,6 +327,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(); @@ -341,7 +343,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; } } @@ -386,7 +388,7 @@ async function getCompletionItemsForRefs(currentStatement: LanguageStatement.def if (tokenAtOffset === undefined && (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 @@ -444,7 +446,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 === "/") { @@ -470,7 +472,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); } } @@ -614,7 +616,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 87940c61..c3755942 100644 --- a/src/language/providers/hoverProvider.ts +++ b/src/language/providers/hoverProvider.ts @@ -15,12 +15,16 @@ import { getPositionData } from "../sql/document"; 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); @@ -206,9 +211,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/index.ts b/src/language/providers/index.ts index 80414299..215906cd 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"; import { checkDocumentDefintion, problemProvider } from "./problemProvider"; import { Db2StatusProvider } from "./statusProvider"; @@ -16,6 +17,7 @@ export function languageInit() { signatureProvider, hoverProvider, openProvider, + peekProvider, ...problemProvider, checkDocumentDefintion, sqlLanguageStatus diff --git a/src/language/providers/peekProvider.ts b/src/language/providers/peekProvider.ts new file mode 100644 index 00000000..32b11c09 --- /dev/null +++ b/src/language/providers/peekProvider.ts @@ -0,0 +1,58 @@ +import { languages, workspace } from "vscode"; +import { getSqlDocument } from "./logic/parse"; +import { JobManager } from "../../config"; +import Statement from "../../database/statement"; +import { remoteAssistIsEnabled } from "./logic/available"; +import Schemas, { } from "../../database/schemas"; + + +export const peekProvider = languages.registerDefinitionProvider({ language: `sql` }, { + async provideDefinition(document, position, token) { + if (!remoteAssistIsEnabled()) return; + + const currentJob = JobManager.getSelection(); + + if (!currentJob) return; + + const defaultSchema = await currentJob.job.getCurrentSchema(); + const naming = currentJob.job.getNaming(); + + 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 ref = refs.find(ref => ref.tokens[0].range.start && offset <= ref.tokens[ref.tokens.length - 1].range.end); + + if (ref) { + 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.delimName(ref.object.schema, true) : naming === `sql` ? defaultSchema : undefined; + + 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); + + const document = await workspace.openTextDocument({ content, language: `sql` }); + + return { + uri: document.uri, + range: document.lineAt(0).range, + }; + } + + } + } + } +}); \ No newline at end of file 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/editJob/systemTab.ts b/src/views/jobManager/editJob/systemTab.ts index be6f3d99..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`, + `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( diff --git a/src/views/jobManager/jobManagerView.ts b/src/views/jobManager/jobManagerView.ts index 670f0379..6c6204f8 100644 --- a/src/views/jobManager/jobManagerView.ts +++ b/src/views/jobManager/jobManagerView.ts @@ -31,8 +31,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": [ 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) { diff --git a/src/views/schemaBrowser/contributes.json b/src/views/schemaBrowser/contributes.json index 225e62ae..5e5ee70b 100644 --- a/src/views/schemaBrowser/contributes.json +++ b/src/views/schemaBrowser/contributes.json @@ -100,11 +100,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", @@ -189,11 +184,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 4160eea7..dc73ac22 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"; @@ -13,22 +13,6 @@ import { getCopyUi } from "./copyUI"; import { getAdvisedIndexesStatement, getIndexesStatement, getMTIStatement, getAuthoritiesStatement, getObjectLocksStatement, getRecordLocksStatement } from "./statements"; import { BasicSQLObject } from "../../types"; -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`, @@ -393,22 +377,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({ @@ -606,7 +574,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;