7 releases
0.0.7 | Jul 30, 2024 |
---|---|
0.0.6 | Jan 20, 2024 |
0.0.5 | Dec 10, 2023 |
0.0.4 | Nov 18, 2023 |
0.0.1 | Apr 13, 2023 |
#1123 in Database interfaces
Used in 2 crates
(via cnsprcy)
59KB
1.5K
SLoC
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]);
# Ok::<(), rusqlite::Error>(())
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 Table
s, 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 Column
s.
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 Column
s, but as a set of Value
s.
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)?;
# Ok::<(), rusqlite::Error>(())
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;
Dependencies
~23MB
~434K SLoC