feat: PostgreSQL sink (psycopg2 COPY FROM STDIN) - #4
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…etup() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Claude finished @starychenko's task in 3m 13s —— View job Огляд PR #4: PostgreSQL sink (psycopg2 COPY FROM STDIN)
РезюмеPR додає Вердикт: 🔴 Request Changes — знайдено 1 блокер та 3 важливі проблеми. 🔴 BLOCKER
with conn.cursor() as cur:
cur.execute(
f"CREATE TABLE IF NOT EXISTS {self._full_table()} ({cols_ddl})"
)
conn.commit() # ← якщо cur.execute() кинув виняток — сюди не дійдеЯкщо Аналогічна проблема є в блоці Виправлення: загорнути весь 🟡 IMPORTANT1. Порожній рядок ↔ NULL — втрата розрізнення ( buf = io.StringIO()
df.to_csv(buf, index=False, header=False, na_rep="") # pandas NaN → ""
...
f"FROM STDIN WITH (FORMAT CSV, NULL '')" # "" → NULL у PostgreSQL
2. Подвійний виклик
3. def _get_conn(self):
if self._conn is None or self._conn.closed: # ← race condition без lock
self._conn = psycopg2.connect(...)
return self._connЯкщо два потоки одночасно перевірять 🔵 NIT
Функція визначена поміж Що зроблено добре
|
|
Claude finished @starychenko's task in 1m 26s —— View job Огляд PR #4 (повторна перевірка після оновлення гілки)
РезюмеПісля синхронізації гілки додано 2 фіксуючі коміти ( Вердикт: 🟡 Comment — блокер знято, але 1 важлива проблема залишається відкритою. Що виправлено ✅BLOCKER → виправлено ( try:
with conn.cursor() as cur:
cur.execute(f"CREATE TABLE IF NOT EXISTS ...")
conn.commit()
except Exception:
conn.rollback()
raise
Thread safety → задокументовано (docstring класу, рядки 446–453) Обмеження явно задокументовано — прийнятне рішення.
🟡 Залишається відкритимПорожній рядок ↔ NULL — втрата розрізнення ( df.to_csv(buf, index=False, header=False, na_rep="") # NaN → ""
# ...
f"FROM STDIN WITH (FORMAT CSV, NULL '')" # "" → NULL у PostgreSQLРеальні порожні рядки у текстових колонках ( Рекомендоване виправлення — використати sentinel-значення df.to_csv(buf, index=False, header=False, na_rep=r"\N")
# ...
f"FROM STDIN WITH (FORMAT CSV, NULL '\\N')"🔵 NIT (залишається)
Функція визначена між Що зроблено добре
|
|
Claude finished @starychenko's task in 1m 23s —— View job Огляд PR #4 (оновлення гілки)
Результат перевіркиВсі 4 зауваження з попереднього огляду (1 blocker + 3 important) — виправлено. Деталі нижче. Вердикт: ✅ Approve — блокерів немає, важливих проблем немає. Що виправлено🔴 BLOCKER → ✅ Виправлено
🟡 IMPORTANT #1 → ✅ Виправлено NULL-sentinel df.to_csv(buf, index=False, header=False, na_rep="\\N")
# ...
r"FROM STDIN WITH (FORMAT CSV, NULL '\N')"Тепер реальні порожні рядки 🟡 IMPORTANT #2 → ✅ Виправлено
🟡 IMPORTANT #3 → ✅ Виправлено Клас 🔵 NIT → ✅ Виправлено
Що зроблено добре (залишається)
|
Summary
PostgreSQLSinkяк третій аналітичний sink поряд із ClickHouse та DuckDBpsycopg2COPY FROM STDIN— найшвидший метод для PostgreSQLsslmode=require) для self-signed сертифікатівЗміни
olap_tool/config.py—PostgreSQLConfigdataclass,load_postgres_from_env(), інтеграція вAppConfigтаbuild_config()olap_tool/sinks.py—PostgreSQLSink:CREATE TABLE IF NOT EXISTS,ALTER TABLE ADD COLUMN IF NOT EXISTS,DELETE(ідемпотентність),COPY FROM STDIN CSV; thread-safe_schema_lock; rollback guard уdelete_period/insertolap_tool/queries.py—ch_only→sink_only, доданоPG/POSTGRESQLдо всіх перевірок форматуolap_tool/runner.py— info-блок підключення + побудова sinkolap_tool/cli.py—--formatприймаєpg,postgresqlrequirements.txt—psycopg2-binary>=2.9.0.env.example— документаціяPG_*зміннихКонфігурація (.env)
Test Plan
python -c "from olap_tool.sinks import PostgreSQLSink; print('OK')"→ OKpython olap.py --format pg --last-weeks 1— завантажує дані в PostgreSQL без XLSXpython olap.py --last-weeks 1зPG_ENABLED=true— паралельно з XLSXSELECT COUNT(*) FROM public.sales🤖 Generated with Claude Code