Skip to content

Commit ba98122

Browse files
authored
Merge pull request #3 from pyconjp/terada/change-metadata
メタデータ変更用スクリプト
2 parents 304049c + 9cfd807 commit ba98122

1 file changed

Lines changed: 126 additions & 0 deletions

File tree

scripts/fix_event_names.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
"""Fix event_name inconsistencies in both DuckDB databases.
2+
3+
Updates event names and years to correct naming variations
4+
originating from Flickr album titles.
5+
6+
DuckDB does not allow UPDATE on a table referenced by a foreign key,
7+
so we temporarily drop and recreate the image_embeddings table.
8+
"""
9+
10+
import argparse
11+
12+
import duckdb
13+
14+
from pyconjp_image_search.config import CLIP_DB_PATH, DB_PATH
15+
16+
# Mapping: (old_event_name) -> (new_event_name, new_event_year)
17+
# new_event_year=None means keep existing year unchanged.
18+
FIXES: list[tuple[str, str, int | None]] = [
19+
("Pycon JP 2019", "PyCon JP 2019", None),
20+
("PyCon APAC Tutorial, Prepare, WelcomeParty", "PyCon APAC 2013", 2013),
21+
("PyCon mini JP", "PyCon mini JP 2011", 2011),
22+
("PyCon US 2025", "PyCon US 2025 report", None),
23+
("Guido meetup", "Guido meetup 2023", 2023),
24+
]
25+
26+
27+
def show_events(conn: duckdb.DuckDBPyConnection) -> None:
28+
rows = conn.execute(
29+
"SELECT event_name, event_year, COUNT(*) AS cnt "
30+
"FROM images GROUP BY event_name, event_year "
31+
"ORDER BY event_name, event_year"
32+
).fetchall()
33+
for name, year, cnt in rows:
34+
print(f" {name:50s} year={year} count={cnt}")
35+
36+
37+
def _fetchone_scalar(conn: duckdb.DuckDBPyConnection, sql: str) -> int:
38+
row = conn.execute(sql).fetchone()
39+
return int(row[0]) if row else 0
40+
41+
42+
def apply_fixes(db_path: str, *, dry_run: bool) -> None:
43+
label = "DRY RUN" if dry_run else "APPLY"
44+
print(f"\n{'=' * 60}")
45+
print(f"[{label}] Database: {db_path}")
46+
print(f"{'=' * 60}")
47+
48+
conn = duckdb.connect(str(db_path), read_only=dry_run)
49+
50+
print("\n--- Before ---")
51+
show_events(conn)
52+
53+
if dry_run:
54+
for old_name, new_name, new_year in FIXES:
55+
if new_year is not None:
56+
sql = "UPDATE images SET event_name = ?, event_year = ? WHERE event_name = ?"
57+
params = [new_name, new_year, old_name]
58+
else:
59+
sql = "UPDATE images SET event_name = ? WHERE event_name = ?"
60+
params = [new_name, old_name]
61+
print(f" [DRY RUN] {sql} params={params}")
62+
else:
63+
# Temporarily drop FK constraint by backing up
64+
# and recreating image_embeddings
65+
has_embeddings = _fetchone_scalar(conn, "SELECT COUNT(*) FROM image_embeddings") > 0
66+
67+
print(" Backing up image_embeddings...")
68+
conn.execute("CREATE TEMPORARY TABLE _emb_backup AS SELECT * FROM image_embeddings")
69+
conn.execute("DROP TABLE image_embeddings")
70+
71+
# Now we can UPDATE images freely
72+
for old_name, new_name, new_year in FIXES:
73+
if new_year is not None:
74+
row = conn.execute(
75+
"UPDATE images SET event_name = ?, event_year = ? WHERE event_name = ?",
76+
[new_name, new_year, old_name],
77+
).fetchone()
78+
else:
79+
row = conn.execute(
80+
"UPDATE images SET event_name = ? WHERE event_name = ?",
81+
[new_name, old_name],
82+
).fetchone()
83+
cnt = int(row[0]) if row else 0
84+
suffix = f" (year={new_year})" if new_year is not None else ""
85+
print(f" Updated {cnt} rows: '{old_name}' -> '{new_name}'" + suffix)
86+
87+
# Restore image_embeddings with FK
88+
print(" Restoring image_embeddings...")
89+
conn.execute("""
90+
CREATE TABLE image_embeddings (
91+
image_id INTEGER NOT NULL,
92+
model_name VARCHAR NOT NULL,
93+
embedding FLOAT[768],
94+
created_at TIMESTAMP DEFAULT current_timestamp,
95+
PRIMARY KEY (image_id, model_name),
96+
FOREIGN KEY (image_id) REFERENCES images(id)
97+
)
98+
""")
99+
conn.execute("INSERT INTO image_embeddings SELECT * FROM _emb_backup")
100+
conn.execute(
101+
"CREATE INDEX IF NOT EXISTS idx_embeddings_model ON image_embeddings(model_name)"
102+
)
103+
conn.execute("DROP TABLE _emb_backup")
104+
105+
restored = _fetchone_scalar(conn, "SELECT COUNT(*) FROM image_embeddings")
106+
print(f" Restored {restored} embeddings (had_data={has_embeddings})")
107+
108+
print("\n--- After ---")
109+
show_events(conn)
110+
111+
conn.close()
112+
113+
114+
def main() -> None:
115+
parser = argparse.ArgumentParser(description="Fix event name inconsistencies in DuckDB")
116+
parser.add_argument("--dry-run", action="store_true", help="Show SQL without executing")
117+
args = parser.parse_args()
118+
119+
for db_path in [DB_PATH, CLIP_DB_PATH]:
120+
apply_fixes(str(db_path), dry_run=args.dry_run)
121+
122+
print("\nDone.")
123+
124+
125+
if __name__ == "__main__":
126+
main()

0 commit comments

Comments
 (0)