Preserve dataflow layout across SQL edits
This commit is contained in:
@@ -22,6 +22,12 @@ class SqlCompilationError(ValueError):
|
||||
self.diagnostics = diagnostics
|
||||
|
||||
|
||||
LAYOUT_ORIGIN_X = 60
|
||||
LAYOUT_CENTER_Y = 180
|
||||
LAYOUT_LAYER_GAP = 240
|
||||
LAYOUT_BRANCH_GAP = 120
|
||||
|
||||
|
||||
def compile_sql(
|
||||
sql_text: str,
|
||||
*,
|
||||
@@ -90,12 +96,13 @@ def compile_sql(
|
||||
[_sql_error("sql.union_join", "JOIN outside UNION BY NAME is not supported.")]
|
||||
)
|
||||
union_tables, union_mode = _union_tables(union_expression)
|
||||
branch_positions = _branch_positions(len(union_tables))
|
||||
for index, table in enumerate(union_tables, start=1):
|
||||
source = _source_node(
|
||||
table,
|
||||
source_node_list,
|
||||
fallback_id=f"source-{index}",
|
||||
position=GraphPosition(x=60, y=80 + ((index - 1) * 140)),
|
||||
position=branch_positions[index - 1],
|
||||
)
|
||||
if any(existing.id == source.id for existing in nodes):
|
||||
raise SqlCompilationError(
|
||||
@@ -111,7 +118,11 @@ def compile_sql(
|
||||
id="union",
|
||||
type="combine.union",
|
||||
label="Append rows",
|
||||
position=GraphPosition(x=300, y=80 + ((len(nodes) - 1) * 70)),
|
||||
position=_position(
|
||||
1,
|
||||
y=sum(position.y for position in branch_positions)
|
||||
/ len(branch_positions),
|
||||
),
|
||||
config={"mode": union_mode},
|
||||
)
|
||||
nodes.append(union_node)
|
||||
@@ -125,6 +136,7 @@ def compile_sql(
|
||||
if source.type.startswith("source.")
|
||||
)
|
||||
previous_node_id = union_node.id
|
||||
next_transform_layer = 2
|
||||
else:
|
||||
left_table = from_clause.this
|
||||
if not isinstance(left_table, exp.Table):
|
||||
@@ -146,16 +158,22 @@ def compile_sql(
|
||||
left_table,
|
||||
source_node_list,
|
||||
fallback_id="source-left" if right_table is not None else "source",
|
||||
position=GraphPosition(x=60, y=120 if right_table is not None else 180),
|
||||
position=(
|
||||
_branch_positions(2)[0]
|
||||
if right_table is not None
|
||||
else _position(0)
|
||||
),
|
||||
)
|
||||
nodes.append(left_source)
|
||||
previous_node_id = left_source.id
|
||||
next_transform_layer = 1
|
||||
if isinstance(right_table, exp.Table):
|
||||
join_positions = _branch_positions(2)
|
||||
right_source = _source_node(
|
||||
right_table,
|
||||
source_node_list,
|
||||
fallback_id="source-right",
|
||||
position=GraphPosition(x=60, y=280),
|
||||
position=join_positions[1],
|
||||
)
|
||||
if right_source.id == left_source.id:
|
||||
raise SqlCompilationError(
|
||||
@@ -168,7 +186,7 @@ def compile_sql(
|
||||
id="join",
|
||||
type="combine.join",
|
||||
label=f"Join {left_table.name} and {right_table.name}",
|
||||
position=GraphPosition(x=300, y=200),
|
||||
position=_position(1),
|
||||
config=join_config,
|
||||
)
|
||||
nodes.extend((right_source, join_node))
|
||||
@@ -194,6 +212,13 @@ def compile_sql(
|
||||
right_table,
|
||||
right_prefix=right_prefix,
|
||||
)
|
||||
next_transform_layer = 2
|
||||
|
||||
def next_transform_position() -> GraphPosition:
|
||||
nonlocal next_transform_layer
|
||||
position = _position(next_transform_layer)
|
||||
next_transform_layer += 1
|
||||
return position
|
||||
|
||||
def append_transform(node: GraphNode) -> None:
|
||||
nonlocal previous_node_id
|
||||
@@ -214,7 +239,7 @@ def compile_sql(
|
||||
id=f"filter-{index}",
|
||||
type="filter",
|
||||
label=f"Filter {index}",
|
||||
position=_position(len(nodes)),
|
||||
position=next_transform_position(),
|
||||
config=_condition_config(
|
||||
condition,
|
||||
qualifier_prefixes=qualifier_prefixes,
|
||||
@@ -286,7 +311,7 @@ def compile_sql(
|
||||
id="aggregate",
|
||||
type="aggregate",
|
||||
label="Aggregate",
|
||||
position=_position(len(nodes)),
|
||||
position=next_transform_position(),
|
||||
config={"group_by": group_by, "aggregates": aggregate_specs},
|
||||
)
|
||||
)
|
||||
@@ -296,7 +321,7 @@ def compile_sql(
|
||||
id="select",
|
||||
type="select",
|
||||
label="Select columns",
|
||||
position=_position(len(nodes)),
|
||||
position=next_transform_position(),
|
||||
config={"fields": projection_fields},
|
||||
)
|
||||
)
|
||||
@@ -311,7 +336,7 @@ def compile_sql(
|
||||
id="distinct",
|
||||
type="distinct",
|
||||
label="Remove duplicates",
|
||||
position=_position(len(nodes)),
|
||||
position=next_transform_position(),
|
||||
config={"columns": []},
|
||||
)
|
||||
)
|
||||
@@ -337,7 +362,7 @@ def compile_sql(
|
||||
id="sort",
|
||||
type="sort",
|
||||
label="Sort",
|
||||
position=_position(len(nodes)),
|
||||
position=next_transform_position(),
|
||||
config={"fields": fields},
|
||||
)
|
||||
)
|
||||
@@ -360,7 +385,7 @@ def compile_sql(
|
||||
id="limit",
|
||||
type="limit",
|
||||
label="Limit",
|
||||
position=_position(len(nodes)),
|
||||
position=next_transform_position(),
|
||||
config={"count": count},
|
||||
)
|
||||
)
|
||||
@@ -370,7 +395,7 @@ def compile_sql(
|
||||
id="output",
|
||||
type="output",
|
||||
label="Preview output",
|
||||
position=_position(len(nodes)),
|
||||
position=next_transform_position(),
|
||||
config={},
|
||||
)
|
||||
)
|
||||
@@ -801,7 +826,12 @@ def _source_node(
|
||||
),
|
||||
None,
|
||||
)
|
||||
return preserved or GraphNode(
|
||||
if preserved is not None:
|
||||
return preserved.model_copy(
|
||||
update={"position": position},
|
||||
deep=True,
|
||||
)
|
||||
return GraphNode(
|
||||
id=fallback_id,
|
||||
type="source.reference",
|
||||
label=source_name,
|
||||
@@ -1099,8 +1129,22 @@ def _column_name(
|
||||
return f"{prefix}{expression.name}"
|
||||
|
||||
|
||||
def _position(index: int) -> GraphPosition:
|
||||
return GraphPosition(x=80 + index * 220, y=180)
|
||||
def _position(layer: int, *, y: float = LAYOUT_CENTER_Y) -> GraphPosition:
|
||||
return GraphPosition(
|
||||
x=LAYOUT_ORIGIN_X + layer * LAYOUT_LAYER_GAP,
|
||||
y=y,
|
||||
)
|
||||
|
||||
|
||||
def _branch_positions(count: int) -> list[GraphPosition]:
|
||||
top = max(
|
||||
40,
|
||||
LAYOUT_CENTER_Y - ((count - 1) * LAYOUT_BRANCH_GAP / 2),
|
||||
)
|
||||
return [
|
||||
_position(0, y=top + index * LAYOUT_BRANCH_GAP)
|
||||
for index in range(count)
|
||||
]
|
||||
|
||||
|
||||
def _combine_and(expressions: list[exp.Expression]) -> exp.Expression:
|
||||
|
||||
Reference in New Issue
Block a user