Coder Social home page Coder Social logo

njord-rs / njord Goto Github PK

View Code? Open in Web Editor NEW
257.0 1.0 13.0 6.85 MB

A lightweight ORM library for Rust ⛵

Home Page: https://njord.rs

License: BSD 3-Clause "New" or "Revised" License

Rust 100.00%
rust rustlang orm orm-library rust-library sqlite njord njord-orm

njord's Introduction

njord

Njord

build crates.io njord: rustc 1.77.1+ njord_derive: rustc 1.77+ discord

A lightweight and extensible ORM library for Rust.

Table of Contents

Supported Databases

Database Support Status
SQLite Currently supported.
PostgreSQL Not supported, help us implement it?
MySQL Not supported, help us implement it?
MariaDB Not supported, help us implement it?
Oracle Not supported, help us implement it?
MSSQL Not supported, help us implement it?

Getting Started

Initializing a new project

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"] }
njord_derive = { version = "<version>" }

Add a schema file

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)]
#[table_name = "users"]
pub struct User {
    id: usize,
    username: String,
    email: String,
    address: String,
}

#[derive(Table)]
#[table_name = "categories"]
pub struct Category {
    id: usize,
    name: String,
}

#[derive(Table)]
#[table_name = "products"]
pub struct Product {
    id: usize,
    name: String,
    description: String,
    price: f64,
    stock_quantity: usize,
    category: Category,     // one-to-one relationship
    discount: Option<f64>,  // allow for null 
}

#[derive(Table)]
#[table_name = "orders"]
pub struct Order {
    id: usize,
    user: User,             // one-to-one relationship
    products: Vec<Product>, // one-to-many relationship - populates from based on junction table (gets from macro attribute "table_name" and combines them for example, orders_products)
    total_cost: f64,
}

Now that we have that in place, we need to create the SQL for setting this up in the database and execute it.

-- users table
CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    username TEXT NOT NULL,
    email TEXT NOT NULL,
    address TEXT NOT NULL
);

-- products table
CREATE TABLE products (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    description TEXT NOT NULL,
    price REAL NOT NULL,
    stock_quantity INTEGER NOT NULL,
    category INTEGER REFERENCES categories(id),
    discount: REAL NULL
);

-- orders table
CREATE TABLE orders (
    id INTEGER PRIMARY KEY,
    user_id INTEGER REFERENCES users(id),
    total_cost REAL NOT NULL
);

-- order_products table
CREATE TABLE order_products (
    order_id INTEGER REFERENCES orders(id),
    product_id INTEGER REFERENCES products(id),
    PRIMARY KEY (order_id, product_id)
);

Usage

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.

SQlite

Establish a connection

To establish a connection we first need to call the sqlite::open() function and use it with a match statement.

let db_name = "njord.db";
let db_path = Path::new(&db_name);

match sqlite::open(db_path) {
    Ok(c) => {
        println!("Database opened successfully!");
        
        // Additional logic when we are connected.
        // We need to open a connection and pass it 
        // to the corresponding sqlite function.
    }
    Err(err) => eprintln!("Error opening the database: {}", err),
}

Insert data

let user = User {
    username: String::from("john_doe"),
    email: String::from("[email protected]"),
    address: String::from("123 Main St"),
};

let result = sqlite::insert(c, vec![user]);
assert!(result.is_ok());

Generated SQL

INSERT INTO users (
    username,
    email,
    address
) VALUES (
    'john_doe',
    '[email protected]',
    '123 Main St'
)

Update data

let columns = vec!["username".to_string(), "address".to_string()];
let where_condition = Condition::Eq("username".to_string(), "john_doe".to_string());

let user = User {
    username: String::from("john_doe_2"),
    email: String::from("[email protected]"),
    address: String::from("1234 Main St"),
};

let mut order = HashMap::new();
order.insert(vec!["id".to_string()], "DESC".to_string());
        
let result = sqlite::update(c, user)
    .set(columns)
    .where_clause(where_condition)
    .order_by(order)
    .limit(4)
    .offset(0)
    .build();

assert!(result.is_ok());

Generated SQL

UPDATE users
SET
    username
WHERE
    username = 'john_doe'
ORDER BY
    id DESC
LIMIT 4
OFFSET 0

Delete data

let where_condition = Condition::Eq("username".to_string(), "john_doe".to_string());

let mut order = HashMap::new();
order.insert(vec!["id".to_string()], "DESC".to_string());

let result = sqlite::delete(c)
    .from(User::default())
    .where_clause(where_condition)
    .order_by(order)
    .limit(20)
    .offset(0)
    .build();

assert!(result.is_ok());

Generated SQL

DELETE FROM users
WHERE
    username = 'john_doe'
ORDER BY
    id DESC
LIMIT 20
OFFSET 0

Select data

let columns = vec!["id".to_string(), "username".to_string(), "email".to_string(), "address".to_string()];
let where_condition = Condition::Eq("username".to_string(), "john_doe".to_string());
let group_by = vec!["username".to_string(), "address".to_string()];

let mut order_by = HashMap::new();
order_by.insert(vec!["id".to_string()], "ASC".to_string());

let having_condition = Condition::Gt("id".to_string(), "1".to_string());

// Build the query
// We need to pass the struct User with the Default trait in .from()
let result: Result<Vec<User>> = sqlite::select(c, columns)
    .from(User::default())
    .where_clause(where_condition)
    .order_by(order_by)
    .group_by(group_by)
    .having(having_condition)
    .build();

