1 //-< TIMESERIES.H >--------------------------------------------------*--------*
2 // FastDB                    Version 1.0         (c) 1999  GARRET    *     ?  *
3 // (Post Relational Database Management System)                      *   /\|  *
4 //                                                                   *  /  \  *
5 //                          Created:     22-Nov-2002  K.A. Knizhnik  * / [] \ *
6 //                          Last update: 22-Nov-2002  K.A. Knizhnik  * GARRET *
7 //-------------------------------------------------------------------*--------*
8 // Container for time serires data
9 //-------------------------------------------------------------------*--------*
10 
11 #ifndef __TIMESERIES_H__
12 #define __TIMESERIES_H__
13 
14 #include "fastdb.h"
15 
16 BEGIN_FASTDB_NAMESPACE
17 
18 #define INFINITE_TIME 0x7fffffff
19 
20 /**
21  * Time series block contaning array of elements. Grouping several elements in one block (record)
22  * reduce space overhead and increase processing speed.<BR>
23  * <B>Attention!</B> This class is not serialized, so it is can be accessed only by one thread<P>
24  * <I>You are defining your own time series class, for example:</I>
25  * <PRE>
26  * class Stock {
27  *   public:
28  *     char const* name;
29  *     TYPE_DESCRIPTOR((KEY(name, INDEXED)));
30  * };
31  *
32  *
33  * class Quote {
34  *   public:
35  *     int4        tickerDate;
36  *     real4       bid;
37  *     int4        bidSize;
38  *     real4       ask;
39  *     int4        askSize;
40  *
41  *     time_t time() const { return tickerDate; } // this method should be defined
42  *
43  *     TYPE_DESCRIPTOR((FIELD(tickerDate), FIELD(bid), FIELD(bidSize), FIELD(ask), FIELD(askSize)));
44  * };
45  * typedef dbTimeSeriesBlock<Quote>  DailyBlock;
46  * REGISTER_TEMPLATE(DailyBlock);
47  * REGISTER(Stock);
48  * </PRE>
49  * <I>Now you can work with time series objects in the followin way:</I>
50  * <PRE>
51  * dbDatabase db;
52  * if (db.open("mydatabase.dbs")) {
53  *     dbTimeSeriesProcessor&lt;Quote&gt; proc(db, MIN_ELEMENTS_IN_BLOCK,MAX_ELEMENTS_IN_BLOCK);
54  *     Quote quote;
55  *     // initialize quote
56  *     Stock stock;
57  *     stock.name = "AAD";
58  *     oid_t stockId = insert(stock).getOid();
59  *     proc.add(stockId, quote); // add new element in time series
60  *
61  *     Quote quoteBuf[MAX_QUOTES];
62  *     // select quotes for the specified interval
63  *     int n = proc.getInterval(stockId, fromDate, tillDate, quoteBuf, MAX_QUOTES);
64  *     for (int i = 0; i < n; i++) {
65  *         printf("bid=d ask=%d\n", quoteBuf[i].bid, quoteBuf[i].ask);
66  *     }
67  * }
68  * </PRE>
69  */
70 template<class T>
71 class dbTimeSeriesBlock {
72   public:
73     db_int8 blockId;
74     db_int4 used;
75     dbArray<T> elements;
76 
77     TYPE_DESCRIPTOR((KEY(blockId, INDEXED), FIELD(used), FIELD(elements)));
78 };
79 
80 
81 /**
82  * Time series processor.<BR>
83  * Element of time series can be arbitrary type with declared TYPE_DESCRIPTOR and defined
84  * <code>time_t time()</code> method
85  */
86 template<class T>
87 class dbTimeSeriesProcessor {
88     struct Interval {
89         db_int8 from;
90         db_int8 till;
91     };
92 
93   public:
94     /**
95      * Virtual method for processing elements, Should be redefinedin derived class.
96      * @param data reference to the processed data element
97      */
process(T const & data)98     virtual void process(T const& data) {}
99 
100     /**
101      * Add new element
102      * @param oid time series identifer (OID of the object associated with this time series)
103      * @param data reference to the inserted element
104      */
add(oid_t oid,T const & data)105     void add(oid_t oid, T const& data)
106     {
107         Interval interval;
108         interval.from = generateBlockId(oid, data.time() - maxBlockTimeInterval);
109         interval.till = generateBlockId(oid, data.time());
110         dbCursor< dbTimeSeriesBlock<T> > blocks;
111         blocks.select(selectBlock, dbCursorForUpdate, &interval);
112         if (blocks.last()) {
113             insertInBlock(oid, blocks, data);
114         } else {
115             addNewBlock(oid, data);
116         }
117     }
118 
119     /**
120      * Process elements in the block belonging to the specified range
121      * @param oid time series identifer (OID of the object associated with this time series)
122      * @param from inclusive low bound for element timestamp (set 0 to disable this criteria)
123      * @param till inclusive high bound for element timestamp (set INFINITE_TIME to disable this criteria)
124      */
select(oid_t oid,time_t from,time_t till)125     void select(oid_t oid, time_t from, time_t till)
126     {
127         Interval interval;
128         interval.from = generateBlockId(oid, from - maxBlockTimeInterval);
129         interval.till = generateBlockId(oid, till);
130         dbCursor< dbTimeSeriesBlock<T> > blocks;
131         if (blocks.select(selectBlock, dbCursorViewOnly, &interval)) {
132             do {
133                 int n = blocks->used;
134                 T const* e =  blocks->elements.get();
135                 int l = 0, r = n;
136                 while (l < r)  {
137                     int i = (l+r) >> 1;
138                     if (from > e[i].time()) {
139                         l = i+1;
140                     } else {
141                         r = i;
142                     }
143                 }
144                 assert(l == r && (l == n || e[l].time() >= from));
145                 while (l < n && e[l].time() <= till) {
146                     process(e[l++]);
147                 }
148             } while (blocks.next());
149         }
150     }
151 
152     /**
153      * Get the time of the first element in time series
154      * @param oid time series identifer (OID of the object associated with this time series)
155      * @return earliest time in times series or -1 if there are no elements in time series
156      */
getFirstTime(oid_t oid)157     time_t getFirstTime(oid_t oid)
158     {
159         Interval interval;
160         interval.from = generateBlockId(oid, 0);
161         interval.till = generateBlockId(oid, INFINITE_TIME);
162         dbCursor< dbTimeSeriesBlock<T> > blocks;
163         blocks.setSelectionLimit(1);
164         if (blocks.select(selectBlock, dbCursorViewOnly, &interval)) {
165             return blocks->elements[0].time();
166         }
167         return (time_t)-1;
168     }
169 
170     /**
171      * Get the time of the last element in time series
172      * @param oid time series identifer (OID of the object associated with this time series)
173      * @return latest time in times series or -1 if there are no elements in time series
174      */
getLastTime(oid_t oid)175     time_t getLastTime(oid_t oid)
176     {
177         Interval interval;
178         interval.from = generateBlockId(oid, 0);
179         interval.till = generateBlockId(oid, INFINITE_TIME);
180         dbCursor< dbTimeSeriesBlock<T> > blocks;
181         blocks.setSelectionLimit(1);
182         if (blocks.select(selectBlockReverse, dbCursorViewOnly, &interval)) {
183             return blocks->elements[blocks->used-1].time();
184         }
185         return (time_t)-1;
186     }
187 
188     /**
189      * Get number of elements in time series.
190      * @param oid time series identifer (OID of the object associated with this time series)
191      * @return number of elements in time series.
192      */
getNumberOfElements(oid_t oid)193     size_t getNumberOfElements(oid_t oid)
194     {
195         Interval interval;
196         interval.from = generateBlockId(oid, 0);
197         interval.till = generateBlockId(oid, INFINITE_TIME);
198         dbCursor< dbTimeSeriesBlock<T> > blocks;
199         int n = 0;
200         if (blocks.select(selectBlock, dbCursorViewOnly, &interval)) {
201             do {
202                 n += blocks->used;
203             } while (blocks.next());
204         }
205         return n;
206     }
207 
208     /**
209      * Select elements belonging to the specified interval
210      * @param oid time series identifer (OID of the object associated with this time series)
211      * @param from inclusive low bound for element timestamp (set 0 to disable this criteria)
212      * @param till inclusive high bound for element timestamp (set INFINITE_TIME to disable this criteria)
213      * @param buf destination buffer for selected elements
214      * @param bufSize size of buffer: up to bufSize elements will be placed in buffer
215      * @return number of elements belonging to the specified interval (can be greater than bufSize)
216      */
getInterval(oid_t oid,time_t from,time_t till,T * buf,size_t bufSize)217     size_t getInterval(oid_t oid, time_t from, time_t till, T* buf, size_t bufSize)
218     {
219         Interval interval;
220         interval.from = generateBlockId(oid, from == 0 ? 0 : from - maxBlockTimeInterval);
221         interval.till = generateBlockId(oid, till);
222         dbCursor< dbTimeSeriesBlock<T> > blocks;
223         size_t nSelected = 0;
224         if (blocks.select(selectBlock, dbCursorViewOnly, &interval)) {
225             do {
226                 int n = blocks->used;
227                 T const* e =  blocks->elements.get();
228                 int l = 0, r = n;
229                 while (l < r)  {
230                     int i = (l+r) >> 1;
231                     if (from > e[i].time()) {
232                         l = i+1;
233                     } else {
234                         r = i;
235                     }
236                 }
237                 assert(l == r && (l == n || e[l].time() >= from));
238                 while (l < n && e[l].time() <= till) {
239                     if (nSelected < bufSize) {
240                         buf[nSelected] = e[l];
241                     }
242                     l += 1;
243                     nSelected += 1;
244                 }
245             } while (blocks.next());
246         }
247         return nSelected;
248     }
249 
250     /**
251      * Get time series element with specified time
252      * @param oid time series identifer (OID of the object associated with this time series)
253      * @param elem reference to the extracted element
254      * @param t timestamp of extracted element
255      * @return <code>true</code> if element with specifed times exists in time series
256      */
getElement(oid_t oid,T & elem,time_t t)257     bool getElement(oid_t oid, T& elem, time_t t)
258     {
259         return getInterval(oid, t, t, &elem, 1) == 1;
260     }
261 
262     /**
263      * Select first N elements of times series with timestamp less than or equal to specified
264      * @param oid time series identifer (OID of the object associated with this time series)
265      * @param till inclusive high bound for element timestamp (set INFINITE_TIME to disable this criteria)
266      * @param buf destination buffer for selected elements
267      * @param bufSize size of buffer: up to bufSize elements will be placed in buffer
268      * @return number of selected elements (can be less than bufSize if there are less elements in time series
269      * with timestamp less or equal than specified, but can not be greater than bufSize)
270      */
getFirstInterval(oid_t oid,time_t till,T * buf,size_t bufSize)271     size_t getFirstInterval(oid_t oid, time_t till, T* buf, size_t bufSize)
272     {
273         if (bufSize == 0) {
274             return 0;
275         }
276         Interval interval;
277         interval.from = generateBlockId(oid, 0);
278         interval.till = generateBlockId(oid, till);
279         dbCursor< dbTimeSeriesBlock<T> > blocks;
280         size_t nSelected = 0;
281         if (blocks.select(selectBlock, dbCursorViewOnly, &interval)) {
282             do {
283                 int n = blocks->used;
284                 T const* e =  blocks->elements.get();
285                 for (int i = 0; i < n && e[i].time() <= till; i++) {
286                     buf[nSelected++] = e[i];
287                     if (nSelected == bufSize) {
288                         return nSelected;
289                     }
290                 }
291             } while (blocks.next());
292         }
293         return nSelected;
294     }
295 
296 
297     /**
298      * Select last N elements of times series with timestamp greater than or equal to specified
299      * @param oid time series identifer (OID of the object associated with this time series)
300      * @param from inclusive low bound for element timestamp (set 0 to disable this criteria)
301      * @param buf destination buffer for selected elements
302      * @param bufSize size of buffer: up to bufSize elements will be placed in buffer
303      * @return number of selected elements (can be less than bufSize if there are less elements in time series
304      * with timestamp greater or equal than specified, but can not be greater than bufSize)
305      */
getLastInterval(oid_t oid,time_t from,T * buf,size_t bufSize)306     size_t getLastInterval(oid_t oid, time_t from, T* buf, size_t bufSize)
307     {
308         if (bufSize == 0) {
309             return 0;
310         }
311         Interval interval;
312         interval.from = generateBlockId(oid, from == 0 ? 0 : from - maxBlockTimeInterval);
313         interval.till = generateBlockId(oid, INFINITE_TIME);
314         dbCursor< dbTimeSeriesBlock<T> > blocks;
315 
316         size_t nSelected = 0;
317         blocks.select(selectBlock, dbCursorViewOnly, &interval);
318         if (blocks.last()) {
319             do {
320                 int n = blocks->used;
321                 T const* e =  blocks->elements.get();
322                 for (int i = n; --i >= 0 && e[i].time() >= from;) {
323                     buf[nSelected++] = e[i];
324                     if (nSelected == bufSize) {
325                         return nSelected;
326                     }
327                 }
328             } while (blocks.prev());
329         }
330         return nSelected;
331     }
332 
333 
334 
335     /**
336      * Check if there is element for specified data in time series
337      * @param oid time series identifer (OID of the object associated with this time series)
338      * @param t timestamp of checked element
339      * @return <code>true</code> if element with specifed times exists in time series
340      */
hasElement(oid_t oid,time_t t)341     bool hasElement(oid_t oid, time_t t)
342     {
343         T dummy;
344         return getElement(oid, dummy, t);
345     }
346 
347     /**
348      * TimeSeries processor constructor
349      * @param database reference to the database
350      * @param minElementsInBlock preallocated number of the elements in the block:
351      * array with specified number of elements will be allocated for new block
352      * @param maxElementsInBlock maximal number of the elements in the block: block will be splitten if it has maxElementsInBlock
353      * elements and new is added to the block
354      * @param maxBlockTimeInterval maximal interval between first and last element in the block, new block will be created if
355      * adding new element to the block cause violation of this assumption. If maxBlockTimeInterval is 0, then it is assigned
356      * to doubled number of seconds in day multipied on maxElementsInBlock
357      */
358     dbTimeSeriesProcessor(dbDatabase& database, int minElementsInBlock=100, int maxElementsInBlock=100, time_t maxBlockTimeInterval=0) :
db(database)359         db(database)
360     {
361         assert(minElementsInBlock > 0 && maxElementsInBlock >= minElementsInBlock);
362         if (maxBlockTimeInterval == 0) {
363             maxBlockTimeInterval = 2*(maxElementsInBlock*24*60*60); // doubled interval in seconds, one element per day
364         }
365         this->maxElementsInBlock = maxElementsInBlock;
366         this->minElementsInBlock = minElementsInBlock;
367         this->maxBlockTimeInterval = maxBlockTimeInterval;
368 
369         // correct instance of interval will be specified in select
370         Interval* dummy = NULL;
371         selectBlock = "blockId between",dummy->from,"and",dummy->till;
372         selectBlockReverse = "blockId between",dummy->from,"and",dummy->till,"order by blockId desc";
373     }
374 
375     /**
376      * Remove elements for the sepcified period
377      * @param oid time series identifer (OID of the object associated with this time series)
378      * @param from inclusive low bound for element timestamp (set 0 to disable this criteria)
379      * @param till inclusive high bound for element timestamp (set INFINITE_TIME to disable this criteria)
380      * @return number of removed elements
381      */
remove(oid_t oid,time_t from,time_t till)382     int remove(oid_t oid, time_t from, time_t till)
383     {
384         Interval interval;
385         interval.from = generateBlockId(oid, from == 0 ? 0 : from - maxBlockTimeInterval);
386         interval.till = generateBlockId(oid, till);
387         dbCursor< dbTimeSeriesBlock<T> > blocks;
388         size_t nRemoved = 0;
389         if (blocks.select(selectBlock, dbCursorForUpdate, &interval)) {
390             do {
391                 int n = blocks->used;
392                 T const* e =  blocks->elements.get();
393                 int l = 0, r = n;
394                 while (l < r)  {
395                     int i = (l+r) >> 1;
396                     if (from > e[i].time()) {
397                         l = i+1;
398                     } else {
399                         r = i;
400                     }
401                 }
402                 assert(l == r && (l == n || e[l].time() >= from));
403                 while (r < n && e[r].time() <= till) {
404                     r += 1;
405                     nRemoved += 1;
406                 }
407                 if (l == 0 && r == n) {
408                     blocks.remove();
409                 } else if (l < n && l != r) {
410                     if (l == 0) {
411                         blocks->blockId = generateBlockId(oid, e[r].time());
412                     }
413                     T* ue = blocks->elements.update();
414                     while (r < n) {
415                         ue[l++] = ue[r++];
416                     }
417                     blocks->used = l;
418                     blocks.update();
419                 }
420             } while (blocks.next());
421         }
422         return nRemoved;
423     }
424 
~dbTimeSeriesProcessor()425     virtual~dbTimeSeriesProcessor() {}
426 
427     /**
428      * This method should be actually private but since there is no portable way of declaration
429      * of friend templates classes recognized by all C++ compiler, it is made public.
430      * Do not use this method yourself.
431      */
_openIteratorCursor(dbCursor<dbTimeSeriesBlock<T>> & cursor,oid_t oid,time_t from,time_t till)432     int _openIteratorCursor(dbCursor< dbTimeSeriesBlock<T> >& cursor, oid_t oid, time_t from, time_t till)
433     {
434         Interval interval;
435         interval.from = generateBlockId(oid, from == 0 ? 0 : from - maxBlockTimeInterval);
436         interval.till = generateBlockId(oid, till);
437         return cursor.select(selectBlock, dbCursorViewOnly, &interval);
438     }
439 
440    private:
generateBlockId(oid_t oid,time_t date)441      db_int8 generateBlockId(oid_t oid, time_t date)
442      {
443         return cons_int8(oid, date);
444      }
445 
446 
addNewBlock(oid_t oid,T const & data)447      void addNewBlock(oid_t oid, T const& data)
448      {
449          dbTimeSeriesBlock<T> block;
450          block.blockId = generateBlockId(oid, data.time());
451          block.elements.resize(minElementsInBlock);
452          block.used = 1;
453          block.elements.putat(0, data);
454          insert(block);
455      }
456 
insertInBlock(oid_t oid,dbCursor<dbTimeSeriesBlock<T>> & blocks,T const & data)457      void insertInBlock(oid_t oid, dbCursor< dbTimeSeriesBlock<T> >& blocks, T const& data)
458      {
459          time_t t = data.time();
460          int i, n = blocks->used;
461 
462          T const* e =  blocks->elements.get();
463          int l = 0, r = n;
464          while (l < r)  {
465              i = (l+r) >> 1;
466              if (t > e[i].time()) {
467                  l = i+1;
468              } else {
469                  r = i;
470              }
471          }
472          assert(l == r && (l == n || e[l].time() >= t));
473          if (r == 0) {
474              if (e[n-1].time() - t > maxBlockTimeInterval || n == maxElementsInBlock) {
475                  addNewBlock(oid, data);
476                  return;
477              }
478              blocks->blockId = generateBlockId(oid, t);
479          } else if (r == n) {
480              if (t - e[0].time() > maxBlockTimeInterval || n == maxElementsInBlock) {
481                  addNewBlock(oid, data);
482                  return;
483              }
484          }
485          if ((size_t)n == blocks->elements.length()) {
486              if (n == maxElementsInBlock) {
487                  T* u = blocks->elements.update();
488                  addNewBlock(oid, u[n-1]);
489                  for (i = n; --i > r; ) {
490                      u[i] = u[i-1];
491                  }
492                  u[r] = data;
493                  blocks.update();
494                  return;
495              }
496              blocks->elements.resize(n + minElementsInBlock < maxElementsInBlock ? n + minElementsInBlock : maxElementsInBlock);
497          }
498          T* u = blocks->elements.update();
499          for (i = n; i > r; i--) {
500              u[i] = u[i-1];
501          }
502          u[r] = data;
503          blocks->used += 1;
504          blocks.update();
505      }
506 
507      dbDatabase& db;
508      int         maxElementsInBlock;
509      int         minElementsInBlock;
510      time_t      maxBlockTimeInterval;
511      dbQuery     selectBlock;
512      dbQuery     selectBlockReverse;
513 };
514 
515 
516 /**
517  * Time series forward iterator
518  */
519 template<class T>
520 class dbTimeSeriesIterator {
521   public:
522     /**
523      * Start iteration through elements belonging to the specified range.
524      * @param processor pointer to time series processor
525      * @param oid time series identifer (OID of the object associated with this time series)
526      * @param from inclusive low bound for element timestamp (set 0 to disable this criteria)
527      * @param till inclusive high bound for element timestamp (set INFINITE_TIME to disable this criteria)
528      */
start(dbTimeSeriesProcessor<T> * processor,oid_t oid,time_t from,time_t till)529     void start(dbTimeSeriesProcessor<T>* processor, oid_t oid, time_t from, time_t till) {
530         first = pos = -1;
531         this->till = till;
532         if (processor->_openIteratorCursor(blocks, oid, from, till)) {
533             do {
534                 int n = blocks->used;
535                 T const* e =  blocks->elements.get();
536                 int l = 0, r = n;
537                 while (l < r)  {
538                     int i = (l+r) >> 1;
539                     if (from > e[i].time()) {
540                         l = i+1;
541                     } else {
542                         r = i;
543                     }
544                 }
545                 assert(l == r && (l == n || e[l].time() >= from));
546                 if (l < n) {
547                     if (e[l].time() <= till) {
548                         first = pos = l;
549                     }
550                     return;
551                 }
552             } while (blocks.next());
553         }
554     }
555 
556     /**
557      * Get current iterator element
558      * @return <code>true</code> if there is current element, <code>false</code> otherwise
559      */
current(T & elem)560     bool current(T& elem) {
561         if (pos >= 0) {
562             elem = blocks->elements[pos];
563             return true;
564         }
565         return false;
566     }
567 
568     /**
569      * Move iterator position to next element.
570      * @return <code>true</code> if next element exists, <code>false</code> otherwise
571      */
next()572     bool next() {
573         if (pos >= 0) {
574             if (++pos == blocks->used) {
575                 if (!blocks.next()) {
576                     pos = -1;
577                     return false;
578                 }
579                 pos = 0;
580             }
581             if (blocks->elements[pos].time() <= till) {
582                 return true;
583             }
584             pos = -1;
585         }
586         return false;
587     }
588 
589     /**
590      * Reset iterator to the initial state
591      */
reset()592     void reset() {
593         blocks.first();
594         pos = first;
595     }
596 
597     /**
598      * Iterator costructor. If current() or next() method will always return false if
599      * them are invoked prior to start()
600      */
dbTimeSeriesIterator()601     dbTimeSeriesIterator() {
602         first = pos = -1;
603     }
604   private:
605     dbCursor< dbTimeSeriesBlock<T> > blocks;
606     int                              pos;
607     int                              first;
608     time_t                           till;
609 };
610 
611 /**
612  * Time series reverse iterator
613  */
614 template<class T>
615 class dbTimeSeriesReverseIterator {
616   public:
617     /**
618      * Start iteration through elements belonging to the specified range.
619      * @param processor pointer to time series processor
620      * @param oid time series identifer (OID of the object associated with this time series)
621      * @param from inclusive low bound for element timestamp (set 0 to disable this criteria)
622      * @param till inclusive high bound for element timestamp (set INFINITE_TIME to disable this criteria)
623      */
start(dbTimeSeriesProcessor<T> * processor,oid_t oid,time_t from,time_t till)624     void start(dbTimeSeriesProcessor<T>* processor, oid_t oid, time_t from, time_t till) {
625         last = pos = -1;
626         this->from = from;
627         if (processor->_openIteratorCursor(blocks, oid, from, till)) {
628             do {
629                 int n = blocks->used;
630                 blocks.last();
631                 T const* e =  blocks->elements.get();
632                 int l = 0, r = n;
633                 while (l < r)  {
634                     int i = (l+r) >> 1;
635                     if (till >= e[i].time()) {
636                         l = i+1;
637                     } else {
638                         r = i;
639                     }
640                 }
641                 assert(l == r && (l == n || e[l].time() > till));
642                 if (l > 0) {
643                     if (e[l-1].time() >= from) {
644                         last = pos = l-1;
645                     }
646                     return;
647                 }
648             } while (blocks.prev());
649         }
650     }
651 
652     /**
653      * Get current iterator element
654      * @return <code>true</code> if there is current element, <code>false</code> otherwise
655      */
current(T & elem)656     bool current(T& elem) {
657         if (pos >= 0) {
658             elem = blocks->elements[pos];
659             return true;
660         }
661         return false;
662     }
663 
664     /**
665      * Move iterator position to next element.
666      * @return <code>true</code> if next element exists, <code>false</code> otherwise
667      */
next()668     bool next() {
669         if (pos >= 0) {
670             if (--pos < 0) {
671                 if (!blocks.prev()) {
672                     return false;
673                 }
674                 pos = blocks->used-1;
675             }
676             if (blocks->elements[pos].time() >= from) {
677                 return true;
678             }
679             pos = -1;
680         }
681         return false;
682     }
683 
684     /**
685      * Reset iterator to the initial state
686      */
reset()687     void reset() {
688         blocks.last();
689         pos = last;
690     }
691 
692     /**
693      * Iterator costructor. If current() or next() method will always return false if
694      * them are invoked prior to start()
695      */
dbTimeSeriesReverseIterator()696     dbTimeSeriesReverseIterator() {
697         last = pos = -1;
698     }
699   private:
700     dbCursor< dbTimeSeriesBlock<T> > blocks;
701     int                              pos;
702     int                              last;
703     time_t                           from;
704 };
705 
706 END_FASTDB_NAMESPACE
707 
708 #endif
709