// store.cpp -- // $Id$ // This is part of Metakit, the homepage is http://www.equi4.com/metakit/ /** @file * Storage management and several other loose ends */ #include "header.h" #include "handler.h" // 19990906 #include "store.h" #include "field.h" #include "persist.h" #include "format.h" // 19990906 #include "mk4io.h" // 19991104 #if !q4_INLINE #include "store.inl" #endif ///////////////////////////////////////////////////////////////////////////// c4_Dependencies::c4_Dependencies () { _refs.SetSize(0, 3); // a little optimization } c4_Dependencies::~c4_Dependencies () { } void c4_Dependencies::Add(c4_Sequence* seq_) { for (int i = 0; i < _refs.GetSize(); ++i) d4_assert(_refs.GetAt(i) != seq_); _refs.Add(seq_); } bool c4_Dependencies::Remove(c4_Sequence* seq_) { int n = _refs.GetSize() - 1; d4_assert(n >= 0); for (int i = 0; i <= n; ++i) if (_refs.GetAt(i) == seq_) { _refs.SetAt(i, _refs.GetAt(n)); _refs.SetSize(n); return n > 0; } d4_assert(0); // dependency not found return true; } ///////////////////////////////////////////////////////////////////////////// c4_Notifier::~c4_Notifier () { if (_type > kNone && _origin->GetDependencies()) { c4_PtrArray& refs = _origin->GetDependencies()->_refs; for (int i = 0; i < refs.GetSize(); ++i) { c4_Sequence* seq = (c4_Sequence*) refs.GetAt(i); d4_assert(seq != 0); seq->PostChange(*this); if (_chain && _chain->_origin == seq) { c4_Notifier* next = _chain->_next; _chain->_next = 0; delete _chain; _chain = next; } } } d4_assert(!_chain); d4_assert(!_next); } void c4_Notifier::StartSetAt(int index_, c4_Cursor& cursor_) { _type = kSetAt; _index = index_; _cursor = &cursor_; Notify(); } void c4_Notifier::StartInsertAt(int i_, c4_Cursor& cursor_, int n_) { _type = kInsertAt; _index = i_; _cursor = &cursor_; _count = n_; Notify(); } void c4_Notifier::StartRemoveAt(int index_, int count_) { _type = kRemoveAt; _index = index_; _count = count_; Notify(); } void c4_Notifier::StartMove(int from_, int to_) { _type = kMove; _index = from_; _count = to_; Notify(); } void c4_Notifier::StartSet(int i_, int propId_, const c4_Bytes& buf_) { _type = kSet; _index = i_; _propId = propId_; _bytes = &buf_; Notify(); } void c4_Notifier::Notify() { d4_assert(_origin->GetDependencies() != 0); c4_PtrArray& refs = _origin->GetDependencies()->_refs; int n = refs.GetSize(); d4_assert(n > 0); c4_Notifier** rover = &_chain; for (int i = 0; i < n; ++i) { c4_Sequence* seq = (c4_Sequence*) refs.GetAt(i); d4_assert(seq != 0); c4_Notifier* ptr = seq->PreChange(*this); if (ptr) { d4_assert(ptr->_origin == seq); d4_assert(!*rover); *rover = ptr; rover = &ptr->_next; } } } ///////////////////////////////////////////////////////////////////////////// /** @class c4_Storage * * Manager for persistent storage of view structures. * * The storage class uses a view, with additional functionality to be able * to store and reload the data it contains (including nested subviews). * * By default, data is loaded on demand, i.e. whenever data which has * not yet been referenced is used for the first time. Loading is limited * to the lifetime of this storage object, since the storage object carries * the file descriptor with it that is needed to access the data file. * * To save changes, call the Commit member. This is the only time * that data is written to file - when using a read-only file simply avoid * calling Commit. * * The LoadFromStream and SaveToStream members can be used to * serialize the contents of this storage row using only sequential I/O * (no seeking, only read or write calls). * * The data storage mechanism implementation provides fail-safe operation: * if anything prevents Commit from completing its task, the last * succesfully committed version of the saved data will be recovered on * the next open. This also includes changes made to the table structure. * * The following code creates a view with 1 row and stores it on file: * @code * c4_StringProp pName ("Name"); * c4_IntProp pAge ("Age"); * * c4_Storage storage ("myfile.dat", true); * c4_View myView = storage.GetAs("Musicians[Name:S,Age:I]"); * * myView.Add(pName ["John Williams"] + pAge [43]); * * storage.Commit(); * @endcode */ c4_Storage::c4_Storage () { // changed to r/o, now that commits don't crash on it anymore Initialize(*d4_new c4_Strategy, true, 0); } c4_Storage::c4_Storage (c4_Strategy& strategy_, bool owned_, int mode_) { Initialize(strategy_, owned_, mode_); Persist()->LoadAll(); } c4_Storage::c4_Storage (const char* fname_, int mode_) { c4_FileStrategy* strat = d4_new c4_FileStrategy; strat->DataOpen(fname_, mode_); Initialize(*strat, true, mode_); if (strat->IsValid()) Persist()->LoadAll(); } c4_Storage::c4_Storage (const c4_View& root_) { if (root_.Persist() != 0) // only restore if view was indeed persistent *(c4_View*) this = root_; else // if this was not possible, start with a fresh empty storage Initialize(*d4_new c4_Strategy, true, 0); } c4_Storage::~c4_Storage () { // cannot unmap here, because there may still be an autocommit pending //((c4_HandlerSeq*) _seq)->UnmapAll(); } void c4_Storage::Initialize(c4_Strategy& strategy_, bool owned_, int mode_) { c4_Persist* pers = d4_new c4_Persist (strategy_, owned_, mode_); c4_HandlerSeq* seq = d4_new c4_HandlerSeq (pers); seq->DefineRoot(); *(c4_View*) this = seq; pers->SetRoot(seq); } /// Get or set a named view in this storage object c4_ViewRef c4_Storage::View(const char* name_) { /* The easy solution would seem to be: c4_ViewProp prop (name_); return prop (Contents()); But this does not work, because the return value would point to an object allocated on the stack. Instead, make sure the view *has* such a property, and use the one inside the c4_Handler for it (since this will stay around). */ // int n = _root->PropIndex(c4_ViewProp (name_)); c4_ViewProp prop (name_); int n = AddProperty(prop); d4_assert(n >= 0); // the following is an expression of the form "property (rowref)" return NthProperty(n) (GetAt(0)); } /// Get a named view, redefining it to match the given structure c4_View c4_Storage::GetAs(const char* description_) { d4_assert(description_ != 0); // Dec 2001: now that GetAs is being used so much more frequently, // add a quick check to see whether restructuring is needed at all const char* q = strchr(description_, '['); if (q != 0) { c4_String vname (description_, q - description_); const char* d = Description(vname); if (d != 0) { c4_String desc (d); if (("[" + desc + "]").CompareNoCase(q) == 0) return View(vname); } } c4_Field* field = d4_new c4_Field (description_); d4_assert(field != 0); d4_assert(!*description_); c4_String name = field->Name(); d4_assert(!name.IsEmpty()); c4_Field& curr = Persist()->Root().Definition(); c4_String newField = "," + field->Description(); bool keep = newField.Find('[') >= 0; c4_String newDef; // go through all subfields for (int i = 0; i < curr.NumSubFields(); ++i) { c4_Field& of = curr.SubField(i); if (of.Name().CompareNoCase(name) == 0) { if (field->IsRepeating()) newDef += newField; // else new is not a repeating entry, so drop this entire field newField.Empty(); // don't append it later on continue; } newDef += "," + of.Description(); // keep original field } if (keep) // added 19990824 ignore if deletion newDef += newField; // appends new definition if not found earlier delete field; const char* p = newDef; SetStructure(*p ? ++p : p); // skip the leading comma if (!keep) // 19990916: avoid adding an empty view again return c4_View (); return View(name); } /// Define the complete view structure of the storage void c4_Storage::SetStructure(const char* description_) { d4_assert(description_ != 0); if (description_ != Description()) { c4_String s = "[" + c4_String (description_) + "]"; description_ = s; c4_Field* field = d4_new c4_Field (description_); d4_assert(!*description_); d4_assert(field != 0); Persist()->Root().Restructure(*field, false); } } /// Return the strategy object associated with this storage c4_Strategy& c4_Storage::Strategy() const { return Persist()->Strategy(); } /// Return a description of the view structure (default is all) const char* c4_Storage::Description(const char* name_) { if (name_ == 0 || *name_ == 0) return c4_View::Description(); c4_View v = View(name_); return v.Description(); } /// Define the storage to use for differential commits bool c4_Storage::SetAside(c4_Storage& aside_) { c4_Persist* pers = Persist(); bool f = pers->SetAside(aside_); // adjust our copy when the root view has been replaced *(c4_View*) this = &pers->Root(); return f; } /// Return storage used for differential commits, or null c4_Storage* c4_Storage::GetAside() const { return Persist()->GetAside(); } /// Flush pending changes to file right now bool c4_Storage::Commit(bool full_) { return Strategy().IsValid() && Persist()->Commit(full_); } /** (Re)initialize for on-demand loading * * Calling Rollback will cancel all uncommitted changes. */ bool c4_Storage::Rollback(bool full_) { c4_Persist* pers = Persist(); bool f = Strategy().IsValid() && pers->Rollback(full_); // adjust our copy when the root view has been replaced *(c4_View*) this = &pers->Root(); return f; } /// Set storage up to always call Commit in the destructor bool c4_Storage::AutoCommit(bool flag_) { return Persist()->AutoCommit(flag_); } /// Load contents from the specified input stream bool c4_Storage::LoadFrom(c4_Stream& stream_) { c4_HandlerSeq* newRoot = c4_Persist::Load(&stream_); if (newRoot == 0) return false; // fix commit-after-load bug, by using a full view copy // this is inefficient, but avoids mapping/strategy problems c4_View temp (newRoot); SetSize(0); SetStructure(temp.Description()); InsertAt(0, temp); return true; } /// Save contents to the specified output stream void c4_Storage::SaveTo(c4_Stream& stream_) { c4_Persist::Save(&stream_, Persist()->Root()); } ///////////////////////////////////////////////////////////////////////////// c4_DerivedSeq::c4_DerivedSeq (c4_Sequence& seq_) : _seq (seq_) { _seq.Attach(this); } c4_DerivedSeq::~c4_DerivedSeq () { _seq.Detach(this); } int c4_DerivedSeq::RemapIndex(int index_, const c4_Sequence* seq_) const { return seq_ == this ? index_ : _seq.RemapIndex(index_, seq_); } int c4_DerivedSeq::NumRows() const { return _seq.NumRows(); } int c4_DerivedSeq::NumHandlers() const { return _seq.NumHandlers(); } c4_Handler& c4_DerivedSeq::NthHandler(int colNum_) const { return _seq.NthHandler(colNum_); } const c4_Sequence* c4_DerivedSeq::HandlerContext(int colNum_) const { return _seq.HandlerContext(colNum_); } int c4_DerivedSeq::AddHandler(c4_Handler* handler_) { return _seq.AddHandler(handler_); } c4_Handler* c4_DerivedSeq::CreateHandler(const c4_Property& prop_) { return _seq.CreateHandler(prop_); } void c4_DerivedSeq::SetNumRows(int size_) { _seq.SetNumRows(size_); } c4_Notifier* c4_DerivedSeq::PreChange(c4_Notifier& nf_) { if (!GetDependencies()) return 0; c4_Notifier* chg = d4_new c4_Notifier (this); switch (nf_._type) { case c4_Notifier::kSetAt: chg->StartSetAt(nf_._index, *nf_._cursor); break; case c4_Notifier::kSet: chg->StartSet(nf_._index, nf_._propId, *nf_._bytes); break; case c4_Notifier::kInsertAt: chg->StartInsertAt(nf_._index, *nf_._cursor, nf_._count); break; case c4_Notifier::kRemoveAt: chg->StartRemoveAt(nf_._index, nf_._count); break; case c4_Notifier::kMove: chg->StartMove(nf_._index, nf_._count); break; } return chg; } ///////////////////////////////////////////////////////////////////////////// c4_StreamStrategy::c4_StreamStrategy (t4_i32 buflen_) : _stream (0), _buffer (d4_new t4_byte [buflen_]), _buflen (buflen_), _position (0) { _mapStart = _buffer; _dataSize = buflen_; } c4_StreamStrategy::c4_StreamStrategy (c4_Stream* stream_) : _stream (stream_), _buffer (0), _buflen (0), _position (0) { } c4_StreamStrategy::~c4_StreamStrategy () { _mapStart = 0; _dataSize = 0; if (_buffer != 0) delete [] _buffer; } bool c4_StreamStrategy::IsValid() const { return true; } int c4_StreamStrategy::DataRead(t4_i32 pos_, void* buffer_, int length_) { if (_buffer != 0) { d4_assert(pos_ <= _buflen); _position = pos_ + _baseOffset; if (length_ > _buflen - _position) length_ = _buflen - _position; if (length_ > 0) memcpy(buffer_, _buffer + _position, length_); } else { d4_assert(_position == pos_ + _baseOffset); length_ = _stream != 0 ? _stream->Read(buffer_, length_) : 0; } _position += length_; return length_; } void c4_StreamStrategy::DataWrite(t4_i32 pos_, const void* buffer_, int length_) { if (_buffer != 0) { d4_assert(pos_ <= _buflen); _position = pos_ + _baseOffset; int n = length_; if (n > _buflen - _position) n = _buflen - _position; if (n > 0) memcpy(_buffer + _position, buffer_, n); } else { d4_assert(_position == pos_ + _baseOffset); if (_stream != 0 && !_stream->Write(buffer_, length_)) ++_failure; } _position += length_; } t4_i32 c4_StreamStrategy::FileSize() { return _position; } /////////////////////////////////////////////////////////////////////////////