mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 06:00:39 +00:00
fix(app): import SQLite file path from Navicat NCX (#1773)
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { parseNavicatConnections } from "../navicatImport";
|
||||
|
||||
class TestElement {
|
||||
readonly tagName: string;
|
||||
readonly attributes: { name: string; value: string }[];
|
||||
readonly children: TestElement[] = [];
|
||||
readonly textContent = "";
|
||||
|
||||
constructor(tagName: string, attributes: { name: string; value: string }[]) {
|
||||
this.tagName = tagName;
|
||||
this.attributes = attributes;
|
||||
}
|
||||
}
|
||||
|
||||
class TestDocument {
|
||||
private readonly elements: TestElement[];
|
||||
|
||||
constructor(xml: string) {
|
||||
this.elements = Array.from(xml.matchAll(/<Connection\b([\s\S]*?)\/>/gi)).map((match) => new TestElement("Connection", parseAttributes(match[1] || "")));
|
||||
}
|
||||
|
||||
querySelector(selector: string) {
|
||||
return selector === "parsererror" ? null : null;
|
||||
}
|
||||
|
||||
querySelectorAll(selector: string) {
|
||||
return selector === "*" ? this.elements : [];
|
||||
}
|
||||
}
|
||||
|
||||
class TestDOMParser {
|
||||
parseFromString(xml: string) {
|
||||
return new TestDocument(xml);
|
||||
}
|
||||
}
|
||||
|
||||
function parseAttributes(source: string) {
|
||||
return Array.from(source.matchAll(/([^\s=]+)="([^"]*)"/g)).map((match) => ({ name: match[1] || "", value: match[2] || "" }));
|
||||
}
|
||||
|
||||
if (!globalThis.DOMParser) {
|
||||
globalThis.DOMParser = TestDOMParser as typeof DOMParser;
|
||||
}
|
||||
|
||||
describe("parseNavicatConnections", () => {
|
||||
it("imports SQLite DatabaseFile as both host and database", async () => {
|
||||
const [connection] = await parseNavicatConnections(`<Connections>
|
||||
<Connection ConnType="SQLite" Name="local-sqlite" DatabaseFile="C:\\Users\\Yang\\demo.db" />
|
||||
</Connections>`);
|
||||
|
||||
expect(connection?.db_type).toBe("sqlite");
|
||||
expect(connection?.host).toBe("C:\\Users\\Yang\\demo.db");
|
||||
expect(connection?.database).toBe("C:\\Users\\Yang\\demo.db");
|
||||
expect(connection?.port).toBe(0);
|
||||
});
|
||||
|
||||
it("imports SQLite numeric ConnType file name as host", async () => {
|
||||
const [connection] = await parseNavicatConnections(`<Connections>
|
||||
<Connection ConnType="3" Name="sqlite-by-code" DatabaseFileName="/home/yang/demo.sqlite" />
|
||||
</Connections>`);
|
||||
|
||||
expect(connection?.db_type).toBe("sqlite");
|
||||
expect(connection?.host).toBe("/home/yang/demo.sqlite");
|
||||
});
|
||||
|
||||
it("uses SQLite Database field as the file path", async () => {
|
||||
const [connection] = await parseNavicatConnections(`<Connections>
|
||||
<Connection ConnType="SQLite" Name="sqlite-database-field" Database="/tmp/app.data" />
|
||||
</Connections>`);
|
||||
|
||||
expect(connection?.db_type).toBe("sqlite");
|
||||
expect(connection?.host).toBe("/tmp/app.data");
|
||||
expect(connection?.database).toBe("/tmp/app.data");
|
||||
});
|
||||
|
||||
it("keeps non-SQLite host and database mapping unchanged", async () => {
|
||||
const [connection] = await parseNavicatConnections(`<Connections>
|
||||
<Connection ConnType="PostgreSQL" Name="pg" Host="db.example.test" Database="appdb" Port="15432" />
|
||||
</Connections>`);
|
||||
|
||||
expect(connection?.db_type).toBe("postgres");
|
||||
expect(connection?.host).toBe("db.example.test");
|
||||
expect(connection?.database).toBe("appdb");
|
||||
expect(connection?.port).toBe(15432);
|
||||
});
|
||||
});
|
||||
@@ -54,6 +54,10 @@ function getAny(values: Record<string, string>, keys: string[]) {
|
||||
return "";
|
||||
}
|
||||
|
||||
function getSqlitePath(values: Record<string, string>) {
|
||||
return getAny(values, ["databaseFile", "databaseFileName", "databaseFilename", "filename", "fileName", "path", "databasePath", "dbPath", "dbFile", "sqliteFile", "sqlitePath", "database", "databaseName"]);
|
||||
}
|
||||
|
||||
function truthyNavicatFlag(value: string) {
|
||||
const normalized = value.trim().toLowerCase();
|
||||
return ["1", "true", "yes", "y", "on", "checked"].includes(normalized);
|
||||
@@ -145,7 +149,7 @@ function isConnectionCandidate(node: ParsedNode) {
|
||||
const type = getAny(node.values, ["type", "connType", "connectionType", "databaseType", "driver"]);
|
||||
const name = getAny(node.values, ["name", "connectionName", "connName", "caption", "title"]);
|
||||
const host = getAny(node.values, ["host", "server", "hostname", "serverHost", "address"]);
|
||||
const file = getAny(node.values, ["databaseFile", "filename", "path", "databasePath"]);
|
||||
const file = getSqlitePath(node.values);
|
||||
return !!(name || host || file) && !!(type || host || file);
|
||||
}
|
||||
|
||||
@@ -174,9 +178,10 @@ async function parseConnection(node: ParsedNode): Promise<ConnectionConfig | nul
|
||||
}
|
||||
}
|
||||
|
||||
const name = getAny(node.values, ["name", "connectionName", "connName", "caption", "title"]) || getAny(node.values, ["host", "server", "hostname"]) || effectiveProfile.label;
|
||||
const host = getAny(node.values, ["host", "server", "hostname", "serverHost", "address"]) || getAny(node.values, ["databaseFile", "filename", "path", "databasePath"]) || (effectiveProfile.dbType === "sqlite" ? "" : "127.0.0.1");
|
||||
const database = getAny(node.values, ["database", "databaseName", "initialDatabase", "serviceName", "sid", "schema"]);
|
||||
const sqlitePath = effectiveProfile.dbType === "sqlite" ? getSqlitePath(node.values) : "";
|
||||
const name = getAny(node.values, ["name", "connectionName", "connName", "caption", "title"]) || getAny(node.values, ["host", "server", "hostname"]) || sqlitePath || effectiveProfile.label;
|
||||
const host = sqlitePath || getAny(node.values, ["host", "server", "hostname", "serverHost", "address"]) || (effectiveProfile.dbType === "sqlite" ? "" : "127.0.0.1");
|
||||
const database = effectiveProfile.dbType === "sqlite" ? sqlitePath : getAny(node.values, ["database", "databaseName", "initialDatabase", "serviceName", "sid", "schema"]);
|
||||
const isOracleLike = effectiveProfile.dbType === "oracle" || effectiveProfile.dbType === "oceanbase-oracle";
|
||||
const oracleConnectionType = isOracleLike && getAny(node.values, ["sid"]) ? "sid" : isOracleLike ? "service_name" : undefined;
|
||||
const username = getAny(node.values, ["user", "username", "userName", "uid"]) || profile.user;
|
||||
|
||||
Reference in New Issue
Block a user