1 // store.cpp --
2 // This is part of Metakit, the homepage is http://www.equi4.com/metakit.html
3
4 /** @file
5 * Storage management and several other loose ends
6 */
7
8 #include "header.h"
9 #include "handler.h" // 19990906
10 #include "store.h"
11 #include "field.h"
12 #include "persist.h"
13 #include "format.h" // 19990906
14
15 #include "mk4io.h" // 19991104
16
17 #if !q4_INLINE
18 #endif
19
20 /////////////////////////////////////////////////////////////////////////////
21
c4_Dependencies()22 c4_Dependencies::c4_Dependencies()
23 {
24 _refs.SetSize(0, 3); // a little optimization
25 }
26
~c4_Dependencies()27 c4_Dependencies::~c4_Dependencies()
28 {
29 }
30
Add(c4_Sequence * seq_)31 void c4_Dependencies::Add(c4_Sequence *seq_)
32 {
33 for (int i = 0; i < _refs.GetSize(); ++i) {
34 d4_assert(_refs.GetAt(i) != seq_);
35 }
36
37 _refs.Add(seq_);
38 }
39
Remove(c4_Sequence * seq_)40 bool c4_Dependencies::Remove(c4_Sequence *seq_)
41 {
42 int n = _refs.GetSize() - 1;
43 d4_assert(n >= 0);
44
45 for (int i = 0; i <= n; ++i) {
46 if (_refs.GetAt(i) == seq_) {
47 _refs.SetAt(i, _refs.GetAt(n));
48 _refs.SetSize(n);
49 return n > 0;
50 }
51 }
52
53 d4_assert(0); // dependency not found
54 return true;
55 }
56
57 /////////////////////////////////////////////////////////////////////////////
58
~c4_Notifier()59 c4_Notifier::~c4_Notifier()
60 {
61 if (_type > kNone && _origin->GetDependencies()) {
62 c4_PtrArray &refs = _origin->GetDependencies()->_refs;
63
64 for (int i = 0; i < refs.GetSize(); ++i) {
65 c4_Sequence *seq = (c4_Sequence *)refs.GetAt(i);
66 d4_assert(seq != 0);
67
68 seq->PostChange(*this);
69
70 if (_chain && _chain->_origin == seq) {
71 c4_Notifier *next = _chain->_next;
72 _chain->_next = nullptr;
73
74 delete _chain;
75
76 _chain = next;
77 }
78 }
79 }
80
81 d4_assert(!_chain);
82 d4_assert(!_next);
83 }
84
StartSetAt(int index_,c4_Cursor & cursor_)85 void c4_Notifier::StartSetAt(int index_, c4_Cursor &cursor_)
86 {
87 _type = kSetAt;
88 _index = index_;
89 _cursor = &cursor_;
90
91 Notify();
92 }
93
StartInsertAt(int i_,c4_Cursor & cursor_,int n_)94 void c4_Notifier::StartInsertAt(int i_, c4_Cursor &cursor_, int n_)
95 {
96 _type = kInsertAt;
97 _index = i_;
98 _cursor = &cursor_;
99 _count = n_;
100
101 Notify();
102 }
103
StartRemoveAt(int index_,int count_)104 void c4_Notifier::StartRemoveAt(int index_, int count_)
105 {
106 _type = kRemoveAt;
107 _index = index_;
108 _count = count_;
109
110 Notify();
111 }
112
StartMove(int from_,int to_)113 void c4_Notifier::StartMove(int from_, int to_)
114 {
115 _type = kMove;
116 _index = from_;
117 _count = to_;
118
119 Notify();
120 }
121
StartSet(int i_,int propId_,const c4_Bytes & buf_)122 void c4_Notifier::StartSet(int i_, int propId_, const c4_Bytes &buf_)
123 {
124 _type = kSet;
125 _index = i_;
126 _propId = propId_;
127 _bytes = &buf_;
128
129 Notify();
130 }
131
Notify()132 void c4_Notifier::Notify()
133 {
134 d4_assert(_origin->GetDependencies() != 0);
135 c4_PtrArray &refs = _origin->GetDependencies()->_refs;
136
137 int n = refs.GetSize();
138 d4_assert(n > 0);
139
140 c4_Notifier **rover = &_chain;
141
142 for (int i = 0; i < n; ++i) {
143 c4_Sequence *seq = (c4_Sequence *)refs.GetAt(i);
144 d4_assert(seq != 0);
145
146 c4_Notifier *ptr = seq->PreChange(*this);
147 if (ptr) {
148 d4_assert(ptr->_origin == seq);
149
150 d4_assert(!*rover);
151 *rover = ptr;
152 rover = &ptr->_next;
153 }
154 }
155 }
156
157 /////////////////////////////////////////////////////////////////////////////
158
159 /** @class c4_Storage
160 *
161 * Manager for persistent storage of view structures.
162 *
163 * The storage class uses a view, with additional functionality to be able
164 * to store and reload the data it contains (including nested subviews).
165 *
166 * By default, data is loaded on demand, i.e. whenever data which has
167 * not yet been referenced is used for the first time. Loading is limited
168 * to the lifetime of this storage object, since the storage object carries
169 * the file descriptor with it that is needed to access the data file.
170 *
171 * To save changes, call the Commit member. This is the only time
172 * that data is written to file - when using a read-only file simply avoid
173 * calling Commit.
174 *
175 * The LoadFromStream and SaveToStream members can be used to
176 * serialize the contents of this storage row using only sequential I/O
177 * (no seeking, only read or write calls).
178 *
179 * The data storage mechanism implementation provides fail-safe operation:
180 * if anything prevents Commit from completing its task, the last
181 * successfully committed version of the saved data will be recovered on
182 * the next open. This also includes changes made to the table structure.
183 *
184 * The following code creates a view with 1 row and stores it on file:
185 * @code
186 * c4_StringProp pName ("Name");
187 * c4_IntProp pAge ("Age");
188 *
189 * c4_Storage storage ("myfile.dat", true);
190 * c4_View myView = storage.GetAs("Musicians[Name:S,Age:I]");
191 *
192 * myView.Add(pName ["John Williams"] + pAge [43]);
193 *
194 * storage.Commit();
195 * @endcode
196 */
197
c4_Storage()198 c4_Storage::c4_Storage()
199 {
200 // changed to r/o, now that commits don't crash on it anymore
201 Initialize(*d4_new c4_Strategy, true, 0);
202 }
203
c4_Storage(c4_Strategy & strategy_,bool owned_,int mode_)204 c4_Storage::c4_Storage(c4_Strategy &strategy_, bool owned_, int mode_)
205 {
206 Initialize(strategy_, owned_, mode_);
207 Persist()->LoadAll();
208 }
209
c4_Storage(const char * fname_,int mode_)210 c4_Storage::c4_Storage(const char *fname_, int mode_)
211 {
212 c4_FileStrategy *strat = d4_new c4_FileStrategy;
213 strat->DataOpen(fname_, mode_);
214
215 Initialize(*strat, true, mode_);
216 if (strat->IsValid()) {
217 Persist()->LoadAll();
218 }
219 }
220
c4_Storage(const c4_View & root_)221 c4_Storage::c4_Storage(const c4_View &root_)
222 {
223 if (root_.Persist() != nullptr) {
224 // only restore if view was indeed persistent
225 *(c4_View *)this = root_;
226 } else {
227 // if this was not possible, start with a fresh empty storage
228 Initialize(*d4_new c4_Strategy, true, 0);
229 }
230 }
231
~c4_Storage()232 c4_Storage::~c4_Storage()
233 {
234 // cannot unmap here, because there may still be an autocommit pending
235 //((c4_HandlerSeq*) _seq)->UnmapAll();
236 }
237
Initialize(c4_Strategy & strategy_,bool owned_,int mode_)238 void c4_Storage::Initialize(c4_Strategy &strategy_, bool owned_, int mode_)
239 {
240 c4_Persist *pers = d4_new c4_Persist(strategy_, owned_, mode_);
241 c4_HandlerSeq *seq = d4_new c4_HandlerSeq(pers);
242 seq->DefineRoot();
243 *(c4_View *)this = seq;
244 pers->SetRoot(seq);
245 }
246
247 /// Get or set a named view in this storage object
View(const char * name_)248 c4_ViewRef c4_Storage::View(const char *name_)
249 {
250 /*
251 The easy solution would seem to be:
252
253 c4_ViewProp prop (name_);
254 return prop (Contents());
255
256 But this does not work, because the return value would point to
257 an object allocated on the stack.
258
259 Instead, make sure the view *has* such a property, and use the
260 one inside the c4_Handler for it (since this will stay around).
261 */
262
263 // int n = _root->PropIndex(c4_ViewProp (name_));
264
265 c4_ViewProp prop(name_);
266 int n = AddProperty(prop);
267 d4_assert(n >= 0);
268
269 // the following is an expression of the form "property (rowref)"
270 return NthProperty(n)(GetAt(0));
271 }
272
273 /// Get a named view, redefining it to match the given structure
GetAs(const char * description_)274 c4_View c4_Storage::GetAs(const char *description_)
275 {
276 d4_assert(description_ != 0);
277
278 // Dec 2001: now that GetAs is being used so much more frequently,
279 // add a quick check to see whether restructuring is needed at all
280 const char *q = strchr(description_, '[');
281 if (q != nullptr) {
282 c4_String vname(description_, q - description_);
283 const char *d = Description(vname);
284 if (d != nullptr) {
285 c4_String desc(d);
286 if (("[" + desc + "]").CompareNoCase(q) == 0) {
287 return View(vname);
288 }
289 }
290 }
291
292 c4_Field *field = d4_new c4_Field(description_);
293 d4_assert(field != 0);
294
295 d4_assert(!*description_);
296
297 c4_String name = field->Name();
298 d4_assert(!name.IsEmpty());
299
300 c4_Field &curr = Persist()->Root().Definition();
301
302 c4_String newField = "," + field->Description();
303 bool keep = newField.Find('[') >= 0;
304
305 c4_String newDef;
306
307 // go through all subfields
308 for (int i = 0; i < curr.NumSubFields(); ++i) {
309 c4_Field &of = curr.SubField(i);
310 if (of.Name().CompareNoCase(name) == 0) {
311 if (field->IsRepeating()) {
312 newDef += newField;
313 }
314 // else new is not a repeating entry, so drop this entire field
315
316 newField.Empty(); // don't append it later on
317 continue;
318 }
319
320 newDef += "," + of.Description(); // keep original field
321 }
322
323 if (keep) {
324 // added 19990824 ignore if deletion
325 newDef += newField;
326 }
327 // appends new definition if not found earlier
328
329 delete field;
330
331 const char *p = newDef;
332 SetStructure(*p ? ++p : p); // skip the leading comma
333
334 if (!keep) {
335 // 19990916: avoid adding an empty view again
336 return c4_View();
337 }
338
339 return View(name);
340 }
341
342 /// Define the complete view structure of the storage
SetStructure(const char * description_)343 void c4_Storage::SetStructure(const char *description_)
344 {
345 d4_assert(description_ != 0);
346
347 if (description_ != Description()) {
348 c4_String s = "[" + c4_String(description_) + "]";
349 description_ = s;
350
351 c4_Field *field = d4_new c4_Field(description_);
352 d4_assert(!*description_);
353
354 d4_assert(field != 0);
355 Persist()->Root().Restructure(*field, false);
356 }
357 }
358
359 /// Return the strategy object associated with this storage
Strategy() const360 c4_Strategy &c4_Storage::Strategy() const
361 {
362 return Persist()->Strategy();
363 }
364
365 /// Return a description of the view structure (default is all)
Description(const char * name_)366 const char *c4_Storage::Description(const char *name_)
367 {
368 if (name_ == nullptr || *name_ == 0) {
369 return c4_View::Description();
370 }
371
372 c4_View v = View(name_);
373 return v.Description();
374 }
375
376 /// Define the storage to use for differential commits
SetAside(c4_Storage & aside_)377 bool c4_Storage::SetAside(c4_Storage &aside_)
378 {
379 c4_Persist *pers = Persist();
380 bool f = pers->SetAside(aside_);
381 // adjust our copy when the root view has been replaced
382 *(c4_View *)this = &pers->Root();
383 return f;
384 }
385
386 /// Return storage used for differential commits, or null
GetAside() const387 c4_Storage *c4_Storage::GetAside() const
388 {
389 return Persist()->GetAside();
390 }
391
392 /// Flush pending changes to file right now
Commit(bool full_)393 bool c4_Storage::Commit(bool full_)
394 {
395 return Strategy().IsValid() && Persist()->Commit(full_);
396 }
397
398 /** (Re)initialize for on-demand loading
399 *
400 * Calling Rollback will cancel all uncommitted changes.
401 */
Rollback(bool full_)402 bool c4_Storage::Rollback(bool full_)
403 {
404 c4_Persist *pers = Persist();
405 bool f = Strategy().IsValid() && pers->Rollback(full_);
406 // adjust our copy when the root view has been replaced
407 *(c4_View *)this = &pers->Root();
408 return f;
409 }
410
411 /// Set storage up to always call Commit in the destructor
AutoCommit(bool flag_)412 bool c4_Storage::AutoCommit(bool flag_)
413 {
414 return Persist()->AutoCommit(flag_);
415 }
416
417 /// Load contents from the specified input stream
LoadFrom(c4_Stream & stream_)418 bool c4_Storage::LoadFrom(c4_Stream &stream_)
419 {
420 c4_HandlerSeq *newRoot = c4_Persist::Load(&stream_);
421 if (newRoot == nullptr) {
422 return false;
423 }
424
425 // fix commit-after-load bug, by using a full view copy
426 // this is inefficient, but avoids mapping/strategy problems
427 c4_View temp(newRoot);
428
429 SetSize(0);
430 SetStructure(temp.Description());
431 InsertAt(0, temp);
432
433 return true;
434 }
435
436 /// Save contents to the specified output stream
SaveTo(c4_Stream & stream_)437 void c4_Storage::SaveTo(c4_Stream &stream_)
438 {
439 c4_Persist::Save(&stream_, Persist()->Root());
440 }
441
FreeSpace(t4_i32 * bytes_)442 t4_i32 c4_Storage::FreeSpace(t4_i32 *bytes_)
443 {
444 return Persist()->FreeBytes(bytes_);
445 }
446
447 /////////////////////////////////////////////////////////////////////////////
448
c4_DerivedSeq(c4_Sequence & seq_)449 c4_DerivedSeq::c4_DerivedSeq(c4_Sequence &seq_) : _seq(seq_)
450 {
451 _seq.Attach(this);
452 }
453
~c4_DerivedSeq()454 c4_DerivedSeq::~c4_DerivedSeq()
455 {
456 _seq.Detach(this);
457 }
458
RemapIndex(int index_,const c4_Sequence * seq_) const459 int c4_DerivedSeq::RemapIndex(int index_, const c4_Sequence *seq_) const
460 {
461 return seq_ == this ? index_ : _seq.RemapIndex(index_, seq_);
462 }
463
NumRows() const464 int c4_DerivedSeq::NumRows() const
465 {
466 return _seq.NumRows();
467 }
468
NumHandlers() const469 int c4_DerivedSeq::NumHandlers() const
470 {
471 return _seq.NumHandlers();
472 }
473
NthHandler(int colNum_) const474 c4_Handler &c4_DerivedSeq::NthHandler(int colNum_) const
475 {
476 return _seq.NthHandler(colNum_);
477 }
478
HandlerContext(int colNum_) const479 const c4_Sequence *c4_DerivedSeq::HandlerContext(int colNum_) const
480 {
481 return _seq.HandlerContext(colNum_);
482 }
483
AddHandler(c4_Handler * handler_)484 int c4_DerivedSeq::AddHandler(c4_Handler *handler_)
485 {
486 return _seq.AddHandler(handler_);
487 }
488
CreateHandler(const c4_Property & prop_)489 c4_Handler *c4_DerivedSeq::CreateHandler(const c4_Property &prop_)
490 {
491 return _seq.CreateHandler(prop_);
492 }
493
SetNumRows(int size_)494 void c4_DerivedSeq::SetNumRows(int size_)
495 {
496 _seq.SetNumRows(size_);
497 }
498
PreChange(c4_Notifier & nf_)499 c4_Notifier *c4_DerivedSeq::PreChange(c4_Notifier &nf_)
500 {
501 if (!GetDependencies()) {
502 return nullptr;
503 }
504
505 c4_Notifier *chg = d4_new c4_Notifier(this);
506
507 switch (nf_._type) {
508 case c4_Notifier::kSetAt:
509 chg->StartSetAt(nf_._index, *nf_._cursor);
510 break;
511
512 case c4_Notifier::kSet:
513 chg->StartSet(nf_._index, nf_._propId, *nf_._bytes)
514 ;
515 break;
516
517 case c4_Notifier::kInsertAt:
518 chg->StartInsertAt(nf_._index, *nf_._cursor,
519 nf_._count);
520 break;
521
522 case c4_Notifier::kRemoveAt:
523 chg->StartRemoveAt(nf_._index, nf_._count);
524 break;
525
526 case c4_Notifier::kMove:
527 chg->StartMove(nf_._index, nf_._count);
528 break;
529 }
530
531 return chg;
532 }
533
534 /////////////////////////////////////////////////////////////////////////////
535
c4_StreamStrategy(t4_i32 buflen_)536 c4_StreamStrategy::c4_StreamStrategy(t4_i32 buflen_) : _stream(nullptr)
537 , _buffer
538 (d4_new t4_byte[buflen_])
539 , _buflen(buflen_)
540 , _position(0)
541 {
542 _mapStart = _buffer;
543 _dataSize = buflen_;
544 }
545
c4_StreamStrategy(c4_Stream * stream_)546 c4_StreamStrategy::c4_StreamStrategy(c4_Stream *stream_) : _stream(stream_)
547 , _buffer(nullptr)
548 , _buflen(0)
549 , _position(0)
550 {
551 }
552
~c4_StreamStrategy()553 c4_StreamStrategy::~c4_StreamStrategy()
554 {
555 _mapStart = nullptr;
556 _dataSize = 0;
557
558 if (_buffer != nullptr) {
559 delete [] _buffer;
560 }
561 }
562
IsValid() const563 bool c4_StreamStrategy::IsValid() const
564 {
565 return true;
566 }
567
DataRead(t4_i32 pos_,void * buffer_,int length_)568 int c4_StreamStrategy::DataRead(t4_i32 pos_, void *buffer_, int length_)
569 {
570 if (_buffer != nullptr) {
571 d4_assert(pos_ <= _buflen);
572 _position = pos_ + _baseOffset;
573
574 if (length_ > _buflen - _position) {
575 length_ = _buflen - _position;
576 }
577 if (length_ > 0) {
578 memcpy(buffer_, _buffer + _position, length_);
579 }
580 } else {
581 d4_assert(_position == pos_ + _baseOffset);
582 length_ = _stream != nullptr ? _stream->Read(buffer_, length_) : 0;
583 }
584
585 _position += length_;
586 return length_;
587 }
588
DataWrite(t4_i32 pos_,const void * buffer_,int length_)589 void c4_StreamStrategy::DataWrite(t4_i32 pos_, const void *buffer_, int length_)
590 {
591 if (_buffer != nullptr) {
592 d4_assert(pos_ <= _buflen);
593 _position = pos_ + _baseOffset;
594
595 int n = length_;
596 if (n > _buflen - _position) {
597 n = _buflen - _position;
598 }
599 if (n > 0) {
600 memcpy(_buffer + _position, buffer_, n);
601 }
602 } else {
603 d4_assert(_position == pos_ + _baseOffset);
604 if (_stream != nullptr && !_stream->Write(buffer_, length_)) {
605 ++_failure;
606 }
607 }
608
609 _position += length_;
610 }
611
FileSize()612 t4_i32 c4_StreamStrategy::FileSize()
613 {
614 return _position;
615 }
616
617 /////////////////////////////////////////////////////////////////////////////
618