Skip to content

Commit

Permalink
Add unqiue constraint for model fields
Browse files Browse the repository at this point in the history
May be specified by adding #[unique] to a field
  • Loading branch information
Electron100 committed Jun 24, 2021
1 parent e26ce3d commit 2770b50
Show file tree
Hide file tree
Showing 17 changed files with 73 additions and 18 deletions.
11 changes: 7 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions butane/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "butane"
version = "0.3.0"
version = "0.3.1"
authors = ["James Oakley <[email protected]>"]
edition = "2018"
description = "An ORM with a focus on simplicity and on writing Rust, not SQL."
Expand All @@ -23,8 +23,8 @@ tls = ["butane_core/tls"]
uuid = ["butane_core/uuid", "butane_codegen/uuid"]

[dependencies]
butane_codegen = { path = "../butane_codegen", version = "0.3.0" }
butane_core = { path = "../butane_core", version = "0.3.0" }
butane_codegen = { path = "../butane_codegen", version = "0.3.1" }
butane_core = { path = "../butane_core", version = "0.3.1" }


[dev-dependencies]
Expand All @@ -38,6 +38,7 @@ proc-macro2="1.0"
once_cell="1.5.2"
postgres = { version = "0.19", features=["with-geo-types-0_7"] }
r2d2_for_test = {package="r2d2", version = "0.8"}
rusqlite = "0.25"
uuid_for_test = {package="uuid", version = "0.8", features=["v4"] }

[package.metadata.docs.rs]
Expand Down
29 changes: 29 additions & 0 deletions butane/tests/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ use butane::prelude::*;
use butane::{butane_type, find, model, query};
use butane::{ForeignKey, ObjectState};
use paste;
#[cfg(feature = "pg")]
use postgres;
#[cfg(feature = "sqlite")]
use rusqlite;

mod common;

Expand All @@ -13,6 +17,7 @@ pub type Whatsit = String;
#[derive(PartialEq, Eq, Debug, Clone)]
struct Foo {
id: i64,
#[unique]
bar: u32,
baz: Whatsit,
blobbity: Vec<u8>,
Expand Down Expand Up @@ -263,3 +268,27 @@ fn basic_rollback_transaction(mut conn: Connection) {
}
}
testall!(basic_rollback_transaction);

fn basic_unique_field_error_on_non_unique(conn: Connection) {
let mut foo1 = Foo::new(1);
foo1.bar = 42;
foo1.save(&conn).unwrap();

let mut foo2 = Foo::new(2);
foo2.bar = foo1.bar;
let e = foo2.save(&conn).unwrap_err();
// Make sure the error is one we expect
assert!(match e {
#[cfg(feature = "sqlite")]
butane::Error::SQLite(rusqlite::Error::SqliteFailure(
rusqlite::ffi::Error { code, .. },
_,
)) if code == rusqlite::ffi::ErrorCode::ConstraintViolation => true,
#[cfg(feature = "pg")]
butane::Error::Postgres(e)
if e.code() == Some(&postgres::error::SqlState::UNIQUE_VIOLATION) =>
true,
_ => false,
});
}
testall!(basic_unique_field_error_on_non_unique);
4 changes: 2 additions & 2 deletions butane_cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "butane_cli"
version = "0.3.0"
version = "0.3.1"
authors = ["James Oakley <[email protected]>"]
edition = "2018"
description = "The CLI for the Butane ORM"
Expand All @@ -15,7 +15,7 @@ path = "src/main.rs"

[dependencies]
anyhow = "1.0"
butane = { path="../butane", version="0.3", features=["default", "sqlite", "pg"] }
butane = { path="../butane", version="0.3.1", features=["default", "sqlite", "pg"] }
chrono = "0.4"
clap = "2.33"
quote = "1.0"
Expand Down
4 changes: 2 additions & 2 deletions butane_codegen/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "butane_codegen"
version = "0.3.0"
version = "0.3.1"
authors = ["James Oakley <[email protected]>"]
edition = "2018"
description = "Macros for Butane. Do not use this crate directly -- use the butane crate."
Expand All @@ -13,7 +13,7 @@ datetime = []

