| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /*------------------------------------------------------------------------- |
| * |
| * lsyscache.c |
| * Convenience routines for common queries in the system catalog cache. |
| * |
| * Portions Copyright (c) 2007-2009, Greenplum inc |
| * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * IDENTIFICATION |
| * $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.138.2.1 2007/03/19 16:30:40 tgl Exp $ |
| * |
| * NOTES |
| * Eventually, the index information should go through here, too. |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include "access/hash.h" |
| #include "access/genam.h" |
| #include "bootstrap/bootstrap.h" |
| #include "catalog/catquery.h" |
| #include "catalog/heap.h" /* SystemAttributeDefinition() */ |
| #include "catalog/pg_authid.h" |
| #include "catalog/pg_amop.h" |
| #include "catalog/pg_amproc.h" |
| #include "catalog/pg_namespace.h" |
| #include "catalog/pg_opclass.h" |
| #include "catalog/pg_operator.h" |
| #include "catalog/pg_proc.h" |
| #include "catalog/pg_aggregate.h" |
| #include "catalog/pg_constraint.h" |
| #include "catalog/pg_statistic.h" |
| #include "catalog/pg_trigger.h" |
| #include "catalog/pg_type.h" |
| #include "cdb/cdbpartition.h" |
| #include "commands/tablecmds.h" |
| #include "commands/trigger.h" |
| #include "miscadmin.h" |
| #include "nodes/makefuncs.h" |
| #include "parser/parse_clause.h" /* for sort_op_can_sort() */ |
| #include "parser/parse_coerce.h" |
| #include "utils/array.h" |
| #include "utils/builtins.h" |
| #include "utils/datum.h" |
| #include "utils/lsyscache.h" |
| #include "utils/syscache.h" |
| #include "utils/fmgroids.h" |
| #include "funcapi.h" |
| |
| static Datum |
| attstatsslot_getattr(cqContext *pcqCtx, |
| TupleDesc tupdesc, |
| HeapTuple statstuple, |
| AttrNumber attnum, bool *isnull); |
| |
| |
| |
| /* ---------- AMOP CACHES ---------- */ |
| |
| /* |
| * op_in_opclass |
| * |
| * Return t iff operator 'opno' is in operator class 'opclass'. |
| */ |
| bool |
| op_in_opclass(Oid opno, Oid opclass) |
| { |
| return (caql_getcount( |
| NULL, |
| cql("SELECT COUNT(*) FROM pg_amop " |
| " WHERE amopopr = :1 " |
| " AND amopclaid = :2 ", |
| ObjectIdGetDatum(opno), |
| ObjectIdGetDatum(opclass))) > 0); |
| } |
| |
| /* |
| * get_op_opclass_strategy |
| * |
| * Get the operator's strategy number within the specified opclass, |
| * or 0 if it's not a member of the opclass. |
| */ |
| int |
| get_op_opclass_strategy(Oid opno, Oid opclass) |
| { |
| HeapTuple tp; |
| Form_pg_amop amop_tup; |
| int result; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_amop " |
| " WHERE amopopr = :1 " |
| " AND amopclaid = :2 ", |
| ObjectIdGetDatum(opno), |
| ObjectIdGetDatum(opclass))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(tp)) |
| return 0; |
| amop_tup = (Form_pg_amop) GETSTRUCT(tp); |
| result = amop_tup->amopstrategy; |
| |
| caql_endscan(pcqCtx); |
| |
| return result; |
| } |
| |
| /* |
| * get_op_opclass_properties |
| * |
| * Get the operator's strategy number, subtype, and recheck (lossy) flag |
| * within the specified opclass. |
| * |
| * Caller should already have verified that opno is a member of opclass, |
| * therefore we raise an error if the tuple is not found. |
| */ |
| void |
| get_op_opclass_properties(Oid opno, Oid opclass, |
| int *strategy, Oid *subtype, bool *recheck) |
| { |
| HeapTuple tp; |
| Form_pg_amop amop_tup; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_amop " |
| " WHERE amopopr = :1 " |
| " AND amopclaid = :2 ", |
| ObjectIdGetDatum(opno), |
| ObjectIdGetDatum(opclass))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "operator %u is not a member of opclass %u", |
| opno, opclass); |
| amop_tup = (Form_pg_amop) GETSTRUCT(tp); |
| *strategy = amop_tup->amopstrategy; |
| *subtype = amop_tup->amopsubtype; |
| *recheck = amop_tup->amopreqcheck; |
| |
| caql_endscan(pcqCtx); |
| } |
| |
| /* |
| * get_opclass_member |
| * Get the OID of the operator that implements the specified strategy |
| * with the specified subtype for the specified opclass. |
| * |
| * Returns InvalidOid if there is no pg_amop entry for the given keys. |
| */ |
| Oid |
| get_opclass_member(Oid opclass, Oid subtype, int16 strategy) |
| { |
| Oid result; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT amopopr FROM pg_amop " |
| " WHERE amopclaid = :1 " |
| " AND amopsubtype = :2 " |
| " AND amopstrategy = :3 ", |
| ObjectIdGetDatum(opclass), |
| ObjectIdGetDatum(subtype), |
| Int16GetDatum(strategy) |
| )); |
| |
| if (!fetchCount) |
| return InvalidOid; |
| |
| return result; |
| } |
| |
| /* |
| * get_op_hash_function |
| * Get the OID of the datatype-specific hash function associated with |
| * a hashable equality operator. |
| * |
| * Returns InvalidOid if no hash function can be found. (This indicates |
| * that the operator should not have been marked oprcanhash.) |
| */ |
| Oid |
| get_op_hash_function(Oid opno) |
| { |
| CatCList *catlist; |
| int i; |
| Oid opclass = InvalidOid; |
| |
| /* |
| * Search pg_amop to see if the target operator is registered as the "=" |
| * operator of any hash opclass. If the operator is registered in |
| * multiple opclasses, assume we can use the associated hash function from |
| * any one. |
| */ |
| catlist = caql_begin_CacheList( |
| NULL, |
| cql("SELECT * FROM pg_amop " |
| " WHERE amopopr = :1 " |
| " ORDER BY amopopr, " |
| " amopclaid ", |
| ObjectIdGetDatum(opno))); |
| |
| for (i = 0; i < catlist->n_members; i++) |
| { |
| HeapTuple tuple = &catlist->members[i]->tuple; |
| Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple); |
| |
| if (aform->amopstrategy == HTEqualStrategyNumber && |
| opclass_is_hash(aform->amopclaid)) |
| { |
| opclass = aform->amopclaid; |
| break; |
| } |
| } |
| |
| caql_end_CacheList(catlist); |
| |
| if (OidIsValid(opclass)) |
| { |
| /* Found a suitable opclass, get its default hash support function */ |
| return get_opclass_proc(opclass, InvalidOid, HASHPROC); |
| } |
| |
| /* Didn't find a match... */ |
| return InvalidOid; |
| } |
| |
| /* |
| * get_op_btree_interpretation |
| * Given an operator's OID, find out which btree opclasses it belongs to, |
| * and what strategy number it has within each one. The results are |
| * returned as an OID list and a parallel integer list. |
| * |
| * In addition to the normal btree operators, we consider a <> operator to be |
| * a "member" of an opclass if its negator is the opclass' equality operator. |
| * ROWCOMPARE_NE is returned as the strategy number for this case. |
| */ |
| void |
| get_op_btree_interpretation(Oid opno, List **opclasses, List **opstrats) |
| { |
| Oid lefttype, |
| righttype; |
| CatCList *catlist; |
| bool op_negated; |
| int i; |
| |
| *opclasses = NIL; |
| *opstrats = NIL; |
| |
| /* |
| * Get the nominal left-hand input type of the operator; we will ignore |
| * opclasses that don't have that as the expected input datatype. This is |
| * a kluge to avoid being confused by binary-compatible opclasses (such as |
| * text_ops and varchar_ops, which share the same operators). |
| */ |
| op_input_types(opno, &lefttype, &righttype); |
| Assert(OidIsValid(lefttype)); |
| |
| /* |
| * Find all the pg_amop entries containing the operator. |
| */ |
| |
| catlist = caql_begin_CacheList( |
| NULL, |
| cql("SELECT * FROM pg_amop " |
| " WHERE amopopr = :1 " |
| " ORDER BY amopopr, " |
| " amopclaid ", |
| ObjectIdGetDatum(opno))); |
| |
| /* |
| * If we can't find any opclass containing the op, perhaps it is a <> |
| * operator. See if it has a negator that is in an opclass. |
| */ |
| op_negated = false; |
| if (catlist->n_members == 0) |
| { |
| Oid op_negator = get_negator(opno); |
| |
| if (OidIsValid(op_negator)) |
| { |
| op_negated = true; |
| caql_end_CacheList(catlist); |
| |
| catlist = caql_begin_CacheList( |
| NULL, |
| cql("SELECT * FROM pg_amop " |
| " WHERE amopopr = :1 " |
| " ORDER BY amopopr, " |
| " amopclaid ", |
| ObjectIdGetDatum(op_negator))); |
| } |
| } |
| |
| /* Now search the opclasses */ |
| for (i = 0; i < catlist->n_members; i++) |
| { |
| HeapTuple op_tuple = &catlist->members[i]->tuple; |
| Form_pg_amop op_form = (Form_pg_amop) GETSTRUCT(op_tuple); |
| Oid opclass_id; |
| StrategyNumber op_strategy; |
| |
| opclass_id = op_form->amopclaid; |
| |
| /* must be btree */ |
| if (!opclass_is_btree(opclass_id)) |
| continue; |
| |
| /* must match operator input type exactly */ |
| if (get_opclass_input_type(opclass_id) != lefttype) |
| continue; |
| |
| /* Get the operator's btree strategy number */ |
| op_strategy = (StrategyNumber) op_form->amopstrategy; |
| Assert(op_strategy >= 1 && op_strategy <= 5); |
| |
| if (op_negated) |
| { |
| /* Only consider negators that are = */ |
| if (op_strategy != BTEqualStrategyNumber) |
| continue; |
| op_strategy = ROWCOMPARE_NE; |
| } |
| |
| *opclasses = lappend_oid(*opclasses, opclass_id); |
| *opstrats = lappend_int(*opstrats, op_strategy); |
| } |
| |
| caql_end_CacheList(catlist); |
| } |
| |
| |
| /* ---------- AMPROC CACHES ---------- */ |
| |
| /* |
| * get_opclass_proc |
| * Get the OID of the specified support function |
| * for the specified opclass and subtype. |
| * |
| * Returns InvalidOid if there is no pg_amproc entry for the given keys. |
| */ |
| Oid |
| get_opclass_proc(Oid opclass, Oid subtype, int16 procnum) |
| { |
| Oid result; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT amproc FROM pg_amproc " |
| " WHERE amopclaid = :1 " |
| " AND amprocsubtype = :2 " |
| " AND amprocnum = :3 ", |
| ObjectIdGetDatum(opclass), |
| ObjectIdGetDatum(subtype), |
| Int16GetDatum(procnum))); |
| |
| if (!fetchCount) |
| return InvalidOid; |
| |
| return result; |
| } |
| |
| |
| /* ---------- ATTRIBUTE CACHES ---------- */ |
| |
| /* |
| * get_attname |
| * Given the relation id and the attribute number, |
| * return the "attname" field from the attribute relation. |
| * |
| * Note: returns a palloc'd copy of the string, or NULL if no such attribute. |
| */ |
| char * |
| get_attname(Oid relid, AttrNumber attnum) |
| { |
| char *result = NULL; |
| int fetchCount; |
| |
| result = caql_getcstring_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT attname FROM pg_attribute " |
| " WHERE attrelid = :1 " |
| " AND attnum = :2 ", |
| ObjectIdGetDatum(relid), |
| Int16GetDatum(attnum))); |
| |
| if (!fetchCount) |
| return NULL; |
| |
| return result; |
| } |
| |
| /* |
| * get_relid_attribute_name |
| * |
| * Same as above routine get_attname(), except that error |
| * is handled by elog() instead of returning NULL. |
| */ |
| char * |
| get_relid_attribute_name(Oid relid, AttrNumber attnum) |
| { |
| char *attname; |
| |
| attname = get_attname(relid, attnum); |
| if (attname == NULL) |
| elog(ERROR, "cache lookup failed for attribute %d of relation %u", |
| attnum, relid); |
| return attname; |
| } |
| |
| /* |
| * get_attnum |
| * |
| * Given the relation id and the attribute name, |
| * return the "attnum" field from the attribute relation. |
| * |
| * Returns InvalidAttrNumber if the attr doesn't exist (or is dropped). |
| */ |
| AttrNumber |
| get_attnum(Oid relid, const char *attname) |
| { |
| return caql_getattnumber(relid, attname); |
| } |
| |
| /* |
| * get_atttype |
| * |
| * Given the relation OID and the attribute number with the relation, |
| * return the attribute type OID. |
| */ |
| Oid |
| get_atttype(Oid relid, AttrNumber attnum) |
| { |
| Oid result; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT atttypid FROM pg_attribute " |
| " WHERE attrelid = :1 " |
| " AND attnum = :2 ", |
| ObjectIdGetDatum(relid), |
| Int16GetDatum(attnum))); |
| |
| if (!fetchCount) |
| return InvalidOid; |
| |
| return result; |
| |
| } |
| |
| /* |
| * get_atttypmod |
| * |
| * Given the relation id and the attribute number, |
| * return the "atttypmod" field from the attribute relation. |
| */ |
| int32 |
| get_atttypmod(Oid relid, AttrNumber attnum) |
| { |
| HeapTuple tp; |
| cqContext *pcqCtx; |
| int32 result = -1; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_attribute " |
| " WHERE attrelid = :1 " |
| " AND attnum = :2 ", |
| ObjectIdGetDatum(relid), |
| Int16GetDatum(attnum))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (HeapTupleIsValid(tp)) |
| { |
| Form_pg_attribute att_tup = (Form_pg_attribute) GETSTRUCT(tp); |
| |
| result = att_tup->atttypmod; |
| } |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * get_atttypetypmod |
| * |
| * A two-fer: given the relation id and the attribute number, |
| * fetch both type OID and atttypmod in a single cache lookup. |
| * |
| * Unlike the otherwise-similar get_atttype/get_atttypmod, this routine |
| * raises an error if it can't obtain the information. |
| */ |
| void |
| get_atttypetypmod(Oid relid, AttrNumber attnum, |
| Oid *typid, int32 *typmod) |
| { |
| HeapTuple tp; |
| Form_pg_attribute att_tup; |
| cqContext *pcqCtx; |
| |
| /* CDB: Get type for sysattr even if relid is no good (e.g. SubqueryScan) */ |
| if (attnum < 0 && |
| attnum > FirstLowInvalidHeapAttributeNumber) |
| { |
| att_tup = SystemAttributeDefinition(attnum, true); |
| *typid = att_tup->atttypid; |
| *typmod = att_tup->atttypmod; |
| return; |
| } |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_attribute " |
| " WHERE attrelid = :1 " |
| " AND attnum = :2 ", |
| ObjectIdGetDatum(relid), |
| Int16GetDatum(attnum))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "cache lookup failed for attribute %d of relation %u", |
| attnum, relid); |
| att_tup = (Form_pg_attribute) GETSTRUCT(tp); |
| |
| *typid = att_tup->atttypid; |
| *typmod = att_tup->atttypmod; |
| |
| caql_endscan(pcqCtx); |
| } |
| |
| /* ---------- INDEX CACHE ---------- */ |
| |
| /* watch this space... |
| */ |
| |
| /* ---------- OPCLASS CACHE ---------- */ |
| |
| /* |
| * opclass_is_btree |
| * |
| * Returns TRUE iff the specified opclass is associated with the |
| * btree index access method. |
| */ |
| bool |
| opclass_is_btree(Oid opclass) |
| { |
| bool result; |
| Oid amoid; |
| int fetchCount; |
| |
| amoid = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT opcamid FROM pg_opclass " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(opclass))); |
| |
| if (!fetchCount) |
| elog(ERROR, "cache lookup failed for opclass %u", opclass); |
| |
| result = (amoid == BTREE_AM_OID); |
| |
| return result; |
| } |
| |
| /* |
| * opclass_is_hash |
| * |
| * Returns TRUE iff the specified opclass is associated with the |
| * hash index access method. |
| */ |
| bool |
| opclass_is_hash(Oid opclass) |
| { |
| bool result; |
| Oid amoid; |
| int fetchCount; |
| |
| amoid = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT opcamid FROM pg_opclass " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(opclass))); |
| |
| if (!fetchCount) |
| elog(ERROR, "cache lookup failed for opclass %u", opclass); |
| |
| result = (amoid == HASH_AM_OID); |
| |
| return result; |
| } |
| |
| /* |
| * opclass_is_default |
| * |
| * Returns TRUE iff the specified opclass is the default for its |
| * index access method and input data type. |
| */ |
| bool |
| opclass_is_default(Oid opclass) |
| { |
| HeapTuple tp; |
| Form_pg_opclass cla_tup; |
| bool result; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_opclass " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(opclass))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "cache lookup failed for opclass %u", opclass); |
| cla_tup = (Form_pg_opclass) GETSTRUCT(tp); |
| |
| result = cla_tup->opcdefault; |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * get_opclass_input_type |
| * |
| * Returns the OID of the datatype the opclass indexes. |
| */ |
| Oid |
| get_opclass_input_type(Oid opclass) |
| { |
| Oid result; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT opcintype FROM pg_opclass " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(opclass))); |
| |
| if (!fetchCount) |
| elog(ERROR, "cache lookup failed for opclass %u", opclass); |
| |
| return result; |
| } |
| |
| /* ---------- OPERATOR CACHE ---------- */ |
| |
| /* |
| * get_opcode |
| * |
| * Returns the regproc id of the routine used to implement an |
| * operator given the operator oid. |
| */ |
| RegProcedure |
| get_opcode(Oid opno) |
| { |
| Oid result; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT oprcode FROM pg_operator " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(opno))); |
| |
| if (!fetchCount) |
| return (RegProcedure) InvalidOid; |
| |
| return (RegProcedure) result; |
| } |
| |
| /* |
| * get_opname |
| * returns the name of the operator with the given opno |
| * |
| * Note: returns a palloc'd copy of the string, or NULL if no such operator. |
| */ |
| char * |
| get_opname(Oid opno) |
| { |
| char *result = NULL; |
| int fetchCount; |
| |
| result = caql_getcstring_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT oprname FROM pg_operator " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(opno))); |
| |
| if (!fetchCount) |
| return NULL; |
| |
| return result; |
| } |
| |
| /* |
| * op_input_types |
| * |
| * Returns the left and right input datatypes for an operator |
| * (InvalidOid if not relevant). |
| */ |
| void |
| op_input_types(Oid opno, Oid *lefttype, Oid *righttype) |
| { |
| HeapTuple tp; |
| Form_pg_operator optup; |
| cqContext *pcqCtx; |
| |
| *lefttype = InvalidOid; |
| *righttype = InvalidOid; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_operator " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(opno))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (HeapTupleIsValid(tp)) |
| { |
| optup = (Form_pg_operator) GETSTRUCT(tp); |
| *lefttype = optup->oprleft; |
| *righttype = optup->oprright; |
| } |
| caql_endscan(pcqCtx); |
| } |
| |
| /* |
| * op_mergejoinable |
| * |
| * Returns the left and right sort operators corresponding to a |
| * mergejoinable operator, or false if the operator is not mergejoinable. |
| */ |
| bool |
| op_mergejoinable(Oid opno, Oid *leftOp, Oid *rightOp) |
| { |
| HeapTuple tp; |
| bool result = false; |
| cqContext *pcqCtx; |
| |
| if (!sort_op_can_sort(opno, true)) |
| return result; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_operator " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(opno))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (HeapTupleIsValid(tp)) |
| { |
| Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp); |
| |
| if (optup->oprlsortop && |
| optup->oprrsortop) |
| { |
| *leftOp = optup->oprlsortop; |
| *rightOp = optup->oprrsortop; |
| result = true; |
| } |
| } |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * op_mergejoin_crossops |
| * |
| * Returns the cross-type comparison operators (ltype "<" rtype and |
| * ltype ">" rtype) for an operator previously determined to be |
| * mergejoinable. Optionally, fetches the regproc ids of these |
| * operators, as well as their operator OIDs. |
| */ |
| void |
| op_mergejoin_crossops(Oid opno, Oid *ltop, Oid *gtop, |
| RegProcedure *ltproc, RegProcedure *gtproc) |
| { |
| HeapTuple tp; |
| Form_pg_operator optup; |
| cqContext *pcqCtx; |
| |
| /* |
| * Get the declared comparison operators of the operator. |
| */ |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_operator " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(opno))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(tp)) /* shouldn't happen */ |
| elog(ERROR, "cache lookup failed for operator %u", opno); |
| optup = (Form_pg_operator) GETSTRUCT(tp); |
| *ltop = optup->oprltcmpop; |
| *gtop = optup->oprgtcmpop; |
| |
| caql_endscan(pcqCtx); |
| |
| /* Check < op provided */ |
| if (!OidIsValid(*ltop)) |
| elog(ERROR, "mergejoin operator %u has no matching < operator", |
| opno); |
| if (ltproc) |
| *ltproc = get_opcode(*ltop); |
| |
| /* Check > op provided */ |
| if (!OidIsValid(*gtop)) |
| elog(ERROR, "mergejoin operator %u has no matching > operator", |
| opno); |
| if (gtproc) |
| *gtproc = get_opcode(*gtop); |
| } |
| |
| /* |
| * op_hashjoinable |
| * |
| * Returns true if the operator is hashjoinable. |
| */ |
| bool |
| op_hashjoinable(Oid opno) |
| { |
| HeapTuple tp; |
| bool result = false; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_operator " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(opno))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (HeapTupleIsValid(tp)) |
| { |
| Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp); |
| |
| result = optup->oprcanhash; |
| } |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * op_strict |
| * |
| * Get the proisstrict flag for the operator's underlying function. |
| */ |
| bool |
| op_strict(Oid opno) |
| { |
| RegProcedure funcid = get_opcode(opno); |
| |
| if (funcid == (RegProcedure) InvalidOid) |
| elog(ERROR, "operator %u does not exist", opno); |
| |
| return func_strict((Oid) funcid); |
| } |
| |
| /* |
| * op_volatile |
| * |
| * Get the provolatile flag for the operator's underlying function. |
| */ |
| char |
| op_volatile(Oid opno) |
| { |
| RegProcedure funcid = get_opcode(opno); |
| |
| if (funcid == (RegProcedure) InvalidOid) |
| elog(ERROR, "operator %u does not exist", opno); |
| |
| return func_volatile((Oid) funcid); |
| } |
| |
| /* |
| * get_commutator |
| * |
| * Returns the corresponding commutator of an operator. |
| */ |
| Oid |
| get_commutator(Oid opno) |
| { |
| Oid result; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT oprcom FROM pg_operator " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(opno))); |
| |
| if (!fetchCount) |
| return InvalidOid; |
| |
| return result; |
| } |
| |
| /* |
| * get_negator |
| * |
| * Returns the corresponding negator of an operator. |
| */ |
| Oid |
| get_negator(Oid opno) |
| { |
| Oid result; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT oprnegate FROM pg_operator " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(opno))); |
| |
| if (!fetchCount) |
| return InvalidOid; |
| |
| return result; |
| } |
| |
| /* |
| * get_oprrest |
| * |
| * Returns procedure id for computing selectivity of an operator. |
| */ |
| RegProcedure |
| get_oprrest(Oid opno) |
| { |
| Oid result; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT oprrest FROM pg_operator " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(opno))); |
| |
| if (!fetchCount) |
| return (RegProcedure) InvalidOid; |
| |
| return (RegProcedure) result; |
| } |
| |
| /* |
| * get_oprjoin |
| * |
| * Returns procedure id for computing selectivity of a join. |
| */ |
| RegProcedure |
| get_oprjoin(Oid opno) |
| { |
| Oid result; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT oprjoin FROM pg_operator " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(opno))); |
| |
| if (!fetchCount) |
| return (RegProcedure) InvalidOid; |
| |
| return (RegProcedure) result; |
| } |
| |
| /* ---------- TRIGGER CACHE ---------- */ |
| |
| /* |
| * get_trigger_name |
| * returns the name of the trigger with the given triggerid |
| * |
| * Note: returns a palloc'd copy of the string, or NULL if no such trigger |
| */ |
| char * |
| get_trigger_name(Oid triggerid) |
| { |
| char *result = NULL; |
| int fetchCount; |
| |
| result = caql_getcstring_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT tgname FROM pg_trigger " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(triggerid))); |
| |
| if (!fetchCount) |
| return NULL; |
| |
| return result; |
| } |
| |
| /* |
| * get_trigger_relid |
| * Given trigger id, return the trigger's relation oid |
| */ |
| Oid |
| get_trigger_relid(Oid triggerid) |
| { |
| Oid result; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT tgrelid FROM pg_trigger " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(triggerid))); |
| |
| if (!fetchCount) |
| return InvalidOid; |
| |
| return result; |
| } |
| |
| /* |
| * get_trigger_funcid |
| * Given trigger id, return the trigger's function oid |
| */ |
| Oid |
| get_trigger_funcid(Oid triggerid) |
| { |
| Oid result; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT tgfoid FROM pg_trigger " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(triggerid))); |
| |
| if (!fetchCount) |
| return InvalidOid; |
| |
| return result; |
| } |
| |
| /* |
| * get_trigger_type |
| * Given trigger id, return the trigger's type |
| */ |
| int32 |
| get_trigger_type(Oid triggerid) |
| { |
| HeapTuple tp; |
| cqContext *pcqCtx; |
| int32 result = -1; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_trigger " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(triggerid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "cache lookup failed for trigger %u", triggerid); |
| |
| result = ((Form_pg_trigger) GETSTRUCT(tp))->tgtype; |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * trigger_enabled |
| * Given trigger id, return the trigger's enabled flag |
| */ |
| bool |
| trigger_enabled(Oid triggerid) |
| { |
| HeapTuple tp; |
| bool result; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_trigger " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(triggerid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "cache lookup failed for trigger %u", triggerid); |
| |
| result = ((Form_pg_trigger) GETSTRUCT(tp))->tgenabled; |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* ---------- FUNCTION CACHE ---------- */ |
| |
| /* |
| * get_func_name |
| * returns the name of the function with the given funcid |
| * |
| * Note: returns a palloc'd copy of the string, or NULL if no such function. |
| */ |
| char * |
| get_func_name(Oid funcid) |
| { |
| char *result = NULL; |
| int fetchCount; |
| |
| result = caql_getcstring_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT proname FROM pg_proc " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(funcid))); |
| |
| if (!fetchCount) |
| return NULL; |
| |
| return result; |
| } |
| |
| /* |
| * get_type_name |
| * returns the name of the type with the given oid |
| * |
| * Note: returns a palloc'd copy of the string, or NULL if no such type. |
| */ |
| char * |
| get_type_name(Oid oid) |
| { |
| char *result = NULL; |
| int fetchCount; |
| |
| result = caql_getcstring_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT typname FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(oid))); |
| |
| if (!fetchCount) |
| return NULL; |
| |
| return result; |
| } |
| |
| /* |
| * get_func_namespace |
| * Returns the pg_namespace OID associated with a given procedure. |
| * |
| */ |
| Oid |
| get_func_namespace(Oid funcid) |
| { |
| Oid result; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT pronamespace FROM pg_proc " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(funcid))); |
| |
| if (!fetchCount) |
| return InvalidOid; |
| |
| return result; |
| } |
| |
| /* |
| * get_func_rettype |
| * Given procedure id, return the function's result type. |
| */ |
| Oid |
| get_func_rettype(Oid funcid) |
| { |
| Oid result; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT prorettype FROM pg_proc " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(funcid))); |
| |
| if (!fetchCount) |
| return InvalidOid; |
| |
| return result; |
| } |
| |
| /* |
| * get_agg_transtype |
| * Given aggregate id, return the aggregate transition function's result type. |
| */ |
| Oid |
| get_agg_transtype(Oid aggid) |
| { |
| Oid result; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT aggtranstype FROM pg_aggregate " |
| " WHERE aggfnoid = :1 ", |
| ObjectIdGetDatum(aggid))); |
| |
| if (!fetchCount) |
| elog(ERROR, "cache lookup failed for aggregate %u", aggid); |
| |
| return result; |
| } |
| |
| /* |
| * is_ordered_agg |
| * Given aggregate id, check if it is an ordered aggregate |
| */ |
| bool |
| is_agg_ordered(Oid aggid) |
| { |
| Relation aggRelation = heap_open(AggregateRelationId, AccessShareLock); |
| cqContext cqc; |
| HeapTuple aggTuple = caql_getfirst( |
| caql_addrel(cqclr(&cqc), aggRelation), |
| cql("SELECT * from pg_aggregate" |
| " WHERE aggfnoid = :1", |
| ObjectIdGetDatum(aggid))); |
| |
| if (!HeapTupleIsValid(aggTuple)) |
| { |
| elog(ERROR, "cache lookup failed for aggregate %u", aggid); |
| } |
| |
| bool isnull = false; |
| bool is_ordered = false; |
| is_ordered = DatumGetBool(heap_getattr(aggTuple, Anum_pg_aggregate_aggordered, RelationGetDescr(aggRelation), &isnull)); |
| |
| heap_freetuple(aggTuple); |
| heap_close(aggRelation, AccessShareLock); |
| |
| Assert(!isnull); |
| |
| return is_ordered; |
| } |
| |
| /* |
| * has_agg_prelimfunc |
| * Given aggregate id, check if it is has a prelim function |
| */ |
| bool has_agg_prelimfunc(Oid aggid) |
| { |
| Relation aggRelation = heap_open(AggregateRelationId, AccessShareLock); |
| cqContext cqc; |
| HeapTuple aggTuple = caql_getfirst( |
| caql_addrel(cqclr(&cqc), aggRelation), |
| cql("SELECT * from pg_aggregate" |
| " WHERE aggfnoid = :1", |
| ObjectIdGetDatum(aggid))); |
| |
| if (!HeapTupleIsValid(aggTuple)) |
| { |
| elog(ERROR, "cache lookup failed for aggregate %u", aggid); |
| } |
| |
| Form_pg_aggregate aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple); |
| bool has_prelimfunc = (InvalidOid != aggform->aggprelimfn); |
| |
| heap_freetuple(aggTuple); |
| heap_close(aggRelation, AccessShareLock); |
| |
| return has_prelimfunc; |
| } |
| |
| |
| /* |
| * agg_has_prelim_or_invprelim_func |
| * Given aggregate id, check if it is has a prelim or inverse prelim function |
| */ |
| bool agg_has_prelim_or_invprelim_func(Oid aggid) |
| { |
| Relation aggRelation = heap_open(AggregateRelationId, AccessShareLock); |
| cqContext cqc; |
| HeapTuple aggTuple = caql_getfirst( |
| caql_addrel(cqclr(&cqc), aggRelation), |
| cql("SELECT * from pg_aggregate" |
| " WHERE aggfnoid = :1", |
| ObjectIdGetDatum(aggid))); |
| |
| if (!HeapTupleIsValid(aggTuple)) |
| { |
| elog(ERROR, "cache lookup failed for aggregate %u", aggid); |
| } |
| |
| Form_pg_aggregate aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple); |
| bool has_prelimfunc = (InvalidOid != aggform->aggprelimfn); |
| bool has_invprelimfunc = (InvalidOid != aggform->agginvprelimfn); |
| |
| heap_freetuple(aggTuple); |
| heap_close(aggRelation, AccessShareLock); |
| |
| return has_prelimfunc || has_invprelimfunc; |
| } |
| |
| |
| /* |
| * get_func_nargs |
| * Given procedure id, return the number of arguments. |
| */ |
| int |
| get_func_nargs(Oid funcid) |
| { |
| HeapTuple tp; |
| int result; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_proc " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(funcid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "cache lookup failed for function %u", funcid); |
| |
| result = ((Form_pg_proc) GETSTRUCT(tp))->pronargs; |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * get_func_signature |
| * Given procedure id, return the function's argument and result types. |
| * (The return value is the result type.) |
| * |
| * The arguments are returned as a palloc'd array. |
| */ |
| Oid |
| get_func_signature(Oid funcid, Oid **argtypes, int *nargs) |
| { |
| HeapTuple tp; |
| Form_pg_proc procstruct; |
| Oid result; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_proc " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(funcid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "cache lookup failed for function %u", funcid); |
| |
| procstruct = (Form_pg_proc) GETSTRUCT(tp); |
| |
| result = procstruct->prorettype; |
| *nargs = (int) procstruct->pronargs; |
| Assert(*nargs == procstruct->proargtypes.dim1); |
| *argtypes = (Oid *) palloc(*nargs * sizeof(Oid)); |
| memcpy(*argtypes, procstruct->proargtypes.values, *nargs * sizeof(Oid)); |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * pfree_ptr_array |
| * Free an array of pointers, after freeing each individual element |
| */ |
| void |
| pfree_ptr_array(char **ptrarray, int nelements) |
| { |
| int i; |
| if (NULL == ptrarray) |
| return; |
| |
| for (i = 0; i < nelements; i++) |
| { |
| if (NULL != ptrarray[i]) |
| { |
| pfree(ptrarray[i]); |
| } |
| } |
| pfree(ptrarray); |
| } |
| |
| /* |
| * get_func_output_arg_types |
| * Given procedure id, return the function's output argument types |
| */ |
| List * |
| get_func_output_arg_types(Oid funcid) |
| { |
| HeapTuple tp; |
| int numargs; |
| Oid *argtypes = NULL; |
| char **argnames = NULL; |
| char *argmodes = NULL; |
| List *l_argtypes = NIL; |
| int i; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_proc " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(funcid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "cache lookup failed for function %u", funcid); |
| |
| numargs = get_func_arg_info(tp, &argtypes, &argnames, &argmodes); |
| |
| if (NULL == argmodes) |
| { |
| pfree_ptr_array(argnames, numargs); |
| if (NULL != argtypes) |
| { |
| pfree(argtypes); |
| } |
| caql_endscan(pcqCtx); |
| return NULL; |
| } |
| |
| for (i = 0; i < numargs; i++) |
| { |
| Oid argtype = argtypes[i]; |
| char argmode = argmodes[i]; |
| |
| if (PROARGMODE_INOUT == argmode || PROARGMODE_OUT == argmode || PROARGMODE_TABLE == argmode) |
| { |
| l_argtypes = lappend_oid(l_argtypes, argtype); |
| } |
| } |
| |
| pfree_ptr_array(argnames, numargs); |
| pfree(argtypes); |
| pfree(argmodes); |
| |
| caql_endscan(pcqCtx); |
| return l_argtypes; |
| } |
| |
| /* |
| * get_func_arg_types |
| * Given procedure id, return all the function's argument types |
| */ |
| List * |
| get_func_arg_types(Oid funcid) |
| { |
| cqContext *pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_proc " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(funcid))); |
| |
| HeapTuple tp = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "cache lookup failed for function %u", funcid); |
| |
| oidvector args = ((Form_pg_proc) GETSTRUCT(tp))->proargtypes; |
| List *result = NIL; |
| for (int i = 0; i < args.dim1; i++) |
| { |
| result = lappend_oid(result, args.values[i]); |
| } |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * get_func_retset |
| * Given procedure id, return the function's proretset flag. |
| */ |
| bool |
| get_func_retset(Oid funcid) |
| { |
| HeapTuple tp; |
| bool result; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_proc " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(funcid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "cache lookup failed for function %u", funcid); |
| |
| result = ((Form_pg_proc) GETSTRUCT(tp))->proretset; |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * func_strict |
| * Given procedure id, return the function's proisstrict flag. |
| */ |
| bool |
| func_strict(Oid funcid) |
| { |
| HeapTuple tp; |
| bool result; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_proc " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(funcid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "cache lookup failed for function %u", funcid); |
| |
| result = ((Form_pg_proc) GETSTRUCT(tp))->proisstrict; |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * func_volatile |
| * Given procedure id, return the function's provolatile flag. |
| */ |
| char |
| func_volatile(Oid funcid) |
| { |
| HeapTuple tp; |
| char result; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_proc " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(funcid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "cache lookup failed for function %u", funcid); |
| |
| result = ((Form_pg_proc) GETSTRUCT(tp))->provolatile; |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * func_data_access |
| * Given procedure id, return the function's data access flag. |
| */ |
| char |
| func_data_access(Oid funcid) |
| { |
| SQLDataAccess sda = GetFuncSQLDataAccess(funcid); |
| switch (sda) |
| { |
| case SDA_NO_SQL: |
| return PRODATAACCESS_NONE; |
| case SDA_CONTAINS_SQL: |
| return PRODATAACCESS_CONTAINS; |
| case SDA_READS_SQL: |
| return PRODATAACCESS_READS; |
| case SDA_MODIFIES_SQL: |
| return PRODATAACCESS_MODIFIES; |
| default: |
| elog(ERROR, "cache lookup failed for function %u", funcid); |
| return '\0'; |
| } |
| } |
| |
| /* ---------- RELATION CACHE ---------- */ |
| |
| /* |
| * get_relname_relid |
| * Given name and namespace of a relation, look up the OID. |
| * |
| * Returns InvalidOid if there is no such relation. |
| */ |
| Oid |
| get_relname_relid(const char *relname, Oid relnamespace) |
| { |
| Oid result; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT oid FROM pg_class " |
| " WHERE relname = :1 " |
| " AND relnamespace = :2 ", |
| PointerGetDatum((char *) relname), |
| ObjectIdGetDatum(relnamespace))); |
| |
| if (!fetchCount) |
| return InvalidOid; |
| |
| return result; |
| } |
| |
| /* |
| * get_relnatts |
| * |
| * Returns the number of attributes for a given relation. |
| */ |
| int |
| get_relnatts(Oid relid) |
| { |
| HeapTuple tp; |
| int result = InvalidAttrNumber; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_class " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(relid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (HeapTupleIsValid(tp)) |
| { |
| Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp); |
| |
| result = reltup->relnatts; |
| } |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * get_rel_name |
| * Returns the name of a given relation. |
| * |
| * Returns a palloc'd copy of the string, or NULL if no such relation. |
| * |
| * NOTE: since relation name is not unique, be wary of code that uses this |
| * for anything except preparing error messages. |
| */ |
| char * |
| get_rel_name(Oid relid) |
| { |
| char *result = NULL; |
| int fetchCount; |
| |
| result = caql_getcstring_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT relname FROM pg_class " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(relid))); |
| |
| if (!fetchCount) |
| return NULL; |
| |
| return result; |
| } |
| |
| |
| /* |
| * get_rel_name_partition |
| * Returns the name of a given relation plus its parent name, if it is a partition table. |
| * If it not a partition table, it returns the relation name only. |
| * |
| * Returns a palloc'd copy of the string, or NULL if no such relation. |
| * The caller is responsible for releasing the palloc'd memory. |
| */ |
| char * |
| get_rel_name_partition(Oid relid) |
| { |
| char *rel_name = get_rel_name(relid); |
| |
| if (rel_name == NULL) return NULL; |
| |
| if (rel_is_child_partition(relid)) |
| { |
| char *result; |
| |
| Oid parent_oid = rel_partition_get_master(relid); |
| Assert(parent_oid != InvalidOid); |
| |
| char *parent_name = get_rel_name(parent_oid); |
| Assert(parent_name); |
| |
| char *partition_name = ""; |
| |
| StringInfo buffer = makeStringInfo(); |
| Assert(buffer); |
| |
| appendStringInfo(buffer, "\"%s\" (partition%s of relation \"%s\")", |
| rel_name, partition_name, parent_name); |
| |
| result = pstrdup(buffer->data); |
| |
| pfree(rel_name); |
| pfree(parent_name); |
| pfree(buffer); |
| |
| rel_name = NULL; |
| parent_name = NULL; |
| buffer = NULL; |
| |
| return result; |
| } |
| return rel_name; |
| } |
| |
| |
| /* |
| * get_rel_namespace |
| * |
| * Returns the pg_namespace OID associated with a given relation. |
| */ |
| Oid |
| get_rel_namespace(Oid relid) |
| { |
| Oid result = InvalidOid; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT relnamespace FROM pg_class " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(relid))); |
| |
| if (!fetchCount) |
| return InvalidOid; |
| |
| return result; |
| } |
| |
| /* |
| * get_rel_type_id |
| * |
| * Returns the pg_type OID associated with a given relation. |
| * |
| * Note: not all pg_class entries have associated pg_type OIDs; so be |
| * careful to check for InvalidOid result. |
| */ |
| Oid |
| get_rel_type_id(Oid relid) |
| { |
| Oid result = InvalidOid; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT reltype FROM pg_class " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(relid))); |
| |
| if (!fetchCount) |
| return InvalidOid; |
| |
| return result; |
| } |
| |
| /* |
| * get_rel_relkind |
| * |
| * Returns the relkind associated with a given relation. |
| */ |
| char |
| get_rel_relkind(Oid relid) |
| { |
| HeapTuple tp; |
| cqContext *pcqCtx; |
| char result = '\0'; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_class " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(relid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (HeapTupleIsValid(tp)) |
| { |
| Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp); |
| |
| result = reltup->relkind; |
| } |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * get_rel_reltuples |
| * |
| * Returns the estimated number of tuples of a given relation. |
| */ |
| float4 |
| get_rel_reltuples(Oid relid) |
| { |
| HeapTuple tp; |
| cqContext *pcqCtx; |
| float4 result = 0; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_class " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(relid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (HeapTupleIsValid(tp)) |
| { |
| Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp); |
| |
| result = reltup->reltuples; |
| } |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * get_rel_relstorage |
| * |
| * Returns the relstorage associated with a given relation. |
| */ |
| char |
| get_rel_relstorage(Oid relid) |
| { |
| HeapTuple tp; |
| cqContext *pcqCtx; |
| char result = '\0'; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_class " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(relid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (HeapTupleIsValid(tp)) |
| { |
| Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp); |
| |
| result = reltup->relstorage; |
| } |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * get_rel_tablespace |
| * |
| * Returns the pg_tablespace OID associated with a given relation. |
| * |
| * Note: InvalidOid might mean either that we couldn't find the relation, |
| * or that it is in the database's default tablespace. |
| */ |
| Oid |
| get_rel_tablespace(Oid relid) |
| { |
| Oid result = InvalidOid; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT reltablespace FROM pg_class " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(relid))); |
| |
| if (!fetchCount) |
| return InvalidOid; |
| |
| return result; |
| } |
| |
| |
| /* ---------- TYPE CACHE ---------- */ |
| |
| /* |
| * get_typisdefined |
| * |
| * Given the type OID, determine whether the type is defined |
| * (if not, it's only a shell). |
| */ |
| bool |
| get_typisdefined(Oid typid) |
| { |
| HeapTuple tp; |
| bool result = false; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(typid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (HeapTupleIsValid(tp)) |
| { |
| Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); |
| |
| result = typtup->typisdefined; |
| } |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * get_typlen |
| * |
| * Given the type OID, return the length of the type. |
| */ |
| int16 |
| get_typlen(Oid typid) |
| { |
| HeapTuple tp; |
| int16 result = 0; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(typid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (HeapTupleIsValid(tp)) |
| { |
| Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); |
| |
| result = typtup->typlen; |
| } |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * get_typbyval |
| * |
| * Given the type OID, determine whether the type is returned by value or |
| * not. Returns true if by value, false if by reference. |
| */ |
| bool |
| get_typbyval(Oid typid) |
| { |
| HeapTuple tp; |
| bool result = false; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(typid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (HeapTupleIsValid(tp)) |
| { |
| Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); |
| |
| result = typtup->typbyval; |
| } |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * get_typlenbyval |
| * |
| * A two-fer: given the type OID, return both typlen and typbyval. |
| * |
| * Since both pieces of info are needed to know how to copy a Datum, |
| * many places need both. Might as well get them with one cache lookup |
| * instead of two. Also, this routine raises an error instead of |
| * returning a bogus value when given a bad type OID. |
| */ |
| void |
| get_typlenbyval(Oid typid, int16 *typlen, bool *typbyval) |
| { |
| HeapTuple tp; |
| Form_pg_type typtup; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(typid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "cache lookup failed for type %u", typid); |
| typtup = (Form_pg_type) GETSTRUCT(tp); |
| *typlen = typtup->typlen; |
| *typbyval = typtup->typbyval; |
| |
| caql_endscan(pcqCtx); |
| } |
| |
| /* |
| * get_typlenbyvalalign |
| * |
| * A three-fer: given the type OID, return typlen, typbyval, typalign. |
| */ |
| void |
| get_typlenbyvalalign(Oid typid, int16 *typlen, bool *typbyval, |
| char *typalign) |
| { |
| HeapTuple tp; |
| Form_pg_type typtup; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(typid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "cache lookup failed for type %u", typid); |
| typtup = (Form_pg_type) GETSTRUCT(tp); |
| *typlen = typtup->typlen; |
| *typbyval = typtup->typbyval; |
| *typalign = typtup->typalign; |
| |
| caql_endscan(pcqCtx); |
| } |
| |
| /* |
| * getTypeIOParam |
| * Given a pg_type row, select the type OID to pass to I/O functions |
| * |
| * Formerly, all I/O functions were passed pg_type.typelem as their second |
| * parameter, but we now have a more complex rule about what to pass. |
| * This knowledge is intended to be centralized here --- direct references |
| * to typelem elsewhere in the code are wrong, if they are associated with |
| * I/O calls and not with actual subscripting operations! (But see |
| * bootstrap.c's boot_get_type_io_data() if you need to change this.) |
| * |
| * As of PostgreSQL 8.1, output functions receive only the value itself |
| * and not any auxiliary parameters, so the name of this routine is now |
| * a bit of a misnomer ... it should be getTypeInputParam. |
| */ |
| Oid |
| getTypeIOParam(HeapTuple typeTuple) |
| { |
| Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTuple); |
| |
| /* |
| * Array types get their typelem as parameter; everybody else gets their |
| * own type OID as parameter. (As of 8.2, domains must get their own OID |
| * even if their base type is an array.) |
| */ |
| if (typeStruct->typtype == TYPTYPE_BASE && OidIsValid(typeStruct->typelem)) |
| return typeStruct->typelem; |
| else |
| return HeapTupleGetOid(typeTuple); |
| } |
| |
| /* |
| * get_type_io_data |
| * |
| * A six-fer: given the type OID, return typlen, typbyval, typalign, |
| * typdelim, typioparam, and IO function OID. The IO function |
| * returned is controlled by IOFuncSelector |
| */ |
| void |
| get_type_io_data(Oid typid, |
| IOFuncSelector which_func, |
| int16 *typlen, |
| bool *typbyval, |
| char *typalign, |
| char *typdelim, |
| Oid *typioparam, |
| Oid *func) |
| { |
| HeapTuple typeTuple; |
| Form_pg_type typeStruct; |
| cqContext *pcqCtx; |
| |
| /* |
| * In bootstrap mode, pass it off to bootstrap.c. This hack allows us to |
| * use array_in and array_out during bootstrap. |
| */ |
| if (IsBootstrapProcessingMode()) |
| { |
| Oid typinput; |
| Oid typoutput; |
| |
| boot_get_type_io_data(typid, |
| typlen, |
| typbyval, |
| typalign, |
| typdelim, |
| typioparam, |
| &typinput, |
| &typoutput); |
| switch (which_func) |
| { |
| case IOFunc_input: |
| *func = typinput; |
| break; |
| case IOFunc_output: |
| *func = typoutput; |
| break; |
| default: |
| elog(ERROR, "binary I/O not supported during bootstrap"); |
| break; |
| } |
| return; |
| } |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(typid))); |
| |
| typeTuple = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(typeTuple)) |
| elog(ERROR, "cache lookup failed for type %u", typid); |
| typeStruct = (Form_pg_type) GETSTRUCT(typeTuple); |
| |
| *typlen = typeStruct->typlen; |
| *typbyval = typeStruct->typbyval; |
| *typalign = typeStruct->typalign; |
| *typdelim = typeStruct->typdelim; |
| *typioparam = getTypeIOParam(typeTuple); |
| switch (which_func) |
| { |
| case IOFunc_input: |
| *func = typeStruct->typinput; |
| break; |
| case IOFunc_output: |
| *func = typeStruct->typoutput; |
| break; |
| case IOFunc_receive: |
| *func = typeStruct->typreceive; |
| break; |
| case IOFunc_send: |
| *func = typeStruct->typsend; |
| break; |
| } |
| |
| caql_endscan(pcqCtx); |
| } |
| |
| #ifdef NOT_USED |
| char |
| get_typalign(Oid typid) |
| { |
| HeapTuple tp; |
| cqContext *pcqCtx; |
| char result = 'i'; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(typid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (HeapTupleIsValid(tp)) |
| { |
| Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); |
| |
| result = typtup->typalign; |
| } |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| #endif |
| |
| char |
| get_typstorage(Oid typid) |
| { |
| HeapTuple tp; |
| cqContext *pcqCtx; |
| char result = 'p'; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(typid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (HeapTupleIsValid(tp)) |
| { |
| Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); |
| |
| result = typtup->typstorage; |
| } |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * get_typdefault |
| * Given a type OID, return the type's default value, if any. |
| * |
| * The result is a palloc'd expression node tree, or NULL if there |
| * is no defined default for the datatype. |
| * |
| * NB: caller should be prepared to coerce result to correct datatype; |
| * the returned expression tree might produce something of the wrong type. |
| */ |
| Node * |
| get_typdefault(Oid typid) |
| { |
| HeapTuple typeTuple; |
| Form_pg_type type; |
| Datum datum; |
| bool isNull; |
| Node *expr; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(typid))); |
| |
| typeTuple = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(typeTuple)) |
| elog(ERROR, "cache lookup failed for type %u", typid); |
| type = (Form_pg_type) GETSTRUCT(typeTuple); |
| |
| /* |
| * typdefault and typdefaultbin are potentially null, so don't try to |
| * access 'em as struct fields. Must do it the hard way with |
| * caql_getattr. |
| */ |
| datum = caql_getattr(pcqCtx, |
| Anum_pg_type_typdefaultbin, |
| &isNull); |
| |
| if (!isNull) |
| { |
| /* We have an expression default */ |
| expr = stringToNode(TextDatumGetCString(datum)); |
| } |
| else |
| { |
| /* Perhaps we have a plain literal default */ |
| datum = caql_getattr(pcqCtx, |
| Anum_pg_type_typdefault, |
| &isNull); |
| |
| if (!isNull) |
| { |
| char *strDefaultVal; |
| |
| /* Convert text datum to C string */ |
| strDefaultVal = TextDatumGetCString(datum); |
| /* Convert C string to a value of the given type */ |
| datum = OidInputFunctionCall(type->typinput, strDefaultVal, |
| getTypeIOParam(typeTuple), -1); |
| /* Build a Const node containing the value */ |
| expr = (Node *) makeConst(typid, -1, |
| type->typlen, |
| datum, |
| false, |
| type->typbyval); |
| pfree(strDefaultVal); |
| } |
| else |
| { |
| /* No default */ |
| expr = NULL; |
| } |
| } |
| |
| caql_endscan(pcqCtx); |
| |
| return expr; |
| } |
| |
| /* |
| * getBaseType |
| * If the given type is a domain, return its base type; |
| * otherwise return the type's own OID. |
| */ |
| Oid |
| getBaseType(Oid typid) |
| { |
| int32 typmod = -1; |
| |
| return getBaseTypeAndTypmod(typid, &typmod); |
| } |
| |
| /* |
| * getBaseTypeAndTypmod |
| * If the given type is a domain, return its base type and typmod; |
| * otherwise return the type's own OID, and leave *typmod unchanged. |
| * |
| * Note that the "applied typmod" should be -1 for every domain level |
| * above the bottommost; therefore, if the passed-in typid is indeed |
| * a domain, *typmod should be -1. |
| */ |
| Oid |
| getBaseTypeAndTypmod(Oid typid, int32 *typmod) |
| { |
| /* |
| * We loop to find the bottom base type in a stack of domains. |
| */ |
| for (;;) |
| { |
| HeapTuple tup; |
| Form_pg_type typTup; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(typid))); |
| |
| tup = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(tup)) |
| elog(ERROR, "cache lookup failed for type %u", typid); |
| typTup = (Form_pg_type) GETSTRUCT(tup); |
| if (typTup->typtype != TYPTYPE_DOMAIN) |
| { |
| /* Not a domain, so done */ |
| caql_endscan(pcqCtx); |
| break; |
| } |
| |
| Assert(*typmod == -1); |
| typid = typTup->typbasetype; |
| *typmod = typTup->typtypmod; |
| |
| caql_endscan(pcqCtx); |
| } |
| |
| return typid; |
| } |
| |
| /* |
| * get_typavgwidth |
| * |
| * Given a type OID and a typmod value (pass -1 if typmod is unknown), |
| * estimate the average width of values of the type. This is used by |
| * the planner, which doesn't require absolutely correct results; |
| * it's OK (and expected) to guess if we don't know for sure. |
| */ |
| int32 |
| get_typavgwidth(Oid typid, int32 typmod) |
| { |
| int typlen = get_typlen(typid); |
| int32 maxwidth; |
| |
| /* |
| * Easy if it's a fixed-width type |
| */ |
| if (typlen > 0) |
| return typlen; |
| |
| /* |
| * type_maximum_size knows the encoding of typmod for some datatypes; |
| * don't duplicate that knowledge here. |
| */ |
| maxwidth = type_maximum_size(typid, typmod); |
| if (maxwidth > 0) |
| { |
| /* |
| * For BPCHAR, the max width is also the only width. Otherwise we |
| * need to guess about the typical data width given the max. A sliding |
| * scale for percentage of max width seems reasonable. |
| */ |
| if (typid == BPCHAROID) |
| return maxwidth; |
| if (maxwidth <= 32) |
| return maxwidth; /* assume full width */ |
| if (maxwidth < 1000) |
| return 32 + (maxwidth - 32) / 2; /* assume 50% */ |
| |
| /* |
| * Beyond 1000, assume we're looking at something like |
| * "varchar(10000)" where the limit isn't actually reached often, and |
| * use a fixed estimate. |
| */ |
| return 32 + (1000 - 32) / 2; |
| } |
| |
| /* |
| * Ooops, we have no idea ... wild guess time. |
| */ |
| return 32; |
| } |
| |
| /* |
| * get_typtype |
| * |
| * Given the type OID, find if it is a basic type, a complex type, etc. |
| * It returns the null char if the cache lookup fails... |
| */ |
| char |
| get_typtype(Oid typid) |
| { |
| HeapTuple tp; |
| cqContext *pcqCtx; |
| char result = '\0'; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(typid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (HeapTupleIsValid(tp)) |
| { |
| Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); |
| |
| result = typtup->typtype; |
| } |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * type_is_rowtype |
| * |
| * Convenience function to determine whether a type OID represents |
| * a "rowtype" type --- either RECORD or a named composite type. |
| */ |
| bool |
| type_is_rowtype(Oid typid) |
| { |
| return (typid == RECORDOID || get_typtype(typid) == TYPTYPE_COMPOSITE); |
| } |
| |
| /* |
| * type_is_basetype |
| * |
| * Convenience function to determine whether a type OID represents |
| * a "rowtype" type --- neither invalid & a named base type. |
| */ |
| bool |
| type_is_basetype(Oid typid) |
| { |
| return (typid != InvalidOid && get_typtype(typid) == TYPTYPE_BASE); |
| } |
| |
| /* |
| * get_typ_typrelid |
| * |
| * Given the type OID, get the typrelid (InvalidOid if not a complex |
| * type). |
| */ |
| Oid |
| get_typ_typrelid(Oid typid) |
| { |
| Oid result = InvalidOid; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT typrelid FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(typid))); |
| |
| if (!fetchCount) |
| return InvalidOid; |
| |
| return result; |
| } |
| |
| /* |
| * get_element_type |
| * |
| * Given the type OID, get the typelem (InvalidOid if not an array type). |
| * |
| * NB: this only considers varlena arrays to be true arrays; InvalidOid is |
| * returned if the input is a fixed-length array type. |
| */ |
| Oid |
| get_element_type(Oid typid) |
| { |
| HeapTuple tp; |
| Oid result = InvalidOid; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(typid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (HeapTupleIsValid(tp)) |
| { |
| Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); |
| |
| if (typtup->typlen == -1) |
| result = typtup->typelem; |
| } |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * get_array_type |
| * |
| * Given the type OID, get the corresponding array type. |
| * Returns InvalidOid if no array type can be found. |
| * |
| * NB: this only considers varlena arrays to be true arrays. |
| */ |
| Oid |
| get_array_type(Oid typid) |
| { |
| HeapTuple tp; |
| Oid result = InvalidOid; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(typid))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (HeapTupleIsValid(tp)) |
| { |
| Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); |
| char *array_typename; |
| Oid namespaceId; |
| cqContext *namcqCtx; |
| |
| array_typename = makeArrayTypeName(NameStr(typtup->typname)); |
| namespaceId = typtup->typnamespace; |
| |
| namcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE typname = :1 " |
| " AND typnamespace = :2 ", |
| PointerGetDatum(array_typename), |
| ObjectIdGetDatum(namespaceId))); |
| |
| tp = caql_getnext(namcqCtx); |
| |
| pfree(array_typename); |
| |
| if (HeapTupleIsValid(tp)) |
| { |
| typtup = (Form_pg_type) GETSTRUCT(tp); |
| if (typtup->typlen == -1 && typtup->typelem == typid) |
| result = HeapTupleGetOid(tp); |
| } |
| caql_endscan(namcqCtx); |
| } |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * get_base_element_type |
| * Given the type OID, get the typelem, looking "through" any domain |
| * to its underlying array type. |
| * |
| * This is equivalent to get_element_type(getBaseType(typid)), but avoids |
| * an extra cache lookup. Note that it fails to provide any information |
| * about the typmod of the array. |
| */ |
| Oid |
| get_base_element_type(Oid typid) |
| { |
| /* |
| * We loop to find the bottom base type in a stack of domains. |
| */ |
| for (;;) |
| { |
| HeapTuple tup; |
| Form_pg_type typTup; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(typid))); |
| |
| tup = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(tup)) |
| { |
| caql_endscan(pcqCtx); |
| break; |
| } |
| |
| typTup = (Form_pg_type) GETSTRUCT(tup); |
| if (typTup->typtype != TYPTYPE_DOMAIN) |
| { |
| /* Not a domain, so stop descending */ |
| Oid result; |
| |
| /* This test must match get_element_type */ |
| if (typTup->typlen == -1) |
| result = typTup->typelem; |
| else |
| result = InvalidOid; |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| typid = typTup->typbasetype; |
| caql_endscan(pcqCtx); |
| } |
| |
| /* Like get_element_type, silently return InvalidOid for bogus input */ |
| return InvalidOid; |
| } |
| |
| /* |
| * getTypeInputInfo |
| * |
| * Get info needed for converting values of a type to internal form |
| */ |
| void |
| getTypeInputInfo(Oid type, Oid *typInput, Oid *typIOParam) |
| { |
| HeapTuple typeTuple; |
| Form_pg_type pt; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(type))); |
| |
| typeTuple = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(typeTuple)) |
| elog(ERROR, "cache lookup failed for type %u", type); |
| pt = (Form_pg_type) GETSTRUCT(typeTuple); |
| |
| if (!pt->typisdefined) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("type %s is only a shell", |
| format_type_be(type)), |
| errOmitLocation(true))); |
| if (!OidIsValid(pt->typinput)) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_FUNCTION), |
| errmsg("no input function available for type %s", |
| format_type_be(type)), |
| errOmitLocation(true))); |
| |
| *typInput = pt->typinput; |
| *typIOParam = getTypeIOParam(typeTuple); |
| |
| caql_endscan(pcqCtx); |
| } |
| |
| /* |
| * getTypeOutputInfo |
| * |
| * Get info needed for printing values of a type |
| */ |
| void |
| getTypeOutputInfo(Oid type, Oid *typOutput, bool *typIsVarlena) |
| { |
| HeapTuple typeTuple; |
| Form_pg_type pt; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(type))); |
| |
| typeTuple = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(typeTuple)) |
| elog(ERROR, "cache lookup failed for type %u", type); |
| pt = (Form_pg_type) GETSTRUCT(typeTuple); |
| |
| if (!pt->typisdefined) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("type %s is only a shell", |
| format_type_be(type)), |
| errOmitLocation(true))); |
| if (!OidIsValid(pt->typoutput)) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_FUNCTION), |
| errmsg("no output function available for type %s", |
| format_type_be(type)), |
| errOmitLocation(true))); |
| |
| *typOutput = pt->typoutput; |
| *typIsVarlena = (!pt->typbyval) && (pt->typlen == -1); |
| |
| caql_endscan(pcqCtx); |
| } |
| |
| /* |
| * getTypeBinaryInputInfo |
| * |
| * Get info needed for binary input of values of a type |
| */ |
| void |
| getTypeBinaryInputInfo(Oid type, Oid *typReceive, Oid *typIOParam) |
| { |
| HeapTuple typeTuple; |
| Form_pg_type pt; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(type))); |
| |
| typeTuple = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(typeTuple)) |
| elog(ERROR, "cache lookup failed for type %u", type); |
| pt = (Form_pg_type) GETSTRUCT(typeTuple); |
| |
| if (!pt->typisdefined) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("type %s is only a shell", |
| format_type_be(type)), |
| errOmitLocation(true))); |
| if (!OidIsValid(pt->typreceive)) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_FUNCTION), |
| errmsg("no binary input function available for type %s", |
| format_type_be(type)), |
| errOmitLocation(true))); |
| |
| *typReceive = pt->typreceive; |
| *typIOParam = getTypeIOParam(typeTuple); |
| |
| caql_endscan(pcqCtx); |
| } |
| |
| /* |
| * getTypeBinaryOutputInfo |
| * |
| * Get info needed for binary output of values of a type |
| */ |
| void |
| getTypeBinaryOutputInfo(Oid type, Oid *typSend, bool *typIsVarlena) |
| { |
| HeapTuple typeTuple; |
| Form_pg_type pt; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(type))); |
| |
| typeTuple = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(typeTuple)) |
| elog(ERROR, "cache lookup failed for type %u", type); |
| pt = (Form_pg_type) GETSTRUCT(typeTuple); |
| |
| if (!pt->typisdefined) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("type %s is only a shell", |
| format_type_be(type)), |
| errOmitLocation(true))); |
| if (!OidIsValid(pt->typsend)) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_FUNCTION), |
| errmsg("no binary output function available for type %s", |
| format_type_be(type)), |
| errOmitLocation(true))); |
| |
| *typSend = pt->typsend; |
| *typIsVarlena = (!pt->typbyval) && (pt->typlen == -1); |
| |
| caql_endscan(pcqCtx); |
| } |
| |
| |
| /* ---------- STATISTICS CACHE ---------- */ |
| |
| /* |
| * get_attavgwidth |
| * |
| * Given the table and attribute number of a column, get the average |
| * width of entries in the column. Return zero if no data available. |
| */ |
| int32 |
| get_attavgwidth(Oid relid, AttrNumber attnum) |
| { |
| HeapTuple tp; |
| int32 result = 0; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_statistic " |
| " WHERE starelid = :1 " |
| " AND staattnum = :2 ", |
| ObjectIdGetDatum(relid), |
| Int16GetDatum(attnum))); |
| |
| tp = caql_getnext(pcqCtx); |
| |
| if (HeapTupleIsValid(tp)) |
| { |
| int32 stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth; |
| |
| if (stawidth > 0) |
| result = stawidth; |
| } |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * get_attdistinct |
| * |
| * Given the table and attribute number of a column, get the number of |
| * distinct values in the column. Return zero if no data available. |
| */ |
| float4 |
| get_attdistinct(Oid relid, AttrNumber attnum) |
| { |
| cqContext *pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_statistic " |
| " WHERE starelid = :1 " |
| " AND staattnum = :2 ", |
| ObjectIdGetDatum(relid), |
| Int16GetDatum(attnum))); |
| |
| HeapTuple tp = caql_getnext(pcqCtx); |
| |
| float4 result = 0; |
| if (HeapTupleIsValid(tp)) |
| { |
| result = ((Form_pg_statistic) GETSTRUCT(tp))->stadistinct; |
| } |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * get_attstatsslot |
| * |
| * Extract the contents of a "slot" of a pg_statistic tuple. |
| * Returns TRUE if requested slot type was found, else FALSE. |
| * |
| * Unlike other routines in this file, this takes a pointer to an |
| * already-looked-up tuple in the pg_statistic cache. We do this since |
| * most callers will want to extract more than one value from the cache |
| * entry, and we don't want to repeat the cache lookup unnecessarily. |
| * |
| * statstuple: pg_statistics tuple to be examined. |
| * atttype: type OID of attribute (can be InvalidOid if values == NULL). |
| * atttypmod: typmod of attribute (can be 0 if values == NULL). |
| * reqkind: STAKIND code for desired statistics slot kind. |
| * reqop: STAOP value wanted, or InvalidOid if don't care. |
| * values, nvalues: if not NULL, the slot's stavalues are extracted. |
| * numbers, nnumbers: if not NULL, the slot's stanumbers are extracted. |
| * |
| * If assigned, values and numbers are set to point to palloc'd arrays. |
| * If the attribute type is pass-by-reference, the values referenced by |
| * the values array are themselves palloc'd. The palloc'd stuff can be |
| * freed by calling free_attstatsslot. |
| */ |
| extern bool get_attstatsslot_desc(TupleDesc tupdesc, HeapTuple statstuple, |
| Oid atttype, int32 atttypmod, |
| int reqkind, Oid reqop, |
| Datum **values, int *nvalues, |
| float4 **numbers, int *nnumbers); |
| |
| bool |
| get_attstatsslot(HeapTuple statstuple, |
| Oid atttype, int32 atttypmod, |
| int reqkind, Oid reqop, |
| Datum **values, int *nvalues, |
| float4 **numbers, int *nnumbers) |
| { |
| return get_attstatsslot_desc(NULL, statstuple, |
| atttype, atttypmod, reqkind, reqop, |
| values, nvalues, numbers, nnumbers |
| ); |
| } |
| |
| /* get an attribute any way you can! */ |
| static Datum |
| attstatsslot_getattr(cqContext *pcqCtx, |
| TupleDesc tupdesc, |
| HeapTuple statstuple, |
| AttrNumber attnum, bool *isnull) |
| { |
| Datum val; |
| |
| if (pcqCtx) |
| val = caql_getattr(pcqCtx, attnum, isnull); |
| else |
| { |
| if(tupdesc) |
| val = heap_getattr(statstuple, attnum, |
| tupdesc, isnull); |
| else |
| val = SysCacheGetAttr(STATRELATT, statstuple, |
| attnum, |
| isnull); |
| } |
| |
| return val; |
| } |
| |
| bool |
| get_attstatsslot_desc(TupleDesc tupdesc, HeapTuple statstuple, |
| Oid atttype, int32 atttypmod, |
| int reqkind, Oid reqop, |
| Datum **values, int *nvalues, |
| float4 **numbers, int *nnumbers) |
| { |
| Form_pg_statistic stats = (Form_pg_statistic) GETSTRUCT(statstuple); |
| int i, |
| j; |
| Datum val; |
| bool isnull; |
| ArrayType *statarray; |
| int narrayelem; |
| HeapTuple typeTuple; |
| Form_pg_type typeForm; |
| |
| for (i = 0; i < STATISTIC_NUM_SLOTS; i++) |
| { |
| if ((&stats->stakind1)[i] == reqkind && |
| (reqop == InvalidOid || (&stats->staop1)[i] == reqop)) |
| break; |
| } |
| if (i >= STATISTIC_NUM_SLOTS) |
| return false; /* not there */ |
| |
| if (values) |
| { |
| *values = NULL; |
| *nvalues = 0; |
| |
| val = attstatsslot_getattr(NULL, |
| tupdesc, |
| statstuple, |
| Anum_pg_statistic_stavalues1 + i, |
| &isnull); |
| |
| if (isnull) |
| elog(ERROR, "stavalues is null"); |
| statarray = DatumGetArrayTypeP(val); |
| |
| /** |
| * Could be an empty array. |
| */ |
| if (ARR_NDIM(statarray) > 0) |
| { |
| cqContext *typcqCtx; |
| |
| /* Need to get info about the array element type */ |
| typcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(atttype))); |
| |
| typeTuple = caql_getnext(typcqCtx); |
| |
| if (!HeapTupleIsValid(typeTuple)) |
| elog(ERROR, "cache lookup failed for type %u", atttype); |
| typeForm = (Form_pg_type) GETSTRUCT(typeTuple); |
| |
| /* Deconstruct array into Datum elements; NULLs not expected */ |
| deconstruct_array(statarray, |
| atttype, |
| typeForm->typlen, |
| typeForm->typbyval, |
| typeForm->typalign, |
| values, NULL, nvalues); |
| |
| /* |
| * If the element type is pass-by-reference, we now have a bunch of |
| * Datums that are pointers into the syscache value. Copy them to |
| * avoid problems if syscache decides to drop the entry. |
| */ |
| if (!typeForm->typbyval) |
| { |
| for (j = 0; j < *nvalues; j++) |
| { |
| (*values)[j] = datumCopy((*values)[j], |
| typeForm->typbyval, |
| typeForm->typlen); |
| } |
| } |
| |
| caql_endscan(typcqCtx); |
| } |
| /* |
| * Free statarray if it's a detoasted copy. |
| */ |
| if ((Pointer) statarray != DatumGetPointer(val)) |
| pfree(statarray); |
| } |
| |
| if (numbers) |
| { |
| *numbers = NULL; |
| *nnumbers = 0; |
| |
| val = attstatsslot_getattr(NULL, |
| tupdesc, |
| statstuple, |
| Anum_pg_statistic_stanumbers1 + i, |
| &isnull); |
| |
| if (isnull) |
| elog(ERROR, "stanumbers is null"); |
| statarray = DatumGetArrayTypeP(val); |
| |
| /** |
| * Could be an empty array. |
| */ |
| if (ARR_NDIM(statarray) > 0) |
| { |
| /* |
| * We expect the array to be a 1-D float4 array; verify that. We don't |
| * need to use deconstruct_array() since the array data is just going |
| * to look like a C array of float4 values. |
| */ |
| narrayelem = ARR_DIMS(statarray)[0]; |
| if (ARR_NDIM(statarray) != 1 || narrayelem <= 0 || |
| ARR_HASNULL(statarray) || |
| ARR_ELEMTYPE(statarray) != FLOAT4OID) |
| elog(ERROR, "stanumbers is not a 1-D float4 array"); |
| *numbers = (float4 *) palloc(narrayelem * sizeof(float4)); |
| *nnumbers = narrayelem; |
| memcpy(*numbers, ARR_DATA_PTR(statarray), narrayelem * sizeof(float4)); |
| } |
| |
| /* |
| * Free statarray if it's a detoasted copy. |
| */ |
| if ((Pointer) statarray != DatumGetPointer(val)) |
| pfree(statarray); |
| } |
| |
| return true; |
| } |
| |
| /* |
| * free_attstatsslot |
| * Free data allocated by get_attstatsslot |
| * |
| * atttype need be valid only if values != NULL. |
| */ |
| void |
| free_attstatsslot(Oid atttype, |
| Datum *values, int nvalues, |
| float4 *numbers, int nnumbers) |
| { |
| if (values) |
| { |
| if (!get_typbyval(atttype)) |
| { |
| int i; |
| |
| for (i = 0; i < nvalues; i++) |
| pfree(DatumGetPointer(values[i])); |
| } |
| pfree(values); |
| } |
| if (numbers) |
| pfree(numbers); |
| } |
| |
| /* |
| * get_att_stats |
| * Get attribute statistics. Return a copy of the HeapTuple object, or NULL |
| * if no stats found for attribute |
| * |
| */ |
| HeapTuple |
| get_att_stats(Oid relid, AttrNumber attrnum) |
| { |
| return ( |
| caql_getfirst( |
| NULL, |
| cql("SELECT * FROM pg_statistic " |
| " WHERE starelid = :1 " |
| " AND staattnum = :2 ", |
| ObjectIdGetDatum(relid), |
| Int16GetDatum(attrnum)))); |
| } |
| |
| /* ---------- PG_NAMESPACE CACHE ---------- */ |
| |
| /* |
| * get_namespace_name |
| * Returns the name of a given namespace |
| * |
| * Returns a palloc'd copy of the string, or NULL if no such namespace. |
| */ |
| char * |
| get_namespace_name(Oid nspid) |
| { |
| char *result = NULL; |
| int fetchCount; |
| |
| result = caql_getcstring_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT nspname FROM pg_namespace " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(nspid))); |
| |
| if (!fetchCount) |
| return NULL; |
| |
| return result; |
| } |
| |
| /* |
| * get_namespace_oid |
| * Returns the oid of a namespace given its name |
| * |
| */ |
| Oid |
| get_namespace_oid(const char* npname) |
| { |
| Oid result; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT oid FROM pg_namespace " |
| " WHERE nspname = :1 ", |
| PointerGetDatum((char *) npname))); |
| |
| if (!fetchCount) |
| return InvalidOid; |
| |
| return result; |
| } |
| /* ---------- PG_AUTHID CACHE ---------- */ |
| |
| /* |
| * get_roleid |
| * Given a role name, look up the role's OID. |
| * Returns InvalidOid if no such role. |
| */ |
| Oid |
| get_roleid(const char *rolname) |
| { |
| Oid result; |
| int fetchCount; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT * FROM pg_authid " |
| " WHERE rolname = :1 ", |
| PointerGetDatum((char *) rolname))); |
| |
| if (!fetchCount) |
| return InvalidOid; |
| |
| return result; |
| } |
| |
| char * |
| get_rolname(Oid roleid) |
| { |
| HeapTuple ht; |
| Form_pg_authid auth_rec; |
| char *result = NULL; |
| |
| ht = SearchSysCache(AUTHOID, |
| ObjectIdGetDatum(roleid), |
| 0, 0, 0); |
| if (!HeapTupleIsValid(ht)) |
| return result; |
| |
| auth_rec = (Form_pg_authid) GETSTRUCT(ht); |
| |
| result = pstrdup(NameStr(auth_rec->rolname)); |
| |
| ReleaseSysCache(ht); |
| return result; |
| } |
| |
| /* |
| * get_relation_storage_type |
| */ |
| char |
| get_relation_storage_type(Oid relid) |
| { |
| HeapTuple classtuple; |
| Datum relstorage; |
| char storageChar; |
| |
| /* find relid in pg_class */ |
| classtuple = SearchSysCache(RELOID, ObjectIdGetDatum(relid), 0, 0, 0); |
| if (!HeapTupleIsValid(classtuple)) |
| { |
| elog(ERROR, "cache lookup failed for relation %u", relid); |
| } |
| relstorage = SysCacheGetAttr(RELOID, classtuple, Anum_pg_class_relstorage, NULL); |
| storageChar = DatumGetChar(relstorage); |
| ReleaseSysCache(classtuple); |
| |
| return storageChar; |
| } |
| |
| /* |
| * get_roleid_checked |
| * Given a role name, look up the role's OID. |
| * ereports if no such role. |
| */ |
| Oid |
| get_roleid_checked(const char *rolname) |
| { |
| Oid roleid; |
| |
| roleid = get_roleid(rolname); |
| if (!OidIsValid(roleid)) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("role \"%s\" does not exist", rolname), |
| errOmitLocation(true))); |
| return roleid; |
| } |
| |
| |
| /* |
| * relation_oids |
| * Extract all relation oids from the catalog. |
| */ |
| List * |
| relation_oids() |
| { |
| List *relationOids = NIL; |
| Relation pgclass = NULL; |
| HeapScanDesc scan = NULL; |
| HeapTuple tuple = NULL; |
| |
| pgclass = heap_open(RelationRelationId, AccessShareLock); |
| |
| scan = heap_beginscan(pgclass, SnapshotNow, 0 /* key length */, NULL); |
| |
| while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) |
| { |
| Form_pg_class pgclassEntry = (Form_pg_class) GETSTRUCT(tuple); |
| |
| switch (pgclassEntry->relstorage) |
| { |
| case RELSTORAGE_HEAP: |
| case RELSTORAGE_AOROWS: |
| case RELSTORAGE_PARQUET: |
| case RELSTORAGE_EXTERNAL: |
| { |
| Oid relOid = HeapTupleGetOid(tuple); |
| relationOids = lappend_oid(relationOids, relOid); |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| heap_endscan(scan); |
| heap_close(pgclass, AccessShareLock); |
| return relationOids; |
| } |
| |
| /* |
| * operator_oids |
| * Extract all operator oids from the catalog. |
| */ |
| List * |
| operator_oids() |
| { |
| List *operatorOids = NIL; |
| Relation operatortable = NULL; |
| HeapScanDesc scan = NULL; |
| HeapTuple tuple = NULL; |
| |
| operatortable = heap_open(OperatorRelationId, AccessShareLock); |
| |
| scan = heap_beginscan(operatortable, SnapshotNow, 0 /* key length */, NULL); |
| |
| while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) |
| { |
| Oid opOid = HeapTupleGetOid(tuple); |
| operatorOids = lappend_oid(operatorOids, opOid); |
| } |
| |
| heap_endscan(scan); |
| heap_close(operatortable, AccessShareLock); |
| return operatorOids; |
| } |
| |
| /* |
| * function_oids |
| * Extract all function oids from the catalog. |
| */ |
| List * |
| function_oids() |
| { |
| List *functionOids = NIL; |
| Relation functiontable = NULL; |
| HeapScanDesc scan = NULL; |
| HeapTuple tuple = NULL; |
| |
| functiontable = heap_open(ProcedureRelationId, AccessShareLock); |
| |
| scan = heap_beginscan(functiontable, SnapshotNow, 0 /* key length */, NULL); |
| |
| while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) |
| { |
| Oid funcOid = HeapTupleGetOid(tuple); |
| functionOids = lappend_oid(functionOids, funcOid); |
| } |
| |
| heap_endscan(scan); |
| heap_close(functiontable, AccessShareLock); |
| return functionOids; |
| } |
| |
| /* |
| * relation_exists |
| * Is there a relation with the given oid |
| */ |
| bool |
| relation_exists(Oid oid) |
| { |
| return SearchSysCacheExists(RELOID, oid, 0, 0, 0); |
| } |
| |
| /* |
| * index_exists |
| * Is there an index with the given oid |
| */ |
| bool |
| index_exists(Oid oid) |
| { |
| return SearchSysCacheExists(INDEXRELID, oid, 0, 0, 0); |
| } |
| |
| /* |
| * type_exists |
| * Is there a type with the given oid |
| */ |
| bool |
| type_exists(Oid oid) |
| { |
| return SearchSysCacheExists(TYPEOID, oid, 0, 0, 0); |
| } |
| |
| /* |
| * operator_exists |
| * Is there an operator with the given oid |
| */ |
| bool |
| operator_exists(Oid oid) |
| { |
| return SearchSysCacheExists(OPEROID, oid, 0, 0, 0); |
| } |
| |
| /* |
| * function_exists |
| * Is there a function with the given oid |
| */ |
| bool |
| function_exists(Oid oid) |
| { |
| return SearchSysCacheExists(PROCOID, oid, 0, 0, 0); |
| } |
| |
| /* |
| * aggregate_exists |
| * Is there an aggregate with the given oid |
| */ |
| bool |
| aggregate_exists(Oid oid) |
| { |
| return SearchSysCacheExists(AGGFNOID, oid, 0, 0, 0); |
| } |
| |
| // Get oid of aggregate with given name and argument type |
| Oid |
| get_aggregate(const char *aggname, Oid oidType) |
| { |
| HeapTuple htup = NULL; |
| |
| // lookup pg_proc for functions with the given name and arg type |
| cqContext *pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_proc " |
| " WHERE proname = :1", |
| PointerGetDatum((char *) aggname))); |
| |
| Oid oidResult = InvalidOid; |
| while (HeapTupleIsValid(htup = caql_getnext(pcqCtx))) |
| { |
| Oid oidProc = HeapTupleGetOid(htup); |
| |
| Form_pg_proc proctuple = (Form_pg_proc) GETSTRUCT(htup); |
| |
| // skip functions with the wrong number of type of arguments |
| if (1 != proctuple->pronargs || oidType != proctuple->proargtypes.values[0]) |
| { |
| continue; |
| } |
| |
| if (caql_getcount( |
| NULL, |
| cql("SELECT COUNT(*) FROM pg_aggregate " |
| " WHERE aggfnoid = :1 ", |
| ObjectIdGetDatum(oidProc))) > 0) |
| { |
| oidResult = oidProc; |
| break; |
| } |
| } |
| |
| caql_endscan(pcqCtx); |
| |
| return oidResult; |
| } |
| |
| /* |
| * trigger_exists |
| * Is there a trigger with the given oid |
| */ |
| bool |
| trigger_exists(Oid oid) |
| { |
| return (caql_getcount( |
| NULL, |
| cql("SELECT COUNT(*) FROM pg_trigger " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(oid))) > 0); |
| } |
| |
| /* |
| * get_relation_keys |
| * Return a list of relation keys |
| */ |
| List * |
| get_relation_keys(Oid relid) |
| { |
| List *keys = NIL; |
| |
| // lookup unique constraints for relation from the catalog table |
| ScanKeyData skey[1]; |
| ScanKeyInit(&skey[0], Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, relid); |
| |
| Relation rel = heap_open(ConstraintRelationId, AccessShareLock); |
| SysScanDesc scan = systable_beginscan |
| ( |
| rel, |
| ConstraintRelidIndexId, |
| true, |
| SnapshotNow, |
| 1, |
| skey |
| ); |
| |
| HeapTuple htup = NULL; |
| |
| while (HeapTupleIsValid(htup = systable_getnext(scan))) |
| { |
| Form_pg_constraint contuple = (Form_pg_constraint) GETSTRUCT(htup); |
| |
| // skip non-unique constraints |
| if (CONSTRAINT_UNIQUE != contuple->contype && |
| CONSTRAINT_PRIMARY != contuple->contype) |
| { |
| continue; |
| } |
| |
| // store key set in an array |
| List *key = NIL; |
| |
| bool null = false; |
| Datum dat = |
| heap_getattr(htup, Anum_pg_constraint_conkey, RelationGetDescr(rel), &null); |
| |
| Datum *dats = NULL; |
| int numKeys = 0; |
| |
| // extract key elements |
| deconstruct_array(DatumGetArrayTypeP(dat), INT2OID, 2, true, 's', &dats, NULL, &numKeys); |
| |
| for (int i = 0; i < numKeys; i++) |
| { |
| int16 key_elem = DatumGetInt16(dats[i]); |
| key = lappend_int(key, key_elem); |
| } |
| |
| keys = lappend(keys, key); |
| } |
| |
| systable_endscan(scan); |
| heap_close(rel, AccessShareLock); |
| |
| return keys; |
| } |
| |
| /* |
| * check_constraint_exists |
| * Is there a check constraint with the given oid |
| */ |
| bool |
| check_constraint_exists(Oid oidCheckconstraint) |
| { |
| return (caql_getcount( |
| NULL, |
| cql("SELECT COUNT(*) FROM pg_constraint " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(oidCheckconstraint))) > 0); |
| } |
| |
| /* |
| * get_check_constraint_relid |
| * Given check constraint id, return the check constraint's relation oid |
| */ |
| Oid |
| get_check_constraint_relid(Oid oidCheckconstraint) |
| { |
| Oid result = InvalidOid; |
| int iFetchCount = 0; |
| |
| result = caql_getoid_plus( |
| NULL, |
| &iFetchCount, |
| NULL, |
| cql("SELECT conrelid FROM pg_constraint " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(oidCheckconstraint))); |
| |
| if (0 == iFetchCount) |
| { |
| return InvalidOid; |
| } |
| |
| return result; |
| } |
| |
| /* |
| * get_check_constraint_oids |
| * Extract all check constraint oid for a given relation. |
| */ |
| List * |
| get_check_constraint_oids(Oid oidRel) |
| { |
| List *plConstraints = NIL; |
| HeapTuple htup = NULL; |
| cqContext *pcqCtx = NULL; |
| |
| // lookup constraints for relation from the catalog table |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_constraint " |
| " WHERE conrelid = :1 ", |
| ObjectIdGetDatum(oidRel))); |
| |
| while (HeapTupleIsValid(htup = caql_getnext(pcqCtx))) |
| { |
| Form_pg_constraint contuple = (Form_pg_constraint) GETSTRUCT(htup); |
| |
| // only consider check constraints |
| if (CONSTRAINT_CHECK != contuple->contype) |
| { |
| continue; |
| } |
| |
| plConstraints = lappend_oid(plConstraints, HeapTupleGetOid(htup)); |
| } |
| |
| caql_endscan(pcqCtx); |
| |
| return plConstraints; |
| } |
| |
| /* |
| * get_check_constraint_name |
| * returns the name of the check constraint with the given oidConstraint. |
| * |
| * Note: returns a palloc'd copy of the string, or NULL if no such constraint. |
| */ |
| char * |
| get_check_constraint_name(Oid oidCheckconstraint) |
| { |
| char *szResult = NULL; |
| int iFetchCount = 0; |
| szResult = caql_getcstring_plus |
| ( |
| NULL, |
| &iFetchCount, |
| NULL, |
| cql("SELECT conname FROM pg_constraint " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(oidCheckconstraint) |
| ) |
| ); |
| |
| if (0 == iFetchCount) |
| { |
| return NULL; |
| } |
| |
| return szResult; |
| } |
| |
| /* |
| * get_check_constraint_expr_tree |
| * returns the expression node tree representing the check constraint |
| * with the given oidConstraint. |
| * |
| * Note: returns a palloc'd expression node tree, or NULL if no such constraint. |
| */ |
| Node * |
| get_check_constraint_expr_tree(Oid oidCheckconstraint) |
| { |
| char *szResult = NULL; |
| int iFetchCount = 0; |
| szResult = caql_getcstring_plus |
| ( |
| NULL, |
| &iFetchCount, |
| NULL, |
| cql("SELECT conbin FROM pg_constraint " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(oidCheckconstraint) |
| ) |
| ); |
| |
| if (0 == iFetchCount) |
| { |
| return NULL; |
| } |
| |
| return stringToNode(szResult); |
| } |
| |
| /* |
| * get_cast_func |
| * finds the cast function between the given source and destination type, |
| * and records its oid and properties in the output parameters. |
| * Returns true if a cast exists, false otherwise. |
| */ |
| bool |
| get_cast_func(Oid oidSrc, Oid oidDest, bool *is_binary_coercible, Oid *oidCastFunc) |
| { |
| if (IsBinaryCoercible(oidSrc, oidDest)) |
| { |
| *is_binary_coercible = true; |
| *oidCastFunc = 0; |
| return true; |
| } |
| |
| *is_binary_coercible = false; |
| |
| return find_coercion_pathway(oidDest, oidSrc, COERCION_IMPLICIT, oidCastFunc); |
| } |
| |
| /* |
| * get_comparison_type |
| * Retrieve comparison type |
| */ |
| CmpType |
| get_comparison_type |
| ( |
| Oid oidOp, |
| Oid oidLeft, |
| Oid oidRight |
| ) |
| { |
| // find the equality comparison between the operands |
| |
| cqContext *pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_operator " |
| " WHERE oprleft = :1 and oprright = :2", |
| ObjectIdGetDatum(oidLeft), |
| ObjectIdGetDatum(oidRight))); |
| |
| Oid oidEq = InvalidOid; |
| |
| HeapTuple ht = NULL; |
| Form_pg_operator optup = NULL; |
| |
| while (HeapTupleIsValid(ht = caql_getnext(pcqCtx))) |
| { |
| optup = (Form_pg_operator) GETSTRUCT(ht); |
| |
| if (optup->oprrest == F_EQSEL) |
| { |
| oidEq = HeapTupleGetOid(ht); |
| break; |
| } |
| } |
| |
| if (InvalidOid == oidEq) |
| { |
| // no equality found between specified types, hence not a special comparison operator |
| caql_endscan(pcqCtx); |
| return CmptOther; |
| } |
| |
| int cmp_oids[][2] = |
| { |
| {oidEq, CmptEq}, |
| {get_negator(oidEq), CmptNEq}, |
| {optup->oprltcmpop, CmptLT}, |
| {optup->oprgtcmpop, CmptGT}, |
| {get_negator(optup->oprltcmpop), CmptGEq}, |
| {get_negator(optup->oprgtcmpop), CmptLEq} |
| }; |
| |
| for (int ul = 0; ul < 6; ul++) |
| { |
| int *map_elem = cmp_oids[ul]; |
| if (map_elem[0] == oidOp) |
| { |
| caql_endscan(pcqCtx); |
| return map_elem[1]; |
| } |
| } |
| |
| caql_endscan(pcqCtx); |
| return CmptOther; |
| } |
| |
| /* |
| * get_comparison_operator |
| * Retrieve comparison operator between given types |
| */ |
| Oid |
| get_comparison_operator |
| ( |
| Oid oidLeft, |
| Oid oidRight, |
| CmpType cmpt |
| ) |
| { |
| // find the equality comparison between the operands |
| cqContext *pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_operator " |
| " WHERE oprleft = :1 and oprright = :2", |
| ObjectIdGetDatum(oidLeft), |
| ObjectIdGetDatum(oidRight))); |
| |
| Oid oidEq = InvalidOid; |
| |
| HeapTuple ht = NULL; |
| Form_pg_operator optup = NULL; |
| |
| while (HeapTupleIsValid(ht = caql_getnext(pcqCtx))) |
| { |
| optup = (Form_pg_operator) GETSTRUCT(ht); |
| |
| if (optup->oprrest == F_EQSEL) |
| { |
| oidEq = HeapTupleGetOid(ht); |
| break; |
| } |
| } |
| |
| if (InvalidOid == oidEq) |
| { |
| // no equality found between specified types, hence not a special comparison operator |
| caql_endscan(pcqCtx); |
| return CmptOther; |
| } |
| |
| int cmp_oids[][2] = |
| { |
| {oidEq, CmptEq}, |
| {get_negator(oidEq), CmptNEq}, |
| {optup->oprltcmpop, CmptLT}, |
| {optup->oprgtcmpop, CmptGT}, |
| {get_negator(optup->oprltcmpop), CmptGEq}, |
| {get_negator(optup->oprgtcmpop), CmptLEq} |
| }; |
| |
| for (int ul = 0; ul < 6; ul++) |
| { |
| int *map_elem = cmp_oids[ul]; |
| if (map_elem[1] == cmpt) |
| { |
| caql_endscan(pcqCtx); |
| return map_elem[0]; |
| } |
| } |
| |
| caql_endscan(pcqCtx); |
| |
| return InvalidOid; |
| } |
| |
| /* |
| * has_subclass_fast |
| * |
| * In the current implementation, has_subclass returns whether a |
| * particular class *might* have a subclass. It will not return the |
| * correct result if a class had a subclass which was later dropped. |
| * This is because relhassubclass in pg_class is not updated when a |
| * subclass is dropped, primarily because of concurrency concerns. |
| * |
| * Currently has_subclass is only used as an efficiency hack to skip |
| * unnecessary inheritance searches, so this is OK. |
| */ |
| bool |
| has_subclass_fast(Oid relationId) |
| { |
| HeapTuple tuple; |
| bool result; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_class " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(relationId))); |
| |
| tuple = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(tuple)) |
| elog(ERROR, "cache lookup failed for relation %u", relationId); |
| |
| result = ((Form_pg_class) GETSTRUCT(tuple))->relhassubclass; |
| |
| caql_endscan(pcqCtx); |
| return result; |
| } |
| |
| /* |
| * has_subclass |
| * |
| * Performs the exhaustive check whether a relation has a subclass. This is |
| * different from has_subclass_fast, in that the latter can return true if a relation. |
| * *might* have a subclass. See comments in has_subclass_fast for more details. |
| * |
| */ |
| bool |
| has_subclass(Oid relationId) |
| { |
| if (!has_subclass_fast(relationId)) |
| { |
| return false; |
| } |
| |
| return (0 < caql_getcount( |
| NULL, |
| cql("SELECT COUNT(*) FROM pg_inherits " |
| " WHERE inhparent = :1 ", |
| ObjectIdGetDatum(relationId)))); |
| } |
| |
| /* |
| * get_operator_opclasses |
| * Get the oid of classes the given operator belongs to |
| */ |
| List * |
| get_operator_opclasses(Oid opno) |
| { |
| cqContext *pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_amop " |
| " WHERE amopopr = :1 ", |
| ObjectIdGetDatum(opno))); |
| |
| HeapTuple htup = NULL; |
| List *opclass_oids = NIL; |
| while (HeapTupleIsValid(htup = caql_getnext(pcqCtx))) |
| { |
| Form_pg_amop amop_tuple = (Form_pg_amop) GETSTRUCT(htup); |
| |
| opclass_oids = lappend_oid(opclass_oids, amop_tuple->amopclaid); |
| } |
| |
| caql_endscan(pcqCtx); |
| |
| return opclass_oids; |
| } |
| |
| /* |
| * get_index_opclasses |
| * Get the oid of classes for the index keys |
| */ |
| List * |
| get_index_opclasses(Oid oidIndex) |
| { |
| cqContext *pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_index " |
| " WHERE indexrelid = :1 ", |
| ObjectIdGetDatum(oidIndex))); |
| |
| HeapTuple htup = caql_getnext(pcqCtx); |
| |
| if (!HeapTupleIsValid(htup)) |
| { |
| elog(ERROR, "Index %u not found", oidIndex); |
| } |
| |
| List *opclass_oids = NIL; |
| |
| /* |
| * use caql_getattr() to retrieve number of index attributes, and the oid vector of indclass |
| */ |
| bool isnull = false; |
| int indnatts = DatumGetInt16(caql_getattr(pcqCtx, Anum_pg_index_indnatts, &isnull)); |
| Assert(!isnull); |
| |
| Datum indclassDatum = caql_getattr(pcqCtx, Anum_pg_index_indclass, &isnull); |
| if (isnull) |
| { |
| return opclass_oids; |
| } |
| oidvector *indclass = (oidvector *) DatumGetPointer(indclassDatum); |
| |
| for (int i = 0; i < indnatts; i++) |
| { |
| Oid oidOpClass = indclass->values[i]; |
| opclass_oids = lappend_oid(opclass_oids, oidOpClass); |
| } |
| |
| caql_endscan(pcqCtx); |
| return opclass_oids; |
| } |
| |
| /* |
| * has_parquet_children |
| * Check if relation has a Parquet child relation |
| */ |
| bool |
| has_parquet_children(Oid relationId) |
| { |
| Assert(InvalidOid != relationId); |
| List *child_oids = find_all_inheritors(relationId); |
| ListCell *lc = NULL; |
| |
| foreach (lc, child_oids) |
| { |
| Oid oidChild = lfirst_oid(lc); |
| Relation rel = RelationIdGetRelation(oidChild); |
| Assert(NULL != rel); |
| if (RELSTORAGE_PARQUET == rel->rd_rel->relstorage) |
| { |
| list_free(child_oids); |
| RelationClose(rel); |
| return true; |
| } |
| |
| RelationClose(rel); |
| } |
| list_free(child_oids); |
| return false; |
| } |
| |
| /* |
| * relation_policy |
| * Return the distribution policy of a table. |
| */ |
| GpPolicy * |
| relation_policy(Relation rel) |
| { |
| Assert(NULL != rel); |
| |
| /* not a partitioned table */ |
| return rel->rd_cdbpolicy; |
| } |
| |
| /* |
| * child_distribution_mismatch |
| * Return true if the table is partitioned and one of its children has a |
| * different distribution policy. The only allowed mismatch is for the parent |
| * to be hash distributed, and its child part to be randomly distributed. |
| */ |
| bool |
| child_distribution_mismatch(Relation rel) |
| { |
| Assert(NULL != rel); |
| if (PART_STATUS_NONE == rel_part_status(rel->rd_id)) |
| { |
| /* not a partitioned table */ |
| return false; |
| } |
| |
| GpPolicy *rootPolicy = rel->rd_cdbpolicy; |
| Assert(NULL != rootPolicy && "Partitioned tables cannot be master-only"); |
| |
| if (POLICYTYPE_PARTITIONED == rootPolicy->ptype && 0 == rootPolicy->nattrs) |
| { |
| /* root partition policy already marked as Random, no mismatch possible as |
| * all children must be random as well */ |
| return false; |
| } |
| |
| List *child_oids = find_all_inheritors(rel->rd_id); |
| ListCell *lc = NULL; |
| |
| foreach (lc, child_oids) |
| { |
| Oid oidChild = lfirst_oid(lc); |
| Relation relChild = RelationIdGetRelation(oidChild); |
| Assert(NULL != relChild); |
| |
| GpPolicy *childPolicy = relChild->rd_cdbpolicy; |
| if (POLICYTYPE_PARTITIONED == childPolicy->ptype && 0 == childPolicy->nattrs) |
| { |
| /* child partition is Random, and parent is not */ |
| RelationClose(relChild); |
| return true; |
| } |
| |
| RelationClose(relChild); |
| } |
| |
| list_free(child_oids); |
| |
| /* all children match the root's distribution policy */ |
| return false; |
| } |
| |
| /* |
| * child_triggers |
| * Return true if the table is partitioned and any of the child partitions |
| * have a trigger of the given type. |
| */ |
| bool |
| child_triggers(Oid relationId, int32 triggerType) |
| { |
| Assert(InvalidOid != relationId); |
| if (PART_STATUS_NONE == rel_part_status(relationId)) |
| { |
| /* not a partitioned table */ |
| return false; |
| } |
| |
| List *childOids = find_all_inheritors(relationId); |
| ListCell *lc = NULL; |
| |
| bool found = false; |
| foreach (lc, childOids) |
| { |
| Oid oidChild = lfirst_oid(lc); |
| Relation relChild = RelationIdGetRelation(oidChild); |
| Assert(NULL != relChild); |
| |
| if (0 < relChild->rd_rel->reltriggers && NULL == relChild->trigdesc) |
| { |
| RelationBuildTriggers(relChild); |
| if (NULL == relChild->trigdesc) |
| { |
| relChild->rd_rel->reltriggers = 0; |
| } |
| } |
| |
| for (int i = 0; i < relChild->rd_rel->reltriggers && !found; i++) |
| { |
| Trigger trigger = relChild->trigdesc->triggers[i]; |
| found = trigger_enabled(trigger.tgoid) && |
| (get_trigger_type(trigger.tgoid) & triggerType) == triggerType; |
| } |
| |
| RelationClose(relChild); |
| if (found) |
| { |
| break; |
| } |
| } |
| |
| list_free(childOids); |
| |
| /* no child triggers matching the given type */ |
| return found; |
| } |