Programming in Rust

Getting Started

Programming YottaDB in the Rust language is accomplished through the yottadb crate, which is a Rust wrapper around the YottaDB C API.

The YottaDB Rust wrapper requires a minimum YottaDB version of r1.30 and is tested with a minimum Rust version of 1.40.

Install YottaDB

Follow the instructions in the Quick Start section to install YottaDB.

Install Rust

The next step is to install Rust. Follow the instructions for rustup to install Rust on your system, or use the OS package manager if that provides the minimum Rust version.

Install YDBRust

Clone the YDBRust repository and run an example.

git clone https://gitlab.com/YottaDB/Lang/YDBRust/
cd YDBRust
echo 'yottadb = "2.0.0"' >> Cargo.toml
cargo run --example say_hello_rust

Rust API

There are two major APIs that are part of the Rust wrapper:

  • craw, the FFI bindings generated directly by bindgen. These are not recommended for normal use, but are available in case functionality is needed beyond that provided by the Context API.

  • The main Context API, which is a safe wrapper around the C API which keeps track of the current tptoken and an error buffer. The reason this metadata is necessary is because this crate binds to the threaded version of YottaDB, which requires a tptoken and err_buffer. See transaction processing for more details on tptoken and transactions.

  • Most operations are encapsulated in methods in the KeyContext struct. Iteration helpers are available to iterate over values in the database in a variety of ways.

Note

To run any of the examples below, create a file (e.g., rust_example.rs) under the examples/ sub-directory and run it, from the YDBRust directory, using the following command:

$ cargo run --quiet --example rust_example

Example:

A basic database operation (set a value, retrieve it, and delete it).

use yottadb::{Context, KeyContext, DeleteType, YDBResult};

fn main() -> YDBResult<()> {
    let ctx = Context::new();
    let key = KeyContext::new(&ctx, "^MyGlobal", &["SubscriptA", "42"]);

    key.set("This is a persistent message")?;
    let buffer = key.get()?;

    println!("{:?}", String::from_utf8(buffer).unwrap());
    key.delete(DeleteType::DelNode)?;
    Ok(())
}

Output:

"This is a persistent message"

Macros

ci_t

macro_rules! ci_t {
    ($tptoken: expr, $err_buffer: expr, $routine: expr $(, $args: expr)* $(,)?) => { ... };
}

ci_t macro is used to make an FFI call to M.

Each argument passed (after routine) must correspond to the appropriate argument expected by routine. If routine returns a value, the first argument must be a pointer to an out parameter in which to store the value. All arguments must be representable as C types.

Example:

Call the M routine described by HelloWorld1 in the call-in table. See also examples/m-ffi/helloworld1.m and examples/m-ffi/calltab.ci.

use std::ffi::CString;
use std::os::raw::c_char;
use yottadb::{craw, ci_t, TpToken};
use std::env;

fn main(){
     env::set_var("ydb_routines", "examples/m-ffi");
     env::set_var("ydb_ci", "examples/m-ffi/calltab.ci");

     let mut buf = Vec::<u8>::with_capacity(100);
     let mut msg = craw::ydb_string_t { length: buf.capacity() as u64, address: buf.as_mut_ptr() as *mut c_char };

     let routine = CString::new("HelloWorld1").unwrap();
     unsafe {
             ci_t!(TpToken::default(), Vec::new(), &routine, &mut msg as *mut _).unwrap();
             buf.set_len(msg.length as usize);
     }
     println!("{:?}", String::from_utf8_lossy(&buf));
}

Output:

"entry called"

cip_t

macro_rules! cip_t {
    ($tptoken: expr, $err_buffer: expr, $routine: expr, $($args: expr),* $(,)?) => { ... };
}

cip_t macro is used to make a FFI call to M using a cached function descriptor.

Each argument passed (after routine) must correspond to the appropriate argument expected by routine. If routine returns a value, the first argument must be a pointer to an out parameter in which to store the value. All arguments must be representable as C types.

Example:

Call the M routine described by HelloWorld1 in the call-in table. See also examples/m-ffi/helloworld1.m and examples/m-ffi/calltab.ci.

use std::env;
use std::ffi::CString;
use std::os::raw::c_char;
use yottadb::{craw, cip_t, CallInDescriptor, TpToken};

