There are other issues about this, but I don't know if they describe the same or a separate problem.
But I think I can provide a clear description what is happening and why, so I will.
See also #1233426: Dirty and queued items don't get removed from the tracking table when deleted
Symptom
I have some code that changes node bundles.
Simplified:
$node = node_load(123);
assert($node->type === 'some_type');
$node->type = 'other_type';
node_save($node);
This triggers search_api_entity_update(), which calls _search_api_entity_datasource_bundle_change(), which calls either $controller->trackItemDelete() or $controller->trackItemInsert().
The trackItemInsert() calls db_insert(), which causes a PDOException if the key already exists, e.g.
Duplicate entry '105631-32' for key 'PRIMARY'
in
INSERT INTO {search_api_item} (item_id, index_id, changed) VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2)
Stack trace:
18: DatabaseStatementBase->execute() (Array, 3 elements)
17: DatabaseConnection->query() (Array, 4 elements)
16: InsertQuery_mysql->execute() (Array, 1 element)
15: SearchApiAbstractDataSourceController->trackItemInsert() (Array, 3 elements)
14: SearchApiEntityDataSourceController->trackItemInsert() (Array, 3 elements)
13: _search_api_entity_datasource_bundle_change() (Array, 5 elements)
12: search_api_entity_update() (Array, 3 elements)
11: call_user_func_array() (Array, 2 elements)
10: module_invoke_all() (Array, 4 elements)
9: node_save() (Array, 2 elements)
[..]
Explanation
So, why would the table already contain an entry with this key?
I'd say, in general it won't.
But it could happen that
1. I try node_save($node). It fails. But it still writes to search_api_item for some reason.
2. I fix my code.
3. I run node_save($node) again. this time it works. But the entry in search_api_item might already exist from previous attempt.
Or maybe there is a problem if the index tracks both the old and new node type?
Solution
Use something with REPLACE INTO. db_merge() should do the job, right?
http://stackoverflow.com/questions/5825337/help-with-db-query-in-drupal-...
But this is one query for every item. Maybe does not scale?
I will update this issue when I find out more.
Comments
Comment #2
donquixote commentedOh I think I really have the explanation.
Create a search index 'my_index'.
Enable indexing for node type 'some_type' (but not 'other_type').
Create some nodes and index them.
-> Now the search_api_item table contains entries for 'some_type'.
And now..
Create node type 'other_type'.
Enable indexing of 'other_type', disable indexing of 'some_type'.
-> Now the search_api_item table now still contains entries for 'some_type'.
Convert nodes from 'some_type' to 'other_type'.
-> Now search_api will try to insert the same items as 'other_type', because it thinks that entries for 'some_type' do not exist.
Comment #3
donquixote commentedHere is a possible fix. Maybe I will produce a patch..
Comment #4
donquixote commented"Prevent the array key to be converted to integer."
Why am I doing this?
I think to remember some experiment where different strings would become the same array key, like this:
Now I cannot find any examples anymore to reproduce this.
EDIT
https://3v4l.org/2XYtc -> behaves as it should.
Maybe this problem only exists when converting from float.
Comment #5
drunken monkeyI'm pretty sure we forbid editing of the indexed bundles after creating the index, for exactly this reason? How are you doing that?
If you've patched your module to allow this, the patch obviously doesn't implement it properly.
If you haven't patched it and it's still possible, that's the actual bug here.
Regarding your suggestion in #3, we're doing something very similar in Drupal 8: see here.
However, if there's no valid way to trigger the problem here, I'm not sure we should sacrifice the performance and add this.
Comment #6
donquixote commentedThis is a complicated project and I don't know exactly why this happened.
My assumption is that it was coming from a feature module somewhere.
Maybe the "forbid editing" only applies to the form, but not to the feature. This would mean the feature needs a hook_update_N() that wipes the search index?
Or if we want to fix this in search_api:
One solution could be to separate the conf variable from the actual state.
- The variable tells us which bundles *should* be indexed.
- A value in cache or elsewhere, possibly even in the search index itself (?), tells us which bundles were actually indexed last time.
Or maybe this:
Catch the exception, tell the user to clear the search index. (if this helps)
Comment #7
donquixote commentedI just tried: The bundle settings can be easily changed with features, by changing feature code and doing drush fr.
Comment #8
drunken monkeySure they can – it just shouldn't be done. Or, if you do it, write an update hook to deal with it properly (as you say). It's not our responsibility in the Search API to deal with people changing settings we defined as "unchangeable".