Skip to content

BaseX v0.7.0

Latest
Compare
Choose a tag to compare
@RomanHodulak RomanHodulak released this 16 Dec 20:11
e62e352

Aside from the promised features, I've been reading Rust for Rustaceans lately, influencing some of the design decisions made in the internal code structure. I'm at 6 out of 13 chapters, can't wait for what's more in the store!

I highly recommend this book for any aspiring Rustacean 📖🦀

And last but not least, CI configuration has improved a lot. It contains multiple stages now, caches build results and dependencies, builds with multiple toolchains, does security audits, and brings static analysis with rustfmt and clippy 📎

The Book: Existential types

In the second chapter of the book, I've learned this trick with "Existential types." You can make a public trait with private implementation. And make methods that would return Type instead return impl Trait. That impl Trait thing is syntax sugar for generic return types.

That way you can change the implementation without introducing a breaking change.

fn info(&self) -> Result<impl Info> { // <- returns some implementation of the public trait
    // ...
    Ok(RawInfo::from_str(...).unwrap()) // <- returns an instance of private type implementing the trait
}

Now I'm wondering where is the stopping point? Should I go and turn all my public structs into traits and hide their implementation from the public API? Guess only the next release will tell!

Features

Let me introduce basex::serializer::Options and basex::compiler::Info. Both of these concepts belong to Query and are returned by Query::info and Query::options.

New: Query::info

fn info(&self) -> Result<impl Info>;

You can now collect parsed compiler info from the query—for instance, the optimized version of the query and its time to produce it.

let info = query.info()?;

// Read compilation info
println!("Optimized Query: {:?}", info.optimized_query());
println!("Total Time: {:?}", info.total_time());
println!("Hit(s): {:?}", info.hits());

The compilation info needs to be enabled before creating the query server-side. This influenced the design of Client::query, which now returns a builder object, asking you if you want to query with_info, unlocking Query::info for you, or without_info, reducing overhead but making Query::info inaccessible.

New: Query::options

fn options(&self) -> Result<Options>;

You can now read and update server-side query result serializer options. This influences the way the result looks and might for instance change encoding.

// Get options from the server for the query
let options = query.options()?;

// Read options
let encoding = options.get("encoding").unwrap();
let indent = options.get("indent").unwrap();

println!("Encoding: {}", encoding.as_str());
println!("Indent: {}", if indent.as_bool().unwrap() { "ON" } else { "OFF" });

// Set options
let encoding = options.set("encoding", "UTF-8");
let indent = options.set("indent", true);

println!("Set Encoding: {}", encoding.as_str());
println!("Set Indent: {}", if indent.as_bool().unwrap() { "ON" } else { "OFF" });

// Save options to session on server
options.save(client);

My decision was to make it like a key-value store. Now an exhaustive could be made using the BaseX documentation, but there is no guarantee that new options might be created in the next release of the BaseX server, making it incompatible with the client.

CI

The build pipeline now has a bunch of new jobs! 🚦

The code is now formatted by rustfmt and further analyzed by clippy.

There are 3 build jobs now using:

  1. Nightly toolchain
  2. Nightly toolchain with 6 months old Rust version
  3. Nightly toolchain with minimal dependency versions

And the build is cached.

Packages are now checked for security issues via cargo-audit, which runs only on Cargo.toml changes and once per day.

Chores

  • Set version range of all dependencies the widest possible.

Fixes

  • Upgrade rust-embed minimal version from 0.5.2 to 0.6.3 due to RUSTSEC-2021-0126 critical security issue.

Upgrading from the previous version

Getting the code work is pretty straightforward, but writing it in a way that makes sense will require a bit more effort.

Before

let mut client = Client::connect("localhost", 1984, "admin", "admin")?;

let query = client.query("count(/None/*)")?;
let info = query.info()?;
let options = query.options()?;

After

let mut client = Client::connect("localhost", 1984, "admin", "admin")?;

let query = client.query("count(/None/*)")?.with_info()?;
let info = query.info()?.to_string();
let options = query.options()?.to_string();

Plans for next version

The feature set is pretty much complete as far as the required functionality goes. The library can be only polished further, introduce async or add features on top. Which is exactly what is planned next.

The next version temporarily stabilizes the API and then basex-rs is going async!