feat(meilisearch): add native document database support
This commit is contained in:
79 files changed
+1801
-170
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import { computed, ref } from "vue";
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { requiresDatabaseSelection, useSqlExecution } from "../useSqlExecution";
|
||||
import { isDangerousSql, requiresDatabaseSelection, supportsSqlTemplateParameters, useSqlExecution } from "../useSqlExecution";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useHistoryStore } from "@/stores/historyStore";
|
||||
import { useQueryStore } from "@/stores/queryStore";
|
||||
@@ -115,6 +115,13 @@ describe("requiresDatabaseSelection", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("supportsSqlTemplateParameters", () => {
|
||||
it("disables SQL parameter prompts for Meilisearch input", () => {
|
||||
expect(supportsSqlTemplateParameters(connection("meilisearch"), "GET /indexes/:uid")).toBe(false);
|
||||
expect(supportsSqlTemplateParameters(connection("meilisearch"), "plain :value")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("useSqlExecution", () => {
|
||||
beforeEach(() => {
|
||||
installLocalStorage();
|
||||
@@ -756,6 +763,20 @@ SELECT @value AS Message;`;
|
||||
expect(executeCurrentSql).toHaveBeenCalledWith(sql, { skipRedisSafetyCheck: false, openInNewResultTab: true });
|
||||
});
|
||||
|
||||
it("distinguishes read-only and mutating Meilisearch REST requests", () => {
|
||||
expect(isDangerousSql("GET /health", "meilisearch")).toBe(false);
|
||||
expect(isDangerousSql('POST /indexes/movies/documents/fetch\n{"limit":10}', "meilisearch")).toBe(false);
|
||||
expect(isDangerousSql('POST /indexes/movies/search\n{"q":"space"}', "meilisearch")).toBe(false);
|
||||
expect(isDangerousSql('POST /indexes\n{"uid":"movies"}', "meilisearch")).toBe(true);
|
||||
expect(isDangerousSql("PUT /indexes/movies/settings", "meilisearch")).toBe(true);
|
||||
expect(isDangerousSql("DELETE /indexes/movies/documents/001", "meilisearch")).toBe(true);
|
||||
});
|
||||
|
||||
it("treats Elasticsearch PATCH requests as mutating", () => {
|
||||
expect(isDangerousSql('PATCH /products/_settings\n{"index":{"refresh_interval":"5s"}}', "elasticsearch")).toBe(true);
|
||||
expect(isDangerousSql('PATCH /products/_settings\n{"index":{"refresh_interval":"5s"}}', "easysearch")).toBe(true);
|
||||
});
|
||||
|
||||
it("requires production confirmation even when ordinary danger prompts are disabled", async () => {
|
||||
const activeTab = ref<QueryTab | undefined>(queryTab("prod_app"));
|
||||
const activeConnection = ref<ConnectionConfig | undefined>({ ...connection("mysql"), production_databases: ["prod_app"] });
|
||||
|
||||
@@ -19,7 +19,7 @@ const QUICK_OPEN_MAX_RESULTS = 200;
|
||||
const INITIAL_SQL_LIBRARY_LIMIT = 20;
|
||||
const INITIAL_SQL_FILE_LIMIT = 20;
|
||||
|
||||
const REMOTE_SEARCH_UNSUPPORTED_TYPES = new Set<ConnectionConfig["db_type"]>(["redis", "mongodb", "elasticsearch", "easysearch", "qdrant", "milvus", "weaviate", "chromadb", "neo4j", "influxdb", "victoriametrics", "etcd", "zookeeper", "mq", "nacos", "consul"]);
|
||||
const REMOTE_SEARCH_UNSUPPORTED_TYPES = new Set<ConnectionConfig["db_type"]>(["redis", "mongodb", "elasticsearch", "easysearch", "meilisearch", "qdrant", "milvus", "weaviate", "chromadb", "neo4j", "influxdb", "victoriametrics", "etcd", "zookeeper", "mq", "nacos", "consul"]);
|
||||
|
||||
export interface QuickOpenItem {
|
||||
id: string;
|
||||
|
||||
@@ -60,19 +60,28 @@ export function stripSqlComments(sql: string): string {
|
||||
const ELASTICSEARCH_TRANSIENT_DELETE_PATHS = [/^\/_search\/scroll\/?$/i, /^\/_pit\/?$/i, /^\/_async_search\/[^/?]+\/?$/i];
|
||||
const ELASTICSEARCH_DESTRUCTIVE_POST_PATHS = [/(?:^|\/)_(?:delete_by_query|update_by_query|bulk)(?:\/|$)/i, /^\/_reindex(?:\/|$)/i, /^\/_aliases(?:\/|$)/i, /\/_restore(?:\/|$)/i];
|
||||
|
||||
function isDangerousElasticsearchRequest(method: "GET" | "POST" | "PUT" | "DELETE" | "HEAD", path: string): boolean {
|
||||
function isDangerousElasticsearchRequest(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD", path: string): boolean {
|
||||
const pathname = path.split("?", 1)[0].replace(/\/+$/, "") || "/";
|
||||
if (method === "DELETE") return !ELASTICSEARCH_TRANSIENT_DELETE_PATHS.some((pattern) => pattern.test(pathname));
|
||||
if (method === "PUT") return true;
|
||||
if (method === "PUT" || method === "PATCH") return true;
|
||||
return method === "POST" && ELASTICSEARCH_DESTRUCTIVE_POST_PATHS.some((pattern) => pattern.test(pathname));
|
||||
}
|
||||
|
||||
function isDangerousMeilisearchRequest(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD", path: string): boolean {
|
||||
if (method === "GET" || method === "HEAD") return false;
|
||||
if (method === "DELETE" || method === "PUT" || method === "PATCH") return true;
|
||||
const pathname = path.split("?", 1)[0].replace(/\/+$/, "") || "/";
|
||||
return !(pathname === "/multi-search" || /\/search$/i.test(pathname) || /\/facet-search$/i.test(pathname) || /\/similar$/i.test(pathname) || /\/documents\/fetch$/i.test(pathname));
|
||||
}
|
||||
|
||||
export function isDangerousSql(sql: string, databaseType?: DatabaseType): boolean {
|
||||
if (databaseType === "elasticsearch" || databaseType === "easysearch") {
|
||||
if (databaseType === "elasticsearch" || databaseType === "easysearch" || databaseType === "meilisearch") {
|
||||
const requests = splitSqlStatementRanges(sql, databaseType)
|
||||
.map((statement) => parseElasticsearchRestRequestTarget(statement.sql))
|
||||
.filter((request): request is NonNullable<typeof request> => request !== null);
|
||||
if (requests.length > 0) return requests.some((request) => isDangerousElasticsearchRequest(request.method, request.path));
|
||||
if (requests.length > 0) {
|
||||
return requests.some((request) => (databaseType === "meilisearch" ? isDangerousMeilisearchRequest(request.method, request.path) : isDangerousElasticsearchRequest(request.method, request.path)));
|
||||
}
|
||||
}
|
||||
const cleaned = stripSqlComments(sql);
|
||||
return cleaned.split(";").some((stmt) => DANGER_RE.test(stmt));
|
||||
@@ -562,6 +571,7 @@ export function useSqlExecution(deps: {
|
||||
|
||||
export function supportsSqlTemplateParameters(connection: Pick<ConnectionConfig, "db_type"> | undefined, sql = ""): boolean {
|
||||
if (!connection) return false;
|
||||
if (connection.db_type === "meilisearch") return false;
|
||||
if (connection.db_type === "elasticsearch" || connection.db_type === "easysearch") return !isElasticsearchRestRequestText(sql);
|
||||
return connection.db_type !== "redis" && connection.db_type !== "mongodb" && connection.db_type !== "victoriametrics";
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user