Line data Source code
1 : /*
2 : * contrib/intarray/_int_gist.c
3 : */
4 : #include "postgres.h"
5 :
6 : #include <limits.h>
7 : #include <math.h>
8 :
9 : #include "_int.h"
10 : #include "access/gist.h"
11 : #include "access/reloptions.h"
12 : #include "access/stratnum.h"
13 :
14 : #define GETENTRY(vec,pos) ((ArrayType *) DatumGetPointer((vec)->vector[(pos)].key))
15 :
16 : /*
17 : * Control the maximum sparseness of compressed keys.
18 : *
19 : * The upper safe bound for this limit is half the maximum allocatable array
20 : * size. A lower bound would give more guarantees that pathological data
21 : * wouldn't eat excessive CPU and memory, but at the expense of breaking
22 : * possibly working (after a fashion) indexes.
23 : */
24 : #define MAXNUMELTS (Min((MaxAllocSize / sizeof(Datum)),((MaxAllocSize - ARR_OVERHEAD_NONULLS(1)) / sizeof(int)))/2)
25 : /* or: #define MAXNUMELTS 1000000 */
26 :
27 : /*
28 : ** GiST support methods
29 : */
30 4 : PG_FUNCTION_INFO_V1(g_int_consistent);
31 4 : PG_FUNCTION_INFO_V1(g_int_compress);
32 4 : PG_FUNCTION_INFO_V1(g_int_decompress);
33 4 : PG_FUNCTION_INFO_V1(g_int_penalty);
34 4 : PG_FUNCTION_INFO_V1(g_int_picksplit);
35 4 : PG_FUNCTION_INFO_V1(g_int_union);
36 4 : PG_FUNCTION_INFO_V1(g_int_same);
37 4 : PG_FUNCTION_INFO_V1(g_int_options);
38 :
39 :
40 : /*
41 : ** The GiST Consistent method for _intments
42 : ** Should return false if for all data items x below entry,
43 : ** the predicate x op query == false, where op is the oper
44 : ** corresponding to strategy in the pg_amop table.
45 : */
46 : Datum
47 265184 : g_int_consistent(PG_FUNCTION_ARGS)
48 : {
49 265184 : GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
50 265184 : ArrayType *query = PG_GETARG_ARRAYTYPE_P_COPY(1);
51 265184 : StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
52 : #ifdef NOT_USED
53 : Oid subtype = PG_GETARG_OID(3);
54 : #endif
55 265184 : bool *recheck = (bool *) PG_GETARG_POINTER(4);
56 265184 : bool retval = false; /* silence compiler warning */
57 :
58 : /* this is exact except for RTSameStrategyNumber */
59 265184 : *recheck = (strategy == RTSameStrategyNumber);
60 :
61 265184 : if (strategy == BooleanSearchStrategy)
62 : {
63 168502 : retval = execconsistent((QUERYTYPE *) query,
64 168502 : (ArrayType *) DatumGetPointer(entry->key),
65 168502 : GIST_LEAF(entry));
66 :
67 168502 : pfree(query);
68 168502 : PG_RETURN_BOOL(retval);
69 : }
70 :
71 : /* sort query for fast search, key is already sorted */
72 96682 : CHECKARRVALID(query);
73 96682 : PREPAREARR(query);
74 :
75 96682 : switch (strategy)
76 : {
77 32170 : case RTOverlapStrategyNumber:
78 32170 : retval = inner_int_overlap((ArrayType *) DatumGetPointer(entry->key),
79 : query);
80 32170 : break;
81 8490 : case RTSameStrategyNumber:
82 8490 : if (GIST_LEAF(entry))
83 7828 : DirectFunctionCall3(g_int_same,
84 : entry->key,
85 : PointerGetDatum(query),
86 : PointerGetDatum(&retval));
87 : else
88 662 : retval = inner_int_contains((ArrayType *) DatumGetPointer(entry->key),
89 : query);
90 8490 : break;
91 56022 : case RTContainsStrategyNumber:
92 : case RTOldContainsStrategyNumber:
93 56022 : retval = inner_int_contains((ArrayType *) DatumGetPointer(entry->key),
94 : query);
95 56022 : break;
96 0 : case RTContainedByStrategyNumber:
97 : case RTOldContainedByStrategyNumber:
98 :
99 : /*
100 : * This code is unreachable as of intarray 1.4, because the <@
101 : * operator has been removed from the opclass. We keep it for now
102 : * to support older versions of the SQL definitions.
103 : */
104 0 : if (GIST_LEAF(entry))
105 0 : retval = inner_int_contains(query,
106 0 : (ArrayType *) DatumGetPointer(entry->key));
107 : else
108 : {
109 : /*
110 : * Unfortunately, because empty arrays could be anywhere in
111 : * the index, we must search the whole tree.
112 : */
113 0 : retval = true;
114 : }
115 0 : break;
116 0 : default:
117 0 : retval = false;
118 : }
119 96682 : pfree(query);
120 96682 : PG_RETURN_BOOL(retval);
121 : }
122 :
123 : Datum
124 115282 : g_int_union(PG_FUNCTION_ARGS)
125 : {
126 115282 : GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
127 115282 : int *size = (int *) PG_GETARG_POINTER(1);
128 : int32 i,
129 : *ptr;
130 : ArrayType *res;
131 115282 : int totlen = 0;
132 :
133 347568 : for (i = 0; i < entryvec->n; i++)
134 : {
135 232286 : ArrayType *ent = GETENTRY(entryvec, i);
136 :
137 232286 : CHECKARRVALID(ent);
138 232286 : totlen += ARRNELEMS(ent);
139 : }
140 :
141 115282 : res = new_intArrayType(totlen);
142 115282 : ptr = ARRPTR(res);
143 :
144 347568 : for (i = 0; i < entryvec->n; i++)
145 : {
146 232286 : ArrayType *ent = GETENTRY(entryvec, i);
147 : int nel;
148 :
149 232286 : nel = ARRNELEMS(ent);
150 232286 : memcpy(ptr, ARRPTR(ent), nel * sizeof(int32));
151 232286 : ptr += nel;
152 : }
153 :
154 115282 : QSORT(res, 1);
155 115282 : res = _int_unique(res);
156 115282 : *size = VARSIZE(res);
157 115282 : PG_RETURN_POINTER(res);
158 : }
159 :
160 : /*
161 : ** GiST Compress and Decompress methods
162 : */
163 : Datum
164 59734 : g_int_compress(PG_FUNCTION_ARGS)
165 : {
166 59734 : GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
167 : GISTENTRY *retval;
168 : ArrayType *r;
169 59734 : int num_ranges = G_INT_GET_NUMRANGES();
170 : int len,
171 : lenr;
172 : int *dr;
173 : int i,
174 : j,
175 : cand;
176 : int64 min;
177 :
178 59734 : if (entry->leafkey)
179 : {
180 40544 : r = DatumGetArrayTypePCopy(entry->key);
181 40544 : CHECKARRVALID(r);
182 40544 : PREPAREARR(r);
183 :
184 40544 : if (ARRNELEMS(r) >= 2 * num_ranges)
185 2 : ereport(ERROR,
186 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
187 : errmsg("input array is too big (%d maximum allowed, %d current), use gist__intbig_ops opclass instead",
188 : 2 * num_ranges - 1, ARRNELEMS(r))));
189 :
190 40542 : retval = palloc_object(GISTENTRY);
191 40542 : gistentryinit(*retval, PointerGetDatum(r),
192 : entry->rel, entry->page, entry->offset, false);
193 :
194 40542 : PG_RETURN_POINTER(retval);
195 : }
196 :
197 : /*
198 : * leaf entries never compress one more time, only when entry->leafkey
199 : * ==true, so now we work only with internal keys
200 : */
201 :
202 19190 : r = DatumGetArrayTypeP(entry->key);
203 19190 : CHECKARRVALID(r);
204 19190 : if (ARRISEMPTY(r))
205 : {
206 0 : if (r != (ArrayType *) DatumGetPointer(entry->key))
207 0 : pfree(r);
208 0 : PG_RETURN_POINTER(entry);
209 : }
210 :
211 19190 : if ((len = ARRNELEMS(r)) >= 2 * num_ranges)
212 : { /* compress */
213 2852 : if (r == (ArrayType *) DatumGetPointer(entry->key))
214 2852 : r = DatumGetArrayTypePCopy(entry->key);
215 2852 : r = resize_intArrayType(r, 2 * (len));
216 :
217 2852 : dr = ARRPTR(r);
218 :
219 : /*
220 : * "len" at this point is the number of ranges we will construct.
221 : * "lenr" is the number of ranges we must eventually remove by
222 : * merging, we must be careful to remove no more than this number.
223 : */
224 2852 : lenr = len - num_ranges;
225 :
226 : /*
227 : * Initially assume we can merge consecutive ints into a range. but we
228 : * must count every value removed and stop when lenr runs out
229 : */
230 118982 : for (j = i = len - 1; i > 0 && lenr > 0; i--, j--)
231 : {
232 116130 : int r_end = dr[i];
233 116130 : int r_start = r_end;
234 :
235 1413622 : while (i > 0 && lenr > 0 && dr[i - 1] == r_start - 1)
236 1297492 : --r_start, --i, --lenr;
237 116130 : dr[2 * j] = r_start;
238 116130 : dr[2 * j + 1] = r_end;
239 : }
240 : /* just copy the rest, if any, as trivial ranges */
241 571682 : for (; i >= 0; i--, j--)
242 568830 : dr[2 * j] = dr[2 * j + 1] = dr[i];
243 :
244 2852 : if (++j)
245 : {
246 : /*
247 : * shunt everything down to start at the right place
248 : */
249 2852 : memmove(&dr[0], &dr[2 * j], 2 * (len - j) * sizeof(int32));
250 : }
251 :
252 : /*
253 : * make "len" be number of array elements, not ranges
254 : */
255 2852 : len = 2 * (len - j);
256 2852 : cand = 1;
257 2852 : while (len > num_ranges * 2)
258 : {
259 0 : min = PG_INT64_MAX;
260 0 : for (i = 2; i < len; i += 2)
261 0 : if (min > ((int64) dr[i] - (int64) dr[i - 1]))
262 : {
263 0 : min = ((int64) dr[i] - (int64) dr[i - 1]);
264 0 : cand = i;
265 : }
266 0 : memmove(&dr[cand - 1], &dr[cand + 1], (len - cand - 1) * sizeof(int32));
267 0 : len -= 2;
268 : }
269 :
270 : /*
271 : * check sparseness of result
272 : */
273 2852 : lenr = internal_size(dr, len);
274 2852 : if (lenr < 0 || lenr > MAXNUMELTS)
275 0 : ereport(ERROR,
276 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
277 : errmsg("data is too sparse, recreate index using gist__intbig_ops opclass instead")));
278 :
279 2852 : r = resize_intArrayType(r, len);
280 2852 : retval = palloc_object(GISTENTRY);
281 2852 : gistentryinit(*retval, PointerGetDatum(r),
282 : entry->rel, entry->page, entry->offset, false);
283 2852 : PG_RETURN_POINTER(retval);
284 : }
285 : else
286 16338 : PG_RETURN_POINTER(entry);
287 : }
288 :
289 : Datum
290 1327214 : g_int_decompress(PG_FUNCTION_ARGS)
291 : {
292 1327214 : GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
293 : GISTENTRY *retval;
294 : ArrayType *r;
295 1327214 : int num_ranges = G_INT_GET_NUMRANGES();
296 : int *dr,
297 : lenr;
298 : ArrayType *in;
299 : int lenin;
300 : int *din;
301 : int i;
302 :
303 1327214 : in = DatumGetArrayTypeP(entry->key);
304 :
305 1327214 : CHECKARRVALID(in);
306 1327214 : if (ARRISEMPTY(in))
307 : {
308 754 : if (in != (ArrayType *) DatumGetPointer(entry->key))
309 : {
310 754 : retval = palloc_object(GISTENTRY);
311 754 : gistentryinit(*retval, PointerGetDatum(in),
312 : entry->rel, entry->page, entry->offset, false);
313 754 : PG_RETURN_POINTER(retval);
314 : }
315 :
316 0 : PG_RETURN_POINTER(entry);
317 : }
318 :
319 1326460 : lenin = ARRNELEMS(in);
320 :
321 1326460 : if (lenin < 2 * num_ranges)
322 : { /* not compressed value */
323 1268494 : if (in != (ArrayType *) DatumGetPointer(entry->key))
324 : {
325 546808 : retval = palloc_object(GISTENTRY);
326 546808 : gistentryinit(*retval, PointerGetDatum(in),
327 : entry->rel, entry->page, entry->offset, false);
328 :
329 546808 : PG_RETURN_POINTER(retval);
330 : }
331 721686 : PG_RETURN_POINTER(entry);
332 : }
333 :
334 57966 : din = ARRPTR(in);
335 57966 : lenr = internal_size(din, lenin);
336 57966 : if (lenr < 0 || lenr > MAXNUMELTS)
337 0 : ereport(ERROR,
338 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
339 : errmsg("compressed array is too big, recreate index using gist__intbig_ops opclass instead")));
340 :
341 57966 : r = new_intArrayType(lenr);
342 57966 : dr = ARRPTR(r);
343 :
344 13707798 : for (i = 0; i < lenin; i += 2)
345 : {
346 : /* use int64 for j in case din[i + 1] is INT_MAX */
347 54952440 : for (int64 j = din[i]; j <= din[i + 1]; j++)
348 41302608 : if ((!i) || *(dr - 1) != j)
349 41302608 : *dr++ = (int) j;
350 : }
351 :
352 57966 : if (in != (ArrayType *) DatumGetPointer(entry->key))
353 57966 : pfree(in);
354 57966 : retval = palloc_object(GISTENTRY);
355 57966 : gistentryinit(*retval, PointerGetDatum(r),
356 : entry->rel, entry->page, entry->offset, false);
357 :
358 57966 : PG_RETURN_POINTER(retval);
359 : }
360 :
361 : /*
362 : ** The GiST Penalty method for _intments
363 : */
364 : Datum
365 641452 : g_int_penalty(PG_FUNCTION_ARGS)
366 : {
367 641452 : GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0);
368 641452 : GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1);
369 641452 : float *result = (float *) PG_GETARG_POINTER(2);
370 : ArrayType *ud;
371 : float tmp1,
372 : tmp2;
373 :
374 641452 : ud = inner_int_union((ArrayType *) DatumGetPointer(origentry->key),
375 641452 : (ArrayType *) DatumGetPointer(newentry->key));
376 641452 : rt__int_size(ud, &tmp1);
377 641452 : rt__int_size((ArrayType *) DatumGetPointer(origentry->key), &tmp2);
378 641452 : *result = tmp1 - tmp2;
379 641452 : pfree(ud);
380 :
381 641452 : PG_RETURN_POINTER(result);
382 : }
383 :
384 :
385 :
386 : Datum
387 123044 : g_int_same(PG_FUNCTION_ARGS)
388 : {
389 123044 : ArrayType *a = PG_GETARG_ARRAYTYPE_P(0);
390 123044 : ArrayType *b = PG_GETARG_ARRAYTYPE_P(1);
391 123044 : bool *result = (bool *) PG_GETARG_POINTER(2);
392 123044 : int32 n = ARRNELEMS(a);
393 : int32 *da,
394 : *db;
395 :
396 123044 : CHECKARRVALID(a);
397 123044 : CHECKARRVALID(b);
398 :
399 123044 : if (n != ARRNELEMS(b))
400 : {
401 23062 : *result = false;
402 23062 : PG_RETURN_POINTER(result);
403 : }
404 99982 : *result = true;
405 99982 : da = ARRPTR(a);
406 99982 : db = ARRPTR(b);
407 30791446 : while (n--)
408 : {
409 30692698 : if (*da++ != *db++)
410 : {
411 1234 : *result = false;
412 1234 : break;
413 : }
414 : }
415 :
416 99982 : PG_RETURN_POINTER(result);
417 : }
418 :
419 : /*****************************************************************
420 : ** Common GiST Method
421 : *****************************************************************/
422 :
423 : typedef struct
424 : {
425 : OffsetNumber pos;
426 : float cost;
427 : } SPLITCOST;
428 :
429 : static int
430 130330 : comparecost(const void *a, const void *b)
431 : {
432 130330 : if (((const SPLITCOST *) a)->cost == ((const SPLITCOST *) b)->cost)
433 67206 : return 0;
434 : else
435 63124 : return (((const SPLITCOST *) a)->cost > ((const SPLITCOST *) b)->cost) ? 1 : -1;
436 : }
437 :
438 : /*
439 : ** The GiST PickSplit method for _intments
440 : ** We use Guttman's poly time split algorithm
441 : */
442 : Datum
443 1344 : g_int_picksplit(PG_FUNCTION_ARGS)
444 : {
445 1344 : GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
446 1344 : GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1);
447 : OffsetNumber i,
448 : j;
449 : ArrayType *datum_alpha,
450 : *datum_beta;
451 : ArrayType *datum_l,
452 : *datum_r;
453 : ArrayType *union_d,
454 : *union_dl,
455 : *union_dr;
456 : ArrayType *inter_d;
457 : bool firsttime;
458 : float size_alpha,
459 : size_beta,
460 : size_union,
461 : size_inter;
462 : float size_waste,
463 : waste;
464 : float size_l,
465 : size_r;
466 : int nbytes;
467 1344 : OffsetNumber seed_1 = 0,
468 1344 : seed_2 = 0;
469 : OffsetNumber *left,
470 : *right;
471 : OffsetNumber maxoff;
472 : SPLITCOST *costvector;
473 :
474 : #ifdef GIST_DEBUG
475 : elog(DEBUG3, "--------picksplit %d", entryvec->n);
476 : #endif
477 :
478 1344 : maxoff = entryvec->n - 2;
479 1344 : nbytes = (maxoff + 2) * sizeof(OffsetNumber);
480 1344 : v->spl_left = (OffsetNumber *) palloc(nbytes);
481 1344 : v->spl_right = (OffsetNumber *) palloc(nbytes);
482 :
483 1344 : firsttime = true;
484 1344 : waste = 0.0;
485 66928 : for (i = FirstOffsetNumber; i < maxoff; i = OffsetNumberNext(i))
486 : {
487 65584 : datum_alpha = GETENTRY(entryvec, i);
488 3649952 : for (j = OffsetNumberNext(i); j <= maxoff; j = OffsetNumberNext(j))
489 : {
490 3584368 : datum_beta = GETENTRY(entryvec, j);
491 :
492 : /* compute the wasted space by unioning these guys */
493 : /* size_waste = size_union - size_inter; */
494 3584368 : union_d = inner_int_union(datum_alpha, datum_beta);
495 3584368 : rt__int_size(union_d, &size_union);
496 3584368 : inter_d = inner_int_inter(datum_alpha, datum_beta);
497 3584368 : rt__int_size(inter_d, &size_inter);
498 3584368 : size_waste = size_union - size_inter;
499 :
500 3584368 : pfree(union_d);
501 3584368 : pfree(inter_d);
502 :
503 : /*
504 : * are these a more promising split that what we've already seen?
505 : */
506 :
507 3584368 : if (size_waste > waste || firsttime)
508 : {
509 6590 : waste = size_waste;
510 6590 : seed_1 = i;
511 6590 : seed_2 = j;
512 6590 : firsttime = false;
513 : }
514 : }
515 : }
516 :
517 1344 : left = v->spl_left;
518 1344 : v->spl_nleft = 0;
519 1344 : right = v->spl_right;
520 1344 : v->spl_nright = 0;
521 1344 : if (seed_1 == 0 || seed_2 == 0)
522 : {
523 0 : seed_1 = 1;
524 0 : seed_2 = 2;
525 : }
526 :
527 1344 : datum_alpha = GETENTRY(entryvec, seed_1);
528 1344 : datum_l = copy_intArrayType(datum_alpha);
529 1344 : rt__int_size(datum_l, &size_l);
530 1344 : datum_beta = GETENTRY(entryvec, seed_2);
531 1344 : datum_r = copy_intArrayType(datum_beta);
532 1344 : rt__int_size(datum_r, &size_r);
533 :
534 1344 : maxoff = OffsetNumberNext(maxoff);
535 :
536 : /*
537 : * sort entries
538 : */
539 1344 : costvector = palloc_array(SPLITCOST, maxoff);
540 69616 : for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
541 : {
542 68272 : costvector[i - 1].pos = i;
543 68272 : datum_alpha = GETENTRY(entryvec, i);
544 68272 : union_d = inner_int_union(datum_l, datum_alpha);
545 68272 : rt__int_size(union_d, &size_alpha);
546 68272 : pfree(union_d);
547 68272 : union_d = inner_int_union(datum_r, datum_alpha);
548 68272 : rt__int_size(union_d, &size_beta);
549 68272 : pfree(union_d);
550 68272 : costvector[i - 1].cost = fabsf((size_alpha - size_l) - (size_beta - size_r));
551 : }
552 1344 : qsort(costvector, maxoff, sizeof(SPLITCOST), comparecost);
553 :
554 : /*
555 : * Now split up the regions between the two seeds. An important property
556 : * of this split algorithm is that the split vector v has the indices of
557 : * items to be split in order in its left and right vectors. We exploit
558 : * this property by doing a merge in the code that actually splits the
559 : * page.
560 : *
561 : * For efficiency, we also place the new index tuple in this loop. This is
562 : * handled at the very end, when we have placed all the existing tuples
563 : * and i == maxoff + 1.
564 : */
565 :
566 :
567 69616 : for (j = 0; j < maxoff; j++)
568 : {
569 68272 : i = costvector[j].pos;
570 :
571 : /*
572 : * If we've already decided where to place this item, just put it on
573 : * the right list. Otherwise, we need to figure out which page needs
574 : * the least enlargement in order to store the item.
575 : */
576 :
577 68272 : if (i == seed_1)
578 : {
579 1344 : *left++ = i;
580 1344 : v->spl_nleft++;
581 1344 : continue;
582 : }
583 66928 : else if (i == seed_2)
584 : {
585 1344 : *right++ = i;
586 1344 : v->spl_nright++;
587 1344 : continue;
588 : }
589 :
590 : /* okay, which page needs least enlargement? */
591 65584 : datum_alpha = GETENTRY(entryvec, i);
592 65584 : union_dl = inner_int_union(datum_l, datum_alpha);
593 65584 : union_dr = inner_int_union(datum_r, datum_alpha);
594 65584 : rt__int_size(union_dl, &size_alpha);
595 65584 : rt__int_size(union_dr, &size_beta);
596 :
597 : /* pick which page to add it to */
598 65584 : if (size_alpha - size_l < size_beta - size_r + WISH_F(v->spl_nleft, v->spl_nright, 0.01))
599 : {
600 31136 : pfree(datum_l);
601 31136 : pfree(union_dr);
602 31136 : datum_l = union_dl;
603 31136 : size_l = size_alpha;
604 31136 : *left++ = i;
605 31136 : v->spl_nleft++;
606 : }
607 : else
608 : {
609 34448 : pfree(datum_r);
610 34448 : pfree(union_dl);
611 34448 : datum_r = union_dr;
612 34448 : size_r = size_beta;
613 34448 : *right++ = i;
614 34448 : v->spl_nright++;
615 : }
616 : }
617 1344 : pfree(costvector);
618 1344 : *right = *left = FirstOffsetNumber;
619 :
620 1344 : v->spl_ldatum = PointerGetDatum(datum_l);
621 1344 : v->spl_rdatum = PointerGetDatum(datum_r);
622 :
623 1344 : PG_RETURN_POINTER(v);
624 : }
625 :
626 : Datum
627 26 : g_int_options(PG_FUNCTION_ARGS)
628 : {
629 26 : local_relopts *relopts = (local_relopts *) PG_GETARG_POINTER(0);
630 :
631 26 : init_local_reloptions(relopts, sizeof(GISTIntArrayOptions));
632 26 : add_local_int_reloption(relopts, "numranges",
633 : "number of ranges for compression",
634 : G_INT_NUMRANGES_DEFAULT, 1, G_INT_NUMRANGES_MAX,
635 : offsetof(GISTIntArrayOptions, num_ranges));
636 :
637 26 : PG_RETURN_VOID();
638 : }
|