Write NULL, not 0, into location's foreign-key column - #37
Merged
Conversation
The core half of this is FOGProject/fogproject#1655: "Clear on all" on Image failed because the mass edit cleared `hosts`.`hostImage` to 0 and a foreign key refuses a 0 that names no row. Asked to sweep for the same shape here, and it found one. LocationDeleteMassItems::deletemassitems() cleaned up after a deleted storage node with ->update(['storagenodeID' => $arguments['itemIDs']], '', 0) where the third argument is the UPDATE DATA. FOGManagerController::update() takes an associative array of columns, or an array of them, and returns false for anything else -- so the call had ALWAYS been a silent no-op, and had it ever worked, 0 is precisely the value the constraint now refuses. `location`.`lStorageNodeID` is nullable with ON DELETE SET NULL, so it writes null now. Kept rather than left to the constraint alone: a server between deploying code and running the schema updater has the column and not the foreign key, and there this is the only thing doing the work. The storagegroup arm above it was the same no-op and is removed, because there is nothing it could ever have done. `lStorageGroupID` is NOT NULL with a RESTRICT foreign key -- confirmed on a live install -- so a storage group any location still points at cannot be deleted at all, and the database refusing it is the intended answer. A location without a storage group is not a valid location. The line read as cleanup that made the delete safe; it never ran. tests/fk-writes-are-null-not-zero.test.php gates all 23 plugin-owned columns that core's schema-constraints.php constrains, not just the three `config` ones where a sentinel was ever plausible -- enumerating only the risky half is how the next one gets missed. It also refuses update($find, $op, <scalar>) anywhere in the tree, which is the shape that made this a no-op and which no caller can have meant. Comments are stripped with the tokenizer first, so documenting a defect does not trip the check that catches it. The field-name mapping is asserted, not trusted: renaming a field behind a constrained column fails here rather than quietly making every grep match nothing. Four mutations were run. The first version of the scalar-update check did NOT catch the real bug -- `\[[^\]]*\]` cannot span the inner `]` in `$arguments['itemIDs']`, so it skipped the exact line it existed for and passed. Bounded on `;` instead, it goes red. Also checked and clean: capone already migrated cImageID/cOSID off the 0 sentinel and guards with isValid() before writing; tasktypeedit and taskstateedit own no tables and delete through core's shared path, which already explains a refusal via ConstraintViolation::explain(). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0166dqQEjAs9fhqUw5zCjvxM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The plugin half of FOGProject/fogproject#1655. Core's bug was that "Clear on all" on Image cleared
hosts.hostImageto0, and a foreign key refuses a 0 that names no row. Asked to sweep for the same shape across the plugins — it found one.The bug
LocationDeleteMassItems::deletemassitems()cleaned up after a deleted storage node withThe third argument is the update data.
FOGManagerController::update()takes an associative array of columns (or an array of them) and returnsfalsefor anything else — so the call had always been a silent no-op, and had it ever worked,0is precisely the value the constraint now refuses.location.lStorageNodeIDis nullable withON DELETE SET NULL, so it writesnullnow. Kept rather than left to the constraint alone: a server between deploying code and running the schema updater has the column and not the foreign key, and there this is the only thing doing the work.The storagegroup arm is removed
Same no-op, but there is nothing it could ever have done. Confirmed on a live install:
lStorageGroupIDis NOT NULL with RESTRICT, so a storage group any location still points at cannot be deleted at all — the database refusing it is the intended answer, and a location without a storage group is not a valid location. The line read as cleanup that made the delete safe; it never ran.The gate
tests/fk-writes-are-null-not-zero.test.phpcovers all 23 plugin-owned columns that core'sschema-constraints.phpconstrains — not just the threeconfigones where a sentinel was ever plausible. A junction column cannot hold a sentinel by construction, but a 0 in one is an orphan refused by the same constraint, and enumerating only the risky half is how the next one gets missed. (ldapUserGrant.lugTargetIDandoidcUserGrant.ougTargetIDare excluded: both arepoly, so no constraint is expressible.)It also refuses
update($find, $op, <scalar>)anywhere in the tree — the shape that made this a no-op, and which no caller can have meant.Two details that matter:
Mutations
update(..., '', 0)->set('locationID', 0)on a junction columnstoragenodeIDinLocation.phpThe first version of the scalar-update check did not catch the real bug:
\[[^\]]*\]cannot span the inner]in$arguments['itemIDs'], so it skipped the exact line it existed for and passed green. Bounded on;instead, it goes red. Worth stating plainly — that gate was fake until it was mutated.Also checked, and clean
cImageID/cOSIDoff the 0 sentinel (CaponeManager.php:154, "stop spelling no reference as 0") and guards withisValid()before writing.taskTypes/taskStates, which are parents only, so there is no FK column to write. Every reference to them is RESTRICT, and both delete through core's shareddeleteModal/general-deletepath, which already turns a refusal into a readable sentence viaConstraintViolation::explain().0or''to any of the 23 constrained columns.sh tests/run-all.sh17/17.🤖 Generated with Claude Code
https://claude.ai/code/session_0166dqQEjAs9fhqUw5zCjvxM