Expand description
Experimental library for using SQLite with minimal SQL
Liter generates complete SQL schema definitions from ordinary Rust struct definitions at compile-time.
Built on top of rusqlite, it leverages powerful user-implementable traits to generate well-constrained and type-aware table definitions.
SQL is not generated by the derive macros directly: they can only operate textually, so they invoke const functions instead.
These then have access to the types and their trait implementations, which give you control over how the SQL is generated.
§Basic Example
Here’s a very simple example of a database with just one table.
use liter::{database, Table};
#[database]
struct ShoppingList (
Item
);
#[derive(Table, PartialEq, Debug)]
struct Item {
count: u32,
name: String
}
let list = ShoppingList::create_in_memory()?;
let oranges = Item {
count: 3,
name: "Orange".to_string(),
};
list.insert(&oranges)?;
let items = list.get_all::<Item>()?;
assert_eq!(oranges, items[0]);Liter generates the following SQL schema for you, as well as the SELECT & INSERT statements.
BEGIN TRANSACTION;
CREATE TABLE item (
count INTEGER NOT NULL,
name TEXT NOT NULL
) STRICT;
END TRANSACTION;§Schema Generation Overview
There are several traits that make up a liter database, but it is actually quite straight-forward.
We’ll start at the top (or root, if you’ll think of it as a tree), and work our way down.
A Schema defines a liter database by its constituent Tables, which is to say it defines the database by the tables it contains.
This trait is implemented by the #[database] proc-macro on a tuple struct declaring the Table types that are part of the database.
The Table trait is implemented by using #[derive(Table)] on a regular struct.
Each row of the generated SQL table (named after the struct) will store an instance of this struct.
You might assume that each field of the struct represents a column in its table, and that is almost correct.
The Value trait is an intermediary layer which represents one or more Columns.
As such, it is implemented for all types that implement Column – which, as you may have guessed, represents a primitive data type that can be stored in a single SQL column – but it is also implemented (and can be implemented by you) for types that require multiple SQL columns.
In fact, a Value can be defined not only as a set of Columns, but as a set of Values.
Though it is admittedly rather generically named, the Value trait is an important abstraction that allows defining database tables with reusable & composable components rather than just column primitives.
For instance, it enables easy foreign key references through the generic Ref struct, even to tables with composite primary keys.
§Example with Primary & Foreign Keys
This slightly more complicated example showcases foreign key references & composite primary keys.
use liter::{database, Table, Id, Ref};
#[database]
struct Dictionary (
Language,
Word
);
#[derive(Table)]
struct Language {
#[key]
id: Id,
name: String
}
#[derive(Table)]
struct Word {
#[key]
language: Ref<Language>,
#[key]
word: String,
definition: String
}
let dict = Dictionary::create_in_memory()?;
let mut lang = Language {
id: Id::NULL,
name: "Latin".to_string()
};
dict.create(&mut lang)?; // assigns the newly created Id
let word = Word {
language: Ref::make_ref(&lang),
word: "nunc".to_string(),
definition:
"now, at present, at this time, at this very moment".to_string()
};
dict.insert(&word)?;Note that this time, a few more SQL statements were generated as part of the HasKey impls for Language & Word.
And here’s the generated schema:
BEGIN TRANSACTION;
CREATE TABLE language (
id INTEGER NOT NULL,
name TEXT NOT NULL,
PRIMARY KEY ( id )
) STRICT;
CREATE TABLE word (
language INTEGER NOT NULL,
word TEXT NOT NULL,
definition TEXT NOT NULL,
PRIMARY KEY ( language, word ),
FOREIGN KEY (language) REFERENCES language
ON UPDATE RESTRICT
ON DELETE RESTRICT
DEFERRABLE INITIALLY DEFERRED
) STRICT;
END TRANSACTION;Re-exports§
pub use column::Column;pub use schema::Schema;pub use table::Entry;pub use table::HasKey;pub use table::Table;pub use types::Bind;pub use types::Binder;pub use types::Fetch;pub use value::Value;
Modules§
- column
- Data primitives – a
Columndefined byAffinity&Checks - meta
- Various meta-programming traits for
Schemavalidation - schema
- The
Schemais a set ofTables and defines aDatabase - table
- SQL
Tables defined at compile-time - types
- Traits and helpers to
Bindto statements andFetchfrom rows - util
- Re-exports of dependencies used by proc-macros
- value
- Datatypes that consist of one or more
Columns and make upTables