/*---------------------------------------------------------------------- * * tableamapi.c * Support routines for API for Postgres table access methods * * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * src/backend/access/table/tableamapi.c *---------------------------------------------------------------------- */ #include "postgres.h" #include "access/heapam.h" #include "access/htup_details.h" #include "access/tableam.h" #include "access/xact.h" #include "catalog/pg_am.h" #include "catalog/pg_proc.h" #include "utils/fmgroids.h" #include "utils/memutils.h" #include "utils/syscache.h" static Oid get_table_am_oid(const char *tableamname, bool missing_ok); /* * GetTableAmRoutine * Call the specified access method handler routine to get its * TableAmRoutine struct, which will be palloc'd in the caller's * memory context. */ const TableAmRoutine * GetTableAmRoutine(Oid amhandler) { Datum datum; const TableAmRoutine *routine; datum = OidFunctionCall0(amhandler); routine = (TableAmRoutine *) DatumGetPointer(datum); if (routine == NULL || !IsA(routine, TableAmRoutine)) elog(ERROR, "Table access method handler %u did not return a TableAmRoutine struct", amhandler); /* * Assert that all required callbacks are present. That makes it a bit * easier to keep AMs up to date, e.g. when forward porting them to a new * major version. */ Assert(routine->scan_begin != NULL); Assert(routine->scan_end != NULL); Assert(routine->scan_rescan != NULL); Assert(routine->parallelscan_estimate != NULL); Assert(routine->parallelscan_initialize != NULL); Assert(routine->parallelscan_reinitialize != NULL); Assert(routine->index_fetch_begin != NULL); Assert(routine->index_fetch_reset != NULL); Assert(routine->index_fetch_end != NULL); Assert(routine->index_fetch_tuple != NULL); Assert(routine->tuple_satisfies_snapshot != NULL); Assert(routine->tuple_insert != NULL); /* * Could be made optional, but would require throwing error during * parse-analysis. */ Assert(routine->tuple_insert_speculative != NULL); Assert(routine->tuple_complete_speculative != NULL); Assert(routine->tuple_delete != NULL); Assert(routine->tuple_update != NULL); Assert(routine->tuple_lock != NULL); return routine; } /* * GetTableAmRoutineByAmId - look up the handler of the table access * method with the given OID, and get its TableAmRoutine struct. */ const TableAmRoutine * GetTableAmRoutineByAmId(Oid amoid) { regproc amhandler; HeapTuple tuple; Form_pg_am amform; /* Get handler function OID for the access method */ tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for access method %u", amoid); amform = (Form_pg_am) GETSTRUCT(tuple); /* Check that it is a table access method */ if (amform->amtype != AMTYPE_TABLE) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("access method \"%s\" is not of type %s", NameStr(amform->amname), "TABLE"))); amhandler = amform->amhandler; /* Complain if handler OID is invalid */ if (!RegProcedureIsValid(amhandler)) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("table access method \"%s\" does not have a handler", NameStr(amform->amname)))); ReleaseSysCache(tuple); /* And finally, call the handler function to get the API struct. */ return GetTableAmRoutine(amhandler); } /* * get_table_am_oid - given a table access method name, look up the OID * * If missing_ok is false, throw an error if table access method name not * found. If true, just return InvalidOid. */ static Oid get_table_am_oid(const char *tableamname, bool missing_ok) { Oid result; Relation rel; TableScanDesc scandesc; HeapTuple tuple; ScanKeyData entry[1]; /* * Search pg_am. We use a heapscan here even though there is an index on * name, on the theory that pg_am will usually have just a few entries and * so an indexed lookup is a waste of effort. */ rel = heap_open(AccessMethodRelationId, AccessShareLock); ScanKeyInit(&entry[0], Anum_pg_am_amname, BTEqualStrategyNumber, F_NAMEEQ, CStringGetDatum(tableamname)); scandesc = table_beginscan_catalog(rel, 1, entry); tuple = heap_getnext(scandesc, ForwardScanDirection); /* We assume that there can be at most one matching tuple */ if (HeapTupleIsValid(tuple) && ((Form_pg_am) GETSTRUCT(tuple))->amtype == AMTYPE_TABLE) result = ((Form_pg_am) GETSTRUCT(tuple))->oid; else result = InvalidOid; table_endscan(scandesc); heap_close(rel, AccessShareLock); if (!OidIsValid(result) && !missing_ok) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("table access method \"%s\" does not exist", tableamname))); return result; } /* check_hook: validate new default_table_access_method */ bool check_default_table_access_method(char **newval, void **extra, GucSource source) { /* * If we aren't inside a transaction, we cannot do database access so * cannot verify the name. Must accept the value on faith. */ if (IsTransactionState()) { if (**newval != '\0' && !OidIsValid(get_table_am_oid(*newval, true))) { /* * When source == PGC_S_TEST, don't throw a hard error for a * nonexistent table access method, only a NOTICE. See comments in * guc.h. */ if (source == PGC_S_TEST) { ereport(NOTICE, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("Table access method \"%s\" does not exist", *newval))); } else { GUC_check_errdetail("Table access method \"%s\" does not exist.", *newval); return false; } } } return true; }