Crate liter

Crate liter 

Source
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 Column defined by Affinity & Checks
meta
Various meta-programming traits for Schema validation
schema
The Schema is a set of Tables and defines a Database
table
SQL Tables defined at compile-time
types
Traits and helpers to Bind to statements and Fetch from rows
util
Re-exports of dependencies used by proc-macros
value
Datatypes that consist of one or more Columns and make up Tables

Structs§

Database
Id
Ref

Attribute Macros§

database

Derive Macros§

Table
Value