use oracle::Connection as OracleConn;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Instant;
use sz_orm_core::dialect::{get_dialect, ColumnDef};
use sz_orm_core::DbType;
use sz_orm_core::Value;
const ORACLE_USER_DEFAULT: &str = "sz_orm_test";
const ORACLE_PASSWORD_DEFAULT: &str = "SzOrmTest2026";
const ORACLE_CONNECT_STRING_DEFAULT: &str = "127.0.0.1:1521/freepdb1.FALSE";
fn oracle_user() -> String {
std::env::var("SZ_ORM_ORACLE_USER").unwrap_or_else(|_| ORACLE_USER_DEFAULT.to_string())
}
fn oracle_password() -> String {
std::env::var("SZ_ORM_ORACLE_PASSWORD").unwrap_or_else(|_| ORACLE_PASSWORD_DEFAULT.to_string())
}
fn oracle_connect_string() -> String {
std::env::var("SZ_ORM_ORACLE_CONNECT_STRING")
.unwrap_or_else(|_| ORACLE_CONNECT_STRING_DEFAULT.to_string())
}
fn open_conn() -> OracleConn {
OracleConn::connect(oracle_user(), oracle_password(), oracle_connect_string())
.expect("oracle connect failed - is Oracle 23ai running on 127.0.0.1:1521?")
}
static TABLE_COUNTER: AtomicU64 = AtomicU64::new(0);
fn unique_table(prefix: &str) -> String {
let pid = std::process::id();
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
let counter = TABLE_COUNTER.fetch_add(1, Ordering::Relaxed);
format!(
"t_{}_{}_{}",
prefix,
pid % 1000,
(nanos % 100000) as u64 * 1000 + counter
)
}
fn create_test_table(conn: &OracleConn, table: &str) {
let dialect = get_dialect(DbType::Oracle).expect("oracle dialect");
let columns = vec![
ColumnDef {
name: "id".to_string(),
sql_type: "NUMBER".to_string(),
nullable: false,
default: None,
auto_increment: true,
primary_key: true,
},
ColumnDef {
name: "name".to_string(),
sql_type: "VARCHAR2(255)".to_string(),
nullable: false,
default: None,
auto_increment: false,
primary_key: false,
},
ColumnDef {
name: "value".to_string(),
sql_type: "NUMBER".to_string(),
nullable: true,
default: None,
auto_increment: false,
primary_key: false,
},
ColumnDef {
name: "data".to_string(),
sql_type: "VARCHAR2(255)".to_string(),
nullable: true,
default: None,
auto_increment: false,
primary_key: false,
},
];
let sql = dialect.build_create_table(table, &columns);
conn.execute(&sql, &[]).expect("create table");
}
fn drop_table_if_exists(conn: &OracleConn, table: &str) {
let dialect = get_dialect(DbType::Oracle).expect("oracle dialect");
let sql = dialect.build_drop_table(table, true);
let _ = conn.execute(&sql, &[]);
}
#[test]
fn test_oracle_dialect_quote_and_escape() {
let dialect = get_dialect(DbType::Oracle).expect("oracle dialect");
assert_eq!(dialect.quote("user"), "\"user\"");
assert_eq!(dialect.quote("with\"quote"), "\"with\"\"quote\"");
assert_eq!(dialect.escape_string("it's"), "it''s");
assert!(dialect.supports_returning());
}
#[test]
fn test_oracle_dialect_pagination_syntax() {
let dialect = get_dialect(DbType::Oracle).expect("oracle dialect");
let sql = dialect.build_pagination("SELECT * FROM t", 3, 10);
assert!(sql.contains("OFFSET 20 ROWS"), "sql = {sql}");
assert!(sql.contains("FETCH NEXT 10 ROWS ONLY"), "sql = {sql}");
}
#[test]
fn test_oracle_dialect_type_mapping() {
let dialect = get_dialect(DbType::Oracle).expect("oracle dialect");
assert_eq!(
dialect.auto_increment_keyword(),
"GENERATED BY DEFAULT AS IDENTITY"
);
assert!(dialect.supports_if_exists());
assert!(dialect.supports_if_not_exists());
}
#[test]
fn test_oracle_value_string_escape() {
let v_str = Value::String("O'Brien".to_string());
let dialect = get_dialect(DbType::Oracle).expect("oracle dialect");
let escaped = dialect.escape_string(v_str.as_str().unwrap());
assert_eq!(escaped, "O''Brien");
}
#[test]
#[ignore = "需要 Oracle 23ai 运行于 127.0.0.1:1521(设置 SZ_ORM_ORACLE_* 环境变量覆盖)"]
fn test_oracle_create_insert_select() {
let conn = open_conn();
let table = unique_table("t1");
drop_table_if_exists(&conn, &table);
create_test_table(&conn, &table);
let dialect = get_dialect(DbType::Oracle).unwrap();
let sql = format!(
"INSERT INTO {} ({}, {}, {}) VALUES (:1, :2, :3)",
dialect.quote(&table),
dialect.quote("name"),
dialect.quote("value"),
dialect.quote("data"),
);
conn.execute(&sql, &[&"alice", &100i64, &"data1"])
.expect("insert 1");
conn.execute(&sql, &[&"bob", &200i64, &"data2"])
.expect("insert 2");
conn.execute(&sql, &[&"carol", &300i64, &"data3"])
.expect("insert 3");
conn.commit().expect("commit");
let select_sql = dialect.build_pagination(
&format!(
"SELECT {}, {}, {} FROM {} ORDER BY {}",
dialect.quote("id"),
dialect.quote("name"),
dialect.quote("value"),
dialect.quote(&table),
dialect.quote("id"),
),
1,
10,
);
let rows: Vec<(i64, String, i64)> = conn
.query_as::<(i64, String, i64)>(&select_sql, &[])
.expect("query")
.filter_map(|r| r.ok())
.collect();
assert_eq!(rows.len(), 3);
assert_eq!(rows[0].1, "alice");
assert_eq!(rows[2].1, "carol");
let count_sql = format!("SELECT COUNT(*) FROM {}", dialect.quote(&table));
let count: i64 = conn.query_row_as::<i64>(&count_sql, &[]).expect("count");
assert_eq!(count, 3);
drop_table_if_exists(&conn, &table);
}
#[test]
#[ignore = "需要 Oracle 23ai 运行于 127.0.0.1:1521(设置 SZ_ORM_ORACLE_* 环境变量覆盖)"]
fn test_oracle_bulk_insert_100k() {
let conn = open_conn();
let table = unique_table("tbulk");
drop_table_if_exists(&conn, &table);
create_test_table(&conn, &table);
let dialect = get_dialect(DbType::Oracle).unwrap();
let total: usize = 100_000;
let start = Instant::now();
let sql = format!(
"INSERT INTO {} ({}, {}, {}) VALUES (:1, :2, :3)",
dialect.quote(&table),
dialect.quote("name"),
dialect.quote("value"),
dialect.quote("data"),
);
let mut stmt = conn.statement(&sql).build().expect("build statement");
for i in 0..total {
stmt.execute(&[
&format!("user_{}", i),
&(i as i64),
&format!("data_{}", i % 1000),
])
.expect("insert");
}
conn.commit().expect("commit");
let elapsed = start.elapsed();
println!(
"oracle bulk insert {} rows in {:?} ({:.0} rows/s)",
total,
elapsed,
total as f64 / elapsed.as_secs_f64()
);
let count_sql = format!("SELECT COUNT(*) FROM {}", dialect.quote(&table));
let count: i64 = conn.query_row_as::<i64>(&count_sql, &[]).expect("count");
assert_eq!(count as usize, total);
let last_sql = format!(
"SELECT {} FROM (SELECT {} FROM {} ORDER BY {} DESC) WHERE ROWNUM = 1",
dialect.quote("name"),
dialect.quote("name"),
dialect.quote(&table),
dialect.quote("id"),
);
let last_name: String = conn
.query_row_as::<String>(&last_sql, &[])
.expect("last name");
assert_eq!(last_name, format!("user_{}", total - 1));
drop_table_if_exists(&conn, &table);
}
#[test]
#[ignore = "需要 Oracle 23ai 运行于 127.0.0.1:1521(设置 SZ_ORM_ORACLE_* 环境变量覆盖)"]
fn test_oracle_transaction_rollback() {
let conn = open_conn();
let table = unique_table("ttx");
drop_table_if_exists(&conn, &table);
create_test_table(&conn, &table);
let dialect = get_dialect(DbType::Oracle).unwrap();
let sql = format!(
"INSERT INTO {} ({}, {}, {}) VALUES (:1, :2, :3)",
dialect.quote(&table),
dialect.quote("name"),
dialect.quote("value"),
dialect.quote("data"),
);
conn.execute(&sql, &[&"alice", &100i64, &"data1"])
.expect("insert");
conn.commit().expect("commit 1");
conn.execute(&sql, &[&"bob", &200i64, &"data2"])
.expect("insert 2");
conn.rollback().expect("rollback");
let count_sql = format!("SELECT COUNT(*) FROM {}", dialect.quote(&table));
let count: i64 = conn.query_row_as::<i64>(&count_sql, &[]).expect("count");
assert_eq!(count, 1);
drop_table_if_exists(&conn, &table);
}
#[test]
#[ignore = "需要 Oracle 23ai 运行于 127.0.0.1:1521(设置 SZ_ORM_ORACLE_* 环境变量覆盖)"]
fn test_oracle_parameterized_insert_query() {
let conn = open_conn();
let table = unique_table("tparam");
drop_table_if_exists(&conn, &table);
create_test_table(&conn, &table);
let dialect = get_dialect(DbType::Oracle).unwrap();
let insert_sql = format!(
"INSERT INTO {} ({}, {}, {}) VALUES (:1, :2, :3)",
dialect.quote(&table),
dialect.quote("name"),
dialect.quote("value"),
dialect.quote("data"),
);
conn.execute(&insert_sql, &[&"alice", &100i64, &"data1"])
.expect("insert 1");
conn.execute(&insert_sql, &[&"bob", &200i64, &"data2"])
.expect("insert 2");
conn.execute(&insert_sql, &[&"carol", &300i64, &"data3"])
.expect("insert 3");
conn.commit().expect("commit");
let select_sql = format!(
"SELECT {}, {} FROM {} WHERE {} > :1 ORDER BY {}",
dialect.quote("name"),
dialect.quote("value"),
dialect.quote(&table),
dialect.quote("value"),
dialect.quote("value"),
);
let rows: Vec<(String, i64)> = conn
.query_as::<(String, i64)>(&select_sql, &[&150i64])
.expect("query")
.filter_map(|r| r.ok())
.collect();
assert_eq!(rows.len(), 2);
assert_eq!(rows[0].0, "bob");
assert_eq!(rows[0].1, 200);
assert_eq!(rows[1].0, "carol");
assert_eq!(rows[1].1, 300);
drop_table_if_exists(&conn, &table);
}
#[test]
fn test_oracle_insert_or_ignore_dialect_sql() {
let dialect = get_dialect(DbType::Oracle).expect("oracle dialect");
let sql = dialect.build_insert_or_ignore_prefix("my_table");
assert_eq!(
sql, "INSERT OR IGNORE INTO \"my_table\"",
"OracleDialect 沿用 Dialect 默认实现生成 INSERT OR IGNORE INTO;\
Oracle 不原生支持此语法,真实执行需用 MERGE INTO 替代"
);
}
#[test]
#[ignore = "需要 Oracle 23ai 运行于 127.0.0.1:1521(设置 SZ_ORM_ORACLE_* 环境变量覆盖)"]
fn test_oracle_merge_into_ignore_semantics() {
let conn = open_conn();
let table = unique_table("tmerge");
drop_table_if_exists(&conn, &table);
let dialect = get_dialect(DbType::Oracle).unwrap();
let create_sql = format!(
"CREATE TABLE {} ({} NUMBER PRIMARY KEY, {} VARCHAR2(100))",
dialect.quote(&table),
dialect.quote("id"),
dialect.quote("name"),
);
conn.execute(&create_sql, &[]).expect("create table");
conn.execute(
&format!(
"INSERT INTO {} ({}, {}) VALUES (:1, :2)",
dialect.quote(&table),
dialect.quote("id"),
dialect.quote("name"),
),
&[&1i64, &"original"],
)
.expect("insert original");
conn.commit().expect("commit");
let merge_sql = format!(
"MERGE INTO {} t USING (SELECT :1 AS {}, :2 AS {} FROM dual) s \
ON (t.{} = s.{}) \
WHEN NOT MATCHED THEN INSERT ({}, {}) VALUES (s.{}, s.{})",
dialect.quote(&table),
dialect.quote("id"),
dialect.quote("name"),
dialect.quote("id"),
dialect.quote("id"),
dialect.quote("id"),
dialect.quote("name"),
dialect.quote("id"),
dialect.quote("name"),
);
conn.execute(&merge_sql, &[&1i64, &"ignored"])
.expect("merge with existing id");
conn.execute(&merge_sql, &[&2i64, &"new"])
.expect("merge with new id");
conn.commit().expect("commit");
let count_sql = format!("SELECT COUNT(*) FROM {}", dialect.quote(&table));
let count: i64 = conn.query_row_as::<i64>(&count_sql, &[]).expect("count");
assert_eq!(count, 2, "应有 2 行:original(id=1) + new(id=2)");
let name_sql = format!(
"SELECT {} FROM {} WHERE {} = :1",
dialect.quote("name"),
dialect.quote(&table),
dialect.quote("id"),
);
let name1: String = conn
.query_row_as::<String>(&name_sql, &[&1i64])
.expect("name 1");
assert_eq!(name1, "original", "id=1 应保持 original,未被覆盖");
let name2: String = conn
.query_row_as::<String>(&name_sql, &[&2i64])
.expect("name 2");
assert_eq!(name2, "new");
drop_table_if_exists(&conn, &table);
}
#[test]
#[ignore = "需要 Oracle 23ai 运行于 127.0.0.1:1521(设置 SZ_ORM_ORACLE_* 环境变量覆盖)"]
fn test_oracle_transaction_commit() {
let conn = open_conn();
let table = unique_table("tcommit");
drop_table_if_exists(&conn, &table);
create_test_table(&conn, &table);
let dialect = get_dialect(DbType::Oracle).unwrap();
let insert_sql = format!(
"INSERT INTO {} ({}, {}, {}) VALUES (:1, :2, :3)",
dialect.quote(&table),
dialect.quote("name"),
dialect.quote("value"),
dialect.quote("data"),
);
conn.execute(&insert_sql, &[&"alice", &100i64, &"data1"])
.expect("insert 1");
conn.execute(&insert_sql, &[&"bob", &200i64, &"data2"])
.expect("insert 2");
conn.commit().expect("commit");
let count_sql = format!("SELECT COUNT(*) FROM {}", dialect.quote(&table));
let count: i64 = conn.query_row_as::<i64>(&count_sql, &[]).expect("count");
assert_eq!(count, 2, "commit 后应可见 2 行");
conn.execute(&insert_sql, &[&"carol", &300i64, &"data3"])
.expect("insert 3");
conn.rollback().expect("rollback");
let count2: i64 = conn.query_row_as::<i64>(&count_sql, &[]).expect("count 2");
assert_eq!(count2, 2, "rollback 后应仍为 2 行");
drop_table_if_exists(&conn, &table);
}
#[test]
#[ignore = "需要 Oracle 23ai 运行于 127.0.0.1:1521(设置 SZ_ORM_ORACLE_* 环境变量覆盖)"]
fn test_oracle_pagination_executes() {
let conn = open_conn();
let table = unique_table("tpage");
drop_table_if_exists(&conn, &table);
create_test_table(&conn, &table);
let dialect = get_dialect(DbType::Oracle).unwrap();
let insert_sql = format!(
"INSERT INTO {} ({}, {}, {}) VALUES (:1, :2, :3)",
dialect.quote(&table),
dialect.quote("name"),
dialect.quote("value"),
dialect.quote("data"),
);
let mut stmt = conn.statement(&insert_sql).build().expect("build stmt");
for i in 0..25i64 {
stmt.execute(&[&format!("user_{}", i), &i, &format!("d{}", i % 5)])
.expect("insert");
}
conn.commit().expect("commit");
let base_select = format!(
"SELECT {}, {} FROM {} ORDER BY {}",
dialect.quote("name"),
dialect.quote("value"),
dialect.quote(&table),
dialect.quote("value"),
);
let page3_sql = dialect.build_pagination(&base_select, 3, 10);
let rows: Vec<(String, i64)> = conn
.query_as::<(String, i64)>(&page3_sql, &[])
.expect("query page 3")
.filter_map(|r| r.ok())
.collect();
assert_eq!(rows.len(), 5, "25 条分页 10/页,第 3 页应 5 条");
assert_eq!(rows[0].0, "user_20");
assert_eq!(rows[0].1, 20);
assert_eq!(rows[4].0, "user_24");
assert_eq!(rows[4].1, 24);
let page1_sql = dialect.build_pagination(&base_select, 1, 10);
let rows1: Vec<(String, i64)> = conn
.query_as::<(String, i64)>(&page1_sql, &[])
.expect("query page 1")
.filter_map(|r| r.ok())
.collect();
assert_eq!(rows1.len(), 10);
assert_eq!(rows1[0].0, "user_0");
let page2_sql = dialect.build_pagination(&base_select, 2, 10);
let rows2: Vec<(String, i64)> = conn
.query_as::<(String, i64)>(&page2_sql, &[])
.expect("query page 2")
.filter_map(|r| r.ok())
.collect();
assert_eq!(rows2.len(), 10);
assert_eq!(rows2[0].0, "user_10");
drop_table_if_exists(&conn, &table);
}
#[test]
#[ignore = "需要 Oracle 23ai 运行于 127.0.0.1:1521(设置 SZ_ORM_ORACLE_* 环境变量覆盖)"]
fn test_oracle_alter_table_executes() {
use sz_orm_core::dialect::TableChange;
let conn = open_conn();
let table = unique_table("talter");
drop_table_if_exists(&conn, &table);
create_test_table(&conn, &table);
let dialect = get_dialect(DbType::Oracle).unwrap();
let insert_sql = format!(
"INSERT INTO {} ({}, {}, {}) VALUES (:1, :2, :3)",
dialect.quote(&table),
dialect.quote("name"),
dialect.quote("value"),
dialect.quote("data"),
);
conn.execute(&insert_sql, &[&"alice", &100i64, &"data1"])
.expect("insert");
conn.commit().expect("commit");
let add_col = TableChange::AddColumn(ColumnDef {
name: "email".to_string(),
sql_type: "VARCHAR2(255)".to_string(),
nullable: true,
default: None,
auto_increment: false,
primary_key: false,
});
let alter_sql = dialect.build_alter_table(&table, &[add_col]);
conn.execute(&alter_sql, &[]).expect("alter add column");
let update_sql = format!(
"UPDATE {} SET {} = :1 WHERE {} = :2",
dialect.quote(&table),
dialect.quote("email"),
dialect.quote("name"),
);
conn.execute(&update_sql, &[&"alice@example.com", &"alice"])
.expect("update email");
conn.commit().expect("commit");
let select_sql = format!(
"SELECT {} FROM {} WHERE {} = :1",
dialect.quote("email"),
dialect.quote(&table),
dialect.quote("name"),
);
let email: String = conn
.query_row_as::<String>(&select_sql, &[&"alice"])
.expect("select email");
assert_eq!(email, "alice@example.com");
let drop_col = TableChange::DropColumn("email".to_string());
let drop_sql = dialect.build_alter_table(&table, &[drop_col]);
conn.execute(&drop_sql, &[]).expect("alter drop column");
let check_sql = format!(
"SELECT COUNT(*) FROM {} WHERE {} = :1",
dialect.quote(&table),
dialect.quote("name"),
);
let cnt: i64 = conn
.query_row_as::<i64>(&check_sql, &[&"alice"])
.expect("check");
assert_eq!(cnt, 1, "DROP COLUMN 后行应仍在");
drop_table_if_exists(&conn, &table);
}
#[test]
#[ignore = "需要 Oracle 23ai 运行于 127.0.0.1:1521(设置 SZ_ORM_ORACLE_* 环境变量覆盖)"]
fn test_oracle_escape_executes() {
let conn = open_conn();
let table = unique_table("tescape");
drop_table_if_exists(&conn, &table);
create_test_table(&conn, &table);
let dialect = get_dialect(DbType::Oracle).unwrap();
let raw = "O'Brien";
let escaped = dialect.escape_string(raw);
assert_eq!(escaped, "O''Brien");
let insert_sql = format!(
"INSERT INTO {} ({}, {}, {}) VALUES (:1, :2, :3)",
dialect.quote(&table),
dialect.quote("name"),
dialect.quote("value"),
dialect.quote("data"),
);
conn.execute(&insert_sql, &[&raw, &1i64, &"single quote"])
.expect("insert with quote");
conn.commit().expect("commit");
let select_sql = format!(
"SELECT {} FROM {} WHERE {} = :1",
dialect.quote("name"),
dialect.quote(&table),
dialect.quote("value"),
);
let name: String = conn
.query_row_as::<String>(&select_sql, &[&1i64])
.expect("select name");
assert_eq!(name, "O'Brien", "参数化绑定应原样存取,无需手动转义");
let count_sql = format!(
"SELECT COUNT(*) FROM {} WHERE {} = '{}'",
dialect.quote(&table),
dialect.quote("name"),
escaped,
);
let count: i64 = conn.query_row_as::<i64>(&count_sql, &[]).expect("count");
assert_eq!(count, 1, "escape_string 生成的字面量应可正确查询");
drop_table_if_exists(&conn, &table);
}
#[test]
#[ignore = "需要 Oracle 23ai 运行于 127.0.0.1:1521(设置 SZ_ORM_ORACLE_* 环境变量覆盖)"]
fn test_oracle_drop_table_executes() {
let conn = open_conn();
let table = unique_table("tdrop");
drop_table_if_exists(&conn, &table);
create_test_table(&conn, &table);
let dialect = get_dialect(DbType::Oracle).unwrap();
let insert_sql = format!(
"INSERT INTO {} ({}, {}, {}) VALUES (:1, :2, :3)",
dialect.quote(&table),
dialect.quote("name"),
dialect.quote("value"),
dialect.quote("data"),
);
conn.execute(&insert_sql, &[&"alice", &1i64, &"d1"])
.expect("insert");
conn.commit().expect("commit");
let count_sql = format!("SELECT COUNT(*) FROM {}", dialect.quote(&table));
let count: i64 = conn.query_row_as::<i64>(&count_sql, &[]).expect("count");
assert_eq!(count, 1);
drop_table_if_exists(&conn, &table);
let check_exist_sql = "SELECT COUNT(*) FROM user_tables WHERE table_name = :1";
let upper_table = table.to_uppercase();
let remain: i64 = conn
.query_row_as::<i64>(check_exist_sql, &[&upper_table])
.expect("check exist");
assert_eq!(remain, 0, "DROP TABLE 后表应不存在");
let drop_again_sql = dialect.build_drop_table(&table, true);
let result = conn.execute(&drop_again_sql, &[]);
assert!(result.is_ok(), "DROP TABLE IF EXISTS 对不存在的表应不报错");
}