icydb 0.238.0

IcyDB — A schema-first typed query engine and persistence runtime for Internet Computer canisters
Documentation
//! Module: db::sql::convert
//!
//! Responsibility: public SQL result and rendering facade.
//! Does not own: SQL parsing, lowering, planning, or execution.
//! Boundary: converts executed core SQL outputs into endpoint-friendly payloads.

use crate::db::{
    RowProjectionOutput,
    sql::{
        SqlConstraintValidationOutput, SqlGroupedRowsOutput, SqlQueryResult,
        value_render::{render_projection_value_text, sql_projection_output_rows},
    },
};

use icydb_core::db::{GroupedRow, SqlStatementResult};

pub(crate) fn sql_query_result_from_statement(
    result: SqlStatementResult,
    entity_name: String,
) -> SqlQueryResult {
    match result {
        SqlStatementResult::Count { row_count } => SqlQueryResult::Count {
            entity: entity_name,
            row_count,
        },
        SqlStatementResult::Projection {
            columns,
            fixed_scales,
            rows,
            row_count,
        } => {
            // Preserve projection-local display contracts such as
            // `ROUND(..., scale)` before packaging the outward shell rows.
            let rows = sql_projection_output_rows(fixed_scales.as_slice(), rows);

            SqlQueryResult::Projection(RowProjectionOutput {
                entity: entity_name,
                columns,
                rows,
                row_count,
            })
        }
        SqlStatementResult::Grouped {
            columns,
            fixed_scales,
            rows,
            row_count,
            next_cursor,
        } => SqlQueryResult::Grouped(sql_grouped_rows_output(
            entity_name,
            columns,
            fixed_scales,
            rows,
            row_count,
            next_cursor,
        )),
        #[cfg(feature = "sql")]
        SqlStatementResult::Explain(explain) => SqlQueryResult::Explain {
            entity: entity_name,
            explain,
        },
        SqlStatementResult::Describe(output) => SqlQueryResult::Describe(output),
        SqlStatementResult::ShowConstraints(constraints) => SqlQueryResult::ShowConstraints {
            entity: entity_name,
            constraints,
        },
        SqlStatementResult::ShowIndexes(indexes) => SqlQueryResult::ShowIndexes {
            entity: entity_name,
            indexes,
        },
        SqlStatementResult::ShowColumns(output) => SqlQueryResult::ShowColumns(output),
        SqlStatementResult::ShowRelations(output) => SqlQueryResult::ShowRelations(output),
        SqlStatementResult::ShowEntities { entities, verbose } => {
            SqlQueryResult::ShowEntities { entities, verbose }
        }
        SqlStatementResult::ShowStores { stores, verbose } => {
            SqlQueryResult::ShowStores { stores, verbose }
        }
        SqlStatementResult::ShowMemory(memory) => SqlQueryResult::ShowMemory { memory },
        SqlStatementResult::Ddl(report) => SqlQueryResult::Ddl {
            entity: entity_name,
            mutation_kind: report.mutation_kind().as_str().to_string(),
            target_index: report.target_index().to_string(),
            target_store: report.target_store().to_string(),
            field_path: report.field_path().to_vec(),
            status: report.execution_status().as_str().to_string(),
            rows_scanned: usize_to_u64_saturating(report.rows_scanned()),
            index_keys_written: usize_to_u64_saturating(report.index_keys_written()),
            constraint_validation: report.constraint_validation().map(|validation| {
                SqlConstraintValidationOutput {
                    constraint_id: validation.constraint_id(),
                    activation_epoch: validation.activation_epoch(),
                    page_sequence: validation.page_sequence(),
                    state: validation.state().as_str().to_string(),
                    revision_status: validation.revision_status().as_str().to_string(),
                    rows_scanned: validation.rows_scanned(),
                    findings: validation.findings().to_vec(),
                    complete: validation.complete(),
                }
            }),
        },
    }
}

fn usize_to_u64_saturating(value: usize) -> u64 {
    u64::try_from(value).unwrap_or(u64::MAX)
}

fn sql_grouped_rows_output(
    entity_name: String,
    columns: Vec<String>,
    fixed_scales: Vec<Option<u32>>,
    rows: Vec<GroupedRow>,
    row_count: u32,
    next_cursor: Option<String>,
) -> SqlGroupedRowsOutput {
    let rows = rows
        .into_iter()
        .map(|row| {
            row.group_key()
                .iter()
                .chain(row.aggregate_values().iter())
                .enumerate()
                .map(|(index, value)| {
                    render_projection_value_text(fixed_scales.get(index).copied().flatten(), value)
                })
                .collect::<Vec<_>>()
        })
        .collect::<Vec<_>>();

    SqlGroupedRowsOutput {
        entity: entity_name,
        columns,
        rows,
        row_count,
        next_cursor,
    }
}