Skip to content

Allow strings instead of jsons in queries' dimensions, measures, time dimensions, and order.column#15

Merged
ZmeiGorynych merged 5 commits intomainfrom
egor/dev-1221-allow-plain-strings-as-some-of-the-entries-in
Apr 9, 2026
Merged

Allow strings instead of jsons in queries' dimensions, measures, time dimensions, and order.column#15
ZmeiGorynych merged 5 commits intomainfrom
egor/dev-1221-allow-plain-strings-as-some-of-the-entries-in

Conversation

@ZmeiGorynych
Copy link
Copy Markdown
Member

@ZmeiGorynych ZmeiGorynych commented Apr 9, 2026

  1. fields list items — plain string treated as {"formula": string}
    "fields": ["count", "revenue_sum", "cumsum(revenue)"]
    // equivalent to
    "fields": [{"formula": "count"}, {"formula": "revenue_sum"}, {"formula": "cumsum(revenue)"}]
    Mix-and-match works: ["count", {"formula": "revenue / count", "name": "aov", "label": "Avg Order Value"}]

  2. dimensions list items — plain string treated as {"name": string}
    "dimensions": ["status", "customers.regions.name"]
    // equivalent to
    "dimensions": [{"name": "status"}, {"name": "customers.regions.name"}]
    Dotted join paths work. Use the dict form when you need label or sql.

  3. time_dimensions[].dimension — plain string treated as {"name": string}
    "time_dimensions": [{"dimension": "created_at", "granularity": "month"}]
    // equivalent to
    "time_dimensions": [{"dimension": {"name": "created_at"}, "granularity": "month"}]

  4. order[].column — plain string treated as {"name": string}
    "order": [{"column": "revenue_sum", "direction": "desc"}]
    // equivalent to
    "order": [{"column": {"name": "revenue_sum"}, "direction": "desc"}]

In all four cases:

  • Implemented via Pydantic BeforeValidator on the model fields, so it works everywhere (Python, REST API, MCP)
  • The full dict syntax still works unchanged
  • Dotted join paths (e.g. "customers.regions.name") are handled correctly — the string becomes a ColumnRef whose _parse_dotted_name validator splits it
  • The MCP server's manual _parse_column_ref, _normalize_time_dimensions, and _normalize_order helpers were removed since the models now handle coercion directly

Summary by CodeRabbit

Release Notes

  • New Features

    • Query API now accepts simplified string formats for field formulas, dimensions, and order specifications alongside the previous object-based syntax. For example, fields: ["count"] instead of fields: [{"formula": "count"}] and dimensions: ["status"] instead of dimensions: [{"name": "status"}].
  • Documentation

    • Updated all guides, examples, and API documentation to demonstrate the new simplified syntax.
  • Tests

    • Added comprehensive test coverage for the new string coercion functionality.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

The pull request simplifies the query interface by allowing fields, dimensions, time_dimensions.dimension, and order.column to accept plain strings instead of structured objects. Coercion validators are added to core query models, documentation is updated throughout with simplified examples, MCP server normalization logic is removed, and test coverage is added for string-to-object coercion behavior.

Changes

