diff --git a/package-lock.json b/package-lock.json index 4bd0b6ea..a152fff5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,6 @@ "@xyflow/react": "^12.3.1", "ahooks": "^3.8.1", "ai": "^3.3.14", - "buffer": "^6.0.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.0", @@ -4200,26 +4199,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/big-integer": { "version": "1.6.52", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", @@ -4295,30 +4274,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -6938,26 +6893,6 @@ "@babel/runtime": "^7.23.2" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", diff --git a/package.json b/package.json index e0976221..821048d8 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "@xyflow/react": "^12.3.1", "ahooks": "^3.8.1", "ai": "^3.3.14", - "buffer": "^6.0.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.0", diff --git a/src/lib/domain/db-dependency.ts b/src/lib/domain/db-dependency.ts index ae1d5d1a..f043a138 100644 --- a/src/lib/domain/db-dependency.ts +++ b/src/lib/domain/db-dependency.ts @@ -4,7 +4,7 @@ import { schemaNameToDomainSchemaName, schemaNameToSchemaId, } from './db-schema'; -import type { DBTable } from './db-table'; +import { decodeViewDefinition, type DBTable } from './db-table'; import { generateId } from '@/lib/utils'; import type { AST } from 'node-sql-parser'; @@ -48,7 +48,6 @@ export const createDependenciesFromMetadata = async ({ databaseType: DatabaseType; }): Promise => { const { Parser } = await import('node-sql-parser'); - const { Buffer } = await import('buffer/'); const parser = new Parser(); const dependencies = views @@ -68,20 +67,10 @@ export const createDependenciesFromMetadata = async ({ if (view.view_definition) { try { - let decodedViewDefinition: string; - - // For other database types, decode the base64-encoded view definition - if (databaseType === DatabaseType.SQL_SERVER) { - decodedViewDefinition = Buffer.from( - view.view_definition, - 'base64' - ).toString('utf16le'); - } else { - decodedViewDefinition = Buffer.from( - view.view_definition, - 'base64' - ).toString('utf-8'); - } + const decodedViewDefinition = decodeViewDefinition( + databaseType, + view.view_definition + ); let modifiedViewDefinition = ''; if ( diff --git a/src/lib/domain/db-table.ts b/src/lib/domain/db-table.ts index 97e9f363..f2f381d4 100644 --- a/src/lib/domain/db-table.ts +++ b/src/lib/domain/db-table.ts @@ -7,11 +7,17 @@ import { materializedViewColor, viewColor, randomColor } from '@/lib/colors'; import type { DBRelationship } from './db-relationship'; import type { PrimaryKeyInfo } from '../data/import-metadata/metadata-types/primary-key-info'; import type { ViewInfo } from '../data/import-metadata/metadata-types/view-info'; -import { deepCopy, generateId } from '../utils'; +import { + decodeBase64ToUtf16LE, + decodeBase64ToUtf8, + deepCopy, + generateId, +} from '../utils'; import { schemaNameToDomainSchemaName, schemaNameToSchemaId, } from './db-schema'; +import { DatabaseType } from './database-type'; export interface DBTable { id: string; @@ -38,18 +44,38 @@ export const shouldShowTablesBySchemaFilter = ( !table.schema || filteredSchemas.includes(schemaNameToSchemaId(table.schema)); +export const decodeViewDefinition = ( + databaseType: DatabaseType, + viewDefinition?: string +): string => { + if (!viewDefinition) { + return ''; + } + + let decodedViewDefinition: string; + if (databaseType === DatabaseType.SQL_SERVER) { + decodedViewDefinition = decodeBase64ToUtf16LE(viewDefinition); + } else { + decodedViewDefinition = decodeBase64ToUtf8(viewDefinition); + } + + return decodedViewDefinition; +}; + export const createTablesFromMetadata = ({ tableInfos, columns, indexes, primaryKeys, views, + databaseType, }: { tableInfos: TableInfo[]; columns: ColumnInfo[]; indexes: IndexInfo[]; primaryKeys: PrimaryKeyInfo[]; views: ViewInfo[]; + databaseType: DatabaseType; }): DBTable[] => { return tableInfos.map((tableInfo: TableInfo) => { const tableSchema = schemaNameToDomainSchemaName(tableInfo.schema); @@ -165,8 +191,10 @@ export const createTablesFromMetadata = ({ const isMaterializedView = views.some( (view) => - view.view_definition?.includes('MATERIALIZED') && - view.view_name === tableInfo.table + view.view_name === tableInfo.table && + decodeViewDefinition(databaseType, view.view_definition) + .toLowerCase() + .includes('materialized') ); // Initial random positions; these will be adjusted later diff --git a/src/lib/domain/diagram.ts b/src/lib/domain/diagram.ts index 4690d278..b29c3934 100644 --- a/src/lib/domain/diagram.ts +++ b/src/lib/domain/diagram.ts @@ -47,6 +47,7 @@ export const loadFromDatabaseMetadata = async ({ indexes, primaryKeys, views, + databaseType, }); // First pass: Create relationships diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 9348937a..c340a6a5 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -59,3 +59,29 @@ export const debounce = ) => ReturnType>( export const removeDups = (array: T[]): T[] => { return [...new Set(array)]; }; + +export const decodeBase64ToUtf16LE = (base64: string) => { + const binaryString = atob(base64); + + const charCodes = new Uint16Array(binaryString.length / 2); + + for (let i = 0; i < charCodes.length; i++) { + charCodes[i] = + binaryString.charCodeAt(i * 2) + + (binaryString.charCodeAt(i * 2 + 1) << 8); + } + + return String.fromCharCode(...charCodes); +}; + +export const decodeBase64ToUtf8 = (base64: string) => { + const binaryString = atob(base64); + + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + const decoder = new TextDecoder('utf-8'); + return decoder.decode(bytes); +};