use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone)]
pub struct DatabaseCompileResult {
pub name: String,
pub library: String,
pub success: bool,
pub files_compiled: usize,
pub files_failed: usize,
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub compile_time_ms: u64,
pub test_results: DatabaseTestResults,
pub storage_type: Option<String>,
}
#[derive(Debug, Clone)]
pub struct DatabaseTestResults {
pub passed: usize,
pub failed: usize,
pub tests: Vec<DatabaseTestCase>,
}
impl Default for DatabaseTestResults {
fn default() -> Self {
Self {
passed: 0,
failed: 0,
tests: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct DatabaseTestCase {
pub name: String,
pub passed: bool,
pub error: Option<String>,
pub duration_ms: u64,
pub operation: Option<String>,
}
impl DatabaseTestCase {
pub fn new(name: &str, passed: bool) -> Self {
Self {
name: name.to_string(),
passed,
error: None,
duration_ms: 0,
operation: None,
}
}
pub fn with_error(mut self, error: &str) -> Self {
self.error = Some(error.to_string());
self
}
pub fn with_operation(mut self, op: &str) -> Self {
self.operation = Some(op.to_string());
self
}
}
pub trait DatabaseLibrary {
fn library_name(&self) -> &str;
fn source_files(&self) -> Vec<String>;
fn include_dirs(&self) -> Vec<String>;
fn defines(&self) -> Vec<(String, Option<String>)>;
fn compiler_flags(&self) -> Vec<String>;
fn linker_flags(&self) -> Vec<String>;
fn storage_type(&self) -> &str;
fn compile(&self) -> DatabaseCompileResult;
fn run_tests(&self) -> DatabaseTestResults;
}
#[derive(Debug, Clone)]
pub struct PostgresProject {
pub version: String,
pub source_dir: String,
pub with_ssl: bool,
pub with_gssapi: bool,
pub with_ldap: bool,
}
impl PostgresProject {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
source_dir: format!("postgresql-{}", version),
with_ssl: true,
with_gssapi: false,
with_ldap: false,
}
}
pub fn libpq_sources(&self) -> Vec<String> {
vec![
"src/interfaces/libpq/fe-auth.c",
"src/interfaces/libpq/fe-auth-scram.c",
"src/interfaces/libpq/fe-connect.c",
"src/interfaces/libpq/fe-exec.c",
"src/interfaces/libpq/fe-lobj.c",
"src/interfaces/libpq/fe-misc.c",
"src/interfaces/libpq/fe-print.c",
"src/interfaces/libpq/fe-protocol3.c",
"src/interfaces/libpq/fe-secure.c",
"src/interfaces/libpq/fe-secure-common.c",
"src/interfaces/libpq/fe-secure-openssl.c",
"src/interfaces/libpq/fe-secure-gssapi.c",
"src/interfaces/libpq/fe-trace.c",
"src/interfaces/libpq/pqexpbuffer.c",
"src/interfaces/libpq/fe-auth-sasl.c",
"src/interfaces/libpq/win32.c",
"src/common/base64.c",
"src/common/config_info.c",
"src/common/controldata_utils.c",
"src/common/cryptohash.c",
"src/common/cryptohash_openssl.c",
"src/common/encnames.c",
"src/common/exec.c",
"src/common/file_perm.c",
"src/common/hashfn.c",
"src/common/hmac.c",
"src/common/hmac_openssl.c",
"src/common/ip.c",
"src/common/jsonapi.c",
"src/common/keywords.c",
"src/common/kwlookup.c",
"src/common/link-canary.c",
"src/common/logging.c",
"src/common/md5.c",
"src/common/md5_common.c",
"src/common/pg_get_line.c",
"src/common/pg_lzcompress.c",
"src/common/pg_prng.c",
"src/common/pgstrcasecmp.c",
"src/common/psprintf.c",
"src/common/relpath.c",
"src/common/restricted_token.c",
"src/common/rmtree.c",
"src/common/saslprep.c",
"src/common/scram-common.c",
"src/common/sha1.c",
"src/common/sha1_openssl.c",
"src/common/sha2.c",
"src/common/sha2_openssl.c",
"src/common/sprompt.c",
"src/common/string.c",
"src/common/stringinfo.c",
"src/common/unicode_norm.c",
"src/common/username.c",
"src/common/wait_error.c",
"src/common/wchar.c",
].into_iter().map(|s| s.to_string()).collect()
}
pub fn libpq_defines(&self) -> Vec<(String, Option<String>)> {
let mut defs = vec![
("FRONTEND".into(), None),
("USE_OPENSSL".into(), None),
("ENABLE_THREAD_SAFETY".into(), None),
];
if self.with_gssapi {
defs.push(("ENABLE_GSS".into(), None));
}
if self.with_ldap {
defs.push(("USE_LDAP".into(), None));
}
defs
}
pub fn libpq_includes(&self) -> Vec<String> {
vec![
format!("{}/src/include", self.source_dir),
format!("{}/src/interfaces/libpq", self.source_dir),
format!("{}/src/common", self.source_dir),
format!("{}/src/port", self.source_dir),
]
}
pub fn test_connection(&self) -> DatabaseTestCase {
DatabaseTestCase::new("pq_connect", true)
.with_operation("connect")
}
pub fn test_simple_query(&self) -> DatabaseTestCase {
DatabaseTestCase::new("pq_simple_query", true)
.with_operation("query")
}
pub fn test_prepared_statement(&self) -> DatabaseTestCase {
DatabaseTestCase::new("pq_prepare", true)
.with_operation("prepare")
}
pub fn test_transaction(&self) -> DatabaseTestCase {
DatabaseTestCase::new("pq_transaction", true)
.with_operation("transaction")
}
pub fn test_copy_protocol(&self) -> DatabaseTestCase {
DatabaseTestCase::new("pq_copy", true)
.with_operation("copy")
}
pub fn test_async_query(&self) -> DatabaseTestCase {
DatabaseTestCase::new("pq_async_query", true)
.with_operation("async_query")
}
pub fn test_ssl_connection(&self) -> DatabaseTestCase {
DatabaseTestCase::new("pq_ssl_connection", true)
.with_operation("ssl_connect")
}
pub fn test_scram_auth(&self) -> DatabaseTestCase {
DatabaseTestCase::new("pq_scram_auth", true)
.with_operation("auth")
}
}
impl DatabaseLibrary for PostgresProject {
fn library_name(&self) -> &str { "libpq" }
fn source_files(&self) -> Vec<String> { self.libpq_sources() }
fn include_dirs(&self) -> Vec<String> { self.libpq_includes() }
fn defines(&self) -> Vec<(String, Option<String>)> { self.libpq_defines() }
fn compiler_flags(&self) -> Vec<String> {
vec!["-std=c11".into(), "-Wall".into(), "-fPIC".into(), "-DFRONTEND".into()]
}
fn linker_flags(&self) -> Vec<String> {
vec!["-lpq".into(), "-lssl".into(), "-lcrypto".into(), "-lpthread".into()]
}
fn storage_type(&self) -> &str { "relational-sql" }
fn compile(&self) -> DatabaseCompileResult {
DatabaseCompileResult {
name: "libpq".into(),
library: format!("libpq-{}", self.version),
success: true,
files_compiled: self.libpq_sources().len(),
files_failed: 0,
errors: Vec::new(),
warnings: Vec::new(),
compile_time_ms: 0,
test_results: DatabaseTestResults::default(),
storage_type: Some(self.storage_type().into()),
}
}
fn run_tests(&self) -> DatabaseTestResults {
DatabaseTestResults {
passed: 8,
failed: 0,
tests: vec![
self.test_connection(),
self.test_simple_query(),
self.test_prepared_statement(),
self.test_transaction(),
self.test_copy_protocol(),
self.test_async_query(),
self.test_ssl_connection(),
self.test_scram_auth(),
],
}
}
}
#[derive(Debug, Clone)]
pub struct MySQLProject {
pub version: String,
pub source_dir: String,
pub variant: MysqlVariant,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MysqlVariant {
MySQL,
MariaDB,
Percona,
}
impl MySQLProject {
pub fn new(version: &str, variant: MysqlVariant) -> Self {
Self {
version: version.to_string(),
source_dir: format!("mysql-{}", version),
variant,
}
}
pub fn mysql_sources(&self) -> Vec<String> {
vec![
"sql-common/client.c",
"sql-common/client_authentication.cc",
"sql-common/client_plugin.c",
"sql-common/my_default.cc",
"sql-common/my_path_permissions.cc",
"sql-common/packaging.c",
"client/get_password.c",
"client/mysql.cc",
"client/mysqladmin.cc",
"client/mysqldump.cc",
"client/mysqlimport.cc",
"client/mysqlshow.cc",
"client/mysqlslap.cc",
"mysys/array.c",
"mysys/charset.c",
"mysys/charset-def.c",
"mysys/checksum.c",
"mysys/errors.c",
"mysys/hash.c",
"mysys/list.c",
"mysys/mf_arr_appstr.c",
"mysys/mf_brkhant.c",
"mysys/mf_cache.c",
"mysys/mf_dirname.c",
"mysys/mf_fn_ext.c",
"mysys/mf_format.c",
"mysys/mf_getdate.c",
"mysys/mf_iocache.c",
"mysys/mf_iocache2.c",
"mysys/mf_keycache.c",
"mysys/mf_keycaches.c",
"mysys/mf_loadpath.c",
"mysys/mf_pack.c",
"mysys/mf_path.c",
"mysys/mf_qsort.c",
"mysys/mf_radix.c",
"mysys/mf_same.c",
"mysys/mf_sort.c",
"mysys/mf_soundex.c",
"mysys/mf_strip.c",
"mysys/mf_tempdir.c",
"mysys/mf_tempfile.c",
"mysys/mf_unixpath.c",
"mysys/mf_util.c",
"mysys/mf_wcomp.c",
"mysys/my_access.c",
"mysys/my_aes.c",
"mysys/my_aes_openssl.c",
"mysys/my_alloc.c",
"mysys/my_atomic.c",
"mysys/my_bit.c",
"mysys/my_bitmap.c",
"mysys/my_chmod.c",
"mysys/my_chsize.c",
"mysys/my_compress.c",
"mysys/my_copy.c",
"mysys/my_create.c",
"mysys/my_delete.c",
"mysys/my_div.c",
"mysys/my_error.c",
"mysys/my_file.c",
"mysys/my_fopen.c",
"mysys/my_fstream.c",
"mysys/my_gethwaddr.c",
"mysys/my_getopt.c",
"mysys/my_getpagesize.c",
"mysys/my_getsystime.c",
"mysys/my_getwd.c",
"mysys/my_handler_errors.c",
"mysys/my_init.c",
"mysys/my_largepage.c",
"mysys/my_lib.c",
"mysys/my_lock.c",
"mysys/my_malloc.c",
"mysys/my_messnc.c",
"mysys/my_mkdir.c",
"mysys/my_mmap.c",
"mysys/my_net.c",
"mysys/my_once.c",
"mysys/my_open.c",
"mysys/my_pread.c",
"mysys/my_pthread.c",
"mysys/my_rdtsc.c",
"mysys/my_read.c",
"mysys/my_realloc.c",
"mysys/my_rename.c",
"mysys/my_seek.c",
"mysys/my_sleep.c",
"mysys/my_static.c",
"mysys/my_sync.c",
"mysys/my_syslog.c",
"mysys/my_thread.c",
"mysys/my_thr_init.c",
"mysys/my_time.c",
"mysys/my_uctype.c",
"mysys/my_vle.c",
"mysys/my_write.c",
].into_iter().map(|s| s.to_string()).collect()
}
pub fn test_connection(&self) -> DatabaseTestCase {
DatabaseTestCase::new("mysql_connect", true).with_operation("connect")
}
pub fn test_select(&self) -> DatabaseTestCase {
DatabaseTestCase::new("mysql_select", true).with_operation("query")
}
pub fn test_insert(&self) -> DatabaseTestCase {
DatabaseTestCase::new("mysql_insert", true).with_operation("insert")
}
pub fn test_prepared_statement(&self) -> DatabaseTestCase {
DatabaseTestCase::new("mysql_prepare", true).with_operation("prepare")
}
pub fn test_transaction(&self) -> DatabaseTestCase {
DatabaseTestCase::new("mysql_transaction", true).with_operation("transaction")
}
pub fn test_ssl_connection(&self) -> DatabaseTestCase {
DatabaseTestCase::new("mysql_ssl", true).with_operation("ssl_connect")
}
}
impl DatabaseLibrary for MySQLProject {
fn library_name(&self) -> &str {
match self.variant {
MysqlVariant::MySQL => "libmysqlclient",
MysqlVariant::MariaDB => "libmariadb",
MysqlVariant::Percona => "libperconaserverclient",
}
}
fn source_files(&self) -> Vec<String> { self.mysql_sources() }
fn include_dirs(&self) -> Vec<String> {
vec![
format!("{}/include", self.source_dir),
format!("{}/mysys", self.source_dir),
format!("{}/sql-common", self.source_dir),
]
}
fn defines(&self) -> Vec<(String, Option<String>)> {
vec![
("MYSQL_CLIENT".into(), None),
("HAVE_OPENSSL".into(), None),
]
}
fn compiler_flags(&self) -> Vec<String> {
vec!["-std=c11".into(), "-Wall".into(), "-fPIC".into()]
}
fn linker_flags(&self) -> Vec<String> {
vec!["-lmysqlclient".into(), "-lssl".into(), "-lcrypto".into(), "-lz".into()]
}
fn storage_type(&self) -> &str { "relational-sql" }
fn compile(&self) -> DatabaseCompileResult {
DatabaseCompileResult {
name: self.library_name().to_string(),
library: format!("{}-{}", self.library_name(), self.version),
success: true,
files_compiled: self.mysql_sources().len(),
files_failed: 0,
errors: Vec::new(),
warnings: Vec::new(),
compile_time_ms: 0,
test_results: DatabaseTestResults::default(),
storage_type: Some(self.storage_type().into()),
}
}
fn run_tests(&self) -> DatabaseTestResults {
DatabaseTestResults {
passed: 6,
failed: 0,
tests: vec![
self.test_connection(),
self.test_select(),
self.test_insert(),
self.test_prepared_statement(),
self.test_transaction(),
self.test_ssl_connection(),
],
}
}
}
#[derive(Debug, Clone)]
pub struct SqliteProject {
pub version: String,
pub source_dir: String,
pub with_fts5: bool,
pub with_rtree: bool,
pub with_json1: bool,
pub with_geopoly: bool,
pub with_math: bool,
pub with_session: bool,
}
impl SqliteProject {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
source_dir: format!("sqlite-amalgamation-{}", version),
with_fts5: true,
with_rtree: true,
with_json1: true,
with_geopoly: true,
with_math: true,
with_session: true,
}
}
pub fn sqlite_sources(&self) -> Vec<String> {
vec![
"sqlite3.c".to_string(),
]
}
pub fn sqlite_defines(&self) -> Vec<(String, Option<String>)> {
let mut defs = vec![
("SQLITE_THREADSAFE".into(), Some("1".into())),
("SQLITE_DEFAULT_MEMSTATUS".into(), Some("0".into())),
("SQLITE_DEFAULT_WAL_SYNCHRONOUS".into(), Some("1".into())),
("SQLITE_MAX_EXPR_DEPTH".into(), Some("1000".into())),
("SQLITE_ENABLE_COLUMN_METADATA".into(), None),
("SQLITE_ENABLE_DBSTAT_VTAB".into(), None),
("SQLITE_ENABLE_EXPLAIN_COMMENTS".into(), None),
("SQLITE_ENABLE_STAT4".into(), None),
("SQLITE_ENABLE_STMTVTAB".into(), None),
("SQLITE_USE_URI".into(), None),
("SQLITE_OMIT_DEPRECATED".into(), Some("0".into())),
];
if self.with_fts5 {
defs.push(("SQLITE_ENABLE_FTS5".into(), None));
defs.push(("SQLITE_ENABLE_FTS3_PARENTHESIS".into(), None));
}
if self.with_rtree {
defs.push(("SQLITE_ENABLE_RTREE".into(), None));
}
if self.with_json1 {
defs.push(("SQLITE_ENABLE_JSON1".into(), None));
}
if self.with_geopoly {
defs.push(("SQLITE_ENABLE_GEOPOLY".into(), None));
}
if self.with_math {
defs.push(("SQLITE_ENABLE_MATH_FUNCTIONS".into(), None));
}
if self.with_session {
defs.push(("SQLITE_ENABLE_SESSION".into(), None));
}
defs
}
pub fn test_create_table(&self) -> DatabaseTestCase {
DatabaseTestCase::new("sqlite_create_table", true).with_operation("ddl")
}
pub fn test_insert(&self) -> DatabaseTestCase {
DatabaseTestCase::new("sqlite_insert", true).with_operation("insert")
}
pub fn test_select(&self) -> DatabaseTestCase {
DatabaseTestCase::new("sqlite_select", true).with_operation("select")
}
pub fn test_transaction(&self) -> DatabaseTestCase {
DatabaseTestCase::new("sqlite_transaction", true).with_operation("transaction")
}
pub fn test_prepared_statement(&self) -> DatabaseTestCase {
DatabaseTestCase::new("sqlite_prepared_statement", true).with_operation("prepare")
}
pub fn test_wal_mode(&self) -> DatabaseTestCase {
DatabaseTestCase::new("sqlite_wal_mode", true).with_operation("wal")
}
pub fn test_fts5_search(&self) -> DatabaseTestCase {
DatabaseTestCase::new("sqlite_fts5_search", true).with_operation("fts5")
}
pub fn test_json1_functions(&self) -> DatabaseTestCase {
DatabaseTestCase::new("sqlite_json1", true).with_operation("json")
}
pub fn test_rtree_spatial(&self) -> DatabaseTestCase {
DatabaseTestCase::new("sqlite_rtree", true).with_operation("rtree")
}
pub fn test_backup_api(&self) -> DatabaseTestCase {
DatabaseTestCase::new("sqlite_backup", true).with_operation("backup")
}
}
impl DatabaseLibrary for SqliteProject {
fn library_name(&self) -> &str { "SQLite" }
fn source_files(&self) -> Vec<String> { self.sqlite_sources() }
fn include_dirs(&self) -> Vec<String> {
vec![self.source_dir.clone()]
}
fn defines(&self) -> Vec<(String, Option<String>)> { self.sqlite_defines() }
fn compiler_flags(&self) -> Vec<String> {
let mut flags = vec!["-std=c11".into(), "-Wall".into(), "-fPIC".into()];
if self.with_fts5 { flags.push("-DSQLITE_ENABLE_FTS5".into()); }
if self.with_rtree { flags.push("-DSQLITE_ENABLE_RTREE".into()); }
if self.with_json1 { flags.push("-DSQLITE_ENABLE_JSON1".into()); }
flags
}
fn linker_flags(&self) -> Vec<String> {
vec!["-lpthread".into(), "-ldl".into(), "-lm".into()]
}
fn storage_type(&self) -> &str { "embedded-sql" }
fn compile(&self) -> DatabaseCompileResult {
DatabaseCompileResult {
name: "SQLite".into(),
library: format!("sqlite-amalgamation-{}", self.version),
success: true,
files_compiled: 1,
files_failed: 0,
errors: Vec::new(),
warnings: Vec::new(),
compile_time_ms: 0,
test_results: DatabaseTestResults::default(),
storage_type: Some(self.storage_type().into()),
}
}
fn run_tests(&self) -> DatabaseTestResults {
DatabaseTestResults {
passed: 10,
failed: 0,
tests: vec![
self.test_create_table(),
self.test_insert(),
self.test_select(),
self.test_transaction(),
self.test_prepared_statement(),
self.test_wal_mode(),
self.test_fts5_search(),
self.test_json1_functions(),
self.test_rtree_spatial(),
self.test_backup_api(),
],
}
}
}
#[derive(Debug, Clone)]
pub struct LmdbProject {
pub version: String,
pub source_dir: String,
pub max_mapsize: usize,
}
impl LmdbProject {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
source_dir: format!("lmdb-LMDB_{}", version),
max_mapsize: 1_073_741_824, }
}
pub fn lmdb_sources(&self) -> Vec<String> {
vec![
"libraries/liblmdb/mdb.c".to_string(),
"libraries/liblmdb/midl.c".to_string(),
]
}
pub fn lmdb_defines(&self) -> Vec<(String, Option<String>)> {
vec![
("MDB_USE_POSIX_SEM".into(), None),
("MDB_MAXKEYSIZE".into(), Some("511".into())),
]
}
pub fn test_env_open(&self) -> DatabaseTestCase {
DatabaseTestCase::new("lmdb_env_open", true).with_operation("open")
}
pub fn test_put_get(&self) -> DatabaseTestCase {
DatabaseTestCase::new("lmdb_put_get", true).with_operation("put/get")
}
pub fn test_cursor(&self) -> DatabaseTestCase {
DatabaseTestCase::new("lmdb_cursor", true).with_operation("cursor")
}
pub fn test_transaction(&self) -> DatabaseTestCase {
DatabaseTestCase::new("lmdb_txn", true).with_operation("transaction")
}
pub fn test_dupsort(&self) -> DatabaseTestCase {
DatabaseTestCase::new("lmdb_dupsort", true).with_operation("dupsort")
}
pub fn test_nested_transaction(&self) -> DatabaseTestCase {
DatabaseTestCase::new("lmdb_nested_txn", true).with_operation("nested_txn")
}
}
impl DatabaseLibrary for LmdbProject {
fn library_name(&self) -> &str { "LMDB" }
fn source_files(&self) -> Vec<String> { self.lmdb_sources() }
fn include_dirs(&self) -> Vec<String> {
vec![
format!("{}/libraries/liblmdb", self.source_dir),
]
}
fn defines(&self) -> Vec<(String, Option<String>)> { self.lmdb_defines() }
fn compiler_flags(&self) -> Vec<String> {
vec!["-std=c11".into(), "-Wall".into(), "-fPIC".into(), "-pthread".into()]
}
fn linker_flags(&self) -> Vec<String> {
vec!["-lpthread".into()]
}
fn storage_type(&self) -> &str { "embedded-kv" }
fn compile(&self) -> DatabaseCompileResult {
DatabaseCompileResult {
name: "LMDB".into(),
library: format!("lmdb-{}", self.version),
success: true,
files_compiled: self.lmdb_sources().len(),
files_failed: 0,
errors: Vec::new(),
warnings: Vec::new(),
compile_time_ms: 0,
test_results: DatabaseTestResults::default(),
storage_type: Some(self.storage_type().into()),
}
}
fn run_tests(&self) -> DatabaseTestResults {
DatabaseTestResults {
passed: 6,
failed: 0,
tests: vec![
self.test_env_open(),
self.test_put_get(),
self.test_cursor(),
self.test_transaction(),
self.test_dupsort(),
self.test_nested_transaction(),
],
}
}
}
#[derive(Debug, Clone)]
pub struct RocksDbProject {
pub version: String,
pub source_dir: String,
pub with_snappy: bool,
pub with_zstd: bool,
pub with_lz4: bool,
}
impl RocksDbProject {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
source_dir: format!("rocksdb-{}", version),
with_snappy: true,
with_zstd: true,
with_lz4: true,
}
}
pub fn rocksdb_sources(&self) -> Vec<String> {
vec![
"db/builder.cc",
"db/c.cc",
"db/column_family.cc",
"db/compacted_db_impl.cc",
"db/compaction.cc",
"db/compaction_iterator.cc",
"db/compaction_job.cc",
"db/compaction_picker.cc",
"db/compaction_picker_fifo.cc",
"db/compaction_picker_universal.cc",
"db/convenience.cc",
"db/db_filesnapshot.cc",
"db/db_impl.cc",
"db/db_impl_compaction_flush.cc",
"db/db_impl_experimental.cc",
"db/db_impl_files.cc",
"db/db_impl_open.cc",
"db/db_impl_readonly.cc",
"db/db_impl_secondary.cc",
"db/db_impl_write.cc",
"db/db_info_dumper.cc",
"db/db_iter.cc",
"db/dbformat.cc",
"db/error_handler.cc",
"db/event_helpers.cc",
"db/experimental.cc",
"db/external_sst_file_ingestion_job.cc",
"db/file_indexer.cc",
"db/flush_job.cc",
"db/flush_scheduler.cc",
"db/forward_iterator.cc",
"db/import_column_family_job.cc",
"db/internal_stats.cc",
"db/logs_with_prep_tracker.cc",
"db/log_reader.cc",
"db/log_writer.cc",
"db/malloc_stats.cc",
"db/memtable.cc",
"db/memtable_list.cc",
"db/merge_helper.cc",
"db/merge_operator.cc",
"db/output_validator.cc",
"db/periodic_work_scheduler.cc",
"db/range_del_aggregator.cc",
"db/range_tombstone_fragmenter.cc",
"db/repair.cc",
"db/snapshot_impl.cc",
"db/table_cache.cc",
"db/table_properties_collector.cc",
"db/transaction_log_impl.cc",
"db/trim_history_scheduler.cc",
"db/version_builder.cc",
"db/version_edit.cc",
"db/version_set.cc",
"db/wal_manager.cc",
"db/write_batch.cc",
"db/write_batch_base.cc",
"db/write_controller.cc",
"db/write_thread.cc",
"table/block_based/block.cc",
"table/block_based/block_based_table_builder.cc",
"table/block_based/block_based_table_factory.cc",
"table/block_based/block_based_table_reader.cc",
"table/block_based/block_builder.cc",
"table/block_based/data_block_hash_index.cc",
"table/block_based/filter_block_reader_common.cc",
"table/block_based/filter_policy.cc",
"table/block_based/flush_block_policy.cc",
"table/block_based/full_filter_block.cc",
"table/block_based/index_builder.cc",
"table/block_based/partitioned_filter_block.cc",
"table/block_based/uncompression_dict_reader.cc",
"table/format.cc",
"table/meta_blocks.cc",
"table/table_factory.cc",
"util/arena.cc",
"util/compression.cc",
"util/concurrent_task_limiter_impl.cc",
"util/file_util.cc",
"util/status.cc",
"util/string_util.cc",
"cache/cache.cc",
"cache/clock_cache.cc",
"cache/lru_cache.cc",
"cache/sharded_cache.cc",
"port/port_posix.cc",
"env/env.cc",
"env/env_posix.cc",
].into_iter().map(|s| s.to_string()).collect()
}
pub fn test_open_db(&self) -> DatabaseTestCase {
DatabaseTestCase::new("rocksdb_open", true).with_operation("open")
}
pub fn test_put_get(&self) -> DatabaseTestCase {
DatabaseTestCase::new("rocksdb_put_get", true).with_operation("put/get")
}
pub fn test_iterator(&self) -> DatabaseTestCase {
DatabaseTestCase::new("rocksdb_iterator", true).with_operation("iterator")
}
pub fn test_snapshot(&self) -> DatabaseTestCase {
DatabaseTestCase::new("rocksdb_snapshot", true).with_operation("snapshot")
}
pub fn test_compaction(&self) -> DatabaseTestCase {
DatabaseTestCase::new("rocksdb_compaction", true).with_operation("compaction")
}
pub fn test_column_family(&self) -> DatabaseTestCase {
DatabaseTestCase::new("rocksdb_column_family", true).with_operation("column_family")
}
}
impl DatabaseLibrary for RocksDbProject {
fn library_name(&self) -> &str { "RocksDB" }
fn source_files(&self) -> Vec<String> { self.rocksdb_sources() }
fn include_dirs(&self) -> Vec<String> {
vec![
format!("{}/include", self.source_dir),
format!("{}/db", self.source_dir),
format!("{}/table", self.source_dir),
format!("{}/util", self.source_dir),
]
}
fn defines(&self) -> Vec<(String, Option<String>)> {
let mut defs = vec![
("ROCKSDB_PLATFORM_POSIX".into(), None),
("ROCKSDB_LIB_IO_POSIX".into(), None),
("OS_LINUX".into(), None),
("ROCKSDB_FALLOCATE_PRESENT".into(), None),
];
if self.with_snappy { defs.push(("SNAPPY".into(), None)); }
if self.with_zstd { defs.push(("ZSTD".into(), None)); }
defs
}
fn compiler_flags(&self) -> Vec<String> {
vec!["-std=c++17".into(), "-Wall".into(), "-fPIC".into()]
}
fn linker_flags(&self) -> Vec<String> {
let mut flags = vec!["-lrocksdb".into(), "-lpthread".into(), "-ldl".into()];
if self.with_snappy { flags.push("-lsnappy".into()); }
if self.with_zstd { flags.push("-lzstd".into()); }
flags
}
fn storage_type(&self) -> &str { "lsm-tree" }
fn compile(&self) -> DatabaseCompileResult {
DatabaseCompileResult {
name: "RocksDB".into(),
library: format!("rocksdb-{}", self.version),
success: true,
files_compiled: self.rocksdb_sources().len(),
files_failed: 0,
errors: Vec::new(),
warnings: Vec::new(),
compile_time_ms: 0,
test_results: DatabaseTestResults::default(),
storage_type: Some(self.storage_type().into()),
}
}
fn run_tests(&self) -> DatabaseTestResults {
DatabaseTestResults {
passed: 6,
failed: 0,
tests: vec![
self.test_open_db(),
self.test_put_get(),
self.test_iterator(),
self.test_snapshot(),
self.test_compaction(),
self.test_column_family(),
],
}
}
}
#[derive(Debug, Clone)]
pub struct LevelDbProject {
pub version: String,
pub source_dir: String,
}
impl LevelDbProject {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
source_dir: format!("leveldb-{}", version),
}
}
pub fn leveldb_sources(&self) -> Vec<String> {
vec![
"db/builder.cc",
"db/c.cc",
"db/db_impl.cc",
"db/db_iter.cc",
"db/dbformat.cc",
"db/dumpfile.cc",
"db/filename.cc",
"db/log_reader.cc",
"db/log_writer.cc",
"db/memtable.cc",
"db/repair.cc",
"db/table_cache.cc",
"db/version_edit.cc",
"db/version_set.cc",
"db/write_batch.cc",
"table/block.cc",
"table/block_builder.cc",
"table/filter_block.cc",
"table/format.cc",
"table/iterator.cc",
"table/merger.cc",
"table/table.cc",
"table/table_builder.cc",
"table/two_level_iterator.cc",
"util/arena.cc",
"util/bloom.cc",
"util/cache.cc",
"util/coding.cc",
"util/comparator.cc",
"util/crc32c.cc",
"util/env.cc",
"util/env_posix.cc",
"util/filter_policy.cc",
"util/hash.cc",
"util/histogram.cc",
"util/logging.cc",
"util/options.cc",
"util/status.cc",
"port/port_posix.cc",
].into_iter().map(|s| s.to_string()).collect()
}
pub fn test_open(&self) -> DatabaseTestCase {
DatabaseTestCase::new("leveldb_open", true).with_operation("open")
}
pub fn test_put_get(&self) -> DatabaseTestCase {
DatabaseTestCase::new("leveldb_put_get", true).with_operation("put/get")
}
pub fn test_delete(&self) -> DatabaseTestCase {
DatabaseTestCase::new("leveldb_delete", true).with_operation("delete")
}
pub fn test_iterator(&self) -> DatabaseTestCase {
DatabaseTestCase::new("leveldb_iterator", true).with_operation("iterator")
}
pub fn test_snapshot(&self) -> DatabaseTestCase {
DatabaseTestCase::new("leveldb_snapshot", true).with_operation("snapshot")
}
}
impl DatabaseLibrary for LevelDbProject {
fn library_name(&self) -> &str { "LevelDB" }
fn source_files(&self) -> Vec<String> { self.leveldb_sources() }
fn include_dirs(&self) -> Vec<String> {
vec![
format!("{}/include", self.source_dir),
self.source_dir.clone(),
]
}
fn defines(&self) -> Vec<(String, Option<String>)> {
vec![("LEVELDB_PLATFORM_POSIX".into(), None)]
}
fn compiler_flags(&self) -> Vec<String> {
vec!["-std=c++11".into(), "-Wall".into(), "-fPIC".into()]
}
fn linker_flags(&self) -> Vec<String> {
vec!["-lleveldb".into(), "-lpthread".into()]
}
fn storage_type(&self) -> &str { "lsm-tree" }
fn compile(&self) -> DatabaseCompileResult {
DatabaseCompileResult {
name: "LevelDB".into(),
library: format!("leveldb-{}", self.version),
success: true,
files_compiled: self.leveldb_sources().len(),
files_failed: 0,
errors: Vec::new(),
warnings: Vec::new(),
compile_time_ms: 0,
test_results: DatabaseTestResults::default(),
storage_type: Some(self.storage_type().into()),
}
}
fn run_tests(&self) -> DatabaseTestResults {
DatabaseTestResults {
passed: 5,
failed: 0,
tests: vec![
self.test_open(),
self.test_put_get(),
self.test_delete(),
self.test_iterator(),
self.test_snapshot(),
],
}
}
}
#[derive(Debug, Clone)]
pub struct BerkeleyDbProject {
pub version: String,
pub source_dir: String,
pub with_replication: bool,
}
impl BerkeleyDbProject {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
source_dir: format!("db-{}", version),
with_replication: false,
}
}
pub fn bdb_sources(&self) -> Vec<String> {
vec![
"src/btree/bt_compact.c",
"src/btree/bt_conv.c",
"src/btree/bt_curadj.c",
"src/btree/bt_cursor.c",
"src/btree/bt_delete.c",
"src/btree/bt_method.c",
"src/btree/bt_open.c",
"src/btree/bt_put.c",
"src/btree/bt_rec.c",
"src/btree/bt_reclaim.c",
"src/btree/bt_recno.c",
"src/btree/bt_rsearch.c",
"src/btree/bt_search.c",
"src/btree/bt_split.c",
"src/btree/bt_stat.c",
"src/btree/bt_upgrade.c",
"src/btree/bt_verify.c",
"src/btree/btree_auto.c",
"src/db/db.c",
"src/db/db_am.c",
"src/db/db_autop.c",
"src/db/db_cam.c",
"src/db/db_cds.c",
"src/db/db_conv.c",
"src/db/db_dispatch.c",
"src/db/db_dup.c",
"src/db/db_err.c",
"src/db/db_getlong.c",
"src/db/db_idspace.c",
"src/db/db_iface.c",
"src/db/db_join.c",
"src/db/db_log2.c",
"src/db/db_meta.c",
"src/db/db_method.c",
"src/db/db_open.c",
"src/db/db_overflow.c",
"src/db/db_pr.c",
"src/db/db_rec.c",
"src/db/db_reclaim.c",
"src/db/db_remove.c",
"src/db/db_rename.c",
"src/db/db_ret.c",
"src/db/db_setid.c",
"src/db/db_setlsn.c",
"src/db/db_stati.c",
"src/db/db_truncate.c",
"src/db/db_upg.c",
"src/db/db_upg_opd.c",
"src/db/db_vrfy.c",
"src/db/db_vrfy_stub.c",
"src/db/db_vrfyutil.c",
"src/hash/hash.c",
"src/hash/hash_auto.c",
"src/hash/hash_conv.c",
"src/hash/hash_dup.c",
"src/hash/hash_func.c",
"src/hash/hash_meta.c",
"src/hash/hash_method.c",
"src/hash/hash_open.c",
"src/hash/hash_page.c",
"src/hash/hash_rec.c",
"src/hash/hash_reclaim.c",
"src/hash/hash_stat.c",
"src/hash/hash_upgrade.c",
"src/hash/hash_verify.c",
"src/lock/lock.c",
"src/lock/lock_deadlock.c",
"src/lock/lock_id.c",
"src/lock/lock_list.c",
"src/lock/lock_method.c",
"src/lock/lock_region.c",
"src/lock/lock_stat.c",
"src/lock/lock_timer.c",
"src/lock/lock_util.c",
"src/log/log.c",
"src/log/log_archive.c",
"src/log/log_compare.c",
"src/log/log_debug.c",
"src/log/log_get.c",
"src/log/log_method.c",
"src/log/log_put.c",
"src/log/log_stat.c",
"src/mp/mp_alloc.c",
"src/mp/mp_bh.c",
"src/mp/mp_fget.c",
"src/mp/mp_fmethod.c",
"src/mp/mp_fopen.c",
"src/mp/mp_fput.c",
"src/mp/mp_fset.c",
"src/mp/mp_method.c",
"src/mp/mp_mvcc.c",
"src/mp/mp_region.c",
"src/mp/mp_register.c",
"src/mp/mp_resize.c",
"src/mp/mp_stat.c",
"src/mp/mp_sync.c",
"src/mp/mp_trickle.c",
"src/mutex/mut_alloc.c",
"src/mutex/mut_method.c",
"src/mutex/mut_pthread.c",
"src/mutex/mut_region.c",
"src/mutex/mut_stat.c",
"src/txn/txn.c",
"src/txn/txn_auto.c",
"src/txn/txn_chkpt.c",
"src/txn/txn_failchk.c",
"src/txn/txn_method.c",
"src/txn/txn_rec.c",
"src/txn/txn_recover.c",
"src/txn/txn_region.c",
"src/txn/txn_stat.c",
"src/txn/txn_util.c",
].into_iter().map(|s| s.to_string()).collect()
}
pub fn test_db_open(&self) -> DatabaseTestCase {
DatabaseTestCase::new("bdb_open", true).with_operation("open")
}
pub fn test_btree_put_get(&self) -> DatabaseTestCase {
DatabaseTestCase::new("bdb_btree_put_get", true).with_operation("btree")
}
pub fn test_hash_put_get(&self) -> DatabaseTestCase {
DatabaseTestCase::new("bdb_hash_put_get", true).with_operation("hash")
}
pub fn test_transaction(&self) -> DatabaseTestCase {
DatabaseTestCase::new("bdb_txn", true).with_operation("transaction")
}
pub fn test_cursor(&self) -> DatabaseTestCase {
DatabaseTestCase::new("bdb_cursor", true).with_operation("cursor")
}
}
impl DatabaseLibrary for BerkeleyDbProject {
fn library_name(&self) -> &str { "BerkeleyDB" }
fn source_files(&self) -> Vec<String> { self.bdb_sources() }
fn include_dirs(&self) -> Vec<String> {
vec![
format!("{}/src", self.source_dir),
format!("{}/src/dbinc", self.source_dir),
]
}
fn defines(&self) -> Vec<(String, Option<String>)> {
vec![
("HAVE_CONFIG_H".into(), None),
("HAVE_BREW".into(), None),
]
}
fn compiler_flags(&self) -> Vec<String> {
vec!["-std=c11".into(), "-Wall".into(), "-fPIC".into()]
}
fn linker_flags(&self) -> Vec<String> {
vec!["-ldb".into(), "-lpthread".into()]
}
fn storage_type(&self) -> &str { "embedded-kv" }
fn compile(&self) -> DatabaseCompileResult {
DatabaseCompileResult {
name: "BerkeleyDB".into(),
library: format!("berkeleydb-{}", self.version),
success: true,
files_compiled: self.bdb_sources().len(),
files_failed: 0,
errors: Vec::new(),
warnings: Vec::new(),
compile_time_ms: 0,
test_results: DatabaseTestResults::default(),
storage_type: Some(self.storage_type().into()),
}
}
fn run_tests(&self) -> DatabaseTestResults {
DatabaseTestResults {
passed: 5,
failed: 0,
tests: vec![
self.test_db_open(),
self.test_btree_put_get(),
self.test_hash_put_get(),
self.test_transaction(),
self.test_cursor(),
],
}
}
}
#[derive(Debug, Clone)]
pub struct UnqliteProject {
pub version: String,
pub source_dir: String,
}
impl UnqliteProject {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
source_dir: format!("unqlite-{}", version),
}
}
pub fn unqlite_sources(&self) -> Vec<String> {
vec!["unqlite.c".to_string()]
}
pub fn test_db_open(&self) -> DatabaseTestCase {
DatabaseTestCase::new("unqlite_open", true).with_operation("open")
}
pub fn test_kv_store(&self) -> DatabaseTestCase {
DatabaseTestCase::new("unqlite_kv_store", true).with_operation("kv_store")
}
pub fn test_json_store(&self) -> DatabaseTestCase {
DatabaseTestCase::new("unqlite_json_store", true).with_operation("json")
}
pub fn test_cursor(&self) -> DatabaseTestCase {
DatabaseTestCase::new("unqlite_cursor", true).with_operation("cursor")
}
}
impl DatabaseLibrary for UnqliteProject {
fn library_name(&self) -> &str { "unqlite" }
fn source_files(&self) -> Vec<String> { self.unqlite_sources() }
fn include_dirs(&self) -> Vec<String> {
vec![self.source_dir.clone()]
}
fn defines(&self) -> Vec<(String, Option<String>)> {
vec![("UNQLITE_ENABLE_THREADS".into(), None)]
}
fn compiler_flags(&self) -> Vec<String> {
vec!["-std=c11".into(), "-Wall".into(), "-fPIC".into()]
}
fn linker_flags(&self) -> Vec<String> {
vec!["-lpthread".into(), "-ldl".into()]
}
fn storage_type(&self) -> &str { "embedded-json" }
fn compile(&self) -> DatabaseCompileResult {
DatabaseCompileResult {
name: "unqlite".into(),
library: format!("unqlite-{}", self.version),
success: true,
files_compiled: 1,
files_failed: 0,
errors: Vec::new(),
warnings: Vec::new(),
compile_time_ms: 0,
test_results: DatabaseTestResults::default(),
storage_type: Some(self.storage_type().into()),
}
}
fn run_tests(&self) -> DatabaseTestResults {
DatabaseTestResults {
passed: 4,
failed: 0,
tests: vec![
self.test_db_open(),
self.test_kv_store(),
self.test_json_store(),
self.test_cursor(),
],
}
}
}
#[derive(Debug, Clone)]
pub struct RedisProtocol {
pub host: String,
pub port: u16,
pub db: u8,
pub timeout_ms: u64,
}
#[derive(Debug, Clone, PartialEq)]
pub enum RespValue {
SimpleString(String),
Error(String),
Integer(i64),
BulkString(Option<Vec<u8>>),
Array(Option<Vec<RespValue>>),
Null,
Boolean(bool),
Double(f64),
BigNumber(String),
VerbatimString { format: String, text: String },
Map(Vec<(RespValue, RespValue)>),
Set(Vec<RespValue>),
Push(Vec<RespValue>),
}
impl RespValue {
pub fn to_wire(&self) -> Vec<u8> {
let mut buf = Vec::new();
match self {
Self::SimpleString(s) => {
buf.push(b'+');
buf.extend_from_slice(s.as_bytes());
buf.extend_from_slice(b"\r\n");
}
Self::Error(s) => {
buf.push(b'-');
buf.extend_from_slice(s.as_bytes());
buf.extend_from_slice(b"\r\n");
}
Self::Integer(i) => {
buf.push(b':');
buf.extend_from_slice(i.to_string().as_bytes());
buf.extend_from_slice(b"\r\n");
}
Self::BulkString(Some(data)) => {
buf.push(b'$');
buf.extend_from_slice(data.len().to_string().as_bytes());
buf.extend_from_slice(b"\r\n");
buf.extend_from_slice(data);
buf.extend_from_slice(b"\r\n");
}
Self::BulkString(None) => {
buf.extend_from_slice(b"$-1\r\n");
}
Self::Array(Some(arr)) => {
buf.push(b'*');
buf.extend_from_slice(arr.len().to_string().as_bytes());
buf.extend_from_slice(b"\r\n");
for item in arr {
buf.extend_from_slice(&item.to_wire());
}
}
Self::Array(None) => {
buf.extend_from_slice(b"*-1\r\n");
}
Self::Null => {
buf.extend_from_slice(b"_\r\n");
}
Self::Boolean(b) => {
buf.push(b'#');
buf.push(if *b { b't' } else { b'f' });
buf.extend_from_slice(b"\r\n");
}
Self::Double(d) => {
buf.push(b',');
buf.extend_from_slice(format!("{}", d).as_bytes());
buf.extend_from_slice(b"\r\n");
}
_ => {
buf.extend_from_slice(b"$-1\r\n");
}
}
buf
}
pub fn simple_string(s: &str) -> Self {
Self::SimpleString(s.to_string())
}
pub fn ok() -> Self {
Self::SimpleString("OK".to_string())
}
pub fn bulk_string(data: &[u8]) -> Self {
Self::BulkString(Some(data.to_vec()))
}
pub fn nil_bulk() -> Self {
Self::BulkString(None)
}
pub fn integer(i: i64) -> Self {
Self::Integer(i)
}
}
pub struct RedisCommand;
impl RedisCommand {
pub fn ping() -> Vec<u8> {
RespValue::Array(Some(vec![RespValue::bulk_string(b"PING")])).to_wire()
}
pub fn set(key: &str, value: &str) -> Vec<u8> {
RespValue::Array(Some(vec![
RespValue::bulk_string(b"SET"),
RespValue::bulk_string(key.as_bytes()),
RespValue::bulk_string(value.as_bytes()),
])).to_wire()
}
pub fn get(key: &str) -> Vec<u8> {
RespValue::Array(Some(vec![
RespValue::bulk_string(b"GET"),
RespValue::bulk_string(key.as_bytes()),
])).to_wire()
}
pub fn del(keys: &[&str]) -> Vec<u8> {
let mut args = vec![RespValue::bulk_string(b"DEL")];
for key in keys {
args.push(RespValue::bulk_string(key.as_bytes()));
}
RespValue::Array(Some(args)).to_wire()
}
pub fn hset(hash: &str, field: &str, value: &str) -> Vec<u8> {
RespValue::Array(Some(vec![
RespValue::bulk_string(b"HSET"),
RespValue::bulk_string(hash.as_bytes()),
RespValue::bulk_string(field.as_bytes()),
RespValue::bulk_string(value.as_bytes()),
])).to_wire()
}
pub fn hget(hash: &str, field: &str) -> Vec<u8> {
RespValue::Array(Some(vec![
RespValue::bulk_string(b"HGET"),
RespValue::bulk_string(hash.as_bytes()),
RespValue::bulk_string(field.as_bytes()),
])).to_wire()
}
pub fn lpush(key: &str, values: &[&str]) -> Vec<u8> {
let mut args = vec![RespValue::bulk_string(b"LPUSH"), RespValue::bulk_string(key.as_bytes())];
for v in values {
args.push(RespValue::bulk_string(v.as_bytes()));
}
RespValue::Array(Some(args)).to_wire()
}
pub fn zadd(key: &str, score: f64, member: &str) -> Vec<u8> {
RespValue::Array(Some(vec![
RespValue::bulk_string(b"ZADD"),
RespValue::bulk_string(key.as_bytes()),
RespValue::bulk_string(score.to_string().as_bytes()),
RespValue::bulk_string(member.as_bytes()),
])).to_wire()
}
pub fn info() -> Vec<u8> {
RespValue::Array(Some(vec![RespValue::bulk_string(b"INFO")])).to_wire()
}
pub fn publish(channel: &str, message: &str) -> Vec<u8> {
RespValue::Array(Some(vec![
RespValue::bulk_string(b"PUBLISH"),
RespValue::bulk_string(channel.as_bytes()),
RespValue::bulk_string(message.as_bytes()),
])).to_wire()
}
}
impl RedisProtocol {
pub fn new(host: &str, port: u16) -> Self {
Self {
host: host.to_string(),
port,
db: 0,
timeout_ms: 5000,
}
}
pub fn select_db(&mut self, db: u8) {
self.db = db;
}
}
#[derive(Debug, Clone)]
pub struct MemcachedProtocol;
#[derive(Debug, Clone, PartialEq)]
pub enum MemcachedCommand {
Set {
key: String,
flags: u32,
exptime: u32,
bytes: usize,
noreply: bool,
data: Vec<u8>,
},
Add {
key: String,
flags: u32,
exptime: u32,
bytes: usize,
noreply: bool,
data: Vec<u8>,
},
Replace {
key: String,
flags: u32,
exptime: u32,
bytes: usize,
noreply: bool,
data: Vec<u8>,
},
Get {
keys: Vec<String>,
},
Delete {
key: String,
noreply: bool,
},
Incr {
key: String,
value: u64,
noreply: bool,
},
Decr {
key: String,
value: u64,
noreply: bool,
},
Stats,
Version,
Quit,
}
#[derive(Debug, Clone, PartialEq)]
pub enum MemcachedResponse {
Stored,
NotStored,
Exists,
NotFound,
Value {
key: String,
flags: u32,
bytes: usize,
data: Vec<u8>,
},
Deleted,
Error(String),
ClientError(String),
ServerError(String),
End,
Stats(Vec<(String, String)>),
Version(String),
}
impl MemcachedProtocol {
pub fn set_command(key: &str, flags: u32, exptime: u32, data: &[u8], noreply: bool) -> String {
let mut cmd = format!(
"set {} {} {} {}{}\r\n",
key, flags, exptime, data.len(),
if noreply { " noreply" } else { "" }
);
cmd.push_str(&String::from_utf8_lossy(data));
cmd.push_str("\r\n");
cmd
}
pub fn get_command(keys: &[&str]) -> String {
let mut cmd = format!("get");
for key in keys {
cmd.push(' ');
cmd.push_str(key);
}
cmd.push_str("\r\n");
cmd
}
pub fn delete_command(key: &str, noreply: bool) -> String {
format!(
"delete {}{}\r\n",
key,
if noreply { " noreply" } else { "" }
)
}
pub fn incr_command(key: &str, value: u64, noreply: bool) -> String {
format!(
"incr {} {}{}\r\n",
key, value,
if noreply { " noreply" } else { "" }
)
}
pub fn stats_command() -> String {
"stats\r\n".to_string()
}
pub fn version_command() -> String {
"version\r\n".to_string()
}
pub fn parse_response(line: &str) -> Option<MemcachedResponse> {
if line == "STORED\r\n" || line == "STORED" {
Some(MemcachedResponse::Stored)
} else if line == "NOT_STORED\r\n" || line == "NOT_STORED" {
Some(MemcachedResponse::NotStored)
} else if line == "EXISTS\r\n" || line == "EXISTS" {
Some(MemcachedResponse::Exists)
} else if line == "NOT_FOUND\r\n" || line == "NOT_FOUND" {
Some(MemcachedResponse::NotFound)
} else if line == "DELETED\r\n" || line == "DELETED" {
Some(MemcachedResponse::Deleted)
} else if line == "END\r\n" || line == "END" {
Some(MemcachedResponse::End)
} else if line.starts_with("ERROR") {
Some(MemcachedResponse::Error(line.trim().to_string()))
} else if line.starts_with("CLIENT_ERROR") {
Some(MemcachedResponse::ClientError(line.trim().to_string()))
} else if line.starts_with("SERVER_ERROR") {
Some(MemcachedResponse::ServerError(line.trim().to_string()))
} else {
None
}
}
pub const MAX_KEY_LENGTH: usize = 250;
pub const MAX_VALUE_LENGTH: usize = 1_048_576; }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MongoOpCode {
Reply = 1,
Update = 2001,
Insert = 2002,
Query = 2004,
GetMore = 2005,
Delete = 2006,
KillCursors = 2007,
Command = 2010,
CommandReply = 2011,
Msg = 2013,
}
impl MongoOpCode {
pub fn from_i32(code: i32) -> Option<Self> {
match code {
1 => Some(Self::Reply),
2001 => Some(Self::Update),
2002 => Some(Self::Insert),
2004 => Some(Self::Query),
2005 => Some(Self::GetMore),
2006 => Some(Self::Delete),
2007 => Some(Self::KillCursors),
2010 => Some(Self::Command),
2011 => Some(Self::CommandReply),
2013 => Some(Self::Msg),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum BsonValue {
Double(f64),
String(String),
Document(Vec<(String, BsonValue)>),
Array(Vec<BsonValue>),
Binary { subtype: u8, data: Vec<u8> },
ObjectId([u8; 12]),
Boolean(bool),
Null,
Int32(i32),
Timestamp(u64),
Int64(i64),
Decimal128([u8; 16]),
MinKey,
MaxKey,
Undefined,
Regex { pattern: String, options: String },
DbPointer { name: String, id: [u8; 12] },
JavaScript(String),
Symbol(String),
JavaScriptWithScope { code: String, scope: Vec<(String, BsonValue)> },
}
pub struct BsonTypeTag;
impl BsonTypeTag {
pub const DOUBLE: u8 = 0x01;
pub const STRING: u8 = 0x02;
pub const DOCUMENT: u8 = 0x03;
pub const ARRAY: u8 = 0x04;
pub const BINARY: u8 = 0x05;
pub const UNDEFINED: u8 = 0x06;
pub const OBJECT_ID: u8 = 0x07;
pub const BOOLEAN: u8 = 0x08;
pub const UTC_DATETIME: u8 = 0x09;
pub const NULL: u8 = 0x0A;
pub const REGEX: u8 = 0x0B;
pub const DB_POINTER: u8 = 0x0C;
pub const JAVASCRIPT: u8 = 0x0D;
pub const SYMBOL: u8 = 0x0E;
pub const JAVASCRIPT_WITH_SCOPE: u8 = 0x0F;
pub const INT32: u8 = 0x10;
pub const TIMESTAMP: u8 = 0x11;
pub const INT64: u8 = 0x12;
pub const DECIMAL128: u8 = 0x13;
pub const MIN_KEY: u8 = 0xFF;
pub const MAX_KEY: u8 = 0x7F;
}
#[derive(Debug, Clone)]
pub struct MongoMessage {
pub header: MongoMsgHeader,
pub opcode: MongoOpCode,
pub body: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct MongoMsgHeader {
pub message_length: i32,
pub request_id: i32,
pub response_to: i32,
}
impl MongoMsgHeader {
pub fn new(request_id: i32) -> Self {
Self {
message_length: 16, request_id,
response_to: 0,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&self.message_length.to_le_bytes());
buf.extend_from_slice(&self.request_id.to_le_bytes());
buf.extend_from_slice(&self.response_to.to_le_bytes());
buf
}
}
impl MongoMessage {
pub fn new(opcode: MongoOpCode, request_id: i32, body: Vec<u8>) -> Self {
let message_length = 16 + body.len() as i32;
Self {
header: MongoMsgHeader {
message_length,
request_id,
response_to: 0,
},
opcode,
body,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = self.header.to_bytes();
buf.extend_from_slice(&(self.opcode as i32).to_le_bytes());
buf.extend_from_slice(&self.body);
buf
}
}
pub struct BsonDocument {
elements: Vec<(String, BsonValue)>,
}
impl BsonDocument {
pub fn new() -> Self {
Self { elements: Vec::new() }
}
pub fn append(&mut self, key: &str, value: BsonValue) {
self.elements.push((key.to_string(), value));
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&[0u8; 4]);
for (key, value) in &self.elements {
match value {
BsonValue::Double(d) => {
buf.push(BsonTypeTag::DOUBLE);
buf.extend_from_slice(key.as_bytes());
buf.push(0x00); buf.extend_from_slice(&d.to_le_bytes());
}
BsonValue::String(s) => {
buf.push(BsonTypeTag::STRING);
buf.extend_from_slice(key.as_bytes());
buf.push(0x00);
let sbytes = s.as_bytes();
buf.extend_from_slice(&(sbytes.len() as i32 + 1).to_le_bytes());
buf.extend_from_slice(sbytes);
buf.push(0x00);
}
BsonValue::Int32(i) => {
buf.push(BsonTypeTag::INT32);
buf.extend_from_slice(key.as_bytes());
buf.push(0x00);
buf.extend_from_slice(&i.to_le_bytes());
}
BsonValue::Int64(i) => {
buf.push(BsonTypeTag::INT64);
buf.extend_from_slice(key.as_bytes());
buf.push(0x00);
buf.extend_from_slice(&i.to_le_bytes());
}
BsonValue::Boolean(b) => {
buf.push(BsonTypeTag::BOOLEAN);
buf.extend_from_slice(key.as_bytes());
buf.push(0x00);
buf.push(if *b { 0x01 } else { 0x00 });
}
BsonValue::Null => {
buf.push(BsonTypeTag::NULL);
buf.extend_from_slice(key.as_bytes());
buf.push(0x00);
}
BsonValue::ObjectId(id) => {
buf.push(BsonTypeTag::OBJECT_ID);
buf.extend_from_slice(key.as_bytes());
buf.push(0x00);
buf.extend_from_slice(id);
}
_ => {
buf.push(BsonTypeTag::NULL);
buf.extend_from_slice(key.as_bytes());
buf.push(0x00);
}
}
}
buf.push(0x00); let total_len = buf.len() as i32;
let len_bytes = total_len.to_le_bytes();
buf[0..4].copy_from_slice(&len_bytes);
buf
}
pub fn build_find(collection: &str, filter: BsonDocument) -> MongoMessage {
let mut body = Vec::new();
body.extend_from_slice(&0i32.to_le_bytes()); body.extend_from_slice(collection.as_bytes());
body.push(0x00);
body.extend_from_slice(&0i32.to_le_bytes()); body.extend_from_slice(&0i32.to_le_bytes()); body.extend_from_slice(&filter.to_bytes());
MongoMessage::new(MongoOpCode::Query, 1, body)
}
pub fn build_insert(collection: &str, documents: Vec<BsonDocument>) -> MongoMessage {
let mut body = Vec::new();
body.extend_from_slice(&0i32.to_le_bytes()); body.extend_from_slice(collection.as_bytes());
body.push(0x00);
for doc in documents {
body.extend_from_slice(&doc.to_bytes());
}
MongoMessage::new(MongoOpCode::Insert, 1, body)
}
}
impl Default for BsonDocument {
fn default() -> Self { Self::new() }
}
#[derive(Debug, Clone)]
pub struct DatabaseRegistry {
pub libraries: Vec<Box<dyn DatabaseLibraryRegistry>>,
pub results: HashMap<String, DatabaseCompileResult>,
}
pub trait DatabaseLibraryRegistry: fmt::Debug {
fn library_name(&self) -> &str;
fn compile(&self) -> DatabaseCompileResult;
fn run_tests(&self) -> DatabaseTestResults;
}
macro_rules! impl_db_registry {
($ty:ty) => {
impl DatabaseLibraryRegistry for $ty {
fn library_name(&self) -> &str { DatabaseLibrary::library_name(self) }
fn compile(&self) -> DatabaseCompileResult { DatabaseLibrary::compile(self) }
fn run_tests(&self) -> DatabaseTestResults { DatabaseLibrary::run_tests(self) }
}
};
}
impl_db_registry!(PostgresProject);
impl_db_registry!(MySQLProject);
impl_db_registry!(SqliteProject);
impl_db_registry!(LmdbProject);
impl_db_registry!(RocksDbProject);
impl_db_registry!(LevelDbProject);
impl_db_registry!(BerkeleyDbProject);
impl_db_registry!(UnqliteProject);
impl DatabaseRegistry {
pub fn new() -> Self {
Self {
libraries: Vec::new(),
results: HashMap::new(),
}
}
pub fn default_registry() -> Self {
let mut reg = Self::new();
reg.add_library(Box::new(PostgresProject::new("16.1")));
reg.add_library(Box::new(MySQLProject::new("8.3", MysqlVariant::MySQL)));
reg.add_library(Box::new(SqliteProject::new("3450000")));
reg.add_library(Box::new(LmdbProject::new("0.9.31")));
reg.add_library(Box::new(RocksDbProject::new("8.10")));
reg.add_library(Box::new(LevelDbProject::new("1.23")));
reg.add_library(Box::new(BerkeleyDbProject::new("18.1")));
reg.add_library(Box::new(UnqliteProject::new("1.1.9")));
reg
}
pub fn add_library(&mut self, lib: Box<dyn DatabaseLibraryRegistry>) {
self.libraries.push(lib);
}
pub fn compile_all(&mut self) -> Vec<DatabaseCompileResult> {
let mut results = Vec::new();
for lib in &self.libraries {
let result = lib.compile();
self.results.insert(lib.library_name().to_string(), result.clone());
results.push(result);
}
results
}
pub fn run_all_tests(&self) -> Vec<DatabaseTestResults> {
let mut results = Vec::new();
for lib in &self.libraries {
results.push(lib.run_tests());
}
results
}
pub fn total_files(&self) -> usize {
self.results.values().map(|r| r.files_compiled).sum()
}
pub fn all_successful(&self) -> bool {
self.results.values().all(|r| r.success)
}
}
impl Default for DatabaseRegistry {
fn default() -> Self {
Self::default_registry()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_postgres_project_creation() {
let project = PostgresProject::new("16.1");
assert_eq!(project.library_name(), "libpq");
assert!(project.libpq_sources().len() > 40);
}
#[test]
fn test_postgres_compile() {
let project = PostgresProject::new("16.1");
let result = project.compile();
assert!(result.success);
assert_eq!(result.storage_type.unwrap(), "relational-sql");
}
#[test]
fn test_postgres_run_tests() {
let project = PostgresProject::new("16.1");
let results = project.run_tests();
assert_eq!(results.passed, 8);
assert!(results.tests.iter().any(|t| t.name == "pq_connect"));
}
#[test]
fn test_mysql_project_creation() {
let project = MySQLProject::new("8.3", MysqlVariant::MySQL);
assert_eq!(project.library_name(), "libmysqlclient");
assert!(project.mysql_sources().len() > 50);
}
#[test]
fn test_mysql_variants() {
assert_eq!(
MySQLProject::new("10.11", MysqlVariant::MariaDB).library_name(),
"libmariadb"
);
assert_eq!(
MySQLProject::new("8.0", MysqlVariant::Percona).library_name(),
"libperconaserverclient"
);
}
#[test]
fn test_mysql_compile() {
let project = MySQLProject::new("8.3", MysqlVariant::MySQL);
let result = project.compile();
assert!(result.success);
}
#[test]
fn test_sqlite_project_creation() {
let project = SqliteProject::new("3450000");
assert_eq!(project.library_name(), "SQLite");
assert!(project.with_fts5);
assert!(project.with_json1);
}
#[test]
fn test_sqlite_defines_with_extensions() {
let project = SqliteProject::new("3450000");
let defs = project.sqlite_defines();
assert!(defs.iter().any(|(k, _)| k == "SQLITE_ENABLE_FTS5"));
assert!(defs.iter().any(|(k, _)| k == "SQLITE_ENABLE_RTREE"));
assert!(defs.iter().any(|(k, _)| k == "SQLITE_ENABLE_JSON1"));
assert!(defs.iter().any(|(k, _)| k == "SQLITE_ENABLE_GEOPOLY"));
}
#[test]
fn test_sqlite_compile() {
let project = SqliteProject::new("3450000");
let result = project.compile();
assert!(result.success);
assert_eq!(result.files_compiled, 1); }
#[test]
fn test_sqlite_run_tests() {
let project = SqliteProject::new("3450000");
let results = project.run_tests();
assert_eq!(results.passed, 10);
assert!(results.tests.iter().any(|t| t.name == "sqlite_fts5_search"));
}
#[test]
fn test_lmdb_project_creation() {
let project = LmdbProject::new("0.9.31");
assert_eq!(project.library_name(), "LMDB");
assert!(project.lmdb_sources().len() == 2);
}
#[test]
fn test_lmdb_compile() {
let project = LmdbProject::new("0.9.31");
let result = project.compile();
assert!(result.success);
}
#[test]
fn test_rocksdb_project_creation() {
let project = RocksDbProject::new("8.10");
assert_eq!(project.library_name(), "RocksDB");
assert!(project.rocksdb_sources().len() > 50);
}
#[test]
fn test_rocksdb_compile() {
let project = RocksDbProject::new("8.10");
let result = project.compile();
assert!(result.success);
assert_eq!(result.storage_type.unwrap(), "lsm-tree");
}
#[test]
fn test_leveldb_project_creation() {
let project = LevelDbProject::new("1.23");
assert_eq!(project.library_name(), "LevelDB");
assert!(project.leveldb_sources().len() > 30);
}
#[test]
fn test_leveldb_compile() {
let project = LevelDbProject::new("1.23");
let result = project.compile();
assert!(result.success);
}
#[test]
fn test_berkeleydb_project_creation() {
let project = BerkeleyDbProject::new("18.1");
assert_eq!(project.library_name(), "BerkeleyDB");
assert!(project.bdb_sources().len() > 80);
}
#[test]
fn test_berkeleydb_compile() {
let project = BerkeleyDbProject::new("18.1");
let result = project.compile();
assert!(result.success);
}
#[test]
fn test_unqlite_project_creation() {
let project = UnqliteProject::new("1.1.9");
assert_eq!(project.library_name(), "unqlite");
assert_eq!(project.unqlite_sources().len(), 1);
}
#[test]
fn test_unqlite_compile() {
let project = UnqliteProject::new("1.1.9");
let result = project.compile();
assert!(result.success);
}
#[test]
fn test_resp_string_serialize() {
let val = RespValue::ok();
let wire = val.to_wire();
assert_eq!(wire, b"+OK\r\n");
}
#[test]
fn test_resp_error_serialize() {
let val = RespValue::Error("ERR bad command".to_string());
let wire = val.to_wire();
assert_eq!(wire, b"-ERR bad command\r\n");
}
#[test]
fn test_resp_integer_serialize() {
let val = RespValue::Integer(42);
let wire = val.to_wire();
assert_eq!(wire, b":42\r\n");
}
#[test]
fn test_resp_bulk_string_serialize() {
let val = RespValue::bulk_string(b"hello");
let wire = val.to_wire();
assert_eq!(wire, b"$5\r\nhello\r\n");
}
#[test]
fn test_resp_nil_bulk_serialize() {
let val = RespValue::nil_bulk();
let wire = val.to_wire();
assert_eq!(wire, b"$-1\r\n");
}
#[test]
fn test_resp_null_serialize() {
let val = RespValue::Null;
let wire = val.to_wire();
assert_eq!(wire, b"_\r\n");
}
#[test]
fn test_resp_boolean_serialize() {
assert_eq!(RespValue::Boolean(true).to_wire(), b"#t\r\n");
assert_eq!(RespValue::Boolean(false).to_wire(), b"#f\r\n");
}
#[test]
fn test_resp_ping_command() {
let cmd = RedisCommand::ping();
assert_eq!(cmd, b"*1\r\n$4\r\nPING\r\n");
}
#[test]
fn test_resp_set_command() {
let cmd = RedisCommand::set("mykey", "myvalue");
let expected = b"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n";
assert_eq!(cmd, expected);
}
#[test]
fn test_resp_get_command() {
let cmd = RedisCommand::get("mykey");
assert_eq!(cmd, b"*2\r\n$3\r\nGET\r\n$5\r\nmykey\r\n");
}
#[test]
fn test_resp_hset_command() {
let cmd = RedisCommand::hset("users", "name", "Alice");
let wire = RespValue::Array(Some(vec![
RespValue::bulk_string(b"HSET"),
RespValue::bulk_string(b"users"),
RespValue::bulk_string(b"name"),
RespValue::bulk_string(b"Alice"),
])).to_wire();
assert_eq!(cmd, wire);
}
#[test]
fn test_resp_array_serialize() {
let arr = RespValue::Array(Some(vec![
RespValue::bulk_string(b"SET"),
RespValue::bulk_string(b"key"),
RespValue::bulk_string(b"val"),
]));
let wire = arr.to_wire();
assert!(wire.starts_with(b"*3\r\n"));
}
#[test]
fn test_memcached_set_command() {
let cmd = MemcachedProtocol::set_command("mykey", 0, 3600, b"value", false);
assert!(cmd.starts_with("set mykey 0 3600 5\r\n"));
assert!(cmd.ends_with("value\r\n"));
}
#[test]
fn test_memcached_get_command() {
let cmd = MemcachedProtocol::get_command(&["key1", "key2"]);
assert_eq!(cmd, "get key1 key2\r\n");
}
#[test]
fn test_memcached_delete_command() {
let cmd = MemcachedProtocol::delete_command("mykey", true);
assert_eq!(cmd, "delete mykey noreply\r\n");
}
#[test]
fn test_memcached_incr_command() {
let cmd = MemcachedProtocol::incr_command("counter", 5, false);
assert_eq!(cmd, "incr counter 5\r\n");
}
#[test]
fn test_memcached_stats_version() {
assert_eq!(MemcachedProtocol::stats_command(), "stats\r\n");
assert_eq!(MemcachedProtocol::version_command(), "version\r\n");
}
#[test]
fn test_memcached_parse_response() {
assert_eq!(MemcachedProtocol::parse_response("STORED").unwrap(), MemcachedResponse::Stored);
assert_eq!(MemcachedProtocol::parse_response("NOT_FOUND").unwrap(), MemcachedResponse::NotFound);
assert_eq!(MemcachedProtocol::parse_response("DELETED").unwrap(), MemcachedResponse::Deleted);
assert_eq!(MemcachedProtocol::parse_response("END").unwrap(), MemcachedResponse::End);
}
#[test]
fn test_mongo_opcode_values() {
assert_eq!(MongoOpCode::Reply as i32, 1);
assert_eq!(MongoOpCode::Query as i32, 2004);
assert_eq!(MongoOpCode::Insert as i32, 2002);
}
#[test]
fn test_mongo_opcode_from_i32() {
assert_eq!(MongoOpCode::from_i32(2004), Some(MongoOpCode::Query));
assert_eq!(MongoOpCode::from_i32(2013), Some(MongoOpCode::Msg));
assert_eq!(MongoOpCode::from_i32(9999), None);
}
#[test]
fn test_bson_document_simple() {
let mut doc = BsonDocument::new();
doc.append("name", BsonValue::String("Alice".to_string()));
doc.append("age", BsonValue::Int32(30));
let bytes = doc.to_bytes();
assert!(bytes.len() > 4);
}
#[test]
fn test_bson_build_find() {
let mut filter = BsonDocument::new();
filter.append("status", BsonValue::String("active".to_string()));
let msg = BsonDocument::build_find("users", filter);
assert_eq!(msg.opcode, MongoOpCode::Query);
}
#[test]
fn test_bson_build_insert() {
let mut doc = BsonDocument::new();
doc.append("name", BsonValue::String("test".to_string()));
let _msg = BsonDocument::build_insert("collection", vec![doc]);
}
#[test]
fn test_mongo_message_header() {
let header = MongoMsgHeader::new(1);
let bytes = header.to_bytes();
assert_eq!(bytes.len(), 12);
}
#[test]
fn test_bson_type_tags() {
assert_eq!(BsonTypeTag::DOUBLE, 0x01);
assert_eq!(BsonTypeTag::STRING, 0x02);
assert_eq!(BsonTypeTag::DOCUMENT, 0x03);
assert_eq!(BsonTypeTag::INT32, 0x10);
assert_eq!(BsonTypeTag::INT64, 0x12);
}
#[test]
fn test_database_registry_default() {
let reg = DatabaseRegistry::default_registry();
assert_eq!(reg.libraries.len(), 8);
}
#[test]
fn test_database_registry_compile_all() {
let mut reg = DatabaseRegistry::default_registry();
let results = reg.compile_all();
assert_eq!(results.len(), 8);
assert!(results.iter().all(|r| r.success));
}
#[test]
fn test_database_registry_run_all_tests() {
let reg = DatabaseRegistry::default_registry();
let results = reg.run_all_tests();
assert_eq!(results.len(), 8);
assert!(results.iter().all(|r| r.failed == 0));
}
#[test]
fn test_database_registry_total_files() {
let mut reg = DatabaseRegistry::default_registry();
reg.compile_all();
assert!(reg.total_files() > 100);
}
#[test]
fn test_redis_command_all() {
assert!(!RedisCommand::ping().is_empty());
assert!(!RedisCommand::set("k", "v").is_empty());
assert!(!RedisCommand::get("k").is_empty());
assert!(!RedisCommand::del(&["k"]).is_empty());
assert!(!RedisCommand::hset("h", "f", "v").is_empty());
assert!(!RedisCommand::hget("h", "f").is_empty());
assert!(!RedisCommand::lpush("list", &["a"]).is_empty());
assert!(!RedisCommand::zadd("z", 1.0, "m").is_empty());
assert!(!RedisCommand::info().is_empty());
assert!(!RedisCommand::publish("ch", "msg").is_empty());
}
}