diff --git a/.vscode/tasks.json b/.vscode/tasks.json index b95c92b5..21bfe0e2 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -7,10 +7,10 @@ "type": "npm", "script": "webpack-dev", "problemMatcher": "$ts-webpack-watch", - "isBackground": false, - + "isBackground": true, "presentation": { - "reveal": "never" + "reveal": "never", + "group": "watchers" }, "group": { "kind": "build", @@ -18,4 +18,4 @@ } } ] -} +} \ No newline at end of file diff --git a/README.md b/README.md index 537d27f4..598be7f2 100644 --- a/README.md +++ b/README.md @@ -21,5 +21,4 @@ Db2 for IBM i provides SQL functionality to VS Code. 1. This project requires VS Code and Node.js. 2. fork & clone repo 3. `npm i` -4. `npm run dsc` (to fetch the Server Component) -5. 'Run Extension' from vscode debug. +4. 'Run Extension' from vscode debug. diff --git a/package-lock.json b/package-lock.json index 1225cd42..319be3c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,29 +1,26 @@ { "name": "vscode-db2i", - "version": "1.17.0", + "version": "2.0.0-dev.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-db2i", - "version": "1.17.0", + "version": "2.0.0-dev.0", "dependencies": { - "@ibm/mapepire-js": "^0.5.0", - "@octokit/rest": "^21.1.1", "chart.js": "^4.4.2", "csv": "^6.1.3", "json-to-markdown-table": "^1.0.0", "lru-cache": "^6.0.0", - "node-fetch": "^3.3.1", - "octokit": "^5.0.3", "showdown": "^2.1.0", "sql-formatter": "^14.0.0" }, "devDependencies": { "@continuedev/core": "^1.0.13", - "@halcyontech/vscode-ibmi-types": "^2.17.0", + "@halcyontech/vscode-ibmi-types": "^3.0.9", + "@ibm/mapepire-js": "^0.5.0", "@types/glob": "^7.1.3", - "@types/node": "18.x", + "@types/node": "25.x", "@types/vscode": "^1.95.0", "esbuild-loader": "^4.3.0", "glob": "^7.1.7", @@ -1482,9 +1479,9 @@ "optional": true }, "node_modules/@halcyontech/vscode-ibmi-types": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/@halcyontech/vscode-ibmi-types/-/vscode-ibmi-types-2.18.0.tgz", - "integrity": "sha512-gHoLJW+wFG3uuGxnwXmCn7xRg5v4zqidAZmq6W/SS2m8v6CUl5ZXEb2S653gB1yY0VzQG8F2azD6CXw7EFT0dw==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@halcyontech/vscode-ibmi-types/-/vscode-ibmi-types-3.0.9.tgz", + "integrity": "sha512-WSrOZFw67NEFtAaVFT+7W2lRQarem0QFzZU21CI9+WXTh2sdO9kKLQ0FS9/EP6vcbfvw4xEgip1MSI0o5zmXyQ==", "dev": true, "license": "ISC" }, @@ -1569,6 +1566,7 @@ "version": "0.5.0", "resolved": "https://registry.npmjs.org/@ibm/mapepire-js/-/mapepire-js-0.5.0.tgz", "integrity": "sha512-IsjVqqv4DRloQMta4gyUoLpZr0tXzRaHBQ9wB0jPASkgngZFX+RhA9ggE5dZEh1MxvictRaGl0YZrZ8KCHFKZg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "ws": "^8.16.0" @@ -1744,1207 +1742,70 @@ ], "license": "MIT" }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@npmcli/fs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", - "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", - "dev": true, - "license": "ISC", - "optional": true, - "dependencies": { - "@gar/promisify": "^1.0.1", - "semver": "^7.3.5" - } - }, - "node_modules/@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@octokit/app": { - "version": "16.1.2", - "resolved": "https://registry.npmjs.org/@octokit/app/-/app-16.1.2.tgz", - "integrity": "sha512-8j7sEpUYVj18dxvh0KWj6W/l6uAiVRBl1JBDVRqH1VHKAO/G5eRVl4yEoYACjakWers1DjUkcCHyJNQK47JqyQ==", - "license": "MIT", - "dependencies": { - "@octokit/auth-app": "^8.1.2", - "@octokit/auth-unauthenticated": "^7.0.3", - "@octokit/core": "^7.0.6", - "@octokit/oauth-app": "^8.0.3", - "@octokit/plugin-paginate-rest": "^14.0.0", - "@octokit/types": "^16.0.0", - "@octokit/webhooks": "^14.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/app/node_modules/@octokit/auth-token": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", - "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/app/node_modules/@octokit/core": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", - "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", - "license": "MIT", - "dependencies": { - "@octokit/auth-token": "^6.0.0", - "@octokit/graphql": "^9.0.3", - "@octokit/request": "^10.0.6", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "before-after-hook": "^4.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/app/node_modules/@octokit/endpoint": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz", - "integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/app/node_modules/@octokit/graphql": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", - "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", - "license": "MIT", - "dependencies": { - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/app/node_modules/@octokit/openapi-types": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", - "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", - "license": "MIT" - }, - "node_modules/@octokit/app/node_modules/@octokit/plugin-paginate-rest": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", - "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/app/node_modules/@octokit/request": { - "version": "10.0.9", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.9.tgz", - "integrity": "sha512-o8Bi3f608eyM+7BmBiUWxFsdjLb3/ym1cQek5LZOv9KkZcxRrHCPhhRzm6xjO6HVZ85ItD6+sTsjxo821SVa/A==", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^11.0.3", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "content-type": "^2.0.0", - "fast-content-type-parse": "^3.0.0", - "json-with-bigint": "^3.5.3", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/app/node_modules/@octokit/request-error": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", - "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/app/node_modules/@octokit/types": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", - "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^27.0.0" - } - }, - "node_modules/@octokit/app/node_modules/before-after-hook": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", - "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", - "license": "Apache-2.0" - }, - "node_modules/@octokit/app/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@octokit/app/node_modules/fast-content-type-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", - "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/@octokit/auth-app": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-8.2.0.tgz", - "integrity": "sha512-vVjdtQQwomrZ4V46B9LaCsxsySxGoHsyw6IYBov/TqJVROrlYdyNgw5q6tQbB7KZt53v1l1W53RiqTvpzL907g==", - "license": "MIT", - "dependencies": { - "@octokit/auth-oauth-app": "^9.0.3", - "@octokit/auth-oauth-user": "^6.0.2", - "@octokit/request": "^10.0.6", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "toad-cache": "^3.7.0", - "universal-github-app-jwt": "^2.2.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-app/node_modules/@octokit/endpoint": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz", - "integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-app/node_modules/@octokit/openapi-types": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", - "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", - "license": "MIT" - }, - "node_modules/@octokit/auth-app/node_modules/@octokit/request": { - "version": "10.0.9", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.9.tgz", - "integrity": "sha512-o8Bi3f608eyM+7BmBiUWxFsdjLb3/ym1cQek5LZOv9KkZcxRrHCPhhRzm6xjO6HVZ85ItD6+sTsjxo821SVa/A==", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^11.0.3", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "content-type": "^2.0.0", - "fast-content-type-parse": "^3.0.0", - "json-with-bigint": "^3.5.3", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-app/node_modules/@octokit/request-error": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", - "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-app/node_modules/@octokit/types": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", - "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^27.0.0" - } - }, - "node_modules/@octokit/auth-app/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@octokit/auth-app/node_modules/fast-content-type-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", - "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/@octokit/auth-oauth-app": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-9.0.3.tgz", - "integrity": "sha512-+yoFQquaF8OxJSxTb7rnytBIC2ZLbLqA/yb71I4ZXT9+Slw4TziV9j/kyGhUFRRTF2+7WlnIWsePZCWHs+OGjg==", - "license": "MIT", - "dependencies": { - "@octokit/auth-oauth-device": "^8.0.3", - "@octokit/auth-oauth-user": "^6.0.2", - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/endpoint": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz", - "integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/openapi-types": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", - "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", - "license": "MIT" - }, - "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/request": { - "version": "10.0.9", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.9.tgz", - "integrity": "sha512-o8Bi3f608eyM+7BmBiUWxFsdjLb3/ym1cQek5LZOv9KkZcxRrHCPhhRzm6xjO6HVZ85ItD6+sTsjxo821SVa/A==", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^11.0.3", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "content-type": "^2.0.0", - "fast-content-type-parse": "^3.0.0", - "json-with-bigint": "^3.5.3", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/request-error": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", - "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/types": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", - "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^27.0.0" - } - }, - "node_modules/@octokit/auth-oauth-app/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@octokit/auth-oauth-app/node_modules/fast-content-type-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", - "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/@octokit/auth-oauth-device": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-8.0.3.tgz", - "integrity": "sha512-zh2W0mKKMh/VWZhSqlaCzY7qFyrgd9oTWmTmHaXnHNeQRCZr/CXy2jCgHo4e4dJVTiuxP5dLa0YM5p5QVhJHbw==", - "license": "MIT", - "dependencies": { - "@octokit/oauth-methods": "^6.0.2", - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/endpoint": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz", - "integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/openapi-types": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", - "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", - "license": "MIT" - }, - "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/request": { - "version": "10.0.9", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.9.tgz", - "integrity": "sha512-o8Bi3f608eyM+7BmBiUWxFsdjLb3/ym1cQek5LZOv9KkZcxRrHCPhhRzm6xjO6HVZ85ItD6+sTsjxo821SVa/A==", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^11.0.3", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "content-type": "^2.0.0", - "fast-content-type-parse": "^3.0.0", - "json-with-bigint": "^3.5.3", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/request-error": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", - "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/types": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", - "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^27.0.0" - } - }, - "node_modules/@octokit/auth-oauth-device/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@octokit/auth-oauth-device/node_modules/fast-content-type-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", - "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/@octokit/auth-oauth-user": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-6.0.2.tgz", - "integrity": "sha512-qLoPPc6E6GJoz3XeDG/pnDhJpTkODTGG4kY0/Py154i/I003O9NazkrwJwRuzgCalhzyIeWQ+6MDvkUmKXjg/A==", - "license": "MIT", - "dependencies": { - "@octokit/auth-oauth-device": "^8.0.3", - "@octokit/oauth-methods": "^6.0.2", - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/endpoint": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz", - "integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/openapi-types": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", - "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", - "license": "MIT" - }, - "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/request": { - "version": "10.0.9", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.9.tgz", - "integrity": "sha512-o8Bi3f608eyM+7BmBiUWxFsdjLb3/ym1cQek5LZOv9KkZcxRrHCPhhRzm6xjO6HVZ85ItD6+sTsjxo821SVa/A==", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^11.0.3", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "content-type": "^2.0.0", - "fast-content-type-parse": "^3.0.0", - "json-with-bigint": "^3.5.3", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/request-error": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", - "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/types": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", - "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^27.0.0" - } - }, - "node_modules/@octokit/auth-oauth-user/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@octokit/auth-oauth-user/node_modules/fast-content-type-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", - "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/@octokit/auth-token": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz", - "integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==", - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-unauthenticated": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-7.0.3.tgz", - "integrity": "sha512-8Jb1mtUdmBHL7lGmop9mU9ArMRUTRhg8vp0T1VtZ4yd9vEm3zcLwmjQkhNEduKawOOORie61xhtYIhTDN+ZQ3g==", - "license": "MIT", - "dependencies": { - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-unauthenticated/node_modules/@octokit/openapi-types": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", - "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", - "license": "MIT" - }, - "node_modules/@octokit/auth-unauthenticated/node_modules/@octokit/request-error": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", - "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/auth-unauthenticated/node_modules/@octokit/types": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", - "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^27.0.0" - } - }, - "node_modules/@octokit/core": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.6.tgz", - "integrity": "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA==", - "license": "MIT", - "dependencies": { - "@octokit/auth-token": "^5.0.0", - "@octokit/graphql": "^8.2.2", - "@octokit/request": "^9.2.3", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "before-after-hook": "^3.0.2", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/endpoint": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", - "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/graphql": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.2.tgz", - "integrity": "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==", - "license": "MIT", - "dependencies": { - "@octokit/request": "^9.2.3", - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/oauth-app": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-8.0.3.tgz", - "integrity": "sha512-jnAjvTsPepyUaMu9e69hYBuozEPgYqP4Z3UnpmvoIzHDpf8EXDGvTY1l1jK0RsZ194oRd+k6Hm13oRU8EoDFwg==", - "license": "MIT", - "dependencies": { - "@octokit/auth-oauth-app": "^9.0.2", - "@octokit/auth-oauth-user": "^6.0.1", - "@octokit/auth-unauthenticated": "^7.0.2", - "@octokit/core": "^7.0.5", - "@octokit/oauth-authorization-url": "^8.0.0", - "@octokit/oauth-methods": "^6.0.1", - "@types/aws-lambda": "^8.10.83", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-app/node_modules/@octokit/auth-token": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", - "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-app/node_modules/@octokit/core": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", - "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", - "license": "MIT", - "dependencies": { - "@octokit/auth-token": "^6.0.0", - "@octokit/graphql": "^9.0.3", - "@octokit/request": "^10.0.6", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "before-after-hook": "^4.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-app/node_modules/@octokit/endpoint": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz", - "integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-app/node_modules/@octokit/graphql": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", - "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", - "license": "MIT", - "dependencies": { - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-app/node_modules/@octokit/openapi-types": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", - "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", - "license": "MIT" - }, - "node_modules/@octokit/oauth-app/node_modules/@octokit/request": { - "version": "10.0.9", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.9.tgz", - "integrity": "sha512-o8Bi3f608eyM+7BmBiUWxFsdjLb3/ym1cQek5LZOv9KkZcxRrHCPhhRzm6xjO6HVZ85ItD6+sTsjxo821SVa/A==", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^11.0.3", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "content-type": "^2.0.0", - "fast-content-type-parse": "^3.0.0", - "json-with-bigint": "^3.5.3", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-app/node_modules/@octokit/request-error": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", - "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-app/node_modules/@octokit/types": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", - "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^27.0.0" - } - }, - "node_modules/@octokit/oauth-app/node_modules/before-after-hook": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", - "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", - "license": "Apache-2.0" - }, - "node_modules/@octokit/oauth-app/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@octokit/oauth-app/node_modules/fast-content-type-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", - "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/@octokit/oauth-authorization-url": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-8.0.0.tgz", - "integrity": "sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==", - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-methods": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-6.0.2.tgz", - "integrity": "sha512-HiNOO3MqLxlt5Da5bZbLV8Zarnphi4y9XehrbaFMkcoJ+FL7sMxH/UlUsCVxpddVu4qvNDrBdaTVE2o4ITK8ng==", - "license": "MIT", - "dependencies": { - "@octokit/oauth-authorization-url": "^8.0.0", - "@octokit/request": "^10.0.6", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-methods/node_modules/@octokit/endpoint": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz", - "integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-methods/node_modules/@octokit/openapi-types": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", - "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", - "license": "MIT" - }, - "node_modules/@octokit/oauth-methods/node_modules/@octokit/request": { - "version": "10.0.9", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.9.tgz", - "integrity": "sha512-o8Bi3f608eyM+7BmBiUWxFsdjLb3/ym1cQek5LZOv9KkZcxRrHCPhhRzm6xjO6HVZ85ItD6+sTsjxo821SVa/A==", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^11.0.3", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "content-type": "^2.0.0", - "fast-content-type-parse": "^3.0.0", - "json-with-bigint": "^3.5.3", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-methods/node_modules/@octokit/request-error": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", - "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-methods/node_modules/@octokit/types": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", - "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^27.0.0" - } - }, - "node_modules/@octokit/oauth-methods/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@octokit/oauth-methods/node_modules/fast-content-type-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", - "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/@octokit/openapi-types": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", - "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", - "license": "MIT" - }, - "node_modules/@octokit/openapi-webhooks-types": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-12.1.0.tgz", - "integrity": "sha512-WiuzhOsiOvb7W3Pvmhf8d2C6qaLHXrWiLBP4nJ/4kydu+wpagV5Fkz9RfQwV2afYzv3PB+3xYgp4mAdNGjDprA==", - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-graphql": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-6.0.0.tgz", - "integrity": "sha512-crfpnIoFiBtRkvPqOyLOsw12XsveYuY2ieP6uYDosoUegBJpSVxGwut9sxUgFFcll3VTOTqpUf8yGd8x1OmAkQ==", - "license": "MIT", - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.6.0.tgz", - "integrity": "sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.10.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^24.2.0" - } - }, - "node_modules/@octokit/plugin-request-log": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-5.3.1.tgz", - "integrity": "sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.5.0.tgz", - "integrity": "sha512-9Pas60Iv9ejO3WlAX3maE1+38c5nqbJXV5GrncEfkndIpZrJ/WPMRd2xYDcPPEt5yzpxcjw9fWNoPhsSGzqKqw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.10.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "license": "MIT" - }, - "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^24.2.0" - } - }, - "node_modules/@octokit/request": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.4.tgz", - "integrity": "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^10.1.4", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "fast-content-type-parse": "^2.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/request-error": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", - "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/rest": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-21.1.1.tgz", - "integrity": "sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg==", + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "license": "MIT", "dependencies": { - "@octokit/core": "^6.1.4", - "@octokit/plugin-paginate-rest": "^11.4.2", - "@octokit/plugin-request-log": "^5.3.1", - "@octokit/plugin-rest-endpoint-methods": "^13.3.0" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" }, "engines": { - "node": ">= 18" + "node": ">= 8" } }, - "node_modules/@octokit/types": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", - "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^25.1.0" + "engines": { + "node": ">= 8" } }, - "node_modules/@octokit/webhooks": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-14.2.0.tgz", - "integrity": "sha512-da6KbdNCV5sr1/txD896V+6W0iamFWrvVl8cHkBSPT+YlvmT3DwXa4jxZnQc+gnuTEqSWbBeoSZYTayXH9wXcw==", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, "license": "MIT", "dependencies": { - "@octokit/openapi-webhooks-types": "12.1.0", - "@octokit/request-error": "^7.0.0", - "@octokit/webhooks-methods": "^6.0.0" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" }, "engines": { - "node": ">= 20" + "node": ">= 8" } }, - "node_modules/@octokit/webhooks-methods": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-6.0.0.tgz", - "integrity": "sha512-MFlzzoDJVw/GcbfzVC1RLR36QqkTLUf79vLVO3D+xn7r0QgxnFoLZgtrzxiQErAjFUOdH6fas2KeQJ1yr/qaXQ==", - "license": "MIT", - "engines": { - "node": ">= 20" + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" } }, - "node_modules/@octokit/webhooks/node_modules/@octokit/openapi-types": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", - "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", - "license": "MIT" - }, - "node_modules/@octokit/webhooks/node_modules/@octokit/request-error": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", - "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@octokit/types": "^16.0.0" + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" }, "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/webhooks/node_modules/@octokit/types": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", - "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^27.0.0" + "node": ">=10" } }, "node_modules/@protobufjs/aspromise": { @@ -3524,12 +2385,6 @@ "node": ">= 6" } }, - "node_modules/@types/aws-lambda": { - "version": "8.10.161", - "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.161.tgz", - "integrity": "sha512-rUYdp+MQwSFocxIOcSsYSF3YYYC/uUpMbCY/mbO21vGqfrEYvNSoPyKYDj6RhXXpPfS0KstW9RwG3qXh9sL7FQ==", - "license": "MIT" - }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -3631,13 +2486,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "18.19.130", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", - "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": ">=7.24.0 <7.24.7" } }, "node_modules/@types/node-fetch": { @@ -4881,12 +3736,6 @@ "tweetnacl": "^0.14.3" } }, - "node_modules/before-after-hook": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", - "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", - "license": "Apache-2.0" - }, "node_modules/big-integer": { "version": "1.6.52", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", @@ -4945,12 +3794,6 @@ "dev": true, "license": "ISC" }, - "node_modules/bottleneck": { - "version": "2.19.5", - "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", - "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", - "license": "MIT" - }, "node_modules/bowser": { "version": "2.14.1", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", @@ -5660,6 +4503,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 12" @@ -6946,22 +5790,6 @@ ], "license": "MIT" }, - "node_modules/fast-content-type-parse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", - "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -7101,6 +5929,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, "funding": [ { "type": "github", @@ -7368,6 +6197,7 @@ "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, "license": "MIT", "dependencies": { "fetch-blob": "^3.1.2" @@ -9043,12 +7873,6 @@ "lodash": "^4.16.4" } }, - "node_modules/json-with-bigint": { - "version": "3.5.8", - "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.8.tgz", - "integrity": "sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==", - "license": "MIT" - }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -9776,6 +8600,7 @@ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", "deprecated": "Use your platform's native DOMException instead", + "dev": true, "funding": [ { "type": "github", @@ -9826,6 +8651,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, "license": "MIT", "dependencies": { "data-uri-to-buffer": "^4.0.0", @@ -10101,225 +8927,6 @@ "dev": true, "license": "MIT" }, - "node_modules/octokit": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/octokit/-/octokit-5.0.5.tgz", - "integrity": "sha512-4+/OFSqOjoyULo7eN7EA97DE0Xydj/PW5aIckxqQIoFjFwqXKuFCvXUJObyJfBF9Khu4RL/jlDRI9FPaMGfPnw==", - "license": "MIT", - "dependencies": { - "@octokit/app": "^16.1.2", - "@octokit/core": "^7.0.6", - "@octokit/oauth-app": "^8.0.3", - "@octokit/plugin-paginate-graphql": "^6.0.0", - "@octokit/plugin-paginate-rest": "^14.0.0", - "@octokit/plugin-rest-endpoint-methods": "^17.0.0", - "@octokit/plugin-retry": "^8.0.3", - "@octokit/plugin-throttling": "^11.0.3", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "@octokit/webhooks": "^14.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/octokit/node_modules/@octokit/auth-token": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", - "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/octokit/node_modules/@octokit/core": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", - "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", - "license": "MIT", - "dependencies": { - "@octokit/auth-token": "^6.0.0", - "@octokit/graphql": "^9.0.3", - "@octokit/request": "^10.0.6", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "before-after-hook": "^4.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/octokit/node_modules/@octokit/endpoint": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz", - "integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/octokit/node_modules/@octokit/graphql": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", - "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", - "license": "MIT", - "dependencies": { - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/octokit/node_modules/@octokit/openapi-types": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", - "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", - "license": "MIT" - }, - "node_modules/octokit/node_modules/@octokit/plugin-paginate-rest": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", - "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/octokit/node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz", - "integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/octokit/node_modules/@octokit/plugin-retry": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.1.0.tgz", - "integrity": "sha512-O1FZgXeiGb2sowEr/hYTr6YunGdSAFWnr2fyW39Ah85H8O33ELASQxcvOFF5LE6Tjekcyu2ms4qAzJVhSaJxTw==", - "license": "MIT", - "dependencies": { - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "bottleneck": "^2.15.3" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=7" - } - }, - "node_modules/octokit/node_modules/@octokit/plugin-throttling": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-11.0.3.tgz", - "integrity": "sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0", - "bottleneck": "^2.15.3" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": "^7.0.0" - } - }, - "node_modules/octokit/node_modules/@octokit/request": { - "version": "10.0.9", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.9.tgz", - "integrity": "sha512-o8Bi3f608eyM+7BmBiUWxFsdjLb3/ym1cQek5LZOv9KkZcxRrHCPhhRzm6xjO6HVZ85ItD6+sTsjxo821SVa/A==", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^11.0.3", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "content-type": "^2.0.0", - "fast-content-type-parse": "^3.0.0", - "json-with-bigint": "^3.5.3", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/octokit/node_modules/@octokit/request-error": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", - "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/octokit/node_modules/@octokit/types": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", - "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^27.0.0" - } - }, - "node_modules/octokit/node_modules/before-after-hook": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", - "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", - "license": "Apache-2.0" - }, - "node_modules/octokit/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/octokit/node_modules/fast-content-type-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", - "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, "node_modules/ollama": { "version": "0.4.9", "resolved": "https://registry.npmjs.org/ollama/-/ollama-0.4.9.tgz", @@ -10466,6 +9073,16 @@ } } }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/openai/node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -10494,6 +9111,13 @@ "dev": true, "license": "MIT" }, + "node_modules/openai/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, "node_modules/openai/node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -13262,15 +11886,6 @@ "node": ">=8.0" } }, - "node_modules/toad-cache": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.1.tgz", - "integrity": "sha512-5DXWzE4Vz7xNHsv+xQ+MGfJYyC78Aok3tEr0MNwHoRf7vZnga1mQXZ4/Nsodld4VR6Wd+VhfmqnNrsRJyYPfrQ==", - "license": "MIT", - "engines": { - "node": ">=20" - } - }, "node_modules/tough-cookie": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", @@ -13599,9 +12214,9 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "dev": true, "license": "MIT" }, @@ -13627,18 +12242,6 @@ "imurmurhash": "^0.1.4" } }, - "node_modules/universal-github-app-jwt": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.2.tgz", - "integrity": "sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==", - "license": "MIT" - }, - "node_modules/universal-user-agent": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", - "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", - "license": "ISC" - }, "node_modules/universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", @@ -13852,18 +12455,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/vite-node/node_modules/@types/node": { - "version": "25.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", - "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "undici-types": ">=7.24.0 <7.24.7" - } - }, "node_modules/vite-node/node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -13895,15 +12486,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/vite-node/node_modules/undici-types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/vite-node/node_modules/vite": { "version": "7.3.3", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz", @@ -14378,6 +12960,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -14840,6 +13423,7 @@ "version": "8.20.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" @@ -14930,18 +13514,6 @@ "funding": { "url": "https://github.com/sponsors/eemeli" } - }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } } } } diff --git a/package.json b/package.json index a44c8409..e2115c5c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-db2i", "displayName": "Db2 for IBM i", "description": "Db2 for IBM i tools in VS Code", - "version": "1.17.0", + "version": "2.0.0-dev.0", "engines": { "vscode": "^1.95.0" }, @@ -33,11 +33,10 @@ "main": "./dist/extension.js", "scripts": { "test": "vitest", - "dsc": "npx tsx src/dsc", "package": "vsce package", - "vscode:prepublish": "rm -rf dist && npm run webpack && npm run dsc", - "webpack": "vscd --clean && webpack --mode development", - "webpack-dev": "vscd --clean && webpack --mode development", + "vscode:prepublish": "npm run webpack", + "webpack": "vscd --clean && webpack --mode production", + "webpack-dev": "vscd --clean && webpack --mode development --watch", "typings": "npx -p typescript tsc ./src/extension.ts --excludeDirectories '**/node_modules' --allowJs --noEmit --esModuleInterop -t es2019 --moduleResolution node --skipLibCheck" }, "contributes": { @@ -1914,9 +1913,10 @@ }, "devDependencies": { "@continuedev/core": "^1.0.13", - "@halcyontech/vscode-ibmi-types": "^2.17.0", + "@halcyontech/vscode-ibmi-types": "^3.0.9", + "@ibm/mapepire-js": "^0.5.0", "@types/glob": "^7.1.3", - "@types/node": "18.x", + "@types/node": "25.x", "@types/vscode": "^1.95.0", "esbuild-loader": "^4.3.0", "glob": "^7.1.7", @@ -1930,14 +1930,10 @@ "webpack-cli": "^4.5.0" }, "dependencies": { - "@ibm/mapepire-js": "^0.5.0", - "@octokit/rest": "^21.1.1", "chart.js": "^4.4.2", "csv": "^6.1.3", "json-to-markdown-table": "^1.0.0", "lru-cache": "^6.0.0", - "node-fetch": "^3.3.1", - "octokit": "^5.0.3", "showdown": "^2.1.0", "sql-formatter": "^14.0.0" } diff --git a/src/IBMiDetail.ts b/src/IBMiDetail.ts index 00a47a4d..67a442eb 100644 --- a/src/IBMiDetail.ts +++ b/src/IBMiDetail.ts @@ -1,19 +1,19 @@ import { commands } from "vscode"; import { getInstance } from "./base"; -import { ServerComponent } from "./connection/serverComponent"; +import { Db2foriOutput } from "./extension"; export type Db2FeatureIds = `SELF`; -const featureRequirements: { [id in Db2FeatureIds]: { [osVersion: number]: number } } = { +const featureRequirements: { [id in Db2FeatureIds]: { [osVersion: string]: number } } = { 'SELF': { - 7.4: 26, - 7.5: 5, - 7.6: 1 + '7.4': 26, + '7.5': 5, + '7.6': 1 } }; export class IBMiDetail { - private version: number = 0; + private version = ''; private db2Level: number = 0; private features: { [id in Db2FeatureIds]: boolean } = { 'SELF': false @@ -42,10 +42,10 @@ export class IBMiDetail { const connection = getInstance().getConnection(); let levelCheckFailed = false; - + const versionResults = await connection.runSQL(`select OS_VERSION concat '.' concat OS_RELEASE as VERSION from sysibmadm.env_sys_info`); - this.version = Number(versionResults[0].VERSION); - + this.version = String(versionResults[0].VERSION); + try { const db2LevelResults = await connection.runSQL([ `select max(ptf_group_level) as HIGHEST_DB2_PTF_GROUP_LEVEL`, @@ -53,16 +53,16 @@ export class IBMiDetail { `where PTF_GROUP_DESCRIPTION like 'DB2 FOR IBM I%' and`, `ptf_group_status = 'INSTALLED';` ].join(` `)); - + this.db2Level = Number(db2LevelResults[0].HIGHEST_DB2_PTF_GROUP_LEVEL); - } catch (e) { - ServerComponent.writeOutput(`Failed to get Db2 level. User does not have enough authority: ${e.message}`); + } catch (e : any) { + Db2foriOutput.writeOutput(`Failed to get Db2 level. User does not have enough authority: ${e.message}`); levelCheckFailed = true; } for (const featureId of features) { - const requiredLevelForFeature = featureRequirements[featureId][String(this.version)]; - const supported = requiredLevelForFeature && this.db2Level >= requiredLevelForFeature; + const requiredLevelForFeature = featureRequirements[featureId][this.version]; + const supported = requiredLevelForFeature !== undefined && this.db2Level >= requiredLevelForFeature; this.setFeatureSupport(featureId, supported); } diff --git a/src/Storage.ts b/src/Storage.ts index 0933ec94..8b66300b 100644 --- a/src/Storage.ts +++ b/src/Storage.ts @@ -1,6 +1,5 @@ import vscode from 'vscode'; -const SERVERCOMPONENT_KEY = `serverVersion`; const QUERIES_KEY = `queries`; const START_UP_CONFIGS_KEY = `startUpConfigs`; @@ -61,14 +60,6 @@ export class ConnectionStorage extends Storage { return `${this.connectionName}.${key}`; } - getServerComponentName(): string|undefined { - return this.get(SERVERCOMPONENT_KEY); - } - - setServerComponentName(name: string) { - return this.set(SERVERCOMPONENT_KEY, name); - } - getPastQueries() { return this.get(QUERIES_KEY) || []; } diff --git a/src/aiProviders/context.ts b/src/aiProviders/context.ts index 37b4dcb5..054f293f 100644 --- a/src/aiProviders/context.ts +++ b/src/aiProviders/context.ts @@ -1,23 +1,15 @@ import { ContextItem } from "@continuedev/core"; -import * as vscode from "vscode"; import { JobManager } from "../config"; -import Schemas, { AllSQLTypes, SQLType } from "../database/schemas"; +import Schemas, { AllSQLTypes } from "../database/schemas"; import Statement from "../database/statement"; -import { DB2_SYSTEM_PROMPT } from "./prompts"; +import { BasicSQLObject, ResolvedSqlObject, TableColumn } from "../types"; import { Db2ContextItems } from "./prompt"; -import { TableColumn, BasicSQLObject, ResolvedSqlObject } from "../types"; +import { DB2_SYSTEM_PROMPT } from "./prompts"; export function canTalkToDb() { return JobManager.getSelection() !== undefined; } -export function getCurrentSchema(): string { - const currentJob = JobManager.getSelection(); - return currentJob && currentJob.job.options.libraries[0] - ? currentJob.job.options.libraries[0] - : `QGPL`; -} - export type TableRefs = { [key: string]: TableColumn[] }; interface MarkdownRef { TABLE_NAME: string; @@ -149,7 +141,7 @@ function splitUpUserInput(input: string): string[] { break; } } - + addPart(); return parts; @@ -163,7 +155,7 @@ function splitUpUserInput(input: string): string[] { * * @param {string} input - A string that may contain table references. */ -export async function getSqlContextItems(input: string): Promise<{items: Db2ContextItems[], refs: ResolvedSqlObject[]}> { +export async function getSqlContextItems(input: string): Promise<{ items: Db2ContextItems[], refs: ResolvedSqlObject[] }> { // Parse all SCHEMA.TABLE references first const tokens = splitUpUserInput(input); @@ -174,11 +166,11 @@ export async function getSqlContextItems(input: string): Promise<{items: Db2Cont .map((word) => word.slice(0, -1)) ); - let possibleRefs: {name: string, schema?: string}[] = []; + let possibleRefs: { name: string, schema?: string }[] = []; for (let i = 0; i < tokens.length; i++) { const token = tokens[i]; - - if (tokens[i+1] && [`.`, `/`].includes(tokens[i+1]) && tokens[i + 2]) { + + if (tokens[i + 1] && [`.`, `/`].includes(tokens[i + 1]) && tokens[i + 2]) { const nextToken = tokens[i + 2]; possibleRefs.push({ @@ -205,7 +197,7 @@ export async function getSqlContextItems(input: string): Promise<{items: Db2Cont } export async function getContentItemsForRefs(allObjects: ResolvedSqlObject[]): Promise { - const items: (Db2ContextItems|undefined)[] = await Promise.all( + const items: (Db2ContextItems | undefined)[] = await Promise.all( allObjects.map(async (o) => { try { if (o.sqlType === `SCHEMA`) { @@ -270,15 +262,12 @@ export function refsToMarkdown(refs: TableRefs): MarkdownRef[] { COLUMN_INFO: refs[tableName] .map((column) => { const lengthPrecision = column.CHARACTER_MAXIMUM_LENGTH - ? `(${column.CHARACTER_MAXIMUM_LENGTH}${ - column.NUMERIC_PRECISION ? `:${column.NUMERIC_PRECISION}` : `` - })` + ? `(${column.CHARACTER_MAXIMUM_LENGTH}${column.NUMERIC_PRECISION ? `:${column.NUMERIC_PRECISION}` : `` + })` : ``; - return `${column.COLUMN_NAME}${ - column.COLUMN_TEXT ? ` - ${column.COLUMN_TEXT}` : `` - } ${column.DATA_TYPE}${lengthPrecision} is_identity: ${ - column.IS_IDENTITY - } is_nullable: ${column.IS_NULLABLE}`; + return `${column.COLUMN_NAME}${column.COLUMN_TEXT ? ` - ${column.COLUMN_TEXT}` : `` + } ${column.DATA_TYPE}${lengthPrecision} is_identity: ${column.IS_IDENTITY + } is_nullable: ${column.IS_NULLABLE}`; }) .join(`\n`), }; @@ -291,22 +280,24 @@ export function refsToMarkdown(refs: TableRefs): MarkdownRef[] { export function createContinueContextItems(refs: MarkdownRef[]) { const contextItems: ContextItem[] = []; const job = JobManager.getSelection(); - if (refs.length === 0) { - contextItems.push({ - name: `SYSTEM PROMPT`, - description: `system prompt context`, - content: DB2_SYSTEM_PROMPT + `\n\nNo references found`, - }); - } else { - for (const tableRef of refs) { - let prompt = `Table: ${tableRef.TABLE_NAME} (Schema: ${tableRef.SCHMEA}) Column Information:\n`; - prompt += `Format: column_name (column_text) type(length:precision) is_identity is_nullable\n`; - prompt += `${tableRef.COLUMN_INFO}`; + if (job) { + if (refs.length === 0) { contextItems.push({ - name: `${job.name}-${tableRef.SCHMEA}-${tableRef.TABLE_NAME}`, - description: `Column information for ${tableRef.TABLE_NAME}`, - content: DB2_SYSTEM_PROMPT + prompt, + name: `SYSTEM PROMPT`, + description: `system prompt context`, + content: DB2_SYSTEM_PROMPT + `\n\nNo references found`, }); + } else { + for (const tableRef of refs) { + let prompt = `Table: ${tableRef.TABLE_NAME} (Schema: ${tableRef.SCHMEA}) Column Information:\n`; + prompt += `Format: column_name (column_text) type(length:precision) is_identity is_nullable\n`; + prompt += `${tableRef.COLUMN_INFO}`; + contextItems.push({ + name: `${job.name}-${tableRef.SCHMEA}-${tableRef.TABLE_NAME}`, + description: `Column information for ${tableRef.TABLE_NAME}`, + content: DB2_SYSTEM_PROMPT + prompt, + }); + } } } diff --git a/src/aiProviders/continue/continueContextProvider.ts b/src/aiProviders/continue/continueContextProvider.ts index 0a3f4cdb..fafac1a8 100644 --- a/src/aiProviders/continue/continueContextProvider.ts +++ b/src/aiProviders/continue/continueContextProvider.ts @@ -11,13 +11,10 @@ import { JobManager } from "../../config"; import { JobInfo } from "../../connection/manager"; import { SelfCodeNode } from "../../views/jobManager/selfCodes/nodes"; import { - buildSchemaDefinition, - canTalkToDb, - getSqlContextItems + canTalkToDb } from "../context"; -import { DB2_SELF_PROMPT, DB2_SYSTEM_PROMPT } from "../prompts"; -import Configuration from "../../configuration"; import { getContextItems } from "../prompt"; +import { DB2_SELF_PROMPT } from "../prompts"; export let isContinueActive = false; @@ -38,16 +35,10 @@ export class db2ContextProvider implements IContextProvider { return db2ContextProviderDesc; } - getCurrentJob(): JobInfo { - const currentJob: JobInfo = JobManager.getSelection(); - return currentJob; + getCurrentJob() { + return JobManager.getSelection(); } - private getDefaultSchema = (): string => { - const currentJob: JobInfo = this.getCurrentJob(); - return currentJob?.job.options.libraries[0] || "QGPL"; - }; - tryParseJson(row: SelfCodeNode) { try { return JSON.parse(row.INITIAL_STACK as unknown as string); @@ -142,7 +133,7 @@ export class db2ContextProvider implements IContextProvider { const job = this.getCurrentJob(); const fullInput = extras.fullInput; - if (job && fullInput.includes(`*SELF`) || query?.includes(`*SELF`)) { + if (job && (fullInput.includes(`*SELF`) || query?.includes(`*SELF`))) { // get current self code errors in job // build promt with error information // add to contextItems diff --git a/src/aiProviders/continue/listTablesContextProvider.ts b/src/aiProviders/continue/listTablesContextProvider.ts index 406cd1bb..627fc99f 100644 --- a/src/aiProviders/continue/listTablesContextProvider.ts +++ b/src/aiProviders/continue/listTablesContextProvider.ts @@ -7,13 +7,14 @@ import { LoadSubmenuItemsArgs, } from "@continuedev/core"; import * as vscode from "vscode"; +import Configuration from "../../configuration"; import Schemas from "../../database/schemas"; import Table from "../../database/table"; +import { BasicSQLObject, TableColumn } from "../../types"; import { - buildSchemaDefinition} from "../context"; -import Configuration from "../../configuration"; + buildSchemaDefinition +} from "../context"; import { getContextItems } from "../prompt"; -import { TableColumn, BasicSQLObject } from "../../types"; const listDb2Table: ContextProviderDescription = { title: "list Db2i Tables", @@ -69,7 +70,7 @@ class ListDb2iTables implements IContextProvider { let contextItems: ContextItem[] = []; if (query.toUpperCase() === this.schema.toUpperCase()) { - const useSchemaDef: boolean = Configuration.get(`ai.useSchemaDefinition`); + const useSchemaDef = Configuration.get(`ai.useSchemaDefinition`); if (useSchemaDef) { const schemaSemantic = await buildSchemaDefinition(this.schema); if (schemaSemantic) { @@ -124,8 +125,8 @@ export async function registerDb2iTablesProvider(schema?: string) { await continueEx.activate(); } - const existingProvider: SchemaContextProvider = providers.find(p => p.schema === schema); - if (existingProvider !== undefined) { + const existingProvider = providers.find(p => p.schema === schema); + if (existingProvider) { return; } else { const continueAPI = continueEx?.exports; diff --git a/src/aiProviders/copilot/index.ts b/src/aiProviders/copilot/index.ts index 6b1958ed..b67fb384 100644 --- a/src/aiProviders/copilot/index.ts +++ b/src/aiProviders/copilot/index.ts @@ -2,7 +2,7 @@ import * as vscode from "vscode"; import { canTalkToDb, } from "../context"; -import { getContextItems, Db2ContextItems } from "../prompt"; +import { getContextItems } from "../prompt"; import { registerSqlRunTool, RUN_SQL_TOOL_ID } from "./sqlTool"; const CHAT_ID = `vscode-db2i.chat`; @@ -106,7 +106,7 @@ export function activateChat(context: vscode.ExtensionContext) { let result = await doRequest(tools); // Then, if there is a tool request, we do the logic to invoke the tool - if (result.toolCalls.length > 0) { + if (result && result.toolCalls.length > 0) { for (const toolcall of result.toolCalls) { if (toolcall.name === RUN_SQL_TOOL_ID) { const result = await vscode.lm.invokeTool(toolcall.name, { toolInvocationToken: request.toolInvocationToken, input: toolcall.input }); @@ -125,7 +125,7 @@ export function activateChat(context: vscode.ExtensionContext) { result = await doRequest(); } - return { metadata: { command: "build", followUps: contextItems.followUps, statement: result.sqlCodeBlock } }; + return { metadata: { command: "build", followUps: contextItems.followUps, statement: result?.sqlCodeBlock } }; } } else { throw new Error( diff --git a/src/aiProviders/prompt.ts b/src/aiProviders/prompt.ts index d46eb0e3..f4d9aa22 100644 --- a/src/aiProviders/prompt.ts +++ b/src/aiProviders/prompt.ts @@ -1,9 +1,8 @@ import { JobManager } from "../config"; import Configuration from "../configuration"; -import { JobInfo } from "../connection/manager"; import Schemas from "../database/schemas"; import Statement from "../database/statement"; -import { buildSchemaDefinition, canTalkToDb, getContentItemsForRefs, getSqlContextItems } from "./context"; +import { buildSchemaDefinition, getContentItemsForRefs, getSqlContextItems } from "./context"; import { DB2_SYSTEM_PROMPT } from "./prompts"; export interface PromptOptions { @@ -23,7 +22,7 @@ export interface BuildResult { } export async function getContextItems(input: string, options: PromptOptions = {}): Promise { - const currentJob: JobInfo = JobManager.getSelection(); + const currentJob = JobManager.getSelection(); let contextItems: Db2ContextItems[] = []; let followUps = []; @@ -35,8 +34,8 @@ export async function getContextItems(input: string, options: PromptOptions = {} }; if (currentJob) { - const currentSchema = currentJob?.job.options.libraries[0] || "QGPL"; - const useSchemaDef: boolean = Configuration.get(`ai.useSchemaDefinition`); + const currentSchema = await currentJob?.job.getCurrentSchema(); + const useSchemaDef = Configuration.get(`ai.useSchemaDefinition`); // TODO: self? diff --git a/src/base.ts b/src/base.ts index 86971f6d..a108673e 100644 --- a/src/base.ts +++ b/src/base.ts @@ -1,28 +1,39 @@ import { CodeForIBMi } from "@halcyontech/vscode-ibmi-types"; import Instance from "@halcyontech/vscode-ibmi-types/Instance"; -import { Extension, ExtensionContext, extensions } from "vscode"; -import { SQLStatementChecker } from "./connection/syntaxChecker"; +import { VscodeTools } from "@halcyontech/vscode-ibmi-types/ui/Tools"; +import { ExtensionContext, extensions } from "vscode"; +import { CheckStatementComponent } from "./connection/components/checkStatement"; +import { ValidateStatementComponent } from "./connection/components/validateStatement"; -let baseExtension: Extension|undefined; +let baseExtension: CodeForIBMi; -export function loadBase(context: ExtensionContext): CodeForIBMi|undefined { - if (!baseExtension) { - baseExtension = (extensions ? extensions.getExtension(`halcyontechltd.code-for-ibmi`) : undefined); - - if (baseExtension) { - baseExtension.activate().then(() => { - baseExtension.exports.componentRegistry.registerComponent(context, new SQLStatementChecker()); - }); - } +export async function loadBase(context: ExtensionContext) { + const code4iExtension = extensions.getExtension(`halcyontechltd.code-for-ibmi`); + if (code4iExtension) { + baseExtension = code4iExtension.isActive ? code4iExtension.exports : await code4iExtension.activate(); + + const componentRegistry = baseExtension.componentRegistry; + componentRegistry.registerComponent(context, new ValidateStatementComponent()); + componentRegistry.registerComponent(context, new CheckStatementComponent()); + } + else { + //This cannot happen since the dependency is in package.json + throw new Error(`${context.extension.id} requires halcyontechltd.code-for-ibmi extension`); } - - return (baseExtension && baseExtension.isActive && baseExtension.exports ? baseExtension.exports : undefined); } -export function getBase(): CodeForIBMi|undefined { - return (baseExtension && baseExtension.isActive && baseExtension.exports ? baseExtension.exports : undefined); +export function getBase(): CodeForIBMi { + return baseExtension; +} + +export function getInstance(): Instance { + return baseExtension.instance; } -export function getInstance(): Instance|undefined { - return (baseExtension && baseExtension.isActive && baseExtension.exports ? baseExtension.exports.instance : undefined); +/** + * Get the VS Code tools from the base extension + * @returns The VscodeTools if available, undefined otherwise + */ +export function getVSCodeTools(): typeof VscodeTools { + return baseExtension.tools; } \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index 001c642d..db1b30af 100644 --- a/src/config.ts +++ b/src/config.ts @@ -4,7 +4,6 @@ import { ConnectionStorage } from "./Storage"; import { getInstance } from "./base"; import Configuration from "./configuration"; import { SQLJobManager } from "./connection/manager"; -import { ServerComponent } from "./connection/serverComponent"; import { Examples, ServiceInfoLabel } from "./views/examples"; import { ConfigManager } from "./views/jobManager/ConfigManager"; import { JobManagerView } from "./views/jobManager/jobManagerView"; @@ -12,28 +11,18 @@ import { updateStatusBar } from "./views/jobManager/statusBar"; export let Config: ConnectionStorage; export let osDetail: IBMiDetail; -export let JobManager: SQLJobManager = new SQLJobManager(); +export const JobManager: SQLJobManager = new SQLJobManager(); export async function onConnectOrServerInstall(): Promise { const instance = getInstance(); - Config.setConnectionName(instance.getConnection().currentConnectionName); - osDetail = new IBMiDetail(); - await osDetail.fetchSystemInfo(); - await ServerComponent.initialise().then(installed => { - if (installed) { - JobManagerView.setVisible(true); - } - }); - - await ServerComponent.checkForUpdate(); - + JobManagerView.setVisible(true); + updateStatusBar(); - if (ServerComponent.isInstalled()) { JobManagerView.setVisible(true); let newJob = Configuration.get(`alwaysStartSQLJob`) || `ask`; @@ -58,7 +47,6 @@ export async function onConnectOrServerInstall(): Promise { return true; } break; - } } return false; } diff --git a/src/connection/SCVersion.ts b/src/connection/SCVersion.ts deleted file mode 100644 index c6497ff4..00000000 --- a/src/connection/SCVersion.ts +++ /dev/null @@ -1,4 +0,0 @@ - -export const VERSION = `2.3.5`; -export const SERVER_VERSION_TAG = `v${VERSION}`; -export const SERVER_VERSION_FILE = `mapepire-server-${VERSION}.jar`; diff --git a/src/connection/components/checkStatement.ts b/src/connection/components/checkStatement.ts new file mode 100644 index 00000000..e3365757 --- /dev/null +++ b/src/connection/components/checkStatement.ts @@ -0,0 +1,91 @@ +import { IBMiComponent, SecureComponentState } from "@halcyontech/vscode-ibmi-types/api/components/component"; +import IBMi from "@halcyontech/vscode-ibmi-types/api/IBMi"; +import { posix } from "path"; +import { getVSCodeTools } from "../../base"; + +export class CheckStatementComponent implements IBMiComponent { + static ID = "CheckStatementComponent"; + private static readonly VERSION = 1; + static readonly FUNCTION_NAME = `CHKSTMNT${CheckStatementComponent.VERSION.toString().padStart(4, "0")}`; + private static readonly SIGNATURE = "7CDD7F08E0CE36EF271A81197C2D770CBBD3A3AA2E02D3217421995EA555A7F0"; + private static readonly VALID_STATEMENT_LENGTH = 32740; + private static readonly TYPE = "PROCEDURE"; + + private getLibrary(connection: IBMi) { + return connection?.getConfig()?.tempLibrary.toUpperCase() || `ILEDITOR`; + } + + getIdentification() { + return { + name: CheckStatementComponent.ID, + version: CheckStatementComponent.VERSION, + signature: CheckStatementComponent.SIGNATURE, + }; + } + + async getRemoteState(connection: IBMi, installDirectory: string): Promise { + const remoteSignature = await this.getSQLRoutineSignature( + connection, + this.getLibrary(connection), + CheckStatementComponent.FUNCTION_NAME, + CheckStatementComponent.TYPE, + ); + return { + status: remoteSignature ? "Installed" : "NotInstalled", + remoteSignature: remoteSignature, + }; + } + + /** + * Custom implementation that uses EXTERNAL_NAME instead of ROUTINEDEF + */ + private async getSQLRoutineSignature(connection: IBMi, library: string, name: string, type: "PROCEDURE" | "FUNCTION") { + return (await connection.runSQL( + /* sql */`select HASH_SHA256(EXTERNAL_NAME) SIGNATURE from qsys2.sysroutines where routine_type = '${type}' and rtnschema = '${library}' and RTNNAME = '${name}' fetch first row only` + )).pop()?.SIGNATURE as string; + } + + async update(connection: IBMi, installDirectory: string): Promise { + return connection.withTempDirectory(async tempDir => { + const tempSourcePath = getVSCodeTools()?.ensureFullPath(posix.join(tempDir, `sqlchecker.sql`), connection?.getConfig()?.homeDirectory); + const srcPath = tempSourcePath ? tempSourcePath : ''; + const library = this.getLibrary(connection); + await connection.getContent().writeStreamfileRaw(srcPath, this.getSource(library, CheckStatementComponent.FUNCTION_NAME, CheckStatementComponent.VERSION)); + const result = await connection.runCommand({ + command: `QSYS/RUNSQLSTM SRCSTMF('${tempSourcePath}') COMMIT(*NONE) NAMING(*SYS) DFTRDBCOL(${library})`, + cwd: `/`, + noLibList: true, + getSpooledFiles: true + }); + + if (result.code !== 0) { + throw Error(result.stderr || result.stdout); + } + + return this.getRemoteState(connection, installDirectory); + }); + } + + private getSource(library: string, name: string, version: number) { + return /*sql*/` + create or replace procedure ${library}.${name} ( + IN statementText char(${CheckStatementComponent.VALID_STATEMENT_LENGTH}) FOR SBCS DATA, + IN statementLength int, + IN recordsProvided int, + IN statementLanguage char(10), + IN options char(24), + OUT statementInfo char(1000), + IN statementInfoLength int, + OUT recordsProcessed int, + OUT errorCode char(1000) + ) + LANGUAGE RPGLE + NOT DETERMINISTIC + MODIFIES SQL DATA + EXTERNAL NAME QSYS/QSQCHKS + PARAMETER STYLE GENERAL; + + comment on procedure ${library}/${name} is '${version} - QSQCHKS Wrapper'; + `; + } +} diff --git a/src/connection/components/validateStatement.ts b/src/connection/components/validateStatement.ts new file mode 100644 index 00000000..e84c4ffc --- /dev/null +++ b/src/connection/components/validateStatement.ts @@ -0,0 +1,284 @@ +import { IBMiComponent, SecureComponentState } from "@halcyontech/vscode-ibmi-types/api/components/component"; +import IBMi from "@halcyontech/vscode-ibmi-types/api/IBMi"; +import { posix } from "path"; +import { getInstance } from "../../base"; +import { JobInfo } from "../manager"; +import { CheckStatementComponent } from "./checkStatement"; + +export const VALID_STATEMENT_LENGTH = 32740; +export const MAX_STATEMENT_COUNT = 200; + +interface SqlCheckError { + CURSTMTLENGTH: number; + ERRORFIRSTCOLUMNNUMBER: number; + ERRORFIRSTRECORDNUMBER: number; + ERRORLASTCOLUMNNUMBER: number; + ERRORLASTRECORDNUMBER: number; + ERRORREPLACEMENTTEXT: string; + ERRORSQLMESSAGEID: string; + ERRORSQLSTATE: string; + ERRORSYNTAXCOLUMNNUMBER: number; + ERRORSYNTAXRECORDNUMBER: number; + MESSAGEFILELIBRARY: string; + MESSAGEFILENAME: string; + MESSAGETEXT: string; + NUMBEROFSTATEMENTSBACK: number; +} + +type SqlErrorType = "error" | "warning" | "none"; + +const ERROR_STATE_MAP: { [state: string]: SqlErrorType } = { + '00': 'none', + '01': 'warning', +} + +export interface SqlSyntaxError { + type: SqlErrorType; + sqlid: string; + sqlstate: string; + text: string; + offset: number; +} + +export class ValidateStatementComponent implements IBMiComponent { + static ID = "ValidateStatement"; + private static readonly VERSION = 1; + private static readonly SIGNATURE = "4DA5046C8080EC338A7516B9589CACDBED20A44C7D4BAFFEEC605FFB7C2EDD47"; + private static readonly FUNCTION_NAME = `VALIDATE_STATEMENT${ValidateStatementComponent.VERSION.toString().padStart(4, "0")}`; + + static async get(): Promise { + return await getInstance()?.getConnection()?.getComponent(ValidateStatementComponent.ID); + } + + private getLibrary(connection: IBMi) { + return connection?.getConfig()?.tempLibrary.toUpperCase() || `ILEDITOR`; + } + + getIdentification() { + return { + name: ValidateStatementComponent.ID, + version: ValidateStatementComponent.VERSION, + signature: ValidateStatementComponent.SIGNATURE, + }; + } + + async getRemoteState(connection: IBMi, installDirectory: string): Promise { + const remoteSignature = await connection.getContent().getSQLRoutineSignature( + this.getLibrary(connection), + ValidateStatementComponent.FUNCTION_NAME, + "FUNCTION" + ); + return { + status: remoteSignature ? "Installed" : "NotInstalled", + remoteSignature: remoteSignature, + }; + } + + async update(connection: IBMi, installDirectory: string): Promise { + return connection.withTempDirectory(async tempDir => { + const tempSourcePath = posix.join(tempDir, `sqlvalidator.sql`); + const srcPath = tempSourcePath ? tempSourcePath : ''; + const library = this.getLibrary(connection); + await connection.getContent().writeStreamfileRaw(srcPath, this.getSource(library, ValidateStatementComponent.VERSION)); + const result = await connection.runCommand({ + command: `QSYS/RUNSQLSTM SRCSTMF('${tempSourcePath}') COMMIT(*NONE) NAMING(*SYS) DFTRDBCOL(${library})`, + cwd: `/`, + noLibList: true, + getSpooledFiles: true + }); + + if (result.code !== 0) { + throw Error(result.stderr || result.stdout); + } + + return this.getRemoteState(connection, installDirectory); + }); + } + + async checkMultipleStatements(currentJob: JobInfo, statements: string[]): Promise { + const connection = getInstance()?.getConnection(); + if (!connection) return undefined; + + const library = this.getLibrary(connection); + + if (library) { + const checks = statements.map(stmt => `select * from table(${library}.${ValidateStatementComponent.FUNCTION_NAME}(?)) x`).join(` union all `); + const stmt = currentJob.job.query(checks, { parameters: statements }); + const result = await stmt.execute(statements.length); + stmt.close(); + + if (!result.success || result.data.length === 0) return []; + + return result.data.map(sqlError => niceError(sqlError)); + } + + return undefined; + } + + + + private getSource(library: string, version: number) { + return /*sql*/` + create or replace function ${library}.${ValidateStatementComponent.FUNCTION_NAME}(statementText char(${VALID_STATEMENT_LENGTH}) FOR SBCS DATA) + returns table ( + messageFileName char(10), + messageFileLibrary char(10), + numberOfStatementsBack int, + curStmtLength int, + errorFirstRecordNumber int, + errorFirstColumnNumber int, + errorLastRecordNumber int, + errorLastColumnNumber int, + errorSyntaxRecordNumber int, + errorSyntaxColumnNumber int, + errorSQLMessageID char(7), + errorSQLSTATE char(5), + errorReplacementText char(1000), + messageText char(132) + ) + modifies sql data + begin + -- Variables required for parameters + declare stmtLength int default 0; + declare recordsProvided int default 1; + declare statementLanguage char(10) default '*NONE'; + declare options char(24) default '000000000000000000000000'; + declare statementInfo char(1000) for bit data default ''; + declare statementInfoLength int default 1000; + declare recordsProcessed int default 0; + declare errorCode char(1000) default ''; + -- + + -- Variables required for parsing the error list + declare messageFileName char(10); + declare messageFileLibrary char(10); + declare numberOfStatementsBack int default 0; + declare currentStatementIndex int default 0; + + -- Variables for each error + declare errorOffset int default 25; + declare curStmtLength int default 0; + declare errorFirstRecordNumber int default 0; + declare errorFirstColumnNumber int default 0; + declare errorLastRecordNumber int default 0; + declare errorLastColumnNumber int default 0; + declare errorSyntaxRecordNumber int default 0; + declare errorSyntaxColumnNumber int default 0; + declare errorSQLMessageID char(7) default ''; + declare errorSQLSTATE char(5) default ''; + declare errorRepLen int default 0; + declare errorReplacementText char(1000) default ''; + declare messageText char(132) default ''; + + set stmtLength = length(rtrim(statementText)); + set options = x'00000001' concat x'00000001' concat x'0000000A' concat '*NONE '; --No naming convention + -- set options = x'00000001' concat x'00000008' concat x'00000004' concat x'000004B0'; -- ccsid + + call ${library}.${CheckStatementComponent.FUNCTION_NAME}( statementText, stmtLength, recordsProvided, statementLanguage, options, statementInfo, statementInfoLength, recordsProcessed, errorCode); + + -- Parse the output + set messageFileName = rtrim(substr(statementInfo, 1, 10)); + set messageFileLibrary = rtrim(substr(statementInfo, 11, 10)); + set numberOfStatementsBack = interpret(substr(statementInfo, 21, 4) as int); + set errorOffset = 25; + + while currentStatementIndex < numberOfStatementsBack do + set curStmtLength = interpret(substr(statementInfo, errorOffset, 4) as int); + set errorFirstRecordNumber = interpret(substr(statementInfo, errorOffset + 4, 4) as int); + set errorFirstColumnNumber = interpret(substr(statementInfo, errorOffset + 8, 4) as int); + set errorLastRecordNumber = interpret(substr(statementInfo, errorOffset + 12, 4) as int); + set errorLastColumnNumber = interpret(substr(statementInfo, errorOffset + 16, 4) as int); + set errorSyntaxRecordNumber = interpret(substr(statementInfo, errorOffset + 20, 4) as int); + set errorSyntaxColumnNumber = interpret(substr(statementInfo, errorOffset + 24, 4) as int); + set errorSQLMessageID = rtrim(substr(statementInfo, errorOffset + 28, 7)); + set errorSQLSTATE = rtrim(substr(statementInfo, errorOffset + 35, 5)); + set errorRepLen = interpret(substr(statementInfo, errorOffset + 40, 4) as int); + set errorReplacementText = rtrim(substr(statementInfo, errorOffset + 44, errorRepLen)); + + set errorOffset = errorOffset + 44 + errorRepLen; + + set currentStatementIndex = currentStatementIndex + 1; + + select message_text + into messageText + from table(qsys2.message_file_data(messageFileLibrary, messageFileName)) + where message_id = errorSQLMessageID; + + pipe ( + messageFileName, + messageFileLibrary, + numberOfStatementsBack, + curStmtLength, + errorFirstRecordNumber, + errorFirstColumnNumber, + errorLastRecordNumber, + errorLastColumnNumber, + errorSyntaxRecordNumber, + errorSyntaxColumnNumber, + errorSQLMessageID, + errorSQLSTATE, + errorReplacementText, + messageText + ); + end while; + + return; + end; + + comment on function ${library}/${ValidateStatementComponent.FUNCTION_NAME} is '${version} - SQL Syntax Checker'; + `; + } +} + +function niceError(sqlError: SqlCheckError): SqlSyntaxError { + const replaceTokens = splitReplaceText(sqlError.ERRORREPLACEMENTTEXT); + + let text = sqlError.MESSAGETEXT || `Unknown error message ${sqlError.ERRORSQLMESSAGEID ? `(${sqlError.ERRORSQLMESSAGEID})` : `(No message ID)`}`; + replaceTokens.forEach((token, index) => { + text = text.replace(`&${index + 1}`, token); + }); + + const sqlState = sqlError.ERRORSQLSTATE.substring(0, 2); + let errorType: SqlErrorType = (sqlState in ERROR_STATE_MAP ? ERROR_STATE_MAP[sqlState] : `error`); + + return { + type: errorType, + sqlid: sqlError.ERRORSQLMESSAGEID, + sqlstate: sqlError.ERRORSQLSTATE, + text, + offset: sqlError.ERRORSYNTAXCOLUMNNUMBER, + }; +} + +function splitReplaceText(input: string) { + const firstGoodChar = input.split('').findIndex(c => c.charCodeAt(0) >= 32 && c.charCodeAt(0) <= 126); + + let replacements: string[] = []; + let inReplacement = false; + let currentReplacement = ``; + + for (let i = firstGoodChar; i < input.length; i++) { + const isGoodChar = input.charCodeAt(i) >= 32 && input.charCodeAt(i) <= 126; + + if (isGoodChar) { + inReplacement = true; + + if (inReplacement) { + currentReplacement += input[i]; + } + } else { + if (inReplacement) { + replacements.push(currentReplacement); + currentReplacement = ``; + } + + inReplacement = false; + } + } + + if (currentReplacement) { + replacements.push(currentReplacement); + } + + return replacements; +} \ No newline at end of file diff --git a/src/connection/extendedSQLJob.ts b/src/connection/extendedSQLJob.ts new file mode 100644 index 00000000..2d41b1be --- /dev/null +++ b/src/connection/extendedSQLJob.ts @@ -0,0 +1,60 @@ +import { SQLJob } from "@ibm/mapepire-js"; +import { QueryResult } from "@ibm/mapepire-js/dist/src/types"; +import { getInstance } from "../base"; +import { SelfValue } from "../views/jobManager/selfCodes/nodes"; +import { JobLogEntry } from "./types"; + +export type ExtendedSQLJob = SQLJob & { + selfState: SelfValue; + currentSchemaStore?: string; + resetCurrentSchemaCache(): void + getSelfCode(): SelfValue + getCurrentSchema(): Promise + getNaming(): undefined | "sql" | "system" + setSelfState(code: SelfValue): Promise + getJobLog(): Promise> + requestCancel(): Promise +} + +export function extendSQLJob(job: SQLJob) { + const wrappedJob = job as ExtendedSQLJob; + wrappedJob.selfState = "*NONE"; + + wrappedJob.resetCurrentSchemaCache = () => delete wrappedJob.currentSchemaStore; + wrappedJob.getSelfCode = () => wrappedJob.selfState; + + wrappedJob.getCurrentSchema = async () => { + if (wrappedJob.getNaming() === `sql`) { + if (wrappedJob.currentSchemaStore) { + return wrappedJob.currentSchemaStore; + } + + const result = await wrappedJob.execute<{ '00001': string }>(`values (current schema)`); + if (result.success && result.data.length > 0) { + wrappedJob.currentSchemaStore = result.data[0]['00001']; + return wrappedJob.currentSchemaStore; + } + } + + return wrappedJob.options?.libraries?.[0] || `QGPL`; + } + + wrappedJob.getNaming = () => wrappedJob.options.naming; + + wrappedJob.setSelfState = async (code: SelfValue) => { + await wrappedJob.query(`SET SYSIBMADM.SELFCODES = '${code}'`).execute(); + wrappedJob.selfState = code; + } + + wrappedJob.getJobLog = async () => wrappedJob.query(`select * from table(qsys2.joblog_info('*')) a`).execute(); + wrappedJob.requestCancel = async () => { + const connection = getInstance().getConnection(); + + // Note that this statement is run via the base extension since it has to be done on a job other than the one whose SQL is getting canceled + await connection.runSQL(`CALL QSYS2.CANCEL_SQL('${wrappedJob.id}')`); + + const [row] = await connection.runSQL(`select V_SQL_STMT_STATUS as STATUS from table(qsys2.get_job_info('${wrappedJob.id}'))`) as { STATUS: string | null }[]; + return !(row && row.STATUS === `ACTIVE`); + } + return wrappedJob; +} \ No newline at end of file diff --git a/src/connection/manager.ts b/src/connection/manager.ts index cdb7040e..67b17fbc 100644 --- a/src/connection/manager.ts +++ b/src/connection/manager.ts @@ -1,17 +1,17 @@ +import { SQLJob } from "@ibm/mapepire-js"; import { Query } from "@ibm/mapepire-js/dist/src/query"; import { JDBCOptions, QueryOptions, QueryResult } from "@ibm/mapepire-js/dist/src/types"; import { getInstance } from "../base"; -import { askAboutNewJob, Config, onConnectOrServerInstall, osDetail } from "../config"; +import { askAboutNewJob, Config, osDetail } from "../config"; import Configuration from "../configuration"; -import { SelfValue } from "../views/jobManager/selfCodes/nodes"; -import { ServerComponent, UpdateStatus } from "./serverComponent"; -import { OldSQLJob } from "./sqlJob"; import { ConfigManager } from "../views/jobManager/ConfigManager"; +import { SelfValue } from "../views/jobManager/selfCodes/nodes"; +import { ExtendedSQLJob, extendSQLJob } from "./extendedSQLJob"; export interface JobInfo { name: string; - job: OldSQLJob; + job: ExtendedSQLJob; } export type NamingFormats = "sql" | "system"; @@ -26,60 +26,53 @@ export class SQLJobManager { constructor() { } - async newJob(predefinedJob?: OldSQLJob, name?: string) { - if (ServerComponent.isInstalled()) { - - const instance = getInstance(); - const connection = instance.getConnection()!; - const config = connection.getConfig(); - - let newJob: OldSQLJob; - if (predefinedJob) { - newJob = predefinedJob; - } else { - let options: JDBCOptions = ConfigManager.getDefaultConfig(); - const startUpConfigList = Config.getStartUpConfigList(); - const startUpConfig = startUpConfigList.find((item) => item.connectionName === connection.currentConnectionName); - if (startUpConfig) { - const savedConfig = ConfigManager.getConfig(startUpConfig.configName); - if (savedConfig) { - name = startUpConfig.configName; - options = savedConfig; - } - } - - if (!options.libraries) { - options.libraries = [config.currentLibrary, ...config.libraryList.filter((item) => item != config.currentLibrary)]; + async newJob(options?: JDBCOptions, name?: string) { + const connection = getInstance().getConnection()!; + const config = connection.getConfig(); + const mapepire = await connection.getComponent("mapepire"); + + if (!options) { + options = ConfigManager.getDefaultConfig(); + const startUpConfigList = Config.getStartUpConfigList(); + const startUpConfig = startUpConfigList.find((item) => item.connectionName === connection.currentConnectionName); + if (startUpConfig) { + const savedConfig = ConfigManager.getConfig(startUpConfig.configName); + if (savedConfig) { + name = startUpConfig.configName; + options = savedConfig; } + } - newJob = new OldSQLJob(options); + if (!options.libraries) { + options.libraries = [config.currentLibrary, ...config.libraryList.filter((item) => item != config.currentLibrary)]; } + } - try { - this.creatingJobs += 1; - await newJob.connect(); + try { + this.creatingJobs += 1; + const newJob = extendSQLJob(await mapepire.newJob(connection, { jdbc: options })); - if (osDetail) { - const features = osDetail.getFeatures(); - const selfDefault = SQLJobManager.getSelfDefault(); - if (features.SELF && selfDefault !== `*NONE`) { - await newJob.setSelfState(selfDefault); - } + if (osDetail) { + const features = osDetail.getFeatures(); + const selfDefault = SQLJobManager.getSelfDefault(); + if (features.SELF && selfDefault !== `*NONE`) { + await newJob.setSelfState(selfDefault); } + } - this.totalJobs += 1; + this.totalJobs += 1; - this.jobs.push({ - name: `${name || 'New job'} (${this.totalJobs})`, - job: newJob - }); + this.jobs.push({ + name: `${name || 'New job'} (${this.totalJobs})`, + job: newJob + }); - this.selectedJob = this.jobs.length - 1; - } catch (e: any) { - throw e; - } finally { - this.creatingJobs -= 1; - } + this.selectedJob = this.jobs.length - 1; + return newJob; + } catch (e: any) { + throw e; + } finally { + this.creatingJobs -= 1; } } @@ -106,7 +99,21 @@ export class SQLJobManager { this.totalJobs = 0; } - async closeJob(index?: number) { + async closeJobById(job: SQLJob) { + const index = this.jobs.findIndex(j => j.job.id === job.id); + if (index > -1) { + return this.closeJob(index); + } + } + + async closeJobByName(name: string) { + const index = this.jobs.findIndex(info => info.name === name); + if (index > -1) { + return this.closeJob(index); + } + } + + async closeJob(index: number) { if (this.jobs[index]) { const selected: JobInfo = this.jobs[index]; @@ -116,11 +123,6 @@ export class SQLJobManager { } } - closeJobByName(name: string) { - const id = this.jobs.findIndex(info => info.name === name); - return this.closeJob(id); - } - getSelection(): JobInfo | undefined { return this.jobs[this.selectedJob]; } @@ -141,7 +143,7 @@ export class SQLJobManager { return this.jobs[jobExists]; } - private resetCurrentSchema(query: string, job: OldSQLJob) { + private resetCurrentSchema(query: string, job: ExtendedSQLJob) { if (query.toUpperCase().startsWith(`SET`)) { const newSchema = query.split(` `)[2]; if (newSchema) { @@ -180,22 +182,11 @@ export class SQLJobManager { async getPagingStatement(query: string, opts?: QueryOptions): Promise> { const selected = this.jobs[this.selectedJob]; - if (ServerComponent.isInstalled() && selected) { + if (selected) { this.resetCurrentSchema(query, selected?.job); - return selected.job.query(query, opts); - - } else if (!ServerComponent.isInstalled()) { - let updateResult = await ServerComponent.checkForUpdate(); - if (UpdateStatus.JUST_UPDATED === updateResult || UpdateStatus.UP_TO_DATE === updateResult) { - await onConnectOrServerInstall(); - return this.getPagingStatement(query, opts); - } - throw new Error(`Database server component is required. Please see documentation for details.`); - } else { const hasNewJob = await askAboutNewJob(); - if (hasNewJob) { return this.getPagingStatement(query, opts); } else { diff --git a/src/connection/serverComponent.ts b/src/connection/serverComponent.ts deleted file mode 100644 index 4ce9605d..00000000 --- a/src/connection/serverComponent.ts +++ /dev/null @@ -1,250 +0,0 @@ -import { getInstance } from "../base"; - -import * as path from "path"; -import { OutputChannel, extensions, window } from "vscode"; -import { Config } from "../config"; - -import { stat } from "fs/promises"; -import { SERVER_VERSION_FILE } from "./SCVersion"; - -import IBMi from "@halcyontech/vscode-ibmi-types/api/IBMi"; -import * as Crypto from 'crypto'; -import { readFileSync } from "fs"; -import Configuration from "../configuration"; - -const ExecutablePathDir = `$HOME/.vscode/`; -const DEFAULT_JAVA_EIGHT = `/QOpenSys/QIBM/ProdData/JavaVM/jdk80/64bit/bin/java`; - -export enum UpdateStatus { - FAILED, - NONE_AVAILABLE, - UP_TO_DATE, - DECLINED_UPDATE, - JUST_UPDATED, -} - -export class ServerComponent { - private static installed: boolean = false; - static outputChannel: OutputChannel; - - static initOutputChannel() { - if (!this.outputChannel) { - this.outputChannel = window.createOutputChannel(`Db2 for i Server Component`, `json`); - } - - return this.outputChannel; - } - - static writeOutput(jsonString: string, show = false) { - if (show) { - this.outputChannel.show(); - } - - if (this.outputChannel) { - this.outputChannel.appendLine(jsonString); - } - } - - static isInstalled() { - return this.installed; - } - - static getInitCommand(): string | undefined { - const path = this.getComponentPath(); - - const jvmVersion = Configuration.get(`jobJava`) || DEFAULT_JAVA_EIGHT; - - if (path) { - return `${jvmVersion} -Dos400.stdio.convert=N -jar ${path} --single` - } - } - - static getComponentPath(): string | undefined { - if (Config.ready) { - const installedVersion = Config.getServerComponentName(); - - if (installedVersion) { - return path.posix.join(ExecutablePathDir, installedVersion); - } - } - - return; - } - - static async initialise(): Promise { - const instance = getInstance(); - const connection = instance.getConnection(); - - if (!connection) { - return false; - } - - if (!Config.ready) { - Config.setConnectionName(connection.currentConnectionName); - } - - if (!this.installed) { - this.installed = await this.isAlreadyInstalled(); - } - - return this.installed; - } - - static reset(){ - this.installed = false; - } - - static async isAlreadyInstalled() { - const instance = getInstance(); - const connection = instance.getConnection(); - - const exists = await connection.sendCommand({ - command: `ls ${this.getComponentPath()}` - }); - - return (exists.code === 0); - } - - /** - * Returns whether server component is installed. - */ - public static async checkForUpdate(): Promise { - let updateResult: UpdateStatus = UpdateStatus.NONE_AVAILABLE; - - const extensionPath = extensions.getExtension(`halcyontechltd.vscode-db2i`).extensionPath; - const instance = getInstance(); - const connection = instance.getConnection(); - - try { - const assetPath = path.join(extensionPath, `dist`, SERVER_VERSION_FILE); - const assetExistsLocally = await exists(assetPath); - - ServerComponent.writeOutput(JSON.stringify({ assetPath, assetExists: assetExistsLocally })); - - if (assetExistsLocally) { - const basename = SERVER_VERSION_FILE; - const lastInstalledName = Config.getServerComponentName(); - - ServerComponent.writeOutput(JSON.stringify({ basename, lastInstalledName })); - - await this.initialise(); - - if (lastInstalledName !== basename || this.installed === false) { - // This means we're currently running a different version, - // or maybe not at all (currentlyInstalled can be undefined) - - // First, we need their home directory - const commandResult = await connection.sendCommand({ - command: `echo ${ExecutablePathDir}` - }); - - this.writeOutput(JSON.stringify(commandResult)); - - if (commandResult.code === 0) { - const stuffInStderr = commandResult.stderr.length > 0; - const remotePath = path.posix.join(commandResult.stdout, basename); - - ServerComponent.writeOutput(JSON.stringify({ remotePath, ExecutablePathDir })); - - const remoteExists = await connection.content.testStreamFile(remotePath, "f"); - const md5IsEqual = remoteExists && await compareMD5Hash(connection, assetPath, remotePath); - if (!remoteExists || !md5IsEqual) { - if (remoteExists) { - const allowWrite = await connection.sendCommand({ - command: `chmod 600 ${remotePath}` - }); - if (allowWrite.code !== 0) { - this.writeOutput(JSON.stringify(allowWrite)); - window.showErrorMessage(`Remote file ${remotePath} cannot be written; try to delete it and reconnect.`, 'Show') - .then(show => { - if (show) { - this.outputChannel.show(); - } - }); - return UpdateStatus.FAILED; - } - } - await connection.uploadFiles([{ local: assetPath, remote: remotePath }]); - - const scAuth = await connection.sendCommand({ - command: `chmod 400 ${remotePath}` - }); - - this.writeOutput(JSON.stringify(scAuth)); - - await Config.setServerComponentName(basename); - - if (stuffInStderr) { - ServerComponent.writeOutput(`Server component was uploaded to ${remotePath} but there was something in stderr, which is not right. It might be worth seeing your user profile startup scripts.`); - } - - window.showInformationMessage(`Db2 for IBM i extension server component has been updated!`); - this.installed = true; - updateResult = UpdateStatus.JUST_UPDATED; - } - else{ - await Config.setServerComponentName(basename); - this.installed = true; - updateResult = UpdateStatus.UP_TO_DATE; - } - } else { - updateResult = UpdateStatus.FAILED; - - window.showErrorMessage(`Something went really wrong when trying to fetch your home directory.`).then(chosen => { - if (chosen === `Show`) { - this.outputChannel.show(); - } - }); - } - - } else { - // Already installed. Move along - updateResult = UpdateStatus.UP_TO_DATE; - } - - } else { - // Uh oh. A release was made by there's no jar file?? - ServerComponent.writeOutput('Unable to get file name from server component release.'); - updateResult = UpdateStatus.NONE_AVAILABLE; - } - - } catch (e) { - updateResult = UpdateStatus.FAILED; - ServerComponent.writeOutput(JSON.stringify(e)); - console.log(e); - - window.showErrorMessage(`Something went really wrong during the update process! Check the Db2 for i Output log for the log`, `Show`).then(chosen => { - if (chosen === `Show`) { - this.outputChannel.show(); - } - }); - } - - return updateResult; - } -} - -async function exists(path: string) { - try { - await stat(path); - return true; - } catch (e) { - return false; - } -} - -async function compareMD5Hash(connection: IBMi, local: string, remote: string) { - const localMD5 = Crypto.createHash("md5") - .update(readFileSync(local)) - .digest("hex") - .toLowerCase(); - - const remoteMD5Result = (await connection.sendCommand({ command: `${connection.remoteFeatures.md5sum} ${remote}` })); - if (remoteMD5Result.code === 0) { - return localMD5 === remoteMD5Result.stdout.split(/\s+/)[0]; - } - else { - ServerComponent.writeOutput(JSON.stringify(remoteMD5Result)); - return false; - } -} \ No newline at end of file diff --git a/src/connection/sqlJob.ts b/src/connection/sqlJob.ts deleted file mode 100644 index a267ce45..00000000 --- a/src/connection/sqlJob.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { getInstance } from "../base"; -import { ServerComponent } from "./serverComponent"; -import { SelfValue } from "../views/jobManager/selfCodes/nodes"; -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`); - -export class OldSQLJob extends SQLJob { - private channel: any; - 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; - - const instance = getInstance(); - const connection = instance.getConnection(); - - const bashPathAvailable = connection.remoteFeatures[`bash`]; - if (bashPathAvailable) { - const commandShellResult = await connection.sendCommand({ - command: `echo $SHELL` - }); - if (!commandShellResult.stderr) { - let userDefaultShell = commandShellResult.stdout.trim(); - if (userDefaultShell === bashPathAvailable) { - useExec = true; - } - } - } - - return useExec; - } - - private async getSshChannel() { - const instance = getInstance(); - const connection = instance.getConnection(); - - let useExec = await OldSQLJob.useExec(); - - return new Promise((resolve, reject) => { - // Setting QIBM_JAVA_STDIO_CONVERT and QIBM_PASE_DESCRIPTOR_STDIO to make sure all PASE and Java converters are off - const startingCommand = `QIBM_JAVA_STDIO_CONVERT=N QIBM_PASE_DESCRIPTOR_STDIO=B QIBM_USE_DESCRIPTOR_STDIO=Y QIBM_MULTI_THREADED=Y ${useExec ? `exec ` : ``}` + ServerComponent.getInitCommand(); - - ServerComponent.writeOutput(startingCommand); - - const a = connection.client.connection.exec(startingCommand, (err: any, stream: any, options: {encoding: `binary`}) => { - if (err) { - reject(err); - ServerComponent.writeOutput(err); - } - - let outString = ``; - - stream.stderr.on(`data`, (data: Buffer) => { - ServerComponent.writeOutput(data.toString()); - }) - - stream.stdout.on(`data`, (data: Buffer) => { - outString += String(data); - if (outString.endsWith(`\n`)) { - for (const thisMsg of outString.split(`\n`)) { - if (thisMsg === ``) continue; - - outString = ``; - if (this.isTracingChannelData) ServerComponent.writeOutput(thisMsg); - try { - let response: ServerResponse = JSON.parse(thisMsg); - this.responseEmitter.emit(response.id, response); - } catch (e: any) { - console.log(`Error: ` + e); - console.log(`Data: ` + thisMsg); - outString = ``; - } - } - } - }); - - resolve(stream); - }); - - console.log(a); - }) - } - - override async send(content: ServerRequest): Promise { - if (this.isTracingChannelData) ServerComponent.writeOutput(JSON.stringify(content)); - - this.channel.stdin.write(JSON.stringify(content) + `\n`); - return new Promise((resolve, reject) => { - this.responseEmitter.on(content.id, (x: ServerResponse) => { - this.responseEmitter.removeAllListeners(x.id); - resolve(x as T); - }); - }); - } - - getStatus(): JobStatus { - const currentListenerCount = this.responseEmitter.eventNames().length; - - return this.channel && currentListenerCount > 0 ? JobStatus.BUSY : this.status as JobStatus; - } - - async connect(): Promise { - this.isTracingChannelData = true; - - this.channel = await this.getSshChannel(); - - this.channel.on(`error`, (err) => { - ServerComponent.writeOutput(err); - this.end(); - }) - - this.channel.on(`close`, (code: number) => { - ServerComponent.writeOutput(`Exited with code ${code}.`) - this.end(); - }) - - const props = Object - .keys(this.options) - .filter(prop => this.options[prop] !== `` && this.options[prop] !== null && this.options[prop] !== undefined) // 0 is valid - .map(prop => { - if (Array.isArray(this.options[prop])) { - return `${prop}=${(this.options[prop] as string[]).join(`,`)}`; - } else { - return `${prop}=${this.options[prop]}`; - } - }) - .join(`;`) - - const connectionObject = { - id: OldSQLJob.getNewUniqueId(), - type: `connect`, - //technique: (getInstance().getConnection().qccsid === 65535 || this.options["database name"]) ? `tcp` : `cli`, //TODO: investigate why QCCSID 65535 breaks CLI and if there is any workaround - technique: `tcp`, // TODO: DOVE does not work in cli mode - application: `vscode-db2i ${DB2I_VERSION}`, - props: props.length > 0 ? props : undefined - } - - const connectResult = await this.send(connectionObject); - - if (connectResult.success === true) { - this.status = JobStatus.READY; - } else { - this.end(); - this.status = JobStatus.NOT_STARTED; - throw new Error(connectResult.error || `Failed to connect to server.`); - } - - this.id = connectResult.job; - this.isTracingChannelData = false; - - return connectResult; - } - - async setSelfState(code: SelfValue) { - try { - const query: string = `SET SYSIBMADM.SELFCODES = '${code}'` - await this.query(query).execute(); - this.selfState = code; - } catch (e) { - throw e; - } - } - - getJobLog(): Promise> { - return this.query(`select * from table(qsys2.joblog_info('*')) a`).execute(); - } - - async requestCancel(): Promise { - const instance = getInstance(); - const connection = instance.getConnection(); - - // Note that this statement is run via the base extension since it has to be done on a job other than the one whose SQL is getting canceled - await connection.runSQL(`CALL QSYS2.CANCEL_SQL('${this.id}')`); - - const [row] = await connection.runSQL(`select V_SQL_STMT_STATUS as STATUS from table(qsys2.get_job_info('${this.id}'))`) as {STATUS: string|null}[]; - - if (row && row.STATUS === `ACTIVE`) return false; - - return true; - } - - async close() { - const exitObject: ServerRequest = { - id: OldSQLJob.getNewUniqueId(), - type: `exit` - }; - - this.send(exitObject); - - this.responseEmitter.eventNames().forEach(event => { - this.responseEmitter.emit(event, JSON.stringify({ - id: event, - success: false, - error: `Job ended before response returned.` - })); - }); - - this.end(); - } - - private end() { - this.channel.close(); - this.channel = undefined; - this.status = JobStatus.ENDED; - this.responseEmitter.removeAllListeners(); - } -} \ No newline at end of file diff --git a/src/connection/syntaxChecker/checker.ts b/src/connection/syntaxChecker/checker.ts deleted file mode 100644 index 2996d14d..00000000 --- a/src/connection/syntaxChecker/checker.ts +++ /dev/null @@ -1,149 +0,0 @@ -export const VALIDATOR_NAME = `VALIDATE_STATEMENT`; -export const WRAPPER_NAME = `CHECKSTMTWRAPPED`; -export const VALID_STATEMENT_LENGTH = 32740; -export const MAX_STATEMENT_COUNT = 200; - -export function getValidatorSource(schema: string, version: number) { - return /*sql*/` - create or replace procedure ${schema}.${WRAPPER_NAME} ( - IN statementText char(${VALID_STATEMENT_LENGTH}) FOR SBCS DATA, - IN statementLength int, - IN recordsProvided int, - IN statementLanguage char(10), - IN options char(24), - OUT statementInfo char(1000), - IN statementInfoLength int, - OUT recordsProcessed int, - OUT errorCode char(1000) - ) - LANGUAGE RPGLE - NOT DETERMINISTIC - MODIFIES SQL DATA - EXTERNAL NAME QSYS/QSQCHKS - PARAMETER STYLE GENERAL; - - comment on procedure ${schema}/${WRAPPER_NAME} is '${version} - QSQCHKS Wrapper'; - - create or replace function ${schema}.${VALIDATOR_NAME}(statementText char(${VALID_STATEMENT_LENGTH}) FOR SBCS DATA) --todo: support 1208 parms - returns table ( - messageFileName char(10), - messageFileLibrary char(10), - numberOfStatementsBack int, - curStmtLength int, - errorFirstRecordNumber int, - errorFirstColumnNumber int, - errorLastRecordNumber int, - errorLastColumnNumber int, - errorSyntaxRecordNumber int, - errorSyntaxColumnNumber int, - errorSQLMessageID char(7), - errorSQLSTATE char(5), - errorReplacementText char(1000), - messageText char(132) - ) - modifies sql data - begin - -- Variables required for parameters - declare stmtLength int default 0; - declare recordsProvided int default 1; - declare statementLanguage char(10) default '*NONE'; - declare options char(24) default '000000000000000000000000'; - declare statementInfo char(1000) for bit data default ''; - declare statementInfoLength int default 1000; - declare recordsProcessed int default 0; - declare errorCode char(1000) default ''; - -- - - -- Variables required for parsing the error list - declare messageFileName char(10); - declare messageFileLibrary char(10); - declare numberOfStatementsBack int default 0; - declare currentStatementIndex int default 0; - - -- Variables for each error - declare errorOffset int default 25; - declare curStmtLength int default 0; - declare errorFirstRecordNumber int default 0; - declare errorFirstColumnNumber int default 0; - declare errorLastRecordNumber int default 0; - declare errorLastColumnNumber int default 0; - declare errorSyntaxRecordNumber int default 0; - declare errorSyntaxColumnNumber int default 0; - declare errorSQLMessageID char(7) default ''; - declare errorSQLSTATE char(5) default ''; - declare errorRepLen int default 0; - declare errorReplacementText char(1000) default ''; - declare messageText char(132) default ''; - - set stmtLength = length(rtrim(statementText)); - set options = x'00000001' concat x'00000001' concat x'0000000A' concat '*NONE '; --No naming convention - -- set options = x'00000001' concat x'00000008' concat x'00000004' concat x'000004B0'; -- ccsid - - call ${schema}.${WRAPPER_NAME}( statementText, stmtLength, recordsProvided, statementLanguage, options, statementInfo, statementInfoLength, recordsProcessed, errorCode); - - -- set ${schema}.outlog = statementInfo; - -- set ${schema}.outlog = substr(statementInfo, 21, 4); - - -- Parse the output - set messageFileName = rtrim(substr(statementInfo, 1, 10)); - set messageFileLibrary = rtrim(substr(statementInfo, 11, 10)); - set numberOfStatementsBack = interpret(substr(statementInfo, 21, 4) as int); - set errorOffset = 25; - - while currentStatementIndex < numberOfStatementsBack do - set curStmtLength = interpret(substr(statementInfo, errorOffset, 4) as int); - set errorFirstRecordNumber = interpret(substr(statementInfo, errorOffset + 4, 4) as int); - set errorFirstColumnNumber = interpret(substr(statementInfo, errorOffset + 8, 4) as int); - set errorLastRecordNumber = interpret(substr(statementInfo, errorOffset + 12, 4) as int); - set errorLastColumnNumber = interpret(substr(statementInfo, errorOffset + 16, 4) as int); - set errorSyntaxRecordNumber = interpret(substr(statementInfo, errorOffset + 20, 4) as int); - set errorSyntaxColumnNumber = interpret(substr(statementInfo, errorOffset + 24, 4) as int); - set errorSQLMessageID = rtrim(substr(statementInfo, errorOffset + 28, 7)); - set errorSQLSTATE = rtrim(substr(statementInfo, errorOffset + 35, 5)); - set errorRepLen = interpret(substr(statementInfo, errorOffset + 40, 4) as int); - set errorReplacementText = rtrim(substr(statementInfo, errorOffset + 44, errorRepLen)); - - set errorOffset = errorOffset + 44 + errorRepLen; - - set currentStatementIndex = currentStatementIndex + 1; - - select message_text - into messageText - from table(qsys2.message_file_data(messageFileLibrary, messageFileName)) - where message_id = errorSQLMessageID; - - pipe ( - messageFileName, - messageFileLibrary, - numberOfStatementsBack, - curStmtLength, - errorFirstRecordNumber, - errorFirstColumnNumber, - errorLastRecordNumber, - errorLastColumnNumber, - errorSyntaxRecordNumber, - errorSyntaxColumnNumber, - errorSQLMessageID, - errorSQLSTATE, - errorReplacementText, - messageText - ); - end while; - - return; - end; - - comment on function ${schema}/${VALIDATOR_NAME} is '${version} - SQL Syntax Checker'; - - --select * - --from table(${schema}.validate_statement('select from sample.employee order by a')) x; - -- - --select * from table(qsys2.message_file_data('QSYS', 'QSQLMSG')); - -- - --values hex(substr(${schema}.outlog, 21, 4)); - --values interpret(hex(substr(${schema}.outlog, 21, 4)) as int); - --values hex(1) concat hex(1) concat hex(10) concat '*NONE '; - --values length(x'000004B8'); - --values hex(1200); - `; -} \ No newline at end of file diff --git a/src/connection/syntaxChecker/index.ts b/src/connection/syntaxChecker/index.ts deleted file mode 100644 index cce5d00a..00000000 --- a/src/connection/syntaxChecker/index.ts +++ /dev/null @@ -1,210 +0,0 @@ -import IBMi from "@halcyontech/vscode-ibmi-types/api/IBMi"; -import { ComponentIdentification, ComponentState, IBMiComponent } from "@halcyontech/vscode-ibmi-types/api/components/component"; -import { posix } from "path"; -import { getInstance } from "../../base"; -import { JobManager } from "../../config"; -import { JobInfo } from "../manager"; -import { getValidatorSource, VALIDATOR_NAME, WRAPPER_NAME } from "./checker"; - -interface SqlCheckError { - CURSTMTLENGTH: number; - ERRORFIRSTCOLUMNNUMBER: number; - ERRORFIRSTRECORDNUMBER: number; - ERRORLASTCOLUMNNUMBER: number; - ERRORLASTRECORDNUMBER: number; - ERRORREPLACEMENTTEXT: string; - ERRORSQLMESSAGEID: string; - ERRORSQLSTATE: string; - ERRORSYNTAXCOLUMNNUMBER: number; - ERRORSYNTAXRECORDNUMBER: number; - MESSAGEFILELIBRARY: string; - MESSAGEFILENAME: string; - MESSAGETEXT: string; - NUMBEROFSTATEMENTSBACK: number; -} - -type SqlErrorType = "error"|"warning"|"none"; - -const ERROR_STATE_MAP: {[state: string]: SqlErrorType} = { - '00': 'none', - '01': 'warning', -} - -export interface SqlSyntaxError { - type: SqlErrorType; - sqlid: string; - sqlstate: string; - text: string; - offset: number; -} - -export class SQLStatementChecker implements IBMiComponent { - static ID = "SQLStatementChecker"; - private readonly functionName = VALIDATOR_NAME; - private readonly currentVersion = 5; - - private library: string|undefined; - - private getLibrary(connection: IBMi) { - if (!this.library) { - this.library = connection?.getConfig()?.tempLibrary.toUpperCase() || `ILEDITOR`; - } - - return this.library; - } - - static async get(): Promise { - return getInstance()?.getConnection()?.getComponent(SQLStatementChecker.ID); - } - - reset() { - // This is called when connecting to a new system. - this.library = undefined; - } - - getIdentification(): ComponentIdentification { - return { name: SQLStatementChecker.ID, version: this.currentVersion }; - } - - static async getVersionOf(connection: IBMi, schema: string, name: string) { - const [result] = await connection.runSQL(`select cast(LONG_COMMENT as VarChar(200)) LONG_COMMENT from qsys2.sysroutines where routine_schema = '${schema}' and routine_name = '${name}'`); - if (result?.LONG_COMMENT) { - const comment = String(result.LONG_COMMENT); - const dash = comment.indexOf('-'); - if (dash > -1) { - return Number(comment.substring(0, dash).trim()); - } - } - - return -1; - } - - async getRemoteState(connection: IBMi): Promise { - const lib = this.getLibrary(connection); - - const wrapperVersion = await SQLStatementChecker.getVersionOf(connection, lib, WRAPPER_NAME); - if (wrapperVersion < this.currentVersion) { - return `NeedsUpdate`; - } - - const installedVersion = await SQLStatementChecker.getVersionOf(connection, lib, this.functionName); - if (installedVersion < this.currentVersion) { - return `NeedsUpdate`; - } - - return `Installed`; - } - - update(connection: IBMi): ComponentState | Promise { - return connection.withTempDirectory(async tempDir => { - const tempSourcePath = posix.join(tempDir, `sqlchecker.sql`); - await connection.getContent().writeStreamfileRaw(tempSourcePath, Buffer.from(this.getSource(connection), "utf-8")); - const result = await connection.runCommand({ - command: `QSYS/RUNSQLSTM SRCSTMF('${tempSourcePath}') COMMIT(*NONE) NAMING(*SYS)`, - noLibList: true - }); - - if (result.code) { - return `Error`; - } else { - return `Installed`; - } - }); - } - - private getSource(connection: IBMi) { - return getValidatorSource(this.getLibrary(connection), this.currentVersion); - } - - async call(statement: string): Promise { - const connection = getInstance()?.getConnection(); - if (!connection) return undefined; - - const currentJob = JobManager.getSelection(); - const library = this.getLibrary(connection); - - if (currentJob && library) { - const result = await currentJob.job.execute(`select * from table(${library}.${this.functionName}(?)) x`, {parameters: [statement]}); - - if (!result.success || result.data.length === 0) return; - - const sqlError = result.data[0]; - return niceError(sqlError); - } - - return undefined; - } - - async checkMultipleStatements(currentJob: JobInfo, statements: string[]): Promise { - const connection = getInstance()?.getConnection(); - if (!connection) return undefined; - - const library = this.getLibrary(connection); - - if (library) { - const checks = statements.map(stmt => `select * from table(${library}.${this.functionName}(?)) x`).join(` union all `); - const stmt = currentJob.job.query(checks, {parameters: statements}); - const result = await stmt.execute(statements.length); - stmt.close(); - - if (!result.success || result.data.length === 0) return []; - - return result.data.map(sqlError => niceError(sqlError)); - } - - return undefined; - } -} - -function niceError(sqlError: SqlCheckError): SqlSyntaxError { - const replaceTokens = splitReplaceText(sqlError.ERRORREPLACEMENTTEXT); - - let text = sqlError.MESSAGETEXT || `Unknown error message ${sqlError.ERRORSQLMESSAGEID ? `(${sqlError.ERRORSQLMESSAGEID})` : `(No message ID)`}`; - replaceTokens.forEach((token, index) => { - text = text.replace(`&${index+1}`, token); - }); - - const sqlState = sqlError.ERRORSQLSTATE.substring(0, 2); - let errorType: SqlErrorType = (sqlState in ERROR_STATE_MAP ? ERROR_STATE_MAP[sqlState] : `error`); - - return { - type: errorType, - sqlid: sqlError.ERRORSQLMESSAGEID, - sqlstate: sqlError.ERRORSQLSTATE, - text, - offset: sqlError.ERRORSYNTAXCOLUMNNUMBER, - }; -} - -function splitReplaceText(input: string) { - const firstGoodChar = input.split('').findIndex(c => c.charCodeAt(0) >= 32 && c.charCodeAt(0) <= 126); - - let replacements: string[] = []; - let inReplacement = false; - let currentReplacement = ``; - - for (let i = firstGoodChar; i < input.length; i++) { - const isGoodChar = input.charCodeAt(i) >= 32 && input.charCodeAt(i) <= 126; - - if (isGoodChar) { - inReplacement = true; - - if (inReplacement) { - currentReplacement += input[i]; - } - } else { - if (inReplacement) { - replacements.push(currentReplacement); - currentReplacement = ``; - } - - inReplacement = false; - } - } - - if (currentReplacement) { - replacements.push(currentReplacement); - } - - return replacements; - } \ No newline at end of file diff --git a/src/database/schemas.ts b/src/database/schemas.ts index 7f3c8268..2eeaaf58 100644 --- a/src/database/schemas.ts +++ b/src/database/schemas.ts @@ -2,7 +2,7 @@ import path from "path"; import { getInstance } from "../base"; import { JobManager } from "../config"; -import { ResolvedSqlObject, BasicSQLObject } from "../types"; +import { BasicSQLObject, ResolvedSqlObject } from "../types"; import Statement from "./statement"; export type SQLType = "schemas" | "tables" | "views" | "aliases" | "masks" | "constraints" | "functions" | "variables" | "indexes" | "procedures" | "receivers" | "journals" | "permissions" | "sequences" | "packages" | "triggers" | "types" | "logicals"; @@ -42,7 +42,7 @@ export const SQL_ESCAPE_CHAR = `\\`; type BasicColumnType = string | number; interface PartStatementInfo { clause: string, parameters: BasicColumnType[] }; -function getFilterClause(againstColumn: string, filter: string, noAnd?: boolean): PartStatementInfo { +function getFilterClause(againstColumn: string, filter?: string, noAnd?: boolean): PartStatementInfo { if (!filter) { return { clause: ``, parameters: [] }; } @@ -174,7 +174,7 @@ export default class Schemas { .map((obj) => `(s.routine_name = ? AND s.routine_schema = ?)`) .join(` OR `); baseStatement += ` and (${qualifiedClause})`; - parameters.push(...qualified.flatMap((obj) => [obj.name, obj.schema])); + parameters.push(...qualified.flatMap((obj) => [obj.name, obj.schema || ''])); } statements.push(baseStatement); diff --git a/src/database/statement.ts b/src/database/statement.ts index ca6a3af4..cfb45530 100644 --- a/src/database/statement.ts +++ b/src/database/statement.ts @@ -2,7 +2,7 @@ import { getInstance } from "../base"; import Configuration from "../configuration"; -import {format, FormatOptionsWithLanguage, IdentifierCase, KeywordCase} from "sql-formatter" +import { format, FormatOptionsWithLanguage, IdentifierCase, KeywordCase } from "sql-formatter"; export default class Statement { static format(sql: string, options: FormatOptionsWithLanguage = {}) { @@ -63,32 +63,28 @@ export default class Statement { * Converts a catalog name to a pretty name for UI purposes * @param name Catalog name */ - static prettyName(name: string) { - // If the name contains characters other than the valid variants, uppercase, digits, or underscores, it must be delimited - if (Statement.validQsysName(name)) return name.toLowerCase(); - else { - // Delimited name - if (name.includes(` `) || name.includes(`.`) || name !== name.toUpperCase()) { - return `"${name}"`; - } else { - return name.toLowerCase(); - } + static prettyName(name?: string) { + if(!name){ + return ''; + } + // If the name contains characters other than the valid variants, uppercase, digits, or underscores, it must be delimited + if (Statement.validQsysName(name)) return name.toLowerCase(); + else { + // Delimited name + if (name.includes(` `) || name.includes(`.`) || name !== name.toUpperCase()) { + return `"${name}"`; + } else { + return name.toLowerCase(); } + } } static noQuotes(name: string) { - if (name.startsWith(`"`) && name.endsWith(`"`)) return name.substring(1, name.length-1); + if (name.startsWith(`"`) && name.endsWith(`"`)) return name.substring(1, name.length - 1); return name; } static escapeString(value: string) { - value = value.replace(/'/g, function (s) { - switch (s) { - case `'`: - return `''`; - } - }); - - return value; + return value.replace(/'/g, () => `''`); } } \ No newline at end of file diff --git a/src/dsc.ts b/src/dsc.ts deleted file mode 100644 index 3c8710fc..00000000 --- a/src/dsc.ts +++ /dev/null @@ -1,56 +0,0 @@ -import path from "path"; - -import { existsSync, mkdirSync } from "fs"; -import { writeFile } from "fs/promises"; -import fetch from "node-fetch"; -import { Octokit } from "@octokit/rest"; -import { SERVER_VERSION_FILE, SERVER_VERSION_TAG } from "./connection/SCVersion"; - -async function work() { - const octokit = new Octokit(); - - const owner = `Mapepire-IBMi`; - const repo = `mapepire-server`; - - try { - const result = await octokit.request(`GET /repos/{owner}/{repo}/releases/tags/${SERVER_VERSION_TAG}`, { - owner, - repo, - headers: { - 'X-GitHub-Api-Version': '2022-11-28' - } - }); - - const newAsset = result.data.assets.find(asset => asset.name.endsWith(`.jar`)); - - if (newAsset) { - console.log(`Asset found: ${newAsset.name}`); - - const url = newAsset.browser_download_url; - const distDirectory = path.join(`.`, `dist`); - if (!existsSync(distDirectory)) { - mkdirSync(distDirectory); - } - - const serverFile = path.join(distDirectory, SERVER_VERSION_FILE); - await downloadFile(url, serverFile); - - console.log(`Asset downloaded: ${serverFile}`); - - } else { - console.log(`Release found but no asset found.`); - } - - - } catch (e) { - console.log(e); - } -} - -function downloadFile(url, outputPath) { - return fetch(url) - .then(x => x.arrayBuffer()) - .then(x => writeFile(outputPath, Buffer.from(x))); -} - -work(); \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 87014bd9..2e96749f 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -11,11 +11,10 @@ import { registerContinueProvider } from "./aiProviders/continue/continueContext import { registerDb2iTablesProvider } from "./aiProviders/continue/listTablesContextProvider"; import { registerCopilotProvider } from "./aiProviders/copilot"; import { getInstance, loadBase } from "./base"; -import { JobManager, initConfig, onConnectOrServerInstall } from "./config"; +import { JobManager, initConfig, onConnectOrServerInstall as onCode4iConnect } from "./config"; import Configuration from "./configuration"; +import { ExtendedSQLJob } from "./connection/extendedSQLJob"; import { SQLJobManager } from "./connection/manager"; -import { ServerComponent } from "./connection/serverComponent"; -import { OldSQLJob } from "./connection/sqlJob"; import Statement from "./database/statement"; import { languageInit } from "./language/providers"; import { DbCache } from "./language/providers/logic/cache"; @@ -31,22 +30,27 @@ import { QueryHistory } from "./views/queryHistoryView"; export interface Db2i { sqlJobManager: SQLJobManager, - sqlJob: (options?: JDBCOptions) => OldSQLJob + sqlJob: (options?: JDBCOptions) => Promise sqlExamples: typeof SQLExamples, statement: typeof Statement, dbCache: typeof DbCache, } -// this method is called when your extension is activated -// your extension is activated the very first time the command is executed +export namespace Db2foriOutput { + const outputChannel = vscode.window.createOutputChannel(`Db2 for IBM i`, `json`); + export function writeOutput(jsonString?: string, show = false) { + if (show) { + outputChannel.show(); + } -export function activate(context: vscode.ExtensionContext): Db2i { - - // Use the console to output diagnostic information (console.log) and errors (console.error) - // This line of code will only be executed once when your extension is activated - console.log(`Congratulations, your extension "vscode-db2i" is now active!`); + if (jsonString) { + outputChannel.appendLine(jsonString); + } + } +} - loadBase(context); +export async function activate(context: vscode.ExtensionContext): Promise { + await loadBase(context); const jobManagerView = new JobManagerView(context); const jobManagerTreeView = vscode.window.createTreeView(`jobManager`, { treeDataProvider: jobManagerView, showCollapseAll: true }); @@ -62,7 +66,6 @@ export function activate(context: vscode.ExtensionContext): Db2i { context.subscriptions.push( ...languageInit(), ...notebookInit(), - ServerComponent.initOutputChannel(), jobManagerTreeView, schemaBrowserTreeView, queryHistoryTreeView, @@ -96,14 +99,14 @@ export function activate(context: vscode.ExtensionContext): Db2i { selfCodesView.setJobOnly(false); setCheckerAvailableContext(); // Refresh the examples when we have it, so we only display certain examples - onConnectOrServerInstall().then(() => { + onCode4iConnect().then(async () => { schemaBrowser.clearCacheAndRefresh(); exampleBrowser.refresh(); queryHistory.refresh(); selfCodesView.setRefreshEnabled(Configuration.get(`jobSelfViewAutoRefresh`) || false); // register list tables const currentJob = JobManager.getSelection(); - const currentSchema = currentJob?.job.options.libraries[0]; + const currentSchema = await currentJob?.job.getCurrentSchema(); registerDb2iTablesProvider(currentSchema); if (devMode && runTests) { runTests(); @@ -117,13 +120,11 @@ export function activate(context: vscode.ExtensionContext): Db2i { // register continue provider registerContinueProvider(); - - - instance.subscribe(context, `disconnected`, `db2i-disconnected`, () => ServerComponent.reset()); + console.log(`Congratulations, ${context.extension.id} is now active!`); return { sqlJobManager: JobManager, - sqlJob: (options?: JDBCOptions) => new OldSQLJob(options), + sqlJob: (options?: JDBCOptions) => JobManager.newJob(options), sqlExamples: SQLExamples, statement: Statement, dbCache: DbCache diff --git a/src/language/providers/actionProvider.ts b/src/language/providers/actionProvider.ts index 74914e82..ef6ca071 100644 --- a/src/language/providers/actionProvider.ts +++ b/src/language/providers/actionProvider.ts @@ -1,4 +1,4 @@ -import { CodeAction, CodeActionKind, languages, TextDocument, Uri, WorkspaceEdit } from "vscode"; +import { CodeAction, CodeActionKind, languages, TextDocument, WorkspaceEdit } from "vscode"; import { remoteAssistIsEnabled } from "./logic/available"; import { getSqlDocument } from "./logic/parse"; @@ -22,9 +22,9 @@ export const actionProvider = languages.registerCodeActionsProvider({ language: if (!sqlDoc) return; const currentStatement = sqlDoc.getStatementByOffset(offset); - const label = currentStatement.getLabel()?.toLowerCase() || ``; + const label = currentStatement?.getLabel()?.toLowerCase(); - if (currentStatement && !invalidBindingLabels.includes(label)) { + if (currentStatement && (!label || !invalidBindingLabels.includes(label))) { const markers = currentStatement.getEmbeddedStatementAreas().filter(a => a.type === `marker`); const codeActions: SqlCodeAction[] = []; diff --git a/src/language/providers/completionProvider.ts b/src/language/providers/completionProvider.ts index be0c34e0..6f27456c 100644 --- a/src/language/providers/completionProvider.ts +++ b/src/language/providers/completionProvider.ts @@ -1,19 +1,19 @@ import { CompletionItem, CompletionItemKind, languages } from "vscode"; import { JobManager } from "../../config"; +import { CallableType } from "../../database/callable"; import { SQLType, } from "../../database/schemas"; import Statement from "../../database/statement"; +import { BasicSQLObject, TableColumn } from "../../types"; import Document from "../sql/document"; import * as LanguageStatement from "../sql/statement"; import { CTEReference, ClauseType, ObjectRef, StatementType } from "../sql/types"; -import { CallableType } from "../../database/callable"; -import { prepareParamType, createCompletionItem, getParmAttributes } from "./logic/completion"; -import { isCallableType, getCallableParameters } from "./logic/callable"; import { localAssistIsEnabled, remoteAssistIsEnabled, useSystemNames } from "./logic/available"; import { DbCache } from "./logic/cache"; +import { getCallableParameters, isCallableType } from "./logic/callable"; +import { createCompletionItem, getParmAttributes, prepareParamType } from "./logic/completion"; import { getSqlDocument } from "./logic/parse"; -import { TableColumn, BasicSQLObject } from "../../types"; export interface CompletionType { order: string; @@ -44,7 +44,7 @@ const completionTypes: { [index: string]: CompletionType } = { functions: { order: `e`, label: `function`, - type: `functions`, + type: `functions`, icon: CompletionItemKind.Method }, variables: { @@ -76,7 +76,7 @@ function getAllColumns(name: string, schema: string, items: CompletionItem[]) { `Schema: ${schema}`, `a@allCols` ); - + allCols.sortText = 'a@allCols'; allCols.insertText = items.map(item => item.label).join(", "); return allCols; @@ -91,18 +91,18 @@ async function getObjectColumns( let completionItems: CompletionItem[]; - schema = Statement.noQuotes(Statement.delimName(schema, true)); - name = Statement.noQuotes(Statement.delimName(name, true)); - + schema = Statement.noQuotes(Statement.delimName(schema, true)); + name = Statement.noQuotes(Statement.delimName(name, true)); + if (isUDTF) { const resultSet = await DbCache.getCachedSignatures(schema, name); - + if (!resultSet?.length ? true : false) { return []; } - const chosenSig = resultSet[resultSet.length-1]; - + const chosenSig = resultSet[resultSet.length - 1]; + completionItems = chosenSig.returns.map((i) => createCompletionItem( Statement.prettyName(i.PARAMETER_NAME), @@ -130,7 +130,7 @@ async function getObjectColumns( ) ); } - + const allCols = getAllColumns(name, schema, completionItems); completionItems.push(allCols); @@ -148,15 +148,15 @@ async function getObjectCompletions( const allObjects = await DbCache.getObjects(forSchema, Object.values(sqlTypes).map(k => k.type)); return allObjects.map((value) => { - const completionData = completionTypes[value.type]; - return createCompletionItem( - Statement.prettyName(value.name), - completionData.icon, - completionData.label, - `Schema: ${value.schema}`, - completionData.order - ) - } + const completionData = completionTypes[value.type]; + return createCompletionItem( + Statement.prettyName(value.name), + completionData.icon, + completionData.label, + `Schema: ${value.schema}`, + completionData.order + ) + } ); } @@ -191,7 +191,7 @@ async function getProcedures( // Handle the general case const promises = refs.map(async (ref) => { - const schema = ref.object.schema || defaultSchema; + const schema = ref.object.schema || defaultSchema || ""; const sanitizedSchema = Statement.noQuotes( Statement.delimName(schema, true) ); @@ -210,25 +210,25 @@ async function getProcedures( async function getCompletionItemsForTriggerDot( currentStatement: LanguageStatement.default, // Statement from ../../database/statement offset: number, - trigger: string + trigger: string | undefined ): Promise { const defaultLibrary = await getDefaultSchema(); let list: CompletionItem[] = []; const curRef = currentStatement.getReferenceByOffset(offset); if (curRef === undefined) { - return; + return []; } //select * from sample.data as a, sample. - const curSchema = curRef.object.schema; + const curSchema = curRef.object.schema || ''; - let curRefIdentifier: ObjectRef; + let curRefIdentifier: ObjectRef | undefined; const objectRefs = currentStatement.getObjectReferences(); // Set the default schema for all references without one - for (let ref of objectRefs) { + for (const ref of objectRefs) { if (!ref.object.schema) { ref.object.schema = defaultLibrary; } @@ -238,27 +238,27 @@ async function getCompletionItemsForTriggerDot( (ref) => ref.alias && ref.object.name && - ref.alias.toUpperCase() === curSchema.toUpperCase() + ref.alias.toUpperCase() === curSchema?.toUpperCase() ); // grab completion items (column names) if alias and name are defined - if (curRefIdentifier) { + if (curRefIdentifier?.object.name) { // invalid case: select DDD/thecolumn from sample/department as DDD if (trigger === "/") { - return; + return []; } - let currentCte: CTEReference; + let currentCte: CTEReference | undefined; // If we're writing a SELECT/WITH, then also make suggestions based on the CTE if (currentStatement.type === StatementType.With) { const cteList = currentStatement.getCTEReferences(); - currentCte = cteList.find(cte => cte.name.toUpperCase() === curRefIdentifier.object.name.toUpperCase()); + currentCte = cteList.find(cte => cte.name.toUpperCase() === curRefIdentifier.object.name?.toUpperCase()); if (currentCte) { list.push(...currentCte.columns.map(col => createCompletionItem( col, CompletionItemKind.Property, - `CTE: ${currentCte.name}`, + `CTE: ${currentCte?.name}`, undefined, `a@cte` ))); @@ -268,16 +268,16 @@ async function getCompletionItemsForTriggerDot( // Else.. go do a table lookup if (!currentCte) { const completionItems = await getObjectColumns( - curRefIdentifier.object.schema, + curRefIdentifier.object.schema!, curRefIdentifier.object.name, - curRefIdentifier.isUDTF, + curRefIdentifier.isUDTF === true, useSystemNames() ); list.push(...completionItems); } } else { - + if (currentStatement.type === StatementType.Call) { const procs = await getProcedures([curRef], defaultLibrary); list.push(...procs); @@ -296,7 +296,7 @@ async function getCompletionItemsForTriggerDot( async function getCachedSchemas() { const allSchemas: BasicSQLObject[] = await DbCache.getObjects( - undefined, + '', [`schemas`] ); const completionItems: CompletionItem[] = allSchemas.map((schema) => @@ -313,7 +313,7 @@ async function getCachedSchemas() { function createCompletionItemForAlias(ref: ObjectRef) { return createCompletionItem( - ref.alias, + ref.alias || '', CompletionItemKind.Reference, "Correlation Name", [ @@ -335,11 +335,11 @@ async function getCompletionItemsForRefs(currentStatement: LanguageStatement.def const curClause = currentStatement.getClauseForOffset(offset); const tokenAtOffset = currentStatement.getTokenByOffset(offset); - - // Get all the schemas - if (objectRefs.length === 0 && cteList.length === 0) { - completionItems.push(...(await getCachedSchemas())); - } + + // Get all the schemas + if (objectRefs.length === 0 && cteList.length === 0) { + completionItems.push(...(await getCachedSchemas())); + } // Set the default schema for all references without one for (let ref of objectRefs) { if (!ref.object.schema) { @@ -350,7 +350,7 @@ async function getCompletionItemsForRefs(currentStatement: LanguageStatement.def // Fetch all the columns for tables that have references in the statement const tableItemPromises = objectRefs.map((ref) => ref.object.name && ref.object.schema - ? getObjectColumns(ref.object.schema, ref.object.name, ref.isUDTF, useSystemNames()) + ? getObjectColumns(ref.object.schema, ref.object.name, ref.isUDTF === true, useSystemNames()) : Promise.resolve([]) ); const results = await Promise.allSettled(tableItemPromises); @@ -395,7 +395,7 @@ async function getCompletionItemsForRefs(currentStatement: LanguageStatement.def // example: select * from sample.emp // -- | if (objectRefs[objectRefs.length - 1]) { - const curSchema: string = objectRefs[objectRefs.length - 1].object.schema; + const curSchema = objectRefs[objectRefs.length - 1].object.schema; if (curClause !== ClauseType.Unknown && tokenAtOffset && curSchema) { completionItems.push( ...(await getObjectCompletions(curSchema, completionTypes)) @@ -418,7 +418,7 @@ async function getCompletionItemsForRefs(currentStatement: LanguageStatement.def for (const cte of cteList) { const hasAlias = objectRefs.some( (ref) => - ref.object.name.toUpperCase() === cte.name.toUpperCase() && + ref.object?.name?.toUpperCase() === cte.name.toUpperCase() && ref.alias !== undefined ); @@ -442,12 +442,12 @@ async function getCompletionItemsForRefs(currentStatement: LanguageStatement.def } async function getCompletionItems( - trigger: string, - currentStatement: LanguageStatement.default|undefined, - offset?: number + trigger: string | undefined, + currentStatement: LanguageStatement.default, + offset: number ) { const defaultSchema = await getDefaultSchema(); - const s = currentStatement ? currentStatement.getTokenByOffset(offset) : null; + const s = currentStatement.getTokenByOffset(offset) || null; if (trigger === "." || (s && s.type === `dot`) || trigger === "/") { return getCompletionItemsForTriggerDot( @@ -457,15 +457,13 @@ async function getCompletionItems( ); } - if (currentStatement) { - const callableRef = currentStatement.getCallableDetail(offset, true); - // TODO: check the function actually exists before returning - if (callableRef) { - const routineType: CallableType = currentStatement.type === StatementType.Call ? `PROCEDURE` : `FUNCTION`; - const isValid = await isCallableType(callableRef.parentRef, routineType); - if (isValid) { - return await getCallableParameters(callableRef, offset); - } + const callableRef = currentStatement.getCallableDetail(offset, true); + // TODO: check the function actually exists before returning + if (callableRef) { + const routineType: CallableType = currentStatement.type === StatementType.Call ? `PROCEDURE` : `FUNCTION`; + const isValid = await isCallableType(callableRef.parentRef, routineType); + if (isValid) { + return await getCallableParameters(callableRef, offset); } } @@ -477,7 +475,7 @@ async function getCompletionItems( } // Determine if writing a statement inside of a CTE, if they are, set that as the current statement - let insideCte: CTEReference|undefined; + let insideCte: CTEReference | undefined; if (offset && currentStatement.type === StatementType.With) { let ctes = currentStatement.getCTEReferences(); insideCte = ctes.find(cte => offset >= cte.statement.range.start && offset <= cte.statement.range.end); @@ -539,7 +537,7 @@ function getLocalDefs(sqlDoc: Document, offset: number) { case StatementType.Create: const [currentCreate] = statement.getObjectReferences(); if (currentCreate) { - items.push(createCompletionItem(currentCreate.object.schema ? `${currentCreate.object.schema}.${currentCreate.object.name}` : currentCreate.object.name, getCompletionItemKindFromSqlType(currentCreate.createType), `Local ${currentCreate.createType || `creation`}`)); + items.push(createCompletionItem(currentCreate.object.schema ? `${currentCreate.object.schema}.${currentCreate.object.name}` : currentCreate.object.name || '', getCompletionItemKindFromSqlType(currentCreate.createType), `Local ${currentCreate.createType || `creation`}`)); } break; } @@ -550,11 +548,11 @@ function getLocalDefs(sqlDoc: Document, offset: number) { // We only care about create statements when there is a body if (statement.type === StatementType.Create) { const [groupDef] = statement.getObjectReferences(); - + // We only care about certain create types if (groupDef) { - const currentType = groupDef.createType.toLowerCase(); - if (groupDef.createType && VALID_CREATE_TYPES.includes(currentType) && groupDef.object.name) { + const currentType = groupDef.createType?.toLowerCase(); + if (groupDef.createType && currentType && VALID_CREATE_TYPES.includes(currentType) && groupDef.object.name) { items.push(createCompletionItem(groupDef.object.schema ? `${groupDef.object.schema}.${groupDef.object.name}` : groupDef.object.name, getCompletionItemKindFromSqlType(groupDef.createType), `Local ${currentType}`)); } } @@ -563,14 +561,14 @@ function getLocalDefs(sqlDoc: Document, offset: number) { if (inGroupRange) { // Show parameters to the routine const localParams = statement.getRoutineParameters(); - items.push(...localParams.map(param => createCompletionItem(param.alias, CompletionItemKind.Property, param.createType))); + items.push(...localParams.map(param => createCompletionItem(param.alias || '', CompletionItemKind.Property, param.createType))); // Show all the local definitions for (let i = 1; i < groupStatements.length; i++) { const subStatement = groupStatements[i]; if (subStatement.type === StatementType.Declare) { const [def] = subStatement.getObjectReferences(); - if (def) { + if (def.object.name) { items.push(createCompletionItem(def.object.name, CompletionItemKind.Variable, def.createType)); } } @@ -589,7 +587,6 @@ export const completionProvider = languages.registerCompletionItemProvider( async provideCompletionItems(document, position, token, context) { if (localAssistIsEnabled()) { const trigger = context.triggerCharacter; - const content = document.getText(); const offset = document.offsetAt(position); const sqlDoc = getSqlDocument(document); diff --git a/src/language/providers/hoverProvider.ts b/src/language/providers/hoverProvider.ts index c3755942..26189448 100644 --- a/src/language/providers/hoverProvider.ts +++ b/src/language/providers/hoverProvider.ts @@ -1,13 +1,12 @@ import { Hover, languages, MarkdownString, workspace } from "vscode"; -import { getSqlDocument } from "./logic/parse"; -import { DbCache, LookupResult, RoutineDetail } from "./logic/cache"; -import { JobManager } from "../../config"; +import { CallableSignature } from "../../database/callable"; import Statement from "../../database/statement"; -import { getParmAttributes, prepareParamType } from "./logic/completion"; +import { getPositionData } from "../sql/document"; import { StatementType } from "../sql/types"; import { remoteAssistIsEnabled } from "./logic/available"; -import { CallableSignature } from "../../database/callable"; -import { getPositionData } from "../sql/document"; +import { DbCache, LookupResult, RoutineDetail } from "./logic/cache"; +import { prepareParamType } from "./logic/completion"; +import { getSqlDocument } from "./logic/parse"; // ================================= // We need to open provider to exist so symbols can be cached for hover support when opening files @@ -80,12 +79,12 @@ export const hoverProvider = languages.registerHoverProvider({ language: `sql` } if (statementAt) { const refs = statementAt.getObjectReferences(); - const possibleNames = refs.map(ref => ref.object.name).filter(name => name); + const possibleNames : string[] = refs.map(ref => ref.object.name).filter((name): name is string => Boolean(name)); for (const ref of refs) { const atRef = offset >= ref.tokens[0].range.start && offset <= ref.tokens[ref.tokens.length - 1].range.end; - if (atRef) { + if (atRef && ref.object.name) { const schema = ref.object.schema || defaultSchema; const result = await lookupSymbol(ref.object.name, schema, possibleNames); @@ -102,14 +101,14 @@ export const hoverProvider = languages.registerHoverProvider({ language: `sql` } const callableRef = statementAt.getCallableDetail(routineOffset, false); if (callableRef) { const { currentCount } = getPositionData(callableRef, routineOffset); - signatures = await DbCache.getCachedSignatures(callableRef.parentRef.object.schema, callableRef.parentRef.object.name); + signatures = DbCache.getCachedSignatures(callableRef.parentRef.object.schema, callableRef.parentRef.object.name); const possibleSignatures = signatures.filter((s) => s.parms.length >= currentCount).sort((a, b) => a.parms.length - b.parms.length); signature = possibleSignatures.find((signature) => currentCount <= signature.parms.length); } } if (!signature) { - signatures = await DbCache.getCachedSignatures(result.routine.schema, result.routine.name); + signatures = DbCache.getCachedSignatures(result.routine.schema, result.routine.name); signature = signatures[0]; } diff --git a/src/language/providers/logic/available.ts b/src/language/providers/logic/available.ts index 21b7378e..137afd95 100644 --- a/src/language/providers/logic/available.ts +++ b/src/language/providers/logic/available.ts @@ -1,9 +1,8 @@ import { env } from "process"; -import { ServerComponent } from "../../../connection/serverComponent"; import { JobManager } from "../../../config"; -import { JobInfo } from "../../../connection/manager"; import Configuration from "../../../configuration"; +import { JobInfo } from "../../../connection/manager"; export function useSystemNames() { return Configuration.get(`syntax.useSystemNames`) || false; @@ -15,7 +14,6 @@ export function localAssistIsEnabled() { export function remoteAssistIsEnabled(needsToBeReady?: boolean): JobInfo|undefined { if (!localAssistIsEnabled()) return; - if (!ServerComponent.isInstalled()) return; const selection = JobManager.getSelection(); if (!selection) return; diff --git a/src/language/providers/logic/cache.ts b/src/language/providers/logic/cache.ts index 945a2096..ca6820c6 100644 --- a/src/language/providers/logic/cache.ts +++ b/src/language/providers/logic/cache.ts @@ -1,7 +1,7 @@ import Callable, { CallableRoutine, CallableSignature, CallableType } from "../../../database/callable"; import Schemas, { PageData, SQLType } from "../../../database/schemas"; import Table from "../../../database/table"; -import { SQLParm, BasicSQLObject, TableColumn } from "../../../types"; +import { BasicSQLObject, SQLParm, TableColumn } from "../../../types"; export interface RoutineDetail { routine: CallableRoutine; @@ -13,7 +13,7 @@ export type LookupResult = RoutineDetail | SQLParm | BasicSQLObject | TableColum export class DbCache { private static schemaObjects: Map = new Map(); private static objectColumns: Map = new Map(); - private static routines: Map = new Map(); + private static routines: Map = new Map(); private static routineSignatures: Map = new Map(); private static toReset: string[] = []; @@ -41,7 +41,7 @@ export class DbCache { return false; } - static async lookupSymbol(name: string, schema: string|undefined, objectFilter: string[]): Promise { + static async lookupSymbol(name: string, schema: string | undefined, objectFilter: string[]): Promise { const included = (lookupName: string) => { if (objectFilter) { return objectFilter.includes(lookupName.toLowerCase()); @@ -98,7 +98,7 @@ export class DbCache { static async getObjectColumns(schema: string, name: string) { const key = getKey(`columns`, schema, name); - + if (!this.objectColumns.has(key) || this.shouldReset(name)) { const result = await Table.getItems(schema, name); if (result) { @@ -111,7 +111,7 @@ export class DbCache { static async getObjects(schema: string, types: SQLType[], details?: PageData) { const key = getKey(`objects`, schema, types.join(`&`)); - + if (!this.schemaObjects.has(key) || this.shouldReset(schema)) { const result = await Schemas.getObjects(schema, types, details); if (result) { @@ -124,7 +124,7 @@ export class DbCache { static async getRoutine(schema: string, name: string, type: CallableType) { const key = getKey(type, schema, name); - + if (!this.routines.has(key) || this.shouldReset(name)) { const result = await Callable.getType(schema, name, type); if (result) { @@ -145,7 +145,7 @@ export class DbCache { static async getSignaturesFor(schema: string, name: string, specificNames: string[]) { const key = getKey(`signatures`, schema, name); - + if (!this.routineSignatures.has(key) || this.shouldReset(name)) { const result = await Callable.getSignaturesFor(schema, specificNames); if (result) { @@ -156,9 +156,12 @@ export class DbCache { return this.routineSignatures.get(key) || []; } - static getCachedSignatures(schema: string, name: string) { - const key = getKey(`signatures`, schema, name); - return this.routineSignatures.get(key) || []; + static getCachedSignatures(schema?: string, name?: string) { + if (schema && name) { + const key = getKey(`signatures`, schema, name); + return this.routineSignatures.get(key) || []; + } + return []; } } diff --git a/src/language/providers/logic/callable.ts b/src/language/providers/logic/callable.ts index 6547cdb6..ba506519 100644 --- a/src/language/providers/logic/callable.ts +++ b/src/language/providers/logic/callable.ts @@ -1,11 +1,11 @@ import { CompletionItem, CompletionItemKind, SnippetString } from "vscode"; -import { CallableSignature, CallableType } from "../../../database/callable"; -import { ObjectRef, CallableReference } from "../../sql/types"; +import { CallableType } from "../../../database/callable"; import Statement from "../../../database/statement"; -import { createCompletionItem, getParmAttributes } from "./completion"; -import { DbCache } from "./cache"; import { SQLParm } from "../../../types"; import { getPositionData } from "../../sql/document"; +import { CallableReference, ObjectRef } from "../../sql/types"; +import { DbCache } from "./cache"; +import { createCompletionItem, getParmAttributes } from "./completion"; /** * Checks if the ref exists as a procedure or function. Then, @@ -34,12 +34,12 @@ export async function isCallableType(ref: ObjectRef, type: CallableType) { * that are stored in the cache for a specific procedure */ export function getCallableParameters(ref: CallableReference, offset: number): CompletionItem[] { - const signatures = DbCache.getCachedSignatures(ref.parentRef.object.schema, ref.parentRef.object.name) + const signatures = DbCache.getCachedSignatures(ref.parentRef.object.schema, ref.parentRef.object.name); if (signatures) { const { firstNamedParameter, currentCount } = getPositionData(ref, offset); - const allParms = signatures.reduce((acc, val) => acc.concat(val.parms), []); - const usedParms = ref.tokens.filter((token) => allParms.some((parm) => parm.PARAMETER_NAME.toUpperCase() === Statement.noQuotes(token.value?.toUpperCase()))).map(token => Statement.noQuotes(token.value.toUpperCase())); + const allParms = signatures.flatMap(s => s.parms); + const usedParms = ref.tokens.filter((token) => allParms.some((parm) => parm.PARAMETER_NAME.toUpperCase() === Statement.noQuotes(token.value?.toUpperCase() || ''))).map(token => Statement.noQuotes(token.value?.toUpperCase() || '')); let validParms: SQLParm[] = []; @@ -62,7 +62,7 @@ export function getCallableParameters(ref: CallableReference, offset: number): C // Get a list of the available parameters const availableParms = validParms.filter((parm, i) => (!usedParms.some((usedParm) => usedParm === parm.PARAMETER_NAME.toUpperCase())) && // Hide parameters that have already been named - parm.ORDINAL_POSITION >= ((firstNamedParameter + 1) || currentCount) // Hide parameters that are before the first named parameter + parm.ORDINAL_POSITION >= (((firstNamedParameter || 0) + 1) || currentCount) // Hide parameters that are before the first named parameter ); return availableParms.map((parm) => { diff --git a/src/language/providers/logic/parse.ts b/src/language/providers/logic/parse.ts index de5d7200..4b674fb4 100644 --- a/src/language/providers/logic/parse.ts +++ b/src/language/providers/logic/parse.ts @@ -1,10 +1,10 @@ import { TextDocument } from "vscode"; +import { VALID_STATEMENT_LENGTH } from "../../../connection/components/validateStatement"; import Document from "../../sql/document"; -import { VALID_STATEMENT_LENGTH } from "../../../connection/syntaxChecker/checker"; -let cached: Map = new Map(); +const cached: Map = new Map(); -export function getSqlDocument(document: TextDocument): Document|undefined { +export function getSqlDocument(document: TextDocument): Document | undefined { if (!isSafeDocument(document)) return undefined; const uri = document.uri.toString(); @@ -17,7 +17,7 @@ export function getSqlDocument(document: TextDocument): Document|undefined { return ast; } } - + const newAsp = new Document(document.getText(), false); cached.set(uri, { ast: newAsp, version: document.version }); diff --git a/src/language/providers/peekProvider.ts b/src/language/providers/peekProvider.ts index 5a54d9da..b0588fca 100644 --- a/src/language/providers/peekProvider.ts +++ b/src/language/providers/peekProvider.ts @@ -1,9 +1,9 @@ import { languages, workspace } from "vscode"; -import { getSqlDocument } from "./logic/parse"; import { JobManager } from "../../config"; +import Schemas from "../../database/schemas"; import Statement from "../../database/statement"; import { remoteAssistIsEnabled } from "./logic/available"; -import Schemas, { } from "../../database/schemas"; +import { getSqlDocument } from "./logic/parse"; export const peekProvider = languages.registerDefinitionProvider({ language: `sql` }, { @@ -20,15 +20,15 @@ export const peekProvider = languages.registerDefinitionProvider({ language: `sq const sqlDoc = getSqlDocument(document); const offset = document.offsetAt(position); - const tokAt = sqlDoc.getTokenByOffset(offset); - const statementAt = sqlDoc.getStatementByOffset(offset); + 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) { + if (ref?.object.name) { const name = Statement.delimName(ref.object.name, true); // Schema is based on a few things: diff --git a/src/language/providers/problemProvider.ts b/src/language/providers/problemProvider.ts index f3a7b9df..de7c1c22 100644 --- a/src/language/providers/problemProvider.ts +++ b/src/language/providers/problemProvider.ts @@ -1,14 +1,13 @@ -import { commands, CompletionItemKind, Diagnostic, Disposable, DiagnosticSeverity, languages, ProgressLocation, Range, TextDocument, Uri, window, workspace } from "vscode"; +import path from "path"; +import { commands, CompletionItemKind, Diagnostic, DiagnosticSeverity, Disposable, languages, ProgressLocation, Range, TextDocument, Uri, window, workspace } from "vscode"; +import Configuration from "../../configuration"; +import { MAX_STATEMENT_COUNT, SqlSyntaxError, VALID_STATEMENT_LENGTH, ValidateStatementComponent } from "../../connection/components/validateStatement"; import { SQLType, } from "../../database/schemas"; -import { remoteAssistIsEnabled } from "./logic/available"; -import Configuration from "../../configuration"; -import { SQLStatementChecker, SqlSyntaxError } from "../../connection/syntaxChecker"; import { StatementGroup, StatementType } from "../sql/types"; -import { MAX_STATEMENT_COUNT, VALID_STATEMENT_LENGTH } from "../../connection/syntaxChecker/checker"; +import { remoteAssistIsEnabled } from "./logic/available"; import { getSqlDocument, isSafeDocument } from "./logic/parse"; -import path from "path"; export interface CompletionType { order: string; @@ -51,7 +50,7 @@ function shouldShowWarnings() { const CHECKER_AVAILABLE_CONTEXT = `vscode-db2i.syntax.checkerAvailable`; const CHECKER_RUNNING_CONTEXT = `vscode-db2i.syntax.checkerRunning`; -const checkerAvailable = async () => (await SQLStatementChecker.get() !== undefined); +const checkerAvailable = async () => (await ValidateStatementComponent.get() !== undefined); export async function setCheckerAvailableContext(additionalState = true) { const available = await checkerAvailable() && additionalState; @@ -72,7 +71,7 @@ export const checkDocumentDefintion = commands.registerCommand(CHECK_DOCUMENT_CO }); export const problemProvider: Disposable[] = [ - sqlDiagnosticCollection, + sqlDiagnosticCollection, workspace.onDidCloseTextDocument(e => { // Only clear errors from unsaved files. @@ -119,7 +118,7 @@ interface SqlDiagnostic extends Diagnostic { } async function validateSqlDocument(document: TextDocument, specificStatement?: number) { - const checker = await SQLStatementChecker.get(); + const checker = await ValidateStatementComponent.get(); const job = remoteAssistIsEnabled(true); if (checker && job) { const basename = document.fileName ? path.basename(document.fileName) : `Untitled`; @@ -183,7 +182,7 @@ async function validateSqlDocument(document: TextDocument, specificStatement?: n let syntaxChecked: SqlSyntaxError[] | undefined; try { syntaxChecked = await window.withProgress({ location: ProgressLocation.Window, title: `$(sync-spin) Checking SQL Syntax` }, () => { return checker.checkMultipleStatements(job, sqlStatementContents) }); - } catch (e) { + } catch (e: any) { window.showErrorMessage(`${basename}: the SQL syntax checker failed to run. ${e.message}`); syntaxChecked = undefined; } @@ -274,13 +273,13 @@ function shouldShowError(error: SqlSyntaxError) { } function getStatementRangeFromGroup(currentGroup: StatementGroup, groupId: number): StatementRange | undefined { - let statementRange: StatementRange; + let statementRange: StatementRange | undefined; const firstStatement = currentGroup.statements[0]; if (firstStatement) { statementRange = { groupId, start: firstStatement.range.start, end: currentGroup.range.end, validate: true }; // Support for ACS' STOP statement. - if (firstStatement.type === StatementType.Unknown && firstStatement.tokens.length === 1 && firstStatement.tokens[0].value.toUpperCase() === `STOP`) { + if (firstStatement.type === StatementType.Unknown && firstStatement.tokens.length === 1 && firstStatement.tokens[0].value?.toUpperCase() === `STOP`) { statementRange.validate = false; return; } @@ -295,13 +294,15 @@ function getStatementRangeFromGroup(currentGroup: StatementGroup, groupId: numbe } } - const stmtLength = currentGroup.range.end - statementRange.start; - if (stmtLength >= VALID_STATEMENT_LENGTH) { - // Just too long for our API. - statementRange.validate = false; - } + if (statementRange) { + const stmtLength = currentGroup.range.end - statementRange.start; + if (stmtLength >= VALID_STATEMENT_LENGTH) { + // Just too long for our API. + statementRange.validate = false; + } - return statementRange; + return statementRange; + } } function documentLargeError(basename: string) { diff --git a/src/language/providers/statusProvider.ts b/src/language/providers/statusProvider.ts index 6a96a8e9..fc39290d 100644 --- a/src/language/providers/statusProvider.ts +++ b/src/language/providers/statusProvider.ts @@ -1,7 +1,7 @@ import { Disposable, languages, LanguageStatusSeverity } from "vscode"; -import { SQLStatementChecker } from "../../connection/syntaxChecker"; import { getCheckerTimeout } from "./problemProvider"; import { useSystemNames } from "./logic/available"; +import { ValidateStatementComponent } from "../../connection/components/validateStatement"; export class Db2StatusProvider extends Disposable { private item = languages.createLanguageStatusItem(`sql`, {language: `sql`}); @@ -14,7 +14,7 @@ export class Db2StatusProvider extends Disposable { async setState(hasJob: Boolean) { if (hasJob) { - const checker = await SQLStatementChecker.get(); + const checker = await ValidateStatementComponent.get(); const checkerTimeout = getCheckerTimeout() / 1000; this.item.text = `SQL assistance available. ${checker ? `Syntax checking enabled (every ${checkerTimeout}s after editing)` : `Syntax checking not available.`}`; this.item.detail = `You're connected to an IBM i. ${checker ? `You can use the advanced SQL language tooling.` : `Syntax checking not available. This means that the syntax checker did not install when connecting to this system.`}`; diff --git a/src/language/sql/document.ts b/src/language/sql/document.ts index 78fd42c0..ec3509e5 100644 --- a/src/language/sql/document.ts +++ b/src/language/sql/document.ts @@ -1,6 +1,6 @@ import Statement from "./statement"; import SQLTokeniser from "./tokens"; -import { CallableReference, Definition, IRange, ParsedEmbeddedStatement, StatementGroup, StatementType, StatementTypeWord, Token } from "./types"; +import { CallableReference, Definition, ParsedEmbeddedStatement, StatementGroup, StatementType, StatementTypeWord, Token } from "./types"; export default class Document { content: string; @@ -63,7 +63,7 @@ export default class Document { break; case `statementType`: - currentStatementType = StatementTypeWord[upperValue]; + currentStatementType = (upperValue ? StatementTypeWord.get(upperValue) : StatementType.Unknown) || StatementType.Unknown; break; case `keyword`: diff --git a/src/language/sql/statement.ts b/src/language/sql/statement.ts index 23bd60a7..5f7ad434 100644 --- a/src/language/sql/statement.ts +++ b/src/language/sql/statement.ts @@ -1,14 +1,14 @@ import SQLTokeniser, { NameTypes } from "./tokens"; -import { CTEReference, CallableReference, ClauseType, ClauseTypeWord, IRange, ObjectRef, QualifiedObject, StatementType, StatementTypeWord, Token } from "./types"; +import { CTEReference, ClauseType, ClauseTypeWord, IRange, ObjectRef, StatementType, StatementTypeWord, Token } from "./types"; -export const tokenIs = (token: Token|undefined, type: string, value?: string) => { +export const tokenIs = (token: Token | undefined, type: string, value?: string) => { return (token && token.type === type && (value ? token.value?.toUpperCase() === value : true)); } export default class Statement { public type: StatementType = StatementType.Unknown; - private label: string|undefined; - private _blockTokens: Token[]|undefined; + private label: string | undefined; + private _blockTokens: Token[] | undefined; private get blockTokens() { if (!this._blockTokens) { @@ -18,9 +18,9 @@ export default class Statement { return this._blockTokens; } - constructor(public tokens: Token[], public range: IRange) { + constructor(public tokens: Token[], public range: IRange) { this.tokens = this.tokens.filter(newToken => newToken.type !== `newline`); - + let first = this.tokens[0]; if (tokenIs(first, `word`, `EXEC`) && tokenIs(this.tokens[1], `word`, `SQL`)) { @@ -31,20 +31,21 @@ export default class Statement { } const wordValue = first?.value?.toUpperCase(); + if (wordValue) { + this.type = StatementTypeWord.get(wordValue) || StatementType.Unknown; - this.type = StatementTypeWord[wordValue] || StatementType.Unknown; - - switch (this.type) { - case StatementType.Create: - // No scalar transformation here.. - break; - default: - this.tokens = SQLTokeniser.findScalars(this.tokens); - break; + switch (this.type) { + case StatementType.Create: + // No scalar transformation here.. + break; + default: + this.tokens = SQLTokeniser.findScalars(this.tokens); + break; + } } } - getLabel(): string|undefined { + getLabel(): string | undefined { return this.label; } @@ -55,7 +56,7 @@ export default class Statement { // These statements can end with BEGIN, which signifies a block starter if ([StatementType.Create, StatementType.Declare].includes(this.type)) { - const last = this.tokens[this.tokens.length-1]; + const last = this.tokens[this.tokens.length - 1]; if (tokenIs(last, `keyword`, `BEGIN`)) { return true; } @@ -81,9 +82,9 @@ export default class Statement { } getTokenByOffset(offset: number) { - const blockSearch = (tokens: Token[]): Token|undefined => { + const blockSearch = (tokens: Token[]): Token | undefined => { const token = tokens.find(token => offset >= token.range.start && offset <= token.range.end); - + if (token?.type === `block` && token.block) { return blockSearch(token.block); } @@ -96,22 +97,22 @@ export default class Statement { getClauseForOffset(offset: number): ClauseType { let currentClause = ClauseType.Unknown; - + for (let i = 0; i < this.tokens.length; i++) { if (offset < this.tokens[i].range.start) { break; } - + if (tokenIs(this.tokens[i], `clause`)) { - currentClause = ClauseTypeWord[this.tokens[i].value!.toUpperCase()] + currentClause = ClauseTypeWord.get(this.tokens[i].value!.toUpperCase()) || ClauseType.Unknown; } } return currentClause; } - getBlockRangeAt(offset: number): IRange|undefined { - const blockContainsOffset = (cOffset: number, block: Token[]): IRange|undefined => { + getBlockRangeAt(offset: number): IRange | undefined { + const blockContainsOffset = (cOffset: number, block: Token[]): IRange | undefined => { const tokenInOffset = block.find(token => cOffset >= token.range.start && cOffset <= token.range.end); if (tokenInOffset) { if (tokenInOffset.type === `block`) { @@ -125,7 +126,7 @@ export default class Statement { } return blockRange; - + } else { const rawEnd = this.tokens.findIndex(token => token.range.end === tokenInOffset.range.end); return { @@ -135,10 +136,10 @@ export default class Statement { } } else { const rawStart = this.tokens.findIndex(token => token.range.start === block[0].range.start); - const rawEnd = this.tokens.findIndex(token => token.range.end === block[block.length-1].range.end); + const rawEnd = this.tokens.findIndex(token => token.range.end === block[block.length - 1].range.end); return { start: rawStart, - end: rawEnd+1 + end: rawEnd + 1 }; } } @@ -149,12 +150,12 @@ export default class Statement { return blockContainsOffset(offset, this.blockTokens); } - getCallableDetail(offset: number, withBlocks = false): CallableReference { + getCallableDetail(offset: number, withBlocks = false) { const range = this.getBlockRangeAt(offset); if (range) { - const hasDot = (tokenIs(this.tokens[range.start-4], `word`) || tokenIs(this.tokens[range.start-4], `sqlName`)) && tokenIs(this.tokens[range.start-3], `dot`); - const parentRef = hasDot ? this.getRefAtToken(range.start-4) : this.getRefAtToken(range.start-2); + const hasDot = (tokenIs(this.tokens[range.start - 4], `word`) || tokenIs(this.tokens[range.start - 4], `sqlName`)) && tokenIs(this.tokens[range.start - 3], `dot`); + const parentRef = hasDot ? this.getRefAtToken(range.start - 4) : this.getRefAtToken(range.start - 2); if (parentRef) { return { @@ -188,7 +189,7 @@ export default class Statement { let i = this.tokens.findIndex(token => offset >= token.range.start && offset <= token.range.end); let currentType; - let prevMustBe: "dot"|"name"; + let prevMustBe: "dot" | "name" | undefined; // Reset to end if (i >= 0) { @@ -196,29 +197,31 @@ export default class Statement { if (currentType === `dot` || currentType === `forwardslash`) prevMustBe = `name`; else if (NameTypes.includes(currentType)) prevMustBe = `dot`; } else { - i = this.tokens.length-1; + i = this.tokens.length - 1; prevMustBe = `name`; } - while (i >= 0 && this.tokens[i-1]) { - i--; - currentType = this.tokens[i].type; - - switch (prevMustBe) { - case `dot`: - if (currentType === `dot`) { - prevMustBe = `name`; - } else { - return this.getRefAtToken(i+1); - } - break; - case `name`: - if (NameTypes.includes(currentType)) { - prevMustBe = `dot`; - } else { - return this.getRefAtToken(i+1); - } - break; + if (prevMustBe) { + while (i >= 0 && this.tokens[i - 1]) { + i--; + currentType = this.tokens[i].type; + + switch (prevMustBe) { + case `dot`: + if (currentType === `dot`) { + prevMustBe = `name`; + } else { + return this.getRefAtToken(i + 1); + } + break; + case `name`: + if (NameTypes.includes(currentType)) { + prevMustBe = `dot`; + } else { + return this.getRefAtToken(i + 1); + } + break; + } } } } @@ -227,20 +230,20 @@ export default class Statement { if (this.type !== StatementType.With) return []; const withBlocks = this.blockTokens; - + let cteList: CTEReference[] = []; for (let i = 0; i < withBlocks.length; i++) { if (tokenIs(withBlocks[i], `word`) || tokenIs(withBlocks[i], `function`)) { let cteName = withBlocks[i].value!; let parameters: string[] = []; - let statementBlockI = i+1; + let statementBlockI = i + 1; - if (tokenIs(withBlocks[i+1], `block`) && tokenIs(withBlocks[i+2], `keyword`, `AS`)) { - parameters = withBlocks[i+1].block!.filter(blockToken => blockToken.type === `word`).map(blockToken => blockToken.value!) - statementBlockI = i+3; - } else if (tokenIs(withBlocks[i+1], `keyword`, `AS`)) { - statementBlockI = i+2; + if (tokenIs(withBlocks[i + 1], `block`) && tokenIs(withBlocks[i + 2], `keyword`, `AS`)) { + parameters = withBlocks[i + 1].block!.filter(blockToken => blockToken.type === `word`).map(blockToken => blockToken.value!) + statementBlockI = i + 3; + } else if (tokenIs(withBlocks[i + 1], `keyword`, `AS`)) { + statementBlockI = i + 2; } const statementBlock = withBlocks[statementBlockI]; @@ -248,7 +251,7 @@ export default class Statement { cteList.push({ name: cteName, columns: parameters, - statement: new Statement(Statement.trimTokens(statementBlock.block), statementBlock.range) + statement: new Statement(Statement.trimTokens(statementBlock.block || []), statementBlock.range) }) } @@ -302,11 +305,11 @@ export default class Statement { for (const parameter of parameters) { // If the first token is the parm type, then the name follows if (tokenIs(parameter[0], `keyword`)) continue; - + let nameIndex = tokenIs(parameter[0], `parmType`) ? 1 : 0; const name = parameter[nameIndex].value!; // Include parmType if it is provided - const definitionTokens = (nameIndex === 1 ? [parameter[0]] : []).concat(parameter.slice(nameIndex+1)); + const definitionTokens = (nameIndex === 1 ? [parameter[0]] : []).concat(parameter.slice(nameIndex + 1)); list.push({ tokens: parameter, @@ -336,7 +339,7 @@ export default class Statement { } const basicQueryFinder = (startIndex: number): void => { - let currentClause: undefined|"select"|"from"; + let currentClause: undefined | "select" | "from"; for (let i = startIndex; i < this.tokens.length; i++) { if (tokenIs(this.tokens[i], `clause`, `FROM`)) { currentClause = `from`; @@ -347,12 +350,12 @@ export default class Statement { currentClause = undefined; } - if (tokenIs(this.tokens[i], `clause`, `FROM`) || - (this.type !== StatementType.Select && tokenIs(this.tokens[i], `clause`, `INTO`)) || - tokenIs(this.tokens[i], `join`) || - (currentClause === `from` && tokenIs(this.tokens[i], `comma`) - )) { - const sqlObj = this.getRefAtToken(i+1); + if (tokenIs(this.tokens[i], `clause`, `FROM`) || + (this.type !== StatementType.Select && tokenIs(this.tokens[i], `clause`, `INTO`)) || + tokenIs(this.tokens[i], `join`) || + (currentClause === `from` && tokenIs(this.tokens[i], `comma`) + )) { + const sqlObj = this.getRefAtToken(i + 1); if (sqlObj) { doAdd(sqlObj); i += sqlObj.tokens.length; @@ -370,7 +373,7 @@ export default class Statement { } } } else if (currentClause === `from` && tokenIs(this.tokens[i], `function`)) { - const sqlObj = this.getRefAtToken(i, {includeParameters: true}); + const sqlObj = this.getRefAtToken(i, { includeParameters: true }); if (sqlObj) { i += sqlObj.tokens.length; if (sqlObj.isUDTF || sqlObj.fromLateral) { @@ -380,27 +383,27 @@ export default class Statement { } } } - + let inFromClause = false; switch (this.type) { case StatementType.Call: // CALL X() - doAdd(this.getRefAtToken(1, {includeParameters: true})); + doAdd(this.getRefAtToken(1, { includeParameters: true })); break; case StatementType.Alter: if (this.tokens.length >= 3) { - let object = this.getRefAtToken(2, {withSystemName: true}); + let object = this.getRefAtToken(2, { withSystemName: true }); if (object) { object.createType = this.tokens[1].value; doAdd(object); - for (let i = object.tokens.length+2; i < this.tokens.length; i++) { + for (let i = object.tokens.length + 2; i < this.tokens.length; i++) { if (tokenIs(this.tokens[i], `keyword`, `REFERENCES`)) { - doAdd(this.getRefAtToken(i+1, {withSystemName: true})); + doAdd(this.getRefAtToken(i + 1, { withSystemName: true })); } } } @@ -418,30 +421,30 @@ export default class Statement { break; case StatementType.Create: - let object: ObjectRef; - let postName: number|undefined; + let object: ObjectRef | undefined; + let postName: number | undefined; if (tokenIs(this.tokens[1], `keyword`, `OR`) && tokenIs(this.tokens[2], `keyword`, `REPLACE`)) { - object = this.getRefAtToken(4, {withSystemName: true}); + object = this.getRefAtToken(4, { withSystemName: true }); if (object) { - postName = object.tokens.length+4; + postName = object.tokens.length + 4; object.createType = this.tokens[3].value; } } else - if (tokenIs(this.tokens[1], `keyword`, `UNIQUE`)) { - object = this.getRefAtToken(3, {withSystemName: true}); - if (object) { - postName = object.tokens.length+3; - object.createType = this.tokens[2].value; - } - - } else { - object = this.getRefAtToken(2, {withSystemName: true}); - if (object) { - postName = object.tokens.length+2; - object.createType = this.tokens[1].value + if (tokenIs(this.tokens[1], `keyword`, `UNIQUE`)) { + object = this.getRefAtToken(3, { withSystemName: true }); + if (object) { + postName = object.tokens.length + 3; + object.createType = this.tokens[2].value; + } + + } else { + object = this.getRefAtToken(2, { withSystemName: true }); + if (object) { + postName = object.tokens.length + 2; + object.createType = this.tokens[1].value + } } - } doAdd(object); @@ -451,14 +454,14 @@ export default class Statement { case `PROCEDURE`: // For functions, perhaps we can use the SPECIFIC keyword for the system name for (let i = postName; i < this.tokens.length; i++) { - if (tokenIs(this.tokens[i], `keyword`, `SPECIFIC`) && this.tokens[i+1]) { - object.object.system = this.tokens[i+1].value; + if (tokenIs(this.tokens[i], `keyword`, `SPECIFIC`) && this.tokens[i + 1]) { + object.object.system = this.tokens[i + 1].value; i++; } // Support for external name - if (tokenIs(this.tokens[i], `keyword`, `EXTERNAL`) && tokenIs(this.tokens[i+1], `word`, `NAME`) && this.tokens[i+2]) { - const externalRef = this.getRefAtToken(i+2); + if (tokenIs(this.tokens[i], `keyword`, `EXTERNAL`) && tokenIs(this.tokens[i + 1], `word`, `NAME`) && this.tokens[i + 2]) { + const externalRef = this.getRefAtToken(i + 2); if (externalRef) { externalRef.createType = `external`; externalRef.alias = undefined; @@ -474,7 +477,7 @@ export default class Statement { // If the type is `INDEX`, the next reference is the `ON` keyword for (let i = postName; i < this.tokens.length; i++) { if (tokenIs(this.tokens[i], `keyword`, `ON`)) { - doAdd(this.getRefAtToken(i+1)); + doAdd(this.getRefAtToken(i + 1)); break; } } @@ -502,7 +505,7 @@ export default class Statement { if (tokenIs(this.tokens[2], `keyword`, `HANDLER`)) { // DECLARE XX HANDLER FOR.... let def: ObjectRef = { - object: {name: this.tokens[1].value}, + object: { name: this.tokens[1].value }, tokens: this.tokens.slice(1, 3) } @@ -510,62 +513,63 @@ export default class Statement { doAdd(def); } else - if (tokenIs(this.tokens[2], `word`, `PROCEDURE`)) { - // DECLARE XX PROCEDURE.. - - } else - if (tokenIs(this.tokens[1], `word`, `GLOBAL`) && tokenIs(this.tokens[2], `word`, `TEMPORARY`), tokenIs(this.tokens[3], `word`, `TABLE`)) { - // Handle DECLARE GLOBAL TEMP TABLE x.x ()... - let def: ObjectRef = this.getRefAtToken(4); - def.createType = `Temporary Table`; - doAdd(def); + if (tokenIs(this.tokens[2], `word`, `PROCEDURE`)) { + // DECLARE XX PROCEDURE.. - } else - if (tokenIs(this.tokens[1], `word`)) { - // DECLARE XX TYPE DEFAULT .. - let def: ObjectRef = { - object: {name: this.tokens[1].value}, - tokens: this.tokens.slice(1) - } + } else + if (tokenIs(this.tokens[1], `word`, `GLOBAL`) && tokenIs(this.tokens[2], `word`, `TEMPORARY`), tokenIs(this.tokens[3], `word`, `TABLE`)) { + // Handle DECLARE GLOBAL TEMP TABLE x.x ()... + const def = this.getRefAtToken(4); + if (def) { + def.createType = `Temporary Table`; + doAdd(def); + } + } else + if (tokenIs(this.tokens[1], `word`)) { + // DECLARE XX TYPE DEFAULT .. + let def: ObjectRef = { + object: { name: this.tokens[1].value }, + tokens: this.tokens.slice(1) + } - if (tokenIs(this.tokens[2], `keyword`, `CURSOR`)) { - def.createType = `Cursor`; - } else { - let defaultIndex = this.tokens.findIndex(t => tokenIs(t, `keyword`, `DEFAULT`)); + if (tokenIs(this.tokens[2], `keyword`, `CURSOR`)) { + def.createType = `Cursor`; + } else { + let defaultIndex = this.tokens.findIndex(t => tokenIs(t, `keyword`, `DEFAULT`)); - const valueTokens = this.tokens - .slice( - 2, - defaultIndex >= 0 ? defaultIndex : undefined - ); + const valueTokens = this.tokens + .slice( + 2, + defaultIndex >= 0 ? defaultIndex : undefined + ); - def.createType = Statement.formatSimpleTokens(valueTokens); - } + def.createType = Statement.formatSimpleTokens(valueTokens); + } - doAdd(def); - } + doAdd(def); + } break; } return list; } - private getRefAtToken(i: number, options: {withSystemName?: boolean, includeParameters?: boolean} = {}): ObjectRef|undefined { - let sqlObj: ObjectRef; + private getRefAtToken(i: number, options: { withSystemName?: boolean, includeParameters?: boolean } = {}): ObjectRef | undefined { + let sqlObj: ObjectRef | undefined; let nextIndex = i; - let nextToken = this.tokens[i]; + let nextToken: Token | undefined = this.tokens[i]; let endIndex = i; const isSubSelect = (tokenIs(nextToken, `function`, `TABLE`) || tokenIs(nextToken, `function`, `LATERAL`) || (options.includeParameters && tokenIs(nextToken, `function`)) && this.type !== StatementType.Call); if (isSubSelect) { - sqlObj = this.getRefAtToken(i+2); + sqlObj = this.getRefAtToken(i + 2); if (sqlObj) { sqlObj.isUDTF = true; const blockTokens = this.getBlockAt(sqlObj.tokens[0].range.end); - + sqlObj.tokens = blockTokens; nextIndex = i + 2 + blockTokens.length; nextToken = this.tokens[nextIndex]; @@ -574,7 +578,7 @@ export default class Statement { nextIndex = -1; nextToken = undefined; } - + } else { if (nextToken && NameTypes.includes(nextToken.type)) { nextIndex = i; @@ -587,11 +591,11 @@ export default class Statement { } } - if (tokenIs(this.tokens[i+1], `dot`) || tokenIs(this.tokens[i+1], `forwardslash`)) { - nextIndex = i+2; + if (tokenIs(this.tokens[i + 1], `dot`) || tokenIs(this.tokens[i + 1], `forwardslash`)) { + nextIndex = i + 2; nextToken = this.tokens[nextIndex]; - endIndex = nextToken ? nextIndex : i+1; + endIndex = nextToken ? nextIndex : i + 1; sqlObj = { tokens: [], @@ -604,42 +608,42 @@ export default class Statement { } }; } - + if (sqlObj) { if (options.withSystemName !== true) { // If the next token is not a clause.. we might have the alias - if (nextToken && this.tokens[nextIndex+1]) { - if (tokenIs(this.tokens[nextIndex+1], `keyword`, `AS`) && tokenIs(this.tokens[nextIndex+2], `word`)) { - endIndex = nextIndex+2; - sqlObj.alias = this.tokens[nextIndex+2].value; + if (nextToken && this.tokens[nextIndex + 1]) { + if (tokenIs(this.tokens[nextIndex + 1], `keyword`, `AS`) && tokenIs(this.tokens[nextIndex + 2], `word`)) { + endIndex = nextIndex + 2; + sqlObj.alias = this.tokens[nextIndex + 2].value; } else - if (tokenIs(this.tokens[nextIndex+1], `word`)) { - endIndex = nextIndex+1; - sqlObj.alias = this.tokens[nextIndex+1].value; - } + if (tokenIs(this.tokens[nextIndex + 1], `word`)) { + endIndex = nextIndex + 1; + sqlObj.alias = this.tokens[nextIndex + 1].value; + } } } if (!isSubSelect && !sqlObj.isUDTF) { - sqlObj.tokens = this.tokens.slice(i, endIndex+1); + sqlObj.tokens = this.tokens.slice(i, endIndex + 1); - if (options.includeParameters && tokenIs(this.tokens[endIndex+1], `openbracket`)) { - const blockTokens = this.getBlockAt(this.tokens[endIndex+1].range.end+1); + if (options.includeParameters && tokenIs(this.tokens[endIndex + 1], `openbracket`)) { + const blockTokens = this.getBlockAt(this.tokens[endIndex + 1].range.end + 1); if (blockTokens.length > 0) { sqlObj.tokens = sqlObj.tokens.concat([ - {type: `openbracket`, value: `(`, range: {start: this.tokens[endIndex+1].range.start, end: this.tokens[endIndex+1].range.end}}, + { type: `openbracket`, value: `(`, range: { start: this.tokens[endIndex + 1].range.start, end: this.tokens[endIndex + 1].range.end } }, ...blockTokens, - {type: `closebracket`, value: `)`, range: {start: blockTokens[blockTokens.length-1].range.end, end: blockTokens[blockTokens.length-1].range.end}} + { type: `closebracket`, value: `)`, range: { start: blockTokens[blockTokens.length - 1].range.end, end: blockTokens[blockTokens.length - 1].range.end } } ]); } } } if (options.withSystemName) { - if (tokenIs(this.tokens[endIndex+1], `statementType`, `FOR`) && tokenIs(this.tokens[endIndex+2], `word`, `SYSTEM`) && tokenIs(this.tokens[endIndex+3], `word`, `NAME`)) { - if (this.tokens[endIndex+4] && NameTypes.includes(this.tokens[endIndex+4].type)) { - sqlObj.object.system = this.tokens[endIndex+4].value; + if (tokenIs(this.tokens[endIndex + 1], `statementType`, `FOR`) && tokenIs(this.tokens[endIndex + 2], `word`, `SYSTEM`) && tokenIs(this.tokens[endIndex + 3], `word`, `NAME`)) { + if (this.tokens[endIndex + 4] && NameTypes.includes(this.tokens[endIndex + 4].type)) { + sqlObj.object.system = this.tokens[endIndex + 4].value; } } } @@ -659,13 +663,13 @@ export default class Statement { // Only these statements support the INTO clause in embedded SQL really const validIntoStatements: StatementType[] = [StatementType.Unknown, StatementType.With, StatementType.Select]; - let ranges: {type: "remove"|"marker", range: IRange, named?: string}[] = []; - let intoClause: Token|undefined; - let declareStmt: Token|undefined; + let ranges: { type: "remove" | "marker", range: IRange, named?: string }[] = []; + let intoClause: Token | undefined; + let declareStmt: Token | undefined; let lastTokenWasMarker = false; for (let i = 0; i < this.tokens.length; i++) { - const prevToken = this.tokens[i-1]; + const prevToken = this.tokens[i - 1]; const currentToken = this.tokens[i]; if (!tokenIs(currentToken, `colon`)) { @@ -674,7 +678,7 @@ export default class Statement { switch (currentToken.type) { case `statementType`: - const currentValue = currentToken.value.toLowerCase(); + const currentValue = currentToken.value?.toLowerCase(); if (declareStmt) { if (currentValue === `for`) { ranges.push({ @@ -684,7 +688,7 @@ export default class Statement { end: currentToken.range.end } }); - + declareStmt = undefined; } @@ -702,10 +706,10 @@ export default class Statement { if (declareStmt) continue; // We need to remove the INTO clause completely. - if (currentToken.value.toLowerCase() === `into`) { + if (currentToken.value?.toLowerCase() === `into`) { intoClause = currentToken; } else if (intoClause) { - const endToken = this.tokens[i-1]; + const endToken = this.tokens[i - 1]; ranges.push({ type: `remove`, @@ -734,9 +738,9 @@ export default class Statement { if (declareStmt) continue; if (prevToken && prevToken.type === `string`) continue; - let nextMustBe: "word"|"dot" = `word`; - let followingTokenI = i+1; - let endToken: Token; + let nextMustBe: "word" | "dot" = `word`; + let followingTokenI = i + 1; + let endToken: Token | undefined; // Handles when we have a host variable // This logic supports qualified host variables @@ -766,7 +770,7 @@ export default class Statement { }); lastTokenWasMarker = true; - i = (followingTokenI-1); + i = (followingTokenI - 1); } break; @@ -774,12 +778,12 @@ export default class Statement { default: if (i === 0 && tokenIs(currentToken, `word`, `EXEC`)) { // We check and remove the starting `EXEC SQL` - if (tokenIs(this.tokens[i+1], `word`, `SQL`)) { + if (tokenIs(this.tokens[i + 1], `word`, `SQL`)) { ranges.push({ type: `remove`, range: { start: currentToken.range.start, - end: this.tokens[i+1].range.end + end: this.tokens[i + 1].range.end } }); } @@ -792,31 +796,31 @@ export default class Statement { } static trimTokens(tokens: Token[]) { - if (tokens.length > 0) { - let realFirstToken = tokens.findIndex(t => t.type !== `newline`); - if (realFirstToken < 0) realFirstToken = 0; + if (tokens.length > 0) { + let realFirstToken = tokens.findIndex(t => t.type !== `newline`); + if (realFirstToken < 0) realFirstToken = 0; - let realLastToken = 0; + let realLastToken = 0; - for (let i = tokens.length - 1; i >= 0; i--) { - if (tokens[i].type !== `newline`) { - realLastToken = i + 1; - break; - } - } + for (let i = tokens.length - 1; i >= 0; i--) { + if (tokens[i].type !== `newline`) { + realLastToken = i + 1; + break; + } + } - tokens = tokens.slice(realFirstToken, realLastToken); - } - return tokens; - } + tokens = tokens.slice(realFirstToken, realLastToken); + } + return tokens; + } public static formatSimpleTokens(tokens: Token[]) { let outString = ``; for (let i = 0; i < tokens.length; i++) { const cT = tokens[i]; - const nT = tokens[i+1]; - const pT = tokens[i-1]; - + const nT = tokens[i + 1]; + const pT = tokens[i - 1]; + switch (cT.type) { case `block`: outString += `(${Statement.formatSimpleTokens(cT.block!)})`; @@ -830,7 +834,7 @@ export default class Statement { break; case `closebracket`: outString += cT.value - + if (nT && nT.type !== cT.type) { outString += ` ` } diff --git a/src/language/sql/tests/statements.test.ts b/src/language/sql/tests/statements.test.ts index c960fcaa..3c6e7453 100644 --- a/src/language/sql/tests/statements.test.ts +++ b/src/language/sql/tests/statements.test.ts @@ -1,7 +1,6 @@ -import { assert, describe, expect, test } from 'vitest' -import SQLTokeniser from '../tokens' +import { describe, expect, test } from 'vitest'; import Document, { getPositionData } from '../document'; -import { CallableReference, ClauseType, StatementType } from '../types'; +import { ClauseType, StatementType } from '../types'; const parserScenarios = describe.each([ {newDoc: (content: string) => new Document(content)}, @@ -1249,8 +1248,8 @@ parserScenarios(`Offset reference tests`, ({newDoc}) => { const ref = statement.getReferenceByOffset(21); expect(ref).toBeDefined(); - expect(ref.object.schema).toBe(`sample`); - expect(ref.object.name).toBeUndefined(); + expect(ref!.object.schema).toBe(`sample`); + expect(ref!.object.name).toBeUndefined(); }); test(`Writing select, invalid middle`, () => { @@ -1268,8 +1267,8 @@ parserScenarios(`Offset reference tests`, ({newDoc}) => { const ref = statement.getReferenceByOffset(9); expect(ref).toBeDefined(); - expect(ref.object.schema).toBe(`b`); - expect(ref.object.name).toBeUndefined(); + expect(ref!.object.schema).toBe(`b`); + expect(ref!.object.name).toBeUndefined(); }); }); @@ -1400,8 +1399,8 @@ parserScenarios(`PL body tests`, ({newDoc}) => { const blockParent = callStatement.getCallableDetail(callStatement.tokens[3].range.start); expect(blockParent).toBeDefined(); - expect(blockParent.tokens.length).toBe(3); - expect(blockParent.parentRef.object.name).toBe(`MEDIAN_RESULT_SET`); + expect(blockParent!.tokens.length).toBe(3); + expect(blockParent!.parentRef.object.name).toBe(`MEDIAN_RESULT_SET`); }); test(`CREATE PROCEDURE after syntax error`, () => { @@ -2037,26 +2036,26 @@ describe(`Parameter statement tests`, () => { const callableA = a.getCallableDetail(23); expect(callableA).toBeDefined(); - expect(callableA.parentRef.object.schema).toBe(`qsys2`); - expect(callableA.parentRef.object.name).toBe(`create_abcd`); + expect(callableA!.parentRef.object.schema).toBe(`qsys2`); + expect(callableA!.parentRef.object.name).toBe(`create_abcd`); const blockB = a.getBlockRangeAt(24); expect(blockB).toMatchObject({ start: 5, end: 5 }); const callableB = a.getCallableDetail(24); expect(callableB).toBeDefined(); - expect(callableB.parentRef.object.schema).toBe(`qsys2`); - expect(callableB.parentRef.object.name).toBe(`create_abcd`); + expect(callableB!.parentRef.object.schema).toBe(`qsys2`); + expect(callableB!.parentRef.object.name).toBe(`create_abcd`); const blockC = b.getBlockRangeAt(49); expect(blockC).toMatchObject({ start: 5, end: 13 }); const callableC = b.getCallableDetail(49, true); expect(callableC).toBeDefined(); - expect(callableC.tokens.length).toBe(4); - expect(callableC.tokens.some(t => t.type === `block` && t.block.length === 3)).toBeTruthy(); - expect(callableC.parentRef.object.schema).toBe(`qsys2`); - expect(callableC.parentRef.object.name).toBe(`create_abcd`); + expect(callableC!.tokens.length).toBe(4); + expect(callableC!.tokens.some(t => t.type === `block` && t.block?.length === 3)).toBeTruthy(); + expect(callableC!.parentRef.object.schema).toBe(`qsys2`); + expect(callableC!.parentRef.object.name).toBe(`create_abcd`); }); test('Partial parameters 1: Position data for procedure call', () => { @@ -2067,12 +2066,12 @@ describe(`Parameter statement tests`, () => { expect(statements.length).toBe(1); - const callableReference: CallableReference = statements[0].getCallableDetail(29); + const callableReference = statements[0].getCallableDetail(29); expect(callableReference).toBeDefined(); - expect(callableReference.parentRef.object.name).toBe(`ifs_write`); - expect(callableReference.parentRef.object.schema).toBe(`qsys2`); + expect(callableReference!.parentRef.object.name).toBe(`ifs_write`); + expect(callableReference!.parentRef.object.schema).toBe(`qsys2`); - const positionData = getPositionData(callableReference, 29); + const positionData = getPositionData(callableReference!, 29); expect(positionData).toBeDefined(); expect(positionData.currentParm).toBe(1); @@ -2087,12 +2086,12 @@ describe(`Parameter statement tests`, () => { expect(statements.length).toBe(1); - const callableReference: CallableReference = statements[0].getCallableDetail(31); + const callableReference = statements[0].getCallableDetail(31); expect(callableReference).toBeDefined(); - expect(callableReference.parentRef.object.name).toBe(`ifs_write`); - expect(callableReference.parentRef.object.schema).toBe(`qsys2`); + expect(callableReference!.parentRef.object.name).toBe(`ifs_write`); + expect(callableReference!.parentRef.object.schema).toBe(`qsys2`); - const positionData = getPositionData(callableReference, 31); + const positionData = getPositionData(callableReference!, 31); expect(positionData).toBeDefined(); expect(positionData.currentParm).toBe(1); @@ -2107,12 +2106,12 @@ describe(`Parameter statement tests`, () => { expect(statements.length).toBe(1); - const callableReference: CallableReference = statements[0].getCallableDetail(25); + const callableReference = statements[0].getCallableDetail(25); expect(callableReference).toBeDefined(); - expect(callableReference.parentRef.object.name).toBe(`ifs_write`); - expect(callableReference.parentRef.object.schema).toBe(`qsys2`); + expect(callableReference!.parentRef.object.name).toBe(`ifs_write`); + expect(callableReference!.parentRef.object.schema).toBe(`qsys2`); - const positionData = getPositionData(callableReference, 25); + const positionData = getPositionData(callableReference!, 25); expect(positionData).toBeDefined(); expect(positionData.currentParm).toBe(0); @@ -2127,18 +2126,18 @@ describe(`Parameter statement tests`, () => { expect(statements.length).toBe(1); - const callableReference: CallableReference = statements[0].getCallableDetail(25); + const callableReference = statements[0].getCallableDetail(25); expect(callableReference).toBeDefined(); - expect(callableReference.parentRef.object.name).toBe(`ifs_write`); - expect(callableReference.parentRef.object.schema).toBe(`qsys2`); + expect(callableReference!.parentRef.object.name).toBe(`ifs_write`); + expect(callableReference!.parentRef.object.schema).toBe(`qsys2`); - const positionDataA = getPositionData(callableReference, 25); + const positionDataA = getPositionData(callableReference!, 25); expect(positionDataA).toBeDefined(); expect(positionDataA.currentParm).toBe(0); expect(positionDataA.currentCount).toBe(3); - const positionDataB = getPositionData(callableReference, 29); + const positionDataB = getPositionData(callableReference!, 29); expect(positionDataB).toBeDefined(); expect(positionDataB.currentParm).toBe(1); @@ -2153,12 +2152,12 @@ describe(`Parameter statement tests`, () => { expect(statements.length).toBe(1); - const callableReference: CallableReference = statements[0].getCallableDetail(50); + const callableReference = statements[0].getCallableDetail(50); expect(callableReference).toBeDefined(); - expect(callableReference.parentRef.object.name).toBe(`ifs_write`); - expect(callableReference.parentRef.object.schema).toBe(`qsys2`); + expect(callableReference!.parentRef.object.name).toBe(`ifs_write`); + expect(callableReference!.parentRef.object.schema).toBe(`qsys2`); - const positionDataA = getPositionData(callableReference, 50); + const positionDataA = getPositionData(callableReference!, 50); expect(positionDataA).toBeDefined(); expect(positionDataA.currentParm).toBe(2); diff --git a/src/language/sql/tests/tokens.test.ts b/src/language/sql/tests/tokens.test.ts index df5c5901..7cb1e3c3 100644 --- a/src/language/sql/tests/tokens.test.ts +++ b/src/language/sql/tests/tokens.test.ts @@ -1,5 +1,5 @@ -import { assert, expect, test } from 'vitest' -import SQLTokeniser from '../tokens' +import { expect, test } from 'vitest'; +import SQLTokeniser from '../tokens'; // Edit an assertion and save to see HMR in action @@ -120,5 +120,5 @@ test('For in data-type (issue #315)', () => { expect(tokens.length).toBe(35); expect(tokens[9].type).toBe(`word`); - expect(tokens[9].value.toLowerCase()).toBe(`for`); + expect(tokens[9].value?.toLowerCase()).toBe(`for`); }); \ No newline at end of file diff --git a/src/language/sql/tokens.ts b/src/language/sql/tokens.ts index a1bc4dcc..5517ad24 100644 --- a/src/language/sql/tokens.ts +++ b/src/language/sql/tokens.ts @@ -310,7 +310,7 @@ export default class SQLTokeniser { } } - if (i > 0 && i < tokens.length - 2 && tokens[i].value.toLowerCase() === 'for' && tokens[i - 1].type === 'closebracket' && tokens[i + 2].value.toLowerCase() === 'data') { + if (i > 0 && i < tokens.length - 2 && tokens[i].value?.toLowerCase() === 'for' && tokens[i - 1].type === 'closebracket' && tokens[i + 2].value?.toLowerCase() === 'data') { goodMatch = false; // data-type with FOR BIT/SBCS/MIXED DATA } diff --git a/src/language/sql/types.ts b/src/language/sql/types.ts index 2bb0e6ca..8803502d 100644 --- a/src/language/sql/types.ts +++ b/src/language/sql/types.ts @@ -59,7 +59,7 @@ export enum StatementType { Lock = "Lock", } -export const StatementTypeWord = { +export const StatementTypeWord = new Map(Object.entries({ 'CREATE': StatementType.Create, 'SELECT': StatementType.Select, 'WITH': StatementType.With, @@ -115,7 +115,7 @@ export const StatementTypeWord = { 'LOCK': StatementType.Lock, 'OPEN': StatementType.Open, 'CLOSE': StatementType.Close, -}; +})); export enum ClauseType { Unknown = "Unknown", @@ -129,7 +129,7 @@ export enum ClauseType { Order = "Order" } -export const ClauseTypeWord = { +export const ClauseTypeWord = new Map(Object.entries({ 'FROM': ClauseType.From, 'INTO': ClauseType.Into, 'WHERE': ClauseType.Where, @@ -138,7 +138,7 @@ export const ClauseTypeWord = { 'LIMIT': ClauseType.Limit, 'OFFSET': ClauseType.Offset, 'ORDER': ClauseType.Order -} +})); export interface CTEReference { name: string; diff --git a/src/testing/jobs.ts b/src/testing/jobs.ts index afc34819..50bc19b6 100644 --- a/src/testing/jobs.ts +++ b/src/testing/jobs.ts @@ -1,44 +1,17 @@ import assert from "assert"; import { TestSuite } from "."; import { getInstance } from "../base"; -import { ServerComponent } from "../connection/serverComponent"; -import { OldSQLJob } from "../connection/sqlJob"; +import { JobManager } from "../config"; import { VisualExplainData } from "../types"; import { ExplainTree } from "../views/results/explain/nodes"; export const JobsSuite: TestSuite = { name: `Connection tests`, tests: [ - {name: `Backend check`, test: async () => { - const backendInstalled = await ServerComponent.initialise(); - - // To run these tests, we need the backend server. If this test fails. Don't bother - assert.strictEqual(backendInstalled, true); - - console.log(`Starting command: ${ServerComponent.getInitCommand()}`); - }}, - - {name: `Backend version check`, test: async () => { - assert.strictEqual(ServerComponent.isInstalled(), true); - - let newJob = new OldSQLJob(); - await newJob.connect(); - - let ver = await newJob.getVersion(); - console.log(`backend version: `+ver.version); - console.log(`backend build date: `+ver.build_date); - assert.notEqual(ver.version, ``); - assert.notEqual(ver.build_date, ``); - assert.ok(`backend build date: `+ver.build_date); - newJob.close(); - }}, - {name: `Verify client special registers are set`, test: async () => { - assert.strictEqual(ServerComponent.isInstalled(), true); - let newJob = new OldSQLJob(); - await newJob.connect(); + const newJob = await JobManager.newJob(); - let qry = newJob.query(`SELECT V_CLIENT_APPLNAME, V_CLIENT_PROGRAMID FROM TABLE(QSYS2.GET_JOB_INFO('*')) limit 1`); + let qry = newJob.query(`SELECT V_CLIENT_APPLNAME, V_CLIENT_PROGRAMID FROM TABLE(QSYS2.GET_JOB_INFO('*')) limit 1`); let qryResults = await qry.execute(); assert.equal(qryResults.success, true); assert.equal(qryResults.data.length, 1); @@ -46,14 +19,11 @@ export const JobsSuite: TestSuite = { assert.equal((``+dataRow['V_CLIENT_APPLNAME']).startsWith(`vscode-db2i`), true); assert.equal((``+dataRow['V_CLIENT_PROGRAMID']).startsWith(`VSCode`), true); - newJob.close(); + JobManager.closeJobById(newJob); }}, - {name: `Backend set trace options and retrieve`, test: async () => { - assert.strictEqual(ServerComponent.isInstalled(), true); - - let newJob = new OldSQLJob(); - await newJob.connect(); + {name: `Backend set trace options and retrieve`, test: async () => { + const newJob = await JobManager.newJob(); let rpy = await newJob.setTraceConfig("IN_MEM", "DATASTREAM"); assert.equal(rpy.success, true); @@ -62,24 +32,19 @@ export const JobsSuite: TestSuite = { let trace = await newJob.getTraceData(); assert.notEqual(``, trace.tracedata); console.log(trace.tracedata); - newJob.close(); + JobManager.closeJobById(newJob); }}, {name: `Backend retrieve trace data without turning on trace`, test: async () => { - assert.strictEqual(ServerComponent.isInstalled(), true); + const newJob = await JobManager.newJob(); - let newJob = new OldSQLJob(); - await newJob.connect(); let trace = await newJob.getTraceData(); assert.notEqual(undefined, trace.tracedata); - newJob.close(); + JobManager.closeJobById(newJob); }}, {name: `Paging query`, test: async () => { - assert.strictEqual(ServerComponent.isInstalled(), true); - - let newJob = new OldSQLJob({libraries: [`QIWS`], naming: `system`}); - await newJob.connect(); + const newJob = await JobManager.newJob({libraries: [`QIWS`], naming: `system`}); let rowsAtATime = 4; let qry = newJob.query(`select * from QIWS.QCUSTCDT`); @@ -97,16 +62,13 @@ export const JobsSuite: TestSuite = { } } - newJob.close(); + JobManager.closeJobById(newJob); }}, {name: `Query with non-terse results`, test: async () => { - assert.strictEqual(ServerComponent.isInstalled(), true); - - let newJob = new OldSQLJob({libraries: [`QIWS`], naming: `system`}); - await newJob.connect(); + const newJob = await JobManager.newJob({libraries: [`QIWS`], naming: `system`}); let rowsAtATime = 4; - let qry = newJob.query(`select * from QIWS.QCUSTCDT limit 1`,{isTerseResults: false}); + let qry = newJob.query(`select * from QIWS.QCUSTCDT limit 1`,{isTerseResults: false}); let qryResults = await qry.execute(rowsAtATime); assert.equal(qryResults.success, true); @@ -114,13 +76,10 @@ export const JobsSuite: TestSuite = { let cusnum = firstRow[`CUSNUM`]; assert.equal(cusnum === undefined, false); assert.equal(Array.isArray(firstRow), false); - newJob.close(); + JobManager.closeJobById(newJob); }}, {name: `Query with terse results`, test: async () => { - assert.strictEqual(ServerComponent.isInstalled(), true); - - let newJob = new OldSQLJob({libraries: [`QIWS`], naming: `system`}); - await newJob.connect(); + const newJob = await JobManager.newJob({libraries: [`QIWS`], naming: `system`}); let rowsAtATime = 4; let qry = newJob.query(`select * from QIWS.QCUSTCDT limit 1`,{isTerseResults: true}); @@ -129,30 +88,25 @@ export const JobsSuite: TestSuite = { assert.equal(qryResults.success, true); let firstRow = qryResults.data[0]; assert.equal(Array.isArray(firstRow), true); - newJob.close(); + JobManager.closeJobById(newJob); }}, {name: `Can round-trip Extended characters`, test: async () => { - assert.strictEqual(ServerComponent.isInstalled(), true); - let testString = `¯\\_(ツ)_/¯` - let newJob = new OldSQLJob(); - await newJob.connect(); - let qryResults = await newJob.query(`create table qtemp.weewoo (col1 varchar(1208) ccsid 1208)`).execute(); + const newJob = await JobManager.newJob(); + + let qryResults = await newJob.query(`create table qtemp.weewoo (col1 varchar(1208) ccsid 1208)`).execute(); assert.equal(qryResults.success, true); - qryResults = await newJob.query(`insert into qtemp.weewoo values('${testString}')`).execute(); + qryResults = await newJob.query(`insert into qtemp.weewoo values('${testString}')`).execute(); assert.equal(qryResults.success, true); - qryResults = await newJob.query(`select COL1 from qtemp.weewoo`).execute(); + qryResults = await newJob.query(`select COL1 from qtemp.weewoo`).execute(); assert.equal(qryResults.success, true); let resultData = qryResults.data[0]['COL1']; assert.equal(resultData, testString); - newJob.close(); + JobManager.closeJobById(newJob); }}, - {name: `CL Command (success)`, test: async () => { - assert.strictEqual(ServerComponent.isInstalled(), true); - - let newJob = new OldSQLJob(); - await newJob.connect(); + {name: `CL Command (success)`, test: async () => { + const newJob = await JobManager.newJob(); let clRes = await newJob.clcommand(`CPYF FROMFILE(QIWS/QCUSTCDT) TOFILE(QTEMP/ILUVSNAKES) CRTFILE(*YES) `).execute(); assert.equal(clRes.success, true); @@ -166,14 +120,11 @@ export const JobsSuite: TestSuite = { } } assert.equal(CPF2880, true); - newJob.close(); + JobManager.closeJobById(newJob); }}, {name: `CL Command (error)`, test: async () => { - assert.strictEqual(ServerComponent.isInstalled(), true); - - let newJob = new OldSQLJob(); - await newJob.connect(); + const newJob = await JobManager.newJob(); let clRes = await newJob.clcommand(`CPYF FROMFILE(QIWS/QCUSTCDT) TOFILE(QTEMP/ILUVDB2) MBROPT(*UPDADD) CRTFILE(*YES) `).execute(); console.log(JSON.stringify(clRes)); @@ -187,16 +138,11 @@ export const JobsSuite: TestSuite = { } } assert.equal(CPD2825, true); - newJob.close(); + JobManager.closeJobById(newJob); }}, {name: `Retrieve job log`, test: async () => { - assert.strictEqual(ServerComponent.isInstalled(), true); - - let newJob = new OldSQLJob(); - await newJob.connect(); - - + const newJob = await JobManager.newJob(); await newJob.clcommand(`DLTLIB QTEMP`).execute(); let joblog = await newJob.getJobLog(); @@ -210,60 +156,43 @@ export const JobsSuite: TestSuite = { } } assert.equal(CPD2165, true); - newJob.close(); + JobManager.closeJobById(newJob); }}, {name: `Creating a job`, test: async () => { - assert.strictEqual(ServerComponent.isInstalled(), true); - - const newJob = new OldSQLJob(); - - assert.strictEqual(newJob.getStatus(), "notStarted"); - - assert.strictEqual(newJob.id, undefined); - - await newJob.connect(); - + const newJob = await JobManager.newJob(); assert.strictEqual(newJob.getStatus(), "ready"); - assert.notStrictEqual(newJob.id, undefined); - await newJob.close(); - + await JobManager.closeJobById(newJob); const lastStatus = newJob.getStatus(); assert.strictEqual(lastStatus, "ended"); }}, {name: `Jobs have different job IDs`, test: async () => { - const jobA = new OldSQLJob(); - const jobB = new OldSQLJob(); - - await jobA.connect(); - await jobB.connect(); - + const jobA = await JobManager.newJob(); + const jobB = await JobManager.newJob(); + assert.notStrictEqual(jobA.id, jobB.id); - await jobA.close(); - await jobB.close(); + await JobManager.closeJobById(jobA); + await JobManager.closeJobById(jobB); }}, {name: `Job can run many queries`, test: async () => { - const newJob = new OldSQLJob(); - - await newJob.connect(); + const newJob = await JobManager.newJob(); - const resultA = await newJob.query(`values (job_name, current_timestamp)`).execute(); - const resultB = await newJob.query(`values (job_name, current_timestamp)`).execute(); + const resultA = await newJob.query(`values (job_name, current_timestamp)`).execute(); + const resultB = await newJob.query(`values (job_name, current_timestamp)`).execute(); assert.strictEqual(resultA.data[0][`00001`], resultB.data[0][`00001`]); - newJob.close(); + JobManager.closeJobById(newJob); }}, {name: `Job can run many queries at once `, test: async () => { - const newJob = new OldSQLJob(); + const newJob = await JobManager.newJob(); - await newJob.connect(); let stmt = `select * from QIWS.QCUSTCDT`; let stmt2 = `values (job_name)`; const resultAPromise = newJob.query(stmt).execute(); @@ -286,38 +215,30 @@ export const JobsSuite: TestSuite = { assert.deepEqual(values[0].data, values[3].data); assert.deepEqual(values[1].data, values[4].data); assert.notDeepEqual(values[0].data, values[1].data); - newJob.close(); + JobManager.closeJobById(newJob); }}, {name: `Library list is used`, test: async () => { - assert.strictEqual(ServerComponent.isInstalled(), true); - - let newJob = new OldSQLJob({libraries: [`QSYS`, `SYSTOOLS`], naming: `system`}); - await newJob.connect(); + let newJob = await JobManager.newJob({libraries: [`QSYS`, `SYSTOOLS`], naming: `system`}); try { await newJob.query(`select * from qcustcdt`).execute(); assert.fail(`Query should not have worked. Library list issue`); - } catch (e) { + } catch (e: any) { assert.notStrictEqual(e.message, undefined); } + JobManager.closeJobById(newJob); - newJob.close(); - - newJob = new OldSQLJob({libraries: [`QSYS`, `QIWS`], naming: `system`}); - await newJob.connect(); + newJob = await JobManager.newJob({libraries: [`QSYS`, `QIWS`], naming: `system`}); const rows = await newJob.query(`select * from qcustcdt`).execute(); assert.notStrictEqual(rows.data.length, 0); - - newJob.close(); + + JobManager.closeJobById(newJob); }}, {name: `Binding parameters`, test: async () => { - assert.strictEqual(ServerComponent.isInstalled(), true); - - let newJob = new OldSQLJob({libraries: [`QIWS`], naming: `system`}); - await newJob.connect(); + let newJob = await JobManager.newJob({libraries: [`QIWS`], naming: `system`}); try { const rows = await newJob.query(`select * from qcustcdt where cusnum = ? and zipcod = ?`, {isClCommand: false, parameters: [938485, 30545]}).execute(); @@ -326,35 +247,29 @@ export const JobsSuite: TestSuite = { assert.fail(`Should not have errored.`); } - newJob.close(); + JobManager.closeJobById(newJob); }}, {name: `Ensure API compatability`, test: async () => { - assert.strictEqual(ServerComponent.isInstalled(), true); - const instance = getInstance(); const content = instance.getContent(); - const newJob = new OldSQLJob({naming: `sql`}); - await newJob.connect(); + const newJob = await JobManager.newJob({naming: `sql`}); const query = `select * from qiws.qcustcdt`; const rowsA = await newJob.query(query).execute(); const rowsB = await content.runSQL(query); - newJob.close(); + JobManager.closeJobById(newJob); assert.deepStrictEqual(rowsA.data, rowsB); }}, - {name: `Performance measuring`, test: async () => { - assert.strictEqual(ServerComponent.isInstalled(), true); - + {name: `Performance measuring`, test: async () => { const instance = getInstance(); const content = instance.getContent(); - const newJob = new OldSQLJob({naming: `sql`}); - await newJob.connect(); + const newJob = await JobManager.newJob({naming: `sql`}); const query = `select * from qiws.qcustcdt`; @@ -368,7 +283,7 @@ export const JobsSuite: TestSuite = { console.log(`New query method took ${ne - ns} milliseconds.`); - newJob.close(); + JobManager.closeJobById(newJob); const os = performance.now(); await content.runSQL(query); @@ -381,8 +296,7 @@ export const JobsSuite: TestSuite = { }}, {name: `Explain API`, test: async () => { - const newJob = new OldSQLJob({"full open": true}); - await newJob.connect(); + const newJob = await JobManager.newJob({"full open": true}); const query = `select * from qiws.qcustcdt`; diff --git a/src/testing/manager.ts b/src/testing/manager.ts index 4f088f05..e2415a1c 100644 --- a/src/testing/manager.ts +++ b/src/testing/manager.ts @@ -1,22 +1,12 @@ import assert from "assert"; import { TestSuite } from "."; import { JobManager } from "../config"; -import { ServerComponent } from "../connection/serverComponent"; export const ManagerSuite: TestSuite = { name: `Job manager tests`, tests: [ - {name: `Backend check`, test: async () => { - const backendInstalled = await ServerComponent.initialise(); - - // To run these tests, we need the backend server. If this test fails. Don't bother - assert.strictEqual(backendInstalled, true); - await JobManager.endAll(); - }}, {name: `Adding a job`, test: async () => { - assert.strictEqual(ServerComponent.isInstalled(), true); - // Ensure we have a blank manager first await JobManager.endAll(); assert.strictEqual(JobManager.getRunningJobs().length, 0); @@ -31,7 +21,7 @@ export const ManagerSuite: TestSuite = { // Check the job is really real const selected = JobManager.getSelection(); - assert.notStrictEqual(selected, undefined); + assert.ok(selected); assert.notStrictEqual(selected.job.id, undefined); assert.strictEqual(selected.job.getStatus(), "ready"); @@ -45,8 +35,6 @@ export const ManagerSuite: TestSuite = { }}, {name: `End all jobs`, test: async () => { - assert.strictEqual(ServerComponent.isInstalled(), true); - // Ensure we have a blank manager first await JobManager.endAll(); assert.strictEqual(JobManager.getRunningJobs().length, 0); @@ -65,9 +53,7 @@ export const ManagerSuite: TestSuite = { assert.strictEqual(JobManager.selectedJob, -1); }}, - {name: `Set selected by name`, test: async () => { - assert.strictEqual(ServerComponent.isInstalled(), true); - + {name: `Set selected by name`, test: async () => { // Ensure we have a blank manager first await JobManager.endAll(); assert.strictEqual(JobManager.getRunningJobs().length, 0); @@ -86,7 +72,7 @@ export const ManagerSuite: TestSuite = { assert.notStrictEqual(JobManager.setSelection(runningJobs[0].name), undefined); - assert.strictEqual(JobManager.getSelection().name, runningJobs[0].name); + assert.strictEqual(JobManager.getSelection()?.name, runningJobs[0].name); await JobManager.endAll(); }} diff --git a/src/testing/selfCodes.ts b/src/testing/selfCodes.ts index 659ac4e6..a4dcd2c3 100644 --- a/src/testing/selfCodes.ts +++ b/src/testing/selfCodes.ts @@ -1,17 +1,12 @@ -import assert from "assert"; import { TestSuite } from "."; -import { ServerComponent } from "../connection/serverComponent"; -import { testSelfCodes } from "../views/jobManager/selfCodes/selfCodesTest"; import { getInstance } from "../base"; +import { testSelfCodes } from "../views/jobManager/selfCodes/selfCodesTest"; export const SelfCodesTestSuite: TestSuite = { name: `Self Codes Tests`, tests: [ - {name: `Backend check`, test: async () => { - const backendInstalled = await ServerComponent.initialise(); - + {name: `Backend check`, test: async () => { // To run these tests, we need the backend server. If this test fails. Don't bother - assert.strictEqual(backendInstalled, true); try { const selfTestSchema = [ `CREATE SCHEMA SELFTEST;`, diff --git a/src/uriHandler.ts b/src/uriHandler.ts index 39ec126b..6ed90a63 100644 --- a/src/uriHandler.ts +++ b/src/uriHandler.ts @@ -1,8 +1,7 @@ -import { commands, env, Selection, Uri, UriHandler, window, workspace } from "vscode"; import querystring from "querystring"; -import Document from "./language/sql/document"; -import { ServerComponent } from "./connection/serverComponent"; +import { commands, env, Selection, Uri, UriHandler, window, workspace } from "vscode"; import { remoteAssistIsEnabled } from "./language/providers/logic/available"; +import Document from "./language/sql/document"; export class Db2iUriHandler implements UriHandler { handleUri(uri: Uri) { diff --git a/src/views/examples/exampleBrowser.ts b/src/views/examples/exampleBrowser.ts index 17f92724..196e2e3f 100644 --- a/src/views/examples/exampleBrowser.ts +++ b/src/views/examples/exampleBrowser.ts @@ -1,9 +1,9 @@ -import { Event, EventEmitter, ExtensionContext, FileSystemWatcher, MarkdownString, RelativePattern, TextDocument, ThemeIcon, TreeDataProvider, TreeItem, TreeItemCollapsibleState, Uri, commands, window, workspace } from "vscode"; +import * as path from 'path'; +import { Event, EventEmitter, ExtensionContext, FileSystemWatcher, MarkdownString, RelativePattern, ThemeIcon, TreeDataProvider, TreeItem, TreeItemCollapsibleState, Uri, commands, window, workspace } from "vscode"; import { Examples, SQLExample, SQLExamples, ServiceInfoLabel } from "."; -import { notebookFromStatements } from "../../notebooks/logic/openAsNotebook"; import { osDetail } from "../../config"; import Configuration from "../../configuration"; -import * as path from 'path'; +import { notebookFromStatements } from "../../notebooks/logic/openAsNotebook"; export const openExampleCommand = `vscode-db2i.examples.open`; @@ -259,7 +259,9 @@ function exampleWorksForOnOS(example: SQLExample): boolean { // If this example has specific system requirements defined if (example.requirements && + //@ts-ignore example.requirements[myOsVersion] && + //@ts-ignore osDetail.getDb2Level() < example.requirements[myOsVersion]) { return false; } diff --git a/src/views/examples/index.ts b/src/views/examples/index.ts index f3911fa1..ab906cb2 100644 --- a/src/views/examples/index.ts +++ b/src/views/examples/index.ts @@ -1,6 +1,6 @@ +import * as path from "path"; import { EndOfLine, FileType, TextDocument, Uri, workspace } from "vscode"; import Configuration from "../../configuration"; -import * as path from "path"; import { getServiceInfo } from "../../database/serviceInfo"; import { getStatementDetail } from "../../notebooks/logic/statement"; @@ -8722,7 +8722,7 @@ export namespace SQLExamples { continue; } } else if (type === FileType.Directory && depth > 0) { - const subContents = await this.getSqlTextDocumentsFromDirectory(uri, depth - 1); + const subContents = await getSqlTextDocumentsFromDirectory(uri, depth - 1); sqlTextDocuments.push(...subContents); } } diff --git a/src/views/html.ts b/src/views/html.ts index c4906ae0..c65103d7 100644 --- a/src/views/html.ts +++ b/src/views/html.ts @@ -205,11 +205,11 @@ export function getHeader(options: { withCollapsed?: boolean } = {}): string { `; } -export const escapeHTML = str => str.replace(/[&<>'"]/g, +export const escapeHTML = (str: string) => str.replace(/[&<>'"]/g, tag => ({ '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' - }[tag])); \ No newline at end of file + }[tag] ?? tag)); \ No newline at end of file diff --git a/src/views/jobManager/ConfigManager.ts b/src/views/jobManager/ConfigManager.ts index a43eaef1..be2979cb 100644 --- a/src/views/jobManager/ConfigManager.ts +++ b/src/views/jobManager/ConfigManager.ts @@ -1,10 +1,10 @@ +import { JDBCOptions } from "@ibm/mapepire-js/dist/src/types"; import { Disposable, ThemeColor, ThemeIcon, TreeItem, TreeItemCollapsibleState, commands, window } from "vscode"; -import Configuration from "../../configuration"; -import { SQLJobItem } from "./jobManagerView"; +import { getInstance } from "../../base"; import { Config, JobManager } from "../../config"; +import Configuration from "../../configuration"; import { editJobUi } from "./editJob"; -import { JDBCOptions } from "@ibm/mapepire-js/dist/src/types"; -import { getInstance } from "../../base"; +import { SQLJobItem } from "./jobManagerView"; interface JobConfigs { [name: string]: JDBCOptions @@ -128,6 +128,7 @@ export class ConfigManager { static hasSavedItems() { const saved = ConfigManager.getSavedConfigs(); + //@ts-ignore const names = Object.keys(saved); return names.length > 0; } @@ -136,6 +137,7 @@ export class ConfigManager { // TODO: add profiles in here? const saved = ConfigManager.getSavedConfigs(); + //@ts-ignore const names = Object.keys(saved); return names.map(name => new SavedConfig(name)); } @@ -146,6 +148,7 @@ export class ConfigManager { static getConfig(name: string): JDBCOptions | undefined { const configs = this.getSavedConfigs(); // Returns a proxy + //@ts-ignore return Object.assign({}, configs[name]); } @@ -160,7 +163,7 @@ export class ConfigManager { private static deleteConfig(name: string) { let configs = ConfigManager.getSavedConfigs(); - + //@ts-ignore configs[name] = undefined; return Configuration.set(`jobConfigs`, configs); diff --git a/src/views/jobManager/editJob/index.ts b/src/views/jobManager/editJob/index.ts index a98772c9..660e3528 100644 --- a/src/views/jobManager/editJob/index.ts +++ b/src/views/jobManager/editJob/index.ts @@ -1,12 +1,12 @@ -import { getBase } from "../../../base"; -import { JDBCOptions } from "@ibm/mapepire-js/dist/src/types"; import { ComplexTab } from "@halcyontech/vscode-ibmi-types/webviews/CustomUI"; +import { JDBCOptions } from "@ibm/mapepire-js/dist/src/types"; +import { getBase } from "../../../base"; -import getPerfTab from "./perfTab"; import getFormatTab from "./formatTab"; -import getSystemTab from "./systemTab"; -import getSortTab from "./sortTab"; import getOtherTab from "./otherTab"; +import getPerfTab from "./perfTab"; +import getSortTab from "./sortTab"; +import getSystemTab from "./systemTab"; export function formatDescription(text: string): string { return text @@ -70,10 +70,13 @@ export async function editJobUi( break; default: - // Handle of true/false values back into boolean types + // Handle of true/false values back into boolean types switch (page.data[key]) { + //@ts-ignore case `true`: options[key] = true; break; + //@ts-ignore case `false`: options[key] = false; break; + //@ts-ignore default: options[key] = page.data[key]; break; } } diff --git a/src/views/jobManager/editJob/otherTab.ts b/src/views/jobManager/editJob/otherTab.ts index 61124e76..9a4478ec 100644 --- a/src/views/jobManager/editJob/otherTab.ts +++ b/src/views/jobManager/editJob/otherTab.ts @@ -1,6 +1,6 @@ +import { JDBCOptions } from "@ibm/mapepire-js/dist/src/types"; import { formatDescription } from "."; import { getBase } from "../../../base"; -import { JDBCOptions } from "@ibm/mapepire-js/dist/src/types"; const dataTruncationText = ` Specifies whether truncation of character data generates warnings and exceptions. When this property is "true", the following apply: @@ -367,21 +367,9 @@ export default function getOtherTab(options: JDBCOptions) { `Specifies the URL to be used for a connection on the middle-tier's DriverManager in a multiple tier environment, if it is different than already specified. This property allows you to use this driver to connect to databases other than DB2 for IBM i. Use a backslash as an escape character before backslashes and semicolons in the URL.`, { default: options["secondary URL"] || `` } ) - - .addSelect(`secure`, `Secure`, [ - { - value: `false`, - text: `encrypt only the password`, - description: `False`, - selected: options["secure"] !== true, - }, - { - value: `true`, - text: `encrypt all client/server communication`, - description: `True`, - selected: options["secure"] === true, - }, - ]) + .addParagraph( + `Other properties are those properties not easily categorized. These properties determine which JDBC driver is used, and specify options related to level of database access, bidirectional string type, data truncation and so on.` + ) .addSelect( `server trace`, `Server trace`, @@ -585,13 +573,13 @@ export default function getOtherTab(options: JDBCOptions) { .addInput( `key ring name`, `Key Ring Name`, - `Specifies the key ring class name used for SSL connections with the system. This property has no effect unless "secure" is set to true and a key ring password is set using the "key ring password" property.`, + `Specifies the key ring class name used for SSL connections with the system. This property has no effect unless "Use secure SQL connection" is enabled at the server connection level and a key ring password is set using the "key ring password" property.`, { default: options["key ring name"] || `` } ) .addInput( `key ring password`, `Key Ring Password`, - `Specifies the password for the key ring class used for SSL communications with the system. This property has no effect unless "secure" is set to true and a key ring name is set using the "key ring name" property.`, + `Specifies the password for the key ring class used for SSL communications with the system. This property has no effect unless "Use secure SQL connection" is enabled at the server connection level and a key ring name is set using the "key ring name" property.`, { default: options["key ring password"] || `` } ); diff --git a/src/views/jobManager/editJob/perfTab.ts b/src/views/jobManager/editJob/perfTab.ts index b279f01b..da7c3199 100644 --- a/src/views/jobManager/editJob/perfTab.ts +++ b/src/views/jobManager/editJob/perfTab.ts @@ -1,5 +1,5 @@ -import { getBase } from "../../../base"; import { JDBCOptions } from "@ibm/mapepire-js/dist/src/types"; +import { getBase } from "../../../base"; export default function getPerfTab(options: JDBCOptions) { const base = getBase(); @@ -326,12 +326,14 @@ export default function getPerfTab(options: JDBCOptions) { value: `true`, text: `Use variable field compression`, description: `True`, + //@ts-ignore remove when https://github.com/Mapepire-IBMi/mapepire-js/pull/80 is merged selected: options["variable field compression"] === true, }, { value: `false`, text: `Do not use variable field compression`, description: `False`, + //@ts-ignore remove when https://github.com/Mapepire-IBMi/mapepire-js/pull/80 is merged selected: options["variable field compression"] === false, }, ]); diff --git a/src/views/jobManager/editJob/systemTab.ts b/src/views/jobManager/editJob/systemTab.ts index 63b8101a..080df60c 100644 --- a/src/views/jobManager/editJob/systemTab.ts +++ b/src/views/jobManager/editJob/systemTab.ts @@ -1,5 +1,5 @@ import { JDBCOptions } from "@ibm/mapepire-js/dist/src/types"; -import { getBase, getInstance, loadBase } from "../../../base"; +import { getBase, getInstance } from "../../../base"; import { formatDescription } from "."; const dbNameText = ` @@ -120,10 +120,7 @@ export default function getSystemTab(options: JDBCOptions) { // ], // formatDescription(autoCommitText) // ) - .addSelect(`database name`, `Database name`, [ - {text: `System Base`, description: `*SYSBAS`, value: ``, selected: options["database name"] === ``}, - ...Object.values(connection.getAllIAsps()).map(asp => ({text: asp.name, description: asp.name, value: asp.rdbName, selected: options["database name"] === asp.rdbName})) - ], formatDescription(dbNameText)) + .addInput(`database name`, `Database name`, formatDescription(dbNameText), { default: options["database name"] }) .addSelect( `decfloat rounding mode`, `Decfloat rounding mode`, diff --git a/src/views/jobManager/jobLog.ts b/src/views/jobManager/jobLog.ts index bfdd4483..171b0974 100644 --- a/src/views/jobManager/jobLog.ts +++ b/src/views/jobManager/jobLog.ts @@ -1,15 +1,14 @@ import { ViewColumn, window } from "vscode"; import { JobInfo } from "../../connection/manager"; -import { escapeHTML, getHeader } from "../html"; import { JobLogEntry } from "../../connection/types"; -import { JobManager } from "../../config"; +import { escapeHTML, getHeader } from "../html"; export async function displayJobLog(selected: JobInfo) { const jobLog = await selected.job.getJobLog(); if (jobLog.data.length > 0) { - const panel = window.createWebviewPanel(`tab`, selected.job.id, {viewColumn: ViewColumn.Active}, {enableScripts: true}); + const panel = window.createWebviewPanel(`tab`, selected.job.id!, {viewColumn: ViewColumn.Active}, {enableScripts: true}); panel.webview.html = generatePage(jobLog.data); panel.reveal(); } else { diff --git a/src/views/jobManager/jobManagerView.ts b/src/views/jobManager/jobManagerView.ts index d3e3ea87..c4520d9d 100644 --- a/src/views/jobManager/jobManagerView.ts +++ b/src/views/jobManager/jobManagerView.ts @@ -1,20 +1,19 @@ +import { JDBCOptions } from "@ibm/mapepire-js/dist/src/types"; import * as vscode from "vscode"; import { ProgressLocation, TreeDataProvider, TreeItemCollapsibleState, Uri, commands, env, window } from "vscode"; +import { registerDb2iTablesProvider } from "../../aiProviders/continue/listTablesContextProvider"; import { JobManager } from "../../config"; import { JobInfo } from "../../connection/manager"; -import { ServerComponent } from "../../connection/serverComponent"; -import { OldSQLJob } from "../../connection/sqlJob"; +import { TransactionEndType } from "../../connection/types"; +import { Db2foriOutput } from "../../extension"; +import { sqlLanguageStatus } from "../../language/providers"; +import { setCancelButtonVisibility } from "../results"; import { ConfigGroup, ConfigManager } from "./ConfigManager"; import { editJobUi } from "./editJob"; import { displayJobLog } from "./jobLog"; import { SelfValue, selfCodesMap } from "./selfCodes/nodes"; import { SelfCodesQuickPickItem } from "./selfCodes/selfCodesBrowser"; import { updateStatusBar } from "./statusBar"; -import { setCancelButtonVisibility } from "../results"; -import { JDBCOptions } from "@ibm/mapepire-js/dist/src/types"; -import { registerDb2iTablesProvider } from "../../aiProviders/continue/listTablesContextProvider"; -import { sqlLanguageStatus } from "../../language/providers"; -import { TransactionEndType } from "../../connection/types"; const selectJobCommand = `vscode-db2i.jobManager.selectJob`; const activeColor = new vscode.ThemeColor(`minimapGutter.addedBackground`); @@ -25,9 +24,7 @@ export class JobManagerView implements TreeDataProvider { constructor(context: vscode.ExtensionContext) { context.subscriptions.push( - vscode.commands.registerCommand(`vscode-db2i.jobManager.refresh`, async () => { - this.refresh(); - }), + vscode.commands.registerCommand(`vscode-db2i.jobManager.refresh`, () => this.refresh()), ...ConfigManager.initialiseSaveCommands(), @@ -43,13 +40,8 @@ export class JobManagerView implements TreeDataProvider { try { updateStatusBar({ newJob: true }); - await window.withProgress({ location: { viewId: `jobManager` } }, async () => { - await JobManager.newJob( - (options ? new OldSQLJob(options) : undefined), - name - ); - }); - } catch (e) { + await window.withProgress({ location: { viewId: `jobManager` } }, async () => await JobManager.newJob(options, name)); + } catch (e: any) { window.showErrorMessage(e.message); } @@ -108,9 +100,8 @@ export class JobManagerView implements TreeDataProvider { vscode.commands.registerCommand(`vscode-db2i.jobManager.copyJobId`, async (node?: SQLJobItem) => { if (node) { const id = node.label as string; - const selected = await JobManager.getJob(id); - - if (selected) { + const selected = JobManager.getJob(id); + if (selected?.job.id) { await env.clipboard.writeText(selected.job.id); window.showInformationMessage(`Copied ${selected.job.id} to clipboard.`); } @@ -126,14 +117,12 @@ export class JobManagerView implements TreeDataProvider { window.withProgress({ location: ProgressLocation.Window }, async (progress) => { progress.report({ message: `Ending current job` }); - await selected.job.close(); + await JobManager.closeJobById(selected.job); progress.report({ message: `Starting new job` }); - selected.job.options = newOptions; - try { - await selected.job.connect(); + selected.job = await JobManager.newJob(newOptions); } catch (e) { window.showErrorMessage(`Failed to start new job with updated properties.`); } @@ -198,7 +187,7 @@ export class JobManagerView implements TreeDataProvider { } }); quickPick.show(); - } catch (e) { + } catch (e: any) { vscode.window.showErrorMessage(e.message); } } @@ -207,43 +196,43 @@ export class JobManagerView implements TreeDataProvider { vscode.commands.registerCommand(`vscode-db2i.jobManager.enableTracing`, async (node?: SQLJobItem) => { if (node) { const id = node.label as string; - const selected = await JobManager.getJob(id); - - ServerComponent.writeOutput(`Enabling tracing for ${selected.name} (${selected.job.id})`, true); - - selected.job.setTraceConfig("IN_MEM", "DATASTREAM"); + const selected = JobManager.getJob(id); + if (selected) { + Db2foriOutput.writeOutput(`Enabling tracing for ${selected.name} (${selected.job.id})`, true); + selected.job.setTraceConfig("IN_MEM", "DATASTREAM"); + } } }), vscode.commands.registerCommand(`vscode-db2i.jobManager.getTrace`, async (node?: SQLJobItem) => { if (node) { const id = node.label as string; - const selected = await JobManager.getJob(id); - - const possibleFile = selected.job.getTraceFilePath(); - - if (possibleFile) { - // Trace was written to a file - vscode.workspace.openTextDocument(Uri.from({ - scheme: `streamfile`, - path: possibleFile - })).then(doc => { - vscode.window.showTextDocument(doc); - }); - - } else { - // This likely means IN_MEM was used - const trace = await selected.job.getTraceData(); - if (trace.success) { - vscode.workspace.openTextDocument({ - content: trace.tracedata.trim() - }).then(doc => { + const selected = JobManager.getJob(id); + if (selected) { + const possibleFile = selected.job.getTraceFilePath(); + if (possibleFile) { + // Trace was written to a file + vscode.workspace.openTextDocument(Uri.from({ + scheme: `streamfile`, + path: possibleFile + })).then(doc => { vscode.window.showTextDocument(doc); - }) + }); } else { - ServerComponent.writeOutput(`Unable to get trace data for ${selected.name} (${selected.job.id}):`, true); - ServerComponent.writeOutput(trace.error); + // This likely means IN_MEM was used + const trace = await selected.job.getTraceData(); + if (trace.success) { + vscode.workspace.openTextDocument({ + content: trace.tracedata.trim() + }).then(doc => { + vscode.window.showTextDocument(doc); + }) + + } else { + Db2foriOutput.writeOutput(`Unable to get trace data for ${selected.name} (${selected.job.id}):`, true); + Db2foriOutput.writeOutput(trace.error); + } } } } @@ -273,7 +262,7 @@ export class JobManagerView implements TreeDataProvider { if (!result.success) { vscode.window.showErrorMessage(`Failed to rollback. ` + result.error); } - } catch (e) { + } catch (e: any) { vscode.window.showErrorMessage(`Failed to rollback. ` + e.message); } @@ -284,7 +273,7 @@ export class JobManagerView implements TreeDataProvider { vscode.commands.registerCommand(selectJobCommand, async (selectedName: string) => { if (selectedName) { - await JobManager.setSelection(selectedName); + JobManager.setSelection(selectedName); this.refresh(); } }), @@ -314,17 +303,9 @@ export class JobManagerView implements TreeDataProvider { const selectedJob = JobManager.getSelection(); // re-register db2i tables context provider with current schema - const selectedSchema = selectedJob?.job.options.libraries[0]; - if ( - selectedJob && - selectedSchema - ) { - registerDb2iTablesProvider(selectedSchema); - } + selectedJob?.job.getCurrentSchema().then(selectedSchema => registerDb2iTablesProvider(selectedSchema)); - setCancelButtonVisibility( - selectedJob && selectedJob.job.getStatus() === "busy" - ); + setCancelButtonVisibility(selectedJob?.job.getStatus() === "busy"); sqlLanguageStatus.setState(selectedJob !== undefined); commands.executeCommand( `setContext`, diff --git a/src/views/jobManager/selfCodes/nodes.ts b/src/views/jobManager/selfCodes/nodes.ts index 5d2f412a..4d485e9f 100644 --- a/src/views/jobManager/selfCodes/nodes.ts +++ b/src/views/jobManager/selfCodes/nodes.ts @@ -39,12 +39,12 @@ export type SelfValue = "*ALL" | "*ERROR" | "*WARNING" | "*NONE"; export interface SelfCodeObject { code: SelfValue; - message: string; + message?: string; } export const selfCodesMap: SelfCodeObject[] = [ - {code: `*ALL`, message: undefined}, - {code: `*ERROR`, message: undefined}, - {code: `*WARNING`, message: undefined}, - {code: `*NONE`, message: undefined} + {code: `*ALL` }, + {code: `*ERROR` }, + {code: `*WARNING` }, + {code: `*NONE` } ]; \ No newline at end of file diff --git a/src/views/jobManager/selfCodes/selfCodesResultsView.ts b/src/views/jobManager/selfCodes/selfCodesResultsView.ts index 6874401b..b3003ad5 100644 --- a/src/views/jobManager/selfCodes/selfCodesResultsView.ts +++ b/src/views/jobManager/selfCodes/selfCodesResultsView.ts @@ -6,17 +6,16 @@ import { ProviderResult, TreeDataProvider, TreeItem, - Uri, - Disposable + Uri } from "vscode"; +import { isContinueActive } from "../../../aiProviders/continue/continueContextProvider"; import { JobManager, osDetail } from "../../../config"; -import { SelfCodeNode, SelfIleStackFrame } from "./nodes"; -import { openExampleCommand } from "../../examples/exampleBrowser"; -import { SQLExample } from "../../examples"; +import { ExtendedSQLJob } from "../../../connection/extendedSQLJob"; import { JobInfo } from "../../../connection/manager"; -import { OldSQLJob } from "../../../connection/sqlJob"; import { JobLogEntry } from "../../../connection/types"; -import { isContinueActive } from "../../../aiProviders/continue/continueContextProvider"; +import { SQLExample } from "../../examples"; +import { openExampleCommand } from "../../examples/exampleBrowser"; +import { SelfCodeNode, SelfIleStackFrame } from "./nodes"; type ChangeTreeDataEventType = SelfCodeTreeItem | undefined | null | void; @@ -92,7 +91,7 @@ export class SelfCodesResultsView implements TreeDataProvider { await vscode.window.showTextDocument(document, { selection: range }); - + vscode.commands.executeCommand(`continue.focusContinueInput`); } }), @@ -134,7 +133,7 @@ export class SelfCodesResultsView implements TreeDataProvider { this.refresh(); } - async getSelfCodes(selected: JobInfo, onlySelected?: boolean): Promise { + async getSelfCodes(selected: JobInfo, onlySelected?: boolean): Promise { const content = `SELECT job_name, user_name, reason_code, logged_time, logged_sqlstate, logged_sqlcode, matches, stmttext, message_text, message_second_level_text, program_library, program_name, program_type, module_name, client_applname, client_programid, initial_stack @@ -151,7 +150,7 @@ export class SelfCodesResultsView implements TreeDataProvider { INITIAL_STACK: JSON.parse(row.INITIAL_STACK as unknown as string) })); - + return data; } } catch (e) { @@ -169,7 +168,7 @@ export class SelfCodesResultsView implements TreeDataProvider { return element; } - async getChildren(element?: SelfCodeTreeItem|SelfErrorStackItem): Promise { + async getChildren(element?: SelfCodeTreeItem | SelfErrorStackItem): Promise { if (element && 'getChildren' in element) { const children = await element.getChildren(); return children; @@ -231,10 +230,7 @@ class SelfCodeItems extends ExtendedTreeItem { async getChildren(): Promise { const selfCodes = await this.selfView.getSelfCodes(this.selected, true); - - if (selfCodes) { - return selfCodes.map((error) => new SelfCodeTreeItem(error)); - } + return selfCodes?.map((error) => new SelfCodeTreeItem(error)) || []; } } @@ -272,7 +268,7 @@ export class SelfCodeTreeItem extends ExtendedTreeItem { path: error.MATCHES.toString() }) - this.iconPath = error.LOGGED_SQLCODE < 0 ? new vscode.ThemeIcon(`error`): new vscode.ThemeIcon(`warning`); + this.iconPath = error.LOGGED_SQLCODE < 0 ? new vscode.ThemeIcon(`error`) : new vscode.ThemeIcon(`warning`); this.contextValue = `selfCodeNode`; } @@ -298,7 +294,7 @@ export class SelfCodeTreeItem extends ExtendedTreeItem { } class JobLogEntiresItem extends ExtendedTreeItem { - constructor(private selected: OldSQLJob) { + constructor(private selected: ExtendedSQLJob) { super(`Job Log`, vscode.TreeItemCollapsibleState.Collapsed); this.iconPath = new vscode.ThemeIcon(`info`); @@ -309,15 +305,17 @@ class JobLogEntiresItem extends ExtendedTreeItem { if (log.has_results) { return log.data.reverse().map((entry) => new JobLogEntryItem(entry)); } + + return []; } } -const JOB_LOG_ENTRY_ICONS = { +const JOB_LOG_ENTRY_ICONS = new Map(Object.entries({ '0': new vscode.ThemeIcon(`info`), '10': new vscode.ThemeIcon(`info`), '20': new vscode.ThemeIcon(`warning`), - '30': new vscode.ThemeIcon(`error`), -} + '30': new vscode.ThemeIcon(`error`) +})); class JobLogEntryItem extends ExtendedTreeItem { constructor(private entry: JobLogEntry) { @@ -330,7 +328,7 @@ class JobLogEntryItem extends ExtendedTreeItem { hoverable.appendText(items.filter(i => i).join(`\n\n`)); this.tooltip = hoverable; - this.iconPath = JOB_LOG_ENTRY_ICONS[entry.SEVERITY]; + this.iconPath = JOB_LOG_ENTRY_ICONS.get(entry.SEVERITY); } async getChildren(): Promise { @@ -352,7 +350,7 @@ class SelfErrorStatementItem extends ExtendedTreeItem { name: `Statement`, content: [statement] } - + this.command = { command: openExampleCommand, title: `Open example`, @@ -408,18 +406,13 @@ class SelfErrorStackFrameItem extends ExtendedTreeItem { // maybe can be used for showing node details export class SelfTreeDecorationProvider implements FileDecorationProvider { - private disposables: Array = []; - readonly _onDidChangeFileDecorations: EventEmitter = new EventEmitter(); readonly onDidChangeFileDecorations: Event = this._onDidChangeFileDecorations.event; - constructor() { - this.disposables = []; - } provideFileDecoration(uri: vscode.Uri, token: vscode.CancellationToken): vscode.ProviderResult { if (uri.scheme === `selfCodeTreeView`) { const errorCount = parseInt(uri.path); - + if (!isNaN(errorCount) && errorCount > 0) { return { badge: errorCount < 100 ? errorCount.toString() : '💯' @@ -431,6 +424,8 @@ export class SelfTreeDecorationProvider implements FileDecorationProvider { } async updateTreeItems(treeItem: TreeItem): Promise { + if (treeItem.resourceUri) { this._onDidChangeFileDecorations.fire(treeItem.resourceUri); + } } } diff --git a/src/views/jobManager/selfCodes/selfCodesTest.ts b/src/views/jobManager/selfCodes/selfCodesTest.ts index aeed391b..8c39f654 100644 --- a/src/views/jobManager/selfCodes/selfCodesTest.ts +++ b/src/views/jobManager/selfCodes/selfCodesTest.ts @@ -1,6 +1,6 @@ -import { OldSQLJob } from "../../../connection/sqlJob"; -import { TestCase } from "../../../testing"; import assert from "assert"; +import { JobManager } from "../../../config"; +import { TestCase } from "../../../testing"; import { SelfValue } from "./nodes"; export const selfCodeTests = [ @@ -19,19 +19,18 @@ export const selfCodeTests = [ export function testSelfCodes(): TestCase[] { let tests: TestCase[] = []; const content = `SELECT job_name, matches FROM qsys2.sql_error_log where job_name = ?`; - let before: string; - let after: string; + for (const test of selfCodeTests) { const testCase: TestCase = { name: `Self code Error for test ${test.name}`, test: async () => { - let newJob = new OldSQLJob(); - await newJob.connect(); + const newJob = await JobManager.newJob(); + assert.ok(newJob.id); await newJob.setSelfState(test.code as SelfValue); try { await newJob.query(test.sql).execute(); } catch (e) {} - let result = await newJob.query(content, {parameters: [newJob.id]}).execute(); + const result = await newJob.query(content, {parameters: [newJob.id]}).execute(); assert(result.data[0]['MATCHES'] >= 1); newJob.close(); diff --git a/src/views/jobManager/statusBar.ts b/src/views/jobManager/statusBar.ts index 58366beb..22c1bd79 100644 --- a/src/views/jobManager/statusBar.ts +++ b/src/views/jobManager/statusBar.ts @@ -1,7 +1,6 @@ -import { MarkdownString, StatusBarAlignment, ThemeColor, languages, window } from "vscode"; -import { ServerComponent } from "../../connection/serverComponent"; -import { JobManager } from "../../config"; +import { MarkdownString, StatusBarAlignment, ThemeColor, window } from "vscode"; import { getInstance } from "../../base"; +import { JobManager } from "../../config"; import Statement from "../../database/statement"; const item = window.createStatusBarItem(`sqlJob`, StatusBarAlignment.Left); @@ -10,7 +9,7 @@ export async function updateStatusBar(options: {newJob?: boolean, canceling?: bo const instance = getInstance(); const connection = instance.getConnection(); - if (connection && ServerComponent.isInstalled()) { + if (connection) { const selected = JobManager.getSelection(); let text; @@ -42,7 +41,7 @@ export async function updateStatusBar(options: {newJob?: boolean, canceling?: bo ``, `Configured user library list for job:`, ``, - ...job.options.libraries.map((lib, i) => `${i+1}. \`${lib}\``) + ...(job.options?.libraries?.map((lib, i) => `${i+1}. \`${lib}\``) || ['N/A']) ].join(`\n`)); } diff --git a/src/views/results/binding.ts b/src/views/results/binding.ts index 7279545e..80bb0cc6 100644 --- a/src/views/results/binding.ts +++ b/src/views/results/binding.ts @@ -1,33 +1,29 @@ import { TextEditor } from "vscode"; import { getSqlDocument } from "../../language/providers/logic/parse"; +import { tokenIs } from "../../language/sql/statement"; import { StatementGroup } from "../../language/sql/types"; import { SqlParameter } from "./resultSetPanelProvider"; -import { tokenIs } from "../../language/sql/statement"; -export function getPriorBindableStatement(editor: TextEditor, offset: number): {statement: string, parameters: number}|undefined { +export function getPriorBindableStatement(editor: TextEditor, offset: number): { statement: string, parameters: number } | undefined { const sqlDocument = getSqlDocument(editor.document); - const groups = sqlDocument.getStatementGroups(); - if (groups.length === 0) { - return undefined; - } - - const currentGroupI = groups.findIndex(g => g.range.start <= offset && g.range.end >= offset); - for (let i = currentGroupI - 1; i >= 0; i--) { - const group = groups[i]; - if (group.statements.length === 1) { - const statement = group.statements[0]; - if (!statement.getLabel()) { - const newStatement = sqlDocument.removeEmbeddedAreas(statement); - return { - statement: newStatement.content, - parameters: newStatement.parameterCount - }; + const groups = sqlDocument?.getStatementGroups(); + if (sqlDocument && groups?.length) { + const currentGroupI = groups.findIndex(g => g.range.start <= offset && g.range.end >= offset); + for (let i = currentGroupI - 1; i >= 0; i--) { + const group = groups[i]; + if (group.statements.length === 1) { + const statement = group.statements[0]; + if (!statement.getLabel()) { + const newStatement = sqlDocument.removeEmbeddedAreas(statement); + return { + statement: newStatement.content, + parameters: newStatement.parameterCount + }; + } } } } - - return undefined; } export function getLiteralsFromStatement(group: StatementGroup): SqlParameter[] { @@ -36,21 +32,22 @@ export function getLiteralsFromStatement(group: StatementGroup): SqlParameter[] let tokens = statement.tokens; for (let i = 0; i < tokens.length; i++) { const token = tokens[i]; - if (token.type === `string`) { + if (token.type === `string` && token.value) { literals.push(token.value.substring(1, token.value.length - 1)); // Remove quotes } else if (token.type === `number`) { // Handle decimal numbers - if (tokenIs(tokens[i+1], `dot`) && tokenIs(tokens[i+2], `number`)) { - literals.push(Number(`${token.value}.${tokens[i+2].value}`)); + if (tokenIs(tokens[i + 1], `dot`) && tokenIs(tokens[i + 2], `number`)) { + literals.push(Number(`${token.value}.${tokens[i + 2].value}`)); i += 2; // Skip the next two tokens as they are part of the decimal number } else { literals.push(Number(token.value)); } } else if (tokenIs(token, `word`, `NULL`)) { + //@ts-ignore literals.push(null); } } } - + return literals; } \ No newline at end of file diff --git a/src/views/results/codegen.ts b/src/views/results/codegen.ts index c492e13b..60d331d3 100644 --- a/src/views/results/codegen.ts +++ b/src/views/results/codegen.ts @@ -1,12 +1,15 @@ import { ColumnMetaData, QueryResult } from "@ibm/mapepire-js"; -import { Token } from "../../language/sql/types"; import { tokenIs } from "../../language/sql/statement"; +import { Token } from "../../language/sql/types"; export function queryResultToRpgDs(result: QueryResult, source: string = 'Name'): string { let content = `dcl-ds row_t qualified template;\n`; - for (let i = 0; i < result.metadata.column_count; i++) { - const name = columnToRpgFieldName(result.metadata.columns[i], source); - content += ` ${name} ${columnToRpgDefinition(result.metadata.columns[i])};\n`; + + if (result.metadata.column_count && result.metadata.columns) { + for (let i = 0; i < result.metadata.column_count; i++) { + const name = columnToRpgFieldName(result.metadata.columns[i], source); + content += ` ${name} ${columnToRpgDefinition(result.metadata.columns[i])};\n`; + } } content += `end-ds;\n`; return content; @@ -64,13 +67,15 @@ export function columnToRpgDefinition(column: ColumnMetaData): string { export function queryResultToUdtf(result: QueryResult, sqlStatement: string, tokens: Token[]): string { let columnDefinitions = ''; - for (let i = 0; i < result.metadata.column_count; i++) { - const column = result.metadata.columns[i]; - columnDefinitions += ` ${column.name} ${columnToSqlDefinition(column)}`; - if (i < result.metadata.column_count - 1) { - columnDefinitions += ',\n'; - } else { - columnDefinitions += '\n'; + if (result.metadata.column_count && result.metadata.columns) { + for (let i = 0; i < result.metadata.column_count; i++) { + const column = result.metadata.columns[i]; + columnDefinitions += ` ${column.name} ${columnToSqlDefinition(column)}`; + if (i < result.metadata.column_count - 1) { + columnDefinitions += ',\n'; + } else { + columnDefinitions += '\n'; + } } } @@ -82,7 +87,7 @@ export function queryResultToUdtf(result: QueryResult, sqlStatement: string const adjustment = tokens[2].range.start; const prefixEnd = tokens[3].range.start - adjustment; const suffixStart = tokens[3].range.end - adjustment; - const columns = result.metadata.columns.map(column => column.name).join(`,\n `) + const columns = result.metadata.columns!.map(column => column.name).join(`,\n `) sqlStatement = `${sqlStatement.substring(0, prefixEnd).trimEnd()} ${columns}\n ${sqlStatement.substring(suffixStart).trimStart()}`; } @@ -162,7 +167,7 @@ export function columnToSqlDefinition(column: ColumnMetaData): string { case 'BOOLEAN': return `BOOLEAN`; default: - if(column.type.includes(`/`)) { + if (column.type.includes(`/`)) { return column.type; } else { return `-- type:${column.type} precision:${column.precision} scale:${column.scale} */`; diff --git a/src/views/results/editorUi.ts b/src/views/results/editorUi.ts index ff60f9e8..e1b877a0 100644 --- a/src/views/results/editorUi.ts +++ b/src/views/results/editorUi.ts @@ -1,7 +1,7 @@ -import { ParsedStatementInfo } from "."; -import crypto from "crypto"; import { ParameterResult } from "@ibm/mapepire-js"; -import { DecorationOptions, ThemeColor, window, Range, MarkdownString, DecorationRangeBehavior } from "vscode"; +import crypto from "crypto"; +import { DecorationOptions, DecorationRangeBehavior, MarkdownString, Range, ThemeColor, window } from "vscode"; +import { ParsedStatementInfo } from "."; import Configuration from "../../configuration"; let priorStatements: { [uniqueHash: string]: ParsedStatementInfo } = {}; @@ -32,7 +32,7 @@ export function statementDone(uniqueId: string, options: { paramsOut?: Parameter return v || `-`; }; - if (existingStatement) { + if (existingStatement && existingStatement.group) { // Huge assumption here the statement is in the active editor // TODO: feature flag @@ -47,7 +47,7 @@ export function statementDone(uniqueId: string, options: { paramsOut?: Parameter options.paramsOut.forEach((p, i) => { markdownString.appendMarkdown(`**Parameter ${i + 1}${p.name ? ` - ${p.name}` : ``}**:\n\n\`\`\`\n${p.value !== undefined ? p.value : `-`}\n\`\`\``); - if (i !== options.paramsOut.length - 1) { + if (i !== options.paramsOut!.length - 1) { markdownString.appendMarkdown(`\n\n---\n\n`); } }); diff --git a/src/views/results/explain/doveNodeView.ts b/src/views/results/explain/doveNodeView.ts index bc26efd8..53d50ae8 100644 --- a/src/views/results/explain/doveNodeView.ts +++ b/src/views/results/explain/doveNodeView.ts @@ -1,8 +1,8 @@ +import * as crypto from "crypto"; import * as vscode from "vscode"; -import { CancellationToken, Event, EventEmitter, ProviderResult, TreeView, TreeDataProvider, TreeItem, TreeItemCollapsibleState, commands } from "vscode"; -import { ExplainNode, ExplainProperty, Highlighting, RecordType, NodeHighlights } from "./nodes"; +import { CancellationToken, Event, EventEmitter, ProviderResult, TreeDataProvider, TreeItem, TreeItemCollapsibleState, TreeView, commands } from "vscode"; import { toDoveTreeDecorationProviderUri } from "./doveTreeDecorationProvider"; -import * as crypto from "crypto"; +import { ExplainNode, ExplainProperty, Highlighting, NodeHighlights, RecordType } from "./nodes"; type EventType = PropertyNode | undefined | null | void; @@ -10,12 +10,12 @@ export class DoveNodeView implements TreeDataProvider { private _onDidChangeTreeData: EventEmitter = new EventEmitter(); readonly onDidChangeTreeData: Event = this._onDidChangeTreeData.event; - private currentNode: ExplainNode; - private propertyNodes: PropertyNode[]; + private currentNode: ExplainNode | undefined; + private propertyNodes: PropertyNode[] = []; private treeView: TreeView; - private defaultTitle: string; + private defaultTitle: string | undefined; constructor() { this.treeView = vscode.window.createTreeView(`vscode-db2i.dove.node`, { treeDataProvider: this, showCollapseAll: true }); @@ -34,7 +34,7 @@ export class DoveNodeView implements TreeDataProvider { this.currentNode = node; this.treeView.title = title || this.defaultTitle; this.propertyNodes = []; - let currentSection: PropertySection = null; + let currentSection: PropertySection | undefined; for (let property of node.props) { let node = property.type === RecordType.HEADING ? new PropertySection(property) : new PropertyNode(property); // If the property node is a new section, add it to the top level node list and set as the current section @@ -61,7 +61,7 @@ export class DoveNodeView implements TreeDataProvider { // Show the detail view and if the explain node has a context defined, set it this.setContext(true); } - getNode(): ExplainNode { + getNode() { return this.currentNode; } diff --git a/src/views/results/explain/doveResultsView.ts b/src/views/results/explain/doveResultsView.ts index fcd9a165..94263e57 100644 --- a/src/views/results/explain/doveResultsView.ts +++ b/src/views/results/explain/doveResultsView.ts @@ -10,7 +10,7 @@ import { ExplainNode } from "./nodes"; * @see https://www.ibm.com/docs/en/i/7.5?topic=ssw_ibm_i_75/apis/qqqvexpl.html#icon_labels * @see https://code.visualstudio.com/api/references/icons-in-labels */ -const icons = { +const icons = new Map(Object.entries({ "Bitmap Merge": `merge`, "Cache": ``, "Cache Probe": ``, @@ -49,7 +49,7 @@ const icons = { "Unknown": `question`, "Update": `replace`, "VALUES LIST": `list-flat`, -} +})); type ChangeTreeDataEventType = ExplainTreeItem | undefined | null | void; @@ -57,7 +57,7 @@ export class DoveResultsView implements TreeDataProvider { private _onDidChangeTreeData: EventEmitter = new EventEmitter(); readonly onDidChangeTreeData: Event = this._onDidChangeTreeData.event; - private topNode: ExplainTreeItem; + private topNode: ExplainTreeItem | undefined; private treeView: TreeView; @@ -79,12 +79,12 @@ export class DoveResultsView implements TreeDataProvider { this.treeView.reveal(this.topNode, { select: false }); return this.topNode; } - getRootNode(): ExplainTreeItem { + getRootNode() { return this.topNode; } - getRootExplainNode(): ExplainNode { - return this.topNode.explainNode; + getRootExplainNode() { + return this.topNode?.explainNode; } close(): void { @@ -116,7 +116,7 @@ export class DoveResultsView implements TreeDataProvider { export class ExplainTreeItem extends TreeItem { explainNode: ExplainNode; - private children: ExplainTreeItem[]; + private children: ExplainTreeItem[] | undefined; constructor(node: ExplainNode) { super(node.title, node.children.length > 0 ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.None); @@ -131,7 +131,7 @@ export class ExplainTreeItem extends TreeItem { // TODO: ideally the tooltip would be built using a MarkdownString, but regardless of everything tried, 'Loading...' is always displayed this.tooltip = [node.title, node.tooltipProps.map(prop => prop.title + `: ` + prop.value).join(`\n`), ``].join(`\n`); this.resourceUri = toDoveTreeDecorationProviderUri(node.highlights); - this.iconPath = new ThemeIcon(icons[node.title] || `server-process`, node.highlights.getPriorityColor()); // `circle-outline` + this.iconPath = new ThemeIcon(icons.get(node.title) || `server-process`, node.highlights.getPriorityColor()); // `circle-outline` } getChildren(): ExplainTreeItem[] { diff --git a/src/views/results/explain/doveTreeDecorationProvider.ts b/src/views/results/explain/doveTreeDecorationProvider.ts index f7977a3f..4b2df0ea 100644 --- a/src/views/results/explain/doveTreeDecorationProvider.ts +++ b/src/views/results/explain/doveTreeDecorationProvider.ts @@ -1,5 +1,5 @@ -import { window, Event, EventEmitter, TreeItem, ThemeColor, FileDecorationProvider, FileDecoration, Uri, Disposable } from "vscode"; -import { NodeHighlights, Highlighting } from "./nodes"; +import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, TreeItem, Uri, window } from "vscode"; +import { Highlighting, NodeHighlights } from "./nodes"; /** * The Uri scheme for VE node highlights @@ -9,8 +9,8 @@ const doveUriScheme = "db2i.dove"; /** * Generates a {@link DoveTreeDecorationProvider} compatible Uri. The Uri scheme is set to {@link doveUriScheme}. */ -export function toDoveTreeDecorationProviderUri(highlights: NodeHighlights): Uri { - return highlights.formatValue != 0 ? Uri.parse(doveUriScheme + ":" + highlights.formatValue, false) : null; +export function toDoveTreeDecorationProviderUri(highlights: NodeHighlights) { + return highlights.formatValue != 0 ? Uri.parse(doveUriScheme + ":" + highlights.formatValue, false) : undefined; } /** @@ -28,7 +28,7 @@ export class DoveTreeDecorationProvider implements FileDecorationProvider { } async updateTreeItems(treeItem: TreeItem): Promise { - this._onDidChangeFileDecorations.fire(treeItem.resourceUri); + this._onDidChangeFileDecorations.fire(treeItem.resourceUri!); } /** @@ -42,25 +42,26 @@ export class DoveTreeDecorationProvider implements FileDecorationProvider { const value: number = Number(uri.fsPath); if (!isNaN(value) && value > 0) { const nodeHighlights = new NodeHighlights(value); - let color: ThemeColor; - let badge: string; - let tooltip: string; + let color; + let badge; + let tooltip; // For attribute section headings, only the color needs to be applied, which is not controlled by the highlight preferences if (nodeHighlights.isSet(Highlighting.ATTRIBUTE_SECTION_HEADING)) { color = Highlighting.Colors[Highlighting.ATTRIBUTE_SECTION_HEADING]; } else { color = nodeHighlights.getPriorityColor(); badge = String(nodeHighlights.getCount()); // The number of highlights set for the node + //@ts-ignore tooltip = "\n" + nodeHighlights.getNames().map(h => "🔥 " + Highlighting.Descriptions[Highlighting[h]]).join("\n"); } + return { color: color, - badge: badge, - tooltip: tooltip, + badge, + tooltip } } } - return null; } dispose() { diff --git a/src/views/results/explain/nodes.ts b/src/views/results/explain/nodes.ts index eab7c5a7..4705db07 100644 --- a/src/views/results/explain/nodes.ts +++ b/src/views/results/explain/nodes.ts @@ -27,7 +27,7 @@ export interface ExplainProperty { } export class ExplainTree { - private readonly topNode: ExplainNode; + private readonly topNode: ExplainNode | undefined; constructor(visualExplainData: VisualExplainData[]) { let topNodeId = Number.MAX_SAFE_INTEGER; @@ -55,11 +55,11 @@ export class ExplainTree { this.topNode = nodesById.get(topNodeId); //Link nodes based on their children ids - [...nodesById.values()].forEach(node => node.children.push(...node.childrenIds.map(id => nodesById.get(id)))); + [...nodesById.values()].forEach(node => node.children.push(...node.childrenIds.map(id => nodesById.get(id)!))); } /** Returns the top node */ - public get(): ExplainNode { + public get() { return this.topNode; } @@ -213,12 +213,12 @@ export class ExplainTree { // If the context data attribute relates to an advised index or statistic, squirrel it away if (contextType == ContextType.ADVISED_INDEX || contextType == ContextType.ADVISED_STATISTIC) { let attributeId: number = data.IFA_CTXORD; - let contextObj: ContextObject; + let contextObj; // The attributes are sequentially ordered, so for a given context type, we look to continue filling attributes of the last instance. // If the attribute we are adding is one that has already been set in the last context object, we start a new one. - let co: ContextObject = node.contextObjects.filter((co) => co.contextType == contextType).pop(); + let co = node.contextObjects.filter((co) => co.contextType == contextType).pop(); // If the attribute is not set, this is the context object that we will update - if (co != undefined && !co.hasProperty(attributeId)) { + if (co && !co.hasProperty(attributeId)) { contextObj = co; } // If we are not updating an existing context object, create one and push it onto the list @@ -243,12 +243,14 @@ export class ExplainTree { /** Collects the context objects from the all the tree nodes, sorted by type */ public getContextObjects(includeType?: number[]): ContextObject[] { let contextObjects: ContextObject[] = []; - this.topNode.contextObjects.forEach(co => contextObjects.push(co)); - this.getAllChildNodes(this.topNode).forEach(en => en.contextObjects.forEach(co => contextObjects.push(co))); - if (includeType) { - contextObjects = contextObjects.filter(co => includeType.includes(co.contextType)); + if (this.topNode) { + this.topNode.contextObjects.forEach(co => contextObjects.push(co)); + this.getAllChildNodes(this.topNode).forEach(en => en.contextObjects.forEach(co => contextObjects.push(co))); + if (includeType) { + contextObjects = contextObjects.filter(co => includeType.includes(co.contextType)); + } + contextObjects.sort((a: ContextObject, b: ContextObject) => a.contextType - b.contextType); } - contextObjects.sort((a: ContextObject, b: ContextObject) => a.contextType - b.contextType); return contextObjects; } @@ -348,7 +350,7 @@ function newContextObjectProperty(propertyId: number, data: any): ContextObjectP */ class ContextObjectProperty implements ExplainProperty { type: number; - title: string; + title: string = ""; value: string | number; constructor(type: number, data: any) { this.type = type; @@ -503,10 +505,10 @@ export namespace Highlighting { export function getFromSettings(): NodeHighlights { let highlights = new NodeHighlights(); // Defined in the configuration section of package.json, managed under Db2 for IBM i->Visual Explain in VS Code Settings - let settings = Configuration.get(`visualExplain.highlighting`); + let settings = Configuration.get>(`visualExplain.highlighting`); if (settings) { let keys = Object.keys(settings); - for (let key of keys) { + for (const key of keys) { if (settings[key]) { switch (key) { case "Index Advised": highlights.set(Highlighting.INDEX_ADVISED); break; @@ -612,6 +614,7 @@ export class NodeHighlights { * Returns the count of highlight bits that are set */ getCount(): number { + //@ts-ignore return Object.keys(Highlighting).filter(h => isNaN(Number(h))).filter(h => this.isSet(Highlighting[h])).map(h => Highlighting[h]).length; } @@ -631,15 +634,15 @@ export class NodeHighlights { /** * Returns the highest priority highlight color that matches the user's highlight preferences */ - getPriorityColor(): ThemeColor { + getPriorityColor() { // From the user's highlight preferences, find the highest priority highlight set for this node - for (let name of Highlighting.getFromSettings().getNames()) { + for (const name of Highlighting.getFromSettings().getNames()) { + //@ts-ignore const highlight: Highlighting = Highlighting[name]; if (this.isSet(highlight)) { return Highlighting.Colors[highlight]; } } - return null; } /** @@ -649,6 +652,7 @@ export class NodeHighlights { if (this.formatValue > 0) { console.log([ " " + this.constructor['name'] + ": " + this.formatValue, + //@ts-ignore ...Object.keys(Highlighting).filter(h => isNaN(Number(h)) && this.isSet(Highlighting[h])).map(h => " - " + h) ].join("\n") ); diff --git a/src/views/results/index.ts b/src/views/results/index.ts index bad04868..844c175e 100644 --- a/src/views/results/index.ts +++ b/src/views/results/index.ts @@ -37,16 +37,16 @@ export interface StatementInfo { } export interface ParsedStatementInfo extends StatementInfo { - statement: Statement; - group: StatementGroup; - embeddedInfo: ParsedEmbeddedStatement; + statement?: Statement; + group?: StatementGroup; + embeddedInfo?: ParsedEmbeddedStatement; } -const DelimValue = { +const DelimValue = new Map(Object.entries({ Comma: `,`, Semicolon: `;`, Tab: `\t` -} +})); export function setCancelButtonVisibility(visible: boolean) { vscode.commands.executeCommand(`setContext`, `vscode-db2i:statementCanCancel`, visible); @@ -171,15 +171,15 @@ export function initialise(context: vscode.ExtensionContext) { vscode.commands.registerCommand(`vscode-db2i.runEditorStatement.multiple.selected`, () => { runMultipleHandler(`selected`) }), vscode.commands.registerCommand(`vscode-db2i.runEditorStatement.multiple.from`, () => { runMultipleHandler(`from`) }), - vscode.commands.registerCommand(`vscode-db2i.editorExplain.withRun`, (options?: StatementInfo) => { runHandler({ qualifier: `explain`, ...options }) }), - vscode.commands.registerCommand(`vscode-db2i.editorExplain.withoutRun`, (options?: StatementInfo) => { runHandler({ qualifier: `onlyexplain`, ...options }) }), - vscode.commands.registerCommand(`vscode-db2i.runEditorStatement.inView`, (options?: StatementInfo) => { runHandler({ viewColumn: ViewColumn.Beside, ...options }) }), - vscode.commands.registerCommand(`vscode-db2i.runEditorStatement`, (options?: StatementInfo) => { runHandler(options) }) + vscode.commands.registerCommand(`vscode-db2i.editorExplain.withRun`, (options: StatementInfo) => runHandler(Object.assign({ qualifier: `explain` }, options))), + vscode.commands.registerCommand(`vscode-db2i.editorExplain.withoutRun`, (options: StatementInfo) => runHandler(Object.assign({ qualifier: `onlyexplain` }, options))), + vscode.commands.registerCommand(`vscode-db2i.runEditorStatement.inView`, (options: StatementInfo) => runHandler({ viewColumn: ViewColumn.Beside, ...options })), + vscode.commands.registerCommand(`vscode-db2i.runEditorStatement`, (options?: StatementInfo) => runHandler(options)) ) } function isStop(statement: Statement) { - return (statement.type === StatementType.Unknown && statement.tokens.length === 1 && statement.tokens[0].value.toUpperCase() === `STOP`); + return (statement.type === StatementType.Unknown && statement.tokens.length === 1 && statement.tokens[0].value?.toUpperCase() === `STOP`); } async function runMultipleHandler(mode: `all` | `selected` | `from`) { @@ -229,7 +229,7 @@ async function runMultipleHandler(mode: `all` | `selected` | `from`) { vscode.window.showErrorMessage(`Cannot run multiple statements with prefix ${prefix}.`); editor.selection = new vscode.Selection( editor.document.positionAt(group.range.start), - editor.document.positionAt(group.range.start + label.length) + editor.document.positionAt(group.range.start + (label?.length || 0)) ); return; } @@ -299,30 +299,31 @@ async function runHandler(options?: StatementInfo) { } if (editor) { - let group = statementDetail.group; - editor.selection = new vscode.Selection(editor.document.positionAt(group.range.start), editor.document.positionAt(group.range.end)); - editor.revealRange(editor.selection); + const group = statementDetail.group; + if (group) { + editor.selection = new vscode.Selection(editor.document.positionAt(group.range.start), editor.document.positionAt(group.range.end)); + editor.revealRange(editor.selection); - if (group.statements.length === 1 && statementDetail.embeddedInfo && statementDetail.embeddedInfo.changed) { - editor.insertSnippet(new SnippetString(statementDetail.embeddedInfo.content)); - return; + if (group.statements.length === 1 && statementDetail.embeddedInfo && statementDetail.embeddedInfo.changed) { + editor.insertSnippet(new SnippetString(statementDetail.embeddedInfo.content)); + return; + } } } const statement = statementDetail.statement; - const refs = statement.getObjectReferences(); - const ref = refs[0]; + const ref = statement?.getObjectReferences()?.[0]; let possibleTitle = `SQL Results`; if (ref && ref.object.name) { possibleTitle = (ref.object.schema ? ref.object.schema + `.` : ``) + ref.object.name; } - if (statement.type === StatementType.Create || statement.type === StatementType.Alter) { + if (ref && (statement?.type === StatementType.Create || statement?.type === StatementType.Alter)) { const databaseObj = - statement.type === StatementType.Create && ref.createType.toUpperCase() === `schema` + statement.type === StatementType.Create && ref.createType?.toUpperCase() === `schema` ? ref.object.schema || `` - : ref.object.schema + ref.object.name; + : ref.object.schema || `` + ref.object.name || ``; if (databaseObj) { DbCache.resetObject(databaseObj); @@ -347,7 +348,7 @@ async function runHandler(options?: StatementInfo) { } else { if (inWindow) { - useWindow(`CL results`, options.viewColumn); + useWindow(`CL results`, options!.viewColumn); } chosenView.setScrolling({ basicSelect: statementDetail.content, @@ -361,7 +362,7 @@ async function runHandler(options?: StatementInfo) { const position = editor.selection.active; const runStatement = getPriorBindableStatement(editor, editor.document.offsetAt(position)); - if (runStatement) { + if (runStatement && statementDetail.group) { parameters = getLiteralsFromStatement(statementDetail.group); if (runStatement.parameters !== parameters.length) { @@ -384,12 +385,12 @@ async function runHandler(options?: StatementInfo) { } else { if (inWindow) { - useWindow(possibleTitle, options.viewColumn); + useWindow(possibleTitle, options!.viewColumn); } let updatableTable: ObjectRef | undefined; - if (statementDetail.qualifier === `update` && statement.type === StatementType.Select && refs.length === 1) { - updatableTable = refs[0]; + if (statementDetail.qualifier === `update` && statement?.type === StatementType.Select && ref) { + updatableTable = ref } const uiId = registerRunStatement(statementDetail); @@ -437,7 +438,7 @@ async function runHandler(options?: StatementInfo) { } } else if (['rpg', 'udtf'].includes(statementDetail.qualifier)) { - if (![StatementType.Select, StatementType.With].includes(statementDetail.statement.type)) { + if (statementDetail.statement?.type && ![StatementType.Select, StatementType.With].includes(statementDetail.statement.type)) { vscode.window.showErrorMessage(`${statementDetail.qualifier.toLocaleUpperCase()} qualifier only supported for select statements`); } else { chosenView.setLoadingText(`Executing SQL statement...`, false); @@ -465,7 +466,7 @@ async function runHandler(options?: StatementInfo) { content = `-- statement:\n` + `-- ${statementDetail.content.replace(/(\r\n|\r|\n)/g, '\n-- ')}\n\n` + `-- User-defined table function\n` - + queryResultToUdtf(result, statementDetail.content, statementDetail.statement.tokens); + + queryResultToUdtf(result, statementDetail.content, statementDetail.statement?.tokens || []); } const textDoc = await vscode.workspace.openTextDocument({ language, content }); @@ -478,7 +479,7 @@ async function runHandler(options?: StatementInfo) { setCancelButtonVisibility(true); updateStatusBar({ executing: true }); - const data = await JobManager.runSQL(statementDetail.content); + const data = await JobManager.runSQL(statementDetail.content); setCancelButtonVisibility(false); if (data.length > 0) { @@ -494,7 +495,7 @@ async function runHandler(options?: StatementInfo) { content = csv.stringify(data, { header: true, quoted_string: true, - delimiter: DelimValue[Configuration.get(`codegen.csvColumnDelimiter`) || `Comma`] + delimiter: DelimValue.get(Configuration.get(`codegen.csvColumnDelimiter`) || `Comma`) }); break; case `json`: content = JSON.stringify(data, null, 2); break; @@ -588,7 +589,7 @@ async function runHandler(options?: StatementInfo) { vscode.commands.executeCommand(`vscode-db2i.queryHistory.prepend`, statementDetail.content); } - } catch (e) { + } catch (e: any) { setCancelButtonVisibility(false); let errorText; @@ -621,20 +622,15 @@ async function runHandler(options?: StatementInfo) { export function parseStatement(editor?: vscode.TextEditor, existingInfo?: StatementInfo): ParsedStatementInfo { let statementInfo: ParsedStatementInfo = { content: ``, - qualifier: `statement`, - group: undefined, - statement: undefined, - embeddedInfo: undefined + qualifier: `statement` }; - let sqlDocument: Document; + let sqlDocument: Document | undefined; if (existingInfo) { statementInfo = { ...existingInfo, - group: existingInfo.group, - statement: undefined, - embeddedInfo: undefined + group: existingInfo.group }; if (!existingInfo.group) { @@ -650,12 +646,12 @@ export function parseStatement(editor?: vscode.TextEditor, existingInfo?: Statem const cursor = editor.document.offsetAt(editor.selection.active); sqlDocument = getSqlDocument(document); - statementInfo.group = sqlDocument.getGroupByOffset(cursor); + statementInfo.group = sqlDocument?.getGroupByOffset(cursor); } - statementInfo.statement = statementInfo.group.statements[0]; + statementInfo.statement = statementInfo?.group?.statements[0]; - if (statementInfo.group && !statementInfo.content) { + if (sqlDocument && statementInfo.group && !statementInfo.content) { statementInfo.content = sqlDocument.content.substring( statementInfo.group.range.start, statementInfo.group.range.end ); @@ -677,10 +673,8 @@ export function parseStatement(editor?: vscode.TextEditor, existingInfo?: Statem statementInfo.content = statementInfo.content.split(eol).map(line => line.trim()).join(` `); } - if (sqlDocument) { - if (![`cl`, `bind`].includes(statementInfo.qualifier)) { - statementInfo.embeddedInfo = sqlDocument.removeEmbeddedAreas(statementInfo.statement, { replacement: `snippet` }); - } + if (sqlDocument && statementInfo.statement && ![`cl`, `bind`].includes(statementInfo.qualifier)) { + statementInfo.embeddedInfo = sqlDocument.removeEmbeddedAreas(statementInfo.statement, { replacement: `snippet` }); } return statementInfo; diff --git a/src/views/results/resultSetPanelProvider.ts b/src/views/results/resultSetPanelProvider.ts index 0c1e3d82..c40d2981 100644 --- a/src/views/results/resultSetPanelProvider.ts +++ b/src/views/results/resultSetPanelProvider.ts @@ -26,14 +26,10 @@ export interface ScrollerOptions { } export class ResultSetPanelProvider implements WebviewViewProvider { - _view: WebviewView | WebviewPanel; - loadingState: boolean; - currentQuery: Query; + _view: WebviewView | WebviewPanel | undefined; + loadingState: boolean = false; + currentQuery: Query | undefined; lastScrollerOptions: ScrollerOptions | undefined; - constructor() { - this._view = undefined; - this.loadingState = false; - } endQuery() { if (this.currentQuery) { @@ -85,7 +81,7 @@ export class ResultSetPanelProvider implements WebviewViewProvider { webviewView.webview.html = html.getLoadingHTML(); const postCellResponse = (id: number, success: boolean) => { - this._view.webview.postMessage({ + this._view?.webview.postMessage({ command: `cellResponse`, id, success @@ -102,15 +98,15 @@ export class ResultSetPanelProvider implements WebviewViewProvider { if (message.id && message.update && message.bindings) { console.log(message); try { - const result = await JobManager.runSQL(message.update, { parameters: message.bindings }); + await JobManager.runSQL(message.update, { parameters: message.bindings }); const substatement = message.bindings - ? `bind: ${message.bindings + ? `bind: ${(message.bindings as string[]) .map(binding => typeof binding === 'string' ? `'${binding}'` : String(binding)) .join(', ')}` : undefined; commands.executeCommand(`vscode-db2i.queryHistory.prepend`, message.update, substatement); postCellResponse(message.id, true); - } catch (e) { + } catch (e: any) { // this.setError(e.message); // if (this.currentQuery) { // this.currentQuery.close(); @@ -142,7 +138,7 @@ export class ResultSetPanelProvider implements WebviewViewProvider { if (this.currentQuery.getState() !== "RUN_DONE") { setCancelButtonVisibility(true); - let queryResults: QueryResult = undefined; + let queryResults: QueryResult | undefined = undefined; let startTime = 0; let endTime = 0; let executionTime: number | undefined; @@ -166,7 +162,7 @@ export class ResultSetPanelProvider implements WebviewViewProvider { } const jobId = this.currentQuery.getHostJob().id; - this._view.webview.postMessage({ + this._view?.webview.postMessage({ command: `rows`, jobId, rows: queryResults.data, @@ -183,9 +179,9 @@ export class ResultSetPanelProvider implements WebviewViewProvider { canRefresh = true; } - } catch (e) { + } catch (e: any) { this.setError(e.message); - this._view.webview.postMessage({ + this._view?.webview.postMessage({ command: `rows`, rows: [], queryId: ``, @@ -233,12 +229,14 @@ export class ResultSetPanelProvider implements WebviewViewProvider { await this.focus(); } - if (!this.loadingState) { - this._view.webview.html = html.getLoadingHTML(); - this.loadingState = true; - } + if (this._view) { + if (!this.loadingState) { + this._view.webview.html = html.getLoadingHTML(); + this.loadingState = true; + } - html.setLoadingText(this._view.webview, content); + html.setLoadingText(this._view.webview, content); + } } /** Update the result table column headings based on the configuration setting */ @@ -253,7 +251,7 @@ export class ResultSetPanelProvider implements WebviewViewProvider { async setScrolling(options: ScrollerOptions) { this.lastScrollerOptions = { ...options }; - + this.loadingState = false; await this.focus(); @@ -263,7 +261,7 @@ export class ResultSetPanelProvider implements WebviewViewProvider { const schema = options.ref.object.schema || options.ref.object.system; if (schema) { const goodSchema = Statement.delimName(schema, true); - const goodName = Statement.delimName(options.ref.object.name, true); + const goodName = Statement.delimName(options.ref.object.name || '', true); try { const isPartitioned = await Table.isPartitioned(goodSchema, goodName); @@ -332,28 +330,34 @@ export class ResultSetPanelProvider implements WebviewViewProvider { }; } } - } catch (e) { + } catch (e: any) { window.showErrorMessage(`Table may not be updatable. This sometimes happens if you're Db2 for i PTF levels are not up to date: ${e.message}`); } } } - this._view.webview.html = html.generateScroller(options.uiId, options.basicSelect, options.parameters, options.isCL, options.withCancel, updatable); + if (this._view) { + this._view.webview.html = html.generateScroller(options.uiId || '', options.basicSelect, options.parameters, options.isCL, options.withCancel, updatable); - this._view.webview.postMessage({ - command: `fetch`, - queryId: options.queryId - }); + this._view.webview.postMessage({ + command: `fetch`, + queryId: options.queryId + }); + } } - setError(error) { + setError(error: string) { this.loadingState = false; // TODO: pretty error - this._view.webview.html = `

${error}

`; + if (this._view) { + this._view.webview.html = `

${error}

`; + } } - clear(){ - this._view.webview.html = ``; + clear() { + if (this._view) { + this._view.webview.html = ``; + } this.resetContext(); } diff --git a/src/views/schemaBrowser/copyUI.ts b/src/views/schemaBrowser/copyUI.ts index 62ed859a..ae0f3185 100644 --- a/src/views/schemaBrowser/copyUI.ts +++ b/src/views/schemaBrowser/copyUI.ts @@ -1,4 +1,4 @@ -import { getBase, getInstance, loadBase } from "../../base"; +import { getBase } from "../../base"; export function getCopyUi() { return getBase()!.customUI() diff --git a/src/views/schemaBrowser/index.ts b/src/views/schemaBrowser/index.ts index 8d3dcacc..6ba8352d 100644 --- a/src/views/schemaBrowser/index.ts +++ b/src/views/schemaBrowser/index.ts @@ -15,7 +15,7 @@ import Types from "../types"; import { getCopyUi } from "./copyUI"; import { getAdvisedIndexesStatement, getAuthoritiesStatement, getIndexesStatement, getMTIStatement, getObjectLocksStatement, getRecordLocksStatement, getRelatedObjects } from "./statements"; -const itemIcons = { +const itemIcons = new Map(Object.entries({ "table": `split-horizontal`, "procedure": `run`, "function": `run`, @@ -33,7 +33,7 @@ const itemIcons = { "permission": `key`, "sequence": `file-binary`, "package": `package` -} +})); export default class SchemaBrowser { private readonly emitter: vscode.EventEmitter; @@ -55,17 +55,16 @@ export default class SchemaBrowser { // Disable the command while it is running this.enableManageCommand(false); try { - const config = getInstance().getConfig(); + const config = getInstance().getConnection().getConfig(); // Get the list of schemas currently selected for display const currentSchemas = config[`databaseBrowserList`] || []; - let allSchemas: BasicSQLObject[]; + ; // Get all the schemas on the system. This might take a while, so display a progress message to let the user know something is happening. - await vscode.window.withProgress({ + const allSchemas: BasicSQLObject[] = await vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: `Retrieving schemas for ${config.name}...` - }, async () => { - allSchemas = await Schemas.getObjects(undefined, [`schemas`]); - }); + }, async () => await Schemas.getObjects('', [`schemas`])); + // Create an array of SchemaQuickPickItem representing the currently selected schemas const selectedItems: SchemaQuickPickItem[] = allSchemas.filter(schema => currentSchemas.includes(Statement.delimName(schema.name))).map(object => new SchemaQuickPickItem(object)); // Prepare the QuickPick window @@ -94,7 +93,7 @@ export default class SchemaBrowser { }) quickPick.onDidHide(() => quickPick.dispose()); quickPick.show(); - } catch (e) { + } catch (e: any) { vscode.window.showErrorMessage(e.message); } finally { // We're done, enable the command @@ -103,7 +102,7 @@ export default class SchemaBrowser { }), vscode.commands.registerCommand(`vscode-db2i.filterDatabaseObjectTypes`, async () => { - const currentTypes = Configuration.get(`schemaBrowser.databaseObjectTypes`); + const currentTypes = Configuration.get>(`schemaBrowser.databaseObjectTypes`); if (currentTypes) { const quickPickItems = Object.keys(currentTypes).map((key) => { return { @@ -129,11 +128,9 @@ export default class SchemaBrowser { vscode.commands.registerCommand(`vscode-db2i.removeSchemaFromSchemaBrowser`, async (node: SchemaItem) => { if (node) { //Running from right click - const config = getInstance().getConfig(); - - let schemas = config[`databaseBrowserList`]; - - let index = schemas.findIndex(file => file === node.label) + const config = getInstance().getConnection().getConfig(); + const schemas: string[] = config[`databaseBrowserList`]; + const index = schemas.findIndex(file => file === node.label) if (index >= 0) { schemas.splice(index, 1); } @@ -157,7 +154,7 @@ export default class SchemaBrowser { const content = await Schemas.generateSQL(object.schema, object.uniqueName(), object.type.toUpperCase()); const textDoc = await vscode.workspace.openTextDocument({ language: `sql`, content }); await vscode.window.showTextDocument(textDoc); - } catch (e) { + } catch (e: any) { vscode.window.showErrorMessage(e.message); } }); @@ -171,7 +168,7 @@ export default class SchemaBrowser { const content = await Schemas.generateSQL(object.schema, object.uniqueName(), object.type.toUpperCase(), false, true); const textDoc = await vscode.workspace.openTextDocument({ language: `sql`, content }); await vscode.window.showTextDocument(textDoc); - } catch (e) { + } catch (e: any) { vscode.window.showErrorMessage(e.message); } }); @@ -185,7 +182,7 @@ export default class SchemaBrowser { const content = await Schemas.generateSQL(object.schema, object.uniqueName(), object.type.toUpperCase(), false, false); const textDoc = await vscode.workspace.openTextDocument({ language: `sql`, content }); await vscode.window.showTextDocument(textDoc); - } catch (e) { + } catch (e: any) { vscode.window.showErrorMessage(e.message); } }); @@ -294,7 +291,7 @@ export default class SchemaBrowser { if (result === 'Yes') { try { await Schemas.clearAdvisedIndexes(object.schema, isObject ? object.name : undefined); - } catch (e) { + } catch (e: any) { vscode.window.showErrorMessage(e.message); } } @@ -318,7 +315,7 @@ export default class SchemaBrowser { vscode.window.showInformationMessage(`${object.name} deleted`); this.clearCacheAndRefresh(); - } catch (e) { + } catch (e: any) { vscode.window.showErrorMessage(e.message); } } @@ -345,7 +342,7 @@ export default class SchemaBrowser { vscode.window.showInformationMessage(`Renamed ${object.name} to ${name}`); this.clearCacheAndRefresh(); - } catch (e) { + } catch (e: any) { vscode.window.showErrorMessage(e.message); } } @@ -371,7 +368,7 @@ export default class SchemaBrowser { }); vscode.window.showInformationMessage(`${object.name} cleared`); - } catch (e) { + } catch (e: any) { vscode.window.showErrorMessage(e.message); } } @@ -397,7 +394,7 @@ export default class SchemaBrowser { vscode.window.showInformationMessage(`Table copied`); this.clearCacheAndRefresh(); - } catch (e) { + } catch (e: any) { vscode.window.showErrorMessage(e.message); } } @@ -425,8 +422,8 @@ export default class SchemaBrowser { prompt: `Show objects that contain this value (case-insensitive). Blank to reset. Use '*' for wildcard at end.`, }); - if (value !== undefined) { - this.filters.set(node.schema, value.trim() === `` ? undefined : value); + if (value) { + this.filters.set(node.schema, value.trim()); updateSchemaNode(node, this.filters.get(node.schema)); this.refresh(node); @@ -437,10 +434,12 @@ export default class SchemaBrowser { vscode.commands.registerCommand(`vscode-db2i.importDataContextMenu`, async () => { vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: `Generating SQL` }, async (arg?: any) => { try { - const data = vscode.window.activeTextEditor.document.getText(); - const uri = vscode.window.activeTextEditor.document.uri; - await this.generateInsert(uri, data); - } catch (e) { + const data = vscode.window.activeTextEditor?.document.getText(); + const uri = vscode.window.activeTextEditor?.document.uri; + if (data && uri) { + await this.generateInsert(uri, data); + } + } catch (e: any) { vscode.window.showErrorMessage(e.message); } }); @@ -454,7 +453,7 @@ export default class SchemaBrowser { const data = await this.readFile(uri); await this.generateInsert(uri, data); - } catch (e) { + } catch (e: any) { vscode.window.showErrorMessage(e.message); } }); @@ -486,10 +485,12 @@ export default class SchemaBrowser { } async generateInsert(uri: vscode.Uri, data: string) { - let ext: string = (uri.fsPath.split('.').pop() || '').toLowerCase(); + let ext: string | undefined = (uri.fsPath.split('.').pop() || '').toLowerCase(); if (ext != `csv` && ext != `json`) { ext = await vscode.window.showQuickPick(['csv', 'json'], { placeHolder: 'What format is this file?' }); - if (!ext) { return; } + if (!ext) { + return; + } } let rows: any[] = []; @@ -586,9 +587,11 @@ export default class SchemaBrowser { let offset = 0; if (addRows || !this.cache.has(key)) { - if (this.cache.has(key)) { - this.cache.get(key).pop(); //Remove more button - offset = this.cache.get(key).length; + + const items = this.cache.get(key); + if (items) { + items.pop(); //Remove more button + offset = items.length; } const data = await Schemas.getObjects(schema, [type], { @@ -605,14 +608,15 @@ export default class SchemaBrowser { } const items = data.map(item => new SQLObject(item)); - if (this.cache.has(key)) { - this.cache.get(key).push(...items); + const treeItems = this.cache.get(key); + if (treeItems) { + treeItems.push(...items); } else { this.cache.set(key, items); } - if (more) { - this.cache.get(key).push(moreButton(schema, type)); + if (more && this.cache.has(key)) { + this.cache.get(key)!.push(moreButton(schema, type)); } } else { vscode.window.showInformationMessage(`No items to load.`); @@ -620,7 +624,7 @@ export default class SchemaBrowser { } if (this.cache.has(key)) { - return this.cache.get(key).slice(0); + return this.cache.get(key)!.slice(0); } else { return []; } @@ -631,7 +635,7 @@ export default class SchemaBrowser { } async getChildren(element?: Schema | SchemaItem | SQLObject) { - let items = []; + const items: any[] = []; if (element) { @@ -643,35 +647,30 @@ export default class SchemaBrowser { if (filterValue) { const validSchemaName = Statement.noQuotes(element.schema); const filteredObjects = await Schemas.getObjects(validSchemaName, AllSQLTypes, { filter: filterValue, sort: true }); - items = filteredObjects.map(obj => new SQLObject(obj)); + items.push(...filteredObjects.map(obj => new SQLObject(obj))); } else { // If no filter is provided, group objects by types - items = getSchemaItems(element.schema); + items.push(...getSchemaItems(element.schema)); } } else if (element instanceof SchemaItem) { - items = await this.fetchData(element.schema, contextValue as SQLType, false); + items.push(...await this.fetchData(element.schema, contextValue as SQLType, false)); } else if (element instanceof SQLObject) { const type = element.type; - if (Types[type]) { - items = await Types[type].getChildren(element.schema, element.uniqueName()); + if (type && type in Types) { + items.push(...await Types[type as keyof typeof Types].getChildren(element.schema, element.uniqueName())); } } - } else { const config = getInstance().getConnection().getConfig(); if (config) { const schemas = config[`databaseBrowserList`] || []; - schemas.sort((s1, s2) => { - if (s1 > s2) return 1; - if (s1 < s2) return -1; - return 0; - }); + schemas.sort((s1: string, s2: string) => s1.localeCompare(s2)); for (const schema of schemas) { items.push(new Schema(schema, this.filters.get(schema))); @@ -732,7 +731,7 @@ class SQLObject extends vscode.TreeItem { constructor(item: BasicSQLObject) { const type = InternalTypes[item.type]; - super(Statement.prettyName(item.name), Types[type] ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None); + super(Statement.prettyName(item.name), type && type in Types ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None); this.contextValue = type; this.path = `${item.schema}.${item.name}`; @@ -749,7 +748,7 @@ class SQLObject extends vscode.TreeItem { if (Schemas.isRoutineType(this.type)) { this.tooltip = new vscode.MarkdownString(`${Statement.prettyName(item.name)} ${item.specificName ? `(*${Statement.prettyName(item.specificName)}*)` : ``}`); // Name (Specific name) } - this.iconPath = itemIcons[type] ? new vscode.ThemeIcon(itemIcons[type]) : undefined; + this.iconPath = itemIcons.get(type); } /** @@ -784,7 +783,7 @@ class SchemaQuickPickItem implements vscode.QuickPickItem { } -const getSchemaItems = (schema) => { +const getSchemaItems = (schema: string) => { const items = [ //new SchemaItem(`All Database Objects`, `all`, schema), new SchemaItem(`Aliases`, `aliases`, schema, `symbol-reference`), @@ -806,11 +805,11 @@ const getSchemaItems = (schema) => { new SchemaItem(`Views`, `views`, schema, `symbol-interface`) ]; - const databaseObjectTypes = Configuration.get(`schemaBrowser.databaseObjectTypes`); - return databaseObjectTypes ? items.filter(item => databaseObjectTypes[item.label.toString()]) : items; + const databaseObjectTypes = Configuration.get>(`schemaBrowser.databaseObjectTypes`); + return databaseObjectTypes ? items.filter(item => item.label && databaseObjectTypes[item.label.toString()]) : items; } -const moreButton = (schema, type) => { +const moreButton = (schema: string, type: SQLType) => { const item = new vscode.TreeItem(`More...`, vscode.TreeItemCollapsibleState.None); item.iconPath = new vscode.ThemeIcon(`add`); item.command = { diff --git a/src/views/types/index.ts b/src/views/types/index.ts index 7895a3f3..2ba54fb0 100644 --- a/src/views/types/index.ts +++ b/src/views/types/index.ts @@ -1,12 +1,12 @@ -import * as Table from "./table"; +import * as Function from "./function"; import * as Procedure from "./procedure"; +import * as Table from "./table"; import * as View from "./view"; -import * as Function from "./function"; export default { - table: Table, - procedure: Procedure, - view: View, - function: Function + "table": Table, + "procedure": Procedure, + "view": View, + "function": Function } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 15a2b161..8523d6f8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,11 @@ { "compilerOptions": { "module": "commonjs", - "target": "ES2019", + "target": "ES2022", "checkJs": true, /* Typecheck .js files. */ "esModuleInterop": true, "lib": [ - "ES2019" + "ES2022" ], "outDir": "./dist", }, diff --git a/webpack.config.js b/webpack.config.js index 71685211..aa7e621f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -19,6 +19,9 @@ if (isProduction) { exclude = path.resolve(__dirname, `src`, `testing`) } +const dist = path.resolve(__dirname, `dist`); +fs.mkdirSync(dist, {recursive: true}); + // We need to hack our chart.js copy and remove the hardcoded exports for our build. const chartJsPackagePath = path.resolve(__dirname, `node_modules`, `chart.js`, `package.json`); let chartJsPackage = JSON.parse(fs.readFileSync(chartJsPackagePath, `utf8`)); @@ -32,7 +35,7 @@ const config = { entry: `./src/extension.ts`, // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ output: { // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ - path: path.resolve(__dirname, `dist`), + path: dist, filename: `extension.js`, libraryTarget: `commonjs2`, devtoolModuleFilenameTemplate: `../[resource-path]`, @@ -66,11 +69,7 @@ const config = { exclude: /node_modules/, use: [ { - loader: `esbuild-loader`, - options: { - // JavaScript version to transpile to - target: 'node18' - } + loader: `esbuild-loader` } ] },