Cohort / File(s) Summary
Documentation Examples
docs/concepts/formulas.md, docs/concepts/ingestion.md, docs/concepts/queries.md, docs/concepts/models.md, docs/examples/03_auto_ingest/auto_ingest.md, docs/examples/04_time/time.md, docs/examples/05_joined_measures/joined_measures.md, docs/examples/06_multistage_queries/multistage_queries.md, docs/getting-started.md, docs/interfaces/cli.md, docs/interfaces/rest-api.md
Updated query examples to use simplified string format for fields, dimensions, time_dimensions[].dimension, and order[].column instead of object wrappers.
API Interface Documentation
docs/interfaces/mcp.md, docs/interfaces/python-client.md
Updated API documentation to reflect string-based inputs for query parameters; removed ColumnRef from examples and adjusted type descriptions.
Core Query Model
slayer/core/query.py
Added _coerce_column_ref, _coerce_fields, and _coerce_dimensions validators using Pydantic BeforeValidator to coerce plain strings into appropriate object structures; updated TimeDimension.dimension, OrderItem.column, SlayerQuery.fields, and SlayerQuery.dimensions type annotations.
MCP Server
slayer/mcp/server.py
Removed _parse_column_ref, _normalize_time_dimensions, and _normalize_order helper functions; simplified query tool to pass arguments directly without preprocessing normalization.
Test Coverage
tests/test_models.py
Removed TestNormalization import; added TestStringCoercion covering string-to-object coercion for fields, dimensions, order, and time_dimensions, including dotted-name parsing and mixed string/dict list support.
Notebook Examples
docs/examples/02_sql_vs_dsl/sql_vs_dsl_nb.ipynb, docs/examples/03_auto_ingest/auto_ingest_nb.ipynb, docs/examples/04_time/time_nb.ipynb, docs/examples/05_joined_measures/joined_measures_nb.ipynb, docs/examples/05_joins/joins_nb.ipynb, docs/examples/06_multistage_queries/multistage_queries_nb.ipynb
Updated query payload syntax in code cells to use simplified string format; added/updated execution metadata timestamps and reformatted markdown cell source arrays.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PR #14: Overlaps with dotted-name parsing and coercion logic for handling column references and string-to-object transformation patterns.
  • PR #9: Modifies ColumnRef and dotted-name support alongside query model changes that intersect with this coercion implementation.
  • PR #10: Updates the same SlayerQuery class definition with additional parameter flags.

Poem

🐰 Strings now dance where objects once stood tall,
Coercion magic transforms them all,
No wrapping dicts for fields and dims—
Just names and formulas on a whim,
Simpler queries, cleaner calls! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: allowing strings as shorthand alternatives to JSON objects in query parameters (dimensions, measures/fields, time dimensions, and order columns).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch egor/dev-1221-allow-plain-strings-as-some-of-the-entries-in

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
docs/examples/03_auto_ingest/auto_ingest.ipynb (1)

379-379: Inconsistent order.column format — should use shorthand string.

The order entries still use the verbose object form {"column": {"name": "quantity_sum"}, "direction": "desc"} while other fields (fields, dimensions) have been updated to use shorthand strings.

For consistency with the PR's shorthand syntax and other updated examples, this should be:

