Troubleshooting
What the common errors mean and how to fix them, from a deploy the loader rejects to a query the planner refuses. 0sql refuses a query it cannot answer correctly instead of returning a wrong number; the message names the field or combination it could not resolve, and that is the thing to fix in the model, not something to work around in the request.
Every HTTP error has the same shape, and zsql prints it as <class>: <message>:
{"error": {"class": "Planner::ResolutionError", "message": "No universe can resolve the query within datasource tpcds."}}
Quick reference
| Symptom | Status and class | Start here |
|---|---|---|
no project.yml here or above | local zsql | Deploy errors |
Error in models/...: ... | 400 DeployError | Model validation |
the archive holds no project.yml | 400 DeployError | Deploy errors |
tests failed | exit code only; deploy was 200 | Deploy errors |
this branch has security policies; a context is required to plan | 400 ContextRequired | Plan errors |
No field named 'x' in this model. | 422 Semantic::NotFound | Fuzzy field corrections |
No universe can resolve the query within datasource ... | 422 Planner::ResolutionError | Query resolution |
Security policy context dimension '...' is not reachable | 422 Planner::SecurityPolicyError | Query resolution |
an API key is required / this API key is not valid | 401 Unauthorized | Auth errors |
query key ... is not granted ... | 403 Forbidden | Auth errors |
| A field you did not ask for appears in the SQL | corrections in the response | Fuzzy field corrections |
| Numbers too high | model, not request | Numbers look wrong |
Deploy errors
zsql deploy tars the project directory and POSTs it; zsql check does the same against /validate and stores nothing. Both fail with a 400 DeployError whose message says which stage refused.
no project.yml here or above; run zsql init to start a project
Local. zsql walks up from the current directory looking for project.yml. Run it inside the project, or pass --project <dir>.
the body must be a tar.gz of the project directory, unpacking the archive: ..., the archive holds no project.yml
Only reachable when you POST to /deploy yourself. The body must be a gzip tar with project.yml at its root or inside one top-level folder. zsql deploy builds this for you.
Error in <file>: <message>
The loader rejected a file. The message is one of the model validation messages below; fix the file and run zsql check until it is clean, then zsql deploy. Nothing is deployed when any file fails: the branch keeps its previous model.
snapshot has N integrity problems:
A compiled model that references something missing. Each line is of the form expression <e> references missing field, join <j> left table <id> is missing, field <f> mapped twice on table <t>, table <t> snapshot date <f> is not a dimension, partition <p> dimension <d> is missing or not a dimension, or duplicate <kind> uid <uid>. Almost always a rename: a table or field was renamed in one file and still referenced by its old name in a relation, a snapshot:, a partition or a formula.
query keys are read only; deploy with your personal key
- The key in
.zsqlorZSQL_API_KEYstarts withzqk_. Deploy with azsk_key. See Auth errors.
Deploy succeeded but zsql deploy exited with tests failed
The server returns 200 even when tests fail; the model is live. zsql prints PASSED, FAILED or ERROR per test with --- generated and --- expected blocks, then exits non-zero. A failing assert_sql usually means the model changed and the expected SQL is stale: compare, update the test, redeploy. ERROR means the test’s projections no longer plan (a renamed field) or its assert_regex does not compile. See Tests.
Warnings
A deploy can succeed with warning: lines. They are worth fixing:
| Warning | Meaning |
|---|---|
<file>: table <name> has no cost; 0 assumed | Add cost: so routing prefers the right table. |
<file>: table|field|join <k>: unknown key '<k>' is ignored | A typo in a YAML key. The setting is not applied. |
<file>: partition on <dim>: predicate <p> is not supported / needs a number or date dimension | The partition will not rank the table. See Partitions. |
formation: root <r> reaches <t> by two routes of cost <c>: ... | Two equal-cost join paths; one was chosen. Set costs so the choice is deliberate. |
formation: ambiguous: from <r> the field "<f>" is reachable at cost <c> through A and B; A is used | Same field reachable through two dimension tables. Set costs or split the dimension. |
policy <uid> context dimension <d> not found; policy is inert | The policy’s context_dimension was removed or renamed. The policy no longer protects anything. |
project.yml not found; the directory name stands in for the project | Add a project.yml with a stable uid. |
Model validation
Loader messages arrive as Error in <file>: followed by one of these. The ones people hit most:
| Message | Fix |
|---|---|
datasources.yml is required / datasources.yml defines no datasource | Add at least one datasource keyed by a short id. |
Datasource errors: adapter '<a>' is not supported | Use one of the supported adapters. See Datasources. |
Datasource errors: tier '<t>' should be hot, warm or cold (<name>) | Fix tier:. |
datasource key is required in table file. | Every tbl.*.yml and rel.*.yml names its datasource:. |
datasource with uid or name <v> not found in branch. | The datasource: value does not match a key in datasources.yml. |
Table errors: Name has already been taken (<name>) | Two table files share a name. |
Table errors: Physical name can't be blank (<name>) | Add physical_name:. |
fields must be a list (<name>) / each field of <name> must be a mapping | YAML shape. Indentation (spaces, not tabs), a missing - or colon. |
Field <name> errors: type should either be a dimension or measure | type: is required on every field. |
Field <name> errors: Data type '<d>' is not included in the list | See Data Types. |
Field '<name>' is missing an expression node. / Expression errors for <name>: Sql can't be blank | Every field needs expression: { sql: ... }. |
Expression errors for <name>: Sql measure should have an aggregation function | A measure’s sql must aggregate: sum(ss_net_paid), not ss_net_paid. |
Expression errors for <name>: Column refs dimensions should reference a table column | A dimension’s expression must read a column of its table. |
Expression errors for <name>: Field <name> is already mapped to this table. | The same field twice in one table. The same name on another table is not an error: it is one concept. |
Field <name> errors: Grains contains invalid values: <bad> / Snapshot '<x>' is not included in the list | The message lists the accepted values. See Snapshot measures. |
Field <name> errors: Exclusion rule Type should be one of: dimension, table, universe | See Exclusions. |
JoinDef errors for <key>: Cardinality can't be blank / Cardinality '<x>' is not included in the list | many_to_one, one_to_many or one_to_one. Many-to-many is not supported; model a junction table with two relations. See Cardinality. |
JoinDef errors for <key>: Sql join sql should be of the format left.column = right.column | Only equi-joins, written with the left. and right. prefixes. |
Left table '<x>' not found in datasource <ds> / Right table ... | Relations never cross datasources, and names must match the table files’ name. |
Join: <<left>-<right>>, already exists. | Two relations between the same pair of tables. |
Circular import detected: a.yml -> b.yml -> a.yml / Import not found: <import> | See Imports. |
could not find snapshot dimension <dim> in branch. | The table’s snapshot: must name a date dimension. |
Partition errors: Dimension must exist (<dim>) / Predicate is not included in the list (<dim>) | See Partitions. |
[<field>]: could not find measure named: <m> / could not find dimension named: <d> | A [Name]@m or [Name]@d reference in a formula does not exist. Names are matched case-insensitively; check spelling. |
[<field>] on <table>: Sql Referenced dimensions in the formula could not be located in a single Universe. | A compound measure references a dimension not every component can reach. Move the conditional part into a standard measure on the fact that has the dimension. See Compound Measures. |
[<field>] on <table>: Sql Could not find a datasource which had all of the required measures. Cross datasource queries are not supported. | Components of a compound measure live in different datasources. Define the missing measure in one of them under the same name. |
[<field>]: Inclusions Dimensions [...] could not be found in a universe with this measure. | See Inclusions. |
Policy '<n>': context_dimension '<d>' not found in this branch. / trigger field '<f>' not found in this branch. | Names in security.yml must match a field in the branch. See Security. |
Unknown mode '<x>'. Use mask_data or filter_data. / Policy '<n>': unresolved '<x>' should be deny or allow | Fix the value. |
Test must have a name / Test must have at least one field in 'projections' / Test must have at least one assertion (assert_sql or assert_regex) | See Tests. |
Plan errors
POST .../sql, /explain and /explore answer 400 for a malformed request, 404 when the branch is not deployed, and 422 when the request is well formed but the model cannot answer it.
| Status | Class | Message | What to do |
|---|---|---|---|
| 400 | Invalid | give a spec or an expr | The body needs spec or expr. |
| 400 | Shorthand | the parse error | The expr line did not parse. See Shorthand. |
| 400 | ContextRequired | this branch has security policies; a context is required to plan | Send a context. See Security. |
| 404 | NotFound | no deployment for project <uid> branch <branch> | Deploy that branch, or check --branch (the default is the checked-out git branch). zsql list shows what is deployed. |
| 422 | Semantic::NotFound | No field named '<x>' in this model. optionally followed by Did you mean: A, B, C? | No field matches by name, uid or synonym. zsql fields <x> searches. See Fuzzy field corrections. |
| 422 | Semantic::Ambiguous | Field '<x>' is ambiguous ... | A dimension and a measure share the name. Write <x>@d or <x>@m. |
| 422 | Query::Spec::InvalidSpecError | malformed spec: ... | Unknown keys, a bad predicate name, a bad decorator type or a segment that is not well formed. See The query spec. |
| 422 | ActiveRecord::RecordInvalid | Validation failed: ... | A calculation or filter failed validation: a raw column in a formula, an unresolved [Name]@m reference, a blank filter value. |
| 422 | Planner::ResolutionError | At least one projection required | Project at least one field. |
| 422 | Planner::ResolutionError | No universe can resolve the query within datasource <ds>. | The fields cannot be joined. See Query resolution. |
| 422 | Planner::ResolutionError | Could not find path for <x> / Could not find Universe for measure|dimension: ... | Same cause, named more precisely. |
| 422 | Planner::ResolutionError | cannot parse date ... | Use YYYY-MM-DD or a relative date such as 28d, 3m, 1y. See Filters. |
| 422 | Planner::ResolutionError | Unsupported filter predicate: ... | The predicate does not apply to that field’s data type. |
| 422 | Planner::ResolutionError | Calculation <x> references itself through <y> | Break the cycle between calculations. |
| 422 | Planner::ResolutionError | Could not resolve segment <s>: ... | The segment’s keys are not reachable from the measures it constrains. See Segments. |
| 422 | Planner::ResolutionError | <table> is not a snapshot table | A snapshot measure on a table without snapshot:. |
| 422 | Planner::SecurityPolicyError | Security policy context dimension '<d>' is not reachable in the universe for this query. Cannot safely enforce security policy. | See Query resolution. |
| 422 | Planner::SecurityPolicyError | Security policy context dimension '<d>' has a composite expression and cannot be used as a security context dimension. | Point the policy at a plain column dimension. |
| 422 | Unimplemented | not implemented: ... | The combination is not supported yet (a custom predicate, a calculation over a rule-bearing measure). |
zsql explain is the fastest way to see why: it prints the node tree, which table each node reads and the paths it joined. See Explain and the full list in Query errors.
Query resolution
No universe can resolve the query within datasource <ds>.
The request asked for a measure grouped or filtered by a dimension its fact cannot reach, and no blend between facts covers it. 0sql refuses rather than guess a join. Check, in order:
- Is there a relation? The dimension’s table must be reachable from the measure’s fact through
many_to_one(orone_to_one) joins. Add the missingrel.*.yml. - Is the change deployed?
zsql deploy, thenzsql tablesandzsql fields <name>show what the branch has. - Is it the wrong concept? If the dimension exists under a different name on the fact’s side (
Ship CountryvsCountry), it is not the same field. Unify the names if they are one concept, or ask for the right one. - Should the measure ignore it? If the measure is meant to be grouped only by some dimensions, model that with an exclusion.
Remove fields one at a time to find the culprit, or ask zsql explore --expr "<the fields that work>" which dimensions and measures can still be added.
Every plan runs in one datasource
A query is answered from exactly one datasource; results are never merged across them. If the fields you need live in two, define the missing measure on a table in the other datasource under the same name, and routing picks whichever datasource can serve everything. See Semantic routing.
Security policy context dimension '<d>' is not reachable in the universe for this query.
A policy fired (the query projects a tagged or named field) but the node’s universe has no path to the policy’s context_dimension. The query is refused because the filter or mask could not be applied. Either give that table a join path to the context dimension, or pick a context dimension every table with a triggered field can reach. See Security.
Fuzzy field corrections
A field reference in a spec or an expr is matched by name, uid or synonym, case-insensitively. When nothing matches exactly, the planner scores every field of the wanted kind by trigram overlap with the reference and applies this rule:
- A reference shorter than four characters never corrects; it resolves exactly or fails.
- If the best candidate scores at least 0.5 and leads the runner-up by at least 0.2, it is used silently and reported in the response’s
correctionsarray. - Otherwise the request fails with
Semantic::NotFound, with up to three candidates scoring at least 0.25 appended asDid you mean: A, B, C?.
{"sql": "...",
"corrections": [{"term": "departmnt", "field_uid": "department", "field_name": "Department", "score": 0.8}]}
corrections is omitted when empty. zsql sql prints each one to stderr as departmnt → Department (~0.8). If a correction surprises you, use the exact name or the uid, and consider adding the misspelling as a synonym on the field so it resolves exactly.
Auth errors
| Status | Class | Message | Fix |
|---|---|---|---|
| 401 | Unauthorized | an API key is required: Authorization: Bearer <key> | Send the header. zsql reads the key from ZSQL_API_KEY, then .zsql, then ~/.zsql/config; run zsql auth --api-key ... in the project. |
| 401 | Unauthorized | this API key is not valid | Revoked, rotated or mistyped. Make a new one in the console. |
| 403 | Forbidden | query keys are read only; deploy with your personal key | Use a zsk_ key for deploy, check, test and remove. |
| 403 | Forbidden | query key <name> is not granted <uid>/<branch> | An owner grants the key that project, with branch as a name, *, or omitted for the production branch. |
| 403 | Forbidden | <email> has <level> access to <uid>; this needs <level> | Ask an owner to raise your level. Read: query. Write: deploy. Owner: access and settings. |
| 403 | Forbidden | <email> has no access to <uid> | The project is restricted; an owner adds you. |
| 403 | Forbidden | deploying to the protected production branch '<b>' of <uid> needs an owner | Deploy a feature branch, or have an owner deploy. |
See Accounts, keys and access.
Numbers look wrong
0sql returns SQL, so a wrong number is a wrong model or a wrong request. zsql explain shows which table and joins produced it.
Numbers are too high (double counting)
- Cardinality is wrong. A relation declared
many_to_onethat is really one-to-many multiplies the measure. Check the data and fixcardinality. See Cardinality. allow_measure_expansion: trueon a join that is not safe. Expansion lets a measure be grouped by dimensions on the many side. Remove it unless the join really is safe.- Many-to-many modelled as a direct join. Use a junction table with two relations.
The same measure gives different numbers in two requests
When a measure is defined on more than one table, routing chooses per request by the requested dimensions, then by cost. If two definitions of Store Net Paid disagree, they are not the same concept or one has a bug. Run zsql explain on both requests to see which table served each; give a distinct name to anything that means something different.
A balance or inventory total is wrong across months
Summing a daily balance over a month is meaningless. Model it as a snapshot measure with snapshot: ending (or beginning) on a table that declares its snapshot: date dimension.
A cross-domain compound measure repeats a value across rows
Expected. When a request groups by a dimension only some components of a compound measure can reach, the others are auto-levelled: aggregated without that dimension and repeated across it. For a rate this is the fixed denominator you want. See Compound measures.
Getting help
zsql checkfor the full list of loader errors and warnings.zsql explain --json --expr "..."for the plan of a refused or surprising request.- Narrow it down:
git diff HEAD~5 -- models/shows what changed; drop fields until the request plans. - Send the
Error inlines, the explain JSON, the relevant YAML and what you expected.