Skip to content

Commit dfb0c4a

Browse files
fix(schema): widen MusicBrainz Discogs ID columns to BIGINT (#376)
brainztableinator throws continuous PostgreSQL "integer out of range" errors when processing MusicBrainz releases: discogs_release_id (and its siblings discogs_artist_id, discogs_label_id, discogs_master_id) were declared INTEGER (int4, max 2,147,483,647), but the extractor parses Discogs IDs as i64 and release IDs now exceed 2.1B. Fix both fresh installs and existing databases: - Change the four cross-reference columns to BIGINT in the CREATE TABLE definitions (fresh installs). - Add idempotent `ALTER COLUMN ... TYPE BIGINT` migrations, since CREATE TABLE IF NOT EXISTS is a no-op on pre-existing tables. Neo4j (brainzgraphinator) is unaffected — Neo4j integers are 64-bit. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b673bac commit dfb0c4a

4 files changed

Lines changed: 63 additions & 9 deletions

File tree

brainztableinator/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ CREATE TABLE musicbrainz.artists (
8080
begin_area TEXT,
8181
end_area TEXT,
8282
disambiguation TEXT,
83-
discogs_artist_id INTEGER,
83+
discogs_artist_id BIGINT,
8484
aliases JSONB,
8585
tags JSONB,
8686
data JSONB,
@@ -102,7 +102,7 @@ CREATE TABLE musicbrainz.labels (
102102
ended BOOLEAN DEFAULT FALSE,
103103
area TEXT,
104104
disambiguation TEXT,
105-
discogs_label_id INTEGER,
105+
discogs_label_id BIGINT,
106106
data JSONB,
107107
created_at TIMESTAMPTZ DEFAULT NOW(),
108108
updated_at TIMESTAMPTZ DEFAULT NOW()
@@ -118,7 +118,7 @@ CREATE TABLE musicbrainz.releases (
118118
barcode TEXT,
119119
status TEXT,
120120
release_group_mbid UUID,
121-
discogs_release_id INTEGER,
121+
discogs_release_id BIGINT,
122122
data JSONB,
123123
created_at TIMESTAMPTZ DEFAULT NOW(),
124124
updated_at TIMESTAMPTZ DEFAULT NOW()
@@ -135,7 +135,7 @@ CREATE TABLE musicbrainz.release_groups (
135135
secondary_types JSONB,
136136
first_release_date TEXT,
137137
disambiguation TEXT,
138-
discogs_master_id INTEGER,
138+
discogs_master_id BIGINT,
139139
data JSONB,
140140
created_at TIMESTAMPTZ DEFAULT NOW(),
141141
updated_at TIMESTAMPTZ DEFAULT NOW()

schema-init/postgres_schema.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@
539539
begin_area TEXT,
540540
end_area TEXT,
541541
disambiguation TEXT,
542-
discogs_artist_id INTEGER,
542+
discogs_artist_id BIGINT,
543543
aliases JSONB,
544544
tags JSONB,
545545
data JSONB,
@@ -559,7 +559,7 @@
559559
ended BOOLEAN DEFAULT FALSE,
560560
area TEXT,
561561
disambiguation TEXT,
562-
discogs_label_id INTEGER,
562+
discogs_label_id BIGINT,
563563
data JSONB,
564564
created_at TIMESTAMPTZ DEFAULT NOW(),
565565
updated_at TIMESTAMPTZ DEFAULT NOW()
@@ -573,7 +573,7 @@
573573
barcode TEXT,
574574
status TEXT,
575575
release_group_mbid UUID,
576-
discogs_release_id INTEGER,
576+
discogs_release_id BIGINT,
577577
data JSONB,
578578
created_at TIMESTAMPTZ DEFAULT NOW(),
579579
updated_at TIMESTAMPTZ DEFAULT NOW()
@@ -588,7 +588,7 @@
588588
secondary_types JSONB,
589589
first_release_date TEXT,
590590
disambiguation TEXT,
591-
discogs_master_id INTEGER,
591+
discogs_master_id BIGINT,
592592
data JSONB,
593593
created_at TIMESTAMPTZ DEFAULT NOW(),
594594
updated_at TIMESTAMPTZ DEFAULT NOW()
@@ -623,6 +623,27 @@
623623
UNIQUE (mbid, entity_type, service_name, url)
624624
)""",
625625
),
626+
# Widen Discogs cross-reference IDs from INTEGER (int4, max 2,147,483,647)
627+
# to BIGINT. Discogs IDs are emitted as i64 by the extractor and release IDs
628+
# now exceed 2.1B, causing "integer out of range" on INSERT. CREATE TABLE
629+
# IF NOT EXISTS above won't alter pre-existing tables, so widen explicitly.
630+
# ALTER COLUMN ... TYPE BIGINT is a no-op when the column is already BIGINT.
631+
(
632+
"musicbrainz.artists.discogs_artist_id widen to BIGINT",
633+
"ALTER TABLE musicbrainz.artists ALTER COLUMN discogs_artist_id TYPE BIGINT",
634+
),
635+
(
636+
"musicbrainz.labels.discogs_label_id widen to BIGINT",
637+
"ALTER TABLE musicbrainz.labels ALTER COLUMN discogs_label_id TYPE BIGINT",
638+
),
639+
(
640+
"musicbrainz.releases.discogs_release_id widen to BIGINT",
641+
"ALTER TABLE musicbrainz.releases ALTER COLUMN discogs_release_id TYPE BIGINT",
642+
),
643+
(
644+
"musicbrainz.release_groups.discogs_master_id widen to BIGINT",
645+
"ALTER TABLE musicbrainz.release_groups ALTER COLUMN discogs_master_id TYPE BIGINT",
646+
),
626647
]
627648

628649

tests/schema-init/test_musicbrainz_schema.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,40 @@ def test_musicbrainz_tables_defined():
1414

1515

1616
def test_musicbrainz_tables_use_if_not_exists():
17+
# ALTER migrations (e.g. widening columns to BIGINT) cannot use IF NOT EXISTS;
18+
# only CREATE TABLE statements must be idempotent via IF NOT EXISTS.
1719
for name, sql in _MUSICBRAINZ_TABLES:
18-
if name != "musicbrainz schema":
20+
if "CREATE TABLE" in sql:
1921
assert "IF NOT EXISTS" in sql, f"{name} missing IF NOT EXISTS"
2022

2123

24+
def test_musicbrainz_discogs_id_columns_are_bigint():
25+
# Discogs IDs are i64 from the extractor and now exceed int4 range (2.1B);
26+
# cross-reference columns must be BIGINT to avoid "integer out of range".
27+
expected = {
28+
"musicbrainz.artists table": "discogs_artist_id BIGINT",
29+
"musicbrainz.labels table": "discogs_label_id BIGINT",
30+
"musicbrainz.releases table": "discogs_release_id BIGINT",
31+
"musicbrainz.release_groups table": "discogs_master_id BIGINT",
32+
}
33+
found = dict.fromkeys(expected, False)
34+
for name, sql in _MUSICBRAINZ_TABLES:
35+
if name in expected:
36+
assert expected[name] in sql, f"{name} missing {expected[name]}"
37+
found[name] = True
38+
assert all(found.values()), f"missing tables: {[n for n, ok in found.items() if not ok]}"
39+
40+
41+
def test_musicbrainz_has_bigint_widening_migrations():
42+
# Existing databases need explicit ALTER COLUMN ... TYPE BIGINT; CREATE TABLE
43+
# IF NOT EXISTS is a no-op on tables that already exist as INTEGER.
44+
migrations = [sql for name, sql in _MUSICBRAINZ_TABLES if "ALTER COLUMN" in sql]
45+
assert any("musicbrainz.artists ALTER COLUMN discogs_artist_id TYPE BIGINT" in m for m in migrations)
46+
assert any("musicbrainz.labels ALTER COLUMN discogs_label_id TYPE BIGINT" in m for m in migrations)
47+
assert any("musicbrainz.releases ALTER COLUMN discogs_release_id TYPE BIGINT" in m for m in migrations)
48+
assert any("musicbrainz.release_groups ALTER COLUMN discogs_master_id TYPE BIGINT" in m for m in migrations)
49+
50+
2251
def test_musicbrainz_indexes_defined():
2352
index_names = [name for name, _ in _MUSICBRAINZ_INDEXES]
2453
assert "idx_mb_artists_discogs_id" in index_names

tests/schema-init/test_postgres_schema.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,10 @@ async def capture(stmt: Any, *_: Any, **__: Any) -> None:
232232

233233
for stmt in captured:
234234
upper = stmt.upper()
235+
# ALTER COLUMN ... TYPE is inherently idempotent (re-applying the same
236+
# target type is a no-op), so it needs no IF NOT EXISTS guard.
237+
if "ALTER COLUMN" in upper and "TYPE " in upper:
238+
continue
235239
assert "IF NOT EXISTS" in upper, f"Statement is not idempotent: {stmt[:80]}..."
236240
# A multi-statement blob could still hide an un-guarded DROP that would
237241
# not be idempotent — any DROP must be guarded with IF EXISTS.

0 commit comments

Comments
 (0)