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

+88 -16
View File
@@ -207,22 +207,94 @@ class PostgresSkeletonTestCase(_EnvCleanMixin, unittest.TestCase):
self.assertEqual(db2.host, "explicit-host")
self.assertEqual(db2.dbname, "env-db")
def test_postgres_methods_are_skeleton_not_fake_success(self):
# 绕过 __init__(驱动探测)构造骨架实例,验证方法体本身
# 就是明确的 NotImplementedError,而不是假装成功。
skeleton = object.__new__(PostgresDatabase)
for name in ("connect", "read_all", "transaction",
"is_locked_error"):
method = getattr(skeleton, name)
with self.assertRaises(NotImplementedError):
if name in ("connect", "transaction"):
method(None) if name == "transaction" else method()
elif name == "is_locked_error":
method(ValueError("x"))
else:
method(None, "SELECT 1")
with self.assertRaises(NotImplementedError):
skeleton.execute_with_retry(None, "SELECT 1", ())
def test_postgres_methods_are_implemented_via_fake_driver(self):
# 骨架已由真实实现取代:用 fake 驱动(不连真实 Postgres)
# 验证 connect / read_all / transaction / is_locked_error
# 均可用,且驱动唯一冲突被映射成模块内完整性错误。
from cloud.db import (
PgConnection,
PostgresIntegrityError,
PostgresOperationalError,
)
class _FakeUnique(Exception):
pass
class _FakeCursor:
def __init__(self):
self.executed = []
self.description = [("id",), ("name",)]
def execute(self, sql, params=()):
self.executed.append((sql, params))
if "dup" in sql:
raise _FakeUnique("duplicate key")
return self
def fetchall(self):
return [("t1", "hello")]
def fetchone(self):
return ("t1", "hello")
@property
def rowcount(self):
return 1
def close(self):
pass
class _FakeConn:
def __init__(self):
self.cursor_obj = _FakeCursor()
self.committed = False
self.rolled_back = False
def cursor(self):
return self.cursor_obj
def commit(self):
self.committed = True
def rollback(self):
self.rolled_back = True
def close(self):
pass
class _FakeDriver:
UniqueViolation = _FakeUnique
OperationalError = Exception
Error = Exception
def __init__(self):
self.conn = _FakeConn()
def connect(self, **kwargs):
return self.conn
fake = _FakeDriver()
with mock.patch("cloud.db._load_postgres_driver",
return_value=("psycopg", fake)):
db = PostgresDatabase(host="test-host", dbname="test-db")
conn = db.connect()
self.assertIsInstance(conn, PgConnection)
rows = db.read_all(conn, "SELECT * FROM t WHERE id = ?",
("t1",))
self.assertEqual(rows[0]["name"], "hello")
self.assertEqual(rows[0][0], "t1")
# 唯一冲突 -> 模块内错误,不是驱动原始异常、不是 sqlite 异常。
with self.assertRaises(PostgresIntegrityError):
db.execute_with_retry(conn, "SELECT dup", ())
# 事务回滚:第二条失败后 rollback 被调用且异常原样抛出。
with self.assertRaises(PostgresIntegrityError):
with db.transaction(conn):
conn.execute("SELECT dup", ())
self.assertTrue(fake.conn.rolled_back)
# 锁判定:本模块锁错误 True,其它错误 False。
self.assertTrue(db.is_locked_error(
PostgresOperationalError("deadlock", locked=True)))
self.assertFalse(db.is_locked_error(ValueError("x")))
def test_postgres_repr_never_contains_password(self):
skeleton = object.__new__(PostgresDatabase)