PostGIS 3.7.0dev-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 1223 of file gserialized_estimate.c.

1225{
1226 MemoryContext old_context;
1227 int d, i; /* Counters */
1228 int notnull_cnt = 0; /* # not null rows in the sample */
1229 int null_cnt = 0; /* # null rows in the sample */
1230 int histogram_features = 0; /* # rows that actually got counted in the histogram */
1231
1232 ND_STATS *nd_stats; /* Our histogram */
1233 size_t nd_stats_size; /* Size to allocate */
1234
1235 double total_width = 0; /* # of bytes used by sample */
1236 double total_cell_count = 0; /* # of cells in histogram affected by sample */
1237
1238 ND_BOX sum; /* Sum of extents of sample boxes */
1239 ND_BOX avg; /* Avg of extents of sample boxes */
1240 ND_BOX stddev; /* StdDev of extents of sample boxes */
1241
1242 const ND_BOX **sample_boxes; /* ND_BOXes for each of the sample features */
1243 ND_BOX sample_extent; /* Extent of the raw sample */
1244 int histo_size[ND_DIMS]; /* histogram nrows, ncols, etc */
1245 ND_BOX histo_extent; /* Spatial extent of the histogram */
1246 ND_BOX histo_extent_new; /* Temporary variable */
1247 int histo_cells_target; /* Number of cells we will shoot for, given the stats target */
1248 int histo_cells; /* Number of cells in the histogram */
1249 int histo_cells_new = 1; /* Temporary variable */
1250
1251 int ndims = 2; /* Dimensionality of the sample */
1252 int histo_ndims = 0; /* Dimensionality of the histogram */
1253 double sample_distribution[ND_DIMS]; /* How homogeneous is distribution of sample in each axis? */
1254 double total_distribution; /* Total of sample_distribution */
1255
1256 int stats_slot; /* What slot is this data going into? (2D vs ND) */
1257 int stats_kind; /* And this is what? (2D vs ND) */
1258
1259 /* Initialize sum and stddev */
1260 nd_box_init(&sum);
1261 nd_box_init(&stddev);
1262 nd_box_init(&avg);
1263 nd_box_init(&histo_extent);
1264 nd_box_init(&histo_extent_new);
1265
1266 /*
1267 * This is where gserialized_analyze_nd
1268 * should put its' custom parameters.
1269 */
1270 /* void *mystats = stats->extra_data; */
1271
1272 POSTGIS_DEBUG(2, "compute_gserialized_stats called");
1273 POSTGIS_DEBUGF(3, " # sample_rows: %d", sample_rows);
1274 POSTGIS_DEBUGF(3, " estimate of total_rows: %.6g", total_rows);
1275
1276 /*
1277 * We might need less space, but don't think
1278 * its worth saving...
1279 */
1280 sample_boxes = palloc(sizeof(ND_BOX*) * sample_rows);
1281
1282 /*
1283 * First scan:
1284 * o read boxes
1285 * o find dimensionality of the sample
1286 * o find extent of the sample
1287 * o count null-infinite/not-null values
1288 * o compute total_width
1289 * o compute total features's box area (for avgFeatureArea)
1290 * o sum features box coordinates (for standard deviation)
1291 */
1292 for ( i = 0; i < sample_rows; i++ )
1293 {
1294 Datum datum;
1295 GBOX gbox = {0};
1296 ND_BOX *nd_box;
1297 bool is_null;
1298
1299 datum = fetchfunc(stats, i, &is_null);
1300
1301 /* Skip all NULLs. */
1302 if ( is_null )
1303 {
1304 POSTGIS_DEBUGF(4, " skipped null geometry %d", i);
1305 null_cnt++;
1306 continue;
1307 }
1308
1309 /* Read the bounds from the gserialized. */
1310 if (LW_FAILURE == gserialized_datum_get_gbox_p(datum, &gbox))
1311 {
1312 /* Skip empties too. */
1313 POSTGIS_DEBUGF(3, " skipped empty geometry %d", i);
1314 continue;
1315 }
1316
1317 /* If we're in 2D mode, zero out the higher dimensions for "safety" */
1318 if ( mode == 2 )
1319 gbox.zmin = gbox.zmax = gbox.mmin = gbox.mmax = 0.0;
1320
1321 /* Check bounds for validity (finite and not NaN) */
1322 if ( ! gbox_is_valid(&gbox) )
1323 {
1324 POSTGIS_DEBUGF(3, " skipped infinite/nan geometry %d", i);
1325 continue;
1326 }
1327
1328 /*
1329 * In N-D mode, set the ndims to the maximum dimensionality found
1330 * in the sample. Otherwise, leave at ndims == 2.
1331 */
1332 if ( mode != 2 )
1333 ndims = Max(gbox_ndims(&gbox), ndims);
1334
1335 /* Convert gbox to n-d box */
1336 nd_box = palloc(sizeof(ND_BOX));
1337 nd_box_from_gbox(&gbox, nd_box);
1338
1339 /* Cache n-d bounding box */
1340 sample_boxes[notnull_cnt] = nd_box;
1341
1342 /* Initialize sample extent before merging first entry */
1343 if ( ! notnull_cnt )
1344 nd_box_init_bounds(&sample_extent);
1345
1346 /* Add current sample to overall sample extent */
1347 nd_box_merge(nd_box, &sample_extent);
1348
1349 /* How many bytes does this sample use? */
1350 total_width += toast_raw_datum_size(datum);
1351
1352 /* Add bounds coordinates to sums for stddev calculation */
1353 for ( d = 0; d < ndims; d++ )
1354 {
1355 sum.min[d] += nd_box->min[d];
1356 sum.max[d] += nd_box->max[d];
1357 }
1358
1359 /* Increment our "good feature" count */
1360 notnull_cnt++;
1361
1362 /* Give backend a chance of interrupting us */
1363#if POSTGIS_PGSQL_VERSION >= 180
1364 vacuum_delay_point(true);
1365#else
1366 vacuum_delay_point();
1367#endif
1368 }
1369
1370#if POSTGIS_PGSQL_VERSION >= 170
1371 POSTGIS_DEBUGF(3, " stats->attstattarget: %d", stats->attstattarget);
1372 histo_cells_target = histogram_cell_budget(total_rows, ndims, stats->attstattarget);
1373#else
1374 POSTGIS_DEBUGF(3, " stats->attr->attstattarget: %d", stats->attr->attstattarget);
1375 histo_cells_target = histogram_cell_budget(total_rows, ndims, stats->attr->attstattarget);
1376#endif
1377 POSTGIS_DEBUGF(3, " target # of histogram cells: %d", histo_cells_target);
1378
1379 /* If there's no useful features, we can't work out stats */
1380 if ( ! notnull_cnt )
1381 {
1382 stats->stats_valid = false;
1383 return;
1384 }
1385
1386 POSTGIS_DEBUGF(3, " sample_extent: %s", nd_box_to_json(&sample_extent, ndims));
1387
1388 /*
1389 * Second scan:
1390 * o compute standard deviation
1391 */
1392 for ( d = 0; d < ndims; d++ )
1393 {
1394 /* Calculate average bounds values */
1395 avg.min[d] = sum.min[d] / notnull_cnt;
1396 avg.max[d] = sum.max[d] / notnull_cnt;
1397
1398 /* Calculate standard deviation for this dimension bounds */
1399 for ( i = 0; i < notnull_cnt; i++ )
1400 {
1401 const ND_BOX *ndb = sample_boxes[i];
1402 stddev.min[d] += (ndb->min[d] - avg.min[d]) * (ndb->min[d] - avg.min[d]);
1403 stddev.max[d] += (ndb->max[d] - avg.max[d]) * (ndb->max[d] - avg.max[d]);
1404 }
1405 stddev.min[d] = sqrt(stddev.min[d] / notnull_cnt);
1406 stddev.max[d] = sqrt(stddev.max[d] / notnull_cnt);
1407
1408 /* Histogram bounds for this dimension bounds is avg +/- SDFACTOR * stdev */
1409 histo_extent.min[d] = Max(avg.min[d] - SDFACTOR * stddev.min[d], sample_extent.min[d]);
1410 histo_extent.max[d] = Min(avg.max[d] + SDFACTOR * stddev.max[d], sample_extent.max[d]);
1411 }
1412
1413 /*
1414 * Third scan:
1415 * o skip hard deviants
1416 * o compute new histogram box
1417 */
1418 nd_box_init_bounds(&histo_extent_new);
1419 for ( i = 0; i < notnull_cnt; i++ )
1420 {
1421 const ND_BOX *ndb = sample_boxes[i];
1422 /* Skip any hard deviants (boxes entirely outside our histo_extent */
1423 if ( ! nd_box_intersects(&histo_extent, ndb, ndims) )
1424 {
1425 POSTGIS_DEBUGF(4, " feature %d is a hard deviant, skipped", i);
1426 sample_boxes[i] = NULL;
1427 continue;
1428 }
1429 /* Expand our new box to fit all the other features. */
1430 nd_box_merge(ndb, &histo_extent_new);
1431 }
1432 /*
1433 * Expand the box slightly (1%) to avoid edge effects
1434 * with objects that are on the boundary
1435 */
1436 nd_box_expand(&histo_extent_new, 0.01);
1437 histo_extent = histo_extent_new;
1438
1439 /*
1440 * How should we allocate our histogram cells to the
1441 * different dimensions? We can't do it by raw dimensional width,
1442 * because in x/y/z space, the z can have different units
1443 * from the x/y. Similarly for x/y/t space.
1444 * So, we instead calculate how much features overlap
1445 * each other in their dimension to figure out which
1446 * dimensions have useful selectivity characteristics (more
1447 * variability in density) and therefore would find
1448 * more cells useful (to distinguish between dense places and
1449 * homogeneous places).
1450 */
1451 nd_box_array_distribution(sample_boxes, notnull_cnt, &histo_extent, ndims,
1452 sample_distribution);
1453
1454 /*
1455 * The sample_distribution array now tells us how spread out the
1456 * data is in each dimension, so we use that data to allocate
1457 * the histogram cells we have available.
1458 * At this point, histo_cells_target is the approximate target number
1459 * of cells.
1460 */
1461
1462 /*
1463 * Some dimensions have basically a uniform distribution, we want
1464 * to allocate no cells to those dimensions, only to dimensions
1465 * that have some interesting differences in data distribution.
1466 * Here we count up the number of interesting dimensions
1467 */
1468 for ( d = 0; d < ndims; d++ )
1469 {
1470 if ( sample_distribution[d] > 0 )
1471 histo_ndims++;
1472 }
1473
1474 if ( histo_ndims == 0 )
1475 {
1476 /* Special case: all our dimensions had low variability! */
1477 /* We just divide the cells up evenly */
1478 POSTGIS_DEBUG(3, " special case: no axes have variability");
1479 histo_cells_new = 1;
1480 for ( d = 0; d < ndims; d++ )
1481 {
1482 histo_size[d] = (int)pow((double)histo_cells_target, 1/(double)ndims);
1483 if ( ! histo_size[d] )
1484 histo_size[d] = 1;
1485 POSTGIS_DEBUGF(3, " histo_size[d]: %d", histo_size[d]);
1486 histo_cells_new *= histo_size[d];
1487 }
1488 POSTGIS_DEBUGF(3, " histo_cells_new: %d", histo_cells_new);
1489 }
1490 else
1491 {
1492 /*
1493 * We're going to express the amount of variability in each dimension
1494 * as a proportion of the total variability and allocate cells in that
1495 * dimension relative to that proportion.
1496 */
1497 POSTGIS_DEBUG(3, " allocating histogram axes based on axis variability");
1498 total_distribution = total_double(sample_distribution, ndims); /* First get the total */
1499 POSTGIS_DEBUGF(3, " total_distribution: %.8g", total_distribution);
1500 histo_cells_new = 1; /* For the number of cells in the final histogram */
1501 for ( d = 0; d < ndims; d++ )
1502 {
1503 if ( sample_distribution[d] == 0 ) /* Uninteresting dimensions don't get any room */
1504 {
1505 histo_size[d] = 1;
1506 }
1507 else /* Interesting dimension */
1508 {
1509 /* How does this dims variability compare to the total? */
1510 float edge_ratio = (float)sample_distribution[d] / (float)total_distribution;
1511 /*
1512 * Scale the target cells number by the # of dims and ratio,
1513 * then take the appropriate root to get the estimated number of cells
1514 * on this axis (eg, pow(0.5) for 2d, pow(0.333) for 3d, pow(0.25) for 4d)
1515 * The dedicated helper clamps pathological floating point inputs so we
1516 * do not resurrect the NaN propagation reported in #5959 on amd64.
1517 */
1518 histo_size[d] = histogram_axis_cells(histo_cells_target, histo_ndims, edge_ratio);
1519 }
1520 histo_cells_new *= histo_size[d];
1521 }
1522 POSTGIS_DEBUGF(3, " histo_cells_new: %d", histo_cells_new);
1523 }
1524
1525 /* Update histo_cells to the actual number of cells we need to allocate */
1526 histo_cells = histo_cells_new;
1527 POSTGIS_DEBUGF(3, " histo_cells: %d", histo_cells);
1528
1529 /*
1530 * Create the histogram (ND_STATS) in the stats memory context
1531 */
1532 old_context = MemoryContextSwitchTo(stats->anl_context);
1533 nd_stats_size = sizeof(ND_STATS) + ((histo_cells - 1) * sizeof(float4));
1534 nd_stats = palloc(nd_stats_size);
1535 memset(nd_stats, 0, nd_stats_size); /* Initialize all values to 0 */
1536 MemoryContextSwitchTo(old_context);
1537
1538 /* Initialize the #ND_STATS objects */
1539 nd_stats->ndims = ndims;
1540 nd_stats->extent = histo_extent;
1541 nd_stats->sample_features = sample_rows;
1542 nd_stats->table_features = total_rows;
1543 nd_stats->not_null_features = notnull_cnt;
1544 /* Copy in the histogram dimensions */
1545 for ( d = 0; d < ndims; d++ )
1546 nd_stats->size[d] = histo_size[d];
1547
1548 /*
1549 * Fourth scan:
1550 * o fill histogram values with the proportion of
1551 * features' bbox overlaps: a feature's bvol
1552 * can fully overlap (1) or partially overlap
1553 * (fraction of 1) an histogram cell.
1554 *
1555 * Note that we are filling each cell with the "portion of
1556 * the feature's box that overlaps the cell". So, if we sum
1557 * up the values in the histogram, we could get the
1558 * histogram feature count.
1559 *
1560 */
1561 for ( i = 0; i < notnull_cnt; i++ )
1562 {
1563 const ND_BOX *nd_box;
1564 ND_IBOX nd_ibox;
1565 int at[ND_DIMS];
1566 double num_cells = 0;
1567 double min[ND_DIMS] = {0.0, 0.0, 0.0, 0.0};
1568 double max[ND_DIMS] = {0.0, 0.0, 0.0, 0.0};
1569 double cellsize[ND_DIMS] = {0.0, 0.0, 0.0, 0.0};
1570
1571 nd_box = sample_boxes[i];
1572 if ( ! nd_box ) continue; /* Skip Null'ed out hard deviants */
1573
1574 /* Give backend a chance of interrupting us */
1575#if POSTGIS_PGSQL_VERSION >= 180
1576 vacuum_delay_point(true);
1577#else
1578 vacuum_delay_point();
1579#endif
1580
1581 /* Find the cells that overlap with this box and put them into the ND_IBOX */
1582 nd_box_overlap(nd_stats, nd_box, &nd_ibox);
1583 memset(at, 0, sizeof(int)*ND_DIMS);
1584
1585 POSTGIS_DEBUGF(3, " feature %d: ibox (%d, %d, %d, %d) (%d, %d, %d, %d)", i,
1586 nd_ibox.min[0], nd_ibox.min[1], nd_ibox.min[2], nd_ibox.min[3],
1587 nd_ibox.max[0], nd_ibox.max[1], nd_ibox.max[2], nd_ibox.max[3]);
1588
1589 for ( d = 0; d < nd_stats->ndims; d++ )
1590 {
1591 /* Initialize the starting values */
1592 at[d] = nd_ibox.min[d];
1593 min[d] = nd_stats->extent.min[d];
1594 max[d] = nd_stats->extent.max[d];
1595 cellsize[d] = (max[d] - min[d])/(nd_stats->size[d]);
1596 }
1597
1598 /*
1599 * Move through all the overlapped histogram cells values and
1600 * add the box overlap proportion to them.
1601 */
1602 do
1603 {
1604 ND_BOX nd_cell = { {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0} };
1605 double ratio;
1606 /* Create a box for this histogram cell */
1607 for ( d = 0; d < nd_stats->ndims; d++ )
1608 {
1609 nd_cell.min[d] = min[d] + (at[d]+0) * cellsize[d];
1610 nd_cell.max[d] = min[d] + (at[d]+1) * cellsize[d];
1611 }
1612
1613 /*
1614 * If a feature box is completely inside one cell the ratio will be
1615 * 1.0. If a feature box is 50% in two cells, each cell will get
1616 * 0.5 added on.
1617 */
1618 ratio = nd_box_ratio(&nd_cell, nd_box, nd_stats->ndims);
1619 nd_stats->value[nd_stats_value_index(nd_stats, at)] += ratio;
1620 num_cells += ratio;
1621 POSTGIS_DEBUGF(3, " ratio (%.8g) num_cells (%.8g)", ratio, num_cells);
1622 POSTGIS_DEBUGF(3, " at (%d, %d, %d, %d)", at[0], at[1], at[2], at[3]);
1623 }
1624 while ( nd_increment(&nd_ibox, nd_stats->ndims, at) );
1625
1626 /* Keep track of overall number of overlaps counted */
1627 total_cell_count += num_cells;
1628 /* How many features have we added to this histogram? */
1629 histogram_features++;
1630 }
1631
1632 POSTGIS_DEBUGF(3, " histogram_features: %d", histogram_features);
1633 POSTGIS_DEBUGF(3, " sample_rows: %d", sample_rows);
1634 POSTGIS_DEBUGF(3, " table_rows: %.6g", total_rows);
1635
1636 /* Error out if we got no sample information */
1637 if ( ! histogram_features )
1638 {
1639 POSTGIS_DEBUG(3, " no stats have been gathered");
1640 elog(NOTICE, " no features lie in the stats histogram, invalid stats");
1641 stats->stats_valid = false;
1642 return;
1643 }
1644
1645 nd_stats->histogram_features = histogram_features;
1646 nd_stats->histogram_cells = histo_cells;
1647 nd_stats->cells_covered = total_cell_count;
1648
1649 /* Put this histogram data into the right slot/kind */
1650 if ( mode == 2 )
1651 {
1652 stats_slot = STATISTIC_SLOT_2D;
1653 stats_kind = STATISTIC_KIND_2D;
1654 }
1655 else
1656 {
1657 stats_slot = STATISTIC_SLOT_ND;
1658 stats_kind = STATISTIC_KIND_ND;
1659 }
1660
1661 /* Write the statistics data */
1662 stats->stakind[stats_slot] = stats_kind;
1663 stats->staop[stats_slot] = InvalidOid;
1664 stats->stanumbers[stats_slot] = (float4*)nd_stats;
1665 stats->numnumbers[stats_slot] = nd_stats_size/sizeof(float4);
1666 stats->stanullfrac = (float4)null_cnt/sample_rows;
1667 stats->stawidth = total_width/notnull_cnt;
1668 stats->stadistinct = -1.0;
1669 stats->stats_valid = true;
1670
1671 POSTGIS_DEBUGF(3, " out: slot 0: kind %d (STATISTIC_KIND_ND)", stats->stakind[0]);
1672 POSTGIS_DEBUGF(3, " out: slot 0: op %d (InvalidOid)", stats->staop[0]);
1673 POSTGIS_DEBUGF(3, " out: slot 0: numnumbers %d", stats->numnumbers[0]);
1674 POSTGIS_DEBUGF(3, " out: null fraction: %f=%d/%d", stats->stanullfrac, null_cnt, sample_rows);
1675 POSTGIS_DEBUGF(3, " out: average width: %d bytes", stats->stawidth);
1676 POSTGIS_DEBUG (3, " out: distinct values: all (no check done)");
1677 POSTGIS_DEBUGF(3, " out: %s", nd_stats_to_json(nd_stats));
1678 /*
1679 POSTGIS_DEBUGF(3, " out histogram:\n%s", nd_stats_to_grid(nd_stats));
1680 */
1681
1682 return;
1683}
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: