Files
dbx/apps/desktop/src/lib/database/databaseNamespaceCreation.ts
T

143 lines
8.9 KiB
TypeScript

import type { ConnectionConfig, DatabaseType, TreeNodeType } from "@/types/database";
import { connectionIsEffectivelyReadOnly } from "@/lib/database/readOnlyWriteAccess";
export type DatabaseNamespaceCreationTarget = "database" | "schema" | "attach" | "special";
type ConnectionCreationTarget = Extract<DatabaseNamespaceCreationTarget, "database" | "schema" | "attach" | "special">;
type DatabaseNodeCreationTarget = Extract<DatabaseNamespaceCreationTarget, "schema">;
export interface DatabaseNamespaceCreationMatrixEntry {
connection?: ConnectionCreationTarget;
database?: DatabaseNodeCreationTarget;
deferred?: string;
}
type CreationConnection = (Pick<ConnectionConfig, "db_type" | "driver_profile" | "read_only"> & Partial<Pick<ConnectionConfig, "host" | "password" | "id">>) | undefined;
// Keep creation target-specific: many products expose schemas, files, or provider-managed namespaces instead of a top-level database.
export const DATABASE_NAMESPACE_CREATION_MATRIX = {
mysql: { connection: "database" },
postgres: { connection: "database", database: "schema" },
sqlite: { connection: "attach" },
rqlite: { deferred: "single SQLite-compatible database per node" },
turso: { deferred: "remote libSQL database lifecycle is provider-managed" },
"cloudflare-d1": { deferred: "Cloudflare D1 database lifecycle is provider-managed" },
redis: { deferred: "numbered logical databases are server-configured" },
duckdb: { connection: "attach" },
clickhouse: { connection: "database" },
sqlserver: { connection: "database", database: "schema" },
mongodb: { connection: "special" },
dynamodb: { deferred: "table creation requires dedicated DynamoDB key and capacity options" },
oracle: { deferred: "Oracle schemas are users; database creation is not a normal connected DDL action" },
elasticsearch: { deferred: "index creation is not modeled as database creation" },
easysearch: { deferred: "index creation is not modeled as database creation" },
meilisearch: { deferred: "index creation is not modeled as database creation" },
salesforce: { deferred: "Salesforce orgs do not expose database creation through SOQL" },
solr: { deferred: "core creation is not modeled as database creation" },
couchdb: { deferred: "database creation is not modeled as SQL database creation" },
hbase: { deferred: "namespace creation needs dedicated HBase namespace options" },
qdrant: { deferred: "collection creation is separate from database creation" },
milvus: { deferred: "collection/database lifecycle needs a dedicated vector workflow" },
weaviate: { deferred: "collection creation is separate from database creation" },
chromadb: { deferred: "collection creation is separate from database creation" },
doris: { connection: "database" },
starrocks: { connection: "database" },
manticoresearch: { deferred: "index/table creation is not database creation" },
databend: { connection: "database" },
redshift: { connection: "database", database: "schema" },
dameng: { connection: "schema" },
gaussdb: { connection: "database", database: "schema" },
kingbase: { connection: "database", database: "schema" },
highgo: { connection: "database", database: "schema" },
uxdb: { database: "schema" },
vastbase: { connection: "database", database: "schema" },
goldendb: { connection: "database" },
kwdb: { connection: "database", database: "schema" },
yashandb: { connection: "database", database: "schema" },
databricks: { database: "schema" },
saphana: { database: "schema" },
teradata: { database: "schema" },
vertica: { database: "schema" },
firebird: { deferred: "database files are created through connection provisioning" },
exasol: { database: "schema" },
opengauss: { connection: "database", database: "schema" },
"oceanbase-oracle": { deferred: "Oracle-mode schemas are users; use a dedicated user workflow" },
questdb: { deferred: "single database model in DBX" },
gbase: { database: "schema" },
// GBase 8c creates databases with an explicit DBCOMPATIBILITY mode that is fixed at
// creation time, so the generic PostgreSQL-shaped create path cannot pick a correct mode.
gbase8c: { deferred: "database creation needs an explicit DBCOMPATIBILITY mode fixed at creation time" },
access: { deferred: "file-backed; create a new connection/file instead" },
h2: { database: "schema" },
snowflake: { connection: "database", database: "schema" },
trino: { database: "schema" },
prestosql: { database: "schema" },
hive: { deferred: "Hive database creation needs agent metadata validation first" },
argo: { deferred: "ArgoDB database creation needs agent metadata validation first" },
transwarp: { connection: "database" },
kyuubi: { deferred: "Kyuubi database creation needs dedicated metadata validation first" },
impala: { deferred: "Impala database creation needs dedicated metadata validation first" },
spark: { deferred: "Spark database creation needs agent metadata validation first" },
db2: { database: "schema" },
informix: { connection: "database" },
neo4j: { deferred: "database creation depends on edition/admin privileges" },
nebula: { deferred: "space creation requires partition, replica and VID type options" },
cassandra: { deferred: "keyspace creation requires replication options" },
bigquery: { deferred: "dataset creation needs project/location options" },
spanner: { deferred: "database creation requires the Cloud Spanner Admin API" },
kylin: { deferred: "project/model lifecycle is not SQL database creation" },
ignite: { deferred: "schema lifecycle is managed through cluster/cache configuration" },
ignite3: { deferred: "schema lifecycle is managed through cluster configuration" },
sundb: { deferred: "creation semantics not verified for first pass" },
oscar: { database: "schema" },
tdengine: { connection: "database" },
xugu: { database: "schema" },
iotdb: { deferred: "storage group/database semantics need dedicated IoTDB handling" },
etcd: { deferred: "key-value namespaces are not databases" },
zookeeper: { deferred: "key-value namespaces are not databases" },
iris: { database: "schema" },
influxdb: { connection: "database" },
influxdb3: { deferred: "database creation on InfluxDB 3.x goes through the /api/v3/configure/database admin endpoint, which is not part of the SQL execution path" },
victoriametrics: { deferred: "metric namespaces are managed by VictoriaMetrics deployment configuration" },
jdbc: { deferred: "generic JDBC does not expose a reliable dialect-specific create target" },
plugin: { deferred: "plugin-owned namespaces are managed by the provider workbench" },
mq: { deferred: "message queue namespaces are handled by MQ admin panels" },
nacos: { deferred: "Nacos namespace creation already uses the Nacos admin flow" },
consul: { deferred: "Consul namespaces and partitions are connection scopes, not KV resources" },
mqtt: { deferred: "MQTT topics are managed via the MQTT console" },
} satisfies Record<DatabaseType, DatabaseNamespaceCreationMatrixEntry>;
function namespaceCreationMatrixEntry(connection: NonNullable<CreationConnection>): DatabaseNamespaceCreationMatrixEntry {
// GBase 8s shares `db_type: "gbase"` with the MySQL-based GBase 8a but is Informix-derived:
// it has no `CREATE SCHEMA <name>` (a schema is the table owner) yet does support
// `CREATE DATABASE`. Route it to the Informix-family semantics instead of the shared `gbase`
// entry, which is written for GBase 8a.
if (connection.db_type === "gbase" && connection.driver_profile === "gbase8s") {
return { connection: "database" };
}
return DATABASE_NAMESPACE_CREATION_MATRIX[connection.db_type];
}
export function connectionNamespaceCreationTarget(connection: CreationConnection): ConnectionCreationTarget | null {
if (!connection || connectionIsEffectivelyReadOnly(connection)) return null;
if (connection.db_type === "sqlite" && (connection.host?.trim().toLowerCase() === ":memory:" || Boolean(connection.password))) {
return null;
}
const entry: DatabaseNamespaceCreationMatrixEntry = namespaceCreationMatrixEntry(connection);
return entry.connection ?? null;
}
export function databaseNodeNamespaceCreationTarget(connection: CreationConnection, node: Pick<{ type: TreeNodeType; database?: string | null }, "type" | "database">): DatabaseNodeCreationTarget | null {
if (!connection || connectionIsEffectivelyReadOnly(connection) || node.type !== "database" || !node.database) return null;
const entry: DatabaseNamespaceCreationMatrixEntry = namespaceCreationMatrixEntry(connection);
return entry.database ?? null;
}
export function canCreateConnectionNamespace(connection: CreationConnection): boolean {
return connectionNamespaceCreationTarget(connection) !== null;
}
export function canCreateDatabaseNodeNamespace(connection: CreationConnection, node: Pick<{ type: TreeNodeType; database?: string | null }, "type" | "database">): boolean {
return databaseNodeNamespaceCreationTarget(connection, node) !== null;
}