mirror of
https://github.com/feigeCode/navop.git
synced 2026-09-22 16:01:30 +00:00
PG 的序列目录从来是空的:`list_sequences` 查 `information_schema.sequences` 时用的 是 `min_value` / `max_value`,而该视图的真实列名是 `minimum_value` / `maximum_value`。PostgreSQL 直接回 `ERROR: column "min_value" does not exist`, 调用侧 `build_database_or_schema_children` 又用 `.unwrap_or_default()` 把错误吞掉, 于是用户只看到一个空目录,没有任何提示。 - 修正列名(`minimum_value::bigint` / `maximum_value::bigint`);NULL 或不合法文本 仍按「取不到就丢这一个值」处理,不会让整张列表失败。 - 对象面板补 schema:`SequencesFolder` 分支原来调 `list_sequences_view`,PG 实现里 schema 固定成 public,非 public schema 下永远查的是 public。新增 `list_sequences_view_in_schema`(默认退回原实现,MySQL 等驱动行为不变),PG 覆盖 并传节点 schema;同时把对象视图的列定义与行构造抽成 `sequences_object_view`, 避免两个入口重复。 - 左侧树的序列目录本来就传了 schema,只有对象面板这条漏了。 验证: - 新增真实库用例 `crates/db/tests/real_databases/postgres/sequences.rs`:在非 public schema 下建 plain / 显式 MINVALUE·MAXVALUE / NO MINVALUE·NO MAXVALUE / smallint 四种序列,断言取值(含 `NO MAXVALUE` 的 i64::MAX、smallint 的 32767)、对象面板 按 schema 取数、不带 schema 的旧入口仍只查 public,以及树的序列目录内容与 元数据。`ONETCLI_TEST_POSTGRES_PASSWORD=... cargo test -p db --test real_postgres` → 6 passed。 - 新增单测 `test_postgres_sequences_use_information_schema_minimum_columns`。变异 验证:把列名改回 `min_value` / `max_value`,单测与真实库用例都变红,改回即绿。 - `cargo test -p db -p db_view` → 1316 passed / 721 passed(各 1 ignored); `cargo check --workspace --all-targets` 无 error;新增行 clippy 无告警;rustfmt 干净。
173 lines
5.9 KiB
Rust
173 lines
5.9 KiB
Rust
use db::connection::DbConnection;
|
|
use db::plugin::DatabasePlugin;
|
|
use db::postgresql::PostgresPlugin;
|
|
use db::types::DbNodeType;
|
|
|
|
use crate::real_databases::common::env::{optional_database, postgres_config, skip_database};
|
|
use crate::real_databases::postgres::core_flow::{
|
|
drop_schema, execute, reset_schema, unique_schema,
|
|
};
|
|
|
|
/// 覆盖三种取值形态:显式 min/max、NO MINVALUE/NO MAXVALUE、以及 smallint 序列的上界。
|
|
const FIXTURE_SQL: &str = r#"
|
|
CREATE SEQUENCE plain;
|
|
CREATE SEQUENCE big AS bigint START 5 INCREMENT 3 MINVALUE 2 MAXVALUE 999 CYCLE;
|
|
CREATE SEQUENCE no_minmax NO MINVALUE NO MAXVALUE;
|
|
CREATE SEQUENCE small AS smallint;
|
|
"#;
|
|
|
|
#[tokio::test]
|
|
async fn postgres_real_sequences_list_values_and_schema_scope() {
|
|
let Some(config) = postgres_config() else {
|
|
skip_database(
|
|
"PostgreSQL",
|
|
"ONETCLI_TEST_POSTGRES_PASSWORD (empty string is valid)",
|
|
);
|
|
return;
|
|
};
|
|
let config = optional_database(
|
|
&config,
|
|
&std::env::var("ONETCLI_TEST_POSTGRES_DATABASE").unwrap_or_else(|_| "postgres".to_string()),
|
|
);
|
|
// 非 public schema:序列列表必须按 schema 取,不能固定查 public。
|
|
let schema = unique_schema("seq");
|
|
let plugin = PostgresPlugin::new();
|
|
let mut connection = plugin
|
|
.create_connection(config)
|
|
.await
|
|
.expect("PostgreSQL should connect");
|
|
let conn: &(dyn DbConnection + Send + Sync) = connection.as_ref();
|
|
|
|
reset_schema(&plugin, conn, &schema).await;
|
|
conn.switch_schema(&schema)
|
|
.await
|
|
.expect("switch to the test schema");
|
|
execute(&plugin, conn, FIXTURE_SQL).await;
|
|
|
|
assert_listed_values(&plugin, conn, &schema).await;
|
|
assert_object_view_uses_schema(&plugin, conn, &schema).await;
|
|
assert_tree_folder_lists_sequences(&plugin, conn, &schema).await;
|
|
|
|
drop_schema(&plugin, conn, &schema).await;
|
|
connection.disconnect().await.expect("disconnect");
|
|
}
|
|
|
|
async fn assert_listed_values(
|
|
plugin: &PostgresPlugin,
|
|
connection: &(dyn DbConnection + Send + Sync),
|
|
schema: &str,
|
|
) {
|
|
let sequences = plugin
|
|
.list_sequences(connection, "postgres", Some(schema.to_string()))
|
|
.await
|
|
.expect("sequences should list");
|
|
let names: Vec<&str> = sequences.iter().map(|seq| seq.name.as_str()).collect();
|
|
assert_eq!(vec!["big", "no_minmax", "plain", "small"], names);
|
|
|
|
let sequence = |name: &str| {
|
|
sequences
|
|
.iter()
|
|
.find(|seq| seq.name == name)
|
|
.unwrap_or_else(|| panic!("{name} should be listed, got {names:?}"))
|
|
};
|
|
|
|
let big = sequence("big");
|
|
assert_eq!(Some(5), big.start_value);
|
|
assert_eq!(Some(3), big.increment);
|
|
assert_eq!(Some(2), big.min_value);
|
|
assert_eq!(Some(999), big.max_value);
|
|
|
|
// NO MINVALUE 不代表取不到值:information_schema 会给实际生效的边界。
|
|
let no_minmax = sequence("no_minmax");
|
|
assert_eq!(Some(1), no_minmax.min_value);
|
|
assert_eq!(Some(i64::MAX), no_minmax.max_value);
|
|
|
|
// smallint 序列的上界不是 bigint 上界,说明取的是序列自身定义而非类型默认值。
|
|
assert_eq!(Some(i64::from(i16::MAX)), sequence("small").max_value);
|
|
assert_eq!(Some(1), sequence("plain").min_value);
|
|
}
|
|
|
|
async fn assert_object_view_uses_schema(
|
|
plugin: &PostgresPlugin,
|
|
connection: &(dyn DbConnection + Send + Sync),
|
|
schema: &str,
|
|
) {
|
|
let view = plugin
|
|
.list_sequences_view_in_schema(connection, "postgres", Some(schema.to_string()))
|
|
.await
|
|
.expect("sequence object view should load");
|
|
assert_eq!(DbNodeType::Sequence, view.db_node_type);
|
|
let rows: Vec<&Vec<String>> = view
|
|
.rows
|
|
.iter()
|
|
.filter(|row| row.first().map(String::as_str) == Some("big"))
|
|
.collect();
|
|
assert_eq!(1, rows.len(), "big should appear exactly once in {view:?}");
|
|
assert_eq!(
|
|
vec!["big", "5", "3", "2", "999"],
|
|
rows[0].iter().map(String::as_str).collect::<Vec<_>>()
|
|
);
|
|
|
|
// 不带 schema 的实现必须仍然查 public:非 public schema 的序列不该在这里露头。
|
|
let public_view = plugin
|
|
.list_sequences_view(connection, "postgres")
|
|
.await
|
|
.expect("public sequence object view should load");
|
|
assert!(
|
|
!public_view
|
|
.rows
|
|
.iter()
|
|
.any(|row| row.first().map(String::as_str) == Some("big")),
|
|
"public view must not leak sequences from another schema"
|
|
);
|
|
assert!(
|
|
!public_view
|
|
.rows
|
|
.iter()
|
|
.any(|row| row.first().map(String::as_str) == Some("plain")),
|
|
"public view must not leak sequences from another schema"
|
|
);
|
|
}
|
|
|
|
async fn assert_tree_folder_lists_sequences(
|
|
plugin: &PostgresPlugin,
|
|
connection: &(dyn DbConnection + Send + Sync),
|
|
schema: &str,
|
|
) {
|
|
let schema_node = db::types::DbNode::new(
|
|
format!("conn:{schema}"),
|
|
schema,
|
|
DbNodeType::Schema,
|
|
"conn".to_string(),
|
|
one_core::storage::DatabaseType::PostgreSQL,
|
|
)
|
|
.with_metadata(std::collections::HashMap::from([(
|
|
"database".to_string(),
|
|
"postgres".to_string(),
|
|
)]));
|
|
|
|
let children = plugin
|
|
.build_database_or_schema_children(connection, &schema_node, Some(schema.to_string()))
|
|
.await
|
|
.expect("schema children should build");
|
|
let folder = children
|
|
.iter()
|
|
.find(|node| node.node_type == DbNodeType::SequencesFolder)
|
|
.expect("sequences folder should exist");
|
|
let names: Vec<&str> = folder
|
|
.children
|
|
.iter()
|
|
.map(|node| node.name.as_str())
|
|
.collect();
|
|
assert_eq!(vec!["big", "no_minmax", "plain", "small"], names);
|
|
assert_eq!(
|
|
Some("5"),
|
|
folder
|
|
.children
|
|
.iter()
|
|
.find(|node| node.name == "big")
|
|
.and_then(|node| node.metadata.get("start_value"))
|
|
.map(String::as_str)
|
|
);
|
|
}
|