-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Clarification on update
#26
Comments
Yeah, you wouldn't want to make the columns This popped into my head as a possible solution. Thoughts? #[changeset_for(users)]
pub struct UserChanges {
id: i32,
#[optional_update]
first_name: Option<String>,
#[optional_update]
last_name: Option<String>,
#[optional_update]
email: Option<String>,
} Could also specify it at the struct level. #[changeset_for(users, behavior_when_none="skip")]
pub struct UserChanges {
// ...
} In the short term, I would just do 3 queries to work around this. |
So I think this is what the impl AsChangeset<users> for UserChanges {
type Changeset = Vec<Box<Changeset<Target=users>>>;
fn as_changeset(self) -> Changeset {
let changes = Vec::new();
// push any fields we always update
if let Some(first_name) = self.first_name {
changes.push(Box::new(users::first_name.eq(first_name)))
}
// repeat for each field
changes
}
} I don't think there's a way to do this without boxing, and also using a |
A valid use case has arisen in #26 that is impossible to work around at the moment. We're going to eventually add a standard API to handle this, so this implementation has no tests. It will get tested through that API, once we've figured out what the API will look like. But I want to get this through so it's possible to work around this by implementing `AsChangeset` manually.
I've pushed up the required |
Trying to build pub struct UserChanges {
id: i32,
first_name: Option<String>,
last_name: Option<String>,
email: Option<String>,
}
use self::users::dsl::*;
impl AsChangeset for UserChanges {
type Changeset = Vec<Box<Changeset<Target=users::table>>>;
fn as_changeset(self) -> Changeset<Target=users::table> {
let changes = Vec::new();
if let Some(first_name) = self.first_name {
changes.push(Box::new(first_name.eq(first_name)))
}
changes
}
}
table! {
users {
id -> Serial,
first_name -> VarChar,
last_name -> VarChar,
email -> VarChar,
}
} produces the error:
|
And trying to use
|
Your return type should be |
And you'll need |
The compiler is still sad for the same ( impl AsChangeset for UserChanges {
type Changeset = Vec<Box<Changeset<Target=users::table>>>;
fn as_changeset(self) -> Self::Changeset> {
let mut changes = Vec::new();
if let Some(first_name) = self.first_name {
changes.push(Box::new(first_name.eq(first_name)))
}
changes
}
} |
|
Whoops, I need to add some |
Just pushed an update. This compiles, and should work as a workaround (it's what I'll have the annotation generate) impl AsChangeset for UserChanges {
type Changeset = Vec<Box<Changeset<Target=users::table>>>;
fn as_changeset(self) -> Self::Changeset {
let mut changes: Vec<Box<Changeset<Target=users::table>>> = Vec::new();
if let Some(first_name) = self.first_name {
changes.push(Box::new(
users::first_name.eq(first_name).as_changeset()
))
}
if let Some(last_name) = self.last_name {
changes.push(Box::new(
users::last_name.eq(last_name).as_changeset()
))
}
if let Some(email) = self.email {
changes.push(Box::new(
users::email.eq(email).as_changeset()
))
}
changes
}
} |
I was able to get update to work implementing AsChangeset, however I was only able to get it to build using execute_returning_count. I could not get it to return the result using query_one. This was the error that I was getting:
and the same error with Query:
|
@mfpiccolo I got let changeset = UserChanges {
id: user_id,
first_name: body.get("first_name").map(|attr| attr[0].to_owned()),
last_name: body.get("last_name").map(|attr| attr[0].to_owned()),
email: body.get("email").map(|attr| attr[0].to_owned()),
};
let command = query_builder::update(users.filter(id.eq(changeset.id))).set(changeset);
let result: Option<User> = connection.query_one(command).unwrap(); |
I think we're seeing rust-lang/rust#28894 strike again. The actual issue is that |
Yep that was it. Passing the command itself and not the borrowed command was the issue. |
This may be old news the people conversing here, but opaleye's approach to insert/update seems like a reasonably nice one, at least in haskell-land: https://github.com/tomjaguarpaw/haskell-opaleye/blob/master/Doc/Tutorial/TutorialManipulation.lhs#L48-L88 |
@bwo Yeah, I'm going to basically give the ability to choose between the current behavior and that behavior. I don't want to stop supporting putting |
I'm going to change the behavior of I will likely introduce an additional type in the future to represent wanting to assign null (which when deserializing from JSON will require the key being present, and the value being |
My original thoughts on how to implement this involved boxing and sticking in a `Vec`. Unfortunately, that requires implementing `AsChangeset` for `UserChanges` and not `&UserChanges`, which I don't want to do by default. If you actually want to assign `NULL`, you can still do so by doing `update(table).set(column.eq(None))`. In the future we might introduce an additional type to make it possible to assign null through codegen. With this change in implementation, I've removed the impl of `Changeset` for `Vec<T>`, as it feels redundant with a tuple containing options. I've done a best effort to make sure we generate valid SQL in the various cases. There is still one error case, when the resulting changeset has 0 fields to update. This will be corrected in another PR, as I'm still considering whether we should do magic to return the same thing as we would otherwise, or whether it's an error case. Fixes #26
First pass at correcting this for those interested: #47 |
My original thoughts on how to implement this involved boxing and sticking in a `Vec`. Unfortunately, that requires implementing `AsChangeset` for `UserChanges` and not `&UserChanges`, which I don't want to do by default. If you actually want to assign `NULL`, you can still do so by doing `update(table).set(column.eq(None))`. In the future we might introduce an additional type to make it possible to assign null through codegen. With this change in implementation, I've removed the impl of `Changeset` for `Vec<T>`, as it feels redundant with a tuple containing options. I've done a best effort to make sure we generate valid SQL in the various cases. There is still one error case, when the resulting changeset has 0 fields to update. This will be corrected in another PR, as I'm still considering whether we should do magic to return the same thing as we would otherwise, or whether it's an error case. Fixes #26
My original thoughts on how to implement this involved boxing and sticking in a `Vec`. Unfortunately, that requires implementing `AsChangeset` for `UserChanges` and not `&UserChanges`, which I don't want to do by default. If you actually want to assign `NULL`, you can still do so by doing `update(table).set(column.eq(None))`. In the future we might introduce an additional type to make it possible to assign null through codegen. With this change in implementation, I've removed the impl of `Changeset` for `Vec<T>`, as it feels redundant with a tuple containing options. I've done a best effort to make sure we generate valid SQL in the various cases. There is still one error case, when the resulting changeset has 0 fields to update. This will be corrected in another PR, as I'm still considering whether we should do magic to return the same thing as we would otherwise, or whether it's an error case. Fixes #26
This is actually quite a major issue for my use case. I use my changeset struct as a proxy object for the actual database object, that is also cached and updates the records on Drop. I do not need fields, that get optionally updated, I am updating the whole record anyway. I need a way to clear the nullable fields. Implementing AsChangeset manually is quite verbose, an additional option for the old behaviour would be very nice. |
FWIW it sounds like you don't actually need to use |
well that is still quite large and verbose, as my struct has a lot of fields. I will go with that for now, but it would be nice to be able to just use |
Yeah, I'll probably add an option for the behavior you want at some point. We just need to be careful to make sure the API is sufficiently clear what the difference is, and make sure its a wide enough use case to justify being in the core library, as we don't want to end up with 18-billion options (e.g. after this I wouldn't be surprised for someone to have a struct where they want one |
I totally understand that, but this recent change just moved the api hole to the less common use case, although there is a quite easy option to work around that. Another Alternative might be to add a new enum that might either be Null or a value, that can be instanciated from an Option. |
Yeah, I've considered that in the past as well. There's a similar gap for inserts, where there's no way to insert For just this case though, I think I'm fine with something like |
Because I am not much a database guy I try to abstract away most of the database internals and just have a nice struct representation of my internal database structures. And that change would allow me easily to have one struct to rule all use cases without write much additional code. |
@sgrif Thanks! |
So far
update
is the hardest to wrap my head around, and I was hoping for some clarification/guidance.In the context of an HTTP API, with a users table that looks like:
how would you go about implementing a Users#update action?
My first instinct is to have something like:
Such that you end up updating whatever valid params the user sends, but that requires having
Nullable
columns in the table macro, when the columns aren't actually nullable.Keep up all the great work!
The text was updated successfully, but these errors were encountered: