extern const sqlite3_api_routines * sqlite3_api;
typedef struct minmaxAggData {
int flag;
int size;
union {
void* buffer; // allocated by sqlite3_malloc
sqlite_int64 i;
double d;
};
} minmaxAggData;
static int compareValue( sqlite3_value * val, minmaxAggData * base ) {
int valType;
assert( val );
assert( base );
valType = sqlite3_value_type( val );
if ( ( base->flag == SQLITE_INTEGER ) || ( base->flag == SQLITE_FLOAT ) ) {
int conversionAttempted = 0;
integerCompare:
switch( valType ) {
case SQLITE_INTEGER:
case SQLITE_FLOAT:
// direct compare possible
if ( ( base->flag == SQLITE_INTEGER ) && ( valType == SQLITE_INTEGER ) ) {
sqlite_int64 i = sqlite3_value_int64( val );
return ( base->i == i ) ? 0 : ( ( base->i > i ) ? -1: 1 );
}
{
double d1 = ( base->flag == SQLITE_INTEGER ) ? ( ( double ) base->i ) : base->d ;
double d2 = sqlite3_value_double( val );
return ( d1 == d2 ) ? 0 : ( ( d1 > d2 ) ? -1: 1 );
}
break;
case SQLITE_NULL:
return -1;
break;
default:
if ( !conversionAttempted ) {
valType = sqlite3_value_numeric_type( val );
conversionAttempted = 1;
goto integerCompare;
}
else {
return -2;
}
break;
}
}
else if ( ( base->flag == SQLITE_BLOB ) || ( base->flag == SQLITE_TEXT ) ) {
int b1 = base->size, b2 = sqlite3_value_bytes( val ), result = 0;
const void * v2 = ( base->flag == SQLITE_BLOB ) ? sqlite3_value_blob( val ) : sqlite3_value_text( val );
if ( v2 ) result = memcmp( v2 , base->buffer, ( b1 <>
if ( ( result == 0 ) && ( b1 != b2 ) ) { result = ( b1 > b2 ) ? 1: -1; }
return result;
}
else if ( ( base->flag == SQLITE_NULL ) || ( base->flag == 0 ) ) {
return ( valType != SQLITE_NULL ) ? 1 : 0;
}
else {
assert( 0 && "unknown datatype" );
return -2;
}
}
static void releaseValue( minmaxAggData * dest) {
assert( dest );
// need to free the buffer
if ( ( dest->flag == SQLITE_TEXT ) || ( dest->flag == SQLITE_BLOB ) ) {
if ( dest->buffer ) {
sqlite3_free( dest->buffer ); } }
memset( dest, 0, sizeof ( minmaxAggData ) );
}
static int copyValue( sqlite3_value * val, minmaxAggData * dest ) {
int valType;
assert( val );
assert( dest );
valType = sqlite3_value_type( val );
releaseValue( dest );
switch( valType ) {
case SQLITE_INTEGER:
dest->i = sqlite3_value_int64( val );
break;
case SQLITE_FLOAT:
dest->d = sqlite3_value_double( val );
break;
case SQLITE_BLOB:
case SQLITE_TEXT:
{
int sz = sqlite3_value_bytes( val );
const void * src = ( valType == SQLITE_TEXT ) ? sqlite3_value_text( val ): sqlite3_value_blob( val );
if ( ( sz ) && ( src ) ) {
if ( ( dest->buffer = sqlite3_malloc( sz + 1 ) ) != 0 ) {
memmove( dest->buffer, src, ( size_t ) sz );
*( ( char* ) dest->buffer + sz ) = 0; // make sure the string is zero terminated
dest->size = sz; } }
}
break;
case SQLITE_NULL:
break;
case 0: // initialized or empty
break;
default:
assert( 0 && "unknown or insupported data type" );
return 0;
}
dest->flag = valType;
return 1;
}
// ---
// Implementation of the min_indexed(), max_indexed()
//
// the call syntax from sql
// min_indexed(
//
static void minmaxStep( sqlite3_context *context, int argc, sqlite3_value **argv, int max ) {
minmaxAggData* pBest;
// validate the parameter count
if ( argc != 2 ) {
sqlite3_result_error( context, "exactly two parameters expected by the indexed min/max functions.", -1);
return; }
if( sqlite3_value_type( argv[0] )==SQLITE_NULL ) return;
pBest = ( minmaxAggData* ) sqlite3_aggregate_context( context, 2 * sizeof( *pBest ) );
if ( !pBest ) return;
if ( pBest[0].flag ) {
switch( compareValue( argv[0], &pBest[0] ) ) {
case 1:
if ( max ) {
copyValue( argv[0], &pBest[0] );
copyValue( argv[1], &pBest[1] );
}
break;
case -1:
if ( !max ) {
copyValue( argv[0], &pBest[0] );
copyValue( argv[1], &pBest[1] );
}
break;
default:
// no action
break;
}
}
else {
// the best value to compare against
copyValue( argv[0], &pBest[0] );
// the value to return
copyValue( argv[1], &pBest[1] );
}
}
void minStep( sqlite3_context *context, int argc, sqlite3_value **argv ) {
minmaxStep( context, argc, argv, 0 ); }
void maxStep( sqlite3_context *context, int argc, sqlite3_value **argv ) {
minmaxStep( context, argc, argv, 1 ); }
void minmaxFinalize( sqlite3_context *context ) {
minmaxAggData* pBest;
pBest = ( minmaxAggData* ) sqlite3_aggregate_context( context, 2 * sizeof( *pBest ) );
if ( !pBest ) {
sqlite3_result_null( context ); return ;}
releaseValue( &pBest[0] );
switch( pBest[1].flag ) {
case SQLITE_INTEGER: sqlite3_result_int64( context, pBest[1].i ); break;
case SQLITE_FLOAT: sqlite3_result_double( context, pBest[1].d ); break;
case SQLITE_BLOB: sqlite3_result_blob( context, pBest[1].buffer, pBest[1].size, sqlite3_free ); break;
case SQLITE_TEXT: sqlite3_result_text( context, ( char * ) pBest[1].buffer, pBest[1].size, sqlite3_free ); break;
default: sqlite3_result_null( context ) ;break;
}
// no need to release the return value. sqlite will take care of that
}