[dependencies]
proc-macro2 = "1.0"
butane_core = { path = "../butane_core", version = "0.3.0" }
butane_core = { path = "../butane_core", version = "0.3.1" }
quote = "1.0"
syn = { version = "1.0", features = ["full", "extra-traits"] }
uuid = {version = "0.8", optional=true}
Expand Down
2 changes: 2 additions & 0 deletions butane_codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ mod filter;
/// initialized based on serial/autoincrement. Currently supported
/// only on the primary key and only if the primary key is an integer
/// type
/// * `#[unique]` on a field indicates that the field's value must be unique
/// (perhaps implemented as the SQL UNIQUE constraint by some backends).
/// * `[default]` should be used on fields added by later migrations to avoid errors on existing objects.
/// Unnecessary if the new field is an `Option<>`
///
Expand Down
2 changes: 1 addition & 1 deletion butane_core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "butane_core"
version = "0.3.0"
version = "0.3.1"
authors = ["James Oakley <[email protected]>"]
edition = "2018"
description = "Internals for Butane. Do not use this crate directly -- use the butane crate."
Expand Down
1 change: 1 addition & 0 deletions butane_core/src/codegen/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ fn create_atables(ast_struct: &ItemStruct, config: &dbobj::Config) -> Vec<ATable
is_nullable(&f),
f == &pk,
is_auto(&f),
is_unique(&f),
get_default(&f).expect("Malformed default attribute"),
);
table.add_column(col);
Expand Down
5 changes: 5 additions & 0 deletions butane_core/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ fn remove_helper_field_attributes(
&& !a.path.is_ident("auto")
&& !a.path.is_ident("sqltype")
&& !a.path.is_ident("default")
&& !a.path.is_ident("unique")
});
}
Ok(fields)
Expand Down Expand Up @@ -289,6 +290,10 @@ fn is_auto(field: &Field) -> bool {
field.attrs.iter().any(|attr| attr.path.is_ident("auto"))
}

fn is_unique(field: &Field) -> bool {
field.attrs.iter().any(|attr| attr.path.is_ident("unique"))
}

