Preserve dataflow layout across SQL edits
This commit is contained in:
@@ -13,8 +13,10 @@ from govoplan_dataflow.backend.schemas import (
|
||||
GraphEdge,
|
||||
GraphNode,
|
||||
GraphPosition,
|
||||
PipelineDraftRequest,
|
||||
PipelineGraph,
|
||||
)
|
||||
from govoplan_dataflow.backend.service import compile_sql_draft
|
||||
from govoplan_dataflow.backend.sql_compiler import SqlCompilationError, compile_sql, render_sql
|
||||
|
||||
|
||||
@@ -301,6 +303,14 @@ class DataflowGraphAndSqlTests(unittest.TestCase):
|
||||
["source.inline", "source.inline", "combine.union", "output"],
|
||||
[node.type for node in roundtrip.nodes],
|
||||
)
|
||||
self.assertEqual(
|
||||
[60, 60, 300, 540],
|
||||
[node.position.x for node in roundtrip.nodes],
|
||||
)
|
||||
self.assertEqual(
|
||||
120,
|
||||
roundtrip.nodes[1].position.y - roundtrip.nodes[0].position.y,
|
||||
)
|
||||
self.assertEqual(result.rows, execute_preview(roundtrip, row_limit=100).rows)
|
||||
|
||||
def test_legacy_composite_edge_ids_remain_readable(self) -> None:
|
||||
@@ -310,6 +320,110 @@ class DataflowGraphAndSqlTests(unittest.TestCase):
|
||||
|
||||
self.assertEqual(edge_id, edge.id)
|
||||
|
||||
def test_sql_roundtrip_preserves_layout_for_compatible_topology(self) -> None:
|
||||
compiled, sql_text, _ = compile_sql(
|
||||
"""
|
||||
SELECT *
|
||||
FROM monthly_files
|
||||
WHERE status = 'open'
|
||||
ORDER BY amount DESC
|
||||
""",
|
||||
source_nodes=[inline_source()],
|
||||
)
|
||||
id_map = {
|
||||
node.id: f"custom-node-{index}"
|
||||
for index, node in enumerate(compiled.nodes, start=1)
|
||||
}
|
||||
reference = compiled.model_copy(
|
||||
update={
|
||||
"nodes": [
|
||||
node.model_copy(
|
||||
update={
|
||||
"id": id_map[node.id],
|
||||
"label": f"Custom {index}",
|
||||
"position": GraphPosition(
|
||||
x=111 + index * 137,
|
||||
y=73 + index * 41,
|
||||
),
|
||||
},
|
||||
deep=True,
|
||||
)
|
||||
for index, node in enumerate(compiled.nodes, start=1)
|
||||
],
|
||||
"edges": [
|
||||
edge.model_copy(
|
||||
update={
|
||||
"id": f"custom-edge-{index}",
|
||||
"source": id_map[edge.source],
|
||||
"target": id_map[edge.target],
|
||||
},
|
||||
deep=True,
|
||||
)
|
||||
for index, edge in enumerate(compiled.edges, start=1)
|
||||
],
|
||||
},
|
||||
deep=True,
|
||||
)
|
||||
reference = reference.model_copy(
|
||||
update={
|
||||
"nodes": list(reversed(reference.nodes)),
|
||||
"edges": list(reversed(reference.edges)),
|
||||
},
|
||||
deep=True,
|
||||
)
|
||||
|
||||
response = compile_sql_draft(
|
||||
PipelineDraftRequest(
|
||||
graph=reference,
|
||||
sql_text=sql_text.replace("'open'", "'closed'"),
|
||||
)
|
||||
)
|
||||
|
||||
self.assertTrue(response.valid)
|
||||
self.assertIsNotNone(response.graph)
|
||||
assert response.graph is not None
|
||||
self.assertEqual(
|
||||
[
|
||||
(node.id, node.label, node.position)
|
||||
for node in reference.nodes
|
||||
],
|
||||
[
|
||||
(node.id, node.label, node.position)
|
||||
for node in response.graph.nodes
|
||||
],
|
||||
)
|
||||
self.assertEqual(
|
||||
[edge.id for edge in reference.edges],
|
||||
[edge.id for edge in response.graph.edges],
|
||||
)
|
||||
filter_node = next(
|
||||
node
|
||||
for node in response.graph.nodes
|
||||
if node.type == "filter"
|
||||
)
|
||||
self.assertEqual("closed", filter_node.config["value"])
|
||||
|
||||
def test_changed_sql_topology_uses_compact_layer_layout(self) -> None:
|
||||
reference, _, _ = compile_sql(
|
||||
"SELECT * FROM monthly_files WHERE status = 'open'",
|
||||
source_nodes=[inline_source()],
|
||||
)
|
||||
|
||||
response = compile_sql_draft(
|
||||
PipelineDraftRequest(
|
||||
graph=reference,
|
||||
sql_text="SELECT * FROM monthly_files",
|
||||
)
|
||||
)
|
||||
|
||||
self.assertTrue(response.valid)
|
||||
self.assertIsNotNone(response.graph)
|
||||
assert response.graph is not None
|
||||
self.assertEqual(
|
||||
[60, 300],
|
||||
[node.position.x for node in response.graph.nodes],
|
||||
)
|
||||
|
||||
def test_connector_source_uses_bounded_resolver_and_records_lineage(self) -> None:
|
||||
source = GraphNode(
|
||||
id="connector",
|
||||
|
||||
Reference in New Issue
Block a user