feat: JSON/JSONB validation, multi-column indexes, CTE execution, wire protocol column types

- Add fkJson to wire protocol with serialization/deserialization
- Validate JSON/JSONB on INSERT/UPDATE via std/json
- Send real column type metadata in wire protocol responses
- Update all 4 clients (Nim, Python, JS, Rust) for JSON and columnTypes
- Implement multi-column index parser (CREATE INDEX idx ON t(a, b))
- Add ciColumns to AST nkCreateIndex
- Build composite index keys as table.col1.col2 with val1|val2
- Support multi-column exact match in SELECT for AND chains
- Implement non-recursive CTE execution via ctx.cteTables materialization
- Add tkRecursive token and parse WITH RECURSIVE
- Fix test isolation: use temp dirs for JOIN, migration, and RLS tests
- All tests passing (0 failures)
This commit is contained in:
2026-05-07 10:27:40 +03:00
parent ca345eb256
commit eaa5760fd4
14 changed files with 300 additions and 46 deletions
+15 -3
View File
@@ -39,6 +39,7 @@ const FK_BYTES: u8 = 0x09;
const FK_ARRAY: u8 = 0x0A;
const FK_OBJECT: u8 = 0x0B;
const FK_VECTOR: u8 = 0x0C;
const FK_JSON: u8 = 0x0D;
/// Connection configuration
#[derive(Debug, Clone)]
@@ -66,6 +67,7 @@ impl Default for Config {
#[derive(Debug)]
pub struct QueryResult {
columns: Vec<String>,
column_types: Vec<String>,
rows: Vec<HashMap<String, String>>,
affected_rows: usize,
}
@@ -75,6 +77,10 @@ impl QueryResult {
&self.columns
}
pub fn column_types(&self) -> &[String] {
&self.column_types
}
pub fn rows(&self) -> &[HashMap<String, String>] {
&self.rows
}
@@ -158,7 +164,7 @@ impl Client {
}
match kind {
MK_READY => Ok(QueryResult { columns: vec![], rows: vec![], affected_rows: 0 }),
MK_READY => Ok(QueryResult { columns: vec![], column_types: vec![], rows: vec![], affected_rows: 0 }),
MK_DATA => {
let mut pos = 0usize;
let col_count = read_u32(&payload, &mut pos) as usize;
@@ -166,6 +172,11 @@ impl Client {
for _ in 0..col_count {
columns.push(read_string(&payload, &mut pos));
}
let mut col_types = Vec::with_capacity(col_count);
for _ in 0..col_count {
col_types.push(format!("{}", payload[pos]));
pos += 1;
}
let row_count = read_u32(&payload, &mut pos) as usize;
let mut rows = Vec::with_capacity(row_count);
for _ in 0..row_count {
@@ -188,13 +199,13 @@ impl Client {
let affected = if comp_kind == MK_COMPLETE && comp_payload.len() >= 4 {
u32::from_be_bytes([comp_payload[0], comp_payload[1], comp_payload[2], comp_payload[3]]) as usize
} else { 0 };
Ok(QueryResult { columns, rows, affected_rows: affected })
Ok(QueryResult { columns, column_types: col_types, rows, affected_rows: affected })
}
MK_COMPLETE => {
let affected = if payload.len() >= 4 {
u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]) as usize
} else { 0 };
Ok(QueryResult { columns: vec![], rows: vec![], affected_rows: affected })
Ok(QueryResult { columns: vec![], column_types: vec![], rows: vec![], affected_rows: affected })
}
MK_ERROR => Err("Query error".into()),
_ => Err(format!("Unknown response kind: {}", kind).into()),
@@ -433,6 +444,7 @@ fn read_value(data: &[u8], pos: &mut usize) -> String {
*pos += (dim * 4) as usize;
format!("<vector:{}>", dim)
}
FK_JSON => read_string(data, pos),
_ => String::new(),
}
}