reddb-io-server 1.0.7

RedDB server-side engine: storage, runtime, replication, MCP, AI, and the gRPC/HTTP/RedWire/PG-wire dispatchers. Re-exported by the umbrella `reddb` crate.
Documentation
//! `write_json_string` is deprecated for new boundary emission
//! (see ADR 0010 / issue #177), but the existing scan-fast-path
//! helpers in this file route through it internally. Allow the
//! internal recursion without warnings; the lint surfaces only
//! at out-of-file callers.
#![allow(deprecated)]

use super::*;

pub(crate) fn scan_reply(page: ScanPage) -> ScanReply {
    ScanReply {
        collection: page.collection,
        total: page.total as u64,
        next_offset: page.next.map(|cursor| cursor.offset as u64),
        items: page.items.into_iter().map(scan_entity).collect(),
    }
}

pub(crate) fn scan_entity(entity: UnifiedEntity) -> ScanEntity {
    ScanEntity {
        id: entity.id.raw(),
        kind: entity.kind.storage_type().to_string(),
        collection: entity.kind.collection().to_string(),
        json: crate::presentation::entity_json::compact_entity_json_string(&entity),
    }
}

pub(crate) fn query_reply(
    result: RuntimeQueryResult,
    entity_types: &Option<Vec<String>>,
    capabilities: &Option<Vec<String>>,
) -> QueryReply {
    let RuntimeQueryResult {
        mode,
        statement,
        engine,
        result,
        ..
    } = result;

    // Fast path: use pre-serialized JSON if available (move, no clone)
    if let Some(pre_serialized_json) = result.pre_serialized_json {
        let count = result.stats.rows_scanned;
        return QueryReply {
            ok: true,
            mode: format!("{mode:?}").to_lowercase(),
            statement: statement.to_string(),
            engine: engine.to_string(),
            columns: result.columns,
            record_count: count,
            result_json: pre_serialized_json,
        };
    }

    let records = crate::presentation::query_view::filter_query_records(
        &result.records,
        entity_types,
        capabilities,
    );
    QueryReply {
        ok: true,
        mode: format!("{mode:?}").to_lowercase(),
        statement: statement.to_string(),
        engine: engine.to_string(),
        columns: result.columns.clone(),
        record_count: records.len() as u64,
        result_json: unified_result_json_string_with_records(
            &result,
            &records,
            entity_types,
            capabilities,
        ),
    }
}

pub(crate) fn unified_result_json_string_with_records(
    result: &crate::storage::query::unified::UnifiedResult,
    records: &[crate::storage::query::unified::UnifiedRecord],
    entity_types: &Option<Vec<String>>,
    capabilities: &Option<Vec<String>>,
) -> String {
    // Fast path: write JSON directly to string buffer (no intermediate JsonValue tree)
    use crate::storage::schema::Value;
    use std::fmt::Write;

    let selection_scope = if entity_types.is_none() && capabilities.is_none() {
        "any"
    } else {
        "filtered"
    };

    // Estimate capacity: ~200 bytes per record for typical user data
    let mut buf = String::with_capacity(128 + records.len() * 200);

    buf.push_str("{\"columns\":[");
    for (i, col) in result.columns.iter().enumerate() {
        if i > 0 {
            buf.push(',');
        }
        write_json_string(&mut buf, col);
    }
    buf.push_str("],\"record_count\":");
    let _ = write!(buf, "{}", records.len());
    buf.push_str(",\"selection\":{\"scope\":\"");
    buf.push_str(selection_scope);
    buf.push_str("\"},\"records\":[");

    for (ri, record) in records.iter().enumerate() {
        if ri > 0 {
            buf.push(',');
        }
        buf.push('{');
        let mut first = true;
        for (key, value) in record.iter_fields() {
            if !first {
                buf.push(',');
            }
            first = false;
            write_json_string(&mut buf, key);
            buf.push(':');
            write_value_json(&mut buf, value);
        }
        buf.push('}');
    }

    buf.push_str("]}");
    buf
}

/// Write a JSON-escaped string (with quotes) to a buffer.
///
/// **Deprecation note (ADR 0010 / issue #177):** the canonical JSON
/// string encoder is `crate::serde_json::Value::escape_string`
/// (used internally by `to_string_compact`). This local fast-path
/// is correct after F-01 hotfix #181 but is not the canonical owner
/// of the serialization boundary; new gRPC reply assembly should
/// route caller-influenced strings through the canonical encoder
/// (or, on the audit boundary, through `AuditFieldEscaper`). Kept
/// here pending a follow-up retirement slice — the gRPC scan path
/// has hot-loop performance characteristics that need a benchmark
/// before retirement.
#[deprecated(
    note = "Use crate::serde_json::Value::to_string_compact for boundary emission; see ADR 0010 / issue #177"
)]
#[inline]
pub fn write_json_string(buf: &mut String, s: &str) {
    buf.push('"');
    for ch in s.chars() {
        match ch {
            '"' => buf.push_str("\\\""),
            '\\' => buf.push_str("\\\\"),
            '\n' => buf.push_str("\\n"),
            '\r' => buf.push_str("\\r"),
            '\t' => buf.push_str("\\t"),
            c if c < '\x20' => {
                let _ = std::fmt::Write::write_fmt(buf, format_args!("\\u{:04x}", c as u32));
            }
            c => buf.push(c),
        }
    }
    buf.push('"');
}

/// Write a storage Value as JSON to a buffer (no intermediate JsonValue).
#[inline]
pub fn write_value_json(buf: &mut String, value: &crate::storage::schema::Value) {
    use crate::storage::schema::Value;
    match value {
        Value::Null => buf.push_str("null"),
        Value::Boolean(b) => buf.push_str(if *b { "true" } else { "false" }),
        Value::Integer(n) => {
            let _ = std::fmt::Write::write_fmt(buf, format_args!("{n}"));
        }
        Value::UnsignedInteger(n) => {
            let _ = std::fmt::Write::write_fmt(buf, format_args!("{n}"));
        }
        Value::Float(f) => {
            if f.is_finite() {
                let _ = std::fmt::Write::write_fmt(buf, format_args!("{f}"));
            } else {
                buf.push_str("null");
            }
        }
        Value::Text(s) => write_json_string(buf, s),
        Value::Timestamp(t) => {
            let _ = std::fmt::Write::write_fmt(buf, format_args!("{t}"));
        }
        Value::Duration(d) => {
            let _ = std::fmt::Write::write_fmt(buf, format_args!("{d}"));
        }
        Value::Blob(bytes) => {
            buf.push('"');
            buf.push_str(&hex::encode(bytes));
            buf.push('"');
        }
        _ => buf.push_str("null"),
    }
}

pub(crate) fn grpc_parse_query_filters(
    request: &QueryRequest,
) -> Result<(Option<Vec<String>>, Option<Vec<String>>), Status> {
    crate::application::query_payload::normalize_search_selection(
        &request.entity_types,
        &request.capabilities,
    )
    .map_err(|err| Status::invalid_argument(err.to_string()))
}