12 Commits

19 changed files with 2209 additions and 474 deletions
-9
View File
@@ -1,9 +0,0 @@
{
"recommendations": [
"vadimcn.vscode-lldb",
"barbosshack.crates-io",
"usernamehw.errorlens",
"tamasfe.even-better-toml",
"rust-lang.rust-analyzer",
]
}
-33
View File
@@ -1,33 +0,0 @@
{
// VSCode
"editor.detectIndentation": false,
"editor.insertSpaces": false,
"editor.tabSize": 4,
"files.exclude": {
"**/target": true,
"**/Cargo.lock": true,
},
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true,
"files.watcherExclude": {
"**/.git/**": true,
"**/target/**": true,
},
// Extensions
"crates.listPreReleases": true,
"evenBetterToml.formatter.alignComments": true,
"evenBetterToml.formatter.alignEntries": false,
"evenBetterToml.formatter.allowedBlankLines": 1,
"evenBetterToml.formatter.arrayAutoExpand": true,
"evenBetterToml.formatter.arrayTrailingComma": true,
"evenBetterToml.formatter.columnWidth": 80,
"evenBetterToml.formatter.reorderKeys": true,
"evenBetterToml.formatter.trailingNewline": true,
"rust-analyzer.imports.granularity.enforce": true,
"rust-analyzer.imports.granularity.group": "module",
"rust-analyzer.imports.group.enable": true,
"rust-analyzer.imports.merge.glob": false,
"rust-analyzer.imports.preferNoStd": true,
"rust-analyzer.showUnlinkedFileNotification": false,
}
+25
View File
@@ -0,0 +1,25 @@
{
"languages": {
"TOML": {
"format_on_save": "on",
"formatter": { "language_server": { "name": "tombi" } },
},
},
"lsp": {
"rust-analyzer": {
"initialization_options": {
"imports": {
"granularity": { "enforce": true, "group": "module" },
"group": { "enable": true },
"merge": { "glob": false },
"preferNoStd": true,
},
"server": {
"extraEnv": {
"RUSTUP_TOOLCHAIN": "stable",
},
},
},
},
},
}
+2 -1
View File
@@ -3,4 +3,5 @@ How to Contribute
We'd love to accept your patches and contributions to this project. We'd love to accept your patches and contributions to this project.
We just need you to follow the Contributor License Agreement outlined We just need you to follow the Contributor License Agreement outlined
in the latest v0.0.x of https://github.com/Skrunix/license in the latest v0.0.x of https://git.skrundz.dev/skrunix/license
(mirrored to https://github.com/skrunix/license)
Generated
+1667 -373
View File
File diff suppressed because it is too large Load Diff
+23 -19
View File
@@ -1,16 +1,22 @@
[workspace] [workspace]
members = ["seamantic"]
resolver = "2" resolver = "2"
members = ["seamantic"]
[workspace.package] [workspace.package]
authors = []
edition = "2024"
license-file = "LICENSE.md" license-file = "LICENSE.md"
rust-version = "1.85.0"
[workspace.lints.rust] edition = "2024"
arithmetic_overflow = "forbid" rust-version = "1.88.0"
unsafe_code = "forbid"
[workspace.dependencies]
seamantic = { path = "seamantic", version = "=0.0.14", default-features = false }
sea-orm = { version = "=2.0.0-rc.38", default-features = false }
sea-orm-migration = { version = "=2.0.0-rc.38", default-features = false }
serde = { version = "^1", default-features = false }
serde_test = { version = "^1", default-features = false }
tokio = { version = "^1", default-features = false }
[workspace.lints.clippy] [workspace.lints.clippy]
arithmetic_side_effects = "forbid" arithmetic_side_effects = "forbid"
@@ -22,20 +28,18 @@ indexing_slicing = "forbid"
integer_division = "forbid" integer_division = "forbid"
integer_division_remainder_used = "forbid" integer_division_remainder_used = "forbid"
transmute_undefined_repr = "forbid" transmute_undefined_repr = "forbid"
unchecked_duration_subtraction = "forbid" unchecked_time_subtraction = "forbid"
unwrap_used = "forbid" unwrap_used = "forbid"
[workspace.lints.rust]
arithmetic_overflow = "forbid"
missing_docs = "forbid"
unsafe_code = "forbid"
unused_doc_comments = "forbid"
[profile.release] [profile.release]
codegen-units = 1
lto = "fat"
opt-level = 3 opt-level = 3
overflow-checks = true
strip = "debuginfo" strip = "debuginfo"
overflow-checks = true
[workspace.dependencies] lto = "fat"
sea-orm = { version = "2.0.0-rc.5", default-features = false } codegen-units = 1
sea-orm-migration = { version = "2.0.0-rc.5", default-features = false }
serde = { version = "^1", default-features = false }
serde_test = { version = "^1", default-features = false }
tokio = { version = "^1", default-features = false }
+2
View File
@@ -9,7 +9,9 @@ A library to enhance SeaORM
- build: `cargo hack --feature-powerset build` - build: `cargo hack --feature-powerset build`
- clippy: `cargo hack --feature-powerset clippy -- -D warnings` - clippy: `cargo hack --feature-powerset clippy -- -D warnings`
- test: `cargo hack --feature-powerset test` - test: `cargo hack --feature-powerset test`
- test old: `cargo +1.88 hack --feature-powerset test`
- example: `cargo run --example=migrations --features=sqlite` - example: `cargo run --example=migrations --features=sqlite`
- fmt: `cargo fmt --check` - fmt: `cargo fmt --check`
- docs: `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features` - docs: `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features`
- semver: `cargo semver-checks --all-features`
- publish: `cargo publish --dry-run -p seamantic` - publish: `cargo publish --dry-run -p seamantic`
+19 -16
View File
@@ -1,36 +1,39 @@
[package] [package]
name = "seamantic" name = "seamantic"
version = "0.0.4" version = "0.0.14"
license-file.workspace = true
categories = []
description = "A library to enhance SeaORM" description = "A library to enhance SeaORM"
repository = "https://github.com/QuantumShade/seamantic" repository = "https://github.com/QuantumShade/seamantic"
categories = []
authors.workspace = true
edition.workspace = true edition.workspace = true
license-file.workspace = true
rust-version.workspace = true rust-version.workspace = true
[lints] [package.metadata.docs.rs]
workspace = true all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[[example]] [[example]]
name = "migrations" name = "migrations"
path = "examples/migrations/main.rs" path = "examples/migrations/main.rs"
required-features = ["sqlite"] required-features = ["sqlite"]
[dependencies]
sea-orm = { workspace = true }
sea-orm-migration = { workspace = true }
serde = { workspace = true, features = ["derive", "std"], optional = true }
[dev-dependencies]
sea-orm = { workspace = true, features = ["entity-registry", "schema-sync"] }
sea-orm-migration = { workspace = true, features = ["runtime-tokio-rustls"] }
serde_test = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt"] }
[features] [features]
default = [] default = []
serde = ["dep:serde"] serde = ["dep:serde"]
sqlite = ["sea-orm-migration/sqlx-sqlite"] sqlite = ["sea-orm-migration/sqlx-sqlite"]
[dependencies] [lints]
sea-orm = { workspace = true } 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"] }
+3
View File
@@ -1,9 +1,12 @@
//! A library for enhacing SeaORM //! A library for enhacing SeaORM
#![cfg_attr(docsrs, feature(doc_cfg))]
pub use sea_orm; pub use sea_orm;
pub use sea_orm_migration; pub use sea_orm_migration;
pub mod model; pub mod model;
pub mod orm;
pub mod schema; pub mod schema;
/// A macro for defining a Migrator with a custom migration table while /// A macro for defining a Migrator with a custom migration table while
+17 -5
View File
@@ -3,7 +3,7 @@
use core::time::Duration; use core::time::Duration;
use sea_orm::sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr}; 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};
// "u64 unsupported by sqlx-sqlite", so use i64 as the bit representation // "u64 unsupported by sqlx-sqlite", so use i64 as the bit representation
type SeaOrmRepr = i64; type SeaOrmRepr = i64;
@@ -13,7 +13,7 @@ type DurationRepr = u64;
/// ///
/// ### Warning: /// ### Warning:
/// Sub-second precision will be lost /// Sub-second precision will be lost
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))] #[cfg_attr(feature = "serde", serde(transparent))]
#[repr(transparent)] #[repr(transparent)]
@@ -40,7 +40,7 @@ impl ValueType for Seconds {
} }
fn type_name() -> String { fn type_name() -> String {
core::any::type_name::<Duration>().to_string() core::any::type_name::<Self>().to_string()
} }
fn array_type() -> ArrayType { fn array_type() -> ArrayType {
@@ -67,6 +67,15 @@ impl TryGetable for Seconds {
} }
} }
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)
}
}
impl Nullable for Seconds { impl Nullable for Seconds {
fn null() -> Value { fn null() -> Value {
SeaOrmRepr::null() SeaOrmRepr::null()
@@ -76,23 +85,26 @@ impl Nullable for Seconds {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use sea_orm::{ use sea_orm::{
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EnumIter, ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
PrimaryKeyTrait, EnumIter, PrimaryKeyTrait,
}; };
use super::Seconds; use super::Seconds;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)] #[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "seconds")] #[sea_orm(table_name = "seconds")]
pub struct Model { pub struct Model {
#[sea_orm(primary_key, auto_increment = false)] #[sea_orm(primary_key, auto_increment = false)]
id: u8, id: u8,
#[sea_orm(primary_key, auto_increment = false)]
seconds: Seconds, seconds: Seconds,
nullable: Option<Seconds>, nullable: Option<Seconds>,
} }
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)] #[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {} pub enum Relation {}
} }
+7 -3
View File
@@ -12,6 +12,8 @@ use sea_orm::{ColIdx, ColumnType, DbErr, QueryResult, TryFromU64, TryGetError, T
pub type SeaOrmRepr = i64; pub type SeaOrmRepr = i64;
/// An opaque type representing a row ID /// An opaque type representing a row ID
///
/// IDs should be tagged with `#[sea_orm(primary_key, auto_increment = false)]`
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))] #[cfg_attr(feature = "serde", serde(transparent))]
#[repr(transparent)] #[repr(transparent)]
@@ -78,7 +80,7 @@ impl<T> Id<T> {
} }
impl<T> fmt::Debug for 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") f.debug_struct("Id")
.field("T", &core::any::type_name::<T>()) .field("T", &core::any::type_name::<T>())
.field("id", &self.id) .field("id", &self.id)
@@ -140,12 +142,13 @@ impl<T> Nullable for Id<T> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use sea_orm::{ use sea_orm::{
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EnumIter, ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
PrimaryKeyTrait, EnumIter, PrimaryKeyTrait,
}; };
use super::Id; use super::Id;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)] #[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "ids")] #[sea_orm(table_name = "ids")]
pub struct Model { pub struct Model {
@@ -156,6 +159,7 @@ mod tests {
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)] #[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {} 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 {}
}
}
+1
View File
@@ -2,4 +2,5 @@
pub mod duration; pub mod duration;
pub mod id; pub mod id;
pub mod ip;
pub mod path; pub mod path;
+16 -4
View File
@@ -9,7 +9,7 @@ use std::os::unix::ffi::OsStringExt as _;
compile_error!("PathBytes is not supported on Windows"); compile_error!("PathBytes is not supported on Windows");
use sea_orm::sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr}; 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>; type SeaOrmRepr = Vec<u8>;
@@ -47,7 +47,7 @@ impl ValueType for PathBytes {
} }
fn type_name() -> String { fn type_name() -> String {
core::any::type_name::<PathBuf>().to_string() core::any::type_name::<Self>().to_string()
} }
fn array_type() -> ArrayType { 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 { impl Nullable for PathBytes {
fn null() -> Value { fn null() -> Value {
SeaOrmRepr::null() SeaOrmRepr::null()
@@ -83,23 +92,26 @@ impl Nullable for PathBytes {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use sea_orm::{ use sea_orm::{
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EnumIter, ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
PrimaryKeyTrait, EnumIter, PrimaryKeyTrait,
}; };
use super::PathBytes; use super::PathBytes;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)] #[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "paths")] #[sea_orm(table_name = "paths")]
pub struct Model { pub struct Model {
#[sea_orm(primary_key, auto_increment = false)] #[sea_orm(primary_key, auto_increment = false)]
id: u8, id: u8,
#[sea_orm(primary_key, auto_increment = false)]
path: PathBytes, path: PathBytes,
nullable: Option<PathBytes>, nullable: Option<PathBytes>,
} }
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)] #[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {} 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> {}
}
+1
View File
@@ -3,4 +3,5 @@
#[cfg(feature = "sqlite")] #[cfg(feature = "sqlite")]
mod sqlite; mod sqlite;
#[cfg(feature = "sqlite")] #[cfg(feature = "sqlite")]
#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))]
pub use sqlite::*; pub use sqlite::*;
+198 -11
View File
@@ -11,34 +11,221 @@ use sea_orm_migration::sea_query::{ColumnDef, IntoIden};
/// and should be tagged with: /// and should be tagged with:
/// ///
/// `#[sea_orm(primary_key, auto_increment = false)]` /// `#[sea_orm(primary_key, auto_increment = false)]`
///
/// When using the new entity format:
///
/// use sea_orm::entity::prelude::*;
/// use seamantic::model::id::Id;
///
/// #[sea_orm::model]
/// #[derive(Debug, Clone, DeriveEntityModel)]
/// #[sea_orm(table_name = "rowid_test")]
/// pub struct Model {
/// #[sea_orm(primary_key, auto_increment = false)]
/// id: Id<Model>,
/// }
/// impl ActiveModelBehavior for ActiveModel {}
pub fn sqlite_rowid_alias<T: IntoIden>(name: T) -> ColumnDef { pub fn sqlite_rowid_alias<T: IntoIden>(name: T) -> ColumnDef {
integer_null(name).primary_key().take() integer_null(name).primary_key().take()
} }
/// Set the column to be a case insensitive string /// Set the column to be a case insensitive string
///
/// When using the new entity format:
///
/// use sea_orm::entity::prelude::*;
/// use seamantic::model::id::Id;
///
/// #[sea_orm::model]
/// #[derive(Debug, Clone, DeriveEntityModel)]
/// #[sea_orm(table_name = "nocase_test")]
/// pub struct Model {
/// #[sea_orm(primary_key, auto_increment = false)]
/// id: i64,
/// #[sea_orm(extra = "COLLATE NOCASE")]
/// nocase: String,
/// }
/// impl ActiveModelBehavior for ActiveModel {}
pub fn sqlite_case_insensitive_string<T: IntoIden>(name: T) -> ColumnDef { pub fn sqlite_case_insensitive_string<T: IntoIden>(name: T) -> ColumnDef {
string(name).extra("COLLATE NOCASE").take() string(name).extra("COLLATE NOCASE").take()
} }
#[cfg(test)]
mod entity_tests {
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
pub async fn new_initialized_memory_db() -> DatabaseConnection {
let options = ConnectOptions::new("sqlite::memory:");
let db = Database::connect(options)
.await
.expect("Database::connect()");
db.get_schema_registry("seamantic::schema::sqlite::*")
.sync(&db)
.await
.expect("db.get_schema_registry().sync()");
db
}
mod rowid_test {
use sea_orm::ActiveValue::{NotSet, Set};
use sea_orm::entity::prelude::*;
use crate::model::id::{Id, SeaOrmRepr};
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "rowid_test")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
id: Id<Model>,
}
impl ActiveModelBehavior for ActiveModel {}
#[tokio::test]
async fn test_sqlite_rowid_alias() {
let db = super::new_initialized_memory_db().await;
// Starts at 1 and increments
for i in 1..=3 {
let model = ActiveModel { id: NotSet };
let model = model.insert(&db).await.expect("insert");
assert_eq!(model.id.into_raw(), i);
}
// Delete the top number and re-add
for i in 3..=3 {
let model = ActiveModel {
id: Set(Id::from_raw(i)),
};
model.delete(&db).await.expect("delete");
let model = ActiveModel { id: NotSet };
let model = model.insert(&db).await.expect("insert");
assert_eq!(model.id.into_raw(), i);
}
// Jump to 100 and increment
for i in 100..=103 {
let model = ActiveModel {
id: Set(Id::from_raw(i)),
};
let model = model.insert(&db).await.expect("insert");
assert_eq!(model.id.into_raw(), i);
}
// Continue to increment
for i in 104..=104 {
let model = ActiveModel { id: NotSet };
let model = model.insert(&db).await.expect("insert");
assert_eq!(model.id.into_raw(), i);
}
// Jump to SeaOrmRepr::MAX and increment
for i in SeaOrmRepr::MAX..=SeaOrmRepr::MAX {
let model = ActiveModel {
id: Set(Id::from_raw(i)),
};
let model = model.insert(&db).await.expect("insert");
assert_eq!(model.id.into_raw(), i);
}
// Next ones are random around the center
for _ in 0..3 {
let model = ActiveModel { id: NotSet };
let model = model.insert(&db).await.expect("insert");
assert!(model.id.into_raw() > 0);
}
// Zero ID is valid
for i in 0..=0 {
let model = ActiveModel {
id: Set(Id::from_raw(i)),
};
let model = model.insert(&db).await.expect("insert");
assert_eq!(model.id.into_raw(), i);
}
// Negative ID is valid
for i in SeaOrmRepr::MIN..=(SeaOrmRepr::MIN + 3) {
let model = ActiveModel {
id: Set(Id::from_raw(i)),
};
let model = model.insert(&db).await.expect("insert");
assert_eq!(model.id.into_raw(), i);
}
}
}
mod nocase_test {
use sea_orm::ActiveValue::Set;
use sea_orm::entity::prelude::*;
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "nocase_test")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
id: i64,
case: String,
#[sea_orm(extra = "COLLATE NOCASE")]
nocase: String,
}
impl ActiveModelBehavior for ActiveModel {}
#[tokio::test]
async fn test_sqlite_case_insensitive_string() {
let db = super::new_initialized_memory_db().await;
let i = 50;
// Insert a lowercase string
{
let model = ActiveModel {
id: Set(i),
case: Set("abcd".to_owned()),
nocase: Set("abcd".to_owned()),
};
model.insert(&db).await.expect("insert");
}
// Query by uppercase string
{
Entity::find()
.filter(Column::Case.eq("ABCD"))
.one(&db)
.await
.expect("find by case insensitive string")
.ok_or(())
.expect_err("find by case insensitive string");
let model = Entity::find()
.filter(Column::Nocase.eq("ABCD"))
.one(&db)
.await
.expect("find by case insensitive string")
.expect("find by case insensitive string");
assert_eq!(model.id, i);
// The string should be read back as-is
assert_eq!(model.nocase, "abcd");
}
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use sea_orm::ActiveValue::{NotSet, Set};
use sea_orm::entity::prelude::*;
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
use sea_orm_migration::async_trait::async_trait; use sea_orm_migration::async_trait::async_trait;
use sea_orm_migration::sea_orm::QueryFilter; use sea_orm_migration::prelude::*;
use sea_orm_migration::sea_orm::{
ActiveModelBehavior, ActiveModelTrait, ActiveValue::NotSet, ActiveValue::Set, ColumnTrait,
ConnectOptions, Database, DatabaseConnection, DbErr, DeriveEntityModel,
DeriveMigrationName, DerivePrimaryKey, EntityTrait, EnumIter, PrimaryKeyTrait, RelationDef,
RelationTrait,
};
use sea_orm_migration::sea_query::{self, Iden, Table};
use sea_orm_migration::{MigrationTrait, MigratorTrait, SchemaManager};
use crate::model::id::{Id, SeaOrmRepr}; use crate::model::id::{Id, SeaOrmRepr};
use super::{sqlite_case_insensitive_string, sqlite_rowid_alias}; use super::{sqlite_case_insensitive_string, sqlite_rowid_alias};
async fn new_memory_db() -> DatabaseConnection { async fn new_memory_db() -> DatabaseConnection {
let options = ConnectOptions::new("sqlite:/tmp/db?mode=memory"); let options = ConnectOptions::new("sqlite::memory:");
Database::connect(options).await.expect("Database::connect") Database::connect(options).await.expect("Database::connect")
} }
@@ -228,7 +415,7 @@ mod tests {
// Query by uppercase string // Query by uppercase string
{ {
let model = Entity::find() let model = Entity::find()
.filter(Column::CiStr.contains("ABCD")) .filter(Column::CiStr.eq("ABCD"))
.one(&db) .one(&db)
.await .await
.expect("find by case insensitive string") .expect("find by case insensitive string")
+11
View File
@@ -0,0 +1,11 @@
toml-version = "v1.0.0"
[format.rules]
indent-style = "tab"
indent-width = 4
# Required for rust <1.94
[[schemas]]
toml-version = "v1.0.0"
path = "tombi://www.schemastore.org/cargo.json"
include = ["Cargo.toml"]