3 Commits

Author SHA1 Message Date
davidskrundz 5d9f14d8b3 Migrate to Zed and update sea-orm 2026-01-04 17:41:33 -07:00
davidskrundz 3bfebe424a Add tests around the new entity format 2025-11-02 10:38:53 -07:00
davidskrundz 9b868c98de Update sea-orm to 2.0.0-rc.16 2025-10-28 21:41:08 -06:00
9 changed files with 1395 additions and 365 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,
}
+33
View File
@@ -0,0 +1,33 @@
{
"project_name": null,
"auto_install_extensions": {
"tombi": true,
"cargo-appraiser": true,
},
"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",
},
},
},
},
},
}
Generated
+1122 -271
View File
File diff suppressed because it is too large Load Diff
+19 -20
View File
@@ -1,18 +1,19 @@
[workspace] [workspace]
members = ["seamantic"]
resolver = "2" resolver = "2"
members = ["seamantic"]
[workspace.package] [workspace.package]
authors = []
edition = "2024" edition = "2024"
license-file = "LICENSE.md"
rust-version = "1.85.0" rust-version = "1.85.0"
license-file = "LICENSE.md"
[workspace.lints.rust] [workspace.dependencies]
arithmetic_overflow = "forbid" sea-orm = { version = "2.0.0-rc.27", default-features = false }
missing_docs = "forbid" sea-orm-migration = { version = "2.0.0-rc.27", default-features = false }
unsafe_code = "forbid" seamantic = { path = "seamantic", version = "0.0.11", default-features = false }
unused_doc_comments = "forbid" 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"
@@ -24,20 +25,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.10", default-features = false } codegen-units = 1
sea-orm-migration = { version = "2.0.0-rc.10", default-features = false }
serde = { version = "^1", default-features = false }
serde_test = { version = "^1", default-features = false }
tokio = { version = "^1", default-features = false }
+17 -20
View File
@@ -1,40 +1,37 @@
[package] [package]
name = "seamantic" name = "seamantic"
version = "0.0.8" version = "0.0.11"
edition.workspace = true
categories = [] rust-version.workspace = true
description = "A library to enhance SeaORM" description = "A library to enhance SeaORM"
repository = "https://github.com/QuantumShade/seamantic" repository = "https://github.com/QuantumShade/seamantic"
authors.workspace = true
edition.workspace = true
license-file.workspace = true license-file.workspace = true
rust-version.workspace = true categories = []
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
[lints]
workspace = true
[[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"] }
+1 -1
View File
@@ -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, 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)]
+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(column_type = "Integer", primary_key, nullable, 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(column_type = "Integer", primary_key, nullable, 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")
+5
View File
@@ -0,0 +1,5 @@
toml-version = "v1.0.0"
[format.rules]
indent-style = "tab"
indent-width = 4