A lightweight and extensible ORM framework for Rust.
- Supported Databases
- Getting Started
- Usage
- Getting Help
- Reporting Issues
- Contributing
- Code of Conduct
- Contributors
- License
Database | Support | Status |
---|---|---|
SQLite | ✅ | Currently supported. |
PostgreSQL | ❌ | Not supported, help us impelement it? |
MySQL | ❌ | Not supported, help us impelement it? |
MariaDB | ❌ | Not supported, help us impelement it? |
Oracle | ❌ | Not supported, help us impelement it? |
MSSQL | ❌ | Not supported, help us impelement it? |
The first thing we need to do is generate our project.
cargo new --bin njord_demo
Now, let’s add Njord to our dependencies. We’re also going to use a tool called .env to manage our environment variables for us. We’ll add it to our dependencies as well.
[dependencies]
# The core APIs, including the Table trait.
# using #[derive(Table)] to make njord work with structs
# and enums defined in your crate.
njord = { version = "<version>", features = ["sqlite"] }
dotenvy = "0.15"
Njord provides a separate CLI tool to help manage your project. Since it’s a standalone binary, and doesn’t affect your project’s code directly, we don’t add it to Cargo.toml
. Instead, we just install it on our system.
cargo install njord --no-default-features --features "sqlite"
SQlite is per default, so the above command only illustrates how you could use a different feature such as postgres
, mysql
, mariadb
, oracle
and mssql
etc. Note that these others are not done yet. Will come in future release!
We now use the setup command of the Njord CLI application to create our .toml
file as well as an initial migrations directory and two .sql
files.
njord setup
Two empty files now exists in a directory called migrations
.
migrations/00000000000000_njord_initial_setup/up.sql
migrations/00000000000000_njord_initial_setup/down.sql
We also get a njord.toml
file which is the configuration file where to load our schema.rs
file and where to store our migrations directory. Default for the schema file is under src/schema.rs
and migrations in the root directory.
# For documentation on how to configure this file,
# see https://njord.rs
[schema]
file = "src/schema.rs"
[migrations_directory]
dir = "migrations"
Now we are going to define our schema file that we will create under src/schema.rs
. We will store basically our structs that will map against the database.
#[derive(Table, Default)]
pub struct User {
user_id: usize,
username: String,
email: String,
address: String,
}
#[derive(Table, Default)]
pub struct Product {
product_id: usize,
name: String,
description: String,
price: f64,
stock_quantity: usize,
category: String,
}
#[derive(Table, Default)]
pub struct Order {
order_id: usize,
user_id: usize,
products: Vec<Product>,
total_cost: f64,
order_date: NaiveDateTime,
}
Now that we have that in place, we need to create the SQL for setting this up in the database so go to migrations/00000000000000_njord_initial_setup
and open up first up.sql
and add the following.
-- users table
CREATE TABLE users (
user_id INTEGER PRIMARY KEY,
username TEXT NOT NULL,
email TEXT NOT NULL,
address TEXT NOT NULL
);
-- products table
CREATE TABLE products (
product_id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
description TEXT NOT NULL,
price REAL NOT NULL,
stock_quantity INTEGER NOT NULL,
category TEXT NOT NULL
);
-- orders table
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
user_id INTEGER REFERENCES users(user_id),
total_cost REAL NOT NULL,
order_date TEXT NOT NULL
);
-- order_products table
CREATE TABLE order_products (
order_id INTEGER REFERENCES orders(order_id),
product_id INTEGER REFERENCES products(product_id),
PRIMARY KEY (order_id, product_id)
);
Now we need to also setup a down.sql
file that will revert back the change.
-- Drop tables in reverse order to handle foreign key constraints
DROP TABLE IF EXISTS order_products;
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS products;
DROP TABLE IF EXISTS users;
Now we are going to generate a new migration so we can update existing schema.
njord migration generate --name=update_something --env=development
Now we should have the following in our migrations
directory.
migrations/00000000000001_update_something/up.sql
migrations/00000000000001_update_something/down.sql
Let's make a change. Open up the up.sql
file and add the following.
-- products table: Add discount column
ALTER TABLE products ADD COLUMN discount REAL;
And we also need to define how we can revert these changes by adding the following.
-- products table: Remove discount column
CREATE TEMPORARY TABLE products_backup AS SELECT * FROM products;
-- Drop original products table
DROP TABLE products;
-- Recreate products table without the discount column
CREATE TABLE products (
product_id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
description TEXT NOT NULL,
price REAL NOT NULL,
stock_quantity INTEGER NOT NULL,
category TEXT NOT NULL
);
-- Restore data from backup
INSERT INTO products SELECT * FROM products_backup;
-- Drop temporary backup table
DROP TABLE products_backup;
Finally we need to modify our schema.rs
file so it will map correctly.
#[derive(Table, Default)]
pub struct Product {
product_id: usize,
name: String,
description: String,
price: f64,
stock_quantity: usize,
category: String,
discount: Option<f64>, // We added a new column here
}
We apply the newly created migration files by running migration run
command with the --env
flag for which target enviroment and we can also set a --log-level
flag to get either more or less log output.
njord migration run --env=development --log-level=debug
To rollback to last previous change we can do that by running the rollback
command and specifying which enviroment we are in.
njord rollback --env=development --to=00000000000000
To rollback to a specific change we can to that by adding the flag --to
with its ID.
njord rollback --env=development --to=00000000000000
So how can we establish a connection and actually select or insert data to our database? Let's go through it. Note that these examples might be outdated, so dont treat it as a source of truth.
To establish a connection we first need to call the sqlite::open()
function and use it with a match statement.
fn main () {
let db_name = "njord.db";
match sqlite::open(db_name) {
Ok(conn) => {
println!("Database opened successfully!");
// additional logic when we are connected...
}
Err(err) => eprintln!("Error opening the database: {}", err),
}
}
fn main () {
let db_name = "njord.db";
let user = User {
user_id: 1,
username: String::from("john_doe"),
email: String::from("john@example.com"),
address: String::from("123 Main St"),
};
match sqlite::open(db_name) {
Ok(conn) => {
println!("Database opened successfully!");
let result = sqlite::insert(conn, &user);
assert!(result.is_ok());
}
Err(err) => eprintln!("Error opening the database: {}", err),
}
}
use njord::table::Table;
use njord_derive::Table;
mod schema;
use schema::User;
fn main () {
let db_name = "njord.db";
// SELECT
let columns = vec!["user_id".to_string(), "username".to_string(), "email".to_string(), "address".to_string()];
// WHERE
let where_condition = Condition::Eq("username".to_string(), "john_doe".to_string());
// GROUP BY
let group_by = vec!["username".to_string(), "address".to_string()];
// ORDER BY
let mut order_by = HashMap::new();
order_by.insert(vec!["user_id".to_string()], "ASC".to_string());
order_by.insert(vec!["email".to_string()], "DESC".to_string());
// HAVING
let having_condition = Condition::Gt("user_id".to_string(), "1".to_string());
match sqlite::open(db_name) {
Ok(conn) => {
println!("Database opened successfully!");
// Build the query
// We need to pass the struct User with the Default trait in .from()
let result: Result<Vec<User>> = sqlite::select(conn, columns)
.from(User::default())
.where_clause(where_condition)
.order_by(order_by)
.group_by(group_by)
.having(having_condition)
.build();
// Match the result
match result {
Ok(result) => {
assert_eq!(result.len(), 1);
}
Err(error) => panic!("Failed to SELECT: {:?}", error),
};
}
Err(err) => eprintln!("Error opening the database: {}", err),
}
}
Are you having trouble with Njord? We want to help!
-
Read through the documentation on our docs.
-
If you are upgrading, read the release notes for upgrade instructions and "new and noteworthy" features.
-
Ask a question we monitor stackoverflow.com for questions tagged with Njord.
-
Report bugs with Njord at https://github.com/mjovanc/njord/issues.
Njord uses GitHub’s integrated issue tracking system to record bugs and feature requests. If you want to raise an issue, please follow the recommendations below:
-
Before you log a bug, please search the issue tracker to see if someone has already reported the problem.
-
If the issue doesn’t already exist, create a new issue.
-
Please provide as much information as possible with the issue report. We like to know the Njord version, operating system, and Rust version version you’re using.
-
If you need to paste code or include a stack trace, use Markdown. ``` escapes before and after your text.
-
If possible, try to create a test case or project that replicates the problem and attach it to the issue.
Before contributing, please read the contribution guide for useful information how to get started with Njord as well as what should be included when submitting a contribution to the project.
Anyone who interacts with Njord in any space, including but not limited to this GitHub repository, must follow our code of conduct.
The following contributors have either helped to start this project, have contributed code, are actively maintaining it (including documentation), or in other ways being awesome contributors to this project. We'd like to take a moment to recognize them.
The GPLv3 License.