143 lines
8.9 KiB
TypeScript
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;
|
|
}
|