Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ name = "geekorm-example-foreignkeys"
path = "./examples/foreignkeys/src/main.rs"
required-features = ["new", "helpers"]

[[example]]
name = "geekorm-example-one-to-many"
path = "./examples/one-to-many/src/main.rs"
required-features = ["new", "helpers", "rand", "libsql"]

[[example]]
name = "geekorm-example-turso-libsql"
path = "./examples/turso-libsql/src/main.rs"
Expand Down
15 changes: 15 additions & 0 deletions examples/one-to-many/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "one-to-many"
version.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
keywords.workspace = true
categories.workspace = true
documentation.workspace = true
repository.workspace = true
authors.workspace = true

[dependencies]
geekorm = { path = "../geekorm", features = ["all", "libsql"] }

86 changes: 86 additions & 0 deletions examples/one-to-many/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use anyhow::Result;

use geekorm::prelude::*;
use geekorm::PrimaryKeyInteger;

#[derive(GeekTable, Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
struct Users {
id: PrimaryKeyInteger,
username: String,

#[geekorm(foreign_key = "Sessions.id")]
#[serde(skip)]
sessions: Vec<Sessions>,
}

#[derive(GeekTable, Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
struct Sessions {
id: PrimaryKeyInteger,

#[geekorm(rand, rand_prefix = "session")]
token: String,
}

#[tokio::main]
async fn main() -> Result<()> {
let conn = init().await?;

let mut user = match Users::query_first(&conn, Users::select_by_username("geekmasher")).await {
Ok(user) => user,
Err(_) => {
let mut user = Users::new("geekmasher");

user.execute_insert(&conn).await?;
user
}
};

// New session
let mut session = Sessions::new();
session.execute_insert(&conn).await?;

// Add session to user
user.sessions.push(session);
// user.execute_update_session(&conn).await?;
user.execute_update(&conn).await?;

println!("{:?}", user);

let mut query_user = Users::query_first(&conn, Users::select_by_primary_key(user.id)).await?;
query_user.fetch_all(&conn).await?;

println!("{:?}", query_user);

Ok(())
}

async fn init() -> Result<libsql::Connection> {
println!(
"{} - v{}\n",
geekorm::GEEKORM_BANNER,
geekorm::GEEKORM_VERSION
);
println!("Turso LibSQL Example\n{:=<40}\n", "=");
let debug_env: bool = std::env::var("DEBUG").is_ok();
env_logger::builder()
.filter_level(if debug_env {
log::LevelFilter::Debug
} else {
log::LevelFilter::Info
})
.init();

// Initialize an in-memory database
// let db = libsql::Builder::new_local(":memory:").build().await?;
let db = libsql::Builder::new_local("/tmp/turso-testing.sqlite")
.build()
.await?;
let conn = db.connect()?;

// TODO: Make this better
let tables = tables!();

tables.create_all(&conn).await?;

Ok(conn)
}
20 changes: 20 additions & 0 deletions geekorm-core/src/builder/columns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ impl Columns {
pub fn get_foreign_keys(&self) -> Vec<&Column> {
self.columns
.iter()
.filter(|col| !col.skip)
.filter(|col| matches!(col.column_type, ColumnType::ForeignKey(_)))
.collect()
}
Expand Down Expand Up @@ -165,6 +166,25 @@ impl Column {
pub fn is_primary_key(&self) -> bool {
self.column_type.is_primary_key()
}

/// Get the foreign key of a column
pub fn foreign_key(&self) -> Option<String> {
match &self.column_type {
ColumnType::ForeignKey(opts) => Some(opts.foreign_key.clone()),
_ => None,
}
}

/// Get the foreign key table name
pub fn foreign_key_table(&self) -> Option<String> {
match &self.column_type {
ColumnType::ForeignKey(opts) => {
let (table, _) = opts.foreign_key.split_once('.').unwrap();
Some(table.to_string())
}
_ => None,
}
}
}

impl Default for Column {
Expand Down
11 changes: 11 additions & 0 deletions geekorm-core/src/builder/columntypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ pub enum ColumnType {
Identifier(ColumnTypeOptions),
/// Foreign Key column type with the table name
ForeignKey(ColumnTypeOptions),
/// One to many column type with options
OneToMany(ColumnTypeOptions),
/// Text column type with options
Text(ColumnTypeOptions),
/// Integer column type with options
Expand All @@ -26,6 +28,7 @@ impl Display for ColumnType {
match self {
ColumnType::Identifier(_) => write!(f, "PrimaryKey"),
ColumnType::ForeignKey(fk) => write!(f, "ForeignKey<{}>", fk),
ColumnType::OneToMany(_) => write!(f, "OneToMany"),
ColumnType::Text(_) => write!(f, "Text"),
ColumnType::Integer(_) => write!(f, "Integer"),
ColumnType::Boolean(_) => write!(f, "Boolean"),
Expand Down Expand Up @@ -76,6 +79,8 @@ impl ToSqlite for ColumnType {
}
format!("BLOB {}", options.on_create(query)?)
}
// Blank
ColumnType::OneToMany(_) => String::new(),
})
}
}
Expand Down Expand Up @@ -189,6 +194,12 @@ impl ToSqlite for ColumnTypeOptions {
}
}

