feat(vector): add weaviate support
This commit is contained in:
parent
09cdf07aab
commit
bf9ef4365b
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
|
|
@ -1040,7 +1040,7 @@ async function handleQuickOpenSelect(item: any) {
|
|||
await connectionStore.loadMongoDatabases(item.connectionId);
|
||||
} else if (config?.db_type === "elasticsearch") {
|
||||
await connectionStore.loadElasticsearchIndices(item.connectionId);
|
||||
} else if (config?.db_type === "qdrant" || config?.db_type === "milvus") {
|
||||
} else if (config?.db_type === "qdrant" || config?.db_type === "milvus" || config?.db_type === "weaviate") {
|
||||
await connectionStore.loadVectorCollections(item.connectionId);
|
||||
} else if (config?.db_type === "mq") {
|
||||
await connectionStore.loadMqTenants(item.connectionId);
|
||||
|
|
@ -1063,7 +1063,7 @@ async function handleQuickOpenSelect(item: any) {
|
|||
await connectionStore.loadMongoDatabases(item.connectionId);
|
||||
} else if (config?.db_type === "elasticsearch") {
|
||||
await connectionStore.loadElasticsearchIndices(item.connectionId);
|
||||
} else if (config?.db_type === "qdrant" || config?.db_type === "milvus") {
|
||||
} else if (config?.db_type === "qdrant" || config?.db_type === "milvus" || config?.db_type === "weaviate") {
|
||||
await connectionStore.loadVectorCollections(item.connectionId);
|
||||
} else if (config?.db_type === "mq") {
|
||||
await connectionStore.loadMqTenants(item.connectionId);
|
||||
|
|
|
|||
|
|
@ -426,6 +426,7 @@ const driverProfiles: Record<
|
|||
},
|
||||
qdrant: { type: "qdrant", port: 6333, user: "", label: "Qdrant", icon: "qdrant" },
|
||||
milvus: { type: "milvus", port: 19530, user: "root", label: "Milvus", icon: "milvus" },
|
||||
weaviate: { type: "weaviate", port: 8080, user: "", label: "Weaviate", icon: "weaviate" },
|
||||
mariadb: { type: "mysql", port: 3306, user: "root", label: "MariaDB", icon: "mariadb" },
|
||||
tidb: { type: "mysql", port: 4000, user: "root", label: "TiDB", icon: "tidb" },
|
||||
oceanbase: { type: "mysql", port: 2881, user: "root", label: "OceanBase", icon: "oceanbase" },
|
||||
|
|
@ -1006,6 +1007,7 @@ const iconTypeMap: Record<string, string> = {
|
|||
elasticsearch: "elasticsearch",
|
||||
qdrant: "qdrant",
|
||||
milvus: "milvus",
|
||||
weaviate: "weaviate",
|
||||
mariadb: "mariadb",
|
||||
tidb: "tidb",
|
||||
oceanbase: "oceanbase",
|
||||
|
|
@ -1073,6 +1075,7 @@ const dbOptions: DbOption[] = [
|
|||
{ value: "elasticsearch", label: "Elasticsearch" },
|
||||
{ value: "qdrant", label: "Qdrant" },
|
||||
{ value: "milvus", label: "Milvus" },
|
||||
{ value: "weaviate", label: "Weaviate" },
|
||||
{ value: "dm", label: "DM (Dameng)" },
|
||||
{ value: "opengauss", label: "openGauss" },
|
||||
{ value: "turso", label: "Turso" },
|
||||
|
|
@ -1178,7 +1181,7 @@ const sqliteExtensionPaths = computed({
|
|||
form.value.url_params = setSqliteExtensionPaths(form.value.url_params, value);
|
||||
},
|
||||
});
|
||||
const tlsCapableDatabaseTypes = new Set<DatabaseType>(["mysql", "postgres", "redshift", "gaussdb", "kwdb", "opengauss", "questdb", "redis", "etcd", "clickhouse", "elasticsearch", "qdrant", "milvus", "influxdb"]);
|
||||
const tlsCapableDatabaseTypes = new Set<DatabaseType>(["mysql", "postgres", "redshift", "gaussdb", "kwdb", "opengauss", "questdb", "redis", "etcd", "clickhouse", "elasticsearch", "qdrant", "milvus", "weaviate", "influxdb"]);
|
||||
const supportsTlsToggle = computed(() => tlsCapableDatabaseTypes.has(form.value.db_type));
|
||||
const supportsCaCertificatePath = computed(() => form.value.db_type === "clickhouse");
|
||||
const supportsGenericUrlParams = computed(() => form.value.db_type !== "manticoresearch");
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ const showModified = ref(true);
|
|||
|
||||
let syncPlanRequestId = 0;
|
||||
|
||||
const sqlConnections = computed(() => store.connections.filter((connection) => !["redis", "mongodb", "elasticsearch", "qdrant", "milvus", "etcd", "mq", "nacos"].includes(connection.db_type)));
|
||||
const sqlConnections = computed(() => store.connections.filter((connection) => !["redis", "mongodb", "elasticsearch", "qdrant", "milvus", "weaviate", "etcd", "mq", "nacos"].includes(connection.db_type)));
|
||||
const selectedSourceTableNames = computed(() => sourceTables.value.filter((table) => selectedSourceTables.value.has(table)));
|
||||
const isBatchCompare = computed(() => selectedSourceTableNames.value.length > 1);
|
||||
const filteredSourceTables = computed(() => {
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ const exportCancelled = ref(false);
|
|||
const pendingPrefillTable = ref("");
|
||||
const pendingPrefillTables = ref<string[]>([]);
|
||||
|
||||
const sqlConnections = computed(() => store.connections.filter((c) => !["redis", "mongodb", "elasticsearch", "qdrant", "milvus", "etcd", "mq", "nacos"].includes(c.db_type)));
|
||||
const sqlConnections = computed(() => store.connections.filter((c) => !["redis", "mongodb", "elasticsearch", "qdrant", "milvus", "weaviate", "etcd", "mq", "nacos"].includes(c.db_type)));
|
||||
|
||||
const canExport = computed(() => connectionId.value && database.value && schema.value && !loadingTables.value && !tableError.value && (tables.value.length === 0 || selectedTables.value.length > 0) && (includeStructure.value || includeData.value || includeObjects.value) && !isExporting.value);
|
||||
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ const assetIcons: Record<string, string> = {
|
|||
etcd: "etcd",
|
||||
qdrant: "qdrant",
|
||||
milvus: "milvus.png",
|
||||
weaviate: "weaviate.png",
|
||||
mq: "pulsar",
|
||||
pulsar: "pulsar",
|
||||
nacos: "nacos.png",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ const activeConnectionValue = computed(() => props.activeConnection?.id || "");
|
|||
const activeSchemaValue = computed(() => props.activeTab.schema || "");
|
||||
const supportsExplain = computed(() => {
|
||||
const dbType = props.activeConnection?.db_type;
|
||||
return dbType !== "redis" && dbType !== "mongodb" && dbType !== "elasticsearch" && dbType !== "qdrant" && dbType !== "milvus" && dbType !== "etcd" && dbType !== "mq" && dbType !== "nacos";
|
||||
return dbType !== "redis" && dbType !== "mongodb" && dbType !== "elasticsearch" && dbType !== "qdrant" && dbType !== "milvus" && dbType !== "weaviate" && dbType !== "etcd" && dbType !== "mq" && dbType !== "nacos";
|
||||
});
|
||||
const isSingleDb = computed(() => isSingleDatabase(props.activeConnection?.db_type));
|
||||
const hasDefaultDatabaseOption = computed(() => activeDatabaseOptions.value.includes(""));
|
||||
|
|
@ -251,7 +251,7 @@ function connectionById(connectionId: string): ConnectionConfig | undefined {
|
|||
</template>
|
||||
</SearchableSelect>
|
||||
</div>
|
||||
<div v-if="activeConnection?.db_type !== 'elasticsearch' && activeConnection?.db_type !== 'qdrant' && activeConnection?.db_type !== 'milvus' && !isSingleDb" class="flex items-center gap-1">
|
||||
<div v-if="activeConnection?.db_type !== 'elasticsearch' && activeConnection?.db_type !== 'qdrant' && activeConnection?.db_type !== 'milvus' && activeConnection?.db_type !== 'weaviate' && !isSingleDb" class="flex items-center gap-1">
|
||||
<Database class="h-3.5 w-3.5 shrink-0" />
|
||||
<SearchableSelect
|
||||
:model-value="activeDatabaseValue"
|
||||
|
|
|
|||
|
|
@ -402,7 +402,7 @@ async function ensureTreeLoadedForTarget(target: ActiveTabSidebarTarget, opts?:
|
|||
await store.loadMongoDatabases(connId);
|
||||
} else if (config.db_type === "elasticsearch") {
|
||||
await store.loadElasticsearchIndices(connId);
|
||||
} else if (config.db_type === "qdrant" || config.db_type === "milvus") {
|
||||
} else if (config.db_type === "qdrant" || config.db_type === "milvus" || config.db_type === "weaviate") {
|
||||
await store.loadVectorCollections(connId);
|
||||
} else if (config.db_type === "mq") {
|
||||
await store.loadMqTenants(connId, loadOptions);
|
||||
|
|
|
|||
|
|
@ -350,6 +350,7 @@ function connectionTooltipScheme(config: Pick<ConnectionConfig, "db_type" | "ssl
|
|||
case "elasticsearch":
|
||||
case "qdrant":
|
||||
case "milvus":
|
||||
case "weaviate":
|
||||
case "rqlite":
|
||||
case "turso":
|
||||
case "mq":
|
||||
|
|
@ -474,7 +475,7 @@ async function toggle() {
|
|||
await connectionStore.loadMongoDatabases(node.connectionId);
|
||||
} else if (config?.db_type === "elasticsearch") {
|
||||
await connectionStore.loadElasticsearchIndices(node.connectionId);
|
||||
} else if (config?.db_type === "qdrant" || config?.db_type === "milvus") {
|
||||
} else if (config?.db_type === "qdrant" || config?.db_type === "milvus" || config?.db_type === "weaviate") {
|
||||
await connectionStore.loadVectorCollections(node.connectionId);
|
||||
} else if (config?.db_type === "mq") {
|
||||
await connectionStore.loadMqTenants(node.connectionId);
|
||||
|
|
@ -3052,7 +3053,7 @@ const nodeIconClass = computed(() => {
|
|||
const canConfigureVisibleDatabases = computed(() => {
|
||||
if (props.node.type !== "connection" || !props.node.connectionId) return false;
|
||||
const dbType = connectionStore.getConfig(props.node.connectionId)?.db_type;
|
||||
return dbType !== "elasticsearch" && dbType !== "qdrant" && dbType !== "milvus" && dbType !== "etcd" && dbType !== "mq" && dbType !== "nacos";
|
||||
return dbType !== "elasticsearch" && dbType !== "qdrant" && dbType !== "milvus" && dbType !== "weaviate" && dbType !== "etcd" && dbType !== "mq" && dbType !== "nacos";
|
||||
});
|
||||
const canCopyFinalProxyPort = computed(() => {
|
||||
if (props.node.type !== "connection" || !props.node.connectionId) return false;
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ const terminalStatus = ref<SqlFileStatus | "idle">("idle");
|
|||
const terminalError = ref("");
|
||||
const refreshedTarget = ref(false);
|
||||
|
||||
const sqlConnections = computed(() => store.connections.filter((c) => !["redis", "mongodb", "elasticsearch", "qdrant", "milvus", "etcd", "mq", "nacos"].includes(c.db_type)));
|
||||
const sqlConnections = computed(() => store.connections.filter((c) => !["redis", "mongodb", "elasticsearch", "qdrant", "milvus", "weaviate", "etcd", "mq", "nacos"].includes(c.db_type)));
|
||||
|
||||
const selectedConnection = computed(() => sqlConnections.value.find((c) => c.id === connectionId.value));
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ const operationMode = ref<VectorOperationMode>("browse");
|
|||
const requestText = ref(defaultRequestText(props.databaseType, props.database, props.collection, operationMode.value));
|
||||
let loadingTimer: ReturnType<typeof setInterval> | undefined;
|
||||
|
||||
const productLabel = computed(() => (props.databaseType === "milvus" ? "Milvus" : "Qdrant"));
|
||||
const productLabel = computed(() => (props.databaseType === "milvus" ? "Milvus" : props.databaseType === "weaviate" ? "Weaviate" : "Qdrant"));
|
||||
const collectionLabel = computed(() => props.collection || t("vector.collectionFallback"));
|
||||
const executeLabel = computed(() => (operationMode.value === "browse" ? t("vector.run") : t("vector.apply")));
|
||||
const operationIcon = computed(() => (operationMode.value === "delete" ? Trash2 : operationMode.value === "upsert" ? Save : Play));
|
||||
|
|
@ -94,6 +94,26 @@ function defaultRequestText(databaseType: DatabaseType | undefined, database: st
|
|||
const endpoint = mode === "delete" ? "delete" : mode === "upsert" ? "upsert" : "query";
|
||||
return `POST /v2/vectordb/entities/${endpoint}\n${JSON.stringify(body, null, 2)}`;
|
||||
}
|
||||
if (databaseType === "weaviate") {
|
||||
const collectionName = collection || "Collection";
|
||||
if (mode === "delete") {
|
||||
return "DELETE /v1/objects/{id}";
|
||||
}
|
||||
if (mode === "upsert") {
|
||||
return `POST /v1/objects\n${JSON.stringify(
|
||||
{
|
||||
class: collectionName,
|
||||
properties: {
|
||||
title: "updated vector",
|
||||
kind: "demo",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}`;
|
||||
}
|
||||
return `GET /v1/objects?class=${encodeURIComponent(collectionName)}&limit=100`;
|
||||
}
|
||||
const collectionPath = pathSegment(collection);
|
||||
if (mode === "delete") {
|
||||
return `POST /collections/${collectionPath}/points/delete?wait=true\n${JSON.stringify({ points: [1] }, null, 2)}`;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
|
|||
connectionStore.activeConnectionId = target.connectionId;
|
||||
const config = connectionStore.getConfig(target.connectionId);
|
||||
const tabTitle = target.schema ? `${target.schema}.${target.tableName}` : target.tableName;
|
||||
if (config?.db_type === "qdrant" || config?.db_type === "milvus") {
|
||||
if (config?.db_type === "qdrant" || config?.db_type === "milvus" || config?.db_type === "weaviate") {
|
||||
await connectionStore.ensureConnected(target.connectionId);
|
||||
const tabId = queryStore.createTab(target.connectionId, target.database || "default", tabTitle, "vector");
|
||||
queryStore.updateSql(tabId, target.tableName);
|
||||
|
|
|
|||
|
|
@ -330,6 +330,7 @@ describe("supportsExecutionTargetPicker", () => {
|
|||
expect(supportsExecutionTargetPicker("elasticsearch")).toBe(false);
|
||||
expect(supportsExecutionTargetPicker("qdrant")).toBe(false);
|
||||
expect(supportsExecutionTargetPicker("milvus")).toBe(false);
|
||||
expect(supportsExecutionTargetPicker("weaviate")).toBe(false);
|
||||
expect(supportsExecutionTargetPicker("etcd")).toBe(false);
|
||||
expect(supportsExecutionTargetPicker("mq")).toBe(false);
|
||||
expect(supportsExecutionTargetPicker("neo4j")).toBe(false);
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ export function connectionUrlPlaceholder(dbType: DatabaseType): string {
|
|||
case "elasticsearch":
|
||||
case "qdrant":
|
||||
case "milvus":
|
||||
case "weaviate":
|
||||
return "http://user:password@host:port";
|
||||
|
||||
case "dameng":
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ const SCHEME_PROFILES: Record<string, ConnectionProfile> = {
|
|||
elasticsearch: { type: "elasticsearch", profile: "elasticsearch", label: "Elasticsearch", defaultPort: 9200 },
|
||||
qdrant: { type: "qdrant", profile: "qdrant", label: "Qdrant", defaultPort: 6333 },
|
||||
milvus: { type: "milvus", profile: "milvus", label: "Milvus", defaultPort: 19530 },
|
||||
weaviate: { type: "weaviate", profile: "weaviate", label: "Weaviate", defaultPort: 8080 },
|
||||
dm: { type: "dameng", profile: "dm", label: "DM (Dameng)", defaultPort: 5236 },
|
||||
dameng: { type: "dameng", profile: "dm", label: "DM (Dameng)", defaultPort: 5236 },
|
||||
gaussdb: { type: "gaussdb", profile: "gaussdb", label: "GaussDB", defaultPort: 5432 },
|
||||
|
|
@ -63,6 +64,7 @@ const HTTP_SELECTED_PROFILES: Record<string, ConnectionProfile> = {
|
|||
elasticsearch: SCHEME_PROFILES.elasticsearch,
|
||||
qdrant: SCHEME_PROFILES.qdrant,
|
||||
milvus: SCHEME_PROFILES.milvus,
|
||||
weaviate: SCHEME_PROFILES.weaviate,
|
||||
};
|
||||
|
||||
function decodeUrlPart(value: string): string {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { filterDatabaseNamesForConnection, normalizeVisibleDatabaseSelection } f
|
|||
|
||||
const DRAFT_VISIBLE_DATABASES_PREFIX = "__visible_draft_";
|
||||
|
||||
const UNSUPPORTED_VISIBLE_DATABASE_TYPES = new Set<DatabaseType>(["elasticsearch", "qdrant", "milvus", "etcd"]);
|
||||
const UNSUPPORTED_VISIBLE_DATABASE_TYPES = new Set<DatabaseType>(["elasticsearch", "qdrant", "milvus", "weaviate", "etcd"]);
|
||||
|
||||
type VisibleDatabaseConnectionFields = Pick<
|
||||
ConnectionConfig,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export function sortSidebarTreeChildrenForParent(parent: Pick<TreeNode, "type">,
|
|||
const regularChildren = normalized.filter((child) => child.type !== "user-admin" && child.type !== "saved-sql-root");
|
||||
const withConnectionUtilityOrder = (children: TreeNode[]) => [...savedSqlNodes, ...children, ...userAdminNodes];
|
||||
|
||||
if (databaseType === "mongodb" || databaseType === "elasticsearch" || databaseType === "qdrant" || databaseType === "milvus") {
|
||||
if (databaseType === "mongodb" || databaseType === "elasticsearch" || databaseType === "qdrant" || databaseType === "milvus" || databaseType === "weaviate") {
|
||||
return withConnectionUtilityOrder(sortByLabel(regularChildren));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ export function areSqlSemanticDiagnosticsEqual(left: readonly SqlSemanticDiagnos
|
|||
}
|
||||
|
||||
export function shouldRunSqlSemanticDiagnostics(sql: string, cursor: number, options: { databaseType?: DatabaseType } = {}): boolean {
|
||||
if (options.databaseType === "mongodb" || options.databaseType === "elasticsearch" || options.databaseType === "qdrant" || options.databaseType === "milvus" || options.databaseType === "redis") return false;
|
||||
if (options.databaseType === "mongodb" || options.databaseType === "elasticsearch" || options.databaseType === "qdrant" || options.databaseType === "milvus" || options.databaseType === "weaviate" || options.databaseType === "redis") return false;
|
||||
const context = getSqlCompletionContext(sql, cursor);
|
||||
if (context.suggestTables || context.exclusiveTableSuggestions || context.exclusiveColumnSuggestions) return false;
|
||||
if (context.qualifier) return false;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export interface SqlTextRange {
|
|||
sql: string;
|
||||
}
|
||||
|
||||
const NON_SQL_EXECUTION_TARGET_TYPES: ReadonlySet<DatabaseType> = new Set(["mongodb", "elasticsearch", "qdrant", "milvus", "etcd", "mq", "neo4j"]);
|
||||
const NON_SQL_EXECUTION_TARGET_TYPES: ReadonlySet<DatabaseType> = new Set(["mongodb", "elasticsearch", "qdrant", "milvus", "weaviate", "etcd", "mq", "neo4j"]);
|
||||
|
||||
export function supportsExecutionTargetPicker(databaseType?: DatabaseType): boolean {
|
||||
return !!databaseType && (databaseType === "redis" || !NON_SQL_EXECUTION_TARGET_TYPES.has(databaseType));
|
||||
|
|
@ -88,6 +88,7 @@ const DATABASE_SOFT_STATEMENT_KEYWORDS: Partial<Record<DatabaseType, readonly st
|
|||
elasticsearch: [],
|
||||
qdrant: [],
|
||||
milvus: [],
|
||||
weaviate: [],
|
||||
mq: [],
|
||||
etcd: [],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@ const capabilityByType: Partial<Record<DatabaseType, Partial<TableMetadataCapabi
|
|||
triggers: false,
|
||||
ddl: false,
|
||||
},
|
||||
weaviate: {
|
||||
indexes: false,
|
||||
foreignKeys: false,
|
||||
triggers: false,
|
||||
ddl: false,
|
||||
},
|
||||
influxdb: {
|
||||
indexes: false,
|
||||
foreignKeys: false,
|
||||
|
|
|
|||
|
|
@ -407,6 +407,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
elasticsearch: "Elasticsearch",
|
||||
qdrant: "Qdrant",
|
||||
milvus: "Milvus",
|
||||
weaviate: "Weaviate",
|
||||
doris: "Doris",
|
||||
starrocks: "StarRocks",
|
||||
manticoresearch: "Manticore Search",
|
||||
|
|
@ -950,7 +951,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
await loadMongoDatabases(connectionId);
|
||||
} else if (config.db_type === "elasticsearch") {
|
||||
await loadElasticsearchIndices(connectionId);
|
||||
} else if (config.db_type === "qdrant" || config.db_type === "milvus") {
|
||||
} else if (config.db_type === "qdrant" || config.db_type === "milvus" || config.db_type === "weaviate") {
|
||||
await loadVectorCollections(connectionId);
|
||||
} else if (config.db_type === "mq") {
|
||||
await loadMqTenants(connectionId, { force: true });
|
||||
|
|
@ -2115,7 +2116,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
await loadMongoDatabases(node.connectionId);
|
||||
} else if (config?.db_type === "elasticsearch") {
|
||||
await loadElasticsearchIndices(node.connectionId);
|
||||
} else if (config?.db_type === "qdrant" || config?.db_type === "milvus") {
|
||||
} else if (config?.db_type === "qdrant" || config?.db_type === "milvus" || config?.db_type === "weaviate") {
|
||||
await loadVectorCollections(node.connectionId);
|
||||
} else if (config?.db_type === "mq") {
|
||||
await loadMqTenants(node.connectionId, options);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export type DatabaseType =
|
|||
| "elasticsearch"
|
||||
| "qdrant"
|
||||
| "milvus"
|
||||
| "weaviate"
|
||||
| "doris"
|
||||
| "starrocks"
|
||||
| "manticoresearch"
|
||||
|
|
|
|||
|
|
@ -378,6 +378,35 @@
|
|||
"driverManagement": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"dbType": "weaviate",
|
||||
"label": "Weaviate",
|
||||
"runtimeMode": "native",
|
||||
"mcpMode": "bridge",
|
||||
"singleConnectionPool": false,
|
||||
"metadataConnectionScoped": false,
|
||||
"skipTcpProbe": false,
|
||||
"defaultPort": 8080,
|
||||
"supportLevel": "browse",
|
||||
"capabilities": {
|
||||
"queryExecution": true,
|
||||
"metadataBrowse": true,
|
||||
"objectBrowser": false,
|
||||
"objectSource": false,
|
||||
"schemaSearch": false,
|
||||
"diagram": false,
|
||||
"tableDataEdit": false,
|
||||
"tableStructureEdit": false,
|
||||
"tableImport": false,
|
||||
"dataTransfer": false,
|
||||
"sqlFileExecution": false,
|
||||
"databaseCreate": false,
|
||||
"fieldLineage": false,
|
||||
"sqlExplain": false,
|
||||
"userAdmin": false,
|
||||
"driverManagement": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"dbType": "doris",
|
||||
"label": "Doris",
|
||||
|
|
|
|||
|
|
@ -831,10 +831,11 @@ impl AppState {
|
|||
db::elasticsearch_driver::test_connection(&mut client, connect_timeout).await?;
|
||||
PoolKind::Elasticsearch(client)
|
||||
}
|
||||
DatabaseType::Qdrant | DatabaseType::Milvus => {
|
||||
DatabaseType::Qdrant | DatabaseType::Milvus | DatabaseType::Weaviate => {
|
||||
let kind = match db_config.db_type {
|
||||
DatabaseType::Qdrant => db::vector_driver::VectorDbKind::Qdrant,
|
||||
DatabaseType::Milvus => db::vector_driver::VectorDbKind::Milvus,
|
||||
DatabaseType::Weaviate => db::vector_driver::VectorDbKind::Weaviate,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let client = db::vector_driver::VectorClient::new(
|
||||
|
|
@ -2124,7 +2125,10 @@ fn base_pool_key_for(
|
|||
let is_single_connection_pool = db_type.as_ref().is_some_and(|db_type| {
|
||||
let is_single = database_capabilities::is_single_connection_pool(db_type)
|
||||
|| (include_elasticsearch_single_pool
|
||||
&& matches!(db_type, DatabaseType::Elasticsearch | DatabaseType::Qdrant | DatabaseType::Milvus));
|
||||
&& matches!(
|
||||
db_type,
|
||||
DatabaseType::Elasticsearch | DatabaseType::Qdrant | DatabaseType::Milvus | DatabaseType::Weaviate
|
||||
));
|
||||
is_single && (!database_capabilities::is_agent_type(db_type) || shares_database_pool_with_connection(db_type))
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -25,10 +25,13 @@ const PATH_SEGMENT_ENCODE_SET: &AsciiSet = &CONTROLS
|
|||
.add(b'|')
|
||||
.add(b'}');
|
||||
|
||||
const QUERY_VALUE_ENCODE_SET: &AsciiSet = &PATH_SEGMENT_ENCODE_SET.add(b'&').add(b'=').add(b'+');
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum VectorDbKind {
|
||||
Qdrant,
|
||||
Milvus,
|
||||
Weaviate,
|
||||
}
|
||||
|
||||
impl VectorDbKind {
|
||||
|
|
@ -36,6 +39,7 @@ impl VectorDbKind {
|
|||
match self {
|
||||
VectorDbKind::Qdrant => "Qdrant",
|
||||
VectorDbKind::Milvus => "Milvus",
|
||||
VectorDbKind::Weaviate => "Weaviate",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -48,7 +52,7 @@ pub struct VectorClient {
|
|||
auth: Option<VectorAuth>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
enum VectorAuth {
|
||||
Basic(String, String),
|
||||
Bearer(String),
|
||||
|
|
@ -108,6 +112,8 @@ fn vector_auth(kind: VectorDbKind, username: Option<&str>, password: Option<&str
|
|||
VectorDbKind::Qdrant => None,
|
||||
VectorDbKind::Milvus if !username.is_empty() => Some(VectorAuth::Bearer(format!("{username}:{password}"))),
|
||||
VectorDbKind::Milvus => None,
|
||||
VectorDbKind::Weaviate if !password.is_empty() => Some(VectorAuth::Bearer(password.to_string())),
|
||||
VectorDbKind::Weaviate => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,10 +122,12 @@ pub async fn test_connection(client: &VectorClient, timeout: Duration) -> Result
|
|||
let path = match client.kind {
|
||||
VectorDbKind::Qdrant => "/collections",
|
||||
VectorDbKind::Milvus => "/v2/vectordb/collections/list",
|
||||
VectorDbKind::Weaviate => "/v1/meta",
|
||||
};
|
||||
let request = match client.kind {
|
||||
VectorDbKind::Qdrant => client.get(path),
|
||||
VectorDbKind::Milvus => client.post(path).json(&serde_json::json!({ "dbName": "default" })),
|
||||
VectorDbKind::Weaviate => client.get(path),
|
||||
};
|
||||
let resp = with_connection_timeout(label, timeout, async {
|
||||
request.send().await.map_err(|e| format!("{label} connection failed: {}", format_reqwest_error(&e)))
|
||||
|
|
@ -132,6 +140,7 @@ pub async fn list_collections(client: &VectorClient) -> Result<Vec<String>, Stri
|
|||
match client.kind {
|
||||
VectorDbKind::Qdrant => list_qdrant_collections(client).await,
|
||||
VectorDbKind::Milvus => list_milvus_collections(client).await,
|
||||
VectorDbKind::Weaviate => list_weaviate_collections(client).await,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -169,6 +178,22 @@ fn collection_name_from_milvus_item(item: &Value) -> Option<String> {
|
|||
.or_else(|| item.get("name").and_then(Value::as_str).map(str::to_string))
|
||||
}
|
||||
|
||||
async fn list_weaviate_collections(client: &VectorClient) -> Result<Vec<String>, String> {
|
||||
let body = send_json(client.get("/v1/schema"), "Weaviate").await?;
|
||||
let mut names = weaviate_collection_names_from_schema(&body);
|
||||
names.sort();
|
||||
Ok(names)
|
||||
}
|
||||
|
||||
fn weaviate_collection_names_from_schema(body: &Value) -> Vec<String> {
|
||||
body.get("classes")
|
||||
.and_then(Value::as_array)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|item| item.get("class").and_then(Value::as_str).map(str::to_string))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub async fn find_documents(
|
||||
client: &VectorClient,
|
||||
collection: &str,
|
||||
|
|
@ -197,6 +222,9 @@ pub async fn find_documents(
|
|||
"outputFields": ["*"],
|
||||
})
|
||||
),
|
||||
VectorDbKind::Weaviate => {
|
||||
format!("GET /v1/objects?class={}&limit={}&offset={}", query_value(collection), limit.max(1), skip)
|
||||
}
|
||||
};
|
||||
let result = execute_rest_query(client, &query).await?;
|
||||
let documents = result
|
||||
|
|
@ -272,6 +300,7 @@ fn default_collection_query(client: &VectorClient, collection: &str) -> Result<r
|
|||
"limit": 100,
|
||||
"outputFields": ["*"],
|
||||
}))),
|
||||
VectorDbKind::Weaviate => Ok(client.get(&format!("/v1/objects?class={}&limit=100", query_value(collection)))),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -283,6 +312,10 @@ fn path_segment(value: &str) -> String {
|
|||
utf8_percent_encode(value, PATH_SEGMENT_ENCODE_SET).to_string()
|
||||
}
|
||||
|
||||
fn query_value(value: &str) -> String {
|
||||
utf8_percent_encode(value, QUERY_VALUE_ENCODE_SET).to_string()
|
||||
}
|
||||
|
||||
async fn send_json(req: reqwest::RequestBuilder, label: &str) -> Result<Value, String> {
|
||||
let resp = req.send().await.map_err(|e| format!("{label} request failed: {e}"))?;
|
||||
let resp = ensure_success(label, resp).await?;
|
||||
|
|
@ -304,6 +337,9 @@ fn json_to_query_result(status: u16, body: Value, start: Instant) -> QueryResult
|
|||
if let Some(Value::Array(items)) = rows_value {
|
||||
return values_to_query_result(items, start);
|
||||
}
|
||||
if let Some(Value::Array(items)) = body.get("objects").cloned() {
|
||||
return values_to_query_result(items, start);
|
||||
}
|
||||
if let Some(Value::Array(items)) = body.pointer("/result/collections").cloned() {
|
||||
return values_to_query_result(items, start);
|
||||
}
|
||||
|
|
@ -361,6 +397,11 @@ fn normalize_row_object(value: Value) -> Map<String, Value> {
|
|||
object.entry(key).or_insert(value);
|
||||
}
|
||||
}
|
||||
if let Some(Value::Object(properties)) = object.remove("properties") {
|
||||
for (key, value) in properties {
|
||||
object.entry(key).or_insert(value);
|
||||
}
|
||||
}
|
||||
object
|
||||
}
|
||||
other => {
|
||||
|
|
@ -386,7 +427,10 @@ fn format_reqwest_error(err: &reqwest::Error) -> String {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{starts_with_http_method, values_to_query_result};
|
||||
use super::{
|
||||
starts_with_http_method, values_to_query_result, vector_auth, weaviate_collection_names_from_schema,
|
||||
VectorAuth, VectorDbKind,
|
||||
};
|
||||
use serde_json::json;
|
||||
use std::time::Instant;
|
||||
|
||||
|
|
@ -405,4 +449,34 @@ mod tests {
|
|||
assert!(result.columns.contains(&"score".to_string()));
|
||||
assert!(result.columns.contains(&"title".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extracts_weaviate_schema_class_names() {
|
||||
let names = weaviate_collection_names_from_schema(&json!({
|
||||
"classes": [
|
||||
{ "class": "Article" },
|
||||
{ "class": "Product" }
|
||||
]
|
||||
}));
|
||||
assert_eq!(names, vec!["Article".to_string(), "Product".to_string()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flattens_weaviate_properties_columns() {
|
||||
let result = values_to_query_result(
|
||||
vec![json!({"id": "abc", "class": "Article", "properties": {"title": "hello"}})],
|
||||
Instant::now(),
|
||||
);
|
||||
assert!(result.columns.contains(&"id".to_string()));
|
||||
assert!(result.columns.contains(&"class".to_string()));
|
||||
assert!(result.columns.contains(&"title".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uses_bearer_auth_for_weaviate_tokens_even_with_username() {
|
||||
assert_eq!(
|
||||
vector_auth(VectorDbKind::Weaviate, Some("user"), Some("token")),
|
||||
Some(VectorAuth::Bearer("token".to_string()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -258,6 +258,8 @@ pub enum DatabaseType {
|
|||
Qdrant,
|
||||
#[serde(rename = "milvus")]
|
||||
Milvus,
|
||||
#[serde(rename = "weaviate")]
|
||||
Weaviate,
|
||||
Doris,
|
||||
#[serde(rename = "starrocks")]
|
||||
StarRocks,
|
||||
|
|
@ -733,7 +735,7 @@ impl ConnectionConfig {
|
|||
format!("mongodb://{host}:{port}{db_part}{suffix}")
|
||||
}
|
||||
DatabaseType::Oracle => format!("oracle://{host}:{port}{db_part}"),
|
||||
DatabaseType::Elasticsearch | DatabaseType::Qdrant | DatabaseType::Milvus => {
|
||||
DatabaseType::Elasticsearch | DatabaseType::Qdrant | DatabaseType::Milvus | DatabaseType::Weaviate => {
|
||||
let scheme = if self.ssl { "https" } else { "http" };
|
||||
format!("{scheme}://{host}:{port}")
|
||||
}
|
||||
|
|
@ -869,7 +871,7 @@ impl ConnectionConfig {
|
|||
DatabaseType::Oracle => {
|
||||
format!("oracle://{}:{}@{host}:{port}{db_part}", username, password)
|
||||
}
|
||||
DatabaseType::Elasticsearch | DatabaseType::Qdrant | DatabaseType::Milvus => {
|
||||
DatabaseType::Elasticsearch | DatabaseType::Qdrant | DatabaseType::Milvus | DatabaseType::Weaviate => {
|
||||
let scheme = if self.ssl { "https" } else { "http" };
|
||||
format!("{scheme}://{host}:{port}")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -733,6 +733,7 @@ fn should_discard_pool_after_query_timeout(db_type: Option<DatabaseType>) -> boo
|
|||
| DatabaseType::Elasticsearch
|
||||
| DatabaseType::Qdrant
|
||||
| DatabaseType::Milvus
|
||||
| DatabaseType::Weaviate
|
||||
| DatabaseType::InfluxDb
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ pub fn supports_sql_query(database_type: DatabaseType) -> bool {
|
|||
| DatabaseType::Elasticsearch
|
||||
| DatabaseType::Qdrant
|
||||
| DatabaseType::Milvus
|
||||
| DatabaseType::Weaviate
|
||||
| DatabaseType::InfluxDb
|
||||
| DatabaseType::Neo4j
|
||||
| DatabaseType::Etcd
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ function formatQueryToolResult(result: QueryResult, title?: string) {
|
|||
}
|
||||
|
||||
export const DBX_CONNECTION_TYPE_DESCRIPTION =
|
||||
"Database type: postgres, mysql, sqlite, rqlite, redis, duckdb, clickhouse, sqlserver, mongodb, oracle, elasticsearch, etcd, doris, starrocks, manticoresearch, milvus, qdrant, redshift, dameng, kingbase, highgo, vastbase, goldendb, databend, gaussdb, kwdb, yashandb, databricks, saphana, teradata, vertica, firebird, exasol, opengauss, oceanbase-oracle, questdb, gbase, h2, snowflake, trino, prestosql, hive, db2, informix, influxdb, iris, neo4j, cassandra, bigquery, kylin, sundb, tdengine, iotdb, xugu, jdbc, access, mq";
|
||||
"Database type: postgres, mysql, sqlite, rqlite, redis, duckdb, clickhouse, sqlserver, mongodb, oracle, elasticsearch, etcd, doris, starrocks, manticoresearch, milvus, qdrant, weaviate, redshift, dameng, kingbase, highgo, vastbase, goldendb, databend, gaussdb, kwdb, yashandb, databricks, saphana, teradata, vertica, firebird, exasol, opengauss, oceanbase-oracle, questdb, gbase, h2, snowflake, trino, prestosql, hive, db2, informix, influxdb, iris, neo4j, cassandra, bigquery, kylin, sundb, tdengine, iotdb, xugu, jdbc, access, mq";
|
||||
const FILE_CAPABLE_CONNECTION_TYPES = new Set(["sqlite", "duckdb", "access", "h2"]);
|
||||
|
||||
interface McpScope {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export const BRIDGE_REQUIRED_TYPES = [
|
|||
"elasticsearch",
|
||||
"qdrant",
|
||||
"milvus",
|
||||
"weaviate",
|
||||
"etcd",
|
||||
"dameng",
|
||||
"kingbase",
|
||||
|
|
|
|||
|
|
@ -712,10 +712,11 @@ pub async fn test_connection(state: State<'_, Arc<AppState>>, config: Connection
|
|||
.await
|
||||
.map(|_| "Connection successful".to_string())
|
||||
}
|
||||
DatabaseType::Qdrant | DatabaseType::Milvus => {
|
||||
DatabaseType::Qdrant | DatabaseType::Milvus | DatabaseType::Weaviate => {
|
||||
let kind = match config.db_type {
|
||||
DatabaseType::Qdrant => db::vector_driver::VectorDbKind::Qdrant,
|
||||
DatabaseType::Milvus => db::vector_driver::VectorDbKind::Milvus,
|
||||
DatabaseType::Weaviate => db::vector_driver::VectorDbKind::Weaviate,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let client = db::vector_driver::VectorClient::new(
|
||||
|
|
@ -997,10 +998,11 @@ pub async fn connect_db(state: State<'_, Arc<AppState>>, config: ConnectionConfi
|
|||
db::elasticsearch_driver::test_connection(&mut client, connect_timeout).await?;
|
||||
PoolKind::Elasticsearch(client)
|
||||
}
|
||||
DatabaseType::Qdrant | DatabaseType::Milvus => {
|
||||
DatabaseType::Qdrant | DatabaseType::Milvus | DatabaseType::Weaviate => {
|
||||
let kind = match db_config.db_type {
|
||||
DatabaseType::Qdrant => db::vector_driver::VectorDbKind::Qdrant,
|
||||
DatabaseType::Milvus => db::vector_driver::VectorDbKind::Milvus,
|
||||
DatabaseType::Weaviate => db::vector_driver::VectorDbKind::Weaviate,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let client = db::vector_driver::VectorClient::new(
|
||||
|
|
|
|||
Loading…
Reference in New Issue