fn main(){
     env::set_var("ydb_routines", "examples/m-ffi");
     env::set_var("ydb_ci", "examples/m-ffi/calltab.ci");

     let mut buf = Vec::<u8>::with_capacity(100);
     let mut msg = craw::ydb_string_t { length: buf.capacity() as u64, address: buf.as_mut_ptr() as *mut c_char };
     let mut routine = CallInDescriptor::new(CString::new("HelloWorld1").unwrap());
     unsafe {
             cip_t!(TpToken::default(), Vec::new(), &mut routine, &mut msg as *mut _).unwrap();
             buf.set_len(msg.length as usize);
     }
     println!("{:?}", String::from_utf8_lossy(&buf));
}

Output:

"entry called"

make_ckey

macro_rules! make_ckey {
    ( $ctx:expr, $var:expr $(,)?) => { ... };
    ( $ctx:expr, $gbl:expr $(, $x:expr)+ ) => { ... };
}

make_ckey macro is used to create a KeyContext with the given subscripts, provided a context.

Example:

use std::error::Error;
use yottadb::Context;

fn main() -> Result<(), Box<dyn Error>> {
    let ctx = Context::new();
    let key = yottadb::make_ckey!(ctx, "^hello", "world");
    key.set("This is a persistent message")?;
    println!("{:?}", key.key);
    println!("{:?}", String::from_utf8(key.get()?).unwrap());
    Ok(())
}

Output:

^hello("world")
"This is a persistent message"

make_key

macro_rules! make_key {
    ( $var:expr $(,)? ) => { ... };
    ( $var: expr $( , $subscript: expr)+ $(,)? ) => { ... };
}

make_key macro provides a Key object for the given subscripts.

Example:

fn main() {
   let my_key = yottadb::make_key!("^MyTimeSeriesData", "5");
   println!("{:?}", my_key);
}

Output:

^MyTimeSeriesData("5")

Note

A node is created only when it is set. If the value of the key is not set key.data() will always return NoData.

Struct yottadb::Context

A struct that keeps track of the current transaction and error buffer.

delete_excl()

As a wrapper for the C function ydb_delete_excl_st(), delete_excl() deletes all local variables except for those passed in saved_variables.

pub fn delete_excl(&self, saved_variables: &[&str]) -> YDBResult<()>

Passing an empty saved_variables slice deletes all local variables. Attempting to save a global or intrinsic special variable is an error.

Example:

use yottadb::{Context, KeyContext, YDBResult};

fn main()-> YDBResult<()> {
    // Create three variables and set all
    let ctx = Context::new();
    let a = KeyContext::variable(&ctx, "deleteExclTestA");
    a.set("test data")?;
    let b = KeyContext::variable(&ctx, "deleteExclTestB");
    b.set("test data 2")?;
    let c = KeyContext::variable(&ctx, "deleteExclTestC");
    c.set("test data 3")?;

    println!("Before deleting any variables:");
    println!("a: {:?}", a.data()?);
    println!("b: {:?}", b.data()?);
    println!("c: {:?}", c.data()?);

    // Delete all variables except `a`
    ctx.delete_excl(&[&a.variable])?;
    println!("After deleting variables b and c:");
    println!("a: {:?}", a.data()?);
    println!("b: {:?}", b.data()?);
    println!("c :{:?}", c.data()?);

    // Delete `a` too
    ctx.delete_excl(&[])?;
    println!("After deleting variable a:");
    println!("a: {:?}", a.data()?);

    Ok(())
}

Output:

Before deleting any variables:
a: ValueData
b: ValueData
c: ValueData
After deleting variables b and c:
a: ValueData
b: NoData
c :NoData
After deleting variable a:
a: NoData

eintr_handler()

Runs the YottaDB deferred signal handler (if necessary).

This function must be called if an application has a tight loop inside a transaction which never calls a YDB function.

pub fn eintr_handler(&self) -> YDBResult<()>

lock()

As a wrapper for the C function ydb_lock_st(), lock() acquires locks specified and releases all others.

pub fn lock(&self, timeout: Duration, locks: &[Key]) -> YDBResult<()>

This operation is atomic. If any lock cannot be acquired, all locks are released. The timeout specifies the maximum time to wait before returning an error. If no locks are specified, all locks are released.

Note that YottaDB locks are per-process, not per-thread.

For implementation reasons, there is a hard limit to the number of Keys that can be passed in locks:

  • 64-bit architecture: 10 Keys

  • 32-bit architecture: 9 Keys

If more than this number of keys are passed, YDB_ERR_MAXARGCNT will be returned.

Example:

use std::slice;
use std::time::Duration;
use yottadb::{Context, KeyContext, Key};
use yottadb::YDBResult;

