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<Quote> 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