surrealdb-core 3.3.1

A scalable, distributed, collaborative, document-graph database, for the realtime web
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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
//! Failures raised while executing a statement.
//!
//! This is the vocabulary of running a query: which statement forms are legal
//! where, whether a clause evaluated to something usable, which permission
//! predicate said no, how deep evaluation may recurse. Both executors - the
//! recursive `compute` path and the streaming planner - speak it, which is why
//! it is one type rather than one per executor.
//!
//! It deliberately names nothing about values, the catalog, keys, indexes,
//! documents, buckets or storage: those layers own their own failures.

// The mapper below is the only place this layer's failures become public.
// A new variant must make that decision explicitly rather than inheriting
// whatever the last arm happened to be.
#![deny(clippy::wildcard_enum_match_arm)]

use common::{LeafError, internal_todo};
use surrealdb_types::{Error as TypesError, ToSql, ValidationError};

use crate::expr::Expr;
use crate::val::CoerceError;

/// A failure in the statement-execution layer.
#[derive(Debug, thiserror::Error)]
pub(crate) enum Error {
	/// A custom error has been thrown
	#[error("An error occurred: {0}")]
	Thrown(String),

	/// No namespace has been selected
	#[error("Specify a namespace to use")]
	NsEmpty,

	/// No database has been selected
	#[error("Specify a database to use")]
	DbEmpty,

	#[error("Invalid query: {message}")]
	Query {
		message: String,
	},

	/// it is not possible to set a variable with the specified name
	#[error("'{name}' is a protected variable and cannot be set")]
	InvalidParam {
		name: String,
	},

	/// The FETCH clause accepts idioms, strings and fields.
	#[error("Found {} on FETCH CLAUSE, but FETCH expects an idiom, a string or fields", value.to_sql())]
	InvalidFetch {
		value: Expr,
	},

	/// The LIMIT clause must evaluate to a positive integer
	#[error("Found {value} but the LIMIT clause must evaluate to a positive integer")]
	InvalidLimit {
		value: String,
	},

	/// The START clause must evaluate to a positive integer
	#[error("Found {value} but the START clause must evaluate to a positive integer")]
	InvalidStart {
		value: String,
	},

	/// There was an error with the provided JavaScript code
	#[error("Problem with embedded script function. {message}")]
	InvalidScript {
		message: String,
	},

	/// There was an error with the provided machine learning model
	#[error("Problem with machine learning computation. {message}")]
	#[allow(dead_code)]
	InvalidModel {
		message: String,
	},

	/// There was a problem running the specified function
	#[error("There was a problem running the {name}() function. {message}")]
	InvalidFunction {
		name: String,
		message: String,
	},

	/// Invalid timeout
	#[error("Invalid control flow statement, break or continue statement found outside of loop.")]
	InvalidControlFlow,

	/// The permissions do not allow for changing to the specified namespace
	#[error("You don't have permission to change to the {ns} namespace")]
	NsNotAllowed {
		ns: String,
	},

	/// The permissions do not allow for changing to the specified database
	#[error("You don't have permission to change to the {db} database")]
	DbNotAllowed {
		db: String,
	},

	/// Reached excessive computation depth due to functions, subqueries, or
	/// computed values
	#[error("Reached excessive computation depth due to functions, subqueries, or computed values")]
	ComputationDepthExceeded,

	/// Tried to execute a statement that can't be used here
	#[error("Invalid statement: {0}")]
	InvalidStatement(String),

	/// Cannot execute statement using the specified value
	#[error("Cannot execute statement using value: {value}")]
	InvalidStatementTarget {
		value: String,
	},

	/// Cannot execute CREATE statement using the specified value
	#[error("Cannot execute CREATE statement using value: {value}")]
	CreateStatement {
		value: String,
	},

	/// Cannot execute UPSERT statement using the specified value
	#[error("Cannot execute UPSERT statement using value: {value}")]
	UpsertStatement {
		value: String,
	},

	/// Cannot execute UPDATE statement using the specified value
	#[error("Cannot execute UPDATE statement using value: {value}")]
	UpdateStatement {
		value: String,
	},

	/// Cannot execute RELATE statement using the specified value
	#[error("Cannot execute RELATE statement where property 'in' is: {value}")]
	RelateStatementIn {
		value: String,
	},

	/// Cannot execute RELATE statement using the specified value
	#[error("Cannot execute RELATE statement where property 'id' is: {value}")]
	RelateStatementId {
		value: String,
	},

	/// Cannot execute RELATE statement using the specified value
	#[error("Cannot execute RELATE statement where property 'out' is: {value}")]
	RelateStatementOut {
		value: String,
	},

