Commit db5f98ab authored by Alvaro Herrera's avatar Alvaro Herrera

Improve BRIN infra, minmax opclass and regression test

The minmax opclass was using the wrong support functions when
cross-datatypes queries were run.  Instead of trying to fix the
pg_amproc definitions (which apparently is not possible), use the
already correct pg_amop entries instead.  This requires jumping through
more hoops (read: extra syscache lookups) to obtain the underlying
functions to execute, but it is necessary for correctness.

Author: Emre Hasegeli, tweaked by Álvaro
Review: Andreas Karlsson

Also change BrinOpcInfo to record each stored type's typecache entry
instead of just the OID.  Turns out that the full type cache is
necessary in brin_deform_tuple: the original code used the indexed
type's byval and typlen properties to extract the stored tuple, which is
correct in Minmax; but in other implementations that want to store
something different, that's wrong.  The realization that this is a bug
comes from Emre also, but I did not use his patch.

I also adopted Emre's regression test code (with smallish changes),
which is more complete.
parent 7be47c56
......@@ -181,7 +181,7 @@ brin_page_items(PG_FUNCTION_ARGS)
column->nstored = opcinfo->oi_nstored;
for (i = 0; i < opcinfo->oi_nstored; i++)
{
getTypeOutputInfo(opcinfo->oi_typids[i], &output, &isVarlena);
getTypeOutputInfo(opcinfo->oi_typcache[i]->type_id, &output, &isVarlena);
fmgr_info(output, &column->outputFn[i]);
}
......
......@@ -428,8 +428,8 @@ typedef struct BrinOpcInfo
/* Opaque pointer for the opclass' private use */
void *oi_opaque;
/* Type IDs of the stored columns */
Oid oi_typids[FLEXIBLE_ARRAY_MEMBER];
/* Type cache entries of the stored columns */
TypeCacheEntry *oi_typcache[FLEXIBLE_ARRAY_MEMBER];
} BrinOpcInfo;
</programlisting>
<structname>BrinOpcInfo</>.<structfield>oi_opaque</> can be used by the
......
......@@ -15,35 +15,21 @@
#include "access/brin_tuple.h"
#include "access/skey.h"
#include "catalog/pg_type.h"
#include "catalog/pg_amop.h"
#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
/*
* Procedure numbers must not collide with BRIN_PROCNUM defines in
* brin_internal.h. Note we only need inequality functions.
*/
#define MINMAX_NUM_PROCNUMS 4 /* # support procs we need */
#define PROCNUM_LESS 11
#define PROCNUM_LESSEQUAL 12
#define PROCNUM_GREATEREQUAL 13
#define PROCNUM_GREATER 14
/*
* Subtract this from procnum to obtain index in MinmaxOpaque arrays
* (Must be equal to minimum of private procnums)
*/
#define PROCNUM_BASE 11
typedef struct MinmaxOpaque
{
FmgrInfo operators[MINMAX_NUM_PROCNUMS];
bool inited[MINMAX_NUM_PROCNUMS];
Oid cached_subtype;
FmgrInfo strategy_procinfos[BTMaxStrategyNumber];
} MinmaxOpaque;
static FmgrInfo *minmax_get_procinfo(BrinDesc *bdesc, uint16 attno,
uint16 procnum);
static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno,
Oid subtype, uint16 strategynum);
Datum
......@@ -53,8 +39,8 @@ brin_minmax_opcinfo(PG_FUNCTION_ARGS)
BrinOpcInfo *result;
/*
* opaque->operators is initialized lazily, as indicated by 'inited' which
* is initialized to all false by palloc0.
* opaque->strategy_procinfos is initialized lazily; here it is set to
* all-uninitialized by palloc0 which sets fn_oid to InvalidOid.
*/
result = palloc0(MAXALIGN(SizeofBrinOpcInfo(2)) +
......@@ -62,8 +48,8 @@ brin_minmax_opcinfo(PG_FUNCTION_ARGS)
result->oi_nstored = 2;
result->oi_opaque = (MinmaxOpaque *)
MAXALIGN((char *) result + SizeofBrinOpcInfo(2));
result->oi_typids[0] = typoid;
result->oi_typids[1] = typoid;
result->oi_typcache[0] = result->oi_typcache[1] =
lookup_type_cache(typoid, 0);
PG_RETURN_POINTER(result);
}
......@@ -122,7 +108,8 @@ brin_minmax_add_value(PG_FUNCTION_ARGS)
* and update them accordingly. First check if it's less than the
* existing minimum.
*/
cmpFn = minmax_get_procinfo(bdesc, attno, PROCNUM_LESS);
cmpFn = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
BTLessStrategyNumber);
compar = FunctionCall2Coll(cmpFn, colloid, newval, column->bv_values[0]);
if (DatumGetBool(compar))
{
......@@ -135,7 +122,8 @@ brin_minmax_add_value(PG_FUNCTION_ARGS)
/*
* And now compare it to the existing maximum.
*/
cmpFn = minmax_get_procinfo(bdesc, attno, PROCNUM_GREATER);
cmpFn = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
BTGreaterStrategyNumber);
compar = FunctionCall2Coll(cmpFn, colloid, newval, column->bv_values[1]);
if (DatumGetBool(compar))
{
......@@ -159,10 +147,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS)
BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
ScanKey key = (ScanKey) PG_GETARG_POINTER(2);
Oid colloid = PG_GET_COLLATION();
Oid colloid = PG_GET_COLLATION(),
subtype;
AttrNumber attno;
Datum value;
Datum matches;
FmgrInfo *finfo;
Assert(key->sk_attno == column->bv_attno);
......@@ -189,18 +179,16 @@ brin_minmax_consistent(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(false);
attno = key->sk_attno;
subtype = key->sk_subtype;
value = key->sk_argument;
switch (key->sk_strategy)
{
case BTLessStrategyNumber:
matches = FunctionCall2Coll(minmax_get_procinfo(bdesc, attno,
PROCNUM_LESS),
colloid, column->bv_values[0], value);
break;
case BTLessEqualStrategyNumber:
matches = FunctionCall2Coll(minmax_get_procinfo(bdesc, attno,
PROCNUM_LESSEQUAL),
colloid, column->bv_values[0], value);
finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
key->sk_strategy);
matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
value);
break;
case BTEqualStrategyNumber:
......@@ -209,25 +197,24 @@ brin_minmax_consistent(PG_FUNCTION_ARGS)
* the current page range if the minimum value in the range <=
* scan key, and the maximum value >= scan key.
*/
matches = FunctionCall2Coll(minmax_get_procinfo(bdesc, attno,
PROCNUM_LESSEQUAL),
colloid, column->bv_values[0], value);
finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
BTLessEqualStrategyNumber);
matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
value);
if (!DatumGetBool(matches))
break;
/* max() >= scankey */
matches = FunctionCall2Coll(minmax_get_procinfo(bdesc, attno,
PROCNUM_GREATEREQUAL),
colloid, column->bv_values[1], value);
finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
BTGreaterEqualStrategyNumber);
matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
value);
break;
case BTGreaterEqualStrategyNumber:
matches = FunctionCall2Coll(minmax_get_procinfo(bdesc, attno,
PROCNUM_GREATEREQUAL),
colloid, column->bv_values[1], value);
break;
case BTGreaterStrategyNumber:
matches = FunctionCall2Coll(minmax_get_procinfo(bdesc, attno,
PROCNUM_GREATER),
colloid, column->bv_values[1], value);
finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
key->sk_strategy);
matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
value);
break;
default:
/* shouldn't happen */
......@@ -252,6 +239,7 @@ brin_minmax_union(PG_FUNCTION_ARGS)
Oid colloid = PG_GET_COLLATION();
AttrNumber attno;
Form_pg_attribute attr;
FmgrInfo *finfo;
bool needsadj;
Assert(col_a->bv_attno == col_b->bv_attno);
......@@ -284,9 +272,10 @@ brin_minmax_union(PG_FUNCTION_ARGS)
}
/* Adjust minimum, if B's min is less than A's min */
needsadj = FunctionCall2Coll(minmax_get_procinfo(bdesc, attno,
PROCNUM_LESS),
colloid, col_b->bv_values[0], col_a->bv_values[0]);
finfo = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
BTLessStrategyNumber);
needsadj = FunctionCall2Coll(finfo, colloid, col_b->bv_values[0],
col_a->bv_values[0]);
if (needsadj)
{
if (!attr->attbyval)
......@@ -296,9 +285,10 @@ brin_minmax_union(PG_FUNCTION_ARGS)
}
/* Adjust maximum, if B's max is greater than A's max */
needsadj = FunctionCall2Coll(minmax_get_procinfo(bdesc, attno,
PROCNUM_GREATER),
colloid, col_b->bv_values[1], col_a->bv_values[1]);
finfo = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid,
BTGreaterStrategyNumber);
needsadj = FunctionCall2Coll(finfo, colloid, col_b->bv_values[1],
col_a->bv_values[1]);
if (needsadj)
{
if (!attr->attbyval)
......@@ -311,27 +301,61 @@ brin_minmax_union(PG_FUNCTION_ARGS)
}
/*
* Return the procedure corresponding to the given function support number.
* Cache and return the procedure for the given strategy.
*/
static FmgrInfo *
minmax_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum)
FmgrInfo *
minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype,
uint16 strategynum)
{
MinmaxOpaque *opaque;
uint16 basenum = procnum - PROCNUM_BASE;
Assert(strategynum >= 1 &&
strategynum <= BTMaxStrategyNumber);
opaque = (MinmaxOpaque *) bdesc->bd_info[attno - 1]->oi_opaque;
/*
* We cache these in the opaque struct, to avoid repetitive syscache
* lookups.
* We cache the procedures for the previous subtype in the opaque struct,
* to avoid repetitive syscache lookups. If the subtype changed,
* invalidate all the cached entries.
*/
if (!opaque->inited[basenum])
if (opaque->cached_subtype != subtype)
{
uint16 i;
for (i = 1; i <= BTMaxStrategyNumber; i++)
opaque->strategy_procinfos[i - 1].fn_oid = InvalidOid;
opaque->cached_subtype = subtype;
}
if (opaque->strategy_procinfos[strategynum - 1].fn_oid == InvalidOid)
{
fmgr_info_copy(&opaque->operators[basenum],
index_getprocinfo(bdesc->bd_index, attno, procnum),
Form_pg_attribute attr;
HeapTuple tuple;
Oid opfamily,
oprid;
bool isNull;
opfamily = bdesc->bd_index->rd_opfamily[attno - 1];
attr = bdesc->bd_tupdesc->attrs[attno - 1];
tuple = SearchSysCache4(AMOPSTRATEGY, ObjectIdGetDatum(opfamily),
ObjectIdGetDatum(attr->atttypid),
ObjectIdGetDatum(subtype),
Int16GetDatum(strategynum));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
strategynum, attr->atttypid, subtype, opfamily);
oprid = DatumGetObjectId(SysCacheGetAttr(AMOPSTRATEGY, tuple,
Anum_pg_amop_amopopr, &isNull));
ReleaseSysCache(tuple);
Assert(!isNull && RegProcedureIsValid(oprid));
fmgr_info_cxt(get_opcode(oprid),
&opaque->strategy_procinfos[strategynum - 1],
bdesc->bd_context);
opaque->inited[basenum] = true;
}
return &opaque->operators[basenum];
return &opaque->strategy_procinfos[strategynum - 1];
}
......@@ -68,7 +68,7 @@ brtuple_disk_tupdesc(BrinDesc *brdesc)
{
for (j = 0; j < brdesc->bd_info[i]->oi_nstored; j++)
TupleDescInitEntry(tupdesc, attno++, NULL,
brdesc->bd_info[i]->oi_typids[j],
brdesc->bd_info[i]->oi_typcache[j]->type_id,
-1, 0);
}
......@@ -444,8 +444,8 @@ brin_deform_tuple(BrinDesc *brdesc, BrinTuple *tuple)
for (i = 0; i < brdesc->bd_info[keyno]->oi_nstored; i++)
dtup->bt_columns[keyno].bv_values[i] =
datumCopy(values[valueno++],
brdesc->bd_tupdesc->attrs[keyno]->attbyval,
brdesc->bd_tupdesc->attrs[keyno]->attlen);
brdesc->bd_info[keyno]->oi_typcache[i]->typbyval,
brdesc->bd_info[keyno]->oi_typcache[i]->typlen);
dtup->bt_columns[keyno].bv_hasnulls = hasnulls[keyno];
dtup->bt_columns[keyno].bv_allnulls = false;
......
......@@ -16,6 +16,7 @@
#include "storage/bufpage.h"
#include "storage/off.h"
#include "utils/relcache.h"
#include "utils/typcache.h"
/*
......@@ -32,13 +33,13 @@ typedef struct BrinOpcInfo
/* Opaque pointer for the opclass' private use */
void *oi_opaque;
/* Type IDs of the stored columns */
Oid oi_typids[FLEXIBLE_ARRAY_MEMBER];
/* Type cache entries of the stored columns */
TypeCacheEntry *oi_typcache[FLEXIBLE_ARRAY_MEMBER];
} BrinOpcInfo;
/* the size of a BrinOpcInfo for the given number of columns */
#define SizeofBrinOpcInfo(ncols) \
(offsetof(BrinOpcInfo, oi_typids) + sizeof(Oid) * ncols)
(offsetof(BrinOpcInfo, oi_typcache) + sizeof(TypeCacheEntry *) * ncols)
typedef struct BrinDesc
{
......
......@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201505051
#define CATALOG_VERSION_NO 201505071
#endif
This diff is collapsed.
......@@ -238,34 +238,34 @@ DATA(insert ( 2742 jsonb_path_ops PGNSP PGUID 4037 3802 f 23 ));
/* BRIN operator classes */
/* no brin opclass for bool */
DATA(insert ( 3580 bytea_minmax_ops PGNSP PGUID 4064 17 t 0 ));
DATA(insert ( 3580 char_minmax_ops PGNSP PGUID 4062 18 t 0 ));
DATA(insert ( 3580 name_minmax_ops PGNSP PGUID 4065 19 t 0 ));
DATA(insert ( 3580 int8_minmax_ops PGNSP PGUID 4054 20 t 0 ));
DATA(insert ( 3580 int2_minmax_ops PGNSP PGUID 4054 21 t 0 ));
DATA(insert ( 3580 int4_minmax_ops PGNSP PGUID 4054 23 t 0 ));
DATA(insert ( 3580 text_minmax_ops PGNSP PGUID 4056 25 t 0 ));
DATA(insert ( 3580 oid_minmax_ops PGNSP PGUID 4068 26 t 0 ));
DATA(insert ( 3580 tid_minmax_ops PGNSP PGUID 4069 27 t 0 ));
DATA(insert ( 3580 float4_minmax_ops PGNSP PGUID 4070 700 t 0 ));
DATA(insert ( 3580 float8_minmax_ops PGNSP PGUID 4070 701 t 0 ));
DATA(insert ( 3580 abstime_minmax_ops PGNSP PGUID 4072 702 t 0 ));
DATA(insert ( 3580 reltime_minmax_ops PGNSP PGUID 4073 703 t 0 ));
DATA(insert ( 3580 macaddr_minmax_ops PGNSP PGUID 4074 829 t 0 ));
DATA(insert ( 3580 inet_minmax_ops PGNSP PGUID 4075 869 f 0 ));
DATA(insert ( 3580 bpchar_minmax_ops PGNSP PGUID 4076 1042 t 0 ));
DATA(insert ( 3580 time_minmax_ops PGNSP PGUID 4077 1083 t 0 ));
DATA(insert ( 3580 date_minmax_ops PGNSP PGUID 4059 1082 t 0 ));
DATA(insert ( 3580 timestamp_minmax_ops PGNSP PGUID 4059 1114 t 0 ));
DATA(insert ( 3580 timestamptz_minmax_ops PGNSP PGUID 4059 1184 t 0 ));
DATA(insert ( 3580 interval_minmax_ops PGNSP PGUID 4078 1186 t 0 ));
DATA(insert ( 3580 timetz_minmax_ops PGNSP PGUID 4058 1266 t 0 ));
DATA(insert ( 3580 bit_minmax_ops PGNSP PGUID 4079 1560 t 0 ));
DATA(insert ( 3580 varbit_minmax_ops PGNSP PGUID 4080 1562 t 0 ));
DATA(insert ( 3580 numeric_minmax_ops PGNSP PGUID 4055 1700 t 0 ));
DATA(insert ( 3580 bytea_minmax_ops PGNSP PGUID 4064 17 t 17 ));
DATA(insert ( 3580 char_minmax_ops PGNSP PGUID 4062 18 t 18 ));
DATA(insert ( 3580 name_minmax_ops PGNSP PGUID 4065 19 t 19 ));
DATA(insert ( 3580 int8_minmax_ops PGNSP PGUID 4054 20 t 20 ));
DATA(insert ( 3580 int2_minmax_ops PGNSP PGUID 4054 21 t 21 ));
DATA(insert ( 3580 int4_minmax_ops PGNSP PGUID 4054 23 t 23 ));
DATA(insert ( 3580 text_minmax_ops PGNSP PGUID 4056 25 t 25 ));
DATA(insert ( 3580 oid_minmax_ops PGNSP PGUID 4068 26 t 26 ));
DATA(insert ( 3580 tid_minmax_ops PGNSP PGUID 4069 27 t 27 ));
DATA(insert ( 3580 float4_minmax_ops PGNSP PGUID 4070 700 t 700 ));
DATA(insert ( 3580 float8_minmax_ops PGNSP PGUID 4070 701 t 701 ));
DATA(insert ( 3580 abstime_minmax_ops PGNSP PGUID 4072 702 t 702 ));
DATA(insert ( 3580 reltime_minmax_ops PGNSP PGUID 4073 703 t 703 ));
DATA(insert ( 3580 macaddr_minmax_ops PGNSP PGUID 4074 829 t 829 ));
DATA(insert ( 3580 inet_minmax_ops PGNSP PGUID 4075 869 f 869 ));
DATA(insert ( 3580 bpchar_minmax_ops PGNSP PGUID 4076 1042 t 1042 ));
DATA(insert ( 3580 time_minmax_ops PGNSP PGUID 4077 1083 t 1083 ));
DATA(insert ( 3580 date_minmax_ops PGNSP PGUID 4059 1082 t 1082 ));
DATA(insert ( 3580 timestamp_minmax_ops PGNSP PGUID 4059 1114 t 1114 ));
DATA(insert ( 3580 timestamptz_minmax_ops PGNSP PGUID 4059 1184 t 1184 ));
DATA(insert ( 3580 interval_minmax_ops PGNSP PGUID 4078 1186 t 1186 ));
DATA(insert ( 3580 timetz_minmax_ops PGNSP PGUID 4058 1266 t 1266 ));
DATA(insert ( 3580 bit_minmax_ops PGNSP PGUID 4079 1560 t 1560 ));
DATA(insert ( 3580 varbit_minmax_ops PGNSP PGUID 4080 1562 t 1562 ));
DATA(insert ( 3580 numeric_minmax_ops PGNSP PGUID 4055 1700 t 1700 ));
/* no brin opclass for record, anyarray */
DATA(insert ( 3580 uuid_minmax_ops PGNSP PGUID 4081 2950 t 0 ));
DATA(insert ( 3580 pg_lsn_minmax_ops PGNSP PGUID 4082 3220 t 0 ));
DATA(insert ( 3580 uuid_minmax_ops PGNSP PGUID 4081 2950 t 2950 ));
DATA(insert ( 3580 pg_lsn_minmax_ops PGNSP PGUID 4082 3220 t 3220 ));
/* no brin opclass for enum, tsvector, tsquery, jsonb, range */
#endif /* PG_OPCLASS_H */
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment