Skip to content

Commit 7825bce

Browse files
committed
fix: Use proper JSON contains function for PostgreSQL
Fix `json_array_contains_value` function to use the `@>` operator for checking if a JSON array contains a value in PostgreSQL. This is necessary because the `has_key` function only works for string values. Also, remove `get_rom_collections` method, as it was doing the same thing as `get_collections_by_rom_id`. Fixes #1441.
1 parent fb02db6 commit 7825bce

File tree

4 files changed

+30
-26
lines changed

4 files changed

+30
-26
lines changed

backend/handler/database/collections_handler.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
from collections.abc import Sequence
2+
from typing import Any
3+
14
from decorators.database import begin_session
25
from models.collection import Collection
36
from sqlalchemy import Select, delete, select, update
47
from sqlalchemy.orm import Session
8+
from sqlalchemy.sql import ColumnExpressionArgument
9+
from utils.database import json_array_contains_value
510

611
from .base_handler import DBBaseHandler
712

@@ -37,11 +42,19 @@ def get_collections(self, session: Session = None) -> Select[tuple[Collection]]:
3742

3843
@begin_session
3944
def get_collections_by_rom_id(
40-
self, rom_id: int, session: Session = None
45+
self,
46+
rom_id: int,
47+
*,
48+
order_by: Sequence[str | ColumnExpressionArgument[Any]] | None = None,
49+
session: Session = None,
4150
) -> list[Collection]:
42-
return session.scalars(
43-
select(Collection).filter(Collection.roms.contains(rom_id))
44-
).all()
51+
query = select(Collection).filter(
52+
json_array_contains_value(Collection.roms, rom_id, session=session)
53+
)
54+
if order_by is not None:
55+
query = query.order_by(*order_by)
56+
57+
return session.scalars(query).all()
4558

4659
@begin_session
4760
def update_collection(

backend/handler/database/roms_handler.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from models.rom import Rom, RomUser
77
from sqlalchemy import and_, delete, func, or_, select, update
88
from sqlalchemy.orm import Query, Session, selectinload
9-
from utils.database import json_array_contains_value
109

1110
from .base_handler import DBBaseHandler
1211

@@ -181,24 +180,6 @@ def get_rom_by_filename_no_ext(
181180
query.filter_by(file_name_no_ext=file_name_no_ext).limit(1)
182181
)
183182

184-
@begin_session
185-
def get_rom_collections(
186-
self, rom: Rom, session: Session = None
187-
) -> list[Collection]:
188-
return (
189-
session.scalars(
190-
select(Collection)
191-
.filter(
192-
json_array_contains_value(
193-
Collection.roms, str(rom.id), session=session
194-
)
195-
)
196-
.order_by(Collection.name.asc())
197-
)
198-
.unique()
199-
.all()
200-
)
201-
202183
@begin_session
203184
def update_rom(self, id: int, data: dict, session: Session = None) -> Rom:
204185
return session.execute(

backend/models/rom.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
String,
1818
Text,
1919
UniqueConstraint,
20+
func,
2021
)
2122
from sqlalchemy.orm import Mapped, mapped_column, relationship
2223
from utils.database import CustomJSON
@@ -141,9 +142,12 @@ def merged_screenshots(self) -> list[str]:
141142
return screenshots
142143

143144
def get_collections(self) -> list[Collection]:
144-
from handler.database import db_rom_handler
145+
from handler.database import db_collection_handler
145146

146-
return db_rom_handler.get_rom_collections(self)
147+
return db_collection_handler.get_collections_by_rom_id(
148+
self.id,
149+
order_by=[func.lower("name")],
150+
)
147151

148152
# Metadata fields
149153
@property

backend/utils/database.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,11 @@ def json_array_contains_value(
2121
"""Check if a JSON array column contains a single value."""
2222
conn = session.get_bind()
2323
if is_postgresql(conn):
24-
return sa.type_coerce(column, sa_pg.JSONB()).has_key(value)
24+
# In PostgreSQL, string values can be checked for containment using the `?` operator.
25+
# For other types, we use the `@>` operator.
26+
if isinstance(value, str):
27+
return sa.type_coerce(column, sa_pg.JSONB).has_key(value)
28+
return sa.type_coerce(column, sa_pg.JSONB).contains(
29+
func.cast(value, sa_pg.JSONB)
30+
)
2531
return func.json_contains(column, value)

0 commit comments

Comments
 (0)