Repository navigation
Expand file tree
/
Copy patherrors.go
More file actions
203 lines (177 loc) · 10.1 KB
/
Copy patherrors.go
File metadata and controls
203 lines (177 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
package forma
import (
"errors"
"fmt"
"strings"
)
// Sentinel errors returned by the entity manager layer.
// Callers should use errors.Is / errors.As for matching; never compare strings.
// ErrNotFound is returned when a requested entity does not exist.
var ErrNotFound = errors.New("not found")
// ErrConflict is returned when an operation would violate a uniqueness constraint
// or produce a duplicate record.
var ErrConflict = errors.New("conflict")
// ErrInvalidInput is returned when caller-supplied input fails validation
// (missing required fields, unsupported values, etc.).
var ErrInvalidInput = errors.New("invalid input")
// Read-path lakehouse errors. These live in the public package rather than in
// internal/federated deliberately: they exist to be discriminated
// programmatically, and an embedder reaching the engine through
// factory.NewEntityManagerWithConfig* cannot import an internal package, which
// would leave it comparing error text — exactly what the errors.Is/errors.As
// contract above forbids.
//
// Both are plain errors, not ErrInvalidInput: nothing about the request is
// wrong. They report that the configured read surface, or the manifest state
// it points at, cannot answer the query. See docs/error-handling.md.
// ErrNoParquetPaths marks a DuckDB-routed federated read whose parquet path set
// resolved empty: no per-request render hint, and either no configured parquet
// source or a source whose manifest lists no files while the fallback glob is
// disabled. Every query that reaches the DuckDB engine wants warm and/or cold
// data (hot-only requests short-circuit to Postgres first), so an empty set
// cannot be answered honestly. Not degradable — a Postgres-only fallback would
// be silently short precisely where the cold tier was requested (#299).
var ErrNoParquetPaths = errors.New("no parquet paths resolved")
// NoParquetPathsError names the schema whose path set came back empty and which
// resolution level was in play, because the two states have different remedies:
// a consulted-but-empty source needs its manifest repaired (or the fallback
// prefix set), while an absent source needs configuring at all.
type NoParquetPathsError struct {
SchemaID int16
// SourceConfigured reports whether a parquet source was consulted and
// returned nothing, as opposed to no source existing to consult.
SourceConfigured bool
}
func (e *NoParquetPathsError) Error() string {
if e.SourceConfigured {
return fmt.Sprintf("schema %d resolved no parquet paths: its manifest lists no files and the fallback glob is disabled; "+
"repair the manifest (forma-tools manifest-reconcile) or set duckdb.s3DataPrefix", e.SchemaID)
}
return fmt.Sprintf("schema %d resolved no parquet paths: no per-request path hint and no manifest parquet source is configured; "+
"set duckdb.manifestTemplate with duckdb.s3Bucket, or pass federated.s3_parquet_path_template", e.SchemaID)
}
func (e *NoParquetPathsError) Unwrap() error { return ErrNoParquetPaths }
// ErrManifestSchemaMismatch marks a manifest object whose recorded schema ID
// disagrees with the schema being read. A manifest addresses one schema by path
// convention alone; nothing downstream re-checks it — the parquet scan does not
// filter rows by schema (files are per-schema by path) and the projection stamps
// whatever it scans as the requested schema. So a path collision between two
// schemas would not merely under-read, it would serve another schema's rows
// under this schema's identity. Not degradable: cross-schema contamination is
// the opposite of a partial answer.
var ErrManifestSchemaMismatch = errors.New("manifest schema id does not match the schema being read")
// ManifestSchemaMismatchError names both schema IDs and the manifest object, so
// an operator can see which template or object is misaddressed.
type ManifestSchemaMismatchError struct {
RequestedSchemaID int16
ManifestSchemaID int16
Path string
}
func (e *ManifestSchemaMismatchError) Error() string {
return fmt.Sprintf("manifest %s is stamped for schema %d but was loaded for schema %d; "+
"the manifest path template must resolve a distinct object per schema",
e.Path, e.ManifestSchemaID, e.RequestedSchemaID)
}
func (e *ManifestSchemaMismatchError) Unwrap() error { return ErrManifestSchemaMismatch }
// ErrManifestUnstamped marks a manifest object that lists entries under
// schema_id 0 (#522). A zero stamp is not "schema 0" (schema IDs are always
// positive) and it is not a legacy format either: SchemaID has been on the
// manifest, and set by every writer, since the manifest package's first
// commit, so a zero stamp can only come from a hand-made or hand-edited
// object. It proves nothing about which schema owns the listed entries, and
// the two-probe template check cannot rule out a template that collides only
// at schema IDs it never renders — so such a manifest may hold another
// schema's tiers and is refused for every schema.
//
// It is a species of ErrManifestSchemaMismatch: the carrier unwraps to both
// sentinels, so every classification keyed on the mismatch (redaction class,
// non-degradable federated read) applies unchanged, while this sentinel tells
// the remedy apart — confirm ownership and set schema_id on the object,
// rather than fix the path template.
var ErrManifestUnstamped = errors.New("manifest lists entries under schema_id 0 and cannot prove which schema owns them")
// ManifestUnstampedError names the manifest object, the schema it was loaded
// for, and how many entries it lists without a stamp.
type ManifestUnstampedError struct {
RequestedSchemaID int16
Path string
Entries int
}
func (e *ManifestUnstampedError) Error() string {
return fmt.Sprintf("manifest %s was loaded for schema %d but has %d entries listed under schema_id 0; "+
"confirm the object belongs to schema %d and set schema_id before rerunning",
e.Path, e.RequestedSchemaID, e.Entries, e.RequestedSchemaID)
}
// Unwrap yields both sentinels: see ErrManifestUnstamped.
func (e *ManifestUnstampedError) Unwrap() []error {
return []error{ErrManifestUnstamped, ErrManifestSchemaMismatch}
}
// ErrParquetSetInconsistent marks a federated read whose manifest lists parquet
// objects that do not exist in storage. The manifest is the authoritative record
// of the schema's cold/warm tier, so a listed-but-absent object means that tier
// has lost data. Not degradable: surfacing it even under
// AllowPartialDegradedMode is the whole point — degrading here would return
// exactly the silently short answer this classification exists to make loud
// (#187 scenario 2).
//
// It lives here rather than in internal/federated for the same reason as the two
// errors above, plus one specific to #301: internal/httpapi must classify it to
// redact its object keys, and cannot import internal/federated without pulling
// DuckDB CGO into a pure-Go test build.
var ErrParquetSetInconsistent = errors.New("parquet set inconsistent with manifest")
// ParquetSetInconsistentError carries the schema and the missing object keys so
// the message names the offending state, per the read-path error style.
//
// MissingKeys holds bucket-relative S3 object keys — operator detail that must
// not cross a public transport. internal/httpapi redacts it from response bodies
// (#301). That redaction is gated on sentinel evidence, not on the status: this
// error wraps no client sentinel, so it is redacted whatever status it is
// classified as — including a 4xx, since DuckDB renders a missing object as
// "404 (Not Found)". Any new transport owes the same treatment.
type ParquetSetInconsistentError struct {
SchemaID int16
MissingKeys []string
}
func (e *ParquetSetInconsistentError) Error() string {
return fmt.Sprintf("schema %d manifest lists %d parquet object(s) missing from storage: %s",
e.SchemaID, len(e.MissingKeys), strings.Join(e.MissingKeys, ", "))
}
func (e *ParquetSetInconsistentError) Unwrap() error { return ErrParquetSetInconsistent }
// ErrParquetTypeIncompatible marks a federated read refused because a live
// parquet object stores an attribute's column in a form the attribute's
// current declaration cannot read without changing values: a type from
// another family, or a value outside what the declared type and its bound
// column can hold (#732). It is a read-path consistency error, never a 4xx:
// the declaration changed after the object was written, and reading on would
// round, truncate, substitute NULL or fail the statement. Not degradable: the
// refusal is deterministic, and a Postgres-only answer would hide it on every
// query until an operator acts.
var ErrParquetTypeIncompatible = errors.New("live parquet stores an attribute in a form its declared type cannot read losslessly")
// ParquetTypeIncompatibleError names the schema, the attribute and its current
// declaration, the offending object and the evidence, per the read-path error
// style. Path is a storage location — operator detail that internal/httpapi
// keeps out of response bodies, like ParquetSetInconsistentError's keys.
type ParquetTypeIncompatibleError struct {
SchemaID int16
Attribute string
// Declared is the attribute's declared type, "list<items>" for a list.
Declared string
// Column is the bound entity_main column, empty for an EAV-only attribute.
Column MainColumn
// Path is the live object; FileType the column's DuckDB type in it.
Path string
FileType string
// Evidence says why the stored column cannot be read as declared: the
// type pair, or the first offending value a scan found.
Evidence string
}
func (e *ParquetTypeIncompatibleError) Error() string {
storage := "EAV-only"
if e.Column != "" {
storage = "bound to " + string(e.Column)
}
return fmt.Sprintf("schema %d attribute %s (declared %s, %s) cannot be read from live parquet %s, which stores it as %s: %s; "+
"the declaration changed after the object was written. Restore the declaration the object was written under; "+
"converting stored values is an explicit migration (#730). See docs/schema-evolution.md (#710)",
e.SchemaID, e.Attribute, e.Declared, storage, e.Path, e.FileType, e.Evidence)
}
func (e *ParquetTypeIncompatibleError) Unwrap() error { return ErrParquetTypeIncompatible }