Postgres real backend: psycopg adapter, schema, migration

Replace the PostgresDatabase skeleton with a real implementation:
psycopg (v3) first, psycopg2 second, lazy load, missing driver
still raises PostgresDriverMissingError with no silent SQLite
fallback. A sqlite3-style adapter (PgConnection/PgCursor/PgRow)
translates ? placeholders to %s outside string literals, maps
UNIQUE/FK violations, lock errors and connection failures to
explicit Postgres error types, and handles INSERT OR REPLACE,
PRAGMA table_info, AUTOINCREMENT and FTS5 refusal. POSTGRES_SCHEMA
covers all business tables with tasks result columns; server
init_db uses it under CLOUD_DB_BACKEND=postgres, SQLite path
unchanged. migrate_sqlite_to_postgres.py copies rows with
ON CONFLICT DO NOTHING and reseeds identity sequences.
Tests: cloud 239 (235 pass, 4 skipped without a real server),
host 27 pass; real-server tests skip unless CLOUD_TEST_PG_DSN
is set.
This commit is contained in:
host committed 2026-10-26 03:23:06 +08:00
1 parent a19e77ee44
commit dc3f572b2b
10 files changed
+1542 -66

No files matched your search

+3 -2
View File
@@ -18,6 +18,7 @@ Design notes:
import sqlite3
from cloud.db import connect as _db_connect
from cloud.db import PostgresOperationalError as _PgOperationalError
import time
_KINDS = ("file", "readme", "doc", "task", "review")
@@ -47,7 +48,7 @@ class KBStore:
"CREATE VIRTUAL TABLE IF NOT EXISTS kb_fts USING fts5(content)"
)
self._fts = True
except sqlite3.OperationalError:
except (sqlite3.OperationalError, _PgOperationalError):
self._fts = False
self.conn.commit()
@@ -129,7 +130,7 @@ class KBStore:
# "用户登录流程") returns zero FTS hits. LIKE substring
# search recovers those cases; the project_id filter
# still applies, so isolation is unchanged.
except sqlite3.OperationalError:
except (sqlite3.OperationalError, _PgOperationalError):
# Query syntax FTS5 cannot parse, or a broken FTS table:
# fall through to the LIKE path rather than crashing.
pass