diff --git a/.idea/cove-chat.iml b/.idea/cove-chat.iml
index 838d228..c59cf18 100644
--- a/.idea/cove-chat.iml
+++ b/.idea/cove-chat.iml
@@ -9,6 +9,7 @@
+
diff --git a/Cargo.lock b/Cargo.lock
index fd142e5..d579358 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -191,11 +191,21 @@ name = "cove-db"
version = "0.1.0"
dependencies = [
"anyhow",
+ "cove-db-macros",
"cove-net-common",
"serde_json",
"sqlx",
]
+[[package]]
+name = "cove-db-macros"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "cove-net-client"
version = "0.1.0"
@@ -1863,9 +1873,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
-version = "2.0.114"
+version = "2.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
+checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb"
dependencies = [
"proc-macro2",
"quote",
diff --git a/Cargo.toml b/Cargo.toml
index 16a3319..da846b1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,8 +3,10 @@ resolver = "3"
members = [
"cove-net/common",
"cove-net/client",
- "cove-net/server"
-, "bin-test", "cove-db"]
+ "cove-net/server",
+ "bin-test",
+ "cove-db",
+ "cove-db/cove-db-macros"]
[workspace.dependencies]
@@ -13,6 +15,7 @@ async-trait = "0.1.89"
anyhow = "1.0.101"
hyper = { version = "1", features = ["full"] }
http-body-util = { version = "0.1.3", features = ["full"] }
+rand = "0.10.0"
cove-net-common = {path = "cove-net/common"}
serde_json = "1.0.149"
@@ -25,4 +28,9 @@ tokio = { version = "1", features = ["full"] }
hyper-util = { version = "0.1", features = ["full"] }
cove-db = {path = "cove-db"}
-sqlx = { version = "0.8.6", features = [ "runtime-tokio", "tls-rustls-ring", "postgres", "time", "uuid", "json", "derive" ]}
\ No newline at end of file
+sqlx = { version = "0.8.6", features = [ "runtime-tokio", "tls-rustls-ring", "postgres", "time", "uuid", "json", "derive" ]}
+
+cove-db-macros = {path = "cove-db/cove-db-macros" }
+syn = {version = "2.0.116", features = ["full"]}
+quote = {version = "1.0.44"}
+proc-macro2 = "1.0.106"
\ No newline at end of file
diff --git a/bin-test/Cargo.toml b/bin-test/Cargo.toml
index 9bb4d34..1c4c886 100644
--- a/bin-test/Cargo.toml
+++ b/bin-test/Cargo.toml
@@ -9,5 +9,5 @@ cove-net-common.workspace = true
tokio.workspace = true
cove-db.workspace = true
sqlx.workspace = true
-anyhow = "1.0.101"
-scc = "3.5.6"
\ No newline at end of file
+anyhow.workspace = true
+scc.workspace = true
\ No newline at end of file
diff --git a/cove-db/Cargo.toml b/cove-db/Cargo.toml
index 5415769..3a36aee 100644
--- a/cove-db/Cargo.toml
+++ b/cove-db/Cargo.toml
@@ -7,5 +7,6 @@ edition = "2024"
sqlx.workspace = true
anyhow.workspace = true
serde_json.workspace = true
+cove-db-macros.workspace = true
cove-net-common.workspace = true
\ No newline at end of file
diff --git a/cove-db/cove-db-macros/Cargo.toml b/cove-db/cove-db-macros/Cargo.toml
new file mode 100644
index 0000000..a9a94f5
--- /dev/null
+++ b/cove-db/cove-db-macros/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "cove-db-macros"
+version = "0.1.0"
+edition = "2024"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+syn.workspace = true
+quote.workspace = true
+proc-macro2.workspace = true
\ No newline at end of file
diff --git a/cove-db/cove-db-macros/src/lib.rs b/cove-db/cove-db-macros/src/lib.rs
new file mode 100644
index 0000000..4166e02
--- /dev/null
+++ b/cove-db/cove-db-macros/src/lib.rs
@@ -0,0 +1,257 @@
+use proc_macro::{TokenStream};
+use quote::{format_ident, quote};
+use syn::{parse_str, Data, DeriveInput, Fields};
+
+#[proc_macro_derive(DeriveTableRow, attributes(table_name))]
+pub fn derive_table_row(input: TokenStream) -> TokenStream {
+ let ast = syn::parse(input).unwrap();
+ impl_derive_table_row(ast)
+}
+
+fn impl_derive_table_row(ast: DeriveInput) -> TokenStream {
+ let table_name: String = {
+ if ast.attrs.len() != 1 {
+ return quote!{compile_error!("The DeriveTableRow macro expects exactly one (1) table_name attribute")}.into();
+ }
+ let attr = ast.attrs.first().unwrap();
+ if attr.path().is_ident("table_name") {
+ if let Ok(args) = attr.parse_args::() {
+ args.value()
+ } else {
+ return quote!{compile_error!("The DeriveTableRow macro's attribute table_name should only consist of a string")}.into();
+ }
+ } else {
+ return quote!{compile_error!("Incorrect attribute name for table_name attribute")}.into();
+ }
+ };
+
+ let name = ast.ident;
+ let partial_name = format_ident!("Partial{}", name);
+
+ let data = ast.data;
+
+ let fields = match data {
+ Data::Struct(data) => {
+ match data.fields {
+ Fields::Named(named_fields) => {
+ named_fields
+ },
+ _ => return quote!{compile_error!("The DeriveTableRow macro can only apply to structs solely consisting of named fields")}.into()
+ }
+ },
+ _ => return quote!{compile_error!("The DeriveTableRow macro can only be applied to structs.")}.into()
+ };
+
+
+
+ let struct_partial_row = {
+ let struct_partial_row_body = {
+ let mut ret = quote!();
+ for field in fields.named.iter() {
+ let name = field.ident.as_ref().unwrap();
+ let ty = &field.ty;
+ ret.extend(quote! {
+ pub #name: Option<#ty>,
+ });
+ }
+ ret
+ };
+ quote! {
+ #[derive(Default)]
+ pub struct #partial_name {
+ #struct_partial_row_body
+ }
+
+
+ }
+ };
+
+ let impl_table_row = quote! {
+ impl crate::rows::TableRow for #name {
+ type Error = anyhow::Error;
+ type PartialRow = #partial_name;
+
+ fn get_table_name() -> &'static str {
+ #table_name
+ }
+ }
+
+
+ };
+
+ let impl_partial_table_row = quote! {
+ impl crate::rows::PartialTableRow for #partial_name {
+ type Error = anyhow::Error;
+ type FullTableRow = #name;
+ }
+
+
+ };
+
+ let impl_insertable_row = {
+ let insert_body = {
+ let mut ret = quote!(
+ crate::part::insert::SqlInsert::with_table(::get_table_name())
+ );
+
+ for field in fields.named.iter() {
+ let name = field.ident.as_ref().unwrap();
+ let quoted_name = format!("{}", name);
+ let ty = &field.ty;
+ ret.extend(
+ quote! {
+ .col::<#ty>(#quoted_name)
+ }
+ )
+ }
+
+ ret
+ };
+
+ let bind_body = {
+ let mut ret = quote! {
+ query
+ };
+
+ let mut idx = 0;
+ let max_idx = fields.named.len();
+ for field in fields.named.iter() {
+ let name = field.ident.as_ref().unwrap();
+ ret.extend(quote! {
+ .bind(&self.#name)
+ });
+ if idx < max_idx-1 {
+ ret.extend(parse_str::("?"));
+ }
+ idx += 1;
+ }
+
+ ret
+ };
+
+ quote! {
+ impl crate::rows::InsertableRow for #name {
+ fn insert(&'_ self) -> crate::part::insert::SqlInsert {
+ #insert_body
+ }
+
+ fn bind<'a>(&'a self, query: crate::part::BindQuery<'a>) -> Result, Self::Error> {
+ #bind_body
+ }
+ }
+
+
+ }
+ };
+
+ let impl_selectable_row = quote! {
+ impl crate::rows::SelectableRow for #partial_name {}
+ };
+
+ let impl_where_row = {
+ let match_body = {
+ let mut ret = quote!();
+ for field in fields.named.iter() {
+ let name = field.ident.as_ref().unwrap();
+ let quoted_name = format!("{}", name);
+ ret.extend(
+ quote! {
+ #quoted_name => query.bind(self.#name.as_ref().unwrap())?,
+ }
+ )
+ }
+ ret
+ };
+
+ quote! {
+ impl crate::rows::WhereRow for #partial_name {
+ fn bind<'a>(&'a self, wheres: crate::part::sql_where::SqlWhere, query: crate::part::BindQuery<'a>) -> Result, Self::Error> {
+ let mut query = query;
+ for (_, col) in wheres.indexed_placeholders {
+ query = match col.as_str() {
+ #match_body
+ _ => return Err(anyhow::anyhow!("No column {col} exists for table users"))
+ }
+ }
+ Ok(query)
+ }
+ }
+
+
+ }
+ };
+
+ let impl_try_from_pgrow_for_full = {
+ let try_from_body = {
+ let mut ret = quote!();
+
+ for field in fields.named.iter() {
+ let name = field.ident.as_ref().unwrap();
+ let quoted_name = format!("{}", name);
+ ret.extend(
+ quote! {
+ #name: row.try_get(#quoted_name)?,
+ }
+ )
+ }
+
+ ret
+ };
+
+ quote! {
+ impl TryFrom for #name {
+ type Error = anyhow::Error;
+
+ fn try_from(row: sqlx::postgres::PgRow) -> Result {
+ use sqlx::Row;
+ Ok(Self {
+ #try_from_body
+ })
+ }
+ }
+
+
+ }
+ };
+
+ let impl_from_pgrow_for_partial = {
+ let from_body = {
+ let mut ret = quote!();
+ for field in fields.named.iter() {
+ let name = field.ident.as_ref().unwrap();
+ let quoted_name = format!("{}", name);
+ ret.extend(
+ quote! {
+ #name: row.try_get(#quoted_name).ok(),
+ }
+ )
+ }
+ ret
+ };
+
+ quote! {
+ impl From for #partial_name {
+ fn from(row: sqlx::postgres::PgRow) -> Self {
+ use sqlx::Row;
+ Self {
+ #from_body
+ }
+ }
+ }
+
+
+ }
+ };
+
+ let mut ret = quote!();
+ ret.extend(struct_partial_row);
+ ret.extend(impl_table_row);
+ ret.extend(impl_partial_table_row);
+ ret.extend(impl_insertable_row);
+ ret.extend(impl_selectable_row);
+ ret.extend(impl_where_row);
+ ret.extend(impl_try_from_pgrow_for_full);
+ ret.extend(impl_from_pgrow_for_partial);
+
+ ret.into()
+}
\ No newline at end of file
diff --git a/cove-db/src/query/text.rs b/cove-db/src/query/text.rs
index e821bef..ae6eb84 100644
--- a/cove-db/src/query/text.rs
+++ b/cove-db/src/query/text.rs
@@ -1,5 +1,5 @@
use anyhow::{Error};
-use sqlx::{Execute, Executor};
+use sqlx::{Execute};
use sqlx::postgres::{ PgQueryResult};
use cove_net_common::id::message_type::MessageType;
use cove_net_common::id::SnowflakeID;
diff --git a/cove-db/src/rows/attachment.rs b/cove-db/src/rows/attachment.rs
index 13558a2..040c1bf 100644
--- a/cove-db/src/rows/attachment.rs
+++ b/cove-db/src/rows/attachment.rs
@@ -1,77 +1,12 @@
-use anyhow::anyhow;
-use sqlx::postgres::PgRow;
-use sqlx::Row;
+use cove_db_macros::DeriveTableRow;
use cove_net_common::id::SnowflakeID;
-use crate::part::BindQuery;
-use crate::part::insert::SqlInsert;
-use crate::part::select::SqlSelect;
-use crate::part::sql_where::SqlWhere;
-use crate::rows::{InsertableRow, SelectableRow, TableRow, WhereRow};
+#[derive(DeriveTableRow)]
+#[table_name("attachments")]
pub struct AttachmentRow {
pub id: SnowflakeID,
pub uploader_id: SnowflakeID,
pub channel_id: Option,
pub file_size: i32,
pub cdn_url: String
-}
-
-impl TableRow for AttachmentRow {
- type Error = anyhow::Error;
-
- fn get_table_name() -> &'static str {
- "attachments"
- }
-}
-
-impl InsertableRow for AttachmentRow {
- fn insert(&'_ self) -> SqlInsert {
- SqlInsert::with_table(Self::get_table_name())
- .col::("id")
- .col::("uploader_id")
- .opt_col::("channel_id")
- .col::("file_size")
- .col::("cdn_url")
- }
-
- fn bind<'a>(&'a self, query: BindQuery<'a>) -> Result, Self::Error> {
- query.bind(&self.id)?
- .bind(&self.uploader_id)?
- .bind(&self.channel_id)?
- .bind(&self.file_size)?
- .bind(&self.cdn_url)
- }
-}
-
-impl SelectableRow for AttachmentRow {}
-
-impl WhereRow for AttachmentRow {
- fn bind<'a>(&'a self, wheres: SqlWhere, query: BindQuery<'a>) -> Result, Self::Error> {
- let mut query = query;
- for (_, col) in wheres.indexed_placeholders {
- query = match col.as_str() {
- "id" => query.bind(&self.id)?,
- "uploader_id" => query.bind(&self.uploader_id)?,
- "channel_id" => query.bind(&self.channel_id)?,
- "file_size" => query.bind(&self.file_size)?,
- "cdn_url" => query.bind(&self.cdn_url)?,
- _ => return Err(anyhow!("No column {col} exists for table attachments"))
- }
- }
- Ok(query)
- }
-}
-
-impl TryFrom for AttachmentRow {
- type Error = anyhow::Error;
-
- fn try_from(value: PgRow) -> Result {
- Ok(Self {
- id: value.try_get("id")?,
- uploader_id: value.try_get("uploader_id")?,
- channel_id: value.try_get("channel_id")?,
- file_size: value.try_get("file_size")?,
- cdn_url: value.try_get("cdn_url")?,
- })
- }
}
\ No newline at end of file
diff --git a/cove-db/src/rows/channel.rs b/cove-db/src/rows/channel.rs
index ad14308..4a6c6bb 100644
--- a/cove-db/src/rows/channel.rs
+++ b/cove-db/src/rows/channel.rs
@@ -1,15 +1,11 @@
-use anyhow::anyhow;
use serde_json::Value;
-use sqlx::postgres::PgRow;
-use sqlx::Row;
use sqlx::types::time::OffsetDateTime;
+use cove_db_macros::DeriveTableRow;
use cove_net_common::id::SnowflakeID;
use cove_net_common::id::types::channel::ChannelMessageType;
-use crate::part::BindQuery;
-use crate::part::insert::SqlInsert;
-use crate::part::sql_where::SqlWhere;
-use crate::rows::{InsertableRow, SelectableRow, TableRow, WhereRow};
+#[derive(DeriveTableRow)]
+#[table_name("channels")]
pub struct ChannelRow {
pub id: SnowflakeID,
pub guild_id: Option,
@@ -37,120 +33,4 @@ pub struct ChannelRow {
pub user_limit: i32,
pub region: Option
-}
-
-impl TableRow for ChannelRow {
- type Error = anyhow::Error;
-
- fn get_table_name() -> &'static str {
- "channels"
- }
-}
-
-impl InsertableRow for ChannelRow {
- fn insert(&'_ self) -> SqlInsert {
- SqlInsert::with_table(Self::get_table_name())
- .col::("id")
- .opt_col::("guild_id")
- .opt_col::("parent_id")
- .col::("name")
- .col::("channel_type")
- .col::("position")
- .col::("permission_overwrites")
- .col::("rate_limit_per_user")
- .col::("nsfw")
- .col::("loud")
- .opt_col::("thread_metadata")
- .col::("member_count")
- .col::("message_count")
- .opt_col::("thread_owner_id")
- .col::("created_at")
- .col::("updated_at")
- .opt_col::("topic")
- .col::("user_limit")
- .opt_col::("region")
- }
-
- fn bind<'a>(&'a self, query: BindQuery<'a>) -> Result, Self::Error> {
- query.bind(&self.id)?
- .bind(&self.guild_id)?
- .bind(&self.parent_id)?
- .bind(&self.name)?
- .bind(&self.channel_type)?
- .bind(&self.position)?
- .bind(&self.permission_overwrites)?
- .bind(&self.rate_limit_per_user)?
- .bind(&self.nsfw)?
- .bind(&self.loud)?
- .bind(&self.thread_metadata)?
- .bind(&self.member_count)?
- .bind(&self.message_count)?
- .bind(&self.thread_owner_id)?
- .bind(&self.created_at)?
- .bind(&self.updated_at)?
- .bind(&self.topic)?
- .bind(&self.user_limit)?
- .bind(&self.region)
- }
-}
-
-impl SelectableRow for ChannelRow {}
-
-impl WhereRow for ChannelRow {
- fn bind<'a>(&'a self, wheres: SqlWhere, query: BindQuery<'a>) -> Result, Self::Error> {
- let mut query = query;
- for (_, col) in wheres.indexed_placeholders {
- query = match col.as_str() {
- "id" => query.bind(&self.id)?,
- "guild_id" => query.bind(&self.guild_id)?,
- "parent_id" => query.bind(&self.parent_id)?,
- "name" => query.bind(&self.name)?,
- "channel_type" => query.bind(&self.channel_type)?,
- "position" => query.bind(&self.position)?,
- "permission_overwrites" => query.bind(&self.permission_overwrites)?,
- "rate_limit_per_user" => query.bind(&self.rate_limit_per_user)?,
- "nsfw" => query.bind(&self.nsfw)?,
- "loud" => query.bind(&self.loud)?,
- "thread_metadata" => query.bind(&self.thread_metadata)?,
- "member_count" => query.bind(&self.member_count)?,
- "message_count" => query.bind(&self.message_count)?,
- "thread_owner_id" => query.bind(&self.thread_owner_id)?,
- "created_at" => query.bind(&self.created_at)?,
- "updated_at" => query.bind(&self.updated_at)?,
- "topic" => query.bind(&self.topic)?,
- "user_limit" => query.bind(&self.user_limit)?,
- "region" => query.bind(&self.region)?,
- _ => return Err(anyhow!("No column {col} exists for table attachments"))
- }
- }
- Ok(query)
- }
-}
-
-impl TryFrom for ChannelRow {
- type Error = anyhow::Error;
-
- fn try_from(value: PgRow) -> Result {
- Ok(Self{
- id: value.try_get("id")?,
- guild_id: value.try_get("guild_id")?,
- parent_id: value.try_get("parent_id")?,
- name: value.try_get("name")?,
- channel_type: value.try_get("channel_type")?,
- position: value.try_get("position")?,
- permission_overwrites: value.try_get("permission_overwrites")?,
- rate_limit_per_user: value.try_get("rate_limit_per_user")?,
- nsfw: value.try_get("nsfw")?,
- loud: value.try_get("loud")?,
- thread_metadata: value.try_get("thread_metadata")?,
- member_count: value.try_get("member_count")?,
- message_count: value.try_get("message_count")?,
- thread_owner_id: value.try_get("thread_owner_id")?,
- created_at: value.try_get("created_at")?,
- updated_at: value.try_get("updated_at")?,
- topic: value.try_get("topic")?,
- user_limit: value.try_get("user_limit")?,
- region: value.try_get("region")?,
- })
- }
}
\ No newline at end of file
diff --git a/cove-db/src/rows/guild.rs b/cove-db/src/rows/guild.rs
index e147dcf..f318933 100644
--- a/cove-db/src/rows/guild.rs
+++ b/cove-db/src/rows/guild.rs
@@ -1,17 +1,13 @@
-use anyhow::anyhow;
-use sqlx::postgres::PgRow;
-use sqlx::Row;
use sqlx::types::time::OffsetDateTime;
+use cove_db_macros::DeriveTableRow;
use cove_net_common::guild::component::default_notification::DefaultMessageNotificationSetting;
use cove_net_common::guild::component::explicit_filter::ExplicitContentFilterSetting;
use cove_net_common::guild::component::verification_level::VerificationLevelSetting;
use cove_net_common::id::SnowflakeID;
-use crate::part::BindQuery;
-use crate::part::insert::SqlInsert;
-use crate::part::sql_where::SqlWhere;
-use crate::rows::{InsertableRow, SelectableRow, TableRow, WhereRow};
-struct GuildRow {
+#[derive(DeriveTableRow)]
+#[table_name("guilds")]
+pub struct GuildRow {
pub id: SnowflakeID,
pub name: String,
@@ -49,152 +45,4 @@ struct GuildRow {
pub updated_at: OffsetDateTime,
pub large: bool,
pub member_count: i32
-}
-
-impl TableRow for GuildRow {
- type Error = anyhow::Error;
-
- fn get_table_name() -> &'static str {
- "guilds"
- }
-}
-
-impl InsertableRow for GuildRow {
- fn insert(&'_ self) -> SqlInsert {
- SqlInsert::with_table(Self::get_table_name())
- .col::("id")
- .col::("name")
- .opt_col::("description")
- .opt_col::("icon")
- .opt_col::("banner")
- .opt_col::("splash")
- .col::("owner_id")
- .opt_col::<[u8; 8]>("owner_permissions")
- .col::("region")
- .col::("features")
- .opt_col::("afk_channel_id")
- .col::("afk_timeout")
- .col::("verification_level")
- .col::("default_message_notifications")
- .col::("explicit_content_filter")
- .opt_col::("system_channel_id")
- .opt_col::("system_channel_flags")
- .arr_col::("premium_boosters")
- .col::("premium_tier")
- .col::("premium_booster_count")
- .col::("widget_enabled")
- .opt_col::("widget_channel_id")
- .col::("preferred_locale")
- .col::("created_at")
- .col::("updated_at")
- .col::("large")
- .col::("member_count")
- }
-
- fn bind<'a>(&'a self, query: BindQuery<'a>) -> Result, Self::Error> {
- query.bind(&self.id)?
- .bind(&self.name)?
- .bind(&self.description)?
- .bind(&self.icon)?
- .bind(&self.banner)?
- .bind(&self.splash)?
- .bind(&self.owner_id)?
- .bind(&self.owner_permissions)?
- .bind(&self.region)?
- .bind(&self.features)?
- .bind(&self.afk_channel_id)?
- .bind(&self.afk_timeout)?
- .bind(&self.verification_level)?
- .bind(&self.default_message_notifications)?
- .bind(&self.explicit_content_filter)?
- .bind(&self.system_channel_id)?
- .bind(&self.system_channel_flags)?
- .bind(&self.premium_boosters)?
- .bind(&self.premium_tier)?
- .bind(&self.premium_booster_count)?
- .bind(&self.widget_enabled)?
- .bind(&self.widget_channel_id)?
- .bind(&self.preferred_locale)?
- .bind(&self.created_at)?
- .bind(&self.updated_at)?
- .bind(&self.large)?
- .bind(&self.member_count)
- }
-}
-
-impl SelectableRow for GuildRow {}
-
-impl WhereRow for GuildRow {
- fn bind<'a>(&'a self, wheres: SqlWhere, query: BindQuery<'a>) -> Result, Self::Error> {
- let mut query = query;
- for (_, col) in wheres.indexed_placeholders {
- query = match col.as_str() {
- "id" => query.bind(&self.id)?,
- "name" => query.bind(&self.name)?,
- "description" => query.bind(&self.description)?,
- "icon" => query.bind(&self.icon)?,
- "banner" => query.bind(&self.banner)?,
- "splash" => query.bind(&self.splash)?,
- "owner_id" => query.bind(&self.owner_id)?,
- "owner_permissions" => query.bind(&self.owner_permissions)?,
- "region" => query.bind(&self.region)?,
- "features" => query.bind(&self.features)?,
- "afk_channel_id" => query.bind(&self.afk_channel_id)?,
- "afk_timeout" => query.bind(&self.afk_timeout)?,
- "verification_level" => query.bind(&self.verification_level)?,
- "default_message_notifications" => query.bind(&self.default_message_notifications)?,
- "explicit_content_filter" => query.bind(&self.explicit_content_filter)?,
- "system_channel_id" => query.bind(&self.system_channel_id)?,
- "system_channel_flags" => query.bind(&self.system_channel_flags)?,
- "premium_boosters" => query.bind(&self.premium_boosters)?,
- "premium_tier" => query.bind(&self.premium_tier)?,
- "premium_booster_count" => query.bind(&self.premium_booster_count)?,
- "widget_enabled" => query.bind(&self.widget_enabled)?,
- "widget_channel_id" => query.bind(&self.widget_channel_id)?,
- "preferred_locale" => query.bind(&self.preferred_locale)?,
- "created_at" => query.bind(&self.created_at)?,
- "updated_at" => query.bind(&self.updated_at)?,
- "large" => query.bind(&self.large)?,
- "member_count" => query.bind(&self.member_count)?,
- _ => return Err(anyhow!("No column {col} exists for table attachments"))
- }
- }
- Ok(query)
- }
-}
-
-impl TryFrom for GuildRow {
- type Error = anyhow::Error;
-
- fn try_from(value: PgRow) -> Result {
- Ok(Self {
- id: value.try_get("id")?,
- name: value.try_get("name")?,
- description: value.try_get("description")?,
- icon: value.try_get("icon")?,
- banner: value.try_get("banner")?,
- splash: value.try_get("splash")?,
- owner_id: value.try_get("owner_id")?,
- owner_permissions: value.try_get("owner_permissions")?,
- region: value.try_get("region")?,
- features: value.try_get("features")?,
- afk_channel_id: value.try_get("afk_channel_id")?,
- afk_timeout: value.try_get("afk_timeout")?,
- verification_level: value.try_get("verification_level")?,
- default_message_notifications: value.try_get("default_message_notifications")?,
- explicit_content_filter: value.try_get("explicit_content_filter")?,
- system_channel_id: value.try_get("system_channel_id")?,
- system_channel_flags: value.try_get("system_channel_flags")?,
- premium_boosters: value.try_get("premium_boosters")?,
- premium_tier: value.try_get("premium_tier")?,
- premium_booster_count: value.try_get("premium_booster_count")?,
- widget_enabled: value.try_get("widget_enabled")?,
- widget_channel_id: value.try_get("widget_channel_id")?,
- preferred_locale: value.try_get("preferred_locale")?,
- created_at: value.try_get("created_at")?,
- updated_at: value.try_get("updated_at")?,
- large: value.try_get("large")?,
- member_count: value.try_get("member_count")?,
- })
- }
}
\ No newline at end of file
diff --git a/cove-db/src/rows/guild_member.rs b/cove-db/src/rows/guild_member.rs
index c5d7333..a9b7fcd 100644
--- a/cove-db/src/rows/guild_member.rs
+++ b/cove-db/src/rows/guild_member.rs
@@ -1,14 +1,10 @@
-use anyhow::anyhow;
-use sqlx::postgres::PgRow;
-use sqlx::Row;
use sqlx::types::time::OffsetDateTime;
+use cove_db_macros::DeriveTableRow;
use cove_net_common::id::SnowflakeID;
-use crate::part::BindQuery;
-use crate::part::insert::SqlInsert;
-use crate::part::sql_where::SqlWhere;
-use crate::rows::{InsertableRow, SelectableRow, TableRow, WhereRow};
-struct GuildMemberRow {
+#[derive(DeriveTableRow)]
+#[table_name("guild_members")]
+pub struct GuildMemberRow {
pub guild_id: SnowflakeID,
pub user_id: SnowflakeID,
@@ -26,92 +22,4 @@ struct GuildMemberRow {
pub timed_out_until: Option,
pub updated_at: OffsetDateTime,
-}
-
-impl TableRow for GuildMemberRow {
- type Error = anyhow::Error;
-
- fn get_table_name() -> &'static str {
- "guild_members"
- }
-}
-
-impl InsertableRow for GuildMemberRow {
- fn insert(&'_ self) -> SqlInsert {
- SqlInsert::with_table(Self::get_table_name())
- .col::("guild_id")
- .col::("user_id")
- .opt_col::("nick")
- .col::("joined_at")
- .arr_col::("roles")
- .opt_col::("boosting_since")
- .opt_col::("voice_channel_id")
- .col::("deafened")
- .col::("muted")
- .col::("pending")
- .opt_col::("timed_out_until")
- .col::("updated_at")
- }
-
- fn bind<'a>(&'a self, query: BindQuery<'a>) -> Result, Self::Error> {
- query.bind(&self.guild_id)?
- .bind(&self.user_id)?
- .bind(&self.nick)?
- .bind(&self.joined_at)?
- .bind(&self.roles)?
- .bind(&self.boosting_since)?
- .bind(&self.voice_channel_id)?
- .bind(&self.deafened)?
- .bind(&self.muted)?
- .bind(&self.pending)?
- .bind(&self.timed_out_until)?
- .bind(&self.updated_at)
- }
-}
-
-impl SelectableRow for GuildMemberRow {}
-
-impl WhereRow for GuildMemberRow {
- fn bind<'a>(&'a self, wheres: SqlWhere, query: BindQuery<'a>) -> Result, Self::Error> {
- let mut query = query;
- for (_, col) in wheres.indexed_placeholders {
- query = match col.as_str() {
- "guild_id" => query.bind(&self.guild_id)?,
- "user_id" => query.bind(&self.user_id)?,
- "nick" => query.bind(&self.nick)?,
- "joined_at" => query.bind(&self.joined_at)?,
- "roles" => query.bind(&self.roles)?,
- "boosting_since" => query.bind(&self.boosting_since)?,
- "voice_channel_id" => query.bind(&self.voice_channel_id)?,
- "deafened" => query.bind(&self.deafened)?,
- "muted" => query.bind(&self.muted)?,
- "pending" => query.bind(&self.pending)?,
- "timed_out_until" => query.bind(&self.timed_out_until)?,
- "updated_at" => query.bind(&self.updated_at)?,
- _ => return Err(anyhow!("No column {col} exists for table attachments"))
- }
- }
- Ok(query)
- }
-}
-
-impl TryFrom for GuildMemberRow {
- type Error = anyhow::Error;
-
- fn try_from(value: PgRow) -> Result {
- Ok(Self {
- guild_id: value.try_get("guild_id")?,
- user_id: value.try_get("user_id")?,
- nick: value.try_get("nick")?,
- joined_at: value.try_get("joined_at")?,
- roles: value.try_get("roles")?,
- boosting_since: value.try_get("boosting_since")?,
- voice_channel_id: value.try_get("voice_channel_id")?,
- deafened: value.try_get("deafened")?,
- muted: value.try_get("muted")?,
- pending: value.try_get("pending")?,
- timed_out_until: value.try_get("timed_out_until")?,
- updated_at: value.try_get("updated_at")?,
- })
- }
}
\ No newline at end of file
diff --git a/cove-db/src/rows/message.rs b/cove-db/src/rows/message.rs
index a6be473..7cc7003 100644
--- a/cove-db/src/rows/message.rs
+++ b/cove-db/src/rows/message.rs
@@ -1,15 +1,11 @@
-use anyhow::anyhow;
-use sqlx::postgres::PgRow;
-use sqlx::Row;
use sqlx::types::time::OffsetDateTime;
+use cove_db_macros::DeriveTableRow;
use cove_net_common::id::SnowflakeID;
use cove_net_common::id::types::text::TextMessageType;
use cove_net_common::message::c2s::text::text::TextEmbed;
-use crate::part::BindQuery;
-use crate::part::insert::SqlInsert;
-use crate::part::sql_where::SqlWhere;
-use crate::rows::{InsertableRow, SelectableRow, TableRow, WhereRow};
+#[derive(DeriveTableRow)]
+#[table_name("messages")]
pub struct MessageRow {
pub id: SnowflakeID,
pub channel_id: SnowflakeID,
@@ -36,120 +32,4 @@ pub struct MessageRow {
pub created_at: OffsetDateTime,
pub updated_at: OffsetDateTime,
-}
-
-impl TableRow for MessageRow {
- type Error = anyhow::Error;
-
- fn get_table_name() -> &'static str {
- "messages"
- }
-}
-
-impl InsertableRow for MessageRow {
- fn insert(&'_ self) -> SqlInsert {
- SqlInsert::with_table(Self::get_table_name())
- .col::("id")
- .col::("channel_id")
- .opt_col::("guild_id")
- .col::("author_id")
- .col::("content")
- .col::("timestamp")
- .opt_col::("edited_timestamp")
- .col::("tts")
- .opt_arr_col::("mentions")
- .col::("mention_everyone")
- .opt_arr_col::("embeds")
- .opt_arr_col::("attachments")
- .opt_col::("reply_message_id")
- .opt_col::("application_id")
- .col::("message_type")
- .opt_col::("thread_name")
- .opt_col::("auto_archive_duration")
- .col::("created_at")
- .col::("updated_at")
- }
-
- fn bind<'a>(&'a self, query: BindQuery<'a>) -> Result, Self::Error> {
- query.bind(&self.id)?
- .bind(&self.channel_id)?
- .bind(&self.guild_id)?
- .bind(&self.author_id)?
- .bind(&self.content)?
- .bind(&self.timestamp)?
- .bind(&self.edited_timestamp)?
- .bind(&self.tts)?
- .bind(&self.mentions)?
- .bind(&self.mention_everyone)?
- .bind(&self.embeds)?
- .bind(&self.attachments)?
- .bind(&self.reply_message_id)?
- .bind(&self.application_id)?
- .bind(&self.message_type)?
- .bind(&self.thread_name)?
- .bind(&self.auto_archive_duration)?
- .bind(&self.created_at)?
- .bind(&self.updated_at)
- }
-}
-
-impl SelectableRow for MessageRow {}
-
-impl WhereRow for MessageRow {
- fn bind<'a>(&'a self, wheres: SqlWhere, query: BindQuery<'a>) -> Result, Self::Error> {
- let mut query = query;
- for (_, col) in wheres.indexed_placeholders {
- query = match col.as_str() {
- "id" => query.bind(&self.id)?,
- "channel_id" => query.bind(&self.channel_id)?,
- "guild_id" => query.bind(&self.guild_id)?,
- "author_id" => query.bind(&self.author_id)?,
- "content" => query.bind(&self.content)?,
- "timestamp" => query.bind(&self.timestamp)?,
- "edited_timestamp" => query.bind(&self.edited_timestamp)?,
- "tts" => query.bind(&self.tts)?,
- "mentions" => query.bind(&self.mentions)?,
- "mention_everyone" => query.bind(&self.mention_everyone)?,
- "embeds" => query.bind(&self.embeds)?,
- "attachments" => query.bind(&self.attachments)?,
- "reply_message_id" => query.bind(&self.reply_message_id)?,
- "application_id" => query.bind(&self.application_id)?,
- "message_type" => query.bind(&self.message_type)?,
- "thread_name" => query.bind(&self.thread_name)?,
- "auto_archive_duration" => query.bind(&self.auto_archive_duration)?,
- "created_at" => query.bind(&self.created_at)?,
- "updated_at" => query.bind(&self.updated_at)?,
- _ => return Err(anyhow!("No column {col} exists for table attachments"))
- }
- }
- Ok(query)
- }
-}
-
-impl TryFrom for MessageRow {
- type Error = anyhow::Error;
-
- fn try_from(value: PgRow) -> Result {
- Ok(Self {
- id: value.try_get("id")?,
- channel_id: value.try_get("channel_id")?,
- guild_id: value.try_get("guild_id")?,
- author_id: value.try_get("author_id")?,
- content: value.try_get("content")?,
- timestamp: value.try_get("timestamp")?,
- edited_timestamp: value.try_get("edited_timestamp")?,
- tts: value.try_get("tts")?,
- mentions: value.try_get("mentions")?,
- mention_everyone: value.try_get("mention_everyone")?,
- embeds: value.try_get("embeds")?,
- attachments: value.try_get("attachments")?,
- reply_message_id: value.try_get("reply_message_id")?,
- application_id: value.try_get("application_id")?,
- message_type: value.try_get("message_type")?,
- thread_name: value.try_get("thread_name")?,
- auto_archive_duration: value.try_get("auto_archive_duration")?,
- created_at: value.try_get("created_at")?,
- updated_at: value.try_get("updated_at")?,
- })
- }
}
\ No newline at end of file
diff --git a/cove-db/src/rows/mod.rs b/cove-db/src/rows/mod.rs
index 5366f3c..22b97fb 100644
--- a/cove-db/src/rows/mod.rs
+++ b/cove-db/src/rows/mod.rs
@@ -3,6 +3,12 @@ use crate::part::insert::SqlInsert;
use crate::part::select::SqlSelect;
use crate::part::sql_where::SqlWhere;
+pub mod attachment;
+pub mod channel;
+pub mod guild;
+pub mod guild_member;
+pub mod message;
+pub mod nonce;
pub mod user;
pub trait TableRow {
diff --git a/cove-db/src/rows/nonce.rs b/cove-db/src/rows/nonce.rs
index 56d4bfc..137d6fe 100644
--- a/cove-db/src/rows/nonce.rs
+++ b/cove-db/src/rows/nonce.rs
@@ -1,77 +1,13 @@
-use anyhow::anyhow;
-use sqlx::postgres::PgRow;
-use sqlx::Row;
use sqlx::types::time::OffsetDateTime;
+use cove_db_macros::DeriveTableRow;
use cove_net_common::id::SnowflakeID;
-use crate::part::BindQuery;
-use crate::part::insert::SqlInsert;
-use crate::part::sql_where::SqlWhere;
-use crate::rows::{InsertableRow, SelectableRow, TableRow, WhereRow};
-struct NonceRow {
+#[derive(DeriveTableRow)]
+#[table_name("pending_nonces")]
+pub struct NonceRow {
pub nonce: String,
pub channel_id: SnowflakeID,
pub author_id: SnowflakeID,
pub created_at: OffsetDateTime,
pub expires_at: OffsetDateTime,
-}
-
-impl TableRow for NonceRow {
- type Error = anyhow::Error;
-
- fn get_table_name() -> &'static str {
- "pending_nonces"
- }
-}
-
-impl InsertableRow for NonceRow {
- fn insert(&'_ self) -> SqlInsert {
- SqlInsert::with_table(Self::get_table_name())
- .col::("nonce")
- .col::("channel_id")
- .col::("author_id")
- .col::("created_at")
- .col::("expires_at")
- }
-
- fn bind<'a>(&'a self, query: BindQuery<'a>) -> Result, Self::Error> {
- query.bind(&self.nonce)?
- .bind(&self.channel_id)?
- .bind(&self.author_id)?
- .bind(&self.created_at)?
- .bind(&self.expires_at)
- }
-}
-
-impl SelectableRow for NonceRow {}
-
-impl WhereRow for NonceRow {
- fn bind<'a>(&'a self, wheres: SqlWhere, query: BindQuery<'a>) -> Result, Self::Error> {
- let mut query = query;
- for (_, col) in wheres.indexed_placeholders {
- query = match col.as_str() {
- "nonce" => query.bind(&self.nonce)?,
- "channel_id" => query.bind(&self.channel_id)?,
- "author_id" => query.bind(&self.author_id)?,
- "created_at" => query.bind(&self.created_at)?,
- "expires_at" => query.bind(&self.expires_at)?,
- _ => return Err(anyhow!("No column {col} exists for table attachments"))
- }
- }
- Ok(query)
- }
-}
-
-impl TryFrom for NonceRow {
- type Error = anyhow::Error;
-
- fn try_from(value: PgRow) -> Result {
- Ok(Self {
- nonce: value.try_get("nonce")?,
- channel_id: value.try_get("channel_id")?,
- author_id: value.try_get("author_id")?,
- created_at: value.try_get("created_at")?,
- expires_at: value.try_get("expires_at")?,
- })
- }
}
\ No newline at end of file
diff --git a/cove-db/src/rows/user.rs b/cove-db/src/rows/user.rs
index 6c15bae..e925bef 100644
--- a/cove-db/src/rows/user.rs
+++ b/cove-db/src/rows/user.rs
@@ -1,15 +1,11 @@
-use anyhow::anyhow;
use serde_json::Value;
-use sqlx::postgres::PgRow;
-use sqlx::Row;
use sqlx::types::time::OffsetDateTime;
+use cove_db_macros::DeriveTableRow;
use cove_net_common::id::SnowflakeID;
-use crate::part::BindQuery;
-use crate::part::insert::SqlInsert;
-use crate::part::sql_where::SqlWhere;
-use crate::rows::{InsertableRow, PartialTableRow, SelectableRow, TableRow, WhereRow};
use crate::types::user_status::UserStatus;
+#[derive(DeriveTableRow)]
+#[table_name("users")]
pub struct UserRow {
pub id: SnowflakeID,
pub username: String,
@@ -30,176 +26,4 @@ pub struct UserRow {
pub preferences: Value,
pub created_at: OffsetDateTime,
pub updated_at: OffsetDateTime,
-}
-
-#[derive(Default)]
-pub struct PartialUserRow {
- pub id: Option,
- pub username: Option,
- pub discriminator: Option,
- pub avatar_hash: Option