mirror of
https://github.com/feigeCode/navop.git
synced 2026-09-22 08:01:27 +00:00
- 适配器:基于官方 taos 0.12 驱动(ws-rustls,经 taosAdapter :6041 WebSocket 协议,纯 Rust 无本地 C 依赖),实现 DatabasePlugin/DbConnection 全量接口: 数据库/超级表/子表列举、DESCRIBE(含 TAG 识别)、分页(LIMIT n OFFSET m)、 语句执行/中断、EXPLAIN 等能力声明,内置 19 个单元测试 - 连接模型:DatabaseType 新增 TDengine 内置变体(builtin_all/as_str/from_str/ 图标映射),连接校验使用 SELECT SERVER_STATUS() - 连接表单:DbFormConfig::tdengine() 声明式配置(常规/高级/SSL/SSH/备注), 端口默认 6041、用户默认 root - db_view 扩散:插件注册、扩展菜单、SQL 编辑器、数据对比/分页/类型映射等 适配;索引 DDL 测试跳过 TDengine(无二级索引概念) - 周边打通:连接导入协议、onetcli 连接构建/Schema、TDengine CLI 命令 (taos -h/-P/-u/-d 与 jdbc:TAOS-RS://)、中英繁语言包 - 图标:新增 tdengine_color/tdengine_line_color 彩色与线条图标 同时包含后续 MQTT 提交所需的共享基础:models.rs 中的 MqttParams/ MqttVersion 连接参数模型、ConnectionType::Mqtt 变体、mqtt(线/面)图标、 以及工作区 Cargo.toml 的 taos/rumqttc 依赖与 mqtt 成员声明。
144 lines
4.1 KiB
Rust
144 lines
4.1 KiB
Rust
use one_core::storage::DatabaseType;
|
|
|
|
use crate::DatabasePlugin;
|
|
use crate::executor::QueryColumnMeta;
|
|
use crate::types::{ColumnInfo, TableCellValue};
|
|
|
|
pub(crate) fn quote_string(value: &str) -> String {
|
|
format!("'{}'", value.replace('\'', "''"))
|
|
}
|
|
|
|
pub(crate) fn parse_boolean(value: &str) -> Option<bool> {
|
|
match value.trim().to_ascii_lowercase().as_str() {
|
|
"true" | "t" | "1" => Some(true),
|
|
"false" | "f" | "0" => Some(false),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn strict_numeric_literal(value: &str) -> Option<&str> {
|
|
let value = value.trim();
|
|
if value.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
let bytes = value.as_bytes();
|
|
let mut index = usize::from(matches!(bytes.first(), Some(b'+') | Some(b'-')));
|
|
if index == bytes.len() {
|
|
return None;
|
|
}
|
|
|
|
let integer_start = index;
|
|
while index < bytes.len() && bytes[index].is_ascii_digit() {
|
|
index += 1;
|
|
}
|
|
let integer_digits = index - integer_start;
|
|
|
|
let mut fractional_digits = 0;
|
|
if bytes.get(index) == Some(&b'.') {
|
|
index += 1;
|
|
let fractional_start = index;
|
|
while index < bytes.len() && bytes[index].is_ascii_digit() {
|
|
index += 1;
|
|
}
|
|
fractional_digits = index - fractional_start;
|
|
}
|
|
|
|
if integer_digits == 0 && fractional_digits == 0 {
|
|
return None;
|
|
}
|
|
|
|
if matches!(bytes.get(index), Some(b'e') | Some(b'E')) {
|
|
index += 1;
|
|
if matches!(bytes.get(index), Some(b'+') | Some(b'-')) {
|
|
index += 1;
|
|
}
|
|
let exponent_start = index;
|
|
while index < bytes.len() && bytes[index].is_ascii_digit() {
|
|
index += 1;
|
|
}
|
|
if index == exponent_start {
|
|
return None;
|
|
}
|
|
}
|
|
|
|
(index == bytes.len()).then_some(value)
|
|
}
|
|
|
|
pub(crate) fn format_binary_literal_for_database(
|
|
database_type: &DatabaseType,
|
|
bytes: &[u8],
|
|
) -> String {
|
|
let hex = hex::encode(bytes);
|
|
match database_type {
|
|
DatabaseType::PostgreSQL => format!("decode('{hex}', 'hex')"),
|
|
DatabaseType::MSSQL => format!("0x{hex}"),
|
|
DatabaseType::Oracle => format!("HEXTORAW('{hex}')"),
|
|
DatabaseType::DuckDB => format!("from_hex('{hex}')"),
|
|
DatabaseType::ClickHouse => format!("unhex('{hex}')"),
|
|
// TDengine 与 MySQL 同臂处理(X'..' 十六进制字面量)。
|
|
DatabaseType::MySQL
|
|
| DatabaseType::SQLite
|
|
| DatabaseType::TDengine
|
|
| DatabaseType::External { .. } => {
|
|
format!("X'{hex}'")
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn format_table_value_for_database(
|
|
database_type: &DatabaseType,
|
|
value: &TableCellValue,
|
|
column: Option<&ColumnInfo>,
|
|
) -> String {
|
|
match value {
|
|
TableCellValue::Null => "NULL".to_string(),
|
|
TableCellValue::Binary(bytes) => format_binary_literal_for_database(database_type, bytes),
|
|
TableCellValue::Text(value) => {
|
|
format_special_table_value_for_database(database_type, value, column)
|
|
.unwrap_or_else(|| quote_string(value))
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn format_query_text_value<P>(
|
|
plugin: &P,
|
|
value: Option<&str>,
|
|
meta: Option<&QueryColumnMeta>,
|
|
) -> String
|
|
where
|
|
P: DatabasePlugin + ?Sized,
|
|
{
|
|
let Some(value) = value else {
|
|
return "NULL".to_string();
|
|
};
|
|
let column = meta.map(column_info_from_query_meta);
|
|
plugin.format_table_change_value(&TableCellValue::Text(value.to_string()), column.as_ref())
|
|
}
|
|
|
|
fn column_info_from_query_meta(meta: &QueryColumnMeta) -> ColumnInfo {
|
|
ColumnInfo {
|
|
name: meta.name.clone(),
|
|
data_type: meta.db_type.clone(),
|
|
is_nullable: meta.nullable,
|
|
is_primary_key: false,
|
|
default_value: None,
|
|
comment: None,
|
|
charset: None,
|
|
collation: None,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn format_special_table_value_for_database(
|
|
database_type: &DatabaseType,
|
|
value: &str,
|
|
column: Option<&ColumnInfo>,
|
|
) -> Option<String> {
|
|
let column = column?;
|
|
crate::sql_literal_values::format_special_table_value(database_type, value, &column.data_type)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "sql_literal_tests.rs"]
|
|
mod tests;
|