feat(d1): add Cloudflare D1 database support
This commit is contained in:
1 parent
977e7f78ca
commit
2a92746dc0
61 files changed
+1732
-69
No files matched your search
@@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import PasswordInput from "@/components/ui/PasswordInput.vue";
|
||||
|
||||
const accountId = defineModel<string>("accountId", { required: true });
|
||||
const databaseId = defineModel<string | undefined>("databaseId");
|
||||
const apiToken = defineModel<string>("apiToken", { required: true });
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="justify-self-start text-left">{{ t("connection.d1AccountId") }}</Label>
|
||||
<Input v-model="accountId" class="col-span-3" placeholder="023e105f4ecef8ad9ca31a8372d0c353" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="justify-self-start text-left">{{ t("connection.d1DatabaseId") }}</Label>
|
||||
<Input v-model="databaseId" class="col-span-3" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="justify-self-start text-left">{{ t("connection.d1ApiToken") }}</Label>
|
||||
<PasswordInput v-model="apiToken" class="col-span-3" placeholder="Cloudflare API Token" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-start gap-4">
|
||||
<span />
|
||||
<p class="col-span-3 text-xs text-muted-foreground">{{ t("connection.d1TokenHint") }}</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -50,9 +50,11 @@ import { buildDraftVisibleDatabasesConnectionId, connectionCanChooseVisibleDatab
|
||||
import { canSaveVisibleDatabaseSelection, connectionUsesVisibleSchemaFilter, filterDatabaseNamesForVisiblePicker, isSystemDatabaseName, normalizeVisibleDatabaseSelection, buildDraftVisibleSchemasConnectionId, normalizeVisibleSchemaSelection } from "@/lib/database/visibleDatabases";
|
||||
import { isSchemaAware, isSingleDatabase } from "@/lib/database/databaseFeatureSupport";
|
||||
import VisibleSchemasDialog from "@/components/sidebar/VisibleSchemasDialog.vue";
|
||||
import CloudflareD1ConnectionFields from "@/components/connection/CloudflareD1ConnectionFields.vue";
|
||||
import { oceanbaseModeConnectionPatch, oceanbaseSubModeFromConfig } from "@/lib/database/oceanbaseConnectionMode";
|
||||
import { translateBackendError } from "@/i18n/backend-errors";
|
||||
import { applyHiveKerberosSubmitConfig, hiveKerberosFormConfig, type HiveKerberosAuthMode } from "@/lib/database/hiveKerberosOptions";
|
||||
import { hasCloudflareD1Credentials, isCloudflareD1Connection, normalizeCloudflareD1Connection } from "@/lib/connection/cloudflareD1";
|
||||
|
||||
type DbOption = { value: string; label: string };
|
||||
type DbCategory = { key: string; title: string; options: DbOption[] };
|
||||
@@ -547,6 +549,7 @@ const driverProfiles: Record<
|
||||
sqlite: { type: "sqlite", port: 0, user: "", label: "SQLite", icon: "sqlite" },
|
||||
rqlite: { type: "rqlite", port: 4001, user: "", label: "RQLite", icon: "rqlite" },
|
||||
turso: { type: "turso", port: 443, user: "", label: "Turso", icon: "turso" },
|
||||
"cloudflare-d1": { type: "cloudflare-d1", port: 443, user: "", label: "Cloudflare D1", icon: "cloudflare-d1" },
|
||||
duckdb: { type: "duckdb", port: 0, user: "", label: "DuckDB", icon: "duckdb" },
|
||||
access: { type: "access", port: 0, user: "", label: "Microsoft Access", icon: "access" },
|
||||
mongodb: { type: "mongodb", port: 27017, user: "", label: "MongoDB", icon: "mongodb" },
|
||||
@@ -1635,6 +1638,7 @@ const iconTypeMap: Record<string, string> = {
|
||||
sqlite: "sqlite",
|
||||
rqlite: "rqlite",
|
||||
turso: "turso",
|
||||
"cloudflare-d1": "cloudflare-d1",
|
||||
access: "access",
|
||||
redis: "redis",
|
||||
mongodb: "mongodb",
|
||||
@@ -1724,6 +1728,7 @@ const dbOptions: DbOption[] = [
|
||||
{ value: "dm", label: "DM (Dameng)" },
|
||||
{ value: "opengauss", label: "openGauss" },
|
||||
{ value: "turso", label: "Turso" },
|
||||
{ value: "cloudflare-d1", label: "Cloudflare D1" },
|
||||
{ value: "duckdb", label: "DuckDB" },
|
||||
{ value: "rqlite", label: "RQLite" },
|
||||
{ value: "access", label: "Microsoft Access" },
|
||||
@@ -1915,7 +1920,7 @@ const zookeeperConnectString = computed({
|
||||
form.value.connection_string = normalizeZooKeeperConnectString(value);
|
||||
},
|
||||
});
|
||||
const canUseTransportLayers = computed(() => form.value.db_type !== "sqlite" && form.value.db_type !== "access" && !isH2FileMode.value);
|
||||
const canUseTransportLayers = computed(() => form.value.db_type !== "sqlite" && form.value.db_type !== "access" && !isCloudflareD1Connection(form.value) && !isH2FileMode.value);
|
||||
const shouldShowAgentDriverInstallHint = computed(() => showAgentDriverInstallHint(form.value.db_type, agentDrivers.value, form.value.driver_profile));
|
||||
const h2DriverMissing = computed(() => form.value.db_type === "h2" && isH2FileMode.value && agentDrivers.value.find((d) => d.db_type === "h2")?.installed !== true);
|
||||
const canChooseVisibleDatabases = computed(() => connectionCanChooseVisibleDatabases(form.value));
|
||||
@@ -2040,6 +2045,7 @@ const hasRequiredConnectionTarget = computed(() => {
|
||||
}
|
||||
if (form.value.db_type === "zookeeper") return !!(form.value.host || form.value.connection_string || connectionUrlInput.value.trim());
|
||||
if (form.value.db_type === "nacos") return !!nacosServerAddr.value.trim();
|
||||
if (isCloudflareD1Connection(form.value)) return hasCloudflareD1Credentials(form.value);
|
||||
if (isH2FileMode.value) return !!(form.value.host.trim() || h2FilePathFromJdbcUrl(form.value.connection_string));
|
||||
return !!(form.value.host || (mongoUseUrl.value && form.value.connection_string) || (form.value.db_type === "jdbc" && form.value.connection_string) || connectionUrlInput.value.trim());
|
||||
});
|
||||
@@ -2290,6 +2296,12 @@ function connectionConfigForSubmit(id: string): ConnectionConfig {
|
||||
throw new Error(t("connection.kingbaseDatabaseRequired"));
|
||||
}
|
||||
}
|
||||
if (isCloudflareD1Connection(config)) {
|
||||
normalizeCloudflareD1Connection(config);
|
||||
if (!hasCloudflareD1Credentials(config)) {
|
||||
throw new Error(t("connection.d1FieldsRequired"));
|
||||
}
|
||||
}
|
||||
config.transport_layers = (config.transport_layers || []).map(normalizeTransportLayer);
|
||||
config.transport_layers = config.transport_layers.map((layer) => {
|
||||
if (layer.type !== "ssh") return layer;
|
||||
@@ -4744,6 +4756,10 @@ function openExternalUrl(url: string) {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="form.db_type === 'cloudflare-d1'">
|
||||
<CloudflareD1ConnectionFields v-model:account-id="form.host" v-model:database-id="form.database" v-model:api-token="form.password" />
|
||||
</template>
|
||||
|
||||
<!-- MySQL / PostgreSQL: host, port, user, password, database -->
|
||||
<template v-else>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
|
||||
@@ -14,6 +14,7 @@ const assetIcons: Record<string, string> = {
|
||||
sqlite: "sqlite",
|
||||
rqlite: "rqlite.png",
|
||||
turso: "turso.png",
|
||||
cloudflare_d1: "cloudflare-d1",
|
||||
redis: "redis",
|
||||
mongodb: "mongodb",
|
||||
mongodb_legacy: "mongodb",
|
||||
|
||||
@@ -474,6 +474,8 @@ function connectionTooltipScheme(config: Pick<ConnectionConfig, "db_type" | "ssl
|
||||
case "turso":
|
||||
case "mq":
|
||||
return config.ssl ? "https" : "http";
|
||||
case "cloudflare-d1":
|
||||
return "https";
|
||||
case "dameng":
|
||||
return "dm";
|
||||
default:
|
||||
@@ -4825,7 +4827,7 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
||||
items.push({ label: t("contextMenu.newQuery"), action: newQuery, icon: TerminalSquare });
|
||||
const sqlHistoryMenu = savedSqlHistorySubmenu();
|
||||
if (sqlHistoryMenu) items.push(sqlHistoryMenu);
|
||||
if (node.type === "database") {
|
||||
if (node.type === "database" && currentDatabaseType() !== "cloudflare-d1") {
|
||||
if (!isNodeDefaultDatabase.value) {
|
||||
items.push({ label: t("contextMenu.setDefaultDatabase"), action: setNodeAsDefaultDatabase, icon: Database });
|
||||
} else {
|
||||
|
||||
@@ -198,6 +198,11 @@ export default {
|
||||
driverName: "Driver Name",
|
||||
driverNamePlaceholder: "Vendor or environment name",
|
||||
urlParams: "URL Params",
|
||||
d1AccountId: "Account ID",
|
||||
d1DatabaseId: "Database ID",
|
||||
d1ApiToken: "API Token",
|
||||
d1TokenHint: "The token needs D1 Read permission; writes, DDL, and imports also require D1 Write.",
|
||||
d1FieldsRequired: "Cloudflare Account ID, D1 Database ID, and API Token are required.",
|
||||
hiveAuthMode: "Auth",
|
||||
hiveAuthNone: "None",
|
||||
hivePrincipal: "Principal",
|
||||
|
||||
@@ -200,6 +200,11 @@ export default withEnglishFallback({
|
||||
driverName: "Nombre del driver",
|
||||
driverNamePlaceholder: "Nombre del proveedor o entorno",
|
||||
urlParams: "Parámetros de URL",
|
||||
d1AccountId: "ID de cuenta",
|
||||
d1DatabaseId: "ID de base de datos",
|
||||
d1ApiToken: "Token de API",
|
||||
d1TokenHint: "El token necesita al menos el permiso D1 Read; las escrituras, el DDL y las importaciones también requieren D1 Write.",
|
||||
d1FieldsRequired: "Se requieren el ID de cuenta de Cloudflare, el ID de la base de datos D1 y el token de API.",
|
||||
gbaseServer: "GBASEDBTSERVER",
|
||||
informixServer: "INFORMIXSERVER",
|
||||
sslEnable: "Habilitar conexión cifrada",
|
||||
|
||||
@@ -199,6 +199,11 @@ export default withEnglishFallback({
|
||||
driverName: "Nome Driver",
|
||||
driverNamePlaceholder: "Nome del fornitore o dell'ambiente",
|
||||
urlParams: "Parametri URL",
|
||||
d1AccountId: "ID account",
|
||||
d1DatabaseId: "ID database",
|
||||
d1ApiToken: "Token API",
|
||||
d1TokenHint: "Il token richiede almeno l'autorizzazione D1 Read; per scritture, DDL e importazioni è necessaria anche D1 Write.",
|
||||
d1FieldsRequired: "Sono obbligatori l'ID account Cloudflare, l'ID database D1 e il token API.",
|
||||
gbaseServer: "GBASEDBTSERVER",
|
||||
informixServer: "INFORMIXSERVER",
|
||||
sslEnable: "Abilita connessione crittografata",
|
||||
|
||||
@@ -199,6 +199,11 @@ export default withEnglishFallback({
|
||||
driverName: "ドライバー名",
|
||||
driverNamePlaceholder: "ベンダー名または環境名",
|
||||
urlParams: "URLパラメータ",
|
||||
d1AccountId: "アカウントID",
|
||||
d1DatabaseId: "データベースID",
|
||||
d1ApiToken: "APIトークン",
|
||||
d1TokenHint: "トークンには少なくともD1 Read権限が必要です。書き込み、DDL、インポートにはD1 Write権限も必要です。",
|
||||
d1FieldsRequired: "CloudflareアカウントID、D1データベースID、APIトークンは必須です。",
|
||||
gbaseServer: "GBASEDBTSERVER",
|
||||
informixServer: "INFORMIXSERVER",
|
||||
sslEnable: "暗号化接続を有効にする",
|
||||
|
||||
@@ -200,6 +200,11 @@ export default withEnglishFallback({
|
||||
driverName: "Nome do Driver",
|
||||
driverNamePlaceholder: "Nome do fornecedor ou ambiente",
|
||||
urlParams: "Parâmetros da URL",
|
||||
d1AccountId: "ID da conta",
|
||||
d1DatabaseId: "ID do banco de dados",
|
||||
d1ApiToken: "Token da API",
|
||||
d1TokenHint: "O token precisa de pelo menos a permissão D1 Read; gravações, DDL e importações também exigem D1 Write.",
|
||||
d1FieldsRequired: "O ID da conta Cloudflare, o ID do banco de dados D1 e o token da API são obrigatórios.",
|
||||
gbaseServer: "GBASEDBTSERVER",
|
||||
informixServer: "INFORMIXSERVER",
|
||||
sslEnable: "Habilitar conexão criptografada",
|
||||
|
||||
@@ -200,6 +200,11 @@ export default withEnglishFallback({
|
||||
driverName: "驱动名称",
|
||||
driverNamePlaceholder: "厂商或环境名称",
|
||||
urlParams: "URL 参数",
|
||||
d1AccountId: "Account ID",
|
||||
d1DatabaseId: "Database ID",
|
||||
d1ApiToken: "API Token",
|
||||
d1TokenHint: "Token 至少需要 D1 Read 权限;执行写入、DDL 或导入时还需要 D1 Write 权限。",
|
||||
d1FieldsRequired: "必须填写 Cloudflare Account ID、D1 Database ID 和 API Token。",
|
||||
hiveAuthMode: "认证",
|
||||
hiveAuthNone: "无",
|
||||
hivePrincipal: "Principal",
|
||||
|
||||
@@ -200,6 +200,11 @@ export default withEnglishFallback({
|
||||
driverName: "驅動程式名稱",
|
||||
driverNamePlaceholder: "廠商或環境名稱",
|
||||
urlParams: "URL 參數",
|
||||
d1AccountId: "帳戶 ID",
|
||||
d1DatabaseId: "資料庫 ID",
|
||||
d1ApiToken: "API Token",
|
||||
d1TokenHint: "Token 至少需要 D1 Read 權限;執行寫入、DDL 或匯入時還需要 D1 Write 權限。",
|
||||
d1FieldsRequired: "必須填寫 Cloudflare Account ID、D1 Database ID 和 API Token。",
|
||||
gbaseServer: "GBASEDBTSERVER",
|
||||
informixServer: "INFORMIXSERVER",
|
||||
sslEnable: "啟用加密連線",
|
||||
|
||||
@@ -16,6 +16,7 @@ describe("supportsTransaction", () => {
|
||||
expect(supportsTransaction("duckdb")).toBe(false);
|
||||
expect(supportsTransaction("qdrant")).toBe(false);
|
||||
expect(supportsTransaction("turso")).toBe(false);
|
||||
expect(supportsTransaction("cloudflare-d1")).toBe(false);
|
||||
expect(supportsTransaction("sqlite")).toBe(false);
|
||||
expect(supportsTransaction("clickhouse")).toBe(false);
|
||||
expect(supportsTransaction("sqlserver")).toBe(false);
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ describe("sqlFormatter", () => {
|
||||
});
|
||||
|
||||
it("maps SQLite-compatible database types to the sqlite formatter dialect", () => {
|
||||
for (const dbType of ["sqlite", "rqlite", "turso"]) {
|
||||
for (const dbType of ["sqlite", "rqlite", "turso", "cloudflare-d1"]) {
|
||||
expect(sqlFormatDialectForDbType(dbType)).toBe("sqlite");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { ConnectionConfig } from "@/types/database";
|
||||
|
||||
type CloudflareD1Config = Pick<ConnectionConfig, "db_type" | "host" | "database" | "password">;
|
||||
type MutableCloudflareD1Config = CloudflareD1Config & Pick<ConnectionConfig, "port" | "username" | "ssl" | "url_params" | "transport_layers">;
|
||||
|
||||
export function isCloudflareD1Connection(config: Pick<ConnectionConfig, "db_type">): boolean {
|
||||
return config.db_type === "cloudflare-d1";
|
||||
}
|
||||
|
||||
export function hasCloudflareD1Credentials(config: CloudflareD1Config): boolean {
|
||||
return !!config.host.trim() && !!config.database?.trim() && !!config.password.trim();
|
||||
}
|
||||
|
||||
export function normalizeCloudflareD1Connection(config: MutableCloudflareD1Config): void {
|
||||
config.host = config.host.trim();
|
||||
config.database = config.database?.trim() || undefined;
|
||||
config.password = config.password.trim();
|
||||
config.port = 443;
|
||||
config.username = "";
|
||||
config.ssl = true;
|
||||
config.url_params = "";
|
||||
config.transport_layers = [];
|
||||
}
|
||||
@@ -17,6 +17,7 @@ export function connectionDriverLabel(connection?: Pick<ConnectionConfig, "db_ty
|
||||
|
||||
export function connectionEndpointLabel(connection?: ConnectionPresentationConfig): string {
|
||||
if (!connection) return "";
|
||||
if (connection.db_type === "cloudflare-d1") return [connection.host, connection.database].filter(Boolean).join("/");
|
||||
if (LOCAL_DATABASE_TYPES.has(connection.db_type) || (connection.db_type === "h2" && connection.port === 0)) {
|
||||
return connection.host || connection.database || "local";
|
||||
}
|
||||
@@ -45,6 +46,7 @@ function redactConnectionHost(host: string): string {
|
||||
|
||||
export function connectionRedactedEndpointLabel(connection?: ConnectionPresentationConfig): string {
|
||||
if (!connection) return "";
|
||||
if (connection.db_type === "cloudflare-d1") return `${REDACTED_HOST_SEGMENT}/${REDACTED_HOST_SEGMENT}`;
|
||||
if (LOCAL_DATABASE_TYPES.has(connection.db_type) || (connection.db_type === "h2" && connection.port === 0)) {
|
||||
return connectionEndpointLabel(connection);
|
||||
}
|
||||
@@ -111,6 +113,9 @@ export function connectionUrlPlaceholder(dbType: DatabaseType): string {
|
||||
case "turso":
|
||||
return "https://[your-db]-[org].turso.io";
|
||||
|
||||
case "cloudflare-d1":
|
||||
return "https://api.cloudflare.com/client/v4/accounts/{account_id}/d1/database/{database_id}";
|
||||
|
||||
case "duckdb":
|
||||
return "duckdb:///absolute/path/to/database.duckdb";
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { filterDatabaseNamesForVisiblePicker, normalizeVisibleDatabaseSelection
|
||||
|
||||
const DRAFT_VISIBLE_DATABASES_PREFIX = "__visible_draft_";
|
||||
|
||||
const UNSUPPORTED_VISIBLE_DATABASE_TYPES = new Set<DatabaseType>(["elasticsearch", "qdrant", "milvus", "weaviate", "chromadb", "etcd", "zookeeper"]);
|
||||
const UNSUPPORTED_VISIBLE_DATABASE_TYPES = new Set<DatabaseType>(["cloudflare-d1", "elasticsearch", "qdrant", "milvus", "weaviate", "chromadb", "etcd", "zookeeper"]);
|
||||
|
||||
type VisibleDatabaseConnectionFields = Pick<
|
||||
ConnectionConfig,
|
||||
|
||||
@@ -75,4 +75,4 @@ export const DATABASE_OBJECT_TREE_TYPES = new Set<DatabaseType>(["jdbc"]);
|
||||
|
||||
export const PG_LIKE_STRUCTURE_TYPES = new Set<DatabaseType>(["postgres", "redshift", "gaussdb", "kwdb", "opengauss", "questdb"]);
|
||||
|
||||
export const DIAGRAM_SQL_TYPES = new Set<DatabaseType>(["mysql", "postgres", "sqlite", "rqlite", "turso", "sqlserver", "oracle", "redshift", "dameng", "gaussdb", "kwdb", "opengauss", "questdb", "oceanbase-oracle"]);
|
||||
export const DIAGRAM_SQL_TYPES = new Set<DatabaseType>(["mysql", "postgres", "sqlite", "rqlite", "turso", "cloudflare-d1", "sqlserver", "oracle", "redshift", "dameng", "gaussdb", "kwdb", "opengauss", "questdb", "oceanbase-oracle"]);
|
||||
@@ -118,7 +118,7 @@ export function supportsObjectBrowserTreeNode(dbType: DatabaseType | undefined,
|
||||
}
|
||||
|
||||
export function supportsTableTruncate(dbType?: DatabaseType): boolean {
|
||||
return !!dbType && dbType !== "sqlite" && dbType !== "rqlite" && dbType !== "turso" && dbType !== "duckdb" && dbType !== "influxdb" && dbType !== "manticoresearch";
|
||||
return !!dbType && dbType !== "sqlite" && dbType !== "rqlite" && dbType !== "turso" && dbType !== "cloudflare-d1" && dbType !== "duckdb" && dbType !== "influxdb" && dbType !== "manticoresearch";
|
||||
}
|
||||
|
||||
export function usesPostgresLikeStructureCopy(dbType?: DatabaseType): boolean {
|
||||
|
||||
@@ -20,6 +20,7 @@ export const DATABASE_NAMESPACE_CREATION_MATRIX = {
|
||||
sqlite: { deferred: "file-backed; create a new connection/file instead" },
|
||||
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" },
|
||||
|
||||
@@ -35,6 +35,7 @@ const DATABASE_TYPE_OBJECTS = new Map<DatabaseType, SidebarObjectKind[]>([
|
||||
["sqlite", TABLE_VIEW_OBJECTS],
|
||||
["rqlite", TABLE_VIEW_OBJECTS],
|
||||
["turso", TABLE_VIEW_OBJECTS],
|
||||
["cloudflare-d1", TABLE_VIEW_OBJECTS],
|
||||
["duckdb", TABLE_VIEW_OBJECTS],
|
||||
["clickhouse", TABLE_VIEW_OBJECTS],
|
||||
["doris", TABLE_VIEW_OBJECTS],
|
||||
|
||||
@@ -21,6 +21,7 @@ export const DATABASE_PROPERTY_EDITING_MATRIX = {
|
||||
sqlite: { deferred: "file-backed database properties are not edited in-place" },
|
||||
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: { deferred: "attached database file properties need a dedicated DuckDB workflow" },
|
||||
clickhouse: { deferred: "database property editing not verified for first pass" },
|
||||
|
||||
@@ -52,6 +52,7 @@ const NAVICAT_STYLE_TABLE_DATA_TYPES = new Set<DatabaseType>([
|
||||
"sqlite",
|
||||
"rqlite",
|
||||
"turso",
|
||||
"cloudflare-d1",
|
||||
"duckdb",
|
||||
"sqlserver",
|
||||
"oracle",
|
||||
|
||||
@@ -4,7 +4,8 @@ import { usesTreeSchemaMode } from "@/lib/database/databaseCapabilities";
|
||||
export const TREE_SCHEMA_DEFAULT_DATABASE_SELECT_VALUE = "__dbx_tree_schema_default_database__";
|
||||
export const EMPTY_DATABASE_SELECT_VALUE = "__dbx_empty_database__";
|
||||
|
||||
export function resolveDefaultDatabase(connection: Pick<ConnectionConfig, "database">, options: string[]): string {
|
||||
export function resolveDefaultDatabase(connection: Pick<ConnectionConfig, "database"> & Partial<Pick<ConnectionConfig, "db_type">>, options: string[]): string {
|
||||
if (connection.db_type === "cloudflare-d1") return "main";
|
||||
return connection.database || options[0] || "";
|
||||
}
|
||||
|
||||
@@ -29,6 +30,7 @@ export function formatDatabaseLabel(connection: Pick<ConnectionConfig, "db_type"
|
||||
return database || labels.noDatabase;
|
||||
}
|
||||
|
||||
export function isDefaultDatabase(connection: Pick<ConnectionConfig, "database"> | undefined, database: string): boolean {
|
||||
export function isDefaultDatabase(connection: (Pick<ConnectionConfig, "database"> & Partial<Pick<ConnectionConfig, "db_type">>) | undefined, database: string): boolean {
|
||||
if (connection?.db_type === "cloudflare-d1") return database === "main";
|
||||
return !!connection?.database && !!database && connection.database === database;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const CLOUDFLARE_D1_COMMON_FUNCTION_NAMES = new Set(["ABS", "AVG", "CAST", "CEIL", "COALESCE", "CONCAT", "COUNT", "FLOOR", "LENGTH", "LOWER", "LTRIM", "MAX", "MIN", "MOD", "NULLIF", "REPLACE", "ROUND", "RTRIM", "SIGN", "SQRT", "SUBSTR", "SUBSTRING", "SUM", "TRIM", "UPPER"]);
|
||||
@@ -155,6 +155,7 @@ export function sqlSemanticDialectFor(options: { databaseType?: DatabaseType; di
|
||||
case "sqlite":
|
||||
case "rqlite":
|
||||
case "turso":
|
||||
case "cloudflare-d1":
|
||||
return SQL_SEMANTIC_DIALECTS.sqlite;
|
||||
case "duckdb":
|
||||
return SQL_SEMANTIC_DIALECTS.duckdb;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Cassandra, MariaSQL, MSSQL, MySQL, PLSQL, PostgreSQL, SQLite, StandardSQL } from "@codemirror/lang-sql";
|
||||
import type { DatabaseType, SqlSnippet } from "@/types/database";
|
||||
import { buildMongoCompletionItemsFromContext, type MongoCompletionItem } from "@/lib/mongo/mongoCompletion";
|
||||
import { CLOUDFLARE_D1_COMMON_FUNCTION_NAMES } from "@/lib/sql/cloudflareD1";
|
||||
|
||||
const SQL_KEYWORDS = [
|
||||
"SELECT",
|
||||
@@ -470,6 +471,7 @@ const DATABASE_SQL_KEYWORDS: Partial<Record<DatabaseType, string[]>> = {
|
||||
sqlite: SQLITE_SQL_KEYWORDS,
|
||||
rqlite: SQLITE_SQL_KEYWORDS,
|
||||
turso: SQLITE_SQL_KEYWORDS,
|
||||
"cloudflare-d1": SQLITE_SQL_KEYWORDS,
|
||||
sqlserver: SQLSERVER_SQL_KEYWORDS,
|
||||
manticoresearch: MANTICORESEARCH_SQL_KEYWORDS,
|
||||
};
|
||||
@@ -896,6 +898,8 @@ const SQLITE_FUNCTION_SIGNATURES = new Map<string, string[]>([
|
||||
["NOW", []],
|
||||
]);
|
||||
|
||||
const CLOUDFLARE_D1_FUNCTION_SIGNATURES = new Map(Array.from(SQLITE_FUNCTION_SIGNATURES.entries()).filter(([name]) => name !== "NOW"));
|
||||
|
||||
const SQLSERVER_FUNCTION_SIGNATURES = new Map<string, string[]>([
|
||||
["TRY_CAST", ["expression AS type"]],
|
||||
["TRY_CONVERT", ["type", "expression"]],
|
||||
@@ -958,6 +962,7 @@ const DATABASE_FUNCTION_SIGNATURES: Partial<Record<DatabaseType, Map<string, str
|
||||
sqlite: SQLITE_FUNCTION_SIGNATURES,
|
||||
rqlite: SQLITE_FUNCTION_SIGNATURES,
|
||||
turso: SQLITE_FUNCTION_SIGNATURES,
|
||||
"cloudflare-d1": CLOUDFLARE_D1_FUNCTION_SIGNATURES,
|
||||
sqlserver: SQLSERVER_FUNCTION_SIGNATURES,
|
||||
manticoresearch: MANTICORESEARCH_FUNCTION_SIGNATURES,
|
||||
};
|
||||
@@ -3727,7 +3732,8 @@ function buildSnippetItems(prefix: string, snippets: SqlSnippet[], keywordCase?:
|
||||
}
|
||||
|
||||
function activeFunctionSignatures(databaseType?: DatabaseType): Map<string, string[]> {
|
||||
const signatures = databaseType ? new Map(Array.from(SQL_FUNCTION_SIGNATURES.entries()).filter(([name]) => COMMON_SQL_FUNCTION_NAMES.has(name))) : new Map(SQL_FUNCTION_SIGNATURES);
|
||||
const commonFunctionNames = databaseType === "cloudflare-d1" ? CLOUDFLARE_D1_COMMON_FUNCTION_NAMES : COMMON_SQL_FUNCTION_NAMES;
|
||||
const signatures = databaseType ? new Map(Array.from(SQL_FUNCTION_SIGNATURES.entries()).filter(([name]) => commonFunctionNames.has(name))) : new Map(SQL_FUNCTION_SIGNATURES);
|
||||
const databaseSignatures = databaseType ? DATABASE_FUNCTION_SIGNATURES[databaseType] : undefined;
|
||||
if (databaseSignatures) {
|
||||
for (const [name, parameters] of databaseSignatures) signatures.set(name, parameters);
|
||||
|
||||
@@ -30,6 +30,7 @@ export function sqlFormatDialectForDbType(dbType: string | null | undefined): Sq
|
||||
case "sqlite":
|
||||
case "rqlite":
|
||||
case "turso":
|
||||
case "cloudflare-d1":
|
||||
return "sqlite";
|
||||
case "sqlserver":
|
||||
return "sqlserver";
|
||||
|
||||
@@ -21,7 +21,7 @@ export function supportsObjectRename(databaseType: DatabaseType | undefined, obj
|
||||
if (objectType === "PROCEDURE" || objectType === "FUNCTION") {
|
||||
return false;
|
||||
}
|
||||
if (databaseType === "sqlite" || databaseType === "rqlite" || databaseType === "turso") return objectType === "TABLE";
|
||||
if (databaseType === "sqlite" || databaseType === "rqlite" || databaseType === "turso" || databaseType === "cloudflare-d1") return objectType === "TABLE";
|
||||
if (databaseType === "mysql" || databaseType === "goldendb") return objectType === "TABLE" || objectType === "VIEW";
|
||||
if (postgresLikeRenameTypes.has(databaseType)) return objectType === "TABLE" || objectType === "VIEW" || objectType === "MATERIALIZED_VIEW";
|
||||
if (oracleLikeRenameTypes.has(databaseType)) return objectType === "TABLE" || objectType === "VIEW" || objectType === "MATERIALIZED_VIEW";
|
||||
|
||||
@@ -151,29 +151,29 @@ export function importDataTypeForDatabase(inferredType: ImportInferredType, data
|
||||
case "boolean":
|
||||
if (["mysql", "doris", "starrocks", "goldendb", "sundb", "databend"].includes(databaseType || "")) return "TINYINT(1)";
|
||||
if (databaseType === "sqlserver") return "BIT";
|
||||
if (databaseType === "sqlite" || databaseType === "rqlite" || databaseType === "turso") return "INTEGER";
|
||||
if (databaseType === "sqlite" || databaseType === "rqlite" || databaseType === "turso" || databaseType === "cloudflare-d1") return "INTEGER";
|
||||
if (databaseType === "oracle" || databaseType === "oceanbase-oracle" || databaseType === "dameng") return "NUMBER(1)";
|
||||
if (databaseType === "clickhouse") return "UInt8";
|
||||
return "BOOLEAN";
|
||||
case "integer":
|
||||
if (databaseType === "sqlite" || databaseType === "rqlite" || databaseType === "turso") return "INTEGER";
|
||||
if (databaseType === "sqlite" || databaseType === "rqlite" || databaseType === "turso" || databaseType === "cloudflare-d1") return "INTEGER";
|
||||
if (databaseType === "oracle" || databaseType === "oceanbase-oracle" || databaseType === "dameng") return "NUMBER(19)";
|
||||
if (databaseType === "clickhouse") return "Int64";
|
||||
return "BIGINT";
|
||||
case "decimal":
|
||||
if (["postgres", "gaussdb", "opengauss", "redshift", "kingbase", "highgo", "kwdb", "vastbase"].includes(databaseType || "")) return "DOUBLE PRECISION";
|
||||
if (databaseType === "sqlite" || databaseType === "rqlite" || databaseType === "turso") return "REAL";
|
||||
if (databaseType === "sqlite" || databaseType === "rqlite" || databaseType === "turso" || databaseType === "cloudflare-d1") return "REAL";
|
||||
if (databaseType === "oracle" || databaseType === "oceanbase-oracle" || databaseType === "dameng") return "BINARY_DOUBLE";
|
||||
if (databaseType === "clickhouse") return "Float64";
|
||||
return "DOUBLE";
|
||||
case "date":
|
||||
if (databaseType === "sqlite" || databaseType === "rqlite" || databaseType === "turso") return "TEXT";
|
||||
if (databaseType === "sqlite" || databaseType === "rqlite" || databaseType === "turso" || databaseType === "cloudflare-d1") return "TEXT";
|
||||
if (databaseType === "clickhouse") return "Date";
|
||||
return "DATE";
|
||||
case "timestamp":
|
||||
if (["mysql", "doris", "starrocks", "goldendb", "sundb", "databend"].includes(databaseType || "")) return "DATETIME";
|
||||
if (databaseType === "sqlserver") return "DATETIME2";
|
||||
if (databaseType === "sqlite" || databaseType === "rqlite" || databaseType === "turso") return "TEXT";
|
||||
if (databaseType === "sqlite" || databaseType === "rqlite" || databaseType === "turso" || databaseType === "cloudflare-d1") return "TEXT";
|
||||
if (databaseType === "clickhouse") return "DateTime64";
|
||||
return "TIMESTAMP";
|
||||
case "json":
|
||||
|
||||
@@ -129,7 +129,7 @@ function columnPlaceholderValue(column: ColumnInfo, databaseType?: DatabaseType)
|
||||
const colName = column.name;
|
||||
|
||||
if (typeLooksNumeric(dataType)) return "0";
|
||||
if (typeLooksBoolean(dataType)) return databaseType === "mysql" || databaseType === "sqlite" || databaseType === "rqlite" ? "1" : "TRUE";
|
||||
if (typeLooksBoolean(dataType)) return databaseType === "mysql" || databaseType === "sqlite" || databaseType === "rqlite" || databaseType === "cloudflare-d1" ? "1" : "TRUE";
|
||||
if (typeLooksDateOnly(dataType)) return "'2024-01-01'";
|
||||
if (typeLooksTimeOnly(dataType)) return "'12:00:00'";
|
||||
if (typeLooksTimestamp(dataType)) return databaseType === "tdengine" ? "NOW" : "CURRENT_TIMESTAMP";
|
||||
|
||||
@@ -1692,6 +1692,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
|
||||
async function setDefaultDatabase(connectionId: string, database: string) {
|
||||
const config = getConfig(connectionId);
|
||||
if (config?.db_type === "cloudflare-d1") return;
|
||||
if (!config || config.database === database) return;
|
||||
await updateConnection({
|
||||
...config,
|
||||
@@ -1701,6 +1702,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
|
||||
async function clearDefaultDatabase(connectionId: string) {
|
||||
const config = getConfig(connectionId);
|
||||
if (config?.db_type === "cloudflare-d1") return;
|
||||
if (!config || !config.database) return;
|
||||
await updateConnection({
|
||||
...config,
|
||||
@@ -1709,7 +1711,9 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
}
|
||||
|
||||
function isDefaultDatabase(connectionId: string, database: string): boolean {
|
||||
return getConfig(connectionId)?.database === database && database !== "";
|
||||
const config = getConfig(connectionId);
|
||||
if (config?.db_type === "cloudflare-d1") return database === "main";
|
||||
return config?.database === database && database !== "";
|
||||
}
|
||||
|
||||
async function setVisibleDatabases(connectionId: string, databaseNames: string[]) {
|
||||
|
||||
@@ -4,6 +4,7 @@ export type DatabaseType =
|
||||
| "sqlite"
|
||||
| "rqlite"
|
||||
| "turso"
|
||||
| "cloudflare-d1"
|
||||
| "redis"
|
||||
| "duckdb"
|
||||
| "clickhouse"
|
||||
|
||||
Reference in new issue
Block a user