fn main()->YDBResult<()>  {
   // You can use either a `Key` or a `KeyContext` to acquire a lock.
   // This uses a `KeyContext` to show that you need to use `.key` to get the inner `Key`.
   let ctx = Context::new();
   let a = KeyContext::variable(&ctx, "lockA");

   // Release any locks held and acquire a single lock
   // using `from_ref` here allows us to use `a` later without moving it
   ctx.lock(Duration::from_secs(1), slice::from_ref(&a.key)).unwrap();

   // Release any locks held and acquire multiple locks
   let locks = vec![a.key, Key::variable("lockB")];
   ctx.lock(Duration::from_secs(1), &locks).unwrap();

   // Release all locks
   ctx.lock(Duration::from_secs(0), &[]).unwrap();
   Ok(())
}

new()

Create a new Context.

pub fn new() -> Context

new_key()

Create a KeyContext from this Context.

pub fn new_key<K: Into<Key>>(&self, key: K) -> KeyContext

str2zwr()

As a wrapper for the C function ydb_str2zwr_st(), str2zwr() serializes the given binary sequence to zwrite format, which is printable ASCII.

pub fn str2zwr(&self, original: &[u8]) -> YDBResult<Vec<u8>>

Example:

When ydb_chset=UTF-8 is set, this will preserve UTF-8 characters:

use yottadb::{Context, YDBResult};

fn main() -> YDBResult<()>{
    let ctx = Context::new();
    let str2zwr = ctx.str2zwr(b"The quick brown dog\x08\x08\x08fox jumps over the lazy fox\x08\x08\x08dog.")?;
    println!("Original string: {}", "The quick brown dog\x08\x08\x08fox jumps over the lazy fox\x08\x08\x08dog.");
    println!("Zwrite formatted string: {:?}",String::from_utf8(str2zwr).unwrap());
    Ok(())
}

Output:

Original string: The quick brown fox jumps over the lazy dog.
Zwrite formatted string: "\"The quick brown dog\"_$C(8,8,8)_\"fox jumps over the lazy fox\"_$C(8,8,8)_\"dog.\""

When the input is invalid UTF-8, it will use the more verbose zwrite format:

use yottadb::{Context, YDBResult};

fn main() -> YDBResult<()>{
    let ctx = Context::new();
    let str2zwr = ctx.str2zwr(b"\xff")?;
    println!("{:?}",String::from_utf8(str2zwr).unwrap());
    Ok(())
}

Output:

"$ZCH(255)"

tp()

pub fn tp<'a, F>(
    &'a self,
    f: F,
    trans_id: &str,
    locals_to_reset: &[&str]
) -> Result<(), Box<dyn Error + Send + Sync>>
where
    F: FnMut(&'a Self) -> Result<TransactionStatus, Box<dyn Error + Send + Sync>>,

As a wrapper for the C function ydb_tp_st(), tp() is used to execute a transaction, where f is the transaction to execute.

tp stands for "transaction processing".

The parameter trans_id is the name logged for the transaction. If trans_id has the special value "BATCH", durability is not enforced by YottaDB. See ydb_tp_st() for details.

The argument passed to f is a transaction processing token.

Application code can return a TransactionStatus in order to rollback or restart. tp() behaves as follows:

  • If f panics, the transaction is rolled back and the panic resumes afterwards.

  • If f returns Ok(TransactionStatus), the transaction will have the behavior documented under TransactionStatus (commit, restart, and rollback, respectively).

  • If f returns an Err(YDBError), the status from that error will be returned to the YottaDB engine. As a result, if the status for the YDBError is YDB_TP_RESTART, the transaction will be restarted. Otherwise, the transaction will be rolled back and the error returned from tp().

  • If f returns any other Err variant, the transaction will be rolled back and the error returned from tp().

f must be FnMut, not FnOnce, since the YottaDB engine may call f many times if necessary to ensure ACID properties. This may affect your application logic; within a transaction, the intrinsic special variable $trestart has the number of times the transaction has been restarted.

Example:

Rollback a transaction if an operation fails:

use yottadb::{Context, KeyContext,  TransactionStatus, YDBResult};

fn fallible_operation() -> Result<(), &'static str> {
   if rand::random() {
      Ok(())
   } else {
      Err("the operation failed")
   }
}

fn main() -> YDBResult<()> {
   let _ctx = Context::new();
   let var = KeyContext::variable(&_ctx, "tpRollbackTest");
   var.set("initial value")?;
   println!("starting tp");
   let maybe_err = _ctx.tp(|_ctx| {
      println!("in tp");
      fallible_operation()?;
      println!("succeeded");
      var.set("new value")?;
      Ok(TransactionStatus::Ok)
   }, "BATCH", &[]);
   let expected_val: &[_] = if maybe_err.is_ok() {
      b"new value"
   } else {
      b"initial value"
   };

   let output_buffer = var.get()?;
   println!("Ouput - {:?}", String::from_utf8(output_buffer).unwrap());
   println!("Expected value - {:?}", String::from_utf8((&expected_val).to_vec()).unwrap());
   Ok(())

}

