1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//! The in-memory columnar master-store write path.
//!
//! Extracted from `write.rs` rather than left in place: `write.rs` reached its
//! 2500-line ceiling, and project doctrine is to split a file that outgrows it
//! rather than raise the allowlist. These three items are the natural seam --
//! they are the only ones that reach the per-type master `Arc<ColumnStore>`,
//! and `execute_set` / `execute_remove` must agree on all of them (a property
//! removed on a node's forked store while the master keeps the value is
//! resurrected by the next clause's handle sweep).
use crateValue;
use crateDirGraph;
use crate;
use NodeIndex;
use Arc;
/// One property write aimed at a node type's master column store.
///
/// `row_id` is `None` for a node whose properties are not `Columnar`, which is
/// one of the fallthrough conditions rather than a caller error — carrying the
/// `Option` here keeps the whole "can this go through the master?" decision in
/// one place.
pub
/// Write one property through the in-memory columnar master store, reporting
/// whether it landed there.
///
/// The fast path for `Columnar` storage: route the write through the per-type
/// master `Arc<ColumnStore>` once, instead of through each node's own handle.
/// Every node of the type points at the same allocation, so `Arc::make_mut` on
/// a *node's* handle would clone the whole store on every write — O(N²) for a
/// batch `SET`. Going through the master forks once; the per-node handles are
/// re-pointed in a single sweep at the end of the clause.
///
/// Returns `false` — leaving the caller to fall through to the per-node setter
/// — for disk-backed graphs (which have their own write path), for non-
/// `Columnar` nodes, for a `Columnar` node whose type is absent from
/// `column_stores`, and for `title`/`name`.
///
/// `title`/`name` are excluded deliberately: the fallthrough sets the inline
/// `node.title`, and `enable_columnar` detects that inline override on save
/// (it differs from the stale `__title__` column) and rebuilds, consolidating
/// the fresh title. That single save-side chokepoint covers every title-write
/// path — Cypher `SET`, `add_nodes` update/replace, connection titles —
/// without per-path master writes (petekSuite bug 2).
///
/// The property name is interned into the graph's `StringInterner` *before*
/// `column_stores` is borrowed. The per-node path gets this free via
/// `node.set_property(…, &mut graph.interner)`; the master path once used
/// `InternedKey::from_str()`, which only hashes, leaving `save()` unable to
/// resolve the key back to a string at serialize time. Symptom: every
/// Cypher-`SET` property on a 0.8.39 in-memory Sodir-scale graph survived
/// in-memory but vanished after save+load, with
/// `BUG: InternedKey N not found in StringInterner`.
///
/// Both journals are handled here, and they pull in opposite directions:
///
/// - **WAL**: the write bypasses the recorded `GraphWrite` path, so the one
/// mutated node is captured explicitly. The end-of-clause refresh sweep must
/// *not* be, or a single `SET` would log every node of the type.
/// - **Undo**: the pre-statement master is captured once per type, as
/// [`UndoEntry::ColumnarHandles`], which is what lets the refresh sweep skip
/// per-node capture entirely. The store itself is never copied — the fork
/// already left the pre-statement one pristine, so holding its handle is the
/// whole pre-image. `touched_columnar_types` keeps this to one attempt per
/// type per clause; the journal's own first-touch rule covers a later clause
/// writing the same type again.
pub
/// Re-point every node's `Arc<ColumnStore>` handle at the graph master, for
/// each type written through the master during this clause.
///
/// Each node holds its own Arc clone for efficient property reads; after a
/// clause wrote through the graph master those per-node handles are stale and
/// would surface pre-clause values. This sweep is O(N) per touched type and
/// runs once per clause regardless of row count — which is the whole reason
/// the master fast paths accumulate a type set instead of refreshing per row.
///
/// Shared by `execute_set` and `execute_remove`, which must agree: a node
/// whose property was removed on its own forked store while the master kept
/// the value has the value resurrected by the *next* clause's sweep.
pub