mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
fix: convert named SQL params to positional for Authority server compatibility
Authority server expects params: SqlValue[] (positional array) but ST-BME was sending params as a named map with :placeholders, causing 'invalid type: map, expected a sequence' deserialization errors. Added convertNamedParamsToPositional() in AuthoritySqlHttpClient that transforms :name placeholders to ? and extracts values in order before sending to the server. This fixes the SQL probe failure that kept the graph stuck in LOADING state.
This commit is contained in:
@@ -342,6 +342,22 @@ function normalizeUpsertCountDelta(delta = {}) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function convertNamedParamsToPositional(sql, params = {}) {
|
||||||
|
if (Array.isArray(params)) return { sql, params };
|
||||||
|
if (!params || typeof params !== "object") return { sql, params: [] };
|
||||||
|
const names = [];
|
||||||
|
const positionalSql = sql.replace(/(?<!:):(\w+)/g, (_, name) => {
|
||||||
|
names.push(name);
|
||||||
|
return "?";
|
||||||
|
});
|
||||||
|
if (!names.length) return { sql: positionalSql, params: [] };
|
||||||
|
const positionalParams = names.map((name) => {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(params, name)) return null;
|
||||||
|
return params[name];
|
||||||
|
});
|
||||||
|
return { sql: positionalSql, params: positionalParams };
|
||||||
|
}
|
||||||
|
|
||||||
export class AuthoritySqlHttpClient {
|
export class AuthoritySqlHttpClient {
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
this.http = new AuthorityHttpClient({
|
this.http = new AuthorityHttpClient({
|
||||||
@@ -352,18 +368,20 @@ export class AuthoritySqlHttpClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async query(sql, params = {}) {
|
async query(sql, params = {}) {
|
||||||
|
const positional = convertNamedParamsToPositional(String(sql || ""), params);
|
||||||
return await this._request(AUTHORITY_SQL_QUERY_ENDPOINT, {
|
return await this._request(AUTHORITY_SQL_QUERY_ENDPOINT, {
|
||||||
database: this.database,
|
database: this.database,
|
||||||
statement: String(sql || ""),
|
statement: positional.sql,
|
||||||
params,
|
params: positional.params,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute(sql, params = {}) {
|
async execute(sql, params = {}) {
|
||||||
|
const positional = convertNamedParamsToPositional(String(sql || ""), params);
|
||||||
return await this._request(AUTHORITY_SQL_EXEC_ENDPOINT, {
|
return await this._request(AUTHORITY_SQL_EXEC_ENDPOINT, {
|
||||||
database: this.database,
|
database: this.database,
|
||||||
statement: String(sql || ""),
|
statement: positional.sql,
|
||||||
params,
|
params: positional.params,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,10 +390,13 @@ export class AuthoritySqlHttpClient {
|
|||||||
database: this.database,
|
database: this.database,
|
||||||
statements: toArray(statements)
|
statements: toArray(statements)
|
||||||
.filter((statement) => statement?.sql)
|
.filter((statement) => statement?.sql)
|
||||||
.map((statement) => ({
|
.map((statement) => {
|
||||||
statement: String(statement.sql || ""),
|
const positional = convertNamedParamsToPositional(String(statement.sql || ""), statement.params || {});
|
||||||
params: statement.params || {},
|
return {
|
||||||
})),
|
statement: positional.sql,
|
||||||
|
params: positional.params,
|
||||||
|
};
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
AUTHORITY_GRAPH_STORE_MODE,
|
AUTHORITY_GRAPH_STORE_MODE,
|
||||||
AuthorityGraphStore,
|
AuthorityGraphStore,
|
||||||
AuthoritySqlHttpClient,
|
AuthoritySqlHttpClient,
|
||||||
|
convertNamedParamsToPositional,
|
||||||
} from "../sync/authority-graph-store.js";
|
} from "../sync/authority-graph-store.js";
|
||||||
import {
|
import {
|
||||||
BME_DB_SCHEMA_VERSION,
|
BME_DB_SCHEMA_VERSION,
|
||||||
@@ -335,10 +336,61 @@ async function testHttpSqlClientBoundary() {
|
|||||||
assert.deepEqual(JSON.parse(requests[1].init.body), {
|
assert.deepEqual(JSON.parse(requests[1].init.body), {
|
||||||
database: "default",
|
database: "default",
|
||||||
statement: "SELECT 1",
|
statement: "SELECT 1",
|
||||||
params: { chatId: "chat" },
|
params: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function testConvertNamedParamsToPositional() {
|
||||||
|
// Named params with :placeholders get converted to positional ? with array
|
||||||
|
const r1 = convertNamedParamsToPositional(
|
||||||
|
"SELECT * FROM t WHERE chat_id = :chatId AND meta_key = :key",
|
||||||
|
{ chatId: "abc", key: "rev" },
|
||||||
|
);
|
||||||
|
assert.equal(r1.sql, "SELECT * FROM t WHERE chat_id = ? AND meta_key = ?");
|
||||||
|
assert.deepEqual(r1.params, ["abc", "rev"]);
|
||||||
|
|
||||||
|
// Duplicate named params produce multiple positional entries
|
||||||
|
const r2 = convertNamedParamsToPositional(
|
||||||
|
"INSERT INTO t (a, b) VALUES (:chatId, :chatId)",
|
||||||
|
{ chatId: "dup" },
|
||||||
|
);
|
||||||
|
assert.equal(r2.sql, "INSERT INTO t (a, b) VALUES (?, ?)");
|
||||||
|
assert.deepEqual(r2.params, ["dup", "dup"]);
|
||||||
|
|
||||||
|
// No placeholders → empty array
|
||||||
|
const r3 = convertNamedParamsToPositional("SELECT 1", { chatId: "x" });
|
||||||
|
assert.equal(r3.sql, "SELECT 1");
|
||||||
|
assert.deepEqual(r3.params, []);
|
||||||
|
|
||||||
|
// Already-array params pass through unchanged
|
||||||
|
const r4 = convertNamedParamsToPositional("SELECT ?", [42]);
|
||||||
|
assert.equal(r4.sql, "SELECT ?");
|
||||||
|
assert.deepEqual(r4.params, [42]);
|
||||||
|
|
||||||
|
// Empty/null params → empty array
|
||||||
|
const r5 = convertNamedParamsToPositional("SELECT 1", null);
|
||||||
|
assert.deepEqual(r5.params, []);
|
||||||
|
const r6 = convertNamedParamsToPositional("SELECT 1", undefined);
|
||||||
|
assert.deepEqual(r6.params, []);
|
||||||
|
|
||||||
|
// Missing param name → null in array
|
||||||
|
const r7 = convertNamedParamsToPositional(
|
||||||
|
"WHERE x = :x AND y = :y",
|
||||||
|
{ x: 1 },
|
||||||
|
);
|
||||||
|
assert.equal(r7.sql, "WHERE x = ? AND y = ?");
|
||||||
|
assert.deepEqual(r7.params, [1, null]);
|
||||||
|
|
||||||
|
// ::typecast is not treated as a named param
|
||||||
|
const r8 = convertNamedParamsToPositional(
|
||||||
|
"SELECT x::text FROM t WHERE id = :id",
|
||||||
|
{ id: 5 },
|
||||||
|
);
|
||||||
|
assert.equal(r8.sql, "SELECT x::text FROM t WHERE id = ?");
|
||||||
|
assert.deepEqual(r8.params, [5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
await testConvertNamedParamsToPositional();
|
||||||
await testOpenSeedsAuthorityMeta();
|
await testOpenSeedsAuthorityMeta();
|
||||||
await testImportCommitAndExportSnapshot();
|
await testImportCommitAndExportSnapshot();
|
||||||
await testPruneAndClear();
|
await testPruneAndClear();
|
||||||
|
|||||||
Reference in New Issue
Block a user