Output:

# Output when transaction is unsuccessful

starting tp
in tp
Ouput - "initial value"
Expected value - "initial value"


# Output when transaction is successful

starting tp
in tp
succeeded
Ouput - "new value"
Expected value - "new value"

Retry a transaction until it succeeds:

use yottadb::{Context, TransactionStatus};

fn main(){
    let ctx = Context::new();
    ctx.tp(|_tptoken| {
        if fallible_operation().is_ok() {
            Ok(TransactionStatus::Ok)
        } else {
            Ok(TransactionStatus::Restart)
        }
    }, "BATCH", &[]).unwrap();
}

fn fallible_operation() -> Result<(), ()> {
    if rand::random() {
        Ok(())
    } else {
        Err(())
    }
}

tptoken()

Return the token for the transaction associated with this Context.

This allows calling yottadb functions in the craw API that have not yet been wrapped and require a tptoken from inside a transaction.

pub fn tptoken(&self) -> TpToken

Example:

use std::env;
use std::ffi::CStr;
use yottadb::{ci_t, Context, TransactionStatus};

fn main() {
   env::set_var("ydb_routines", "examples/m-ffi");
   env::set_var("ydb_ci", "examples/m-ffi/calltab.ci");
   let ctx = Context::new();
   ctx.tp(|ctx| {
       let tptoken = ctx.tptoken();
       let routine = CStr::from_bytes_with_nul(b"noop\0").unwrap();
       unsafe { ci_t!(tptoken, Vec::new(), routine)?; }
       Ok(TransactionStatus::Ok)
   }, "BATCH", &[]).unwrap();
}

zwr2str()

As a wrapper for the C funtion ydb_zwr2str_st(), zwr2str() deserializes a zwrite formatted buffer to the original binary buffer.

pub fn zwr2str(&self, serialized: &[u8]) -> Result<Vec<u8>, YDBError>

Example:

use yottadb::{Context, YDBResult};

fn main() -> YDBResult<()>{
    let ctx = Context::new();

    // Use "$ZCH" (instead of "$C") below as that will work in both M and UTF-8 modes (of "ydb_chset" env var)
    // Note: Cannot use "$ZCHAR" below as "$ZCH" is the only input format recognized by "zwr2str()".
    let out_buf = ctx.zwr2str(b"\"The quick brown dog\"_$ZCH(8,8,8)_\"fox jumps over the lazy fox\"_$ZCH(8,8,8)_\"dog.\"")?;
    println!("Zwrite formatted string: {}","The quick brown dog\"_$ZCH(8,8,8)_\"fox jumps over the lazy fox\"_$ZCH(8,8,8)_\"dog.");
    println!("String after zwr2str: {}",String::from_utf8(out_buf).unwrap());

    Ok(())
}

Output:

Zwrite formatted string: The quick brown dog"_$ZCH(8,8,8)_"fox jumps over the lazy fox"_$ZCH(8,8,8)_"dog.
String after zwr2str: The quick brown fox jumps over the lazy dog.

zwr2str() writes directly to out_buf to avoid returning multiple output buffers.

Call-in functions

ci_tab_open()

Open the call-in table stored in file and return its file descriptor.

You can later switch the active call-in table by calling ci_tab_switch() with the file descriptor.

pub fn ci_tab_open(&self, file: &CStr) -> YDBResult<CallInTableDescriptor>

Refer to the ci_tab_switch() example to see how ci_tab_open() works.

ci_tab_switch()

Switch the active call-in table to new_handle. Returns the previously active table.

new_handle is a file descriptor returned by ci_tab_open().

pub fn ci_tab_switch(&self,new_handle: CallInTableDescriptor) -> YDBResult<CallInTableDescriptor>

Example:

use std::env;
use std::ffi::CString;
use std::os::raw::c_char;
use yottadb::{craw, ci_t, TpToken, Context, YDBResult};

fn main() -> YDBResult<()>{
    env::set_var("ydb_routines", "examples/m-ffi");
    let ctx = Context::new();
    let file = CString::new("examples/m-ffi/calltab.ci").unwrap();
    let descriptor = ctx.ci_tab_open(&file)?;
    ctx.ci_tab_switch(descriptor)?;

    let mut buf = Vec::<u8>::with_capacity(100);
    let mut msg = craw::ydb_string_t { length: buf.capacity() as u64, address: buf.as_mut_ptr() as *mut c_char };

    let routine = CString::new("HelloWorld1").unwrap();
    unsafe {
      ci_t!(TpToken::default(), Vec::new(), &routine, &mut msg as *mut _).unwrap();
      buf.set_len(msg.length as usize);
    }
    println!("{:?}", String::from_utf8_lossy(&buf));
    Ok(())
}