	/// Cannot execute DELETE statement using the specified value
	#[error("Cannot execute DELETE statement using value: {value}")]
	DeleteStatement {
		value: String,
	},

	/// Cannot execute INSERT statement using the specified value
	#[error("Cannot execute INSERT statement using value: {value}")]
	InsertStatement {
		value: String,
	},

	/// Cannot execute INSERT statement using the specified value
	#[error("Cannot execute INSERT statement where property 'in' is: {value}")]
	InsertStatementIn {
		value: String,
	},

	/// Cannot execute INSERT statement using the specified value
	#[error("Cannot execute INSERT statement where property 'id' is: {value}")]
	InsertStatementId {
		value: String,
	},

	/// Cannot execute INSERT statement using the specified value
	#[error("Cannot execute INSERT statement where property 'out' is: {value}")]
	InsertStatementOut {
		value: String,
	},

	/// Cannot execute LIVE statement using the specified value
	#[error("Cannot execute LIVE statement using value: {value}")]
	LiveStatement {
		value: String,
	},

	/// Cannot execute KILL statement using the specified id
	#[error("Cannot execute KILL statement using id: {value}")]
	KillStatement {
		value: String,
	},

	/// Cannot execute CREATE statement using the specified value
	#[error("Expected a single result output when using the ONLY keyword")]
	SingleOnlyOutput,

	/// The permissions do not allow this query to be run on this table
	#[error("You don't have permission to view the ${name} parameter")]
	ParamPermissions {
		name: String,
	},

	/// The permissions do not allow this query to be run on this table
	#[error("You don't have permission to run the {name} function")]
	FunctionPermissions {
		name: String,
	},

	/// A `SELECT` permission predicate attempted to perform a write or other
	/// side effect while being evaluated (GHSA-66r2-5gwj-gxm2). Permission
	/// expressions are evaluated with permission enforcement disabled, so they
	/// must be free of observable side effects. The create/update/delete
	/// clauses are reached from a write and may carry side effects.
	#[error("A PERMISSIONS clause cannot contain a statement that modifies data")]
	PermissionPredicateSideEffect,

	/// A `COMPUTED` body reached a data-modifying statement while being
	/// evaluated. The definition-time check ([`Error::ComputedWrite`]) rejects a
	/// mutation written into the body itself, but treats a call to a
	/// user-defined function as opaque, since the callee is stored separately,
	/// can be redefined afterwards, and may take its writing branch only on
	/// inputs this body never supplies. This is where those are caught.
	#[error("A COMPUTED clause cannot contain a statement that modifies data")]
	ComputedFieldSideEffect,

	/// The specified value did not conform to the LET type check
	#[error("Tried to set `${name}`, but couldn't coerce value: {error}")]
	SetCoerce {
		name: String,
		error: Box<CoerceError>,
	},

	/// The specified value did not conform to the LET type check
	#[error("Couldn't coerce return value from function `{name}`: {error}")]
	ReturnCoerce {
		name: String,
		error: Box<CoerceError>,
	},

	/// Internal server error
	/// Unimplemented functionality
	#[error("Unimplemented functionality: {0}")]
	Unimplemented(String),

	/// The planner does not support this statement type (e.g. DML/DDL).
	/// Callers should always fall back to the compute path.
	#[error("Planner unsupported: {0}")]
	PlannerUnsupported(String),