impl From<&ColumnTypeOptions> for String {
fn from(options: &ColumnTypeOptions) -> Self {
options.to_string()
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
52 changes: 46 additions & 6 deletions geekorm-core/src/builder/table.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,28 @@
use crate::{Columns, QueryBuilder, ToSqlite, Values};
use crate::{Columns, Query, QueryBuilder, ToSqlite, Values};
use serde::{Deserialize, Serialize};
use std::fmt::Display;

/// The Table struct for defining a table
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tables {
/// Tables in the database
pub tables: Vec<Table>,
}

impl Tables {
/// Create all tables in the database
#[cfg(feature = "libsql")]
pub async fn create_all(&self, conn: &libsql::Connection) -> Result<(), crate::Error> {
for table in &self.tables {
let query = table.create()?;
conn.execute(query.to_str(), ())
.await
.map_err(|e| crate::Error::LibSQLError(format!("Error creating table: {}", e)))?;
}
Ok(())
}
}

/// The Table struct for defining a table
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Table {
Expand All @@ -27,6 +48,11 @@ impl Table {
.unwrap_or_else(|| String::from("id"))
}

/// Get all foreign keys in the table
pub fn get_foreign_keys(&self) -> Vec<&crate::Column> {
self.columns.get_foreign_keys()
}

/// Get the foreign key by table name
pub fn get_foreign_key(&self, table_name: String) -> &crate::Column {
for column in self.columns.get_foreign_keys() {
Expand All @@ -49,15 +75,28 @@ impl Table {
};
Ok(format!("{}.{}", self.name, name))
}

/// Create Query
pub fn create(&self) -> Result<Query, crate::Error> {
QueryBuilder::create().table(self.clone()).build()
}
}

impl ToSqlite for Table {
fn on_create(&self, query: &QueryBuilder) -> Result<String, crate::Error> {
Ok(format!(
"CREATE TABLE IF NOT EXISTS {} {};",
self.name,
self.columns.on_create(query)?
))
let mut queries = Vec::new();

if query.pivot_tables.is_empty() {
queries.push(format!(
"CREATE TABLE IF NOT EXISTS {} {};",
self.name,
self.columns.on_create(query)?
));
} else {
todo!("Pivot tables are not yet supported");
}

Ok(queries.join("\n"))
}

fn on_select(&self, qb: &QueryBuilder) -> Result<String, crate::Error> {
Expand All @@ -80,6 +119,7 @@ impl ToSqlite for Table {
self.columns
.columns
.iter()
.filter(|col| !col.skip)
.map(|col| col.name.clone())
.collect()
};
Expand Down
56 changes: 44 additions & 12 deletions geekorm-core/src/builder/values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,18 +240,6 @@ impl From<usize> for Value {
}
}

impl From<Vec<String>> for Value {
fn from(value: Vec<String>) -> Self {
Value::Blob(serde_json::to_vec(&value).unwrap())
}
}

impl From<&Vec<String>> for Value {
fn from(value: &Vec<String>) -> Self {
Value::Blob(serde_json::to_vec(value).unwrap())
}
}

impl From<Vec<u8>> for Value {
fn from(value: Vec<u8>) -> Self {
Value::Blob(value)
Expand All @@ -264,6 +252,50 @@ impl From<&Vec<u8>> for Value {
}
}

impl<T> From<std::vec::Vec<T>> for Value
where
T: Into<Value>,
{
fn from(value: std::vec::Vec<T>) -> Self {
Value::Blob(
value
.into_iter()
.map(|value| value.into())
.flat_map(|value| match value {
Value::Text(value) => value.into_bytes(),
Value::Integer(value) => value.to_string().into_bytes(),
Value::Boolean(value) => value.to_string().into_bytes(),
Value::Identifier(value) => value.into_bytes(),
Value::Blob(value) => value,
Value::Null => Vec::new(),
})
.collect(),
)
}
}

impl<T> From<&std::vec::Vec<T>> for Value
where
T: Into<Value> + Clone,
{
fn from(value: &std::vec::Vec<T>) -> Self {
Value::Blob(
value
.iter()
.map(|value| value.clone().into())
.flat_map(|value| match value {
Value::Text(value) => value.into_bytes(),
Value::Integer(value) => value.to_string().into_bytes(),
Value::Boolean(value) => value.to_string().into_bytes(),
Value::Identifier(value) => value.into_bytes(),
Value::Blob(value) => value,
Value::Null => Vec::new(),
})
.collect(),
)
}
}

/// Serialize a Value
impl Serialize for Value {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
Expand Down
2 changes: 2 additions & 0 deletions geekorm-core/src/queries/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ use crate::{
#[derive(Debug, Clone, Default)]
pub struct QueryBuilder {
pub(crate) table: Table,
/// Vector of tables to pivot
pub(crate) pivot_tables: Vec<Table>,
pub(crate) query_type: QueryType,
/// If a query should use aliases
pub(crate) aliases: bool,
Expand Down
2 changes: 2 additions & 0 deletions geekorm-core/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
/// The Cryptography module
pub mod crypto;

pub mod tables;

#[cfg(feature = "rand")]
pub use crypto::rand::generate_random_string;

Expand Down
1 change: 1 addition & 0 deletions geekorm-core/src/utils/tables.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
//! Tables
Loading