Output:

"entry called"

Utility Functions

message()

Return the message corresponding to a YottaDB error code.

pub fn message(&self, status: i32) -> YDBResult<Vec<u8>>

Example:

use yottadb::{Context, KeyContext};

fn main(){
   let ctx = Context::new();
   let key = KeyContext::variable(&ctx, "oopsNotDefined");

   let err = key.get().unwrap_err();

   let buf = ctx.message(err.status).unwrap();
   let msg = String::from_utf8(buf).unwrap();
   println!("{:?}", msg);
}

Output:

"%YDB-E-LVUNDEF, Undefined local variable: !AD"
release()

Return a string in the format rustwr <rust wrapper version> <$ZYRELEASE>.

$ZYRELEASE is the intrinsic special variable containing the version of the underlying C database and <rust wrapper version> is the version of yottadb published to crates.io.

Example:

use yottadb::{Context, YDBError};

fn main() -> Result<(), YDBError>{
   let ctx = Context::new();
   let release = ctx.release()?;
   println!("{:?}",release);
   Ok(())
}

Output:

"rustwr 2.0.0 YottaDB r1.34 Linux x86_64"

Struct yottadb::KeyContext

A key which keeps track of the current transaction and error buffer.

Keys are used to get, set, and delete values in the database.

data()

As a wrapper for the C function ydb_data_st(), data() provides information about whether or not a global or local variable node has data and/or a subtree.

pub fn data(&self) -> YDBResult<DataReturn>

It returns the following information in DataReturn about a local or global variable node:

  • NoData: There is neither a value nor a subtree; i.e. it is undefined

  • ValueData: There is a value, but no subtree

  • TreeData: There is no value, but there is a subtree

  • ValueTreeData: There are both a value and a subtree

Example:

use yottadb::{Context, make_ckey, DeleteType};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let ctx = Context::new();
    let key = make_ckey!(ctx, "^helloDoesNotExist");

    println!("{:?}", key.data()?);
    key.set("Setting the value for key")?;
    println!("{:?}", key.data()?);
    key.delete(DeleteType::DelNode)?;
    Ok(())
}

Output:

NoData
ValueData

delete()

As a wrapper for the C function ydb_delete_st(), delete() deletes nodes in the local or global variable tree or subtree specified.

pub fn delete(&self, delete_type: DeleteType) -> YDBResult<()>

A value of DelNode or DelTree for DeleteType specifies whether to delete just the node at the root, leaving the (sub)tree intact, or to delete the node as well as the (sub)tree.

Example:

use yottadb::{Context, DeleteType, make_ckey};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let ctx = Context::new();
    let key = make_ckey!(ctx, "^helloDeleteMe");

    key.set("Hello world!")?;
    println!("{:?}", String::from_utf8(key.get()?).unwrap());
    key.delete(DeleteType::DelTree)?;

    println!("{:?}", key.data()?);
    Ok(())
}

Output:

"Hello world!"
NoData

get()

As a wrapper for the C function ydb_get_st(), get() gets the value of this key from the database and returns the value.

pub fn get(&self) -> YDBResult<Vec<u8>>

Example:

use yottadb::{Context, make_ckey};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let ctx = Context::new();
    let key = make_ckey!(ctx, "^hello");

    key.set("Hello world!")?;
    let output_buffer = key.get()?;

    println!( "{:?}", String::from_utf8(output_buffer).unwrap());
    Ok(())
}

Output:

"Hello world!"

get() can be used to get the value of an Intrinsic Special Variable as well.

Example:

use yottadb::{Context, KeyContext, YDBResult};

fn main() -> YDBResult<()> {
    let ctx = Context::new();
    let key = KeyContext::variable(&ctx, "$zyrelease");

    let zyrelease = key.get()?;

    println!("$zyrelease: {}", String::from_utf8(zyrelease).unwrap());
    Ok(())
}

Output:

$zyrelease: YottaDB r1.34 Linux x86_64

get_and_parse()

Retrieve a value from the database and parse it into a Rust data structure.

This is a shorthand for String::from_utf8(key.get()).parse() that collects the errors into a single enum.

pub fn get_and_parse<T: FromStr>(&self) -> Result<T, ParseError<T::Err>>

Example:

Set and retrieve an integer, with error handling.

