From 5dae0aeed376b342fd3d0468889ad192af202b0f Mon Sep 17 00:00:00 2001 From: Bartok9 Date: Sun, 19 Jul 2026 02:25:17 -0400 Subject: [PATCH 1/4] fix(memory): skip non-dict models/columns in seed query generation --- core/wren/src/wren/memory/seed_queries.py | 29 +++++++++++++------ .../tests/unit/test_seed_queries_nonduct.py | 22 ++++++++++++++ 2 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 core/wren/tests/unit/test_seed_queries_nonduct.py diff --git a/core/wren/src/wren/memory/seed_queries.py b/core/wren/src/wren/memory/seed_queries.py index cfc8a2e26d..81d7b74867 100644 --- a/core/wren/src/wren/memory/seed_queries.py +++ b/core/wren/src/wren/memory/seed_queries.py @@ -31,13 +31,19 @@ def generate_seed_queries(manifest: dict) -> list[dict]: """Return a list of {"nl": ..., "sql": ...} seed pairs.""" pairs: list[dict] = [] + models = manifest.get("models", []) or [] + if not isinstance(models, list): + models = [] model_layers = { model["name"]: _prop_value(model, "dbtLayer", "dbt_layer") - for model in manifest.get("models", []) + for model in models + if isinstance(model, dict) and model.get("name") is not None } relationship_keys = _relationship_key_columns(manifest) - for model in manifest.get("models", []): + for model in models: + if not isinstance(model, dict) or model.get("name") is None: + continue if model_layers.get(model["name"]) == "raw": continue pairs.extend( @@ -46,10 +52,14 @@ def generate_seed_queries(manifest: dict) -> list[dict]: ) ) - for rel in manifest.get("relationships", []): - pair = _relationship_seed(rel, model_layers) - if pair: - pairs.append(pair) + rels = manifest.get("relationships", []) or [] + if isinstance(rels, list): + for rel in rels: + if not isinstance(rel, dict): + continue + pair = _relationship_seed(rel, model_layers) + if pair: + pairs.append(pair) return pairs @@ -58,9 +68,10 @@ def _model_seeds( model: dict, relationship_keys: frozenset[str] = frozenset() ) -> list[dict]: name = model["name"] - columns = [ - c for c in (model.get("columns") or []) if isinstance(c, dict) and c.get("name") - ] + columns = model.get("columns", []) or [] + if not isinstance(columns, list): + columns = [] + columns = [c for c in columns if isinstance(c, dict) and c.get("name") is not None] primary_keys = _primary_key_columns(model) pairs = [] diff --git a/core/wren/tests/unit/test_seed_queries_nonduct.py b/core/wren/tests/unit/test_seed_queries_nonduct.py new file mode 100644 index 0000000000..30248a5da4 --- /dev/null +++ b/core/wren/tests/unit/test_seed_queries_nonduct.py @@ -0,0 +1,22 @@ +from wren.memory.seed_queries import generate_seed_queries + + +def test_generate_seed_queries_skips_nonduct_models_and_columns(): + pairs = generate_seed_queries( + { + "models": [ + { + "name": "orders", + "columns": [ + {"name": "amount", "type": "double"}, + "bad", + {"type": "int"}, + ], + }, + "nope", + ], + "relationships": ["x"], + } + ) + assert any("orders" in p["nl"] for p in pairs) + assert all(isinstance(p.get("sql"), str) for p in pairs) From 92ae87c536c6eb04ab306d1e28116533e0d969a5 Mon Sep 17 00:00:00 2001 From: Bartok9 Date: Sun, 19 Jul 2026 02:25:36 -0400 Subject: [PATCH 2/4] fix(memory): guard relationship key columns against non-dict rels --- core/wren/src/wren/memory/seed_queries.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/wren/src/wren/memory/seed_queries.py b/core/wren/src/wren/memory/seed_queries.py index 81d7b74867..4313a5274b 100644 --- a/core/wren/src/wren/memory/seed_queries.py +++ b/core/wren/src/wren/memory/seed_queries.py @@ -177,7 +177,12 @@ def _relationship_key_columns(manifest: dict) -> dict[str, frozenset[str]]: of aggregation seeds. """ accum: dict[str, set[str]] = {} - for rel in manifest.get("relationships", []): + rels = manifest.get("relationships", []) or [] + if not isinstance(rels, list): + return {} + for rel in rels: + if not isinstance(rel, dict): + continue condition = rel.get("condition") or "" try: tree = sqlglot.parse_one(condition) From 41329ccc7378476ac197a05c44f2310d4bacef6f Mon Sep 17 00:00:00 2001 From: Bartok9 Date: Sun, 19 Jul 2026 03:07:25 -0400 Subject: [PATCH 3/4] fix(memory): use isinstance str check for column names in seed queries Guards against malformed column names (int/bool) that pass 'is not None' but fail on _norm_ident .strip(). Addresses CodeRabbit review on #2542. --- core/wren/src/wren/memory/seed_queries.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/wren/src/wren/memory/seed_queries.py b/core/wren/src/wren/memory/seed_queries.py index 4313a5274b..d1f7ec4aad 100644 --- a/core/wren/src/wren/memory/seed_queries.py +++ b/core/wren/src/wren/memory/seed_queries.py @@ -71,7 +71,9 @@ def _model_seeds( columns = model.get("columns", []) or [] if not isinstance(columns, list): columns = [] - columns = [c for c in columns if isinstance(c, dict) and c.get("name") is not None] + columns = [ + c for c in columns if isinstance(c, dict) and isinstance(c.get("name"), str) + ] primary_keys = _primary_key_columns(model) pairs = [] From c24f0b58040d524d7ebd1242fa73db318efae9ca Mon Sep 17 00:00:00 2001 From: Bartok9 Date: Wed, 22 Jul 2026 20:07:42 -0400 Subject: [PATCH 4/4] fix(redshift): use public strip_trailing_semicolon on unlimited path CI failed NameError/F821: unlimited query path called undefined _strip_trailing_semicolon while limited path already imports the public strip_trailing_semicolon helper. --- core/wren/src/wren/connector/redshift.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/wren/src/wren/connector/redshift.py b/core/wren/src/wren/connector/redshift.py index 918c8a2244..4ac76b4940 100644 --- a/core/wren/src/wren/connector/redshift.py +++ b/core/wren/src/wren/connector/redshift.py @@ -52,7 +52,7 @@ def query(self, sql: str, limit: int | None = None) -> pa.Table: else: # Unlimited path also rejects trailing ``;`` for single statements # depending on driver/session settings — strip for consistency. - sql = _strip_trailing_semicolon(sql) + sql = strip_trailing_semicolon(sql) with closing(self.connection.cursor()) as cursor: cursor.execute(sql) cols = [desc[0] for desc in cursor.description]