/************************* * * * * * * * * * * * * *************************** Copyright (c) 1999-2005 Ryan Bobko ryan@ostrich-emulators.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ************************** * * * * * * * * * * * * **************************/ #include "qhacctable.h" #include "qhaccutils.h" #include #include using namespace std; /*****************************************************************/ /* QHACCTABLE */ /* This is the basis of all data storage in QHacc. This table */ /* will keep an index of ids if necessary, but otherwise */ /* provides no search functionality. Use a QHACCTABLEINDEX to */ /* index arbitrary columns. */ /* */ /* */ /*****************************************************************/ // these are error codes const int QHaccTable::DUPLKEY= -1; const int QHaccTable::NOTFOUND=-2; const int QHaccTable::INITINIT=5; const int QHaccTable::INITGROW=5; const int QHaccTable::INITFREE=8; const char * QHaccTable::ERRORS[]={ "", "column key already exists", "update row not found" }; const char * QHaccTable::newerror( int code ) const { return ERRORS[-code]; } QHaccTable::QHaccTable( int sz, const ColType * cs, const char * n, uint init, uint grow, uint max ) : QHaccResultSet( sz, cs, init, grow ){ if( n ){ name=QString( n ); std::ostream * str; if( idebug( Utils::CURIOSITY, str ) ) *str<<"creating "<sorts( c ) ) return; delete pki; pki=new QHaccTableIndex( this, c, types[c] ); } void QHaccTable::setName( QString s ){ name=s; } const QString& QHaccTable::getName() const { return name; } int QHaccTable::idcol() const { return ( pki ? pki->sorts() : -1 ); } void QHaccTable::clear(){ data.clear(); data.reserve( INITFREE ); if( pki ) pki->remake(); } void QHaccTable::istartLoad( uint rows ){ loading=true; data.reserve( rows+data.size() ); std::ostream * str=0; if( idebug( Utils::CURIOSITY, str ) ) *str<<"starting load of "<contains( row[pki->sorts()] ) ) reason=DUPLKEY; } std::ostream * str=0; if( reasonsorts()<<"):"<contains( col, idx ); if( inthere ) idx=index->at( idx ); } else{ // full table search uint rrr=rows(); for( uint i=0; isorts( pos, spos ) ) ret=indexes[pos]; return ret!=0; } bool QHaccTable::getIndexOn( int pos, QHaccTableIndex *& ret ) const { // get an index for pos if it exists return true if we find an index ret=0; if( pki && pki->sorts( pos ) ) ret=pki; else ret=indexes[pos]; return ret!=0; } vector QHaccTable::igetWhere( const TableSelect& sel ) const { vector indxs; int check=sel.check(); if( check==TableSelect::NO ) return indxs; if( check==TableSelect::ALL ){ uint rrr=rows(); for( uint i=0; istarts( model ); uint e=useme->ends( model ); if( check==TableSelect::NE ){ //cout<<" idx says to omit "<at( i ) ); uint lim=rows(); for( uint i=e; iat( i ) ); } else{ uint start=0, end=rows(); if( check==TableSelect::EQ ){ start=s; end=e; } else if( check==TableSelect::GT ) start=e; else if( check==TableSelect::LT ) end=s; else if( check==TableSelect::GE ) start=s; else if( check==TableSelect::LE ) end=e; //cout<<" idx says "<at( i ) ); } } else{ // NO INDEX // we need to check every row for matches SLOW! std::ostream * str=0; if( idebug( Utils::CURIOSITY, str ) ) *str<<"not using index for "< QHaccTable::getWhere( const TableGet& tg, vector ts, uint& retrows ) const { // first, get the results as normal uint tgsz=tg.cnt(); auto_ptr temp=getWhere( ts, retrows ); // if we aren't selecting individual columns or we failed // to select anything, just return what we have if( tgsz==0 || retrows==0 ) return temp; // figure out what we're going to keep ColType * cts=new ColType[tgsz]; TableCol * tcs=new TableCol[tgsz]; int uqcol=-1; for( uint i=0; i=cols ){ std::ostream * str=0; if( ierror( Utils::ERROPER, str ) ) *str<<"cannot get column "<=0 ){ QHaccTable * t2=new QHaccTable( cols, types ); QHaccTableIndex useme( temp.get(), uqcol, types[uqcol] ); vector unique=useme.unique(); uint vsz=unique.size(); for( uint i=0; iadd( temp->at( useme[unique[i]] ) ); temp.reset( t2 ); retrows=temp->rows(); } // end of uniqueness junk // make sure we can return something auto_ptr ret( new QHaccResultSet( tgsz, cts ) ); // here's where we actually get the columns we want ret->startLoad( retrows ); for( uint i=0; iat( i ).get( tg[j] ); ret->add( TableRow( tcs, tgsz ) ); } ret->stopLoad(); delete [] cts; delete [] tcs; return ret; } auto_ptr QHaccTable::getWhere( vector ts, uint& retrows ) const { auto_ptr ret( new QHaccResultSet( cols, types ) ); if( !ts.empty() ){ // we're trying to use indices to fulfill all our selections, so // get a list of all our rows for each selection, and then // cycle through the smallest vector and look for matching // indices in the other vectors. const uint TSSZ=ts.size(); uint smallest=0, smallestrr=rows(); vector * selections=new vector[TSSZ]; //cout<<"calling igetwhere"<1 ){ if( smallestrr>0 ){ // if our smallest vector has 0 elements, // there isn't much point in looking further // cycle through the vectors finding the intersections... // we'll start with the smallest vector because that will probably // result in the fewest matches. // matches is our current set of rows that should be included in // the final resultset. We seed matches with the first set deque matches; copy( selections[smallest].begin(), selections[smallest].end(), front_insert_iterator >( matches ) ); //cout<<"matches has "< matches2; set_intersection( matches.begin(), matches.end(), selections[i].begin(), selections[i].end(), front_insert_iterator >( matches2 ) ); //cout<<"intersection leaves: "<startLoad( matches.size() ); for( deque::iterator it=matches.begin(); it!=matches.end(); it++ ) ret->add( at( *it ) ); ret->stopLoad(); } // smallestrr>0 } // TSSZ>1 else{ // no hash-sorting necessary, just load const uint VSZ=selections[0].size(); ret->startLoad( VSZ ); for( uint i=0; iadd( at( selections[0][i] ) ); ret->stopLoad(); } delete [] selections; } // ts.empty() else ret->load( this ); retrows=ret->rows(); return ret; } auto_ptr QHaccTable::getWhere( const TableSelect& sel, uint& retrows ) const { vector v( 1, sel ); auto_ptr rslt=getWhere( v, retrows ); return rslt; } TableRow QHaccTable::getWhere( const TableSelect& sel ) const { TableRow ret; uint rr=0; auto_ptr rslt=getWhere( sel, rr ); if( rr>0 ) ret=rslt->at( 0 ); return ret; } void QHaccTable::iadd( uint ssz ){ //reindex(); if( !loading ){ for( int i=0; inewvalat( ssz ); if( pki ) pki->newvalat( ssz ); } } void QHaccTable::iresize(){ remake(); } void QHaccTable::deleteWhere( const TableSelect& sel ){ // get the indices that need to be removed and remove them // all before doing any reindexing int check=sel.check(); if( check==TableSelect::NO ) return; if( check==TableSelect::ALL ) clear(); else{ vector idxs=igetWhere( sel ); const uint vsz=idxs.size(); if( vsz>0 ){ // the indices are sorted, so iterate from the // back, deleting positions as they show up for( vector::reverse_iterator it=idxs.rbegin(); it!=idxs.rend(); it++ ){ //cout<<"calling remvalat for "<<*it<remvalat( *it ); if( pki ) pki->remvalat( *it ); data.erase( data.begin()+*it ); } //reindex(); } } } void QHaccTable::addIndexOn( int col ){ // create and index on a column; if( !indexes[col] ) indexes[col]=new QHaccTableIndex( this, col, types[col] ); } void QHaccTable::addIndexOn( int col, int subcol ){ // create and index on a column; QHaccTableIndex * ret=0; if( !getIndexOn( col, subcol, ret ) ){ indexes[col]=new QHaccTableIndex( this, col, types[col], subcol, types[subcol] ); } } TableCol QHaccTable::max( int col ) { QHaccTableIndex * idx=0; if( getIndexOn( col, idx ) ) return idx->max(); else{ TableCol hi; for( uint i=0; i0 ) hi=row[col]; } return hi; } } TableCol QHaccTable::min( int col ) { QHaccTableIndex * idx=0; if( getIndexOn( col, idx ) ) return idx->min(); else{ TableCol low; for( uint i=0; ireindex(); if( writer ) *str<<"reindex called on "<sorts()<reindex(); } } } void QHaccTable::remake(){ if( !loading ){ std::ostream * str=0; bool writer=idebug( Utils::CURIOSITY, str ); for( int i=0; iremake(); } } if( pki ){ if( writer ) *str<<"remaking index on "<sorts()<remake(); } } } void QHaccTable::updateWhere( const TableSelect& sel, const TableRow& row ){ int valid=QHaccResultSet::verifyRow( row ); if( valid idxs=igetWhere( sel ); for( vector::reverse_iterator it=idxs.rbegin(); it!=idxs.rend(); it++ ){ data.erase( data.begin()+*it ); data.insert( data.begin()+*it, new TableRow( row ) ); } } reindex(); } } void QHaccTable::updateWhere( const TableSelect& sel, const TableUpdate& upds ){ const uint LIM=upds.cnt(); vector idxs=igetWhere( sel ); const uint vsz=idxs.size(); // update all positions that need updates for( uint i=0; iset( upds[j] ); } if( vsz>0 ){ // now reindex changed columns for( uint i=0; ireindex(); // import/export can change IDs, so we // might as well check pki for resort if( pki && pki->sorts( col ) ) pki->reindex(); } } } /***********************************************************/ /* QHACCTABLEINDEX */ /* an index for a column in a QHACCTABLE. This works on */ /* any type of column, and uses a quicksort to provide */ /* the ordering. */ /***********************************************************/ QHaccTableIndex::QHaccTableIndex( QHaccResultSet * table, int f, ColType ft, int s, ColType st ){ init( table, f, ft, s, st ); reindex(); } QHaccTableIndex& QHaccTableIndex::operator=( const QHaccTableIndex& model ){ if( &model!=this ){ init( model.data, model.field, model.ftype, model.subfield, model.subtype ); for( uint i=0; irows(); i++ ) lookup[i]=model.lookup[i]; } return *this; } QHaccTableIndex::QHaccTableIndex( const QHaccTableIndex& model ) { init( model.data, model.field, model.ftype, model.subfield, model.subtype ); for( uint i=0; irows(); i++ ) { lookup.push_back( model.lookup[i] ); } } void QHaccTableIndex::init( QHaccResultSet * table, int f1, ColType f1t, int f2, ColType f2t ){ data=table; uint rows=0; if( data ) rows=data->trows(); lookup.clear(); for( uint i=0; iat( lookup[i] ); } uint QHaccTableIndex::at( uint i ) const { return lookup[i]; } uint QHaccTableIndex::operator[]( uint i ) const { return lookup[i]; } void QHaccTableIndex::remake(){ reindex(); } // FIXME: these are globals, but I don't know how to get rid of them! uint compara=0, scompara=0; ColType fcomp, scomp; int ffield, sfield; bool compo::operator()( const TableRow * x, const TableRow * y ) const { compara++; int rslt=( *x )[ffield].compareTo( ( *y )[ffield], fcomp ); if( sfield>-1 && rslt==0 ){ // need a secondary comparison scompara++; rslt=( *x )[sfield].compareTo( ( *y )[sfield], scomp ); } return rslt<0; } void QHaccTableIndex::reindex(){ uint rws=data->rows(); lookup.clear(); if( data->isEmpty() ) return; compara=scompara=0; //for( uint i=0; i-1 ) qisort( 0, rws-1 ); //cout<<"sorted an index! size: "<( &( data->at( i ) ), i ) ); rws=0; for( multimap::iterator iter=mapper.begin(); iter!=mapper.end(); iter++ ){ lookup.push_back( iter->second ); } //cout<<"sorted an index! size: "<at( datapos ).toString()<( &( data->at( datapos ) ), datapos ) ); //cout<<"newvalat! size: "<rows()<<" comparisons: " //<::iterator iter=mapper.begin(); iter!=mapper.end(); iter++ ) lookup.push_back( iter->second ); } void QHaccTableIndex::remvalat( uint datapos ){ // remove the index position that holds the row at position datapos in // the parent table. we can't quite take advantage of mapper's find function // because there may be duplicates in the map. instead, we just cycle through // lookup until we find our victim, and then erase it /* fcomp=ftype; ffield=field; scomp=subtype; sfield=subfield; compara=scompara=0; */ //TableRow row=data->at( datapos ); //cout<<"index sorts "<::iterator mit=mapper.begin(); mit!=mapper.end(); mit++ ){ if( mit->second==datapos ){ //cout<<"mapper: "<first->toString()<::iterator mit=mapper.begin(); mit!=mapper.end(); mit++ ){ if( mit->second>=datapos ) mit->second--; } lookup.clear(); for( multimap::iterator iter=mapper.begin(); iter!=mapper.end(); iter++ ) lookup.push_back( iter->second ); } } uint QHaccTableIndex::starts( const TableCol& col ) const { // return the position where the given TableCol // starts. if a perfect match can't be found, // return the position where the col would be inserted if( data->isEmpty() || field==-1 ) return 0; /* cout<<"into starts for "<rows(); if( r>100 ) r=60; cout<<"in "<at( i ).get( field ).gets()<rows(); int idx=0; compara=0; while( end-start>1 ){ idx=( start+end )/2; //cout<<"s="<rows() && dat( idx ).get( field )!=col ) idx--; //cout<<" returning "<isEmpty() || field==-1 ) return data->rows(); //uint r=data->rows(); //cout<<"into ends for "<rows(); int idx=0; compara=0; while( end-start>1 ){ idx=( start+end )/2; //cout<<"s="<isEmpty() ) return false; idx=starts( col ); //if( idxrows() ){ //cout<<"starts is "<rows() ) return ( dat( idx ).get( field )==col ); else return false; } vector QHaccTableIndex::unique() const { uint end=data->rows(); uint curloc=0; vector ret; while( curlocisEmpty() ) return TableCol(); return dat( data->rows()-1 ).get( field ); } TableCol QHaccTableIndex::min() const { if( field==-1 || data->isEmpty() ) return TableCol(); return dat( 0 ).get( field ); }