// derived.cpp -- // $Id$ // This is part of Metakit, see http://www.equi4.com/metakit/ /** @file * Derived views are virtual views which track changes */ #include "header.h" #include "handler.h" #include "store.h" #include "derived.h" #include // qsort ///////////////////////////////////////////////////////////////////////////// // Implemented in this file // class c4_Sequence; class c4_DerivedSeq; class c4_FilterSeq; class c4_SortSeq; class c4_ProjectSeq; ///////////////////////////////////////////////////////////////////////////// class c4_FilterSeq : public c4_DerivedSeq { protected: c4_DWordArray _rowMap; c4_DWordArray _revMap; c4_Row _lowRow; c4_Row _highRow; c4_Bytes _rowIds; protected: c4_FilterSeq (c4_Sequence& seq_); virtual ~c4_FilterSeq (); void FixupReverseMap(); int PosInMap(int index_) const; bool Match(int index_, c4_Sequence& seq_, const int* =0, const int* =0) const; bool MatchOne(int prop_, const c4_Bytes& data_) const; public: c4_FilterSeq (c4_Sequence& seq_, c4_Cursor low_, c4_Cursor high_); virtual int RemapIndex(int, const c4_Sequence*) const; virtual int NumRows() const; virtual int Compare(int, c4_Cursor) const; virtual bool Get(int, int, c4_Bytes&); virtual void InsertAt(int, c4_Cursor, int =1); virtual void RemoveAt(int, int =1); virtual void Set(int, const c4_Property&, const c4_Bytes&); virtual void SetSize(int); virtual c4_Notifier* PreChange(c4_Notifier& nf_); virtual void PostChange(c4_Notifier& nf_); }; ///////////////////////////////////////////////////////////////////////////// c4_FilterSeq::c4_FilterSeq (c4_Sequence& seq_) : c4_DerivedSeq (seq_) { _rowMap.SetSize(_seq.NumRows()); _revMap.SetSize(_seq.NumRows()); d4_assert(NumRows() == _seq.NumRows()); for (int i = 0; i < NumRows(); ++i) { _rowMap.SetAt(i, i); _revMap.SetAt(i, i); } } c4_FilterSeq::c4_FilterSeq (c4_Sequence& seq_, c4_Cursor low_, c4_Cursor high_) : c4_DerivedSeq (seq_), _lowRow (*low_), _highRow (*high_) { d4_assert((&_lowRow)._index == 0); d4_assert((&_highRow)._index == 0); // use a sneaky way to obtain the sequence pointers and indices c4_Sequence* lowSeq = (& _lowRow)._seq; c4_Sequence* highSeq = (& _highRow)._seq; d4_assert(lowSeq && highSeq); // prepare column numbers to avoid looking them up on every row // lowCols is a vector of column numbers to use for the low limits // highCols is a vector of column numbers to use for the high limits int nl = lowSeq->NumHandlers(), nh = highSeq->NumHandlers(); c4_Bytes lowVec, highVec; int* lowCols = (int*) lowVec.SetBufferClear(nl * sizeof (int)); int* highCols = (int*) highVec.SetBufferClear(nh * sizeof (int)); for (int il = 0; il < nl; ++il) lowCols[il] = seq_.PropIndex(lowSeq->NthPropId(il)); for (int ih = 0; ih < nh; ++ih) highCols[ih] = seq_.PropIndex(highSeq->NthPropId(ih)); // set _rowIds flag buffer for fast matching { int max = -1; { for (int i1 = 0; i1 < nl; ++i1) { int n = lowSeq->NthPropId(i1); if (max < n) max = n; } for (int i2 = 0; i2 < nh; ++i2) { int n = highSeq->NthPropId(i2); if (max < n) max = n; } } t4_byte* p = _rowIds.SetBufferClear(max + 1); { for (int i1 = 0; i1 < nl; ++i1) p[lowSeq->NthPropId(i1)] |= 1; for (int i2 = 0; i2 < nh; ++i2) p[highSeq->NthPropId(i2)] |= 2; } } // now go through all rows and select the ones that are in range _rowMap.SetSize(_seq.NumRows()); // avoid growing, use safe upper bound int n = 0; for (int i = 0; i < _seq.NumRows(); ++i) if (Match(i, _seq, lowCols, highCols)) _rowMap.SetAt(n++, i); _rowMap.SetSize(n); FixupReverseMap(); } c4_FilterSeq::~c4_FilterSeq () { } void c4_FilterSeq::FixupReverseMap() { int n = _seq.NumRows(); _revMap.SetSize(0); if (n > 0) { _revMap.InsertAt(0, ~ (t4_i32) 0, n); //! for (int i = 0; i < _rowMap.GetSize(); ++i) _revMap.SetAt((int) _rowMap.GetAt(i), i); } } bool c4_FilterSeq::MatchOne(int prop_, const c4_Bytes& data_) const { d4_assert(prop_ < _rowIds.Size()); t4_byte flag = _rowIds.Contents()[prop_]; d4_assert(flag); if (flag & 1) { c4_Sequence* lowSeq = (& _lowRow)._seq; c4_Handler& h = lowSeq->NthHandler(lowSeq->PropIndex(prop_)); if (h.Compare(0, data_) > 0) return false; } if (flag & 2) { c4_Sequence* highSeq = (& _highRow)._seq; c4_Handler& h = highSeq->NthHandler(highSeq->PropIndex(prop_)); if (h.Compare(0, data_) < 0) return false; } return true; } bool c4_FilterSeq::Match(int index_, c4_Sequence& seq_, const int* lowCols_, const int* highCols_) const { // use a sneaky way to obtain the sequence pointers and indices c4_Sequence* lowSeq = (& _lowRow)._seq; c4_Sequence* highSeq = (& _highRow)._seq; d4_assert(lowSeq && highSeq); int nl = lowSeq->NumHandlers(), nh = highSeq->NumHandlers(); c4_Bytes data; // check each of the lower limits for (int cl = 0; cl < nl; ++cl) { c4_Handler& hl = lowSeq->NthHandler(cl); int n = lowCols_ ? lowCols_[cl] : seq_.PropIndex(lowSeq->NthPropId(cl)); if (n >= 0) { c4_Handler& h = seq_.NthHandler(n); const c4_Sequence* hc = seq_.HandlerContext(n); int i = seq_.RemapIndex(index_, hc); h.GetBytes(i, data); } else hl.ClearBytes(data); if (hl.Compare(0, data) > 0) return false; } // check each of the upper limits for (int ch = 0; ch < nh; ++ch) { c4_Handler& hh = highSeq->NthHandler(ch); int n = highCols_ ? highCols_[ch] : seq_.PropIndex(highSeq->NthPropId(ch)); if (n >= 0) { c4_Handler& h = seq_.NthHandler(n); const c4_Sequence* hc = seq_.HandlerContext(n); int i = seq_.RemapIndex(index_, hc); h.GetBytes(i, data); } else hh.ClearBytes(data); if (hh.Compare(0, data) < 0) return false; } return true; } int c4_FilterSeq::RemapIndex(int index_, const c4_Sequence* seq_) const { return seq_ == this ? index_ : _seq.RemapIndex((int) _rowMap.GetAt(index_), seq_); } int c4_FilterSeq::NumRows() const { return _rowMap.GetSize(); } int c4_FilterSeq::Compare(int index_, c4_Cursor cursor_) const { return _seq.Compare((int) _rowMap.GetAt(index_), cursor_); } bool c4_FilterSeq::Get(int index_, int propId_, c4_Bytes& bytes_) { return _seq.Get((int) _rowMap.GetAt(index_), propId_, bytes_); } void c4_FilterSeq::InsertAt(int, c4_Cursor, int) { d4_assert(0); } void c4_FilterSeq::RemoveAt(int, int) { d4_assert(0); } void c4_FilterSeq::Set(int, const c4_Property&, const c4_Bytes&) { d4_assert(0); } void c4_FilterSeq::SetSize(int) { d4_assert(0); } int c4_FilterSeq::PosInMap(int index_) const { int i = 0; while (i < NumRows()) if ((int) _rowMap.GetAt(i) >= index_) break; else ++i; return i; } c4_Notifier* c4_FilterSeq::PreChange(c4_Notifier& nf_) { if (!GetDependencies()) return 0; c4_Notifier* chg = d4_new c4_Notifier (this); bool pass = false; switch (nf_._type) { case c4_Notifier::kSet: pass = nf_._propId >= _rowIds.Size() || _rowIds.Contents()[nf_._propId] == 0; // fall through... case c4_Notifier::kSetAt: { int r = (int) _revMap.GetAt(nf_._index); bool includeRow = r >= 0; if (!pass) if (nf_._type == c4_Notifier::kSetAt) { d4_assert(nf_._cursor != 0); includeRow = Match(nf_._cursor->_index, *nf_._cursor->_seq); } else // set just one property, and it's not in a row yet includeRow = MatchOne(nf_._propId, *nf_._bytes); if (r >= 0 && !includeRow) chg->StartRemoveAt(r, 1); else if (r < 0 && includeRow) chg->StartInsertAt(PosInMap(nf_._index), *nf_._cursor, 1); else if (includeRow) { d4_assert(r >= 0); if (nf_._type == c4_Notifier::kSetAt) chg->StartSetAt(r, *nf_._cursor); else chg->StartSet(r, nf_._propId, *nf_._bytes); } } break; case c4_Notifier::kInsertAt: { int i = PosInMap(nf_._index); d4_assert(nf_._cursor != 0); if (Match(nf_._cursor->_index, *nf_._cursor->_seq)) chg->StartInsertAt(i, *nf_._cursor, nf_._count); } break; case c4_Notifier::kRemoveAt: { int i = PosInMap(nf_._index); int j = PosInMap(nf_._index + nf_._count); d4_assert(j >= i); if (j > i) chg->StartRemoveAt(i, j - i); } break; case c4_Notifier::kMove: { int i = PosInMap(nf_._index); bool inMap = i < NumRows() && (int) _rowMap.GetAt(i) == nf_._index; if (inMap && nf_._index != nf_._count) chg->StartMove(i, PosInMap(nf_._count)); } break; } return chg; } void c4_FilterSeq::PostChange(c4_Notifier& nf_) { bool pass = false; switch (nf_._type) { case c4_Notifier::kSet: pass = nf_._propId >= _rowIds.Size() || _rowIds.Contents()[nf_._propId] == 0; // fall through... case c4_Notifier::kSetAt: { int r = (int) _revMap.GetAt(nf_._index); bool includeRow = r >= 0; if (!pass) if (nf_._type == c4_Notifier::kSetAt) { d4_assert(nf_._cursor != 0); includeRow = Match(nf_._cursor->_index, *nf_._cursor->_seq); } else // set just one property, and it's not in a row yet includeRow = MatchOne(nf_._propId, *nf_._bytes); if (r >= 0 && !includeRow) _rowMap.RemoveAt(r); else if (r < 0 && includeRow) _rowMap.InsertAt(PosInMap(nf_._index), nf_._index); else break; FixupReverseMap(); } break; case c4_Notifier::kInsertAt: { int i = PosInMap(nf_._index); if (Match(nf_._index, _seq)) { _rowMap.InsertAt(i, 0, nf_._count); for (int j = 0; j < nf_._count; ++j) _rowMap.SetAt(i++, nf_._index + j); } while (i < NumRows()) _rowMap.ElementAt(i++) += nf_._count; FixupReverseMap(); } break; case c4_Notifier::kRemoveAt: { int i = PosInMap(nf_._index); int j = PosInMap(nf_._index + nf_._count); d4_assert(j >= i); if (j > i) _rowMap.RemoveAt(i, j - i); while (i < NumRows()) _rowMap.ElementAt(i++) -= nf_._count; FixupReverseMap(); } break; case c4_Notifier::kMove: { int i = PosInMap(nf_._index); bool inMap = i < NumRows() && (int) _rowMap.GetAt(i) == nf_._index; if (inMap && nf_._index != nf_._count) { int j = PosInMap(nf_._count); _rowMap.RemoveAt(i); if (j > i) --j; _rowMap.InsertAt(j, nf_._count); FixupReverseMap(); } } break; } } ///////////////////////////////////////////////////////////////////////////// class c4_SortSeq : public c4_FilterSeq { public: typedef t4_i32 T; c4_SortSeq (c4_Sequence& seq_, c4_Sequence* down_); virtual ~c4_SortSeq (); virtual c4_Notifier* PreChange(c4_Notifier& nf_); virtual void PostChange(c4_Notifier& nf_); private: struct c4_SortInfo { c4_Handler* _handler; const c4_Sequence* _context; c4_Bytes _buffer; int CompareOne(c4_Sequence& seq_, T a, T b) { _handler->GetBytes(seq_.RemapIndex((int) b, _context), _buffer, true); return _handler->Compare(seq_.RemapIndex((int) a, _context), _buffer); } }; bool LessThan(T a, T b); bool TestSwap( T& first , T& second ); void MergeSortThis( T* ar, int size , T scratch[] ); void MergeSort( T ar[] , int size ); virtual int Compare(int, c4_Cursor) const; int PosInMap(c4_Cursor cursor_) const; c4_SortInfo* _info; c4_Bytes _down; int _width; }; ///////////////////////////////////////////////////////////////////////////// bool c4_SortSeq::LessThan(T a, T b) { if (a == b) return false; // go through each of the columns and compare values, but since // handler access is used, we must be careful to remap indices c4_SortInfo* info; for (info = _info; info->_handler; ++info) { int f = info->CompareOne(_seq, a, b); if (f) { int n = info - _info; if (_width < n) _width = n; return (_down.Contents()[n] ? -f : f) < 0; } } _width = info - _info; return a < b; } inline bool c4_SortSeq::TestSwap( T& first , T& second ) { if ( LessThan( second , first ) ) { T temp = first; first = second; second = temp; return true; } return false; } void c4_SortSeq::MergeSortThis( T* ar, int size , T scratch[] ) { switch( size ) { //Handle the special cases for speed: case 2: TestSwap( ar[ 0 ] , ar[ 1 ] ); break; case 3: TestSwap( ar[ 0 ] , ar[ 1 ] ); if ( TestSwap( ar[ 1 ] , ar[ 2 ] ) ) TestSwap( ar[ 0 ] , ar[ 1 ] ); break; case 4: //Gotta optimize this.... TestSwap( ar[ 0 ] , ar[ 1 ] ); TestSwap( ar[ 2 ] , ar[ 3 ] ); TestSwap( ar[ 0 ] , ar[ 2 ] ); TestSwap( ar[ 1 ] , ar[ 3 ] ); TestSwap( ar[ 1 ] , ar[ 2 ] ); break; //Gotta do special case for list of five. default: //Subdivide the list, recurse, and merge { int s1 = size / 2; int s2 = size - s1; T* from1_ = scratch; T* from2_ = scratch + s1; MergeSortThis( from1_ , s1 , ar ); MergeSortThis( from2_ , s2 , ar + s1 ); T* to1_ = from1_ + s1; T* to2_ = from2_ + s2; for (;;) { if ( LessThan( *from1_, *from2_) ) { *ar++ = *from1_++; if (from1_ >= to1_) { while( from2_ < to2_ ) *ar++ = *from2_++; break; } } else { *ar++ = *from2_++; if (from2_ >= to2_) { while( from1_ < to1_ ) *ar++ = *from1_++; break; } } } } } } void c4_SortSeq::MergeSort( T ar[] , int size ) { if ( size > 1 ) { T* scratch = d4_new T [size]; memcpy(scratch , ar , size * sizeof (T)); MergeSortThis(ar , size , scratch); delete [] scratch; } } c4_SortSeq::c4_SortSeq (c4_Sequence& seq_, c4_Sequence* down_) : c4_FilterSeq (seq_), _info (0), _width (-1) { d4_assert(NumRows() == seq_.NumRows()); if (NumRows() > 0) { // down is a vector of flags, true to sort in reverse order char* down = (char*) _down.SetBufferClear(NumHandlers()); // set the down flag for all properties to be sorted in reverse if (down_) for (int i = 0; i < NumHandlers(); ++i) if (down_->PropIndex(NthPropId(i)) >= 0) down[i] = 1; _width = -1; int n = NumHandlers() + 1; _info = d4_new c4_SortInfo [n]; int j; for (j = 0; j < NumHandlers(); ++j) { _info[j]._handler = & _seq.NthHandler(j); _info[j]._context = _seq.HandlerContext(j); } _info[j]._handler = 0; // everything is ready, go sort the row index vector MergeSort((T*) &_rowMap.ElementAt(0), NumRows()); delete [] _info; _info = 0; FixupReverseMap(); } } c4_SortSeq::~c4_SortSeq () { d4_assert(!_info); } int c4_SortSeq::Compare(int index_, c4_Cursor cursor_) const { d4_assert(cursor_._seq != 0); const char* down = (const char*) _down.Contents(); d4_assert(_down.Size() <= NumHandlers()); c4_Bytes data; for (int colNum = 0; colNum < NumHandlers(); ++colNum) { c4_Handler& h = NthHandler(colNum); const c4_Sequence* hc = HandlerContext(colNum); if (!cursor_._seq->Get(cursor_._index, h.PropId(), data)) h.ClearBytes(data); int f = h.Compare(RemapIndex(index_, hc), data); if (f != 0) return colNum < _down.Size() && down[colNum] ? -f : +f; } return 0; } int c4_SortSeq::PosInMap(c4_Cursor cursor_) const { int i = 0; while (i < NumRows()) if (Compare(i, cursor_) >= 0) break; else ++i; d4_assert(i == NumRows() || Compare(i, cursor_) >= 0); return i; } c4_Notifier* c4_SortSeq::PreChange(c4_Notifier& /*nf_*/) { if (!GetDependencies()) return 0; #if 0 c4_Notifier* chg = d4_new c4_Notifier (this); switch (nf_._type) { case c4_Notifier::kSetAt: case c4_Notifier::kSet: { d4_assert(0); // also needs nested propagation /* change can require a move *and* a change of contents */ } break; case c4_Notifier::kInsertAt: { d4_assert(0); // this case isn't really difficult } break; case c4_Notifier::kRemoveAt: { d4_assert(0); // nested propagation is too difficult for now // i.e. can only use sort as last derived view /* possible solution: if 1 row, simple else if contig in map, also simple else propagate reorder first, then delete contig it can be done here, as multiple notifications, by simulating n-1 SetAt's of first row in others needs some map juggling, allow temp dup entries? or perhaps more consistent with n separate removes */ } break; case c4_Notifier::kMove: { // incorrect: may need to move if recnum matters (recs same) } break; } return chg; #endif // d4_assert(0); // fail, cannot handle a view dependent on this one yet return 0; } void c4_SortSeq::PostChange(c4_Notifier& nf_) { switch (nf_._type) { case c4_Notifier::kSet: if (_seq.PropIndex(nf_._propId) > _width) break; // cannot affect sort order, valuable optimization case c4_Notifier::kSetAt: { int oi = (int) _revMap.GetAt(nf_._index); d4_assert(oi >= 0); c4_Cursor cursor (_seq, nf_._index); // move the entry if the sort order has been disrupted if ((oi > 0 && Compare(oi - 1, cursor) > 0) || (oi+1 < NumRows() && Compare(oi+1, cursor) < 0)) { _rowMap.RemoveAt(oi); _rowMap.InsertAt(PosInMap(cursor), nf_._index); FixupReverseMap(); } _width = NumHandlers(); // sorry, no more optimization } break; case c4_Notifier::kInsertAt: { // if cursor was not set, it started out as a single Set c4_Cursor cursor (_seq, nf_._index); if (nf_._cursor) cursor = *nf_._cursor; for (int n = 0; n < NumRows(); ++n) if ((int) _rowMap.GetAt(n) >= nf_._index) _rowMap.ElementAt(n) += nf_._count; int i = PosInMap(cursor); _rowMap.InsertAt(i, 0, nf_._count); for (int j = 0; j < nf_._count; ++j) _rowMap.SetAt(i++, nf_._index + j); FixupReverseMap(); _width = NumHandlers(); // sorry, no more optimization } break; case c4_Notifier::kRemoveAt: { int lo = nf_._index; int hi = nf_._index + nf_._count; int j = 0; for (int i = 0; i < NumRows(); ++i) { int n = (int) _rowMap.GetAt(i); if (n >= hi) _rowMap.ElementAt(i) -= nf_._count; if (!(lo <= n && n < hi)) _rowMap.SetAt(j++, _rowMap.GetAt(i)); } d4_assert(j + nf_._count == NumRows()); _rowMap.SetSize(j); FixupReverseMap(); _width = NumHandlers(); // sorry, no more optimization } break; case c4_Notifier::kMove: { // incorrect: may need to move if recnum matters (recs same) } break; } } ///////////////////////////////////////////////////////////////////////////// class c4_ProjectSeq : public c4_DerivedSeq { c4_DWordArray _colMap; // a bit large, but bytes would be too small bool _frozen; int _omitCount; // if > 0 then this is a dynamic "project without" public: c4_ProjectSeq (c4_Sequence& seq_, c4_Sequence& in_, bool, c4_Sequence* out_); virtual ~c4_ProjectSeq (); virtual int NumHandlers() const; virtual c4_Handler& NthHandler(int) const; virtual const c4_Sequence* HandlerContext(int) const; virtual int AddHandler(c4_Handler*); virtual bool Get(int, int, c4_Bytes&); virtual void Set(int, const c4_Property&, const c4_Bytes&); }; ///////////////////////////////////////////////////////////////////////////// c4_ProjectSeq::c4_ProjectSeq (c4_Sequence& seq_, c4_Sequence& in_, bool reorder_, c4_Sequence* out_) : c4_DerivedSeq (seq_), _frozen (!reorder_ && !out_), _omitCount (0) { // build the array with column indexes for (int j = 0; j < in_.NumHandlers(); ++j) { int propId = in_.NthPropId(j); int idx = _seq.PropIndex(propId); // if the j'th property is in the sequence, add it if (idx >= 0) { // but only if it's not in the out_ view if (out_ && out_->PropIndex(propId) >= 0) ++_omitCount; else _colMap.Add(idx); } } // if only reordering, append remaining columns from original view if (reorder_) { for (int i = 0; i < _seq.NumHandlers(); ++i) { int propId = _seq.NthPropId(i); // only consider properties we did not deal with before if (in_.PropIndex(propId) < 0) _colMap.Add(i); } d4_assert(_colMap.GetSize() == _seq.NumHandlers()); } } c4_ProjectSeq::~c4_ProjectSeq () { } int c4_ProjectSeq::NumHandlers() const { return _frozen ? _colMap.GetSize() : _seq.NumHandlers() - _omitCount; } c4_Handler& c4_ProjectSeq::NthHandler(int colNum_) const { int n = colNum_ < _colMap.GetSize() ? _colMap.GetAt(colNum_) : colNum_; return _seq.NthHandler(n); } const c4_Sequence* c4_ProjectSeq::HandlerContext(int colNum_) const { int n = colNum_ < _colMap.GetSize() ? _colMap.GetAt(colNum_) : colNum_; return _seq.HandlerContext(n); } int c4_ProjectSeq::AddHandler(c4_Handler* handler_) { int n = _seq.AddHandler(handler_); return _frozen ? _colMap.Add(n) : n - _omitCount; } bool c4_ProjectSeq::Get(int index_, int propId_, c4_Bytes& buf_) { // fixed in 1.8: check that the property is visible return PropIndex(propId_) >= 0 && _seq.Get(index_, propId_, buf_); } void c4_ProjectSeq::Set(int index_, const c4_Property& prop_, const c4_Bytes& bytes_) { int n = _seq.NumHandlers(); _seq.Set(index_, prop_, bytes_); // if the number of handlers changed, then one must have been added if (n != _seq.NumHandlers()) { d4_assert(n == _seq.NumHandlers() - 1); if (_frozen) _colMap.Add(n); } } ///////////////////////////////////////////////////////////////////////////// c4_Sequence* f4_CreateFilter(c4_Sequence& seq_, c4_Cursor l_, c4_Cursor h_) { return d4_new c4_FilterSeq (seq_, l_, h_); } c4_Sequence* f4_CreateSort(c4_Sequence& seq_, c4_Sequence* down_) { return d4_new c4_SortSeq (seq_, down_); } c4_Sequence* f4_CreateProject(c4_Sequence& seq_, c4_Sequence& in_, bool reorder_, c4_Sequence* out_) { return d4_new c4_ProjectSeq (seq_, in_, reorder_, out_); } /////////////////////////////////////////////////////////////////////////////