	/// The planner intends to support this but it is not yet implemented.
	/// Callers fall back in BestEffort mode; hard error in AllReadOnlyStatements mode.
	#[error("Planner not yet implemented: {0}")]
	#[allow(
		dead_code,
		reason = "no planner path raises this today; the ladders that read it are already in place"
	)]
	PlannerUnimplemented(String),

	/// The access method cannot be defined on the requested level
	#[error("The access method cannot be defined on the requested level")]
	AccessLevelMismatch,

	#[error(
		"The ES512 algorithm is not currently supported. Please use ES384 or another supported algorithm"
	)]
	AccessUnsupportedAlgorithm,

	/// The access names the redaction placeholder as its key.
	///
	/// Every surface that renders an access substitutes that placeholder for
	/// the real key, export included, so the text of a rendered access is not
	/// a definition that can be replayed — defining from it would sign tokens
	/// with a published string.
	#[error(
		"The access method '{ac}' names the redaction placeholder as its key, which is published wherever an access is rendered; restore it from a definition carrying the real key, or define it again"
	)]
	AccessRedactedKey {
		ac: String,
	},

	#[error(
		"Tokens issued by record access methods can be consumed by third parties and must have an expiration; DURATION FOR TOKEN cannot be NONE on TYPE RECORD access"
	)]
	AccessRecordTokenDurationRequired,

	#[error(
		"A CONTEXT clause is evaluated on every authentication and its result is exposed to the client; it must be read-only"
	)]
	AccessContextNotReadOnly,

	#[error(
		"A CONTEXT clause is evaluated with the authenticated record's own permissions, so it can only be defined on an access method that authenticates a record"
	)]
	AccessContextRequiresRecord,

	#[error("This access grant has an invalid subject")]
	AccessGrantInvalidSubject,

	#[error("This access grant has been revoked")]
	AccessGrantRevoked,

	/// Found an unexpected value in a range
	#[error("Found {found} for bound but expected {expected}.")]
	InvalidBound {
		found: String,
		expected: String,
	},

	/// Found an unexpected value in a range
	#[error("Exceeded the idiom recursion limit of {limit}.")]
	IdiomRecursionLimitExceeded {
		limit: u32,
	},

	/// Tried to use an idiom RepeatRecurse symbol in a position where it is not
	/// supported
	#[error("Tried to use a `@` repeat recurse symbol in a position where it is not supported")]
	UnsupportedRepeatRecurse,

	/// Tried to use an idiom RepeatRecurse symbol in a position where it is not
	/// supported
	#[error("Cannot construct a recursion plan when an instruction is provided")]
	RecursionInstructionPlanConflict,

	/// Encountered a non-record-id value during recursive graph traversal
	#[error("Expected a record ID during recursive graph traversal, but found `{value}`")]
	InvalidRecursionTarget {
		value: String,
	},

	/// The `REFERENCE` keyword can only be used in combination with a type
	/// referencing a record
	#[error(
		"Cannot use the `REFERENCE` keyword with `TYPE {0}`. Specify only a `record` type, or a type containing only records, instead."
	)]
	ReferenceTypeConflict(String),

	#[error(
		"Cannot use the `REFERENCE` keyword on nested field `{0}`. Specify a referencing field at the root level instead."
	)]
	ReferenceNestedField(String),

	#[error(
		"Cannot set field `{name}` with type `{kind}` as it mismatched with field `{existing_name}` with type `{existing_kind}`"
	)]
	MismatchedFieldTypes {
		name: String,
		kind: String,
		existing_name: String,
		existing_kind: String,
	},

	/// The `COMPUTED` clause cannot be used with other clauses altering or
	/// working with the value
	#[error("Cannot use the `{0}` keyword with `COMPUTED`.")]
	ComputedKeywordConflict(String),

	/// The `COMPUTED` clause cannot be used with other nested fields
	#[error("Cannot define field `{0}` as `COMPUTED` since a nested field `{1}` already exists.")]
	ComputedNestedFieldConflict(String, String),

	/// The `COMPUTED` clause cannot be used with other nested fields
	#[error("Cannot define nested field `{0}` as parent field `{1}` is a `COMPUTED` field.")]
	ComputedParentFieldConflict(String, String),

	#[error("Cannot define field `{0}` as `COMPUTED` fields must be top-level.")]
	ComputedNestedField(String),

	/// A `COMPUTED` body is evaluated on every read of the field, inside the
	/// reading statement's transaction, so it must not modify data.
	#[error("Cannot define field `{0}` as `COMPUTED` bodies must be read-only.")]
	ComputedWrite(String),

	/// A field's DEFAULT / VALUE / ASSERT / COMPUTED clauses read each other in
	/// a cycle, so there is no order in which they can all be evaluated
	#[error("Cyclic dependency detected among field clauses: {0}")]
	ComputedFieldCycle(String),

	/// Cannot use the `{0}` keyword on the `id` field
	#[error("Cannot use the `{0}` keyword on the `id` field.")]
	IdFieldKeywordConflict(String),

	/// Cannot use the `{0}` keyword on the `id` field
	#[error("Cannot use the `{0}` type on the `id` field, as that's not a valid record id key.")]
	IdFieldUnsupportedKind(String),

	#[error("Computed fields cannot be indexed. Index: '{index}' - Field: '{field}'")]
	ComputedFieldCannotBeIndexed {
		field: String,
		index: String,
	},
}

