mirror of
https://github.com/postgres/postgres.git
synced 2025-11-29 23:43:17 +03:00
Implement comparison of generic records (composite types), and invent a
pseudo-type record[] to represent arrays of possibly-anonymous composite types. Since composite datums carry their own type identification, no extra knowledge is needed at the array level. The main reason for doing this right now is that it is necessary to support the general case of detection of cycles in recursive queries: if you need to compare more than one column to detect a cycle, you need to compare a ROW() to an array built from ROW()s, at least if you want to do it as the spec suggests. Add some documentation and regression tests concerning the cycle detection issue.
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* rowtypes.c
|
||||
* I/O functions for generic composite types.
|
||||
* I/O and comparison functions for generic composite types.
|
||||
*
|
||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/rowtypes.c,v 1.21 2008/05/12 00:00:51 alvherre Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/rowtypes.c,v 1.22 2008/10/13 16:25:19 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -42,6 +42,24 @@ typedef struct RecordIOData
|
||||
ColumnIOData columns[1]; /* VARIABLE LENGTH ARRAY */
|
||||
} RecordIOData;
|
||||
|
||||
/*
|
||||
* structure to cache metadata needed for record comparison
|
||||
*/
|
||||
typedef struct ColumnCompareData
|
||||
{
|
||||
TypeCacheEntry *typentry; /* has everything we need, actually */
|
||||
} ColumnCompareData;
|
||||
|
||||
typedef struct RecordCompareData
|
||||
{
|
||||
int ncolumns; /* allocated length of columns[] */
|
||||
Oid record1_type;
|
||||
int32 record1_typmod;
|
||||
Oid record2_type;
|
||||
int32 record2_typmod;
|
||||
ColumnCompareData columns[1]; /* VARIABLE LENGTH ARRAY */
|
||||
} RecordCompareData;
|
||||
|
||||
|
||||
/*
|
||||
* record_in - input routine for any composite type.
|
||||
@@ -734,3 +752,479 @@ record_send(PG_FUNCTION_ARGS)
|
||||
|
||||
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* record_cmp()
|
||||
* Internal comparison function for records.
|
||||
*
|
||||
* Returns -1, 0 or 1
|
||||
*
|
||||
* Do not assume that the two inputs are exactly the same record type;
|
||||
* for instance we might be comparing an anonymous ROW() construct against a
|
||||
* named composite type. We will compare as long as they have the same number
|
||||
* of non-dropped columns of the same types.
|
||||
*/
|
||||
static int
|
||||
record_cmp(FunctionCallInfo fcinfo)
|
||||
{
|
||||
HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
|
||||
HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
|
||||
int result = 0;
|
||||
Oid tupType1;
|
||||
Oid tupType2;
|
||||
int32 tupTypmod1;
|
||||
int32 tupTypmod2;
|
||||
TupleDesc tupdesc1;
|
||||
TupleDesc tupdesc2;
|
||||
HeapTupleData tuple1;
|
||||
HeapTupleData tuple2;
|
||||
int ncolumns1;
|
||||
int ncolumns2;
|
||||
RecordCompareData *my_extra;
|
||||
int ncols;
|
||||
Datum *values1;
|
||||
Datum *values2;
|
||||
bool *nulls1;
|
||||
bool *nulls2;
|
||||
int i1;
|
||||
int i2;
|
||||
int j;
|
||||
|
||||
/* Extract type info from the tuples */
|
||||
tupType1 = HeapTupleHeaderGetTypeId(record1);
|
||||
tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
|
||||
tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
|
||||
ncolumns1 = tupdesc1->natts;
|
||||
tupType2 = HeapTupleHeaderGetTypeId(record2);
|
||||
tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
|
||||
tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
|
||||
ncolumns2 = tupdesc2->natts;
|
||||
|
||||
/* Build temporary HeapTuple control structures */
|
||||
tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
|
||||
ItemPointerSetInvalid(&(tuple1.t_self));
|
||||
tuple1.t_tableOid = InvalidOid;
|
||||
tuple1.t_data = record1;
|
||||
tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
|
||||
ItemPointerSetInvalid(&(tuple2.t_self));
|
||||
tuple2.t_tableOid = InvalidOid;
|
||||
tuple2.t_data = record2;
|
||||
|
||||
/*
|
||||
* We arrange to look up the needed comparison info just once per series
|
||||
* of calls, assuming the record types don't change underneath us.
|
||||
*/
|
||||
ncols = Max(ncolumns1, ncolumns2);
|
||||
my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
|
||||
if (my_extra == NULL ||
|
||||
my_extra->ncolumns < ncols)
|
||||
{
|
||||
fcinfo->flinfo->fn_extra =
|
||||
MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
|
||||
sizeof(RecordCompareData) - sizeof(ColumnCompareData)
|
||||
+ ncols * sizeof(ColumnCompareData));
|
||||
my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
|
||||
my_extra->ncolumns = ncols;
|
||||
my_extra->record1_type = InvalidOid;
|
||||
my_extra->record1_typmod = 0;
|
||||
my_extra->record2_type = InvalidOid;
|
||||
my_extra->record2_typmod = 0;
|
||||
}
|
||||
|
||||
if (my_extra->record1_type != tupType1 ||
|
||||
my_extra->record1_typmod != tupTypmod1 ||
|
||||
my_extra->record2_type != tupType2 ||
|
||||
my_extra->record2_typmod != tupTypmod2)
|
||||
{
|
||||
MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
|
||||
my_extra->record1_type = tupType1;
|
||||
my_extra->record1_typmod = tupTypmod1;
|
||||
my_extra->record2_type = tupType2;
|
||||
my_extra->record2_typmod = tupTypmod2;
|
||||
}
|
||||
|
||||
/* Break down the tuples into fields */
|
||||
values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
|
||||
nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
|
||||
heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
|
||||
values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
|
||||
nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
|
||||
heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
|
||||
|
||||
/*
|
||||
* Scan corresponding columns, allowing for dropped columns in different
|
||||
* places in the two rows. i1 and i2 are physical column indexes,
|
||||
* j is the logical column index.
|
||||
*/
|
||||
i1 = i2 = j = 0;
|
||||
while (i1 < ncolumns1 || i2 < ncolumns2)
|
||||
{
|
||||
TypeCacheEntry *typentry;
|
||||
FunctionCallInfoData locfcinfo;
|
||||
int32 cmpresult;
|
||||
|
||||
/*
|
||||
* Skip dropped columns
|
||||
*/
|
||||
if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
|
||||
{
|
||||
i1++;
|
||||
continue;
|
||||
}
|
||||
if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
|
||||
{
|
||||
i2++;
|
||||
continue;
|
||||
}
|
||||
if (i1 >= ncolumns1 || i2 >= ncolumns2)
|
||||
break; /* we'll deal with mismatch below loop */
|
||||
|
||||
/*
|
||||
* Have two matching columns, they must be same type
|
||||
*/
|
||||
if (tupdesc1->attrs[i1]->atttypid !=
|
||||
tupdesc2->attrs[i2]->atttypid)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("cannot compare dissimilar column types %s and %s at record column %d",
|
||||
format_type_be(tupdesc1->attrs[i1]->atttypid),
|
||||
format_type_be(tupdesc2->attrs[i2]->atttypid),
|
||||
j+1)));
|
||||
|
||||
/*
|
||||
* Lookup the comparison function if not done already
|
||||
*/
|
||||
typentry = my_extra->columns[j].typentry;
|
||||
if (typentry == NULL ||
|
||||
typentry->type_id != tupdesc1->attrs[i1]->atttypid)
|
||||
{
|
||||
typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid,
|
||||
TYPECACHE_CMP_PROC_FINFO);
|
||||
if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_FUNCTION),
|
||||
errmsg("could not identify a comparison function for type %s",
|
||||
format_type_be(typentry->type_id))));
|
||||
my_extra->columns[j].typentry = typentry;
|
||||
}
|
||||
|
||||
/*
|
||||
* We consider two NULLs equal; NULL > not-NULL.
|
||||
*/
|
||||
if (!nulls1[i1] || !nulls2[i2])
|
||||
{
|
||||
if (nulls1[i1])
|
||||
{
|
||||
/* arg1 is greater than arg2 */
|
||||
result = 1;
|
||||
break;
|
||||
}
|
||||
if (nulls2[i2])
|
||||
{
|
||||
/* arg1 is less than arg2 */
|
||||
result = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Compare the pair of elements */
|
||||
InitFunctionCallInfoData(locfcinfo, &typentry->cmp_proc_finfo, 2,
|
||||
NULL, NULL);
|
||||
locfcinfo.arg[0] = values1[i1];
|
||||
locfcinfo.arg[1] = values2[i2];
|
||||
locfcinfo.argnull[0] = false;
|
||||
locfcinfo.argnull[1] = false;
|
||||
locfcinfo.isnull = false;
|
||||
cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo));
|
||||
|
||||
if (cmpresult < 0)
|
||||
{
|
||||
/* arg1 is less than arg2 */
|
||||
result = -1;
|
||||
break;
|
||||
}
|
||||
else if (cmpresult > 0)
|
||||
{
|
||||
/* arg1 is greater than arg2 */
|
||||
result = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* equal, so continue to next column */
|
||||
i1++, i2++, j++;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we didn't break out of the loop early, check for column count
|
||||
* mismatch. (We do not report such mismatch if we found unequal
|
||||
* column values; is that a feature or a bug?)
|
||||
*/
|
||||
if (result == 0)
|
||||
{
|
||||
if (i1 != ncolumns1 || i2 != ncolumns2)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("cannot compare record types with different numbers of columns")));
|
||||
}
|
||||
|
||||
pfree(values1);
|
||||
pfree(nulls1);
|
||||
pfree(values2);
|
||||
pfree(nulls2);
|
||||
ReleaseTupleDesc(tupdesc1);
|
||||
ReleaseTupleDesc(tupdesc2);
|
||||
|
||||
/* Avoid leaking memory when handed toasted input. */
|
||||
PG_FREE_IF_COPY(record1, 0);
|
||||
PG_FREE_IF_COPY(record2, 1);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* record_eq :
|
||||
* compares two records for equality
|
||||
* result :
|
||||
* returns true if the records are equal, false otherwise.
|
||||
*
|
||||
* Note: we do not use record_cmp here, since equality may be meaningful in
|
||||
* datatypes that don't have a total ordering (and hence no btree support).
|
||||
*/
|
||||
Datum
|
||||
record_eq(PG_FUNCTION_ARGS)
|
||||
{
|
||||
HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
|
||||
HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
|
||||
bool result = true;
|
||||
Oid tupType1;
|
||||
Oid tupType2;
|
||||
int32 tupTypmod1;
|
||||
int32 tupTypmod2;
|
||||
TupleDesc tupdesc1;
|
||||
TupleDesc tupdesc2;
|
||||
HeapTupleData tuple1;
|
||||
HeapTupleData tuple2;
|
||||
int ncolumns1;
|
||||
int ncolumns2;
|
||||
RecordCompareData *my_extra;
|
||||
int ncols;
|
||||
Datum *values1;
|
||||
Datum *values2;
|
||||
bool *nulls1;
|
||||
bool *nulls2;
|
||||
int i1;
|
||||
int i2;
|
||||
int j;
|
||||
|
||||
/* Extract type info from the tuples */
|
||||
tupType1 = HeapTupleHeaderGetTypeId(record1);
|
||||
tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
|
||||
tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
|
||||
ncolumns1 = tupdesc1->natts;
|
||||
tupType2 = HeapTupleHeaderGetTypeId(record2);
|
||||
tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
|
||||
tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
|
||||
ncolumns2 = tupdesc2->natts;
|
||||
|
||||
/* Build temporary HeapTuple control structures */
|
||||
tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
|
||||
ItemPointerSetInvalid(&(tuple1.t_self));
|
||||
tuple1.t_tableOid = InvalidOid;
|
||||
tuple1.t_data = record1;
|
||||
tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
|
||||
ItemPointerSetInvalid(&(tuple2.t_self));
|
||||
tuple2.t_tableOid = InvalidOid;
|
||||
tuple2.t_data = record2;
|
||||
|
||||
/*
|
||||
* We arrange to look up the needed comparison info just once per series
|
||||
* of calls, assuming the record types don't change underneath us.
|
||||
*/
|
||||
ncols = Max(ncolumns1, ncolumns2);
|
||||
my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
|
||||
if (my_extra == NULL ||
|
||||
my_extra->ncolumns < ncols)
|
||||
{
|
||||
fcinfo->flinfo->fn_extra =
|
||||
MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
|
||||
sizeof(RecordCompareData) - sizeof(ColumnCompareData)
|
||||
+ ncols * sizeof(ColumnCompareData));
|
||||
my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
|
||||
my_extra->ncolumns = ncols;
|
||||
my_extra->record1_type = InvalidOid;
|
||||
my_extra->record1_typmod = 0;
|
||||
my_extra->record2_type = InvalidOid;
|
||||
my_extra->record2_typmod = 0;
|
||||
}
|
||||
|
||||
if (my_extra->record1_type != tupType1 ||
|
||||
my_extra->record1_typmod != tupTypmod1 ||
|
||||
my_extra->record2_type != tupType2 ||
|
||||
my_extra->record2_typmod != tupTypmod2)
|
||||
{
|
||||
MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
|
||||
my_extra->record1_type = tupType1;
|
||||
my_extra->record1_typmod = tupTypmod1;
|
||||
my_extra->record2_type = tupType2;
|
||||
my_extra->record2_typmod = tupTypmod2;
|
||||
}
|
||||
|
||||
/* Break down the tuples into fields */
|
||||
values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
|
||||
nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
|
||||
heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
|
||||
values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
|
||||
nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
|
||||
heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
|
||||
|
||||
/*
|
||||
* Scan corresponding columns, allowing for dropped columns in different
|
||||
* places in the two rows. i1 and i2 are physical column indexes,
|
||||
* j is the logical column index.
|
||||
*/
|
||||
i1 = i2 = j = 0;
|
||||
while (i1 < ncolumns1 || i2 < ncolumns2)
|
||||
{
|
||||
TypeCacheEntry *typentry;
|
||||
FunctionCallInfoData locfcinfo;
|
||||
bool oprresult;
|
||||
|
||||
/*
|
||||
* Skip dropped columns
|
||||
*/
|
||||
if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
|
||||
{
|
||||
i1++;
|
||||
continue;
|
||||
}
|
||||
if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
|
||||
{
|
||||
i2++;
|
||||
continue;
|
||||
}
|
||||
if (i1 >= ncolumns1 || i2 >= ncolumns2)
|
||||
break; /* we'll deal with mismatch below loop */
|
||||
|
||||
/*
|
||||
* Have two matching columns, they must be same type
|
||||
*/
|
||||
if (tupdesc1->attrs[i1]->atttypid !=
|
||||
tupdesc2->attrs[i2]->atttypid)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("cannot compare dissimilar column types %s and %s at record column %d",
|
||||
format_type_be(tupdesc1->attrs[i1]->atttypid),
|
||||
format_type_be(tupdesc2->attrs[i2]->atttypid),
|
||||
j+1)));
|
||||
|
||||
/*
|
||||
* Lookup the equality function if not done already
|
||||
*/
|
||||
typentry = my_extra->columns[j].typentry;
|
||||
if (typentry == NULL ||
|
||||
typentry->type_id != tupdesc1->attrs[i1]->atttypid)
|
||||
{
|
||||
typentry = lookup_type_cache(tupdesc1->attrs[i1]->atttypid,
|
||||
TYPECACHE_EQ_OPR_FINFO);
|
||||
if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_FUNCTION),
|
||||
errmsg("could not identify an equality operator for type %s",
|
||||
format_type_be(typentry->type_id))));
|
||||
my_extra->columns[j].typentry = typentry;
|
||||
}
|
||||
|
||||
/*
|
||||
* We consider two NULLs equal; NULL > not-NULL.
|
||||
*/
|
||||
if (!nulls1[i1] || !nulls2[i2])
|
||||
{
|
||||
if (nulls1[i1] || nulls2[i2])
|
||||
{
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Compare the pair of elements */
|
||||
InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2,
|
||||
NULL, NULL);
|
||||
locfcinfo.arg[0] = values1[i1];
|
||||
locfcinfo.arg[1] = values2[i2];
|
||||
locfcinfo.argnull[0] = false;
|
||||
locfcinfo.argnull[1] = false;
|
||||
locfcinfo.isnull = false;
|
||||
oprresult = DatumGetBool(FunctionCallInvoke(&locfcinfo));
|
||||
if (!oprresult)
|
||||
{
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* equal, so continue to next column */
|
||||
i1++, i2++, j++;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we didn't break out of the loop early, check for column count
|
||||
* mismatch. (We do not report such mismatch if we found unequal
|
||||
* column values; is that a feature or a bug?)
|
||||
*/
|
||||
if (result)
|
||||
{
|
||||
if (i1 != ncolumns1 || i2 != ncolumns2)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("cannot compare record types with different numbers of columns")));
|
||||
}
|
||||
|
||||
pfree(values1);
|
||||
pfree(nulls1);
|
||||
pfree(values2);
|
||||
pfree(nulls2);
|
||||
ReleaseTupleDesc(tupdesc1);
|
||||
ReleaseTupleDesc(tupdesc2);
|
||||
|
||||
/* Avoid leaking memory when handed toasted input. */
|
||||
PG_FREE_IF_COPY(record1, 0);
|
||||
PG_FREE_IF_COPY(record2, 1);
|
||||
|
||||
PG_RETURN_BOOL(result);
|
||||
}
|
||||
|
||||
Datum
|
||||
record_ne(PG_FUNCTION_ARGS)
|
||||
{
|
||||
PG_RETURN_BOOL(!DatumGetBool(record_eq(fcinfo)));
|
||||
}
|
||||
|
||||
Datum
|
||||
record_lt(PG_FUNCTION_ARGS)
|
||||
{
|
||||
PG_RETURN_BOOL(record_cmp(fcinfo) < 0);
|
||||
}
|
||||
|
||||
Datum
|
||||
record_gt(PG_FUNCTION_ARGS)
|
||||
{
|
||||
PG_RETURN_BOOL(record_cmp(fcinfo) > 0);
|
||||
}
|
||||
|
||||
Datum
|
||||
record_le(PG_FUNCTION_ARGS)
|
||||
{
|
||||
PG_RETURN_BOOL(record_cmp(fcinfo) <= 0);
|
||||
}
|
||||
|
||||
Datum
|
||||
record_ge(PG_FUNCTION_ARGS)
|
||||
{
|
||||
PG_RETURN_BOOL(record_cmp(fcinfo) >= 0);
|
||||
}
|
||||
|
||||
Datum
|
||||
btrecordcmp(PG_FUNCTION_ARGS)
|
||||
{
|
||||
PG_RETURN_INT32(record_cmp(fcinfo));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user