1use std::{
4 borrow::Cow,
5 time::{Duration, Instant},
6};
7
8use selene_core::DbString;
9
10use crate::{AnalysisError, GqlStatus, ParserError, PlannerError, ProcedureError, SourceSpan};
11
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14#[non_exhaustive]
15pub enum DataExceptionSubclass {
16 DataException,
18 StringDataRightTruncation,
20 NumericValueOutOfRange,
22 NullValueNotAllowed,
24 InvalidDatetimeFormat,
26 SubstringError,
28 DivisionByZero,
30 InvalidArgumentForNaturalLogarithm,
32 InvalidArgumentForPowerFunction,
34 TrimError,
36 InvalidCharacterValueForCast,
38 InvalidTimeZone,
40 NegativeLimitValue,
42 InvalidValueType,
44 ValuesNotComparable,
46 InvalidDatetimeFunctionFieldName,
48 InvalidDatetimeFunctionValue,
50 InvalidDurationFunctionFieldName,
52 ListDataRightTruncation,
54 ListElementError,
56 InvalidDurationFormat,
58 PathDataRightTruncation,
60 IncompatibleTemporalInstantUnitGroups,
62 MultipleAssignmentsToGraphElementProperty,
64 NodeLabelsBelowSupportedMinimum,
66 NodeLabelsExceedSupportedMaximum,
68 EdgeLabelsBelowSupportedMinimum,
70 EdgeLabelsExceedSupportedMaximum,
72 NodePropertiesExceedSupportedMaximum,
74 EdgePropertiesExceedSupportedMaximum,
76 RecordFieldsDoNotMatch,
78 RecordDataFieldUnassignable,
80 MalformedPath,
82}
83
84impl DataExceptionSubclass {
85 #[must_use]
87 pub const fn gqlstatus(self) -> GqlStatus {
88 match self {
89 Self::DataException => GqlStatus::DATA_EXCEPTION,
90 Self::StringDataRightTruncation => GqlStatus::STRING_DATA_RIGHT_TRUNCATION,
91 Self::NumericValueOutOfRange => GqlStatus::NUMERIC_VALUE_OUT_OF_RANGE,
92 Self::NullValueNotAllowed => GqlStatus::NULL_VALUE_NOT_ALLOWED,
93 Self::InvalidDatetimeFormat => GqlStatus::INVALID_DATETIME_FORMAT,
94 Self::SubstringError => GqlStatus::SUBSTRING_ERROR,
95 Self::DivisionByZero => GqlStatus::DIVISION_BY_ZERO,
96 Self::InvalidArgumentForNaturalLogarithm => {
97 GqlStatus::INVALID_ARGUMENT_FOR_NATURAL_LOGARITHM
98 }
99 Self::InvalidArgumentForPowerFunction => GqlStatus::INVALID_ARGUMENT_FOR_POWER_FUNCTION,
100 Self::TrimError => GqlStatus::TRIM_ERROR,
101 Self::InvalidCharacterValueForCast => GqlStatus::INVALID_CHARACTER_VALUE_FOR_CAST,
102 Self::InvalidTimeZone => GqlStatus::INVALID_TIME_ZONE,
103 Self::NegativeLimitValue => GqlStatus::NEGATIVE_LIMIT_VALUE,
104 Self::InvalidValueType => GqlStatus::DATATYPE_MISMATCH,
105 Self::ValuesNotComparable => GqlStatus::VALUES_NOT_COMPARABLE,
106 Self::InvalidDatetimeFunctionFieldName => {
107 GqlStatus::INVALID_DATETIME_FUNCTION_FIELD_NAME
108 }
109 Self::InvalidDatetimeFunctionValue => GqlStatus::INVALID_DATETIME_FUNCTION_VALUE,
110 Self::InvalidDurationFunctionFieldName => {
111 GqlStatus::INVALID_DURATION_FUNCTION_FIELD_NAME
112 }
113 Self::ListDataRightTruncation => GqlStatus::LIST_DATA_RIGHT_TRUNCATION,
114 Self::ListElementError => GqlStatus::LIST_ELEMENT_ERROR,
115 Self::InvalidDurationFormat => GqlStatus::INVALID_DURATION_FORMAT,
116 Self::PathDataRightTruncation => GqlStatus::PATH_DATA_RIGHT_TRUNCATION,
117 Self::IncompatibleTemporalInstantUnitGroups => {
118 GqlStatus::INCOMPATIBLE_TEMPORAL_INSTANT_UNIT_GROUPS
119 }
120 Self::MultipleAssignmentsToGraphElementProperty => {
121 GqlStatus::MULTIPLE_ASSIGNMENTS_TO_GRAPH_ELEMENT_PROPERTY
122 }
123 Self::NodeLabelsBelowSupportedMinimum => GqlStatus::NODE_LABELS_BELOW_SUPPORTED_MINIMUM,
124 Self::NodeLabelsExceedSupportedMaximum => {
125 GqlStatus::NODE_LABELS_EXCEED_SUPPORTED_MAXIMUM
126 }
127 Self::EdgeLabelsBelowSupportedMinimum => GqlStatus::EDGE_LABELS_BELOW_SUPPORTED_MINIMUM,
128 Self::EdgeLabelsExceedSupportedMaximum => {
129 GqlStatus::EDGE_LABELS_EXCEED_SUPPORTED_MAXIMUM
130 }
131 Self::NodePropertiesExceedSupportedMaximum => {
132 GqlStatus::NODE_PROPERTIES_EXCEED_SUPPORTED_MAXIMUM
133 }
134 Self::EdgePropertiesExceedSupportedMaximum => {
135 GqlStatus::EDGE_PROPERTIES_EXCEED_SUPPORTED_MAXIMUM
136 }
137 Self::RecordFieldsDoNotMatch => GqlStatus::RECORD_FIELDS_DO_NOT_MATCH,
138 Self::RecordDataFieldUnassignable => GqlStatus::RECORD_DATA_FIELD_UNASSIGNABLE,
139 Self::MalformedPath => GqlStatus::MALFORMED_PATH,
140 }
141 }
142}
143
144#[derive(Clone, Debug, Eq, PartialEq)]
146pub struct ExecutorWarning {
147 pub code: GqlStatus,
149 pub message: String,
151 pub span: SourceSpan,
153}
154
155pub trait WarningSink: Send {
161 fn emit(&mut self, warning: ExecutorWarning);
163}
164
165#[derive(Debug, thiserror::Error, miette::Diagnostic)]
167#[non_exhaustive]
168pub enum ExecutorError {
169 #[error("parse failed: {source}")]
171 #[diagnostic(code(SLENE_X_PARSE))]
172 Parse {
173 #[source]
175 source: ParserError,
176 },
177
178 #[error("analysis failed: {source}")]
180 #[diagnostic(code(SLENE_X_ANALYZE))]
181 Analysis {
182 #[source]
184 source: AnalysisError,
185 },
186
187 #[error("planning failed: {source}")]
189 #[diagnostic(code(SLENE_X_PLAN))]
190 Plan {
191 #[source]
193 source: PlannerError,
194 },
195
196 #[error("execution data exception: {message}")]
198 #[diagnostic(code(SLENE_X_22000))]
199 DataException {
200 subclass: DataExceptionSubclass,
202 message: String,
204 #[label("data exception")]
206 span: SourceSpan,
207 },
208
209 #[error("dependent object still exists: {message}")]
211 #[diagnostic(code(SLENE_X_G1001))]
212 DependentObjectStillExists {
213 message: String,
215 #[label("dependent object still exists")]
217 span: SourceSpan,
218 },
219
220 #[error("graph type violation: {message}")]
222 #[diagnostic(code(SLENE_X_G2000))]
223 GraphTypeViolation {
224 message: String,
226 #[label("graph type violation")]
228 span: SourceSpan,
229 },
230
231 #[error("invalid runtime reference: {name}")]
233 #[diagnostic(code(SLENE_X_42002))]
234 InvalidReference {
235 name: String,
237 #[label("invalid reference")]
239 span: SourceSpan,
240 },
241
242 #[error("unbound parameter: ${name}")]
246 #[diagnostic(code(SLENE_X_22G03))]
247 UnboundParameter {
248 name: DbString,
250 #[label("unbound parameter")]
252 span: SourceSpan,
253 },
254
255 #[error("invalid parameter type for ${name}: expected {expected}, got {actual}")]
259 #[diagnostic(code(SLENE_X_22G03))]
260 InvalidParameterType {
261 name: DbString,
263 expected: Cow<'static, str>,
265 actual: &'static str,
267 #[label("invalid parameter type")]
269 span: SourceSpan,
270 },
271
272 #[error("unknown function: {name}")]
276 #[diagnostic(code(SLENE_X_22G03))]
277 UnknownFunction {
278 name: String,
280 #[label("unknown function")]
282 span: SourceSpan,
283 },
284
285 #[error("function {name} expected {expected} argument(s), got {actual}")]
289 #[diagnostic(code(SLENE_X_22G03))]
290 FunctionArityMismatch {
291 name: String,
293 expected: &'static str,
295 actual: usize,
297 #[label("wrong arity")]
299 span: SourceSpan,
300 },
301
302 #[error("function {name} does not allow {modifier}")]
306 #[diagnostic(code(SLENE_X_22G03))]
307 InvalidFunctionModifier {
308 name: String,
310 modifier: &'static str,
312 #[label("invalid function modifier")]
314 span: SourceSpan,
315 },
316
317 #[error("feature not yet supported: {feature}")]
323 #[diagnostic(code(SLENE_X_42N01))]
324 FeatureNotSupportedYet {
325 feature: &'static str,
327 #[label("feature not yet supported")]
329 span: SourceSpan,
330 },
331
332 #[error("duplicate {kind} name: {name}")]
334 #[diagnostic(code(SLENE_X_42N10))]
335 DuplicateObject {
336 kind: &'static str,
338 name: DbString,
340 #[label("duplicate object name")]
342 span: SourceSpan,
343 },
344
345 #[error("invalid transaction state: {detail}")]
347 #[diagnostic(code(SLENE_X_25000))]
348 InvalidTransactionState {
349 detail: &'static str,
351 #[label("invalid transaction state")]
353 span: SourceSpan,
354 },
355
356 #[error("transaction already active")]
358 #[diagnostic(code(SLENE_X_25000))]
359 TransactionAlreadyActive {
360 #[label("transaction already active")]
362 span: SourceSpan,
363 },
364
365 #[error("no active transaction")]
367 #[diagnostic(code(SLENE_X_25000))]
368 NoActiveTransaction {
369 #[label("no active transaction")]
371 span: SourceSpan,
372 },
373
374 #[error("statement issued against aborted explicit transaction")]
376 #[diagnostic(code(SLENE_X_25N02))]
377 InFailedTransaction {
378 #[label("aborted transaction; issue ROLLBACK to recover")]
380 span: SourceSpan,
381 },
382
383 #[error("session is closed")]
388 #[diagnostic(code(SLENE_X_2DN01))]
389 SessionClosed {
390 #[label("session closed; open a new session")]
392 span: SourceSpan,
393 },
394
395 #[error("statement cancelled")]
397 #[diagnostic(code(SLENE_X_5GQL2))]
398 Cancelled {
399 #[label("cancelled here")]
401 span: SourceSpan,
402 },
403
404 #[error("statement deadline exceeded")]
406 #[diagnostic(code(SLENE_X_5GQL3))]
407 Timeout {
408 deadline: Instant,
410 elapsed: Duration,
412 #[label("deadline exceeded here")]
414 span: SourceSpan,
415 },
416
417 #[error("statement row cap exceeded ({cap})")]
419 #[diagnostic(code(SLENE_X_5GQL1))]
420 RowCapExceeded {
421 cap: usize,
423 #[label("row cap exceeded here")]
425 span: SourceSpan,
426 },
427
428 #[error("program limit exceeded: {detail}")]
430 #[diagnostic(code(SLENE_X_5GQL1))]
431 ProgramLimitExceeded {
432 detail: &'static str,
434 #[label("program limit exceeded here")]
436 span: SourceSpan,
437 },
438
439 #[error("graph mutation failed: {source}")]
441 #[diagnostic(code(SLENE_X_5GQL0_GRAPH_MUTATION))]
442 GraphMutation {
443 #[source]
445 source: selene_graph::GraphError,
446 #[label("graph mutation failed")]
448 span: SourceSpan,
449 },
450
451 #[error("durability flush failed for provider {provider_tag}: {reason}")]
453 #[diagnostic(code(SLENE_X_5GQL0_FLUSH))]
454 Flush {
455 provider_tag: selene_graph::ProviderTag,
457 reason: String,
459 },
460
461 #[error("procedure execution failed: {source}")]
463 #[diagnostic(code(SLENE_X_PROC))]
464 Procedure {
465 #[source]
467 source: ProcedureError,
468 #[label("procedure failed")]
470 span: SourceSpan,
471 },
472
473 #[error("implementation-defined executor failure: {detail}")]
475 #[diagnostic(code(SLENE_X_5GQL0_IMPLEMENTATION_DEFINED))]
476 ImplementationDefined {
477 detail: &'static str,
479 },
480}
481
482impl ExecutorError {
483 #[must_use]
485 pub fn gqlstatus(&self) -> GqlStatus {
486 match self {
487 Self::Parse { source } => source.gqlstatus(),
488 Self::Analysis { source } => source.gqlstatus(),
489 Self::Plan { source } => source.gqlstatus(),
490 Self::DataException { subclass, .. } => subclass.gqlstatus(),
491 Self::DependentObjectStillExists { .. } => GqlStatus::DEPENDENT_OBJECT_STILL_EXISTS,
492 Self::GraphTypeViolation { .. } => GqlStatus::GRAPH_TYPE_VIOLATION,
493 Self::InvalidReference { .. } => GqlStatus::INVALID_REFERENCE,
494 Self::UnboundParameter { .. } | Self::InvalidParameterType { .. } => {
495 GqlStatus::INVALID_PROCEDURE_ARGUMENT
496 }
497 Self::UnknownFunction { .. }
498 | Self::FunctionArityMismatch { .. }
499 | Self::InvalidFunctionModifier { .. } => GqlStatus::DATATYPE_MISMATCH,
500 Self::FeatureNotSupportedYet { .. } => GqlStatus::FEATURE_NOT_SUPPORTED,
501 Self::DuplicateObject { .. } => GqlStatus::DUPLICATE_OBJECT,
502 Self::InvalidTransactionState { .. } => GqlStatus::READ_ONLY_TRANSACTION_VIOLATION,
503 Self::TransactionAlreadyActive { .. } => GqlStatus::ACTIVE_TRANSACTION,
504 Self::NoActiveTransaction { .. } => GqlStatus::INVALID_TRANSACTION_TERMINATION,
505 Self::InFailedTransaction { .. } => GqlStatus::IN_FAILED_TRANSACTION,
506 Self::SessionClosed { .. } => GqlStatus::SESSION_CLOSED,
507 Self::Cancelled { .. } => GqlStatus::OPERATION_CANCELLED,
508 Self::Timeout { .. } => GqlStatus::DEADLINE_EXCEEDED,
509 Self::RowCapExceeded { .. } | Self::ProgramLimitExceeded { .. } => {
510 GqlStatus::PROGRAM_LIMIT_EXCEEDED
511 }
512 Self::GraphMutation { source, .. } => GqlStatus::from_code(source.gqlstatus())
513 .unwrap_or(GqlStatus::IMPLEMENTATION_DEFINED_ERROR),
514 Self::Flush { .. } => GqlStatus::IMPLEMENTATION_DEFINED_ERROR,
515 Self::Procedure { source, .. } => source.gqlstatus(),
516 Self::ImplementationDefined { .. } => GqlStatus::IMPLEMENTATION_DEFINED_ERROR,
517 }
518 }
519
520 pub(crate) fn data_exception(
521 subclass: DataExceptionSubclass,
522 message: impl Into<String>,
523 span: SourceSpan,
524 ) -> Self {
525 Self::DataException {
526 subclass,
527 message: message.into(),
528 span,
529 }
530 }
531}
532
533#[cfg(test)]
534mod tests {
535 use super::DataExceptionSubclass;
536
537 #[test]
538 fn substring_error_subclass_maps_to_22011() {
539 assert_eq!(
540 DataExceptionSubclass::SubstringError.gqlstatus().as_str(),
541 "22011"
542 );
543 }
544
545 #[test]
546 fn trim_error_subclass_maps_to_22027() {
547 assert_eq!(
548 DataExceptionSubclass::TrimError.gqlstatus().as_str(),
549 "22027"
550 );
551 }
552}