diff --git a/CHANGELOG.md b/CHANGELOG.md index 81487f95..0f2ff67b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,8 @@ - add subcommand to generate shell completions - function `generate_files` now takes in `&Path`s instead of `PathBuf`s - remove `to_singular` name generations -- add option `--create-str` to set `Create*` structs string type -- add option `--update-str` to set `Update*` structs string type +- add option `--create-str` to set `Create*` structs string & byte types +- add option `--update-str` to set `Update*` structs string & byte type - add option `--single-model-file` to only generate a single file instead of a directory with `mod.rs` and `generated.rs` - add option `--readonly-prefix` and `--readonly-suffix` to treat a matching name as a readonly struct - add option `--no-crud` to not generate any `impl` blocks diff --git a/src/bin/main.rs b/src/bin/main.rs index 574b4098..ea4adf11 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,7 +1,7 @@ use clap::{CommandFactory, Parser, Subcommand, ValueEnum}; use clap_complete::{generate, Shell}; use dsync::{error::IOErrorToError, GenerationConfig, TableOptions}; -use dsync::{FileChangeStatus, StringType}; +use dsync::{BytesType, FileChangeStatus, StringType}; use std::collections::HashMap; use std::io::{BufWriter, Write}; use std::path::PathBuf; @@ -98,6 +98,14 @@ pub struct MainOptions { #[arg(long = "update-str", default_value = "string")] pub update_str: StringTypeCli, + /// Set which bytes type to use for Create* structs + #[arg(long = "create-bytes", default_value = "vec")] + pub create_bytes: BytesTypeCli, + + /// Set which bytes type to use for Update* structs + #[arg(long = "update-bytes", default_value = "vec")] + pub update_bytes: BytesTypeCli, + /// Only Generate a single model file instead of a directory with "mod.rs" and "generated.rs" #[arg(long = "single-model-file")] pub single_model_file: bool, @@ -140,6 +148,27 @@ impl From for StringType { } } +#[derive(Debug, ValueEnum, Clone, PartialEq, Default)] +pub enum BytesTypeCli { + /// Use "Vec" + #[default] + Vec, + /// Use "&[u8]" + Slice, + /// Use "Cow<[u8]>" + Cow, +} + +impl From for BytesType { + fn from(value: BytesTypeCli) -> Self { + match value { + BytesTypeCli::Vec => BytesType::Vec, + BytesTypeCli::Slice => BytesType::Slice, + BytesTypeCli::Cow => BytesType::Cow, + } + } +} + fn main() { let res = actual_main(); @@ -183,7 +212,9 @@ fn actual_main() -> dsync::Result<()> { let mut default_table_options = TableOptions::default() .autogenerated_columns(cols.iter().map(|t| t.as_str()).collect::>()) .create_str_type(args.create_str.into()) - .update_str_type(args.update_str.into()); + .update_str_type(args.update_str.into()) + .create_bytes_type(args.create_bytes.into()) + .update_bytes_type(args.update_bytes.into()); #[cfg(feature = "tsync")] if args.tsync { diff --git a/src/code.rs b/src/code.rs index 47c2ab95..36b27df9 100644 --- a/src/code.rs +++ b/src/code.rs @@ -291,12 +291,22 @@ impl<'a> Struct<'a> { } let lifetimes = { - let lifetimes = match self.ty { + let s_lifetimes = match self.ty { StructType::Read => "", StructType::Update => self.opts.get_update_str_type().get_lifetime(), StructType::Create => self.opts.get_create_str_type().get_lifetime(), }; + let b_lifetimes = match self.ty { + StructType::Read => "", + StructType::Update => self.opts.get_update_bytes_type().get_lifetime(), + StructType::Create => self.opts.get_create_bytes_type().get_lifetime(), + }; + let lifetimes = [s_lifetimes, b_lifetimes] + .iter() + .copied() + .max_by_key(|l| l.len()) + .unwrap_or(""); if lifetimes.is_empty() { String::new() } else { @@ -314,6 +324,12 @@ impl<'a> Struct<'a> { StructType::Update => self.opts.get_update_str_type().as_str().to_string(), StructType::Create => self.opts.get_create_str_type().as_str().to_string(), } + } else if f.base_type == "Vec" { + f.base_type = match self.ty { + StructType::Read => f.base_type, + StructType::Update => self.opts.get_update_bytes_type().as_str().to_string(), + StructType::Create => self.opts.get_create_bytes_type().as_str().to_string(), + } } let mut field_type = f.to_rust_type(); diff --git a/src/lib.rs b/src/lib.rs index 6b8bc6e1..d269d1fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,6 +57,38 @@ impl StringType { } } +/// Available options for bytes types +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub enum BytesType { + /// Use `Vec` + #[default] + Vec, + /// Use `&[u8]` + Slice, + /// Use `Cow<[u8]>` + Cow, +} + +impl BytesType { + /// Get the current [BytesType] as a rust type string + pub fn as_str(&self) -> &'static str { + match self { + BytesType::Vec => "Vec", + BytesType::Slice => "&'a [u8]", + BytesType::Cow => "Cow<'a, [u8]>", + } + } + + /// Get the lifetime used for the current [BytesType] + pub fn get_lifetime(&self) -> &'static str { + match self { + BytesType::Vec => "", + BytesType::Slice => "'a", + BytesType::Cow => "'a", + } + } +} + /// Options for a individual table #[derive(Debug, Clone)] pub struct TableOptions<'a> { @@ -85,6 +117,12 @@ pub struct TableOptions<'a> { /// Determines which string type to use for Update* structs update_str_type: StringType, + /// Determines which bytes type to use for Create* structs + create_bytes_type: BytesType, + + /// Determines which bytes type to use for Update* structs + update_bytes_type: BytesType, + /// Only Generate a single model file instead of a directory with "mod.rs" and "generated.rs" single_model_file: bool, @@ -123,6 +161,14 @@ impl<'a> TableOptions<'a> { self.update_str_type } + pub fn get_create_bytes_type(&self) -> BytesType { + self.create_bytes_type + } + + pub fn get_update_bytes_type(&self) -> BytesType { + self.update_bytes_type + } + pub fn get_autogenerated_columns(&self) -> &[&'_ str] { self.autogenerated_columns.as_deref().unwrap_or_default() } @@ -193,6 +239,20 @@ impl<'a> TableOptions<'a> { } } + pub fn create_bytes_type(self, type_: BytesType) -> Self { + Self { + create_bytes_type: type_, + ..self + } + } + + pub fn update_bytes_type(self, type_: BytesType) -> Self { + Self { + update_bytes_type: type_, + ..self + } + } + pub fn set_read_only(&mut self, value: bool) { self.read_only = value; } @@ -214,6 +274,8 @@ impl<'a> TableOptions<'a> { fns: self.fns || other.fns, create_str_type: other.create_str_type, update_str_type: other.update_str_type, + create_bytes_type: other.create_bytes_type, + update_bytes_type: other.update_bytes_type, single_model_file: self.single_model_file || other.single_model_file, read_only: self.read_only || other.read_only, } @@ -233,6 +295,8 @@ impl<'a> Default for TableOptions<'a> { fns: true, create_str_type: Default::default(), update_str_type: Default::default(), + create_bytes_type: Default::default(), + update_bytes_type: Default::default(), single_model_file: false, read_only: false, } diff --git a/test/create_update_bytes_cow/models/mod.rs b/test/create_update_bytes_cow/models/mod.rs new file mode 100644 index 00000000..015a6a2b --- /dev/null +++ b/test/create_update_bytes_cow/models/mod.rs @@ -0,0 +1 @@ +pub mod todos; diff --git a/test/create_update_bytes_cow/models/todos/generated.rs b/test/create_update_bytes_cow/models/todos/generated.rs new file mode 100644 index 00000000..3b07f00a --- /dev/null +++ b/test/create_update_bytes_cow/models/todos/generated.rs @@ -0,0 +1,98 @@ +/* @generated and managed by dsync */ + +use crate::diesel::*; +use crate::schema::*; +use diesel::QueryResult; + +pub type ConnectionType = diesel::r2d2::PooledConnection>; + +/// Struct representing a row in table `todos` +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Queryable, Selectable, QueryableByName)] +#[diesel(table_name=todos, primary_key(data))] +pub struct Todos { + /// Field representing column `data` + pub data: Vec, + /// Field representing column `data_nullable` + pub data_nullable: Option>, +} + +/// Create Struct for a row in table `todos` for [`Todos`] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Insertable)] +#[diesel(table_name=todos)] +pub struct CreateTodos<'a> { + /// Field representing column `data` + pub data: Cow<'a, [u8]>, + /// Field representing column `data_nullable` + pub data_nullable: Option>, +} + +/// Update Struct for a row in table `todos` for [`Todos`] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, AsChangeset, PartialEq, Default)] +#[diesel(table_name=todos)] +pub struct UpdateTodos<'a> { + /// Field representing column `data_nullable` + pub data_nullable: Option>>, +} + +/// Result of a `.paginate` function +#[derive(Debug, serde::Serialize)] +pub struct PaginationResult { + /// Resulting items that are from the current page + pub items: Vec, + /// The count of total items there are + pub total_items: i64, + /// Current page, 0-based index + pub page: i64, + /// Size of a page + pub page_size: i64, + /// Number of total possible pages, given the `page_size` and `total_items` + pub num_pages: i64, +} + +impl Todos { + /// Insert a new row into `todos` with a given [`CreateTodos`] + pub fn create(db: &mut ConnectionType, item: &CreateTodos) -> QueryResult { + use crate::schema::todos::dsl::*; + + insert_into(todos).values(item).get_result::(db) + } + + /// Get a row from `todos`, identified by the primary key + pub fn read(db: &mut ConnectionType, param_data: Vec) -> QueryResult { + use crate::schema::todos::dsl::*; + + todos.filter(data.eq(param_data)).first::(db) + } + + /// Paginates through the table where page is a 0-based index (i.e. page 0 is the first page) + pub fn paginate(db: &mut ConnectionType, page: i64, page_size: i64) -> QueryResult> { + use crate::schema::todos::dsl::*; + + let page_size = if page_size < 1 { 1 } else { page_size }; + let total_items = todos.count().get_result(db)?; + let items = todos.limit(page_size).offset(page * page_size).load::(db)?; + + Ok(PaginationResult { + items, + total_items, + page, + page_size, + /* ceiling division of integers */ + num_pages: total_items / page_size + i64::from(total_items % page_size != 0) + }) + } + + /// Update a row in `todos`, identified by the primary key with [`UpdateTodos`] + pub fn update(db: &mut ConnectionType, param_data: Vec, item: &UpdateTodos) -> QueryResult { + use crate::schema::todos::dsl::*; + + diesel::update(todos.filter(data.eq(param_data))).set(item).get_result(db) + } + + /// Delete a row in `todos`, identified by the primary key + pub fn delete(db: &mut ConnectionType, param_data: Vec) -> QueryResult { + use crate::schema::todos::dsl::*; + + diesel::delete(todos.filter(data.eq(param_data))).execute(db) + } +} diff --git a/test/create_update_bytes_cow/models/todos/mod.rs b/test/create_update_bytes_cow/models/todos/mod.rs new file mode 100644 index 00000000..a5bb9b90 --- /dev/null +++ b/test/create_update_bytes_cow/models/todos/mod.rs @@ -0,0 +1,2 @@ +pub use generated::*; +pub mod generated; diff --git a/test/create_update_bytes_cow/schema.rs b/test/create_update_bytes_cow/schema.rs new file mode 100644 index 00000000..7cec0bce --- /dev/null +++ b/test/create_update_bytes_cow/schema.rs @@ -0,0 +1,6 @@ +diesel::table! { + todos (data) { + data -> Binary, + data_nullable -> Nullable, + } +} diff --git a/test/create_update_bytes_cow/test.sh b/test/create_update_bytes_cow/test.sh new file mode 100755 index 00000000..ab894ead --- /dev/null +++ b/test/create_update_bytes_cow/test.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +cd $SCRIPT_DIR + +cargo run -- -i schema.rs -o models -g id -g created_at -g updated_at -c "diesel::r2d2::PooledConnection>" --create-bytes=cow --update-bytes=cow diff --git a/test/create_update_bytes_slice/models/mod.rs b/test/create_update_bytes_slice/models/mod.rs new file mode 100644 index 00000000..015a6a2b --- /dev/null +++ b/test/create_update_bytes_slice/models/mod.rs @@ -0,0 +1 @@ +pub mod todos; diff --git a/test/create_update_bytes_slice/models/todos/generated.rs b/test/create_update_bytes_slice/models/todos/generated.rs new file mode 100644 index 00000000..a567b876 --- /dev/null +++ b/test/create_update_bytes_slice/models/todos/generated.rs @@ -0,0 +1,98 @@ +/* @generated and managed by dsync */ + +use crate::diesel::*; +use crate::schema::*; +use diesel::QueryResult; + +pub type ConnectionType = diesel::r2d2::PooledConnection>; + +/// Struct representing a row in table `todos` +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Queryable, Selectable, QueryableByName)] +#[diesel(table_name=todos, primary_key(data))] +pub struct Todos { + /// Field representing column `data` + pub data: Vec, + /// Field representing column `data_nullable` + pub data_nullable: Option>, +} + +/// Create Struct for a row in table `todos` for [`Todos`] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Insertable)] +#[diesel(table_name=todos)] +pub struct CreateTodos<'a> { + /// Field representing column `data` + pub data: &'a [u8], + /// Field representing column `data_nullable` + pub data_nullable: Option<&'a [u8]>, +} + +/// Update Struct for a row in table `todos` for [`Todos`] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, AsChangeset, PartialEq, Default)] +#[diesel(table_name=todos)] +pub struct UpdateTodos<'a> { + /// Field representing column `data_nullable` + pub data_nullable: Option>, +} + +/// Result of a `.paginate` function +#[derive(Debug, serde::Serialize)] +pub struct PaginationResult { + /// Resulting items that are from the current page + pub items: Vec, + /// The count of total items there are + pub total_items: i64, + /// Current page, 0-based index + pub page: i64, + /// Size of a page + pub page_size: i64, + /// Number of total possible pages, given the `page_size` and `total_items` + pub num_pages: i64, +} + +impl Todos { + /// Insert a new row into `todos` with a given [`CreateTodos`] + pub fn create(db: &mut ConnectionType, item: &CreateTodos) -> QueryResult { + use crate::schema::todos::dsl::*; + + insert_into(todos).values(item).get_result::(db) + } + + /// Get a row from `todos`, identified by the primary key + pub fn read(db: &mut ConnectionType, param_data: Vec) -> QueryResult { + use crate::schema::todos::dsl::*; + + todos.filter(data.eq(param_data)).first::(db) + } + + /// Paginates through the table where page is a 0-based index (i.e. page 0 is the first page) + pub fn paginate(db: &mut ConnectionType, page: i64, page_size: i64) -> QueryResult> { + use crate::schema::todos::dsl::*; + + let page_size = if page_size < 1 { 1 } else { page_size }; + let total_items = todos.count().get_result(db)?; + let items = todos.limit(page_size).offset(page * page_size).load::(db)?; + + Ok(PaginationResult { + items, + total_items, + page, + page_size, + /* ceiling division of integers */ + num_pages: total_items / page_size + i64::from(total_items % page_size != 0) + }) + } + + /// Update a row in `todos`, identified by the primary key with [`UpdateTodos`] + pub fn update(db: &mut ConnectionType, param_data: Vec, item: &UpdateTodos) -> QueryResult { + use crate::schema::todos::dsl::*; + + diesel::update(todos.filter(data.eq(param_data))).set(item).get_result(db) + } + + /// Delete a row in `todos`, identified by the primary key + pub fn delete(db: &mut ConnectionType, param_data: Vec) -> QueryResult { + use crate::schema::todos::dsl::*; + + diesel::delete(todos.filter(data.eq(param_data))).execute(db) + } +} diff --git a/test/create_update_bytes_slice/models/todos/mod.rs b/test/create_update_bytes_slice/models/todos/mod.rs new file mode 100644 index 00000000..a5bb9b90 --- /dev/null +++ b/test/create_update_bytes_slice/models/todos/mod.rs @@ -0,0 +1,2 @@ +pub use generated::*; +pub mod generated; diff --git a/test/create_update_bytes_slice/schema.rs b/test/create_update_bytes_slice/schema.rs new file mode 100644 index 00000000..7cec0bce --- /dev/null +++ b/test/create_update_bytes_slice/schema.rs @@ -0,0 +1,6 @@ +diesel::table! { + todos (data) { + data -> Binary, + data_nullable -> Nullable, + } +} diff --git a/test/create_update_bytes_slice/test.sh b/test/create_update_bytes_slice/test.sh new file mode 100755 index 00000000..97f5688d --- /dev/null +++ b/test/create_update_bytes_slice/test.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +cd $SCRIPT_DIR + +cargo run -- -i schema.rs -o models -g id -g created_at -g updated_at -c "diesel::r2d2::PooledConnection>" --create-bytes=slice --update-bytes=slice