-    "order": [{"column": {"name": "quantity_sum"}, "direction": "desc"}],
+    "order": [{"column": "quantity_sum", "direction": "desc"}],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/examples/03_auto_ingest/auto_ingest.ipynb` at line 379, Replace the
verbose order entry objects inside the engine.execute calls with the shorthand
string form for the column (e.g., replace {"column": {"name": "quantity_sum"},
"direction": "desc"} with "quantity_sum") in both query invocations shown (the
1-hop and multi-hop examples) so the order list matches the shorthand syntax
used for fields and dimensions; update the order arrays passed to engine.execute
accordingly.
docs/examples/06_multistage_queries/multistage_queries.ipynb (1)

173-173: Use shorthand order.column here for consistency with the rest of the notebook.

Line 173 and Line 193 still use nested {"column": {"name": ...}}; switching to {"column": "..."} would match the new style shown elsewhere in this PR.

Also applies to: 193-193

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/examples/06_multistage_queries/multistage_queries.ipynb` at line 173,
The order specification for the engine.execute query uses the verbose form
{"column": {"name": "..."}}; update the query passed to engine.execute (the dict
assigned to query in the Stage 1 total orders per customer block that produces
inner_result) to use the shorthand form {"column": "orders.count"} (and
similarly for the other occurrence in the notebook) so the order entries follow
the consistent {"column": "<column_name>","direction": "desc"} style used
elsewhere in this PR.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@slayer/core/query.py`:
- Around line 121-133: The coercion helpers _coerce_fields and
_coerce_dimensions currently iterate over any non-None input, causing scalar
strings or dicts to be treated like iterables (e.g., "count" → chars); update
both functions to first validate the input is a list (or tuple) and raise a
TypeError (or ValueError) if it's a scalar or other non-iterable type, then
perform the existing comprehension only on the validated sequence; specifically
change _coerce_fields and _coerce_dimensions to check isinstance(v, (list,
tuple)) before mapping and return or raise for anything else to fail fast on
invalid inputs.

---

Nitpick comments:
In `@docs/examples/03_auto_ingest/auto_ingest.ipynb`:
- Line 379: Replace the verbose order entry objects inside the engine.execute
calls with the shorthand string form for the column (e.g., replace {"column":
{"name": "quantity_sum"}, "direction": "desc"} with "quantity_sum") in both
query invocations shown (the 1-hop and multi-hop examples) so the order list
matches the shorthand syntax used for fields and dimensions; update the order
arrays passed to engine.execute accordingly.

In `@docs/examples/06_multistage_queries/multistage_queries.ipynb`:
- Line 173: The order specification for the engine.execute query uses the
verbose form {"column": {"name": "..."}}; update the query passed to
engine.execute (the dict assigned to query in the Stage 1 total orders per
customer block that produces inner_result) to use the shorthand form {"column":
"orders.count"} (and similarly for the other occurrence in the notebook) so the
order entries follow the consistent {"column": "<column_name>","direction":
"desc"} style used elsewhere in this PR.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b7600f03-1f9a-40d5-aa43-4572a351ac9f

📥 Commits

Reviewing files that changed from the base of the PR and between b7191ec and 1099147.

📒 Files selected for processing (23)
  • docs/concepts/formulas.md
  • docs/concepts/ingestion.md
  • docs/concepts/models.md
  • docs/concepts/queries.md
  • docs/examples/02_sql_vs_dsl/sql_vs_dsl.ipynb
  • docs/examples/03_auto_ingest/auto_ingest.ipynb
  • docs/examples/03_auto_ingest/auto_ingest.md
  • docs/examples/04_time/time.ipynb
  • docs/examples/04_time/time.md
  • docs/examples/05_joined_measures/joined_measures.ipynb
  • docs/examples/05_joined_measures/joined_measures.md
  • docs/examples/05_joins/joins.ipynb
  • docs/examples/06_multistage_queries/multistage_queries.ipynb
  • docs/examples/06_multistage_queries/multistage_queries.md
  • docs/getting-started.md
  • docs/interfaces/cli.md
  • docs/interfaces/mcp.md
  • docs/interfaces/python-client.md
  • docs/interfaces/rest-api.md
  • slayer/core/query.py
  • slayer/mcp/server.py
  • tests/test_mcp_server.py
  • tests/test_models.py
💤 Files with no reviewable changes (1)
  • tests/test_mcp_server.py

@AivanF AivanF marked this pull request as draft April 9, 2026 10:56
@AivanF AivanF marked this pull request as ready for review April 9, 2026 10:56
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
docs/examples/05_joins/joins.ipynb (2)

152-156: Add explicit ordering for reproducible example outputs.

These examples display limited/grouped results but don’t define a stable order, so printed rows can vary across runs/backends.

Deterministic ordering example
 result = engine.execute(query={
     "source_model": "orders",
     "fields": ["count"],
     "dimensions": ["stores.name"],
+    "order": [{"column": "count", "direction": "desc"}, {"column": "stores.name", "direction": "asc"}],
 })
 result = engine.execute(query={
     "source_model": "order_items",
     "fields": ["count"],
     "dimensions": ["orders.customers.name"],
+    "order": [{"column": "count", "direction": "desc"}, {"column": "orders.customers.name", "direction": "asc"}],
     "limit": 3,
 })

Also applies to: 271-276

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/examples/05_joins/joins.ipynb` around lines 152 - 156, The query passed
to engine.execute (the dict with "source_model": "orders", "fields": ["count"],
"dimensions": ["stores.name"]) lacks an explicit ordering which makes example
outputs non-deterministic; update these example queries (including the similar
block at lines referenced 271-276) to include a stable "order" clause (e.g.,
ordering by the grouped dimension and/or aggregated field) in the query dict so
printed rows are reproducible across runs and backends (modify the example query
objects used by engine.execute to add an "order" key with a deterministic sort
specification).

27-34: Strip volatile notebook execution metadata to reduce diff noise.

The timestamp-heavy metadata.execution blocks will churn on every run and obscure meaningful doc changes. Consider clearing execution metadata before committing notebooks.

