fix(db): исправить импорт jsonb из sqlite
Docker images / prepare-release (push) Successful in 11s
Docker images / backend-test (push) Failing after 12s
Docker images / backend-image (push) Skipped
Docker images / frontend-image (push) Successful in 3m14s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 47s
Docker images / publish-release (push) Skipped

JSON-колонки передаются строкой с приведением ::jsonb, иначе node-pg кодирует массивы как PostgreSQL array и ETL падает. Повтор импорта идёт, пока нет маркера. Том PG18 монтируется в /var/lib/postgresql.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-09-08 09:28:03 +07:00
co-authored by Cursor
parent ec43591a99
commit 30a8ec4420
11 changed files with 66 additions and 13 deletions
+15 -5
View File
@@ -278,9 +278,11 @@ function coerce(kind: ColKind, value: unknown, strict: boolean, rejects: string[
case "bool":
return parseBool(value)
case "json":
return parseJson(value, [])
// Always JSON text. JS arrays must not go to node-pg as values — it encodes
// them as PG arrays (`{...}`), which jsonb rejects (22P02).
return JSON.stringify(parseJson(value, []))
case "json-null":
return value == null || value === "" ? null : parseJson(value, null)
return value == null || value === "" ? null : JSON.stringify(parseJson(value, null))
case "bigint-id": {
const t = String(value ?? "").trim()
if (!t) return null
@@ -341,7 +343,9 @@ async function copyTable(
const total = (sqlite.prepare(`SELECT COUNT(*) AS c FROM ${spec.table}${where}`).get() as { c: number }).c
const part = specForParent(spec.table)
const cols = spec.columns.map(([c]) => c)
const placeholders = cols.map((_, i) => `$${i + 1}`).join(", ")
const placeholders = spec.columns
.map(([, kind], i) => (kind === "json" || kind === "json-null" ? `$${i + 1}::jsonb` : `$${i + 1}`))
.join(", ")
const conflictSql = spec.upsert
? `ON CONFLICT (id) DO UPDATE SET ${cols.filter((c) => c !== "id").map((c) => `${c} = EXCLUDED.${c}`).join(", ")}`
: `ON CONFLICT DO NOTHING`
@@ -362,7 +366,8 @@ async function copyTable(
await client.query("COMMIT")
} catch (err) {
await client.query("ROLLBACK")
throw err
const detail = err instanceof Error ? err.message : String(err)
throw new Error(`${spec.table}: ${detail}`)
} finally {
client.release()
batch.length = 0
@@ -421,7 +426,11 @@ export async function shouldImportSqlite(pool: Pool, sqlitePath: string): Promis
)
if (marker.rows[0]?.sqlite_imported_at) return false
const servers = await pool.query<{ c: string }>(`SELECT COUNT(*)::text AS c FROM servers`)
if (Number(servers.rows[0]?.c ?? 0) > 0) return false
if (Number(servers.rows[0]?.c ?? 0) > 0) {
console.warn(
"SQLite → PostgreSQL: повтор недописанного импорта (маркера нет, servers уже не пустые)",
)
}
return true
}
@@ -462,6 +471,7 @@ export async function importSqliteToPostgres(
return report
}
for (const spec of TABLES) {
console.log(`SQLite → PostgreSQL: таблица ${spec.table}`)
report.tables[spec.table] = await copyTable(sqlite, pool, spec, { strict, fullHistory, rejects })
}
sqlite.close()