fn fields(ast_struct: &ItemStruct) -> impl Iterator<Item = &Field> {
ast_struct
.fields
Expand Down
3 changes: 3 additions & 0 deletions butane_core/src/db/pg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,9 @@ fn define_column(col: &AColumn) -> Result<String> {
if col.is_pk() {
constraints.push("PRIMARY KEY".to_string());
}
if col.unique() {
constraints.push("UNIQUE".to_string());
}
Ok(format!(
"{} {} {}",
&col.name(),
Expand Down
3 changes: 3 additions & 0 deletions butane_core/src/db/sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,9 @@ fn define_column(col: &AColumn) -> String {
// and we only allow auto on integer types
constraints.push("AUTOINCREMENT".to_string());
}
if col.unique() {
constraints.push("UNIQUE".to_string());
}
format!(
"{} {} {}",
&col.name(),
Expand Down
11 changes: 9 additions & 2 deletions butane_core/src/migrations/adb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,8 @@ pub struct AColumn {
nullable: bool,
pk: bool,
auto: bool,
#[serde(default)]
unique: bool,
default: Option<SqlVal>,
}
impl AColumn {
Expand All @@ -318,6 +320,7 @@ impl AColumn {
nullable: bool,
pk: bool,
auto: bool,
unique: bool,
default: Option<SqlVal>,
) -> Self {
AColumn {
Expand All @@ -326,19 +329,23 @@ impl AColumn {
nullable,
pk,
auto,
unique,
default,
}
}
/// Simple column that is non-null, non-auto, non-pk with no default
/// Simple column that is non-null, non-auto, non-pk, non-unique with no default
pub fn new_simple(name: impl Into<String>, sqltype: DeferredSqlType) -> Self {
Self::new(name, sqltype, false, false, false, None)
Self::new(name, sqltype, false, false, false, false, None)
}
pub fn name(&self) -> &str {
&self.name
}
pub fn nullable(&self) -> bool {
self.nullable
}
pub fn unique(&self) -> bool {
self.unique
}
pub fn is_pk(&self) -> bool {
self.pk
}
Expand Down
1 change: 1 addition & 0 deletions butane_core/src/migrations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ fn migrations_table() -> ATable {
false, // nullable
true, // pk
false, // auto
false, // unique
None,
);
table.add_column(col);
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"name":"Blog","columns":[{"name":"id","sqltype":{"KnownId":{"Ty":"BigInt"}},"nullable":false,"pk":true,"auto":true,"default":null},{"name":"name","sqltype":{"KnownId":{"Ty":"Text"}},"nullable":false,"pk":false,"auto":false,"default":null}]}
{"name":"Blog","columns":[{"name":"id","sqltype":{"KnownId":{"Ty":"BigInt"}},"nullable":false,"pk":true,"auto":true,"unique":false,"default":null},{"name":"name","sqltype":{"KnownId":{"Ty":"Text"}},"nullable":false,"pk":false,"auto":false,"unique":false,"default":null}]}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"name":"Post","columns":[{"name":"id","sqltype":{"KnownId":{"Ty":"Int"}},"nullable":false,"pk":true,"auto":true,"default":null},{"name":"title","sqltype":{"KnownId":{"Ty":"Text"}},"nullable":false,"pk":false,"auto":false,"default":null},{"name":"body","sqltype":{"KnownId":{"Ty":"Text"}},"nullable":false,"pk":false,"auto":false,"default":null},{"name":"published","sqltype":{"KnownId":{"Ty":"Bool"}},"nullable":false,"pk":false,"auto":false,"default":null},{"name":"blog","sqltype":{"Deferred":"PK:Blog"},"nullable":false,"pk":false,"auto":false,"default":null},{"name":"byline","sqltype":{"KnownId":{"Ty":"Text"}},"nullable":true,"pk":false,"auto":false,"default":null},{"name":"likes","sqltype":{"KnownId":{"Ty":"Int"}},"nullable":false,"pk":false,"auto":false,"default":null}]}
{"name":"Post","columns":[{"name":"id","sqltype":{"KnownId":{"Ty":"Int"}},"nullable":false,"pk":true,"auto":true,"unique":false,"default":null},{"name":"title","sqltype":{"KnownId":{"Ty":"Text"}},"nullable":false,"pk":false,"auto":false,"unique":false,"default":null},{"name":"body","sqltype":{"KnownId":{"Ty":"Text"}},"nullable":false,"pk":false,"auto":false,"unique":false,"default":null},{"name":"published","sqltype":{"KnownId":{"Ty":"Bool"}},"nullable":false,"pk":false,"auto":false,"unique":false,"default":null},{"name":"blog","sqltype":{"Deferred":"PK:Blog"},"nullable":false,"pk":false,"auto":false,"unique":false,"default":null},{"name":"byline","sqltype":{"KnownId":{"Ty":"Text"}},"nullable":true,"pk":false,"auto":false,"unique":false,"default":null},{"name":"likes","sqltype":{"KnownId":{"Ty":"Int"}},"nullable":false,"pk":false,"auto":false,"unique":false,"default":null}]}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"name":"Post_tags_Many","columns":[{"name":"owner","sqltype":{"KnownId":{"Ty":"Int"}},"nullable":false,"pk":false,"auto":false,"default":null},{"name":"has","sqltype":{"Deferred":"PK:Tag"},"nullable":false,"pk":false,"auto":false,"default":null}]}
{"name":"Post_tags_Many","columns":[{"name":"owner","sqltype":{"KnownId":{"Ty":"Int"}},"nullable":false,"pk":false,"auto":false,"unique":false,"default":null},{"name":"has","sqltype":{"Deferred":"PK:Tag"},"nullable":false,"pk":false,"auto":false,"unique":false,"default":null}]}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"name":"Tag","columns":[{"name":"tag","sqltype":{"KnownId":{"Ty":"Text"}},"nullable":false,"pk":true,"auto":false,"default":null}]}
{"name":"Tag","columns":[{"name":"tag","sqltype":{"KnownId":{"Ty":"Text"}},"nullable":false,"pk":true,"auto":false,"unique":false,"default":null}]}

0 comments on commit 2770b50

Please sign in to comment.