/src/postgres/src/backend/access/common/reloptions.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * reloptions.c |
4 | | * Core support for relation options (pg_class.reloptions) |
5 | | * |
6 | | * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group |
7 | | * Portions Copyright (c) 1994, Regents of the University of California |
8 | | * |
9 | | * |
10 | | * IDENTIFICATION |
11 | | * src/backend/access/common/reloptions.c |
12 | | * |
13 | | *------------------------------------------------------------------------- |
14 | | */ |
15 | | |
16 | | #include "postgres.h" |
17 | | |
18 | | #include <float.h> |
19 | | |
20 | | #include "access/gist_private.h" |
21 | | #include "access/hash.h" |
22 | | #include "access/heaptoast.h" |
23 | | #include "access/htup_details.h" |
24 | | #include "access/nbtree.h" |
25 | | #include "access/reloptions.h" |
26 | | #include "access/spgist_private.h" |
27 | | #include "catalog/pg_type.h" |
28 | | #include "commands/defrem.h" |
29 | | #include "commands/tablespace.h" |
30 | | #include "nodes/makefuncs.h" |
31 | | #include "utils/array.h" |
32 | | #include "utils/attoptcache.h" |
33 | | #include "utils/builtins.h" |
34 | | #include "utils/guc.h" |
35 | | #include "utils/memutils.h" |
36 | | #include "utils/rel.h" |
37 | | |
38 | | /* |
39 | | * Contents of pg_class.reloptions |
40 | | * |
41 | | * To add an option: |
42 | | * |
43 | | * (i) decide on a type (bool, integer, real, enum, string), name, default |
44 | | * value, upper and lower bounds (if applicable); for strings, consider a |
45 | | * validation routine. |
46 | | * (ii) add a record below (or use add_<type>_reloption). |
47 | | * (iii) add it to the appropriate options struct (perhaps StdRdOptions) |
48 | | * (iv) add it to the appropriate handling routine (perhaps |
49 | | * default_reloptions) |
50 | | * (v) make sure the lock level is set correctly for that operation |
51 | | * (vi) don't forget to document the option |
52 | | * |
53 | | * The default choice for any new option should be AccessExclusiveLock. |
54 | | * In some cases the lock level can be reduced from there, but the lock |
55 | | * level chosen should always conflict with itself to ensure that multiple |
56 | | * changes aren't lost when we attempt concurrent changes. |
57 | | * The choice of lock level depends completely upon how that parameter |
58 | | * is used within the server, not upon how and when you'd like to change it. |
59 | | * Safety first. Existing choices are documented here, and elsewhere in |
60 | | * backend code where the parameters are used. |
61 | | * |
62 | | * In general, anything that affects the results obtained from a SELECT must be |
63 | | * protected by AccessExclusiveLock. |
64 | | * |
65 | | * Autovacuum related parameters can be set at ShareUpdateExclusiveLock |
66 | | * since they are only used by the AV procs and don't change anything |
67 | | * currently executing. |
68 | | * |
69 | | * Fillfactor can be set at ShareUpdateExclusiveLock because it applies only to |
70 | | * subsequent changes made to data blocks, as documented in hio.c |
71 | | * |
72 | | * n_distinct options can be set at ShareUpdateExclusiveLock because they |
73 | | * are only used during ANALYZE, which uses a ShareUpdateExclusiveLock, |
74 | | * so the ANALYZE will not be affected by in-flight changes. Changing those |
75 | | * values has no effect until the next ANALYZE, so no need for stronger lock. |
76 | | * |
77 | | * Planner-related parameters can be set at ShareUpdateExclusiveLock because |
78 | | * they only affect planning and not the correctness of the execution. Plans |
79 | | * cannot be changed in mid-flight, so changes here could not easily result in |
80 | | * new improved plans in any case. So we allow existing queries to continue |
81 | | * and existing plans to survive, a small price to pay for allowing better |
82 | | * plans to be introduced concurrently without interfering with users. |
83 | | * |
84 | | * Setting parallel_workers at ShareUpdateExclusiveLock is safe, since it acts |
85 | | * the same as max_parallel_workers_per_gather which is a USERSET parameter |
86 | | * that doesn't affect existing plans or queries. |
87 | | * |
88 | | * vacuum_truncate can be set at ShareUpdateExclusiveLock because it |
89 | | * is only used during VACUUM, which uses a ShareUpdateExclusiveLock, |
90 | | * so the VACUUM will not be affected by in-flight changes. Changing its |
91 | | * value has no effect until the next VACUUM, so no need for stronger lock. |
92 | | */ |
93 | | |
94 | | static relopt_bool boolRelOpts[] = |
95 | | { |
96 | | { |
97 | | { |
98 | | "autosummarize", |
99 | | "Enables automatic summarization on this BRIN index", |
100 | | RELOPT_KIND_BRIN, |
101 | | AccessExclusiveLock |
102 | | }, |
103 | | false |
104 | | }, |
105 | | { |
106 | | { |
107 | | "autovacuum_enabled", |
108 | | "Enables autovacuum in this relation", |
109 | | RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, |
110 | | ShareUpdateExclusiveLock |
111 | | }, |
112 | | true |
113 | | }, |
114 | | { |
115 | | { |
116 | | "user_catalog_table", |
117 | | "Declare a table as an additional catalog table, e.g. for the purpose of logical replication", |
118 | | RELOPT_KIND_HEAP, |
119 | | AccessExclusiveLock |
120 | | }, |
121 | | false |
122 | | }, |
123 | | { |
124 | | { |
125 | | "fastupdate", |
126 | | "Enables \"fast update\" feature for this GIN index", |
127 | | RELOPT_KIND_GIN, |
128 | | AccessExclusiveLock |
129 | | }, |
130 | | true |
131 | | }, |
132 | | { |
133 | | { |
134 | | "security_barrier", |
135 | | "View acts as a row security barrier", |
136 | | RELOPT_KIND_VIEW, |
137 | | AccessExclusiveLock |
138 | | }, |
139 | | false |
140 | | }, |
141 | | { |
142 | | { |
143 | | "security_invoker", |
144 | | "Privileges on underlying relations are checked as the invoking user, not the view owner", |
145 | | RELOPT_KIND_VIEW, |
146 | | AccessExclusiveLock |
147 | | }, |
148 | | false |
149 | | }, |
150 | | { |
151 | | { |
152 | | "vacuum_truncate", |
153 | | "Enables vacuum to truncate empty pages at the end of this table", |
154 | | RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, |
155 | | ShareUpdateExclusiveLock |
156 | | }, |
157 | | true |
158 | | }, |
159 | | { |
160 | | { |
161 | | "deduplicate_items", |
162 | | "Enables \"deduplicate items\" feature for this btree index", |
163 | | RELOPT_KIND_BTREE, |
164 | | ShareUpdateExclusiveLock /* since it applies only to later |
165 | | * inserts */ |
166 | | }, |
167 | | true |
168 | | }, |
169 | | /* list terminator */ |
170 | | {{NULL}} |
171 | | }; |
172 | | |
173 | | static relopt_int intRelOpts[] = |
174 | | { |
175 | | { |
176 | | { |
177 | | "fillfactor", |
178 | | "Packs table pages only to this percentage", |
179 | | RELOPT_KIND_HEAP, |
180 | | ShareUpdateExclusiveLock /* since it applies only to later |
181 | | * inserts */ |
182 | | }, |
183 | | HEAP_DEFAULT_FILLFACTOR, HEAP_MIN_FILLFACTOR, 100 |
184 | | }, |
185 | | { |
186 | | { |
187 | | "fillfactor", |
188 | | "Packs btree index pages only to this percentage", |
189 | | RELOPT_KIND_BTREE, |
190 | | ShareUpdateExclusiveLock /* since it applies only to later |
191 | | * inserts */ |
192 | | }, |
193 | | BTREE_DEFAULT_FILLFACTOR, BTREE_MIN_FILLFACTOR, 100 |
194 | | }, |
195 | | { |
196 | | { |
197 | | "fillfactor", |
198 | | "Packs hash index pages only to this percentage", |
199 | | RELOPT_KIND_HASH, |
200 | | ShareUpdateExclusiveLock /* since it applies only to later |
201 | | * inserts */ |
202 | | }, |
203 | | HASH_DEFAULT_FILLFACTOR, HASH_MIN_FILLFACTOR, 100 |
204 | | }, |
205 | | { |
206 | | { |
207 | | "fillfactor", |
208 | | "Packs gist index pages only to this percentage", |
209 | | RELOPT_KIND_GIST, |
210 | | ShareUpdateExclusiveLock /* since it applies only to later |
211 | | * inserts */ |
212 | | }, |
213 | | GIST_DEFAULT_FILLFACTOR, GIST_MIN_FILLFACTOR, 100 |
214 | | }, |
215 | | { |
216 | | { |
217 | | "fillfactor", |
218 | | "Packs spgist index pages only to this percentage", |
219 | | RELOPT_KIND_SPGIST, |
220 | | ShareUpdateExclusiveLock /* since it applies only to later |
221 | | * inserts */ |
222 | | }, |
223 | | SPGIST_DEFAULT_FILLFACTOR, SPGIST_MIN_FILLFACTOR, 100 |
224 | | }, |
225 | | { |
226 | | { |
227 | | "autovacuum_vacuum_threshold", |
228 | | "Minimum number of tuple updates or deletes prior to vacuum", |
229 | | RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, |
230 | | ShareUpdateExclusiveLock |
231 | | }, |
232 | | -1, 0, INT_MAX |
233 | | }, |
234 | | { |
235 | | { |
236 | | "autovacuum_vacuum_max_threshold", |
237 | | "Maximum number of tuple updates or deletes prior to vacuum", |
238 | | RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, |
239 | | ShareUpdateExclusiveLock |
240 | | }, |
241 | | -2, -1, INT_MAX |
242 | | }, |
243 | | { |
244 | | { |
245 | | "autovacuum_vacuum_insert_threshold", |
246 | | "Minimum number of tuple inserts prior to vacuum, or -1 to disable insert vacuums", |
247 | | RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, |
248 | | ShareUpdateExclusiveLock |
249 | | }, |
250 | | -2, -1, INT_MAX |
251 | | }, |
252 | | { |
253 | | { |
254 | | "autovacuum_analyze_threshold", |
255 | | "Minimum number of tuple inserts, updates or deletes prior to analyze", |
256 | | RELOPT_KIND_HEAP, |
257 | | ShareUpdateExclusiveLock |
258 | | }, |
259 | | -1, 0, INT_MAX |
260 | | }, |
261 | | { |
262 | | { |
263 | | "autovacuum_vacuum_cost_limit", |
264 | | "Vacuum cost amount available before napping, for autovacuum", |
265 | | RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, |
266 | | ShareUpdateExclusiveLock |
267 | | }, |
268 | | -1, 1, 10000 |
269 | | }, |
270 | | { |
271 | | { |
272 | | "autovacuum_freeze_min_age", |
273 | | "Minimum age at which VACUUM should freeze a table row, for autovacuum", |
274 | | RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, |
275 | | ShareUpdateExclusiveLock |
276 | | }, |
277 | | -1, 0, 1000000000 |
278 | | }, |
279 | | { |
280 | | { |
281 | | "autovacuum_multixact_freeze_min_age", |
282 | | "Minimum multixact age at which VACUUM should freeze a row multixact's, for autovacuum", |
283 | | RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, |
284 | | ShareUpdateExclusiveLock |
285 | | }, |
286 | | -1, 0, 1000000000 |
287 | | }, |
288 | | { |
289 | | { |
290 | | "autovacuum_freeze_max_age", |
291 | | "Age at which to autovacuum a table to prevent transaction ID wraparound", |
292 | | RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, |
293 | | ShareUpdateExclusiveLock |
294 | | }, |
295 | | -1, 100000, 2000000000 |
296 | | }, |
297 | | { |
298 | | { |
299 | | "autovacuum_multixact_freeze_max_age", |
300 | | "Multixact age at which to autovacuum a table to prevent multixact wraparound", |
301 | | RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, |
302 | | ShareUpdateExclusiveLock |
303 | | }, |
304 | | -1, 10000, 2000000000 |
305 | | }, |
306 | | { |
307 | | { |
308 | | "autovacuum_freeze_table_age", |
309 | | "Age at which VACUUM should perform a full table sweep to freeze row versions", |
310 | | RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, |
311 | | ShareUpdateExclusiveLock |
312 | | }, -1, 0, 2000000000 |
313 | | }, |
314 | | { |
315 | | { |
316 | | "autovacuum_multixact_freeze_table_age", |
317 | | "Age of multixact at which VACUUM should perform a full table sweep to freeze row versions", |
318 | | RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, |
319 | | ShareUpdateExclusiveLock |
320 | | }, -1, 0, 2000000000 |
321 | | }, |
322 | | { |
323 | | { |
324 | | "log_autovacuum_min_duration", |
325 | | "Sets the minimum execution time above which autovacuum actions will be logged", |
326 | | RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, |
327 | | ShareUpdateExclusiveLock |
328 | | }, |
329 | | -1, -1, INT_MAX |
330 | | }, |
331 | | { |
332 | | { |
333 | | "toast_tuple_target", |
334 | | "Sets the target tuple length at which external columns will be toasted", |
335 | | RELOPT_KIND_HEAP, |
336 | | ShareUpdateExclusiveLock |
337 | | }, |
338 | | TOAST_TUPLE_TARGET, 128, TOAST_TUPLE_TARGET_MAIN |
339 | | }, |
340 | | { |
341 | | { |
342 | | "pages_per_range", |
343 | | "Number of pages that each page range covers in a BRIN index", |
344 | | RELOPT_KIND_BRIN, |
345 | | AccessExclusiveLock |
346 | | }, 128, 1, 131072 |
347 | | }, |
348 | | { |
349 | | { |
350 | | "gin_pending_list_limit", |
351 | | "Maximum size of the pending list for this GIN index, in kilobytes.", |
352 | | RELOPT_KIND_GIN, |
353 | | AccessExclusiveLock |
354 | | }, |
355 | | -1, 64, MAX_KILOBYTES |
356 | | }, |
357 | | { |
358 | | { |
359 | | "effective_io_concurrency", |
360 | | "Number of simultaneous requests that can be handled efficiently by the disk subsystem.", |
361 | | RELOPT_KIND_TABLESPACE, |
362 | | ShareUpdateExclusiveLock |
363 | | }, |
364 | | -1, 0, MAX_IO_CONCURRENCY |
365 | | }, |
366 | | { |
367 | | { |
368 | | "maintenance_io_concurrency", |
369 | | "Number of simultaneous requests that can be handled efficiently by the disk subsystem for maintenance work.", |
370 | | RELOPT_KIND_TABLESPACE, |
371 | | ShareUpdateExclusiveLock |
372 | | }, |
373 | | -1, 0, MAX_IO_CONCURRENCY |
374 | | }, |
375 | | { |
376 | | { |
377 | | "parallel_workers", |
378 | | "Number of parallel processes that can be used per executor node for this relation.", |
379 | | RELOPT_KIND_HEAP, |
380 | | ShareUpdateExclusiveLock |
381 | | }, |
382 | | -1, 0, 1024 |
383 | | }, |
384 | | |
385 | | /* list terminator */ |
386 | | {{NULL}} |
387 | | }; |
388 | | |
389 | | static relopt_real realRelOpts[] = |
390 | | { |
391 | | { |
392 | | { |
393 | | "autovacuum_vacuum_cost_delay", |
394 | | "Vacuum cost delay in milliseconds, for autovacuum", |
395 | | RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, |
396 | | ShareUpdateExclusiveLock |
397 | | }, |
398 | | -1, 0.0, 100.0 |
399 | | }, |
400 | | { |
401 | | { |
402 | | "autovacuum_vacuum_scale_factor", |
403 | | "Number of tuple updates or deletes prior to vacuum as a fraction of reltuples", |
404 | | RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, |
405 | | ShareUpdateExclusiveLock |
406 | | }, |
407 | | -1, 0.0, 100.0 |
408 | | }, |
409 | | { |
410 | | { |
411 | | "autovacuum_vacuum_insert_scale_factor", |
412 | | "Number of tuple inserts prior to vacuum as a fraction of reltuples", |
413 | | RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, |
414 | | ShareUpdateExclusiveLock |
415 | | }, |
416 | | -1, 0.0, 100.0 |
417 | | }, |
418 | | { |
419 | | { |
420 | | "autovacuum_analyze_scale_factor", |
421 | | "Number of tuple inserts, updates or deletes prior to analyze as a fraction of reltuples", |
422 | | RELOPT_KIND_HEAP, |
423 | | ShareUpdateExclusiveLock |
424 | | }, |
425 | | -1, 0.0, 100.0 |
426 | | }, |
427 | | { |
428 | | { |
429 | | "vacuum_max_eager_freeze_failure_rate", |
430 | | "Fraction of pages in a relation vacuum can scan and fail to freeze before disabling eager scanning.", |
431 | | RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, |
432 | | ShareUpdateExclusiveLock |
433 | | }, |
434 | | -1, 0.0, 1.0 |
435 | | }, |
436 | | |
437 | | { |
438 | | { |
439 | | "seq_page_cost", |
440 | | "Sets the planner's estimate of the cost of a sequentially fetched disk page.", |
441 | | RELOPT_KIND_TABLESPACE, |
442 | | ShareUpdateExclusiveLock |
443 | | }, |
444 | | -1, 0.0, DBL_MAX |
445 | | }, |
446 | | { |
447 | | { |
448 | | "random_page_cost", |
449 | | "Sets the planner's estimate of the cost of a nonsequentially fetched disk page.", |
450 | | RELOPT_KIND_TABLESPACE, |
451 | | ShareUpdateExclusiveLock |
452 | | }, |
453 | | -1, 0.0, DBL_MAX |
454 | | }, |
455 | | { |
456 | | { |
457 | | "n_distinct", |
458 | | "Sets the planner's estimate of the number of distinct values appearing in a column (excluding child relations).", |
459 | | RELOPT_KIND_ATTRIBUTE, |
460 | | ShareUpdateExclusiveLock |
461 | | }, |
462 | | 0, -1.0, DBL_MAX |
463 | | }, |
464 | | { |
465 | | { |
466 | | "n_distinct_inherited", |
467 | | "Sets the planner's estimate of the number of distinct values appearing in a column (including child relations).", |
468 | | RELOPT_KIND_ATTRIBUTE, |
469 | | ShareUpdateExclusiveLock |
470 | | }, |
471 | | 0, -1.0, DBL_MAX |
472 | | }, |
473 | | { |
474 | | { |
475 | | "vacuum_cleanup_index_scale_factor", |
476 | | "Deprecated B-Tree parameter.", |
477 | | RELOPT_KIND_BTREE, |
478 | | ShareUpdateExclusiveLock |
479 | | }, |
480 | | -1, 0.0, 1e10 |
481 | | }, |
482 | | /* list terminator */ |
483 | | {{NULL}} |
484 | | }; |
485 | | |
486 | | /* values from StdRdOptIndexCleanup */ |
487 | | static relopt_enum_elt_def StdRdOptIndexCleanupValues[] = |
488 | | { |
489 | | {"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO}, |
490 | | {"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON}, |
491 | | {"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF}, |
492 | | {"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON}, |
493 | | {"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF}, |
494 | | {"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON}, |
495 | | {"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF}, |
496 | | {"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON}, |
497 | | {"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF}, |
498 | | {(const char *) NULL} /* list terminator */ |
499 | | }; |
500 | | |
501 | | /* values from GistOptBufferingMode */ |
502 | | static relopt_enum_elt_def gistBufferingOptValues[] = |
503 | | { |
504 | | {"auto", GIST_OPTION_BUFFERING_AUTO}, |
505 | | {"on", GIST_OPTION_BUFFERING_ON}, |
506 | | {"off", GIST_OPTION_BUFFERING_OFF}, |
507 | | {(const char *) NULL} /* list terminator */ |
508 | | }; |
509 | | |
510 | | /* values from ViewOptCheckOption */ |
511 | | static relopt_enum_elt_def viewCheckOptValues[] = |
512 | | { |
513 | | /* no value for NOT_SET */ |
514 | | {"local", VIEW_OPTION_CHECK_OPTION_LOCAL}, |
515 | | {"cascaded", VIEW_OPTION_CHECK_OPTION_CASCADED}, |
516 | | {(const char *) NULL} /* list terminator */ |
517 | | }; |
518 | | |
519 | | static relopt_enum enumRelOpts[] = |
520 | | { |
521 | | { |
522 | | { |
523 | | "vacuum_index_cleanup", |
524 | | "Controls index vacuuming and index cleanup", |
525 | | RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, |
526 | | ShareUpdateExclusiveLock |
527 | | }, |
528 | | StdRdOptIndexCleanupValues, |
529 | | STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO, |
530 | | gettext_noop("Valid values are \"on\", \"off\", and \"auto\".") |
531 | | }, |
532 | | { |
533 | | { |
534 | | "buffering", |
535 | | "Enables buffering build for this GiST index", |
536 | | RELOPT_KIND_GIST, |
537 | | AccessExclusiveLock |
538 | | }, |
539 | | gistBufferingOptValues, |
540 | | GIST_OPTION_BUFFERING_AUTO, |
541 | | gettext_noop("Valid values are \"on\", \"off\", and \"auto\".") |
542 | | }, |
543 | | { |
544 | | { |
545 | | "check_option", |
546 | | "View has WITH CHECK OPTION defined (local or cascaded).", |
547 | | RELOPT_KIND_VIEW, |
548 | | AccessExclusiveLock |
549 | | }, |
550 | | viewCheckOptValues, |
551 | | VIEW_OPTION_CHECK_OPTION_NOT_SET, |
552 | | gettext_noop("Valid values are \"local\" and \"cascaded\".") |
553 | | }, |
554 | | /* list terminator */ |
555 | | {{NULL}} |
556 | | }; |
557 | | |
558 | | static relopt_string stringRelOpts[] = |
559 | | { |
560 | | /* list terminator */ |
561 | | {{NULL}} |
562 | | }; |
563 | | |
564 | | static relopt_gen **relOpts = NULL; |
565 | | static bits32 last_assigned_kind = RELOPT_KIND_LAST_DEFAULT; |
566 | | |
567 | | static int num_custom_options = 0; |
568 | | static relopt_gen **custom_options = NULL; |
569 | | static bool need_initialization = true; |
570 | | |
571 | | static void initialize_reloptions(void); |
572 | | static void parse_one_reloption(relopt_value *option, char *text_str, |
573 | | int text_len, bool validate); |
574 | | |
575 | | /* |
576 | | * Get the length of a string reloption (either default or the user-defined |
577 | | * value). This is used for allocation purposes when building a set of |
578 | | * relation options. |
579 | | */ |
580 | | #define GET_STRING_RELOPTION_LEN(option) \ |
581 | 0 | ((option).isset ? strlen((option).values.string_val) : \ |
582 | 0 | ((relopt_string *) (option).gen)->default_len) |
583 | | |
584 | | /* |
585 | | * initialize_reloptions |
586 | | * initialization routine, must be called before parsing |
587 | | * |
588 | | * Initialize the relOpts array and fill each variable's type and name length. |
589 | | */ |
590 | | static void |
591 | | initialize_reloptions(void) |
592 | 0 | { |
593 | 0 | int i; |
594 | 0 | int j; |
595 | |
|
596 | 0 | j = 0; |
597 | 0 | for (i = 0; boolRelOpts[i].gen.name; i++) |
598 | 0 | { |
599 | 0 | Assert(DoLockModesConflict(boolRelOpts[i].gen.lockmode, |
600 | 0 | boolRelOpts[i].gen.lockmode)); |
601 | 0 | j++; |
602 | 0 | } |
603 | 0 | for (i = 0; intRelOpts[i].gen.name; i++) |
604 | 0 | { |
605 | 0 | Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, |
606 | 0 | intRelOpts[i].gen.lockmode)); |
607 | 0 | j++; |
608 | 0 | } |
609 | 0 | for (i = 0; realRelOpts[i].gen.name; i++) |
610 | 0 | { |
611 | 0 | Assert(DoLockModesConflict(realRelOpts[i].gen.lockmode, |
612 | 0 | realRelOpts[i].gen.lockmode)); |
613 | 0 | j++; |
614 | 0 | } |
615 | 0 | for (i = 0; enumRelOpts[i].gen.name; i++) |
616 | 0 | { |
617 | 0 | Assert(DoLockModesConflict(enumRelOpts[i].gen.lockmode, |
618 | 0 | enumRelOpts[i].gen.lockmode)); |
619 | 0 | j++; |
620 | 0 | } |
621 | 0 | for (i = 0; stringRelOpts[i].gen.name; i++) |
622 | 0 | { |
623 | 0 | Assert(DoLockModesConflict(stringRelOpts[i].gen.lockmode, |
624 | 0 | stringRelOpts[i].gen.lockmode)); |
625 | 0 | j++; |
626 | 0 | } |
627 | 0 | j += num_custom_options; |
628 | |
|
629 | 0 | if (relOpts) |
630 | 0 | pfree(relOpts); |
631 | 0 | relOpts = MemoryContextAlloc(TopMemoryContext, |
632 | 0 | (j + 1) * sizeof(relopt_gen *)); |
633 | |
|
634 | 0 | j = 0; |
635 | 0 | for (i = 0; boolRelOpts[i].gen.name; i++) |
636 | 0 | { |
637 | 0 | relOpts[j] = &boolRelOpts[i].gen; |
638 | 0 | relOpts[j]->type = RELOPT_TYPE_BOOL; |
639 | 0 | relOpts[j]->namelen = strlen(relOpts[j]->name); |
640 | 0 | j++; |
641 | 0 | } |
642 | |
|
643 | 0 | for (i = 0; intRelOpts[i].gen.name; i++) |
644 | 0 | { |
645 | 0 | relOpts[j] = &intRelOpts[i].gen; |
646 | 0 | relOpts[j]->type = RELOPT_TYPE_INT; |
647 | 0 | relOpts[j]->namelen = strlen(relOpts[j]->name); |
648 | 0 | j++; |
649 | 0 | } |
650 | |
|
651 | 0 | for (i = 0; realRelOpts[i].gen.name; i++) |
652 | 0 | { |
653 | 0 | relOpts[j] = &realRelOpts[i].gen; |
654 | 0 | relOpts[j]->type = RELOPT_TYPE_REAL; |
655 | 0 | relOpts[j]->namelen = strlen(relOpts[j]->name); |
656 | 0 | j++; |
657 | 0 | } |
658 | |
|
659 | 0 | for (i = 0; enumRelOpts[i].gen.name; i++) |
660 | 0 | { |
661 | 0 | relOpts[j] = &enumRelOpts[i].gen; |
662 | 0 | relOpts[j]->type = RELOPT_TYPE_ENUM; |
663 | 0 | relOpts[j]->namelen = strlen(relOpts[j]->name); |
664 | 0 | j++; |
665 | 0 | } |
666 | |
|
667 | 0 | for (i = 0; stringRelOpts[i].gen.name; i++) |
668 | 0 | { |
669 | 0 | relOpts[j] = &stringRelOpts[i].gen; |
670 | 0 | relOpts[j]->type = RELOPT_TYPE_STRING; |
671 | 0 | relOpts[j]->namelen = strlen(relOpts[j]->name); |
672 | 0 | j++; |
673 | 0 | } |
674 | |
|
675 | 0 | for (i = 0; i < num_custom_options; i++) |
676 | 0 | { |
677 | 0 | relOpts[j] = custom_options[i]; |
678 | 0 | j++; |
679 | 0 | } |
680 | | |
681 | | /* add a list terminator */ |
682 | 0 | relOpts[j] = NULL; |
683 | | |
684 | | /* flag the work is complete */ |
685 | 0 | need_initialization = false; |
686 | 0 | } |
687 | | |
688 | | /* |
689 | | * add_reloption_kind |
690 | | * Create a new relopt_kind value, to be used in custom reloptions by |
691 | | * user-defined AMs. |
692 | | */ |
693 | | relopt_kind |
694 | | add_reloption_kind(void) |
695 | 0 | { |
696 | | /* don't hand out the last bit so that the enum's behavior is portable */ |
697 | 0 | if (last_assigned_kind >= RELOPT_KIND_MAX) |
698 | 0 | ereport(ERROR, |
699 | 0 | (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), |
700 | 0 | errmsg("user-defined relation parameter types limit exceeded"))); |
701 | 0 | last_assigned_kind <<= 1; |
702 | 0 | return (relopt_kind) last_assigned_kind; |
703 | 0 | } |
704 | | |
705 | | /* |
706 | | * add_reloption |
707 | | * Add an already-created custom reloption to the list, and recompute the |
708 | | * main parser table. |
709 | | */ |
710 | | static void |
711 | | add_reloption(relopt_gen *newoption) |
712 | 0 | { |
713 | 0 | static int max_custom_options = 0; |
714 | |
|
715 | 0 | if (num_custom_options >= max_custom_options) |
716 | 0 | { |
717 | 0 | MemoryContext oldcxt; |
718 | |
|
719 | 0 | oldcxt = MemoryContextSwitchTo(TopMemoryContext); |
720 | |
|
721 | 0 | if (max_custom_options == 0) |
722 | 0 | { |
723 | 0 | max_custom_options = 8; |
724 | 0 | custom_options = palloc(max_custom_options * sizeof(relopt_gen *)); |
725 | 0 | } |
726 | 0 | else |
727 | 0 | { |
728 | 0 | max_custom_options *= 2; |
729 | 0 | custom_options = repalloc(custom_options, |
730 | 0 | max_custom_options * sizeof(relopt_gen *)); |
731 | 0 | } |
732 | 0 | MemoryContextSwitchTo(oldcxt); |
733 | 0 | } |
734 | 0 | custom_options[num_custom_options++] = newoption; |
735 | |
|
736 | 0 | need_initialization = true; |
737 | 0 | } |
738 | | |
739 | | /* |
740 | | * init_local_reloptions |
741 | | * Initialize local reloptions that will parsed into bytea structure of |
742 | | * 'relopt_struct_size'. |
743 | | */ |
744 | | void |
745 | | init_local_reloptions(local_relopts *relopts, Size relopt_struct_size) |
746 | 0 | { |
747 | 0 | relopts->options = NIL; |
748 | 0 | relopts->validators = NIL; |
749 | 0 | relopts->relopt_struct_size = relopt_struct_size; |
750 | 0 | } |
751 | | |
752 | | /* |
753 | | * register_reloptions_validator |
754 | | * Register custom validation callback that will be called at the end of |
755 | | * build_local_reloptions(). |
756 | | */ |
757 | | void |
758 | | register_reloptions_validator(local_relopts *relopts, relopts_validator validator) |
759 | 0 | { |
760 | 0 | relopts->validators = lappend(relopts->validators, validator); |
761 | 0 | } |
762 | | |
763 | | /* |
764 | | * add_local_reloption |
765 | | * Add an already-created custom reloption to the local list. |
766 | | */ |
767 | | static void |
768 | | add_local_reloption(local_relopts *relopts, relopt_gen *newoption, int offset) |
769 | 0 | { |
770 | 0 | local_relopt *opt = palloc(sizeof(*opt)); |
771 | |
|
772 | 0 | Assert(offset < relopts->relopt_struct_size); |
773 | |
|
774 | 0 | opt->option = newoption; |
775 | 0 | opt->offset = offset; |
776 | |
|
777 | 0 | relopts->options = lappend(relopts->options, opt); |
778 | 0 | } |
779 | | |
780 | | /* |
781 | | * allocate_reloption |
782 | | * Allocate a new reloption and initialize the type-agnostic fields |
783 | | * (for types other than string) |
784 | | */ |
785 | | static relopt_gen * |
786 | | allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, |
787 | | LOCKMODE lockmode) |
788 | 0 | { |
789 | 0 | MemoryContext oldcxt; |
790 | 0 | size_t size; |
791 | 0 | relopt_gen *newoption; |
792 | |
|
793 | 0 | if (kinds != RELOPT_KIND_LOCAL) |
794 | 0 | oldcxt = MemoryContextSwitchTo(TopMemoryContext); |
795 | 0 | else |
796 | 0 | oldcxt = NULL; |
797 | |
|
798 | 0 | switch (type) |
799 | 0 | { |
800 | 0 | case RELOPT_TYPE_BOOL: |
801 | 0 | size = sizeof(relopt_bool); |
802 | 0 | break; |
803 | 0 | case RELOPT_TYPE_INT: |
804 | 0 | size = sizeof(relopt_int); |
805 | 0 | break; |
806 | 0 | case RELOPT_TYPE_REAL: |
807 | 0 | size = sizeof(relopt_real); |
808 | 0 | break; |
809 | 0 | case RELOPT_TYPE_ENUM: |
810 | 0 | size = sizeof(relopt_enum); |
811 | 0 | break; |
812 | 0 | case RELOPT_TYPE_STRING: |
813 | 0 | size = sizeof(relopt_string); |
814 | 0 | break; |
815 | 0 | default: |
816 | 0 | elog(ERROR, "unsupported reloption type %d", type); |
817 | 0 | return NULL; /* keep compiler quiet */ |
818 | 0 | } |
819 | | |
820 | 0 | newoption = palloc(size); |
821 | |
|
822 | 0 | newoption->name = pstrdup(name); |
823 | 0 | if (desc) |
824 | 0 | newoption->desc = pstrdup(desc); |
825 | 0 | else |
826 | 0 | newoption->desc = NULL; |
827 | 0 | newoption->kinds = kinds; |
828 | 0 | newoption->namelen = strlen(name); |
829 | 0 | newoption->type = type; |
830 | 0 | newoption->lockmode = lockmode; |
831 | |
|
832 | 0 | if (oldcxt != NULL) |
833 | 0 | MemoryContextSwitchTo(oldcxt); |
834 | |
|
835 | 0 | return newoption; |
836 | 0 | } |
837 | | |
838 | | /* |
839 | | * init_bool_reloption |
840 | | * Allocate and initialize a new boolean reloption |
841 | | */ |
842 | | static relopt_bool * |
843 | | init_bool_reloption(bits32 kinds, const char *name, const char *desc, |
844 | | bool default_val, LOCKMODE lockmode) |
845 | 0 | { |
846 | 0 | relopt_bool *newoption; |
847 | |
|
848 | 0 | newoption = (relopt_bool *) allocate_reloption(kinds, RELOPT_TYPE_BOOL, |
849 | 0 | name, desc, lockmode); |
850 | 0 | newoption->default_val = default_val; |
851 | |
|
852 | 0 | return newoption; |
853 | 0 | } |
854 | | |
855 | | /* |
856 | | * add_bool_reloption |
857 | | * Add a new boolean reloption |
858 | | */ |
859 | | void |
860 | | add_bool_reloption(bits32 kinds, const char *name, const char *desc, |
861 | | bool default_val, LOCKMODE lockmode) |
862 | 0 | { |
863 | 0 | relopt_bool *newoption = init_bool_reloption(kinds, name, desc, |
864 | 0 | default_val, lockmode); |
865 | |
|
866 | 0 | add_reloption((relopt_gen *) newoption); |
867 | 0 | } |
868 | | |
869 | | /* |
870 | | * add_local_bool_reloption |
871 | | * Add a new boolean local reloption |
872 | | * |
873 | | * 'offset' is offset of bool-typed field. |
874 | | */ |
875 | | void |
876 | | add_local_bool_reloption(local_relopts *relopts, const char *name, |
877 | | const char *desc, bool default_val, int offset) |
878 | 0 | { |
879 | 0 | relopt_bool *newoption = init_bool_reloption(RELOPT_KIND_LOCAL, |
880 | 0 | name, desc, |
881 | 0 | default_val, 0); |
882 | |
|
883 | 0 | add_local_reloption(relopts, (relopt_gen *) newoption, offset); |
884 | 0 | } |
885 | | |
886 | | |
887 | | /* |
888 | | * init_real_reloption |
889 | | * Allocate and initialize a new integer reloption |
890 | | */ |
891 | | static relopt_int * |
892 | | init_int_reloption(bits32 kinds, const char *name, const char *desc, |
893 | | int default_val, int min_val, int max_val, |
894 | | LOCKMODE lockmode) |
895 | 0 | { |
896 | 0 | relopt_int *newoption; |
897 | |
|
898 | 0 | newoption = (relopt_int *) allocate_reloption(kinds, RELOPT_TYPE_INT, |
899 | 0 | name, desc, lockmode); |
900 | 0 | newoption->default_val = default_val; |
901 | 0 | newoption->min = min_val; |
902 | 0 | newoption->max = max_val; |
903 | |
|
904 | 0 | return newoption; |
905 | 0 | } |
906 | | |
907 | | /* |
908 | | * add_int_reloption |
909 | | * Add a new integer reloption |
910 | | */ |
911 | | void |
912 | | add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, |
913 | | int min_val, int max_val, LOCKMODE lockmode) |
914 | 0 | { |
915 | 0 | relopt_int *newoption = init_int_reloption(kinds, name, desc, |
916 | 0 | default_val, min_val, |
917 | 0 | max_val, lockmode); |
918 | |
|
919 | 0 | add_reloption((relopt_gen *) newoption); |
920 | 0 | } |
921 | | |
922 | | /* |
923 | | * add_local_int_reloption |
924 | | * Add a new local integer reloption |
925 | | * |
926 | | * 'offset' is offset of int-typed field. |
927 | | */ |
928 | | void |
929 | | add_local_int_reloption(local_relopts *relopts, const char *name, |
930 | | const char *desc, int default_val, int min_val, |
931 | | int max_val, int offset) |
932 | 0 | { |
933 | 0 | relopt_int *newoption = init_int_reloption(RELOPT_KIND_LOCAL, |
934 | 0 | name, desc, default_val, |
935 | 0 | min_val, max_val, 0); |
936 | |
|
937 | 0 | add_local_reloption(relopts, (relopt_gen *) newoption, offset); |
938 | 0 | } |
939 | | |
940 | | /* |
941 | | * init_real_reloption |
942 | | * Allocate and initialize a new real reloption |
943 | | */ |
944 | | static relopt_real * |
945 | | init_real_reloption(bits32 kinds, const char *name, const char *desc, |
946 | | double default_val, double min_val, double max_val, |
947 | | LOCKMODE lockmode) |
948 | 0 | { |
949 | 0 | relopt_real *newoption; |
950 | |
|
951 | 0 | newoption = (relopt_real *) allocate_reloption(kinds, RELOPT_TYPE_REAL, |
952 | 0 | name, desc, lockmode); |
953 | 0 | newoption->default_val = default_val; |
954 | 0 | newoption->min = min_val; |
955 | 0 | newoption->max = max_val; |
956 | |
|
957 | 0 | return newoption; |
958 | 0 | } |
959 | | |
960 | | /* |
961 | | * add_real_reloption |
962 | | * Add a new float reloption |
963 | | */ |
964 | | void |
965 | | add_real_reloption(bits32 kinds, const char *name, const char *desc, |
966 | | double default_val, double min_val, double max_val, |
967 | | LOCKMODE lockmode) |
968 | 0 | { |
969 | 0 | relopt_real *newoption = init_real_reloption(kinds, name, desc, |
970 | 0 | default_val, min_val, |
971 | 0 | max_val, lockmode); |
972 | |
|
973 | 0 | add_reloption((relopt_gen *) newoption); |
974 | 0 | } |
975 | | |
976 | | /* |
977 | | * add_local_real_reloption |
978 | | * Add a new local float reloption |
979 | | * |
980 | | * 'offset' is offset of double-typed field. |
981 | | */ |
982 | | void |
983 | | add_local_real_reloption(local_relopts *relopts, const char *name, |
984 | | const char *desc, double default_val, |
985 | | double min_val, double max_val, int offset) |
986 | 0 | { |
987 | 0 | relopt_real *newoption = init_real_reloption(RELOPT_KIND_LOCAL, |
988 | 0 | name, desc, |
989 | 0 | default_val, min_val, |
990 | 0 | max_val, 0); |
991 | |
|
992 | 0 | add_local_reloption(relopts, (relopt_gen *) newoption, offset); |
993 | 0 | } |
994 | | |
995 | | /* |
996 | | * init_enum_reloption |
997 | | * Allocate and initialize a new enum reloption |
998 | | */ |
999 | | static relopt_enum * |
1000 | | init_enum_reloption(bits32 kinds, const char *name, const char *desc, |
1001 | | relopt_enum_elt_def *members, int default_val, |
1002 | | const char *detailmsg, LOCKMODE lockmode) |
1003 | 0 | { |
1004 | 0 | relopt_enum *newoption; |
1005 | |
|
1006 | 0 | newoption = (relopt_enum *) allocate_reloption(kinds, RELOPT_TYPE_ENUM, |
1007 | 0 | name, desc, lockmode); |
1008 | 0 | newoption->members = members; |
1009 | 0 | newoption->default_val = default_val; |
1010 | 0 | newoption->detailmsg = detailmsg; |
1011 | |
|
1012 | 0 | return newoption; |
1013 | 0 | } |
1014 | | |
1015 | | |
1016 | | /* |
1017 | | * add_enum_reloption |
1018 | | * Add a new enum reloption |
1019 | | * |
1020 | | * The members array must have a terminating NULL entry. |
1021 | | * |
1022 | | * The detailmsg is shown when unsupported values are passed, and has this |
1023 | | * form: "Valid values are \"foo\", \"bar\", and \"bar\"." |
1024 | | * |
1025 | | * The members array and detailmsg are not copied -- caller must ensure that |
1026 | | * they are valid throughout the life of the process. |
1027 | | */ |
1028 | | void |
1029 | | add_enum_reloption(bits32 kinds, const char *name, const char *desc, |
1030 | | relopt_enum_elt_def *members, int default_val, |
1031 | | const char *detailmsg, LOCKMODE lockmode) |
1032 | 0 | { |
1033 | 0 | relopt_enum *newoption = init_enum_reloption(kinds, name, desc, |
1034 | 0 | members, default_val, |
1035 | 0 | detailmsg, lockmode); |
1036 | |
|
1037 | 0 | add_reloption((relopt_gen *) newoption); |
1038 | 0 | } |
1039 | | |
1040 | | /* |
1041 | | * add_local_enum_reloption |
1042 | | * Add a new local enum reloption |
1043 | | * |
1044 | | * 'offset' is offset of int-typed field. |
1045 | | */ |
1046 | | void |
1047 | | add_local_enum_reloption(local_relopts *relopts, const char *name, |
1048 | | const char *desc, relopt_enum_elt_def *members, |
1049 | | int default_val, const char *detailmsg, int offset) |
1050 | 0 | { |
1051 | 0 | relopt_enum *newoption = init_enum_reloption(RELOPT_KIND_LOCAL, |
1052 | 0 | name, desc, |
1053 | 0 | members, default_val, |
1054 | 0 | detailmsg, 0); |
1055 | |
|
1056 | 0 | add_local_reloption(relopts, (relopt_gen *) newoption, offset); |
1057 | 0 | } |
1058 | | |
1059 | | /* |
1060 | | * init_string_reloption |
1061 | | * Allocate and initialize a new string reloption |
1062 | | */ |
1063 | | static relopt_string * |
1064 | | init_string_reloption(bits32 kinds, const char *name, const char *desc, |
1065 | | const char *default_val, |
1066 | | validate_string_relopt validator, |
1067 | | fill_string_relopt filler, |
1068 | | LOCKMODE lockmode) |
1069 | 0 | { |
1070 | 0 | relopt_string *newoption; |
1071 | | |
1072 | | /* make sure the validator/default combination is sane */ |
1073 | 0 | if (validator) |
1074 | 0 | (validator) (default_val); |
1075 | |
|
1076 | 0 | newoption = (relopt_string *) allocate_reloption(kinds, RELOPT_TYPE_STRING, |
1077 | 0 | name, desc, lockmode); |
1078 | 0 | newoption->validate_cb = validator; |
1079 | 0 | newoption->fill_cb = filler; |
1080 | 0 | if (default_val) |
1081 | 0 | { |
1082 | 0 | if (kinds == RELOPT_KIND_LOCAL) |
1083 | 0 | newoption->default_val = strdup(default_val); |
1084 | 0 | else |
1085 | 0 | newoption->default_val = MemoryContextStrdup(TopMemoryContext, default_val); |
1086 | 0 | newoption->default_len = strlen(default_val); |
1087 | 0 | newoption->default_isnull = false; |
1088 | 0 | } |
1089 | 0 | else |
1090 | 0 | { |
1091 | 0 | newoption->default_val = ""; |
1092 | 0 | newoption->default_len = 0; |
1093 | 0 | newoption->default_isnull = true; |
1094 | 0 | } |
1095 | |
|
1096 | 0 | return newoption; |
1097 | 0 | } |
1098 | | |
1099 | | /* |
1100 | | * add_string_reloption |
1101 | | * Add a new string reloption |
1102 | | * |
1103 | | * "validator" is an optional function pointer that can be used to test the |
1104 | | * validity of the values. It must elog(ERROR) when the argument string is |
1105 | | * not acceptable for the variable. Note that the default value must pass |
1106 | | * the validation. |
1107 | | */ |
1108 | | void |
1109 | | add_string_reloption(bits32 kinds, const char *name, const char *desc, |
1110 | | const char *default_val, validate_string_relopt validator, |
1111 | | LOCKMODE lockmode) |
1112 | 0 | { |
1113 | 0 | relopt_string *newoption = init_string_reloption(kinds, name, desc, |
1114 | 0 | default_val, |
1115 | 0 | validator, NULL, |
1116 | 0 | lockmode); |
1117 | |
|
1118 | 0 | add_reloption((relopt_gen *) newoption); |
1119 | 0 | } |
1120 | | |
1121 | | /* |
1122 | | * add_local_string_reloption |
1123 | | * Add a new local string reloption |
1124 | | * |
1125 | | * 'offset' is offset of int-typed field that will store offset of string value |
1126 | | * in the resulting bytea structure. |
1127 | | */ |
1128 | | void |
1129 | | add_local_string_reloption(local_relopts *relopts, const char *name, |
1130 | | const char *desc, const char *default_val, |
1131 | | validate_string_relopt validator, |
1132 | | fill_string_relopt filler, int offset) |
1133 | 0 | { |
1134 | 0 | relopt_string *newoption = init_string_reloption(RELOPT_KIND_LOCAL, |
1135 | 0 | name, desc, |
1136 | 0 | default_val, |
1137 | 0 | validator, filler, |
1138 | 0 | 0); |
1139 | |
|
1140 | 0 | add_local_reloption(relopts, (relopt_gen *) newoption, offset); |
1141 | 0 | } |
1142 | | |
1143 | | /* |
1144 | | * Transform a relation options list (list of DefElem) into the text array |
1145 | | * format that is kept in pg_class.reloptions, including only those options |
1146 | | * that are in the passed namespace. The output values do not include the |
1147 | | * namespace. |
1148 | | * |
1149 | | * This is used for three cases: CREATE TABLE/INDEX, ALTER TABLE SET, and |
1150 | | * ALTER TABLE RESET. In the ALTER cases, oldOptions is the existing |
1151 | | * reloptions value (possibly NULL), and we replace or remove entries |
1152 | | * as needed. |
1153 | | * |
1154 | | * If acceptOidsOff is true, then we allow oids = false, but throw error when |
1155 | | * on. This is solely needed for backwards compatibility. |
1156 | | * |
1157 | | * Note that this is not responsible for determining whether the options |
1158 | | * are valid, but it does check that namespaces for all the options given are |
1159 | | * listed in validnsps. The NULL namespace is always valid and need not be |
1160 | | * explicitly listed. Passing a NULL pointer means that only the NULL |
1161 | | * namespace is valid. |
1162 | | * |
1163 | | * Both oldOptions and the result are text arrays (or NULL for "default"), |
1164 | | * but we declare them as Datums to avoid including array.h in reloptions.h. |
1165 | | */ |
1166 | | Datum |
1167 | | transformRelOptions(Datum oldOptions, List *defList, const char *nameSpace, |
1168 | | const char *const validnsps[], bool acceptOidsOff, bool isReset) |
1169 | 0 | { |
1170 | 0 | Datum result; |
1171 | 0 | ArrayBuildState *astate; |
1172 | 0 | ListCell *cell; |
1173 | | |
1174 | | /* no change if empty list */ |
1175 | 0 | if (defList == NIL) |
1176 | 0 | return oldOptions; |
1177 | | |
1178 | | /* We build new array using accumArrayResult */ |
1179 | 0 | astate = NULL; |
1180 | | |
1181 | | /* Copy any oldOptions that aren't to be replaced */ |
1182 | 0 | if (DatumGetPointer(oldOptions) != NULL) |
1183 | 0 | { |
1184 | 0 | ArrayType *array = DatumGetArrayTypeP(oldOptions); |
1185 | 0 | Datum *oldoptions; |
1186 | 0 | int noldoptions; |
1187 | 0 | int i; |
1188 | |
|
1189 | 0 | deconstruct_array_builtin(array, TEXTOID, &oldoptions, NULL, &noldoptions); |
1190 | |
|
1191 | 0 | for (i = 0; i < noldoptions; i++) |
1192 | 0 | { |
1193 | 0 | char *text_str = VARDATA(DatumGetPointer(oldoptions[i])); |
1194 | 0 | int text_len = VARSIZE(DatumGetPointer(oldoptions[i])) - VARHDRSZ; |
1195 | | |
1196 | | /* Search for a match in defList */ |
1197 | 0 | foreach(cell, defList) |
1198 | 0 | { |
1199 | 0 | DefElem *def = (DefElem *) lfirst(cell); |
1200 | 0 | int kw_len; |
1201 | | |
1202 | | /* ignore if not in the same namespace */ |
1203 | 0 | if (nameSpace == NULL) |
1204 | 0 | { |
1205 | 0 | if (def->defnamespace != NULL) |
1206 | 0 | continue; |
1207 | 0 | } |
1208 | 0 | else if (def->defnamespace == NULL) |
1209 | 0 | continue; |
1210 | 0 | else if (strcmp(def->defnamespace, nameSpace) != 0) |
1211 | 0 | continue; |
1212 | | |
1213 | 0 | kw_len = strlen(def->defname); |
1214 | 0 | if (text_len > kw_len && text_str[kw_len] == '=' && |
1215 | 0 | strncmp(text_str, def->defname, kw_len) == 0) |
1216 | 0 | break; |
1217 | 0 | } |
1218 | 0 | if (!cell) |
1219 | 0 | { |
1220 | | /* No match, so keep old option */ |
1221 | 0 | astate = accumArrayResult(astate, oldoptions[i], |
1222 | 0 | false, TEXTOID, |
1223 | 0 | CurrentMemoryContext); |
1224 | 0 | } |
1225 | 0 | } |
1226 | 0 | } |
1227 | | |
1228 | | /* |
1229 | | * If CREATE/SET, add new options to array; if RESET, just check that the |
1230 | | * user didn't say RESET (option=val). (Must do this because the grammar |
1231 | | * doesn't enforce it.) |
1232 | | */ |
1233 | 0 | foreach(cell, defList) |
1234 | 0 | { |
1235 | 0 | DefElem *def = (DefElem *) lfirst(cell); |
1236 | |
|
1237 | 0 | if (isReset) |
1238 | 0 | { |
1239 | 0 | if (def->arg != NULL) |
1240 | 0 | ereport(ERROR, |
1241 | 0 | (errcode(ERRCODE_SYNTAX_ERROR), |
1242 | 0 | errmsg("RESET must not include values for parameters"))); |
1243 | 0 | } |
1244 | 0 | else |
1245 | 0 | { |
1246 | 0 | const char *name; |
1247 | 0 | const char *value; |
1248 | 0 | text *t; |
1249 | 0 | Size len; |
1250 | | |
1251 | | /* |
1252 | | * Error out if the namespace is not valid. A NULL namespace is |
1253 | | * always valid. |
1254 | | */ |
1255 | 0 | if (def->defnamespace != NULL) |
1256 | 0 | { |
1257 | 0 | bool valid = false; |
1258 | 0 | int i; |
1259 | |
|
1260 | 0 | if (validnsps) |
1261 | 0 | { |
1262 | 0 | for (i = 0; validnsps[i]; i++) |
1263 | 0 | { |
1264 | 0 | if (strcmp(def->defnamespace, validnsps[i]) == 0) |
1265 | 0 | { |
1266 | 0 | valid = true; |
1267 | 0 | break; |
1268 | 0 | } |
1269 | 0 | } |
1270 | 0 | } |
1271 | |
|
1272 | 0 | if (!valid) |
1273 | 0 | ereport(ERROR, |
1274 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
1275 | 0 | errmsg("unrecognized parameter namespace \"%s\"", |
1276 | 0 | def->defnamespace))); |
1277 | 0 | } |
1278 | | |
1279 | | /* ignore if not in the same namespace */ |
1280 | 0 | if (nameSpace == NULL) |
1281 | 0 | { |
1282 | 0 | if (def->defnamespace != NULL) |
1283 | 0 | continue; |
1284 | 0 | } |
1285 | 0 | else if (def->defnamespace == NULL) |
1286 | 0 | continue; |
1287 | 0 | else if (strcmp(def->defnamespace, nameSpace) != 0) |
1288 | 0 | continue; |
1289 | | |
1290 | | /* |
1291 | | * Flatten the DefElem into a text string like "name=arg". If we |
1292 | | * have just "name", assume "name=true" is meant. Note: the |
1293 | | * namespace is not output. |
1294 | | */ |
1295 | 0 | name = def->defname; |
1296 | 0 | if (def->arg != NULL) |
1297 | 0 | value = defGetString(def); |
1298 | 0 | else |
1299 | 0 | value = "true"; |
1300 | | |
1301 | | /* Insist that name not contain "=", else "a=b=c" is ambiguous */ |
1302 | 0 | if (strchr(name, '=') != NULL) |
1303 | 0 | ereport(ERROR, |
1304 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
1305 | 0 | errmsg("invalid option name \"%s\": must not contain \"=\"", |
1306 | 0 | name))); |
1307 | | |
1308 | | /* |
1309 | | * This is not a great place for this test, but there's no other |
1310 | | * convenient place to filter the option out. As WITH (oids = |
1311 | | * false) will be removed someday, this seems like an acceptable |
1312 | | * amount of ugly. |
1313 | | */ |
1314 | 0 | if (acceptOidsOff && def->defnamespace == NULL && |
1315 | 0 | strcmp(name, "oids") == 0) |
1316 | 0 | { |
1317 | 0 | if (defGetBoolean(def)) |
1318 | 0 | ereport(ERROR, |
1319 | 0 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
1320 | 0 | errmsg("tables declared WITH OIDS are not supported"))); |
1321 | | /* skip over option, reloptions machinery doesn't know it */ |
1322 | 0 | continue; |
1323 | 0 | } |
1324 | | |
1325 | 0 | len = VARHDRSZ + strlen(name) + 1 + strlen(value); |
1326 | | /* +1 leaves room for sprintf's trailing null */ |
1327 | 0 | t = (text *) palloc(len + 1); |
1328 | 0 | SET_VARSIZE(t, len); |
1329 | 0 | sprintf(VARDATA(t), "%s=%s", name, value); |
1330 | |
|
1331 | 0 | astate = accumArrayResult(astate, PointerGetDatum(t), |
1332 | 0 | false, TEXTOID, |
1333 | 0 | CurrentMemoryContext); |
1334 | 0 | } |
1335 | 0 | } |
1336 | | |
1337 | 0 | if (astate) |
1338 | 0 | result = makeArrayResult(astate, CurrentMemoryContext); |
1339 | 0 | else |
1340 | 0 | result = (Datum) 0; |
1341 | |
|
1342 | 0 | return result; |
1343 | 0 | } |
1344 | | |
1345 | | |
1346 | | /* |
1347 | | * Convert the text-array format of reloptions into a List of DefElem. |
1348 | | * This is the inverse of transformRelOptions(). |
1349 | | */ |
1350 | | List * |
1351 | | untransformRelOptions(Datum options) |
1352 | 0 | { |
1353 | 0 | List *result = NIL; |
1354 | 0 | ArrayType *array; |
1355 | 0 | Datum *optiondatums; |
1356 | 0 | int noptions; |
1357 | 0 | int i; |
1358 | | |
1359 | | /* Nothing to do if no options */ |
1360 | 0 | if (DatumGetPointer(options) == NULL) |
1361 | 0 | return result; |
1362 | | |
1363 | 0 | array = DatumGetArrayTypeP(options); |
1364 | |
|
1365 | 0 | deconstruct_array_builtin(array, TEXTOID, &optiondatums, NULL, &noptions); |
1366 | |
|
1367 | 0 | for (i = 0; i < noptions; i++) |
1368 | 0 | { |
1369 | 0 | char *s; |
1370 | 0 | char *p; |
1371 | 0 | Node *val = NULL; |
1372 | |
|
1373 | 0 | s = TextDatumGetCString(optiondatums[i]); |
1374 | 0 | p = strchr(s, '='); |
1375 | 0 | if (p) |
1376 | 0 | { |
1377 | 0 | *p++ = '\0'; |
1378 | 0 | val = (Node *) makeString(p); |
1379 | 0 | } |
1380 | 0 | result = lappend(result, makeDefElem(s, val, -1)); |
1381 | 0 | } |
1382 | |
|
1383 | 0 | return result; |
1384 | 0 | } |
1385 | | |
1386 | | /* |
1387 | | * Extract and parse reloptions from a pg_class tuple. |
1388 | | * |
1389 | | * This is a low-level routine, expected to be used by relcache code and |
1390 | | * callers that do not have a table's relcache entry (e.g. autovacuum). For |
1391 | | * other uses, consider grabbing the rd_options pointer from the relcache entry |
1392 | | * instead. |
1393 | | * |
1394 | | * tupdesc is pg_class' tuple descriptor. amoptions is a pointer to the index |
1395 | | * AM's options parser function in the case of a tuple corresponding to an |
1396 | | * index, or NULL otherwise. |
1397 | | */ |
1398 | | bytea * |
1399 | | extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, |
1400 | | amoptions_function amoptions) |
1401 | 0 | { |
1402 | 0 | bytea *options; |
1403 | 0 | bool isnull; |
1404 | 0 | Datum datum; |
1405 | 0 | Form_pg_class classForm; |
1406 | |
|
1407 | 0 | datum = fastgetattr(tuple, |
1408 | 0 | Anum_pg_class_reloptions, |
1409 | 0 | tupdesc, |
1410 | 0 | &isnull); |
1411 | 0 | if (isnull) |
1412 | 0 | return NULL; |
1413 | | |
1414 | 0 | classForm = (Form_pg_class) GETSTRUCT(tuple); |
1415 | | |
1416 | | /* Parse into appropriate format; don't error out here */ |
1417 | 0 | switch (classForm->relkind) |
1418 | 0 | { |
1419 | 0 | case RELKIND_RELATION: |
1420 | 0 | case RELKIND_TOASTVALUE: |
1421 | 0 | case RELKIND_MATVIEW: |
1422 | 0 | options = heap_reloptions(classForm->relkind, datum, false); |
1423 | 0 | break; |
1424 | 0 | case RELKIND_PARTITIONED_TABLE: |
1425 | 0 | options = partitioned_table_reloptions(datum, false); |
1426 | 0 | break; |
1427 | 0 | case RELKIND_VIEW: |
1428 | 0 | options = view_reloptions(datum, false); |
1429 | 0 | break; |
1430 | 0 | case RELKIND_INDEX: |
1431 | 0 | case RELKIND_PARTITIONED_INDEX: |
1432 | 0 | options = index_reloptions(amoptions, datum, false); |
1433 | 0 | break; |
1434 | 0 | case RELKIND_FOREIGN_TABLE: |
1435 | 0 | options = NULL; |
1436 | 0 | break; |
1437 | 0 | default: |
1438 | 0 | Assert(false); /* can't get here */ |
1439 | 0 | options = NULL; /* keep compiler quiet */ |
1440 | 0 | break; |
1441 | 0 | } |
1442 | | |
1443 | 0 | return options; |
1444 | 0 | } |
1445 | | |
1446 | | static void |
1447 | | parseRelOptionsInternal(Datum options, bool validate, |
1448 | | relopt_value *reloptions, int numoptions) |
1449 | 0 | { |
1450 | 0 | ArrayType *array = DatumGetArrayTypeP(options); |
1451 | 0 | Datum *optiondatums; |
1452 | 0 | int noptions; |
1453 | 0 | int i; |
1454 | |
|
1455 | 0 | deconstruct_array_builtin(array, TEXTOID, &optiondatums, NULL, &noptions); |
1456 | |
|
1457 | 0 | for (i = 0; i < noptions; i++) |
1458 | 0 | { |
1459 | 0 | char *text_str = VARDATA(DatumGetPointer(optiondatums[i])); |
1460 | 0 | int text_len = VARSIZE(DatumGetPointer(optiondatums[i])) - VARHDRSZ; |
1461 | 0 | int j; |
1462 | | |
1463 | | /* Search for a match in reloptions */ |
1464 | 0 | for (j = 0; j < numoptions; j++) |
1465 | 0 | { |
1466 | 0 | int kw_len = reloptions[j].gen->namelen; |
1467 | |
|
1468 | 0 | if (text_len > kw_len && text_str[kw_len] == '=' && |
1469 | 0 | strncmp(text_str, reloptions[j].gen->name, kw_len) == 0) |
1470 | 0 | { |
1471 | 0 | parse_one_reloption(&reloptions[j], text_str, text_len, |
1472 | 0 | validate); |
1473 | 0 | break; |
1474 | 0 | } |
1475 | 0 | } |
1476 | |
|
1477 | 0 | if (j >= numoptions && validate) |
1478 | 0 | { |
1479 | 0 | char *s; |
1480 | 0 | char *p; |
1481 | |
|
1482 | 0 | s = TextDatumGetCString(optiondatums[i]); |
1483 | 0 | p = strchr(s, '='); |
1484 | 0 | if (p) |
1485 | 0 | *p = '\0'; |
1486 | 0 | ereport(ERROR, |
1487 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
1488 | 0 | errmsg("unrecognized parameter \"%s\"", s))); |
1489 | 0 | } |
1490 | 0 | } |
1491 | | |
1492 | | /* It's worth avoiding memory leaks in this function */ |
1493 | 0 | pfree(optiondatums); |
1494 | |
|
1495 | 0 | if (((void *) array) != DatumGetPointer(options)) |
1496 | 0 | pfree(array); |
1497 | 0 | } |
1498 | | |
1499 | | /* |
1500 | | * Interpret reloptions that are given in text-array format. |
1501 | | * |
1502 | | * options is a reloption text array as constructed by transformRelOptions. |
1503 | | * kind specifies the family of options to be processed. |
1504 | | * |
1505 | | * The return value is a relopt_value * array on which the options actually |
1506 | | * set in the options array are marked with isset=true. The length of this |
1507 | | * array is returned in *numrelopts. Options not set are also present in the |
1508 | | * array; this is so that the caller can easily locate the default values. |
1509 | | * |
1510 | | * If there are no options of the given kind, numrelopts is set to 0 and NULL |
1511 | | * is returned (unless options are illegally supplied despite none being |
1512 | | * defined, in which case an error occurs). |
1513 | | * |
1514 | | * Note: values of type int, bool and real are allocated as part of the |
1515 | | * returned array. Values of type string are allocated separately and must |
1516 | | * be freed by the caller. |
1517 | | */ |
1518 | | static relopt_value * |
1519 | | parseRelOptions(Datum options, bool validate, relopt_kind kind, |
1520 | | int *numrelopts) |
1521 | 0 | { |
1522 | 0 | relopt_value *reloptions = NULL; |
1523 | 0 | int numoptions = 0; |
1524 | 0 | int i; |
1525 | 0 | int j; |
1526 | |
|
1527 | 0 | if (need_initialization) |
1528 | 0 | initialize_reloptions(); |
1529 | | |
1530 | | /* Build a list of expected options, based on kind */ |
1531 | |
|
1532 | 0 | for (i = 0; relOpts[i]; i++) |
1533 | 0 | if (relOpts[i]->kinds & kind) |
1534 | 0 | numoptions++; |
1535 | |
|
1536 | 0 | if (numoptions > 0) |
1537 | 0 | { |
1538 | 0 | reloptions = palloc(numoptions * sizeof(relopt_value)); |
1539 | |
|
1540 | 0 | for (i = 0, j = 0; relOpts[i]; i++) |
1541 | 0 | { |
1542 | 0 | if (relOpts[i]->kinds & kind) |
1543 | 0 | { |
1544 | 0 | reloptions[j].gen = relOpts[i]; |
1545 | 0 | reloptions[j].isset = false; |
1546 | 0 | j++; |
1547 | 0 | } |
1548 | 0 | } |
1549 | 0 | } |
1550 | | |
1551 | | /* Done if no options */ |
1552 | 0 | if (DatumGetPointer(options) != NULL) |
1553 | 0 | parseRelOptionsInternal(options, validate, reloptions, numoptions); |
1554 | |
|
1555 | 0 | *numrelopts = numoptions; |
1556 | 0 | return reloptions; |
1557 | 0 | } |
1558 | | |
1559 | | /* Parse local unregistered options. */ |
1560 | | static relopt_value * |
1561 | | parseLocalRelOptions(local_relopts *relopts, Datum options, bool validate) |
1562 | 0 | { |
1563 | 0 | int nopts = list_length(relopts->options); |
1564 | 0 | relopt_value *values = palloc(sizeof(*values) * nopts); |
1565 | 0 | ListCell *lc; |
1566 | 0 | int i = 0; |
1567 | |
|
1568 | 0 | foreach(lc, relopts->options) |
1569 | 0 | { |
1570 | 0 | local_relopt *opt = lfirst(lc); |
1571 | |
|
1572 | 0 | values[i].gen = opt->option; |
1573 | 0 | values[i].isset = false; |
1574 | |
|
1575 | 0 | i++; |
1576 | 0 | } |
1577 | |
|
1578 | 0 | if (options != (Datum) 0) |
1579 | 0 | parseRelOptionsInternal(options, validate, values, nopts); |
1580 | |
|
1581 | 0 | return values; |
1582 | 0 | } |
1583 | | |
1584 | | /* |
1585 | | * Subroutine for parseRelOptions, to parse and validate a single option's |
1586 | | * value |
1587 | | */ |
1588 | | static void |
1589 | | parse_one_reloption(relopt_value *option, char *text_str, int text_len, |
1590 | | bool validate) |
1591 | 0 | { |
1592 | 0 | char *value; |
1593 | 0 | int value_len; |
1594 | 0 | bool parsed; |
1595 | 0 | bool nofree = false; |
1596 | |
|
1597 | 0 | if (option->isset && validate) |
1598 | 0 | ereport(ERROR, |
1599 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
1600 | 0 | errmsg("parameter \"%s\" specified more than once", |
1601 | 0 | option->gen->name))); |
1602 | | |
1603 | 0 | value_len = text_len - option->gen->namelen - 1; |
1604 | 0 | value = (char *) palloc(value_len + 1); |
1605 | 0 | memcpy(value, text_str + option->gen->namelen + 1, value_len); |
1606 | 0 | value[value_len] = '\0'; |
1607 | |
|
1608 | 0 | switch (option->gen->type) |
1609 | 0 | { |
1610 | 0 | case RELOPT_TYPE_BOOL: |
1611 | 0 | { |
1612 | 0 | parsed = parse_bool(value, &option->values.bool_val); |
1613 | 0 | if (validate && !parsed) |
1614 | 0 | ereport(ERROR, |
1615 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
1616 | 0 | errmsg("invalid value for boolean option \"%s\": %s", |
1617 | 0 | option->gen->name, value))); |
1618 | 0 | } |
1619 | 0 | break; |
1620 | 0 | case RELOPT_TYPE_INT: |
1621 | 0 | { |
1622 | 0 | relopt_int *optint = (relopt_int *) option->gen; |
1623 | |
|
1624 | 0 | parsed = parse_int(value, &option->values.int_val, 0, NULL); |
1625 | 0 | if (validate && !parsed) |
1626 | 0 | ereport(ERROR, |
1627 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
1628 | 0 | errmsg("invalid value for integer option \"%s\": %s", |
1629 | 0 | option->gen->name, value))); |
1630 | 0 | if (validate && (option->values.int_val < optint->min || |
1631 | 0 | option->values.int_val > optint->max)) |
1632 | 0 | ereport(ERROR, |
1633 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
1634 | 0 | errmsg("value %s out of bounds for option \"%s\"", |
1635 | 0 | value, option->gen->name), |
1636 | 0 | errdetail("Valid values are between \"%d\" and \"%d\".", |
1637 | 0 | optint->min, optint->max))); |
1638 | 0 | } |
1639 | 0 | break; |
1640 | 0 | case RELOPT_TYPE_REAL: |
1641 | 0 | { |
1642 | 0 | relopt_real *optreal = (relopt_real *) option->gen; |
1643 | |
|
1644 | 0 | parsed = parse_real(value, &option->values.real_val, 0, NULL); |
1645 | 0 | if (validate && !parsed) |
1646 | 0 | ereport(ERROR, |
1647 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
1648 | 0 | errmsg("invalid value for floating point option \"%s\": %s", |
1649 | 0 | option->gen->name, value))); |
1650 | 0 | if (validate && (option->values.real_val < optreal->min || |
1651 | 0 | option->values.real_val > optreal->max)) |
1652 | 0 | ereport(ERROR, |
1653 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
1654 | 0 | errmsg("value %s out of bounds for option \"%s\"", |
1655 | 0 | value, option->gen->name), |
1656 | 0 | errdetail("Valid values are between \"%f\" and \"%f\".", |
1657 | 0 | optreal->min, optreal->max))); |
1658 | 0 | } |
1659 | 0 | break; |
1660 | 0 | case RELOPT_TYPE_ENUM: |
1661 | 0 | { |
1662 | 0 | relopt_enum *optenum = (relopt_enum *) option->gen; |
1663 | 0 | relopt_enum_elt_def *elt; |
1664 | |
|
1665 | 0 | parsed = false; |
1666 | 0 | for (elt = optenum->members; elt->string_val; elt++) |
1667 | 0 | { |
1668 | 0 | if (pg_strcasecmp(value, elt->string_val) == 0) |
1669 | 0 | { |
1670 | 0 | option->values.enum_val = elt->symbol_val; |
1671 | 0 | parsed = true; |
1672 | 0 | break; |
1673 | 0 | } |
1674 | 0 | } |
1675 | 0 | if (validate && !parsed) |
1676 | 0 | ereport(ERROR, |
1677 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
1678 | 0 | errmsg("invalid value for enum option \"%s\": %s", |
1679 | 0 | option->gen->name, value), |
1680 | 0 | optenum->detailmsg ? |
1681 | 0 | errdetail_internal("%s", _(optenum->detailmsg)) : 0)); |
1682 | | |
1683 | | /* |
1684 | | * If value is not among the allowed string values, but we are |
1685 | | * not asked to validate, just use the default numeric value. |
1686 | | */ |
1687 | 0 | if (!parsed) |
1688 | 0 | option->values.enum_val = optenum->default_val; |
1689 | 0 | } |
1690 | 0 | break; |
1691 | 0 | case RELOPT_TYPE_STRING: |
1692 | 0 | { |
1693 | 0 | relopt_string *optstring = (relopt_string *) option->gen; |
1694 | |
|
1695 | 0 | option->values.string_val = value; |
1696 | 0 | nofree = true; |
1697 | 0 | if (validate && optstring->validate_cb) |
1698 | 0 | (optstring->validate_cb) (value); |
1699 | 0 | parsed = true; |
1700 | 0 | } |
1701 | 0 | break; |
1702 | 0 | default: |
1703 | 0 | elog(ERROR, "unsupported reloption type %d", option->gen->type); |
1704 | 0 | parsed = true; /* quiet compiler */ |
1705 | 0 | break; |
1706 | 0 | } |
1707 | | |
1708 | 0 | if (parsed) |
1709 | 0 | option->isset = true; |
1710 | 0 | if (!nofree) |
1711 | 0 | pfree(value); |
1712 | 0 | } |
1713 | | |
1714 | | /* |
1715 | | * Given the result from parseRelOptions, allocate a struct that's of the |
1716 | | * specified base size plus any extra space that's needed for string variables. |
1717 | | * |
1718 | | * "base" should be sizeof(struct) of the reloptions struct (StdRdOptions or |
1719 | | * equivalent). |
1720 | | */ |
1721 | | static void * |
1722 | | allocateReloptStruct(Size base, relopt_value *options, int numoptions) |
1723 | 0 | { |
1724 | 0 | Size size = base; |
1725 | 0 | int i; |
1726 | |
|
1727 | 0 | for (i = 0; i < numoptions; i++) |
1728 | 0 | { |
1729 | 0 | relopt_value *optval = &options[i]; |
1730 | |
|
1731 | 0 | if (optval->gen->type == RELOPT_TYPE_STRING) |
1732 | 0 | { |
1733 | 0 | relopt_string *optstr = (relopt_string *) optval->gen; |
1734 | |
|
1735 | 0 | if (optstr->fill_cb) |
1736 | 0 | { |
1737 | 0 | const char *val = optval->isset ? optval->values.string_val : |
1738 | 0 | optstr->default_isnull ? NULL : optstr->default_val; |
1739 | |
|
1740 | 0 | size += optstr->fill_cb(val, NULL); |
1741 | 0 | } |
1742 | 0 | else |
1743 | 0 | size += GET_STRING_RELOPTION_LEN(*optval) + 1; |
1744 | 0 | } |
1745 | 0 | } |
1746 | |
|
1747 | 0 | return palloc0(size); |
1748 | 0 | } |
1749 | | |
1750 | | /* |
1751 | | * Given the result of parseRelOptions and a parsing table, fill in the |
1752 | | * struct (previously allocated with allocateReloptStruct) with the parsed |
1753 | | * values. |
1754 | | * |
1755 | | * rdopts is the pointer to the allocated struct to be filled. |
1756 | | * basesize is the sizeof(struct) that was passed to allocateReloptStruct. |
1757 | | * options, of length numoptions, is parseRelOptions' output. |
1758 | | * elems, of length numelems, is the table describing the allowed options. |
1759 | | * When validate is true, it is expected that all options appear in elems. |
1760 | | */ |
1761 | | static void |
1762 | | fillRelOptions(void *rdopts, Size basesize, |
1763 | | relopt_value *options, int numoptions, |
1764 | | bool validate, |
1765 | | const relopt_parse_elt *elems, int numelems) |
1766 | 0 | { |
1767 | 0 | int i; |
1768 | 0 | int offset = basesize; |
1769 | |
|
1770 | 0 | for (i = 0; i < numoptions; i++) |
1771 | 0 | { |
1772 | 0 | int j; |
1773 | 0 | bool found = false; |
1774 | |
|
1775 | 0 | for (j = 0; j < numelems; j++) |
1776 | 0 | { |
1777 | 0 | if (strcmp(options[i].gen->name, elems[j].optname) == 0) |
1778 | 0 | { |
1779 | 0 | relopt_string *optstring; |
1780 | 0 | char *itempos = ((char *) rdopts) + elems[j].offset; |
1781 | 0 | char *string_val; |
1782 | | |
1783 | | /* |
1784 | | * If isset_offset is provided, store whether the reloption is |
1785 | | * set there. |
1786 | | */ |
1787 | 0 | if (elems[j].isset_offset > 0) |
1788 | 0 | { |
1789 | 0 | char *setpos = ((char *) rdopts) + elems[j].isset_offset; |
1790 | |
|
1791 | 0 | *(bool *) setpos = options[i].isset; |
1792 | 0 | } |
1793 | |
|
1794 | 0 | switch (options[i].gen->type) |
1795 | 0 | { |
1796 | 0 | case RELOPT_TYPE_BOOL: |
1797 | 0 | *(bool *) itempos = options[i].isset ? |
1798 | 0 | options[i].values.bool_val : |
1799 | 0 | ((relopt_bool *) options[i].gen)->default_val; |
1800 | 0 | break; |
1801 | 0 | case RELOPT_TYPE_INT: |
1802 | 0 | *(int *) itempos = options[i].isset ? |
1803 | 0 | options[i].values.int_val : |
1804 | 0 | ((relopt_int *) options[i].gen)->default_val; |
1805 | 0 | break; |
1806 | 0 | case RELOPT_TYPE_REAL: |
1807 | 0 | *(double *) itempos = options[i].isset ? |
1808 | 0 | options[i].values.real_val : |
1809 | 0 | ((relopt_real *) options[i].gen)->default_val; |
1810 | 0 | break; |
1811 | 0 | case RELOPT_TYPE_ENUM: |
1812 | 0 | *(int *) itempos = options[i].isset ? |
1813 | 0 | options[i].values.enum_val : |
1814 | 0 | ((relopt_enum *) options[i].gen)->default_val; |
1815 | 0 | break; |
1816 | 0 | case RELOPT_TYPE_STRING: |
1817 | 0 | optstring = (relopt_string *) options[i].gen; |
1818 | 0 | if (options[i].isset) |
1819 | 0 | string_val = options[i].values.string_val; |
1820 | 0 | else if (!optstring->default_isnull) |
1821 | 0 | string_val = optstring->default_val; |
1822 | 0 | else |
1823 | 0 | string_val = NULL; |
1824 | |
|
1825 | 0 | if (optstring->fill_cb) |
1826 | 0 | { |
1827 | 0 | Size size = |
1828 | 0 | optstring->fill_cb(string_val, |
1829 | 0 | (char *) rdopts + offset); |
1830 | |
|
1831 | 0 | if (size) |
1832 | 0 | { |
1833 | 0 | *(int *) itempos = offset; |
1834 | 0 | offset += size; |
1835 | 0 | } |
1836 | 0 | else |
1837 | 0 | *(int *) itempos = 0; |
1838 | 0 | } |
1839 | 0 | else if (string_val == NULL) |
1840 | 0 | *(int *) itempos = 0; |
1841 | 0 | else |
1842 | 0 | { |
1843 | 0 | strcpy((char *) rdopts + offset, string_val); |
1844 | 0 | *(int *) itempos = offset; |
1845 | 0 | offset += strlen(string_val) + 1; |
1846 | 0 | } |
1847 | 0 | break; |
1848 | 0 | default: |
1849 | 0 | elog(ERROR, "unsupported reloption type %d", |
1850 | 0 | options[i].gen->type); |
1851 | 0 | break; |
1852 | 0 | } |
1853 | 0 | found = true; |
1854 | 0 | break; |
1855 | 0 | } |
1856 | 0 | } |
1857 | 0 | if (validate && !found) |
1858 | 0 | elog(ERROR, "reloption \"%s\" not found in parse table", |
1859 | 0 | options[i].gen->name); |
1860 | 0 | } |
1861 | 0 | SET_VARSIZE(rdopts, offset); |
1862 | 0 | } |
1863 | | |
1864 | | |
1865 | | /* |
1866 | | * Option parser for anything that uses StdRdOptions. |
1867 | | */ |
1868 | | bytea * |
1869 | | default_reloptions(Datum reloptions, bool validate, relopt_kind kind) |
1870 | 0 | { |
1871 | 0 | static const relopt_parse_elt tab[] = { |
1872 | 0 | {"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)}, |
1873 | 0 | {"autovacuum_enabled", RELOPT_TYPE_BOOL, |
1874 | 0 | offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)}, |
1875 | 0 | {"autovacuum_vacuum_threshold", RELOPT_TYPE_INT, |
1876 | 0 | offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_threshold)}, |
1877 | 0 | {"autovacuum_vacuum_max_threshold", RELOPT_TYPE_INT, |
1878 | 0 | offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_max_threshold)}, |
1879 | 0 | {"autovacuum_vacuum_insert_threshold", RELOPT_TYPE_INT, |
1880 | 0 | offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_ins_threshold)}, |
1881 | 0 | {"autovacuum_analyze_threshold", RELOPT_TYPE_INT, |
1882 | 0 | offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_threshold)}, |
1883 | 0 | {"autovacuum_vacuum_cost_limit", RELOPT_TYPE_INT, |
1884 | 0 | offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_limit)}, |
1885 | 0 | {"autovacuum_freeze_min_age", RELOPT_TYPE_INT, |
1886 | 0 | offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_min_age)}, |
1887 | 0 | {"autovacuum_freeze_max_age", RELOPT_TYPE_INT, |
1888 | 0 | offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_max_age)}, |
1889 | 0 | {"autovacuum_freeze_table_age", RELOPT_TYPE_INT, |
1890 | 0 | offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_table_age)}, |
1891 | 0 | {"autovacuum_multixact_freeze_min_age", RELOPT_TYPE_INT, |
1892 | 0 | offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_min_age)}, |
1893 | 0 | {"autovacuum_multixact_freeze_max_age", RELOPT_TYPE_INT, |
1894 | 0 | offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_max_age)}, |
1895 | 0 | {"autovacuum_multixact_freeze_table_age", RELOPT_TYPE_INT, |
1896 | 0 | offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_table_age)}, |
1897 | 0 | {"log_autovacuum_min_duration", RELOPT_TYPE_INT, |
1898 | 0 | offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, log_min_duration)}, |
1899 | 0 | {"toast_tuple_target", RELOPT_TYPE_INT, |
1900 | 0 | offsetof(StdRdOptions, toast_tuple_target)}, |
1901 | 0 | {"autovacuum_vacuum_cost_delay", RELOPT_TYPE_REAL, |
1902 | 0 | offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_delay)}, |
1903 | 0 | {"autovacuum_vacuum_scale_factor", RELOPT_TYPE_REAL, |
1904 | 0 | offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_scale_factor)}, |
1905 | 0 | {"autovacuum_vacuum_insert_scale_factor", RELOPT_TYPE_REAL, |
1906 | 0 | offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_ins_scale_factor)}, |
1907 | 0 | {"autovacuum_analyze_scale_factor", RELOPT_TYPE_REAL, |
1908 | 0 | offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_scale_factor)}, |
1909 | 0 | {"user_catalog_table", RELOPT_TYPE_BOOL, |
1910 | 0 | offsetof(StdRdOptions, user_catalog_table)}, |
1911 | 0 | {"parallel_workers", RELOPT_TYPE_INT, |
1912 | 0 | offsetof(StdRdOptions, parallel_workers)}, |
1913 | 0 | {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, |
1914 | 0 | offsetof(StdRdOptions, vacuum_index_cleanup)}, |
1915 | 0 | {"vacuum_truncate", RELOPT_TYPE_BOOL, |
1916 | 0 | offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, |
1917 | 0 | {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, |
1918 | 0 | offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} |
1919 | 0 | }; |
1920 | |
|
1921 | 0 | return (bytea *) build_reloptions(reloptions, validate, kind, |
1922 | 0 | sizeof(StdRdOptions), |
1923 | 0 | tab, lengthof(tab)); |
1924 | 0 | } |
1925 | | |
1926 | | /* |
1927 | | * build_reloptions |
1928 | | * |
1929 | | * Parses "reloptions" provided by the caller, returning them in a |
1930 | | * structure containing the parsed options. The parsing is done with |
1931 | | * the help of a parsing table describing the allowed options, defined |
1932 | | * by "relopt_elems" of length "num_relopt_elems". |
1933 | | * |
1934 | | * "validate" must be true if reloptions value is freshly built by |
1935 | | * transformRelOptions(), as opposed to being read from the catalog, in which |
1936 | | * case the values contained in it must already be valid. |
1937 | | * |
1938 | | * NULL is returned if the passed-in options did not match any of the options |
1939 | | * in the parsing table, unless validate is true in which case an error would |
1940 | | * be reported. |
1941 | | */ |
1942 | | void * |
1943 | | build_reloptions(Datum reloptions, bool validate, |
1944 | | relopt_kind kind, |
1945 | | Size relopt_struct_size, |
1946 | | const relopt_parse_elt *relopt_elems, |
1947 | | int num_relopt_elems) |
1948 | 0 | { |
1949 | 0 | int numoptions; |
1950 | 0 | relopt_value *options; |
1951 | 0 | void *rdopts; |
1952 | | |
1953 | | /* parse options specific to given relation option kind */ |
1954 | 0 | options = parseRelOptions(reloptions, validate, kind, &numoptions); |
1955 | 0 | Assert(numoptions <= num_relopt_elems); |
1956 | | |
1957 | | /* if none set, we're done */ |
1958 | 0 | if (numoptions == 0) |
1959 | 0 | { |
1960 | 0 | Assert(options == NULL); |
1961 | 0 | return NULL; |
1962 | 0 | } |
1963 | | |
1964 | | /* allocate and fill the structure */ |
1965 | 0 | rdopts = allocateReloptStruct(relopt_struct_size, options, numoptions); |
1966 | 0 | fillRelOptions(rdopts, relopt_struct_size, options, numoptions, |
1967 | 0 | validate, relopt_elems, num_relopt_elems); |
1968 | |
|
1969 | 0 | pfree(options); |
1970 | |
|
1971 | 0 | return rdopts; |
1972 | 0 | } |
1973 | | |
1974 | | /* |
1975 | | * Parse local options, allocate a bytea struct that's of the specified |
1976 | | * 'base_size' plus any extra space that's needed for string variables, |
1977 | | * fill its option's fields located at the given offsets and return it. |
1978 | | */ |
1979 | | void * |
1980 | | build_local_reloptions(local_relopts *relopts, Datum options, bool validate) |
1981 | 0 | { |
1982 | 0 | int noptions = list_length(relopts->options); |
1983 | 0 | relopt_parse_elt *elems = palloc(sizeof(*elems) * noptions); |
1984 | 0 | relopt_value *vals; |
1985 | 0 | void *opts; |
1986 | 0 | int i = 0; |
1987 | 0 | ListCell *lc; |
1988 | |
|
1989 | 0 | foreach(lc, relopts->options) |
1990 | 0 | { |
1991 | 0 | local_relopt *opt = lfirst(lc); |
1992 | |
|
1993 | 0 | elems[i].optname = opt->option->name; |
1994 | 0 | elems[i].opttype = opt->option->type; |
1995 | 0 | elems[i].offset = opt->offset; |
1996 | 0 | elems[i].isset_offset = 0; /* not supported for local relopts yet */ |
1997 | |
|
1998 | 0 | i++; |
1999 | 0 | } |
2000 | |
|
2001 | 0 | vals = parseLocalRelOptions(relopts, options, validate); |
2002 | 0 | opts = allocateReloptStruct(relopts->relopt_struct_size, vals, noptions); |
2003 | 0 | fillRelOptions(opts, relopts->relopt_struct_size, vals, noptions, validate, |
2004 | 0 | elems, noptions); |
2005 | |
|
2006 | 0 | if (validate) |
2007 | 0 | foreach(lc, relopts->validators) |
2008 | 0 | ((relopts_validator) lfirst(lc)) (opts, vals, noptions); |
2009 | |
|
2010 | 0 | if (elems) |
2011 | 0 | pfree(elems); |
2012 | |
|
2013 | 0 | return opts; |
2014 | 0 | } |
2015 | | |
2016 | | /* |
2017 | | * Option parser for partitioned tables |
2018 | | */ |
2019 | | bytea * |
2020 | | partitioned_table_reloptions(Datum reloptions, bool validate) |
2021 | 0 | { |
2022 | 0 | if (validate && reloptions) |
2023 | 0 | ereport(ERROR, |
2024 | 0 | errcode(ERRCODE_WRONG_OBJECT_TYPE), |
2025 | 0 | errmsg("cannot specify storage parameters for a partitioned table"), |
2026 | 0 | errhint("Specify storage parameters for its leaf partitions instead.")); |
2027 | 0 | return NULL; |
2028 | 0 | } |
2029 | | |
2030 | | /* |
2031 | | * Option parser for views |
2032 | | */ |
2033 | | bytea * |
2034 | | view_reloptions(Datum reloptions, bool validate) |
2035 | 0 | { |
2036 | 0 | static const relopt_parse_elt tab[] = { |
2037 | 0 | {"security_barrier", RELOPT_TYPE_BOOL, |
2038 | 0 | offsetof(ViewOptions, security_barrier)}, |
2039 | 0 | {"security_invoker", RELOPT_TYPE_BOOL, |
2040 | 0 | offsetof(ViewOptions, security_invoker)}, |
2041 | 0 | {"check_option", RELOPT_TYPE_ENUM, |
2042 | 0 | offsetof(ViewOptions, check_option)} |
2043 | 0 | }; |
2044 | |
|
2045 | 0 | return (bytea *) build_reloptions(reloptions, validate, |
2046 | 0 | RELOPT_KIND_VIEW, |
2047 | 0 | sizeof(ViewOptions), |
2048 | 0 | tab, lengthof(tab)); |
2049 | 0 | } |
2050 | | |
2051 | | /* |
2052 | | * Parse options for heaps, views and toast tables. |
2053 | | */ |
2054 | | bytea * |
2055 | | heap_reloptions(char relkind, Datum reloptions, bool validate) |
2056 | 0 | { |
2057 | 0 | StdRdOptions *rdopts; |
2058 | |
|
2059 | 0 | switch (relkind) |
2060 | 0 | { |
2061 | 0 | case RELKIND_TOASTVALUE: |
2062 | 0 | rdopts = (StdRdOptions *) |
2063 | 0 | default_reloptions(reloptions, validate, RELOPT_KIND_TOAST); |
2064 | 0 | if (rdopts != NULL) |
2065 | 0 | { |
2066 | | /* adjust default-only parameters for TOAST relations */ |
2067 | 0 | rdopts->fillfactor = 100; |
2068 | 0 | rdopts->autovacuum.analyze_threshold = -1; |
2069 | 0 | rdopts->autovacuum.analyze_scale_factor = -1; |
2070 | 0 | } |
2071 | 0 | return (bytea *) rdopts; |
2072 | 0 | case RELKIND_RELATION: |
2073 | 0 | case RELKIND_MATVIEW: |
2074 | 0 | return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP); |
2075 | 0 | default: |
2076 | | /* other relkinds are not supported */ |
2077 | 0 | return NULL; |
2078 | 0 | } |
2079 | 0 | } |
2080 | | |
2081 | | |
2082 | | /* |
2083 | | * Parse options for indexes. |
2084 | | * |
2085 | | * amoptions index AM's option parser function |
2086 | | * reloptions options as text[] datum |
2087 | | * validate error flag |
2088 | | */ |
2089 | | bytea * |
2090 | | index_reloptions(amoptions_function amoptions, Datum reloptions, bool validate) |
2091 | 0 | { |
2092 | 0 | Assert(amoptions != NULL); |
2093 | | |
2094 | | /* Assume function is strict */ |
2095 | 0 | if (DatumGetPointer(reloptions) == NULL) |
2096 | 0 | return NULL; |
2097 | | |
2098 | 0 | return amoptions(reloptions, validate); |
2099 | 0 | } |
2100 | | |
2101 | | /* |
2102 | | * Option parser for attribute reloptions |
2103 | | */ |
2104 | | bytea * |
2105 | | attribute_reloptions(Datum reloptions, bool validate) |
2106 | 0 | { |
2107 | 0 | static const relopt_parse_elt tab[] = { |
2108 | 0 | {"n_distinct", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct)}, |
2109 | 0 | {"n_distinct_inherited", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct_inherited)} |
2110 | 0 | }; |
2111 | |
|
2112 | 0 | return (bytea *) build_reloptions(reloptions, validate, |
2113 | 0 | RELOPT_KIND_ATTRIBUTE, |
2114 | 0 | sizeof(AttributeOpts), |
2115 | 0 | tab, lengthof(tab)); |
2116 | 0 | } |
2117 | | |
2118 | | /* |
2119 | | * Option parser for tablespace reloptions |
2120 | | */ |
2121 | | bytea * |
2122 | | tablespace_reloptions(Datum reloptions, bool validate) |
2123 | 0 | { |
2124 | 0 | static const relopt_parse_elt tab[] = { |
2125 | 0 | {"random_page_cost", RELOPT_TYPE_REAL, offsetof(TableSpaceOpts, random_page_cost)}, |
2126 | 0 | {"seq_page_cost", RELOPT_TYPE_REAL, offsetof(TableSpaceOpts, seq_page_cost)}, |
2127 | 0 | {"effective_io_concurrency", RELOPT_TYPE_INT, offsetof(TableSpaceOpts, effective_io_concurrency)}, |
2128 | 0 | {"maintenance_io_concurrency", RELOPT_TYPE_INT, offsetof(TableSpaceOpts, maintenance_io_concurrency)} |
2129 | 0 | }; |
2130 | |
|
2131 | 0 | return (bytea *) build_reloptions(reloptions, validate, |
2132 | 0 | RELOPT_KIND_TABLESPACE, |
2133 | 0 | sizeof(TableSpaceOpts), |
2134 | 0 | tab, lengthof(tab)); |
2135 | 0 | } |
2136 | | |
2137 | | /* |
2138 | | * Determine the required LOCKMODE from an option list. |
2139 | | * |
2140 | | * Called from AlterTableGetLockLevel(), see that function |
2141 | | * for a longer explanation of how this works. |
2142 | | */ |
2143 | | LOCKMODE |
2144 | | AlterTableGetRelOptionsLockLevel(List *defList) |
2145 | 0 | { |
2146 | 0 | LOCKMODE lockmode = NoLock; |
2147 | 0 | ListCell *cell; |
2148 | |
|
2149 | 0 | if (defList == NIL) |
2150 | 0 | return AccessExclusiveLock; |
2151 | | |
2152 | 0 | if (need_initialization) |
2153 | 0 | initialize_reloptions(); |
2154 | |
|
2155 | 0 | foreach(cell, defList) |
2156 | 0 | { |
2157 | 0 | DefElem *def = (DefElem *) lfirst(cell); |
2158 | 0 | int i; |
2159 | |
|
2160 | 0 | for (i = 0; relOpts[i]; i++) |
2161 | 0 | { |
2162 | 0 | if (strncmp(relOpts[i]->name, |
2163 | 0 | def->defname, |
2164 | 0 | relOpts[i]->namelen + 1) == 0) |
2165 | 0 | { |
2166 | 0 | if (lockmode < relOpts[i]->lockmode) |
2167 | 0 | lockmode = relOpts[i]->lockmode; |
2168 | 0 | } |
2169 | 0 | } |
2170 | 0 | } |
2171 | |
|
2172 | 0 | return lockmode; |
2173 | 0 | } |