/src/postgres/src/backend/access/spgist/spgutils.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * spgutils.c |
4 | | * various support functions for SP-GiST |
5 | | * |
6 | | * |
7 | | * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group |
8 | | * Portions Copyright (c) 1994, Regents of the University of California |
9 | | * |
10 | | * IDENTIFICATION |
11 | | * src/backend/access/spgist/spgutils.c |
12 | | * |
13 | | *------------------------------------------------------------------------- |
14 | | */ |
15 | | |
16 | | #include "postgres.h" |
17 | | |
18 | | #include "access/amvalidate.h" |
19 | | #include "access/htup_details.h" |
20 | | #include "access/reloptions.h" |
21 | | #include "access/spgist_private.h" |
22 | | #include "access/toast_compression.h" |
23 | | #include "access/transam.h" |
24 | | #include "access/xact.h" |
25 | | #include "catalog/pg_amop.h" |
26 | | #include "commands/vacuum.h" |
27 | | #include "nodes/nodeFuncs.h" |
28 | | #include "parser/parse_coerce.h" |
29 | | #include "storage/bufmgr.h" |
30 | | #include "storage/indexfsm.h" |
31 | | #include "utils/catcache.h" |
32 | | #include "utils/fmgrprotos.h" |
33 | | #include "utils/index_selfuncs.h" |
34 | | #include "utils/lsyscache.h" |
35 | | #include "utils/rel.h" |
36 | | #include "utils/syscache.h" |
37 | | |
38 | | |
39 | | /* |
40 | | * SP-GiST handler function: return IndexAmRoutine with access method parameters |
41 | | * and callbacks. |
42 | | */ |
43 | | Datum |
44 | | spghandler(PG_FUNCTION_ARGS) |
45 | 0 | { |
46 | 0 | static const IndexAmRoutine amroutine = { |
47 | 0 | .type = T_IndexAmRoutine, |
48 | 0 | .amstrategies = 0, |
49 | 0 | .amsupport = SPGISTNProc, |
50 | 0 | .amoptsprocnum = SPGIST_OPTIONS_PROC, |
51 | 0 | .amcanorder = false, |
52 | 0 | .amcanorderbyop = true, |
53 | 0 | .amcanhash = false, |
54 | 0 | .amconsistentequality = false, |
55 | 0 | .amconsistentordering = false, |
56 | 0 | .amcanbackward = false, |
57 | 0 | .amcanunique = false, |
58 | 0 | .amcanmulticol = false, |
59 | 0 | .amoptionalkey = true, |
60 | 0 | .amsearcharray = false, |
61 | 0 | .amsearchnulls = true, |
62 | 0 | .amstorage = true, |
63 | 0 | .amclusterable = false, |
64 | 0 | .ampredlocks = false, |
65 | 0 | .amcanparallel = false, |
66 | 0 | .amcanbuildparallel = false, |
67 | 0 | .amcaninclude = true, |
68 | 0 | .amusemaintenanceworkmem = false, |
69 | 0 | .amsummarizing = false, |
70 | 0 | .amparallelvacuumoptions = |
71 | 0 | VACUUM_OPTION_PARALLEL_BULKDEL | VACUUM_OPTION_PARALLEL_COND_CLEANUP, |
72 | 0 | .amkeytype = InvalidOid, |
73 | |
|
74 | 0 | .ambuild = spgbuild, |
75 | 0 | .ambuildempty = spgbuildempty, |
76 | 0 | .aminsert = spginsert, |
77 | 0 | .aminsertcleanup = NULL, |
78 | 0 | .ambulkdelete = spgbulkdelete, |
79 | 0 | .amvacuumcleanup = spgvacuumcleanup, |
80 | 0 | .amcanreturn = spgcanreturn, |
81 | 0 | .amcostestimate = spgcostestimate, |
82 | 0 | .amgettreeheight = NULL, |
83 | 0 | .amoptions = spgoptions, |
84 | 0 | .amproperty = spgproperty, |
85 | 0 | .ambuildphasename = NULL, |
86 | 0 | .amvalidate = spgvalidate, |
87 | 0 | .amadjustmembers = spgadjustmembers, |
88 | 0 | .ambeginscan = spgbeginscan, |
89 | 0 | .amrescan = spgrescan, |
90 | 0 | .amgettuple = spggettuple, |
91 | 0 | .amgetbitmap = spggetbitmap, |
92 | 0 | .amendscan = spgendscan, |
93 | 0 | .ammarkpos = NULL, |
94 | 0 | .amrestrpos = NULL, |
95 | 0 | .amestimateparallelscan = NULL, |
96 | 0 | .aminitparallelscan = NULL, |
97 | 0 | .amparallelrescan = NULL, |
98 | 0 | .amtranslatestrategy = NULL, |
99 | 0 | .amtranslatecmptype = NULL, |
100 | 0 | }; |
101 | |
|
102 | 0 | PG_RETURN_POINTER(&amroutine); |
103 | 0 | } |
104 | | |
105 | | /* |
106 | | * GetIndexInputType |
107 | | * Determine the nominal input data type for an index column |
108 | | * |
109 | | * We define the "nominal" input type as the associated opclass's opcintype, |
110 | | * or if that is a polymorphic type, the base type of the heap column or |
111 | | * expression that is the index's input. The reason for preferring the |
112 | | * opcintype is that non-polymorphic opclasses probably don't want to hear |
113 | | * about binary-compatible input types. For instance, if a text opclass |
114 | | * is being used with a varchar heap column, we want to report "text" not |
115 | | * "varchar". Likewise, opclasses don't want to hear about domain types, |
116 | | * so if we do consult the actual input type, we make sure to flatten domains. |
117 | | * |
118 | | * At some point maybe this should go somewhere else, but it's not clear |
119 | | * if any other index AMs have a use for it. |
120 | | */ |
121 | | static Oid |
122 | | GetIndexInputType(Relation index, AttrNumber indexcol) |
123 | 0 | { |
124 | 0 | Oid opcintype; |
125 | 0 | AttrNumber heapcol; |
126 | 0 | List *indexprs; |
127 | 0 | ListCell *indexpr_item; |
128 | |
|
129 | 0 | Assert(index->rd_index != NULL); |
130 | 0 | Assert(indexcol > 0 && indexcol <= index->rd_index->indnkeyatts); |
131 | 0 | opcintype = index->rd_opcintype[indexcol - 1]; |
132 | 0 | if (!IsPolymorphicType(opcintype)) |
133 | 0 | return opcintype; |
134 | 0 | heapcol = index->rd_index->indkey.values[indexcol - 1]; |
135 | 0 | if (heapcol != 0) /* Simple index column? */ |
136 | 0 | return getBaseType(get_atttype(index->rd_index->indrelid, heapcol)); |
137 | | |
138 | | /* |
139 | | * If the index expressions are already cached, skip calling |
140 | | * RelationGetIndexExpressions, as it will make a copy which is overkill. |
141 | | * We're not going to modify the trees, and we're not going to do anything |
142 | | * that would invalidate the relcache entry before we're done. |
143 | | */ |
144 | 0 | if (index->rd_indexprs) |
145 | 0 | indexprs = index->rd_indexprs; |
146 | 0 | else |
147 | 0 | indexprs = RelationGetIndexExpressions(index); |
148 | 0 | indexpr_item = list_head(indexprs); |
149 | 0 | for (int i = 1; i <= index->rd_index->indnkeyatts; i++) |
150 | 0 | { |
151 | 0 | if (index->rd_index->indkey.values[i - 1] == 0) |
152 | 0 | { |
153 | | /* expression column */ |
154 | 0 | if (indexpr_item == NULL) |
155 | 0 | elog(ERROR, "wrong number of index expressions"); |
156 | 0 | if (i == indexcol) |
157 | 0 | return getBaseType(exprType((Node *) lfirst(indexpr_item))); |
158 | 0 | indexpr_item = lnext(indexprs, indexpr_item); |
159 | 0 | } |
160 | 0 | } |
161 | 0 | elog(ERROR, "wrong number of index expressions"); |
162 | 0 | return InvalidOid; /* keep compiler quiet */ |
163 | 0 | } |
164 | | |
165 | | /* Fill in a SpGistTypeDesc struct with info about the specified data type */ |
166 | | static void |
167 | | fillTypeDesc(SpGistTypeDesc *desc, Oid type) |
168 | 0 | { |
169 | 0 | HeapTuple tp; |
170 | 0 | Form_pg_type typtup; |
171 | |
|
172 | 0 | desc->type = type; |
173 | 0 | tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type)); |
174 | 0 | if (!HeapTupleIsValid(tp)) |
175 | 0 | elog(ERROR, "cache lookup failed for type %u", type); |
176 | 0 | typtup = (Form_pg_type) GETSTRUCT(tp); |
177 | 0 | desc->attlen = typtup->typlen; |
178 | 0 | desc->attbyval = typtup->typbyval; |
179 | 0 | desc->attalign = typtup->typalign; |
180 | 0 | desc->attstorage = typtup->typstorage; |
181 | 0 | ReleaseSysCache(tp); |
182 | 0 | } |
183 | | |
184 | | /* |
185 | | * Fetch local cache of AM-specific info about the index, initializing it |
186 | | * if necessary |
187 | | */ |
188 | | SpGistCache * |
189 | | spgGetCache(Relation index) |
190 | 0 | { |
191 | 0 | SpGistCache *cache; |
192 | |
|
193 | 0 | if (index->rd_amcache == NULL) |
194 | 0 | { |
195 | 0 | Oid atttype; |
196 | 0 | spgConfigIn in; |
197 | 0 | FmgrInfo *procinfo; |
198 | |
|
199 | 0 | cache = MemoryContextAllocZero(index->rd_indexcxt, |
200 | 0 | sizeof(SpGistCache)); |
201 | | |
202 | | /* SPGiST must have one key column and can also have INCLUDE columns */ |
203 | 0 | Assert(IndexRelationGetNumberOfKeyAttributes(index) == 1); |
204 | 0 | Assert(IndexRelationGetNumberOfAttributes(index) <= INDEX_MAX_KEYS); |
205 | | |
206 | | /* |
207 | | * Get the actual (well, nominal) data type of the key column. We |
208 | | * pass this to the opclass config function so that polymorphic |
209 | | * opclasses are possible. |
210 | | */ |
211 | 0 | atttype = GetIndexInputType(index, spgKeyColumn + 1); |
212 | | |
213 | | /* Call the config function to get config info for the opclass */ |
214 | 0 | in.attType = atttype; |
215 | |
|
216 | 0 | procinfo = index_getprocinfo(index, 1, SPGIST_CONFIG_PROC); |
217 | 0 | FunctionCall2Coll(procinfo, |
218 | 0 | index->rd_indcollation[spgKeyColumn], |
219 | 0 | PointerGetDatum(&in), |
220 | 0 | PointerGetDatum(&cache->config)); |
221 | | |
222 | | /* |
223 | | * If leafType isn't specified, use the declared index column type, |
224 | | * which index.c will have derived from the opclass's opcintype. |
225 | | * (Although we now make spgvalidate.c warn if these aren't the same, |
226 | | * old user-defined opclasses may not set the STORAGE parameter |
227 | | * correctly, so believe leafType if it's given.) |
228 | | */ |
229 | 0 | if (!OidIsValid(cache->config.leafType)) |
230 | 0 | { |
231 | 0 | cache->config.leafType = |
232 | 0 | TupleDescAttr(RelationGetDescr(index), spgKeyColumn)->atttypid; |
233 | | |
234 | | /* |
235 | | * If index column type is binary-coercible to atttype (for |
236 | | * example, it's a domain over atttype), treat it as plain atttype |
237 | | * to avoid thinking we need to compress. |
238 | | */ |
239 | 0 | if (cache->config.leafType != atttype && |
240 | 0 | IsBinaryCoercible(cache->config.leafType, atttype)) |
241 | 0 | cache->config.leafType = atttype; |
242 | 0 | } |
243 | | |
244 | | /* Get the information we need about each relevant datatype */ |
245 | 0 | fillTypeDesc(&cache->attType, atttype); |
246 | |
|
247 | 0 | if (cache->config.leafType != atttype) |
248 | 0 | { |
249 | 0 | if (!OidIsValid(index_getprocid(index, 1, SPGIST_COMPRESS_PROC))) |
250 | 0 | ereport(ERROR, |
251 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
252 | 0 | errmsg("compress method must be defined when leaf type is different from input type"))); |
253 | | |
254 | 0 | fillTypeDesc(&cache->attLeafType, cache->config.leafType); |
255 | 0 | } |
256 | 0 | else |
257 | 0 | { |
258 | | /* Save lookups in this common case */ |
259 | 0 | cache->attLeafType = cache->attType; |
260 | 0 | } |
261 | | |
262 | 0 | fillTypeDesc(&cache->attPrefixType, cache->config.prefixType); |
263 | 0 | fillTypeDesc(&cache->attLabelType, cache->config.labelType); |
264 | | |
265 | | /* |
266 | | * Finally, if it's a real index (not a partitioned one), get the |
267 | | * lastUsedPages data from the metapage |
268 | | */ |
269 | 0 | if (index->rd_rel->relkind != RELKIND_PARTITIONED_INDEX) |
270 | 0 | { |
271 | 0 | Buffer metabuffer; |
272 | 0 | SpGistMetaPageData *metadata; |
273 | |
|
274 | 0 | metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO); |
275 | 0 | LockBuffer(metabuffer, BUFFER_LOCK_SHARE); |
276 | |
|
277 | 0 | metadata = SpGistPageGetMeta(BufferGetPage(metabuffer)); |
278 | |
|
279 | 0 | if (metadata->magicNumber != SPGIST_MAGIC_NUMBER) |
280 | 0 | elog(ERROR, "index \"%s\" is not an SP-GiST index", |
281 | 0 | RelationGetRelationName(index)); |
282 | | |
283 | 0 | cache->lastUsedPages = metadata->lastUsedPages; |
284 | |
|
285 | 0 | UnlockReleaseBuffer(metabuffer); |
286 | 0 | } |
287 | | |
288 | 0 | index->rd_amcache = cache; |
289 | 0 | } |
290 | 0 | else |
291 | 0 | { |
292 | | /* assume it's up to date */ |
293 | 0 | cache = (SpGistCache *) index->rd_amcache; |
294 | 0 | } |
295 | | |
296 | 0 | return cache; |
297 | 0 | } |
298 | | |
299 | | /* |
300 | | * Compute a tuple descriptor for leaf tuples or index-only-scan result tuples. |
301 | | * |
302 | | * We can use the relcache's tupdesc as-is in many cases, and it's always |
303 | | * OK so far as any INCLUDE columns are concerned. However, the entry for |
304 | | * the key column has to match leafType in the first case or attType in the |
305 | | * second case. While the relcache's tupdesc *should* show leafType, this |
306 | | * might not hold for legacy user-defined opclasses, since before v14 they |
307 | | * were not allowed to declare their true storage type in CREATE OPCLASS. |
308 | | * Also, attType can be different from what is in the relcache. |
309 | | * |
310 | | * This function gives back either a pointer to the relcache's tupdesc |
311 | | * if that is suitable, or a palloc'd copy that's been adjusted to match |
312 | | * the specified key column type. We can avoid doing any catalog lookups |
313 | | * here by insisting that the caller pass an SpGistTypeDesc not just an OID. |
314 | | */ |
315 | | TupleDesc |
316 | | getSpGistTupleDesc(Relation index, SpGistTypeDesc *keyType) |
317 | 0 | { |
318 | 0 | TupleDesc outTupDesc; |
319 | 0 | Form_pg_attribute att; |
320 | |
|
321 | 0 | if (keyType->type == |
322 | 0 | TupleDescAttr(RelationGetDescr(index), spgKeyColumn)->atttypid) |
323 | 0 | outTupDesc = RelationGetDescr(index); |
324 | 0 | else |
325 | 0 | { |
326 | 0 | outTupDesc = CreateTupleDescCopy(RelationGetDescr(index)); |
327 | 0 | att = TupleDescAttr(outTupDesc, spgKeyColumn); |
328 | | /* It's sufficient to update the type-dependent fields of the column */ |
329 | 0 | att->atttypid = keyType->type; |
330 | 0 | att->atttypmod = -1; |
331 | 0 | att->attlen = keyType->attlen; |
332 | 0 | att->attbyval = keyType->attbyval; |
333 | 0 | att->attalign = keyType->attalign; |
334 | 0 | att->attstorage = keyType->attstorage; |
335 | | /* We shouldn't need to bother with making these valid: */ |
336 | 0 | att->attcompression = InvalidCompressionMethod; |
337 | 0 | att->attcollation = InvalidOid; |
338 | |
|
339 | 0 | populate_compact_attribute(outTupDesc, spgKeyColumn); |
340 | 0 | TupleDescFinalize(outTupDesc); |
341 | 0 | } |
342 | 0 | return outTupDesc; |
343 | 0 | } |
344 | | |
345 | | /* Initialize SpGistState for working with the given index */ |
346 | | void |
347 | | initSpGistState(SpGistState *state, Relation index) |
348 | 0 | { |
349 | 0 | SpGistCache *cache; |
350 | |
|
351 | 0 | state->index = index; |
352 | | |
353 | | /* Get cached static information about index */ |
354 | 0 | cache = spgGetCache(index); |
355 | |
|
356 | 0 | state->config = cache->config; |
357 | 0 | state->attType = cache->attType; |
358 | 0 | state->attLeafType = cache->attLeafType; |
359 | 0 | state->attPrefixType = cache->attPrefixType; |
360 | 0 | state->attLabelType = cache->attLabelType; |
361 | | |
362 | | /* Ensure we have a valid descriptor for leaf tuples */ |
363 | 0 | state->leafTupDesc = getSpGistTupleDesc(state->index, &state->attLeafType); |
364 | | |
365 | | /* Make workspace for constructing dead tuples */ |
366 | 0 | state->deadTupleStorage = palloc0(SGDTSIZE); |
367 | | |
368 | | /* |
369 | | * Set horizon XID to use in redirection tuples. Use our own XID if we |
370 | | * have one, else use InvalidTransactionId. The latter case can happen in |
371 | | * VACUUM or REINDEX CONCURRENTLY, and in neither case would it be okay to |
372 | | * force an XID to be assigned. VACUUM won't create any redirection |
373 | | * tuples anyway, but REINDEX CONCURRENTLY can. Fortunately, REINDEX |
374 | | * CONCURRENTLY doesn't mark the index valid until the end, so there could |
375 | | * never be any concurrent scans "in flight" to a redirection tuple it has |
376 | | * inserted. And it locks out VACUUM until the end, too. So it's okay |
377 | | * for VACUUM to immediately expire a redirection tuple that contains an |
378 | | * invalid xid. |
379 | | */ |
380 | 0 | state->redirectXid = GetTopTransactionIdIfAny(); |
381 | | |
382 | | /* Assume we're not in an index build (spgbuild will override) */ |
383 | 0 | state->isBuild = false; |
384 | 0 | } |
385 | | |
386 | | /* |
387 | | * Allocate a new page (either by recycling, or by extending the index file). |
388 | | * |
389 | | * The returned buffer is already pinned and exclusive-locked. |
390 | | * Caller is responsible for initializing the page by calling SpGistInitBuffer. |
391 | | */ |
392 | | Buffer |
393 | | SpGistNewBuffer(Relation index) |
394 | 0 | { |
395 | 0 | Buffer buffer; |
396 | | |
397 | | /* First, try to get a page from FSM */ |
398 | 0 | for (;;) |
399 | 0 | { |
400 | 0 | BlockNumber blkno = GetFreeIndexPage(index); |
401 | |
|
402 | 0 | if (blkno == InvalidBlockNumber) |
403 | 0 | break; /* nothing known to FSM */ |
404 | | |
405 | | /* |
406 | | * The fixed pages shouldn't ever be listed in FSM, but just in case |
407 | | * one is, ignore it. |
408 | | */ |
409 | 0 | if (SpGistBlockIsFixed(blkno)) |
410 | 0 | continue; |
411 | | |
412 | 0 | buffer = ReadBuffer(index, blkno); |
413 | | |
414 | | /* |
415 | | * We have to guard against the possibility that someone else already |
416 | | * recycled this page; the buffer may be locked if so. |
417 | | */ |
418 | 0 | if (ConditionalLockBuffer(buffer)) |
419 | 0 | { |
420 | 0 | Page page = BufferGetPage(buffer); |
421 | |
|
422 | 0 | if (PageIsNew(page)) |
423 | 0 | return buffer; /* OK to use, if never initialized */ |
424 | | |
425 | 0 | if (SpGistPageIsDeleted(page) || PageIsEmpty(page)) |
426 | 0 | return buffer; /* OK to use */ |
427 | | |
428 | 0 | LockBuffer(buffer, BUFFER_LOCK_UNLOCK); |
429 | 0 | } |
430 | | |
431 | | /* Can't use it, so release buffer and try again */ |
432 | 0 | ReleaseBuffer(buffer); |
433 | 0 | } |
434 | | |
435 | 0 | buffer = ExtendBufferedRel(BMR_REL(index), MAIN_FORKNUM, NULL, |
436 | 0 | EB_LOCK_FIRST); |
437 | |
|
438 | 0 | return buffer; |
439 | 0 | } |
440 | | |
441 | | /* |
442 | | * Update index metapage's lastUsedPages info from local cache, if possible |
443 | | * |
444 | | * Updating meta page isn't critical for index working, so |
445 | | * 1 use ConditionalLockBuffer to improve concurrency |
446 | | * 2 don't WAL-log metabuffer changes to decrease WAL traffic |
447 | | */ |
448 | | void |
449 | | SpGistUpdateMetaPage(Relation index) |
450 | 0 | { |
451 | 0 | SpGistCache *cache = (SpGistCache *) index->rd_amcache; |
452 | |
|
453 | 0 | if (cache != NULL) |
454 | 0 | { |
455 | 0 | Buffer metabuffer; |
456 | |
|
457 | 0 | metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO); |
458 | |
|
459 | 0 | if (ConditionalLockBuffer(metabuffer)) |
460 | 0 | { |
461 | 0 | Page metapage = BufferGetPage(metabuffer); |
462 | 0 | SpGistMetaPageData *metadata = SpGistPageGetMeta(metapage); |
463 | |
|
464 | 0 | metadata->lastUsedPages = cache->lastUsedPages; |
465 | | |
466 | | /* |
467 | | * Set pd_lower just past the end of the metadata. This is |
468 | | * essential, because without doing so, metadata will be lost if |
469 | | * xlog.c compresses the page. (We must do this here because |
470 | | * pre-v11 versions of PG did not set the metapage's pd_lower |
471 | | * correctly, so a pg_upgraded index might contain the wrong |
472 | | * value.) |
473 | | */ |
474 | 0 | ((PageHeader) metapage)->pd_lower = |
475 | 0 | ((char *) metadata + sizeof(SpGistMetaPageData)) - (char *) metapage; |
476 | |
|
477 | 0 | MarkBufferDirty(metabuffer); |
478 | 0 | UnlockReleaseBuffer(metabuffer); |
479 | 0 | } |
480 | 0 | else |
481 | 0 | { |
482 | 0 | ReleaseBuffer(metabuffer); |
483 | 0 | } |
484 | 0 | } |
485 | 0 | } |
486 | | |
487 | | /* Macro to select proper element of lastUsedPages cache depending on flags */ |
488 | | /* Masking flags with SPGIST_CACHED_PAGES is just for paranoia's sake */ |
489 | 0 | #define GET_LUP(c, f) (&(c)->lastUsedPages.cachedPage[((unsigned int) (f)) % SPGIST_CACHED_PAGES]) |
490 | | |
491 | | /* |
492 | | * Allocate and initialize a new buffer of the type and parity specified by |
493 | | * flags. The returned buffer is already pinned and exclusive-locked. |
494 | | * |
495 | | * When requesting an inner page, if we get one with the wrong parity, |
496 | | * we just release the buffer and try again. We will get a different page |
497 | | * because GetFreeIndexPage will have marked the page used in FSM. The page |
498 | | * is entered in our local lastUsedPages cache, so there's some hope of |
499 | | * making use of it later in this session, but otherwise we rely on VACUUM |
500 | | * to eventually re-enter the page in FSM, making it available for recycling. |
501 | | * Note that such a page does not get marked dirty here, so unless it's used |
502 | | * fairly soon, the buffer will just get discarded and the page will remain |
503 | | * as it was on disk. |
504 | | * |
505 | | * When we return a buffer to the caller, the page is *not* entered into |
506 | | * the lastUsedPages cache; we expect the caller will do so after it's taken |
507 | | * whatever space it will use. This is because after the caller has used up |
508 | | * some space, the page might have less space than whatever was cached already |
509 | | * so we'd rather not trash the old cache entry. |
510 | | */ |
511 | | static Buffer |
512 | | allocNewBuffer(Relation index, int flags) |
513 | 0 | { |
514 | 0 | SpGistCache *cache = spgGetCache(index); |
515 | 0 | uint16 pageflags = 0; |
516 | |
|
517 | 0 | if (GBUF_REQ_LEAF(flags)) |
518 | 0 | pageflags |= SPGIST_LEAF; |
519 | 0 | if (GBUF_REQ_NULLS(flags)) |
520 | 0 | pageflags |= SPGIST_NULLS; |
521 | |
|
522 | 0 | for (;;) |
523 | 0 | { |
524 | 0 | Buffer buffer; |
525 | |
|
526 | 0 | buffer = SpGistNewBuffer(index); |
527 | 0 | SpGistInitBuffer(buffer, pageflags); |
528 | |
|
529 | 0 | if (pageflags & SPGIST_LEAF) |
530 | 0 | { |
531 | | /* Leaf pages have no parity concerns, so just use it */ |
532 | 0 | return buffer; |
533 | 0 | } |
534 | 0 | else |
535 | 0 | { |
536 | 0 | BlockNumber blkno = BufferGetBlockNumber(buffer); |
537 | 0 | int blkFlags = GBUF_INNER_PARITY(blkno); |
538 | |
|
539 | 0 | if ((flags & GBUF_PARITY_MASK) == blkFlags) |
540 | 0 | { |
541 | | /* Page has right parity, use it */ |
542 | 0 | return buffer; |
543 | 0 | } |
544 | 0 | else |
545 | 0 | { |
546 | | /* Page has wrong parity, record it in cache and try again */ |
547 | 0 | if (pageflags & SPGIST_NULLS) |
548 | 0 | blkFlags |= GBUF_NULLS; |
549 | 0 | cache->lastUsedPages.cachedPage[blkFlags].blkno = blkno; |
550 | 0 | cache->lastUsedPages.cachedPage[blkFlags].freeSpace = |
551 | 0 | PageGetExactFreeSpace(BufferGetPage(buffer)); |
552 | 0 | UnlockReleaseBuffer(buffer); |
553 | 0 | } |
554 | 0 | } |
555 | 0 | } |
556 | 0 | } |
557 | | |
558 | | /* |
559 | | * Get a buffer of the type and parity specified by flags, having at least |
560 | | * as much free space as indicated by needSpace. We use the lastUsedPages |
561 | | * cache to assign the same buffer previously requested when possible. |
562 | | * The returned buffer is already pinned and exclusive-locked. |
563 | | * |
564 | | * *isNew is set true if the page was initialized here, false if it was |
565 | | * already valid. |
566 | | */ |
567 | | Buffer |
568 | | SpGistGetBuffer(Relation index, int flags, int needSpace, bool *isNew) |
569 | 0 | { |
570 | 0 | SpGistCache *cache = spgGetCache(index); |
571 | 0 | SpGistLastUsedPage *lup; |
572 | | |
573 | | /* Bail out if even an empty page wouldn't meet the demand */ |
574 | 0 | if (needSpace > SPGIST_PAGE_CAPACITY) |
575 | 0 | elog(ERROR, "desired SPGiST tuple size is too big"); |
576 | | |
577 | | /* |
578 | | * If possible, increase the space request to include relation's |
579 | | * fillfactor. This ensures that when we add unrelated tuples to a page, |
580 | | * we try to keep 100-fillfactor% available for adding tuples that are |
581 | | * related to the ones already on it. But fillfactor mustn't cause an |
582 | | * error for requests that would otherwise be legal. |
583 | | */ |
584 | 0 | needSpace += SpGistGetTargetPageFreeSpace(index); |
585 | 0 | needSpace = Min(needSpace, SPGIST_PAGE_CAPACITY); |
586 | | |
587 | | /* Get the cache entry for this flags setting */ |
588 | 0 | lup = GET_LUP(cache, flags); |
589 | | |
590 | | /* If we have nothing cached, just turn it over to allocNewBuffer */ |
591 | 0 | if (lup->blkno == InvalidBlockNumber) |
592 | 0 | { |
593 | 0 | *isNew = true; |
594 | 0 | return allocNewBuffer(index, flags); |
595 | 0 | } |
596 | | |
597 | | /* fixed pages should never be in cache */ |
598 | 0 | Assert(!SpGistBlockIsFixed(lup->blkno)); |
599 | | |
600 | | /* If cached freeSpace isn't enough, don't bother looking at the page */ |
601 | 0 | if (lup->freeSpace >= needSpace) |
602 | 0 | { |
603 | 0 | Buffer buffer; |
604 | 0 | Page page; |
605 | |
|
606 | 0 | buffer = ReadBuffer(index, lup->blkno); |
607 | |
|
608 | 0 | if (!ConditionalLockBuffer(buffer)) |
609 | 0 | { |
610 | | /* |
611 | | * buffer is locked by another process, so return a new buffer |
612 | | */ |
613 | 0 | ReleaseBuffer(buffer); |
614 | 0 | *isNew = true; |
615 | 0 | return allocNewBuffer(index, flags); |
616 | 0 | } |
617 | | |
618 | 0 | page = BufferGetPage(buffer); |
619 | |
|
620 | 0 | if (PageIsNew(page) || SpGistPageIsDeleted(page) || PageIsEmpty(page)) |
621 | 0 | { |
622 | | /* OK to initialize the page */ |
623 | 0 | uint16 pageflags = 0; |
624 | |
|
625 | 0 | if (GBUF_REQ_LEAF(flags)) |
626 | 0 | pageflags |= SPGIST_LEAF; |
627 | 0 | if (GBUF_REQ_NULLS(flags)) |
628 | 0 | pageflags |= SPGIST_NULLS; |
629 | 0 | SpGistInitBuffer(buffer, pageflags); |
630 | 0 | lup->freeSpace = PageGetExactFreeSpace(page) - needSpace; |
631 | 0 | *isNew = true; |
632 | 0 | return buffer; |
633 | 0 | } |
634 | | |
635 | | /* |
636 | | * Check that page is of right type and has enough space. We must |
637 | | * recheck this since our cache isn't necessarily up to date. |
638 | | */ |
639 | 0 | if ((GBUF_REQ_LEAF(flags) ? SpGistPageIsLeaf(page) : !SpGistPageIsLeaf(page)) && |
640 | 0 | (GBUF_REQ_NULLS(flags) ? SpGistPageStoresNulls(page) : !SpGistPageStoresNulls(page))) |
641 | 0 | { |
642 | 0 | int freeSpace = PageGetExactFreeSpace(page); |
643 | |
|
644 | 0 | if (freeSpace >= needSpace) |
645 | 0 | { |
646 | | /* Success, update freespace info and return the buffer */ |
647 | 0 | lup->freeSpace = freeSpace - needSpace; |
648 | 0 | *isNew = false; |
649 | 0 | return buffer; |
650 | 0 | } |
651 | 0 | } |
652 | | |
653 | | /* |
654 | | * fallback to allocation of new buffer |
655 | | */ |
656 | 0 | UnlockReleaseBuffer(buffer); |
657 | 0 | } |
658 | | |
659 | | /* No success with cache, so return a new buffer */ |
660 | 0 | *isNew = true; |
661 | 0 | return allocNewBuffer(index, flags); |
662 | 0 | } |
663 | | |
664 | | /* |
665 | | * Update lastUsedPages cache when done modifying a page. |
666 | | * |
667 | | * We update the appropriate cache entry if it already contained this page |
668 | | * (its freeSpace is likely obsolete), or if this page has more space than |
669 | | * whatever we had cached. |
670 | | */ |
671 | | void |
672 | | SpGistSetLastUsedPage(Relation index, Buffer buffer) |
673 | 0 | { |
674 | 0 | SpGistCache *cache = spgGetCache(index); |
675 | 0 | SpGistLastUsedPage *lup; |
676 | 0 | int freeSpace; |
677 | 0 | Page page = BufferGetPage(buffer); |
678 | 0 | BlockNumber blkno = BufferGetBlockNumber(buffer); |
679 | 0 | int flags; |
680 | | |
681 | | /* Never enter fixed pages (root pages) in cache, though */ |
682 | 0 | if (SpGistBlockIsFixed(blkno)) |
683 | 0 | return; |
684 | | |
685 | 0 | if (SpGistPageIsLeaf(page)) |
686 | 0 | flags = GBUF_LEAF; |
687 | 0 | else |
688 | 0 | flags = GBUF_INNER_PARITY(blkno); |
689 | 0 | if (SpGistPageStoresNulls(page)) |
690 | 0 | flags |= GBUF_NULLS; |
691 | |
|
692 | 0 | lup = GET_LUP(cache, flags); |
693 | |
|
694 | 0 | freeSpace = PageGetExactFreeSpace(page); |
695 | 0 | if (lup->blkno == InvalidBlockNumber || lup->blkno == blkno || |
696 | 0 | lup->freeSpace < freeSpace) |
697 | 0 | { |
698 | 0 | lup->blkno = blkno; |
699 | 0 | lup->freeSpace = freeSpace; |
700 | 0 | } |
701 | 0 | } |
702 | | |
703 | | /* |
704 | | * Initialize an SPGiST page to empty, with specified flags |
705 | | */ |
706 | | void |
707 | | SpGistInitPage(Page page, uint16 f) |
708 | 0 | { |
709 | 0 | SpGistPageOpaque opaque; |
710 | |
|
711 | 0 | PageInit(page, BLCKSZ, sizeof(SpGistPageOpaqueData)); |
712 | 0 | opaque = SpGistPageGetOpaque(page); |
713 | 0 | opaque->flags = f; |
714 | 0 | opaque->spgist_page_id = SPGIST_PAGE_ID; |
715 | 0 | } |
716 | | |
717 | | /* |
718 | | * Initialize a buffer's page to empty, with specified flags |
719 | | */ |
720 | | void |
721 | | SpGistInitBuffer(Buffer b, uint16 f) |
722 | 0 | { |
723 | 0 | Assert(BufferGetPageSize(b) == BLCKSZ); |
724 | 0 | SpGistInitPage(BufferGetPage(b), f); |
725 | 0 | } |
726 | | |
727 | | /* |
728 | | * Initialize metadata page |
729 | | */ |
730 | | void |
731 | | SpGistInitMetapage(Page page) |
732 | 0 | { |
733 | 0 | SpGistMetaPageData *metadata; |
734 | 0 | int i; |
735 | |
|
736 | 0 | SpGistInitPage(page, SPGIST_META); |
737 | 0 | metadata = SpGistPageGetMeta(page); |
738 | 0 | memset(metadata, 0, sizeof(SpGistMetaPageData)); |
739 | 0 | metadata->magicNumber = SPGIST_MAGIC_NUMBER; |
740 | | |
741 | | /* initialize last-used-page cache to empty */ |
742 | 0 | for (i = 0; i < SPGIST_CACHED_PAGES; i++) |
743 | 0 | metadata->lastUsedPages.cachedPage[i].blkno = InvalidBlockNumber; |
744 | | |
745 | | /* |
746 | | * Set pd_lower just past the end of the metadata. This is essential, |
747 | | * because without doing so, metadata will be lost if xlog.c compresses |
748 | | * the page. |
749 | | */ |
750 | 0 | ((PageHeader) page)->pd_lower = |
751 | 0 | ((char *) metadata + sizeof(SpGistMetaPageData)) - (char *) page; |
752 | 0 | } |
753 | | |
754 | | /* |
755 | | * reloptions processing for SPGiST |
756 | | */ |
757 | | bytea * |
758 | | spgoptions(Datum reloptions, bool validate) |
759 | 0 | { |
760 | 0 | static const relopt_parse_elt tab[] = { |
761 | 0 | {"fillfactor", RELOPT_TYPE_INT, offsetof(SpGistOptions, fillfactor)}, |
762 | 0 | }; |
763 | |
|
764 | 0 | return (bytea *) build_reloptions(reloptions, validate, |
765 | 0 | RELOPT_KIND_SPGIST, |
766 | 0 | sizeof(SpGistOptions), |
767 | 0 | tab, lengthof(tab)); |
768 | 0 | } |
769 | | |
770 | | /* |
771 | | * Get the space needed to store a non-null datum of the indicated type |
772 | | * in an inner tuple (that is, as a prefix or node label). |
773 | | * Note the result is already rounded up to a MAXALIGN boundary. |
774 | | * Here we follow the convention that pass-by-val types are just stored |
775 | | * in their Datum representation (compare memcpyInnerDatum). |
776 | | */ |
777 | | unsigned int |
778 | | SpGistGetInnerTypeSize(SpGistTypeDesc *att, Datum datum) |
779 | 0 | { |
780 | 0 | unsigned int size; |
781 | |
|
782 | 0 | if (att->attbyval) |
783 | 0 | size = sizeof(Datum); |
784 | 0 | else if (att->attlen > 0) |
785 | 0 | size = att->attlen; |
786 | 0 | else |
787 | 0 | size = VARSIZE_ANY(DatumGetPointer(datum)); |
788 | |
|
789 | 0 | return MAXALIGN(size); |
790 | 0 | } |
791 | | |
792 | | /* |
793 | | * Copy the given non-null datum to *target, in the inner-tuple case |
794 | | */ |
795 | | static void |
796 | | memcpyInnerDatum(void *target, SpGistTypeDesc *att, Datum datum) |
797 | 0 | { |
798 | 0 | unsigned int size; |
799 | |
|
800 | 0 | if (att->attbyval) |
801 | 0 | { |
802 | 0 | memcpy(target, &datum, sizeof(Datum)); |
803 | 0 | } |
804 | 0 | else |
805 | 0 | { |
806 | 0 | size = (att->attlen > 0) ? att->attlen : VARSIZE_ANY(DatumGetPointer(datum)); |
807 | 0 | memcpy(target, DatumGetPointer(datum), size); |
808 | 0 | } |
809 | 0 | } |
810 | | |
811 | | /* |
812 | | * Compute space required for a leaf tuple holding the given data. |
813 | | * |
814 | | * This must match the size-calculation portion of spgFormLeafTuple. |
815 | | */ |
816 | | Size |
817 | | SpGistGetLeafTupleSize(TupleDesc tupleDescriptor, |
818 | | const Datum *datums, const bool *isnulls) |
819 | 0 | { |
820 | 0 | Size size; |
821 | 0 | Size data_size; |
822 | 0 | bool needs_null_mask = false; |
823 | 0 | int natts = tupleDescriptor->natts; |
824 | | |
825 | | /* |
826 | | * Decide whether we need a nulls bitmask. |
827 | | * |
828 | | * If there is only a key attribute (natts == 1), never use a bitmask, for |
829 | | * compatibility with the pre-v14 layout of leaf tuples. Otherwise, we |
830 | | * need one if any attribute is null. |
831 | | */ |
832 | 0 | if (natts > 1) |
833 | 0 | { |
834 | 0 | for (int i = 0; i < natts; i++) |
835 | 0 | { |
836 | 0 | if (isnulls[i]) |
837 | 0 | { |
838 | 0 | needs_null_mask = true; |
839 | 0 | break; |
840 | 0 | } |
841 | 0 | } |
842 | 0 | } |
843 | | |
844 | | /* |
845 | | * Calculate size of the data part; same as for heap tuples. |
846 | | */ |
847 | 0 | data_size = heap_compute_data_size(tupleDescriptor, datums, isnulls); |
848 | | |
849 | | /* |
850 | | * Compute total size. |
851 | | */ |
852 | 0 | size = SGLTHDRSZ(needs_null_mask); |
853 | 0 | size += data_size; |
854 | 0 | size = MAXALIGN(size); |
855 | | |
856 | | /* |
857 | | * Ensure that we can replace the tuple with a dead tuple later. This test |
858 | | * is unnecessary when there are any non-null attributes, but be safe. |
859 | | */ |
860 | 0 | if (size < SGDTSIZE) |
861 | 0 | size = SGDTSIZE; |
862 | |
|
863 | 0 | return size; |
864 | 0 | } |
865 | | |
866 | | /* |
867 | | * Construct a leaf tuple containing the given heap TID and datum values |
868 | | */ |
869 | | SpGistLeafTuple |
870 | | spgFormLeafTuple(SpGistState *state, const ItemPointerData *heapPtr, |
871 | | const Datum *datums, const bool *isnulls) |
872 | 0 | { |
873 | 0 | SpGistLeafTuple tup; |
874 | 0 | TupleDesc tupleDescriptor = state->leafTupDesc; |
875 | 0 | Size size; |
876 | 0 | Size hoff; |
877 | 0 | Size data_size; |
878 | 0 | bool needs_null_mask = false; |
879 | 0 | int natts = tupleDescriptor->natts; |
880 | 0 | char *tp; /* ptr to tuple data */ |
881 | 0 | uint16 tupmask = 0; /* unused heap_fill_tuple output */ |
882 | | |
883 | | /* |
884 | | * Decide whether we need a nulls bitmask. |
885 | | * |
886 | | * If there is only a key attribute (natts == 1), never use a bitmask, for |
887 | | * compatibility with the pre-v14 layout of leaf tuples. Otherwise, we |
888 | | * need one if any attribute is null. |
889 | | */ |
890 | 0 | if (natts > 1) |
891 | 0 | { |
892 | 0 | for (int i = 0; i < natts; i++) |
893 | 0 | { |
894 | 0 | if (isnulls[i]) |
895 | 0 | { |
896 | 0 | needs_null_mask = true; |
897 | 0 | break; |
898 | 0 | } |
899 | 0 | } |
900 | 0 | } |
901 | | |
902 | | /* |
903 | | * Calculate size of the data part; same as for heap tuples. |
904 | | */ |
905 | 0 | data_size = heap_compute_data_size(tupleDescriptor, datums, isnulls); |
906 | | |
907 | | /* |
908 | | * Compute total size. |
909 | | */ |
910 | 0 | hoff = SGLTHDRSZ(needs_null_mask); |
911 | 0 | size = hoff + data_size; |
912 | 0 | size = MAXALIGN(size); |
913 | | |
914 | | /* |
915 | | * Ensure that we can replace the tuple with a dead tuple later. This test |
916 | | * is unnecessary when there are any non-null attributes, but be safe. |
917 | | */ |
918 | 0 | if (size < SGDTSIZE) |
919 | 0 | size = SGDTSIZE; |
920 | | |
921 | | /* OK, form the tuple */ |
922 | 0 | tup = (SpGistLeafTuple) palloc0(size); |
923 | |
|
924 | 0 | tup->size = size; |
925 | 0 | SGLT_SET_NEXTOFFSET(tup, InvalidOffsetNumber); |
926 | 0 | tup->heapPtr = *heapPtr; |
927 | |
|
928 | 0 | tp = (char *) tup + hoff; |
929 | |
|
930 | 0 | if (needs_null_mask) |
931 | 0 | { |
932 | 0 | uint8 *bp; /* ptr to null bitmap in tuple */ |
933 | | |
934 | | /* Set nullmask presence bit in SpGistLeafTuple header */ |
935 | 0 | SGLT_SET_HASNULLMASK(tup, true); |
936 | | /* Fill the data area and null mask */ |
937 | 0 | bp = (uint8 *) ((char *) tup + sizeof(SpGistLeafTupleData)); |
938 | 0 | heap_fill_tuple(tupleDescriptor, datums, isnulls, tp, data_size, |
939 | 0 | &tupmask, bp); |
940 | 0 | } |
941 | 0 | else if (natts > 1 || !isnulls[spgKeyColumn]) |
942 | 0 | { |
943 | | /* Fill data area only */ |
944 | 0 | heap_fill_tuple(tupleDescriptor, datums, isnulls, tp, data_size, |
945 | 0 | &tupmask, (uint8 *) NULL); |
946 | 0 | } |
947 | | /* otherwise we have no data, nor a bitmap, to fill */ |
948 | |
|
949 | 0 | return tup; |
950 | 0 | } |
951 | | |
952 | | /* |
953 | | * Construct a node (to go into an inner tuple) containing the given label |
954 | | * |
955 | | * Note that the node's downlink is just set invalid here. Caller will fill |
956 | | * it in later. |
957 | | */ |
958 | | SpGistNodeTuple |
959 | | spgFormNodeTuple(SpGistState *state, Datum label, bool isnull) |
960 | 0 | { |
961 | 0 | SpGistNodeTuple tup; |
962 | 0 | unsigned int size; |
963 | 0 | unsigned short infomask = 0; |
964 | | |
965 | | /* compute space needed (note result is already maxaligned) */ |
966 | 0 | size = SGNTHDRSZ; |
967 | 0 | if (!isnull) |
968 | 0 | size += SpGistGetInnerTypeSize(&state->attLabelType, label); |
969 | | |
970 | | /* |
971 | | * Here we make sure that the size will fit in the field reserved for it |
972 | | * in t_info. |
973 | | */ |
974 | 0 | if ((size & INDEX_SIZE_MASK) != size) |
975 | 0 | ereport(ERROR, |
976 | 0 | (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), |
977 | 0 | errmsg("index row requires %zu bytes, maximum size is %zu", |
978 | 0 | (Size) size, (Size) INDEX_SIZE_MASK))); |
979 | | |
980 | 0 | tup = (SpGistNodeTuple) palloc0(size); |
981 | |
|
982 | 0 | if (isnull) |
983 | 0 | infomask |= INDEX_NULL_MASK; |
984 | | /* we don't bother setting the INDEX_VAR_MASK bit */ |
985 | 0 | infomask |= size; |
986 | 0 | tup->t_info = infomask; |
987 | | |
988 | | /* The TID field will be filled in later */ |
989 | 0 | ItemPointerSetInvalid(&tup->t_tid); |
990 | |
|
991 | 0 | if (!isnull) |
992 | 0 | memcpyInnerDatum(SGNTDATAPTR(tup), &state->attLabelType, label); |
993 | |
|
994 | 0 | return tup; |
995 | 0 | } |
996 | | |
997 | | /* |
998 | | * Construct an inner tuple containing the given prefix and node array |
999 | | */ |
1000 | | SpGistInnerTuple |
1001 | | spgFormInnerTuple(SpGistState *state, bool hasPrefix, Datum prefix, |
1002 | | int nNodes, SpGistNodeTuple *nodes) |
1003 | 0 | { |
1004 | 0 | SpGistInnerTuple tup; |
1005 | 0 | unsigned int size; |
1006 | 0 | unsigned int prefixSize; |
1007 | 0 | int i; |
1008 | 0 | char *ptr; |
1009 | | |
1010 | | /* Compute size needed */ |
1011 | 0 | if (hasPrefix) |
1012 | 0 | prefixSize = SpGistGetInnerTypeSize(&state->attPrefixType, prefix); |
1013 | 0 | else |
1014 | 0 | prefixSize = 0; |
1015 | |
|
1016 | 0 | size = SGITHDRSZ + prefixSize; |
1017 | | |
1018 | | /* Note: we rely on node tuple sizes to be maxaligned already */ |
1019 | 0 | for (i = 0; i < nNodes; i++) |
1020 | 0 | size += IndexTupleSize(nodes[i]); |
1021 | | |
1022 | | /* |
1023 | | * Ensure that we can replace the tuple with a dead tuple later. This |
1024 | | * test is unnecessary given current tuple layouts, but let's be safe. |
1025 | | */ |
1026 | 0 | if (size < SGDTSIZE) |
1027 | 0 | size = SGDTSIZE; |
1028 | | |
1029 | | /* |
1030 | | * Inner tuple should be small enough to fit on a page |
1031 | | */ |
1032 | 0 | if (size > SPGIST_PAGE_CAPACITY - sizeof(ItemIdData)) |
1033 | 0 | ereport(ERROR, |
1034 | 0 | (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), |
1035 | 0 | errmsg("SP-GiST inner tuple size %zu exceeds maximum %zu", |
1036 | 0 | (Size) size, |
1037 | 0 | SPGIST_PAGE_CAPACITY - sizeof(ItemIdData)), |
1038 | 0 | errhint("Values larger than a buffer page cannot be indexed."))); |
1039 | | |
1040 | | /* |
1041 | | * Check for overflow of header fields --- probably can't fail if the |
1042 | | * above succeeded, but let's be paranoid |
1043 | | */ |
1044 | 0 | if (size > SGITMAXSIZE || |
1045 | 0 | prefixSize > SGITMAXPREFIXSIZE || |
1046 | 0 | nNodes > SGITMAXNNODES) |
1047 | 0 | elog(ERROR, "SPGiST inner tuple header field is too small"); |
1048 | | |
1049 | | /* OK, form the tuple */ |
1050 | 0 | tup = (SpGistInnerTuple) palloc0(size); |
1051 | |
|
1052 | 0 | tup->nNodes = nNodes; |
1053 | 0 | tup->prefixSize = prefixSize; |
1054 | 0 | tup->size = size; |
1055 | |
|
1056 | 0 | if (hasPrefix) |
1057 | 0 | memcpyInnerDatum(SGITDATAPTR(tup), &state->attPrefixType, prefix); |
1058 | |
|
1059 | 0 | ptr = (char *) SGITNODEPTR(tup); |
1060 | |
|
1061 | 0 | for (i = 0; i < nNodes; i++) |
1062 | 0 | { |
1063 | 0 | SpGistNodeTuple node = nodes[i]; |
1064 | |
|
1065 | 0 | memcpy(ptr, node, IndexTupleSize(node)); |
1066 | 0 | ptr += IndexTupleSize(node); |
1067 | 0 | } |
1068 | |
|
1069 | 0 | return tup; |
1070 | 0 | } |
1071 | | |
1072 | | /* |
1073 | | * Construct a "dead" tuple to replace a tuple being deleted. |
1074 | | * |
1075 | | * The state can be SPGIST_REDIRECT, SPGIST_DEAD, or SPGIST_PLACEHOLDER. |
1076 | | * For a REDIRECT tuple, a pointer (blkno+offset) must be supplied, and |
1077 | | * the xid field is filled in automatically. |
1078 | | * |
1079 | | * This is called in critical sections, so we don't use palloc; the tuple |
1080 | | * is built in preallocated storage. It should be copied before another |
1081 | | * call with different parameters can occur. |
1082 | | */ |
1083 | | SpGistDeadTuple |
1084 | | spgFormDeadTuple(SpGistState *state, int tupstate, |
1085 | | BlockNumber blkno, OffsetNumber offnum) |
1086 | 0 | { |
1087 | 0 | SpGistDeadTuple tuple = (SpGistDeadTuple) state->deadTupleStorage; |
1088 | |
|
1089 | 0 | tuple->tupstate = tupstate; |
1090 | 0 | tuple->size = SGDTSIZE; |
1091 | 0 | SGLT_SET_NEXTOFFSET(tuple, InvalidOffsetNumber); |
1092 | |
|
1093 | 0 | if (tupstate == SPGIST_REDIRECT) |
1094 | 0 | { |
1095 | 0 | ItemPointerSet(&tuple->pointer, blkno, offnum); |
1096 | 0 | tuple->xid = state->redirectXid; |
1097 | 0 | } |
1098 | 0 | else |
1099 | 0 | { |
1100 | 0 | ItemPointerSetInvalid(&tuple->pointer); |
1101 | 0 | tuple->xid = InvalidTransactionId; |
1102 | 0 | } |
1103 | |
|
1104 | 0 | return tuple; |
1105 | 0 | } |
1106 | | |
1107 | | /* |
1108 | | * Convert an SPGiST leaf tuple into Datum/isnull arrays. |
1109 | | * |
1110 | | * The caller must allocate sufficient storage for the output arrays. |
1111 | | * (INDEX_MAX_KEYS entries should be enough.) |
1112 | | */ |
1113 | | void |
1114 | | spgDeformLeafTuple(SpGistLeafTuple tup, TupleDesc tupleDescriptor, |
1115 | | Datum *datums, bool *isnulls, bool keyColumnIsNull) |
1116 | 0 | { |
1117 | 0 | bool hasNullsMask = SGLT_GET_HASNULLMASK(tup); |
1118 | 0 | char *tp; /* ptr to tuple data */ |
1119 | 0 | uint8 *bp; /* ptr to null bitmap in tuple */ |
1120 | |
|
1121 | 0 | if (keyColumnIsNull && tupleDescriptor->natts == 1) |
1122 | 0 | { |
1123 | | /* |
1124 | | * Trivial case: there is only the key attribute and we're in a nulls |
1125 | | * tree. The hasNullsMask bit in the tuple header should not be set |
1126 | | * (and thus we can't use index_deform_tuple_internal), but |
1127 | | * nonetheless the result is NULL. |
1128 | | * |
1129 | | * Note: currently this is dead code, because noplace calls this when |
1130 | | * there is only the key attribute. But we should cover the case. |
1131 | | */ |
1132 | 0 | Assert(!hasNullsMask); |
1133 | |
|
1134 | 0 | datums[spgKeyColumn] = (Datum) 0; |
1135 | 0 | isnulls[spgKeyColumn] = true; |
1136 | 0 | return; |
1137 | 0 | } |
1138 | | |
1139 | 0 | tp = (char *) tup + SGLTHDRSZ(hasNullsMask); |
1140 | 0 | bp = (uint8 *) ((char *) tup + sizeof(SpGistLeafTupleData)); |
1141 | |
|
1142 | 0 | index_deform_tuple_internal(tupleDescriptor, |
1143 | 0 | datums, isnulls, |
1144 | 0 | tp, bp, hasNullsMask); |
1145 | | |
1146 | | /* |
1147 | | * Key column isnull value from the tuple should be consistent with |
1148 | | * keyColumnIsNull flag from the caller. |
1149 | | */ |
1150 | 0 | Assert(keyColumnIsNull == isnulls[spgKeyColumn]); |
1151 | 0 | } |
1152 | | |
1153 | | /* |
1154 | | * Extract the label datums of the nodes within innerTuple |
1155 | | * |
1156 | | * Returns NULL if label datums are NULLs |
1157 | | */ |
1158 | | Datum * |
1159 | | spgExtractNodeLabels(SpGistState *state, SpGistInnerTuple innerTuple) |
1160 | 0 | { |
1161 | 0 | Datum *nodeLabels; |
1162 | 0 | int i; |
1163 | 0 | SpGistNodeTuple node; |
1164 | | |
1165 | | /* Either all the labels must be NULL, or none. */ |
1166 | 0 | node = SGITNODEPTR(innerTuple); |
1167 | 0 | if (IndexTupleHasNulls(node)) |
1168 | 0 | { |
1169 | 0 | SGITITERATE(innerTuple, i, node) |
1170 | 0 | { |
1171 | 0 | if (!IndexTupleHasNulls(node)) |
1172 | 0 | elog(ERROR, "some but not all node labels are null in SPGiST inner tuple"); |
1173 | 0 | } |
1174 | | /* They're all null, so just return NULL */ |
1175 | 0 | return NULL; |
1176 | 0 | } |
1177 | 0 | else |
1178 | 0 | { |
1179 | 0 | nodeLabels = palloc_array(Datum, innerTuple->nNodes); |
1180 | 0 | SGITITERATE(innerTuple, i, node) |
1181 | 0 | { |
1182 | 0 | if (IndexTupleHasNulls(node)) |
1183 | 0 | elog(ERROR, "some but not all node labels are null in SPGiST inner tuple"); |
1184 | 0 | nodeLabels[i] = SGNTDATUM(node, state); |
1185 | 0 | } |
1186 | 0 | return nodeLabels; |
1187 | 0 | } |
1188 | 0 | } |
1189 | | |
1190 | | /* |
1191 | | * Add a new item to the page, replacing a PLACEHOLDER item if possible. |
1192 | | * Return the location it's inserted at, or InvalidOffsetNumber on failure. |
1193 | | * |
1194 | | * If startOffset isn't NULL, we start searching for placeholders at |
1195 | | * *startOffset, and update that to the next place to search. This is just |
1196 | | * an optimization for repeated insertions. |
1197 | | * |
1198 | | * If errorOK is false, we throw error when there's not enough room, |
1199 | | * rather than returning InvalidOffsetNumber. |
1200 | | */ |
1201 | | OffsetNumber |
1202 | | SpGistPageAddNewItem(SpGistState *state, Page page, const void *item, Size size, |
1203 | | OffsetNumber *startOffset, bool errorOK) |
1204 | 0 | { |
1205 | 0 | SpGistPageOpaque opaque = SpGistPageGetOpaque(page); |
1206 | 0 | OffsetNumber i, |
1207 | 0 | maxoff, |
1208 | 0 | offnum; |
1209 | |
|
1210 | 0 | if (opaque->nPlaceholder > 0 && |
1211 | 0 | PageGetExactFreeSpace(page) + SGDTSIZE >= MAXALIGN(size)) |
1212 | 0 | { |
1213 | | /* Try to replace a placeholder */ |
1214 | 0 | maxoff = PageGetMaxOffsetNumber(page); |
1215 | 0 | offnum = InvalidOffsetNumber; |
1216 | |
|
1217 | 0 | for (;;) |
1218 | 0 | { |
1219 | 0 | if (startOffset && *startOffset != InvalidOffsetNumber) |
1220 | 0 | i = *startOffset; |
1221 | 0 | else |
1222 | 0 | i = FirstOffsetNumber; |
1223 | 0 | for (; i <= maxoff; i++) |
1224 | 0 | { |
1225 | 0 | SpGistDeadTuple it = (SpGistDeadTuple) PageGetItem(page, |
1226 | 0 | PageGetItemId(page, i)); |
1227 | |
|
1228 | 0 | if (it->tupstate == SPGIST_PLACEHOLDER) |
1229 | 0 | { |
1230 | 0 | offnum = i; |
1231 | 0 | break; |
1232 | 0 | } |
1233 | 0 | } |
1234 | | |
1235 | | /* Done if we found a placeholder */ |
1236 | 0 | if (offnum != InvalidOffsetNumber) |
1237 | 0 | break; |
1238 | | |
1239 | 0 | if (startOffset && *startOffset != InvalidOffsetNumber) |
1240 | 0 | { |
1241 | | /* Hint was no good, re-search from beginning */ |
1242 | 0 | *startOffset = InvalidOffsetNumber; |
1243 | 0 | continue; |
1244 | 0 | } |
1245 | | |
1246 | | /* Hmm, no placeholder found? */ |
1247 | 0 | opaque->nPlaceholder = 0; |
1248 | 0 | break; |
1249 | 0 | } |
1250 | |
|
1251 | 0 | if (offnum != InvalidOffsetNumber) |
1252 | 0 | { |
1253 | | /* Replace the placeholder tuple */ |
1254 | 0 | PageIndexTupleDelete(page, offnum); |
1255 | |
|
1256 | 0 | offnum = PageAddItem(page, item, size, offnum, false, false); |
1257 | | |
1258 | | /* |
1259 | | * We should not have failed given the size check at the top of |
1260 | | * the function, but test anyway. If we did fail, we must PANIC |
1261 | | * because we've already deleted the placeholder tuple, and |
1262 | | * there's no other way to keep the damage from getting to disk. |
1263 | | */ |
1264 | 0 | if (offnum != InvalidOffsetNumber) |
1265 | 0 | { |
1266 | 0 | Assert(opaque->nPlaceholder > 0); |
1267 | 0 | opaque->nPlaceholder--; |
1268 | 0 | if (startOffset) |
1269 | 0 | *startOffset = offnum + 1; |
1270 | 0 | } |
1271 | 0 | else |
1272 | 0 | elog(PANIC, "failed to add item of size %zu to SPGiST index page", |
1273 | 0 | size); |
1274 | | |
1275 | 0 | return offnum; |
1276 | 0 | } |
1277 | 0 | } |
1278 | | |
1279 | | /* No luck in replacing a placeholder, so just add it to the page */ |
1280 | 0 | offnum = PageAddItem(page, item, size, |
1281 | 0 | InvalidOffsetNumber, false, false); |
1282 | |
|
1283 | 0 | if (offnum == InvalidOffsetNumber && !errorOK) |
1284 | 0 | elog(ERROR, "failed to add item of size %zu to SPGiST index page", |
1285 | 0 | size); |
1286 | | |
1287 | 0 | return offnum; |
1288 | 0 | } |
1289 | | |
1290 | | /* |
1291 | | * spgproperty() -- Check boolean properties of indexes. |
1292 | | * |
1293 | | * This is optional for most AMs, but is required for SP-GiST because the core |
1294 | | * property code doesn't support AMPROP_DISTANCE_ORDERABLE. |
1295 | | */ |
1296 | | bool |
1297 | | spgproperty(Oid index_oid, int attno, |
1298 | | IndexAMProperty prop, const char *propname, |
1299 | | bool *res, bool *isnull) |
1300 | 0 | { |
1301 | 0 | Oid opclass, |
1302 | 0 | opfamily, |
1303 | 0 | opcintype; |
1304 | 0 | CatCList *catlist; |
1305 | 0 | int i; |
1306 | | |
1307 | | /* Only answer column-level inquiries */ |
1308 | 0 | if (attno == 0) |
1309 | 0 | return false; |
1310 | | |
1311 | 0 | switch (prop) |
1312 | 0 | { |
1313 | 0 | case AMPROP_DISTANCE_ORDERABLE: |
1314 | 0 | break; |
1315 | 0 | default: |
1316 | 0 | return false; |
1317 | 0 | } |
1318 | | |
1319 | | /* |
1320 | | * Currently, SP-GiST distance-ordered scans require that there be a |
1321 | | * distance operator in the opclass with the default types. So we assume |
1322 | | * that if such an operator exists, then there's a reason for it. |
1323 | | */ |
1324 | | |
1325 | | /* First we need to know the column's opclass. */ |
1326 | 0 | opclass = get_index_column_opclass(index_oid, attno); |
1327 | 0 | if (!OidIsValid(opclass)) |
1328 | 0 | { |
1329 | 0 | *isnull = true; |
1330 | 0 | return true; |
1331 | 0 | } |
1332 | | |
1333 | | /* Now look up the opclass family and input datatype. */ |
1334 | 0 | if (!get_opclass_opfamily_and_input_type(opclass, &opfamily, &opcintype)) |
1335 | 0 | { |
1336 | 0 | *isnull = true; |
1337 | 0 | return true; |
1338 | 0 | } |
1339 | | |
1340 | | /* And now we can check whether the operator is provided. */ |
1341 | 0 | catlist = SearchSysCacheList1(AMOPSTRATEGY, |
1342 | 0 | ObjectIdGetDatum(opfamily)); |
1343 | |
|
1344 | 0 | *res = false; |
1345 | |
|
1346 | 0 | for (i = 0; i < catlist->n_members; i++) |
1347 | 0 | { |
1348 | 0 | HeapTuple amoptup = &catlist->members[i]->tuple; |
1349 | 0 | Form_pg_amop amopform = (Form_pg_amop) GETSTRUCT(amoptup); |
1350 | |
|
1351 | 0 | if (amopform->amoppurpose == AMOP_ORDER && |
1352 | 0 | (amopform->amoplefttype == opcintype || |
1353 | 0 | amopform->amoprighttype == opcintype) && |
1354 | 0 | opfamily_can_sort_type(amopform->amopsortfamily, |
1355 | 0 | get_op_rettype(amopform->amopopr))) |
1356 | 0 | { |
1357 | 0 | *res = true; |
1358 | 0 | break; |
1359 | 0 | } |
1360 | 0 | } |
1361 | |
|
1362 | 0 | ReleaseSysCacheList(catlist); |
1363 | |
|
1364 | 0 | *isnull = false; |
1365 | |
|
1366 | | return true; |
1367 | 0 | } |