Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/linkml_map/compiler/sql_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def compile_class(
if not col_trs:
return
stmt += ", \n".join(col_trs)
stmt += f" FROM {cd.name}"
stmt += f" FROM {cd.populated_from or cd.name}"
compiled.serialization += f"{stmt};\n"

def compile_slot_derivation(self, sd) -> str:
Expand All @@ -85,7 +85,7 @@ def compile_slot_derivation(self, sd) -> str:
expr = f"CAST({expr} AS TEXT)"
elif delimiter:
expr = f"STRING_AGG({expr}, '{delimiter}')"
return f" {sd.name} AS {expr}"
return f" {expr} AS {sd.name}"

def create_ddl(self, schemaview: SchemaView) -> str:
"""
Expand Down
103 changes: 102 additions & 1 deletion tests/test_compiler/test_duckdb_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,103 @@ def test_hidden_slots_excluded_from_sql() -> None:
assert "label" in compiled.serialization


def test_from_clause_uses_class_populated_from() -> None:
"""SQLCompiler must use class_derivation.populated_from as the FROM table.

Issue #48: previously the FROM clause used cd.name (the target class name),
producing INSERT INTO Target ... FROM Target — a self-INSERT that reads
from the empty target table and silently inserts zero rows.
"""
from linkml_map.datamodel.transformer_model import (
ClassDerivation,
SlotDerivation,
TransformationSpecification,
)

spec = TransformationSpecification(
id="test",
class_derivations={
"Agent": ClassDerivation(
name="Agent",
populated_from="Person",
slot_derivations={
"label": SlotDerivation(name="label", populated_from="name"),
},
),
},
)
compiled = SQLCompiler().compile(spec)
assert "INSERT INTO Agent" in compiled.serialization
assert "FROM Person" in compiled.serialization
assert "FROM Agent" not in compiled.serialization


def test_slot_aliased_source_as_target() -> None:
"""SQLCompiler must emit `source AS target`, not `target AS source`.

Issue #48: with `label: populated_from: name`, the generated SQL must read
the `name` column from the source table and alias it as `label` in the
result. The inverted form references a `label` column that doesn't exist
in the source.
"""
from linkml_map.datamodel.transformer_model import (
ClassDerivation,
SlotDerivation,
TransformationSpecification,
)

spec = TransformationSpecification(
id="test",
class_derivations={
"Agent": ClassDerivation(
name="Agent",
populated_from="Person",
slot_derivations={
"label": SlotDerivation(name="label", populated_from="name"),
},
),
},
)
compiled = SQLCompiler().compile(spec)
assert "name AS label" in compiled.serialization
assert "label AS name" not in compiled.serialization


def test_compiled_sql_executes_against_duckdb() -> None:
"""End-to-end: compiled INSERT reads from source table and lands rows in target.

Issue #48: with both bugs (FROM clause + AS direction), source rows never
reached target tables. This verifies the round-trip on a minimal spec.
"""
from linkml_map.datamodel.transformer_model import (
ClassDerivation,
SlotDerivation,
TransformationSpecification,
)

spec = TransformationSpecification(
id="test",
class_derivations={
"Agent": ClassDerivation(
name="Agent",
populated_from="Person",
slot_derivations={
"label": SlotDerivation(name="label", populated_from="name"),
},
),
},
)
compiled = SQLCompiler().compile(spec)

conn = duckdb.connect(":memory:")
conn.execute("CREATE TABLE Person (name TEXT);")
conn.execute("CREATE TABLE Agent (label TEXT);")
conn.execute("INSERT INTO Person VALUES ('Alice'), ('Bob');")
conn.execute(compiled.serialization)
rows = conn.execute("SELECT label FROM Agent ORDER BY label;").fetchall()
assert rows == [("Alice",), ("Bob",)]


def test_compile(session: Session) -> None:
"""Test the DuckDb compiler."""
compiler = SQLCompiler()
Expand All @@ -73,5 +170,9 @@ def test_compile(session: Session) -> None:
print(target_ddl)

conn = duckdb.connect(":memory:")
conn.execute(source_ddl)
conn.execute(target_ddl)
conn.execute(compiled.serialization)
# TODO #150: compiled INSERTs don't yet provide every target column for
# arbitrary specs (the target's derived schema can include columns not
# named by any slot_derivation). Re-enable execution once SQLCompiler
# emits explicit column lists / handles full target shape.
Loading