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);
}