From bcf89b1f092afa8b81b3190def9db10e30b2a0a0 Mon Sep 17 00:00:00 2001 From: Nybkox Date: Thu, 23 Jan 2025 15:46:15 +0100 Subject: [PATCH] WIP --- docker-compose.yaml | 45 ++--- integration-tests/docker-compose.yaml | 62 +++---- .../api/src/proc/databaseConnectionProcess.js | 8 +- packages/datalib/src/ChangeSet.ts | 72 +++++--- packages/sqltree/src/dumpSqlCommand.ts | 2 + .../tools/src/createBulkInsertStreamBase.ts | 1 + packages/types/dialect.d.ts | 8 + packages/types/engines.d.ts | 4 +- .../web/src/appobj/ColumnAppObject.svelte | 25 ++- .../web/src/modals/SqlGeneratorModal.svelte | 2 + .../settings/ConnectionDriverFields.svelte | 10 ++ .../src/tableeditor/ColumnEditorModal.svelte | 4 +- .../web/src/tableeditor/DataTypeEditor.svelte | 1 + packages/web/src/tableeditor/newTable.ts | 2 +- packages/web/src/tabs/ConnectionTab.svelte | 2 + plugins/dbgate-plugin-cassandra/README.md | 6 + plugins/dbgate-plugin-cassandra/icon.svg | 35 ++++ plugins/dbgate-plugin-cassandra/package.json | 38 ++++ .../prettier.config.js | 8 + .../src/backend/Analyser.js | 60 +++++++ .../src/backend/createBulkInsertStream.js | 84 +++++++++ .../src/backend/driver.js | 162 ++++++++++++++++++ .../src/backend/index.js | 6 + .../src/backend/sql/columns.js | 9 + .../src/backend/sql/index.js | 9 + .../src/backend/sql/tables.js | 5 + .../src/backend/sql/views.js | 10 ++ .../src/frontend/Dumper.js | 27 +++ .../src/frontend/driver.js | 117 +++++++++++++ .../src/frontend/index.js | 6 + .../webpack-backend.config.js | 29 ++++ .../webpack-frontend.config.js | 24 +++ yarn.lock | 36 +++- 33 files changed, 832 insertions(+), 87 deletions(-) create mode 100644 plugins/dbgate-plugin-cassandra/README.md create mode 100644 plugins/dbgate-plugin-cassandra/icon.svg create mode 100644 plugins/dbgate-plugin-cassandra/package.json create mode 100644 plugins/dbgate-plugin-cassandra/prettier.config.js create mode 100644 plugins/dbgate-plugin-cassandra/src/backend/Analyser.js create mode 100644 plugins/dbgate-plugin-cassandra/src/backend/createBulkInsertStream.js create mode 100644 plugins/dbgate-plugin-cassandra/src/backend/driver.js create mode 100644 plugins/dbgate-plugin-cassandra/src/backend/index.js create mode 100644 plugins/dbgate-plugin-cassandra/src/backend/sql/columns.js create mode 100644 plugins/dbgate-plugin-cassandra/src/backend/sql/index.js create mode 100644 plugins/dbgate-plugin-cassandra/src/backend/sql/tables.js create mode 100644 plugins/dbgate-plugin-cassandra/src/backend/sql/views.js create mode 100644 plugins/dbgate-plugin-cassandra/src/frontend/Dumper.js create mode 100644 plugins/dbgate-plugin-cassandra/src/frontend/driver.js create mode 100644 plugins/dbgate-plugin-cassandra/src/frontend/index.js create mode 100644 plugins/dbgate-plugin-cassandra/webpack-backend.config.js create mode 100644 plugins/dbgate-plugin-cassandra/webpack-frontend.config.js diff --git a/docker-compose.yaml b/docker-compose.yaml index addc1da93..59d1bebe4 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,6 +1,6 @@ # this compose file is for testing purposes only # use it for testing docker containsers built on local machine -version: "3" +version: '3' services: dbgate: build: docker @@ -15,31 +15,31 @@ services: volumes: - dbgate-data:/root/.dbgate - + # environment: # WEB_ROOT: /dbgate - # CONNECTIONS: mssql - # LABEL_mssql: MS Sql - # SERVER_mssql: mssql - # USER_mssql: sa - # PORT_mssql: 1433 - # PASSWORD_mssql: Pwd2020Db - # ENGINE_mssql: mssql@dbgate-plugin-mssql - # proxy: - # # image: nginx - # build: test/nginx - # ports: - # - 8082:80 + # CONNECTIONS: mssql + # LABEL_mssql: MS Sql + # SERVER_mssql: mssql + # USER_mssql: sa + # PORT_mssql: 1433 + # PASSWORD_mssql: Pwd2020Db + # ENGINE_mssql: mssql@dbgate-plugin-mssql + proxy: + # image: nginx + build: test/nginx + ports: + - 8082:80 - # volumes: - # - /home/jena/test/chinook:/mnt/sqt - # environment: - # CONNECTIONS: sqlite + volumes: + - /home/jena/test/chinook:/mnt/sqt + environment: + CONNECTIONS: sqlite - # LABEL_sqlite: sqt - # FILE_sqlite: /mnt/sqt/Chinook.db - # ENGINE_sqlite: sqlite@dbgate-plugin-sqlite + LABEL_sqlite: sqt + FILE_sqlite: /mnt/sqt/Chinook.db + ENGINE_sqlite: sqlite@dbgate-plugin-sqlite # mssql: # image: mcr.microsoft.com/mssql/server @@ -51,4 +51,5 @@ services: volumes: dbgate-data: - driver: local \ No newline at end of file + driver: local + diff --git a/integration-tests/docker-compose.yaml b/integration-tests/docker-compose.yaml index 29cfb9371..208d2ec07 100644 --- a/integration-tests/docker-compose.yaml +++ b/integration-tests/docker-compose.yaml @@ -1,35 +1,41 @@ version: '3' services: - postgres: - image: postgres - restart: always - environment: - POSTGRES_PASSWORD: Pwd2020Db - ports: - - 15000:5432 - - mariadb: - image: mariadb - command: --default-authentication-plugin=mysql_native_password - restart: always - ports: - - 15004:3306 - environment: - - MYSQL_ROOT_PASSWORD=Pwd2020Db + # postgres: + # image: postgres + # restart: always + # environment: + # POSTGRES_PASSWORD: Pwd2020Db + # ports: + # - 15000:5432 + # + # mariadb: + # image: mariadb + # command: --default-authentication-plugin=mysql_native_password + # restart: always + # ports: + # - 15004:3306 + # environment: + # - MYSQL_ROOT_PASSWORD=Pwd2020Db # mysql: # image: mysql:8.0.18 # command: --default-authentication-plugin=mysql_native_password # restart: always - # ports: + # ports: # - 15001:3306 # environment: # - MYSQL_ROOT_PASSWORD=Pwd2020Db + # + + cassandradb: + image: cassandra:5.0.2 + ports: + - 15942:9042 # clickhouse: # image: bitnami/clickhouse:24.8.4 # restart: always - # ports: + # ports: # - 15005:8123 # environment: # - CLICKHOUSE_ADMIN_PASSWORD=Pwd2020Db @@ -37,19 +43,18 @@ services: # mssql: # image: mcr.microsoft.com/mssql/server # restart: always - # ports: + # ports: # - 15002:1433 # environment: # - ACCEPT_EULA=Y # - SA_PASSWORD=Pwd2020Db # - MSSQL_PID=Express - + # cockroachdb: # image: cockroachdb/cockroach # ports: # - 15003:26257 # command: start-single-node --insecure - # mongodb: # image: mongo:4.0.12 # restart: always @@ -59,20 +64,19 @@ services: # ports: # - 27017:27017 - # cockroachdb-init: # image: cockroachdb/cockroach # # build: cockroach -# # entrypoint: /cockroach/init.sh +# # entrypoint: /cockroach/init.sh # entrypoint: ./cockroach sql --insecure --host="cockroachdb" --execute="CREATE DATABASE IF NOT EXISTS test;" # depends_on: # - cockroachdb # restart: on-failure - oracle: - image: gvenzl/oracle-xe:21-slim - environment: - ORACLE_PASSWORD: Pwd2020Db - ports: - - 15006:1521 +# oracle: +# image: gvenzl/oracle-xe:21-slim +# environment: +# ORACLE_PASSWORD: Pwd2020Db +# ports: +# - 15006:1521 diff --git a/packages/api/src/proc/databaseConnectionProcess.js b/packages/api/src/proc/databaseConnectionProcess.js index c691aaf59..497dc62c9 100644 --- a/packages/api/src/proc/databaseConnectionProcess.js +++ b/packages/api/src/proc/databaseConnectionProcess.js @@ -213,13 +213,12 @@ async function handleRunOperation({ msgid, operation, useTransaction }, skipRead } } -async function handleQueryData({ msgid, sql }, skipReadonlyCheck = false) { +async function handleQueryData({ msgid, sql, range }, skipReadonlyCheck = false) { await waitConnected(); const driver = requireEngineDriver(storedConnection); try { if (!skipReadonlyCheck) ensureExecuteCustomScript(driver); - // console.log(sql); - const res = await driver.query(dbhan, sql); + const res = await driver.query(dbhan, sql, { range }); process.send({ msgtype: 'response', msgid, ...res }); } catch (err) { process.send({ @@ -234,7 +233,7 @@ async function handleSqlSelect({ msgid, select }) { const driver = requireEngineDriver(storedConnection); const dmp = driver.createDumper(); dumpSqlSelect(dmp, select); - return handleQueryData({ msgid, sql: dmp.s }, true); + return handleQueryData({ msgid, sql: dmp.s, range: select.range }, true); } async function handleDriverDataCore(msgid, callMethod, { logName }) { @@ -340,6 +339,7 @@ async function handleSqlPreview({ msgid, objects, options }) { }, 500); } } catch (err) { + console.error(err); process.send({ msgtype: 'response', msgid, diff --git a/packages/datalib/src/ChangeSet.ts b/packages/datalib/src/ChangeSet.ts index aeb3789e0..def8ef508 100644 --- a/packages/datalib/src/ChangeSet.ts +++ b/packages/datalib/src/ChangeSet.ts @@ -9,7 +9,7 @@ import { AllowIdentityInsert, Expression, } from 'dbgate-sqltree'; -import type { NamedObjectInfo, DatabaseInfo, TableInfo } from 'dbgate-types'; +import type { NamedObjectInfo, DatabaseInfo, TableInfo, SqlDialect } from 'dbgate-types'; import { JsonDataObjectUpdateCommand } from 'dbgate-tools'; export interface ChangeSetItem { @@ -252,7 +252,8 @@ function extractFields(item: ChangeSetItem, allowNulls = true, allowedDocumentCo function changeSetInsertToSql( item: ChangeSetItem, - dbinfo: DatabaseInfo = null + dbinfo: DatabaseInfo = null, + dialect: SqlDialect = null ): [AllowIdentityInsert, Insert, AllowIdentityInsert] { const table = dbinfo?.tables?.find(x => x.schemaName == item.schemaName && x.pureName == item.pureName); const fields = extractFields( @@ -299,19 +300,39 @@ function changeSetInsertToSql( ]; } -export function extractChangeSetCondition(item: ChangeSetItem, alias?: string): Condition { +export function extractChangeSetCondition( + item: ChangeSetItem, + alias?: string, + table?: TableInfo, + dialect?: SqlDialect +): Condition { + function getShouldUseRawRightValue(columnName: string) { + if (!table || !dialect || !dialect.rawUuids) return false; + + const column = table.columns.find(x => x.columnName == columnName); + if (!column) return false; + + if (column.dataType !== 'uuid') return false; + + return true; + } + function getColumnCondition(columnName: string): Condition { + const shouldUseRawRightValue = getShouldUseRawRightValue(columnName); + const value = item.condition[columnName]; const expr: Expression = { exprType: 'column', columnName, - source: { - name: { - pureName: item.pureName, - schemaName: item.schemaName, - }, - alias, - }, + source: dialect?.omitTableBeforeColumn + ? undefined + : { + name: { + pureName: item.pureName, + schemaName: item.schemaName, + }, + alias, + }, }; if (value == null) { return { @@ -323,10 +344,15 @@ export function extractChangeSetCondition(item: ChangeSetItem, alias?: string): conditionType: 'binary', operator: '=', left: expr, - right: { - exprType: 'value', - value, - }, + right: shouldUseRawRightValue + ? { + exprType: 'raw', + sql: value, + } + : { + exprType: 'value', + value, + }, }; } } @@ -366,7 +392,7 @@ function compileSimpleChangeSetCondition(fields: { [column: string]: string }): }; } -function changeSetUpdateToSql(item: ChangeSetItem, dbinfo: DatabaseInfo = null): Update { +function changeSetUpdateToSql(item: ChangeSetItem, dbinfo: DatabaseInfo = null, dialect: SqlDialect = null): Update { const table = dbinfo?.tables?.find(x => x.schemaName == item.schemaName && x.pureName == item.pureName); const autoIncCol = table?.columns?.find(x => x.autoIncrement); @@ -384,11 +410,13 @@ function changeSetUpdateToSql(item: ChangeSetItem, dbinfo: DatabaseInfo = null): true, table?.columns?.map(x => x.columnName).filter(x => x != autoIncCol?.columnName) ), - where: extractChangeSetCondition(item), + where: extractChangeSetCondition(item, undefined, table, dialect), }; } -function changeSetDeleteToSql(item: ChangeSetItem): Delete { +function changeSetDeleteToSql(item: ChangeSetItem, dbinfo: DatabaseInfo = null, dialect: SqlDialect = null): Delete { + const table = dbinfo?.tables?.find(x => x.schemaName == item.schemaName && x.pureName == item.pureName); + return { from: { name: { @@ -397,16 +425,16 @@ function changeSetDeleteToSql(item: ChangeSetItem): Delete { }, }, commandType: 'delete', - where: extractChangeSetCondition(item), + where: extractChangeSetCondition(item, undefined, table, dialect), }; } -export function changeSetToSql(changeSet: ChangeSet, dbinfo: DatabaseInfo): Command[] { +export function changeSetToSql(changeSet: ChangeSet, dbinfo: DatabaseInfo, dialect: SqlDialect): Command[] { return _.compact( _.flatten([ - ...(changeSet.inserts.map(item => changeSetInsertToSql(item, dbinfo)) as any), - ...changeSet.updates.map(item => changeSetUpdateToSql(item, dbinfo)), - ...changeSet.deletes.map(changeSetDeleteToSql), + ...(changeSet.inserts.map(item => changeSetInsertToSql(item, dbinfo, dialect)) as any), + ...changeSet.updates.map(item => changeSetUpdateToSql(item, dbinfo, dialect)), + ...changeSet.deletes.map(item => changeSetDeleteToSql(item, dbinfo, dialect)), ]) ); } diff --git a/packages/sqltree/src/dumpSqlCommand.ts b/packages/sqltree/src/dumpSqlCommand.ts index d73d7d690..4122ea3f8 100644 --- a/packages/sqltree/src/dumpSqlCommand.ts +++ b/packages/sqltree/src/dumpSqlCommand.ts @@ -52,6 +52,8 @@ export function dumpSqlSelect(dmp: SqlDumper, cmd: Select) { if (cmd.range) { if (dmp.dialect.offsetFetchRangeSyntax) { dmp.put('^offset %s ^rows ^fetch ^next %s ^rows ^only', cmd.range.offset, cmd.range.limit); + } else if (dmp.dialect.offsetNotSupported) { + dmp.put('^limit %s', cmd.range.limit + cmd.range.offset); } else { dmp.put('^limit %s ^offset %s ', cmd.range.limit, cmd.range.offset); } diff --git a/packages/tools/src/createBulkInsertStreamBase.ts b/packages/tools/src/createBulkInsertStreamBase.ts index 78b466aa8..9aa3129ce 100644 --- a/packages/tools/src/createBulkInsertStreamBase.ts +++ b/packages/tools/src/createBulkInsertStreamBase.ts @@ -14,6 +14,7 @@ export function createBulkInsertStreamBase(driver: EngineDriver, stream, dbhan, objectMode: true, }); + writable.fullNameQuoted = fullNameQuoted; writable.buffer = []; writable.structure = null; writable.columnNames = null; diff --git a/packages/types/dialect.d.ts b/packages/types/dialect.d.ts index 9e6b69549..97c9ba6f1 100644 --- a/packages/types/dialect.d.ts +++ b/packages/types/dialect.d.ts @@ -1,3 +1,5 @@ +import { ColumnInfo } from './dbinfo'; + export interface SqlDialect { rangeSelect?: boolean; limitSelect?: boolean; @@ -6,6 +8,7 @@ export interface SqlDialect { topRecords?: boolean; stringEscapeChar: string; offsetFetchRangeSyntax?: boolean; + offsetNotSupported?: boolean; quoteIdentifier(s: string): string; fallbackDataType?: string; explicitDropConstraint?: boolean; @@ -14,6 +17,7 @@ export interface SqlDialect { enableConstraintsPerTable?: boolean; requireStandaloneSelectForScopeIdentity?: boolean; allowMultipleValuesInsert?: boolean; + rawUuids?: boolean; dropColumnDependencies?: string[]; changeColumnDependencies?: string[]; @@ -45,6 +49,10 @@ export interface SqlDialect { omitUniqueConstraints?: boolean; omitIndexes?: boolean; omitTableAliases?: boolean; + omitTableBeforeColumn?: boolean; + disableAutoIncrement?: boolean; + disableNonPrimaryKeyRename?: boolean; + defaultNewTableColumns?: ColumnInfo[]; sortingKeys?: boolean; // syntax for create column: ALTER TABLE table ADD COLUMN column diff --git a/packages/types/engines.d.ts b/packages/types/engines.d.ts index 6b4c47a15..d0aac0854 100644 --- a/packages/types/engines.d.ts +++ b/packages/types/engines.d.ts @@ -32,6 +32,7 @@ export interface RunScriptOptions { export interface QueryOptions { discardResult?: boolean; importSqlDump?: boolean; + range?: { offset: number; limit: number }; } export interface WriteTableOptions { @@ -180,6 +181,7 @@ export interface EngineDriver extends FilterBehaviourProvider { beforeConnectionSave?: (values: any) => any; databaseUrlPlaceholder?: string; defaultAuthTypeName?: string; + defaultLocalDataCenter?: string; defaultSocketPath?: string; authTypeLabel?: string; importExportArgs?: any[]; @@ -251,7 +253,7 @@ export interface EngineDriver extends FilterBehaviourProvider { createSaveChangeSetScript( changeSet: any, dbinfo: DatabaseInfo, - defaultCreator: (changeSet: any, dbinfo: DatabaseInfo) => any + defaultCreator: (changeSet: any, dbinfo: DatabaseInfo, dialect: SqlDialect) => any ): any[]; // adapts table info from different source (import, other database) to be suitable for this database adaptTableInfo(table: TableInfo): TableInfo; diff --git a/packages/web/src/appobj/ColumnAppObject.svelte b/packages/web/src/appobj/ColumnAppObject.svelte index e53d78657..b6ca328ac 100644 --- a/packages/web/src/appobj/ColumnAppObject.svelte +++ b/packages/web/src/appobj/ColumnAppObject.svelte @@ -21,11 +21,15 @@ import { renameDatabaseObjectDialog, alterDatabaseDialog } from '../utility/alterDatabaseTools'; import AppObjectCore from './AppObjectCore.svelte'; - import { DEFAULT_OBJECT_SEARCH_SETTINGS } from '../stores'; - import { filterName } from 'dbgate-tools'; + import { DEFAULT_OBJECT_SEARCH_SETTINGS, extensions } from '../stores'; + import { filterName, findEngineDriver } from 'dbgate-tools'; + import { useConnectionInfo } from '../utility/metadataLoaders'; export let data; + $: connection = useConnectionInfo({ conid: data.conid }); + $: driver = findEngineDriver($connection, $extensions); + function handleRenameColumn() { renameDatabaseObjectDialog(data.conid, data.database, data.columnName, (db, newName) => { const tbl = db.tables.find(x => x.schemaName == data.schemaName && x.pureName == data.pureName); @@ -42,11 +46,20 @@ } function createMenu() { - return [ - { text: 'Rename column', onClick: handleRenameColumn }, + const isPrimaryKey = !!data.primaryKey?.columns?.some(i => i.columnName == data.columnName); + + const menu = []; + + if (!driver.dialect.disableNonPrimaryKeyRename || isPrimaryKey) { + menu.push({ text: 'Rename column', onClick: handleRenameColumn }); + } + + menu.push( { text: 'Drop column', onClick: handleDropColumn }, - { text: 'Copy name', onClick: () => navigator.clipboard.writeText(data.columnName) }, - ]; + { text: 'Copy name', onClick: () => navigator.clipboard.writeText(data.columnName) } + ); + + return menu; } function getExtInfo(data) { diff --git a/packages/web/src/modals/SqlGeneratorModal.svelte b/packages/web/src/modals/SqlGeneratorModal.svelte index ef4dbfa4c..10025aae7 100644 --- a/packages/web/src/modals/SqlGeneratorModal.svelte +++ b/packages/web/src/modals/SqlGeneratorModal.svelte @@ -57,6 +57,7 @@ let initialized = false; let error = null; let truncated = false; + $: console.log(error); $: dbinfo = useDatabaseInfo({ conid, database }); @@ -97,6 +98,7 @@ return; } const { sql, isTruncated, isError, errorMessage } = response || {}; + console.log(response); truncated = isTruncated; if (isError) { diff --git a/packages/web/src/settings/ConnectionDriverFields.svelte b/packages/web/src/settings/ConnectionDriverFields.svelte index f92cc35a2..453947f7b 100644 --- a/packages/web/src/settings/ConnectionDriverFields.svelte +++ b/packages/web/src/settings/ConnectionDriverFields.svelte @@ -135,6 +135,16 @@ /> {/if} +{#if driver?.showConnectionField('localDataCenter', $values, showConnectionFieldArgs)} + +{/if} + {#if $authTypes && driver?.showConnectionField('authType', $values, showConnectionFieldArgs)} {#key $authTypes} {/if} - + {#if !driver?.dialect?.disableAutoIncrement} + + {/if} diff --git a/packages/web/src/tableeditor/newTable.ts b/packages/web/src/tableeditor/newTable.ts index 15259635e..c7b4a8c68 100644 --- a/packages/web/src/tableeditor/newTable.ts +++ b/packages/web/src/tableeditor/newTable.ts @@ -22,7 +22,7 @@ export default function newTable(connection, database) { current: { pureName: 'new_table', schemaName: getAppliedCurrentSchema() ?? driver?.dialect?.defaultSchemaName, - columns: [ + columns: driver.dialect?.defaultNewTableColumns ?? [ { columnName: 'id', dataType: 'int', diff --git a/packages/web/src/tabs/ConnectionTab.svelte b/packages/web/src/tabs/ConnectionTab.svelte index 380659861..a7da5907b 100644 --- a/packages/web/src/tabs/ConnectionTab.svelte +++ b/packages/web/src/tabs/ConnectionTab.svelte @@ -61,6 +61,7 @@ $: engine = $values.engine; $: driver = $extensions.drivers.find(x => x.engine == engine); $: config = useConfig(); + $: console.log('#values', $values); const testIdRef = createRef(0); @@ -110,6 +111,7 @@ 'port', 'user', 'password', + 'localDataCenter', 'defaultDatabase', 'singleDatabase', 'socketPath', diff --git a/plugins/dbgate-plugin-cassandra/README.md b/plugins/dbgate-plugin-cassandra/README.md new file mode 100644 index 000000000..95bf6b8e0 --- /dev/null +++ b/plugins/dbgate-plugin-cassandra/README.md @@ -0,0 +1,6 @@ +[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) +[![NPM version](https://img.shields.io/npm/v/dbgate-plugin-cassandra.svg)](https://www.npmjs.com/package/dbgate-plugin-cassandra) + +# dbgate-plugin-cassandra + +Use DbGate for install of this plugin diff --git a/plugins/dbgate-plugin-cassandra/icon.svg b/plugins/dbgate-plugin-cassandra/icon.svg new file mode 100644 index 000000000..cfe0335e5 --- /dev/null +++ b/plugins/dbgate-plugin-cassandra/icon.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + diff --git a/plugins/dbgate-plugin-cassandra/package.json b/plugins/dbgate-plugin-cassandra/package.json new file mode 100644 index 000000000..d6a916293 --- /dev/null +++ b/plugins/dbgate-plugin-cassandra/package.json @@ -0,0 +1,38 @@ +{ + "name": "dbgate-plugin-cassandra", + "main": "dist/backend.js", + "version": "6.0.0-alpha.1", + "license": "GPL-3.0", + "author": "Jan Prochazka", + "description": "cassandra connector for DbGate", + "keywords": [ + "dbgate", + "cassandra", + "dbgatebuiltin" + ], + "files": [ + "dist", + "icon.svg" + ], + "scripts": { + "build:frontend": "webpack --config webpack-frontend.config", + "build:frontend:watch": "webpack --watch --config webpack-frontend.config", + "build:backend": "webpack --config webpack-backend.config.js", + "build": "yarn build:frontend && yarn build:backend", + "plugin": "yarn build && yarn pack && dbgate-plugin dbgate-plugin-cassandra", + "plugout": "dbgate-plugout dbgate-plugin-cassandra", + "copydist": "yarn build && yarn pack && dbgate-copydist ../dist/dbgate-plugin-cassandra", + "prepublishOnly": "yarn build" + }, + "devDependencies": { + "dbgate-plugin-tools": "^1.0.8", + "webpack": "^5.91.0", + "webpack-cli": "^5.1.4" + }, + "dependencies": { + "dbgate-tools": "^6.0.0-alpha.1", + "json-stable-stringify": "^1.0.1", + "lodash": "^4.17.21", + "cassandra-driver": "^4.7.2" + } +} diff --git a/plugins/dbgate-plugin-cassandra/prettier.config.js b/plugins/dbgate-plugin-cassandra/prettier.config.js new file mode 100644 index 000000000..406484074 --- /dev/null +++ b/plugins/dbgate-plugin-cassandra/prettier.config.js @@ -0,0 +1,8 @@ +module.exports = { + trailingComma: 'es5', + tabWidth: 2, + semi: true, + singleQuote: true, + arrowParen: 'avoid', + printWidth: 120, +}; diff --git a/plugins/dbgate-plugin-cassandra/src/backend/Analyser.js b/plugins/dbgate-plugin-cassandra/src/backend/Analyser.js new file mode 100644 index 000000000..55edb82bf --- /dev/null +++ b/plugins/dbgate-plugin-cassandra/src/backend/Analyser.js @@ -0,0 +1,60 @@ +const { DatabaseAnalyser } = global.DBGATE_PACKAGES['dbgate-tools']; +const sql = require('./sql'); + +class Analyser extends DatabaseAnalyser { + constructor(connection, driver) { + super(connection, driver); + } + + createQuery(resFileName, typeFields, replacements = {}) { + let res = sql[resFileName]; + res = res.replace('#DATABASE#', this.dbhan.database); + return super.createQuery(res, typeFields, replacements); + } + + async _runAnalysis() { + this.feedback({ analysingMessage: 'Loading tables' }); + const tables = await this.analyserQuery('tables', ['tables']); + this.feedback({ analysingMessage: 'Loading columns' }); + const columns = await this.analyserQuery('columns', ['tables']); + // this.feedback({ analysingMessage: 'Loading views' }); + // const views = await this.analyserQuery('views', ['views']); + + const res = { + tables: tables.rows.map((table) => { + const tableColumns = columns.rows.filter((col) => col.pureName == table.pureName); + const pkColumns = tableColumns.filter((i) => i.kind === 'partition_key' || i.kind === 'clustering'); + + return { + ...table, + primaryKeyColumns: pkColumns, + columns: tableColumns, + primaryKey: pkColumns.length ? { columns: pkColumns } : null, + foreignKeys: [], + }; + }), + views: [], + functions: [], + triggers: [], + }; + this.feedback({ analysingMessage: null }); + return res; + } + + async singleObjectAnalysis(dbhan, typeField) { + const structure = await this._runAnalysis(dbhan, typeField); + const item = structure[typeField]?.find((i) => i.pureName === dbhan.pureName); + return item; + } + + // async _computeSingleObjectId() { + // const { pureName } = this.singleObjectFilter; + // const resId = await this.driver.query( + // this.dbhan, + // `SELECT uuid as id FROM system.tables WHERE database = '${this.dbhan.database}' AND name='${pureName}'` + // ); + // this.singleObjectId = resId.rows[0]?.id; + // } +} + +module.exports = Analyser; diff --git a/plugins/dbgate-plugin-cassandra/src/backend/createBulkInsertStream.js b/plugins/dbgate-plugin-cassandra/src/backend/createBulkInsertStream.js new file mode 100644 index 000000000..51de12ad0 --- /dev/null +++ b/plugins/dbgate-plugin-cassandra/src/backend/createBulkInsertStream.js @@ -0,0 +1,84 @@ +const { createBulkInsertStreamBase } = global.DBGATE_PACKAGES['dbgate-tools']; + +/** + * + * @param {import('dbgate-types').TableInfo} tableInfo + * @param {string} columnName + * @returns {{columnName: string, dataType: string} | null} + */ +function getColumnInfo(tableInfo, columnName) { + const column = tableInfo.columns.find((x) => x.columnName == columnName); + if (!column) return null; + + return { + columnName, + dataType: column.dataType, + }; +} + +/** + * + * @param {string} tableName + * @returns {import('dbgate-types').TableInfo | null} + */ + +/** + * @param {string} tableName + * @returns {{ shouldAddUuidPk: true, pkColumnName: string } | { shouldAddUuidPk: false }} + */ +function getShouldAddUuidPkInfo(tableInfo) { + const pkColumnName = tableInfo.primaryKey?.columns[0]?.columnName; + if (!pkColumnName) return { shouldAddUuidPk: true, pkColumnName: 'id' }; + + const columnInfo = getColumnInfo(tableInfo, pkColumnName); + if (!columnInfo || columnInfo.dataType.toLowerCase() !== 'uuid') return { shouldAddUuidPk: false }; + + const shouldAddUuidPk = writable.columnNames.every((i) => i !== columnInfo.columnName); + if (!shouldAddUuidPk) return { shouldAddUuidPk }; + + return { shouldAddUuidPk, pkColumnName }; +} + +/** + * + * @param {import('dbgate-types').EngineDriver} driver + * @param {import('stream')} stream + * @param {import('dbgate-types').DatabaseHandle} dbhan + * @param {import('dbgate-types').NamedObjectInfo} name + * @param {import('dbgate-types').WriteTableOptions} option + */ +function createCassandraBulkInsertStream(driver, stream, dbhan, name, options) { + const writable = createBulkInsertStreamBase(driver, stream, dbhan, name, options); + + writable.send = async () => { + const { shouldAddUuidPk, pkColumnName } = getShouldAddUuidPkInfo(writable.structure); + + const rows = writable.buffer; + const fullNameQuoted = writable.fullNameQuoted; + writable.buffer = []; + + for (const row of rows) { + const dmp = driver.createDumper(); + dmp.putRaw(`INSERT INTO ${fullNameQuoted} (`); + if (shouldAddUuidPk) { + dmp.putRaw(driver.dialect.quoteIdentifier(pkColumnName)); + dmp.putRaw(', '); + } + dmp.putCollection(',', writable.columnNames, (col) => dmp.putRaw(driver.dialect.quoteIdentifier(col))); + dmp.putRaw(')\n VALUES\n'); + + dmp.putRaw('('); + if (shouldAddUuidPk) { + dmp.putRaw('uuid()'); + dmp.putRaw(', '); + } + dmp.putCollection(',', writable.columnNames, (col) => dmp.putValue(row[col])); + dmp.putRaw(')'); + await driver.query(dbhan, dmp.s, { discardResult: true }); + } + }; + + return writable; +} + +module.exports = createCassandraBulkInsertStream; diff --git a/plugins/dbgate-plugin-cassandra/src/backend/driver.js b/plugins/dbgate-plugin-cassandra/src/backend/driver.js new file mode 100644 index 000000000..dd0e982a4 --- /dev/null +++ b/plugins/dbgate-plugin-cassandra/src/backend/driver.js @@ -0,0 +1,162 @@ +const _ = require('lodash'); +const stream = require('stream'); +const driverBase = require('../frontend/driver'); +const Analyser = require('./Analyser'); +const cassandra = require('cassandra-driver'); +const createCassandraBulkInsertStream = require('./createBulkInsertStream.js'); + +function getTypeName(code) { + return Object.keys(cassandra.types.dataTypes).find((key) => cassandra.types.dataTypes[key] === code); +} + +/** @type {import('dbgate-types').EngineDriver} */ +const driver = { + ...driverBase, + analyserClass: Analyser, + // creating connection + async connect({ server, port, user, password, database, localDataCenter, useDatabaseUrl, databaseUrl }) { + console.log('#conn', localDataCenter); + const client = new cassandra.Client({ + // port, + // user, + // password, + contactPoints: server.split(','), + localDataCenter: localDataCenter ?? this.defaultLocalDataCenter, + keyspace: database, + }); + + client.connect(); + + return { + client, + database, + }; + }, + + // called for retrieve data (eg. browse in data grid) and for update database + async query(dbhan, query, options) { + const offset = options?.range?.offset; + if (options?.discardResult) { + await dbhan.client.execute(query); + return { + rows: [], + columns: [], + }; + } + const result = await dbhan.client.execute(query); + if (!result.rows?.[0]) { + return { + rows: [], + columns: [], + }; + } + + const columns = result.columns.map(({ name, type: { code } }) => ({ + columnName: name, + dataType: getTypeName(code), + })); + + return { + rows: offset ? result.rows.slice(offset) : result.rows, + columns, + }; + }, + // called in query console + async stream(dbhan, query, options) { + try { + if (!query.match(/^\s*SELECT/i)) { + await dbhan.client.execute(query); + options.done(); + return; + } + + const strm = dbhan.client.stream(query); + + strm.on('readable', () => { + let row; + while ((row = strm.read())) { + options.row(row); + } + }); + + strm.on('end', () => { + options.done(); + }); + + strm.on('error', (err) => { + options.info({ + message: err.toString(), + time: new Date(), + severity: 'error', + }); + options.done(); + }); + } catch (err) { + const mLine = err.message.match(/\(line (\d+)\,/); + let line = undefined; + if (mLine) { + line = parseInt(mLine[1]) - 1; + } + + options.info({ + message: err.message, + time: new Date(), + severity: 'error', + line, + }); + options.done(); + } + }, + // called when exporting table or view + async readQuery(dbhan, query, structure) { + const pass = new stream.PassThrough({ + objectMode: true, + highWaterMark: 100, + }); + + const strm = dbhan.client.stream(query); + + strm.on('readable', () => { + let row; + while ((row = strm.read())) { + pass.write(row); + } + }); + + strm.on('end', () => { + pass.end(); + }); + + strm.on('error', (err) => { + pass.info({ + message: err.toString(), + time: new Date(), + severity: 'error', + }); + pass.end(); + }); + + return pass; + }, + async writeTable(dbhan, name, options) { + return createCassandraBulkInsertStream(this, stream, dbhan, name, options); + }, + // detect server version + async getVersion(dbhan) { + const result = await dbhan.client.execute('SELECT release_version from system.local'); + return { version: result.rows[0].release_version }; + }, + // list databases on server + async listDatabases(dbhan) { + const result = await dbhan.client.execute( + "SELECT keyspace_name FROM system_schema.keyspaces WHERE keyspace_name >= 'system' ALLOW FILTERING" + ); + return result.rows.map((row) => ({ name: row.keyspace_name })); + }, + + async close(dbhan) { + return dbhan.client.shutdown(); + }, +}; + +module.exports = driver; diff --git a/plugins/dbgate-plugin-cassandra/src/backend/index.js b/plugins/dbgate-plugin-cassandra/src/backend/index.js new file mode 100644 index 000000000..5098c926d --- /dev/null +++ b/plugins/dbgate-plugin-cassandra/src/backend/index.js @@ -0,0 +1,6 @@ +const driver = require('./driver'); + +module.exports = { + packageName: 'dbgate-plugin-cassandra', + drivers: [driver], +}; diff --git a/plugins/dbgate-plugin-cassandra/src/backend/sql/columns.js b/plugins/dbgate-plugin-cassandra/src/backend/sql/columns.js new file mode 100644 index 000000000..021aa276d --- /dev/null +++ b/plugins/dbgate-plugin-cassandra/src/backend/sql/columns.js @@ -0,0 +1,9 @@ +module.exports = ` +SELECT + table_name as "pureName", + column_name as "columnName", + type as "dataType", + kind as "kind" +FROM system_schema.columns +WHERE keyspace_name = '#DATABASE#' +`; diff --git a/plugins/dbgate-plugin-cassandra/src/backend/sql/index.js b/plugins/dbgate-plugin-cassandra/src/backend/sql/index.js new file mode 100644 index 000000000..3ef80fe8a --- /dev/null +++ b/plugins/dbgate-plugin-cassandra/src/backend/sql/index.js @@ -0,0 +1,9 @@ +const columns = require('./columns'); +const tables = require('./tables'); +const views = require('./views'); + +module.exports = { + columns, + tables, + views, +}; diff --git a/plugins/dbgate-plugin-cassandra/src/backend/sql/tables.js b/plugins/dbgate-plugin-cassandra/src/backend/sql/tables.js new file mode 100644 index 000000000..6e1bc0285 --- /dev/null +++ b/plugins/dbgate-plugin-cassandra/src/backend/sql/tables.js @@ -0,0 +1,5 @@ +module.exports = ` +SELECT table_name as "pureName" +FROM system_schema.tables +WHERE keyspace_name='#DATABASE#'; +`; diff --git a/plugins/dbgate-plugin-cassandra/src/backend/sql/views.js b/plugins/dbgate-plugin-cassandra/src/backend/sql/views.js new file mode 100644 index 000000000..c773517a7 --- /dev/null +++ b/plugins/dbgate-plugin-cassandra/src/backend/sql/views.js @@ -0,0 +1,10 @@ +module.exports = ` +select + tables.name as "pureName", + tables.uuid as "objectId", + views.view_definition as "viewDefinition", + tables.metadata_modification_time as "contentHash" +from information_schema.views +inner join system.tables on views.table_name = tables.name and views.table_schema = tables.database +where views.table_schema='#DATABASE#' and tables.uuid =OBJECT_ID_CONDITION +`; diff --git a/plugins/dbgate-plugin-cassandra/src/frontend/Dumper.js b/plugins/dbgate-plugin-cassandra/src/frontend/Dumper.js new file mode 100644 index 000000000..9dabf7c6e --- /dev/null +++ b/plugins/dbgate-plugin-cassandra/src/frontend/Dumper.js @@ -0,0 +1,27 @@ +/** + * @type {{ SqlDumper: import('dbgate-types').SqlDumper}} + */ +const { SqlDumper } = global.DBGATE_PACKAGES['dbgate-tools']; + +class Dumper extends SqlDumper { + /** + * @param {import('dbgate-types').ColumnInfo} column + * @param {string} newName + * + * @returns {void} + */ + renameColumn(column, newName) { + this.putCmd('^alter ^table %f ^rename %i ^to %i', column, column.columnName, newName); + } + + /** + * @param {import('dbgate-types').ColumnInfo} column + * + * @returns {void} + */ + dropColumn(column) { + this.putCmd('^alter ^table %f ^drop %i', column, column.columnName); + } +} + +module.exports = Dumper; diff --git a/plugins/dbgate-plugin-cassandra/src/frontend/driver.js b/plugins/dbgate-plugin-cassandra/src/frontend/driver.js new file mode 100644 index 000000000..68667e8cf --- /dev/null +++ b/plugins/dbgate-plugin-cassandra/src/frontend/driver.js @@ -0,0 +1,117 @@ +const { driverBase } = global.DBGATE_PACKAGES['dbgate-tools']; +const Dumper = require('./Dumper'); +const { mysqlSplitterOptions } = require('dbgate-query-splitter/lib/options'); +const _cloneDeepWith = require('lodash/cloneDeepWith'); + +/** @type {import('dbgate-types').SqlDialect} */ +const dialect = { + limitSelect: true, + rangeSelect: true, + rawUuids: true, + stringEscapeChar: "'", + fallbackDataType: 'varchar', + offsetNotSupported: true, + allowMultipleValuesInsert: false, + createColumn: true, + dropColumn: true, + changeColumn: true, + changeAutoIncrement: true, + createIndex: true, + dropIndex: true, + anonymousPrimaryKey: true, + createColumnWithColumnKeyword: true, + specificNullabilityImplementation: true, + omitForeignKeys: true, + omitUniqueConstraints: true, + omitIndexes: true, + omitTableAliases: true, + omitTableBeforeColumn: true, + sortingKeys: true, + predefinedDataTypes: [ + 'custom', + 'ascii', + 'bigint', + 'blob', + 'boolean', + 'counter', + 'decimal', + 'double', + 'float', + 'int', + 'text', + 'timestamp', + 'uuid', + 'varchar', + 'varint', + 'timeuuid', + 'inet', + 'date', + 'time', + 'smallint', + 'tinyint', + 'duration', + 'list', + 'map', + 'set', + 'udt', + 'tuple', + ], + disableAutoIncrement: true, + disableNonPrimaryKeyRename: true, + defaultNewTableColumns: [ + { + columnName: 'id', + dataType: 'uuid', + notNull: true, + }, + ], + columnProperties: { + columnComment: true, + }, + + quoteIdentifier(s) { + return `"${s}"`; + }, +}; + +/** @type {import('dbgate-types').EngineDriver} */ +const driver = { + ...driverBase, + supportsTransactions: false, + defaultPort: 9042, + defaultLocalDataCenter: 'datacenter1', + dumperClass: Dumper, + dialect, + engine: 'cassandra@dbgate-plugin-cassandra', + title: 'Cassandra', + showConnectionField: (field, values) => + ['server', 'port', 'singleDatabase', 'localDataCenter', 'isReadOnly', 'user', 'password'].includes(field), + getQuerySplitterOptions: (usage) => + usage == 'editor' + ? { ...mysqlSplitterOptions, ignoreComments: true, preventSingleLineSplit: true } + : mysqlSplitterOptions, + adaptTableInfo(table) { + if (!table.primaryKey && !table.sortingKey) { + return { + ...table, + primaryKey: { + columns: [ + { + columnName: 'id', + }, + ], + }, + columns: [ + { + columnName: 'id', + dataType: 'uuid', + }, + ...table.columns, + ], + }; + } + return table; + }, +}; + +module.exports = driver; diff --git a/plugins/dbgate-plugin-cassandra/src/frontend/index.js b/plugins/dbgate-plugin-cassandra/src/frontend/index.js new file mode 100644 index 000000000..bdc90b642 --- /dev/null +++ b/plugins/dbgate-plugin-cassandra/src/frontend/index.js @@ -0,0 +1,6 @@ +import driver from './driver'; + +export default { + packageName: 'dbgate-plugin-cassandra', + drivers: [driver], +}; diff --git a/plugins/dbgate-plugin-cassandra/webpack-backend.config.js b/plugins/dbgate-plugin-cassandra/webpack-backend.config.js new file mode 100644 index 000000000..20c953370 --- /dev/null +++ b/plugins/dbgate-plugin-cassandra/webpack-backend.config.js @@ -0,0 +1,29 @@ +var webpack = require('webpack'); +var path = require('path'); + +const packageJson = require('./package.json'); +const buildPluginExternals = require('../../common/buildPluginExternals'); +const externals = buildPluginExternals(packageJson); + +var config = { + context: __dirname + '/src/backend', + + entry: { + app: './index.js', + }, + target: 'node', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'backend.js', + libraryTarget: 'commonjs2', + }, + + // uncomment for disable minimalization + // optimization: { + // minimize: false, + // }, + + externals, +}; + +module.exports = config; diff --git a/plugins/dbgate-plugin-cassandra/webpack-frontend.config.js b/plugins/dbgate-plugin-cassandra/webpack-frontend.config.js new file mode 100644 index 000000000..db07de291 --- /dev/null +++ b/plugins/dbgate-plugin-cassandra/webpack-frontend.config.js @@ -0,0 +1,24 @@ +var webpack = require("webpack"); +var path = require("path"); + +var config = { + context: __dirname + "/src/frontend", + + entry: { + app: "./index.js", + }, + target: "web", + output: { + path: path.resolve(__dirname, "dist"), + filename: "frontend.js", + libraryTarget: "var", + library: 'plugin', + }, + + // uncomment for disable minimalization + // optimization: { + // minimize: false, + // }, +}; + +module.exports = config; diff --git a/yarn.lock b/yarn.lock index 1ec9fdafb..bf13b37fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2444,6 +2444,13 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.1.tgz#0fabfcf2f2127ef73b119d98452bd317c4a17eb8" integrity sha512-X+2qazGS3jxLAIz5JDXDzglAF3KpijdhFxlf/V1+hEsOUc+HnWi81L/uv/EvGuV90WY+7mPGFCUDGfQC3Gj95Q== +"@types/long@~5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/long/-/long-5.0.0.tgz#daaa7b7f74c919c946ff74889d5ca2afe363b2cd" + integrity sha512-eQs9RsucA/LNjnMoJvWG/nXa7Pot/RbBzilF/QRIU/xRl+0ApxrSUFsV5lmf01SvSlqMzJ7Zwxe440wmz2SJGA== + dependencies: + long "*" + "@types/markdown-it@^14.1.1": version "14.1.2" resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-14.1.2.tgz#57f2532a0800067d9b934f3521429a2e8bfb4c61" @@ -2479,6 +2486,13 @@ dependencies: undici-types "~5.26.4" +"@types/node@>=8": + version "22.10.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.5.tgz#95af89a3fb74a2bb41ef9927f206e6472026e48b" + integrity sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ== + dependencies: + undici-types "~6.20.0" + "@types/node@^13.7.0": version "13.13.52" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.52.tgz#03c13be70b9031baaed79481c0c0cfb0045e53f7" @@ -2846,6 +2860,11 @@ adler-32@~1.3.0: resolved "https://registry.yarnpkg.com/adler-32/-/adler-32-1.3.1.tgz#1dbf0b36dda0012189a32b3679061932df1821e2" integrity sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A== +adm-zip@~0.5.10: + version "0.5.16" + resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.5.16.tgz#0b5e4c779f07dedea5805cdccb1147071d94a909" + integrity sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ== + agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -3683,6 +3702,16 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== +cassandra-driver@^4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/cassandra-driver/-/cassandra-driver-4.7.2.tgz#87f120b6d73d64f0ff3e91cdd4e56bec416fca48" + integrity sha512-gwl1DeYvL8Wy3i1GDMzFtpUg5G473fU7EnHFZj7BUtdLB7loAfgZgB3zBhROc9fbaDSUDs6YwOPPojS5E1kbSA== + dependencies: + "@types/long" "~5.0.0" + "@types/node" ">=8" + adm-zip "~0.5.10" + long "~5.2.3" + catharsis@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/catharsis/-/catharsis-0.9.0.tgz#40382a168be0e6da308c277d3a2b3eb40c7d2121" @@ -8350,7 +8379,7 @@ lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7. resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -long@^5.2.1: +long@*, long@^5.2.1, long@~5.2.3: version "5.2.3" resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== @@ -11823,6 +11852,11 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"