Skip to content
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

Allow to ignore field of struct #860

Open
DavidBM opened this issue Apr 15, 2017 · 61 comments
Open

Allow to ignore field of struct #860

DavidBM opened this issue Apr 15, 2017 · 61 comments

Comments

@DavidBM
Copy link

DavidBM commented Apr 15, 2017

Actually I have this struct

#[derive(Clone, Debug, Queryable, Serialize, AsChangeset, Identifiable, Associations)]
#[has_many(authors)]
pub struct User {
	pub id: i32,
	pub name: String,
	pub email: String,
	#[serde(skip_serializing)]
	pub password: String,
	pub created_at: DateTime<UTC>,
	pub author: Option<Author>
}

The column author is not in the database, but when I return the model, I would like to put inside of the struct the Author (a user can or not to have an author). For that I want to tell diesel that ignores the field author. I'm searching if something like #[ignore_field].

	#[ignore_field]
	pub author: Option<Author>
@killercup
Copy link
Member

I'd call it #[diesel(skip_deserializing)] to be similar to serde. And we should probably only allow that for fields whose type implements Default.

@DavidBM
Copy link
Author

DavidBM commented Apr 17, 2017

Umm, I don't know if I explain correctly the idea, maybe the serde annotation in my code confuses more than helps.

What I was searching is a way to totally ignore that field in the database. That means, Diesel to load (in that case) always None in the author field. For being able to add data after query to the struct.

Other question than can help. Having this struct:

#[derive(Clone, Debug, Queryable, Serialize, AsChangeset, Identifiable, Associations)]
#[has_many(authors)]
pub struct User {
	pub id: i32,
	pub email: String,
	pub author: Vec<Author>
}

is possible to have something like #[loads_has_many(authors)] to tell diesel to load all the authors in that field?

@killercup
Copy link
Member

What I was searching is a way to totally ignore that field in the database. That means, Diesel to load (in that case) always None in the author field. For being able to add data after query to the struct.

Oh, yes, I think that's what I understood as well. I was suggesting

  1. an attribute name that would not collide with other crates,

  2. and to use the Default trait to figure out what the value of that field should be when diesel tries to deserialize into a User.

    (We could specialize this to Option<_>, but I'd rather not, as this is exactly what Default is for. Option<_>::default() == None, but also String::default() == "" and Vec::default() == vec![].)

Did I get that right?

is possible to have something like #[loads_has_many(authors)] to tell diesel to load all the authors in that field?

Currently, it's possible to get (User, Vec<Author>) but you'd have to write a bit of boilerplate to put that into a new struct. I can see why this is nice to have (and a lot of ORMs have this), but I'm not quite sure how it'd fit Diesel's design. This is usually the "feature" that leads to n+1 query bugs, and we currently try to stay away from mangling models with association data. So, I'd try to work with tuples (using type aliases liberally), and see how it goes.

@DavidBM
Copy link
Author

DavidBM commented Apr 17, 2017

Ok, you understand correctly :)

Thanks! I will check this frequently!

@sgrif
Copy link
Member

sgrif commented May 9, 2017

I've definitely warmed to the idea of marking a field as ignorable for update/insert (to skip a field for update, you can always use my hilarious, hacky workaround for now. I'm not sure I see the use case for skipping something on Queryable though. If you want to populate other data after loading, it seems like it'd make more sense to have another struct that is struct Foo { data_from_database: DbModel, other_data: whatever }

@matt4682
Copy link

I think the only real use case for skipping a field on Queryable would be to imitate the classic ORM approach to associations.

Using the example code from the association docs:

let user = try!(users::find(1).first(&connection));
let posts = Post::belonging_to(&user).load(&connection);

then you would make posts a proper child of user:

user.posts = posts;

The docs say that you could pass it around as (User, Vec<Post>) but that doesn't exactly play nicely with serializing to JSON, which seems to be the initial reason behind this issue. The typical JSON representation would be:

{
   "id": 0,
   "name": "Foo",
   "posts": []
}