use yottadb::{Context, ParseError, YDBResult};

fn main() -> YDBResult<()> {
   let ctx = Context::new();
   let key = ctx.new_key("weekday");
   key.set(5.to_string())?;
   let day: u8 = match key.get_and_parse() {
       Ok(day) => day,
       Err(ParseError::YDB(err)) => return Err(err),
       Err(ParseError::Utf8(err)) => {
           eprintln!("warning: had an invalid string");
           String::from_utf8_lossy(&err.as_bytes()).parse().unwrap()
       }
       Err(ParseError::Parse(err, original)) => {
           panic!("{} is not a valid string: {}", original, err);
       }
   };
   println!("{:?}", day);
   Ok(())
}

Output:

5

Set and retrieve an integer, without error handling.

use yottadb::{Context,  YDBResult};

fn main() -> YDBResult<()> {
   let ctx = Context::new();
   let key = ctx.new_key("weekday");
   key.set(5.to_string())?;
   let day: u8 = key.get_and_parse().unwrap();
   println!("{:?}", day);
   Ok(())
}

Output:

5

increment()

As a wrapper for the C function ydb_incr_st(), increment() converts the value to a number and increments it based on the value specified by Option.

pub fn increment(&self, increment: Option<&[u8]>) -> YDBResult<Vec<u8>>

increment defaults to 1 if the value is None.

Example:

use yottadb::{Context, make_ckey};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let ctx = Context::new();
    let key = make_ckey!(ctx, "helloIncrementMe");

    key.set("0")?;
    let mut output_buffer = key.get()?;

    println!("Before increment: {:?}", String::from_utf8(output_buffer).unwrap());
    key.increment(None)?;
    output_buffer = key.get()?;

    println!("Incremented by 1 (default): {:?}", String::from_utf8(output_buffer).unwrap());

    println!("Before increment : {:?}", "100" );
    key.increment(Some(b"100"))?;
    output_buffer = key.get()?;

    println!("After increment: {:?}", String::from_utf8(output_buffer).unwrap());
    Ok(())
}

Output:

Before increment: "0"
Incremented by 1 (default): "1"
Before increment : "100"
After increment: "101"

iter_key_nodes()

Iterates over all nodes for the local or global variable pointed to by the key and returns a copy of the key at each node.

pub fn iter_key_nodes(&mut self) -> ForwardKeyNodeIterator<'_>

iter_key_nodes_reverse()

Iterates in reverse order over all nodes for the local or global variable pointed to by the key and returns a copy of the key at each node.

pub fn iter_key_nodes_reverse(&mut self) -> ReverseKeyNodeIterator<'_>

iter_key_subs()

Iterates over all subscripts at this level of the local or global variable tree and returns a copy of the key at each subscript.

pub fn iter_key_subs(&mut self) -> ForwardKeySubIterator<'_>

iter_key_subs_reverse()

Iterates in reverse order over all subscripts at this level of the local or global variable tree and returns a copy of the key at each subscript.

pub fn iter_key_subs_reverse(&mut self) -> ReverseKeySubIterator<'_>

iter_nodes()

Iterates over all nodes for the local or global variable pointed to by the key and returns the value at each node.

pub fn iter_nodes(&mut self) -> ForwardNodeIterator<'_>

iter_nodes_reverse()

Iterates in reverse order over all nodes for the local or global variable pointed to by the key and returns the value at each node.

pub fn iter_nodes_reverse(&mut self) -> ReverseNodeIterator<'_>

iter_subs()

Iterated over all the subscripts at this level of the local or global variable tree and returns the subscript for each node.

pub fn iter_subs(&mut self) -> ForwardSubIterator<'_>

iter_subs_reverse()

Iterates in reverse order over all the subscripts at this level of the local or global variable tree and returns the subscript for each node.

pub fn iter_subs_reverse(&mut self) -> ReverseSubIterator<'_>

iter_subs_values()

Iterates over all the subscripts at this level of the local or global variable tree and returns the subscript and value for each node.

pub fn iter_subs_values(&mut self) -> ForwardSubValueIterator<'_>

iter_subs_values_reverse()

Iterates in reverse order over all the subscripts at this level of the local or global variable tree and returns the subscript and value for each node.

pub fn iter_subs_values_reverse(&mut self) -> ReverseSubValueIterator<'_>

iter_values()

Iterates over all the values at this level of the local or global variable tree and returns the value for each node.

pub fn iter_values(&mut self) -> ForwardValueIterator<'_>

iter_values_reverse()

Iterates in reverse order over all the values at this level of the local or global variable tree and returns the value for each node.

