Repository navigation
Rewrite upsertGraph() and insertGraph() (#1268, #1378, #1694, #853) - #350
Merged
Merged
Conversation
lehni
force-pushed
the
refactor/graph-upsert
branch
from
October 12, 2026 20:07
72299ef to
fe9900e
Compare
lehni
force-pushed
the
refactor/graph-upsert
branch
3 times, most recently
from
October 12, 2026 21:27
ebabd4f to
0d9884b
Compare
lehni
force-pushed
the
refactor/graph-upsert
branch
4 times, most recently
from
October 12, 2026 22:18
d0c1d1b to
fe57f5d
Compare
lehni
force-pushed
the
refactor/graph-upsert
branch
from
October 12, 2026 22:30
fe57f5d to
d717b1d
Compare
lehni
marked this pull request as ready for review
October 12, 2026 22:35
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.
What
A rewrite of the graph code: 22 files and 3,359 lines become 6 files and 2,390 lines, 28% less, with the same behaviour apart from the changes listed below.
GraphNode: the graph model. The markers (#id,#ref,#dbRef,#unrelate,#delete) are read once when the graph is built, and#ref/#ref{}are linked there.GraphLoader: fetches the current graph in rounds, and pairs each row with its model.insertMissing. A#refto an existing model loads that model's row.upsertGraph()calls after all other writes.GraphPlan: the rules.shouldX()/shouldXIgnoreDisable()pairs.GraphWriter: the writes, in the order of upsertGraph() deletes rows that the same graph relates to another owner #214.beforeInserthooks, which may still change the columns, are inserted one by one.UPDATE … CASEper table. Models with hooks are still patched one by one.withGraphFetched().NotFoundErroronly if the row is gone. Rows that a trigger skips, or that MySQL doesn't count as changed, aren't errors.Relation#keySide('owner','related'or'join') tells the graph where a relation keeps its keys, instead of the graph checking relation classes. Owners of relations that aren't one-to-one hold arrays of keys, which is howBelongsToManyRelationfrom Add BelongsToManyRelation for owners that hold an array of keys (#2667) #319 takes part.GraphOptionsonly parses and matches the options. Options with values of the wrong type always throw.GraphInsertgo throughinsertGraph()andupsertGraph().Fixes
#ref{}in a foreign key that the relation sets too no longer fails withreplace is not a function.codecolumn that the relation joins through, instead of being inserted with NULL.#refwith relations, related through aManyToManyRelationto a model that the graph patches elsewhere, no longer fails with aNotFoundError.insertGraph()andupsertGraph()validate their models once the references and keys are resolved, so write-only input like passwords belongs in$beforeInsert/$beforeUpdatehooks, not in setters.Speed
Median ms, with queries in parentheses. The no-op row is about CPU.
Breaking
These are in the changelog and the migration guide.
NotFoundErrorbefore anything is written.itemsat once.#id,#ref,#dbRef,#unrelateor#delete.Relationdirectly need akeySidegetter to be used in graphs.Reasoning
CASEpatches and the batched removals only apply where nothing observes single rows: no update hooks, noreturning('*'), plain values, no join table extras. Everything else is written one row at a time, as before.Relation#keySidedescribes where the keys are stored, not which class a relation is. That keeps the graph code free of relation types, and it would let other relation classes take part.