But passing it around as a tuple would have it represented as:

{
  "user": {
     "id": 0,
     "name": "Foo"
  },
  "posts": []
}

@TatriX
Copy link

TatriX commented Jan 20, 2018

My use case. I have a struct:

#[derive(Deserialize, AsChangeset)]
#[table_name = "users"]
pub struct UpdateUserData {
    username: Option<String>,
    email: Option<String>,
    bio: Option<String>,
    image: Option<String>,
    #[skip_somehow_plz]
    password: Option<String>,
}

I use this struct to parse POST params and then I'd like to use it as a changeset.
But my table doesn't have a password field. It has hash field.

So I want to skip password field in the changeset and set hash manually based on password.

@carlosdp
Copy link
Contributor

I have the same use case as @TatriX, the reason I want the field to be in the same struct as Insertable is because I use it for field validations as well.

@TatriX
Copy link

TatriX commented Jan 29, 2018

As far as I know this depends on #1512
Original use case currently can be fulfilled with nested structs and query with join.
Example from @weiznich:

#[derive(Queryable)]
struct Foo {
    id: i32,
    name: String,
    bar: Bar,
}

#[derive(Queryable)]
struct Bar {
     id: i32,
    name: String
}

let r: Vec<Foo> = foo::table.inner_join(bar::table).select((foo::id, foo::name, (bar::id, bar::name)).load(conn)?;

I'm ready to take this issue if you think this should be implemented.

@sgrif
Copy link
Member

sgrif commented Jan 29, 2018

It's not blocked on that PR. It just hasn't been a priority. As I mentioned in #860 (comment) though, if/when this lands, it'll be for AsChangeset and Insertable, not Queryable.

@TatriX
Copy link

TatriX commented Jan 30, 2018

It has a good use case to be implemented on Quaryable too. Basically it's the same as with Insertable.
I want to be able to reuse the same struct to do select queries, then fill the Option<NestedStruct> from another select and pass it to the json serializer.

@misha-krainik
Copy link

misha-krainik commented Feb 4, 2019

I've definitely warmed to the idea of marking a field as ignorable for update/insert (to skip a field for update, you can always use my hilarious, hacky workaround for now. I'm not sure I see the use case for skipping something on Queryable though. If you want to populate other data after loading, it seems like it'd make more sense to have another struct that is struct Foo { data_from_database: DbModel, other_data: whatever }

The link is broken. Could you repeat your solution? I have a structure with only one field other in update and create action (e.g. upsert action), and I want to use one struct with more than 40 fields in two cases.

@weiznich
Copy link
Member

weiznich commented Feb 4, 2019

@mikhail-krainik That was this thing. But be warned: I'm quite sure doing that is a bad idea…

@DavidBM DavidBM changed the title Allow to ignore field os struct Allow to ignore field of struct Feb 7, 2019
@TrevorFSmith
Copy link

TrevorFSmith commented Sep 23, 2019

I believe I have another use case where it would be helpful for marking fields of a struct to be ignored for a derived AsChangeset: timestamps that are automatically updated by the database.

For example, most of my tables track when they were created and the most recent modification time in two columns, created and modified. I never want diesel to update those columns. It would be very handy to mark those columns as ignored during an update, like so:

#[derive(Identifiable, AsChangeset, PartialEq, Debug, Serialize)]
#[table_name = "bodies"]
pub struct Body {
	pub id: Uuid,
	pub name: String,
	#[ignore_during_update]
	pub created: NaiveDateTime,
	#[ignore_during_update]
	pub modified: NaiveDateTime,
}

@lamka02sk
Copy link

@TrevorFSmith This would be lovely, or maybe detect on update fields in schema and then skip them automatically.

@alexwennerberg
Copy link

Interested in this feature. My use case is that I want to take a nested JSON object returned by an API and generate a number of insertable structs. I want the parent struct to have the child struct as a Vec field so I can deserialize the JSON, but that makes it so I can't insert the struct into the database.

@dyst5422
Copy link

Another use case:

Reuse of objects between Diesel and Juniper

@weiznich
Copy link
Member

@dyst5422 I've written wundergraph for this use case as you have to think about much more than a few field attributes for providing a graphql API that does not suffer from N+1 query problems. The normal juniper API generates N+1 queries for a nested entity.

@tuxzz
Copy link

tuxzz commented May 18, 2020

I have to add some std::marker::PhantomData<T> into my struct. I can't achieve this without this function.

@weiznich
Copy link
Member

@tuxzz Just commenting here that you need a certain feature will help that the feature appears in a magical way. You either need to implement all traits manually, which would allow you to do whatever you want there, or sit down and implement that feature for diesel or find a other solution.

@alfonso-eusebio
Copy link

TL;DR: I have an initial implementation of something that could address this issue.

I think there are a number of user cases that are fairly common throughout different projects.
Perhaps one of the main underlying reasons is that nobody likes having to create a bunch of different structs to deal with the same "object" (e.g. user, account, etc.)
I believe there are reasons for Diesel separating the Queryables from the Insertables, and I don't have any basis to argue that there's a better way - even if I'd prefer a more unified approach, purely out of laziness.

However, it is often the case that the struct that you get from DB needs to be "augmented" with additional information before it becomes complete, and that process would (ideally) be lazy in nature.
A typical scenario (at least the one I'm in) is: DB -> struct -> logic -> struct -> JSON. By the time the "object" coming from DB is fully formed to serialise into JSON it might have required the addition of other bits of data.
Now, you can do that by "collating" the main struct and the additional info into another, bigger struct. However, given Rust's ownership rules, I've found this to be quite challenging without cloning data all over the place. I really dislike cloning data for no good reason; and I have the impression that's pretty common amongst Rust users.

It would be really useful if the more experienced Diesel users could share their best practices to overcome the challenge described above. It could alleviate the problem of not having optional fields and, most importantly, show me some proper Rust coding practices! :-)

So, now that the long and winded intro is out of the way, I've actually gone ahead and created my version of Queryable that allows for optional fields. It looks something like this:

#[derive(PartialQueryable, Serialize)]
pub struct Account {
    pub id: u32,
    pub name: String,
    #[table_name = "group_acc"]
    #[column_name = "group_id"]
    pub id_of_group = u32,
    
    // ... other fields ...
    
    #[diesel(skip)]
    pub activity_dates: Vec<AccActivityDates>
}

For now I'm calling it PartialQueryable and it allows you to add a #[diesel(skip)] attribute to any struct field, which would not be included in the derive's code generation.
Furthermore, in order to simplify the query generation code, it creates a tuple with all the columns as defined in the struct. For the example above it would create Account::ALL_COLUMNS, so you can write something like this:

account::table
    .inner_join(group_acc::table)
    .filter(account::id.eq_any(acc_ids))
    // This is equivalent to (accounts::id, accounts::name, ...) - `activity_dates` not included
    .select(Account::ALL_COLUMNS)
    .load::<Account>(&conn)
    .expect("Something went wrong.")

This is working in my own project, but I have to clarify that I'm only using it in one place at the moment.

In fact, it includes some additional options, which you might have noticed in the example.
In order to allow you to include fields from other tables in your struct - and still be able to auto-generate ::ALL_COLUMNS -, you can use the table_name and column_name attributes for fields. In the example above I'm including the Account's id_of_group in the struct, which is comming from a second table inner-join'd in the query. I've used a weird field name to illustrate using column_name.
So Account::ALL_COLUMNS is actually something like (accounts::id, accounts::name, group_acc::group_id, ...).

My implementation is fairly basic and probably doesn't cover a number of corner cases. Things might (will?) start to fall apart if you start doing fancy stuff with these additional "powers".
In any case, I'll be happy to create a PR if the maintainers think it is worth considering for review and potential inclusion - as an extension to Queryable or as a separate derive.

At the moment this is just on a local repo that's simply a manual copy of enough code from Queryable to make it work. But I could prepare it for a PR if it's going to be considered.

Cheers!

@weiznich
Copy link
Member

@alfonso-eusebio As a general note at the top: As pointed out in this comment (#860 (comment)) and this PR (#1582) we are not interested in having an option to ignore fields for Queryable as it breaks the basic contract that Queryable (and similar functionality) is a simple direct mapping from results to a struct. It wouldn't be clear anymore what would be used to initialize the other fields. This has been discussed a few times before and I honestly do not see any new arguments here. For the general case there are already several solutions for solving the perceived difference between the data structure returned by the query and the data structure to return a certain serialization format:

  • For anything using serde (so serializing to json) it is as simple as using #[serde(flatten)] on a field on an outer struct that contains the data loaded from the database. So something like this already just works out of the box: (In decreasing order of relevance)
#[derive(Queryable, Serialize)]
struct User {
    id: i32,
    name: String,
    // …
}

#[derive(Serialize)]
struct OuterUser {
    #[serde(flatten)]
    user: User
    other_additional_field: Foo,
}

// serializing OuterUser results in
// { "id": 42, "name": "John", "other_additional_field": {}}

(This case already covers >90% of the use cases of the requested feature in my experience)

  • Request a default value via query: users::table.select((users::id, users::name, 1.into_sql::<Integer>()).load::<UserWithOtherField>(&conn)?;
  • Manually implementing Queryable, that allows you to specify whatever constant value you want to use to initialize a "ignored" struct field
  • Provide a third party derive that does the last thing automatically

The following section addresses specific parts of the comment above:

Now, you can do that by "collating" the main struct and the additional info into another, bigger struct. However, given Rust's ownership rules, I've found this to be quite challenging without cloning data all over the place. I really dislike cloning data for no good reason; and I have the impression that's pretty common amongst Rust users.

I'm not sure where exactly additional cloning is involved to include additional information. The workflow with your proposed extension is roughly the following:

#[derive(PartialQueryable, Serialize)]
pub struct Account {
    pub id: u32,
    pub name: String,  
    // ... other fields ...   
    #[diesel(skip)]
    pub activity_dates: Vec<AccActivityDates>
}

let mut db_result = accounts::table.load::<Account>(&conn)?;
for a in &mut db_result {
    // just a unrelated side note:
    // if this accesses the database you get a N+1 query bug
    a.get_activity_dates();
}

while using the built in API + serde gives you this:

#[derive(Queryable, Serialize)]
pub struct Account {
    pub id: u32,
    pub name: String,  
}
#[derive(Serialize)]
pub struct JsonAccount {
    #[serde(flatten)]
    pub user: Account,
    pub activity_dates: Vec<AccActivityDates>
}

let db_results = accounts::table.load::<Account>(&conn);

db_results.into_iter().map(|user| { 
    // same about N+1 queries applies here
    // but if you need to load stuff from the database here
    // you can now use the `diesel::associations` api to separately load
    // those data at once and then use `.zip()` instead to merge the lists
    JsonAccount {
        user,
        activity_dates: get_activity_dates()
    }
}).collect(); 

In fact, it includes some additional options, which you might have noticed in the example.
In order to allow you to include fields from other tables in your struct - and still be able to auto-generate ::ALL_COLUMNS -, you can use the table_name and column_name attributes for fields. In the example above I'm including the Account's id_of_group in the struct, which is comming from a second table inner-join'd in the query. I've used a weird field name to illustrate using column_name.
So Account::ALL_COLUMNS is actually something like (accounts::id, accounts::name, group_acc::group_id, ...).

That is a completely separate feature in my opinion. In fact there is already something like that in development with #2367. I would appreciate feedback about the usability of this API there. Also if someone comes up with good idea that does not require a separate query method to implement this feature that would also be fantastic.

@Ten0
Copy link
Member

Ten0 commented Nov 21, 2021

Is it just that extra .table_data.field thing that is the problem

Totally just that. I feel the caller should not have to bother with handling that table_data level when it seems to just be implementation detail of the "provide me with the data" part of the code. I do not have a particularly compelling example.

Basically something like this

I'm not sure to what extent such a user wouldn't with the current interface, either:

  • If they are going to ignore that kind of performance issue, make the same mistake by reading only the first part of the associations API and (pretty much):
    let posts: Vec<Post, Vec<Comment>> = posts
        .into_iter()
        .map(|post| Ok((post, Comment::belonging_to(&post).load::<Post>(connection)?)))
        .collect()?
  • If they are going to not be looking for such tooling in the doc (which happened to me for instance), write (pretty much)
    let mut comments = comments::table
        .filter(comments::post_id.eq_any(posts.iter().map(|p| p.id)))
        .load(conn)?
        .map(|c| (c.post_id, c))
        .into_group_map();
    let posts = posts.into_iter().map(|p| (p, comments.remove(&p.id).unwrap_or_default()))

If users typically already do either of these things, then it looks like it wouldn't really increase the mistake rate.
But it's true that's pretty uncertain, and perhaps tricking them into reading that documentation looking for something that would help packing related fields inside their structures, even if that actually doesn't enable them to do precisely that, can prevent them from doing this mistake at least.

@weiznich
Copy link
Member

Is it just that extra .table_data.field thing that is the problem

Totally just that. I feel the caller should not have to bother with handling that table_data level when it seems to just be implementation detail of the "provide me with the data" part of the code. I do not have a particularly compelling example.

In that case there is nothing wrong with having another type for the return type there, right? We need always to iterate over all post to group the corresponding comments there. Having an additional map there to construct a named struct instead of a tuple should be a noop performance wise. The only remaining overhead is having a additional type. For that the reasoning can be similar like for the Queryable/Insertable split for tables with auto incrementing keys.
Maybe we should just add something like that as explicit example to the documentation 🤔 ? Or even write a select and a associations guide after the 2.0 release?

If they are going to not be looking for such tooling in the doc (which happened to me for instance), write (pretty much)

That's pretty much the implementation of belonging_to for arrays. So choosing this way is fine from a performance point of view.

@zacbre
Copy link

zacbre commented Sep 9, 2022

Are there any updates to this particular issue? I am running into the same problem, only wanting to set a field from an associated query. This would be hugely helpful, as other popular ORMs for different languages (C# - Linq2Db, EF) have support for this.

@Ten0
Copy link
Member

Ten0 commented Sep 9, 2022

This is not a matter of technical limitation but rather a matter of whether we want to do that, and currently we are not sure we want it for the reasons described above - there are no "other updates" in this topic than the discussions in this thread.

#860 (comment)
#860 (comment)

If you have a "compelling use case" not already presented above you're welcome to present it.

@thomasmost
Copy link
Contributor

@weiznich I have a different use-case that may merit discussion. I am running into a ResourceExhausted error on a rather large query for historical transactions in my application:

thread 'rocket-worker-thread' panicked at 'Error reading org_transactions: DatabaseError(Unknown, "rpc error: code = ResourceExhausted desc = grpc: received message larger than max (19188992 vs. 16777216)")', src/dal/tx.rs:219:8

I believe this is because of a raw_json field on each transaction that is probably greatly expanding the size of each record from the db.

I opened this discussion (#3379) because I would like to write a new queryable type that does not have the raw_json, or somehow set it to none within the query such that it is not actually pulled over from the database.

Also, is the default GRPC max of 16 megabytes configurable at all?

@Ten0
Copy link
Member

Ten0 commented Oct 25, 2022

@thomasmost

Your issue seems unrelated to this, as implementing this is not a requirement to solve it: #3379 (comment).

gRPC discussions are unrelated to diesel, but this is a matter of both client and server configuration.

@jayvdb

This comment was marked as off-topic.

@weiznich

This comment was marked as off-topic.

@mikeygithub
Copy link

Are there any updates? It really is a very useful feature.

@Ten0
Copy link
Member

Ten0 commented Jul 8, 2023

@mikeygithub no. #2933 (comment)

@salheb

This comment was marked as duplicate.

@weiznich

This comment was marked as off-topic.

@AlexStorm1313
Copy link

AlexStorm1313 commented Sep 20, 2023

@weiznich @Ten0
After reading the comments on this issue and the corresponding drafted pull request I want to give my use case and implementation details for this feature. The problem that I am currently facing is the inability to reuse structs throughout my application.

To give some context currently I have a struct for each 'action' Create, Read, Update and Delete these structs are used internally for interacting with the database. Now to expose this data to for example an end user via an API I want to omit the ID field that is used by the database. There are multiple solutions for this problem one of them is to create a different struct that doesn't have this ID field present and with a From/Into this can be done pretty elegant. Because the API exposes this data via JSON the serde crate offers a #[serde(skip)] to omit this data from the end user I have to agree this is not applicable for everyone, but I don't have to maintain two structs of each action for each table(which adds to a lot of boilerplate code fast).

Now if I want to have a struct that combines multiple tables into one representation I need to have a struct for internal use that has a field foreign_table_id: i32 and a struct that has foreign_table: ForeignStruct while the other fields are the same.

My proposition is to use the same syntax as serde uses for skipping fields #[diesel(skip)] Optional(#[diesel(skip_serializing)], #[diesel(skip_deserializing)]). Using skip makes diesel ignore the field for schema.rs matching that way the struct can be used internally and externally. Other naming suggestion: #[diesel(skip_schema)] to imply that this field does not exist in the database. To be super clear when using #[diesel(skip)] diesel is not aware of this field no Default no nothing so it must be wrapped in an Option<T> or otherwise things like Queryable and Selectable would fail.

In this example I can use this struct to retrieve it and it's relations and populate the ForeignStruct using the associations API, Shown in the example PagesWithBooks

struct ReusableStruct{
  #[serde(skip)]
  pub id: i32,
  #[serde(skip)]
  pub foreign_table_id: i32,
  #[diesel(skip)]
  pub foreign_table: Option<ForeignStruct>,
}

Reading this #860 (comment) I do agree that a 1 to 1 representation is better, something like dsync would solve this manual 'duplication' of code but the tool is not configurable enough(yet) to fully automate this unfortunately. An other suggestion I have is to lock this field skipping behind a feature flag to take away the concern that people will create 1+N queries and only enable this when they know what it does and know what they are doing with it.

I hope this makes a compelling argument for including this feature. If there are any questions, further clarifications or oversight feel free!

@Ten0
Copy link
Member

Ten0 commented Sep 21, 2023

@AlexStorm1313 Hi! Thanks for the details on your use-case.

I think I understand. IIUC the design you currently "have to have" is something like this:

#[derive(Queryable, Selectable)]
struct ForInternalUse {
	id: i32,
	foo: String,
	bar_fk: i32,
}

struct UserFacing {
	id: i32,
	foo: String,
	bar: Bar,
}

If that is indeed the case, have you considered doing:

#[derive(Queryable, Selectable, Serialize)]
struct ForSelect {
	#[serde(skip)]
	id: i32,
	foo: String,
	#[serde(skip)]
	bar_fk: i32,
}

#[derive(Serialize, derive_more::Deref)]
struct ForUserAndInternalUseAfterPopulatingFks {
	#[deref]
	#[serde(flatten)]
	internal: ForSelect,
	bar: Bar,
}

AFAICT this also avoids field duplication, while providing you with better typing all along the code chain than your proposed solution (IIUC):

#[derive(Serialize, Queryable, Selectable)]
struct ForBothUse {
	#[serde(skip)]
	id: i32,
	foo: String,
	#[serde(skip)]
	bar_fk: i32,
	#[diesel(skip)]
	bar: Option<Bar>,
}

Typing is better in the previous solution because you have no scenario where you can forget to populate bar and then use it as if it had been populated (that is, by unwrapping "this should have been present", which is verbose in itself, or worse, considering a Vec empty when it should not have been and propagate a further bug).

NBs:

  • IIRC there isn't a strong opposition to ignoring fields of structs when it comes to inserting/updating in database (the feature might even be already there, not sure), and this thread is solely about whether it's really a good thing to allow filling with dummy values when selecting, considering the above alternative is available.
  • Setting a field to its default value can't really be considered as "ignoring it". It's instead explicitly setting it to its default value, which can have dire consequences if you're not aware that the field is filled with dummy values, so I think default or even skip_querying_use_default makes for a better name with regards to the select part, considering how harmless "skip" looks at first glance.
  • I'm not sure why skipping sending the id to the user is something you ever want to do because that makes it harder for the user to reference your resource. Also I'm not sure how it is relevant to the current question since it seems you have already solved that point using #[serde(skip)], did I miss something?

@AlexStorm1313
Copy link

@Ten0 Thanks for the reply with detailed suggestions. After trying out the suggestions and some variations of it I have come to the same conclusion. The option to ignore/skip fields is not desirable. For anyone that is facing a similar situation my suggestion is to bite the bullet and have separate structs for use with diesel(database). I find that a lot of code can be put in macros and/or traits and be reusable that way. Think of generic CRUD functions or default fields like id and created_at.

@knickish
Copy link

I just ran into this issue with a generated column in postgres which I need the DB to manage, but I have to manually implement 'Insertable' in order to not write to that column. A 'diesel(read_only)' attribute for that column/struct field would have been very helpful.

@weiznich
Copy link
Member

@knickish As written several times above: We are open to accept PR's that adds skip attribute for Insertable/AsChangeset, it's just at least for me no priority item to work on. Repeating that such an attribute would be helpful is not productive, so consider working on contributing this feature.

@DylanVerstraete
Copy link

Code becomes confusing when having different structures for inserting / updating / querying / .. I think you can use the diesel query syntax to submit only the fields of a struct that you want to insert.

Imagine having the following struct:

struct Post {
    pub id: i32,
    pub name: String,
    pub content: String,
}

And you want to insert this Post into the database:

    let result = diesel::insert_into(posts_table)
        .values((
            posts::name.eq(post.name),
            posts::content.eq(post.content),
        ))
        .returning(posts::id)
        .get_result(connection)
        .await;

Of couse for large structures this becomes a huge effort to extend and maintain.

@Ten0
Copy link
Member

Ten0 commented Oct 24, 2023

Code becomes confusing when having different structures for inserting / updating / querying

IMO it's even more confusing/dirty when you have to put dummy values and risk misusing fields that contain dummy values.
Give a go at having Post and PostData with Deref and embed. Will feel the same without the associated risk.

For any post below:
same thing. Feels like we've been repeating the same thing several times and it always turned out to be the proper solution.

@phayes
Copy link

phayes commented May 16, 2024

Hi @weiznich,

We need this feature but don't have the skillset to implement it. Would you or someone on the diesel team be willing to accept a monetary sponsorship / bounty to implement this feature?

@weiznich
Copy link
Member

@phayes I'm generally open to such sponsorships, but it really depends on what exactly you expect to happen here:

  • skip_insertion is already implemented on the master branch and will be available with the next feature release. This would allow to skip a field for #[derive(Insertable)]
  • a hypothetical skip_deserialize attribute for #[derive(Queryable)] is something that we explicitly do not want to support for the reasons outlined several times in this thread.
  • a hypothetical skip_update attribute for #[derive(AsChangeset)] would be acceptable. That's something that is currently not implemented.

Given that the only attribute that could be sponsored would be skip_update.

@phayes
Copy link

phayes commented May 16, 2024

Hi @weiznich,

What I'm looking for is something like in the orignal post that opened this issue, an #[ignore_field] attribute that makes Diesel ignore a field in it's entirety (schema generation, inserts, updates, and queries - all of it). It wasn't clear to me reading the discussion that a blanket #[ignore_field] attribute was off the table. If that is the case I apologize for adding to the noise here.

@marziply
Copy link

Hi @weiznich,

What I'm looking for is something like in the orignal post that opened this issue, an #[ignore_field] attribute that makes Diesel ignore a field in it's entirety (schema generation, inserts, updates, and queries - all of it). It wasn't clear to me reading the discussion that a blanket #[ignore_field] attribute was off the table. If that is the case I apologize for adding to the noise here.

My sentiments are the same, this would be a really good feature to have.

@weiznich
Copy link
Member

To repeat what is already written several times above: It's fine that you believe that this would good to have but we as maintainers have not seen a good use case for this that cannot be solved in a better way with existing tooling. Feel free to present a novel use case that is not listed above, otherwise this does not add much to the discussion.

@marziply
Copy link

To repeat what is already written several times above: It's fine that you believe that this would good to have but we as maintainers have not seen a good use case for this that cannot be solved in a better way with existing tooling. Feel free to present a novel use case that is not listed above, otherwise this does not add much to the discussion.

This whole thread is filled with your combative responses as well as unnecessary policing of comments. It's perfectly reasonable to ask for updates (within moderation, once every few months maybe) because I've seen plenty of issues in FOSS pick up pace with a nudge from the community. Folks have given plenty of use cases for this feature so I'm not sure why you're asking for more.

It's probably better for you to work the problem, ask more questions, and listen more to the community than to say "those points have been made already".

Having multiple people say the same thing multiple times is actually a good thing. It tells us there is consensus outside of your perspective.

@weiznich
Copy link
Member

@marziply To be clear here: I need to do nothing as I own you (any anyone else) nothing. Diesel is free software, that means it is provided as it is without any guarantees. You can use it, patch it, do whatever you want, but you cannot request from me to do something. I might be interested in accepting certain contributions or not. In this case I've already expressed multiple times that we are not interested in this specific feature for the reasons outlined in this thread. I still see no reason to implement that feature as no one cared to explain why the existing solutions are not sufficient and how the outlined problems with the "proposed" solution can be solved. Did you caro to write that down before you claimed that there is "some community" consensus on this feature? I see anything here, but "consensus" as everyone seems to want something with slightly different semantics. So if you really care about this feature I would suggest that you sit down and write a proposal that answers the following questions:

  • How is this feature supposed to work exactly (what exactly should happen for fields marked as ignore_field while loading from the database)
  • How does it address the outlined concerns (mainly about N+1 queries)
  • How would that be easier to use than the existing outlined solutions

To write that down as last sentence: Please stop to request anything here. This behavior is not welcome as outlined above.

@1Dragoon
Copy link

I've found myself in want of this a few times, usually working around it by having two separate structures, but that inevitably means more code in ways that proc macros are meant to solve. Here are my two cents:

How is this feature supposed to work exactly (what exactly should happen for fields marked as ignore_field while loading from the database)

My instinct is to do serde-esque things like skip, skip_serializing, skip_serializing_if, skip_deserializing, default, as, etc. In the case of serde, the field's data type must implement Default, and if the field has no data or you just tell it to skip_deserialing, it yields the default value for that field.

How does it address the outlined concerns (mainly about N+1 queries)

I don't understand the the internals of diesel that well, but can't the proc macro simply insert Default::default() for this field at compile time?

@weiznich
Copy link
Member

weiznich commented May 21, 2024

@1Dragoon I'm sorry but this still repeated the same points that were made before. More importantly it does not address the concerns listed in this (#860 (comment)) and the following comments. It's really not helpful to repeat the same discussion again and again. It also explains the N+1 problem more in depth (Hint: Your proposal does not even touch that problem at all).

On a more general note: As some people already claimed that "everyone" agrees on that's a good feature and it needs to be implemented now: There is nothing stopping you from implementing an extension to diesel that supports this feature. After all all the derives are already in an external crate, so you can easily provide an in your opinion enhanced drop in replacement for any derive. Given that no such thing exists today I don't believe that it is something that "everyone" wants.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests