diff --git a/.circleci/config.yml b/.circleci/config.yml index f28e253a8e..7a99a0c0d8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -831,32 +831,32 @@ workflows: - deploy-snapshot-linux-arm64: filters: branches: - only: [development, master, "3.0"] + only: [development, master] - deploy-snapshot-linux-x86_64: filters: branches: - only: [development, master, "3.0"] + only: [development, master] - deploy-snapshot-mac-arm64: filters: branches: - only: [development, master, "3.0"] + only: [development, master] - deploy-snapshot-mac-x86_64: filters: branches: - only: [development, master, "3.0"] + only: [development, master] - deploy-snapshot-windows-x86_64: filters: branches: - only: [development, master, "3.0"] + only: [development, master] - deploy-snapshot-any: filters: branches: - only: [development, master, "3.0"] + only: [development, master] requires: - deploy-snapshot-linux-arm64 - deploy-snapshot-linux-x86_64 @@ -878,7 +878,7 @@ workflows: - test-snapshot-linux-arm64: filters: branches: - only: [master, "3.0"] + only: [master] requires: - deploy-snapshot-linux-arm64 - deploy-snapshot-any @@ -887,7 +887,7 @@ workflows: - test-snapshot-linux-x86_64: filters: branches: - only: [master, "3.0"] + only: [master] requires: - deploy-snapshot-linux-x86_64 - deploy-snapshot-any @@ -896,7 +896,7 @@ workflows: - test-snapshot-mac-arm64: filters: branches: - only: [master, "3.0"] + only: [master] requires: - deploy-snapshot-mac-arm64 - deploy-snapshot-any @@ -905,27 +905,26 @@ workflows: - test-snapshot-mac-x86_64: filters: branches: - only: [master, "3.0"] + only: [master] requires: - deploy-snapshot-mac-x86_64 - deploy-snapshot-any # - deploy-snapshot-dotnet-any -# TODO: Windows typedb artifact is not ready -# - test-snapshot-windows-x86_64: -# filters: -# branches: -# only: [master, "3.0"] -# requires: -# - deploy-snapshot-windows-x86_64 -# - deploy-snapshot-any + - test-snapshot-windows-x86_64: + filters: + branches: + only: [master] + requires: + - deploy-snapshot-windows-x86_64 + - deploy-snapshot-any # - deploy-snapshot-dotnet-any # TODO: npm is not ready # - test-snapshot-any: # filters: # branches: -# only: [master, "3.0"] +# only: [master] # requires: # - deploy-snapshot-any diff --git a/.factory/automation.yml b/.factory/automation.yml index 06d2bc6bea..497d93926e 100644 --- a/.factory/automation.yml +++ b/.factory/automation.yml @@ -87,8 +87,8 @@ build: tool/docs/update_readme.sh git add . - git diff --exit-code || { - echo "Failed to verify README files: plese update it manually and verify the changes" + git diff --exit-code || { + echo "Failed to verify README files: please update it manually and verify the changes" exit 1 } @@ -111,7 +111,7 @@ build: if [[ -n "$COMMUNITY_FAILED" ]]; then exit 1; fi # TODO: Use cluster server artifact with 3 nodes for the same common tests when available - # tool/test/start-cluster-servers.sh 3 && + # source tool/test/start-cluster-servers.sh 3 && # bazel test //rust/tests/integration:all --test_output=streamed --test_arg=--nocapture && # export CLUSTER_FAILED= || export CLUSTER_FAILED=1 # tool/test/stop-cluster-servers.sh @@ -133,22 +133,22 @@ build: tool/test/stop-community-server.sh exit $TEST_SUCCESS - # TODO: Use cluster server artifact with 3 nodes when available (it would do the same thing as community now) - # test-rust-behaviour-cluster: - # image: typedb-ubuntu-20.04 # Ubuntu 20.04 has GLIBC version 2.31 (2020) which we should verify to compile against - # dependencies: - # - build - # command: | - # export ARTIFACT_USERNAME=$REPO_TYPEDB_USERNAME - # export ARTIFACT_PASSWORD=$REPO_TYPEDB_PASSWORD - # bazel run @typedb_dependencies//tool/bazelinstall:remote_cache_setup.sh - # bazel run @typedb_dependencies//distribution/artifact:create-netrc - # - # tool/test/start-cluster-servers.sh 3 && - # bazel test //rust/tests/behaviour/... --//rust/tests/behaviour/config:mode=cluster --test_output=streamed --jobs=1 && - # export TEST_SUCCESS=0 || export TEST_SUCCESS=1 - # tool/test/stop-cluster-servers.sh - # exit $TEST_SUCCESS + # TODO: Use cluster server artifact with 3 nodes when available (it currently uses 1) + test-rust-behaviour-cluster: + image: typedb-ubuntu-20.04 # Ubuntu 20.04 has GLIBC version 2.31 (2020) which we should verify to compile against + dependencies: + - build + command: | + export ARTIFACT_USERNAME=$REPO_TYPEDB_USERNAME + export ARTIFACT_PASSWORD=$REPO_TYPEDB_PASSWORD + bazel run @typedb_dependencies//tool/bazelinstall:remote_cache_setup.sh + bazel run @typedb_dependencies//distribution/artifact:create-netrc + + source tool/test/start-cluster-servers.sh 1 && + bazel test //rust/tests/behaviour/... --test_env=ROOT_CA=$ROOT_CA --//rust/tests/behaviour/config:mode=cluster --test_output=streamed --jobs=1 && + export TEST_SUCCESS=0 || export TEST_SUCCESS=1 + tool/test/stop-cluster-servers.sh + exit $TEST_SUCCESS # test-c-integration: # image: typedb-ubuntu-20.04 # Ubuntu 20.04 has GLIBC version 2.31 (2020) which we should verify to compile against @@ -182,7 +182,7 @@ build: if [[ -n "$COMMUNITY_FAILED" ]]; then exit 1; fi # TODO: Use cluster server artifact with 3 nodes for the same common tests when available - # tool/test/start-cluster-servers.sh 3 && + # source tool/test/start-cluster-servers.sh 3 && # bazel test //java/test/integration:all --test_output=streamed --jobs=1 && # export CLUSTER_FAILED= || export CLUSTER_FAILED=1 # tool/test/stop-cluster-servers.sh @@ -204,22 +204,22 @@ build: tool/test/stop-community-server.sh exit $TEST_SUCCESS - # TODO: Use cluster server artifact with 3 nodes when available (it would do the same thing as community now) - # test-java-behaviour-cluster: - # image: typedb-ubuntu-22.04 - # dependencies: - # - build - # command: | - # export ARTIFACT_USERNAME=$REPO_TYPEDB_USERNAME - # export ARTIFACT_PASSWORD=$REPO_TYPEDB_PASSWORD - # bazel run @typedb_dependencies//tool/bazelinstall:remote_cache_setup.sh - # bazel run @typedb_dependencies//distribution/artifact:create-netrc - # - # tool/test/start-cluster-servers.sh 3 && - # .factory/test-cluster.sh //java/test/behaviour/... --test_output=streamed --jobs=1 && - # export TEST_SUCCESS=0 || export TEST_SUCCESS=1 - # tool/test/stop-cluster-servers.sh - # exit $TEST_SUCCESS + # TODO: Use cluster server artifact with 3 nodes when available (it currently uses 1) + test-java-behaviour-cluster: + image: typedb-ubuntu-22.04 + dependencies: + - build + command: | + export ARTIFACT_USERNAME=$REPO_TYPEDB_USERNAME + export ARTIFACT_PASSWORD=$REPO_TYPEDB_PASSWORD + bazel run @typedb_dependencies//tool/bazelinstall:remote_cache_setup.sh + bazel run @typedb_dependencies//distribution/artifact:create-netrc + + source tool/test/start-cluster-servers.sh 1 && + .factory/test-cluster.sh //java/test/behaviour/... --test_env=ROOT_CA=$ROOT_CA --test_output=streamed --jobs=1 && + export TEST_SUCCESS=0 || export TEST_SUCCESS=1 + tool/test/stop-cluster-servers.sh + exit $TEST_SUCCESS test-python-integration: image: typedb-ubuntu-22.04 @@ -240,7 +240,7 @@ build: if [[ -n "$COMMUNITY_FAILED" ]]; then exit 1; fi # TODO: Use cluster server artifact with 3 nodes when available - # tool/test/start-cluster-servers.sh 3 && + # source tool/test/start-cluster-servers.sh 3 && # bazel test //python/tests/integration:all --test_output=streamed --jobs=1 && # export CLUSTER_FAILED= || export CLUSTER_FAILED=1 # tool/test/stop-cluster-servers.sh @@ -264,24 +264,24 @@ build: tool/test/stop-community-server.sh exit $TEST_SUCCESS - # TODO: Use cluster server artifact with 3 nodes when available (it would do the same thing as community now) - # test-python-behaviour-cluster: - # image: typedb-ubuntu-22.04 - # dependencies: - # - build - # type: foreground - # command: | - # export PATH="$HOME/.local/bin:$PATH" - # export ARTIFACT_USERNAME=$REPO_TYPEDB_USERNAME - # export ARTIFACT_PASSWORD=$REPO_TYPEDB_PASSWORD - # bazel run @typedb_dependencies//tool/bazelinstall:remote_cache_setup.sh - # bazel run @typedb_dependencies//distribution/artifact:create-netrc - # - # tool/test/start-cluster-servers.sh 3 && - # .factory/test-cluster.sh //python/tests/behaviour/... --test_output=streamed --jobs=1 && - # export TEST_SUCCESS=0 || export TEST_SUCCESS=1 - # tool/test/stop-cluster-servers.sh - # exit $TEST_SUCCESS + # TODO: Use cluster server artifact with 3 nodes when available (it currently uses 1) + test-python-behaviour-cluster: + image: typedb-ubuntu-22.04 + dependencies: + - build + type: foreground + command: | + export PATH="$HOME/.local/bin:$PATH" + export ARTIFACT_USERNAME=$REPO_TYPEDB_USERNAME + export ARTIFACT_PASSWORD=$REPO_TYPEDB_PASSWORD + bazel run @typedb_dependencies//tool/bazelinstall:remote_cache_setup.sh + bazel run @typedb_dependencies//distribution/artifact:create-netrc + + source tool/test/start-cluster-servers.sh 1 && + .factory/test-cluster.sh //python/tests/behaviour/... --test_env=ROOT_CA=$ROOT_CA --test_output=streamed --jobs=1 && + export TEST_SUCCESS=0 || export TEST_SUCCESS=1 + tool/test/stop-cluster-servers.sh + exit $TEST_SUCCESS # test-nodejs-integration: # image: typedb-ubuntu-22.04 diff --git a/Cargo.lock b/Cargo.lock index 1890760f27..527c2e32d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1397,10 +1397,11 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -1464,9 +1465,9 @@ dependencies = [ [[package]] name = "macro_rules_attribute" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a82271f7bc033d84bbca59a3ce3e4159938cb08a9c3aebbe54d215131518a13" +checksum = "65049d7923698040cd0b1ddcced9b0eb14dd22c5f86ae59c3740eab64a676520" dependencies = [ "macro_rules_attribute-proc_macro", "paste", @@ -1474,9 +1475,9 @@ dependencies = [ [[package]] name = "macro_rules_attribute-proc_macro" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dd856d451cc0da70e2ef2ce95a18e39a93b7558bedf10201ad28503f918568" +checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30" [[package]] name = "matchit" @@ -1899,9 +1900,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.13.2" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60caa6738c7369b940c3d49246a8d1749323674c65cb13010134f5c9bad5b519" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" dependencies = [ "prost", ] @@ -2753,7 +2754,7 @@ dependencies = [ [[package]] name = "typedb-protocol" version = "0.0.0" -source = "git+https://github.com/typedb/typedb-protocol?rev=38f66a1cc4db3b7a301676f50800e9530ac5c8a3#38f66a1cc4db3b7a301676f50800e9530ac5c8a3" +source = "git+https://github.com/typedb/typedb-protocol?rev=d1b71067c7e78364c69701625ea3f6d6b1f24a9d#d1b71067c7e78364c69701625ea3f6d6b1f24a9d" dependencies = [ "prost", "tonic", @@ -2818,13 +2819,15 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ "getrandom 0.3.1", + "js-sys", "rand 0.9.0", "serde", + "wasm-bindgen", ] [[package]] @@ -2881,24 +2884,24 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn 2.0.87", @@ -2919,9 +2922,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2929,9 +2932,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -2942,9 +2945,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" diff --git a/c/README.md b/c/README.md index efe4d17554..cc64790257 100644 --- a/c/README.md +++ b/c/README.md @@ -22,13 +22,13 @@ Code examples can be found in the [integration tests](https://github.com/typedb/ Functions parameters & return values are either primitives or pointers to opaque structs, e.g.: ```c -Driver *driver_open_core(const char *address); +Driver *driver_new_core(const char *address); ``` These pointers are then used for further operations: ```c char* dbName = "hello"; - Driver *driver = driver_open_core("127.0.0.1:1729"); + Driver *driver = driver_new_core("127.0.0.1:1729"); DatabaseManager* databaseManager = database_manager_new(driver); databases_create(databaseManager, dbName); Database* database = databases_get(databaseManager, dbName); diff --git a/c/src/answer.rs b/c/src/answer.rs index d8d32c2fb9..3c298b77e6 100644 --- a/c/src/answer.rs +++ b/c/src/answer.rs @@ -26,16 +26,14 @@ use typedb_driver::{ BoxPromise, Promise, Result, }; -use super::{ - concept::ConceptIterator, - iterator::CIterator, - memory::{borrow, free, release, release_optional, release_string, string_view}, -}; use crate::{ - common::StringIterator, - concept::ConceptRowIterator, - error::{try_release, try_release_optional}, - memory::take_ownership, + common::{ + error::{try_release, try_release_optional}, + iterator::CIterator, + memory::{borrow, free, release, release_string, string_view, take_ownership}, + StringIterator, + }, + concept::{ConceptIterator, ConceptRowIterator}, }; /// Promise object representing the result of an asynchronous operation. @@ -62,7 +60,7 @@ pub extern "C" fn query_answer_promise_drop(promise: *mut QueryAnswerPromise) { drop(take_ownership(promise)) } -/// Retrieve the executed query's type of the QueryAnswer. +/// Retrieves the executed query's type of the QueryAnswer. #[no_mangle] pub extern "C" fn query_answer_get_query_type(query_answer: *const QueryAnswer) -> QueryType { borrow(query_answer).get_query_type() @@ -120,7 +118,7 @@ pub extern "C" fn concept_row_get_column_names(concept_row: *const ConceptRow) - release(StringIterator(CIterator(box_stream(borrow(concept_row).get_column_names().into_iter().cloned().map(Ok))))) } -/// Retrieve the executed query's type of the ConceptRow's header. +/// Retrieves the executed query's type of the ConceptRow's header. #[no_mangle] pub extern "C" fn concept_row_get_query_type(concept_row: *const ConceptRow) -> QueryType { borrow(concept_row).get_query_type() diff --git a/c/src/error.rs b/c/src/common/error.rs similarity index 86% rename from c/src/error.rs rename to c/src/common/error.rs index 57ed509aa4..d86a9563f1 100644 --- a/c/src/error.rs +++ b/c/src/common/error.rs @@ -57,35 +57,35 @@ fn ok_record_flatten(result: Option>) -> Option { result.and_then(ok_record) } -pub(super) fn try_release(result: Result) -> *mut T { +pub(crate) fn try_release(result: Result) -> *mut T { release_optional(ok_record(result)) } -pub(super) fn try_release_optional(result: Option>) -> *mut T { +pub(crate) fn try_release_optional(result: Option>) -> *mut T { release_optional(ok_record_flatten(result)) } -pub(super) fn try_release_string(result: Result) -> *mut c_char { +pub(crate) fn try_release_string(result: Result) -> *mut c_char { ok_record(result).map(release_string).unwrap_or_else(null_mut) } -pub(super) fn try_release_optional_string(result: Option>) -> *mut c_char { +pub(crate) fn try_release_optional_string(result: Option>) -> *mut c_char { ok_record_flatten(result).map(release_string).unwrap_or_else(null_mut) } -pub(super) fn try_release_arc(result: Result>) -> *const T { +pub(crate) fn try_release_arc(result: Result>) -> *const T { try_release_optional_arc(ok_record(result)) } -pub(super) fn try_release_optional_arc(result: Option>) -> *const T { +pub(crate) fn try_release_optional_arc(result: Option>) -> *const T { result.map(release_arc).unwrap_or_else(null) } -pub(super) fn unwrap_or_default(result: Result) -> T { +pub(crate) fn unwrap_or_default(result: Result) -> T { ok_record(result).unwrap_or_default() } -pub(super) fn unwrap_void(result: Result) { +pub(crate) fn unwrap_void(result: Result) { ok_record(result); } diff --git a/c/src/iterator.rs b/c/src/common/iterator.rs similarity index 77% rename from c/src/iterator.rs rename to c/src/common/iterator.rs index 2a3874b56a..5143cf8001 100644 --- a/c/src/iterator.rs +++ b/c/src/common/iterator.rs @@ -22,21 +22,20 @@ use std::sync::Arc; use typedb_driver::{BoxStream, Result}; use super::{ - error::try_release_optional, + error::{try_release_optional, try_release_optional_arc}, memory::{borrow_mut, release_optional}, }; -use crate::error::try_release_optional_arc; -pub struct CIterator(pub(super) BoxStream<'static, T>); +pub struct CIterator(pub(crate) BoxStream<'static, T>); -pub(super) fn iterator_next(it: *mut CIterator) -> *mut T { +pub(crate) fn iterator_next(it: *mut CIterator) -> *mut T { release_optional(borrow_mut(it).0.next()) } -pub(super) fn iterator_try_next(it: *mut CIterator>) -> *mut T { +pub(crate) fn iterator_try_next(it: *mut CIterator>) -> *mut T { try_release_optional(borrow_mut(it).0.next()) } -pub(super) fn iterator_arc_next(it: *mut CIterator>) -> *const T { +pub(crate) fn iterator_arc_next(it: *mut CIterator>) -> *const T { try_release_optional_arc(borrow_mut(it).0.next()) } diff --git a/c/src/memory.rs b/c/src/common/memory.rs similarity index 78% rename from c/src/memory.rs rename to c/src/common/memory.rs index 4aa959c8c6..5557d00fb5 100644 --- a/c/src/memory.rs +++ b/c/src/common/memory.rs @@ -20,7 +20,7 @@ use std::{ cell::RefCell, ffi::{c_char, CStr, CString}, - ptr::{null, null_mut}, + ptr::null_mut, sync::Arc, }; @@ -31,74 +31,74 @@ thread_local! { static LAST_ERROR: RefCell> = RefCell::new(None); } -pub(super) fn release(t: T) -> *mut T { +pub(crate) fn release(t: T) -> *mut T { let raw = Box::into_raw(Box::new(t)); trace!("Releasing ownership of <{}> @ {:?}", std::any::type_name::(), raw); raw } -pub(super) fn release_optional(t: Option) -> *mut T { +pub(crate) fn release_optional(t: Option) -> *mut T { t.map(release).unwrap_or_else(null_mut) } -pub(super) fn release_string(str: String) -> *mut c_char { +pub(crate) fn release_string(str: String) -> *mut c_char { let raw = CString::new(str).unwrap().into_raw(); trace!("Releasing ownership of @ {:?}", raw); raw } -pub(super) fn release_optional_string(str: Option) -> *mut c_char { +pub(crate) fn release_optional_string(str: Option) -> *mut c_char { str.map(release_string).unwrap_or_else(null_mut) } -pub(super) fn release_arc(t: Arc) -> *const T { +pub(crate) fn release_arc(t: Arc) -> *const T { let raw = Arc::into_raw(t); trace!("Releasing ownership of arc <{}> @ {:?}", std::any::type_name::(), raw); raw } -pub(super) fn take_arc(raw: *const T) -> Arc { +pub(crate) fn take_arc(raw: *const T) -> Arc { trace!("Taking ownership of arced <{}> @ {:?}", std::any::type_name::(), raw); unsafe { Arc::from_raw(raw) } } -pub(super) fn decrement_arc(raw: *const T) { +pub(crate) fn decrement_arc(raw: *const T) { trace!("Decrementing arced <{}> @ {:?}", std::any::type_name::(), raw); assert!(!raw.is_null()); drop(take_arc(raw)) } -pub(super) fn borrow(raw: *const T) -> &'static T { +pub(crate) fn borrow(raw: *const T) -> &'static T { trace!("Borrowing <{}> @ {:?}", std::any::type_name::(), raw); assert!(!raw.is_null()); unsafe { &*raw } } -pub(super) fn borrow_mut(raw: *mut T) -> &'static mut T { +pub(crate) fn borrow_mut(raw: *mut T) -> &'static mut T { trace!("Borrowing (mut) <{}> @ {:?}", std::any::type_name::(), raw); assert!(!raw.is_null()); unsafe { &mut *raw } } -pub(super) fn borrow_optional(raw: *const T) -> Option<&'static T> { +pub(crate) fn borrow_optional(raw: *const T) -> Option<&'static T> { trace!("Borrowing optional (null ok) <{}> @ {:?}", std::any::type_name::(), raw); unsafe { raw.as_ref() } } -pub(super) fn take_ownership(raw: *mut T) -> T { +pub(crate) fn take_ownership(raw: *mut T) -> T { trace!("Taking ownership of <{}> @ {:?}", std::any::type_name::(), raw); assert!(!raw.is_null()); unsafe { *Box::from_raw(raw) } } -pub(super) fn free(raw: *mut T) { +pub(crate) fn free(raw: *mut T) { trace!("Freeing <{}> @ {:?}", std::any::type_name::(), raw); if !raw.is_null() { unsafe { drop(Box::from_raw(raw)) } } } -pub(super) fn string_view(str: *const c_char) -> &'static str { +pub(crate) fn string_view(str: *const c_char) -> &'static str { assert!(!str.is_null()); unsafe { CStr::from_ptr(str).to_str().unwrap() } } @@ -113,12 +113,12 @@ pub extern "C" fn string_free(str: *mut c_char) { } } -pub(super) fn array_view(ts: *const *const T) -> impl Iterator { +pub(crate) fn array_view(ts: *const *const T) -> impl Iterator { assert!(!ts.is_null()); unsafe { (0..).map_while(move |i| (*ts.add(i)).as_ref()) } } -pub(super) fn string_array_view(strs: *const *const c_char) -> impl Iterator { +pub(crate) fn string_array_view(strs: *const *const c_char) -> impl Iterator { assert!(!strs.is_null()); unsafe { (0..).map_while(move |i| (*strs.add(i)).as_ref()).map(|p| string_view(p)) } } diff --git a/c/src/common.rs b/c/src/common/mod.rs similarity index 75% rename from c/src/common.rs rename to c/src/common/mod.rs index dea0ff67e4..ed9acb0c5b 100644 --- a/c/src/common.rs +++ b/c/src/common/mod.rs @@ -17,15 +17,19 @@ * under the License. */ -use std::{ffi::c_char, ptr::null_mut}; +use std::{collections::HashMap, ffi::c_char, hash::Hash, ptr::null_mut}; +use itertools::Itertools; use typedb_driver::Result; -use super::{ - iterator::CIterator, - memory::{borrow_mut, free}, -}; -use crate::error::try_release_string; +pub(crate) mod error; +pub(crate) mod iterator; +pub(crate) mod memory; +pub(crate) mod promise; + +use error::try_release_string; +use iterator::CIterator; +use memory::{borrow_mut, free}; /// Iterator over the strings in the result of a request or a TypeQL Fetch query. pub struct StringIterator(pub CIterator>); @@ -42,3 +46,10 @@ pub extern "C" fn string_iterator_next(it: *mut StringIterator) -> *mut c_char { pub extern "C" fn string_iterator_drop(it: *mut StringIterator) { free(it); } + +pub(crate) fn iterators_to_map( + keys: impl Iterator, + values: impl Iterator, +) -> HashMap { + keys.zip_eq(values).collect() +} diff --git a/c/src/promise.rs b/c/src/common/promise.rs similarity index 99% rename from c/src/promise.rs rename to c/src/common/promise.rs index 7ea30681b5..0f8c157d24 100644 --- a/c/src/promise.rs +++ b/c/src/common/promise.rs @@ -21,7 +21,7 @@ use std::ffi::c_char; use typedb_driver::{BoxPromise, Promise, Result}; -use crate::{ +use super::{ error::{try_release_optional_string, unwrap_or_default, unwrap_void}, memory::take_ownership, }; diff --git a/c/src/concept/concept.rs b/c/src/concept/concept.rs index 115e76f2b0..b5e04a3469 100644 --- a/c/src/concept/concept.rs +++ b/c/src/concept/concept.rs @@ -24,11 +24,11 @@ use typedb_driver::{ box_stream, concept::{ value::{Decimal, Duration, TimeZone}, - Attribute, AttributeType, Concept, Entity, EntityType, Relation, RelationType, RoleType, Value, + Attribute, Concept, Entity, Relation, Value, }, }; -use crate::{ +use crate::common::{ iterator::CIterator, memory::{ borrow, borrow_mut, free, release, release_optional, release_optional_string, release_string, string_free, @@ -48,17 +48,9 @@ impl DatetimeInNanos { pub fn new(datetime: &DateTime) -> Self { Self { seconds: datetime.timestamp(), subsec_nanos: datetime.timestamp_subsec_nanos() } } - - pub fn get_seconds(self) -> i64 { - self.seconds - } - - pub fn get_subsec_nanos(self) -> u32 { - self.subsec_nanos - } } -/// A DatetimeAndTimeZone used to represent time zoned datetime in FFI. +/// DatetimeAndTimeZone is used to represent time zoned datetime in FFI. /// Time zone can be represented either as an IANA Tz or as a FixedOffset. /// Either the zone_name (is_fixed_offset == false) or offset (is_fixed_offset == true) is set. #[repr(C)] @@ -84,22 +76,6 @@ impl DatetimeAndTimeZone { is_fixed_offset, } } - - pub fn get_datetime_in_nanos(self) -> DatetimeInNanos { - self.datetime_in_nanos - } - - pub fn get_zone_name(self) -> *mut c_char { - self.zone_name - } - - pub fn get_local_minus_utc_offset(self) -> i32 { - self.local_minus_utc_offset - } - - pub fn get_is_fixed_offset(self) -> bool { - self.is_fixed_offset - } } impl Drop for DatetimeAndTimeZone { @@ -171,7 +147,7 @@ pub extern "C" fn concept_try_get_iid(thing: *mut Concept) -> *mut c_char { /// If this is a Type, returns the label of the type. #[no_mangle] pub extern "C" fn concept_get_label(concept: *const Concept) -> *mut c_char { - release_string(borrow(concept).get_label().clone().to_owned()) + release_string(borrow(concept).get_label().to_owned()) } /// Retrieves the optional label of this Concept. @@ -180,7 +156,7 @@ pub extern "C" fn concept_get_label(concept: *const Concept) -> *mut c_char { /// If this is a Type, returns the label of the type. #[no_mangle] pub extern "C" fn concept_try_get_label(concept: *const Concept) -> *mut c_char { - release_optional_string(borrow(concept).try_get_label().map(|str| str.clone().to_owned())) + release_optional_string(borrow(concept).try_get_label().map(|str| str.to_owned())) } /// Retrieves the value type of this Concept, if it exists. @@ -190,7 +166,7 @@ pub extern "C" fn concept_try_get_label(concept: *const Concept) -> *mut c_char /// Otherwise, returns null. #[no_mangle] pub extern "C" fn concept_try_get_value_type(concept: *const Concept) -> *mut c_char { - release_optional_string(borrow(concept).try_get_value_label().map(|str| str.clone().to_owned())) + release_optional_string(borrow(concept).try_get_value_label().map(|str| str.to_owned())) } /// Retrieves the value of this Concept, if it exists. @@ -317,7 +293,7 @@ pub extern "C" fn concept_get_decimal(concept: *const Concept) -> Decimal { #[no_mangle] pub extern "C" fn concept_get_string(concept: *const Concept) -> *mut c_char { match borrow(concept).try_get_string() { - Some(value) => release_string(value.clone().to_owned()), + Some(value) => release_string(value.to_owned()), None => unreachable!("Attempting to unwrap a non-string {:?} as string", borrow(concept)), } } @@ -345,9 +321,9 @@ pub extern "C" fn concept_get_datetime(concept: *const Concept) -> DatetimeInNan /// Returns the value of this datetime-tz value concept as seconds and nanoseconds parts since the start of the UNIX epoch and timezone information. /// If the value has another type, the error is set. #[no_mangle] -pub extern "C" fn concept_get_datetime_tz(concept: *const Concept) -> DatetimeAndTimeZone { +pub extern "C" fn concept_get_datetime_tz(concept: *const Concept) -> *mut DatetimeAndTimeZone { match borrow(concept).try_get_datetime_tz() { - Some(value) => DatetimeAndTimeZone::new(&value), + Some(value) => release(DatetimeAndTimeZone::new(&value)), None => unreachable!("Attempting to unwrap a non-datetime-tz {:?} as datetime-tz", borrow(concept)), } } diff --git a/c/src/concept/instance.rs b/c/src/concept/instance.rs index e0b753fb15..7374d4f4aa 100644 --- a/c/src/concept/instance.rs +++ b/c/src/concept/instance.rs @@ -22,7 +22,7 @@ use std::ptr::null_mut; use typedb_driver::concept::Concept; use super::concept::{borrow_as_attribute, borrow_as_entity, borrow_as_relation}; -use crate::memory::release; +use crate::common::memory::release; /// Retrieves the type which this ``Entity`` belongs to. #[no_mangle] diff --git a/c/src/concept/mod.rs b/c/src/concept/mod.rs index 6f6bae970e..12dcd1a9f8 100644 --- a/c/src/concept/mod.rs +++ b/c/src/concept/mod.rs @@ -19,11 +19,13 @@ use std::ptr::addr_of_mut; -use itertools::Itertools; use typedb_driver::{answer::ConceptRow, concept::Concept, BoxPromise, Promise, Result}; -use super::{iterator::iterator_try_next, memory::free}; -use crate::{error::try_release_optional, iterator::CIterator, memory::take_ownership}; +use crate::common::{ + error::try_release_optional, + iterator::{iterator_try_next, CIterator}, + memory::{free, take_ownership}, +}; mod concept; mod instance; diff --git a/c/src/connection.rs b/c/src/connection.rs deleted file mode 100644 index fe569e73bb..0000000000 --- a/c/src/connection.rs +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -use std::{ffi::c_char, path::Path}; - -use typedb_driver::{Credentials, DriverOptions, TypeDBDriver}; - -use super::{ - error::{try_release, unwrap_void}, - memory::{borrow, free, string_view}, -}; -use crate::memory::release; - -const DRIVER_LANG: &'static str = "c"; - -/// Open a TypeDB C Driver to a TypeDB server available at the provided address. -/// -/// @param address The address (host:port) on which the TypeDB Server is running -/// @param credentials The Credentials to connect with -/// @param driver_options The DriverOptions to connect with -#[no_mangle] -pub extern "C" fn driver_open( - address: *const c_char, - credentials: *const Credentials, - driver_options: *const DriverOptions, -) -> *mut TypeDBDriver { - try_release(TypeDBDriver::new_with_description( - string_view(address), - borrow(credentials).clone(), - borrow(driver_options).clone(), - DRIVER_LANG, - )) -} - -/// Open a TypeDB Driver to a TypeDB server available at the provided address. -/// This method allows driver language specification for drivers built on top of the native C layer. -/// -/// @param address The address (host:port) on which the TypeDB Server is running -/// @param credentials The Credentials to connect with -/// @param driver_options The DriverOptions to connect with -/// @param driver_lang The language of the driver connecting to the server -#[no_mangle] -pub extern "C" fn driver_open_with_description( - address: *const c_char, - credentials: *const Credentials, - driver_options: *const DriverOptions, - driver_lang: *const c_char, -) -> *mut TypeDBDriver { - try_release(TypeDBDriver::new_with_description( - string_view(address), - borrow(credentials).clone(), - borrow(driver_options).clone(), - string_view(driver_lang), - )) -} - -/// Closes the driver. Before instantiating a new driver, the driver that’s currently open should first be closed. -/// Closing a driver frees the underlying Rust object. -#[no_mangle] -pub extern "C" fn driver_close(driver: *mut TypeDBDriver) { - free(driver); -} - -/// Checks whether this connection is presently open. -#[no_mangle] -pub extern "C" fn driver_is_open(driver: *const TypeDBDriver) -> bool { - borrow(driver).is_open() -} - -/// Forcibly closes the driver. To be used in exceptional cases. -#[no_mangle] -pub extern "C" fn driver_force_close(driver: *mut TypeDBDriver) { - unwrap_void(borrow(driver).force_close()); -} - -// Creates a new Credentials for connecting to TypeDB Server. -// -// @param username The name of the user to connect as -// @param password The password for the user -#[no_mangle] -pub extern "C" fn credentials_new(username: *const c_char, password: *const c_char) -> *mut Credentials { - release(Credentials::new(string_view(username), string_view(password))) -} - -// Frees the native rust Credentials object -#[no_mangle] -pub extern "C" fn credentials_drop(credentials: *mut Credentials) { - free(credentials); -} - -// Creates a new DriverOptions for connecting to TypeDB Server. -// -// @param tls_root_ca Path to the CA certificate to use for authenticating server certificates. -// @param with_tls Specify whether the connection to TypeDB Cloud must be done over TLS -#[no_mangle] -pub extern "C" fn driver_options_new(is_tls_enabled: bool, tls_root_ca: *const c_char) -> *mut DriverOptions { - let tls_root_ca_path = unsafe { tls_root_ca.as_ref().map(|str| Path::new(string_view(str))) }; - try_release(DriverOptions::new(is_tls_enabled, tls_root_ca_path)) -} - -// Frees the native rust DriverOptions object -#[no_mangle] -pub extern "C" fn driver_options_drop(driver_options: *mut DriverOptions) { - free(driver_options); -} diff --git a/c/src/credentials.rs b/c/src/credentials.rs new file mode 100644 index 0000000000..7d1a11ecee --- /dev/null +++ b/c/src/credentials.rs @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +use std::ffi::c_char; + +use typedb_driver::Credentials; + +use crate::common::memory::{free, release, string_view}; + +/// Creates a new Credentials for connecting to TypeDB Server. +/// +/// @param username The name of the user to connect as +/// @param password The password for the user +#[no_mangle] +pub extern "C" fn credentials_new(username: *const c_char, password: *const c_char) -> *mut Credentials { + release(Credentials::new(string_view(username), string_view(password))) +} + +/// Frees the native rust Credentials object +#[no_mangle] +pub extern "C" fn credentials_drop(credentials: *mut Credentials) { + free(credentials); +} diff --git a/c/src/database.rs b/c/src/database.rs deleted file mode 100644 index dacf866e1e..0000000000 --- a/c/src/database.rs +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -use std::{ffi::c_char, path::Path, ptr::addr_of_mut, sync::Arc}; - -use typedb_driver::{box_stream, info::ReplicaInfo, Database}; - -use super::{ - error::{try_release_string, unwrap_void}, - iterator::{iterator_next, CIterator}, - memory::{borrow, borrow_mut, free, release, release_optional, release_string, take_ownership}, -}; -use crate::memory::{decrement_arc, string_view, take_arc}; - -/// Frees the native rust Database object -#[no_mangle] -pub extern "C" fn database_close(database: *const Database) { - decrement_arc(database) -} - -/// The database name as a string. -#[no_mangle] -pub extern "C" fn database_get_name(database: *const Database) -> *mut c_char { - release_string(borrow(database).name().to_owned()) -} - -/// Deletes this database. -#[no_mangle] -pub extern "C" fn database_delete(database: *const Database) { - unwrap_void(take_arc(database).delete()); -} - -/// A full schema text as a valid TypeQL define query string. -#[no_mangle] -pub extern "C" fn database_schema(database: *const Database) -> *mut c_char { - try_release_string(borrow(database).schema()) -} - -/// The types in the schema as a valid TypeQL define query string. -#[no_mangle] -pub extern "C" fn database_type_schema(database: *const Database) -> *mut c_char { - try_release_string(borrow(database).type_schema()) -} - -/// Export a database into a schema definition and a data files saved to the disk. -/// This is a blocking operation and may take a significant amount of time depending on the database size. -/// -/// @param database The Database object to export from. -/// @param schema_file The path to the schema definition file to be created. -/// @param data_file The path to the data file to be created. -#[no_mangle] -pub extern "C" fn database_export_to_file( - database: *const Database, - schema_file: *const c_char, - data_file: *const c_char, -) { - let schema_file_path = Path::new(string_view(schema_file)); - let data_file_path = Path::new(string_view(data_file)); - unwrap_void(borrow(database).export_to_file(schema_file_path, data_file_path)) -} - -// /// Iterator over the ReplicaInfo corresponding to each replica of a TypeDB cloud database. -// pub struct ReplicaInfoIterator(CIterator); -// -// /// Forwards the ReplicaInfoIterator and returns the next ReplicaInfo if it exists, -// /// or null if there are no more elements. -// #[no_mangle] -// pub extern "C" fn replica_info_iterator_next(it: *mut ReplicaInfoIterator) -> *mut ReplicaInfo { -// unsafe { iterator_next(addr_of_mut!((*it).0)) } -// } -// -// /// Frees the native rust ReplicaInfoIterator object -// #[no_mangle] -// pub extern "C" fn replica_info_iterator_drop(it: *mut ReplicaInfoIterator) { -// free(it); -// } -// -// /// Set of Replica instances for this database. -// /// Only works in TypeDB Cloud -// #[no_mangle] -// pub extern "C" fn database_get_replicas_info(database: *const Database) -> *mut ReplicaInfoIterator { -// release(ReplicaInfoIterator(CIterator(box_stream(borrow(database).replicas_info().into_iter())))) -// } -// -// /// Returns the primary replica for this database. -// /// _Only works in TypeDB Cloud_ -// #[no_mangle] -// pub extern "C" fn database_get_primary_replica_info(database: *const Database) -> *mut ReplicaInfo { -// release_optional(borrow(database).primary_replica_info()) -// } -// -// /// Returns the preferred replica for this database. -// /// Operations which can be run on any replica will prefer to use this replica. -// /// _Only works in TypeDB Cloud_ -// #[no_mangle] -// pub extern "C" fn database_get_preferred_replica_info(database: *const Database) -> *mut ReplicaInfo { -// release_optional(borrow(database).preferred_replica_info()) -// } -// -// /// Frees the native rust ReplicaInfo object -// #[no_mangle] -// pub extern "C" fn replica_info_drop(replica_info: *mut ReplicaInfo) { -// free(replica_info); -// } -// -// /// The server hosting this replica -// #[no_mangle] -// pub extern "C" fn replica_info_get_server(replica_info: *const ReplicaInfo) -> *mut c_char { -// release_string(borrow(replica_info).server.to_string()) -// } -// -// /// Checks whether this is the primary replica of the raft cluster. -// #[no_mangle] -// pub extern "C" fn replica_info_is_primary(replica_info: *const ReplicaInfo) -> bool { -// borrow(replica_info).is_primary -// } -// -// /// Checks whether this is the preferred replica of the raft cluster. -// /// If true, Operations which can be run on any replica will prefer to use this replica. -// #[no_mangle] -// pub extern "C" fn replica_info_is_preferred(replica_info: *const ReplicaInfo) -> bool { -// borrow(replica_info).is_preferred -// } -// -// /// The raft protocol ‘term’ of this replica. -// #[no_mangle] -// pub extern "C" fn replica_info_get_term(replica_info: *const ReplicaInfo) -> i64 { -// borrow(replica_info).term -// } diff --git a/c/src/database/database.rs b/c/src/database/database.rs new file mode 100644 index 0000000000..f7e1301abd --- /dev/null +++ b/c/src/database/database.rs @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +use std::{ffi::c_char, path::Path}; + +use typedb_driver::Database; + +use crate::{ + common::{ + error::{try_release_string, unwrap_void}, + memory::{borrow, decrement_arc, release_string, string_view, take_arc}, + }, + server::consistency_level::{native_consistency_level, ConsistencyLevel}, +}; + +/// Frees the native rust Database object. +#[no_mangle] +pub extern "C" fn database_close(database: *const Database) { + decrement_arc(database) +} + +/// The Database name as a string. +#[no_mangle] +pub extern "C" fn database_get_name(database: *const Database) -> *mut c_char { + release_string(borrow(database).name().to_owned()) +} + +/// Deletes this Database. +#[no_mangle] +pub extern "C" fn database_delete(database: *const Database) { + unwrap_void(take_arc(database).delete()); +} + +/// A full schema text as a valid TypeQL define query string. +/// +/// @param database The Database to get the schema from. +/// @param consistency_level The consistency level to use for the operation. Strongest possible if null. +#[no_mangle] +pub extern "C" fn database_schema( + database: *const Database, + consistency_level: *const ConsistencyLevel, +) -> *mut c_char { + let database = borrow(database); + let result = match native_consistency_level(consistency_level) { + Some(consistency_level) => database.schema_with_consistency(consistency_level), + None => database.schema(), + }; + try_release_string(result) +} + +/// The types in the schema as a valid TypeQL define query string. +/// +/// @param database The Database to get the type schema from. +/// @param consistency_level The consistency level to use for the operation. Strongest possible if null. +#[no_mangle] +pub extern "C" fn database_type_schema( + database: *const Database, + consistency_level: *const ConsistencyLevel, +) -> *mut c_char { + let database = borrow(database); + let result = match native_consistency_level(consistency_level) { + Some(consistency_level) => database.type_schema_with_consistency(consistency_level), + None => database.type_schema(), + }; + try_release_string(result) +} + +/// Export a database into a schema definition and a data files saved to the disk. +/// This is a blocking operation and may take a significant amount of time depending on the database size. +/// +/// @param database The Database object to export from. +/// @param schema_file The path to the schema definition file to be created. +/// @param data_file The path to the data file to be created. +/// @param consistency_level The consistency level to use for the operation. Strongest possible if null. +#[no_mangle] +pub extern "C" fn database_export_to_file( + database: *const Database, + schema_file: *const c_char, + data_file: *const c_char, + consistency_level: *const ConsistencyLevel, +) { + let database = borrow(database); + let schema_file_path = Path::new(string_view(schema_file)); + let data_file_path = Path::new(string_view(data_file)); + let result = match native_consistency_level(consistency_level) { + Some(consistency_level) => { + database.export_to_file_with_consistency(schema_file_path, data_file_path, consistency_level) + } + None => database.export_to_file(schema_file_path, data_file_path), + }; + unwrap_void(result) +} diff --git a/c/src/database_manager.rs b/c/src/database/database_manager.rs similarity index 50% rename from c/src/database_manager.rs rename to c/src/database/database_manager.rs index 3345bb5971..aeed17a434 100644 --- a/c/src/database_manager.rs +++ b/c/src/database/database_manager.rs @@ -21,12 +21,14 @@ use std::{ffi::c_char, path::Path, ptr::addr_of_mut, sync::Arc}; use typedb_driver::{box_stream, Database, TypeDBDriver}; -use super::{ - error::{try_release, unwrap_or_default, unwrap_void}, - iterator::CIterator, - memory::{borrow_mut, free, string_view}, +use crate::{ + common::{ + error::{try_release, try_release_arc, unwrap_or_default, unwrap_void}, + iterator::{iterator_arc_next, CIterator}, + memory::{borrow_mut, free, string_view}, + }, + server::consistency_level::{native_consistency_level, ConsistencyLevel}, }; -use crate::{error::try_release_arc, iterator::iterator_arc_next}; /// An Iterator over databases present on the TypeDB server. pub struct DatabaseIterator(CIterator>); @@ -45,20 +47,70 @@ pub extern "C" fn database_iterator_drop(it: *mut DatabaseIterator) { } /// Returns a DatabaseIterator over all databases present on the TypeDB server. +/// +/// @param driver The TypeDBDriver object. +/// @param consistency_level The consistency level to use for the operation. Strongest possible if null. +#[no_mangle] +pub extern "C" fn databases_all( + driver: *mut TypeDBDriver, + consistency_level: *const ConsistencyLevel, +) -> *mut DatabaseIterator { + let databases = borrow_mut(driver).databases(); + let result = match native_consistency_level(consistency_level) { + Some(consistency_level) => databases.all_with_consistency(consistency_level), + None => databases.all(), + }; + try_release(result.map(|dbs| DatabaseIterator(CIterator(box_stream(dbs.into_iter()))))) +} + +/// Checks if a database with the given name exists. +/// +/// @param driver The TypeDBDriver object. +/// @param name The name of the database. +/// @param consistency_level The consistency level to use for the operation. Strongest possible if null. +#[no_mangle] +pub extern "C" fn databases_contains( + driver: *mut TypeDBDriver, + name: *const c_char, + consistency_level: *const ConsistencyLevel, +) -> bool { + let databases = borrow_mut(driver).databases(); + let name = string_view(name); + unwrap_or_default(match native_consistency_level(consistency_level) { + Some(consistency_level) => databases.contains_with_consistency(name, consistency_level), + None => databases.contains(name), + }) +} + +/// Retrieves the database with the given name. +/// +/// @param driver The TypeDBDriver object. +/// @param name The name of the database. +/// @param consistency_level The consistency level to use for the operation. Strongest possible if null. #[no_mangle] -pub extern "C" fn databases_all(driver: *mut TypeDBDriver) -> *mut DatabaseIterator { - try_release( - borrow_mut(driver).databases().all().map(|dbs| DatabaseIterator(CIterator(box_stream(dbs.into_iter())))), - ) +pub extern "C" fn databases_get( + driver: *mut TypeDBDriver, + name: *const c_char, + consistency_level: *const ConsistencyLevel, +) -> *const Database { + let databases = borrow_mut(driver).databases(); + let name = string_view(name); + try_release_arc(match native_consistency_level(consistency_level) { + Some(consistency_level) => databases.get_with_consistency(name, consistency_level), + None => databases.get(name), + }) } -/// Create a database with the given name. +/// Creates a database with the given name. +/// +/// @param driver The TypeDBDriver object. +/// @param name The name of the database to be created. #[no_mangle] pub extern "C" fn databases_create(driver: *mut TypeDBDriver, name: *const c_char) { unwrap_void(borrow_mut(driver).databases().create(string_view(name))); } -/// Create a database with the given name based on previously exported another database's data +/// Creates a database with the given name based on previously exported another database's data /// loaded from a file. /// This is a blocking operation and may take a significant amount of time depending on the database /// size. @@ -77,15 +129,3 @@ pub extern "C" fn databases_import_from_file( let data_file_path = Path::new(string_view(data_file)); unwrap_void(borrow_mut(driver).databases().import_from_file(string_view(name), string_view(schema), data_file_path)) } - -/// Checks if a database with the given name exists. -#[no_mangle] -pub extern "C" fn databases_contains(driver: *mut TypeDBDriver, name: *const c_char) -> bool { - unwrap_or_default(borrow_mut(driver).databases().contains(string_view(name))) -} - -/// Retrieve the database with the given name. -#[no_mangle] -pub extern "C" fn databases_get(driver: *mut TypeDBDriver, name: *const c_char) -> *const Database { - try_release_arc(borrow_mut(driver).databases().get(string_view(name))) -} diff --git a/c/src/database/mod.rs b/c/src/database/mod.rs new file mode 100644 index 0000000000..4194e83962 --- /dev/null +++ b/c/src/database/mod.rs @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +pub(crate) mod database; +pub(crate) mod database_manager; diff --git a/c/src/driver.rs b/c/src/driver.rs new file mode 100644 index 0000000000..b322da0c88 --- /dev/null +++ b/c/src/driver.rs @@ -0,0 +1,257 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +use std::ffi::c_char; + +use typedb_driver::{box_stream, Addresses, Credentials, DriverOptions, ServerReplica, TypeDBDriver}; + +use crate::{ + common::{ + error::{try_release, unwrap_or_default, unwrap_void}, + iterator::CIterator, + iterators_to_map, + memory::{borrow, free, release, release_optional, string_array_view, string_view}, + }, + server::{ + consistency_level::{native_consistency_level, ConsistencyLevel}, + server_replica::ServerReplicaIterator, + server_version::ServerVersion, + }, +}; + +const DRIVER_LANG: &'static str = "c"; + +/// Open a TypeDB C Driver to a TypeDB server available at the provided address. +/// +/// @param address The address on which the TypeDB Server is running +/// @param credentials The Credentials to connect with +/// @param driver_options The DriverOptions to connect with +#[no_mangle] +pub extern "C" fn driver_new( + address: *const c_char, + credentials: *const Credentials, + driver_options: *const DriverOptions, +) -> *mut TypeDBDriver { + try_release(TypeDBDriver::new_with_description( + unwrap_or_default(Addresses::try_from_address_str(string_view(address))), + borrow(credentials).clone(), + borrow(driver_options).clone(), + DRIVER_LANG, + )) +} + +/// Open a TypeDB Driver to a TypeDB server available at the provided address. +/// This method allows driver language specification for drivers built on top of the native C layer. +/// +/// @param address The address on which the TypeDB Server is running +/// @param credentials The Credentials to connect with +/// @param driver_options The DriverOptions to connect with +/// @param driver_lang The language of the driver connecting to the server +#[no_mangle] +pub extern "C" fn driver_new_with_description( + address: *const c_char, + credentials: *const Credentials, + driver_options: *const DriverOptions, + driver_lang: *const c_char, +) -> *mut TypeDBDriver { + try_release(TypeDBDriver::new_with_description( + unwrap_or_default(Addresses::try_from_address_str(string_view(address))), + borrow(credentials).clone(), + borrow(driver_options).clone(), + string_view(driver_lang), + )) +} + +/// Open a TypeDB C Driver to a TypeDB cluster available at the provided addresses. +/// +/// @param addresses A null-terminated array holding the server addresses on for connection +/// @param credentials The Credentials to connect with +/// @param driver_options The DriverOptions to connect with +#[no_mangle] +pub extern "C" fn driver_new_with_addresses( + addresses: *const *const c_char, + credentials: *const Credentials, + driver_options: *const DriverOptions, +) -> *mut TypeDBDriver { + try_release(TypeDBDriver::new_with_description( + unwrap_or_default(Addresses::try_from_addresses_str(string_array_view(addresses))), + borrow(credentials).clone(), + borrow(driver_options).clone(), + DRIVER_LANG, + )) +} + +/// Open a TypeDB Driver to a TypeDB cluster available at the provided addresses. +/// This method allows driver language specification for drivers built on top of the native C layer. +/// +/// @param addresses A null-terminated array holding the TypeDB cluster replica addresses on for connection +/// @param credentials The Credentials to connect with +/// @param driver_options The DriverOptions to connect with +/// @param driver_lang The language of the driver connecting to the server +#[no_mangle] +pub extern "C" fn driver_new_with_addresses_with_description( + addresses: *const *const c_char, + credentials: *const Credentials, + driver_options: *const DriverOptions, + driver_lang: *const c_char, +) -> *mut TypeDBDriver { + try_release(TypeDBDriver::new_with_description( + unwrap_or_default(Addresses::try_from_addresses_str(string_array_view(addresses))), + borrow(credentials).clone(), + borrow(driver_options).clone(), + string_view(driver_lang), + )) +} + +/// Open a TypeDB C Driver to a TypeDB cluster, using the provided address translation. +/// +/// @param public_addresses A null-terminated array holding the replica addresses on for connection. +/// @param private_addresses A null-terminated array holding the private replica addresses, configured on the server side. +/// This array must have the same length as public_addresses. +/// @param credentials The Credentials to connect with +/// @param driver_options The DriverOptions to connect with +#[no_mangle] +pub extern "C" fn driver_new_with_address_translation( + public_addresses: *const *const c_char, + private_addresses: *const *const c_char, + credentials: *const Credentials, + driver_options: *const DriverOptions, +) -> *mut TypeDBDriver { + let addresses = iterators_to_map(string_array_view(public_addresses), string_array_view(private_addresses)); + try_release(TypeDBDriver::new_with_description( + unwrap_or_default(Addresses::try_from_translation_str(addresses)), + borrow(credentials).clone(), + borrow(driver_options).clone(), + DRIVER_LANG, + )) +} + +/// Open a TypeDB Driver to a TypeDB cluster, using the provided address translation. +/// This method allows driver language specification for drivers built on top of the native C layer. +/// +/// @param public_addresses A null-terminated array holding the replica addresses on for connection. +/// @param private_addresses A null-terminated array holding the private replica addresses, configured on the server side. +/// This array must have the same length as public_addresses. +/// @param credentials The Credentials to connect with +/// @param driver_options The DriverOptions to connect with +/// @param driver_lang The language of the driver connecting to the server +#[no_mangle] +pub extern "C" fn driver_new_with_address_translation_with_description( + public_addresses: *const *const c_char, + private_addresses: *const *const c_char, + credentials: *const Credentials, + driver_options: *const DriverOptions, + driver_lang: *const c_char, +) -> *mut TypeDBDriver { + let addresses = iterators_to_map(string_array_view(public_addresses), string_array_view(private_addresses)); + try_release(TypeDBDriver::new_with_description( + unwrap_or_default(Addresses::try_from_translation_str(addresses)), + borrow(credentials).clone(), + borrow(driver_options).clone(), + string_view(driver_lang), + )) +} + +/// Closes the TypeDBDriver. Before instantiating a new driver, the driver that’s currently open should first be closed. +/// Closing a driver frees the underlying Rust object. +#[no_mangle] +pub extern "C" fn driver_close(driver: *mut TypeDBDriver) { + free(driver); +} + +/// Forcibly closes the TypeDBDriver. To be used in exceptional cases. +#[no_mangle] +pub extern "C" fn driver_force_close(driver: *mut TypeDBDriver) { + unwrap_void(borrow(driver).force_close()); +} + +/// Checks whether this connection is presently open. +#[no_mangle] +pub extern "C" fn driver_is_open(driver: *const TypeDBDriver) -> bool { + borrow(driver).is_open() +} + +/// Retrieves the server version and distribution information. +/// +/// @param driver The TypeDBDriver object. +/// @param consistency_level The consistency level to use for the operation. Strongest possible if null. +#[no_mangle] +pub extern "C" fn driver_server_version( + driver: *const TypeDBDriver, + consistency_level: *const ConsistencyLevel, +) -> *mut ServerVersion { + let driver = borrow(driver); + let result = match native_consistency_level(consistency_level) { + Some(consistency_level) => driver.server_version_with_consistency(consistency_level), + None => driver.server_version(), + }; + release(unwrap_or_default(result.map(|server_version| { + ServerVersion::new(server_version.distribution().to_string(), server_version.version().to_string()) + }))) +} + +/// Retrieves the server's replicas. +#[no_mangle] +pub extern "C" fn driver_replicas(driver: *const TypeDBDriver) -> *mut ServerReplicaIterator { + release(ServerReplicaIterator(CIterator(box_stream(unwrap_or_default(borrow(driver).replicas()).into_iter())))) +} + +/// Retrieves the server's primary replica, if exists. +#[no_mangle] +pub extern "C" fn driver_primary_replica(driver: *const TypeDBDriver) -> *mut ServerReplica { + release_optional(borrow(driver).primary_replica()) +} + +/// Registers a new replica in the cluster the driver is currently connected to. The registered +/// replica will become available eventually, depending on the behavior of the whole cluster. +/// To register a replica, its clustering address should be passed, not the connection address. +/// +/// @param replica_id The numeric identifier of the new replica +/// @param address The address(es) of the TypeDB replica as a string +#[no_mangle] +pub extern "C" fn driver_register_replica(driver: *const TypeDBDriver, replica_id: i64, address: *const c_char) { + unwrap_void(borrow(driver).register_replica(replica_id as u64, string_view(address).to_string())) +} + +/// Deregisters a replica from the cluster the driver is currently connected to. This replica +/// will no longer play a raft role in this cluster. +/// +/// @param replica_id The numeric identifier of the deregistered replica +#[no_mangle] +pub extern "C" fn driver_deregister_replica(driver: *const TypeDBDriver, replica_id: i64) { + unwrap_void(borrow(driver).deregister_replica(replica_id as u64)) +} + +/// Updates address translation of the driver. This lets you actualize new translation +/// information without recreating the driver from scratch. Useful after registering new +/// replicas requiring address translation. +/// +/// @param public_addresses A null-terminated array holding the replica addresses on for connection. +/// @param private_addresses A null-terminated array holding the private replica addresses, configured on the server side. +/// This array must have the same length as public_addresses. +#[no_mangle] +pub extern "C" fn driver_update_address_translation( + driver: *const TypeDBDriver, + public_addresses: *const *const c_char, + private_addresses: *const *const c_char, +) { + let translation = iterators_to_map(string_array_view(public_addresses), string_array_view(private_addresses)); + let addresses = unwrap_or_default(Addresses::try_from_translation_str(translation)); + unwrap_void(borrow(driver).update_address_translation(addresses)) +} diff --git a/c/src/driver_options.rs b/c/src/driver_options.rs new file mode 100644 index 0000000000..66ec44b7aa --- /dev/null +++ b/c/src/driver_options.rs @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +use std::{ffi::c_char, path::Path}; + +use typedb_driver::DriverOptions; + +use crate::common::{ + error::unwrap_void, + memory::{borrow, borrow_mut, borrow_optional, free, release, release_string, string_view}, +}; + +/// Creates a new DriverOptions for connecting to TypeDB Server. +#[no_mangle] +pub extern "C" fn driver_options_new() -> *mut DriverOptions { + release(DriverOptions::new()) +} + +/// Frees the native rust DriverOptions object. +#[no_mangle] +pub extern "C" fn driver_options_drop(driver_options: *mut DriverOptions) { + free(driver_options); +} + +/// Explicitly sets whether the connection to TypeDB must be done over TLS. +#[no_mangle] +pub extern "C" fn driver_options_set_is_tls_enabled(options: *mut DriverOptions, is_tls_enabled: bool) { + borrow_mut(options).is_tls_enabled = is_tls_enabled; +} + +/// Returns the value set for the TLS flag in this DriverOptions object. +/// Specifies whether the connection to TypeDB must be done over TLS. Defaults to false. +#[no_mangle] +pub extern "C" fn driver_options_get_is_tls_enabled(options: *const DriverOptions) -> bool { + borrow(options).is_tls_enabled +} + +/// Specifies the root CA used in the TLS config for server certificates authentication. +/// Uses system roots if None is set. +#[no_mangle] +pub extern "C" fn driver_options_set_tls_root_ca_path(options: *mut DriverOptions, tls_root_ca: *const c_char) { + let tls_root_ca_path = borrow_optional(tls_root_ca).map(|str| Path::new(string_view(str))); + unwrap_void(borrow_mut(options).set_tls_root_ca(tls_root_ca_path)) +} + +/// Returns the TLS root CA set in this DriverOptions object. +/// Specifies the root CA used in the TLS config for server certificates authentication. +/// Uses system roots if None is set. +#[no_mangle] +pub extern "C" fn driver_options_get_tls_root_ca_path(options: *const DriverOptions) -> *mut c_char { + release_string(borrow(options).get_tls_root_ca().unwrap().to_string_lossy().to_string()) +} + +/// Checks whether TLS root CA was explicitly set for this DriverOptions object. +#[no_mangle] +pub extern "C" fn driver_options_has_tls_root_ca_path(options: *const DriverOptions) -> bool { + borrow(options).get_tls_root_ca().is_some() +} + +/// Specifies whether the connection to TypeDB can use cluster replicas provided by the server +/// or it should be limited to a single configured address. +/// Defaults to true. +#[no_mangle] +pub extern "C" fn driver_options_set_use_replication(options: *mut DriverOptions, use_replication: bool) { + borrow_mut(options).use_replication = use_replication; +} + +/// Returns the value set for the replication usage flag in this DriverOptions object. +/// Specifies whether the connection to TypeDB can use cluster replicas provided by the server +/// or it should be limited to a single configured address. +#[no_mangle] +pub extern "C" fn driver_options_get_use_replication(options: *const DriverOptions) -> bool { + borrow(options).use_replication +} + +/// Limits the number of attempts to redirect a strongly consistent request to another +/// primary replica in case of a failure due to the change of replica roles. +/// Defaults to 1. +#[no_mangle] +pub extern "C" fn driver_options_set_primary_failover_retries( + options: *mut DriverOptions, + primary_failover_retries: i64, +) { + borrow_mut(options).primary_failover_retries = primary_failover_retries as usize; +} + +/// Returns the value set for the primary failover retries limit in this DriverOptions object. +/// Limits the number of attempts to redirect a strongly consistent request to another +/// primary replica in case of a failure due to the change of replica roles. +#[no_mangle] +pub extern "C" fn driver_options_get_primary_failover_retries(options: *const DriverOptions) -> i64 { + borrow(options).primary_failover_retries as i64 +} + +/// Limits the number of driver attempts to discover a single working replica to perform an +/// operation in case of a replica unavailability. Every replica is tested once, which means +/// that at most: +/// - {limit} operations are performed if the limit <= the number of replicas. +/// - {number of replicas} operations are performed if the limit > the number of replicas. +/// - {number of replicas} operations are performed if the limit is None. +/// Affects every eventually consistent operation, including redirect failover, when the new +/// primary replica is unknown. If not set, the maximum (practically unlimited) value is used. +#[no_mangle] +pub extern "C" fn driver_options_set_replica_discovery_attempts( + options: *mut DriverOptions, + replica_discovery_attempts: i64, +) { + borrow_mut(options).replica_discovery_attempts = Some(replica_discovery_attempts as usize); +} + +/// Returns the value set for the replica discovery attempts limit in this DriverOptions object. +/// Limits the number of driver attempts to discover a single working replica to perform an +/// operation in case of a replica unavailability. Every replica is tested once, which means +/// that at most: +/// - {limit} operations are performed if the limit <= the number of replicas. +/// - {number of replicas} operations are performed if the limit > the number of replicas. +/// - {number of replicas} operations are performed if the limit is None. +/// Affects every eventually consistent operation, including redirect failover, when the new +/// primary replica is unknown. +#[no_mangle] +pub extern "C" fn driver_options_get_replica_discovery_attempts(options: *const DriverOptions) -> i64 { + borrow(options).replica_discovery_attempts.unwrap() as i64 +} + +/// Checks whether the replica discovery attempts limit was explicitly set for this DriverOptions object. +#[no_mangle] +pub extern "C" fn driver_options_has_replica_discovery_attempts(options: *const DriverOptions) -> bool { + borrow(options).replica_discovery_attempts.is_some() +} diff --git a/c/src/lib.rs b/c/src/lib.rs index 5cd285ea14..07d86ccce2 100644 --- a/c/src/lib.rs +++ b/c/src/lib.rs @@ -20,15 +20,12 @@ mod answer; mod common; mod concept; -mod connection; +mod credentials; mod database; -mod database_manager; -mod error; -mod iterator; -mod memory; -mod promise; +mod driver; +mod driver_options; mod query_options; +mod server; mod transaction; mod transaction_options; mod user; -mod user_manager; diff --git a/c/src/query_options.rs b/c/src/query_options.rs index 6841683d6c..1c4f7f5db1 100644 --- a/c/src/query_options.rs +++ b/c/src/query_options.rs @@ -19,7 +19,7 @@ use typedb_driver::QueryOptions; -use super::memory::{borrow, borrow_mut, free, release}; +use crate::common::memory::{borrow, borrow_mut, free, release}; /// Produces a new QueryOptions object. #[no_mangle] @@ -33,7 +33,7 @@ pub extern "C" fn query_options_drop(options: *mut QueryOptions) { free(options); } -/// Explicitly set the "include instance types" flag. +/// Explicitly setsthe "include instance types" flag. /// If set, specifies if types should be included in instance structs returned in ConceptRow answers. /// This option allows reducing the amount of unnecessary data transmitted. #[no_mangle] @@ -55,7 +55,7 @@ pub extern "C" fn query_options_has_include_instance_types(options: *const Query borrow(options).include_instance_types.is_some() } -/// Explicitly set the prefetch size. +/// Explicitly setsthe prefetch size. /// If set, specifies the number of extra query responses sent before the client side has to re-request more responses. /// Increasing this may increase performance for queries with a huge number of answers, as it can /// reduce the number of network round-trips at the cost of more resources on the server side. diff --git a/c/src/server/consistency_level.rs b/c/src/server/consistency_level.rs new file mode 100644 index 0000000000..2196739e23 --- /dev/null +++ b/c/src/server/consistency_level.rs @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +use std::ffi::c_char; + +use typedb_driver::consistency_level::ConsistencyLevel as NativeConsistencyLevel; + +use crate::common::{ + error::unwrap_or_default, + memory::{borrow_optional, free, release, release_string, string_free, string_view}, +}; + +/// ConsistencyLevelTag is used to represent consistency levels in FFI. +/// It is the tag part, which is combined with optional fields to form an instance of the original +/// enum. +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub enum ConsistencyLevelTag { + Strong, + Eventual, + ReplicaDependant, +} + +/// ConsistencyLevel is used to represent consistency levels in FFI. +/// It combines ConsistencyLevelTag and optional fields to form an instance of the +/// original enum. +/// address is not null only when tag is ReplicaDependant. +#[repr(C)] +#[derive(Debug, Clone)] +pub struct ConsistencyLevel { + pub(crate) tag: ConsistencyLevelTag, + pub(crate) address: *mut c_char, +} + +impl ConsistencyLevel { + fn new_strong() -> Self { + ConsistencyLevel { tag: ConsistencyLevelTag::Strong, address: std::ptr::null_mut() } + } + + fn new_eventual() -> Self { + ConsistencyLevel { tag: ConsistencyLevelTag::Eventual, address: std::ptr::null_mut() } + } + + fn new_replica_dependant(address: *mut c_char) -> Self { + ConsistencyLevel { tag: ConsistencyLevelTag::ReplicaDependant, address } + } +} + +impl Drop for ConsistencyLevel { + fn drop(&mut self) { + string_free(self.address); + } +} + +/// Creates a strong ConsistencyLevel object. +#[no_mangle] +pub extern "C" fn consistency_level_strong() -> *mut ConsistencyLevel { + release(ConsistencyLevel::new_strong()) +} + +/// Creates an eventual ConsistencyLevel object. +#[no_mangle] +pub extern "C" fn consistency_level_eventual() -> *mut ConsistencyLevel { + release(ConsistencyLevel::new_eventual()) +} + +/// Creates a replica dependant ConsistencyLevel object. +/// +/// @param address The address of the replica to depend on. +#[no_mangle] +pub extern "C" fn consistency_level_replica_dependant(address: *const c_char) -> *mut ConsistencyLevel { + release(ConsistencyLevel::new_replica_dependant(release_string(string_view(address.clone()).to_string()))) +} + +/// Drops the ConsistencyLevel object. +#[no_mangle] +pub extern "C" fn consistency_level_drop(consistency_level: *mut ConsistencyLevel) { + free(consistency_level) +} + +pub(crate) fn native_consistency_level(consistency_level: *const ConsistencyLevel) -> Option { + borrow_optional(consistency_level).cloned().map(|consistency_level| consistency_level.into()) +} + +impl Into for ConsistencyLevel { + fn into(self) -> NativeConsistencyLevel { + let ConsistencyLevel { tag, address } = self; + match tag { + ConsistencyLevelTag::Strong => NativeConsistencyLevel::Strong, + ConsistencyLevelTag::Eventual => NativeConsistencyLevel::Eventual, + ConsistencyLevelTag::ReplicaDependant => { + let address = unwrap_or_default(string_view(address).parse()); + NativeConsistencyLevel::ReplicaDependant { address } + } + } + } +} + +impl From for ConsistencyLevel { + fn from(value: NativeConsistencyLevel) -> Self { + match value { + NativeConsistencyLevel::Strong => ConsistencyLevel::new_strong(), + NativeConsistencyLevel::Eventual => ConsistencyLevel::new_eventual(), + NativeConsistencyLevel::ReplicaDependant { address } => { + ConsistencyLevel::new_replica_dependant(release_string(address.to_string())) + } + } + } +} diff --git a/c/src/server/mod.rs b/c/src/server/mod.rs new file mode 100644 index 0000000000..4fe89b5313 --- /dev/null +++ b/c/src/server/mod.rs @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +pub(crate) mod consistency_level; +pub(crate) mod server_replica; +pub(crate) mod server_version; diff --git a/c/src/server/server_replica.rs b/c/src/server/server_replica.rs new file mode 100644 index 0000000000..851cf64fc6 --- /dev/null +++ b/c/src/server/server_replica.rs @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +use std::{ffi::c_char, ptr::addr_of_mut}; + +use typedb_driver::{ReplicaType, ServerReplica}; + +use crate::common::{ + iterator::{iterator_next, CIterator}, + memory::{borrow, free, release_string}, +}; + +/// Iterator over the ServerReplica corresponding to each replica of a TypeDB cluster. +pub struct ServerReplicaIterator(pub(crate) CIterator); + +/// Forwards the ServerReplicaIterator and returns the next ServerReplica if it exists, +/// or null if there are no more elements. +#[no_mangle] +pub extern "C" fn server_replica_iterator_next(it: *mut ServerReplicaIterator) -> *mut ServerReplica { + unsafe { iterator_next(addr_of_mut!((*it).0)) } +} + +/// Frees the native rust ServerReplicaIterator object. +#[no_mangle] +pub extern "C" fn server_replica_iterator_drop(it: *mut ServerReplicaIterator) { + free(it); +} + +/// Frees the native rust ServerReplica object. +#[no_mangle] +pub extern "C" fn server_replica_drop(replica_info: *mut ServerReplica) { + free(replica_info); +} + +/// Returns the id of this replica. +#[no_mangle] +pub extern "C" fn server_replica_get_id(replica_info: *const ServerReplica) -> i64 { + borrow(replica_info).id() as i64 +} + +/// Returns the address this replica is hosted at. +#[no_mangle] +pub extern "C" fn server_replica_get_address(replica_info: *const ServerReplica) -> *mut c_char { + release_string(borrow(replica_info).address().to_string()) +} + +/// Returns whether this is the primary replica of the raft cluster or any of the supporting types. +#[no_mangle] +pub extern "C" fn server_replica_get_type(replica_info: *const ServerReplica) -> ReplicaType { + borrow(replica_info).replica_type() +} + +/// Checks whether this is the primary replica of the raft cluster. +#[no_mangle] +pub extern "C" fn server_replica_is_primary(replica_info: *const ServerReplica) -> bool { + borrow(replica_info).is_primary() +} + +/// Returns the raft protocol ‘term’ of this replica. +#[no_mangle] +pub extern "C" fn server_replica_get_term(replica_info: *const ServerReplica) -> i64 { + borrow(replica_info).term() as i64 +} diff --git a/c/src/server/server_version.rs b/c/src/server/server_version.rs new file mode 100644 index 0000000000..4509be51a2 --- /dev/null +++ b/c/src/server/server_version.rs @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +use std::{ffi::c_char, ptr::null_mut}; + +use crate::common::memory::{free, release_string, string_free}; + +/// ServerVersion is an FFI representation of a full server's version specification. +#[repr(C)] +#[derive(Debug)] +pub struct ServerVersion { + distribution: *mut c_char, + version: *mut c_char, +} + +impl ServerVersion { + pub fn new(distribution: String, version: String) -> Self { + Self { distribution: release_string(distribution), version: release_string(version) } + } +} + +impl Default for ServerVersion { + fn default() -> Self { + Self { distribution: null_mut(), version: null_mut() } + } +} + +impl Drop for ServerVersion { + fn drop(&mut self) { + string_free(self.distribution); + string_free(self.version); + } +} + +/// Frees the native rust ServerVersion object +#[no_mangle] +pub extern "C" fn server_version_drop(server_version: *mut ServerVersion) { + free(server_version); +} diff --git a/c/src/transaction.rs b/c/src/transaction.rs index 8e4f31d7da..a822291ae7 100644 --- a/c/src/transaction.rs +++ b/c/src/transaction.rs @@ -19,12 +19,16 @@ use std::{ffi::c_char, ptr::null_mut}; -use typedb_driver::{ - DatabaseManager, Error, QueryOptions, Transaction, TransactionOptions, TransactionType, TypeDBDriver, -}; +use typedb_driver::{Error, QueryOptions, Transaction, TransactionOptions, TransactionType, TypeDBDriver}; -use super::memory::{borrow, borrow_mut, free, release, take_ownership}; -use crate::{answer::QueryAnswerPromise, error::try_release, memory::string_view, promise::VoidPromise}; +use crate::{ + answer::QueryAnswerPromise, + common::{ + error::try_release, + memory::{borrow, borrow_mut, free, release, string_view, take_ownership}, + promise::VoidPromise, + }, +}; /// Opens a transaction to perform read or write queries on the database connected to the session. /// @@ -39,7 +43,7 @@ pub extern "C" fn transaction_new( type_: TransactionType, options: *const TransactionOptions, ) -> *mut Transaction { - try_release(borrow(driver).transaction_with_options(string_view(database_name), type_, *borrow(options))) + try_release(borrow(driver).transaction_with_options(string_view(database_name), type_, borrow(options).clone())) } /// Performs a TypeQL query in the transaction. diff --git a/c/src/transaction_options.rs b/c/src/transaction_options.rs index af2c3797ee..48aa761e34 100644 --- a/c/src/transaction_options.rs +++ b/c/src/transaction_options.rs @@ -21,7 +21,10 @@ use std::time::Duration; use typedb_driver::TransactionOptions; -use super::memory::{borrow, borrow_mut, free, release}; +use crate::{ + common::memory::{borrow, borrow_mut, free, release}, + server::consistency_level::{native_consistency_level, ConsistencyLevel}, +}; /// Produces a new TransactionOptions object. #[no_mangle] @@ -35,7 +38,7 @@ pub extern "C" fn transaction_options_drop(options: *mut TransactionOptions) { free(options); } -/// Explicitly set a transaction timeout. +/// Explicitly sets a transaction timeout. /// If set, specifies a timeout for killing transactions automatically, preventing memory leaks in unclosed transactions. #[no_mangle] pub extern "C" fn transaction_options_set_transaction_timeout_millis( @@ -84,3 +87,34 @@ pub extern "C" fn transaction_options_has_schema_lock_acquire_timeout_millis( ) -> bool { borrow(options).schema_lock_acquire_timeout.is_some() } + +/// Explicitly sets read consistency level. +/// If set, specifies the requested consistency level of the transaction opening operation. +/// Affects only read transactions, as write and schema transactions require primary replicas. +#[no_mangle] +pub extern "C" fn transaction_options_set_read_consistency_level( + options: *mut TransactionOptions, + read_consistency_level: *const ConsistencyLevel, +) { + // TODO: This is a hacky feature - you can unset the read consistency level if you pass a nullptr. + // We should decide what is the general approach if we want to unset an option: whether we also + // introduce "unset" (a little irritating to maintain) or we consider that the user just creates + // another object of options. We hardly want to crash here if it's a nullptr. + borrow_mut(options).read_consistency_level = native_consistency_level(read_consistency_level); +} + +/// Returns the value set for the read consistency level in this TransactionOptions object. +/// If set, specifies the requested consistency level of the transaction opening operation. +/// Affects only read transactions, as write and schema transactions require primary replicas. +#[no_mangle] +pub extern "C" fn transaction_options_get_read_consistency_level( + options: *const TransactionOptions, +) -> *mut ConsistencyLevel { + release(ConsistencyLevel::from(borrow(options).read_consistency_level.clone().unwrap())) +} + +/// Checks whether the option for read consistency level was explicitly set for this TransactionOptions object. +#[no_mangle] +pub extern "C" fn transaction_options_has_read_consistency_level(options: *const TransactionOptions) -> bool { + borrow(options).read_consistency_level.is_some() +} diff --git a/c/src/user/mod.rs b/c/src/user/mod.rs new file mode 100644 index 0000000000..3d2fb9f230 --- /dev/null +++ b/c/src/user/mod.rs @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +pub(crate) mod user; +pub(crate) mod user_manager; diff --git a/c/src/user.rs b/c/src/user/user.rs similarity index 78% rename from c/src/user.rs rename to c/src/user/user.rs index 0615fc4464..30de506637 100644 --- a/c/src/user.rs +++ b/c/src/user/user.rs @@ -19,13 +19,12 @@ use std::ffi::c_char; -use typedb_driver::{Database, TypeDBDriver, User, UserManager}; +use typedb_driver::User; -use super::{ +use crate::common::{ error::unwrap_void, - memory::{borrow, free, release_string, string_view}, + memory::{borrow, free, release_string, string_view, take_ownership}, }; -use crate::memory::{take_arc, take_ownership}; /// Frees the native rust User object. #[no_mangle] @@ -36,15 +35,9 @@ pub extern "C" fn user_drop(user: *mut User) { /// Returns the name of this user. #[no_mangle] pub extern "C" fn user_get_name(user: *mut User) -> *mut c_char { - release_string(borrow(user).name.clone()) + release_string(borrow(user).name().to_string()) } -// /// Returns the number of seconds remaining till this user’s current password expires. -// #[no_mangle] -// pub extern "C" fn user_get_password_expiry_seconds(user: *mut User) -> i64 { -// borrow(user).password_expiry_seconds.unwrap_or(-1) -// } - /// Updates the password for the current authenticated user. /// /// @param user The user to update the password of - must be the current user. diff --git a/c/src/user/user_manager.rs b/c/src/user/user_manager.rs new file mode 100644 index 0000000000..a223fca4de --- /dev/null +++ b/c/src/user/user_manager.rs @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +use std::{ffi::c_char, ptr::addr_of_mut}; + +use typedb_driver::{box_stream, TypeDBDriver, User}; + +use crate::{ + common::{ + error::{try_release, try_release_optional, unwrap_or_default, unwrap_void}, + iterator::{iterator_next, CIterator}, + memory::{borrow, free, string_view}, + }, + server::consistency_level::{native_consistency_level, ConsistencyLevel}, +}; + +/// Iterator over a set of Users. +pub struct UserIterator(CIterator); + +/// Forwards the UserIterator and returns the next User if it exists, +/// or null if there are no more elements. +#[no_mangle] +pub extern "C" fn user_iterator_next(it: *mut UserIterator) -> *mut User { + unsafe { iterator_next(addr_of_mut!((*it).0)) } +} + +/// Frees the native rust UserIterator object. +#[no_mangle] +pub extern "C" fn user_iterator_drop(it: *mut UserIterator) { + free(it); +} + +/// Retrieves all users which exist on the TypeDB server. +/// +/// @param driver The TypeDBDriver object. +/// @param consistency_level The consistency level to use for the operation. Strongest possible if null. +#[no_mangle] +pub extern "C" fn users_all( + driver: *const TypeDBDriver, + consistency_level: *const ConsistencyLevel, +) -> *mut UserIterator { + let users = borrow(driver).users(); + let result = match native_consistency_level(consistency_level) { + Some(consistency_level) => users.all_with_consistency(consistency_level), + None => users.all(), + }; + try_release(result.map(|users| UserIterator(CIterator(box_stream(users.into_iter()))))) +} + +/// Checks if a user with the given name exists. +/// +/// @param driver The TypeDBDriver object. +/// @param username The username of the user. +/// @param consistency_level The consistency level to use for the operation. Strongest possible if null. +#[no_mangle] +pub extern "C" fn users_contains( + driver: *const TypeDBDriver, + username: *const c_char, + consistency_level: *const ConsistencyLevel, +) -> bool { + let users = borrow(driver).users(); + let username = string_view(username); + unwrap_or_default(match native_consistency_level(consistency_level) { + Some(consistency_level) => users.contains_with_consistency(username, consistency_level), + None => users.contains(username), + }) +} + +/// Retrieves a user with the given name. +/// +/// @param driver The TypeDBDriver object. +/// @param username The username of the user. +/// @param consistency_level The consistency level to use for the operation. Strongest possible if null. +#[no_mangle] +pub extern "C" fn users_get( + driver: *const TypeDBDriver, + username: *const c_char, + consistency_level: *const ConsistencyLevel, +) -> *mut User { + let users = borrow(driver).users(); + let username = string_view(username); + let result = match native_consistency_level(consistency_level) { + Some(consistency_level) => users.get_with_consistency(username, consistency_level), + None => users.get(username), + }; + try_release_optional(result.transpose()) +} + +/// Retrieves the username of the user who opened this connection. +/// +/// @param driver The TypeDBDriver object. +/// @param consistency_level The consistency level to use for the operation. Strongest possible if null. +#[no_mangle] +pub extern "C" fn users_get_current_user( + driver: *const TypeDBDriver, + consistency_level: *const ConsistencyLevel, +) -> *mut User { + let users = borrow(driver).users(); + let result = match native_consistency_level(consistency_level) { + Some(consistency_level) => users.get_current_user_with_consistency(consistency_level), + None => users.get_current_user(), + }; + try_release_optional(result.transpose()) +} + +/// Creates a user with the given name & password. +/// +/// @param username The username of the user to be created. +/// @param consistency_level The consistency level to use for the operation. Strongest possible if null. +#[no_mangle] +pub extern "C" fn users_create(driver: *const TypeDBDriver, username: *const c_char, password: *const c_char) { + unwrap_void(borrow(driver).users().create(string_view(username), string_view(password))); +} diff --git a/c/src/user_manager.rs b/c/src/user_manager.rs deleted file mode 100644 index fefa0f8e87..0000000000 --- a/c/src/user_manager.rs +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -use std::{ffi::c_char, ptr::addr_of_mut}; - -use typedb_driver::{box_stream, TypeDBDriver, User, UserManager}; - -use super::{ - error::{try_release, try_release_optional, unwrap_or_default, unwrap_void}, - iterator::{iterator_next, CIterator}, - memory::{borrow, free, release, string_view}, -}; -use crate::{error::try_release_string, memory::release_string}; - -/// Iterator over a set of Users -pub struct UserIterator(CIterator); - -/// Forwards the UserIterator and returns the next User if it exists, -/// or null if there are no more elements. -#[no_mangle] -pub extern "C" fn user_iterator_next(it: *mut UserIterator) -> *mut User { - unsafe { iterator_next(addr_of_mut!((*it).0)) } -} - -/// Frees the native rust UserIterator object -#[no_mangle] -pub extern "C" fn user_iterator_drop(it: *mut UserIterator) { - free(it); -} - -/// Retrieves all users which exist on the TypeDB server. -#[no_mangle] -pub extern "C" fn users_all(driver: *const TypeDBDriver) -> *mut UserIterator { - try_release(borrow(driver).users().all().map(|users| UserIterator(CIterator(box_stream(users.into_iter()))))) -} - -/// Checks if a user with the given name exists. -#[no_mangle] -pub extern "C" fn users_contains(driver: *const TypeDBDriver, username: *const c_char) -> bool { - unwrap_or_default(borrow(driver).users().contains(string_view(username))) -} - -/// Creates a user with the given name & password. -#[no_mangle] -pub extern "C" fn users_create(driver: *const TypeDBDriver, username: *const c_char, password: *const c_char) { - unwrap_void(borrow(driver).users().create(string_view(username), string_view(password))); -} - -/// Retrieves a user with the given name. -#[no_mangle] -pub extern "C" fn users_get(driver: *const TypeDBDriver, username: *const c_char) -> *mut User { - try_release_optional(borrow(driver).users().get(string_view(username)).transpose()) -} - -/// Retrieves the username of the user who opened this connection -#[no_mangle] -pub extern "C" fn users_get_current_user(driver: *const TypeDBDriver) -> *mut User { - try_release_optional(borrow(driver).users().get_current_user().transpose()) -} diff --git a/c/swig/typedb_driver_java.swg b/c/swig/typedb_driver_java.swg index f2617c8ebb..473bb8a609 100644 --- a/c/swig/typedb_driver_java.swg +++ b/c/swig/typedb_driver_java.swg @@ -88,9 +88,9 @@ %nojavaexception transaction_options_get_schema_lock_acquire_timeout_millis; %nojavaexception transaction_options_set_schema_lock_acquire_timeout_millis; %nojavaexception transaction_options_has_schema_lock_acquire_timeout_millis; -%nojavaexception transaction_options_get_parallel; -%nojavaexception transaction_options_set_parallel; -%nojavaexception transaction_options_has_parallel; +%nojavaexception transaction_options_get_read_consistency_level; +%nojavaexception transaction_options_set_read_consistency_level; +%nojavaexception transaction_options_has_read_consistency_level; %nojavaexception query_options_new; %nojavaexception query_options_get_include_instance_types; @@ -104,21 +104,39 @@ %nojavaexception error_message; %nojavaexception driver_is_open; +// TODO: Remove from this list if we start making network calls to retrieve this +%nojavaexception driver_primary_replica; + +%nojavaexception driver_options_new; +%nojavaexception driver_options_set_is_tls_enabled; +%nojavaexception driver_options_get_is_tls_enabled; +%nojavaexception driver_options_set_tls_root_ca_path; +%nojavaexception driver_options_get_tls_root_ca_path; +%nojavaexception driver_options_has_tls_root_ca_path; +%nojavaexception driver_options_set_use_replication; +%nojavaexception driver_options_get_use_replication; +%nojavaexception driver_options_set_primary_failover_retries; +%nojavaexception driver_options_get_primary_failover_retries; +%nojavaexception driver_options_set_replica_discovery_attempts; +%nojavaexception driver_options_get_replica_discovery_attempts; +%nojavaexception driver_options_has_replica_discovery_attempts; %nojavaexception transaction_is_open; %nojavaexception user_get_name; %nojavaexception user_get_password_expiry_seconds; -//%nojavaexception replica_info_get_server; -//%nojavaexception replica_info_is_primary; -//%nojavaexception replica_info_is_preferred; -//%nojavaexception replica_info_get_term; +%nojavaexception server_replica_get_address; +%nojavaexception server_replica_is_primary; +%nojavaexception server_replica_get_id; +%nojavaexception server_replica_get_type; +%nojavaexception server_replica_get_term; %nojavaexception database_get_name; -//%nojavaexception database_get_replicas_info; -//%nojavaexception database_get_primary_replica_info; -//%nojavaexception database_get_preferred_replica_info; + +%nojavaexception consistency_level_strong; +%nojavaexception consistency_level_eventual; +%nojavaexception consistency_level_replica_dependant; %nojavaexception concept_is_entity; %nojavaexception concept_is_relation; @@ -186,6 +204,9 @@ %nojavaexception query_answer_is_concept_row_stream; %nojavaexception query_answer_is_concept_document_stream; +%nojavaexception ServerVersion::distribution; +%nojavaexception ServerVersion::version; + %nojavaexception StringPair::_0; %nojavaexception StringPair::_1; @@ -207,6 +228,9 @@ %nojavaexception Duration::days; %nojavaexception Duration::nanos; +%nojavaexception ConsistencyLevel::tag; +%nojavaexception ConsistencyLevel::address; + /* director constructors do not throw */ %nojavaexception TransactionCallbackDirector; @@ -215,6 +239,7 @@ %nojavaexception ~ConceptIterator; %nojavaexception ~ConceptRow; %nojavaexception ~ConceptRowIterator; +%nojavaexception ~ConsistencyLevel; %nojavaexception ~DriverOptions; %nojavaexception ~Credentials; %nojavaexception ~Database; @@ -224,7 +249,9 @@ %nojavaexception ~Decimal; %nojavaexception ~Duration; %nojavaexception ~Error; -//%nojavaexception ~ReplicaInfo; +%nojavaexception ~ServerReplica; +%nojavaexception ~ServerReplicaIterator; +%nojavaexception ~ServerVersion; %nojavaexception ~StringIterator; %nojavaexception ~StringAndOptValue; %nojavaexception ~StringAndOptValueIterator; @@ -384,7 +411,7 @@ %iterator(StringAndOptValue, string_and_opt_value) %iterator(User, user) %iterator(Database, database) -//%iterator(ReplicaInfo, replica_info) +%iterator(ServerReplica, server_replica) %typemap(javabody) QueryAnswer %{ private transient long swigCPtr; diff --git a/c/tests/assembly/test.c b/c/tests/assembly/test.c index 09d74724d8..0b6c4c7df5 100644 --- a/c/tests/assembly/test.c +++ b/c/tests/assembly/test.c @@ -49,7 +49,7 @@ int main() { bool success = false; - connection = driver_open_core(TYPEDB_CORE_ADDRESS, DRIVER_LANG); + connection = driver_new_core(TYPEDB_CORE_ADDRESS, DRIVER_LANG); if (FAILED()) goto cleanup; databaseManager = database_manager_new(connection); diff --git a/c/tests/integration/driver_test.c b/c/tests/integration/driver_test.c index 2efd963d02..f12e963d78 100644 --- a/c/tests/integration/driver_test.c +++ b/c/tests/integration/driver_test.c @@ -33,7 +33,7 @@ bool test_database_management() { bool success = false; - driver = driver_open_core(TYPEDB_CORE_ADDRESS, DRIVER_LANG); + driver = driver_new_core(TYPEDB_CORE_ADDRESS, DRIVER_LANG); if (FAILED()) goto cleanup; databaseManager = database_manager_new(driver); @@ -87,7 +87,7 @@ bool test_query_schema() { bool success = false; // Set up connection & database - connection = driver_open_core(TYPEDB_CORE_ADDRESS, DRIVER_LANG); + connection = driver_new_core(TYPEDB_CORE_ADDRESS, DRIVER_LANG); if (FAILED()) goto cleanup; databaseManager = database_manager_new(connection); @@ -160,7 +160,7 @@ bool test_query_data() { bool success = false; // Set up connection & database - connection = driver_open_core(TYPEDB_CORE_ADDRESS, DRIVER_LANG); + connection = driver_new_core(TYPEDB_CORE_ADDRESS, DRIVER_LANG); if (FAILED()) goto cleanup; databaseManager = database_manager_new(connection); @@ -253,7 +253,7 @@ bool test_concept_api_schema() { bool success = false; - connection = driver_open_core(TYPEDB_CORE_ADDRESS, DRIVER_LANG); + connection = driver_new_core(TYPEDB_CORE_ADDRESS, DRIVER_LANG); if (FAILED()) goto cleanup; databaseManager = database_manager_new(connection); @@ -338,7 +338,7 @@ bool test_concept_api_data() { bool success = false; - connection = driver_open_core(TYPEDB_CORE_ADDRESS, DRIVER_LANG); + connection = driver_new_core(TYPEDB_CORE_ADDRESS, DRIVER_LANG); if (FAILED()) goto cleanup; databaseManager = database_manager_new(connection); diff --git a/c/typedb_driver.i b/c/typedb_driver.i index f95d874103..0962b0b999 100644 --- a/c/typedb_driver.i +++ b/c/typedb_driver.i @@ -41,7 +41,9 @@ extern "C" { %nodefaultctor; -%ignore driver_open; // use `driver_open_with_description` +%ignore driver_new; // use `driver_new_with_description` +%ignore driver_new_with_addresses; // use `driver_new_with_addresses_with_description` +%ignore driver_new_with_address_translation; // use `driver_new_with_address_translation_with_description` %define %dropproxy(Type, function_prefix) struct Type {}; @@ -60,6 +62,7 @@ struct Type {}; %dropproxy(DriverOptions, driver_options) %dropproxy(TransactionOptions, transaction_options) %dropproxy(QueryOptions, query_options) +%dropproxydefined(ServerVersion, server_version) #define typedb_driver_drop driver_close #define transaction_drop transaction_close @@ -70,8 +73,8 @@ struct Type {}; %dropproxy(Database, database) %dropproxy(DatabaseIterator, database_iterator) -//%dropproxy(ReplicaInfo, replica_info) -//%dropproxy(ReplicaInfoIterator, replica_info_iterator) +%dropproxy(ServerReplica, server_replica) +%dropproxy(ServerReplicaIterator, server_replica_iterator) %dropproxy(User, user) %dropproxy(UserIterator, user_iterator) @@ -81,11 +84,11 @@ struct Type {}; %dropproxy(ConceptRow, concept_row) %dropproxy(ConceptRowIterator, concept_row_iterator) +%dropproxydefined(ConsistencyLevel, consistency_level) %dropproxydefined(DatetimeAndTimeZone, datetime_and_time_zone) %dropproxydefined(StringAndOptValue, string_and_opt_value) %dropproxy(StringAndOptValueIterator, string_and_opt_value_iterator) - %dropproxy(StringIterator, string_iterator) %dropproxy(QueryAnswer, query_answer) @@ -140,8 +143,8 @@ void transaction_on_close_register(const Transaction* transaction, TransactionCa } %} -%delobject database_delete; - +%newobject transaction_new; +%newobject transaction_query; %delobject transaction_commit; %typemap(newfree) char* "string_free($1);"; @@ -155,9 +158,6 @@ void transaction_on_close_register(const Transaction* transaction, TransactionCa %newobject concept_row_get_index; %newobject concept_row_to_string; -%newobject value_get_string; -%newobject value_get_datetime_tz; - %newobject query_answer_into_rows; %newobject query_answer_into_documents; %delobject query_answer_into_rows; @@ -176,19 +176,23 @@ void transaction_on_close_register(const Transaction* transaction, TransactionCa %newobject concept_try_get_value; %newobject credentials_new; -%newobject driver_open_with_description; +%newobject driver_new_with_description; +%newobject driver_new_with_addresses_with_description; +%newobject driver_new_with_address_translation_with_description; +%newobject driver_server_version; +%newobject driver_primary_replica; +%newobject driver_replicas; + %newobject driver_options_new; +%newobject driver_options_get_tls_root_ca_path; +%newobject transaction_options_get_read_consistency_level; %newobject database_get_name; %newobject database_schema; %newobject database_type_schema; +%delobject database_delete; -//%newobject database_get_preferred_replica_info; -//%newobject database_get_primary_replica_info; -//%newobject database_get_replicas_info; -// -//%newobject replica_info_get_server; -//%newobject replica_info_iterator_next; +%newobject server_replica_get_address; %newobject databases_all; %newobject databases_get; @@ -203,17 +207,14 @@ void transaction_on_close_register(const Transaction* transaction, TransactionCa %newobject concept_iterator_next; %newobject concept_row_iterator_next; %newobject database_iterator_next; +%newobject server_replica_iterator_next; %newobject string_iterator_next; %newobject string_and_opt_value_iterator_next; %newobject user_iterator_next; -%newobject transaction_new; -%newobject transaction_query; - %newobject users_all; %newobject users_get_current_user; %newobject users_get; - %newobject user_get_name; %delobject user_delete; diff --git a/cpp/include/typedb/user/user_manager.hpp b/cpp/include/typedb/user/user_manager.hpp index 6c27e1ed6f..59b1c31608 100644 --- a/cpp/include/typedb/user/user_manager.hpp +++ b/cpp/include/typedb/user/user_manager.hpp @@ -43,7 +43,7 @@ class UserManager { * driver.users.contains(username); * * - * @param username The user name to be checked + * @param username The username to be checked */ bool contains(const std::string& username) const; diff --git a/csharp/Api/User/IUserManager.cs b/csharp/Api/User/IUserManager.cs index ead4a16dd1..4406a05e11 100644 --- a/csharp/Api/User/IUserManager.cs +++ b/csharp/Api/User/IUserManager.cs @@ -36,7 +36,7 @@ public interface IUserManager * driver.Users.Contains(username); * * - * @param username The user name to be checked + * @param username The username to be checked */ bool Contains(string username); diff --git a/dependencies/typedb/artifacts.bzl b/dependencies/typedb/artifacts.bzl index 8812c25fe9..bc87bfb4a2 100644 --- a/dependencies/typedb/artifacts.bzl +++ b/dependencies/typedb/artifacts.bzl @@ -25,7 +25,7 @@ def typedb_artifact(): artifact_name = "typedb-all-{platform}-{version}.{ext}", tag_source = deployment["artifact"]["release"]["download"], commit_source = deployment["artifact"]["snapshot"]["download"], - commit = "c6e8b678b6fd1e47ad9a6af0f0e6e9cb92e0ad38" + commit = "1fb9cafe5345c91b7bfae2207f505b6dcdc5f016" ) #def typedb_cloud_artifact(): @@ -37,8 +37,3 @@ def typedb_artifact(): # commit_source = deployment_private["artifact"]["snapshot"]["download"], # tag = "e4e4fee9d488e2a6e89e29716b98e3213d228809", # ) - -#maven_artifacts = { -# 'com.typedb:typedb-runner': '2.28.3', -# 'com.typedb:typedb-cloud-runner': '2.28.3', -#} diff --git a/dependencies/typedb/repositories.bzl b/dependencies/typedb/repositories.bzl index 91814964f2..7f291a64a5 100644 --- a/dependencies/typedb/repositories.bzl +++ b/dependencies/typedb/repositories.bzl @@ -25,15 +25,17 @@ def typedb_dependencies(): ) def typedb_protocol(): + # TODO: Return ref after merge to master git_repository( name = "typedb_protocol", remote = "https://github.com/typedb/typedb-protocol", - commit = "38f66a1cc4db3b7a301676f50800e9530ac5c8a3", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_protocol + commit = "d1b71067c7e78364c69701625ea3f6d6b1f24a9d", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_protocol ) def typedb_behaviour(): + # TODO: Update ref after merge to master git_repository( name = "typedb_behaviour", remote = "https://github.com/typedb/typedb-behaviour", - commit = "d05a284e445a53de96868c3d27a35ffbef58a077", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_behaviour + commit = "bd271460ce37a54d4d1b48e00a9eb78153b357e4", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_behaviour ) diff --git a/docs/modules/ROOT/partials/c/connection/database.adoc b/docs/modules/ROOT/partials/c/connection/database.adoc index 9a1b004125..70c4b2d076 100644 --- a/docs/modules/ROOT/partials/c/connection/database.adoc +++ b/docs/modules/ROOT/partials/c/connection/database.adoc @@ -112,7 +112,7 @@ struct ReplicaInfoIterator* database_get_replicas_info(const struct Database* da -Set of ``Replica`` instances for this database. Only works in TypeDB Cloud +Set of ``ServerReplica`` instances for this database. Only works in TypeDB Cloud [caption=""] .Returns diff --git a/docs/modules/ROOT/partials/c/session/options.adoc b/docs/modules/ROOT/partials/c/session/options.adoc index 91923d882b..386380f351 100644 --- a/docs/modules/ROOT/partials/c/session/options.adoc +++ b/docs/modules/ROOT/partials/c/session/options.adoc @@ -525,7 +525,7 @@ void options_set_transaction_timeout_millis(struct Options* options, int64_t tim -Explicitly set a transaction timeout. If set, specifies a timeout for killing transactions automatically, preventing memory leaks in unclosed transactions. +Explicitly sets a transaction timeout. If set, specifies a timeout for killing transactions automatically, preventing memory leaks in unclosed transactions. [caption=""] .Returns diff --git a/docs/modules/ROOT/partials/cpp/connection/Database.adoc b/docs/modules/ROOT/partials/cpp/connection/Database.adoc index f38fcec906..e6427f9124 100644 --- a/docs/modules/ROOT/partials/cpp/connection/Database.adoc +++ b/docs/modules/ROOT/partials/cpp/connection/Database.adoc @@ -106,7 +106,7 @@ ReplicaInfoIterable TypeDB::Database::replicas() -Set of ``Replica`` instances for this database. Only works in TypeDB Cloud +Set of ``ServerReplica`` instances for this database. Only works in TypeDB Cloud [caption=""] diff --git a/docs/modules/ROOT/partials/cpp/session/Options.adoc b/docs/modules/ROOT/partials/cpp/session/Options.adoc index 0109dd1023..de8d35c991 100644 --- a/docs/modules/ROOT/partials/cpp/session/Options.adoc +++ b/docs/modules/ROOT/partials/cpp/session/Options.adoc @@ -581,7 +581,7 @@ Options& TypeDB::Options::transactionTimeoutMillis(int64_t timeoutMillis) -Explicitly set a transaction timeout. If set, specifies a timeout for killing transactions automatically, preventing memory leaks in unclosed transactions. +Explicitly sets a transaction timeout. If set, specifies a timeout for killing transactions automatically, preventing memory leaks in unclosed transactions. [caption=""] diff --git a/docs/modules/ROOT/partials/csharp/connection/IDatabase.adoc b/docs/modules/ROOT/partials/csharp/connection/IDatabase.adoc index aa892a4f30..1cb591aa88 100644 --- a/docs/modules/ROOT/partials/csharp/connection/IDatabase.adoc +++ b/docs/modules/ROOT/partials/csharp/connection/IDatabase.adoc @@ -38,7 +38,7 @@ ISet< IReplica > GetReplicas() -Set of ``Replica`` instances for this database. Only works in TypeDB Cloud +Set of ``ServerReplica`` instances for this database. Only works in TypeDB Cloud [caption=""] diff --git a/docs/modules/ROOT/partials/csharp/session/TypeDBOptions.adoc b/docs/modules/ROOT/partials/csharp/session/TypeDBOptions.adoc index e2f4c3e88b..324367fe59 100644 --- a/docs/modules/ROOT/partials/csharp/session/TypeDBOptions.adoc +++ b/docs/modules/ROOT/partials/csharp/session/TypeDBOptions.adoc @@ -555,7 +555,7 @@ TypeDBOptions TransactionTimeoutMillis(int transactionTimeoutMillis) -Explicitly set a transaction timeout. If set, specifies a timeout for killing transactions automatically, preventing memory leaks in unclosed transactions. +Explicitly sets a transaction timeout. If set, specifies a timeout for killing transactions automatically, preventing memory leaks in unclosed transactions. [caption=""] diff --git a/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.Eventual.adoc b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.Eventual.adoc new file mode 100644 index 0000000000..50d0f59dd1 --- /dev/null +++ b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.Eventual.adoc @@ -0,0 +1,66 @@ +[#_ConsistencyLevel_Eventual] +=== ConsistencyLevel.Eventual + +*Package*: `com.typedb.driver.api` + +Allow stale reads from any replica. May not reflect latest writes. The execution may be eventually faster compared to other consistency levels. + +// tag::methods[] +[#_ConsistencyLevel_Eventual_nativeValue_] +==== nativeValue + +[source,java] +---- +public com.typedb.driver.jni.ConsistencyLevel nativeValue() +---- + + + +[caption=""] +.Returns +`public com.typedb.driver.jni.ConsistencyLevel` + +[#_ConsistencyLevel_Eventual_nativeValue_ConsistencyLevel] +==== nativeValue + +[source,java] +---- +public static com.typedb.driver.jni.ConsistencyLevel nativeValue​(ConsistencyLevel consistencyLevel) +---- + + + +[caption=""] +.Returns +`public static com.typedb.driver.jni.ConsistencyLevel` + +[#_ConsistencyLevel_Eventual_of_com_typedb_driver_jni_ConsistencyLevel] +==== of + +[source,java] +---- +public static ConsistencyLevel of​(com.typedb.driver.jni.ConsistencyLevel nativeValue) +---- + + + +[caption=""] +.Returns +`public static ConsistencyLevel` + +[#_ConsistencyLevel_Eventual_toString_] +==== toString + +[source,java] +---- +public java.lang.String toString() +---- + + + +[caption=""] +.Returns +`public java.lang.String` + +// end::methods[] + diff --git a/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.ReplicaDependant.adoc b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.ReplicaDependant.adoc new file mode 100644 index 0000000000..509673a628 --- /dev/null +++ b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.ReplicaDependant.adoc @@ -0,0 +1,94 @@ +[#_ConsistencyLevel_ReplicaDependant] +=== ConsistencyLevel.ReplicaDependant + +*Package*: `com.typedb.driver.api` + +The operation is executed against the provided replica address only. Its guarantees depend on the replica selected. + +// tag::methods[] +[#_ConsistencyLevel_ReplicaDependant_ReplicaDependant_java_lang_String] +==== ReplicaDependant + +[source,java] +---- +public ReplicaDependant​(java.lang.String address) +---- + + + +[caption=""] +.Returns +`public` + +[#_ConsistencyLevel_ReplicaDependant_getAddress_] +==== getAddress + +[source,java] +---- +public java.lang.String getAddress() +---- + +Retrieves the address of the replica this consistency level depends on. + +[caption=""] +.Returns +`public java.lang.String` + +[#_ConsistencyLevel_ReplicaDependant_nativeValue_] +==== nativeValue + +[source,java] +---- +public com.typedb.driver.jni.ConsistencyLevel nativeValue() +---- + + + +[caption=""] +.Returns +`public com.typedb.driver.jni.ConsistencyLevel` + +[#_ConsistencyLevel_ReplicaDependant_nativeValue_ConsistencyLevel] +==== nativeValue + +[source,java] +---- +public static com.typedb.driver.jni.ConsistencyLevel nativeValue​(ConsistencyLevel consistencyLevel) +---- + + + +[caption=""] +.Returns +`public static com.typedb.driver.jni.ConsistencyLevel` + +[#_ConsistencyLevel_ReplicaDependant_of_com_typedb_driver_jni_ConsistencyLevel] +==== of + +[source,java] +---- +public static ConsistencyLevel of​(com.typedb.driver.jni.ConsistencyLevel nativeValue) +---- + + + +[caption=""] +.Returns +`public static ConsistencyLevel` + +[#_ConsistencyLevel_ReplicaDependant_toString_] +==== toString + +[source,java] +---- +public java.lang.String toString() +---- + + + +[caption=""] +.Returns +`public java.lang.String` + +// end::methods[] + diff --git a/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.Strong.adoc b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.Strong.adoc new file mode 100644 index 0000000000..273e7f314c --- /dev/null +++ b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.Strong.adoc @@ -0,0 +1,66 @@ +[#_ConsistencyLevel_Strong] +=== ConsistencyLevel.Strong + +*Package*: `com.typedb.driver.api` + +Strongest consistency, always up-to-date due to the guarantee of the primary replica usage. May require more time for operation execution. + +// tag::methods[] +[#_ConsistencyLevel_Strong_nativeValue_] +==== nativeValue + +[source,java] +---- +public com.typedb.driver.jni.ConsistencyLevel nativeValue() +---- + + + +[caption=""] +.Returns +`public com.typedb.driver.jni.ConsistencyLevel` + +[#_ConsistencyLevel_Strong_nativeValue_ConsistencyLevel] +==== nativeValue + +[source,java] +---- +public static com.typedb.driver.jni.ConsistencyLevel nativeValue​(ConsistencyLevel consistencyLevel) +---- + + + +[caption=""] +.Returns +`public static com.typedb.driver.jni.ConsistencyLevel` + +[#_ConsistencyLevel_Strong_of_com_typedb_driver_jni_ConsistencyLevel] +==== of + +[source,java] +---- +public static ConsistencyLevel of​(com.typedb.driver.jni.ConsistencyLevel nativeValue) +---- + + + +[caption=""] +.Returns +`public static ConsistencyLevel` + +[#_ConsistencyLevel_Strong_toString_] +==== toString + +[source,java] +---- +public java.lang.String toString() +---- + + + +[caption=""] +.Returns +`public java.lang.String` + +// end::methods[] + diff --git a/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.adoc b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.adoc new file mode 100644 index 0000000000..85400faaf7 --- /dev/null +++ b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.adoc @@ -0,0 +1,66 @@ +[#_ConsistencyLevel] +=== ConsistencyLevel + +*Package*: `com.typedb.driver.api` + +Consistency levels of operations against a distributed server. All driver methods have default recommended values, however, readonly operations can be configured in order to potentially speed up the execution (introducing risks of stale data) or test a specific replica. This setting does not affect clusters with a single node. + +// tag::methods[] +[#_ConsistencyLevel_ConsistencyLevel_] +==== ConsistencyLevel + +[source,java] +---- +public ConsistencyLevel() +---- + + + +[caption=""] +.Returns +`public` + +[#_ConsistencyLevel_nativeValue_] +==== nativeValue + +[source,java] +---- +public abstract com.typedb.driver.jni.ConsistencyLevel nativeValue() +---- + + + +[caption=""] +.Returns +`public abstract com.typedb.driver.jni.ConsistencyLevel` + +[#_ConsistencyLevel_nativeValue_ConsistencyLevel] +==== nativeValue + +[source,java] +---- +public static com.typedb.driver.jni.ConsistencyLevel nativeValue​(ConsistencyLevel consistencyLevel) +---- + + + +[caption=""] +.Returns +`public static com.typedb.driver.jni.ConsistencyLevel` + +[#_ConsistencyLevel_of_com_typedb_driver_jni_ConsistencyLevel] +==== of + +[source,java] +---- +public static ConsistencyLevel of​(com.typedb.driver.jni.ConsistencyLevel nativeValue) +---- + + + +[caption=""] +.Returns +`public static ConsistencyLevel` + +// end::methods[] + diff --git a/docs/modules/ROOT/partials/java/connection/Database.adoc b/docs/modules/ROOT/partials/java/connection/Database.adoc index 951c504ec3..9fdd1a5301 100644 --- a/docs/modules/ROOT/partials/java/connection/Database.adoc +++ b/docs/modules/ROOT/partials/java/connection/Database.adoc @@ -30,10 +30,45 @@ database.delete() [#_Database_exportToFile_java_lang_String_java_lang_String] ==== exportToFile +[source,java] +---- +default void exportToFile​(java.lang.String schemaFilePath, + java.lang.String dataFilePath) + throws TypeDBDriverException +---- + +Export a database into a schema definition and a data files saved to the disk, using default strong consistency. This is a blocking operation and may take a significant amount of time depending on the database size. See <<#_exportToFile_java_lang_String_java_lang_String_com_typedb_driver_api_ConsistencyLevel,``exportToFile(String, String, ConsistencyLevel)``>> for more details and options. + + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `schemaFilePath` a| The path to the schema definition file to be created a| `java.lang.String` +a| `dataFilePath` a| The path to the data file to be created a| `java.lang.String` +|=== + +[caption=""] +.Returns +`void` + +[caption=""] +.Code examples +[source,java] +---- +database.exportToFile("schema.typeql", "data.typedb") +---- + +[#_Database_exportToFile_java_lang_String_java_lang_String_ConsistencyLevel] +==== exportToFile + [source,java] ---- void exportToFile​(java.lang.String schemaFilePath, - java.lang.String dataFilePath) + java.lang.String dataFilePath, + ConsistencyLevel consistencyLevel) throws TypeDBDriverException ---- @@ -48,6 +83,7 @@ Export a database into a schema definition and a data files saved to the disk. T |Name |Description |Type a| `schemaFilePath` a| The path to the schema definition file to be created a| `java.lang.String` a| `dataFilePath` a| The path to the data file to be created a| `java.lang.String` +a| `consistencyLevel` a| The consistency level to use for the operation a| `ConsistencyLevel` |=== [caption=""] @@ -58,7 +94,7 @@ a| `dataFilePath` a| The path to the data file to be created a| `java.lang.Strin .Code examples [source,java] ---- -database.exportToFile("schema.typeql", "data.typedb") +database.exportToFile("schema.typeql", "data.typedb", ConsistencyLevel.Strong) ---- [#_Database_name_] @@ -70,25 +106,66 @@ database.exportToFile("schema.typeql", "data.typedb") java.lang.String name() ---- -The database name as a string. +The database name as a string. + [caption=""] .Returns `java.lang.String` +[caption=""] +.Code examples +[source,java] +---- +database.name() +---- + [#_Database_schema_] ==== schema [source,java] ---- @CheckReturnValue -java.lang.String schema() +default java.lang.String schema() + throws TypeDBDriverException +---- + +A full schema text as a valid TypeQL define query string, using default strong consistency. See <<#_schema_com_typedb_driver_api_ConsistencyLevel,``schema(ConsistencyLevel)``>> for more details and options. + + +[caption=""] +.Returns +`java.lang.String` + +[caption=""] +.Code examples +[source,java] +---- +database.schema() +---- + +[#_Database_schema_ConsistencyLevel] +==== schema + +[source,java] +---- +@CheckReturnValue +java.lang.String schema​(ConsistencyLevel consistencyLevel) throws TypeDBDriverException ---- A full schema text as a valid TypeQL define query string. +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `consistencyLevel` a| The consistency level to use for the operation a| `ConsistencyLevel` +|=== + [caption=""] .Returns `java.lang.String` @@ -97,7 +174,7 @@ A full schema text as a valid TypeQL define query string. .Code examples [source,java] ---- -database.schema() +database.schema(ConsistencyLevel.Strong) ---- [#_Database_typeSchema_] @@ -106,13 +183,46 @@ database.schema() [source,java] ---- @CheckReturnValue -java.lang.String typeSchema() +default java.lang.String typeSchema() + throws TypeDBDriverException +---- + +The types in the schema as a valid TypeQL define query string, using default strong consistency. See <<#_typeSchema_com_typedb_driver_api_ConsistencyLevel,``typeSchema(ConsistencyLevel)``>> for more details and options. + + +[caption=""] +.Returns +`java.lang.String` + +[caption=""] +.Code examples +[source,java] +---- +database.typeSchema() +---- + +[#_Database_typeSchema_ConsistencyLevel] +==== typeSchema + +[source,java] +---- +@CheckReturnValue +java.lang.String typeSchema​(ConsistencyLevel consistencyLevel) throws TypeDBDriverException ---- The types in the schema as a valid TypeQL define query string. +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `consistencyLevel` a| The consistency level to use for the operation a| `ConsistencyLevel` +|=== + [caption=""] .Returns `java.lang.String` @@ -121,7 +231,7 @@ The types in the schema as a valid TypeQL define query string. .Code examples [source,java] ---- -database.typeSchema() +database.typeSchema(ConsistencyLevel.Strong) ---- // end::methods[] diff --git a/docs/modules/ROOT/partials/java/connection/DatabaseManager.adoc b/docs/modules/ROOT/partials/java/connection/DatabaseManager.adoc index 091d097210..57071a0131 100644 --- a/docs/modules/ROOT/partials/java/connection/DatabaseManager.adoc +++ b/docs/modules/ROOT/partials/java/connection/DatabaseManager.adoc @@ -12,13 +12,46 @@ Provides access to all database management methods. [source,java] ---- @CheckReturnValue -java.util.List all() +default java.util.List all() + throws TypeDBDriverException +---- + +Retrieves all databases present on the TypeDB server, using default strong consistency. See <<#_all_com_typedb_driver_api_ConsistencyLevel,``all(ConsistencyLevel)``>> for more details and options. + + +[caption=""] +.Returns +`java.util.List` + +[caption=""] +.Code examples +[source,java] +---- +driver.databases().all() +---- + +[#_DatabaseManager_all_ConsistencyLevel] +==== all + +[source,java] +---- +@CheckReturnValue +java.util.List all​(ConsistencyLevel consistencyLevel) throws TypeDBDriverException ---- Retrieves all databases present on the TypeDB server. +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `consistencyLevel` a| The consistency level to use for the operation a| `ConsistencyLevel` +|=== + [caption=""] .Returns `java.util.List` @@ -27,7 +60,7 @@ Retrieves all databases present on the TypeDB server. .Code examples [source,java] ---- -driver.databases().all() +driver.databases().all(ConsistencyLevel.Strong) ---- [#_DatabaseManager_contains_java_lang_String] @@ -36,7 +69,41 @@ driver.databases().all() [source,java] ---- @CheckReturnValue -boolean contains​(java.lang.String name) +default boolean contains​(java.lang.String name) + throws TypeDBDriverException +---- + +Checks if a database with the given name exists, using default strong consistency. See <<#_contains_java_lang_String_com_typedb_driver_api_ConsistencyLevel,``contains(String, ConsistencyLevel)``>> for more details and options. + + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `name` a| The database name to be checked a| `java.lang.String` +|=== + +[caption=""] +.Returns +`boolean` + +[caption=""] +.Code examples +[source,java] +---- +driver.databases().contains(name) +---- + +[#_DatabaseManager_contains_java_lang_String_ConsistencyLevel] +==== contains + +[source,java] +---- +@CheckReturnValue +boolean contains​(java.lang.String name, + ConsistencyLevel consistencyLevel) throws TypeDBDriverException ---- @@ -50,6 +117,7 @@ Checks if a database with the given name exists. |=== |Name |Description |Type a| `name` a| The database name to be checked a| `java.lang.String` +a| `consistencyLevel` a| The consistency level to use for the operation a| `ConsistencyLevel` |=== [caption=""] @@ -60,7 +128,7 @@ a| `name` a| The database name to be checked a| `java.lang.String` .Code examples [source,java] ---- -driver.databases().contains(name) +driver.databases().contains(name, ConsistencyLevel.Strong) ---- [#_DatabaseManager_create_java_lang_String] @@ -72,7 +140,7 @@ void create​(java.lang.String name) throws TypeDBDriverException ---- -Create a database with the given name. +Creates a database with the given name. [caption=""] @@ -101,11 +169,11 @@ driver.databases().create(name) [source,java] ---- @CheckReturnValue -Database get​(java.lang.String name) - throws TypeDBDriverException +default Database get​(java.lang.String name) + throws TypeDBDriverException ---- -Retrieve the database with the given name. +Retrieves the database with the given name, using default strong consistency. See <<#_get_java_lang_String_com_typedb_driver_api_ConsistencyLevel,``get(String, ConsistencyLevel)``>> for more details and options. [caption=""] @@ -128,6 +196,41 @@ a| `name` a| The name of the database to retrieve a| `java.lang.String` driver.databases().get(name) ---- +[#_DatabaseManager_get_java_lang_String_ConsistencyLevel] +==== get + +[source,java] +---- +@CheckReturnValue +Database get​(java.lang.String name, + ConsistencyLevel consistencyLevel) + throws TypeDBDriverException +---- + +Retrieves the database with the given name. + + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `name` a| The name of the database to retrieve a| `java.lang.String` +a| `consistencyLevel` a| The consistency level to use for the operation a| `ConsistencyLevel` +|=== + +[caption=""] +.Returns +`Database` + +[caption=""] +.Code examples +[source,java] +---- +driver.databases().get(name, ConsistencyLevel.Strong) +---- + [#_DatabaseManager_importFromFile_java_lang_String_java_lang_String_java_lang_String] ==== importFromFile @@ -139,7 +242,7 @@ void importFromFile​(java.lang.String name, throws TypeDBDriverException ---- -Create a database with the given name based on previously exported another database's data loaded from a file. This is a blocking operation and may take a significant amount of time depending on the database size. +Creates a database with the given name based on previously exported another database's data loaded from a file. This is a blocking operation and may take a significant amount of time depending on the database size. [caption=""] diff --git a/docs/modules/ROOT/partials/java/connection/Driver.adoc b/docs/modules/ROOT/partials/java/connection/Driver.adoc index 51de34edcc..abb1ebc1e9 100644 --- a/docs/modules/ROOT/partials/java/connection/Driver.adoc +++ b/docs/modules/ROOT/partials/java/connection/Driver.adoc @@ -38,7 +38,7 @@ Closes the driver. Before instantiating a new driver, the driver that’s curren .Code examples [source,java] ---- -driver.close() +driver.close(); ---- [#_Driver_databases_] @@ -50,12 +50,51 @@ driver.close() DatabaseManager databases() ---- -The ``DatabaseManager`` for this connection, providing access to database management methods. +The ``DatabaseManager`` for this connection, providing access to database management methods. + [caption=""] .Returns `DatabaseManager` +[caption=""] +.Code examples +[source,java] +---- +driver.databases(); +---- + +[#_Driver_deregisterReplica_long] +==== deregisterReplica + +[source,java] +---- +void deregisterReplica​(long replicaID) +---- + +Deregisters a replica from the cluster the driver is currently connected to. This replica will no longer play a raft role in this cluster. + + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `replicaID` a| The numeric identifier of the deregistered replica a| `long` +|=== + +[caption=""] +.Returns +`void` + +[caption=""] +.Code examples +[source,java] +---- +driver.deregisterReplica(2); +---- + [#_Driver_isOpen_] ==== isOpen @@ -79,6 +118,140 @@ Checks whether this connection is presently open. driver.isOpen(); ---- +[#_Driver_primaryReplica_] +==== primaryReplica + +[source,java] +---- +@CheckReturnValue +java.util.Optional primaryReplica() +---- + +Returns the primary replica for this driver connection. + + +[caption=""] +.Returns +`java.util.Optional` + +[caption=""] +.Code examples +[source,java] +---- +driver.primaryReplica(); +---- + +[#_Driver_registerReplica_long_java_lang_String] +==== registerReplica + +[source,java] +---- +void registerReplica​(long replicaID, + java.lang.String address) +---- + +Registers a new replica in the cluster the driver is currently connected to. The registered replica will become available eventually, depending on the behavior of the whole cluster. To register a replica, its clustering address should be passed, not the connection address. + + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `replicaID` a| The numeric identifier of the new replica a| `long` +a| `address` a| The address(es) of the TypeDB replica as a string a| `java.lang.String` +|=== + +[caption=""] +.Returns +`void` + +[caption=""] +.Code examples +[source,java] +---- +driver.registerReplica(2, "127.0.0.1:11729"); +---- + +[#_Driver_replicas_] +==== replicas + +[source,java] +---- +@CheckReturnValue +java.util.Set replicas() +---- + +Set of ``Replica`` instances for this driver connection. + + +[caption=""] +.Returns +`java.util.Set` + +[caption=""] +.Code examples +[source,java] +---- +driver.replicas(); +---- + +[#_Driver_serverVersion_] +==== serverVersion + +[source,java] +---- +@CheckReturnValue +default ServerVersion serverVersion() +---- + +Retrieves the server's version, using default strong consistency. See <<#_serverVersion_com_typedb_driver_api_ConsistencyLevel,``serverVersion(ConsistencyLevel)``>> for more details and options. + + +[caption=""] +.Returns +`ServerVersion` + +[caption=""] +.Code examples +[source,java] +---- +driver.serverVersion(); +---- + +[#_Driver_serverVersion_ConsistencyLevel] +==== serverVersion + +[source,java] +---- +@CheckReturnValue +ServerVersion serverVersion​(ConsistencyLevel consistencyLevel) +---- + +Retrieves the server's version. + + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `consistencyLevel` a| The consistency level to use for the operation a| `ConsistencyLevel` +|=== + +[caption=""] +.Returns +`ServerVersion` + +[caption=""] +.Code examples +[source,java] +---- +driver.serverVersion(); +---- + [#_Driver_transaction_java_lang_String_Transaction_Type] ==== transaction @@ -150,6 +323,37 @@ a| `options` a| ``TransactionOptions`` to configure the opened transaction a| `T driver.transaction(database, sessionType); ---- +[#_Driver_updateAddressTranslation_java_util_Map_java_lang_String_​java_lang_String_] +==== updateAddressTranslation + +[source,java] +---- +void updateAddressTranslation​(java.util.Map addressTranslation) +---- + +Updates address translation of the driver. This lets you actualize new translation information without recreating the driver from scratch. Useful after registering new replicas requiring address translation. + + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `addressTranslation` a| The translation of public TypeDB cluster replica addresses (keys) to server-side private addresses (values) a| `java.util.Map` +|=== + +[caption=""] +.Returns +`void` + +[caption=""] +.Code examples +[source,java] +---- +driver.updateAddressTranslation(2); +---- + [#_Driver_users_] ==== users @@ -159,7 +363,7 @@ driver.transaction(database, sessionType); UserManager users() ---- -The ``UserManager`` instance for this connection, providing access to user management methods. +The ``UserManager`` for this connection, providing access to user management methods. [caption=""] diff --git a/docs/modules/ROOT/partials/java/connection/DriverOptions.adoc b/docs/modules/ROOT/partials/java/connection/DriverOptions.adoc index 827a527e17..c34cbf026c 100644 --- a/docs/modules/ROOT/partials/java/connection/DriverOptions.adoc +++ b/docs/modules/ROOT/partials/java/connection/DriverOptions.adoc @@ -3,26 +3,63 @@ *Package*: `com.typedb.driver.api` -User connection settings (TLS encryption, etc.) for connecting to TypeDB Server. +TypeDB driver options. ``DriverOptions`` are used to specify the driver's connection behavior. +// tag::methods[] +[#_DriverOptions_DriverOptions_] +==== DriverOptions + +[source,java] +---- +public DriverOptions() +---- + +Produces a new ``DriverOptions`` object. + + +[caption=""] +.Returns +`public` [caption=""] -.Examples +.Code examples [source,java] ---- -DriverOptions driverOptions = new DriverOptions(true, Path.of("path/to/ca-certificate.pem")); +DriverOptions options = DriverOptions(); ---- -// tag::methods[] -[#_DriverOptions_DriverOptions_boolean_java_lang_String] -==== DriverOptions +[#_DriverOptions_isTlsEnabled_] +==== isTlsEnabled [source,java] ---- -public DriverOptions​(boolean isTlsEnabled, - java.lang.String tlsRootCAPath) +@CheckReturnValue +public java.lang.Boolean isTlsEnabled() ---- +Returns the value set for the TLS flag in this ``DriverOptions`` object. Specifies whether the connection to TypeDB must be done over TLS. + + +[caption=""] +.Returns +`public java.lang.Boolean` + +[caption=""] +.Code examples +[source,java] +---- +options.isTlsEnabled(); +---- + +[#_DriverOptions_isTlsEnabled_boolean] +==== isTlsEnabled + +[source,java] +---- +public DriverOptions isTlsEnabled​(boolean isTlsEnabled) +---- + +Explicitly sets whether the connection to TypeDB must be done over TLS. [caption=""] @@ -31,13 +68,235 @@ public DriverOptions​(boolean isTlsEnabled, [options="header"] |=== |Name |Description |Type -a| `isTlsEnabled` a| Specify whether the connection to TypeDB Server must be done over TLS. a| `boolean` -a| `tlsRootCAPath` a| Path to the CA certificate to use for authenticating server certificates. a| `java.lang.String` +a| `isTlsEnabled` a| Whether the connection to TypeDB must be done over TLS. a| `boolean` |=== [caption=""] .Returns -`public` +`public DriverOptions` + +[caption=""] +.Code examples +[source,java] +---- +options.isTlsEnabled(true); +---- + +[#_DriverOptions_primaryFailoverRetries_] +==== primaryFailoverRetries + +[source,java] +---- +@CheckReturnValue +public java.lang.Integer primaryFailoverRetries() +---- + +Returns the value set for the primary failover retries limit in this ``DriverOptions`` object. Limits the number of attempts to redirect a strongly consistent request to another primary replica in case of a failure due to the change of replica roles. + + +[caption=""] +.Returns +`public java.lang.Integer` + +[caption=""] +.Code examples +[source,java] +---- +options.primaryFailoverRetries(); +---- + +[#_DriverOptions_primaryFailoverRetries_int] +==== primaryFailoverRetries + +[source,java] +---- +public DriverOptions primaryFailoverRetries​(int primaryFailoverRetries) +---- + +Explicitly sets the limit on the number of attempts to redirect a strongly consistent request to another primary replica in case of a failure due to the change of replica roles. Defaults to 1. + + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `primaryFailoverRetries` a| The limit of primary failover retries. a| `int` +|=== + +[caption=""] +.Returns +`public DriverOptions` + +[caption=""] +.Code examples +[source,java] +---- +options.primaryFailoverRetries(1); +---- + +[#_DriverOptions_replicaDiscoveryAttempts_] +==== replicaDiscoveryAttempts + +[source,java] +---- +@CheckReturnValue +public java.util.Optional replicaDiscoveryAttempts() +---- + +Returns the value set for the replica discovery attempts limit in this ``DriverOptions`` object. Limits the number of driver attempts to discover a single working replica to perform an operation in case of a replica unavailability. Every replica is tested once, which means that at most: - {limit} operations are performed if the limit <= the number of replicas. - {number of replicas} operations are performed if the limit > the number of replicas. - {number of replicas} operations are performed if the limit is None. Affects every eventually consistent operation, including redirect failover, when the new primary replica is unknown. + + +[caption=""] +.Returns +`public java.util.Optional` + +[caption=""] +.Code examples +[source,java] +---- +options.replicaDiscoveryAttempts(); +---- + +[#_DriverOptions_replicaDiscoveryAttempts_int] +==== replicaDiscoveryAttempts + +[source,java] +---- +public DriverOptions replicaDiscoveryAttempts​(int replicaDiscoveryAttempts) +---- + +Limits the number of driver attempts to discover a single working replica to perform an operation in case of a replica unavailability. Every replica is tested once, which means that at most: - {limit} operations are performed if the limit <= the number of replicas. - {number of replicas} operations are performed if the limit > the number of replicas. - {number of replicas} operations are performed if the limit is None. Affects every eventually consistent operation, including redirect failover, when the new primary replica is unknown. If not set, the maximum (practically unlimited) value is used. + + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `replicaDiscoveryAttempts` a| The limit of replica discovery attempts. a| `int` +|=== + +[caption=""] +.Returns +`public DriverOptions` + +[caption=""] +.Code examples +[source,java] +---- +options.primaryFailoverRetries(1); +---- + +[#_DriverOptions_tlsRootCAPath_] +==== tlsRootCAPath + +[source,java] +---- +@CheckReturnValue +public java.util.Optional tlsRootCAPath() +---- + +Returns the TLS root CA set in this ``DriverOptions`` object. Specifies the root CA used in the TLS config for server certificates authentication. Uses system roots if None is set. + + +[caption=""] +.Returns +`public java.util.Optional` + +[caption=""] +.Code examples +[source,java] +---- +options.tlsRootCAPath(); +---- + +[#_DriverOptions_tlsRootCAPath_java_util_Optional_java_lang_String_] +==== tlsRootCAPath + +[source,java] +---- +public DriverOptions tlsRootCAPath​(java.util.Optional tlsRootCAPath) +---- + +Returns the TLS root CA set in this ``DriverOptions`` object. Specifies the root CA used in the TLS config for server certificates authentication. Uses system roots if None is set. + + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `tlsRootCAPath` a| The path to the TLS root CA. If None, system roots are used. a| `java.util.Optional` +|=== + +[caption=""] +.Returns +`public DriverOptions` + +[caption=""] +.Code examples +[source,java] +---- +options.tlsRootCAPath(Optional.of("/path/to/ca-certificate.pem")); +---- + +[#_DriverOptions_useReplication_] +==== useReplication + +[source,java] +---- +@CheckReturnValue +public java.lang.Boolean useReplication() +---- + +Returns the value set for the replication usage flag in this ``DriverOptions`` object. Specifies whether the connection to TypeDB can use cluster replicas provided by the server or it should be limited to a single configured address. + + +[caption=""] +.Returns +`public java.lang.Boolean` + +[caption=""] +.Code examples +[source,java] +---- +options.useReplication(); +---- + +[#_DriverOptions_useReplication_boolean] +==== useReplication + +[source,java] +---- +public DriverOptions useReplication​(boolean useReplication) +---- + +Explicitly sets whether the connection to TypeDB can use cluster replicas provided by the server or it should be limited to a single configured address. Defaults to true. + + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `useReplication` a| Whether the connection to TypeDB can use replication. a| `boolean` +|=== + +[caption=""] +.Returns +`public DriverOptions` + +[caption=""] +.Code examples +[source,java] +---- +options.useReplication(true); +---- // end::methods[] diff --git a/docs/modules/ROOT/partials/java/connection/ReplicaType.adoc b/docs/modules/ROOT/partials/java/connection/ReplicaType.adoc new file mode 100644 index 0000000000..7b40cd9ee5 --- /dev/null +++ b/docs/modules/ROOT/partials/java/connection/ReplicaType.adoc @@ -0,0 +1,138 @@ +[#_ReplicaType] +=== ReplicaType + +*Package*: `com.typedb.driver.api.server` + +This enum is used to specify the type of replica. + + +[caption=""] +.Examples +[source,java] +---- +replica.getType(); +---- + +[caption=""] +.Enum constants +// tag::enum_constants[] +[cols=""] +[options="header"] +|=== +|Name +a| `PRIMARY` +a| `SECONDARY` +|=== +// end::enum_constants[] + +// tag::methods[] +[#_ReplicaType_id_] +==== id + +[source,java] +---- +public int id() +---- + + + +[caption=""] +.Returns +`public int` + +[#_ReplicaType_isPrimary_] +==== isPrimary + +[source,java] +---- +public boolean isPrimary() +---- + +Checks whether this is the primary replica of the raft cluster. + +[caption=""] +.Returns +`public boolean` + +[#_ReplicaType_isSecondary_] +==== isSecondary + +[source,java] +---- +public boolean isSecondary() +---- + +Checks whether this is a secondary replica of the raft cluster. + +[caption=""] +.Returns +`public boolean` + +[#_ReplicaType_of_com_typedb_driver_jni_ReplicaType] +==== of + +[source,java] +---- +public static ReplicaType of​(com.typedb.driver.jni.ReplicaType nativeType) +---- + + + +[caption=""] +.Returns +`public static ReplicaType` + +[#_ReplicaType_valueOf_java_lang_String] +==== valueOf + +[source,java] +---- +public static ReplicaType valueOf​(java.lang.String name) +---- + +Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.) + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `name` a| the name of the enum constant to be returned. a| `java.lang.String` +|=== + +[caption=""] +.Returns +`public static ReplicaType` + +[#_ReplicaType_values_] +==== values + +[source,java] +---- +public static ReplicaType[] values() +---- + +Returns an array containing the constants of this enum type, in the order they are declared. This method may be used to iterate over the constants as follows: +[source,java] +---- +for (ReplicaType c : ReplicaType.values()) + System.out.println(c); + +---- + + +[caption=""] +.Returns +`public static ReplicaType[]` + +[caption=""] +.Code examples +[source,java] +---- +for (ReplicaType c : ReplicaType.values()) + System.out.println(c); +---- + +// end::methods[] + diff --git a/docs/modules/ROOT/partials/java/connection/ServerReplica.adoc b/docs/modules/ROOT/partials/java/connection/ServerReplica.adoc new file mode 100644 index 0000000000..61a2674100 --- /dev/null +++ b/docs/modules/ROOT/partials/java/connection/ServerReplica.adoc @@ -0,0 +1,85 @@ +[#_ServerReplica] +=== ServerReplica + +*Package*: `com.typedb.driver.api.server` + +The metadata and state of an individual raft replica of a driver connection. + +// tag::methods[] +[#_ServerReplica_getAddress_] +==== getAddress + +[source,java] +---- +@CheckReturnValue +java.lang.String getAddress() +---- + +Returns the address this replica is hosted at. + +[caption=""] +.Returns +`java.lang.String` + +[#_ServerReplica_getID_] +==== getID + +[source,java] +---- +@CheckReturnValue +long getID() +---- + +Returns the id of this replica. + +[caption=""] +.Returns +`long` + +[#_ServerReplica_getTerm_] +==== getTerm + +[source,java] +---- +@CheckReturnValue +long getTerm() +---- + +Returns the raft protocol ‘term’ of this replica. + +[caption=""] +.Returns +`long` + +[#_ServerReplica_getType_] +==== getType + +[source,java] +---- +@CheckReturnValue +ReplicaType getType() +---- + +Returns whether this is the primary replica of the raft cluster or any of the supporting types. + +[caption=""] +.Returns +`ReplicaType` + +[#_ServerReplica_isPrimary_] +==== isPrimary + +[source,java] +---- +@CheckReturnValue +java.lang.Boolean isPrimary() +---- + +Checks whether this is the primary replica of the raft cluster. + +[caption=""] +.Returns +`java.lang.Boolean` + +// end::methods[] + diff --git a/docs/modules/ROOT/partials/java/connection/ServerVersion.adoc b/docs/modules/ROOT/partials/java/connection/ServerVersion.adoc new file mode 100644 index 0000000000..17b8e25def --- /dev/null +++ b/docs/modules/ROOT/partials/java/connection/ServerVersion.adoc @@ -0,0 +1,62 @@ +[#_ServerVersion] +=== ServerVersion + +*Package*: `com.typedb.driver.api.server` + +A full TypeDB server's version specification. + + +[caption=""] +.Examples +[source,java] +---- +driver.serverVersion(); +---- + +// tag::methods[] +[#_ServerVersion_getDistribution_] +==== getDistribution + +[source,java] +---- +public java.lang.String getDistribution() +---- + +Returns the server's distribution. + + +[caption=""] +.Returns +`public java.lang.String` + +[caption=""] +.Code examples +[source,java] +---- +serverVersion.getDistribution(); +---- + +[#_ServerVersion_getVersion_] +==== getVersion + +[source,java] +---- +public java.lang.String getVersion() +---- + +Returns the server's version. + + +[caption=""] +.Returns +`public java.lang.String` + +[caption=""] +.Code examples +[source,java] +---- +serverVersion.getVersion(); +---- + +// end::methods[] + diff --git a/docs/modules/ROOT/partials/java/connection/TypeDB.adoc b/docs/modules/ROOT/partials/java/connection/TypeDB.adoc index 61a6354e29..82a05937c1 100644 --- a/docs/modules/ROOT/partials/java/connection/TypeDB.adoc +++ b/docs/modules/ROOT/partials/java/connection/TypeDB.adoc @@ -51,7 +51,7 @@ Open a TypeDB Driver to a TypeDB server available at the provided address. |Name |Description |Type a| `address` a| The address of the TypeDB server a| `java.lang.String` a| `credentials` a| The credentials to connect with a| `Credentials` -a| `driverOptions` a| The connection settings to connect with a| `DriverOptions` +a| `driverOptions` a| The driver connection options to connect with a| `DriverOptions` |=== [caption=""] @@ -65,5 +65,77 @@ a| `driverOptions` a| The connection settings to connect with a| `DriverOptions` TypeDB.driver(address); ---- +[#_TypeDB_driver_java_util_Set_java_lang_String_Credentials_DriverOptions] +==== driver + +[source,java] +---- +public static Driver driver​(java.util.Set addresses, + Credentials credentials, + DriverOptions driverOptions) + throws TypeDBDriverException +---- + +Open a TypeDB Driver to a TypeDB cluster available at the provided addresses. + + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `addresses` a| The addresses of TypeDB cluster replicas for connection a| `java.util.Set` +a| `credentials` a| The credentials to connect with a| `Credentials` +a| `driverOptions` a| The driver connection options to connect with a| `DriverOptions` +|=== + +[caption=""] +.Returns +`public static Driver` + +[caption=""] +.Code examples +[source,java] +---- +TypeDB.driver(address); +---- + +[#_TypeDB_driver_java_util_Map_java_lang_String_​java_lang_String_Credentials_DriverOptions] +==== driver + +[source,java] +---- +public static Driver driver​(java.util.Map addressTranslation, + Credentials credentials, + DriverOptions driverOptions) + throws TypeDBDriverException +---- + +Open a TypeDB Driver to a TypeDB cluster, using the provided address translation. + + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `addressTranslation` a| The translation of public TypeDB cluster replica addresses (keys) to server-side private addresses (values) a| `java.util.Map` +a| `credentials` a| The credentials to connect with a| `Credentials` +a| `driverOptions` a| The driver connection options to connect with a| `DriverOptions` +|=== + +[caption=""] +.Returns +`public static Driver` + +[caption=""] +.Code examples +[source,java] +---- +TypeDB.driver(addresses); +---- + // end::methods[] diff --git a/docs/modules/ROOT/partials/java/connection/UserManager.adoc b/docs/modules/ROOT/partials/java/connection/UserManager.adoc index 83626fc911..3c29766a7d 100644 --- a/docs/modules/ROOT/partials/java/connection/UserManager.adoc +++ b/docs/modules/ROOT/partials/java/connection/UserManager.adoc @@ -11,13 +11,45 @@ Provides access to all user management methods. [source,java] ---- -java.util.Set all() +default java.util.Set all() + throws TypeDBDriverException +---- + +Retrieves all users which exist on the TypeDB server, using default strong consistency. See <<#_all_com_typedb_driver_api_ConsistencyLevel,``all(ConsistencyLevel)``>> for more details and options. + + +[caption=""] +.Returns +`java.util.Set` + +[caption=""] +.Code examples +[source,java] +---- +driver.users().all(); +---- + +[#_UserManager_all_ConsistencyLevel] +==== all + +[source,java] +---- +java.util.Set all​(ConsistencyLevel consistencyLevel) throws TypeDBDriverException ---- Retrieves all users which exist on the TypeDB server. +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `consistencyLevel` a| The consistency level to use for the operation a| `ConsistencyLevel` +|=== + [caption=""] .Returns `java.util.Set` @@ -26,7 +58,7 @@ Retrieves all users which exist on the TypeDB server. .Code examples [source,java] ---- -driver.users().all(); +driver.users().all(ConsistencyLevel.Strong); ---- [#_UserManager_contains_java_lang_String] @@ -35,7 +67,41 @@ driver.users().all(); [source,java] ---- @CheckReturnValue -boolean contains​(java.lang.String username) +default boolean contains​(java.lang.String username) + throws TypeDBDriverException +---- + +Checks if a user with the given name exists., using default strong consistency. See <<#_contains_java_lang_String_com_typedb_driver_api_ConsistencyLevel,``contains(String, ConsistencyLevel)``>> for more details and options. + + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `username` a| The username to be checked a| `java.lang.String` +|=== + +[caption=""] +.Returns +`boolean` + +[caption=""] +.Code examples +[source,java] +---- +driver.users().contains(username); +---- + +[#_UserManager_contains_java_lang_String_ConsistencyLevel] +==== contains + +[source,java] +---- +@CheckReturnValue +boolean contains​(java.lang.String username, + ConsistencyLevel consistencyLevel) throws TypeDBDriverException ---- @@ -48,7 +114,8 @@ Checks if a user with the given name exists. [options="header"] |=== |Name |Description |Type -a| `username` a| The user name to be checked a| `java.lang.String` +a| `username` a| The username to be checked a| `java.lang.String` +a| `consistencyLevel` a| The consistency level to use for the operation a| `ConsistencyLevel` |=== [caption=""] @@ -59,7 +126,7 @@ a| `username` a| The user name to be checked a| `java.lang.String` .Code examples [source,java] ---- -driver.users().contains(username); +driver.users().contains(username, ConsistencyLevel.Strong); ---- [#_UserManager_create_java_lang_String_java_lang_String] @@ -102,7 +169,41 @@ driver.users().create(username, password); [source,java] ---- @CheckReturnValue -User get​(java.lang.String username) +default User get​(java.lang.String username) + throws TypeDBDriverException +---- + +Retrieves a user with the given name, using default strong consistency. See <<#_get_java_lang_String_com_typedb_driver_api_ConsistencyLevel,``get(String, ConsistencyLevel)``>> for more details and options. + + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `username` a| The name of the user to retrieve a| `java.lang.String` +|=== + +[caption=""] +.Returns +`User` + +[caption=""] +.Code examples +[source,java] +---- +driver.users().get(username); +---- + +[#_UserManager_get_java_lang_String_ConsistencyLevel] +==== get + +[source,java] +---- +@CheckReturnValue +User get​(java.lang.String username, + ConsistencyLevel consistencyLevel) throws TypeDBDriverException ---- @@ -116,6 +217,7 @@ Retrieves a user with the given name. |=== |Name |Description |Type a| `username` a| The name of the user to retrieve a| `java.lang.String` +a| `consistencyLevel` a| The consistency level to use for the operation a| `ConsistencyLevel` |=== [caption=""] @@ -126,7 +228,7 @@ a| `username` a| The name of the user to retrieve a| `java.lang.String` .Code examples [source,java] ---- -driver.users().get(username); +driver.users().get(username, ConsistencyLevel.Strong); ---- [#_UserManager_getCurrentUser_] @@ -135,13 +237,46 @@ driver.users().get(username); [source,java] ---- @CheckReturnValue -User getCurrentUser() +default User getCurrentUser() + throws TypeDBDriverException +---- + +Retrieves the name of the user who opened the current connection, using default strong consistency. See <<#_getCurrentUser_com_typedb_driver_api_ConsistencyLevel,``getCurrentUser(ConsistencyLevel)``>> for more details and options. + + +[caption=""] +.Returns +`User` + +[caption=""] +.Code examples +[source,java] +---- +driver.users().getCurrentUser(); +---- + +[#_UserManager_getCurrentUser_ConsistencyLevel] +==== getCurrentUser + +[source,java] +---- +@CheckReturnValue +User getCurrentUser​(ConsistencyLevel consistencyLevel) throws TypeDBDriverException ---- Retrieves the name of the user who opened the current connection. +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `consistencyLevel` a| The consistency level to use for the operation a| `ConsistencyLevel` +|=== + [caption=""] .Returns `User` @@ -150,7 +285,7 @@ Retrieves the name of the user who opened the current connection. .Code examples [source,java] ---- -driver.users().getCurrentUsername(); +driver.users().getCurrentUser(ConsistencyLevel.Strong); ---- // end::methods[] diff --git a/docs/modules/ROOT/partials/java/transaction/QueryOptions.adoc b/docs/modules/ROOT/partials/java/transaction/QueryOptions.adoc index aae8f6b443..f12d3d9282 100644 --- a/docs/modules/ROOT/partials/java/transaction/QueryOptions.adoc +++ b/docs/modules/ROOT/partials/java/transaction/QueryOptions.adoc @@ -59,7 +59,7 @@ options.includeInstanceTypes(); public QueryOptions includeInstanceTypes​(boolean includeInstanceTypes) ---- -Explicitly set the "include instance types" flag. If set, specifies if types should be included in instance structs returned in ConceptRow answers. This option allows reducing the amount of unnecessary data transmitted. +Explicitly setsthe "include instance types" flag. If set, specifies if types should be included in instance structs returned in ConceptRow answers. This option allows reducing the amount of unnecessary data transmitted. [caption=""] @@ -113,7 +113,7 @@ options.prefetchSize(); public QueryOptions prefetchSize​(int prefetchSize) ---- -Explicitly set the prefetch size. If set, specifies the number of extra query responses sent before the client side has to re-request more responses. Increasing this may increase performance for queries with a huge number of answers, as it can reduce the number of network round-trips at the cost of more resources on the server side. Minimal value: 1. +Explicitly setsthe prefetch size. If set, specifies the number of extra query responses sent before the client side has to re-request more responses. Increasing this may increase performance for queries with a huge number of answers, as it can reduce the number of network round-trips at the cost of more resources on the server side. Minimal value: 1. [caption=""] diff --git a/docs/modules/ROOT/partials/java/transaction/TransactionOptions.adoc b/docs/modules/ROOT/partials/java/transaction/TransactionOptions.adoc index 821f36096a..6b3dff2340 100644 --- a/docs/modules/ROOT/partials/java/transaction/TransactionOptions.adoc +++ b/docs/modules/ROOT/partials/java/transaction/TransactionOptions.adoc @@ -28,6 +28,59 @@ Produces a new ``TransactionOptions`` object. TransactionOptions options = TransactionOptions(); ---- +[#_TransactionOptions_readConsistencyLevel_] +==== readConsistencyLevel + +[source,java] +---- +public java.util.Optional readConsistencyLevel() +---- + +Returns the value set for the read consistency level in this ``TransactionOptions`` object. If set, specifies the requested consistency level of the transaction opening operation. Affects only read transactions, as write and schema transactions require primary replicas. + + +[caption=""] +.Returns +`public java.util.Optional` + +[caption=""] +.Code examples +[source,java] +---- +options.readConsistencyLevel(); +---- + +[#_TransactionOptions_readConsistencyLevel_ConsistencyLevel] +==== readConsistencyLevel + +[source,java] +---- +public TransactionOptions readConsistencyLevel​(ConsistencyLevel readConsistencyLevel) +---- + +Explicitly sets read consistency level. If set, specifies the requested consistency level of the transaction opening operation. Affects only read transactions, as write and schema transactions require primary replicas. + + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `readConsistencyLevel` a| The requested consistency level a| `ConsistencyLevel` +|=== + +[caption=""] +.Returns +`public TransactionOptions` + +[caption=""] +.Code examples +[source,java] +---- +options.schemaLockAcquireTimeoutMillis(schemaLockAcquireTimeoutMillis); +---- + [#_TransactionOptions_schemaLockAcquireTimeoutMillis_] ==== schemaLockAcquireTimeoutMillis @@ -112,7 +165,7 @@ options.transactionTimeoutMillis(); public TransactionOptions transactionTimeoutMillis​(int transactionTimeoutMillis) ---- -Explicitly set a transaction timeout. If set, specifies how long the driver should wait if opening a transaction is blocked by an exclusive schema write lock. +Explicitly sets a transaction timeout. If set, specifies how long the driver should wait if opening a transaction is blocked by an exclusive schema write lock. [caption=""] diff --git a/docs/modules/ROOT/partials/nodejs/connection/Database.adoc b/docs/modules/ROOT/partials/nodejs/connection/Database.adoc index cd9193a2b2..3159bf941d 100644 --- a/docs/modules/ROOT/partials/nodejs/connection/Database.adoc +++ b/docs/modules/ROOT/partials/nodejs/connection/Database.adoc @@ -9,9 +9,9 @@ |=== |Name |Type |Description a| `name` a| `string` a| The database name as a string. -a| `preferredReplica` a| `Replica` a| The preferred replica for this database. Operations which can be run on any replica will prefer to use this replica. Only works in TypeDB Cloud -a| `primaryReplica` a| `Replica` a| The primary replica for this database. Only works in TypeDB Cloud -a| `replicas` a| `Replica` a| The Replica instances for this database. Only works in TypeDB Cloud +a| `preferredReplica` a| `ServerReplica` a| The preferred replica for this database. Operations which can be run on any replica will prefer to use this replica. Only works in TypeDB Cloud +a| `primaryReplica` a| `ServerReplica` a| The primary replica for this database. Only works in TypeDB Cloud +a| `replicas` a| `ServerReplica` a| The Replica instances for this database. Only works in TypeDB Cloud |=== // end::properties[] diff --git a/docs/modules/ROOT/partials/python/connection/ConsistencyLevel.adoc b/docs/modules/ROOT/partials/python/connection/ConsistencyLevel.adoc new file mode 100644 index 0000000000..c022b65bfa --- /dev/null +++ b/docs/modules/ROOT/partials/python/connection/ConsistencyLevel.adoc @@ -0,0 +1,92 @@ +[#_ConsistencyLevel] +=== ConsistencyLevel + +Consistency levels of operations against a distributed server. All driver methods have default recommended values, however, readonly operations can be configured in order to potentially speed up the execution (introducing risks of stale data) or test a specific replica. This setting does not affect clusters with a single node. + +// tag::methods[] +[#_ConsistencyLevel_is_eventual_] +==== is_eventual + +[source,python] +---- +is_eventual() -> bool +---- + + + +[caption=""] +.Returns +`bool` + +[#_ConsistencyLevel_is_replica_dependant_] +==== is_replica_dependant + +[source,python] +---- +is_replica_dependant() -> bool +---- + + + +[caption=""] +.Returns +`bool` + +[#_ConsistencyLevel_is_strong_] +==== is_strong + +[source,python] +---- +is_strong() -> bool +---- + + + +[caption=""] +.Returns +`bool` + +[#_ConsistencyLevel_native_object_] +==== native_object + +[source,python] +---- +native_object() +---- + + + +[caption=""] +.Returns +`` + +[#_ConsistencyLevel_native_value_] +==== native_value + +[source,python] +---- +static native_value(consistency_level: ConsistencyLevel | None) -> ConsistencyLevel +---- + + + +[caption=""] +.Returns +`ConsistencyLevel` + +[#_ConsistencyLevel_of_] +==== of + +[source,python] +---- +static of(native_value: ConsistencyLevel) -> None | Strong | Eventual | ReplicaDependant +---- + + + +[caption=""] +.Returns +`None | Strong | Eventual | ReplicaDependant` + +// end::methods[] + diff --git a/docs/modules/ROOT/partials/python/connection/Database.adoc b/docs/modules/ROOT/partials/python/connection/Database.adoc index 5e451870e0..ab4bbe3dc5 100644 --- a/docs/modules/ROOT/partials/python/connection/Database.adoc +++ b/docs/modules/ROOT/partials/python/connection/Database.adoc @@ -34,12 +34,12 @@ Deletes this database. database.delete() ---- -[#_Database_export_to_file_schema_file_path_str_data_file_path_str] +[#_Database_export_to_file_schema_file_path_str_data_file_path_str_consistency_level_ConsistencyLevel_None] ==== export_to_file [source,python] ---- -export_to_file(schema_file_path: str, data_file_path: str) -> None +export_to_file(schema_file_path: str, data_file_path: str, consistency_level: ConsistencyLevel | None = None) -> None ---- Export a database into a schema definition and a data files saved to the disk. This is a blocking operation and may take a significant amount of time depending on the database size. @@ -52,6 +52,7 @@ Export a database into a schema definition and a data files saved to the disk. T |Name |Description |Type |Default Value a| `schema_file_path` a| The path to the schema definition file to be created a| `str` a| a| `data_file_path` a| The path to the data file to be created a| `str` a| +a| `consistency_level` a| The consistency level to use for the operation. Strongest possible by default a| `ConsistencyLevel \| None` a| `None` |=== [caption=""] @@ -63,18 +64,28 @@ a| `data_file_path` a| The path to the data file to be created a| `str` a| [source,python] ---- database.export_to_file("schema.typeql", "data.typedb") +database.export_to_file("schema.typeql", "data.typedb", ConsistencyLevel.Strong()) ---- -[#_Database_schema_] +[#_Database_schema_consistency_level_ConsistencyLevel_None] ==== schema [source,python] ---- -schema() -> str +schema(consistency_level: ConsistencyLevel | None = None) -> str ---- Returns a full schema text as a valid TypeQL define query string. +[caption=""] +.Input parameters +[cols=",,,"] +[options="header"] +|=== +|Name |Description |Type |Default Value +a| `consistency_level` a| The consistency level to use for the operation. Strongest possible by default a| `ConsistencyLevel \| None` a| `None` +|=== + [caption=""] .Returns `str` @@ -84,18 +95,28 @@ Returns a full schema text as a valid TypeQL define query string. [source,python] ---- database.schema() +database.schema(ConsistencyLevel.Strong()) ---- -[#_Database_type_schema_] +[#_Database_type_schema_consistency_level_ConsistencyLevel_None] ==== type_schema [source,python] ---- -type_schema() -> str +type_schema(consistency_level: ConsistencyLevel | None = None) -> str ---- Returns the types in the schema as a valid TypeQL define query string. +[caption=""] +.Input parameters +[cols=",,,"] +[options="header"] +|=== +|Name |Description |Type |Default Value +a| `consistency_level` a| The consistency level to use for the operation. Strongest possible by default a| `ConsistencyLevel \| None` a| `None` +|=== + [caption=""] .Returns `str` @@ -105,6 +126,7 @@ Returns the types in the schema as a valid TypeQL define query string. [source,python] ---- database.type_schema() +database.type_schema(ConsistencyLevel.Strong()) ---- // end::methods[] diff --git a/docs/modules/ROOT/partials/python/connection/DatabaseManager.adoc b/docs/modules/ROOT/partials/python/connection/DatabaseManager.adoc index 71dbdf44ef..cee2ee6166 100644 --- a/docs/modules/ROOT/partials/python/connection/DatabaseManager.adoc +++ b/docs/modules/ROOT/partials/python/connection/DatabaseManager.adoc @@ -4,16 +4,25 @@ Provides access to all database management methods. // tag::methods[] -[#_DatabaseManager_all_] +[#_DatabaseManager_all_consistency_level_ConsistencyLevel_None] ==== all [source,python] ---- -all() -> List[Database] +all(consistency_level: ConsistencyLevel | None = None) -> List[Database] ---- Retrieves all databases present on the TypeDB server. +[caption=""] +.Input parameters +[cols=",,,"] +[options="header"] +|=== +|Name |Description |Type |Default Value +a| `consistency_level` a| The consistency level to use for the operation. Strongest possible by default a| `ConsistencyLevel \| None` a| `None` +|=== + [caption=""] .Returns `List[Database]` @@ -23,14 +32,15 @@ Retrieves all databases present on the TypeDB server. [source,python] ---- driver.databases.all() +driver.databases.all(ConsistencyLevel.Strong()) ---- -[#_DatabaseManager_contains_name_str] +[#_DatabaseManager_contains_name_str_consistency_level_ConsistencyLevel_None] ==== contains [source,python] ---- -contains(name: str) -> bool +contains(name: str, consistency_level: ConsistencyLevel | None = None) -> bool ---- Checks if a database with the given name exists. @@ -42,6 +52,7 @@ Checks if a database with the given name exists. |=== |Name |Description |Type |Default Value a| `name` a| The database name to be checked a| `str` a| +a| `consistency_level` a| The consistency level to use for the operation. Strongest possible by default a| `ConsistencyLevel \| None` a| `None` |=== [caption=""] @@ -53,6 +64,7 @@ a| `name` a| The database name to be checked a| `str` a| [source,python] ---- driver.databases.contains(name) +driver.databases.contains(name, ConsistencyLevel.Strong()) ---- [#_DatabaseManager_create_name_str] @@ -63,7 +75,7 @@ driver.databases.contains(name) create(name: str) -> None ---- -Create a database with the given name. +Creates a database with the given name. [caption=""] .Input parameters @@ -85,15 +97,15 @@ a| `name` a| The name of the database to be created a| `str` a| driver.databases.create(name) ---- -[#_DatabaseManager_get_name_str] +[#_DatabaseManager_get_name_str_consistency_level_ConsistencyLevel_None] ==== get [source,python] ---- -get(name: str) -> Database +get(name: str, consistency_level: ConsistencyLevel | None = None) -> Database ---- -Retrieve the database with the given name. +Retrieves the database with the given name. [caption=""] .Input parameters @@ -102,6 +114,7 @@ Retrieve the database with the given name. |=== |Name |Description |Type |Default Value a| `name` a| The name of the database to retrieve a| `str` a| +a| `consistency_level` a| The consistency level to use for the operation. Strongest possible by default a| `ConsistencyLevel \| None` a| `None` |=== [caption=""] @@ -113,6 +126,7 @@ a| `name` a| The name of the database to retrieve a| `str` a| [source,python] ---- driver.databases.get(name) +driver.databases.get(name, ConsistencyLevel.Strong()) ---- [#_DatabaseManager_import_from_file_name_str_schema_str_data_file_path_str] @@ -123,7 +137,7 @@ driver.databases.get(name) import_from_file(name: str, schema: str, data_file_path: str) -> None ---- -Create a database with the given name based on previously exported another database’s data loaded from a file. This is a blocking operation and may take a significant amount of time depending on the database size. +Creates a database with the given name based on previously exported another database’s data loaded from a file. This is a blocking operation and may take a significant amount of time depending on the database size. [caption=""] .Input parameters diff --git a/docs/modules/ROOT/partials/python/connection/Driver.adoc b/docs/modules/ROOT/partials/python/connection/Driver.adoc index eab119bb3d..c1c6b9d64c 100644 --- a/docs/modules/ROOT/partials/python/connection/Driver.adoc +++ b/docs/modules/ROOT/partials/python/connection/Driver.adoc @@ -9,7 +9,7 @@ |=== |Name |Type |Description a| `databases` a| `DatabaseManager` a| The ``DatabaseManager`` for this connection, providing access to database management methods. -a| `users` a| `UserManager` a| The ``UserManager`` instance for this connection, providing access to user management methods. Only for TypeDB Cloud. +a| `users` a| `UserManager` a| The ``UserManager`` for this connection, providing access to user management methods. |=== // end::properties[] @@ -35,6 +35,36 @@ Closes the driver. Before instantiating a new driver, the driver that’s curren driver.close() ---- +[#_Driver_deregister_replica_replica_id_int] +==== deregister_replica + +[source,python] +---- +deregister_replica(replica_id: int) -> None +---- + +Deregisters a replica from the cluster the driver is currently connected to. This replica will no longer play a raft role in this cluster. + +[caption=""] +.Input parameters +[cols=",,,"] +[options="header"] +|=== +|Name |Description |Type |Default Value +a| `replica_id` a| The numeric identifier of the deregistered replica a| `int` a| +|=== + +[caption=""] +.Returns +`None` + +[caption=""] +.Code examples +[source,python] +---- +driver.deregister_replica(2) +---- + [#_Driver_is_open_] ==== is_open @@ -56,6 +86,110 @@ Checks whether this connection is presently open. driver.is_open() ---- +[#_Driver_primary_replica_] +==== primary_replica + +[source,python] +---- +primary_replica() -> ServerReplica | None +---- + +Returns the primary replica for this driver connection. + +[caption=""] +.Returns +`ServerReplica | None` + +[caption=""] +.Code examples +[source,python] +---- +driver.primary_replica() +---- + +[#_Driver_register_replica_replica_id_int_address_str] +==== register_replica + +[source,python] +---- +register_replica(replica_id: int, address: str) -> None +---- + +Registers a new replica in the cluster the driver is currently connected to. The registered replica will become available eventually, depending on the behavior of the whole cluster. To register a replica, its clustering address should be passed, not the connection address. + +[caption=""] +.Input parameters +[cols=",,,"] +[options="header"] +|=== +|Name |Description |Type |Default Value +a| `replica_id` a| The numeric identifier of the new replica a| `int` a| +a| `address` a| The address(es) of the TypeDB replica as a string a| `str` a| +|=== + +[caption=""] +.Returns +`None` + +[caption=""] +.Code examples +[source,python] +---- +driver.register_replica(2, "127.0.0.1:11729") +---- + +[#_Driver_replicas_] +==== replicas + +[source,python] +---- +replicas() -> Set[ServerReplica] +---- + +Set of ``Replica`` instances for this driver connection. + +[caption=""] +.Returns +`Set[ServerReplica]` + +[caption=""] +.Code examples +[source,python] +---- +driver.replicas() +---- + +[#_Driver_server_version_consistency_level_ConsistencyLevel_None] +==== server_version + +[source,python] +---- +server_version(consistency_level: ConsistencyLevel | None = None) -> ServerVersion +---- + +Retrieves the server’s version. + +[caption=""] +.Input parameters +[cols=",,,"] +[options="header"] +|=== +|Name |Description |Type |Default Value +a| `consistency_level` a| The consistency level to use for the operation. Strongest possible by default a| `ConsistencyLevel \| None` a| `None` +|=== + +[caption=""] +.Returns +`ServerVersion` + +[caption=""] +.Code examples +[source,python] +---- +driver.server_version() +driver.server_version(ConsistencyLevel.Strong()) +---- + [#_Driver_transaction_database_name_str_transaction_type_TransactionType_options_TransactionOptions_None] ==== transaction @@ -88,5 +222,35 @@ a| `options` a| ``TransactionOptions`` to configure the opened transaction a| `T driver.transaction(database, transaction_type, options) ---- +[#_Driver_update_address_translation_address_translation_Mapping_str_str_] +==== update_address_translation + +[source,python] +---- +update_address_translation(address_translation: Mapping[str, str]) -> None +---- + +Updates address translation of the driver. This lets you actualize new translation information without recreating the driver from scratch. Useful after registering new replicas requiring address translation. + +[caption=""] +.Input parameters +[cols=",,,"] +[options="header"] +|=== +|Name |Description |Type |Default Value +a| `address_translation` a| The translation of public TypeDB cluster replica addresses (keys) to server-side private addresses (values) a| `Mapping[str, str]` a| +|=== + +[caption=""] +.Returns +`None` + +[caption=""] +.Code examples +[source,python] +---- +driver.update_address_translation({"typedb-cloud.ext:11729": "127.0.0.1:11729"}) +---- + // end::methods[] diff --git a/docs/modules/ROOT/partials/python/connection/DriverOptions.adoc b/docs/modules/ROOT/partials/python/connection/DriverOptions.adoc index 84e5b85bfe..95dae7afb3 100644 --- a/docs/modules/ROOT/partials/python/connection/DriverOptions.adoc +++ b/docs/modules/ROOT/partials/python/connection/DriverOptions.adoc @@ -1,12 +1,30 @@ [#_DriverOptions] === DriverOptions -User credentials and TLS encryption settings for connecting to TypeDB Server. Arguments: 1) is_tls_enabled: Specify whether the connection to TypeDB Cloud must be done over TLS. 2) tls_root_ca_path: Path to the CA certificate to use for authenticating server certificates. +TypeDB driver options. ``DriverOptions`` are used to specify the driver’s connection behavior. + +Options could be specified either as constructor arguments or using properties assignment. [caption=""] .Examples [source,python] ---- -driver_options = DriverOptions(tls_enabled=True, tls_root_ca_path="path/to/ca-certificate.pem") +driver_options = DriverOptions(tls_enabled=True) +driver_options.tls_root_ca_path = "path/to/ca-certificate.pem" ---- +[caption=""] +.Properties +// tag::properties[] +[cols=",,"] +[options="header"] +|=== +|Name |Type |Description +a| `is_tls_enabled` a| `bool` a| Returns the value set for the TLS flag in this ``DriverOptions`` object. Specifies whether the connection to TypeDB must be done over TLS. +a| `primary_failover_retries` a| `int` a| Returns the value set for the primary failover retries limit in this ``DriverOptions`` object. Limits the number of attempts to redirect a strongly consistent request to another primary replica in case of a failure due to the change of replica roles. +a| `replica_discovery_attempts` a| `int \| None` a| Returns the value set for the replica discovery attempts limit in this ``DriverOptions`` object. Limits the number of driver attempts to discover a single working replica to perform an operation in case of a replica unavailability. Every replica is tested once, which means that at most: - {limit} operations are performed if the limit <= the number of replicas. - {number of replicas} operations are performed if the limit > the number of replicas. - {number of replicas} operations are performed if the limit is None. Affects every eventually consistent operation, including redirect failover, when the new primary replica is unknown. If not set, the maximum (practically unlimited) value is used. +a| `tls_root_ca_path` a| `str \| None` a| Returns the TLS root CA set in this ``DriverOptions`` object. Specifies the root CA used in the TLS config for server certificates authentication. Uses system roots if None is set. +a| `use_replication` a| `bool` a| Returns the value set for the replication usage flag in this ``DriverOptions`` object. Specifies whether the connection to TypeDB can use cluster replicas provided by the server or it should be limited to a single configured address. +|=== +// end::properties[] + diff --git a/docs/modules/ROOT/partials/python/connection/Eventual.adoc b/docs/modules/ROOT/partials/python/connection/Eventual.adoc new file mode 100644 index 0000000000..2543aff95e --- /dev/null +++ b/docs/modules/ROOT/partials/python/connection/Eventual.adoc @@ -0,0 +1,22 @@ +[#_Eventual] +=== Eventual + +Eventual consistency level. Allow stale reads from any replica. May not reflect latest writes. The execution may be eventually faster compared to other consistency levels. + +// tag::methods[] +[#_Eventual_native_object_] +==== native_object + +[source,python] +---- +native_object() +---- + + + +[caption=""] +.Returns +`` + +// end::methods[] + diff --git a/docs/modules/ROOT/partials/python/connection/ReplicaDependant.adoc b/docs/modules/ROOT/partials/python/connection/ReplicaDependant.adoc new file mode 100644 index 0000000000..0e5ac0f668 --- /dev/null +++ b/docs/modules/ROOT/partials/python/connection/ReplicaDependant.adoc @@ -0,0 +1,33 @@ +[#_ReplicaDependant] +=== ReplicaDependant + +Replica dependant consistency level. The operation is executed against the provided replica address only. Its guarantees depend on the replica selected. + +[caption=""] +.Properties +// tag::properties[] +[cols=",,"] +[options="header"] +|=== +|Name |Type |Description +a| `address` a| `str` a| +|=== +// end::properties[] + +// tag::methods[] +[#_ReplicaDependant_native_object_] +==== native_object + +[source,python] +---- +native_object() +---- + + + +[caption=""] +.Returns +`` + +// end::methods[] + diff --git a/docs/modules/ROOT/partials/python/connection/ReplicaType.adoc b/docs/modules/ROOT/partials/python/connection/ReplicaType.adoc new file mode 100644 index 0000000000..bc60781902 --- /dev/null +++ b/docs/modules/ROOT/partials/python/connection/ReplicaType.adoc @@ -0,0 +1,55 @@ +[#_ReplicaType] +=== ReplicaType + +This class is used to specify the type of replica. + +[caption=""] +.Examples +[source,python] +---- +server_replica.replica_type +---- + +[caption=""] +.Enum constants +// tag::enum_constants[] +[cols=","] +[options="header"] +|=== +|Name |Value +a| `PRIMARY` a| `0` +a| `SECONDARY` a| `1` +|=== +// end::enum_constants[] + +// tag::methods[] +[#_ReplicaType_is_primary_] +==== is_primary + +[source,python] +---- +is_primary() -> bool +---- + + + +[caption=""] +.Returns +`bool` + +[#_ReplicaType_is_secondary_] +==== is_secondary + +[source,python] +---- +is_secondary() -> bool +---- + + + +[caption=""] +.Returns +`bool` + +// end::methods[] + diff --git a/docs/modules/ROOT/partials/python/connection/ServerReplica.adoc b/docs/modules/ROOT/partials/python/connection/ServerReplica.adoc new file mode 100644 index 0000000000..6b8cbec360 --- /dev/null +++ b/docs/modules/ROOT/partials/python/connection/ServerReplica.adoc @@ -0,0 +1,51 @@ +[#_ServerReplica] +=== ServerReplica + +The metadata and state of an individual raft replica of a driver connection. + +[caption=""] +.Properties +// tag::properties[] +[cols=",,"] +[options="header"] +|=== +|Name |Type |Description +a| `address` a| `str` a| Returns the address this replica is hosted at. + + +a| `id` a| `int` a| Returns the id of this replica. + + +a| `replica_type` a| `ReplicaType` a| Returns whether this is the primary replica of the raft cluster or any of the supporting types. + + +a| `term` a| `int` a| Returns the raft protocol ‘term’ of this replica. + + +|=== +// end::properties[] + +// tag::methods[] +[#_ServerReplica_is_primary_] +==== is_primary + +[source,python] +---- +is_primary() -> bool +---- + +Checks whether this is the primary replica of the raft cluster. + +[caption=""] +.Returns +`bool` + +[caption=""] +.Code examples +[source,python] +---- +server_replica.is_primary() +---- + +// end::methods[] + diff --git a/docs/modules/ROOT/partials/python/connection/ServerVersion.adoc b/docs/modules/ROOT/partials/python/connection/ServerVersion.adoc new file mode 100644 index 0000000000..770fdb2a26 --- /dev/null +++ b/docs/modules/ROOT/partials/python/connection/ServerVersion.adoc @@ -0,0 +1,28 @@ +[#_ServerVersion] +=== ServerVersion + +A full TypeDB server’s version specification. + +[caption=""] +.Examples +[source,python] +---- +driver.server_version() +---- + +[caption=""] +.Properties +// tag::properties[] +[cols=",,"] +[options="header"] +|=== +|Name |Type |Description +a| `distribution` a| `str` a| Returns the server’s distribution. + + +a| `version` a| `str` a| Returns the server’s version. + + +|=== +// end::properties[] + diff --git a/docs/modules/ROOT/partials/python/connection/Strong.adoc b/docs/modules/ROOT/partials/python/connection/Strong.adoc new file mode 100644 index 0000000000..0b6ac63a08 --- /dev/null +++ b/docs/modules/ROOT/partials/python/connection/Strong.adoc @@ -0,0 +1,22 @@ +[#_Strong] +=== Strong + +The strongest consistency level. Always up-to-date due to the guarantee of the primary replica usage. May require more time for operation execution. + +// tag::methods[] +[#_Strong_native_object_] +==== native_object + +[source,python] +---- +native_object() +---- + + + +[caption=""] +.Returns +`` + +// end::methods[] + diff --git a/docs/modules/ROOT/partials/python/connection/TypeDB.adoc b/docs/modules/ROOT/partials/python/connection/TypeDB.adoc index 9d3fd58295..57e8fec668 100644 --- a/docs/modules/ROOT/partials/python/connection/TypeDB.adoc +++ b/docs/modules/ROOT/partials/python/connection/TypeDB.adoc @@ -2,25 +2,25 @@ === TypeDB // tag::methods[] -[#_TypeDB_driver_address_str_credentials_Credentials_driver_options_DriverOptions] +[#_TypeDB_driver_addresses_Mapping_str_str_Iterable_str_str] ==== driver [source,python] ---- -static driver(address: str, credentials: Credentials, driver_options: DriverOptions) -> Driver +static driver(addresses: Mapping[str, str] | Iterable[str] | str, credentials: Credentials, driver_options: DriverOptions) -> Driver ---- Creates a connection to TypeDB. +Can be a single string, multiple strings, or multiple pairs of strings. :param credentials: The credentials to connect with. :param driver_options: The driver connection options to connect with. :return: + [caption=""] .Input parameters [cols=",,,"] [options="header"] |=== |Name |Description |Type |Default Value -a| `address` a| Address of the TypeDB server. a| `str` a| -a| `credentials` a| The credentials to connect with. a| `Credentials` a| -a| `driver_options` a| The connection settings to connect with. a| `DriverOptions` a| +a| `addresses` a| Address(-es) of the TypeDB server(-s). a| `Mapping[str, str] \| Iterable[str] \| str` a| |=== [caption=""] diff --git a/docs/modules/ROOT/partials/python/connection/UserManager.adoc b/docs/modules/ROOT/partials/python/connection/UserManager.adoc index 77bd1e2e9c..9a9ef5a839 100644 --- a/docs/modules/ROOT/partials/python/connection/UserManager.adoc +++ b/docs/modules/ROOT/partials/python/connection/UserManager.adoc @@ -4,16 +4,25 @@ Provides access to all user management methods. // tag::methods[] -[#_UserManager_all_] +[#_UserManager_all_consistency_level_ConsistencyLevel_None] ==== all [source,python] ---- -all() -> List[User] +all(consistency_level: ConsistencyLevel | None = None) -> List[User] ---- Retrieves all users which exist on the TypeDB server. +[caption=""] +.Input parameters +[cols=",,,"] +[options="header"] +|=== +|Name |Description |Type |Default Value +a| `consistency_level` a| The consistency level to use for the operation. Strongest possible by default a| `ConsistencyLevel \| None` a| `None` +|=== + [caption=""] .Returns `List[User]` @@ -23,14 +32,15 @@ Retrieves all users which exist on the TypeDB server. [source,python] ---- driver.users.all() +driver.users.all(ConsistencyLevel.Strong()) ---- -[#_UserManager_contains_username_str] +[#_UserManager_contains_username_str_consistency_level_ConsistencyLevel_None] ==== contains [source,python] ---- -contains(username: str) -> bool +contains(username: str, consistency_level: ConsistencyLevel | None = None) -> bool ---- Checks if a user with the given name exists. @@ -41,7 +51,8 @@ Checks if a user with the given name exists. [options="header"] |=== |Name |Description |Type |Default Value -a| `username` a| The user name to be checked a| `str` a| +a| `username` a| The username to be checked a| `str` a| +a| `consistency_level` a| The consistency level to use for the operation. Strongest possible by default a| `ConsistencyLevel \| None` a| `None` |=== [caption=""] @@ -53,6 +64,7 @@ a| `username` a| The user name to be checked a| `str` a| [source,python] ---- driver.users.contains(username) +driver.users.contains(username, ConsistencyLevel.Strong()) ---- [#_UserManager_create_username_str_password_str] @@ -63,7 +75,7 @@ driver.users.contains(username) create(username: str, password: str) -> None ---- -Create a user with the given name and password. +Creates a user with the given name and password. [caption=""] .Input parameters @@ -86,15 +98,15 @@ a| `password` a| The password of the user to be created a| `str` a| driver.users.create(username, password) ---- -[#_UserManager_get_username_str] +[#_UserManager_get_username_str_consistency_level_ConsistencyLevel_None] ==== get [source,python] ---- -get(username: str) -> User | None +get(username: str, consistency_level: ConsistencyLevel | None = None) -> User | None ---- -Retrieve a user with the given name. +Retrieves a user with the given name. [caption=""] .Input parameters @@ -103,6 +115,7 @@ Retrieve a user with the given name. |=== |Name |Description |Type |Default Value a| `username` a| The name of the user to retrieve a| `str` a| +a| `consistency_level` a| The consistency level to use for the operation. Strongest possible by default a| `ConsistencyLevel \| None` a| `None` |=== [caption=""] @@ -114,17 +127,27 @@ a| `username` a| The name of the user to retrieve a| `str` a| [source,python] ---- driver.users.get(username) +driver.users.get(username, ConsistencyLevel.Strong()) ---- -[#_UserManager_get_current_user_] +[#_UserManager_get_current_user_consistency_level_ConsistencyLevel_None] ==== get_current_user [source,python] ---- -get_current_user() -> User | None +get_current_user(consistency_level: ConsistencyLevel | None = None) -> User | None ---- -Retrieve the name of the user who opened the current connection. +Retrieves the name of the user who opened the current connection. + +[caption=""] +.Input parameters +[cols=",,,"] +[options="header"] +|=== +|Name |Description |Type |Default Value +a| `consistency_level` a| The consistency level to use for the operation. Strongest possible by default a| `ConsistencyLevel \| None` a| `None` +|=== [caption=""] .Returns @@ -135,6 +158,7 @@ Retrieve the name of the user who opened the current connection. [source,python] ---- driver.users.get_current_user() +driver.users.get_current_user(ConsistencyLevel.Strong()) ---- // end::methods[] diff --git a/docs/modules/ROOT/partials/python/transaction/QueryOptions.adoc b/docs/modules/ROOT/partials/python/transaction/QueryOptions.adoc index 2e2b78a390..6d0ebac172 100644 --- a/docs/modules/ROOT/partials/python/transaction/QueryOptions.adoc +++ b/docs/modules/ROOT/partials/python/transaction/QueryOptions.adoc @@ -1,7 +1,7 @@ [#_QueryOptions] === QueryOptions -TypeDB transaction options. ``QueryOptions`` object can be used to override the default server behaviour for executed queries. +TypeDB query options. ``QueryOptions`` object can be used to override the default server behaviour for executed queries. Options could be specified either as constructor arguments or using properties assignment. diff --git a/docs/modules/ROOT/partials/python/transaction/TransactionOptions.adoc b/docs/modules/ROOT/partials/python/transaction/TransactionOptions.adoc index a9d1f5e550..5fe3a43715 100644 --- a/docs/modules/ROOT/partials/python/transaction/TransactionOptions.adoc +++ b/docs/modules/ROOT/partials/python/transaction/TransactionOptions.adoc @@ -20,6 +20,7 @@ transaction_options.schema_lock_acquire_timeout_millis = 50_000 [options="header"] |=== |Name |Type |Description +a| `read_consistency_level` a| `ConsistencyLevel \| None` a| If set, specifies the requested consistency level of the transaction opening operation. Affects only read transactions, as write and schema transactions require primary replicas. a| `schema_lock_acquire_timeout_millis` a| `int \| None` a| If set, specifies how long the driver should wait if opening a transaction is blocked by a schema write lock. a| `transaction_timeout_millis` a| `int \| None` a| If set, specifies a timeout for killing transactions automatically, preventing memory leaks in unclosed transactions. |=== diff --git a/docs/modules/ROOT/partials/python/value/Datetime.adoc b/docs/modules/ROOT/partials/python/value/Datetime.adoc index 7d318b3900..11bdedb61f 100644 --- a/docs/modules/ROOT/partials/python/value/Datetime.adoc +++ b/docs/modules/ROOT/partials/python/value/Datetime.adoc @@ -10,23 +10,23 @@ An extension class for ``datetime.datetime`` class to store additional informati [options="header"] |=== |Name |Type |Description -a| `date` a| `date` a| Return the date part. -a| `datetime_without_nanos` a| `datetime` a| Return the standard library’s datetime, containing data up to microseconds. -a| `day` a| `int` a| Return the datetime’s day (1-31). -a| `hour` a| `int` a| Return the datetime’s hour (0-23). -a| `microsecond` a| `int` a| Return the rounded number of microseconds. -a| `minute` a| `int` a| Return the datetime’s minute (0-59). -a| `month` a| `int` a| Return the datetime’s month (1-12). -a| `nanos` a| `int` a| Return the nanoseconds part. -a| `offset_seconds` a| `str \| None` a| Return the timezone offset (local minus UTC) in seconds. None if an IANA name is used for the initialisation instead. -a| `second` a| `int` a| Return the datetime’s second (0-59). -a| `total_seconds` a| `float` a| Return the total number of seconds including the nanoseconds part as a float. +a| `date` a| `date` a| Returns the date part. +a| `datetime_without_nanos` a| `datetime` a| Returns the standard library’s datetime, containing data up to microseconds. +a| `day` a| `int` a| Returns the datetime’s day (1-31). +a| `hour` a| `int` a| Returns the datetime’s hour (0-23). +a| `microsecond` a| `int` a| Returns the rounded number of microseconds. +a| `minute` a| `int` a| Returns the datetime’s minute (0-59). +a| `month` a| `int` a| Returns the datetime’s month (1-12). +a| `nanos` a| `int` a| Returns the nanoseconds part. +a| `offset_seconds` a| `str \| None` a| Returns the timezone offset (local minus UTC) in seconds. None if an IANA name is used for the initialisation instead. +a| `second` a| `int` a| Returns the datetime’s second (0-59). +a| `total_seconds` a| `float` a| Returns the total number of seconds including the nanoseconds part as a float. ValueError – If timestamp is before the start of the epoch. -a| `tz_name` a| `str \| None` a| Return the timezone IANA name. None if fixed offset is used for the initialisation instead. -a| `tzinfo` a| `tzinfo` a| Return timezone info. -a| `weekday` a| `int` a| Return the day of the week as an integer, where Monday == 0 … Sunday == 6. -a| `year` a| `int` a| Return the datetime’s year (1-9999). +a| `tz_name` a| `str \| None` a| Returns the timezone IANA name. None if fixed offset is used for the initialisation instead. +a| `tzinfo` a| `tzinfo` a| Returns timezone info. +a| `weekday` a| `int` a| Returns the day of the week as an integer, where Monday == 0 … Sunday == 6. +a| `year` a| `int` a| Returns the datetime’s year (1-9999). |=== // end::properties[] @@ -101,7 +101,7 @@ a| `offset_seconds` a| Offset in seconds from UTC (e.g., 3600 for +01:00, -18000 isoformat() -> str ---- -Return the time formatted according to ISO. +Returns the time formatted according to ISO. [caption=""] .Returns diff --git a/docs/modules/ROOT/partials/rust/answer/ConceptDocument.adoc b/docs/modules/ROOT/partials/rust/answer/ConceptDocument.adoc deleted file mode 100644 index 9da44627b7..0000000000 --- a/docs/modules/ROOT/partials/rust/answer/ConceptDocument.adoc +++ /dev/null @@ -1,50 +0,0 @@ -[#_struct_ConceptDocument] -=== ConceptDocument - -*Implements traits:* - -* `Clone` -* `Debug` -* `PartialEq` -* `StructuralPartialEq` - -A single document of concepts representing substitutions for variables in the query. Contains a Header (query type), and the document of concepts. - -[caption=""] -.Fields -// tag::properties[] -[cols=",,"] -[options="header"] -|=== -|Name |Type |Description -a| `root` a| `Option` a| -|=== -// end::properties[] - -// tag::methods[] -[#_struct_ConceptDocument_get_query_type_] -==== get_query_type - -[source,rust] ----- -pub fn get_query_type(&self) -> QueryType ----- - -Retrieve the executed query’s type (shared by all elements in this stream). - -[caption=""] -.Returns -[source,rust] ----- -QueryType ----- - -[caption=""] -.Code examples -[source,rust] ----- -concept_document.get_query_type() ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/answer/ConceptDocumentHeader.adoc b/docs/modules/ROOT/partials/rust/answer/ConceptDocumentHeader.adoc deleted file mode 100644 index 799d1ab33e..0000000000 --- a/docs/modules/ROOT/partials/rust/answer/ConceptDocumentHeader.adoc +++ /dev/null @@ -1,20 +0,0 @@ -[#_struct_ConceptDocumentHeader] -=== ConceptDocumentHeader - -*Implements traits:* - -* `Debug` -* `PartialEq` -* `StructuralPartialEq` - -[caption=""] -.Fields -// tag::properties[] -[cols=",,"] -[options="header"] -|=== -|Name |Type |Description -a| `query_type` a| `QueryType` a| -|=== -// end::properties[] - diff --git a/docs/modules/ROOT/partials/rust/answer/ConceptRow.adoc b/docs/modules/ROOT/partials/rust/answer/ConceptRow.adoc deleted file mode 100644 index 78ad2c27b1..0000000000 --- a/docs/modules/ROOT/partials/rust/answer/ConceptRow.adoc +++ /dev/null @@ -1,165 +0,0 @@ -[#_struct_ConceptRow] -=== ConceptRow - -*Implements traits:* - -* `Clone` -* `Debug` -* `Display` -* `PartialEq` -* `StructuralPartialEq` - -A single row of concepts representing substitutions for variables in the query. Contains a Header (column names and query type), and the row of optional concepts. An empty concept in a column means the variable does not have a substitution in this answer. - -[caption=""] -.Fields -// tag::properties[] -[cols=",,"] -[options="header"] -|=== -|Name |Type |Description -a| `row` a| `Vec>` a| -|=== -// end::properties[] - -// tag::methods[] -[#_struct_ConceptRow_get_var_name] -==== get - -[source,rust] ----- -pub fn get(&self, column_name: &str) -> Result> ----- - -Retrieves a concept for a given variable. Returns an empty optional if the variable name has an empty answer. Returns an error if the variable name is not present. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `var_name` a| — The variable name in the row to retrieve a| -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result> ----- - -[caption=""] -.Code examples -[source,rust] ----- -concept_row.get(var_name) ----- - -[#_struct_ConceptRow_get_column_names_] -==== get_column_names - -[source,rust] ----- -pub fn get_column_names(&self) -> &[String] ----- - -Retrieve the row column names (shared by all elements in this stream). - -[caption=""] -.Returns -[source,rust] ----- -&[String] ----- - -[caption=""] -.Code examples -[source,rust] ----- -concept_row.get_column_names() ----- - -[#_struct_ConceptRow_get_concepts_] -==== get_concepts - -[source,rust] ----- -pub fn get_concepts(&self) -> impl Iterator ----- - -Produces an iterator over all concepts in this ``ConceptRow``, skipping empty results - -[caption=""] -.Returns -[source,rust] ----- -impl Iterator ----- - -[caption=""] -.Code examples -[source,rust] ----- -concept_row.concepts() ----- - -[#_struct_ConceptRow_get_index_column_index_usize] -==== get_index - -[source,rust] ----- -pub fn get_index(&self, column_index: usize) -> Result> ----- - -Retrieves a concept for a given column index. Returns an empty optional if the index points to an empty answer. Returns an error if the index is not in the row’s range. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `column_index` a| — The position in the row to retrieve a| `usize` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result> ----- - -[caption=""] -.Code examples -[source,rust] ----- -concept_row.get_position(column_index) ----- - -[#_struct_ConceptRow_get_query_type_] -==== get_query_type - -[source,rust] ----- -pub fn get_query_type(&self) -> QueryType ----- - -Retrieve the executed query’s type (shared by all elements in this stream). - -[caption=""] -.Returns -[source,rust] ----- -QueryType ----- - -[caption=""] -.Code examples -[source,rust] ----- -concept_row.get_query_type() ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/answer/ConceptRowHeader.adoc b/docs/modules/ROOT/partials/rust/answer/ConceptRowHeader.adoc deleted file mode 100644 index 19ecc29235..0000000000 --- a/docs/modules/ROOT/partials/rust/answer/ConceptRowHeader.adoc +++ /dev/null @@ -1,21 +0,0 @@ -[#_struct_ConceptRowHeader] -=== ConceptRowHeader - -*Implements traits:* - -* `Debug` -* `PartialEq` -* `StructuralPartialEq` - -[caption=""] -.Fields -// tag::properties[] -[cols=",,"] -[options="header"] -|=== -|Name |Type |Description -a| `column_names` a| `Vec` a| -a| `query_type` a| `QueryType` a| -|=== -// end::properties[] - diff --git a/docs/modules/ROOT/partials/rust/answer/JSON.adoc b/docs/modules/ROOT/partials/rust/answer/JSON.adoc deleted file mode 100644 index cc82c6c277..0000000000 --- a/docs/modules/ROOT/partials/rust/answer/JSON.adoc +++ /dev/null @@ -1,19 +0,0 @@ -[#_enum_JSON] -=== JSON - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `Array(Vec)` -a| `Boolean(bool)` -a| `Null` -a| `Number(f64)` -a| `Object(HashMap, JSON>)` -a| `String(Cow<'static, str>)` -|=== -// end::enum_constants[] - diff --git a/docs/modules/ROOT/partials/rust/answer/Leaf.adoc b/docs/modules/ROOT/partials/rust/answer/Leaf.adoc deleted file mode 100644 index 3ae4666bc4..0000000000 --- a/docs/modules/ROOT/partials/rust/answer/Leaf.adoc +++ /dev/null @@ -1,17 +0,0 @@ -[#_enum_Leaf] -=== Leaf - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `Concept(Concept)` -a| `Empty` -a| `Kind(Kind)` -a| `ValueType(ValueType)` -|=== -// end::enum_constants[] - diff --git a/docs/modules/ROOT/partials/rust/answer/Node.adoc b/docs/modules/ROOT/partials/rust/answer/Node.adoc deleted file mode 100644 index 740520c2bb..0000000000 --- a/docs/modules/ROOT/partials/rust/answer/Node.adoc +++ /dev/null @@ -1,16 +0,0 @@ -[#_enum_Node] -=== Node - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `Leaf(Option)` -a| `List(Vec)` -a| `Map(HashMap)` -|=== -// end::enum_constants[] - diff --git a/docs/modules/ROOT/partials/rust/answer/QueryAnswer.adoc b/docs/modules/ROOT/partials/rust/answer/QueryAnswer.adoc deleted file mode 100644 index a08f3fc6ce..0000000000 --- a/docs/modules/ROOT/partials/rust/answer/QueryAnswer.adoc +++ /dev/null @@ -1,163 +0,0 @@ -[#_enum_QueryAnswer] -=== QueryAnswer - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `ConceptDocumentStream(Arc, BoxStream<'static, Result>)` -a| `ConceptRowStream(Arc, BoxStream<'static, Result>)` -a| `Ok(QueryType)` -|=== -// end::enum_constants[] - -// tag::methods[] -[#_enum_QueryAnswer_get_query_type_] -==== get_query_type - -[source,rust] ----- -pub fn get_query_type(&self) -> QueryType ----- - -Retrieve the executed query’s type (shared by all elements in this stream). - -[caption=""] -.Returns -[source,rust] ----- -QueryType ----- - -[caption=""] -.Code examples -[source,rust] ----- -query_answer.get_query_type() ----- - -[#_enum_QueryAnswer_into_documents_] -==== into_documents - -[source,rust] ----- -pub fn into_documents(self) -> BoxStream<'static, Result> ----- - -Unwraps the ``QueryAnswer`` into a ``ConceptDocumentStream``. Panics if it is not a ``ConceptDocumentStream``. - -[caption=""] -.Returns -[source,rust] ----- -BoxStream<'static, Result> ----- - -[caption=""] -.Code examples -[source,rust] ----- -query_answer.into_documents() ----- - -[#_enum_QueryAnswer_into_rows_] -==== into_rows - -[source,rust] ----- -pub fn into_rows(self) -> BoxStream<'static, Result> ----- - -Unwraps the ``QueryAnswer`` into a ``ConceptRowStream``. Panics if it is not a ``ConceptRowStream``. - -[caption=""] -.Returns -[source,rust] ----- -BoxStream<'static, Result> ----- - -[caption=""] -.Code examples -[source,rust] ----- -query_answer.into_rows() ----- - -[#_enum_QueryAnswer_is_document_stream_] -==== is_document_stream - -[source,rust] ----- -pub fn is_document_stream(&self) -> bool ----- - -Check if the ``QueryAnswer`` is a ``ConceptDocumentStream``. - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[caption=""] -.Code examples -[source,rust] ----- -query_answer.is_document_stream() ----- - -[#_enum_QueryAnswer_is_ok_] -==== is_ok - -[source,rust] ----- -pub fn is_ok(&self) -> bool ----- - -Check if the ``QueryAnswer`` is an ``Ok`` response. - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[caption=""] -.Code examples -[source,rust] ----- -query_answer.is_ok() ----- - -[#_enum_QueryAnswer_is_row_stream_] -==== is_row_stream - -[source,rust] ----- -pub fn is_row_stream(&self) -> bool ----- - -Check if the ``QueryAnswer`` is a ``ConceptRowStream``. - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[caption=""] -.Code examples -[source,rust] ----- -query_answer.is_row_stream() ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/answer/QueryType.adoc b/docs/modules/ROOT/partials/rust/answer/QueryType.adoc deleted file mode 100644 index 60402861e9..0000000000 --- a/docs/modules/ROOT/partials/rust/answer/QueryType.adoc +++ /dev/null @@ -1,18 +0,0 @@ -[#_enum_QueryType] -=== QueryType - -This enum is used to specify the type of the query resulted in this answer. - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `ReadQuery = 0` -a| `SchemaQuery = 2` -a| `WriteQuery = 1` -|=== -// end::enum_constants[] - diff --git a/docs/modules/ROOT/partials/rust/answer/Trait_Promise.adoc b/docs/modules/ROOT/partials/rust/answer/Trait_Promise.adoc deleted file mode 100644 index a1e818a9f2..0000000000 --- a/docs/modules/ROOT/partials/rust/answer/Trait_Promise.adoc +++ /dev/null @@ -1,34 +0,0 @@ -[#_trait_Promise] -=== Trait Promise - -[tabs] -==== -async:: -+ --- -Async promise, an alias for Rust’s built-in Future. A ``BoxPromise`` is an alias for Rust’s built-in BoxFuture. - -[caption=""] -.Examples -[source,rust] ----- -promise.await ----- - --- - -sync:: -+ --- -A resolvable promise that can be resolved at a later time. a ``BoxPromise`` is in practical terms a ``Box<dyn Promise>`` and resolves with ``.resolve()``. - -[caption=""] -.Examples -[source,rust] ----- -promise.resolve() ----- - --- -==== - diff --git a/docs/modules/ROOT/partials/rust/concept/Concept.adoc b/docs/modules/ROOT/partials/rust/concept/Concept.adoc deleted file mode 100644 index 7784cace50..0000000000 --- a/docs/modules/ROOT/partials/rust/concept/Concept.adoc +++ /dev/null @@ -1,673 +0,0 @@ -[#_enum_Concept] -=== Concept - -The fundamental TypeQL object. - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `Attribute(Attribute)` -a| `AttributeType(AttributeType)` -a| `Entity(Entity)` -a| `EntityType(EntityType)` -a| `Relation(Relation)` -a| `RelationType(RelationType)` -a| `RoleType(RoleType)` -a| `Value(Value)` -|=== -// end::enum_constants[] - -// tag::methods[] -[#_enum_Concept_get_category_] -==== get_category - -[source,rust] ----- -pub fn get_category(&self) -> ConceptCategory ----- - -Retrieves the category of this Concept. - -[caption=""] -.Returns -[source,rust] ----- -ConceptCategory ----- - -[#_enum_Concept_get_label_] -==== get_label - -[source,rust] ----- -pub fn get_label(&self) -> &str ----- - -Retrieves the label of this Concept. If this is an Instance, returns the label of the type of this instance (“unknown” if type fetching is disabled). If this is a Value, returns the label of the value type of the value. If this is a Type, returns the label of the type. - -[caption=""] -.Returns -[source,rust] ----- -&str ----- - -[#_enum_Concept_is_attribute_] -==== is_attribute - -[source,rust] ----- -pub fn is_attribute(&self) -> bool ----- - -Check if this Concept represents an Attribute instance from the database - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[#_enum_Concept_is_attribute_type_] -==== is_attribute_type - -[source,rust] ----- -pub fn is_attribute_type(&self) -> bool ----- - -Check if this Concept represents an Attribute Type from the schema of the database - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[#_enum_Concept_is_boolean_] -==== is_boolean - -[source,rust] ----- -pub fn is_boolean(&self) -> bool ----- - -Check if this Concept holds a boolean as an AttributeType, an Attribute, or a Value - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[#_enum_Concept_is_date_] -==== is_date - -[source,rust] ----- -pub fn is_date(&self) -> bool ----- - -Check if this Concept holds a date as an AttributeType, an Attribute, or a Value - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[#_enum_Concept_is_datetime_] -==== is_datetime - -[source,rust] ----- -pub fn is_datetime(&self) -> bool ----- - -Check if this Concept holds a datetime as an AttributeType, an Attribute, or a Value - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[#_enum_Concept_is_datetime_tz_] -==== is_datetime_tz - -[source,rust] ----- -pub fn is_datetime_tz(&self) -> bool ----- - -Check if this Concept holds a timezoned-datetime as an AttributeType, an Attribute, or a Value - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[#_enum_Concept_is_decimal_] -==== is_decimal - -[source,rust] ----- -pub fn is_decimal(&self) -> bool ----- - -Check if this Concept holds a fixed-decimal as an AttributeType, an Attribute, or a Value - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[#_enum_Concept_is_double_] -==== is_double - -[source,rust] ----- -pub fn is_double(&self) -> bool ----- - -Check if this Concept holds a double as an AttributeType, an Attribute, or a Value - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[#_enum_Concept_is_duration_] -==== is_duration - -[source,rust] ----- -pub fn is_duration(&self) -> bool ----- - -Check if this Concept holds a duration as an AttributeType, an Attribute, or a Value - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[#_enum_Concept_is_entity_] -==== is_entity - -[source,rust] ----- -pub fn is_entity(&self) -> bool ----- - -Check if this Concept represents an Entity instance from the database - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[#_enum_Concept_is_entity_type_] -==== is_entity_type - -[source,rust] ----- -pub fn is_entity_type(&self) -> bool ----- - -Check if this Concept represents an Entity Type from the schema of the database - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[#_enum_Concept_is_instance_] -==== is_instance - -[source,rust] ----- -pub fn is_instance(&self) -> bool ----- - -Check if this Concept represents a stored database instance from the database. These are exactly: Entity, Relation, and Attribute - -Equivalent to: - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[caption=""] -.Code examples -[source,rust] ----- -concept.is_entity() || concept.is_relation() || concept.is_attribute() ----- - -[#_enum_Concept_is_integer_] -==== is_integer - -[source,rust] ----- -pub fn is_integer(&self) -> bool ----- - -Check if this Concept holds an integer as an AttributeType, an Attribute, or a Value - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[#_enum_Concept_is_relation_] -==== is_relation - -[source,rust] ----- -pub fn is_relation(&self) -> bool ----- - -Check if this Concept represents an Relation instance from the database - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[#_enum_Concept_is_relation_type_] -==== is_relation_type - -[source,rust] ----- -pub fn is_relation_type(&self) -> bool ----- - -Check if this Concept represents a Relation Type from the schema of the database - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[#_enum_Concept_is_role_type_] -==== is_role_type - -[source,rust] ----- -pub fn is_role_type(&self) -> bool ----- - -Check if this Concept represents a Role Type from the schema of the database - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[#_enum_Concept_is_string_] -==== is_string - -[source,rust] ----- -pub fn is_string(&self) -> bool ----- - -Check if this Concept holds a string as an AttributeType, an Attribute, or a Value - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[#_enum_Concept_is_struct_] -==== is_struct - -[source,rust] ----- -pub fn is_struct(&self) -> bool ----- - -Check if this Concept holds a struct as an AttributeType, an Attribute, or a Value - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[#_enum_Concept_is_type_] -==== is_type - -[source,rust] ----- -pub fn is_type(&self) -> bool ----- - -Check if this Concept represents a Type from the schema of the database. These are exactly: Entity Types, Relation Types, Role Types, and Attribute Types - -Equivalent to: - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[caption=""] -.Code examples -[source,rust] ----- -concept.is_entity_type() || concept.is_relation_type() || concept.is_role_type() || concept.is_attribute_type() ----- - -[#_enum_Concept_is_value_] -==== is_value - -[source,rust] ----- -pub fn is_value(&self) -> bool ----- - -Check if this Concept represents a Value returned by the database - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[#_enum_Concept_try_get_boolean_] -==== try_get_boolean - -[source,rust] ----- -pub fn try_get_boolean(&self) -> Option ----- - -Retrieves the boolean value of this Concept, if it exists. If this is a boolean-valued Attribute Instance, returns the boolean value of this instance. If this a boolean-valued Value, returns the boolean value. Otherwise, returns None. - -[caption=""] -.Returns -[source,rust] ----- -Option ----- - -[#_enum_Concept_try_get_date_] -==== try_get_date - -[source,rust] ----- -pub fn try_get_date(&self) -> Option ----- - -Retrieves the date value of this Concept, if it exists. If this is a date-valued Attribute Instance, returns the date value of this instance. If this a date-valued Value, returns the date value. Otherwise, returns None. - -[caption=""] -.Returns -[source,rust] ----- -Option ----- - -[#_enum_Concept_try_get_datetime_] -==== try_get_datetime - -[source,rust] ----- -pub fn try_get_datetime(&self) -> Option ----- - -Retrieves the datetime value of this Concept, if it exists. If this is a datetime-valued Attribute Instance, returns the datetime value of this instance. If this a datetime-valued Value, returns the datetime value. Otherwise, returns None. - -[caption=""] -.Returns -[source,rust] ----- -Option ----- - -[#_enum_Concept_try_get_datetime_tz_] -==== try_get_datetime_tz - -[source,rust] ----- -pub fn try_get_datetime_tz(&self) -> Option> ----- - -Retrieves the timezoned-datetime value of this Concept, if it exists. If this is a timezoned-datetime valued Attribute Instance, returns the timezoned-datetime value of this instance. If this a timezoned-datetime valued Value, returns the timezoned-datetime value. Otherwise, returns None. - -[caption=""] -.Returns -[source,rust] ----- -Option> ----- - -[#_enum_Concept_try_get_decimal_] -==== try_get_decimal - -[source,rust] ----- -pub fn try_get_decimal(&self) -> Option ----- - -Retrieves the fixed-decimal value of this Concept, if it exists. If this is a fixed-decimal valued Attribute Instance, returns the fixed-decimal value of this instance. If this a fixed-decimal valued Value, returns the fixed-decimal value. Otherwise, returns None. - -[caption=""] -.Returns -[source,rust] ----- -Option ----- - -[#_enum_Concept_try_get_double_] -==== try_get_double - -[source,rust] ----- -pub fn try_get_double(&self) -> Option ----- - -Retrieves the double value of this Concept, if it exists. If this is a double-valued Attribute Instance, returns the double value of this instance. If this a double-valued Value, returns the double value. Otherwise, returns None. - -[caption=""] -.Returns -[source,rust] ----- -Option ----- - -[#_enum_Concept_try_get_duration_] -==== try_get_duration - -[source,rust] ----- -pub fn try_get_duration(&self) -> Option ----- - -Retrieves the duration value of this Concept, if it exists. If this is a duration-valued Attribute Instance, returns the duration value of this instance. If this a duration-valued Value, returns the duration value. Otherwise, returns None. - -[caption=""] -.Returns -[source,rust] ----- -Option ----- - -[#_enum_Concept_try_get_iid_] -==== try_get_iid - -[source,rust] ----- -pub fn try_get_iid(&self) -> Option<&IID> ----- - -Retrieves the unique id (IID) of this Concept. If this is an Entity or Relation Instance, returns the IID of the instance. Otherwise, returns None. - -[caption=""] -.Returns -[source,rust] ----- -Option<&IID> ----- - -[#_enum_Concept_try_get_integer_] -==== try_get_integer - -[source,rust] ----- -pub fn try_get_integer(&self) -> Option ----- - -Retrieves the integer value of this Concept, if it exists. If this is an integer-valued Attribute Instance, returns the integer value of this instance. If this an integer-valued Value, returns the integer value. Otherwise, returns None. - -[caption=""] -.Returns -[source,rust] ----- -Option ----- - -[#_enum_Concept_try_get_label_] -==== try_get_label - -[source,rust] ----- -pub fn try_get_label(&self) -> Option<&str> ----- - -Retrieves the optional label of the concept. If this is an Instance, returns the label of the type of this instance (None if type fetching is disabled). If this is a Value, returns the label of the value type of the value. If this is a Type, returns the label of the type. - -[caption=""] -.Returns -[source,rust] ----- -Option<&str> ----- - -[#_enum_Concept_try_get_string_] -==== try_get_string - -[source,rust] ----- -pub fn try_get_string(&self) -> Option<&str> ----- - -Retrieves the string value of this Concept, if it exists. If this is a string-valued Attribute Instance, returns the string value of this instance. If this a string-valued Value, returns the string value. Otherwise, returns None. - -[caption=""] -.Returns -[source,rust] ----- -Option<&str> ----- - -[#_enum_Concept_try_get_struct_] -==== try_get_struct - -[source,rust] ----- -pub fn try_get_struct(&self) -> Option<&Struct> ----- - -Retrieves the struct value of this Concept, if it exists. If this is a struct-valued Attribute Instance, returns the struct value of this instance. If this a struct-valued Value, returns the struct value. Otherwise, returns None. - -[caption=""] -.Returns -[source,rust] ----- -Option<&Struct> ----- - -[#_enum_Concept_try_get_value_] -==== try_get_value - -[source,rust] ----- -pub fn try_get_value(&self) -> Option<&Value> ----- - -Retrieves the value of this Concept, if it exists. If this is an Attribute Instance, returns the value of this instance. If this a Value, returns the value. Otherwise, returns empty. - -[caption=""] -.Returns -[source,rust] ----- -Option<&Value> ----- - -[#_enum_Concept_try_get_value_label_] -==== try_get_value_label - -[source,rust] ----- -pub fn try_get_value_label(&self) -> Option<&str> ----- - -Retrieves the label of the value type of the concept, if it exists. If this is an Attribute Instance, returns the label of the value of this instance. If this is a Value, returns the label of the value. If this is an Attribute Type, returns the label of the value type that the schema permits for the attribute type, if one is defined. Otherwise, returns None. - -[caption=""] -.Returns -[source,rust] ----- -Option<&str> ----- - -[#_enum_Concept_try_get_value_type_] -==== try_get_value_type - -[source,rust] ----- -pub fn try_get_value_type(&self) -> Option ----- - -Retrieves the value type enum of the concept, if it exists. If this is an Attribute Instance, returns the value type of the value of this instance. If this is a Value, returns the value type of the value. If this is an Attribute Type, returns value type that the schema permits for the attribute type, if one is defined. Otherwise, returns None. - -[caption=""] -.Returns -[source,rust] ----- -Option ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/concept/ConceptCategory.adoc b/docs/modules/ROOT/partials/rust/concept/ConceptCategory.adoc deleted file mode 100644 index e143bae7fe..0000000000 --- a/docs/modules/ROOT/partials/rust/concept/ConceptCategory.adoc +++ /dev/null @@ -1,21 +0,0 @@ -[#_enum_ConceptCategory] -=== ConceptCategory - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `Attribute` -a| `AttributeType` -a| `Entity` -a| `EntityType` -a| `Relation` -a| `RelationType` -a| `RoleType` -a| `Value` -|=== -// end::enum_constants[] - diff --git a/docs/modules/ROOT/partials/rust/concept/Kind.adoc b/docs/modules/ROOT/partials/rust/concept/Kind.adoc deleted file mode 100644 index b45d83ad15..0000000000 --- a/docs/modules/ROOT/partials/rust/concept/Kind.adoc +++ /dev/null @@ -1,19 +0,0 @@ -[#_enum_Kind] -=== Kind - -Kind represents the base of a defined type to describe its capabilities. For example, “define entity person;” defines a type “person” of a kind “entity”. - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `Attribute` -a| `Entity` -a| `Relation` -a| `Role` -|=== -// end::enum_constants[] - diff --git a/docs/modules/ROOT/partials/rust/connection/Credentials.adoc b/docs/modules/ROOT/partials/rust/connection/Credentials.adoc deleted file mode 100644 index ebe39a1200..0000000000 --- a/docs/modules/ROOT/partials/rust/connection/Credentials.adoc +++ /dev/null @@ -1,81 +0,0 @@ -[#_struct_Credentials] -=== Credentials - -*Implements traits:* - -* `Clone` -* `Debug` - -User credentials for connecting to TypeDB - -// tag::methods[] -[#_struct_Credentials_new_username_str_password_str] -==== new - -[source,rust] ----- -pub fn new(username: &str, password: &str) -> Self ----- - -Creates a credentials with username and password. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `username` a| — The name of the user to connect as a| `&str` -a| `password` a| — The password for the user a| `&str` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Self ----- - -[caption=""] -.Code examples -[source,rust] ----- -Credentials::new(username, password); ----- - -[#_struct_Credentials_password_] -==== password - -[source,rust] ----- -pub fn password(&self) -> &str ----- - -Retrieves the password used. - -[caption=""] -.Returns -[source,rust] ----- -&str ----- - -[#_struct_Credentials_username_] -==== username - -[source,rust] ----- -pub fn username(&self) -> &str ----- - -Retrieves the username used. - -[caption=""] -.Returns -[source,rust] ----- -&str ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/connection/Database.adoc b/docs/modules/ROOT/partials/rust/connection/Database.adoc deleted file mode 100644 index eb86049973..0000000000 --- a/docs/modules/ROOT/partials/rust/connection/Database.adoc +++ /dev/null @@ -1,359 +0,0 @@ -[#_struct_Database] -=== Database - -*Implements traits:* - -* `Debug` - -A TypeDB database - -// tag::methods[] -[#_struct_Database_delete_] -==== delete - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn delete(self: Arc) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn delete(self: Arc) -> Result ----- - --- -==== - -Deletes this database. - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -database.delete().await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -database.delete(); ----- - --- -==== - -[#_struct_Database_export_to_file_schema_file_path_impl_AsRef_Path_data_file_path_impl_AsRef_Path_] -==== export_to_file - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn export_to_file( - &self, - schema_file_path: impl AsRef, - data_file_path: impl AsRef, -) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn export_to_file( - &self, - schema_file_path: impl AsRef, - data_file_path: impl AsRef, -) -> Result ----- - --- -==== - -Export a database into a schema definition and a data files saved to the disk. This is a blocking operation and may take a significant amount of time depending on the database size. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `schema_file_path` a| — The path to the schema definition file to be created a| `impl AsRef` -a| `data_file_path` a| — The path to the data file to be created a| `impl AsRef` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -database.export_to_file(schema_path, data_path).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -database.export_to_file(schema_path, data_path); ----- - --- -==== - -[#_struct_Database_name_] -==== name - -[source,rust] ----- -pub fn name(&self) -> &str ----- - -Retrieves the database name as a string. - -[caption=""] -.Returns -[source,rust] ----- -&str ----- - -[#_struct_Database_preferred_replica_info_] -==== preferred_replica_info - -[source,rust] ----- -pub fn preferred_replica_info(&self) -> Option ----- - -Returns the preferred replica for this database. Operations which can be run on any replica will prefer to use this replica. _Only works in TypeDB Cloud / Enterprise_ - -[caption=""] -.Returns -[source,rust] ----- -Option ----- - -[caption=""] -.Code examples -[source,rust] ----- -database.preferred_replica_info(); ----- - -[#_struct_Database_primary_replica_info_] -==== primary_replica_info - -[source,rust] ----- -pub fn primary_replica_info(&self) -> Option ----- - -Returns the primary replica for this database. _Only works in TypeDB Cloud / Enterprise_ - -[caption=""] -.Returns -[source,rust] ----- -Option ----- - -[caption=""] -.Code examples -[source,rust] ----- -database.primary_replica_info() ----- - -[#_struct_Database_replicas_info_] -==== replicas_info - -[source,rust] ----- -pub fn replicas_info(&self) -> Vec ----- - -Returns the ``Replica`` instances for this database. _Only works in TypeDB Cloud / Enterprise_ - -[caption=""] -.Returns -[source,rust] ----- -Vec ----- - -[caption=""] -.Code examples -[source,rust] ----- -database.replicas_info() ----- - -[#_struct_Database_schema_] -==== schema - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn schema(&self) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn schema(&self) -> Result ----- - --- -==== - -Returns a full schema text as a valid TypeQL define query string. - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -database.schema().await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -database.schema(); ----- - --- -==== - -[#_struct_Database_type_schema_] -==== type_schema - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn type_schema(&self) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn type_schema(&self) -> Result ----- - --- -==== - -Returns the types in the schema as a valid TypeQL define query string. - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -database.type_schema().await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -database.type_schema(); ----- - --- -==== - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/connection/DatabaseManager.adoc b/docs/modules/ROOT/partials/rust/connection/DatabaseManager.adoc deleted file mode 100644 index 5f2f7cb73a..0000000000 --- a/docs/modules/ROOT/partials/rust/connection/DatabaseManager.adoc +++ /dev/null @@ -1,360 +0,0 @@ -[#_struct_DatabaseManager] -=== DatabaseManager - -*Implements traits:* - -* `Debug` - -Provides access to all database management methods. - -// tag::methods[] -[#_struct_DatabaseManager_all_] -==== all - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn all(&self) -> Result>> ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn all(&self) -> Result>> ----- - --- -==== - -Retrieves all databases present on the TypeDB server - -[caption=""] -.Returns -[source,rust] ----- -Result>> ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.databases().all().await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.databases().all(); ----- - --- -==== - -[#_struct_DatabaseManager_contains_name_impl_Into_String_] -==== contains - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn contains(&self, name: impl Into) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn contains(&self, name: impl Into) -> Result ----- - --- -==== - -Checks if a database with the given name exists - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `name` a| — The database name to be checked a| `impl Into` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.databases().contains(name).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.databases().contains(name); ----- - --- -==== - -[#_struct_DatabaseManager_create_name_impl_Into_String_] -==== create - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn create(&self, name: impl Into) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn create(&self, name: impl Into) -> Result ----- - --- -==== - -Create a database with the given name - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `name` a| — The name of the database to be created a| `impl Into` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.databases().create(name).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.databases().create(name); ----- - --- -==== - -[#_struct_DatabaseManager_get_name_impl_AsRef_str_] -==== get - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn get(&self, name: impl AsRef) -> Result> ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn get(&self, name: impl AsRef) -> Result> ----- - --- -==== - -Retrieve the database with the given name. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `name` a| — The name of the database to retrieve a| `impl AsRef` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result> ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.databases().get(name).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.databases().get(name); ----- - --- -==== - -[#_struct_DatabaseManager_import_from_file_name_impl_Into_String_schema_impl_Into_String_data_file_path_impl_AsRef_Path_] -==== import_from_file - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn import_from_file( - &self, - name: impl Into, - schema: impl Into, - data_file_path: impl AsRef, -) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn import_from_file( - &self, - name: impl Into, - schema: impl Into, - data_file_path: impl AsRef, -) -> Result ----- - --- -==== - -Create a database with the given name based on previously exported another database’s data loaded from a file. This is a blocking operation and may take a significant amount of time depending on the database size. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `name` a| — The name of the database to be created a| `impl Into` -a| `schema` a| — The schema definition query string for the database a| `impl Into` -a| `data_file_path` a| — The exported database file to import the data from a| `impl AsRef` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.databases().import_from_file(name, schema, data_path).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.databases().import_from_file(name, schema, data_path); ----- - --- -==== - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/connection/DriverOptions.adoc b/docs/modules/ROOT/partials/rust/connection/DriverOptions.adoc deleted file mode 100644 index f3264cae57..0000000000 --- a/docs/modules/ROOT/partials/rust/connection/DriverOptions.adoc +++ /dev/null @@ -1,64 +0,0 @@ -[#_struct_DriverOptions] -=== DriverOptions - -*Implements traits:* - -* `Clone` -* `Debug` - -User connection settings for connecting to TypeDB. - -// tag::methods[] -[#_struct_DriverOptions_is_tls_enabled_] -==== is_tls_enabled - -[source,rust] ----- -pub fn is_tls_enabled(&self) -> bool ----- - -Retrieves whether TLS is enabled for the connection. - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[#_struct_DriverOptions_new_is_tls_enabled_bool_tls_root_ca_Option_Path_] -==== new - -[source,rust] ----- -pub fn new(is_tls_enabled: bool, tls_root_ca: Option<&Path>) -> Result ----- - -Creates a credentials with username and password. Specifies the connection must use TLS - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `is_tls_enabled` a| — Specify whether the connection to TypeDB Server must be done over TLS. a| `bool` -a| `tls_root_ca` a| — Path to the CA certificate to use for authenticating server certificates. a| `Option<&Path>` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[source,rust] ----- -DriverOptions::new(true, Some(&path_to_ca)); ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/connection/ReplicaInfo.adoc b/docs/modules/ROOT/partials/rust/connection/ReplicaInfo.adoc deleted file mode 100644 index b0156c5329..0000000000 --- a/docs/modules/ROOT/partials/rust/connection/ReplicaInfo.adoc +++ /dev/null @@ -1,23 +0,0 @@ -[#_struct_ReplicaInfo] -=== ReplicaInfo - -*Implements traits:* - -* `Debug` - -The metadata and state of an individual raft replica of a database. - -[caption=""] -.Fields -// tag::properties[] -[cols=",,"] -[options="header"] -|=== -|Name |Type |Description -a| `is_preferred` a| `bool` a| Whether this is the preferred replica of the raft cluster. If true, Operations which can be run on any replica will prefer to use this replica. -a| `is_primary` a| `bool` a| Whether this is the primary replica of the raft cluster. -a| `server` a| `Address` a| The server hosting this replica -a| `term` a| `i64` a| The raft protocol ‘term’ of this replica. -|=== -// end::properties[] - diff --git a/docs/modules/ROOT/partials/rust/connection/TypeDBDriver.adoc b/docs/modules/ROOT/partials/rust/connection/TypeDBDriver.adoc deleted file mode 100644 index 8ab2c8a93f..0000000000 --- a/docs/modules/ROOT/partials/rust/connection/TypeDBDriver.adoc +++ /dev/null @@ -1,327 +0,0 @@ -[#_struct_TypeDBDriver] -=== TypeDBDriver - -*Implements traits:* - -* `Debug` - -A connection to a TypeDB server which serves as the starting point for all interaction. - -// tag::methods[] -[#_struct_TypeDBDriver_force_close_] -==== force_close - -[source,rust] ----- -pub fn force_close(&self) -> Result ----- - -Closes this connection if it is open. - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[source,rust] ----- -driver.force_close() ----- - -[#_struct_TypeDBDriver_is_open_] -==== is_open - -[source,rust] ----- -pub fn is_open(&self) -> bool ----- - -Checks it this connection is opened. - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[caption=""] -.Code examples -[source,rust] ----- -driver.is_open() ----- - -[#_struct_TypeDBDriver_new_address_impl_AsRef_str_credentials_Credentials_driver_options_DriverOptions] -==== new - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn new( - address: impl AsRef, - credentials: Credentials, - driver_options: DriverOptions, -) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn new( - address: impl AsRef, - credentials: Credentials, - driver_options: DriverOptions, -) -> Result ----- - --- -==== - -Creates a new TypeDB Server connection. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `address` a| — The address (host:port) on which the TypeDB Server is running a| `impl AsRef` -a| `credentials` a| — The Credentials to connect with a| `Credentials` -a| `driver_options` a| — The DriverOptions to connect with a| `DriverOptions` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -TypeDBDriver::new("127.0.0.1:1729").await ----- - --- - -sync:: -+ --- -[source,rust] ----- -TypeDBDriver::new("127.0.0.1:1729") ----- - --- -==== - -[#_struct_TypeDBDriver_new_with_description_address_impl_AsRef_str_credentials_Credentials_driver_options_DriverOptions_driver_lang_impl_AsRef_str_] -==== new_with_description - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn new_with_description( - address: impl AsRef, - credentials: Credentials, - driver_options: DriverOptions, - driver_lang: impl AsRef, -) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn new_with_description( - address: impl AsRef, - credentials: Credentials, - driver_options: DriverOptions, - driver_lang: impl AsRef, -) -> Result ----- - --- -==== - -Creates a new TypeDB Server connection with a description. This method is generally used by TypeDB drivers built on top of the Rust driver. In other cases, use <<#_struct_TypeDBDriver_method_new,`Self::new`>> instead. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `address` a| — The address (host:port) on which the TypeDB Server is running a| `impl AsRef` -a| `credentials` a| — The Credentials to connect with a| `Credentials` -a| `driver_options` a| — The DriverOptions to connect with a| `DriverOptions` -a| `driver_lang` a| — The language of the driver connecting to the server a| `impl AsRef` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -TypeDBDriver::new_with_description("127.0.0.1:1729", "rust").await ----- - --- - -sync:: -+ --- -[source,rust] ----- -TypeDBDriver::new_with_description("127.0.0.1:1729", "rust") ----- - --- -==== - -[#_struct_TypeDBDriver_transaction_] -==== transaction - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn transaction( - &self, - database_name: impl AsRef, - transaction_type: TransactionType, -) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn transaction( - &self, - database_name: impl AsRef, - transaction_type: TransactionType, -) -> Result ----- - --- -==== - -Opens a transaction with default options. See <<#_struct_TypeDBDriver_method_transaction_with_options,`TypeDBDriver::transaction_with_options`>> - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[#_struct_TypeDBDriver_transaction_with_options_database_name_impl_AsRef_str_transaction_type_TransactionType_options_TransactionOptions] -==== transaction_with_options - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn transaction_with_options( - &self, - database_name: impl AsRef, - transaction_type: TransactionType, - options: TransactionOptions, -) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn transaction_with_options( - &self, - database_name: impl AsRef, - transaction_type: TransactionType, - options: TransactionOptions, -) -> Result ----- - --- -==== - -Performs a TypeQL query in this transaction. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `database_name` a| — The name of the database to connect to a| `impl AsRef` -a| `transaction_type` a| — The TransactionType to open the transaction with a| `TransactionType` -a| `options` a| — The TransactionOptions to open the transaction with a| `TransactionOptions` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[source,rust] ----- -transaction.transaction_with_options(database_name, transaction_type, options) ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/connection/User.adoc b/docs/modules/ROOT/partials/rust/connection/User.adoc deleted file mode 100644 index fab0c0c2c0..0000000000 --- a/docs/modules/ROOT/partials/rust/connection/User.adoc +++ /dev/null @@ -1,167 +0,0 @@ -[#_struct_User] -=== User - -*Implements traits:* - -* `Clone` -* `Debug` - -[caption=""] -.Fields -// tag::properties[] -[cols=",,"] -[options="header"] -|=== -|Name |Type |Description -a| `name` a| `String` a| -a| `password` a| `Option` a| -a| `server_connections` a| `HashMap` a| -|=== -// end::properties[] - -// tag::methods[] -[#_struct_User_delete_username] -==== delete - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn delete(self) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn delete(self) -> Result ----- - --- -==== - -Deletes this user - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `username` a| — The name of the user to be deleted a| -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -user.delete().await; -user.delete(username).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -user.delete(); -user.delete(username).await; ----- - --- -==== - -[#_struct_User_update_password_username_password_impl_Into_String_-_Result_] -==== update_password - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn update_password(&self, password: impl Into) -> Result<()> ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn update_password(&self, password: impl Into) -> Result<()> ----- - --- -==== - -Update the user’s password. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `username` a| — The name of the user a| -a| `password` a| — The new password a| `impl Into) -> Result<(` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result<()> ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -user.update_password(username, password).await; -user.update_password(username, password).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -user.update_password(username, password); -user.update_password(username, password).await; ----- - --- -==== - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/connection/UserManager.adoc b/docs/modules/ROOT/partials/rust/connection/UserManager.adoc deleted file mode 100644 index 36ccfd8f9b..0000000000 --- a/docs/modules/ROOT/partials/rust/connection/UserManager.adoc +++ /dev/null @@ -1,216 +0,0 @@ -[#_struct_UserManager] -=== UserManager - -*Implements traits:* - -* `Debug` - -Provides access to all user management methods. - -// tag::methods[] -[#_struct_UserManager_all_] -==== all - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn all(&self) -> Result> ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn all(&self) -> Result> ----- - --- -==== - -Retrieves all users which exist on the TypeDB server. - -[caption=""] -.Returns -[source,rust] ----- -Result> ----- - -[caption=""] -.Code examples -[source,rust] ----- -driver.users.all().await; ----- - -[#_struct_UserManager_contains_username_impl_Into_String_] -==== contains - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn contains(&self, username: impl Into) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn contains(&self, username: impl Into) -> Result ----- - --- -==== - -Checks if a user with the given name exists. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `username` a| — The user name to be checked a| `impl Into` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[source,rust] ----- -driver.users.contains(username).await; ----- - -[#_struct_UserManager_create_username_impl_Into_String_password_impl_Into_String_] -==== create - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn create( - &self, - username: impl Into, - password: impl Into, -) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn create( - &self, - username: impl Into, - password: impl Into, -) -> Result ----- - --- -==== - -Create a user with the given name & password. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `username` a| — The name of the user to be created a| `impl Into` -a| `password` a| — The password of the user to be created a| `impl Into` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[source,rust] ----- -driver.users.create(username, password).await; ----- - -[#_struct_UserManager_get_username_impl_Into_String_] -==== get - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn get(&self, username: impl Into) -> Result> ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn get(&self, username: impl Into) -> Result> ----- - --- -==== - -Retrieve a user with the given name. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `username` a| — The name of the user to retrieve a| `impl Into` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result> ----- - -[caption=""] -.Code examples -[source,rust] ----- -driver.users.get(username).await; ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/data/Attribute.adoc b/docs/modules/ROOT/partials/rust/data/Attribute.adoc deleted file mode 100644 index 66a19df985..0000000000 --- a/docs/modules/ROOT/partials/rust/data/Attribute.adoc +++ /dev/null @@ -1,25 +0,0 @@ -[#_struct_Attribute] -=== Attribute - -*Implements traits:* - -* `Clone` -* `Debug` -* `PartialEq` -* `StructuralPartialEq` - -Attribute is an instance of the attribute type and has a value. This value is fixed and unique for every given instance of the attribute type. Attributes can be uniquely addressed by their type and value. - -[caption=""] -.Fields -// tag::properties[] -[cols=",,"] -[options="header"] -|=== -|Name |Type |Description -a| `iid` a| `IID` a| The unique id of this Attribute (internal use only) -a| `type_` a| `Option` a| The type which this Attribute belongs to -a| `value` a| `Value` a| The (dataful) value of this attribute -|=== -// end::properties[] - diff --git a/docs/modules/ROOT/partials/rust/data/Relation.adoc b/docs/modules/ROOT/partials/rust/data/Relation.adoc deleted file mode 100644 index 1db64f526d..0000000000 --- a/docs/modules/ROOT/partials/rust/data/Relation.adoc +++ /dev/null @@ -1,52 +0,0 @@ -[#_struct_Relation] -=== Relation - -*Implements traits:* - -* `Clone` -* `Debug` -* `Eq` -* `PartialEq` -* `StructuralPartialEq` - -Relation is an instance of a relation type and can be uniquely addressed by a combination of its type, owned attributes and role players. - -[caption=""] -.Fields -// tag::properties[] -[cols=",,"] -[options="header"] -|=== -|Name |Type |Description -a| `iid` a| `IID` a| The unique id of this Relation -a| `type_` a| `Option` a| The type which this Relation belongs to -|=== -// end::properties[] - -// tag::methods[] -[#_struct_Relation_iid_] -==== iid - -[source,rust] ----- -pub fn iid(&self) -> &IID ----- - -Retrieves the unique id of the ``Relation``. - -[caption=""] -.Returns -[source,rust] ----- -&IID ----- - -[caption=""] -.Code examples -[source,rust] ----- -relation.iid(); ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/data/Value.adoc b/docs/modules/ROOT/partials/rust/data/Value.adoc deleted file mode 100644 index c9150bed94..0000000000 --- a/docs/modules/ROOT/partials/rust/data/Value.adoc +++ /dev/null @@ -1,74 +0,0 @@ -[#_enum_Value] -=== Value - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `Boolean(bool)` -a| `Date(NaiveDate)` -a| `Datetime(NaiveDateTime)` -a| `DatetimeTZ(DateTime)` -a| `Decimal(Decimal)` -a| `Double(f64)` -a| `Duration(Duration)` -a| `Integer(i64)` -a| `String(String)` -a| `Struct(Struct, String)` -|=== -// end::enum_constants[] - -// tag::methods[] -[#_enum_Value_get_type_] -==== get_type - -[source,rust] ----- -pub fn get_type(&self) -> ValueType ----- - -Retrieves the ``ValueType`` of this value concept. - -[caption=""] -.Returns -[source,rust] ----- -ValueType ----- - -[caption=""] -.Code examples -[source,rust] ----- -value.get_type(); ----- - -[#_enum_Value_get_type_name_] -==== get_type_name - -[source,rust] ----- -pub fn get_type_name(&self) -> &str ----- - -Retrieves the name of the ``ValueType`` of this value concept. - -[caption=""] -.Returns -[source,rust] ----- -&str ----- - -[caption=""] -.Code examples -[source,rust] ----- -value.get_type_name(); ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/errors/ConceptError.adoc b/docs/modules/ROOT/partials/rust/errors/ConceptError.adoc deleted file mode 100644 index a75f3d3b77..0000000000 --- a/docs/modules/ROOT/partials/rust/errors/ConceptError.adoc +++ /dev/null @@ -1,15 +0,0 @@ -[#_enum_ConceptError] -=== ConceptError - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `UnavailableRowIndex` -a| `UnavailableRowVariable` -|=== -// end::enum_constants[] - diff --git a/docs/modules/ROOT/partials/rust/errors/ConnectionError.adoc b/docs/modules/ROOT/partials/rust/errors/ConnectionError.adoc deleted file mode 100644 index e7a0621b55..0000000000 --- a/docs/modules/ROOT/partials/rust/errors/ConnectionError.adoc +++ /dev/null @@ -1,46 +0,0 @@ -[#_enum_ConnectionError] -=== ConnectionError - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `AddressTranslationMismatch` -a| `BrokenPipe` -a| `ClusterAllNodesFailed` -a| `ClusterReplicaNotPrimary` -a| `ConnectionFailed` -a| `DatabaseExportChannelIsClosed` -a| `DatabaseExportStreamNoResponse` -a| `DatabaseImportChannelIsClosed` -a| `DatabaseImportStreamUnexpectedResponse` -a| `DatabaseNotFound` -a| `EncryptionSettingsMismatch` -a| `InvalidResponseField` -a| `ListsNotImplemented` -a| `MissingPort` -a| `MissingResponseField` -a| `QueryStreamNoResponse` -a| `RPCMethodUnavailable` -a| `SSLCertificateNotValidated` -a| `ServerConnectionFailed` -a| `ServerConnectionFailedStatusError` -a| `ServerConnectionFailedWithError` -a| `ServerConnectionIsClosed` -a| `TokenCredentialInvalid` -a| `TransactionIsClosed` -a| `TransactionIsClosedWithErrors` -a| `UnexpectedConnectionClose` -a| `UnexpectedKind` -a| `UnexpectedQueryType` -a| `UnexpectedResponse` -a| `UnknownRequestId` -a| `ValueStructNotImplemented` -a| `ValueTimeZoneNameNotRecognised` -a| `ValueTimeZoneOffsetNotRecognised` -|=== -// end::enum_constants[] - diff --git a/docs/modules/ROOT/partials/rust/errors/DurationParseError.adoc b/docs/modules/ROOT/partials/rust/errors/DurationParseError.adoc deleted file mode 100644 index 45f68b0056..0000000000 --- a/docs/modules/ROOT/partials/rust/errors/DurationParseError.adoc +++ /dev/null @@ -1,7 +0,0 @@ -[#_struct_DurationParseError] -=== DurationParseError - -*Implements traits:* - -* `Debug` - diff --git a/docs/modules/ROOT/partials/rust/errors/Error.adoc b/docs/modules/ROOT/partials/rust/errors/Error.adoc deleted file mode 100644 index 482eea46fb..0000000000 --- a/docs/modules/ROOT/partials/rust/errors/Error.adoc +++ /dev/null @@ -1,21 +0,0 @@ -[#_enum_Error] -=== Error - -Represents errors encountered during operation. - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `Concept(ConceptError)` -a| `Connection(ConnectionError)` -a| `Internal(InternalError)` -a| `Migration(MigrationError)` -a| `Other(String)` -a| `Server(ServerError)` -|=== -// end::enum_constants[] - diff --git a/docs/modules/ROOT/partials/rust/errors/InternalError.adoc b/docs/modules/ROOT/partials/rust/errors/InternalError.adoc deleted file mode 100644 index 78f7c480b5..0000000000 --- a/docs/modules/ROOT/partials/rust/errors/InternalError.adoc +++ /dev/null @@ -1,19 +0,0 @@ -[#_enum_InternalError] -=== InternalError - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `EnumOutOfBounds` -a| `RecvError` -a| `SendError` -a| `UnexpectedRequestType` -a| `UnexpectedResponseType` -a| `UnknownServer` -|=== -// end::enum_constants[] - diff --git a/docs/modules/ROOT/partials/rust/errors/MigrationError.adoc b/docs/modules/ROOT/partials/rust/errors/MigrationError.adoc deleted file mode 100644 index 8eda9e95ce..0000000000 --- a/docs/modules/ROOT/partials/rust/errors/MigrationError.adoc +++ /dev/null @@ -1,19 +0,0 @@ -[#_enum_MigrationError] -=== MigrationError - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `CannotCreateExportFile` -a| `CannotDecodeImportedConcept` -a| `CannotDecodeImportedConceptLength` -a| `CannotEncodeExportedConcept` -a| `CannotExportToTheSameFile` -a| `CannotOpenImportFile` -|=== -// end::enum_constants[] - diff --git a/docs/modules/ROOT/partials/rust/errors/ServerError.adoc b/docs/modules/ROOT/partials/rust/errors/ServerError.adoc deleted file mode 100644 index 4d0d197a2d..0000000000 --- a/docs/modules/ROOT/partials/rust/errors/ServerError.adoc +++ /dev/null @@ -1,13 +0,0 @@ -[#_struct_ServerError] -=== ServerError - -*Implements traits:* - -* `Clone` -* `Debug` -* `Display` -* `Eq` -* `From` -* `PartialEq` -* `StructuralPartialEq` - diff --git a/docs/modules/ROOT/partials/rust/schema/AttributeType.adoc b/docs/modules/ROOT/partials/rust/schema/AttributeType.adoc deleted file mode 100644 index daf454d060..0000000000 --- a/docs/modules/ROOT/partials/rust/schema/AttributeType.adoc +++ /dev/null @@ -1,82 +0,0 @@ -[#_struct_AttributeType] -=== AttributeType - -*Implements traits:* - -* `Clone` -* `Debug` -* `Display` -* `PartialEq` -* `StructuralPartialEq` - -Attribute types represent properties that other types can own. - -Attribute types have a value type. This value type is fixed and unique for every given instance of the attribute type. - -Other types can own an attribute type. That means that instances of these other types can own an instance of this attribute type. This usually means that an object in our domain has a property with the matching value. - -Multiple types can own the same attribute type, and different instances of the same type or different types can share ownership of the same attribute instance. - -[caption=""] -.Fields -// tag::properties[] -[cols=",,"] -[options="header"] -|=== -|Name |Type |Description -a| `label` a| `String` a| -a| `value_type` a| `Option` a| -|=== -// end::properties[] - -// tag::methods[] -[#_struct_AttributeType_label_] -==== label - -[source,rust] ----- -pub fn label(&self) -> &str ----- - -Retrieves the unique label of the ``AttributeType``. - -[caption=""] -.Returns -[source,rust] ----- -&str ----- - -[caption=""] -.Code examples -[source,rust] ----- -attribute_type.label() ----- - -[#_struct_AttributeType_value_type_] -==== value_type - -[source,rust] ----- -pub fn value_type(&self) -> Option<&ValueType> ----- - -Retrieves the ``ValueType`` of the ``AttributeType``. - -[caption=""] -.Returns -[source,rust] ----- -Option<&ValueType> ----- - -[caption=""] -.Code examples -[source,rust] ----- -attribute_type.value_type() ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/schema/EntityType.adoc b/docs/modules/ROOT/partials/rust/schema/EntityType.adoc deleted file mode 100644 index 37c108059b..0000000000 --- a/docs/modules/ROOT/partials/rust/schema/EntityType.adoc +++ /dev/null @@ -1,52 +0,0 @@ -[#_struct_EntityType] -=== EntityType - -*Implements traits:* - -* `Clone` -* `Debug` -* `Display` -* `Eq` -* `PartialEq` -* `StructuralPartialEq` - -Entity types represent the classification of independent objects in the data model of the business domain. - -[caption=""] -.Fields -// tag::properties[] -[cols=",,"] -[options="header"] -|=== -|Name |Type |Description -a| `label` a| `String` a| -|=== -// end::properties[] - -// tag::methods[] -[#_struct_EntityType_label_] -==== label - -[source,rust] ----- -pub fn label(&self) -> &str ----- - -Retrieves the unique label of the ``EntityType``. - -[caption=""] -.Returns -[source,rust] ----- -&str ----- - -[caption=""] -.Code examples -[source,rust] ----- -entity_type.label() ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/schema/RelationType.adoc b/docs/modules/ROOT/partials/rust/schema/RelationType.adoc deleted file mode 100644 index 723fbe5140..0000000000 --- a/docs/modules/ROOT/partials/rust/schema/RelationType.adoc +++ /dev/null @@ -1,56 +0,0 @@ -[#_struct_RelationType] -=== RelationType - -*Implements traits:* - -* `Clone` -* `Debug` -* `Display` -* `Eq` -* `PartialEq` -* `StructuralPartialEq` - -Relation types (or subtypes of the relation root type) represent relationships between types. Relation types have roles. - -Other types can play roles in relations if it’s mentioned in their definition. - -A relation type must specify at least one role. - -[caption=""] -.Fields -// tag::properties[] -[cols=",,"] -[options="header"] -|=== -|Name |Type |Description -a| `label` a| `String` a| -|=== -// end::properties[] - -// tag::methods[] -[#_struct_RelationType_label_] -==== label - -[source,rust] ----- -pub fn label(&self) -> &str ----- - -Retrieves the unique label of the ``RelationType``. - -[caption=""] -.Returns -[source,rust] ----- -&str ----- - -[caption=""] -.Code examples -[source,rust] ----- -relation_type.label() ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/schema/RoleType.adoc b/docs/modules/ROOT/partials/rust/schema/RoleType.adoc deleted file mode 100644 index 12a7136a3a..0000000000 --- a/docs/modules/ROOT/partials/rust/schema/RoleType.adoc +++ /dev/null @@ -1,54 +0,0 @@ -[#_struct_RoleType] -=== RoleType - -*Implements traits:* - -* `Clone` -* `Debug` -* `Display` -* `Eq` -* `PartialEq` -* `StructuralPartialEq` - -Roles are special internal types used by relations. We can not create an instance of a role in a database. But we can set an instance of another type (role player) to play a role in a particular instance of a relation type. - -Roles allow a schema to enforce logical constraints on types of role players. - -[caption=""] -.Fields -// tag::properties[] -[cols=",,"] -[options="header"] -|=== -|Name |Type |Description -a| `label` a| `String` a| -|=== -// end::properties[] - -// tag::methods[] -[#_struct_RoleType_label_] -==== label - -[source,rust] ----- -pub fn label(&self) -> &str ----- - -Retrieves the unique label of the ``RoleType``. - -[caption=""] -.Returns -[source,rust] ----- -&str ----- - -[caption=""] -.Code examples -[source,rust] ----- -role_type.label() ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/schema/ValueType.adoc b/docs/modules/ROOT/partials/rust/schema/ValueType.adoc deleted file mode 100644 index 1f624031e9..0000000000 --- a/docs/modules/ROOT/partials/rust/schema/ValueType.adoc +++ /dev/null @@ -1,25 +0,0 @@ -[#_enum_ValueType] -=== ValueType - -Represents the type of primitive value is held by a Value or Attribute. - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `Boolean` -a| `Date` -a| `Datetime` -a| `DatetimeTZ` -a| `Decimal` -a| `Double` -a| `Duration` -a| `Integer` -a| `String` -a| `Struct(String)` -|=== -// end::enum_constants[] - diff --git a/docs/modules/ROOT/partials/rust/transaction/QueryOptions.adoc b/docs/modules/ROOT/partials/rust/transaction/QueryOptions.adoc deleted file mode 100644 index 1d8065991f..0000000000 --- a/docs/modules/ROOT/partials/rust/transaction/QueryOptions.adoc +++ /dev/null @@ -1,61 +0,0 @@ -[#_struct_QueryOptions] -=== QueryOptions - -*Implements traits:* - -* `Clone` -* `Copy` -* `Debug` -* `Default` - -TypeDB query options. ``QueryOptions`` object can be used to override the default server behaviour for executed queries. - -[caption=""] -.Fields -// tag::properties[] -[cols=",,"] -[options="header"] -|=== -|Name |Type |Description -a| `include_instance_types` a| `Option` a| If set, specifies if types should be included in instance structs returned in ConceptRow answers. This option allows reducing the amount of unnecessary data transmitted. -a| `prefetch_size` a| `Option` a| If set, specifies the number of extra query responses sent before the client side has to re-request more responses. Increasing this may increase performance for queries with a huge number of answers, as it can reduce the number of network round-trips at the cost of more resources on the server side. Minimal value: 1. -|=== -// end::properties[] - -// tag::methods[] -[#_struct_QueryOptions_include_instance_types_] -==== include_instance_types - -[source,rust] ----- -pub fn include_instance_types(self, include_instance_types: bool) -> Self ----- - -If set, specifies if types should be included in instance structs returned in ConceptRow answers. This option allows reducing the amount of unnecessary data transmitted. - -[caption=""] -.Returns -[source,rust] ----- -Self ----- - -[#_struct_QueryOptions_prefetch_size_] -==== prefetch_size - -[source,rust] ----- -pub fn prefetch_size(self, prefetch_size: u64) -> Self ----- - -If set, specifies the number of extra query responses sent before the client side has to re-request more responses. Increasing this may increase performance for queries with a huge number of answers, as it can reduce the number of network round-trips at the cost of more resources on the server side. - -[caption=""] -.Returns -[source,rust] ----- -Self ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/transaction/Transaction.adoc b/docs/modules/ROOT/partials/rust/transaction/Transaction.adoc deleted file mode 100644 index 7482e4b7fa..0000000000 --- a/docs/modules/ROOT/partials/rust/transaction/Transaction.adoc +++ /dev/null @@ -1,255 +0,0 @@ -[#_struct_Transaction] -=== Transaction - -*Implements traits:* - -* `Debug` - -A transaction with a TypeDB database. - -// tag::methods[] -[#_struct_Transaction_commit_] -==== commit - -[source,rust] ----- -pub fn commit(self) -> impl Promise<'static, Result> ----- - -Commits the changes made via this transaction to the TypeDB database. Whether or not the transaction is commited successfully, it gets closed after the commit call. - -[caption=""] -.Returns -[source,rust] ----- -impl Promise<'static, Result> ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -transaction.commit().await ----- - --- - -sync:: -+ --- -[source,rust] ----- -transaction.commit() ----- - --- -==== - -[#_struct_Transaction_force_close_] -==== force_close - -[source,rust] ----- -pub fn force_close(&self) ----- - -Closes the transaction. - -[caption=""] -.Returns -[source,rust] ----- -null ----- - -[caption=""] -.Code examples -[source,rust] ----- -transaction.force_close() ----- - -[#_struct_Transaction_is_open_] -==== is_open - -[source,rust] ----- -pub fn is_open(&self) -> bool ----- - -Closes the transaction. - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[caption=""] -.Code examples -[source,rust] ----- -transaction.close() ----- - -[#_struct_Transaction_on_close_function] -==== on_close - -[source,rust] ----- -pub fn on_close( - &self, - callback: impl FnOnce(Option) + Send + Sync + 'static, -) ----- - -Registers a callback function which will be executed when this transaction is closed. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `function` a| — The callback function. a| -|=== - -[caption=""] -.Returns -[source,rust] ----- -null ----- - -[caption=""] -.Code examples -[source,rust] ----- -transaction.on_close(function) ----- - -[#_struct_Transaction_query_] -==== query - -[source,rust] ----- -pub fn query( - &self, - query: impl AsRef, -) -> impl Promise<'static, Result> ----- - -Performs a TypeQL query with default options. See <<#_struct_Transaction_method_query_with_options,`Transaction::query_with_options`>> - -[caption=""] -.Returns -[source,rust] ----- -impl Promise<'static, Result> ----- - -[#_struct_Transaction_query_with_options_query_impl_AsRef_str_options_QueryOptions] -==== query_with_options - -[source,rust] ----- -pub fn query_with_options( - &self, - query: impl AsRef, - options: QueryOptions, -) -> impl Promise<'static, Result> ----- - -Performs a TypeQL query in this transaction. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `query` a| — The TypeQL query to be executed a| `impl AsRef` -a| `options` a| — The QueryOptions to execute the query with a| `QueryOptions` -|=== - -[caption=""] -.Returns -[source,rust] ----- -impl Promise<'static, Result> ----- - -[caption=""] -.Code examples -[source,rust] ----- -transaction.query_with_options(query, options) ----- - -[#_struct_Transaction_rollback_] -==== rollback - -[source,rust] ----- -pub fn rollback(&self) -> impl Promise<'_, Result> ----- - -Rolls back the uncommitted changes made via this transaction. - -[caption=""] -.Returns -[source,rust] ----- -impl Promise<'_, Result> ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -transaction.rollback().await ----- - --- - -sync:: -+ --- -[source,rust] ----- -transaction.rollback() ----- - --- -==== - -[#_struct_Transaction_type_] -==== type_ - -[source,rust] ----- -pub fn type_(&self) -> TransactionType ----- - -Retrieves the transaction’s type (READ or WRITE). - -[caption=""] -.Returns -[source,rust] ----- -TransactionType ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/transaction/TransactionOptions.adoc b/docs/modules/ROOT/partials/rust/transaction/TransactionOptions.adoc deleted file mode 100644 index 171ba38409..0000000000 --- a/docs/modules/ROOT/partials/rust/transaction/TransactionOptions.adoc +++ /dev/null @@ -1,61 +0,0 @@ -[#_struct_TransactionOptions] -=== TransactionOptions - -*Implements traits:* - -* `Clone` -* `Copy` -* `Debug` -* `Default` - -TypeDB transaction options. ``TransactionOptions`` object can be used to override the default server behaviour for opened transactions. - -[caption=""] -.Fields -// tag::properties[] -[cols=",,"] -[options="header"] -|=== -|Name |Type |Description -a| `schema_lock_acquire_timeout` a| `Option` a| If set, specifies how long the driver should wait if opening a transaction is blocked by an exclusive schema write lock. -a| `transaction_timeout` a| `Option` a| If set, specifies a timeout for killing transactions automatically, preventing memory leaks in unclosed transactions. -|=== -// end::properties[] - -// tag::methods[] -[#_struct_TransactionOptions_schema_lock_acquire_timeout_] -==== schema_lock_acquire_timeout - -[source,rust] ----- -pub fn schema_lock_acquire_timeout(self, timeout: Duration) -> Self ----- - -If set, specifies how long the driver should wait if opening a transaction is blocked by an exclusive schema write lock. - -[caption=""] -.Returns -[source,rust] ----- -Self ----- - -[#_struct_TransactionOptions_transaction_timeout_] -==== transaction_timeout - -[source,rust] ----- -pub fn transaction_timeout(self, timeout: Duration) -> Self ----- - -If set, specifies a timeout for killing transactions automatically, preventing memory leaks in unclosed transactions. - -[caption=""] -.Returns -[source,rust] ----- -Self ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/transaction/TransactionType.adoc b/docs/modules/ROOT/partials/rust/transaction/TransactionType.adoc deleted file mode 100644 index a712ae5e4e..0000000000 --- a/docs/modules/ROOT/partials/rust/transaction/TransactionType.adoc +++ /dev/null @@ -1,18 +0,0 @@ -[#_enum_TransactionType] -=== TransactionType - -This enum is used to specify the type of transaction. - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `Read = 0` -a| `Schema = 2` -a| `Write = 1` -|=== -// end::enum_constants[] - diff --git a/docs/modules/ROOT/partials/rust/value/Decimal.adoc b/docs/modules/ROOT/partials/rust/value/Decimal.adoc deleted file mode 100644 index 5313cf98bc..0000000000 --- a/docs/modules/ROOT/partials/rust/value/Decimal.adoc +++ /dev/null @@ -1,59 +0,0 @@ -[#_struct_Decimal] -=== Decimal - -*Implements traits:* - -* `Add` -* `Clone` -* `Copy` -* `Debug` -* `Default` -* `Display` -* `Eq` -* `Hash` -* `Neg` -* `Ord` -* `PartialEq` -* `PartialOrd` -* `StructuralPartialEq` -* `Sub` - -A fixed-point decimal number. Holds exactly 19 digits after the decimal point and a 64-bit value before the decimal point. - -// tag::methods[] -[#_struct_Decimal_fractional_part_] -==== fractional_part - -[source,rust] ----- -pub fn fractional_part(&self) -> u64 ----- - -Get the fractional part of the decimal, in multiples of 10^-19 (Decimal::FRACTIONAL_PART_DENOMINATOR) This means, the smallest decimal representable is 10^-19, and up to 19 decimal places are supported. - -[caption=""] -.Returns -[source,rust] ----- -u64 ----- - -[#_struct_Decimal_integer_part_] -==== integer_part - -[source,rust] ----- -pub fn integer_part(&self) -> i64 ----- - -Get the integer part of the decimal as normal signed 64 bit number - -[caption=""] -.Returns -[source,rust] ----- -i64 ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/value/Duration.adoc b/docs/modules/ROOT/partials/rust/value/Duration.adoc deleted file mode 100644 index 3d9b14892f..0000000000 --- a/docs/modules/ROOT/partials/rust/value/Duration.adoc +++ /dev/null @@ -1,31 +0,0 @@ -[#_struct_Duration] -=== Duration - -*Implements traits:* - -* `Clone` -* `Copy` -* `Debug` -* `Display` -* `Eq` -* `FromStr` -* `Hash` -* `PartialEq` -* `StructuralPartialEq` -* `TryFrom` - -A relative duration, which contains months, days, and nanoseconds. Can be used for calendar-relative durations (eg 7 days forward), or for absolute durations using the nanosecond component When used as an absolute duration, convertible to chrono::Duration - -[caption=""] -.Fields -// tag::properties[] -[cols=",,"] -[options="header"] -|=== -|Name |Type |Description -a| `days` a| `u32` a| -a| `months` a| `u32` a| -a| `nanos` a| `u64` a| -|=== -// end::properties[] - diff --git a/docs/modules/ROOT/partials/rust/value/Offset.adoc b/docs/modules/ROOT/partials/rust/value/Offset.adoc deleted file mode 100644 index 8441725600..0000000000 --- a/docs/modules/ROOT/partials/rust/value/Offset.adoc +++ /dev/null @@ -1,17 +0,0 @@ -[#_enum_Offset] -=== Offset - -Offset for datetime-tz. Can be retrieved from an IANA Tz or a FixedOffset. - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `Fixed(FixedOffset)` -a| `IANA(::Offset)` -|=== -// end::enum_constants[] - diff --git a/docs/modules/ROOT/partials/rust/value/Struct.adoc b/docs/modules/ROOT/partials/rust/value/Struct.adoc deleted file mode 100644 index 574abf444c..0000000000 --- a/docs/modules/ROOT/partials/rust/value/Struct.adoc +++ /dev/null @@ -1,11 +0,0 @@ -[#_struct_Struct] -=== Struct - -*Implements traits:* - -* `Clone` -* `Debug` -* `Display` -* `PartialEq` -* `StructuralPartialEq` - diff --git a/docs/modules/ROOT/partials/rust/value/TimeZone.adoc b/docs/modules/ROOT/partials/rust/value/TimeZone.adoc deleted file mode 100644 index e5020c10c1..0000000000 --- a/docs/modules/ROOT/partials/rust/value/TimeZone.adoc +++ /dev/null @@ -1,17 +0,0 @@ -[#_enum_TimeZone] -=== TimeZone - -TimeZone for datetime-tz. Can be represented as an IANA Tz or as a FixedOffset. - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `Fixed(FixedOffset)` -a| `IANA(Tz)` -|=== -// end::enum_constants[] - diff --git a/java/README.md b/java/README.md index 291519ddbf..77d62b2713 100644 --- a/java/README.md +++ b/java/README.md @@ -89,7 +89,7 @@ import java.util.stream.Collectors; public class TypeDBExample { public void example() { // Open a driver connection. Try-with-resources can be used for automatic driver connection management - try (Driver driver = TypeDB.driver(TypeDB.DEFAULT_ADDRESS, new Credentials("admin", "password"), new DriverOptions(false, null))) { + try (Driver driver = TypeDB.driver(TypeDB.DEFAULT_ADDRESS, new Credentials("admin", "password"), new DriverOptions())) { // Create a database driver.databases().create("typedb"); Database database = driver.databases().get("typedb"); diff --git a/java/TypeDB.java b/java/TypeDB.java index 93e530c2bc..84c6034528 100644 --- a/java/TypeDB.java +++ b/java/TypeDB.java @@ -25,8 +25,11 @@ import com.typedb.driver.common.exception.TypeDBDriverException; import com.typedb.driver.connection.DriverImpl; +import java.util.Map; +import java.util.Set; + public class TypeDB { - public static final String DEFAULT_ADDRESS = "localhost:1729"; + public static final String DEFAULT_ADDRESS = "127.0.0.1:1729"; /** * Open a TypeDB Driver to a TypeDB server available at the provided address. @@ -38,9 +41,41 @@ public class TypeDB { * * @param address The address of the TypeDB server * @param credentials The credentials to connect with - * @param driverOptions The connection settings to connect with + * @param driverOptions The driver connection options to connect with */ public static Driver driver(String address, Credentials credentials, DriverOptions driverOptions) throws TypeDBDriverException { return new DriverImpl(address, credentials, driverOptions); } + + /** + * Open a TypeDB Driver to a TypeDB cluster available at the provided addresses. + * + *

Examples

+ *
+     * TypeDB.driver(address);
+     * 
+ * + * @param addresses The addresses of TypeDB cluster replicas for connection + * @param credentials The credentials to connect with + * @param driverOptions The driver connection options to connect with + */ + public static Driver driver(Set addresses, Credentials credentials, DriverOptions driverOptions) throws TypeDBDriverException { + return new DriverImpl(addresses, credentials, driverOptions); + } + + /** + * Open a TypeDB Driver to a TypeDB cluster, using the provided address translation. + * + *

Examples

+ *
+     * TypeDB.driver(addresses);
+     * 
+ * + * @param addressTranslation The translation of public TypeDB cluster replica addresses (keys) to server-side private addresses (values) + * @param credentials The credentials to connect with + * @param driverOptions The driver connection options to connect with + */ + public static Driver driver(Map addressTranslation, Credentials credentials, DriverOptions driverOptions) throws TypeDBDriverException { + return new DriverImpl(addressTranslation, credentials, driverOptions); + } } diff --git a/java/TypeDBExample.java b/java/TypeDBExample.java index e127e429c7..47554be234 100644 --- a/java/TypeDBExample.java +++ b/java/TypeDBExample.java @@ -31,7 +31,7 @@ public class TypeDBExample { public void example() { // Open a driver connection. Try-with-resources can be used for automatic driver connection management - try (Driver driver = TypeDB.driver(TypeDB.DEFAULT_ADDRESS, new Credentials("admin", "password"), new DriverOptions(false, null))) { + try (Driver driver = TypeDB.driver(TypeDB.DEFAULT_ADDRESS, new Credentials("admin", "password"), new DriverOptions())) { // Create a database driver.databases().create("typedb"); Database database = driver.databases().get("typedb"); diff --git a/java/api/ConsistencyLevel.java b/java/api/ConsistencyLevel.java new file mode 100644 index 0000000000..371f98dbbc --- /dev/null +++ b/java/api/ConsistencyLevel.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.typedb.driver.api; + +import com.typedb.driver.common.exception.TypeDBDriverException; + +import static com.typedb.driver.common.exception.ErrorMessage.Internal.UNEXPECTED_NATIVE_VALUE; +import static com.typedb.driver.jni.typedb_driver.consistency_level_eventual; +import static com.typedb.driver.jni.typedb_driver.consistency_level_replica_dependant; +import static com.typedb.driver.jni.typedb_driver.consistency_level_strong; + +/** + * Consistency levels of operations against a distributed server. All driver methods have default + * recommended values, however, readonly operations can be configured in order to potentially + * speed up the execution (introducing risks of stale data) or test a specific replica. + * This setting does not affect clusters with a single node. + */ +public abstract class ConsistencyLevel { + public abstract com.typedb.driver.jni.ConsistencyLevel nativeValue(); + + public static ConsistencyLevel of(com.typedb.driver.jni.ConsistencyLevel nativeValue) { + if (nativeValue.getTag() == com.typedb.driver.jni.ConsistencyLevelTag.Strong) return new Strong(); + else if (nativeValue.getTag() == com.typedb.driver.jni.ConsistencyLevelTag.Eventual) return new Eventual(); + else if (nativeValue.getTag() == com.typedb.driver.jni.ConsistencyLevelTag.ReplicaDependant) { + return new ReplicaDependant(nativeValue.getAddress()); + } + throw new TypeDBDriverException(UNEXPECTED_NATIVE_VALUE); + } + + public static com.typedb.driver.jni.ConsistencyLevel nativeValue(ConsistencyLevel consistencyLevel) { + if (consistencyLevel == null) { + return null; + } else { + return consistencyLevel.nativeValue(); + } + } + + /** + * Strongest consistency, always up-to-date due to the guarantee of the primary replica usage. + * May require more time for operation execution. + */ + public static final class Strong extends ConsistencyLevel { + Strong() { + } + + @Override + public com.typedb.driver.jni.ConsistencyLevel nativeValue() { + return newNative(); + } + + private static com.typedb.driver.jni.ConsistencyLevel newNative() { + return consistency_level_strong(); + } + + @Override + public String toString() { + return "Strong"; + } + } + + /** + * Allow stale reads from any replica. May not reflect latest writes. The execution may be + * eventually faster compared to other consistency levels. + */ + public static final class Eventual extends ConsistencyLevel { + Eventual() { + } + + @Override + public com.typedb.driver.jni.ConsistencyLevel nativeValue() { + return newNative(); + } + + private static com.typedb.driver.jni.ConsistencyLevel newNative() { + return consistency_level_eventual(); + } + + @Override + public String toString() { + return "Eventual"; + } + } + + /** + * The operation is executed against the provided replica address only. Its guarantees depend + * on the replica selected. + */ + public static final class ReplicaDependant extends ConsistencyLevel { + private final String address; + + public ReplicaDependant(String address) { + this.address = address; + } + + @Override + public com.typedb.driver.jni.ConsistencyLevel nativeValue() { + return newNative(this.address); + } + + private static com.typedb.driver.jni.ConsistencyLevel newNative(String address) { + return consistency_level_replica_dependant(address); + } + + /** + * Retrieves the address of the replica this consistency level depends on. + */ + public String getAddress() { + return address; + } + + @Override + public String toString() { + return "ReplicaDependant(" + address + ")"; + } + } +} \ No newline at end of file diff --git a/java/api/Driver.java b/java/api/Driver.java index acf156ff68..c51d468207 100644 --- a/java/api/Driver.java +++ b/java/api/Driver.java @@ -20,10 +20,15 @@ package com.typedb.driver.api; import com.typedb.driver.api.database.DatabaseManager; +import com.typedb.driver.api.server.ServerReplica; +import com.typedb.driver.api.server.ServerVersion; import com.typedb.driver.api.user.UserManager; import com.typedb.driver.common.exception.TypeDBDriverException; import javax.annotation.CheckReturnValue; +import java.util.Map; +import java.util.Optional; +import java.util.Set; public interface Driver extends AutoCloseable { String LANGUAGE = "java"; @@ -39,12 +44,55 @@ public interface Driver extends AutoCloseable { @CheckReturnValue boolean isOpen(); + /** + * Retrieves the server's version, using default strong consistency. + * See {@link #serverVersion(ConsistencyLevel)} for more details and options. + * + *

Examples

+ *
+     * driver.serverVersion();
+     * 
+ */ + @CheckReturnValue + default ServerVersion serverVersion() { + return serverVersion(null); + } + + /** + * Retrieves the server's version. + * + *

Examples

+ *
+     * driver.serverVersion();
+     * 
+ * + * @param consistencyLevel The consistency level to use for the operation + */ + @CheckReturnValue + ServerVersion serverVersion(ConsistencyLevel consistencyLevel); + /** * The DatabaseManager for this connection, providing access to database management methods. + * + *

Examples

+ *
+     * driver.databases();
+     * 
*/ @CheckReturnValue DatabaseManager databases(); + /** + * The UserManager for this connection, providing access to user management methods. + * + *

Examples

+ *
+     * driver.users();
+     * 
+ */ + @CheckReturnValue + UserManager users(); + /** * Opens a communication tunnel (transaction) to the given database on the running TypeDB server. * @@ -75,23 +123,76 @@ public interface Driver extends AutoCloseable { Transaction transaction(String database, Transaction.Type type, TransactionOptions options); /** - * Closes the driver. Before instantiating a new driver, the driver that’s currently open should first be closed. + * Set of Replica instances for this driver connection. * *

Examples

*
-     * driver.close()
+     * driver.replicas();
      * 
*/ - void close(); + @CheckReturnValue + Set replicas(); /** - * The UserManager instance for this connection, providing access to user management methods. + * Returns the primary replica for this driver connection. * *

Examples

*
-     * driver.users();
+     * driver.primaryReplica();
      * 
*/ @CheckReturnValue - UserManager users(); + Optional primaryReplica(); + + /** + * Registers a new replica in the cluster the driver is currently connected to. The registered + * replica will become available eventually, depending on the behavior of the whole cluster. + * To register a replica, its clustering address should be passed, not the connection address. + * + *

Examples

+ *
+     * driver.registerReplica(2, "127.0.0.1:11729");
+     * 
+ * + * @param replicaID The numeric identifier of the new replica + * @param address The address(es) of the TypeDB replica as a string + */ + void registerReplica(long replicaID, String address); + + /** + * Deregisters a replica from the cluster the driver is currently connected to. This replica + * will no longer play a raft role in this cluster. + * + *

Examples

+ *
+     * driver.deregisterReplica(2);
+     * 
+ * + * @param replicaID The numeric identifier of the deregistered replica + */ + void deregisterReplica(long replicaID); + + /** + * Updates address translation of the driver. This lets you actualize new translation + * information without recreating the driver from scratch. Useful after registering new + * replicas requiring address translation. + * + *

Examples

+ *
+     * driver.updateAddressTranslation(2);
+     * 
+ * + * @param addressTranslation The translation of public TypeDB cluster replica addresses (keys) to server-side private addresses (values) + */ + void updateAddressTranslation(Map addressTranslation); + + /** + * Closes the driver. Before instantiating a new driver, the driver that’s currently open should first be closed. + * + *

Examples

+ *
+     * driver.close();
+     * 
+ */ + void close(); } diff --git a/java/api/DriverOptions.java b/java/api/DriverOptions.java index f14e42ff9c..6091950b37 100644 --- a/java/api/DriverOptions.java +++ b/java/api/DriverOptions.java @@ -20,34 +20,212 @@ package com.typedb.driver.api; import com.typedb.driver.common.NativeObject; -import com.typedb.driver.common.exception.TypeDBDriverException; +import com.typedb.driver.common.Validator; -import javax.annotation.Nullable; +import javax.annotation.CheckReturnValue; +import java.util.Optional; +import static com.typedb.driver.jni.typedb_driver.driver_options_get_is_tls_enabled; +import static com.typedb.driver.jni.typedb_driver.driver_options_get_primary_failover_retries; +import static com.typedb.driver.jni.typedb_driver.driver_options_get_replica_discovery_attempts; +import static com.typedb.driver.jni.typedb_driver.driver_options_get_tls_root_ca_path; +import static com.typedb.driver.jni.typedb_driver.driver_options_get_use_replication; +import static com.typedb.driver.jni.typedb_driver.driver_options_has_replica_discovery_attempts; +import static com.typedb.driver.jni.typedb_driver.driver_options_has_tls_root_ca_path; import static com.typedb.driver.jni.typedb_driver.driver_options_new; +import static com.typedb.driver.jni.typedb_driver.driver_options_set_is_tls_enabled; +import static com.typedb.driver.jni.typedb_driver.driver_options_set_primary_failover_retries; +import static com.typedb.driver.jni.typedb_driver.driver_options_set_replica_discovery_attempts; +import static com.typedb.driver.jni.typedb_driver.driver_options_set_tls_root_ca_path; +import static com.typedb.driver.jni.typedb_driver.driver_options_set_use_replication; /** - * User connection settings (TLS encryption, etc.) for connecting to TypeDB Server. - * - *

Examples

- *
- * DriverOptions driverOptions = new DriverOptions(true, Path.of("path/to/ca-certificate.pem"));
- * 
+ * TypeDB driver options. DriverOptions are used to specify the driver's connection behavior. */ public class DriverOptions extends NativeObject { /** - * @param isTlsEnabled Specify whether the connection to TypeDB Server must be done over TLS. - * @param tlsRootCAPath Path to the CA certificate to use for authenticating server certificates. + * Produces a new DriverOptions object. + * + *

Examples

+ *
+     * DriverOptions options = DriverOptions();
+     * 
+ */ + public DriverOptions() { + super(driver_options_new()); + } + + /** + * Returns the value set for the TLS flag in this DriverOptions object. + * Specifies whether the connection to TypeDB must be done over TLS. + * + *

Examples

+ *
+     * options.isTlsEnabled();
+     * 
+ */ + @CheckReturnValue + public Boolean isTlsEnabled() { + return driver_options_get_is_tls_enabled(nativeObject); + } + + /** + * Explicitly sets whether the connection to TypeDB must be done over TLS. + * + *

Examples

+ *
+     * options.isTlsEnabled(true);
+     * 
+ * + * @param isTlsEnabled Whether the connection to TypeDB must be done over TLS. + */ + public DriverOptions isTlsEnabled(boolean isTlsEnabled) { + driver_options_set_is_tls_enabled(nativeObject, isTlsEnabled); + return this; + } + + /** + * Returns the TLS root CA set in this DriverOptions object. + * Specifies the root CA used in the TLS config for server certificates authentication. + * Uses system roots if None is set. + * + *

Examples

+ *
+     * options.tlsRootCAPath();
+     * 
+ */ + @CheckReturnValue + public Optional tlsRootCAPath() { + if (driver_options_has_tls_root_ca_path(nativeObject)) + return Optional.of(driver_options_get_tls_root_ca_path(nativeObject)); + return Optional.empty(); + } + + /** + * Returns the TLS root CA set in this DriverOptions object. + * Specifies the root CA used in the TLS config for server certificates authentication. + * Uses system roots if None is set. + * + *

Examples

+ *
+     * options.tlsRootCAPath(Optional.of("/path/to/ca-certificate.pem"));
+     * 
+ * + * @param tlsRootCAPath The path to the TLS root CA. If None, system roots are used. */ - public DriverOptions(boolean isTlsEnabled, String tlsRootCAPath) { // TODO: Maybe Optional? Optional.of(Path.of(..))?.. - super(newNative(isTlsEnabled, tlsRootCAPath)); + public DriverOptions tlsRootCAPath(Optional tlsRootCAPath) { + driver_options_set_tls_root_ca_path(nativeObject, tlsRootCAPath.orElse(null)); + return this; } - private static com.typedb.driver.jni.DriverOptions newNative(boolean isTlsEnabled, @Nullable String tlsRootCAPath) { - try { - return driver_options_new(isTlsEnabled, tlsRootCAPath); - } catch (com.typedb.driver.jni.Error error) { - throw new TypeDBDriverException(error); - } + /** + * Returns the value set for the replication usage flag in this DriverOptions object. + * Specifies whether the connection to TypeDB can use cluster replicas provided by the server + * or it should be limited to a single configured address. + * + *

Examples

+ *
+     * options.useReplication();
+     * 
+ */ + @CheckReturnValue + public Boolean useReplication() { + return driver_options_get_use_replication(nativeObject); + } + + /** + * Explicitly sets whether the connection to TypeDB can use cluster replicas provided by the server + * or it should be limited to a single configured address. + * Defaults to true. + * + *

Examples

+ *
+     * options.useReplication(true);
+     * 
+ * + * @param useReplication Whether the connection to TypeDB can use replication. + */ + public DriverOptions useReplication(boolean useReplication) { + driver_options_set_use_replication(nativeObject, useReplication); + return this; + } + + /** + * Returns the value set for the primary failover retries limit in this DriverOptions object. + * Limits the number of attempts to redirect a strongly consistent request to another + * primary replica in case of a failure due to the change of replica roles. + * + *

Examples

+ *
+     * options.primaryFailoverRetries();
+     * 
+ */ + @CheckReturnValue + public Integer primaryFailoverRetries() { + return (int) driver_options_get_primary_failover_retries(nativeObject); + } + + /** + * Explicitly sets the limit on the number of attempts to redirect a strongly consistent request to another + * primary replica in case of a failure due to the change of replica roles. + * Defaults to 1. + * + *

Examples

+ *
+     * options.primaryFailoverRetries(1);
+     * 
+ * + * @param primaryFailoverRetries The limit of primary failover retries. + */ + public DriverOptions primaryFailoverRetries(int primaryFailoverRetries) { + Validator.requireNonNegative(primaryFailoverRetries, "primaryFailoverRetries"); + driver_options_set_primary_failover_retries(nativeObject, primaryFailoverRetries); + return this; + } + + /** + * Returns the value set for the replica discovery attempts limit in this DriverOptions object. + * Limits the number of driver attempts to discover a single working replica to perform an + * operation in case of a replica unavailability. Every replica is tested once, which means + * that at most: + * - {limit} operations are performed if the limit <= the number of replicas. + * - {number of replicas} operations are performed if the limit > the number of replicas. + * - {number of replicas} operations are performed if the limit is None. + * Affects every eventually consistent operation, including redirect failover, when the new + * primary replica is unknown. + * + *

Examples

+ *
+     * options.replicaDiscoveryAttempts();
+     * 
+ */ + @CheckReturnValue + public Optional replicaDiscoveryAttempts() { + if (driver_options_has_replica_discovery_attempts(nativeObject)) + return Optional.of((int) driver_options_get_replica_discovery_attempts(nativeObject)); + return Optional.empty(); + } + + /** + * Limits the number of driver attempts to discover a single working replica to perform an + * operation in case of a replica unavailability. Every replica is tested once, which means + * that at most: + * - {limit} operations are performed if the limit <= the number of replicas. + * - {number of replicas} operations are performed if the limit > the number of replicas. + * - {number of replicas} operations are performed if the limit is None. + * Affects every eventually consistent operation, including redirect failover, when the new + * primary replica is unknown. If not set, the maximum (practically unlimited) value is used. + * + *

Examples

+ *
+     * options.primaryFailoverRetries(1);
+     * 
+ * + * @param replicaDiscoveryAttempts The limit of replica discovery attempts. + */ + public DriverOptions replicaDiscoveryAttempts(int replicaDiscoveryAttempts) { + Validator.requireNonNegative(replicaDiscoveryAttempts, "replicaDiscoveryAttempts"); + driver_options_set_replica_discovery_attempts(nativeObject, replicaDiscoveryAttempts); + return this; } } diff --git a/java/api/QueryOptions.java b/java/api/QueryOptions.java index 1bc97c10cc..77e762283d 100644 --- a/java/api/QueryOptions.java +++ b/java/api/QueryOptions.java @@ -68,7 +68,7 @@ public Optional includeInstanceTypes() { } /** - * Explicitly set the "include instance types" flag. + * Explicitly setsthe "include instance types" flag. * If set, specifies if types should be included in instance structs returned in ConceptRow answers. * This option allows reducing the amount of unnecessary data transmitted. * @@ -103,7 +103,7 @@ public Optional prefetchSize() { } /** - * Explicitly set the prefetch size. + * Explicitly setsthe prefetch size. * If set, specifies the number of extra query responses sent before the client side has to re-request more responses. * Increasing this may increase performance for queries with a huge number of answers, as it can * reduce the number of network round-trips at the cost of more resources on the server side. diff --git a/java/api/TransactionOptions.java b/java/api/TransactionOptions.java index cbf246b347..4b6d3d587d 100644 --- a/java/api/TransactionOptions.java +++ b/java/api/TransactionOptions.java @@ -25,15 +25,17 @@ import javax.annotation.CheckReturnValue; import java.util.Optional; +import static com.typedb.driver.jni.typedb_driver.transaction_options_get_read_consistency_level; import static com.typedb.driver.jni.typedb_driver.transaction_options_get_schema_lock_acquire_timeout_millis; import static com.typedb.driver.jni.typedb_driver.transaction_options_get_transaction_timeout_millis; +import static com.typedb.driver.jni.typedb_driver.transaction_options_has_read_consistency_level; import static com.typedb.driver.jni.typedb_driver.transaction_options_has_schema_lock_acquire_timeout_millis; import static com.typedb.driver.jni.typedb_driver.transaction_options_has_transaction_timeout_millis; import static com.typedb.driver.jni.typedb_driver.transaction_options_new; +import static com.typedb.driver.jni.typedb_driver.transaction_options_set_read_consistency_level; import static com.typedb.driver.jni.typedb_driver.transaction_options_set_schema_lock_acquire_timeout_millis; import static com.typedb.driver.jni.typedb_driver.transaction_options_set_transaction_timeout_millis; - /** * TypeDB transaction options. TransactionOptions object can be used to override * the default server behaviour for opened transactions. @@ -68,7 +70,7 @@ public Optional transactionTimeoutMillis() { } /** - * Explicitly set a transaction timeout. + * Explicitly sets a transaction timeout. * If set, specifies how long the driver should wait if opening a transaction is blocked by an exclusive schema write lock. * *

Examples

@@ -116,4 +118,37 @@ public TransactionOptions schemaLockAcquireTimeoutMillis(int schemaLockAcquireTi transaction_options_set_schema_lock_acquire_timeout_millis(nativeObject, schemaLockAcquireTimeoutMillis); return this; } + + /** + * Returns the value set for the read consistency level in this TransactionOptions object. + * If set, specifies the requested consistency level of the transaction opening operation. + * Affects only read transactions, as write and schema transactions require primary replicas. + * + *

Examples

+ *
+     * options.readConsistencyLevel();
+     * 
+ */ + public Optional readConsistencyLevel() { + if (transaction_options_has_read_consistency_level(nativeObject)) + return Optional.of(ConsistencyLevel.of(transaction_options_get_read_consistency_level(nativeObject))); + return Optional.empty(); + } + + /** + * Explicitly sets read consistency level. + * If set, specifies the requested consistency level of the transaction opening operation. + * Affects only read transactions, as write and schema transactions require primary replicas. + * + *

Examples

+ *
+     * options.schemaLockAcquireTimeoutMillis(schemaLockAcquireTimeoutMillis);
+     * 
+ * + * @param readConsistencyLevel The requested consistency level + */ + public TransactionOptions readConsistencyLevel(ConsistencyLevel readConsistencyLevel) { + transaction_options_set_read_consistency_level(nativeObject, readConsistencyLevel.nativeValue()); + return this; + } } diff --git a/java/api/answer/ConceptRow.java b/java/api/answer/ConceptRow.java index 1a463683b2..83d388aa56 100644 --- a/java/api/answer/ConceptRow.java +++ b/java/api/answer/ConceptRow.java @@ -65,7 +65,7 @@ public interface ConceptRow { * conceptRow.get(columnName); * * - * @param columnName the variable (column name from ``column_names``) + * @param columnName the variable (column name from column_names) */ @CheckReturnValue Optional get(String columnName) throws TypeDBDriverException; diff --git a/java/api/database/Database.java b/java/api/database/Database.java index 4b2bb4bc15..66e69dc209 100644 --- a/java/api/database/Database.java +++ b/java/api/database/Database.java @@ -19,6 +19,7 @@ package com.typedb.driver.api.database; +import com.typedb.driver.api.ConsistencyLevel; import com.typedb.driver.common.exception.TypeDBDriverException; import javax.annotation.CheckReturnValue; @@ -27,12 +28,18 @@ public interface Database { /** * The database name as a string. + * + *

Examples

+ *
+     * database.name()
+     * 
*/ @CheckReturnValue String name(); /** - * A full schema text as a valid TypeQL define query string. + * A full schema text as a valid TypeQL define query string, using default strong consistency. + * See {@link #schema(ConsistencyLevel)} for more details and options. * *

Examples

*
@@ -40,10 +47,26 @@ public interface Database {
      * 
*/ @CheckReturnValue - String schema() throws TypeDBDriverException; + default String schema() throws TypeDBDriverException { + return schema(null); + } /** - * The types in the schema as a valid TypeQL define query string. + * A full schema text as a valid TypeQL define query string. + * + *

Examples

+ *
+     * database.schema(ConsistencyLevel.Strong)
+     * 
+ * + * @param consistencyLevel The consistency level to use for the operation + */ + @CheckReturnValue + String schema(ConsistencyLevel consistencyLevel) throws TypeDBDriverException; + + /** + * The types in the schema as a valid TypeQL define query string, using default strong consistency. + * See {@link #typeSchema(ConsistencyLevel)} for more details and options. * *

Examples

*
@@ -51,11 +74,27 @@ public interface Database {
      * 
*/ @CheckReturnValue - String typeSchema() throws TypeDBDriverException; + default String typeSchema() throws TypeDBDriverException { + return typeSchema(null); + } /** - * Export a database into a schema definition and a data files saved to the disk. + * The types in the schema as a valid TypeQL define query string. + * + *

Examples

+ *
+     * database.typeSchema(ConsistencyLevel.Strong)
+     * 
+ * + * @param consistencyLevel The consistency level to use for the operation + */ + @CheckReturnValue + String typeSchema(ConsistencyLevel consistencyLevel) throws TypeDBDriverException; + + /** + * Export a database into a schema definition and a data files saved to the disk, using default strong consistency. * This is a blocking operation and may take a significant amount of time depending on the database size. + * See {@link #exportToFile(String, String, ConsistencyLevel)} for more details and options. * *

Examples

*
@@ -65,7 +104,24 @@ public interface Database {
      * @param schemaFilePath The path to the schema definition file to be created
      * @param dataFilePath   The path to the data file to be created
      */
-    void exportToFile(String schemaFilePath, String dataFilePath) throws TypeDBDriverException;
+    default void exportToFile(String schemaFilePath, String dataFilePath) throws TypeDBDriverException {
+        exportToFile(schemaFilePath, dataFilePath, null);
+    }
+
+    /**
+     * Export a database into a schema definition and a data files saved to the disk.
+     * This is a blocking operation and may take a significant amount of time depending on the database size.
+     *
+     * 

Examples

+ *
+     * database.exportToFile("schema.typeql", "data.typedb", ConsistencyLevel.Strong)
+     * 
+ * + * @param schemaFilePath The path to the schema definition file to be created + * @param dataFilePath The path to the data file to be created + * @param consistencyLevel The consistency level to use for the operation + */ + void exportToFile(String schemaFilePath, String dataFilePath, ConsistencyLevel consistencyLevel) throws TypeDBDriverException; /** * Deletes this database. @@ -76,71 +132,4 @@ public interface Database { *
*/ void delete() throws TypeDBDriverException; - -// /** -// * Set of Replica instances for this database. -// * Only works in TypeDB Cloud / Enterprise -// * -// *

Examples

-// *
-//     * database.replicas()
-//     * 
-// */ -// @CheckReturnValue -// Set replicas(); -// -// /** -// * Returns the primary replica for this database. -// * Only works in TypeDB Cloud / Enterprise -// * -// *

Examples

-// *
-//     * database.primaryReplica()
-//     * 
-// */ -// @CheckReturnValue -// Optional primaryReplica(); -// -// /** -// * Returns the preferred replica for this database. Operations which can be run on any replica will prefer to use this replica. -// * Only works in TypeDB Cloud / Enterprise -// * -// *

Examples

-// *
-//     * database.preferredReplica()
-//     * 
-// */ -// @CheckReturnValue -// Optional preferredReplica(); -// -// /** -// * The metadata and state of an individual raft replica of a database. -// */ -// interface Replica { -// -// /** -// * The server hosting this replica -// */ -// @CheckReturnValue -// String server(); -// -// /** -// * Checks whether this is the primary replica of the raft cluster. -// */ -// @CheckReturnValue -// boolean isPrimary(); -// -// /** -// * Checks whether this is the preferred replica of the raft cluster. -// * If true, Operations which can be run on any replica will prefer to use this replica. -// */ -// @CheckReturnValue -// boolean isPreferred(); -// -// /** -// * The raft protocol ‘term’ of this replica. -// */ -// @CheckReturnValue -// long term(); -// } } diff --git a/java/api/database/DatabaseManager.java b/java/api/database/DatabaseManager.java index 7fdb30e5da..52b2572c8a 100644 --- a/java/api/database/DatabaseManager.java +++ b/java/api/database/DatabaseManager.java @@ -19,6 +19,7 @@ package com.typedb.driver.api.database; +import com.typedb.driver.api.ConsistencyLevel; import com.typedb.driver.common.exception.TypeDBDriverException; import javax.annotation.CheckReturnValue; @@ -28,22 +29,36 @@ * Provides access to all database management methods. */ public interface DatabaseManager { + /** + * Retrieves all databases present on the TypeDB server, using default strong consistency. + * See {@link #all(ConsistencyLevel)} for more details and options. + * + *

Examples

+ *
+     * driver.databases().all()
+     * 
+ */ + @CheckReturnValue + default List all() throws TypeDBDriverException { + return all(null); + } /** - * Retrieve the database with the given name. + * Retrieves all databases present on the TypeDB server. * *

Examples

*
-     * driver.databases().get(name)
+     * driver.databases().all(ConsistencyLevel.Strong)
      * 
* - * @param name The name of the database to retrieve + * @param consistencyLevel The consistency level to use for the operation */ @CheckReturnValue - Database get(String name) throws TypeDBDriverException; + List all(ConsistencyLevel consistencyLevel) throws TypeDBDriverException; /** - * Checks if a database with the given name exists. + * Checks if a database with the given name exists, using default strong consistency. + * See {@link #contains(String, ConsistencyLevel)} for more details and options. * *

Examples

*
@@ -53,10 +68,56 @@ public interface DatabaseManager {
      * @param name The database name to be checked
      */
     @CheckReturnValue
-    boolean contains(String name) throws TypeDBDriverException;
+    default boolean contains(String name) throws TypeDBDriverException {
+        return contains(name, null);
+    }
+
+    /**
+     * Checks if a database with the given name exists.
+     *
+     * 

Examples

+ *
+     * driver.databases().contains(name, ConsistencyLevel.Strong)
+     * 
+ * + * @param name The database name to be checked + * @param consistencyLevel The consistency level to use for the operation + */ + @CheckReturnValue + boolean contains(String name, ConsistencyLevel consistencyLevel) throws TypeDBDriverException; + + /** + * Retrieves the database with the given name, using default strong consistency. + * See {@link #get(String, ConsistencyLevel)} for more details and options. + * + *

Examples

+ *
+     * driver.databases().get(name)
+     * 
+ * + * @param name The name of the database to retrieve + */ + @CheckReturnValue + default Database get(String name) throws TypeDBDriverException { + return get(name, null); + } + + /** + * Retrieves the database with the given name. + * + *

Examples

+ *
+     * driver.databases().get(name, ConsistencyLevel.Strong)
+     * 
+ * + * @param name The name of the database to retrieve + * @param consistencyLevel The consistency level to use for the operation + */ + @CheckReturnValue + Database get(String name, ConsistencyLevel consistencyLevel) throws TypeDBDriverException; /** - * Create a database with the given name. + * Creates a database with the given name. * *

Examples

*
@@ -68,7 +129,7 @@ public interface DatabaseManager {
     void create(String name) throws TypeDBDriverException;
 
     /**
-     * Create a database with the given name based on previously exported another database's data loaded from a file.
+     * Creates a database with the given name based on previously exported another database's data loaded from a file.
      * This is a blocking operation and may take a significant amount of time depending on the database size.
      *
      * 

Examples

@@ -81,15 +142,4 @@ public interface DatabaseManager { * @param dataFilePath The exported database file path to import the data from */ void importFromFile(String name, String schema, String dataFilePath) throws TypeDBDriverException; - - /** - * Retrieves all databases present on the TypeDB server. - * - *

Examples

- *
-     * driver.databases().all()
-     * 
- */ - @CheckReturnValue - List all() throws TypeDBDriverException; } diff --git a/java/api/server/ReplicaType.java b/java/api/server/ReplicaType.java new file mode 100644 index 0000000000..a05c454e26 --- /dev/null +++ b/java/api/server/ReplicaType.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.typedb.driver.api.server; + +import com.typedb.driver.common.exception.TypeDBDriverException; + +import static com.typedb.driver.common.exception.ErrorMessage.Internal.UNEXPECTED_NATIVE_VALUE; + +/** + * This enum is used to specify the type of replica. + * + *

Examples

+ *
+ * replica.getType();
+ * 
+ */ +public enum ReplicaType { + PRIMARY(0, com.typedb.driver.jni.ReplicaType.Primary), + SECONDARY(1, com.typedb.driver.jni.ReplicaType.Secondary); + + public final com.typedb.driver.jni.ReplicaType nativeObject; + private final int id; + + ReplicaType(int id, com.typedb.driver.jni.ReplicaType nativeObject) { + this.id = id; + this.nativeObject = nativeObject; + } + + public static ReplicaType of(com.typedb.driver.jni.ReplicaType nativeType) { + if (nativeType == com.typedb.driver.jni.ReplicaType.Primary) return PRIMARY; + else if (nativeType == com.typedb.driver.jni.ReplicaType.Secondary) return SECONDARY; + throw new TypeDBDriverException(UNEXPECTED_NATIVE_VALUE); + } + + public int id() { + return id; + } + + /** + * Checks whether this is the primary replica of the raft cluster. + */ + public boolean isPrimary() { + return nativeObject == com.typedb.driver.jni.ReplicaType.Primary; + } + + /** + * Checks whether this is a secondary replica of the raft cluster. + */ + public boolean isSecondary() { + return nativeObject == com.typedb.driver.jni.ReplicaType.Secondary; + } +} diff --git a/java/api/server/ServerReplica.java b/java/api/server/ServerReplica.java new file mode 100644 index 0000000000..c9f0519bc5 --- /dev/null +++ b/java/api/server/ServerReplica.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.typedb.driver.api.server; + +import javax.annotation.CheckReturnValue; + +/** + * The metadata and state of an individual raft replica of a driver connection. + */ +public interface ServerReplica { + // TODO: This is what u64 is converted to. This one feels weird, although I don't know what to do with it. + + /** + * Returns the id of this replica. + */ + @CheckReturnValue + long getID(); + + /** + * Returns the address this replica is hosted at. + */ + @CheckReturnValue + String getAddress(); + + /** + * Returns whether this is the primary replica of the raft cluster or any of the supporting types. + */ + @CheckReturnValue + ReplicaType getType(); + + /** + * Checks whether this is the primary replica of the raft cluster. + */ + @CheckReturnValue + Boolean isPrimary(); + + /** + * Returns the raft protocol ‘term’ of this replica. + */ + @CheckReturnValue + long getTerm(); +} diff --git a/java/api/server/ServerVersion.java b/java/api/server/ServerVersion.java new file mode 100644 index 0000000000..f216a8ed79 --- /dev/null +++ b/java/api/server/ServerVersion.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.typedb.driver.api.server; + +import com.typedb.driver.common.NativeObject; + +/** + * A full TypeDB server's version specification. + * + *

Examples

+ *
+ * driver.serverVersion();
+ * 
+ */ +public class ServerVersion extends NativeObject { + /** + * @hidden + */ + public ServerVersion(com.typedb.driver.jni.ServerVersion nativeObject) { + super(nativeObject); + } + + /** + * Returns the server's distribution. + * + *

Examples

+ *
+     * serverVersion.getDistribution();
+     * 
+ */ + public String getDistribution() { + assert (nativeObject.isOwned()); + return nativeObject.getDistribution(); + } + + /** + * Returns the server's version. + * + *

Examples

+ *
+     * serverVersion.getVersion();
+     * 
+ */ + public String getVersion() { + assert (nativeObject.isOwned()); + return nativeObject.getVersion(); + } +} diff --git a/java/api/user/UserManager.java b/java/api/user/UserManager.java index 5c2a693fb3..64e334cc93 100644 --- a/java/api/user/UserManager.java +++ b/java/api/user/UserManager.java @@ -19,6 +19,7 @@ package com.typedb.driver.api.user; +import com.typedb.driver.api.ConsistencyLevel; import com.typedb.driver.common.exception.TypeDBDriverException; import javax.annotation.CheckReturnValue; @@ -29,20 +30,63 @@ */ public interface UserManager { /** - * Checks if a user with the given name exists. + * Retrieves all users which exist on the TypeDB server, using default strong consistency. + * See {@link #all(ConsistencyLevel)} for more details and options. + * + *

Examples

+ *
+     * driver.users().all();
+     * 
+ */ + default Set all() throws TypeDBDriverException { + return all(null); + } + + /** + * Retrieves all users which exist on the TypeDB server. + * + *

Examples

+ *
+     * driver.users().all(ConsistencyLevel.Strong);
+     * 
+ * + * @param consistencyLevel The consistency level to use for the operation + */ + Set all(ConsistencyLevel consistencyLevel) throws TypeDBDriverException; + + /** + * Checks if a user with the given name exists., using default strong consistency. + * See {@link #contains(String, ConsistencyLevel)} for more details and options. * *

Examples

*
      * driver.users().contains(username);
      * 
* - * @param username The user name to be checked + * @param username The username to be checked */ @CheckReturnValue - boolean contains(String username) throws TypeDBDriverException; + default boolean contains(String username) throws TypeDBDriverException { + return contains(username, null); + } /** - * Retrieves a user with the given name. + * Checks if a user with the given name exists. + * + *

Examples

+ *
+     * driver.users().contains(username, ConsistencyLevel.Strong);
+     * 
+ * + * @param username The username to be checked + * @param consistencyLevel The consistency level to use for the operation + */ + @CheckReturnValue + boolean contains(String username, ConsistencyLevel consistencyLevel) throws TypeDBDriverException; + + /** + * Retrieves a user with the given name, using default strong consistency. + * See {@link #get(String, ConsistencyLevel)} for more details and options. * *

Examples

*
@@ -52,30 +96,50 @@ public interface UserManager {
      * @param username The name of the user to retrieve
      */
     @CheckReturnValue
-    User get(String username) throws TypeDBDriverException;
+    default User get(String username) throws TypeDBDriverException {
+        return get(username, null);
+    }
 
-    // TODO: I don't like this, leaving this way for now. Use driver.users().get(username)
+    /**
+     * Retrieves a user with the given name.
+     *
+     * 

Examples

+ *
+     * driver.users().get(username, ConsistencyLevel.Strong);
+     * 
+ * + * @param username The name of the user to retrieve + * @param consistencyLevel The consistency level to use for the operation + */ + @CheckReturnValue + User get(String username, ConsistencyLevel consistencyLevel) throws TypeDBDriverException; /** - * Retrieves the name of the user who opened the current connection. + * Retrieves the name of the user who opened the current connection, using default strong consistency. + * See {@link #getCurrentUser(ConsistencyLevel)} for more details and options. * *

Examples

*
-     * driver.users().getCurrentUsername();
+     * driver.users().getCurrentUser();
      * 
*/ @CheckReturnValue - User getCurrentUser() throws TypeDBDriverException; + default User getCurrentUser() throws TypeDBDriverException { + return getCurrentUser(null); + } /** - * Retrieves all users which exist on the TypeDB server. + * Retrieves the name of the user who opened the current connection. * *

Examples

*
-     * driver.users().all();
+     * driver.users().getCurrentUser(ConsistencyLevel.Strong);
      * 
+ * + * @param consistencyLevel The consistency level to use for the operation */ - Set all() throws TypeDBDriverException; + @CheckReturnValue + User getCurrentUser(ConsistencyLevel consistencyLevel) throws TypeDBDriverException; /** * Creates a user with the given name & password. diff --git a/java/connection/DatabaseImpl.java b/java/connection/DatabaseImpl.java index 24cb2a83cc..301c81bcfd 100644 --- a/java/connection/DatabaseImpl.java +++ b/java/connection/DatabaseImpl.java @@ -19,6 +19,7 @@ package com.typedb.driver.connection; +import com.typedb.driver.api.ConsistencyLevel; import com.typedb.driver.api.database.Database; import com.typedb.driver.common.NativeObject; import com.typedb.driver.common.Validator; @@ -43,31 +44,31 @@ public String name() { } @Override - public String schema() throws TypeDBDriverException { + public String schema(ConsistencyLevel consistencyLevel) throws TypeDBDriverException { if (!nativeObject.isOwned()) throw new TypeDBDriverException(DATABASE_DELETED); try { - return database_schema(nativeObject); + return database_schema(nativeObject, ConsistencyLevel.nativeValue(consistencyLevel)); } catch (com.typedb.driver.jni.Error e) { throw new TypeDBDriverException(e); } } @Override - public String typeSchema() throws TypeDBDriverException { + public String typeSchema(ConsistencyLevel consistencyLevel) throws TypeDBDriverException { if (!nativeObject.isOwned()) throw new TypeDBDriverException(DATABASE_DELETED); try { - return database_type_schema(nativeObject); + return database_type_schema(nativeObject, ConsistencyLevel.nativeValue(consistencyLevel)); } catch (com.typedb.driver.jni.Error e) { throw new TypeDBDriverException(e); } } @Override - public void exportToFile(String schemaFilePath, String dataFilePath) throws TypeDBDriverException { + public void exportToFile(String schemaFilePath, String dataFilePath, ConsistencyLevel consistencyLevel) throws TypeDBDriverException { Validator.requireNonNull(schemaFilePath, "schemaFilePath"); Validator.requireNonNull(dataFilePath, "dataFilePath"); try { - database_export_to_file(nativeObject, schemaFilePath, dataFilePath); + database_export_to_file(nativeObject, schemaFilePath, dataFilePath, ConsistencyLevel.nativeValue(consistencyLevel)); } catch (com.typedb.driver.jni.Error e) { throw new TypeDBDriverException(e); } @@ -88,52 +89,4 @@ public void delete() throws TypeDBDriverException { public String toString() { return name(); } - -// @Override -// public Set replicas() { -// if (!nativeObject.isOwned()) throw new TypeDBDriverException(DATABASE_DELETED); -// return new NativeIterator<>(database_get_replicas_info(nativeObject)).stream().map(Replica::new).collect(Collectors.toSet()); -// } -// -// @Override -// public Optional primaryReplica() { -// if (!nativeObject.isOwned()) throw new TypeDBDriverException(DATABASE_DELETED); -// com.typedb.driver.jni.ReplicaInfo res = database_get_primary_replica_info(nativeObject); -// if (res != null) return Optional.of(new Replica(res)); -// else return Optional.empty(); -// } -// -// @Override -// public Optional preferredReplica() { -// if (!nativeObject.isOwned()) throw new TypeDBDriverException(DATABASE_DELETED); -// com.typedb.driver.jni.ReplicaInfo res = database_get_preferred_replica_info(nativeObject); -// if (res != null) return Optional.of(new Replica(res)); -// else return Optional.empty(); -// } -// -// public static class Replica extends NativeObject implements Database.Replica { -// Replica(com.typedb.driver.jni.ReplicaInfo replicaInfo) { -// super(replicaInfo); -// } -// -// @Override -// public String server() { -// return replica_info_get_server(nativeObject); -// } -// -// @Override -// public boolean isPrimary() { -// return replica_info_is_primary(nativeObject); -// } -// -// @Override -// public boolean isPreferred(){ -// return replica_info_is_preferred(nativeObject); -// } -// -// @Override -// public long term() { -// return replica_info_get_term(nativeObject); -// } -// } } diff --git a/java/connection/DatabaseManagerImpl.java b/java/connection/DatabaseManagerImpl.java index 8c2a2f83df..e1028a79ef 100644 --- a/java/connection/DatabaseManagerImpl.java +++ b/java/connection/DatabaseManagerImpl.java @@ -19,6 +19,7 @@ package com.typedb.driver.connection; +import com.typedb.driver.api.ConsistencyLevel; import com.typedb.driver.api.database.Database; import com.typedb.driver.api.database.DatabaseManager; import com.typedb.driver.common.NativeIterator; @@ -42,51 +43,51 @@ public DatabaseManagerImpl(com.typedb.driver.jni.TypeDBDriver driver) { } @Override - public Database get(String name) throws TypeDBDriverException { - Validator.requireNonNull(name, "name"); + public List all(ConsistencyLevel consistencyLevel) throws TypeDBDriverException { try { - return new DatabaseImpl(databases_get(nativeDriver, name)); + return new NativeIterator<>(databases_all(nativeDriver, ConsistencyLevel.nativeValue(consistencyLevel))).stream().map(DatabaseImpl::new).collect(toList()); } catch (com.typedb.driver.jni.Error e) { throw new TypeDBDriverException(e); } } @Override - public boolean contains(String name) throws TypeDBDriverException { + public boolean contains(String name, ConsistencyLevel consistencyLevel) throws TypeDBDriverException { Validator.requireNonNull(name, "name"); try { - return databases_contains(nativeDriver, name); + return databases_contains(nativeDriver, name, ConsistencyLevel.nativeValue(consistencyLevel)); } catch (com.typedb.driver.jni.Error e) { throw new TypeDBDriverException(e); } } @Override - public void create(String name) throws TypeDBDriverException { + public Database get(String name, ConsistencyLevel consistencyLevel) throws TypeDBDriverException { Validator.requireNonNull(name, "name"); try { - databases_create(nativeDriver, name); + return new DatabaseImpl(databases_get(nativeDriver, name, ConsistencyLevel.nativeValue(consistencyLevel))); } catch (com.typedb.driver.jni.Error e) { throw new TypeDBDriverException(e); } } @Override - public void importFromFile(String name, String schema, String dataFilePath) throws TypeDBDriverException { + public void create(String name) throws TypeDBDriverException { Validator.requireNonNull(name, "name"); - Validator.requireNonNull(schema, "schema"); - Validator.requireNonNull(dataFilePath, "dataFilePath"); try { - databases_import_from_file(nativeDriver, name, schema, dataFilePath); + databases_create(nativeDriver, name); } catch (com.typedb.driver.jni.Error e) { throw new TypeDBDriverException(e); } } @Override - public List all() throws TypeDBDriverException { + public void importFromFile(String name, String schema, String dataFilePath) throws TypeDBDriverException { + Validator.requireNonNull(name, "name"); + Validator.requireNonNull(schema, "schema"); + Validator.requireNonNull(dataFilePath, "dataFilePath"); try { - return new NativeIterator<>(databases_all(nativeDriver)).stream().map(DatabaseImpl::new).collect(toList()); + databases_import_from_file(nativeDriver, name, schema, dataFilePath); } catch (com.typedb.driver.jni.Error e) { throw new TypeDBDriverException(e); } diff --git a/java/connection/DriverImpl.java b/java/connection/DriverImpl.java index 1bcfd734d5..1957c3179c 100644 --- a/java/connection/DriverImpl.java +++ b/java/connection/DriverImpl.java @@ -19,21 +19,41 @@ package com.typedb.driver.connection; +import com.typedb.driver.api.ConsistencyLevel; import com.typedb.driver.api.Credentials; import com.typedb.driver.api.Driver; import com.typedb.driver.api.DriverOptions; import com.typedb.driver.api.Transaction; import com.typedb.driver.api.TransactionOptions; import com.typedb.driver.api.database.DatabaseManager; +import com.typedb.driver.api.server.ServerReplica; +import com.typedb.driver.api.server.ServerVersion; import com.typedb.driver.api.user.UserManager; +import com.typedb.driver.common.NativeIterator; import com.typedb.driver.common.NativeObject; import com.typedb.driver.common.Validator; import com.typedb.driver.common.exception.TypeDBDriverException; import com.typedb.driver.user.UserManagerImpl; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static com.typedb.driver.jni.typedb_driver.driver_deregister_replica; import static com.typedb.driver.jni.typedb_driver.driver_force_close; import static com.typedb.driver.jni.typedb_driver.driver_is_open; -import static com.typedb.driver.jni.typedb_driver.driver_open_with_description; +import static com.typedb.driver.jni.typedb_driver.driver_new_with_address_translation_with_description; +import static com.typedb.driver.jni.typedb_driver.driver_new_with_addresses_with_description; +import static com.typedb.driver.jni.typedb_driver.driver_new_with_description; +import static com.typedb.driver.jni.typedb_driver.driver_primary_replica; +import static com.typedb.driver.jni.typedb_driver.driver_register_replica; +import static com.typedb.driver.jni.typedb_driver.driver_replicas; +import static com.typedb.driver.jni.typedb_driver.driver_server_version; +import static com.typedb.driver.jni.typedb_driver.driver_update_address_translation; +import static java.util.stream.Collectors.toSet; public class DriverImpl extends NativeObject implements Driver { @@ -41,6 +61,14 @@ public DriverImpl(String address, Credentials credentials, DriverOptions driverO this(open(address, credentials, driverOptions)); } + public DriverImpl(Set addresses, Credentials credentials, DriverOptions driverOptions) throws TypeDBDriverException { + this(open(addresses, credentials, driverOptions)); + } + + public DriverImpl(Map addressTranslation, Credentials credentials, DriverOptions driverOptions) throws TypeDBDriverException { + this(open(addressTranslation, credentials, driverOptions)); + } + private DriverImpl(com.typedb.driver.jni.TypeDBDriver connection) { super(connection); } @@ -50,7 +78,30 @@ private static com.typedb.driver.jni.TypeDBDriver open(String address, Credentia Validator.requireNonNull(credentials, "credentials"); Validator.requireNonNull(driverOptions, "driverOptions"); try { - return driver_open_with_description(address, credentials.nativeObject, driverOptions.nativeObject, LANGUAGE); + return driver_new_with_description(address, credentials.nativeObject, driverOptions.nativeObject, LANGUAGE); + } catch (com.typedb.driver.jni.Error e) { + throw new TypeDBDriverException(e); + } + } + + private static com.typedb.driver.jni.TypeDBDriver open(Set addresses, Credentials credentials, DriverOptions driverOptions) { + Validator.requireNonNull(addresses, "addresses"); + Validator.requireNonNull(credentials, "credentials"); + Validator.requireNonNull(driverOptions, "driverOptions"); + try { + return driver_new_with_addresses_with_description(addresses.toArray(new String[0]), credentials.nativeObject, driverOptions.nativeObject, LANGUAGE); + } catch (com.typedb.driver.jni.Error e) { + throw new TypeDBDriverException(e); + } + } + + private static com.typedb.driver.jni.TypeDBDriver open(Map addressTranslation, Credentials credentials, DriverOptions driverOptions) { + Validator.requireNonNull(addressTranslation, "addressTranslation"); + Validator.requireNonNull(credentials, "credentials"); + Validator.requireNonNull(driverOptions, "driverOptions"); + try { + Map.Entry addresses = getTranslatedAddresses(addressTranslation); + return driver_new_with_address_translation_with_description(addresses.getKey(), addresses.getValue(), credentials.nativeObject, driverOptions.nativeObject, LANGUAGE); } catch (com.typedb.driver.jni.Error e) { throw new TypeDBDriverException(e); } @@ -61,6 +112,15 @@ public boolean isOpen() { return driver_is_open(nativeObject); } + @Override + public ServerVersion serverVersion(ConsistencyLevel consistencyLevel) { + try { + return new ServerVersion(driver_server_version(nativeObject, ConsistencyLevel.nativeValue(consistencyLevel))); + } catch (com.typedb.driver.jni.Error e) { + throw new TypeDBDriverException(e); + } + } + @Override public UserManager users() { return new UserManagerImpl(nativeObject); @@ -83,6 +143,53 @@ public Transaction transaction(String database, Transaction.Type type, Transacti return new TransactionImpl(this, database, type, options); } + @Override + public Set replicas() { + try { + return new NativeIterator<>(driver_replicas(nativeObject)).stream().map(ServerReplicaImpl::new).collect(toSet()); + } catch (com.typedb.driver.jni.Error error) { + throw new TypeDBDriverException(error); + } + } + + @Override + public Optional primaryReplica() { + com.typedb.driver.jni.ServerReplica nativeReplica = driver_primary_replica(nativeObject); + if (nativeReplica != null) { + return Optional.of(new ServerReplicaImpl(nativeReplica)); + } + return Optional.empty(); + } + + @Override + public void registerReplica(long replicaID, String address) { + try { + driver_register_replica(nativeObject, replicaID, address); + } catch (com.typedb.driver.jni.Error error) { + throw new TypeDBDriverException(error); + } + } + + @Override + public void deregisterReplica(long replicaID) { + try { + driver_deregister_replica(nativeObject, replicaID); + } catch (com.typedb.driver.jni.Error error) { + throw new TypeDBDriverException(error); + } + } + + @Override + public void updateAddressTranslation(Map addressTranslation) { + Validator.requireNonNull(addressTranslation, "addressTranslation"); + try { + Map.Entry addresses = getTranslatedAddresses(addressTranslation); + driver_update_address_translation(nativeObject, addresses.getKey(), addresses.getValue()); + } catch (com.typedb.driver.jni.Error e) { + throw new TypeDBDriverException(e); + } + } + @Override public void close() { try { @@ -91,4 +198,16 @@ public void close() { throw new TypeDBDriverException(error); } } + + public static Map.Entry getTranslatedAddresses(Map addressTranslation) { + List publicAddresses = new ArrayList<>(); + List privateAddresses = new ArrayList<>(); + + for (Map.Entry entry : addressTranslation.entrySet()) { + publicAddresses.add(entry.getKey()); + privateAddresses.add(entry.getValue()); + } + + return new AbstractMap.SimpleEntry<>(publicAddresses.toArray(new String[0]), privateAddresses.toArray(new String[0])); + } } diff --git a/java/connection/ServerReplicaImpl.java b/java/connection/ServerReplicaImpl.java new file mode 100644 index 0000000000..ac7ebae798 --- /dev/null +++ b/java/connection/ServerReplicaImpl.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.typedb.driver.connection; + +import com.typedb.driver.api.server.ReplicaType; +import com.typedb.driver.api.server.ServerReplica; +import com.typedb.driver.common.NativeObject; + +import static com.typedb.driver.jni.typedb_driver.server_replica_get_address; +import static com.typedb.driver.jni.typedb_driver.server_replica_get_id; +import static com.typedb.driver.jni.typedb_driver.server_replica_is_primary; +import static com.typedb.driver.jni.typedb_driver.server_replica_get_term; +import static com.typedb.driver.jni.typedb_driver.server_replica_get_type; + +public class ServerReplicaImpl extends NativeObject implements ServerReplica { + public ServerReplicaImpl(com.typedb.driver.jni.ServerReplica serverReplica) { + super(serverReplica); + } + + @Override + public long getID() { + return server_replica_get_id(nativeObject); + } + + @Override + public String getAddress() { + return server_replica_get_address(nativeObject); + } + + @Override + public ReplicaType getType() { + return ReplicaType.of(server_replica_get_type(nativeObject)); + } + + @Override + public Boolean isPrimary() { + return server_replica_is_primary(nativeObject); + } + + @Override + public long getTerm() { + return server_replica_get_term(nativeObject); + } + + @Override + public String toString() { + return getAddress(); + } +} diff --git a/java/docs_structure.bzl b/java/docs_structure.bzl index ebf4d27f50..9fec398dc0 100644 --- a/java/docs_structure.bzl +++ b/java/docs_structure.bzl @@ -28,10 +28,16 @@ dir_mapping = { "TypeDB.adoc": "connection", "DriverOptions.adoc": "connection", "Credentials.adoc": "connection", + "ConsistencyLevel.adoc": "connection", + "ConsistencyLevel.Strong.adoc": "connection", + "ConsistencyLevel.Eventual.adoc": "connection", + "ConsistencyLevel.ReplicaDependant.adoc": "connection", + "ReplicaType.adoc": "connection", + "ServerReplica.adoc": "connection", + "ServerVersion.adoc": "connection", "Driver.adoc": "connection", "User.adoc": "connection", "UserManager.adoc": "connection", -# "Database.Replica.adoc": "connection", "Database.adoc": "connection", "DatabaseManager.adoc": "connection", "Relation.adoc": "data", diff --git a/java/test/behaviour/connection/BUILD b/java/test/behaviour/connection/BUILD index 3508c0c97a..e252dd941c 100644 --- a/java/test/behaviour/connection/BUILD +++ b/java/test/behaviour/connection/BUILD @@ -33,6 +33,7 @@ java_library( # External dependencies from Maven "@maven//:junit_junit", # "@maven//:com_typedb_typedb_runner", + "@maven//:io_cucumber_cucumber_java", ], ) diff --git a/java/test/behaviour/connection/ConnectionStepsBase.java b/java/test/behaviour/connection/ConnectionStepsBase.java index d2df6a82c1..9b63458b23 100644 --- a/java/test/behaviour/connection/ConnectionStepsBase.java +++ b/java/test/behaviour/connection/ConnectionStepsBase.java @@ -25,14 +25,13 @@ import com.typedb.driver.api.QueryOptions; import com.typedb.driver.api.Transaction; import com.typedb.driver.api.TransactionOptions; +import com.typedb.driver.api.server.ServerVersion; import com.typedb.driver.test.behaviour.config.Parameters; import com.typedb.driver.test.behaviour.util.Util; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; @@ -41,13 +40,13 @@ import static com.typedb.driver.test.behaviour.util.Util.createTempDir; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; public abstract class ConnectionStepsBase { public static final String ADMIN_USERNAME = "admin"; public static final String ADMIN_PASSWORD = "password"; public static final Credentials DEFAULT_CREDENTIALS = new Credentials(ADMIN_USERNAME, ADMIN_PASSWORD); - public static final DriverOptions DEFAULT_CONNECTION_SETTINGS = new DriverOptions(false, null); - public static final Map serverOptions = Collections.emptyMap(); public static int THREAD_POOL_SIZE = 32; public static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE); public static Driver driver; @@ -57,6 +56,7 @@ public abstract class ConnectionStepsBase { public static List backgroundTransactions = new ArrayList<>(); public static List> transactionsParallel = new ArrayList<>(); + public static DriverOptions driverOptions = new DriverOptions(); public static Optional transactionOptions = Optional.empty(); public static Optional queryOptions = Optional.empty(); static boolean isBeforeAllRan = false; @@ -124,6 +124,7 @@ void after() { driver.databases().all().forEach(database -> driver.databases().get(database.name()).delete()); driver.close(); backgroundDriver.close(); + driverOptions = new DriverOptions(); } void cleanupTransactions() { @@ -144,11 +145,9 @@ void cleanupBackgroundTransactions() { backgroundTransactions.clear(); } - abstract Driver createTypeDBDriver(String address, Credentials credentials, DriverOptions driverOptions); - abstract Driver createDefaultTypeDBDriver(); - public static void initTransactionOptionsIfNeeded() { // TODO: Implement steps + public static void initTransactionOptionsIfNeeded() { if (transactionOptions.isEmpty()) { transactionOptions = Optional.of(new TransactionOptions()); } @@ -173,6 +172,28 @@ void connection_is_open(boolean isOpen) { assertEquals(isOpen, driver != null && driver.isOpen()); } + void connection_contains_distribution(Parameters.MayError mayError) { + mayError.check(() -> { + ServerVersion serverVersion = driver.serverVersion(); + assertFalse(serverVersion.getDistribution().isEmpty()); + }); + } + + void connection_contains_version(Parameters.MayError mayError) { + mayError.check(() -> { + ServerVersion serverVersion = driver.serverVersion(); + assertFalse(serverVersion.getVersion().isEmpty()); + }); + } + + void connection_has_count_replicas(int count) { + assertEquals(driver.replicas().size(), count); + } + + void connection_contains_primary_replica() { + assertTrue(driver.primaryReplica().isPresent()); + } + void connection_has_count_databases(int count) { assertEquals(count, driver.databases().all().size()); } @@ -180,4 +201,16 @@ void connection_has_count_databases(int count) { void connection_has_count_users(int count) { assertEquals(count, driver.users().all().size()); } + + void set_driver_option_use_replication_to(boolean value) { + driverOptions = driverOptions.useReplication(value); + } + + void set_driver_option_primary_failover_retries_to(int value) { + driverOptions = driverOptions.primaryFailoverRetries(value); + } + + void set_driver_option_replica_discovery_attempts_to(int value) { + driverOptions = driverOptions.replicaDiscoveryAttempts(value); + } } diff --git a/java/test/behaviour/connection/ConnectionStepsCluster.java b/java/test/behaviour/connection/ConnectionStepsCluster.java index 10a3ff029c..77b5485e9f 100644 --- a/java/test/behaviour/connection/ConnectionStepsCluster.java +++ b/java/test/behaviour/connection/ConnectionStepsCluster.java @@ -25,10 +25,18 @@ import com.typedb.driver.test.behaviour.config.Parameters; import io.cucumber.java.After; import io.cucumber.java.Before; -import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; import io.cucumber.java.en.When; +import java.util.Optional; +import java.util.Set; + public class ConnectionStepsCluster extends ConnectionStepsBase { + // TODO: Add 2 more addresses + public static Set DEFAULT_CLUSTER_ADDRESSES = Set.of( + "https://127.0.0.1:11729" + ); + @Override public void beforeAll() { super.beforeAll(); @@ -36,6 +44,7 @@ public void beforeAll() { @Before public synchronized void before() { + driverOptions = driverOptions.isTlsEnabled(true).tlsRootCAPath(Optional.of(System.getenv("ROOT_CA"))); super.before(); } @@ -44,15 +53,17 @@ public synchronized void after() { super.after(); } - @Override Driver createTypeDBDriver(String address, Credentials credentials, DriverOptions driverOptions) { + return TypeDB.driver(Set.of(address), credentials, driverOptions); + } + + Driver createTypeDBDriver(Set address, Credentials credentials, DriverOptions driverOptions) { return TypeDB.driver(address, credentials, driverOptions); } @Override Driver createDefaultTypeDBDriver() { - // TODO: Add encryption to cluster tests - return createTypeDBDriver(TypeDB.DEFAULT_ADDRESS, DEFAULT_CREDENTIALS, DEFAULT_CONNECTION_SETTINGS); + return createTypeDBDriver(DEFAULT_CLUSTER_ADDRESSES, DEFAULT_CREDENTIALS, driverOptions); } @When("typedb starts") @@ -67,27 +78,28 @@ public void connection_opens_with_default_authentication() { @When("connection opens with username '{non_semicolon}', password '{non_semicolon}'{may_error}") public void connection_opens_with_username_password(String username, String password, Parameters.MayError mayError) { Credentials credentials = new Credentials(username, password); - mayError.check(() -> driver = createTypeDBDriver(TypeDB.DEFAULT_ADDRESS, credentials, DEFAULT_CONNECTION_SETTINGS)); + mayError.check(() -> driver = createTypeDBDriver(DEFAULT_CLUSTER_ADDRESSES, credentials, driverOptions)); } @When("connection opens with a wrong host{may_error}") public void connection_opens_with_a_wrong_host(Parameters.MayError mayError) { mayError.check(() -> driver = createTypeDBDriver( - TypeDB.DEFAULT_ADDRESS.replace("localhost", "surely-not-localhost"), + DEFAULT_CLUSTER_ADDRESSES.iterator().next().replace("127.0.0.1", "surely-not-localhost"), DEFAULT_CREDENTIALS, - DEFAULT_CONNECTION_SETTINGS + driverOptions )); } @When("connection opens with a wrong port{may_error}") public void connection_opens_with_a_wrong_port(Parameters.MayError mayError) { mayError.check(() -> driver = createTypeDBDriver( - TypeDB.DEFAULT_ADDRESS.replace("localhost", "surely-not-localhost"), + DEFAULT_CLUSTER_ADDRESSES.iterator().next().replace("127.0.0.1", "surely-not-localhost"), DEFAULT_CREDENTIALS, - DEFAULT_CONNECTION_SETTINGS + driverOptions )); } + @Override @When("connection closes") public void connection_closes() { @@ -95,20 +107,62 @@ public void connection_closes() { } @Override - @Given("connection is open: {bool}") + @Then("connection is open: {bool}") public void connection_is_open(boolean isOpen) { super.connection_is_open(isOpen); } @Override - @Given("connection has {integer} database(s)") + @Then("connection contains distribution{may_error}") + public void connection_contains_distribution(Parameters.MayError mayError) { + super.connection_contains_distribution(mayError); + } + + @Override + @Then("connection contains version{may_error}") + public void connection_contains_version(Parameters.MayError mayError) { + super.connection_contains_version(mayError); + } + + @Override + @Then("connection has {integer} replica(s)") + public void connection_has_count_replicas(int count) { + super.connection_has_count_replicas(count); + } + + @Override + @Then("connection contains primary replica") + public void connection_contains_primary_replica() { + super.connection_contains_primary_replica(); + } + + @Override + @Then("connection has {integer} database(s)") public void connection_has_count_databases(int count) { super.connection_has_count_databases(count); } @Override - @Given("connection has {integer} user(s)") + @Then("connection has {integer} user(s)") public void connection_has_count_users(int count) { super.connection_has_count_users(count); } + + @Override + @When("set driver option use_replication to: {bool}") + public void set_driver_option_use_replication_to(boolean value) { + super.set_driver_option_use_replication_to(value); + } + + @Override + @When("set driver option primary_failover_retries to: {integer}") + public void set_driver_option_primary_failover_retries_to(int value) { + super.set_driver_option_primary_failover_retries_to(value); + } + + @Override + @When("set driver option replica_discovery_attempts to: {integer}") + public void set_driver_option_replica_discovery_attempts_to(int value) { + super.set_driver_option_replica_discovery_attempts_to(value); + } } diff --git a/java/test/behaviour/connection/ConnectionStepsCommunity.java b/java/test/behaviour/connection/ConnectionStepsCommunity.java index 2e0a619de8..ec66a968e7 100644 --- a/java/test/behaviour/connection/ConnectionStepsCommunity.java +++ b/java/test/behaviour/connection/ConnectionStepsCommunity.java @@ -26,7 +26,7 @@ import com.typedb.driver.test.behaviour.config.Parameters; import io.cucumber.java.After; import io.cucumber.java.Before; -import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; import io.cucumber.java.en.When; public class ConnectionStepsCommunity extends ConnectionStepsBase { @@ -45,14 +45,13 @@ public synchronized void after() { super.after(); } - @Override Driver createTypeDBDriver(String address, Credentials credentials, DriverOptions driverOptions) { return TypeDB.driver(address, credentials, driverOptions); } @Override Driver createDefaultTypeDBDriver() { - return createTypeDBDriver(TypeDB.DEFAULT_ADDRESS, DEFAULT_CREDENTIALS, DEFAULT_CONNECTION_SETTINGS); + return createTypeDBDriver(TypeDB.DEFAULT_ADDRESS, DEFAULT_CREDENTIALS, driverOptions); } @When("typedb starts") @@ -67,24 +66,24 @@ public void connection_opens_with_default_authentication() { @When("connection opens with username '{non_semicolon}', password '{non_semicolon}'{may_error}") public void connection_opens_with_username_password(String username, String password, Parameters.MayError mayError) { Credentials credentials = new Credentials(username, password); - mayError.check(() -> driver = createTypeDBDriver(TypeDB.DEFAULT_ADDRESS, credentials, DEFAULT_CONNECTION_SETTINGS)); + mayError.check(() -> driver = createTypeDBDriver(TypeDB.DEFAULT_ADDRESS, credentials, driverOptions)); } @When("connection opens with a wrong host{may_error}") public void connection_opens_with_a_wrong_host(Parameters.MayError mayError) { mayError.check(() -> driver = createTypeDBDriver( - TypeDB.DEFAULT_ADDRESS.replace("localhost", "surely-not-localhost"), + TypeDB.DEFAULT_ADDRESS.replace("127.0.0.1", "surely-not-localhost"), DEFAULT_CREDENTIALS, - DEFAULT_CONNECTION_SETTINGS + driverOptions )); } @When("connection opens with a wrong port{may_error}") public void connection_opens_with_a_wrong_port(Parameters.MayError mayError) { mayError.check(() -> driver = createTypeDBDriver( - TypeDB.DEFAULT_ADDRESS.replace("localhost", "surely-not-localhost"), + TypeDB.DEFAULT_ADDRESS.replace("127.0.0.1", "surely-not-localhost"), DEFAULT_CREDENTIALS, - DEFAULT_CONNECTION_SETTINGS + driverOptions )); } @@ -95,20 +94,62 @@ public void connection_closes() { } @Override - @Given("connection is open: {bool}") + @Then("connection is open: {bool}") public void connection_is_open(boolean isOpen) { super.connection_is_open(isOpen); } @Override - @Given("connection has {integer} database(s)") + @Then("connection contains distribution{may_error}") + public void connection_contains_distribution(Parameters.MayError mayError) { + super.connection_contains_distribution(mayError); + } + + @Override + @Then("connection contains version{may_error}") + public void connection_contains_version(Parameters.MayError mayError) { + super.connection_contains_version(mayError); + } + + @Override + @Then("connection has {integer} replica(s)") + public void connection_has_count_replicas(int count) { + super.connection_has_count_replicas(count); + } + + @Override + @Then("connection contains primary replica") + public void connection_contains_primary_replica() { + super.connection_contains_primary_replica(); + } + + @Override + @Then("connection has {integer} database(s)") public void connection_has_count_databases(int count) { super.connection_has_count_databases(count); } @Override - @Given("connection has {integer} user(s)") + @Then("connection has {integer} user(s)") public void connection_has_count_users(int count) { super.connection_has_count_users(count); } + + @Override + @When("set driver option use_replication to: {bool}") + public void set_driver_option_use_replication_to(boolean value) { + super.set_driver_option_use_replication_to(value); + } + + @Override + @When("set driver option primary_failover_retries to: {integer}") + public void set_driver_option_primary_failover_retries_to(int value) { + super.set_driver_option_primary_failover_retries_to(value); + } + + @Override + @When("set driver option replica_discovery_attempts to: {integer}") + public void set_driver_option_replica_discovery_attempts_to(int value) { + super.set_driver_option_replica_discovery_attempts_to(value); + } } diff --git a/java/test/deployment/src/test/java/application/MavenApplicationTest.java b/java/test/deployment/src/test/java/application/MavenApplicationTest.java index ef1d593989..edac6b35d9 100644 --- a/java/test/deployment/src/test/java/application/MavenApplicationTest.java +++ b/java/test/deployment/src/test/java/application/MavenApplicationTest.java @@ -37,7 +37,7 @@ public void test() { Driver driver = TypeDB.driver( TypeDB.DEFAULT_ADDRESS, new Credentials("admin", "password"), - new DriverOptions(false, null) + new DriverOptions() ); if (driver.databases().contains(DB_NAME)) { driver.databases().get(DB_NAME).delete(); diff --git a/java/test/integration/ExampleTest.java b/java/test/integration/ExampleTest.java index 147cead227..f810635d22 100644 --- a/java/test/integration/ExampleTest.java +++ b/java/test/integration/ExampleTest.java @@ -64,7 +64,7 @@ public class ExampleTest { // EXAMPLE END MARKER @BeforeClass public static void setUpClass() { - Driver typedbDriver = TypeDB.driver(TypeDB.DEFAULT_ADDRESS, new Credentials("admin", "password"), new DriverOptions(false, null)); + Driver typedbDriver = TypeDB.driver(TypeDB.DEFAULT_ADDRESS, new Credentials("admin", "password"), new DriverOptions()); if (typedbDriver.databases().contains("typedb")) { typedbDriver.databases().get("typedb").delete(); } @@ -75,7 +75,7 @@ public static void setUpClass() { // EXAMPLE START MARKER public void example() { // Open a driver connection. Try-with-resources can be used for automatic driver connection management - try (Driver driver = TypeDB.driver(TypeDB.DEFAULT_ADDRESS, new Credentials("admin", "password"), new DriverOptions(false, null))) { + try (Driver driver = TypeDB.driver(TypeDB.DEFAULT_ADDRESS, new Credentials("admin", "password"), new DriverOptions())) { // Create a database driver.databases().create("typedb"); Database database = driver.databases().get("typedb"); diff --git a/java/test/integration/ValueTest.java b/java/test/integration/ValueTest.java index b996084d5a..bb31cb3415 100644 --- a/java/test/integration/ValueTest.java +++ b/java/test/integration/ValueTest.java @@ -63,7 +63,7 @@ public class ValueTest { @BeforeClass public static void setUpClass() { - typedbDriver = TypeDB.driver(ADDRESS, new Credentials("admin", "password"), new DriverOptions(false, null)); + typedbDriver = TypeDB.driver(ADDRESS, new Credentials("admin", "password"), new DriverOptions()); if (typedbDriver.databases().contains(DB_NAME)) typedbDriver.databases().get(DB_NAME).delete(); typedbDriver.databases().create(DB_NAME); } diff --git a/java/user/UserManagerImpl.java b/java/user/UserManagerImpl.java index fc08b63dd3..c9ef3942c7 100644 --- a/java/user/UserManagerImpl.java +++ b/java/user/UserManagerImpl.java @@ -19,6 +19,7 @@ package com.typedb.driver.user; +import com.typedb.driver.api.ConsistencyLevel; import com.typedb.driver.api.user.User; import com.typedb.driver.api.user.UserManager; import com.typedb.driver.common.NativeIterator; @@ -42,31 +43,29 @@ public UserManagerImpl(com.typedb.driver.jni.TypeDBDriver driver) { } @Override - public boolean contains(String username) throws TypeDBDriverException { - Validator.requireNonNull(username, "username"); + public Set all(ConsistencyLevel consistencyLevel) throws TypeDBDriverException { try { - return users_contains(nativeDriver, username); + return new NativeIterator<>(users_all(nativeDriver, ConsistencyLevel.nativeValue(consistencyLevel))).stream().map(user -> new UserImpl(user, this)).collect(Collectors.toSet()); } catch (com.typedb.driver.jni.Error e) { throw new TypeDBDriverException(e); } } @Override - public User get(String username) throws TypeDBDriverException { + public boolean contains(String username, ConsistencyLevel consistencyLevel) throws TypeDBDriverException { Validator.requireNonNull(username, "username"); try { - com.typedb.driver.jni.User user = users_get(nativeDriver, username); - if (user != null) return new UserImpl(user, this); - else return null; + return users_contains(nativeDriver, username, ConsistencyLevel.nativeValue(consistencyLevel)); } catch (com.typedb.driver.jni.Error e) { throw new TypeDBDriverException(e); } } @Override - public User getCurrentUser() throws TypeDBDriverException { - try { // TODO: Make noexcept if we leave it returning just a String - com.typedb.driver.jni.User user = users_get_current_user(nativeDriver); + public User get(String username, ConsistencyLevel consistencyLevel) throws TypeDBDriverException { + Validator.requireNonNull(username, "username"); + try { + com.typedb.driver.jni.User user = users_get(nativeDriver, username, ConsistencyLevel.nativeValue(consistencyLevel)); if (user != null) return new UserImpl(user, this); else return null; } catch (com.typedb.driver.jni.Error e) { @@ -75,9 +74,11 @@ public User getCurrentUser() throws TypeDBDriverException { } @Override - public Set all() throws TypeDBDriverException { + public User getCurrentUser(ConsistencyLevel consistencyLevel) throws TypeDBDriverException { try { - return new NativeIterator<>(users_all(nativeDriver)).stream().map(user -> new UserImpl(user, this)).collect(Collectors.toSet()); + com.typedb.driver.jni.User user = users_get_current_user(nativeDriver, ConsistencyLevel.nativeValue(consistencyLevel)); + if (user != null) return new UserImpl(user, this); + else return null; } catch (com.typedb.driver.jni.Error e) { throw new TypeDBDriverException(e); } diff --git a/nodejs/api/connection/user/UserManager.ts b/nodejs/api/connection/user/UserManager.ts index 761dd1b097..c791f53584 100644 --- a/nodejs/api/connection/user/UserManager.ts +++ b/nodejs/api/connection/user/UserManager.ts @@ -30,7 +30,7 @@ export interface UserManager { * driver.users.contains(username) * ``` * - * @param username - The user name to be checked + * @param username - The username to be checked */ contains(name: string): Promise; diff --git a/python/README.md b/python/README.md index 130c42aaa1..cf8e793785 100644 --- a/python/README.md +++ b/python/README.md @@ -21,10 +21,11 @@ pip3 install typedb-driver ``` 3. Make sure the [TypeDB Server](https://docs.typedb.com/docs/running-typedb/install-and-run#start-the-typedb-server) is running. 4. In your python program, import from `typedb.driver` (see [Example usage](#example-usage) or `tests/integration` for examples): + ```py from typedb.driver import * -driver = TypeDB.driver(address=TypeDB.DEFAULT_ADDRESS, ...) +driver = TypeDB.driver(addresses=TypeDB.DEFAULT_ADDRESS, ...) ``` ## Example usage diff --git a/python/docs_structure.bzl b/python/docs_structure.bzl index 46c574feb7..d3aa04c0ab 100644 --- a/python/docs_structure.bzl +++ b/python/docs_structure.bzl @@ -32,12 +32,18 @@ dir_mapping = { "Concept.adoc": "concept", ".DS_Store": "concept", "TypeDB.adoc": "connection", - "DriverOptions.adoc": "connection", "Credentials.adoc": "connection", + "ConsistencyLevel.adoc": "connection", + "Strong.adoc": "connection", + "Eventual.adoc": "connection", + "ReplicaDependant.adoc": "connection", + "DriverOptions.adoc": "connection", "Driver.adoc": "connection", + "ServerReplica.adoc": "connection", + "ServerVersion.adoc": "connection", + "ReplicaType.adoc": "connection", "User.adoc": "connection", "UserManager.adoc": "connection", -# "Replica.adoc": "connection", "Database.adoc": "connection", "DatabaseManager.adoc": "connection", "Relation.adoc": "data", diff --git a/python/tests/behaviour/background/cluster/environment.py b/python/tests/behaviour/background/cluster/environment.py index 258caecbae..74e4967200 100644 --- a/python/tests/behaviour/background/cluster/environment.py +++ b/python/tests/behaviour/background/cluster/environment.py @@ -15,60 +15,48 @@ # specific language governing permissions and limitations # under the License. +import os + from tests.behaviour.background import environment_base from tests.behaviour.context import Context from typedb.driver import * -# TODO: Reuse code from the community environment when needed, update for multiple clusters -IGNORE_TAGS = ["ignore", "ignore-typedb-driver", "ignore-typedb-driver-python"] - def before_all(context: Context): environment_base.before_all(context) - # context.credentials_root_ca_path = os.environ["ROOT_CA"] # TODO: test root ca with cluster - context.create_driver_fn = lambda host="localhost", port=None, user=None, password=None: \ - create_driver(context, host, port, user, password) - context.setup_context_driver_fn = lambda host="localhost", port=None, username=None, password=None: \ - setup_context_driver(context, host, port, username, password) + context.tls_root_ca_path = os.environ["ROOT_CA"] + context.create_driver_fn = lambda address=None, user=None, password=None: \ + create_driver(context, address, user, password) + context.setup_context_driver_fn = lambda address=None, username=None, password=None: \ + setup_context_driver(context, address, username, password) + # TODO: Add 2 more addresses (or 1?) + context.default_address = ["https://127.0.0.1:11729"] def before_scenario(context: Context, scenario): - for tag in IGNORE_TAGS: - if tag in scenario.effective_tags: - scenario.skip("tagged with @" + tag) - return - environment_base.before_scenario(context) + environment_base.before_scenario(context, scenario) + context.driver_options.is_tls_enabled = True + context.driver_options.tls_root_ca_path = context.tls_root_ca_path -def setup_context_driver(context, host="localhost", port=None, username=None, password=None): - # TODO: Use root_ca_path in connection - context.driver = create_driver(context, host, port, username, password) +def setup_context_driver(context, address=None, username=None, password=None): + context.driver = create_driver(context, address, username, password) -def create_driver(context, host="localhost", port=None, username=None, password=None) -> Driver: - if port is None: - port = int(context.config.userdata["port"]) +def create_driver(context, address=None, username=None, password=None) -> Driver: + if address is None: + address = context.default_address if username is None: - username = "admin" + username = context.DEFAULT_USERNAME if password is None: - password = "password" + password = context.DEFAULT_PASSWORD credentials = Credentials(username, password) - return TypeDB.driver(address=f"{host}:{port}", credentials=credentials, driver_options=DriverOptions()) + return TypeDB.driver(addresses=address, credentials=credentials, driver_options=context.driver_options) def after_scenario(context: Context, scenario): environment_base.after_scenario(context, scenario) - # TODO: reset the database through the TypeDB runner once it exists - context.setup_context_driver_fn() - for database in context.driver.databases.all(): - database.delete() - for user in context.driver.users.all(): - if user.name == "admin": - continue - context.driver.users.get(user.name).delete() - context.driver.close() - def after_all(context: Context): environment_base.after_all(context) diff --git a/python/tests/behaviour/background/community/environment.py b/python/tests/behaviour/background/community/environment.py index d1c3ff838c..0ebbfcd172 100644 --- a/python/tests/behaviour/background/community/environment.py +++ b/python/tests/behaviour/background/community/environment.py @@ -19,53 +19,38 @@ from tests.behaviour.context import Context from typedb.driver import * -IGNORE_TAGS = ["ignore", "ignore-typedb-driver", "ignore-typedb-driver-python"] - def before_all(context: Context): environment_base.before_all(context) - context.create_driver_fn = lambda host="localhost", port=None, user=None, password=None: \ - create_driver(context, host, port, user, password) - context.setup_context_driver_fn = lambda host="localhost", port=None, username=None, password=None: \ - setup_context_driver(context, host, port, username, password) + context.create_driver_fn = lambda address=None, user=None, password=None: \ + create_driver(context, address, user, password) + context.setup_context_driver_fn = lambda address=None, username=None, password=None: \ + setup_context_driver(context, address, username, password) + context.default_address = TypeDB.DEFAULT_ADDRESS def before_scenario(context: Context, scenario): - for tag in IGNORE_TAGS: - if tag in scenario.effective_tags: - scenario.skip("tagged with @" + tag) - return - environment_base.before_scenario(context) + environment_base.before_scenario(context, scenario) -def setup_context_driver(context, host="localhost", port=None, username=None, password=None): - context.driver = create_driver(context, host, port, username, password) +def setup_context_driver(context, address=None, username=None, password=None): + context.driver = create_driver(context, address, username, password) -def create_driver(context, host="localhost", port=None, username=None, password=None) -> Driver: - if port is None: - port = int(context.config.userdata["port"]) +def create_driver(context, address=None, username=None, password=None) -> Driver: + if address is None: + address = context.default_address if username is None: - username = "admin" + username = context.DEFAULT_USERNAME if password is None: - password = "password" + password = context.DEFAULT_PASSWORD credentials = Credentials(username, password) - return TypeDB.driver(address=f"{host}:{port}", credentials=credentials, driver_options=DriverOptions()) + return TypeDB.driver(addresses=address, credentials=credentials, driver_options=context.driver_options) def after_scenario(context: Context, scenario): environment_base.after_scenario(context, scenario) - # TODO: reset the database through the TypeDB runner once it exists - context.setup_context_driver_fn() - for database in context.driver.databases.all(): - database.delete() - for user in context.driver.users.all(): - if user.name == "admin": - continue - context.driver.users.get(user.name).delete() - context.driver.close() - def after_all(context: Context): environment_base.after_all(context) diff --git a/python/tests/behaviour/background/environment_base.py b/python/tests/behaviour/background/environment_base.py index ae45bda82b..b23b50afaa 100644 --- a/python/tests/behaviour/background/environment_base.py +++ b/python/tests/behaviour/background/environment_base.py @@ -24,14 +24,24 @@ from tests.behaviour.util.util import delete_dir from typedb.driver import * +IGNORE_TAGS = ["ignore", "ignore-typedb-driver", "ignore-typedb-driver-python"] def before_all(context: Context): context.THREAD_POOL_SIZE = 32 + context.DEFAULT_USERNAME = "admin" + context.DEFAULT_PASSWORD = "password" context.init_transaction_options_if_needed_fn = lambda: _init_transaction_options_if_needed(context) context.init_query_options_if_needed_fn = lambda: _init_query_options_if_needed(context) + context.full_path = lambda file_name: _full_path(context, file_name) + context.tx = lambda: next(iter(context.transactions), None) + context.clear_answers = lambda: _clear_answers(context) + context.clear_concurrent_answers = lambda: _clear_concurrent_answers(context) -def before_scenario(context: Context): +def before_scenario(context: Context, scenario): + if ignored(scenario): + return + # setup context state context.temp_dir = None context.background_driver = None @@ -43,13 +53,17 @@ def before_scenario(context: Context): context.collected_answer = None # [ConceptRow] / [dict] context.concurrent_answers = None context.unwrapped_concurrent_answers = None - # setup context functions - context.full_path = lambda file_name: _full_path(context, file_name) - context.tx = lambda: next(iter(context.transactions), None) - context.clear_answers = lambda: _clear_answers(context) - context.clear_concurrent_answers = lambda: _clear_concurrent_answers(context) context.transaction_options = None context.query_options = None + context.driver_options = DriverOptions() + + +def ignored(scenario): + for tag in IGNORE_TAGS: + if tag in scenario.effective_tags: + scenario.skip("tagged with @" + tag) + return True + return False def after_scenario(context: Context, scenario): @@ -62,6 +76,14 @@ def after_scenario(context: Context, scenario): if context.background_driver and context.background_driver.is_open(): context.background_driver.close() + context.setup_context_driver_fn() + for database in context.driver.databases.all(): + database.delete() + for user in context.driver.users.all(): + if user.name != "admin": + context.driver.users.get(user.name).delete() + context.driver.close() + def after_all(_: Context): pass diff --git a/python/tests/behaviour/connection/connection_steps.py b/python/tests/behaviour/connection/connection_steps.py index 28bfa66e30..12aa3261bb 100644 --- a/python/tests/behaviour/connection/connection_steps.py +++ b/python/tests/behaviour/connection/connection_steps.py @@ -16,9 +16,19 @@ # under the License. from behave import * -from tests.behaviour.config.parameters import MayError +from hamcrest import * +from tests.behaviour.config.parameters import MayError, check_is_none from tests.behaviour.context import Context +def replace_host(address: str, new_host: str) -> str: + return address.replace("127.0.0.1", new_host) + + +def replace_port(address: str, new_port: str) -> str: + address_parts = address.rsplit(":", 1) + address_parts[-1] = new_port + return "".join(address_parts) + @step(u'typedb has configuration') def step_impl(context: Context): @@ -39,12 +49,26 @@ def step_impl(context: Context): @step(u'connection opens with a wrong host{may_error:MayError}') def step_impl(context: Context, may_error: MayError): - may_error.check(lambda: context.setup_context_driver_fn(host="surely-not-localhost")) + address = context.default_address + if isinstance(address, str): + address = replace_host(address, "surely-not-localhost") + elif isinstance(address, list): + address = [replace_host(addr, "surely-not-localhost") for addr in address] + else: + raise Exception("Unexpected default address: cannot finish the test") + may_error.check(lambda: context.setup_context_driver_fn(address=address)) @step(u'connection opens with a wrong port{may_error:MayError}') def step_impl(context: Context, may_error: MayError): - may_error.check(lambda: context.setup_context_driver_fn(port=0)) + address = context.default_address + if isinstance(address, str): + address = replace_port(address, "0") + elif isinstance(address, list): + address = [replace_port(addr, "0") for addr in address] + else: + raise Exception("Unexpected default address: cannot finish the test") + may_error.check(lambda: context.setup_context_driver_fn(address=address)) @step( @@ -67,16 +91,52 @@ def step_impl(context: Context): @step("connection is open: {is_open:Bool}") def step_impl(context: Context, is_open: bool): real_is_open = hasattr(context, 'driver') and context.driver and context.driver.is_open() - assert is_open == real_is_open + assert_that(real_is_open, equal_to(is_open)) + + +@step(u'connection contains distribution{may_error:MayError}') +def step_impl(context: Context, may_error: MayError): + may_error.check(lambda: assert_that(len(context.driver.server_version().distribution), greater_than(0))) + + +@step(u'connection contains version{may_error:MayError}') +def step_impl(context: Context, may_error: MayError): + may_error.check(lambda: assert_that(len(context.driver.server_version().version), greater_than(0))) + + +@step("connection has {count:Int} replica") +@step("connection has {count:Int} replicas") +def step_impl(context: Context, count: int): + assert_that(len(context.driver.replicas()), equal_to(count)) + + +@step(u'connection contains primary replica') +def step_impl(context: Context): + check_is_none(context.driver.primary_replica(), False) @step("connection has {count:Int} database") @step("connection has {count:Int} databases") def step_impl(context: Context, count: int): - assert len(context.driver.databases.all()) == count + assert_that(len(context.driver.databases.all()), equal_to(count)) @step("connection has {count:Int} user") @step("connection has {count:Int} users") def step_impl(context: Context, count: int): - assert len(context.driver.users.all()) == count + assert_that(len(context.driver.users.all()), equal_to(count)) + + +@step("set driver option use_replication to: {value:Bool}") +def step_impl(context: Context, value: bool): + context.driver_options.use_replication = value + + +@step("set driver option primary_failover_retries to: {value:Int}") +def step_impl(context: Context, value: int): + context.driver_options.primary_failover_retries = value + + +@step("set driver option replica_discovery_attempts to: {value:Int}") +def step_impl(context: Context, value: int): + context.driver_options.replica_discovery_attempts = value diff --git a/python/typedb/api/connection/consistency_level.py b/python/typedb/api/connection/consistency_level.py new file mode 100644 index 0000000000..ff34af2308 --- /dev/null +++ b/python/typedb/api/connection/consistency_level.py @@ -0,0 +1,111 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from abc import ABC, abstractmethod +from typing import Optional, Union + +from typedb.common.exception import TypeDBDriverException, UNEXPECTED_NATIVE_VALUE +from typedb.native_driver_wrapper import consistency_level_strong, consistency_level_eventual, \ + consistency_level_replica_dependant, ConsistencyLevel as NativeConsistencyLevel, \ + Strong as NativeStrong, Eventual as NativeEventual, ReplicaDependant as NativeReplicaDependant + + +class ConsistencyLevel(ABC): + """ + Consistency levels of operations against a distributed server. All driver methods have default + recommended values, however, readonly operations can be configured in order to potentially + speed up the execution (introducing risks of stale data) or test a specific replica. + This setting does not affect clusters with a single node. + """ + + @abstractmethod + def native_object(self): + pass + + @staticmethod + def of(native_value: NativeConsistencyLevel) -> Union[None, "Strong", "Eventual", "ReplicaDependant"]: + if native_value is None: + return None + + if native_value.tag == NativeStrong: + return ConsistencyLevel.Strong() + elif native_value.tag == NativeEventual: + return ConsistencyLevel.Eventual() + elif native_value.tag == NativeReplicaDependant: + return ConsistencyLevel.ReplicaDependant(native_value.address) + else: + raise TypeDBDriverException(UNEXPECTED_NATIVE_VALUE) + + @staticmethod + def native_value(consistency_level: Optional["ConsistencyLevel"]) -> NativeConsistencyLevel: + return None if consistency_level is None else consistency_level.native_object() + + def is_strong(self) -> bool: + return isinstance(self, ConsistencyLevel.Strong) + + def is_eventual(self) -> bool: + return isinstance(self, ConsistencyLevel.Eventual) + + def is_replica_dependant(self) -> bool: + return isinstance(self, ConsistencyLevel.ReplicaDependant) + + class Strong: + """ + The strongest consistency level. Always up-to-date due to the guarantee of the primary replica usage. + May require more time for operation execution. + """ + + def native_object(self): + return consistency_level_strong() + + def __str__(self): + return "Strong" + + class Eventual: + """ + Eventual consistency level. Allow stale reads from any replica. May not reflect latest writes. + The execution may be eventually faster compared to other consistency levels. + """ + + def native_object(self): + return consistency_level_eventual() + + def __str__(self): + return "Eventual" + + class ReplicaDependant: + """ + Replica dependant consistency level. The operation is executed against the provided replica address only. + Its guarantees depend on the replica selected. + """ + + def __init__(self, address: str): + self._address = address + + def native_object(self): + return consistency_level_replica_dependant(self._address) + + """ + Retrieves the address of the replica this consistency level depends on. + """ + + @property + def address(self) -> str: + return self._address + + def __str__(self): + return f"ReplicaDependant({self._address})" diff --git a/python/typedb/api/connection/database.py b/python/typedb/api/connection/database.py deleted file mode 100644 index a04d907e5f..0000000000 --- a/python/typedb/api/connection/database.py +++ /dev/null @@ -1,285 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -from __future__ import annotations - -from abc import ABC, abstractmethod -from typing import List - - -class Database(ABC): - - @property - @abstractmethod - def name(self) -> str: - """ - The database name as a string. - """ - pass - - @abstractmethod - def schema(self) -> str: - """ - Returns a full schema text as a valid TypeQL define query string. - - :return: - - Examples: - --------- - :: - - database.schema() - """ - pass - - def type_schema(self) -> str: - """ - Returns the types in the schema as a valid TypeQL define query string. - - :return: - - Examples: - --------- - :: - - database.type_schema() - """ - pass - - def export_to_file(self, schema_file_path: str, data_file_path: str) -> None: - """ - Export a database into a schema definition and a data files saved to the disk. - This is a blocking operation and may take a significant amount of time depending on the database size. - - :param schema_file_path: The path to the schema definition file to be created - :param data_file_path: The path to the data file to be created - :return: - - Examples: - --------- - :: - - database.export_to_file("schema.typeql", "data.typedb") - """ - pass - - @abstractmethod - def delete(self) -> None: - """ - Deletes this database. - - :return: - - Examples: - --------- - :: - - database.delete() - """ - pass - - # @abstractmethod - # def replicas(self) -> Set[Replica]: - # """ - # Set of ``Replica`` instances for this database. - # *Only works in TypeDB Cloud* - # - # :return: - # - # Examples: - # --------- - # :: - # - # database.replicas() - # """ - # pass - # - # @abstractmethod - # def primary_replica(self) -> Optional[Replica]: - # """ - # Returns the primary replica for this database. - # *Only works in TypeDB Cloud* - # - # :return: - # - # Examples: - # --------- - # :: - # - # database.primary_replica() - # """ - # pass - # - # @abstractmethod - # def preferred_replica(self) -> Optional[Replica]: - # """ - # Returns the preferred replica for this database. - # Operations which can be run on any replica will prefer to use this replica. - # *Only works in TypeDB Cloud* - # - # :return: - # - # Examples: - # --------- - # :: - # - # database.preferred_replica() - # """ - # pass - - -# -# -# class Replica(ABC): -# """ -# The metadata and state of an individual raft replica of a database. -# """ -# -# @abstractmethod -# def database(self) -> Database: -# """ -# Retrieves the database for which this is a replica -# -# :return: -# """ -# pass -# -# @abstractmethod -# def server(self) -> str: -# """ -# The server hosting this replica -# -# :return: -# """ -# pass -# -# @abstractmethod -# def is_primary(self) -> bool: -# """ -# Checks whether this is the primary replica of the raft cluster. -# -# :return: -# """ -# -# pass -# -# @abstractmethod -# def is_preferred(self) -> bool: -# """ -# Checks whether this is the preferred replica of the raft cluster. -# If true, Operations which can be run on any replica will prefer to use this replica. -# -# :return: -# """ -# pass -# -# @abstractmethod -# def term(self) -> int: -# """ -# The raft protocol 'term' of this replica. -# -# :return: -# """ -# pass - - -class DatabaseManager(ABC): - """ - Provides access to all database management methods. - """ - - @abstractmethod - def get(self, name: str) -> Database: - """ - Retrieve the database with the given name. - - :param name: The name of the database to retrieve - :return: - - Examples: - --------- - :: - - driver.databases.get(name) - """ - pass - - @abstractmethod - def contains(self, name: str) -> bool: - """ - Checks if a database with the given name exists. - - :param name: The database name to be checked - :return: - - Examples: - --------- - :: - - driver.databases.contains(name) - """ - pass - - @abstractmethod - def create(self, name: str) -> None: - """ - Create a database with the given name. - - :param name: The name of the database to be created - :return: - - Examples: - --------- - :: - - driver.databases.create(name) - """ - pass - - @abstractmethod - def import_from_file(self, name: str, schema: str, data_file_path: str) -> None: - """ - Create a database with the given name based on previously exported another database's data loaded from a file. - This is a blocking operation and may take a significant amount of time depending on the database size. - - :param name: The name of the database to be created - :param schema: The schema definition query string for the database - :param data_file_path: The exported database file path to import the data from - :return: - - Examples: - --------- - :: - - driver.databases.import_from_file(name, schema, "data.typedb") - """ - pass - - @abstractmethod - def all(self) -> List[Database]: - """ - Retrieves all databases present on the TypeDB server. - - :return: - - Examples: - --------- - :: - - driver.databases.all() - """ - pass diff --git a/python/typedb/api/connection/driver.py b/python/typedb/api/connection/driver.py index 34891d1173..af35796909 100644 --- a/python/typedb/api/connection/driver.py +++ b/python/typedb/api/connection/driver.py @@ -18,13 +18,16 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, Set, Mapping if TYPE_CHECKING: - from typedb.api.connection.database import DatabaseManager + from typedb.api.connection.consistency_level import ConsistencyLevel + from typedb.api.database.database_manager import DatabaseManager from typedb.api.connection.transaction_options import TransactionOptions from typedb.api.connection.transaction import Transaction, TransactionType - from typedb.api.user.user import UserManager + from typedb.api.user.user_manager import UserManager + from typedb.api.server.server_replica import ServerReplica + from typedb.api.server.server_version import ServerVersion class Driver(ABC): @@ -53,6 +56,31 @@ def databases(self) -> DatabaseManager: """ pass + @property + @abstractmethod + def users(self) -> UserManager: + """ + The ``UserManager`` for this connection, providing access to user management methods. + """ + pass + + @abstractmethod + def server_version(self, consistency_level: Optional[ConsistencyLevel] = None) -> ServerVersion: + """ + Retrieves the server's version. + + :param consistency_level: The consistency level to use for the operation. Strongest possible by default + :return: + + Examples: + --------- + :: + + driver.server_version() + driver.server_version(ConsistencyLevel.Strong()) + """ + pass + @abstractmethod def transaction(self, database_name: str, transaction_type: TransactionType, options: Optional[TransactionOptions] = None) -> Transaction: @@ -73,9 +101,9 @@ def transaction(self, database_name: str, transaction_type: TransactionType, pass @abstractmethod - def close(self) -> None: + def replicas(self) -> Set[ServerReplica]: """ - Closes the driver. Before instantiating a new driver, the driver that’s currently open should first be closed. + Set of ``Replica`` instances for this driver connection. :return: @@ -83,16 +111,91 @@ def close(self) -> None: --------- :: - driver.close() + driver.replicas() """ pass - @property @abstractmethod - def users(self) -> UserManager: + def primary_replica(self) -> Optional[ServerReplica]: + """ + Returns the primary replica for this driver connection. + + :return: + + Examples: + --------- + :: + + driver.primary_replica() + """ + pass + + @abstractmethod + def register_replica(self, replica_id: int, address: str) -> None: + """ + Registers a new replica in the cluster the driver is currently connected to. The registered + replica will become available eventually, depending on the behavior of the whole cluster. + To register a replica, its clustering address should be passed, not the connection address. + + :param replica_id: The numeric identifier of the new replica + :param address: The address(es) of the TypeDB replica as a string + :return: + + Examples: + --------- + :: + + driver.register_replica(2, "127.0.0.1:11729") """ - The ``UserManager`` instance for this connection, providing access to user management methods. - Only for TypeDB Cloud. + pass + + @abstractmethod + def deregister_replica(self, replica_id: int) -> None: + """ + Deregisters a replica from the cluster the driver is currently connected to. This replica + will no longer play a raft role in this cluster. + + :param replica_id: The numeric identifier of the deregistered replica + :return: + + Examples: + --------- + :: + + driver.deregister_replica(2) + """ + pass + + @abstractmethod + def update_address_translation(self, address_translation: Mapping[str, str]) -> None: + """ + Updates address translation of the driver. This lets you actualize new translation + information without recreating the driver from scratch. Useful after registering new + replicas requiring address translation. + + :param address_translation: The translation of public TypeDB cluster replica addresses (keys) to server-side private addresses (values) + :return: + + Examples: + --------- + :: + + driver.update_address_translation({"typedb-cloud.ext:11729": "127.0.0.1:11729"}) + """ + pass + + @abstractmethod + def close(self) -> None: + """ + Closes the driver. Before instantiating a new driver, the driver that’s currently open should first be closed. + + :return: + + Examples: + --------- + :: + + driver.close() """ pass diff --git a/python/typedb/api/connection/driver_options.py b/python/typedb/api/connection/driver_options.py index 9cd92c09df..5a55a308ff 100644 --- a/python/typedb/api/connection/driver_options.py +++ b/python/typedb/api/connection/driver_options.py @@ -19,25 +19,123 @@ from typedb.common.exception import TypeDBDriverException, ILLEGAL_STATE from typedb.common.native_wrapper import NativeWrapper -from typedb.native_driver_wrapper import driver_options_new, DriverOptions as NativeDriverOptions +from typedb.common.validation import require_non_negative +from typedb.native_driver_wrapper import driver_options_get_is_tls_enabled, driver_options_get_tls_root_ca_path, \ + driver_options_has_tls_root_ca_path, driver_options_new, driver_options_set_is_tls_enabled, \ + driver_options_set_tls_root_ca_path, driver_options_get_use_replication, driver_options_set_use_replication, \ + driver_options_get_primary_failover_retries, driver_options_set_primary_failover_retries, \ + driver_options_get_replica_discovery_attempts, driver_options_set_replica_discovery_attempts, \ + driver_options_has_replica_discovery_attempts, DriverOptions as NativeDriverOptions class DriverOptions(NativeWrapper[NativeDriverOptions]): """ - User credentials and TLS encryption settings for connecting to TypeDB Server. Arguments: - 1) is_tls_enabled: Specify whether the connection to TypeDB Cloud must be done over TLS. - 2) tls_root_ca_path: Path to the CA certificate to use for authenticating server certificates. + TypeDB driver options. ``DriverOptions`` are used to specify the driver's connection behavior. + + Options could be specified either as constructor arguments or using + properties assignment. Examples: -------- :: - driver_options = DriverOptions(tls_enabled=True, tls_root_ca_path="path/to/ca-certificate.pem") + driver_options = DriverOptions(tls_enabled=True) + driver_options.tls_root_ca_path = "path/to/ca-certificate.pem" """ - def __init__(self, is_tls_enabled: bool = False, tls_root_ca_path: Optional[str] = None): - super().__init__(driver_options_new(is_tls_enabled, tls_root_ca_path)) + def __init__(self, *, + is_tls_enabled: Optional[bool] = None, + tls_root_ca_path: Optional[str] = None, + use_replication: Optional[bool] = None, + primary_failover_retries: Optional[int] = None, + replica_discovery_attempts: Optional[int] = None, + ): + super().__init__(driver_options_new()) + if is_tls_enabled is not None: + self.is_tls_enabled = is_tls_enabled + if tls_root_ca_path is not None: + self.tls_root_ca_path = tls_root_ca_path + if use_replication is not None: + self.use_replication = use_replication + if primary_failover_retries is not None: + self.primary_failover_retries = primary_failover_retries + if replica_discovery_attempts is not None: + self.replica_discovery_attempts = replica_discovery_attempts @property def _native_object_not_owned_exception(self) -> TypeDBDriverException: return TypeDBDriverException(ILLEGAL_STATE) + + @property + def is_tls_enabled(self) -> bool: + """ + Returns the value set for the TLS flag in this ``DriverOptions`` object. + Specifies whether the connection to TypeDB must be done over TLS. + """ + return driver_options_get_is_tls_enabled(self.native_object) + + @is_tls_enabled.setter + def is_tls_enabled(self, is_tls_enabled: bool): + driver_options_set_is_tls_enabled(self.native_object, is_tls_enabled) + + @property + def tls_root_ca_path(self) -> Optional[str]: + """ + Returns the TLS root CA set in this ``DriverOptions`` object. + Specifies the root CA used in the TLS config for server certificates authentication. + Uses system roots if None is set. + """ + return driver_options_get_tls_root_ca_path(self.native_object) \ + if driver_options_has_tls_root_ca_path(self.native_object) else None + + @tls_root_ca_path.setter + def tls_root_ca_path(self, tls_root_ca_path: Optional[str]): + driver_options_set_tls_root_ca_path(self.native_object, tls_root_ca_path) + + @property + def use_replication(self) -> bool: + """ + Returns the value set for the replication usage flag in this ``DriverOptions`` object. + Specifies whether the connection to TypeDB can use cluster replicas provided by the server + or it should be limited to a single configured address. + """ + return driver_options_get_use_replication(self.native_object) + + @use_replication.setter + def use_replication(self, use_replication: bool): + driver_options_set_use_replication(self.native_object, use_replication) + + @property + def primary_failover_retries(self) -> int: + """ + Returns the value set for the primary failover retries limit in this ``DriverOptions`` object. + Limits the number of attempts to redirect a strongly consistent request to another + primary replica in case of a failure due to the change of replica roles. + """ + return driver_options_get_primary_failover_retries(self.native_object) + + @primary_failover_retries.setter + def primary_failover_retries(self, primary_failover_retries: int): + require_non_negative(primary_failover_retries, "primary_failover_retries") + driver_options_set_primary_failover_retries(self.native_object, primary_failover_retries) + + @property + def replica_discovery_attempts(self) -> Optional[int]: + """ + Returns the value set for the replica discovery attempts limit in this ``DriverOptions`` object. + Limits the number of driver attempts to discover a single working replica to perform an + operation in case of a replica unavailability. Every replica is tested once, which means + that at most: + - {limit} operations are performed if the limit <= the number of replicas. + - {number of replicas} operations are performed if the limit > the number of replicas. + - {number of replicas} operations are performed if the limit is None. + Affects every eventually consistent operation, including redirect failover, when the new + primary replica is unknown. If not set, the maximum (practically unlimited) value is used. + """ + return driver_options_get_replica_discovery_attempts(self.native_object) \ + if driver_options_has_replica_discovery_attempts(self.native_object) else None + + @replica_discovery_attempts.setter + def replica_discovery_attempts(self, replica_discovery_attempts: int): + require_non_negative(replica_discovery_attempts, "replica_discovery_attempts") + driver_options_set_replica_discovery_attempts(self.native_object, replica_discovery_attempts) diff --git a/python/typedb/api/connection/query_options.py b/python/typedb/api/connection/query_options.py index e1c0c384ec..c4d14be519 100644 --- a/python/typedb/api/connection/query_options.py +++ b/python/typedb/api/connection/query_options.py @@ -30,7 +30,7 @@ class QueryOptions(NativeWrapper[NativeOptions]): """ - TypeDB transaction options. ``QueryOptions`` object can be used to override the default server behaviour + TypeDB query options. ``QueryOptions`` object can be used to override the default server behaviour for executed queries. Options could be specified either as constructor arguments or using diff --git a/python/typedb/api/connection/transaction_options.py b/python/typedb/api/connection/transaction_options.py index 519095e54b..8428b93f8b 100644 --- a/python/typedb/api/connection/transaction_options.py +++ b/python/typedb/api/connection/transaction_options.py @@ -19,6 +19,7 @@ from typing import Optional +from typedb.api.connection.consistency_level import ConsistencyLevel from typedb.common.exception import TypeDBDriverException, ILLEGAL_STATE from typedb.common.native_wrapper import NativeWrapper from typedb.common.validation import require_positive @@ -26,7 +27,9 @@ transaction_options_has_transaction_timeout_millis, transaction_options_get_transaction_timeout_millis, \ transaction_options_set_transaction_timeout_millis, transaction_options_get_schema_lock_acquire_timeout_millis, \ transaction_options_has_schema_lock_acquire_timeout_millis, \ - transaction_options_set_schema_lock_acquire_timeout_millis, TransactionOptions as NativeOptions + transaction_options_set_schema_lock_acquire_timeout_millis, transaction_options_get_read_consistency_level, \ + transaction_options_has_read_consistency_level, transaction_options_set_read_consistency_level, \ + TransactionOptions as NativeOptions class TransactionOptions(NativeWrapper[NativeOptions]): @@ -49,12 +52,15 @@ class TransactionOptions(NativeWrapper[NativeOptions]): def __init__(self, *, transaction_timeout_millis: Optional[int] = None, schema_lock_acquire_timeout_millis: Optional[int] = None, + read_consistency_level: Optional[ConsistencyLevel] = None, ): super().__init__(transaction_options_new()) if transaction_timeout_millis is not None: self.transaction_timeout_millis = transaction_timeout_millis if schema_lock_acquire_timeout_millis is not None: self.schema_lock_acquire_timeout_millis = schema_lock_acquire_timeout_millis + if read_consistency_level is not None: + self.read_consistency_level = read_consistency_level @property def _native_object_not_owned_exception(self) -> TypeDBDriverException: @@ -88,3 +94,16 @@ def schema_lock_acquire_timeout_millis(self, schema_lock_acquire_timeout_millis: require_positive(schema_lock_acquire_timeout_millis, "schema_lock_acquire_timeout_millis") transaction_options_set_schema_lock_acquire_timeout_millis(self.native_object, schema_lock_acquire_timeout_millis) + + @property + def read_consistency_level(self) -> Optional[ConsistencyLevel]: + """ + If set, specifies the requested consistency level of the transaction opening operation. + Affects only read transactions, as write and schema transactions require primary replicas. + """ + return ConsistencyLevel.of(transaction_options_get_read_consistency_level(self.native_object)) \ + if transaction_options_has_read_consistency_level(self.native_object) else None + + @read_consistency_level.setter + def read_consistency_level(self, read_consistency_level: ConsistencyLevel): + transaction_options_set_read_consistency_level(self.native_object, read_consistency_level.native_object()) diff --git a/python/typedb/api/database/database.py b/python/typedb/api/database/database.py new file mode 100644 index 0000000000..60dd4caede --- /dev/null +++ b/python/typedb/api/database/database.py @@ -0,0 +1,102 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from __future__ import annotations + +from abc import ABC, abstractmethod +from typedb.api.connection.consistency_level import ConsistencyLevel +from typing import Optional + + +class Database(ABC): + + @property + @abstractmethod + def name(self) -> str: + """ + The database name as a string. + """ + pass + + @abstractmethod + def schema(self, consistency_level: Optional[ConsistencyLevel] = None) -> str: + """ + Returns a full schema text as a valid TypeQL define query string. + + :param consistency_level: The consistency level to use for the operation. Strongest possible by default + :return: + + Examples: + --------- + :: + + database.schema() + database.schema(ConsistencyLevel.Strong()) + """ + pass + + @abstractmethod + def type_schema(self, consistency_level: Optional[ConsistencyLevel] = None) -> str: + """ + Returns the types in the schema as a valid TypeQL define query string. + + :param consistency_level: The consistency level to use for the operation. Strongest possible by default + :return: + + Examples: + --------- + :: + + database.type_schema() + database.type_schema(ConsistencyLevel.Strong()) + """ + pass + + def export_to_file(self, schema_file_path: str, data_file_path: str, + consistency_level: Optional[ConsistencyLevel] = None) -> None: + """ + Export a database into a schema definition and a data files saved to the disk. + This is a blocking operation and may take a significant amount of time depending on the database size. + + :param schema_file_path: The path to the schema definition file to be created + :param data_file_path: The path to the data file to be created + :param consistency_level: The consistency level to use for the operation. Strongest possible by default + :return: + + Examples: + --------- + :: + + database.export_to_file("schema.typeql", "data.typedb") + database.export_to_file("schema.typeql", "data.typedb", ConsistencyLevel.Strong()) + """ + pass + + @abstractmethod + def delete(self) -> None: + """ + Deletes this database. + + :return: + + Examples: + --------- + :: + + database.delete() + """ + pass diff --git a/python/typedb/api/database/database_manager.py b/python/typedb/api/database/database_manager.py new file mode 100644 index 0000000000..ee42a3325f --- /dev/null +++ b/python/typedb/api/database/database_manager.py @@ -0,0 +1,118 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, List, Optional + +if TYPE_CHECKING: + from typedb.api.connection.consistency_level import ConsistencyLevel + + +class DatabaseManager(ABC): + """ + Provides access to all database management methods. + """ + + @abstractmethod + def all(self, consistency_level: Optional[ConsistencyLevel] = None) -> List[Database]: + """ + Retrieves all databases present on the TypeDB server. + + :param consistency_level: The consistency level to use for the operation. Strongest possible by default + :return: + + Examples: + --------- + :: + + driver.databases.all() + driver.databases.all(ConsistencyLevel.Strong()) + """ + pass + + @abstractmethod + def contains(self, name: str, consistency_level: Optional[ConsistencyLevel] = None) -> bool: + """ + Checks if a database with the given name exists. + + :param name: The database name to be checked + :param consistency_level: The consistency level to use for the operation. Strongest possible by default + :return: + + Examples: + --------- + :: + + driver.databases.contains(name) + driver.databases.contains(name, ConsistencyLevel.Strong()) + """ + pass + + @abstractmethod + def get(self, name: str, consistency_level: Optional[ConsistencyLevel] = None) -> Database: + """ + Retrieves the database with the given name. + + :param name: The name of the database to retrieve + :param consistency_level: The consistency level to use for the operation. Strongest possible by default + :return: + + Examples: + --------- + :: + + driver.databases.get(name) + driver.databases.get(name, ConsistencyLevel.Strong()) + """ + pass + + @abstractmethod + def create(self, name: str) -> None: + """ + Creates a database with the given name. + + :param name: The name of the database to be created + :return: + + Examples: + --------- + :: + + driver.databases.create(name) + """ + pass + + @abstractmethod + def import_from_file(self, name: str, schema: str, data_file_path: str) -> None: + """ + Creates a database with the given name based on previously exported another database's data loaded from a file. + This is a blocking operation and may take a significant amount of time depending on the database size. + + :param name: The name of the database to be created + :param schema: The schema definition query string for the database + :param data_file_path: The exported database file path to import the data from + :return: + + Examples: + --------- + :: + + driver.databases.import_from_file(name, schema, "data.typedb") + """ + pass diff --git a/python/typedb/api/server/replica_type.py b/python/typedb/api/server/replica_type.py new file mode 100644 index 0000000000..8544f27035 --- /dev/null +++ b/python/typedb/api/server/replica_type.py @@ -0,0 +1,40 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from __future__ import annotations + +import enum + + +class ReplicaType(enum.Enum): + """ + This class is used to specify the type of replica. + + Examples + -------- + :: + + server_replica.replica_type + """ + PRIMARY = 0 + SECONDARY = 1 + + def is_primary(self) -> bool: + return self is ReplicaType.PRIMARY + + def is_secondary(self) -> bool: + return self is ReplicaType.SECONDARY diff --git a/python/typedb/api/server/server_replica.py b/python/typedb/api/server/server_replica.py new file mode 100644 index 0000000000..6f7411297c --- /dev/null +++ b/python/typedb/api/server/server_replica.py @@ -0,0 +1,110 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + + +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typedb.api.server.replica_type import ReplicaType + + +class ServerReplica(ABC): + """ + The metadata and state of an individual raft replica of a driver connection. + """ + + @property + @abstractmethod + def id(self) -> int: + """ + Returns the id of this replica. + + :return: + + Examples + -------- + :: + + server_replica.id + """ + pass + + @property + @abstractmethod + def address(self) -> str: + """ + Returns the address this replica is hosted at. + + :return: + + Examples + -------- + :: + + server_replica.address + """ + pass + + @property + @abstractmethod + def replica_type(self) -> ReplicaType: + """ + Returns whether this is the primary replica of the raft cluster or any of the supporting types. + + :return: + + Examples + -------- + :: + + server_replica.replica_type + """ + pass + + @abstractmethod + def is_primary(self) -> bool: + """ + Checks whether this is the primary replica of the raft cluster. + + :return: + + Examples + -------- + :: + + server_replica.is_primary() + """ + pass + + @property + @abstractmethod + def term(self) -> int: + """ + Returns the raft protocol ‘term’ of this replica. + + :return: + + Examples + -------- + :: + + server_replica.term() + """ + pass diff --git a/python/typedb/api/server/server_version.py b/python/typedb/api/server/server_version.py new file mode 100644 index 0000000000..2664c4fc21 --- /dev/null +++ b/python/typedb/api/server/server_version.py @@ -0,0 +1,75 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from typedb.common.exception import TypeDBDriverException, ILLEGAL_STATE +from typedb.common.native_wrapper import NativeWrapper +from typedb.native_driver_wrapper import ServerVersion as NativeServerVersion + + +class ServerVersion(NativeWrapper[NativeServerVersion]): + """ + A full TypeDB server's version specification. + + Examples: + -------- + :: + + driver.server_version() + """ + + def __init__(self, native_object: NativeServerVersion): + super().__init__(native_object) + + @property + def _native_object_not_owned_exception(self) -> TypeDBDriverException: + return TypeDBDriverException(ILLEGAL_STATE) + + @property + def distribution(self) -> str: + """ + Returns the server's distribution. + + :return: + + Examples + -------- + :: + + server_version.distribution + """ + return self.native_object.distribution + + @property + def version(self) -> str: + """ + Returns the server's version. + + :return: + + Examples + -------- + :: + + server_version.version + """ + return self.native_object.version + + def __str__(self): + return f"{self.distribution} {self.version}" + + def __repr__(self): + return f"ServerVersion('{str(self)}')" diff --git a/python/typedb/api/user/user.py b/python/typedb/api/user/user.py index d46e808b13..5f793634a6 100644 --- a/python/typedb/api/user/user.py +++ b/python/typedb/api/user/user.py @@ -16,7 +16,6 @@ # under the License. from abc import ABC, abstractmethod -from typing import Optional, List class User(ABC): @@ -32,16 +31,6 @@ def name(self) -> str: """ pass - # TODO: Not implemented - # @abstractmethod - # def password_expiry_seconds(self) -> Optional[int]: - # """ - # Returns the number of seconds remaining till this user's current password expires. - # - # :return: - # """ - # pass - @abstractmethod def update_password(self, password: str) -> None: """ @@ -68,89 +57,3 @@ def delete(self) -> None: driver.users.delete(username) """ pass - - -class UserManager(ABC): - """ - Provides access to all user management methods. - """ - - @abstractmethod - def contains(self, username: str) -> bool: - """ - Checks if a user with the given name exists. - - :param username: The user name to be checked - :return: - - Examples: - --------- - :: - - driver.users.contains(username) - """ - pass - - @abstractmethod - def create(self, username: str, password: str) -> None: - """ - Create a user with the given name and password. - - :param username: The name of the user to be created - :param password: The password of the user to be created - :return: - - Examples: - --------- - :: - - driver.users.create(username, password) - """ - pass - - @abstractmethod - def get(self, username: str) -> Optional[User]: - """ - Retrieve a user with the given name. - - :param username: The name of the user to retrieve - :return: - - Examples: - --------- - :: - - driver.users.get(username) - """ - pass - - @abstractmethod - def get_current_user(self) -> Optional[User]: - """ - Retrieve the name of the user who opened the current connection. - - :return: - - Examples: - --------- - :: - - driver.users.get_current_user() - """ - pass - - @abstractmethod - def all(self) -> List[User]: - """ - Retrieves all users which exist on the TypeDB server. - - :return: - - Examples: - --------- - :: - - driver.users.all() - - """ - pass diff --git a/python/typedb/api/user/user_manager.py b/python/typedb/api/user/user_manager.py new file mode 100644 index 0000000000..1b35867cf5 --- /dev/null +++ b/python/typedb/api/user/user_manager.py @@ -0,0 +1,114 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from abc import ABC, abstractmethod +from typing import Optional, List +from typedb.api.connection.consistency_level import ConsistencyLevel +from typedb.api.user.user import User + + +class UserManager(ABC): + """ + Provides access to all user management methods. + """ + + @abstractmethod + def all(self, consistency_level: Optional[ConsistencyLevel] = None) -> List[User]: + """ + Retrieves all users which exist on the TypeDB server. + + :param consistency_level: The consistency level to use for the operation. Strongest possible by default + :return: + + Examples: + --------- + :: + + driver.users.all() + driver.users.all(ConsistencyLevel.Strong()) + """ + pass + + @abstractmethod + def contains(self, username: str, consistency_level: Optional[ConsistencyLevel] = None) -> bool: + """ + Checks if a user with the given name exists. + + :param username: The username to be checked + :param consistency_level: The consistency level to use for the operation. Strongest possible by default + :return: + + Examples: + --------- + :: + + driver.users.contains(username) + driver.users.contains(username, ConsistencyLevel.Strong()) + """ + pass + + @abstractmethod + def get(self, username: str, consistency_level: Optional[ConsistencyLevel] = None) -> Optional[User]: + """ + Retrieves a user with the given name. + + :param username: The name of the user to retrieve + :param consistency_level: The consistency level to use for the operation. Strongest possible by default + :return: + + Examples: + --------- + :: + + driver.users.get(username) + driver.users.get(username, ConsistencyLevel.Strong()) + """ + pass + + @abstractmethod + def get_current_user(self, consistency_level: Optional[ConsistencyLevel] = None) -> Optional[User]: + """ + Retrieves the name of the user who opened the current connection. + + :param consistency_level: The consistency level to use for the operation. Strongest possible by default + :return: + + Examples: + --------- + :: + + driver.users.get_current_user() + driver.users.get_current_user(ConsistencyLevel.Strong()) + """ + pass + + @abstractmethod + def create(self, username: str, password: str) -> None: + """ + Creates a user with the given name and password. + + :param username: The name of the user to be created + :param password: The password of the user to be created + :return: + + Examples: + --------- + :: + + driver.users.create(username, password) + """ + pass diff --git a/python/typedb/common/datetime.py b/python/typedb/common/datetime.py index dbcfe71777..7c8e3622fe 100644 --- a/python/typedb/common/datetime.py +++ b/python/typedb/common/datetime.py @@ -206,19 +206,19 @@ def offset_seconds_fromstring(cls, offset: str) -> int: @property def datetime_without_nanos(self) -> datetime: - """Return the standard library's datetime, containing data up to microseconds.""" + """Returns the standard library's datetime, containing data up to microseconds.""" return datetime(year=self.year, month=self.month, day=self.day, hour=self.hour, minute=self.minute, second=self.second, microsecond=self.microsecond, tzinfo=self.tzinfo) @property def tz_name(self) -> Optional[str]: - """Return the timezone IANA name. None if fixed offset is used for the initialisation instead.""" + """Returns the timezone IANA name. None if fixed offset is used for the initialisation instead.""" return self._tz_name @property def offset_seconds(self) -> Optional[str]: """ - Return the timezone offset (local minus UTC) in seconds. + Returns the timezone offset (local minus UTC) in seconds. None if an IANA name is used for the initialisation instead. """ return self._offset_seconds @@ -226,7 +226,7 @@ def offset_seconds(self) -> Optional[str]: @property def total_seconds(self) -> float: """ - Return the total number of seconds including the nanoseconds part as a float. + Returns the total number of seconds including the nanoseconds part as a float. :raises ValueError: If timestamp is before the start of the epoch. """ @@ -234,63 +234,63 @@ def total_seconds(self) -> float: @property def year(self) -> int: - """Return the datetime's year (1-9999).""" + """Returns the datetime's year (1-9999).""" return self._datetime_of_seconds.year @property def month(self) -> int: - """Return the datetime's month (1-12).""" + """Returns the datetime's month (1-12).""" return self._datetime_of_seconds.month @property def day(self) -> int: - """Return the datetime's day (1-31).""" + """Returns the datetime's day (1-31).""" return self._datetime_of_seconds.day @property def hour(self) -> int: - """Return the datetime's hour (0-23).""" + """Returns the datetime's hour (0-23).""" return self._datetime_of_seconds.hour @property def minute(self) -> int: - """Return the datetime's minute (0-59).""" + """Returns the datetime's minute (0-59).""" return self._datetime_of_seconds.minute @property def second(self) -> int: - """Return the datetime's second (0-59).""" + """Returns the datetime's second (0-59).""" return self._datetime_of_seconds.second @property def microsecond(self) -> int: - """Return the rounded number of microseconds.""" + """Returns the rounded number of microseconds.""" return self._nanos // MICROS_IN_NANO @property def nanos(self) -> int: - """Return the nanoseconds part.""" + """Returns the nanoseconds part.""" return self._nanos @property def tzinfo(self) -> tzinfo: - """Return timezone info.""" + """Returns timezone info.""" return self._datetime_of_seconds.tzinfo @property def date(self) -> date: - """Return the date part.""" + """Returns the date part.""" return self._datetime_of_seconds.date() @property def weekday(self) -> int: - """Return the day of the week as an integer, where Monday == 0 ... Sunday == 6.""" + """Returns the day of the week as an integer, where Monday == 0 ... Sunday == 6.""" return self._datetime_of_seconds.weekday() ISO_TZ_LEN = 6 def isoformat(self) -> str: - """Return the time formatted according to ISO.""" + """Returns the time formatted according to ISO.""" datetime_part = self._datetime_of_seconds.isoformat() tz_part = "" if self._tz_name is not None or self._offset_seconds is not None: diff --git a/python/typedb/common/exception.py b/python/typedb/common/exception.py index ef3b0d5d35..0819cdedd0 100644 --- a/python/typedb/common/exception.py +++ b/python/typedb/common/exception.py @@ -92,6 +92,7 @@ def __init__(self, code: int, message: str): NON_NEGATIVE_VALUE_REQUIRED = DriverErrorMessage(5, "Value of '%s' should be non-negative, was: '%d'.") NON_NULL_VALUE_REQUIRED = DriverErrorMessage(6, "Value of '%s' should not be null.") UNIMPLEMENTED = DriverErrorMessage(7, "This operation is not implemented yet.") +INVALID_ADDRESS_FORMAT = DriverErrorMessage(8, "Driver addresses should be either a string, a list, or a dict.") class ConceptErrorMessage(ErrorMessage): diff --git a/python/typedb/connection/database.py b/python/typedb/connection/database.py index 5710a10c5e..5887f891ed 100644 --- a/python/typedb/connection/database.py +++ b/python/typedb/connection/database.py @@ -17,7 +17,10 @@ from __future__ import annotations -from typedb.api.connection.database import Database +from typing import Optional + +from typedb.api.connection.consistency_level import ConsistencyLevel +from typedb.api.database.database import Database from typedb.common.exception import TypeDBDriverException, DATABASE_DELETED, NULL_NATIVE_OBJECT from typedb.common.native_wrapper import NativeWrapper from typedb.common.validation import require_non_null @@ -44,23 +47,25 @@ def name(self) -> str: raise self._native_object_not_owned_exception return self._name - def schema(self) -> str: + def schema(self, consistency_level: Optional[ConsistencyLevel] = None) -> str: try: - return database_schema(self.native_object) + return database_schema(self.native_object, ConsistencyLevel.native_value(consistency_level)) except TypeDBDriverExceptionNative as e: raise TypeDBDriverException.of(e) from None - def type_schema(self) -> str: + def type_schema(self, consistency_level: Optional[ConsistencyLevel] = None) -> str: try: - return database_type_schema(self.native_object) + return database_type_schema(self.native_object, ConsistencyLevel.native_value(consistency_level)) except TypeDBDriverExceptionNative as e: raise TypeDBDriverException.of(e) from None - def export_to_file(self, schema_file_path: str, data_file_path: str) -> None: + def export_to_file(self, schema_file_path: str, data_file_path: str, + consistency_level: Optional[ConsistencyLevel] = None) -> None: require_non_null(schema_file_path, "schema_file_path") require_non_null(data_file_path, "data_file_path") try: - return database_export_to_file(self.native_object, schema_file_path, data_file_path) + consistency_level = ConsistencyLevel.native_value(consistency_level) + return database_export_to_file(self.native_object, schema_file_path, data_file_path, consistency_level) except TypeDBDriverExceptionNative as e: raise TypeDBDriverException.of(e) from None @@ -71,45 +76,8 @@ def delete(self) -> None: except TypeDBDriverExceptionNative as e: raise TypeDBDriverException.of(e) from None - # def replicas(self) -> set[Replica]: - # try: - # repl_iter = IteratorWrapper(database_get_replicas_info(self.native_object), replica_info_iterator_next) - # return set(_Database.Replica(replica_info) for replica_info in repl_iter) - # except TypeDBDriverExceptionNative as e: - # raise TypeDBDriverException.of(e) from None - # - # def primary_replica(self) -> Optional[Replica]: - # if res := database_get_primary_replica_info(self.native_object): - # return _Database.Replica(res) - # return None - # - # def preferred_replica(self) -> Optional[Replica]: - # if res := database_get_preferred_replica_info(self.native_object): - # return _Database.Replica(res) - # return None - def __str__(self): return self.name def __repr__(self): return f"Database('{str(self)}')" - - # class Replica(Replica): - # - # def __init__(self, replica_info: ReplicaInfo): - # self._info = replica_info - # - # def database(self) -> Database: - # pass - # - # def server(self) -> str: - # return replica_info_get_server(self._info) - # - # def is_primary(self) -> bool: - # return replica_info_is_primary(self._info) - # - # def is_preferred(self) -> bool: - # return replica_info_is_preferred(self._info) - # - # def term(self) -> int: - # return replica_info_get_term(self._info) diff --git a/python/typedb/connection/database_manager.py b/python/typedb/connection/database_manager.py index 88ddcebc2c..ce6c798ed8 100644 --- a/python/typedb/connection/database_manager.py +++ b/python/typedb/connection/database_manager.py @@ -17,9 +17,10 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional -from typedb.api.connection.database import DatabaseManager +from typedb.api.connection.consistency_level import ConsistencyLevel +from typedb.api.database.database_manager import DatabaseManager from typedb.common.exception import TypeDBDriverException from typedb.common.iterator_wrapper import IteratorWrapper from typedb.common.validation import require_non_null @@ -36,17 +37,24 @@ class _DatabaseManager(DatabaseManager): def __init__(self, native_driver: NativeDriver): self.native_driver = native_driver - def get(self, name: str) -> _Database: + def all(self, consistency_level: Optional[ConsistencyLevel] = None) -> list[_Database]: + try: + databases = databases_all(self.native_driver, ConsistencyLevel.native_value(consistency_level)) + return list(map(_Database, IteratorWrapper(databases, database_iterator_next))) + except TypeDBDriverExceptionNative as e: + raise TypeDBDriverException.of(e) from None + + def contains(self, name: str, consistency_level: Optional[ConsistencyLevel] = None) -> bool: require_non_null(name, "name") try: - return _Database(databases_get(self.native_driver, name)) + return databases_contains(self.native_driver, name, ConsistencyLevel.native_value(consistency_level)) except TypeDBDriverExceptionNative as e: raise TypeDBDriverException.of(e) from None - def contains(self, name: str) -> bool: + def get(self, name: str, consistency_level: Optional[ConsistencyLevel] = None) -> _Database: require_non_null(name, "name") try: - return databases_contains(self.native_driver, name) + return _Database(databases_get(self.native_driver, name, ConsistencyLevel.native_value(consistency_level))) except TypeDBDriverExceptionNative as e: raise TypeDBDriverException.of(e) from None @@ -65,9 +73,3 @@ def import_from_file(self, name: str, schema: str, data_file_path: str) -> None: databases_import_from_file(self.native_driver, name, schema, data_file_path) except TypeDBDriverExceptionNative as e: raise TypeDBDriverException.of(e) from None - - def all(self) -> list[_Database]: - try: - return list(map(_Database, IteratorWrapper(databases_all(self.native_driver), database_iterator_next))) - except TypeDBDriverExceptionNative as e: - raise TypeDBDriverException.of(e) from None diff --git a/python/typedb/connection/driver.py b/python/typedb/connection/driver.py index 827b16a1e0..d6769eb8e1 100644 --- a/python/typedb/connection/driver.py +++ b/python/typedb/connection/driver.py @@ -17,35 +17,61 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, Tuple +from typedb.api.connection.consistency_level import ConsistencyLevel from typedb.api.connection.driver import Driver +from typedb.api.connection.transaction import Transaction from typedb.api.connection.transaction_options import TransactionOptions -from typedb.common.exception import TypeDBDriverException, DRIVER_CLOSED +from typedb.api.server.server_version import ServerVersion +from typedb.common.exception import TypeDBDriverException, DRIVER_CLOSED, INVALID_ADDRESS_FORMAT +from typedb.common.iterator_wrapper import IteratorWrapper from typedb.common.native_wrapper import NativeWrapper -from typedb.common.validation import require_non_null +from typedb.common.validation import require_non_null, require_non_negative from typedb.connection.database_manager import _DatabaseManager +from typedb.connection.server_replica import _ServerReplica from typedb.connection.transaction import _Transaction -from typedb.native_driver_wrapper import driver_open_with_description, driver_is_open, driver_force_close, \ - TypeDBDriver as NativeDriver, TypeDBDriverExceptionNative +from typedb.native_driver_wrapper import driver_new_with_description, driver_new_with_addresses_with_description, \ + driver_new_with_address_translation_with_description, driver_is_open, driver_force_close, driver_register_replica, \ + driver_deregister_replica, driver_replicas, driver_primary_replica, driver_server_version, \ + driver_update_address_translation, server_replica_iterator_next, TypeDBDriver as NativeDriver, \ + TypeDBDriverExceptionNative from typedb.user.user_manager import _UserManager if TYPE_CHECKING: - from typedb.connection.driver_options import DriverOptions + from typedb.api.connection.driver_options import DriverOptions from typedb.api.connection.credentials import Credentials from typedb.api.connection.transaction import TransactionType - from typedb.api.user.user import UserManager + from typedb.api.user.user_manager import UserManager + from typedb.api.server.server_replica import ServerReplica class _Driver(Driver, NativeWrapper[NativeDriver]): - def __init__(self, address: str, credentials: Credentials, driver_options: DriverOptions): - require_non_null(address, "address") + def __init__(self, addresses: str | list[str] | dict[str, str], credentials: Credentials, + driver_options: DriverOptions): + require_non_null(addresses, "addresses") require_non_null(credentials, "credentials") require_non_null(driver_options, "driver_options") + try: - native_driver = driver_open_with_description(address, credentials.native_object, - driver_options.native_object, Driver.LANGUAGE) + if isinstance(addresses, str): + native_driver = driver_new_with_description(addresses, credentials.native_object, + driver_options.native_object, + Driver.LANGUAGE) + elif isinstance(addresses, list): + native_driver = driver_new_with_addresses_with_description(addresses, credentials.native_object, + driver_options.native_object, + Driver.LANGUAGE) + elif isinstance(addresses, dict): + public_addresses, private_addresses = _Driver._get_translated_addresses(addresses) + native_driver = driver_new_with_address_translation_with_description(public_addresses, + private_addresses, + credentials.native_object, + driver_options.native_object, + Driver.LANGUAGE) + else: + raise TypeDBDriverException(INVALID_ADDRESS_FORMAT) except TypeDBDriverExceptionNative as e: raise TypeDBDriverException.of(e) from None super().__init__(native_driver) @@ -58,12 +84,6 @@ def _native_object_not_owned_exception(self) -> TypeDBDriverException: def _native_driver(self) -> NativeDriver: return self.native_object - def transaction(self, database_name: str, transaction_type: TransactionType, - options: Optional[TransactionOptions] = None) -> Transaction: - require_non_null(database_name, "database_name") - require_non_null(transaction_type, "transaction_type") - return _Transaction(self, database_name, transaction_type, options if options else TransactionOptions()) - def is_open(self) -> bool: return driver_is_open(self._native_driver) @@ -75,6 +95,63 @@ def databases(self) -> _DatabaseManager: def users(self) -> UserManager: return _UserManager(self._native_driver) + def server_version(self, consistency_level: Optional[ConsistencyLevel] = None) -> ServerVersion: + try: + consistency_level = ConsistencyLevel.native_value(consistency_level) + return ServerVersion(driver_server_version(self._native_driver, consistency_level)) + except TypeDBDriverExceptionNative as e: + raise TypeDBDriverException.of(e) from None + + def transaction(self, database_name: str, transaction_type: TransactionType, + options: Optional[TransactionOptions] = None) -> Transaction: + require_non_null(database_name, "database_name") + require_non_null(transaction_type, "transaction_type") + return _Transaction(self, database_name, transaction_type, options if options else TransactionOptions()) + + def replicas(self) -> set[ServerReplica]: + try: + replica_iter = IteratorWrapper(driver_replicas(self._native_driver), server_replica_iterator_next) + return set(_ServerReplica(server_replica) for server_replica in replica_iter) + except TypeDBDriverExceptionNative as e: + raise TypeDBDriverException.of(e) from None + + def primary_replica(self) -> Optional[ServerReplica]: + if res := driver_primary_replica(self._native_driver): + return _ServerReplica(res) + return None + + def register_replica(self, replica_id: int, address: str) -> None: + require_non_negative(replica_id, "replica_id") + require_non_null(address, "address") + try: + driver_register_replica(self._native_driver, replica_id, address) + except TypeDBDriverExceptionNative as e: + raise TypeDBDriverException.of(e) from None + + def deregister_replica(self, replica_id: int) -> None: + require_non_negative(replica_id, "replica_id") + try: + driver_deregister_replica(self._native_driver, replica_id) + except TypeDBDriverExceptionNative as e: + raise TypeDBDriverException.of(e) from None + + def update_address_translation(self, address_translation: dict[str, str]) -> None: + require_non_null(address_translation, "address_translation") + public_addresses, private_addresses = _Driver._get_translated_addresses(address_translation) + try: + driver_update_address_translation(self._native_driver, public_addresses, private_addresses) + except TypeDBDriverExceptionNative as e: + raise TypeDBDriverException.of(e) from None + + def close(self) -> None: + driver_force_close(self._native_driver) + + @classmethod + def _get_translated_addresses(cls, address_translation: dict[str, str]) -> Tuple[list[str], list[str]]: + public_addresses = list(address_translation.keys()) + private_addresses = [address_translation[public] for public in public_addresses] + return public_addresses, private_addresses + def __enter__(self): return self @@ -82,6 +159,3 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.close() if exc_tb is not None: return False - - def close(self) -> None: - driver_force_close(self._native_driver) diff --git a/python/typedb/connection/server_replica.py b/python/typedb/connection/server_replica.py new file mode 100644 index 0000000000..61b84a786a --- /dev/null +++ b/python/typedb/connection/server_replica.py @@ -0,0 +1,62 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from __future__ import annotations + +from typedb.api.server.server_replica import ServerReplica +from typedb.common.exception import TypeDBDriverException, NULL_NATIVE_OBJECT, ILLEGAL_STATE +from typedb.common.native_wrapper import NativeWrapper +from typedb.native_driver_wrapper import server_replica_get_address, server_replica_get_id, server_replica_get_type, \ + server_replica_is_primary, server_replica_get_term, ServerReplica as NativeServerReplica, \ + TypeDBDriverExceptionNative + + +class _ServerReplica(ServerReplica, NativeWrapper[NativeServerReplica]): + + def __init__(self, server_replica: NativeServerReplica): + if not server_replica: + raise TypeDBDriverException(NULL_NATIVE_OBJECT) + super().__init__(server_replica) + + @property + def _native_object_not_owned_exception(self) -> TypeDBDriverException: + return TypeDBDriverException(ILLEGAL_STATE) + + @property + def id(self) -> int: + return server_replica_get_id(self.native_object) + + @property + def address(self) -> str: + return server_replica_get_address(self.native_object) + + @property + def replica_type(self) -> str: + return server_replica_get_type(self.native_object) + + def is_primary(self) -> bool: + return server_replica_is_primary(self.native_object) + + @property + def term(self) -> str: + return server_replica_get_term(self.native_object) + + def __str__(self): + return self.address + + def __repr__(self): + return f"ServerReplica('{str(self)}')" diff --git a/python/typedb/driver.py b/python/typedb/driver.py index f519d9724c..e6fd78be8a 100644 --- a/python/typedb/driver.py +++ b/python/typedb/driver.py @@ -15,6 +15,9 @@ # specific language governing permissions and limitations # under the License. +from collections.abc import Mapping as ABCMapping +from typing import Iterable + from typedb.api.answer.concept_document_iterator import * # noqa # pylint: disable=unused-import from typedb.api.answer.concept_row import * # noqa # pylint: disable=unused-import from typedb.api.answer.concept_row_iterator import * # noqa # pylint: disable=unused-import @@ -32,13 +35,15 @@ from typedb.api.concept.type.type import * # noqa # pylint: disable=unused-import from typedb.api.concept.value import * # noqa # pylint: disable=unused-import from typedb.api.connection.credentials import * # noqa # pylint: disable=unused-import -from typedb.api.connection.database import * # noqa # pylint: disable=unused-import from typedb.api.connection.driver import * # noqa # pylint: disable=unused-import from typedb.api.connection.driver_options import * # noqa # pylint: disable=unused-import from typedb.api.connection.query_options import * # noqa # pylint: disable=unused-import from typedb.api.connection.transaction import * # noqa # pylint: disable=unused-import from typedb.api.connection.transaction_options import * # noqa # pylint: disable=unused-import +from typedb.api.database.database import * # noqa # pylint: disable=unused-import +from typedb.api.database.database_manager import * # noqa # pylint: disable=unused-import from typedb.api.user.user import * # noqa # pylint: disable=unused-import +from typedb.api.user.user_manager import * # noqa # pylint: disable=unused-import from typedb.common.datetime import * # noqa # pylint: disable=unused-import from typedb.common.duration import * # noqa # pylint: disable=unused-import from typedb.common.exception import * # noqa # pylint: disable=unused-import @@ -49,16 +54,23 @@ class TypeDB: - DEFAULT_ADDRESS = "localhost:1729" + DEFAULT_ADDRESS = "127.0.0.1:1729" + ADDRESSES = Union[Mapping[str, str], Iterable[str], str] @staticmethod - def driver(address: str, credentials: Credentials, driver_options: DriverOptions) -> Driver: + def driver(addresses: ADDRESSES, credentials: Credentials, driver_options: DriverOptions) -> Driver: """ Creates a connection to TypeDB. - :param address: Address of the TypeDB server. + :param addresses: Address(-es) of the TypeDB server(-s). + Can be a single string, multiple strings, or multiple pairs of strings. :param credentials: The credentials to connect with. - :param driver_options: The connection settings to connect with. + :param driver_options: The driver connection options to connect with. :return: """ - return _Driver(address, credentials, driver_options) + if isinstance(addresses, str): + return _Driver(addresses, credentials, driver_options) + elif isinstance(addresses, ABCMapping): + return _Driver(dict(addresses), credentials, driver_options) + else: + return _Driver(list(addresses), credentials, driver_options) diff --git a/python/typedb/user/user_manager.py b/python/typedb/user/user_manager.py index a1571a5406..803649571c 100644 --- a/python/typedb/user/user_manager.py +++ b/python/typedb/user/user_manager.py @@ -19,7 +19,8 @@ from typing import TYPE_CHECKING, Optional -from typedb.api.user.user import UserManager +from typedb.api.connection.consistency_level import ConsistencyLevel +from typedb.api.user.user_manager import UserManager from typedb.common.exception import TypeDBDriverException from typedb.common.iterator_wrapper import IteratorWrapper from typedb.common.validation import require_non_null @@ -37,40 +38,41 @@ class _UserManager(UserManager): def __init__(self, native_driver: NativeDriver): self.native_driver = native_driver - def contains(self, username: str) -> bool: - require_non_null(username, "username") + def all(self, consistency_level: Optional[ConsistencyLevel] = None) -> list[User]: try: - return users_contains(self.native_driver, username) + native_users = users_all(self.native_driver, ConsistencyLevel.native_value(consistency_level)) + return [_User(user, self) for user in IteratorWrapper(native_users, user_iterator_next)] except TypeDBDriverExceptionNative as e: raise TypeDBDriverException.of(e) from None - def create(self, username: str, password: str) -> None: + def contains(self, username: str, consistency_level: Optional[ConsistencyLevel] = None) -> bool: require_non_null(username, "username") - require_non_null(password, "password") try: - users_create(self.native_driver, username, password) + return users_contains(self.native_driver, username, ConsistencyLevel.native_value(consistency_level)) except TypeDBDriverExceptionNative as e: raise TypeDBDriverException.of(e) from None - def all(self) -> list[User]: + def get(self, username: str, consistency_level: Optional[ConsistencyLevel] = None) -> Optional[User]: + require_non_null(username, "username") try: - return [_User(user, self) for user in IteratorWrapper(users_all(self.native_driver), user_iterator_next)] + if user := users_get(self.native_driver, username, ConsistencyLevel.native_value(consistency_level)): + return _User(user, self) + return None except TypeDBDriverExceptionNative as e: raise TypeDBDriverException.of(e) from None - def get(self, username: str) -> Optional[User]: - require_non_null(username, "username") + def get_current_user(self, consistency_level: Optional[ConsistencyLevel] = None) -> Optional[User]: try: - if user := users_get(self.native_driver, username): + if user := users_get_current_user(self.native_driver, ConsistencyLevel.native_value(consistency_level)): return _User(user, self) return None except TypeDBDriverExceptionNative as e: raise TypeDBDriverException.of(e) from None - def get_current_user(self) -> Optional[User]: + def create(self, username: str, password: str) -> None: + require_non_null(username, "username") + require_non_null(password, "password") try: - if user := users_get_current_user(self.native_driver): - return _User(user, self) - return None + users_create(self.native_driver, username, password) except TypeDBDriverExceptionNative as e: raise TypeDBDriverException.of(e) from None diff --git a/rust/Cargo.toml b/rust/Cargo.toml index df6aa895be..22c56bb669 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -60,7 +60,7 @@ [dependencies.typedb-protocol] features = [] - rev = "38f66a1cc4db3b7a301676f50800e9530ac5c8a3" + rev = "d1b71067c7e78364c69701625ea3f6d6b1f24a9d" git = "https://github.com/typedb/typedb-protocol" default-features = false @@ -129,6 +129,10 @@ version = "0.8.4" default-features = false +[[test]] + path = "tests/integration/cluster/clustering.rs" + name = "test_clustering" + [[test]] path = "tests/integration/example.rs" name = "test_example" diff --git a/rust/README.md b/rust/README.md index b68e5e0a03..102f6ec942 100644 --- a/rust/README.md +++ b/rust/README.md @@ -72,16 +72,16 @@ use typedb_driver::{ ConceptRow, QueryAnswer, }, concept::{Concept, ValueType}, - Credentials, DriverOptions, Error, QueryOptions, TransactionOptions, TransactionType, TypeDBDriver, + Addresses, Credentials, DriverOptions, Error, QueryOptions, TransactionOptions, TransactionType, TypeDBDriver, }; fn typedb_example() { async_std::task::block_on(async { // Open a driver connection. Specify your parameters if needed let driver = TypeDBDriver::new( - TypeDBDriver::DEFAULT_ADDRESS, + Addresses::try_from_address_str(TypeDBDriver::DEFAULT_ADDRESS).unwrap(), Credentials::new("admin", "password"), - DriverOptions::new(false, None).unwrap(), + DriverOptions::new(), ) .await .unwrap(); @@ -224,7 +224,7 @@ fn typedb_example() { // just call `commit`, which will wait for all ongoing operations to finish before executing. let queries = ["insert $a isa person, has name \"Alice\";", "insert $b isa person, has name \"Bob\";"]; for query in queries { - transaction.query(query); + let _unawaited_future = transaction.query(query); } transaction.commit().await.unwrap(); diff --git a/rust/docs_structure.bzl b/rust/docs_structure.bzl index e1e1e0abcb..619c6fd063 100644 --- a/rust/docs_structure.bzl +++ b/rust/docs_structure.bzl @@ -30,13 +30,17 @@ dir_mapping = { "ValueGroup.adoc": "answer", "Concept.adoc": "concept", "ConceptCategory.adoc": "concept", + "Kind.adoc": "concept", "TypeDBDriver.adoc": "connection", + "Addresses.adoc": "connection", + "ConsistencyLevel.adoc": "connection", + "Credentials.adoc": "connection", "Database.adoc": "connection", "DatabaseManager.adoc": "connection", "DriverOptions.adoc": "connection", - "Credentials.adoc": "connection", - "Kind.adoc": "concept", - "ReplicaInfo.adoc": "connection", + "ReplicaType.adoc": "connection", + "ServerReplica.adoc": "connection", + "ServerVersion.adoc": "connection", "User.adoc": "connection", "UserManager.adoc": "connection", "Relation.adoc": "data", diff --git a/rust/example.rs b/rust/example.rs index 58de9a7f80..a353341fef 100644 --- a/rust/example.rs +++ b/rust/example.rs @@ -9,16 +9,16 @@ use typedb_driver::{ ConceptRow, QueryAnswer, }, concept::{Concept, ValueType}, - Credentials, DriverOptions, Error, QueryOptions, TransactionOptions, TransactionType, TypeDBDriver, + Addresses, Credentials, DriverOptions, Error, QueryOptions, TransactionOptions, TransactionType, TypeDBDriver, }; fn typedb_example() { async_std::task::block_on(async { // Open a driver connection. Specify your parameters if needed let driver = TypeDBDriver::new( - TypeDBDriver::DEFAULT_ADDRESS, + Addresses::try_from_address_str(TypeDBDriver::DEFAULT_ADDRESS).unwrap(), Credentials::new("admin", "password"), - DriverOptions::new(false, None).unwrap(), + DriverOptions::new(), ) .await .unwrap(); @@ -161,7 +161,7 @@ fn typedb_example() { // just call `commit`, which will wait for all ongoing operations to finish before executing. let queries = ["insert $a isa person, has name \"Alice\";", "insert $b isa person, has name \"Bob\";"]; for query in queries { - transaction.query(query); + let _unawaited_future = transaction.query(query); } transaction.commit().await.unwrap(); diff --git a/rust/src/answer/concept_document.rs b/rust/src/answer/concept_document.rs index 7447b1289c..58202d5044 100644 --- a/rust/src/answer/concept_document.rs +++ b/rust/src/answer/concept_document.rs @@ -49,7 +49,7 @@ impl ConceptDocument { } } - /// Retrieve the executed query's type (shared by all elements in this stream). + /// Retrieves the executed query's type (shared by all elements in this stream). /// /// # Examples /// diff --git a/rust/src/answer/concept_row.rs b/rust/src/answer/concept_row.rs index d9acd233a4..7b2fd1d406 100644 --- a/rust/src/answer/concept_row.rs +++ b/rust/src/answer/concept_row.rs @@ -53,7 +53,7 @@ impl ConceptRow { Self { header, row } } - /// Retrieve the row column names (shared by all elements in this stream). + /// Retrieves the row column names (shared by all elements in this stream). /// /// # Examples /// @@ -64,7 +64,7 @@ impl ConceptRow { &self.header.column_names } - /// Retrieve the executed query's type (shared by all elements in this stream). + /// Retrieves the executed query's type (shared by all elements in this stream). /// /// # Examples /// diff --git a/rust/src/answer/mod.rs b/rust/src/answer/mod.rs index 6638095be1..aa40bbfe29 100644 --- a/rust/src/answer/mod.rs +++ b/rust/src/answer/mod.rs @@ -38,7 +38,7 @@ pub enum QueryAnswer { } impl QueryAnswer { - /// Retrieve the executed query's type (shared by all elements in this stream). + /// Retrieves the executed query's type (shared by all elements in this stream). /// /// # Examples /// @@ -53,7 +53,7 @@ impl QueryAnswer { } } - /// Check if the QueryAnswer is an Ok response. + /// Checks if the QueryAnswer is an Ok response. /// /// # Examples /// @@ -64,7 +64,7 @@ impl QueryAnswer { matches!(self, Self::Ok(_)) } - /// Check if the QueryAnswer is a ConceptRowStream. + /// Checks if the QueryAnswer is a ConceptRowStream. /// /// # Examples /// @@ -75,7 +75,7 @@ impl QueryAnswer { matches!(self, Self::ConceptRowStream(_, _)) } - /// Check if the QueryAnswer is a ConceptDocumentStream. + /// Checks if the QueryAnswer is a ConceptDocumentStream. /// /// # Examples /// diff --git a/rust/src/common/address.rs b/rust/src/common/address/address.rs similarity index 70% rename from rust/src/common/address.rs rename to rust/src/common/address/address.rs index a258f29f35..4b31fc32f8 100644 --- a/rust/src/common/address.rs +++ b/rust/src/common/address/address.rs @@ -26,15 +26,31 @@ use crate::{ error::ConnectionError, }; -#[derive(Clone, Hash, PartialEq, Eq)] +#[derive(Clone, Hash, PartialEq, Eq, Default)] pub struct Address { uri: Uri, } impl Address { + const DEFAULT_SCHEME: &'static str = "http"; + pub(crate) fn into_uri(self) -> Uri { self.uri } + + pub(crate) fn uri_scheme(&self) -> Option<&http::uri::Scheme> { + self.uri.scheme() + } + + pub(crate) fn is_https(&self) -> bool { + self.uri_scheme().map_or(false, |scheme| scheme == &http::uri::Scheme::HTTPS) + } + + pub(crate) fn with_scheme(&self, scheme: http::uri::Scheme) -> Self { + let mut parts = self.uri.clone().into_parts(); + parts.scheme = Some(scheme); + Self { uri: Uri::from_parts(parts).expect("Valid URI after scheme change") } + } } impl FromStr for Address { @@ -44,7 +60,7 @@ impl FromStr for Address { let uri = if address.contains("://") { address.parse::()? } else { - format!("http://{address}").parse::()? + format!("{}://{}", Self::DEFAULT_SCHEME, address).parse::()? }; if uri.port().is_none() { return Err(Error::Connection(ConnectionError::MissingPort { address: address.to_owned() })); @@ -61,6 +77,6 @@ impl fmt::Display for Address { impl fmt::Debug for Address { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self, f) + write!(f, "{:?}", self.uri) } } diff --git a/rust/src/common/address/address_translation.rs b/rust/src/common/address/address_translation.rs new file mode 100644 index 0000000000..4d050bf8e3 --- /dev/null +++ b/rust/src/common/address/address_translation.rs @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +use std::collections::HashMap; + +use crate::common::address::Address; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub(crate) enum AddressTranslation { + // public : private + Mapping(HashMap), +} + +impl AddressTranslation { + pub(crate) fn to_private(&self, address: &Address) -> Option
{ + match self { + AddressTranslation::Mapping(mapping) => mapping.get(address).cloned(), + } + } + + pub(crate) fn to_public(&self, address: &Address) -> Option
{ + match self { + AddressTranslation::Mapping(mapping) => { + mapping.iter().find(|(_, private)| private == &address).map(|(public, _)| public.clone()) + } + } + } +} diff --git a/rust/src/common/address/addresses.rs b/rust/src/common/address/addresses.rs new file mode 100644 index 0000000000..9873dfde4d --- /dev/null +++ b/rust/src/common/address/addresses.rs @@ -0,0 +1,223 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +use std::{ + collections::HashMap, + fmt, + fmt::{Formatter, Write}, +}; + +use itertools::Itertools; + +use crate::common::address::{address_translation::AddressTranslation, Address}; + +/// A collection of server addresses used for connection. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum Addresses { + Direct(Vec
), + Translated(HashMap), +} + +impl Addresses { + /// Prepare addresses based on a single "host:port" string. + /// + /// # Examples + /// + /// ```rust + /// Addresses::try_from_address_str("127.0.0.1:11729") + /// ``` + pub fn try_from_address_str(address_str: impl AsRef) -> crate::Result { + let address = address_str.as_ref().parse()?; + Ok(Self::from_address(address)) + } + + /// Prepare addresses based on a single TypeDB address. + /// + /// # Examples + /// + /// ```rust + /// let address = "127.0.0.1:11729".parse().unwrap(); + /// Addresses::from_address(address) + /// ``` + pub fn from_address(address: Address) -> Self { + Self::Direct(Vec::from([address])) + } + + /// Prepare addresses based on multiple "host:port" strings. + /// Is used to specify multiple addresses connect to. + /// + /// # Examples + /// + /// ```rust + /// Addresses::try_from_addresses_str(["127.0.0.1:11729", "127.0.0.1:11730", "127.0.0.1:11731"]) + /// ``` + pub fn try_from_addresses_str(addresses_str: impl IntoIterator>) -> crate::Result { + let addresses: Vec
= + addresses_str.into_iter().map(|address_str| address_str.as_ref().parse()).try_collect()?; + Ok(Self::from_addresses(addresses)) + } + + /// Prepare addresses based on multiple TypeDB addresses. + /// + /// # Examples + /// + /// ```rust + /// let address1 = "127.0.0.1:11729".parse().unwrap(); + /// let address2 = "127.0.0.1:11730".parse().unwrap(); + /// let address3 = "127.0.0.1:11731".parse().unwrap(); + /// Addresses::from_addresses([address1, address2, address3]) + /// ``` + pub fn from_addresses(addresses: impl IntoIterator) -> Self { + Self::Direct(addresses.into_iter().collect()) + } + + /// Prepare addresses based on multiple key-value (public-private) "key:port" string pairs. + /// Translation map from addresses to be used by the driver for connection to addresses received + /// from the TypeDB server(s). + /// + /// # Examples + /// + /// ```rust + /// Addresses::try_from_addresses_str( + /// [ + /// ("typedb-cloud.ext:11729", "127.0.0.1:11729"), + /// ("typedb-cloud.ext:11730", "127.0.0.1:11730"), + /// ("typedb-cloud.ext:11731", "127.0.0.1:11731") + /// ].into() + /// ) + /// ``` + pub fn try_from_translation_str(addresses_str: HashMap, impl AsRef>) -> crate::Result { + let mut addresses = HashMap::new(); + for (address_key, address_value) in addresses_str.into_iter() { + addresses.insert(address_key.as_ref().parse()?, address_value.as_ref().parse()?); + } + Ok(Self::from_translation(addresses)) + } + + /// Prepare addresses based on multiple key-value (public-private) TypeDB address pairs. + /// Translation map from addresses to be used by the driver for connection to addresses received + /// from the TypeDB server(s). + /// + /// # Examples + /// + /// ```rust + /// let translation: HashMap = [ + /// ("typedb-cloud.ext:11729".parse()?, "127.0.0.1:11729".parse()?), + /// ("typedb-cloud.ext:11730".parse()?, "127.0.0.1:11730".parse()?), + /// ("typedb-cloud.ext:11731".parse()?, "127.0.0.1:11731".parse()?) + /// ].into(); + /// Addresses::from_translation(translation) + /// ``` + pub fn from_translation(addresses: HashMap) -> Self { + Self::Translated(addresses) + } + + /// Returns the number of address entries (addresses or address pairs) in the collection. + /// + /// # Examples + /// + /// ```rust + /// addresses.len() + /// ``` + pub fn len(&self) -> usize { + match self { + Addresses::Direct(vec) => vec.len(), + Addresses::Translated(map) => map.len(), + } + } + + /// Checks if the public address is a part of the addresses. + /// + /// # Examples + /// + /// ```rust + /// addresses.contains(&address) + /// ``` + pub fn contains(&self, address: &Address) -> bool { + match self { + Addresses::Direct(vec) => vec.contains(address), + Addresses::Translated(map) => map.contains_key(address), + } + } + + pub(crate) fn addresses(&self) -> AddressIter<'_> { + match self { + Addresses::Direct(vec) => AddressIter::Direct(vec.iter()), + Addresses::Translated(map) => AddressIter::Translated(map.keys()), + } + } + + pub(crate) fn address_translation(&self) -> AddressTranslation { + match self { + Addresses::Direct(addresses) => AddressTranslation::Mapping( + addresses.into_iter().map(|address| (address.clone(), address.clone())).collect(), + ), + Addresses::Translated(translation) => AddressTranslation::Mapping(translation.clone()), + } + } +} + +impl fmt::Display for Addresses { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Addresses::Direct(addresses) => { + f.write_char('[')?; + for (i, address) in addresses.iter().enumerate() { + if i > 0 { + f.write_str(", ")?; + } + write!(f, "{address}")?; + } + f.write_char(']') + } + Addresses::Translated(translation) => { + f.write_char('{')?; + for (i, (public, private)) in translation.iter().enumerate() { + if i > 0 { + f.write_str(", ")?; + } + write!(f, "{public}: {private}")?; + } + f.write_char('}') + } + } + } +} + +impl Default for Addresses { + fn default() -> Self { + Self::Direct(Vec::default()) + } +} + +pub(crate) enum AddressIter<'a> { + Direct(std::slice::Iter<'a, Address>), + Translated(std::collections::hash_map::Keys<'a, Address, Address>), +} + +impl<'a> Iterator for AddressIter<'a> { + type Item = &'a Address; + + fn next(&mut self) -> Option { + match self { + AddressIter::Direct(iter) => iter.next(), + AddressIter::Translated(iter) => iter.next(), + } + } +} diff --git a/tool/test/EchoJavaHome.java b/rust/src/common/address/mod.rs similarity index 81% rename from tool/test/EchoJavaHome.java rename to rust/src/common/address/mod.rs index 42c783955a..83ba5ed8f2 100644 --- a/tool/test/EchoJavaHome.java +++ b/rust/src/common/address/mod.rs @@ -17,10 +17,8 @@ * under the License. */ -package com.typedb.driver.tool.test; +pub use self::{address::Address, addresses::Addresses}; -public class EchoJavaHome { - public static void main(String[] args) { - System.out.println(System.getProperty("java.home")); - } -} +pub(crate) mod address; +pub(crate) mod address_translation; +pub(crate) mod addresses; diff --git a/rust/src/common/consistency_level.rs b/rust/src/common/consistency_level.rs new file mode 100644 index 0000000000..d4b9a33720 --- /dev/null +++ b/rust/src/common/consistency_level.rs @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +use std::fmt; + +use crate::common::address::Address; + +/// Consistency levels of operations against a distributed server. All driver methods have default +/// recommended values, however, readonly operations can be configured in order to potentially +/// speed up the execution (introducing risks of stale data) or test a specific replica. +/// This setting does not affect clusters with a single node. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ConsistencyLevel { + /// Strongest consistency, always up-to-date due to the guarantee of the primary replica usage. + /// May require more time for operation execution. + Strong, + + /// Allow stale reads from any replica. May not reflect latest writes. The execution may be + /// eventually faster compared to other consistency levels. + Eventual, + + /// The operation is executed against the provided replica address only. Its guarantees depend + /// on the replica selected. + ReplicaDependant { address: Address }, +} + +impl fmt::Display for ConsistencyLevel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ConsistencyLevel::Strong => write!(f, "Strong"), + ConsistencyLevel::Eventual => write!(f, "Eventual"), + ConsistencyLevel::ReplicaDependant { address } => write!(f, "Target({address})"), + } + } +} diff --git a/rust/src/common/error.rs b/rust/src/common/error.rs index a6493869ee..9efb386be7 100644 --- a/rust/src/common/error.rs +++ b/rust/src/common/error.rs @@ -17,13 +17,13 @@ * under the License. */ -use std::{collections::HashSet, error::Error as StdError, fmt}; +use std::{error::Error as StdError, fmt}; -use itertools::Itertools; use tonic::{Code, Status}; -use tonic_types::{ErrorDetails, ErrorInfo, StatusExt}; +use tonic_types::StatusExt; -use super::{address::Address, RequestID}; +use super::RequestID; +use crate::common::address::{Address, Addresses}; macro_rules! error_messages { { @@ -116,15 +116,24 @@ macro_rules! error_messages { }; } +macro_rules! __retryable_helper { + (retryable) => { + true + }; + () => { + false + }; +} + error_messages! { ConnectionError code: "CXN", type: "Connection Error", RPCMethodUnavailable { message: String } = 1: "The server does not support this method, please check the driver-server compatibility:\n'{message}'.", - ServerConnectionFailed { addresses: Vec
} = - 2: "Unable to connect to TypeDB server(s) at: \n{addresses:?}", + ServerConnectionFailed { configured_addresses: Addresses, accessed_addresses: Addresses, details: String } = + 2: "Unable to connect to TypeDB server(s).\nInitially configured addresses: {configured_addresses}.\nTried accessing addresses: {accessed_addresses}. Details: {details}", ServerConnectionFailedWithError { error: String } = 3: "Unable to connect to TypeDB server(s), received errors: \n{error}", - ServerConnectionFailedStatusError { error: String } = + ServerConnectionFailedNetworking { error: String } = 4: "Unable to connect to TypeDB server(s), received network or protocol error: \n{error}", ServerConnectionIsClosed = 5: "The connection has been closed and no further operation is allowed.", @@ -132,38 +141,34 @@ error_messages! { ConnectionError 6: "The transaction is closed and no further operation is allowed.", TransactionIsClosedWithErrors { errors: String } = 7: "The transaction is closed because of the error(s):\n{errors}", - DatabaseNotFound { name: String } = - 8: "Database '{name}' not found.", MissingResponseField { field: &'static str } = 9: "Missing field in message received from server: '{field}'. This is either a version compatibility issue or a bug.", UnknownRequestId { request_id: RequestID } = 10: "Received a response with unknown request id '{request_id}'", UnexpectedResponse { response: String } = 11: "Received unexpected response from server: '{response}'. This is either a version compatibility issue or a bug.", - InvalidResponseField { name: &'static str } = - 12: "Invalid field in message received from server: '{name}'. This is either a version compatibility issue or a bug.", QueryStreamNoResponse = 13: "Didn't receive any server responses for the query.", UnexpectedQueryType { query_type: i32 } = 14: "Unexpected query type in message received from server: {query_type}. This is either a version compatibility issue or a bug.", ClusterReplicaNotPrimary = 15: "The replica is not the primary replica.", - ClusterAllNodesFailed { errors: String } = - 16: "Attempted connecting to all TypeDB Cluster servers, but the following errors occurred: \n{errors}.", + UnknownDirectReplica { address: Address, configured_addresses: Addresses } = + 16: "Could not execute operation against '{address}' since it's not a known replica of configured addresses ({configured_addresses}).", TokenCredentialInvalid = 17: "Invalid token credentials.", EncryptionSettingsMismatch = 18: "Unable to connect to TypeDB: possible encryption settings mismatch.", - SSLCertificateNotValidated = + SslCertificateNotValidated = 19: "SSL handshake with TypeDB failed: the server's identity could not be verified. Possible CA mismatch.", BrokenPipe = 20: "Stream closed because of a broken pipe. This could happen if you are attempting to connect to an unencrypted TypeDB server using a TLS-enabled credentials.", - ConnectionFailed = - 21: "Connection failed. Please check the server is running and the address is accessible. Encrypted TypeDB endpoints may also have misconfigured SSL certificates.", + ConnectionRefusedNetworking = + 21: "Connection refused. Please check the server is running and the address is accessible. Encrypted TypeDB endpoints may also have misconfigured SSL certificates.", MissingPort { address: String } = 22: "Invalid URL '{address}': missing port.", - AddressTranslationMismatch { unknown: HashSet
, unmapped: HashSet
} = - 23: "Address translation map does not match the server's advertised address list. User-provided servers not in the advertised list: {unknown:?}. Advertised servers not mapped by user: {unmapped:?}.", + UnexpectedReplicaType { replica_type: i32 } = + 23: "Unexpected replica type in message received from server: {replica_type}. This is either a version compatibility issue or a bug.", ValueTimeZoneNameNotRecognised { time_zone: String } = 24: "Time zone provided by the server has name '{time_zone}', which is not an officially recognized timezone.", ValueTimeZoneOffsetNotRecognised { offset: i32 } = @@ -184,6 +189,18 @@ error_messages! { ConnectionError 32: "The database export channel is closed and no further operation is allowed.", DatabaseExportStreamNoResponse = 33: "Didn't receive any server responses for the database export command.", + MissingTlsConfigForTls = + 34: "TLS config object must be set when TLS is enabled.", + MultipleAddressesForNoReplicationMode { addresses: Addresses } = + 35: "Server replicas usage is turned off, but multiple connection addresses ({addresses}) are provided. This is error-prone, thus prohibited.", + NotPrimaryOnReadOnly { address: Address } = + 36: "Could not execute a readonly operation on a non-primary replica '{address}'. It is either a version compatibility issue or a bug.", + NoAvailableReplicas { configured_addresses: Addresses } = + 37: "Could not connect: no available replicas read from addresses {configured_addresses}.", + HttpHttpsMismatch { addresses: Addresses } = + 38: "Invalid encryption used: either all or none addresses must use 'https': {addresses}.", + AddressTranslationWithoutTranslation { addresses: Addresses } = + 39: "Specified addresses do not contain address translation: {addresses}.", } error_messages! { ConceptError @@ -220,10 +237,8 @@ error_messages! { InternalError 3: "Unexpected request type for remote procedure call: {request_type}. This is either a version compatibility issue or a bug.", UnexpectedResponseType { response_type: String } = 4: "Unexpected response type for remote procedure call: {response_type}. This is either a version compatibility issue or a bug.", - UnknownServer { server: Address } = - 5: "Received replica at unrecognized server: {server}.", - EnumOutOfBounds { value: i32, enum_name: &'static str } = - 6: "Value '{value}' is out of bounds for enum '{enum_name}'.", + Unimplemented { details: String } = + 5: "Unimplemented feature: {details}.", } #[derive(Clone, PartialEq, Eq)] @@ -302,10 +317,7 @@ impl Error { } } - fn try_extracting_connection_error(status: &Status, code: &str) -> Option { - // TODO: We should probably catch more connection errors instead of wrapping them into - // ServerErrors. However, the most valuable information even for connection is inside - // stacktraces now. + fn try_extracting_connection_error(_status: &Status, code: &str) -> Option { match code { "AUT2" | "AUT3" => Some(ConnectionError::TokenCredentialInvalid {}), _ => None, @@ -313,8 +325,11 @@ impl Error { } fn from_message(message: &str) -> Self { - // TODO: Consider converting some of the messages to connection errors - Self::Other(message.to_owned()) + if is_rst_stream(message) { + Self::Connection(ConnectionError::ServerConnectionFailedNetworking { error: message.to_owned() }) + } else { + Self::Other(message.to_owned()) + } } fn parse_unavailable(status_message: &str) -> Error { @@ -323,11 +338,11 @@ impl Error { } else if status_message.contains("received corrupt message") { Error::Connection(ConnectionError::EncryptionSettingsMismatch) } else if status_message.contains("UnknownIssuer") { - Error::Connection(ConnectionError::SSLCertificateNotValidated) + Error::Connection(ConnectionError::SslCertificateNotValidated) } else if status_message.contains("Connection refused") { - Error::Connection(ConnectionError::ConnectionFailed) + Error::Connection(ConnectionError::ConnectionRefusedNetworking) } else { - Error::Connection(ConnectionError::ServerConnectionFailedStatusError { error: status_message.to_owned() }) + Error::Connection(ConnectionError::ServerConnectionFailedNetworking { error: status_message.to_owned() }) } } } @@ -370,6 +385,12 @@ impl From for Error { } } +impl From for Error { + fn from(error: MigrationError) -> Self { + Self::Migration(error) + } +} + impl From for Error { fn from(error: InternalError) -> Self { Self::Internal(error) @@ -392,39 +413,36 @@ impl From for Error { } else if let Some(error_info) = details.error_info() { let code = error_info.reason.clone(); if let Some(connection_error) = Self::try_extracting_connection_error(&status, &code) { - return Self::Connection(connection_error); + Self::Connection(connection_error) + } else { + let domain = error_info.domain.clone(); + let stack_trace = + details.debug_info().map(|debug_info| debug_info.stack_entries.clone()).unwrap_or_default(); + Self::Server(ServerError::new(code, domain, status.message().to_owned(), stack_trace)) } - let domain = error_info.domain.clone(); - let stack_trace = - if let Some(debug_info) = details.debug_info() { debug_info.stack_entries.clone() } else { vec![] }; - - Self::Server(ServerError::new(code, domain, status.message().to_owned(), stack_trace)) } else { Self::from_message(status.message()) } } else { - if status.code() == Code::Unavailable { - Self::parse_unavailable(status.message()) - } else if status.code() == Code::Unknown - || is_rst_stream(&status) - || status.code() == Code::FailedPrecondition - || status.code() == Code::AlreadyExists - { - Self::Connection(ConnectionError::ServerConnectionFailedStatusError { - error: status.message().to_owned(), - }) - } else if status.code() == Code::Unimplemented { - Self::Connection(ConnectionError::RPCMethodUnavailable { message: status.message().to_owned() }) - } else { - Self::from_message(status.message()) + match status.code() { + Code::Unavailable => Self::parse_unavailable(status.message()), + Code::Unknown | Code::FailedPrecondition | Code::AlreadyExists => { + Self::Connection(ConnectionError::ServerConnectionFailedNetworking { + error: status.message().to_owned(), + }) + } + Code::Unimplemented => { + Self::Connection(ConnectionError::RPCMethodUnavailable { message: status.message().to_owned() }) + } + _ => Self::from_message(status.message()), } } } } -fn is_rst_stream(status: &Status) -> bool { +fn is_rst_stream(message: &str) -> bool { // "Received Rst Stream" occurs if the server is in the process of shutting down. - status.message().contains("Received Rst Stream") + message.contains("Received Rst Stream") } impl From for Error { diff --git a/rust/src/common/info.rs b/rust/src/common/info.rs index 23eb0e3e4b..c73d02f29d 100644 --- a/rust/src/common/info.rs +++ b/rust/src/common/info.rs @@ -17,26 +17,9 @@ * under the License. */ -use super::address::Address; - #[derive(Debug)] pub(crate) struct DatabaseInfo { pub(crate) name: String, - pub(crate) replicas: Vec, -} - -/// The metadata and state of an individual raft replica of a database. -#[derive(Debug)] -pub struct ReplicaInfo { - /// The server hosting this replica - pub server: Address, - /// Whether this is the primary replica of the raft cluster. - pub is_primary: bool, - /// Whether this is the preferred replica of the raft cluster. - /// If true, Operations which can be run on any replica will prefer to use this replica. - pub is_preferred: bool, - /// The raft protocol ‘term’ of this replica. - pub term: i64, } #[derive(Debug)] diff --git a/rust/src/common/mod.rs b/rust/src/common/mod.rs index d7ae057e05..3fb96295e3 100644 --- a/rust/src/common/mod.rs +++ b/rust/src/common/mod.rs @@ -18,6 +18,7 @@ */ pub use self::{ + address::{Address, Addresses}, error::Error, promise::{box_promise, BoxPromise, Promise}, query_options::QueryOptions, @@ -26,6 +27,7 @@ pub use self::{ }; pub(crate) mod address; +pub mod consistency_level; pub mod error; mod id; pub mod info; diff --git a/rust/src/common/transaction_options.rs b/rust/src/common/transaction_options.rs index 36c33edf88..39ae880e36 100644 --- a/rust/src/common/transaction_options.rs +++ b/rust/src/common/transaction_options.rs @@ -19,8 +19,10 @@ use std::time::Duration; +use crate::common::consistency_level::ConsistencyLevel; + /// TypeDB transaction options. -/// `TransactionOptions` object can be used to override the default server behaviour for opened +/// `TransactionOptions` object can be used to override the default behaviour for opened /// transactions. /// /// # Examples @@ -28,12 +30,15 @@ use std::time::Duration; /// ```rust /// let options = TransactionOptions::new().transaction_timeout(Duration::from_secs(60)); /// ``` -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct TransactionOptions { /// If set, specifies a timeout for killing transactions automatically, preventing memory leaks in unclosed transactions. pub transaction_timeout: Option, /// If set, specifies how long the driver should wait if opening a transaction is blocked by an exclusive schema write lock. pub schema_lock_acquire_timeout: Option, + /// If set, specifies the requested consistency level of the transaction opening operation. + /// Affects only read transactions, as write and schema transactions require primary replicas. + pub read_consistency_level: Option, } impl TransactionOptions { @@ -50,4 +55,10 @@ impl TransactionOptions { pub fn schema_lock_acquire_timeout(self, timeout: Duration) -> Self { Self { schema_lock_acquire_timeout: Some(timeout), ..self } } + + /// If set, specifies the requested consistency level of the transaction opening operation. + /// Affects only read transactions, as write and schema transactions require primary replicas. + pub fn consistency_level(self, consistency_level: ConsistencyLevel) -> Self { + Self { read_consistency_level: Some(consistency_level), ..self } + } } diff --git a/rust/src/concept/mod.rs b/rust/src/concept/mod.rs index af96cff279..01abaaa123 100644 --- a/rust/src/concept/mod.rs +++ b/rust/src/concept/mod.rs @@ -262,7 +262,7 @@ impl Concept { } } - /// Check if this Concept represents a Type from the schema of the database. + /// Checks if this Concept represents a Type from the schema of the database. /// These are exactly: Entity Types, Relation Types, Role Types, and Attribute Types /// /// Equivalent to: @@ -276,27 +276,27 @@ impl Concept { } } - /// Check if this Concept represents an Entity Type from the schema of the database + /// Checks if this Concept represents an Entity Type from the schema of the database pub fn is_entity_type(&self) -> bool { matches!(self.get_category(), ConceptCategory::EntityType) } - /// Check if this Concept represents a Relation Type from the schema of the database + /// Checks if this Concept represents a Relation Type from the schema of the database pub fn is_relation_type(&self) -> bool { matches!(self.get_category(), ConceptCategory::RelationType) } - /// Check if this Concept represents a Role Type from the schema of the database + /// Checks if this Concept represents a Role Type from the schema of the database pub fn is_role_type(&self) -> bool { matches!(self.get_category(), ConceptCategory::RoleType) } - /// Check if this Concept represents an Attribute Type from the schema of the database + /// Checks if this Concept represents an Attribute Type from the schema of the database pub fn is_attribute_type(&self) -> bool { matches!(self.get_category(), ConceptCategory::AttributeType) } - /// Check if this Concept represents a stored database instance from the database. + /// Checks if this Concept represents a stored database instance from the database. /// These are exactly: Entity, Relation, and Attribute /// /// Equivalent to: @@ -310,72 +310,72 @@ impl Concept { } } - /// Check if this Concept represents an Entity instance from the database + /// Checks if this Concept represents an Entity instance from the database pub fn is_entity(&self) -> bool { matches!(self.get_category(), ConceptCategory::Entity) } - /// Check if this Concept represents an Relation instance from the database + /// Checks if this Concept represents an Relation instance from the database pub fn is_relation(&self) -> bool { matches!(self.get_category(), ConceptCategory::Relation) } - /// Check if this Concept represents an Attribute instance from the database + /// Checks if this Concept represents an Attribute instance from the database pub fn is_attribute(&self) -> bool { matches!(self.get_category(), ConceptCategory::Attribute) } - /// Check if this Concept represents a Value returned by the database + /// Checks if this Concept represents a Value returned by the database pub fn is_value(&self) -> bool { matches!(self.get_category(), ConceptCategory::Value) } - /// Check if this Concept holds a boolean as an AttributeType, an Attribute, or a Value + /// Checks if this Concept holds a boolean as an AttributeType, an Attribute, or a Value pub fn is_boolean(&self) -> bool { matches!(self.try_get_value_type(), Some(ValueType::Boolean)) } - /// Check if this Concept holds an integer as an AttributeType, an Attribute, or a Value + /// Checks if this Concept holds an integer as an AttributeType, an Attribute, or a Value pub fn is_integer(&self) -> bool { matches!(self.try_get_value_type(), Some(ValueType::Integer)) } - /// Check if this Concept holds a fixed-decimal as an AttributeType, an Attribute, or a Value + /// Checks if this Concept holds a fixed-decimal as an AttributeType, an Attribute, or a Value pub fn is_decimal(&self) -> bool { matches!(self.try_get_value_type(), Some(ValueType::Decimal)) } - /// Check if this Concept holds a double as an AttributeType, an Attribute, or a Value + /// Checks if this Concept holds a double as an AttributeType, an Attribute, or a Value pub fn is_double(&self) -> bool { matches!(self.try_get_value_type(), Some(ValueType::Double)) } - /// Check if this Concept holds a string as an AttributeType, an Attribute, or a Value + /// Checks if this Concept holds a string as an AttributeType, an Attribute, or a Value pub fn is_string(&self) -> bool { matches!(self.try_get_value_type(), Some(ValueType::String)) } - /// Check if this Concept holds a date as an AttributeType, an Attribute, or a Value + /// Checks if this Concept holds a date as an AttributeType, an Attribute, or a Value pub fn is_date(&self) -> bool { matches!(self.try_get_value_type(), Some(ValueType::Date)) } - /// Check if this Concept holds a datetime as an AttributeType, an Attribute, or a Value + /// Checks if this Concept holds a datetime as an AttributeType, an Attribute, or a Value pub fn is_datetime(&self) -> bool { matches!(self.try_get_value_type(), Some(ValueType::Datetime)) } - /// Check if this Concept holds a timezoned-datetime as an AttributeType, an Attribute, or a Value + /// Checks if this Concept holds a timezoned-datetime as an AttributeType, an Attribute, or a Value pub fn is_datetime_tz(&self) -> bool { matches!(self.try_get_value_type(), Some(ValueType::DatetimeTZ)) } - /// Check if this Concept holds a duration as an AttributeType, an Attribute, or a Value + /// Checks if this Concept holds a duration as an AttributeType, an Attribute, or a Value pub fn is_duration(&self) -> bool { matches!(self.try_get_value_type(), Some(ValueType::Duration)) } - /// Check if this Concept holds a struct as an AttributeType, an Attribute, or a Value + /// Checks if this Concept holds a struct as an AttributeType, an Attribute, or a Value pub fn is_struct(&self) -> bool { matches!(self.try_get_value_type(), Some(ValueType::Struct(_))) } diff --git a/rust/src/concept/value.rs b/rust/src/concept/value.rs index 37e830d504..26f9d8b376 100644 --- a/rust/src/concept/value.rs +++ b/rust/src/concept/value.rs @@ -267,8 +267,11 @@ impl fmt::Debug for Value { #[repr(C)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct Decimal { - integer: i64, - fractional: u64, + /// The integer part of the decimal as normal signed 64 bit number + pub integer: i64, + /// The fractional part of the decimal, in multiples of 10^-19 (Decimal::FRACTIONAL_PART_DENOMINATOR). + /// This means that the smallest decimal representable is 10^-19, and up to 19 decimal places are supported. + pub fractional: u64, } impl Decimal { @@ -281,17 +284,6 @@ impl Decimal { assert!(fractional < Decimal::FRACTIONAL_PART_DENOMINATOR); Self { integer, fractional } } - - /// Get the integer part of the decimal as normal signed 64 bit number - pub fn integer_part(&self) -> i64 { - self.integer - } - - /// Get the fractional part of the decimal, in multiples of 10^-19 (Decimal::FRACTIONAL_PART_DENOMINATOR) - /// This means, the smallest decimal representable is 10^-19, and up to 19 decimal places are supported. - pub fn fractional_part(&self) -> u64 { - self.fractional - } } impl Neg for Decimal { @@ -345,7 +337,7 @@ impl fmt::Display for Decimal { impl fmt::Debug for Decimal { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.fractional == 0 { - write!(f, "{}.0", self.integer_part())?; + write!(f, "{}.0", self.integer)?; } else { // count number of tailing 0's that don't have to be represented let mut tail_0s = 0; @@ -356,7 +348,7 @@ impl fmt::Debug for Decimal { } let fractional_width = Self::FRACTIONAL_PART_DENOMINATOR_LOG10 - tail_0s; - write!(f, "{}.{:0width$}dec", self.integer_part(), fractional, width = fractional_width as usize)?; + write!(f, "{}.{:0width$}dec", self.integer, fractional, width = fractional_width as usize)?; } Ok(()) } @@ -461,18 +453,6 @@ impl Duration { Self { months, days, nanos } } - pub fn months(&self) -> u32 { - self.months - } - - pub fn days(&self) -> u32 { - self.days - } - - pub fn nanos(&self) -> u64 { - self.nanos - } - fn is_empty(&self) -> bool { self.months == 0 && self.days == 0 && self.nanos == 0 } diff --git a/rust/src/connection/database/import_stream.rs b/rust/src/connection/database/import_stream.rs index 9897d75b2e..a3929426f8 100644 --- a/rust/src/connection/database/import_stream.rs +++ b/rust/src/connection/database/import_stream.rs @@ -24,7 +24,7 @@ use futures::{stream, StreamExt}; use typedb_protocol::migration; use crate::{ - common::{stream::Stream, Promise, Result}, + common::{Promise, Result}, connection::{message::DatabaseImportRequest, network::transmitter::DatabaseImportTransmitter}, promisify, resolve, }; diff --git a/rust/src/connection/driver_options.rs b/rust/src/connection/driver_options.rs index b16ddc5ece..114186dfc2 100644 --- a/rust/src/connection/driver_options.rs +++ b/rust/src/connection/driver_options.rs @@ -17,46 +17,137 @@ * under the License. */ -use std::{fs, path::Path}; +use std::{ + fs, + path::{Path, PathBuf}, +}; use tonic::transport::{Certificate, ClientTlsConfig}; -/// User connection settings for connecting to TypeDB. +// When changing these numbers, also update docs in DriverOptions +const DEFAULT_IS_TLS_ENABLED: bool = false; +const DEFAULT_TLS_CONFIG: Option = None; +const DEFAULT_TLS_ROOT_CA_PATH: Option = None; +const DEFAULT_USE_REPLICATION: bool = true; +const DEFAULT_REDIRECT_FAILOVER_RETRIES: usize = 1; +const DEFAULT_DISCOVERY_FAILOVER_RETRIES: Option = None; + +/// TypeDB driver connection options. +/// `DriverOptions` object can be used to override the default driver behavior while connecting to +/// TypeDB. +/// +/// # Examples +/// +/// ```rust +/// let options = DriverOptions::new().is_tls_enabled(true).tls_root_ca(Some(&path_to_ca)).unwrap(); +/// ``` #[derive(Debug, Clone)] pub struct DriverOptions { - is_tls_enabled: bool, + /// Specifies whether the connection to TypeDB must be done over TLS. + /// Defaults to false. + pub is_tls_enabled: bool, + /// Specifies whether the connection to TypeDB can use cluster replicas provided by the server + /// or it should be limited to a single configured address. + /// Defaults to true. + pub use_replication: bool, + /// Limits the number of attempts to redirect a strongly consistent request to another + /// primary replica in case of a failure due to the change of replica roles. + /// Defaults to 1. + pub primary_failover_retries: usize, + /// Limits the number of driver attempts to discover a single working replica to perform an + /// operation in case of a replica unavailability. Every replica is tested once, which means + /// that at most: + /// - {limit} operations are performed if the limit <= the number of replicas. + /// - {number of replicas} operations are performed if the limit > the number of replicas. + /// - {number of replicas} operations are performed if the limit is None. + /// Affects every eventually consistent operation, including redirect failover, when the new + /// primary replica is unknown. + /// Defaults to None. + pub replica_discovery_attempts: Option, + tls_config: Option, + tls_root_ca: Option, } impl DriverOptions { - /// Creates a credentials with username and password. Specifies the connection must use TLS - /// - /// # Arguments - /// - /// * `is_tls_enabled` — Specify whether the connection to TypeDB Server must be done over TLS. - /// * `tls_root_ca` — Path to the CA certificate to use for authenticating server certificates. - /// - /// # Examples - /// - /// ```rust - /// DriverOptions::new(true, Some(&path_to_ca)); - ///``` - pub fn new(is_tls_enabled: bool, tls_root_ca: Option<&Path>) -> crate::Result { - let tls_config = Some(if let Some(tls_root_ca) = tls_root_ca { + pub fn new() -> Self { + Self::default() + } + + /// Specifies whether the connection to TypeDB must be done over TLS. + pub fn is_tls_enabled(self, is_tls_enabled: bool) -> Self { + Self { is_tls_enabled, ..self } + } + + /// Specifies the root CA used in the TLS config for server certificates authentication. + /// Uses system roots if None is set. See [`Self::is_tls_enabled`] to enable or disable TLS. + pub fn tls_root_ca(mut self, tls_root_ca: Option<&Path>) -> crate::Result { + self.set_tls_root_ca(tls_root_ca)?; + Ok(self) + } + + /// Specifies the root CA used in the TLS config for server certificates authentication. + /// Uses system roots if None is set. See [`Self::is_tls_enabled`] to enable or disable TLS. + pub fn set_tls_root_ca(&mut self, tls_root_ca: Option<&Path>) -> crate::Result { + let tls_config = if let Some(tls_root_ca) = tls_root_ca { + self.tls_root_ca = Some(tls_root_ca.to_path_buf()); ClientTlsConfig::new().ca_certificate(Certificate::from_pem(fs::read_to_string(tls_root_ca)?)) } else { + self.tls_root_ca = None; ClientTlsConfig::new().with_native_roots() - }); + }; + self.tls_config = Some(tls_config); + Ok(()) + } - Ok(Self { is_tls_enabled, tls_config }) + /// Retrieves the TLS config of this options object if configured. + pub fn get_tls_config(&self) -> Option<&ClientTlsConfig> { + self.tls_config.as_ref() } - /// Retrieves whether TLS is enabled for the connection. - pub fn is_tls_enabled(&self) -> bool { - self.is_tls_enabled + /// Retrieves the TLS root CA path of this options object if configured. + pub fn get_tls_root_ca(&self) -> Option<&Path> { + self.tls_root_ca.as_deref() } - pub fn tls_config(&self) -> &Option { - &self.tls_config + /// Specifies whether the connection to TypeDB can use cluster replicas provided by the server + /// or it should be limited to the provided address. + /// If set to false, restricts the driver to only a single address. + pub fn use_replication(self, use_replication: bool) -> Self { + Self { use_replication, ..self } + } + + /// Limits the number of attempts to redirect a strongly consistent request to another + /// primary replica in case of a failure due to the change of replica roles. + /// Defaults to 1. + pub fn primary_failover_retries(self, primary_failover_retries: usize) -> Self { + Self { primary_failover_retries, ..self } + } + + /// Limits the number of driver attempts to discover a single working replica to perform an + /// operation in case of a replica unavailability. Every replica is tested once, which means + /// that at most: + /// - {limit} operations are performed if the limit <= the number of replicas. + /// - {number of replicas} operations are performed if the limit > the number of replicas. + /// - {number of replicas} operations are performed if the limit is None. + /// Affects every eventually consistent operation, including redirect failover, when the new + /// primary replica is unknown. + /// Defaults to None. + pub fn replica_discovery_attempts(self, replica_discovery_attempts: Option) -> Self { + Self { replica_discovery_attempts, ..self } + } +} + +impl Default for DriverOptions { + fn default() -> Self { + Self { + is_tls_enabled: DEFAULT_IS_TLS_ENABLED, + use_replication: DEFAULT_USE_REPLICATION, + primary_failover_retries: DEFAULT_REDIRECT_FAILOVER_RETRIES, + replica_discovery_attempts: DEFAULT_DISCOVERY_FAILOVER_RETRIES, + + tls_config: DEFAULT_TLS_CONFIG, + tls_root_ca: DEFAULT_TLS_ROOT_CA_PATH, + } } } diff --git a/rust/src/connection/message.rs b/rust/src/connection/message.rs index 20239740ca..dc785e8498 100644 --- a/rust/src/connection/message.rs +++ b/rust/src/connection/message.rs @@ -30,8 +30,9 @@ use crate::{ concept_row::ConceptRowHeader, QueryType, }, - common::{address::Address, info::DatabaseInfo, RequestID}, + common::{info::DatabaseInfo, RequestID}, concept::Concept, + connection::{server_replica::ServerReplica, server_version::ServerVersion}, error::ServerError, info::UserInfo, Credentials, QueryOptions, TransactionOptions, TransactionType, @@ -42,6 +43,9 @@ pub(super) enum Request { ConnectionOpen { driver_lang: String, driver_version: String, credentials: Credentials }, ServersAll, + ServersRegister { replica_id: u64, address: String }, + ServersDeregister { replica_id: u64 }, + ServerVersion, DatabasesAll, DatabaseGet { database_name: String }, @@ -69,11 +73,16 @@ pub(super) enum Response { ConnectionOpen { connection_id: Uuid, server_duration_millis: u64, - databases: Vec, + servers: Vec, }, ServersAll { - servers: Vec
, + servers: Vec, + }, + ServersRegister, + ServersDeregister, + ServerVersion { + server_version: ServerVersion, }, DatabasesContains { diff --git a/rust/src/connection/mod.rs b/rust/src/connection/mod.rs index 25a8d4ecaa..86cad4a4a8 100644 --- a/rust/src/connection/mod.rs +++ b/rust/src/connection/mod.rs @@ -18,7 +18,11 @@ */ pub(crate) use self::transaction_stream::TransactionStream; -pub use self::{credentials::Credentials, driver_options::DriverOptions}; +pub use self::{ + credentials::Credentials, + driver_options::DriverOptions, + server::{server_replica, server_version}, +}; mod credentials; pub(crate) mod database; @@ -26,5 +30,5 @@ mod driver_options; mod message; mod network; pub(crate) mod runtime; -pub(crate) mod server_connection; +pub(crate) mod server; pub(crate) mod transaction_stream; diff --git a/rust/src/connection/network/channel.rs b/rust/src/connection/network/channel.rs index a96ec9bddd..4f43ef058f 100644 --- a/rust/src/connection/network/channel.rs +++ b/rust/src/connection/network/channel.rs @@ -22,7 +22,6 @@ use std::sync::{Arc, RwLock}; use tonic::{ body::BoxBody, client::GrpcService, - metadata::MetadataValue, service::{ interceptor::{InterceptedService, ResponseFuture as InterceptorResponseFuture}, Interceptor, @@ -33,7 +32,8 @@ use tonic::{ use crate::{ common::{address::Address, Result, StdResult}, - Credentials, DriverOptions, + error::ConnectionError, + Credentials, DriverOptions, Error, }; type ResponseFuture = InterceptorResponseFuture; @@ -53,9 +53,11 @@ pub(super) fn open_callcred_channel( driver_options: DriverOptions, ) -> Result<(CallCredChannel, Arc)> { let mut builder = Channel::builder(address.into_uri()); - if driver_options.is_tls_enabled() { - let tls_config = - driver_options.tls_config().clone().expect("TLS config object must be set when TLS is enabled"); + if driver_options.is_tls_enabled { + let tls_config = driver_options + .get_tls_config() + .cloned() + .ok_or_else(|| Error::Connection(ConnectionError::MissingTlsConfigForTls))?; builder = builder.tls_config(tls_config)?; } let channel = builder.connect_lazy(); diff --git a/rust/src/connection/network/proto/common.rs b/rust/src/connection/network/proto/common.rs index b4b9f43483..602581a8c7 100644 --- a/rust/src/connection/network/proto/common.rs +++ b/rust/src/connection/network/proto/common.rs @@ -19,7 +19,7 @@ use typedb_protocol::{ options::{Query as QueryOptionsProto, Transaction as TransactionOptionsProto}, - transaction, Options, + transaction, }; use super::{IntoProto, TryFromProto}; diff --git a/rust/src/connection/network/proto/concept.rs b/rust/src/connection/network/proto/concept.rs index fb4c3c4c3e..a4b6fe3141 100644 --- a/rust/src/connection/network/proto/concept.rs +++ b/rust/src/connection/network/proto/concept.rs @@ -21,7 +21,6 @@ use std::{collections::HashMap, str::FromStr}; use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, TimeZone as ChronoTimeZone}; use chrono_tz::Tz; -use futures::TryFutureExt; use itertools::Itertools; use typedb_protocol::{ concept, diff --git a/rust/src/connection/network/proto/database.rs b/rust/src/connection/network/proto/database.rs index 306845bca6..935a26e392 100644 --- a/rust/src/connection/network/proto/database.rs +++ b/rust/src/connection/network/proto/database.rs @@ -17,31 +17,13 @@ * under the License. */ -use itertools::Itertools; -use typedb_protocol::{database_replicas::Replica as ReplicaProto, DatabaseReplicas as DatabaseProto}; +use typedb_protocol::Database as DatabaseProto; use super::TryFromProto; -use crate::common::{ - info::{DatabaseInfo, ReplicaInfo}, - Result, -}; +use crate::common::{info::DatabaseInfo, Result}; impl TryFromProto for DatabaseInfo { fn try_from_proto(proto: DatabaseProto) -> Result { - Ok(Self { - name: proto.name, - replicas: proto.replicas.into_iter().map(ReplicaInfo::try_from_proto).try_collect()?, - }) - } -} - -impl TryFromProto for ReplicaInfo { - fn try_from_proto(proto: ReplicaProto) -> Result { - Ok(Self { - server: proto.address.parse()?, - is_primary: proto.primary, - is_preferred: proto.preferred, - term: proto.term, - }) + Ok(Self { name: proto.name }) } } diff --git a/rust/src/connection/network/proto/message.rs b/rust/src/connection/network/proto/message.rs index eb8a81da0c..742a294847 100644 --- a/rust/src/connection/network/proto/message.rs +++ b/rust/src/connection/network/proto/message.rs @@ -19,7 +19,7 @@ use itertools::Itertools; use typedb_protocol::{ - authentication, connection, database, database_manager, migration, query::initial_res::Res, server_manager, + authentication, connection, database, database_manager, migration, query::initial_res::Res, server, server_manager, transaction, user, user_manager, Version::Version, }; use uuid::Uuid; @@ -28,9 +28,13 @@ use super::{FromProto, IntoProto, TryFromProto, TryIntoProto}; use crate::{ answer::{concept_document::ConceptDocumentHeader, concept_row::ConceptRowHeader, QueryType}, common::{info::DatabaseInfo, RequestID, Result}, - connection::message::{ - DatabaseExportResponse, DatabaseImportRequest, QueryRequest, QueryResponse, Request, Response, - TransactionRequest, TransactionResponse, + connection::{ + message::{ + DatabaseExportResponse, DatabaseImportRequest, QueryRequest, QueryResponse, Request, Response, + TransactionRequest, TransactionResponse, + }, + server_replica::ServerReplica, + server_version::ServerVersion, }, error::{ConnectionError, InternalError, ServerError}, info::UserInfo, @@ -60,6 +64,33 @@ impl TryIntoProto for Request { } } +impl TryIntoProto for Request { + fn try_into_proto(self) -> Result { + match self { + Self::ServersRegister { replica_id, address } => Ok(server_manager::register::Req { replica_id, address }), + other => Err(InternalError::UnexpectedRequestType { request_type: format!("{other:?}") }.into()), + } + } +} + +impl TryIntoProto for Request { + fn try_into_proto(self) -> Result { + match self { + Self::ServersDeregister { replica_id } => Ok(server_manager::deregister::Req { replica_id }), + other => Err(InternalError::UnexpectedRequestType { request_type: format!("{other:?}") }.into()), + } + } +} + +impl TryIntoProto for Request { + fn try_into_proto(self) -> Result { + match self { + Self::ServerVersion => Ok(server::version::Req {}), + other => Err(InternalError::UnexpectedRequestType { request_type: format!("{other:?}") }.into()), + } + } +} + impl TryIntoProto for Request { fn try_into_proto(self) -> Result { match self { @@ -285,15 +316,23 @@ impl TryIntoProto for Credentials { impl TryFromProto for Response { fn try_from_proto(proto: connection::open::Res) -> Result { - let mut database_infos = Vec::new(); - for database_info_proto in proto.databases_all.expect("Expected databases data").databases { - database_infos.push(DatabaseInfo::try_from_proto(database_info_proto)?); + let mut servers = Vec::new(); + for server_proto in + proto.servers_all.ok_or(ConnectionError::MissingResponseField { field: "servers_all" })?.servers + { + servers.push(ServerReplica::try_from_proto(server_proto)?); } Ok(Self::ConnectionOpen { - connection_id: Uuid::from_slice(proto.connection_id.expect("Expected connection id").id.as_slice()) - .unwrap(), + connection_id: Uuid::from_slice( + proto + .connection_id + .ok_or(ConnectionError::MissingResponseField { field: "connection_id" })? + .id + .as_slice(), + ) + .expect("Expected connection id creation"), server_duration_millis: proto.server_duration_millis, - databases: database_infos, + servers, }) } } @@ -301,11 +340,30 @@ impl TryFromProto for Response { impl TryFromProto for Response { fn try_from_proto(proto: server_manager::all::Res) -> Result { let server_manager::all::Res { servers } = proto; - let servers = servers.into_iter().map(|server| server.address.parse()).try_collect()?; + let servers = servers.into_iter().map(|server| ServerReplica::try_from_proto(server)).try_collect()?; Ok(Self::ServersAll { servers }) } } +impl TryFromProto for Response { + fn try_from_proto(_proto: server_manager::register::Res) -> Result { + Ok(Self::ServersRegister) + } +} + +impl TryFromProto for Response { + fn try_from_proto(_proto: server_manager::deregister::Res) -> Result { + Ok(Self::ServersDeregister) + } +} + +impl TryFromProto for Response { + fn try_from_proto(proto: server::version::Res) -> Result { + let server::version::Res { distribution, version } = proto; + Ok(Self::ServerVersion { server_version: ServerVersion { distribution, version } }) + } +} + impl TryFromProto for Response { fn try_from_proto(proto: database_manager::all::Res) -> Result { let database_manager::all::Res { databases } = proto; @@ -331,7 +389,11 @@ impl FromProto for Response { impl TryFromProto for Response { fn try_from_proto(proto: database_manager::create::Res) -> Result { - Ok(Self::DatabaseCreate { database: DatabaseInfo::try_from_proto(proto.database.unwrap())? }) + Ok(Self::DatabaseCreate { + database: DatabaseInfo::try_from_proto( + proto.database.ok_or(ConnectionError::MissingResponseField { field: "database" })?, + )?, + }) } } diff --git a/rust/src/connection/network/proto/mod.rs b/rust/src/connection/network/proto/mod.rs index bf307ee5cd..ffa886e5a2 100644 --- a/rust/src/connection/network/proto/mod.rs +++ b/rust/src/connection/network/proto/mod.rs @@ -23,6 +23,7 @@ mod common; mod concept; mod database; mod message; +mod server; mod user; pub(super) trait IntoProto { diff --git a/rust/src/connection/network/proto/server.rs b/rust/src/connection/network/proto/server.rs new file mode 100644 index 0000000000..7b2d6a55a0 --- /dev/null +++ b/rust/src/connection/network/proto/server.rs @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +use typedb_protocol::{ + server::{version::Res as VersionProto, ReplicaStatus as ReplicaStatusProto}, + Server as ServerProto, +}; + +use super::TryFromProto; +use crate::{ + common::Result, + connection::{ + server_replica::{ReplicaStatus, ReplicaType, ServerReplica}, + server_version::ServerVersion, + }, + error::ConnectionError, +}; + +impl TryFromProto for ServerReplica { + fn try_from_proto(proto: ServerProto) -> Result { + let address = proto.address.parse()?; + let replica_status = match proto.replica_status { + Some(replica_status) => ReplicaStatus::try_from_proto(replica_status)?, + None => ReplicaStatus::default(), + }; + Ok(Self::from_private(address, replica_status)) + } +} + +impl TryFromProto for ReplicaStatus { + fn try_from_proto(proto: ReplicaStatusProto) -> Result { + Ok(Self { + id: proto.replica_id, + replica_type: ReplicaType::try_from_proto(proto.replica_type)?, + term: proto.term, + }) + } +} + +impl TryFromProto for ReplicaType { + fn try_from_proto(replica_type: i32) -> Result { + match replica_type { + 0 => Ok(Self::Primary), + 1 => Ok(Self::Secondary), + _ => Err(ConnectionError::UnexpectedReplicaType { replica_type }.into()), + } + } +} + +impl TryFromProto for ServerVersion { + fn try_from_proto(proto: VersionProto) -> Result { + Ok(Self { distribution: proto.distribution, version: proto.version }) + } +} diff --git a/rust/src/connection/network/stub.rs b/rust/src/connection/network/stub.rs index 9da178c79b..929d12c25f 100644 --- a/rust/src/connection/network/stub.rs +++ b/rust/src/connection/network/stub.rs @@ -20,12 +20,12 @@ use std::sync::Arc; use futures::{future::BoxFuture, FutureExt, TryFutureExt}; -use log::{debug, trace, warn}; +use log::{debug, trace}; use tokio::sync::mpsc::{unbounded_channel as unbounded_async, UnboundedSender}; use tokio_stream::wrappers::UnboundedReceiverStream; use tonic::{Response, Status, Streaming}; use typedb_protocol::{ - authentication, connection, database, database_manager, migration, server_manager, transaction, + connection, database, database_manager, migration, server, server_manager, transaction, type_db_client::TypeDbClient as GRPC, user, user_manager, }; @@ -89,6 +89,24 @@ impl RPCStub { self.single(|this| Box::pin(this.grpc.servers_all(req.clone()))).await } + pub(super) async fn servers_register( + &mut self, + req: server_manager::register::Req, + ) -> Result { + self.single(|this| Box::pin(this.grpc.servers_register(req.clone()))).await + } + + pub(super) async fn servers_deregister( + &mut self, + req: server_manager::deregister::Req, + ) -> Result { + self.single(|this| Box::pin(this.grpc.servers_deregister(req.clone()))).await + } + + pub(super) async fn server_version(&mut self, req: server::version::Req) -> Result { + self.single(|this| Box::pin(this.grpc.server_version(req.clone()))).await + } + pub(super) async fn databases_all( &mut self, req: database_manager::all::Req, diff --git a/rust/src/connection/network/transmitter/import.rs b/rust/src/connection/network/transmitter/import.rs index 892d2595c4..ffe062ed20 100644 --- a/rust/src/connection/network/transmitter/import.rs +++ b/rust/src/connection/network/transmitter/import.rs @@ -17,7 +17,7 @@ * under the License. */ -use std::{ops::Deref, sync::Arc, thread::sleep, time::Duration}; +use std::{sync::Arc, thread::sleep, time::Duration}; use futures::StreamExt; #[cfg(not(feature = "sync"))] diff --git a/rust/src/connection/network/transmitter/response_sink.rs b/rust/src/connection/network/transmitter/response_sink.rs index 561876e339..4c7d5dd227 100644 --- a/rust/src/connection/network/transmitter/response_sink.rs +++ b/rust/src/connection/network/transmitter/response_sink.rs @@ -20,13 +20,12 @@ use std::{fmt, fmt::Formatter, sync::Arc}; use crossbeam::channel::Sender as SyncOneshotSender; -use itertools::Either; use log::{debug, error}; use tokio::sync::{mpsc::UnboundedSender, oneshot::Sender as AsyncOneshotSender}; use crate::{ common::{RequestID, Result}, - error::{ConnectionError, InternalError}, + error::InternalError, Error, }; diff --git a/rust/src/connection/network/transmitter/rpc.rs b/rust/src/connection/network/transmitter/rpc.rs index 7fb87d0cb6..cb8b13b00a 100644 --- a/rust/src/connection/network/transmitter/rpc.rs +++ b/rust/src/connection/network/transmitter/rpc.rs @@ -115,6 +115,15 @@ impl RPCTransmitter { } Request::ServersAll => rpc.servers_all(request.try_into_proto()?).await.and_then(Response::try_from_proto), + Request::ServersRegister { .. } => { + rpc.servers_register(request.try_into_proto()?).await.and_then(Response::try_from_proto) + } + Request::ServersDeregister { .. } => { + rpc.servers_deregister(request.try_into_proto()?).await.and_then(Response::try_from_proto) + } + Request::ServerVersion => { + rpc.server_version(request.try_into_proto()?).await.and_then(Response::try_from_proto) + } Request::DatabasesAll => { rpc.databases_all(request.try_into_proto()?).await.and_then(Response::try_from_proto) @@ -143,7 +152,7 @@ impl RPCTransmitter { rpc.database_type_schema(request.try_into_proto()?).await.map(Response::from_proto) } Request::DatabaseExport { .. } => { - let mut response_source = rpc.database_export(request.try_into_proto()?).await?; + let response_source = rpc.database_export(request.try_into_proto()?).await?; Ok(Response::DatabaseExportStream { response_source }) } diff --git a/rust/src/connection/network/transmitter/transaction.rs b/rust/src/connection/network/transmitter/transaction.rs index 81465efadb..3f8f0a07d5 100644 --- a/rust/src/connection/network/transmitter/transaction.rs +++ b/rust/src/connection/network/transmitter/transaction.rs @@ -36,21 +36,16 @@ use log::{debug, error}; use prost::Message; #[cfg(not(feature = "sync"))] use tokio::sync::oneshot::channel as oneshot; -use tokio::{ - select, - sync::{ - mpsc::{error::SendError, unbounded_channel as unbounded_async, UnboundedReceiver, UnboundedSender}, - oneshot::{channel as oneshot_async, Sender as AsyncOneshotSender}, - }, - time::{sleep_until, Instant}, +use tokio::sync::{ + mpsc::{unbounded_channel as unbounded_async, UnboundedReceiver, UnboundedSender}, + oneshot::{channel as oneshot_async, Sender as AsyncOneshotSender}, }; use tonic::Streaming; use typedb_protocol::transaction::{self, res_part::ResPart, server::Server, stream_signal::res_part::State}; -use uuid::Uuid; #[cfg(feature = "sync")] use super::oneshot_blocking as oneshot; -use super::response_sink::{ImmediateHandler, ResponseSink, StreamResponse}; +use super::response_sink::{ResponseSink, StreamResponse}; use crate::{ common::{ box_promise, @@ -59,10 +54,9 @@ use crate::{ Callback, Promise, RequestID, Result, }, connection::{ - message::{QueryResponse, Request, Response, TransactionRequest, TransactionResponse}, + message::{QueryResponse, TransactionRequest, TransactionResponse}, network::proto::{FromProto, IntoProto, TryFromProto}, runtime::BackgroundRuntime, - server_connection::LatencyTracker, }, Error, }; @@ -235,7 +229,7 @@ impl TransactionTransmitter { shutdown_sink: UnboundedSender<()>, shutdown_signal: UnboundedReceiver<()>, ) { - let mut collector = ResponseCollector { + let collector = ResponseCollector { callbacks: Default::default(), is_open, error, diff --git a/rust/src/connection/server/mod.rs b/rust/src/connection/server/mod.rs new file mode 100644 index 0000000000..b068d0a425 --- /dev/null +++ b/rust/src/connection/server/mod.rs @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +pub(crate) mod server_connection; +pub(crate) mod server_manager; +pub mod server_replica; +pub mod server_version; diff --git a/rust/src/connection/server_connection.rs b/rust/src/connection/server/server_connection.rs similarity index 81% rename from rust/src/connection/server_connection.rs rename to rust/src/connection/server/server_connection.rs index 721a13083a..cd48f62776 100644 --- a/rust/src/connection/server_connection.rs +++ b/rust/src/connection/server/server_connection.rs @@ -18,7 +18,6 @@ */ use std::{ - collections::{HashMap, HashSet}, fmt, sync::{ atomic::{AtomicU64, Ordering}, @@ -31,19 +30,21 @@ use tokio::{sync::mpsc::UnboundedSender, time::Instant}; use uuid::Uuid; use crate::{ - common::{address::Address, RequestID}, + common::address::Address, connection::{ database::{export_stream::DatabaseExportStream, import_stream::DatabaseImportStream}, - message::{DatabaseImportRequest, Request, Response, TransactionRequest, TransactionResponse}, + message::{DatabaseImportRequest, Request, Response, TransactionRequest}, network::transmitter::{ DatabaseExportTransmitter, DatabaseImportTransmitter, RPCTransmitter, TransactionTransmitter, }, runtime::BackgroundRuntime, + server_replica::ServerReplica, + server_version::ServerVersion, TransactionStream, }, error::{ConnectionError, InternalError}, info::{DatabaseInfo, UserInfo}, - Credentials, DriverOptions, TransactionOptions, TransactionType, User, + Credentials, DriverOptions, Result, TransactionOptions, TransactionType, }; #[derive(Clone)] @@ -65,11 +66,11 @@ impl ServerConnection { driver_options: DriverOptions, driver_lang: &str, driver_version: &str, - ) -> crate::Result<(Self, Vec)> { + ) -> Result<(Self, Vec)> { let username = credentials.username().to_string(); let request_transmitter = Arc::new(RPCTransmitter::start(address, credentials.clone(), driver_options, &background_runtime)?); - let (connection_id, latency, database_info) = + let (connection_id, latency, servers) = Self::open_connection(&request_transmitter, driver_lang, driver_version, credentials).await?; let latency_tracker = LatencyTracker::new(latency); let server_connection = Self { @@ -80,7 +81,7 @@ impl ServerConnection { shutdown_senders: Default::default(), latency_tracker, }; - Ok((server_connection, database_info)) + Ok((server_connection, servers)) } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] @@ -89,7 +90,7 @@ impl ServerConnection { driver_lang: &str, driver_version: &str, credentials: Credentials, - ) -> crate::Result<(Uuid, Duration, Vec)> { + ) -> Result<(Uuid, Duration, Vec)> { let message = Request::ConnectionOpen { driver_lang: driver_lang.to_owned(), driver_version: driver_version.to_owned(), @@ -98,50 +99,60 @@ impl ServerConnection { let request_time = Instant::now(); match request_transmitter.request(message).await? { - Response::ConnectionOpen { connection_id, server_duration_millis, databases: database_info } => { + Response::ConnectionOpen { connection_id, server_duration_millis, servers } => { let latency = Instant::now().duration_since(request_time) - Duration::from_millis(server_duration_millis); - Ok((connection_id, latency, database_info)) + Ok((connection_id, latency, servers)) } other => Err(ConnectionError::UnexpectedResponse { response: format!("{other:?}") }.into()), } } - pub fn username(&self) -> &str { + pub(crate) fn username(&self) -> &str { self.username.as_str() } + pub(crate) fn force_close(&self) -> Result { + for sender in self.shutdown_senders.lock().unwrap().drain(..) { + let _ = sender.send(()); + } + self.request_transmitter.force_close() + } + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - async fn request(&self, request: Request) -> crate::Result { - if !self.background_runtime.is_open() { - return Err(ConnectionError::ServerConnectionIsClosed.into()); + pub(crate) async fn servers_all(&self) -> Result> { + match self.request(Request::ServersAll).await? { + Response::ServersAll { servers } => Ok(servers), + other => Err(InternalError::UnexpectedResponseType { response_type: format!("{other:?}") }.into()), } - self.request_transmitter.request(request).await } - fn request_blocking(&self, request: Request) -> crate::Result { - if !self.background_runtime.is_open() { - return Err(ConnectionError::ServerConnectionIsClosed.into()); + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub(crate) async fn servers_register(&self, replica_id: u64, address: String) -> Result { + match self.request(Request::ServersRegister { replica_id, address }).await? { + Response::ServersRegister => Ok(()), + other => Err(InternalError::UnexpectedResponseType { response_type: format!("{other:?}") }.into()), } - self.request_transmitter.request_blocking(request) } - pub(crate) fn force_close(&self) -> crate::Result { - for sender in self.shutdown_senders.lock().unwrap().drain(..) { - let _ = sender.send(()); + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub(crate) async fn servers_deregister(&self, replica_id: u64) -> Result { + match self.request(Request::ServersDeregister { replica_id }).await? { + Response::ServersDeregister => Ok(()), + other => Err(InternalError::UnexpectedResponseType { response_type: format!("{other:?}") }.into()), } - self.request_transmitter.force_close() } - pub(crate) fn servers_all(&self) -> crate::Result> { - match self.request_blocking(Request::ServersAll)? { - Response::ServersAll { servers } => Ok(servers), + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub(crate) async fn version(&self) -> Result { + match self.request(Request::ServerVersion).await? { + Response::ServerVersion { server_version: version } => Ok(version), other => Err(InternalError::UnexpectedResponseType { response_type: format!("{other:?}") }.into()), } } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn all_databases(&self) -> crate::Result> { + pub(crate) async fn all_databases(&self) -> Result> { match self.request(Request::DatabasesAll).await? { Response::DatabasesAll { databases } => Ok(databases), other => Err(InternalError::UnexpectedResponseType { response_type: format!("{other:?}") }.into()), @@ -149,35 +160,31 @@ impl ServerConnection { } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn get_database_replicas(&self, database_name: String) -> crate::Result { - match self.request(Request::DatabaseGet { database_name }).await? { - Response::DatabaseGet { database } => Ok(database), + pub(crate) async fn contains_database(&self, database_name: String) -> Result { + match self.request(Request::DatabasesContains { database_name }).await? { + Response::DatabasesContains { contains } => Ok(contains), other => Err(InternalError::UnexpectedResponseType { response_type: format!("{other:?}") }.into()), } } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn contains_database(&self, database_name: String) -> crate::Result { - match self.request(Request::DatabasesContains { database_name }).await? { - Response::DatabasesContains { contains } => Ok(contains), + pub(crate) async fn get_database(&self, database_name: String) -> Result { + match self.request(Request::DatabaseGet { database_name }).await? { + Response::DatabaseGet { database } => Ok(database), other => Err(InternalError::UnexpectedResponseType { response_type: format!("{other:?}") }.into()), } } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn create_database(&self, database_name: String) -> crate::Result { + pub(crate) async fn create_database(&self, database_name: String) -> Result { match self.request(Request::DatabaseCreate { database_name }).await? { - Response::DatabaseCreate { database } => Ok(database), + Response::DatabaseCreate { .. } => Ok(()), other => Err(InternalError::UnexpectedResponseType { response_type: format!("{other:?}") }.into()), } } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn import_database( - &self, - database_name: String, - schema: String, - ) -> crate::Result { + pub(crate) async fn import_database(&self, database_name: String, schema: String) -> Result { match self .request(Request::DatabaseImport(DatabaseImportRequest::Initial { name: database_name, schema })) .await? @@ -196,13 +203,13 @@ impl ServerConnection { } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn delete_database(&self, database_name: String) -> crate::Result { + pub(crate) async fn delete_database(&self, database_name: String) -> Result { self.request(Request::DatabaseDelete { database_name }).await?; Ok(()) } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn database_schema(&self, database_name: String) -> crate::Result { + pub(crate) async fn database_schema(&self, database_name: String) -> Result { match self.request(Request::DatabaseSchema { database_name }).await? { Response::DatabaseSchema { schema } => Ok(schema), other => Err(InternalError::UnexpectedResponseType { response_type: format!("{other:?}") }.into()), @@ -210,7 +217,7 @@ impl ServerConnection { } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn database_type_schema(&self, database_name: String) -> crate::Result { + pub(crate) async fn database_type_schema(&self, database_name: String) -> Result { match self.request(Request::DatabaseTypeSchema { database_name }).await? { Response::DatabaseTypeSchema { schema } => Ok(schema), other => Err(InternalError::UnexpectedResponseType { response_type: format!("{other:?}") }.into()), @@ -218,7 +225,7 @@ impl ServerConnection { } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn database_export(&self, database_name: String) -> crate::Result { + pub(crate) async fn database_export(&self, database_name: String) -> Result { match self.request(Request::DatabaseExport { database_name }).await? { Response::DatabaseExportStream { response_source } => { let transmitter = DatabaseExportTransmitter::new(self.background_runtime.clone(), response_source); @@ -237,7 +244,7 @@ impl ServerConnection { database_name: &str, transaction_type: TransactionType, options: TransactionOptions, - ) -> crate::Result { + ) -> Result { let network_latency = self.latency_tracker.current_latency(); let open_request_start = Instant::now(); @@ -245,13 +252,13 @@ impl ServerConnection { .request(Request::Transaction(TransactionRequest::Open { database: database_name.to_owned(), transaction_type, - options, + options: options.clone(), network_latency, })) .await? { Response::TransactionStream { - open_request_id: request_id, + open_request_id: _, request_sink, response_source, server_duration_millis, @@ -272,7 +279,7 @@ impl ServerConnection { } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn all_users(&self) -> crate::Result> { + pub(crate) async fn all_users(&self) -> Result> { match self.request(Request::UsersAll).await? { Response::UsersAll { users } => Ok(users), other => Err(InternalError::UnexpectedResponseType { response_type: format!("{other:?}") }.into()), @@ -280,7 +287,7 @@ impl ServerConnection { } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn get_user(&self, name: String) -> crate::Result> { + pub(crate) async fn get_user(&self, name: String) -> Result> { match self.request(Request::UsersGet { name }).await? { Response::UsersGet { user } => Ok(user), other => Err(InternalError::UnexpectedResponseType { response_type: format!("{other:?}") }.into()), @@ -288,7 +295,7 @@ impl ServerConnection { } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn contains_user(&self, name: String) -> crate::Result { + pub(crate) async fn contains_user(&self, name: String) -> Result { match self.request(Request::UsersContains { name }).await? { Response::UsersContain { contains } => Ok(contains), other => Err(InternalError::UnexpectedResponseType { response_type: format!("{other:?}") }.into()), @@ -296,7 +303,7 @@ impl ServerConnection { } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn create_user(&self, name: String, password: String) -> crate::Result { + pub(crate) async fn create_user(&self, name: String, password: String) -> Result { match self.request(Request::UsersCreate { user: UserInfo { name, password: Some(password) } }).await? { Response::UsersCreate => Ok(()), other => Err(InternalError::UnexpectedResponseType { response_type: format!("{other:?}") }.into()), @@ -304,7 +311,7 @@ impl ServerConnection { } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn update_password(&self, name: String, password: String) -> crate::Result { + pub(crate) async fn update_password(&self, name: String, password: String) -> Result { match self .request(Request::UsersUpdate { username: name, @@ -318,12 +325,27 @@ impl ServerConnection { } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn delete_user(&self, name: String) -> crate::Result { + pub(crate) async fn delete_user(&self, name: String) -> Result { match self.request(Request::UsersDelete { name }).await? { Response::UsersDelete => Ok(()), other => Err(InternalError::UnexpectedResponseType { response_type: format!("{other:?}") }.into()), } } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn request(&self, request: Request) -> Result { + if !self.background_runtime.is_open() { + return Err(ConnectionError::ServerConnectionIsClosed.into()); + } + self.request_transmitter.request(request).await + } + + fn request_blocking(&self, request: Request) -> Result { + if !self.background_runtime.is_open() { + return Err(ConnectionError::ServerConnectionIsClosed.into()); + } + self.request_transmitter.request_blocking(request) + } } impl fmt::Debug for ServerConnection { diff --git a/rust/src/connection/server/server_manager.rs b/rust/src/connection/server/server_manager.rs new file mode 100644 index 0000000000..fe5eab6344 --- /dev/null +++ b/rust/src/connection/server/server_manager.rs @@ -0,0 +1,504 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +use std::{ + collections::{HashMap, HashSet}, + fmt, + future::Future, + iter, + sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}, + time::Duration, +}; + +use itertools::{enumerate, Itertools}; +use log::debug; + +use crate::{ + common::{ + address::{address_translation::AddressTranslation, Address, Addresses}, + consistency_level::ConsistencyLevel, + }, + connection::{ + runtime::BackgroundRuntime, + server::{server_connection::ServerConnection, server_replica::ServerReplica}, + }, + error::{ConnectionError, InternalError}, + Credentials, DriverOptions, Error, Result, +}; + +pub(crate) struct ServerManager { + configured_addresses: Addresses, + replicas: RwLock>, + replica_connections: RwLock>, + connection_scheme: http::uri::Scheme, + address_translation: RwLock, + + background_runtime: Arc, + credentials: Credentials, + driver_options: DriverOptions, + driver_lang: String, + driver_version: String, +} + +impl ServerManager { + // TODO: Introduce a timer-based connections update to have a more actualized connections list + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub(crate) async fn new( + background_runtime: Arc, + addresses: Addresses, + credentials: Credentials, + driver_options: DriverOptions, + driver_lang: impl AsRef, + driver_version: impl AsRef, + ) -> Result { + if !driver_options.use_replication && addresses.len() > 1 { + return Err(ConnectionError::MultipleAddressesForNoReplicationMode { addresses: addresses.clone() }.into()); + } + + let is_https = addresses.addresses().all(|address| address.is_https()); + let is_http = addresses.addresses().all(|address| !address.is_https()); + if !is_https && !is_http { + return Err(ConnectionError::HttpHttpsMismatch { addresses: addresses.clone() }.into()); + } + + let connection_scheme = if is_https { http::uri::Scheme::HTTPS } else { http::uri::Scheme::HTTP }; + + let (source_connections, replicas) = Self::fetch_replicas_from_addresses( + background_runtime.clone(), + &addresses, + &connection_scheme, + credentials.clone(), + driver_options.clone(), + driver_lang.as_ref(), + driver_version.as_ref(), + driver_options.use_replication, + ) + .await?; + let address_translation = addresses.address_translation(); + + let server_manager = Self { + configured_addresses: addresses, + replicas: RwLock::new(replicas), + replica_connections: RwLock::new(source_connections), + connection_scheme, + address_translation: RwLock::new(address_translation), + background_runtime, + credentials, + driver_options, + driver_lang: driver_lang.as_ref().to_string(), + driver_version: driver_version.as_ref().to_string(), + }; + server_manager.update_replica_connections().await?; + Ok(server_manager) + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub(crate) async fn register_replica(&self, replica_id: u64, address: String) -> Result { + self.execute(ConsistencyLevel::Strong, |replica_connection| { + let address = address.clone(); + async move { replica_connection.servers_register(replica_id, address).await } + }) + .await + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub(crate) async fn deregister_replica(&self, replica_id: u64) -> Result { + self.execute(ConsistencyLevel::Strong, |replica_connection| async move { + replica_connection.servers_deregister(replica_id).await + }) + .await + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub(crate) fn update_address_translation(&self, addresses: Addresses) -> Result { + if !matches!(addresses, Addresses::Translated(_)) { + return Err(ConnectionError::AddressTranslationWithoutTranslation { addresses }.into()); + } + *self.address_translation.write().expect("Expected address translation write access") = + addresses.address_translation(); + Ok(()) + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn update_replica_connections(&self) -> Result { + let replicas = self.read_replicas().clone(); + let mut connection_errors = Vec::with_capacity(replicas.len()); + let mut new_replica_connections = HashMap::new(); + let connection_addresses: HashSet
= self.read_replica_connections().keys().cloned().collect(); + for replica in &replicas { + let private_address = replica.private_address().clone(); + if !connection_addresses.contains(&private_address) { + match self.new_replica_connection(replica.address().clone()).await { + Ok(replica_connection) => { + new_replica_connections.insert(private_address, replica_connection); + } + Err(err) => { + connection_errors.push(err); + } + } + } + } + + let replica_addresses: HashSet
= + replicas.into_iter().map(|replica| replica.private_address().clone()).collect(); + let mut replica_connections = self.write_replica_connections(); + replica_connections.retain(|address, _| replica_addresses.contains(address)); + replica_connections.extend(new_replica_connections); + + if replica_connections.is_empty() { + Err(self.server_connection_failed_err(None, connection_errors)) + } else { + Ok(()) + } + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn new_replica_connection(&self, address: Address) -> Result { + ServerConnection::new( + self.background_runtime.clone(), + address, + self.credentials.clone(), + self.driver_options.clone(), + self.driver_lang.as_ref(), + self.driver_version.as_ref(), + ) + .await + .map(|(replica_connection, _)| replica_connection) + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn record_new_replica_connection( + &self, + public_address: Address, + private_address: Address, + ) -> Result { + let replica_connection = self.new_replica_connection(public_address).await?; + let mut replica_connections = self.write_replica_connections(); + replica_connections.insert(private_address, replica_connection.clone()); + Ok(replica_connection) + } + + fn read_replica_connections(&self) -> RwLockReadGuard<'_, HashMap> { + self.replica_connections.read().expect("Expected server connections read access") + } + + fn write_replica_connections(&self) -> RwLockWriteGuard<'_, HashMap> { + self.replica_connections.write().expect("Expected server connections write access") + } + + fn read_replicas(&self) -> RwLockReadGuard<'_, HashSet> { + self.replicas.read().expect("Expected a read replica lock") + } + + fn read_address_translation(&self) -> RwLockReadGuard<'_, AddressTranslation> { + self.address_translation.read().expect("Expected address translation read access") + } + + pub(crate) fn force_close(&self) -> Result { + self.read_replica_connections().values().map(ServerConnection::force_close).try_collect().map_err(Into::into) + } + + fn replicas(&self) -> HashSet { + self.read_replicas().iter().cloned().collect() + } + + pub(crate) fn primary_replica(&self) -> Option { + self.read_replicas().iter().filter(|replica| replica.is_primary()).max_by_key(|replica| replica.term()).cloned() + } + + pub(crate) fn username(&self) -> Result { + match self.read_replica_connections().iter().next() { + Some((_, replica_connection)) => Ok(replica_connection.username().to_string()), + None => Err(ConnectionError::ServerConnectionIsClosed {}.into()), + } + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub(crate) async fn execute(&self, consistency_level: ConsistencyLevel, task: F) -> Result + where + F: Fn(ServerConnection) -> P, + P: Future>, + { + match consistency_level { + ConsistencyLevel::Strong => self.execute_strongly_consistent(task).await, + // TODO: Uncomment when reads from secondary replicas are implemented + // ConsistencyLevel::Eventual => self.execute_eventually_consistent(task).await, + ConsistencyLevel::Eventual => { + Err(InternalError::Unimplemented { details: "eventual consistency".to_string() }.into()) + } + ConsistencyLevel::ReplicaDependant { address } => { + if self.read_replicas().iter().find(|replica| replica.address() == &address).is_none() { + return Err(ConnectionError::UnknownDirectReplica { + address, + configured_addresses: self.configured_addresses.clone(), + } + .into()); + } + let private_address = + self.read_address_translation().to_private(&address).unwrap_or_else(|| address.clone()); + self.execute_on(&address, &private_address, false, &task).await + } + } + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn execute_strongly_consistent(&self, task: F) -> Result + where + F: Fn(ServerConnection) -> P, + P: Future>, + { + let mut primary_replica = match self.primary_replica() { + Some(replica) => replica, + None => self.seek_primary_replica_in(self.replicas()).await?, + }; + + let retries = self.driver_options.primary_failover_retries; + let mut connection_errors = Vec::with_capacity(retries + 1); + for _ in 0..=retries { + let private_address = primary_replica.private_address().clone(); + match self.execute_on(primary_replica.address(), &private_address, false, &task).await { + Err(Error::Connection(connection_error)) => { + let replicas_without_old_primary = + self.replicas().into_iter().filter(|replica| replica.private_address() != &private_address); + primary_replica = match &connection_error { + ConnectionError::ClusterReplicaNotPrimary => { + debug!("Could not connect to the primary replica: no longer primary. Retrying..."); + let replicas = iter::once(primary_replica).chain(replicas_without_old_primary); + self.seek_primary_replica_in(replicas).await? + } + err => { + debug!("Could not connect to the primary replica: {err:?}. Retrying..."); + self.seek_primary_replica_in(replicas_without_old_primary).await? + } + }; + + connection_errors.push(connection_error.into()); + + if primary_replica.private_address() == &private_address { + break; + } + } + res => return res, + } + } + Err(self.server_connection_failed_err(None, connection_errors)) + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn execute_eventually_consistent(&self, task: F) -> Result + where + F: Fn(ServerConnection) -> P, + P: Future>, + { + let replicas = self.replicas(); + self.execute_on_any(replicas, task).await + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn execute_on_any(&self, replicas: impl IntoIterator, task: F) -> Result + where + F: Fn(ServerConnection) -> P, + P: Future>, + { + let limit = self.driver_options.replica_discovery_attempts.unwrap_or(usize::MAX); + let mut connection_errors = Vec::new(); + for (attempt, replica) in enumerate(replicas.into_iter()) { + if attempt == limit { + break; + } + // TODO: If we only use eventual consistency, we won't ever reconnect to disconnected / + // new replicas. We need to think how to update the connections in this case. + match self.execute_on(replica.address(), replica.private_address(), true, &task).await { + Err(Error::Connection(ConnectionError::ClusterReplicaNotPrimary)) => { + return Err(ConnectionError::NotPrimaryOnReadOnly { address: replica.address().clone() }.into()); + } + Err(Error::Connection(err)) => { + debug!("Unable to connect to {}: {err:?}. May attempt the next server.", replica.address()); + connection_errors.push(err.into()); + } + res => return res, + } + } + Err(self.server_connection_failed_err(None, connection_errors)) + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn execute_on( + &self, + public_address: &Address, + private_address: &Address, + require_connected: bool, + task: &F, + ) -> Result + where + F: Fn(ServerConnection) -> P, + P: Future>, + { + let existing_connection = { self.read_replica_connections().get(private_address).cloned() }; + let replica_connection = match existing_connection { + Some(replica_connection) => replica_connection, + None => match require_connected { + false => self.record_new_replica_connection(public_address.clone(), private_address.clone()).await?, + true => { + debug!("Tried executing on {public_address}, but there is no connection to this replica."); + return Err(self.server_connection_failed_err( + Some(Addresses::from_address(public_address.clone())), + Vec::default(), + )); + } + }, + }; + task(replica_connection).await + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn seek_primary_replica_in( + &self, + source_replicas: impl IntoIterator, + ) -> Result { + self.execute_on_any(source_replicas, |replica_connection| async { + self.seek_primary_replica(replica_connection).await + }) + .await + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn seek_primary_replica(&self, replica_connection: ServerConnection) -> Result { + let address_translation = self.read_address_translation().clone(); + let replicas = + Self::fetch_replicas_from_connection(&replica_connection, &address_translation, &self.connection_scheme) + .await?; + *self.replicas.write().expect("Expected replicas write lock") = replicas; + if let Some(replica) = self.primary_replica() { + self.update_replica_connections().await?; + Ok(replica) + } else { + Err(self.server_connection_failed_err(None, Vec::default())) + } + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn fetch_replicas_from_addresses( + background_runtime: Arc, + addresses: &Addresses, + connection_scheme: &http::uri::Scheme, + credentials: Credentials, + driver_options: DriverOptions, + driver_lang: impl AsRef, + driver_version: impl AsRef, + use_replication: bool, + ) -> Result<(HashMap, HashSet)> { + let address_translation = addresses.address_translation(); + let mut errors = Vec::with_capacity(addresses.len()); + for address in addresses.addresses() { + let replica_connection = ServerConnection::new( + background_runtime.clone(), + address.clone(), + credentials.clone(), + driver_options.clone(), + driver_lang.as_ref(), + driver_version.as_ref(), + ) + .await; + match replica_connection { + Ok((replica_connection, replicas)) => { + debug!("Fetched replicas from configured address '{address}': {replicas:?}"); + let translated_replicas = + Self::translate_replicas(replicas, connection_scheme, &address_translation); + if use_replication { + let mut source_connections = HashMap::with_capacity(translated_replicas.len()); + source_connections.insert(address.clone(), replica_connection); + return Ok((source_connections, translated_replicas)); + } else { + if let Some(target_replica) = + translated_replicas.into_iter().find(|replica| replica.address() == address) + { + let source_connections = HashMap::from([(address.clone(), replica_connection)]); + return Ok((source_connections, HashSet::from([target_replica]))); + } + } + } + Err(Error::Connection(err)) => { + debug!("Unable to fetch replicas from {}: {err:?}. Attempting next server.", address); + errors.push(err); + } + Err(err) => return Err(err), + } + } + Err(ConnectionError::ServerConnectionFailed { + configured_addresses: addresses.clone(), + accessed_addresses: addresses.clone(), + details: errors.into_iter().join(";\n"), + } + .into()) + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub(crate) async fn fetch_replicas(&self) -> Result> { + // TODO: Update the replica cache? + self.execute(ConsistencyLevel::Strong, |replica_connection| { + let connection_scheme = self.connection_scheme.clone(); + let address_translation = self.read_address_translation().clone(); + async move { + Self::fetch_replicas_from_connection(&replica_connection, &address_translation, &connection_scheme) + .await + } + }) + .await + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn fetch_replicas_from_connection( + replica_connection: &ServerConnection, + address_translation: &AddressTranslation, + connection_scheme: &http::uri::Scheme, + ) -> Result> { + replica_connection + .servers_all() + .await + .map(|replicas| Self::translate_replicas(replicas, connection_scheme, address_translation)) + } + + fn translate_replicas( + replicas: impl IntoIterator, + connection_scheme: &http::uri::Scheme, + address_translation: &AddressTranslation, + ) -> HashSet { + replicas.into_iter().map(|replica| replica.translated(connection_scheme, address_translation)).collect() + } + + fn server_connection_failed_err(&self, accessed_addresses: Option, errors: Vec) -> Error { + let accessed_addresses = accessed_addresses.unwrap_or_else(|| { + Addresses::from_addresses(self.read_replicas().iter().map(|replica| replica.address().clone())) + }); + ConnectionError::ServerConnectionFailed { + configured_addresses: self.configured_addresses.clone(), + accessed_addresses, + details: errors.into_iter().join(";\n"), + } + .into() + } +} + +impl fmt::Debug for ServerManager { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ServerConnection").field("replicas", &self.replicas).finish() + } +} diff --git a/rust/src/connection/server/server_replica.rs b/rust/src/connection/server/server_replica.rs new file mode 100644 index 0000000000..6417e4f76e --- /dev/null +++ b/rust/src/connection/server/server_replica.rs @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +use crate::common::address::{address_translation::AddressTranslation, Address}; + +/// The metadata and state of an individual raft replica of a driver connection. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct ServerReplica { + private_address: Address, + public_address: Option
, + replica_status: ReplicaStatus, +} + +impl ServerReplica { + pub(crate) fn from_private(private_address: Address, replica_status: ReplicaStatus) -> Self { + Self { private_address, public_address: None, replica_status } + } + + pub(crate) fn translate_address( + &mut self, + connection_scheme: &http::uri::Scheme, + address_translation: &AddressTranslation, + ) { + if let Some(translated) = address_translation.to_public(self.private_address()) { + self.public_address = Some(translated); + } else if let Some(scheme) = self.address().uri_scheme() { + if scheme != connection_scheme { + self.public_address = Some(self.address().with_scheme(connection_scheme.clone())); + } + } + } + + pub(crate) fn translated( + mut self, + connection_scheme: &http::uri::Scheme, + address_translation: &AddressTranslation, + ) -> Self { + self.translate_address(connection_scheme, address_translation); + self + } + + pub(crate) fn private_address(&self) -> &Address { + &self.private_address + } + + /// Returns the id of this replica. + pub fn id(&self) -> u64 { + self.replica_status.id + } + + /// Returns the address this replica is hosted at. + pub fn address(&self) -> &Address { + self.public_address.as_ref().unwrap_or(&self.private_address) + } + + /// Returns whether this is the primary replica of the raft cluster or any of the supporting types. + pub fn replica_type(&self) -> ReplicaType { + self.replica_status.replica_type + } + + /// Checks whether this is the primary replica of the raft cluster. + pub fn is_primary(&self) -> bool { + matches!(self.replica_type(), ReplicaType::Primary) + } + + /// Returns the raft protocol ‘term’ of this replica. + pub fn term(&self) -> u64 { + self.replica_status.term + } +} + +/// The metadata and state of an individual server as a raft replica. +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub(crate) struct ReplicaStatus { + /// The id of this replica. + pub id: u64, + /// The role of this replica in the raft cluster. + pub replica_type: ReplicaType, + /// The raft protocol ‘term’ of this server replica. + pub term: u64, +} + +impl Default for ReplicaStatus { + fn default() -> Self { + Self { id: 0, replica_type: ReplicaType::Primary, term: 0 } + } +} + +/// This enum is used to specify the type of replica. +#[repr(C)] +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub enum ReplicaType { + Primary, + Secondary, +} diff --git a/rust/src/connection/server/server_version.rs b/rust/src/connection/server/server_version.rs new file mode 100644 index 0000000000..2214f691cc --- /dev/null +++ b/rust/src/connection/server/server_version.rs @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +use std::fmt; + +/// A full TypeDB server's version specification +#[derive(Debug, Clone)] +pub struct ServerVersion { + pub(crate) distribution: String, + pub(crate) version: String, +} + +impl ServerVersion { + /// Retrieves the server's distribution. + pub fn distribution(&self) -> &str { + &self.distribution + } + + /// Retrieves the server's version number. + pub fn version(&self) -> &str { + &self.version + } +} + +impl fmt::Display for ServerVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} {}", self.distribution, self.version) + } +} diff --git a/rust/src/connection/transaction_stream.rs b/rust/src/connection/transaction_stream.rs index 44aca8275f..69a09e664f 100644 --- a/rust/src/connection/transaction_stream.rs +++ b/rust/src/connection/transaction_stream.rs @@ -86,8 +86,8 @@ impl TransactionStream { self.type_ } - pub(crate) fn options(&self) -> TransactionOptions { - self.options + pub(crate) fn options(&self) -> &TransactionOptions { + &self.options } pub(crate) fn on_close(&self, callback: impl FnOnce(Option) + Send + Sync + 'static) { diff --git a/rust/src/database/database.rs b/rust/src/database/database.rs index 59a315ac24..ab6c97d567 100644 --- a/rust/src/database/database.rs +++ b/rust/src/database/database.rs @@ -17,68 +17,37 @@ * under the License. */ -#[cfg(not(feature = "sync"))] -use std::future::Future; use std::{ - collections::HashMap, - fmt, - fs::File, io::{BufWriter, Write}, path::Path, - sync::{ - atomic::{AtomicU64, Ordering}, - Arc, RwLock, - }, - thread::sleep, - time::Duration, + sync::Arc, }; -use itertools::Itertools; -use log::{debug, error}; use prost::Message; use crate::{ - common::{ - address::Address, - error::ConnectionError, - info::{DatabaseInfo, ReplicaInfo}, - Error, Result, - }, - connection::{database::export_stream::DatabaseExportStream, server_connection::ServerConnection}, + common::{consistency_level::ConsistencyLevel, info::DatabaseInfo, Error, Result}, + connection::server::server_manager::ServerManager, database::migration::{try_create_export_file, try_open_existing_export_file, DatabaseExportAnswer}, - driver::TypeDBDriver, - error::{InternalError, MigrationError}, - resolve, Transaction, TransactionOptions, TransactionType, + error::MigrationError, + resolve, }; -/// A TypeDB database +/// A TypeDB database. +#[derive(Debug, Clone)] pub struct Database { name: String, - replicas: RwLock>, - server_connections: HashMap, + server_manager: Arc, } impl Database { - const PRIMARY_REPLICA_TASK_MAX_RETRIES: usize = 10; - const FETCH_REPLICAS_MAX_RETRIES: usize = 10; - const WAIT_FOR_PRIMARY_REPLICA_SELECTION: Duration = Duration::from_secs(2); - - pub(super) fn new( - database_info: DatabaseInfo, - server_connections: HashMap, - ) -> Result { - let name = database_info.name.clone(); - let replicas = RwLock::new(Replica::try_from_info(database_info, &server_connections)?); - Ok(Self { name, replicas, server_connections }) + pub(super) fn new(database_info: DatabaseInfo, server_manager: Arc) -> Result { + Ok(Self { name: database_info.name, server_manager }) } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(super) async fn get(name: String, server_connections: HashMap) -> Result { - Ok(Self { - name: name.clone(), - replicas: RwLock::new(Replica::fetch_all(name, &server_connections).await?), - server_connections, - }) + pub(super) async fn get(name: String, server_manager: Arc) -> Result { + Ok(Self { name, server_manager }) } /// Retrieves the database name as a string. @@ -86,80 +55,117 @@ impl Database { self.name.as_str() } - /// Returns the `Replica` instances for this database. - /// _Only works in TypeDB Cloud / Enterprise_ + /// Deletes this database. Always uses strong consistency. /// /// # Examples /// /// ```rust - /// database.replicas_info() + #[cfg_attr(feature = "sync", doc = "database.delete();")] + #[cfg_attr(not(feature = "sync"), doc = "database.delete().await;")] /// ``` - pub fn replicas_info(&self) -> Vec { - self.replicas.read().unwrap().iter().map(Replica::to_info).collect() + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub async fn delete(self: Arc) -> Result { + self.delete_with_consistency(ConsistencyLevel::Strong).await + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn delete_with_consistency(self: Arc, consistency_level: ConsistencyLevel) -> Result { + self.server_manager + .execute(consistency_level, |server_connection| { + let name = self.name.clone(); + async move { server_connection.delete_database(name).await } + }) + .await } - /// Returns the primary replica for this database. - /// _Only works in TypeDB Cloud / Enterprise_ + /// Returns a full schema text as a valid TypeQL define query string, using default strong consistency. + /// + /// See [`Self::schema_with_consistency`] for more details and options. /// /// # Examples /// /// ```rust - /// database.primary_replica_info() + #[cfg_attr(feature = "sync", doc = "database.schema();")] + #[cfg_attr(not(feature = "sync"), doc = "database.schema().await;")] /// ``` - pub fn primary_replica_info(&self) -> Option { - self.primary_replica().map(|replica| replica.to_info()) + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub async fn schema(&self) -> Result { + self.schema_with_consistency(ConsistencyLevel::Strong).await } - /// Returns the preferred replica for this database. - /// Operations which can be run on any replica will prefer to use this replica. - /// _Only works in TypeDB Cloud / Enterprise_ + /// Returns a full schema text as a valid TypeQL define query string. + /// + /// # Arguments + /// + /// * `consistency_level` — The consistency level to use for the operation /// /// # Examples /// /// ```rust - /// database.preferred_replica_info(); + #[cfg_attr(feature = "sync", doc = "database.schema_with_consistency(ConsistencyLevel::Strong);")] + #[cfg_attr(not(feature = "sync"), doc = "database.schema_with_consistency(ConsistencyLevel::Strong).await;")] /// ``` - pub fn preferred_replica_info(&self) -> Option { - self.preferred_replica().map(|replica| replica.to_info()) + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub async fn schema_with_consistency(&self, consistency_level: ConsistencyLevel) -> Result { + self.server_manager + .execute(consistency_level, |server_connection| { + let name = self.name.clone(); + async move { server_connection.database_schema(name).await } + }) + .await } - /// Deletes this database. + /// Returns the types in the schema as a valid TypeQL define query string, using default strong consistency. + /// + /// See [`Self::type_schema_with_consistency`] for more details and options. /// /// # Examples /// /// ```rust - #[cfg_attr(feature = "sync", doc = "database.delete();")] - #[cfg_attr(not(feature = "sync"), doc = "database.delete().await;")] + #[cfg_attr(feature = "sync", doc = "database.type_schema();")] + #[cfg_attr(not(feature = "sync"), doc = "database.type_schema().await;")] /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub async fn delete(self: Arc) -> Result { - self.run_on_primary_replica(|database| database.delete()).await + pub async fn type_schema(&self) -> Result { + self.type_schema_with_consistency(ConsistencyLevel::Strong).await } - /// Returns a full schema text as a valid TypeQL define query string. + /// Returns the types in the schema as a valid TypeQL define query string. + /// + /// # Arguments + /// + /// * `consistency_level` — The consistency level to use for the operation /// /// # Examples /// /// ```rust - #[cfg_attr(feature = "sync", doc = "database.schema();")] - #[cfg_attr(not(feature = "sync"), doc = "database.schema().await;")] + #[cfg_attr(feature = "sync", doc = "database.type_schema_with_consistency(ConsistencyLevel::Strong);")] + #[cfg_attr(not(feature = "sync"), doc = "database.type_schema_with_consistency(ConsistencyLevel::Strong).await;")] /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub async fn schema(&self) -> Result { - self.run_failsafe(|database| async move { database.schema().await }).await + pub async fn type_schema_with_consistency(&self, consistency_level: ConsistencyLevel) -> Result { + self.server_manager + .execute(consistency_level, |server_connection| { + let name = self.name.clone(); + async move { server_connection.database_type_schema(name).await } + }) + .await } - /// Returns the types in the schema as a valid TypeQL define query string. + /// Export a database into a schema definition and a data files saved to the disk, using default strong consistency. + /// This is a blocking operation and may take a significant amount of time depending on the database size. + /// + /// See [`Self::export_to_file_with_consistency`] for more details and options. /// /// # Examples /// /// ```rust - #[cfg_attr(feature = "sync", doc = "database.type_schema();")] - #[cfg_attr(not(feature = "sync"), doc = "database.type_schema().await;")] + #[cfg_attr(feature = "sync", doc = "database.export_to_file(schema_path, data_path);")] + #[cfg_attr(not(feature = "sync"), doc = "database.export_to_file(schema_path, data_path).await;")] /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub async fn type_schema(&self) -> Result { - self.run_failsafe(|database| async move { database.type_schema().await }).await + pub async fn export_to_file(&self, schema_file_path: impl AsRef, data_file_path: impl AsRef) -> Result { + self.export_to_file_with_consistency(schema_file_path, data_file_path, ConsistencyLevel::Strong).await } /// Export a database into a schema definition and a data files saved to the disk. @@ -169,15 +175,27 @@ impl Database { /// /// * `schema_file_path` — The path to the schema definition file to be created /// * `data_file_path` — The path to the data file to be created + /// * `consistency_level` — The consistency level to use for the operation /// /// # Examples /// /// ```rust - #[cfg_attr(feature = "sync", doc = "database.export_to_file(schema_path, data_path);")] - #[cfg_attr(not(feature = "sync"), doc = "database.export_to_file(schema_path, data_path).await;")] + #[cfg_attr( + feature = "sync", + doc = "database.export_to_file_with_consistency(schema_path, data_path, ConsistencyLevel::Strong);" + )] + #[cfg_attr( + not(feature = "sync"), + doc = "database.export_to_file_with_consistency(schema_path, data_path, ConsistencyLevel::Strong).await;" + )] /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub async fn export_to_file(&self, schema_file_path: impl AsRef, data_file_path: impl AsRef) -> Result { + pub async fn export_to_file_with_consistency( + &self, + schema_file_path: impl AsRef, + data_file_path: impl AsRef, + consistency_level: ConsistencyLevel, + ) -> Result { let schema_file_path = schema_file_path.as_ref(); let data_file_path = data_file_path.as_ref(); if schema_file_path == data_file_path { @@ -191,11 +209,37 @@ impl Database { } let result = self - .run_failsafe(|database| async move { - // File opening should be idempotent for multiple function invocations - let schema_file = try_open_existing_export_file(schema_file_path)?; - let data_file = try_open_existing_export_file(data_file_path)?; - database.export_to_file(schema_file, data_file).await + .server_manager + .execute(consistency_level, |server_connection| { + let name = self.name.clone(); + async move { + // File opening should be idempotent for multiple function invocations + let mut schema_file = try_open_existing_export_file(schema_file_path)?; + let data_file = try_open_existing_export_file(data_file_path)?; + let mut export_stream = server_connection.database_export(name).await?; + let mut data_writer = BufWriter::new(data_file); + + loop { + match resolve!(export_stream.next())? { + DatabaseExportAnswer::Done => break, + DatabaseExportAnswer::Schema(schema) => { + schema_file.write_all(schema.as_bytes())?; + schema_file.flush()?; + } + DatabaseExportAnswer::Items(items) => { + for item in items { + let mut buf = Vec::new(); + item.encode_length_delimited(&mut buf) + .map_err(|_| Error::Migration(MigrationError::CannotEncodeExportedConcept))?; + data_writer.write_all(&buf)?; + } + } + } + } + + data_writer.flush()?; + Ok(()) + } }) .await; @@ -205,276 +249,4 @@ impl Database { } result } - - #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn run_failsafe(&self, task: F) -> Result - where - F: Fn(ServerDatabase) -> P, - P: Future>, - { - match self.run_on_any_replica(&task).await { - Err(Error::Connection(ConnectionError::ClusterReplicaNotPrimary)) => { - debug!("Attempted to run on a non-primary replica, retrying on primary..."); - self.run_on_primary_replica(&task).await - } - res => res, - } - } - - #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(super) async fn run_on_any_replica(&self, task: F) -> Result - where - F: Fn(ServerDatabase) -> P, - P: Future>, - { - let replicas = self.replicas.read().unwrap().clone(); - for replica in replicas { - match task(replica.database.clone()).await { - Err(Error::Connection( - ConnectionError::ServerConnectionFailedStatusError { .. } | ConnectionError::ConnectionFailed, - )) => { - debug!("Unable to connect to {}. Attempting next server.", replica.server); - } - res => return res, - } - } - Err(Self::unable_to_connect_error(&self.server_connections)) - } - - #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(super) async fn run_on_primary_replica(&self, task: F) -> Result - where - F: Fn(ServerDatabase) -> P, - P: Future>, - { - let mut primary_replica = - if let Some(replica) = self.primary_replica() { replica } else { self.seek_primary_replica().await? }; - - for _ in 0..Self::PRIMARY_REPLICA_TASK_MAX_RETRIES { - match task(primary_replica.database.clone()).await { - Err(Error::Connection( - ConnectionError::ClusterReplicaNotPrimary - | ConnectionError::ServerConnectionFailedStatusError { .. } - | ConnectionError::ConnectionFailed, - )) => { - debug!("Primary replica error, waiting..."); - Self::wait_for_primary_replica_selection().await; - primary_replica = self.seek_primary_replica().await?; - } - res => return res, - } - } - Err(Self::unable_to_connect_error(&self.server_connections)) - } - - #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - async fn seek_primary_replica(&self) -> Result { - for _ in 0..Self::FETCH_REPLICAS_MAX_RETRIES { - let replicas = Replica::fetch_all(self.name.clone(), &self.server_connections).await?; - *self.replicas.write().unwrap() = replicas; - if let Some(replica) = self.primary_replica() { - return Ok(replica); - } - Self::wait_for_primary_replica_selection().await; - } - Err(Self::unable_to_connect_error(&self.server_connections)) - } - - fn unable_to_connect_error(server_connections: &HashMap) -> Error { - Error::Connection(ConnectionError::ServerConnectionFailed { - addresses: server_connections.keys().map(Address::clone).collect_vec(), - }) - } - - fn primary_replica(&self) -> Option { - self.replicas.read().unwrap().iter().filter(|r| r.is_primary).max_by_key(|r| r.term).cloned() - } - - fn preferred_replica(&self) -> Option { - self.replicas.read().unwrap().iter().filter(|r| r.is_preferred).max_by_key(|r| r.term).cloned() - } - - #[cfg(feature = "sync")] - fn wait_for_primary_replica_selection() { - sleep(Self::WAIT_FOR_PRIMARY_REPLICA_SELECTION); - } - - #[cfg(not(feature = "sync"))] - async fn wait_for_primary_replica_selection() { - tokio::time::sleep(Self::WAIT_FOR_PRIMARY_REPLICA_SELECTION).await - } -} - -impl fmt::Debug for Database { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Database").field("name", &self.name).field("replicas", &self.replicas).finish() - } -} - -/// The metadata and state of an individual raft replica of a database. -#[derive(Clone)] -pub(super) struct Replica { - /// The server hosting this replica - server: Address, - /// Retrieves the database name for which this is a replica - database_name: String, - /// Checks whether this is the primary replica of the raft cluster. - is_primary: bool, - /// The raft protocol ‘term’ of this replica. - term: i64, - /// Checks whether this is the preferred replica of the raft cluster. If true, Operations which can be run on any replica will prefer to use this replica. - is_preferred: bool, - /// Retrieves the database for which this is a replica - database: ServerDatabase, -} - -impl Replica { - fn new(name: String, metadata: ReplicaInfo, server_connection: ServerConnection) -> Self { - Self { - server: metadata.server, - database_name: name.clone(), - is_primary: metadata.is_primary, - term: metadata.term, - is_preferred: metadata.is_preferred, - database: ServerDatabase::new(name, server_connection), - } - } - - fn try_from_info( - database_info: DatabaseInfo, - server_connections: &HashMap, - ) -> Result> { - database_info - .replicas - .into_iter() - .map(|replica| { - if server_connections.len() == 1 { - Ok(Self::new( - database_info.name.clone(), - replica, - server_connections.values().next().unwrap().clone(), - )) - } else { - // TODO: actually check the advertised == provided, if that is the strategy we want - let server_connection = server_connections - .get(&replica.server) - .ok_or_else(|| InternalError::UnknownServer { server: replica.server.clone() })?; - Ok(Self::new(database_info.name.clone(), replica, server_connection.clone())) - } - }) - .collect() - } - - fn to_info(&self) -> ReplicaInfo { - ReplicaInfo { - server: self.server.clone(), - is_primary: self.is_primary, - is_preferred: self.is_preferred, - term: self.term, - } - } - - #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - async fn fetch_all(name: String, server_connections: &HashMap) -> Result> { - for (server, server_connection) in server_connections { - let res = server_connection.get_database_replicas(name.clone()).await; - match res { - Ok(info) => { - return Self::try_from_info(info, server_connections); - } - Err(Error::Connection( - ConnectionError::DatabaseNotFound { .. } - | ConnectionError::ServerConnectionFailedStatusError { .. } - | ConnectionError::ConnectionFailed, - )) => { - error!( - "Failed to fetch replica info for database '{}' from {}. Attempting next server.", - name, server - ); - } - Err(err) => return Err(err), - } - } - Err(Database::unable_to_connect_error(server_connections)) - } -} - -impl fmt::Debug for Replica { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Replica") - .field("server", &self.server) - .field("database_name", &self.database_name) - .field("is_primary", &self.is_primary) - .field("term", &self.term) - .field("is_preferred", &self.is_preferred) - .finish() - } -} - -#[derive(Clone, Debug)] -pub(crate) struct ServerDatabase { - name: String, - connection: ServerConnection, -} - -impl ServerDatabase { - fn new(name: String, connection: ServerConnection) -> Self { - Self { name, connection } - } - - pub fn name(&self) -> &str { - self.name.as_str() - } - - pub(crate) fn connection(&self) -> &ServerConnection { - &self.connection - } - - #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - async fn delete(self) -> Result { - self.connection.delete_database(self.name).await - } - - #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - async fn schema(&self) -> Result { - self.connection.database_schema(self.name.clone()).await - } - - #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - async fn type_schema(&self) -> Result { - self.connection.database_type_schema(self.name.clone()).await - } - - #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - async fn export_to_file(&self, mut schema_file: File, data_file: File) -> Result { - let mut export_stream = self.connection.database_export(self.name.clone()).await?; - let mut data_writer = BufWriter::new(data_file); - - loop { - match resolve!(export_stream.next())? { - DatabaseExportAnswer::Done => break, - DatabaseExportAnswer::Schema(schema) => { - schema_file.write_all(schema.as_bytes())?; - schema_file.flush()?; - } - DatabaseExportAnswer::Items(items) => { - for item in items { - let mut buf = Vec::new(); - item.encode_length_delimited(&mut buf) - .map_err(|_| Error::Migration(MigrationError::CannotEncodeExportedConcept))?; - data_writer.write_all(&buf)?; - } - } - } - } - - data_writer.flush()?; - Ok(()) - } -} - -impl fmt::Display for ServerDatabase { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.name) - } } diff --git a/rust/src/database/database_manager.rs b/rust/src/database/database_manager.rs index 40ca837947..5f95383e16 100644 --- a/rust/src/database/database_manager.rs +++ b/rust/src/database/database_manager.rs @@ -17,49 +17,35 @@ * under the License. */ -#[cfg(not(feature = "sync"))] -use std::future::Future; -use std::{ - collections::HashMap, - io::{BufReader, BufWriter, Cursor, Read}, - path::Path, - sync::{Arc, RwLock}, -}; +use std::{io::BufReader, path::Path, sync::Arc}; -use prost::{decode_length_delimiter, Message}; +use itertools::Itertools; use typedb_protocol::migration::Item; use super::Database; use crate::{ - common::{address::Address, error::ConnectionError, Result}, - connection::server_connection::ServerConnection, + common::{consistency_level::ConsistencyLevel, Result}, + connection::server::server_manager::ServerManager, database::migration::{try_open_import_file, ProtoMessageIterator}, info::DatabaseInfo, - resolve, Error, + resolve, }; /// Provides access to all database management methods. #[derive(Debug)] pub struct DatabaseManager { - server_connections: HashMap, - databases_cache: RwLock>>, + server_manager: Arc, } /// Provides access to all database management methods. impl DatabaseManager { - pub(crate) fn new( - server_connections: HashMap, - database_info: Vec, - ) -> Result { - let mut databases = HashMap::new(); - for info in database_info { - let database = Database::new(info, server_connections.clone())?; - databases.insert(database.name().to_owned(), Arc::new(database)); - } - Ok(Self { server_connections, databases_cache: RwLock::new(databases) }) + pub(crate) fn new(server_manager: Arc) -> Result { + Ok(Self { server_manager }) } - /// Retrieves all databases present on the TypeDB server + /// Retrieves all databases present on the TypeDB server, using default strong consistency. + /// + /// See [`Self::all_with_consistency`] for more details and options. /// /// # Examples /// @@ -69,31 +55,38 @@ impl DatabaseManager { /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn all(&self) -> Result>> { - let mut error_buffer = Vec::with_capacity(self.server_connections.len()); - for (server_id, server_connection) in self.server_connections.iter() { - match server_connection.all_databases().await { - Ok(list) => { - let mut new_databases: Vec> = Vec::new(); - for db_info in list { - new_databases.push(Arc::new(Database::new(db_info, self.server_connections.clone())?)); - } - let mut databases = self.databases_cache.write().unwrap(); - databases.clear(); - databases - .extend(new_databases.iter().map(|database| (database.name().to_owned(), database.clone()))); - return Ok(new_databases); - } - Err(err) => error_buffer.push(format!("- {}: {}", server_id, err)), - } - } - Err(ConnectionError::ServerConnectionFailedWithError { error: error_buffer.join("\n") })? + self.all_with_consistency(ConsistencyLevel::Strong).await } - /// Retrieve the database with the given name. + /// Retrieves all databases present on the TypeDB server. /// /// # Arguments /// - /// * `name` — The name of the database to retrieve + /// * `consistency_level` — The consistency level to use for the operation + /// + /// # Examples + /// + /// ```rust + #[cfg_attr(feature = "sync", doc = "driver.databases().all_with_consistency(ConsistencyLevel::Strong);")] + #[cfg_attr(not(feature = "sync"), doc = "driver.databases().all_with_consistency(ConsistencyLevel::Strong).await;")] + /// ``` + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub async fn all_with_consistency(&self, consistency_level: ConsistencyLevel) -> Result>> { + self.server_manager + .execute(consistency_level, move |server_connection| async move { + server_connection + .all_databases() + .await? + .into_iter() + .map(|database_info| self.try_build_database(database_info)) + .try_collect() + }) + .await + } + + /// Retrieves the database with the given name, using default strong consistency. + /// + /// See [`Self::get_with_consistency`] for more details and options. /// /// # Examples /// @@ -102,27 +95,46 @@ impl DatabaseManager { #[cfg_attr(not(feature = "sync"), doc = "driver.databases().get(name).await;")] /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub async fn get(&self, name: impl AsRef) -> Result> { - let name = name.as_ref(); - - if !self.contains(name.to_owned()).await? { - self.databases_cache.write().unwrap().remove(name); - return Err(ConnectionError::DatabaseNotFound { name: name.to_owned() }.into()); - } - - if let Some(cached_database) = self.try_get_cached(name) { - return Ok(cached_database); - } - - self.cache_insert(Database::get(name.to_owned(), self.server_connections.clone()).await?); - Ok(self.try_get_cached(name).unwrap()) + pub async fn get(&self, name: impl Into) -> Result> { + self.get_with_consistency(name, ConsistencyLevel::Strong).await } - /// Checks if a database with the given name exists + /// Retrieves the database with the given name. /// /// # Arguments /// - /// * `name` — The database name to be checked + /// * `name` — The name of the database to retrieve + /// * `consistency_level` — The consistency level to use for the operation + /// + /// # Examples + /// + /// ```rust + #[cfg_attr(feature = "sync", doc = "driver.databases().get_with_consistency(name, ConsistencyLevel::Strong);")] + #[cfg_attr( + not(feature = "sync"), + doc = "driver.databases().get_with_consistency(name, ConsistencyLevel::Strong).await;" + )] + /// ``` + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub async fn get_with_consistency( + &self, + name: impl Into, + consistency_level: ConsistencyLevel, + ) -> Result> { + let name = name.into(); + let database_info = self + .server_manager + .execute(consistency_level, move |server_connection| { + let name = name.clone(); + async move { server_connection.get_database(name).await } + }) + .await?; + self.try_build_database(database_info) + } + + /// Checks if a database with the given name exists, using default strong consistency. + /// + /// See [`Self::contains_with_consistency`] for more details and options. /// /// # Examples /// @@ -132,15 +144,41 @@ impl DatabaseManager { /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn contains(&self, name: impl Into) -> Result { + self.contains_with_consistency(name, ConsistencyLevel::Strong).await + } + + /// Checks if a database with the given name exists. + /// + /// # Arguments + /// + /// * `name` — The database name to be checked + /// * `consistency_level` — The consistency level to use for the operation + /// + /// # Examples + /// + /// ```rust + #[cfg_attr(feature = "sync", doc = "driver.databases().contains_with_consistency(name, ConsistencyLevel::Strong);")] + #[cfg_attr( + not(feature = "sync"), + doc = "driver.databases().contains_with_consistency(name, ConsistencyLevel::Strong).await;" + )] + /// ``` + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub async fn contains_with_consistency( + &self, + name: impl Into, + consistency_level: ConsistencyLevel, + ) -> Result { let name = name.into(); - self.run_failsafe( - name, - |server_connection, name| async move { server_connection.contains_database(name).await }, - ) - .await + self.server_manager + .execute(consistency_level, move |server_connection| { + let name = name.clone(); + async move { server_connection.contains_database(name).await } + }) + .await } - /// Create a database with the given name + /// Creates a database with the given name. Always uses strong consistency. /// /// # Arguments /// @@ -154,16 +192,22 @@ impl DatabaseManager { /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn create(&self, name: impl Into) -> Result { + self.create_with_consistency(name, ConsistencyLevel::Strong).await + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn create_with_consistency(&self, name: impl Into, consistency_level: ConsistencyLevel) -> Result { let name = name.into(); - let database_info = self - .run_failsafe(name, |server_connection, name| async move { server_connection.create_database(name).await }) // TODO: run_failsafe produces additiona Connection error if the database name is incorrect. Is it ok? - .await?; - self.cache_insert(Database::new(database_info, self.server_connections.clone())?); - Ok(()) + self.server_manager + .execute(consistency_level, move |server_connection| { + let name = name.clone(); + async move { server_connection.create_database(name).await } + }) + .await } - /// Create a database with the given name based on previously exported another database's data - /// loaded from a file. + /// Creates a database with the given name based on previously exported another database's data + /// loaded from a file. Always uses strong consistency. /// This is a blocking operation and may take a significant amount of time depending on the /// database size. /// @@ -185,6 +229,17 @@ impl DatabaseManager { name: impl Into, schema: impl Into, data_file_path: impl AsRef, + ) -> Result { + self.import_from_file_with_consistency(name, schema, data_file_path, ConsistencyLevel::Strong).await + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn import_from_file_with_consistency( + &self, + name: impl Into, + schema: impl Into, + data_file_path: impl AsRef, + consistency_level: ConsistencyLevel, ) -> Result { const ITEM_BATCH_SIZE: usize = 250; @@ -193,74 +248,35 @@ impl DatabaseManager { let schema_ref: &str = schema.as_ref(); let data_file_path = data_file_path.as_ref(); - self.run_failsafe(name, |server_connection, name| async move { - let file = try_open_import_file(data_file_path)?; - let mut import_stream = server_connection.import_database(name, schema_ref.to_string()).await?; - - let mut item_buffer = Vec::with_capacity(ITEM_BATCH_SIZE); - let mut read_item_iterator = ProtoMessageIterator::::new(BufReader::new(file)); - - while let Some(item) = read_item_iterator.next() { - let item = item?; - item_buffer.push(item); - if item_buffer.len() >= ITEM_BATCH_SIZE { - import_stream.send_items(item_buffer.split_off(0))?; - } - } - - if !item_buffer.is_empty() { - import_stream.send_items(item_buffer)?; - } + self.server_manager + .execute(consistency_level, move |server_connection| { + let name = name.clone(); + async move { + let file = try_open_import_file(data_file_path)?; + let mut import_stream = server_connection.import_database(name, schema_ref.to_string()).await?; - resolve!(import_stream.done()) - }) - .await - } + let mut item_buffer = Vec::with_capacity(ITEM_BATCH_SIZE); + let mut read_item_iterator = ProtoMessageIterator::::new(BufReader::new(file)); - #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn get_cached_or_fetch(&self, name: &str) -> Result> { - match self.try_get_cached(name) { - Some(cached_database) => Ok(cached_database), - None => self.get(name).await, - } - } + while let Some(item) = read_item_iterator.next() { + let item = item?; + item_buffer.push(item); + if item_buffer.len() >= ITEM_BATCH_SIZE { + import_stream.send_items(item_buffer.split_off(0))?; + } + } - fn try_get_cached(&self, name: &str) -> Option> { - self.databases_cache.read().unwrap().get(name).cloned() - } + if !item_buffer.is_empty() { + import_stream.send_items(item_buffer)?; + } - fn cache_insert(&self, database: Database) { - self.databases_cache.write().unwrap().insert(database.name().to_owned(), Arc::new(database)); + resolve!(import_stream.done()) + } + }) + .await } - #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - async fn run_failsafe(&self, name: String, task: F) -> Result - where - F: Fn(ServerConnection, String) -> P, - P: Future>, - { - let mut error_buffer = Vec::with_capacity(self.server_connections.len()); - for (server_id, server_connection) in self.server_connections.iter() { - match task(server_connection.clone(), name.clone()).await { - Ok(res) => return Ok(res), - // TODO: database manager should never encounter NOT PRIMARY errors since we are failing over server connections, not replicas - - // Err(Error::Connection(ConnectionError::ClusterReplicaNotPrimary)) => { - // return Database::get(name, self.connection.clone()) - // .await? - // .run_on_primary_replica(|database| { - // let task = &task; - // async move { task(database.connection().clone(), database.name().to_owned()).await } - // }) - // .await - // } - err @ Err(Error::Connection(ConnectionError::ServerConnectionIsClosed)) => return err, - Err(err) => error_buffer.push(format!("- {}: {}", server_id, err)), - } - } - // TODO: With this, every operation fails with - // [CXN03] Connection Error: Unable to connect to TypeDB server(s), received errors: .... - // Which is quite confusing as it's not really connected to connection. - Err(ConnectionError::ServerConnectionFailedWithError { error: error_buffer.join("\n") })? + fn try_build_database(&self, database_info: DatabaseInfo) -> Result> { + Database::new(database_info, self.server_manager.clone()).map(Arc::new) } } diff --git a/rust/src/database/migration.rs b/rust/src/database/migration.rs index 959f8124c6..5c3330e3bf 100644 --- a/rust/src/database/migration.rs +++ b/rust/src/database/migration.rs @@ -21,7 +21,7 @@ use std::{ cmp::max, collections::VecDeque, fs::{File, OpenOptions}, - io::{BufRead, BufWriter, Read, Write}, + io::BufRead, marker::PhantomData, path::Path, }; @@ -73,14 +73,14 @@ impl ProtoMessageIterator { return if self.buffer.is_empty() { Ok(None) } else { - Err(Error::Migration(MigrationError::CannotDecodeImportedConceptLength)) + Err(MigrationError::CannotDecodeImportedConceptLength.into()) }; } - Err(_) => return Err(Error::Migration(MigrationError::CannotDecodeImportedConceptLength)), + Err(_) => return Err(MigrationError::CannotDecodeImportedConceptLength.into()), Ok(_) => continue, } } else { - return Err(Error::Migration(MigrationError::CannotDecodeImportedConceptLength)); + return Err(MigrationError::CannotDecodeImportedConceptLength.into()); } } } @@ -107,12 +107,12 @@ impl Iterator for ProtoMessageIterator { let to_read = max(required, Self::BUF_CAPACITY); match self.try_read_more(to_read) { Ok(bytes_read) if bytes_read >= required => {} - _ => return Some(Err(Error::Migration(MigrationError::CannotDecodeImportedConcept))), + _ => return Some(Err(MigrationError::CannotDecodeImportedConcept.into())), } } let mut message_buf = self.get_message_buf(message_len); - Some(M::decode(&mut message_buf).map_err(|_| Error::Migration(MigrationError::CannotDecodeImportedConcept))) + Some(M::decode(&mut message_buf).map_err(|_| MigrationError::CannotDecodeImportedConcept.into())) } } diff --git a/rust/src/driver.rs b/rust/src/driver.rs index 93f3b5ffde..1147700bc0 100644 --- a/rust/src/driver.rs +++ b/rust/src/driver.rs @@ -17,27 +17,23 @@ * under the License. */ -use std::{ - collections::{HashMap, HashSet}, - fmt, - sync::Arc, -}; - -use itertools::Itertools; +use std::{collections::HashSet, fmt, sync::Arc}; use crate::{ - common::{ - address::Address, - error::{ConnectionError, Error}, - Result, + common::{consistency_level::ConsistencyLevel, Addresses, Result}, + connection::{ + runtime::BackgroundRuntime, + server::{ + server_connection::ServerConnection, server_manager::ServerManager, server_replica::ServerReplica, + server_version::ServerVersion, + }, }, - connection::{runtime::BackgroundRuntime, server_connection::ServerConnection}, Credentials, DatabaseManager, DriverOptions, Transaction, TransactionOptions, TransactionType, UserManager, }; /// A connection to a TypeDB server which serves as the starting point for all interaction. pub struct TypeDBDriver { - server_connections: HashMap, + server_manager: Arc, database_manager: DatabaseManager, user_manager: UserManager, background_runtime: Arc, @@ -50,29 +46,31 @@ impl TypeDBDriver { Some(version) => version, }; - pub const DEFAULT_ADDRESS: &'static str = "localhost:1729"; + pub const DEFAULT_ADDRESS: &'static str = "127.0.0.1:1729"; /// Creates a new TypeDB Server connection. /// /// # Arguments /// - /// * `address` — The address (host:port) on which the TypeDB Server is running + /// * `addresses` — The address(es) of the TypeDB Server(s), provided in a unified format /// * `credentials` — The Credentials to connect with /// * `driver_options` — The DriverOptions to connect with /// /// # Examples /// /// ```rust - #[cfg_attr(feature = "sync", doc = "TypeDBDriver::new(\"127.0.0.1:1729\")")] - #[cfg_attr(not(feature = "sync"), doc = "TypeDBDriver::new(\"127.0.0.1:1729\").await")] + #[cfg_attr( + feature = "sync", + doc = "TypeDBDriver::new(Addresses::try_from_address_str(\"127.0.0.1:1729\").unwrap())" + )] + #[cfg_attr( + not(feature = "sync"), + doc = "TypeDBDriver::new(Addresses::try_from_address_str(\"127.0.0.1:1729\").unwrap()).await" + )] /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub async fn new( - address: impl AsRef, - credentials: Credentials, - driver_options: DriverOptions, - ) -> Result { - Self::new_with_description(address, credentials, driver_options, Self::DRIVER_LANG).await + pub async fn new(addresses: Addresses, credentials: Credentials, driver_options: DriverOptions) -> Result { + Self::new_with_description(addresses, credentials, driver_options, Self::DRIVER_LANG).await } /// Creates a new TypeDB Server connection with a description. @@ -81,7 +79,7 @@ impl TypeDBDriver { /// /// # Arguments /// - /// * `address` — The address (host:port) on which the TypeDB Server is running + /// * `addresses` — The address(es) of the TypeDB Server(s), provided in a unified format /// * `credentials` — The Credentials to connect with /// * `driver_options` — The DriverOptions to connect with /// * `driver_lang` — The language of the driver connecting to the server @@ -89,82 +87,41 @@ impl TypeDBDriver { /// # Examples /// /// ```rust - #[cfg_attr(feature = "sync", doc = "TypeDBDriver::new_with_description(\"127.0.0.1:1729\", \"rust\")")] - #[cfg_attr(not(feature = "sync"), doc = "TypeDBDriver::new_with_description(\"127.0.0.1:1729\", \"rust\").await")] + #[cfg_attr( + feature = "sync", + doc = "TypeDBDriver::new_with_description(Addresses::try_from_address_str(\"127.0.0.1:1729\").unwrap(), \"rust\")" + )] + #[cfg_attr( + not(feature = "sync"), + doc = "TypeDBDriver::new_with_description(Addresses::try_from_address_str(\"127.0.0.1:1729\").unwrap(), \"rust\").await" + )] /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn new_with_description( - address: impl AsRef, + addresses: Addresses, credentials: Credentials, driver_options: DriverOptions, driver_lang: impl AsRef, ) -> Result { - let id = address.as_ref().to_string(); - let address: Address = id.parse()?; let background_runtime = Arc::new(BackgroundRuntime::new()?); - - let (server_connection, database_info) = ServerConnection::new( - background_runtime.clone(), - address.clone(), - credentials, - driver_options, - driver_lang.as_ref(), - Self::VERSION, - ) - .await?; - - // // validate - // let advertised_address = server_connection - // .servers_all()? - // .into_iter() - // .exactly_one() - // .map_err(|e| ConnectionError::ServerConnectionFailedStatusError { error: e.to_string() })?; - - // TODO: this solidifies the assumption that servers don't change - let server_connections: HashMap = [(address, server_connection)].into(); - let database_manager = DatabaseManager::new(server_connections.clone(), database_info)?; - let user_manager = UserManager::new(server_connections.clone()); - - Ok(Self { server_connections, database_manager, user_manager, background_runtime }) - } - - #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - async fn fetch_server_list( - background_runtime: Arc, - addresses: impl IntoIterator> + Clone, - credentials: Credentials, - driver_options: DriverOptions, - ) -> Result> { - let addresses: Vec
= addresses.into_iter().map(|addr| addr.as_ref().parse()).try_collect()?; - for address in &addresses { - let server_connection = ServerConnection::new( + let server_manager = Arc::new( + ServerManager::new( background_runtime.clone(), - address.clone(), - credentials.clone(), - driver_options.clone(), - Self::DRIVER_LANG, + addresses, + credentials, + driver_options, + driver_lang.as_ref(), Self::VERSION, ) - .await; - match server_connection { - Ok((server_connection, _)) => match server_connection.servers_all() { - Ok(servers) => return Ok(servers.into_iter().collect()), - Err(Error::Connection( - ConnectionError::ServerConnectionFailedStatusError { .. } | ConnectionError::ConnectionFailed, - )) => (), - Err(err) => Err(err)?, - }, - Err(Error::Connection( - ConnectionError::ServerConnectionFailedStatusError { .. } | ConnectionError::ConnectionFailed, - )) => (), - Err(err) => Err(err)?, - } - } - Err(ConnectionError::ServerConnectionFailed { addresses }.into()) + .await?, + ); + let database_manager = DatabaseManager::new(server_manager.clone())?; + let user_manager = UserManager::new(server_manager.clone()); + Ok(Self { server_manager, database_manager, user_manager, background_runtime }) } /// Checks it this connection is opened. - // + /// /// # Examples /// /// ```rust @@ -174,16 +131,155 @@ impl TypeDBDriver { self.background_runtime.is_open() } + /// The ``DatabaseManager`` for this connection, providing access to database management methods. + /// + /// # Examples + /// + /// ```rust + /// driver.databases() + /// ``` pub fn databases(&self) -> &DatabaseManager { &self.database_manager } + /// The ``UserManager`` for this connection, providing access to user management methods. + /// + /// # Examples + /// + /// ```rust + /// driver.databases() + /// ``` pub fn users(&self) -> &UserManager { &self.user_manager } + /// Retrieves the server's version, using default strong consistency. + /// + /// See [`Self::server_version_with_consistency`] for more details and options. + /// + /// # Examples + /// + /// ```rust + #[cfg_attr(feature = "sync", doc = "driver.server_version()")] + #[cfg_attr(not(feature = "sync"), doc = "driver.server_version().await")] + /// ``` + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub async fn server_version(&self) -> Result { + self.server_version_with_consistency(ConsistencyLevel::Strong).await + } + + /// Retrieves the server's version, using default strong consistency. + /// + /// # Arguments + /// + /// * `consistency_level` — The consistency level to use for the operation + /// + /// # Examples + /// + /// ```rust + #[cfg_attr(feature = "sync", doc = "driver.server_version_with_consistency(ConsistencyLevel::Strong);")] + #[cfg_attr(not(feature = "sync"), doc = "driver.server_version_with_consistency(ConsistencyLevel::Strong).await;")] + /// ``` + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub async fn server_version_with_consistency(&self, consistency_level: ConsistencyLevel) -> Result { + self.server_manager + .execute(consistency_level, |server_connection| async move { server_connection.version().await }) + .await + } + + /// Retrieves the server's replicas. + /// + /// # Examples + /// + /// ```rust + #[cfg_attr(feature = "sync", doc = "driver.replicas();")] + #[cfg_attr(not(feature = "sync"), doc = "driver.replicas().await;")] + /// ``` + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub async fn replicas(&self) -> Result> { + // TODO: Probably should be only with Strong consistency? + self.server_manager.fetch_replicas().await + } + + /// Retrieves the server's primary replica, if exists. + /// + /// # Examples + /// + /// ```rust + /// driver.primary_replica() + /// ``` + pub fn primary_replica(&self) -> Option { + // TODO: Query the server or not? + self.server_manager.primary_replica() + } + + /// Registers a new replica in the cluster the driver is currently connected to. The registered + /// replica will become available eventually, depending on the behavior of the whole cluster. + /// To register a replica, its clustering address should be passed, not the connection address. + /// + /// # Arguments + /// + /// * `replica_id` — The numeric identifier of the new replica + /// * `address` — The clustering address of the TypeDB replica as a string + /// + /// # Examples + /// + /// ```rust + #[cfg_attr(feature = "sync", doc = "driver.register_replica(2, \"127.0.0.1:2729\")")] + #[cfg_attr(not(feature = "sync"), doc = "driver.register_replica(2, \"127.0.0.1:2729\").await")] + /// ``` + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub async fn register_replica(&self, replica_id: u64, address: String) -> Result { + self.server_manager.register_replica(replica_id, address).await + } + + // TODO: Rename to replica_register and replica_deregister? + + /// Deregisters a replica from the cluster the driver is currently connected to. This replica + /// will no longer play a raft role in this cluster. + /// + /// # Arguments + /// + /// * `replica_id` — The numeric identifier of the deregistered replica + /// + /// # Examples + /// + /// ```rust + #[cfg_attr(feature = "sync", doc = "driver.deregister_replica(2)")] + #[cfg_attr(not(feature = "sync"), doc = "driver.deregister_replica(2).await")] + /// ``` + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub async fn deregister_replica(&self, replica_id: u64) -> Result { + self.server_manager.deregister_replica(replica_id).await + } + + /// Updates address translation of the driver. This lets you actualize new translation + /// information without recreating the driver from scratch. Useful after registering new + /// replicas requiring address translation. + /// + /// # Arguments + /// + /// * `addresses` — Addresses containing the new address translation information + /// + /// # Examples + /// + /// ```rust + /// driver.update_address_translation(Addresses::try_from_translation_str([("typedb-cloud.ext:11729", "127.0.0.1:1729")].into()).unwrap()) + /// ``` + pub fn update_address_translation(&self, addresses: Addresses) -> Result { + self.server_manager.update_address_translation(addresses) + } + /// Opens a transaction with default options. - /// See [`TypeDBDriver::transaction_with_options`] + /// + /// See [`TypeDBDriver::transaction_with_options`] for more details. + /// + /// # Examples + /// + /// ```rust + #[cfg_attr(feature = "sync", doc = "driver.users().all_with_consistency(ConsistencyLevel::Strong);")] + #[cfg_attr(not(feature = "sync"), doc = "driver.users().all_with_consistency(ConsistencyLevel::Strong).await;")] + /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn transaction( &self, @@ -193,7 +289,10 @@ impl TypeDBDriver { self.transaction_with_options(database_name, transaction_type, TransactionOptions::new()).await } - /// Performs a TypeQL query in this transaction. + /// Opens a new transaction with the following consistency level: + /// * read transaction - strong consistency, can be overridden through `options`; + /// * write transaction - strong consistency, cannot be overridden; + /// * schema transaction - strong consistency, cannot be overridden. /// /// # Arguments /// @@ -204,7 +303,14 @@ impl TypeDBDriver { /// # Examples /// /// ```rust - /// transaction.transaction_with_options(database_name, transaction_type, options) + #[cfg_attr( + feature = "sync", + doc = "transaction.transaction_with_options(database_name, transaction_type, options)" + )] + #[cfg_attr( + not(feature = "sync"), + doc = "transaction.transaction_with_options(database_name, transaction_type, options).await" + )] /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn transaction_with_options( @@ -214,12 +320,21 @@ impl TypeDBDriver { options: TransactionOptions, ) -> Result { let database_name = database_name.as_ref(); - let database = self.database_manager.get_cached_or_fetch(database_name).await?; - let transaction_stream = database - .run_failsafe(|database| async move { - database.connection().open_transaction(database.name(), transaction_type, options).await - }) - .await?; + let consistency_level = options.read_consistency_level.clone(); + let open_fn = |server_connection: ServerConnection| { + let options = options.clone(); + async move { server_connection.open_transaction(database_name, transaction_type, options).await } + }; + let transaction_stream = match transaction_type { + TransactionType::Read => { + self.server_manager + .execute(consistency_level.unwrap_or_else(|| ConsistencyLevel::Strong), open_fn) + .await? + } + TransactionType::Write | TransactionType::Schema => { + self.server_manager.execute(ConsistencyLevel::Strong, open_fn).await? + } + }; Ok(Transaction::new(transaction_stream)) } @@ -235,36 +350,12 @@ impl TypeDBDriver { return Ok(()); } - let result = - self.server_connections.values().map(ServerConnection::force_close).try_collect().map_err(Into::into); - self.background_runtime.force_close().and(result) - } - - pub(crate) fn server_count(&self) -> usize { - self.server_connections.len() - } - - pub(crate) fn servers(&self) -> impl Iterator { - self.server_connections.keys() - } - - pub(crate) fn connection(&self, id: &Address) -> Option<&ServerConnection> { - self.server_connections.get(id) - } - - pub(crate) fn connections(&self) -> impl Iterator + '_ { - self.server_connections.iter() - } - - pub(crate) fn unable_to_connect_error(&self) -> Error { - Error::Connection(ConnectionError::ServerConnectionFailed { - addresses: self.servers().map(Address::clone).collect_vec(), - }) + self.server_manager.force_close().and(self.background_runtime.force_close()) } } impl fmt::Debug for TypeDBDriver { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Connection").field("server_connections", &self.server_connections).finish() + f.debug_struct("TypeDBDriver").field("server_manager", &self.server_manager).finish() } } diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 20a3d78016..87bc5942c6 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -22,10 +22,14 @@ pub use self::{ common::{ - box_stream, error, info, BoxPromise, BoxStream, Error, Promise, QueryOptions, Result, TransactionOptions, - TransactionType, IID, + box_stream, consistency_level, error, info, Address, Addresses, BoxPromise, BoxStream, Error, Promise, + QueryOptions, Result, TransactionOptions, TransactionType, IID, + }, + connection::{ + server_replica::{ReplicaType, ServerReplica}, + server_version::ServerVersion, + Credentials, DriverOptions, }, - connection::{Credentials, DriverOptions}, database::{Database, DatabaseManager}, driver::TypeDBDriver, transaction::Transaction, diff --git a/rust/src/transaction.rs b/rust/src/transaction.rs index 93c9daccb5..5e6936260c 100644 --- a/rust/src/transaction.rs +++ b/rust/src/transaction.rs @@ -38,15 +38,19 @@ pub struct Transaction { impl Transaction { pub(super) fn new(transaction_stream: TransactionStream) -> Self { let transaction_stream = Box::pin(transaction_stream); - Transaction { type_: transaction_stream.type_(), options: transaction_stream.options(), transaction_stream } + Transaction { + type_: transaction_stream.type_(), + options: transaction_stream.options().clone(), + transaction_stream, + } } - /// Closes the transaction. + /// Checks if the transaction is open. /// /// # Examples /// /// ```rust - /// transaction.close() + /// transaction.is_open() /// ``` pub fn is_open(&self) -> bool { self.transaction_stream.is_open() @@ -54,6 +58,12 @@ impl Transaction { /// Performs a TypeQL query with default options. /// See [`Transaction::query_with_options`] + /// + /// # Examples + /// + /// ```rust + /// transaction.query(query) + /// ``` pub fn query(&self, query: impl AsRef) -> impl Promise<'static, Result> { self.query_with_options(query, QueryOptions::new()) } diff --git a/rust/src/user/user.rs b/rust/src/user/user.rs index 98b9fb3061..ccfae7c3a6 100644 --- a/rust/src/user/user.rs +++ b/rust/src/user/user.rs @@ -16,52 +16,74 @@ * specific language governing permissions and limitations * under the License. */ -use std::{collections::HashMap, sync::Arc}; +use std::sync::Arc; use crate::{ - common::{address::Address, Result}, - connection::server_connection::ServerConnection, - error::ConnectionError, + common::{consistency_level::ConsistencyLevel, Result}, + connection::server::server_manager::ServerManager, + info::UserInfo, }; #[derive(Clone, Debug)] pub struct User { - pub name: String, - pub password: Option, - pub server_connections: HashMap, + name: String, + password: Option, + server_manager: Arc, } impl User { - /// Update the user's password. + pub(crate) fn new(name: String, password: Option, server_manager: Arc) -> Self { + Self { name, password, server_manager } + } + + pub(crate) fn from_info(user_info: UserInfo, server_manager: Arc) -> Self { + Self::new(user_info.name, user_info.password, server_manager) + } + + /// Retrieves the username as a string. + pub fn name(&self) -> &str { + self.name.as_str() + } + + /// Retrieves the password as a string, if accessible. + pub fn password(&self) -> Option<&str> { + self.password.as_ref().map(|value| value.as_str()) + } + + /// Updates the user's password. Always uses strong consistency. /// /// # Arguments /// - /// * `username` — The name of the user /// * `password` — The new password /// /// # Examples /// /// ```rust - #[cfg_attr(feature = "sync", doc = "user.update_password(username, password);")] - #[cfg_attr(not(feature = "sync"), doc = "user.update_password(username, password).await;")] - /// user.update_password(username, password).await; + #[cfg_attr(feature = "sync", doc = "user.update_password(password);")] + #[cfg_attr(not(feature = "sync"), doc = "user.update_password(password).await;")] /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn update_password(&self, password: impl Into) -> Result<()> { + self.update_password_with_consistency(password, ConsistencyLevel::Strong).await + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn update_password_with_consistency( + &self, + password: impl Into, + consistency_level: ConsistencyLevel, + ) -> Result<()> { let password = password.into(); - let mut error_buffer = Vec::with_capacity(self.server_connections.len()); - for (server_id, server_connection) in self.server_connections.iter() { - match server_connection.update_password(self.name.clone(), password.clone()).await { - Ok(res) => return Ok(()), - Err(err) => error_buffer.push(format!("- {}: {}", server_id, err)), - } - } - Err(ConnectionError::ServerConnectionFailedWithError { error: error_buffer.join("\n") })? + self.server_manager + .execute(consistency_level, |server_connection| { + let name = self.name.clone(); + let password = password.clone(); + async move { server_connection.update_password(name, password).await } + }) + .await } - /// Deletes this user - /// - /// * `username` — The name of the user to be deleted + /// Deletes this user. Always uses strong consistency. /// /// # Examples /// @@ -72,13 +94,16 @@ impl User { /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn delete(self) -> Result { - let mut error_buffer = Vec::with_capacity(self.server_connections.len()); - for (server_id, server_connection) in self.server_connections.iter() { - match server_connection.delete_user(self.name.clone()).await { - Ok(res) => return Ok(res), - Err(err) => error_buffer.push(format!("- {}: {}", server_id, err)), - } - } - Err(ConnectionError::ServerConnectionFailedWithError { error: error_buffer.join("\n") })? + self.delete_with_consistency(ConsistencyLevel::Strong).await + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn delete_with_consistency(self, consistency_level: ConsistencyLevel) -> Result { + self.server_manager + .execute(consistency_level, |server_connection| { + let name = self.name.clone(); + async move { server_connection.delete_user(name).await } + }) + .await } } diff --git a/rust/src/user/user_manager.rs b/rust/src/user/user_manager.rs index 39dbc4837c..e44fcb61f5 100644 --- a/rust/src/user/user_manager.rs +++ b/rust/src/user/user_manager.rs @@ -16,119 +16,200 @@ * specific language governing permissions and limitations * under the License. */ -use std::collections::HashMap; +use std::sync::Arc; use crate::{ - common::{address::Address, Result}, - connection::server_connection::ServerConnection, - error::ConnectionError, + common::{consistency_level::ConsistencyLevel, Result}, + connection::server::server_manager::ServerManager, User, }; /// Provides access to all user management methods. #[derive(Debug)] pub struct UserManager { - server_connections: HashMap, + server_manager: Arc, } impl UserManager { - pub fn new(server_connections: HashMap) -> Self { - Self { server_connections } + pub fn new(server_manager: Arc) -> Self { + Self { server_manager } } + /// Returns the user of the current connection, using default strong consistency. + /// + /// See [`Self::get_current_user_with_consistency`] for more details and options. + /// + /// # Examples + /// + /// ```rust + #[cfg_attr(feature = "sync", doc = "driver.users().get_current_user();")] + #[cfg_attr(not(feature = "sync"), doc = "driver.users().get_current_user().await;")] + /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn get_current_user(&self) -> Result> { - let (_, connection) = self - .server_connections - .iter() - .next() - .expect("Unexpected condition: the server connection collection is empty"); - self.get(connection.username()).await + self.get(self.server_manager.username()?).await } - /// Checks if a user with the given name exists. + /// Returns the user of the current connection. /// /// # Arguments /// - /// * `username` — The user name to be checked + /// * `consistency_level` — The consistency level to use for the operation /// /// # Examples /// /// ```rust - /// driver.users.contains(username).await; + #[cfg_attr(feature = "sync", doc = "driver.users().get_current_user_with_consistency(ConsistencyLevel::Strong);")] + #[cfg_attr( + not(feature = "sync"), + doc = "driver.users().get_current_user_with_consistency(ConsistencyLevel::Strong).await;" + )] + /// ``` + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub async fn get_current_user_with_consistency(&self, consistency_level: ConsistencyLevel) -> Result> { + self.get_with_consistency(self.server_manager.username()?, consistency_level).await + } + + /// Checks if a user with the given name exists, using default strong consistency. + /// + /// See [`Self::contains_with_consistency`] for more details and options. + /// + /// # Examples + /// + /// ```rust + #[cfg_attr(feature = "sync", doc = "driver.users().contains(username);")] + #[cfg_attr(not(feature = "sync"), doc = "driver.users().contains(username).await;")] /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn contains(&self, username: impl Into) -> Result { + self.contains_with_consistency(username, ConsistencyLevel::Strong).await + } + + /// Checks if a user with the given name exists. + /// + /// # Arguments + /// + /// * `username` — The username to be checked + /// * `consistency_level` — The consistency level to use for the operation + /// + /// # Examples + /// + /// ```rust + #[cfg_attr(feature = "sync", doc = "driver.users().contains_with_consistency(username, ConsistencyLevel::Strong);")] + #[cfg_attr( + not(feature = "sync"), + doc = "driver.users().contains_with_consistency(username, ConsistencyLevel::Strong).await;" + )] + /// ``` + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub async fn contains_with_consistency( + &self, + username: impl Into, + consistency_level: ConsistencyLevel, + ) -> Result { let username = username.into(); - let mut error_buffer = Vec::with_capacity(self.server_connections.len()); - for (server_id, server_connection) in self.server_connections.iter() { - match server_connection.contains_user(username.clone()).await { - Ok(res) => return Ok(res), - Err(err) => error_buffer.push(format!("- {}: {}", server_id, err)), - } - } - Err(ConnectionError::ServerConnectionFailedWithError { error: error_buffer.join("\n") })? + self.server_manager + .execute(consistency_level, move |server_connection| { + let username = username.clone(); + async move { server_connection.contains_user(username).await } + }) + .await + } + + /// Retrieves a user with the given name, using default strong consistency. + /// + /// See [`Self::get_with_consistency`] for more details and options. + /// + /// # Examples + /// + /// ```rust + #[cfg_attr(feature = "sync", doc = "driver.users().get(username);")] + #[cfg_attr(not(feature = "sync"), doc = "driver.users().get(username).await;")] + /// ``` + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub async fn get(&self, username: impl Into) -> Result> { + self.get_with_consistency(username, ConsistencyLevel::Strong).await } - /// Retrieve a user with the given name. + /// Retrieves a user with the given name. /// /// # Arguments /// /// * `username` — The name of the user to retrieve + /// * `consistency_level` — The consistency level to use for the operation /// /// # Examples /// /// ```rust - /// driver.users.get(username).await; + #[cfg_attr(feature = "sync", doc = "driver.users().get_with_consistency(username, ConsistencyLevel::Strong);")] + #[cfg_attr( + not(feature = "sync"), + doc = "driver.users().get_with_consistency(username, ConsistencyLevel::Strong).await;" + )] /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub async fn get(&self, username: impl Into) -> Result> { - let uname = username.into(); - let mut error_buffer = Vec::with_capacity(self.server_connections.len()); - for (server_id, server_connection) in self.server_connections.iter() { - match server_connection.get_user(uname.clone()).await { - Ok(res) => { - return Ok(res.map(|u_info| User { - name: u_info.name, - password: u_info.password, - server_connections: self.server_connections.clone(), - })) + pub async fn get_with_consistency( + &self, + username: impl Into, + consistency_level: ConsistencyLevel, + ) -> Result> { + let username = username.into(); + self.server_manager + .execute(consistency_level, |server_connection| { + let username = username.clone(); + let server_manager = self.server_manager.clone(); + async move { + let user_info = server_connection.get_user(username).await?; + Ok(user_info.map(|user_info| User::from_info(user_info, server_manager))) } - Err(err) => error_buffer.push(format!("- {}: {}", server_id, err)), - } - } - Err(ConnectionError::ServerConnectionFailedWithError { error: error_buffer.join("\n") })? + }) + .await } - /// Retrieves all users which exist on the TypeDB server. + /// Retrieves all users which exist on the TypeDB server, using default strong consistency. + /// + /// See [`Self::all_with_consistency`] for more details and options. /// /// # Examples /// /// ```rust - /// driver.users.all().await; + #[cfg_attr(feature = "sync", doc = "driver.users().all();")] + #[cfg_attr(not(feature = "sync"), doc = "driver.users().all().await;")] /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn all(&self) -> Result> { - let mut error_buffer = Vec::with_capacity(self.server_connections.len()); - for (server_id, server_connection) in self.server_connections.iter() { - match server_connection.all_users().await { - Ok(res) => { - return Ok(res - .iter() - .map(|u_info| User { - name: u_info.name.clone(), - password: u_info.password.clone(), - server_connections: self.server_connections.clone(), - }) + self.all_with_consistency(ConsistencyLevel::Strong).await + } + + /// Retrieves all users which exist on the TypeDB server. + /// + /// # Arguments + /// + /// * `consistency_level` — The consistency level to use for the operation + /// + /// # Examples + /// + /// ```rust + #[cfg_attr(feature = "sync", doc = "driver.users().all_with_consistency(ConsistencyLevel::Strong);")] + #[cfg_attr(not(feature = "sync"), doc = "driver.users().all_with_consistency(ConsistencyLevel::Strong).await;")] + /// ``` + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub async fn all_with_consistency(&self, consistency_level: ConsistencyLevel) -> Result> { + self.server_manager + .execute(consistency_level, |server_connection| { + let server_manager = self.server_manager.clone(); + async move { + let user_infos = server_connection.all_users().await?; + Ok(user_infos + .into_iter() + .map(|user_info| User::from_info(user_info, server_manager.clone())) .collect()) } - Err(err) => error_buffer.push(format!("- {}: {}", server_id, err)), - } - } - Err(ConnectionError::ServerConnectionFailedWithError { error: error_buffer.join("\n") })? + }) + .await } - /// Create a user with the given name & password. + /// Creates a user with the given name & password. Always uses strong consistency. /// /// # Arguments /// @@ -138,19 +219,29 @@ impl UserManager { /// # Examples /// /// ```rust - /// driver.users.create(username, password).await; + #[cfg_attr(feature = "sync", doc = "driver.users().create(username, password);")] + #[cfg_attr(not(feature = "sync"), doc = "driver.users().create(username, password).await;")] /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn create(&self, username: impl Into, password: impl Into) -> Result { - let uname = username.into(); - let passwd = password.into(); - let mut error_buffer = Vec::with_capacity(self.server_connections.len()); - for (server_id, server_connection) in self.server_connections.iter() { - match server_connection.create_user(uname.clone(), passwd.clone()).await { - Ok(res) => return Ok(res), - Err(err) => error_buffer.push(format!("- {}: {}", server_id, err)), - } - } - Err(ConnectionError::ServerConnectionFailedWithError { error: error_buffer.join("\n") })? + self.create_with_consistency(username, password, ConsistencyLevel::Strong).await + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn create_with_consistency( + &self, + username: impl Into, + password: impl Into, + consistency_level: ConsistencyLevel, + ) -> Result { + let username = username.into(); + let password = password.into(); + self.server_manager + .execute(consistency_level, move |server_connection| { + let username = username.clone(); + let password = password.clone(); + async move { server_connection.create_user(username, password).await } + }) + .await } } diff --git a/rust/tests/BUILD b/rust/tests/BUILD index 5efcddde72..da2593c008 100644 --- a/rust/tests/BUILD +++ b/rust/tests/BUILD @@ -33,6 +33,7 @@ rustfmt_test( "//rust/tests/behaviour/driver:test_user", "//rust/tests/integration:test_example", + "//rust/tests/integration/cluster:test_clustering", ], size = "small", ) diff --git a/rust/tests/behaviour/steps/connection/database.rs b/rust/tests/behaviour/steps/connection/database.rs index 6a42aa20c2..7cd536e1af 100644 --- a/rust/tests/behaviour/steps/connection/database.rs +++ b/rust/tests/behaviour/steps/connection/database.rs @@ -17,16 +17,14 @@ * under the License. */ -use std::{collections::HashSet, fs::File, io::Read}; - use cucumber::{gherkin::Step, given, then, when}; use futures::{ future::{join_all, try_join_all}, - stream, StreamExt, TryFutureExt, + TryFutureExt, }; use macro_rules_attribute::apply; use tokio::time::sleep; -use typedb_driver::{Database, DatabaseManager, Result as TypeDBResult, TransactionType, TypeDBDriver}; +use typedb_driver::{Database, Result as TypeDBResult, TransactionType, TypeDBDriver}; use uuid::Uuid; use crate::{ diff --git a/rust/tests/behaviour/steps/connection/mod.rs b/rust/tests/behaviour/steps/connection/mod.rs index 293a881083..a5adf3dd76 100644 --- a/rust/tests/behaviour/steps/connection/mod.rs +++ b/rust/tests/behaviour/steps/connection/mod.rs @@ -16,11 +16,12 @@ * specific language governing permissions and limitations * under the License. */ +use std::time::Duration; use cucumber::{given, then, when}; +use itertools::Itertools; use macro_rules_attribute::apply; -use tokio::time::sleep; -use typedb_driver::{Credentials, TypeDBDriver}; +use typedb_driver::{ServerVersion, TypeDBDriver}; use crate::{assert_with_timeout, generic_step, params, params::check_boolean, Context}; @@ -28,6 +29,10 @@ mod database; mod transaction; mod user; +async fn get_server_version(driver: &TypeDBDriver, may_error: params::MayError) -> Option { + may_error.check(driver.server_version().await) +} + #[apply(generic_step)] #[step("typedb starts")] async fn typedb_starts(_: &mut Context) {} @@ -110,6 +115,34 @@ async fn connection_has_been_opened(context: &mut Context, is_open: params::Bool check_boolean!(is_open, context.driver.is_some() && context.driver.as_ref().unwrap().is_open()); } +#[apply(generic_step)] +#[step(expr = r"connection contains distribution{may_error}")] +async fn connection_has_distribution(context: &mut Context, may_error: params::MayError) { + if let Some(server_version) = get_server_version(context.driver.as_ref().unwrap(), may_error).await { + assert!(!server_version.distribution().is_empty()); + } +} + +#[apply(generic_step)] +#[step(expr = r"connection contains version{may_error}")] +async fn connection_has_version(context: &mut Context, may_error: params::MayError) { + if let Some(server_version) = get_server_version(context.driver.as_ref().unwrap(), may_error).await { + assert!(!server_version.version().is_empty()); + } +} + +#[apply(generic_step)] +#[step(expr = r"connection has {int} replica(s)")] +async fn connection_has_count_replicas(context: &mut Context, count: usize) { + assert_eq!(context.driver.as_ref().unwrap().replicas().await.unwrap().len(), count); +} + +#[apply(generic_step)] +#[step(expr = r"connection contains primary replica")] +async fn connection_contains_primary_replica(context: &mut Context, count: usize) { + assert!(context.driver.as_ref().unwrap().primary_replica().is_some()); +} + #[apply(generic_step)] #[step(expr = r"connection has {int} database(s)")] async fn connection_has_count_databases(context: &mut Context, count: usize) { @@ -128,3 +161,24 @@ async fn driver_closes(context: &mut Context, may_error: params::MayError) { may_error.check(context.driver.as_ref().unwrap().force_close()); context.cleanup_transactions().await; } + +#[apply(generic_step)] +#[step(expr = "set driver option use_replication to: {boolean}")] +pub async fn set_transaction_option_use_replication(context: &mut Context, value: params::Boolean) { + context.init_driver_options_if_needed(); + context.driver_options_mut().unwrap().use_replication = value.to_bool(); +} + +#[apply(generic_step)] +#[step(expr = "set driver option primary_failover_retries to: {int}")] +pub async fn set_transaction_option_primary_failover_retries(context: &mut Context, value: usize) { + context.init_driver_options_if_needed(); + context.driver_options_mut().unwrap().primary_failover_retries = value; +} + +#[apply(generic_step)] +#[step(expr = "set driver option replica_discovery_attempts to: {int}")] +pub async fn set_transaction_option_replica_discovery_attempts(context: &mut Context, value: usize) { + context.init_driver_options_if_needed(); + context.driver_options_mut().unwrap().replica_discovery_attempts = Some(value); +} diff --git a/rust/tests/behaviour/steps/connection/transaction.rs b/rust/tests/behaviour/steps/connection/transaction.rs index ede25f09c4..6be37bdcdb 100644 --- a/rust/tests/behaviour/steps/connection/transaction.rs +++ b/rust/tests/behaviour/steps/connection/transaction.rs @@ -20,13 +20,11 @@ use std::{collections::VecDeque, time::Duration}; use cucumber::{gherkin::Step, given, then, when}; -use futures::{future::join_all, FutureExt}; +use futures::future::join_all; use macro_rules_attribute::apply; use typedb_driver::{Result as TypeDBResult, Transaction, TransactionOptions, TransactionType, TypeDBDriver}; -use crate::{ - generic_step, in_background, in_oneshot_background, params, params::check_boolean, util::iter_table, Context, -}; +use crate::{generic_step, in_background, params, params::check_boolean, util::iter_table, Context}; pub(crate) async fn open_transaction_for_database( driver: &TypeDBDriver, @@ -55,7 +53,7 @@ pub async fn connection_open_transaction_for_database( context.driver.as_ref().unwrap(), &database_name, type_.transaction_type, - context.transaction_options, + context.transaction_options(), ) .await, ), @@ -73,7 +71,7 @@ async fn connection_open_transactions_for_database(context: &mut Context, databa context.driver.as_ref().unwrap(), &database_name, transaction_type, - context.transaction_options, + context.transaction_options(), ) .await, ) @@ -90,7 +88,7 @@ pub async fn connection_open_transactions_in_parallel(context: &mut Context, dat context.driver.as_ref().unwrap(), &database_name, transaction_type, - context.transaction_options, + context.transaction_options(), ) })) .await @@ -115,7 +113,7 @@ pub async fn in_background_connection_open_transaction_for_database( &background, &database_name, type_.transaction_type, - context.transaction_options, + context.transaction_options(), ) .await, ), @@ -169,12 +167,12 @@ pub async fn transaction_rollbacks(context: &mut Context, may_error: params::May #[step(expr = "set transaction option transaction_timeout_millis to: {int}")] pub async fn set_transaction_option_transaction_timeout_millis(context: &mut Context, value: u64) { context.init_transaction_options_if_needed(); - context.transaction_options.as_mut().unwrap().transaction_timeout = Some(Duration::from_millis(value)); + context.transaction_options_mut().unwrap().transaction_timeout = Some(Duration::from_millis(value)); } #[apply(generic_step)] #[step(expr = "set transaction option schema_lock_acquire_timeout_millis to: {int}")] pub async fn set_transaction_option_schema_lock_acquire_timeout_millis(context: &mut Context, value: u64) { context.init_transaction_options_if_needed(); - context.transaction_options.as_mut().unwrap().schema_lock_acquire_timeout = Some(Duration::from_millis(value)); + context.transaction_options_mut().unwrap().schema_lock_acquire_timeout = Some(Duration::from_millis(value)); } diff --git a/rust/tests/behaviour/steps/connection/user.rs b/rust/tests/behaviour/steps/connection/user.rs index 139c7ae48e..59cbaf9732 100644 --- a/rust/tests/behaviour/steps/connection/user.rs +++ b/rust/tests/behaviour/steps/connection/user.rs @@ -23,12 +23,21 @@ use cucumber::{gherkin::Step, given, then, when}; use futures::TryFutureExt; use macro_rules_attribute::apply; use tokio::time::sleep; -use typedb_driver::{Database, Result as TypeDBResult, TypeDBDriver, User}; -use crate::{assert_err, assert_with_timeout, generic_step, params, util::iter_table, Context}; +use crate::{assert_with_timeout, generic_step, params, util::iter_table, Context}; async fn all_user_names(context: &Context) -> HashSet { - context.driver.as_ref().unwrap().users().all().await.unwrap().into_iter().map(|user| user.name).collect() + context + .driver + .as_ref() + .unwrap() + .users() + .all() + .await + .unwrap() + .into_iter() + .map(|user| user.name().to_string()) + .collect() } #[apply(generic_step)] @@ -61,7 +70,7 @@ async fn get_user(context: &mut Context, username: String, may_error: params::Ma #[step(expr = r"get user\({word}\) get name: {word}")] async fn get_user_get_name(context: &mut Context, user: String, name: String) { let user = context.driver.as_ref().unwrap().users().get(user).await.unwrap().unwrap(); - assert_eq!(user.name, name); + assert_eq!(user.name(), name); } #[apply(generic_step)] @@ -100,5 +109,5 @@ async fn delete_user(context: &mut Context, username: String, may_error: params: #[apply(generic_step)] #[step(expr = "get current username: {word}")] async fn get_current_username(context: &mut Context, username: String) { - assert_eq!(context.driver.as_ref().unwrap().users().get_current_user().await.unwrap().unwrap().name, username); + assert_eq!(context.driver.as_ref().unwrap().users().get_current_user().await.unwrap().unwrap().name(), username); } diff --git a/rust/tests/behaviour/steps/lib.rs b/rust/tests/behaviour/steps/lib.rs index a33b5412f4..a35ebe11a2 100644 --- a/rust/tests/behaviour/steps/lib.rs +++ b/rust/tests/behaviour/steps/lib.rs @@ -38,8 +38,8 @@ use itertools::Itertools; use tokio::time::{sleep, Duration}; use typedb_driver::{ answer::{ConceptDocument, ConceptRow, QueryAnswer, QueryType}, - BoxStream, Credentials, DriverOptions, QueryOptions, Result as TypeDBResult, Transaction, TransactionOptions, - TypeDBDriver, + Addresses, BoxStream, Credentials, DriverOptions, QueryOptions, Result as TypeDBResult, Transaction, + TransactionOptions, TypeDBDriver, }; use crate::{ @@ -97,7 +97,8 @@ impl> cucumber::Parser for SingletonParser { #[derive(World)] pub struct Context { pub is_cluster: bool, - pub tls_root_ca: PathBuf, + pub tls_root_ca: Option, + pub driver_options: Option, pub transaction_options: Option, pub query_options: Option, pub driver: Option, @@ -119,6 +120,7 @@ impl fmt::Debug for Context { f.debug_struct("Context") .field("is_cluster", &self.is_cluster) .field("tls_root_ca", &self.tls_root_ca) + .field("driver_options", &self.driver_options) .field("transaction_options", &self.transaction_options) .field("query_options", &self.query_options) .field("driver", &self.driver) @@ -143,7 +145,7 @@ impl fmt::Debug for Context { impl Context { const DEFAULT_ADDRESS: &'static str = "127.0.0.1:1729"; // TODO when multiple nodes are available: "127.0.0.1:11729", "127.0.0.1:21729", "127.0.0.1:31729" - const DEFAULT_CLUSTER_ADDRESSES: [&'static str; 1] = ["127.0.0.1:1729"]; + const DEFAULT_CLUSTER_ADDRESSES: [&'static str; 1] = ["127.0.0.1:11729"]; const ADMIN_USERNAME: &'static str = "admin"; const ADMIN_PASSWORD: &'static str = "password"; const STEP_REATTEMPT_SLEEP: Duration = Duration::from_millis(250); @@ -252,7 +254,7 @@ impl Context { .await .expect("Expected all users") .into_iter() - .filter(|user| user.name != Context::ADMIN_USERNAME) + .filter(|user| user.name() != Context::ADMIN_USERNAME) .map(|user| user.delete()), ) .await @@ -281,6 +283,22 @@ impl Context { self.concurrent_rows_streams = None; } + pub fn driver_options(&self) -> Option { + self.driver_options.clone() + } + + pub fn driver_options_mut(&mut self) -> Option<&mut DriverOptions> { + self.driver_options.as_mut() + } + + pub fn transaction_options(&self) -> Option { + self.transaction_options.clone() + } + + pub fn transaction_options_mut(&mut self) -> Option<&mut TransactionOptions> { + self.transaction_options.as_mut() + } + pub fn transaction_opt(&self) -> Option<&Transaction> { self.transactions.get(0) } @@ -316,6 +334,12 @@ impl Context { self.transactions = transactions; } + pub fn init_driver_options_if_needed(&mut self) { + if self.driver_options.is_none() { + self.driver_options = Some(DriverOptions::default()); + } + } + pub fn init_transaction_options_if_needed(&mut self) { if self.transaction_options.is_none() { self.transaction_options = Some(TransactionOptions::default()); @@ -440,10 +464,10 @@ impl Context { username: &str, password: &str, ) -> TypeDBResult { - assert!(!self.is_cluster); + assert!(!self.is_cluster, "Only non-cluster drivers are available in this mode"); + let addresses = Addresses::try_from_address_str(address).expect("Expected addresses"); let credentials = Credentials::new(username, password); - let conn_settings = DriverOptions::new(false, None)?; - TypeDBDriver::new(address, credentials, conn_settings).await + TypeDBDriver::new(addresses, credentials, self.driver_options().unwrap_or_default()).await } async fn create_driver_cluster( @@ -452,14 +476,18 @@ impl Context { username: &str, password: &str, ) -> TypeDBResult { - assert!(self.is_cluster); - // TODO: Change when multiple addresses are introduced - let address = addresses.iter().next().expect("Expected at least one address"); + assert!(self.is_cluster, "Only cluster drivers are available in this mode"); + let https_addresses = addresses.iter().map(|address| format!("https://{address}")); + let addresses = Addresses::try_from_addresses_str(https_addresses).expect("Expected addresses"); - // TODO: We probably want to add encryption to cluster tests let credentials = Credentials::new(username, password); - let conn_settings = DriverOptions::new(false, None)?; - TypeDBDriver::new(address, credentials, conn_settings).await + assert!(self.tls_root_ca.is_some(), "Root CA is expected for cluster tests!"); + let driver_options = self + .driver_options() + .unwrap_or_default() + .is_tls_enabled(true) + .tls_root_ca(self.tls_root_ca.as_ref().map(|path| path.as_path()))?; + TypeDBDriver::new(addresses, credentials, driver_options).await } pub fn set_driver(&mut self, driver: TypeDBDriver) { @@ -475,13 +503,11 @@ impl Context { impl Default for Context { fn default() -> Self { - let tls_root_ca = match std::env::var("ROOT_CA") { - Ok(root_ca) => PathBuf::from(root_ca), - Err(_) => PathBuf::new(), - }; + let tls_root_ca = std::env::var("ROOT_CA").ok().map(|root_ca| PathBuf::from(root_ca)); Self { is_cluster: false, tls_root_ca, + driver_options: None, transaction_options: None, query_options: None, driver: None, diff --git a/rust/tests/behaviour/steps/params.rs b/rust/tests/behaviour/steps/params.rs index bd173c6dca..d14aad7edc 100644 --- a/rust/tests/behaviour/steps/params.rs +++ b/rust/tests/behaviour/steps/params.rs @@ -17,7 +17,7 @@ * under the License. */ -use std::{borrow::Borrow, convert::Infallible, fmt, str::FromStr}; +use std::{convert::Infallible, fmt, str::FromStr}; use chrono::{FixedOffset, NaiveDate, NaiveDateTime, NaiveTime}; use cucumber::Parameter; @@ -29,7 +29,7 @@ use typedb_driver::{ #[derive(Debug, Default, Parameter, Clone)] #[param(name = "value", regex = ".*?")] -pub(crate) struct Value { +pub struct Value { raw_value: String, } @@ -238,7 +238,7 @@ impl FromStr for Var { #[derive(Debug, Parameter)] #[param(name = "boolean", regex = "(true|false)")] -pub(crate) enum Boolean { +pub enum Boolean { False, True, } @@ -279,7 +279,7 @@ impl FromStr for Boolean { #[derive(Debug, Clone, Parameter)] #[param(name = "may_error", regex = "(|; fails|; parsing fails|; fails with a message containing: \".*\")")] -pub(crate) enum MayError { +pub enum MayError { False, True(Option), } @@ -335,7 +335,7 @@ impl FromStr for MayError { #[derive(Debug, Parameter)] #[param(name = "is_or_not", regex = "(is|is not)")] -pub(crate) enum IsOrNot { +pub enum IsOrNot { Is, IsNot, } @@ -388,7 +388,7 @@ impl FromStr for IsOrNot { #[derive(Debug, Parameter)] #[param(name = "contains_or_doesnt", regex = "(contains|does not contain)")] -pub(crate) enum ContainsOrDoesnt { +pub enum ContainsOrDoesnt { Contains, DoesNotContain, } @@ -431,7 +431,7 @@ impl FromStr for ContainsOrDoesnt { #[derive(Debug, Parameter)] #[param(name = "exists_or_doesnt", regex = "(exists|does not exist)")] -pub(crate) enum ExistsOrDoesnt { +pub enum ExistsOrDoesnt { Exists, DoesNotExist, } @@ -474,7 +474,7 @@ impl FromStr for ExistsOrDoesnt { #[derive(Debug, Parameter)] #[param(name = "is_by_var_index", regex = "(| by index of variable)")] -pub(crate) enum IsByVarIndex { +pub enum IsByVarIndex { Is, IsNot, } @@ -492,7 +492,7 @@ impl FromStr for IsByVarIndex { #[derive(Debug, Clone, Copy, Parameter)] #[param(name = "query_answer_type", regex = "(ok|concept rows|concept documents)")] -pub(crate) enum QueryAnswerType { +pub enum QueryAnswerType { Ok, ConceptRows, ConceptDocuments, @@ -525,7 +525,7 @@ impl fmt::Display for QueryAnswerType { name = "concept_kind", regex = "(concept|variable|type|instance|entity type|relation type|attribute type|role type|entity|relation|attribute|value)" )] -pub(crate) enum ConceptKind { +pub enum ConceptKind { Concept, Type, Instance, @@ -540,7 +540,7 @@ pub(crate) enum ConceptKind { } impl ConceptKind { - pub(crate) fn matches_concept(&self, concept: &Concept) -> bool { + pub fn matches_concept(&self, concept: &Concept) -> bool { match self { ConceptKind::Concept => true, ConceptKind::Type => match concept { diff --git a/rust/tests/behaviour/steps/query.rs b/rust/tests/behaviour/steps/query.rs index 5258cfd3b3..69a210c121 100644 --- a/rust/tests/behaviour/steps/query.rs +++ b/rust/tests/behaviour/steps/query.rs @@ -17,23 +17,20 @@ * under the License. */ -use std::{collections::VecDeque, ops::Index}; - use cucumber::{gherkin::Step, given, then, when}; -use futures::{future::join_all, StreamExt, TryStreamExt}; +use futures::{future::join_all, StreamExt}; use itertools::Itertools; use macro_rules_attribute::apply; use typedb_driver::{ - answer::{ConceptRow, QueryAnswer, JSON}, + answer::{ConceptRow, QueryAnswer}, concept::{AttributeType, Concept, ConceptCategory, EntityType, RelationType, Value, ValueType}, error::ConceptError, QueryOptions, Result as TypeDBResult, Transaction, }; use crate::{ - assert_err, generic_step, params, + generic_step, params, params::check_boolean, - util, util::{iter_table, list_contains_json, parse_json}, BehaviourTestOptionalError, Context, }; diff --git a/rust/tests/behaviour/steps/util.rs b/rust/tests/behaviour/steps/util.rs index 84ae730ae3..dd5fae7bf9 100644 --- a/rust/tests/behaviour/steps/util.rs +++ b/rust/tests/behaviour/steps/util.rs @@ -23,28 +23,14 @@ use std::{ env, fs, fs::File, io::{Read, Write}, - iter, mem, ops::Deref, path::{Path, PathBuf}, }; -use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; -use cucumber::{ - gherkin::{Feature, Step}, - given, then, when, StatsWriter, World, -}; -use futures::{ - future::{try_join_all, Either}, - stream::{self, StreamExt}, -}; -use itertools::Itertools; +use cucumber::{gherkin::Step, given, then, when, StatsWriter}; use macro_rules_attribute::apply; use tokio::time::{sleep, Duration}; -use typedb_driver::{ - answer::{ConceptRow, JSON}, - concept::{Attribute, AttributeType, Concept, Entity, EntityType, Relation, RelationType, RoleType, Value}, - DatabaseManager, Error, Result as TypeDBResult, -}; +use typedb_driver::{answer::JSON, Result as TypeDBResult}; use uuid::Uuid; use crate::{assert_with_timeout, generic_step, params, params::check_boolean, Context}; diff --git a/rust/tests/integration/BUILD b/rust/tests/integration/BUILD index 11ba6b4760..0ba2f64b5c 100644 --- a/rust/tests/integration/BUILD +++ b/rust/tests/integration/BUILD @@ -20,11 +20,6 @@ package(default_visibility = ["//visibility:public"]) load("@rules_rust//rust:defs.bzl", "rust_test") load("@typedb_dependencies//tool/checkstyle:rules.bzl", "checkstyle_test") -exports_files( - ["quickstart"], - visibility = ["//rust:__subpackages__"], -) - rust_test( name = "test_example", srcs = ["example.rs"], diff --git a/rust/tests/integration/cluster/BUILD b/rust/tests/integration/cluster/BUILD index f61fa8fb80..99c58c8eb8 100644 --- a/rust/tests/integration/cluster/BUILD +++ b/rust/tests/integration/cluster/BUILD @@ -17,10 +17,29 @@ package(default_visibility = ["//visibility:public"]) +load("@rules_rust//rust:defs.bzl", "rust_test") load("@typedb_dependencies//tool/checkstyle:rules.bzl", "checkstyle_test") -# TODO: This package can contain cluster-specific integration tests. Tests available for running for both community -# edition and cluster should be run for both setups (and be limited: always prefer BDDs to integration tests)! +rust_test( + name = "test_clustering", + srcs = ["clustering.rs"], + deps = [ + "//rust:typedb_driver", + "@crates//:async-std", + "@crates//:chrono", + "@crates//:futures", + "@crates//:itertools", + "@crates//:regex", + "@crates//:serde_json", + "@crates//:serial_test", + "@crates//:smol", + "@crates//:tokio", + "@crates//:uuid", + ], + # TODO: Temporary. Use a binary extracted in a better way + data = ["//tool/test/temp-cluster-server:typedb", + "//tool/test/temp-cluster-server:config.yml"], +) checkstyle_test( name = "checkstyle", diff --git a/rust/tests/integration/cluster/clustering.rs b/rust/tests/integration/cluster/clustering.rs new file mode 100644 index 0000000000..2e8f2e0949 --- /dev/null +++ b/rust/tests/integration/cluster/clustering.rs @@ -0,0 +1,338 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +use std::{ + env, fs, + path::Path, + process::{Child, Command}, + str::FromStr, + time::Duration, +}; + +use async_std::task::sleep; +use futures::{StreamExt, TryStreamExt}; +use serial_test::serial; +use typedb_driver::{ + answer::ConceptRow, Addresses, Credentials, DriverOptions, Error, ServerReplica, TransactionOptions, + TransactionType, TypeDBDriver, +}; + +const ADDRESSES: [&'static str; 3] = ["127.0.0.1:11729", "127.0.0.1:21729", "127.0.0.1:31729"]; +const CLUSTERING_ADDRESSES: [&'static str; 3] = ["127.0.0.1:11730", "127.0.0.1:21730", "127.0.0.1:31730"]; +const USERNAME: &'static str = "admin"; +const PASSWORD: &'static str = "password"; + +#[test] +#[serial] +fn primary_reelection_read_query() { + async_std::task::block_on(async { + kill_servers().await.ok(); + + for (i, address) in ADDRESSES.iter().enumerate() { + let (_, port) = address.rsplit_once(':').unwrap(); + start_server_replica_from_parts(&(i + 1).to_string(), port).await; + } + sleep(Duration::from_secs(10)).await; + + remove_test_database().await; + + println!("Building the main driver"); + let driver = TypeDBDriver::new( + Addresses::try_from_address_str(ADDRESSES[0]).unwrap(), + Credentials::new(USERNAME, PASSWORD), + DriverOptions::new(), + ) + .await + .unwrap(); + + for (i, address) in CLUSTERING_ADDRESSES[1..].iter().enumerate() { + driver.register_replica((i + 2) as u64, address.to_string()).await.unwrap(); + } + + // TODO: Temp, resolve it somehow!!! + sleep(Duration::from_secs(6)).await; + println!("Recreating the driver to fetch new replicas"); + drop(driver); + let driver = TypeDBDriver::new( + Addresses::try_from_address_str(ADDRESSES[0]).unwrap(), + Credentials::new(USERNAME, PASSWORD), + DriverOptions::new(), + ) + .await + .unwrap(); + + // sleep(Duration::from_secs(5)).await; + // TODO: Does it need to return all the current replicas? Or is it not needed? + // It's currently handled automatically, so we won't see the new replicas now after registering them!!! + let replicas = driver.replicas().await; + println!("Replicas: {replicas:?}"); + + let database_name = "typedb"; + println!("Registered replicas. Creating the database"); + driver.databases().create(database_name).await.unwrap(); + println!("Retrieving the database..."); + + verify_created_database(&driver, database_name).await; + + for iteration in 0..10 { + let primary_replica = get_primary_replica(&driver).await; + println!("Stopping primary replica (iteration {}). Testing retrieval from other replicas", iteration); + kill_server_replica(&primary_replica).await.unwrap(); + + sleep(Duration::from_secs(5)).await; + verify_created_database(&driver, database_name).await; + + start_server_replica(&primary_replica).await; + } + + println!("Done!"); + }); + + async_std::task::block_on(async { + println!("Cleanup!"); + remove_test_database().await; + kill_servers().await.unwrap(); + println!("Successfully cleaned up!"); + }) +} + +// #[test] +// #[serial] +// fn primary_reelection_read_query() { +// async_std::task::block_on(async { +// kill_servers().await.ok(); +// +// for (i, address) in ADDRESSES.iter().enumerate() { +// let (_, port) = address.rsplit_once(':').unwrap(); +// start_server_replica_from_parts(&(i + 1).to_string(), port).await; +// } +// sleep(Duration::from_secs(10)).await; +// +// remove_test_database().await; +// +// println!("Building the main driver"); +// let driver = TypeDBDriver::new( +// Addresses::try_from_address_str(ADDRESSES[0]).unwrap(), +// Credentials::new(USERNAME, PASSWORD), +// DriverOptions::new(), +// ) +// .await +// .unwrap(); +// +// for (i, address) in CLUSTERING_ADDRESSES[1..].iter().enumerate() { +// driver.register_replica((i + 2) as u64, address.to_string()).await.unwrap(); +// } +// +// // sleep(Duration::from_secs(5)).await; +// // TODO: Does it need to return all the current replicas? Or is it not needed? +// // It's currently handled automatically, so we won't see the new replicas now after registering them!!! +// // let replicas = driver.replicas(); +// +// println!("Registered replicas. Creating the database"); +// driver.databases().create("typedb").await.unwrap(); +// println!("Retrieving the database..."); +// let database = driver.databases().get("typedb").await.unwrap(); +// assert_eq!(database.name(), "typedb"); +// +// println!("Created database {}. Initializing schema", database.name()); +// { +// let transaction = driver +// .transaction_with_options(database.name(), TransactionType::Schema, TransactionOptions::default()) +// .await +// .unwrap(); +// transaction.query("define entity person;").await.unwrap(); +// transaction.commit().await.unwrap(); +// } +// +// verify_defined(&driver, database.name()).await; +// +// for iteration in 0..10 { +// let primary_replica = get_primary_replica(&driver).await; +// println!("Stopping primary replica (iteration {}). Testing retrieval from other replicas", iteration); +// kill_server_replica(&primary_replica).await.unwrap(); +// +// sleep(Duration::from_secs(5)).await; +// verify_defined(&driver, database.name()).await; +// +// start_server_replica(&primary_replica).await; +// } +// +// println!("Done!"); +// }); +// +// async_std::task::block_on(async { +// println!("Cleanup!"); +// remove_test_database().await; +// kill_servers().await.unwrap(); +// println!("Successfully cleaned up!"); +// }) +// } + +async fn remove_test_database() { + let driver = TypeDBDriver::new( + Addresses::try_from_addresses_str(ADDRESSES.iter()).unwrap(), + Credentials::new(USERNAME, PASSWORD), + DriverOptions::new(), + ) + .await + .unwrap(); + if driver.databases().contains("typedb").await.unwrap() { + driver.databases().get("typedb").await.unwrap().delete().await.unwrap(); + } +} + +async fn kill_servers() -> Result<(), TestError> { + for address in &ADDRESSES { + let (_, port) = address.rsplit_once(':').unwrap(); + kill_server_replica_from_parts(port).await?; + } + Ok(()) +} + +fn start_server(index: &str) -> Child { + // Command::new(format!("../{index}/typedb")) + let data_directory_path = format!("{index}/data"); + let data_directory = Path::new(&data_directory_path); + fs::create_dir_all(data_directory).unwrap(); + let data_directory_path = fs::canonicalize(data_directory).unwrap(); + let logs_directory_path = format!("{index}/logs"); + let logs_directory = Path::new(&logs_directory_path); + fs::create_dir_all(logs_directory).unwrap(); + let logs_directory_path = fs::canonicalize(logs_directory).unwrap(); + // TODO: Temporary, should be called in a better way + Command::new(format!("{}/tool/test/temp-cluster-server/typedb", env::current_dir().unwrap().to_string_lossy())) + .args([ + "--server.address", + &format!("127.0.0.1:{index}1729"), + "--server-clustering-id", + &format!("{index}"), + "--server-clustering-address", + &format!("127.0.0.1:{index}1730"), + "--storage.data-directory", + &data_directory_path.display().to_string(), + "--logging.logdir", + &logs_directory_path.display().to_string(), + "--diagnostics.deployment-id", + "test", + "--server.encryption.enabled", + "false", + "--diagnostics.monitoring.port", + &format!("{index}1731"), + "--server.http.enabled", + "false", + "--development-mode.enabled", + "true", + ]) + .spawn() + .expect("Failed to start TypeDB server") +} + +async fn get_primary_replica(driver: &TypeDBDriver) -> ServerReplica { + for _ in 0..10 { + if let Some(replica) = driver.primary_replica() { + return replica; + } + println!("No primary replica yet. Retrying in 2s..."); + sleep(Duration::from_secs(2)).await; + } + panic!("Retry limit exceeded while seeking a primary replica."); +} + +async fn verify_defined_type(driver: &TypeDBDriver, database_name: impl AsRef) { + let transaction = driver + .transaction_with_options(database_name, TransactionType::Read, TransactionOptions::default()) + .await + .unwrap(); + let answer = transaction.query("match entity $p;").await.unwrap(); + let rows: Vec = answer.into_rows().try_collect().await.unwrap(); + assert_eq!(rows.len(), 1); + let row = rows.get(0).unwrap(); + let entity_type = row.get("p").unwrap().unwrap(); + assert_eq!(entity_type.is_entity_type(), true); + assert_eq!(entity_type.get_label(), "person"); +} + +async fn verify_created_database(driver: &TypeDBDriver, database_name: impl AsRef) { + // TODO: Can additionally test with eventual consistency when it's introduced. Like this: + // use typedb_driver::consistency_level::ConsistencyLevel; + // assert_eq!(driver.databases().get_with_consistency(database_name.as_ref(), ConsistencyLevel::Eventual).await.unwrap().name(), database_name.as_ref()); + // assert!(driver.databases().contains_with_consistency(database_name.as_ref(), ConsistencyLevel::Eventual).await.unwrap()); + + assert_eq!(driver.databases().get(database_name.as_ref()).await.unwrap().name(), database_name.as_ref()); + assert!(driver.databases().contains(database_name.as_ref()).await.unwrap()); +} + +async fn start_server_replica(server_replica: &ServerReplica) { + let address_parts = ServerReplicaAddressParts::from_str(&server_replica.address().to_string()).unwrap(); + start_server_replica_from_parts(&address_parts.replica_id, &address_parts.port).await +} + +async fn start_server_replica_from_parts(replica_id: &str, port: &str) { + println!("Starting server replica from parts: {replica_id}, port: {port}"); + let _child = start_server(replica_id); + let mut attempts = 0; + while attempts < 60 { + sleep(Duration::from_secs(1)).await; + let check = Command::new("lsof").args(["-i", &format!(":{}", port)]).output(); + if check.is_ok() { + break; + } + attempts += 1; + } +} + +async fn kill_server_replica(server_replica: &ServerReplica) -> Result<(), TestError> { + let address_parts = ServerReplicaAddressParts::from_str(&server_replica.address().to_string()).unwrap(); + kill_server_replica_from_parts(&address_parts.port).await +} + +async fn kill_server_replica_from_parts(port: &str) -> Result<(), TestError> { + let lsof = Command::new("lsof").args(["-i", &format!(":{}", port)]).output().expect("Failed to run lsof"); + + let stdout = String::from_utf8_lossy(&lsof.stdout); + let pid = stdout + .lines() + .find(|line| line.contains("LISTEN")) + .and_then(|line| line.split_whitespace().nth(1)) + .ok_or(TestError::NoServerPid)?; + + let res = Command::new("kill").args(["-9", pid]).status(); + println!("Replica on port {port} killed"); + res.map(|_| ()).map_err(|_| TestError::NoServerProcess) +} + +struct ServerReplicaAddressParts { + replica_id: String, + port: String, +} + +impl FromStr for ServerReplicaAddressParts { + type Err = Error; + + fn from_str(address: &str) -> typedb_driver::Result { + let port = address.rsplit_once(':').map(|(_, port)| port.to_string()).unwrap(); + let replica_id = port[0..1].to_string(); + Ok(Self { replica_id, port }) + } +} + +#[derive(Debug)] +enum TestError { + NoServerPid, + NoServerProcess, +} diff --git a/rust/tests/integration/cluster/mod.rs b/rust/tests/integration/cluster/mod.rs index 042f3ce1f3..37c7d4f9a6 100644 --- a/rust/tests/integration/cluster/mod.rs +++ b/rust/tests/integration/cluster/mod.rs @@ -16,3 +16,5 @@ * specific language governing permissions and limitations * under the License. */ + +mod clustering; diff --git a/rust/tests/integration/example.rs b/rust/tests/integration/example.rs index fee42911a5..6aece30432 100644 --- a/rust/tests/integration/example.rs +++ b/rust/tests/integration/example.rs @@ -29,16 +29,16 @@ use typedb_driver::{ ConceptRow, QueryAnswer, }, concept::{Concept, ValueType}, - Credentials, DriverOptions, Error, QueryOptions, TransactionOptions, TransactionType, TypeDBDriver, + Addresses, Credentials, DriverOptions, Error, QueryOptions, TransactionOptions, TransactionType, TypeDBDriver, }; // EXAMPLE END MARKER async fn cleanup() { let driver = TypeDBDriver::new( - TypeDBDriver::DEFAULT_ADDRESS, + Addresses::try_from_address_str(TypeDBDriver::DEFAULT_ADDRESS).unwrap(), Credentials::new("admin", "password"), - DriverOptions::new(false, None).unwrap(), + DriverOptions::new(), ) .await .unwrap(); @@ -57,9 +57,9 @@ fn example() { // EXAMPLE START MARKER // Open a driver connection. Specify your parameters if needed let driver = TypeDBDriver::new( - TypeDBDriver::DEFAULT_ADDRESS, + Addresses::try_from_address_str(TypeDBDriver::DEFAULT_ADDRESS).unwrap(), Credentials::new("admin", "password"), - DriverOptions::new(false, None).unwrap(), + DriverOptions::new(), ) .await .unwrap(); @@ -228,7 +228,7 @@ fn example() { // just call `commit`, which will wait for all ongoing operations to finish before executing. let queries = ["insert $a isa person, has name \"Alice\";", "insert $b isa person, has name \"Bob\";"]; for query in queries { - transaction.query(query); + let _unawaited_future = transaction.query(query); } transaction.commit().await.unwrap(); diff --git a/tool/test/BUILD b/tool/test/BUILD index a2c8e02cc9..ba612a7cc8 100644 --- a/tool/test/BUILD +++ b/tool/test/BUILD @@ -26,13 +26,6 @@ checkstyle_test( license_type = "apache-header", ) -java_binary( - name = "echo-java-home", - srcs = ["EchoJavaHome.java"], - deps = [], - main_class = "com.typedb.driver.tool.test.EchoJavaHome" -) - native_typedb_artifact( name = "native-typedb-artifact", native_artifacts = { @@ -40,22 +33,22 @@ native_typedb_artifact( "@typedb_bazel_distribution//platform:is_linux_x86_64": ["@typedb_artifact_linux-x86_64//file"], "@typedb_bazel_distribution//platform:is_mac_arm64": ["@typedb_artifact_mac-arm64//file"], "@typedb_bazel_distribution//platform:is_mac_x86_64": ["@typedb_artifact_mac-x86_64//file"], -# "@typedb_bazel_distribution//platform:is_windows_x86_64": ["@typedb_artifact_windows-x86_64//file"], + "@typedb_bazel_distribution//platform:is_windows_x86_64": ["@typedb_artifact_windows-x86_64//file"], }, output = "typedb-artifact.tar.gz", visibility = ["//visibility:public"], ) #native_typedb_artifact( -# name = "native-typedb-cloud-artifact", +# name = "native-typedb-cluster-artifact", # native_artifacts = { -# "@typedb_bazel_distribution//platform:is_linux_arm64": ["@typedb_cloud_artifact_linux-arm64//file"], -# "@typedb_bazel_distribution//platform:is_linux_x86_64": ["@typedb_cloud_artifact_linux-x86_64//file"], -# "@typedb_bazel_distribution//platform:is_mac_arm64": ["@typedb_cloud_artifact_mac-arm64//file"], -# "@typedb_bazel_distribution//platform:is_mac_x86_64": ["@typedb_cloud_artifact_mac-x86_64//file"], -# "@typedb_bazel_distribution//platform:is_windows_x86_64": ["@typedb_cloud_artifact_windows-x86_64//file"], +# "@typedb_bazel_distribution//platform:is_linux_arm64": ["@typedb_cluster_artifact_linux-arm64//file"], +# "@typedb_bazel_distribution//platform:is_linux_x86_64": ["@typedb_cluster_artifact_linux-x86_64//file"], +# "@typedb_bazel_distribution//platform:is_mac_arm64": ["@typedb_cluster_artifact_mac-arm64//file"], +# "@typedb_bazel_distribution//platform:is_mac_x86_64": ["@typedb_cluster_artifact_mac-x86_64//file"], +# "@typedb_bazel_distribution//platform:is_windows_x86_64": ["@typedb_cluster_artifact_windows-x86_64//file"], # }, -# output = "typedb-cloud-artifact.tar.gz", +# output = "typedb-cluster-artifact.tar.gz", # visibility = ["//visibility:public"], #) @@ -65,6 +58,6 @@ artifact_extractor( ) artifact_extractor( - name = "typedb-cloud-extractor", - artifact = ":native-typedb-cloud-artifact", + name = "typedb-cluster-extractor", + artifact = ":native-typedb-cluster-artifact", ) diff --git a/tool/test/start-cluster-servers.sh b/tool/test/start-cluster-servers.sh index 9a43255332..e12d8cdd88 100755 --- a/tool/test/start-cluster-servers.sh +++ b/tool/test/start-cluster-servers.sh @@ -18,45 +18,52 @@ set -e -export BAZEL_JAVA_HOME=$(bazel run //tool/test:echo-java-home) NODE_COUNT=${1:-1} +ENCRYPTION_ENABLED=${2:-true} -peers= -for i in $(seq 1 $NODE_COUNT); do - peers="${peers} --server.peers.peer-${i}.address=localhost:${i}1729" - peers="${peers} --server.peers.peer-${i}.internal-address.zeromq=localhost:${i}1730" - peers="${peers} --server.peers.peer-${i}.internal-address.grpc=localhost:${i}1731" -done +# TODO: Update configs +#peers= +#for i in $(seq 1 $NODE_COUNT); do +# peers="${peers} --server.peers.peer-${i}.address=127.0.0.1:${i}1729" +# peers="${peers} --server.peers.peer-${i}.internal-address.zeromq=127.0.0.1:${i}1730" +# peers="${peers} --server.peers.peer-${i}.internal-address.grpc=127.0.0.1:${i}1731" +#done function server_start() { - JAVA_HOME=$BAZEL_JAVA_HOME ./${1}/typedb server \ - --storage.data=server/data \ - --server.address=localhost:${1}1729 \ - --server.internal-address.zeromq=localhost:${1}1730 \ - --server.internal-address.grpc=localhost:${1}1731 \ - $(echo $peers) \ - --server.encryption.enable=true \ - --server.encryption.file.enable=true \ - --server.encryption.file.external-grpc.private-key=`realpath tool/test/resources/encryption/ext-grpc-private-key.pem` \ - --server.encryption.file.external-grpc.certificate=`realpath tool/test/resources/encryption/ext-grpc-certificate.pem` \ - --server.encryption.file.external-grpc.root-ca=`realpath tool/test/resources/encryption/ext-grpc-root-ca.pem` \ - --server.encryption.file.internal-grpc.private-key=`realpath tool/test/resources/encryption/int-grpc-private-key.pem` \ - --server.encryption.file.internal-grpc.certificate=`realpath tool/test/resources/encryption/int-grpc-certificate.pem` \ - --server.encryption.file.internal-grpc.root-ca=`realpath tool/test/resources/encryption/int-grpc-root-ca.pem` \ - --server.encryption.file.internal-zmq.private-key=`realpath tool/test/resources/encryption/int-zmq-private-key` \ - --server.encryption.file.internal-zmq.public-key=`realpath tool/test/resources/encryption/int-zmq-public-key` \ - --diagnostics.monitoring.port=${1}1732 \ - --development-mode.enable=true + ./${1}/typedb server \ + --server.address=127.0.0.1:${1}1729 \ + --server.encryption.enabled=$ENCRYPTION_ENABLED \ + --server.encryption.certificate=`realpath tool/test/resources/encryption/ext-grpc-certificate.pem` \ + --server.encryption.certificate-key=`realpath tool/test/resources/encryption/ext-grpc-private-key.pem` \ + --server.encryption.ca-certificate=`realpath tool/test/resources/encryption/ext-grpc-root-ca.pem` \ + --diagnostics.monitoring.port ${1}1732 \ + --development-mode.enabled true +# --storage.data=server/data \ +# --server.internal-address.zeromq=127.0.0.1:${1}1730 \ +# --server.internal-address.grpc=127.0.0.1:${1}1731 \ +# $(echo $peers) \ +# --server.encryption.enable=true \ +# --server.encryption.file.enable=true \ +# --server.encryption.file.external-grpc.private-key=`realpath tool/test/resources/encryption/ext-grpc-private-key.pem` \ +# --server.encryption.file.external-grpc.certificate=`realpath tool/test/resources/encryption/ext-grpc-certificate.pem` \ +# --server.encryption.file.external-grpc.root-ca=`realpath tool/test/resources/encryption/ext-grpc-root-ca.pem` \ +# --server.encryption.file.internal-grpc.private-key=`realpath tool/test/resources/encryption/int-grpc-private-key.pem` \ +# --server.encryption.file.internal-grpc.certificate=`realpath tool/test/resources/encryption/int-grpc-certificate.pem` \ +# --server.encryption.file.internal-grpc.root-ca=`realpath tool/test/resources/encryption/int-grpc-root-ca.pem` \ +# --server.encryption.file.internal-zmq.private-key=`realpath tool/test/resources/encryption/int-zmq-private-key` \ +# --server.encryption.file.internal-zmq.public-key=`realpath tool/test/resources/encryption/int-zmq-public-key` \ } -rm -rf $(seq 1 $NODE_COUNT) typedb-cloud-all +rm -rf $(seq 1 $NODE_COUNT) typedb-cluster-all + +#bazel run //tool/test:typedb-cluster-extractor -- typedb-cluster-all +bazel run //tool/test:typedb-extractor -- typedb-cluster-all -bazel run //tool/test:typedb-cloud-extractor -- typedb-cloud-all -echo Successfully unarchived TypeDB distribution. Creating $NODE_COUNT copies. +echo Successfully unarchived a TypeDB distribution. Creating $NODE_COUNT copies ${1}. for i in $(seq 1 $NODE_COUNT); do - cp -r typedb-cloud-all $i || exit 1 + cp -r typedb-cluster-all $i || exit 1 done -echo Starting a cloud consisting of $NODE_COUNT servers... +echo Starting a cluster consisting of $NODE_COUNT servers... for i in $(seq 1 $NODE_COUNT); do server_start $i & done @@ -70,7 +77,7 @@ RETRY_NUM=0 while [[ $RETRY_NUM -lt $MAX_RETRIES ]]; do RETRY_NUM=$(($RETRY_NUM + 1)) if [[ $(($RETRY_NUM % 4)) -eq 0 ]]; then - echo Waiting for TypeDB Cloud servers to start \($(($RETRY_NUM / 2))s\)... + echo Waiting for TypeDB Cluster servers to start \($(($RETRY_NUM / 2))s\)... fi ALL_STARTED=1 for i in $(seq 1 $NODE_COUNT); do @@ -82,7 +89,7 @@ while [[ $RETRY_NUM -lt $MAX_RETRIES ]]; do sleep $POLL_INTERVAL_SECS done if (( ! $ALL_STARTED )); then - echo Failed to start one or more TypeDB Cloud servers + echo Failed to start one or more TypeDB Cluster servers exit 1 fi -echo $NODE_COUNT TypeDB Cloud database servers started +echo $NODE_COUNT TypeDB Cluster database servers started diff --git a/tool/test/start-community-server.sh b/tool/test/start-community-server.sh index 7d35a7baa3..bf85e2bd1d 100755 --- a/tool/test/start-community-server.sh +++ b/tool/test/start-community-server.sh @@ -21,7 +21,9 @@ set -e rm -rf typedb-all bazel run //tool/test:typedb-extractor -- typedb-all -./typedb-all/typedb server --development-mode.enabled true --server.authentication.token_ttl_seconds 15 & +./typedb-all/typedb server \ + --development-mode.enabled true \ + --server.authentication.token_ttl_seconds 15 & set +e POLL_INTERVAL_SECS=0.5 @@ -32,5 +34,5 @@ while [[ $RETRY_NUM -gt 0 ]]; do ((RETRY_NUM-=1)) sleep $POLL_INTERVAL_SECS done -echo "TypeDB server failed to start within $((POLL_INTERVAL_SECS * RETRY_NUM)) seconds, aborting..." +echo "TypeDB CE server failed to start within $((POLL_INTERVAL_SECS * RETRY_NUM)) seconds, aborting..." exit 1 diff --git a/tool/test/stop-cluster-servers.sh b/tool/test/stop-cluster-servers.sh index 3d6a2c6d6f..63b3992753 100755 --- a/tool/test/stop-cluster-servers.sh +++ b/tool/test/stop-cluster-servers.sh @@ -18,8 +18,10 @@ set -e -procs=$(ps aux | awk '/TypeDBCloudServe[r]/ {print $2}' | paste -sd " " -) -echo $procs -if [ -n "$procs" ]; then - kill $procs -fi +kill $(ps aux | awk '/typedb[_server_bin]/ {print $2}') + +#procs=$(ps aux | awk '/TypeDBCloudServe[r]/ {print $2}' | paste -sd " " -) +#echo $procs +#if [ -n "$procs" ]; then +# kill $procs +#fi diff --git a/tool/test/temp-cluster-server/BUILD b/tool/test/temp-cluster-server/BUILD new file mode 100644 index 0000000000..be589aafc4 --- /dev/null +++ b/tool/test/temp-cluster-server/BUILD @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +load("@typedb_dependencies//tool/checkstyle:rules.bzl", "checkstyle_test") + +# TODO: The whole directory is temporary. REMOVE it after a normal cluster distribution is ready! + +exports_files([ + "typedb", + "config.yml", +]) + +checkstyle_test( + name = "checkstyle", + include = glob(["*"]), + license_type = "apache-header", +) diff --git a/tool/test/temp-cluster-server/config.yml b/tool/test/temp-cluster-server/config.yml new file mode 100644 index 0000000000..92eb522f7a --- /dev/null +++ b/tool/test/temp-cluster-server/config.yml @@ -0,0 +1,34 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +server: + address: 127.0.0.1:11729 + http-enabled: false + http-address: 127.0.0.1:8000 + + authentication: + token-expiration-seconds: 5000 + + encryption: + enabled: false + certificate: + certificate_key: + ca-certificate: + +storage: + data-directory: "data" + +logging: + directory: "logs" + +diagnostics: + monitoring: + enabled: false + port: 4104 + reporting: + metrics: false + errors: false + +development-mode: + enabled: true diff --git a/tool/test/temp-cluster-server/start-cluster-servers.sh b/tool/test/temp-cluster-server/start-cluster-servers.sh new file mode 100755 index 0000000000..07f5ec911a --- /dev/null +++ b/tool/test/temp-cluster-server/start-cluster-servers.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +set -e + +NODE_COUNT=${1:-1} + +function server_start() { + /Users/georgii/work/typedb-driver/tool/test/temp-cluster-server/typedb \ + --server.address=127.0.0.1:${1}1729 \ + --server.encryption.enabled=false \ + --diagnostics.monitoring.port ${1}1732 \ + --server-clustering-id=${1} \ + --server-clustering-address=127.0.0.1:${1}1730 \ + --diagnostics.deployment-id=test \ + --storage.data-directory=${1}/data \ + --logging.logdir=${1}/logs \ + --development-mode.enabled true +} + +for i in $(seq 1 $NODE_COUNT); do + rm -rf $i/data 2>/dev/null + rm -rf $i/logs 2>/dev/null + mkdir -p $i/data || exit 1 + mkdir -p $i/logs || exit 1 +done +echo Starting a cluster consisting of $NODE_COUNT servers... +for i in $(seq 1 $NODE_COUNT); do + server_start $i & +done + +ROOT_CA=`realpath tool/test/resources/encryption/ext-grpc-root-ca.pem` +export ROOT_CA + +POLL_INTERVAL_SECS=0.5 +MAX_RETRIES=60 +RETRY_NUM=0 +while [[ $RETRY_NUM -lt $MAX_RETRIES ]]; do + RETRY_NUM=$(($RETRY_NUM + 1)) + if [[ $(($RETRY_NUM % 4)) -eq 0 ]]; then + echo Waiting for TypeDB Cluster servers to start \($(($RETRY_NUM / 2))s\)... + fi + ALL_STARTED=1 + for i in $(seq 1 $NODE_COUNT); do + lsof -i :${i}1729 || ALL_STARTED=0 + done + if (( $ALL_STARTED )); then + break + fi + sleep $POLL_INTERVAL_SECS +done +if (( ! $ALL_STARTED )); then + echo Failed to start one or more TypeDB Cluster servers + exit 1 +fi +echo $NODE_COUNT TypeDB Cluster database servers started diff --git a/tool/test/temp-cluster-server/stop-cluster-servers.sh b/tool/test/temp-cluster-server/stop-cluster-servers.sh new file mode 100755 index 0000000000..aed5d555f8 --- /dev/null +++ b/tool/test/temp-cluster-server/stop-cluster-servers.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +set -e + +ps aux | grep '[t]emp-cluster-server/typedb' | awk '{print $2}' | xargs kill + +#procs=$(ps aux | awk '/TypeDBCloudServe[r]/ {print $2}' | paste -sd " " -) +#echo $procs +#if [ -n "$procs" ]; then +# kill $procs +#fi