From f9c320a63b2ac8d8b0bcb64f97069d3f7296c019 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Fri, 20 Oct 2023 12:15:13 +0200 Subject: [PATCH 01/29] Increment version --- .github/workflows/broken_links_checker.yml | 6 +- .github/workflows/project-keeper-verify.yml | 4 +- .github/workflows/project-keeper.sh | 2 +- .project-keeper.yml | 1 - doc/changes/changelog.md | 1 + doc/changes/changes_0.3.2.md | 22 + package-lock.json | 707 ++++++++++---------- package.json | 16 +- 8 files changed, 393 insertions(+), 366 deletions(-) create mode 100644 doc/changes/changes_0.3.2.md diff --git a/.github/workflows/broken_links_checker.yml b/.github/workflows/broken_links_checker.yml index f2079ec..82ec1cd 100644 --- a/.github/workflows/broken_links_checker.yml +++ b/.github/workflows/broken_links_checker.yml @@ -15,7 +15,7 @@ jobs: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Configure broken links checker run: | mkdir -p ./target @@ -27,6 +27,6 @@ jobs: ']}' > ./target/broken_links_checker.json - uses: gaurav-nelson/github-action-markdown-link-check@v1 with: - use-quiet-mode: 'yes' - use-verbose-mode: 'yes' + use-quiet-mode: "yes" + use-verbose-mode: "yes" config-file: ./target/broken_links_checker.json diff --git a/.github/workflows/project-keeper-verify.yml b/.github/workflows/project-keeper-verify.yml index 6dc8f84..e7ffe78 100644 --- a/.github/workflows/project-keeper-verify.yml +++ b/.github/workflows/project-keeper-verify.yml @@ -14,7 +14,7 @@ jobs: cancel-in-progress: true steps: - name: Checkout the repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -27,7 +27,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: 1.20 + go-version: "1.20" - name: Set up NPM uses: actions/setup-node@v3 diff --git a/.github/workflows/project-keeper.sh b/.github/workflows/project-keeper.sh index af0f76f..e75b381 100755 --- a/.github/workflows/project-keeper.sh +++ b/.github/workflows/project-keeper.sh @@ -5,7 +5,7 @@ set -o nounset set -o pipefail readonly pk_mode="${1-verify}"; -readonly version="2.9.10" +readonly version="2.9.12" readonly pk_jar="$HOME/.m2/repository/com/exasol/project-keeper-cli/$version/project-keeper-cli-$version.jar" diff --git a/.project-keeper.yml b/.project-keeper.yml index 1e3be0b..6a8e692 100644 --- a/.project-keeper.yml +++ b/.project-keeper.yml @@ -1,4 +1,3 @@ sources: - type: npm path: package.json -version: 0.3.1 diff --git a/doc/changes/changelog.md b/doc/changes/changelog.md index 3bba240..ff7b250 100644 --- a/doc/changes/changelog.md +++ b/doc/changes/changelog.md @@ -1,5 +1,6 @@ # Changes +* [0.3.2](changes_0.3.2.md) * [0.3.1](changes_0.3.1.md) * [0.3.0](changes_0.3.0.md) * [0.2.0](changes_0.2.0.md) diff --git a/doc/changes/changes_0.3.2.md b/doc/changes/changes_0.3.2.md new file mode 100644 index 0000000..1365e12 --- /dev/null +++ b/doc/changes/changes_0.3.2.md @@ -0,0 +1,22 @@ +# Extension Manager Interface 0.3.2, released 2023-??-?? + +Code name: Simplify Virtual Schema Extensions + +## Summary + +This release simplifies creating extension definitions for virtual schemas. + +## Features + +* #49: Extracted common code for virtual schema extensions + +## Dependency Updates + +### Development Dependency Updates + +* Updated `eslint:^8.47.0` to `^8.51.0` +* Updated `@jest/globals:^29.6.3` to `^29.7.0` +* Updated `@typescript-eslint/parser:^6.4.1` to `^6.8.0` +* Updated `typescript:5.1.6` to `5.2.2` +* Updated `@typescript-eslint/eslint-plugin:^6.4.1` to `^6.8.0` +* Updated `jest:^29.6.3` to `^29.7.0` diff --git a/package-lock.json b/package-lock.json index 14dfaa2..2999d40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,22 +1,22 @@ { "name": "@exasol/extension-manager-interface", - "version": "0.3.1", + "version": "0.3.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@exasol/extension-manager-interface", - "version": "0.3.1", + "version": "0.3.2", "license": "MIT", "devDependencies": { - "@jest/globals": "^29.6.3", - "@typescript-eslint/eslint-plugin": "^6.4.1", - "@typescript-eslint/parser": "^6.4.1", - "eslint": "^8.47.0", - "jest": "^29.6.3", + "@jest/globals": "^29.7.0", + "@typescript-eslint/eslint-plugin": "^6.8.0", + "@typescript-eslint/parser": "^6.8.0", + "eslint": "^8.51.0", + "jest": "^29.7.0", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", - "typescript": "5.1.6" + "typescript": "5.2.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -766,21 +766,21 @@ } }, "node_modules/@eslint/js": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.47.0.tgz", - "integrity": "sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", + "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", - "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.12.tgz", + "integrity": "sha512-NlGesA1usRNn6ctHCZ21M4/dKPgW9Nn1FypRdIKKgZOKzkVV4T1FlK5mBiLhHBCDmEbdQG0idrcXlbZfksJ+RA==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.0", "debug": "^4.1.1", "minimatch": "^3.0.5" }, @@ -802,9 +802,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.0.tgz", + "integrity": "sha512-9S9QrXY2K0L4AGDcSgTi9vgiCcG8VcBv4Mp7/1hDPYoswIy6Z6KO5blYto82BT8M0MZNRWmCFLpCs3HlpYGGdw==", "dev": true }, "node_modules/@istanbuljs/load-nyc-config": { @@ -916,16 +916,16 @@ } }, "node_modules/@jest/console": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.3.tgz", - "integrity": "sha512-ukZbHAdDH4ktZIOKvWs1juAXhiVAdvCyM8zv4S/7Ii3vJSDvMW5k+wOVGMQmHLHUFw3Ko63ZQNy7NI6PSlsD5w==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^29.6.3", - "jest-util": "^29.6.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0" }, "engines": { @@ -933,15 +933,15 @@ } }, "node_modules/@jest/core": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.3.tgz", - "integrity": "sha512-skV1XrfNxfagmjRUrk2FyN5/2YwIzdWVVBa/orUfbLvQUANXxERq2pTvY0I+FinWHjDKB2HRmpveUiph4X0TJw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, "dependencies": { - "@jest/console": "^29.6.3", - "@jest/reporters": "^29.6.3", - "@jest/test-result": "^29.6.3", - "@jest/transform": "^29.6.3", + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", @@ -949,21 +949,21 @@ "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.6.3", - "jest-config": "^29.6.3", - "jest-haste-map": "^29.6.3", - "jest-message-util": "^29.6.3", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.6.3", - "jest-resolve-dependencies": "^29.6.3", - "jest-runner": "^29.6.3", - "jest-runtime": "^29.6.3", - "jest-snapshot": "^29.6.3", - "jest-util": "^29.6.3", - "jest-validate": "^29.6.3", - "jest-watcher": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", - "pretty-format": "^29.6.3", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, @@ -980,37 +980,37 @@ } }, "node_modules/@jest/environment": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.3.tgz", - "integrity": "sha512-u/u3cCztYCfgBiGHsamqP5x+XvucftOGPbf5RJQxfpeC1y4AL8pCjKvPDA3oCmdhZYPgk5AE0VOD/flweR69WA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, "dependencies": { - "@jest/fake-timers": "^29.6.3", + "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.6.3" + "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.3.tgz", - "integrity": "sha512-Ic08XbI2jlg6rECy+CGwk/8NDa6VE7UmIG6++9OTPAMnQmNGY28hu69Nf629CWv6T7YMODLbONxDFKdmQeI9FA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, "dependencies": { - "expect": "^29.6.3", - "jest-snapshot": "^29.6.3" + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.3.tgz", - "integrity": "sha512-nvOEW4YoqRKD9HBJ9OJ6przvIvP9qilp5nAn1462P5ZlL/MM9SgPEZFyjTGPfs7QkocdUsJa6KjHhyRn4ueItA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, "dependencies": { "jest-get-type": "^29.6.3" @@ -1020,47 +1020,47 @@ } }, "node_modules/@jest/fake-timers": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.3.tgz", - "integrity": "sha512-pa1wmqvbj6eX0nMvOM2VDAWvJOI5A/Mk3l8O7n7EsAh71sMZblaKO9iT4GjIj0LwwK3CP/Jp1ypEV0x3m89RvA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "jest-message-util": "^29.6.3", - "jest-mock": "^29.6.3", - "jest-util": "^29.6.3" + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/globals": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.3.tgz", - "integrity": "sha512-RB+uI+CZMHntzlnOPlll5x/jgRff3LEPl/td/jzMXiIgR0iIhKq9qm1HLU+EC52NuoVy/1swit/sDGjVn4bc6A==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.3", - "@jest/expect": "^29.6.3", + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", "@jest/types": "^29.6.3", - "jest-mock": "^29.6.3" + "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/reporters": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.3.tgz", - "integrity": "sha512-kGz59zMi0GkVjD2CJeYWG9k6cvj7eBqt9aDAqo2rcCLRTYlvQ62Gu/n+tOmJMBHGjzeijjuCENjzTyYBgrtLUw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.6.3", - "@jest/test-result": "^29.6.3", - "@jest/transform": "^29.6.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", @@ -1074,9 +1074,9 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.6.3", - "jest-util": "^29.6.3", - "jest-worker": "^29.6.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", @@ -1121,12 +1121,12 @@ } }, "node_modules/@jest/test-result": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.3.tgz", - "integrity": "sha512-k7ZZaNvOSMBHPZYiy0kuiaFoyansR5QnTwDux1EjK3kD5iWpRVyJIJ0RAIV39SThafchuW59vra7F8mdy5Hfgw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, "dependencies": { - "@jest/console": "^29.6.3", + "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" @@ -1136,14 +1136,14 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.3.tgz", - "integrity": "sha512-/SmijaAU2TY9ComFGIYa6Z+fmKqQMnqs2Nmwb0P/Z/tROdZ7M0iruES1EaaU9PBf8o9uED5xzaJ3YPFEIcDgAg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, "dependencies": { - "@jest/test-result": "^29.6.3", + "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.3", + "jest-haste-map": "^29.7.0", "slash": "^3.0.0" }, "engines": { @@ -1151,9 +1151,9 @@ } }, "node_modules/@jest/transform": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.3.tgz", - "integrity": "sha512-dPIc3DsvMZ/S8ut4L2ViCj265mKO0owB0wfzBv2oGzL9pQ+iRvJewHqLBmsGb7XFb5UotWIEtvY5A/lnylaIoQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", @@ -1164,9 +1164,9 @@ "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.3", + "jest-haste-map": "^29.7.0", "jest-regex-util": "^29.6.3", - "jest-util": "^29.6.3", + "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", @@ -1366,9 +1366,9 @@ } }, "node_modules/@types/graceful-fs": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", - "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.8.tgz", + "integrity": "sha512-NhRH7YzWq8WiNKVavKPBmtLYZHxNY19Hh+az28O/phfp68CF45pMFud+ZzJ8ewnxnC5smIdF3dqFeiSUQ5I+pw==", "dev": true, "dependencies": { "@types/node": "*" @@ -1399,9 +1399,9 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", + "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", "dev": true }, "node_modules/@types/node": { @@ -1411,15 +1411,15 @@ "dev": true }, "node_modules/@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", "dev": true }, "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.2.tgz", + "integrity": "sha512-g7CK9nHdwjK2n0ymT2CW698FuWJRIx+RP6embAzZ2Qi8/ilIrA1Imt2LVSeHUzKvpoi7BhmmQcXz95eS0f2JXw==", "dev": true }, "node_modules/@types/yargs": { @@ -1438,16 +1438,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.4.1.tgz", - "integrity": "sha512-3F5PtBzUW0dYlq77Lcqo13fv+58KDwUib3BddilE8ajPJT+faGgxmI9Sw+I8ZS22BYwoir9ZhNXcLi+S+I2bkw==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.8.0.tgz", + "integrity": "sha512-GosF4238Tkes2SHPQ1i8f6rMtG6zlKwMEB0abqSJ3Npvos+doIlc/ATG+vX1G9coDF3Ex78zM3heXHLyWEwLUw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.4.1", - "@typescript-eslint/type-utils": "6.4.1", - "@typescript-eslint/utils": "6.4.1", - "@typescript-eslint/visitor-keys": "6.4.1", + "@typescript-eslint/scope-manager": "6.8.0", + "@typescript-eslint/type-utils": "6.8.0", + "@typescript-eslint/utils": "6.8.0", + "@typescript-eslint/visitor-keys": "6.8.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -1473,15 +1473,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.4.1.tgz", - "integrity": "sha512-610G6KHymg9V7EqOaNBMtD1GgpAmGROsmfHJPXNLCU9bfIuLrkdOygltK784F6Crboyd5tBFayPB7Sf0McrQwg==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.8.0.tgz", + "integrity": "sha512-5tNs6Bw0j6BdWuP8Fx+VH4G9fEPDxnVI7yH1IAPkQH5RUtvKwRoqdecAPdQXv4rSOADAaz1LFBZvZG7VbXivSg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.4.1", - "@typescript-eslint/types": "6.4.1", - "@typescript-eslint/typescript-estree": "6.4.1", - "@typescript-eslint/visitor-keys": "6.4.1", + "@typescript-eslint/scope-manager": "6.8.0", + "@typescript-eslint/types": "6.8.0", + "@typescript-eslint/typescript-estree": "6.8.0", + "@typescript-eslint/visitor-keys": "6.8.0", "debug": "^4.3.4" }, "engines": { @@ -1501,13 +1501,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.4.1.tgz", - "integrity": "sha512-p/OavqOQfm4/Hdrr7kvacOSFjwQ2rrDVJRPxt/o0TOWdFnjJptnjnZ+sYDR7fi4OimvIuKp+2LCkc+rt9fIW+A==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.8.0.tgz", + "integrity": "sha512-xe0HNBVwCph7rak+ZHcFD6A+q50SMsFwcmfdjs9Kz4qDh5hWhaPhFjRs/SODEhroBI5Ruyvyz9LfwUJ624O40g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.4.1", - "@typescript-eslint/visitor-keys": "6.4.1" + "@typescript-eslint/types": "6.8.0", + "@typescript-eslint/visitor-keys": "6.8.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1518,13 +1518,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.4.1.tgz", - "integrity": "sha512-7ON8M8NXh73SGZ5XvIqWHjgX2f+vvaOarNliGhjrJnv1vdjG0LVIz+ToYfPirOoBi56jxAKLfsLm40+RvxVVXA==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.8.0.tgz", + "integrity": "sha512-RYOJdlkTJIXW7GSldUIHqc/Hkto8E+fZN96dMIFhuTJcQwdRoGN2rEWA8U6oXbLo0qufH7NPElUb+MceHtz54g==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.4.1", - "@typescript-eslint/utils": "6.4.1", + "@typescript-eslint/typescript-estree": "6.8.0", + "@typescript-eslint/utils": "6.8.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -1545,9 +1545,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.4.1.tgz", - "integrity": "sha512-zAAopbNuYu++ijY1GV2ylCsQsi3B8QvfPHVqhGdDcbx/NK5lkqMnCGU53amAjccSpk+LfeONxwzUhDzArSfZJg==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.8.0.tgz", + "integrity": "sha512-p5qOxSum7W3k+llc7owEStXlGmSl8FcGvhYt8Vjy7FqEnmkCVlM3P57XQEGj58oqaBWDQXbJDZxwUWMS/EAPNQ==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1558,13 +1558,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.4.1.tgz", - "integrity": "sha512-xF6Y7SatVE/OyV93h1xGgfOkHr2iXuo8ip0gbfzaKeGGuKiAnzS+HtVhSPx8Www243bwlW8IF7X0/B62SzFftg==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.8.0.tgz", + "integrity": "sha512-ISgV0lQ8XgW+mvv5My/+iTUdRmGspducmQcDw5JxznasXNnZn3SKNrTRuMsEXv+V/O+Lw9AGcQCfVaOPCAk/Zg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.4.1", - "@typescript-eslint/visitor-keys": "6.4.1", + "@typescript-eslint/types": "6.8.0", + "@typescript-eslint/visitor-keys": "6.8.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1585,17 +1585,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.4.1.tgz", - "integrity": "sha512-F/6r2RieNeorU0zhqZNv89s9bDZSovv3bZQpUNOmmQK1L80/cV4KEu95YUJWi75u5PhboFoKUJBnZ4FQcoqhDw==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.8.0.tgz", + "integrity": "sha512-dKs1itdE2qFG4jr0dlYLQVppqTE+Itt7GmIf/vX6CSvsW+3ov8PbWauVKyyfNngokhIO9sKZeRGCUo1+N7U98Q==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.4.1", - "@typescript-eslint/types": "6.4.1", - "@typescript-eslint/typescript-estree": "6.4.1", + "@typescript-eslint/scope-manager": "6.8.0", + "@typescript-eslint/types": "6.8.0", + "@typescript-eslint/typescript-estree": "6.8.0", "semver": "^7.5.4" }, "engines": { @@ -1610,12 +1610,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.4.1.tgz", - "integrity": "sha512-y/TyRJsbZPkJIZQXrHfdnxVnxyKegnpEvnRGNam7s3TRR2ykGefEWOhaef00/UUN3IZxizS7BTO3svd3lCOJRQ==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.8.0.tgz", + "integrity": "sha512-oqAnbA7c+pgOhW2OhGvxm0t1BULX5peQI/rLsNDpGM78EebV3C9IGbX5HNZabuZ6UQrYveCLjKo8Iy/lLlBkkg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.4.1", + "@typescript-eslint/types": "6.8.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -1758,12 +1758,12 @@ } }, "node_modules/babel-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.3.tgz", - "integrity": "sha512-1Ne93zZZEy5XmTa4Q+W5+zxBrDpExX8E3iy+xJJ+24ewlfo/T3qHfQJCzi/MMVFmBQDNxtRR/Gfd2dwb/0yrQw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, "dependencies": { - "@jest/transform": "^29.6.3", + "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.6.3", @@ -2104,6 +2104,27 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -2276,16 +2297,16 @@ } }, "node_modules/eslint": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.47.0.tgz", - "integrity": "sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", + "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "^8.47.0", - "@humanwhocodes/config-array": "^0.11.10", + "@eslint/js": "8.51.0", + "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.12.4", @@ -2462,16 +2483,16 @@ } }, "node_modules/expect": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.3.tgz", - "integrity": "sha512-x1vY4LlEMWUYVZQrFi4ZANXFwqYbJ/JNQspLVvzhW2BNY28aNcXMQH6imBbt+RBf5sVRTodYHXtSP/TLEU0Dxw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, "dependencies": { - "@jest/expect-utils": "^29.6.3", + "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.6.3", - "jest-message-util": "^29.6.3", - "jest-util": "^29.6.3" + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -2620,12 +2641,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2745,13 +2760,10 @@ "dev": true }, "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, "engines": { "node": ">= 0.4.0" } @@ -2952,9 +2964,9 @@ } }, "node_modules/istanbul-lib-instrument": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.0.tgz", - "integrity": "sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", + "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", "dev": true, "dependencies": { "@babel/core": "^7.12.3", @@ -3009,15 +3021,15 @@ } }, "node_modules/jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.3.tgz", - "integrity": "sha512-alueLuoPCDNHFcFGmgETR4KpQ+0ff3qVaiJwxQM4B5sC0CvXcgg4PEi7xrDkxuItDmdz/FVc7SSit4KEu8GRvw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "dependencies": { - "@jest/core": "^29.6.3", + "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", "import-local": "^3.0.2", - "jest-cli": "^29.6.3" + "jest-cli": "^29.7.0" }, "bin": { "jest": "bin/jest.js" @@ -3035,13 +3047,13 @@ } }, "node_modules/jest-changed-files": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.6.3.tgz", - "integrity": "sha512-G5wDnElqLa4/c66ma5PG9eRjE342lIbF6SUnTJi26C3J28Fv2TVY2rOyKB9YGbSA5ogwevgmxc4j4aVjrEK6Yg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, "dependencies": { "execa": "^5.0.0", - "jest-util": "^29.6.3", + "jest-util": "^29.7.0", "p-limit": "^3.1.0" }, "engines": { @@ -3049,28 +3061,28 @@ } }, "node_modules/jest-circus": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.3.tgz", - "integrity": "sha512-p0R5YqZEMnOpHqHLWRSjm2z/0p6RNsrNE/GRRT3eli8QGOAozj6Ys/3Tv+Ej+IfltJoSPwcQ6/hOCRkNlxLLCw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.3", - "@jest/expect": "^29.6.3", - "@jest/test-result": "^29.6.3", + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", - "jest-each": "^29.6.3", - "jest-matcher-utils": "^29.6.3", - "jest-message-util": "^29.6.3", - "jest-runtime": "^29.6.3", - "jest-snapshot": "^29.6.3", - "jest-util": "^29.6.3", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "p-limit": "^3.1.0", - "pretty-format": "^29.6.3", + "pretty-format": "^29.7.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" @@ -3080,22 +3092,21 @@ } }, "node_modules/jest-cli": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.3.tgz", - "integrity": "sha512-KuPdXUPXQIf0t6DvmG8MV4QyhcjR1a6ruKl3YL7aGn/AQ8JkROwFkWzEpDIpt11Qy188dHbRm8WjwMsV/4nmnQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, "dependencies": { - "@jest/core": "^29.6.3", - "@jest/test-result": "^29.6.3", + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "chalk": "^4.0.0", + "create-jest": "^29.7.0", "exit": "^0.1.2", - "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^29.6.3", - "jest-util": "^29.6.3", - "jest-validate": "^29.6.3", - "prompts": "^2.0.1", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "yargs": "^17.3.1" }, "bin": { @@ -3114,31 +3125,31 @@ } }, "node_modules/jest-config": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.3.tgz", - "integrity": "sha512-nb9bOq2aEqogbyL4F9mLkAeQGAgNt7Uz6U59YtQDIxFPiL7Ejgq0YIrp78oyEHD6H4CIV/k7mFrK7eFDzUJ69w==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.6.3", + "@jest/test-sequencer": "^29.7.0", "@jest/types": "^29.6.3", - "babel-jest": "^29.6.3", + "babel-jest": "^29.7.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^29.6.3", - "jest-environment-node": "^29.6.3", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", "jest-get-type": "^29.6.3", "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.6.3", - "jest-runner": "^29.6.3", - "jest-util": "^29.6.3", - "jest-validate": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^29.6.3", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -3159,24 +3170,24 @@ } }, "node_modules/jest-diff": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.3.tgz", - "integrity": "sha512-3sw+AdWnwH9sSNohMRKA7JiYUJSRr/WS6+sEFfBuhxU5V5GlEVKfvUn8JuMHE0wqKowemR1C2aHy8VtXbaV8dQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", - "pretty-format": "^29.6.3" + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-docblock": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.6.3.tgz", - "integrity": "sha512-2+H+GOTQBEm2+qFSQ7Ma+BvyV+waiIFxmZF5LdpBsAEjWX8QYjSCa4FrkIYtbfXUJJJnFCYrOtt6TZ+IAiTjBQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, "dependencies": { "detect-newline": "^3.0.0" @@ -3186,33 +3197,33 @@ } }, "node_modules/jest-each": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.3.tgz", - "integrity": "sha512-KoXfJ42k8cqbkfshW7sSHcdfnv5agDdHCPA87ZBdmHP+zJstTJc0ttQaJ/x7zK6noAL76hOuTIJ6ZkQRS5dcyg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", - "jest-util": "^29.6.3", - "pretty-format": "^29.6.3" + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-environment-node": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.3.tgz", - "integrity": "sha512-PKl7upfPJXMYbWpD+60o4HP86KvFO2c9dZ+Zr6wUzsG5xcPx/65o3ArNgHW5M0RFvLYdW4/aieR4JSooD0a2ew==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.3", - "@jest/fake-timers": "^29.6.3", + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.6.3", - "jest-util": "^29.6.3" + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -3228,9 +3239,9 @@ } }, "node_modules/jest-haste-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.3.tgz", - "integrity": "sha512-GecR5YavfjkhOytEFHAeI6aWWG3f/cOKNB1YJvj/B76xAmeVjy4zJUYobGF030cRmKaO1FBw3V8CZZ6KVh9ZSw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", @@ -3240,8 +3251,8 @@ "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.6.3", - "jest-util": "^29.6.3", - "jest-worker": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, @@ -3253,37 +3264,37 @@ } }, "node_modules/jest-leak-detector": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.3.tgz", - "integrity": "sha512-0kfbESIHXYdhAdpLsW7xdwmYhLf1BRu4AA118/OxFm0Ho1b2RcTmO4oF6aAMaxpxdxnJ3zve2rgwzNBD4Zbm7Q==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, "dependencies": { "jest-get-type": "^29.6.3", - "pretty-format": "^29.6.3" + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.3.tgz", - "integrity": "sha512-6ZrMYINZdwduSt5Xu18/n49O1IgXdjsfG7NEZaQws9k69eTKWKcVbJBw/MZsjOZe2sSyJFmuzh8042XWwl54Zg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^29.6.3", + "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", - "pretty-format": "^29.6.3" + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.3.tgz", - "integrity": "sha512-FtzaEEHzjDpQp51HX4UMkPZjy46ati4T5pEMyM6Ik48ztu4T9LQplZ6OsimHx7EuM9dfEh5HJa6D3trEftu3dA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", @@ -3292,7 +3303,7 @@ "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.6.3", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -3301,14 +3312,14 @@ } }, "node_modules/jest-mock": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.3.tgz", - "integrity": "sha512-Z7Gs/mOyTSR4yPsaZ72a/MtuK6RnC3JYqWONe48oLaoEcYwEDxqvbXz85G4SJrm2Z5Ar9zp6MiHF4AlFlRM4Pg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", - "jest-util": "^29.6.3" + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -3341,17 +3352,17 @@ } }, "node_modules/jest-resolve": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.3.tgz", - "integrity": "sha512-WMXwxhvzDeA/J+9jz1i8ZKGmbw/n+s988EiUvRI4egM+eTn31Hb5v10Re3slG3/qxntkBt2/6GkQVDGu6Bwyhw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.3", + "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.6.3", - "jest-validate": "^29.6.3", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" @@ -3361,43 +3372,43 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.3.tgz", - "integrity": "sha512-iah5nhSPTwtUV7yzpTc9xGg8gP3Ch2VNsuFMsKoCkNCrQSbFtx5KRPemmPJ32AUhTSDqJXB6djPN6zAaUGV53g==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, "dependencies": { "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.6.3" + "jest-snapshot": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-runner": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.3.tgz", - "integrity": "sha512-E4zsMhQnjhirFPhDTJgoLMWUrVCDij/KGzWlbslDHGuO8Hl2pVUfOiygMzVZtZq+BzmlqwEr7LYmW+WFLlmX8w==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, "dependencies": { - "@jest/console": "^29.6.3", - "@jest/environment": "^29.6.3", - "@jest/test-result": "^29.6.3", - "@jest/transform": "^29.6.3", + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", - "jest-docblock": "^29.6.3", - "jest-environment-node": "^29.6.3", - "jest-haste-map": "^29.6.3", - "jest-leak-detector": "^29.6.3", - "jest-message-util": "^29.6.3", - "jest-resolve": "^29.6.3", - "jest-runtime": "^29.6.3", - "jest-util": "^29.6.3", - "jest-watcher": "^29.6.3", - "jest-worker": "^29.6.3", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -3406,17 +3417,17 @@ } }, "node_modules/jest-runtime": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.3.tgz", - "integrity": "sha512-VM0Z3a9xaqizGpEKwCOIhImkrINYzxgwk8oQAvrmAiXX8LNrJrRjyva30RkuRY0ETAotHLlUcd2moviCA1hgsQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.3", - "@jest/fake-timers": "^29.6.3", - "@jest/globals": "^29.6.3", + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.6.3", - "@jest/transform": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", @@ -3424,13 +3435,13 @@ "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.3", - "jest-message-util": "^29.6.3", - "jest-mock": "^29.6.3", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.6.3", - "jest-snapshot": "^29.6.3", - "jest-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -3439,9 +3450,9 @@ } }, "node_modules/jest-snapshot": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.3.tgz", - "integrity": "sha512-66Iu7H1ojiveQMGFnKecHIZPPPBjZwfQEnF6wxqpxGf57sV3YSUtAb5/sTKM5TPa3OndyxZp1wxHFbmgVhc53w==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", @@ -3449,20 +3460,20 @@ "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.6.3", - "@jest/transform": "^29.6.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^29.6.3", + "expect": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-diff": "^29.6.3", + "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.6.3", - "jest-message-util": "^29.6.3", - "jest-util": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "natural-compare": "^1.4.0", - "pretty-format": "^29.6.3", + "pretty-format": "^29.7.0", "semver": "^7.5.3" }, "engines": { @@ -3470,9 +3481,9 @@ } }, "node_modules/jest-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.3.tgz", - "integrity": "sha512-QUjna/xSy4B32fzcKTSz1w7YYzgiHrjjJjevdRf61HYk998R5vVMMNmrHESYZVDS5DSWs+1srPLPKxXPkeSDOA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", @@ -3487,9 +3498,9 @@ } }, "node_modules/jest-validate": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.3.tgz", - "integrity": "sha512-e7KWZcAIX+2W1o3cHfnqpGajdCs1jSM3DkXjGeLSNmCazv1EeI1ggTeK5wdZhF+7N+g44JI2Od3veojoaumlfg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", @@ -3497,7 +3508,7 @@ "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "leven": "^3.1.0", - "pretty-format": "^29.6.3" + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -3516,18 +3527,18 @@ } }, "node_modules/jest-watcher": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.3.tgz", - "integrity": "sha512-NgpFjZ2U2MKusjidbi4Oiu7tfs+nrgdIxIEVROvH1cFmOei9Uj25lwkMsakqLnH/s0nEcvxO1ck77FiRlcnpZg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, "dependencies": { - "@jest/test-result": "^29.6.3", + "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", - "jest-util": "^29.6.3", + "jest-util": "^29.7.0", "string-length": "^4.0.1" }, "engines": { @@ -3535,13 +3546,13 @@ } }, "node_modules/jest-worker": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.3.tgz", - "integrity": "sha512-wacANXecZ/GbQakpf2CClrqrlwsYYDSXFd4fIGdL+dXpM2GWoJ+6bhQ7vR3TKi3+gkSfBkjy1/khH/WrYS4Q6g==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, "dependencies": { "@types/node": "*", - "jest-util": "^29.6.3", + "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -4074,9 +4085,9 @@ } }, "node_modules/pretty-format": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz", - "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "dependencies": { "@jest/schemas": "^29.6.3", @@ -4122,9 +4133,9 @@ } }, "node_modules/pure-rand": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", - "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", + "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", "dev": true, "funding": [ { @@ -4173,9 +4184,9 @@ } }, "node_modules/resolve": { - "version": "1.22.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", - "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, "dependencies": { "is-core-module": "^2.13.0", @@ -4669,9 +4680,9 @@ } }, "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -4727,25 +4738,19 @@ "dev": true }, "node_modules/v8-to-istanbul": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", + "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" + "convert-source-map": "^2.0.0" }, "engines": { "node": ">=10.12.0" } }, - "node_modules/v8-to-istanbul/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", diff --git a/package.json b/package.json index ecfbe52..19a6195 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@exasol/extension-manager-interface", - "version": "0.3.1", + "version": "0.3.2", "main": "dist/api.js", "types": "dist/api.d.ts", "description": "Interface for extensions for the Exasol extension manager.", @@ -13,14 +13,14 @@ "lint": "eslint --report-unused-disable-directives --exit-on-fatal-error ./src/" }, "devDependencies": { - "@jest/globals": "^29.6.3", - "@typescript-eslint/eslint-plugin": "^6.4.1", - "@typescript-eslint/parser": "^6.4.1", - "eslint": "^8.47.0", - "jest": "^29.6.3", + "@jest/globals": "^29.7.0", + "@typescript-eslint/eslint-plugin": "^6.8.0", + "@typescript-eslint/parser": "^6.8.0", + "eslint": "^8.51.0", + "jest": "^29.7.0", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", - "typescript": "5.1.6" + "typescript": "5.2.2" }, "files": [ "LICENSE", @@ -31,4 +31,4 @@ "src/", "dist/" ] -} \ No newline at end of file +} From 6217d1b40da8af36573a518fe98c714cdd582255 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Fri, 20 Oct 2023 12:15:35 +0200 Subject: [PATCH 02/29] Fix vulnerability in dev dependencies --- package-lock.json | 90 +++++++++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2999d40..6dffa18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,12 +42,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz", - "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.10", + "@babel/highlight": "^7.22.13", "chalk": "^2.4.2" }, "engines": { @@ -180,12 +180,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz", - "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.22.10", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -220,22 +220,22 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -327,9 +327,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -359,12 +359,12 @@ } }, "node_modules/@babel/highlight": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz", - "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, @@ -444,9 +444,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz", - "integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -633,33 +633,33 @@ } }, "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.10.tgz", - "integrity": "sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.10", - "@babel/generator": "^7.22.10", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.10", - "@babel/types": "^7.22.10", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -677,13 +677,13 @@ } }, "node_modules/@babel/types": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", - "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { From 4ece76f5cdfbdd251000df7460569219f3f8de78 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Fri, 20 Oct 2023 12:18:22 +0200 Subject: [PATCH 03/29] Cleanup --- .github/workflows/ci-build.yml | 4 ++-- .github/workflows/release.yml | 2 +- README.md | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index b408314..a14203b 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -14,13 +14,13 @@ jobs: cancel-in-progress: true steps: - name: Checkout the repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Node 18 uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 cache: "npm" - name: Install dependencies run: npm ci diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eb6bb40..d91dec4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,7 +8,7 @@ jobs: name: Build and release runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: node-version: 18 diff --git a/README.md b/README.md index f7b60c1..5ac00e2 100644 --- a/README.md +++ b/README.md @@ -11,5 +11,6 @@ the [Exasol extension-manager](https://github.com/exasol/extension-manager/). ## Additional Information * [Changelog](doc/changes/changelog.md) +* [Guide for developing an extension for Extension Manager](https://github.com/exasol/extension-manager/blob/main/doc/extension_developer_guide.md) * [Developer Guide](doc/developer_guide/developer_guide.md) * [Dependencies](dependencies.md) From b6419549b7c14aaef14e40768adc4dd175fa3a7b Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Fri, 20 Oct 2023 12:20:12 +0200 Subject: [PATCH 04/29] Update version number --- src/api.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/api.ts b/src/api.ts index 701d804..922c9b5 100644 --- a/src/api.ts +++ b/src/api.ts @@ -5,7 +5,7 @@ import { BadRequestError, NotFoundError, PreconditionFailedError } from "./error import { ExaMetadata } from "./exasolSchema"; import { Parameter } from "./parameters"; -export const CURRENT_API_VERSION = "0.3.1"; +export const CURRENT_API_VERSION = "0.3.2"; /** * This class represents an extension that can be installed and managed with the extension-manager. @@ -211,4 +211,3 @@ export * from "./error"; export * from "./exasolSchema"; export * from "./parameters"; export * from "./sqlClient"; - From 182786c6d32efa5a6f36ecfc325f4884dff58973 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Fri, 20 Oct 2023 13:53:57 +0200 Subject: [PATCH 05/29] #49: Add initial version of VS Base Extension --- src/base-vs/getInstanceParameters.test.ts | 27 ++++++++++++++++++++ src/base-vs/index.ts | 21 +++++++++++++++ src/base-vs/test-vs-utils.ts | 18 +++++++++++++ src/base/getInstanceParameters.test.ts | 21 +++++++++++++++ src/base/readInstanceParameterValues.test.ts | 21 +++++++++++++++ 5 files changed, 108 insertions(+) create mode 100644 src/base-vs/getInstanceParameters.test.ts create mode 100644 src/base-vs/index.ts create mode 100644 src/base-vs/test-vs-utils.ts create mode 100644 src/base/getInstanceParameters.test.ts create mode 100644 src/base/readInstanceParameterValues.test.ts diff --git a/src/base-vs/getInstanceParameters.test.ts b/src/base-vs/getInstanceParameters.test.ts new file mode 100644 index 0000000..589fff5 --- /dev/null +++ b/src/base-vs/getInstanceParameters.test.ts @@ -0,0 +1,27 @@ + +import { describe, expect, it } from '@jest/globals'; +import { convertVirtualSchemaBaseExtension } from '.'; +import { Parameter } from '../api'; +import { createMockContext } from '../base/test-utils'; +import { PreconditionFailedError } from '../error'; +import { emptyBaseVsExtension } from './test-vs-utils'; + +function getInstanceParameters(version: string): Parameter[] { + const baseExtension = emptyBaseVsExtension() + baseExtension.name = "testing-extension" + baseExtension.instanceParameters = [{ id: "param1", name: "Param Name", type: "string" }] + const installations = convertVirtualSchemaBaseExtension(baseExtension).getInstanceParameters(createMockContext(), version) + expect(installations).toBeDefined() + return installations +} + +describe("getInstanceParameters", () => { + it("fails for unsupported version", () => { + expect(() => getInstanceParameters("wrong version")) + .toThrowError(new PreconditionFailedError("Version 'wrong version' not supported, can only use 'v0'.")) + }) + it("succeeds for supported version", () => { + expect(getInstanceParameters("v0")) + .toStrictEqual([{ id: "param1", name: "Param Name", type: "string" }]) + }) +}) diff --git a/src/base-vs/index.ts b/src/base-vs/index.ts new file mode 100644 index 0000000..b4800d1 --- /dev/null +++ b/src/base-vs/index.ts @@ -0,0 +1,21 @@ +import { Context, ExasolExtension, NotFoundError, Parameter } from "../api"; +import { JavaBaseExtension, convertBaseExtension } from "../base"; + +export interface JavaVirtualSchemaBaseExtension extends JavaBaseExtension { + instanceParameters: Parameter[] +} + + +export function convertVirtualSchemaBaseExtension(baseExtension: JavaVirtualSchemaBaseExtension): ExasolExtension { + const extension = convertBaseExtension(baseExtension) + + return { + ...extension, + getInstanceParameters(context: Context, extensionVersion: string): Parameter[] { + if (baseExtension.version !== extensionVersion) { + throw new NotFoundError(`Version '${extensionVersion}' not supported, can only use '${baseExtension.version}'.`) + } + return baseExtension.instanceParameters + }, + } +} diff --git a/src/base-vs/test-vs-utils.ts b/src/base-vs/test-vs-utils.ts new file mode 100644 index 0000000..344600b --- /dev/null +++ b/src/base-vs/test-vs-utils.ts @@ -0,0 +1,18 @@ +import { JavaVirtualSchemaBaseExtension } from "."; +import { successResult } from "../base/common"; + +export function emptyBaseVsExtension(): JavaVirtualSchemaBaseExtension { + return { + name: "testing-base-extension", + version: "v0", + category: "test-category", + description: "Testing base extension", + file: { + name: "test-ext.jar", + size: 12345 + }, + scripts: [], + scriptVersionExtractor: () => successResult("dummy version"), + instanceParameters: [], + } +} diff --git a/src/base/getInstanceParameters.test.ts b/src/base/getInstanceParameters.test.ts new file mode 100644 index 0000000..1ebd8fe --- /dev/null +++ b/src/base/getInstanceParameters.test.ts @@ -0,0 +1,21 @@ + +import { describe, expect, it } from '@jest/globals'; +import { convertBaseExtension } from '.'; +import { Parameter } from '../api'; +import { PreconditionFailedError } from '../error'; +import { createMockContext, emptyBaseExtension } from './test-utils'; + +function getInstanceParameters(): Parameter[] { + const baseExtension = emptyBaseExtension() + baseExtension.name = "testing-extension" + const installations = convertBaseExtension(baseExtension).getInstanceParameters(createMockContext(), "version") + expect(installations).toBeDefined() + return installations +} + +describe("getInstanceParameters", () => { + it("not supported", () => { + expect(() => getInstanceParameters()) + .toThrowError(new PreconditionFailedError("Creating instances not supported")) + }) +}) diff --git a/src/base/readInstanceParameterValues.test.ts b/src/base/readInstanceParameterValues.test.ts new file mode 100644 index 0000000..aa74025 --- /dev/null +++ b/src/base/readInstanceParameterValues.test.ts @@ -0,0 +1,21 @@ + +import { describe, expect, it } from '@jest/globals'; +import { convertBaseExtension } from '.'; +import { ParameterValues } from '../api'; +import { PreconditionFailedError } from '../error'; +import { createMockContext, emptyBaseExtension } from './test-utils'; + +function readInstanceParameterValues(): ParameterValues { + const baseExtension = emptyBaseExtension() + baseExtension.name = "testing-extension" + const installations = convertBaseExtension(baseExtension).readInstanceParameterValues(createMockContext(), "version", "instanceId") + expect(installations).toBeDefined() + return installations +} + +describe("readInstanceParameterValues", () => { + it("not supported", () => { + expect(() => readInstanceParameterValues()) + .toThrowError(new PreconditionFailedError("Reading instance parameter values not supported")) + }) +}) From f0785147cdf7e705de2b657e6de9a4c9a72c45a7 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Fri, 20 Oct 2023 14:13:28 +0200 Subject: [PATCH 06/29] #49: Implement findInstances() --- src/base-vs/findInstances.test.ts | 35 +++++++++++++++++++++++++++++++ src/base-vs/findInstances.ts | 12 +++++++++++ src/base-vs/index.ts | 7 ++++++- src/base-vs/test-vs-utils.ts | 1 + 4 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 src/base-vs/findInstances.test.ts create mode 100644 src/base-vs/findInstances.ts diff --git a/src/base-vs/findInstances.test.ts b/src/base-vs/findInstances.test.ts new file mode 100644 index 0000000..2272866 --- /dev/null +++ b/src/base-vs/findInstances.test.ts @@ -0,0 +1,35 @@ + +import { describe, expect, it } from '@jest/globals'; +import { convertVirtualSchemaBaseExtension } from '.'; +import { Instance, Row } from '../api'; +import { ContextMock, createMockContext } from '../base/test-utils'; +import { emptyBaseVsExtension } from './test-vs-utils'; + +let context: ContextMock = undefined + +function findInstances(rows: Row[]): Instance[] { + const baseExtension = emptyBaseVsExtension() + baseExtension.name = "testing-extension" + context = createMockContext(); + context.mocks.sqlQuery.mockReturnValueOnce({ columns: [], rows }) + const installations = convertVirtualSchemaBaseExtension(baseExtension).findInstances(context, "version") + expect(installations).toBeDefined() + return installations +} + +describe("findInstances", () => { + it("returns empty array when no virtual schema exists", () => { + expect(findInstances([])).toStrictEqual([]) + }) + it("returns single entry when virtual schema exists", () => { + expect(findInstances([["vs-name"]])).toStrictEqual([{ id: "vs-name", name: "vs-name" }]) + }) + it("returns multiple entries", () => { + expect(findInstances([["vs1"], ["vs2"]])).toStrictEqual([{ id: "vs1", name: "vs1" }, { id: "vs2", name: "vs2" }]) + }) + it("executes expected query", () => { + findInstances([["vs1"], ["vs2"]]) + const expectedQuery = "SELECT SCHEMA_NAME FROM SYS.EXA_ALL_VIRTUAL_SCHEMAS WHERE ADAPTER_SCRIPT_SCHEMA = ? AND ADAPTER_SCRIPT_NAME = ? ORDER BY SCHEMA_NAME"; + expect(context.mocks.sqlQuery.mock.calls).toStrictEqual([[expectedQuery, "ext-schema", "vs-adapter-script-name"]]) + }) +}) diff --git a/src/base-vs/findInstances.ts b/src/base-vs/findInstances.ts new file mode 100644 index 0000000..1b5a71b --- /dev/null +++ b/src/base-vs/findInstances.ts @@ -0,0 +1,12 @@ +import { Instance } from "../api"; +import { Context } from "../context"; + +export function findInstances(context: Context, adapterScript: string): Instance[] { + const result = context.sqlClient.query("SELECT SCHEMA_NAME FROM SYS.EXA_ALL_VIRTUAL_SCHEMAS" + + " WHERE ADAPTER_SCRIPT_SCHEMA = ? AND ADAPTER_SCRIPT_NAME = ? " + + " ORDER BY SCHEMA_NAME", context.extensionSchemaName, adapterScript) + return result.rows.map(row => { + const schemaName = row[0]; + return { id: schemaName, name: schemaName } + }) +} diff --git a/src/base-vs/index.ts b/src/base-vs/index.ts index b4800d1..8c9777a 100644 --- a/src/base-vs/index.ts +++ b/src/base-vs/index.ts @@ -1,8 +1,10 @@ -import { Context, ExasolExtension, NotFoundError, Parameter } from "../api"; +import { Context, ExasolExtension, Instance, NotFoundError, Parameter } from "../api"; import { JavaBaseExtension, convertBaseExtension } from "../base"; +import { findInstances } from "./findInstances"; export interface JavaVirtualSchemaBaseExtension extends JavaBaseExtension { instanceParameters: Parameter[] + virtualSchemaAdapterScript: string } @@ -17,5 +19,8 @@ export function convertVirtualSchemaBaseExtension(baseExtension: JavaVirtualSche } return baseExtension.instanceParameters }, + findInstances(context: Context, _version: string): Instance[] { + return findInstances(context, baseExtension.virtualSchemaAdapterScript); + }, } } diff --git a/src/base-vs/test-vs-utils.ts b/src/base-vs/test-vs-utils.ts index 344600b..d7aa7ef 100644 --- a/src/base-vs/test-vs-utils.ts +++ b/src/base-vs/test-vs-utils.ts @@ -14,5 +14,6 @@ export function emptyBaseVsExtension(): JavaVirtualSchemaBaseExtension { scripts: [], scriptVersionExtractor: () => successResult("dummy version"), instanceParameters: [], + virtualSchemaAdapterScript: "vs-adapter-script-name" } } From 089702e46ec3e93c2b0b6f63240e27f237a891d0 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Fri, 20 Oct 2023 14:54:46 +0200 Subject: [PATCH 07/29] Add "addInstance()" request --- src/base-vs/addInstance.ts | 6 ++++++ src/base-vs/getInstanceParameters.test.ts | 2 +- src/base-vs/index.ts | 13 ++++++++++--- src/base-vs/test-vs-utils.ts | 2 +- 4 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 src/base-vs/addInstance.ts diff --git a/src/base-vs/addInstance.ts b/src/base-vs/addInstance.ts new file mode 100644 index 0000000..273a68c --- /dev/null +++ b/src/base-vs/addInstance.ts @@ -0,0 +1,6 @@ +import { Instance } from "../api"; +import { Context } from "../context"; + +export function addInstance(context: Context, paramDefinitions: Parameter[], paramValues: ParameterValues): Instance { + +} diff --git a/src/base-vs/getInstanceParameters.test.ts b/src/base-vs/getInstanceParameters.test.ts index 589fff5..ffe9f72 100644 --- a/src/base-vs/getInstanceParameters.test.ts +++ b/src/base-vs/getInstanceParameters.test.ts @@ -9,7 +9,7 @@ import { emptyBaseVsExtension } from './test-vs-utils'; function getInstanceParameters(version: string): Parameter[] { const baseExtension = emptyBaseVsExtension() baseExtension.name = "testing-extension" - baseExtension.instanceParameters = [{ id: "param1", name: "Param Name", type: "string" }] + baseExtension.instanceParameterDefinitions = [{ id: "param1", name: "Param Name", type: "string" }] const installations = convertVirtualSchemaBaseExtension(baseExtension).getInstanceParameters(createMockContext(), version) expect(installations).toBeDefined() return installations diff --git a/src/base-vs/index.ts b/src/base-vs/index.ts index 8c9777a..972c886 100644 --- a/src/base-vs/index.ts +++ b/src/base-vs/index.ts @@ -1,9 +1,10 @@ -import { Context, ExasolExtension, Instance, NotFoundError, Parameter } from "../api"; +import { Context, ExasolExtension, Instance, NotFoundError, Parameter, ParameterValues } from "../api"; import { JavaBaseExtension, convertBaseExtension } from "../base"; +import { addInstance } from "./addInstance"; import { findInstances } from "./findInstances"; export interface JavaVirtualSchemaBaseExtension extends JavaBaseExtension { - instanceParameters: Parameter[] + instanceParameterDefinitions: Parameter[] virtualSchemaAdapterScript: string } @@ -17,10 +18,16 @@ export function convertVirtualSchemaBaseExtension(baseExtension: JavaVirtualSche if (baseExtension.version !== extensionVersion) { throw new NotFoundError(`Version '${extensionVersion}' not supported, can only use '${baseExtension.version}'.`) } - return baseExtension.instanceParameters + return baseExtension.instanceParameterDefinitions }, findInstances(context: Context, _version: string): Instance[] { return findInstances(context, baseExtension.virtualSchemaAdapterScript); }, + addInstance(context: Context, versionToInstall: string, paramValues: ParameterValues): Instance { + if (baseExtension.version !== versionToInstall) { + throw new Error(`Version '${versionToInstall}' not supported, can only use ${baseExtension.version}.`) + } + return addInstance(context, instanceParameterDefinitions, paramValues) + } } } diff --git a/src/base-vs/test-vs-utils.ts b/src/base-vs/test-vs-utils.ts index d7aa7ef..bd8b4fc 100644 --- a/src/base-vs/test-vs-utils.ts +++ b/src/base-vs/test-vs-utils.ts @@ -13,7 +13,7 @@ export function emptyBaseVsExtension(): JavaVirtualSchemaBaseExtension { }, scripts: [], scriptVersionExtractor: () => successResult("dummy version"), - instanceParameters: [], + instanceParameterDefinitions: [], virtualSchemaAdapterScript: "vs-adapter-script-name" } } From 5f7a3c5bb662338f0f7325f806e907b24be33ea5 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Mon, 30 Oct 2023 16:59:57 +0100 Subject: [PATCH 08/29] #49: Implement creating virtual schema --- src/base-vs/addInstance.test.ts | 0 src/base-vs/addInstance.ts | 38 +++++++++- src/base-vs/builders.ts | 75 ++++++++++++++++++ src/base-vs/common.ts | 14 ++++ src/base-vs/getInstanceParameters.test.ts | 18 ++++- src/base-vs/getInstanceParameters.ts | 8 ++ src/base-vs/index.ts | 35 +++++++-- src/base-vs/parameterResolver.test.ts | 92 +++++++++++++++++++++++ src/base-vs/parameterResolver.ts | 45 +++++++++++ src/base-vs/parameters.ts | 6 ++ src/base-vs/test-vs-utils.ts | 17 ++++- src/parameters.ts | 7 +- 12 files changed, 341 insertions(+), 14 deletions(-) create mode 100644 src/base-vs/addInstance.test.ts create mode 100644 src/base-vs/builders.ts create mode 100644 src/base-vs/common.ts create mode 100644 src/base-vs/getInstanceParameters.ts create mode 100644 src/base-vs/parameterResolver.test.ts create mode 100644 src/base-vs/parameterResolver.ts create mode 100644 src/base-vs/parameters.ts diff --git a/src/base-vs/addInstance.test.ts b/src/base-vs/addInstance.test.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/base-vs/addInstance.ts b/src/base-vs/addInstance.ts index 273a68c..9ed861f 100644 --- a/src/base-vs/addInstance.ts +++ b/src/base-vs/addInstance.ts @@ -1,6 +1,42 @@ +import { JavaVirtualSchemaBaseExtension } from "."; import { Instance } from "../api"; import { Context } from "../context"; +import { convertSchemaNameToInstanceId, escapeSingleQuotes, getConnectionName } from "./common"; +import { ParameterResolver } from "./parameterResolver"; +import { PARAM_VIRTUAL_SCHEMA_NAME } from "./parameters"; -export function addInstance(context: Context, paramDefinitions: Parameter[], paramValues: ParameterValues): Instance { +export function addInstance(context: Context, baseExtension: JavaVirtualSchemaBaseExtension, parameterResolver: ParameterResolver): Instance { + const virtualSchemaName = parameterResolver.resolve(PARAM_VIRTUAL_SCHEMA_NAME) + const connectionName = getConnectionName(virtualSchemaName) + context.sqlClient.execute(buildConnectionStatement(baseExtension, parameterResolver, connectionName)) + context.sqlClient.execute(buildVirtualSchemaStatement(baseExtension, parameterResolver, connectionName, context, virtualSchemaName)) + + const comment = `Created by Extension Manager for ${baseExtension.name} v${baseExtension.version} ${escapeSingleQuotes(virtualSchemaName)}`; + context.sqlClient.execute(`COMMENT ON CONNECTION "${connectionName}" IS '${comment}'`); + context.sqlClient.execute(`COMMENT ON SCHEMA "${virtualSchemaName}" IS '${comment}'`); + return { id: convertSchemaNameToInstanceId(virtualSchemaName), name: virtualSchemaName } +} + +function buildVirtualSchemaStatement(baseExtension: JavaVirtualSchemaBaseExtension, parameterResolver: ParameterResolver, connectionName: string, context: Context, virtualSchemaName: string) { + const def = baseExtension.builder.buildVirtualSchema(parameterResolver, connectionName); + const adapter = `"${context.extensionSchemaName}"."${def.adapterName}"`; + let stmt = `CREATE VIRTUAL SCHEMA "${virtualSchemaName}" USING ${adapter}`; + for (const property of def.properties) { + stmt += ` ${property.property} = '${escapeSingleQuotes(property.value)}'`; + } + return stmt; +} + +function buildConnectionStatement(baseExtension: JavaVirtualSchemaBaseExtension, parameterResolver: ParameterResolver, connectionName: string) { + const connDef = baseExtension.builder.buildConnection(parameterResolver); + const to = connDef.connectionTo ?? ''; + let stmt = `CREATE OR REPLACE CONNECTION "${connectionName}" TO '${escapeSingleQuotes(to)}'`; + if (connDef.user) { + stmt += ` USER '${escapeSingleQuotes(connDef.user)}'`; + } + if (connDef.identifiedBy) { + stmt += ` IDENTIFIED BY '${escapeSingleQuotes(connDef.identifiedBy)}'`; + } + return stmt; } diff --git a/src/base-vs/builders.ts b/src/base-vs/builders.ts new file mode 100644 index 0000000..2df6fb1 --- /dev/null +++ b/src/base-vs/builders.ts @@ -0,0 +1,75 @@ +import { ConnectionDefinition, VirtualSchemaBuilder, VirtualSchemaProperty } from "."; +import { Parameter } from "../parameters"; +import { ParameterResolver } from "./parameterResolver"; + + +export interface ConnectionParameterDefinition { + /** Parameter definitions used in the CONNECTION */ + parameters: Parameter[] + /** Factory function for a connection definition */ + builder: (parameterResolver: ParameterResolver) => ConnectionDefinition +} + +export interface Config { + /** Name of the adapter script, e.g. "S3_FILES_ADAPTER" */ + adapterName: string + /** Parameter definitions used as properties for the VIRTUAL SCHEMA */ + virtualSchemaParameters: Parameter[] + /** Name of the Virtual Schema's connection property, e.g. "CONNECTION_NAME" */ + connectionNameProperty: string + /** Connection definition */ + connectionDefinition: ConnectionParameterDefinition +} + +export function createVirtualSchemaBuilder({ adapterName, virtualSchemaParameters, connectionNameProperty, connectionDefinition }: Config): VirtualSchemaBuilder { + + function convertVirtualSchemaParameters(connectionName: string, parameterResolver: ParameterResolver) { + const result: VirtualSchemaProperty[] = [] + result.push({ property: connectionNameProperty, value: connectionName }) + for (const param of virtualSchemaParameters) { + const value = parameterResolver.resolveOptional(param) + if (value) { + result.push({ property: param.id, value }) + } + } + return result + } + + return { + getParameters() { + return [...virtualSchemaParameters, ...connectionDefinition.parameters] + }, + buildConnection: connectionDefinition.builder, + buildVirtualSchema(parameterResolver: ParameterResolver, connectionName: string) { + return { adapterName, properties: convertVirtualSchemaParameters(connectionName, parameterResolver) } + }, + } +} + +export function createJsonConnectionDefinition(parameters: Parameter[]): ConnectionParameterDefinition { + function builder(parameterResolver: ParameterResolver): ConnectionDefinition { + /* eslint-disable @typescript-eslint/no-explicit-any */ + const paramValues: any = {} + for (const param of parameters) { + const value = parameterResolver.resolveOptional(param) + if (value) { + /* eslint-disable @typescript-eslint/no-unsafe-member-access */ + paramValues[param.id] = value + } + } + + return { identifiedBy: JSON.stringify(paramValues) } + } + return { parameters, builder } +} + +export function createUserPasswordConnectionDefinition(addressParam: Parameter, userParam: Parameter, passwordParam: Parameter): ConnectionParameterDefinition { + function builder(parameterResolver: ParameterResolver): ConnectionDefinition { + return { + connectionTo: parameterResolver.resolve(addressParam), + user: parameterResolver.resolve(userParam), + identifiedBy: parameterResolver.resolve(passwordParam) + } + } + return { parameters: [addressParam, userParam, passwordParam], builder } +} diff --git a/src/base-vs/common.ts b/src/base-vs/common.ts new file mode 100644 index 0000000..8431fe8 --- /dev/null +++ b/src/base-vs/common.ts @@ -0,0 +1,14 @@ +function identity(arg: string): string { + return arg; +} + +export const convertInstanceIdToSchemaName = identity +export const convertSchemaNameToInstanceId = identity + +export function getConnectionName(virtualSchemaName: string): string { + return `${virtualSchemaName}_CONNECTION`; +} + +export function escapeSingleQuotes(value: string): string { + return value.replace(/'/g, "''") +} diff --git a/src/base-vs/getInstanceParameters.test.ts b/src/base-vs/getInstanceParameters.test.ts index ffe9f72..4f2f79e 100644 --- a/src/base-vs/getInstanceParameters.test.ts +++ b/src/base-vs/getInstanceParameters.test.ts @@ -9,12 +9,12 @@ import { emptyBaseVsExtension } from './test-vs-utils'; function getInstanceParameters(version: string): Parameter[] { const baseExtension = emptyBaseVsExtension() baseExtension.name = "testing-extension" - baseExtension.instanceParameterDefinitions = [{ id: "param1", name: "Param Name", type: "string" }] const installations = convertVirtualSchemaBaseExtension(baseExtension).getInstanceParameters(createMockContext(), version) expect(installations).toBeDefined() return installations } + describe("getInstanceParameters", () => { it("fails for unsupported version", () => { expect(() => getInstanceParameters("wrong version")) @@ -22,6 +22,20 @@ describe("getInstanceParameters", () => { }) it("succeeds for supported version", () => { expect(getInstanceParameters("v0")) - .toStrictEqual([{ id: "param1", name: "Param Name", type: "string" }]) + .toStrictEqual([ + { + description: "Name for the new virtual schema", + id: "base-vs.virtual-schema-name", + name: "Virtual Schema name", + placeholder: "MY_VIRTUAL_SCHEMA", + regex: "[a-zA-Z_]+", + required: true, + type: "string", + }, + { id: "vs-required", name: "n1", required: true, type: "string" }, + { id: "vs-optional", name: "n2", required: false, type: "string" }, + { id: "conn-required", name: "n1", required: true, type: "string" }, + { id: "conn-optional", name: "n2", required: false, type: "string" }, + ]) }) }) diff --git a/src/base-vs/getInstanceParameters.ts b/src/base-vs/getInstanceParameters.ts new file mode 100644 index 0000000..17e3db9 --- /dev/null +++ b/src/base-vs/getInstanceParameters.ts @@ -0,0 +1,8 @@ +import { JavaVirtualSchemaBaseExtension } from "."; +import { Parameter } from "../parameters"; +import { PARAM_VIRTUAL_SCHEMA_NAME } from "./parameters"; + + +export function getInstanceParameters(baseExtension: JavaVirtualSchemaBaseExtension): Parameter[] { + return [PARAM_VIRTUAL_SCHEMA_NAME, ...baseExtension.builder.getParameters()] +} diff --git a/src/base-vs/index.ts b/src/base-vs/index.ts index 972c886..f348086 100644 --- a/src/base-vs/index.ts +++ b/src/base-vs/index.ts @@ -2,23 +2,44 @@ import { Context, ExasolExtension, Instance, NotFoundError, Parameter, Parameter import { JavaBaseExtension, convertBaseExtension } from "../base"; import { addInstance } from "./addInstance"; import { findInstances } from "./findInstances"; +import { getInstanceParameters } from "./getInstanceParameters"; +import { ParameterResolver, createParameterResolver } from "./parameterResolver"; export interface JavaVirtualSchemaBaseExtension extends JavaBaseExtension { - instanceParameterDefinitions: Parameter[] - virtualSchemaAdapterScript: string + virtualSchemaAdapterScript: string, + builder: VirtualSchemaBuilder, } +export interface ConnectionDefinition { + connectionTo?: string + user?: string + identifiedBy?: string +} + +export interface VirtualSchemaProperty { + property: string + value: string +} +export interface VirtualSchemaDefinition { + adapterName: string + properties: VirtualSchemaProperty[] +} + +export interface VirtualSchemaBuilder { + getParameters: () => Parameter[] + buildVirtualSchema: (parameterResolver: ParameterResolver, connectionName?: string) => VirtualSchemaDefinition + buildConnection: (parameterResolver: ParameterResolver) => ConnectionDefinition +} export function convertVirtualSchemaBaseExtension(baseExtension: JavaVirtualSchemaBaseExtension): ExasolExtension { const extension = convertBaseExtension(baseExtension) - return { ...extension, getInstanceParameters(context: Context, extensionVersion: string): Parameter[] { if (baseExtension.version !== extensionVersion) { throw new NotFoundError(`Version '${extensionVersion}' not supported, can only use '${baseExtension.version}'.`) } - return baseExtension.instanceParameterDefinitions + return getInstanceParameters(baseExtension) }, findInstances(context: Context, _version: string): Instance[] { return findInstances(context, baseExtension.virtualSchemaAdapterScript); @@ -27,7 +48,11 @@ export function convertVirtualSchemaBaseExtension(baseExtension: JavaVirtualSche if (baseExtension.version !== versionToInstall) { throw new Error(`Version '${versionToInstall}' not supported, can only use ${baseExtension.version}.`) } - return addInstance(context, instanceParameterDefinitions, paramValues) + const parameterResolver = createParameterResolver(paramValues) + return addInstance(context, baseExtension, parameterResolver) } } } + + +export * from './builders'; diff --git a/src/base-vs/parameterResolver.test.ts b/src/base-vs/parameterResolver.test.ts new file mode 100644 index 0000000..3e2fafe --- /dev/null +++ b/src/base-vs/parameterResolver.test.ts @@ -0,0 +1,92 @@ + +import { describe, expect, it } from '@jest/globals'; +import { Parameter, ParameterValue } from '../api'; +import { createParameterResolver } from './parameterResolver'; + +describe("ParameterResolver", () => { + function param(name: string, value: string): ParameterValue { + return { name, value } + } + function testee(...values: ParameterValue[]) { + return createParameterResolver({ values }) + } + + describe("createParameterResolver()", () => { + it("succeeds for empty parameter list", () => { + expect(testee()).toBeDefined() + }) + it("succeeds for single parameter", () => { + expect(testee(param("p1", "v1"))).toBeDefined() + }) + it("succeeds for multiple parameters", () => { + expect(testee(param("p1", "v1"), param("p2", "v2"))).toBeDefined() + }) + it("fails for duplicate parameter names", () => { + expect(() => testee(param("p1", "v1"), param("p1", "v2"))).toThrow("Two values 'v2' and 'v1' found for parameter p1") + }) + }) + + describe("resolveOptional()", () => { + it("fails for required parameter", () => { + expect(() => testee().resolveOptional({ id: "p1", name: "n1", type: "string", required: true })).toThrow("Parameter p1 is required but not defined") + }) + it("fails for unsupported parameter type boolean", () => { + expect(() => testee().resolveOptional({ id: "p1", name: "n1", type: "boolean" })).toThrow("Parameter type boolean for parameter p1 not supported") + }) + describe("supported parameter types", () => { + const tests: { name: string, param: Parameter }[] = [ + { name: "string", param: { id: "p1", name: "n1", type: "string", required: false } }, + { name: "select", param: { id: "p1", name: "n1", type: "select", options: [], required: false } }, + ] + tests.forEach(test => describe(test.name, () => { + it("returns undefined for empty parameter values", () => { + expect(testee().resolveOptional(test.param)).toBeUndefined() + }) + it("returns undefined for missing parameter", () => { + expect(testee(param("wrong-name", "value")).resolveOptional(test.param)).toBeUndefined() + }) + it("returns value for available parameter", () => { + expect(testee(param(test.param.id, "value")).resolveOptional(test.param)).toEqual("value") + }) + it("returns undefined for undefined parameter", () => { + expect(testee(param(test.param.id, undefined)).resolveOptional(test.param)).toBeUndefined() + }) + it("returns value for multiple parameter", () => { + expect(testee(param("wrong-name", "value"), param(test.param.id, "value")).resolveOptional(test.param)).toEqual("value") + }) + })) + }) + }) + + describe("resolve()", () => { + it("fails for optional parameter", () => { + expect(() => testee().resolve({ id: "p1", name: "n1", type: "string", required: false })).toThrow("Parameter p1 is optional. Use method 'resolveOptional()' to resolve it.") + }) + it("fails for unsupported parameter type boolean", () => { + expect(() => testee().resolve({ id: "p1", name: "n1", type: "boolean" })).toThrow("Parameter type boolean for parameter p1 not supported") + }) + describe("supported parameter types", () => { + const tests: { name: string, param: Parameter }[] = [ + { name: "string", param: { id: "p1", name: "n1", type: "string", required: true } }, + { name: "select", param: { id: "p1", name: "n1", type: "select", options: [], required: true } }, + ] + tests.forEach(test => describe(test.name, () => { + it("fails for empty parameter values", () => { + expect(() => testee().resolve(test.param)).toThrow("No value found for required parameter p1") + }) + it("fails for missing parameter", () => { + expect(() => testee(param("wrong-name", "value")).resolve(test.param)).toThrow("No value found for required parameter p1") + }) + it("returns value for available parameter", () => { + expect(testee(param(test.param.id, "value")).resolve(test.param)).toEqual("value") + }) + it("fails for undefined parameter", () => { + expect(() => testee(param(test.param.id, undefined)).resolve(test.param)).toThrow("No value found for required parameter p1") + }) + it("returns value for multiple parameter", () => { + expect(testee(param("wrong-name", "value"), param(test.param.id, "value")).resolve(test.param)).toEqual("value") + }) + })) + }) + }) +}) diff --git a/src/base-vs/parameterResolver.ts b/src/base-vs/parameterResolver.ts new file mode 100644 index 0000000..5f1719b --- /dev/null +++ b/src/base-vs/parameterResolver.ts @@ -0,0 +1,45 @@ +import { Parameter, ParameterValues } from "../api"; + +export interface ParameterResolver { + resolve: (parameterDefinition: Parameter) => string; + resolveOptional: (parameterDefinition: Parameter) => string | undefined; +} + +function buildValuesMap(values: ParameterValues): Map { + const valuesMap = new Map() + for (const value of values.values) { + if (valuesMap.has(value.name)) { + throw new Error(`Two values '${value.value}' and '${valuesMap.get(value.name)}' found for parameter ${value.name}`) + } + valuesMap.set(value.name, value.value) + } + return valuesMap +} + +export function createParameterResolver(paramValues: ParameterValues): ParameterResolver { + const values = buildValuesMap(paramValues) + function resolveOptional(paramDef: Parameter): string | undefined { + if (paramDef.type !== "string" && paramDef.type !== "select") { + throw new Error(`Parameter type ${paramDef.type} for parameter ${paramDef.id} not supported`) + } + if (paramDef.required && !values.has(paramDef.id)) { + throw new Error(`Parameter ${paramDef.id} is required but not defined`) + } + return values.get(paramDef.id); + } + + function resolve(paramDef: Parameter): string { + if (paramDef.type !== "string" && paramDef.type !== "select") { + throw new Error(`Parameter type ${paramDef.type} for parameter ${paramDef.id} not supported`) + } + if (!paramDef.required) { + throw new Error(`Parameter ${paramDef.id} is optional. Use method 'resolveOptional()' to resolve it.`) + } + const value = values.get(paramDef.id) + if (!value) { + throw new Error(`No value found for required parameter ${paramDef.id}`) + } + return value + } + return { resolveOptional, resolve } +} diff --git a/src/base-vs/parameters.ts b/src/base-vs/parameters.ts new file mode 100644 index 0000000..1e04c99 --- /dev/null +++ b/src/base-vs/parameters.ts @@ -0,0 +1,6 @@ +import { Parameter } from "../parameters"; + +export const PARAM_VIRTUAL_SCHEMA_NAME: Parameter = { + id: "base-vs.virtual-schema-name", name: "Virtual Schema name", description: "Name for the new virtual schema", + type: "string", required: true, placeholder: "MY_VIRTUAL_SCHEMA", regex: "[a-zA-Z_]+" +} diff --git a/src/base-vs/test-vs-utils.ts b/src/base-vs/test-vs-utils.ts index bd8b4fc..0062d3a 100644 --- a/src/base-vs/test-vs-utils.ts +++ b/src/base-vs/test-vs-utils.ts @@ -1,7 +1,8 @@ -import { JavaVirtualSchemaBaseExtension } from "."; +import { JavaVirtualSchemaBaseExtension, createJsonConnectionDefinition, createVirtualSchemaBuilder } from "."; import { successResult } from "../base/common"; export function emptyBaseVsExtension(): JavaVirtualSchemaBaseExtension { + const adapterName = "TESTING_ADAPTER" return { name: "testing-base-extension", version: "v0", @@ -11,9 +12,17 @@ export function emptyBaseVsExtension(): JavaVirtualSchemaBaseExtension { name: "test-ext.jar", size: 12345 }, - scripts: [], + scripts: [{ name: adapterName, type: "SCALAR", args: "...", scriptClass: "com.exasol.TestingAdapter" }], scriptVersionExtractor: () => successResult("dummy version"), - instanceParameterDefinitions: [], - virtualSchemaAdapterScript: "vs-adapter-script-name" + virtualSchemaAdapterScript: "vs-adapter-script-name", + builder: createVirtualSchemaBuilder({ + adapterName, connectionNameProperty: "CONNECTION_NAME", + virtualSchemaParameters: [ + { id: "vs-required", name: "n1", type: "string", required: true }, + { id: "vs-optional", name: "n2", type: "string", required: false }], + connectionDefinition: createJsonConnectionDefinition([ + { id: "conn-required", name: "n1", type: "string", required: true }, + { id: "conn-optional", name: "n2", type: "string", required: false }]) + }) } } diff --git a/src/parameters.ts b/src/parameters.ts index 5a69086..db00430 100644 --- a/src/parameters.ts +++ b/src/parameters.ts @@ -2,8 +2,8 @@ /** * The definition of a parameter. */ - export type Parameter = StringParameter | SelectParameter | BooleanParameter; - +export type Parameter = StringParameter | SelectParameter | BooleanParameter; + /** * Abstract base interface with common fields for parameter definitions. */ @@ -12,6 +12,8 @@ interface BaseParameter { id: string /** The name displayed to the user */ name: string + /** Detailed description displayed to the user e.g. in a tooltip */ + description?: string /** The type of the parameter */ type: string /** Defines if a value must be specified (`true`) or if it is optional (`false`). Default: `false`. */ @@ -19,6 +21,7 @@ interface BaseParameter { /** An optional condition */ condition?: Condition default?: string + /** Short hint that describes the expected value of the input field */ placeholder?: string readOnly?: boolean } From 5eed8d2f946322a74618b38dc99c431a27af0174 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Tue, 31 Oct 2023 09:22:15 +0100 Subject: [PATCH 09/29] Add support for ADAPTER scripts --- package.json | 2 +- src/base-vs/builders.ts | 17 ++++++++- src/base-vs/getInstanceParameters.test.ts | 1 - src/base-vs/index.ts | 6 +++- src/base-vs/parameterResolver.test.ts | 8 ++--- src/base-vs/parameterResolver.ts | 6 ---- src/base-vs/test-vs-utils.ts | 2 +- src/base/findInstallations.test.ts | 7 ++-- src/base/index.ts | 34 +++++++++++++++--- src/base/install.test.ts | 44 +++++++++++++++++------ src/base/install.ts | 13 +++++-- src/base/uninstall.test.ts | 6 ++-- src/base/upgrade.test.ts | 8 ++--- src/base/validateScripts.test.ts | 8 ++--- 14 files changed, 113 insertions(+), 49 deletions(-) diff --git a/package.json b/package.json index 19a6195..9bf5b38 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "build": "tsc --build", "clean": "tsc --build --clean", "test": "jest --silent", - "test-watch": "jest --watch", + "test-watch": "jest --watch --silent", "lint": "eslint --report-unused-disable-directives --exit-on-fatal-error ./src/" }, "devDependencies": { diff --git a/src/base-vs/builders.ts b/src/base-vs/builders.ts index 2df6fb1..afa53df 100644 --- a/src/base-vs/builders.ts +++ b/src/base-vs/builders.ts @@ -17,7 +17,10 @@ export interface Config { virtualSchemaParameters: Parameter[] /** Name of the Virtual Schema's connection property, e.g. "CONNECTION_NAME" */ connectionNameProperty: string - /** Connection definition */ + /** + * Connection definition. Create a connection definition with one of the functions + * {@link createJsonConnectionDefinition} or {@link createUserPasswordConnectionDefinition} + */ connectionDefinition: ConnectionParameterDefinition } @@ -46,6 +49,11 @@ export function createVirtualSchemaBuilder({ adapterName, virtualSchemaParameter } } +/** + * Creates the definition for a connection that contains all configuration formatted as JSON in the IDENTIFIED BY clause. + * @param parameters parameter definitions + * @returns connection definition + */ export function createJsonConnectionDefinition(parameters: Parameter[]): ConnectionParameterDefinition { function builder(parameterResolver: ParameterResolver): ConnectionDefinition { /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -63,6 +71,13 @@ export function createJsonConnectionDefinition(parameters: Parameter[]): Connect return { parameters, builder } } +/** + * Creates the definition for a connection that contains TO, USER and IDENTIFIED BY clauses. + * @param addressParam parameter definition for the TO clause + * @param userParam parameter definition for the USER clause + * @param passwordParam parameter definition for the IDENTIFIED BY clause + * @returns connection definition + */ export function createUserPasswordConnectionDefinition(addressParam: Parameter, userParam: Parameter, passwordParam: Parameter): ConnectionParameterDefinition { function builder(parameterResolver: ParameterResolver): ConnectionDefinition { return { diff --git a/src/base-vs/getInstanceParameters.test.ts b/src/base-vs/getInstanceParameters.test.ts index 4f2f79e..b23bb1b 100644 --- a/src/base-vs/getInstanceParameters.test.ts +++ b/src/base-vs/getInstanceParameters.test.ts @@ -14,7 +14,6 @@ function getInstanceParameters(version: string): Parameter[] { return installations } - describe("getInstanceParameters", () => { it("fails for unsupported version", () => { expect(() => getInstanceParameters("wrong version")) diff --git a/src/base-vs/index.ts b/src/base-vs/index.ts index f348086..25c3372 100644 --- a/src/base-vs/index.ts +++ b/src/base-vs/index.ts @@ -5,8 +5,13 @@ import { findInstances } from "./findInstances"; import { getInstanceParameters } from "./getInstanceParameters"; import { ParameterResolver, createParameterResolver } from "./parameterResolver"; +/** + * Simplified version of an {@link ExasolExtension} specifically for Java based virtual schemas. + */ export interface JavaVirtualSchemaBaseExtension extends JavaBaseExtension { + /** Unqualified name of the virtual schema adapter script, e.g. "S3_FILES_ADAPTER" */ virtualSchemaAdapterScript: string, + /** A builder for virtual schemas. Use function {@link createVirtualSchemaBuilder()} to create it. */ builder: VirtualSchemaBuilder, } @@ -54,5 +59,4 @@ export function convertVirtualSchemaBaseExtension(baseExtension: JavaVirtualSche } } - export * from './builders'; diff --git a/src/base-vs/parameterResolver.test.ts b/src/base-vs/parameterResolver.test.ts index 3e2fafe..ad2bd05 100644 --- a/src/base-vs/parameterResolver.test.ts +++ b/src/base-vs/parameterResolver.test.ts @@ -30,13 +30,11 @@ describe("ParameterResolver", () => { it("fails for required parameter", () => { expect(() => testee().resolveOptional({ id: "p1", name: "n1", type: "string", required: true })).toThrow("Parameter p1 is required but not defined") }) - it("fails for unsupported parameter type boolean", () => { - expect(() => testee().resolveOptional({ id: "p1", name: "n1", type: "boolean" })).toThrow("Parameter type boolean for parameter p1 not supported") - }) describe("supported parameter types", () => { const tests: { name: string, param: Parameter }[] = [ { name: "string", param: { id: "p1", name: "n1", type: "string", required: false } }, { name: "select", param: { id: "p1", name: "n1", type: "select", options: [], required: false } }, + { name: "boolean", param: { id: "p1", name: "n1", type: "boolean", required: false } }, ] tests.forEach(test => describe(test.name, () => { it("returns undefined for empty parameter values", () => { @@ -62,13 +60,11 @@ describe("ParameterResolver", () => { it("fails for optional parameter", () => { expect(() => testee().resolve({ id: "p1", name: "n1", type: "string", required: false })).toThrow("Parameter p1 is optional. Use method 'resolveOptional()' to resolve it.") }) - it("fails for unsupported parameter type boolean", () => { - expect(() => testee().resolve({ id: "p1", name: "n1", type: "boolean" })).toThrow("Parameter type boolean for parameter p1 not supported") - }) describe("supported parameter types", () => { const tests: { name: string, param: Parameter }[] = [ { name: "string", param: { id: "p1", name: "n1", type: "string", required: true } }, { name: "select", param: { id: "p1", name: "n1", type: "select", options: [], required: true } }, + { name: "boolean", param: { id: "p1", name: "n1", type: "boolean", required: true } }, ] tests.forEach(test => describe(test.name, () => { it("fails for empty parameter values", () => { diff --git a/src/base-vs/parameterResolver.ts b/src/base-vs/parameterResolver.ts index 5f1719b..a689ec7 100644 --- a/src/base-vs/parameterResolver.ts +++ b/src/base-vs/parameterResolver.ts @@ -19,9 +19,6 @@ function buildValuesMap(values: ParameterValues): Map { export function createParameterResolver(paramValues: ParameterValues): ParameterResolver { const values = buildValuesMap(paramValues) function resolveOptional(paramDef: Parameter): string | undefined { - if (paramDef.type !== "string" && paramDef.type !== "select") { - throw new Error(`Parameter type ${paramDef.type} for parameter ${paramDef.id} not supported`) - } if (paramDef.required && !values.has(paramDef.id)) { throw new Error(`Parameter ${paramDef.id} is required but not defined`) } @@ -29,9 +26,6 @@ export function createParameterResolver(paramValues: ParameterValues): Parameter } function resolve(paramDef: Parameter): string { - if (paramDef.type !== "string" && paramDef.type !== "select") { - throw new Error(`Parameter type ${paramDef.type} for parameter ${paramDef.id} not supported`) - } if (!paramDef.required) { throw new Error(`Parameter ${paramDef.id} is optional. Use method 'resolveOptional()' to resolve it.`) } diff --git a/src/base-vs/test-vs-utils.ts b/src/base-vs/test-vs-utils.ts index 0062d3a..3534e93 100644 --- a/src/base-vs/test-vs-utils.ts +++ b/src/base-vs/test-vs-utils.ts @@ -12,7 +12,7 @@ export function emptyBaseVsExtension(): JavaVirtualSchemaBaseExtension { name: "test-ext.jar", size: 12345 }, - scripts: [{ name: adapterName, type: "SCALAR", args: "...", scriptClass: "com.exasol.TestingAdapter" }], + scripts: [{ name: adapterName, type: "SCALAR", parameters: "...", emitParameters: "...", scriptClass: "com.exasol.TestingAdapter" }], scriptVersionExtractor: () => successResult("dummy version"), virtualSchemaAdapterScript: "vs-adapter-script-name", builder: createVirtualSchemaBuilder({ diff --git a/src/base/findInstallations.test.ts b/src/base/findInstallations.test.ts index 09df269..24a4d43 100644 --- a/src/base/findInstallations.test.ts +++ b/src/base/findInstallations.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from '@jest/globals'; -import { ScriptDefinition, VersionExtractor, convertBaseExtension } from '.'; +import { ScalarSetScriptDefinition, ScriptDefinition, VersionExtractor, convertBaseExtension } from '.'; import { Installation } from '../api'; import { PreconditionFailedError } from '../error'; import { ExaMetadata, ExaScriptsRow } from '../exasolSchema'; @@ -13,8 +13,8 @@ const failingVersionExtractor: (failureMessage: string) => VersionExtractor = (f function script({ schema = "schema", name = "name", inputType, resultType = "EMITS", type = "UDF", text = "", comment }: Partial): ExaScriptsRow { return { schema, name, inputType, resultType, type, text, comment } } -function def({ name = "name", type = "SET", args = "args", scriptClass = "script class" }: Partial): ScriptDefinition { - return { name, type, args, scriptClass }; +function def({ name = "name", type = "SET", parameters = "param", emitParameters = "emitParam", scriptClass = "script class" }: Partial): ScriptDefinition { + return { name, type, parameters, emitParameters, scriptClass }; } function findInstallations(allScripts: ExaScriptsRow[], scriptDefinitions: ScriptDefinition[], versionExtractor: VersionExtractor): Installation[] { @@ -48,4 +48,3 @@ describe("findInstallations", () => { .toStrictEqual([{ name: "testing-extension", version: "v1" }]) }) }) - diff --git a/src/base/index.ts b/src/base/index.ts index 21f90ea..20bae23 100644 --- a/src/base/index.ts +++ b/src/base/index.ts @@ -6,23 +6,50 @@ import { uninstall } from "./uninstall"; import { upgrade } from "./upgrade"; /** Definition of a Java based Exasol `SCRIPT` with all information required for creating it in the database. */ -export interface ScriptDefinition { +export type ScriptDefinition = ScalarSetScriptDefinition | AdapterScriptDefinition + +/** Definition of a SCALAR or SET script */ +export interface ScalarSetScriptDefinition { + /** Script type */ + type: "SCALAR" | "SET" + /** Unqualified script name, e.g. "IMPORT_FROM_S3_DOCUMENT_FILES", KAFKA_CONSUMER */ + name: string + /** Script parameters, e.g. "..." or "params VARCHAR(2000), kafka_partition DECIMAL(18, 0), kafka_offset DECIMAL(36, 0)" */ + parameters: string + /** Emit parameters, e.g. "..." or "filename VARCHAR(2000), partition_index VARCHAR(100), start_index DECIMAL(36, 0), end_index DECIMAL(36, 0)" */ + emitParameters: string + /** Script Java class name, e.g. "com.exasol.adapter.document.UdfEntryPoint" */ + scriptClass: string +} + +/** Definition of an ADAPTER script */ +export interface AdapterScriptDefinition { + /** Unqualified script name, e.g. "S3_FILES_ADAPTER" */ name: string - type: "SET" | "SCALAR" - args: string + type: "ADAPTER" + /** Script Java class name, e.g. "com.exasol.adapter.document.UdfEntryPoint" */ scriptClass: string } +/** + * Simplified version of an {@link ExasolExtension} specifically for Java based extensions. + */ export interface JavaBaseExtension { + /** Readable extension name, e.g. "S3 Virtual Schema" */ name: string + /** Extension description, e.g. "Virtual Schema for document files on AWS S3" */ description: string + /** Extension category, e.g. "document-virtual-schema", "jdbc-virtual-schema", ... */ category: string + /** Current extension version, e.g. 1.2.3 */ version: string file: { name: string size: number } + /** Adapter script definitions for this extension */ scripts: ScriptDefinition[] + /** Extracts the version number from the SCRIPT text. A possible implementation is {@link jarFileVersionExtractor}. */ scriptVersionExtractor: VersionExtractor } @@ -72,4 +99,3 @@ export function convertBaseExtension(baseExtension: JavaBaseExtension): ExasolEx } } } - diff --git a/src/base/install.test.ts b/src/base/install.test.ts index eafa650..6093658 100644 --- a/src/base/install.test.ts +++ b/src/base/install.test.ts @@ -4,9 +4,6 @@ import { PreconditionFailedError } from '../error'; import { ScriptDefinition, convertBaseExtension } from './index'; import { ContextMock, createMockContext, emptyBaseExtension } from './test-utils'; -function def({ name = "name", type = "SET", args = "args", scriptClass = "script class" }: Partial): ScriptDefinition { - return { name, type, args, scriptClass }; -} describe("install", () => { @@ -27,25 +24,52 @@ describe("install", () => { expect(executeCalls.length).toBe(0) }) - it("single script", () => { - install("v1", [def({ name: "SCRIPT_1", type: "SET", args: "SCRIPT_ARGS", scriptClass: "com.example.Script" })]) + it("creates comment", () => { + install("v1", [{ name: "SCRIPT_1", type: "SET", parameters: "param", emitParameters: "emitParam", scriptClass: "com.example.Script" }]) + const executeCalls = context.mocks.sqlExecute.mock.calls + expect(executeCalls.length).toBe(2) + expect(executeCalls[1][0]).toBe(`COMMENT ON SCRIPT "ext-schema"."SCRIPT_1" IS 'Created by Extension Manager for test-ext v1'`) + }) + + it("creates a set script", () => { + install("v1", [{ name: "SCRIPT_1", type: "SET", parameters: "param", emitParameters: "emitParam", scriptClass: "com.example.Script" }]) const executeCalls = context.mocks.sqlExecute.mock.calls expect(executeCalls.length).toBe(2) - expect(executeCalls[0][0]).toBe(`CREATE OR REPLACE JAVA SET SCRIPT "ext-schema"."SCRIPT_1"(...) EMITS (SCRIPT_ARGS) AS + expect(executeCalls[0][0]).toBe(`CREATE OR REPLACE JAVA SET SCRIPT "ext-schema"."SCRIPT_1"(param) EMITS (emitParam) AS + %scriptclass com.example.Script; + %jar /bucketfs/test-ext.jar;`) + }) + + it("creates a scalar script", () => { + install("v1", [{ name: "SCRIPT_1", type: "SCALAR", parameters: "...", emitParameters: "emitArgs", scriptClass: "com.example.Script" }]) + const executeCalls = context.mocks.sqlExecute.mock.calls + expect(executeCalls.length).toBe(2) + + expect(executeCalls[0][0]).toBe(`CREATE OR REPLACE JAVA SCALAR SCRIPT "ext-schema"."SCRIPT_1"(...) EMITS (emitArgs) AS + %scriptclass com.example.Script; + %jar /bucketfs/test-ext.jar;`) + }) + + + it("creates an adapter script", () => { + install("v1", [{ name: "SCRIPT_1", type: "ADAPTER", scriptClass: "com.example.Script" }]) + const executeCalls = context.mocks.sqlExecute.mock.calls + expect(executeCalls.length).toBe(2) + + expect(executeCalls[0][0]).toBe(`CREATE OR REPLACE JAVA ADAPTER SCRIPT "ext-schema"."SCRIPT_1" AS %scriptclass com.example.Script; %jar /bucketfs/test-ext.jar;`) - expect(executeCalls[1][0]).toBe(`COMMENT ON SCRIPT "ext-schema"."SCRIPT_1" IS 'Created by Extension Manager for test-ext v1'`) }) it("two scripts", () => { install("v1", [ - def({ name: "SCRIPT_1", type: "SET", args: "SCRIPT_ARGS", scriptClass: "com.example.Script1" }), - def({ name: "SCRIPT_2", type: "SCALAR", args: "...", scriptClass: "com.example.Script2" })]) + { name: "SCRIPT_1", type: "SET", parameters: "param", emitParameters: "emitParam", scriptClass: "com.example.Script1" }, + { name: "SCRIPT_2", type: "SCALAR", parameters: "...", emitParameters: "...", scriptClass: "com.example.Script2" }]) const executeCalls = context.mocks.sqlExecute.mock.calls expect(executeCalls.length).toBe(4) - expect(executeCalls[0][0]).toBe(`CREATE OR REPLACE JAVA SET SCRIPT "ext-schema"."SCRIPT_1"(...) EMITS (SCRIPT_ARGS) AS + expect(executeCalls[0][0]).toBe(`CREATE OR REPLACE JAVA SET SCRIPT "ext-schema"."SCRIPT_1"(param) EMITS (emitParam) AS %scriptclass com.example.Script1; %jar /bucketfs/test-ext.jar;`) expect(executeCalls[1][0]).toBe(`CREATE OR REPLACE JAVA SCALAR SCRIPT "ext-schema"."SCRIPT_2"(...) EMITS (...) AS diff --git a/src/base/install.ts b/src/base/install.ts index 845ea9f..e2cb384 100644 --- a/src/base/install.ts +++ b/src/base/install.ts @@ -14,14 +14,21 @@ export function installExtension(context: Context, extension: JavaBaseExtension, } function createScript(script: ScriptDefinition): string { - return `CREATE OR REPLACE JAVA ${script.type} SCRIPT ${qualifiedName(script)}(...) EMITS (${script.args}) AS - %scriptclass ${script.scriptClass}; - %jar ${jarPath};`; + let stmt = `CREATE OR REPLACE JAVA ${script.type} SCRIPT ${qualifiedName(script)}` + if (script.type == "SCALAR" || script.type == "SET") { + stmt += `(${script.parameters}) EMITS (${script.emitParameters})` + } + stmt += " AS\n" + stmt += ` %scriptclass ${script.scriptClass};\n` + stmt += ` %jar ${jarPath};` + return stmt } + function createComment(script: ScriptDefinition): string { return `COMMENT ON SCRIPT ${qualifiedName(script)} IS 'Created by Extension Manager for ${extension.name} ${extension.version}'`; } + console.log(`Installing extension '${extension.name}' in version ${extension.version} with ${extension.scripts.length} scripts...`) extension.scripts.forEach(script => context.sqlClient.execute(createScript(script))); extension.scripts.forEach(script => context.sqlClient.execute(createComment(script))); } diff --git a/src/base/uninstall.test.ts b/src/base/uninstall.test.ts index 480d4ce..83c17d3 100644 --- a/src/base/uninstall.test.ts +++ b/src/base/uninstall.test.ts @@ -1,11 +1,11 @@ import { beforeEach, describe, expect, it } from '@jest/globals'; import { PreconditionFailedError } from '../error'; -import { ScriptDefinition, convertBaseExtension } from './index'; +import { ScalarSetScriptDefinition, ScriptDefinition, convertBaseExtension } from './index'; import { ContextMock, createMockContext, emptyBaseExtension } from './test-utils'; -function def({ name = "name", type = "SET", args = "args", scriptClass = "script class" }: Partial): ScriptDefinition { - return { name, type, args, scriptClass }; +function def({ name = "name", type = "SET", parameters = "param", emitParameters = "emitParam", scriptClass = "script class" }: Partial): ScriptDefinition { + return { name, type, parameters, emitParameters, scriptClass }; } describe("uninstall", () => { diff --git a/src/base/upgrade.test.ts b/src/base/upgrade.test.ts index d343658..c02bdd0 100644 --- a/src/base/upgrade.test.ts +++ b/src/base/upgrade.test.ts @@ -3,11 +3,11 @@ import { beforeEach, describe, expect, it } from '@jest/globals'; import { PreconditionFailedError } from '../error'; import { ExaScriptsRow } from '../exasolSchema'; import { successResult } from './common'; -import { ScriptDefinition, VersionExtractor, convertBaseExtension } from './index'; +import { ScalarSetScriptDefinition, ScriptDefinition, VersionExtractor, convertBaseExtension } from './index'; import { ContextMock, createMockContext, emptyBaseExtension } from './test-utils'; -function def({ name = "name", type = "SET", args = "args", scriptClass = "script class" }: Partial): ScriptDefinition { - return { name, type, args, scriptClass }; +function def({ name = "name", type = "SET", parameters = "param", emitParameters = "emitParam", scriptClass = "script class" }: Partial): ScriptDefinition { + return { name, type, parameters, emitParameters, scriptClass }; } function script({ schema = "schema", name = "name", inputType, resultType = "EMITS", type = "UDF", text = "", comment }: Partial): ExaScriptsRow { @@ -63,7 +63,7 @@ describe("upgrade", () => { const executeCalls = context.mocks.sqlExecute.mock.calls expect(executeCalls.length).toBe(2) - expect(executeCalls[0][0]).toBe(`CREATE OR REPLACE JAVA SET SCRIPT "ext-schema"."SCRIPT_1"(...) EMITS (args) AS + expect(executeCalls[0][0]).toBe(`CREATE OR REPLACE JAVA SET SCRIPT "ext-schema"."SCRIPT_1"(param) EMITS (emitParam) AS %scriptclass com.example.Script; %jar /bucketfs/test-ext.jar;`) expect(executeCalls[1][0]).toBe(`COMMENT ON SCRIPT "ext-schema"."SCRIPT_1" IS 'Created by Extension Manager for test-ext v1'`) diff --git a/src/base/validateScripts.test.ts b/src/base/validateScripts.test.ts index 448a68f..9cf1b55 100644 --- a/src/base/validateScripts.test.ts +++ b/src/base/validateScripts.test.ts @@ -3,15 +3,15 @@ import { describe, expect, it } from '@jest/globals'; import { ExaScriptsRow } from '../exasolSchema'; import { AdapterScript } from './adapterScript'; import { failureResult, successResult } from './common'; -import { ScriptDefinition, VersionExtractor } from './index'; +import { ScalarSetScriptDefinition, ScriptDefinition, VersionExtractor } from './index'; import { InstalledScripts, validateInstalledScripts, validateVersions } from './validateScripts'; function script({ schema = "schema", name = "name", inputType, resultType = "EMITS", type = "UDF", text = "", comment }: Partial): ExaScriptsRow { return { schema, name, inputType, resultType, type, text, comment } } -function def({ name = "name", type = "SET", args = "args", scriptClass = "script class" }: Partial): ScriptDefinition { - return { name: name, type, args, scriptClass }; +function def({ name = "name", type = "SET", parameters = "param", emitParameters = "emitParam", scriptClass = "script class" }: Partial): ScriptDefinition { + return { name, type, parameters, emitParameters, scriptClass }; } function scriptWithVersion(name: string, version: string): AdapterScript { @@ -91,4 +91,4 @@ describe("validateScripts", () => { }) }) }) -}) \ No newline at end of file +}) From bc02e29e8b43db05095dbd6c335b35676940f0fb Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Tue, 31 Oct 2023 11:29:25 +0100 Subject: [PATCH 10/29] Fix CREATE VIRTUAL SCHEMA syntax --- src/base-vs/addInstance.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/base-vs/addInstance.ts b/src/base-vs/addInstance.ts index 9ed861f..22b2cfe 100644 --- a/src/base-vs/addInstance.ts +++ b/src/base-vs/addInstance.ts @@ -22,9 +22,13 @@ function buildVirtualSchemaStatement(baseExtension: JavaVirtualSchemaBaseExtensi const def = baseExtension.builder.buildVirtualSchema(parameterResolver, connectionName); const adapter = `"${context.extensionSchemaName}"."${def.adapterName}"`; let stmt = `CREATE VIRTUAL SCHEMA "${virtualSchemaName}" USING ${adapter}`; - for (const property of def.properties) { - stmt += ` ${property.property} = '${escapeSingleQuotes(property.value)}'`; + if (def.properties.length > 0) { + stmt += " WITH" + for (const property of def.properties) { + stmt += `\n ${property.property} = '${escapeSingleQuotes(property.value)}'`; + } } + console.log(`Creating virtual schema with statement ${stmt}`) return stmt; } From 59828e7eb85652be290c684bb2c7b8c7fc121ed7 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Tue, 31 Oct 2023 11:29:36 +0100 Subject: [PATCH 11/29] Fix dropping adapter scripts --- src/base/uninstall.test.ts | 26 +++++++++++++++++++------- src/base/uninstall.ts | 10 +++++++++- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/base/uninstall.test.ts b/src/base/uninstall.test.ts index 83c17d3..143df2c 100644 --- a/src/base/uninstall.test.ts +++ b/src/base/uninstall.test.ts @@ -1,13 +1,17 @@ import { beforeEach, describe, expect, it } from '@jest/globals'; import { PreconditionFailedError } from '../error'; -import { ScalarSetScriptDefinition, ScriptDefinition, convertBaseExtension } from './index'; +import { AdapterScriptDefinition, ScalarSetScriptDefinition, ScriptDefinition, convertBaseExtension } from './index'; import { ContextMock, createMockContext, emptyBaseExtension } from './test-utils'; function def({ name = "name", type = "SET", parameters = "param", emitParameters = "emitParam", scriptClass = "script class" }: Partial): ScriptDefinition { return { name, type, parameters, emitParameters, scriptClass }; } +function adapterDef({ name = "name", scriptClass = "script class" }: Partial): ScriptDefinition { + return { name, type: 'ADAPTER', scriptClass }; +} + describe("uninstall", () => { let context: ContextMock @@ -53,12 +57,20 @@ describe("uninstall", () => { expect(executeCalls.length).toBe(0) }) - it("executes expected statements for single script", () => { - simulateSchemaExists() - uninstall("v1", [def({ name: "SCRIPT1" })]) - const calls = context.mocks.sqlExecute.mock.calls - expect(calls.length).toEqual(1) - expect(calls[0]).toEqual([`DROP SCRIPT "ext-schema"."SCRIPT1"`]) + describe("executes expected statements", () => { + const tests: { name: string, script: ScriptDefinition, expectedDropStatement: string }[] = [ + { name: "set script", script: def({ name: "SCRIPT1", type: "SET" }), expectedDropStatement: `DROP SCRIPT "ext-schema"."SCRIPT1"` }, + { name: "scalar script", script: def({ name: "SCRIPT1", type: "SCALAR" }), expectedDropStatement: `DROP SCRIPT "ext-schema"."SCRIPT1"` }, + { name: "adapter script", script: adapterDef({ name: "SCRIPT1" }), expectedDropStatement: `DROP ADAPTER SCRIPT "ext-schema"."SCRIPT1"` }, + ] + + tests.forEach(test => it(test.name, () => { + simulateSchemaExists() + uninstall("v1", [test.script]) + const calls = context.mocks.sqlExecute.mock.calls + expect(calls.length).toEqual(1) + expect(calls[0]).toEqual([test.expectedDropStatement]) + })) }) it("executes expected statements for two scripts", () => { diff --git a/src/base/uninstall.ts b/src/base/uninstall.ts index bf05d0c..9b05e82 100644 --- a/src/base/uninstall.ts +++ b/src/base/uninstall.ts @@ -17,9 +17,17 @@ export function uninstall(context: Context, extension: JavaBaseExtension, versio return `"${context.extensionSchemaName}"."${script.name}"` } + function getDropScriptStatement(script: ScriptDefinition): string { + if (script.type === "ADAPTER") { + return `DROP ADAPTER SCRIPT ${qualifiedName(script)}` + } else { + return `DROP SCRIPT ${qualifiedName(script)}` + } + } + if (extensionSchemaExists()) { // Drop commands fail when schema does not exist. extension.scripts.forEach(script => - context.sqlClient.execute(`DROP SCRIPT ${qualifiedName(script)}`) + context.sqlClient.execute(getDropScriptStatement(script)) ); } } From 4763c27057a23ffa8921b634e416cc5d327bc4d8 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Tue, 31 Oct 2023 12:51:23 +0100 Subject: [PATCH 12/29] Centralize version check --- src/base-vs/deleteInstance.ts | 17 +++++++++++++++++ src/base-vs/index.ts | 22 +++++++++++++++------- src/base/index.ts | 12 ++++++++++-- src/base/install.test.ts | 2 +- src/base/install.ts | 6 +----- src/base/uninstall.test.ts | 2 +- src/base/uninstall.ts | 6 +----- src/base/upgrade.ts | 2 +- 8 files changed, 47 insertions(+), 22 deletions(-) create mode 100644 src/base-vs/deleteInstance.ts diff --git a/src/base-vs/deleteInstance.ts b/src/base-vs/deleteInstance.ts new file mode 100644 index 0000000..08a153f --- /dev/null +++ b/src/base-vs/deleteInstance.ts @@ -0,0 +1,17 @@ +import { JavaVirtualSchemaBaseExtension } from "."; +import { Context } from "../api"; +import { convertInstanceIdToSchemaName, getConnectionName } from "./common"; + +export function deleteInstance(context: Context, extension: JavaVirtualSchemaBaseExtension, instanceId: string): void { + const schemaName = convertInstanceIdToSchemaName(instanceId); + context.sqlClient.execute(dropVirtualSchemaStatement(schemaName)); + context.sqlClient.execute(dropConnectionStatement(getConnectionName(schemaName))); +} + +function dropVirtualSchemaStatement(schemaName: string): string { + return `DROP VIRTUAL SCHEMA IF EXISTS "${schemaName}" CASCADE`; +} + +function dropConnectionStatement(connectionName: string): string { + return `DROP CONNECTION IF EXISTS "${connectionName}"`; +} diff --git a/src/base-vs/index.ts b/src/base-vs/index.ts index 25c3372..7f81af4 100644 --- a/src/base-vs/index.ts +++ b/src/base-vs/index.ts @@ -1,6 +1,7 @@ import { Context, ExasolExtension, Instance, NotFoundError, Parameter, ParameterValues } from "../api"; import { JavaBaseExtension, convertBaseExtension } from "../base"; import { addInstance } from "./addInstance"; +import { deleteInstance } from "./deleteInstance"; import { findInstances } from "./findInstances"; import { getInstanceParameters } from "./getInstanceParameters"; import { ParameterResolver, createParameterResolver } from "./parameterResolver"; @@ -38,24 +39,31 @@ export interface VirtualSchemaBuilder { export function convertVirtualSchemaBaseExtension(baseExtension: JavaVirtualSchemaBaseExtension): ExasolExtension { const extension = convertBaseExtension(baseExtension) + + function verifyVersion(version: string) { + if (baseExtension.version !== version) { + throw new NotFoundError(`Version '${version}' not supported, can only use '${baseExtension.version}'.`) + } + } + return { ...extension, getInstanceParameters(context: Context, extensionVersion: string): Parameter[] { - if (baseExtension.version !== extensionVersion) { - throw new NotFoundError(`Version '${extensionVersion}' not supported, can only use '${baseExtension.version}'.`) - } + verifyVersion(extensionVersion) return getInstanceParameters(baseExtension) }, findInstances(context: Context, _version: string): Instance[] { return findInstances(context, baseExtension.virtualSchemaAdapterScript); }, addInstance(context: Context, versionToInstall: string, paramValues: ParameterValues): Instance { - if (baseExtension.version !== versionToInstall) { - throw new Error(`Version '${versionToInstall}' not supported, can only use ${baseExtension.version}.`) - } + verifyVersion(versionToInstall) const parameterResolver = createParameterResolver(paramValues) return addInstance(context, baseExtension, parameterResolver) - } + }, + deleteInstance(context: Context, extensionVersion: string, instanceId: string) { + verifyVersion(extensionVersion) + deleteInstance(context, baseExtension, instanceId) + }, } } diff --git a/src/base/index.ts b/src/base/index.ts index 20bae23..e69b01b 100644 --- a/src/base/index.ts +++ b/src/base/index.ts @@ -57,6 +57,12 @@ export type VersionExtractor = (adapterScriptText: string) => Result export { jarFileVersionExtractor } from './jarFileVersionExtractor'; export function convertBaseExtension(baseExtension: JavaBaseExtension): ExasolExtension { + function verifyVersion(version: string) { + if (baseExtension.version !== version) { + throw new NotFoundError(`Version '${version}' not supported, can only use '${baseExtension.version}'.`) + } + } + return { name: baseExtension.name, description: baseExtension.description, @@ -74,10 +80,12 @@ export function convertBaseExtension(baseExtension: JavaBaseExtension): ExasolEx return findInstallations(metadata.allScripts.rows, baseExtension) }, install(context: Context, version: string): void { - installExtension(context, baseExtension, version) + verifyVersion(version) + installExtension(context, baseExtension) }, uninstall(context: Context, version: string) { - uninstall(context, baseExtension, version) + verifyVersion(version) + uninstall(context, baseExtension) }, upgrade(context: Context) { return upgrade(context, baseExtension) diff --git a/src/base/install.test.ts b/src/base/install.test.ts index 6093658..2e81e7b 100644 --- a/src/base/install.test.ts +++ b/src/base/install.test.ts @@ -81,6 +81,6 @@ describe("install", () => { it("fails for wrong version", () => { expect(() => { install("wrongVersion") }) - .toThrowError(new PreconditionFailedError(`Installing version 'wrongVersion' not supported, try 'v1'.`)) + .toThrowError(new PreconditionFailedError(`Version 'wrongVersion' not supported, can only use 'v1'.`)) }) }) diff --git a/src/base/install.ts b/src/base/install.ts index e2cb384..ca2df37 100644 --- a/src/base/install.ts +++ b/src/base/install.ts @@ -3,12 +3,8 @@ import { Context } from "../context"; import { BadRequestError } from "../error"; -export function installExtension(context: Context, extension: JavaBaseExtension, versionToInstall: string): void { - if (extension.version !== versionToInstall) { - throw new BadRequestError(`Installing version '${versionToInstall}' not supported, try '${extension.version}'.`); - } +export function installExtension(context: Context, extension: JavaBaseExtension): void { const jarPath = context.bucketFs.resolvePath(extension.file.name); - function qualifiedName(script: ScriptDefinition) { return `"${context.extensionSchemaName}"."${script.name}"` } diff --git a/src/base/uninstall.test.ts b/src/base/uninstall.test.ts index 143df2c..72aa630 100644 --- a/src/base/uninstall.test.ts +++ b/src/base/uninstall.test.ts @@ -84,6 +84,6 @@ describe("uninstall", () => { it("fails for wrong version", () => { expect(() => { uninstall("wrongVersion") }) - .toThrowError(new PreconditionFailedError(`Uninstalling version 'wrongVersion' not supported, try 'v1'.`)) + .toThrowError(new PreconditionFailedError(`Version 'wrongVersion' not supported, can only use 'v1'.`)) }) }) diff --git a/src/base/uninstall.ts b/src/base/uninstall.ts index 9b05e82..ee5aebb 100644 --- a/src/base/uninstall.ts +++ b/src/base/uninstall.ts @@ -3,11 +3,7 @@ import { Context } from "../context"; import { NotFoundError } from "../error"; -export function uninstall(context: Context, extension: JavaBaseExtension, versionToUninstall: string): void { - if (extension.version !== versionToUninstall) { - throw new NotFoundError(`Uninstalling version '${versionToUninstall}' not supported, try '${extension.version}'.`) - } - +export function uninstall(context: Context, extension: JavaBaseExtension): void { function extensionSchemaExists(): boolean { const result = context.sqlClient.query("SELECT 1 FROM SYS.EXA_ALL_SCHEMAS WHERE SCHEMA_NAME=?", context.extensionSchemaName) return result.rows.length > 0 diff --git a/src/base/upgrade.ts b/src/base/upgrade.ts index 6ebe50a..ced7f07 100644 --- a/src/base/upgrade.ts +++ b/src/base/upgrade.ts @@ -31,6 +31,6 @@ export function upgrade(context: Context, extension: JavaBaseExtension): Upgrade if (previousVersion.result === newVersion) { throw new PreconditionFailedError(`Extension is already installed in latest version ${newVersion}`) } - installExtension(context, extension, newVersion) + installExtension(context, extension) return { previousVersion: previousVersion.result, newVersion }; } From 031cc9fe5b1d11c2bac64ca9518a56e55ed8405c Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Tue, 31 Oct 2023 12:51:49 +0100 Subject: [PATCH 13/29] resolveOptional resolves missing required parameters --- src/base-vs/parameterResolver.test.ts | 12 ++++++------ src/base-vs/parameterResolver.ts | 3 --- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/base-vs/parameterResolver.test.ts b/src/base-vs/parameterResolver.test.ts index ad2bd05..9dba3e2 100644 --- a/src/base-vs/parameterResolver.test.ts +++ b/src/base-vs/parameterResolver.test.ts @@ -27,14 +27,14 @@ describe("ParameterResolver", () => { }) describe("resolveOptional()", () => { - it("fails for required parameter", () => { - expect(() => testee().resolveOptional({ id: "p1", name: "n1", type: "string", required: true })).toThrow("Parameter p1 is required but not defined") - }) describe("supported parameter types", () => { const tests: { name: string, param: Parameter }[] = [ - { name: "string", param: { id: "p1", name: "n1", type: "string", required: false } }, - { name: "select", param: { id: "p1", name: "n1", type: "select", options: [], required: false } }, - { name: "boolean", param: { id: "p1", name: "n1", type: "boolean", required: false } }, + { name: "optional string", param: { id: "p1", name: "n1", type: "string", required: false } }, + { name: "optional select", param: { id: "p1", name: "n1", type: "select", options: [], required: false } }, + { name: "optional boolean", param: { id: "p1", name: "n1", type: "boolean", required: false } }, + { name: "required string", param: { id: "p1", name: "n1", type: "string", required: true } }, + { name: "required select", param: { id: "p1", name: "n1", type: "select", options: [], required: true } }, + { name: "required boolean", param: { id: "p1", name: "n1", type: "boolean", required: true } }, ] tests.forEach(test => describe(test.name, () => { it("returns undefined for empty parameter values", () => { diff --git a/src/base-vs/parameterResolver.ts b/src/base-vs/parameterResolver.ts index a689ec7..d75af72 100644 --- a/src/base-vs/parameterResolver.ts +++ b/src/base-vs/parameterResolver.ts @@ -19,9 +19,6 @@ function buildValuesMap(values: ParameterValues): Map { export function createParameterResolver(paramValues: ParameterValues): ParameterResolver { const values = buildValuesMap(paramValues) function resolveOptional(paramDef: Parameter): string | undefined { - if (paramDef.required && !values.has(paramDef.id)) { - throw new Error(`Parameter ${paramDef.id} is required but not defined`) - } return values.get(paramDef.id); } From c4d48a7ed5b62aaeab2a849a0658d6ad428afebf Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Tue, 31 Oct 2023 12:52:04 +0100 Subject: [PATCH 14/29] Cleanup queries --- src/base-vs/addInstance.ts | 2 +- src/base-vs/findInstances.test.ts | 2 +- src/base-vs/findInstances.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/base-vs/addInstance.ts b/src/base-vs/addInstance.ts index 22b2cfe..2a10e8b 100644 --- a/src/base-vs/addInstance.ts +++ b/src/base-vs/addInstance.ts @@ -25,7 +25,7 @@ function buildVirtualSchemaStatement(baseExtension: JavaVirtualSchemaBaseExtensi if (def.properties.length > 0) { stmt += " WITH" for (const property of def.properties) { - stmt += `\n ${property.property} = '${escapeSingleQuotes(property.value)}'`; + stmt += ` ${property.property} = '${escapeSingleQuotes(property.value)}'`; } } console.log(`Creating virtual schema with statement ${stmt}`) diff --git a/src/base-vs/findInstances.test.ts b/src/base-vs/findInstances.test.ts index 2272866..995a5a8 100644 --- a/src/base-vs/findInstances.test.ts +++ b/src/base-vs/findInstances.test.ts @@ -29,7 +29,7 @@ describe("findInstances", () => { }) it("executes expected query", () => { findInstances([["vs1"], ["vs2"]]) - const expectedQuery = "SELECT SCHEMA_NAME FROM SYS.EXA_ALL_VIRTUAL_SCHEMAS WHERE ADAPTER_SCRIPT_SCHEMA = ? AND ADAPTER_SCRIPT_NAME = ? ORDER BY SCHEMA_NAME"; + const expectedQuery = "SELECT SCHEMA_NAME FROM SYS.EXA_ALL_VIRTUAL_SCHEMAS WHERE ADAPTER_SCRIPT_SCHEMA = ? AND ADAPTER_SCRIPT_NAME = ? ORDER BY SCHEMA_NAME"; expect(context.mocks.sqlQuery.mock.calls).toStrictEqual([[expectedQuery, "ext-schema", "vs-adapter-script-name"]]) }) }) diff --git a/src/base-vs/findInstances.ts b/src/base-vs/findInstances.ts index 1b5a71b..2b03180 100644 --- a/src/base-vs/findInstances.ts +++ b/src/base-vs/findInstances.ts @@ -4,7 +4,7 @@ import { Context } from "../context"; export function findInstances(context: Context, adapterScript: string): Instance[] { const result = context.sqlClient.query("SELECT SCHEMA_NAME FROM SYS.EXA_ALL_VIRTUAL_SCHEMAS" + " WHERE ADAPTER_SCRIPT_SCHEMA = ? AND ADAPTER_SCRIPT_NAME = ? " - + " ORDER BY SCHEMA_NAME", context.extensionSchemaName, adapterScript) + + "ORDER BY SCHEMA_NAME", context.extensionSchemaName, adapterScript) return result.rows.map(row => { const schemaName = row[0]; return { id: schemaName, name: schemaName } From 110b00cb3bc4e6cbc5ad5be4f89c0713ff1cf863 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Tue, 31 Oct 2023 16:17:03 +0100 Subject: [PATCH 15/29] Added tests for addInstance() --- src/base-vs/addInstance.test.ts | 151 ++++++++++++++++++++++++++ src/base-vs/addInstance.ts | 2 - src/base-vs/builders.ts | 6 +- src/base-vs/parameterResolver.test.ts | 9 ++ src/base-vs/parameterResolver.ts | 6 +- src/base-vs/test-vs-utils.ts | 9 ++ 6 files changed, 177 insertions(+), 6 deletions(-) diff --git a/src/base-vs/addInstance.test.ts b/src/base-vs/addInstance.test.ts index e69de29..6feae5b 100644 --- a/src/base-vs/addInstance.test.ts +++ b/src/base-vs/addInstance.test.ts @@ -0,0 +1,151 @@ + +import { describe, expect, it } from '@jest/globals'; +import { ConnectionParameterDefinition, convertVirtualSchemaBaseExtension, createJsonConnectionDefinition, createUserPasswordConnectionDefinition, createVirtualSchemaBuilder } from '.'; +import { Instance, Parameter, ParameterValue } from '../api'; +import { ContextMock, createMockContext } from '../base/test-utils'; +import { emptyBaseVsExtension, param, vsNameParam } from './test-vs-utils'; + +let context: ContextMock = undefined +const adapterName = "ADAPTER_SCRIPT" +function addInstance(paramValues: ParameterValue[], version: string = "v0", virtualSchemaParameterDefs: Parameter[] = [], connectionParameterDefs: Parameter[] = []): Instance { + return addInstanceWithConnectionDef(paramValues, version, virtualSchemaParameterDefs, createJsonConnectionDefinition(connectionParameterDefs)) +} +function addInstanceWithUserPasswordConnection(paramValues: ParameterValue[], version: string = "v0", virtualSchemaParameterDefs: Parameter[] = [], addressParam: Parameter = undefined, userParam: Parameter = undefined, passwordParam: Parameter = undefined): Instance { + return addInstanceWithConnectionDef(paramValues, version, virtualSchemaParameterDefs, createUserPasswordConnectionDefinition(addressParam, userParam, passwordParam)) +} +function addInstanceWithConnectionDef(paramValues: ParameterValue[], version: string = "v0", virtualSchemaParameterDefs: Parameter[] = [], connectionDefinition: ConnectionParameterDefinition): Instance { + const baseExtension = emptyBaseVsExtension() + baseExtension.name = "testing-extension" + baseExtension.builder = createVirtualSchemaBuilder({ + adapterName, connectionNameProperty: "CONNECTION_NAME", + virtualSchemaParameters: virtualSchemaParameterDefs, + connectionDefinition + }) + context = createMockContext(); + const installations = convertVirtualSchemaBaseExtension(baseExtension).addInstance(context, version, { values: paramValues }) + expect(installations).toBeDefined() + return installations +} + +function getStatement(index: number): string { + const sqlStatements = context.mocks.sqlExecute.mock.calls + expect(sqlStatements).toHaveLength(4) + expect(sqlStatements[index]).toHaveLength(1) + const stmt = sqlStatements[index][0]; + expect(stmt).toBeDefined() + return stmt +} + +function getCreateConnectionStatement(): string { + return getStatement(0) +} + +function getCreateVirtualSchemaStatement(): string { + return getStatement(1) +} + +describe("addInstance()", () => { + it("fails for wrong version", () => { + expect(() => addInstance([], "wrongVersion")).toThrow("Version 'wrongVersion' not supported, can only use 'v0'.") + }) + it("returns new instance", () => { + expect(addInstance([vsNameParam("vs1")])).toStrictEqual({ id: "vs1", name: "vs1" }) + }) + it("executes statements", () => { + addInstance([vsNameParam("vs1")]) + expect(getStatement(0)).toBe(`CREATE OR REPLACE CONNECTION "vs1_CONNECTION" TO '' IDENTIFIED BY '{}'`) + expect(getStatement(1)).toBe(`CREATE VIRTUAL SCHEMA "vs1" USING "ext-schema"."ADAPTER_SCRIPT" WITH CONNECTION_NAME = 'vs1_CONNECTION'`) + expect(getStatement(2)).toBe(`COMMENT ON CONNECTION "vs1_CONNECTION" IS 'Created by Extension Manager for testing-extension vv0 vs1'`) + expect(getStatement(3)).toBe(`COMMENT ON SCHEMA "vs1" IS 'Created by Extension Manager for testing-extension vv0 vs1'`) + }) + + describe("creates virtual schema", () => { + const tests: { name: string, vsParamDefs: Parameter[], params: ParameterValue[], expected: string }[] = [ + { name: "no param", vsParamDefs: [], params: [], expected: "" }, + { name: "param def without value", vsParamDefs: [{ id: "p1", name: "P1", type: "string" }], params: [], expected: "" }, + { name: "param def with value", vsParamDefs: [{ id: "p1", name: "P1", type: "string" }], params: [param("p1", "v1")], expected: " p1 = 'v1'" }, + { + name: "multiple values", vsParamDefs: [{ id: "p1", name: "P1", type: "string" }, { id: "p2", name: "P2", type: "string" }], + params: [param("p1", "v1"), param("p2", "v2")], expected: " p1 = 'v1' p2 = 'v2'" + }, + { name: "single quotes escaped", vsParamDefs: [{ id: "p1", name: "P1", type: "string" }], params: [param("p1", "va'lue")], expected: " p1 = 'va''lue'" }, + { name: "double quotes escaped", vsParamDefs: [{ id: "p1", name: "P1", type: "string" }], params: [param("p1", "va\"lue")], expected: " p1 = 'va\"lue'" }, + ] + tests.forEach(test => it(test.name, () => { + addInstance([vsNameParam("vs1"), ...test.params], "v0", test.vsParamDefs, []) + expect(getCreateVirtualSchemaStatement()).toBe(`CREATE VIRTUAL SCHEMA "vs1" USING "ext-schema"."ADAPTER_SCRIPT" ` + + `WITH CONNECTION_NAME = 'vs1_CONNECTION'${test.expected}`) + })) + }) + + describe("creates connection", () => { + describe("with JSON payload", () => { + const tests: { name: string, connParamDefs: Parameter[], params: ParameterValue[], expected: string }[] = [ + { name: "no param", connParamDefs: [], params: [], expected: `{}` }, + { name: "ignores unknown params", connParamDefs: [], params: [param("unknown", "val")], expected: `{}` }, + { name: "param with default value", connParamDefs: [{ id: "p1", name: "P1", type: "string", default: "def" }], params: [], expected: `{"p1":"def"}` }, + { name: "optional string param", connParamDefs: [{ id: "p1", name: "P1", type: "string", required: false }], params: [param("p1", "v1")], expected: `{"p1":"v1"}` }, + { name: "required string param", connParamDefs: [{ id: "p1", name: "P1", type: "string", required: true }], params: [param("p1", "v1")], expected: `{"p1":"v1"}` }, + { name: "escapes single quotes", connParamDefs: [{ id: "p1", name: "P1", type: "string" }], params: [param("p1", "v'1")], expected: `{"p1":"v''1"}` }, + { name: "escapes double quotes", connParamDefs: [{ id: "p1", name: "P1", type: "string" }], params: [param("p1", "v\"1")], expected: `{"p1":"v\\"1"}` }, + { name: "outputs boolean as string", connParamDefs: [{ id: "p1", name: "P1", type: "boolean" }], params: [param("p1", "true")], expected: `{"p1":"true"}` }, + { name: "outputs select as string", connParamDefs: [{ id: "p1", name: "P1", type: "select", options: [] }], params: [param("p1", "v1")], expected: `{"p1":"v1"}` }, + { name: "multiple parameter definitions, value missing", connParamDefs: [{ id: "p1", name: "P1", type: "string" }, { id: "p2", name: "P2", type: "string" }], params: [param("p1", "v1")], expected: `{"p1":"v1"}` }, + { name: "multiple parameter definitions", connParamDefs: [{ id: "p1", name: "P1", type: "string" }, { id: "p2", name: "P2", type: "string" }], params: [param("p1", "v1"), param("p2", "v2")], expected: `{"p1":"v1","p2":"v2"}` }, + ] + tests.forEach(test => it(test.name, () => { + addInstance([vsNameParam("vs1"), ...test.params], "v0", [], test.connParamDefs) + expect(getCreateConnectionStatement()).toBe(`CREATE OR REPLACE CONNECTION "vs1_CONNECTION" TO '' IDENTIFIED BY '${test.expected}'`) + })) + }) + + describe("with user & password", () => { + interface Test { name: string, connAddr?: Parameter, connUser?: Parameter, connPassword?: Parameter, params: ParameterValue[], expected: string } + function addressValue(testName: string, paramValue: string, expectedAddress: string): Test { + const params = [] + if (paramValue) { + params.push(param("p1", paramValue)) + } + return { name: testName, connAddr: { id: "p1", type: "string", name: "P1" }, params, expected: expectedAddress } + } + function userValue(testName: string, paramValue: string, expectedUser: string): Test { + const params = [] + if (paramValue) { + params.push(param("p1", paramValue)) + } + return { name: testName, connUser: { id: "p1", type: "string", name: "P1" }, params, expected: `TO ''${expectedUser}` } + } + function passwordValue(testName: string, paramValue: string, expectedPassword: string): Test { + const params = [] + if (paramValue) { + params.push(param("p1", paramValue)) + } + return { name: testName, connPassword: { id: "p1", type: "string", name: "P1" }, params, expected: `TO ''${expectedPassword}` } + } + + const tests: Test[] = [ + { name: "no param", params: [], expected: `TO ''` }, + { + name: "all params", connAddr: { id: "p1", type: "string", name: "P1" }, connUser: { id: "p2", type: "string", name: "P2" }, connPassword: { id: "p3", type: "string", name: "P3" }, + params: [param("p1", "addr"), param("p2", "user"), param("p3", "pass")], expected: `TO 'addr' USER 'user' IDENTIFIED BY 'pass'` + }, + addressValue("address value not present", undefined, "TO ''"), + addressValue("address value present", "addr", "TO 'addr'"), + addressValue("address value with single quote escaped", "ad'dr", "TO 'ad''dr'"), + addressValue("address value with double quote escaped", "ad\"dr", "TO 'ad\"dr'"), + userValue("user value not present", undefined, ""), + userValue("user value present", "user", " USER 'user'"), + userValue("user value with single quote escaped", "us'er", " USER 'us''er'"), + userValue("user value with double quote escaped", "us\"er", " USER 'us\"er'"), + passwordValue("password value not present", undefined, ""), + passwordValue("password value present", "pass", " IDENTIFIED BY 'pass'"), + passwordValue("password value with single quote escaped", "pa'ss", " IDENTIFIED BY 'pa''ss'"), + passwordValue("password value with double quote escaped", "pa\"ss", " IDENTIFIED BY 'pa\"ss'"), + ] + tests.forEach(test => it(test.name, () => { + addInstanceWithUserPasswordConnection([vsNameParam("vs1"), ...test.params], "v0", [], test.connAddr, test.connUser, test.connPassword) + expect(getCreateConnectionStatement()).toBe(`CREATE OR REPLACE CONNECTION "vs1_CONNECTION" ${test.expected}`) + })) + }) + }) +}) diff --git a/src/base-vs/addInstance.ts b/src/base-vs/addInstance.ts index 2a10e8b..2bebc3b 100644 --- a/src/base-vs/addInstance.ts +++ b/src/base-vs/addInstance.ts @@ -5,7 +5,6 @@ import { convertSchemaNameToInstanceId, escapeSingleQuotes, getConnectionName } import { ParameterResolver } from "./parameterResolver"; import { PARAM_VIRTUAL_SCHEMA_NAME } from "./parameters"; - export function addInstance(context: Context, baseExtension: JavaVirtualSchemaBaseExtension, parameterResolver: ParameterResolver): Instance { const virtualSchemaName = parameterResolver.resolve(PARAM_VIRTUAL_SCHEMA_NAME) const connectionName = getConnectionName(virtualSchemaName) @@ -28,7 +27,6 @@ function buildVirtualSchemaStatement(baseExtension: JavaVirtualSchemaBaseExtensi stmt += ` ${property.property} = '${escapeSingleQuotes(property.value)}'`; } } - console.log(`Creating virtual schema with statement ${stmt}`) return stmt; } diff --git a/src/base-vs/builders.ts b/src/base-vs/builders.ts index afa53df..2a8e8ca 100644 --- a/src/base-vs/builders.ts +++ b/src/base-vs/builders.ts @@ -81,9 +81,9 @@ export function createJsonConnectionDefinition(parameters: Parameter[]): Connect export function createUserPasswordConnectionDefinition(addressParam: Parameter, userParam: Parameter, passwordParam: Parameter): ConnectionParameterDefinition { function builder(parameterResolver: ParameterResolver): ConnectionDefinition { return { - connectionTo: parameterResolver.resolve(addressParam), - user: parameterResolver.resolve(userParam), - identifiedBy: parameterResolver.resolve(passwordParam) + connectionTo: addressParam ? parameterResolver.resolveOptional(addressParam) : undefined, + user: userParam ? parameterResolver.resolveOptional(userParam) : undefined, + identifiedBy: passwordParam ? parameterResolver.resolveOptional(passwordParam) : undefined } } return { parameters: [addressParam, userParam, passwordParam], builder } diff --git a/src/base-vs/parameterResolver.test.ts b/src/base-vs/parameterResolver.test.ts index 9dba3e2..3b28fc9 100644 --- a/src/base-vs/parameterResolver.test.ts +++ b/src/base-vs/parameterResolver.test.ts @@ -52,6 +52,12 @@ describe("ParameterResolver", () => { it("returns value for multiple parameter", () => { expect(testee(param("wrong-name", "value"), param(test.param.id, "value")).resolveOptional(test.param)).toEqual("value") }) + it("returns undefined for missing default", () => { + expect(testee().resolveOptional({ ...test.param, default: undefined })).toBeUndefined() + }) + it("returns undefined for missing default", () => { + expect(testee().resolveOptional({ default: "def", ...test.param })).toEqual("def") + }) })) }) }) @@ -60,6 +66,9 @@ describe("ParameterResolver", () => { it("fails for optional parameter", () => { expect(() => testee().resolve({ id: "p1", name: "n1", type: "string", required: false })).toThrow("Parameter p1 is optional. Use method 'resolveOptional()' to resolve it.") }) + it("fails for optional parameter with default", () => { + expect(() => testee().resolve({ id: "p1", name: "n1", type: "string", required: false, default: "def" })).toThrow("Parameter p1 is optional. Use method 'resolveOptional()' to resolve it.") + }) describe("supported parameter types", () => { const tests: { name: string, param: Parameter }[] = [ { name: "string", param: { id: "p1", name: "n1", type: "string", required: true } }, diff --git a/src/base-vs/parameterResolver.ts b/src/base-vs/parameterResolver.ts index d75af72..6dca9f1 100644 --- a/src/base-vs/parameterResolver.ts +++ b/src/base-vs/parameterResolver.ts @@ -19,7 +19,11 @@ function buildValuesMap(values: ParameterValues): Map { export function createParameterResolver(paramValues: ParameterValues): ParameterResolver { const values = buildValuesMap(paramValues) function resolveOptional(paramDef: Parameter): string | undefined { - return values.get(paramDef.id); + const value = values.get(paramDef.id); + if (!value) { + return paramDef.default + } + return value; } function resolve(paramDef: Parameter): string { diff --git a/src/base-vs/test-vs-utils.ts b/src/base-vs/test-vs-utils.ts index 3534e93..d027798 100644 --- a/src/base-vs/test-vs-utils.ts +++ b/src/base-vs/test-vs-utils.ts @@ -1,4 +1,5 @@ import { JavaVirtualSchemaBaseExtension, createJsonConnectionDefinition, createVirtualSchemaBuilder } from "."; +import { ParameterValue } from "../api"; import { successResult } from "../base/common"; export function emptyBaseVsExtension(): JavaVirtualSchemaBaseExtension { @@ -26,3 +27,11 @@ export function emptyBaseVsExtension(): JavaVirtualSchemaBaseExtension { }) } } + +export function param(name: string, value: string): ParameterValue { + return { name, value } +} + +export function vsNameParam(vsName: string): ParameterValue { + return param("base-vs.virtual-schema-name", vsName) +} From b84c5460dab70d8c3a1d1c6d9841915064f6d95f Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Tue, 31 Oct 2023 16:38:59 +0100 Subject: [PATCH 16/29] Fix linter warnings --- src/base/install.ts | 2 -- src/base/uninstall.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/base/install.ts b/src/base/install.ts index ca2df37..84af339 100644 --- a/src/base/install.ts +++ b/src/base/install.ts @@ -1,7 +1,5 @@ import { JavaBaseExtension, ScriptDefinition } from "."; import { Context } from "../context"; -import { BadRequestError } from "../error"; - export function installExtension(context: Context, extension: JavaBaseExtension): void { const jarPath = context.bucketFs.resolvePath(extension.file.name); diff --git a/src/base/uninstall.ts b/src/base/uninstall.ts index ee5aebb..2020cdf 100644 --- a/src/base/uninstall.ts +++ b/src/base/uninstall.ts @@ -1,7 +1,5 @@ import { JavaBaseExtension, ScriptDefinition } from "."; import { Context } from "../context"; -import { NotFoundError } from "../error"; - export function uninstall(context: Context, extension: JavaBaseExtension): void { function extensionSchemaExists(): boolean { From 26f95c60c417e09833c61b37939d489af14b1351 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Thu, 2 Nov 2023 07:20:44 +0100 Subject: [PATCH 17/29] Increment minor version and upgrade dependencies --- .github/workflows/project-keeper-verify.yml | 15 +-- .github/workflows/project-keeper.sh | 2 +- doc/changes/changelog.md | 2 +- doc/changes/changes_0.3.2.md | 22 ---- doc/changes/changes_0.4.0.md | 22 ++++ package-lock.json | 131 +++++++++++--------- package.json | 8 +- 7 files changed, 101 insertions(+), 101 deletions(-) delete mode 100644 doc/changes/changes_0.3.2.md create mode 100644 doc/changes/changes_0.4.0.md diff --git a/.github/workflows/project-keeper-verify.yml b/.github/workflows/project-keeper-verify.yml index e7ffe78..abaf33a 100644 --- a/.github/workflows/project-keeper-verify.yml +++ b/.github/workflows/project-keeper-verify.yml @@ -28,6 +28,10 @@ jobs: uses: actions/setup-go@v4 with: go-version: "1.20" + cache-dependency-path: | + go.sum + .github/workflows/project-keeper-verify.yml + .github/workflows/project-keeper.sh - name: Set up NPM uses: actions/setup-node@v3 @@ -44,16 +48,5 @@ jobs: restore-keys: | ${{ runner.os }}-pk- - - name: Cache go-licenses - uses: actions/cache@v3 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-licenses-${{ hashFiles('.github/workflows/project-keeper-verify.yml') }} - restore-keys: | - ${{ runner.os }}-go-licenses-${{ hashFiles('.github/workflows/project-keeper-verify.yml') }} - ${{ runner.os }}-go-licenses- - - name: Project Keeper Verify run: ./.github/workflows/project-keeper.sh diff --git a/.github/workflows/project-keeper.sh b/.github/workflows/project-keeper.sh index e75b381..af9eeec 100755 --- a/.github/workflows/project-keeper.sh +++ b/.github/workflows/project-keeper.sh @@ -5,7 +5,7 @@ set -o nounset set -o pipefail readonly pk_mode="${1-verify}"; -readonly version="2.9.12" +readonly version="2.9.15" readonly pk_jar="$HOME/.m2/repository/com/exasol/project-keeper-cli/$version/project-keeper-cli-$version.jar" diff --git a/doc/changes/changelog.md b/doc/changes/changelog.md index ff7b250..1950025 100644 --- a/doc/changes/changelog.md +++ b/doc/changes/changelog.md @@ -1,6 +1,6 @@ # Changes -* [0.3.2](changes_0.3.2.md) +* [0.4.0](changes_0.4.0.md) * [0.3.1](changes_0.3.1.md) * [0.3.0](changes_0.3.0.md) * [0.2.0](changes_0.2.0.md) diff --git a/doc/changes/changes_0.3.2.md b/doc/changes/changes_0.3.2.md deleted file mode 100644 index 1365e12..0000000 --- a/doc/changes/changes_0.3.2.md +++ /dev/null @@ -1,22 +0,0 @@ -# Extension Manager Interface 0.3.2, released 2023-??-?? - -Code name: Simplify Virtual Schema Extensions - -## Summary - -This release simplifies creating extension definitions for virtual schemas. - -## Features - -* #49: Extracted common code for virtual schema extensions - -## Dependency Updates - -### Development Dependency Updates - -* Updated `eslint:^8.47.0` to `^8.51.0` -* Updated `@jest/globals:^29.6.3` to `^29.7.0` -* Updated `@typescript-eslint/parser:^6.4.1` to `^6.8.0` -* Updated `typescript:5.1.6` to `5.2.2` -* Updated `@typescript-eslint/eslint-plugin:^6.4.1` to `^6.8.0` -* Updated `jest:^29.6.3` to `^29.7.0` diff --git a/doc/changes/changes_0.4.0.md b/doc/changes/changes_0.4.0.md new file mode 100644 index 0000000..db6620e --- /dev/null +++ b/doc/changes/changes_0.4.0.md @@ -0,0 +1,22 @@ +# Extension Manager Interface 0.4.0, released 2023-??-?? + +Code name: Simplify Virtual Schema Extensions + +## Summary + +This release simplifies creating extension definitions for Java-based virtual schemas. To use it, simply create your `ExasolExtension` by calling `convertVirtualSchemaBaseExtension(baseExtension: JavaVirtualSchemaBaseExtension)`. This works similar to `convertBaseExtension(baseExtension: JavaBaseExtension)` for Java-based script extensions. No need to implement all extension methods yourself, just configure required `SCRIPT`s, `CONNECTION` parameters and `VIRTUAL SCHEMA` properties. + +## Features + +* #49: Extracted common code for virtual schema extensions + +## Dependency Updates + +### Development Dependency Updates + +* Updated `eslint:^8.47.0` to `^8.52.0` +* Updated `@jest/globals:^29.6.3` to `^29.7.0` +* Updated `@typescript-eslint/parser:^6.4.1` to `^6.9.1` +* Updated `typescript:5.1.6` to `5.2.2` +* Updated `@typescript-eslint/eslint-plugin:^6.4.1` to `^6.9.1` +* Updated `jest:^29.6.3` to `^29.7.0` diff --git a/package-lock.json b/package-lock.json index 6dffa18..6c433f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "@exasol/extension-manager-interface", - "version": "0.3.2", + "version": "0.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@exasol/extension-manager-interface", - "version": "0.3.2", + "version": "0.4.0", "license": "MIT", "devDependencies": { "@jest/globals": "^29.7.0", - "@typescript-eslint/eslint-plugin": "^6.8.0", - "@typescript-eslint/parser": "^6.8.0", - "eslint": "^8.51.0", + "@typescript-eslint/eslint-plugin": "^6.9.1", + "@typescript-eslint/parser": "^6.9.1", + "eslint": "^8.52.0", "jest": "^29.7.0", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", @@ -766,21 +766,21 @@ } }, "node_modules/@eslint/js": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", - "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", + "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.12", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.12.tgz", - "integrity": "sha512-NlGesA1usRNn6ctHCZ21M4/dKPgW9Nn1FypRdIKKgZOKzkVV4T1FlK5mBiLhHBCDmEbdQG0idrcXlbZfksJ+RA==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.0", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" }, @@ -802,9 +802,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.0.tgz", - "integrity": "sha512-9S9QrXY2K0L4AGDcSgTi9vgiCcG8VcBv4Mp7/1hDPYoswIy6Z6KO5blYto82BT8M0MZNRWmCFLpCs3HlpYGGdw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "node_modules/@istanbuljs/load-nyc-config": { @@ -1438,16 +1438,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.8.0.tgz", - "integrity": "sha512-GosF4238Tkes2SHPQ1i8f6rMtG6zlKwMEB0abqSJ3Npvos+doIlc/ATG+vX1G9coDF3Ex78zM3heXHLyWEwLUw==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.9.1.tgz", + "integrity": "sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.8.0", - "@typescript-eslint/type-utils": "6.8.0", - "@typescript-eslint/utils": "6.8.0", - "@typescript-eslint/visitor-keys": "6.8.0", + "@typescript-eslint/scope-manager": "6.9.1", + "@typescript-eslint/type-utils": "6.9.1", + "@typescript-eslint/utils": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -1473,15 +1473,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.8.0.tgz", - "integrity": "sha512-5tNs6Bw0j6BdWuP8Fx+VH4G9fEPDxnVI7yH1IAPkQH5RUtvKwRoqdecAPdQXv4rSOADAaz1LFBZvZG7VbXivSg==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.9.1.tgz", + "integrity": "sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.8.0", - "@typescript-eslint/types": "6.8.0", - "@typescript-eslint/typescript-estree": "6.8.0", - "@typescript-eslint/visitor-keys": "6.8.0", + "@typescript-eslint/scope-manager": "6.9.1", + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/typescript-estree": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1", "debug": "^4.3.4" }, "engines": { @@ -1501,13 +1501,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.8.0.tgz", - "integrity": "sha512-xe0HNBVwCph7rak+ZHcFD6A+q50SMsFwcmfdjs9Kz4qDh5hWhaPhFjRs/SODEhroBI5Ruyvyz9LfwUJ624O40g==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.9.1.tgz", + "integrity": "sha512-38IxvKB6NAne3g/+MyXMs2Cda/Sz+CEpmm+KLGEM8hx/CvnSRuw51i8ukfwB/B/sESdeTGet1NH1Wj7I0YXswg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.8.0", - "@typescript-eslint/visitor-keys": "6.8.0" + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1518,13 +1518,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.8.0.tgz", - "integrity": "sha512-RYOJdlkTJIXW7GSldUIHqc/Hkto8E+fZN96dMIFhuTJcQwdRoGN2rEWA8U6oXbLo0qufH7NPElUb+MceHtz54g==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.9.1.tgz", + "integrity": "sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.8.0", - "@typescript-eslint/utils": "6.8.0", + "@typescript-eslint/typescript-estree": "6.9.1", + "@typescript-eslint/utils": "6.9.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -1545,9 +1545,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.8.0.tgz", - "integrity": "sha512-p5qOxSum7W3k+llc7owEStXlGmSl8FcGvhYt8Vjy7FqEnmkCVlM3P57XQEGj58oqaBWDQXbJDZxwUWMS/EAPNQ==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.9.1.tgz", + "integrity": "sha512-BUGslGOb14zUHOUmDB2FfT6SI1CcZEJYfF3qFwBeUrU6srJfzANonwRYHDpLBuzbq3HaoF2XL2hcr01c8f8OaQ==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1558,13 +1558,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.8.0.tgz", - "integrity": "sha512-ISgV0lQ8XgW+mvv5My/+iTUdRmGspducmQcDw5JxznasXNnZn3SKNrTRuMsEXv+V/O+Lw9AGcQCfVaOPCAk/Zg==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.1.tgz", + "integrity": "sha512-U+mUylTHfcqeO7mLWVQ5W/tMLXqVpRv61wm9ZtfE5egz7gtnmqVIw9ryh0mgIlkKk9rZLY3UHygsBSdB9/ftyw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.8.0", - "@typescript-eslint/visitor-keys": "6.8.0", + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1585,17 +1585,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.8.0.tgz", - "integrity": "sha512-dKs1itdE2qFG4jr0dlYLQVppqTE+Itt7GmIf/vX6CSvsW+3ov8PbWauVKyyfNngokhIO9sKZeRGCUo1+N7U98Q==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.9.1.tgz", + "integrity": "sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.8.0", - "@typescript-eslint/types": "6.8.0", - "@typescript-eslint/typescript-estree": "6.8.0", + "@typescript-eslint/scope-manager": "6.9.1", + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/typescript-estree": "6.9.1", "semver": "^7.5.4" }, "engines": { @@ -1610,12 +1610,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.8.0.tgz", - "integrity": "sha512-oqAnbA7c+pgOhW2OhGvxm0t1BULX5peQI/rLsNDpGM78EebV3C9IGbX5HNZabuZ6UQrYveCLjKo8Iy/lLlBkkg==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.1.tgz", + "integrity": "sha512-MUaPUe/QRLEffARsmNfmpghuQkW436DvESW+h+M52w0coICHRfD6Np9/K6PdACwnrq1HmuLl+cSPZaJmeVPkSw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.8.0", + "@typescript-eslint/types": "6.9.1", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -1626,6 +1626,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -2297,18 +2303,19 @@ } }, "node_modules/eslint": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", - "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", + "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.51.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/js": "8.52.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", diff --git a/package.json b/package.json index 9bf5b38..608e63a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@exasol/extension-manager-interface", - "version": "0.3.2", + "version": "0.4.0", "main": "dist/api.js", "types": "dist/api.d.ts", "description": "Interface for extensions for the Exasol extension manager.", @@ -14,9 +14,9 @@ }, "devDependencies": { "@jest/globals": "^29.7.0", - "@typescript-eslint/eslint-plugin": "^6.8.0", - "@typescript-eslint/parser": "^6.8.0", - "eslint": "^8.51.0", + "@typescript-eslint/eslint-plugin": "^6.9.1", + "@typescript-eslint/parser": "^6.9.1", + "eslint": "^8.52.0", "jest": "^29.7.0", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", From 7fedd039bf9b20c1b3d038da45bfcb75862413ad Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Thu, 2 Nov 2023 07:44:00 +0100 Subject: [PATCH 18/29] Remove unused methods --- src/base/adapterScript.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/base/adapterScript.ts b/src/base/adapterScript.ts index 8b7651b..5be57e5 100644 --- a/src/base/adapterScript.ts +++ b/src/base/adapterScript.ts @@ -14,13 +14,4 @@ export class AdapterScript { get name() { return this.script.name } - get qualifiedName() { - return `${this.script.schema}.${this.script.name}` - } - get schema() { - return this.script.schema - } - get text() { - return this.script.text - } } From a25ab0b77301e127e84f7150142fe8ae9b2e4b67 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Thu, 2 Nov 2023 07:44:42 +0100 Subject: [PATCH 19/29] Update API version --- src/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api.ts b/src/api.ts index 922c9b5..327db10 100644 --- a/src/api.ts +++ b/src/api.ts @@ -5,7 +5,7 @@ import { BadRequestError, NotFoundError, PreconditionFailedError } from "./error import { ExaMetadata } from "./exasolSchema"; import { Parameter } from "./parameters"; -export const CURRENT_API_VERSION = "0.3.2"; +export const CURRENT_API_VERSION = "0.4.0"; /** * This class represents an extension that can be installed and managed with the extension-manager. From b905c2cc522edd666395f27208c1d54d0f14711a Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Thu, 2 Nov 2023 07:44:50 +0100 Subject: [PATCH 20/29] Simplify unit test --- src/base-vs/addInstance.test.ts | 35 ++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/base-vs/addInstance.test.ts b/src/base-vs/addInstance.test.ts index 6feae5b..3b9057d 100644 --- a/src/base-vs/addInstance.test.ts +++ b/src/base-vs/addInstance.test.ts @@ -101,26 +101,37 @@ describe("addInstance()", () => { describe("with user & password", () => { interface Test { name: string, connAddr?: Parameter, connUser?: Parameter, connPassword?: Parameter, params: ParameterValue[], expected: string } - function addressValue(testName: string, paramValue: string, expectedAddress: string): Test { + + + function testValue(testName: string, type: "addr" | "user" | "password", paramValue: string, expected: string): Test { const params = [] if (paramValue) { params.push(param("p1", paramValue)) } - return { name: testName, connAddr: { id: "p1", type: "string", name: "P1" }, params, expected: expectedAddress } + const paramTemplate: Parameter = { id: "p1", type: "string", name: "P1" } + let connectionParam = undefined + switch (type) { + case "addr": + connectionParam = { connAddr: paramTemplate }; + break + case "user": + connectionParam = { connUser: paramTemplate }; + break + case "password": + connectionParam = { connPassword: paramTemplate }; + break + } + return { name: testName, ...connectionParam, params, expected: expected } + } + + function addressValue(testName: string, paramValue: string, expectedAddress: string): Test { + return testValue(testName, "addr", paramValue, expectedAddress) } function userValue(testName: string, paramValue: string, expectedUser: string): Test { - const params = [] - if (paramValue) { - params.push(param("p1", paramValue)) - } - return { name: testName, connUser: { id: "p1", type: "string", name: "P1" }, params, expected: `TO ''${expectedUser}` } + return testValue(testName, "user", paramValue, `TO ''${expectedUser}`) } function passwordValue(testName: string, paramValue: string, expectedPassword: string): Test { - const params = [] - if (paramValue) { - params.push(param("p1", paramValue)) - } - return { name: testName, connPassword: { id: "p1", type: "string", name: "P1" }, params, expected: `TO ''${expectedPassword}` } + return testValue(testName, "password", paramValue, `TO ''${expectedPassword}`) } const tests: Test[] = [ From 23027fe0f351dd60cfaf78a8b9457219270aa8a4 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Thu, 2 Nov 2023 07:48:30 +0100 Subject: [PATCH 21/29] Fix compiler warnings --- src/base-vs/addInstance.test.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/base-vs/addInstance.test.ts b/src/base-vs/addInstance.test.ts index 3b9057d..a391bfe 100644 --- a/src/base-vs/addInstance.test.ts +++ b/src/base-vs/addInstance.test.ts @@ -7,13 +7,13 @@ import { emptyBaseVsExtension, param, vsNameParam } from './test-vs-utils'; let context: ContextMock = undefined const adapterName = "ADAPTER_SCRIPT" -function addInstance(paramValues: ParameterValue[], version: string = "v0", virtualSchemaParameterDefs: Parameter[] = [], connectionParameterDefs: Parameter[] = []): Instance { +function addInstance(paramValues: ParameterValue[], version: string, virtualSchemaParameterDefs: Parameter[], connectionParameterDefs: Parameter[]): Instance { return addInstanceWithConnectionDef(paramValues, version, virtualSchemaParameterDefs, createJsonConnectionDefinition(connectionParameterDefs)) } -function addInstanceWithUserPasswordConnection(paramValues: ParameterValue[], version: string = "v0", virtualSchemaParameterDefs: Parameter[] = [], addressParam: Parameter = undefined, userParam: Parameter = undefined, passwordParam: Parameter = undefined): Instance { +function addInstanceWithUserPasswordConnection(paramValues: ParameterValue[], version: string, virtualSchemaParameterDefs: Parameter[], addressParam: Parameter = undefined, userParam: Parameter = undefined, passwordParam: Parameter = undefined): Instance { return addInstanceWithConnectionDef(paramValues, version, virtualSchemaParameterDefs, createUserPasswordConnectionDefinition(addressParam, userParam, passwordParam)) } -function addInstanceWithConnectionDef(paramValues: ParameterValue[], version: string = "v0", virtualSchemaParameterDefs: Parameter[] = [], connectionDefinition: ConnectionParameterDefinition): Instance { +function addInstanceWithConnectionDef(paramValues: ParameterValue[], version: string, virtualSchemaParameterDefs: Parameter[], connectionDefinition: ConnectionParameterDefinition): Instance { const baseExtension = emptyBaseVsExtension() baseExtension.name = "testing-extension" baseExtension.builder = createVirtualSchemaBuilder({ @@ -46,13 +46,13 @@ function getCreateVirtualSchemaStatement(): string { describe("addInstance()", () => { it("fails for wrong version", () => { - expect(() => addInstance([], "wrongVersion")).toThrow("Version 'wrongVersion' not supported, can only use 'v0'.") + expect(() => addInstance([], "wrongVersion", [], [])).toThrow("Version 'wrongVersion' not supported, can only use 'v0'.") }) it("returns new instance", () => { - expect(addInstance([vsNameParam("vs1")])).toStrictEqual({ id: "vs1", name: "vs1" }) + expect(addInstance([vsNameParam("vs1")], "v0", [], [])).toStrictEqual({ id: "vs1", name: "vs1" }) }) it("executes statements", () => { - addInstance([vsNameParam("vs1")]) + addInstance([vsNameParam("vs1")], "v0", [], []) expect(getStatement(0)).toBe(`CREATE OR REPLACE CONNECTION "vs1_CONNECTION" TO '' IDENTIFIED BY '{}'`) expect(getStatement(1)).toBe(`CREATE VIRTUAL SCHEMA "vs1" USING "ext-schema"."ADAPTER_SCRIPT" WITH CONNECTION_NAME = 'vs1_CONNECTION'`) expect(getStatement(2)).toBe(`COMMENT ON CONNECTION "vs1_CONNECTION" IS 'Created by Extension Manager for testing-extension vv0 vs1'`) @@ -102,7 +102,6 @@ describe("addInstance()", () => { describe("with user & password", () => { interface Test { name: string, connAddr?: Parameter, connUser?: Parameter, connPassword?: Parameter, params: ParameterValue[], expected: string } - function testValue(testName: string, type: "addr" | "user" | "password", paramValue: string, expected: string): Test { const params = [] if (paramValue) { From b3f84c19d88bd93960a66e93e94b8f8f46ae30d1 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Thu, 2 Nov 2023 08:07:55 +0100 Subject: [PATCH 22/29] Add unit tests --- src/base-vs/common.test.ts | 31 ++++++++++++++++++++++++ src/base-vs/deleteInstance.test.ts | 39 ++++++++++++++++++++++++++++++ src/base/test-utils.ts | 4 +-- 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 src/base-vs/common.test.ts create mode 100644 src/base-vs/deleteInstance.test.ts diff --git a/src/base-vs/common.test.ts b/src/base-vs/common.test.ts new file mode 100644 index 0000000..369fa8f --- /dev/null +++ b/src/base-vs/common.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from '@jest/globals'; +import { convertInstanceIdToSchemaName, convertSchemaNameToInstanceId, escapeSingleQuotes, getConnectionName } from './common'; + +describe("common", () => { + describe("convertInstanceIdToSchemaName()", () => { + it("returns same value", () => { + expect(convertInstanceIdToSchemaName("My Value")).toBe("My Value") + }) + }) + describe("convertSchemaNameToInstanceId()", () => { + it("returns same value", () => { + expect(convertSchemaNameToInstanceId("My Value")).toBe("My Value") + }) + }) + describe("getConnectionName()", () => { + it("appends _CONNECTION", () => { + expect(getConnectionName("My Value")).toBe("My Value_CONNECTION") + }) + }) + describe("escapeSingleQuotes()", () => { + const tests: { name: string, input: string, expectedResult: string }[] = [ + { name: "no single quote", input: "My Value", expectedResult: "My Value" }, + { name: "single quote", input: "My'Value", expectedResult: "My''Value" }, + { name: "two single quote", input: "My''Value", expectedResult: "My''''Value" }, + { name: "quoted string", input: "My 'Value'", expectedResult: "My ''Value''" }, + ] + tests.forEach(test => it(test.name, () => { + expect(escapeSingleQuotes(test.input)).toBe(test.expectedResult) + })) + }) +}) diff --git a/src/base-vs/deleteInstance.test.ts b/src/base-vs/deleteInstance.test.ts new file mode 100644 index 0000000..15c1843 --- /dev/null +++ b/src/base-vs/deleteInstance.test.ts @@ -0,0 +1,39 @@ + +import { describe, expect, it } from '@jest/globals'; +import { convertVirtualSchemaBaseExtension, createJsonConnectionDefinition, createVirtualSchemaBuilder } from '.'; +import { ContextMock, createMockContext } from '../base/test-utils'; +import { emptyBaseVsExtension } from './test-vs-utils'; + +let context: ContextMock = undefined + +function deleteInstance(version: string, instanceId: string): void { + const baseExtension = emptyBaseVsExtension() + baseExtension.name = "testing-extension" + baseExtension.builder = createVirtualSchemaBuilder({ + adapterName: "adapterName", connectionNameProperty: "CONNECTION_NAME", + virtualSchemaParameters: [], + connectionDefinition: createJsonConnectionDefinition([]) + }) + context = createMockContext(); + convertVirtualSchemaBaseExtension(baseExtension).deleteInstance(context, version, instanceId) +} + +function getStatement(index: number): string { + const sqlStatements = context.mocks.sqlExecute.mock.calls + expect(sqlStatements).toHaveLength(2) + expect(sqlStatements[index]).toHaveLength(1) + const stmt = sqlStatements[index][0]; + expect(stmt).toBeDefined() + return stmt +} + +describe("deleteInstance()", () => { + it("fails for wrong version", () => { + expect(() => deleteInstance("wrongVersion", "instId")).toThrow("Version 'wrongVersion' not supported, can only use 'v0'.") + }) + it("executes DROP statements", () => { + deleteInstance("v0", "instId") + expect(getStatement(0)).toBe(`DROP VIRTUAL SCHEMA IF EXISTS "instId" CASCADE`) + expect(getStatement(1)).toBe(`DROP CONNECTION IF EXISTS "instId_CONNECTION"`) + }) +}) diff --git a/src/base/test-utils.ts b/src/base/test-utils.ts index ddef372..2d57bff 100644 --- a/src/base/test-utils.ts +++ b/src/base/test-utils.ts @@ -33,7 +33,7 @@ export function createMockContext(): ContextMock { sqlClient, bucketFs: { resolvePath(fileName: string) { - return "/bucketfs/" + fileName; + return `/bucketfs/${fileName}` }, }, metadata: { @@ -64,4 +64,4 @@ export function emptyBaseExtension(): JavaBaseExtension { scripts: [], scriptVersionExtractor: () => successResult("dummy version"), } -} \ No newline at end of file +} From aab3608bda5e00b24c07d027f9b3e71244798636 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Thu, 2 Nov 2023 08:22:37 +0100 Subject: [PATCH 23/29] Improve test coverage --- src/base/index.test.ts | 45 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/base/index.test.ts diff --git a/src/base/index.test.ts b/src/base/index.test.ts new file mode 100644 index 0000000..d371ab7 --- /dev/null +++ b/src/base/index.test.ts @@ -0,0 +1,45 @@ + +import { describe, expect, it } from '@jest/globals'; +import { ExasolExtension, NotFoundError } from '../api'; +import { convertBaseExtension } from './index'; +import { emptyBaseExtension } from './test-utils'; + + +describe("index", () => { + function testee(): ExasolExtension { + const baseExtension = emptyBaseExtension() + baseExtension.name = "test-ext" + baseExtension.version = "v1" + return convertBaseExtension(baseExtension) + } + + describe("findInstances()", () => { + it("is not supported", () => { + expect(() => testee().findInstances(undefined, undefined)).toThrowError(new NotFoundError("Finding instances not supported")) + }) + }) + + describe("addInstance()", () => { + it("is not supported", () => { + expect(() => testee().addInstance(undefined, undefined, undefined)).toThrowError(new NotFoundError("Creating instances not supported")) + }) + }) + + describe("deleteInstance()", () => { + it("is not supported", () => { + expect(() => testee().deleteInstance(undefined, undefined, undefined)).toThrowError(new NotFoundError("Deleting instances not supported")) + }) + }) + + describe("getInstanceParameters()", () => { + it("is not supported", () => { + expect(() => testee().getInstanceParameters(undefined, undefined)).toThrowError(new NotFoundError("Creating instances not supported")) + }) + }) + + describe("readInstanceParameterValues()", () => { + it("is not supported", () => { + expect(() => testee().readInstanceParameterValues(undefined, undefined, undefined)).toThrowError(new NotFoundError("Reading instance parameter values not supported")) + }) + }) +}) From c50b563f0911e3ee8372897772955ec532a4fa42 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Thu, 2 Nov 2023 08:30:45 +0100 Subject: [PATCH 24/29] Don't return default value for missing parameter values --- src/base-vs/addInstance.test.ts | 2 +- src/base-vs/parameterResolver.test.ts | 6 +++--- src/base-vs/parameterResolver.ts | 6 +----- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/base-vs/addInstance.test.ts b/src/base-vs/addInstance.test.ts index a391bfe..ea87b3a 100644 --- a/src/base-vs/addInstance.test.ts +++ b/src/base-vs/addInstance.test.ts @@ -83,7 +83,7 @@ describe("addInstance()", () => { const tests: { name: string, connParamDefs: Parameter[], params: ParameterValue[], expected: string }[] = [ { name: "no param", connParamDefs: [], params: [], expected: `{}` }, { name: "ignores unknown params", connParamDefs: [], params: [param("unknown", "val")], expected: `{}` }, - { name: "param with default value", connParamDefs: [{ id: "p1", name: "P1", type: "string", default: "def" }], params: [], expected: `{"p1":"def"}` }, + { name: "missing param with default value ignored", connParamDefs: [{ id: "p1", name: "P1", type: "string", default: "def" }], params: [], expected: `{}` }, { name: "optional string param", connParamDefs: [{ id: "p1", name: "P1", type: "string", required: false }], params: [param("p1", "v1")], expected: `{"p1":"v1"}` }, { name: "required string param", connParamDefs: [{ id: "p1", name: "P1", type: "string", required: true }], params: [param("p1", "v1")], expected: `{"p1":"v1"}` }, { name: "escapes single quotes", connParamDefs: [{ id: "p1", name: "P1", type: "string" }], params: [param("p1", "v'1")], expected: `{"p1":"v''1"}` }, diff --git a/src/base-vs/parameterResolver.test.ts b/src/base-vs/parameterResolver.test.ts index 3b28fc9..f4f4290 100644 --- a/src/base-vs/parameterResolver.test.ts +++ b/src/base-vs/parameterResolver.test.ts @@ -52,11 +52,11 @@ describe("ParameterResolver", () => { it("returns value for multiple parameter", () => { expect(testee(param("wrong-name", "value"), param(test.param.id, "value")).resolveOptional(test.param)).toEqual("value") }) - it("returns undefined for missing default", () => { + it("returns undefined for missing parameter without default value", () => { expect(testee().resolveOptional({ ...test.param, default: undefined })).toBeUndefined() }) - it("returns undefined for missing default", () => { - expect(testee().resolveOptional({ default: "def", ...test.param })).toEqual("def") + it("returns undefined for missing parameter with default value", () => { + expect(testee().resolveOptional({ default: "def", ...test.param })).toBeUndefined() }) })) }) diff --git a/src/base-vs/parameterResolver.ts b/src/base-vs/parameterResolver.ts index 6dca9f1..d75af72 100644 --- a/src/base-vs/parameterResolver.ts +++ b/src/base-vs/parameterResolver.ts @@ -19,11 +19,7 @@ function buildValuesMap(values: ParameterValues): Map { export function createParameterResolver(paramValues: ParameterValues): ParameterResolver { const values = buildValuesMap(paramValues) function resolveOptional(paramDef: Parameter): string | undefined { - const value = values.get(paramDef.id); - if (!value) { - return paramDef.default - } - return value; + return values.get(paramDef.id); } function resolve(paramDef: Parameter): string { From a4277a113ae3ac867c071bc695e2e1dd6cccddd6 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Thu, 2 Nov 2023 08:43:37 +0100 Subject: [PATCH 25/29] Update release date --- doc/changes/changes_0.4.0.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/changes/changes_0.4.0.md b/doc/changes/changes_0.4.0.md index db6620e..7a376bc 100644 --- a/doc/changes/changes_0.4.0.md +++ b/doc/changes/changes_0.4.0.md @@ -1,10 +1,10 @@ -# Extension Manager Interface 0.4.0, released 2023-??-?? +# Extension Manager Interface 0.4.0, released 2023-11-02 Code name: Simplify Virtual Schema Extensions ## Summary -This release simplifies creating extension definitions for Java-based virtual schemas. To use it, simply create your `ExasolExtension` by calling `convertVirtualSchemaBaseExtension(baseExtension: JavaVirtualSchemaBaseExtension)`. This works similar to `convertBaseExtension(baseExtension: JavaBaseExtension)` for Java-based script extensions. No need to implement all extension methods yourself, just configure required `SCRIPT`s, `CONNECTION` parameters and `VIRTUAL SCHEMA` properties. +This release simplifies creating extension definitions for Java-based virtual schemas. To use it, simply create your `ExasolExtension` by calling `convertVirtualSchemaBaseExtension(baseExtension: JavaVirtualSchemaBaseExtension)`. This works similar to `convertBaseExtension(baseExtension: JavaBaseExtension)` for Java-based script extensions. No need to implement all extension methods yourself, just configure required `SCRIPT`s, `CONNECTION` parameters and `VIRTUAL SCHEMA` properties. See the the S3 Virtual Schema [extension](https://github.com/exasol/s3-document-files-virtual-schema/blob/main/extension/src/extension.ts) updated in [#139](https://github.com/exasol/s3-document-files-virtual-schema/pull/139) as an example. ## Features From dfe4bb649b56e2b29d38a41b13dd2d6c0400a280 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Thu, 2 Nov 2023 14:46:15 +0100 Subject: [PATCH 26/29] Remove unused "adapterName" property --- src/base-vs/addInstance.test.ts | 8 ++++---- src/base-vs/addInstance.ts | 2 +- src/base-vs/builders.ts | 7 ++----- src/base-vs/deleteInstance.test.ts | 2 +- src/base-vs/getInstanceParameters.ts | 1 - src/base-vs/index.ts | 2 +- src/base-vs/test-vs-utils.ts | 6 +++--- 7 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/base-vs/addInstance.test.ts b/src/base-vs/addInstance.test.ts index ea87b3a..6a4f3cd 100644 --- a/src/base-vs/addInstance.test.ts +++ b/src/base-vs/addInstance.test.ts @@ -6,7 +6,7 @@ import { ContextMock, createMockContext } from '../base/test-utils'; import { emptyBaseVsExtension, param, vsNameParam } from './test-vs-utils'; let context: ContextMock = undefined -const adapterName = "ADAPTER_SCRIPT" + function addInstance(paramValues: ParameterValue[], version: string, virtualSchemaParameterDefs: Parameter[], connectionParameterDefs: Parameter[]): Instance { return addInstanceWithConnectionDef(paramValues, version, virtualSchemaParameterDefs, createJsonConnectionDefinition(connectionParameterDefs)) } @@ -17,7 +17,7 @@ function addInstanceWithConnectionDef(paramValues: ParameterValue[], version: st const baseExtension = emptyBaseVsExtension() baseExtension.name = "testing-extension" baseExtension.builder = createVirtualSchemaBuilder({ - adapterName, connectionNameProperty: "CONNECTION_NAME", + connectionNameProperty: "CONNECTION_NAME", virtualSchemaParameters: virtualSchemaParameterDefs, connectionDefinition }) @@ -54,7 +54,7 @@ describe("addInstance()", () => { it("executes statements", () => { addInstance([vsNameParam("vs1")], "v0", [], []) expect(getStatement(0)).toBe(`CREATE OR REPLACE CONNECTION "vs1_CONNECTION" TO '' IDENTIFIED BY '{}'`) - expect(getStatement(1)).toBe(`CREATE VIRTUAL SCHEMA "vs1" USING "ext-schema"."ADAPTER_SCRIPT" WITH CONNECTION_NAME = 'vs1_CONNECTION'`) + expect(getStatement(1)).toBe(`CREATE VIRTUAL SCHEMA "vs1" USING "ext-schema"."vs-adapter-script-name" WITH CONNECTION_NAME = 'vs1_CONNECTION'`) expect(getStatement(2)).toBe(`COMMENT ON CONNECTION "vs1_CONNECTION" IS 'Created by Extension Manager for testing-extension vv0 vs1'`) expect(getStatement(3)).toBe(`COMMENT ON SCHEMA "vs1" IS 'Created by Extension Manager for testing-extension vv0 vs1'`) }) @@ -73,7 +73,7 @@ describe("addInstance()", () => { ] tests.forEach(test => it(test.name, () => { addInstance([vsNameParam("vs1"), ...test.params], "v0", test.vsParamDefs, []) - expect(getCreateVirtualSchemaStatement()).toBe(`CREATE VIRTUAL SCHEMA "vs1" USING "ext-schema"."ADAPTER_SCRIPT" ` + expect(getCreateVirtualSchemaStatement()).toBe(`CREATE VIRTUAL SCHEMA "vs1" USING "ext-schema"."vs-adapter-script-name" ` + `WITH CONNECTION_NAME = 'vs1_CONNECTION'${test.expected}`) })) }) diff --git a/src/base-vs/addInstance.ts b/src/base-vs/addInstance.ts index 2bebc3b..af25e97 100644 --- a/src/base-vs/addInstance.ts +++ b/src/base-vs/addInstance.ts @@ -19,7 +19,7 @@ export function addInstance(context: Context, baseExtension: JavaVirtualSchemaBa function buildVirtualSchemaStatement(baseExtension: JavaVirtualSchemaBaseExtension, parameterResolver: ParameterResolver, connectionName: string, context: Context, virtualSchemaName: string) { const def = baseExtension.builder.buildVirtualSchema(parameterResolver, connectionName); - const adapter = `"${context.extensionSchemaName}"."${def.adapterName}"`; + const adapter = `"${context.extensionSchemaName}"."${baseExtension.virtualSchemaAdapterScript}"`; let stmt = `CREATE VIRTUAL SCHEMA "${virtualSchemaName}" USING ${adapter}`; if (def.properties.length > 0) { stmt += " WITH" diff --git a/src/base-vs/builders.ts b/src/base-vs/builders.ts index 2a8e8ca..327a617 100644 --- a/src/base-vs/builders.ts +++ b/src/base-vs/builders.ts @@ -11,8 +11,6 @@ export interface ConnectionParameterDefinition { } export interface Config { - /** Name of the adapter script, e.g. "S3_FILES_ADAPTER" */ - adapterName: string /** Parameter definitions used as properties for the VIRTUAL SCHEMA */ virtualSchemaParameters: Parameter[] /** Name of the Virtual Schema's connection property, e.g. "CONNECTION_NAME" */ @@ -24,8 +22,7 @@ export interface Config { connectionDefinition: ConnectionParameterDefinition } -export function createVirtualSchemaBuilder({ adapterName, virtualSchemaParameters, connectionNameProperty, connectionDefinition }: Config): VirtualSchemaBuilder { - +export function createVirtualSchemaBuilder({ virtualSchemaParameters, connectionNameProperty, connectionDefinition }: Config): VirtualSchemaBuilder { function convertVirtualSchemaParameters(connectionName: string, parameterResolver: ParameterResolver) { const result: VirtualSchemaProperty[] = [] result.push({ property: connectionNameProperty, value: connectionName }) @@ -44,7 +41,7 @@ export function createVirtualSchemaBuilder({ adapterName, virtualSchemaParameter }, buildConnection: connectionDefinition.builder, buildVirtualSchema(parameterResolver: ParameterResolver, connectionName: string) { - return { adapterName, properties: convertVirtualSchemaParameters(connectionName, parameterResolver) } + return { properties: convertVirtualSchemaParameters(connectionName, parameterResolver) } }, } } diff --git a/src/base-vs/deleteInstance.test.ts b/src/base-vs/deleteInstance.test.ts index 15c1843..1f17a7b 100644 --- a/src/base-vs/deleteInstance.test.ts +++ b/src/base-vs/deleteInstance.test.ts @@ -10,7 +10,7 @@ function deleteInstance(version: string, instanceId: string): void { const baseExtension = emptyBaseVsExtension() baseExtension.name = "testing-extension" baseExtension.builder = createVirtualSchemaBuilder({ - adapterName: "adapterName", connectionNameProperty: "CONNECTION_NAME", + connectionNameProperty: "CONNECTION_NAME", virtualSchemaParameters: [], connectionDefinition: createJsonConnectionDefinition([]) }) diff --git a/src/base-vs/getInstanceParameters.ts b/src/base-vs/getInstanceParameters.ts index 17e3db9..a62170e 100644 --- a/src/base-vs/getInstanceParameters.ts +++ b/src/base-vs/getInstanceParameters.ts @@ -2,7 +2,6 @@ import { JavaVirtualSchemaBaseExtension } from "."; import { Parameter } from "../parameters"; import { PARAM_VIRTUAL_SCHEMA_NAME } from "./parameters"; - export function getInstanceParameters(baseExtension: JavaVirtualSchemaBaseExtension): Parameter[] { return [PARAM_VIRTUAL_SCHEMA_NAME, ...baseExtension.builder.getParameters()] } diff --git a/src/base-vs/index.ts b/src/base-vs/index.ts index 7f81af4..ae313b9 100644 --- a/src/base-vs/index.ts +++ b/src/base-vs/index.ts @@ -26,8 +26,8 @@ export interface VirtualSchemaProperty { property: string value: string } + export interface VirtualSchemaDefinition { - adapterName: string properties: VirtualSchemaProperty[] } diff --git a/src/base-vs/test-vs-utils.ts b/src/base-vs/test-vs-utils.ts index d027798..42c83dc 100644 --- a/src/base-vs/test-vs-utils.ts +++ b/src/base-vs/test-vs-utils.ts @@ -3,7 +3,7 @@ import { ParameterValue } from "../api"; import { successResult } from "../base/common"; export function emptyBaseVsExtension(): JavaVirtualSchemaBaseExtension { - const adapterName = "TESTING_ADAPTER" + const adapterName = "vs-adapter-script-name" return { name: "testing-base-extension", version: "v0", @@ -15,9 +15,9 @@ export function emptyBaseVsExtension(): JavaVirtualSchemaBaseExtension { }, scripts: [{ name: adapterName, type: "SCALAR", parameters: "...", emitParameters: "...", scriptClass: "com.exasol.TestingAdapter" }], scriptVersionExtractor: () => successResult("dummy version"), - virtualSchemaAdapterScript: "vs-adapter-script-name", + virtualSchemaAdapterScript: adapterName, builder: createVirtualSchemaBuilder({ - adapterName, connectionNameProperty: "CONNECTION_NAME", + connectionNameProperty: "CONNECTION_NAME", virtualSchemaParameters: [ { id: "vs-required", name: "n1", type: "string", required: true }, { id: "vs-optional", name: "n2", type: "string", required: false }], From dc1ba80c27baa561dcebe3ed97bb9cf4568efa68 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Thu, 2 Nov 2023 15:51:17 +0100 Subject: [PATCH 27/29] Implement version check for findInstances() --- src/base-vs/findInstances.test.ts | 17 ++++++++++------- src/base-vs/index.ts | 3 ++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/base-vs/findInstances.test.ts b/src/base-vs/findInstances.test.ts index 995a5a8..a03b626 100644 --- a/src/base-vs/findInstances.test.ts +++ b/src/base-vs/findInstances.test.ts @@ -7,28 +7,31 @@ import { emptyBaseVsExtension } from './test-vs-utils'; let context: ContextMock = undefined -function findInstances(rows: Row[]): Instance[] { +function findInstances(version: string, rows: Row[]): Instance[] { const baseExtension = emptyBaseVsExtension() baseExtension.name = "testing-extension" context = createMockContext(); context.mocks.sqlQuery.mockReturnValueOnce({ columns: [], rows }) - const installations = convertVirtualSchemaBaseExtension(baseExtension).findInstances(context, "version") + const installations = convertVirtualSchemaBaseExtension(baseExtension).findInstances(context, version) expect(installations).toBeDefined() return installations } -describe("findInstances", () => { +describe("findInstances()", () => { + it("fails for wrong version", () => { + expect(() => findInstances("wrongVersion", [])).toThrow("Version 'wrongVersion' not supported, can only use 'v0'.") + }) it("returns empty array when no virtual schema exists", () => { - expect(findInstances([])).toStrictEqual([]) + expect(findInstances("v0", [])).toStrictEqual([]) }) it("returns single entry when virtual schema exists", () => { - expect(findInstances([["vs-name"]])).toStrictEqual([{ id: "vs-name", name: "vs-name" }]) + expect(findInstances("v0", [["vs-name"]])).toStrictEqual([{ id: "vs-name", name: "vs-name" }]) }) it("returns multiple entries", () => { - expect(findInstances([["vs1"], ["vs2"]])).toStrictEqual([{ id: "vs1", name: "vs1" }, { id: "vs2", name: "vs2" }]) + expect(findInstances("v0", [["vs1"], ["vs2"]])).toStrictEqual([{ id: "vs1", name: "vs1" }, { id: "vs2", name: "vs2" }]) }) it("executes expected query", () => { - findInstances([["vs1"], ["vs2"]]) + findInstances("v0", [["vs1"], ["vs2"]]) const expectedQuery = "SELECT SCHEMA_NAME FROM SYS.EXA_ALL_VIRTUAL_SCHEMAS WHERE ADAPTER_SCRIPT_SCHEMA = ? AND ADAPTER_SCRIPT_NAME = ? ORDER BY SCHEMA_NAME"; expect(context.mocks.sqlQuery.mock.calls).toStrictEqual([[expectedQuery, "ext-schema", "vs-adapter-script-name"]]) }) diff --git a/src/base-vs/index.ts b/src/base-vs/index.ts index ae313b9..0df95ca 100644 --- a/src/base-vs/index.ts +++ b/src/base-vs/index.ts @@ -52,7 +52,8 @@ export function convertVirtualSchemaBaseExtension(baseExtension: JavaVirtualSche verifyVersion(extensionVersion) return getInstanceParameters(baseExtension) }, - findInstances(context: Context, _version: string): Instance[] { + findInstances(context: Context, version: string): Instance[] { + verifyVersion(version) return findInstances(context, baseExtension.virtualSchemaAdapterScript); }, addInstance(context: Context, versionToInstall: string, paramValues: ParameterValues): Instance { From 7330f78e4266496828abb75b1f5c7f8be978e127 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Thu, 2 Nov 2023 16:06:42 +0100 Subject: [PATCH 28/29] Implement review findings --- src/base-vs/addInstance.ts | 18 +++--- src/base-vs/builders.ts | 64 +++++++++++-------- src/base-vs/index.ts | 48 ++++++++++++-- ...lver.test.ts => parameterAccessor.test.ts} | 44 ++++++------- src/base-vs/parameterAccessor.ts | 48 ++++++++++++++ src/base-vs/parameterResolver.ts | 36 ----------- src/base/index.ts | 2 + 7 files changed, 160 insertions(+), 100 deletions(-) rename src/base-vs/{parameterResolver.test.ts => parameterAccessor.test.ts} (69%) create mode 100644 src/base-vs/parameterAccessor.ts delete mode 100644 src/base-vs/parameterResolver.ts diff --git a/src/base-vs/addInstance.ts b/src/base-vs/addInstance.ts index af25e97..bc243b2 100644 --- a/src/base-vs/addInstance.ts +++ b/src/base-vs/addInstance.ts @@ -2,14 +2,14 @@ import { JavaVirtualSchemaBaseExtension } from "."; import { Instance } from "../api"; import { Context } from "../context"; import { convertSchemaNameToInstanceId, escapeSingleQuotes, getConnectionName } from "./common"; -import { ParameterResolver } from "./parameterResolver"; +import { ParameterAccessor } from "./parameterAccessor"; import { PARAM_VIRTUAL_SCHEMA_NAME } from "./parameters"; -export function addInstance(context: Context, baseExtension: JavaVirtualSchemaBaseExtension, parameterResolver: ParameterResolver): Instance { - const virtualSchemaName = parameterResolver.resolve(PARAM_VIRTUAL_SCHEMA_NAME) +export function addInstance(context: Context, baseExtension: JavaVirtualSchemaBaseExtension, parameters: ParameterAccessor): Instance { + const virtualSchemaName = parameters.get(PARAM_VIRTUAL_SCHEMA_NAME) const connectionName = getConnectionName(virtualSchemaName) - context.sqlClient.execute(buildConnectionStatement(baseExtension, parameterResolver, connectionName)) - context.sqlClient.execute(buildVirtualSchemaStatement(baseExtension, parameterResolver, connectionName, context, virtualSchemaName)) + context.sqlClient.execute(buildConnectionStatement(baseExtension, parameters, connectionName)) + context.sqlClient.execute(buildVirtualSchemaStatement(baseExtension, parameters, connectionName, context, virtualSchemaName)) const comment = `Created by Extension Manager for ${baseExtension.name} v${baseExtension.version} ${escapeSingleQuotes(virtualSchemaName)}`; context.sqlClient.execute(`COMMENT ON CONNECTION "${connectionName}" IS '${comment}'`); @@ -17,8 +17,8 @@ export function addInstance(context: Context, baseExtension: JavaVirtualSchemaBa return { id: convertSchemaNameToInstanceId(virtualSchemaName), name: virtualSchemaName } } -function buildVirtualSchemaStatement(baseExtension: JavaVirtualSchemaBaseExtension, parameterResolver: ParameterResolver, connectionName: string, context: Context, virtualSchemaName: string) { - const def = baseExtension.builder.buildVirtualSchema(parameterResolver, connectionName); +function buildVirtualSchemaStatement(baseExtension: JavaVirtualSchemaBaseExtension, parameters: ParameterAccessor, connectionName: string, context: Context, virtualSchemaName: string) { + const def = baseExtension.builder.buildVirtualSchema(parameters, connectionName); const adapter = `"${context.extensionSchemaName}"."${baseExtension.virtualSchemaAdapterScript}"`; let stmt = `CREATE VIRTUAL SCHEMA "${virtualSchemaName}" USING ${adapter}`; if (def.properties.length > 0) { @@ -30,8 +30,8 @@ function buildVirtualSchemaStatement(baseExtension: JavaVirtualSchemaBaseExtensi return stmt; } -function buildConnectionStatement(baseExtension: JavaVirtualSchemaBaseExtension, parameterResolver: ParameterResolver, connectionName: string) { - const connDef = baseExtension.builder.buildConnection(parameterResolver); +function buildConnectionStatement(baseExtension: JavaVirtualSchemaBaseExtension, parameters: ParameterAccessor, connectionName: string) { + const connDef = baseExtension.builder.buildConnection(parameters); const to = connDef.connectionTo ?? ''; let stmt = `CREATE OR REPLACE CONNECTION "${connectionName}" TO '${escapeSingleQuotes(to)}'`; if (connDef.user) { diff --git a/src/base-vs/builders.ts b/src/base-vs/builders.ts index 327a617..75f3605 100644 --- a/src/base-vs/builders.ts +++ b/src/base-vs/builders.ts @@ -1,19 +1,24 @@ import { ConnectionDefinition, VirtualSchemaBuilder, VirtualSchemaProperty } from "."; import { Parameter } from "../parameters"; -import { ParameterResolver } from "./parameterResolver"; - +import { ParameterAccessor } from "./parameterAccessor"; +/** + * Definition of `CONNECTION` parameters. + * @see {@link createUserPasswordConnectionDefinition} + * @see {@link createJsonConnectionDefinition} + */ export interface ConnectionParameterDefinition { - /** Parameter definitions used in the CONNECTION */ + /** Parameter definitions used in the `CONNECTION` */ parameters: Parameter[] - /** Factory function for a connection definition */ - builder: (parameterResolver: ParameterResolver) => ConnectionDefinition + /** Factory function for a `CONNECTION` definition */ + builder: (parameters: ParameterAccessor) => ConnectionDefinition } +/** Configuration for the function {@link createVirtualSchemaBuilder()} */ export interface Config { - /** Parameter definitions used as properties for the VIRTUAL SCHEMA */ + /** Parameter definitions used as properties for the `VIRTUAL SCHEMA` */ virtualSchemaParameters: Parameter[] - /** Name of the Virtual Schema's connection property, e.g. "CONNECTION_NAME" */ + /** Name of the Virtual Schema's connection property, e.g. `CONNECTION_NAME` */ connectionNameProperty: string /** * Connection definition. Create a connection definition with one of the functions @@ -22,12 +27,17 @@ export interface Config { connectionDefinition: ConnectionParameterDefinition } +/** + * Create a virtual schema builder. + * @param param0 configuration + * @returns a new virtual schema builder + */ export function createVirtualSchemaBuilder({ virtualSchemaParameters, connectionNameProperty, connectionDefinition }: Config): VirtualSchemaBuilder { - function convertVirtualSchemaParameters(connectionName: string, parameterResolver: ParameterResolver) { + function convertVirtualSchemaParameters(connectionName: string, parameters: ParameterAccessor) { const result: VirtualSchemaProperty[] = [] result.push({ property: connectionNameProperty, value: connectionName }) for (const param of virtualSchemaParameters) { - const value = parameterResolver.resolveOptional(param) + const value = parameters.getOptional(param) if (value) { result.push({ property: param.id, value }) } @@ -40,23 +50,24 @@ export function createVirtualSchemaBuilder({ virtualSchemaParameters, connection return [...virtualSchemaParameters, ...connectionDefinition.parameters] }, buildConnection: connectionDefinition.builder, - buildVirtualSchema(parameterResolver: ParameterResolver, connectionName: string) { - return { properties: convertVirtualSchemaParameters(connectionName, parameterResolver) } + buildVirtualSchema(parameters: ParameterAccessor, connectionName: string) { + return { properties: convertVirtualSchemaParameters(connectionName, parameters) } }, } } /** - * Creates the definition for a connection that contains all configuration formatted as JSON in the IDENTIFIED BY clause. - * @param parameters parameter definitions + * Creates the definition for a connection that contains all configuration formatted as JSON in the `IDENTIFIED BY` clause. + * This is usually used in document based virtual schemas. + * @param parameterDefinitions parameter definitions * @returns connection definition */ -export function createJsonConnectionDefinition(parameters: Parameter[]): ConnectionParameterDefinition { - function builder(parameterResolver: ParameterResolver): ConnectionDefinition { +export function createJsonConnectionDefinition(parameterDefinitions: Parameter[]): ConnectionParameterDefinition { + function builder(parameters: ParameterAccessor): ConnectionDefinition { /* eslint-disable @typescript-eslint/no-explicit-any */ const paramValues: any = {} - for (const param of parameters) { - const value = parameterResolver.resolveOptional(param) + for (const param of parameterDefinitions) { + const value = parameters.getOptional(param) if (value) { /* eslint-disable @typescript-eslint/no-unsafe-member-access */ paramValues[param.id] = value @@ -65,22 +76,23 @@ export function createJsonConnectionDefinition(parameters: Parameter[]): Connect return { identifiedBy: JSON.stringify(paramValues) } } - return { parameters, builder } + return { parameters: parameterDefinitions, builder } } /** - * Creates the definition for a connection that contains TO, USER and IDENTIFIED BY clauses. - * @param addressParam parameter definition for the TO clause - * @param userParam parameter definition for the USER clause - * @param passwordParam parameter definition for the IDENTIFIED BY clause + * Creates the definition for a connection that contains `TO`, `USER` and `IDENTIFIED BY` clauses. + * This is usually used in JDBC based virtual schemas. + * @param addressParam parameter definition for the `TO` clause + * @param userParam parameter definition for the `USER` clause + * @param passwordParam parameter definition for the `IDENTIFIED BY` clause * @returns connection definition */ export function createUserPasswordConnectionDefinition(addressParam: Parameter, userParam: Parameter, passwordParam: Parameter): ConnectionParameterDefinition { - function builder(parameterResolver: ParameterResolver): ConnectionDefinition { + function builder(parameters: ParameterAccessor): ConnectionDefinition { return { - connectionTo: addressParam ? parameterResolver.resolveOptional(addressParam) : undefined, - user: userParam ? parameterResolver.resolveOptional(userParam) : undefined, - identifiedBy: passwordParam ? parameterResolver.resolveOptional(passwordParam) : undefined + connectionTo: addressParam ? parameters.getOptional(addressParam) : undefined, + user: userParam ? parameters.getOptional(userParam) : undefined, + identifiedBy: passwordParam ? parameters.getOptional(passwordParam) : undefined } } return { parameters: [addressParam, userParam, passwordParam], builder } diff --git a/src/base-vs/index.ts b/src/base-vs/index.ts index 0df95ca..0702ebe 100644 --- a/src/base-vs/index.ts +++ b/src/base-vs/index.ts @@ -4,39 +4,73 @@ import { addInstance } from "./addInstance"; import { deleteInstance } from "./deleteInstance"; import { findInstances } from "./findInstances"; import { getInstanceParameters } from "./getInstanceParameters"; -import { ParameterResolver, createParameterResolver } from "./parameterResolver"; +import { ParameterAccessor, createParameterAccessor } from "./parameterAccessor"; /** - * Simplified version of an {@link ExasolExtension} specifically for Java based virtual schemas. + * Simplified version of an {@link ExasolExtension} specifically for Java based `VIRTUAL SCHEMA`s. */ export interface JavaVirtualSchemaBaseExtension extends JavaBaseExtension { - /** Unqualified name of the virtual schema adapter script, e.g. "S3_FILES_ADAPTER" */ + /** Unqualified name of the virtual schema `ADAPTER SCRIPT`, e.g. `S3_FILES_ADAPTER` */ virtualSchemaAdapterScript: string, /** A builder for virtual schemas. Use function {@link createVirtualSchemaBuilder()} to create it. */ builder: VirtualSchemaBuilder, } +/** Definition of an Exasol `CONNECTION` object */ export interface ConnectionDefinition { + /** Connection address, i.e. the `CONNECT TO '...'` part */ connectionTo?: string + /** Connection user, i.e. the `USER '...'` part */ user?: string + /** Connection password, i.e. the `IDENTIFIED BY '...'` part */ identifiedBy?: string } +/** + * Property in the `WITH` clause of a `VIRTUAL SCHEMA` definition. + * @see {@link VirtualSchemaDefinition} + */ export interface VirtualSchemaProperty { + /** Property name, e.g. `CONNECTION_NAME` or `MAPPING` */ property: string + /** Value of the property, e.g. `MY_S3_VS_CONNECTION` or `{...}` */ value: string } +/** Definition of a `VIRTUAL SCHEMA` */ export interface VirtualSchemaDefinition { + /** Property values for the `WITH` clause */ properties: VirtualSchemaProperty[] } +/** A builder for creating `VIRTUAL SCHEMA`s including their `CONNECTION`. */ export interface VirtualSchemaBuilder { + /** + * Get all parameters for the `VIRTUAL SCHEMA` and `CONNECTION`. + * @returns all parameters + */ getParameters: () => Parameter[] - buildVirtualSchema: (parameterResolver: ParameterResolver, connectionName?: string) => VirtualSchemaDefinition - buildConnection: (parameterResolver: ParameterResolver) => ConnectionDefinition + /** + * Create the `VIRTUAL SCHEMA` definition incl. user provided parameter values. + * @param parameters resolves {@link Parameter} objects to actual values supplied by the user + * @param connectionName name of the connection to use for the virtual schema + * @returns `VIRTUAL SCHEMA` definition + */ + buildVirtualSchema: (parameters: ParameterAccessor, connectionName: string) => VirtualSchemaDefinition + /** + * Create the `CONNECTION` definition incl. user provided parameter values. + * @param parameters resolves {@link Parameter} objects to actual values supplied by the user + * @returns `CONNECTION` definition + */ + buildConnection: (parameters: ParameterAccessor) => ConnectionDefinition } +/** + * Converts a {@link JavaVirtualSchemaBaseExtension} to an {@link ExasolExtension}. + * Use this in your virtual schema extension to avoid duplicating code. + * @param baseExtension the base extension to convert + * @returns an {@link ExasolExtension} + */ export function convertVirtualSchemaBaseExtension(baseExtension: JavaVirtualSchemaBaseExtension): ExasolExtension { const extension = convertBaseExtension(baseExtension) @@ -58,8 +92,8 @@ export function convertVirtualSchemaBaseExtension(baseExtension: JavaVirtualSche }, addInstance(context: Context, versionToInstall: string, paramValues: ParameterValues): Instance { verifyVersion(versionToInstall) - const parameterResolver = createParameterResolver(paramValues) - return addInstance(context, baseExtension, parameterResolver) + const parameters = createParameterAccessor(paramValues) + return addInstance(context, baseExtension, parameters) }, deleteInstance(context: Context, extensionVersion: string, instanceId: string) { verifyVersion(extensionVersion) diff --git a/src/base-vs/parameterResolver.test.ts b/src/base-vs/parameterAccessor.test.ts similarity index 69% rename from src/base-vs/parameterResolver.test.ts rename to src/base-vs/parameterAccessor.test.ts index f4f4290..8511509 100644 --- a/src/base-vs/parameterResolver.test.ts +++ b/src/base-vs/parameterAccessor.test.ts @@ -1,17 +1,17 @@ import { describe, expect, it } from '@jest/globals'; import { Parameter, ParameterValue } from '../api'; -import { createParameterResolver } from './parameterResolver'; +import { createParameterAccessor } from './parameterAccessor'; -describe("ParameterResolver", () => { +describe("ParameterAccessor", () => { function param(name: string, value: string): ParameterValue { return { name, value } } function testee(...values: ParameterValue[]) { - return createParameterResolver({ values }) + return createParameterAccessor({ values }) } - describe("createParameterResolver()", () => { + describe("createParameterAccessor()", () => { it("succeeds for empty parameter list", () => { expect(testee()).toBeDefined() }) @@ -26,7 +26,7 @@ describe("ParameterResolver", () => { }) }) - describe("resolveOptional()", () => { + describe("getOptional()", () => { describe("supported parameter types", () => { const tests: { name: string, param: Parameter }[] = [ { name: "optional string", param: { id: "p1", name: "n1", type: "string", required: false } }, @@ -38,36 +38,36 @@ describe("ParameterResolver", () => { ] tests.forEach(test => describe(test.name, () => { it("returns undefined for empty parameter values", () => { - expect(testee().resolveOptional(test.param)).toBeUndefined() + expect(testee().getOptional(test.param)).toBeUndefined() }) it("returns undefined for missing parameter", () => { - expect(testee(param("wrong-name", "value")).resolveOptional(test.param)).toBeUndefined() + expect(testee(param("wrong-name", "value")).getOptional(test.param)).toBeUndefined() }) it("returns value for available parameter", () => { - expect(testee(param(test.param.id, "value")).resolveOptional(test.param)).toEqual("value") + expect(testee(param(test.param.id, "value")).getOptional(test.param)).toEqual("value") }) it("returns undefined for undefined parameter", () => { - expect(testee(param(test.param.id, undefined)).resolveOptional(test.param)).toBeUndefined() + expect(testee(param(test.param.id, undefined)).getOptional(test.param)).toBeUndefined() }) - it("returns value for multiple parameter", () => { - expect(testee(param("wrong-name", "value"), param(test.param.id, "value")).resolveOptional(test.param)).toEqual("value") + it("returns value for multiple parameters", () => { + expect(testee(param("wrong-name", "value"), param(test.param.id, "value")).getOptional(test.param)).toEqual("value") }) it("returns undefined for missing parameter without default value", () => { - expect(testee().resolveOptional({ ...test.param, default: undefined })).toBeUndefined() + expect(testee().getOptional({ ...test.param, default: undefined })).toBeUndefined() }) it("returns undefined for missing parameter with default value", () => { - expect(testee().resolveOptional({ default: "def", ...test.param })).toBeUndefined() + expect(testee().getOptional({ default: "def", ...test.param })).toBeUndefined() }) })) }) }) - describe("resolve()", () => { + describe("get()", () => { it("fails for optional parameter", () => { - expect(() => testee().resolve({ id: "p1", name: "n1", type: "string", required: false })).toThrow("Parameter p1 is optional. Use method 'resolveOptional()' to resolve it.") + expect(() => testee().get({ id: "p1", name: "n1", type: "string", required: false })).toThrow("Parameter p1 is optional. Use method 'resolveOptional()' to resolve it.") }) it("fails for optional parameter with default", () => { - expect(() => testee().resolve({ id: "p1", name: "n1", type: "string", required: false, default: "def" })).toThrow("Parameter p1 is optional. Use method 'resolveOptional()' to resolve it.") + expect(() => testee().get({ id: "p1", name: "n1", type: "string", required: false, default: "def" })).toThrow("Parameter p1 is optional. Use method 'resolveOptional()' to resolve it.") }) describe("supported parameter types", () => { const tests: { name: string, param: Parameter }[] = [ @@ -77,19 +77,19 @@ describe("ParameterResolver", () => { ] tests.forEach(test => describe(test.name, () => { it("fails for empty parameter values", () => { - expect(() => testee().resolve(test.param)).toThrow("No value found for required parameter p1") + expect(() => testee().get(test.param)).toThrow("No value found for required parameter p1") }) it("fails for missing parameter", () => { - expect(() => testee(param("wrong-name", "value")).resolve(test.param)).toThrow("No value found for required parameter p1") + expect(() => testee(param("wrong-name", "value")).get(test.param)).toThrow("No value found for required parameter p1") }) it("returns value for available parameter", () => { - expect(testee(param(test.param.id, "value")).resolve(test.param)).toEqual("value") + expect(testee(param(test.param.id, "value")).get(test.param)).toEqual("value") }) it("fails for undefined parameter", () => { - expect(() => testee(param(test.param.id, undefined)).resolve(test.param)).toThrow("No value found for required parameter p1") + expect(() => testee(param(test.param.id, undefined)).get(test.param)).toThrow("No value found for required parameter p1") }) - it("returns value for multiple parameter", () => { - expect(testee(param("wrong-name", "value"), param(test.param.id, "value")).resolve(test.param)).toEqual("value") + it("returns value for multiple parameters", () => { + expect(testee(param("wrong-name", "value"), param(test.param.id, "value")).get(test.param)).toEqual("value") }) })) }) diff --git a/src/base-vs/parameterAccessor.ts b/src/base-vs/parameterAccessor.ts new file mode 100644 index 0000000..263cb88 --- /dev/null +++ b/src/base-vs/parameterAccessor.ts @@ -0,0 +1,48 @@ +import { Parameter, ParameterValues } from "../api"; + +/** Gives access to parameter values provided by the user. */ +export interface ParameterAccessor { + /** + * Get the value of a mandatory parameter. + * @param parameterDefinition parameter definition for which to get the parameter value + * @returns parameter value + * @throws an Error if the user did not provide a value for the parameter + */ + get: (parameterDefinition: Parameter) => string; + /** + * Get the value of an optional parameter. + * @param parameterDefinition parameter definition for which to get the parameter value + * @returns parameter value or `undefined` if no value is available + */ + getOptional: (parameterDefinition: Parameter) => string | undefined; +} + +function buildValuesMap(values: ParameterValues): Map { + const valuesMap = new Map() + for (const value of values.values) { + if (valuesMap.has(value.name)) { + throw new Error(`Two values '${value.value}' and '${valuesMap.get(value.name)}' found for parameter ${value.name}`) + } + valuesMap.set(value.name, value.value) + } + return valuesMap +} + +export function createParameterAccessor(paramValues: ParameterValues): ParameterAccessor { + const values = buildValuesMap(paramValues) + function getOptional(paramDef: Parameter): string | undefined { + return values.get(paramDef.id); + } + + function get(paramDef: Parameter): string { + if (!paramDef.required) { + throw new Error(`Parameter ${paramDef.id} is optional. Use method 'resolveOptional()' to resolve it.`) + } + const value = values.get(paramDef.id) + if (!value) { + throw new Error(`No value found for required parameter ${paramDef.id}`) + } + return value + } + return { getOptional, get } +} diff --git a/src/base-vs/parameterResolver.ts b/src/base-vs/parameterResolver.ts deleted file mode 100644 index d75af72..0000000 --- a/src/base-vs/parameterResolver.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Parameter, ParameterValues } from "../api"; - -export interface ParameterResolver { - resolve: (parameterDefinition: Parameter) => string; - resolveOptional: (parameterDefinition: Parameter) => string | undefined; -} - -function buildValuesMap(values: ParameterValues): Map { - const valuesMap = new Map() - for (const value of values.values) { - if (valuesMap.has(value.name)) { - throw new Error(`Two values '${value.value}' and '${valuesMap.get(value.name)}' found for parameter ${value.name}`) - } - valuesMap.set(value.name, value.value) - } - return valuesMap -} - -export function createParameterResolver(paramValues: ParameterValues): ParameterResolver { - const values = buildValuesMap(paramValues) - function resolveOptional(paramDef: Parameter): string | undefined { - return values.get(paramDef.id); - } - - function resolve(paramDef: Parameter): string { - if (!paramDef.required) { - throw new Error(`Parameter ${paramDef.id} is optional. Use method 'resolveOptional()' to resolve it.`) - } - const value = values.get(paramDef.id) - if (!value) { - throw new Error(`No value found for required parameter ${paramDef.id}`) - } - return value - } - return { resolveOptional, resolve } -} diff --git a/src/base/index.ts b/src/base/index.ts index e69b01b..35bdae8 100644 --- a/src/base/index.ts +++ b/src/base/index.ts @@ -26,6 +26,7 @@ export interface ScalarSetScriptDefinition { export interface AdapterScriptDefinition { /** Unqualified script name, e.g. "S3_FILES_ADAPTER" */ name: string + /** Script type */ type: "ADAPTER" /** Script Java class name, e.g. "com.exasol.adapter.document.UdfEntryPoint" */ scriptClass: string @@ -33,6 +34,7 @@ export interface AdapterScriptDefinition { /** * Simplified version of an {@link ExasolExtension} specifically for Java based extensions. + * Use function {@link convertBaseExtension} to convert this to a {@link ExasolExtension}. */ export interface JavaBaseExtension { /** Readable extension name, e.g. "S3 Virtual Schema" */ From bf1b9bf8d89f1fadc583027838ff1dd59e09e483 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Thu, 2 Nov 2023 16:54:43 +0100 Subject: [PATCH 29/29] Update release date --- doc/changes/changes_0.4.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changes/changes_0.4.0.md b/doc/changes/changes_0.4.0.md index 7a376bc..552666c 100644 --- a/doc/changes/changes_0.4.0.md +++ b/doc/changes/changes_0.4.0.md @@ -1,4 +1,4 @@ -# Extension Manager Interface 0.4.0, released 2023-11-02 +# Extension Manager Interface 0.4.0, released 2023-11-03 Code name: Simplify Virtual Schema Extensions