6 Commits

Author SHA1 Message Date
davidskrundz 800916c111 Update contributing 2026-05-31 17:09:20 -06:00
davidskrundz b8f25b635d Make Seconds Copy 2026-05-29 21:40:22 -06:00
davidskrundz e066509031 Update sea-orm to rc38 2026-04-18 19:36:59 -06:00
davidskrundz 0a585c09e5 Simplify Id requirements 2026-01-07 20:59:25 -07:00
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
12 changed files with 1830 additions and 400 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 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
+1551 -306
View File
File diff suppressed because it is too large Load Diff
+23 -21
View File
@@ -1,18 +1,22 @@
[workspace]
members = ["seamantic"]
resolver = "2"
members = ["seamantic"]
[workspace.package]
authors = []
edition = "2024"
license-file = "LICENSE.md"
rust-version = "1.85.0"
[workspace.lints.rust]
arithmetic_overflow = "forbid"
missing_docs = "forbid"
unsafe_code = "forbid"
unused_doc_comments = "forbid"
edition = "2024"
rust-version = "1.88.0"
[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]
arithmetic_side_effects = "forbid"
@@ -24,20 +28,18 @@ indexing_slicing = "forbid"
integer_division = "forbid"
integer_division_remainder_used = "forbid"
transmute_undefined_repr = "forbid"
unchecked_duration_subtraction = "forbid"
unchecked_time_subtraction = "forbid"
unwrap_used = "forbid"
[workspace.lints.rust]
arithmetic_overflow = "forbid"
missing_docs = "forbid"
unsafe_code = "forbid"
unused_doc_comments = "forbid"
[profile.release]
codegen-units = 1
lto = "fat"
opt-level = 3
overflow-checks = true
strip = "debuginfo"
[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 }
serde = { version = "^1", default-features = false }
serde_test = { version = "^1", default-features = false }
tokio = { version = "^1", default-features = false }
overflow-checks = true
lto = "fat"
codegen-units = 1
+1 -1
View File
@@ -9,7 +9,7 @@ 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`
- test old: `cargo +1.88 hack --feature-powerset test`
- example: `cargo run --example=migrations --features=sqlite`
- fmt: `cargo fmt --check`
- docs: `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features`
+16 -17
View File
@@ -1,40 +1,39 @@
[package]
name = "seamantic"
version = "0.0.9"
version = "0.0.14"
license-file.workspace = true
categories = []
description = "A library to enhance SeaORM"
repository = "https://github.com/QuantumShade/seamantic"
categories = []
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"]
[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]
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"] }
[lints]
workspace = true
+1 -1
View File
@@ -13,7 +13,7 @@ type DurationRepr = u64;
///
/// ### Warning:
/// 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", serde(transparent))]
#[repr(transparent)]
+2
View File
@@ -12,6 +12,8 @@ use sea_orm::{ColIdx, ColumnType, DbErr, QueryResult, TryFromU64, TryGetError, T
pub type SeaOrmRepr = i64;
/// 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", serde(transparent))]
#[repr(transparent)]
+198 -11
View File
@@ -11,34 +11,221 @@ use sea_orm_migration::sea_query::{ColumnDef, IntoIden};
/// and should be tagged with:
///
/// `#[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 {
integer_null(name).primary_key().take()
}
/// 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 {
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)]
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::sea_orm::QueryFilter;
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 sea_orm_migration::prelude::*;
use crate::model::id::{Id, SeaOrmRepr};
use super::{sqlite_case_insensitive_string, sqlite_rowid_alias};
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")
}
@@ -228,7 +415,7 @@ mod tests {
// Query by uppercase string
{
let model = Entity::find()
.filter(Column::CiStr.contains("ABCD"))
.filter(Column::CiStr.eq("ABCD"))
.one(&db)
.await
.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"]