Repository navigation
Expand file tree
/
Copy pathcorDbEntityBulkUpdate.c
More file actions
179 lines (150 loc) · 6.61 KB
/
Copy pathcorDbEntityBulkUpdate.c
File metadata and controls
179 lines (150 loc) · 6.61 KB
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
167
168
169
170
171
172
173
174
175
176
177
178
179
//
// FILE corDbEntityBulkUpdate.c
//
// AUTHOR Ken Zangelin
//
// Copyright 2026 Seamware
// SPDX-License-Identifier: Apache-2.0
//
// corDB has no native bulk primitive — we walk the input array and
// replace each existing entity with its caller-supplied, already-merged
// final state. Per-entity outcome is written to resultsV so the service
// routine can assemble the BatchOperationResult.
//
#include <stdlib.h> // malloc, free
#include <string.h> // strcmp
#include "corLog/corLog.h" // COR_E
#include "corAlloc/corAlloc.h" // corAlloc
#include "corTree/CorNode.h" // CorNode
#include "corTree/corTreeClone.h" // corTreeClone
#include "corTree/corTreeFree.h" // corTreeFree
#include "corTree/corTreeChildReplace.h" // corTreeChildReplace
#include "corTree/corTreeLookup.h" // corTreeLookup
#include "db/DbDriver.h" // DB_OK, DB_NOT_FOUND, DB_ERR, Tenant
#include "corDB/corDbSysTimes.h" // corDbTreeIn, corDbTreeOut, corDbFullView
#include "corDB/corDbIndex.h" // corDbIndexLookup, corDbIndexReplace
#include "corDB/corDbPersist.h" // corDbPersistAppend
#include "corDB/corDbHistory.h" // corDbHistoryOn
#include "corDB/corDbHistoryWrite.h" // corDbHistoryCreated, ...Replaced, ...Merged, ...Deleted
#include "corRest/CorRestState.h" // corRest (kallocP - the history's scratch)
#include "corDB/corDbStore.h" // corDbEntities
#include "corDB/corDbEntityBulkUpdate.h" // Own interface
// -----------------------------------------------------------------------------
//
// corDbEntityBulkUpdate -
//
int corDbEntityBulkUpdate(Tenant* tenantP, CorNode* entitiesArr, int* resultsV)
{
if (corDbStoreWriteRefused(tenantP) == true) // the broker is paused: nothing is touched
return DB_ERR;
if (entitiesArr == NULL || entitiesArr->type != CorArray)
return DB_ERR;
//
// Clones BEFORE the write lock, the replaced entities freed AFTER it - only the swaps need
// the lock (see corDbEntityBulkCreate for what doing the rest under it cost). An entity
// swapped out and dropped from the index is reachable by nobody else, so it is parked in
// the same slot of cloneV (its clone went into the store) until the lock is released.
//
int count = 0;
for (CorNode* inP = entitiesArr->value.head; inP != NULL; inP = inP->next)
++count;
CorNode** cloneV = (count > 0) ? (CorNode**) malloc(count * sizeof(CorNode*)) : NULL;
CorNode** origV = (count > 0) ? (CorNode**) corAlloc(&corRest.kalloc, count * sizeof(CorNode*)) : NULL;
if ((count > 0) && (cloneV == NULL))
return DB_ERR;
int ix = 0;
for (CorNode* inP = entitiesArr->value.head; inP != NULL; inP = inP->next, ix++)
{
CorNode* idP = corTreeLookup(inP, "id");
cloneV[ix] = ((idP != NULL) && (idP->type == CorString)) ? corDbTreeIn(inP, 0) : NULL;
origV[ix] = inP; // the request's: every time in place - for history
}
bool anyOk = false;
//
// Every record's body encoded now, before the lock (corDbPersist.h, CorDbPre) - index ix is entity ix
//
COR_DB_PRE(pre);
if (corDbPersistOn())
{
for (int i = 0; i < count; i++)
corDbPersistPreAdd(&pre, cloneV[i]);
}
COR_DB_HIST_PREV(hist); // their history records too (--troe corDB)
if (corDbHistoryOn)
corDbHistoryPrepareV(&hist, origV, count, "replaced", corRest.kallocP);
{
COR_DB_WRITE(tenantP);
CorNode* entities = corDbEntities(tenantP);
CorDbStore* idxStoreP = corDbStoreOf(tenantP);
bool indexed = (idxStoreP != NULL) && (idxStoreP->idToPrevEntity != NULL);
ix = 0;
for (CorNode* inP = entitiesArr->value.head; inP != NULL; inP = inP->next, ix++)
{
CorNode* idP = corTreeLookup(inP, "id");
if (idP == NULL || idP->type != CorString)
{
resultsV[ix] = DB_ERR;
continue;
}
//
// Locate the existing entity by id - one hop via the id index. This walked the WHOLE
// store for every entity of the batch, under the write lock, the same walk that cost
// corDbEntityBulkCreate a factor of 24. The walk stays as the fallback for a store with
// no index: a NULL index must not mean "not there".
//
CorNode* existing = indexed ? corDbIndexLookup(idxStoreP, idP->value.s) : NULL;
if (indexed == false)
{
for (CorNode* eP = entities->value.head; eP != NULL; eP = eP->next)
{
CorNode* existingId = corTreeLookup(eP, "id");
if (existingId != NULL && existingId->type == CorString &&
strcmp(existingId->value.s, idP->value.s) == 0)
{
existing = eP;
break;
}
}
}
if (existing == NULL)
{
resultsV[ix] = DB_NOT_FOUND;
continue;
}
CorNode* cloneP = cloneV[ix];
if (cloneP == NULL)
{
COR_E("corDB: corTreeClone failed for entity '%s'", idP->value.s);
resultsV[ix] = DB_ERR;
continue;
}
// Replace in place so the entity keeps its store (creation-order) position
// — a GET without orderBy stays stable and matches mongoc, which preserves
// createdAt on update. corTreeChildReplace does not free the old node.
//
// Through the index, which swaps in O(1) and re-points the successor's entry, which
// named `existing` as its predecessor - see corDbIndex.c.
//
corDbIndexReplace(idxStoreP, existing, cloneP);
corDbPersistAppendPre(corDbLockedStore->persistP, CorDbLogEntityPut, &pre, ix, cloneP);
if (ix < hist.n)
corDbHistoryReplacedPre(corDbLockedStore, &hist.v[ix], corDbFullView(cloneP, corRest.kallocP), corDbFullView(existing, corRest.kallocP), corRest.kallocP);
else
corDbHistoryReplaced(corDbLockedStore, corDbFullView(cloneP, corRest.kallocP), corDbFullView(existing, corRest.kallocP), corRest.kallocP);
cloneV[ix] = existing; // stored clone out, replaced entity in - freed below, unlocked
resultsV[ix] = DB_OK;
anyOk = true;
}
}
//
// Every slot now holds something the store does not: an entity that was replaced, or a clone
// that was not used (not found, no id)
//
for (ix = 0; ix < count; ix++)
{
if (cloneV[ix] != NULL)
corTreeFree(cloneV[ix]);
}
free(cloneV);
return anyOk ? DB_OK : DB_ERR;
}