impl LeafError for Error {
	fn map_kind(self, message: String) -> TypesError {
		match self {
			// The query named a scope or a parameter it may not use.
			Error::NsEmpty => TypesError::validation(message, ValidationError::NamespaceEmpty),
			Error::DbEmpty => TypesError::validation(message, ValidationError::DatabaseEmpty),
			Error::InvalidParam {
				name,
			} => TypesError::validation(
				message,
				ValidationError::InvalidParameter {
					name,
				},
			),
			Error::AccessUnsupportedAlgorithm => TypesError::validation(message, None),
			// The statement carries a key that cannot be a real one, which the
			// caller can see and correct in the text they sent.
			Error::AccessRedactedKey {
				..
			} => TypesError::validation(message, None),
			// The definition itself is malformed, so the client can act on it.
			// `ComputedFieldSideEffect` is the same fault caught at read time
			// instead of definition time, so it reaches clients the same way.
			Error::ComputedWrite(_) | Error::ComputedFieldSideEffect => {
				TypesError::validation(message, ValidationError::InvalidRequest)
			}
			// A `CONTEXT` clause the access method may not carry: the author of
			// the DEFINE/ALTER can correct it, so it is a validation fault
			// rather than an engine gap.
			Error::AccessContextNotReadOnly | Error::AccessContextRequiresRecord => {
				TypesError::validation(message, ValidationError::InvalidRequest)
			}

			// A `THROW`, reaching the client verbatim. The only kind in this
			// type the query author chooses.
			Error::Thrown(_) => TypesError::thrown(message),

			// A gap in the engine rather than a fault in the query: there is
			// nothing for a client to correct, so it stays internal.
			Error::Unimplemented(_) => TypesError::internal(message),

			// A cross-tenant namespace/database refusal: the authenticated auth
			// level is not permitted to reach the selected ns/db. This is an
			// authorization denial, so it reaches clients as `NotAllowed` rather
			// than as an internal error.
			Error::NsNotAllowed {
				..
			}
			| Error::DbNotAllowed {
				..
			} => TypesError::not_allowed(message, None),

			// Reach clients as an untyped internal error only because they always
			// have. Each is a candidate for a real kind, and giving one a kind
			// moves the wire snapshot, so it is a deliberate change and not a
			// tidy-up.
			Error::Query {
				..
			}
			| Error::InvalidFetch {
				..
			}
			| Error::InvalidLimit {
				..
			}
			| Error::InvalidStart {
				..
			}
			| Error::InvalidScript {
				..
			}
			| Error::InvalidModel {
				..
			}
			| Error::InvalidFunction {
				..
			}
			| Error::InvalidControlFlow
			| Error::ComputationDepthExceeded
			| Error::InvalidStatement(_)
			| Error::InvalidStatementTarget {
				..
			}
			| Error::CreateStatement {
				..
			}
			| Error::UpsertStatement {
				..
			}
			| Error::UpdateStatement {
				..
			}
			| Error::RelateStatementIn {
				..
			}
			| Error::RelateStatementId {
				..
			}
			| Error::RelateStatementOut {
				..
			}
			| Error::DeleteStatement {
				..
			}
			| Error::InsertStatement {
				..
			}
			| Error::InsertStatementIn {
				..
			}
			| Error::InsertStatementId {
				..
			}
			| Error::InsertStatementOut {
				..
			}
			| Error::LiveStatement {
				..
			}
			| Error::KillStatement {
				..
			}
			| Error::SingleOnlyOutput
			| Error::ParamPermissions {
				..
			}
			| Error::FunctionPermissions {
				..
			}
			| Error::PermissionPredicateSideEffect
			| Error::SetCoerce {
				..
			}
			| Error::ReturnCoerce {
				..
			}
			| Error::PlannerUnsupported(_)
			| Error::PlannerUnimplemented(_)
			| Error::AccessLevelMismatch
			| Error::AccessRecordTokenDurationRequired
			| Error::AccessGrantInvalidSubject
			| Error::AccessGrantRevoked
			| Error::InvalidBound {
				..
			}
			| Error::IdiomRecursionLimitExceeded {
				..
			}
			| Error::UnsupportedRepeatRecurse
			| Error::RecursionInstructionPlanConflict
			| Error::InvalidRecursionTarget {
				..
			}
			| Error::ReferenceTypeConflict(_)
			| Error::ReferenceNestedField(_)
			| Error::MismatchedFieldTypes {
				..
			}
			| Error::ComputedKeywordConflict(_)
			| Error::ComputedNestedFieldConflict(..)
			| Error::ComputedParentFieldConflict(..)
			| Error::ComputedNestedField(_)
			| Error::ComputedFieldCycle(_)
			| Error::IdFieldKeywordConflict(_)
			| Error::IdFieldUnsupportedKind(_)
			| Error::ComputedFieldCannotBeIndexed {
				..
			} => internal_todo(message),
		}
	}
}

/// Raised inside the recursive `compute` path, which signals through
/// [`ControlFlow`](crate::expr::ControlFlow) rather than returning `Err`.
impl From<Error> for crate::expr::ControlFlow {
	fn from(error: Error) -> Self {
		crate::expr::ControlFlow::Err(anyhow::Error::new(error))
	}
}