selene-db-gql 1.3.0

ISO/IEC 39075:2024 GQL parser, planner, optimizer, and executor for selene-db.
Documentation
use selene_core::{DbString, Value};

use crate::{
    AnalyzedType, BindingTableColumn, GqlType, ProjectExpr, RowExpansionPosition,
    RowExpansionPositionKind, SourceSpan,
    runtime::{Binding, BindingTable, DataExceptionSubclass, EvalCtx, ExecutorError, evaluator},
};

pub(super) fn execute(
    source: &ProjectExpr,
    alias: DbString,
    position: Option<RowExpansionPosition>,
    span: SourceSpan,
    table: BindingTable,
    ctx: &EvalCtx<'_, '_, '_, '_>,
) -> Result<BindingTable, ExecutorError> {
    let (input_schema, input_rows) = table.into_parts();
    let mut output_schema = input_schema.clone();
    output_schema.columns.push(BindingTableColumn {
        name: Some(alias),
        hidden: None,
        ty: element_type(&source.ty),
    });
    if let Some(position) = &position {
        output_schema.columns.push(BindingTableColumn {
            name: Some(position.alias.clone()),
            hidden: None,
            ty: AnalyzedType::Resolved(GqlType::Integer),
        });
    }

    let mut rows = Vec::new();
    let mut rows_since_check = 0;
    for row in input_rows {
        ctx.tx.check_cancellation_stride(&mut rows_since_check, 1)?;
        match evaluator::evaluate(&source.expr, &row, &input_schema, ctx)? {
            Value::List(values) => {
                for (index, value) in values.into_iter().enumerate() {
                    ctx.tx.check_cancellation_stride(&mut rows_since_check, 1)?;
                    let mut output = row.values().to_vec();
                    output.push(value);
                    if let Some(position) = &position {
                        output.push(position_value(position.kind, index, span)?);
                    }
                    rows.push(Binding::new(output));
                }
            }
            Value::Null => {}
            _ => {
                return Err(ExecutorError::data_exception(
                    DataExceptionSubclass::InvalidValueType,
                    "row expansion requires a list value",
                    span,
                ));
            }
        }
    }
    Ok(BindingTable::new(output_schema, rows))
}

fn position_value(
    kind: RowExpansionPositionKind,
    index: usize,
    span: SourceSpan,
) -> Result<Value, ExecutorError> {
    let offset = i64::try_from(index).map_err(|_| {
        ExecutorError::data_exception(
            DataExceptionSubclass::NumericValueOutOfRange,
            "row expansion position exceeds INTEGER range",
            span,
        )
    })?;
    let value = match kind {
        RowExpansionPositionKind::Offset => offset,
        RowExpansionPositionKind::Ordinality => offset.checked_add(1).ok_or_else(|| {
            ExecutorError::data_exception(
                DataExceptionSubclass::NumericValueOutOfRange,
                "row expansion position exceeds INTEGER range",
                span,
            )
        })?,
    };
    Ok(Value::Int(value))
}

fn element_type(ty: &AnalyzedType) -> AnalyzedType {
    match ty {
        AnalyzedType::Resolved(GqlType::List(inner))
        | AnalyzedType::Resolved(GqlType::BoundedList {
            element_type: inner,
            ..
        }) => AnalyzedType::Resolved((**inner).clone()),
        _ => AnalyzedType::DYNAMIC,
    }
}