match result {
    Ok(result) => {
        assert_eq!(result.len(), 1);
    }
    Err(error) => panic!("Failed to SELECT: {:?}", error),
};

Generated SQL

SELECT
    id,
    username,
    email,
    address
FROM
    users
WHERE
    username = 'mjovanc'
GROUP BY
    username,
    email
HAVING
    id > 1
ORDER BY
    email DESC

Getting Help

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.

Reporting 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.

Contributing

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.

Code of Conduct

Anyone who interacts with Njord in any space, including but not limited to this GitHub repository, must follow our code of conduct.

Contributors

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.

mjovanc appelskrutt34

License

The BSD 3-Clause License.

njord's People

Contributors

ahsentekd avatar appelskrutt34 avatar chinmer avatar dependabot[bot] avatar mjovanc avatar renovate[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

njord's Issues

Have a feature for each database

Would be nice that we can activate features with what database support is needed like this:

njord = { version = "0.1.0", features = ["derive", "sqlite", "mysql", "postgres"] }

id field should be possible to set as Option<T>

We need to implement in the procedural macro so that id fields can be used with Option<T> since we want the user to control if they want to populate the id field when doing an INSERT operation. If you want SQlite to handle autoincrement itself, we don't need to add a value to it.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

This repository currently has no open or pending branches.

Detected dependencies

cargo
Cargo.toml
  • rusqlite 0.29.0
github-actions
.github/workflows/master-ci.yml
  • actions/checkout v3
  • actions-rs/toolchain v1
.github/workflows/pr-ci.yml
  • actions/checkout v3
  • actions-rs/toolchain v1

  • Check this box to trigger a request for Renovate to run again on this repository

Create one-to-many relationship for SQLite

We want to add the possibility to add junction tables for a MANY-TO-MANY relationship if we define our tables such as:

#[derive(Table, Default)]
struct Posts {
    title: String,
    description: String,
    category: Categories
}

#[derive(Table, Default)]
struct Categories {
    name: String,
}

Add UPDATE

UPDATE table_name
SET column1 = value1, column2 = value2, ...
WHERE condition;

Add method chaining for select for sqlite

We need to have method chaining for SELECT since we can have multiple conditions. An example of a use case would be:

sqlite::select(columns).from(table).where(condition);

Build a CLI for running schema changes, migration etc

Commands that should exist:

njord migration generate <name>
njord migration run
njord migration rollback

Flags that should exist on njord:

  • -v and --version
  • -h and --help

Flags that should exist on njord migration:

  • --dry-run (simulate the migration)
  • --env (specify development/test/staging/production etc)
  • --dir (specify target directory for generated migration changes)
  • --to (rollback to specific version, for example 20191225120000)
  • --log-level (standard, debug etc)

Examples:

njord setup
njord migration generate <name> --env=development --dry-run
njord migration run --env=production --log-level=debug
njord migration rollback --env=development --to=20191225120000

Add JOIN

INNER JOIN, LEFT JOIN, RIGHT JOIN, and FULL JOIN to combine rows from two or more tables based on a related column between them.

Create custom field type: PrimaryKey

Just thinking of creating a custom field type called PrimaryKey to add to a field for example id: https://github.com/njord-rs/njord/blob/master/njord/tests/sqlite_test.rs#L15

Some example implementation:

pub struct PrimaryKey<T>(Option<T>);

impl<T> PrimaryKey<T> {
    pub fn new(value: Option<T>) -> Self {
        PrimaryKey(value)
    }

    pub fn get(&self) -> Option<&T> {
        self.0.as_ref()
    }
}

impl<T> Default for PrimaryKey<T> {
    fn default() -> Self {
        PrimaryKey(None)
    }
}

Usage example:

let table_row: User = User {
    id: PrimaryKey::default(),
    username: "mjovanc".to_string(),
    email: "[email protected]".to_string(),
    address: "Some Random Address 1".to_string(),
};

When setting it to ::default we are saying that we want the database to increment the id field automatically using autoincrement.

Fix one-to-many relationship for SQLite

We need to be able to fix the possibility of storing a vector of structs that derive the Table procedural macro:

#[derive(Table)]
#[table_name = "orders"]
pub struct Order {
    id: usize,
    user: User,             // one-to-one relationship
    products: Vec<Product>, // one-to-many relationship - populates from based on junction table (gets from macro attribute "table_name" and combines them for example, orders_products)
    total_cost: f64,
}

As you can see here when we define a field called products and add a vector of Product. It will complain about:

- doesn't satisfy `syn::Type: ToString` or `syn::Type: std::fmt::Display`

We need to be able to support this in the njord_derive library.

Add feature for dependency

Would be great if we could add features like below to not need to include the njord_derive in the dependencies list:

njord = { version = "0.1.0", features = ["derive"] }

Fix id field during instantiation of Table derived struct based on proc attribute

We need to add #[primary_key] or something similar over the field like this:

#[derive(Table)]
#[table_name = "categories"]
pub struct Category {
    #[primary_key]
    id: usize,
    name: String,
}

So when instantiating it, it should be enough to do:

let table_row: User = User {
        name: "mjovanc".to_string(),
    };

This is important since when doing INSERT operations when having autoincrement on the id field and not knowing the current id beforehand, and we definitely do not want to query to get that information before...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.