1
0
mirror of https://github.com/postgres/postgres.git synced 2026-01-26 09:41:40 +03:00
Files
postgres/src/backend/access/table/tableamapi.c
Tom Lane bc6374cd76 Change IndexAmRoutines to be statically-allocated structs.
Up to now, index amhandlers were expected to produce a new, palloc'd
struct on each call.  That requires palloc/pfree overhead, and creates
a risk of memory leaks if the caller fails to pfree, and the time
taken to fill such a large structure isn't nil.  Moreover, we were
storing these things in the relcache, eating several hundred bytes for
each cached index.  There is not anything in these structs that needs
to vary at runtime, so let's change the definition so that an
amhandler can return a pointer to a "static const" struct of which
there's only one copy per index AM.  Mark all the core code's
IndexAmRoutine pointers const so that we catch anyplace that might
still try to change or pfree one.

(This is similar to the way we were already handling TableAmRoutine
structs.  This commit does fix one comment that was infelicitously
copied-and-pasted into tableamapi.c.)

This commit needs to be called out in the v19 release notes as an API
change for extension index AMs.  An un-updated AM will still work
(as of now, anyway) but it risks memory leaks and will be slower than
necessary.

Author: Matthias van de Meent <boekewurm+postgres@gmail.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/CAEoWx2=vApYk2LRu8R0DdahsPNEhWUxGBZ=rbZo1EXE=uA+opQ@mail.gmail.com
2025-12-30 18:26:23 -05:00

149 lines
4.3 KiB
C

/*----------------------------------------------------------------------
*
* tableamapi.c
* Support routines for API for Postgres table access methods
*
* Portions Copyright (c) 1996-2025, 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/tableam.h"
#include "access/xact.h"
#include "commands/defrem.h"
#include "miscadmin.h"
#include "utils/guc_hooks.h"
/*
* GetTableAmRoutine
* Call the specified access method handler routine to get its
* TableAmRoutine struct, which we expect to be statically allocated.
*/
const TableAmRoutine *
GetTableAmRoutine(Oid amhandler)
{
Datum datum;
const TableAmRoutine *routine;
datum = OidFunctionCall0(amhandler);
routine = (const 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->scan_getnextslot != 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_fetch_row_version != NULL);
Assert(routine->tuple_tid_valid != NULL);
Assert(routine->tuple_get_latest_tid != NULL);
Assert(routine->tuple_satisfies_snapshot != NULL);
Assert(routine->index_delete_tuples != 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->multi_insert != NULL);
Assert(routine->tuple_delete != NULL);
Assert(routine->tuple_update != NULL);
Assert(routine->tuple_lock != NULL);
Assert(routine->relation_set_new_filelocator != NULL);
Assert(routine->relation_nontransactional_truncate != NULL);
Assert(routine->relation_copy_data != NULL);
Assert(routine->relation_copy_for_cluster != NULL);
Assert(routine->relation_vacuum != NULL);
Assert(routine->scan_analyze_next_block != NULL);
Assert(routine->scan_analyze_next_tuple != NULL);
Assert(routine->index_build_range_scan != NULL);
Assert(routine->index_validate_scan != NULL);
Assert(routine->relation_size != NULL);
Assert(routine->relation_needs_toast_table != NULL);
Assert(routine->relation_estimate_size != NULL);
Assert(routine->scan_sample_next_block != NULL);
Assert(routine->scan_sample_next_tuple != NULL);
return routine;
}
/* check_hook: validate new default_table_access_method */
bool
check_default_table_access_method(char **newval, void **extra, GucSource source)
{
if (**newval == '\0')
{
GUC_check_errdetail("\"%s\" cannot be empty.",
"default_table_access_method");
return false;
}
if (strlen(*newval) >= NAMEDATALEN)
{
GUC_check_errdetail("\"%s\" is too long (maximum %d characters).",
"default_table_access_method", NAMEDATALEN - 1);
return false;
}
/*
* If we aren't inside a transaction, or not connected to a database, we
* cannot do the catalog access necessary to verify the method. Must
* accept the value on faith.
*/
if (IsTransactionState() && MyDatabaseId != InvalidOid)
{
if (!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;
}