X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Futils%2Fcache%2Fsyscache.c;h=8779125d1591d4554ee0c9c8809626df6dd1b6de;hb=a6fd7b7a5f7bf3a8aa3f3d076cf09d922c1c6dd2;hp=204f13ffab9f34aa3a92a1a659d37f86cb76ce90;hpb=bd272cace63effa23d68a6b344a07e326b6a41e2;p=postgresql diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 204f13ffab..922718c9d1 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -3,76 +3,192 @@ * syscache.c * System cache management routines * - * Copyright (c) 1994, Regents of the University of California + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/cache/syscache.c,v 1.36 1999/09/18 19:07:55 tgl Exp $ + * src/backend/utils/cache/syscache.c * * NOTES * These routines allow the parser/planner/executor to perform * rapid lookups on the contents of the system catalogs. * - * see catalog/syscache.h for a list of the cache id's + * see utils/syscache.h for a list of the cache IDs * *------------------------------------------------------------------------- */ #include "postgres.h" -#include "access/heapam.h" -#include "catalog/catname.h" +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "catalog/indexing.h" #include "catalog/pg_aggregate.h" +#include "catalog/pg_am.h" #include "catalog/pg_amop.h" -#include "catalog/pg_group.h" -#include "catalog/pg_index.h" -#include "catalog/pg_inherits.h" +#include "catalog/pg_amproc.h" +#include "catalog/pg_auth_members.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_cast.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_constraint.h" +#include "catalog/pg_conversion.h" +#include "catalog/pg_database.h" +#include "catalog/pg_db_role_setting.h" +#include "catalog/pg_default_acl.h" +#include "catalog/pg_depend.h" +#include "catalog/pg_description.h" +#include "catalog/pg_enum.h" +#include "catalog/pg_event_trigger.h" +#include "catalog/pg_foreign_data_wrapper.h" +#include "catalog/pg_foreign_server.h" +#include "catalog/pg_foreign_table.h" #include "catalog/pg_language.h" -#include "catalog/pg_listener.h" +#include "catalog/pg_namespace.h" #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" +#include "catalog/pg_opfamily.h" +#include "catalog/pg_partitioned_table.h" #include "catalog/pg_proc.h" +#include "catalog/pg_publication.h" +#include "catalog/pg_publication_rel.h" +#include "catalog/pg_range.h" #include "catalog/pg_rewrite.h" -#include "catalog/pg_shadow.h" +#include "catalog/pg_seclabel.h" +#include "catalog/pg_sequence.h" +#include "catalog/pg_shdepend.h" +#include "catalog/pg_shdescription.h" +#include "catalog/pg_shseclabel.h" +#include "catalog/pg_replication_origin.h" +#include "catalog/pg_statistic.h" +#include "catalog/pg_statistic_ext.h" +#include "catalog/pg_subscription.h" +#include "catalog/pg_subscription_rel.h" +#include "catalog/pg_tablespace.h" +#include "catalog/pg_transform.h" +#include "catalog/pg_ts_config.h" +#include "catalog/pg_ts_config_map.h" +#include "catalog/pg_ts_dict.h" +#include "catalog/pg_ts_parser.h" +#include "catalog/pg_ts_template.h" #include "catalog/pg_type.h" +#include "catalog/pg_user_mapping.h" +#include "utils/rel.h" #include "utils/catcache.h" -#include "utils/temprel.h" +#include "utils/syscache.h" -extern bool AMI_OVERRIDE; /* XXX style */ -#include "utils/syscache.h" -#include "catalog/indexing.h" +/*--------------------------------------------------------------------------- + + Adding system caches: + + Add your new cache to the list in include/utils/syscache.h. + Keep the list sorted alphabetically. + + Add your entry to the cacheinfo[] array below. All cache lists are + alphabetical, so add it in the proper place. Specify the relation OID, + index OID, number of keys, key attribute numbers, and initial number of + hash buckets. + + The number of hash buckets must be a power of 2. It's reasonable to + set this to the number of entries that might be in the particular cache + in a medium-size database. -typedef HeapTuple (*ScanFunc) (); + There must be a unique index underlying each syscache (ie, an index + whose key is the same as that of the cache). If there is not one + already, add definitions for it to include/catalog/indexing.h: you need + to add a DECLARE_UNIQUE_INDEX macro and a #define for the index OID. + (Adding an index requires a catversion.h update, while simply + adding/deleting caches only requires a recompile.) -/* ---------------- - * Warning: cacheinfo[] below is changed, then be sure and - * update the magic constants in syscache.h! - * ---------------- + Finally, any place your relation gets heap_insert() or + heap_update() calls, use CatalogTupleInsert() or CatalogTupleUpdate() + instead, which also update indexes. The heap_* calls do not do that. + +*--------------------------------------------------------------------------- +*/ + +/* + * struct cachedesc: information defining a single syscache */ -static struct cachedesc cacheinfo[] = { - {AccessMethodOperatorRelationName, /* AMOPOPID */ - 3, +struct cachedesc +{ + Oid reloid; /* OID of the relation being cached */ + Oid indoid; /* OID of index relation for this cache */ + int nkeys; /* # of keys needed for cache lookup */ + int key[4]; /* attribute numbers of key attrs */ + int nbuckets; /* number of hash buckets for this cache */ +}; + +static const struct cachedesc cacheinfo[] = { + {AggregateRelationId, /* AGGFNOID */ + AggregateFnoidIndexId, + 1, { - Anum_pg_amop_amopclaid, - Anum_pg_amop_amopopr, - Anum_pg_amop_amopid, + Anum_pg_aggregate_aggfnoid, + 0, + 0, + 0 + }, + 16 + }, + {AccessMethodRelationId, /* AMNAME */ + AmNameIndexId, + 1, + { + Anum_pg_am_amname, + 0, + 0, + 0 + }, + 4 + }, + {AccessMethodRelationId, /* AMOID */ + AmOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, 0 }, - sizeof(FormData_pg_amop), - NULL, - (ScanFunc) NULL}, - {AccessMethodOperatorRelationName, /* AMOPSTRATEGY */ + 4 + }, + {AccessMethodOperatorRelationId, /* AMOPOPID */ + AccessMethodOperatorIndexId, 3, { - Anum_pg_amop_amopid, - Anum_pg_amop_amopclaid, - Anum_pg_amop_amopstrategy, + Anum_pg_amop_amopopr, + Anum_pg_amop_amoppurpose, + Anum_pg_amop_amopfamily, 0 }, - sizeof(FormData_pg_amop), - NULL, - (ScanFunc) NULL}, - {AttributeRelationName, /* ATTNAME */ + 64 + }, + {AccessMethodOperatorRelationId, /* AMOPSTRATEGY */ + AccessMethodStrategyIndexId, + 4, + { + Anum_pg_amop_amopfamily, + Anum_pg_amop_amoplefttype, + Anum_pg_amop_amoprighttype, + Anum_pg_amop_amopstrategy + }, + 64 + }, + {AccessMethodProcedureRelationId, /* AMPROCNUM */ + AccessMethodProcedureIndexId, + 4, + { + Anum_pg_amproc_amprocfamily, + Anum_pg_amproc_amproclefttype, + Anum_pg_amproc_amprocrighttype, + Anum_pg_amproc_amprocnum + }, + 16 + }, + {AttributeRelationId, /* ATTNAME */ + AttributeRelidNameIndexId, 2, { Anum_pg_attribute_attrelid, @@ -80,10 +196,10 @@ static struct cachedesc cacheinfo[] = { 0, 0 }, - ATTRIBUTE_TUPLE_SIZE, - AttributeNameIndex, - (ScanFunc) AttributeNameIndexScan}, - {AttributeRelationName, /* ATTNUM */ + 32 + }, + {AttributeRelationId, /* ATTNUM */ + AttributeRelidNumIndexId, 2, { Anum_pg_attribute_attrelid, @@ -91,10 +207,275 @@ static struct cachedesc cacheinfo[] = { 0, 0 }, - ATTRIBUTE_TUPLE_SIZE, - AttributeNumIndex, - (ScanFunc) AttributeNumIndexScan}, - {IndexRelationName, /* INDEXRELID */ + 128 + }, + {AuthMemRelationId, /* AUTHMEMMEMROLE */ + AuthMemMemRoleIndexId, + 2, + { + Anum_pg_auth_members_member, + Anum_pg_auth_members_roleid, + 0, + 0 + }, + 8 + }, + {AuthMemRelationId, /* AUTHMEMROLEMEM */ + AuthMemRoleMemIndexId, + 2, + { + Anum_pg_auth_members_roleid, + Anum_pg_auth_members_member, + 0, + 0 + }, + 8 + }, + {AuthIdRelationId, /* AUTHNAME */ + AuthIdRolnameIndexId, + 1, + { + Anum_pg_authid_rolname, + 0, + 0, + 0 + }, + 8 + }, + {AuthIdRelationId, /* AUTHOID */ + AuthIdOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 8 + }, + { + CastRelationId, /* CASTSOURCETARGET */ + CastSourceTargetIndexId, + 2, + { + Anum_pg_cast_castsource, + Anum_pg_cast_casttarget, + 0, + 0 + }, + 256 + }, + {OperatorClassRelationId, /* CLAAMNAMENSP */ + OpclassAmNameNspIndexId, + 3, + { + Anum_pg_opclass_opcmethod, + Anum_pg_opclass_opcname, + Anum_pg_opclass_opcnamespace, + 0 + }, + 8 + }, + {OperatorClassRelationId, /* CLAOID */ + OpclassOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 8 + }, + {CollationRelationId, /* COLLNAMEENCNSP */ + CollationNameEncNspIndexId, + 3, + { + Anum_pg_collation_collname, + Anum_pg_collation_collencoding, + Anum_pg_collation_collnamespace, + 0 + }, + 8 + }, + {CollationRelationId, /* COLLOID */ + CollationOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 8 + }, + {ConversionRelationId, /* CONDEFAULT */ + ConversionDefaultIndexId, + 4, + { + Anum_pg_conversion_connamespace, + Anum_pg_conversion_conforencoding, + Anum_pg_conversion_contoencoding, + ObjectIdAttributeNumber, + }, + 8 + }, + {ConversionRelationId, /* CONNAMENSP */ + ConversionNameNspIndexId, + 2, + { + Anum_pg_conversion_conname, + Anum_pg_conversion_connamespace, + 0, + 0 + }, + 8 + }, + {ConstraintRelationId, /* CONSTROID */ + ConstraintOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 16 + }, + {ConversionRelationId, /* CONVOID */ + ConversionOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 8 + }, + {DatabaseRelationId, /* DATABASEOID */ + DatabaseOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 4 + }, + {DefaultAclRelationId, /* DEFACLROLENSPOBJ */ + DefaultAclRoleNspObjIndexId, + 3, + { + Anum_pg_default_acl_defaclrole, + Anum_pg_default_acl_defaclnamespace, + Anum_pg_default_acl_defaclobjtype, + 0 + }, + 8 + }, + {EnumRelationId, /* ENUMOID */ + EnumOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 8 + }, + {EnumRelationId, /* ENUMTYPOIDNAME */ + EnumTypIdLabelIndexId, + 2, + { + Anum_pg_enum_enumtypid, + Anum_pg_enum_enumlabel, + 0, + 0 + }, + 8 + }, + {EventTriggerRelationId, /* EVENTTRIGGERNAME */ + EventTriggerNameIndexId, + 1, + { + Anum_pg_event_trigger_evtname, + 0, + 0, + 0 + }, + 8 + }, + {EventTriggerRelationId, /* EVENTTRIGGEROID */ + EventTriggerOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 8 + }, + {ForeignDataWrapperRelationId, /* FOREIGNDATAWRAPPERNAME */ + ForeignDataWrapperNameIndexId, + 1, + { + Anum_pg_foreign_data_wrapper_fdwname, + 0, + 0, + 0 + }, + 2 + }, + {ForeignDataWrapperRelationId, /* FOREIGNDATAWRAPPEROID */ + ForeignDataWrapperOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 2 + }, + {ForeignServerRelationId, /* FOREIGNSERVERNAME */ + ForeignServerNameIndexId, + 1, + { + Anum_pg_foreign_server_srvname, + 0, + 0, + 0 + }, + 2 + }, + {ForeignServerRelationId, /* FOREIGNSERVEROID */ + ForeignServerOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 2 + }, + {ForeignTableRelationId, /* FOREIGNTABLEREL */ + ForeignTableRelidIndexId, + 1, + { + Anum_pg_foreign_table_ftrelid, + 0, + 0, + 0 + }, + 4 + }, + {IndexRelationId, /* INDEXRELID */ + IndexRelidIndexId, 1, { Anum_pg_index_indexrelid, @@ -102,10 +483,10 @@ static struct cachedesc cacheinfo[] = { 0, 0 }, - offsetof(FormData_pg_index, indpred), - NULL, - NULL}, - {LanguageRelationName, /* LANNAME */ + 64 + }, + {LanguageRelationId, /* LANGNAME */ + LanguageNameIndexId, 1, { Anum_pg_language_lanname, @@ -113,21 +494,76 @@ static struct cachedesc cacheinfo[] = { 0, 0 }, - offsetof(FormData_pg_language, lancompiler), - NULL, - NULL}, - {OperatorRelationName, /* OPRNAME */ + 4 + }, + {LanguageRelationId, /* LANGOID */ + LanguageOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 4 + }, + {NamespaceRelationId, /* NAMESPACENAME */ + NamespaceNameIndexId, + 1, + { + Anum_pg_namespace_nspname, + 0, + 0, + 0 + }, + 4 + }, + {NamespaceRelationId, /* NAMESPACEOID */ + NamespaceOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 16 + }, + {OperatorRelationId, /* OPERNAMENSP */ + OperatorNameNspIndexId, 4, { Anum_pg_operator_oprname, Anum_pg_operator_oprleft, Anum_pg_operator_oprright, - Anum_pg_operator_oprkind + Anum_pg_operator_oprnamespace + }, + 256 + }, + {OperatorRelationId, /* OPEROID */ + OperatorOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 }, - sizeof(FormData_pg_operator), - NULL, - NULL}, - {OperatorRelationName, /* OPROID */ + 32 + }, + {OperatorFamilyRelationId, /* OPFAMILYAMNAMENSP */ + OpfamilyAmNameNspIndexId, + 3, + { + Anum_pg_opfamily_opfmethod, + Anum_pg_opfamily_opfname, + Anum_pg_opfamily_opfnamespace, + 0 + }, + 8 + }, + {OperatorFamilyRelationId, /* OPFAMILYOID */ + OpfamilyOidIndexId, 1, { ObjectIdAttributeNumber, @@ -135,21 +571,32 @@ static struct cachedesc cacheinfo[] = { 0, 0 }, - sizeof(FormData_pg_operator), - NULL, - (ScanFunc) NULL}, - {ProcedureRelationName, /* PRONAME */ + 8 + }, + {PartitionedRelationId, /* PARTRELID */ + PartitionedRelidIndexId, + 1, + { + Anum_pg_partitioned_table_partrelid, + 0, + 0, + 0 + }, + 32 + }, + {ProcedureRelationId, /* PROCNAMEARGSNSP */ + ProcedureNameArgsNspIndexId, 3, { Anum_pg_proc_proname, - Anum_pg_proc_pronargs, Anum_pg_proc_proargtypes, + Anum_pg_proc_pronamespace, 0 }, - offsetof(FormData_pg_proc, prosrc), - ProcedureNameIndex, - (ScanFunc) ProcedureNameIndexScan}, - {ProcedureRelationName, /* PROOID */ + 128 + }, + {ProcedureRelationId, /* PROCOID */ + ProcedureOidIndexId, 1, { ObjectIdAttributeNumber, @@ -157,21 +604,32 @@ static struct cachedesc cacheinfo[] = { 0, 0 }, - offsetof(FormData_pg_proc, prosrc), - ProcedureOidIndex, - (ScanFunc) ProcedureOidIndexScan}, - {RelationRelationName, /* RELNAME */ + 128 + }, + {RangeRelationId, /* RANGETYPE */ + RangeTypidIndexId, 1, { - Anum_pg_class_relname, + Anum_pg_range_rngtypid, + 0, 0, + 0 + }, + 4 + }, + {RelationRelationId, /* RELNAMENSP */ + ClassNameNspIndexId, + 2, + { + Anum_pg_class_relname, + Anum_pg_class_relnamespace, 0, 0 }, - CLASS_TUPLE_SIZE, - ClassNameIndex, - (ScanFunc) ClassNameIndexScan}, - {RelationRelationName, /* RELOID */ + 128 + }, + {RelationRelationId, /* RELOID */ + ClassOidIndexId, 1, { ObjectIdAttributeNumber, @@ -179,21 +637,32 @@ static struct cachedesc cacheinfo[] = { 0, 0 }, - CLASS_TUPLE_SIZE, - ClassOidIndex, - (ScanFunc) ClassOidIndexScan}, - {TypeRelationName, /* TYPNAME */ + 128 + }, + {ReplicationOriginRelationId, /* REPLORIGIDENT */ + ReplicationOriginIdentIndex, 1, { - Anum_pg_type_typname, + Anum_pg_replication_origin_roident, + 0, + 0, + 0 + }, + 16 + }, + {ReplicationOriginRelationId, /* REPLORIGNAME */ + ReplicationOriginNameIndex, + 1, + { + Anum_pg_replication_origin_roname, 0, 0, 0 }, - offsetof(FormData_pg_type, typalign) +sizeof(char), - TypeNameIndex, - TypeNameIndexScan}, - {TypeRelationName, /* TYPOID */ + 16 + }, + {PublicationRelationId, /* PUBLICATIONOID */ + PublicationObjectIndexId, 1, { ObjectIdAttributeNumber, @@ -201,54 +670,98 @@ static struct cachedesc cacheinfo[] = { 0, 0 }, - offsetof(FormData_pg_type, typalign) +sizeof(char), - TypeOidIndex, - TypeOidIndexScan}, - {AccessMethodRelationName, /* AMNAME */ + 8 + }, + {PublicationRelationId, /* PUBLICATIONNAME */ + PublicationNameIndexId, 1, { - Anum_pg_am_amname, + Anum_pg_publication_pubname, 0, 0, 0 }, - sizeof(FormData_pg_am), - NULL, - NULL}, - {OperatorClassRelationName, /* CLANAME */ + 8 + }, + {PublicationRelRelationId, /* PUBLICATIONREL */ + PublicationRelObjectIndexId, 1, { - Anum_pg_opclass_opcname, + ObjectIdAttributeNumber, 0, 0, 0 }, - sizeof(FormData_pg_opclass), - NULL, - NULL}, - {IndexRelationName, /* INDRELIDKEY *//* never used */ + 64 + }, + {PublicationRelRelationId, /* PUBLICATIONRELMAP */ + PublicationRelPrrelidPrpubidIndexId, 2, { - Anum_pg_index_indrelid, - Anum_pg_index_indkey, + Anum_pg_publication_rel_prrelid, + Anum_pg_publication_rel_prpubid, 0, 0 }, - offsetof(FormData_pg_index, indpred), - NULL, - (ScanFunc) NULL}, - {InheritsRelationName, /* INHRELID */ + 64 + }, + {RewriteRelationId, /* RULERELNAME */ + RewriteRelRulenameIndexId, 2, { - Anum_pg_inherits_inhrel, - Anum_pg_inherits_inhseqno, + Anum_pg_rewrite_ev_class, + Anum_pg_rewrite_rulename, + 0, + 0 + }, + 8 + }, + {SequenceRelationId, /* SEQRELID */ + SequenceRelidIndexId, + 1, + { + Anum_pg_sequence_seqrelid, + 0, 0, 0 }, - sizeof(FormData_pg_inherits), - NULL, - (ScanFunc) NULL}, - {RewriteRelationName, /* RULOID */ + 32 + }, + {StatisticExtRelationId, /* STATEXTNAMENSP */ + StatisticExtNameIndexId, + 2, + { + Anum_pg_statistic_ext_stxname, + Anum_pg_statistic_ext_stxnamespace, + 0, + 0 + }, + 4 + }, + {StatisticExtRelationId, /* STATEXTOID */ + StatisticExtOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 4 + }, + {StatisticRelationId, /* STATRELATTINH */ + StatisticRelidAttnumInhIndexId, + 3, + { + Anum_pg_statistic_starelid, + Anum_pg_statistic_staattnum, + Anum_pg_statistic_stainherit, + 0 + }, + 128 + }, + {SubscriptionRelationId, /* SUBSCRIPTIONOID */ + SubscriptionObjectIndexId, 1, { ObjectIdAttributeNumber, @@ -256,109 +769,186 @@ static struct cachedesc cacheinfo[] = { 0, 0 }, - offsetof(FormData_pg_rewrite, ev_qual), - NULL, - (ScanFunc) NULL}, - {AggregateRelationName, /* AGGNAME */ + 4 + }, + {SubscriptionRelationId, /* SUBSCRIPTIONNAME */ + SubscriptionNameIndexId, 2, { - Anum_pg_aggregate_aggname, - Anum_pg_aggregate_aggbasetype, + Anum_pg_subscription_subdbid, + Anum_pg_subscription_subname, 0, 0 }, - offsetof(FormData_pg_aggregate, agginitval1), - NULL, - (ScanFunc) NULL}, - {ListenerRelationName, /* LISTENREL */ + 4 + }, + {SubscriptionRelRelationId, /* SUBSCRIPTIONRELMAP */ + SubscriptionRelSrrelidSrsubidIndexId, 2, { - Anum_pg_listener_relname, - Anum_pg_listener_pid, + Anum_pg_subscription_rel_srrelid, + Anum_pg_subscription_rel_srsubid, 0, 0 }, - sizeof(FormData_pg_listener), - NULL, - (ScanFunc) NULL}, - {ShadowRelationName, /* USENAME */ + 64 + }, + {TableSpaceRelationId, /* TABLESPACEOID */ + TablespaceOidIndexId, 1, { - Anum_pg_shadow_usename, + ObjectIdAttributeNumber, + 0, 0, 0, - 0 }, - sizeof(FormData_pg_shadow), - NULL, - (ScanFunc) NULL}, - {ShadowRelationName, /* USESYSID */ + 4 + }, + {TransformRelationId, /* TRFOID */ + TransformOidIndexId, 1, { - Anum_pg_shadow_usesysid, + ObjectIdAttributeNumber, 0, 0, + 0, + }, + 16 + }, + {TransformRelationId, /* TRFTYPELANG */ + TransformTypeLangIndexId, + 2, + { + Anum_pg_transform_trftype, + Anum_pg_transform_trflang, + 0, + 0, + }, + 16 + }, + {TSConfigMapRelationId, /* TSCONFIGMAP */ + TSConfigMapIndexId, + 3, + { + Anum_pg_ts_config_map_mapcfg, + Anum_pg_ts_config_map_maptokentype, + Anum_pg_ts_config_map_mapseqno, 0 }, - sizeof(FormData_pg_shadow), - NULL, - (ScanFunc) NULL}, - {GroupRelationName, /* GRONAME */ + 2 + }, + {TSConfigRelationId, /* TSCONFIGNAMENSP */ + TSConfigNameNspIndexId, + 2, + { + Anum_pg_ts_config_cfgname, + Anum_pg_ts_config_cfgnamespace, + 0, + 0 + }, + 2 + }, + {TSConfigRelationId, /* TSCONFIGOID */ + TSConfigOidIndexId, 1, { - Anum_pg_group_groname, + ObjectIdAttributeNumber, + 0, 0, + 0 + }, + 2 + }, + {TSDictionaryRelationId, /* TSDICTNAMENSP */ + TSDictionaryNameNspIndexId, + 2, + { + Anum_pg_ts_dict_dictname, + Anum_pg_ts_dict_dictnamespace, 0, 0 }, - offsetof(FormData_pg_group, grolist[0]), - NULL, - (ScanFunc) NULL}, - {GroupRelationName, /* GROSYSID */ + 2 + }, + {TSDictionaryRelationId, /* TSDICTOID */ + TSDictionaryOidIndexId, 1, { - Anum_pg_group_grosysid, + ObjectIdAttributeNumber, 0, 0, 0 }, - offsetof(FormData_pg_group, grolist[0]), - NULL, - (ScanFunc) NULL}, - {RewriteRelationName, /* REWRITENAME */ + 2 + }, + {TSParserRelationId, /* TSPARSERNAMENSP */ + TSParserNameNspIndexId, + 2, + { + Anum_pg_ts_parser_prsname, + Anum_pg_ts_parser_prsnamespace, + 0, + 0 + }, + 2 + }, + {TSParserRelationId, /* TSPARSEROID */ + TSParserOidIndexId, 1, { - Anum_pg_rewrite_rulename, + ObjectIdAttributeNumber, 0, 0, 0 }, - offsetof(FormData_pg_rewrite, ev_qual), - NULL, - (ScanFunc) NULL}, - {ProcedureRelationName, /* PROSRC */ + 2 + }, + {TSTemplateRelationId, /* TSTEMPLATENAMENSP */ + TSTemplateNameNspIndexId, + 2, + { + Anum_pg_ts_template_tmplname, + Anum_pg_ts_template_tmplnamespace, + 0, + 0 + }, + 2 + }, + {TSTemplateRelationId, /* TSTEMPLATEOID */ + TSTemplateOidIndexId, 1, { - Anum_pg_proc_prosrc, + ObjectIdAttributeNumber, 0, 0, 0 }, - offsetof(FormData_pg_proc, prosrc), - ProcedureSrcIndex, - (ScanFunc) ProcedureSrcIndexScan}, - {OperatorClassRelationName, /* CLADEFTYPE */ + 2 + }, + {TypeRelationId, /* TYPENAMENSP */ + TypeNameNspIndexId, + 2, + { + Anum_pg_type_typname, + Anum_pg_type_typnamespace, + 0, + 0 + }, + 64 + }, + {TypeRelationId, /* TYPEOID */ + TypeOidIndexId, 1, { - Anum_pg_opclass_opcdeftype, + ObjectIdAttributeNumber, 0, 0, 0 }, - sizeof(FormData_pg_opclass), - NULL, - (ScanFunc) NULL}, - {LanguageRelationName, /* LANOID */ + 64 + }, + {UserMappingRelationId, /* USERMAPPINGOID */ + UserMappingOidIndexId, 1, { ObjectIdAttributeNumber, @@ -366,286 +956,501 @@ static struct cachedesc cacheinfo[] = { 0, 0 }, - offsetof(FormData_pg_language, lancompiler), - NULL, - NULL} + 2 + }, + {UserMappingRelationId, /* USERMAPPINGUSERSERVER */ + UserMappingUserServerIndexId, + 2, + { + Anum_pg_user_mapping_umuser, + Anum_pg_user_mapping_umserver, + 0, + 0 + }, + 2 + } }; -static struct catcache *SysCache[lengthof(cacheinfo)]; -static int32 SysCacheSize = lengthof(cacheinfo); +static CatCache *SysCache[SysCacheSize]; + +static bool CacheInitialized = false; + +/* Sorted array of OIDs of tables that have caches on them */ +static Oid SysCacheRelationOid[SysCacheSize]; +static int SysCacheRelationOidSize; + +/* Sorted array of OIDs of tables and indexes used by caches */ +static Oid SysCacheSupportingRelOid[SysCacheSize * 2]; +static int SysCacheSupportingRelOidSize; + +static int oid_compare(const void *a, const void *b); /* - * zerocaches + * InitCatalogCache - initialize the caches * - * Make sure the SysCache structure is zero'd. + * Note that no database access is done here; we only allocate memory + * and initialize the cache structure. Interrogation of the database + * to complete initialization of a cache happens upon first use + * of that cache. */ void -zerocaches() +InitCatalogCache(void) { - MemSet((char *) SysCache, 0, SysCacheSize * sizeof(struct catcache *)); + int cacheId; + int i, + j; + + StaticAssertStmt(SysCacheSize == (int) lengthof(cacheinfo), + "SysCacheSize does not match syscache.c's array"); + + Assert(!CacheInitialized); + + SysCacheRelationOidSize = SysCacheSupportingRelOidSize = 0; + + for (cacheId = 0; cacheId < SysCacheSize; cacheId++) + { + SysCache[cacheId] = InitCatCache(cacheId, + cacheinfo[cacheId].reloid, + cacheinfo[cacheId].indoid, + cacheinfo[cacheId].nkeys, + cacheinfo[cacheId].key, + cacheinfo[cacheId].nbuckets); + if (!PointerIsValid(SysCache[cacheId])) + elog(ERROR, "could not initialize cache %u (%d)", + cacheinfo[cacheId].reloid, cacheId); + /* Accumulate data for OID lists, too */ + SysCacheRelationOid[SysCacheRelationOidSize++] = + cacheinfo[cacheId].reloid; + SysCacheSupportingRelOid[SysCacheSupportingRelOidSize++] = + cacheinfo[cacheId].reloid; + SysCacheSupportingRelOid[SysCacheSupportingRelOidSize++] = + cacheinfo[cacheId].indoid; + /* see comments for RelationInvalidatesSnapshotsOnly */ + Assert(!RelationInvalidatesSnapshotsOnly(cacheinfo[cacheId].reloid)); + } + + Assert(SysCacheRelationOidSize <= lengthof(SysCacheRelationOid)); + Assert(SysCacheSupportingRelOidSize <= lengthof(SysCacheSupportingRelOid)); + + /* Sort and de-dup OID arrays, so we can use binary search. */ + pg_qsort(SysCacheRelationOid, SysCacheRelationOidSize, + sizeof(Oid), oid_compare); + for (i = 1, j = 0; i < SysCacheRelationOidSize; i++) + { + if (SysCacheRelationOid[i] != SysCacheRelationOid[j]) + SysCacheRelationOid[++j] = SysCacheRelationOid[i]; + } + SysCacheRelationOidSize = j + 1; + + pg_qsort(SysCacheSupportingRelOid, SysCacheSupportingRelOidSize, + sizeof(Oid), oid_compare); + for (i = 1, j = 0; i < SysCacheSupportingRelOidSize; i++) + { + if (SysCacheSupportingRelOid[i] != SysCacheSupportingRelOid[j]) + SysCacheSupportingRelOid[++j] = SysCacheSupportingRelOid[i]; + } + SysCacheSupportingRelOidSize = j + 1; + + CacheInitialized = true; } /* - * Note: - * This function was written because the initialized catalog caches - * are used to determine which caches may contain tuples which need - * to be invalidated in other backends. + * InitCatalogCachePhase2 - finish initializing the caches + * + * Finish initializing all the caches, including necessary database + * access. + * + * This is *not* essential; normally we allow syscaches to be initialized + * on first use. However, it is useful as a mechanism to preload the + * relcache with entries for the most-commonly-used system catalogs. + * Therefore, we invoke this routine when we need to write a new relcache + * init file. */ void -InitCatalogCache() +InitCatalogCachePhase2(void) { - int cacheId; /* XXX type */ + int cacheId; - if (!AMI_OVERRIDE) - { - for (cacheId = 0; cacheId < SysCacheSize; cacheId += 1) - { - - Assert(!PointerIsValid((Pointer) SysCache[cacheId])); - - SysCache[cacheId] = InitSysCache(cacheinfo[cacheId].name, - cacheinfo[cacheId].indname, - cacheId, - cacheinfo[cacheId].nkeys, - cacheinfo[cacheId].key, - cacheinfo[cacheId].iScanFunc); - if (!PointerIsValid((char *) SysCache[cacheId])) - { - elog(ERROR, - "InitCatalogCache: Can't init cache %s(%d)", - cacheinfo[cacheId].name, - cacheId); - } - - } - } + Assert(CacheInitialized); + + for (cacheId = 0; cacheId < SysCacheSize; cacheId++) + InitCatCachePhase2(SysCache[cacheId], true); } + /* - * SearchSysCacheTupleCopy + * SearchSysCache + * + * A layer on top of SearchCatCache that does the initialization and + * key-setting for you. * - * This is like SearchSysCacheTuple, except it returns a copy of the tuple - * that the user is required to pfree(). + * Returns the cache copy of the tuple if one is found, NULL if not. + * The tuple is the 'cache' copy and must NOT be modified! + * + * When the caller is done using the tuple, call ReleaseSysCache() + * to release the reference count grabbed by SearchSysCache(). If this + * is not done, the tuple will remain locked in cache until end of + * transaction, which is tolerable but not desirable. + * + * CAUTION: The tuple that is returned must NOT be freed by the caller! */ HeapTuple -SearchSysCacheTupleCopy(int cacheId, /* cache selection code */ - Datum key1, - Datum key2, - Datum key3, - Datum key4) +SearchSysCache(int cacheId, + Datum key1, + Datum key2, + Datum key3, + Datum key4) { - HeapTuple cachetup; + if (cacheId < 0 || cacheId >= SysCacheSize || + !PointerIsValid(SysCache[cacheId])) + elog(ERROR, "invalid cache ID: %d", cacheId); - cachetup = SearchSysCacheTuple(cacheId, key1, key2, key3, key4); - if (PointerIsValid(cachetup)) - return heap_copytuple(cachetup); - else - return cachetup; /* NULL */ + return SearchCatCache(SysCache[cacheId], key1, key2, key3, key4); } +/* + * ReleaseSysCache + * Release previously grabbed reference count on a tuple + */ +void +ReleaseSysCache(HeapTuple tuple) +{ + ReleaseCatCache(tuple); +} /* - * SearchSysCacheTuple + * SearchSysCacheCopy * - * A layer on top of SearchSysCache that does the initialization and - * key-setting for you. + * A convenience routine that does SearchSysCache and (if successful) + * returns a modifiable copy of the syscache entry. The original + * syscache entry is released before returning. The caller should + * heap_freetuple() the result when done with it. + */ +HeapTuple +SearchSysCacheCopy(int cacheId, + Datum key1, + Datum key2, + Datum key3, + Datum key4) +{ + HeapTuple tuple, + newtuple; + + tuple = SearchSysCache(cacheId, key1, key2, key3, key4); + if (!HeapTupleIsValid(tuple)) + return tuple; + newtuple = heap_copytuple(tuple); + ReleaseSysCache(tuple); + return newtuple; +} + +/* + * SearchSysCacheExists * - * Returns the cache copy of the tuple if one is found, NULL if not. - * The tuple is the 'cache' copy. + * A convenience routine that just probes to see if a tuple can be found. + * No lock is retained on the syscache entry. + */ +bool +SearchSysCacheExists(int cacheId, + Datum key1, + Datum key2, + Datum key3, + Datum key4) +{ + HeapTuple tuple; + + tuple = SearchSysCache(cacheId, key1, key2, key3, key4); + if (!HeapTupleIsValid(tuple)) + return false; + ReleaseSysCache(tuple); + return true; +} + +/* + * GetSysCacheOid * - * XXX The tuple that is returned is NOT supposed to be pfree'd! + * A convenience routine that does SearchSysCache and returns the OID + * of the found tuple, or InvalidOid if no tuple could be found. + * No lock is retained on the syscache entry. + */ +Oid +GetSysCacheOid(int cacheId, + Datum key1, + Datum key2, + Datum key3, + Datum key4) +{ + HeapTuple tuple; + Oid result; + + tuple = SearchSysCache(cacheId, key1, key2, key3, key4); + if (!HeapTupleIsValid(tuple)) + return InvalidOid; + result = HeapTupleGetOid(tuple); + ReleaseSysCache(tuple); + return result; +} + + +/* + * SearchSysCacheAttName + * + * This routine is equivalent to SearchSysCache on the ATTNAME cache, + * except that it will return NULL if the found attribute is marked + * attisdropped. This is convenient for callers that want to act as + * though dropped attributes don't exist. */ HeapTuple -SearchSysCacheTuple(int cacheId,/* cache selection code */ - Datum key1, - Datum key2, - Datum key3, - Datum key4) +SearchSysCacheAttName(Oid relid, const char *attname) { - HeapTuple tp; + HeapTuple tuple; - if (cacheId < 0 || cacheId >= SysCacheSize) + tuple = SearchSysCache2(ATTNAME, + ObjectIdGetDatum(relid), + CStringGetDatum(attname)); + if (!HeapTupleIsValid(tuple)) + return NULL; + if (((Form_pg_attribute) GETSTRUCT(tuple))->attisdropped) { - elog(ERROR, "SearchSysCacheTuple: Bad cache id %d", cacheId); - return (HeapTuple) NULL; + ReleaseSysCache(tuple); + return NULL; } + return tuple; +} - Assert(AMI_OVERRIDE || PointerIsValid(SysCache[cacheId])); +/* + * SearchSysCacheCopyAttName + * + * As above, an attisdropped-aware version of SearchSysCacheCopy. + */ +HeapTuple +SearchSysCacheCopyAttName(Oid relid, const char *attname) +{ + HeapTuple tuple, + newtuple; - if (!PointerIsValid(SysCache[cacheId])) - { - SysCache[cacheId] = InitSysCache(cacheinfo[cacheId].name, - cacheinfo[cacheId].indname, - cacheId, - cacheinfo[cacheId].nkeys, - cacheinfo[cacheId].key, - cacheinfo[cacheId].iScanFunc); - if (!PointerIsValid(SysCache[cacheId])) - elog(ERROR, - "InitCatalogCache: Can't init cache %s(%d)", - cacheinfo[cacheId].name, - cacheId); - } + tuple = SearchSysCacheAttName(relid, attname); + if (!HeapTupleIsValid(tuple)) + return tuple; + newtuple = heap_copytuple(tuple); + ReleaseSysCache(tuple); + return newtuple; +} - /* temp table name remapping */ - if (cacheId == RELNAME) - { - char *nontemp_relname; +/* + * SearchSysCacheExistsAttName + * + * As above, an attisdropped-aware version of SearchSysCacheExists. + */ +bool +SearchSysCacheExistsAttName(Oid relid, const char *attname) +{ + HeapTuple tuple; - if ((nontemp_relname = - get_temp_rel_by_name(DatumGetPointer(key1))) != NULL) - key1 = PointerGetDatum(nontemp_relname); - } - - tp = SearchSysCache(SysCache[cacheId], key1, key2, key3, key4); - if (!HeapTupleIsValid(tp)) + tuple = SearchSysCacheAttName(relid, attname); + if (!HeapTupleIsValid(tuple)) + return false; + ReleaseSysCache(tuple); + return true; +} + + +/* + * SysCacheGetAttr + * + * Given a tuple previously fetched by SearchSysCache(), + * extract a specific attribute. + * + * This is equivalent to using heap_getattr() on a tuple fetched + * from a non-cached relation. Usually, this is only used for attributes + * that could be NULL or variable length; the fixed-size attributes in + * a system table are accessed just by mapping the tuple onto the C struct + * declarations from include/catalog/. + * + * As with heap_getattr(), if the attribute is of a pass-by-reference type + * then a pointer into the tuple data area is returned --- the caller must + * not modify or pfree the datum! + * + * Note: it is legal to use SysCacheGetAttr() with a cacheId referencing + * a different cache for the same catalog the tuple was fetched from. + */ +Datum +SysCacheGetAttr(int cacheId, HeapTuple tup, + AttrNumber attributeNumber, + bool *isNull) +{ + /* + * We just need to get the TupleDesc out of the cache entry, and then we + * can apply heap_getattr(). Normally the cache control data is already + * valid (because the caller recently fetched the tuple via this same + * cache), but there are cases where we have to initialize the cache here. + */ + if (cacheId < 0 || cacheId >= SysCacheSize || + !PointerIsValid(SysCache[cacheId])) + elog(ERROR, "invalid cache ID: %d", cacheId); + if (!PointerIsValid(SysCache[cacheId]->cc_tupdesc)) { -#ifdef CACHEDEBUG - elog(DEBUG, - "SearchSysCacheTuple: Search %s(%d) %d %d %d %d failed", - cacheinfo[cacheId].name, - cacheId, key1, key2, key3, key4); -#endif - return (HeapTuple) NULL; + InitCatCachePhase2(SysCache[cacheId], false); + Assert(PointerIsValid(SysCache[cacheId]->cc_tupdesc)); } - return tp; + + return heap_getattr(tup, attributeNumber, + SysCache[cacheId]->cc_tupdesc, + isNull); } /* - * SearchSysCacheStruct - * Fills 's' with the information retrieved by calling SearchSysCache() - * with arguments key1...key4. Retrieves only the portion of the tuple - * which is not variable-length. + * GetSysCacheHashValue * - * NOTE: we are assuming that non-variable-length fields in the system - * catalogs will always be defined! + * Get the hash value that would be used for a tuple in the specified cache + * with the given search keys. * - * Returns 1L if a tuple was found, 0L if not. + * The reason for exposing this as part of the API is that the hash value is + * exposed in cache invalidation operations, so there are places outside the + * catcache code that need to be able to compute the hash values. */ -int32 -SearchSysCacheStruct(int cacheId, /* cache selection code */ - char *returnStruct, /* (preallocated!) */ +uint32 +GetSysCacheHashValue(int cacheId, Datum key1, Datum key2, Datum key3, Datum key4) { - HeapTuple tp; + if (cacheId < 0 || cacheId >= SysCacheSize || + !PointerIsValid(SysCache[cacheId])) + elog(ERROR, "invalid cache ID: %d", cacheId); - if (!PointerIsValid(returnStruct)) - { - elog(ERROR, "SearchSysCacheStruct: No receiving struct"); - return 0; - } - tp = SearchSysCacheTuple(cacheId, key1, key2, key3, key4); - if (!HeapTupleIsValid(tp)) - return 0; - memcpy(returnStruct, (char *) GETSTRUCT(tp), cacheinfo[cacheId].size); - return 1; + return GetCatCacheHashValue(SysCache[cacheId], key1, key2, key3, key4); } +/* + * List-search interface + */ +struct catclist * +SearchSysCacheList(int cacheId, int nkeys, + Datum key1, Datum key2, Datum key3, Datum key4) +{ + if (cacheId < 0 || cacheId >= SysCacheSize || + !PointerIsValid(SysCache[cacheId])) + elog(ERROR, "invalid cache ID: %d", cacheId); + + return SearchCatCacheList(SysCache[cacheId], nkeys, + key1, key2, key3, key4); +} /* - * SearchSysCacheGetAttribute - * Returns the attribute corresponding to 'attributeNumber' for - * a given cached tuple. This routine usually needs to be used for - * attributes that might be NULL or might be at a variable offset - * in the tuple. + * SysCacheInvalidate * - * XXX This re-opens the relation, so this is slower than just pulling - * fixed-location fields out of the struct returned by SearchSysCacheTuple. + * Invalidate entries in the specified cache, given a hash value. + * See CatCacheInvalidate() for more info. * - * [callers all assume this returns a (struct varlena *). -ay 10/94] + * This routine is only quasi-public: it should only be used by inval.c. */ -void * -SearchSysCacheGetAttribute(int cacheId, - AttrNumber attributeNumber, - Datum key1, - Datum key2, - Datum key3, - Datum key4) +void +SysCacheInvalidate(int cacheId, uint32 hashValue) { - HeapTuple tp; - char *cacheName; - Relation relation; - int32 attributeLength, - attributeByValue; - bool isNull; - Datum attributeValue; - void *returnValue; + if (cacheId < 0 || cacheId >= SysCacheSize) + elog(ERROR, "invalid cache ID: %d", cacheId); - /* - * Open the relation first, to ensure we are in sync with SI inval - * events --- we don't want the tuple found in the cache to be - * invalidated out from under us. - */ - cacheName = cacheinfo[cacheId].name; - relation = heap_openr(cacheName, AccessShareLock); + /* if this cache isn't initialized yet, no need to do anything */ + if (!PointerIsValid(SysCache[cacheId])) + return; - tp = SearchSysCacheTuple(cacheId, key1, key2, key3, key4); + CatCacheInvalidate(SysCache[cacheId], hashValue); +} - if (!HeapTupleIsValid(tp)) +/* + * Certain relations that do not have system caches send snapshot invalidation + * messages in lieu of catcache messages. This is for the benefit of + * GetCatalogSnapshot(), which can then reuse its existing MVCC snapshot + * for scanning one of those catalogs, rather than taking a new one, if no + * invalidation has been received. + * + * Relations that have syscaches need not (and must not) be listed here. The + * catcache invalidation messages will also flush the snapshot. If you add a + * syscache for one of these relations, remove it from this list. + */ +bool +RelationInvalidatesSnapshotsOnly(Oid relid) +{ + switch (relid) { - heap_close(relation, AccessShareLock); -#ifdef CACHEDEBUG - elog(DEBUG, - "SearchSysCacheGetAttribute: Lookup in %s(%d) failed", - cacheName, cacheId); -#endif /* defined(CACHEDEBUG) */ - return NULL; + case DbRoleSettingRelationId: + case DependRelationId: + case SharedDependRelationId: + case DescriptionRelationId: + case SharedDescriptionRelationId: + case SecLabelRelationId: + case SharedSecLabelRelationId: + return true; + default: + break; } - if (attributeNumber < 0 && - attributeNumber > FirstLowInvalidHeapAttributeNumber) - { - attributeLength = heap_sysattrlen(attributeNumber); - attributeByValue = heap_sysattrbyval(attributeNumber); - } - else if (attributeNumber > 0 && - attributeNumber <= relation->rd_rel->relnatts) - { - attributeLength = relation->rd_att->attrs[attributeNumber - 1]->attlen; - attributeByValue = relation->rd_att->attrs[attributeNumber - 1]->attbyval; - } - else - { - heap_close(relation, AccessShareLock); - elog(ERROR, - "SearchSysCacheGetAttribute: Bad attr # %d in %s(%d)", - attributeNumber, cacheName, cacheId); - return NULL; - } + return false; +} - attributeValue = heap_getattr(tp, - attributeNumber, - RelationGetDescr(relation), - &isNull); +/* + * Test whether a relation has a system cache. + */ +bool +RelationHasSysCache(Oid relid) +{ + int low = 0, + high = SysCacheRelationOidSize - 1; - if (isNull) + while (low <= high) { - /* - * Used to be an elog(DEBUG, ...) here and a claim that it should - * be a FATAL error, I don't think either is warranted -mer 6/9/92 - */ - heap_close(relation, AccessShareLock); - return NULL; + int middle = low + (high - low) / 2; + + if (SysCacheRelationOid[middle] == relid) + return true; + if (SysCacheRelationOid[middle] < relid) + low = middle + 1; + else + high = middle - 1; } - if (attributeByValue) - returnValue = (void *) attributeValue; - else + return false; +} + +/* + * Test whether a relation supports a system cache, ie it is either a + * cached table or the index used for a cache. + */ +bool +RelationSupportsSysCache(Oid relid) +{ + int low = 0, + high = SysCacheSupportingRelOidSize - 1; + + while (low <= high) { - char *tmp; - int size = (attributeLength < 0) - ? VARSIZE((struct varlena *) attributeValue) /* variable length */ - : attributeLength; /* fixed length */ - - tmp = (char *) palloc(size); - memcpy(tmp, (void *) attributeValue, size); - returnValue = (void *) tmp; + int middle = low + (high - low) / 2; + + if (SysCacheSupportingRelOid[middle] == relid) + return true; + if (SysCacheSupportingRelOid[middle] < relid) + low = middle + 1; + else + high = middle - 1; } - heap_close(relation, AccessShareLock); - return returnValue; + return false; +} + + +/* + * OID comparator for pg_qsort + */ +static int +oid_compare(const void *a, const void *b) +{ + Oid oa = *((const Oid *) a); + Oid ob = *((const Oid *) b); + + if (oa == ob) + return 0; + return (oa > ob) ? 1 : -1; }