Conversation
A source that writes a definite error into its persist shard poisons the collection permanently, because nothing ever writes the compensating retraction. The only escape today is to drop the source and recreate it, paying a full re-ingest. Storage already names the intended repair in a health hint, but no user-facing mechanism performs it. This design adds a statement-level `WITH (IGNORE ERRORS)` option to `SELECT` and `SUBSCRIBE` that discards the statement's error collection and reports one discarded error as a notice. Scope is limited to ad-hoc reads so that no durable object can be defined in terms of the weakened guarantees, and the option requires nothing of the optimizer. The document revisits the per-relation form proposed and rejected in 20240609_error_handling.md, and records why statement-level scope, drop-all semantics, and mandatory reporting were chosen over per-relation scope, filtering by error variant, and write-time retraction. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add a statement-level `WITH (IGNORE ERRORS)` option to `SELECT` and `SUBSCRIBE`, gated behind the `enable_ignore_errors` feature flag. The option is carried to `SelectPlan` and `SubscribePlan` and is not yet honored during execution. The option attaches to `SelectStatement` rather than to `Query`, so the statements that define a catalog item cannot carry it: `CREATE VIEW`, `CREATE MATERIALIZED VIEW`, `CREATE INDEX` and `INSERT ... SELECT` all parse a `Query` and have no position in which the option could appear. This mirrors the existing restriction on `AS OF`. A `WITH (...)` list rather than a trailing keyword keeps `IGNORE` usable as a bare table alias, which a trailing form would break because `IGNORE` is not reserved in table-alias position. A side-effecting function is planned outside the normal query path, which has no error collection to discard, so the option is rejected there rather than silently dropped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Carry the option to the replica on `Peek`, skip the errors a peek meets instead of answering with them, and report one skipped error to the client as a warning. Each read path drops errors where it previously surfaced them. The index walk continues past an error rather than returning it, the persist fast path skips the row, and constant folding returns an empty result. A row-iteration limit still fails the peek, because it is a resource limit rather than an error in the data. The return direction needs a shape change, because `PeekResponse` separates `Rows` from `Error` while this option produces rows together with an error. `Rows` becomes a struct variant carrying the retained error, and `StashedPeekResponse` gains the same field. Merging across workers keeps any one of them: a retained error is a sample rather than a tally, since collapsing multiplicities and collection-wide errors both leave a count meaning something other than affected rows. The notice reaches the session through the notice channel, which the peek response stream holds because it runs without a session of its own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Carry the option to the sink, which discards errors and keeps streaming rows instead of poisoning the subscribe, and report the first discarded error to the client as a warning. The poison channel is `SubscribeBatch.updates`, whose `Err` arm is what this option removes, so a discarded error has no slot to travel in and the batch gains a field for it. Compute sets it on the one batch that discarded the error and leaves it empty afterwards, so a stream that errors continuously reports once rather than on every batch. The controller keeps whichever worker's error arrives first when merging. Also fix the side-effecting function guard, which rejected the option before deciding whether the statement was such a call at all, so `SELECT 1 / 0 WITH (IGNORE ERRORS)` was refused instead of planned. The check now runs once the call is identified. The frontend peek path has a constant-folding branch of its own, which needed the same handling as the one in the coordinator. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A statement can queue a notice while its rows stream, which happens after the drain at the top of the connection loop has already run for that statement. Such a notice was delivered with the next statement instead, and a session that ended after a single query dropped it entirely. Draining immediately before `ReadyForQuery` keeps the notice with the statement that raised it. `SELECT ... WITH (IGNORE ERRORS)` is the first caller to depend on this: it learns what it discarded only as its rows stream, and the notice is the only signal that the answer is degraded. Also record in the design document what the implementation settled: the option's position relative to `ORDER BY` and `AS OF`, that a per-row error omits only the rows that raised it, that both peek paths fold constants and each needs the branch, and the notice ordering above. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four independent failures, all consequences of the new statement option. The sqllogictest file is exempt from `--auto-index-selects`. That mode wraps a successful `SELECT` in an indexed view and compares outcomes, but the option is rejected on a view by design, so every query carrying it reports an inconsistent outcome. The file is added to `tests_without_views` and `tests_no_auto_index_selects`. The pretty printer dropped the option, so a statement carrying it did not survive the parse, print, reparse round trip that `mz-sql-pretty` asserts. It now renders the option list in the same position `AstDisplay` does. `ComputeResponse` grew from 112 to 120 bytes. The subscribe variant is the largest, so the error a subscribe discards lands on the whole enum. Boxing the field does not avoid this, because the variant pads to the same width either way, so the assertion is updated and the cause recorded next to it. The doc comment on `SelectStatementOptionName` referred to `SelectOptionName`, which lives in another module and did not resolve as an intra-doc link. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
QA LLM Review1. MEDIUM --
|
|
My agent's summary is below, but the main feedback is:
DetailsBlocking
Strong suggestions
Nits
Verdict The parser, planner, and protocol shape are clean, the statement-level scoping is well justified, and the comments are of the right density. Blocked on the CI default, the COPY TO S3 silent drop, and the missing SUBSCRIBE/notice coverage. With those addressed this improves codebase health and I would approve. |
mgree
left a comment
There was a problem hiding this comment.
The problem with this approach is that it ignores errors in the source (good, in moderation) but also errors in the computation (bad).
I think we want something with a slightly different shape: make it possible to push errors from a source into the data plane (CREATE TABLE FROM SOURCE ... WITH ERROR TABLE ...), where the "error table" is essentially a dead-letter queue.
We'll need tools to manage the error table/DLQ, and in the fullness of time we'll need tools for managing the error stream of any dataflow. We might get away at the beginning by only allowing ad-hoc selects into the error table/DLQ. Management may be as simple as allowing DELETEs in an error table.
A source can write a definite error into its persist shard, and nothing ever writes the compensating retraction. Every later read returns that error instead of data, so the collection is poisoned permanently. Kafka does this when the topic does not exist, when compaction has advanced the low watermark past the resume upper, and when the topic is recreated or deleted underneath a running source. The only escape today is to drop the source and recreate it, paying a full re-ingest, even when the user only wants to look at the rows that did land before deciding whether to rebuild.
Storage already names the intended repair and provides no way to take it: a definite error passing through the ingestion pipeline carries the health hint
retracting the errored value may resume the source, but no user-facing mechanism performs that retraction.This adds a statement-level option that discards the statement's error collection and answers from the ok rows instead:
It is gated behind the
enable_ignore_errorsfeature flag, which defaults off in production and on in the test configuration.Scope
The option is confined to ad-hoc reads. It attaches to
SelectStatementrather than toQuery, soCREATE VIEW,CREATE MATERIALIZED VIEW,CREATE INDEXandINSERT ... SELECThave no position in which it could appear, and no durable object can be defined in terms of the weakened guarantees. This mirrors the existing restriction onAS OF. AWITH (...)list rather than a trailing keyword keepsIGNOREusable as a bare table alias, which a trailing form would break becauseIGNOREis not reserved in table-alias position.Statement-level scope means the option requires nothing of the optimizer, which is the trade this design makes. The 2024 error-handling design proposed a per-relation form and restricted it to sources and subsources precisely to keep a guarantee that a decode error omits only the record that failed. That form needs the optimizer to treat the annotated
Getas a barrier. This design drops the guarantee instead. The design document records why, along with the alternatives that were rejected, including filtering by error variant, which would make the variant assignment a compatibility surface because query results would then depend on how an error is classified.Reporting
Reporting is required rather than optional, because a statement that silently returns a degraded answer is a worse failure than the error it replaces: the caller cannot distinguish it from a clean one. The server emits a warning carrying one discarded error.
The notice carries no count. Every read path stops at the first error it meets, so counting would turn a bounded probe into a full walk of the error trace on every execution, and the resulting number would not mean affected rows in any case: compute collapses error multiplicities per binding during rendering, and a single source error poisons a whole collection with no row correspondence at all.
Protocol
The change is asymmetric. Outbound is a boolean on
PeekandSubscribeSinkConnection. The return direction needs a new shape, because both response types encode rows exclusive-or an error while this option produces rows together with an error.PeekResponse::Rowsbecomes a struct variant carrying the retained error andStashedPeekResponsegains the same field; merging across workers keeps any one of them, since a retained error is a sample rather than a tally. For subscribe, theErrarm ofSubscribeBatch.updatesis the poison channel this option removes, so the batch gains a separate field. None of these structures is durable.Notice delivery
A peek learns what it discarded only as its rows stream, which is after the connection loop has already drained pending notices for that statement. The notice therefore arrived with the next statement, or was lost entirely when the session ended after a single query, which is the interactive case the feature exists for.
ready()now drains pending notices immediately beforeReadyForQuery.That commit changes behavior shared by every notice, not only this feature's: a notice queued during row streaming now arrives with the statement that raised it instead of the next one. It is worth a careful look, and is kept as its own commit for that reason.
Tests
Adds
test/sqllogictest/ignore_errors.slt, covering the feature gate, a clean query under the option, an error raised by the statement's own expressions, per-row errors where the rows that did not error are still returned, rejection on every statement that defines a catalog item and on a subquery, and thatIGNOREstill parses as a bare table alias. Extends the parser datadriven tests insrc/sql-parser/tests/testdata/selectandddlwith the new syntax, including its interaction withORDER BY,LIMIT,AS OFand a leading CTE.Release note
This release adds a
WITH (IGNORE ERRORS)option toSELECTandSUBSCRIBE, which returns the rows of a collection whose errors would otherwise fail the query, together with a warning naming one discarded error. The rows carry no correctness guarantee while errors are present, so the option is intended for inspecting a collection that a definite error has made unreadable, not for routine querying. The option cannot be used in a view, materialized view, index, orINSERT ... SELECT.🤖 Generated with Claude Code