surrealdb-core 3.2.3

A scalable, distributed, collaborative, document-graph database, for the realtime web
Documentation
//! Computed index access part -- `[expr]`.

use std::sync::Arc;

use surrealdb_types::{SqlFormat, ToSql};

use crate::exec::physical_expr::{EvalContext, PhysicalExpr};
use crate::exec::{AccessMode, BoxFut, ContextLevel};
use crate::expr::FlowResult;
use crate::val::Value;

/// Computed index access - `[expr]`.
#[derive(Debug, Clone)]
pub struct IndexPart {
	pub expr: Arc<dyn PhysicalExpr>,
}
impl PhysicalExpr for IndexPart {
	fn name(&self) -> &'static str {
		"Index"
	}

	fn as_any(&self) -> &dyn std::any::Any {
		self
	}

	fn required_context(&self) -> ContextLevel {
		self.expr.required_context()
	}

	fn evaluate<'a>(&'a self, ctx: EvalContext<'a>) -> BoxFut<'a, FlowResult<Value>> {
		Box::pin(async move {
			let value = ctx.current_value.unwrap_or(&Value::NONE);
			// Evaluate the index expression against the document root (if available)
			// so that dynamic key references like `[field]` or `[$param]` resolve
			// against the full document rather than the chain's current position.
			// This matches the old compute path where Part::Value evaluates the
			// expression with `doc` (the full cursor document).
			let index_ctx = if let Some(doc) = ctx.document_root {
				ctx.with_value(doc)
			} else {
				ctx
			};
			let index = self.expr.evaluate(index_ctx).await?;
			Ok(evaluate_index(value, &index)?)
		})
	}

	fn access_mode(&self) -> AccessMode {
		self.expr.access_mode()
	}
}

impl ToSql for IndexPart {
	fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
		f.push('[');
		self.expr.fmt_sql(f, fmt);
		f.push(']');
	}
}

/// Index access on arrays, sets, objects, and record IDs.
pub(crate) fn evaluate_index(value: &Value, index: &Value) -> anyhow::Result<Value> {
	use crate::val::record_id::RecordIdKey;

	match (value, index) {
		// Array with numeric index
		(Value::Array(arr), Value::Number(n)) => {
			Ok(n.as_array_index().and_then(|idx| arr.get(idx).cloned()).unwrap_or(Value::None))
		}
		// Set with numeric index
		(Value::Set(set), Value::Number(n)) => {
			Ok(n.as_array_index().and_then(|idx| set.nth(idx).cloned()).unwrap_or(Value::None))
		}
		// Array with range
		(Value::Array(arr), Value::Range(range)) => {
			let slice = range
				.as_ref()
				.clone()
				.coerce_to_typed::<i64>()
				.map_err(|e| anyhow::anyhow!("Invalid range: {}", e))?
				.slice(arr.as_slice())
				.map(|s| Value::Array(s.to_vec().into()))
				.unwrap_or(Value::None);
			Ok(slice)
		}
		// Object with string key
		(Value::Object(obj), Value::String(key)) => {
			Ok(obj.get(key.as_str()).cloned().unwrap_or(Value::None))
		}
		// Object with numeric key (converted to string)
		(Value::Object(obj), Value::Number(n)) => {
			let key = n.to_string();
			Ok(obj.get(&key).cloned().unwrap_or(Value::None))
		}
		// RecordId with numeric index - only array keys support indexing
		(Value::RecordId(rid), Value::Number(n)) => match &rid.key {
			RecordIdKey::Array(arr) => {
				Ok(n.as_array_index().and_then(|idx| arr.get(idx).cloned()).unwrap_or(Value::None))
			}
			_ => Ok(Value::None),
		},
		_ => Ok(Value::None),
	}
}