postgresql/src/backend/access/common/reloptions.c

1638 lines
42 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* reloptions.c
* Core support for relation options (pg_class.reloptions)
*
* Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
2010-09-20 22:08:53 +02:00
* src/backend/access/common/reloptions.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <float.h>
#include "access/gist_private.h"
#include "access/hash.h"
#include "access/htup_details.h"
#include "access/nbtree.h"
#include "access/reloptions.h"
#include "access/spgist.h"
#include "access/tuptoaster.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/tablespace.h"
#include "commands/view.h"
#include "nodes/makefuncs.h"
#include "postmaster/postmaster.h"
#include "utils/array.h"
#include "utils/attoptcache.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/rel.h"
/*
* Contents of pg_class.reloptions
*
* To add an option:
*
* (i) decide on a type (integer, real, bool, string), name, default value,
* upper and lower bounds (if applicable); for strings, consider a validation
* routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
* default_reloptions)
* (v) make sure the lock level is set correctly for that operation
* (vi) don't forget to document the option
*
* Note that we don't handle "oids" in relOpts because it is handled by
* interpretOidsOption().
*
* The default choice for any new option should be AccessExclusiveLock.
* In some cases the lock level can be reduced from there, but the lock
* level chosen should always conflict with itself to ensure that multiple
* changes aren't lost when we attempt concurrent changes.
* The choice of lock level depends completely upon how that parameter
* is used within the server, not upon how and when you'd like to change it.
* Safety first. Existing choices are documented here, and elsewhere in
* backend code where the parameters are used.
*
* In general, anything that affects the results obtained from a SELECT must be
* protected by AccessExclusiveLock.
*
* Autovacuum related parameters can be set at ShareUpdateExclusiveLock
* since they are only used by the AV procs and don't change anything
* currently executing.
*
* Fillfactor can be set because it applies only to subsequent changes made to
* data blocks, as documented in heapio.c
*
* n_distinct options can be set at ShareUpdateExclusiveLock because they
* are only used during ANALYZE, which uses a ShareUpdateExclusiveLock,
* so the ANALYZE will not be affected by in-flight changes. Changing those
* values has no affect until the next ANALYZE, so no need for stronger lock.
*
* Planner-related parameters can be set with ShareUpdateExclusiveLock because
* they only affect planning and not the correctness of the execution. Plans
* cannot be changed in mid-flight, so changes here could not easily result in
* new improved plans in any case. So we allow existing queries to continue
* and existing plans to survive, a small price to pay for allowing better
* plans to be introduced concurrently without interfering with users.
*
* Setting parallel_workers is safe, since it acts the same as
* max_parallel_workers_per_gather which is a USERSET parameter that doesn't
* affect existing plans or queries.
*/
static relopt_bool boolRelOpts[] =
{
BRIN auto-summarization Previously, only VACUUM would cause a page range to get initially summarized by BRIN indexes, which for some use cases takes too much time since the inserts occur. To avoid the delay, have brininsert request a summarization run for the previous range as soon as the first tuple is inserted into the first page of the next range. Autovacuum is in charge of processing these requests, after doing all the regular vacuuming/ analyzing work on tables. This doesn't impose any new tasks on autovacuum, because autovacuum was already in charge of doing summarizations. The only actual effect is to change the timing, i.e. that it occurs earlier. For this reason, we don't go any great lengths to record these requests very robustly; if they are lost because of a server crash or restart, they will happen at a later time anyway. Most of the new code here is in autovacuum, which can now be told about "work items" to process. This can be used for other things such as GIN pending list cleaning, perhaps visibility map bit setting, both of which are currently invoked during vacuum, but do not really depend on vacuum taking place. The requests are at the page range level, a granularity for which we did not have SQL-level access; we only had index-level summarization requests via brin_summarize_new_values(). It seems reasonable to add SQL-level access to range-level summarization too, so add a function brin_summarize_range() to do that. Authors: Álvaro Herrera, based on sketch from Simon Riggs. Reviewed-by: Thomas Munro. Discussion: https://postgr.es/m/20170301045823.vneqdqkmsd4as4ds@alvherre.pgsql
2017-04-01 19:00:53 +02:00
{
{
"autosummarize",
"Enables automatic summarization on this BRIN index",
RELOPT_KIND_BRIN,
AccessExclusiveLock
},
false
},
{
{
"autovacuum_enabled",
"Enables autovacuum in this relation",
RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
ShareUpdateExclusiveLock
},
true
},
{
{
"user_catalog_table",
"Declare a table as an additional catalog table, e.g. for the purpose of logical replication",
RELOPT_KIND_HEAP,
AccessExclusiveLock
},
false
},
{
{
"fastupdate",
"Enables \"fast update\" feature for this GIN index",
RELOPT_KIND_GIN,
AccessExclusiveLock
},
true
},
{
{
"recheck_on_update",
"Recheck functional index expression for changed value after update",
RELOPT_KIND_INDEX,
ShareUpdateExclusiveLock /* since only applies to later UPDATEs */
},
true
},
{
{
"security_barrier",
"View acts as a row security barrier",
RELOPT_KIND_VIEW,
AccessExclusiveLock
},
false
},
/* list terminator */
{{NULL}}
};
static relopt_int intRelOpts[] =
{
{
{
"fillfactor",
"Packs table pages only to this percentage",
RELOPT_KIND_HEAP,
2016-06-10 00:02:36 +02:00
ShareUpdateExclusiveLock /* since it applies only to later
* inserts */
},
HEAP_DEFAULT_FILLFACTOR, HEAP_MIN_FILLFACTOR, 100
},
{
{
"fillfactor",
"Packs btree index pages only to this percentage",
RELOPT_KIND_BTREE,
2016-06-10 00:02:36 +02:00
ShareUpdateExclusiveLock /* since it applies only to later
* inserts */
},
BTREE_DEFAULT_FILLFACTOR, BTREE_MIN_FILLFACTOR, 100
},
{
{
"fillfactor",
"Packs hash index pages only to this percentage",
RELOPT_KIND_HASH,
2016-06-10 00:02:36 +02:00
ShareUpdateExclusiveLock /* since it applies only to later
* inserts */
},
HASH_DEFAULT_FILLFACTOR, HASH_MIN_FILLFACTOR, 100
},
{
{
"fillfactor",
"Packs gist index pages only to this percentage",
RELOPT_KIND_GIST,
2016-06-10 00:02:36 +02:00
ShareUpdateExclusiveLock /* since it applies only to later
* inserts */
},
GIST_DEFAULT_FILLFACTOR, GIST_MIN_FILLFACTOR, 100
},
{
{
"fillfactor",
"Packs spgist index pages only to this percentage",
RELOPT_KIND_SPGIST,
2016-06-10 00:02:36 +02:00
ShareUpdateExclusiveLock /* since it applies only to later
* inserts */
},
SPGIST_DEFAULT_FILLFACTOR, SPGIST_MIN_FILLFACTOR, 100
},
{
{
"autovacuum_vacuum_threshold",
"Minimum number of tuple updates or deletes prior to vacuum",
RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
ShareUpdateExclusiveLock
},
-1, 0, INT_MAX
},
{
{
"autovacuum_analyze_threshold",
"Minimum number of tuple inserts, updates or deletes prior to analyze",
RELOPT_KIND_HEAP,
ShareUpdateExclusiveLock
},
-1, 0, INT_MAX
},
{
{
"autovacuum_vacuum_cost_delay",
"Vacuum cost delay in milliseconds, for autovacuum",
RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
ShareUpdateExclusiveLock
},
-1, 0, 100
},
{
{
"autovacuum_vacuum_cost_limit",
"Vacuum cost amount available before napping, for autovacuum",
RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
ShareUpdateExclusiveLock
},
-1, 1, 10000
},
{
{
"autovacuum_freeze_min_age",
"Minimum age at which VACUUM should freeze a table row, for autovacuum",
RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
ShareUpdateExclusiveLock
},
-1, 0, 1000000000
},
{
{
"autovacuum_multixact_freeze_min_age",
"Minimum multixact age at which VACUUM should freeze a row multixact's, for autovacuum",
RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
ShareUpdateExclusiveLock
},
-1, 0, 1000000000
},
{
{
"autovacuum_freeze_max_age",
"Age at which to autovacuum a table to prevent transaction ID wraparound",
RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
ShareUpdateExclusiveLock
},
-1, 100000, 2000000000
},
{
{
"autovacuum_multixact_freeze_max_age",
"Multixact age at which to autovacuum a table to prevent multixact wraparound",
RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
ShareUpdateExclusiveLock
},
-1, 10000, 2000000000
},
{
{
"autovacuum_freeze_table_age",
"Age at which VACUUM should perform a full table sweep to freeze row versions",
RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
ShareUpdateExclusiveLock
}, -1, 0, 2000000000
},
{
{
"autovacuum_multixact_freeze_table_age",
"Age of multixact at which VACUUM should perform a full table sweep to freeze row versions",
RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
ShareUpdateExclusiveLock
}, -1, 0, 2000000000
},
{
{
"log_autovacuum_min_duration",
"Sets the minimum execution time above which autovacuum actions will be logged",
RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
ShareUpdateExclusiveLock
},
-1, -1, INT_MAX
},
{
{
"toast_tuple_target",
"Sets the target tuple length at which external columns will be toasted",
RELOPT_KIND_HEAP,
ShareUpdateExclusiveLock
},
TOAST_TUPLE_TARGET, 128, TOAST_TUPLE_TARGET_MAIN
},
BRIN: Block Range Indexes BRIN is a new index access method intended to accelerate scans of very large tables, without the maintenance overhead of btrees or other traditional indexes. They work by maintaining "summary" data about block ranges. Bitmap index scans work by reading each summary tuple and comparing them with the query quals; all pages in the range are returned in a lossy TID bitmap if the quals are consistent with the values in the summary tuple, otherwise not. Normal index scans are not supported because these indexes do not store TIDs. As new tuples are added into the index, the summary information is updated (if the block range in which the tuple is added is already summarized) or not; in the latter case, a subsequent pass of VACUUM or the brin_summarize_new_values() function will create the summary information. For data types with natural 1-D sort orders, the summary info consists of the maximum and the minimum values of each indexed column within each page range. This type of operator class we call "Minmax", and we supply a bunch of them for most data types with B-tree opclasses. Since the BRIN code is generalized, other approaches are possible for things such as arrays, geometric types, ranges, etc; even for things such as enum types we could do something different than minmax with better results. In this commit I only include minmax. Catalog version bumped due to new builtin catalog entries. There's more that could be done here, but this is a good step forwards. Loosely based on ideas from Simon Riggs; code mostly by Álvaro Herrera, with contribution by Heikki Linnakangas. Patch reviewed by: Amit Kapila, Heikki Linnakangas, Robert Haas. Testing help from Jeff Janes, Erik Rijkers, Emanuel Calvo. PS: The research leading to these results has received funding from the European Union's Seventh Framework Programme (FP7/2007-2013) under grant agreement n° 318633.
2014-11-07 20:38:14 +01:00
{
{
"pages_per_range",
"Number of pages that each page range covers in a BRIN index",
RELOPT_KIND_BRIN,
AccessExclusiveLock
BRIN: Block Range Indexes BRIN is a new index access method intended to accelerate scans of very large tables, without the maintenance overhead of btrees or other traditional indexes. They work by maintaining "summary" data about block ranges. Bitmap index scans work by reading each summary tuple and comparing them with the query quals; all pages in the range are returned in a lossy TID bitmap if the quals are consistent with the values in the summary tuple, otherwise not. Normal index scans are not supported because these indexes do not store TIDs. As new tuples are added into the index, the summary information is updated (if the block range in which the tuple is added is already summarized) or not; in the latter case, a subsequent pass of VACUUM or the brin_summarize_new_values() function will create the summary information. For data types with natural 1-D sort orders, the summary info consists of the maximum and the minimum values of each indexed column within each page range. This type of operator class we call "Minmax", and we supply a bunch of them for most data types with B-tree opclasses. Since the BRIN code is generalized, other approaches are possible for things such as arrays, geometric types, ranges, etc; even for things such as enum types we could do something different than minmax with better results. In this commit I only include minmax. Catalog version bumped due to new builtin catalog entries. There's more that could be done here, but this is a good step forwards. Loosely based on ideas from Simon Riggs; code mostly by Álvaro Herrera, with contribution by Heikki Linnakangas. Patch reviewed by: Amit Kapila, Heikki Linnakangas, Robert Haas. Testing help from Jeff Janes, Erik Rijkers, Emanuel Calvo. PS: The research leading to these results has received funding from the European Union's Seventh Framework Programme (FP7/2007-2013) under grant agreement n° 318633.
2014-11-07 20:38:14 +01:00
}, 128, 1, 131072
},
{
{
"gin_pending_list_limit",
"Maximum size of the pending list for this GIN index, in kilobytes.",
RELOPT_KIND_GIN,
AccessExclusiveLock
},
-1, 64, MAX_KILOBYTES
},
{
{
"effective_io_concurrency",
"Number of simultaneous requests that can be handled efficiently by the disk subsystem.",
RELOPT_KIND_TABLESPACE,
ShareUpdateExclusiveLock
},
#ifdef USE_PREFETCH
-1, 0, MAX_IO_CONCURRENCY
#else
0, 0, 0
#endif
},
{
{
"parallel_workers",
"Number of parallel processes that can be used per executor node for this relation.",
RELOPT_KIND_HEAP,
ShareUpdateExclusiveLock
},
-1, 0, 1024
},
/* list terminator */
{{NULL}}
};
static relopt_real realRelOpts[] =
{
{
{
"autovacuum_vacuum_scale_factor",
"Number of tuple updates or deletes prior to vacuum as a fraction of reltuples",
RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
ShareUpdateExclusiveLock
},
-1, 0.0, 100.0
},
{
{
"autovacuum_analyze_scale_factor",
"Number of tuple inserts, updates or deletes prior to analyze as a fraction of reltuples",
RELOPT_KIND_HEAP,
ShareUpdateExclusiveLock
},
-1, 0.0, 100.0
},
{
{
"seq_page_cost",
"Sets the planner's estimate of the cost of a sequentially fetched disk page.",
RELOPT_KIND_TABLESPACE,
ShareUpdateExclusiveLock
},
-1, 0.0, DBL_MAX
},
{
{
"random_page_cost",
"Sets the planner's estimate of the cost of a nonsequentially fetched disk page.",
RELOPT_KIND_TABLESPACE,
ShareUpdateExclusiveLock
},
-1, 0.0, DBL_MAX
},
{
{
"n_distinct",
"Sets the planner's estimate of the number of distinct values appearing in a column (excluding child relations).",
RELOPT_KIND_ATTRIBUTE,
ShareUpdateExclusiveLock
},
0, -1.0, DBL_MAX
},
{
{
"n_distinct_inherited",
"Sets the planner's estimate of the number of distinct values appearing in a column (including child relations).",
RELOPT_KIND_ATTRIBUTE,
ShareUpdateExclusiveLock
},
0, -1.0, DBL_MAX
},
Skip full index scan during cleanup of B-tree indexes when possible Vacuum of index consists from two stages: multiple (zero of more) ambulkdelete calls and one amvacuumcleanup call. When workload on particular table is append-only, then autovacuum isn't intended to touch this table. However, user may run vacuum manually in order to fill visibility map and get benefits of index-only scans. Then ambulkdelete wouldn't be called for indexes of such table (because no heap tuples were deleted), only amvacuumcleanup would be called In this case, amvacuumcleanup would perform full index scan for two objectives: put recyclable pages into free space map and update index statistics. This patch allows btvacuumclanup to skip full index scan when two conditions are satisfied: no pages are going to be put into free space map and index statistics isn't stalled. In order to check first condition, we store oldest btpo_xact in the meta-page. When it's precedes RecentGlobalXmin, then there are some recyclable pages. In order to check second condition we store number of heap tuples observed during previous full index scan by cleanup. If fraction of newly inserted tuples is less than vacuum_cleanup_index_scale_factor, then statistics isn't considered to be stalled. vacuum_cleanup_index_scale_factor can be defined as both reloption and GUC (default). This patch bumps B-tree meta-page version. Upgrade of meta-page is performed "on the fly": during VACUUM meta-page is rewritten with new version. No special handling in pg_upgrade is required. Author: Masahiko Sawada, Alexander Korotkov Review by: Peter Geoghegan, Kyotaro Horiguchi, Alexander Korotkov, Yura Sokolov Discussion: https://www.postgresql.org/message-id/flat/CAD21AoAX+d2oD_nrd9O2YkpzHaFr=uQeGr9s1rKC3O4ENc568g@mail.gmail.com
2018-04-04 18:29:00 +02:00
{
{
"vacuum_cleanup_index_scale_factor",
"Number of tuple inserts prior to index cleanup as a fraction of reltuples.",
RELOPT_KIND_BTREE,
ShareUpdateExclusiveLock
},
-1, 0.0, 1e10
Skip full index scan during cleanup of B-tree indexes when possible Vacuum of index consists from two stages: multiple (zero of more) ambulkdelete calls and one amvacuumcleanup call. When workload on particular table is append-only, then autovacuum isn't intended to touch this table. However, user may run vacuum manually in order to fill visibility map and get benefits of index-only scans. Then ambulkdelete wouldn't be called for indexes of such table (because no heap tuples were deleted), only amvacuumcleanup would be called In this case, amvacuumcleanup would perform full index scan for two objectives: put recyclable pages into free space map and update index statistics. This patch allows btvacuumclanup to skip full index scan when two conditions are satisfied: no pages are going to be put into free space map and index statistics isn't stalled. In order to check first condition, we store oldest btpo_xact in the meta-page. When it's precedes RecentGlobalXmin, then there are some recyclable pages. In order to check second condition we store number of heap tuples observed during previous full index scan by cleanup. If fraction of newly inserted tuples is less than vacuum_cleanup_index_scale_factor, then statistics isn't considered to be stalled. vacuum_cleanup_index_scale_factor can be defined as both reloption and GUC (default). This patch bumps B-tree meta-page version. Upgrade of meta-page is performed "on the fly": during VACUUM meta-page is rewritten with new version. No special handling in pg_upgrade is required. Author: Masahiko Sawada, Alexander Korotkov Review by: Peter Geoghegan, Kyotaro Horiguchi, Alexander Korotkov, Yura Sokolov Discussion: https://www.postgresql.org/message-id/flat/CAD21AoAX+d2oD_nrd9O2YkpzHaFr=uQeGr9s1rKC3O4ENc568g@mail.gmail.com
2018-04-04 18:29:00 +02:00
},
/* list terminator */
{{NULL}}
};
static relopt_string stringRelOpts[] =
{
{
{
"buffering",
"Enables buffering build for this GiST index",
RELOPT_KIND_GIST,
AccessExclusiveLock
},
4,
false,
gistValidateBufferingOption,
"auto"
},
{
{
"check_option",
"View has WITH CHECK OPTION defined (local or cascaded).",
RELOPT_KIND_VIEW,
AccessExclusiveLock
},
0,
true,
validateWithCheckOption,
NULL
},
/* list terminator */
{{NULL}}
};
static relopt_gen **relOpts = NULL;
static bits32 last_assigned_kind = RELOPT_KIND_LAST_DEFAULT;
static int num_custom_options = 0;
static relopt_gen **custom_options = NULL;
static bool need_initialization = true;
static void initialize_reloptions(void);
static void parse_one_reloption(relopt_value *option, char *text_str,
int text_len, bool validate);
/*
* initialize_reloptions
* initialization routine, must be called before parsing
*
* Initialize the relOpts array and fill each variable's type and name length.
*/
static void
initialize_reloptions(void)
{
int i;
int j;
j = 0;
for (i = 0; boolRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(boolRelOpts[i].gen.lockmode,
boolRelOpts[i].gen.lockmode));
j++;
}
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
intRelOpts[i].gen.lockmode));
j++;
}
for (i = 0; realRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(realRelOpts[i].gen.lockmode,
realRelOpts[i].gen.lockmode));
j++;
}
for (i = 0; stringRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(stringRelOpts[i].gen.lockmode,
stringRelOpts[i].gen.lockmode));
j++;
}
j += num_custom_options;
if (relOpts)
pfree(relOpts);
relOpts = MemoryContextAlloc(TopMemoryContext,
(j + 1) * sizeof(relopt_gen *));
j = 0;
for (i = 0; boolRelOpts[i].gen.name; i++)
{
relOpts[j] = &boolRelOpts[i].gen;
relOpts[j]->type = RELOPT_TYPE_BOOL;
relOpts[j]->namelen = strlen(relOpts[j]->name);
j++;
}
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
relOpts[j]->type = RELOPT_TYPE_INT;
relOpts[j]->namelen = strlen(relOpts[j]->name);
j++;
}
for (i = 0; realRelOpts[i].gen.name; i++)
{
relOpts[j] = &realRelOpts[i].gen;
relOpts[j]->type = RELOPT_TYPE_REAL;
relOpts[j]->namelen = strlen(relOpts[j]->name);
j++;
}
for (i = 0; stringRelOpts[i].gen.name; i++)
{
relOpts[j] = &stringRelOpts[i].gen;
relOpts[j]->type = RELOPT_TYPE_STRING;
relOpts[j]->namelen = strlen(relOpts[j]->name);
j++;
}
for (i = 0; i < num_custom_options; i++)
{
relOpts[j] = custom_options[i];
j++;
}
/* add a list terminator */
relOpts[j] = NULL;
/* flag the work is complete */
need_initialization = false;
}
/*
* add_reloption_kind
* Create a new relopt_kind value, to be used in custom reloptions by
* user-defined AMs.
*/
relopt_kind
add_reloption_kind(void)
{
/* don't hand out the last bit so that the enum's behavior is portable */
if (last_assigned_kind >= RELOPT_KIND_MAX)
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("user-defined relation parameter types limit exceeded")));
last_assigned_kind <<= 1;
return (relopt_kind) last_assigned_kind;
}
/*
* add_reloption
* Add an already-created custom reloption to the list, and recompute the
* main parser table.
*/
static void
add_reloption(relopt_gen *newoption)
{
static int max_custom_options = 0;
if (num_custom_options >= max_custom_options)
{
MemoryContext oldcxt;
oldcxt = MemoryContextSwitchTo(TopMemoryContext);
if (max_custom_options == 0)
{
max_custom_options = 8;
custom_options = palloc(max_custom_options * sizeof(relopt_gen *));
}
else
{
max_custom_options *= 2;
custom_options = repalloc(custom_options,
max_custom_options * sizeof(relopt_gen *));
}
MemoryContextSwitchTo(oldcxt);
}
custom_options[num_custom_options++] = newoption;
need_initialization = true;
}
/*
* allocate_reloption
* Allocate a new reloption and initialize the type-agnostic fields
* (for types other than string)
*/
static relopt_gen *
allocate_reloption(bits32 kinds, int type, const char *name, const char *desc)
{
MemoryContext oldcxt;
size_t size;
relopt_gen *newoption;
oldcxt = MemoryContextSwitchTo(TopMemoryContext);
switch (type)
{
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
case RELOPT_TYPE_REAL:
size = sizeof(relopt_real);
break;
case RELOPT_TYPE_STRING:
size = sizeof(relopt_string);
break;
default:
elog(ERROR, "unsupported reloption type %d", type);
return NULL; /* keep compiler quiet */
}
newoption = palloc(size);
newoption->name = pstrdup(name);
if (desc)
newoption->desc = pstrdup(desc);
else
newoption->desc = NULL;
newoption->kinds = kinds;
newoption->namelen = strlen(name);
newoption->type = type;
MemoryContextSwitchTo(oldcxt);
return newoption;
}
/*
* add_bool_reloption
* Add a new boolean reloption
*/
void
add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val)
{
relopt_bool *newoption;
newoption = (relopt_bool *) allocate_reloption(kinds, RELOPT_TYPE_BOOL,
name, desc);
newoption->default_val = default_val;
add_reloption((relopt_gen *) newoption);
}
/*
* add_int_reloption
* Add a new integer reloption
*/
void
add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val,
int min_val, int max_val)
{
relopt_int *newoption;
newoption = (relopt_int *) allocate_reloption(kinds, RELOPT_TYPE_INT,
name, desc);
newoption->default_val = default_val;
newoption->min = min_val;
newoption->max = max_val;
add_reloption((relopt_gen *) newoption);
}
/*
* add_real_reloption
* Add a new float reloption
*/
void
add_real_reloption(bits32 kinds, const char *name, const char *desc, double default_val,
double min_val, double max_val)
{
relopt_real *newoption;
newoption = (relopt_real *) allocate_reloption(kinds, RELOPT_TYPE_REAL,
name, desc);
newoption->default_val = default_val;
newoption->min = min_val;
newoption->max = max_val;
add_reloption((relopt_gen *) newoption);
}
/*
* add_string_reloption
* Add a new string reloption
*
* "validator" is an optional function pointer that can be used to test the
* validity of the values. It must elog(ERROR) when the argument string is
* not acceptable for the variable. Note that the default value must pass
* the validation.
*/
void
add_string_reloption(bits32 kinds, const char *name, const char *desc, const char *default_val,
validate_string_relopt validator)
{
relopt_string *newoption;
/* make sure the validator/default combination is sane */
if (validator)
(validator) (default_val);
newoption = (relopt_string *) allocate_reloption(kinds, RELOPT_TYPE_STRING,
name, desc);
newoption->validate_cb = validator;
if (default_val)
{
newoption->default_val = MemoryContextStrdup(TopMemoryContext,
default_val);
newoption->default_len = strlen(default_val);
newoption->default_isnull = false;
}
else
{
newoption->default_val = "";
newoption->default_len = 0;
newoption->default_isnull = true;
}
add_reloption((relopt_gen *) newoption);
}
/*
* Transform a relation options list (list of DefElem) into the text array
* format that is kept in pg_class.reloptions, including only those options
* that are in the passed namespace. The output values do not include the
* namespace.
*
* This is used for three cases: CREATE TABLE/INDEX, ALTER TABLE SET, and
* ALTER TABLE RESET. In the ALTER cases, oldOptions is the existing
* reloptions value (possibly NULL), and we replace or remove entries
* as needed.
*
Remove WITH OIDS support, change oid catalog column visibility. Previously tables declared WITH OIDS, including a significant fraction of the catalog tables, stored the oid column not as a normal column, but as part of the tuple header. This special column was not shown by default, which was somewhat odd, as it's often (consider e.g. pg_class.oid) one of the more important parts of a row. Neither pg_dump nor COPY included the contents of the oid column by default. The fact that the oid column was not an ordinary column necessitated a significant amount of special case code to support oid columns. That already was painful for the existing, but upcoming work aiming to make table storage pluggable, would have required expanding and duplicating that "specialness" significantly. WITH OIDS has been deprecated since 2005 (commit ff02d0a05280e0). Remove it. Removing includes: - CREATE TABLE and ALTER TABLE syntax for declaring the table to be WITH OIDS has been removed (WITH (oids[ = true]) will error out) - pg_dump does not support dumping tables declared WITH OIDS and will issue a warning when dumping one (and ignore the oid column). - restoring an pg_dump archive with pg_restore will warn when restoring a table with oid contents (and ignore the oid column) - COPY will refuse to load binary dump that includes oids. - pg_upgrade will error out when encountering tables declared WITH OIDS, they have to be altered to remove the oid column first. - Functionality to access the oid of the last inserted row (like plpgsql's RESULT_OID, spi's SPI_lastoid, ...) has been removed. The syntax for declaring a table WITHOUT OIDS (or WITH (oids = false) for CREATE TABLE) is still supported. While that requires a bit of support code, it seems unnecessary to break applications / dumps that do not use oids, and are explicit about not using them. The biggest user of WITH OID columns was postgres' catalog. This commit changes all 'magic' oid columns to be columns that are normally declared and stored. To reduce unnecessary query breakage all the newly added columns are still named 'oid', even if a table's column naming scheme would indicate 'reloid' or such. This obviously requires adapting a lot code, mostly replacing oid access via HeapTupleGetOid() with access to the underlying Form_pg_*->oid column. The bootstrap process now assigns oids for all oid columns in genbki.pl that do not have an explicit value (starting at the largest oid previously used), only oids assigned later by oids will be above FirstBootstrapObjectId. As the oid column now is a normal column the special bootstrap syntax for oids has been removed. Oids are not automatically assigned during insertion anymore, all backend code explicitly assigns oids with GetNewOidWithIndex(). For the rare case that insertions into the catalog via SQL are called for the new pg_nextoid() function can be used (which only works on catalog tables). The fact that oid columns on system tables are now normal columns means that they will be included in the set of columns expanded by * (i.e. SELECT * FROM pg_class will now include the table's oid, previously it did not). It'd not technically be hard to hide oid column by default, but that'd mean confusing behavior would either have to be carried forward forever, or it'd cause breakage down the line. While it's not unlikely that further adjustments are needed, the scope/invasiveness of the patch makes it worthwhile to get merge this now. It's painful to maintain externally, too complicated to commit after the code code freeze, and a dependency of a number of other patches. Catversion bump, for obvious reasons. Author: Andres Freund, with contributions by John Naylor Discussion: https://postgr.es/m/20180930034810.ywp2c7awz7opzcfr@alap3.anarazel.de
2018-11-21 00:36:57 +01:00
* If acceptOidsOff is true, then we allow oids = false, but throw error when
* on. This is solely needed for backwards compatibility.
*
* Note that this is not responsible for determining whether the options
* are valid, but it does check that namespaces for all the options given are
2012-04-25 20:28:58 +02:00
* listed in validnsps. The NULL namespace is always valid and need not be
* explicitly listed. Passing a NULL pointer means that only the NULL
* namespace is valid.
*
* Both oldOptions and the result are text arrays (or NULL for "default"),
* but we declare them as Datums to avoid including array.h in reloptions.h.
*/
Datum
transformRelOptions(Datum oldOptions, List *defList, const char *namspace,
Remove WITH OIDS support, change oid catalog column visibility. Previously tables declared WITH OIDS, including a significant fraction of the catalog tables, stored the oid column not as a normal column, but as part of the tuple header. This special column was not shown by default, which was somewhat odd, as it's often (consider e.g. pg_class.oid) one of the more important parts of a row. Neither pg_dump nor COPY included the contents of the oid column by default. The fact that the oid column was not an ordinary column necessitated a significant amount of special case code to support oid columns. That already was painful for the existing, but upcoming work aiming to make table storage pluggable, would have required expanding and duplicating that "specialness" significantly. WITH OIDS has been deprecated since 2005 (commit ff02d0a05280e0). Remove it. Removing includes: - CREATE TABLE and ALTER TABLE syntax for declaring the table to be WITH OIDS has been removed (WITH (oids[ = true]) will error out) - pg_dump does not support dumping tables declared WITH OIDS and will issue a warning when dumping one (and ignore the oid column). - restoring an pg_dump archive with pg_restore will warn when restoring a table with oid contents (and ignore the oid column) - COPY will refuse to load binary dump that includes oids. - pg_upgrade will error out when encountering tables declared WITH OIDS, they have to be altered to remove the oid column first. - Functionality to access the oid of the last inserted row (like plpgsql's RESULT_OID, spi's SPI_lastoid, ...) has been removed. The syntax for declaring a table WITHOUT OIDS (or WITH (oids = false) for CREATE TABLE) is still supported. While that requires a bit of support code, it seems unnecessary to break applications / dumps that do not use oids, and are explicit about not using them. The biggest user of WITH OID columns was postgres' catalog. This commit changes all 'magic' oid columns to be columns that are normally declared and stored. To reduce unnecessary query breakage all the newly added columns are still named 'oid', even if a table's column naming scheme would indicate 'reloid' or such. This obviously requires adapting a lot code, mostly replacing oid access via HeapTupleGetOid() with access to the underlying Form_pg_*->oid column. The bootstrap process now assigns oids for all oid columns in genbki.pl that do not have an explicit value (starting at the largest oid previously used), only oids assigned later by oids will be above FirstBootstrapObjectId. As the oid column now is a normal column the special bootstrap syntax for oids has been removed. Oids are not automatically assigned during insertion anymore, all backend code explicitly assigns oids with GetNewOidWithIndex(). For the rare case that insertions into the catalog via SQL are called for the new pg_nextoid() function can be used (which only works on catalog tables). The fact that oid columns on system tables are now normal columns means that they will be included in the set of columns expanded by * (i.e. SELECT * FROM pg_class will now include the table's oid, previously it did not). It'd not technically be hard to hide oid column by default, but that'd mean confusing behavior would either have to be carried forward forever, or it'd cause breakage down the line. While it's not unlikely that further adjustments are needed, the scope/invasiveness of the patch makes it worthwhile to get merge this now. It's painful to maintain externally, too complicated to commit after the code code freeze, and a dependency of a number of other patches. Catversion bump, for obvious reasons. Author: Andres Freund, with contributions by John Naylor Discussion: https://postgr.es/m/20180930034810.ywp2c7awz7opzcfr@alap3.anarazel.de
2018-11-21 00:36:57 +01:00
char *validnsps[], bool acceptOidsOff, bool isReset)
{
Datum result;
ArrayBuildState *astate;
ListCell *cell;
/* no change if empty list */
if (defList == NIL)
return oldOptions;
/* We build new array using accumArrayResult */
astate = NULL;
/* Copy any oldOptions that aren't to be replaced */
if (PointerIsValid(DatumGetPointer(oldOptions)))
{
2006-10-04 02:30:14 +02:00
ArrayType *array = DatumGetArrayTypeP(oldOptions);
Datum *oldoptions;
int noldoptions;
int i;
deconstruct_array(array, TEXTOID, -1, false, 'i',
&oldoptions, NULL, &noldoptions);
for (i = 0; i < noldoptions; i++)
{
char *text_str = VARDATA(oldoptions[i]);
int text_len = VARSIZE(oldoptions[i]) - VARHDRSZ;
/* Search for a match in defList */
foreach(cell, defList)
{
DefElem *def = (DefElem *) lfirst(cell);
int kw_len;
/* ignore if not in the same namespace */
if (namspace == NULL)
{
if (def->defnamespace != NULL)
continue;
}
else if (def->defnamespace == NULL)
continue;
Avoid unnecessary use of pg_strcasecmp for already-downcased identifiers. We have a lot of code in which option names, which from the user's viewpoint are logically keywords, are passed through the grammar as plain identifiers, and then matched to string literals during command execution. This approach avoids making words into lexer keywords unnecessarily. Some places matched these strings using plain strcmp, some using pg_strcasecmp. But the latter should be unnecessary since identifiers would have been downcased on their way through the parser. Aside from any efficiency concerns (probably not a big factor), the lack of consistency in this area creates a hazard of subtle bugs due to different places coming to different conclusions about whether two option names are the same or different. Hence, standardize on using strcmp() to match any option names that are expected to have been fed through the parser. This does create a user-visible behavioral change, which is that while formerly all of these would work: alter table foo set (fillfactor = 50); alter table foo set (FillFactor = 50); alter table foo set ("fillfactor" = 50); alter table foo set ("FillFactor" = 50); now the last case will fail because that double-quoted identifier is different from the others. However, none of our documentation says that you can use a quoted identifier in such contexts at all, and we should discourage doing so since it would break if we ever decide to parse such constructs as true lexer keywords rather than poor man's substitutes. So this shouldn't create a significant compatibility issue for users. Daniel Gustafsson, reviewed by Michael Paquier, small changes by me Discussion: https://postgr.es/m/29405B24-564E-476B-98C0-677A29805B84@yesql.se
2018-01-27 00:25:02 +01:00
else if (strcmp(def->defnamespace, namspace) != 0)
continue;
kw_len = strlen(def->defname);
if (text_len > kw_len && text_str[kw_len] == '=' &&
Avoid unnecessary use of pg_strcasecmp for already-downcased identifiers. We have a lot of code in which option names, which from the user's viewpoint are logically keywords, are passed through the grammar as plain identifiers, and then matched to string literals during command execution. This approach avoids making words into lexer keywords unnecessarily. Some places matched these strings using plain strcmp, some using pg_strcasecmp. But the latter should be unnecessary since identifiers would have been downcased on their way through the parser. Aside from any efficiency concerns (probably not a big factor), the lack of consistency in this area creates a hazard of subtle bugs due to different places coming to different conclusions about whether two option names are the same or different. Hence, standardize on using strcmp() to match any option names that are expected to have been fed through the parser. This does create a user-visible behavioral change, which is that while formerly all of these would work: alter table foo set (fillfactor = 50); alter table foo set (FillFactor = 50); alter table foo set ("fillfactor" = 50); alter table foo set ("FillFactor" = 50); now the last case will fail because that double-quoted identifier is different from the others. However, none of our documentation says that you can use a quoted identifier in such contexts at all, and we should discourage doing so since it would break if we ever decide to parse such constructs as true lexer keywords rather than poor man's substitutes. So this shouldn't create a significant compatibility issue for users. Daniel Gustafsson, reviewed by Michael Paquier, small changes by me Discussion: https://postgr.es/m/29405B24-564E-476B-98C0-677A29805B84@yesql.se
2018-01-27 00:25:02 +01:00
strncmp(text_str, def->defname, kw_len) == 0)
break;
}
if (!cell)
{
/* No match, so keep old option */
astate = accumArrayResult(astate, oldoptions[i],
false, TEXTOID,
CurrentMemoryContext);
}
}
}
/*
2006-10-04 02:30:14 +02:00
* If CREATE/SET, add new options to array; if RESET, just check that the
* user didn't say RESET (option=val). (Must do this because the grammar
* doesn't enforce it.)
*/
foreach(cell, defList)
{
DefElem *def = (DefElem *) lfirst(cell);
if (isReset)
{
if (def->arg != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("RESET must not include values for parameters")));
}
else
{
2006-10-04 02:30:14 +02:00
text *t;
const char *value;
2006-10-04 02:30:14 +02:00
Size len;
/*
* Error out if the namespace is not valid. A NULL namespace is
* always valid.
*/
if (def->defnamespace != NULL)
{
bool valid = false;
int i;
if (validnsps)
{
for (i = 0; validnsps[i]; i++)
{
Avoid unnecessary use of pg_strcasecmp for already-downcased identifiers. We have a lot of code in which option names, which from the user's viewpoint are logically keywords, are passed through the grammar as plain identifiers, and then matched to string literals during command execution. This approach avoids making words into lexer keywords unnecessarily. Some places matched these strings using plain strcmp, some using pg_strcasecmp. But the latter should be unnecessary since identifiers would have been downcased on their way through the parser. Aside from any efficiency concerns (probably not a big factor), the lack of consistency in this area creates a hazard of subtle bugs due to different places coming to different conclusions about whether two option names are the same or different. Hence, standardize on using strcmp() to match any option names that are expected to have been fed through the parser. This does create a user-visible behavioral change, which is that while formerly all of these would work: alter table foo set (fillfactor = 50); alter table foo set (FillFactor = 50); alter table foo set ("fillfactor" = 50); alter table foo set ("FillFactor" = 50); now the last case will fail because that double-quoted identifier is different from the others. However, none of our documentation says that you can use a quoted identifier in such contexts at all, and we should discourage doing so since it would break if we ever decide to parse such constructs as true lexer keywords rather than poor man's substitutes. So this shouldn't create a significant compatibility issue for users. Daniel Gustafsson, reviewed by Michael Paquier, small changes by me Discussion: https://postgr.es/m/29405B24-564E-476B-98C0-677A29805B84@yesql.se
2018-01-27 00:25:02 +01:00
if (strcmp(def->defnamespace, validnsps[i]) == 0)
{
valid = true;
break;
}
}
}
if (!valid)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unrecognized parameter namespace \"%s\"",
def->defnamespace)));
}
/* ignore if not in the same namespace */
if (namspace == NULL)
{
if (def->defnamespace != NULL)
continue;
}
else if (def->defnamespace == NULL)
continue;
Avoid unnecessary use of pg_strcasecmp for already-downcased identifiers. We have a lot of code in which option names, which from the user's viewpoint are logically keywords, are passed through the grammar as plain identifiers, and then matched to string literals during command execution. This approach avoids making words into lexer keywords unnecessarily. Some places matched these strings using plain strcmp, some using pg_strcasecmp. But the latter should be unnecessary since identifiers would have been downcased on their way through the parser. Aside from any efficiency concerns (probably not a big factor), the lack of consistency in this area creates a hazard of subtle bugs due to different places coming to different conclusions about whether two option names are the same or different. Hence, standardize on using strcmp() to match any option names that are expected to have been fed through the parser. This does create a user-visible behavioral change, which is that while formerly all of these would work: alter table foo set (fillfactor = 50); alter table foo set (FillFactor = 50); alter table foo set ("fillfactor" = 50); alter table foo set ("FillFactor" = 50); now the last case will fail because that double-quoted identifier is different from the others. However, none of our documentation says that you can use a quoted identifier in such contexts at all, and we should discourage doing so since it would break if we ever decide to parse such constructs as true lexer keywords rather than poor man's substitutes. So this shouldn't create a significant compatibility issue for users. Daniel Gustafsson, reviewed by Michael Paquier, small changes by me Discussion: https://postgr.es/m/29405B24-564E-476B-98C0-677A29805B84@yesql.se
2018-01-27 00:25:02 +01:00
else if (strcmp(def->defnamespace, namspace) != 0)
continue;
/*
* Flatten the DefElem into a text string like "name=arg". If we
* have just "name", assume "name=true" is meant. Note: the
* namespace is not output.
*/
if (def->arg != NULL)
value = defGetString(def);
else
value = "true";
Remove WITH OIDS support, change oid catalog column visibility. Previously tables declared WITH OIDS, including a significant fraction of the catalog tables, stored the oid column not as a normal column, but as part of the tuple header. This special column was not shown by default, which was somewhat odd, as it's often (consider e.g. pg_class.oid) one of the more important parts of a row. Neither pg_dump nor COPY included the contents of the oid column by default. The fact that the oid column was not an ordinary column necessitated a significant amount of special case code to support oid columns. That already was painful for the existing, but upcoming work aiming to make table storage pluggable, would have required expanding and duplicating that "specialness" significantly. WITH OIDS has been deprecated since 2005 (commit ff02d0a05280e0). Remove it. Removing includes: - CREATE TABLE and ALTER TABLE syntax for declaring the table to be WITH OIDS has been removed (WITH (oids[ = true]) will error out) - pg_dump does not support dumping tables declared WITH OIDS and will issue a warning when dumping one (and ignore the oid column). - restoring an pg_dump archive with pg_restore will warn when restoring a table with oid contents (and ignore the oid column) - COPY will refuse to load binary dump that includes oids. - pg_upgrade will error out when encountering tables declared WITH OIDS, they have to be altered to remove the oid column first. - Functionality to access the oid of the last inserted row (like plpgsql's RESULT_OID, spi's SPI_lastoid, ...) has been removed. The syntax for declaring a table WITHOUT OIDS (or WITH (oids = false) for CREATE TABLE) is still supported. While that requires a bit of support code, it seems unnecessary to break applications / dumps that do not use oids, and are explicit about not using them. The biggest user of WITH OID columns was postgres' catalog. This commit changes all 'magic' oid columns to be columns that are normally declared and stored. To reduce unnecessary query breakage all the newly added columns are still named 'oid', even if a table's column naming scheme would indicate 'reloid' or such. This obviously requires adapting a lot code, mostly replacing oid access via HeapTupleGetOid() with access to the underlying Form_pg_*->oid column. The bootstrap process now assigns oids for all oid columns in genbki.pl that do not have an explicit value (starting at the largest oid previously used), only oids assigned later by oids will be above FirstBootstrapObjectId. As the oid column now is a normal column the special bootstrap syntax for oids has been removed. Oids are not automatically assigned during insertion anymore, all backend code explicitly assigns oids with GetNewOidWithIndex(). For the rare case that insertions into the catalog via SQL are called for the new pg_nextoid() function can be used (which only works on catalog tables). The fact that oid columns on system tables are now normal columns means that they will be included in the set of columns expanded by * (i.e. SELECT * FROM pg_class will now include the table's oid, previously it did not). It'd not technically be hard to hide oid column by default, but that'd mean confusing behavior would either have to be carried forward forever, or it'd cause breakage down the line. While it's not unlikely that further adjustments are needed, the scope/invasiveness of the patch makes it worthwhile to get merge this now. It's painful to maintain externally, too complicated to commit after the code code freeze, and a dependency of a number of other patches. Catversion bump, for obvious reasons. Author: Andres Freund, with contributions by John Naylor Discussion: https://postgr.es/m/20180930034810.ywp2c7awz7opzcfr@alap3.anarazel.de
2018-11-21 00:36:57 +01:00
/*
* This is not a great place for this test, but there's no other
* convenient place to filter the option out. As WITH (oids =
* false) will be removed someday, this seems like an acceptable
* amount of ugly.
*/
if (acceptOidsOff && def->defnamespace == NULL &&
strcmp(def->defname, "oids") == 0)
{
if (defGetBoolean(def))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("tables declared WITH OIDS are not supported")));
/* skip over option, reloptions machinery doesn't know it */
continue;
}
len = VARHDRSZ + strlen(def->defname) + 1 + strlen(value);
/* +1 leaves room for sprintf's trailing null */
t = (text *) palloc(len + 1);
SET_VARSIZE(t, len);
sprintf(VARDATA(t), "%s=%s", def->defname, value);
astate = accumArrayResult(astate, PointerGetDatum(t),
false, TEXTOID,
CurrentMemoryContext);
}
}
if (astate)
result = makeArrayResult(astate, CurrentMemoryContext);
else
result = (Datum) 0;
return result;
}
/*
* Convert the text-array format of reloptions into a List of DefElem.
* This is the inverse of transformRelOptions().
*/
List *
untransformRelOptions(Datum options)
{
List *result = NIL;
ArrayType *array;
Datum *optiondatums;
int noptions;
int i;
/* Nothing to do if no options */
if (!PointerIsValid(DatumGetPointer(options)))
return result;
array = DatumGetArrayTypeP(options);
deconstruct_array(array, TEXTOID, -1, false, 'i',
&optiondatums, NULL, &noptions);
for (i = 0; i < noptions; i++)
{
char *s;
char *p;
Node *val = NULL;
s = TextDatumGetCString(optiondatums[i]);
p = strchr(s, '=');
if (p)
{
*p++ = '\0';
val = (Node *) makeString(pstrdup(p));
}
result = lappend(result, makeDefElem(pstrdup(s), val, -1));
}
return result;
}
/*
* Extract and parse reloptions from a pg_class tuple.
*
* This is a low-level routine, expected to be used by relcache code and
* callers that do not have a table's relcache entry (e.g. autovacuum). For
* other uses, consider grabbing the rd_options pointer from the relcache entry
* instead.
*
* tupdesc is pg_class' tuple descriptor. amoptions is a pointer to the index
* AM's options parser function in the case of a tuple corresponding to an
* index, or NULL otherwise.
*/
bytea *
extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
amoptions_function amoptions)
{
bytea *options;
bool isnull;
Datum datum;
Form_pg_class classForm;
datum = fastgetattr(tuple,
Anum_pg_class_reloptions,
tupdesc,
&isnull);
if (isnull)
return NULL;
classForm = (Form_pg_class) GETSTRUCT(tuple);
/* Parse into appropriate format; don't error out here */
switch (classForm->relkind)
{
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
Implement table partitioning. Table partitioning is like table inheritance and reuses much of the existing infrastructure, but there are some important differences. The parent is called a partitioned table and is always empty; it may not have indexes or non-inherited constraints, since those make no sense for a relation with no data of its own. The children are called partitions and contain all of the actual data. Each partition has an implicit partitioning constraint. Multiple inheritance is not allowed, and partitioning and inheritance can't be mixed. Partitions can't have extra columns and may not allow nulls unless the parent does. Tuples inserted into the parent are automatically routed to the correct partition, so tuple-routing ON INSERT triggers are not needed. Tuple routing isn't yet supported for partitions which are foreign tables, and it doesn't handle updates that cross partition boundaries. Currently, tables can be range-partitioned or list-partitioned. List partitioning is limited to a single column, but range partitioning can involve multiple columns. A partitioning "column" can be an expression. Because table partitioning is less general than table inheritance, it is hoped that it will be easier to reason about properties of partitions, and therefore that this will serve as a better foundation for a variety of possible optimizations, including query planner optimizations. The tuple routing based which this patch does based on the implicit partitioning constraints is an example of this, but it seems likely that many other useful optimizations are also possible. Amit Langote, reviewed and tested by Robert Haas, Ashutosh Bapat, Amit Kapila, Rajkumar Raghuwanshi, Corey Huinker, Jaime Casanova, Rushabh Lathia, Erik Rijkers, among others. Minor revisions by me.
2016-12-07 19:17:43 +01:00
case RELKIND_PARTITIONED_TABLE:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
options = view_reloptions(datum, false);
break;
case RELKIND_INDEX:
case RELKIND_PARTITIONED_INDEX:
options = index_reloptions(amoptions, datum, false);
break;
case RELKIND_FOREIGN_TABLE:
options = NULL;
break;
default:
Assert(false); /* can't get here */
options = NULL; /* keep compiler quiet */
break;
}
return options;
}
/*
* Interpret reloptions that are given in text-array format.
*
* options is a reloption text array as constructed by transformRelOptions.
* kind specifies the family of options to be processed.
*
* The return value is a relopt_value * array on which the options actually
* set in the options array are marked with isset=true. The length of this
* array is returned in *numrelopts. Options not set are also present in the
* array; this is so that the caller can easily locate the default values.
*
* If there are no options of the given kind, numrelopts is set to 0 and NULL
* is returned (unless options are illegally supplied despite none being
* defined, in which case an error occurs).
*
* Note: values of type int, bool and real are allocated as part of the
* returned array. Values of type string are allocated separately and must
* be freed by the caller.
*/
relopt_value *
parseRelOptions(Datum options, bool validate, relopt_kind kind,
int *numrelopts)
{
relopt_value *reloptions = NULL;
int numoptions = 0;
int i;
int j;
if (need_initialization)
initialize_reloptions();
/* Build a list of expected options, based on kind */
for (i = 0; relOpts[i]; i++)
if (relOpts[i]->kinds & kind)
numoptions++;
if (numoptions > 0)
{
reloptions = palloc(numoptions * sizeof(relopt_value));
for (i = 0, j = 0; relOpts[i]; i++)
{
if (relOpts[i]->kinds & kind)
{
reloptions[j].gen = relOpts[i];
reloptions[j].isset = false;
j++;
}
}
}
/* Done if no options */
if (PointerIsValid(DatumGetPointer(options)))
{
ArrayType *array = DatumGetArrayTypeP(options);
Datum *optiondatums;
int noptions;
deconstruct_array(array, TEXTOID, -1, false, 'i',
&optiondatums, NULL, &noptions);
for (i = 0; i < noptions; i++)
{
char *text_str = VARDATA(optiondatums[i]);
int text_len = VARSIZE(optiondatums[i]) - VARHDRSZ;
int j;
/* Search for a match in reloptions */
for (j = 0; j < numoptions; j++)
{
int kw_len = reloptions[j].gen->namelen;
if (text_len > kw_len && text_str[kw_len] == '=' &&
Avoid unnecessary use of pg_strcasecmp for already-downcased identifiers. We have a lot of code in which option names, which from the user's viewpoint are logically keywords, are passed through the grammar as plain identifiers, and then matched to string literals during command execution. This approach avoids making words into lexer keywords unnecessarily. Some places matched these strings using plain strcmp, some using pg_strcasecmp. But the latter should be unnecessary since identifiers would have been downcased on their way through the parser. Aside from any efficiency concerns (probably not a big factor), the lack of consistency in this area creates a hazard of subtle bugs due to different places coming to different conclusions about whether two option names are the same or different. Hence, standardize on using strcmp() to match any option names that are expected to have been fed through the parser. This does create a user-visible behavioral change, which is that while formerly all of these would work: alter table foo set (fillfactor = 50); alter table foo set (FillFactor = 50); alter table foo set ("fillfactor" = 50); alter table foo set ("FillFactor" = 50); now the last case will fail because that double-quoted identifier is different from the others. However, none of our documentation says that you can use a quoted identifier in such contexts at all, and we should discourage doing so since it would break if we ever decide to parse such constructs as true lexer keywords rather than poor man's substitutes. So this shouldn't create a significant compatibility issue for users. Daniel Gustafsson, reviewed by Michael Paquier, small changes by me Discussion: https://postgr.es/m/29405B24-564E-476B-98C0-677A29805B84@yesql.se
2018-01-27 00:25:02 +01:00
strncmp(text_str, reloptions[j].gen->name, kw_len) == 0)
{
parse_one_reloption(&reloptions[j], text_str, text_len,
validate);
break;
}
}
if (j >= numoptions && validate)
{
char *s;
char *p;
s = TextDatumGetCString(optiondatums[i]);
p = strchr(s, '=');
if (p)
*p = '\0';
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unrecognized parameter \"%s\"", s)));
}
}
/* It's worth avoiding memory leaks in this function */
pfree(optiondatums);
if (((void *) array) != DatumGetPointer(options))
pfree(array);
}
*numrelopts = numoptions;
return reloptions;
}
/*
* Subroutine for parseRelOptions, to parse and validate a single option's
* value
*/
static void
parse_one_reloption(relopt_value *option, char *text_str, int text_len,
bool validate)
{
char *value;
int value_len;
bool parsed;
bool nofree = false;
if (option->isset && validate)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("parameter \"%s\" specified more than once",
option->gen->name)));
value_len = text_len - option->gen->namelen - 1;
value = (char *) palloc(value_len + 1);
memcpy(value, text_str + option->gen->namelen + 1, value_len);
value[value_len] = '\0';
switch (option->gen->type)
{
case RELOPT_TYPE_BOOL:
{
parsed = parse_bool(value, &option->values.bool_val);
if (validate && !parsed)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid value for boolean option \"%s\": %s",
option->gen->name, value)));
}
break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
parsed = parse_int(value, &option->values.int_val, 0, NULL);
if (validate && !parsed)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid value for integer option \"%s\": %s",
option->gen->name, value)));
if (validate && (option->values.int_val < optint->min ||
option->values.int_val > optint->max))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("value %s out of bounds for option \"%s\"",
value, option->gen->name),
errdetail("Valid values are between \"%d\" and \"%d\".",
optint->min, optint->max)));
}
break;
case RELOPT_TYPE_REAL:
{
relopt_real *optreal = (relopt_real *) option->gen;
parsed = parse_real(value, &option->values.real_val);
if (validate && !parsed)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid value for floating point option \"%s\": %s",
option->gen->name, value)));
if (validate && (option->values.real_val < optreal->min ||
option->values.real_val > optreal->max))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("value %s out of bounds for option \"%s\"",
value, option->gen->name),
errdetail("Valid values are between \"%f\" and \"%f\".",
optreal->min, optreal->max)));
}
break;
case RELOPT_TYPE_STRING:
{
relopt_string *optstring = (relopt_string *) option->gen;
option->values.string_val = value;
nofree = true;
if (validate && optstring->validate_cb)
(optstring->validate_cb) (value);
parsed = true;
}
break;
default:
elog(ERROR, "unsupported reloption type %d", option->gen->type);
parsed = true; /* quiet compiler */
break;
}
if (parsed)
option->isset = true;
if (!nofree)
pfree(value);
}
/*
* Given the result from parseRelOptions, allocate a struct that's of the
* specified base size plus any extra space that's needed for string variables.
*
* "base" should be sizeof(struct) of the reloptions struct (StdRdOptions or
* equivalent).
*/
void *
allocateReloptStruct(Size base, relopt_value *options, int numoptions)
{
Size size = base;
int i;
for (i = 0; i < numoptions; i++)
if (options[i].gen->type == RELOPT_TYPE_STRING)
size += GET_STRING_RELOPTION_LEN(options[i]) + 1;
return palloc0(size);
}
/*
* Given the result of parseRelOptions and a parsing table, fill in the
* struct (previously allocated with allocateReloptStruct) with the parsed
* values.
*
* rdopts is the pointer to the allocated struct to be filled.
* basesize is the sizeof(struct) that was passed to allocateReloptStruct.
* options, of length numoptions, is parseRelOptions' output.
* elems, of length numelems, is the table describing the allowed options.
* When validate is true, it is expected that all options appear in elems.
*/
void
fillRelOptions(void *rdopts, Size basesize,
relopt_value *options, int numoptions,
bool validate,
const relopt_parse_elt *elems, int numelems)
{
int i;
int offset = basesize;
for (i = 0; i < numoptions; i++)
{
int j;
bool found = false;
for (j = 0; j < numelems; j++)
{
Avoid unnecessary use of pg_strcasecmp for already-downcased identifiers. We have a lot of code in which option names, which from the user's viewpoint are logically keywords, are passed through the grammar as plain identifiers, and then matched to string literals during command execution. This approach avoids making words into lexer keywords unnecessarily. Some places matched these strings using plain strcmp, some using pg_strcasecmp. But the latter should be unnecessary since identifiers would have been downcased on their way through the parser. Aside from any efficiency concerns (probably not a big factor), the lack of consistency in this area creates a hazard of subtle bugs due to different places coming to different conclusions about whether two option names are the same or different. Hence, standardize on using strcmp() to match any option names that are expected to have been fed through the parser. This does create a user-visible behavioral change, which is that while formerly all of these would work: alter table foo set (fillfactor = 50); alter table foo set (FillFactor = 50); alter table foo set ("fillfactor" = 50); alter table foo set ("FillFactor" = 50); now the last case will fail because that double-quoted identifier is different from the others. However, none of our documentation says that you can use a quoted identifier in such contexts at all, and we should discourage doing so since it would break if we ever decide to parse such constructs as true lexer keywords rather than poor man's substitutes. So this shouldn't create a significant compatibility issue for users. Daniel Gustafsson, reviewed by Michael Paquier, small changes by me Discussion: https://postgr.es/m/29405B24-564E-476B-98C0-677A29805B84@yesql.se
2018-01-27 00:25:02 +01:00
if (strcmp(options[i].gen->name, elems[j].optname) == 0)
{
relopt_string *optstring;
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
*(bool *) itempos = options[i].isset ?
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
((relopt_int *) options[i].gen)->default_val;
break;
case RELOPT_TYPE_REAL:
*(double *) itempos = options[i].isset ?
options[i].values.real_val :
((relopt_real *) options[i].gen)->default_val;
break;
case RELOPT_TYPE_STRING:
optstring = (relopt_string *) options[i].gen;
if (options[i].isset)
string_val = options[i].values.string_val;
else if (!optstring->default_isnull)
string_val = optstring->default_val;
else
string_val = NULL;
if (string_val == NULL)
*(int *) itempos = 0;
else
{
strcpy((char *) rdopts + offset, string_val);
*(int *) itempos = offset;
offset += strlen(string_val) + 1;
}
break;
default:
elog(ERROR, "unsupported reloption type %d",
options[i].gen->type);
break;
}
found = true;
break;
}
}
if (validate && !found && options[i].gen->kinds != RELOPT_KIND_INDEX)
elog(ERROR, "reloption \"%s\" not found in parse table",
options[i].gen->name);
}
SET_VARSIZE(rdopts, offset);
}
/*
* Option parser for anything that uses StdRdOptions.
*/
bytea *
default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
{
relopt_value *options;
StdRdOptions *rdopts;
int numoptions;
static const relopt_parse_elt tab[] = {
{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
{"autovacuum_enabled", RELOPT_TYPE_BOOL,
2017-06-21 20:39:04 +02:00
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
{"autovacuum_vacuum_threshold", RELOPT_TYPE_INT,
2017-06-21 20:39:04 +02:00
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_threshold)},
{"autovacuum_analyze_threshold", RELOPT_TYPE_INT,
2017-06-21 20:39:04 +02:00
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_threshold)},
{"autovacuum_vacuum_cost_delay", RELOPT_TYPE_INT,
2017-06-21 20:39:04 +02:00
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_delay)},
{"autovacuum_vacuum_cost_limit", RELOPT_TYPE_INT,
2017-06-21 20:39:04 +02:00
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_limit)},
{"autovacuum_freeze_min_age", RELOPT_TYPE_INT,
2017-06-21 20:39:04 +02:00
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_min_age)},
{"autovacuum_freeze_max_age", RELOPT_TYPE_INT,
2017-06-21 20:39:04 +02:00
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_max_age)},
{"autovacuum_freeze_table_age", RELOPT_TYPE_INT,
2017-06-21 20:39:04 +02:00
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_table_age)},
{"autovacuum_multixact_freeze_min_age", RELOPT_TYPE_INT,
2017-06-21 20:39:04 +02:00
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_min_age)},
{"autovacuum_multixact_freeze_max_age", RELOPT_TYPE_INT,
2017-06-21 20:39:04 +02:00
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_max_age)},
{"autovacuum_multixact_freeze_table_age", RELOPT_TYPE_INT,
2017-06-21 20:39:04 +02:00
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_table_age)},
{"log_autovacuum_min_duration", RELOPT_TYPE_INT,
2017-06-21 20:39:04 +02:00
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, log_min_duration)},
{"toast_tuple_target", RELOPT_TYPE_INT,
offsetof(StdRdOptions, toast_tuple_target)},
{"autovacuum_vacuum_scale_factor", RELOPT_TYPE_REAL,
2017-06-21 20:39:04 +02:00
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_scale_factor)},
{"autovacuum_analyze_scale_factor", RELOPT_TYPE_REAL,
2017-06-21 20:39:04 +02:00
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_scale_factor)},
{"user_catalog_table", RELOPT_TYPE_BOOL,
offsetof(StdRdOptions, user_catalog_table)},
{"parallel_workers", RELOPT_TYPE_INT,
Skip full index scan during cleanup of B-tree indexes when possible Vacuum of index consists from two stages: multiple (zero of more) ambulkdelete calls and one amvacuumcleanup call. When workload on particular table is append-only, then autovacuum isn't intended to touch this table. However, user may run vacuum manually in order to fill visibility map and get benefits of index-only scans. Then ambulkdelete wouldn't be called for indexes of such table (because no heap tuples were deleted), only amvacuumcleanup would be called In this case, amvacuumcleanup would perform full index scan for two objectives: put recyclable pages into free space map and update index statistics. This patch allows btvacuumclanup to skip full index scan when two conditions are satisfied: no pages are going to be put into free space map and index statistics isn't stalled. In order to check first condition, we store oldest btpo_xact in the meta-page. When it's precedes RecentGlobalXmin, then there are some recyclable pages. In order to check second condition we store number of heap tuples observed during previous full index scan by cleanup. If fraction of newly inserted tuples is less than vacuum_cleanup_index_scale_factor, then statistics isn't considered to be stalled. vacuum_cleanup_index_scale_factor can be defined as both reloption and GUC (default). This patch bumps B-tree meta-page version. Upgrade of meta-page is performed "on the fly": during VACUUM meta-page is rewritten with new version. No special handling in pg_upgrade is required. Author: Masahiko Sawada, Alexander Korotkov Review by: Peter Geoghegan, Kyotaro Horiguchi, Alexander Korotkov, Yura Sokolov Discussion: https://www.postgresql.org/message-id/flat/CAD21AoAX+d2oD_nrd9O2YkpzHaFr=uQeGr9s1rKC3O4ENc568g@mail.gmail.com
2018-04-04 18:29:00 +02:00
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_cleanup_index_scale_factor", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_cleanup_index_scale_factor)}
};
options = parseRelOptions(reloptions, validate, kind, &numoptions);
/* if none set, we're done */
if (numoptions == 0)
return NULL;
rdopts = allocateReloptStruct(sizeof(StdRdOptions), options, numoptions);
fillRelOptions((void *) rdopts, sizeof(StdRdOptions), options, numoptions,
validate, tab, lengthof(tab));
pfree(options);
return (bytea *) rdopts;
}
/*
* Option parser for views
*/
bytea *
view_reloptions(Datum reloptions, bool validate)
{
relopt_value *options;
ViewOptions *vopts;
int numoptions;
static const relopt_parse_elt tab[] = {
{"security_barrier", RELOPT_TYPE_BOOL,
offsetof(ViewOptions, security_barrier)},
{"check_option", RELOPT_TYPE_STRING,
offsetof(ViewOptions, check_option_offset)}
};
options = parseRelOptions(reloptions, validate, RELOPT_KIND_VIEW, &numoptions);
/* if none set, we're done */
if (numoptions == 0)
return NULL;
vopts = allocateReloptStruct(sizeof(ViewOptions), options, numoptions);
fillRelOptions((void *) vopts, sizeof(ViewOptions), options, numoptions,
validate, tab, lengthof(tab));
pfree(options);
return (bytea *) vopts;
}
/*
* Parse options for heaps, views and toast tables.
*/
bytea *
heap_reloptions(char relkind, Datum reloptions, bool validate)
{
StdRdOptions *rdopts;
switch (relkind)
{
case RELKIND_TOASTVALUE:
rdopts = (StdRdOptions *)
default_reloptions(reloptions, validate, RELOPT_KIND_TOAST);
if (rdopts != NULL)
{
/* adjust default-only parameters for TOAST relations */
rdopts->fillfactor = 100;
rdopts->autovacuum.analyze_threshold = -1;
rdopts->autovacuum.analyze_scale_factor = -1;
}
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
case RELKIND_PARTITIONED_TABLE:
return default_reloptions(reloptions, validate,
RELOPT_KIND_PARTITIONED);
default:
/* other relkinds are not supported */
return NULL;
}
}
/*
* Parse options for indexes.
*
* amoptions index AM's option parser function
* reloptions options as text[] datum
* validate error flag
*/
bytea *
index_reloptions(amoptions_function amoptions, Datum reloptions, bool validate)
{
Assert(amoptions != NULL);
/* Assume function is strict */
if (!PointerIsValid(DatumGetPointer(reloptions)))
return NULL;
return amoptions(reloptions, validate);
}
/*
* Parse generic options for all indexes.
*
* reloptions options as text[] datum
* validate error flag
*/
bytea *
index_generic_reloptions(Datum reloptions, bool validate)
{
int numoptions;
GenericIndexOpts *idxopts;
relopt_value *options;
static const relopt_parse_elt tab[] = {
{"recheck_on_update", RELOPT_TYPE_BOOL, offsetof(GenericIndexOpts, recheck_on_update)}
};
options = parseRelOptions(reloptions, validate,
RELOPT_KIND_INDEX,
&numoptions);
/* if none set, we're done */
if (numoptions == 0)
return NULL;
idxopts = allocateReloptStruct(sizeof(GenericIndexOpts), options, numoptions);
fillRelOptions((void *) idxopts, sizeof(GenericIndexOpts), options, numoptions,
validate, tab, lengthof(tab));
pfree(options);
return (bytea *) idxopts;
}
/*
* Option parser for attribute reloptions
*/
bytea *
attribute_reloptions(Datum reloptions, bool validate)
{
relopt_value *options;
2010-02-26 03:01:40 +01:00
AttributeOpts *aopts;
int numoptions;
static const relopt_parse_elt tab[] = {
{"n_distinct", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct)},
{"n_distinct_inherited", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct_inherited)}
};
options = parseRelOptions(reloptions, validate, RELOPT_KIND_ATTRIBUTE,
&numoptions);
/* if none set, we're done */
if (numoptions == 0)
return NULL;
aopts = allocateReloptStruct(sizeof(AttributeOpts), options, numoptions);
fillRelOptions((void *) aopts, sizeof(AttributeOpts), options, numoptions,
validate, tab, lengthof(tab));
pfree(options);
return (bytea *) aopts;
}
/*
* Option parser for tablespace reloptions
*/
bytea *
tablespace_reloptions(Datum reloptions, bool validate)
{
relopt_value *options;
2010-02-26 03:01:40 +01:00
TableSpaceOpts *tsopts;
int numoptions;
static const relopt_parse_elt tab[] = {
{"random_page_cost", RELOPT_TYPE_REAL, offsetof(TableSpaceOpts, random_page_cost)},
{"seq_page_cost", RELOPT_TYPE_REAL, offsetof(TableSpaceOpts, seq_page_cost)},
{"effective_io_concurrency", RELOPT_TYPE_INT, offsetof(TableSpaceOpts, effective_io_concurrency)}
};
options = parseRelOptions(reloptions, validate, RELOPT_KIND_TABLESPACE,
&numoptions);
/* if none set, we're done */
if (numoptions == 0)
return NULL;
tsopts = allocateReloptStruct(sizeof(TableSpaceOpts), options, numoptions);
fillRelOptions((void *) tsopts, sizeof(TableSpaceOpts), options, numoptions,
validate, tab, lengthof(tab));
pfree(options);
return (bytea *) tsopts;
}
/*
* Determine the required LOCKMODE from an option list.
*
* Called from AlterTableGetLockLevel(), see that function
* for a longer explanation of how this works.
*/
LOCKMODE
AlterTableGetRelOptionsLockLevel(List *defList)
{
2016-06-10 00:02:36 +02:00
LOCKMODE lockmode = NoLock;
ListCell *cell;
if (defList == NIL)
return AccessExclusiveLock;
if (need_initialization)
initialize_reloptions();
foreach(cell, defList)
{
2016-06-10 00:02:36 +02:00
DefElem *def = (DefElem *) lfirst(cell);
int i;
for (i = 0; relOpts[i]; i++)
{
Avoid unnecessary use of pg_strcasecmp for already-downcased identifiers. We have a lot of code in which option names, which from the user's viewpoint are logically keywords, are passed through the grammar as plain identifiers, and then matched to string literals during command execution. This approach avoids making words into lexer keywords unnecessarily. Some places matched these strings using plain strcmp, some using pg_strcasecmp. But the latter should be unnecessary since identifiers would have been downcased on their way through the parser. Aside from any efficiency concerns (probably not a big factor), the lack of consistency in this area creates a hazard of subtle bugs due to different places coming to different conclusions about whether two option names are the same or different. Hence, standardize on using strcmp() to match any option names that are expected to have been fed through the parser. This does create a user-visible behavioral change, which is that while formerly all of these would work: alter table foo set (fillfactor = 50); alter table foo set (FillFactor = 50); alter table foo set ("fillfactor" = 50); alter table foo set ("FillFactor" = 50); now the last case will fail because that double-quoted identifier is different from the others. However, none of our documentation says that you can use a quoted identifier in such contexts at all, and we should discourage doing so since it would break if we ever decide to parse such constructs as true lexer keywords rather than poor man's substitutes. So this shouldn't create a significant compatibility issue for users. Daniel Gustafsson, reviewed by Michael Paquier, small changes by me Discussion: https://postgr.es/m/29405B24-564E-476B-98C0-677A29805B84@yesql.se
2018-01-27 00:25:02 +01:00
if (strncmp(relOpts[i]->name,
def->defname,
relOpts[i]->namelen + 1) == 0)
{
if (lockmode < relOpts[i]->lockmode)
lockmode = relOpts[i]->lockmode;
}
}
}
return lockmode;
}