diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 08050be0..99536bd9 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -65,12 +65,12 @@ jobs: - name: Running Integration Tests for SQLite run: cargo test --test sqlite_tests -# - name: Set up MySQL schema -# env: -# MYSQL_PWD: rootpassword -# run: | -# echo "Injecting schema and data into MySQL..." -# mysql -h 127.0.0.1 -u root njord_db < njord/db/test/mysql.sql + - name: Set up MySQL schema + env: + MYSQL_PWD: rootpassword + run: | + echo "Injecting schema and data into MySQL..." + mysql -h 127.0.0.1 -u root njord_db < njord/db/test/mysql.sql - name: Running Unit Tests for MySQL run: cargo test mysql diff --git a/docker-compose.yml b/docker-compose.yml index a55753d8..99c46843 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,6 +14,7 @@ services: volumes: - mysql_data:/var/lib/mysql - ./njord_examples/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql + - ./njord/db/test/mysql.sql:/docker-entrypoint-initdb.d/tests.sql volumes: mysql_data: diff --git a/njord/db/test/mysql.sql b/njord/db/test/mysql.sql index e69de29b..f2468ce3 100644 --- a/njord/db/test/mysql.sql +++ b/njord/db/test/mysql.sql @@ -0,0 +1,31 @@ +DROP DATABASE IF EXISTS njord_db; + +CREATE DATABASE njord_db; + +USE njord_db; + +-- Table: users +CREATE TABLE users ( + id INT AUTO_INCREMENT PRIMARY KEY, -- Auto incrementing primary key for the user ID + username VARCHAR(255) NOT NULL, -- Username field + email VARCHAR(255) NOT NULL, -- Email field + address VARCHAR(255) -- Address field +); + +-- Table: categories +CREATE TABLE categories ( + id INT PRIMARY KEY, -- Primary key for categories + name VARCHAR(255) NOT NULL -- Name of the category +); + +-- Table: products +CREATE TABLE products ( + id INT PRIMARY KEY, -- Primary key for products + name VARCHAR(255) NOT NULL, -- Product name + description TEXT, -- Product description + price DECIMAL(10, 2) NOT NULL, -- Price with up to two decimal places + stock_quantity INT NOT NULL, -- Stock quantity + category_id INT NOT NULL, -- Foreign key to categories (one-to-one relationship) + discount DECIMAL(5, 2) DEFAULT 0.00, -- Discount field with default value + FOREIGN KEY (category_id) REFERENCES categories(id) -- Foreign key constraint to categories table +); diff --git a/njord/src/mysql/select.rs b/njord/src/mysql/select.rs index 9387e9a0..d614571e 100644 --- a/njord/src/mysql/select.rs +++ b/njord/src/mysql/select.rs @@ -42,7 +42,7 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc}; use log::info; use mysql::prelude::*; -use mysql::{prelude::FromRow, Error, PooledConn}; +use mysql::{Error, PooledConn, Value}; use crate::table::Table; use crate::util::{Join, JoinType}; @@ -57,7 +57,7 @@ use crate::util::{Join, JoinType}; /// # Returns /// /// A `SelectQueryBuilder` instance. -pub fn select<'a, T: Table + Default + FromRow>( +pub fn select<'a, T: Table + Default>( conn: &'a mut PooledConn, columns: Vec>, ) -> SelectQueryBuilder<'a, T> { @@ -66,7 +66,7 @@ pub fn select<'a, T: Table + Default + FromRow>( /// A builder for constructing SELECT queries. #[derive(Clone)] -pub struct SelectQueryBuilder<'a, T: Table + Default + FromRow> { +pub struct SelectQueryBuilder<'a, T: Table + Default> { conn: Rc>, table: Option, columns: Vec>, @@ -82,7 +82,7 @@ pub struct SelectQueryBuilder<'a, T: Table + Default + FromRow> { joins: Option>>, } -impl<'a, T: Table + Default + FromRow> SelectQueryBuilder<'a, T> { +impl<'a, T: Table + Default> SelectQueryBuilder<'a, T> { /// Creates a new `SelectQueryBuilder` instance. /// /// # Arguments @@ -366,10 +366,46 @@ impl<'a, T: Table + Default + FromRow> SelectQueryBuilder<'a, T> { info!("{}", final_query); println!("{}", final_query); - // Borrow the connection mutably from the RefCell let mut conn = self.conn.borrow_mut(); + let query_set = conn.query_iter(final_query.as_str()).unwrap(); + + let mut results: Vec = Vec::new(); + + for row_result in query_set { + let row = row_result.unwrap(); // Unwrap the row result + let mut instance = T::default(); + + for column in row.columns_ref() { + // Cells in a row can be indexed by numeric index or by column name + let column_value = &row[column.name_str().as_ref()]; + + let column_value_str = match column_value { + Value::NULL => "NULL".to_string(), + Value::Bytes(bytes) => String::from_utf8_lossy(bytes).to_string(), + Value::Int(i) => i.to_string(), + Value::UInt(u) => u.to_string(), + Value::Float(f) => f.to_string(), + Value::Double(d) => d.to_string(), + Value::Date(year, month, day, hour, min, sec, micro) => format!( + "{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:06}", + year, month, day, hour, min, sec, micro + ), + Value::Time(neg, days, hours, minutes, seconds, micros) => format!( + "{}{:02}:{:02}:{:02}.{:06}", + if *neg { "-" } else { "" }, + days * 24 + u32::from(*hours), + minutes, + seconds, + micros + ), + }; + + instance.set_column_value(column.name_str().as_ref(), &column_value_str); + } - let results: Vec = conn.query(final_query.as_str())?; + // Move `instance` to the `results` only after it is fully set up + results.push(instance); + } Ok(results) } @@ -380,7 +416,7 @@ impl<'a, T: Table + Default + FromRow> SelectQueryBuilder<'a, T> { /// The where statement ensures the T is long lived impl<'a, T> QueryBuilder<'a> for SelectQueryBuilder<'a, T> where - T: Table + Default + Clone + FromRow + 'a, // Added 'a bound here + T: Table + Default + Clone + 'a, // Added 'a bound here { fn to_sql(&self) -> String { self.build_query() diff --git a/njord/tests/mysql/insert_test.rs b/njord/tests/mysql/insert_test.rs new file mode 100644 index 00000000..c64fe1a9 --- /dev/null +++ b/njord/tests/mysql/insert_test.rs @@ -0,0 +1,27 @@ +use super::User; +use njord::keys::AutoIncrementPrimaryKey; +use njord::mysql; +use std::vec; + +#[test] +fn insert_row() { + let url = "mysql://njord_user:njord_password@localhost:3306/njord_db"; + let mut conn = mysql::open(url); + + let table_row: User = User { + id: AutoIncrementPrimaryKey::default(), + username: "mjovanc".to_string(), + email: "mjovanc@icloud.com".to_string(), + address: "Some Random Address 1".to_string(), + }; + + match conn { + Ok(ref mut c) => { + let result = mysql::insert(c, vec![table_row]); + assert!(result.is_ok()); + } + Err(e) => { + panic!("Failed to INSERT: {:?}", e); + } + } +} diff --git a/njord/tests/mysql/mod.rs b/njord/tests/mysql/mod.rs index e69de29b..9ffb8d1c 100644 --- a/njord/tests/mysql/mod.rs +++ b/njord/tests/mysql/mod.rs @@ -0,0 +1,51 @@ +mod insert_test; + +use njord::keys::{AutoIncrementPrimaryKey, PrimaryKey}; +use njord::table::Table; +use njord_derive::Table; + +#[derive(Table, Clone)] +#[table_name = "users"] +pub struct User { + pub id: AutoIncrementPrimaryKey, + pub username: String, + pub email: String, + pub address: String, +} + +#[derive(Table)] +#[table_name = "users"] +pub struct UserWithSubQuery { + pub id: AutoIncrementPrimaryKey, + pub username: String, + pub email: String, + pub address: String, + pub additional_address: String, +} + +#[derive(Table)] +#[table_name = "categories"] +pub struct Category { + pub id: PrimaryKey, + pub name: String, +} + +#[derive(Table)] +#[table_name = "products"] +pub struct Product { + pub id: PrimaryKey, + pub name: String, + pub description: String, + pub price: f64, + pub stock_quantity: usize, + pub category: Category, // one-to-one relationship + pub discount: f64, +} + +#[derive(Table)] +#[table_name = "users"] +pub struct UsersWithJoin { + username: String, + price: f64, + name: String, +} diff --git a/njord_examples/mysql/src/main.rs b/njord_examples/mysql/src/main.rs index fc832b0f..370b6761 100644 --- a/njord_examples/mysql/src/main.rs +++ b/njord_examples/mysql/src/main.rs @@ -1,6 +1,7 @@ use std::fmt::Error; use crate::schema::NearEarthObject; +use njord::column::Column; use njord::keys::AutoIncrementPrimaryKey; use njord::mysql; use reqwest::header::ACCEPT; @@ -26,6 +27,29 @@ pub struct NearEarthObjectResponse { #[tokio::main] async fn main() -> Result<(), Error> { + let _ = insert().await; + let _ = select(); + + Ok(()) +} + +fn select() -> Result<(), Box> { + let url = "mysql://njord_user:njord_password@localhost:3306/njord_db"; + let mut conn = mysql::open(url).unwrap(); + + let results = mysql::select(&mut conn, vec![Column::Text("id".to_string())]) + .from(NearEarthObject::default()) + .build(); + + match results { + Ok(data) => println!("Selected: {:#?}", data.len()), + Err(err) => eprintln!("Error: {}", err), + } + + Ok(()) +} + +async fn insert() -> Result<(), Box> { let neo = get_near_earth_objects(0, 10).await; let mut near_earth_objects: Vec = Vec::new();