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