#![allow(unused_imports)] #![allow(dead_code)] mod common; mod database_upgrade { use super::common::docker::DockerContainer; use super::common::expected::Expected; use super::common::rest_client::RestClient; use serde_json::Value as JsonValue; use serial_test::serial; use std::time::Duration; use surrealdb::engine::any::{connect, Any}; use surrealdb::{Connection, Surreal}; use test_log::test; use tracing::info; use ulid::Ulid; const CNX_TIMEOUT: Duration = Duration::from_secs(180); const NS: &str = "test"; const DB: &str = "test"; const USER: &str = "root"; const PASS: &str = "root"; async fn upgrade_test_1_0(version: &str) { // Start the docker instance let (path, mut docker, client) = start_docker(version).await; // Create the data set create_data_on_docker(&client, "FTS", &DATA_FTS).await; // Check the data set check_data_on_docker(&client, "FTS", &CHECK_FTS).await; check_data_on_docker(&client, "DB", &CHECK_DB).await; // Stop the docker instance docker.stop(); // Extract the database directory docker.extract_data_dir(&path); // Connect to a local instance let db = new_local_instance(&path).await; // Check that the data has properly migrated check_migrated_data(&db, "DB", &CHECK_DB).await; check_migrated_data(&db, "FTS", &CHECK_FTS).await; } #[test(tokio::test(flavor = "multi_thread"))] #[cfg(feature = "storage-rocksdb")] #[serial] async fn upgrade_test_1_0_0() { upgrade_test_1_0("1.0.0").await; } #[test(tokio::test(flavor = "multi_thread"))] #[cfg(feature = "storage-rocksdb")] #[serial] async fn upgrade_test_1_0_1() { upgrade_test_1_0("1.0.1").await; } async fn upgrade_test_1_1(version: &str) { // Start the docker instance let (path, mut docker, client) = start_docker(version).await; // Create the data set create_data_on_docker(&client, "FTS", &DATA_FTS).await; create_data_on_docker(&client, "MTREE", &DATA_MTREE).await; // Check the data set check_data_on_docker(&client, "DB", &CHECK_DB).await; check_data_on_docker(&client, "FTS", &CHECK_FTS).await; check_data_on_docker(&client, "MTREE", &CHECK_MTREE_RPC).await; // Stop the docker instance docker.stop(); // Extract the database directory docker.extract_data_dir(&path); // Connect to a local instance let db = new_local_instance(&path).await; // Check that the data has properly migrated check_migrated_data(&db, "DB", &CHECK_DB).await; check_migrated_data(&db, "FTS", &CHECK_FTS).await; check_migrated_data(&db, "MTREE", &CHECK_MTREE_DB).await; } #[test(tokio::test(flavor = "multi_thread"))] #[cfg(feature = "storage-rocksdb")] #[serial] async fn upgrade_test_1_1_0() { upgrade_test_1_1("v1.1.0").await; } #[test(tokio::test(flavor = "multi_thread"))] #[cfg(feature = "storage-rocksdb")] #[serial] async fn upgrade_test_1_1_1() { upgrade_test_1_1("v1.1.1").await; } async fn upgrade_test_1_2(version: &str) { // Start the docker instance let (path, mut docker, client) = start_docker(version).await; // Create the data set create_data_on_docker(&client, "FTS", &DATA_FTS).await; create_data_on_docker(&client, "MTREE", &DATA_MTREE).await; // Check the data set check_data_on_docker(&client, "DB", &CHECK_DB).await; check_data_on_docker(&client, "FTS", &CHECK_FTS).await; check_data_on_docker(&client, "MTREE", &CHECK_MTREE_RPC).await; // Stop the docker instance docker.stop(); // Extract the database directory docker.extract_data_dir(&path); // Connect to a local instance let db = new_local_instance(&path).await; // Check that the data has properly migrated check_migrated_data(&db, "DB", &CHECK_DB).await; check_migrated_data(&db, "FTS", &CHECK_FTS).await; check_migrated_data(&db, "MTREE", &CHECK_MTREE_DB).await; check_migrated_data(&db, "KNN_BRUTEFORCE", &CHECK_KNN_BRUTEFORCE).await; } #[test(tokio::test(flavor = "multi_thread"))] #[cfg(feature = "storage-rocksdb")] #[serial] async fn upgrade_test_1_2_0() { upgrade_test_1_2("v1.2.0").await; } #[test(tokio::test(flavor = "multi_thread"))] #[cfg(feature = "storage-rocksdb")] #[serial] async fn upgrade_test_1_2_1() { upgrade_test_1_2("v1.2.1").await; } // ******* // DATASET // ******* // Set of DATA for Full Text Search const DATA_FTS: [&str; 5] = [ "DEFINE ANALYZER name TOKENIZERS class FILTERS lowercase,ngram(1,128)", "DEFINE ANALYZER userdefinedid TOKENIZERS blank FILTERS lowercase,ngram(1,32)", "DEFINE INDEX account_name_search_idx ON TABLE account COLUMNS name SEARCH ANALYZER name BM25(1.2,0.75) HIGHLIGHTS", "DEFINE INDEX account_user_defined_id_search_idx ON TABLE account COLUMNS user_defined_id SEARCH ANALYZER userdefinedid BM25 HIGHLIGHTS", "CREATE account SET name='Tobie', user_defined_id='Tobie'", ]; // Set of QUERY and RESULT to check for Full Text Search const CHECK_FTS: [Check; 1] = [( "SELECT search::highlight('','', 1) AS name FROM account WHERE name @1@ 'Tobie'", Expected::One("{\"name\":\"Tobie\"}"), )]; // Set of DATA for VectorSearch and Knn Operator checking const DATA_MTREE: [&str; 4] = [ "CREATE pts:1 SET point = [1,2,3,4]", "CREATE pts:2 SET point = [4,5,6,7]", "CREATE pts:3 SET point = [8,9,10,11]", "DEFINE INDEX mt_pts ON pts FIELDS point MTREE DIMENSION 4", ]; const CHECK_MTREE_RPC: [Check; 1] = [ ("SELECT id, vector::distance::euclidean(point, [2,3,4,5]) AS dist FROM pts WHERE point <2> [2,3,4,5]", Expected::Two("{\"dist\": 2.0, \"id\": \"pts:1\"}", "{ \"dist\": 4.0, \"id\": \"pts:2\"}"))]; const CHECK_MTREE_DB: [Check; 1] = [ ("SELECT id, vector::distance::euclidean(point, [2,3,4,5]) AS dist FROM pts WHERE point <2> [2,3,4,5]", Expected::Two("{\"dist\": 2.0, \"id\": {\"tb\": \"pts\", \"id\": {\"Number\": 1}}}", "{ \"dist\": 4.0, \"id\": {\"tb\": \"pts\", \"id\": {\"Number\": 2}}}"))]; const CHECK_KNN_BRUTEFORCE: [Check; 1] = [ ("SELECT id, vector::distance::euclidean(point, [2,3,4,5]) AS dist FROM pts WHERE point <2,EUCLIDEAN> [2,3,4,5]", Expected::Two("{\"dist\": 2.0, \"id\": {\"tb\": \"pts\", \"id\": {\"Number\": 1}}}", "{ \"dist\": 4.0, \"id\": {\"tb\": \"pts\", \"id\": {\"Number\": 2}}}"))]; type Check = (&'static str, Expected); const CHECK_DB: [Check; 1] = [("INFO FOR DB", Expected::Any)]; // ******* // HELPERS // ******* async fn start_docker(docker_version: &str) -> (String, DockerContainer, RestClient) { // Location of the database files (RocksDB) in the Host let file_path = format!("/tmp/{}.db", Ulid::new()); let docker = DockerContainer::start(docker_version, &file_path, USER, PASS); let client = RestClient::new(NS, DB, USER, PASS) .wait_for_connection(&CNX_TIMEOUT) .await .unwrap_or_else(|| { docker.logs(); panic!("No connected client") }); (file_path, docker, client) } async fn create_data_on_docker(client: &RestClient, info: &str, data: &[&str]) { info!("Create {info} data on Docker's instance"); for l in data { client.checked_query(l, &Expected::Any).await; } } async fn check_data_on_docker(client: &RestClient, info: &str, queries: &[Check]) { info!("Check {info} data on Docker's instance"); for (query, expected) in queries { client.checked_query(query, expected).await; } } async fn check_migrated_data(db: &Surreal, info: &str, queries: &[Check]) { info!("Check migrated {info} data"); for (query, expected_results) in queries { checked_query(db, query, expected_results).await; } } // Executes the query and ensures to print out the query if it does not pass async fn checked_query(db: &Surreal, q: &str, expected: &Expected) where C: Connection, { let mut res = db.query(q).await.expect(q).check().expect(q); assert_eq!(res.num_statements(), 1, "Wrong number of result on query {q}"); let results: Vec = res.take(0).unwrap(); expected.check_results(q, &results); } async fn new_local_instance(file_path: &String) -> Surreal { let db = connect(format!("file:{}", file_path)).await.unwrap(); db.use_ns(NS).await.unwrap(); db.use_db(DB).await.unwrap(); db } }