Upgrade sea-orm to 2.0, update structure and lints

This commit is contained in:
2026-09-04 23:38:02 -06:00
parent 800916c111
commit a5454bda71
26 changed files with 1435 additions and 980 deletions
+4
View File
@@ -0,0 +1,4 @@
allow-expect-in-tests = true
check-private-items = true
allowed-duplicate-crates = []
+22 -6
View File
@@ -1,7 +1,23 @@
# OS Files *
*~ !**/
._*
.DS_Store
# Rust !.zed/settings.json
/target !.gitignore
!/CONTRIBUTING.md
!/LICENSE.md
!/README.md
!/.clippy.toml
!/.rustfmt.toml
!/.tombi.toml
!/Cargo.toml
!/Cargo.lock
!/justfile
!/rust-toolchain.toml
!/crates/**/*.rs
!/crates/**/Cargo.toml
!/crates/**/README.md
!/lints/*.toml
-1
View File
@@ -1,2 +1 @@
hard_tabs = true hard_tabs = true
wrap_comments = true
+2 -3
View File
@@ -1,10 +1,9 @@
toml-version = "v1.0.0"
[format.rules] [format.rules]
indent-style = "tab" indent-style = "tab"
indent-width = 4 indent-width = 4
line-width = 120
# Required for rust <1.94 # Disable TOML 1.1 features from tombi as long as we support rust <1.94
[[schemas]] [[schemas]]
toml-version = "v1.0.0" toml-version = "v1.0.0"
path = "tombi://www.schemastore.org/cargo.json" path = "tombi://www.schemastore.org/cargo.json"
+7 -6
View File
@@ -1,11 +1,12 @@
{ {
"languages": {
"TOML": {
"format_on_save": "on",
"formatter": { "language_server": { "name": "tombi" } },
},
},
"lsp": { "lsp": {
"just-lsp": {
"initialization_options": {
"formatting": {
"indentation": "\t",
},
},
},
"rust-analyzer": { "rust-analyzer": {
"initialization_options": { "initialization_options": {
"imports": { "imports": {
Generated
+492 -801
View File
File diff suppressed because it is too large Load Diff
+524 -28
View File
@@ -1,41 +1,537 @@
[workspace] [workspace]
resolver = "2" resolver = "3"
members = ["seamantic"] members = ["crates/*"]
[workspace.package] [workspace.package]
license-file = "LICENSE.md"
edition = "2024" edition = "2024"
rust-version = "1.88.0" rust-version = "1.94"
license-file = "LICENSE.md"
publish = false
[workspace.dependencies] [workspace.dependencies]
seamantic = { path = "seamantic", version = "=0.0.14", default-features = false } sea-orm = { version = "^2.0.0", default-features = false }
sea-orm-migration = { version = "^2.0.0", default-features = false }
sea-orm = { version = "=2.0.0-rc.38", default-features = false } serde = { version = "^1.0.220", default-features = false }
sea-orm-migration = { version = "=2.0.0-rc.38", default-features = false } serde_test = { version = "^1.0.0", default-features = false }
tokio = { version = "^1.38.2", 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" cargo = { level = "warn", priority = -1 }
as_conversions = "forbid" complexity = { level = "warn", priority = -1 }
checked_conversions = "forbid" correctness = { level = "deny", priority = -1 }
default_union_representation = "forbid" perf = { level = "warn", priority = -1 }
expect_used = "forbid" style = { level = "warn", priority = -1 }
indexing_slicing = "forbid" suspicious = { level = "warn", priority = -1 }
integer_division = "forbid"
integer_division_remainder_used = "forbid" absolute-paths = "allow"
transmute_undefined_repr = "forbid" alloc-instead-of-core = "warn"
unchecked_time_subtraction = "forbid" allow-attributes = "warn"
unwrap_used = "forbid" allow-attributes-without-reason = "deny"
arbitrary-source-item-ordering = "allow"
arithmetic-side-effects = "deny"
as-conversions = "deny"
as-pointer-underscore = "deny"
as-ptr-cast-mut = "deny"
as-underscore = "deny"
assertions-on-result-states = "warn"
assigning-clones = "warn"
big-endian-bytes = "allow"
bool-to-int-with-if = "warn"
borrow-as-ptr = "warn"
branches-sharing-code = "warn"
case-sensitive-file-extension-comparisons = "warn"
cast-lossless = "deny"
cast-possible-truncation = "deny"
cast-possible-wrap = "deny"
cast-precision-loss = "deny"
cast-ptr-alignment = "deny"
cast-sign-loss = "deny"
cfg-not-test = "deny"
checked-conversions = "deny"
clear-with-drain = "warn"
clone-on-ref-ptr = "warn"
cloned-instead-of-copied = "warn"
coerce-container-to-any = "warn"
cognitive-complexity = "allow"
collapsible-else-if = "warn"
collection-is-never-read = "warn"
comparison-chain = "warn"
copy-iterator = "warn"
create-dir = "warn"
dbg-macro = "warn"
debug-assert-with-mut-call = "deny"
decimal-bitwise-operands = "warn"
decimal-literal-representation = "warn"
default-numeric-fallback = "warn"
default-trait-access = "warn"
default-union-representation = "allow"
deref-by-slicing = "warn"
derive-partial-eq-without-eq = "warn"
disallowed-script-idents = "deny"
doc-broken-link = "warn"
doc-comment-double-space-linebreaks = "warn"
doc-include-without-cfg = "warn"
doc-link-code = "warn"
doc-link-with-quotes = "warn"
doc-markdown = "warn"
doc-paragraphs-missing-punctuation = "warn"
duration-suboptimal-units = "warn"
elidable-lifetime-names = "warn"
else-if-without-else = "warn"
empty-drop = "warn"
empty-enum-variants-with-brackets = "warn"
empty-enums = "warn"
empty-structs-with-brackets = "warn"
enum-glob-use = "warn"
equatable-if-let = "warn"
error-impl-error = "warn"
exhaustive-enums = "warn"
exhaustive-structs = "warn"
exit = "warn"
expect-used = "warn"
expl-impl-clone-on-copy = "warn"
explicit-deref-methods = "warn"
explicit-into-iter-loop = "warn"
explicit-iter-loop = "warn"
fallible-impl-from = "warn"
field-scoped-visibility-modifiers = "warn"
filetype-is-file = "warn"
filter-map-next = "warn"
flat-map-option = "warn"
float-arithmetic = "warn"
float-cmp = "warn"
float-cmp-const = "warn"
fn-params-excessive-bools = "allow"
fn-to-numeric-cast-any = "warn"
format-collect = "warn"
format-push-string = "warn"
future-not-send = "warn"
get-unwrap = "warn"
host-endian-bytes = "allow"
if-not-else = "warn"
if-then-some-else-none = "warn"
ignore-without-reason = "warn"
ignored-unit-patterns = "warn"
impl-trait-in-params = "warn"
implicit-clone = "warn"
implicit-hasher = "warn"
implicit-return = "allow"
imprecise-flops = "warn"
inconsistent-struct-constructor = "warn"
index-refutable-slice = "deny"
indexing-slicing = "deny"
inefficient-to-string = "warn"
infinite-loop = "warn"
inline-always = "warn"
inline-asm-x86-att-syntax = "warn"
inline-asm-x86-intel-syntax = "warn"
inline-modules = "allow"
inline-trait-bounds = "allow"
integer-division = "deny"
integer-division-remainder-used = "deny"
into-iter-without-iter = "warn"
invalid-upcast-comparisons = "warn"
ip-constant = "warn"
items-after-statements = "warn"
iter-filter-is-ok = "warn"
iter-filter-is-some = "warn"
iter-not-returning-iterator = "warn"
iter-on-empty-collections = "warn"
iter-on-single-items = "warn"
iter-over-hash-type = "warn"
iter-with-drain = "warn"
iter-without-into-iter = "warn"
large-digit-groups = "warn"
large-futures = "warn"
large-include-file = "warn"
large-stack-arrays = "warn"
large-stack-frames = "warn"
large-types-passed-by-value = "warn"
let-underscore-must-use = "warn"
let-underscore-untyped = "warn"
linkedlist = "warn"
literal-string-with-formatting-args = "warn"
little-endian-bytes = "allow"
lossy-float-literal = "warn"
macro-use-imports = "warn"
manual-assert = "warn"
manual-assert-eq = "warn"
manual-ilog2 = "warn"
manual-instant-elapsed = "warn"
manual-is-power-of-two = "warn"
manual-is-variant-and = "warn"
manual-let-else = "warn"
manual-midpoint = "warn"
manual-string-new = "warn"
many-single-char-names = "warn"
map-err-ignore = "warn"
map-unwrap-or = "warn"
map-with-unused-argument-over-ranges = "warn"
match-bool = "warn"
match-same-arms = "warn"
match-wild-err-arm = "warn"
match-wildcard-for-single-variants = "warn"
maybe-infinite-iter = "warn"
mem-forget = "warn"
min-ident-chars = "allow"
mismatching-type-param-order = "warn"
missing-assert-message = "warn"
missing-asserts-for-indexing = "warn"
missing-const-for-fn = "warn"
missing-docs-in-private-items = "warn"
missing-errors-doc = "warn"
missing-fields-in-debug = "warn"
missing-inline-in-public-items = "warn"
missing-panics-doc = "warn"
missing-trait-methods = "warn"
mixed-read-write-in-expression = "warn"
mod-module-files = "allow"
module-name-repetitions = "warn"
modulo-arithmetic = "warn"
multiple-crate-versions = "allow"
multiple-inherent-impl = "warn"
multiple-unsafe-ops-per-block = "warn"
must-use-candidate = "warn"
mut-mut = "warn"
mutex-atomic = "warn"
mutex-integer = "warn"
naive-bytecount = "warn"
needless-bitwise-bool = "warn"
needless-collect = "warn"
needless-continue = "warn"
needless-for-each = "warn"
needless-pass-by-ref-mut = "warn"
needless-pass-by-value = "warn"
needless-raw-string-hashes = "warn"
needless-raw-strings = "warn"
needless-type-cast = "warn"
no-effect-underscore-binding = "warn"
no-mangle-with-rust-abi = "warn"
non-ascii-literal = "allow"
non-send-fields-in-send-ty = "warn"
non-std-lazy-statics = "warn"
non-zero-suggestions = "warn"
nonminimal-bool = "warn"
nonstandard-macro-braces = "warn"
option-as-ref-cloned = "warn"
option-if-let-else = "warn"
option-option = "warn"
or-fun-call = "warn"
overly-complex-bool-expr = "allow"
panic = "warn"
panic-in-result-fn = "warn"
partial-pub-fields = "warn"
path-buf-push-overwrite = "warn"
pathbuf-init-then-push = "warn"
pattern-type-mismatch = "allow"
pointer-format = "warn"
precedence-bits = "warn"
print-stderr = "warn"
print-stdout = "warn"
ptr-as-ptr = "warn"
ptr-cast-constness = "warn"
ptr-offset-by-literal = "warn"
pub-underscore-fields = "warn"
pub-use = "allow"
pub-with-shorthand = "allow"
pub-without-shorthand = "warn"
question-mark-used = "allow"
range-minus-one = "warn"
range-plus-one = "warn"
rc-buffer = "warn"
rc-mutex = "warn"
read-zero-byte-vec = "warn"
redundant-clone = "warn"
redundant-closure-for-method-calls = "warn"
redundant-else = "warn"
redundant-pub-crate = "allow"
redundant-test-prefix = "warn"
redundant-type-annotations = "warn"
ref-as-ptr = "warn"
ref-binding-to-reference = "warn"
ref-option = "warn"
ref-option-ref = "warn"
ref-patterns = "warn"
renamed-function-params = "warn"
rest-pat-in-fully-bound-structs = "warn"
return-and-then = "warn"
return-self-not-must-use = "warn"
same-functions-in-if-condition = "warn"
same-length-and-capacity = "warn"
same-name-method = "warn"
search-is-some = "warn"
self-named-module-files = "warn"
self-only-used-in-recursion = "warn"
semicolon-if-nothing-returned = "warn"
semicolon-inside-block = "warn"
semicolon-outside-block = "allow"
separated-literal-suffix = "allow"
set-contains-or-insert = "warn"
shadow-reuse = "allow"
shadow-same = "allow"
shadow-unrelated = "allow"
should-panic-without-expect = "warn"
significant-drop-in-scrutinee = "warn"
significant-drop-tightening = "warn"
similar-names = "allow"
single-call-fn = "allow"
single-char-lifetime-names = "allow"
single-char-pattern = "allow"
single-match-else = "warn"
single-option-map = "warn"
stable-sort-primitive = "warn"
std-instead-of-alloc = "warn"
std-instead-of-core = "warn"
str-split-at-newline = "warn"
str-to-string = "warn"
string-add = "warn"
string-add-assign = "warn"
string-lit-as-bytes = "warn"
string-lit-chars-any = "warn"
string-slice = "warn"
struct-excessive-bools = "allow"
struct-field-names = "warn"
suboptimal-flops = "warn"
suspicious-operation-groupings = "warn"
suspicious-xor-used-as-pow = "warn"
tests-outside-test-module = "warn"
todo = "warn"
too-long-first-doc-paragraph = "allow"
too-many-lines = "allow"
trailing-empty-array = "warn"
trait-duplication-in-bounds = "warn"
transmute-ptr-to-ptr = "warn"
transmute-undefined-repr = "warn"
trivial-regex = "warn"
trivially-copy-pass-by-ref = "warn"
try-err = "warn"
tuple-array-conversions = "warn"
type-repetition-in-bounds = "warn"
unchecked-time-subtraction = "deny"
undocumented-unsafe-blocks = "deny"
unicode-not-nfc = "warn"
unimplemented = "warn"
uninhabited-references = "warn"
uninlined-format-args = "warn"
unnecessary-box-returns = "warn"
unnecessary-debug-formatting = "warn"
unnecessary-join = "warn"
unnecessary-literal-bound = "warn"
unnecessary-safety-comment = "warn"
unnecessary-safety-doc = "warn"
unnecessary-self-imports = "warn"
unnecessary-semicolon = "warn"
unnecessary-struct-initialization = "warn"
unnecessary-trailing-comma = "warn"
unnecessary-wraps = "warn"
unneeded-field-pattern = "warn"
unnested-or-patterns = "warn"
unreachable = "warn"
unreadable-literal = "warn"
unsafe-derive-deserialize = "warn"
unseparated-literal-suffix = "warn"
unused-async = "warn"
unused-async-trait-impl = "warn"
unused-peekable = "warn"
unused-result-ok = "warn"
unused-rounding = "warn"
unused-self = "warn"
unused-trait-names = "warn"
unwrap-in-result = "warn"
unwrap-used = "warn"
use-debug = "warn"
use-self = "warn"
used-underscore-binding = "warn"
used-underscore-items = "warn"
useless-let-if-seq = "warn"
verbose-bit-mask = "warn"
verbose-file-reads = "warn"
volatile-composites = "warn"
while-float = "warn"
wildcard-enum-match-arm = "warn"
wildcard-imports = "warn"
with-capacity-zero = "warn"
zero-sized-map-values = "warn"
[workspace.lints.rust] [workspace.lints.rust]
arithmetic_overflow = "forbid" deprecated-safe = "forbid"
missing_docs = "forbid" future-incompatible = "deny"
unsafe_code = "forbid" keyword-idents = "deny"
unused_doc_comments = "forbid" let-underscore = "warn"
nonstandard-style = "warn"
refining-impl-trait = "warn"
rust-2018-compatibility = "deny"
rust-2018-idioms = "deny"
rust-2021-compatibility = "deny"
rust-2024-compatibility = "deny"
unknown-or-malformed-diagnostic-attributes = "deny"
unused = { level = "warn", priority = -1 }
ambiguous-glob-reexports = "warn"
ambiguous-negative-literals = "deny"
ambiguous-wide-pointer-comparisons = "warn"
arithmetic-overflow = "deny"
asm-sub-register = "warn"
async-fn-in-trait = "warn"
bad-asm-style = "warn"
binary-asm-labels = "deny"
bindings-with-variant-name = "deny"
break-with-label-and-loop = "warn"
c-void-returns = "warn"
clashing-extern-declarations = "warn"
closure-returning-async-block = "warn"
confusable-idents = "warn"
const-item-interior-mutations = "warn"
const-item-mutation = "warn"
dangerous-implicit-autorefs = "deny"
dangling-pointers-from-locals = "warn"
dangling-pointers-from-temporaries = "warn"
dead-code-pub-in-binary = "warn"
deprecated = "warn"
deprecated-in-future = "allow"
deprecated-where-clause-location = "warn"
deref-into-dyn-supertrait = "warn"
deref-nullptr = "deny"
double-negations = "warn"
drop-bounds = "warn"
dropping-copy-types = "warn"
dropping-references = "warn"
duplicate-features = "deny"
duplicate-macro-attributes = "warn"
dyn-drop = "warn"
enum-intrinsics-non-enums = "deny"
explicit-builtin-cfgs-in-flags = "deny"
exported-private-dependencies = "warn"
ffi-unwind-calls = "warn"
for-loops-over-fallibles = "warn"
forgetting-copy-types = "warn"
forgetting-references = "warn"
function-casts-as-integer = "warn"
function-item-references = "warn"
hidden-glob-reexports = "warn"
impl-trait-redundant-captures = "warn"
improper-ctypes = "warn"
improper-ctypes-definitions = "warn"
improper-gpu-kernel-arg = "warn"
incomplete-features = "warn"
incomplete-include = "deny"
ineffective-unstable-trait-impl = "deny"
inline-no-sanitize = "warn"
integer-to-ptr-transmutes = "warn"
internal-features = "warn"
invalid-atomic-ordering = "deny"
invalid-doc-attributes = "warn"
invalid-from-utf8 = "warn"
invalid-from-utf8-unchecked = "deny"
invalid-nan-comparisons = "warn"
invalid-null-arguments = "deny"
invalid-reference-casting = "deny"
invalid-runtime-symbol-definitions = "deny"
invalid-value = "warn"
irrefutable-let-patterns = "warn"
large-assignments = "warn"
linker-info = "allow"
linker-messages = "warn"
long-running-const-eval = "deny"
macro-use-extern-crate = "warn"
meta-variable-misuse = "warn"
mismatched-lifetime-syntaxes = "warn"
missing-abi = "warn"
missing-copy-implementations = "warn"
missing-debug-implementations = "warn"
missing-docs = "warn"
missing-gpu-kernel-export-name = "warn"
mixed-script-confusables = "warn"
mutable-transmutes = "deny"
named-arguments-used-positionally = "warn"
named-asm-labels = "deny"
no-mangle-const-items = "deny"
no-mangle-generic-items = "warn"
non-ascii-idents = "deny"
non-contiguous-range-endpoints = "warn"
non-local-definitions = "warn"
non-shorthand-field-patterns = "warn"
noop-method-call = "warn"
opaque-hidden-inferred-bound = "warn"
overflowing-literals = "deny"
overlapping-range-endpoints = "warn"
private-bounds = "warn"
private-interfaces = "warn"
ptr-to-integer-transmute-in-consts = "warn"
redundant-imports = "warn"
redundant-lifetimes = "warn"
renamed-and-removed-lints = "warn"
rtsan-nonblocking-async = "warn"
single-use-lifetimes = "warn"
special-module-name = "warn"
stable-features = "warn"
suspicious-double-ref-op = "warn"
suspicious-runtime-symbol-definitions = "warn"
text-direction-codepoint-in-comment = "deny"
text-direction-codepoint-in-literal = "deny"
trivial-bounds = "warn"
trivial-casts = "warn"
trivial-numeric-casts = "warn"
type-alias-bounds = "warn"
uncommon-codepoints = "warn"
unconditional-panic = "deny"
unconditional-recursion = "warn"
undropped-manually-drops = "deny"
unexpected-cfgs = "warn"
unfulfilled-lint-expectations = "warn"
ungated-async-fn-track-caller = "warn"
unit-bindings = "warn"
unknown-crate-types = "deny"
unknown-lints = "warn"
unnameable-test-items = "warn"
unnameable-types = "warn"
unnecessary-transmutes = "warn"
unpredictable-function-pointer-comparisons = "warn"
unreachable-cfg-select-predicates = "warn"
unreachable-pub = "warn"
unsafe-code = "forbid"
unstable-features = "forbid"
unused-associated-type-bounds = "warn"
unused-comparisons = "warn"
unused-crate-dependencies = "warn"
unused-import-braces = "warn"
unused-lifetimes = "warn"
unused-qualifications = "warn"
unused-results = "warn"
useless-deprecated = "deny"
useless-ptr-null-checks = "warn"
uses-power-alignment = "warn"
variant-size-differences = "warn"
warnings = "warn"
while-true = "warn"
# unstable lints
# default-overrides-default-fields = "deny"
# deprecated-llvm-intrinsic = "warn"
# implicit-provenance-casts = "deny"
# multiple-supertrait-upcastable = "warn"
# must-not-suspend = "warn"
# non-exhaustive-omitted-patterns = "warn"
# resolving-to-items-shadowing-supertrait-items = "warn"
# shadowing-supertrait-items = "warn"
# tail-call-track-caller = "warn"
# test-unstable-lint = "deny"
# unqualified-local-imports = "deny"
[workspace.lints.rustdoc]
bare-urls = "warn"
broken-intra-doc-links = "warn"
invalid-codeblock-attributes = "warn"
invalid-html-tags = "warn"
invalid-rust-codeblocks = "warn"
missing-crate-level-docs = "warn"
private-doc-tests = "warn"
private-intra-doc-links = "warn"
redundant-explicit-links = "warn"
unescaped-backticks = "warn"
# unstable lints
# missing-doc-code-examples = "warn"
[profile.release] [profile.release]
opt-level = 3 opt-level = 3
+11 -12
View File
@@ -1,17 +1,16 @@
# Seamantic # Seamantic
[![Crates Version](https://img.shields.io/crates/v/seamantic.svg)](https://crates.io/crates/seamantic) ## Development
A library to enhance SeaORM Run the full validation suite before committing:
## Various builds ```sh
just all
just install
```
- build: `cargo hack --feature-powerset build` ##### Publish
- clippy: `cargo hack --feature-powerset clippy -- -D warnings`
- test: `cargo hack --feature-powerset test` - `cargo semver-checks --all-features`
- test old: `cargo +1.88 hack --feature-powerset test` - `cargo publish --dry-run -p <crate>`
- example: `cargo run --example=migrations --features=sqlite` - `cargo publish --dry-run --workspace`
- fmt: `cargo fmt --check`
- docs: `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features`
- semver: `cargo semver-checks --all-features`
- publish: `cargo publish --dry-run -p seamantic`
@@ -1,14 +1,16 @@
[package] [package]
name = "seamantic" name = "seamantic"
version = "0.0.14" version = "1.0.0"
license-file.workspace = true
description = "A library to enhance SeaORM" description = "A library to enhance SeaORM"
repository = "https://github.com/QuantumShade/seamantic" repository = "https://git.skrundz.dev/quantumshade/seamantic"
categories = [] keywords = ["SeaORM"]
categories = ["database"]
include = ["/src"]
publish = true
edition.workspace = true edition.workspace = true
rust-version.workspace = true rust-version.workspace = true
license-file.workspace = true
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
@@ -31,7 +33,7 @@ serde_test = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt"] } tokio = { workspace = true, features = ["macros", "rt"] }
[features] [features]
default = [] default = ["sqlite"]
serde = ["dep:serde"] serde = ["dep:serde"]
sqlite = ["sea-orm-migration/sqlx-sqlite"] sqlite = ["sea-orm-migration/sqlx-sqlite"]
@@ -14,6 +14,10 @@ pub(super) struct Migration;
#[async_trait::async_trait] #[async_trait::async_trait]
impl MigrationTrait for Migration { impl MigrationTrait for Migration {
#[expect(
elided_lifetimes_in_paths,
reason = "async_trait causes lifetimes to be strange"
)]
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager manager
.create_table( .create_table(
@@ -28,6 +32,10 @@ impl MigrationTrait for Migration {
Ok(()) Ok(())
} }
#[expect(
elided_lifetimes_in_paths,
reason = "async_trait causes lifetimes to be strange"
)]
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager manager
.drop_table(Table::drop().table(Objects::Table).to_owned()) .drop_table(Table::drop().table(Objects::Table).to_owned())
@@ -15,3 +15,6 @@ async fn main() {
let database = Database::connect(options).await.expect("Database::connect"); let database = Database::connect(options).await.expect("Database::connect");
Migrator::up(&database, None).await.expect("Migrator::up"); Migrator::up(&database, None).await.expect("Migrator::up");
} }
// dev-dependencies cannot be optional and `serde_test` is not used
use {serde_test as _, tokio as _};
@@ -1,4 +1,4 @@
//! A library for enhacing SeaORM //! A library for enhacing `SeaORM`.
#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))]
@@ -46,3 +46,8 @@ macro_rules! migrations {
} }
}; };
} }
// dev-dependencies cannot be optional and `tokio` and `serde_test` are only used
// when the `sqlite` feature is active
#[cfg(test)]
use {serde_test as _, tokio as _};
@@ -1,37 +1,45 @@
//! [Duration] utilities //! [Duration] utilities.
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, DbErr, QueryResult, TryFromU64, 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 /// The internal representation that `SeaORM` uses for durations.
///
/// "u64 unsupported by sqlx-sqlite", so use i64 as the bit representation.
type SeaOrmRepr = i64; type SeaOrmRepr = i64;
/// The internal represenation that [Duration] uses.
type DurationRepr = u64; type DurationRepr = u64;
/// Wrapper around [Duration] to store a number of seconds /// Wrapper around [Duration] to store a number of seconds.
/// ///
/// ### Warning: /// # Warning:
/// Sub-second precision will be lost /// Sub-second precision will be lost.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] #[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)]
#[expect(clippy::exhaustive_structs, reason = "unlikely to change")]
pub struct Seconds(pub Duration); pub struct Seconds(pub Duration);
impl From<Duration> for Seconds { impl From<Duration> for Seconds {
#[inline]
fn from(value: Duration) -> Self { fn from(value: Duration) -> Self {
Self(Duration::from_secs(value.as_secs())) Self(Duration::from_secs(value.as_secs()))
} }
} }
impl From<Seconds> for Duration { impl From<Seconds> for Duration {
#[inline]
fn from(value: Seconds) -> Self { fn from(value: Seconds) -> Self {
value.0 value.0
} }
} }
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ValueType for Seconds { impl ValueType for Seconds {
#[inline]
fn try_from(v: Value) -> Result<Self, ValueTypeErr> { fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
<SeaOrmRepr as ValueType>::try_from(v) <SeaOrmRepr as ValueType>::try_from(v)
.map(|i| DurationRepr::from_ne_bytes(i.to_ne_bytes())) .map(|i| DurationRepr::from_ne_bytes(i.to_ne_bytes()))
@@ -39,26 +47,32 @@ impl ValueType for Seconds {
.map(Self) .map(Self)
} }
#[inline]
fn type_name() -> String { fn type_name() -> String {
core::any::type_name::<Self>().to_string() core::any::type_name::<Self>().to_owned()
} }
#[inline]
fn array_type() -> ArrayType { fn array_type() -> ArrayType {
SeaOrmRepr::array_type() SeaOrmRepr::array_type()
} }
#[inline]
fn column_type() -> ColumnType { fn column_type() -> ColumnType {
SeaOrmRepr::column_type() SeaOrmRepr::column_type()
} }
} }
impl From<Seconds> for Value { impl From<Seconds> for Value {
#[inline]
fn from(value: Seconds) -> Self { fn from(value: Seconds) -> Self {
value.0.as_secs().into() value.0.as_secs().into()
} }
} }
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl TryGetable for Seconds { impl TryGetable for Seconds {
#[inline]
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> { fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> {
SeaOrmRepr::try_get_by(res, index) SeaOrmRepr::try_get_by(res, index)
.map(|i| DurationRepr::from_ne_bytes(i.to_ne_bytes())) .map(|i| DurationRepr::from_ne_bytes(i.to_ne_bytes()))
@@ -68,6 +82,7 @@ impl TryGetable for Seconds {
} }
impl TryFromU64 for Seconds { impl TryFromU64 for Seconds {
#[inline]
fn try_from_u64(n: u64) -> Result<Self, DbErr> { fn try_from_u64(n: u64) -> Result<Self, DbErr> {
SeaOrmRepr::try_from_u64(n) SeaOrmRepr::try_from_u64(n)
.map(|i| DurationRepr::from_ne_bytes(i.to_ne_bytes())) .map(|i| DurationRepr::from_ne_bytes(i.to_ne_bytes()))
@@ -77,6 +92,7 @@ impl TryFromU64 for Seconds {
} }
impl Nullable for Seconds { impl Nullable for Seconds {
#[inline]
fn null() -> Value { fn null() -> Value {
SeaOrmRepr::null() SeaOrmRepr::null()
} }
@@ -91,10 +107,9 @@ mod tests {
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 { 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)] #[sea_orm(primary_key, auto_increment = false)]
@@ -102,9 +117,9 @@ mod tests {
nullable: Option<Seconds>, nullable: Option<Seconds>,
} }
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)] #[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {} enum Relation {}
} }
@@ -1,4 +1,4 @@
//! Typed IDs for use as primary keys //! Typed IDs for use as primary keys.
use core::cmp::Ordering; use core::cmp::Ordering;
use core::fmt; use core::fmt;
@@ -8,57 +8,70 @@ use core::marker::PhantomData;
use sea_orm::sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr}; use sea_orm::sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr};
use sea_orm::{ColIdx, ColumnType, DbErr, QueryResult, TryFromU64, TryGetError, TryGetable, Value}; use sea_orm::{ColIdx, ColumnType, DbErr, QueryResult, TryFromU64, TryGetError, TryGetable, Value};
/// The internal representation used by the database /// The internal representation used by the database.
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)]` /// 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)]
pub struct Id<T> { pub struct Id<T> {
/// The ID.
id: SeaOrmRepr, id: SeaOrmRepr,
/// `PhantomData` for `T`.
#[cfg_attr(feature = "serde", serde(skip_serializing, default))] #[cfg_attr(feature = "serde", serde(skip_serializing, default))]
_phantom: PhantomData<T>, _phantom: PhantomData<T>,
} }
// Manual implementation since `T: Clone` is not required // Manual implementation since `T: Clone` is not required.
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl<T> Clone for Id<T> { impl<T> Clone for Id<T> {
#[inline]
fn clone(&self) -> Self { fn clone(&self) -> Self {
*self *self
} }
} }
// Manual implementation since `T: Copy` is not required // Manual implementation since `T: Copy` is not required.
impl<T> Copy for Id<T> {} impl<T> Copy for Id<T> {}
// Manual implementation since `T: PartialEq` is not required // Manual implementation since `T: PartialEq` is not required.
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl<T> PartialEq for Id<T> { impl<T> PartialEq for Id<T> {
#[inline]
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.id == other.id self.id.eq(&other.id)
} }
} }
// Manual implementation since `T: Eq` is not required // Manual implementation since `T: Eq` is not required.
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl<T> Eq for Id<T> {} impl<T> Eq for Id<T> {}
// Manual implementation since `T: PartialOrd` is not required // Manual implementation since `T: PartialOrd` is not required.
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl<T> PartialOrd for Id<T> { impl<T> PartialOrd for Id<T> {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
} }
} }
// Manual implementation since `T: Ord` is not required // Manual implementation since `T: Ord` is not required
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl<T> Ord for Id<T> { impl<T> Ord for Id<T> {
#[inline]
fn cmp(&self, other: &Self) -> Ordering { fn cmp(&self, other: &Self) -> Ordering {
self.id.cmp(&other.id) self.id.cmp(&other.id)
} }
} }
// Manual implementation since `T: Hash` is not required // Manual implementation since `T: Hash` is not required
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl<T> Hash for Id<T> { impl<T> Hash for Id<T> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state); self.id.hash(state);
} }
@@ -66,7 +79,9 @@ impl<T> Hash for Id<T> {
impl<T> Id<T> { impl<T> Id<T> {
/// Allows the conversion from a raw value to [Id], though the use is discouraged. /// Allows the conversion from a raw value to [Id], though the use is discouraged.
pub fn from_raw(raw: SeaOrmRepr) -> Self { #[inline]
#[must_use]
pub const fn from_raw(raw: SeaOrmRepr) -> Self {
Self { Self {
id: raw, id: raw,
_phantom: PhantomData, _phantom: PhantomData,
@@ -74,13 +89,16 @@ impl<T> Id<T> {
} }
/// Allows extracting the raw value, though the use is discouraged. /// Allows extracting the raw value, though the use is discouraged.
pub fn into_raw(self) -> SeaOrmRepr { #[inline]
#[must_use]
pub const fn into_raw(self) -> SeaOrmRepr {
self.id self.id
} }
} }
impl<T> fmt::Debug for Id<T> { impl<T> fmt::Debug for Id<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { #[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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)
@@ -88,7 +106,9 @@ impl<T> fmt::Debug for Id<T> {
} }
} }
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl<T> ValueType for Id<T> { impl<T> ValueType for Id<T> {
#[inline]
fn try_from(v: Value) -> Result<Self, ValueTypeErr> { fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
<SeaOrmRepr as ValueType>::try_from(v).map(|id| Self { <SeaOrmRepr as ValueType>::try_from(v).map(|id| Self {
id, id,
@@ -96,26 +116,32 @@ impl<T> ValueType for Id<T> {
}) })
} }
#[inline]
fn type_name() -> String { fn type_name() -> String {
format!("Id<{}>", &core::any::type_name::<T>()) format!("Id<{}>", core::any::type_name::<T>())
} }
#[inline]
fn array_type() -> ArrayType { fn array_type() -> ArrayType {
SeaOrmRepr::array_type() SeaOrmRepr::array_type()
} }
#[inline]
fn column_type() -> ColumnType { fn column_type() -> ColumnType {
SeaOrmRepr::column_type() SeaOrmRepr::column_type()
} }
} }
impl<T> From<Id<T>> for Value { impl<T> From<Id<T>> for Value {
#[inline]
fn from(value: Id<T>) -> Self { fn from(value: Id<T>) -> Self {
value.id.into() value.id.into()
} }
} }
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl<T> TryGetable for Id<T> { impl<T> TryGetable for Id<T> {
#[inline]
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> { fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> {
SeaOrmRepr::try_get_by(res, index).map(|id| Self { SeaOrmRepr::try_get_by(res, index).map(|id| Self {
id, id,
@@ -125,6 +151,7 @@ impl<T> TryGetable for Id<T> {
} }
impl<T> TryFromU64 for Id<T> { impl<T> TryFromU64 for Id<T> {
#[inline]
fn try_from_u64(n: u64) -> Result<Self, DbErr> { fn try_from_u64(n: u64) -> Result<Self, DbErr> {
SeaOrmRepr::try_from_u64(n).map(|id| Self { SeaOrmRepr::try_from_u64(n).map(|id| Self {
id, id,
@@ -134,6 +161,7 @@ impl<T> TryFromU64 for Id<T> {
} }
impl<T> Nullable for Id<T> { impl<T> Nullable for Id<T> {
#[inline]
fn null() -> Value { fn null() -> Value {
SeaOrmRepr::null() SeaOrmRepr::null()
} }
@@ -148,24 +176,32 @@ mod tests {
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 { #[expect(clippy::use_self, reason = "derive macros cause Self to be invalid")]
struct Model {
#[sea_orm(primary_key, auto_increment = false)] #[sea_orm(primary_key, auto_increment = false)]
id: Id<Model>, id: Id<Model>,
nullable: Option<Id<Model>>, nullable: Option<Id<Model>>,
} }
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)] #[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {} enum Relation {}
#[test]
#[expect(clippy::missing_panics_doc, reason = "unit test")]
fn round_trip() {
let raw = 1234;
let id = Id::<()>::from_raw(raw);
assert_eq!(raw, id.into_raw());
}
#[test] #[test]
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
fn test_serde() { fn ser_de() {
let id: Id<()> = Id::from_raw(1234); let id: Id<()> = Id::from_raw(1234);
serde_test::assert_tokens(&id, &[serde_test::Token::I64(1234)]); serde_test::assert_tokens(&id, &[serde_test::Token::I64(1234)]);
} }
+6
View File
@@ -0,0 +1,6 @@
//! `SeaORM` column types for enforcing data consistency.
pub mod duration;
pub mod id;
pub mod net;
pub mod path;
@@ -1,74 +1,89 @@
//! [IpAddr], [Ipv4Addr], and [Ipv6Addr] utilities //! [`IpAddr`], [`Ipv4Addr`], and [`Ipv6Addr`] utilities.
use core::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use core::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use sea_orm::sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr}; use sea_orm::sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr};
use sea_orm::{ColIdx, ColumnType, DbErr, QueryResult, TryFromU64, TryGetError, TryGetable, Value}; use sea_orm::{ColIdx, ColumnType, DbErr, QueryResult, TryFromU64, TryGetError, TryGetable, Value};
/// The internal representation used by the database.
type SeaOrmRepr = String; type SeaOrmRepr = String;
/// Wrapper around [IpAddr] /// Wrapper around [`IpAddr`].
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[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)]
#[expect(clippy::exhaustive_structs, reason = "unlikely to change")]
pub struct IpAddress(pub IpAddr); pub struct IpAddress(pub IpAddr);
/// Wrapper around [IpAddr] /// Wrapper around [`Ipv4Addr`].
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[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)]
#[expect(clippy::exhaustive_structs, reason = "unlikely to change")]
pub struct Ipv4Address(pub Ipv4Addr); pub struct Ipv4Address(pub Ipv4Addr);
/// Wrapper around [IpAddr] /// Wrapper around [`Ipv6Addr`].
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[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)]
#[expect(clippy::exhaustive_structs, reason = "unlikely to change")]
pub struct Ipv6Address(pub Ipv6Addr); pub struct Ipv6Address(pub Ipv6Addr);
/// Implements `SeaORM` traits for `IP` types.
macro_rules! impl_addr { macro_rules! impl_addr {
($t:ty, $inner:ty, $repr:ty) => { ($t:ty, $inner:ty, $repr:ty) => {
impl From<$inner> for $t { impl From<$inner> for $t {
#[inline]
fn from(value: $inner) -> Self { fn from(value: $inner) -> Self {
Self(value) Self(value)
} }
} }
impl From<$t> for $inner { impl From<$t> for $inner {
#[inline]
fn from(value: $t) -> Self { fn from(value: $t) -> Self {
value.0 value.0
} }
} }
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ValueType for $t { impl ValueType for $t {
#[inline]
fn try_from(v: Value) -> Result<Self, ValueTypeErr> { fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
<SeaOrmRepr as ValueType>::try_from(v) <SeaOrmRepr as ValueType>::try_from(v)
.and_then(|s| s.parse().map_err(|_| ValueTypeErr)) .and_then(|s| s.parse().map_err(|_| ValueTypeErr))
.map(Self) .map(Self)
} }
#[inline]
fn type_name() -> String { fn type_name() -> String {
core::any::type_name::<Self>().to_string() core::any::type_name::<Self>().to_string()
} }
#[inline]
fn array_type() -> ArrayType { fn array_type() -> ArrayType {
SeaOrmRepr::array_type() SeaOrmRepr::array_type()
} }
#[inline]
fn column_type() -> ColumnType { fn column_type() -> ColumnType {
SeaOrmRepr::column_type() SeaOrmRepr::column_type()
} }
} }
impl From<$t> for Value { impl From<$t> for Value {
#[inline]
fn from(value: $t) -> Self { fn from(value: $t) -> Self {
value.0.to_string().into() value.0.to_string().into()
} }
} }
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl TryGetable for $t { impl TryGetable for $t {
#[inline]
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> { fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> {
SeaOrmRepr::try_get_by(res, index) SeaOrmRepr::try_get_by(res, index)
.and_then(|s| s.parse().map_err(|_| TryGetError::Null(Self::type_name()))) .and_then(|s| s.parse().map_err(|_| TryGetError::Null(Self::type_name())))
@@ -77,6 +92,7 @@ macro_rules! impl_addr {
} }
impl TryFromU64 for $t { impl TryFromU64 for $t {
#[inline]
fn try_from_u64(n: u64) -> Result<Self, DbErr> { fn try_from_u64(n: u64) -> Result<Self, DbErr> {
SeaOrmRepr::try_from_u64(n) SeaOrmRepr::try_from_u64(n)
.and_then(|s| { .and_then(|s| {
@@ -88,6 +104,7 @@ macro_rules! impl_addr {
} }
impl Nullable for $t { impl Nullable for $t {
#[inline]
fn null() -> Value { fn null() -> Value {
SeaOrmRepr::null() SeaOrmRepr::null()
} }
@@ -109,10 +126,9 @@ mod tests {
use super::super::IpAddress; use super::super::IpAddress;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)] #[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "ipaddress")] #[sea_orm(table_name = "ipaddress")]
pub struct Model { 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)] #[sea_orm(primary_key, auto_increment = false)]
@@ -120,11 +136,11 @@ mod tests {
nullable: Option<IpAddress>, nullable: Option<IpAddress>,
} }
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)] #[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {} enum Relation {}
} }
mod ipv4address { mod ipv4address {
use sea_orm::{ use sea_orm::{
@@ -134,10 +150,9 @@ mod tests {
use super::super::Ipv4Address; use super::super::Ipv4Address;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)] #[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "ipv4address")] #[sea_orm(table_name = "ipv4address")]
pub struct Model { 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)] #[sea_orm(primary_key, auto_increment = false)]
@@ -145,11 +160,11 @@ mod tests {
nullable: Option<Ipv4Address>, nullable: Option<Ipv4Address>,
} }
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)] #[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {} enum Relation {}
} }
mod ipv6address { mod ipv6address {
use sea_orm::{ use sea_orm::{
@@ -159,10 +174,9 @@ mod tests {
use super::super::Ipv6Address; use super::super::Ipv6Address;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)] #[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "ipv6address")] #[sea_orm(table_name = "ipv6address")]
pub struct Model { 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)] #[sea_orm(primary_key, auto_increment = false)]
@@ -170,10 +184,10 @@ mod tests {
nullable: Option<Ipv6Address>, nullable: Option<Ipv6Address>,
} }
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)] #[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {} enum Relation {}
} }
} }
@@ -1,4 +1,4 @@
//! [Path] and [PathBuf] utilities //! [Path] and [`PathBuf`] utilities.
use std::ffi::OsString; use std::ffi::OsString;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@@ -11,34 +11,42 @@ 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, DbErr, QueryResult, TryFromU64, TryGetError, TryGetable, Value}; use sea_orm::{ColIdx, ColumnType, DbErr, QueryResult, TryFromU64, TryGetError, TryGetable, Value};
/// The internal representation used by the database.
type SeaOrmRepr = Vec<u8>; type SeaOrmRepr = Vec<u8>;
/// Wrapper around [PathBuf] to store paths as bytes in SeaORM /// Wrapper around [`PathBuf`] to store paths as bytes in `SeaORM`.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[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)]
#[expect(clippy::exhaustive_structs, reason = "unlikely to change")]
#[expect(clippy::module_name_repetitions, reason = "Bytes wouldn't make sense")]
pub struct PathBytes(pub PathBuf); pub struct PathBytes(pub PathBuf);
impl From<PathBuf> for PathBytes { impl From<PathBuf> for PathBytes {
#[inline]
fn from(value: PathBuf) -> Self { fn from(value: PathBuf) -> Self {
Self(value) Self(value)
} }
} }
impl From<PathBytes> for PathBuf { impl From<PathBytes> for PathBuf {
#[inline]
fn from(value: PathBytes) -> Self { fn from(value: PathBytes) -> Self {
value.0 value.0
} }
} }
impl AsRef<Path> for PathBytes { impl AsRef<Path> for PathBytes {
#[inline]
fn as_ref(&self) -> &Path { fn as_ref(&self) -> &Path {
&self.0 &self.0
} }
} }
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ValueType for PathBytes { impl ValueType for PathBytes {
#[inline]
fn try_from(v: Value) -> Result<Self, ValueTypeErr> { fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
<SeaOrmRepr as ValueType>::try_from(v) <SeaOrmRepr as ValueType>::try_from(v)
.map(OsString::from_vec) .map(OsString::from_vec)
@@ -46,26 +54,32 @@ impl ValueType for PathBytes {
.map(Self) .map(Self)
} }
#[inline]
fn type_name() -> String { fn type_name() -> String {
core::any::type_name::<Self>().to_string() core::any::type_name::<Self>().to_owned()
} }
#[inline]
fn array_type() -> ArrayType { fn array_type() -> ArrayType {
SeaOrmRepr::array_type() SeaOrmRepr::array_type()
} }
#[inline]
fn column_type() -> ColumnType { fn column_type() -> ColumnType {
SeaOrmRepr::column_type() SeaOrmRepr::column_type()
} }
} }
impl From<PathBytes> for Value { impl From<PathBytes> for Value {
#[inline]
fn from(value: PathBytes) -> Self { fn from(value: PathBytes) -> Self {
value.0.into_os_string().into_vec().into() value.0.into_os_string().into_vec().into()
} }
} }
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl TryGetable for PathBytes { impl TryGetable for PathBytes {
#[inline]
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> { fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> {
SeaOrmRepr::try_get_by(res, index) SeaOrmRepr::try_get_by(res, index)
.map(OsString::from_vec) .map(OsString::from_vec)
@@ -75,6 +89,7 @@ impl TryGetable for PathBytes {
} }
impl TryFromU64 for PathBytes { impl TryFromU64 for PathBytes {
#[inline]
fn try_from_u64(n: u64) -> Result<Self, DbErr> { fn try_from_u64(n: u64) -> Result<Self, DbErr> {
SeaOrmRepr::try_from_u64(n) SeaOrmRepr::try_from_u64(n)
.map(OsString::from_vec) .map(OsString::from_vec)
@@ -84,6 +99,7 @@ impl TryFromU64 for PathBytes {
} }
impl Nullable for PathBytes { impl Nullable for PathBytes {
#[inline]
fn null() -> Value { fn null() -> Value {
SeaOrmRepr::null() SeaOrmRepr::null()
} }
@@ -98,10 +114,9 @@ mod tests {
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 { 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)] #[sea_orm(primary_key, auto_increment = false)]
@@ -109,9 +124,9 @@ mod tests {
nullable: Option<PathBytes>, nullable: Option<PathBytes>,
} }
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
#[allow(dead_code)]
#[derive(Debug, EnumIter, DeriveRelation)] #[derive(Debug, EnumIter, DeriveRelation)]
pub enum Relation {} enum Relation {}
} }
@@ -1,4 +1,4 @@
//! Helpers for working with SeaORM //! Helpers for working with `SeaORM`.
mod upsert; mod upsert;
pub use upsert::UpsertTrait; pub use upsert::UpsertTrait;
+66
View File
@@ -0,0 +1,66 @@
//! Provides [`UpsertTrait`] for performing an upsert into a database.
use sea_orm::sea_query::{IntoColumnRef as _, OnConflict};
use sea_orm::{ActiveModelTrait, EntityTrait, Insert, Iterable as _};
/// 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.
#[must_use]
fn on_conflict_upsert(self) -> Self;
}
/// Returns an [Iterator] over the `PrimaryKey` columns of an [`ActiveModelTrait`].
fn primary_key_iter<A: ActiveModelTrait>()
-> impl Iterator<Item = <A::Entity as EntityTrait>::PrimaryKey> {
<A::Entity as EntityTrait>::PrimaryKey::iter()
}
/// Returns an [Iterator] over the [`EntityTrait`] columns of an [`ActiveModelTrait`].
fn column_iter<A: ActiveModelTrait>() -> impl Iterator<Item = <A::Entity as EntityTrait>::Column> {
<<A as ActiveModelTrait>::Entity as EntityTrait>::Column::iter()
}
impl<A: ActiveModelTrait> private::Sealed for Insert<A> {}
impl<A: ActiveModelTrait> UpsertTrait for Insert<A> {
#[inline]
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(),
)
}
}
/// Private module.
mod private {
/// Sealed trait to prevent implementations of `UpsertTrait`.
#[expect(unnameable_types, reason = "sealed trait")]
pub trait Sealed {}
}
#[cfg(test)]
mod tests {
use super::{UpsertTrait, private::Sealed};
#[test]
fn seal() {
struct S;
impl Sealed for S {}
impl UpsertTrait for S {
fn on_conflict_upsert(self) -> Self {
self
}
}
fn is_sealed<T: Sealed>() {}
fn is_upsert<T: UpsertTrait>() {}
is_sealed::<S>();
is_upsert::<S>();
}
}
@@ -1,4 +1,4 @@
//! Helpers for defining SeaORM schemas //! Helpers for defining `SeaORM` schemas.
#[cfg(feature = "sqlite")] #[cfg(feature = "sqlite")]
mod sqlite; mod sqlite;
@@ -1,16 +1,18 @@
//! Helper functions for declaring `SQLite` schemas.
use sea_orm_migration::schema::{integer_null, string}; use sea_orm_migration::schema::{integer_null, string};
use sea_orm_migration::sea_query::{ColumnDef, IntoIden}; use sea_orm_migration::sea_query::{ColumnDef, IntoIden};
/// Sets the column to be an alias for SQLite's rowid. /// Sets the column to be an alias for SQLite's rowid.
/// ///
/// Required conditions: /// Required conditions:
/// - This column must *not* be auto_increment /// - This column must *not* be `auto_increment`
/// - There cannot be other primary_key columns in the table /// - There cannot be other `primary_key` columns in the table
/// ///
/// When using `DeriveEntityModel`, the type must be `i64` (or equivalent) /// When using `DeriveEntityModel`, the type must be `i64` (or equivalent)
/// 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: /// When using the new entity format:
/// ///
@@ -25,11 +27,12 @@ use sea_orm_migration::sea_query::{ColumnDef, IntoIden};
/// id: Id<Model>, /// id: Id<Model>,
/// } /// }
/// impl ActiveModelBehavior for ActiveModel {} /// impl ActiveModelBehavior for ActiveModel {}
#[inline]
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: /// When using the new entity format:
/// ///
@@ -46,6 +49,7 @@ pub fn sqlite_rowid_alias<T: IntoIden>(name: T) -> ColumnDef {
/// nocase: String, /// nocase: String,
/// } /// }
/// impl ActiveModelBehavior for ActiveModel {} /// impl ActiveModelBehavior for ActiveModel {}
#[inline]
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()
} }
@@ -54,7 +58,12 @@ pub fn sqlite_case_insensitive_string<T: IntoIden>(name: T) -> ColumnDef {
mod entity_tests { mod entity_tests {
use sea_orm::{ConnectOptions, Database, DatabaseConnection}; use sea_orm::{ConnectOptions, Database, DatabaseConnection};
pub async fn new_initialized_memory_db() -> DatabaseConnection { /// Create and initialize a new in-memory SQLite database.
///
/// # Panics
/// If the database driver fails to connect, or if the schema registry fails
/// to sync the database schema.
async fn new_initialized_memory_db() -> DatabaseConnection {
let options = ConnectOptions::new("sqlite::memory:"); let options = ConnectOptions::new("sqlite::memory:");
let db = Database::connect(options) let db = Database::connect(options)
@@ -67,6 +76,22 @@ mod entity_tests {
db db
} }
#[expect(
clippy::unused_async_trait_impl,
reason = "sea_orm::model generates unpolled futures"
)]
#[expect(
clippy::same_name_method,
reason = "sea_orm::model generates the same method names"
)]
#[expect(
clippy::allow_attributes,
reason = "under different conditions sea_orm::model generates different code"
)]
#[allow(
clippy::future_not_send,
reason = "sea_orm::model generates un-send futures"
)]
mod rowid_test { mod rowid_test {
use sea_orm::ActiveValue::{NotSet, Set}; use sea_orm::ActiveValue::{NotSet, Set};
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
@@ -76,14 +101,18 @@ mod entity_tests {
#[sea_orm::model] #[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)] #[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "rowid_test")] #[sea_orm(table_name = "rowid_test")]
pub struct Model { struct Model {
#[sea_orm(primary_key, auto_increment = false)] #[sea_orm(primary_key, auto_increment = false)]
id: Id<Model>, id: Id<Model>,
} }
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
#[cfg(not(miri))]
#[tokio::test] #[tokio::test]
async fn test_sqlite_rowid_alias() { #[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "numbers are not ambiguous")]
async fn sqlite_rowid_alias() {
let db = super::new_initialized_memory_db().await; let db = super::new_initialized_memory_db().await;
// Starts at 1 and increments // Starts at 1 and increments
@@ -98,7 +127,7 @@ mod entity_tests {
let model = ActiveModel { let model = ActiveModel {
id: Set(Id::from_raw(i)), id: Set(Id::from_raw(i)),
}; };
model.delete(&db).await.expect("delete"); _ = model.delete(&db).await.expect("delete");
let model = ActiveModel { id: NotSet }; let model = ActiveModel { id: NotSet };
let model = model.insert(&db).await.expect("insert"); let model = model.insert(&db).await.expect("insert");
assert_eq!(model.id.into_raw(), i); assert_eq!(model.id.into_raw(), i);
@@ -156,6 +185,22 @@ mod entity_tests {
} }
} }
#[expect(
clippy::unused_async_trait_impl,
reason = "sea_orm::model generates unpolled futures"
)]
#[expect(
clippy::same_name_method,
reason = "sea_orm::model generates the same method names"
)]
#[expect(
clippy::allow_attributes,
reason = "under different conditions sea_orm::model generates different code"
)]
#[allow(
clippy::future_not_send,
reason = "sea_orm::model generates un-send futures"
)]
mod nocase_test { mod nocase_test {
use sea_orm::ActiveValue::Set; use sea_orm::ActiveValue::Set;
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
@@ -163,17 +208,20 @@ mod entity_tests {
#[sea_orm::model] #[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)] #[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "nocase_test")] #[sea_orm(table_name = "nocase_test")]
pub struct Model { struct Model {
#[sea_orm(primary_key, auto_increment = false)] #[sea_orm(primary_key, auto_increment = false)]
id: i64, id: i64,
case: String, case: String,
#[sea_orm(extra = "COLLATE NOCASE")] #[sea_orm(extra = "COLLATE NOCASE")]
nocase: String, nocase: String,
} }
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
#[cfg(not(miri))]
#[tokio::test] #[tokio::test]
async fn test_sqlite_case_insensitive_string() { #[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn sqlite_case_insensitive_string() {
let db = super::new_initialized_memory_db().await; let db = super::new_initialized_memory_db().await;
let i = 50; let i = 50;
@@ -185,7 +233,7 @@ mod entity_tests {
case: Set("abcd".to_owned()), case: Set("abcd".to_owned()),
nocase: Set("abcd".to_owned()), nocase: Set("abcd".to_owned()),
}; };
model.insert(&db).await.expect("insert"); drop(model.insert(&db).await.expect("insert"));
} }
// Query by uppercase string // Query by uppercase string
@@ -216,7 +264,7 @@ mod entity_tests {
mod tests { mod tests {
use sea_orm::ActiveValue::{NotSet, Set}; use sea_orm::ActiveValue::{NotSet, Set};
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
use sea_orm::{ConnectOptions, Database, DatabaseConnection}; use sea_orm::{ConnectOptions, Database};
use sea_orm_migration::async_trait::async_trait; use sea_orm_migration::async_trait::async_trait;
use sea_orm_migration::prelude::*; use sea_orm_migration::prelude::*;
@@ -224,14 +272,21 @@ mod tests {
use super::{sqlite_case_insensitive_string, sqlite_rowid_alias}; use super::{sqlite_case_insensitive_string, sqlite_rowid_alias};
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn new_memory_db() -> DatabaseConnection { async fn new_memory_db() -> DatabaseConnection {
let options = ConnectOptions::new("sqlite::memory:"); let options = ConnectOptions::new("sqlite::memory:");
Database::connect(options).await.expect("Database::connect") Database::connect(options).await.expect("Database::connect")
} }
#[cfg(not(miri))]
#[tokio::test] #[tokio::test]
#[expect(unused_qualifications, reason = "derive macro qualifies everything")]
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "numbers are not ambiguous")]
#[expect(clippy::redundant_test_prefix, reason = "name collision")]
async fn test_sqlite_rowid_alias() { async fn test_sqlite_rowid_alias() {
struct Migrator; struct Migrator;
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl MigratorTrait for Migrator { impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> { fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![Box::new(Migration)] vec![Box::new(Migration)]
@@ -246,7 +301,12 @@ mod tests {
#[derive(DeriveMigrationName)] #[derive(DeriveMigrationName)]
struct Migration; struct Migration;
#[async_trait] #[async_trait]
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl MigrationTrait for Migration { impl MigrationTrait for Migration {
#[expect(
elided_lifetimes_in_paths,
reason = "async_trait causes lifetimes to be strange"
)]
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager manager
.create_table( .create_table(
@@ -263,17 +323,21 @@ mod tests {
#[derive(Debug, Clone, DeriveEntityModel)] #[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "test_table")] #[sea_orm(table_name = "test_table")]
pub struct Model { #[expect(clippy::use_self, reason = "derive macros cause Self to be invalid")]
struct Model {
#[sea_orm(primary_key, auto_increment = false)] #[sea_orm(primary_key, auto_increment = false)]
id: Id<Model>, id: Id<Model>,
} }
#[derive(Debug, EnumIter)] #[derive(Debug, EnumIter)]
pub enum Relation {} enum Relation {}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl RelationTrait for Relation { impl RelationTrait for Relation {
#[expect(clippy::panic, reason = "no relation")]
fn def(&self) -> RelationDef { fn def(&self) -> RelationDef {
panic!("No RelationDef") panic!("No RelationDef")
} }
} }
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
let db = new_memory_db().await; let db = new_memory_db().await;
@@ -291,7 +355,7 @@ mod tests {
let model = ActiveModel { let model = ActiveModel {
id: Set(Id::from_raw(i)), id: Set(Id::from_raw(i)),
}; };
model.delete(&db).await.expect("delete"); _ = model.delete(&db).await.expect("delete");
let model = ActiveModel { id: NotSet }; let model = ActiveModel { id: NotSet };
let model = model.insert(&db).await.expect("insert"); let model = model.insert(&db).await.expect("insert");
assert_eq!(model.id.into_raw(), i); assert_eq!(model.id.into_raw(), i);
@@ -348,9 +412,14 @@ mod tests {
} }
} }
#[cfg(not(miri))]
#[tokio::test] #[tokio::test]
#[expect(unused_qualifications, reason = "derive macro qualifies everything")]
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::redundant_test_prefix, reason = "name collision")]
async fn test_sqlite_case_insensitive_string() { async fn test_sqlite_case_insensitive_string() {
struct Migrator; struct Migrator;
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl MigratorTrait for Migrator { impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> { fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![Box::new(Migration)] vec![Box::new(Migration)]
@@ -366,7 +435,12 @@ mod tests {
#[derive(DeriveMigrationName)] #[derive(DeriveMigrationName)]
struct Migration; struct Migration;
#[async_trait] #[async_trait]
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl MigrationTrait for Migration { impl MigrationTrait for Migration {
#[expect(
elided_lifetimes_in_paths,
reason = "async_trait causes lifetimes to be strange"
)]
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager manager
.create_table( .create_table(
@@ -384,18 +458,21 @@ mod tests {
#[derive(Debug, Clone, DeriveEntityModel)] #[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "test_table")] #[sea_orm(table_name = "test_table")]
pub struct Model { struct Model {
#[sea_orm(primary_key, auto_increment = false)] #[sea_orm(primary_key, auto_increment = false)]
id: i64, id: i64,
ci_str: String, ci_str: String,
} }
#[derive(Debug, EnumIter)] #[derive(Debug, EnumIter)]
pub enum Relation {} enum Relation {}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl RelationTrait for Relation { impl RelationTrait for Relation {
#[expect(clippy::panic, reason = "no relation")]
fn def(&self) -> RelationDef { fn def(&self) -> RelationDef {
panic!("No RelationDef") panic!("No RelationDef")
} }
} }
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
let db = new_memory_db().await; let db = new_memory_db().await;
@@ -409,7 +486,7 @@ mod tests {
id: Set(i), id: Set(i),
ci_str: Set("abcd".to_owned()), ci_str: Set("abcd".to_owned()),
}; };
model.insert(&db).await.expect("insert"); drop(model.insert(&db).await.expect("insert"));
} }
// Query by uppercase string // Query by uppercase string
+43
View File
@@ -0,0 +1,43 @@
set minimum-version := '1.55.0'
set default-list
alias update := update-min
all:
just all-min
just all-max
all-min: update-min build lint test example doc format
all-max: update-max build lint test example doc format
update-min:
cargo +nightly update -Z direct-minimal-versions
update-max:
cargo update
build:
cargo hack --feature-powerset build
lint:
cargo hack --feature-powerset clippy -- -D warnings
cargo hack --feature-powerset clippy --tests -- -D warnings
test:
cargo hack --feature-powerset test
cargo +1.94 hack --feature-powerset test
cargo +nightly hack --feature-powerset miri test
example:
cargo run --example=migrations --features=sqlite
doc *args:
RUSTDOCFLAGS="--cfg docsrs -D warnings" cargo +nightly doc \
--workspace --all-features --document-private-items \
--lib --bins --no-deps --locked \
{{ args }}
format:
cargo fmt --check
install:
cargo install --path crates/lint-policy-cli
+1 -1
View File
@@ -1,2 +1,2 @@
[toolchain] [toolchain]
channel = "stable" channel = "1.98"
-6
View File
@@ -1,6 +0,0 @@
//! SeaORM column types for enforcing data consistency
pub mod duration;
pub mod id;
pub mod ip;
pub mod path;
-34
View File
@@ -1,34 +0,0 @@
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> {}
}