Skip to content

Commit c12124e

Browse files
skearnesclaude
andauthored
Accommodate ORM schema reorg for search/browse queries (#207)
* Accommodate ORM schema reorg (ord=index, public=payload, derived=computed) ord-schema's upcoming 0.8 release reorganizes the ORM into role-based schemas: the served Reaction proto moves to public.reactions, per-dataset metadata (num_reactions, submitted_at) to public.datasets, and the generated SMILES plus rdkit_*_id links to the derived.* tables (reaction_smiles, compound_smiles, product_compound_smiles). The ord.* schema is now a pure search index. Update the search/browse queries to read from the new locations: - ReactionSmartsQuery / ReactionComponentQuery join ord.reaction (and the compound tables) through the derived SMILES tables to reach the rdkit_*_id links instead of the dropped ord.reaction.rdkit_reaction_id / compound.rdkit_mol_id columns. - fetch_reactions reads proto from public.reactions. - The dataset-stats aggregation joins the derived SMILES tables for the SMILES. - get_datasets / get_dataset join public.datasets for num_reactions / submitted_at. All joins to the derived tables are INNER, mirroring the prior NULL-column filtering: update_derived_tables inserts a derived row only when a SMILES can be generated, so un-derivable entities are dropped exactly as before. Bump the ord-schema pin to >=0.8,<0.9. uv.lock must be regenerated once 0.8.0 is published. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Accommodate ingest/derive split in setup_test_postgres ord-schema 0.8 splits dataset loading into ingest (add_dataset) and derivation (update_derived_data); update the test-postgres helper to call both, mirroring the ord-schema ORM conftest and preserving the rdkit-cartridge gating. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Regenerate uv.lock for ord-schema 0.8.0 0.8.0 is now published, so resolve the >=0.8,<0.9 pin against it (drops the stale 0.7.1 lock) and pull in uuid6, the new transitive dependency. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 8cafe29 commit c12124e

6 files changed

Lines changed: 74 additions & 29 deletions

File tree

ord_interface/api/queries.py

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ def query_and_parameters(self) -> tuple[str, list]:
154154
query = """
155155
SELECT DISTINCT reaction.reaction_id
156156
FROM ord.reaction
157-
JOIN rdkit.reactions ON rdkit.reactions.id = reaction.rdkit_reaction_id
157+
JOIN derived.reaction_smiles ON reaction_smiles.reaction_id = reaction.id
158+
JOIN rdkit.reactions ON rdkit.reactions.id = reaction_smiles.rdkit_reaction_id
158159
WHERE rdkit.reactions.reaction @> reaction_from_smarts(%s)
159160
"""
160161
return query, [self._reaction_smarts]
@@ -323,12 +324,14 @@ def _mols_join(self) -> LiteralString:
323324
return """
324325
JOIN ord.reaction_input ON reaction_input.reaction_id = reaction.id
325326
JOIN ord.compound ON compound.reaction_input_id = reaction_input.id
326-
JOIN rdkit.mols ON rdkit.mols.id = compound.rdkit_mol_id
327+
JOIN derived.compound_smiles ON compound_smiles.compound_id = compound.id
328+
JOIN rdkit.mols ON rdkit.mols.id = compound_smiles.rdkit_mol_id
327329
"""
328330
return """
329331
JOIN ord.reaction_outcome ON reaction_outcome.reaction_id = reaction.id
330332
JOIN ord.product_compound ON product_compound.reaction_outcome_id = reaction_outcome.id
331-
JOIN rdkit.mols ON rdkit.mols.id = product_compound.rdkit_mol_id
333+
JOIN derived.product_compound_smiles ON product_compound_smiles.product_compound_id = product_compound.id
334+
JOIN rdkit.mols ON rdkit.mols.id = product_compound_smiles.rdkit_mol_id
332335
"""
333336

334337
@property
@@ -544,9 +547,10 @@ async def fetch_reactions(
544547
"""
545548
unique_ids = list(dict.fromkeys(reaction_ids)) # De-dup, preserving order.
546549
query = """
547-
SELECT dataset.dataset_id, reaction.reaction_id, reaction.proto
550+
SELECT dataset.dataset_id, reaction.reaction_id, reactions.proto
548551
FROM ord.reaction
549552
JOIN ord.dataset ON dataset.id = reaction.dataset_id
553+
JOIN public.reactions ON reactions.reaction_id = reaction.reaction_id
550554
WHERE reaction.reaction_id = ANY (%s)
551555
"""
552556
await cursor.execute(query, (unique_ids,))
@@ -574,39 +578,54 @@ async def _fetch_dataset_most_used_smiles(
574578
compound_table: str,
575579
join_table: str,
576580
foreign_key: str,
581+
smiles_table: str,
582+
smiles_foreign_key: str,
577583
limit: int,
578584
) -> list[StatsResult]:
579585
"""Fetches the top K most used SMILES for a dataset, joined through the given tables.
580586
581587
Args:
582588
cursor: Database cursor.
583589
dataset_id: Dataset to aggregate over.
584-
compound_table: Table holding SMILES, e.g. "compound" or "product_compound".
590+
compound_table: Compound table linking to reactions, e.g. "compound" or
591+
"product_compound".
585592
join_table: Table linking compounds to reactions, e.g. "reaction_input".
586593
foreign_key: Column on ``compound_table`` referencing ``join_table``.
594+
smiles_table: Derived table holding the generated SMILES, e.g.
595+
"compound_smiles" or "product_compound_smiles".
596+
smiles_foreign_key: Column on ``smiles_table`` referencing ``compound_table``.
587597
limit: Maximum number of rows to return.
588598
589599
Returns:
590600
Most frequently appearing SMILES, in descending order of frequency.
591601
"""
592602
# Compose schema-qualified identifiers safely rather than interpolating raw
593-
# strings into the SQL text.
603+
# strings into the SQL text. The SMILES live in the derived schema, one row per
604+
# compound, joined back to the compound table by its primary key.
594605
compound = sql.Identifier("ord", compound_table)
595606
join = sql.Identifier("ord", join_table)
607+
smiles = sql.Identifier("derived", smiles_table)
596608
query = sql.SQL(
597609
"""
598-
SELECT smiles, COUNT(*) AS times_appearing
610+
SELECT {smiles}.smiles AS smiles, COUNT(*) AS times_appearing
599611
FROM {compound}
600612
JOIN {join} ON {compound}.{foreign_key} = {join}.id
601613
JOIN ord.reaction ON {join}.reaction_id = ord.reaction.id
602614
JOIN ord.dataset ON ord.reaction.dataset_id = ord.dataset.id
615+
JOIN {smiles} ON {smiles}.{smiles_foreign_key} = {compound}.id
603616
WHERE ord.dataset.dataset_id = %s
604-
AND smiles IS NOT NULL
605-
GROUP BY smiles
617+
AND {smiles}.smiles IS NOT NULL
618+
GROUP BY {smiles}.smiles
606619
ORDER BY times_appearing DESC
607620
LIMIT %s
608621
"""
609-
).format(compound=compound, join=join, foreign_key=sql.Identifier(foreign_key))
622+
).format(
623+
compound=compound,
624+
join=join,
625+
foreign_key=sql.Identifier(foreign_key),
626+
smiles=smiles,
627+
smiles_foreign_key=sql.Identifier(smiles_foreign_key),
628+
)
610629
await cursor.execute(query, (dataset_id, limit))
611630
results = []
612631
async for row in cursor:
@@ -624,6 +643,8 @@ async def fetch_dataset_most_used_smiles_for_inputs(
624643
compound_table="compound",
625644
join_table="reaction_input",
626645
foreign_key="reaction_input_id",
646+
smiles_table="compound_smiles",
647+
smiles_foreign_key="compound_id",
627648
limit=limit,
628649
)
629650

@@ -638,5 +659,7 @@ async def fetch_dataset_most_used_smiles_for_products(
638659
compound_table="product_compound",
639660
join_table="reaction_outcome",
640661
foreign_key="reaction_outcome_id",
662+
smiles_table="product_compound_smiles",
663+
smiles_foreign_key="product_compound_id",
641664
limit=limit,
642665
)

ord_interface/api/queries_test.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,13 +185,15 @@ async def _max_similarities(test_cursor, reaction_ids, pattern, target):
185185
join = """
186186
JOIN ord.reaction_input ON reaction_input.reaction_id = reaction.id
187187
JOIN ord.compound ON compound.reaction_input_id = reaction_input.id
188-
JOIN rdkit.mols ON rdkit.mols.id = compound.rdkit_mol_id
188+
JOIN derived.compound_smiles ON compound_smiles.compound_id = compound.id
189+
JOIN rdkit.mols ON rdkit.mols.id = compound_smiles.rdkit_mol_id
189190
"""
190191
else:
191192
join = """
192193
JOIN ord.reaction_outcome ON reaction_outcome.reaction_id = reaction.id
193194
JOIN ord.product_compound ON product_compound.reaction_outcome_id = reaction_outcome.id
194-
JOIN rdkit.mols ON rdkit.mols.id = product_compound.rdkit_mol_id
195+
JOIN derived.product_compound_smiles ON product_compound_smiles.product_compound_id = product_compound.id
196+
JOIN rdkit.mols ON rdkit.mols.id = product_compound_smiles.rdkit_mol_id
195197
"""
196198
await test_cursor.execute(
197199
f"""

ord_interface/api/search.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,11 @@ async def get_datasets() -> list[DatasetInfo]:
263263
"""Returns info about the current datasets, most recently submitted first."""
264264
async with get_cursor() as cursor:
265265
await cursor.execute(
266-
"SELECT dataset_id, name, description, num_reactions, submitted_at FROM dataset "
267-
"ORDER BY submitted_at DESC NULLS LAST, dataset_id"
266+
"SELECT dataset.dataset_id, dataset.name, dataset.description, "
267+
"datasets.num_reactions, datasets.submitted_at "
268+
"FROM ord.dataset "
269+
"JOIN public.datasets ON datasets.dataset_id = dataset.dataset_id "
270+
"ORDER BY datasets.submitted_at DESC NULLS LAST, dataset.dataset_id"
268271
)
269272
return [DatasetInfo(**row) async for row in cursor]
270273

@@ -274,7 +277,11 @@ async def get_dataset(dataset_id: str) -> DatasetInfo:
274277
"""Returns info about a single dataset."""
275278
async with get_cursor() as cursor:
276279
await cursor.execute(
277-
"SELECT dataset_id, name, description, num_reactions, submitted_at FROM dataset WHERE dataset_id = %s",
280+
"SELECT dataset.dataset_id, dataset.name, dataset.description, "
281+
"datasets.num_reactions, datasets.submitted_at "
282+
"FROM ord.dataset "
283+
"JOIN public.datasets ON datasets.dataset_id = dataset.dataset_id "
284+
"WHERE dataset.dataset_id = %s",
278285
(dataset_id,),
279286
)
280287
row = await cursor.fetchone()

ord_interface/api/testing.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from glob import glob
1919

2020
from ord_schema.message_helpers import load_message
21-
from ord_schema.orm.database import add_dataset, prepare_database
21+
from ord_schema.orm.database import add_dataset, prepare_database, update_derived_data
2222
from ord_schema.proto import dataset_pb2
2323
from sqlalchemy import create_engine
2424
from sqlalchemy.orm import Session
@@ -38,4 +38,7 @@ def setup_test_postgres(url: str) -> None:
3838
with Session(engine) as session:
3939
for dataset in datasets:
4040
with session.begin():
41-
add_dataset(dataset, session, rdkit_cartridge=rdkit_cartridge)
41+
add_dataset(dataset, session)
42+
update_derived_data(
43+
dataset.dataset_id, session, rdkit_cartridge=rdkit_cartridge
44+
)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ dependencies = [
3232
"gunicorn",
3333
"jinja2>=2.0.0",
3434
"numpy>=1.26.4",
35-
"ord-schema[orm]>=0.7.1,<0.8",
35+
"ord-schema[orm]>=0.8,<0.9",
3636
"pandas>=1.0.4",
3737
"protobuf>=4.22,<6",
3838
"psycopg[binary,pool]>=3",

uv.lock

Lines changed: 21 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)