From 0f1bdd02a7dd3de6bb2a5f5d81dd0bf24f16853b Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Wed, 18 Jun 2025 17:55:52 +0100 Subject: [PATCH 01/35] Generalise server management. Add configurable addresses. Start refactoring components to use server manager --- Cargo.lock | 2 +- c/src/concept/concept.rs | 24 -- c/src/connection.rs | 51 +++- c/src/database.rs | 2 +- c/swig/typedb_driver_java.swg | 4 + c/typedb_driver.i | 1 + dependencies/typedb/repositories.bzl | 5 +- java/api/Driver.java | 25 ++ java/api/database/Database.java | 67 ---- java/api/server/Replica.java | 49 +++ java/api/server/ReplicaType.java | 71 +++++ java/api/server/ServerVersion.java | 74 +++++ rust/Cargo.toml | 2 +- rust/src/common/error.rs | 5 +- rust/src/common/info.rs | 17 -- rust/src/concept/value.rs | 34 +-- rust/src/connection/database/import_stream.rs | 2 +- rust/src/connection/message.rs | 9 +- rust/src/connection/mod.rs | 4 +- rust/src/connection/network/channel.rs | 1 - rust/src/connection/network/proto/common.rs | 2 +- rust/src/connection/network/proto/database.rs | 24 +- rust/src/connection/network/proto/message.rs | 56 +++- rust/src/connection/network/proto/mod.rs | 1 + rust/src/connection/network/proto/server.rs | 69 +++++ rust/src/connection/network/stub.rs | 8 +- .../src/connection/network/transmitter/rpc.rs | 3 + .../network/transmitter/transaction.rs | 15 +- rust/src/connection/server/addresses.rs | 150 +++++++++ rust/src/connection/server/mod.rs | 26 ++ .../{ => server}/server_connection.rs | 112 +++---- rust/src/connection/server/server_manager.rs | 287 ++++++++++++++++++ rust/src/connection/server/server_replica.rs | 70 +++++ rust/src/connection/server/server_version.rs | 37 +++ rust/src/database/database.rs | 284 +---------------- rust/src/database/database_manager.rs | 199 ++++-------- rust/src/database/migration.rs | 2 +- rust/src/driver.rs | 182 +++++------ rust/src/user/user.rs | 2 +- rust/src/user/user_manager.rs | 14 +- 40 files changed, 1215 insertions(+), 777 deletions(-) create mode 100644 java/api/server/Replica.java create mode 100644 java/api/server/ReplicaType.java create mode 100644 java/api/server/ServerVersion.java create mode 100644 rust/src/connection/network/proto/server.rs create mode 100644 rust/src/connection/server/addresses.rs create mode 100644 rust/src/connection/server/mod.rs rename rust/src/connection/{ => server}/server_connection.rs (87%) create mode 100644 rust/src/connection/server/server_manager.rs create mode 100644 rust/src/connection/server/server_replica.rs create mode 100644 rust/src/connection/server/server_version.rs diff --git a/Cargo.lock b/Cargo.lock index 1890760f27..68efe4c664 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2753,7 +2753,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=36c7175097f02b3e2d6f0956652d90564e5c89f8#36c7175097f02b3e2d6f0956652d90564e5c89f8" dependencies = [ "prost", "tonic", diff --git a/c/src/concept/concept.rs b/c/src/concept/concept.rs index 115e76f2b0..200c31ad4a 100644 --- a/c/src/concept/concept.rs +++ b/c/src/concept/concept.rs @@ -48,14 +48,6 @@ 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. @@ -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 { diff --git a/c/src/connection.rs b/c/src/connection.rs index fe569e73bb..cdc7325e42 100644 --- a/c/src/connection.rs +++ b/c/src/connection.rs @@ -19,13 +19,14 @@ use std::{ffi::c_char, path::Path}; -use typedb_driver::{Credentials, DriverOptions, TypeDBDriver}; +use chrono::DateTime; +use typedb_driver::{concept::value::TimeZone, Credentials, DriverOptions, TypeDBDriver}; use super::{ error::{try_release, unwrap_void}, memory::{borrow, free, string_view}, }; -use crate::memory::release; +use crate::memory::{release, release_string, string_free}; const DRIVER_LANG: &'static str = "c"; @@ -89,33 +90,59 @@ 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 +/// 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 +/// 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 +/// 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 +/// Frees the native rust DriverOptions object #[no_mangle] pub extern "C" fn driver_options_drop(driver_options: *mut DriverOptions) { free(driver_options); } + +/// ServerVersion is an FFI representation of a full server's version specification. +#[repr(C)] +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 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/database.rs b/c/src/database.rs index dacf866e1e..768a92693b 100644 --- a/c/src/database.rs +++ b/c/src/database.rs @@ -19,7 +19,7 @@ use std::{ffi::c_char, path::Path, ptr::addr_of_mut, sync::Arc}; -use typedb_driver::{box_stream, info::ReplicaInfo, Database}; +use typedb_driver::{box_stream, info::ServerInfo, Database}; use super::{ error::{try_release_string, unwrap_void}, diff --git a/c/swig/typedb_driver_java.swg b/c/swig/typedb_driver_java.swg index f2617c8ebb..06855a4062 100644 --- a/c/swig/typedb_driver_java.swg +++ b/c/swig/typedb_driver_java.swg @@ -186,6 +186,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; @@ -225,6 +228,7 @@ %nojavaexception ~Duration; %nojavaexception ~Error; //%nojavaexception ~ReplicaInfo; +%nojavaexception ~ServerVersion; %nojavaexception ~StringIterator; %nojavaexception ~StringAndOptValue; %nojavaexception ~StringAndOptValueIterator; diff --git a/c/typedb_driver.i b/c/typedb_driver.i index f95d874103..75e80662b8 100644 --- a/c/typedb_driver.i +++ b/c/typedb_driver.i @@ -84,6 +84,7 @@ struct Type {}; %dropproxydefined(DatetimeAndTimeZone, datetime_and_time_zone) %dropproxydefined(StringAndOptValue, string_and_opt_value) +%dropproxydefined(ServerVersion, server_version) %dropproxy(StringAndOptValueIterator, string_and_opt_value_iterator) %dropproxy(StringIterator, string_iterator) diff --git a/dependencies/typedb/repositories.bzl b/dependencies/typedb/repositories.bzl index 91814964f2..b9086f4bb5 100644 --- a/dependencies/typedb/repositories.bzl +++ b/dependencies/typedb/repositories.bzl @@ -25,10 +25,11 @@ def typedb_dependencies(): ) def typedb_protocol(): + # TODO: Return typedb 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 + remote = "https://github.com/farost/typedb-protocol", + commit = "36c7175097f02b3e2d6f0956652d90564e5c89f8", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_protocol ) def typedb_behaviour(): diff --git a/java/api/Driver.java b/java/api/Driver.java index acf156ff68..20b9965201 100644 --- a/java/api/Driver.java +++ b/java/api/Driver.java @@ -20,11 +20,14 @@ package com.typedb.driver.api; import com.typedb.driver.api.database.DatabaseManager; +import com.typedb.driver.api.server.Replica; import com.typedb.driver.api.user.UserManager; import com.typedb.driver.common.exception.TypeDBDriverException; import javax.annotation.CheckReturnValue; +import static com.typedb.driver.common.exception.ErrorMessage.Internal.UNEXPECTED_NATIVE_VALUE; + public interface Driver extends AutoCloseable { String LANGUAGE = "java"; @@ -94,4 +97,26 @@ public interface Driver extends AutoCloseable { */ @CheckReturnValue UserManager users(); + + /** + * Set of Replica instances for this driver connection. + * + *

Examples

+ *
+     * driver.replicas()
+     * 
+ */ + @CheckReturnValue + Set replicas(); + + /** + * Returns the primary replica for this driver connection. + * + *

Examples

+ *
+     * driver.primaryReplica()
+     * 
+ */ + @CheckReturnValue + Optional primaryReplica(); } diff --git a/java/api/database/Database.java b/java/api/database/Database.java index 4b2bb4bc15..948de93909 100644 --- a/java/api/database/Database.java +++ b/java/api/database/Database.java @@ -76,71 +76,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/server/Replica.java b/java/api/server/Replica.java new file mode 100644 index 0000000000..a87c3d9fb4 --- /dev/null +++ b/java/api/server/Replica.java @@ -0,0 +1,49 @@ +/* + * 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 javax.annotation.CheckReturnValue; + +import static com.typedb.driver.common.exception.ErrorMessage.Internal.UNEXPECTED_NATIVE_VALUE; + +/** + * The metadata and state of an individual raft replica of a driver connection. + */ +public interface Replica { + /** + * The address this replica is hosted at. + */ + @CheckReturnValue + String address(); + + /** + * Gets the type of this replica: whether it's a primary or a secondary replica. + */ + @CheckReturnValue + ReplicaType type(); + + /** + * The raft protocol ‘term’ of this replica. + */ + @CheckReturnValue + long term(); +} diff --git a/java/api/server/ReplicaType.java b/java/api/server/ReplicaType.java new file mode 100644 index 0000000000..62e6956750 --- /dev/null +++ b/java/api/server/ReplicaType.java @@ -0,0 +1,71 @@ +/* + * 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 javax.annotation.CheckReturnValue; + +import static com.typedb.driver.common.exception.ErrorMessage.Internal.UNEXPECTED_NATIVE_VALUE; + +/** + * Type of replica. + * + *

Examples

+ *
+ * replica.type();
+ * 
+ */ +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.QueryType.Primary; + } + + /** + * Checks whether this is a secondary replica of the raft cluster. + */ + public boolean isSecondary() { + return nativeObject == com.typedb.driver.jni.QueryType.Secondary; + } +} diff --git a/java/api/server/ServerVersion.java b/java/api/server/ServerVersion.java new file mode 100644 index 0000000000..1f48ec2df0 --- /dev/null +++ b/java/api/server/ServerVersion.java @@ -0,0 +1,74 @@ +/* + * 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.ErrorMessage; +import com.typedb.driver.common.exception.TypeDBDriverException; + +import static com.typedb.driver.common.exception.ErrorMessage.Internal.UNEXPECTED_NATIVE_VALUE; + +/** + * Type of replica. + * + *

Examples

+ *
+ * replica.type();
+ * 
+ */ +public class ServerVersion { + private final String distribution; + private final String version; + + /** + * @hidden + */ + ServerVersion(com.typedb.driver.jni.ServerVersion nativeObject) { + if (nativeObject == null) throw new TypeDBDriverException(ErrorMessage.Internal.NULL_NATIVE_VALUE); + this.distribution = nativeObject.getDistribution(); + this.version = nativeObject.getVersion(); + } + + // TODO: Rename to getters? Make the fields public instead? + + /** + * Returns the server's distribution. + * + *

Examples

+ *
+     * serverVersion.distribution();
+     * 
+ */ + public String distribution() { + return distribution; + } + + + /** + * Returns the server's version. + * + *

Examples

+ *
+     * serverVersion.version();
+     * 
+ */ + public String version() { + return version; + } +} diff --git a/rust/Cargo.toml b/rust/Cargo.toml index df6aa895be..72b3ec44e7 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -60,7 +60,7 @@ [dependencies.typedb-protocol] features = [] - rev = "38f66a1cc4db3b7a301676f50800e9530ac5c8a3" + rev = "36c7175097f02b3e2d6f0956652d90564e5c89f8" git = "https://github.com/typedb/typedb-protocol" default-features = false diff --git a/rust/src/common/error.rs b/rust/src/common/error.rs index a6493869ee..dcfd6d54be 100644 --- a/rust/src/common/error.rs +++ b/rust/src/common/error.rs @@ -24,6 +24,7 @@ use tonic::{Code, Status}; use tonic_types::{ErrorDetails, ErrorInfo, StatusExt}; use super::{address::Address, RequestID}; +use crate::connection::server::Addresses; macro_rules! error_messages { { @@ -120,7 +121,7 @@ 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
} = + ServerConnectionFailed { addresses: Addresses } = 2: "Unable to connect to TypeDB server(s) at: \n{addresses:?}", ServerConnectionFailedWithError { error: String } = 3: "Unable to connect to TypeDB server(s), received errors: \n{error}", @@ -184,6 +185,8 @@ 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.", + UnexpectedReplicaType { replica_type: i32 } = + 34: "Unexpected replica type in message received from server: {replica_type}. This is either a version compatibility issue or a bug.", } error_messages! { ConceptError 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/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/message.rs b/rust/src/connection/message.rs index 20239740ca..81a354644e 100644 --- a/rust/src/connection/message.rs +++ b/rust/src/connection/message.rs @@ -32,6 +32,7 @@ use crate::{ }, common::{address::Address, info::DatabaseInfo, RequestID}, concept::Concept, + connection::{server::server_replica::ServerReplica, ServerVersion}, error::ServerError, info::UserInfo, Credentials, QueryOptions, TransactionOptions, TransactionType, @@ -42,6 +43,7 @@ pub(super) enum Request { ConnectionOpen { driver_lang: String, driver_version: String, credentials: Credentials }, ServersAll, + ServerVersion, DatabasesAll, DatabaseGet { database_name: String }, @@ -69,11 +71,14 @@ pub(super) enum Response { ConnectionOpen { connection_id: Uuid, server_duration_millis: u64, - databases: Vec, + servers: Vec, }, ServersAll { - servers: Vec
, + servers: Vec, + }, + ServerVersion { + server_version: ServerVersion, }, DatabasesContains { diff --git a/rust/src/connection/mod.rs b/rust/src/connection/mod.rs index 25a8d4ecaa..41be61b38c 100644 --- a/rust/src/connection/mod.rs +++ b/rust/src/connection/mod.rs @@ -18,7 +18,7 @@ */ pub(crate) use self::transaction_stream::TransactionStream; -pub use self::{credentials::Credentials, driver_options::DriverOptions}; +pub use self::{credentials::Credentials, driver_options::DriverOptions, server::ServerVersion}; mod credentials; pub(crate) mod database; @@ -26,5 +26,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..b7c47c092e 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, 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/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..5c54e6bd42 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::server_replica::ServerReplica, + ServerVersion, }, error::{ConnectionError, InternalError, ServerError}, info::UserInfo, @@ -60,6 +64,15 @@ impl TryIntoProto for Request { } } +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 +298,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 +322,18 @@ 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::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 +359,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..90589deca8 --- /dev/null +++ b/rust/src/connection/network/proto/server.rs @@ -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. + */ + +use typedb_protocol::{ + server::{version::Res as VersionProto, ReplicationStatus as ReplicationStatusProto}, + Server as ServerProto, +}; + +use super::TryFromProto; +use crate::{ + common::Result, + connection::{ + server::server_replica::{ReplicaType, ReplicationStatus, ServerReplica}, + ServerVersion, + }, + error::ConnectionError, +}; + +impl TryFromProto for ServerReplica { + fn try_from_proto(proto: ServerProto) -> Result { + let address = match proto.address { + Some(address) => address.address.parse()?, + None => return Err(ConnectionError::MissingResponseField { field: "address" }.into()), + }; + let replication_status = match proto.replication_status { + Some(replication_status) => ReplicationStatus::try_from_proto(replication_status)?, + None => ReplicationStatus::default(), + }; + Ok(Self { address, replica_type: replication_status.replica_type, term: replication_status.term }) + } +} + +impl TryFromProto for ReplicationStatus { + fn try_from_proto(proto: ReplicationStatusProto) -> Result { + Ok(Self { 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..dd67295ddb 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,10 @@ impl RPCStub { self.single(|this| Box::pin(this.grpc.servers_all(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/rpc.rs b/rust/src/connection/network/transmitter/rpc.rs index 7fb87d0cb6..855c5384ae 100644 --- a/rust/src/connection/network/transmitter/rpc.rs +++ b/rust/src/connection/network/transmitter/rpc.rs @@ -115,6 +115,9 @@ impl RPCTransmitter { } Request::ServersAll => rpc.servers_all(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) diff --git a/rust/src/connection/network/transmitter/transaction.rs b/rust/src/connection/network/transmitter/transaction.rs index 81465efadb..acae3bfc4d 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,7 +54,7 @@ use crate::{ Callback, Promise, RequestID, Result, }, connection::{ - message::{QueryResponse, Request, Response, TransactionRequest, TransactionResponse}, + message::{QueryResponse, Response, TransactionRequest, TransactionResponse}, network::proto::{FromProto, IntoProto, TryFromProto}, runtime::BackgroundRuntime, server_connection::LatencyTracker, diff --git a/rust/src/connection/server/addresses.rs b/rust/src/connection/server/addresses.rs new file mode 100644 index 0000000000..83c987bec0 --- /dev/null +++ b/rust/src/connection/server/addresses.rs @@ -0,0 +1,150 @@ +/* + * 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 itertools::Itertools; + +use crate::common::address::Address; + +// TODO: Move to common/address.rs? + +/// 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: String) -> crate::Result { + let address = address_str.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.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 "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) -> crate::Result { + let mut addresses = HashMap::new(); + for (address_key, address_value) in addresses_str.into_iter() { + addresses.insert(address_key.parse()?, address_value.parse()?); + } + Ok(Self::from_translation(addresses)) + } + + /// Prepare addresses based on multiple 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) + } + + pub(crate) fn addresses(&self) -> AddressIter<'_> { + match self { + Addresses::Direct(vec) => AddressIter::Direct(vec.iter()), + Addresses::Translated(map) => AddressIter::Translated(map.keys()), + } + } +} + +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/rust/src/connection/server/mod.rs b/rust/src/connection/server/mod.rs new file mode 100644 index 0000000000..938e2705eb --- /dev/null +++ b/rust/src/connection/server/mod.rs @@ -0,0 +1,26 @@ +/* + * 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 use self::{addresses::Addresses, server_version::ServerVersion}; + +pub(crate) mod addresses; +pub(crate) mod server_connection; +pub(crate) mod server_manager; +pub(crate) mod server_replica; +mod server_version; diff --git a/rust/src/connection/server_connection.rs b/rust/src/connection/server/server_connection.rs similarity index 87% rename from rust/src/connection/server_connection.rs rename to rust/src/connection/server/server_connection.rs index 721a13083a..072dd25922 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,20 @@ 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, - TransactionStream, + server::server_replica::ServerReplica, + ServerVersion, TransactionStream, }, error::{ConnectionError, InternalError}, info::{DatabaseInfo, UserInfo}, - Credentials, DriverOptions, TransactionOptions, TransactionType, User, + Credentials, DriverOptions, Result, TransactionOptions, TransactionType, }; #[derive(Clone)] @@ -65,11 +65,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 +80,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 +89,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,42 +98,27 @@ 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() } - #[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()); - } - 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()); - } - self.request_transmitter.request_blocking(request) - } - - pub(crate) fn force_close(&self) -> crate::Result { + pub(crate) fn force_close(&self) -> Result { for sender in self.shutdown_senders.lock().unwrap().drain(..) { let _ = sender.send(()); } self.request_transmitter.force_close() } - pub(crate) fn servers_all(&self) -> crate::Result> { + pub(crate) fn servers_all(&self) -> Result> { match self.request_blocking(Request::ServersAll)? { Response::ServersAll { servers } => Ok(servers), other => Err(InternalError::UnexpectedResponseType { response_type: format!("{other:?}") }.into()), @@ -141,23 +126,23 @@ impl ServerConnection { } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn all_databases(&self) -> crate::Result> { - match self.request(Request::DatabasesAll).await? { - Response::DatabasesAll { databases } => Ok(databases), + 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 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 all_databases(&self) -> Result> { + match self.request(Request::DatabasesAll).await? { + Response::DatabasesAll { databases } => Ok(databases), 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 { + 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()), @@ -165,19 +150,23 @@ impl ServerConnection { } #[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 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) -> 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 +185,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 +199,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 +207,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 +226,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(); @@ -272,7 +261,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 +269,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 +277,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 +285,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 +293,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 +307,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..765f1aa203 --- /dev/null +++ b/rust/src/connection/server/server_manager.rs @@ -0,0 +1,287 @@ +/* + * 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, + sync::{Arc, RwLock}, + time::Duration, +}; + +use itertools::Itertools; +use log::debug; + +use crate::{ + common::address::Address, + connection::{ + message::{Request, Response}, + runtime::BackgroundRuntime, + server::{server_connection::ServerConnection, server_replica::ServerReplica, Addresses}, + }, + error::{ConnectionError, InternalError}, + Credentials, DriverOptions, Error, Result, +}; + +pub(crate) struct ServerManager { + // TODO: Merge ServerConnection with ServerReplica? + server_connections: HashMap, + replicas: RwLock>, +} + +impl ServerManager { + 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); + + #[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 { + let replicas = Self::fetch_server_list( + background_runtime.clone(), + &addresses, + credentials.clone(), + driver_options.clone(), + driver_lang.as_ref(), + driver_version.as_ref(), + ) + .await?; + let mut server_connections = HashMap::new(); + for replica in &replicas { + let (server_connection, _) = ServerConnection::new( + background_runtime.clone(), + replica.address().clone(), + credentials.clone(), + driver_options.clone(), + driver_lang.as_ref(), + driver_version.as_ref(), + ) + .await?; + server_connections.insert(replica.address().clone(), server_connection); + } + + if server_connections.is_empty() { + return Err(ConnectionError::ServerConnectionFailed { addresses }.into()); + } + + Ok(Self { server_connections, replicas: RwLock::new(replicas) }) + } + + pub(crate) fn force_close(&self) -> Result { + self.server_connections.values().map(ServerConnection::force_close).try_collect().map_err(Into::into) + } + + pub(crate) async fn servers_all(&self) -> Result> { + self.run_failsafe(|server_connection| async move { + server_connection.servers_all() + }).await + } + + 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, address: &Address) -> Option<&ServerConnection> { + self.server_connections.get(address) + } + + pub(crate) fn connections(&self) -> impl Iterator + '_ { + self.server_connections.iter() + } + + // TODO: Implement everything below + + // #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + // pub(crate) async fn run_failsafe(&self, task: F) -> Result + // where + // F: Fn(crate::database::database::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(crate) async fn run_failsafe(&self, task: F) -> Result + where + F: Fn(ServerConnection) -> 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()).await { + Ok(res) => return Ok(res), + // TODO: Refactor errors + // 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") })? + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub(super) async fn run_on_any_replica(&self, task: F) -> Result + where + F: Fn(crate::database::database::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(crate::database::database::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 = ServerReplica::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: Addresses::from_addresses(server_connections.keys().map(Address::clone)), + }) + } + + fn primary_replica(&self) -> Option { + self.replicas + .read() + .expect("Expected a read replica lock") + .iter() + .filter(|replica| replica.is_primary()) + .max_by_key(|replica| replica.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 + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn fetch_server_list( + background_runtime: Arc, + addresses: &Addresses, + credentials: Credentials, + driver_options: DriverOptions, + driver_lang: impl AsRef, + driver_version: impl AsRef, + ) -> Result> { + for address in addresses.addresses() { + let server_connection = ServerConnection::new( + background_runtime.clone(), + address.clone(), + credentials.clone(), + driver_options.clone(), + driver_lang.as_ref(), + driver_version.as_ref(), + ) + .await; + match server_connection { + // TODO: Don't we need to close the connection? + Ok((_, servers)) => return Ok(servers.into_iter().collect()), + // TODO: Rework connection errors + Err(Error::Connection( + ConnectionError::ServerConnectionFailedStatusError { .. } | ConnectionError::ConnectionFailed, + )) => (), + Err(err) => return Err(err), + } + } + Err(ConnectionError::ServerConnectionFailed { addresses: addresses.clone() }.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..4208e5e78c --- /dev/null +++ b/rust/src/connection/server/server_replica.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 crate::common::address::Address; + +/// The metadata and state of an individual raft replica of a driver connection. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct ServerReplica { + /// The address this replica is hosted at. + pub(crate) address: Address, + /// Checks whether this is the primary replica of the raft cluster. + pub(crate) replica_type: ReplicaType, + /// The raft protocol ‘term’ of this replica. + pub(crate) term: i64, +} + +impl ServerReplica { + pub fn address(&self) -> &Address { + &self.address + } + + pub fn replica_type(&self) -> ReplicaType { + self.replica_type + } + + pub fn is_primary(&self) -> bool { + matches!(self.replica_type, ReplicaType::Primary) + } + + pub fn term(&self) -> i64 { + self.term + } +} + +/// The metadata and state of an individual server as a raft replica. +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct ReplicationStatus { + /// The role of this replica in the raft cluster. + pub replica_type: ReplicaType, + /// The raft protocol ‘term’ of this server replica. + pub term: i64, +} + +impl Default for ReplicationStatus { + fn default() -> Self { + Self { replica_type: ReplicaType::Primary, term: 0 } + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub(crate) 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..7df9f7db0a --- /dev/null +++ b/rust/src/connection/server/server_version.rs @@ -0,0 +1,37 @@ +/* + * 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. + */ + +/// A full TypeDB's server 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 + } +} diff --git a/rust/src/database/database.rs b/rust/src/database/database.rs index 59a315ac24..997b9299f3 100644 --- a/rust/src/database/database.rs +++ b/rust/src/database/database.rs @@ -17,68 +17,39 @@ * 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, + sync::Arc, time::Duration, }; -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::{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 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,43 +57,6 @@ impl Database { self.name.as_str() } - /// Returns the `Replica` instances for this database. - /// _Only works in TypeDB Cloud / Enterprise_ - /// - /// # Examples - /// - /// ```rust - /// database.replicas_info() - /// ``` - pub fn replicas_info(&self) -> Vec { - self.replicas.read().unwrap().iter().map(Replica::to_info).collect() - } - - /// Returns the primary replica for this database. - /// _Only works in TypeDB Cloud / Enterprise_ - /// - /// # Examples - /// - /// ```rust - /// database.primary_replica_info() - /// ``` - pub fn primary_replica_info(&self) -> Option { - self.primary_replica().map(|replica| replica.to_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 / Enterprise_ - /// - /// # Examples - /// - /// ```rust - /// database.preferred_replica_info(); - /// ``` - pub fn preferred_replica_info(&self) -> Option { - self.preferred_replica().map(|replica| replica.to_info()) - } - /// Deletes this database. /// /// # Examples @@ -205,104 +139,6 @@ 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 { @@ -311,106 +147,6 @@ impl fmt::Debug for Database { } } -/// 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, diff --git a/rust/src/database/database_manager.rs b/rust/src/database/database_manager.rs index 40ca837947..3ff7edf0af 100644 --- a/rust/src/database/database_manager.rs +++ b/rust/src/database/database_manager.rs @@ -17,49 +17,33 @@ * 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::{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. /// /// # Examples /// @@ -69,24 +53,16 @@ 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.server_manager + .run_failsafe(move |server_connection| async move { + server_connection + .all_databases() + .await? + .into_iter() + .map(|database_info| self.try_build_database(database_info)) + .try_collect() + }) + .await } /// Retrieve the database with the given name. @@ -102,20 +78,16 @@ 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> { + let name = name.into(); + let database_info = self + .server_manager + .run_failsafe(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 @@ -133,14 +105,15 @@ impl DatabaseManager { #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn contains(&self, name: impl Into) -> Result { let name = name.into(); - self.run_failsafe( - name, - |server_connection, name| async move { server_connection.contains_database(name).await }, - ) - .await + self.server_manager + .run_failsafe(move |server_connection| { + let name = name.clone(); + async move { server_connection.contains_database(name).await } + }) + .await } - /// Create a database with the given name + /// Create a database with the given name. /// /// # Arguments /// @@ -155,11 +128,12 @@ impl DatabaseManager { #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn create(&self, name: impl Into) -> 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 + .run_failsafe(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 @@ -193,74 +167,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?; + self.server_manager + .run_failsafe(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?; + + 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))?; + } + } - let mut item_buffer = Vec::with_capacity(ITEM_BATCH_SIZE); - let mut read_item_iterator = ProtoMessageIterator::::new(BufReader::new(file)); + if !item_buffer.is_empty() { + import_stream.send_items(item_buffer)?; + } - 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))?; + resolve!(import_stream.done()) } - } - - if !item_buffer.is_empty() { - import_stream.send_items(item_buffer)?; - } - - resolve!(import_stream.done()) - }) - .await - } - - #[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, - } + }) + .await } - fn try_get_cached(&self, name: &str) -> Option> { - self.databases_cache.read().unwrap().get(name).cloned() - } - - fn cache_insert(&self, database: Database) { - self.databases_cache.write().unwrap().insert(database.name().to_owned(), Arc::new(database)); - } - - #[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..6abb108b26 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, }; diff --git a/rust/src/driver.rs b/rust/src/driver.rs index 93f3b5ffde..ae85106f31 100644 --- a/rust/src/driver.rs +++ b/rust/src/driver.rs @@ -17,11 +17,7 @@ * under the License. */ -use std::{ - collections::{HashMap, HashSet}, - fmt, - sync::Arc, -}; +use std::{collections::HashMap, fmt, sync::Arc}; use itertools::Itertools; @@ -31,13 +27,21 @@ use crate::{ error::{ConnectionError, Error}, Result, }, - connection::{runtime::BackgroundRuntime, server_connection::ServerConnection}, - Credentials, DatabaseManager, DriverOptions, Transaction, TransactionOptions, TransactionType, UserManager, + connection::{ + runtime::BackgroundRuntime, + server::{ + server_connection::ServerConnection, server_manager::ServerManager, server_replica::ServerReplica, + Addresses, + }, + ServerVersion, + }, + Credentials, Database, 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, @@ -56,23 +60,22 @@ 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 /// /// # 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(Address::try_from_address_str(\"127.0.0.1:1729\").unwrap())")] + #[cfg_attr( + not(feature = "sync"), + doc = "TypeDBDriver::new(Address::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 +84,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 +92,70 @@ 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(Address::try_from_address_str(\"127.0.0.1:1729\").unwrap(), \"rust\")" + )] + #[cfg_attr( + not(feature = "sync"), + doc = "TypeDBDriver::new_with_description(Address::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_manager = Arc::new( + ServerManager::new( + background_runtime.clone(), + addresses, + credentials, + driver_options, + driver_lang.as_ref(), + Self::VERSION, + ) + .await?, + ); + let database_manager = DatabaseManager::new(server_manager.clone())?; + let user_manager = UserManager::new(server_manager.clone()); - 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_manager, database_manager, user_manager, background_runtime }) + } - Ok(Self { server_connections, database_manager, user_manager, background_runtime }) + /// Retrieves the server's version. + /// + /// # 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 { + // TODO: check expect + self.server_connections.iter().next().expect("TODO: Change to failsafe or smth").1.version().await } + /// Retrieves the server's replicas. + /// + /// # 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)] - 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( - background_runtime.clone(), - address.clone(), - credentials.clone(), - driver_options.clone(), - Self::DRIVER_LANG, - 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()) + pub async fn replicas(&self) -> Result> { + // TODO: check expect + todo!("Todo implement") } /// Checks it this connection is opened. - // + /// /// # Examples /// /// ```rust @@ -193,7 +184,7 @@ impl TypeDBDriver { self.transaction_with_options(database_name, transaction_type, TransactionOptions::new()).await } - /// Performs a TypeQL query in this transaction. + /// Opens a new transaction. /// /// # Arguments /// @@ -204,7 +195,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,7 +212,7 @@ 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 database = self.database_manager.get(database_name).await?; let transaction_stream = database .run_failsafe(|database| async move { database.connection().open_transaction(database.name(), transaction_type, options).await @@ -239,28 +237,6 @@ impl TypeDBDriver { 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(), - }) - } } impl fmt::Debug for TypeDBDriver { diff --git a/rust/src/user/user.rs b/rust/src/user/user.rs index 98b9fb3061..0a6d687c36 100644 --- a/rust/src/user/user.rs +++ b/rust/src/user/user.rs @@ -52,7 +52,7 @@ impl User { 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(()), + Ok(res) => return Ok(res), Err(err) => error_buffer.push(format!("- {}: {}", server_id, err)), } } diff --git a/rust/src/user/user_manager.rs b/rust/src/user/user_manager.rs index 39dbc4837c..8ce6d78d80 100644 --- a/rust/src/user/user_manager.rs +++ b/rust/src/user/user_manager.rs @@ -16,11 +16,11 @@ * 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, + common::{Result}, + connection::{server::server_manager::ServerManager}, error::ConnectionError, User, }; @@ -28,12 +28,12 @@ use crate::{ /// 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 } } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] @@ -42,7 +42,7 @@ impl UserManager { .server_connections .iter() .next() - .expect("Unexpected condition: the server connection collection is empty"); + .expect("Expected a non-empty server connection collection"); self.get(connection.username()).await } From b567dae1da0b1522c6c3c7b60b8ea38531e466cc Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Thu, 19 Jun 2025 17:38:11 +0100 Subject: [PATCH 02/35] Finished the first dirty version fo the server manager. Updated database-related structs and the driver --- rust/src/common/error.rs | 6 +- rust/src/connection/network/proto/server.rs | 2 +- .../network/transmitter/transaction.rs | 1 - rust/src/connection/server/addresses.rs | 13 +- rust/src/connection/server/server_manager.rs | 266 ++++++++++-------- rust/src/connection/server/server_replica.rs | 38 ++- rust/src/database/database.rs | 135 ++++----- rust/src/database/database_manager.rs | 14 +- rust/src/driver.rs | 17 +- rust/src/user/user_manager.rs | 16 +- 10 files changed, 264 insertions(+), 244 deletions(-) diff --git a/rust/src/common/error.rs b/rust/src/common/error.rs index dcfd6d54be..a8b98fe2bf 100644 --- a/rust/src/common/error.rs +++ b/rust/src/common/error.rs @@ -163,8 +163,8 @@ error_messages! { ConnectionError 21: "Connection failed. 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 } = @@ -185,8 +185,6 @@ 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.", - UnexpectedReplicaType { replica_type: i32 } = - 34: "Unexpected replica type in message received from server: {replica_type}. This is either a version compatibility issue or a bug.", } error_messages! { ConceptError diff --git a/rust/src/connection/network/proto/server.rs b/rust/src/connection/network/proto/server.rs index 90589deca8..a4c579b6e5 100644 --- a/rust/src/connection/network/proto/server.rs +++ b/rust/src/connection/network/proto/server.rs @@ -42,7 +42,7 @@ impl TryFromProto for ServerReplica { Some(replication_status) => ReplicationStatus::try_from_proto(replication_status)?, None => ReplicationStatus::default(), }; - Ok(Self { address, replica_type: replication_status.replica_type, term: replication_status.term }) + Ok(Self::from_private(address, replication_status.replica_type, replication_status.term)) } } diff --git a/rust/src/connection/network/transmitter/transaction.rs b/rust/src/connection/network/transmitter/transaction.rs index acae3bfc4d..2df25c3d8b 100644 --- a/rust/src/connection/network/transmitter/transaction.rs +++ b/rust/src/connection/network/transmitter/transaction.rs @@ -57,7 +57,6 @@ use crate::{ message::{QueryResponse, Response, TransactionRequest, TransactionResponse}, network::proto::{FromProto, IntoProto, TryFromProto}, runtime::BackgroundRuntime, - server_connection::LatencyTracker, }, Error, }; diff --git a/rust/src/connection/server/addresses.rs b/rust/src/connection/server/addresses.rs index 83c987bec0..55141ec766 100644 --- a/rust/src/connection/server/addresses.rs +++ b/rust/src/connection/server/addresses.rs @@ -84,7 +84,7 @@ impl Addresses { Self::Direct(addresses.into_iter().collect()) } - /// Prepare addresses based on multiple key-value "key:port" string pairs. + /// 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). /// @@ -107,7 +107,7 @@ impl Addresses { Ok(Self::from_translation(addresses)) } - /// Prepare addresses based on multiple TypeDB address pairs. + /// 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). /// @@ -131,6 +131,15 @@ impl Addresses { Addresses::Translated(map) => AddressIter::Translated(map.keys()), } } + + pub(crate) fn address_translation(&self) -> HashMap { + match self { + Addresses::Direct(addresses) => { + addresses.into_iter().map(|address| (address.clone(), address.clone())).collect() + } + Addresses::Translated(translation) => translation.clone(), + } + } } enum AddressIter<'a> { diff --git a/rust/src/connection/server/server_manager.rs b/rust/src/connection/server/server_manager.rs index 765f1aa203..2591365f1d 100644 --- a/rust/src/connection/server/server_manager.rs +++ b/rust/src/connection/server/server_manager.rs @@ -21,7 +21,7 @@ use std::{ collections::{HashMap, HashSet}, fmt, future::Future, - sync::{Arc, RwLock}, + sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}, time::Duration, }; @@ -31,7 +31,7 @@ use log::debug; use crate::{ common::address::Address, connection::{ - message::{Request, Response}, + message::Response, runtime::BackgroundRuntime, server::{server_connection::ServerConnection, server_replica::ServerReplica, Addresses}, }, @@ -40,9 +40,17 @@ use crate::{ }; pub(crate) struct ServerManager { - // TODO: Merge ServerConnection with ServerReplica? - server_connections: HashMap, - replicas: RwLock>, + // TODO: Merge ServerConnection with ServerReplica? Probably should not as they can be different + initial_addresses: Addresses, + replicas: RwLock>, + server_connections: RwLock>, + address_translation: HashMap, + + background_runtime: Arc, + credentials: Credentials, + driver_options: DriverOptions, + driver_lang: String, + driver_version: String, } impl ServerManager { @@ -59,7 +67,7 @@ impl ServerManager { driver_lang: impl AsRef, driver_version: impl AsRef, ) -> Result { - let replicas = Self::fetch_server_list( + let replicas = Self::fetch_replicas_from_addresses( background_runtime.clone(), &addresses, credentials.clone(), @@ -68,163 +76,163 @@ impl ServerManager { driver_version.as_ref(), ) .await?; - let mut server_connections = HashMap::new(); - for replica in &replicas { - let (server_connection, _) = ServerConnection::new( - background_runtime.clone(), - replica.address().clone(), - credentials.clone(), - driver_options.clone(), - driver_lang.as_ref(), - driver_version.as_ref(), - ) - .await?; - server_connections.insert(replica.address().clone(), server_connection); + let address_translation = addresses.address_translation(); + + let mut server_manager = Self { + initial_addresses: addresses, + replicas: RwLock::new(replicas), + server_connections: RwLock::new(HashMap::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_server_connections().await?; + Ok(server_manager) + } + + async fn update_server_connections(&self) -> Result { + let replicas = self.replicas.read().expect("Expected replicas read access"); + let replica_addresses: HashSet
= + replicas.iter().map(|replica| replica.private_address().clone()).collect(); + let mut connection_errors = Vec::with_capacity(replicas.len()); + let mut server_connections = self.write_server_connections(); + server_connections.retain(|address, _| replica_addresses.contains(address)); + for replica in replicas.iter() { + let private_address = replica.private_address().clone(); + if !server_connections.contains_key(&private_address) { + match ServerConnection::new( + self.background_runtime.clone(), + replica.address().clone(), + self.credentials.clone(), + self.driver_options.clone(), + self.driver_lang.as_ref(), + self.driver_version.as_ref(), + ) + .await + { + Ok((server_connection, _)) => { + server_connections.insert(private_address, server_connection); + } + Err(err) => { + connection_errors.push(err); + } + } + } } if server_connections.is_empty() { - return Err(ConnectionError::ServerConnectionFailed { addresses }.into()); + // TODO: use connection_errors (convert to string or what??) + Err(ConnectionError::ServerConnectionFailed { addresses: self.initial_addresses.clone() }.into()) + } else { + Ok(()) } - - Ok(Self { server_connections, replicas: RwLock::new(replicas) }) } - pub(crate) fn force_close(&self) -> Result { - self.server_connections.values().map(ServerConnection::force_close).try_collect().map_err(Into::into) - } - - pub(crate) async fn servers_all(&self) -> Result> { - self.run_failsafe(|server_connection| async move { - server_connection.servers_all() - }).await + fn read_server_connections(&self) -> RwLockReadGuard<'_, HashMap> { + self.server_connections.read().expect("Expected server connections read access") } - pub(crate) fn server_count(&self) -> usize { - self.server_connections.len() + fn write_server_connections(&self) -> RwLockWriteGuard<'_, HashMap> { + self.server_connections.write().expect("Expected server connections write access") } - pub(crate) fn servers(&self) -> impl Iterator { - self.server_connections.keys() + pub(crate) fn force_close(&self) -> Result { + self.read_server_connections().values().map(ServerConnection::force_close).try_collect().map_err(Into::into) } - pub(crate) fn connection(&self, address: &Address) -> Option<&ServerConnection> { - self.server_connections.get(address) + pub(crate) async fn servers_all(&self) -> Result> { + self.run_read_operation(|server_connection| async move { server_connection.servers_all() }).await } - pub(crate) fn connections(&self) -> impl Iterator + '_ { - self.server_connections.iter() + pub(crate) fn server_count(&self) -> usize { + self.read_server_connections().len() } - // TODO: Implement everything below - - // #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - // pub(crate) async fn run_failsafe(&self, task: F) -> Result - // where - // F: Fn(crate::database::database::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(crate) async fn run_failsafe(&self, task: F) -> Result + pub(crate) async fn run_read_operation(&self, task: F) -> Result where F: Fn(ServerConnection) -> P, P: Future>, { - let mut error_buffer = Vec::with_capacity(self.server_connections.len()); - for (server_id, server_connection) in self.server_connections.iter() { + // TODO: Add retries? + // TODO: Sort randomly? Remember the last working replica to try it first? + for (address, server_connection) in self.read_server_connections().iter() { match task(server_connection.clone()).await { - Ok(res) => return Ok(res), - // TODO: Refactor errors - // 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") })? - } - - #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(super) async fn run_on_any_replica(&self, task: F) -> Result - where - F: Fn(crate::database::database::ServerDatabase) -> P, - P: Future>, - { - let replicas = self.replicas.read().unwrap().clone(); - for replica in replicas { - match task(replica.database.clone()).await { + // TODO: refactor errors Err(Error::Connection( ConnectionError::ServerConnectionFailedStatusError { .. } | ConnectionError::ConnectionFailed, )) => { - debug!("Unable to connect to {}. Attempting next server.", replica.server); + // TODO: Expose public instead + debug!("Unable to connect to {} (private). Attempting next server.", address); } res => return res, } } - Err(Self::unable_to_connect_error(&self.server_connections)) + Err(ConnectionError::ServerConnectionFailed { + addresses: Addresses::from_addresses(self.read_server_connections().keys().map(Address::clone)), + } + .into()) } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(super) async fn run_on_primary_replica(&self, task: F) -> Result + pub(crate) async fn run_write_operation(&self, task: F) -> Result where - F: Fn(crate::database::database::ServerDatabase) -> P, + F: Fn(ServerConnection) -> P, P: Future>, { - let mut primary_replica = - if let Some(replica) = self.primary_replica() { replica } else { self.seek_primary_replica().await? }; + let mut primary_replica = match self.primary_replica() { + Some(replica) => replica, + None => self.seek_primary_replica().await?, + }; + let server_connections = self.read_server_connections(); 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?; + if let Some(server_connection) = server_connections.get(primary_replica.private_address()) { + match task(server_connection.clone()).await { + // TODO: Refactor errors + 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, } - res => return res, + } else { + // TODO: Refactor + debug!("Could not connect to the primary replica, waiting..."); + Self::wait_for_primary_replica_selection().await; + primary_replica = self.seek_primary_replica().await?; } } - Err(Self::unable_to_connect_error(&self.server_connections)) + Err(ConnectionError::ServerConnectionFailed { + addresses: Addresses::from_addresses(self.read_server_connections().keys().map(Address::clone)), + } + .into()) } #[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 = ServerReplica::fetch_all(self.name.clone(), &self.server_connections).await?; - *self.replicas.write().unwrap() = replicas; + let replicas = self.fetch_replicas()?; + *self.replicas.write().expect("Expected replicas write lock") = replicas; if let Some(replica) = self.primary_replica() { + self.update_server_connections().await?; 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: Addresses::from_addresses(server_connections.keys().map(Address::clone)), - }) + self.update_server_connections().await?; + Err(ConnectionError::ServerConnectionFailed { + addresses: Addresses::from_addresses(self.read_server_connections().keys().map(Address::clone)), + } + .into()) } fn primary_replica(&self) -> Option { @@ -233,7 +241,7 @@ impl ServerManager { .expect("Expected a read replica lock") .iter() .filter(|replica| replica.is_primary()) - .max_by_key(|replica| replica.term) + .max_by_key(|replica| replica.term()) .cloned() } @@ -248,14 +256,15 @@ impl ServerManager { } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - async fn fetch_server_list( + async fn fetch_replicas_from_addresses( background_runtime: Arc, addresses: &Addresses, credentials: Credentials, driver_options: DriverOptions, driver_lang: impl AsRef, driver_version: impl AsRef, - ) -> Result> { + ) -> Result> { + let address_translation = addresses.address_translation(); for address in addresses.addresses() { let server_connection = ServerConnection::new( background_runtime.clone(), @@ -268,7 +277,7 @@ impl ServerManager { .await; match server_connection { // TODO: Don't we need to close the connection? - Ok((_, servers)) => return Ok(servers.into_iter().collect()), + Ok((_, replicas)) => return Ok(Self::translate_replicas(replicas, &address_translation)), // TODO: Rework connection errors Err(Error::Connection( ConnectionError::ServerConnectionFailedStatusError { .. } | ConnectionError::ConnectionFailed, @@ -278,6 +287,29 @@ impl ServerManager { } Err(ConnectionError::ServerConnectionFailed { addresses: addresses.clone() }.into()) } + + fn fetch_replicas(&self) -> Result> { + for (_, server_connection) in self.read_server_connections().iter() { + match server_connection.servers_all() { + Ok(replicas) => { + return Ok(Self::translate_replicas(replicas, &self.address_translation)); + } // TODO: Rework connection errors + Err(Error::Connection( + ConnectionError::ServerConnectionFailedStatusError { .. } | ConnectionError::ConnectionFailed, + )) => (), + Err(err) => return Err(err), + } + } + // TODO: This addresses are the initial addresses. They can be changed. What to return here? + Err(ConnectionError::ServerConnectionFailed { addresses: self.initial_addresses.clone() }.into()) + } + + fn translate_replicas( + replicas: Vec, + address_translation: &HashMap, + ) -> Vec { + replicas.into_iter().map(|replica| replica.translated(address_translation)).collect() + } } impl fmt::Debug for ServerManager { diff --git a/rust/src/connection/server/server_replica.rs b/rust/src/connection/server/server_replica.rs index 4208e5e78c..205c98fc63 100644 --- a/rust/src/connection/server/server_replica.rs +++ b/rust/src/connection/server/server_replica.rs @@ -16,25 +16,48 @@ * specific language governing permissions and limitations * under the License. */ +use std::collections::HashMap; use crate::common::address::Address; /// The metadata and state of an individual raft replica of a driver connection. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct ServerReplica { - /// The address this replica is hosted at. - pub(crate) address: Address, - /// Checks whether this is the primary replica of the raft cluster. - pub(crate) replica_type: ReplicaType, - /// The raft protocol ‘term’ of this replica. - pub(crate) term: i64, + private_address: Address, + public_address: Option
, + replica_type: ReplicaType, + term: i64, } impl ServerReplica { + pub(crate) fn from_private(private_address: Address, replica_type: ReplicaType, term: i64) -> Self { + Self { private_address, public_address: None, replica_type, term } + } + + pub(crate) fn translate_address(&mut self, address_translation: &HashMap) -> bool { + if let Some((public, _)) = address_translation.iter().find(|(_, private)| private == &self.address()) { + self.private_address = public.clone(); + true + } else { + false + } + } + + pub(crate) fn translated(mut self, address_translation: &HashMap) -> Self { + self.translate_address(address_translation); + self + } + + pub(crate) fn private_address(&self) -> &Address { + &self.private_address + } + + /// The address this replica is hosted at. pub fn address(&self) -> &Address { - &self.address + self.public_address.as_ref().unwrap_or(&self.private_address) } + /// Whether this is the primary replica of the raft cluster or any of the supporting types. pub fn replica_type(&self) -> ReplicaType { self.replica_type } @@ -43,6 +66,7 @@ impl ServerReplica { matches!(self.replica_type, ReplicaType::Primary) } + /// The raft protocol ‘term’ of this replica. pub fn term(&self) -> i64 { self.term } diff --git a/rust/src/database/database.rs b/rust/src/database/database.rs index 997b9299f3..0762aa83b7 100644 --- a/rust/src/database/database.rs +++ b/rust/src/database/database.rs @@ -36,7 +36,8 @@ use crate::{ resolve, }; -/// A TypeDB database +/// A TypeDB database. +#[derive(Debug, Clone)] pub struct Database { name: String, server_manager: Arc, @@ -67,7 +68,12 @@ impl Database { /// ``` #[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 + self.server_manager + .run_write_operation(|server_connection| { + let name = self.name.clone(); + async move { server_connection.delete_database(name).await } + }) + .await } /// Returns a full schema text as a valid TypeQL define query string. @@ -80,7 +86,12 @@ impl Database { /// ``` #[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 + self.server_manager + .run_write_operation(|server_connection| { + let name = self.name.clone(); + async move { server_connection.database_schema(name).await } + }) + .await } /// Returns the types in the schema as a valid TypeQL define query string. @@ -93,7 +104,12 @@ impl Database { /// ``` #[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 + self.server_manager + .run_write_operation(|server_connection| { + let name = self.name.clone(); + async move { server_connection.database_type_schema(name).await } + }) + .await } /// Export a database into a schema definition and a data files saved to the disk. @@ -124,12 +140,39 @@ impl Database { return Err(err); } + // TODO: What happens if the leader changes in the process?.. 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 + .run_write_operation(|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; @@ -140,77 +183,3 @@ impl Database { result } } - -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() - } -} - -#[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 3ff7edf0af..f67cd15e84 100644 --- a/rust/src/database/database_manager.rs +++ b/rust/src/database/database_manager.rs @@ -24,8 +24,8 @@ use typedb_protocol::migration::Item; use super::Database; use crate::{ - common::{Result}, - connection::server::{server_manager::ServerManager}, + common::Result, + connection::server::server_manager::ServerManager, database::migration::{try_open_import_file, ProtoMessageIterator}, info::DatabaseInfo, resolve, @@ -54,7 +54,7 @@ impl DatabaseManager { #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn all(&self) -> Result>> { self.server_manager - .run_failsafe(move |server_connection| async move { + .run_read_operation(move |server_connection| async move { server_connection .all_databases() .await? @@ -82,7 +82,7 @@ impl DatabaseManager { let name = name.into(); let database_info = self .server_manager - .run_failsafe(move |server_connection| { + .run_read_operation(move |server_connection| { let name = name.clone(); async move { server_connection.get_database(name).await } }) @@ -106,7 +106,7 @@ impl DatabaseManager { pub async fn contains(&self, name: impl Into) -> Result { let name = name.into(); self.server_manager - .run_failsafe(move |server_connection| { + .run_read_operation(move |server_connection| { let name = name.clone(); async move { server_connection.contains_database(name).await } }) @@ -129,7 +129,7 @@ impl DatabaseManager { pub async fn create(&self, name: impl Into) -> Result { let name = name.into(); self.server_manager - .run_failsafe(move |server_connection| { + .run_write_operation(move |server_connection| { let name = name.clone(); async move { server_connection.create_database(name).await } }) @@ -168,7 +168,7 @@ impl DatabaseManager { let data_file_path = data_file_path.as_ref(); self.server_manager - .run_failsafe(move |server_connection| { + .run_write_operation(move |server_connection| { let name = name.clone(); async move { let file = try_open_import_file(data_file_path)?; diff --git a/rust/src/driver.rs b/rust/src/driver.rs index ae85106f31..a181cb4c77 100644 --- a/rust/src/driver.rs +++ b/rust/src/driver.rs @@ -17,13 +17,10 @@ * under the License. */ -use std::{collections::HashMap, fmt, sync::Arc}; - -use itertools::Itertools; +use std::{fmt, sync::Arc}; use crate::{ common::{ - address::Address, error::{ConnectionError, Error}, Result, }, @@ -136,8 +133,9 @@ impl TypeDBDriver { /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn server_version(&self) -> Result { - // TODO: check expect - self.server_connections.iter().next().expect("TODO: Change to failsafe or smth").1.version().await + self.server_manager + .run_write_operation(|server_connection| async move { server_connection.version().await }) + .await } /// Retrieves the server's replicas. @@ -213,6 +211,7 @@ impl TypeDBDriver { ) -> Result { let database_name = database_name.as_ref(); let database = self.database_manager.get(database_name).await?; + // TODO: Will probably need a failsafe operation! let transaction_stream = database .run_failsafe(|database| async move { database.connection().open_transaction(database.name(), transaction_type, options).await @@ -233,14 +232,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) + 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("Connection").field("server_manager", &self.server_manager).finish() } } diff --git a/rust/src/user/user_manager.rs b/rust/src/user/user_manager.rs index 8ce6d78d80..8676b449df 100644 --- a/rust/src/user/user_manager.rs +++ b/rust/src/user/user_manager.rs @@ -16,14 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -use std::{sync::Arc}; +use std::sync::Arc; -use crate::{ - common::{Result}, - connection::{server::server_manager::ServerManager}, - error::ConnectionError, - User, -}; +use crate::{common::Result, connection::server::server_manager::ServerManager, error::ConnectionError, User}; /// Provides access to all user management methods. #[derive(Debug)] @@ -38,11 +33,8 @@ impl UserManager { #[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("Expected a non-empty server connection collection"); + let (_, connection) = + self.server_connections.iter().next().expect("Expected a non-empty server connection collection"); self.get(connection.username()).await } From 42f8217b3670c6c526b291463b1d8d22e35b8712 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Fri, 20 Jun 2025 12:03:06 +0100 Subject: [PATCH 03/35] Finish the first dirty version, it builds --- rust/src/connection/server/addresses.rs | 15 +-- .../connection/server/server_connection.rs | 7 +- rust/src/connection/server/server_manager.rs | 22 +++-- rust/src/driver.rs | 16 ++-- rust/src/lib.rs | 2 +- rust/src/user/user.rs | 59 +++++++----- rust/src/user/user_manager.rs | 95 +++++++++---------- rust/tests/behaviour/steps/lib.rs | 10 +- rust/tests/integration/example.rs | 6 +- 9 files changed, 126 insertions(+), 106 deletions(-) diff --git a/rust/src/connection/server/addresses.rs b/rust/src/connection/server/addresses.rs index 55141ec766..bd2ec0118a 100644 --- a/rust/src/connection/server/addresses.rs +++ b/rust/src/connection/server/addresses.rs @@ -40,8 +40,8 @@ impl Addresses { /// ```rust /// Addresses::try_from_address_str("127.0.0.1:11729") /// ``` - pub fn try_from_address_str(address_str: String) -> crate::Result { - let address = address_str.parse()?; + pub fn try_from_address_str(address_str: impl AsRef) -> crate::Result { + let address = address_str.as_ref().parse()?; Ok(Self::from_address(address)) } @@ -65,8 +65,9 @@ impl Addresses { /// ```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.parse()).try_collect()?; + 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)) } @@ -99,10 +100,10 @@ impl Addresses { /// ].into() /// ) /// ``` - pub fn try_from_translation_str(addresses_str: HashMap) -> crate::Result { + 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.parse()?, address_value.parse()?); + addresses.insert(address_key.as_ref().parse()?, address_value.as_ref().parse()?); } Ok(Self::from_translation(addresses)) } @@ -142,7 +143,7 @@ impl Addresses { } } -enum AddressIter<'a> { +pub(crate) enum AddressIter<'a> { Direct(std::slice::Iter<'a, Address>), Translated(std::collections::hash_map::Keys<'a, Address, Address>), } diff --git a/rust/src/connection/server/server_connection.rs b/rust/src/connection/server/server_connection.rs index 072dd25922..8f44507035 100644 --- a/rust/src/connection/server/server_connection.rs +++ b/rust/src/connection/server/server_connection.rs @@ -118,8 +118,9 @@ impl ServerConnection { self.request_transmitter.force_close() } - pub(crate) fn servers_all(&self) -> Result> { - match self.request_blocking(Request::ServersAll)? { + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + 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()), } @@ -240,7 +241,7 @@ impl ServerConnection { .await? { Response::TransactionStream { - open_request_id: request_id, + open_request_id: _, request_sink, response_source, server_duration_millis, diff --git a/rust/src/connection/server/server_manager.rs b/rust/src/connection/server/server_manager.rs index 2591365f1d..b2f25a4114 100644 --- a/rust/src/connection/server/server_manager.rs +++ b/rust/src/connection/server/server_manager.rs @@ -22,6 +22,7 @@ use std::{ fmt, future::Future, sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}, + thread::sleep, time::Duration, }; @@ -31,7 +32,6 @@ use log::debug; use crate::{ common::address::Address, connection::{ - message::Response, runtime::BackgroundRuntime, server::{server_connection::ServerConnection, server_replica::ServerReplica, Addresses}, }, @@ -78,7 +78,7 @@ impl ServerManager { .await?; let address_translation = addresses.address_translation(); - let mut server_manager = Self { + let server_manager = Self { initial_addresses: addresses, replicas: RwLock::new(replicas), server_connections: RwLock::new(HashMap::new()), @@ -93,6 +93,7 @@ impl ServerManager { Ok(server_manager) } + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] async fn update_server_connections(&self) -> Result { let replicas = self.replicas.read().expect("Expected replicas read access"); let replica_addresses: HashSet
= @@ -143,14 +144,22 @@ impl ServerManager { self.read_server_connections().values().map(ServerConnection::force_close).try_collect().map_err(Into::into) } + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub(crate) async fn servers_all(&self) -> Result> { - self.run_read_operation(|server_connection| async move { server_connection.servers_all() }).await + self.run_read_operation(|server_connection| async move { server_connection.servers_all().await }).await } pub(crate) fn server_count(&self) -> usize { self.read_server_connections().len() } + pub(crate) fn username(&self) -> Result { + match self.read_server_connections().iter().next() { + Some((_, server_connection)) => Ok(server_connection.username().to_string()), + None => Err(ConnectionError::ServerConnectionIsClosed {}.into()), + } + } + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub(crate) async fn run_read_operation(&self, task: F) -> Result where @@ -220,7 +229,7 @@ impl ServerManager { #[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 = self.fetch_replicas()?; + let replicas = self.fetch_replicas().await?; *self.replicas.write().expect("Expected replicas write lock") = replicas; if let Some(replica) = self.primary_replica() { self.update_server_connections().await?; @@ -288,9 +297,10 @@ impl ServerManager { Err(ConnectionError::ServerConnectionFailed { addresses: addresses.clone() }.into()) } - fn fetch_replicas(&self) -> Result> { + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn fetch_replicas(&self) -> Result> { for (_, server_connection) in self.read_server_connections().iter() { - match server_connection.servers_all() { + match server_connection.servers_all().await { Ok(replicas) => { return Ok(Self::translate_replicas(replicas, &self.address_translation)); } // TODO: Rework connection errors diff --git a/rust/src/driver.rs b/rust/src/driver.rs index a181cb4c77..dcc1bd3f79 100644 --- a/rust/src/driver.rs +++ b/rust/src/driver.rs @@ -210,13 +210,15 @@ impl TypeDBDriver { options: TransactionOptions, ) -> Result { let database_name = database_name.as_ref(); - let database = self.database_manager.get(database_name).await?; - // TODO: Will probably need a failsafe operation! - let transaction_stream = database - .run_failsafe(|database| async move { - database.connection().open_transaction(database.name(), transaction_type, options).await - }) - .await?; + let open_fn = |server_connection: ServerConnection| async move { + server_connection.open_transaction(database_name, transaction_type, options).await + }; + let transaction_stream = match transaction_type { + TransactionType::Read => self.server_manager.run_read_operation(open_fn).await?, + TransactionType::Write | TransactionType::Schema => { + self.server_manager.run_write_operation(open_fn).await? + } + }; Ok(Transaction::new(transaction_stream)) } diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 20a3d78016..0c047e954e 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -25,7 +25,7 @@ pub use self::{ box_stream, error, info, BoxPromise, BoxStream, Error, Promise, QueryOptions, Result, TransactionOptions, TransactionType, IID, }, - connection::{Credentials, DriverOptions}, + connection::{server::Addresses, Credentials, DriverOptions}, database::{Database, DatabaseManager}, driver::TypeDBDriver, transaction::Transaction, diff --git a/rust/src/user/user.rs b/rust/src/user/user.rs index 0a6d687c36..c815d90224 100644 --- a/rust/src/user/user.rs +++ b/rust/src/user/user.rs @@ -16,22 +16,36 @@ * 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, -}; +use crate::{common::Result, connection::server::server_manager::ServerManager, info::UserInfo}; #[derive(Clone, Debug)] pub struct User { - pub name: String, + pub name: String, // TODO: Should not be PUB pub password: Option, - pub server_connections: HashMap, + server_manager: Arc, } impl User { + 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()) + } + /// Update the user's password. /// /// # Arguments @@ -49,14 +63,13 @@ impl User { #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn update_password(&self, password: impl Into) -> 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(res), - Err(err) => error_buffer.push(format!("- {}: {}", server_id, err)), - } - } - Err(ConnectionError::ServerConnectionFailedWithError { error: error_buffer.join("\n") })? + self.server_manager + .run_read_operation(|server_connection| { + let name = self.name.clone(); + let password = password.clone(); + async move { server_connection.update_password(name, password).await } + }) + .await } /// Deletes this user @@ -72,13 +85,11 @@ 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.server_manager + .run_read_operation(|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 8676b449df..0edef0adf5 100644 --- a/rust/src/user/user_manager.rs +++ b/rust/src/user/user_manager.rs @@ -31,18 +31,23 @@ impl UserManager { Self { server_manager } } + /// Returns the user of the current connection. + /// + /// # Examples + /// + /// ```rust + /// 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("Expected a non-empty server connection collection"); - self.get(connection.username()).await + self.get(self.server_manager.username()?).await } /// Checks if a user with the given name exists. /// /// # Arguments /// - /// * `username` — The user name to be checked + /// * `username` — The username to be checked /// /// # Examples /// @@ -52,14 +57,12 @@ impl UserManager { #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn contains(&self, username: impl Into) -> 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 + .run_read_operation(move |server_connection| { + let username = username.clone(); + async move { server_connection.contains_user(username).await } + }) + .await } /// Retrieve a user with the given name. @@ -75,21 +78,17 @@ impl UserManager { /// ``` #[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(), - })) + let username = username.into(); + self.server_manager + .run_read_operation(|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. @@ -101,23 +100,18 @@ impl UserManager { /// ``` #[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.server_manager + .run_read_operation(|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. @@ -134,15 +128,14 @@ impl UserManager { /// ``` #[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") })? + let username = username.into(); + let password = password.into(); + self.server_manager + .run_read_operation(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/behaviour/steps/lib.rs b/rust/tests/behaviour/steps/lib.rs index a33b5412f4..bb469c1722 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::{ @@ -441,9 +441,10 @@ impl Context { password: &str, ) -> TypeDBResult { assert!(!self.is_cluster); + 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, conn_settings).await } async fn create_driver_cluster( @@ -455,11 +456,12 @@ impl Context { assert!(self.is_cluster); // TODO: Change when multiple addresses are introduced let address = addresses.iter().next().expect("Expected at least one address"); + let addresses = Addresses::try_from_address_str(address).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 + TypeDBDriver::new(addresses, credentials, conn_settings).await } pub fn set_driver(&mut self, driver: TypeDBDriver) { diff --git a/rust/tests/integration/example.rs b/rust/tests/integration/example.rs index fee42911a5..efa9d77f5e 100644 --- a/rust/tests/integration/example.rs +++ b/rust/tests/integration/example.rs @@ -29,14 +29,14 @@ 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(), ) @@ -57,7 +57,7 @@ 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(), ) From 3a4a4a3f0291b8913d1391c6af17165371834f7d Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Fri, 20 Jun 2025 12:43:05 +0100 Subject: [PATCH 04/35] Enhance connection error --- rust/src/common/error.rs | 8 +--- .../connection/network/transmitter/import.rs | 2 +- rust/src/connection/server/addresses.rs | 33 +++++++++++++- rust/src/connection/server/server_manager.rs | 44 ++++++++++--------- rust/src/database/database.rs | 6 +-- rust/src/driver.rs | 2 +- rust/src/user/user.rs | 4 +- rust/src/user/user_manager.rs | 2 +- 8 files changed, 66 insertions(+), 35 deletions(-) diff --git a/rust/src/common/error.rs b/rust/src/common/error.rs index a8b98fe2bf..1b9cd4197e 100644 --- a/rust/src/common/error.rs +++ b/rust/src/common/error.rs @@ -121,8 +121,8 @@ 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: Addresses } = - 2: "Unable to connect to TypeDB server(s) at: \n{addresses:?}", + ServerConnectionFailed { configured_addresses: Addresses, accessed_addresses: Addresses } = + 2: "Unable to connect to TypeDB server(s).\nInitially configured addresses: {configured_addresses}.\nTried accessing addresses: {accessed_addresses}", ServerConnectionFailedWithError { error: String } = 3: "Unable to connect to TypeDB server(s), received errors: \n{error}", ServerConnectionFailedStatusError { error: String } = @@ -221,10 +221,6 @@ 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}'.", } #[derive(Clone, PartialEq, Eq)] 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/server/addresses.rs b/rust/src/connection/server/addresses.rs index bd2ec0118a..333a216e9b 100644 --- a/rust/src/connection/server/addresses.rs +++ b/rust/src/connection/server/addresses.rs @@ -17,7 +17,11 @@ * under the License. */ -use std::collections::HashMap; +use std::{ + collections::HashMap, + fmt, + fmt::{Formatter, Write}, +}; use itertools::Itertools; @@ -143,6 +147,33 @@ impl Addresses { } } +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('}') + } + } + } +} + pub(crate) enum AddressIter<'a> { Direct(std::slice::Iter<'a, Address>), Translated(std::collections::hash_map::Keys<'a, Address, Address>), diff --git a/rust/src/connection/server/server_manager.rs b/rust/src/connection/server/server_manager.rs index b2f25a4114..c06a058dab 100644 --- a/rust/src/connection/server/server_manager.rs +++ b/rust/src/connection/server/server_manager.rs @@ -41,7 +41,7 @@ use crate::{ pub(crate) struct ServerManager { // TODO: Merge ServerConnection with ServerReplica? Probably should not as they can be different - initial_addresses: Addresses, + configured_addresses: Addresses, replicas: RwLock>, server_connections: RwLock>, address_translation: HashMap, @@ -79,7 +79,7 @@ impl ServerManager { let address_translation = addresses.address_translation(); let server_manager = Self { - initial_addresses: addresses, + configured_addresses: addresses, replicas: RwLock::new(replicas), server_connections: RwLock::new(HashMap::new()), address_translation, @@ -125,8 +125,7 @@ impl ServerManager { } if server_connections.is_empty() { - // TODO: use connection_errors (convert to string or what??) - Err(ConnectionError::ServerConnectionFailed { addresses: self.initial_addresses.clone() }.into()) + Err(self.server_connection_failed_err()) } else { Ok(()) } @@ -166,7 +165,7 @@ impl ServerManager { F: Fn(ServerConnection) -> P, P: Future>, { - // TODO: Add retries? + // TODO: Add retries? YES, if no connections are alive, update connections based on replicas // TODO: Sort randomly? Remember the last working replica to try it first? for (address, server_connection) in self.read_server_connections().iter() { match task(server_connection.clone()).await { @@ -180,10 +179,7 @@ impl ServerManager { res => return res, } } - Err(ConnectionError::ServerConnectionFailed { - addresses: Addresses::from_addresses(self.read_server_connections().keys().map(Address::clone)), - } - .into()) + Err(self.server_connection_failed_err()) } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] @@ -220,10 +216,7 @@ impl ServerManager { primary_replica = self.seek_primary_replica().await?; } } - Err(ConnectionError::ServerConnectionFailed { - addresses: Addresses::from_addresses(self.read_server_connections().keys().map(Address::clone)), - } - .into()) + Err(self.server_connection_failed_err()) } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] @@ -238,10 +231,7 @@ impl ServerManager { Self::wait_for_primary_replica_selection().await; } self.update_server_connections().await?; - Err(ConnectionError::ServerConnectionFailed { - addresses: Addresses::from_addresses(self.read_server_connections().keys().map(Address::clone)), - } - .into()) + Err(self.server_connection_failed_err()) } fn primary_replica(&self) -> Option { @@ -294,7 +284,11 @@ impl ServerManager { Err(err) => return Err(err), } } - Err(ConnectionError::ServerConnectionFailed { addresses: addresses.clone() }.into()) + Err(ConnectionError::ServerConnectionFailed { + configured_addresses: addresses.clone(), + accessed_addresses: addresses.clone(), + } + .into()) } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] @@ -310,8 +304,7 @@ impl ServerManager { Err(err) => return Err(err), } } - // TODO: This addresses are the initial addresses. They can be changed. What to return here? - Err(ConnectionError::ServerConnectionFailed { addresses: self.initial_addresses.clone() }.into()) + Err(self.server_connection_failed_err()) } fn translate_replicas( @@ -320,6 +313,17 @@ impl ServerManager { ) -> Vec { replicas.into_iter().map(|replica| replica.translated(address_translation)).collect() } + + fn server_connection_failed_err(&self) -> Error { + let accessed_addresses = Addresses::from_addresses( + self.replicas.read().expect("Expected a read replica lock").iter().map(|replica| replica.address().clone()), + ); + ConnectionError::ServerConnectionFailed { + configured_addresses: self.configured_addresses.clone(), + accessed_addresses, + } + .into() + } } impl fmt::Debug for ServerManager { diff --git a/rust/src/database/database.rs b/rust/src/database/database.rs index 0762aa83b7..6bcf4da23d 100644 --- a/rust/src/database/database.rs +++ b/rust/src/database/database.rs @@ -87,7 +87,7 @@ impl Database { #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn schema(&self) -> Result { self.server_manager - .run_write_operation(|server_connection| { + .run_read_operation(|server_connection| { let name = self.name.clone(); async move { server_connection.database_schema(name).await } }) @@ -105,7 +105,7 @@ impl Database { #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn type_schema(&self) -> Result { self.server_manager - .run_write_operation(|server_connection| { + .run_read_operation(|server_connection| { let name = self.name.clone(); async move { server_connection.database_type_schema(name).await } }) @@ -143,7 +143,7 @@ impl Database { // TODO: What happens if the leader changes in the process?.. let result = self .server_manager - .run_write_operation(|server_connection| { + .run_read_operation(|server_connection| { let name = self.name.clone(); async move { // File opening should be idempotent for multiple function invocations diff --git a/rust/src/driver.rs b/rust/src/driver.rs index dcc1bd3f79..a429238605 100644 --- a/rust/src/driver.rs +++ b/rust/src/driver.rs @@ -134,7 +134,7 @@ impl TypeDBDriver { #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn server_version(&self) -> Result { self.server_manager - .run_write_operation(|server_connection| async move { server_connection.version().await }) + .run_read_operation(|server_connection| async move { server_connection.version().await }) .await } diff --git a/rust/src/user/user.rs b/rust/src/user/user.rs index c815d90224..6934f8cdab 100644 --- a/rust/src/user/user.rs +++ b/rust/src/user/user.rs @@ -64,7 +64,7 @@ impl User { pub async fn update_password(&self, password: impl Into) -> Result<()> { let password = password.into(); self.server_manager - .run_read_operation(|server_connection| { + .run_write_operation(|server_connection| { let name = self.name.clone(); let password = password.clone(); async move { server_connection.update_password(name, password).await } @@ -86,7 +86,7 @@ impl User { #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn delete(self) -> Result { self.server_manager - .run_read_operation(|server_connection| { + .run_write_operation(|server_connection| { let name = self.name.clone(); async move { server_connection.delete_user(name).await } }) diff --git a/rust/src/user/user_manager.rs b/rust/src/user/user_manager.rs index 0edef0adf5..e0bd19b4d1 100644 --- a/rust/src/user/user_manager.rs +++ b/rust/src/user/user_manager.rs @@ -131,7 +131,7 @@ impl UserManager { let username = username.into(); let password = password.into(); self.server_manager - .run_read_operation(move |server_connection| { + .run_write_operation(move |server_connection| { let username = username.clone(); let password = password.clone(); async move { server_connection.create_user(username, password).await } From a4cf6a56eba12713af26c15912984ae7fe2a4797 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Fri, 20 Jun 2025 18:02:14 +0100 Subject: [PATCH 05/35] Improve error messages, refactor driver options, add an option to connect only to a single target replica --- c/src/connection.rs | 14 +-- c/src/database.rs | 7 +- c/src/error.rs | 2 +- c/src/user.rs | 6 +- rust/example.rs | 2 +- rust/src/common/error.rs | 89 +++++++++++++------ rust/src/connection/driver_options.rs | 63 ++++++++----- rust/src/connection/network/channel.rs | 11 ++- rust/src/connection/server/addresses.rs | 20 +++++ rust/src/connection/server/server_manager.rs | 40 ++++++--- rust/src/database/database.rs | 2 +- rust/src/database/database_manager.rs | 6 +- rust/src/database/migration.rs | 10 +-- rust/src/user/user.rs | 4 +- rust/src/user/user_manager.rs | 6 +- .../behaviour/steps/connection/database.rs | 6 +- rust/tests/behaviour/steps/connection/user.rs | 16 +++- rust/tests/behaviour/steps/lib.rs | 6 +- rust/tests/integration/example.rs | 4 +- 19 files changed, 207 insertions(+), 107 deletions(-) diff --git a/c/src/connection.rs b/c/src/connection.rs index cdc7325e42..3e4f3107ac 100644 --- a/c/src/connection.rs +++ b/c/src/connection.rs @@ -19,14 +19,16 @@ use std::{ffi::c_char, path::Path}; -use chrono::DateTime; -use typedb_driver::{concept::value::TimeZone, Credentials, DriverOptions, TypeDBDriver}; +use typedb_driver::{Addresses, Credentials, DriverOptions, TypeDBDriver}; use super::{ error::{try_release, unwrap_void}, memory::{borrow, free, string_view}, }; -use crate::memory::{release, release_string, string_free}; +use crate::{ + error::unwrap_or_default, + memory::{release, release_string, string_free}, +}; const DRIVER_LANG: &'static str = "c"; @@ -42,7 +44,7 @@ pub extern "C" fn driver_open( driver_options: *const DriverOptions, ) -> *mut TypeDBDriver { try_release(TypeDBDriver::new_with_description( - string_view(address), + unwrap_or_default(Addresses::try_from_address_str(string_view(address))), borrow(credentials).clone(), borrow(driver_options).clone(), DRIVER_LANG, @@ -64,7 +66,7 @@ pub extern "C" fn driver_open_with_description( driver_lang: *const c_char, ) -> *mut TypeDBDriver { try_release(TypeDBDriver::new_with_description( - string_view(address), + unwrap_or_default(Addresses::try_from_address_str(string_view(address))), borrow(credentials).clone(), borrow(driver_options).clone(), string_view(driver_lang), @@ -112,7 +114,7 @@ pub extern "C" fn credentials_drop(credentials: *mut Credentials) { #[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)) + try_release(DriverOptions::new().is_tls_enabled(is_tls_enabled).tls_root_ca(tls_root_ca_path)) } /// Frees the native rust DriverOptions object diff --git a/c/src/database.rs b/c/src/database.rs index 768a92693b..8e669e778f 100644 --- a/c/src/database.rs +++ b/c/src/database.rs @@ -17,14 +17,13 @@ * under the License. */ -use std::{ffi::c_char, path::Path, ptr::addr_of_mut, sync::Arc}; +use std::{ffi::c_char, path::Path}; -use typedb_driver::{box_stream, info::ServerInfo, Database}; +use typedb_driver::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}, + memory::{borrow, release_string}, }; use crate::memory::{decrement_arc, string_view, take_arc}; diff --git a/c/src/error.rs b/c/src/error.rs index 57ed509aa4..3920409029 100644 --- a/c/src/error.rs +++ b/c/src/error.rs @@ -81,7 +81,7 @@ pub(super) 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(super) fn unwrap_or_default(result: Result) -> T { ok_record(result).unwrap_or_default() } diff --git a/c/src/user.rs b/c/src/user.rs index 0615fc4464..1fbba1d7b5 100644 --- a/c/src/user.rs +++ b/c/src/user.rs @@ -19,13 +19,13 @@ use std::ffi::c_char; -use typedb_driver::{Database, TypeDBDriver, User, UserManager}; +use typedb_driver::User; use super::{ error::unwrap_void, memory::{borrow, free, release_string, string_view}, }; -use crate::memory::{take_arc, take_ownership}; +use crate::memory::take_ownership; /// Frees the native rust User object. #[no_mangle] @@ -36,7 +36,7 @@ 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. diff --git a/rust/example.rs b/rust/example.rs index 58de9a7f80..45313ced6c 100644 --- a/rust/example.rs +++ b/rust/example.rs @@ -18,7 +18,7 @@ fn typedb_example() { let driver = TypeDBDriver::new( TypeDBDriver::DEFAULT_ADDRESS, Credentials::new("admin", "password"), - DriverOptions::new(false, None).unwrap(), + DriverOptions::new(), ) .await .unwrap(); diff --git a/rust/src/common/error.rs b/rust/src/common/error.rs index 1b9cd4197e..96ff2e2ffa 100644 --- a/rust/src/common/error.rs +++ b/rust/src/common/error.rs @@ -117,6 +117,15 @@ macro_rules! error_messages { }; } +macro_rules! __retryable_helper { + (retryable) => { + true + }; + () => { + false + }; +} + error_messages! { ConnectionError code: "CXN", type: "Connection Error", RPCMethodUnavailable { message: String } = @@ -125,7 +134,7 @@ error_messages! { ConnectionError 2: "Unable to connect to TypeDB server(s).\nInitially configured addresses: {configured_addresses}.\nTried accessing addresses: {accessed_addresses}", 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.", @@ -159,8 +168,8 @@ error_messages! { ConnectionError 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.", + ConnectionRefused = + 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.", UnexpectedReplicaType { replica_type: i32 } = @@ -185,6 +194,22 @@ 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.", +} + +impl ConnectionError { + pub fn retryable(&self) -> bool { + match self { + ConnectionError::ServerConnectionFailedNetworking { .. } | ConnectionError::ConnectionRefused => true, + // | ConnectionError::DatabaseNotFound {} // TODO??? + // | ConnectionError::ClusterReplicaNotPrimary // TODO??? + // | ConnectionError::TokenCredentialInvalid // TODO??? + _ => false, + } + } } error_messages! { ConceptError @@ -310,8 +335,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 { @@ -322,9 +350,9 @@ impl Error { } else if status_message.contains("UnknownIssuer") { Error::Connection(ConnectionError::SSLCertificateNotValidated) } else if status_message.contains("Connection refused") { - Error::Connection(ConnectionError::ConnectionFailed) + Error::Connection(ConnectionError::ConnectionRefused) } else { - Error::Connection(ConnectionError::ServerConnectionFailedStatusError { error: status_message.to_owned() }) + Error::Connection(ConnectionError::ServerConnectionFailedNetworking { error: status_message.to_owned() }) } } } @@ -367,6 +395,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) @@ -389,39 +423,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/connection/driver_options.rs b/rust/src/connection/driver_options.rs index b16ddc5ece..1f73e7dc40 100644 --- a/rust/src/connection/driver_options.rs +++ b/rust/src/connection/driver_options.rs @@ -21,42 +21,59 @@ use std::{fs, path::Path}; use tonic::transport::{Certificate, ClientTlsConfig}; -/// User connection settings for connecting to TypeDB. +/// 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, - tls_config: Option, + /// Specifies whether the connection to TypeDB must be done over TLS. + /// Defaults to false. + pub is_tls_enabled: bool, + /// If set, specifies the TLS config to use for server certificates authentication. + /// Defaults to None. + pub tls_config: Option, + /// Specifies whether the connection to TypeDB can use cluster replicas provided by the server + /// or it should be limited to the provided address. + /// Defaults to true. If set to false, restricts the driver to only a single address. + pub use_replication: bool, } 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 { + 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 } + } + + /// If set, specifies the path to the CA certificate to use for server certificates authentication. + pub fn tls_root_ca(self, tls_root_ca: Option<&Path>) -> crate::Result { let tls_config = Some(if let Some(tls_root_ca) = tls_root_ca { ClientTlsConfig::new().ca_certificate(Certificate::from_pem(fs::read_to_string(tls_root_ca)?)) } else { ClientTlsConfig::new().with_native_roots() }); - - Ok(Self { is_tls_enabled, tls_config }) + Ok(Self { tls_config, ..self }) } - /// Retrieves whether TLS is enabled for the connection. - pub fn is_tls_enabled(&self) -> bool { - self.is_tls_enabled + /// 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 } } +} - pub fn tls_config(&self) -> &Option { - &self.tls_config +impl Default for DriverOptions { + fn default() -> Self { + Self { is_tls_enabled: false, tls_config: None, use_replication: true } } } diff --git a/rust/src/connection/network/channel.rs b/rust/src/connection/network/channel.rs index b7c47c092e..0c53b5c772 100644 --- a/rust/src/connection/network/channel.rs +++ b/rust/src/connection/network/channel.rs @@ -32,7 +32,8 @@ use tonic::{ use crate::{ common::{address::Address, Result, StdResult}, - Credentials, DriverOptions, + error::ConnectionError, + Credentials, DriverOptions, Error, }; type ResponseFuture = InterceptorResponseFuture; @@ -52,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 + .tls_config + .clone() + .ok_or_else(|| Error::Connection(ConnectionError::MissingTlsConfigForTls))?; builder = builder.tls_config(tls_config)?; } let channel = builder.connect_lazy(); diff --git a/rust/src/connection/server/addresses.rs b/rust/src/connection/server/addresses.rs index 333a216e9b..58fadec4d4 100644 --- a/rust/src/connection/server/addresses.rs +++ b/rust/src/connection/server/addresses.rs @@ -130,6 +130,20 @@ impl Addresses { 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(), + } + } + pub(crate) fn addresses(&self) -> AddressIter<'_> { match self { Addresses::Direct(vec) => AddressIter::Direct(vec.iter()), @@ -174,6 +188,12 @@ impl fmt::Display for Addresses { } } +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>), diff --git a/rust/src/connection/server/server_manager.rs b/rust/src/connection/server/server_manager.rs index c06a058dab..3e95324cc3 100644 --- a/rust/src/connection/server/server_manager.rs +++ b/rust/src/connection/server/server_manager.rs @@ -67,13 +67,14 @@ impl ServerManager { driver_lang: impl AsRef, driver_version: impl AsRef, ) -> Result { - let replicas = Self::fetch_replicas_from_addresses( + let (source_connections, replicas) = Self::fetch_replicas_from_addresses( background_runtime.clone(), &addresses, credentials.clone(), driver_options.clone(), driver_lang.as_ref(), driver_version.as_ref(), + driver_options.use_replication, ) .await?; let address_translation = addresses.address_translation(); @@ -81,7 +82,7 @@ impl ServerManager { let server_manager = Self { configured_addresses: addresses, replicas: RwLock::new(replicas), - server_connections: RwLock::new(HashMap::new()), + server_connections: RwLock::new(source_connections), address_translation, background_runtime, credentials, @@ -171,7 +172,7 @@ impl ServerManager { match task(server_connection.clone()).await { // TODO: refactor errors Err(Error::Connection( - ConnectionError::ServerConnectionFailedStatusError { .. } | ConnectionError::ConnectionFailed, + ConnectionError::ServerConnectionFailedNetworking { .. } | ConnectionError::ConnectionRefused, )) => { // TODO: Expose public instead debug!("Unable to connect to {} (private). Attempting next server.", address); @@ -200,8 +201,8 @@ impl ServerManager { // TODO: Refactor errors Err(Error::Connection( ConnectionError::ClusterReplicaNotPrimary - | ConnectionError::ServerConnectionFailedStatusError { .. } - | ConnectionError::ConnectionFailed, + | ConnectionError::ServerConnectionFailedNetworking { .. } + | ConnectionError::ConnectionRefused, )) => { debug!("Primary replica error, waiting..."); Self::wait_for_primary_replica_selection().await; @@ -262,9 +263,15 @@ impl ServerManager { driver_options: DriverOptions, driver_lang: impl AsRef, driver_version: impl AsRef, - ) -> Result> { + use_replication: bool, + ) -> Result<(HashMap, Vec)> { + if !use_replication && addresses.len() > 1 { + return Err(ConnectionError::MultipleAddressesForNoReplicationMode { addresses: addresses.clone() }.into()); + } + let address_translation = addresses.address_translation(); for address in addresses.addresses() { + // TODO: Insert into server connections right away to connect only once? let server_connection = ServerConnection::new( background_runtime.clone(), address.clone(), @@ -276,10 +283,23 @@ impl ServerManager { .await; match server_connection { // TODO: Don't we need to close the connection? - Ok((_, replicas)) => return Ok(Self::translate_replicas(replicas, &address_translation)), + Ok((server_connection, replicas)) => { + let translated_replicas = Self::translate_replicas(replicas, &address_translation); + if use_replication { + let source_connections = HashMap::from([(address.clone(), server_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(), server_connection)]); + return Ok((source_connections, vec![target_replica])); + } + } + } // TODO: Rework connection errors Err(Error::Connection( - ConnectionError::ServerConnectionFailedStatusError { .. } | ConnectionError::ConnectionFailed, + ConnectionError::ServerConnectionFailedNetworking { .. } | ConnectionError::ConnectionRefused, )) => (), Err(err) => return Err(err), } @@ -299,7 +319,7 @@ impl ServerManager { return Ok(Self::translate_replicas(replicas, &self.address_translation)); } // TODO: Rework connection errors Err(Error::Connection( - ConnectionError::ServerConnectionFailedStatusError { .. } | ConnectionError::ConnectionFailed, + ConnectionError::ServerConnectionFailedNetworking { .. } | ConnectionError::ConnectionRefused, )) => (), Err(err) => return Err(err), } @@ -308,7 +328,7 @@ impl ServerManager { } fn translate_replicas( - replicas: Vec, + replicas: impl IntoIterator, address_translation: &HashMap, ) -> Vec { replicas.into_iter().map(|replica| replica.translated(address_translation)).collect() diff --git a/rust/src/database/database.rs b/rust/src/database/database.rs index 6bcf4da23d..3a8e483efa 100644 --- a/rust/src/database/database.rs +++ b/rust/src/database/database.rs @@ -143,7 +143,7 @@ impl Database { // TODO: What happens if the leader changes in the process?.. let result = self .server_manager - .run_read_operation(|server_connection| { + .run_write_operation(|server_connection| { let name = self.name.clone(); async move { // File opening should be idempotent for multiple function invocations diff --git a/rust/src/database/database_manager.rs b/rust/src/database/database_manager.rs index f67cd15e84..1090cedf2b 100644 --- a/rust/src/database/database_manager.rs +++ b/rust/src/database/database_manager.rs @@ -54,7 +54,7 @@ impl DatabaseManager { #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn all(&self) -> Result>> { self.server_manager - .run_read_operation(move |server_connection| async move { + .run_write_operation(move |server_connection| async move { server_connection .all_databases() .await? @@ -82,7 +82,7 @@ impl DatabaseManager { let name = name.into(); let database_info = self .server_manager - .run_read_operation(move |server_connection| { + .run_write_operation(move |server_connection| { let name = name.clone(); async move { server_connection.get_database(name).await } }) @@ -106,7 +106,7 @@ impl DatabaseManager { pub async fn contains(&self, name: impl Into) -> Result { let name = name.into(); self.server_manager - .run_read_operation(move |server_connection| { + .run_write_operation(move |server_connection| { let name = name.clone(); async move { server_connection.contains_database(name).await } }) diff --git a/rust/src/database/migration.rs b/rust/src/database/migration.rs index 6abb108b26..5c3330e3bf 100644 --- a/rust/src/database/migration.rs +++ b/rust/src/database/migration.rs @@ -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/user/user.rs b/rust/src/user/user.rs index 6934f8cdab..05f27af1d7 100644 --- a/rust/src/user/user.rs +++ b/rust/src/user/user.rs @@ -22,8 +22,8 @@ use crate::{common::Result, connection::server::server_manager::ServerManager, i #[derive(Clone, Debug)] pub struct User { - pub name: String, // TODO: Should not be PUB - pub password: Option, + name: String, + password: Option, server_manager: Arc, } diff --git a/rust/src/user/user_manager.rs b/rust/src/user/user_manager.rs index e0bd19b4d1..6dba7b49ba 100644 --- a/rust/src/user/user_manager.rs +++ b/rust/src/user/user_manager.rs @@ -58,7 +58,7 @@ impl UserManager { pub async fn contains(&self, username: impl Into) -> Result { let username = username.into(); self.server_manager - .run_read_operation(move |server_connection| { + .run_write_operation(move |server_connection| { let username = username.clone(); async move { server_connection.contains_user(username).await } }) @@ -80,7 +80,7 @@ impl UserManager { pub async fn get(&self, username: impl Into) -> Result> { let username = username.into(); self.server_manager - .run_read_operation(|server_connection| { + .run_write_operation(|server_connection| { let username = username.clone(); let server_manager = self.server_manager.clone(); async move { @@ -101,7 +101,7 @@ impl UserManager { #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn all(&self) -> Result> { self.server_manager - .run_read_operation(|server_connection| { + .run_write_operation(|server_connection| { let server_manager = self.server_manager.clone(); async move { let user_infos = server_connection.all_users().await?; 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/user.rs b/rust/tests/behaviour/steps/connection/user.rs index 139c7ae48e..fdc30ea38e 100644 --- a/rust/tests/behaviour/steps/connection/user.rs +++ b/rust/tests/behaviour/steps/connection/user.rs @@ -28,7 +28,17 @@ use typedb_driver::{Database, Result as TypeDBResult, TypeDBDriver, User}; use crate::{assert_err, 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 +71,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 +110,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 bb469c1722..7f613acd8b 100644 --- a/rust/tests/behaviour/steps/lib.rs +++ b/rust/tests/behaviour/steps/lib.rs @@ -252,7 +252,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 @@ -443,7 +443,7 @@ impl Context { assert!(!self.is_cluster); let addresses = Addresses::try_from_address_str(address).expect("Expected addresses"); let credentials = Credentials::new(username, password); - let conn_settings = DriverOptions::new(false, None)?; + let conn_settings = DriverOptions::new(); TypeDBDriver::new(addresses, credentials, conn_settings).await } @@ -460,7 +460,7 @@ impl Context { // TODO: We probably want to add encryption to cluster tests let credentials = Credentials::new(username, password); - let conn_settings = DriverOptions::new(false, None)?; + let conn_settings = DriverOptions::new(); TypeDBDriver::new(addresses, credentials, conn_settings).await } diff --git a/rust/tests/integration/example.rs b/rust/tests/integration/example.rs index efa9d77f5e..bddf700f89 100644 --- a/rust/tests/integration/example.rs +++ b/rust/tests/integration/example.rs @@ -38,7 +38,7 @@ async fn cleanup() { let driver = TypeDBDriver::new( Addresses::try_from_address_str(TypeDBDriver::DEFAULT_ADDRESS).unwrap(), Credentials::new("admin", "password"), - DriverOptions::new(false, None).unwrap(), + DriverOptions::new(), ) .await .unwrap(); @@ -59,7 +59,7 @@ fn example() { let driver = TypeDBDriver::new( Addresses::try_from_address_str(TypeDBDriver::DEFAULT_ADDRESS).unwrap(), Credentials::new("admin", "password"), - DriverOptions::new(false, None).unwrap(), + DriverOptions::new(), ) .await .unwrap(); From 9b3df4c18d899dbb46f3685f9a3338d509eaffab Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Tue, 24 Jun 2025 12:39:48 +0100 Subject: [PATCH 06/35] Introduce consistency levels. Refactor server manager execution. Upgrade not primary replica errors --- Cargo.lock | 49 ++++---- c/src/answer.rs | 4 +- c/src/database_manager.rs | 6 +- dependencies/typedb/repositories.bzl | 5 +- java/TypeDBExample.java | 6 +- java/api/database/DatabaseManager.java | 6 +- python/example.py | 6 +- python/typedb/api/connection/database.py | 6 +- python/typedb/api/user/user.py | 6 +- python/typedb/common/datetime.py | 32 ++--- rust/BUILD | 1 + rust/Cargo.toml | 9 +- rust/example.rs | 6 +- rust/src/answer/concept_document.rs | 2 +- rust/src/answer/concept_row.rs | 4 +- rust/src/answer/mod.rs | 8 +- rust/src/common/consistency_level.rs | 46 +++++++ rust/src/common/error.rs | 61 ++++++--- rust/src/common/mod.rs | 2 + rust/src/common/proto.rs | 52 ++++++++ rust/src/common/transaction_options.rs | 15 ++- rust/src/concept/mod.rs | 40 +++--- .../connection/server/server_connection.rs | 2 +- rust/src/connection/server/server_manager.rs | 60 +++++---- rust/src/connection/transaction_stream.rs | 4 +- rust/src/database/database.rs | 97 ++++++++++++--- rust/src/database/database_manager.rs | 117 +++++++++++++++--- rust/src/driver.rs | 68 +++++++--- rust/src/transaction.rs | 6 +- rust/src/user/user.rs | 36 ++++-- rust/src/user/user_manager.rs | 115 ++++++++++++++--- rust/tests/behaviour/steps/Cargo.toml | 6 +- .../behaviour/steps/connection/transaction.rs | 12 +- rust/tests/behaviour/steps/lib.rs | 4 + 34 files changed, 668 insertions(+), 231 deletions(-) create mode 100644 rust/src/common/consistency_level.rs create mode 100644 rust/src/common/proto.rs diff --git a/Cargo.lock b/Cargo.lock index 68efe4c664..d3dd311b40 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", ] @@ -2737,6 +2738,7 @@ dependencies = [ "log", "maybe-async", "prost", + "prost-types", "regex", "serde_json", "serial_test", @@ -2818,13 +2820,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 +2885,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 +2923,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 +2933,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 +2946,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/src/answer.rs b/c/src/answer.rs index d8d32c2fb9..78adf3fa88 100644 --- a/c/src/answer.rs +++ b/c/src/answer.rs @@ -62,7 +62,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 +120,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/database_manager.rs b/c/src/database_manager.rs index 3345bb5971..ef8dbe39e2 100644 --- a/c/src/database_manager.rs +++ b/c/src/database_manager.rs @@ -52,13 +52,13 @@ pub extern "C" fn databases_all(driver: *mut TypeDBDriver) -> *mut DatabaseItera ) } -/// Create a database with the given name. +/// Creates a database with the given name. #[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. @@ -84,7 +84,7 @@ pub extern "C" fn databases_contains(driver: *mut TypeDBDriver, name: *const c_c unwrap_or_default(borrow_mut(driver).databases().contains(string_view(name))) } -/// Retrieve the database with the given name. +/// Retrieves 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/dependencies/typedb/repositories.bzl b/dependencies/typedb/repositories.bzl index b9086f4bb5..32dd76c32a 100644 --- a/dependencies/typedb/repositories.bzl +++ b/dependencies/typedb/repositories.bzl @@ -18,10 +18,11 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") def typedb_dependencies(): + # TODO: Return typedb git_repository( name = "typedb_dependencies", - remote = "https://github.com/typedb/typedb-dependencies", - commit = "fac1121c903b0c9e5924d391a883e4a0749a82a2", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_dependencies + remote = "https://github.com/farost/typedb-dependencies", + commit = "566ccff865210e54eaca7d924996824638f31597", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_dependencies ) def typedb_protocol(): diff --git a/java/TypeDBExample.java b/java/TypeDBExample.java index e127e429c7..a63390f7c7 100644 --- a/java/TypeDBExample.java +++ b/java/TypeDBExample.java @@ -32,7 +32,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))) { - // Create a database + // Creates a database driver.databases().create("typedb"); Database database = driver.databases().get("typedb"); @@ -93,7 +93,7 @@ public void example() { conceptByName.getLabel(), conceptByIndex.getLabel()); - // Check if it's an entity type before the conversion + // Checks if it's an entity type before the conversion if (conceptByName.isEntityType()) { System.out.printf("Both represent the defined entity type: '%s' (in case of a doubt: '%s')%n", conceptByName.asEntityType().getLabel(), @@ -115,7 +115,7 @@ public void example() { conceptByName = attributeRow.get(columnName).get(); - // Check if it's an attribute type before the conversion + // Checks if it's an attribute type before the conversion if (conceptByName.isAttributeType()) { AttributeType attributeType = conceptByName.asAttributeType(); System.out.printf("Defined attribute type's label: '%s', value type: '%s'%n", attributeType.getLabel(), attributeType.tryGetValueType().get()); diff --git a/java/api/database/DatabaseManager.java b/java/api/database/DatabaseManager.java index 7fdb30e5da..bfc01a965e 100644 --- a/java/api/database/DatabaseManager.java +++ b/java/api/database/DatabaseManager.java @@ -30,7 +30,7 @@ public interface DatabaseManager { /** - * Retrieve the database with the given name. + * Retrieves the database with the given name. * *

Examples

*
@@ -56,7 +56,7 @@ public interface DatabaseManager {
     boolean contains(String name) throws TypeDBDriverException;
 
     /**
-     * Create a database with the given name.
+     * Creates a database with the given name.
      *
      * 

Examples

*
@@ -68,7 +68,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

diff --git a/python/example.py b/python/example.py index b11cb462b7..bfde89f3cb 100644 --- a/python/example.py +++ b/python/example.py @@ -9,7 +9,7 @@ def typedb_example(self): # Open a driver connection. Specify your parameters if needed # The connection will be automatically closed on the "with" block exit with TypeDB.driver(TypeDB.DEFAULT_ADDRESS, Credentials("admin", "password"), DriverOptions()) as driver: - # Create a database + # Creates a database driver.databases.create("typedb") database = driver.databases.get("typedb") @@ -71,7 +71,7 @@ def typedb_example(self): print(f"Getting concepts by variable names ({concept_by_name.get_label()}) and " f"indexes ({concept_by_index.get_label()}) is equally correct. ") - # Check if it's an entity type before the conversion + # Checks if it's an entity type before the conversion if concept_by_name.is_entity_type(): print(f"Both represent the defined entity type: '{concept_by_name.as_entity_type().get_label()}' " f"(in case of a doubt: '{concept_by_index.as_entity_type().get_label()}')") @@ -89,7 +89,7 @@ def typedb_example(self): concept_by_name = row.get(column_name) - # Check if it's an attribute type before the conversion + # Checks if it's an attribute type before the conversion if concept_by_name.is_attribute_type(): attribute_type = concept_by_name.as_attribute_type() print(f"Defined attribute type's label: '{attribute_type.get_label()}', " diff --git a/python/typedb/api/connection/database.py b/python/typedb/api/connection/database.py index a04d907e5f..202ded398d 100644 --- a/python/typedb/api/connection/database.py +++ b/python/typedb/api/connection/database.py @@ -205,7 +205,7 @@ class DatabaseManager(ABC): @abstractmethod def get(self, name: str) -> Database: """ - Retrieve the database with the given name. + Retrieves the database with the given name. :param name: The name of the database to retrieve :return: @@ -237,7 +237,7 @@ def contains(self, name: str) -> bool: @abstractmethod def create(self, name: str) -> None: """ - Create a database with the given name. + Creates a database with the given name. :param name: The name of the database to be created :return: @@ -253,7 +253,7 @@ def create(self, name: str) -> None: @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. + 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 diff --git a/python/typedb/api/user/user.py b/python/typedb/api/user/user.py index d46e808b13..a62f360b1f 100644 --- a/python/typedb/api/user/user.py +++ b/python/typedb/api/user/user.py @@ -94,7 +94,7 @@ def contains(self, username: str) -> bool: @abstractmethod def create(self, username: str, password: str) -> None: """ - Create a user with the given name and password. + 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 @@ -111,7 +111,7 @@ def create(self, username: str, password: str) -> None: @abstractmethod def get(self, username: str) -> Optional[User]: """ - Retrieve a user with the given name. + Retrieves a user with the given name. :param username: The name of the user to retrieve :return: @@ -127,7 +127,7 @@ def get(self, username: str) -> Optional[User]: @abstractmethod def get_current_user(self) -> Optional[User]: """ - Retrieve the name of the user who opened the current connection. + Retrieves the name of the user who opened the current connection. :return: 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/rust/BUILD b/rust/BUILD index 4a393d7516..0911051c67 100644 --- a/rust/BUILD +++ b/rust/BUILD @@ -37,6 +37,7 @@ typedb_driver_deps = [ "@crates//:itertools", "@crates//:log", "@crates//:prost", + "@crates//:prost-types", "@crates//:tokio", "@crates//:tokio-stream", "@crates//:tonic", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 72b3ec44e7..abbd82c6b1 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -55,7 +55,7 @@ [dependencies.tokio] features = ["bytes", "default", "fs", "full", "io-std", "io-util", "libc", "macros", "mio", "net", "parking_lot", "process", "rt", "rt-multi-thread", "signal", "signal-hook-registry", "socket2", "sync", "time", "tokio-macros"] - version = "1.45.0" + version = "1.45.1" default-features = false [dependencies.typedb-protocol] @@ -86,7 +86,7 @@ [dependencies.uuid] features = ["default", "fast-rng", "rng", "serde", "std", "v4"] - version = "1.16.0" + version = "1.17.0" default-features = false [dependencies.prost] @@ -104,6 +104,11 @@ version = "0.12.3" default-features = false + [dependencies.prost-types] + features = ["default", "std"] + version = "0.13.5" + default-features = false + [dependencies.http] features = ["default", "std"] version = "1.3.1" diff --git a/rust/example.rs b/rust/example.rs index 45313ced6c..4158dfc83a 100644 --- a/rust/example.rs +++ b/rust/example.rs @@ -78,7 +78,7 @@ fn typedb_example() { let rows: Vec = answer.into_rows().try_collect().await.unwrap(); let row = rows.get(0).unwrap(); - // Retrieve column names to get concepts by index if the variable names are lost + // Retrieves column names to get concepts by index if the variable names are lost let column_names = row.get_column_names(); let column_name = column_names.get(0).unwrap(); @@ -89,7 +89,7 @@ fn typedb_example() { // Get concept by the header's index let concept_by_index = row.get_index(0).unwrap().unwrap(); - // Check if it's an entity type + // Checks if it's an entity type if concept_by_name.is_entity_type() { print!("Getting concepts by variable names and indexes is equally correct. "); println!( @@ -110,7 +110,7 @@ fn typedb_example() { let concept_by_name = row.get(column_name).unwrap().unwrap(); - // Check if it's an attribute type to safely retrieve its value type + // Checks if it's an attribute type to safely retrieve its value type if concept_by_name.is_attribute_type() { let label = concept_by_name.get_label(); let value_type = concept_by_name.try_get_value_type().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/consistency_level.rs b/rust/src/common/consistency_level.rs new file mode 100644 index 0000000000..3eaeca278e --- /dev/null +++ b/rust/src/common/consistency_level.rs @@ -0,0 +1,46 @@ +/* + * 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; + +#[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 96ff2e2ffa..ef429d9e65 100644 --- a/rust/src/common/error.rs +++ b/rust/src/common/error.rs @@ -17,14 +17,16 @@ * under the License. */ -use std::{collections::HashSet, error::Error as StdError, fmt}; +use std::{error::Error as StdError, fmt, str::FromStr}; -use itertools::Itertools; use tonic::{Code, Status}; -use tonic_types::{ErrorDetails, ErrorInfo, StatusExt}; +use tonic_types::StatusExt; -use super::{address::Address, RequestID}; -use crate::connection::server::Addresses; +use super::RequestID; +use crate::{ + common::{address::Address, proto::decode_address}, + connection::server::Addresses, +}; macro_rules! error_messages { { @@ -142,29 +144,25 @@ 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}.", + ClusterReplicaNotPrimaryHinted { primary_hint: Address } = + 16: "The replica is not the primary replica. Use {primary_hint} instead.", 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.", @@ -203,10 +201,10 @@ error_messages! { ConnectionError impl ConnectionError { pub fn retryable(&self) -> bool { match self { - ConnectionError::ServerConnectionFailedNetworking { .. } | ConnectionError::ConnectionRefused => true, - // | ConnectionError::DatabaseNotFound {} // TODO??? - // | ConnectionError::ClusterReplicaNotPrimary // TODO??? - // | ConnectionError::TokenCredentialInvalid // TODO??? + ConnectionError::ServerConnectionFailedNetworking { .. } + | ConnectionError::ConnectionRefused + | ConnectionError::ClusterReplicaNotPrimary + | ConnectionError::ClusterReplicaNotPrimaryHinted { .. } => true, _ => false, } } @@ -324,16 +322,39 @@ impl Error { } } + pub fn retryable(&self) -> bool { + match self { + Error::Connection(error) => error.retryable(), + Error::Concept(_) => false, + Error::Migration(_) => false, + Error::Internal(_) => false, + Error::Server(_) => false, + Error::Other(_) => false, + } + } + 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. match code { "AUT2" | "AUT3" => Some(ConnectionError::TokenCredentialInvalid {}), + "TEST1" => match Self::decode_address(status) { + Ok(Some(primary_hint)) => Some(ConnectionError::ClusterReplicaNotPrimaryHinted { primary_hint }), + Ok(None) => Some(ConnectionError::ClusterReplicaNotPrimary), + Err(err) => { + debug_assert!( + false, + "Unexpected error while decoding primary replica address: {err:?}, for {status:?}" + ); + Some(ConnectionError::ClusterReplicaNotPrimary) + } + }, _ => None, } } + fn decode_address(status: &Status) -> crate::Result> { + decode_address(status).map(|address| Address::from_str(&address.address)).transpose() + } + fn from_message(message: &str) -> Self { if is_rst_stream(message) { Self::Connection(ConnectionError::ServerConnectionFailedNetworking { error: message.to_owned() }) @@ -348,7 +369,7 @@ 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::ConnectionRefused) } else { diff --git a/rust/src/common/mod.rs b/rust/src/common/mod.rs index d7ae057e05..a4346de171 100644 --- a/rust/src/common/mod.rs +++ b/rust/src/common/mod.rs @@ -26,12 +26,14 @@ pub use self::{ }; pub(crate) mod address; +pub mod consistency_level; pub mod error; mod id; pub mod info; #[cfg_attr(not(feature = "sync"), path = "promise_async.rs")] #[cfg_attr(feature = "sync", path = "promise_sync.rs")] mod promise; +mod proto; mod query_options; #[cfg_attr(not(feature = "sync"), path = "stream_async.rs")] #[cfg_attr(feature = "sync", path = "stream_sync.rs")] diff --git a/rust/src/common/proto.rs b/rust/src/common/proto.rs new file mode 100644 index 0000000000..86397a36f7 --- /dev/null +++ b/rust/src/common/proto.rs @@ -0,0 +1,52 @@ +/* + * 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 tonic::Status; +use typedb_protocol::server::Address; + +macro_rules! decode_from_status { + ($status:expr, $target_ty:ty) => {{ + decode_from_status!($status, $target_ty, stringify!($target_ty)) + }}; + + ($status:expr, $target_ty:ty, $target_suffix:expr) => {{ + use std::io::Cursor; + + use prost::{bytes::Buf, Message}; + use prost_types::Any; + + let mut buf = Cursor::new($status.details()); + while buf.has_remaining() { + if let Ok(detail) = Any::decode_length_delimited(&mut buf) { + if detail.type_url.ends_with($target_suffix) { + let mut value_buf = Cursor::new(&detail.value); + if let Ok(decoded) = <$target_ty>::decode(&mut value_buf) { + return Some(decoded); + } + } + } else { + break; + } + } + None + }}; +} + +pub(crate) fn decode_address(status: &Status) -> Option
{ + decode_from_status!(status, Address) +} 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/connection/server/server_connection.rs b/rust/src/connection/server/server_connection.rs index 8f44507035..ea21504c8a 100644 --- a/rust/src/connection/server/server_connection.rs +++ b/rust/src/connection/server/server_connection.rs @@ -235,7 +235,7 @@ impl ServerConnection { .request(Request::Transaction(TransactionRequest::Open { database: database_name.to_owned(), transaction_type, - options, + options: options.clone(), network_latency, })) .await? diff --git a/rust/src/connection/server/server_manager.rs b/rust/src/connection/server/server_manager.rs index 3e95324cc3..fe14967b07 100644 --- a/rust/src/connection/server/server_manager.rs +++ b/rust/src/connection/server/server_manager.rs @@ -30,7 +30,7 @@ use itertools::Itertools; use log::debug; use crate::{ - common::address::Address, + common::{address::Address, consistency_level::ConsistencyLevel}, connection::{ runtime::BackgroundRuntime, server::{server_connection::ServerConnection, server_replica::ServerReplica, Addresses}, @@ -39,6 +39,12 @@ use crate::{ Credentials, DriverOptions, Error, Result, }; +macro_rules! primary_replica_hinted_error { + ($primary_hint:ident) => { + Err(Error::Connection(ConnectionError::ClusterReplicaNotPrimaryHinted { primary_hint: $primary_hint })) + }; +} + pub(crate) struct ServerManager { // TODO: Merge ServerConnection with ServerReplica? Probably should not as they can be different configured_addresses: Addresses, @@ -146,7 +152,9 @@ impl ServerManager { #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub(crate) async fn servers_all(&self) -> Result> { - self.run_read_operation(|server_connection| async move { server_connection.servers_all().await }).await + // TODO: May need to expose multiple consistency levels. Check the driver's "replicas" impl! + self.execute(ConsistencyLevel::Strong, |server_connection| async move { server_connection.servers_all().await }) + .await } pub(crate) fn server_count(&self) -> usize { @@ -161,7 +169,7 @@ impl ServerManager { } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn run_read_operation(&self, task: F) -> Result + pub(crate) async fn execute_read(&self, task: F) -> Result where F: Fn(ServerConnection) -> P, P: Future>, @@ -170,10 +178,11 @@ impl ServerManager { // TODO: Sort randomly? Remember the last working replica to try it first? for (address, server_connection) in self.read_server_connections().iter() { match task(server_connection.clone()).await { - // TODO: refactor errors - Err(Error::Connection( - ConnectionError::ServerConnectionFailedNetworking { .. } | ConnectionError::ConnectionRefused, - )) => { + primary_replica_hinted_error!(primary) => { + // TODO: Should probably return the whole replica information... Otherwise, it's unsynced + todo!("Use primary_hint") // TODO + } + Err(err) if err.retryable() => { // TODO: Expose public instead debug!("Unable to connect to {} (private). Attempting next server.", address); } @@ -184,7 +193,7 @@ impl ServerManager { } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn run_write_operation(&self, task: F) -> Result + pub(crate) async fn execute(&self, consistency_level: ConsistencyLevel, task: F) -> Result where F: Fn(ServerConnection) -> P, P: Future>, @@ -198,24 +207,16 @@ impl ServerManager { for _ in 0..Self::PRIMARY_REPLICA_TASK_MAX_RETRIES { if let Some(server_connection) = server_connections.get(primary_replica.private_address()) { match task(server_connection.clone()).await { - // TODO: Refactor errors - Err(Error::Connection( - ConnectionError::ClusterReplicaNotPrimary - | ConnectionError::ServerConnectionFailedNetworking { .. } - | ConnectionError::ConnectionRefused, - )) => { - debug!("Primary replica error, waiting..."); - Self::wait_for_primary_replica_selection().await; - primary_replica = self.seek_primary_replica().await?; + primary_replica_hinted_error!(primary) => { + todo!("Use primary_hint") // TODO } + Err(err) if err.retryable() => (), res => return res, } - } else { - // TODO: Refactor - debug!("Could not connect to the primary replica, waiting..."); - Self::wait_for_primary_replica_selection().await; - primary_replica = self.seek_primary_replica().await?; } + debug!("Could not connect to the primary replica, waiting..."); + Self::wait_for_primary_replica_selection().await; + primary_replica = self.seek_primary_replica().await?; } Err(self.server_connection_failed_err()) } @@ -271,7 +272,6 @@ impl ServerManager { let address_translation = addresses.address_translation(); for address in addresses.addresses() { - // TODO: Insert into server connections right away to connect only once? let server_connection = ServerConnection::new( background_runtime.clone(), address.clone(), @@ -286,7 +286,8 @@ impl ServerManager { Ok((server_connection, replicas)) => { let translated_replicas = Self::translate_replicas(replicas, &address_translation); if use_replication { - let source_connections = HashMap::from([(address.clone(), server_connection)]); + let mut source_connections = HashMap::with_capacity(translated_replicas.len()); + source_connections.insert(address.clone(), server_connection); return Ok((source_connections, translated_replicas)); } else { if let Some(target_replica) = @@ -297,10 +298,7 @@ impl ServerManager { } } } - // TODO: Rework connection errors - Err(Error::Connection( - ConnectionError::ServerConnectionFailedNetworking { .. } | ConnectionError::ConnectionRefused, - )) => (), + Err(err) if err.retryable() => (), Err(err) => return Err(err), } } @@ -317,10 +315,8 @@ impl ServerManager { match server_connection.servers_all().await { Ok(replicas) => { return Ok(Self::translate_replicas(replicas, &self.address_translation)); - } // TODO: Rework connection errors - Err(Error::Connection( - ConnectionError::ServerConnectionFailedNetworking { .. } | ConnectionError::ConnectionRefused, - )) => (), + } + Err(err) if err.retryable() => (), Err(err) => return Err(err), } } 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 3a8e483efa..ab6c97d567 100644 --- a/rust/src/database/database.rs +++ b/rust/src/database/database.rs @@ -18,18 +18,15 @@ */ use std::{ - fmt, - fs::File, io::{BufWriter, Write}, path::Path, sync::Arc, - time::Duration, }; use prost::Message; use crate::{ - common::{info::DatabaseInfo, Error, Result}, + 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}, error::MigrationError, @@ -58,7 +55,7 @@ impl Database { self.name.as_str() } - /// Deletes this database. + /// Deletes this database. Always uses strong consistency. /// /// # Examples /// @@ -68,15 +65,22 @@ impl Database { /// ``` #[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 - .run_write_operation(|server_connection| { + .execute(consistency_level, |server_connection| { let name = self.name.clone(); async move { server_connection.delete_database(name).await } }) .await } - /// Returns a full schema text as a valid TypeQL define query string. + /// 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 /// @@ -86,15 +90,34 @@ impl Database { /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn schema(&self) -> Result { + self.schema_with_consistency(ConsistencyLevel::Strong).await + } + + /// 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 + #[cfg_attr(feature = "sync", doc = "database.schema_with_consistency(ConsistencyLevel::Strong);")] + #[cfg_attr(not(feature = "sync"), doc = "database.schema_with_consistency(ConsistencyLevel::Strong).await;")] + /// ``` + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub async fn schema_with_consistency(&self, consistency_level: ConsistencyLevel) -> Result { self.server_manager - .run_read_operation(|server_connection| { + .execute(consistency_level, |server_connection| { let name = self.name.clone(); async move { server_connection.database_schema(name).await } }) .await } - /// Returns the types in the schema as a valid TypeQL define query string. + /// 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 /// @@ -104,14 +127,47 @@ impl Database { /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn type_schema(&self) -> Result { + self.type_schema_with_consistency(ConsistencyLevel::Strong).await + } + + /// 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.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 type_schema_with_consistency(&self, consistency_level: ConsistencyLevel) -> Result { self.server_manager - .run_read_operation(|server_connection| { + .execute(consistency_level, |server_connection| { let name = self.name.clone(); async move { server_connection.database_type_schema(name).await } }) .await } + /// 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.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 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. /// This is a blocking operation and may take a significant amount of time depending on the database size. /// @@ -119,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 { @@ -140,10 +208,9 @@ impl Database { return Err(err); } - // TODO: What happens if the leader changes in the process?.. let result = self .server_manager - .run_write_operation(|server_connection| { + .execute(consistency_level, |server_connection| { let name = self.name.clone(); async move { // File opening should be idempotent for multiple function invocations diff --git a/rust/src/database/database_manager.rs b/rust/src/database/database_manager.rs index 1090cedf2b..5f95383e16 100644 --- a/rust/src/database/database_manager.rs +++ b/rust/src/database/database_manager.rs @@ -24,7 +24,7 @@ use typedb_protocol::migration::Item; use super::Database; use crate::{ - common::Result, + common::{consistency_level::ConsistencyLevel, Result}, connection::server::server_manager::ServerManager, database::migration::{try_open_import_file, ProtoMessageIterator}, info::DatabaseInfo, @@ -43,7 +43,9 @@ impl DatabaseManager { 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 /// @@ -53,8 +55,25 @@ impl DatabaseManager { /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn all(&self) -> Result>> { + self.all_with_consistency(ConsistencyLevel::Strong).await + } + + /// Retrieves all databases present on the TypeDB server. + /// + /// # Arguments + /// + /// * `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 - .run_write_operation(move |server_connection| async move { + .execute(consistency_level, move |server_connection| async move { server_connection .all_databases() .await? @@ -65,11 +84,9 @@ impl DatabaseManager { .await } - /// Retrieve the database with the given name. - /// - /// # Arguments + /// Retrieves the database with the given name, using default strong consistency. /// - /// * `name` — The name of the database to retrieve + /// See [`Self::get_with_consistency`] for more details and options. /// /// # Examples /// @@ -79,10 +96,35 @@ impl DatabaseManager { /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn get(&self, name: impl Into) -> Result> { + self.get_with_consistency(name, ConsistencyLevel::Strong).await + } + + /// Retrieves the database with the given name. + /// + /// # 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().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 - .run_write_operation(move |server_connection| { + .execute(consistency_level, move |server_connection| { let name = name.clone(); async move { server_connection.get_database(name).await } }) @@ -90,11 +132,9 @@ impl DatabaseManager { self.try_build_database(database_info) } - /// Checks if a database with the given name exists - /// - /// # Arguments + /// Checks if a database with the given name exists, using default strong consistency. /// - /// * `name` — The database name to be checked + /// See [`Self::contains_with_consistency`] for more details and options. /// /// # Examples /// @@ -104,16 +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.server_manager - .run_write_operation(move |server_connection| { + .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 /// @@ -127,17 +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(); self.server_manager - .run_write_operation(move |server_connection| { + .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. /// @@ -159,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; @@ -168,7 +249,7 @@ impl DatabaseManager { let data_file_path = data_file_path.as_ref(); self.server_manager - .run_write_operation(move |server_connection| { + .execute(consistency_level, move |server_connection| { let name = name.clone(); async move { let file = try_open_import_file(data_file_path)?; diff --git a/rust/src/driver.rs b/rust/src/driver.rs index a429238605..110be300a4 100644 --- a/rust/src/driver.rs +++ b/rust/src/driver.rs @@ -21,6 +21,7 @@ use std::{fmt, sync::Arc}; use crate::{ common::{ + consistency_level::ConsistencyLevel, error::{ConnectionError, Error}, Result, }, @@ -64,10 +65,13 @@ impl TypeDBDriver { /// # Examples /// /// ```rust - #[cfg_attr(feature = "sync", doc = "TypeDBDriver::new(Address::try_from_address_str(\"127.0.0.1:1729\").unwrap())")] + #[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(Address::try_from_address_str(\"127.0.0.1:1729\").unwrap()).await" + doc = "TypeDBDriver::new(Addresses::try_from_address_str(\"127.0.0.1:1729\").unwrap()).await" )] /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] @@ -91,11 +95,11 @@ impl TypeDBDriver { /// ```rust #[cfg_attr( feature = "sync", - doc = "TypeDBDriver::new_with_description(Address::try_from_address_str(\"127.0.0.1:1729\").unwrap(), \"rust\")" + 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(Address::try_from_address_str(\"127.0.0.1:1729\").unwrap(), \"rust\").await" + 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)] @@ -123,7 +127,9 @@ impl TypeDBDriver { Ok(Self { server_manager, database_manager, user_manager, background_runtime }) } - /// Retrieves the server's version. + /// Retrieves the server's version, using default strong consistency. + /// + /// See [`Self::server_version_with_consistency`] for more details and options. /// /// # Examples /// @@ -133,8 +139,25 @@ impl TypeDBDriver { /// ``` #[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 - .run_read_operation(|server_connection| async move { server_connection.version().await }) + .execute(consistency_level, |server_connection| async move { server_connection.version().await }) .await } @@ -143,8 +166,8 @@ impl TypeDBDriver { /// # Examples /// /// ```rust - #[cfg_attr(feature = "sync", doc = "driver.server_version()")] - #[cfg_attr(not(feature = "sync"), doc = "driver.server_version().await")] + #[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> { @@ -172,7 +195,15 @@ impl TypeDBDriver { } /// 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, @@ -182,7 +213,10 @@ impl TypeDBDriver { self.transaction_with_options(database_name, transaction_type, TransactionOptions::new()).await } - /// Opens a new 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 /// @@ -210,13 +244,19 @@ impl TypeDBDriver { options: TransactionOptions, ) -> Result { let database_name = database_name.as_ref(); - let open_fn = |server_connection: ServerConnection| async move { - server_connection.open_transaction(database_name, transaction_type, options).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.run_read_operation(open_fn).await?, + TransactionType::Read => { + self.server_manager + .execute(consistency_level.unwrap_or_else(|| ConsistencyLevel::Strong), open_fn) + .await? + } TransactionType::Write | TransactionType::Schema => { - self.server_manager.run_write_operation(open_fn).await? + self.server_manager.execute(ConsistencyLevel::Strong, open_fn).await? } }; Ok(Transaction::new(transaction_stream)) diff --git a/rust/src/transaction.rs b/rust/src/transaction.rs index 93c9daccb5..9f2a3a6bdd 100644 --- a/rust/src/transaction.rs +++ b/rust/src/transaction.rs @@ -38,7 +38,11 @@ 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. diff --git a/rust/src/user/user.rs b/rust/src/user/user.rs index 05f27af1d7..ccfae7c3a6 100644 --- a/rust/src/user/user.rs +++ b/rust/src/user/user.rs @@ -18,7 +18,11 @@ */ use std::sync::Arc; -use crate::{common::Result, connection::server::server_manager::ServerManager, info::UserInfo}; +use crate::{ + common::{consistency_level::ConsistencyLevel, Result}, + connection::server::server_manager::ServerManager, + info::UserInfo, +}; #[derive(Clone, Debug)] pub struct User { @@ -46,25 +50,32 @@ impl User { self.password.as_ref().map(|value| value.as_str()) } - /// Update the user's password. + /// 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(); self.server_manager - .run_write_operation(|server_connection| { + .execute(consistency_level, |server_connection| { let name = self.name.clone(); let password = password.clone(); async move { server_connection.update_password(name, password).await } @@ -72,9 +83,7 @@ impl User { .await } - /// Deletes this user - /// - /// * `username` — The name of the user to be deleted + /// Deletes this user. Always uses strong consistency. /// /// # Examples /// @@ -85,8 +94,13 @@ impl User { /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn delete(self) -> Result { + 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 - .run_write_operation(|server_connection| { + .execute(consistency_level, |server_connection| { let name = self.name.clone(); async move { server_connection.delete_user(name).await } }) diff --git a/rust/src/user/user_manager.rs b/rust/src/user/user_manager.rs index 6dba7b49ba..0613fa1fab 100644 --- a/rust/src/user/user_manager.rs +++ b/rust/src/user/user_manager.rs @@ -18,7 +18,12 @@ */ use std::sync::Arc; -use crate::{common::Result, connection::server::server_manager::ServerManager, error::ConnectionError, User}; +use crate::{ + common::{consistency_level::ConsistencyLevel, Result}, + connection::server::server_manager::ServerManager, + error::ConnectionError, + User, +}; /// Provides access to all user management methods. #[derive(Debug)] @@ -36,51 +41,100 @@ impl UserManager { /// # Examples /// /// ```rust - /// driver.users.get_current_user().await; + /// driver.users().get_current_user().await; /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn get_current_user(&self) -> Result> { self.get(self.server_manager.username()?).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 - /// driver.users.contains(username).await; + #[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(&self, username: impl Into) -> Result { + pub async fn contains_with_consistency( + &self, + username: impl Into, + consistency_level: ConsistencyLevel, + ) -> Result { let username = username.into(); self.server_manager - .run_write_operation(move |server_connection| { + .execute(consistency_level, move |server_connection| { let username = username.clone(); async move { server_connection.contains_user(username).await } }) .await } - /// Retrieve a user with the given name. + /// 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 + } + + /// 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> { + pub async fn get_with_consistency( + &self, + username: impl Into, + consistency_level: ConsistencyLevel, + ) -> Result> { let username = username.into(); self.server_manager - .run_write_operation(|server_connection| { + .execute(consistency_level, |server_connection| { let username = username.clone(); let server_manager = self.server_manager.clone(); async move { @@ -91,17 +145,37 @@ impl UserManager { .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> { + 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 - .run_write_operation(|server_connection| { + .execute(consistency_level, |server_connection| { let server_manager = self.server_manager.clone(); async move { let user_infos = server_connection.all_users().await?; @@ -114,7 +188,7 @@ impl UserManager { .await } - /// Create a user with the given name & password. + /// Creates a user with the given name & password. Always uses strong consistency. /// /// # Arguments /// @@ -124,14 +198,25 @@ 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 { + 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 - .run_write_operation(move |server_connection| { + .execute(consistency_level, move |server_connection| { let username = username.clone(); let password = password.clone(); async move { server_connection.create_user(username, password).await } diff --git a/rust/tests/behaviour/steps/Cargo.toml b/rust/tests/behaviour/steps/Cargo.toml index f1655d76b1..c543e03765 100644 --- a/rust/tests/behaviour/steps/Cargo.toml +++ b/rust/tests/behaviour/steps/Cargo.toml @@ -16,7 +16,7 @@ features = {} [dependencies.tokio] features = ["bytes", "default", "fs", "full", "io-std", "io-util", "libc", "macros", "mio", "net", "parking_lot", "process", "rt", "rt-multi-thread", "signal", "signal-hook-registry", "socket2", "sync", "time", "tokio-macros"] - version = "1.45.0" + version = "1.45.1" default-features = false [dependencies.smol] @@ -41,7 +41,7 @@ features = {} [dependencies.macro_rules_attribute] features = ["default"] - version = "0.2.0" + version = "0.2.2" default-features = false [dependencies.typedb-driver] @@ -61,7 +61,7 @@ features = {} [dependencies.uuid] features = ["default", "fast-rng", "rng", "serde", "std", "v4"] - version = "1.16.0" + version = "1.17.0" default-features = false [dependencies.itertools] diff --git a/rust/tests/behaviour/steps/connection/transaction.rs b/rust/tests/behaviour/steps/connection/transaction.rs index ede25f09c4..e0187dade0 100644 --- a/rust/tests/behaviour/steps/connection/transaction.rs +++ b/rust/tests/behaviour/steps/connection/transaction.rs @@ -55,7 +55,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 +73,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 +90,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 +115,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 +169,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().as_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().as_mut().unwrap().schema_lock_acquire_timeout = Some(Duration::from_millis(value)); } diff --git a/rust/tests/behaviour/steps/lib.rs b/rust/tests/behaviour/steps/lib.rs index 7f613acd8b..6c69652d1a 100644 --- a/rust/tests/behaviour/steps/lib.rs +++ b/rust/tests/behaviour/steps/lib.rs @@ -281,6 +281,10 @@ impl Context { self.concurrent_rows_streams = None; } + pub fn transaction_options(&self) -> Option { + self.transaction_options.clone() + } + pub fn transaction_opt(&self) -> Option<&Transaction> { self.transactions.get(0) } From ee03d533513b08162330b4e4d9a15fcec4ce010f Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Wed, 25 Jun 2025 12:39:19 +0100 Subject: [PATCH 07/35] Remove excessive deps. Progress on server manager cleanup and redesign --- Cargo.lock | 3 +- dependencies/typedb/repositories.bzl | 7 +- rust/BUILD | 1 - rust/Cargo.toml | 11 +- rust/src/common/error.rs | 75 +++--- rust/src/common/mod.rs | 1 - rust/src/common/proto.rs | 52 ----- rust/src/connection/network/proto/server.rs | 5 +- .../network/transmitter/response_sink.rs | 3 +- rust/src/connection/server/addresses.rs | 14 ++ rust/src/connection/server/server_manager.rs | 213 ++++++++++++------ rust/src/user/user_manager.rs | 1 - rust/tests/behaviour/steps/Cargo.toml | 6 +- .../behaviour/steps/connection/transaction.rs | 6 +- rust/tests/behaviour/steps/connection/user.rs | 3 +- rust/tests/behaviour/steps/params.rs | 2 +- rust/tests/behaviour/steps/query.rs | 7 +- rust/tests/behaviour/steps/util.rs | 13 +- rust/tests/integration/example.rs | 2 +- 19 files changed, 227 insertions(+), 198 deletions(-) delete mode 100644 rust/src/common/proto.rs diff --git a/Cargo.lock b/Cargo.lock index d3dd311b40..f799e1102e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2738,7 +2738,6 @@ dependencies = [ "log", "maybe-async", "prost", - "prost-types", "regex", "serde_json", "serial_test", @@ -2755,7 +2754,7 @@ dependencies = [ [[package]] name = "typedb-protocol" version = "0.0.0" -source = "git+https://github.com/typedb/typedb-protocol?rev=36c7175097f02b3e2d6f0956652d90564e5c89f8#36c7175097f02b3e2d6f0956652d90564e5c89f8" +source = "git+https://github.com/typedb/typedb-protocol?rev=ab1e84f24e861437d00bbb6082ae282e50860763#ab1e84f24e861437d00bbb6082ae282e50860763" dependencies = [ "prost", "tonic", diff --git a/dependencies/typedb/repositories.bzl b/dependencies/typedb/repositories.bzl index 32dd76c32a..57155678fe 100644 --- a/dependencies/typedb/repositories.bzl +++ b/dependencies/typedb/repositories.bzl @@ -18,11 +18,10 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") def typedb_dependencies(): - # TODO: Return typedb git_repository( name = "typedb_dependencies", - remote = "https://github.com/farost/typedb-dependencies", - commit = "566ccff865210e54eaca7d924996824638f31597", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_dependencies + remote = "https://github.com/typedb/typedb-dependencies", + commit = "fac1121c903b0c9e5924d391a883e4a0749a82a2", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_dependencies ) def typedb_protocol(): @@ -30,7 +29,7 @@ def typedb_protocol(): git_repository( name = "typedb_protocol", remote = "https://github.com/farost/typedb-protocol", - commit = "36c7175097f02b3e2d6f0956652d90564e5c89f8", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_protocol + commit = "ab1e84f24e861437d00bbb6082ae282e50860763", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_protocol ) def typedb_behaviour(): diff --git a/rust/BUILD b/rust/BUILD index 0911051c67..4a393d7516 100644 --- a/rust/BUILD +++ b/rust/BUILD @@ -37,7 +37,6 @@ typedb_driver_deps = [ "@crates//:itertools", "@crates//:log", "@crates//:prost", - "@crates//:prost-types", "@crates//:tokio", "@crates//:tokio-stream", "@crates//:tonic", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index abbd82c6b1..b8f2bfef2b 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -55,12 +55,12 @@ [dependencies.tokio] features = ["bytes", "default", "fs", "full", "io-std", "io-util", "libc", "macros", "mio", "net", "parking_lot", "process", "rt", "rt-multi-thread", "signal", "signal-hook-registry", "socket2", "sync", "time", "tokio-macros"] - version = "1.45.1" + version = "1.45.0" default-features = false [dependencies.typedb-protocol] features = [] - rev = "36c7175097f02b3e2d6f0956652d90564e5c89f8" + rev = "ab1e84f24e861437d00bbb6082ae282e50860763" git = "https://github.com/typedb/typedb-protocol" default-features = false @@ -86,7 +86,7 @@ [dependencies.uuid] features = ["default", "fast-rng", "rng", "serde", "std", "v4"] - version = "1.17.0" + version = "1.16.0" default-features = false [dependencies.prost] @@ -104,11 +104,6 @@ version = "0.12.3" default-features = false - [dependencies.prost-types] - features = ["default", "std"] - version = "0.13.5" - default-features = false - [dependencies.http] features = ["default", "std"] version = "1.3.1" diff --git a/rust/src/common/error.rs b/rust/src/common/error.rs index ef429d9e65..af82f12fb6 100644 --- a/rust/src/common/error.rs +++ b/rust/src/common/error.rs @@ -17,16 +17,13 @@ * under the License. */ -use std::{error::Error as StdError, fmt, str::FromStr}; +use std::{error::Error as StdError, fmt}; use tonic::{Code, Status}; use tonic_types::StatusExt; use super::RequestID; -use crate::{ - common::{address::Address, proto::decode_address}, - connection::server::Addresses, -}; +use crate::{common::address::Address, connection::server::Addresses}; macro_rules! error_messages { { @@ -156,8 +153,8 @@ error_messages! { ConnectionError 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.", - ClusterReplicaNotPrimaryHinted { primary_hint: Address } = - 16: "The replica is not the primary replica. Use {primary_hint} instead.", + 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 = @@ -166,7 +163,7 @@ error_messages! { ConnectionError 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.", - ConnectionRefused = + 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.", @@ -202,9 +199,24 @@ impl ConnectionError { pub fn retryable(&self) -> bool { match self { ConnectionError::ServerConnectionFailedNetworking { .. } - | ConnectionError::ConnectionRefused - | ConnectionError::ClusterReplicaNotPrimary - | ConnectionError::ClusterReplicaNotPrimaryHinted { .. } => true, + | ConnectionError::ConnectionRefusedNetworking + | ConnectionError::ClusterReplicaNotPrimary => true, + _ => false, + } + } + + pub fn not_primary(&self) -> bool { + match self { + ConnectionError::ClusterReplicaNotPrimary => true, + _ => false, + } + } + + pub fn networking(&self) -> bool { + match self { + ConnectionError::ServerConnectionFailedNetworking { .. } | ConnectionError::ConnectionRefusedNetworking => { + true + } _ => false, } } @@ -333,28 +345,35 @@ impl Error { } } - fn try_extracting_connection_error(status: &Status, code: &str) -> Option { + pub fn not_primary(&self) -> bool { + match self { + Error::Connection(error) => error.not_primary(), + Error::Concept(_) => false, + Error::Migration(_) => false, + Error::Internal(_) => false, + Error::Server(_) => false, + Error::Other(_) => false, + } + } + + pub fn networking(&self) -> bool { + match self { + Error::Connection(error) => error.networking(), + Error::Concept(_) => false, + Error::Migration(_) => false, + Error::Internal(_) => false, + Error::Server(_) => false, + Error::Other(_) => false, + } + } + + fn try_extracting_connection_error(_status: &Status, code: &str) -> Option { match code { "AUT2" | "AUT3" => Some(ConnectionError::TokenCredentialInvalid {}), - "TEST1" => match Self::decode_address(status) { - Ok(Some(primary_hint)) => Some(ConnectionError::ClusterReplicaNotPrimaryHinted { primary_hint }), - Ok(None) => Some(ConnectionError::ClusterReplicaNotPrimary), - Err(err) => { - debug_assert!( - false, - "Unexpected error while decoding primary replica address: {err:?}, for {status:?}" - ); - Some(ConnectionError::ClusterReplicaNotPrimary) - } - }, _ => None, } } - fn decode_address(status: &Status) -> crate::Result> { - decode_address(status).map(|address| Address::from_str(&address.address)).transpose() - } - fn from_message(message: &str) -> Self { if is_rst_stream(message) { Self::Connection(ConnectionError::ServerConnectionFailedNetworking { error: message.to_owned() }) @@ -371,7 +390,7 @@ impl Error { } else if status_message.contains("UnknownIssuer") { Error::Connection(ConnectionError::SslCertificateNotValidated) } else if status_message.contains("Connection refused") { - Error::Connection(ConnectionError::ConnectionRefused) + Error::Connection(ConnectionError::ConnectionRefusedNetworking) } else { Error::Connection(ConnectionError::ServerConnectionFailedNetworking { error: status_message.to_owned() }) } diff --git a/rust/src/common/mod.rs b/rust/src/common/mod.rs index a4346de171..1e14d062e4 100644 --- a/rust/src/common/mod.rs +++ b/rust/src/common/mod.rs @@ -33,7 +33,6 @@ pub mod info; #[cfg_attr(not(feature = "sync"), path = "promise_async.rs")] #[cfg_attr(feature = "sync", path = "promise_sync.rs")] mod promise; -mod proto; mod query_options; #[cfg_attr(not(feature = "sync"), path = "stream_async.rs")] #[cfg_attr(feature = "sync", path = "stream_sync.rs")] diff --git a/rust/src/common/proto.rs b/rust/src/common/proto.rs deleted file mode 100644 index 86397a36f7..0000000000 --- a/rust/src/common/proto.rs +++ /dev/null @@ -1,52 +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 tonic::Status; -use typedb_protocol::server::Address; - -macro_rules! decode_from_status { - ($status:expr, $target_ty:ty) => {{ - decode_from_status!($status, $target_ty, stringify!($target_ty)) - }}; - - ($status:expr, $target_ty:ty, $target_suffix:expr) => {{ - use std::io::Cursor; - - use prost::{bytes::Buf, Message}; - use prost_types::Any; - - let mut buf = Cursor::new($status.details()); - while buf.has_remaining() { - if let Ok(detail) = Any::decode_length_delimited(&mut buf) { - if detail.type_url.ends_with($target_suffix) { - let mut value_buf = Cursor::new(&detail.value); - if let Ok(decoded) = <$target_ty>::decode(&mut value_buf) { - return Some(decoded); - } - } - } else { - break; - } - } - None - }}; -} - -pub(crate) fn decode_address(status: &Status) -> Option
{ - decode_from_status!(status, Address) -} diff --git a/rust/src/connection/network/proto/server.rs b/rust/src/connection/network/proto/server.rs index a4c579b6e5..235f8f8669 100644 --- a/rust/src/connection/network/proto/server.rs +++ b/rust/src/connection/network/proto/server.rs @@ -34,10 +34,7 @@ use crate::{ impl TryFromProto for ServerReplica { fn try_from_proto(proto: ServerProto) -> Result { - let address = match proto.address { - Some(address) => address.address.parse()?, - None => return Err(ConnectionError::MissingResponseField { field: "address" }.into()), - }; + let address = proto.address.parse()?; let replication_status = match proto.replication_status { Some(replication_status) => ReplicationStatus::try_from_proto(replication_status)?, None => ReplicationStatus::default(), 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/server/addresses.rs b/rust/src/connection/server/addresses.rs index 58fadec4d4..51765a9509 100644 --- a/rust/src/connection/server/addresses.rs +++ b/rust/src/connection/server/addresses.rs @@ -144,6 +144,20 @@ impl Addresses { } } + /// 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()), diff --git a/rust/src/connection/server/server_manager.rs b/rust/src/connection/server/server_manager.rs index fe14967b07..b907b28fb2 100644 --- a/rust/src/connection/server/server_manager.rs +++ b/rust/src/connection/server/server_manager.rs @@ -21,6 +21,7 @@ use std::{ collections::{HashMap, HashSet}, fmt, future::Future, + iter, sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}, thread::sleep, time::Duration, @@ -35,21 +36,17 @@ use crate::{ runtime::BackgroundRuntime, server::{server_connection::ServerConnection, server_replica::ServerReplica, Addresses}, }, - error::{ConnectionError, InternalError}, + error::ConnectionError, Credentials, DriverOptions, Error, Result, }; -macro_rules! primary_replica_hinted_error { - ($primary_hint:ident) => { - Err(Error::Connection(ConnectionError::ClusterReplicaNotPrimaryHinted { primary_hint: $primary_hint })) - }; -} - pub(crate) struct ServerManager { // TODO: Merge ServerConnection with ServerReplica? Probably should not as they can be different configured_addresses: Addresses, replicas: RwLock>, server_connections: RwLock>, + + // public - private address_translation: HashMap, background_runtime: Arc, @@ -60,8 +57,7 @@ pub(crate) struct ServerManager { } impl ServerManager { - const PRIMARY_REPLICA_TASK_MAX_RETRIES: usize = 10; - const FETCH_REPLICAS_MAX_RETRIES: usize = 10; + const PRIMARY_REPLICA_TASK_MAX_RETRIES: usize = 1; const WAIT_FOR_PRIMARY_REPLICA_SELECTION: Duration = Duration::from_secs(2); #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] @@ -102,7 +98,7 @@ impl ServerManager { #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] async fn update_server_connections(&self) -> Result { - let replicas = self.replicas.read().expect("Expected replicas read access"); + let replicas = self.read_replicas(); let replica_addresses: HashSet
= replicas.iter().map(|replica| replica.private_address().clone()).collect(); let mut connection_errors = Vec::with_capacity(replicas.len()); @@ -111,17 +107,8 @@ impl ServerManager { for replica in replicas.iter() { let private_address = replica.private_address().clone(); if !server_connections.contains_key(&private_address) { - match ServerConnection::new( - self.background_runtime.clone(), - replica.address().clone(), - self.credentials.clone(), - self.driver_options.clone(), - self.driver_lang.as_ref(), - self.driver_version.as_ref(), - ) - .await - { - Ok((server_connection, _)) => { + match self.new_server_connection(replica.address().clone()).await { + Ok(server_connection) => { server_connections.insert(private_address, server_connection); } Err(err) => { @@ -138,6 +125,32 @@ impl ServerManager { } } + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn new_server_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(|(server_connection, _)| server_connection) + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn record_new_server_connection( + &self, + public_address: Address, + private_address: Address, + ) -> Result { + let server_connection = self.new_server_connection(public_address).await?; + let mut server_connections = self.write_server_connections(); + server_connections.insert(private_address, server_connection.clone()); + Ok(server_connection) + } + fn read_server_connections(&self) -> RwLockReadGuard<'_, HashMap> { self.server_connections.read().expect("Expected server connections read access") } @@ -146,6 +159,10 @@ impl ServerManager { self.server_connections.write().expect("Expected server connections write access") } + fn read_replicas(&self) -> RwLockReadGuard<'_, Vec> { + self.replicas.read().expect("Expected a read replica lock") + } + pub(crate) fn force_close(&self) -> Result { self.read_server_connections().values().map(ServerConnection::force_close).try_collect().map_err(Into::into) } @@ -169,73 +186,136 @@ impl ServerManager { } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn execute_read(&self, task: F) -> Result + pub(crate) async fn execute(&self, consistency_level: ConsistencyLevel, task: F) -> Result where F: Fn(ServerConnection) -> P, P: Future>, { - // TODO: Add retries? YES, if no connections are alive, update connections based on replicas - // TODO: Sort randomly? Remember the last working replica to try it first? - for (address, server_connection) in self.read_server_connections().iter() { - match task(server_connection.clone()).await { - primary_replica_hinted_error!(primary) => { - // TODO: Should probably return the whole replica information... Otherwise, it's unsynced - todo!("Use primary_hint") // TODO - } - Err(err) if err.retryable() => { - // TODO: Expose public instead - debug!("Unable to connect to {} (private). Attempting next server.", address); + match consistency_level { + ConsistencyLevel::Strong => self.execute_strongly_consistent(task).await, + ConsistencyLevel::Eventual => self.execute_eventually_consistent(task).await, + 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()); } - res => return res, + self.execute_on(&address, self.private_address(&address), &task).await } } - Err(self.server_connection_failed_err()) } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn execute(&self, consistency_level: ConsistencyLevel, task: F) -> Result + 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().await?, + None => { + let replicas = self.read_replicas().clone(); + self.seek_primary_replica(replicas).await? + } }; let server_connections = self.read_server_connections(); for _ in 0..Self::PRIMARY_REPLICA_TASK_MAX_RETRIES { - if let Some(server_connection) = server_connections.get(primary_replica.private_address()) { + let private_address = primary_replica.private_address().clone(); + // TODO: Instead of "if let some", make connection if empty or throw networking error" + if let Some(server_connection) = server_connections.get(&private_address) { match task(server_connection.clone()).await { - primary_replica_hinted_error!(primary) => { - todo!("Use primary_hint") // TODO + Err(err) if err.not_primary() => { + debug!("Could not connect to the primary replica (no longer primary)..."); + let replicas = iter::once(primary_replica).chain( + self.read_replicas() + .clone() + .into_iter() + .filter(|replica| replica.private_address() != &private_address), + ); + primary_replica = self.seek_primary_replica(replicas).await?; + } + Err(err) if err.networking() => { + debug!("Could not connect to the primary replica (networking)..."); + let replicas = self + .read_replicas() + .clone() + .into_iter() + .filter(|replica| replica.private_address() != primary_replica.private_address()); + primary_replica = self.seek_primary_replica(replicas).await?; } - Err(err) if err.retryable() => (), res => return res, } } - debug!("Could not connect to the primary replica, waiting..."); - Self::wait_for_primary_replica_selection().await; - primary_replica = self.seek_primary_replica().await?; } Err(self.server_connection_failed_err()) } #[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 = self.fetch_replicas().await?; - *self.replicas.write().expect("Expected replicas write lock") = replicas; - if let Some(replica) = self.primary_replica() { - self.update_server_connections().await?; - return Ok(replica); + async fn execute_eventually_consistent(&self, task: F) -> Result + where + F: Fn(ServerConnection) -> P, + P: Future>, + { + let replicas = self.read_replicas().clone(); + 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>, + { + for replica in replicas.into_iter() { + match self.execute_on(replica.address(), replica.private_address(), &task).await { + Err(Error::Connection(err)) => { + debug!("Unable to connect to {}: {err:?}. Attempting next server.", replica.address()); + } + res => return res, } - Self::wait_for_primary_replica_selection().await; } - self.update_server_connections().await?; Err(self.server_connection_failed_err()) } + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn execute_on(&self, public_address: &Address, private_address: &Address, task: &F) -> Result + where + F: Fn(ServerConnection) -> P, + P: Future>, + { + let server_connection = match self.read_server_connections().get(private_address).cloned() { + Some(server_connection) => server_connection, + None => self.record_new_server_connection(public_address.clone(), private_address.clone()).await?, + }; + task(server_connection).await + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn seek_primary_replica( + &self, + source_replicas: impl IntoIterator, + ) -> Result { + self.execute_on_any(source_replicas, |server_connection| async { + self.try_fetch_primary_replica(server_connection).await + }) + .await + } + + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + async fn try_fetch_primary_replica(&self, server_connection: ServerConnection) -> Result { + let replicas = self.fetch_replicas(&server_connection).await?; + *self.replicas.write().expect("Expected replicas write lock") = replicas; + if let Some(replica) = self.primary_replica() { + self.update_server_connections().await?; + Ok(replica) + } else { + Err(self.server_connection_failed_err()) + } + } + fn primary_replica(&self) -> Option { self.replicas .read() @@ -282,8 +362,8 @@ impl ServerManager { ) .await; match server_connection { - // TODO: Don't we need to close the connection? Ok((server_connection, replicas)) => { + debug!("Fetched replicas from configured address '{address}': {replicas:?}"); let translated_replicas = Self::translate_replicas(replicas, &address_translation); if use_replication { let mut source_connections = HashMap::with_capacity(translated_replicas.len()); @@ -310,17 +390,11 @@ impl ServerManager { } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - async fn fetch_replicas(&self) -> Result> { - for (_, server_connection) in self.read_server_connections().iter() { - match server_connection.servers_all().await { - Ok(replicas) => { - return Ok(Self::translate_replicas(replicas, &self.address_translation)); - } - Err(err) if err.retryable() => (), - Err(err) => return Err(err), - } - } - Err(self.server_connection_failed_err()) + async fn fetch_replicas(&self, server_connection: &ServerConnection) -> Result> { + server_connection + .servers_all() + .await + .map(|replicas| Self::translate_replicas(replicas, &self.address_translation)) } fn translate_replicas( @@ -331,15 +405,18 @@ impl ServerManager { } fn server_connection_failed_err(&self) -> Error { - let accessed_addresses = Addresses::from_addresses( - self.replicas.read().expect("Expected a read replica lock").iter().map(|replica| replica.address().clone()), - ); + let accessed_addresses = + Addresses::from_addresses(self.read_replicas().iter().map(|replica| replica.address().clone())); ConnectionError::ServerConnectionFailed { configured_addresses: self.configured_addresses.clone(), accessed_addresses, } .into() } + + fn private_address<'a>(&'a self, address: &'a Address) -> &'a Address { + self.address_translation.get(address).unwrap_or_else(|| address) + } } impl fmt::Debug for ServerManager { diff --git a/rust/src/user/user_manager.rs b/rust/src/user/user_manager.rs index 0613fa1fab..668c189baf 100644 --- a/rust/src/user/user_manager.rs +++ b/rust/src/user/user_manager.rs @@ -125,7 +125,6 @@ impl UserManager { doc = "driver.users().get_with_consistency(username, ConsistencyLevel::Strong).await;" )] /// ``` - /// ``` #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub async fn get_with_consistency( &self, diff --git a/rust/tests/behaviour/steps/Cargo.toml b/rust/tests/behaviour/steps/Cargo.toml index c543e03765..f1655d76b1 100644 --- a/rust/tests/behaviour/steps/Cargo.toml +++ b/rust/tests/behaviour/steps/Cargo.toml @@ -16,7 +16,7 @@ features = {} [dependencies.tokio] features = ["bytes", "default", "fs", "full", "io-std", "io-util", "libc", "macros", "mio", "net", "parking_lot", "process", "rt", "rt-multi-thread", "signal", "signal-hook-registry", "socket2", "sync", "time", "tokio-macros"] - version = "1.45.1" + version = "1.45.0" default-features = false [dependencies.smol] @@ -41,7 +41,7 @@ features = {} [dependencies.macro_rules_attribute] features = ["default"] - version = "0.2.2" + version = "0.2.0" default-features = false [dependencies.typedb-driver] @@ -61,7 +61,7 @@ features = {} [dependencies.uuid] features = ["default", "fast-rng", "rng", "serde", "std", "v4"] - version = "1.17.0" + version = "1.16.0" default-features = false [dependencies.itertools] diff --git a/rust/tests/behaviour/steps/connection/transaction.rs b/rust/tests/behaviour/steps/connection/transaction.rs index e0187dade0..504851180e 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, diff --git a/rust/tests/behaviour/steps/connection/user.rs b/rust/tests/behaviour/steps/connection/user.rs index fdc30ea38e..59cbaf9732 100644 --- a/rust/tests/behaviour/steps/connection/user.rs +++ b/rust/tests/behaviour/steps/connection/user.rs @@ -23,9 +23,8 @@ 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 diff --git a/rust/tests/behaviour/steps/params.rs b/rust/tests/behaviour/steps/params.rs index bd173c6dca..6890e859c2 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; diff --git a/rust/tests/behaviour/steps/query.rs b/rust/tests/behaviour/steps/query.rs index 5258cfd3b3..5c0b8b679f 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 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..4e10327308 100644 --- a/rust/tests/behaviour/steps/util.rs +++ b/rust/tests/behaviour/steps/util.rs @@ -23,28 +23,19 @@ 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 futures::stream::StreamExt; use itertools::Itertools; 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/example.rs b/rust/tests/integration/example.rs index bddf700f89..6aece30432 100644 --- a/rust/tests/integration/example.rs +++ b/rust/tests/integration/example.rs @@ -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(); From 604e929469f47435799efa7f68d83215c7bc5a3b Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Wed, 25 Jun 2025 13:05:44 +0100 Subject: [PATCH 08/35] Simplified error processing --- rust/src/common/error.rs | 62 +------------------- rust/src/connection/server/server_manager.rs | 15 +++-- 2 files changed, 12 insertions(+), 65 deletions(-) diff --git a/rust/src/common/error.rs b/rust/src/common/error.rs index af82f12fb6..41ee666541 100644 --- a/rust/src/common/error.rs +++ b/rust/src/common/error.rs @@ -193,33 +193,8 @@ error_messages! { ConnectionError 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.", -} - -impl ConnectionError { - pub fn retryable(&self) -> bool { - match self { - ConnectionError::ServerConnectionFailedNetworking { .. } - | ConnectionError::ConnectionRefusedNetworking - | ConnectionError::ClusterReplicaNotPrimary => true, - _ => false, - } - } - - pub fn not_primary(&self) -> bool { - match self { - ConnectionError::ClusterReplicaNotPrimary => true, - _ => false, - } - } - - pub fn networking(&self) -> bool { - match self { - ConnectionError::ServerConnectionFailedNetworking { .. } | ConnectionError::ConnectionRefusedNetworking => { - true - } - _ => false, - } - } + 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.", } error_messages! { ConceptError @@ -334,39 +309,6 @@ impl Error { } } - pub fn retryable(&self) -> bool { - match self { - Error::Connection(error) => error.retryable(), - Error::Concept(_) => false, - Error::Migration(_) => false, - Error::Internal(_) => false, - Error::Server(_) => false, - Error::Other(_) => false, - } - } - - pub fn not_primary(&self) -> bool { - match self { - Error::Connection(error) => error.not_primary(), - Error::Concept(_) => false, - Error::Migration(_) => false, - Error::Internal(_) => false, - Error::Server(_) => false, - Error::Other(_) => false, - } - } - - pub fn networking(&self) -> bool { - match self { - Error::Connection(error) => error.networking(), - Error::Concept(_) => false, - Error::Migration(_) => false, - Error::Internal(_) => false, - Error::Server(_) => false, - Error::Other(_) => false, - } - } - fn try_extracting_connection_error(_status: &Status, code: &str) -> Option { match code { "AUT2" | "AUT3" => Some(ConnectionError::TokenCredentialInvalid {}), diff --git a/rust/src/connection/server/server_manager.rs b/rust/src/connection/server/server_manager.rs index b907b28fb2..1715a5cc08 100644 --- a/rust/src/connection/server/server_manager.rs +++ b/rust/src/connection/server/server_manager.rs @@ -227,8 +227,8 @@ impl ServerManager { // TODO: Instead of "if let some", make connection if empty or throw networking error" if let Some(server_connection) = server_connections.get(&private_address) { match task(server_connection.clone()).await { - Err(err) if err.not_primary() => { - debug!("Could not connect to the primary replica (no longer primary)..."); + Err(Error::Connection(ConnectionError::ClusterReplicaNotPrimary)) => { + debug!("Could not connect to the primary replica: no longer primary..."); let replicas = iter::once(primary_replica).chain( self.read_replicas() .clone() @@ -237,8 +237,8 @@ impl ServerManager { ); primary_replica = self.seek_primary_replica(replicas).await?; } - Err(err) if err.networking() => { - debug!("Could not connect to the primary replica (networking)..."); + Err(Error::Connection(err)) => { + debug!("Could not connect to the primary replica..."); let replicas = self .read_replicas() .clone() @@ -271,6 +271,9 @@ impl ServerManager { { for replica in replicas.into_iter() { match self.execute_on(replica.address(), replica.private_address(), &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:?}. Attempting next server.", replica.address()); } @@ -378,7 +381,9 @@ impl ServerManager { } } } - Err(err) if err.retryable() => (), + Err(Error::Connection(err)) => { + debug!("Unable to fetch replicas from {}: {err:?}. Attempting next server.", address); + } Err(err) => return Err(err), } } From f51daee38ad7ae7eccbf7464c139afdadc14401e Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Wed, 25 Jun 2025 15:45:10 +0100 Subject: [PATCH 09/35] Implement execute_strongly_consistent (it might even work) --- rust/src/connection/server/server_manager.rs | 98 ++++++++++---------- 1 file changed, 47 insertions(+), 51 deletions(-) diff --git a/rust/src/connection/server/server_manager.rs b/rust/src/connection/server/server_manager.rs index 1715a5cc08..d22cf7a05c 100644 --- a/rust/src/connection/server/server_manager.rs +++ b/rust/src/connection/server/server_manager.rs @@ -22,9 +22,11 @@ use std::{ fmt, future::Future, iter, + iter::Filter, sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}, thread::sleep, time::Duration, + vec::IntoIter, }; use itertools::Itertools; @@ -58,7 +60,6 @@ pub(crate) struct ServerManager { impl ServerManager { const PRIMARY_REPLICA_TASK_MAX_RETRIES: usize = 1; - const WAIT_FOR_PRIMARY_REPLICA_SELECTION: Duration = Duration::from_secs(2); #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub(crate) async fn new( @@ -119,7 +120,7 @@ impl ServerManager { } if server_connections.is_empty() { - Err(self.server_connection_failed_err()) + Err(self.server_connection_failed_err(None)) } else { Ok(()) } @@ -202,7 +203,7 @@ impl ServerManager { } .into()); } - self.execute_on(&address, self.private_address(&address), &task).await + self.execute_on(&address, self.private_address(&address), false, &task).await } } } @@ -215,42 +216,35 @@ impl ServerManager { { let mut primary_replica = match self.primary_replica() { Some(replica) => replica, - None => { - let replicas = self.read_replicas().clone(); - self.seek_primary_replica(replicas).await? - } + None => self.seek_primary_replica(self.read_replicas().clone()).await?, }; - let server_connections = self.read_server_connections(); - for _ in 0..Self::PRIMARY_REPLICA_TASK_MAX_RETRIES { + for _ in 0..=Self::PRIMARY_REPLICA_TASK_MAX_RETRIES { let private_address = primary_replica.private_address().clone(); - // TODO: Instead of "if let some", make connection if empty or throw networking error" - if let Some(server_connection) = server_connections.get(&private_address) { - match task(server_connection.clone()).await { - Err(Error::Connection(ConnectionError::ClusterReplicaNotPrimary)) => { - debug!("Could not connect to the primary replica: no longer primary..."); - let replicas = iter::once(primary_replica).chain( - self.read_replicas() - .clone() - .into_iter() - .filter(|replica| replica.private_address() != &private_address), - ); - primary_replica = self.seek_primary_replica(replicas).await?; - } - Err(Error::Connection(err)) => { - debug!("Could not connect to the primary replica..."); - let replicas = self - .read_replicas() - .clone() - .into_iter() - .filter(|replica| replica.private_address() != primary_replica.private_address()); - primary_replica = self.seek_primary_replica(replicas).await?; - } - res => return res, + match self.execute_on(primary_replica.address(), &private_address, false, &task).await { + Err(Error::Connection(connection_error)) => { + let replicas_without_old_primary = self + .read_replicas() + .clone() + .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(replicas).await? + } + err => { + debug!("Could not connect to the primary replica: {err:?}. Retrying..."); + self.seek_primary_replica(replicas_without_old_primary).await? + } + }; } + res => return res, } } - Err(self.server_connection_failed_err()) + Err(self.server_connection_failed_err(None)) } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] @@ -270,7 +264,7 @@ impl ServerManager { P: Future>, { for replica in replicas.into_iter() { - match self.execute_on(replica.address(), replica.private_address(), &task).await { + 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()); } @@ -280,18 +274,29 @@ impl ServerManager { res => return res, } } - Err(self.server_connection_failed_err()) + Err(self.server_connection_failed_err(None)) } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - async fn execute_on(&self, public_address: &Address, private_address: &Address, task: &F) -> Result + 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 server_connection = match self.read_server_connections().get(private_address).cloned() { Some(server_connection) => server_connection, - None => self.record_new_server_connection(public_address.clone(), private_address.clone()).await?, + None => match require_connected { + false => self.record_new_server_connection(public_address.clone(), private_address.clone()).await?, + true => { + return Err(self.server_connection_failed_err(Some(Addresses::from_address(public_address.clone())))) + } + }, }; task(server_connection).await } @@ -315,7 +320,7 @@ impl ServerManager { self.update_server_connections().await?; Ok(replica) } else { - Err(self.server_connection_failed_err()) + Err(self.server_connection_failed_err(None)) } } @@ -329,16 +334,6 @@ impl ServerManager { .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 - } - #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] async fn fetch_replicas_from_addresses( background_runtime: Arc, @@ -409,9 +404,10 @@ impl ServerManager { replicas.into_iter().map(|replica| replica.translated(address_translation)).collect() } - fn server_connection_failed_err(&self) -> Error { - let accessed_addresses = - Addresses::from_addresses(self.read_replicas().iter().map(|replica| replica.address().clone())); + fn server_connection_failed_err(&self, accessed_addresses: Option) -> 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, From d2f10661eff8d84d5dd4692f94fc75622a6a3b80 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Wed, 25 Jun 2025 16:33:49 +0100 Subject: [PATCH 10/35] Cleanups, expose replica interfaces in Rust --- rust/src/connection/message.rs | 2 +- .../src/connection/network/transmitter/rpc.rs | 2 +- .../network/transmitter/transaction.rs | 4 +- rust/src/connection/server/server_manager.rs | 44 ++++++------------- rust/src/connection/server/server_replica.rs | 8 +++- rust/src/driver.rs | 29 ++++++------ rust/src/user/user_manager.rs | 1 - rust/tests/behaviour/steps/connection/mod.rs | 2 - 8 files changed, 39 insertions(+), 53 deletions(-) diff --git a/rust/src/connection/message.rs b/rust/src/connection/message.rs index 81a354644e..e9c37ea7a9 100644 --- a/rust/src/connection/message.rs +++ b/rust/src/connection/message.rs @@ -30,7 +30,7 @@ use crate::{ concept_row::ConceptRowHeader, QueryType, }, - common::{address::Address, info::DatabaseInfo, RequestID}, + common::{info::DatabaseInfo, RequestID}, concept::Concept, connection::{server::server_replica::ServerReplica, ServerVersion}, error::ServerError, diff --git a/rust/src/connection/network/transmitter/rpc.rs b/rust/src/connection/network/transmitter/rpc.rs index 855c5384ae..76b9b51215 100644 --- a/rust/src/connection/network/transmitter/rpc.rs +++ b/rust/src/connection/network/transmitter/rpc.rs @@ -146,7 +146,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 2df25c3d8b..3f8f0a07d5 100644 --- a/rust/src/connection/network/transmitter/transaction.rs +++ b/rust/src/connection/network/transmitter/transaction.rs @@ -54,7 +54,7 @@ use crate::{ Callback, Promise, RequestID, Result, }, connection::{ - message::{QueryResponse, Response, TransactionRequest, TransactionResponse}, + message::{QueryResponse, TransactionRequest, TransactionResponse}, network::proto::{FromProto, IntoProto, TryFromProto}, runtime::BackgroundRuntime, }, @@ -229,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/server_manager.rs b/rust/src/connection/server/server_manager.rs index d22cf7a05c..4722b31965 100644 --- a/rust/src/connection/server/server_manager.rs +++ b/rust/src/connection/server/server_manager.rs @@ -22,11 +22,8 @@ use std::{ fmt, future::Future, iter, - iter::Filter, sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}, - thread::sleep, time::Duration, - vec::IntoIter, }; use itertools::Itertools; @@ -59,6 +56,7 @@ pub(crate) struct ServerManager { } impl ServerManager { + // TODO: Introduce a timer-based connections update const PRIMARY_REPLICA_TASK_MAX_RETRIES: usize = 1; #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] @@ -168,15 +166,12 @@ impl ServerManager { self.read_server_connections().values().map(ServerConnection::force_close).try_collect().map_err(Into::into) } - #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub(crate) async fn servers_all(&self) -> Result> { - // TODO: May need to expose multiple consistency levels. Check the driver's "replicas" impl! - self.execute(ConsistencyLevel::Strong, |server_connection| async move { server_connection.servers_all().await }) - .await + pub(crate) fn replicas(&self) -> Vec { + self.read_replicas().clone() } - pub(crate) fn server_count(&self) -> usize { - self.read_server_connections().len() + 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 { @@ -203,7 +198,8 @@ impl ServerManager { } .into()); } - self.execute_on(&address, self.private_address(&address), false, &task).await + let private_address = self.address_translation.get(&address).unwrap_or_else(|| &address); + self.execute_on(&address, private_address, false, &task).await } } } @@ -216,7 +212,7 @@ impl ServerManager { { let mut primary_replica = match self.primary_replica() { Some(replica) => replica, - None => self.seek_primary_replica(self.read_replicas().clone()).await?, + None => self.seek_primary_replica_in(self.read_replicas().clone()).await?, }; for _ in 0..=Self::PRIMARY_REPLICA_TASK_MAX_RETRIES { @@ -233,11 +229,11 @@ impl ServerManager { 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(replicas).await? + self.seek_primary_replica_in(replicas).await? } err => { debug!("Could not connect to the primary replica: {err:?}. Retrying..."); - self.seek_primary_replica(replicas_without_old_primary).await? + self.seek_primary_replica_in(replicas_without_old_primary).await? } }; } @@ -302,18 +298,18 @@ impl ServerManager { } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - async fn seek_primary_replica( + async fn seek_primary_replica_in( &self, source_replicas: impl IntoIterator, ) -> Result { self.execute_on_any(source_replicas, |server_connection| async { - self.try_fetch_primary_replica(server_connection).await + self.seek_primary_replica(server_connection).await }) .await } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - async fn try_fetch_primary_replica(&self, server_connection: ServerConnection) -> Result { + async fn seek_primary_replica(&self, server_connection: ServerConnection) -> Result { let replicas = self.fetch_replicas(&server_connection).await?; *self.replicas.write().expect("Expected replicas write lock") = replicas; if let Some(replica) = self.primary_replica() { @@ -324,16 +320,6 @@ impl ServerManager { } } - fn primary_replica(&self) -> Option { - self.replicas - .read() - .expect("Expected a read replica lock") - .iter() - .filter(|replica| replica.is_primary()) - .max_by_key(|replica| replica.term()) - .cloned() - } - #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] async fn fetch_replicas_from_addresses( background_runtime: Arc, @@ -414,10 +400,6 @@ impl ServerManager { } .into() } - - fn private_address<'a>(&'a self, address: &'a Address) -> &'a Address { - self.address_translation.get(address).unwrap_or_else(|| address) - } } impl fmt::Debug for ServerManager { diff --git a/rust/src/connection/server/server_replica.rs b/rust/src/connection/server/server_replica.rs index 205c98fc63..57a572d5c8 100644 --- a/rust/src/connection/server/server_replica.rs +++ b/rust/src/connection/server/server_replica.rs @@ -35,8 +35,12 @@ impl ServerReplica { } pub(crate) fn translate_address(&mut self, address_translation: &HashMap) -> bool { - if let Some((public, _)) = address_translation.iter().find(|(_, private)| private == &self.address()) { - self.private_address = public.clone(); + if let Some(translated) = address_translation + .iter() + .find(|(_, private)| private == &self.private_address()) + .map(|(public, _)| public.clone()) + { + self.public_address = Some(translated); true } else { false diff --git a/rust/src/driver.rs b/rust/src/driver.rs index 110be300a4..09a93f8132 100644 --- a/rust/src/driver.rs +++ b/rust/src/driver.rs @@ -20,11 +20,7 @@ use std::{fmt, sync::Arc}; use crate::{ - common::{ - consistency_level::ConsistencyLevel, - error::{ConnectionError, Error}, - Result, - }, + common::{consistency_level::ConsistencyLevel, Result}, connection::{ runtime::BackgroundRuntime, server::{ @@ -33,8 +29,7 @@ use crate::{ }, ServerVersion, }, - Credentials, Database, DatabaseManager, DriverOptions, Transaction, TransactionOptions, TransactionType, - UserManager, + Credentials, DatabaseManager, DriverOptions, Transaction, TransactionOptions, TransactionType, UserManager, }; /// A connection to a TypeDB server which serves as the starting point for all interaction. @@ -166,13 +161,21 @@ impl TypeDBDriver { /// # Examples /// /// ```rust - #[cfg_attr(feature = "sync", doc = "driver.replicas()")] - #[cfg_attr(not(feature = "sync"), doc = "driver.replicas().await")] + /// driver.replicas() /// ``` - #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub async fn replicas(&self) -> Result> { - // TODO: check expect - todo!("Todo implement") + pub fn replicas(&self) -> Vec { + self.server_manager.replicas() + } + + /// Retrieves the server's primary replica, if exists. + /// + /// # Examples + /// + /// ```rust + /// driver.primary_replica() + /// ``` + pub fn primary_replica(&self) -> Option { + self.server_manager.primary_replica() } /// Checks it this connection is opened. diff --git a/rust/src/user/user_manager.rs b/rust/src/user/user_manager.rs index 668c189baf..756aa85726 100644 --- a/rust/src/user/user_manager.rs +++ b/rust/src/user/user_manager.rs @@ -21,7 +21,6 @@ use std::sync::Arc; use crate::{ common::{consistency_level::ConsistencyLevel, Result}, connection::server::server_manager::ServerManager, - error::ConnectionError, User, }; diff --git a/rust/tests/behaviour/steps/connection/mod.rs b/rust/tests/behaviour/steps/connection/mod.rs index 293a881083..67d85db5cb 100644 --- a/rust/tests/behaviour/steps/connection/mod.rs +++ b/rust/tests/behaviour/steps/connection/mod.rs @@ -19,8 +19,6 @@ use cucumber::{given, then, when}; use macro_rules_attribute::apply; -use tokio::time::sleep; -use typedb_driver::{Credentials, TypeDBDriver}; use crate::{assert_with_timeout, generic_step, params, params::check_boolean, Context}; From 615713f11c8dafde05aa718f34385f5a50219f5b Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Thu, 26 Jun 2025 13:00:12 +0100 Subject: [PATCH 11/35] Provide FFI for the new calls. Add example usages in Java --- c/src/concept/concept.rs | 6 +- c/src/connection.rs | 21 ++- c/src/consistency_level.rs | 97 ++++++++++++++ c/src/database.rs | 69 ---------- c/src/database_manager.rs | 21 ++- c/src/lib.rs | 2 + c/src/server_replica.rs | 74 +++++++++++ c/src/transaction.rs | 6 +- c/swig/typedb_driver_java.swg | 23 ++-- c/typedb_driver.i | 30 ++--- .../ROOT/partials/c/connection/database.adoc | 2 +- .../partials/cpp/connection/Database.adoc | 2 +- .../partials/csharp/connection/IDatabase.adoc | 2 +- .../partials/nodejs/connection/Database.adoc | 6 +- .../partials/rust/connection/Database.adoc | 2 +- java/api/ConsistencyLevel.java | 120 ++++++++++++++++++ java/api/Driver.java | 10 +- java/api/database/DatabaseManager.java | 24 +++- java/api/server/ReplicaType.java | 12 +- .../{Replica.java => ServerReplica.java} | 12 +- java/api/server/ServerVersion.java | 13 +- java/connection/DatabaseImpl.java | 48 ------- java/connection/DatabaseManagerImpl.java | 5 +- java/connection/DriverImpl.java | 18 +++ python/typedb/connection/database.py | 10 +- rust/src/common/address.rs | 2 +- rust/src/common/consistency_level.rs | 4 + rust/src/connection/mod.rs | 6 +- rust/src/connection/network/proto/server.rs | 2 +- rust/src/connection/server/mod.rs | 2 +- rust/src/connection/server/server_manager.rs | 9 +- rust/src/connection/server/server_replica.rs | 28 ++-- rust/src/driver.rs | 4 +- rust/src/lib.rs | 10 +- 34 files changed, 474 insertions(+), 228 deletions(-) create mode 100644 c/src/consistency_level.rs create mode 100644 c/src/server_replica.rs create mode 100644 java/api/ConsistencyLevel.java rename java/api/server/{Replica.java => ServerReplica.java} (83%) diff --git a/c/src/concept/concept.rs b/c/src/concept/concept.rs index 200c31ad4a..87a3fb5dc7 100644 --- a/c/src/concept/concept.rs +++ b/c/src/concept/concept.rs @@ -50,7 +50,7 @@ impl DatetimeInNanos { } } -/// 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)] @@ -321,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/connection.rs b/c/src/connection.rs index 3e4f3107ac..e3edb91260 100644 --- a/c/src/connection.rs +++ b/c/src/connection.rs @@ -19,7 +19,10 @@ use std::{ffi::c_char, path::Path}; -use typedb_driver::{Addresses, Credentials, DriverOptions, TypeDBDriver}; +use typedb_driver::{ + box_stream, consistency_level::ConsistencyLevel, Addresses, Credentials, Database, DriverOptions, ServerReplica, + TypeDBDriver, +}; use super::{ error::{try_release, unwrap_void}, @@ -27,7 +30,9 @@ use super::{ }; use crate::{ error::unwrap_or_default, - memory::{release, release_string, string_free}, + iterator::CIterator, + memory::{release, release_optional, release_string, string_free}, + server_replica::ServerReplicaIterator, }; const DRIVER_LANG: &'static str = "c"; @@ -148,3 +153,15 @@ impl Drop for ServerVersion { pub extern "C" fn server_version_drop(server_version: *mut ServerVersion) { free(server_version); } + +/// Retrieves the server's replicas. +#[no_mangle] +pub extern "C" fn driver_replicas(driver: *const TypeDBDriver) -> *mut ServerReplicaIterator { + release(ServerReplicaIterator(CIterator(box_stream(borrow(driver).replicas().into_iter())))) +} + +/// Retrieves the server's primary replica, if exists. +#[no_mangle] +pub extern "C" fn driver_primary_replicas(driver: *const TypeDBDriver) -> *mut ServerReplica { + release_optional(borrow(driver).primary_replica()) +} diff --git a/c/src/consistency_level.rs b/c/src/consistency_level.rs new file mode 100644 index 0000000000..d683b5be0c --- /dev/null +++ b/c/src/consistency_level.rs @@ -0,0 +1,97 @@ +/* + * 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::{ + error::unwrap_or_default, + memory::{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 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 { tag: ConsistencyLevelTag::Strong, address: std::ptr::null_mut() }) +} + +/// Creates an eventual ConsistencyLevel object. +#[no_mangle] +pub extern "C" fn consistency_level_eventual() -> *mut ConsistencyLevel { + release(ConsistencyLevel { tag: ConsistencyLevelTag::Eventual, address: std::ptr::null_mut() }) +} + +/// 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 { + tag: ConsistencyLevelTag::ReplicaDependant, + address: 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) +} + +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 } + } + } + } +} diff --git a/c/src/database.rs b/c/src/database.rs index 8e669e778f..1a95c0ec20 100644 --- a/c/src/database.rs +++ b/c/src/database.rs @@ -73,72 +73,3 @@ pub extern "C" fn database_export_to_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_manager.rs b/c/src/database_manager.rs index ef8dbe39e2..41ff3a7642 100644 --- a/c/src/database_manager.rs +++ b/c/src/database_manager.rs @@ -26,7 +26,9 @@ use super::{ iterator::CIterator, memory::{borrow_mut, free, string_view}, }; -use crate::{error::try_release_arc, iterator::iterator_arc_next}; +use crate::{ + consistency_level::ConsistencyLevel, error::try_release_arc, iterator::iterator_arc_next, memory::borrow_optional, +}; /// An Iterator over databases present on the TypeDB server. pub struct DatabaseIterator(CIterator>); @@ -45,11 +47,20 @@ 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. #[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_all( + driver: *mut TypeDBDriver, + consistency_level: *const ConsistencyLevel, +) -> *mut DatabaseIterator { + let databases = borrow_mut(driver).databases(); + let result = match borrow_optional(consistency_level).cloned() { + Some(consistency_level) => databases.all_with_consistency(consistency_level.into()), + None => databases.all(), + }; + try_release(result.map(|dbs| DatabaseIterator(CIterator(box_stream(dbs.into_iter()))))) } /// Creates a database with the given name. diff --git a/c/src/lib.rs b/c/src/lib.rs index 5cd285ea14..82c0f621eb 100644 --- a/c/src/lib.rs +++ b/c/src/lib.rs @@ -21,6 +21,7 @@ mod answer; mod common; mod concept; mod connection; +mod consistency_level; mod database; mod database_manager; mod error; @@ -28,6 +29,7 @@ mod iterator; mod memory; mod promise; mod query_options; +mod server_replica; mod transaction; mod transaction_options; mod user; diff --git a/c/src/server_replica.rs b/c/src/server_replica.rs new file mode 100644 index 0000000000..dc352f02ad --- /dev/null +++ b/c/src/server_replica.rs @@ -0,0 +1,74 @@ +/* + * 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 super::memory::{borrow, release_string}; +use crate::{ + iterator::{iterator_next, CIterator}, + memory::free, +}; + +/// 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 address this replica is hosted at. +#[no_mangle] +pub extern "C" fn server_replica_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_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_term(replica_info: *const ServerReplica) -> i64 { + borrow(replica_info).term() +} diff --git a/c/src/transaction.rs b/c/src/transaction.rs index 8e4f31d7da..1e7bdfc1eb 100644 --- a/c/src/transaction.rs +++ b/c/src/transaction.rs @@ -19,9 +19,7 @@ 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}; @@ -39,7 +37,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/swig/typedb_driver_java.swg b/c/swig/typedb_driver_java.swg index 06855a4062..f6bf2f2ba1 100644 --- a/c/swig/typedb_driver_java.swg +++ b/c/swig/typedb_driver_java.swg @@ -104,21 +104,24 @@ %nojavaexception error_message; %nojavaexception driver_is_open; +%nojavaexception driver_replicas; +%nojavaexception driver_primary_replica; %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_address; +%nojavaexception server_replica_is_primary; +%nojavaexception server_replica_type; +%nojavaexception server_replica_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; @@ -218,6 +221,7 @@ %nojavaexception ~ConceptIterator; %nojavaexception ~ConceptRow; %nojavaexception ~ConceptRowIterator; +%nojavaexception ~ConsistencyLevel; %nojavaexception ~DriverOptions; %nojavaexception ~Credentials; %nojavaexception ~Database; @@ -227,7 +231,8 @@ %nojavaexception ~Decimal; %nojavaexception ~Duration; %nojavaexception ~Error; -//%nojavaexception ~ReplicaInfo; +%nojavaexception ~ServerReplica; +%nojavaexception ~ServerReplicaIterator; %nojavaexception ~ServerVersion; %nojavaexception ~StringIterator; %nojavaexception ~StringAndOptValue; @@ -388,7 +393,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/typedb_driver.i b/c/typedb_driver.i index 75e80662b8..310dd51761 100644 --- a/c/typedb_driver.i +++ b/c/typedb_driver.i @@ -60,6 +60,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 +71,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,12 +82,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) -%dropproxydefined(ServerVersion, server_version) %dropproxy(StringAndOptValueIterator, string_and_opt_value_iterator) - %dropproxy(StringIterator, string_iterator) %dropproxy(QueryAnswer, query_answer) @@ -141,8 +141,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);"; @@ -156,9 +156,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; @@ -179,17 +176,15 @@ void transaction_on_close_register(const Transaction* transaction, TransactionCa %newobject driver_open_with_description; %newobject driver_options_new; +%newobject driver_primary_replica; +%newobject driver_replicas; %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_address; %newobject databases_all; %newobject databases_get; @@ -204,17 +199,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/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/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/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/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/rust/connection/Database.adoc b/docs/modules/ROOT/partials/rust/connection/Database.adoc index eb86049973..065e60ae72 100644 --- a/docs/modules/ROOT/partials/rust/connection/Database.adoc +++ b/docs/modules/ROOT/partials/rust/connection/Database.adoc @@ -219,7 +219,7 @@ database.primary_replica_info() pub fn replicas_info(&self) -> Vec ---- -Returns the ``Replica`` instances for this database. _Only works in TypeDB Cloud / Enterprise_ +Returns the ``ServerReplica`` instances for this database. _Only works in TypeDB Cloud / Enterprise_ [caption=""] .Returns diff --git a/java/api/ConsistencyLevel.java b/java/api/ConsistencyLevel.java new file mode 100644 index 0000000000..e9e6a38d97 --- /dev/null +++ b/java/api/ConsistencyLevel.java @@ -0,0 +1,120 @@ +/* + * 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.jni.typedb_driver.consistency_level_eventual; +import static com.typedb.driver.jni.typedb_driver.consistency_level_strong; +import static com.typedb.driver.jni.typedb_driver.consistency_level_replica_dependant; + +/** + * Consistency levels of operations against a distributed database. 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 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); + } + + 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 20b9965201..19e82259e4 100644 --- a/java/api/Driver.java +++ b/java/api/Driver.java @@ -20,13 +20,13 @@ package com.typedb.driver.api; import com.typedb.driver.api.database.DatabaseManager; -import com.typedb.driver.api.server.Replica; +import com.typedb.driver.api.server.ServerReplica; import com.typedb.driver.api.user.UserManager; import com.typedb.driver.common.exception.TypeDBDriverException; import javax.annotation.CheckReturnValue; - -import static com.typedb.driver.common.exception.ErrorMessage.Internal.UNEXPECTED_NATIVE_VALUE; +import java.util.Optional; +import java.util.Set; public interface Driver extends AutoCloseable { String LANGUAGE = "java"; @@ -107,7 +107,7 @@ public interface Driver extends AutoCloseable { *
*/ @CheckReturnValue - Set replicas(); + Set replicas(); /** * Returns the primary replica for this driver connection. @@ -118,5 +118,5 @@ public interface Driver extends AutoCloseable { *
*/ @CheckReturnValue - Optional primaryReplica(); + Optional primaryReplica(); } diff --git a/java/api/database/DatabaseManager.java b/java/api/database/DatabaseManager.java index bfc01a965e..22982bf5f7 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; @@ -83,13 +84,32 @@ public interface DatabaseManager { void importFromFile(String name, String schema, String dataFilePath) throws TypeDBDriverException; /** - * Retrieves all databases present on the TypeDB server. + * 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()
      * 
+ * + * @see #allWithConsistency(ConsistencyLevel) + */ + @CheckReturnValue + default List all() throws TypeDBDriverException { + return all(null); + } + + /** + * Retrieves all databases present on the TypeDB server. + * + *

Examples

+ *
+     * driver.databases().all(ConsistencyLevel.Strong)
+     * 
+ * + * @param consistencyLevel The consistency level to use for the operation + * @see #all() */ @CheckReturnValue - List all() throws TypeDBDriverException; + List all(ConsistencyLevel consistencyLevel) throws TypeDBDriverException; } diff --git a/java/api/server/ReplicaType.java b/java/api/server/ReplicaType.java index 62e6956750..a05c454e26 100644 --- a/java/api/server/ReplicaType.java +++ b/java/api/server/ReplicaType.java @@ -21,21 +21,19 @@ import com.typedb.driver.common.exception.TypeDBDriverException; -import javax.annotation.CheckReturnValue; - import static com.typedb.driver.common.exception.ErrorMessage.Internal.UNEXPECTED_NATIVE_VALUE; /** - * Type of replica. + * This enum is used to specify the type of replica. * *

Examples

*
- * replica.type();
+ * replica.getType();
  * 
*/ public enum ReplicaType { PRIMARY(0, com.typedb.driver.jni.ReplicaType.Primary), - SECONDARY(1, com.typedb.driver.jni.ReplicaType.Secondary), + SECONDARY(1, com.typedb.driver.jni.ReplicaType.Secondary); public final com.typedb.driver.jni.ReplicaType nativeObject; private final int id; @@ -59,13 +57,13 @@ public int id() { * Checks whether this is the primary replica of the raft cluster. */ public boolean isPrimary() { - return nativeObject == com.typedb.driver.jni.QueryType.Primary; + 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.QueryType.Secondary; + return nativeObject == com.typedb.driver.jni.ReplicaType.Secondary; } } diff --git a/java/api/server/Replica.java b/java/api/server/ServerReplica.java similarity index 83% rename from java/api/server/Replica.java rename to java/api/server/ServerReplica.java index a87c3d9fb4..ec4b075029 100644 --- a/java/api/server/Replica.java +++ b/java/api/server/ServerReplica.java @@ -19,31 +19,27 @@ package com.typedb.driver.api.server; -import com.typedb.driver.common.exception.TypeDBDriverException; - import javax.annotation.CheckReturnValue; -import static com.typedb.driver.common.exception.ErrorMessage.Internal.UNEXPECTED_NATIVE_VALUE; - /** * The metadata and state of an individual raft replica of a driver connection. */ -public interface Replica { +public interface ServerReplica { /** * The address this replica is hosted at. */ @CheckReturnValue - String address(); + String getAddress(); /** * Gets the type of this replica: whether it's a primary or a secondary replica. */ @CheckReturnValue - ReplicaType type(); + ReplicaType getType(); /** * The raft protocol ‘term’ of this replica. */ @CheckReturnValue - long term(); + long getTerm(); } diff --git a/java/api/server/ServerVersion.java b/java/api/server/ServerVersion.java index 1f48ec2df0..cb525ec6a0 100644 --- a/java/api/server/ServerVersion.java +++ b/java/api/server/ServerVersion.java @@ -22,8 +22,6 @@ import com.typedb.driver.common.exception.ErrorMessage; import com.typedb.driver.common.exception.TypeDBDriverException; -import static com.typedb.driver.common.exception.ErrorMessage.Internal.UNEXPECTED_NATIVE_VALUE; - /** * Type of replica. * @@ -45,30 +43,27 @@ public class ServerVersion { this.version = nativeObject.getVersion(); } - // TODO: Rename to getters? Make the fields public instead? - /** * Returns the server's distribution. * *

Examples

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

Examples

*
-     * serverVersion.version();
+     * serverVersion.getVersion();
      * 
*/ - public String version() { + public String getVersion() { return version; } } diff --git a/java/connection/DatabaseImpl.java b/java/connection/DatabaseImpl.java index 24cb2a83cc..62bb0492e1 100644 --- a/java/connection/DatabaseImpl.java +++ b/java/connection/DatabaseImpl.java @@ -88,52 +88,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..f0da07e617 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; @@ -84,9 +85,9 @@ public void importFromFile(String name, String schema, String dataFilePath) thro } @Override - public List all() throws TypeDBDriverException { + public List all(ConsistencyLevel consistencyLevel) throws TypeDBDriverException { try { - return new NativeIterator<>(databases_all(nativeDriver)).stream().map(DatabaseImpl::new).collect(toList()); + 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); } diff --git a/java/connection/DriverImpl.java b/java/connection/DriverImpl.java index 1bcfd734d5..cc7d6b312c 100644 --- a/java/connection/DriverImpl.java +++ b/java/connection/DriverImpl.java @@ -25,12 +25,17 @@ 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.user.UserManager; 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.HashSet; +import java.util.Optional; +import java.util.Set; + 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; @@ -83,6 +88,19 @@ public Transaction transaction(String database, Transaction.Type type, Transacti return new TransactionImpl(this, database, type, options); } + @Override + public Set replicas() { + // TODO: Implement + HashSet replicas = new HashSet<>(); + return replicas; + } + + @Override + public Optional primaryReplica() { + // TODO: Implement + return Optional.empty(); + } + @Override public void close() { try { diff --git a/python/typedb/connection/database.py b/python/typedb/connection/database.py index 5710a10c5e..fb9d3c4643 100644 --- a/python/typedb/connection/database.py +++ b/python/typedb/connection/database.py @@ -73,7 +73,7 @@ def delete(self) -> None: # def replicas(self) -> set[Replica]: # try: - # repl_iter = IteratorWrapper(database_get_replicas_info(self.native_object), replica_info_iterator_next) + # repl_iter = IteratorWrapper(database_get_replicas_info(self.native_object), server_replica_iterator_next) # return set(_Database.Replica(replica_info) for replica_info in repl_iter) # except TypeDBDriverExceptionNative as e: # raise TypeDBDriverException.of(e) from None @@ -103,13 +103,13 @@ def __repr__(self): # pass # # def server(self) -> str: - # return replica_info_get_server(self._info) + # return server_replica_get_server(self._info) # # def is_primary(self) -> bool: - # return replica_info_is_primary(self._info) + # return server_replica_is_primary(self._info) # # def is_preferred(self) -> bool: - # return replica_info_is_preferred(self._info) + # return server_replica_is_preferred(self._info) # # def term(self) -> int: - # return replica_info_get_term(self._info) + # return server_replica_get_term(self._info) diff --git a/rust/src/common/address.rs b/rust/src/common/address.rs index a258f29f35..d9c9939b13 100644 --- a/rust/src/common/address.rs +++ b/rust/src/common/address.rs @@ -26,7 +26,7 @@ use crate::{ error::ConnectionError, }; -#[derive(Clone, Hash, PartialEq, Eq)] +#[derive(Clone, Hash, PartialEq, Eq, Default)] pub struct Address { uri: Uri, } diff --git a/rust/src/common/consistency_level.rs b/rust/src/common/consistency_level.rs index 3eaeca278e..001014d0fc 100644 --- a/rust/src/common/consistency_level.rs +++ b/rust/src/common/consistency_level.rs @@ -20,6 +20,10 @@ use std::fmt; use crate::common::address::Address; +/// Consistency levels of operations against a distributed database. 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. diff --git a/rust/src/connection/mod.rs b/rust/src/connection/mod.rs index 41be61b38c..baf1a06fa4 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, server::ServerVersion}; +pub use self::{ + credentials::Credentials, + driver_options::DriverOptions, + server::{server_replica, ServerVersion}, +}; mod credentials; pub(crate) mod database; diff --git a/rust/src/connection/network/proto/server.rs b/rust/src/connection/network/proto/server.rs index 235f8f8669..1f15a0db43 100644 --- a/rust/src/connection/network/proto/server.rs +++ b/rust/src/connection/network/proto/server.rs @@ -39,7 +39,7 @@ impl TryFromProto for ServerReplica { Some(replication_status) => ReplicationStatus::try_from_proto(replication_status)?, None => ReplicationStatus::default(), }; - Ok(Self::from_private(address, replication_status.replica_type, replication_status.term)) + Ok(Self::from_private(address, replication_status)) } } diff --git a/rust/src/connection/server/mod.rs b/rust/src/connection/server/mod.rs index 938e2705eb..e550425afd 100644 --- a/rust/src/connection/server/mod.rs +++ b/rust/src/connection/server/mod.rs @@ -22,5 +22,5 @@ pub use self::{addresses::Addresses, server_version::ServerVersion}; pub(crate) mod addresses; pub(crate) mod server_connection; pub(crate) mod server_manager; -pub(crate) mod server_replica; +pub mod server_replica; mod server_version; diff --git a/rust/src/connection/server/server_manager.rs b/rust/src/connection/server/server_manager.rs index 4722b31965..c79108a208 100644 --- a/rust/src/connection/server/server_manager.rs +++ b/rust/src/connection/server/server_manager.rs @@ -166,8 +166,8 @@ impl ServerManager { self.read_server_connections().values().map(ServerConnection::force_close).try_collect().map_err(Into::into) } - pub(crate) fn replicas(&self) -> Vec { - self.read_replicas().clone() + pub(crate) fn replicas(&self) -> HashSet { + self.read_replicas().iter().cloned().collect() } pub(crate) fn primary_replica(&self) -> Option { @@ -214,6 +214,7 @@ impl ServerManager { Some(replica) => replica, None => self.seek_primary_replica_in(self.read_replicas().clone()).await?, }; + let mut primary_changed = true; for _ in 0..=Self::PRIMARY_REPLICA_TASK_MAX_RETRIES { let private_address = primary_replica.private_address().clone(); @@ -236,6 +237,10 @@ impl ServerManager { self.seek_primary_replica_in(replicas_without_old_primary).await? } }; + + if primary_replica.private_address() == &private_address { + break; + } } res => return res, } diff --git a/rust/src/connection/server/server_replica.rs b/rust/src/connection/server/server_replica.rs index 57a572d5c8..01152060b1 100644 --- a/rust/src/connection/server/server_replica.rs +++ b/rust/src/connection/server/server_replica.rs @@ -25,13 +25,12 @@ use crate::common::address::Address; pub struct ServerReplica { private_address: Address, public_address: Option
, - replica_type: ReplicaType, - term: i64, + replication_status: ReplicationStatus, } impl ServerReplica { - pub(crate) fn from_private(private_address: Address, replica_type: ReplicaType, term: i64) -> Self { - Self { private_address, public_address: None, replica_type, term } + pub(crate) fn from_private(private_address: Address, replication_status: ReplicationStatus) -> Self { + Self { private_address, public_address: None, replication_status } } pub(crate) fn translate_address(&mut self, address_translation: &HashMap) -> bool { @@ -56,29 +55,30 @@ impl ServerReplica { &self.private_address } - /// The address this replica is hosted at. + /// Returns the address this replica is hosted at. pub fn address(&self) -> &Address { self.public_address.as_ref().unwrap_or(&self.private_address) } - /// Whether this is the primary replica of the raft cluster or any of the supporting types. + /// 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_type + self.replication_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) + matches!(self.replica_type(), ReplicaType::Primary) } - /// The raft protocol ‘term’ of this replica. + /// Returns the raft protocol ‘term’ of this replica. pub fn term(&self) -> i64 { - self.term + self.replication_status.term } } /// The metadata and state of an individual server as a raft replica. -#[derive(Debug, PartialEq, Eq, Hash)] -pub struct ReplicationStatus { +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub(crate) struct ReplicationStatus { /// The role of this replica in the raft cluster. pub replica_type: ReplicaType, /// The raft protocol ‘term’ of this server replica. @@ -91,8 +91,10 @@ impl Default for ReplicationStatus { } } +/// This enum is used to specify the type of replica. +#[repr(C)] #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] -pub(crate) enum ReplicaType { +pub enum ReplicaType { Primary, Secondary, } diff --git a/rust/src/driver.rs b/rust/src/driver.rs index 09a93f8132..a5dcca8df5 100644 --- a/rust/src/driver.rs +++ b/rust/src/driver.rs @@ -17,7 +17,7 @@ * under the License. */ -use std::{fmt, sync::Arc}; +use std::{collections::HashSet, fmt, sync::Arc}; use crate::{ common::{consistency_level::ConsistencyLevel, Result}, @@ -163,7 +163,7 @@ impl TypeDBDriver { /// ```rust /// driver.replicas() /// ``` - pub fn replicas(&self) -> Vec { + pub fn replicas(&self) -> HashSet { self.server_manager.replicas() } diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 0c047e954e..d3d232731c 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, BoxPromise, BoxStream, Error, Promise, QueryOptions, Result, + TransactionOptions, TransactionType, IID, + }, + connection::{ + server::Addresses, + server_replica::{ReplicaType, ServerReplica}, + Credentials, DriverOptions, ServerVersion, }, - connection::{server::Addresses, Credentials, DriverOptions}, database::{Database, DatabaseManager}, driver::TypeDBDriver, transaction::Transaction, From 59547fa99650030c449697b74fbc30ce63264104 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Fri, 27 Jun 2025 16:04:09 +0100 Subject: [PATCH 12/35] Make methods Send --- dependencies/typedb/repositories.bzl | 3 +- rust/src/connection/server/server_manager.rs | 35 ++++++++++---------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/dependencies/typedb/repositories.bzl b/dependencies/typedb/repositories.bzl index 57155678fe..11a9e41220 100644 --- a/dependencies/typedb/repositories.bzl +++ b/dependencies/typedb/repositories.bzl @@ -33,8 +33,9 @@ def 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/rust/src/connection/server/server_manager.rs b/rust/src/connection/server/server_manager.rs index c79108a208..69d93ae48e 100644 --- a/rust/src/connection/server/server_manager.rs +++ b/rust/src/connection/server/server_manager.rs @@ -97,18 +97,16 @@ impl ServerManager { #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] async fn update_server_connections(&self) -> Result { - let replicas = self.read_replicas(); - let replica_addresses: HashSet
= - replicas.iter().map(|replica| replica.private_address().clone()).collect(); + let replicas = self.read_replicas().clone(); let mut connection_errors = Vec::with_capacity(replicas.len()); - let mut server_connections = self.write_server_connections(); - server_connections.retain(|address, _| replica_addresses.contains(address)); - for replica in replicas.iter() { + let mut new_server_connections = HashMap::new(); + let connection_addresses: HashSet
= self.read_server_connections().keys().cloned().collect(); + for replica in &replicas { let private_address = replica.private_address().clone(); - if !server_connections.contains_key(&private_address) { + if !connection_addresses.contains(&private_address) { match self.new_server_connection(replica.address().clone()).await { Ok(server_connection) => { - server_connections.insert(private_address, server_connection); + new_server_connections.insert(private_address, server_connection); } Err(err) => { connection_errors.push(err); @@ -117,6 +115,12 @@ impl ServerManager { } } + let replica_addresses: HashSet
= + replicas.into_iter().map(|replica| replica.private_address().clone()).collect(); + let mut server_connections = self.write_server_connections(); + server_connections.retain(|address, _| replica_addresses.contains(address)); + server_connections.extend(new_server_connections); + if server_connections.is_empty() { Err(self.server_connection_failed_err(None)) } else { @@ -212,19 +216,15 @@ impl ServerManager { { let mut primary_replica = match self.primary_replica() { Some(replica) => replica, - None => self.seek_primary_replica_in(self.read_replicas().clone()).await?, + None => self.seek_primary_replica_in(self.replicas()).await?, }; - let mut primary_changed = true; for _ in 0..=Self::PRIMARY_REPLICA_TASK_MAX_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 - .read_replicas() - .clone() - .into_iter() - .filter(|replica| replica.private_address() != &private_address); + let replicas_without_old_primary = + self.replicas().into_iter().filter(|replica| replica.private_address() != &private_address); primary_replica = match connection_error { ConnectionError::ClusterReplicaNotPrimary => { @@ -254,7 +254,7 @@ impl ServerManager { F: Fn(ServerConnection) -> P, P: Future>, { - let replicas = self.read_replicas().clone(); + let replicas = self.replicas(); self.execute_on_any(replicas, task).await } @@ -290,7 +290,8 @@ impl ServerManager { F: Fn(ServerConnection) -> P, P: Future>, { - let server_connection = match self.read_server_connections().get(private_address).cloned() { + let existing_connection = { self.read_server_connections().get(private_address).cloned() }; + let server_connection = match existing_connection { Some(server_connection) => server_connection, None => match require_connected { false => self.record_new_server_connection(public_address.clone(), private_address.clone()).await?, From 502543a594e9c7da94f63d844e085a25d71dcb0a Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Fri, 27 Jun 2025 17:27:01 +0100 Subject: [PATCH 13/35] Fix bdds in Rust --- dependencies/typedb/repositories.bzl | 4 ++++ rust/src/common/error.rs | 2 ++ rust/src/transaction.rs | 10 ++++++++-- rust/tests/behaviour/steps/connection/transaction.rs | 4 ++-- rust/tests/behaviour/steps/lib.rs | 4 ++++ 5 files changed, 20 insertions(+), 4 deletions(-) diff --git a/dependencies/typedb/repositories.bzl b/dependencies/typedb/repositories.bzl index 11a9e41220..2794195c7b 100644 --- a/dependencies/typedb/repositories.bzl +++ b/dependencies/typedb/repositories.bzl @@ -34,6 +34,10 @@ def typedb_protocol(): def typedb_behaviour(): # TODO: Update ref after merge to master +# native.local_repository( +# name = "typedb_behaviour", +# path = "../typedb-behaviour", +# ) git_repository( name = "typedb_behaviour", remote = "https://github.com/typedb/typedb-behaviour", diff --git a/rust/src/common/error.rs b/rust/src/common/error.rs index 41ee666541..24d084cfdd 100644 --- a/rust/src/common/error.rs +++ b/rust/src/common/error.rs @@ -195,6 +195,8 @@ error_messages! { ConnectionError 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}.", } error_messages! { ConceptError diff --git a/rust/src/transaction.rs b/rust/src/transaction.rs index 9f2a3a6bdd..5e6936260c 100644 --- a/rust/src/transaction.rs +++ b/rust/src/transaction.rs @@ -45,12 +45,12 @@ impl Transaction { } } - /// 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() @@ -58,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/tests/behaviour/steps/connection/transaction.rs b/rust/tests/behaviour/steps/connection/transaction.rs index 504851180e..6be37bdcdb 100644 --- a/rust/tests/behaviour/steps/connection/transaction.rs +++ b/rust/tests/behaviour/steps/connection/transaction.rs @@ -167,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/lib.rs b/rust/tests/behaviour/steps/lib.rs index 6c69652d1a..1362b89f89 100644 --- a/rust/tests/behaviour/steps/lib.rs +++ b/rust/tests/behaviour/steps/lib.rs @@ -285,6 +285,10 @@ impl Context { 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) } From 611bc119a35038523dc0e8b72666b25b46aede06 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Fri, 27 Jun 2025 18:35:28 +0100 Subject: [PATCH 14/35] Add new driver options for retries --- rust/src/connection/driver_options.rs | 53 ++++++++++++++++++-- rust/src/connection/server/server_manager.rs | 11 ++-- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/rust/src/connection/driver_options.rs b/rust/src/connection/driver_options.rs index 1f73e7dc40..0f90e79f8e 100644 --- a/rust/src/connection/driver_options.rs +++ b/rust/src/connection/driver_options.rs @@ -21,6 +21,13 @@ use std::{fs, path::Path}; use tonic::transport::{Certificate, ClientTlsConfig}; +// When changing these numbers, also update docs in DriverOptions +const DEFAULT_IS_TLS_ENABLED: bool = false; +const DEFAULT_TLS_CONFIG: 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. @@ -39,9 +46,23 @@ pub struct DriverOptions { /// Defaults to None. pub tls_config: Option, /// Specifies whether the connection to TypeDB can use cluster replicas provided by the server - /// or it should be limited to the provided address. - /// Defaults to true. If set to false, restricts the driver to only a single address. + /// 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 redirect_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 discovery_failover_retries: Option, } impl DriverOptions { @@ -70,10 +91,36 @@ impl DriverOptions { 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 redirect_failover_retries(self, redirect_failover_retries: usize) -> Self { + Self { redirect_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 discovery_failover_retries(self, discovery_failover_retries: Option) -> Self { + Self { discovery_failover_retries, ..self } + } } impl Default for DriverOptions { fn default() -> Self { - Self { is_tls_enabled: false, tls_config: None, use_replication: true } + Self { + is_tls_enabled: DEFAULT_IS_TLS_ENABLED, + tls_config: DEFAULT_TLS_CONFIG, + use_replication: DEFAULT_USE_REPLICATION, + redirect_failover_retries: DEFAULT_REDIRECT_FAILOVER_RETRIES, + discovery_failover_retries: DEFAULT_DISCOVERY_FAILOVER_RETRIES, + } } } diff --git a/rust/src/connection/server/server_manager.rs b/rust/src/connection/server/server_manager.rs index 69d93ae48e..cd24fe6cdb 100644 --- a/rust/src/connection/server/server_manager.rs +++ b/rust/src/connection/server/server_manager.rs @@ -26,7 +26,7 @@ use std::{ time::Duration, }; -use itertools::Itertools; +use itertools::{enumerate, Itertools}; use log::debug; use crate::{ @@ -57,7 +57,6 @@ pub(crate) struct ServerManager { impl ServerManager { // TODO: Introduce a timer-based connections update - const PRIMARY_REPLICA_TASK_MAX_RETRIES: usize = 1; #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub(crate) async fn new( @@ -219,7 +218,7 @@ impl ServerManager { None => self.seek_primary_replica_in(self.replicas()).await?, }; - for _ in 0..=Self::PRIMARY_REPLICA_TASK_MAX_RETRIES { + for _ in 0..=self.driver_options.redirect_failover_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)) => { @@ -264,7 +263,11 @@ impl ServerManager { F: Fn(ServerConnection) -> P, P: Future>, { - for replica in replicas.into_iter() { + let limit = self.driver_options.discovery_failover_retries.unwrap_or(usize::MAX); + for (attempt, replica) in enumerate(replicas.into_iter()) { + if attempt == limit { + break; + } 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()); From d670e803cd6f7958c26ec8c78f238d57dc2c363d Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Mon, 30 Jun 2025 09:43:07 +0100 Subject: [PATCH 15/35] Update refs --- Cargo.lock | 2 +- dependencies/typedb/repositories.bzl | 10 +++------- rust/Cargo.toml | 2 +- rust/src/connection/network/proto/server.rs | 16 ++++++++-------- rust/src/connection/server/server_replica.rs | 14 +++++++------- 5 files changed, 20 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f799e1102e..96585743f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2754,7 +2754,7 @@ dependencies = [ [[package]] name = "typedb-protocol" version = "0.0.0" -source = "git+https://github.com/typedb/typedb-protocol?rev=ab1e84f24e861437d00bbb6082ae282e50860763#ab1e84f24e861437d00bbb6082ae282e50860763" +source = "git+https://github.com/typedb/typedb-protocol?rev=94560a87db703ba1686fa830d12d095dc8ab2795#94560a87db703ba1686fa830d12d095dc8ab2795" dependencies = [ "prost", "tonic", diff --git a/dependencies/typedb/repositories.bzl b/dependencies/typedb/repositories.bzl index 2794195c7b..2577ec1d5f 100644 --- a/dependencies/typedb/repositories.bzl +++ b/dependencies/typedb/repositories.bzl @@ -25,19 +25,15 @@ def typedb_dependencies(): ) def typedb_protocol(): - # TODO: Return typedb + # TODO: Return ref after merge to master git_repository( name = "typedb_protocol", - remote = "https://github.com/farost/typedb-protocol", - commit = "ab1e84f24e861437d00bbb6082ae282e50860763", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_protocol + remote = "https://github.com/typedb/typedb-protocol", + commit = "94560a87db703ba1686fa830d12d095dc8ab2795", # 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 -# native.local_repository( -# name = "typedb_behaviour", -# path = "../typedb-behaviour", -# ) git_repository( name = "typedb_behaviour", remote = "https://github.com/typedb/typedb-behaviour", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index b8f2bfef2b..0e6817c427 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -60,7 +60,7 @@ [dependencies.typedb-protocol] features = [] - rev = "ab1e84f24e861437d00bbb6082ae282e50860763" + rev = "94560a87db703ba1686fa830d12d095dc8ab2795" git = "https://github.com/typedb/typedb-protocol" default-features = false diff --git a/rust/src/connection/network/proto/server.rs b/rust/src/connection/network/proto/server.rs index 1f15a0db43..ec6cf43fa6 100644 --- a/rust/src/connection/network/proto/server.rs +++ b/rust/src/connection/network/proto/server.rs @@ -18,7 +18,7 @@ */ use typedb_protocol::{ - server::{version::Res as VersionProto, ReplicationStatus as ReplicationStatusProto}, + server::{version::Res as VersionProto, ReplicaStatus as ReplicaStatusProto}, Server as ServerProto, }; @@ -26,7 +26,7 @@ use super::TryFromProto; use crate::{ common::Result, connection::{ - server::server_replica::{ReplicaType, ReplicationStatus, ServerReplica}, + server::server_replica::{ReplicaType, ReplicaStatus, ServerReplica}, ServerVersion, }, error::ConnectionError, @@ -35,16 +35,16 @@ use crate::{ impl TryFromProto for ServerReplica { fn try_from_proto(proto: ServerProto) -> Result { let address = proto.address.parse()?; - let replication_status = match proto.replication_status { - Some(replication_status) => ReplicationStatus::try_from_proto(replication_status)?, - None => ReplicationStatus::default(), + let replica_status = match proto.replica_status { + Some(replica_status) => ReplicaStatus::try_from_proto(replica_status)?, + None => ReplicaStatus::default(), }; - Ok(Self::from_private(address, replication_status)) + Ok(Self::from_private(address, replica_status)) } } -impl TryFromProto for ReplicationStatus { - fn try_from_proto(proto: ReplicationStatusProto) -> Result { +impl TryFromProto for ReplicaStatus { + fn try_from_proto(proto: ReplicaStatusProto) -> Result { Ok(Self { replica_type: ReplicaType::try_from_proto(proto.replica_type)?, term: proto.term }) } } diff --git a/rust/src/connection/server/server_replica.rs b/rust/src/connection/server/server_replica.rs index 01152060b1..5346cfaf8e 100644 --- a/rust/src/connection/server/server_replica.rs +++ b/rust/src/connection/server/server_replica.rs @@ -25,12 +25,12 @@ use crate::common::address::Address; pub struct ServerReplica { private_address: Address, public_address: Option
, - replication_status: ReplicationStatus, + replica_status: ReplicaStatus, } impl ServerReplica { - pub(crate) fn from_private(private_address: Address, replication_status: ReplicationStatus) -> Self { - Self { private_address, public_address: None, replication_status } + 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, address_translation: &HashMap) -> bool { @@ -62,7 +62,7 @@ impl ServerReplica { /// Returns whether this is the primary replica of the raft cluster or any of the supporting types. pub fn replica_type(&self) -> ReplicaType { - self.replication_status.replica_type + self.replica_status.replica_type } /// Checks whether this is the primary replica of the raft cluster. @@ -72,20 +72,20 @@ impl ServerReplica { /// Returns the raft protocol ‘term’ of this replica. pub fn term(&self) -> i64 { - self.replication_status.term + 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 ReplicationStatus { +pub(crate) struct ReplicaStatus { /// The role of this replica in the raft cluster. pub replica_type: ReplicaType, /// The raft protocol ‘term’ of this server replica. pub term: i64, } -impl Default for ReplicationStatus { +impl Default for ReplicaStatus { fn default() -> Self { Self { replica_type: ReplicaType::Primary, term: 0 } } From 86d66a1962660805c06eb14c6fd4b8b808449810 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Tue, 1 Jul 2025 12:15:11 +0100 Subject: [PATCH 16/35] Temporarily update test server scripts. Enhance errors. Fix encryption for clusters --- .circleci/config.yml | 37 +++++----- dependencies/typedb/artifacts.bzl | 7 +- rust/src/common/address.rs | 21 +++++- rust/src/common/error.rs | 6 +- rust/src/connection/server/server_manager.rs | 60 ++++++++++----- rust/src/connection/server/server_replica.rs | 15 ++-- tool/test/BUILD | 27 +++---- tool/test/EchoJavaHome.java | 26 ------- tool/test/start-cluster-servers.sh | 78 +++++++++++--------- tool/test/start-community-server.sh | 6 +- tool/test/stop-cluster-servers.sh | 12 +-- 11 files changed, 155 insertions(+), 140 deletions(-) delete mode 100644 tool/test/EchoJavaHome.java 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/dependencies/typedb/artifacts.bzl b/dependencies/typedb/artifacts.bzl index 8812c25fe9..dca5d00cec 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 = "cb947d4c25c77d4bfdaaf8d650a3658e1f917154" ) #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/rust/src/common/address.rs b/rust/src/common/address.rs index d9c9939b13..7aa6f3b82f 100644 --- a/rust/src/common/address.rs +++ b/rust/src/common/address.rs @@ -18,7 +18,6 @@ */ use std::{fmt, str::FromStr}; - use http::Uri; use crate::{ @@ -32,9 +31,25 @@ pub struct Address { } 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 +59,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 +76,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/error.rs b/rust/src/common/error.rs index 24d084cfdd..36f254adb9 100644 --- a/rust/src/common/error.rs +++ b/rust/src/common/error.rs @@ -129,8 +129,8 @@ 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 { configured_addresses: Addresses, accessed_addresses: Addresses } = - 2: "Unable to connect to TypeDB server(s).\nInitially configured addresses: {configured_addresses}.\nTried accessing addresses: {accessed_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}", ServerConnectionFailedNetworking { error: String } = @@ -197,6 +197,8 @@ error_messages! { ConnectionError 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}.", } error_messages! { ConceptError diff --git a/rust/src/connection/server/server_manager.rs b/rust/src/connection/server/server_manager.rs index cd24fe6cdb..70936f9c33 100644 --- a/rust/src/connection/server/server_manager.rs +++ b/rust/src/connection/server/server_manager.rs @@ -44,6 +44,7 @@ pub(crate) struct ServerManager { configured_addresses: Addresses, replicas: RwLock>, server_connections: RwLock>, + connection_scheme: http::uri::Scheme, // public - private address_translation: HashMap, @@ -67,9 +68,26 @@ impl ServerManager { 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(), @@ -83,6 +101,7 @@ impl ServerManager { configured_addresses: addresses, replicas: RwLock::new(replicas), server_connections: RwLock::new(source_connections), + connection_scheme, address_translation, background_runtime, credentials, @@ -121,7 +140,7 @@ impl ServerManager { server_connections.extend(new_server_connections); if server_connections.is_empty() { - Err(self.server_connection_failed_err(None)) + Err(self.server_connection_failed_err(None, connection_errors)) } else { Ok(()) } @@ -218,14 +237,15 @@ impl ServerManager { None => self.seek_primary_replica_in(self.replicas()).await?, }; - for _ in 0..=self.driver_options.redirect_failover_retries { + let retries = self.driver_options.redirect_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 { + 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); @@ -237,6 +257,8 @@ impl ServerManager { } }; + connection_errors.push(connection_error.into()); + if primary_replica.private_address() == &private_address { break; } @@ -244,7 +266,7 @@ impl ServerManager { res => return res, } } - Err(self.server_connection_failed_err(None)) + Err(self.server_connection_failed_err(None, connection_errors)) } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] @@ -264,6 +286,7 @@ impl ServerManager { P: Future>, { let limit = self.driver_options.discovery_failover_retries.unwrap_or(usize::MAX); + let mut connection_errors = Vec::new(); for (attempt, replica) in enumerate(replicas.into_iter()) { if attempt == limit { break; @@ -274,11 +297,12 @@ impl ServerManager { } Err(Error::Connection(err)) => { debug!("Unable to connect to {}: {err:?}. Attempting next server.", replica.address()); + connection_errors.push(err.into()); } res => return res, } } - Err(self.server_connection_failed_err(None)) + Err(self.server_connection_failed_err(None, connection_errors)) } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] @@ -299,7 +323,7 @@ impl ServerManager { None => match require_connected { false => self.record_new_server_connection(public_address.clone(), private_address.clone()).await?, true => { - return Err(self.server_connection_failed_err(Some(Addresses::from_address(public_address.clone())))) + return Err(self.server_connection_failed_err(Some(Addresses::from_address(public_address.clone())), Vec::default())) } }, }; @@ -325,7 +349,7 @@ impl ServerManager { self.update_server_connections().await?; Ok(replica) } else { - Err(self.server_connection_failed_err(None)) + Err(self.server_connection_failed_err(None, Vec::default())) } } @@ -333,17 +357,15 @@ impl ServerManager { 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, Vec)> { - if !use_replication && addresses.len() > 1 { - return Err(ConnectionError::MultipleAddressesForNoReplicationMode { addresses: addresses.clone() }.into()); - } - let address_translation = addresses.address_translation(); + let mut errors = Vec::with_capacity(addresses.len()); for address in addresses.addresses() { let server_connection = ServerConnection::new( background_runtime.clone(), @@ -357,14 +379,14 @@ impl ServerManager { match server_connection { Ok((server_connection, replicas)) => { debug!("Fetched replicas from configured address '{address}': {replicas:?}"); - let translated_replicas = Self::translate_replicas(replicas, &address_translation); + 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(), server_connection); return Ok((source_connections, translated_replicas)); } else { if let Some(target_replica) = - translated_replicas.into_iter().find(|replica| replica.address() == address) + translated_replicas.into_iter().find(|replica| {replica.address() == address}) { let source_connections = HashMap::from([(address.clone(), server_connection)]); return Ok((source_connections, vec![target_replica])); @@ -373,6 +395,7 @@ impl ServerManager { } Err(Error::Connection(err)) => { debug!("Unable to fetch replicas from {}: {err:?}. Attempting next server.", address); + errors.push(err); } Err(err) => return Err(err), } @@ -380,6 +403,7 @@ impl ServerManager { Err(ConnectionError::ServerConnectionFailed { configured_addresses: addresses.clone(), accessed_addresses: addresses.clone(), + details: errors.into_iter().join(";\n"), } .into()) } @@ -389,23 +413,25 @@ impl ServerManager { server_connection .servers_all() .await - .map(|replicas| Self::translate_replicas(replicas, &self.address_translation)) + .map(|replicas| Self::translate_replicas(replicas, &self.connection_scheme, &self.address_translation)) } fn translate_replicas( replicas: impl IntoIterator, + connection_scheme: &http::uri::Scheme, address_translation: &HashMap, ) -> Vec { - replicas.into_iter().map(|replica| replica.translated(address_translation)).collect() + replicas.into_iter().map(|replica| replica.translated(connection_scheme, address_translation)).collect() } - fn server_connection_failed_err(&self, accessed_addresses: Option) -> Error { + 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() } diff --git a/rust/src/connection/server/server_replica.rs b/rust/src/connection/server/server_replica.rs index 5346cfaf8e..8d5590ac06 100644 --- a/rust/src/connection/server/server_replica.rs +++ b/rust/src/connection/server/server_replica.rs @@ -17,7 +17,7 @@ * under the License. */ use std::collections::HashMap; - +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use crate::common::address::Address; /// The metadata and state of an individual raft replica of a driver connection. @@ -33,21 +33,22 @@ impl ServerReplica { Self { private_address, public_address: None, replica_status } } - pub(crate) fn translate_address(&mut self, address_translation: &HashMap) -> bool { + pub(crate) fn translate_address(&mut self, connection_scheme: &http::uri::Scheme, address_translation: &HashMap) { if let Some(translated) = address_translation .iter() .find(|(_, private)| private == &self.private_address()) .map(|(public, _)| public.clone()) { self.public_address = Some(translated); - true - } else { - false + } 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, address_translation: &HashMap) -> Self { - self.translate_address(address_translation); + pub(crate) fn translated(mut self, connection_scheme: &http::uri::Scheme, address_translation: &HashMap) -> Self { + self.translate_address(connection_scheme, address_translation); self } 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/EchoJavaHome.java b/tool/test/EchoJavaHome.java deleted file mode 100644 index 42c783955a..0000000000 --- a/tool/test/EchoJavaHome.java +++ /dev/null @@ -1,26 +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. - */ - -package com.typedb.driver.tool.test; - -public class EchoJavaHome { - public static void main(String[] args) { - System.out.println(System.getProperty("java.home")); - } -} diff --git a/tool/test/start-cluster-servers.sh b/tool/test/start-cluster-servers.sh index 9a43255332..580ef405bd 100755 --- a/tool/test/start-cluster-servers.sh +++ b/tool/test/start-cluster-servers.sh @@ -18,59 +18,65 @@ set -e -export BAZEL_JAVA_HOME=$(bazel run //tool/test:echo-java-home) NODE_COUNT=${1:-1} -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=0.0.0.0:${1}1729 \ + --server.encryption.enabled true \ + --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. +ROOT_CA=`realpath tool/test/resources/encryption/ext-grpc-root-ca.pem` +export ROOT_CA + +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 -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 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 +88,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 From 8b9c4defa8a8ad43ffbd2be65e93cbead7323d74 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Tue, 1 Jul 2025 12:56:30 +0100 Subject: [PATCH 17/35] Introduce cluster bdd tests to Rust with encryption --- .factory/automation.yml | 42 ++++++++++---------- rust/src/common/address.rs | 1 + rust/src/connection/network/proto/server.rs | 2 +- rust/src/connection/server/server_manager.rs | 18 ++++----- rust/src/connection/server/server_replica.rs | 19 +++++++-- rust/tests/behaviour/steps/connection/mod.rs | 1 + rust/tests/behaviour/steps/lib.rs | 26 ++++++------ rust/tests/behaviour/steps/params.rs | 20 +++++----- rust/tests/behaviour/steps/query.rs | 2 +- rust/tests/behaviour/steps/util.rs | 7 +--- tool/test/start-cluster-servers.sh | 8 ++-- 11 files changed, 76 insertions(+), 70 deletions(-) diff --git a/.factory/automation.yml b/.factory/automation.yml index 06d2bc6bea..95f637ec0f 100644 --- a/.factory/automation.yml +++ b/.factory/automation.yml @@ -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 @@ -215,7 +215,7 @@ build: # 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 && + # source 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 @@ -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 @@ -277,7 +277,7 @@ build: # 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 && + # source 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 diff --git a/rust/src/common/address.rs b/rust/src/common/address.rs index 7aa6f3b82f..4b31fc32f8 100644 --- a/rust/src/common/address.rs +++ b/rust/src/common/address.rs @@ -18,6 +18,7 @@ */ use std::{fmt, str::FromStr}; + use http::Uri; use crate::{ diff --git a/rust/src/connection/network/proto/server.rs b/rust/src/connection/network/proto/server.rs index ec6cf43fa6..f112f2036d 100644 --- a/rust/src/connection/network/proto/server.rs +++ b/rust/src/connection/network/proto/server.rs @@ -26,7 +26,7 @@ use super::TryFromProto; use crate::{ common::Result, connection::{ - server::server_replica::{ReplicaType, ReplicaStatus, ServerReplica}, + server::server_replica::{ReplicaStatus, ReplicaType, ServerReplica}, ServerVersion, }, error::ConnectionError, diff --git a/rust/src/connection/server/server_manager.rs b/rust/src/connection/server/server_manager.rs index 70936f9c33..25fb818de6 100644 --- a/rust/src/connection/server/server_manager.rs +++ b/rust/src/connection/server/server_manager.rs @@ -78,11 +78,7 @@ impl ServerManager { return Err(ConnectionError::HttpHttpsMismatch { addresses: addresses.clone() }.into()); } - let connection_scheme = if is_https { - http::uri::Scheme::HTTPS - } else { - http::uri::Scheme::HTTP - }; + 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(), @@ -323,7 +319,10 @@ impl ServerManager { None => match require_connected { false => self.record_new_server_connection(public_address.clone(), private_address.clone()).await?, true => { - return Err(self.server_connection_failed_err(Some(Addresses::from_address(public_address.clone())), Vec::default())) + return Err(self.server_connection_failed_err( + Some(Addresses::from_address(public_address.clone())), + Vec::default(), + )) } }, }; @@ -379,14 +378,15 @@ impl ServerManager { match server_connection { Ok((server_connection, replicas)) => { debug!("Fetched replicas from configured address '{address}': {replicas:?}"); - let translated_replicas = Self::translate_replicas(replicas, connection_scheme, &address_translation); + 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(), server_connection); return Ok((source_connections, translated_replicas)); } else { if let Some(target_replica) = - translated_replicas.into_iter().find(|replica| {replica.address() == address}) + translated_replicas.into_iter().find(|replica| replica.address() == address) { let source_connections = HashMap::from([(address.clone(), server_connection)]); return Ok((source_connections, vec![target_replica])); @@ -431,7 +431,7 @@ impl ServerManager { ConnectionError::ServerConnectionFailed { configured_addresses: self.configured_addresses.clone(), accessed_addresses, - details: errors.into_iter().join(";\n") + details: errors.into_iter().join(";\n"), } .into() } diff --git a/rust/src/connection/server/server_replica.rs b/rust/src/connection/server/server_replica.rs index 8d5590ac06..f766d52008 100644 --- a/rust/src/connection/server/server_replica.rs +++ b/rust/src/connection/server/server_replica.rs @@ -16,8 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -use std::collections::HashMap; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::{ + collections::HashMap, + net::{IpAddr, Ipv4Addr, SocketAddr}, +}; + use crate::common::address::Address; /// The metadata and state of an individual raft replica of a driver connection. @@ -33,7 +36,11 @@ impl ServerReplica { Self { private_address, public_address: None, replica_status } } - pub(crate) fn translate_address(&mut self, connection_scheme: &http::uri::Scheme, address_translation: &HashMap) { + pub(crate) fn translate_address( + &mut self, + connection_scheme: &http::uri::Scheme, + address_translation: &HashMap, + ) { if let Some(translated) = address_translation .iter() .find(|(_, private)| private == &self.private_address()) @@ -47,7 +54,11 @@ impl ServerReplica { } } - pub(crate) fn translated(mut self, connection_scheme: &http::uri::Scheme, address_translation: &HashMap) -> Self { + pub(crate) fn translated( + mut self, + connection_scheme: &http::uri::Scheme, + address_translation: &HashMap, + ) -> Self { self.translate_address(connection_scheme, address_translation); self } diff --git a/rust/tests/behaviour/steps/connection/mod.rs b/rust/tests/behaviour/steps/connection/mod.rs index 67d85db5cb..29ceca4b1b 100644 --- a/rust/tests/behaviour/steps/connection/mod.rs +++ b/rust/tests/behaviour/steps/connection/mod.rs @@ -18,6 +18,7 @@ */ use cucumber::{given, then, when}; +use itertools::Itertools; use macro_rules_attribute::apply; use crate::{assert_with_timeout, generic_step, params, params::check_boolean, Context}; diff --git a/rust/tests/behaviour/steps/lib.rs b/rust/tests/behaviour/steps/lib.rs index 1362b89f89..f94aab55e8 100644 --- a/rust/tests/behaviour/steps/lib.rs +++ b/rust/tests/behaviour/steps/lib.rs @@ -97,7 +97,7 @@ 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 transaction_options: Option, pub query_options: Option, pub driver: Option, @@ -143,7 +143,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); @@ -451,8 +451,8 @@ impl Context { assert!(!self.is_cluster); let addresses = Addresses::try_from_address_str(address).expect("Expected addresses"); let credentials = Credentials::new(username, password); - let conn_settings = DriverOptions::new(); - TypeDBDriver::new(addresses, credentials, conn_settings).await + let driver_options = DriverOptions::new(); + TypeDBDriver::new(addresses, credentials, driver_options).await } async fn create_driver_cluster( @@ -462,14 +462,15 @@ impl Context { 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"); - let addresses = Addresses::try_from_address_str(address).expect("Expected addresses"); + 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(); - TypeDBDriver::new(addresses, credentials, conn_settings).await + assert!(self.tls_root_ca.is_some(), "Root CA is expected for cluster tests!"); + let driver_options = DriverOptions::new() + .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) { @@ -485,10 +486,7 @@ 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, diff --git a/rust/tests/behaviour/steps/params.rs b/rust/tests/behaviour/steps/params.rs index 6890e859c2..d14aad7edc 100644 --- a/rust/tests/behaviour/steps/params.rs +++ b/rust/tests/behaviour/steps/params.rs @@ -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 5c0b8b679f..69a210c121 100644 --- a/rust/tests/behaviour/steps/query.rs +++ b/rust/tests/behaviour/steps/query.rs @@ -18,7 +18,7 @@ */ 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::{ diff --git a/rust/tests/behaviour/steps/util.rs b/rust/tests/behaviour/steps/util.rs index 4e10327308..dd5fae7bf9 100644 --- a/rust/tests/behaviour/steps/util.rs +++ b/rust/tests/behaviour/steps/util.rs @@ -27,12 +27,7 @@ use std::{ path::{Path, PathBuf}, }; -use cucumber::{ - gherkin::{Feature, Step}, - given, then, when, StatsWriter, World, -}; -use futures::stream::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::JSON, Result as TypeDBResult}; diff --git a/tool/test/start-cluster-servers.sh b/tool/test/start-cluster-servers.sh index 580ef405bd..fa5398fa53 100755 --- a/tool/test/start-cluster-servers.sh +++ b/tool/test/start-cluster-servers.sh @@ -30,7 +30,7 @@ NODE_COUNT=${1:-1} function server_start() { ./${1}/typedb server \ - --server.address=0.0.0.0:${1}1729 \ + --server.address=127.0.0.1:${1}1729 \ --server.encryption.enabled true \ --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` \ @@ -58,9 +58,6 @@ 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 -ROOT_CA=`realpath tool/test/resources/encryption/ext-grpc-root-ca.pem` -export ROOT_CA - echo Successfully unarchived a TypeDB distribution. Creating $NODE_COUNT copies ${1}. for i in $(seq 1 $NODE_COUNT); do cp -r typedb-cluster-all $i || exit 1 @@ -70,6 +67,9 @@ 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 From 9a4a4482af8e54299b8f0f8e5f61a73c022262a7 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Tue, 1 Jul 2025 16:23:59 +0100 Subject: [PATCH 18/35] Refactor address translation --- .factory/automation.yml | 2 +- .../connection/ConsistencyLevel.Eventual.adoc | 52 +++ .../ConsistencyLevel.ReplicaDependant.adoc | 80 ++++ .../connection/ConsistencyLevel.Strong.adoc | 52 +++ .../java/connection/ConsistencyLevel.adoc | 52 +++ .../java/connection/DatabaseManager.adoc | 51 ++- .../ROOT/partials/java/connection/Driver.adoc | 46 +++ .../partials/java/connection/ReplicaType.adoc | 138 +++++++ .../java/connection/ServerReplica.adoc | 55 +++ .../java/connection/ServerVersion.adoc | 62 ++++ .../python/connection/DatabaseManager.adoc | 6 +- .../python/connection/UserManager.adoc | 6 +- .../ROOT/partials/python/value/Datetime.adoc | 32 +- .../partials/rust/answer/ConceptDocument.adoc | 2 +- .../ROOT/partials/rust/answer/ConceptRow.adoc | 4 +- .../partials/rust/answer/QueryAnswer.adoc | 8 +- .../ROOT/partials/rust/concept/Concept.adoc | 40 +- .../partials/rust/connection/Addresses.adoc | 231 ++++++++++++ .../rust/connection/ConsistencyLevel.adoc | 18 + .../partials/rust/connection/Database.adoc | 242 +++++++++--- .../rust/connection/DatabaseManager.adoc | 247 +++++++++++- .../rust/connection/DriverOptions.adoc | 94 ++++- .../partials/rust/connection/ReplicaInfo.adoc | 23 -- .../partials/rust/connection/ReplicaType.adoc | 17 + .../rust/connection/ServerReplica.adoc | 85 +++++ .../rust/connection/ServerVersion.adoc | 47 +++ .../rust/connection/TypeDBDriver.adoc | 261 ++++++++++++- .../ROOT/partials/rust/connection/User.adoc | 71 ++-- .../partials/rust/connection/UserManager.adoc | 351 +++++++++++++++++- .../partials/rust/errors/ConnectionError.adoc | 17 +- .../partials/rust/errors/InternalError.adoc | 2 - .../rust/transaction/Transaction.adoc | 11 +- .../rust/transaction/TransactionOptions.adoc | 21 +- .../ROOT/partials/rust/value/Decimal.adoc | 45 +-- java/docs_structure.bzl | 8 +- python/docs_structure.bzl | 7 +- rust/docs_structure.bzl | 10 +- rust/src/common/{ => address}/address.rs | 0 .../src/common/address/address_translation.rs | 44 +++ .../server => common/address}/addresses.rs | 14 +- rust/src/common/address/mod.rs | 25 ++ rust/src/common/error.rs | 2 +- rust/src/common/mod.rs | 1 + rust/src/connection/message.rs | 2 +- rust/src/connection/mod.rs | 2 +- rust/src/connection/network/proto/concept.rs | 1 - rust/src/connection/network/proto/message.rs | 4 +- rust/src/connection/network/proto/server.rs | 4 +- rust/src/connection/server/mod.rs | 5 +- .../connection/server/server_connection.rs | 5 +- rust/src/connection/server/server_manager.rs | 17 +- rust/src/connection/server/server_replica.rs | 17 +- rust/src/driver.rs | 5 +- rust/src/lib.rs | 8 +- 54 files changed, 2340 insertions(+), 312 deletions(-) create mode 100644 docs/modules/ROOT/partials/java/connection/ConsistencyLevel.Eventual.adoc create mode 100644 docs/modules/ROOT/partials/java/connection/ConsistencyLevel.ReplicaDependant.adoc create mode 100644 docs/modules/ROOT/partials/java/connection/ConsistencyLevel.Strong.adoc create mode 100644 docs/modules/ROOT/partials/java/connection/ConsistencyLevel.adoc create mode 100644 docs/modules/ROOT/partials/java/connection/ReplicaType.adoc create mode 100644 docs/modules/ROOT/partials/java/connection/ServerReplica.adoc create mode 100644 docs/modules/ROOT/partials/java/connection/ServerVersion.adoc create mode 100644 docs/modules/ROOT/partials/rust/connection/Addresses.adoc create mode 100644 docs/modules/ROOT/partials/rust/connection/ConsistencyLevel.adoc delete mode 100644 docs/modules/ROOT/partials/rust/connection/ReplicaInfo.adoc create mode 100644 docs/modules/ROOT/partials/rust/connection/ReplicaType.adoc create mode 100644 docs/modules/ROOT/partials/rust/connection/ServerReplica.adoc create mode 100644 docs/modules/ROOT/partials/rust/connection/ServerVersion.adoc rename rust/src/common/{ => address}/address.rs (100%) create mode 100644 rust/src/common/address/address_translation.rs rename rust/src/{connection/server => common/address}/addresses.rs (95%) create mode 100644 rust/src/common/address/mod.rs diff --git a/.factory/automation.yml b/.factory/automation.yml index 95f637ec0f..221b949adf 100644 --- a/.factory/automation.yml +++ b/.factory/automation.yml @@ -88,7 +88,7 @@ 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" + echo "Failed to verify README files: please update it manually and verify the changes" exit 1 } 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..8d9466113a --- /dev/null +++ b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.Eventual.adoc @@ -0,0 +1,52 @@ +[#_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_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..32bd2ef84b --- /dev/null +++ b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.ReplicaDependant.adoc @@ -0,0 +1,80 @@ +[#_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() +---- + + + +[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_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..7c56e284e4 --- /dev/null +++ b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.Strong.adoc @@ -0,0 +1,52 @@ +[#_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_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..9a2fb7fc74 --- /dev/null +++ b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.adoc @@ -0,0 +1,52 @@ +[#_ConsistencyLevel] +=== ConsistencyLevel + +*Package*: `com.typedb.driver.api` + +Consistency levels of operations against a distributed database. 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` + +// end::methods[] + diff --git a/docs/modules/ROOT/partials/java/connection/DatabaseManager.adoc b/docs/modules/ROOT/partials/java/connection/DatabaseManager.adoc index 091d097210..5b35c487f2 100644 --- a/docs/modules/ROOT/partials/java/connection/DatabaseManager.adoc +++ b/docs/modules/ROOT/partials/java/connection/DatabaseManager.adoc @@ -12,13 +12,54 @@ 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. + + + +See also: ``#allWithConsistency(ConsistencyLevel)`` + + +[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. + +See also: <<#_all_,``all()``>> + + +[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 +68,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] @@ -72,7 +113,7 @@ void create​(java.lang.String name) throws TypeDBDriverException ---- -Create a database with the given name. +Creates a database with the given name. [caption=""] @@ -105,7 +146,7 @@ Database get​(java.lang.String name) throws TypeDBDriverException ---- -Retrieve the database with the given name. +Retrieves the database with the given name. [caption=""] @@ -139,7 +180,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..41ed1db7a9 100644 --- a/docs/modules/ROOT/partials/java/connection/Driver.adoc +++ b/docs/modules/ROOT/partials/java/connection/Driver.adoc @@ -79,6 +79,52 @@ 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_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_transaction_java_lang_String_Transaction_Type] ==== transaction 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..39558b9778 --- /dev/null +++ b/docs/modules/ROOT/partials/java/connection/ServerReplica.adoc @@ -0,0 +1,55 @@ +[#_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() +---- + +The address this replica is hosted at. + +[caption=""] +.Returns +`java.lang.String` + +[#_ServerReplica_getTerm_] +==== getTerm + +[source,java] +---- +@CheckReturnValue +long getTerm() +---- + +The raft protocol ‘term’ of this replica. + +[caption=""] +.Returns +`long` + +[#_ServerReplica_getType_] +==== getType + +[source,java] +---- +@CheckReturnValue +ReplicaType getType() +---- + +Gets the type of this replica: whether it's a primary or a secondary replica. + +[caption=""] +.Returns +`ReplicaType` + +// 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..97d5722acc --- /dev/null +++ b/docs/modules/ROOT/partials/java/connection/ServerVersion.adoc @@ -0,0 +1,62 @@ +[#_ServerVersion] +=== ServerVersion + +*Package*: `com.typedb.driver.api.server` + +Type of replica. + + +[caption=""] +.Examples +[source,java] +---- +replica.type(); +---- + +// 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/python/connection/DatabaseManager.adoc b/docs/modules/ROOT/partials/python/connection/DatabaseManager.adoc index 71dbdf44ef..a9a713d768 100644 --- a/docs/modules/ROOT/partials/python/connection/DatabaseManager.adoc +++ b/docs/modules/ROOT/partials/python/connection/DatabaseManager.adoc @@ -63,7 +63,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 @@ -93,7 +93,7 @@ driver.databases.create(name) get(name: str) -> Database ---- -Retrieve the database with the given name. +Retrieves the database with the given name. [caption=""] .Input parameters @@ -123,7 +123,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/UserManager.adoc b/docs/modules/ROOT/partials/python/connection/UserManager.adoc index 77bd1e2e9c..5e1b364808 100644 --- a/docs/modules/ROOT/partials/python/connection/UserManager.adoc +++ b/docs/modules/ROOT/partials/python/connection/UserManager.adoc @@ -63,7 +63,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 @@ -94,7 +94,7 @@ driver.users.create(username, password) get(username: str) -> User | None ---- -Retrieve a user with the given name. +Retrieves a user with the given name. [caption=""] .Input parameters @@ -124,7 +124,7 @@ driver.users.get(username) get_current_user() -> 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=""] .Returns 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 index 9da44627b7..e6f38521b4 100644 --- a/docs/modules/ROOT/partials/rust/answer/ConceptDocument.adoc +++ b/docs/modules/ROOT/partials/rust/answer/ConceptDocument.adoc @@ -30,7 +30,7 @@ a| `root` a| `Option` a| pub fn get_query_type(&self) -> QueryType ---- -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). [caption=""] .Returns diff --git a/docs/modules/ROOT/partials/rust/answer/ConceptRow.adoc b/docs/modules/ROOT/partials/rust/answer/ConceptRow.adoc index 78ad2c27b1..1931cfffd1 100644 --- a/docs/modules/ROOT/partials/rust/answer/ConceptRow.adoc +++ b/docs/modules/ROOT/partials/rust/answer/ConceptRow.adoc @@ -64,7 +64,7 @@ concept_row.get(var_name) pub fn get_column_names(&self) -> &[String] ---- -Retrieve the row column names (shared by all elements in this stream). +Retrieves the row column names (shared by all elements in this stream). [caption=""] .Returns @@ -145,7 +145,7 @@ concept_row.get_position(column_index) pub fn get_query_type(&self) -> QueryType ---- -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). [caption=""] .Returns diff --git a/docs/modules/ROOT/partials/rust/answer/QueryAnswer.adoc b/docs/modules/ROOT/partials/rust/answer/QueryAnswer.adoc index a08f3fc6ce..c5d6f5ac47 100644 --- a/docs/modules/ROOT/partials/rust/answer/QueryAnswer.adoc +++ b/docs/modules/ROOT/partials/rust/answer/QueryAnswer.adoc @@ -23,7 +23,7 @@ a| `Ok(QueryType)` pub fn get_query_type(&self) -> QueryType ---- -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). [caption=""] .Returns @@ -95,7 +95,7 @@ query_answer.into_rows() pub fn is_document_stream(&self) -> bool ---- -Check if the ``QueryAnswer`` is a ``ConceptDocumentStream``. +Checks if the ``QueryAnswer`` is a ``ConceptDocumentStream``. [caption=""] .Returns @@ -119,7 +119,7 @@ query_answer.is_document_stream() pub fn is_ok(&self) -> bool ---- -Check if the ``QueryAnswer`` is an ``Ok`` response. +Checks if the ``QueryAnswer`` is an ``Ok`` response. [caption=""] .Returns @@ -143,7 +143,7 @@ query_answer.is_ok() pub fn is_row_stream(&self) -> bool ---- -Check if the ``QueryAnswer`` is a ``ConceptRowStream``. +Checks if the ``QueryAnswer`` is a ``ConceptRowStream``. [caption=""] .Returns diff --git a/docs/modules/ROOT/partials/rust/concept/Concept.adoc b/docs/modules/ROOT/partials/rust/concept/Concept.adoc index 7784cace50..c4300be8f8 100644 --- a/docs/modules/ROOT/partials/rust/concept/Concept.adoc +++ b/docs/modules/ROOT/partials/rust/concept/Concept.adoc @@ -64,7 +64,7 @@ Retrieves the label of this Concept. If this is an Instance, returns the label o pub fn is_attribute(&self) -> bool ---- -Check if this Concept represents an Attribute instance from the database +Checks if this Concept represents an Attribute instance from the database [caption=""] .Returns @@ -81,7 +81,7 @@ bool pub fn is_attribute_type(&self) -> bool ---- -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 [caption=""] .Returns @@ -98,7 +98,7 @@ bool pub fn is_boolean(&self) -> bool ---- -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 [caption=""] .Returns @@ -115,7 +115,7 @@ bool pub fn is_date(&self) -> bool ---- -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 [caption=""] .Returns @@ -132,7 +132,7 @@ bool pub fn is_datetime(&self) -> bool ---- -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 [caption=""] .Returns @@ -149,7 +149,7 @@ bool pub fn is_datetime_tz(&self) -> bool ---- -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 [caption=""] .Returns @@ -166,7 +166,7 @@ bool pub fn is_decimal(&self) -> bool ---- -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 [caption=""] .Returns @@ -183,7 +183,7 @@ bool pub fn is_double(&self) -> bool ---- -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 [caption=""] .Returns @@ -200,7 +200,7 @@ bool pub fn is_duration(&self) -> bool ---- -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 [caption=""] .Returns @@ -217,7 +217,7 @@ bool pub fn is_entity(&self) -> bool ---- -Check if this Concept represents an Entity instance from the database +Checks if this Concept represents an Entity instance from the database [caption=""] .Returns @@ -234,7 +234,7 @@ bool pub fn is_entity_type(&self) -> bool ---- -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 [caption=""] .Returns @@ -251,7 +251,7 @@ bool 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 +Checks if this Concept represents a stored database instance from the database. These are exactly: Entity, Relation, and Attribute Equivalent to: @@ -277,7 +277,7 @@ concept.is_entity() || concept.is_relation() || concept.is_attribute() pub fn is_integer(&self) -> bool ---- -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 [caption=""] .Returns @@ -294,7 +294,7 @@ bool pub fn is_relation(&self) -> bool ---- -Check if this Concept represents an Relation instance from the database +Checks if this Concept represents an Relation instance from the database [caption=""] .Returns @@ -311,7 +311,7 @@ bool pub fn is_relation_type(&self) -> bool ---- -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 [caption=""] .Returns @@ -328,7 +328,7 @@ bool pub fn is_role_type(&self) -> bool ---- -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 [caption=""] .Returns @@ -345,7 +345,7 @@ bool pub fn is_string(&self) -> bool ---- -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 [caption=""] .Returns @@ -362,7 +362,7 @@ bool pub fn is_struct(&self) -> bool ---- -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 [caption=""] .Returns @@ -379,7 +379,7 @@ bool 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 +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: @@ -405,7 +405,7 @@ concept.is_entity_type() || concept.is_relation_type() || concept.is_role_type() pub fn is_value(&self) -> bool ---- -Check if this Concept represents a Value returned by the database +Checks if this Concept represents a Value returned by the database [caption=""] .Returns diff --git a/docs/modules/ROOT/partials/rust/connection/Addresses.adoc b/docs/modules/ROOT/partials/rust/connection/Addresses.adoc new file mode 100644 index 0000000000..c77f300d74 --- /dev/null +++ b/docs/modules/ROOT/partials/rust/connection/Addresses.adoc @@ -0,0 +1,231 @@ +[#_enum_Addresses] +=== Addresses + +A collection of server addresses used for connection. + +[caption=""] +.Enum variants +// tag::enum_constants[] +[cols=""] +[options="header"] +|=== +|Variant +a| `Direct(Vec
)` +a| `Translated(HashMap)` +|=== +// end::enum_constants[] + +// tag::methods[] +[#_enum_Addresses_contains_] +==== contains + +[source,rust] +---- +pub fn contains(&self, address: &Address) -> bool +---- + +Checks if the public address is a part of the addresses. + +[caption=""] +.Returns +[source,rust] +---- +bool +---- + +[caption=""] +.Code examples +[source,rust] +---- +addresses.contains(&address) +---- + +[#_enum_Addresses_from_address_] +==== from_address + +[source,rust] +---- +pub fn from_address(address: Address) -> Self +---- + +Prepare addresses based on a single TypeDB address. + +[caption=""] +.Returns +[source,rust] +---- +Self +---- + +[caption=""] +.Code examples +[source,rust] +---- +let address = "127.0.0.1:11729".parse().unwrap(); +Addresses::from_address(address) +---- + +[#_enum_Addresses_from_addresses_] +==== from_addresses + +[source,rust] +---- +pub fn from_addresses(addresses: impl IntoIterator) -> Self +---- + +Prepare addresses based on multiple TypeDB addresses. + +[caption=""] +.Returns +[source,rust] +---- +Self +---- + +[caption=""] +.Code examples +[source,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]) +---- + +[#_enum_Addresses_from_translation_] +==== from_translation + +[source,rust] +---- +pub fn from_translation(addresses: HashMap) -> Self +---- + +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). + +[caption=""] +.Returns +[source,rust] +---- +Self +---- + +[caption=""] +.Code examples +[source,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) +---- + +[#_enum_Addresses_len_] +==== len + +[source,rust] +---- +pub fn len(&self) -> usize +---- + +Returns the number of address entries (addresses or address pairs) in the collection. + +[caption=""] +.Returns +[source,rust] +---- +usize +---- + +[caption=""] +.Code examples +[source,rust] +---- +addresses.len() +---- + +[#_enum_Addresses_try_from_address_str_] +==== try_from_address_str + +[source,rust] +---- +pub fn try_from_address_str(address_str: impl AsRef) -> Result +---- + +Prepare addresses based on a single “host:port” string. + +[caption=""] +.Returns +[source,rust] +---- +Result +---- + +[caption=""] +.Code examples +[source,rust] +---- +Addresses::try_from_address_str("127.0.0.1:11729") +---- + +[#_enum_Addresses_try_from_addresses_str_] +==== try_from_addresses_str + +[source,rust] +---- +pub fn try_from_addresses_str( + addresses_str: impl IntoIterator>, +) -> Result +---- + +Prepare addresses based on multiple “host:port” strings. Is used to specify multiple addresses connect to. + +[caption=""] +.Returns +[source,rust] +---- +Result +---- + +[caption=""] +.Code examples +[source,rust] +---- +Addresses::try_from_addresses_str(["127.0.0.1:11729", "127.0.0.1:11730", "127.0.0.1:11731"]) +---- + +[#_enum_Addresses_try_from_translation_str_] +==== try_from_translation_str + +[source,rust] +---- +pub fn try_from_translation_str( + addresses_str: HashMap, impl AsRef>, +) -> Result +---- + +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). + +[caption=""] +.Returns +[source,rust] +---- +Result +---- + +[caption=""] +.Code examples +[source,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() +) +---- + +// end::methods[] + diff --git a/docs/modules/ROOT/partials/rust/connection/ConsistencyLevel.adoc b/docs/modules/ROOT/partials/rust/connection/ConsistencyLevel.adoc new file mode 100644 index 0000000000..493ca68876 --- /dev/null +++ b/docs/modules/ROOT/partials/rust/connection/ConsistencyLevel.adoc @@ -0,0 +1,18 @@ +[#_enum_ConsistencyLevel] +=== ConsistencyLevel + +Consistency levels of operations against a distributed database. 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. + +[caption=""] +.Enum variants +// tag::enum_constants[] +[cols=""] +[options="header"] +|=== +|Variant +a| `Eventual` +a| `ReplicaDependant` +a| `Strong` +|=== +// end::enum_constants[] + diff --git a/docs/modules/ROOT/partials/rust/connection/Database.adoc b/docs/modules/ROOT/partials/rust/connection/Database.adoc index 065e60ae72..0152f99ea4 100644 --- a/docs/modules/ROOT/partials/rust/connection/Database.adoc +++ b/docs/modules/ROOT/partials/rust/connection/Database.adoc @@ -3,9 +3,10 @@ *Implements traits:* +* `Clone` * `Debug` -A TypeDB database +A TypeDB database. // tag::methods[] [#_struct_Database_delete_] @@ -34,7 +35,7 @@ pub fn delete(self: Arc) -> Result -- ==== -Deletes this database. +Deletes this database. Always uses strong consistency. [caption=""] .Returns @@ -68,7 +69,7 @@ database.delete(); -- ==== -[#_struct_Database_export_to_file_schema_file_path_impl_AsRef_Path_data_file_path_impl_AsRef_Path_] +[#_struct_Database_export_to_file_] ==== export_to_file [tabs] @@ -102,6 +103,78 @@ pub fn export_to_file( -- ==== +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 <<#_struct_Database_method_export_to_file_with_consistency,`Self::export_to_file_with_consistency`>> for more details and options. + +[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_export_to_file_with_consistency_schema_file_path_impl_AsRef_Path_data_file_path_impl_AsRef_Path_consistency_level_ConsistencyLevel] +==== export_to_file_with_consistency + +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +pub async fn export_to_file_with_consistency( + &self, + schema_file_path: impl AsRef, + data_file_path: impl AsRef, + consistency_level: ConsistencyLevel, +) -> Result +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +pub fn export_to_file_with_consistency( + &self, + schema_file_path: impl AsRef, + data_file_path: impl AsRef, + consistency_level: ConsistencyLevel, +) -> 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=""] @@ -112,6 +185,7 @@ Export a database into a schema definition and a data files saved to the disk. T |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` +a| `consistency_level` a| — The consistency level to use for the operation a| `ConsistencyLevel` |=== [caption=""] @@ -130,7 +204,7 @@ async:: -- [source,rust] ---- -database.export_to_file(schema_path, data_path).await; +database.export_to_file_with_consistency(schema_path, data_path, ConsistencyLevel::Strong).await; ---- -- @@ -140,7 +214,7 @@ sync:: -- [source,rust] ---- -database.export_to_file(schema_path, data_path); +database.export_to_file_with_consistency(schema_path, data_path, ConsistencyLevel::Strong); ---- -- @@ -163,80 +237,145 @@ Retrieves the database name as a string. &str ---- -[#_struct_Database_preferred_replica_info_] -==== preferred_replica_info +[#_struct_Database_schema_] +==== schema +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +pub async fn schema(&self) -> Result +---- + +-- + +sync:: ++ +-- [source,rust] ---- -pub fn preferred_replica_info(&self) -> Option +pub fn schema(&self) -> Result ---- -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, using default strong consistency. + +See <<#_struct_Database_method_schema_with_consistency,`Self::schema_with_consistency`>> for more details and options. [caption=""] .Returns [source,rust] ---- -Option +Result ---- [caption=""] .Code examples +[tabs] +==== +async:: ++ +-- [source,rust] ---- -database.preferred_replica_info(); +database.schema().await; ---- -[#_struct_Database_primary_replica_info_] -==== primary_replica_info +-- +sync:: ++ +-- [source,rust] ---- -pub fn primary_replica_info(&self) -> Option +database.schema(); ---- -Returns the primary replica for this database. _Only works in TypeDB Cloud / Enterprise_ +-- +==== -[caption=""] -.Returns -[source,rust] ----- -Option ----- +[#_struct_Database_schema_with_consistency_consistency_level_ConsistencyLevel] +==== schema_with_consistency -[caption=""] -.Code examples +[tabs] +==== +async:: ++ +-- [source,rust] ---- -database.primary_replica_info() +pub async fn schema_with_consistency( + &self, + consistency_level: ConsistencyLevel, +) -> Result ---- -[#_struct_Database_replicas_info_] -==== replicas_info +-- +sync:: ++ +-- [source,rust] ---- -pub fn replicas_info(&self) -> Vec +pub fn schema_with_consistency( + &self, + consistency_level: ConsistencyLevel, +) -> Result ---- -Returns the ``ServerReplica`` instances for this database. _Only works in TypeDB Cloud / Enterprise_ +-- +==== + +Returns a full schema text as a valid TypeQL define query string. + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `consistency_level` a| — The consistency level to use for the operation a| `ConsistencyLevel` +|=== [caption=""] .Returns [source,rust] ---- -Vec +Result ---- [caption=""] .Code examples +[tabs] +==== +async:: ++ +-- [source,rust] ---- -database.replicas_info() +database.schema_with_consistency(ConsistencyLevel::Strong).await; ---- -[#_struct_Database_schema_] -==== schema +-- + +sync:: ++ +-- +[source,rust] +---- +database.schema_with_consistency(ConsistencyLevel::Strong); +---- + +-- +==== + +[#_struct_Database_type_schema_] +==== type_schema [tabs] ==== @@ -245,7 +384,7 @@ async:: -- [source,rust] ---- -pub async fn schema(&self) -> Result +pub async fn type_schema(&self) -> Result ---- -- @@ -255,13 +394,15 @@ sync:: -- [source,rust] ---- -pub fn schema(&self) -> Result +pub fn type_schema(&self) -> Result ---- -- ==== -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, using default strong consistency. + +See <<#_struct_Database_method_type_schema_with_consistency,`Self::type_schema_with_consistency`>> for more details and options. [caption=""] .Returns @@ -279,7 +420,7 @@ async:: -- [source,rust] ---- -database.schema().await; +database.type_schema().await; ---- -- @@ -289,14 +430,14 @@ sync:: -- [source,rust] ---- -database.schema(); +database.type_schema(); ---- -- ==== -[#_struct_Database_type_schema_] -==== type_schema +[#_struct_Database_type_schema_with_consistency_consistency_level_ConsistencyLevel] +==== type_schema_with_consistency [tabs] ==== @@ -305,7 +446,10 @@ async:: -- [source,rust] ---- -pub async fn type_schema(&self) -> Result +pub async fn type_schema_with_consistency( + &self, + consistency_level: ConsistencyLevel, +) -> Result ---- -- @@ -315,7 +459,10 @@ sync:: -- [source,rust] ---- -pub fn type_schema(&self) -> Result +pub fn type_schema_with_consistency( + &self, + consistency_level: ConsistencyLevel, +) -> Result ---- -- @@ -323,6 +470,15 @@ pub fn type_schema(&self) -> Result Returns the types in the schema as a valid TypeQL define query string. +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `consistency_level` a| — The consistency level to use for the operation a| `ConsistencyLevel` +|=== + [caption=""] .Returns [source,rust] @@ -339,7 +495,7 @@ async:: -- [source,rust] ---- -database.type_schema().await; +database.type_schema_with_consistency(ConsistencyLevel::Strong).await; ---- -- @@ -349,7 +505,7 @@ sync:: -- [source,rust] ---- -database.type_schema(); +database.type_schema_with_consistency(ConsistencyLevel::Strong); ---- -- diff --git a/docs/modules/ROOT/partials/rust/connection/DatabaseManager.adoc b/docs/modules/ROOT/partials/rust/connection/DatabaseManager.adoc index 5f2f7cb73a..82ca07ba2c 100644 --- a/docs/modules/ROOT/partials/rust/connection/DatabaseManager.adoc +++ b/docs/modules/ROOT/partials/rust/connection/DatabaseManager.adoc @@ -34,7 +34,9 @@ pub fn all(&self) -> Result>> -- ==== -Retrieves all databases present on the TypeDB server +Retrieves all databases present on the TypeDB server, using default strong consistency. + +See <<#_struct_DatabaseManager_method_all_with_consistency,`Self::all_with_consistency`>> for more details and options. [caption=""] .Returns @@ -68,7 +70,82 @@ driver.databases().all(); -- ==== -[#_struct_DatabaseManager_contains_name_impl_Into_String_] +[#_struct_DatabaseManager_all_with_consistency_consistency_level_ConsistencyLevel] +==== all_with_consistency + +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +pub async fn all_with_consistency( + &self, + consistency_level: ConsistencyLevel, +) -> Result>> +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +pub fn all_with_consistency( + &self, + consistency_level: ConsistencyLevel, +) -> Result>> +---- + +-- +==== + +Retrieves all databases present on the TypeDB server. + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `consistency_level` a| — The consistency level to use for the operation a| `ConsistencyLevel` +|=== + +[caption=""] +.Returns +[source,rust] +---- +Result>> +---- + +[caption=""] +.Code examples +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +driver.databases().all_with_consistency(ConsistencyLevel::Strong).await; +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +driver.databases().all_with_consistency(ConsistencyLevel::Strong); +---- + +-- +==== + +[#_struct_DatabaseManager_contains_] ==== contains [tabs] @@ -94,7 +171,77 @@ pub fn contains(&self, name: impl Into) -> Result -- ==== -Checks if a database with the given name exists +Checks if a database with the given name exists, using default strong consistency. + +See <<#_struct_DatabaseManager_method_contains_with_consistency,`Self::contains_with_consistency`>> for more details and options. + +[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_contains_with_consistency_name_impl_Into_String_consistency_level_ConsistencyLevel] +==== contains_with_consistency + +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +pub async fn contains_with_consistency( + &self, + name: impl Into, + consistency_level: ConsistencyLevel, +) -> Result +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +pub fn contains_with_consistency( + &self, + name: impl Into, + consistency_level: ConsistencyLevel, +) -> Result +---- + +-- +==== + +Checks if a database with the given name exists. [caption=""] .Input parameters @@ -103,6 +250,7 @@ Checks if a database with the given name exists |=== |Name |Description |Type a| `name` a| — The database name to be checked a| `impl Into` +a| `consistency_level` a| — The consistency level to use for the operation a| `ConsistencyLevel` |=== [caption=""] @@ -121,7 +269,7 @@ async:: -- [source,rust] ---- -driver.databases().contains(name).await; +driver.databases().contains_with_consistency(name, ConsistencyLevel::Strong).await; ---- -- @@ -131,7 +279,7 @@ sync:: -- [source,rust] ---- -driver.databases().contains(name); +driver.databases().contains_with_consistency(name, ConsistencyLevel::Strong); ---- -- @@ -163,7 +311,7 @@ pub fn create(&self, name: impl Into) -> Result -- ==== -Create a database with the given name +Creates a database with the given name. Always uses strong consistency. [caption=""] .Input parameters @@ -206,7 +354,7 @@ driver.databases().create(name); -- ==== -[#_struct_DatabaseManager_get_name_impl_AsRef_str_] +[#_struct_DatabaseManager_get_] ==== get [tabs] @@ -216,7 +364,43 @@ async:: -- [source,rust] ---- -pub async fn get(&self, name: impl AsRef) -> Result> +pub async fn get(&self, name: impl Into) -> Result> +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +pub fn get(&self, name: impl Into) -> Result> +---- + +-- +==== + +Retrieves the database with the given name, using default strong consistency. + +See <<#_struct_DatabaseManager_method_get_with_consistency,`Self::get_with_consistency`>> for more details and options. + +[caption=""] +.Returns +[source,rust] +---- +Result> +---- + +[caption=""] +.Code examples +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +driver.databases().get(name).await; ---- -- @@ -226,13 +410,47 @@ sync:: -- [source,rust] ---- -pub fn get(&self, name: impl AsRef) -> Result> +driver.databases().get(name); ---- -- ==== -Retrieve the database with the given name. +[#_struct_DatabaseManager_get_with_consistency_name_impl_Into_String_consistency_level_ConsistencyLevel] +==== get_with_consistency + +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +pub async fn get_with_consistency( + &self, + name: impl Into, + consistency_level: ConsistencyLevel, +) -> Result> +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +pub fn get_with_consistency( + &self, + name: impl Into, + consistency_level: ConsistencyLevel, +) -> Result> +---- + +-- +==== + +Retrieves the database with the given name. [caption=""] .Input parameters @@ -240,7 +458,8 @@ Retrieve the database with the given name. [options="header"] |=== |Name |Description |Type -a| `name` a| — The name of the database to retrieve a| `impl AsRef` +a| `name` a| — The name of the database to retrieve a| `impl Into` +a| `consistency_level` a| — The consistency level to use for the operation a| `ConsistencyLevel` |=== [caption=""] @@ -259,7 +478,7 @@ async:: -- [source,rust] ---- -driver.databases().get(name).await; +driver.databases().get_with_consistency(name, ConsistencyLevel::Strong).await; ---- -- @@ -269,7 +488,7 @@ sync:: -- [source,rust] ---- -driver.databases().get(name); +driver.databases().get_with_consistency(name, ConsistencyLevel::Strong); ---- -- @@ -311,7 +530,7 @@ pub fn import_from_file( -- ==== -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. Always uses strong consistency. 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/rust/connection/DriverOptions.adoc b/docs/modules/ROOT/partials/rust/connection/DriverOptions.adoc index f3264cae57..912a0af254 100644 --- a/docs/modules/ROOT/partials/rust/connection/DriverOptions.adoc +++ b/docs/modules/ROOT/partials/rust/connection/DriverOptions.adoc @@ -5,46 +5,94 @@ * `Clone` * `Debug` +* `Default` -User connection settings for connecting to TypeDB. +TypeDB driver connection options. ``DriverOptions`` object can be used to override the default driver behavior while connecting to TypeDB. + +[caption=""] +.Fields +// tag::properties[] +[cols=",,"] +[options="header"] +|=== +|Name |Type |Description +a| `discovery_failover_retries` a| `Option` a| 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. + +a| `is_tls_enabled` a| `bool` a| Specifies whether the connection to TypeDB must be done over TLS. Defaults to false. +a| `redirect_failover_retries` a| `usize` a| 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. +a| `tls_config` a| `Option` a| If set, specifies the TLS config to use for server certificates authentication. Defaults to None. +a| `use_replication` a| `bool` a| 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. +|=== +// end::properties[] // tag::methods[] +[#_struct_DriverOptions_discovery_failover_retries_] +==== discovery_failover_retries + +[source,rust] +---- +pub fn discovery_failover_retries( + self, + discovery_failover_retries: Option, +) -> 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: + +[caption=""] +.Returns +[source,rust] +---- +Self +---- + [#_struct_DriverOptions_is_tls_enabled_] ==== is_tls_enabled [source,rust] ---- -pub fn is_tls_enabled(&self) -> bool +pub fn is_tls_enabled(self, is_tls_enabled: bool) -> Self ---- -Retrieves whether TLS is enabled for the connection. +Specifies whether the connection to TypeDB must be done over TLS. [caption=""] .Returns [source,rust] ---- -bool +Self ---- -[#_struct_DriverOptions_new_is_tls_enabled_bool_tls_root_ca_Option_Path_] -==== new +[#_struct_DriverOptions_redirect_failover_retries_] +==== redirect_failover_retries [source,rust] ---- -pub fn new(is_tls_enabled: bool, tls_root_ca: Option<&Path>) -> Result +pub fn redirect_failover_retries(self, redirect_failover_retries: usize) -> Self ---- -Creates a credentials with username and password. Specifies the connection must use TLS +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. [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>` -|=== +.Returns +[source,rust] +---- +Self +---- + +[#_struct_DriverOptions_tls_root_ca_] +==== tls_root_ca + +[source,rust] +---- +pub fn tls_root_ca(self, tls_root_ca: Option<&Path>) -> Result +---- + +If set, specifies the path to the CA certificate to use for server certificates authentication. [caption=""] .Returns @@ -53,11 +101,21 @@ a| `tls_root_ca` a| — Path to the CA certificate to use for authenticating se Result ---- +[#_struct_DriverOptions_use_replication_] +==== use_replication + +[source,rust] +---- +pub fn use_replication(self, use_replication: bool) -> Self +---- + +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. + [caption=""] -.Code examples +.Returns [source,rust] ---- -DriverOptions::new(true, Some(&path_to_ca)); +Self ---- // 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/ReplicaType.adoc b/docs/modules/ROOT/partials/rust/connection/ReplicaType.adoc new file mode 100644 index 0000000000..9653e9fff9 --- /dev/null +++ b/docs/modules/ROOT/partials/rust/connection/ReplicaType.adoc @@ -0,0 +1,17 @@ +[#_enum_ReplicaType] +=== ReplicaType + +This enum is used to specify the type of replica. + +[caption=""] +.Enum variants +// tag::enum_constants[] +[cols=""] +[options="header"] +|=== +|Variant +a| `Primary = 0` +a| `Secondary = 1` +|=== +// end::enum_constants[] + diff --git a/docs/modules/ROOT/partials/rust/connection/ServerReplica.adoc b/docs/modules/ROOT/partials/rust/connection/ServerReplica.adoc new file mode 100644 index 0000000000..ed72d84cd0 --- /dev/null +++ b/docs/modules/ROOT/partials/rust/connection/ServerReplica.adoc @@ -0,0 +1,85 @@ +[#_struct_ServerReplica] +=== ServerReplica + +*Implements traits:* + +* `Clone` +* `Debug` +* `Eq` +* `Hash` +* `PartialEq` +* `StructuralPartialEq` + +The metadata and state of an individual raft replica of a driver connection. + +// tag::methods[] +[#_struct_ServerReplica_address_] +==== address + +[source,rust] +---- +pub fn address(&self) -> &Address +---- + +Returns the address this replica is hosted at. + +[caption=""] +.Returns +[source,rust] +---- +&Address +---- + +[#_struct_ServerReplica_is_primary_] +==== is_primary + +[source,rust] +---- +pub fn is_primary(&self) -> bool +---- + +Checks whether this is the primary replica of the raft cluster. + +[caption=""] +.Returns +[source,rust] +---- +bool +---- + +[#_struct_ServerReplica_replica_type_] +==== replica_type + +[source,rust] +---- +pub fn replica_type(&self) -> ReplicaType +---- + +Returns whether this is the primary replica of the raft cluster or any of the supporting types. + +[caption=""] +.Returns +[source,rust] +---- +ReplicaType +---- + +[#_struct_ServerReplica_term_] +==== term + +[source,rust] +---- +pub fn term(&self) -> i64 +---- + +Returns the raft protocol ‘term’ of this replica. + +[caption=""] +.Returns +[source,rust] +---- +i64 +---- + +// end::methods[] + diff --git a/docs/modules/ROOT/partials/rust/connection/ServerVersion.adoc b/docs/modules/ROOT/partials/rust/connection/ServerVersion.adoc new file mode 100644 index 0000000000..74c5c4ffb0 --- /dev/null +++ b/docs/modules/ROOT/partials/rust/connection/ServerVersion.adoc @@ -0,0 +1,47 @@ +[#_struct_ServerVersion] +=== ServerVersion + +*Implements traits:* + +* `Clone` +* `Debug` + +A full TypeDB’s server version specification + +// tag::methods[] +[#_struct_ServerVersion_distribution_] +==== distribution + +[source,rust] +---- +pub fn distribution(&self) -> &str +---- + +Retrieves the server’s distribution. + +[caption=""] +.Returns +[source,rust] +---- +&str +---- + +[#_struct_ServerVersion_version_] +==== version + +[source,rust] +---- +pub fn version(&self) -> &str +---- + +Retrieves the server’s version number. + +[caption=""] +.Returns +[source,rust] +---- +&str +---- + +// end::methods[] + diff --git a/docs/modules/ROOT/partials/rust/connection/TypeDBDriver.adoc b/docs/modules/ROOT/partials/rust/connection/TypeDBDriver.adoc index 8ab2c8a93f..42d318f00a 100644 --- a/docs/modules/ROOT/partials/rust/connection/TypeDBDriver.adoc +++ b/docs/modules/ROOT/partials/rust/connection/TypeDBDriver.adoc @@ -56,7 +56,7 @@ bool driver.is_open() ---- -[#_struct_TypeDBDriver_new_address_impl_AsRef_str_credentials_Credentials_driver_options_DriverOptions] +[#_struct_TypeDBDriver_new_addresses_Addresses_credentials_Credentials_driver_options_DriverOptions] ==== new [tabs] @@ -67,7 +67,7 @@ async:: [source,rust] ---- pub async fn new( - address: impl AsRef, + addresses: Addresses, credentials: Credentials, driver_options: DriverOptions, ) -> Result @@ -81,7 +81,7 @@ sync:: [source,rust] ---- pub fn new( - address: impl AsRef, + addresses: Addresses, credentials: Credentials, driver_options: DriverOptions, ) -> Result @@ -98,7 +98,7 @@ Creates a new TypeDB Server connection. [options="header"] |=== |Name |Description |Type -a| `address` a| — The address (host:port) on which the TypeDB Server is running a| `impl AsRef` +a| `addresses` a| — The address(es) of the TypeDB Server(s), provided in a unified format a| `Addresses` a| `credentials` a| — The Credentials to connect with a| `Credentials` a| `driver_options` a| — The DriverOptions to connect with a| `DriverOptions` |=== @@ -119,7 +119,7 @@ async:: -- [source,rust] ---- -TypeDBDriver::new("127.0.0.1:1729").await +TypeDBDriver::new(Addresses::try_from_address_str("127.0.0.1:1729").unwrap()).await ---- -- @@ -129,13 +129,13 @@ sync:: -- [source,rust] ---- -TypeDBDriver::new("127.0.0.1:1729") +TypeDBDriver::new(Addresses::try_from_address_str("127.0.0.1:1729").unwrap()) ---- -- ==== -[#_struct_TypeDBDriver_new_with_description_address_impl_AsRef_str_credentials_Credentials_driver_options_DriverOptions_driver_lang_impl_AsRef_str_] +[#_struct_TypeDBDriver_new_with_description_addresses_Addresses_credentials_Credentials_driver_options_DriverOptions_driver_lang_impl_AsRef_str_] ==== new_with_description [tabs] @@ -146,7 +146,7 @@ async:: [source,rust] ---- pub async fn new_with_description( - address: impl AsRef, + addresses: Addresses, credentials: Credentials, driver_options: DriverOptions, driver_lang: impl AsRef, @@ -161,7 +161,7 @@ sync:: [source,rust] ---- pub fn new_with_description( - address: impl AsRef, + addresses: Addresses, credentials: Credentials, driver_options: DriverOptions, driver_lang: impl AsRef, @@ -179,7 +179,7 @@ Creates a new TypeDB Server connection with a description. This method is genera [options="header"] |=== |Name |Description |Type -a| `address` a| — The address (host:port) on which the TypeDB Server is running a| `impl AsRef` +a| `addresses` a| — The address(es) of the TypeDB Server(s), provided in a unified format a| `Addresses` 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` @@ -201,7 +201,7 @@ async:: -- [source,rust] ---- -TypeDBDriver::new_with_description("127.0.0.1:1729", "rust").await +TypeDBDriver::new_with_description(Addresses::try_from_address_str("127.0.0.1:1729").unwrap(), "rust").await ---- -- @@ -211,7 +211,192 @@ sync:: -- [source,rust] ---- -TypeDBDriver::new_with_description("127.0.0.1:1729", "rust") +TypeDBDriver::new_with_description(Addresses::try_from_address_str("127.0.0.1:1729").unwrap(), "rust") +---- + +-- +==== + +[#_struct_TypeDBDriver_primary_replica_] +==== primary_replica + +[source,rust] +---- +pub fn primary_replica(&self) -> Option +---- + +Retrieves the server’s primary replica, if exists. + +[caption=""] +.Returns +[source,rust] +---- +Option +---- + +[caption=""] +.Code examples +[source,rust] +---- +driver.primary_replica() +---- + +[#_struct_TypeDBDriver_replicas_] +==== replicas + +[source,rust] +---- +pub fn replicas(&self) -> HashSet +---- + +Retrieves the server’s replicas. + +[caption=""] +.Returns +[source,rust] +---- +HashSet +---- + +[caption=""] +.Code examples +[source,rust] +---- +driver.replicas() +---- + +[#_struct_TypeDBDriver_server_version_] +==== server_version + +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +pub async fn server_version(&self) -> Result +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +pub fn server_version(&self) -> Result +---- + +-- +==== + +Retrieves the server’s version, using default strong consistency. + +See <<#_struct_TypeDBDriver_method_server_version_with_consistency,`Self::server_version_with_consistency`>> for more details and options. + +[caption=""] +.Returns +[source,rust] +---- +Result +---- + +[caption=""] +.Code examples +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +driver.server_version().await +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +driver.server_version() +---- + +-- +==== + +[#_struct_TypeDBDriver_server_version_with_consistency_consistency_level_ConsistencyLevel] +==== server_version_with_consistency + +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +pub async fn server_version_with_consistency( + &self, + consistency_level: ConsistencyLevel, +) -> Result +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +pub fn server_version_with_consistency( + &self, + consistency_level: ConsistencyLevel, +) -> Result +---- + +-- +==== + +Retrieves the server’s version, using default strong consistency. + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `consistency_level` a| — The consistency level to use for the operation a| `ConsistencyLevel` +|=== + +[caption=""] +.Returns +[source,rust] +---- +Result +---- + +[caption=""] +.Code examples +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +driver.server_version_with_consistency(ConsistencyLevel::Strong).await; +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +driver.server_version_with_consistency(ConsistencyLevel::Strong); ---- -- @@ -251,7 +436,9 @@ pub fn transaction( -- ==== -Opens a transaction with default options. See <<#_struct_TypeDBDriver_method_transaction_with_options,`TypeDBDriver::transaction_with_options`>> +Opens a transaction with default options. + +See <<#_struct_TypeDBDriver_method_transaction_with_options,`TypeDBDriver::transaction_with_options`>> for more details. [caption=""] .Returns @@ -260,7 +447,32 @@ Opens a transaction with default options. See <<#_struct_TypeDBDriver_method_tra Result ---- -[#_struct_TypeDBDriver_transaction_with_options_database_name_impl_AsRef_str_transaction_type_TransactionType_options_TransactionOptions] +[caption=""] +.Code examples +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +driver.users().all_with_consistency(ConsistencyLevel::Strong).await; +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +driver.users().all_with_consistency(ConsistencyLevel::Strong); +---- + +-- +==== + +[#_struct_TypeDBDriver_transaction_with_options_options_TransactionOptions_database_name_impl_AsRef_str_transaction_type_TransactionType_options_TransactionOptions] ==== transaction_with_options [tabs] @@ -296,7 +508,7 @@ pub fn transaction_with_options( -- ==== -Performs a TypeQL query in this transaction. +Opens a new transaction with the following consistency level: [caption=""] .Input parameters @@ -304,6 +516,7 @@ Performs a TypeQL query in this transaction. [options="header"] |=== |Name |Description |Type +a| `options` a| read transaction - strong consistency, can be overridden through ; a| `TransactionOptions` 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` @@ -318,10 +531,28 @@ Result [caption=""] .Code examples +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +transaction.transaction_with_options(database_name, transaction_type, options).await +---- + +-- + +sync:: ++ +-- [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 index fab0c0c2c0..43769abf4f 100644 --- a/docs/modules/ROOT/partials/rust/connection/User.adoc +++ b/docs/modules/ROOT/partials/rust/connection/User.adoc @@ -6,21 +6,8 @@ * `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] +[#_struct_User_delete_] ==== delete [tabs] @@ -46,16 +33,7 @@ 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| -|=== +Deletes this user. Always uses strong consistency. [caption=""] .Returns @@ -91,7 +69,41 @@ user.delete(username).await; -- ==== -[#_struct_User_update_password_username_password_impl_Into_String_-_Result_] +[#_struct_User_name_] +==== name + +[source,rust] +---- +pub fn name(&self) -> &str +---- + +Retrieves the username as a string. + +[caption=""] +.Returns +[source,rust] +---- +&str +---- + +[#_struct_User_password_] +==== password + +[source,rust] +---- +pub fn password(&self) -> Option<&str> +---- + +Retrieves the password as a string, if accessible. + +[caption=""] +.Returns +[source,rust] +---- +Option<&str> +---- + +[#_struct_User_update_password_password_impl_Into_String_-_Result_] ==== update_password [tabs] @@ -117,7 +129,7 @@ pub fn update_password(&self, password: impl Into) -> Result<()> -- ==== -Update the user’s password. +Updates the user’s password. Always uses strong consistency. [caption=""] .Input parameters @@ -125,7 +137,6 @@ Update the user’s password. [options="header"] |=== |Name |Description |Type -a| `username` a| — The name of the user a| a| `password` a| — The new password a| `impl Into) -> Result<(` |=== @@ -145,8 +156,7 @@ async:: -- [source,rust] ---- -user.update_password(username, password).await; -user.update_password(username, password).await; +user.update_password(password).await; ---- -- @@ -156,8 +166,7 @@ sync:: -- [source,rust] ---- -user.update_password(username, password); -user.update_password(username, password).await; +user.update_password(password); ---- -- diff --git a/docs/modules/ROOT/partials/rust/connection/UserManager.adoc b/docs/modules/ROOT/partials/rust/connection/UserManager.adoc index 36ccfd8f9b..3ebd2e8133 100644 --- a/docs/modules/ROOT/partials/rust/connection/UserManager.adoc +++ b/docs/modules/ROOT/partials/rust/connection/UserManager.adoc @@ -34,8 +34,85 @@ pub fn all(&self) -> Result> -- ==== +Retrieves all users which exist on the TypeDB server, using default strong consistency. + +See <<#_struct_UserManager_method_all_with_consistency,`Self::all_with_consistency`>> for more details and options. + +[caption=""] +.Returns +[source,rust] +---- +Result> +---- + +[caption=""] +.Code examples +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +driver.users().all().await; +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +driver.users().all(); +---- + +-- +==== + +[#_struct_UserManager_all_with_consistency_consistency_level_ConsistencyLevel] +==== all_with_consistency + +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +pub async fn all_with_consistency( + &self, + consistency_level: ConsistencyLevel, +) -> Result> +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +pub fn all_with_consistency( + &self, + consistency_level: ConsistencyLevel, +) -> Result> +---- + +-- +==== + Retrieves all users which exist on the TypeDB server. +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `consistency_level` a| — The consistency level to use for the operation a| `ConsistencyLevel` +|=== + [caption=""] .Returns [source,rust] @@ -45,12 +122,30 @@ Result> [caption=""] .Code examples +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +driver.users().all_with_consistency(ConsistencyLevel::Strong).await; +---- + +-- + +sync:: ++ +-- [source,rust] ---- -driver.users.all().await; +driver.users().all_with_consistency(ConsistencyLevel::Strong); ---- -[#_struct_UserManager_contains_username_impl_Into_String_] +-- +==== + +[#_struct_UserManager_contains_] ==== contains [tabs] @@ -76,6 +171,76 @@ pub fn contains(&self, username: impl Into) -> Result -- ==== +Checks if a user with the given name exists, using default strong consistency. + +See <<#_struct_UserManager_method_contains_with_consistency,`Self::contains_with_consistency`>> for more details and options. + +[caption=""] +.Returns +[source,rust] +---- +Result +---- + +[caption=""] +.Code examples +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +driver.users().contains(username).await; +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +driver.users().contains(username); +---- + +-- +==== + +[#_struct_UserManager_contains_with_consistency_username_impl_Into_String_consistency_level_ConsistencyLevel] +==== contains_with_consistency + +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +pub async fn contains_with_consistency( + &self, + username: impl Into, + consistency_level: ConsistencyLevel, +) -> Result +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +pub fn contains_with_consistency( + &self, + username: impl Into, + consistency_level: ConsistencyLevel, +) -> Result +---- + +-- +==== + Checks if a user with the given name exists. [caption=""] @@ -84,7 +249,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| `impl Into` +a| `username` a| — The username to be checked a| `impl Into` +a| `consistency_level` a| — The consistency level to use for the operation a| `ConsistencyLevel` |=== [caption=""] @@ -96,11 +262,29 @@ Result [caption=""] .Code examples +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +driver.users().contains_with_consistency(username, ConsistencyLevel::Strong).await; +---- + +-- + +sync:: ++ +-- [source,rust] ---- -driver.users.contains(username).await; +driver.users().contains_with_consistency(username, ConsistencyLevel::Strong); ---- +-- +==== + [#_struct_UserManager_create_username_impl_Into_String_password_impl_Into_String_] ==== create @@ -135,7 +319,7 @@ pub fn create( -- ==== -Create a user with the given name & password. +Creates a user with the given name & password. Always uses strong consistency. [caption=""] .Input parameters @@ -156,12 +340,30 @@ Result [caption=""] .Code examples +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +driver.users().create(username, password).await; +---- + +-- + +sync:: ++ +-- [source,rust] ---- -driver.users.create(username, password).await; +driver.users().create(username, password); ---- -[#_struct_UserManager_get_username_impl_Into_String_] +-- +==== + +[#_struct_UserManager_get_] ==== get [tabs] @@ -187,7 +389,119 @@ pub fn get(&self, username: impl Into) -> Result> -- ==== -Retrieve a user with the given name. +Retrieves a user with the given name, using default strong consistency. + +See <<#_struct_UserManager_method_get_with_consistency,`Self::get_with_consistency`>> for more details and options. + +[caption=""] +.Returns +[source,rust] +---- +Result> +---- + +[caption=""] +.Code examples +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +driver.users().get(username).await; +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +driver.users().get(username); +---- + +-- +==== + +[#_struct_UserManager_get_current_user_] +==== get_current_user + +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +pub async fn get_current_user(&self) -> Result> +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +pub fn get_current_user(&self) -> Result> +---- + +-- +==== + +Returns the user of the current connection. + +[caption=""] +.Returns +[source,rust] +---- +Result> +---- + +[caption=""] +.Code examples +[source,rust] +---- +driver.users().get_current_user().await; +---- + +[#_struct_UserManager_get_with_consistency_username_impl_Into_String_consistency_level_ConsistencyLevel] +==== get_with_consistency + +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +pub async fn get_with_consistency( + &self, + username: impl Into, + consistency_level: ConsistencyLevel, +) -> Result> +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +pub fn get_with_consistency( + &self, + username: impl Into, + consistency_level: ConsistencyLevel, +) -> Result> +---- + +-- +==== + +Retrieves a user with the given name. [caption=""] .Input parameters @@ -196,6 +510,7 @@ Retrieve a user with the given name. |=== |Name |Description |Type a| `username` a| — The name of the user to retrieve a| `impl Into` +a| `consistency_level` a| — The consistency level to use for the operation a| `ConsistencyLevel` |=== [caption=""] @@ -207,10 +522,28 @@ Result> [caption=""] .Code examples +[tabs] +==== +async:: ++ +-- [source,rust] ---- -driver.users.get(username).await; +driver.users().get_with_consistency(username, ConsistencyLevel::Strong).await; ---- +-- + +sync:: ++ +-- +[source,rust] +---- +driver.users().get_with_consistency(username, ConsistencyLevel::Strong); +---- + +-- +==== + // end::methods[] diff --git a/docs/modules/ROOT/partials/rust/errors/ConnectionError.adoc b/docs/modules/ROOT/partials/rust/errors/ConnectionError.adoc index e7a0621b55..fefa7cfea8 100644 --- a/docs/modules/ROOT/partials/rust/errors/ConnectionError.adoc +++ b/docs/modules/ROOT/partials/rust/errors/ConnectionError.adoc @@ -8,35 +8,38 @@ [options="header"] |=== |Variant -a| `AddressTranslationMismatch` a| `BrokenPipe` -a| `ClusterAllNodesFailed` a| `ClusterReplicaNotPrimary` -a| `ConnectionFailed` +a| `ConnectionRefusedNetworking` a| `DatabaseExportChannelIsClosed` a| `DatabaseExportStreamNoResponse` a| `DatabaseImportChannelIsClosed` a| `DatabaseImportStreamUnexpectedResponse` -a| `DatabaseNotFound` a| `EncryptionSettingsMismatch` -a| `InvalidResponseField` +a| `HttpHttpsMismatch` a| `ListsNotImplemented` a| `MissingPort` a| `MissingResponseField` +a| `MissingTlsConfigForTls` +a| `MultipleAddressesForNoReplicationMode` +a| `NoAvailableReplicas` +a| `NotPrimaryOnReadOnly` a| `QueryStreamNoResponse` a| `RPCMethodUnavailable` -a| `SSLCertificateNotValidated` a| `ServerConnectionFailed` -a| `ServerConnectionFailedStatusError` +a| `ServerConnectionFailedNetworking` a| `ServerConnectionFailedWithError` a| `ServerConnectionIsClosed` +a| `SslCertificateNotValidated` a| `TokenCredentialInvalid` a| `TransactionIsClosed` a| `TransactionIsClosedWithErrors` a| `UnexpectedConnectionClose` a| `UnexpectedKind` a| `UnexpectedQueryType` +a| `UnexpectedReplicaType` a| `UnexpectedResponse` +a| `UnknownDirectReplica` a| `UnknownRequestId` a| `ValueStructNotImplemented` a| `ValueTimeZoneNameNotRecognised` diff --git a/docs/modules/ROOT/partials/rust/errors/InternalError.adoc b/docs/modules/ROOT/partials/rust/errors/InternalError.adoc index 78f7c480b5..ea308aed7c 100644 --- a/docs/modules/ROOT/partials/rust/errors/InternalError.adoc +++ b/docs/modules/ROOT/partials/rust/errors/InternalError.adoc @@ -8,12 +8,10 @@ [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/transaction/Transaction.adoc b/docs/modules/ROOT/partials/rust/transaction/Transaction.adoc index 7482e4b7fa..acd58274e0 100644 --- a/docs/modules/ROOT/partials/rust/transaction/Transaction.adoc +++ b/docs/modules/ROOT/partials/rust/transaction/Transaction.adoc @@ -82,7 +82,7 @@ transaction.force_close() pub fn is_open(&self) -> bool ---- -Closes the transaction. +Checks if the transaction is open. [caption=""] .Returns @@ -95,7 +95,7 @@ bool .Code examples [source,rust] ---- -transaction.close() +transaction.is_open() ---- [#_struct_Transaction_on_close_function] @@ -154,6 +154,13 @@ Performs a TypeQL query with default options. See <<#_struct_Transaction_method_ impl Promise<'static, Result> ---- +[caption=""] +.Code examples +[source,rust] +---- +transaction.query(query) +---- + [#_struct_Transaction_query_with_options_query_impl_AsRef_str_options_QueryOptions] ==== query_with_options diff --git a/docs/modules/ROOT/partials/rust/transaction/TransactionOptions.adoc b/docs/modules/ROOT/partials/rust/transaction/TransactionOptions.adoc index 171ba38409..1c75dccc68 100644 --- a/docs/modules/ROOT/partials/rust/transaction/TransactionOptions.adoc +++ b/docs/modules/ROOT/partials/rust/transaction/TransactionOptions.adoc @@ -4,11 +4,10 @@ *Implements traits:* * `Clone` -* `Copy` * `Debug` * `Default` -TypeDB transaction options. ``TransactionOptions`` object can be used to override the default server behaviour for opened transactions. +TypeDB transaction options. ``TransactionOptions`` object can be used to override the default behaviour for opened transactions. [caption=""] .Fields @@ -17,12 +16,30 @@ TypeDB transaction options. ``TransactionOptions`` object can be used to overrid [options="header"] |=== |Name |Type |Description +a| `read_consistency_level` a| `Option` 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` 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_consistency_level_] +==== consistency_level + +[source,rust] +---- +pub fn consistency_level(self, consistency_level: ConsistencyLevel) -> 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. + +[caption=""] +.Returns +[source,rust] +---- +Self +---- + [#_struct_TransactionOptions_schema_lock_acquire_timeout_] ==== schema_lock_acquire_timeout diff --git a/docs/modules/ROOT/partials/rust/value/Decimal.adoc b/docs/modules/ROOT/partials/rust/value/Decimal.adoc index 5313cf98bc..c7b123bb64 100644 --- a/docs/modules/ROOT/partials/rust/value/Decimal.adoc +++ b/docs/modules/ROOT/partials/rust/value/Decimal.adoc @@ -20,40 +20,15 @@ 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[] +.Fields +// tag::properties[] +[cols=",,"] +[options="header"] +|=== +|Name |Type |Description +a| `fractional` a| `u64` a| 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. +a| `integer` a| `i64` a| The integer part of the decimal as normal signed 64 bit number +|=== +// end::properties[] 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/python/docs_structure.bzl b/python/docs_structure.bzl index 46c574feb7..be19e7fb5a 100644 --- a/python/docs_structure.bzl +++ b/python/docs_structure.bzl @@ -32,12 +32,15 @@ dir_mapping = { "Concept.adoc": "concept", ".DS_Store": "concept", "TypeDB.adoc": "connection", - "DriverOptions.adoc": "connection", "Credentials.adoc": "connection", +# "ConsistencyLevel.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/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/src/common/address.rs b/rust/src/common/address/address.rs similarity index 100% rename from rust/src/common/address.rs rename to rust/src/common/address/address.rs 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/connection/server/addresses.rs b/rust/src/common/address/addresses.rs similarity index 95% rename from rust/src/connection/server/addresses.rs rename to rust/src/common/address/addresses.rs index 51765a9509..9873dfde4d 100644 --- a/rust/src/connection/server/addresses.rs +++ b/rust/src/common/address/addresses.rs @@ -25,9 +25,7 @@ use std::{ use itertools::Itertools; -use crate::common::address::Address; - -// TODO: Move to common/address.rs? +use crate::common::address::{address_translation::AddressTranslation, Address}; /// A collection of server addresses used for connection. #[derive(Debug, Clone, Eq, PartialEq)] @@ -165,12 +163,12 @@ impl Addresses { } } - pub(crate) fn address_translation(&self) -> HashMap { + pub(crate) fn address_translation(&self) -> AddressTranslation { match self { - Addresses::Direct(addresses) => { - addresses.into_iter().map(|address| (address.clone(), address.clone())).collect() - } - Addresses::Translated(translation) => translation.clone(), + Addresses::Direct(addresses) => AddressTranslation::Mapping( + addresses.into_iter().map(|address| (address.clone(), address.clone())).collect(), + ), + Addresses::Translated(translation) => AddressTranslation::Mapping(translation.clone()), } } } diff --git a/rust/src/common/address/mod.rs b/rust/src/common/address/mod.rs new file mode 100644 index 0000000000..90f1b24946 --- /dev/null +++ b/rust/src/common/address/mod.rs @@ -0,0 +1,25 @@ +/* + * 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) use self::address::Address; +pub use self::addresses::Addresses; + +pub(crate) mod address; +pub(crate) mod address_translation; +pub(crate) mod addresses; diff --git a/rust/src/common/error.rs b/rust/src/common/error.rs index 36f254adb9..231be9af2d 100644 --- a/rust/src/common/error.rs +++ b/rust/src/common/error.rs @@ -23,7 +23,7 @@ use tonic::{Code, Status}; use tonic_types::StatusExt; use super::RequestID; -use crate::{common::address::Address, connection::server::Addresses}; +use crate::common::address::{Address, Addresses}; macro_rules! error_messages { { diff --git a/rust/src/common/mod.rs b/rust/src/common/mod.rs index 1e14d062e4..1ff5fdcdfa 100644 --- a/rust/src/common/mod.rs +++ b/rust/src/common/mod.rs @@ -18,6 +18,7 @@ */ pub use self::{ + address::Addresses, error::Error, promise::{box_promise, BoxPromise, Promise}, query_options::QueryOptions, diff --git a/rust/src/connection/message.rs b/rust/src/connection/message.rs index e9c37ea7a9..1a30db4a1b 100644 --- a/rust/src/connection/message.rs +++ b/rust/src/connection/message.rs @@ -32,7 +32,7 @@ use crate::{ }, common::{info::DatabaseInfo, RequestID}, concept::Concept, - connection::{server::server_replica::ServerReplica, ServerVersion}, + connection::{server_replica::ServerReplica, server_version::ServerVersion}, error::ServerError, info::UserInfo, Credentials, QueryOptions, TransactionOptions, TransactionType, diff --git a/rust/src/connection/mod.rs b/rust/src/connection/mod.rs index baf1a06fa4..86cad4a4a8 100644 --- a/rust/src/connection/mod.rs +++ b/rust/src/connection/mod.rs @@ -21,7 +21,7 @@ pub(crate) use self::transaction_stream::TransactionStream; pub use self::{ credentials::Credentials, driver_options::DriverOptions, - server::{server_replica, ServerVersion}, + server::{server_replica, server_version}, }; mod credentials; 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/message.rs b/rust/src/connection/network/proto/message.rs index 5c54e6bd42..5b70b27caa 100644 --- a/rust/src/connection/network/proto/message.rs +++ b/rust/src/connection/network/proto/message.rs @@ -33,8 +33,8 @@ use crate::{ DatabaseExportResponse, DatabaseImportRequest, QueryRequest, QueryResponse, Request, Response, TransactionRequest, TransactionResponse, }, - server::server_replica::ServerReplica, - ServerVersion, + server_replica::ServerReplica, + server_version::ServerVersion, }, error::{ConnectionError, InternalError, ServerError}, info::UserInfo, diff --git a/rust/src/connection/network/proto/server.rs b/rust/src/connection/network/proto/server.rs index f112f2036d..0f010595fa 100644 --- a/rust/src/connection/network/proto/server.rs +++ b/rust/src/connection/network/proto/server.rs @@ -26,8 +26,8 @@ use super::TryFromProto; use crate::{ common::Result, connection::{ - server::server_replica::{ReplicaStatus, ReplicaType, ServerReplica}, - ServerVersion, + server_replica::{ReplicaStatus, ReplicaType, ServerReplica}, + server_version::ServerVersion, }, error::ConnectionError, }; diff --git a/rust/src/connection/server/mod.rs b/rust/src/connection/server/mod.rs index e550425afd..b068d0a425 100644 --- a/rust/src/connection/server/mod.rs +++ b/rust/src/connection/server/mod.rs @@ -17,10 +17,7 @@ * under the License. */ -pub use self::{addresses::Addresses, server_version::ServerVersion}; - -pub(crate) mod addresses; pub(crate) mod server_connection; pub(crate) mod server_manager; pub mod server_replica; -mod server_version; +pub mod server_version; diff --git a/rust/src/connection/server/server_connection.rs b/rust/src/connection/server/server_connection.rs index ea21504c8a..fdebe5587a 100644 --- a/rust/src/connection/server/server_connection.rs +++ b/rust/src/connection/server/server_connection.rs @@ -38,8 +38,9 @@ use crate::{ DatabaseExportTransmitter, DatabaseImportTransmitter, RPCTransmitter, TransactionTransmitter, }, runtime::BackgroundRuntime, - server::server_replica::ServerReplica, - ServerVersion, TransactionStream, + server_replica::ServerReplica, + server_version::ServerVersion, + TransactionStream, }, error::{ConnectionError, InternalError}, info::{DatabaseInfo, UserInfo}, diff --git a/rust/src/connection/server/server_manager.rs b/rust/src/connection/server/server_manager.rs index 25fb818de6..16475ae4a2 100644 --- a/rust/src/connection/server/server_manager.rs +++ b/rust/src/connection/server/server_manager.rs @@ -30,10 +30,13 @@ use itertools::{enumerate, Itertools}; use log::debug; use crate::{ - common::{address::Address, consistency_level::ConsistencyLevel}, + common::{ + address::{address_translation::AddressTranslation, Address, Addresses}, + consistency_level::ConsistencyLevel, + }, connection::{ runtime::BackgroundRuntime, - server::{server_connection::ServerConnection, server_replica::ServerReplica, Addresses}, + server::{server_connection::ServerConnection, server_replica::ServerReplica}, }, error::ConnectionError, Credentials, DriverOptions, Error, Result, @@ -45,9 +48,7 @@ pub(crate) struct ServerManager { replicas: RwLock>, server_connections: RwLock>, connection_scheme: http::uri::Scheme, - - // public - private - address_translation: HashMap, + address_translation: AddressTranslation, background_runtime: Arc, credentials: Credentials, @@ -216,8 +217,8 @@ impl ServerManager { } .into()); } - let private_address = self.address_translation.get(&address).unwrap_or_else(|| &address); - self.execute_on(&address, private_address, false, &task).await + let private_address = self.address_translation.to_private(&address).unwrap_or_else(|| address.clone()); + self.execute_on(&address, &private_address, false, &task).await } } } @@ -419,7 +420,7 @@ impl ServerManager { fn translate_replicas( replicas: impl IntoIterator, connection_scheme: &http::uri::Scheme, - address_translation: &HashMap, + address_translation: &AddressTranslation, ) -> Vec { replicas.into_iter().map(|replica| replica.translated(connection_scheme, address_translation)).collect() } diff --git a/rust/src/connection/server/server_replica.rs b/rust/src/connection/server/server_replica.rs index f766d52008..04a4efd15b 100644 --- a/rust/src/connection/server/server_replica.rs +++ b/rust/src/connection/server/server_replica.rs @@ -16,12 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -use std::{ - collections::HashMap, - net::{IpAddr, Ipv4Addr, SocketAddr}, -}; - -use crate::common::address::Address; +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)] @@ -39,13 +34,9 @@ impl ServerReplica { pub(crate) fn translate_address( &mut self, connection_scheme: &http::uri::Scheme, - address_translation: &HashMap, + address_translation: &AddressTranslation, ) { - if let Some(translated) = address_translation - .iter() - .find(|(_, private)| private == &self.private_address()) - .map(|(public, _)| public.clone()) - { + 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 { @@ -57,7 +48,7 @@ impl ServerReplica { pub(crate) fn translated( mut self, connection_scheme: &http::uri::Scheme, - address_translation: &HashMap, + address_translation: &AddressTranslation, ) -> Self { self.translate_address(connection_scheme, address_translation); self diff --git a/rust/src/driver.rs b/rust/src/driver.rs index a5dcca8df5..f5cb331769 100644 --- a/rust/src/driver.rs +++ b/rust/src/driver.rs @@ -20,14 +20,13 @@ use std::{collections::HashSet, fmt, sync::Arc}; use crate::{ - common::{consistency_level::ConsistencyLevel, Result}, + common::{consistency_level::ConsistencyLevel, Addresses, Result}, connection::{ runtime::BackgroundRuntime, server::{ server_connection::ServerConnection, server_manager::ServerManager, server_replica::ServerReplica, - Addresses, + server_version::ServerVersion, }, - ServerVersion, }, Credentials, DatabaseManager, DriverOptions, Transaction, TransactionOptions, TransactionType, UserManager, }; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index d3d232731c..5954166b59 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -22,13 +22,13 @@ pub use self::{ common::{ - box_stream, consistency_level, error, info, BoxPromise, BoxStream, Error, Promise, QueryOptions, Result, - TransactionOptions, TransactionType, IID, + box_stream, consistency_level, error, info, Addresses, BoxPromise, BoxStream, Error, Promise, QueryOptions, + Result, TransactionOptions, TransactionType, IID, }, connection::{ - server::Addresses, server_replica::{ReplicaType, ServerReplica}, - Credentials, DriverOptions, ServerVersion, + server_version::ServerVersion, + Credentials, DriverOptions, }, database::{Database, DatabaseManager}, driver::TypeDBDriver, From 8afa2cdf732a7a0b08e376e7d5ff66c152dc75ee Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Tue, 1 Jul 2025 17:50:01 +0100 Subject: [PATCH 19/35] Refactor the c package. Introduce additional constructors to C and Java --- c/README.md | 4 +- c/src/answer.rs | 10 +- c/src/{ => common}/error.rs | 16 +- c/src/{ => common}/iterator.rs | 11 +- c/src/{ => common}/memory.rs | 30 ++-- c/src/{common.rs => common/mod.rs} | 24 ++- c/src/{ => common}/promise.rs | 2 +- c/src/concept/concept.rs | 4 +- c/src/concept/instance.rs | 2 +- c/src/concept/mod.rs | 4 +- c/src/connection.rs | 167 -------------------- c/src/credentials.rs | 47 ++++++ c/src/{ => database}/database.rs | 4 +- c/src/{ => database}/database_manager.rs | 12 +- c/src/database/mod.rs | 21 +++ c/src/driver.rs | 192 +++++++++++++++++++++++ c/src/driver_options.rs | 47 ++++++ c/src/lib.rs | 13 +- c/src/query_options.rs | 2 +- c/src/{ => server}/consistency_level.rs | 2 +- c/src/server/mod.rs | 22 +++ c/src/{ => server}/server_replica.rs | 5 +- c/src/server/server_version.rs | 48 ++++++ c/src/transaction.rs | 5 +- c/src/transaction_options.rs | 2 +- c/src/user/mod.rs | 21 +++ c/src/{ => user}/user.rs | 11 +- c/src/{ => user}/user_manager.rs | 7 +- c/tests/assembly/test.c | 2 +- c/tests/integration/driver_test.c | 10 +- c/typedb_driver.i | 8 +- java/TypeDB.java | 35 +++++ java/connection/DriverImpl.java | 45 +++++- python/typedb/connection/driver.py | 4 +- 34 files changed, 571 insertions(+), 268 deletions(-) rename c/src/{ => common}/error.rs (87%) rename c/src/{ => common}/iterator.rs (77%) rename c/src/{ => common}/memory.rs (79%) rename c/src/{common.rs => common/mod.rs} (73%) rename c/src/{ => common}/promise.rs (99%) delete mode 100644 c/src/connection.rs create mode 100644 c/src/credentials.rs rename c/src/{ => database}/database.rs (96%) rename c/src/{ => database}/database_manager.rs (93%) create mode 100644 c/src/database/mod.rs create mode 100644 c/src/driver.rs create mode 100644 c/src/driver_options.rs rename c/src/{ => server}/consistency_level.rs (99%) create mode 100644 c/src/server/mod.rs rename c/src/{ => server}/server_replica.rs (97%) create mode 100644 c/src/server/server_version.rs create mode 100644 c/src/user/mod.rs rename c/src/{ => user}/user.rs (83%) rename c/src/{ => user}/user_manager.rs (93%) 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 78adf3fa88..6f857c15b5 100644 --- a/c/src/answer.rs +++ b/c/src/answer.rs @@ -26,16 +26,16 @@ use typedb_driver::{ BoxPromise, Promise, Result, }; -use super::{ +use crate::{ concept::ConceptIterator, - iterator::CIterator, - memory::{borrow, free, release, release_optional, release_string, string_view}, + common::iterator::CIterator, + common::memory::{borrow, free, release, 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}, + common::memory::take_ownership, }; /// Promise object representing the result of an asynchronous operation. diff --git a/c/src/error.rs b/c/src/common/error.rs similarity index 87% rename from c/src/error.rs rename to c/src/common/error.rs index 3920409029..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 79% rename from c/src/memory.rs rename to c/src/common/memory.rs index 4aa959c8c6..6a6b7e1741 100644 --- a/c/src/memory.rs +++ b/c/src/common/memory.rs @@ -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 73% rename from c/src/common.rs rename to c/src/common/mod.rs index dea0ff67e4..5d495ad60f 100644 --- a/c/src/common.rs +++ b/c/src/common/mod.rs @@ -18,14 +18,20 @@ */ use std::{ffi::c_char, ptr::null_mut}; - +use std::collections::HashMap; +use std::hash::Hash; +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; +use memory::free; /// Iterator over the strings in the result of a request or a TypeQL Fetch query. pub struct StringIterator(pub CIterator>); @@ -34,7 +40,7 @@ pub struct StringIterator(pub CIterator>); /// or null if there are no more elements. #[no_mangle] pub extern "C" fn string_iterator_next(it: *mut StringIterator) -> *mut c_char { - borrow_mut(it).0 .0.next().map(try_release_string).unwrap_or_else(null_mut) + borrow_mut(it).0.0.next().map(try_release_string).unwrap_or_else(null_mut) } /// Frees the native rust StringIterator object @@ -42,3 +48,7 @@ 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 87a3fb5dc7..f2389afc9d 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, 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..03faf5a9d9 100644 --- a/c/src/concept/mod.rs +++ b/c/src/concept/mod.rs @@ -22,8 +22,8 @@ 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::{iterator::iterator_try_next, memory::free}; +use crate::common::{error::try_release_optional, iterator::CIterator, memory::take_ownership}; mod concept; mod instance; diff --git a/c/src/connection.rs b/c/src/connection.rs deleted file mode 100644 index e3edb91260..0000000000 --- a/c/src/connection.rs +++ /dev/null @@ -1,167 +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::{ - box_stream, consistency_level::ConsistencyLevel, Addresses, Credentials, Database, DriverOptions, ServerReplica, - TypeDBDriver, -}; - -use super::{ - error::{try_release, unwrap_void}, - memory::{borrow, free, string_view}, -}; -use crate::{ - error::unwrap_or_default, - iterator::CIterator, - memory::{release, release_optional, release_string, string_free}, - server_replica::ServerReplicaIterator, -}; - -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( - 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 (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( - unwrap_or_default(Addresses::try_from_address_str(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(is_tls_enabled).tls_root_ca(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); -} - -/// ServerVersion is an FFI representation of a full server's version specification. -#[repr(C)] -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 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); -} - -/// Retrieves the server's replicas. -#[no_mangle] -pub extern "C" fn driver_replicas(driver: *const TypeDBDriver) -> *mut ServerReplicaIterator { - release(ServerReplicaIterator(CIterator(box_stream(borrow(driver).replicas().into_iter())))) -} - -/// Retrieves the server's primary replica, if exists. -#[no_mangle] -pub extern "C" fn driver_primary_replicas(driver: *const TypeDBDriver) -> *mut ServerReplica { - release_optional(borrow(driver).primary_replica()) -} diff --git a/c/src/credentials.rs b/c/src/credentials.rs new file mode 100644 index 0000000000..8ff5face3a --- /dev/null +++ b/c/src/credentials.rs @@ -0,0 +1,47 @@ +/* + * 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, string_view}, +}; +use crate::common::{ + memory::{release}, +}; + +/// 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/database.rs similarity index 96% rename from c/src/database.rs rename to c/src/database/database.rs index 1a95c0ec20..27eb8f13bd 100644 --- a/c/src/database.rs +++ b/c/src/database/database.rs @@ -21,11 +21,11 @@ use std::{ffi::c_char, path::Path}; use typedb_driver::Database; -use super::{ +use crate::common::{ error::{try_release_string, unwrap_void}, memory::{borrow, release_string}, }; -use crate::memory::{decrement_arc, string_view, take_arc}; +use crate::common::memory::{decrement_arc, string_view, take_arc}; /// Frees the native rust Database object #[no_mangle] diff --git a/c/src/database_manager.rs b/c/src/database/database_manager.rs similarity index 93% rename from c/src/database_manager.rs rename to c/src/database/database_manager.rs index 41ff3a7642..c9e044d1ad 100644 --- a/c/src/database_manager.rs +++ b/c/src/database/database_manager.rs @@ -21,14 +21,12 @@ 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::{ - consistency_level::ConsistencyLevel, error::try_release_arc, iterator::iterator_arc_next, memory::borrow_optional, +use crate::common::{ + error::{try_release, try_release_arc, unwrap_or_default, unwrap_void}, + iterator::{iterator_arc_next, CIterator}, + memory::{borrow_mut, borrow_optional, free, string_view}, }; +use crate::server::consistency_level::ConsistencyLevel; /// An Iterator over databases present on the TypeDB server. pub struct DatabaseIterator(CIterator>); 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..87da4a285c --- /dev/null +++ b/c/src/driver.rs @@ -0,0 +1,192 @@ +/* + * 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_view}}; +use crate::common::memory::string_array_view; +use crate::server::server_replica::ServerReplicaIterator; + +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 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()); +} + +/// Retrieves the server's replicas. +#[no_mangle] +pub extern "C" fn driver_replicas(driver: *const TypeDBDriver) -> *mut ServerReplicaIterator { + release(ServerReplicaIterator(CIterator(box_stream(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()) +} diff --git a/c/src/driver_options.rs b/c/src/driver_options.rs new file mode 100644 index 0000000000..38776801ae --- /dev/null +++ b/c/src/driver_options.rs @@ -0,0 +1,47 @@ +/* + * 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::{try_release}, + memory::{free, string_view,}, +}; + +// TODO: Refactor + +/// 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(is_tls_enabled).tls_root_ca(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/lib.rs b/c/src/lib.rs index 82c0f621eb..02b79c68b2 100644 --- a/c/src/lib.rs +++ b/c/src/lib.rs @@ -20,17 +20,12 @@ mod answer; mod common; mod concept; -mod connection; -mod consistency_level; +mod driver; +mod credentials; +mod server; mod database; -mod database_manager; -mod error; -mod iterator; -mod memory; -mod promise; +mod driver_options; mod query_options; -mod server_replica; 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..8699963863 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] diff --git a/c/src/consistency_level.rs b/c/src/server/consistency_level.rs similarity index 99% rename from c/src/consistency_level.rs rename to c/src/server/consistency_level.rs index d683b5be0c..41bcf8ca3c 100644 --- a/c/src/consistency_level.rs +++ b/c/src/server/consistency_level.rs @@ -20,7 +20,7 @@ use std::ffi::c_char; use typedb_driver::consistency_level::ConsistencyLevel as NativeConsistencyLevel; -use crate::{ +use crate::common::{ error::unwrap_or_default, memory::{free, release, release_string, string_free, string_view}, }; diff --git a/c/src/server/mod.rs b/c/src/server/mod.rs new file mode 100644 index 0000000000..0e64f5c031 --- /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; +mod server_version; diff --git a/c/src/server_replica.rs b/c/src/server/server_replica.rs similarity index 97% rename from c/src/server_replica.rs rename to c/src/server/server_replica.rs index dc352f02ad..69c99bd71d 100644 --- a/c/src/server_replica.rs +++ b/c/src/server/server_replica.rs @@ -21,10 +21,9 @@ use std::{ffi::c_char, ptr::addr_of_mut}; use typedb_driver::{ReplicaType, ServerReplica}; -use super::memory::{borrow, release_string}; -use crate::{ +use crate::common::memory::{borrow, release_string, free}; +use crate::common::{ iterator::{iterator_next, CIterator}, - memory::free, }; /// Iterator over the ServerReplica corresponding to each replica of a TypeDB cluster. diff --git a/c/src/server/server_version.rs b/c/src/server/server_version.rs new file mode 100644 index 0000000000..ae3ee3e8db --- /dev/null +++ b/c/src/server/server_version.rs @@ -0,0 +1,48 @@ +/* + * 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 crate::common::memory::{release_string, string_free, free}; + +/// ServerVersion is an FFI representation of a full server's version specification. +#[repr(C)] +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 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 1e7bdfc1eb..3d6982ba41 100644 --- a/c/src/transaction.rs +++ b/c/src/transaction.rs @@ -20,9 +20,8 @@ use std::{ffi::c_char, ptr::null_mut}; 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; +use crate::common::{error::try_release, memory::{borrow, borrow_mut, free, release, take_ownership, string_view}, promise::VoidPromise}; /// Opens a transaction to perform read or write queries on the database connected to the session. /// diff --git a/c/src/transaction_options.rs b/c/src/transaction_options.rs index af2c3797ee..28e95b7c2d 100644 --- a/c/src/transaction_options.rs +++ b/c/src/transaction_options.rs @@ -21,7 +21,7 @@ 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}; /// Produces a new TransactionOptions object. #[no_mangle] 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 83% rename from c/src/user.rs rename to c/src/user/user.rs index 1fbba1d7b5..228706b8b8 100644 --- a/c/src/user.rs +++ b/c/src/user/user.rs @@ -21,11 +21,10 @@ use std::ffi::c_char; use typedb_driver::User; -use super::{ +use crate::common::{ error::unwrap_void, - memory::{borrow, free, release_string, string_view}, + memory::{borrow,take_ownership, free, release_string, string_view}, }; -use crate::memory::take_ownership; /// Frees the native rust User object. #[no_mangle] @@ -39,12 +38,6 @@ pub extern "C" fn user_get_name(user: *mut User) -> *mut c_char { 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_manager.rs b/c/src/user/user_manager.rs similarity index 93% rename from c/src/user_manager.rs rename to c/src/user/user_manager.rs index fefa0f8e87..396d38fb0b 100644 --- a/c/src/user_manager.rs +++ b/c/src/user/user_manager.rs @@ -19,14 +19,13 @@ use std::{ffi::c_char, ptr::addr_of_mut}; -use typedb_driver::{box_stream, TypeDBDriver, User, UserManager}; +use typedb_driver::{box_stream, TypeDBDriver, User}; -use super::{ +use crate::common::{ error::{try_release, try_release_optional, unwrap_or_default, unwrap_void}, iterator::{iterator_next, CIterator}, - memory::{borrow, free, release, string_view}, + memory::{borrow, free, string_view}, }; -use crate::{error::try_release_string, memory::release_string}; /// Iterator over a set of Users pub struct UserIterator(CIterator); 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 310dd51761..3c382312de 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 {}; @@ -174,7 +176,9 @@ 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_options_new; %newobject driver_primary_replica; %newobject driver_replicas; diff --git a/java/TypeDB.java b/java/TypeDB.java index 93e530c2bc..790ff1e396 100644 --- a/java/TypeDB.java +++ b/java/TypeDB.java @@ -25,6 +25,9 @@ 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"; @@ -43,4 +46,36 @@ public class TypeDB { 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 connection settings 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(address);
+     * 
+ * + * @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 connection settings 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/connection/DriverImpl.java b/java/connection/DriverImpl.java index cc7d6b312c..f11fbfbfee 100644 --- a/java/connection/DriverImpl.java +++ b/java/connection/DriverImpl.java @@ -32,13 +32,18 @@ import com.typedb.driver.common.exception.TypeDBDriverException; import com.typedb.driver.user.UserManagerImpl; +import java.util.ArrayList; import java.util.HashSet; +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_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; public class DriverImpl extends NativeObject implements Driver { @@ -46,6 +51,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); } @@ -55,7 +68,35 @@ 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 { + List publicAddresses = new ArrayList<>(); + List privateAddresses = new ArrayList<>(); + for (Map.Entry entry: addressTranslation.entrySet()) { + publicAddresses.add(entry.getKey()); + privateAddresses.add(entry.getValue()); + } + return driver_new_with_address_translation_with_description(publicAddresses.toArray(new String[0]), privateAddresses.toArray(new String[0]), credentials.nativeObject, driverOptions.nativeObject, LANGUAGE); } catch (com.typedb.driver.jni.Error e) { throw new TypeDBDriverException(e); } diff --git a/python/typedb/connection/driver.py b/python/typedb/connection/driver.py index 827b16a1e0..2ee93f4d18 100644 --- a/python/typedb/connection/driver.py +++ b/python/typedb/connection/driver.py @@ -26,7 +26,7 @@ from typedb.common.validation import require_non_null from typedb.connection.database_manager import _DatabaseManager from typedb.connection.transaction import _Transaction -from typedb.native_driver_wrapper import driver_open_with_description, driver_is_open, driver_force_close, \ +from typedb.native_driver_wrapper import driver_new_with_description, driver_is_open, driver_force_close, \ TypeDBDriver as NativeDriver, TypeDBDriverExceptionNative from typedb.user.user_manager import _UserManager @@ -44,7 +44,7 @@ def __init__(self, address: str, credentials: Credentials, driver_options: Drive require_non_null(credentials, "credentials") require_non_null(driver_options, "driver_options") try: - native_driver = driver_open_with_description(address, credentials.native_object, + native_driver = driver_new_with_description(address, credentials.native_object, driver_options.native_object, Driver.LANGUAGE) except TypeDBDriverExceptionNative as e: raise TypeDBDriverException.of(e) from None From 883cfd0d577096d9065748aad8602943ade5e9d8 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Wed, 2 Jul 2025 12:59:39 +0100 Subject: [PATCH 20/35] Refactor driver options. Propagated it to Java, not working yet --- .factory/automation.yml | 68 +++++----- c/src/answer.rs | 16 ++- c/src/common/memory.rs | 2 +- c/src/common/mod.rs | 15 +-- c/src/concept/concept.rs | 10 +- c/src/concept/mod.rs | 8 +- c/src/credentials.rs | 14 +-- c/src/database/database.rs | 3 +- c/src/database/database_manager.rs | 12 +- c/src/driver.rs | 18 +-- c/src/driver_options.rs | 55 +++++--- c/src/lib.rs | 4 +- c/src/query_options.rs | 8 +- c/src/server/server_replica.rs | 2 +- c/src/server/server_version.rs | 6 +- c/src/transaction.rs | 11 +- c/src/transaction_options.rs | 6 +- c/src/user/user.rs | 2 +- c/swig/typedb_driver_java.swg | 7 ++ c/typedb_driver.i | 4 +- .../ROOT/partials/c/session/options.adoc | 22 ++-- .../ROOT/partials/cpp/session/Options.adoc | 2 +- .../csharp/session/TypeDBOptions.adoc | 2 +- .../java/connection/DriverOptions.adoc | 119 ++++++++++++++++-- .../ROOT/partials/java/connection/TypeDB.adoc | 72 +++++++++++ .../java/transaction/QueryOptions.adoc | 4 +- .../java/transaction/TransactionOptions.adoc | 2 +- .../rust/connection/DriverOptions.adoc | 54 +++++++- java/README.md | 2 +- java/TypeDBExample.java | 5 +- java/api/DriverOptions.java | 98 ++++++++++++--- java/api/QueryOptions.java | 4 +- java/api/TransactionOptions.java | 3 +- .../connection/ConnectionStepsBase.java | 8 +- .../connection/ConnectionStepsCluster.java | 12 +- .../connection/ConnectionStepsCommunity.java | 8 +- .../application/MavenApplicationTest.java | 2 +- java/test/integration/ExampleTest.java | 4 +- java/test/integration/ValueTest.java | 2 +- rust/src/connection/driver_options.rs | 47 +++++-- rust/src/connection/network/channel.rs | 4 +- 41 files changed, 547 insertions(+), 200 deletions(-) diff --git a/.factory/automation.yml b/.factory/automation.yml index 221b949adf..1990867c32 100644 --- a/.factory/automation.yml +++ b/.factory/automation.yml @@ -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 - # - # source 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 @@ -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 - # - # source 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/c/src/answer.rs b/c/src/answer.rs index 6f857c15b5..3c298b77e6 100644 --- a/c/src/answer.rs +++ b/c/src/answer.rs @@ -27,15 +27,13 @@ use typedb_driver::{ }; use crate::{ - concept::ConceptIterator, - common::iterator::CIterator, - common::memory::{borrow, free, release, release_string, string_view}, -}; -use crate::{ - common::StringIterator, - concept::ConceptRowIterator, - common::error::{try_release, try_release_optional}, - common::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. diff --git a/c/src/common/memory.rs b/c/src/common/memory.rs index 6a6b7e1741..5557d00fb5 100644 --- a/c/src/common/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, }; diff --git a/c/src/common/mod.rs b/c/src/common/mod.rs index 5d495ad60f..ed9acb0c5b 100644 --- a/c/src/common/mod.rs +++ b/c/src/common/mod.rs @@ -17,9 +17,8 @@ * under the License. */ -use std::{ffi::c_char, ptr::null_mut}; -use std::collections::HashMap; -use std::hash::Hash; +use std::{collections::HashMap, ffi::c_char, hash::Hash, ptr::null_mut}; + use itertools::Itertools; use typedb_driver::Result; @@ -30,8 +29,7 @@ pub(crate) mod promise; use error::try_release_string; use iterator::CIterator; -use memory::borrow_mut; -use memory::free; +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>); @@ -40,7 +38,7 @@ pub struct StringIterator(pub CIterator>); /// or null if there are no more elements. #[no_mangle] pub extern "C" fn string_iterator_next(it: *mut StringIterator) -> *mut c_char { - borrow_mut(it).0.0.next().map(try_release_string).unwrap_or_else(null_mut) + borrow_mut(it).0 .0.next().map(try_release_string).unwrap_or_else(null_mut) } /// Frees the native rust StringIterator object @@ -49,6 +47,9 @@ 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 { +pub(crate) fn iterators_to_map( + keys: impl Iterator, + values: impl Iterator, +) -> HashMap { keys.zip_eq(values).collect() } diff --git a/c/src/concept/concept.rs b/c/src/concept/concept.rs index f2389afc9d..b5e04a3469 100644 --- a/c/src/concept/concept.rs +++ b/c/src/concept/concept.rs @@ -24,7 +24,7 @@ use typedb_driver::{ box_stream, concept::{ value::{Decimal, Duration, TimeZone}, - Attribute, Concept, Entity,Relation, Value, + Attribute, Concept, Entity, Relation, Value, }, }; @@ -147,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. @@ -156,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. @@ -166,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. @@ -293,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)), } } diff --git a/c/src/concept/mod.rs b/c/src/concept/mod.rs index 03faf5a9d9..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 crate::common::{iterator::iterator_try_next, memory::free}; -use crate::common::{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/credentials.rs b/c/src/credentials.rs index 8ff5face3a..7d1a11ecee 100644 --- a/c/src/credentials.rs +++ b/c/src/credentials.rs @@ -17,19 +17,11 @@ * under the License. */ -use std::{ffi::c_char}; +use std::ffi::c_char; -use typedb_driver::{ - Credentials - -}; +use typedb_driver::Credentials; -use crate::common::{ - memory::{free, string_view}, -}; -use crate::common::{ - memory::{release}, -}; +use crate::common::memory::{free, release, string_view}; /// Creates a new Credentials for connecting to TypeDB Server. /// diff --git a/c/src/database/database.rs b/c/src/database/database.rs index 27eb8f13bd..aa3641ba1d 100644 --- a/c/src/database/database.rs +++ b/c/src/database/database.rs @@ -23,9 +23,8 @@ use typedb_driver::Database; use crate::common::{ error::{try_release_string, unwrap_void}, - memory::{borrow, release_string}, + memory::{borrow, decrement_arc, release_string, string_view, take_arc}, }; -use crate::common::memory::{decrement_arc, string_view, take_arc}; /// Frees the native rust Database object #[no_mangle] diff --git a/c/src/database/database_manager.rs b/c/src/database/database_manager.rs index c9e044d1ad..fe9dff65be 100644 --- a/c/src/database/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 crate::common::{ - error::{try_release, try_release_arc, unwrap_or_default, unwrap_void}, - iterator::{iterator_arc_next, CIterator}, - memory::{borrow_mut, borrow_optional, 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, borrow_optional, free, string_view}, + }, + server::consistency_level::ConsistencyLevel, }; -use crate::server::consistency_level::ConsistencyLevel; /// An Iterator over databases present on the TypeDB server. pub struct DatabaseIterator(CIterator>); diff --git a/c/src/driver.rs b/c/src/driver.rs index 87da4a285c..ef6a3116d6 100644 --- a/c/src/driver.rs +++ b/c/src/driver.rs @@ -18,14 +18,18 @@ */ 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_view}}; -use crate::common::memory::string_array_view; -use crate::server::server_replica::ServerReplicaIterator; +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::server_replica::ServerReplicaIterator, +}; const DRIVER_LANG: &'static str = "c"; diff --git a/c/src/driver_options.rs b/c/src/driver_options.rs index 38776801ae..ccfe5137d5 100644 --- a/c/src/driver_options.rs +++ b/c/src/driver_options.rs @@ -19,29 +19,56 @@ use std::{ffi::c_char, path::Path}; -use typedb_driver::{ - DriverOptions, -}; +use typedb_driver::DriverOptions; use crate::common::{ - error::{try_release}, - memory::{free, string_view,}, + error::unwrap_void, + memory::{borrow, borrow_mut, borrow_optional, free, release, release_string, string_view}, }; -// TODO: Refactor - /// 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(is_tls_enabled).tls_root_ca(tls_root_ca_path)) +pub extern "C" fn driver_options_new() -> *mut DriverOptions { + release(DriverOptions::new()) } -/// Frees the native rust DriverOptions object +/// 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 setsfor 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() +} diff --git a/c/src/lib.rs b/c/src/lib.rs index 02b79c68b2..07d86ccce2 100644 --- a/c/src/lib.rs +++ b/c/src/lib.rs @@ -20,12 +20,12 @@ mod answer; mod common; mod concept; -mod driver; mod credentials; -mod server; mod database; +mod driver; mod driver_options; mod query_options; +mod server; mod transaction; mod transaction_options; mod user; diff --git a/c/src/query_options.rs b/c/src/query_options.rs index 8699963863..4190a3d6f7 100644 --- a/c/src/query_options.rs +++ b/c/src/query_options.rs @@ -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] @@ -49,13 +49,13 @@ pub extern "C" fn query_options_get_include_instance_types(options: *const Query borrow(options).include_instance_types.unwrap() } -/// Checks whether the "include instance types" flag was explicitly set for this QueryOptions object. +/// Checks whether the "include instance types" flag was Explicitly setsfor this QueryOptions object. #[no_mangle] pub extern "C" fn query_options_has_include_instance_types(options: *const QueryOptions) -> bool { 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. @@ -75,7 +75,7 @@ pub extern "C" fn query_options_get_prefetch_size(options: *const QueryOptions) borrow(options).prefetch_size.unwrap() as i64 } -/// Checks whether the prefetch size was explicitly set for this QueryOptions object. +/// Checks whether the prefetch size was Explicitly setsfor this QueryOptions object. #[no_mangle] pub extern "C" fn query_options_has_prefetch_size(options: *const QueryOptions) -> bool { borrow(options).prefetch_size.is_some() diff --git a/c/src/server/server_replica.rs b/c/src/server/server_replica.rs index 69c99bd71d..265d89c8ad 100644 --- a/c/src/server/server_replica.rs +++ b/c/src/server/server_replica.rs @@ -21,9 +21,9 @@ use std::{ffi::c_char, ptr::addr_of_mut}; use typedb_driver::{ReplicaType, ServerReplica}; -use crate::common::memory::{borrow, release_string, free}; use crate::common::{ iterator::{iterator_next, CIterator}, + memory::{borrow, free, release_string}, }; /// Iterator over the ServerReplica corresponding to each replica of a TypeDB cluster. diff --git a/c/src/server/server_version.rs b/c/src/server/server_version.rs index ae3ee3e8db..746b0fd79b 100644 --- a/c/src/server/server_version.rs +++ b/c/src/server/server_version.rs @@ -17,9 +17,11 @@ * under the License. */ -use std::{ffi::c_char}; +use std::ffi::c_char; -use crate::common::memory::{release_string, string_free, free}; +use crate::common::memory::{free, release_string, string_free}; + +// TODO: Use /// ServerVersion is an FFI representation of a full server's version specification. #[repr(C)] diff --git a/c/src/transaction.rs b/c/src/transaction.rs index 3d6982ba41..a822291ae7 100644 --- a/c/src/transaction.rs +++ b/c/src/transaction.rs @@ -20,8 +20,15 @@ use std::{ffi::c_char, ptr::null_mut}; use typedb_driver::{Error, QueryOptions, Transaction, TransactionOptions, TransactionType, TypeDBDriver}; -use crate::answer::QueryAnswerPromise; -use crate::common::{error::try_release, memory::{borrow, borrow_mut, free, release, take_ownership, 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. /// diff --git a/c/src/transaction_options.rs b/c/src/transaction_options.rs index 28e95b7c2d..d489695183 100644 --- a/c/src/transaction_options.rs +++ b/c/src/transaction_options.rs @@ -35,7 +35,7 @@ pub extern "C" fn transaction_options_drop(options: *mut TransactionOptions) { free(options); } -/// Explicitly set a transaction timeout. +/// Explicitly setsa 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( @@ -52,7 +52,7 @@ pub extern "C" fn transaction_options_get_transaction_timeout_millis(options: *c borrow(options).transaction_timeout.unwrap().as_millis() as i64 } -/// Checks whether the option for transaction timeout was explicitly set for this TransactionOptions object. +/// Checks whether the option for transaction timeout was Explicitly setsfor this TransactionOptions object. #[no_mangle] pub extern "C" fn transaction_options_has_transaction_timeout_millis(options: *const TransactionOptions) -> bool { borrow(options).transaction_timeout.is_some() @@ -77,7 +77,7 @@ pub extern "C" fn transaction_options_get_schema_lock_acquire_timeout_millis( borrow(options).schema_lock_acquire_timeout.unwrap().as_millis() as i64 } -/// Checks whether the option for schema lock acquire timeout was explicitly set for this TransactionOptions object. +/// Checks whether the option for schema lock acquire timeout was Explicitly setsfor this TransactionOptions object. #[no_mangle] pub extern "C" fn transaction_options_has_schema_lock_acquire_timeout_millis( options: *const TransactionOptions, diff --git a/c/src/user/user.rs b/c/src/user/user.rs index 228706b8b8..30de506637 100644 --- a/c/src/user/user.rs +++ b/c/src/user/user.rs @@ -23,7 +23,7 @@ use typedb_driver::User; use crate::common::{ error::unwrap_void, - memory::{borrow,take_ownership, free, release_string, string_view}, + memory::{borrow, free, release_string, string_view, take_ownership}, }; /// Frees the native rust User object. diff --git a/c/swig/typedb_driver_java.swg b/c/swig/typedb_driver_java.swg index f6bf2f2ba1..ba1478e7f2 100644 --- a/c/swig/typedb_driver_java.swg +++ b/c/swig/typedb_driver_java.swg @@ -107,6 +107,13 @@ %nojavaexception driver_replicas; %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 transaction_is_open; %nojavaexception user_get_name; diff --git a/c/typedb_driver.i b/c/typedb_driver.i index 3c382312de..749e70ce39 100644 --- a/c/typedb_driver.i +++ b/c/typedb_driver.i @@ -179,10 +179,12 @@ void transaction_on_close_register(const Transaction* transaction, TransactionCa %newobject driver_new_with_description; %newobject driver_new_with_addresses_with_description; %newobject driver_new_with_address_translation_with_description; -%newobject driver_options_new; %newobject driver_primary_replica; %newobject driver_replicas; +%newobject driver_options_new; +%newobject driver_options_get_tls_root_ca_path; + %newobject database_get_name; %newobject database_schema; %newobject database_type_schema; diff --git a/docs/modules/ROOT/partials/c/session/options.adoc b/docs/modules/ROOT/partials/c/session/options.adoc index 91923d882b..41578e47b3 100644 --- a/docs/modules/ROOT/partials/c/session/options.adoc +++ b/docs/modules/ROOT/partials/c/session/options.adoc @@ -195,7 +195,7 @@ bool options_has_explain(const struct Options* options) -Checks whether the option for explanation was explicitly set for this ``TypeDBOptions`` object. +Checks whether the option for explanation was Explicitly setsfor this ``TypeDBOptions`` object. [caption=""] .Returns @@ -211,7 +211,7 @@ bool options_has_infer(const struct Options* options) -Checks whether the option for inference was explicitly set for this ``TypeDBOptions`` object. +Checks whether the option for inference was Explicitly setsfor this ``TypeDBOptions`` object. [caption=""] .Returns @@ -227,7 +227,7 @@ bool options_has_parallel(const struct Options* options) -Checks whether the option for parallel execution was explicitly set for this ``TypeDBOptions`` object. +Checks whether the option for parallel execution was Explicitly setsfor this ``TypeDBOptions`` object. [caption=""] .Returns @@ -243,7 +243,7 @@ bool options_has_prefetch(const struct Options* options) -Checks whether the option for prefetching was explicitly set for this ``TypeDBOptions`` object. +Checks whether the option for prefetching was Explicitly setsfor this ``TypeDBOptions`` object. [caption=""] .Returns @@ -259,7 +259,7 @@ bool options_has_prefetch_size(const struct Options* options) -Checks whether the option for prefetch size was explicitly set for this ``TypeDBOptions`` object. +Checks whether the option for prefetch size was Explicitly setsfor this ``TypeDBOptions`` object. [caption=""] .Returns @@ -275,7 +275,7 @@ bool options_has_read_any_replica(const struct Options* options) -Checks whether the option for reading data from any replica was explicitly set for this ``TypeDBOptions`` object. +Checks whether the option for reading data from any replica was Explicitly setsfor this ``TypeDBOptions`` object. [caption=""] .Returns @@ -291,7 +291,7 @@ bool options_has_schema_lock_acquire_timeout_millis(const struct Options* option -Checks whether the option for schema lock acquire timeout was explicitly set for this ``TypeDBOptions`` object. +Checks whether the option for schema lock acquire timeout was Explicitly setsfor this ``TypeDBOptions`` object. [caption=""] .Returns @@ -307,7 +307,7 @@ bool options_has_session_idle_timeout_millis(const struct Options* options) -Checks whether the option for the session idle timeout was explicitly set for this ``TypeDBOptions`` object. +Checks whether the option for the session idle timeout was Explicitly setsfor this ``TypeDBOptions`` object. [caption=""] .Returns @@ -323,7 +323,7 @@ bool options_has_trace_inference(const struct Options* options) -Checks whether the option for reasoning tracing was explicitly set for this ``TypeDBOptions`` object. +Checks whether the option for reasoning tracing was Explicitly setsfor this ``TypeDBOptions`` object. [caption=""] .Returns @@ -339,7 +339,7 @@ bool options_has_transaction_timeout_millis(const struct Options* options) -Checks whether the option for transaction timeout was explicitly set for this ``TypeDBOptions`` object. +Checks whether the option for transaction timeout was Explicitly setsfor this ``TypeDBOptions`` object. [caption=""] .Returns @@ -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 setsa 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/session/Options.adoc b/docs/modules/ROOT/partials/cpp/session/Options.adoc index 0109dd1023..af8c0c05da 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 setsa 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/session/TypeDBOptions.adoc b/docs/modules/ROOT/partials/csharp/session/TypeDBOptions.adoc index e2f4c3e88b..5182eb8d10 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 setsa 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/DriverOptions.adoc b/docs/modules/ROOT/partials/java/connection/DriverOptions.adoc index 827a527e17..1cb6981914 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=""] -.Examples +.Returns +`public` + +[caption=""] +.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,73 @@ 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_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")); +---- // end::methods[] diff --git a/docs/modules/ROOT/partials/java/connection/TypeDB.adoc b/docs/modules/ROOT/partials/java/connection/TypeDB.adoc index 61a6354e29..f5793c936c 100644 --- a/docs/modules/ROOT/partials/java/connection/TypeDB.adoc +++ b/docs/modules/ROOT/partials/java/connection/TypeDB.adoc @@ -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 connection settings 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 connection settings to connect with a| `DriverOptions` +|=== + +[caption=""] +.Returns +`public static Driver` + +[caption=""] +.Code examples +[source,java] +---- +TypeDB.driver(address); +---- + // 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..43994798f3 100644 --- a/docs/modules/ROOT/partials/java/transaction/TransactionOptions.adoc +++ b/docs/modules/ROOT/partials/java/transaction/TransactionOptions.adoc @@ -112,7 +112,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 setsa 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/rust/connection/DriverOptions.adoc b/docs/modules/ROOT/partials/rust/connection/DriverOptions.adoc index 912a0af254..1dd38ea588 100644 --- a/docs/modules/ROOT/partials/rust/connection/DriverOptions.adoc +++ b/docs/modules/ROOT/partials/rust/connection/DriverOptions.adoc @@ -24,7 +24,6 @@ a| `discovery_failover_retries` a| `Option` a| Limits the number of drive a| `is_tls_enabled` a| `bool` a| Specifies whether the connection to TypeDB must be done over TLS. Defaults to false. a| `redirect_failover_retries` a| `usize` a| 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. -a| `tls_config` a| `Option` a| If set, specifies the TLS config to use for server certificates authentication. Defaults to None. a| `use_replication` a| `bool` a| 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. |=== // end::properties[] @@ -50,6 +49,40 @@ Limits the number of driver attempts to discover a single working replica to per Self ---- +[#_struct_DriverOptions_get_tls_config_] +==== get_tls_config + +[source,rust] +---- +pub fn get_tls_config(&self) -> Option<&ClientTlsConfig> +---- + +Retrieves the TLS config of this options object if configured. + +[caption=""] +.Returns +[source,rust] +---- +Option<&ClientTlsConfig> +---- + +[#_struct_DriverOptions_get_tls_root_ca_] +==== get_tls_root_ca + +[source,rust] +---- +pub fn get_tls_root_ca(&self) -> Option<&Path> +---- + +Retrieves the TLS root CA path of this options object if configured. + +[caption=""] +.Returns +[source,rust] +---- +Option<&Path> +---- + [#_struct_DriverOptions_is_tls_enabled_] ==== is_tls_enabled @@ -84,6 +117,23 @@ Limits the number of attempts to redirect a strongly consistent request to anoth Self ---- +[#_struct_DriverOptions_set_tls_root_ca_] +==== set_tls_root_ca + +[source,rust] +---- +pub fn set_tls_root_ca(&mut self, tls_root_ca: Option<&Path>) -> Result +---- + +Specifies the root CA used in the TLS config for server certificates authentication. Uses system roots if None is set. See <<#_struct_DriverOptions_method_is_tls_enabled,`Self::is_tls_enabled`>> to enable or disable TLS. + +[caption=""] +.Returns +[source,rust] +---- +Result +---- + [#_struct_DriverOptions_tls_root_ca_] ==== tls_root_ca @@ -92,7 +142,7 @@ Self pub fn tls_root_ca(self, tls_root_ca: Option<&Path>) -> Result ---- -If set, specifies the path to the CA certificate to use for server certificates authentication. +Specifies the root CA used in the TLS config for server certificates authentication. Uses system roots if None is set. See <<#_struct_DriverOptions_method_is_tls_enabled,`Self::is_tls_enabled`>> to enable or disable TLS. [caption=""] .Returns 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/TypeDBExample.java b/java/TypeDBExample.java index a63390f7c7..634e46710b 100644 --- a/java/TypeDBExample.java +++ b/java/TypeDBExample.java @@ -2,12 +2,10 @@ // It is not intended for manual editing. package com.typedb.driver; -import com.typedb.driver.TypeDB; import com.typedb.driver.api.Credentials; import com.typedb.driver.api.Driver; import com.typedb.driver.api.DriverOptions; import com.typedb.driver.api.QueryOptions; -import com.typedb.driver.api.QueryType; import com.typedb.driver.api.Transaction; import com.typedb.driver.api.TransactionOptions; import com.typedb.driver.api.answer.ConceptRow; @@ -23,7 +21,6 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -31,7 +28,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())) { // Creates a database driver.databases().create("typedb"); Database database = driver.databases().get("typedb"); diff --git a/java/api/DriverOptions.java b/java/api/DriverOptions.java index f14e42ff9c..131a972057 100644 --- a/java/api/DriverOptions.java +++ b/java/api/DriverOptions.java @@ -20,34 +20,96 @@ package com.typedb.driver.api; import com.typedb.driver.common.NativeObject; -import com.typedb.driver.common.exception.TypeDBDriverException; -import javax.annotation.Nullable; +import javax.annotation.CheckReturnValue; + +import java.util.Optional; import static com.typedb.driver.jni.typedb_driver.driver_options_new; +import static com.typedb.driver.jni.typedb_driver.driver_options_get_is_tls_enabled; +import static com.typedb.driver.jni.typedb_driver.driver_options_set_is_tls_enabled; +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_get_tls_root_ca_path; +import static com.typedb.driver.jni.typedb_driver.driver_options_set_tls_root_ca_path; /** - * 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(boolean isTlsEnabled, String tlsRootCAPath) { // TODO: Maybe Optional? Optional.of(Path.of(..))?.. - super(newNative(isTlsEnabled, tlsRootCAPath)); + public DriverOptions() { + super(driver_options_new()); } - 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 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 tlsRootCAPath(Optional tlsRootCAPath) { + driver_options_set_tls_root_ca_path(nativeObject, tlsRootCAPath.orElse(null)); + return this; + } + + // TODO: Add other flags when they are finalized! } 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..4427e2ed98 100644 --- a/java/api/TransactionOptions.java +++ b/java/api/TransactionOptions.java @@ -33,7 +33,6 @@ 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 +67,7 @@ public Optional transactionTimeoutMillis() { } /** - * Explicitly set a transaction timeout. + * Explicitly setsa transaction timeout. * If set, specifies how long the driver should wait if opening a transaction is blocked by an exclusive schema write lock. * *

Examples

diff --git a/java/test/behaviour/connection/ConnectionStepsBase.java b/java/test/behaviour/connection/ConnectionStepsBase.java index d2df6a82c1..3e2478d282 100644 --- a/java/test/behaviour/connection/ConnectionStepsBase.java +++ b/java/test/behaviour/connection/ConnectionStepsBase.java @@ -30,9 +30,7 @@ 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; @@ -46,8 +44,6 @@ 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 +53,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; @@ -115,6 +112,7 @@ void after() { cleanupTransactions(); cleanupBackgroundTransactions(); + driverOptions = new DriverOptions(); transactionOptions = Optional.empty(); queryOptions = Optional.empty(); @@ -148,7 +146,7 @@ void cleanupBackgroundTransactions() { abstract Driver createDefaultTypeDBDriver(); - public static void initTransactionOptionsIfNeeded() { // TODO: Implement steps + public static void initTransactionOptionsIfNeeded() { if (transactionOptions.isEmpty()) { transactionOptions = Optional.of(new TransactionOptions()); } diff --git a/java/test/behaviour/connection/ConnectionStepsCluster.java b/java/test/behaviour/connection/ConnectionStepsCluster.java index 10a3ff029c..8f76676e76 100644 --- a/java/test/behaviour/connection/ConnectionStepsCluster.java +++ b/java/test/behaviour/connection/ConnectionStepsCluster.java @@ -28,6 +28,8 @@ import io.cucumber.java.en.Given; import io.cucumber.java.en.When; +import java.util.Optional; + public class ConnectionStepsCluster extends ConnectionStepsBase { @Override public void beforeAll() { @@ -36,6 +38,7 @@ public void beforeAll() { @Before public synchronized void before() { + driverOptions = driverOptions.isTlsEnabled(true).tlsRootCAPath(Optional.of(System.getenv("ROOT_CA"))); super.before(); } @@ -51,8 +54,7 @@ Driver createTypeDBDriver(String address, Credentials credentials, DriverOptions @Override Driver createDefaultTypeDBDriver() { - // TODO: Add encryption to cluster tests - return createTypeDBDriver(TypeDB.DEFAULT_ADDRESS, DEFAULT_CREDENTIALS, DEFAULT_CONNECTION_SETTINGS); + return createTypeDBDriver(TypeDB.DEFAULT_ADDRESS, DEFAULT_CREDENTIALS, driverOptions); } @When("typedb starts") @@ -67,7 +69,7 @@ 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}") @@ -75,7 +77,7 @@ public void connection_opens_with_a_wrong_host(Parameters.MayError mayError) { mayError.check(() -> driver = createTypeDBDriver( TypeDB.DEFAULT_ADDRESS.replace("localhost", "surely-not-localhost"), DEFAULT_CREDENTIALS, - DEFAULT_CONNECTION_SETTINGS + driverOptions )); } @@ -84,7 +86,7 @@ public void connection_opens_with_a_wrong_port(Parameters.MayError mayError) { mayError.check(() -> driver = createTypeDBDriver( TypeDB.DEFAULT_ADDRESS.replace("localhost", "surely-not-localhost"), DEFAULT_CREDENTIALS, - DEFAULT_CONNECTION_SETTINGS + driverOptions )); } diff --git a/java/test/behaviour/connection/ConnectionStepsCommunity.java b/java/test/behaviour/connection/ConnectionStepsCommunity.java index 2e0a619de8..71a2337566 100644 --- a/java/test/behaviour/connection/ConnectionStepsCommunity.java +++ b/java/test/behaviour/connection/ConnectionStepsCommunity.java @@ -52,7 +52,7 @@ Driver createTypeDBDriver(String address, Credentials 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,7 +67,7 @@ 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}") @@ -75,7 +75,7 @@ public void connection_opens_with_a_wrong_host(Parameters.MayError mayError) { mayError.check(() -> driver = createTypeDBDriver( TypeDB.DEFAULT_ADDRESS.replace("localhost", "surely-not-localhost"), DEFAULT_CREDENTIALS, - DEFAULT_CONNECTION_SETTINGS + driverOptions )); } @@ -84,7 +84,7 @@ public void connection_opens_with_a_wrong_port(Parameters.MayError mayError) { mayError.check(() -> driver = createTypeDBDriver( TypeDB.DEFAULT_ADDRESS.replace("localhost", "surely-not-localhost"), DEFAULT_CREDENTIALS, - DEFAULT_CONNECTION_SETTINGS + driverOptions )); } 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/rust/src/connection/driver_options.rs b/rust/src/connection/driver_options.rs index 0f90e79f8e..ed76b1962e 100644 --- a/rust/src/connection/driver_options.rs +++ b/rust/src/connection/driver_options.rs @@ -17,13 +17,17 @@ * under the License. */ -use std::{fs, path::Path}; +use std::{ + fs, + path::{Path, PathBuf}, +}; use tonic::transport::{Certificate, ClientTlsConfig}; // 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; @@ -42,9 +46,6 @@ pub struct DriverOptions { /// Specifies whether the connection to TypeDB must be done over TLS. /// Defaults to false. pub is_tls_enabled: bool, - /// If set, specifies the TLS config to use for server certificates authentication. - /// Defaults to None. - pub tls_config: Option, /// 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. @@ -63,6 +64,9 @@ pub struct DriverOptions { /// primary replica is unknown. /// Defaults to None. pub discovery_failover_retries: Option, + + tls_config: Option, + tls_root_ca: Option, } impl DriverOptions { @@ -75,14 +79,35 @@ impl DriverOptions { Self { is_tls_enabled, ..self } } - /// If set, specifies the path to the CA certificate to use for server certificates authentication. - pub fn tls_root_ca(self, tls_root_ca: Option<&Path>) -> crate::Result { - let tls_config = Some(if let Some(tls_root_ca) = tls_root_ca { + /// 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() - }); - Ok(Self { tls_config, ..self }) + }; + self.tls_config = Some(tls_config); + Ok(()) + } + + /// Retrieves the TLS config of this options object if configured. + pub fn get_tls_config(&self) -> Option<&ClientTlsConfig> { + self.tls_config.as_ref() + } + + /// 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() } /// Specifies whether the connection to TypeDB can use cluster replicas provided by the server @@ -117,10 +142,12 @@ impl Default for DriverOptions { fn default() -> Self { Self { is_tls_enabled: DEFAULT_IS_TLS_ENABLED, - tls_config: DEFAULT_TLS_CONFIG, use_replication: DEFAULT_USE_REPLICATION, redirect_failover_retries: DEFAULT_REDIRECT_FAILOVER_RETRIES, discovery_failover_retries: DEFAULT_DISCOVERY_FAILOVER_RETRIES, + + tls_config: DEFAULT_TLS_CONFIG, + tls_root_ca: DEFAULT_TLS_ROOT_CA_PATH, } } } diff --git a/rust/src/connection/network/channel.rs b/rust/src/connection/network/channel.rs index 0c53b5c772..4f43ef058f 100644 --- a/rust/src/connection/network/channel.rs +++ b/rust/src/connection/network/channel.rs @@ -55,8 +55,8 @@ pub(super) fn open_callcred_channel( let mut builder = Channel::builder(address.into_uri()); if driver_options.is_tls_enabled { let tls_config = driver_options - .tls_config - .clone() + .get_tls_config() + .cloned() .ok_or_else(|| Error::Connection(ConnectionError::MissingTlsConfigForTls))?; builder = builder.tls_config(tls_config)?; } From f98315c7f11f78f135e6ea471f10b6289fbc9162 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Wed, 2 Jul 2025 14:48:02 +0100 Subject: [PATCH 21/35] Fix java bbds --- java/TypeDB.java | 2 +- .../connection/ConnectionStepsBase.java | 4 +--- .../connection/ConnectionStepsCluster.java | 19 ++++++++++++++----- .../connection/ConnectionStepsCommunity.java | 5 ++--- python/typedb/driver.py | 2 +- rust/src/driver.rs | 4 ++-- 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/java/TypeDB.java b/java/TypeDB.java index 790ff1e396..bc3cca63dd 100644 --- a/java/TypeDB.java +++ b/java/TypeDB.java @@ -29,7 +29,7 @@ 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. diff --git a/java/test/behaviour/connection/ConnectionStepsBase.java b/java/test/behaviour/connection/ConnectionStepsBase.java index 3e2478d282..62097ee1b3 100644 --- a/java/test/behaviour/connection/ConnectionStepsBase.java +++ b/java/test/behaviour/connection/ConnectionStepsBase.java @@ -112,7 +112,6 @@ void after() { cleanupTransactions(); cleanupBackgroundTransactions(); - driverOptions = new DriverOptions(); transactionOptions = Optional.empty(); queryOptions = Optional.empty(); @@ -122,6 +121,7 @@ void after() { driver.databases().all().forEach(database -> driver.databases().get(database.name()).delete()); driver.close(); backgroundDriver.close(); + driverOptions = new DriverOptions(); } void cleanupTransactions() { @@ -142,8 +142,6 @@ void cleanupBackgroundTransactions() { backgroundTransactions.clear(); } - abstract Driver createTypeDBDriver(String address, Credentials credentials, DriverOptions driverOptions); - abstract Driver createDefaultTypeDBDriver(); public static void initTransactionOptionsIfNeeded() { diff --git a/java/test/behaviour/connection/ConnectionStepsCluster.java b/java/test/behaviour/connection/ConnectionStepsCluster.java index 8f76676e76..09553dffcf 100644 --- a/java/test/behaviour/connection/ConnectionStepsCluster.java +++ b/java/test/behaviour/connection/ConnectionStepsCluster.java @@ -29,8 +29,14 @@ 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(); @@ -47,14 +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() { - return createTypeDBDriver(TypeDB.DEFAULT_ADDRESS, DEFAULT_CREDENTIALS, driverOptions); + return createTypeDBDriver(DEFAULT_CLUSTER_ADDRESSES, DEFAULT_CREDENTIALS, driverOptions); } @When("typedb starts") @@ -69,13 +78,13 @@ 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, driverOptions)); + 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, driverOptions )); @@ -84,7 +93,7 @@ public void connection_opens_with_a_wrong_host(Parameters.MayError mayError) { @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, driverOptions )); diff --git a/java/test/behaviour/connection/ConnectionStepsCommunity.java b/java/test/behaviour/connection/ConnectionStepsCommunity.java index 71a2337566..0ff8f5ea09 100644 --- a/java/test/behaviour/connection/ConnectionStepsCommunity.java +++ b/java/test/behaviour/connection/ConnectionStepsCommunity.java @@ -45,7 +45,6 @@ public synchronized void after() { super.after(); } - @Override Driver createTypeDBDriver(String address, Credentials credentials, DriverOptions driverOptions) { return TypeDB.driver(address, credentials, driverOptions); } @@ -73,7 +72,7 @@ public void connection_opens_with_username_password(String username, String pass @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, driverOptions )); @@ -82,7 +81,7 @@ public void connection_opens_with_a_wrong_host(Parameters.MayError mayError) { @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, driverOptions )); diff --git a/python/typedb/driver.py b/python/typedb/driver.py index f519d9724c..32c061d3a2 100644 --- a/python/typedb/driver.py +++ b/python/typedb/driver.py @@ -49,7 +49,7 @@ class TypeDB: - DEFAULT_ADDRESS = "localhost:1729" + DEFAULT_ADDRESS = "127.0.0.1:1729" @staticmethod def driver(address: str, credentials: Credentials, driver_options: DriverOptions) -> Driver: diff --git a/rust/src/driver.rs b/rust/src/driver.rs index f5cb331769..d907636d43 100644 --- a/rust/src/driver.rs +++ b/rust/src/driver.rs @@ -46,7 +46,7 @@ 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. /// @@ -103,6 +103,7 @@ impl TypeDBDriver { driver_options: DriverOptions, driver_lang: impl AsRef, ) -> Result { + println!("Opening a new driver with these options: {driver_options:?}, to: {addresses}"); let background_runtime = Arc::new(BackgroundRuntime::new()?); let server_manager = Arc::new( ServerManager::new( @@ -117,7 +118,6 @@ impl TypeDBDriver { ); 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 }) } From 57292e1f51be1aaed0023855c882ba754c488512 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Wed, 2 Jul 2025 15:30:01 +0100 Subject: [PATCH 22/35] Propagate new additional structs from Rust to Java --- c/src/database/database_manager.rs | 2 + c/src/driver.rs | 16 ++++-- c/src/server/mod.rs | 2 +- c/src/server/server_version.rs | 11 ++-- java/TypeDB.java | 4 +- java/api/ConsistencyLevel.java | 4 +- java/api/Driver.java | 12 +++++ java/api/DriverOptions.java | 7 ++- java/api/database/DatabaseManager.java | 2 +- java/api/server/ServerVersion.java | 22 +++----- java/connection/DriverImpl.java | 27 +++++++--- java/connection/ServerReplicaImpl.java | 54 +++++++++++++++++++ .../connection/ConnectionStepsCluster.java | 2 +- rust/src/connection/server/server_version.rs | 2 +- rust/src/driver.rs | 1 - 15 files changed, 127 insertions(+), 41 deletions(-) create mode 100644 java/connection/ServerReplicaImpl.java diff --git a/c/src/database/database_manager.rs b/c/src/database/database_manager.rs index fe9dff65be..f49eeef748 100644 --- a/c/src/database/database_manager.rs +++ b/c/src/database/database_manager.rs @@ -46,6 +46,8 @@ pub extern "C" fn database_iterator_drop(it: *mut DatabaseIterator) { free(it); } +// TODO: Propage consistency_levels to every read function! + /// Returns a DatabaseIterator over all databases present on the TypeDB server. /// /// @param driver The TypeDBDriver object. diff --git a/c/src/driver.rs b/c/src/driver.rs index ef6a3116d6..7cbe50f957 100644 --- a/c/src/driver.rs +++ b/c/src/driver.rs @@ -28,7 +28,7 @@ use crate::{ iterators_to_map, memory::{borrow, free, release, release_optional, string_array_view, string_view}, }, - server::server_replica::ServerReplicaIterator, + server::{server_replica::ServerReplicaIterator, server_version::ServerVersion}, }; const DRIVER_LANG: &'static str = "c"; @@ -171,16 +171,24 @@ pub extern "C" fn driver_close(driver: *mut TypeDBDriver) { free(driver); } +/// 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()); +} + /// 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. +/// Retrieves the server version and distribution information. #[no_mangle] -pub extern "C" fn driver_force_close(driver: *mut TypeDBDriver) { - unwrap_void(borrow(driver).force_close()); +pub extern "C" fn driver_server_version(driver: *const TypeDBDriver) -> *mut ServerVersion { + release(unwrap_or_default(borrow(driver).server_version().map(|server_version| { + ServerVersion::new(server_version.distribution().to_string(), server_version.version().to_string()) + }))) } /// Retrieves the server's replicas. diff --git a/c/src/server/mod.rs b/c/src/server/mod.rs index 0e64f5c031..4fe89b5313 100644 --- a/c/src/server/mod.rs +++ b/c/src/server/mod.rs @@ -19,4 +19,4 @@ pub(crate) mod consistency_level; pub(crate) mod server_replica; -mod server_version; +pub(crate) mod server_version; diff --git a/c/src/server/server_version.rs b/c/src/server/server_version.rs index 746b0fd79b..4509be51a2 100644 --- a/c/src/server/server_version.rs +++ b/c/src/server/server_version.rs @@ -17,14 +17,13 @@ * under the License. */ -use std::ffi::c_char; +use std::{ffi::c_char, ptr::null_mut}; use crate::common::memory::{free, release_string, string_free}; -// TODO: Use - /// 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, @@ -36,6 +35,12 @@ impl ServerVersion { } } +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); diff --git a/java/TypeDB.java b/java/TypeDB.java index bc3cca63dd..491b11dd96 100644 --- a/java/TypeDB.java +++ b/java/TypeDB.java @@ -72,8 +72,8 @@ public static Driver driver(Set addresses, Credentials credentials, Driv * * * @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 connection settings to connect with + * @param credentials The credentials to connect with + * @param driverOptions The connection settings 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/api/ConsistencyLevel.java b/java/api/ConsistencyLevel.java index e9e6a38d97..7996ffae48 100644 --- a/java/api/ConsistencyLevel.java +++ b/java/api/ConsistencyLevel.java @@ -19,11 +19,9 @@ package com.typedb.driver.api; -import com.typedb.driver.common.exception.TypeDBDriverException; - import static com.typedb.driver.jni.typedb_driver.consistency_level_eventual; -import static com.typedb.driver.jni.typedb_driver.consistency_level_strong; 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 database. All driver methods have default diff --git a/java/api/Driver.java b/java/api/Driver.java index 19e82259e4..c221a68626 100644 --- a/java/api/Driver.java +++ b/java/api/Driver.java @@ -21,6 +21,7 @@ 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; @@ -42,6 +43,17 @@ public interface Driver extends AutoCloseable { @CheckReturnValue boolean isOpen(); + /** + * Retrieves the server's version. + * + *

Examples

+ *
+     * driver.serverVersion();
+     * 
+ */ + @CheckReturnValue + ServerVersion serverVersion(); + /** * The DatabaseManager for this connection, providing access to database management methods. */ diff --git a/java/api/DriverOptions.java b/java/api/DriverOptions.java index 131a972057..0a58bb4ecb 100644 --- a/java/api/DriverOptions.java +++ b/java/api/DriverOptions.java @@ -22,14 +22,13 @@ import com.typedb.driver.common.NativeObject; import javax.annotation.CheckReturnValue; - import java.util.Optional; -import static com.typedb.driver.jni.typedb_driver.driver_options_new; import static com.typedb.driver.jni.typedb_driver.driver_options_get_is_tls_enabled; -import static com.typedb.driver.jni.typedb_driver.driver_options_set_is_tls_enabled; -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_get_tls_root_ca_path; +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_tls_root_ca_path; /** diff --git a/java/api/database/DatabaseManager.java b/java/api/database/DatabaseManager.java index 22982bf5f7..d1c0ce615b 100644 --- a/java/api/database/DatabaseManager.java +++ b/java/api/database/DatabaseManager.java @@ -92,7 +92,7 @@ public interface DatabaseManager { * driver.databases().all() * * - * @see #allWithConsistency(ConsistencyLevel) + * @see #all(ConsistencyLevel) */ @CheckReturnValue default List all() throws TypeDBDriverException { diff --git a/java/api/server/ServerVersion.java b/java/api/server/ServerVersion.java index cb525ec6a0..a402d2cf96 100644 --- a/java/api/server/ServerVersion.java +++ b/java/api/server/ServerVersion.java @@ -19,28 +19,22 @@ package com.typedb.driver.api.server; -import com.typedb.driver.common.exception.ErrorMessage; -import com.typedb.driver.common.exception.TypeDBDriverException; +import com.typedb.driver.common.NativeObject; /** - * Type of replica. + * A full TypeDB server's version specification. * *

Examples

*
- * replica.type();
+ * driver.serverVersion();
  * 
*/ -public class ServerVersion { - private final String distribution; - private final String version; - +public class ServerVersion extends NativeObject { /** * @hidden */ - ServerVersion(com.typedb.driver.jni.ServerVersion nativeObject) { - if (nativeObject == null) throw new TypeDBDriverException(ErrorMessage.Internal.NULL_NATIVE_VALUE); - this.distribution = nativeObject.getDistribution(); - this.version = nativeObject.getVersion(); + public ServerVersion(com.typedb.driver.jni.ServerVersion nativeObject) { + super(nativeObject); } /** @@ -52,7 +46,7 @@ public class ServerVersion { * */ public String getDistribution() { - return distribution; + return nativeObject.getDistribution(); } /** @@ -64,6 +58,6 @@ public String getDistribution() { * */ public String getVersion() { - return version; + return nativeObject.getVersion(); } } diff --git a/java/connection/DriverImpl.java b/java/connection/DriverImpl.java index f11fbfbfee..253c1b5c7e 100644 --- a/java/connection/DriverImpl.java +++ b/java/connection/DriverImpl.java @@ -26,14 +26,15 @@ 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.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -44,6 +45,10 @@ 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_replicas; +import static com.typedb.driver.jni.typedb_driver.driver_server_version; +import static java.util.stream.Collectors.toSet; public class DriverImpl extends NativeObject implements Driver { @@ -92,7 +97,7 @@ private static com.typedb.driver.jni.TypeDBDriver open(Map addre try { List publicAddresses = new ArrayList<>(); List privateAddresses = new ArrayList<>(); - for (Map.Entry entry: addressTranslation.entrySet()) { + for (Map.Entry entry : addressTranslation.entrySet()) { publicAddresses.add(entry.getKey()); privateAddresses.add(entry.getValue()); } @@ -107,6 +112,15 @@ public boolean isOpen() { return driver_is_open(nativeObject); } + @Override + public ServerVersion serverVersion() { + try { + return new ServerVersion(driver_server_version(nativeObject)); + } catch (com.typedb.driver.jni.Error e) { + throw new TypeDBDriverException(e); + } + } + @Override public UserManager users() { return new UserManagerImpl(nativeObject); @@ -131,14 +145,15 @@ public Transaction transaction(String database, Transaction.Type type, Transacti @Override public Set replicas() { - // TODO: Implement - HashSet replicas = new HashSet<>(); - return replicas; + return new NativeIterator<>(driver_replicas(nativeObject)).stream().map(ServerReplicaImpl::new).collect(toSet()); } @Override public Optional primaryReplica() { - // TODO: Implement + com.typedb.driver.jni.ServerReplica nativeReplica = driver_primary_replica(nativeObject); + if (nativeReplica != null) { + return Optional.of(new ServerReplicaImpl(nativeReplica)); + } return Optional.empty(); } diff --git a/java/connection/ServerReplicaImpl.java b/java/connection/ServerReplicaImpl.java new file mode 100644 index 0000000000..a80a06894c --- /dev/null +++ b/java/connection/ServerReplicaImpl.java @@ -0,0 +1,54 @@ +/* + * 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_address; +import static com.typedb.driver.jni.typedb_driver.server_replica_term; +import static com.typedb.driver.jni.typedb_driver.server_replica_type; + +public class ServerReplicaImpl extends NativeObject implements ServerReplica { + public ServerReplicaImpl(com.typedb.driver.jni.ServerReplica serverReplica) { + super(serverReplica); + } + + @Override + public String getAddress() { + return server_replica_address(nativeObject); + } + + @Override + public ReplicaType getType() { + return ReplicaType.of(server_replica_type(nativeObject)); + } + + @Override + public long getTerm() { + return server_replica_term(nativeObject); + } + + @Override + public String toString() { + return getAddress(); + } +} diff --git a/java/test/behaviour/connection/ConnectionStepsCluster.java b/java/test/behaviour/connection/ConnectionStepsCluster.java index 09553dffcf..aac9fc09ac 100644 --- a/java/test/behaviour/connection/ConnectionStepsCluster.java +++ b/java/test/behaviour/connection/ConnectionStepsCluster.java @@ -34,7 +34,7 @@ public class ConnectionStepsCluster extends ConnectionStepsBase { // TODO: Add 2 more addresses public static Set DEFAULT_CLUSTER_ADDRESSES = Set.of( - "https://127.0.0.1:11729" + "https://127.0.0.1:11729" ); @Override diff --git a/rust/src/connection/server/server_version.rs b/rust/src/connection/server/server_version.rs index 7df9f7db0a..817d144b84 100644 --- a/rust/src/connection/server/server_version.rs +++ b/rust/src/connection/server/server_version.rs @@ -17,7 +17,7 @@ * under the License. */ -/// A full TypeDB's server version specification +/// A full TypeDB server's version specification #[derive(Debug, Clone)] pub struct ServerVersion { pub(crate) distribution: String, diff --git a/rust/src/driver.rs b/rust/src/driver.rs index d907636d43..1a2fbe3ff0 100644 --- a/rust/src/driver.rs +++ b/rust/src/driver.rs @@ -103,7 +103,6 @@ impl TypeDBDriver { driver_options: DriverOptions, driver_lang: impl AsRef, ) -> Result { - println!("Opening a new driver with these options: {driver_options:?}, to: {addresses}"); let background_runtime = Arc::new(BackgroundRuntime::new()?); let server_manager = Arc::new( ServerManager::new( From 2347a88093c8f078d3cded89ce0547b5cb3a89d6 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Wed, 2 Jul 2025 18:09:43 +0100 Subject: [PATCH 23/35] Propagate almost everything to Python --- .../java/connection/DatabaseManager.adoc | 2 +- .../ROOT/partials/java/connection/Driver.adoc | 23 ++++ .../java/connection/ServerReplica.adoc | 4 +- .../java/connection/ServerVersion.adoc | 4 +- .../ROOT/partials/java/connection/TypeDB.adoc | 6 +- .../partials/python/connection/Driver.adoc | 63 +++++++++++ .../python/connection/DriverOptions.adoc | 19 +++- .../python/connection/ReplicaType.adoc | 55 +++++++++ .../python/connection/ServerReplica.adoc | 24 ++++ .../python/connection/ServerVersion.adoc | 28 +++++ .../partials/python/connection/TypeDB.adoc | 10 +- .../python/transaction/QueryOptions.adoc | 2 +- .../rust/connection/ServerVersion.adoc | 2 +- java/TypeDB.java | 6 +- java/TypeDBExample.java | 9 +- java/api/answer/ConceptRow.java | 2 +- java/api/server/ServerReplica.java | 4 +- python/README.md | 3 +- python/docs_structure.bzl | 6 +- python/example.py | 6 +- .../background/cluster/environment.py | 49 +++----- .../background/community/environment.py | 41 +++---- .../behaviour/background/environment_base.py | 34 +++++- python/typedb/api/connection/database.py | 105 +----------------- python/typedb/api/connection/driver.py | 49 +++++++- .../typedb/api/connection/driver_options.py | 51 +++++++-- python/typedb/api/connection/query_options.py | 2 +- python/typedb/api/server/replica_type.py | 40 +++++++ python/typedb/api/server/server_replica.py | 79 +++++++++++++ python/typedb/api/server/server_version.py | 75 +++++++++++++ python/typedb/common/exception.py | 1 + python/typedb/connection/database.py | 37 ------ python/typedb/connection/driver.py | 52 +++++++-- python/typedb/connection/server_replica.py | 54 +++++++++ python/typedb/driver.py | 18 ++- rust/README.md | 8 +- rust/example.rs | 12 +- 37 files changed, 714 insertions(+), 271 deletions(-) create mode 100644 docs/modules/ROOT/partials/python/connection/ReplicaType.adoc create mode 100644 docs/modules/ROOT/partials/python/connection/ServerReplica.adoc create mode 100644 docs/modules/ROOT/partials/python/connection/ServerVersion.adoc create mode 100644 python/typedb/api/server/replica_type.py create mode 100644 python/typedb/api/server/server_replica.py create mode 100644 python/typedb/api/server/server_version.py create mode 100644 python/typedb/connection/server_replica.py diff --git a/docs/modules/ROOT/partials/java/connection/DatabaseManager.adoc b/docs/modules/ROOT/partials/java/connection/DatabaseManager.adoc index 5b35c487f2..ae1fd56a1b 100644 --- a/docs/modules/ROOT/partials/java/connection/DatabaseManager.adoc +++ b/docs/modules/ROOT/partials/java/connection/DatabaseManager.adoc @@ -20,7 +20,7 @@ Retrieves all databases present on the TypeDB server, using default strong consi -See also: ``#allWithConsistency(ConsistencyLevel)`` +See also: <<#_all_com_typedb_driver_api_ConsistencyLevel,``all(ConsistencyLevel)``>> [caption=""] diff --git a/docs/modules/ROOT/partials/java/connection/Driver.adoc b/docs/modules/ROOT/partials/java/connection/Driver.adoc index 41ed1db7a9..875e9beeab 100644 --- a/docs/modules/ROOT/partials/java/connection/Driver.adoc +++ b/docs/modules/ROOT/partials/java/connection/Driver.adoc @@ -125,6 +125,29 @@ Set of ``Replica`` instances for this driver connection. driver.replicas() ---- +[#_Driver_serverVersion_] +==== serverVersion + +[source,java] +---- +@CheckReturnValue +ServerVersion serverVersion() +---- + +Retrieves the server's version. + + +[caption=""] +.Returns +`ServerVersion` + +[caption=""] +.Code examples +[source,java] +---- +driver.serverVersion(); +---- + [#_Driver_transaction_java_lang_String_Transaction_Type] ==== transaction diff --git a/docs/modules/ROOT/partials/java/connection/ServerReplica.adoc b/docs/modules/ROOT/partials/java/connection/ServerReplica.adoc index 39558b9778..228f5cbb25 100644 --- a/docs/modules/ROOT/partials/java/connection/ServerReplica.adoc +++ b/docs/modules/ROOT/partials/java/connection/ServerReplica.adoc @@ -15,7 +15,7 @@ The metadata and state of an individual raft replica of a driver connection. java.lang.String getAddress() ---- -The address this replica is hosted at. +Returns the address this replica is hosted at. [caption=""] .Returns @@ -30,7 +30,7 @@ The address this replica is hosted at. long getTerm() ---- -The raft protocol ‘term’ of this replica. +Returns the raft protocol ‘term’ of this replica. [caption=""] .Returns diff --git a/docs/modules/ROOT/partials/java/connection/ServerVersion.adoc b/docs/modules/ROOT/partials/java/connection/ServerVersion.adoc index 97d5722acc..17b8e25def 100644 --- a/docs/modules/ROOT/partials/java/connection/ServerVersion.adoc +++ b/docs/modules/ROOT/partials/java/connection/ServerVersion.adoc @@ -3,14 +3,14 @@ *Package*: `com.typedb.driver.api.server` -Type of replica. +A full TypeDB server's version specification. [caption=""] .Examples [source,java] ---- -replica.type(); +driver.serverVersion(); ---- // tag::methods[] diff --git a/docs/modules/ROOT/partials/java/connection/TypeDB.adoc b/docs/modules/ROOT/partials/java/connection/TypeDB.adoc index f5793c936c..f922711fc0 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=""] @@ -87,7 +87,7 @@ Open a TypeDB Driver to a TypeDB cluster available at the provided addresses. |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 connection settings to connect with a| `DriverOptions` +a| `driverOptions` a| The driver connection options to connect with a| `DriverOptions` |=== [caption=""] @@ -123,7 +123,7 @@ Open a TypeDB Driver to a TypeDB cluster, using the provided address translation |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 connection settings to connect with a| `DriverOptions` +a| `driverOptions` a| The driver connection options to connect with a| `DriverOptions` |=== [caption=""] diff --git a/docs/modules/ROOT/partials/python/connection/Driver.adoc b/docs/modules/ROOT/partials/python/connection/Driver.adoc index eab119bb3d..b71fa23fcf 100644 --- a/docs/modules/ROOT/partials/python/connection/Driver.adoc +++ b/docs/modules/ROOT/partials/python/connection/Driver.adoc @@ -56,6 +56,69 @@ 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_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_] +==== server_version + +[source,python] +---- +server_version() -> ServerVersion +---- + +Retrieves the server’s version. + +[caption=""] +.Returns +`ServerVersion` + +[caption=""] +.Code examples +[source,python] +---- +driver.server_version() +---- + [#_Driver_transaction_database_name_str_transaction_type_TransactionType_options_TransactionOptions_None] ==== transaction diff --git a/docs/modules/ROOT/partials/python/connection/DriverOptions.adoc b/docs/modules/ROOT/partials/python/connection/DriverOptions.adoc index 84e5b85bfe..5634a0f7e8 100644 --- a/docs/modules/ROOT/partials/python/connection/DriverOptions.adoc +++ b/docs/modules/ROOT/partials/python/connection/DriverOptions.adoc @@ -1,12 +1,27 @@ [#_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| `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. +|=== +// end::properties[] + 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..a76634d1cd --- /dev/null +++ b/docs/modules/ROOT/partials/python/connection/ServerReplica.adoc @@ -0,0 +1,24 @@ +[#_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| `replica_type` a| `ReplicaType` a| Gets the type of this replica: whether it’s a primary or a secondary replica. + + +a| `term` a| `int` a| Returns the raft protocol ‘term’ of this replica. + + +|=== +// end::properties[] + 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/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/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/rust/connection/ServerVersion.adoc b/docs/modules/ROOT/partials/rust/connection/ServerVersion.adoc index 74c5c4ffb0..5f1ac19565 100644 --- a/docs/modules/ROOT/partials/rust/connection/ServerVersion.adoc +++ b/docs/modules/ROOT/partials/rust/connection/ServerVersion.adoc @@ -6,7 +6,7 @@ * `Clone` * `Debug` -A full TypeDB’s server version specification +A full TypeDB server’s version specification // tag::methods[] [#_struct_ServerVersion_distribution_] diff --git a/java/TypeDB.java b/java/TypeDB.java index 491b11dd96..a5bb7a0776 100644 --- a/java/TypeDB.java +++ b/java/TypeDB.java @@ -41,7 +41,7 @@ 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); @@ -57,7 +57,7 @@ public static Driver driver(String address, Credentials credentials, DriverOptio * * @param addresses The addresses of TypeDB cluster replicas for connection * @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(Set addresses, Credentials credentials, DriverOptions driverOptions) throws TypeDBDriverException { return new DriverImpl(addresses, credentials, driverOptions); @@ -73,7 +73,7 @@ public static Driver driver(Set addresses, Credentials credentials, Driv * * @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 connection settings 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 634e46710b..47554be234 100644 --- a/java/TypeDBExample.java +++ b/java/TypeDBExample.java @@ -2,10 +2,12 @@ // It is not intended for manual editing. package com.typedb.driver; +import com.typedb.driver.TypeDB; import com.typedb.driver.api.Credentials; import com.typedb.driver.api.Driver; import com.typedb.driver.api.DriverOptions; import com.typedb.driver.api.QueryOptions; +import com.typedb.driver.api.QueryType; import com.typedb.driver.api.Transaction; import com.typedb.driver.api.TransactionOptions; import com.typedb.driver.api.answer.ConceptRow; @@ -21,6 +23,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -29,7 +32,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())) { - // Creates a database + // Create a database driver.databases().create("typedb"); Database database = driver.databases().get("typedb"); @@ -90,7 +93,7 @@ public void example() { conceptByName.getLabel(), conceptByIndex.getLabel()); - // Checks if it's an entity type before the conversion + // Check if it's an entity type before the conversion if (conceptByName.isEntityType()) { System.out.printf("Both represent the defined entity type: '%s' (in case of a doubt: '%s')%n", conceptByName.asEntityType().getLabel(), @@ -112,7 +115,7 @@ public void example() { conceptByName = attributeRow.get(columnName).get(); - // Checks if it's an attribute type before the conversion + // Check if it's an attribute type before the conversion if (conceptByName.isAttributeType()) { AttributeType attributeType = conceptByName.asAttributeType(); System.out.printf("Defined attribute type's label: '%s', value type: '%s'%n", attributeType.getLabel(), attributeType.tryGetValueType().get()); 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/server/ServerReplica.java b/java/api/server/ServerReplica.java index ec4b075029..7287d50eec 100644 --- a/java/api/server/ServerReplica.java +++ b/java/api/server/ServerReplica.java @@ -26,7 +26,7 @@ */ public interface ServerReplica { /** - * The address this replica is hosted at. + * Returns the address this replica is hosted at. */ @CheckReturnValue String getAddress(); @@ -38,7 +38,7 @@ public interface ServerReplica { ReplicaType getType(); /** - * The raft protocol ‘term’ of this replica. + * Returns the raft protocol ‘term’ of this replica. */ @CheckReturnValue long getTerm(); 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 be19e7fb5a..f49fdec33d 100644 --- a/python/docs_structure.bzl +++ b/python/docs_structure.bzl @@ -36,9 +36,9 @@ dir_mapping = { # "ConsistencyLevel.adoc": "connection", "DriverOptions.adoc": "connection", "Driver.adoc": "connection", -# "ServerReplica.adoc": "connection", -# "ServerVersion.adoc": "connection", -# "ReplicaType.adoc": "connection", + "ServerReplica.adoc": "connection", + "ServerVersion.adoc": "connection", + "ReplicaType.adoc": "connection", "User.adoc": "connection", "UserManager.adoc": "connection", "Database.adoc": "connection", diff --git a/python/example.py b/python/example.py index bfde89f3cb..b11cb462b7 100644 --- a/python/example.py +++ b/python/example.py @@ -9,7 +9,7 @@ def typedb_example(self): # Open a driver connection. Specify your parameters if needed # The connection will be automatically closed on the "with" block exit with TypeDB.driver(TypeDB.DEFAULT_ADDRESS, Credentials("admin", "password"), DriverOptions()) as driver: - # Creates a database + # Create a database driver.databases.create("typedb") database = driver.databases.get("typedb") @@ -71,7 +71,7 @@ def typedb_example(self): print(f"Getting concepts by variable names ({concept_by_name.get_label()}) and " f"indexes ({concept_by_index.get_label()}) is equally correct. ") - # Checks if it's an entity type before the conversion + # Check if it's an entity type before the conversion if concept_by_name.is_entity_type(): print(f"Both represent the defined entity type: '{concept_by_name.as_entity_type().get_label()}' " f"(in case of a doubt: '{concept_by_index.as_entity_type().get_label()}')") @@ -89,7 +89,7 @@ def typedb_example(self): concept_by_name = row.get(column_name) - # Checks if it's an attribute type before the conversion + # Check if it's an attribute type before the conversion if concept_by_name.is_attribute_type(): attribute_type = concept_by_name.as_attribute_type() print(f"Defined attribute type's label: '{attribute_type.get_label()}', " diff --git a/python/tests/behaviour/background/cluster/environment.py b/python/tests/behaviour/background/cluster/environment.py index 258caecbae..bcd6c7fe81 100644 --- a/python/tests/behaviour/background/cluster/environment.py +++ b/python/tests/behaviour/background/cluster/environment.py @@ -19,56 +19,41 @@ 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 addresses=None, user=None, password=None: \ + create_driver(context, addresses, user, password) + context.setup_context_driver_fn = lambda addresses=None, username=None, password=None: \ + setup_context_driver(context, addresses, username, password) + # TODO: Add 2 more addresses (or 1?) + context.DEFAULT_ADDRESSES = ["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 = context.driver_options.is_tls_enabled(True).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, addresses=None, username=None, password=None): + context.driver = create_driver(context, addresses, 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, addresses=None, username=None, password=None) -> Driver: + if addresses is None: + addresses = context.DEFAULT_ADDRESSES 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=addresses, 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..79fc9d1cb6 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: \ + 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, host, port, 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/typedb/api/connection/database.py b/python/typedb/api/connection/database.py index 202ded398d..e68ab944fd 100644 --- a/python/typedb/api/connection/database.py +++ b/python/typedb/api/connection/database.py @@ -46,6 +46,7 @@ def schema(self) -> str: """ pass + @abstractmethod def type_schema(self) -> str: """ Returns the types in the schema as a valid TypeQL define query string. @@ -92,110 +93,6 @@ def delete(self) -> None: """ 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): """ diff --git a/python/typedb/api/connection/driver.py b/python/typedb/api/connection/driver.py index 34891d1173..38b97a52c6 100644 --- a/python/typedb/api/connection/driver.py +++ b/python/typedb/api/connection/driver.py @@ -18,13 +18,15 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, Set if TYPE_CHECKING: from typedb.api.connection.database 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.server.server_replica import ServerReplica + from typedb.api.server.server_version import ServerVersion class Driver(ABC): @@ -45,6 +47,21 @@ def is_open(self) -> bool: """ pass + @abstractmethod + def server_version(self) -> ServerVersion: + """ + Retrieves the server's version. + + :return: + + Examples: + --------- + :: + + driver.server_version() + """ + pass + @property @abstractmethod def databases(self) -> DatabaseManager: @@ -96,6 +113,36 @@ def users(self) -> UserManager: """ pass + @abstractmethod + def replicas(self) -> Set[ServerReplica]: + """ + Set of ``Replica`` instances for this driver connection. + + :return: + + Examples: + --------- + :: + + driver.replicas() + """ + pass + + @abstractmethod + def primary_replica(self) -> Optional[ServerReplica]: + """ + Returns the primary replica for this driver connection. + + :return: + + Examples: + --------- + :: + + driver.primary_replica() + """ + pass + @abstractmethod def __enter__(self): pass diff --git a/python/typedb/api/connection/driver_options.py b/python/typedb/api/connection/driver_options.py index 9cd92c09df..4a457248a9 100644 --- a/python/typedb/api/connection/driver_options.py +++ b/python/typedb/api/connection/driver_options.py @@ -19,25 +19,62 @@ 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.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, 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, + ): + 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 @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) 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/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..403625074d --- /dev/null +++ b/python/typedb/api/server/server_replica.py @@ -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. + + +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 address(self) -> str: + """ + Returns the address this replica is hosted at. + + :return: + + Examples + -------- + :: + + server_replica.address + """ + pass + + @property + @abstractmethod + def replica_type(self) -> ReplicaType: + """ + Gets the type of this replica: whether it's a primary or a secondary replica. + + :return: + + Examples + -------- + :: + + server_replica.query_type + """ + 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/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 fb9d3c4643..bfe11a49ef 100644 --- a/python/typedb/connection/database.py +++ b/python/typedb/connection/database.py @@ -71,45 +71,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), server_replica_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 server_replica_get_server(self._info) - # - # def is_primary(self) -> bool: - # return server_replica_is_primary(self._info) - # - # def is_preferred(self) -> bool: - # return server_replica_is_preferred(self._info) - # - # def term(self) -> int: - # return server_replica_get_term(self._info) diff --git a/python/typedb/connection/driver.py b/python/typedb/connection/driver.py index 2ee93f4d18..16dbe99eae 100644 --- a/python/typedb/connection/driver.py +++ b/python/typedb/connection/driver.py @@ -20,32 +20,50 @@ from typing import TYPE_CHECKING, Optional 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.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.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_new_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_replicas, \ + driver_primary_replica, driver_server_version, 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.server.server_replica import ServerReplica + from typedb.api.server.server_version import ServerVersion 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") + + if isinstance(addresses, str): + driver_new_fn = driver_new_with_description + elif isinstance(addresses, list): + driver_new_fn = driver_new_with_addresses_with_description + elif isinstance(addresses, dict): + driver_new_fn = driver_new_with_address_translation_with_description + else: + raise TypeDBDriverException(INVALID_ADDRESS_FORMAT) + try: - native_driver = driver_new_with_description(address, credentials.native_object, - driver_options.native_object, Driver.LANGUAGE) + native_driver = driver_new_fn(addresses, credentials.native_object, driver_options.native_object, + Driver.LANGUAGE) except TypeDBDriverExceptionNative as e: raise TypeDBDriverException.of(e) from None super().__init__(native_driver) @@ -58,6 +76,12 @@ def _native_object_not_owned_exception(self) -> TypeDBDriverException: def _native_driver(self) -> NativeDriver: return self.native_object + def server_version(self) -> ServerVersion: + try: + return ServerVersion(driver_server_version(self._native_driver)) + 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") @@ -75,6 +99,18 @@ def databases(self) -> _DatabaseManager: def users(self) -> UserManager: return _UserManager(self._native_driver) + 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 __enter__(self): return self diff --git a/python/typedb/connection/server_replica.py b/python/typedb/connection/server_replica.py new file mode 100644 index 0000000000..a8e344be5d --- /dev/null +++ b/python/typedb/connection/server_replica.py @@ -0,0 +1,54 @@ +# 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_address, server_replica_type, server_replica_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 address(self) -> str: + return server_replica_address(self.native_object) + + @property + def replica_type(self) -> str: + return server_replica_type(self.native_object) + + @property + def term(self) -> str: + return server_replica_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 32c061d3a2..241129b83f 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, Mapping, Union + 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 @@ -50,15 +53,22 @@ class TypeDB: 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/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/example.rs b/rust/example.rs index 4158dfc83a..a353341fef 100644 --- a/rust/example.rs +++ b/rust/example.rs @@ -9,14 +9,14 @@ 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(), ) @@ -78,7 +78,7 @@ fn typedb_example() { let rows: Vec = answer.into_rows().try_collect().await.unwrap(); let row = rows.get(0).unwrap(); - // Retrieves column names to get concepts by index if the variable names are lost + // Retrieve column names to get concepts by index if the variable names are lost let column_names = row.get_column_names(); let column_name = column_names.get(0).unwrap(); @@ -89,7 +89,7 @@ fn typedb_example() { // Get concept by the header's index let concept_by_index = row.get_index(0).unwrap().unwrap(); - // Checks if it's an entity type + // Check if it's an entity type if concept_by_name.is_entity_type() { print!("Getting concepts by variable names and indexes is equally correct. "); println!( @@ -110,7 +110,7 @@ fn typedb_example() { let concept_by_name = row.get(column_name).unwrap().unwrap(); - // Checks if it's an attribute type to safely retrieve its value type + // Check if it's an attribute type to safely retrieve its value type if concept_by_name.is_attribute_type() { let label = concept_by_name.get_label(); let value_type = concept_by_name.try_get_value_type().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(); From 8438aa66b329a7065d398304591aabf06e7f39a9 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Thu, 3 Jul 2025 16:19:11 +0100 Subject: [PATCH 24/35] Propagate consistency levels to Python, use it more --- c/src/database/database_manager.rs | 60 ++++++++---- c/src/server/consistency_level.rs | 6 +- .../ConsistencyLevel.ReplicaDependant.adoc | 2 +- .../java/connection/ConsistencyLevel.adoc | 2 +- .../java/connection/DatabaseManager.adoc | 78 +++++++++++++++- .../python/connection/ConsistencyLevel.adoc | 78 ++++++++++++++++ .../python/connection/DatabaseManager.adoc | 26 ++++-- .../partials/python/connection/Eventual.adoc | 22 +++++ .../python/connection/ReplicaDependant.adoc | 33 +++++++ .../partials/python/connection/Strong.adoc | 22 +++++ .../rust/connection/ConsistencyLevel.adoc | 2 +- java/api/ConsistencyLevel.java | 5 +- java/api/database/DatabaseManager.java | 93 +++++++++++++------ java/connection/DatabaseManagerImpl.java | 26 +++--- python/docs_structure.bzl | 5 +- .../background/cluster/environment.py | 5 +- .../api/connection/consistency_level.py | 93 +++++++++++++++++++ python/typedb/api/connection/database.py | 45 +++++---- python/typedb/connection/database_manager.py | 24 ++--- rust/src/common/consistency_level.rs | 2 +- 20 files changed, 520 insertions(+), 109 deletions(-) create mode 100644 docs/modules/ROOT/partials/python/connection/ConsistencyLevel.adoc create mode 100644 docs/modules/ROOT/partials/python/connection/Eventual.adoc create mode 100644 docs/modules/ROOT/partials/python/connection/ReplicaDependant.adoc create mode 100644 docs/modules/ROOT/partials/python/connection/Strong.adoc create mode 100644 python/typedb/api/connection/consistency_level.py diff --git a/c/src/database/database_manager.rs b/c/src/database/database_manager.rs index f49eeef748..a2e21bc655 100644 --- a/c/src/database/database_manager.rs +++ b/c/src/database/database_manager.rs @@ -25,9 +25,9 @@ use crate::{ common::{ error::{try_release, try_release_arc, unwrap_or_default, unwrap_void}, iterator::{iterator_arc_next, CIterator}, - memory::{borrow_mut, borrow_optional, free, string_view}, + memory::{borrow_mut, free, string_view}, }, - server::consistency_level::ConsistencyLevel, + server::consistency_level::{native_consistency_level, ConsistencyLevel}, }; /// An Iterator over databases present on the TypeDB server. @@ -46,8 +46,6 @@ pub extern "C" fn database_iterator_drop(it: *mut DatabaseIterator) { free(it); } -// TODO: Propage consistency_levels to every read function! - /// Returns a DatabaseIterator over all databases present on the TypeDB server. /// /// @param driver The TypeDBDriver object. @@ -58,13 +56,51 @@ pub extern "C" fn databases_all( consistency_level: *const ConsistencyLevel, ) -> *mut DatabaseIterator { let databases = borrow_mut(driver).databases(); - let result = match borrow_optional(consistency_level).cloned() { - Some(consistency_level) => databases.all_with_consistency(consistency_level.into()), + 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 consistency_level The consistency level to use for the operation. +/// @param name The name of the database. +#[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 consistency_level The consistency level to use for the operation. +/// @param name The name of the database. +#[no_mangle] +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), + }) +} + /// Creates a database with the given name. #[no_mangle] pub extern "C" fn databases_create(driver: *mut TypeDBDriver, name: *const c_char) { @@ -90,15 +126,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))) -} - -/// Retrieves 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/server/consistency_level.rs b/c/src/server/consistency_level.rs index 41bcf8ca3c..fdc91416cf 100644 --- a/c/src/server/consistency_level.rs +++ b/c/src/server/consistency_level.rs @@ -22,7 +22,7 @@ use typedb_driver::consistency_level::ConsistencyLevel as NativeConsistencyLevel use crate::common::{ error::unwrap_or_default, - memory::{free, release, release_string, string_free, string_view}, + memory::{borrow_optional, free, release, release_string, string_free, string_view}, }; /// ConsistencyLevelTag is used to represent consistency levels in FFI. @@ -82,6 +82,10 @@ pub extern "C" fn consistency_level_drop(consistency_level: *mut ConsistencyLeve 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; diff --git a/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.ReplicaDependant.adoc b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.ReplicaDependant.adoc index 32bd2ef84b..3cc8d807e6 100644 --- a/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.ReplicaDependant.adoc +++ b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.ReplicaDependant.adoc @@ -28,7 +28,7 @@ public ReplicaDependant​(java.lang.String address) public java.lang.String getAddress() ---- - +Retrieves the address of the replica this consistency level depends on. [caption=""] .Returns diff --git a/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.adoc b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.adoc index 9a2fb7fc74..6b9078d842 100644 --- a/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.adoc +++ b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.adoc @@ -3,7 +3,7 @@ *Package*: `com.typedb.driver.api` -Consistency levels of operations against a distributed database. 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. +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_] diff --git a/docs/modules/ROOT/partials/java/connection/DatabaseManager.adoc b/docs/modules/ROOT/partials/java/connection/DatabaseManager.adoc index ae1fd56a1b..724fc3db60 100644 --- a/docs/modules/ROOT/partials/java/connection/DatabaseManager.adoc +++ b/docs/modules/ROOT/partials/java/connection/DatabaseManager.adoc @@ -77,7 +77,41 @@ driver.databases().all(ConsistencyLevel.Strong) [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 ---- @@ -91,6 +125,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=""] @@ -101,7 +136,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] @@ -142,7 +177,41 @@ driver.databases().create(name) [source,java] ---- @CheckReturnValue -Database get​(java.lang.String name) +default Database get​(java.lang.String name) + throws TypeDBDriverException +---- + +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=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `name` a| The name of the database to retrieve a| `java.lang.String` +|=== + +[caption=""] +.Returns +`Database` + +[caption=""] +.Code examples +[source,java] +---- +driver.databases().get(name) +---- + +[#_DatabaseManager_get_java_lang_String_ConsistencyLevel] +==== get + +[source,java] +---- +@CheckReturnValue +Database get​(java.lang.String name, + ConsistencyLevel consistencyLevel) throws TypeDBDriverException ---- @@ -156,6 +225,7 @@ Retrieves the database with the given name. |=== |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=""] @@ -166,7 +236,7 @@ a| `name` a| The name of the database to retrieve a| `java.lang.String` .Code examples [source,java] ---- -driver.databases().get(name) +driver.databases().get(name, ConsistencyLevel.Strong) ---- [#_DatabaseManager_importFromFile_java_lang_String_java_lang_String_java_lang_String] 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..84707a838b --- /dev/null +++ b/docs/modules/ROOT/partials/python/connection/ConsistencyLevel.adoc @@ -0,0 +1,78 @@ +[#_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` + +// end::methods[] + diff --git a/docs/modules/ROOT/partials/python/connection/DatabaseManager.adoc b/docs/modules/ROOT/partials/python/connection/DatabaseManager.adoc index a9a713d768..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] @@ -85,12 +97,12 @@ 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 ---- Retrieves the database with the given name. @@ -102,6 +114,7 @@ Retrieves 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] 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/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/rust/connection/ConsistencyLevel.adoc b/docs/modules/ROOT/partials/rust/connection/ConsistencyLevel.adoc index 493ca68876..f6e386ab5b 100644 --- a/docs/modules/ROOT/partials/rust/connection/ConsistencyLevel.adoc +++ b/docs/modules/ROOT/partials/rust/connection/ConsistencyLevel.adoc @@ -1,7 +1,7 @@ [#_enum_ConsistencyLevel] === ConsistencyLevel -Consistency levels of operations against a distributed database. 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. +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. [caption=""] .Enum variants diff --git a/java/api/ConsistencyLevel.java b/java/api/ConsistencyLevel.java index 7996ffae48..5a3a760d45 100644 --- a/java/api/ConsistencyLevel.java +++ b/java/api/ConsistencyLevel.java @@ -24,7 +24,7 @@ import static com.typedb.driver.jni.typedb_driver.consistency_level_strong; /** - * Consistency levels of operations against a distributed database. All driver methods have default + * 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. @@ -106,6 +106,9 @@ 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; } diff --git a/java/api/database/DatabaseManager.java b/java/api/database/DatabaseManager.java index d1c0ce615b..636a50ca13 100644 --- a/java/api/database/DatabaseManager.java +++ b/java/api/database/DatabaseManager.java @@ -29,87 +29,120 @@ * Provides access to all database management methods. */ public interface DatabaseManager { - /** - * Retrieves the database with the given name. + * Retrieves all databases present on the TypeDB server, using default strong consistency. + * See {@link #all(ConsistencyLevel)} for more details and options. * *

Examples

*
-     * driver.databases().get(name)
+     * driver.databases().all()
      * 
* - * @param name The name of the database to retrieve + * @see #all(ConsistencyLevel) */ @CheckReturnValue - Database get(String name) throws TypeDBDriverException; + default List all() throws TypeDBDriverException { + return all(null); + } /** - * Checks if a database with the given name exists. + * Retrieves all databases present on the TypeDB server. * *

Examples

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

Examples

*
-     * driver.databases().create(name)
+     * driver.databases().contains(name)
      * 
* - * @param name The name of the database to be created + * @param name The database name to be checked */ - void create(String name) throws TypeDBDriverException; + @CheckReturnValue + default boolean contains(String name) throws TypeDBDriverException { + return contains(name, null); + } /** - * 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. + * Checks if a database with the given name exists. * *

Examples

*
-     * driver.databases().importFromFile(name, schema, "data.typedb")
+     * driver.databases().contains(name, ConsistencyLevel.Strong)
      * 
* - * @param name The name of the database to be created - * @param schema The schema definition query string for the database - * @param dataFilePath The exported database file path to import the data from + * @param name The database name to be checked + * @param consistencyLevel The consistency level to use for the operation */ - void importFromFile(String name, String schema, String dataFilePath) throws TypeDBDriverException; + @CheckReturnValue + boolean contains(String name, ConsistencyLevel consistencyLevel) throws TypeDBDriverException; /** - * Retrieves all databases present on the TypeDB server, using default strong consistency. - * See {@link #all(ConsistencyLevel)} for more details and options. + * Retrieves the database with the given name, using default strong consistency. + * See {@link #get(String, ConsistencyLevel)} for more details and options. * *

Examples

*
-     * driver.databases().all()
+     * driver.databases().get(name)
      * 
* - * @see #all(ConsistencyLevel) + * @param name The name of the database to retrieve */ @CheckReturnValue - default List all() throws TypeDBDriverException { - return all(null); + default Database get(String name) throws TypeDBDriverException { + return get(name, null); } /** - * Retrieves all databases present on the TypeDB server. + * Retrieves the database with the given name. * *

Examples

*
-     * driver.databases().all(ConsistencyLevel.Strong)
+     * 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 - * @see #all() */ @CheckReturnValue - List all(ConsistencyLevel consistencyLevel) throws TypeDBDriverException; + Database get(String name, ConsistencyLevel consistencyLevel) throws TypeDBDriverException; + + /** + * Creates a database with the given name. + * + *

Examples

+ *
+     * driver.databases().create(name)
+     * 
+ * + * @param name The name of the database to be created + */ + void create(String name) throws TypeDBDriverException; + + /** + * 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

+ *
+     * driver.databases().importFromFile(name, schema, "data.typedb")
+     * 
+ * + * @param name The name of the database to be created + * @param schema The schema definition query string for the database + * @param dataFilePath The exported database file path to import the data from + */ + void importFromFile(String name, String schema, String dataFilePath) throws TypeDBDriverException; } diff --git a/java/connection/DatabaseManagerImpl.java b/java/connection/DatabaseManagerImpl.java index f0da07e617..e1028a79ef 100644 --- a/java/connection/DatabaseManagerImpl.java +++ b/java/connection/DatabaseManagerImpl.java @@ -43,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(ConsistencyLevel consistencyLevel) 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, ConsistencyLevel.nativeValue(consistencyLevel))).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/python/docs_structure.bzl b/python/docs_structure.bzl index f49fdec33d..d3aa04c0ab 100644 --- a/python/docs_structure.bzl +++ b/python/docs_structure.bzl @@ -33,7 +33,10 @@ dir_mapping = { ".DS_Store": "concept", "TypeDB.adoc": "connection", "Credentials.adoc": "connection", -# "ConsistencyLevel.adoc": "connection", + "ConsistencyLevel.adoc": "connection", + "Strong.adoc": "connection", + "Eventual.adoc": "connection", + "ReplicaDependant.adoc": "connection", "DriverOptions.adoc": "connection", "Driver.adoc": "connection", "ServerReplica.adoc": "connection", diff --git a/python/tests/behaviour/background/cluster/environment.py b/python/tests/behaviour/background/cluster/environment.py index bcd6c7fe81..6d1896c74f 100644 --- a/python/tests/behaviour/background/cluster/environment.py +++ b/python/tests/behaviour/background/cluster/environment.py @@ -15,6 +15,8 @@ # 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 * @@ -33,7 +35,8 @@ def before_all(context: Context): def before_scenario(context: Context, scenario): environment_base.before_scenario(context, scenario) - context.driver_options = context.driver_options.is_tls_enabled(True).tls_root_ca_path(context.tls_root_ca_path) + context.driver_options.is_tls_enabled = True + context.driver_options.tls_root_ca_path = context.tls_root_ca_path def setup_context_driver(context, addresses=None, username=None, password=None): diff --git a/python/typedb/api/connection/consistency_level.py b/python/typedb/api/connection/consistency_level.py new file mode 100644 index 0000000000..c722fba2cb --- /dev/null +++ b/python/typedb/api/connection/consistency_level.py @@ -0,0 +1,93 @@ +# 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 + +from typedb.native_driver_wrapper import consistency_level_strong, consistency_level_eventual, \ + consistency_level_replica_dependant, ConsistencyLevel as NativeConsistencyLevel + +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 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(ABC): + """ + 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(ABC): + """ + 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(ABC): + """ + 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 index e68ab944fd..a41c5779a7 100644 --- a/python/typedb/api/connection/database.py +++ b/python/typedb/api/connection/database.py @@ -18,7 +18,8 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import List +from typedb.api.connection.consistency_level import ConsistencyLevel +from typing import List, Optional class Database(ABC): @@ -100,27 +101,29 @@ class DatabaseManager(ABC): """ @abstractmethod - def get(self, name: str) -> Database: + def all(self, consistency_level: Optional[ConsistencyLevel] = None) -> List[Database]: """ - Retrieves the database with the given name. + Retrieves all databases present on the TypeDB server. - :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.all() + driver.databases.all(ConsistencyLevel.Strong()) """ pass @abstractmethod - def contains(self, name: str) -> bool: + 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: @@ -128,55 +131,59 @@ def contains(self, name: str) -> bool: :: driver.databases.contains(name) + driver.databases.contains(name, ConsistencyLevel.Strong()) """ pass @abstractmethod - def create(self, name: str) -> None: + def get(self, name: str, consistency_level: Optional[ConsistencyLevel] = None) -> Database: """ - Creates a database with the given name. + Retrieves the database with the given name. - :param name: The name of the database to be created + :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.create(name) + driver.databases.get(name) + driver.databases.get(name, ConsistencyLevel.Strong()) """ pass @abstractmethod - def import_from_file(self, name: str, schema: str, data_file_path: str) -> None: + def create(self, name: 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. + Creates a database with the given name. :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") + driver.databases.create(name) """ pass @abstractmethod - def all(self) -> List[Database]: + def import_from_file(self, name: str, schema: str, data_file_path: str) -> None: """ - Retrieves all databases present on the TypeDB server. + 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.all() + driver.databases.import_from_file(name, schema, "data.typedb") """ pass diff --git a/python/typedb/connection/database_manager.py b/python/typedb/connection/database_manager.py index 88ddcebc2c..eb288c6ce9 100644 --- a/python/typedb/connection/database_manager.py +++ b/python/typedb/connection/database_manager.py @@ -17,8 +17,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional +from typedb.api.connection.consistency_level import ConsistencyLevel from typedb.api.connection.database import DatabaseManager from typedb.common.exception import TypeDBDriverException from typedb.common.iterator_wrapper import IteratorWrapper @@ -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/rust/src/common/consistency_level.rs b/rust/src/common/consistency_level.rs index 001014d0fc..d4b9a33720 100644 --- a/rust/src/common/consistency_level.rs +++ b/rust/src/common/consistency_level.rs @@ -20,7 +20,7 @@ use std::fmt; use crate::common::address::Address; -/// Consistency levels of operations against a distributed database. All driver methods have default +/// 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. From c70a01fa276962aa695a3b450e97fd5e86b4265b Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Thu, 3 Jul 2025 17:56:20 +0100 Subject: [PATCH 25/35] Update protocol, introduce new calls (rust only), add missing fields into FFI --- Cargo.lock | 2 +- c/src/server/server_replica.rs | 6 + c/swig/typedb_driver_java.swg | 1 + dependencies/typedb/repositories.bzl | 2 +- .../java/connection/ServerReplica.adoc | 32 +++- .../python/connection/ServerReplica.adoc | 29 +++- .../rust/connection/ServerReplica.adoc | 17 +++ .../rust/connection/TypeDBDriver.adoc | 139 ++++++++++++++++++ java/api/server/ServerReplica.java | 16 +- java/connection/ServerReplicaImpl.java | 14 ++ .../background/cluster/environment.py | 22 +-- .../background/community/environment.py | 6 +- .../behaviour/connection/connection_steps.py | 27 +++- python/typedb/api/server/server_replica.py | 35 ++++- python/typedb/connection/server_replica.py | 11 +- rust/Cargo.toml | 2 +- rust/src/connection/message.rs | 4 + rust/src/connection/network/proto/message.rs | 30 ++++ rust/src/connection/network/proto/server.rs | 2 +- rust/src/connection/network/stub.rs | 8 + .../src/connection/network/transmitter/rpc.rs | 2 + .../connection/server/server_connection.rs | 16 ++ rust/src/connection/server/server_manager.rs | 15 ++ rust/src/connection/server/server_replica.rs | 9 +- rust/src/driver.rs | 37 +++++ 25 files changed, 456 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 96585743f7..45393f2bd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2754,7 +2754,7 @@ dependencies = [ [[package]] name = "typedb-protocol" version = "0.0.0" -source = "git+https://github.com/typedb/typedb-protocol?rev=94560a87db703ba1686fa830d12d095dc8ab2795#94560a87db703ba1686fa830d12d095dc8ab2795" +source = "git+https://github.com/typedb/typedb-protocol?rev=5230f8aff1a70f42b0ae4c82d042a359350233dd#5230f8aff1a70f42b0ae4c82d042a359350233dd" dependencies = [ "prost", "tonic", diff --git a/c/src/server/server_replica.rs b/c/src/server/server_replica.rs index 265d89c8ad..69ffa5bc1d 100644 --- a/c/src/server/server_replica.rs +++ b/c/src/server/server_replica.rs @@ -48,6 +48,12 @@ 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_id(replica_info: *const ServerReplica) -> u64 { + borrow(replica_info).id() +} + /// Returns the address this replica is hosted at. #[no_mangle] pub extern "C" fn server_replica_address(replica_info: *const ServerReplica) -> *mut c_char { diff --git a/c/swig/typedb_driver_java.swg b/c/swig/typedb_driver_java.swg index ba1478e7f2..ceb9f6e957 100644 --- a/c/swig/typedb_driver_java.swg +++ b/c/swig/typedb_driver_java.swg @@ -121,6 +121,7 @@ %nojavaexception server_replica_address; %nojavaexception server_replica_is_primary; +%nojavaexception server_replica_id; %nojavaexception server_replica_type; %nojavaexception server_replica_term; diff --git a/dependencies/typedb/repositories.bzl b/dependencies/typedb/repositories.bzl index 2577ec1d5f..c18f6a52cf 100644 --- a/dependencies/typedb/repositories.bzl +++ b/dependencies/typedb/repositories.bzl @@ -29,7 +29,7 @@ def typedb_protocol(): git_repository( name = "typedb_protocol", remote = "https://github.com/typedb/typedb-protocol", - commit = "94560a87db703ba1686fa830d12d095dc8ab2795", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_protocol + commit = "5230f8aff1a70f42b0ae4c82d042a359350233dd", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_protocol ) def typedb_behaviour(): diff --git a/docs/modules/ROOT/partials/java/connection/ServerReplica.adoc b/docs/modules/ROOT/partials/java/connection/ServerReplica.adoc index 228f5cbb25..369f39a48a 100644 --- a/docs/modules/ROOT/partials/java/connection/ServerReplica.adoc +++ b/docs/modules/ROOT/partials/java/connection/ServerReplica.adoc @@ -21,6 +21,21 @@ Returns the address this replica is hosted at. .Returns `java.lang.String` +[#_ServerReplica_getID_] +==== getID + +[source,java] +---- +@CheckReturnValue +java.math.BigInteger getID() +---- + +Returns the id of this replica. + +[caption=""] +.Returns +`java.math.BigInteger` + [#_ServerReplica_getTerm_] ==== getTerm @@ -45,11 +60,26 @@ Returns the raft protocol ‘term’ of this replica. ReplicaType getType() ---- -Gets the type of this replica: whether it's a primary or a secondary replica. +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/python/connection/ServerReplica.adoc b/docs/modules/ROOT/partials/python/connection/ServerReplica.adoc index a76634d1cd..6b8cbec360 100644 --- a/docs/modules/ROOT/partials/python/connection/ServerReplica.adoc +++ b/docs/modules/ROOT/partials/python/connection/ServerReplica.adoc @@ -13,7 +13,10 @@ The metadata and state of an individual raft replica of a driver connection. a| `address` a| `str` a| Returns the address this replica is hosted at. -a| `replica_type` a| `ReplicaType` a| Gets the type of this replica: whether it’s a primary or a secondary replica. +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. @@ -22,3 +25,27 @@ 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/rust/connection/ServerReplica.adoc b/docs/modules/ROOT/partials/rust/connection/ServerReplica.adoc index ed72d84cd0..93c1e34dc7 100644 --- a/docs/modules/ROOT/partials/rust/connection/ServerReplica.adoc +++ b/docs/modules/ROOT/partials/rust/connection/ServerReplica.adoc @@ -30,6 +30,23 @@ Returns the address this replica is hosted at. &Address ---- +[#_struct_ServerReplica_id_] +==== id + +[source,rust] +---- +pub fn id(&self) -> u64 +---- + +Returns the id of this replica. + +[caption=""] +.Returns +[source,rust] +---- +u64 +---- + [#_struct_ServerReplica_is_primary_] ==== is_primary diff --git a/docs/modules/ROOT/partials/rust/connection/TypeDBDriver.adoc b/docs/modules/ROOT/partials/rust/connection/TypeDBDriver.adoc index 42d318f00a..bfcfac65c9 100644 --- a/docs/modules/ROOT/partials/rust/connection/TypeDBDriver.adoc +++ b/docs/modules/ROOT/partials/rust/connection/TypeDBDriver.adoc @@ -8,6 +8,75 @@ A connection to a TypeDB server which serves as the starting point for all interaction. // tag::methods[] +[#_struct_TypeDBDriver_deregister_replica_replica_id_u64] +==== deregister_replica + +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +pub async fn deregister_replica(&self, replica_id: u64) -> Result +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +pub fn deregister_replica(&self, replica_id: u64) -> Result +---- + +-- +==== + +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| `replica_id` a| — The numeric identifier of the deregistered replica a| `u64` +|=== + +[caption=""] +.Returns +[source,rust] +---- +Result +---- + +[caption=""] +.Code examples +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +driver.deregister_replica(2).await +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +driver.deregister_replica(2) +---- + +-- +==== + [#_struct_TypeDBDriver_force_close_] ==== force_close @@ -241,6 +310,76 @@ Option driver.primary_replica() ---- +[#_struct_TypeDBDriver_register_replica_replica_id_u64_address_String] +==== register_replica + +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +pub async fn register_replica(&self, replica_id: u64, address: String) -> Result +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +pub fn register_replica(&self, replica_id: u64, address: String) -> Result +---- + +-- +==== + +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. + +[caption=""] +.Input parameters +[cols=",,"] +[options="header"] +|=== +|Name |Description |Type +a| `replica_id` a| — The numeric identifier of the new replica a| `u64` +a| `address` a| — The address(es) of the TypeDB replica as a string a| `String` +|=== + +[caption=""] +.Returns +[source,rust] +---- +Result +---- + +[caption=""] +.Code examples +[tabs] +==== +async:: ++ +-- +[source,rust] +---- +driver.register_replica(2, "127.0.0.1:2729").await +---- + +-- + +sync:: ++ +-- +[source,rust] +---- +driver.register_replica(2, "127.0.0.1:2729") +---- + +-- +==== + [#_struct_TypeDBDriver_replicas_] ==== replicas diff --git a/java/api/server/ServerReplica.java b/java/api/server/ServerReplica.java index 7287d50eec..ac2bde8678 100644 --- a/java/api/server/ServerReplica.java +++ b/java/api/server/ServerReplica.java @@ -20,11 +20,19 @@ package com.typedb.driver.api.server; import javax.annotation.CheckReturnValue; +import java.math.BigInteger; /** * 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 + BigInteger getID(); + /** * Returns the address this replica is hosted at. */ @@ -32,11 +40,17 @@ public interface ServerReplica { String getAddress(); /** - * Gets the type of this replica: whether it's a primary or a secondary replica. + * 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. */ diff --git a/java/connection/ServerReplicaImpl.java b/java/connection/ServerReplicaImpl.java index a80a06894c..171fdfc1f0 100644 --- a/java/connection/ServerReplicaImpl.java +++ b/java/connection/ServerReplicaImpl.java @@ -23,7 +23,11 @@ import com.typedb.driver.api.server.ServerReplica; import com.typedb.driver.common.NativeObject; +import java.math.BigInteger; + import static com.typedb.driver.jni.typedb_driver.server_replica_address; +import static com.typedb.driver.jni.typedb_driver.server_replica_id; +import static com.typedb.driver.jni.typedb_driver.server_replica_is_primary; import static com.typedb.driver.jni.typedb_driver.server_replica_term; import static com.typedb.driver.jni.typedb_driver.server_replica_type; @@ -32,6 +36,11 @@ public ServerReplicaImpl(com.typedb.driver.jni.ServerReplica serverReplica) { super(serverReplica); } + @Override + public BigInteger getID() { + return server_replica_id(nativeObject); + } + @Override public String getAddress() { return server_replica_address(nativeObject); @@ -42,6 +51,11 @@ public ReplicaType getType() { return ReplicaType.of(server_replica_type(nativeObject)); } + @Override + public Boolean isPrimary() { + return server_replica_is_primary(nativeObject); + } + @Override public long getTerm() { return server_replica_term(nativeObject); diff --git a/python/tests/behaviour/background/cluster/environment.py b/python/tests/behaviour/background/cluster/environment.py index 6d1896c74f..74e4967200 100644 --- a/python/tests/behaviour/background/cluster/environment.py +++ b/python/tests/behaviour/background/cluster/environment.py @@ -25,12 +25,12 @@ def before_all(context: Context): environment_base.before_all(context) context.tls_root_ca_path = os.environ["ROOT_CA"] - context.create_driver_fn = lambda addresses=None, user=None, password=None: \ - create_driver(context, addresses, user, password) - context.setup_context_driver_fn = lambda addresses=None, username=None, password=None: \ - setup_context_driver(context, addresses, 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) # TODO: Add 2 more addresses (or 1?) - context.DEFAULT_ADDRESSES = ["https://127.0.0.1:11729"] + context.default_address = ["https://127.0.0.1:11729"] def before_scenario(context: Context, scenario): @@ -39,19 +39,19 @@ def before_scenario(context: Context, scenario): context.driver_options.tls_root_ca_path = context.tls_root_ca_path -def setup_context_driver(context, addresses=None, username=None, password=None): - context.driver = create_driver(context, addresses, 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, addresses=None, username=None, password=None) -> Driver: - if addresses is None: - addresses = context.DEFAULT_ADDRESSES +def create_driver(context, address=None, username=None, password=None) -> Driver: + if address is None: + address = context.default_address if username is None: username = context.DEFAULT_USERNAME if password is None: password = context.DEFAULT_PASSWORD credentials = Credentials(username, password) - return TypeDB.driver(addresses=addresses, credentials=credentials, driver_options=context.driver_options) + return TypeDB.driver(addresses=address, credentials=credentials, driver_options=context.driver_options) def after_scenario(context: Context, scenario): diff --git a/python/tests/behaviour/background/community/environment.py b/python/tests/behaviour/background/community/environment.py index 79fc9d1cb6..0ebbfcd172 100644 --- a/python/tests/behaviour/background/community/environment.py +++ b/python/tests/behaviour/background/community/environment.py @@ -25,8 +25,8 @@ def before_all(context: Context): 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, host, port, username, password) - context.DEFAULT_ADDRESS = TypeDB.DEFAULT_ADDRESS + setup_context_driver(context, address, username, password) + context.default_address = TypeDB.DEFAULT_ADDRESS def before_scenario(context: Context, scenario): @@ -39,7 +39,7 @@ def setup_context_driver(context, address=None, username=None, password=None): def create_driver(context, address=None, username=None, password=None) -> Driver: if address is None: - address = context.DEFAULT_ADDRESS + address = context.default_address if username is None: username = context.DEFAULT_USERNAME if password is None: diff --git a/python/tests/behaviour/connection/connection_steps.py b/python/tests/behaviour/connection/connection_steps.py index 28bfa66e30..c0ad0d98b5 100644 --- a/python/tests/behaviour/connection/connection_steps.py +++ b/python/tests/behaviour/connection/connection_steps.py @@ -19,6 +19,15 @@ from tests.behaviour.config.parameters import MayError 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 +48,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( diff --git a/python/typedb/api/server/server_replica.py b/python/typedb/api/server/server_replica.py index 403625074d..6f7411297c 100644 --- a/python/typedb/api/server/server_replica.py +++ b/python/typedb/api/server/server_replica.py @@ -30,6 +30,22 @@ 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: @@ -50,7 +66,22 @@ def address(self) -> str: @abstractmethod def replica_type(self) -> ReplicaType: """ - Gets the type of this replica: whether it's a primary or a secondary replica. + 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: @@ -58,7 +89,7 @@ def replica_type(self) -> ReplicaType: -------- :: - server_replica.query_type + server_replica.is_primary() """ pass diff --git a/python/typedb/connection/server_replica.py b/python/typedb/connection/server_replica.py index a8e344be5d..7a572e054a 100644 --- a/python/typedb/connection/server_replica.py +++ b/python/typedb/connection/server_replica.py @@ -20,8 +20,8 @@ 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_address, server_replica_type, server_replica_term, \ - ServerReplica as NativeServerReplica, TypeDBDriverExceptionNative +from typedb.native_driver_wrapper import server_replica_address, server_replica_id, server_replica_type, \ + server_replica_is_primary, server_replica_term, ServerReplica as NativeServerReplica, TypeDBDriverExceptionNative class _ServerReplica(ServerReplica, NativeWrapper[NativeServerReplica]): @@ -35,6 +35,10 @@ def __init__(self, server_replica: NativeServerReplica): def _native_object_not_owned_exception(self) -> TypeDBDriverException: return TypeDBDriverException(ILLEGAL_STATE) + @property + def id(self) -> int: + return server_replica_id(self.native_object) + @property def address(self) -> str: return server_replica_address(self.native_object) @@ -43,6 +47,9 @@ def address(self) -> str: def replica_type(self) -> str: return server_replica_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_term(self.native_object) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 0e6817c427..75978c0437 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -60,7 +60,7 @@ [dependencies.typedb-protocol] features = [] - rev = "94560a87db703ba1686fa830d12d095dc8ab2795" + rev = "5230f8aff1a70f42b0ae4c82d042a359350233dd" git = "https://github.com/typedb/typedb-protocol" default-features = false diff --git a/rust/src/connection/message.rs b/rust/src/connection/message.rs index 1a30db4a1b..dc785e8498 100644 --- a/rust/src/connection/message.rs +++ b/rust/src/connection/message.rs @@ -43,6 +43,8 @@ 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, @@ -77,6 +79,8 @@ pub(super) enum Response { ServersAll { servers: Vec, }, + ServersRegister, + ServersDeregister, ServerVersion { server_version: ServerVersion, }, diff --git a/rust/src/connection/network/proto/message.rs b/rust/src/connection/network/proto/message.rs index 5b70b27caa..742a294847 100644 --- a/rust/src/connection/network/proto/message.rs +++ b/rust/src/connection/network/proto/message.rs @@ -64,6 +64,24 @@ 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 { @@ -327,6 +345,18 @@ impl TryFromProto for Response { } } +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; diff --git a/rust/src/connection/network/proto/server.rs b/rust/src/connection/network/proto/server.rs index 0f010595fa..245ed6e014 100644 --- a/rust/src/connection/network/proto/server.rs +++ b/rust/src/connection/network/proto/server.rs @@ -45,7 +45,7 @@ impl TryFromProto for ServerReplica { impl TryFromProto for ReplicaStatus { fn try_from_proto(proto: ReplicaStatusProto) -> Result { - Ok(Self { replica_type: ReplicaType::try_from_proto(proto.replica_type)?, term: proto.term }) + Ok(Self { id: proto.replica_id, replica_type: ReplicaType::try_from_proto(proto.replica_type)?, term: proto.term }) } } diff --git a/rust/src/connection/network/stub.rs b/rust/src/connection/network/stub.rs index dd67295ddb..3bfa3914be 100644 --- a/rust/src/connection/network/stub.rs +++ b/rust/src/connection/network/stub.rs @@ -89,6 +89,14 @@ 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 } diff --git a/rust/src/connection/network/transmitter/rpc.rs b/rust/src/connection/network/transmitter/rpc.rs index 76b9b51215..cc5ca5bdca 100644 --- a/rust/src/connection/network/transmitter/rpc.rs +++ b/rust/src/connection/network/transmitter/rpc.rs @@ -115,6 +115,8 @@ 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) } diff --git a/rust/src/connection/server/server_connection.rs b/rust/src/connection/server/server_connection.rs index fdebe5587a..cd48f62776 100644 --- a/rust/src/connection/server/server_connection.rs +++ b/rust/src/connection/server/server_connection.rs @@ -127,6 +127,22 @@ impl ServerConnection { } } + #[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()), + } + } + + #[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()), + } + } + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] pub(crate) async fn version(&self) -> Result { match self.request(Request::ServerVersion).await? { diff --git a/rust/src/connection/server/server_manager.rs b/rust/src/connection/server/server_manager.rs index 16475ae4a2..1dceca2f34 100644 --- a/rust/src/connection/server/server_manager.rs +++ b/rust/src/connection/server/server_manager.rs @@ -110,6 +110,21 @@ impl ServerManager { 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, |server_connection| { + let address = address.clone(); + async move { server_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, |server_connection| async move { server_connection.servers_deregister(replica_id).await }) + .await + } + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] async fn update_server_connections(&self) -> Result { let replicas = self.read_replicas().clone(); diff --git a/rust/src/connection/server/server_replica.rs b/rust/src/connection/server/server_replica.rs index 04a4efd15b..3ddd0d77cd 100644 --- a/rust/src/connection/server/server_replica.rs +++ b/rust/src/connection/server/server_replica.rs @@ -58,6 +58,11 @@ impl ServerReplica { &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) @@ -82,6 +87,8 @@ impl ServerReplica { /// 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. @@ -90,7 +97,7 @@ pub(crate) struct ReplicaStatus { impl Default for ReplicaStatus { fn default() -> Self { - Self { replica_type: ReplicaType::Primary, term: 0 } + Self { id: 0, replica_type: ReplicaType::Primary, term: 0 } } } diff --git a/rust/src/driver.rs b/rust/src/driver.rs index 1a2fbe3ff0..38f6fec14b 100644 --- a/rust/src/driver.rs +++ b/rust/src/driver.rs @@ -154,6 +154,43 @@ impl TypeDBDriver { .await } + /// 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. + /// + /// # Arguments + /// + /// * `replica_id` — The numeric identifier of the new replica + /// * `address` — The address(es) 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 + } + + /// 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 + } + /// Retrieves the server's replicas. /// /// # Examples From f1bf3d9e327344fe7a67084c13da4eb0fb5ca2be Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Fri, 4 Jul 2025 11:08:10 +0100 Subject: [PATCH 26/35] Propagate all the needed interfaces and update data types --- Cargo.lock | 2 +- c/src/driver.rs | 20 ++++ c/src/server/server_replica.rs | 6 +- c/src/transaction_options.rs | 2 +- dependencies/typedb/artifacts.bzl | 2 +- dependencies/typedb/repositories.bzl | 2 +- .../ROOT/partials/c/session/options.adoc | 2 +- .../ROOT/partials/cpp/session/Options.adoc | 2 +- .../csharp/session/TypeDBOptions.adoc | 2 +- .../ROOT/partials/java/connection/Driver.adoc | 82 +++++++++++++++- .../java/connection/ServerReplica.adoc | 4 +- .../java/transaction/TransactionOptions.adoc | 2 +- .../partials/python/connection/Driver.adoc | 63 +++++++++++- .../rust/connection/ServerReplica.adoc | 4 +- .../rust/connection/TypeDBDriver.adoc | 48 ++++++++++ java/api/Driver.java | 54 ++++++++--- java/api/TransactionOptions.java | 2 +- java/api/server/ServerReplica.java | 3 +- java/connection/DriverImpl.java | 20 ++++ java/connection/ServerReplicaImpl.java | 4 +- python/typedb/api/connection/driver.py | 68 +++++++++---- python/typedb/connection/driver.py | 39 +++++--- rust/Cargo.toml | 2 +- rust/src/connection/server/server_replica.rs | 4 +- rust/src/driver.rs | 96 +++++++++++-------- 25 files changed, 421 insertions(+), 114 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45393f2bd5..527c2e32d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2754,7 +2754,7 @@ dependencies = [ [[package]] name = "typedb-protocol" version = "0.0.0" -source = "git+https://github.com/typedb/typedb-protocol?rev=5230f8aff1a70f42b0ae4c82d042a359350233dd#5230f8aff1a70f42b0ae4c82d042a359350233dd" +source = "git+https://github.com/typedb/typedb-protocol?rev=d1b71067c7e78364c69701625ea3f6d6b1f24a9d#d1b71067c7e78364c69701625ea3f6d6b1f24a9d" dependencies = [ "prost", "tonic", diff --git a/c/src/driver.rs b/c/src/driver.rs index 7cbe50f957..9ebedd2100 100644 --- a/c/src/driver.rs +++ b/c/src/driver.rs @@ -30,6 +30,7 @@ use crate::{ }, server::{server_replica::ServerReplicaIterator, server_version::ServerVersion}, }; +use crate::common::memory::borrow_mut; const DRIVER_LANG: &'static str = "c"; @@ -202,3 +203,22 @@ pub extern "C" fn driver_replicas(driver: *const TypeDBDriver) -> *mut ServerRep 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. +/// +/// @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)) +} diff --git a/c/src/server/server_replica.rs b/c/src/server/server_replica.rs index 69ffa5bc1d..710191d7d4 100644 --- a/c/src/server/server_replica.rs +++ b/c/src/server/server_replica.rs @@ -50,8 +50,8 @@ pub extern "C" fn server_replica_drop(replica_info: *mut ServerReplica) { /// Returns the id of this replica. #[no_mangle] -pub extern "C" fn server_replica_id(replica_info: *const ServerReplica) -> u64 { - borrow(replica_info).id() +pub extern "C" fn server_replica_id(replica_info: *const ServerReplica) -> i64 { + borrow(replica_info).id() as i64 } /// Returns the address this replica is hosted at. @@ -75,5 +75,5 @@ pub extern "C" fn server_replica_is_primary(replica_info: *const ServerReplica) /// Returns the raft protocol ‘term’ of this replica. #[no_mangle] pub extern "C" fn server_replica_term(replica_info: *const ServerReplica) -> i64 { - borrow(replica_info).term() + borrow(replica_info).term() as i64 } diff --git a/c/src/transaction_options.rs b/c/src/transaction_options.rs index d489695183..ea0519574c 100644 --- a/c/src/transaction_options.rs +++ b/c/src/transaction_options.rs @@ -35,7 +35,7 @@ pub extern "C" fn transaction_options_drop(options: *mut TransactionOptions) { free(options); } -/// Explicitly setsa 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( diff --git a/dependencies/typedb/artifacts.bzl b/dependencies/typedb/artifacts.bzl index dca5d00cec..6d113aceef 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 = "cb947d4c25c77d4bfdaaf8d650a3658e1f917154" + commit = "0a16c4ae00be4275854d17c2aeeca8fca08276cf" ) #def typedb_cloud_artifact(): diff --git a/dependencies/typedb/repositories.bzl b/dependencies/typedb/repositories.bzl index c18f6a52cf..7f291a64a5 100644 --- a/dependencies/typedb/repositories.bzl +++ b/dependencies/typedb/repositories.bzl @@ -29,7 +29,7 @@ def typedb_protocol(): git_repository( name = "typedb_protocol", remote = "https://github.com/typedb/typedb-protocol", - commit = "5230f8aff1a70f42b0ae4c82d042a359350233dd", # 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(): diff --git a/docs/modules/ROOT/partials/c/session/options.adoc b/docs/modules/ROOT/partials/c/session/options.adoc index 41578e47b3..db9a3af7ea 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 setsa 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/session/Options.adoc b/docs/modules/ROOT/partials/cpp/session/Options.adoc index af8c0c05da..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 setsa 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/session/TypeDBOptions.adoc b/docs/modules/ROOT/partials/csharp/session/TypeDBOptions.adoc index 5182eb8d10..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 setsa 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/Driver.adoc b/docs/modules/ROOT/partials/java/connection/Driver.adoc index 875e9beeab..cb84d8c8d5 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 @@ -99,7 +138,40 @@ Returns the primary replica for this driver connection. .Code examples [source,java] ---- -driver.primaryReplica() +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. + + +[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_] @@ -122,7 +194,7 @@ Set of ``Replica`` instances for this driver connection. .Code examples [source,java] ---- -driver.replicas() +driver.replicas(); ---- [#_Driver_serverVersion_] @@ -228,7 +300,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/ServerReplica.adoc b/docs/modules/ROOT/partials/java/connection/ServerReplica.adoc index 369f39a48a..61a2674100 100644 --- a/docs/modules/ROOT/partials/java/connection/ServerReplica.adoc +++ b/docs/modules/ROOT/partials/java/connection/ServerReplica.adoc @@ -27,14 +27,14 @@ Returns the address this replica is hosted at. [source,java] ---- @CheckReturnValue -java.math.BigInteger getID() +long getID() ---- Returns the id of this replica. [caption=""] .Returns -`java.math.BigInteger` +`long` [#_ServerReplica_getTerm_] ==== getTerm diff --git a/docs/modules/ROOT/partials/java/transaction/TransactionOptions.adoc b/docs/modules/ROOT/partials/java/transaction/TransactionOptions.adoc index 43994798f3..1bcbdfa730 100644 --- a/docs/modules/ROOT/partials/java/transaction/TransactionOptions.adoc +++ b/docs/modules/ROOT/partials/java/transaction/TransactionOptions.adoc @@ -112,7 +112,7 @@ options.transactionTimeoutMillis(); public TransactionOptions transactionTimeoutMillis​(int transactionTimeoutMillis) ---- -Explicitly setsa 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/python/connection/Driver.adoc b/docs/modules/ROOT/partials/python/connection/Driver.adoc index b71fa23fcf..8fba5eb414 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 @@ -77,6 +107,37 @@ Returns the primary replica for this driver connection. 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. + +[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 diff --git a/docs/modules/ROOT/partials/rust/connection/ServerReplica.adoc b/docs/modules/ROOT/partials/rust/connection/ServerReplica.adoc index 93c1e34dc7..e97a70d1f1 100644 --- a/docs/modules/ROOT/partials/rust/connection/ServerReplica.adoc +++ b/docs/modules/ROOT/partials/rust/connection/ServerReplica.adoc @@ -86,7 +86,7 @@ ReplicaType [source,rust] ---- -pub fn term(&self) -> i64 +pub fn term(&self) -> u64 ---- Returns the raft protocol ‘term’ of this replica. @@ -95,7 +95,7 @@ Returns the raft protocol ‘term’ of this replica. .Returns [source,rust] ---- -i64 +u64 ---- // end::methods[] diff --git a/docs/modules/ROOT/partials/rust/connection/TypeDBDriver.adoc b/docs/modules/ROOT/partials/rust/connection/TypeDBDriver.adoc index bfcfac65c9..880099e383 100644 --- a/docs/modules/ROOT/partials/rust/connection/TypeDBDriver.adoc +++ b/docs/modules/ROOT/partials/rust/connection/TypeDBDriver.adoc @@ -8,6 +8,30 @@ A connection to a TypeDB server which serves as the starting point for all interaction. // tag::methods[] +[#_struct_TypeDBDriver_databases_] +==== databases + +[source,rust] +---- +pub fn databases(&self) -> &DatabaseManager +---- + +The ``DatabaseManager`` for this connection, providing access to database management methods. + +[caption=""] +.Returns +[source,rust] +---- +&DatabaseManager +---- + +[caption=""] +.Code examples +[source,rust] +---- +driver.databases() +---- + [#_struct_TypeDBDriver_deregister_replica_replica_id_u64] ==== deregister_replica @@ -693,5 +717,29 @@ transaction.transaction_with_options(database_name, transaction_type, options) -- ==== +[#_struct_TypeDBDriver_users_] +==== users + +[source,rust] +---- +pub fn users(&self) -> &UserManager +---- + +The ``UserManager`` for this connection, providing access to user management methods. + +[caption=""] +.Returns +[source,rust] +---- +&UserManager +---- + +[caption=""] +.Code examples +[source,rust] +---- +driver.databases() +---- + // end::methods[] diff --git a/java/api/Driver.java b/java/api/Driver.java index c221a68626..93f8ea29e2 100644 --- a/java/api/Driver.java +++ b/java/api/Driver.java @@ -56,10 +56,26 @@ public interface Driver extends AutoCloseable { /** * 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. * @@ -94,41 +110,57 @@ public interface Driver extends AutoCloseable { * *

Examples

*
-     * driver.close()
+     * driver.close();
      * 
*/ void close(); /** - * The UserManager instance for this connection, providing access to user management methods. + * Set of Replica instances for this driver connection. * *

Examples

*
-     * driver.users();
+     * driver.replicas();
      * 
*/ @CheckReturnValue - UserManager users(); + Set replicas(); /** - * Set of Replica instances for this driver connection. + * Returns the primary replica for this driver connection. * *

Examples

*
-     * driver.replicas()
+     * driver.primaryReplica();
      * 
*/ @CheckReturnValue - Set replicas(); + Optional primaryReplica(); /** - * Returns the primary replica for this driver connection. + * 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. * *

Examples

*
-     * driver.primaryReplica()
+     * 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 */ - @CheckReturnValue - Optional primaryReplica(); + 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); } diff --git a/java/api/TransactionOptions.java b/java/api/TransactionOptions.java index 4427e2ed98..96b2980a15 100644 --- a/java/api/TransactionOptions.java +++ b/java/api/TransactionOptions.java @@ -67,7 +67,7 @@ public Optional transactionTimeoutMillis() { } /** - * Explicitly setsa 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

diff --git a/java/api/server/ServerReplica.java b/java/api/server/ServerReplica.java index ac2bde8678..4a7ff02b6f 100644 --- a/java/api/server/ServerReplica.java +++ b/java/api/server/ServerReplica.java @@ -20,7 +20,6 @@ package com.typedb.driver.api.server; import javax.annotation.CheckReturnValue; -import java.math.BigInteger; /** * The metadata and state of an individual raft replica of a driver connection. @@ -31,7 +30,7 @@ public interface ServerReplica { * Returns the id of this replica. */ @CheckReturnValue - BigInteger getID(); + long getID(); /** * Returns the address this replica is hosted at. diff --git a/java/connection/DriverImpl.java b/java/connection/DriverImpl.java index 253c1b5c7e..d55fd52941 100644 --- a/java/connection/DriverImpl.java +++ b/java/connection/DriverImpl.java @@ -40,12 +40,14 @@ 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_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 java.util.stream.Collectors.toSet; @@ -157,6 +159,24 @@ public Optional primaryReplica() { 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 close() { try { diff --git a/java/connection/ServerReplicaImpl.java b/java/connection/ServerReplicaImpl.java index 171fdfc1f0..73a0624d55 100644 --- a/java/connection/ServerReplicaImpl.java +++ b/java/connection/ServerReplicaImpl.java @@ -23,8 +23,6 @@ import com.typedb.driver.api.server.ServerReplica; import com.typedb.driver.common.NativeObject; -import java.math.BigInteger; - import static com.typedb.driver.jni.typedb_driver.server_replica_address; import static com.typedb.driver.jni.typedb_driver.server_replica_id; import static com.typedb.driver.jni.typedb_driver.server_replica_is_primary; @@ -37,7 +35,7 @@ public ServerReplicaImpl(com.typedb.driver.jni.ServerReplica serverReplica) { } @Override - public BigInteger getID() { + public long getID() { return server_replica_id(nativeObject); } diff --git a/python/typedb/api/connection/driver.py b/python/typedb/api/connection/driver.py index 38b97a52c6..ed2a304d5e 100644 --- a/python/typedb/api/connection/driver.py +++ b/python/typedb/api/connection/driver.py @@ -47,6 +47,22 @@ def is_open(self) -> bool: """ pass + @property + @abstractmethod + def databases(self) -> DatabaseManager: + """ + The ``DatabaseManager`` for this connection, providing access to database management methods. + """ + pass + + @property + @abstractmethod + def users(self) -> UserManager: + """ + The ``UserManager`` for this connection, providing access to user management methods. + """ + pass + @abstractmethod def server_version(self) -> ServerVersion: """ @@ -62,14 +78,6 @@ def server_version(self) -> ServerVersion: """ pass - @property - @abstractmethod - def databases(self) -> DatabaseManager: - """ - The ``DatabaseManager`` for this connection, providing access to database management methods. - """ - pass - @abstractmethod def transaction(self, database_name: str, transaction_type: TransactionType, options: Optional[TransactionOptions] = None) -> Transaction: @@ -104,15 +112,6 @@ def close(self) -> None: """ pass - @property - @abstractmethod - def users(self) -> UserManager: - """ - The ``UserManager`` instance for this connection, providing access to user management methods. - Only for TypeDB Cloud. - """ - pass - @abstractmethod def replicas(self) -> Set[ServerReplica]: """ @@ -143,6 +142,41 @@ def primary_replica(self) -> Optional[ServerReplica]: """ 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. + + :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") + """ + 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 __enter__(self): pass diff --git a/python/typedb/connection/driver.py b/python/typedb/connection/driver.py index 16dbe99eae..9d06df8fd6 100644 --- a/python/typedb/connection/driver.py +++ b/python/typedb/connection/driver.py @@ -25,14 +25,14 @@ 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_new_with_description, driver_new_with_addresses_with_description, \ - driver_new_with_address_translation_with_description, driver_is_open, driver_force_close, driver_replicas, \ - driver_primary_replica, driver_server_version, server_replica_iterator_next, TypeDBDriver as NativeDriver, \ - TypeDBDriverExceptionNative + 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, \ + server_replica_iterator_next, TypeDBDriver as NativeDriver, TypeDBDriverExceptionNative from typedb.user.user_manager import _UserManager if TYPE_CHECKING: @@ -76,6 +76,17 @@ def _native_object_not_owned_exception(self) -> TypeDBDriverException: def _native_driver(self) -> NativeDriver: return self.native_object + def is_open(self) -> bool: + return driver_is_open(self._native_driver) + + @property + def databases(self) -> _DatabaseManager: + return _DatabaseManager(self._native_driver) + + @property + def users(self) -> UserManager: + return _UserManager(self._native_driver) + def server_version(self) -> ServerVersion: try: return ServerVersion(driver_server_version(self._native_driver)) @@ -88,17 +99,6 @@ def transaction(self, database_name: str, transaction_type: TransactionType, 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) - - @property - def databases(self) -> _DatabaseManager: - return _DatabaseManager(self._native_driver) - - @property - def users(self) -> UserManager: - return _UserManager(self._native_driver) - def replicas(self) -> set[ServerReplica]: try: replica_iter = IteratorWrapper(driver_replicas(self._native_driver), server_replica_iterator_next) @@ -111,6 +111,15 @@ def primary_replica(self) -> Optional[ServerReplica]: 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") + driver_register_replica(self._native_driver) + + def deregister_replica(self, replica_id: int) -> None: + require_non_negative(replica_id, "replica_id") + driver_deregister_replica(self._native_driver) + def __enter__(self): return self diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 75978c0437..e98a251ff5 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -60,7 +60,7 @@ [dependencies.typedb-protocol] features = [] - rev = "5230f8aff1a70f42b0ae4c82d042a359350233dd" + rev = "d1b71067c7e78364c69701625ea3f6d6b1f24a9d" git = "https://github.com/typedb/typedb-protocol" default-features = false diff --git a/rust/src/connection/server/server_replica.rs b/rust/src/connection/server/server_replica.rs index 3ddd0d77cd..6417e4f76e 100644 --- a/rust/src/connection/server/server_replica.rs +++ b/rust/src/connection/server/server_replica.rs @@ -79,7 +79,7 @@ impl ServerReplica { } /// Returns the raft protocol ‘term’ of this replica. - pub fn term(&self) -> i64 { + pub fn term(&self) -> u64 { self.replica_status.term } } @@ -92,7 +92,7 @@ pub(crate) struct ReplicaStatus { /// The role of this replica in the raft cluster. pub replica_type: ReplicaType, /// The raft protocol ‘term’ of this server replica. - pub term: i64, + pub term: u64, } impl Default for ReplicaStatus { diff --git a/rust/src/driver.rs b/rust/src/driver.rs index 38f6fec14b..6735b93c28 100644 --- a/rust/src/driver.rs +++ b/rust/src/driver.rs @@ -120,6 +120,39 @@ impl TypeDBDriver { Ok(Self { server_manager, database_manager, user_manager, background_runtime }) } + /// Checks it this connection is opened. + /// + /// # Examples + /// + /// ```rust + /// driver.is_open() + /// ``` + pub fn is_open(&self) -> bool { + 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. @@ -154,6 +187,28 @@ impl TypeDBDriver { .await } + /// Retrieves the server's replicas. + /// + /// # Examples + /// + /// ```rust + /// driver.replicas() + /// ``` + pub fn replicas(&self) -> HashSet { + self.server_manager.replicas() + } + + /// Retrieves the server's primary replica, if exists. + /// + /// # Examples + /// + /// ```rust + /// driver.primary_replica() + /// ``` + pub fn primary_replica(&self) -> Option { + 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. /// @@ -191,47 +246,6 @@ impl TypeDBDriver { self.server_manager.deregister_replica(replica_id).await } - /// Retrieves the server's replicas. - /// - /// # Examples - /// - /// ```rust - /// driver.replicas() - /// ``` - pub fn replicas(&self) -> HashSet { - self.server_manager.replicas() - } - - /// Retrieves the server's primary replica, if exists. - /// - /// # Examples - /// - /// ```rust - /// driver.primary_replica() - /// ``` - pub fn primary_replica(&self) -> Option { - self.server_manager.primary_replica() - } - - /// Checks it this connection is opened. - /// - /// # Examples - /// - /// ```rust - /// driver.is_open() - /// ``` - pub fn is_open(&self) -> bool { - self.background_runtime.is_open() - } - - pub fn databases(&self) -> &DatabaseManager { - &self.database_manager - } - - pub fn users(&self) -> &UserManager { - &self.user_manager - } - /// Opens a transaction with default options. /// /// See [`TypeDBDriver::transaction_with_options`] for more details. From c4f7f8ba10a33bced008e73f345a9991b40f60c7 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Mon, 7 Jul 2025 14:41:22 +0100 Subject: [PATCH 27/35] Adjust tests a little --- c/src/driver.rs | 3 +- rust/Cargo.toml | 4 + rust/src/common/address/mod.rs | 3 +- rust/src/common/mod.rs | 2 +- rust/src/connection/network/proto/server.rs | 6 +- rust/src/connection/network/stub.rs | 10 +- .../src/connection/network/transmitter/rpc.rs | 8 +- rust/src/connection/server/server_manager.rs | 8 +- rust/src/lib.rs | 4 +- rust/tests/BUILD | 1 + rust/tests/integration/BUILD | 5 - rust/tests/integration/cluster/BUILD | 23 +- rust/tests/integration/cluster/clustering.rs | 241 ++++++++++++++++++ rust/tests/integration/cluster/mod.rs | 2 + tool/test/temp-cluster-server/BUILD | 68 +++++ tool/test/temp-cluster-server/config.yml | 34 +++ 16 files changed, 400 insertions(+), 22 deletions(-) create mode 100644 rust/tests/integration/cluster/clustering.rs create mode 100644 tool/test/temp-cluster-server/BUILD create mode 100644 tool/test/temp-cluster-server/config.yml diff --git a/c/src/driver.rs b/c/src/driver.rs index 9ebedd2100..d627b7b4d0 100644 --- a/c/src/driver.rs +++ b/c/src/driver.rs @@ -26,11 +26,10 @@ use crate::{ error::{try_release, unwrap_or_default, unwrap_void}, iterator::CIterator, iterators_to_map, - memory::{borrow, free, release, release_optional, string_array_view, string_view}, + memory::{borrow, borrow_mut, free, release, release_optional, string_array_view, string_view}, }, server::{server_replica::ServerReplicaIterator, server_version::ServerVersion}, }; -use crate::common::memory::borrow_mut; const DRIVER_LANG: &'static str = "c"; diff --git a/rust/Cargo.toml b/rust/Cargo.toml index e98a251ff5..22c56bb669 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -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/src/common/address/mod.rs b/rust/src/common/address/mod.rs index 90f1b24946..83ba5ed8f2 100644 --- a/rust/src/common/address/mod.rs +++ b/rust/src/common/address/mod.rs @@ -17,8 +17,7 @@ * under the License. */ -pub(crate) use self::address::Address; -pub use self::addresses::Addresses; +pub use self::{address::Address, addresses::Addresses}; pub(crate) mod address; pub(crate) mod address_translation; diff --git a/rust/src/common/mod.rs b/rust/src/common/mod.rs index 1ff5fdcdfa..3fb96295e3 100644 --- a/rust/src/common/mod.rs +++ b/rust/src/common/mod.rs @@ -18,7 +18,7 @@ */ pub use self::{ - address::Addresses, + address::{Address, Addresses}, error::Error, promise::{box_promise, BoxPromise, Promise}, query_options::QueryOptions, diff --git a/rust/src/connection/network/proto/server.rs b/rust/src/connection/network/proto/server.rs index 245ed6e014..7b2d6a55a0 100644 --- a/rust/src/connection/network/proto/server.rs +++ b/rust/src/connection/network/proto/server.rs @@ -45,7 +45,11 @@ impl TryFromProto for ServerReplica { 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 }) + Ok(Self { + id: proto.replica_id, + replica_type: ReplicaType::try_from_proto(proto.replica_type)?, + term: proto.term, + }) } } diff --git a/rust/src/connection/network/stub.rs b/rust/src/connection/network/stub.rs index 3bfa3914be..929d12c25f 100644 --- a/rust/src/connection/network/stub.rs +++ b/rust/src/connection/network/stub.rs @@ -89,11 +89,17 @@ 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 { + 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 { + 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 } diff --git a/rust/src/connection/network/transmitter/rpc.rs b/rust/src/connection/network/transmitter/rpc.rs index cc5ca5bdca..cb8b13b00a 100644 --- a/rust/src/connection/network/transmitter/rpc.rs +++ b/rust/src/connection/network/transmitter/rpc.rs @@ -115,8 +115,12 @@ 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::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) } diff --git a/rust/src/connection/server/server_manager.rs b/rust/src/connection/server/server_manager.rs index 1dceca2f34..500e7b54cc 100644 --- a/rust/src/connection/server/server_manager.rs +++ b/rust/src/connection/server/server_manager.rs @@ -116,13 +116,15 @@ impl ServerManager { let address = address.clone(); async move { server_connection.servers_register(replica_id, address).await } }) - .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, |server_connection| async move { server_connection.servers_deregister(replica_id).await }) - .await + self.execute(ConsistencyLevel::Strong, |server_connection| async move { + server_connection.servers_deregister(replica_id).await + }) + .await } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 5954166b59..87bc5942c6 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -22,8 +22,8 @@ pub use self::{ common::{ - box_stream, consistency_level, error, info, Addresses, 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}, 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/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..cdb4447bfe 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_cluster_server_bin", + "//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..4717aa2c76 --- /dev/null +++ b/rust/tests/integration/cluster/clustering.rs @@ -0,0 +1,241 @@ +/* + * 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, io, + process::{Child, Command, ExitStatus}, + str::FromStr, + time::Duration, +}; + +use async_std::task::sleep; +use futures::{StreamExt, TryStreamExt}; +use serial_test::serial; +use typedb_driver::{ + answer::ConceptRow, Address, 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 USERNAME: &'static str = "admin"; +const PASSWORD: &'static str = "password"; + +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(()) +} + +#[test] +#[serial] +fn primary_reelection_read() { + 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 ADDRESSES[1..].iter().enumerate() { + driver.register_replica((i + 1) as u64, address.to_string()).await; + } + + // 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!"); + }) +} + +fn start_server(index: &str) -> Child { + // Command::new(format!("../{index}/typedb")) + // TODO: Temporary, should be called in a better way + Command::new(format!( + "{}/tool/test/temp-cluster-server/typedb_cluster_server_bin", + 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"), + "--diagnostics.deployment-id", + "test", + "--server.encryption.enabled", + "false", + "--diagnostics.monitoring.port", + &format!("{index}1731"), + "--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(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 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/tool/test/temp-cluster-server/BUILD b/tool/test/temp-cluster-server/BUILD new file mode 100644 index 0000000000..6884fdaa9c --- /dev/null +++ b/tool/test/temp-cluster-server/BUILD @@ -0,0 +1,68 @@ +#!/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") +load("@typedb_bazel_distribution//artifact:rules.bzl", "artifact_extractor") +load("@typedb_dependencies//builder/java:rules.bzl", "native_typedb_artifact") + + +exports_files([ + "temp-cluster-server/typedb_cluster_server_bin", +]) + +checkstyle_test( + name = "checkstyle", + include = glob(["*"]), + license_type = "apache-header", +) + +native_typedb_artifact( + name = "native-typedb-artifact", + native_artifacts = { + "@typedb_bazel_distribution//platform:is_linux_arm64": ["@typedb_artifact_linux-arm64//file"], + "@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"], + }, + output = "typedb-artifact.tar.gz", + visibility = ["//visibility:public"], +) + +#native_typedb_artifact( +# name = "native-typedb-cluster-artifact", +# native_artifacts = { +# "@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-cluster-artifact.tar.gz", +# visibility = ["//visibility:public"], +#) + +artifact_extractor( + name = "typedb-extractor", + artifact = ":native-typedb-artifact", +) + +artifact_extractor( + name = "typedb-cluster-extractor", + artifact = ":native-typedb-cluster-artifact", +) diff --git a/tool/test/temp-cluster-server/config.yml b/tool/test/temp-cluster-server/config.yml new file mode 100644 index 0000000000..78b3301e54 --- /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: true + 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: true + port: 4104 + reporting: + metrics: true + errors: true + +development-mode: + enabled: false From 53af3d77bba9f36a2d2dc188b10894968a20c6fa Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Mon, 7 Jul 2025 15:12:47 +0100 Subject: [PATCH 28/35] Add display to Serverversion --- rust/src/connection/server/server_version.rs | 7 +++++++ rust/src/driver.rs | 2 ++ 2 files changed, 9 insertions(+) diff --git a/rust/src/connection/server/server_version.rs b/rust/src/connection/server/server_version.rs index 817d144b84..2214f691cc 100644 --- a/rust/src/connection/server/server_version.rs +++ b/rust/src/connection/server/server_version.rs @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +use std::fmt; /// A full TypeDB server's version specification #[derive(Debug, Clone)] @@ -35,3 +36,9 @@ impl ServerVersion { &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/driver.rs b/rust/src/driver.rs index 6735b93c28..d5df44ddfb 100644 --- a/rust/src/driver.rs +++ b/rust/src/driver.rs @@ -228,6 +228,8 @@ impl TypeDBDriver { 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. /// From f8c3906ad60ef91a3074db0c10861d142a9d4ba9 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Mon, 7 Jul 2025 17:20:38 +0100 Subject: [PATCH 29/35] Add dynamic address translation update --- c/src/driver.rs | 18 ++++ java/TypeDB.java | 2 +- java/api/Driver.java | 35 +++++-- java/api/database/DatabaseManager.java | 4 +- java/api/server/ServerReplica.java | 1 + java/connection/DriverImpl.java | 34 +++++-- python/typedb/api/connection/driver.py | 50 ++++++---- python/typedb/connection/driver.py | 58 +++++++---- rust/src/common/error.rs | 2 + rust/src/connection/server/server_manager.rs | 24 ++++- rust/src/driver.rs | 25 +++++ rust/tests/integration/cluster/BUILD | 2 +- rust/tests/integration/cluster/clustering.rs | 58 ++++++----- .../start-cluster-servers.sh | 95 +++++++++++++++++++ .../stop-cluster-servers.sh | 27 ++++++ 15 files changed, 352 insertions(+), 83 deletions(-) create mode 100755 tool/test/temp-cluster-server/start-cluster-servers.sh create mode 100755 tool/test/temp-cluster-server/stop-cluster-servers.sh diff --git a/c/src/driver.rs b/c/src/driver.rs index d627b7b4d0..a9a3401ed5 100644 --- a/c/src/driver.rs +++ b/c/src/driver.rs @@ -221,3 +221,21 @@ pub extern "C" fn driver_register_replica(driver: *const TypeDBDriver, replica_i 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/java/TypeDB.java b/java/TypeDB.java index a5bb7a0776..84c6034528 100644 --- a/java/TypeDB.java +++ b/java/TypeDB.java @@ -68,7 +68,7 @@ public static Driver driver(Set addresses, Credentials credentials, Driv * *

Examples

*
-     * TypeDB.driver(address);
+     * TypeDB.driver(addresses);
      * 
* * @param addressTranslation The translation of public TypeDB cluster replica addresses (keys) to server-side private addresses (values) diff --git a/java/api/Driver.java b/java/api/Driver.java index 93f8ea29e2..29a4753ed0 100644 --- a/java/api/Driver.java +++ b/java/api/Driver.java @@ -26,6 +26,7 @@ import com.typedb.driver.common.exception.TypeDBDriverException; import javax.annotation.CheckReturnValue; +import java.util.Map; import java.util.Optional; import java.util.Set; @@ -105,16 +106,6 @@ public interface Driver extends AutoCloseable { @CheckReturnValue 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. - * - *

Examples

- *
-     * driver.close();
-     * 
- */ - void close(); - /** * Set of Replica instances for this driver connection. * @@ -163,4 +154,28 @@ public interface Driver extends AutoCloseable { * @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/database/DatabaseManager.java b/java/api/database/DatabaseManager.java index 636a50ca13..79ac46450d 100644 --- a/java/api/database/DatabaseManager.java +++ b/java/api/database/DatabaseManager.java @@ -83,7 +83,7 @@ default boolean contains(String name) throws TypeDBDriverException { * driver.databases().contains(name, ConsistencyLevel.Strong) * * - * @param name The database name to be checked + * @param name The database name to be checked * @param consistencyLevel The consistency level to use for the operation */ @CheckReturnValue @@ -113,7 +113,7 @@ default Database get(String name) throws TypeDBDriverException { * driver.databases().get(name, ConsistencyLevel.Strong) * * - * @param name The name of the database to retrieve + * @param name The name of the database to retrieve * @param consistencyLevel The consistency level to use for the operation */ @CheckReturnValue diff --git a/java/api/server/ServerReplica.java b/java/api/server/ServerReplica.java index 4a7ff02b6f..c9f0519bc5 100644 --- a/java/api/server/ServerReplica.java +++ b/java/api/server/ServerReplica.java @@ -26,6 +26,7 @@ */ 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. */ diff --git a/java/connection/DriverImpl.java b/java/connection/DriverImpl.java index d55fd52941..76e83fdc5e 100644 --- a/java/connection/DriverImpl.java +++ b/java/connection/DriverImpl.java @@ -34,6 +34,7 @@ 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; @@ -50,6 +51,7 @@ 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 { @@ -97,13 +99,8 @@ private static com.typedb.driver.jni.TypeDBDriver open(Map addre Validator.requireNonNull(credentials, "credentials"); Validator.requireNonNull(driverOptions, "driverOptions"); try { - List publicAddresses = new ArrayList<>(); - List privateAddresses = new ArrayList<>(); - for (Map.Entry entry : addressTranslation.entrySet()) { - publicAddresses.add(entry.getKey()); - privateAddresses.add(entry.getValue()); - } - return driver_new_with_address_translation_with_description(publicAddresses.toArray(new String[0]), privateAddresses.toArray(new String[0]), credentials.nativeObject, driverOptions.nativeObject, LANGUAGE); + 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); } @@ -177,6 +174,17 @@ public void deregisterReplica(long replicaID) { } } + @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 { @@ -185,4 +193,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/python/typedb/api/connection/driver.py b/python/typedb/api/connection/driver.py index ed2a304d5e..5cdea31231 100644 --- a/python/typedb/api/connection/driver.py +++ b/python/typedb/api/connection/driver.py @@ -18,7 +18,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Optional, Set +from typing import TYPE_CHECKING, Optional, Set, Mapping if TYPE_CHECKING: from typedb.api.connection.database import DatabaseManager @@ -97,21 +97,6 @@ def transaction(self, database_name: str, transaction_type: TransactionType, """ 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 - @abstractmethod def replicas(self) -> Set[ServerReplica]: """ @@ -177,6 +162,39 @@ def deregister_replica(self, replica_id: int) -> None: """ 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 + @abstractmethod def __enter__(self): pass diff --git a/python/typedb/connection/driver.py b/python/typedb/connection/driver.py index 9d06df8fd6..7e2f144149 100644 --- a/python/typedb/connection/driver.py +++ b/python/typedb/connection/driver.py @@ -17,7 +17,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, Tuple from typedb.api.connection.driver import Driver from typedb.api.connection.transaction import Transaction @@ -32,7 +32,8 @@ 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, \ - server_replica_iterator_next, TypeDBDriver as NativeDriver, TypeDBDriverExceptionNative + driver_update_address_translation, server_replica_iterator_next, TypeDBDriver as NativeDriver, \ + TypeDBDriverExceptionNative from typedb.user.user_manager import _UserManager if TYPE_CHECKING: @@ -52,18 +53,19 @@ def __init__(self, addresses: str | list[str] | dict[str, str], credentials: Cre require_non_null(credentials, "credentials") require_non_null(driver_options, "driver_options") - if isinstance(addresses, str): - driver_new_fn = driver_new_with_description - elif isinstance(addresses, list): - driver_new_fn = driver_new_with_addresses_with_description - elif isinstance(addresses, dict): - driver_new_fn = driver_new_with_address_translation_with_description - else: - raise TypeDBDriverException(INVALID_ADDRESS_FORMAT) - try: - native_driver = driver_new_fn(addresses, 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) @@ -114,11 +116,34 @@ def primary_replica(self) -> Optional[ServerReplica]: def register_replica(self, replica_id: int, address: str) -> None: require_non_negative(replica_id, "replica_id") require_non_null(address, "address") - driver_register_replica(self._native_driver) + 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") - driver_deregister_replica(self._native_driver) + 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 @@ -127,6 +152,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/rust/src/common/error.rs b/rust/src/common/error.rs index 231be9af2d..5f2d9887ef 100644 --- a/rust/src/common/error.rs +++ b/rust/src/common/error.rs @@ -199,6 +199,8 @@ error_messages! { ConnectionError 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 diff --git a/rust/src/connection/server/server_manager.rs b/rust/src/connection/server/server_manager.rs index 500e7b54cc..33a39fe70a 100644 --- a/rust/src/connection/server/server_manager.rs +++ b/rust/src/connection/server/server_manager.rs @@ -48,7 +48,7 @@ pub(crate) struct ServerManager { replicas: RwLock>, server_connections: RwLock>, connection_scheme: http::uri::Scheme, - address_translation: AddressTranslation, + address_translation: RwLock, background_runtime: Arc, credentials: Credentials, @@ -99,7 +99,7 @@ impl ServerManager { replicas: RwLock::new(replicas), server_connections: RwLock::new(source_connections), connection_scheme, - address_translation, + address_translation: RwLock::new(address_translation), background_runtime, credentials, driver_options, @@ -127,6 +127,16 @@ impl ServerManager { .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_server_connections(&self) -> Result { let replicas = self.read_replicas().clone(); @@ -198,6 +208,10 @@ impl ServerManager { 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_server_connections().values().map(ServerConnection::force_close).try_collect().map_err(Into::into) } @@ -234,7 +248,8 @@ impl ServerManager { } .into()); } - let private_address = self.address_translation.to_private(&address).unwrap_or_else(|| address.clone()); + let private_address = + self.read_address_translation().to_private(&address).unwrap_or_else(|| address.clone()); self.execute_on(&address, &private_address, false, &task).await } } @@ -428,10 +443,11 @@ impl ServerManager { #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] async fn fetch_replicas(&self, server_connection: &ServerConnection) -> Result> { + let address_translation = self.read_address_translation(); server_connection .servers_all() .await - .map(|replicas| Self::translate_replicas(replicas, &self.connection_scheme, &self.address_translation)) + .map(|replicas| Self::translate_replicas(replicas, &self.connection_scheme, &address_translation)) } fn translate_replicas( diff --git a/rust/src/driver.rs b/rust/src/driver.rs index d5df44ddfb..6c938a4685 100644 --- a/rust/src/driver.rs +++ b/rust/src/driver.rs @@ -248,6 +248,31 @@ impl TypeDBDriver { 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 + #[cfg_attr( + feature = "sync", + doc = "driver.update_address_translation(Addresses::try_from_translation_str([(\"typedb-cloud.ext:11729\", \"127.0.0.1:1729\")].into()).unwrap())" + )] + #[cfg_attr( + not(feature = "sync"), + doc = "driver.update_address_translation(Addresses::try_from_translation_str([(\"typedb-cloud.ext:11729\", \"127.0.0.1:1729\")].into()).unwrap()).await" + )] + /// ``` + #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] + pub async fn update_address_translation(&self, addresses: Addresses) -> Result { + self.server_manager.update_address_translation(addresses).await + } + /// Opens a transaction with default options. /// /// See [`TypeDBDriver::transaction_with_options`] for more details. diff --git a/rust/tests/integration/cluster/BUILD b/rust/tests/integration/cluster/BUILD index cdb4447bfe..99c58c8eb8 100644 --- a/rust/tests/integration/cluster/BUILD +++ b/rust/tests/integration/cluster/BUILD @@ -37,7 +37,7 @@ rust_test( "@crates//:uuid", ], # TODO: Temporary. Use a binary extracted in a better way - data = ["//tool/test/temp-cluster-server:typedb_cluster_server_bin", + data = ["//tool/test/temp-cluster-server:typedb", "//tool/test/temp-cluster-server:config.yml"], ) diff --git a/rust/tests/integration/cluster/clustering.rs b/rust/tests/integration/cluster/clustering.rs index 4717aa2c76..4ed8e6f7cb 100644 --- a/rust/tests/integration/cluster/clustering.rs +++ b/rust/tests/integration/cluster/clustering.rs @@ -17,7 +17,8 @@ * under the License. */ use std::{ - env, io, + env, fs, io, + path::Path, process::{Child, Command, ExitStatus}, str::FromStr, time::Duration, @@ -80,7 +81,7 @@ fn primary_reelection_read() { .unwrap(); for (i, address) in ADDRESSES[1..].iter().enumerate() { - driver.register_replica((i + 1) as u64, address.to_string()).await; + driver.register_replica((i + 1) as u64, address.to_string()).await.unwrap(); } // sleep(Duration::from_secs(5)).await; @@ -130,29 +131,38 @@ fn primary_reelection_read() { 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_cluster_server_bin", - 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"), - "--diagnostics.deployment-id", - "test", - "--server.encryption.enabled", - "false", - "--diagnostics.monitoring.port", - &format!("{index}1731"), - "--development-mode.enabled", - "true", - ]) - .spawn() - .expect("Failed to start TypeDB server") + 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"), + "--development-mode.enabled", + "true", + ]) + .spawn() + .expect("Failed to start TypeDB server") } async fn get_primary_replica(driver: &TypeDBDriver) -> ServerReplica { 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..e12d8cdd88 --- /dev/null +++ b/tool/test/temp-cluster-server/start-cluster-servers.sh @@ -0,0 +1,95 @@ +#!/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} +ENCRYPTION_ENABLED=${2:-true} + +# 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() { + ./${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-cluster-all + +#bazel run //tool/test:typedb-cluster-extractor -- typedb-cluster-all +bazel run //tool/test:typedb-extractor -- typedb-cluster-all + +echo Successfully unarchived a TypeDB distribution. Creating $NODE_COUNT copies ${1}. +for i in $(seq 1 $NODE_COUNT); do + cp -r typedb-cluster-all $i || 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..63b3992753 --- /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 + +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 From f2ada4d27fda7402af7992ab7280870fa1176547 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Mon, 7 Jul 2025 17:34:09 +0100 Subject: [PATCH 30/35] Fix build --- rust/src/driver.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/rust/src/driver.rs b/rust/src/driver.rs index 6c938a4685..8f3206769f 100644 --- a/rust/src/driver.rs +++ b/rust/src/driver.rs @@ -259,18 +259,10 @@ impl TypeDBDriver { /// # Examples /// /// ```rust - #[cfg_attr( - feature = "sync", - doc = "driver.update_address_translation(Addresses::try_from_translation_str([(\"typedb-cloud.ext:11729\", \"127.0.0.1:1729\")].into()).unwrap())" - )] - #[cfg_attr( - not(feature = "sync"), - doc = "driver.update_address_translation(Addresses::try_from_translation_str([(\"typedb-cloud.ext:11729\", \"127.0.0.1:1729\")].into()).unwrap()).await" - )] + /// driver.update_address_translation(Addresses::try_from_translation_str([("typedb-cloud.ext:11729", "127.0.0.1:1729")].into()).unwrap()) /// ``` - #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - pub async fn update_address_translation(&self, addresses: Addresses) -> Result { - self.server_manager.update_address_translation(addresses).await + pub fn update_address_translation(&self, addresses: Addresses) -> Result { + self.server_manager.update_address_translation(addresses) } /// Opens a transaction with default options. From 41e42b94ef0bee98d59ae627e46c45acb14a0700 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Mon, 7 Jul 2025 17:38:46 +0100 Subject: [PATCH 31/35] Fix console build to make everything Send + Sync --- rust/src/connection/server/server_manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/src/connection/server/server_manager.rs b/rust/src/connection/server/server_manager.rs index 33a39fe70a..36f3e34c96 100644 --- a/rust/src/connection/server/server_manager.rs +++ b/rust/src/connection/server/server_manager.rs @@ -443,7 +443,7 @@ impl ServerManager { #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] async fn fetch_replicas(&self, server_connection: &ServerConnection) -> Result> { - let address_translation = self.read_address_translation(); + let address_translation = self.read_address_translation().clone(); server_connection .servers_all() .await From 2c16f2d949c7d9bdf4b549611e847abf895126bc Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Tue, 8 Jul 2025 18:06:01 +0100 Subject: [PATCH 32/35] Propagate options from Rust to Java. Fix small thingies, renames, progress with testing --- c/src/database/database.rs | 56 ++++-- c/src/database/database_manager.rs | 6 +- c/src/driver.rs | 30 +++- c/src/driver_options.rs | 71 ++++++++ c/src/server/server_replica.rs | 8 +- c/swig/typedb_driver_java.swg | 17 +- c/typedb_driver.i | 2 +- java/api/Driver.java | 19 +- java/api/DriverOptions.java | 116 +++++++++++- java/api/database/Database.java | 68 ++++++- java/api/database/DatabaseManager.java | 3 - java/connection/DatabaseImpl.java | 13 +- java/connection/DriverImpl.java | 11 +- java/connection/ServerReplicaImpl.java | 16 +- python/typedb/api/connection/database.py | 12 +- python/typedb/api/connection/driver.py | 6 +- python/typedb/connection/database.py | 16 +- python/typedb/connection/driver.py | 6 +- python/typedb/connection/server_replica.py | 13 +- rust/src/common/error.rs | 2 + rust/src/connection/driver_options.rs | 16 +- rust/src/connection/server/server_manager.rs | 154 +++++++++------- rust/src/driver.rs | 15 +- rust/tests/integration/cluster/clustering.rs | 175 ++++++++++++++----- tool/test/start-cluster-servers.sh | 9 +- 25 files changed, 658 insertions(+), 202 deletions(-) diff --git a/c/src/database/database.rs b/c/src/database/database.rs index aa3641ba1d..f7e1301abd 100644 --- a/c/src/database/database.rs +++ b/c/src/database/database.rs @@ -21,39 +21,64 @@ 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}, +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 +/// 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. +/// 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. +/// 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) -> *mut c_char { - try_release_string(borrow(database).schema()) +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) -> *mut c_char { - try_release_string(borrow(database).type_schema()) +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. @@ -62,13 +87,22 @@ pub extern "C" fn database_type_schema(database: *const Database) -> *mut c_char /// @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)); - unwrap_void(borrow(database).export_to_file(schema_file_path, data_file_path)) + 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/database_manager.rs b/c/src/database/database_manager.rs index a2e21bc655..18ebcad206 100644 --- a/c/src/database/database_manager.rs +++ b/c/src/database/database_manager.rs @@ -49,7 +49,7 @@ 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. +/// @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, @@ -66,7 +66,7 @@ pub extern "C" fn databases_all( /// Checks if a database with the given name exists. /// /// @param driver The TypeDBDriver object. -/// @param consistency_level The consistency level to use for the operation. +/// @param consistency_level The consistency level to use for the operation. Strongest possible if null. /// @param name The name of the database. #[no_mangle] pub extern "C" fn databases_contains( @@ -85,7 +85,7 @@ pub extern "C" fn databases_contains( /// Retrieves the database with the given name. /// /// @param driver The TypeDBDriver object. -/// @param consistency_level The consistency level to use for the operation. +/// @param consistency_level The consistency level to use for the operation. Strongest possible if null. /// @param name The name of the database. #[no_mangle] pub extern "C" fn databases_get( diff --git a/c/src/driver.rs b/c/src/driver.rs index a9a3401ed5..b322da0c88 100644 --- a/c/src/driver.rs +++ b/c/src/driver.rs @@ -26,9 +26,13 @@ use crate::{ error::{try_release, unwrap_or_default, unwrap_void}, iterator::CIterator, iterators_to_map, - memory::{borrow, borrow_mut, free, release, release_optional, string_array_view, string_view}, + memory::{borrow, free, release, release_optional, string_array_view, string_view}, + }, + server::{ + consistency_level::{native_consistency_level, ConsistencyLevel}, + server_replica::ServerReplicaIterator, + server_version::ServerVersion, }, - server::{server_replica::ServerReplicaIterator, server_version::ServerVersion}, }; const DRIVER_LANG: &'static str = "c"; @@ -164,14 +168,14 @@ pub extern "C" fn driver_new_with_address_translation_with_description( )) } -/// Closes the driver. Before instantiating a new driver, the driver that’s currently open should first be closed. +/// 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 driver. To be used in exceptional cases. +/// 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()); @@ -184,9 +188,20 @@ pub extern "C" fn driver_is_open(driver: *const TypeDBDriver) -> bool { } /// 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) -> *mut ServerVersion { - release(unwrap_or_default(borrow(driver).server_version().map(|server_version| { +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()) }))) } @@ -194,7 +209,7 @@ pub extern "C" fn driver_server_version(driver: *const TypeDBDriver) -> *mut Ser /// Retrieves the server's replicas. #[no_mangle] pub extern "C" fn driver_replicas(driver: *const TypeDBDriver) -> *mut ServerReplicaIterator { - release(ServerReplicaIterator(CIterator(box_stream(borrow(driver).replicas().into_iter())))) + release(ServerReplicaIterator(CIterator(box_stream(unwrap_or_default(borrow(driver).replicas()).into_iter())))) } /// Retrieves the server's primary replica, if exists. @@ -205,6 +220,7 @@ pub extern "C" fn driver_primary_replica(driver: *const TypeDBDriver) -> *mut Se /// 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 diff --git a/c/src/driver_options.rs b/c/src/driver_options.rs index ccfe5137d5..301489d967 100644 --- a/c/src/driver_options.rs +++ b/c/src/driver_options.rs @@ -72,3 +72,74 @@ pub extern "C" fn driver_options_get_tls_root_ca_path(options: *const DriverOpti 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/server/server_replica.rs b/c/src/server/server_replica.rs index 710191d7d4..851cf64fc6 100644 --- a/c/src/server/server_replica.rs +++ b/c/src/server/server_replica.rs @@ -50,19 +50,19 @@ pub extern "C" fn server_replica_drop(replica_info: *mut ServerReplica) { /// Returns the id of this replica. #[no_mangle] -pub extern "C" fn server_replica_id(replica_info: *const ServerReplica) -> i64 { +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_address(replica_info: *const ServerReplica) -> *mut c_char { +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_type(replica_info: *const ServerReplica) -> ReplicaType { +pub extern "C" fn server_replica_get_type(replica_info: *const ServerReplica) -> ReplicaType { borrow(replica_info).replica_type() } @@ -74,6 +74,6 @@ pub extern "C" fn server_replica_is_primary(replica_info: *const ServerReplica) /// Returns the raft protocol ‘term’ of this replica. #[no_mangle] -pub extern "C" fn server_replica_term(replica_info: *const ServerReplica) -> i64 { +pub extern "C" fn server_replica_get_term(replica_info: *const ServerReplica) -> i64 { borrow(replica_info).term() as i64 } diff --git a/c/swig/typedb_driver_java.swg b/c/swig/typedb_driver_java.swg index ceb9f6e957..0d00cfa4d4 100644 --- a/c/swig/typedb_driver_java.swg +++ b/c/swig/typedb_driver_java.swg @@ -104,7 +104,7 @@ %nojavaexception error_message; %nojavaexception driver_is_open; -%nojavaexception driver_replicas; +// TODO: Remove from this list if we start making network calls to retrieve this %nojavaexception driver_primary_replica; %nojavaexception driver_options_new; @@ -113,17 +113,24 @@ %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 server_replica_address; +%nojavaexception server_replica_get_address; %nojavaexception server_replica_is_primary; -%nojavaexception server_replica_id; -%nojavaexception server_replica_type; -%nojavaexception server_replica_term; +%nojavaexception server_replica_get_id; +%nojavaexception server_replica_get_type; +%nojavaexception server_replica_get_term; %nojavaexception database_get_name; diff --git a/c/typedb_driver.i b/c/typedb_driver.i index 749e70ce39..d7d2cc7558 100644 --- a/c/typedb_driver.i +++ b/c/typedb_driver.i @@ -190,7 +190,7 @@ void transaction_on_close_register(const Transaction* transaction, TransactionCa %newobject database_type_schema; %delobject database_delete; -%newobject server_replica_address; +%newobject server_replica_get_address; %newobject databases_all; %newobject databases_get; diff --git a/java/api/Driver.java b/java/api/Driver.java index 29a4753ed0..c51d468207 100644 --- a/java/api/Driver.java +++ b/java/api/Driver.java @@ -44,6 +44,20 @@ 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. * @@ -51,9 +65,11 @@ public interface Driver extends AutoCloseable { *
      * driver.serverVersion();
      * 
+ * + * @param consistencyLevel The consistency level to use for the operation */ @CheckReturnValue - ServerVersion serverVersion(); + ServerVersion serverVersion(ConsistencyLevel consistencyLevel); /** * The DatabaseManager for this connection, providing access to database management methods. @@ -131,6 +147,7 @@ public interface Driver extends AutoCloseable { /** * 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

*
diff --git a/java/api/DriverOptions.java b/java/api/DriverOptions.java
index 0a58bb4ecb..64383179d3 100644
--- a/java/api/DriverOptions.java
+++ b/java/api/DriverOptions.java
@@ -25,11 +25,18 @@
 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;
 
 /**
  * TypeDB driver options. DriverOptions are used to specify the driver's connection behavior.
@@ -110,5 +117,112 @@ public DriverOptions tlsRootCAPath(Optional tlsRootCAPath) {
         return this;
     }
 
-    // TODO: Add other flags when they are finalized!
+    /**
+     * 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) { + 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) { + driver_options_set_replica_discovery_attempts(nativeObject, replicaDiscoveryAttempts); + return this; + } } diff --git a/java/api/database/Database.java b/java/api/database/Database.java index 948de93909..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. diff --git a/java/api/database/DatabaseManager.java b/java/api/database/DatabaseManager.java index 79ac46450d..52b2572c8a 100644 --- a/java/api/database/DatabaseManager.java +++ b/java/api/database/DatabaseManager.java @@ -37,8 +37,6 @@ public interface DatabaseManager { *
      * driver.databases().all()
      * 
- * - * @see #all(ConsistencyLevel) */ @CheckReturnValue default List all() throws TypeDBDriverException { @@ -54,7 +52,6 @@ default List all() throws TypeDBDriverException { *
* * @param consistencyLevel The consistency level to use for the operation - * @see #all() */ @CheckReturnValue List all(ConsistencyLevel consistencyLevel) throws TypeDBDriverException; diff --git a/java/connection/DatabaseImpl.java b/java/connection/DatabaseImpl.java index 62bb0492e1..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); } diff --git a/java/connection/DriverImpl.java b/java/connection/DriverImpl.java index 76e83fdc5e..1957c3179c 100644 --- a/java/connection/DriverImpl.java +++ b/java/connection/DriverImpl.java @@ -19,6 +19,7 @@ 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; @@ -112,9 +113,9 @@ public boolean isOpen() { } @Override - public ServerVersion serverVersion() { + public ServerVersion serverVersion(ConsistencyLevel consistencyLevel) { try { - return new ServerVersion(driver_server_version(nativeObject)); + return new ServerVersion(driver_server_version(nativeObject, ConsistencyLevel.nativeValue(consistencyLevel))); } catch (com.typedb.driver.jni.Error e) { throw new TypeDBDriverException(e); } @@ -144,7 +145,11 @@ public Transaction transaction(String database, Transaction.Type type, Transacti @Override public Set replicas() { - return new NativeIterator<>(driver_replicas(nativeObject)).stream().map(ServerReplicaImpl::new).collect(toSet()); + 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 diff --git a/java/connection/ServerReplicaImpl.java b/java/connection/ServerReplicaImpl.java index 73a0624d55..ac7ebae798 100644 --- a/java/connection/ServerReplicaImpl.java +++ b/java/connection/ServerReplicaImpl.java @@ -23,11 +23,11 @@ import com.typedb.driver.api.server.ServerReplica; import com.typedb.driver.common.NativeObject; -import static com.typedb.driver.jni.typedb_driver.server_replica_address; -import static com.typedb.driver.jni.typedb_driver.server_replica_id; +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_term; -import static com.typedb.driver.jni.typedb_driver.server_replica_type; +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) { @@ -36,17 +36,17 @@ public ServerReplicaImpl(com.typedb.driver.jni.ServerReplica serverReplica) { @Override public long getID() { - return server_replica_id(nativeObject); + return server_replica_get_id(nativeObject); } @Override public String getAddress() { - return server_replica_address(nativeObject); + return server_replica_get_address(nativeObject); } @Override public ReplicaType getType() { - return ReplicaType.of(server_replica_type(nativeObject)); + return ReplicaType.of(server_replica_get_type(nativeObject)); } @Override @@ -56,7 +56,7 @@ public Boolean isPrimary() { @Override public long getTerm() { - return server_replica_term(nativeObject); + return server_replica_get_term(nativeObject); } @Override diff --git a/python/typedb/api/connection/database.py b/python/typedb/api/connection/database.py index a41c5779a7..4809f781f8 100644 --- a/python/typedb/api/connection/database.py +++ b/python/typedb/api/connection/database.py @@ -33,10 +33,11 @@ def name(self) -> str: pass @abstractmethod - def schema(self) -> str: + 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: @@ -44,14 +45,16 @@ def schema(self) -> str: :: database.schema() + database.schema(ConsistencyLevel.Strong()) """ pass @abstractmethod - def type_schema(self) -> str: + 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: @@ -59,16 +62,18 @@ def type_schema(self) -> str: :: database.type_schema() + database.type_schema(ConsistencyLevel.Strong()) """ pass - 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: """ 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: @@ -76,6 +81,7 @@ def export_to_file(self, schema_file_path: str, data_file_path: str) -> None: :: database.export_to_file("schema.typeql", "data.typedb") + database.export_to_file("schema.typeql", "data.typedb", ConsistencyLevel.Strong()) """ pass diff --git a/python/typedb/api/connection/driver.py b/python/typedb/api/connection/driver.py index 5cdea31231..cd10408832 100644 --- a/python/typedb/api/connection/driver.py +++ b/python/typedb/api/connection/driver.py @@ -21,6 +21,7 @@ from typing import TYPE_CHECKING, Optional, Set, Mapping if TYPE_CHECKING: + from typedb.api.connection.consistency_level import ConsistencyLevel from typedb.api.connection.database import DatabaseManager from typedb.api.connection.transaction_options import TransactionOptions from typedb.api.connection.transaction import Transaction, TransactionType @@ -64,10 +65,11 @@ def users(self) -> UserManager: pass @abstractmethod - def server_version(self) -> ServerVersion: + 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: @@ -75,6 +77,7 @@ def server_version(self) -> ServerVersion: :: driver.server_version() + driver.server_version(ConsistencyLevel.Strong()) """ pass @@ -132,6 +135,7 @@ 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 diff --git a/python/typedb/connection/database.py b/python/typedb/connection/database.py index bfe11a49ef..3e05c79ae2 100644 --- a/python/typedb/connection/database.py +++ b/python/typedb/connection/database.py @@ -17,6 +17,9 @@ from __future__ import annotations +from typing import Optional + +from typedb.api.connection.consistency_level import ConsistencyLevel from typedb.api.connection.database import Database from typedb.common.exception import TypeDBDriverException, DATABASE_DELETED, NULL_NATIVE_OBJECT from typedb.common.native_wrapper import NativeWrapper @@ -44,23 +47,24 @@ 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 diff --git a/python/typedb/connection/driver.py b/python/typedb/connection/driver.py index 7e2f144149..4ba9804a57 100644 --- a/python/typedb/connection/driver.py +++ b/python/typedb/connection/driver.py @@ -37,6 +37,7 @@ from typedb.user.user_manager import _UserManager if TYPE_CHECKING: + from typedb.api.connection.consistency_level import ConsistencyLevel from typedb.api.connection.driver_options import DriverOptions from typedb.api.connection.credentials import Credentials from typedb.api.connection.transaction import TransactionType @@ -89,9 +90,10 @@ def databases(self) -> _DatabaseManager: def users(self) -> UserManager: return _UserManager(self._native_driver) - def server_version(self) -> ServerVersion: + def server_version(self, consistency_level: Optional[ConsistencyLevel] = None) -> ServerVersion: try: - return ServerVersion(driver_server_version(self._native_driver)) + 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 diff --git a/python/typedb/connection/server_replica.py b/python/typedb/connection/server_replica.py index 7a572e054a..61b84a786a 100644 --- a/python/typedb/connection/server_replica.py +++ b/python/typedb/connection/server_replica.py @@ -20,8 +20,9 @@ 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_address, server_replica_id, server_replica_type, \ - server_replica_is_primary, server_replica_term, ServerReplica as NativeServerReplica, TypeDBDriverExceptionNative +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]): @@ -37,22 +38,22 @@ def _native_object_not_owned_exception(self) -> TypeDBDriverException: @property def id(self) -> int: - return server_replica_id(self.native_object) + return server_replica_get_id(self.native_object) @property def address(self) -> str: - return server_replica_address(self.native_object) + return server_replica_get_address(self.native_object) @property def replica_type(self) -> str: - return server_replica_type(self.native_object) + 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_term(self.native_object) + return server_replica_get_term(self.native_object) def __str__(self): return self.address diff --git a/rust/src/common/error.rs b/rust/src/common/error.rs index 5f2d9887ef..9efb386be7 100644 --- a/rust/src/common/error.rs +++ b/rust/src/common/error.rs @@ -237,6 +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.", + Unimplemented { details: String } = + 5: "Unimplemented feature: {details}.", } #[derive(Clone, PartialEq, Eq)] diff --git a/rust/src/connection/driver_options.rs b/rust/src/connection/driver_options.rs index ed76b1962e..114186dfc2 100644 --- a/rust/src/connection/driver_options.rs +++ b/rust/src/connection/driver_options.rs @@ -53,7 +53,7 @@ pub struct DriverOptions { /// 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 redirect_failover_retries: usize, + 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: @@ -63,7 +63,7 @@ pub struct DriverOptions { /// Affects every eventually consistent operation, including redirect failover, when the new /// primary replica is unknown. /// Defaults to None. - pub discovery_failover_retries: Option, + pub replica_discovery_attempts: Option, tls_config: Option, tls_root_ca: Option, @@ -120,8 +120,8 @@ impl DriverOptions { /// 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 redirect_failover_retries(self, redirect_failover_retries: usize) -> Self { - Self { redirect_failover_retries, ..self } + 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 @@ -133,8 +133,8 @@ impl DriverOptions { /// Affects every eventually consistent operation, including redirect failover, when the new /// primary replica is unknown. /// Defaults to None. - pub fn discovery_failover_retries(self, discovery_failover_retries: Option) -> Self { - Self { discovery_failover_retries, ..self } + pub fn replica_discovery_attempts(self, replica_discovery_attempts: Option) -> Self { + Self { replica_discovery_attempts, ..self } } } @@ -143,8 +143,8 @@ impl Default for DriverOptions { Self { is_tls_enabled: DEFAULT_IS_TLS_ENABLED, use_replication: DEFAULT_USE_REPLICATION, - redirect_failover_retries: DEFAULT_REDIRECT_FAILOVER_RETRIES, - discovery_failover_retries: DEFAULT_DISCOVERY_FAILOVER_RETRIES, + 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/server/server_manager.rs b/rust/src/connection/server/server_manager.rs index 36f3e34c96..fe5eab6344 100644 --- a/rust/src/connection/server/server_manager.rs +++ b/rust/src/connection/server/server_manager.rs @@ -38,15 +38,14 @@ use crate::{ runtime::BackgroundRuntime, server::{server_connection::ServerConnection, server_replica::ServerReplica}, }, - error::ConnectionError, + error::{ConnectionError, InternalError}, Credentials, DriverOptions, Error, Result, }; pub(crate) struct ServerManager { - // TODO: Merge ServerConnection with ServerReplica? Probably should not as they can be different configured_addresses: Addresses, - replicas: RwLock>, - server_connections: RwLock>, + replicas: RwLock>, + replica_connections: RwLock>, connection_scheme: http::uri::Scheme, address_translation: RwLock, @@ -58,7 +57,7 @@ pub(crate) struct ServerManager { } impl ServerManager { - // TODO: Introduce a timer-based connections update + // 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( @@ -97,7 +96,7 @@ impl ServerManager { let server_manager = Self { configured_addresses: addresses, replicas: RwLock::new(replicas), - server_connections: RwLock::new(source_connections), + replica_connections: RwLock::new(source_connections), connection_scheme, address_translation: RwLock::new(address_translation), background_runtime, @@ -106,23 +105,23 @@ impl ServerManager { driver_lang: driver_lang.as_ref().to_string(), driver_version: driver_version.as_ref().to_string(), }; - server_manager.update_server_connections().await?; + 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, |server_connection| { + self.execute(ConsistencyLevel::Strong, |replica_connection| { let address = address.clone(); - async move { server_connection.servers_register(replica_id, address).await } + 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, |server_connection| async move { - server_connection.servers_deregister(replica_id).await + self.execute(ConsistencyLevel::Strong, |replica_connection| async move { + replica_connection.servers_deregister(replica_id).await }) .await } @@ -138,17 +137,17 @@ impl ServerManager { } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - async fn update_server_connections(&self) -> Result { + 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_server_connections = HashMap::new(); - let connection_addresses: HashSet
= self.read_server_connections().keys().cloned().collect(); + 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_server_connection(replica.address().clone()).await { - Ok(server_connection) => { - new_server_connections.insert(private_address, server_connection); + 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); @@ -159,11 +158,11 @@ impl ServerManager { let replica_addresses: HashSet
= replicas.into_iter().map(|replica| replica.private_address().clone()).collect(); - let mut server_connections = self.write_server_connections(); - server_connections.retain(|address, _| replica_addresses.contains(address)); - server_connections.extend(new_server_connections); + let mut replica_connections = self.write_replica_connections(); + replica_connections.retain(|address, _| replica_addresses.contains(address)); + replica_connections.extend(new_replica_connections); - if server_connections.is_empty() { + if replica_connections.is_empty() { Err(self.server_connection_failed_err(None, connection_errors)) } else { Ok(()) @@ -171,7 +170,7 @@ impl ServerManager { } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - async fn new_server_connection(&self, address: Address) -> Result { + async fn new_replica_connection(&self, address: Address) -> Result { ServerConnection::new( self.background_runtime.clone(), address, @@ -181,30 +180,30 @@ impl ServerManager { self.driver_version.as_ref(), ) .await - .map(|(server_connection, _)| server_connection) + .map(|(replica_connection, _)| replica_connection) } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - async fn record_new_server_connection( + async fn record_new_replica_connection( &self, public_address: Address, private_address: Address, ) -> Result { - let server_connection = self.new_server_connection(public_address).await?; - let mut server_connections = self.write_server_connections(); - server_connections.insert(private_address, server_connection.clone()); - Ok(server_connection) + 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_server_connections(&self) -> RwLockReadGuard<'_, HashMap> { - self.server_connections.read().expect("Expected server connections read access") + fn read_replica_connections(&self) -> RwLockReadGuard<'_, HashMap> { + self.replica_connections.read().expect("Expected server connections read access") } - fn write_server_connections(&self) -> RwLockWriteGuard<'_, HashMap> { - self.server_connections.write().expect("Expected server connections write access") + fn write_replica_connections(&self) -> RwLockWriteGuard<'_, HashMap> { + self.replica_connections.write().expect("Expected server connections write access") } - fn read_replicas(&self) -> RwLockReadGuard<'_, Vec> { + fn read_replicas(&self) -> RwLockReadGuard<'_, HashSet> { self.replicas.read().expect("Expected a read replica lock") } @@ -213,10 +212,10 @@ impl ServerManager { } pub(crate) fn force_close(&self) -> Result { - self.read_server_connections().values().map(ServerConnection::force_close).try_collect().map_err(Into::into) + self.read_replica_connections().values().map(ServerConnection::force_close).try_collect().map_err(Into::into) } - pub(crate) fn replicas(&self) -> HashSet { + fn replicas(&self) -> HashSet { self.read_replicas().iter().cloned().collect() } @@ -225,8 +224,8 @@ impl ServerManager { } pub(crate) fn username(&self) -> Result { - match self.read_server_connections().iter().next() { - Some((_, server_connection)) => Ok(server_connection.username().to_string()), + match self.read_replica_connections().iter().next() { + Some((_, replica_connection)) => Ok(replica_connection.username().to_string()), None => Err(ConnectionError::ServerConnectionIsClosed {}.into()), } } @@ -239,7 +238,11 @@ impl ServerManager { { match consistency_level { ConsistencyLevel::Strong => self.execute_strongly_consistent(task).await, - ConsistencyLevel::Eventual => self.execute_eventually_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 { @@ -266,7 +269,7 @@ impl ServerManager { None => self.seek_primary_replica_in(self.replicas()).await?, }; - let retries = self.driver_options.redirect_failover_retries; + 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(); @@ -314,18 +317,20 @@ impl ServerManager { F: Fn(ServerConnection) -> P, P: Future>, { - let limit = self.driver_options.discovery_failover_retries.unwrap_or(usize::MAX); + 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:?}. Attempting next server.", replica.address()); + debug!("Unable to connect to {}: {err:?}. May attempt the next server.", replica.address()); connection_errors.push(err.into()); } res => return res, @@ -346,20 +351,21 @@ impl ServerManager { F: Fn(ServerConnection) -> P, P: Future>, { - let existing_connection = { self.read_server_connections().get(private_address).cloned() }; - let server_connection = match existing_connection { - Some(server_connection) => server_connection, + 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_server_connection(public_address.clone(), private_address.clone()).await?, + 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(server_connection).await + task(replica_connection).await } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] @@ -367,18 +373,21 @@ impl ServerManager { &self, source_replicas: impl IntoIterator, ) -> Result { - self.execute_on_any(source_replicas, |server_connection| async { - self.seek_primary_replica(server_connection).await + 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, server_connection: ServerConnection) -> Result { - let replicas = self.fetch_replicas(&server_connection).await?; + 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_server_connections().await?; + self.update_replica_connections().await?; Ok(replica) } else { Err(self.server_connection_failed_err(None, Vec::default())) @@ -395,11 +404,11 @@ impl ServerManager { driver_lang: impl AsRef, driver_version: impl AsRef, use_replication: bool, - ) -> Result<(HashMap, Vec)> { + ) -> Result<(HashMap, HashSet)> { let address_translation = addresses.address_translation(); let mut errors = Vec::with_capacity(addresses.len()); for address in addresses.addresses() { - let server_connection = ServerConnection::new( + let replica_connection = ServerConnection::new( background_runtime.clone(), address.clone(), credentials.clone(), @@ -408,21 +417,21 @@ impl ServerManager { driver_version.as_ref(), ) .await; - match server_connection { - Ok((server_connection, replicas)) => { + 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(), server_connection); + 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(), server_connection)]); - return Ok((source_connections, vec![target_replica])); + let source_connections = HashMap::from([(address.clone(), replica_connection)]); + return Ok((source_connections, HashSet::from([target_replica]))); } } } @@ -442,19 +451,36 @@ impl ServerManager { } #[cfg_attr(feature = "sync", maybe_async::must_be_sync)] - async fn fetch_replicas(&self, server_connection: &ServerConnection) -> Result> { - let address_translation = self.read_address_translation().clone(); - server_connection + 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, &self.connection_scheme, &address_translation)) + .map(|replicas| Self::translate_replicas(replicas, connection_scheme, address_translation)) } fn translate_replicas( replicas: impl IntoIterator, connection_scheme: &http::uri::Scheme, address_translation: &AddressTranslation, - ) -> Vec { + ) -> HashSet { replicas.into_iter().map(|replica| replica.translated(connection_scheme, address_translation)).collect() } diff --git a/rust/src/driver.rs b/rust/src/driver.rs index 8f3206769f..1147700bc0 100644 --- a/rust/src/driver.rs +++ b/rust/src/driver.rs @@ -192,10 +192,13 @@ impl TypeDBDriver { /// # Examples /// /// ```rust - /// driver.replicas() + #[cfg_attr(feature = "sync", doc = "driver.replicas();")] + #[cfg_attr(not(feature = "sync"), doc = "driver.replicas().await;")] /// ``` - pub fn replicas(&self) -> HashSet { - self.server_manager.replicas() + #[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. @@ -206,16 +209,18 @@ impl TypeDBDriver { /// 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 address(es) of the TypeDB replica as a string + /// * `address` — The clustering address of the TypeDB replica as a string /// /// # Examples /// @@ -351,6 +356,6 @@ impl TypeDBDriver { impl fmt::Debug for TypeDBDriver { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Connection").field("server_manager", &self.server_manager).finish() + f.debug_struct("TypeDBDriver").field("server_manager", &self.server_manager).finish() } } diff --git a/rust/tests/integration/cluster/clustering.rs b/rust/tests/integration/cluster/clustering.rs index 4ed8e6f7cb..2e8f2e0949 100644 --- a/rust/tests/integration/cluster/clustering.rs +++ b/rust/tests/integration/cluster/clustering.rs @@ -17,9 +17,9 @@ * under the License. */ use std::{ - env, fs, io, + env, fs, path::Path, - process::{Child, Command, ExitStatus}, + process::{Child, Command}, str::FromStr, time::Duration, }; @@ -28,38 +28,18 @@ use async_std::task::sleep; use futures::{StreamExt, TryStreamExt}; use serial_test::serial; use typedb_driver::{ - answer::ConceptRow, Address, Addresses, Credentials, DriverOptions, Error, ServerReplica, TransactionOptions, + 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"; -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(()) -} - #[test] #[serial] -fn primary_reelection_read() { +fn primary_reelection_read_query() { async_std::task::block_on(async { kill_servers().await.ok(); @@ -80,32 +60,34 @@ fn primary_reelection_read() { .await .unwrap(); - for (i, address) in ADDRESSES[1..].iter().enumerate() { - driver.register_replica((i + 1) as u64, address.to_string()).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(); + let replicas = driver.replicas().await; + println!("Replicas: {replicas:?}"); + let database_name = "typedb"; println!("Registered replicas. Creating the database"); - driver.databases().create("typedb").await.unwrap(); + driver.databases().create(database_name).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; + verify_created_database(&driver, database_name).await; for iteration in 0..10 { let primary_replica = get_primary_replica(&driver).await; @@ -113,7 +95,7 @@ fn primary_reelection_read() { kill_server_replica(&primary_replica).await.unwrap(); sleep(Duration::from_secs(5)).await; - verify_defined(&driver, database.name()).await; + verify_created_database(&driver, database_name).await; start_server_replica(&primary_replica).await; } @@ -129,6 +111,99 @@ fn primary_reelection_read() { }) } +// #[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"); @@ -158,6 +233,8 @@ fn start_server(index: &str) -> Child { "false", "--diagnostics.monitoring.port", &format!("{index}1731"), + "--server.http.enabled", + "false", "--development-mode.enabled", "true", ]) @@ -176,7 +253,7 @@ async fn get_primary_replica(driver: &TypeDBDriver) -> ServerReplica { panic!("Retry limit exceeded while seeking a primary replica."); } -async fn verify_defined(driver: &TypeDBDriver, database_name: impl AsRef) { +async fn verify_defined_type(driver: &TypeDBDriver, database_name: impl AsRef) { let transaction = driver .transaction_with_options(database_name, TransactionType::Read, TransactionOptions::default()) .await @@ -190,6 +267,16 @@ async fn verify_defined(driver: &TypeDBDriver, database_name: impl AsRef) { 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 diff --git a/tool/test/start-cluster-servers.sh b/tool/test/start-cluster-servers.sh index fa5398fa53..e12d8cdd88 100755 --- a/tool/test/start-cluster-servers.sh +++ b/tool/test/start-cluster-servers.sh @@ -19,6 +19,7 @@ set -e NODE_COUNT=${1:-1} +ENCRYPTION_ENABLED=${2:-true} # TODO: Update configs #peers= @@ -31,10 +32,10 @@ NODE_COUNT=${1:-1} function server_start() { ./${1}/typedb server \ --server.address=127.0.0.1:${1}1729 \ - --server.encryption.enabled true \ - --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` \ + --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 \ From 3228821f83eddcbac347b6932af3e52f8558fb8e Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Wed, 9 Jul 2025 12:30:55 +0100 Subject: [PATCH 33/35] Add bdd steps for new driver read apis --- c/typedb_driver.i | 1 + java/api/server/ServerVersion.java | 2 + java/test/behaviour/connection/BUILD | 1 + .../connection/ConnectionStepsBase.java | 25 ++++++++ .../connection/ConnectionStepsCluster.java | 33 ++++++++-- .../connection/ConnectionStepsCommunity.java | 32 ++++++++-- .../behaviour/connection/connection_steps.py | 30 ++++++++-- .../typedb/api/connection/driver_options.py | 60 ++++++++++++++++++- python/typedb/connection/driver.py | 4 +- rust/tests/behaviour/steps/connection/mod.rs | 33 ++++++++++ 10 files changed, 206 insertions(+), 15 deletions(-) diff --git a/c/typedb_driver.i b/c/typedb_driver.i index d7d2cc7558..74c92405cc 100644 --- a/c/typedb_driver.i +++ b/c/typedb_driver.i @@ -179,6 +179,7 @@ void transaction_on_close_register(const Transaction* transaction, TransactionCa %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; diff --git a/java/api/server/ServerVersion.java b/java/api/server/ServerVersion.java index a402d2cf96..594836c1a4 100644 --- a/java/api/server/ServerVersion.java +++ b/java/api/server/ServerVersion.java @@ -46,6 +46,7 @@ public ServerVersion(com.typedb.driver.jni.ServerVersion nativeObject) { *
*/ public String getDistribution() { + assert(nativeObject.isOwned()); return nativeObject.getDistribution(); } @@ -58,6 +59,7 @@ public String getDistribution() { * */ public String getVersion() { + assert(nativeObject.isOwned()); return nativeObject.getVersion(); } } 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 62097ee1b3..f9c196be8c 100644 --- a/java/test/behaviour/connection/ConnectionStepsBase.java +++ b/java/test/behaviour/connection/ConnectionStepsBase.java @@ -25,6 +25,7 @@ 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; @@ -39,6 +40,8 @@ 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"; @@ -169,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()); } diff --git a/java/test/behaviour/connection/ConnectionStepsCluster.java b/java/test/behaviour/connection/ConnectionStepsCluster.java index aac9fc09ac..db466770d9 100644 --- a/java/test/behaviour/connection/ConnectionStepsCluster.java +++ b/java/test/behaviour/connection/ConnectionStepsCluster.java @@ -25,7 +25,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; import java.util.Optional; @@ -99,6 +99,7 @@ public void connection_opens_with_a_wrong_port(Parameters.MayError mayError) { )); } + @Override @When("connection closes") public void connection_closes() { @@ -106,19 +107,43 @@ 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); } diff --git a/java/test/behaviour/connection/ConnectionStepsCommunity.java b/java/test/behaviour/connection/ConnectionStepsCommunity.java index 0ff8f5ea09..1a199f9e8a 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 { @@ -94,19 +94,43 @@ 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); } diff --git a/python/tests/behaviour/connection/connection_steps.py b/python/tests/behaviour/connection/connection_steps.py index c0ad0d98b5..9ec337e4f1 100644 --- a/python/tests/behaviour/connection/connection_steps.py +++ b/python/tests/behaviour/connection/connection_steps.py @@ -16,7 +16,8 @@ # 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: @@ -90,16 +91,37 @@ 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)) diff --git a/python/typedb/api/connection/driver_options.py b/python/typedb/api/connection/driver_options.py index 4a457248a9..128df710d3 100644 --- a/python/typedb/api/connection/driver_options.py +++ b/python/typedb/api/connection/driver_options.py @@ -21,7 +21,10 @@ from typedb.common.native_wrapper import NativeWrapper 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, DriverOptions as NativeDriverOptions + 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]): @@ -42,12 +45,21 @@ class DriverOptions(NativeWrapper[NativeDriverOptions]): 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: @@ -78,3 +90,49 @@ def tls_root_ca_path(self) -> Optional[str]: @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): + 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): + driver_options_set_replica_discovery_attempts(self.native_object, replica_discovery_attempts) diff --git a/python/typedb/connection/driver.py b/python/typedb/connection/driver.py index 4ba9804a57..8633448bfa 100644 --- a/python/typedb/connection/driver.py +++ b/python/typedb/connection/driver.py @@ -19,9 +19,11 @@ 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.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 @@ -37,13 +39,11 @@ from typedb.user.user_manager import _UserManager if TYPE_CHECKING: - from typedb.api.connection.consistency_level import ConsistencyLevel 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.server.server_replica import ServerReplica - from typedb.api.server.server_version import ServerVersion class _Driver(Driver, NativeWrapper[NativeDriver]): diff --git a/rust/tests/behaviour/steps/connection/mod.rs b/rust/tests/behaviour/steps/connection/mod.rs index 29ceca4b1b..faff30ccbd 100644 --- a/rust/tests/behaviour/steps/connection/mod.rs +++ b/rust/tests/behaviour/steps/connection/mod.rs @@ -20,6 +20,7 @@ use cucumber::{given, then, when}; use itertools::Itertools; use macro_rules_attribute::apply; +use typedb_driver::{ServerVersion, TypeDBDriver}; use crate::{assert_with_timeout, generic_step, params, params::check_boolean, Context}; @@ -27,6 +28,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) {} @@ -109,6 +114,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) { From c8dbbf8fcba9bbc6e83234f0b0719879dce5ecd4 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Wed, 9 Jul 2025 17:06:15 +0100 Subject: [PATCH 34/35] Propagate moreeeee interfaces to all the languages, especially consistency levels --- c/src/database/database_manager.rs | 7 +- c/src/driver_options.rs | 2 +- c/src/query_options.rs | 4 +- c/src/server/consistency_level.rs | 35 +- c/src/transaction_options.rs | 40 +- c/src/user/user_manager.rs | 93 ++- c/swig/typedb_driver_java.swg | 9 +- c/typedb_driver.i | 1 + cpp/include/typedb/user/user_manager.hpp | 2 +- csharp/Api/User/IUserManager.cs | 2 +- .../ROOT/partials/c/session/options.adoc | 20 +- .../connection/ConsistencyLevel.Eventual.adoc | 14 + .../ConsistencyLevel.ReplicaDependant.adoc | 14 + .../connection/ConsistencyLevel.Strong.adoc | 14 + .../java/connection/ConsistencyLevel.adoc | 14 + .../partials/java/connection/Database.adoc | 124 ++- .../java/connection/DatabaseManager.adoc | 8 - .../ROOT/partials/java/connection/Driver.adoc | 67 +- .../java/connection/DriverOptions.adoc | 162 ++++ .../ROOT/partials/java/connection/TypeDB.adoc | 2 +- .../partials/java/connection/UserManager.adoc | 153 +++- .../java/transaction/TransactionOptions.adoc | 53 ++ .../python/connection/ConsistencyLevel.adoc | 14 + .../partials/python/connection/Database.adoc | 34 +- .../partials/python/connection/Driver.adoc | 46 +- .../python/connection/DriverOptions.adoc | 3 + .../python/connection/UserManager.adoc | 42 +- .../transaction/TransactionOptions.adoc | 1 + .../partials/rust/answer/ConceptDocument.adoc | 50 -- .../rust/answer/ConceptDocumentHeader.adoc | 20 - .../ROOT/partials/rust/answer/ConceptRow.adoc | 165 ---- .../rust/answer/ConceptRowHeader.adoc | 21 - .../ROOT/partials/rust/answer/JSON.adoc | 19 - .../ROOT/partials/rust/answer/Leaf.adoc | 17 - .../ROOT/partials/rust/answer/Node.adoc | 16 - .../partials/rust/answer/QueryAnswer.adoc | 163 ---- .../ROOT/partials/rust/answer/QueryType.adoc | 18 - .../partials/rust/answer/Trait_Promise.adoc | 34 - .../ROOT/partials/rust/concept/Concept.adoc | 673 ---------------- .../rust/concept/ConceptCategory.adoc | 21 - .../ROOT/partials/rust/concept/Kind.adoc | 19 - .../partials/rust/connection/Addresses.adoc | 231 ------ .../rust/connection/ConsistencyLevel.adoc | 18 - .../partials/rust/connection/Credentials.adoc | 81 -- .../partials/rust/connection/Database.adoc | 515 ------------ .../rust/connection/DatabaseManager.adoc | 579 -------------- .../rust/connection/DriverOptions.adoc | 172 ---- .../partials/rust/connection/ReplicaType.adoc | 17 - .../rust/connection/ServerReplica.adoc | 102 --- .../rust/connection/ServerVersion.adoc | 47 -- .../rust/connection/TypeDBDriver.adoc | 745 ------------------ .../ROOT/partials/rust/connection/User.adoc | 176 ----- .../partials/rust/connection/UserManager.adoc | 549 ------------- .../ROOT/partials/rust/data/Attribute.adoc | 25 - .../ROOT/partials/rust/data/Relation.adoc | 52 -- .../ROOT/partials/rust/data/Value.adoc | 74 -- .../partials/rust/errors/ConceptError.adoc | 15 - .../partials/rust/errors/ConnectionError.adoc | 49 -- .../rust/errors/DurationParseError.adoc | 7 - .../ROOT/partials/rust/errors/Error.adoc | 21 - .../partials/rust/errors/InternalError.adoc | 17 - .../partials/rust/errors/MigrationError.adoc | 19 - .../partials/rust/errors/ServerError.adoc | 13 - .../partials/rust/schema/AttributeType.adoc | 82 -- .../ROOT/partials/rust/schema/EntityType.adoc | 52 -- .../partials/rust/schema/RelationType.adoc | 56 -- .../ROOT/partials/rust/schema/RoleType.adoc | 54 -- .../ROOT/partials/rust/schema/ValueType.adoc | 25 - .../rust/transaction/QueryOptions.adoc | 61 -- .../rust/transaction/Transaction.adoc | 262 ------ .../rust/transaction/TransactionOptions.adoc | 78 -- .../rust/transaction/TransactionType.adoc | 18 - .../ROOT/partials/rust/value/Decimal.adoc | 34 - .../ROOT/partials/rust/value/Duration.adoc | 31 - .../ROOT/partials/rust/value/Offset.adoc | 17 - .../ROOT/partials/rust/value/Struct.adoc | 11 - .../ROOT/partials/rust/value/TimeZone.adoc | 17 - java/api/ConsistencyLevel.java | 12 + java/api/DriverOptions.java | 3 + java/api/TransactionOptions.java | 36 + java/api/server/ServerVersion.java | 4 +- java/api/user/UserManager.java | 88 ++- .../connection/ConnectionStepsBase.java | 12 + .../connection/ConnectionStepsCluster.java | 18 + .../connection/ConnectionStepsCommunity.java | 18 + java/user/UserManagerImpl.java | 25 +- nodejs/api/connection/user/UserManager.ts | 2 +- .../behaviour/connection/connection_steps.py | 15 + .../api/connection/consistency_level.py | 28 +- python/typedb/api/connection/driver.py | 4 +- .../typedb/api/connection/driver_options.py | 3 + .../api/connection/transaction_options.py | 21 +- python/typedb/api/database/database.py | 102 +++ .../database_manager.py} | 83 +- python/typedb/api/user/user.py | 97 --- python/typedb/api/user/user_manager.py | 114 +++ python/typedb/connection/database.py | 5 +- python/typedb/connection/database_manager.py | 2 +- python/typedb/connection/driver.py | 13 +- python/typedb/driver.py | 6 +- python/typedb/user/user_manager.py | 34 +- rust/src/user/user_manager.rs | 27 +- rust/tests/behaviour/steps/connection/mod.rs | 22 + rust/tests/behaviour/steps/lib.rs | 28 +- 104 files changed, 1465 insertions(+), 5895 deletions(-) delete mode 100644 docs/modules/ROOT/partials/rust/answer/ConceptDocument.adoc delete mode 100644 docs/modules/ROOT/partials/rust/answer/ConceptDocumentHeader.adoc delete mode 100644 docs/modules/ROOT/partials/rust/answer/ConceptRow.adoc delete mode 100644 docs/modules/ROOT/partials/rust/answer/ConceptRowHeader.adoc delete mode 100644 docs/modules/ROOT/partials/rust/answer/JSON.adoc delete mode 100644 docs/modules/ROOT/partials/rust/answer/Leaf.adoc delete mode 100644 docs/modules/ROOT/partials/rust/answer/Node.adoc delete mode 100644 docs/modules/ROOT/partials/rust/answer/QueryAnswer.adoc delete mode 100644 docs/modules/ROOT/partials/rust/answer/QueryType.adoc delete mode 100644 docs/modules/ROOT/partials/rust/answer/Trait_Promise.adoc delete mode 100644 docs/modules/ROOT/partials/rust/concept/Concept.adoc delete mode 100644 docs/modules/ROOT/partials/rust/concept/ConceptCategory.adoc delete mode 100644 docs/modules/ROOT/partials/rust/concept/Kind.adoc delete mode 100644 docs/modules/ROOT/partials/rust/connection/Addresses.adoc delete mode 100644 docs/modules/ROOT/partials/rust/connection/ConsistencyLevel.adoc delete mode 100644 docs/modules/ROOT/partials/rust/connection/Credentials.adoc delete mode 100644 docs/modules/ROOT/partials/rust/connection/Database.adoc delete mode 100644 docs/modules/ROOT/partials/rust/connection/DatabaseManager.adoc delete mode 100644 docs/modules/ROOT/partials/rust/connection/DriverOptions.adoc delete mode 100644 docs/modules/ROOT/partials/rust/connection/ReplicaType.adoc delete mode 100644 docs/modules/ROOT/partials/rust/connection/ServerReplica.adoc delete mode 100644 docs/modules/ROOT/partials/rust/connection/ServerVersion.adoc delete mode 100644 docs/modules/ROOT/partials/rust/connection/TypeDBDriver.adoc delete mode 100644 docs/modules/ROOT/partials/rust/connection/User.adoc delete mode 100644 docs/modules/ROOT/partials/rust/connection/UserManager.adoc delete mode 100644 docs/modules/ROOT/partials/rust/data/Attribute.adoc delete mode 100644 docs/modules/ROOT/partials/rust/data/Relation.adoc delete mode 100644 docs/modules/ROOT/partials/rust/data/Value.adoc delete mode 100644 docs/modules/ROOT/partials/rust/errors/ConceptError.adoc delete mode 100644 docs/modules/ROOT/partials/rust/errors/ConnectionError.adoc delete mode 100644 docs/modules/ROOT/partials/rust/errors/DurationParseError.adoc delete mode 100644 docs/modules/ROOT/partials/rust/errors/Error.adoc delete mode 100644 docs/modules/ROOT/partials/rust/errors/InternalError.adoc delete mode 100644 docs/modules/ROOT/partials/rust/errors/MigrationError.adoc delete mode 100644 docs/modules/ROOT/partials/rust/errors/ServerError.adoc delete mode 100644 docs/modules/ROOT/partials/rust/schema/AttributeType.adoc delete mode 100644 docs/modules/ROOT/partials/rust/schema/EntityType.adoc delete mode 100644 docs/modules/ROOT/partials/rust/schema/RelationType.adoc delete mode 100644 docs/modules/ROOT/partials/rust/schema/RoleType.adoc delete mode 100644 docs/modules/ROOT/partials/rust/schema/ValueType.adoc delete mode 100644 docs/modules/ROOT/partials/rust/transaction/QueryOptions.adoc delete mode 100644 docs/modules/ROOT/partials/rust/transaction/Transaction.adoc delete mode 100644 docs/modules/ROOT/partials/rust/transaction/TransactionOptions.adoc delete mode 100644 docs/modules/ROOT/partials/rust/transaction/TransactionType.adoc delete mode 100644 docs/modules/ROOT/partials/rust/value/Decimal.adoc delete mode 100644 docs/modules/ROOT/partials/rust/value/Duration.adoc delete mode 100644 docs/modules/ROOT/partials/rust/value/Offset.adoc delete mode 100644 docs/modules/ROOT/partials/rust/value/Struct.adoc delete mode 100644 docs/modules/ROOT/partials/rust/value/TimeZone.adoc create mode 100644 python/typedb/api/database/database.py rename python/typedb/api/{connection/database.py => database/database_manager.py} (60%) create mode 100644 python/typedb/api/user/user_manager.py diff --git a/c/src/database/database_manager.rs b/c/src/database/database_manager.rs index 18ebcad206..aeed17a434 100644 --- a/c/src/database/database_manager.rs +++ b/c/src/database/database_manager.rs @@ -66,8 +66,8 @@ pub extern "C" fn databases_all( /// Checks if a database with the given name exists. /// /// @param driver The TypeDBDriver object. -/// @param consistency_level The consistency level to use for the operation. Strongest possible if null. /// @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, @@ -85,8 +85,8 @@ pub extern "C" fn databases_contains( /// Retrieves the database with the given name. /// /// @param driver The TypeDBDriver object. -/// @param consistency_level The consistency level to use for the operation. Strongest possible if null. /// @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_get( driver: *mut TypeDBDriver, @@ -102,6 +102,9 @@ pub extern "C" fn databases_get( } /// 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))); diff --git a/c/src/driver_options.rs b/c/src/driver_options.rs index 301489d967..66ec44b7aa 100644 --- a/c/src/driver_options.rs +++ b/c/src/driver_options.rs @@ -67,7 +67,7 @@ pub extern "C" fn driver_options_get_tls_root_ca_path(options: *const DriverOpti release_string(borrow(options).get_tls_root_ca().unwrap().to_string_lossy().to_string()) } -/// Checks whether TLS root CA was Explicitly setsfor this DriverOptions object. +/// 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() diff --git a/c/src/query_options.rs b/c/src/query_options.rs index 4190a3d6f7..1c4f7f5db1 100644 --- a/c/src/query_options.rs +++ b/c/src/query_options.rs @@ -49,7 +49,7 @@ pub extern "C" fn query_options_get_include_instance_types(options: *const Query borrow(options).include_instance_types.unwrap() } -/// Checks whether the "include instance types" flag was Explicitly setsfor this QueryOptions object. +/// Checks whether the "include instance types" flag was explicitly set for this QueryOptions object. #[no_mangle] pub extern "C" fn query_options_has_include_instance_types(options: *const QueryOptions) -> bool { borrow(options).include_instance_types.is_some() @@ -75,7 +75,7 @@ pub extern "C" fn query_options_get_prefetch_size(options: *const QueryOptions) borrow(options).prefetch_size.unwrap() as i64 } -/// Checks whether the prefetch size was Explicitly setsfor this QueryOptions object. +/// Checks whether the prefetch size was explicitly set for this QueryOptions object. #[no_mangle] pub extern "C" fn query_options_has_prefetch_size(options: *const QueryOptions) -> bool { borrow(options).prefetch_size.is_some() diff --git a/c/src/server/consistency_level.rs b/c/src/server/consistency_level.rs index fdc91416cf..2196739e23 100644 --- a/c/src/server/consistency_level.rs +++ b/c/src/server/consistency_level.rs @@ -47,6 +47,20 @@ pub struct ConsistencyLevel { 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); @@ -56,13 +70,13 @@ impl Drop for ConsistencyLevel { /// Creates a strong ConsistencyLevel object. #[no_mangle] pub extern "C" fn consistency_level_strong() -> *mut ConsistencyLevel { - release(ConsistencyLevel { tag: ConsistencyLevelTag::Strong, address: std::ptr::null_mut() }) + release(ConsistencyLevel::new_strong()) } /// Creates an eventual ConsistencyLevel object. #[no_mangle] pub extern "C" fn consistency_level_eventual() -> *mut ConsistencyLevel { - release(ConsistencyLevel { tag: ConsistencyLevelTag::Eventual, address: std::ptr::null_mut() }) + release(ConsistencyLevel::new_eventual()) } /// Creates a replica dependant ConsistencyLevel object. @@ -70,10 +84,7 @@ pub extern "C" fn consistency_level_eventual() -> *mut ConsistencyLevel { /// @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 { - tag: ConsistencyLevelTag::ReplicaDependant, - address: release_string(string_view(address.clone()).to_string()), - }) + release(ConsistencyLevel::new_replica_dependant(release_string(string_view(address.clone()).to_string()))) } /// Drops the ConsistencyLevel object. @@ -99,3 +110,15 @@ impl Into for ConsistencyLevel { } } } + +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/transaction_options.rs b/c/src/transaction_options.rs index ea0519574c..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 crate::common::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] @@ -52,7 +55,7 @@ pub extern "C" fn transaction_options_get_transaction_timeout_millis(options: *c borrow(options).transaction_timeout.unwrap().as_millis() as i64 } -/// Checks whether the option for transaction timeout was Explicitly setsfor this TransactionOptions object. +/// Checks whether the option for transaction timeout was explicitly set for this TransactionOptions object. #[no_mangle] pub extern "C" fn transaction_options_has_transaction_timeout_millis(options: *const TransactionOptions) -> bool { borrow(options).transaction_timeout.is_some() @@ -77,10 +80,41 @@ pub extern "C" fn transaction_options_get_schema_lock_acquire_timeout_millis( borrow(options).schema_lock_acquire_timeout.unwrap().as_millis() as i64 } -/// Checks whether the option for schema lock acquire timeout was Explicitly setsfor this TransactionOptions object. +/// Checks whether the option for schema lock acquire timeout was explicitly set for this TransactionOptions object. #[no_mangle] pub extern "C" fn transaction_options_has_schema_lock_acquire_timeout_millis( options: *const TransactionOptions, ) -> 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/user_manager.rs b/c/src/user/user_manager.rs index 396d38fb0b..a223fca4de 100644 --- a/c/src/user/user_manager.rs +++ b/c/src/user/user_manager.rs @@ -21,13 +21,16 @@ 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}, +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 +/// Iterator over a set of Users. pub struct UserIterator(CIterator); /// Forwards the UserIterator and returns the next User if it exists, @@ -37,38 +40,90 @@ 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 +/// 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) -> *mut UserIterator { - try_release(borrow(driver).users().all().map(|users| UserIterator(CIterator(box_stream(users.into_iter()))))) +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) -> bool { - unwrap_or_default(borrow(driver).users().contains(string_view(username))) +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), + }) } -/// Creates a user with the given name & password. +/// 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_create(driver: *const TypeDBDriver, username: *const c_char, password: *const c_char) { - unwrap_void(borrow(driver).users().create(string_view(username), string_view(password))); +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 a user with the given name. +/// 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(driver: *const TypeDBDriver, username: *const c_char) -> *mut User { - try_release_optional(borrow(driver).users().get(string_view(username)).transpose()) +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()) } -/// Retrieves the username of the user who opened this connection +/// 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_get_current_user(driver: *const TypeDBDriver) -> *mut User { - try_release_optional(borrow(driver).users().get_current_user().transpose()) +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/swig/typedb_driver_java.swg b/c/swig/typedb_driver_java.swg index 0d00cfa4d4..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; @@ -228,6 +228,9 @@ %nojavaexception Duration::days; %nojavaexception Duration::nanos; +%nojavaexception ConsistencyLevel::tag; +%nojavaexception ConsistencyLevel::address; + /* director constructors do not throw */ %nojavaexception TransactionCallbackDirector; diff --git a/c/typedb_driver.i b/c/typedb_driver.i index 74c92405cc..0962b0b999 100644 --- a/c/typedb_driver.i +++ b/c/typedb_driver.i @@ -185,6 +185,7 @@ void transaction_on_close_register(const Transaction* transaction, TransactionCa %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; 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/docs/modules/ROOT/partials/c/session/options.adoc b/docs/modules/ROOT/partials/c/session/options.adoc index db9a3af7ea..386380f351 100644 --- a/docs/modules/ROOT/partials/c/session/options.adoc +++ b/docs/modules/ROOT/partials/c/session/options.adoc @@ -195,7 +195,7 @@ bool options_has_explain(const struct Options* options) -Checks whether the option for explanation was Explicitly setsfor this ``TypeDBOptions`` object. +Checks whether the option for explanation was explicitly set for this ``TypeDBOptions`` object. [caption=""] .Returns @@ -211,7 +211,7 @@ bool options_has_infer(const struct Options* options) -Checks whether the option for inference was Explicitly setsfor this ``TypeDBOptions`` object. +Checks whether the option for inference was explicitly set for this ``TypeDBOptions`` object. [caption=""] .Returns @@ -227,7 +227,7 @@ bool options_has_parallel(const struct Options* options) -Checks whether the option for parallel execution was Explicitly setsfor this ``TypeDBOptions`` object. +Checks whether the option for parallel execution was explicitly set for this ``TypeDBOptions`` object. [caption=""] .Returns @@ -243,7 +243,7 @@ bool options_has_prefetch(const struct Options* options) -Checks whether the option for prefetching was Explicitly setsfor this ``TypeDBOptions`` object. +Checks whether the option for prefetching was explicitly set for this ``TypeDBOptions`` object. [caption=""] .Returns @@ -259,7 +259,7 @@ bool options_has_prefetch_size(const struct Options* options) -Checks whether the option for prefetch size was Explicitly setsfor this ``TypeDBOptions`` object. +Checks whether the option for prefetch size was explicitly set for this ``TypeDBOptions`` object. [caption=""] .Returns @@ -275,7 +275,7 @@ bool options_has_read_any_replica(const struct Options* options) -Checks whether the option for reading data from any replica was Explicitly setsfor this ``TypeDBOptions`` object. +Checks whether the option for reading data from any replica was explicitly set for this ``TypeDBOptions`` object. [caption=""] .Returns @@ -291,7 +291,7 @@ bool options_has_schema_lock_acquire_timeout_millis(const struct Options* option -Checks whether the option for schema lock acquire timeout was Explicitly setsfor this ``TypeDBOptions`` object. +Checks whether the option for schema lock acquire timeout was explicitly set for this ``TypeDBOptions`` object. [caption=""] .Returns @@ -307,7 +307,7 @@ bool options_has_session_idle_timeout_millis(const struct Options* options) -Checks whether the option for the session idle timeout was Explicitly setsfor this ``TypeDBOptions`` object. +Checks whether the option for the session idle timeout was explicitly set for this ``TypeDBOptions`` object. [caption=""] .Returns @@ -323,7 +323,7 @@ bool options_has_trace_inference(const struct Options* options) -Checks whether the option for reasoning tracing was Explicitly setsfor this ``TypeDBOptions`` object. +Checks whether the option for reasoning tracing was explicitly set for this ``TypeDBOptions`` object. [caption=""] .Returns @@ -339,7 +339,7 @@ bool options_has_transaction_timeout_millis(const struct Options* options) -Checks whether the option for transaction timeout was Explicitly setsfor this ``TypeDBOptions`` object. +Checks whether the option for transaction timeout was explicitly set for this ``TypeDBOptions`` object. [caption=""] .Returns diff --git a/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.Eventual.adoc b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.Eventual.adoc index 8d9466113a..50d0f59dd1 100644 --- a/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.Eventual.adoc +++ b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.Eventual.adoc @@ -34,6 +34,20 @@ public static com.typedb.driver.jni.ConsistencyLevel nativeValue​(ConsistencyL .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 diff --git a/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.ReplicaDependant.adoc b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.ReplicaDependant.adoc index 3cc8d807e6..509673a628 100644 --- a/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.ReplicaDependant.adoc +++ b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.ReplicaDependant.adoc @@ -62,6 +62,20 @@ public static com.typedb.driver.jni.ConsistencyLevel nativeValue​(ConsistencyL .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 diff --git a/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.Strong.adoc b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.Strong.adoc index 7c56e284e4..273e7f314c 100644 --- a/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.Strong.adoc +++ b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.Strong.adoc @@ -34,6 +34,20 @@ public static com.typedb.driver.jni.ConsistencyLevel nativeValue​(ConsistencyL .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 diff --git a/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.adoc b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.adoc index 6b9078d842..85400faaf7 100644 --- a/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.adoc +++ b/docs/modules/ROOT/partials/java/connection/ConsistencyLevel.adoc @@ -48,5 +48,19 @@ public static com.typedb.driver.jni.ConsistencyLevel nativeValue​(ConsistencyL .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 724fc3db60..57071a0131 100644 --- a/docs/modules/ROOT/partials/java/connection/DatabaseManager.adoc +++ b/docs/modules/ROOT/partials/java/connection/DatabaseManager.adoc @@ -19,10 +19,6 @@ default java.util.List all() 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. - -See also: <<#_all_com_typedb_driver_api_ConsistencyLevel,``all(ConsistencyLevel)``>> - - [caption=""] .Returns `java.util.List` @@ -47,10 +43,6 @@ java.util.List all​(ConsistencyLevel consistencyLevel) Retrieves all databases present on the TypeDB server. - -See also: <<#_all_,``all()``>> - - [caption=""] .Input parameters [cols=",,"] diff --git a/docs/modules/ROOT/partials/java/connection/Driver.adoc b/docs/modules/ROOT/partials/java/connection/Driver.adoc index cb84d8c8d5..abb1ebc1e9 100644 --- a/docs/modules/ROOT/partials/java/connection/Driver.adoc +++ b/docs/modules/ROOT/partials/java/connection/Driver.adoc @@ -150,7 +150,7 @@ 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. +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=""] @@ -203,12 +203,44 @@ driver.replicas(); [source,java] ---- @CheckReturnValue -ServerVersion serverVersion() +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` @@ -291,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 diff --git a/docs/modules/ROOT/partials/java/connection/DriverOptions.adoc b/docs/modules/ROOT/partials/java/connection/DriverOptions.adoc index 1cb6981914..c34cbf026c 100644 --- a/docs/modules/ROOT/partials/java/connection/DriverOptions.adoc +++ b/docs/modules/ROOT/partials/java/connection/DriverOptions.adoc @@ -82,6 +82,114 @@ a| `isTlsEnabled` a| Whether the connection to TypeDB must be done over TLS. a| 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 @@ -136,5 +244,59 @@ a| `tlsRootCAPath` a| The path to the TLS root CA. If None, system roots are use 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/TypeDB.adoc b/docs/modules/ROOT/partials/java/connection/TypeDB.adoc index f922711fc0..82a05937c1 100644 --- a/docs/modules/ROOT/partials/java/connection/TypeDB.adoc +++ b/docs/modules/ROOT/partials/java/connection/TypeDB.adoc @@ -134,7 +134,7 @@ a| `driverOptions` a| The driver connection options to connect with a| `DriverOp .Code examples [source,java] ---- -TypeDB.driver(address); +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/TransactionOptions.adoc b/docs/modules/ROOT/partials/java/transaction/TransactionOptions.adoc index 1bcbdfa730..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 diff --git a/docs/modules/ROOT/partials/python/connection/ConsistencyLevel.adoc b/docs/modules/ROOT/partials/python/connection/ConsistencyLevel.adoc index 84707a838b..c022b65bfa 100644 --- a/docs/modules/ROOT/partials/python/connection/ConsistencyLevel.adoc +++ b/docs/modules/ROOT/partials/python/connection/ConsistencyLevel.adoc @@ -74,5 +74,19 @@ static native_value(consistency_level: ConsistencyLevel | None) -> ConsistencyLe .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/Driver.adoc b/docs/modules/ROOT/partials/python/connection/Driver.adoc index 8fba5eb414..c1c6b9d64c 100644 --- a/docs/modules/ROOT/partials/python/connection/Driver.adoc +++ b/docs/modules/ROOT/partials/python/connection/Driver.adoc @@ -115,7 +115,7 @@ driver.primary_replica() 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. +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 @@ -159,16 +159,25 @@ Set of ``Replica`` instances for this driver connection. driver.replicas() ---- -[#_Driver_server_version_] +[#_Driver_server_version_consistency_level_ConsistencyLevel_None] ==== server_version [source,python] ---- -server_version() -> ServerVersion +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` @@ -178,6 +187,7 @@ Retrieves the server’s version. [source,python] ---- driver.server_version() +driver.server_version(ConsistencyLevel.Strong()) ---- [#_Driver_transaction_database_name_str_transaction_type_TransactionType_options_TransactionOptions_None] @@ -212,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 5634a0f7e8..95dae7afb3 100644 --- a/docs/modules/ROOT/partials/python/connection/DriverOptions.adoc +++ b/docs/modules/ROOT/partials/python/connection/DriverOptions.adoc @@ -21,7 +21,10 @@ driver_options.tls_root_ca_path = "path/to/ca-certificate.pem" |=== |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/UserManager.adoc b/docs/modules/ROOT/partials/python/connection/UserManager.adoc index 5e1b364808..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] @@ -86,12 +98,12 @@ 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 ---- Retrieves a user with the given name. @@ -103,6 +115,7 @@ Retrieves 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,18 +127,28 @@ 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 ---- 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 `User | None` @@ -135,6 +158,7 @@ Retrieves 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/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/rust/answer/ConceptDocument.adoc b/docs/modules/ROOT/partials/rust/answer/ConceptDocument.adoc deleted file mode 100644 index e6f38521b4..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 ----- - -Retrieves 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 1931cfffd1..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] ----- - -Retrieves 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 ----- - -Retrieves 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 c5d6f5ac47..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 ----- - -Retrieves 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 ----- - -Checks 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 ----- - -Checks 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 ----- - -Checks 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 c4300be8f8..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 ----- - -Checks 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 ----- - -Checks 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 ----- - -Checks 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 ----- - -Checks 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 ----- - -Checks 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 ----- - -Checks 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 ----- - -Checks 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 ----- - -Checks 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 ----- - -Checks 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 ----- - -Checks 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 ----- - -Checks 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 ----- - -Checks 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 ----- - -Checks 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 ----- - -Checks 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 ----- - -Checks 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 ----- - -Checks 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 ----- - -Checks 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 ----- - -Checks 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 ----- - -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: - -[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 ----- - -Checks 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/Addresses.adoc b/docs/modules/ROOT/partials/rust/connection/Addresses.adoc deleted file mode 100644 index c77f300d74..0000000000 --- a/docs/modules/ROOT/partials/rust/connection/Addresses.adoc +++ /dev/null @@ -1,231 +0,0 @@ -[#_enum_Addresses] -=== Addresses - -A collection of server addresses used for connection. - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `Direct(Vec
)` -a| `Translated(HashMap)` -|=== -// end::enum_constants[] - -// tag::methods[] -[#_enum_Addresses_contains_] -==== contains - -[source,rust] ----- -pub fn contains(&self, address: &Address) -> bool ----- - -Checks if the public address is a part of the addresses. - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[caption=""] -.Code examples -[source,rust] ----- -addresses.contains(&address) ----- - -[#_enum_Addresses_from_address_] -==== from_address - -[source,rust] ----- -pub fn from_address(address: Address) -> Self ----- - -Prepare addresses based on a single TypeDB address. - -[caption=""] -.Returns -[source,rust] ----- -Self ----- - -[caption=""] -.Code examples -[source,rust] ----- -let address = "127.0.0.1:11729".parse().unwrap(); -Addresses::from_address(address) ----- - -[#_enum_Addresses_from_addresses_] -==== from_addresses - -[source,rust] ----- -pub fn from_addresses(addresses: impl IntoIterator) -> Self ----- - -Prepare addresses based on multiple TypeDB addresses. - -[caption=""] -.Returns -[source,rust] ----- -Self ----- - -[caption=""] -.Code examples -[source,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]) ----- - -[#_enum_Addresses_from_translation_] -==== from_translation - -[source,rust] ----- -pub fn from_translation(addresses: HashMap) -> Self ----- - -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). - -[caption=""] -.Returns -[source,rust] ----- -Self ----- - -[caption=""] -.Code examples -[source,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) ----- - -[#_enum_Addresses_len_] -==== len - -[source,rust] ----- -pub fn len(&self) -> usize ----- - -Returns the number of address entries (addresses or address pairs) in the collection. - -[caption=""] -.Returns -[source,rust] ----- -usize ----- - -[caption=""] -.Code examples -[source,rust] ----- -addresses.len() ----- - -[#_enum_Addresses_try_from_address_str_] -==== try_from_address_str - -[source,rust] ----- -pub fn try_from_address_str(address_str: impl AsRef) -> Result ----- - -Prepare addresses based on a single “host:port” string. - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[source,rust] ----- -Addresses::try_from_address_str("127.0.0.1:11729") ----- - -[#_enum_Addresses_try_from_addresses_str_] -==== try_from_addresses_str - -[source,rust] ----- -pub fn try_from_addresses_str( - addresses_str: impl IntoIterator>, -) -> Result ----- - -Prepare addresses based on multiple “host:port” strings. Is used to specify multiple addresses connect to. - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[source,rust] ----- -Addresses::try_from_addresses_str(["127.0.0.1:11729", "127.0.0.1:11730", "127.0.0.1:11731"]) ----- - -[#_enum_Addresses_try_from_translation_str_] -==== try_from_translation_str - -[source,rust] ----- -pub fn try_from_translation_str( - addresses_str: HashMap, impl AsRef>, -) -> Result ----- - -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). - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[source,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() -) ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/connection/ConsistencyLevel.adoc b/docs/modules/ROOT/partials/rust/connection/ConsistencyLevel.adoc deleted file mode 100644 index f6e386ab5b..0000000000 --- a/docs/modules/ROOT/partials/rust/connection/ConsistencyLevel.adoc +++ /dev/null @@ -1,18 +0,0 @@ -[#_enum_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. - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `Eventual` -a| `ReplicaDependant` -a| `Strong` -|=== -// 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 0152f99ea4..0000000000 --- a/docs/modules/ROOT/partials/rust/connection/Database.adoc +++ /dev/null @@ -1,515 +0,0 @@ -[#_struct_Database] -=== Database - -*Implements traits:* - -* `Clone` -* `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. Always uses strong consistency. - -[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_] -==== 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, using default strong consistency. This is a blocking operation and may take a significant amount of time depending on the database size. - -See <<#_struct_Database_method_export_to_file_with_consistency,`Self::export_to_file_with_consistency`>> for more details and options. - -[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_export_to_file_with_consistency_schema_file_path_impl_AsRef_Path_data_file_path_impl_AsRef_Path_consistency_level_ConsistencyLevel] -==== export_to_file_with_consistency - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn export_to_file_with_consistency( - &self, - schema_file_path: impl AsRef, - data_file_path: impl AsRef, - consistency_level: ConsistencyLevel, -) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn export_to_file_with_consistency( - &self, - schema_file_path: impl AsRef, - data_file_path: impl AsRef, - consistency_level: ConsistencyLevel, -) -> 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` -a| `consistency_level` a| — The consistency level to use for the operation a| `ConsistencyLevel` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -database.export_to_file_with_consistency(schema_path, data_path, ConsistencyLevel::Strong).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -database.export_to_file_with_consistency(schema_path, data_path, ConsistencyLevel::Strong); ----- - --- -==== - -[#_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_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, using default strong consistency. - -See <<#_struct_Database_method_schema_with_consistency,`Self::schema_with_consistency`>> for more details and options. - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -database.schema().await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -database.schema(); ----- - --- -==== - -[#_struct_Database_schema_with_consistency_consistency_level_ConsistencyLevel] -==== schema_with_consistency - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn schema_with_consistency( - &self, - consistency_level: ConsistencyLevel, -) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn schema_with_consistency( - &self, - consistency_level: ConsistencyLevel, -) -> Result ----- - --- -==== - -Returns a full schema text as a valid TypeQL define query string. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `consistency_level` a| — The consistency level to use for the operation a| `ConsistencyLevel` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -database.schema_with_consistency(ConsistencyLevel::Strong).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -database.schema_with_consistency(ConsistencyLevel::Strong); ----- - --- -==== - -[#_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, using default strong consistency. - -See <<#_struct_Database_method_type_schema_with_consistency,`Self::type_schema_with_consistency`>> for more details and options. - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -database.type_schema().await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -database.type_schema(); ----- - --- -==== - -[#_struct_Database_type_schema_with_consistency_consistency_level_ConsistencyLevel] -==== type_schema_with_consistency - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn type_schema_with_consistency( - &self, - consistency_level: ConsistencyLevel, -) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn type_schema_with_consistency( - &self, - consistency_level: ConsistencyLevel, -) -> Result ----- - --- -==== - -Returns the types in the schema as a valid TypeQL define query string. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `consistency_level` a| — The consistency level to use for the operation a| `ConsistencyLevel` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -database.type_schema_with_consistency(ConsistencyLevel::Strong).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -database.type_schema_with_consistency(ConsistencyLevel::Strong); ----- - --- -==== - -// 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 82ca07ba2c..0000000000 --- a/docs/modules/ROOT/partials/rust/connection/DatabaseManager.adoc +++ /dev/null @@ -1,579 +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, using default strong consistency. - -See <<#_struct_DatabaseManager_method_all_with_consistency,`Self::all_with_consistency`>> for more details and options. - -[caption=""] -.Returns -[source,rust] ----- -Result>> ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.databases().all().await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.databases().all(); ----- - --- -==== - -[#_struct_DatabaseManager_all_with_consistency_consistency_level_ConsistencyLevel] -==== all_with_consistency - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn all_with_consistency( - &self, - consistency_level: ConsistencyLevel, -) -> Result>> ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn all_with_consistency( - &self, - consistency_level: ConsistencyLevel, -) -> Result>> ----- - --- -==== - -Retrieves all databases present on the TypeDB server. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `consistency_level` a| — The consistency level to use for the operation a| `ConsistencyLevel` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result>> ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.databases().all_with_consistency(ConsistencyLevel::Strong).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.databases().all_with_consistency(ConsistencyLevel::Strong); ----- - --- -==== - -[#_struct_DatabaseManager_contains_] -==== 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, using default strong consistency. - -See <<#_struct_DatabaseManager_method_contains_with_consistency,`Self::contains_with_consistency`>> for more details and options. - -[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_contains_with_consistency_name_impl_Into_String_consistency_level_ConsistencyLevel] -==== contains_with_consistency - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn contains_with_consistency( - &self, - name: impl Into, - consistency_level: ConsistencyLevel, -) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn contains_with_consistency( - &self, - name: impl Into, - consistency_level: ConsistencyLevel, -) -> 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` -a| `consistency_level` a| — The consistency level to use for the operation a| `ConsistencyLevel` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.databases().contains_with_consistency(name, ConsistencyLevel::Strong).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.databases().contains_with_consistency(name, ConsistencyLevel::Strong); ----- - --- -==== - -[#_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 ----- - --- -==== - -Creates a database with the given name. Always uses strong consistency. - -[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_] -==== get - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn get(&self, name: impl Into) -> Result> ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn get(&self, name: impl Into) -> Result> ----- - --- -==== - -Retrieves the database with the given name, using default strong consistency. - -See <<#_struct_DatabaseManager_method_get_with_consistency,`Self::get_with_consistency`>> for more details and options. - -[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_get_with_consistency_name_impl_Into_String_consistency_level_ConsistencyLevel] -==== get_with_consistency - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn get_with_consistency( - &self, - name: impl Into, - consistency_level: ConsistencyLevel, -) -> Result> ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn get_with_consistency( - &self, - name: impl Into, - consistency_level: ConsistencyLevel, -) -> Result> ----- - --- -==== - -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| `impl Into` -a| `consistency_level` a| — The consistency level to use for the operation a| `ConsistencyLevel` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result> ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.databases().get_with_consistency(name, ConsistencyLevel::Strong).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.databases().get_with_consistency(name, ConsistencyLevel::Strong); ----- - --- -==== - -[#_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 ----- - --- -==== - -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. - -[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 1dd38ea588..0000000000 --- a/docs/modules/ROOT/partials/rust/connection/DriverOptions.adoc +++ /dev/null @@ -1,172 +0,0 @@ -[#_struct_DriverOptions] -=== DriverOptions - -*Implements traits:* - -* `Clone` -* `Debug` -* `Default` - -TypeDB driver connection options. ``DriverOptions`` object can be used to override the default driver behavior while connecting to TypeDB. - -[caption=""] -.Fields -// tag::properties[] -[cols=",,"] -[options="header"] -|=== -|Name |Type |Description -a| `discovery_failover_retries` a| `Option` a| 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. - -a| `is_tls_enabled` a| `bool` a| Specifies whether the connection to TypeDB must be done over TLS. Defaults to false. -a| `redirect_failover_retries` a| `usize` a| 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. -a| `use_replication` a| `bool` a| 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. -|=== -// end::properties[] - -// tag::methods[] -[#_struct_DriverOptions_discovery_failover_retries_] -==== discovery_failover_retries - -[source,rust] ----- -pub fn discovery_failover_retries( - self, - discovery_failover_retries: Option, -) -> 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: - -[caption=""] -.Returns -[source,rust] ----- -Self ----- - -[#_struct_DriverOptions_get_tls_config_] -==== get_tls_config - -[source,rust] ----- -pub fn get_tls_config(&self) -> Option<&ClientTlsConfig> ----- - -Retrieves the TLS config of this options object if configured. - -[caption=""] -.Returns -[source,rust] ----- -Option<&ClientTlsConfig> ----- - -[#_struct_DriverOptions_get_tls_root_ca_] -==== get_tls_root_ca - -[source,rust] ----- -pub fn get_tls_root_ca(&self) -> Option<&Path> ----- - -Retrieves the TLS root CA path of this options object if configured. - -[caption=""] -.Returns -[source,rust] ----- -Option<&Path> ----- - -[#_struct_DriverOptions_is_tls_enabled_] -==== is_tls_enabled - -[source,rust] ----- -pub fn is_tls_enabled(self, is_tls_enabled: bool) -> Self ----- - -Specifies whether the connection to TypeDB must be done over TLS. - -[caption=""] -.Returns -[source,rust] ----- -Self ----- - -[#_struct_DriverOptions_redirect_failover_retries_] -==== redirect_failover_retries - -[source,rust] ----- -pub fn redirect_failover_retries(self, redirect_failover_retries: usize) -> 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. - -[caption=""] -.Returns -[source,rust] ----- -Self ----- - -[#_struct_DriverOptions_set_tls_root_ca_] -==== set_tls_root_ca - -[source,rust] ----- -pub fn set_tls_root_ca(&mut self, tls_root_ca: Option<&Path>) -> Result ----- - -Specifies the root CA used in the TLS config for server certificates authentication. Uses system roots if None is set. See <<#_struct_DriverOptions_method_is_tls_enabled,`Self::is_tls_enabled`>> to enable or disable TLS. - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[#_struct_DriverOptions_tls_root_ca_] -==== tls_root_ca - -[source,rust] ----- -pub fn tls_root_ca(self, tls_root_ca: Option<&Path>) -> Result ----- - -Specifies the root CA used in the TLS config for server certificates authentication. Uses system roots if None is set. See <<#_struct_DriverOptions_method_is_tls_enabled,`Self::is_tls_enabled`>> to enable or disable TLS. - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[#_struct_DriverOptions_use_replication_] -==== use_replication - -[source,rust] ----- -pub fn use_replication(self, use_replication: bool) -> Self ----- - -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. - -[caption=""] -.Returns -[source,rust] ----- -Self ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/connection/ReplicaType.adoc b/docs/modules/ROOT/partials/rust/connection/ReplicaType.adoc deleted file mode 100644 index 9653e9fff9..0000000000 --- a/docs/modules/ROOT/partials/rust/connection/ReplicaType.adoc +++ /dev/null @@ -1,17 +0,0 @@ -[#_enum_ReplicaType] -=== ReplicaType - -This enum is used to specify the type of replica. - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `Primary = 0` -a| `Secondary = 1` -|=== -// end::enum_constants[] - diff --git a/docs/modules/ROOT/partials/rust/connection/ServerReplica.adoc b/docs/modules/ROOT/partials/rust/connection/ServerReplica.adoc deleted file mode 100644 index e97a70d1f1..0000000000 --- a/docs/modules/ROOT/partials/rust/connection/ServerReplica.adoc +++ /dev/null @@ -1,102 +0,0 @@ -[#_struct_ServerReplica] -=== ServerReplica - -*Implements traits:* - -* `Clone` -* `Debug` -* `Eq` -* `Hash` -* `PartialEq` -* `StructuralPartialEq` - -The metadata and state of an individual raft replica of a driver connection. - -// tag::methods[] -[#_struct_ServerReplica_address_] -==== address - -[source,rust] ----- -pub fn address(&self) -> &Address ----- - -Returns the address this replica is hosted at. - -[caption=""] -.Returns -[source,rust] ----- -&Address ----- - -[#_struct_ServerReplica_id_] -==== id - -[source,rust] ----- -pub fn id(&self) -> u64 ----- - -Returns the id of this replica. - -[caption=""] -.Returns -[source,rust] ----- -u64 ----- - -[#_struct_ServerReplica_is_primary_] -==== is_primary - -[source,rust] ----- -pub fn is_primary(&self) -> bool ----- - -Checks whether this is the primary replica of the raft cluster. - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[#_struct_ServerReplica_replica_type_] -==== replica_type - -[source,rust] ----- -pub fn replica_type(&self) -> ReplicaType ----- - -Returns whether this is the primary replica of the raft cluster or any of the supporting types. - -[caption=""] -.Returns -[source,rust] ----- -ReplicaType ----- - -[#_struct_ServerReplica_term_] -==== term - -[source,rust] ----- -pub fn term(&self) -> u64 ----- - -Returns the raft protocol ‘term’ of this replica. - -[caption=""] -.Returns -[source,rust] ----- -u64 ----- - -// end::methods[] - diff --git a/docs/modules/ROOT/partials/rust/connection/ServerVersion.adoc b/docs/modules/ROOT/partials/rust/connection/ServerVersion.adoc deleted file mode 100644 index 5f1ac19565..0000000000 --- a/docs/modules/ROOT/partials/rust/connection/ServerVersion.adoc +++ /dev/null @@ -1,47 +0,0 @@ -[#_struct_ServerVersion] -=== ServerVersion - -*Implements traits:* - -* `Clone` -* `Debug` - -A full TypeDB server’s version specification - -// tag::methods[] -[#_struct_ServerVersion_distribution_] -==== distribution - -[source,rust] ----- -pub fn distribution(&self) -> &str ----- - -Retrieves the server’s distribution. - -[caption=""] -.Returns -[source,rust] ----- -&str ----- - -[#_struct_ServerVersion_version_] -==== version - -[source,rust] ----- -pub fn version(&self) -> &str ----- - -Retrieves the server’s version number. - -[caption=""] -.Returns -[source,rust] ----- -&str ----- - -// end::methods[] - 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 880099e383..0000000000 --- a/docs/modules/ROOT/partials/rust/connection/TypeDBDriver.adoc +++ /dev/null @@ -1,745 +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_databases_] -==== databases - -[source,rust] ----- -pub fn databases(&self) -> &DatabaseManager ----- - -The ``DatabaseManager`` for this connection, providing access to database management methods. - -[caption=""] -.Returns -[source,rust] ----- -&DatabaseManager ----- - -[caption=""] -.Code examples -[source,rust] ----- -driver.databases() ----- - -[#_struct_TypeDBDriver_deregister_replica_replica_id_u64] -==== deregister_replica - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn deregister_replica(&self, replica_id: u64) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn deregister_replica(&self, replica_id: u64) -> Result ----- - --- -==== - -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| `replica_id` a| — The numeric identifier of the deregistered replica a| `u64` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.deregister_replica(2).await ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.deregister_replica(2) ----- - --- -==== - -[#_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_addresses_Addresses_credentials_Credentials_driver_options_DriverOptions] -==== new - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn new( - addresses: Addresses, - credentials: Credentials, - driver_options: DriverOptions, -) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn new( - addresses: Addresses, - credentials: Credentials, - driver_options: DriverOptions, -) -> Result ----- - --- -==== - -Creates a new TypeDB Server connection. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `addresses` a| — The address(es) of the TypeDB Server(s), provided in a unified format a| `Addresses` -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(Addresses::try_from_address_str("127.0.0.1:1729").unwrap()).await ----- - --- - -sync:: -+ --- -[source,rust] ----- -TypeDBDriver::new(Addresses::try_from_address_str("127.0.0.1:1729").unwrap()) ----- - --- -==== - -[#_struct_TypeDBDriver_new_with_description_addresses_Addresses_credentials_Credentials_driver_options_DriverOptions_driver_lang_impl_AsRef_str_] -==== new_with_description - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn new_with_description( - addresses: Addresses, - credentials: Credentials, - driver_options: DriverOptions, - driver_lang: impl AsRef, -) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn new_with_description( - addresses: Addresses, - 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| `addresses` a| — The address(es) of the TypeDB Server(s), provided in a unified format a| `Addresses` -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(Addresses::try_from_address_str("127.0.0.1:1729").unwrap(), "rust").await ----- - --- - -sync:: -+ --- -[source,rust] ----- -TypeDBDriver::new_with_description(Addresses::try_from_address_str("127.0.0.1:1729").unwrap(), "rust") ----- - --- -==== - -[#_struct_TypeDBDriver_primary_replica_] -==== primary_replica - -[source,rust] ----- -pub fn primary_replica(&self) -> Option ----- - -Retrieves the server’s primary replica, if exists. - -[caption=""] -.Returns -[source,rust] ----- -Option ----- - -[caption=""] -.Code examples -[source,rust] ----- -driver.primary_replica() ----- - -[#_struct_TypeDBDriver_register_replica_replica_id_u64_address_String] -==== register_replica - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn register_replica(&self, replica_id: u64, address: String) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn register_replica(&self, replica_id: u64, address: String) -> Result ----- - --- -==== - -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. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `replica_id` a| — The numeric identifier of the new replica a| `u64` -a| `address` a| — The address(es) of the TypeDB replica as a string a| `String` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.register_replica(2, "127.0.0.1:2729").await ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.register_replica(2, "127.0.0.1:2729") ----- - --- -==== - -[#_struct_TypeDBDriver_replicas_] -==== replicas - -[source,rust] ----- -pub fn replicas(&self) -> HashSet ----- - -Retrieves the server’s replicas. - -[caption=""] -.Returns -[source,rust] ----- -HashSet ----- - -[caption=""] -.Code examples -[source,rust] ----- -driver.replicas() ----- - -[#_struct_TypeDBDriver_server_version_] -==== server_version - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn server_version(&self) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn server_version(&self) -> Result ----- - --- -==== - -Retrieves the server’s version, using default strong consistency. - -See <<#_struct_TypeDBDriver_method_server_version_with_consistency,`Self::server_version_with_consistency`>> for more details and options. - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.server_version().await ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.server_version() ----- - --- -==== - -[#_struct_TypeDBDriver_server_version_with_consistency_consistency_level_ConsistencyLevel] -==== server_version_with_consistency - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn server_version_with_consistency( - &self, - consistency_level: ConsistencyLevel, -) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn server_version_with_consistency( - &self, - consistency_level: ConsistencyLevel, -) -> Result ----- - --- -==== - -Retrieves the server’s version, using default strong consistency. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `consistency_level` a| — The consistency level to use for the operation a| `ConsistencyLevel` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.server_version_with_consistency(ConsistencyLevel::Strong).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.server_version_with_consistency(ConsistencyLevel::Strong); ----- - --- -==== - -[#_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`>> for more details. - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.users().all_with_consistency(ConsistencyLevel::Strong).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.users().all_with_consistency(ConsistencyLevel::Strong); ----- - --- -==== - -[#_struct_TypeDBDriver_transaction_with_options_options_TransactionOptions_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 ----- - --- -==== - -Opens a new transaction with the following consistency level: - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `options` a| read transaction - strong consistency, can be overridden through ; a| `TransactionOptions` -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 -[tabs] -==== -async:: -+ --- -[source,rust] ----- -transaction.transaction_with_options(database_name, transaction_type, options).await ----- - --- - -sync:: -+ --- -[source,rust] ----- -transaction.transaction_with_options(database_name, transaction_type, options) ----- - --- -==== - -[#_struct_TypeDBDriver_users_] -==== users - -[source,rust] ----- -pub fn users(&self) -> &UserManager ----- - -The ``UserManager`` for this connection, providing access to user management methods. - -[caption=""] -.Returns -[source,rust] ----- -&UserManager ----- - -[caption=""] -.Code examples -[source,rust] ----- -driver.databases() ----- - -// 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 43769abf4f..0000000000 --- a/docs/modules/ROOT/partials/rust/connection/User.adoc +++ /dev/null @@ -1,176 +0,0 @@ -[#_struct_User] -=== User - -*Implements traits:* - -* `Clone` -* `Debug` - -// tag::methods[] -[#_struct_User_delete_] -==== delete - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn delete(self) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn delete(self) -> Result ----- - --- -==== - -Deletes this user. Always uses strong consistency. - -[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_name_] -==== name - -[source,rust] ----- -pub fn name(&self) -> &str ----- - -Retrieves the username as a string. - -[caption=""] -.Returns -[source,rust] ----- -&str ----- - -[#_struct_User_password_] -==== password - -[source,rust] ----- -pub fn password(&self) -> Option<&str> ----- - -Retrieves the password as a string, if accessible. - -[caption=""] -.Returns -[source,rust] ----- -Option<&str> ----- - -[#_struct_User_update_password_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<()> ----- - --- -==== - -Updates the user’s password. Always uses strong consistency. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -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(password).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -user.update_password(password); ----- - --- -==== - -// 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 3ebd2e8133..0000000000 --- a/docs/modules/ROOT/partials/rust/connection/UserManager.adoc +++ /dev/null @@ -1,549 +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, using default strong consistency. - -See <<#_struct_UserManager_method_all_with_consistency,`Self::all_with_consistency`>> for more details and options. - -[caption=""] -.Returns -[source,rust] ----- -Result> ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.users().all().await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.users().all(); ----- - --- -==== - -[#_struct_UserManager_all_with_consistency_consistency_level_ConsistencyLevel] -==== all_with_consistency - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn all_with_consistency( - &self, - consistency_level: ConsistencyLevel, -) -> Result> ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn all_with_consistency( - &self, - consistency_level: ConsistencyLevel, -) -> Result> ----- - --- -==== - -Retrieves all users which exist on the TypeDB server. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `consistency_level` a| — The consistency level to use for the operation a| `ConsistencyLevel` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result> ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.users().all_with_consistency(ConsistencyLevel::Strong).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.users().all_with_consistency(ConsistencyLevel::Strong); ----- - --- -==== - -[#_struct_UserManager_contains_] -==== 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, using default strong consistency. - -See <<#_struct_UserManager_method_contains_with_consistency,`Self::contains_with_consistency`>> for more details and options. - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.users().contains(username).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.users().contains(username); ----- - --- -==== - -[#_struct_UserManager_contains_with_consistency_username_impl_Into_String_consistency_level_ConsistencyLevel] -==== contains_with_consistency - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn contains_with_consistency( - &self, - username: impl Into, - consistency_level: ConsistencyLevel, -) -> Result ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn contains_with_consistency( - &self, - username: impl Into, - consistency_level: ConsistencyLevel, -) -> Result ----- - --- -==== - -Checks if a user with the given name exists. - -[caption=""] -.Input parameters -[cols=",,"] -[options="header"] -|=== -|Name |Description |Type -a| `username` a| — The username to be checked a| `impl Into` -a| `consistency_level` a| — The consistency level to use for the operation a| `ConsistencyLevel` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.users().contains_with_consistency(username, ConsistencyLevel::Strong).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.users().contains_with_consistency(username, ConsistencyLevel::Strong); ----- - --- -==== - -[#_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 ----- - --- -==== - -Creates a user with the given name & password. Always uses strong consistency. - -[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 -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.users().create(username, password).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.users().create(username, password); ----- - --- -==== - -[#_struct_UserManager_get_] -==== 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> ----- - --- -==== - -Retrieves a user with the given name, using default strong consistency. - -See <<#_struct_UserManager_method_get_with_consistency,`Self::get_with_consistency`>> for more details and options. - -[caption=""] -.Returns -[source,rust] ----- -Result> ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.users().get(username).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.users().get(username); ----- - --- -==== - -[#_struct_UserManager_get_current_user_] -==== get_current_user - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn get_current_user(&self) -> Result> ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn get_current_user(&self) -> Result> ----- - --- -==== - -Returns the user of the current connection. - -[caption=""] -.Returns -[source,rust] ----- -Result> ----- - -[caption=""] -.Code examples -[source,rust] ----- -driver.users().get_current_user().await; ----- - -[#_struct_UserManager_get_with_consistency_username_impl_Into_String_consistency_level_ConsistencyLevel] -==== get_with_consistency - -[tabs] -==== -async:: -+ --- -[source,rust] ----- -pub async fn get_with_consistency( - &self, - username: impl Into, - consistency_level: ConsistencyLevel, -) -> Result> ----- - --- - -sync:: -+ --- -[source,rust] ----- -pub fn get_with_consistency( - &self, - username: impl Into, - consistency_level: ConsistencyLevel, -) -> Result> ----- - --- -==== - -Retrieves 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` -a| `consistency_level` a| — The consistency level to use for the operation a| `ConsistencyLevel` -|=== - -[caption=""] -.Returns -[source,rust] ----- -Result> ----- - -[caption=""] -.Code examples -[tabs] -==== -async:: -+ --- -[source,rust] ----- -driver.users().get_with_consistency(username, ConsistencyLevel::Strong).await; ----- - --- - -sync:: -+ --- -[source,rust] ----- -driver.users().get_with_consistency(username, ConsistencyLevel::Strong); ----- - --- -==== - -// 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 fefa7cfea8..0000000000 --- a/docs/modules/ROOT/partials/rust/errors/ConnectionError.adoc +++ /dev/null @@ -1,49 +0,0 @@ -[#_enum_ConnectionError] -=== ConnectionError - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `BrokenPipe` -a| `ClusterReplicaNotPrimary` -a| `ConnectionRefusedNetworking` -a| `DatabaseExportChannelIsClosed` -a| `DatabaseExportStreamNoResponse` -a| `DatabaseImportChannelIsClosed` -a| `DatabaseImportStreamUnexpectedResponse` -a| `EncryptionSettingsMismatch` -a| `HttpHttpsMismatch` -a| `ListsNotImplemented` -a| `MissingPort` -a| `MissingResponseField` -a| `MissingTlsConfigForTls` -a| `MultipleAddressesForNoReplicationMode` -a| `NoAvailableReplicas` -a| `NotPrimaryOnReadOnly` -a| `QueryStreamNoResponse` -a| `RPCMethodUnavailable` -a| `ServerConnectionFailed` -a| `ServerConnectionFailedNetworking` -a| `ServerConnectionFailedWithError` -a| `ServerConnectionIsClosed` -a| `SslCertificateNotValidated` -a| `TokenCredentialInvalid` -a| `TransactionIsClosed` -a| `TransactionIsClosedWithErrors` -a| `UnexpectedConnectionClose` -a| `UnexpectedKind` -a| `UnexpectedQueryType` -a| `UnexpectedReplicaType` -a| `UnexpectedResponse` -a| `UnknownDirectReplica` -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 ea308aed7c..0000000000 --- a/docs/modules/ROOT/partials/rust/errors/InternalError.adoc +++ /dev/null @@ -1,17 +0,0 @@ -[#_enum_InternalError] -=== InternalError - -[caption=""] -.Enum variants -// tag::enum_constants[] -[cols=""] -[options="header"] -|=== -|Variant -a| `RecvError` -a| `SendError` -a| `UnexpectedRequestType` -a| `UnexpectedResponseType` -|=== -// 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 acd58274e0..0000000000 --- a/docs/modules/ROOT/partials/rust/transaction/Transaction.adoc +++ /dev/null @@ -1,262 +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 ----- - -Checks if the transaction is open. - -[caption=""] -.Returns -[source,rust] ----- -bool ----- - -[caption=""] -.Code examples -[source,rust] ----- -transaction.is_open() ----- - -[#_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> ----- - -[caption=""] -.Code examples -[source,rust] ----- -transaction.query(query) ----- - -[#_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 1c75dccc68..0000000000 --- a/docs/modules/ROOT/partials/rust/transaction/TransactionOptions.adoc +++ /dev/null @@ -1,78 +0,0 @@ -[#_struct_TransactionOptions] -=== TransactionOptions - -*Implements traits:* - -* `Clone` -* `Debug` -* `Default` - -TypeDB transaction options. ``TransactionOptions`` object can be used to override the default behaviour for opened transactions. - -[caption=""] -.Fields -// tag::properties[] -[cols=",,"] -[options="header"] -|=== -|Name |Type |Description -a| `read_consistency_level` a| `Option` 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` 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_consistency_level_] -==== consistency_level - -[source,rust] ----- -pub fn consistency_level(self, consistency_level: ConsistencyLevel) -> 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. - -[caption=""] -.Returns -[source,rust] ----- -Self ----- - -[#_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 c7b123bb64..0000000000 --- a/docs/modules/ROOT/partials/rust/value/Decimal.adoc +++ /dev/null @@ -1,34 +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. - -[caption=""] -.Fields -// tag::properties[] -[cols=",,"] -[options="header"] -|=== -|Name |Type |Description -a| `fractional` a| `u64` a| 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. -a| `integer` a| `i64` a| The integer part of the decimal as normal signed 64 bit number -|=== -// end::properties[] - 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/api/ConsistencyLevel.java b/java/api/ConsistencyLevel.java index 5a3a760d45..371f98dbbc 100644 --- a/java/api/ConsistencyLevel.java +++ b/java/api/ConsistencyLevel.java @@ -19,6 +19,9 @@ 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; @@ -32,6 +35,15 @@ 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; diff --git a/java/api/DriverOptions.java b/java/api/DriverOptions.java index 64383179d3..6091950b37 100644 --- a/java/api/DriverOptions.java +++ b/java/api/DriverOptions.java @@ -20,6 +20,7 @@ package com.typedb.driver.api; import com.typedb.driver.common.NativeObject; +import com.typedb.driver.common.Validator; import javax.annotation.CheckReturnValue; import java.util.Optional; @@ -177,6 +178,7 @@ public Integer primaryFailoverRetries() { * @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; } @@ -222,6 +224,7 @@ public Optional replicaDiscoveryAttempts() { * @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/TransactionOptions.java b/java/api/TransactionOptions.java index 96b2980a15..4b6d3d587d 100644 --- a/java/api/TransactionOptions.java +++ b/java/api/TransactionOptions.java @@ -25,11 +25,14 @@ 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; @@ -115,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/server/ServerVersion.java b/java/api/server/ServerVersion.java index 594836c1a4..f216a8ed79 100644 --- a/java/api/server/ServerVersion.java +++ b/java/api/server/ServerVersion.java @@ -46,7 +46,7 @@ public ServerVersion(com.typedb.driver.jni.ServerVersion nativeObject) { * */ public String getDistribution() { - assert(nativeObject.isOwned()); + assert (nativeObject.isOwned()); return nativeObject.getDistribution(); } @@ -59,7 +59,7 @@ public String getDistribution() { * */ public String getVersion() { - assert(nativeObject.isOwned()); + 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/test/behaviour/connection/ConnectionStepsBase.java b/java/test/behaviour/connection/ConnectionStepsBase.java index f9c196be8c..9b63458b23 100644 --- a/java/test/behaviour/connection/ConnectionStepsBase.java +++ b/java/test/behaviour/connection/ConnectionStepsBase.java @@ -201,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 db466770d9..77b5485e9f 100644 --- a/java/test/behaviour/connection/ConnectionStepsCluster.java +++ b/java/test/behaviour/connection/ConnectionStepsCluster.java @@ -147,4 +147,22 @@ public void connection_has_count_databases(int count) { 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 1a199f9e8a..ec66a968e7 100644 --- a/java/test/behaviour/connection/ConnectionStepsCommunity.java +++ b/java/test/behaviour/connection/ConnectionStepsCommunity.java @@ -134,4 +134,22 @@ public void connection_has_count_databases(int count) { 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/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/tests/behaviour/connection/connection_steps.py b/python/tests/behaviour/connection/connection_steps.py index 9ec337e4f1..12aa3261bb 100644 --- a/python/tests/behaviour/connection/connection_steps.py +++ b/python/tests/behaviour/connection/connection_steps.py @@ -125,3 +125,18 @@ def step_impl(context: Context, count: int): @step("connection has {count:Int} users") def step_impl(context: Context, count: int): 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 index c722fba2cb..ff34af2308 100644 --- a/python/typedb/api/connection/consistency_level.py +++ b/python/typedb/api/connection/consistency_level.py @@ -16,10 +16,13 @@ # under the License. from abc import ABC, abstractmethod -from typing import Optional +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 + consistency_level_replica_dependant, ConsistencyLevel as NativeConsistencyLevel, \ + Strong as NativeStrong, Eventual as NativeEventual, ReplicaDependant as NativeReplicaDependant + class ConsistencyLevel(ABC): """ @@ -33,6 +36,20 @@ class ConsistencyLevel(ABC): 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() @@ -46,7 +63,7 @@ def is_eventual(self) -> bool: def is_replica_dependant(self) -> bool: return isinstance(self, ConsistencyLevel.ReplicaDependant) - class Strong(ABC): + 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. @@ -58,7 +75,7 @@ def native_object(self): def __str__(self): return "Strong" - class Eventual(ABC): + 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. @@ -70,7 +87,7 @@ def native_object(self): def __str__(self): return "Eventual" - class ReplicaDependant(ABC): + class ReplicaDependant: """ Replica dependant consistency level. The operation is executed against the provided replica address only. Its guarantees depend on the replica selected. @@ -85,6 +102,7 @@ def native_object(self): """ Retrieves the address of the replica this consistency level depends on. """ + @property def address(self) -> str: return self._address diff --git a/python/typedb/api/connection/driver.py b/python/typedb/api/connection/driver.py index cd10408832..af35796909 100644 --- a/python/typedb/api/connection/driver.py +++ b/python/typedb/api/connection/driver.py @@ -22,10 +22,10 @@ if TYPE_CHECKING: from typedb.api.connection.consistency_level import ConsistencyLevel - from typedb.api.connection.database import DatabaseManager + 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 diff --git a/python/typedb/api/connection/driver_options.py b/python/typedb/api/connection/driver_options.py index 128df710d3..5a55a308ff 100644 --- a/python/typedb/api/connection/driver_options.py +++ b/python/typedb/api/connection/driver_options.py @@ -19,6 +19,7 @@ from typedb.common.exception import TypeDBDriverException, ILLEGAL_STATE from typedb.common.native_wrapper import NativeWrapper +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, \ @@ -115,6 +116,7 @@ def primary_failover_retries(self) -> int: @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 @@ -135,4 +137,5 @@ def replica_discovery_attempts(self) -> Optional[int]: @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/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/connection/database.py b/python/typedb/api/database/database_manager.py similarity index 60% rename from python/typedb/api/connection/database.py rename to python/typedb/api/database/database_manager.py index 4809f781f8..ee42a3325f 100644 --- a/python/typedb/api/connection/database.py +++ b/python/typedb/api/database/database_manager.py @@ -18,87 +18,10 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typedb.api.connection.consistency_level import ConsistencyLevel -from typing import List, Optional +from typing import TYPE_CHECKING, List, 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 +if TYPE_CHECKING: + from typedb.api.connection.consistency_level import ConsistencyLevel class DatabaseManager(ABC): diff --git a/python/typedb/api/user/user.py b/python/typedb/api/user/user.py index a62f360b1f..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: - """ - 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 - - @abstractmethod - def get(self, username: str) -> Optional[User]: - """ - Retrieves 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]: - """ - Retrieves 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/connection/database.py b/python/typedb/connection/database.py index 3e05c79ae2..5887f891ed 100644 --- a/python/typedb/connection/database.py +++ b/python/typedb/connection/database.py @@ -20,7 +20,7 @@ from typing import Optional from typedb.api.connection.consistency_level import ConsistencyLevel -from typedb.api.connection.database import Database +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 @@ -59,7 +59,8 @@ def type_schema(self, consistency_level: Optional[ConsistencyLevel] = None) -> s except TypeDBDriverExceptionNative as e: raise TypeDBDriverException.of(e) from None - def export_to_file(self, schema_file_path: str, data_file_path: str, consistency_level: Optional[ConsistencyLevel] = None) -> 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: diff --git a/python/typedb/connection/database_manager.py b/python/typedb/connection/database_manager.py index eb288c6ce9..ce6c798ed8 100644 --- a/python/typedb/connection/database_manager.py +++ b/python/typedb/connection/database_manager.py @@ -20,7 +20,7 @@ from typing import TYPE_CHECKING, Optional from typedb.api.connection.consistency_level import ConsistencyLevel -from typedb.api.connection.database import DatabaseManager +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 diff --git a/python/typedb/connection/driver.py b/python/typedb/connection/driver.py index 8633448bfa..d6769eb8e1 100644 --- a/python/typedb/connection/driver.py +++ b/python/typedb/connection/driver.py @@ -42,7 +42,7 @@ 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 @@ -56,14 +56,19 @@ def __init__(self, addresses: str | list[str] | dict[str, str], credentials: Cre try: if isinstance(addresses, str): - native_driver = driver_new_with_description(addresses, credentials.native_object, driver_options.native_object, + 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, + 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, + 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) diff --git a/python/typedb/driver.py b/python/typedb/driver.py index 241129b83f..e6fd78be8a 100644 --- a/python/typedb/driver.py +++ b/python/typedb/driver.py @@ -16,7 +16,7 @@ # under the License. from collections.abc import Mapping as ABCMapping -from typing import Iterable, Mapping, Union +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 @@ -35,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 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/src/user/user_manager.rs b/rust/src/user/user_manager.rs index 756aa85726..e44fcb61f5 100644 --- a/rust/src/user/user_manager.rs +++ b/rust/src/user/user_manager.rs @@ -35,18 +35,41 @@ impl UserManager { Self { server_manager } } - /// Returns the user of the current connection. + /// 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 - /// driver.users().get_current_user().await; + #[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> { self.get(self.server_manager.username()?).await } + /// Returns the user of the current connection. + /// + /// # Arguments + /// + /// * `consistency_level` — The consistency level to use for the operation + /// + /// # Examples + /// + /// ```rust + #[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. diff --git a/rust/tests/behaviour/steps/connection/mod.rs b/rust/tests/behaviour/steps/connection/mod.rs index faff30ccbd..a5adf3dd76 100644 --- a/rust/tests/behaviour/steps/connection/mod.rs +++ b/rust/tests/behaviour/steps/connection/mod.rs @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +use std::time::Duration; use cucumber::{given, then, when}; use itertools::Itertools; @@ -160,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/lib.rs b/rust/tests/behaviour/steps/lib.rs index f94aab55e8..a35ebe11a2 100644 --- a/rust/tests/behaviour/steps/lib.rs +++ b/rust/tests/behaviour/steps/lib.rs @@ -98,6 +98,7 @@ impl> cucumber::Parser for SingletonParser { pub struct Context { pub is_cluster: bool, 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) @@ -281,6 +283,14 @@ 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() } @@ -324,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()); @@ -448,11 +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 driver_options = DriverOptions::new(); - TypeDBDriver::new(addresses, credentials, driver_options).await + TypeDBDriver::new(addresses, credentials, self.driver_options().unwrap_or_default()).await } async fn create_driver_cluster( @@ -461,13 +476,15 @@ impl Context { username: &str, password: &str, ) -> TypeDBResult { - assert!(self.is_cluster); + 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"); let credentials = Credentials::new(username, password); assert!(self.tls_root_ca.is_some(), "Root CA is expected for cluster tests!"); - let driver_options = DriverOptions::new() + 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 @@ -490,6 +507,7 @@ impl Default for Context { Self { is_cluster: false, tls_root_ca, + driver_options: None, transaction_options: None, query_options: None, driver: None, From db596d823561dfb6f07db6fc73bcc6d4611bb15e Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Fri, 11 Jul 2025 19:39:09 +0100 Subject: [PATCH 35/35] Add temporary testing things --- .factory/automation.yml | 2 +- dependencies/typedb/artifacts.bzl | 2 +- tool/test/temp-cluster-server/BUILD | 42 ++--------------- tool/test/temp-cluster-server/config.yml | 10 ++-- .../start-cluster-servers.sh | 46 +++++-------------- .../stop-cluster-servers.sh | 2 +- 6 files changed, 22 insertions(+), 82 deletions(-) diff --git a/.factory/automation.yml b/.factory/automation.yml index 1990867c32..497d93926e 100644 --- a/.factory/automation.yml +++ b/.factory/automation.yml @@ -87,7 +87,7 @@ build: tool/docs/update_readme.sh git add . - git diff --exit-code || { + git diff --exit-code || { echo "Failed to verify README files: please update it manually and verify the changes" exit 1 } diff --git a/dependencies/typedb/artifacts.bzl b/dependencies/typedb/artifacts.bzl index 6d113aceef..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 = "0a16c4ae00be4275854d17c2aeeca8fca08276cf" + commit = "1fb9cafe5345c91b7bfae2207f505b6dcdc5f016" ) #def typedb_cloud_artifact(): diff --git a/tool/test/temp-cluster-server/BUILD b/tool/test/temp-cluster-server/BUILD index 6884fdaa9c..be589aafc4 100644 --- a/tool/test/temp-cluster-server/BUILD +++ b/tool/test/temp-cluster-server/BUILD @@ -17,12 +17,12 @@ # under the License. load("@typedb_dependencies//tool/checkstyle:rules.bzl", "checkstyle_test") -load("@typedb_bazel_distribution//artifact:rules.bzl", "artifact_extractor") -load("@typedb_dependencies//builder/java:rules.bzl", "native_typedb_artifact") +# TODO: The whole directory is temporary. REMOVE it after a normal cluster distribution is ready! exports_files([ - "temp-cluster-server/typedb_cluster_server_bin", + "typedb", + "config.yml", ]) checkstyle_test( @@ -30,39 +30,3 @@ checkstyle_test( include = glob(["*"]), license_type = "apache-header", ) - -native_typedb_artifact( - name = "native-typedb-artifact", - native_artifacts = { - "@typedb_bazel_distribution//platform:is_linux_arm64": ["@typedb_artifact_linux-arm64//file"], - "@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"], - }, - output = "typedb-artifact.tar.gz", - visibility = ["//visibility:public"], -) - -#native_typedb_artifact( -# name = "native-typedb-cluster-artifact", -# native_artifacts = { -# "@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-cluster-artifact.tar.gz", -# visibility = ["//visibility:public"], -#) - -artifact_extractor( - name = "typedb-extractor", - artifact = ":native-typedb-artifact", -) - -artifact_extractor( - name = "typedb-cluster-extractor", - artifact = ":native-typedb-cluster-artifact", -) diff --git a/tool/test/temp-cluster-server/config.yml b/tool/test/temp-cluster-server/config.yml index 78b3301e54..92eb522f7a 100644 --- a/tool/test/temp-cluster-server/config.yml +++ b/tool/test/temp-cluster-server/config.yml @@ -4,7 +4,7 @@ server: address: 127.0.0.1:11729 - http-enabled: true + http-enabled: false http-address: 127.0.0.1:8000 authentication: @@ -24,11 +24,11 @@ logging: diagnostics: monitoring: - enabled: true + enabled: false port: 4104 reporting: - metrics: true - errors: true + metrics: false + errors: false development-mode: - enabled: false + enabled: true diff --git a/tool/test/temp-cluster-server/start-cluster-servers.sh b/tool/test/temp-cluster-server/start-cluster-servers.sh index e12d8cdd88..07f5ec911a 100755 --- a/tool/test/temp-cluster-server/start-cluster-servers.sh +++ b/tool/test/temp-cluster-server/start-cluster-servers.sh @@ -19,49 +19,25 @@ set -e NODE_COUNT=${1:-1} -ENCRYPTION_ENABLED=${2:-true} - -# 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() { - ./${1}/typedb server \ + /Users/georgii/work/typedb-driver/tool/test/temp-cluster-server/typedb \ --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` \ + --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 -# --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-cluster-all - -#bazel run //tool/test:typedb-cluster-extractor -- typedb-cluster-all -bazel run //tool/test:typedb-extractor -- typedb-cluster-all - -echo Successfully unarchived a TypeDB distribution. Creating $NODE_COUNT copies ${1}. for i in $(seq 1 $NODE_COUNT); do - cp -r typedb-cluster-all $i || exit 1 + 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 diff --git a/tool/test/temp-cluster-server/stop-cluster-servers.sh b/tool/test/temp-cluster-server/stop-cluster-servers.sh index 63b3992753..aed5d555f8 100755 --- a/tool/test/temp-cluster-server/stop-cluster-servers.sh +++ b/tool/test/temp-cluster-server/stop-cluster-servers.sh @@ -18,7 +18,7 @@ set -e -kill $(ps aux | awk '/typedb[_server_bin]/ {print $2}') +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