You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
588 lines
14 KiB
588 lines
14 KiB
// 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;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|