# vTable **Repository Path**: cpsoft13/v-table ## Basic Information - **Project Name**: vTable - **Description**: 虚拟数据库引擎,提供更多虚拟字段类型。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-05-28 - **Last Updated**: 2026-05-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # v-table 虚拟表引擎 — 解析 SQL,解析逻辑字段,针对任意后端执行查询。 ## 架构 ``` 客户端 SQL ──> Parser ──> LogicalPlan ──> Resolver ──> ResolvedPlan │ ▼ QueryResult <── Executor <── PhysicalPlan <── Planner <── Validator ``` 每个阶段生成类型化的中间表示,调用方可以在传入下游之前检查或修改任意阶段。 ## 存储后端 引擎通过 `StorageBackend` trait 与数据库交互。项目提供了三种内置后端实现,通过 Cargo feature 按需启用: | Feature | 后端 | 用途 | |---------|------|------| | `backend-duckdb` | DuckDB | 嵌入式分析数据库(零依赖,进程内运行) | | `backend-postgres` | PostgreSQL | 远程关系数据库(通过 sqlx 连接池) | | `backend-redis` | Redis | 缓存 / 键值存储 / 发布订阅 | ### 自定义后端 如需接入其他数据库,实现 `StorageBackend` trait 即可: ```rust #[async_trait] pub trait StorageBackend: Send + Sync { async fn execute_query(&self, sql: &str) -> Result; async fn execute(&self, sql: &str) -> Result; async fn execute_with_params( &self, sql: &str, params: &[serde_json::Value], ) -> Result; async fn transaction(&self) -> Result>; fn backend_type(&self) -> BackendType; async fn run_migrations(&self) -> Result<()>; async fn close(&self) -> Result<()>; } ``` 引擎通过 `backend_type()` 选择 SQL 方言(DuckDB 或 PostgreSQL)。至少需要实现 `execute_query` 和 `backend_type`,其余方法如不需要可返回 `unimplemented!()`。 ## 快速开始 ```rust use std::collections::HashMap; use std::sync::Arc; use v_table::{ VirtualSqlEngine, TableMetadata, FieldMetadata, FieldKind, connection::{ConnectionConfig, create_backend}, types::{FieldId, TableId, QueryDefinition}, }; // 1. 选择内置后端(以 DuckDB 内存数据库为例) let config = ConnectionConfig::duckdb_in_memory(); let backend = create_backend(&config).await?; // 2. 描述引擎可以查询的表和字段 let tables = vec![ TableMetadata::new(TableId::new(), "users"), ]; let mut fields = HashMap::new(); fields.insert( "name".to_string(), FieldMetadata::new(FieldId::new(), "name", FieldKind::Text), ); fields.insert( "age".to_string(), FieldMetadata::new(FieldId::new(), "age", FieldKind::Number), ); // 3. 创建引擎并执行 let engine = VirtualSqlEngine::new(Arc::from(backend)); let plan = engine.compile_raw_sql( "SELECT name, age FROM users WHERE age > 18 ORDER BY age DESC LIMIT 50", &tables, &fields, )?; let result = engine.execute(&plan).await?; ``` ## 内置数据库连接 ### DuckDB ```rust use v_table::connection::{ConnectionConfig, create_backend}; // 内存数据库 let config = ConnectionConfig::duckdb_in_memory(); // 或文件持久化 let config = ConnectionConfig::duckdb_file("/path/to/data.db"); let backend = create_backend(&config).await?; ``` DuckDB 为嵌入式数据库,无需外部进程。所有操作通过 `tokio::task::spawn_blocking` 调度,安全地在异步上下文中运行。 ### PostgreSQL ```rust use v_table::connection::{ConnectionConfig, create_backend}; // 字段式配置 let config = ConnectionConfig::postgres("localhost", 5432, "mydb", "user", "pass"); // 或连接 URL let config = ConnectionConfig::postgres_url("postgres://user:pass@localhost:5432/mydb"); let backend = create_backend(&config).await?; ``` 基于 `sqlx::PgPool` 连接池,支持异步查询、参数绑定、事务。 ### Redis ```rust use v_table::connection::{ConnectionConfig, create_redis_client}; // Redis 不是 SQL 后端,使用独立的 create_redis_client 函数 let config = ConnectionConfig::redis("localhost", 6379, None, 0); let client = create_redis_client(&config).await?; // 基础键值操作 client.set("key", "value").await?; let val = client.get("key").await?; // Option client.set_ex("cache_key", "data", 3600).await?; // 带 TTL client.del(&["key"]).await?; // Hash 操作 client.hset("hash", "field", "value").await?; let items = client.hgetall("hash").await?; // HashMap // 发布订阅 client.publish("channel", "message").await?; // JSON 序列化 client.set_json("user", &user_struct).await?; let user: Option = client.get_json("user").await?; ``` ### 配置文件反序列化 `ConnectionConfig` 支持 JSON 反序列化,可从配置文件加载: ```json { "backend": "duck_db", "path": "/data/app.db" } ``` ```json { "backend": "postgre_sql", "host": "db.example.com", "port": 5432, "database": "prod", "username": "app", "password": "***" } ``` ```json { "backend": "redis", "host": "cache.example.com", "port": 6379, "password": "***", "db": 0 } ``` ```rust let config: ConnectionConfig = serde_json::from_str(&json_str)?; let backend = create_backend(&config).await?; ``` ## 两种入口 ### 入口 A — 结构化 QueryDefinition 通过类型安全的结构体编程式构建查询,可序列化/反序列化为 JSON 以便持久化: ```rust let qd = QueryDefinition { source_table_id: users_table_id, fields: vec![ QueryField { table_alias: None, field_id: name_field_id, alias: Some("user_name".into()), aggregate: None, }, ], filter: Some(FilterGroup { operator: LogicalOperator::And, conditions: vec![ FilterNode::Condition(FilterCondition { field_id: age_field_id, operator: FilterOperator::Gt, value: Some(serde_json::json!(18)), }), ], }), sorts: vec![SortConfig { field_id: created_field_id, direction: SortDirection::Desc, field_kind: None, }], limit: Some(100), ..Default::default() }; let plan = engine.compile_query_definition(&qd, &tables, &fields)?; let result = engine.execute(&plan).await?; ``` **QueryDefinition JSON 传输格式** — 完全兼容 serde。上例序列化结果: ```json { "source_table_id": "...", "fields": [ { "field_id": "...", "alias": "user_name" } ], "filter": { "operator": "and", "conditions": [ { "field_id": "...", "operator": "gt", "value": 18 } ] }, "sorts": [ { "field_id": "...", "direction": "desc" } ], "limit": 100 } ``` ### 入口 B — 原始 SQL 运行时解析 PostgreSQL 方言的 SELECT 语句: ```rust let plan = engine.compile_raw_sql( "WITH active AS (SELECT * FROM users WHERE status = 'active') SELECT name, age FROM active ORDER BY name LIMIT 50", &tables, &fields, )?; let result = engine.execute(&plan).await?; ``` ### 仅预览不执行 ```rust let sql = engine.preview(&qd, &tables, &fields)?; // 在执行前查看生成的物理 SQL ``` ## 支持的 SQL 特性 | 特性 | 支持情况 | |------|----------| | SELECT / DISTINCT | 完整支持 | | FROM(单表) | 完整支持 | | WHERE 带 AND/OR 嵌套 | 完整支持 | | JOIN(INNER/LEFT/RIGHT/CROSS) | 完整支持 | | GROUP BY / HAVING | 完整支持 | | ORDER BY(ASC/DESC) | 完整支持 | | LIMIT / OFFSET | 完整支持 | | WITH(CTE,非递归) | 完整支持 | | 聚合函数(COUNT/SUM/AVG/MIN/MAX) | 完整支持 | | FROM 中的子查询 | 不支持 | | INSERT/UPDATE/DELETE | 拒绝执行 | | 递归 CTE | 不支持 | **刻意不支持**:写操作、多语句、子查询以及所有 DDL/DML。校验器在 `validate_readonly_sql()` 关卡拒绝这些操作。 ## 字段元数据 每个可查询字段必须注册为 `FieldMetadata`: ```rust FieldMetadata { field_id: FieldId::new(), // 唯一标识 logical_name: "price".into(), // 面向用户的列名 kind: FieldKind::Number, // 用于类型转换/校验 physical_path: "values->>'$.fld_price'".into(), // JSON 提取路径 is_system: false, // true → 在 SELECT * 中隐藏 formula_expression: None, // 计算字段填 Some("{Price} * {Qty}") } ``` **物理路径约定**:默认路径为 `values->>'$.FIELD_ID'`,适用于将字段值存储在 JSON 列中的存储后端。不同 schema 的后端可以覆盖 `physical_path`。 ## 公式表达式 公式字段会自动翻译为 SQL: ```rust let mut price = FieldMetadata::new(fid, "total", FieldKind::Formula); price = price.with_formula_expression("{Price} * {Quantity}".into()); // Planner 生成: CAST(values->>'$.fld_price' AS DOUBLE) * CAST(values->>'$.fld_qty' AS DOUBLE) ``` **支持**:算术运算、比较运算、逻辑运算(AND/OR/NOT)、字符串操作(CONCAT、LEFT、RIGHT、UPPER 等)、日期/时间(NOW、TODAY、YEAR、MONTH 等)、条件判断(IF)、类型转换。 **公式语法**遵循 Airtable 惯例: ``` {Price} * {Quantity} * 1.2 IF({Status} = "done", 1, 0) UPPER(LEFT({Name}, 3)) TODAY() - {DueDate} ``` ## 安全模型 - **只读强制**:每条编译的 SQL 经过 `validate_readonly_sql()` 校验 — INSERT/UPDATE/DELETE/DROP/ALTER 等在执行前被拒绝 - **多语句注入拦截**:分号分隔的多条语句被拒绝 - **语句白名单**:仅允许 SELECT、WITH、EXPLAIN、DESCRIBE、SHOW 作为语句起始 - **LIMIT 上限**:所有查询上限为 10000 行(resolver 中的硬上限) - **隐式软删除**:每条查询自动追加 `WHERE deleted_at IS NULL` - **表隔离**:每条查询自动追加 `WHERE table_id = ''` 以隔离不同表 ## 错误类型 ```rust // 引擎级错误(执行前) pub enum EngineError { ParseError(String), // SQL 语法错误 UnknownTable(String), // 元数据中不存在的表 UnknownField(String), // 元数据中不存在的字段 AmbiguousField(String), // 字段名冲突 ValidationError(String), // 语义校验失败 WriteNotAllowed(String), // 禁止的 SQL 关键字 TypeError(String), // 类型不匹配 PlanError(String), // 物理计划生成失败 ExecutionError(String), // 后端返回错误 FormulaTranslationError(String), Internal(String), } // 顶层错误(跨层) pub enum VTableError { NotFound(String), Validation(String), Database(String), Internal(String), } pub type EngineResult = Result; pub type Result = Result; ``` `EngineError` 通过 `From` 自动转换为 `VTableError` — `engine.execute().await` 直接返回 `Result`。 ## QueryResult ```rust pub struct QueryResult { pub columns: Vec, // 逻辑列名 pub column_types: Vec, // 类型提示 pub rows: Vec>>, // 行数据 pub affected_rows: u64, } ``` 使用 `executor::filter_system_columns(&mut result)` 在返回结果给客户端之前剥离 `table_id`、`deleted_at` 和 `created_by` 列。 ## 自定义后端集成 如需接入内置后端不支持的数据库,实现 `StorageBackend` trait: ```rust struct MyBackend { /* ... */ } #[async_trait] impl StorageBackend for MyBackend { async fn execute_query(&self, sql: &str) -> v_table::Result { // 执行 SQL,将结果转为 QueryResult } fn backend_type(&self) -> BackendType { BackendType::PostgreSQL } async fn execute(&self, sql: &str) -> v_table::Result { todo!() } async fn execute_with_params(&self, sql: &str, p: &[serde_json::Value]) -> v_table::Result { todo!() } async fn transaction(&self) -> v_table::Result> { todo!() } async fn run_migrations(&self) -> v_table::Result<()> { todo!() } async fn close(&self) -> v_table::Result<()> { todo!() } } ``` ## 依赖 | Crate | 版本 | 用途 | Feature | |-------|------|------|---------| | sqlparser | 0.56 | PostgreSQL 方言 SQL 解析 | 始终 | | serde / serde_json | 1 | JSON 序列化 | 始终 | | uuid | 1 | 类型安全标识符 | 始终 | | tokio | 1 | 异步运行时 | 始终 | | async-trait | 0.1 | 异步 trait | 始终 | | thiserror | 2 | 错误派生 | 始终 | | tracing | 0.1 | 结构化日志 | 始终 | | duckdb | 1 | DuckDB 嵌入式数据库 | `backend-duckdb` | | sqlx | 0.8 | PostgreSQL 异步驱动 | `backend-postgres` | | redis | 0.31 | Redis 客户端 | `backend-redis` | ## 许可证 MIT