diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2008-10-13 16:25:20 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2008-10-13 16:25:20 +0000 |
commit | e3b0117459fd24b15cb5e88f563b5d87f051cfdc (patch) | |
tree | acca06e323ff71b1cd964fab08a2cf5fe9ca37ac /src/backend | |
parent | d6dfa1e6c63e01def34dc3fcc5978b0a60ea6ca8 (diff) | |
download | postgresql-e3b0117459fd24b15cb5e88f563b5d87f051cfdc.tar.gz postgresql-e3b0117459fd24b15cb5e88f563b5d87f051cfdc.zip |
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.
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/commands/indexcmds.c | 5 | ||||
-rw-r--r-- | src/backend/parser/parse_coerce.c | 64 | ||||
-rw-r--r-- | src/backend/utils/adt/rowtypes.c | 498 |
3 files changed, 560 insertions, 7 deletions
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index cbf440fc438..c4e548becb7 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.179 2008/08/25 22:42:32 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.180 2008/10/13 16:25:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -795,7 +795,8 @@ ComputeIndexAttrs(IndexInfo *indexInfo, atttype = attform->atttypid; ReleaseSysCache(atttuple); } - else if (attribute->expr && IsA(attribute->expr, Var)) + else if (attribute->expr && IsA(attribute->expr, Var) && + ((Var *) attribute->expr)->varattno != InvalidAttrNumber) { /* Tricky tricky, he wrote (column) ... treat as simple attr */ Var *var = (Var *) attribute->expr; diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index 69efaddfecf..7c66c638047 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.168 2008/10/06 17:39:26 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.169 2008/10/13 16:25:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -46,6 +46,7 @@ static Node *coerce_record_to_complex(ParseState *pstate, Node *node, CoercionContext ccontext, CoercionForm cformat, int location); +static bool is_complex_array(Oid typid); /* @@ -402,6 +403,21 @@ coerce_type(ParseState *pstate, Node *node, /* NB: we do NOT want a RelabelType here */ return node; } +#ifdef NOT_USED + if (inputTypeId == RECORDARRAYOID && + is_complex_array(targetTypeId)) + { + /* Coerce record[] to a specific complex array type */ + /* not implemented yet ... */ + } +#endif + if (targetTypeId == RECORDARRAYOID && + is_complex_array(inputTypeId)) + { + /* Coerce a specific complex array type to record[] */ + /* NB: we do NOT want a RelabelType here */ + return node; + } if (typeInheritsFrom(inputTypeId, targetTypeId)) { /* @@ -492,6 +508,23 @@ can_coerce_type(int nargs, Oid *input_typeids, Oid *target_typeids, ISCOMPLEX(inputTypeId)) continue; +#ifdef NOT_USED /* not implemented yet */ + /* + * If input is record[] and target is a composite array type, + * assume we can coerce (may need tighter checking here) + */ + if (inputTypeId == RECORDARRAYOID && + is_complex_array(targetTypeId)) + continue; +#endif + + /* + * If input is a composite array type and target is record[], accept + */ + if (targetTypeId == RECORDARRAYOID && + is_complex_array(inputTypeId)) + continue; + /* * If input is a class type that inherits from target, accept */ @@ -1724,8 +1757,8 @@ IsPreferredType(TYPCATEGORY category, Oid type) * invokable, no-function-needed pg_cast entry. Also, a domain is always * binary-coercible to its base type, though *not* vice versa (in the other * direction, one must apply domain constraint checks before accepting the - * value as legitimate). We also need to special-case the polymorphic - * ANYARRAY type. + * value as legitimate). We also need to special-case various polymorphic + * types. * * This function replaces IsBinaryCompatible(), which was an inherently * symmetric test. Since the pg_cast entries aren't necessarily symmetric, @@ -1765,6 +1798,16 @@ IsBinaryCoercible(Oid srctype, Oid targettype) if (type_is_enum(srctype)) return true; + /* Also accept any composite type as coercible to RECORD */ + if (targettype == RECORDOID) + if (ISCOMPLEX(srctype)) + return true; + + /* Also accept any composite array type as coercible to RECORD[] */ + if (targettype == RECORDARRAYOID) + if (is_complex_array(srctype)) + return true; + /* Else look in pg_cast */ tuple = SearchSysCache(CASTSOURCETARGET, ObjectIdGetDatum(srctype), @@ -2002,3 +2045,18 @@ find_typmod_coercion_function(Oid typeId, return result; } + +/* + * is_complex_array + * Is this type an array of composite? + * + * Note: this will not return true for record[]; check for RECORDARRAYOID + * separately if needed. + */ +static bool +is_complex_array(Oid typid) +{ + Oid elemtype = get_element_type(typid); + + return (OidIsValid(elemtype) && ISCOMPLEX(elemtype)); +} diff --git a/src/backend/utils/adt/rowtypes.c b/src/backend/utils/adt/rowtypes.c index b7981660ef2..f08244216e8 100644 --- a/src/backend/utils/adt/rowtypes.c +++ b/src/backend/utils/adt/rowtypes.c @@ -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)); +} |