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
204
205
206
207
208
//! Planner diagnostics.
use selene_core::DbString;
use crate::{GqlStatus, SourceSpan, analyze::BindingId};
/// Query-planning failure.
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
#[non_exhaustive]
pub enum PlannerError {
/// The planner reached a syntactic surface it does not yet lower.
#[error("planner cannot lower {feature}: not yet supported")]
#[diagnostic(code(SLENE_P_010))]
NotImplemented {
/// Stable missing-feature tag asserted by tests.
feature: &'static str,
/// Source span requiring the missing planner capability.
#[label("not implemented by the planner yet")]
span: SourceSpan,
},
/// A binding reference produced by the analyzer no longer points at a
/// declaration during lowering.
#[error("binding {binding:?} resolved by analyzer is missing during lowering")]
#[diagnostic(code(SLENE_P_011))]
BindingResolutionLost {
/// Lost analyzer binding.
binding: BindingId,
/// Source span where the binding was required.
#[label("binding should resolve here")]
span: SourceSpan,
},
/// A value expression in the analyzed AST has no expression-type cell.
#[error("expression cell missing for value expression at {span:?}")]
#[diagnostic(code(SLENE_P_012))]
ExpressionTypeMissing {
/// Source span of the expression with no analyzer type cell.
#[label("missing expression type")]
span: SourceSpan,
},
/// The procedure registry passed to planning no longer contains a
/// procedure that was resolved during analysis.
#[error("procedure {procedure:?} not found in registry during planning")]
#[diagnostic(code(SLENE_P_013))]
UnknownProcedure {
/// Qualified procedure name.
procedure: Box<[DbString]>,
/// Source span of the procedure call.
#[label("unknown procedure")]
span: SourceSpan,
},
/// A mutation statement reached the planner without its analyzer write set.
#[error("mutation statement reached planner without analyzer write set")]
#[diagnostic(code(SLENE_P_014))]
WriteSetMissing {
/// Source span of the mutation pipeline.
#[label("missing write set")]
span: SourceSpan,
},
/// Analyzer write-set order and planner AST walk disagreed.
#[error("write-set entry could not be paired with an INSERT AST element")]
#[diagnostic(code(SLENE_P_015))]
WriteSetPatternMismatch {
/// Source span of the unmatched write site.
#[label("unmatched write-set entry")]
span: SourceSpan,
},
/// Procedure registry metadata changed between analysis and planning.
#[error("procedure {procedure:?} metadata changed between analyze and plan: {detail}")]
#[diagnostic(code(SLENE_P_016))]
ProcedureMetadataMismatch {
/// Qualified procedure name.
procedure: Box<[DbString]>,
/// Stable mismatch detail tag.
detail: &'static str,
/// Source span of the procedure call or yield item.
#[label("metadata mismatch")]
span: SourceSpan,
},
/// The planner could not construct a static database string during lowering.
#[error("planner could not construct static database string during lowering: {detail}")]
#[diagnostic(code(SLENE_P_017))]
StaticStringConstructionFailed {
/// Stable static identifier detail tag.
detail: &'static str,
/// Source span of the construct requiring the static identifier.
#[label("string construction failed")]
span: SourceSpan,
},
/// Set-composition arms are not column name-equal (ISO/IEC 39075:2024
/// §14.2 SR v "FCQE and FLQE shall be column name-equal and
/// column-combinable"). The binder binds each arm independently, so a
/// `RETURN 1 AS x UNION ALL RETURN 2 AS y` would otherwise silently
/// relabel the right arm to the left arm's schema.
#[error("{op} arms are not column name-equal at column {position}: lhs={lhs:?}, rhs={rhs:?}")]
#[diagnostic(code(SLENE_P_019))]
SetOpArmsNotCombinable {
/// Set operator keyword.
op: &'static str,
/// Zero-based column position of the first name mismatch.
position: usize,
/// Left arm column name (`None` for an unnamed column).
lhs: Option<DbString>,
/// Right arm column name (`None` for an unnamed column).
rhs: Option<DbString>,
/// Source span of the composite query.
#[label("set-op arms must have equal column names")]
span: SourceSpan,
},
/// A planner-visible implementation-defined limit would be exceeded.
#[error("{limit_name} {actual} exceeds implementation-defined limit {limit}")]
#[diagnostic(code(SLENE_P_018))]
ProgramLimitExceeded {
/// Stable limit name asserted by tests.
limit_name: &'static str,
/// Configured limit.
limit: u32,
/// Requested value.
actual: u32,
/// Source span of the construct exceeding the limit.
#[label("limit exceeded")]
span: SourceSpan,
},
/// An explicit `<...type key label set>` (Feature GG21) declared a key
/// label set whose effective cardinality falls outside the
/// implementation-defined (IL003) element-type key-label-set cardinality
/// bounds (ISO/IEC 39075:2024 §18.2 SR10/SR11 for nodes, §18.3 SR11/SR12 for
/// edges). selene-db sets the IL003 minimum and maximum both to 1, so any
/// cardinality other than 1 (the empty bare `<implies>`, or a multi-label
/// `:A & :B =>`) is rejected here with the spec-defined GQLSTATUS.
#[error(
"{element} type key label set cardinality {actual} is outside the supported range [{min}, {max}]"
)]
#[diagnostic(code(SLENE_P_020))]
KeyLabelSetCardinality {
/// `"node"` or `"edge"`.
element: &'static str,
/// Observed key-label-set cardinality.
actual: usize,
/// IL003 minimum cardinality.
min: u32,
/// IL003 maximum cardinality.
max: u32,
/// The spec-defined GQLSTATUS for this element/direction (42012/42013
/// for nodes, 42014/42015 for edges).
status: GqlStatus,
/// Source span of the key-label-set production.
#[label("key label set out of range")]
span: SourceSpan,
},
/// An explicit `<...type key label set>` was followed by a separate implied
/// `<...type label set>` (the `:Key => :Implied` shape). Per ISO/IEC
/// 39075:2024 §18.2 SR7/SR8 the element type's label set is the union of the
/// key label set and the implied label set, which requires key-label-set
/// *containment* identification rather than exact equality. selene-db defers
/// that semantics; this is an honest `FEATURE_NOT_SUPPORTED` rather than a
/// silent mis-identification.
#[error(
"{element} type key label set with a separate implied label set is not yet supported \
(key label set followed by a distinct implied label set requires containment identification)"
)]
#[diagnostic(code(SLENE_P_021))]
SeparateImpliedLabelSet {
/// `"node"` or `"edge"`.
element: &'static str,
/// Source span of the key-label-set production.
#[label("separate implied label set not supported")]
span: SourceSpan,
},
}
impl PlannerError {
/// Map this planner failure to its GQLSTATUS code.
#[must_use]
pub const fn gqlstatus(&self) -> GqlStatus {
match self {
Self::NotImplemented { .. } => GqlStatus::FEATURE_NOT_SUPPORTED,
Self::BindingResolutionLost { .. }
| Self::ExpressionTypeMissing { .. }
| Self::UnknownProcedure { .. }
| Self::WriteSetMissing { .. }
| Self::WriteSetPatternMismatch { .. }
| Self::ProcedureMetadataMismatch { .. }
| Self::StaticStringConstructionFailed { .. } => {
GqlStatus::IMPLEMENTATION_DEFINED_ERROR
}
Self::ProgramLimitExceeded { .. } => GqlStatus::PROGRAM_LIMIT_EXCEEDED,
// ISO §14.2 SR v is a Syntax Rule; its violation is the syntax
// error class (matching the analyzer's SR-violation precedents).
Self::SetOpArmsNotCombinable { .. } => GqlStatus::SYNTAX_ERROR,
// ISO §18.2 SR10/SR11 / §18.3 SR11/SR12 attach a specific
// GQLSTATUS to the IL003 cardinality violation per element/direction.
Self::KeyLabelSetCardinality { status, .. } => *status,
// The separate-implied-label-set shape is a deferred feature.
Self::SeparateImpliedLabelSet { .. } => GqlStatus::FEATURE_NOT_SUPPORTED,
}
}
}