use remdb::config::WALConfig;
use remdb::platform::{FileHandle, FileMode, FileResult, Platform, SeekWhence};
use remdb::*;
use serial_test::serial;
struct TestPlatform;
impl Platform for TestPlatform {
fn get_timestamp(&self) -> u64 {
0
}
fn get_timestamp_us(&self) -> u64 {
0
}
fn spin_lock(&self, lock: &mut u32) {
unsafe {
while core::sync::atomic::AtomicU32::from_ptr(lock as *mut u32)
.compare_exchange(
0,
1,
core::sync::atomic::Ordering::Acquire,
core::sync::atomic::Ordering::Relaxed,
)
.is_err()
{
core::hint::spin_loop();
}
}
}
fn spin_unlock(&self, lock: &mut u32) {
unsafe {
core::sync::atomic::AtomicU32::from_ptr(lock as *mut u32)
.store(0, core::sync::atomic::Ordering::Release);
}
}
fn compiler_barrier(&self) {
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
}
fn full_memory_barrier(&self) {
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
}
fn memcpy(&self, dest: *mut u8, src: *const u8, size: usize) {
unsafe {
core::ptr::copy_nonoverlapping(src, dest, size);
}
}
fn memset(&self, dest: *mut u8, value: u8, size: usize) {
unsafe {
core::ptr::write_bytes(dest, value, size);
}
}
fn delay_ms(&self, _ms: u32) {
}
fn delay_us(&self, _us: u32) {
}
fn file_open(&self, _path: &str, _mode: FileMode) -> FileResult<FileHandle> {
Ok(core::ptr::null())
}
fn file_close(&self, _handle: FileHandle) -> FileResult<()> {
Ok(())
}
fn file_write(
&self,
_handle: FileHandle,
_buffer: *const u8,
size: usize,
) -> FileResult<usize> {
Ok(size)
}
fn file_read(&self, _handle: FileHandle, _buffer: *mut u8, _size: usize) -> FileResult<usize> {
Ok(0)
}
fn file_seek(&self, _handle: FileHandle, _offset: i64, _whence: SeekWhence) -> FileResult<u64> {
Ok(0)
}
fn file_remove(&self, _path: &str) -> FileResult<()> {
Ok(())
}
fn file_size(&self, _path: &str) -> FileResult<usize> {
Ok(0)
}
fn crc32(&self, _data: *const u8, _size: usize) -> u32 {
0
}
}
static TEST_PLATFORM: TestPlatform = TestPlatform;
static DEFAULT_ALLOCATOR: config::DefaultMemoryAllocator = config::DefaultMemoryAllocator;
static TEST_CONFIG: std::sync::LazyLock<config::DbConfig> = std::sync::LazyLock::new(|| {
config::DbConfig {
tables: vec![],
total_memory: 1024 * 1024, low_power_mode_supported: false,
low_power_max_records: None,
default_max_records: 100, memory_allocator: &DEFAULT_ALLOCATOR,
wal_config: WALConfig {
log_path: "./wal",
log_mode: config::LogMode::Sync,
checkpoint_interval_ms: 60000,
log_file_size_limit: 16 * 1024 * 1024,
log_prealloc_size: 1 * 1024 * 1024,
log_segment_size: 16 * 1024 * 1024,
max_consecutive_invalid: 100,
retained_checkpoints: 2,
skip_threshold: 1000,
skip_block_size: 1024 * 1024,
max_skip_attempts: 3,
compression_type: remdb::config::WALCompressionType::None,
compression_level: 3,
},
time_series_defaults: config::TimeSeriesConfig::DEFAULT,
#[cfg(feature = "pubsub")]
pubsub_config: None,
#[cfg(feature = "ha")]
ha_config: Some(config::HAConfig {
node_id: 1,
ha_role: remdb::ha::HARole::Auto,
replication_mode: remdb::ha::ReplicationMode::Async,
heartbeat_interval_ms: 1000,
failure_detection_ms: 3000,
sync_timeout_ms: 2000,
master_address: None,
master_port: None,
replication_port: 5556,
}),
model_worker_config: Default::default(),
}
});
static mut DB_MEMORY: [u8; 4 * 1024 * 1024] = [0u8; 4 * 1024 * 1024];
fn setup_test_env() {
platform::init_platform(&TEST_PLATFORM);
unsafe {
core::ptr::write_bytes(DB_MEMORY.as_mut_ptr(), 0, DB_MEMORY.len());
}
let result =
memory::allocator::init_global_allocator(unsafe { DB_MEMORY.as_mut_ptr() }, unsafe {
DB_MEMORY.len()
});
assert!(
result.is_ok(),
"Failed to initialize global allocator: {:?}",
result.err()
);
reset_global_db();
}
#[test]
#[serial]
fn test_alter_table_add_column() {
setup_test_env();
let mut db = RemDb::new(&*TEST_CONFIG);
let result = db.create_table(
"users",
&[
("id", DataType::UInt32, 4, None, None),
("name", DataType::VarChar, 32, None, None),
("age", DataType::UInt8, 1, None, None),
],
Some(vec![0]), );
assert!(result.is_ok(), "Failed to create table: {:?}", result.err());
let result = db.sql_query("ALTER TABLE users ADD COLUMN active BOOL");
assert!(result.is_ok(), "Failed to add column: {:?}", result.err());
}
#[test]
#[serial]
fn test_alter_table_drop_column() {
setup_test_env();
let mut db = RemDb::new(&*TEST_CONFIG);
let result = db.create_table(
"users",
&[
("id", DataType::UInt32, 4, None, None),
("name", DataType::VarChar, 32, None, None),
("age", DataType::UInt8, 1, None, None),
("active", DataType::Bool, 1, None, None),
],
Some(vec![0]), );
assert!(result.is_ok(), "Failed to create table: {:?}", result.err());
let result = db.sql_query("ALTER TABLE users DROP COLUMN active");
assert!(result.is_ok(), "Failed to drop column: {:?}", result.err());
}
#[test]
#[serial]
fn test_alter_table_modify_column() {
setup_test_env();
let mut db = RemDb::new(&*TEST_CONFIG);
let result = db.create_table(
"users",
&[
("id", DataType::UInt32, 4, None, None),
("name", DataType::VarChar, 32, None, None),
("age", DataType::UInt8, 1, None, None),
],
Some(vec![0]), );
assert!(result.is_ok(), "Failed to create table: {:?}", result.err());
let result = db.sql_query("ALTER TABLE users MODIFY COLUMN age UInt16");
assert!(
result.is_ok(),
"Failed to modify column: {:?}",
result.err()
);
}
#[test]
#[serial]
fn test_alter_table_rename_column() {
setup_test_env();
let mut db = RemDb::new(&*TEST_CONFIG);
let result = db.create_table(
"users",
&[
("id", DataType::UInt32, 4, None, None),
("name", DataType::VarChar, 32, None, None),
("age", DataType::UInt8, 1, None, None),
],
Some(vec![0]), );
assert!(result.is_ok(), "Failed to create table: {:?}", result.err());
let result = db.sql_query("ALTER TABLE users RENAME COLUMN age TO user_age");
assert!(
result.is_ok(),
"Failed to rename column: {:?}",
result.err()
);
}
#[test]
#[serial]
fn test_alter_table_data_migration() {
setup_test_env();
let mut db = RemDb::new(&*TEST_CONFIG);
let result = db.create_table(
"test_table",
&[
("id", DataType::UInt32, 4, None, None),
("name", DataType::VarChar, 32, None, None),
("age", DataType::UInt8, 1, None, None),
],
Some(vec![0]), );
assert!(result.is_ok(), "Failed to create table: {:?}", result.err());
let result = db.sql_query("INSERT INTO test_table (id, name, age) VALUES (1, 'test1', 20)");
assert!(
result.is_ok(),
"Failed to insert record1: {:?}",
result.err()
);
let result = db.sql_query("INSERT INTO test_table (id, name, age) VALUES (2, 'test2', 30)");
assert!(
result.is_ok(),
"Failed to insert record2: {:?}",
result.err()
);
let result = db.sql_query("ALTER TABLE test_table ADD COLUMN active BOOL");
assert!(result.is_ok(), "Failed to add column: {:?}", result.err());
let result = db.sql_query("SELECT id FROM test_table WHERE id = 1");
assert!(
result.is_ok(),
"Failed to select record 1: {:?}",
result.err()
);
let result_set = result.unwrap();
assert_eq!(
result_set.rows.len(),
1,
"Expected 1 row for id=1, got {}",
result_set.rows.len()
);
let result = db.sql_query("SELECT id FROM test_table WHERE id = 2");
assert!(
result.is_ok(),
"Failed to select record 2: {:?}",
result.err()
);
let result_set = result.unwrap();
assert_eq!(
result_set.rows.len(),
1,
"Expected 1 row for id=2, got {}",
result_set.rows.len()
);
let result = db.sql_query("SELECT COUNT(*) FROM test_table");
assert!(
result.is_ok(),
"Failed to count records: {:?}",
result.err()
);
let result_set = result.unwrap();
assert_eq!(
result_set.rows.len(),
1,
"Expected 1 row for count, got {}",
result_set.rows.len()
);
}