8 Commits

Author SHA1 Message Date
davidskrundz 9b868c98de Update sea-orm to 2.0.0-rc.16 2025-10-28 21:41:08 -06:00
davidskrundz 9cf1f1f0c5 Support IP addresses 2025-10-20 10:49:10 -06:00
davidskrundz 302612281f Update sea-orm to 2.0.0-rc.10 2025-10-13 23:18:16 -06:00
davidskrundz 96f260d02d std -> core 2025-10-13 21:30:39 -06:00
davidskrundz 2bd654249d Add upsert conflict capability 2025-09-20 20:40:00 -06:00
davidskrundz fcd437d608 Update sea-orm to 2.0.0-rc.7 2025-09-18 22:23:08 -06:00
davidskrundz 792abdadfe Update sea-orm to 2.0.0-rc.5 2025-08-29 23:10:24 -06:00
davidskrundz 0ede87c913 Fix u64 incompatibility 2025-08-17 21:34:14 -06:00
17 changed files with 679 additions and 371 deletions
Generated
+355 -322
View File
File diff suppressed because it is too large Load Diff
+12 -36
View File
@@ -1,23 +1,20 @@
[package]
name = "seamantic"
version = "0.0.2"
categories = []
description = "A library to enhance SeaORM"
repository = "https://github.com/QuantumShade/seamantic"
[workspace]
members = ["seamantic"]
resolver = "2"
[workspace.package]
authors = []
edition = "2024"
license-file = "LICENSE.md"
resolver = "2"
rust-version = "1.85.0"
[lints.rust]
[workspace.lints.rust]
arithmetic_overflow = "forbid"
missing_docs = "forbid"
unsafe_code = "forbid"
unused_doc_comments = "forbid"
[lints.clippy]
[workspace.lints.clippy]
arithmetic_side_effects = "forbid"
as_conversions = "forbid"
checked_conversions = "forbid"
@@ -37,31 +34,10 @@ opt-level = 3
overflow-checks = true
strip = "debuginfo"
[[example]]
name = "migrations"
path = "examples/migrations/main.rs"
required-features = ["sqlite"]
[workspace.dependencies]
sea-orm = { version = "2.0.0-rc.16", default-features = false }
sea-orm-migration = { version = "2.0.0-rc.16", default-features = false }
[features]
default = []
serde = ["dep:serde"]
sqlite = ["sea-orm-migration/sqlx-sqlite"]
[dependencies]
sea-orm = { version = "^1", default-features = false }
sea-orm-migration = { version = "^1", default-features = false }
serde = { version = "^1", default-features = false, features = [
"derive",
"std",
], optional = true }
[dev-dependencies]
sea-orm-migration = { version = "^1", default-features = false, features = [
"runtime-tokio-rustls",
] }
serde = { version = "^1", default-features = false }
serde_test = { version = "^1", default-features = false }
tokio = { version = "^1", default-features = false, features = [
"rt",
"macros",
] }
tokio = { version = "^1", default-features = false }
+3 -1
View File
@@ -9,7 +9,9 @@ A library to enhance SeaORM
- build: `cargo hack --feature-powerset build`
- clippy: `cargo hack --feature-powerset clippy -- -D warnings`
- test: `cargo hack --feature-powerset test`
- test old: `cargo +1.85 hack --feature-powerset test`
- example: `cargo run --example=migrations --features=sqlite`
- fmt: `cargo fmt --check`
- docs: `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features`
- publish: `cargo publish --dry-run`
- semver: `cargo semver-checks --all-features`
- publish: `cargo publish --dry-run -p seamantic`
+40
View File
@@ -0,0 +1,40 @@
[package]
name = "seamantic"
version = "0.0.9"
categories = []
description = "A library to enhance SeaORM"
repository = "https://github.com/QuantumShade/seamantic"
authors.workspace = true
edition.workspace = true
license-file.workspace = true
rust-version.workspace = true
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[lints]
workspace = true
[[example]]
name = "migrations"
path = "examples/migrations/main.rs"
required-features = ["sqlite"]
[features]
default = []
serde = ["dep:serde"]
sqlite = ["sea-orm-migration/sqlx-sqlite"]
[dependencies]
sea-orm = { workspace = true }
sea-orm-migration = { workspace = true }
serde = { workspace = true, features = ["derive", "std"], optional = true }
[dev-dependencies]
sea-orm-migration = { workspace = true, features = ["runtime-tokio-rustls"] }
serde_test = { workspace = true }
tokio = { workspace = true, features = ["rt", "macros"] }
+5
View File
@@ -0,0 +1,5 @@
# Seamantic
[![Crates Version](https://img.shields.io/crates/v/seamantic.svg)](https://crates.io/crates/seamantic)
A library to enhance SeaORM
+3
View File
@@ -1,9 +1,12 @@
//! A library for enhacing SeaORM
#![cfg_attr(docsrs, feature(doc_cfg))]
pub use sea_orm;
pub use sea_orm_migration;
pub mod model;
pub mod orm;
pub mod schema;
/// A macro for defining a Migrator with a custom migration table while
@@ -3,9 +3,11 @@
use core::time::Duration;
use sea_orm::sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr};
use sea_orm::{ColIdx, ColumnType, QueryResult, TryGetError, TryGetable, Value};
use sea_orm::{ColIdx, ColumnType, DbErr, QueryResult, TryFromU64, TryGetError, TryGetable, Value};
type SeaOrmRepr = u64;
// "u64 unsupported by sqlx-sqlite", so use i64 as the bit representation
type SeaOrmRepr = i64;
type DurationRepr = u64;
/// Wrapper around [Duration] to store a number of seconds
///
@@ -32,12 +34,13 @@ impl From<Seconds> for Duration {
impl ValueType for Seconds {
fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
<SeaOrmRepr as ValueType>::try_from(v)
.map(|i| DurationRepr::from_ne_bytes(i.to_ne_bytes()))
.map(Duration::from_secs)
.map(Self)
}
fn type_name() -> String {
core::any::type_name::<Duration>().to_string()
core::any::type_name::<Self>().to_string()
}
fn array_type() -> ArrayType {
@@ -58,6 +61,16 @@ impl From<Seconds> for Value {
impl TryGetable for Seconds {
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> {
SeaOrmRepr::try_get_by(res, index)
.map(|i| DurationRepr::from_ne_bytes(i.to_ne_bytes()))
.map(Duration::from_secs)
.map(Self)
}
}
impl TryFromU64 for Seconds {
fn try_from_u64(n: u64) -> Result<Self, DbErr> {
SeaOrmRepr::try_from_u64(n)
.map(|i| DurationRepr::from_ne_bytes(i.to_ne_bytes()))
.map(Duration::from_secs)
.map(Self)
}
@@ -72,23 +85,26 @@ impl Nullable for Seconds {
#[cfg(test)]
mod tests {
use sea_orm::{
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EnumIter,
PrimaryKeyTrait,
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
EnumIter, PrimaryKeyTrait,
};
use super::Seconds;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "seconds")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
id: u8,
#[sea_orm(primary_key, auto_increment = false)]
seconds: Seconds,
nullable: Option<Seconds>,
}
impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
}
@@ -78,7 +78,7 @@ impl<T> Id<T> {
}
impl<T> fmt::Debug for Id<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Id")
.field("T", &core::any::type_name::<T>())
.field("id", &self.id)
@@ -140,12 +140,13 @@ impl<T> Nullable for Id<T> {
#[cfg(test)]
mod tests {
use sea_orm::{
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EnumIter,
PrimaryKeyTrait,
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
EnumIter, PrimaryKeyTrait,
};
use super::Id;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "ids")]
pub struct Model {
@@ -156,6 +157,7 @@ mod tests {
impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
+179
View File
@@ -0,0 +1,179 @@
//! [IpAddr], [Ipv4Addr], and [Ipv6Addr] utilities
use core::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use sea_orm::sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr};
use sea_orm::{ColIdx, ColumnType, DbErr, QueryResult, TryFromU64, TryGetError, TryGetable, Value};
type SeaOrmRepr = String;
/// Wrapper around [IpAddr]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[repr(transparent)]
pub struct IpAddress(pub IpAddr);
/// Wrapper around [IpAddr]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[repr(transparent)]
pub struct Ipv4Address(pub Ipv4Addr);
/// Wrapper around [IpAddr]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[repr(transparent)]
pub struct Ipv6Address(pub Ipv6Addr);
macro_rules! impl_addr {
($t:ty, $inner:ty, $repr:ty) => {
impl From<$inner> for $t {
fn from(value: $inner) -> Self {
Self(value)
}
}
impl From<$t> for $inner {
fn from(value: $t) -> Self {
value.0
}
}
impl ValueType for $t {
fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
<SeaOrmRepr as ValueType>::try_from(v)
.and_then(|s| s.parse().map_err(|_| ValueTypeErr))
.map(Self)
}
fn type_name() -> String {
core::any::type_name::<Self>().to_string()
}
fn array_type() -> ArrayType {
SeaOrmRepr::array_type()
}
fn column_type() -> ColumnType {
SeaOrmRepr::column_type()
}
}
impl From<$t> for Value {
fn from(value: $t) -> Self {
value.0.to_string().into()
}
}
impl TryGetable for $t {
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> {
SeaOrmRepr::try_get_by(res, index)
.and_then(|s| s.parse().map_err(|_| TryGetError::Null(Self::type_name())))
.map(Self)
}
}
impl TryFromU64 for $t {
fn try_from_u64(n: u64) -> Result<Self, DbErr> {
SeaOrmRepr::try_from_u64(n)
.and_then(|s| {
s.parse()
.map_err(|_| DbErr::ConvertFromU64(core::any::type_name::<Self>()))
})
.map(Self)
}
}
impl Nullable for $t {
fn null() -> Value {
SeaOrmRepr::null()
}
}
};
}
impl_addr!(IpAddress, IpAddr, SeaOrmRepr);
impl_addr!(Ipv4Address, Ipv4Addr, SeaOrmRepr);
impl_addr!(Ipv6Address, Ipv6Addr, SeaOrmRepr);
#[cfg(test)]
mod tests {
mod ipaddress {
use sea_orm::{
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
EnumIter, PrimaryKeyTrait,
};
use super::super::IpAddress;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "ipaddress")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
id: u8,
#[sea_orm(primary_key, auto_increment = false)]
addr: IpAddress,
nullable: Option<IpAddress>,
}
impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
}
mod ipv4address {
use sea_orm::{
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
EnumIter, PrimaryKeyTrait,
};
use super::super::Ipv4Address;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "ipv4address")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
id: u8,
#[sea_orm(primary_key, auto_increment = false)]
addr: Ipv4Address,
nullable: Option<Ipv4Address>,
}
impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
}
mod ipv6address {
use sea_orm::{
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
EnumIter, PrimaryKeyTrait,
};
use super::super::Ipv6Address;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "ipv6address")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
id: u8,
#[sea_orm(primary_key, auto_increment = false)]
addr: Ipv6Address,
nullable: Option<Ipv6Address>,
}
impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
}
}
@@ -2,4 +2,5 @@
pub mod duration;
pub mod id;
pub mod ip;
pub mod path;
@@ -9,7 +9,7 @@ use std::os::unix::ffi::OsStringExt as _;
compile_error!("PathBytes is not supported on Windows");
use sea_orm::sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr};
use sea_orm::{ColIdx, ColumnType, QueryResult, TryGetError, TryGetable, Value};
use sea_orm::{ColIdx, ColumnType, DbErr, QueryResult, TryFromU64, TryGetError, TryGetable, Value};
type SeaOrmRepr = Vec<u8>;
@@ -47,7 +47,7 @@ impl ValueType for PathBytes {
}
fn type_name() -> String {
core::any::type_name::<PathBuf>().to_string()
core::any::type_name::<Self>().to_string()
}
fn array_type() -> ArrayType {
@@ -74,6 +74,15 @@ impl TryGetable for PathBytes {
}
}
impl TryFromU64 for PathBytes {
fn try_from_u64(n: u64) -> Result<Self, DbErr> {
SeaOrmRepr::try_from_u64(n)
.map(OsString::from_vec)
.map(PathBuf::from)
.map(Self)
}
}
impl Nullable for PathBytes {
fn null() -> Value {
SeaOrmRepr::null()
@@ -83,23 +92,26 @@ impl Nullable for PathBytes {
#[cfg(test)]
mod tests {
use sea_orm::{
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EnumIter,
PrimaryKeyTrait,
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
EnumIter, PrimaryKeyTrait,
};
use super::PathBytes;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "paths")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
id: u8,
#[sea_orm(primary_key, auto_increment = false)]
path: PathBytes,
nullable: Option<PathBytes>,
}
impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
}
+4
View File
@@ -0,0 +1,4 @@
//! Helpers for working with SeaORM
mod upsert;
pub use upsert::UpsertTrait;
+34
View File
@@ -0,0 +1,34 @@
use sea_orm::sea_query::{IntoColumnRef, OnConflict};
use sea_orm::{ActiveModelTrait, EntityTrait, Insert, Iterable};
/// This trait add a method on [Insert] to allow for upsert behavior
pub trait UpsertTrait: private::Sealed {
/// Set ON CONFLICT on primary key to update all other columns
fn on_conflict_upsert(self) -> Self;
}
fn primary_key_iter<A: ActiveModelTrait>()
-> impl Iterator<Item = <A::Entity as EntityTrait>::PrimaryKey> {
<A::Entity as EntityTrait>::PrimaryKey::iter()
}
fn column_iter<A: ActiveModelTrait>() -> impl Iterator<Item = <A::Entity as EntityTrait>::Column> {
<<A as ActiveModelTrait>::Entity as EntityTrait>::Column::iter()
}
impl<A: ActiveModelTrait> UpsertTrait for Insert<A> {
fn on_conflict_upsert(self) -> Self {
self.on_conflict(
OnConflict::columns(primary_key_iter::<A>())
.update_columns(column_iter::<A>().filter(|col| {
!primary_key_iter::<A>().any(|pk| pk.into_column_ref() == col.into_column_ref())
}))
.to_owned(),
)
}
}
mod private {
pub trait Sealed {}
impl<A: ::sea_orm::ActiveModelTrait> Sealed for ::sea_orm::Insert<A> {}
}
@@ -3,4 +3,5 @@
#[cfg(feature = "sqlite")]
mod sqlite;
#[cfg(feature = "sqlite")]
#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))]
pub use sqlite::*;