diff --git a/.github/workflows/webpack_ci.yaml b/.github/workflows/webpack_ci.yaml index cff735fb..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}} @@ -70,4 +70,4 @@ jobs: 👋 A new build is available for this PR based on ${{ github.event.pull_request.head.sha }}. * [Download here.](https://github.com/codefori/vscode-db2i/actions/runs/${{ github.run_id }}) - * [Read more about how to test](https://github.com/codefori/vscode-db2i/blob/master/.github/pr_testing_template.md) \ No newline at end of file + * [Read more about how to test](https://github.com/codefori/vscode-db2i/blob/master/.github/pr_testing_template.md) diff --git a/src/database/schemas.ts b/src/database/schemas.ts index 152ee781..ccabdf95 100644 --- a/src/database/schemas.ts +++ b/src/database/schemas.ts @@ -15,6 +15,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; @@ -212,27 +228,46 @@ export default class Schemas { schema: object.BASE_SCHEMA || undefined, name: object.BASE_OBJ || undefined } - })); + } as BasicSQLObject)); } /** * @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`); + + 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'`, + ); + } + 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 )` + `CALL QSYS2.GENERATE_SQL( ${options.join(`, `)} )`, ].join(` `), { parameters: [object, schema, internalType] }); // TODO: eventually .content -> .getContent(), it's not available yet 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..a91f697d --- /dev/null +++ b/src/language/providers/peekProvider.ts @@ -0,0 +1,67 @@ +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"; + + +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 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.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[] = [`-- 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 document = await workspace.openTextDocument({ content: lines.join(`\n`), language: `sql` }); + + return { + uri: document.uri, + range: document.lineAt(1).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 424e27af..cc0a9d46 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 { getCopyUi } from "./copyUI"; import { getAdvisedIndexesStatement, getIndexesStatement, getMTIStatement, getAuthoritiesStatement, getObjectLocksStatement } 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`, @@ -577,7 +561,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;