mirror of
https://github.com/timescale/timescaledb.git
synced 2025-05-23 06:22:03 +08:00
242 lines
7.2 KiB
C
242 lines
7.2 KiB
C
/*
|
|
* This file and its contents are licensed under the Apache License 2.0.
|
|
* Please see the included NOTICE for copyright information and
|
|
* LICENSE-APACHE for a copy of the license.
|
|
*/
|
|
/* This file will be used by the versioned timescaledb extension and the loader
|
|
* Because we want the loader not to export symbols all files here should be static
|
|
* and be included via #include "extension_utils.c" instead of the regular linking process
|
|
*/
|
|
|
|
#include <postgres.h>
|
|
#include <access/genam.h>
|
|
#include <access/relscan.h>
|
|
#include <access/xact.h>
|
|
#include <catalog/indexing.h>
|
|
#include <catalog/namespace.h>
|
|
#include <catalog/pg_authid.h>
|
|
#include <catalog/pg_extension.h>
|
|
#include <commands/extension.h>
|
|
#include <miscadmin.h>
|
|
#include <parser/analyze.h>
|
|
#include <utils/acl.h>
|
|
#include <utils/builtins.h>
|
|
#include <utils/fmgroids.h>
|
|
#include <utils/guc.h>
|
|
#include <utils/lsyscache.h>
|
|
#include <utils/rel.h>
|
|
|
|
#include "compat/compat.h"
|
|
#include "extension_constants.h"
|
|
|
|
#define EXTENSION_PROXY_TABLE "cache_inval_extension"
|
|
|
|
#define RENDEZVOUS_LOADER_PRESENT_NAME "timescaledb.loader_present"
|
|
|
|
enum ExtensionState
|
|
{
|
|
/*
|
|
* NOT_INSTALLED means that this backend knows that the extension is not
|
|
* present. In this state we know that the proxy table is not present.
|
|
* This state is never saved since there is no real way to get out of it
|
|
* since we cannot signal via the proxy table as its relid is not known
|
|
* post installation without a full lookup, which is not allowed in the
|
|
* relcache callback.
|
|
*/
|
|
EXTENSION_STATE_NOT_INSTALLED,
|
|
|
|
/*
|
|
* UNKNOWN state is used only if we cannot be sure what the state is. This
|
|
* can happen in two cases: 1) at the start of a backend or 2) We got a
|
|
* relcache event outside of a transaction and thus could not check the
|
|
* cache for the presence/absence of the proxy table or extension.
|
|
*/
|
|
EXTENSION_STATE_UNKNOWN,
|
|
|
|
/*
|
|
* TRANSITIONING only occurs in the middle of a CREATE EXTENSION or ALTER
|
|
* EXTENSION UPDATE
|
|
*/
|
|
EXTENSION_STATE_TRANSITIONING,
|
|
|
|
/*
|
|
* CREATED means we know the extension is loaded, metadata is up-to-date,
|
|
* and we therefore do not need a full check until a RelCacheInvalidation
|
|
* on the proxy table.
|
|
*/
|
|
EXTENSION_STATE_CREATED,
|
|
};
|
|
|
|
static char *
|
|
extension_version(void)
|
|
{
|
|
Datum result;
|
|
Relation rel;
|
|
SysScanDesc scandesc;
|
|
HeapTuple tuple;
|
|
ScanKeyData entry[1];
|
|
bool is_null = true;
|
|
char *sql_version = NULL;
|
|
|
|
rel = table_open(ExtensionRelationId, AccessShareLock);
|
|
|
|
ScanKeyInit(&entry[0],
|
|
Anum_pg_extension_extname,
|
|
BTEqualStrategyNumber,
|
|
F_NAMEEQ,
|
|
CStringGetDatum(EXTENSION_NAME));
|
|
|
|
scandesc = systable_beginscan(rel, ExtensionNameIndexId, true, NULL, 1, entry);
|
|
|
|
tuple = systable_getnext(scandesc);
|
|
|
|
/* We assume that there can be at most one matching tuple */
|
|
if (HeapTupleIsValid(tuple))
|
|
{
|
|
result = heap_getattr(tuple, Anum_pg_extension_extversion, RelationGetDescr(rel), &is_null);
|
|
|
|
if (!is_null)
|
|
{
|
|
sql_version = pstrdup(TextDatumGetCString(result));
|
|
}
|
|
}
|
|
|
|
systable_endscan(scandesc);
|
|
table_close(rel, AccessShareLock);
|
|
|
|
if (sql_version == NULL)
|
|
{
|
|
elog(ERROR, "extension not found while getting version");
|
|
}
|
|
return sql_version;
|
|
}
|
|
|
|
static Oid
|
|
get_proxy_table_relid()
|
|
{
|
|
Oid nsid = get_namespace_oid(CACHE_SCHEMA_NAME, true);
|
|
|
|
if (!OidIsValid(nsid))
|
|
return InvalidOid;
|
|
|
|
return get_relname_relid(EXTENSION_PROXY_TABLE, nsid);
|
|
}
|
|
|
|
static bool inline extension_exists()
|
|
{
|
|
return OidIsValid(get_extension_oid(EXTENSION_NAME, true));
|
|
}
|
|
|
|
static bool inline extension_is_transitioning()
|
|
{
|
|
/*
|
|
* Determine whether the extension is being created or upgraded (as a
|
|
* misnomer creating_extension is true during upgrades)
|
|
*/
|
|
if (creating_extension)
|
|
{
|
|
return get_extension_oid(EXTENSION_NAME, true) == CurrentExtensionObject;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Returns the recomputed current state */
|
|
static enum ExtensionState
|
|
extension_current_state()
|
|
{
|
|
Oid proxy_relid;
|
|
|
|
/*
|
|
* NormalProcessingMode necessary to avoid accessing cache before its
|
|
* ready (which may result in an infinite loop). More concretely we need
|
|
* RelationCacheInitializePhase3 to have been already called.
|
|
*/
|
|
if (!IsNormalProcessingMode() || !IsTransactionState() || !OidIsValid(MyDatabaseId))
|
|
return EXTENSION_STATE_UNKNOWN;
|
|
|
|
/*
|
|
* NOTE: do not check for proxy_table_exists here. Want to be in
|
|
* TRANSITIONING state even before the proxy table is created
|
|
*/
|
|
if (extension_is_transitioning())
|
|
return EXTENSION_STATE_TRANSITIONING;
|
|
|
|
/*
|
|
* proxy_table_exists uses syscache. Must come first. NOTE: during DROP
|
|
* EXTENSION proxy_table_exists() will return false right away, while
|
|
* extension_exists will return true until the end of the command
|
|
*/
|
|
|
|
proxy_relid = get_proxy_table_relid();
|
|
|
|
if (OidIsValid(proxy_relid))
|
|
{
|
|
Assert(extension_exists());
|
|
return EXTENSION_STATE_CREATED;
|
|
}
|
|
|
|
return EXTENSION_STATE_NOT_INSTALLED;
|
|
}
|
|
|
|
static void
|
|
extension_load_without_preload()
|
|
{
|
|
/* cannot use GUC variable here since extension not yet loaded */
|
|
char *allow_install_without_preload =
|
|
GetConfigOptionByName("timescaledb.allow_install_without_preload", NULL, true);
|
|
|
|
if (allow_install_without_preload == NULL || strcmp(allow_install_without_preload, "on") != 0)
|
|
{
|
|
/*
|
|
* These are FATAL because otherwise the loader ends up in a weird
|
|
* half-loaded state after an ERROR
|
|
*/
|
|
/* Only privileged users can get the value of `config file` */
|
|
|
|
if (has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
|
|
{
|
|
char *config_file = GetConfigOptionByName("config_file", NULL, false);
|
|
|
|
ereport(FATAL,
|
|
(errmsg("extension \"%s\" must be preloaded", EXTENSION_NAME),
|
|
errhint("Please preload the timescaledb library via "
|
|
"shared_preload_libraries.\n\n"
|
|
"This can be done by editing the config file at: %1$s\n"
|
|
"and adding 'timescaledb' to the list in the shared_preload_libraries "
|
|
"config.\n"
|
|
" # Modify postgresql.conf:\n shared_preload_libraries = "
|
|
"'timescaledb'\n\n"
|
|
"Another way to do this, if not preloading other libraries, is with "
|
|
"the command:\n"
|
|
" echo \"shared_preload_libraries = 'timescaledb'\" >> %1$s \n\n"
|
|
"(Will require a database restart.)\n\n"
|
|
"If you REALLY know what you are doing and would like to load the "
|
|
"library without preloading, you can disable this check with: \n"
|
|
" SET timescaledb.allow_install_without_preload = 'on';",
|
|
config_file)));
|
|
}
|
|
else
|
|
{
|
|
ereport(FATAL,
|
|
(errmsg("extension \"%s\" must be preloaded", EXTENSION_NAME),
|
|
errhint(
|
|
"Please preload the timescaledb library via shared_preload_libraries.\n\n"
|
|
"This can be done by editing the postgres config file \n"
|
|
"and adding 'timescaledb' to the list in the shared_preload_libraries "
|
|
"config.\n"
|
|
" # Modify postgresql.conf:\n shared_preload_libraries = "
|
|
"'timescaledb'\n\n"
|
|
"Another way to do this, if not preloading other libraries, is with the "
|
|
"command:\n"
|
|
" echo \"shared_preload_libraries = 'timescaledb'\" >> "
|
|
"/path/to/config/file \n\n"
|
|
"(Will require a database restart.)\n\n"
|
|
"If you REALLY know what you are doing and would like to load the library "
|
|
"without preloading, you can disable this check with: \n"
|
|
" SET timescaledb.allow_install_without_preload = 'on';")));
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|