blob: 7fe94a80d07929852b428a2e374bde5f9674ba0d [file]
/*------------------------------------------------------------------------------
* Copyright (C) 2003-2006 Ben van Klinken and the CLucene Team
*
* Distributable under the terms of either the Apache License (Version 2.0) or
* the GNU Lesser General Public License, as specified in the COPYING file.
------------------------------------------------------------------------------*/
#include "QueryUtils.h"
#include "CLucene/search/Scorer.h"
#include "CheckHits.h"
/////////////////////////////////////////////////////////////////////////////
int32_t QueryUtils::skip_op = 0;
int32_t QueryUtils::next_op = 1;
float_t QueryUtils::maxDiff = 1e-5f;
/////////////////////////////////////////////////////////////////////////////
class WhackyQuery : public CL_NS(search)::Query
{
public:
WhackyQuery() {};
virtual ~WhackyQuery() {}
Query * clone() const
{
return _CLNEW WhackyQuery();
}
static const char * getClassName()
{
return "WhackyQuery";
}
const char * getObjectName() const
{
return getClassName();
}
TCHAR* toString(const TCHAR* field) const
{
return STRDUP_TtoT( _T( "My Whacky Query" ));
}
bool equals(Query* other) const
{
if( this == other ) return true;
if( other == NULL || !( other->instanceOf( WhackyQuery::getClassName() )))
return false;
return true;
}
size_t hashCode() const
{
size_t result = Similarity::floatToByte( getBoost() ) ^ 0x97AF937F;
return result;
}
};
/////////////////////////////////////////////////////////////////////////////
class QueryUtilsHitCollector1 : public CL_NS(search)::HitCollector
{
public:
int32_t * order;
int32_t * opidx;
int32_t orderLength;
int32_t * sdoc;
Scorer * scorer;
Query * q;
CuTest * tc;
public:
void collect( const int32_t doc, const float_t score )
{
int32_t op = order[ (opidx[ 0 ]++ ) % orderLength ];
bool more = ( op == QueryUtils::skip_op ) ? scorer->skipTo( sdoc[ 0 ] + 1 ) : scorer->next();
sdoc[ 0 ] = scorer->doc();
float_t scorerScore = scorer->score();
float_t scorerScore2 = scorer->score();
float_t scoreDiff = score > scorerScore ? score - scorerScore : scorerScore - score;
float_t scorerDiff = scorerScore2 > scorerScore2 ? scorerScore2 - scorerScore : scorerScore - scorerScore2;
if( ! more || doc != sdoc[ 0 ] || scoreDiff > QueryUtils::maxDiff || scorerDiff > QueryUtils::maxDiff )
{
StringBuffer buffer;
buffer.append( _T( "ERROR matching docs:\n\t" ));
buffer.append( doc != sdoc[ 0 ] ? _T( "--> doc=" ) : _T( "doc=" ));
buffer.appendInt( sdoc[ 0 ] );
buffer.append( ! more ? _T( "\n\t--> tscorer.more=" ) : _T( "\n\ttscorer.more=" ));
buffer.appendBool( more );
buffer.append( scoreDiff > QueryUtils::maxDiff ? _T( "\n\t--> scorerScore=" ) : _T( "\n\tscorerScore=" ));
buffer.appendFloat( scorerScore, 2 );
buffer.append( _T( " scoreDiff=" ));
buffer.appendFloat( scoreDiff, 2 );
buffer.append( _T( " maxDiff=" ));
buffer.appendFloat( QueryUtils::maxDiff, 2 );
buffer.append( scorerDiff > QueryUtils::maxDiff ? _T( "\n\t--> scorerScore2=" ) : _T( "\n\tscorerScore2=" ));
buffer.appendFloat( scorerScore2, 2 );
buffer.append( _T( " scorerDiff=" ));
buffer.appendFloat( scorerDiff, 2 );
buffer.append( _T( "\n\thitCollector.doc=" ));
buffer.appendInt( doc );
buffer.append( _T( " score=" ));
buffer.appendFloat( score, 2 );
buffer.append( _T( "\n\t Scorer=" ));
TCHAR * tmp = scorer->toString();
buffer.append( tmp );
_CLDELETE_LARRAY( tmp );
buffer.append( _T( "\n\t Query=" ));
tmp = q->toString();
buffer.append( tmp );
_CLDELETE_ARRAY( tmp );
buffer.append( _T( "\n\t Order=" ));
for( int32_t i = 0; i < orderLength; i++)
buffer.append( order[ i ] == QueryUtils::skip_op ? _T( " skip()" ): _T( " next()" ));
buffer.append( _T( "\n\t Op=" ));
buffer.append( op == QueryUtils::skip_op ? _T( " skip()" ) : _T( " next()" ));
assertTrueMsg( buffer.getBuffer(), false );
}
}
};
/////////////////////////////////////////////////////////////////////////////
class QueryUtilsHitCollector2 : public CL_NS(search)::HitCollector
{
public:
int32_t * lastDoc;
Query * q;
IndexSearcher * s;
CuTest * tc;
public:
void collect( const int32_t doc, const float_t score )
{
for( int32_t i = lastDoc[ 0 ] + 1; i <= doc; i++ )
{
Weight * w = q->weight( s );
Scorer * scorer = w->scorer( s->getReader() );
if( ! scorer->skipTo( i ) )
{
StringBuffer buffer;
buffer.append( _T( "query collected " ));
buffer.appendInt( doc );
buffer.append( _T( " but skipTo(" ));
buffer.appendInt( i );
buffer.append( _T( ") says no more docs!" ));
assertTrueMsg( buffer.getBuffer(), false );
}
if( doc != scorer->doc() )
{
StringBuffer buffer;
buffer.append( _T( "query collected " ));
buffer.appendInt( doc );
buffer.append( _T( " but skipTo(" ));
buffer.appendInt( i );
buffer.append( _T( ") got to " ));
buffer.appendInt( scorer->doc() );
assertTrueMsg( buffer.getBuffer(), false );
}
float_t skipToScore = scorer->score();
float_t sd = skipToScore - scorer->score();
if( ( sd < 0 ? sd * -1 : sd ) > QueryUtils::maxDiff )
{
StringBuffer buffer;
buffer.append( _T( "unstable skipTo(" ));
buffer.appendInt( i );
buffer.append( _T( ") score: " ));
buffer.appendFloat( skipToScore, 2 );
buffer.append( _T( "/") );
buffer.appendFloat( QueryUtils::maxDiff, 2 );
assertTrueMsg( buffer.getBuffer(), false );
}
if( ( skipToScore > score ? skipToScore - score : score - skipToScore ) > QueryUtils::maxDiff )
{
StringBuffer buffer;
buffer.append( _T( "query assigned doc " ));
buffer.appendInt( doc );
buffer.append( _T( " a score of <" ));
buffer.appendFloat( score, 2 );
buffer.append( _T( "> but skipTo(" ));
buffer.appendInt( i );
buffer.append( _T( ") has <" ));
buffer.appendFloat( skipToScore, 2 );
buffer.append( _T( ">!" ));
assertTrueMsg( buffer.getBuffer(), false );
}
_CLLDELETE( scorer );
_CLLDELETE( w );
}
lastDoc[ 0 ] = doc;
}
};
/////////////////////////////////////////////////////////////////////////////
void QueryUtils::check( CuTest* tc, Query * q )
{
checkHashEquals( tc, q );
}
void QueryUtils::checkHashEquals( CuTest* tc, Query * q )
{
Query * q2 = q->clone();
checkEqual( tc, q, q2 );
Query * q3 = q->clone();
q3->setBoost( 7.21792348f );
checkUnequal( tc, q, q3 );
// test that a class check is done so that no exception is thrown
// in the implementation of equals()
Query * whacky = _CLNEW WhackyQuery();
whacky->setBoost( q->getBoost() );
checkUnequal( tc, q, whacky );
_CLLDELETE( q2 );
_CLLDELETE( q3 );
_CLLDELETE( whacky );
}
void QueryUtils::checkEqual( CuTest* tc, Query * q1, Query * q2 )
{
assertTrue( q1->equals( q2 ));
assertTrue( q2->equals( q1 ));
assertTrue( q1->hashCode() == q2->hashCode() );
}
void QueryUtils::checkUnequal( CuTest* tc, Query * q1, Query * q2 )
{
assertTrue( ! q1->equals( q2 ));
assertTrue( ! q2->equals( q1 ));
// possible this test can fail on a hash collision... if that
// happens, please change test to use a different example.
assertTrue( q1->hashCode() != q2->hashCode());
}
void QueryUtils::checkExplanations( CuTest* tc, Query * q, Searcher * s )
{
CheckHits::checkExplanations( tc, q, NULL, s, true );
}
void QueryUtils::check( CuTest* tc, Query * q1, Searcher * s )
{
check( tc, q1 );
if( s )
{
if( s->getObjectName() == IndexSearcher::getClassName())
{
IndexSearcher * is = (IndexSearcher*) s;
checkFirstSkipTo( tc, q1, is );
checkSkipTo( tc, q1, is );
}
checkExplanations( tc, q1, s );
checkSerialization( tc, q1, s );
}
}
void QueryUtils::checkSerialization( CuTest* tc, Query * q, Searcher * s )
{
Weight * w = q->weight( s );
// TODO: Port this test
// try {
// ByteArrayOutputStream bos = new ByteArrayOutputStream();
// ObjectOutputStream oos = new ObjectOutputStream(bos);
// oos.writeObject(w.);
// oos.close();
// ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
// ois.readObject();
// ois.close();
//
// //skip rquals() test for now - most weights don't overide equals() and we won't add this just for the tests.
// //TestCase.assertEquals("writeObject(w) != w. ("+w+")",w2,w);
//
// } catch (Exception e) {
// IOException e2 = new IOException("Serialization failed for "+w);
// e2.initCause(e);
// throw e2;
// }
_CLLDELETE( w );
}
void QueryUtils::checkSkipTo( CuTest* tc, Query * q, IndexSearcher * s )
{
if( BooleanQuery::getAllowDocsOutOfOrder())
return; // in this case order of skipTo() might differ from that of next().
int32_t order0[] = {next_op};
int32_t order1[] = {skip_op};
int32_t order2[] = {skip_op, next_op};
int32_t order3[] = {next_op, skip_op};
int32_t order4[] = {skip_op, skip_op, next_op, next_op};
int32_t order5[] = {next_op, next_op, skip_op, skip_op};
int32_t order6[] = {skip_op, skip_op, skip_op, next_op, next_op};
int32_t ordersLength[] = { 1, 1, 2, 2, 4, 4, 5 };
int32_t * orders[] = { order0, order1, order2, order3, order4, order5, order6 };
size_t ordersCount = 7;
for( size_t k = 0; k < ordersCount; k++ )
{
int32_t * order = orders[ k ];
int32_t opidx[] = { 0 };
Weight * w = q->weight( s );
Scorer * scorer = w->scorer( s->getReader() );
// FUTURE: ensure scorer.doc()==-1
int32_t * sdoc = _CL_NEWARRAY( int32_t, 1 );
sdoc[ 0 ] = -1;
QueryUtilsHitCollector1 hitCollector;
hitCollector.order = order;
hitCollector.opidx = opidx;
hitCollector.orderLength = ordersLength[ k ];
hitCollector.sdoc = sdoc;
hitCollector.scorer = scorer;
hitCollector.q = q;
hitCollector.tc = tc;
s->_search( q, NULL, &hitCollector );
// make sure next call to scorer is false.
int32_t op = order[ (opidx[ 0 ]++ ) % ordersLength[ k ] ];
bool more = ( op == skip_op ) ? scorer->skipTo( sdoc[ 0 ] + 1 ) : scorer->next();
assertTrue( ! more );
_CLDELETE_LARRAY( sdoc );
_CLLDELETE( scorer );
_CLLDELETE( w );
}
}
void QueryUtils::checkFirstSkipTo( CuTest* tc, Query * q, IndexSearcher * s )
{
int32_t lastDoc[] = {-1};
QueryUtilsHitCollector2 hitCollector;
hitCollector.lastDoc = lastDoc;
hitCollector.q = q;
hitCollector.s = s;
hitCollector.tc = tc;
s->_search( q, NULL, &hitCollector );
Weight * w = q->weight( s );
Scorer * scorer = w->scorer( s->getReader() );
bool more = scorer->skipTo( lastDoc[ 0 ] + 1 );
if( more )
{
StringBuffer buffer;
buffer.append( _T( "query's last doc was " ));
buffer.appendInt( lastDoc[ 0 ] );
buffer.append( _T( " but skipTo(" ));
buffer.appendInt( lastDoc[ 0 ] + 1 );
buffer.append( _T( ") got to " ));
buffer.appendInt( scorer->doc() );
assertTrueMsg( buffer.getBuffer(), false );
}
_CLLDELETE( scorer );
_CLLDELETE( w );
}