PostGIS 3.6.2dev-r@@SVN_REVISION@@
Loading...
Searching...
No Matches

◆ compute_gserialized_stats_mode()

static void compute_gserialized_stats_mode ( VacAttrStats *  stats,
AnalyzeAttrFetchFunc  fetchfunc,
int  sample_rows,
double  total_rows,
int  mode 
)
static

The gserialized_analyze_nd sets this function as a callback on the stats object when called by the ANALYZE command.

ANALYZE then gathers the requisite number of sample rows and then calls this function.

We could also pass stats->extra_data in from gserialized_analyze_nd (things like the column type or other stuff from the system catalogs) but so far we don't use that capability.

Our job is to build some statistics on the sample data for use by operator estimators.

We will populate an n-d histogram using the provided sample rows. The selectivity estimators (sel and joinsel) can then use the histogram

Definition at line 1227 of file gserialized_estimate.c.

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}
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...
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,...
#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.
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 STATISTIC_KIND_ND
static double total_double(const double *vals, int nvals)
Given double array, return sum of values.
#define SDFACTOR
static void nd_box_from_gbox(const GBOX *gbox, ND_BOX *nd_box)
Set the values of an ND_BOX from a GBOX.
static int nd_box_init(ND_BOX *a)
Zero out an ND_BOX.
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...
#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,...
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)
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...
#define LW_FAILURE
Definition liblwgeom.h:96
double zmax
Definition liblwgeom.h:359
double zmin
Definition liblwgeom.h:358
double mmax
Definition liblwgeom.h:361
double mmin
Definition liblwgeom.h:360

References ND_STATS_T::cells_covered, ND_STATS_T::extent, gbox_is_valid(), gbox_ndims(), gserialized_datum_get_gbox_p(), histogram_axis_cells(), histogram_cell_budget(), ND_STATS_T::histogram_cells, ND_STATS_T::histogram_features, LW_FAILURE, ND_BOX_T::max, ND_IBOX_T::max, ND_BOX_T::min, ND_IBOX_T::min, GBOX::mmax, GBOX::mmin, nd_box_array_distribution(), nd_box_expand(), nd_box_from_gbox(), nd_box_init(), nd_box_init_bounds(), nd_box_intersects(), nd_box_merge(), nd_box_overlap(), nd_box_ratio(), nd_box_to_json(), ND_DIMS, nd_increment(), nd_stats_to_json(), nd_stats_value_index(), ND_STATS_T::ndims, ND_STATS_T::not_null_features, ND_STATS_T::sample_features, SDFACTOR, ND_STATS_T::size, STATISTIC_KIND_2D, STATISTIC_KIND_ND, STATISTIC_SLOT_2D, STATISTIC_SLOT_ND, ND_STATS_T::table_features, total_double(), ND_STATS_T::value, GBOX::zmax, and GBOX::zmin.

Referenced by compute_gserialized_stats().

Here is the call graph for this function:
Here is the caller graph for this function: