When using the "update" mode of drupal_write_record(), and your primary keys are not serials, the query that is generated and executed updates the columns that are being used to select the row! Silly Example that updates node.title based on "created":
$object = array('created' => 123, 'title' => 'new title');
drupal_write_record('node', $object, 'created');
This actually executed:
UPDATE {node} SET title = '%s', created = %d WHERE created = %d
with query arguments of: 'new title', 123, 123.
It should really be executing
UPDATE {node} SET title = '%s' WHERE created = %d
with query arguments of: 'new title', 123.
Comments
Comment #1
lyricnz commentedPS: there's no need for the third argument to drupal_write_record to actually be primary keys: it's just used to build the WHERE part of the insert/update.
Comment #2
lyricnz commentedHere's a patch that prevents fields in the "primary key" being included in the update fields.
Comment #3
lyricnz commentedCNR
Comment #4
lyricnz commentedPlease review.
Comment #5
damien tournoud commentedBugs are fixed in 7.x first, before being considered for backport.
Comment #6
lyricnz commentedBug is still present in D7:
UPDATE {node} SET title=:db_update_placeholder_0, created=:db_update_placeholder_1 WHERE (created = :db_condition_placeholder_0)Comment #7
lyricnz commentedPatch for D7 attached.
Comment #9
lyricnz commentedUpdate patch to fix bug in includes/path.inc where it passes NULL to drupal_write_record()
Comment #10
berdirShouldn't it be array('pid') then?(No, not required, wasn't clear from the patch...)Also, did you leave the debugging code there for a reason?
Comment #11
lyricnz commentedThe argument I replaced in path.inc was "NULL", which should not be passed as third argument to drupal_write_record(). The third argument to that function lists the "primary key" (used for updates) - when it is missing/array(), it's actually an insert.
Yes, I had just noticed the debug code - removed. Thanks.
Comment #12
lyricnz commentedWahoo. Would you consider that RTBC now, Berdir?
Comment #13
jbrown commentedI have implemented this in a cleaner manner.
I also renamed the $primary_keys parameter to $update_keys as it better describes what it (now) does.
I have included your fix to path_save() as it misuses drupal_write_record() .
See also
Comment #14
damien tournoud commentedThis is not the behavior I would expect. I would expect we skip the field if it is an update key *and* it is not present in
$object.Comment #15
jbrown commentedYes - good point!
Fields are skipped anyway for update if they are not provided. It's not possible for drupal_write_record() to determine if an update key is intended to be updated.
After all, it updates other fields in the row regardless if they have changed or not.
This isn't fixable without changing the drupal_write_record()'s parameters. The values of the update keys would have to be specified separately.
I say we don't fix this, as it doesn't really cause a problem.
However, I do like renaming $primary_keys to $update_keys as it better describes what it is for.
Comment #16
jbrown commentedNo - sorry I am talking out of my ass.
#14 is incorrect. There is no point in updating a value to the value it is in the WHERE condition.
drupal_write_record() simply does not support modifying the value of update_keys.
I'm still happy with my patch in #13.
Comment #17
jbrown commentedComment #18
lyricnz commentedHow is this "in a cleaner manner"? All you did was rename the argument, and remove the comment that explained *why* items are removed from $fields. Actually, I disagree with the parameter name change - "update keys" has no meaning. (Admittedly, "primary key" wasn't quite accurate either - but that's what we're all used to, and it's roughly right).
Comment #19
jbrown commented@lyricnz The modification I made to your patch means that it earlys out of the foreach. There is no point in setting keys just to unset them immediately after.
I have improved the comment.
It isn't necessary to not update serial fields - although you would probably never need to. I removed this if statement. This exposed a bug in the date format code which I fixed.
Comment #21
jbrown commentedSeems a lot of core passes values for serial fields expecting them to be ignored...
Comment #22
lyricnz commentedOkay, last patch looks fine - though I don't really agree with the parameter name change.
Comment #23
jbrown commented$primary_keys is plain wrong. drupal_write_record() supports updating based on key(s) that are not primary.
I think $update_keys makes sense because if you want to perform an update, then you need to specify the key(s) of the record you want to update.
Comment #24
lyricnz commentedThe whole method is kindof ugly, eh? Missing/extra parameter that totally changes what it does? It should be
drupal_create_record()
drupal_update_record()
Or some other semantic equivalent (thinking CRUD above).
Comment #25
jbrown commentedYes - I think you're right, but I don't think it is feasible to make such an API change this late in the D7 cycle.
Comment #26
neilnz commentedIf you ask me, the example of using drupal_write_record() with 'created' as a key is just plain misuse, as the 'created' field is not a key at all.
drupal_write_record should only ever insert/update one row (hence being write_record and not write_records), although this isn't enforced with a LIMIT 1 or anything. Use db_update() for the more generic case.
Beyond that, I agree it's meaningless to update a field to the value it already has.
Also, it should probably be a bit more forgiving in its parameters, and cast a FALSE or NULL to be equivalent to an empty array, as I'm sure some contrib would think passing NULL as the update key should prevent the update behaviour.
Just my 2c.
Comment #27
jbrown commented#26
@lyricnz stated that his example was 'silly'.
No one has suggested using drupal_write_record() to insert/update more that one row per invocation. I think drupal_write_record() should only be able to use to the primary key(s) as defined by the schema, but this is a D8 discussion.
This issue is really about making very minor changes to this function.
I implemented your recommendation about handling other empty values for $update_keys .
Comment #28
lyricnz commented@jbrown #25: agree, too late for D7. I know this function is on Crell's hit-list also :)
@neilnz #26: yes, my example was contrived - to use a table that everyone would understand
@jbrown #27: I think that silently "handling" bad input is a bad idea - this function takes either a string (field name) or an array (of field names). Nothing else. ... [Edit: or maybe NULL, but document it. Definitely not other bad input]
Comment #29
jbrown commented@lyricnz Are you happy with the patch in #21?
Comment #30
lyricnz commented@jbrown Yes. This is a very long issue, for a very minor fix :)
Comment #31
jbrown commentedCan you set it RTBC?
Comment #32
heine commentedThat drupal_write_record doesn't ENFORCE the use of actual primary keys doesn't mean the API doc should be disregarded. What's the issue here again?
Comment #33
lyricnz commentedHeine: the original issue was simply that drupal_write_record(), when operating in it's "update" mode, also updated the values of the columns name in the select part.
i.e.: UPDATE {node} SET title = '%s', created = %d WHERE created = %d
Somehow, during this thread, it was decided that the name of the third parameter to drupal_write_record() should be changed too. Personally, I'd rather see that change raised as a separate issue (so we can get the original bugfix in, which is not an API change, and the parameter name change can be debated on it's own merits).
Comment #34
jbrown commentedIn system_date_format_save() non-primary keys are passed to $primary_keys . This should either be fixed or $primary_keys should become $update_keys.
I would prefer it if system_date_format_save() was fixed.
I can make this a separate issue if required.
Comment #35
lyricnz commentedEdit: cross-post.
Here is the patch from #21 *without* the parameter name change. @jbrown, can you raise another issue please? Thanks for your patience.
Comment #36
lyricnz commentedRepatch - missed one "update" -> "primary" (make sure you catch it in your ticket :)
Comment #37
jbrown commentedNo you didn't - these two patches are the same.
Comment #38
jbrown commentedCreated another issue: #828990: system_date_format_save() passes non-primary keys to drupal_write_record().
Comment #39
lyricnz commentedThe patches are not the same.
-+ // Skip field if it is an update key as it is unnecessary to update a
++ // Skip field if it is a primary key as it is unnecessary to update a
Thanks for the issue-split. Will you RTBC this one, once it passes automated testing?
Edit: ha, you already did :)
Comment #40
jbrown commentedFixed the comment - we are not testing if the field is primary, but rather if it is in $primary_keys.
Comment #41
jbrown commentedComment #42
lyricnz commentedRTBC on #40 from me.
Comment #43
jbrown commentedGreat!
@lyricnz - I only just realized that you are Simon Roberts! We have met several times.
Comment #44
jbrown commented#40: drupal_write_record-update.patch queued for re-testing.
Comment #45
jbrown commented#40: drupal_write_record-update.patch queued for re-testing.
Comment #46
lyricnz commentedThe patch still looks good (and obvious) to me - I hope a D7 maintainer applies it sometime.... :)
Comment #47
jbrown commentedTagging.
Comment #48
dries commentedCommitted.
Comment #49
lyricnz commentedYay. Thanks, Dries.