Suggested normalization pattern
- "metadata": {
-  "execution": {
-   "iopub.execute_input": "...",
-   "iopub.status.busy": "...",
-   "iopub.status.idle": "...",
-   "shell.execute_reply": "..."
-  }
- },
+ "metadata": {},

Also applies to: 62-69, 128-135, 167-174, 225-232, 299-306, 378-385, 424-431, 485-492

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/examples/05_joins/joins.ipynb` around lines 27 - 34, The notebook
contains volatile timestamped execution metadata under the "metadata.execution"
objects (e.g., keys like "iopub.execute_input", "iopub.status.busy",
"iopub.status.idle", "shell.execute_reply") which churns diffs; remove or
sanitize these blocks across the notebook (and similar per-cell execution fields
such as "execution_count" and per-cell "outputs" if desired) before committing.
Use a normalization step (nbstripout, nbformat-based script, or CI pre-commit
hook) to strip "metadata.execution" and other transient execution fields from
the joins.ipynb content so that only meaningful notebook changes remain. Ensure
you apply the same stripping to the other listed ranges/cells to eliminate
repeated timestamped entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@docs/examples/05_joins/joins.ipynb`:
- Around line 152-156: The query passed to engine.execute (the dict with
"source_model": "orders", "fields": ["count"], "dimensions": ["stores.name"])
lacks an explicit ordering which makes example outputs non-deterministic; update
these example queries (including the similar block at lines referenced 271-276)
to include a stable "order" clause (e.g., ordering by the grouped dimension
and/or aggregated field) in the query dict so printed rows are reproducible
across runs and backends (modify the example query objects used by
engine.execute to add an "order" key with a deterministic sort specification).
- Around line 27-34: The notebook contains volatile timestamped execution
metadata under the "metadata.execution" objects (e.g., keys like
"iopub.execute_input", "iopub.status.busy", "iopub.status.idle",
"shell.execute_reply") which churns diffs; remove or sanitize these blocks
across the notebook (and similar per-cell execution fields such as
"execution_count" and per-cell "outputs" if desired) before committing. Use a
normalization step (nbstripout, nbformat-based script, or CI pre-commit hook) to
strip "metadata.execution" and other transient execution fields from the
joins.ipynb content so that only meaningful notebook changes remain. Ensure you
apply the same stripping to the other listed ranges/cells to eliminate repeated
timestamped entries.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6bb3d5e5-934f-405c-8595-df60d0da9e4f

📥 Commits

Reviewing files that changed from the base of the PR and between 1099147 and ceda4b9.

📒 Files selected for processing (6)
  • docs/examples/02_sql_vs_dsl/sql_vs_dsl.ipynb
  • docs/examples/03_auto_ingest/auto_ingest.ipynb
  • docs/examples/04_time/time.ipynb
  • docs/examples/05_joined_measures/joined_measures.ipynb
  • docs/examples/05_joins/joins.ipynb
  • docs/examples/06_multistage_queries/multistage_queries.ipynb
🚧 Files skipped from review as they are similar to previous changes (5)
  • docs/examples/03_auto_ingest/auto_ingest.ipynb
  • docs/examples/04_time/time.ipynb
  • docs/examples/05_joined_measures/joined_measures.ipynb
  • docs/examples/06_multistage_queries/multistage_queries.ipynb
  • docs/examples/02_sql_vs_dsl/sql_vs_dsl.ipynb

…ev-1221-allow-plain-strings-as-some-of-the-entries-in
…ev-1221-allow-plain-strings-as-some-of-the-entries-in
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
docs/examples/03_auto_ingest/auto_ingest_nb.ipynb (1)

462-481: Consider using shorthand for order.column here too for style consistency.

This notebook now mixes shorthand and nested {"name": ...} in adjacent examples. Since shorthand is the focus of this PR, switching these two order.column entries keeps the docs uniform.

Suggested consistency patch
-    "order": [{"column": {"name": "quantity_sum"}, "direction": "desc"}],
+    "order": [{"column": "quantity_sum", "direction": "desc"}],
@@
-    "order": [{"column": {"name": "quantity_sum"}, "direction": "desc"}],
+    "order": [{"column": "quantity_sum", "direction": "desc"}],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/examples/03_auto_ingest/auto_ingest_nb.ipynb` around lines 462 - 481,
The notebook mixes shorthand and nested {"name": ...} styles in adjacent
examples for the order column; update the two engine.execute calls that set
"order": [{ "column": { "name": "quantity_sum" }, "direction": "desc" }] to use
the shorthand form (e.g. "order": [{ "column": "quantity_sum", "direction":
"desc" }]) so both the 1-hop (order_items -> products) and multi-hop
(order_items -> orders -> customers) queries use the same shorthand style and
keep the docs consistent; look for the engine.execute invocations and the
"order" entries to apply the change.
docs/examples/06_multistage_queries/multistage_queries_nb.ipynb (1)

309-315: Optional: switch remaining order.column objects to shorthand for consistency.

These two query blocks still use {"column": {"name": ...}} while surrounding updates use string shorthand.

Suggested consistency patch
-    "order": [{"column": {"name": "count"}, "direction": "desc"}],
+    "order": [{"column": "count", "direction": "desc"}],
@@
-        "order": [{"column": {"name": "order_total_sum"}, "direction": "desc"}],
+        "order": [{"column": "order_total_sum", "direction": "desc"}],

Also applies to: 390-393

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/examples/06_multistage_queries/multistage_queries_nb.ipynb` around lines
309 - 315, The query objects passed to engine.execute use verbose order entries
like {"column": {"name": "count"}}, which should be converted to the shorthand
string form used elsewhere; update the order arrays in the inner_result query
(and the later similar query) to use order entries with "column": "count" (i.e.,
replace the nested {"column": {"name": ...}} objects with the equivalent string
shorthand) so the query objects are consistent with the surrounding examples.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@docs/examples/03_auto_ingest/auto_ingest_nb.ipynb`:
- Around line 462-481: The notebook mixes shorthand and nested {"name": ...}
styles in adjacent examples for the order column; update the two engine.execute
calls that set "order": [{ "column": { "name": "quantity_sum" }, "direction":
"desc" }] to use the shorthand form (e.g. "order": [{ "column": "quantity_sum",
"direction": "desc" }]) so both the 1-hop (order_items -> products) and
multi-hop (order_items -> orders -> customers) queries use the same shorthand
style and keep the docs consistent; look for the engine.execute invocations and
the "order" entries to apply the change.

In `@docs/examples/06_multistage_queries/multistage_queries_nb.ipynb`:
- Around line 309-315: The query objects passed to engine.execute use verbose
order entries like {"column": {"name": "count"}}, which should be converted to
the shorthand string form used elsewhere; update the order arrays in the
inner_result query (and the later similar query) to use order entries with
"column": "count" (i.e., replace the nested {"column": {"name": ...}} objects
with the equivalent string shorthand) so the query objects are consistent with
the surrounding examples.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 51a81eee-adad-4156-a813-faef85e47ec9

📥 Commits

Reviewing files that changed from the base of the PR and between 2b4113a and 80baef9.

📒 Files selected for processing (10)
  • docs/examples/02_sql_vs_dsl/sql_vs_dsl_nb.ipynb
  • docs/examples/03_auto_ingest/auto_ingest.md
  • docs/examples/03_auto_ingest/auto_ingest_nb.ipynb
  • docs/examples/04_time/time.md
  • docs/examples/04_time/time_nb.ipynb
  • docs/examples/05_joined_measures/joined_measures.md
  • docs/examples/05_joined_measures/joined_measures_nb.ipynb
  • docs/examples/05_joins/joins_nb.ipynb
  • docs/examples/06_multistage_queries/multistage_queries.md
  • docs/examples/06_multistage_queries/multistage_queries_nb.ipynb
✅ Files skipped from review due to trivial changes (4)
  • docs/examples/06_multistage_queries/multistage_queries.md
  • docs/examples/04_time/time.md
  • docs/examples/05_joined_measures/joined_measures.md
  • docs/examples/03_auto_ingest/auto_ingest.md

@AivanF AivanF self-requested a review April 9, 2026 13:25
@ZmeiGorynych ZmeiGorynych merged commit 2a61051 into main Apr 9, 2026
3 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Apr 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants