feat(db): 支持 VictoriaMetrics 数据库

* feat(victoriametrics): support native MetricsQL queries

* fix(victoriametrics): 完善指标浏览和结果导出

* fix(victoriametrics): 修复指标对象通用操作入口

* fix(victoriametrics): 修正指标注释和截断提示

* fix(i18n): 补齐 VictoriaMetrics 连接配置翻译

---------

Co-authored-by: zipg <4047349+zipg@users.noreply.github.com>
This commit is contained in:
zipgandzipg authored and GitHub committed 2026-08-05 23:58:11 +08:00
1 parent 930aa48c20
commit 2ede64f1cb
64 files changed
+1293 -64

No files matched your search

@@ -674,7 +674,9 @@ pub fn profile_for(db_type: DatabaseType) -> DdlDialectProfile {
// Non-tabular / not applicable for relational CREATE TABLE
Redis | MongoDb | Elasticsearch | Easysearch | Qdrant | Milvus | Weaviate | ChromaDb | Neo4j | Cassandra
| Etcd | ZooKeeper | Nacos | InfluxDb | MessageQueue | Mqtt | Hbase => conservative_ansi(db_type),
| Etcd | ZooKeeper | Nacos | InfluxDb | VictoriaMetrics | MessageQueue | Mqtt | Hbase => {
conservative_ansi(db_type)
}
}
}
@@ -13,6 +13,9 @@ use super::types::{
};
pub fn build_count_table_sql(database_type: Option<DatabaseType>, schema: Option<&str>, table_name: &str) -> String {
if database_type == Some(DatabaseType::VictoriaMetrics) {
return format!("count({})", victoriametrics_metric_selector(table_name));
}
format!("SELECT COUNT(*) AS row_count FROM {}", qualified_table_name(database_type, schema, table_name))
}
@@ -22,6 +25,9 @@ pub fn build_table_data_select_sql(options: TableDataSelectSqlOptions) -> String
if database_type == Some(DatabaseType::Neo4j) {
return build_neo4j_table_select_sql(&options, limit);
}
if database_type == Some(DatabaseType::VictoriaMetrics) {
return format!("{}[1h]", victoriametrics_metric_selector(&options.table_name));
}
// Doris / StarRocks multi-catalog: prefix the catalog for external-catalog tables.
let table = if uses_connection_identifier_quote(database_type, options.identifier_quote.as_deref()) {
@@ -214,6 +220,9 @@ fn is_view_table_type(table_type: Option<&str>) -> bool {
pub fn build_table_select_sql(options: TableSelectSqlOptions<'_>) -> String {
let database_type = options.database_type;
if database_type == Some(DatabaseType::VictoriaMetrics) {
return format!("{}[1h]", victoriametrics_metric_selector(options.table_name));
}
let table = qualified_table_name(database_type, options.schema, options.table_name);
let select_columns = quoted_table_columns_or_star(database_type, options.columns);
let order_by = if options.order_columns.is_empty() {
@@ -257,6 +266,11 @@ pub fn build_table_select_sql(options: TableSelectSqlOptions<'_>) -> String {
}
}
fn victoriametrics_metric_selector(metric_name: &str) -> String {
let escaped = metric_name.replace('\\', "\\\\").replace('"', "\\\"").replace('\n', "\\n");
format!(r#"{{__name__="{escaped}"}}"#)
}
fn informix_row_limit_clause(limit: usize, offset: usize) -> String {
if offset > 0 {
format!("SKIP {offset} FIRST {limit}")
@@ -555,4 +569,16 @@ mod tests {
assert!(!sql.contains("iceberg_catalog"), "sql was: {sql}");
assert!(sql.contains("orders"), "sql was: {sql}");
}
#[test]
fn victoriametrics_builds_metric_queries_without_sql_identifiers() {
assert_eq!(
build_table_data_select_sql(opts(DatabaseType::VictoriaMetrics, None, None, "rack_temperature")),
r#"{__name__="rack_temperature"}[1h]"#
);
assert_eq!(
build_count_table_sql(Some(DatabaseType::VictoriaMetrics), None, "rack\\\"temperature"),
r#"count({__name__="rack\\\"temperature"})"#
);
}
}