PostGIS 3.6.2dev-r@@SVN_REVISION@@
Loading...
Searching...
No Matches
gserialized_estimate.c
Go to the documentation of this file.
1/**********************************************************************
2 *
3 * PostGIS - Spatial Types for PostgreSQL
4 * http://postgis.net
5 *
6 * PostGIS is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * PostGIS is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with PostGIS. If not, see <http://www.gnu.org/licenses/>.
18 *
19 **********************************************************************
20 *
21 * Copyright 2012 (C) Paul Ramsey <pramsey@cleverelephant.ca>
22 * Copyright 2025 (C) Darafei Praliaskouski <me@komzpa.net>
23 *
24 **********************************************************************/
25
26/**********************************************************************
27 THEORY OF OPERATION
28
29The ANALYZE command hooks to a callback (gserialized_analyze_nd) that
30calculates (compute_gserialized_stats_mode) two histograms of occurrences of
31features, once for the 2D domain (and the && operator) one for the
32ND domain (and the &&& operator).
33
34Queries in PostgreSQL call into the selectivity sub-system to find out
35the relative effectiveness of different clauses in sub-setting
36relations. Queries with constant arguments call gserialized_gist_sel,
37queries with relations on both sides call gserialized_gist_joinsel.
38
39gserialized_gist_sel sums up the values in the histogram that overlap
40the constant search box.
41
42gserialized_gist_joinsel sums up the product of the overlapping
43cells in each relation's histogram.
44
45Depending on the operator and type, the mode of selectivity calculation
46will be 2D or ND.
47
48- geometry && geometry ==> 2D
49- geometry &&& geometry ==> ND
50- geography && geography ==> ND
51
52The 2D mode is put in effect by retrieving the 2D histogram from the
53statistics cache and then allowing the generic ND calculations to
54go to work.
55
56TO DO: More testing and examination of the &&& operator and mixed
57dimensionality cases. (2D geometry) &&& (3D column), etc.
58
59**********************************************************************/
60
61#include "postgres.h"
62
63#include "access/genam.h"
64#include "access/gin.h"
65#include "access/gist.h"
66#include "access/gist_private.h"
67#include "access/gistscan.h"
68#if PG_VERSION_NUM < 130000
69#include "access/tuptoaster.h" /* For toast_raw_datum_size */
70#else
71#include "access/detoast.h" /* For toast_raw_datum_size */
72#endif
73#include "utils/datum.h"
74#include "access/heapam.h"
75#include "catalog/index.h"
76#include "catalog/pg_am.h"
77#include "miscadmin.h"
78#include "storage/lmgr.h"
79#include "catalog/namespace.h"
80#include "catalog/indexing.h"
81
82#include "utils/regproc.h"
83#include "utils/varlena.h"
84
85#include "utils/builtins.h"
86#include "utils/datum.h"
87#include "utils/snapmgr.h"
88#include "utils/fmgroids.h"
89#include "funcapi.h"
90#include "access/heapam.h"
91#include "catalog/pg_type.h"
92#include "access/relscan.h"
93
94#include "executor/spi.h"
95#include "fmgr.h"
96#include "commands/vacuum.h"
97#include "nodes/pathnodes.h"
98
99#include "parser/parsetree.h"
100#include "utils/array.h"
101#include "utils/lsyscache.h"
102#include "utils/builtins.h"
103#include "utils/syscache.h"
104#include "utils/rel.h"
105#include "utils/selfuncs.h"
106
107#include "../postgis_config.h"
108
109#include "access/htup_details.h"
110
111#include "stringbuffer.h"
112#include "liblwgeom.h"
113#include "lwgeodetic.h"
114#include "lwgeom_pg.h" /* For debugging macros. */
115#include "gserialized_gist.h" /* For index common functions */
117
118#include <math.h>
119#include <limits.h>
120#if HAVE_IEEEFP_H
121#include <ieeefp.h>
122#endif
123#include <float.h>
124#include <string.h>
125#include <stdio.h>
126#include <ctype.h>
127
128
129/************************************************************************/
130
131
132/* Prototypes */
133Datum gserialized_gist_joinsel(PG_FUNCTION_ARGS);
134Datum gserialized_gist_joinsel_2d(PG_FUNCTION_ARGS);
135Datum gserialized_gist_joinsel_nd(PG_FUNCTION_ARGS);
136Datum gserialized_gist_sel(PG_FUNCTION_ARGS);
137Datum gserialized_gist_sel_2d(PG_FUNCTION_ARGS);
138Datum gserialized_gist_sel_nd(PG_FUNCTION_ARGS);
139Datum gserialized_analyze_nd(PG_FUNCTION_ARGS);
140Datum gserialized_estimated_extent(PG_FUNCTION_ARGS);
141Datum _postgis_gserialized_index_extent(PG_FUNCTION_ARGS);
142Datum _postgis_gserialized_sel(PG_FUNCTION_ARGS);
143Datum _postgis_gserialized_joinsel(PG_FUNCTION_ARGS);
144Datum _postgis_gserialized_stats(PG_FUNCTION_ARGS);
145
146/* Local prototypes */
147static Oid table_get_spatial_index(Oid tbl_oid, int16 attnum, int *key_type, int16 *idx_attnum);
148static GBOX *spatial_index_read_extent(Oid idx_oid, int idx_att_num, int key_type);
149
150/* Other prototypes */
151float8 gserialized_joinsel_internal(PlannerInfo *root, List *args, JoinType jointype, int mode);
152float8 gserialized_sel_internal(PlannerInfo *root, List *args, int varRelid, int mode);
153
154/* Old Prototype */
155Datum geometry_estimated_extent(PG_FUNCTION_ARGS);
156
157/*
158 * Assign a number to the n-dimensional statistics kind
159 *
160 * tgl suggested:
161 *
162 * 1-100: reserved for assignment by the core Postgres project
163 * 100-199: reserved for assignment by PostGIS
164 * 200-9999: reserved for other globally-known stats kinds
165 * 10000-32767: reserved for private site-local use
166 */
167#define STATISTIC_KIND_ND 102
168#define STATISTIC_KIND_2D 103
169
170/*
171 * Postgres does not pin its slots and uses them as they come.
172 * We need to preserve its Correlation for brin to work
173 * 0 may be MCV
174 * 1 may be Histogram
175 * 2 may be Correlation
176 * We take 3 and 4.
177 */
178#define STATISTIC_SLOT_ND 3
179#define STATISTIC_SLOT_2D 4
180
181/*
182* The SD factor restricts the side of the statistics histogram
183* based on the standard deviation of the extent of the data.
184* SDFACTOR is the number of standard deviations from the mean
185* the histogram will extend.
186*/
187#define SDFACTOR 3.25
188
195#define MIN_DIMENSION_WIDTH 0.000000001
196
201#define MAX_DIMENSION_WIDTH 1.0E+20
202
206#define DEFAULT_ND_SEL 0.0001
207#define DEFAULT_ND_JOINSEL 0.001
208
212#define FALLBACK_ND_SEL 0.2
213#define FALLBACK_ND_JOINSEL 0.3
214
215typedef struct {
216 /* Saved state from std_typanalyze() */
217 AnalyzeAttrComputeStatsFunc std_compute_stats;
220
227static int
228gbox_ndims(const GBOX* gbox)
229{
230 int dims = 2;
231 if ( FLAGS_GET_GEODETIC(gbox->flags) )
232 return 3;
233 if ( FLAGS_GET_Z(gbox->flags) )
234 dims++;
235 if ( FLAGS_GET_M(gbox->flags) )
236 dims++;
237 return dims;
238}
239
245static int
246text_p_get_mode(const text *txt)
247{
248 int mode = 2;
249 char *modestr;
250 if (VARSIZE_ANY_EXHDR(txt) <= 0)
251 return mode;
252 modestr = (char *)VARDATA(txt);
253 if (modestr[0] == 'N')
254 mode = 0;
255 return mode;
256}
257
261static int
262cmp_int (const void *a, const void *b)
263{
264 int ia = *((const int*)a);
265 int ib = *((const int*)b);
266
267 if ( ia == ib )
268 return 0;
269 else if ( ia > ib )
270 return 1;
271 else
272 return -1;
273}
274
279// static int
280// range_quintile(int *vals, int nvals)
281// {
282// qsort(vals, nvals, sizeof(int), cmp_int);
283// return vals[4*nvals/5] - vals[nvals/5];
284// }
285
289static int
290range_full(int *vals, int nvals)
291{
292 qsort(vals, nvals, sizeof(int), cmp_int);
293 return vals[nvals-1] - vals[0];
294}
295
299static double
300total_double(const double *vals, int nvals)
301{
302 int i;
303 float total = 0;
304 /* Calculate total */
305 for (i = 0; i < nvals; i++)
306 total += vals[i];
307
308 return total;
309}
310
311#if POSTGIS_DEBUG_LEVEL >= 3
312
316static int
317total_int(const int *vals, int nvals)
318{
319 int i;
320 int total = 0;
321 /* Calculate total */
322 for ( i = 0; i < nvals; i++ )
323 total += vals[i];
324
325 return total;
326}
327
331static double
332avg(const int *vals, int nvals)
333{
334 int t = total_int(vals, nvals);
335 return (double)t / (double)nvals;
336}
337
341static double
342stddev(const int *vals, int nvals)
343{
344 int i;
345 double sigma2 = 0;
346 double mean = avg(vals, nvals);
347
348 /* Calculate sigma2 */
349 for ( i = 0; i < nvals; i++ )
350 {
351 double v = (double)(vals[i]);
352 sigma2 += (mean - v) * (mean - v);
353 }
354 return sqrt(sigma2 / nvals);
355}
356#endif /* POSTGIS_DEBUG_LEVEL >= 3 */
357
361static char*
362nd_box_to_json(const ND_BOX *nd_box, int ndims)
363{
364 char *rv;
365 int i;
367
368 stringbuffer_append(sb, "{\"min\":[");
369 for ( i = 0; i < ndims; i++ )
370 {
371 if ( i ) stringbuffer_append(sb, ",");
372 stringbuffer_aprintf(sb, "%.6g", nd_box->min[i]);
373 }
374 stringbuffer_append(sb, "],\"max\":[");
375 for ( i = 0; i < ndims; i++ )
376 {
377 if ( i ) stringbuffer_append(sb, ",");
378 stringbuffer_aprintf(sb, "%.6g", nd_box->max[i]);
379 }
380 stringbuffer_append(sb, "]}");
381
384 return rv;
385}
386
387
392static char*
394{
395 char *json_extent, *str;
396 int d;
398 int ndims = (int)roundf(nd_stats->ndims);
399
400 stringbuffer_append(sb, "{");
401 stringbuffer_aprintf(sb, "\"ndims\":%d,", ndims);
402
403 /* Size */
404 stringbuffer_append(sb, "\"size\":[");
405 for ( d = 0; d < ndims; d++ )
406 {
407 if ( d ) stringbuffer_append(sb, ",");
408 stringbuffer_aprintf(sb, "%d", (int)roundf(nd_stats->size[d]));
409 }
410 stringbuffer_append(sb, "],");
411
412 /* Extent */
413 json_extent = nd_box_to_json(&(nd_stats->extent), ndims);
414 stringbuffer_aprintf(sb, "\"extent\":%s,", json_extent);
415 pfree(json_extent);
416
417 stringbuffer_aprintf(sb, "\"table_features\":%d,", (int)roundf(nd_stats->table_features));
418 stringbuffer_aprintf(sb, "\"sample_features\":%d,", (int)roundf(nd_stats->sample_features));
419 stringbuffer_aprintf(sb, "\"not_null_features\":%d,", (int)roundf(nd_stats->not_null_features));
420 stringbuffer_aprintf(sb, "\"histogram_features\":%d,", (int)roundf(nd_stats->histogram_features));
421 stringbuffer_aprintf(sb, "\"histogram_cells\":%d,", (int)roundf(nd_stats->histogram_cells));
422 stringbuffer_aprintf(sb, "\"cells_covered\":%d", (int)roundf(nd_stats->cells_covered));
423 stringbuffer_append(sb, "}");
424
427 return str;
428}
429
430
436static char*
438{
439 char *rv;
440 int j, k;
441 int sizex = (int)roundf(stats->size[0]);
442 int sizey = (int)roundf(stats->size[1]);
444
445 for ( k = 0; k < sizey; k++ )
446 {
447 for ( j = 0; j < sizex; j++ )
448 {
449 stringbuffer_aprintf(sb, "%3d ", (int)roundf(stats->value[j + k*sizex]));
450 }
451 stringbuffer_append(sb, "\n");
452 }
453
456 return rv;
457}
458
459
461static int
462nd_box_merge(const ND_BOX *source, ND_BOX *target)
463{
464 int d;
465 for ( d = 0; d < ND_DIMS; d++ )
466 {
467 target->min[d] = Min(target->min[d], source->min[d]);
468 target->max[d] = Max(target->max[d], source->max[d]);
469 }
470 return true;
471}
472
474static int
476{
477 memset(a, 0, sizeof(ND_BOX));
478 return true;
479}
480
486static int
488{
489 int d;
490 for ( d = 0; d < ND_DIMS; d++ )
491 {
492 a->min[d] = FLT_MAX;
493 a->max[d] = -1 * FLT_MAX;
494 }
495 return true;
496}
497
499static void
500nd_box_from_gbox(const GBOX *gbox, ND_BOX *nd_box)
501{
502 volatile int d = 0;
503 POSTGIS_DEBUGF(3, " %s", gbox_to_string(gbox));
504
505 nd_box_init(nd_box);
506 nd_box->min[d] = gbox->xmin;
507 nd_box->max[d] = gbox->xmax;
508 d++;
509 nd_box->min[d] = gbox->ymin;
510 nd_box->max[d] = gbox->ymax;
511 d++;
512 if ( FLAGS_GET_GEODETIC(gbox->flags) )
513 {
514 nd_box->min[d] = gbox->zmin;
515 nd_box->max[d] = gbox->zmax;
516 return;
517 }
518 if ( FLAGS_GET_Z(gbox->flags) )
519 {
520 nd_box->min[d] = gbox->zmin;
521 nd_box->max[d] = gbox->zmax;
522 d++;
523 }
524 if ( FLAGS_GET_M(gbox->flags) )
525 {
526 nd_box->min[d] = gbox->mmin;
527 nd_box->max[d] = gbox->mmax;
528 d++;
529 }
530 return;
531}
532
536static int
537nd_box_intersects(const ND_BOX *a, const ND_BOX *b, int ndims)
538{
539 int d;
540 for ( d = 0; d < ndims; d++ )
541 {
542 if ( (a->min[d] > b->max[d]) || (a->max[d] < b->min[d]) )
543 return false;
544 }
545 return true;
546}
547
551static int
552nd_box_contains(const ND_BOX *a, const ND_BOX *b, int ndims)
553{
554 int d;
555 for ( d = 0; d < ndims; d++ )
556 {
557 if ( ! ((a->min[d] < b->min[d]) && (a->max[d] > b->max[d])) )
558 return false;
559 }
560 return true;
561}
562
567static int
568nd_box_expand(ND_BOX *nd_box, double expansion_factor)
569{
570 int d;
571 double size;
572 for ( d = 0; d < ND_DIMS; d++ )
573 {
574 size = nd_box->max[d] - nd_box->min[d];
575 /* Avoid expanding boxes that are either too wide or too narrow*/
576 if (size < MIN_DIMENSION_WIDTH || size > MAX_DIMENSION_WIDTH)
577 continue;
578 nd_box->min[d] -= size * expansion_factor / 2;
579 nd_box->max[d] += size * expansion_factor / 2;
580 }
581 return true;
582}
583
588static inline int
589nd_box_overlap(const ND_STATS *nd_stats, const ND_BOX *nd_box, ND_IBOX *nd_ibox)
590{
591 int d;
592
593 POSTGIS_DEBUGF(4, " nd_box: %s", nd_box_to_json(nd_box, nd_stats->ndims));
594
595 /* Initialize ibox */
596 memset(nd_ibox, 0, sizeof(ND_IBOX));
597
598 /* In each dimension... */
599 for ( d = 0; d < nd_stats->ndims; d++ )
600 {
601 double smin = nd_stats->extent.min[d];
602 double smax = nd_stats->extent.max[d];
603 double width = smax - smin;
604
605 if (width < MIN_DIMENSION_WIDTH)
606 {
607 nd_ibox->min[d] = nd_ibox->max[d] = nd_stats->extent.min[d];
608 }
609 else
610 {
611 int size = (int)roundf(nd_stats->size[d]);
612
613 /* ... find cells the box overlaps with in this dimension */
614 nd_ibox->min[d] = floor(size * (nd_box->min[d] - smin) / width);
615 nd_ibox->max[d] = floor(size * (nd_box->max[d] - smin) / width);
616
617 POSTGIS_DEBUGF(5, " stats: dim %d: min %g: max %g: width %g", d, smin, smax, width);
618 POSTGIS_DEBUGF(5, " overlap: dim %d: (%d, %d)", d, nd_ibox->min[d], nd_ibox->max[d]);
619
620 /* Push any out-of range values into range */
621 nd_ibox->min[d] = Max(nd_ibox->min[d], 0);
622 nd_ibox->max[d] = Min(nd_ibox->max[d], size - 1);
623 }
624 }
625 return true;
626}
627
628/* How many bins shall we use in figuring out the distribution? */
629#define MAX_NUM_BINS 50
630#define BIN_MIN_SIZE 10
631
647static int
648nd_box_array_distribution(const ND_BOX **nd_boxes, int num_boxes, const ND_BOX *extent, int ndims, double *distribution)
649{
650 int d, i, k, range;
651 int *counts;
652 double smin, smax; /* Spatial min, spatial max */
653 double swidth; /* Spatial width of dimension */
654#if POSTGIS_DEBUG_LEVEL >= 3
655 double average, sdev, sdev_ratio;
656#endif
657 int bmin, bmax; /* Bin min, bin max */
658 const ND_BOX *ndb;
659
660 int num_bins = Min(Max(2, num_boxes/BIN_MIN_SIZE), MAX_NUM_BINS);
661 counts = palloc0(num_bins * sizeof(int));
662
663 /* For each dimension... */
664 for ( d = 0; d < ndims; d++ )
665 {
666 /* Initialize counts for this dimension */
667 memset(counts, 0, num_bins * sizeof(int));
668
669
670 smin = extent->min[d];
671 smax = extent->max[d];
672 swidth = smax - smin;
673
674 /* Don't try and calculate distribution of overly narrow */
675 /* or overly wide dimensions. Here we're being pretty geographical, */
676 /* expecting "normal" planar or geographic coordinates. */
677 /* Otherwise we have to "handle" +/- Inf bounded features and */
678 /* the assumptions needed for that are as bad as this hack. */
679 if ( swidth < MIN_DIMENSION_WIDTH || swidth > MAX_DIMENSION_WIDTH )
680 {
681 distribution[d] = 0;
682 continue;
683 }
684
685 /* Sum up the overlaps of each feature with the dimensional bins */
686 for ( i = 0; i < num_boxes; i++ )
687 {
688 double minoffset, maxoffset;
689
690 /* Skip null entries */
691 ndb = nd_boxes[i];
692 if ( ! ndb ) continue;
693
694 /* Where does box fall relative to the working range */
695 minoffset = ndb->min[d] - smin;
696 maxoffset = ndb->max[d] - smin;
697
698 /* Skip boxes that our outside our working range */
699 if ( minoffset < 0 || minoffset > swidth ||
700 maxoffset < 0 || maxoffset > swidth )
701 {
702 continue;
703 }
704
705 /* What bins does this range correspond to? */
706 bmin = floor(num_bins * minoffset / swidth);
707 bmax = floor(num_bins * maxoffset / swidth);
708
709 /* Should only happen when maxoffset==swidth */
710 if (bmax >= num_bins)
711 bmax = num_bins-1;
712
713 POSTGIS_DEBUGF(4, " dimension %d, feature %d: bin %d to bin %d", d, i, bmin, bmax);
714
715 /* Increment the counts in all the bins this feature overlaps */
716 for ( k = bmin; k <= bmax; k++ )
717 {
718 counts[k] += 1;
719 }
720
721 }
722
723 /* How dispersed is the distribution of features across bins? */
724 // range = range_quintile(counts, num_bins);
725 range = range_full(counts, num_bins);
726
727#if POSTGIS_DEBUG_LEVEL >= 3
728 average = avg(counts, num_bins);
729 sdev = stddev(counts, num_bins);
730 sdev_ratio = sdev/average;
731
732 POSTGIS_DEBUGF(3, " dimension %d: range = %d", d, range);
733 POSTGIS_DEBUGF(3, " dimension %d: average = %.6g", d, average);
734 POSTGIS_DEBUGF(3, " dimension %d: stddev = %.6g", d, sdev);
735 POSTGIS_DEBUGF(3, " dimension %d: stddev_ratio = %.6g", d, sdev_ratio);
736#endif
737
738 distribution[d] = range;
739 }
740
741 pfree(counts);
742
743 return true;
744}
745
751static inline int
752nd_increment(ND_IBOX *ibox, int ndims, int *counter)
753{
754 int d = 0;
755
756 while (d < ndims)
757 {
758 if (counter[d] < ibox->max[d])
759 {
760 counter[d] += 1;
761 break;
762 }
763 counter[d] = ibox->min[d];
764 d++;
765 }
766 /* That's it, cannot increment any more! */
767 if (d == ndims)
768 return false;
769
770 /* Increment complete! */
771 return true;
772}
773
774static ND_STATS*
775pg_nd_stats_from_tuple(HeapTuple stats_tuple, int mode)
776{
777 int stats_kind = STATISTIC_KIND_ND;
778 int rv;
779 ND_STATS *nd_stats;
780
781 /* If we're in 2D mode, set the kind appropriately */
782 if ( mode == 2 ) stats_kind = STATISTIC_KIND_2D;
783
784 /* Then read the geom status histogram from that */
785 {
786 AttStatsSlot sslot;
787 rv = get_attstatsslot(&sslot, stats_tuple, stats_kind, InvalidOid,
788 ATTSTATSSLOT_NUMBERS);
789 if ( ! rv ) {
790 POSTGIS_DEBUGF(2, "no slot of kind %d in stats tuple", stats_kind);
791 return NULL;
792 }
793
794 /* Clone the stats here so we can release the attstatsslot immediately */
795 nd_stats = palloc(sizeof(float4) * sslot.nnumbers);
796 memcpy(nd_stats, sslot.numbers, sizeof(float4) * sslot.nnumbers);
797
798 free_attstatsslot(&sslot);
799 }
800 return nd_stats;
801}
802
807static ND_STATS*
808pg_get_nd_stats(const Oid table_oid, AttrNumber att_num, int mode, bool only_parent)
809{
810 HeapTuple stats_tuple = NULL;
811 ND_STATS *nd_stats;
812
813 /* First pull the stats tuple for the whole tree */
814 if ( ! only_parent )
815 {
816 POSTGIS_DEBUGF(2, "searching whole tree stats for \"%s\"", get_rel_name(table_oid)? get_rel_name(table_oid) : "NULL");
817 stats_tuple = SearchSysCache3(STATRELATTINH, ObjectIdGetDatum(table_oid), Int16GetDatum(att_num), BoolGetDatum(true));
818 if ( stats_tuple )
819 POSTGIS_DEBUGF(2, "found whole tree stats for \"%s\"", get_rel_name(table_oid)? get_rel_name(table_oid) : "NULL");
820 }
821 /* Fall-back to main table stats only, if not found for whole tree or explicitly ignored */
822 if ( only_parent || ! stats_tuple )
823 {
824 POSTGIS_DEBUGF(2, "searching parent table stats for \"%s\"", get_rel_name(table_oid)? get_rel_name(table_oid) : "NULL");
825 stats_tuple = SearchSysCache3(STATRELATTINH, ObjectIdGetDatum(table_oid), Int16GetDatum(att_num), BoolGetDatum(false));
826 if ( stats_tuple )
827 POSTGIS_DEBUGF(2, "found parent table stats for \"%s\"", get_rel_name(table_oid)? get_rel_name(table_oid) : "NULL");
828 }
829 if ( ! stats_tuple )
830 {
831 POSTGIS_DEBUGF(2, "stats for \"%s\" do not exist", get_rel_name(table_oid)? get_rel_name(table_oid) : "NULL");
832 return NULL;
833 }
834
835 nd_stats = pg_nd_stats_from_tuple(stats_tuple, mode);
836 ReleaseSysCache(stats_tuple);
837 if ( ! nd_stats )
838 {
839 POSTGIS_DEBUGF(2,
840 "histogram for attribute %d of table \"%s\" does not exist?",
841 att_num, get_rel_name(table_oid));
842 }
843
844 return nd_stats;
845}
846
855static ND_STATS*
856pg_get_nd_stats_by_name(const Oid table_oid, const text *att_text, int mode, bool only_parent)
857{
858 const char *att_name = text_to_cstring(att_text);
859 AttrNumber att_num;
860
861 /* We know the name? Look up the num */
862 if ( att_text )
863 {
864 /* Get the attribute number */
865 att_num = get_attnum(table_oid, att_name);
866 if ( ! att_num ) {
867 elog(ERROR, "attribute \"%s\" does not exist", att_name);
868 return NULL;
869 }
870 }
871 else
872 {
873 elog(ERROR, "attribute name is null");
874 return NULL;
875 }
876
877 return pg_get_nd_stats(table_oid, att_num, mode, only_parent);
878}
879
893static float8
895{
896 int ncells1, ncells2;
897 int ndims1, ndims2, ndims;
898 double ntuples_max;
899 double ntuples_not_null1, ntuples_not_null2;
900
901 ND_BOX extent1, extent2;
902 ND_IBOX ibox1, ibox2;
903 int at1[ND_DIMS];
904 int at2[ND_DIMS];
905 double min1[ND_DIMS];
906 double width1[ND_DIMS];
907 double cellsize1[ND_DIMS];
908 int size2[ND_DIMS];
909 double min2[ND_DIMS];
910 double width2[ND_DIMS];
911 double cellsize2[ND_DIMS];
912 int size1[ND_DIMS];
913 int d;
914 double val = 0;
915 float8 selectivity;
916
917 /* Drop out on null inputs */
918 if ( ! ( s1 && s2 ) )
919 {
920 elog(NOTICE, " estimate_join_selectivity called with null inputs");
921 return FALLBACK_ND_SEL;
922 }
923
924 /* We need to know how many cells each side has... */
925 ncells1 = (int)roundf(s1->histogram_cells);
926 ncells2 = (int)roundf(s2->histogram_cells);
927
928 /* ...so that we can drive the summation loop with the smaller histogram. */
929 if ( ncells1 > ncells2 )
930 {
931 const ND_STATS *stats_tmp = s1;
932 s1 = s2;
933 s2 = stats_tmp;
934 }
935
936 POSTGIS_DEBUGF(3, "s1: %s", nd_stats_to_json(s1));
937 POSTGIS_DEBUGF(3, "s2: %s", nd_stats_to_json(s2));
938
939 /* Re-read that info after the swap */
940 ncells1 = (int)roundf(s1->histogram_cells);
941 ncells2 = (int)roundf(s2->histogram_cells);
942
943 /* Q: What's the largest possible join size these relations can create? */
944 /* A: The product of the # of non-null rows in each relation. */
945 ntuples_not_null1 = s1->table_features * ((double)s1->not_null_features / s1->sample_features);
946 ntuples_not_null2 = s2->table_features * ((double)s2->not_null_features / s2->sample_features);
947 ntuples_max = ntuples_not_null1 * ntuples_not_null2;
948
949 /* Get the ndims as ints */
950 ndims1 = (int)roundf(s1->ndims);
951 ndims2 = (int)roundf(s2->ndims);
952 ndims = Max(ndims1, ndims2);
953
954 /* Get the extents */
955 extent1 = s1->extent;
956 extent2 = s2->extent;
957
958 /* If relation stats do not intersect, join is very very selective. */
959 if ( ! nd_box_intersects(&extent1, &extent2, ndims) )
960 {
961 POSTGIS_DEBUG(3, "relation stats do not intersect, returning 0");
962 PG_RETURN_FLOAT8(0.0);
963 }
964
965 /*
966 * First find the index range of the part of the smaller
967 * histogram that overlaps the larger one.
968 */
969 if ( ! nd_box_overlap(s1, &extent2, &ibox1) )
970 {
971 POSTGIS_DEBUG(3, "could not calculate overlap of relations");
972 PG_RETURN_FLOAT8(FALLBACK_ND_JOINSEL);
973 }
974
975 /* Initialize counters / constants on s1 */
976 for ( d = 0; d < ndims1; d++ )
977 {
978 at1[d] = ibox1.min[d];
979 min1[d] = s1->extent.min[d];
980 width1[d] = s1->extent.max[d] - s1->extent.min[d];
981 size1[d] = (int)roundf(s1->size[d]);
982 cellsize1[d] = width1[d] / size1[d];
983 }
984
985 /* Initialize counters / constants on s2 */
986 for ( d = 0; d < ndims2; d++ )
987 {
988 min2[d] = s2->extent.min[d];
989 width2[d] = s2->extent.max[d] - s2->extent.min[d];
990 size2[d] = (int)roundf(s2->size[d]);
991 cellsize2[d] = width2[d] / size2[d];
992 }
993
994 /* For each affected cell of s1... */
995 do
996 {
997 double val1;
998 /* Construct the bounds of this cell */
999 ND_BOX nd_cell1;
1000 nd_box_init(&nd_cell1);
1001 for ( d = 0; d < ndims1; d++ )
1002 {
1003 nd_cell1.min[d] = min1[d] + (at1[d]+0) * cellsize1[d];
1004 nd_cell1.max[d] = min1[d] + (at1[d]+1) * cellsize1[d];
1005 }
1006
1007 /* Find the cells of s2 that cell1 overlaps.. */
1008 nd_box_overlap(s2, &nd_cell1, &ibox2);
1009
1010 /* Initialize counter */
1011 for ( d = 0; d < ndims2; d++ )
1012 {
1013 at2[d] = ibox2.min[d];
1014 }
1015
1016 POSTGIS_DEBUGF(3, "at1 %d,%d %s", at1[0], at1[1], nd_box_to_json(&nd_cell1, ndims1));
1017
1018 /* Get the value at this cell */
1019 val1 = s1->value[nd_stats_value_index(s1, at1)];
1020
1021 /* For each overlapped cell of s2... */
1022 do
1023 {
1024 double ratio2;
1025 double val2;
1026
1027 /* Construct the bounds of this cell */
1028 ND_BOX nd_cell2;
1029 nd_box_init(&nd_cell2);
1030 for ( d = 0; d < ndims2; d++ )
1031 {
1032 nd_cell2.min[d] = min2[d] + (at2[d]+0) * cellsize2[d];
1033 nd_cell2.max[d] = min2[d] + (at2[d]+1) * cellsize2[d];
1034 }
1035
1036 POSTGIS_DEBUGF(3, " at2 %d,%d %s", at2[0], at2[1], nd_box_to_json(&nd_cell2, ndims2));
1037
1038 /* Calculate overlap ratio of the cells */
1039 ratio2 = nd_box_ratio(&nd_cell1, &nd_cell2, Max(ndims1, ndims2));
1040
1041 /* Multiply the cell counts, scaled by overlap ratio */
1042 val2 = s2->value[nd_stats_value_index(s2, at2)];
1043 POSTGIS_DEBUGF(3, " val1 %.6g val2 %.6g ratio %.6g", val1, val2, ratio2);
1044 val += val1 * (val2 * ratio2);
1045 }
1046 while ( nd_increment(&ibox2, ndims2, at2) );
1047
1048 }
1049 while( nd_increment(&ibox1, ndims1, at1) );
1050
1051 POSTGIS_DEBUGF(3, "val of histogram = %g", val);
1052
1053 /*
1054 * In order to compare our total cell count "val" to the
1055 * ntuples_max, we need to scale val up to reflect a full
1056 * table estimate. So, multiply by ratio of table size to
1057 * sample size.
1058 */
1059 val *= (s1->table_features / s1->sample_features);
1060 val *= (s2->table_features / s2->sample_features);
1061
1062 POSTGIS_DEBUGF(3, "val scaled to full table size = %g", val);
1063
1064 /*
1065 * Because the cell counts are over-determined due to
1066 * double counting of features that overlap multiple cells
1067 * (see the compute_gserialized_stats routine)
1068 * we also have to scale our cell count "val" *down*
1069 * to adjust for the double counting.
1070 */
1071// val /= (s1->cells_covered / s1->histogram_features);
1072// val /= (s2->cells_covered / s2->histogram_features);
1073
1074 /*
1075 * Finally, the selectivity is the estimated number of
1076 * rows to be returned divided by the maximum possible
1077 * number of rows that can be returned.
1078 */
1079 selectivity = val / ntuples_max;
1080
1081 /* Guard against over-estimates and crazy numbers :) */
1082 if ( isnan(selectivity) || ! isfinite(selectivity) || selectivity < 0.0 )
1083 {
1084 selectivity = DEFAULT_ND_JOINSEL;
1085 }
1086 else if ( selectivity > 1.0 )
1087 {
1088 selectivity = 1.0;
1089 }
1090
1091 return selectivity;
1092}
1093
1099Datum gserialized_gist_joinsel_nd(PG_FUNCTION_ARGS)
1100{
1101 PG_RETURN_DATUM(DirectFunctionCall5(
1103 PG_GETARG_DATUM(0), PG_GETARG_DATUM(1),
1104 PG_GETARG_DATUM(2), PG_GETARG_DATUM(3),
1105 Int32GetDatum(0) /* ND mode */
1106 ));
1107}
1108
1114Datum gserialized_gist_joinsel_2d(PG_FUNCTION_ARGS)
1115{
1116 PG_RETURN_DATUM(DirectFunctionCall5(
1118 PG_GETARG_DATUM(0), PG_GETARG_DATUM(1),
1119 PG_GETARG_DATUM(2), PG_GETARG_DATUM(3),
1120 Int32GetDatum(2) /* 2D mode */
1121 ));
1122}
1123
1124double
1125gserialized_joinsel_internal(PlannerInfo *root, List *args, JoinType jointype, int mode)
1126{
1127 float8 selectivity;
1128 Oid relid1, relid2;
1129 ND_STATS *stats1, *stats2;
1130 Node *arg1 = (Node*) linitial(args);
1131 Node *arg2 = (Node*) lsecond(args);
1132 Var *var1 = (Var*) arg1;
1133 Var *var2 = (Var*) arg2;
1134
1135 POSTGIS_DEBUGF(2, "%s: entered function", __func__);
1136
1137 /* We only do column joins right now, no functional joins */
1138 /* TODO: handle g1 && ST_Expand(g2) */
1139 if (!IsA(arg1, Var) || !IsA(arg2, Var))
1140 {
1141 POSTGIS_DEBUGF(1, "%s called with arguments that are not column references", __func__);
1142 return DEFAULT_ND_JOINSEL;
1143 }
1144
1145 /* What are the Oids of our tables/relations? */
1146 relid1 = rt_fetch(var1->varno, root->parse->rtable)->relid;
1147 relid2 = rt_fetch(var2->varno, root->parse->rtable)->relid;
1148
1149 /* Pull the stats from the stats system. */
1150 stats1 = pg_get_nd_stats(relid1, var1->varattno, mode, false);
1151 stats2 = pg_get_nd_stats(relid2, var2->varattno, mode, false);
1152
1153 /* If we can't get stats, we have to stop here! */
1154 if (!stats1)
1155 {
1156 POSTGIS_DEBUGF(2, "%s: cannot find stats for \"%s\"", __func__, get_rel_name(relid2) ? get_rel_name(relid2) : "NULL");
1157 return DEFAULT_ND_JOINSEL;
1158 }
1159 else if (!stats2)
1160 {
1161 POSTGIS_DEBUGF(2, "%s: cannot find stats for \"%s\"", __func__, get_rel_name(relid2) ? get_rel_name(relid2) : "NULL");
1162 return DEFAULT_ND_JOINSEL;
1163 }
1164
1165 selectivity = estimate_join_selectivity(stats1, stats2);
1166 POSTGIS_DEBUGF(2, "got selectivity %g", selectivity);
1167 pfree(stats1);
1168 pfree(stats2);
1169 return selectivity;
1170}
1171
1181Datum gserialized_gist_joinsel(PG_FUNCTION_ARGS)
1182{
1183 PlannerInfo *root = (PlannerInfo *)PG_GETARG_POINTER(0);
1184 /* Oid operator = PG_GETARG_OID(1); */
1185 List *args = (List *)PG_GETARG_POINTER(2);
1186 JoinType jointype = (JoinType) PG_GETARG_INT16(3);
1187 int mode = PG_GETARG_INT32(4);
1188
1189 POSTGIS_DEBUGF(2, "%s: entered function", __func__);
1190
1191 /* Check length of args and punt on > 2 */
1192 if (list_length(args) != 2)
1193 {
1194 POSTGIS_DEBUGF(2, "%s: got nargs == %d", __func__, list_length(args));
1195 PG_RETURN_FLOAT8(DEFAULT_ND_JOINSEL);
1196 }
1197
1198 /* Only respond to an inner join/unknown context join */
1199 if (jointype != JOIN_INNER)
1200 {
1201 POSTGIS_DEBUGF(1, "%s: jointype %d not supported", __func__, jointype);
1202 PG_RETURN_FLOAT8(DEFAULT_ND_JOINSEL);
1203 }
1204
1205 PG_RETURN_FLOAT8(gserialized_joinsel_internal(root, args, jointype, mode));
1206}
1207
1226static void
1227compute_gserialized_stats_mode(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
1228 int sample_rows, double total_rows, int mode)
1229{
1230 MemoryContext old_context;
1231 int d, i; /* Counters */
1232 int notnull_cnt = 0; /* # not null rows in the sample */
1233 int null_cnt = 0; /* # null rows in the sample */
1234 int histogram_features = 0; /* # rows that actually got counted in the histogram */
1235
1236 ND_STATS *nd_stats; /* Our histogram */
1237 size_t nd_stats_size; /* Size to allocate */
1238
1239 double total_width = 0; /* # of bytes used by sample */
1240 double total_cell_count = 0; /* # of cells in histogram affected by sample */
1241
1242 ND_BOX sum; /* Sum of extents of sample boxes */
1243 ND_BOX avg; /* Avg of extents of sample boxes */
1244 ND_BOX stddev; /* StdDev of extents of sample boxes */
1245
1246 const ND_BOX **sample_boxes; /* ND_BOXes for each of the sample features */
1247 ND_BOX sample_extent; /* Extent of the raw sample */
1248 int histo_size[ND_DIMS]; /* histogram nrows, ncols, etc */
1249 ND_BOX histo_extent; /* Spatial extent of the histogram */
1250 ND_BOX histo_extent_new; /* Temporary variable */
1251 int histo_cells_target; /* Number of cells we will shoot for, given the stats target */
1252 int histo_cells; /* Number of cells in the histogram */
1253 int histo_cells_new = 1; /* Temporary variable */
1254
1255 int ndims = 2; /* Dimensionality of the sample */
1256 int histo_ndims = 0; /* Dimensionality of the histogram */
1257 double sample_distribution[ND_DIMS]; /* How homogeneous is distribution of sample in each axis? */
1258 double total_distribution; /* Total of sample_distribution */
1259
1260 int stats_slot; /* What slot is this data going into? (2D vs ND) */
1261 int stats_kind; /* And this is what? (2D vs ND) */
1262
1263 /* Initialize sum and stddev */
1264 nd_box_init(&sum);
1265 nd_box_init(&stddev);
1266 nd_box_init(&avg);
1267 nd_box_init(&histo_extent);
1268 nd_box_init(&histo_extent_new);
1269
1270 /*
1271 * This is where gserialized_analyze_nd
1272 * should put its' custom parameters.
1273 */
1274 /* void *mystats = stats->extra_data; */
1275
1276 POSTGIS_DEBUG(2, "compute_gserialized_stats called");
1277 POSTGIS_DEBUGF(3, " # sample_rows: %d", sample_rows);
1278 POSTGIS_DEBUGF(3, " estimate of total_rows: %.6g", total_rows);
1279
1280 /*
1281 * We might need less space, but don't think
1282 * its worth saving...
1283 */
1284 sample_boxes = palloc(sizeof(ND_BOX*) * sample_rows);
1285
1286 /*
1287 * First scan:
1288 * o read boxes
1289 * o find dimensionality of the sample
1290 * o find extent of the sample
1291 * o count null-infinite/not-null values
1292 * o compute total_width
1293 * o compute total features's box area (for avgFeatureArea)
1294 * o sum features box coordinates (for standard deviation)
1295 */
1296 for ( i = 0; i < sample_rows; i++ )
1297 {
1298 Datum datum;
1299 GBOX gbox = {0};
1300 ND_BOX *nd_box;
1301 bool is_null;
1302
1303 datum = fetchfunc(stats, i, &is_null);
1304
1305 /* Skip all NULLs. */
1306 if ( is_null )
1307 {
1308 POSTGIS_DEBUGF(4, " skipped null geometry %d", i);
1309 null_cnt++;
1310 continue;
1311 }
1312
1313 /* Read the bounds from the gserialized. */
1314 if (LW_FAILURE == gserialized_datum_get_gbox_p(datum, &gbox))
1315 {
1316 /* Skip empties too. */
1317 POSTGIS_DEBUGF(3, " skipped empty geometry %d", i);
1318 continue;
1319 }
1320
1321 /* If we're in 2D mode, zero out the higher dimensions for "safety" */
1322 if ( mode == 2 )
1323 gbox.zmin = gbox.zmax = gbox.mmin = gbox.mmax = 0.0;
1324
1325 /* Check bounds for validity (finite and not NaN) */
1326 if ( ! gbox_is_valid(&gbox) )
1327 {
1328 POSTGIS_DEBUGF(3, " skipped infinite/nan geometry %d", i);
1329 continue;
1330 }
1331
1332 /*
1333 * In N-D mode, set the ndims to the maximum dimensionality found
1334 * in the sample. Otherwise, leave at ndims == 2.
1335 */
1336 if ( mode != 2 )
1337 ndims = Max(gbox_ndims(&gbox), ndims);
1338
1339 /* Convert gbox to n-d box */
1340 nd_box = palloc(sizeof(ND_BOX));
1341 nd_box_from_gbox(&gbox, nd_box);
1342
1343 /* Cache n-d bounding box */
1344 sample_boxes[notnull_cnt] = nd_box;
1345
1346 /* Initialize sample extent before merging first entry */
1347 if ( ! notnull_cnt )
1348 nd_box_init_bounds(&sample_extent);
1349
1350 /* Add current sample to overall sample extent */
1351 nd_box_merge(nd_box, &sample_extent);
1352
1353 /* How many bytes does this sample use? */
1354 total_width += toast_raw_datum_size(datum);
1355
1356 /* Add bounds coordinates to sums for stddev calculation */
1357 for ( d = 0; d < ndims; d++ )
1358 {
1359 sum.min[d] += nd_box->min[d];
1360 sum.max[d] += nd_box->max[d];
1361 }
1362
1363 /* Increment our "good feature" count */
1364 notnull_cnt++;
1365
1366 /* Give backend a chance of interrupting us */
1367#if POSTGIS_PGSQL_VERSION >= 180
1368 vacuum_delay_point(true);
1369#else
1370 vacuum_delay_point();
1371#endif
1372 }
1373
1374#if POSTGIS_PGSQL_VERSION >= 170
1375 POSTGIS_DEBUGF(3, " stats->attstattarget: %d", stats->attstattarget);
1376 histo_cells_target = histogram_cell_budget(total_rows, ndims, stats->attstattarget);
1377#else
1378 POSTGIS_DEBUGF(3, " stats->attr->attstattarget: %d", stats->attr->attstattarget);
1379 histo_cells_target = histogram_cell_budget(total_rows, ndims, stats->attr->attstattarget);
1380#endif
1381 POSTGIS_DEBUGF(3, " target # of histogram cells: %d", histo_cells_target);
1382
1383 /* If there's no useful features, we can't work out stats */
1384 if ( ! notnull_cnt )
1385 {
1386 stats->stats_valid = false;
1387 return;
1388 }
1389
1390 POSTGIS_DEBUGF(3, " sample_extent: %s", nd_box_to_json(&sample_extent, ndims));
1391
1392 /*
1393 * Second scan:
1394 * o compute standard deviation
1395 */
1396 for ( d = 0; d < ndims; d++ )
1397 {
1398 /* Calculate average bounds values */
1399 avg.min[d] = sum.min[d] / notnull_cnt;
1400 avg.max[d] = sum.max[d] / notnull_cnt;
1401
1402 /* Calculate standard deviation for this dimension bounds */
1403 for ( i = 0; i < notnull_cnt; i++ )
1404 {
1405 const ND_BOX *ndb = sample_boxes[i];
1406 stddev.min[d] += (ndb->min[d] - avg.min[d]) * (ndb->min[d] - avg.min[d]);
1407 stddev.max[d] += (ndb->max[d] - avg.max[d]) * (ndb->max[d] - avg.max[d]);
1408 }
1409 stddev.min[d] = sqrt(stddev.min[d] / notnull_cnt);
1410 stddev.max[d] = sqrt(stddev.max[d] / notnull_cnt);
1411
1412 /* Histogram bounds for this dimension bounds is avg +/- SDFACTOR * stdev */
1413 histo_extent.min[d] = Max(avg.min[d] - SDFACTOR * stddev.min[d], sample_extent.min[d]);
1414 histo_extent.max[d] = Min(avg.max[d] + SDFACTOR * stddev.max[d], sample_extent.max[d]);
1415 }
1416
1417 /*
1418 * Third scan:
1419 * o skip hard deviants
1420 * o compute new histogram box
1421 */
1422 nd_box_init_bounds(&histo_extent_new);
1423 for ( i = 0; i < notnull_cnt; i++ )
1424 {
1425 const ND_BOX *ndb = sample_boxes[i];
1426 /* Skip any hard deviants (boxes entirely outside our histo_extent */
1427 if ( ! nd_box_intersects(&histo_extent, ndb, ndims) )
1428 {
1429 POSTGIS_DEBUGF(4, " feature %d is a hard deviant, skipped", i);
1430 sample_boxes[i] = NULL;
1431 continue;
1432 }
1433 /* Expand our new box to fit all the other features. */
1434 nd_box_merge(ndb, &histo_extent_new);
1435 }
1436 /*
1437 * Expand the box slightly (1%) to avoid edge effects
1438 * with objects that are on the boundary
1439 */
1440 nd_box_expand(&histo_extent_new, 0.01);
1441 histo_extent = histo_extent_new;
1442
1443 /*
1444 * How should we allocate our histogram cells to the
1445 * different dimensions? We can't do it by raw dimensional width,
1446 * because in x/y/z space, the z can have different units
1447 * from the x/y. Similarly for x/y/t space.
1448 * So, we instead calculate how much features overlap
1449 * each other in their dimension to figure out which
1450 * dimensions have useful selectivity characteristics (more
1451 * variability in density) and therefore would find
1452 * more cells useful (to distinguish between dense places and
1453 * homogeneous places).
1454 */
1455 nd_box_array_distribution(sample_boxes, notnull_cnt, &histo_extent, ndims,
1456 sample_distribution);
1457
1458 /*
1459 * The sample_distribution array now tells us how spread out the
1460 * data is in each dimension, so we use that data to allocate
1461 * the histogram cells we have available.
1462 * At this point, histo_cells_target is the approximate target number
1463 * of cells.
1464 */
1465
1466 /*
1467 * Some dimensions have basically a uniform distribution, we want
1468 * to allocate no cells to those dimensions, only to dimensions
1469 * that have some interesting differences in data distribution.
1470 * Here we count up the number of interesting dimensions
1471 */
1472 for ( d = 0; d < ndims; d++ )
1473 {
1474 if ( sample_distribution[d] > 0 )
1475 histo_ndims++;
1476 }
1477
1478 if ( histo_ndims == 0 )
1479 {
1480 /* Special case: all our dimensions had low variability! */
1481 /* We just divide the cells up evenly */
1482 POSTGIS_DEBUG(3, " special case: no axes have variability");
1483 histo_cells_new = 1;
1484 for ( d = 0; d < ndims; d++ )
1485 {
1486 histo_size[d] = (int)pow((double)histo_cells_target, 1/(double)ndims);
1487 if ( ! histo_size[d] )
1488 histo_size[d] = 1;
1489 POSTGIS_DEBUGF(3, " histo_size[d]: %d", histo_size[d]);
1490 histo_cells_new *= histo_size[d];
1491 }
1492 POSTGIS_DEBUGF(3, " histo_cells_new: %d", histo_cells_new);
1493 }
1494 else
1495 {
1496 /*
1497 * We're going to express the amount of variability in each dimension
1498 * as a proportion of the total variability and allocate cells in that
1499 * dimension relative to that proportion.
1500 */
1501 POSTGIS_DEBUG(3, " allocating histogram axes based on axis variability");
1502 total_distribution = total_double(sample_distribution, ndims); /* First get the total */
1503 POSTGIS_DEBUGF(3, " total_distribution: %.8g", total_distribution);
1504 histo_cells_new = 1; /* For the number of cells in the final histogram */
1505 for ( d = 0; d < ndims; d++ )
1506 {
1507 if ( sample_distribution[d] == 0 ) /* Uninteresting dimensions don't get any room */
1508 {
1509 histo_size[d] = 1;
1510 }
1511 else /* Interesting dimension */
1512 {
1513 /* How does this dims variability compare to the total? */
1514 float edge_ratio = (float)sample_distribution[d] / (float)total_distribution;
1515 /*
1516 * Scale the target cells number by the # of dims and ratio,
1517 * then take the appropriate root to get the estimated number of cells
1518 * on this axis (eg, pow(0.5) for 2d, pow(0.333) for 3d, pow(0.25) for 4d)
1519 * The dedicated helper clamps pathological floating point inputs so we
1520 * do not resurrect the NaN propagation reported in #5959 on amd64.
1521 */
1522 histo_size[d] = histogram_axis_cells(histo_cells_target, histo_ndims, edge_ratio);
1523 }
1524 histo_cells_new *= histo_size[d];
1525 }
1526 POSTGIS_DEBUGF(3, " histo_cells_new: %d", histo_cells_new);
1527 }
1528
1529 /* Update histo_cells to the actual number of cells we need to allocate */
1530 histo_cells = histo_cells_new;
1531 POSTGIS_DEBUGF(3, " histo_cells: %d", histo_cells);
1532
1533 /*
1534 * Create the histogram (ND_STATS) in the stats memory context
1535 */
1536 old_context = MemoryContextSwitchTo(stats->anl_context);
1537 nd_stats_size = sizeof(ND_STATS) + ((histo_cells - 1) * sizeof(float4));
1538 nd_stats = palloc(nd_stats_size);
1539 memset(nd_stats, 0, nd_stats_size); /* Initialize all values to 0 */
1540 MemoryContextSwitchTo(old_context);
1541
1542 /* Initialize the #ND_STATS objects */
1543 nd_stats->ndims = ndims;
1544 nd_stats->extent = histo_extent;
1545 nd_stats->sample_features = sample_rows;
1546 nd_stats->table_features = total_rows;
1547 nd_stats->not_null_features = notnull_cnt;
1548 /* Copy in the histogram dimensions */
1549 for ( d = 0; d < ndims; d++ )
1550 nd_stats->size[d] = histo_size[d];
1551
1552 /*
1553 * Fourth scan:
1554 * o fill histogram values with the proportion of
1555 * features' bbox overlaps: a feature's bvol
1556 * can fully overlap (1) or partially overlap
1557 * (fraction of 1) an histogram cell.
1558 *
1559 * Note that we are filling each cell with the "portion of
1560 * the feature's box that overlaps the cell". So, if we sum
1561 * up the values in the histogram, we could get the
1562 * histogram feature count.
1563 *
1564 */
1565 for ( i = 0; i < notnull_cnt; i++ )
1566 {
1567 const ND_BOX *nd_box;
1568 ND_IBOX nd_ibox;
1569 int at[ND_DIMS];
1570 double num_cells = 0;
1571 double min[ND_DIMS] = {0.0, 0.0, 0.0, 0.0};
1572 double max[ND_DIMS] = {0.0, 0.0, 0.0, 0.0};
1573 double cellsize[ND_DIMS] = {0.0, 0.0, 0.0, 0.0};
1574
1575 nd_box = sample_boxes[i];
1576 if ( ! nd_box ) continue; /* Skip Null'ed out hard deviants */
1577
1578 /* Give backend a chance of interrupting us */
1579#if POSTGIS_PGSQL_VERSION >= 180
1580 vacuum_delay_point(true);
1581#else
1582 vacuum_delay_point();
1583#endif
1584
1585 /* Find the cells that overlap with this box and put them into the ND_IBOX */
1586 nd_box_overlap(nd_stats, nd_box, &nd_ibox);
1587 memset(at, 0, sizeof(int)*ND_DIMS);
1588
1589 POSTGIS_DEBUGF(3, " feature %d: ibox (%d, %d, %d, %d) (%d, %d, %d, %d)", i,
1590 nd_ibox.min[0], nd_ibox.min[1], nd_ibox.min[2], nd_ibox.min[3],
1591 nd_ibox.max[0], nd_ibox.max[1], nd_ibox.max[2], nd_ibox.max[3]);
1592
1593 for ( d = 0; d < nd_stats->ndims; d++ )
1594 {
1595 /* Initialize the starting values */
1596 at[d] = nd_ibox.min[d];
1597 min[d] = nd_stats->extent.min[d];
1598 max[d] = nd_stats->extent.max[d];
1599 cellsize[d] = (max[d] - min[d])/(nd_stats->size[d]);
1600 }
1601
1602 /*
1603 * Move through all the overlapped histogram cells values and
1604 * add the box overlap proportion to them.
1605 */
1606 do
1607 {
1608 ND_BOX nd_cell = { {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0} };
1609 double ratio;
1610 /* Create a box for this histogram cell */
1611 for ( d = 0; d < nd_stats->ndims; d++ )
1612 {
1613 nd_cell.min[d] = min[d] + (at[d]+0) * cellsize[d];
1614 nd_cell.max[d] = min[d] + (at[d]+1) * cellsize[d];
1615 }
1616
1617 /*
1618 * If a feature box is completely inside one cell the ratio will be
1619 * 1.0. If a feature box is 50% in two cells, each cell will get
1620 * 0.5 added on.
1621 */
1622 ratio = nd_box_ratio(&nd_cell, nd_box, nd_stats->ndims);
1623 nd_stats->value[nd_stats_value_index(nd_stats, at)] += ratio;
1624 num_cells += ratio;
1625 POSTGIS_DEBUGF(3, " ratio (%.8g) num_cells (%.8g)", ratio, num_cells);
1626 POSTGIS_DEBUGF(3, " at (%d, %d, %d, %d)", at[0], at[1], at[2], at[3]);
1627 }
1628 while ( nd_increment(&nd_ibox, nd_stats->ndims, at) );
1629
1630 /* Keep track of overall number of overlaps counted */
1631 total_cell_count += num_cells;
1632 /* How many features have we added to this histogram? */
1633 histogram_features++;
1634 }
1635
1636 POSTGIS_DEBUGF(3, " histogram_features: %d", histogram_features);
1637 POSTGIS_DEBUGF(3, " sample_rows: %d", sample_rows);
1638 POSTGIS_DEBUGF(3, " table_rows: %.6g", total_rows);
1639
1640 /* Error out if we got no sample information */
1641 if ( ! histogram_features )
1642 {
1643 POSTGIS_DEBUG(3, " no stats have been gathered");
1644 elog(NOTICE, " no features lie in the stats histogram, invalid stats");
1645 stats->stats_valid = false;
1646 return;
1647 }
1648
1649 nd_stats->histogram_features = histogram_features;
1650 nd_stats->histogram_cells = histo_cells;
1651 nd_stats->cells_covered = total_cell_count;
1652
1653 /* Put this histogram data into the right slot/kind */
1654 if ( mode == 2 )
1655 {
1656 stats_slot = STATISTIC_SLOT_2D;
1657 stats_kind = STATISTIC_KIND_2D;
1658 }
1659 else
1660 {
1661 stats_slot = STATISTIC_SLOT_ND;
1662 stats_kind = STATISTIC_KIND_ND;
1663 }
1664
1665 /* Write the statistics data */
1666 stats->stakind[stats_slot] = stats_kind;
1667 stats->staop[stats_slot] = InvalidOid;
1668 stats->stanumbers[stats_slot] = (float4*)nd_stats;
1669 stats->numnumbers[stats_slot] = nd_stats_size/sizeof(float4);
1670 stats->stanullfrac = (float4)null_cnt/sample_rows;
1671 stats->stawidth = total_width/notnull_cnt;
1672 stats->stadistinct = -1.0;
1673 stats->stats_valid = true;
1674
1675 POSTGIS_DEBUGF(3, " out: slot 0: kind %d (STATISTIC_KIND_ND)", stats->stakind[0]);
1676 POSTGIS_DEBUGF(3, " out: slot 0: op %d (InvalidOid)", stats->staop[0]);
1677 POSTGIS_DEBUGF(3, " out: slot 0: numnumbers %d", stats->numnumbers[0]);
1678 POSTGIS_DEBUGF(3, " out: null fraction: %f=%d/%d", stats->stanullfrac, null_cnt, sample_rows);
1679 POSTGIS_DEBUGF(3, " out: average width: %d bytes", stats->stawidth);
1680 POSTGIS_DEBUG (3, " out: distinct values: all (no check done)");
1681 POSTGIS_DEBUGF(3, " out: %s", nd_stats_to_json(nd_stats));
1682 /*
1683 POSTGIS_DEBUGF(3, " out histogram:\n%s", nd_stats_to_grid(nd_stats));
1684 */
1685
1686 return;
1687}
1705static void
1706compute_gserialized_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
1707 int sample_rows, double total_rows)
1708{
1709 GserializedAnalyzeExtraData *extra_data = (GserializedAnalyzeExtraData *)stats->extra_data;
1710 /* Call standard statistics calculation routine to fill in correlation for BRIN to work */
1711 stats->extra_data = extra_data->std_extra_data;
1712 extra_data->std_compute_stats(stats, fetchfunc, sample_rows, total_rows);
1713 stats->extra_data = extra_data;
1714
1715 /* 2D Mode */
1716 compute_gserialized_stats_mode(stats, fetchfunc, sample_rows, total_rows, 2);
1717
1718 if (stats->stats_valid)
1719 {
1720 /* ND Mode: Only computed if 2D was computed too (not NULL and valid) */
1721 compute_gserialized_stats_mode(stats, fetchfunc, sample_rows, total_rows, 0);
1722 }
1723}
1724
1752Datum gserialized_analyze_nd(PG_FUNCTION_ARGS)
1753{
1754 VacAttrStats *stats = (VacAttrStats *)PG_GETARG_POINTER(0);
1755 GserializedAnalyzeExtraData *extra_data =
1757
1758 /* Ask for standard analyze to fill in as much as possible */
1759 if (!std_typanalyze(stats))
1760 PG_RETURN_BOOL(false);
1761
1762 /* Save old compute_stats and extra_data for scalar statistics ... */
1763 extra_data->std_compute_stats = stats->compute_stats;
1764 extra_data->std_extra_data = stats->extra_data;
1765 /* ... and replace with our info */
1766 stats->compute_stats = compute_gserialized_stats;
1767 stats->extra_data = extra_data;
1768
1769 /* Indicate we are done successfully */
1770 PG_RETURN_BOOL(true);
1771}
1772
1785static float8
1786estimate_selectivity(const GBOX *box, const ND_STATS *nd_stats, int mode)
1787{
1788 int d; /* counter */
1789 float8 selectivity;
1790 ND_BOX nd_box;
1791 ND_IBOX nd_ibox;
1792 int at[ND_DIMS];
1793 double cell_size[ND_DIMS];
1794 double min[ND_DIMS];
1795 double max[ND_DIMS];
1796 double total_count = 0.0;
1797 int ndims_max;
1798
1799 /* Calculate the overlap of the box on the histogram */
1800 if ( ! nd_stats )
1801 {
1802 elog(NOTICE, " estimate_selectivity called with null input");
1803 return FALLBACK_ND_SEL;
1804 }
1805
1806 ndims_max = Max(nd_stats->ndims, gbox_ndims(box));
1807
1808 /* Initialize nd_box. */
1809 nd_box_from_gbox(box, &nd_box);
1810
1811 /*
1812 * To return 2D stats on an ND sample, we need to make the
1813 * 2D box cover the full range of the other dimensions in the
1814 * histogram.
1815 */
1816 POSTGIS_DEBUGF(3, " mode: %d", mode);
1817 if ( mode == 2 )
1818 {
1819 POSTGIS_DEBUG(3, " in 2d mode, stripping the computation down to 2d");
1820 ndims_max = 2;
1821 }
1822
1823 POSTGIS_DEBUGF(3, " nd_stats->extent: %s", nd_box_to_json(&(nd_stats->extent), nd_stats->ndims));
1824 POSTGIS_DEBUGF(3, " nd_box: %s", nd_box_to_json(&(nd_box), gbox_ndims(box)));
1825
1826 // elog(DEBUG1, "out histogram:\n%s", nd_stats_to_grid(nd_stats));
1827
1828 /*
1829 * Search box completely misses histogram extent?
1830 * We have to intersect in all N dimensions or else we have
1831 * zero interaction under the &&& operator. It's important
1832 * to short circuit in this case, as some of the tests below
1833 * will return junk results when run on non-intersecting inputs.
1834 */
1835 if ( ! nd_box_intersects(&nd_box, &(nd_stats->extent), ndims_max) )
1836 {
1837 POSTGIS_DEBUG(3, " search box does not overlap histogram, returning 0");
1838 return 0.0;
1839 }
1840
1841 /* Search box completely contains histogram extent! */
1842 if ( nd_box_contains(&nd_box, &(nd_stats->extent), ndims_max) )
1843 {
1844 POSTGIS_DEBUG(3, " search box contains histogram, returning 1");
1845 return 1.0;
1846 }
1847
1848 /* Calculate the overlap of the box on the histogram */
1849 if ( ! nd_box_overlap(nd_stats, &nd_box, &nd_ibox) )
1850 {
1851 POSTGIS_DEBUG(3, " search box overlap with stats histogram failed");
1852 return FALLBACK_ND_SEL;
1853 }
1854
1855 /* Work out some measurements of the histogram */
1856 for ( d = 0; d < nd_stats->ndims; d++ )
1857 {
1858 /* Cell size in each dim */
1859 min[d] = nd_stats->extent.min[d];
1860 max[d] = nd_stats->extent.max[d];
1861 cell_size[d] = (max[d] - min[d]) / nd_stats->size[d];
1862 POSTGIS_DEBUGF(3, " cell_size[%d] : %.9g", d, cell_size[d]);
1863
1864 /* Initialize the counter */
1865 at[d] = nd_ibox.min[d];
1866 }
1867
1868 /* Move through all the overlap values and sum them */
1869 do
1870 {
1871 float cell_count, ratio;
1872 ND_BOX nd_cell = { {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0} };
1873
1874 /* We have to pro-rate partially overlapped cells. */
1875 for ( d = 0; d < nd_stats->ndims; d++ )
1876 {
1877 nd_cell.min[d] = min[d] + (at[d]+0) * cell_size[d];
1878 nd_cell.max[d] = min[d] + (at[d]+1) * cell_size[d];
1879 }
1880
1881 ratio = nd_box_ratio(&nd_box, &nd_cell, nd_stats->ndims);
1882 cell_count = nd_stats->value[nd_stats_value_index(nd_stats, at)];
1883
1884 /* Add the pro-rated count for this cell to the overall total */
1885 total_count += (double)cell_count * ratio;
1886 POSTGIS_DEBUGF(4, " cell (%d,%d), cell value %.6f, ratio %.6f", at[0], at[1], cell_count, ratio);
1887 }
1888 while ( nd_increment(&nd_ibox, nd_stats->ndims, at) );
1889
1890 /* Scale by the number of features in our histogram to get the proportion */
1891 selectivity = total_count / nd_stats->histogram_features;
1892
1893 POSTGIS_DEBUGF(3, " nd_stats->histogram_features = %f", nd_stats->histogram_features);
1894 POSTGIS_DEBUGF(3, " nd_stats->histogram_cells = %f", nd_stats->histogram_cells);
1895 POSTGIS_DEBUGF(3, " sum(overlapped histogram cells) = %f", total_count);
1896 POSTGIS_DEBUGF(3, " selectivity = %f", selectivity);
1897
1898 /* Prevent rounding overflows */
1899 if (selectivity > 1.0) selectivity = 1.0;
1900 else if (selectivity < 0.0) selectivity = 0.0;
1901
1902 return selectivity;
1903}
1904
1905
1906
1912Datum _postgis_gserialized_stats(PG_FUNCTION_ARGS)
1913{
1914 Oid table_oid = PG_GETARG_OID(0);
1915 text *att_text = PG_GETARG_TEXT_P(1);
1916 ND_STATS *nd_stats;
1917 char *str;
1918 text *json;
1919 int mode = 2; /* default to 2D mode */
1920 bool only_parent = false; /* default to whole tree stats */
1921
1922 /* Check if we've been asked to not use 2d mode */
1923 if ( ! PG_ARGISNULL(2) )
1924 mode = text_p_get_mode(PG_GETARG_TEXT_P(2));
1925
1926 /* Retrieve the stats object */
1927 nd_stats = pg_get_nd_stats_by_name(table_oid, att_text, mode, only_parent);
1928 if ( ! nd_stats )
1929 elog(ERROR, "stats for \"%s.%s\" do not exist", get_rel_name(table_oid), text_to_cstring(att_text));
1930
1931 /* Convert to JSON */
1932 elog(DEBUG1, "stats grid:\n%s", nd_stats_to_grid(nd_stats));
1933 str = nd_stats_to_json(nd_stats);
1934 json = cstring_to_text(str);
1935 pfree(str);
1936 pfree(nd_stats);
1937
1938 PG_RETURN_TEXT_P(json);
1939}
1940
1941
1947Datum _postgis_gserialized_sel(PG_FUNCTION_ARGS)
1948{
1949 Oid table_oid = PG_GETARG_OID(0);
1950 text *att_text = PG_GETARG_TEXT_P(1);
1951 Datum geom_datum = PG_GETARG_DATUM(2);
1952 GBOX gbox; /* search box read from gserialized datum */
1953 float8 selectivity = 0;
1954 ND_STATS *nd_stats;
1955 int mode = 2; /* 2D mode by default */
1956
1957 /* Check if we've been asked to not use 2d mode */
1958 if ( ! PG_ARGISNULL(3) )
1959 mode = text_p_get_mode(PG_GETARG_TEXT_P(3));
1960
1961 /* Retrieve the stats object */
1962 nd_stats = pg_get_nd_stats_by_name(table_oid, att_text, mode, false);
1963
1964 if ( ! nd_stats )
1965 elog(ERROR, "stats for \"%s.%s\" do not exist", get_rel_name(table_oid), text_to_cstring(att_text));
1966
1967 /* Calculate the gbox */
1968 if ( ! gserialized_datum_get_gbox_p(geom_datum, &gbox) )
1969 elog(ERROR, "unable to calculate bounding box from geometry");
1970
1971 POSTGIS_DEBUGF(3, " %s", gbox_to_string(&gbox));
1972
1973 /* Do the estimation */
1974 selectivity = estimate_selectivity(&gbox, nd_stats, mode);
1975
1976 pfree(nd_stats);
1977 PG_RETURN_FLOAT8(selectivity);
1978}
1979
1980
1986Datum _postgis_gserialized_joinsel(PG_FUNCTION_ARGS)
1987{
1988 Oid table_oid1 = PG_GETARG_OID(0);
1989 text *att_text1 = PG_GETARG_TEXT_P(1);
1990 Oid table_oid2 = PG_GETARG_OID(2);
1991 text *att_text2 = PG_GETARG_TEXT_P(3);
1992 ND_STATS *nd_stats1, *nd_stats2;
1993 float8 selectivity = 0;
1994 int mode = 2; /* 2D mode by default */
1995
1996
1997 /* Retrieve the stats object */
1998 nd_stats1 = pg_get_nd_stats_by_name(table_oid1, att_text1, mode, false);
1999 nd_stats2 = pg_get_nd_stats_by_name(table_oid2, att_text2, mode, false);
2000
2001 if ( ! nd_stats1 )
2002 elog(ERROR, "stats for \"%s.%s\" do not exist", get_rel_name(table_oid1), text_to_cstring(att_text1));
2003
2004 if ( ! nd_stats2 )
2005 elog(ERROR, "stats for \"%s.%s\" do not exist", get_rel_name(table_oid2), text_to_cstring(att_text2));
2006
2007 /* Check if we've been asked to not use 2d mode */
2008 if ( ! PG_ARGISNULL(4) )
2009 {
2010 text *modetxt = PG_GETARG_TEXT_P(4);
2011 char *modestr = text_to_cstring(modetxt);
2012 if ( modestr[0] == 'N' )
2013 mode = 0;
2014 }
2015
2016 /* Do the estimation */
2017 selectivity = estimate_join_selectivity(nd_stats1, nd_stats2);
2018
2019 pfree(nd_stats1);
2020 pfree(nd_stats2);
2021 PG_RETURN_FLOAT8(selectivity);
2022}
2023
2029Datum gserialized_gist_sel_2d(PG_FUNCTION_ARGS)
2030{
2031 PG_RETURN_DATUM(DirectFunctionCall5(
2033 PG_GETARG_DATUM(0), PG_GETARG_DATUM(1),
2034 PG_GETARG_DATUM(2), PG_GETARG_DATUM(3),
2035 Int32GetDatum(2) /* 2-D mode */
2036 ));
2037}
2038
2044Datum gserialized_gist_sel_nd(PG_FUNCTION_ARGS)
2045{
2046 PG_RETURN_DATUM(DirectFunctionCall5(
2048 PG_GETARG_DATUM(0), PG_GETARG_DATUM(1),
2049 PG_GETARG_DATUM(2), PG_GETARG_DATUM(3),
2050 Int32GetDatum(0) /* N-D mode */
2051 ));
2052}
2053
2054
2069float8
2070gserialized_sel_internal(PlannerInfo *root, List *args, int varRelid, int mode)
2071{
2072 VariableStatData vardata;
2073 Node *other = NULL;
2074 bool varonleft;
2075 ND_STATS *nd_stats = NULL;
2076
2077 GBOX search_box;
2078 float8 selectivity = 0;
2079 Const *otherConst;
2080
2081 POSTGIS_DEBUGF(2, "%s: entered function", __func__);
2082
2083 if (!get_restriction_variable(root, args, varRelid, &vardata, &other, &varonleft))
2084 {
2085 POSTGIS_DEBUGF(2, "%s: could not find vardata", __func__);
2086 return DEFAULT_ND_SEL;
2087 }
2088
2089 if (!IsA(other, Const))
2090 {
2091 ReleaseVariableStats(vardata);
2092 POSTGIS_DEBUGF(2, "%s: no constant argument, returning default selectivity %g", __func__, DEFAULT_ND_SEL);
2093 return DEFAULT_ND_SEL;
2094 }
2095
2096 otherConst = (Const*)other;
2097 if ((!otherConst) || otherConst->constisnull)
2098 {
2099 ReleaseVariableStats(vardata);
2100 POSTGIS_DEBUGF(2, "%s: constant argument is NULL", __func__);
2101 return DEFAULT_ND_SEL;
2102 }
2103
2104 if (!gserialized_datum_get_gbox_p(otherConst->constvalue, &search_box))
2105 {
2106 ReleaseVariableStats(vardata);
2107 POSTGIS_DEBUGF(2, "%s: search box is EMPTY", __func__);
2108 return 0.0;
2109 }
2110
2111 if (!vardata.statsTuple)
2112 {
2113 POSTGIS_DEBUGF(1, "%s: no statistics available on table. Empty? Need to ANALYZE?", __func__);
2114 return DEFAULT_ND_SEL;
2115 }
2116
2117 nd_stats = pg_nd_stats_from_tuple(vardata.statsTuple, mode);
2118 ReleaseVariableStats(vardata);
2119 selectivity = estimate_selectivity(&search_box, nd_stats, mode);
2120 if (nd_stats)
2121 pfree(nd_stats);
2122
2123 return selectivity;
2124}
2125
2127Datum gserialized_gist_sel(PG_FUNCTION_ARGS)
2128{
2129 PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
2130 // Oid operator_oid = PG_GETARG_OID(1);
2131 List *args = (List *) PG_GETARG_POINTER(2);
2132 int varRelid = PG_GETARG_INT32(3);
2133 int mode = PG_GETARG_INT32(4);
2134 float8 selectivity = gserialized_sel_internal(root, args, varRelid, mode);
2135 POSTGIS_DEBUGF(2, "%s: selectivity is %g", __func__, selectivity);
2136 PG_RETURN_FLOAT8(selectivity);
2137}
2138
2139/************************************************************************/
2140
2141
2142/*
2143 * Given an index and table column, confirm the
2144 * index was built on that column, and return the
2145 * corresponding index attribute for that column.
2146 */
2147static int16
2148index_has_attr(Oid index_oid, Oid table_oid, int16 table_attnum)
2149{
2150 HeapTuple index_tuple;
2151 Form_pg_index index_form;
2152 int16 index_attnum = InvalidAttrNumber;
2153
2154 /* Check if the index is on the desired column */
2155 index_tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));
2156 if (!HeapTupleIsValid(index_tuple))
2157 elog(ERROR, "cache lookup failed for index %u", index_oid);
2158
2159 index_form = (Form_pg_index) GETSTRUCT(index_tuple);
2160
2161 /* Something went wrong, this index isn't on our table of interest */
2162 if (index_form->indrelid != table_oid)
2163 elog(ERROR, "table=%u and index=%u are not related", table_oid, index_oid);
2164
2165 /* Check if the attnum is in the indkey array */
2166 for (int16 i = 0; i < (int16)(index_form->indkey.dim1); i++)
2167 {
2168 if (index_form->indkey.values[i] == table_attnum)
2169 {
2170 index_attnum = i+1;
2171 break;
2172 }
2173 }
2174 ReleaseSysCache(index_tuple);
2175 return index_attnum;
2176}
2177
2178
2179/*
2180 * Given an index return the access method.
2181 * (We only work with GIST access method.)
2182 */
2183static int
2184index_get_am(Oid index_oid)
2185{
2186 int index_am;
2187 Form_pg_class index_rel_form;
2188 HeapTuple index_rel_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(index_oid));
2189
2190 if (!HeapTupleIsValid(index_rel_tuple))
2191 elog(ERROR, "cache lookup failed for index %u", index_oid);
2192
2193 index_rel_form = (Form_pg_class) GETSTRUCT(index_rel_tuple);
2194 index_am = index_rel_form->relam;
2195 ReleaseSysCache(index_rel_tuple);
2196 return index_am;
2197}
2198
2199
2200/*
2201 * Given an index and index attribute, lookup the
2202 * key type (box2df or gidx) of that index column.
2203 */
2204static int
2205index_get_keytype (Oid index_oid, int16 index_attnum)
2206{
2207 Oid atttypid = InvalidOid;
2208 Form_pg_attribute att_form;
2209
2210 /* Get the key type for the index key? */
2211 HeapTuple att_tuple = SearchSysCache2(ATTNUM,
2212 ObjectIdGetDatum(index_oid),
2213 Int16GetDatum(index_attnum));
2214
2215 if (!HeapTupleIsValid(att_tuple))
2216 elog(ERROR, "cache lookup failed for index %u attribute %d", index_oid, index_attnum);
2217
2218 att_form = (Form_pg_attribute) GETSTRUCT(att_tuple);
2219 atttypid = att_form->atttypid;
2220 ReleaseSysCache(att_tuple);
2221 return atttypid;
2222}
2223
2224
2225/*
2226 * Given a table and attribute number, find any
2227 * "spatial index" of that attribute. For our purposes
2228 * a spatial index is one we can read the top page of,
2229 * namely a geometry or geography column, with
2230 * a GIST index having either a gidx or box2df key.
2231 */
2232static Oid
2233table_get_spatial_index(Oid table_oid, int16 attnum, int *key_type, int16 *idx_attnum)
2234{
2235 Relation table_rel;
2236 List *index_list;
2237 ListCell *lc;
2238
2239 /* Lookup our spatial index key types */
2240 Oid b2d_oid = postgis_oid(BOX2DFOID);
2241 Oid gdx_oid = postgis_oid(GIDXOID);
2242
2243 if (!(b2d_oid && gdx_oid))
2244 return InvalidOid;
2245
2246 /* Read a list of all indexes on this table */
2247 table_rel = RelationIdGetRelation(table_oid);
2248 index_list = RelationGetIndexList(table_rel);
2249 RelationClose(table_rel);
2250
2251 /* For each index associated with this table... */
2252 foreach(lc, index_list)
2253 {
2254 Oid index_oid = lfirst_oid(lc);
2255 Oid atttypid;
2256
2257 /* Is our attribute indexed by this index? */
2258 *idx_attnum = index_has_attr(index_oid, table_oid, attnum);
2259
2260 /* No, move on */
2261 if (*idx_attnum == InvalidAttrNumber)
2262 continue;
2263
2264 /* We only handle GIST spatial indexes */
2265 if (index_get_am(index_oid) != GIST_AM_OID)
2266 continue;
2267
2268 /* Is the column actually spatial? */
2269 /* Only if it uses our spatial key types */
2270 atttypid = index_get_keytype (index_oid, *idx_attnum);
2271 if (atttypid == b2d_oid || atttypid == gdx_oid)
2272 {
2273 /* Spatial key found in this index! */
2274 *key_type = (atttypid == b2d_oid ? STATISTIC_KIND_2D : STATISTIC_KIND_ND);
2275 return index_oid;
2276 }
2277 }
2278 return InvalidOid;
2279}
2280
2281/*
2282 * Given an index and indexed attribute, look up
2283 * the keys in the top page of the index, and using
2284 * the appropriate key type, return a box that is the
2285 * union of all those keys.
2286 */
2287static GBOX *
2288spatial_index_read_extent(Oid idx_oid, int idx_att_num, int key_type)
2289{
2290 BOX2DF *bounds_2df = NULL;
2291 GIDX *bounds_gidx = NULL;
2292 GBOX *gbox = NULL;
2293 Relation idx_rel;
2294 Buffer buffer;
2295 Page page;
2296 unsigned long offset;
2297 unsigned long offset_max;
2298
2299 if (!idx_oid)
2300 return NULL;
2301
2302 idx_rel = index_open(idx_oid, AccessShareLock);
2303 buffer = ReadBuffer(idx_rel, GIST_ROOT_BLKNO);
2304 page = (Page) BufferGetPage(buffer);
2305 offset = FirstOffsetNumber;
2306 offset_max = PageGetMaxOffsetNumber(page);
2307 while (offset <= offset_max)
2308 {
2309 ItemId iid = PageGetItemId(page, offset);
2310 IndexTuple ituple;
2311 if (!iid)
2312 {
2313 ReleaseBuffer(buffer);
2314 index_close(idx_rel, AccessShareLock);
2315 return NULL;
2316 }
2317 ituple = (IndexTuple) PageGetItem(page, iid);
2318 if (!GistTupleIsInvalid(ituple))
2319 {
2320 bool isnull;
2321 Datum idx_attr = index_getattr(ituple, idx_att_num, idx_rel->rd_att, &isnull);
2322 if (!isnull)
2323 {
2324 if (key_type == STATISTIC_KIND_2D)
2325 {
2326 BOX2DF *b = (BOX2DF*)DatumGetPointer(idx_attr);
2327 if (bounds_2df)
2328 box2df_merge(bounds_2df, b);
2329 else
2330 bounds_2df = box2df_copy(b);
2331 }
2332 else
2333 {
2334 GIDX *b = (GIDX*)DatumGetPointer(idx_attr);
2335 if (bounds_gidx)
2336 gidx_merge(&bounds_gidx, b);
2337 else
2338 bounds_gidx = gidx_copy(b);
2339 }
2340 }
2341 }
2342 offset++;
2343 }
2344
2345 ReleaseBuffer(buffer);
2346 index_close(idx_rel, AccessShareLock);
2347
2348 if (key_type == STATISTIC_KIND_2D && bounds_2df)
2349 {
2350 if (box2df_is_empty(bounds_2df))
2351 return NULL;
2352 gbox = gbox_new(0);
2353 box2df_to_gbox_p(bounds_2df, gbox);
2354 }
2355 else if (key_type == STATISTIC_KIND_ND && bounds_gidx)
2356 {
2357 lwflags_t flags = 0;
2358 if (gidx_is_unknown(bounds_gidx))
2359 return NULL;
2360 FLAGS_SET_Z(flags, GIDX_NDIMS(bounds_gidx) > 2);
2361 FLAGS_SET_M(flags, GIDX_NDIMS(bounds_gidx) > 3);
2362 gbox = gbox_new(flags);
2363 gbox_from_gidx(bounds_gidx, gbox, flags);
2364 }
2365 else
2366 return NULL;
2367
2368 return gbox;
2369}
2370
2371/*
2372CREATE OR REPLACE FUNCTION _postgis_index_extent(tbl regclass, col text)
2373 RETURNS box2d
2374 AS '$libdir/postgis-2.5','_postgis_gserialized_index_extent'
2375 LANGUAGE 'c' STABLE STRICT;
2376*/
2377
2380{
2381 GBOX *gbox = NULL;
2382 int key_type;
2383 int16 att_num, idx_att_num = InvalidAttrNumber;
2384 Oid tbl_oid = PG_GETARG_DATUM(0);
2385 char *col = text_to_cstring(PG_GETARG_TEXT_P(1));
2386 Oid idx_oid;
2387
2388 if(!tbl_oid)
2389 PG_RETURN_NULL();
2390
2391 /* We need to initialize the internal cache to access it later via postgis_oid() */
2392 postgis_initialize_cache();
2393
2394 att_num = get_attnum(tbl_oid, col);
2395 if (att_num == InvalidAttrNumber)
2396 PG_RETURN_NULL();
2397
2398 idx_oid = table_get_spatial_index(tbl_oid, att_num, &key_type, &idx_att_num);
2399 if (!idx_oid)
2400 PG_RETURN_NULL();
2401
2402 gbox = spatial_index_read_extent(idx_oid, idx_att_num, key_type);
2403 if (!gbox)
2404 PG_RETURN_NULL();
2405 else
2406 PG_RETURN_POINTER(gbox);
2407}
2408
2409
2410/*
2411 * Given a table and column name, look up the attribute number
2412 * and type of that column.
2413 */
2414static bool
2415get_attnum_attypid(Oid table_oid, const char *col, int16 *attnum, Oid *atttypid)
2416{
2417 HeapTuple att_tuple;
2418 Form_pg_attribute att;
2419
2420 if (!attnum || !atttypid)
2421 elog(ERROR, "%s got null input parameters", __func__);
2422
2423 /* Is the index on the column name we are looking for? */
2424 att_tuple = SearchSysCache2(ATTNAME,
2425 ObjectIdGetDatum(table_oid),
2426 PointerGetDatum(col));
2427
2428 if (!HeapTupleIsValid(att_tuple))
2429 return false;
2430
2431 att = (Form_pg_attribute) GETSTRUCT(att_tuple);
2432 *atttypid = att->atttypid;
2433 *attnum = att->attnum;
2434 ReleaseSysCache(att_tuple);
2435 return true;
2436}
2437
2438
2445Datum gserialized_estimated_extent(PG_FUNCTION_ARGS)
2446{
2447 text *coltxt = NULL;
2448 char *col = NULL;
2449 int16 attnum, idx_attnum;
2450 Oid atttypid = InvalidOid;
2451 char nsp_tbl[2*NAMEDATALEN+6];
2452 char *tbl;
2453 Oid tbl_oid, idx_oid = 0;
2454 ND_STATS *nd_stats;
2455 GBOX *gbox = NULL;
2456 bool only_parent = false;
2457 int key_type;
2458 Oid geographyOid = postgis_oid(GEOGRAPHYOID);
2459 Oid geometryOid = postgis_oid(GEOMETRYOID);
2460
2461 /* We need to initialize the internal cache to access it later via postgis_oid() */
2462 postgis_initialize_cache();
2463
2464 if (PG_NARGS() < 2 || PG_NARGS() > 4)
2465 elog(ERROR, "ST_EstimatedExtent() called with wrong number of arguments");
2466
2467 if ( PG_NARGS() == 4 )
2468 {
2469 only_parent = PG_GETARG_BOOL(3);
2470 }
2471 if ( PG_NARGS() >= 3 )
2472 {
2473 char *nsp = text_to_cstring(PG_GETARG_TEXT_P(0));
2474 tbl = text_to_cstring(PG_GETARG_TEXT_P(1));
2475 coltxt = PG_GETARG_TEXT_P(2);
2476 snprintf(nsp_tbl, sizeof(nsp_tbl), "\"%s\".\"%s\"", nsp, tbl);
2477 }
2478 if ( PG_NARGS() == 2 )
2479 {
2480 tbl = text_to_cstring(PG_GETARG_TEXT_P(0));
2481 coltxt = PG_GETARG_TEXT_P(1);
2482 snprintf(nsp_tbl, sizeof(nsp_tbl), "\"%s\"", tbl);
2483 }
2484
2485 /* Parse the namespace/table strings and lookup in system catalogs */
2486 tbl_oid = DatumGetObjectId(DirectFunctionCall1(regclassin, CStringGetDatum(nsp_tbl)));
2487 if (!tbl_oid)
2488 elog(ERROR, "cannot lookup table %s", nsp_tbl);
2489
2490 /* Get the attribute number and type from the column name */
2491 col = text_to_cstring(coltxt);
2492 if (!get_attnum_attypid(tbl_oid, col, &attnum, &atttypid))
2493 elog(ERROR, "column %s.\"%s\" does not exist", nsp_tbl, col);
2494
2495 /* We can only do estimates on geograpy and geometry */
2496 if ((atttypid != geographyOid) && (atttypid != geometryOid))
2497 {
2498 elog(ERROR, "column %s.\"%s\" must be a geometry or geography", nsp_tbl, col);
2499 }
2500
2501 /* Read the extent from the head of the spatial index */
2502 /* works if there is a spatial index */
2503 idx_oid = table_get_spatial_index(tbl_oid, attnum, &key_type, &idx_attnum);
2504 if (idx_oid != InvalidOid)
2505 {
2506 /* TODO: how about only_parent ? */
2507 gbox = spatial_index_read_extent(idx_oid, idx_attnum, key_type);
2508 elog(DEBUG3, "index for %s.\"%s\" exists, reading gbox from there", nsp_tbl, col);
2509 if (!gbox) PG_RETURN_NULL();
2510 }
2511 /* Read the extent from the stats tables, */
2512 /* works if ANALYZE has been run */
2513 else
2514 {
2515 int stats_mode = 2;
2516 elog(DEBUG3, "index for %s.\"%s\" does not exist", nsp_tbl, col);
2517
2518 /* For a geography column, we need the XYZ geocentric bounds */
2519 if (atttypid == geographyOid)
2520 stats_mode = 3;
2521
2522 /* ND stats include an extent for the histogram */
2523 nd_stats = pg_get_nd_stats_by_name(tbl_oid, coltxt, stats_mode, only_parent);
2524
2525 /* Error out on no stats */
2526 if (!nd_stats)
2527 {
2528 elog(WARNING, "stats for \"%s.%s\" do not exist", tbl, col);
2529 PG_RETURN_NULL();
2530 }
2531
2532 /* Construct the box */
2533 gbox = gbox_new(0);
2534 gbox->xmin = nd_stats->extent.min[0];
2535 gbox->xmax = nd_stats->extent.max[0];
2536 gbox->ymin = nd_stats->extent.min[1];
2537 gbox->ymax = nd_stats->extent.max[1];
2538 if (stats_mode != 2)
2539 {
2540 FLAGS_SET_Z(gbox->flags, 1);
2541 gbox->zmin = nd_stats->extent.min[2];
2542 gbox->zmax = nd_stats->extent.max[2];
2543 }
2544
2545 pfree(nd_stats);
2546 }
2547
2548 /* Convert geocentric geography box into a planar box */
2549 /* that users understand */
2550 if (atttypid == geographyOid)
2551 {
2552 GBOX *gbox_planar = gbox_new(0);
2553 gbox_geocentric_get_gbox_cartesian(gbox, gbox_planar);
2554 PG_RETURN_POINTER(gbox_planar);
2555 }
2556 else
2557 PG_RETURN_POINTER(gbox);
2558}
2559
2560/*
2561 * Legacy prototype for Estimated_Extent()
2562 */
2564Datum geometry_estimated_extent(PG_FUNCTION_ARGS)
2565{
2566 if ( PG_NARGS() == 3 )
2567 {
2568 PG_RETURN_DATUM(
2569 DirectFunctionCall3(gserialized_estimated_extent,
2570 PG_GETARG_DATUM(0),
2571 PG_GETARG_DATUM(1),
2572 PG_GETARG_DATUM(2)));
2573 }
2574 else if ( PG_NARGS() == 2 )
2575 {
2576 PG_RETURN_DATUM(
2577 DirectFunctionCall2(gserialized_estimated_extent,
2578 PG_GETARG_DATUM(0),
2579 PG_GETARG_DATUM(1)));
2580 }
2581
2582 elog(ERROR, "geometry_estimated_extent() called with wrong number of arguments");
2583 PG_RETURN_NULL();
2584}
GBOX * gbox_new(lwflags_t flags)
Create a new gbox with the dimensionality indicated by the flags.
Definition gbox.c:32
char * gbox_to_string(const GBOX *gbox)
Allocate a string representation of the GBOX, based on dimensionality of flags.
Definition gbox.c:404
int gbox_is_valid(const GBOX *gbox)
Return false if any of the dimensions is NaN or infinite.
Definition gbox.c:197
static int nd_box_intersects(const ND_BOX *a, const ND_BOX *b, int ndims)
Return true if ND_BOX a overlaps b, false otherwise.
static int nd_box_init_bounds(ND_BOX *a)
Prepare an ND_BOX for bounds calculation: set the maxes to the smallest thing possible and the mins t...
Datum gserialized_gist_joinsel_2d(PG_FUNCTION_ARGS)
static int index_get_keytype(Oid index_oid, int16 index_attnum)
static int nd_increment(ND_IBOX *ibox, int ndims, int *counter)
Given an n-d index array (counter), and a domain to increment it in (ibox) increment it by one,...
static ND_STATS * pg_get_nd_stats(const Oid table_oid, AttrNumber att_num, int mode, bool only_parent)
Pull the stats object from the PgSQL system catalogs.
#define STATISTIC_SLOT_ND
static int gbox_ndims(const GBOX *gbox)
Given that geodetic boxes are X/Y/Z regardless of the underlying geometry dimensionality and other bo...
static char * nd_box_to_json(const ND_BOX *nd_box, int ndims)
Convert an ND_BOX to a JSON string for printing.
Datum gserialized_gist_joinsel_nd(PG_FUNCTION_ARGS)
static float8 estimate_selectivity(const GBOX *box, const ND_STATS *nd_stats, int mode)
This function returns an estimate of the selectivity of a search GBOX by looking at data in the ND_ST...
static int range_full(int *vals, int nvals)
The difference between the fourth and first quintile values, the "inter-quintile range".
static char * nd_stats_to_json(const ND_STATS *nd_stats)
Convert an ND_STATS to a JSON representation for external use.
#define STATISTIC_KIND_2D
static int nd_box_merge(const ND_BOX *source, ND_BOX *target)
Expand the bounds of target to include source.
#define DEFAULT_ND_JOINSEL
#define STATISTIC_KIND_ND
#define FALLBACK_ND_SEL
More modest fallback selectivity factor.
PG_FUNCTION_INFO_V1(gserialized_gist_joinsel_nd)
For (geometry &&& geometry) and (geography && geography) we call into the N-D mode.
Datum gserialized_estimated_extent(PG_FUNCTION_ARGS)
#define DEFAULT_ND_SEL
Default geometry selectivity factor.
Datum _postgis_gserialized_joinsel(PG_FUNCTION_ARGS)
static double total_double(const double *vals, int nvals)
Given double array, return sum of values.
static bool get_attnum_attypid(Oid table_oid, const char *col, int16 *attnum, Oid *atttypid)
#define SDFACTOR
static GBOX * spatial_index_read_extent(Oid idx_oid, int idx_att_num, int key_type)
#define FALLBACK_ND_JOINSEL
#define MAX_NUM_BINS
static void compute_gserialized_stats_mode(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc, int sample_rows, double total_rows, int mode)
The gserialized_analyze_nd sets this function as a callback on the stats object when called by the AN...
static int cmp_int(const void *a, const void *b)
Integer comparison function for qsort.
static int nd_box_contains(const ND_BOX *a, const ND_BOX *b, int ndims)
Return true if ND_BOX a contains b, false otherwise.
static void compute_gserialized_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc, int sample_rows, double total_rows)
In order to do useful selectivity calculations in both 2-D and N-D modes, we actually have to generat...
static int text_p_get_mode(const text *txt)
Utility function to see if the first letter of the mode argument is 'N'.
static ND_STATS * pg_nd_stats_from_tuple(HeapTuple stats_tuple, int mode)
Datum gserialized_gist_sel(PG_FUNCTION_ARGS)
float8 gserialized_joinsel_internal(PlannerInfo *root, List *args, JoinType jointype, int mode)
static void nd_box_from_gbox(const GBOX *gbox, ND_BOX *nd_box)
Set the values of an ND_BOX from a GBOX.
float8 gserialized_sel_internal(PlannerInfo *root, List *args, int varRelid, int mode)
This function should return an estimation of the number of rows returned by a query involving an over...
Datum _postgis_gserialized_stats(PG_FUNCTION_ARGS)
static int nd_box_init(ND_BOX *a)
Zero out an ND_BOX.
Datum gserialized_gist_sel_nd(PG_FUNCTION_ARGS)
#define MAX_DIMENSION_WIDTH
Maximum width of a dimension that we'll bother trying to compute statistics on.
Datum gserialized_analyze_nd(PG_FUNCTION_ARGS)
static Oid table_get_spatial_index(Oid tbl_oid, int16 attnum, int *key_type, int16 *idx_attnum)
static int index_get_am(Oid index_oid)
Datum _postgis_gserialized_sel(PG_FUNCTION_ARGS)
#define BIN_MIN_SIZE
static ND_STATS * pg_get_nd_stats_by_name(const Oid table_oid, const text *att_text, int mode, bool only_parent)
Pull the stats object from the PgSQL system catalogs.
static int nd_box_expand(ND_BOX *nd_box, double expansion_factor)
Expand an ND_BOX ever so slightly.
static int nd_box_overlap(const ND_STATS *nd_stats, const ND_BOX *nd_box, ND_IBOX *nd_ibox)
What stats cells overlap with this ND_BOX? Put the lowest cell addresses in ND_IBOX->min and the high...
static float8 estimate_join_selectivity(const ND_STATS *s1, const ND_STATS *s2)
Given two statistics histograms, what is the selectivity of a join driven by the && or &&& operator?
Datum gserialized_gist_sel_2d(PG_FUNCTION_ARGS)
Datum _postgis_gserialized_index_extent(PG_FUNCTION_ARGS)
Datum gserialized_gist_joinsel(PG_FUNCTION_ARGS)
#define MIN_DIMENSION_WIDTH
Minimum width of a dimension that we'll bother trying to compute statistics on.
static char * nd_stats_to_grid(const ND_STATS *stats)
Create a printable view of the ND_STATS histogram.
Datum geometry_estimated_extent(PG_FUNCTION_ARGS)
#define STATISTIC_SLOT_2D
static int nd_box_array_distribution(const ND_BOX **nd_boxes, int num_boxes, const ND_BOX *extent, int ndims, double *distribution)
Calculate how much a set of boxes is homogeneously distributed or contentrated within one dimension,...
static int16 index_has_attr(Oid index_oid, Oid table_oid, int16 table_attnum)
struct ND_STATS_T ND_STATS
static int histogram_axis_cells(int histo_cells_target, int histo_ndims, double edge_ratio)
static double nd_box_ratio(const ND_BOX *cover, const ND_BOX *target, int ndims)
static int histogram_cell_budget(double total_rows, int ndims, int attstattarget)
static int nd_stats_value_index(const ND_STATS *stats, const int *indexes)
void box2df_merge(BOX2DF *b_union, BOX2DF *b_new)
bool box2df_is_empty(const BOX2DF *a)
int box2df_to_gbox_p(BOX2DF *a, GBOX *box)
int gserialized_datum_get_gbox_p(Datum gsdatum, GBOX *gbox)
Given a GSERIALIZED datum, as quickly as possible (peaking into the top of the memory) return the gbo...
BOX2DF * box2df_copy(BOX2DF *b)
bool gidx_is_unknown(const GIDX *a)
GIDX * gidx_copy(GIDX *b)
void gidx_merge(GIDX **b_union, GIDX *b_new)
#define LW_FAILURE
Definition liblwgeom.h:96
uint16_t lwflags_t
Definition liblwgeom.h:299
#define FLAGS_GET_Z(flags)
Definition liblwgeom.h:165
#define FLAGS_GET_M(flags)
Definition liblwgeom.h:166
#define FLAGS_SET_M(flags, value)
Definition liblwgeom.h:173
#define FLAGS_SET_Z(flags, value)
Definition liblwgeom.h:172
#define FLAGS_GET_GEODETIC(flags)
Definition liblwgeom.h:168
This library is the generic geometry handling section of PostGIS.
int gbox_geocentric_get_gbox_cartesian(const GBOX *gbox_geocentric, GBOX *gbox_planar)
#define str(s)
Datum buffer(PG_FUNCTION_ARGS)
stringbuffer_t * stringbuffer_create(void)
Allocate a new stringbuffer_t.
int stringbuffer_aprintf(stringbuffer_t *s, const char *fmt,...)
Appends a formatted string to the current string buffer, using the format and argument list provided.
char * stringbuffer_getstringcopy(stringbuffer_t *s)
Returns a newly allocated string large enough to contain the current state of the string.
void stringbuffer_destroy(stringbuffer_t *s)
Free the stringbuffer_t and all memory managed within it.
static void stringbuffer_append(stringbuffer_t *s, const char *a)
Append the specified string to the stringbuffer_t.
double ymax
Definition liblwgeom.h:357
double zmax
Definition liblwgeom.h:359
double xmax
Definition liblwgeom.h:355
double zmin
Definition liblwgeom.h:358
double mmax
Definition liblwgeom.h:361
double ymin
Definition liblwgeom.h:356
double xmin
Definition liblwgeom.h:354
double mmin
Definition liblwgeom.h:360
lwflags_t flags
Definition liblwgeom.h:353
AnalyzeAttrComputeStatsFunc std_compute_stats