pub fn iter_values_reverse(&mut self) -> ReverseValueIterator<'_>

lock_decr()

As a wrapper for the C function ydb_lock_decr_st(), lock_decr() decrements the count of a lock held by the process.

When the count for a lock goes from 1 to 0, it is released. Attempting to decrement a lock not owned by the current process has no effect.

pub fn lock_decr(&self) -> YDBResult<()>

Example:

use yottadb::{Context, KeyContext, YDBResult};
use std::time::Duration;

fn main() -> YDBResult<()> {
   let ctx = Context::new();
   let key = KeyContext::variable(&ctx, "lockDecrTest");
   key.lock_incr(Duration::from_secs(1))?;
   key.lock_decr()?;
   Ok(())
}

lock_incr()

As a wrapper for the C function ydb_lock_incr_st(), lock_incr() acquires a lock not currently held by the process, or increments the count for locks already held.

pub fn lock_incr(&self, timeout: Duration) -> YDBResult<()>

timeout specifies a time that the function waits to acquire the requested locks. If timeout is 0, the function makes exactly one attempt to acquire the lock.

Example:

use yottadb::{Context, KeyContext, YDBResult};
use std::time::Duration;
fn main() -> YDBResult<()>{
   let ctx = Context::new();
   let key = KeyContext::variable(&ctx, "lockIncrTest");
   key.lock_incr(Duration::from_secs(1))?;
   Ok(())
}

new()

Create a new KeyContext, creating the Key at the same time.

pub fn new<V, S>(ctx: &Context, variable: V, subscripts: &[S]) -> KeyContext
where
    V: Into<String>,
    S: Into<Vec<u8>> + Clone,

next_node()

As a wrapper for the C function ydb_node_next_st(), next_node() facilitates traversal of a local or global variable tree to return the next node.

pub fn next_node(&mut self) -> YDBResult<KeyContext>

Example:

use yottadb::{Context, make_ckey};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let ctx = Context::new();
    let mut key = make_ckey!(ctx, "^hello", "0", "0");

    key.set("Hello world!")?;
    // Forget the second subscript
    key.truncate(1);
    let k2 = key.next_node()?;

    println!("Current node : {:?}",key.key);
    println!("Next node: {:?}", k2.key);
    Ok(())
}

Output:

Current node : ^hello("0")
Next node: ^hello("0", "0")

next_node_self()

As a wrapper for the C function ydb_node_next_st(), next_mode_self() facilitates traversal of a local or global variable tree, and passes itself as the output parameter.

pub fn next_node_self(&mut self) -> YDBResult<()>

Example:

use yottadb::{Context, make_ckey};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let ctx = Context::new();
    let mut key = make_ckey!(ctx, "^hello", "0", "0");

    key.set("Hello world!")?;
    // Forget the second subscript
    key.truncate(1);
    println!("Current node (self) : {:?}",key.key);
    key.next_node_self()?;

    println!("Next node (self) : {:?}",key.key);
    Ok(())
}

Output:

Current node (self) : ^hello("0")
Next node (self) : ^hello("0", "0")

next_sub()

As a wrapper for the C function ydb_subscript_next_st(), next_sub() implements traversal of a tree by searching for the next subscript.

pub fn next_sub(&self) -> YDBResult<Vec<u8>>

Example:

use yottadb::{Context, make_ckey};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let ctx = Context::new();
    let mut key = make_ckey!(ctx, "^hello", "0");

    key.set("Hello world!")?;
    key[0] = Vec::from("1");
    key.set("Hello world!")?;
    key[0] = Vec::from("0");
    println!("Current subscript : {:?}", std::str::from_utf8(&key[0]).unwrap());

    let subscript = key.next_sub()?;
    println!("Next subscript : {:?}", String::from_utf8(subscript).unwrap());
    Ok(())
}

Output:

Current subscript : "0"
Next subscript : "1"

next_sub_self()

As a wrapper for the C function ydb_subscript_next_st(), next_sub_self() implements traversal of a tree by searching for the next subscript, and passes itself as the output parameter.

pub fn next_sub_self(&mut self) -> YDBResult<()>

Example:

use yottadb::{Context, make_ckey};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let ctx = Context::new();
    let mut key = make_ckey!(ctx, "^hello", "a");

    key.set("Hello world!")?;
    key[0] = Vec::from("b");
    key.set("Hello world!")?;
    key[0] = Vec::from("a");
    println!("Current subscript (self) : {:?}", std::str::from_utf8(&key[0]).unwrap());


    // Starting at a, the next sub should be b
    key.next_sub_self()?;
    let curr_key = &key[0];
    println!("Next subscript (self) : {:?}", std::str::from_utf8(curr_key).unwrap());
    Ok(())
}

Output:

Current subscript (self) : "a"
Next subscript (self) : "b"

prev_node()

As a wrapper for the C function ydb_node_previous_st(), prev_node() facilitates reverse traversal of a local or global variable tree to return the previous node.

pub fn prev_node(&mut self) -> YDBResult<KeyContext>

Example:

use yottadb::{Context,make_ckey};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let ctx = Context::new();
    let mut key = make_ckey!(ctx, "^helloPrevNode", "0", "0");

    key.set("Hello world!")?;
    // Forget the second subscript
    key.truncate(1);
    // Go to the next node, then walk backward
    key[0] = "1".into();
    let k2 = key.prev_node()?;

    println!("Current node: {:?}",key.key);
    println!("Previous node: {:?}",k2.key);
    Ok(())
}

Output:

Current node: ^helloPrevNode("1")
Previous node: ^helloPrevNode("0", "0")

prev_node_self()

As a wrapper for the C function ydb_node_previous_st(), prev_node_self() facilitates reverse traversal of a local or global variable tree and reports the predecessor node, passing itself as the output parameter.

pub fn prev_node_self(&mut self) -> YDBResult<()>

Example:

use yottadb::{Context, make_ckey};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let ctx = Context::new();
    let mut key = make_ckey!(ctx, "^hello", "0", "0");

    key.set("Hello world!")?;
    // Forget the second subscript
    key.truncate(1);
    println!("Current node (self) : {:?}",key.key);
    // Go to the next node, then walk backward
    key[0] = Vec::from("1");
    key.prev_node_self()?;

    println!("Previous node (self) : {:?}",key.key);
    Ok(())
}

Output:

Current node (self) : ^hello("0")
Previous node (self) : ^hello("0", "0")

prev_sub()

As a wrapper for the C function ydb_subscript_previous_st(), prev_sub() implements traversal of a tree by searching for the previous subscript.

pub fn prev_sub(&self) -> YDBResult<Vec<u8>>

Example:

use yottadb::{Context, make_ckey};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let ctx = Context::new();
    let mut key = make_ckey!(ctx, "^hello", "0");

    key.set(b"Hello world!")?;
    key[0] = Vec::from("1");
    key.set("Hello world!")?;
    key[0] = Vec::from("1");
    println!("Current subscript : {:?}", std::str::from_utf8(&key[0]).unwrap());

    let subscript = key.prev_sub()?;
    println!("Previous subscript : {:?}", String::from_utf8(subscript).unwrap());
    Ok(())
}

Output:

Current subscript : "1"
Previous subscript : "0"

prev_sub_self()

As a wrapper for the C function ydb_subscript_previous_st(), prev_sub_self() implements reverse traversal of a tree by searching for the previous subscript, and passes itself in as the output parameter.

pub fn prev_sub_self(&mut self) -> YDBResult<()>

Example:

use yottadb::{Context, make_ckey};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let ctx = Context::new();
    let mut key = make_ckey!(ctx, "^hello", "0");

    key.set("Hello world!")?;
    key[0] = Vec::from("1");
    key.set("Hello world!")?;
    key[0] = Vec::from("1");
    println!("Current subscript (self): {:?}", std::str::from_utf8(&key[0]).unwrap());

    key.prev_sub_self()?;
    let curr_key = &key[0];
    println!("Previous subscript (self): {:?}", std::str::from_utf8(curr_key).unwrap());
    Ok(())
}

Output:

Current subscript (self): "1"
Previous subscript (self): "0"

set()

As a wrapper for the C function ydb_set_st(), set() sets the value of a key in the database.

pub fn set<U: AsRef<[u8]>>(&self, new_val: U) -> YDBResult<()>

Example:

use yottadb::{Context, make_ckey};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let ctx = Context::new();
    let key = make_ckey!(ctx, "^hello");

    key.set("Hello world!")?;
    let output_buffer = key.get()?;

    println!("{:?}", String::from_utf8(output_buffer).unwrap());
    Ok(())
}

Output:

"Hello world!"

variable()

Shortcut for creating a KeyContext with no subscripts.

pub fn variable<V: Into<String>>(ctx: &Context, var: V) -> Self

with_key()

Create a new KeyContext using an existing key.

pub fn with_key<K: Into<Key>>(ctx: &Context, key: K) -> Self

Advantages of the Rust wrapper

  • Rust has almost no overhead calling into C.

  • Rust has a context API, where the buffers are re-used between calls so it's not constantly allocating and deallocating.

  • Rust can also pass numbers into M FFI, not just ydb_string_t.