Repository: duckdb/duckdb
File: src/storage/table/row_group.cpp (C++)

Code before:
	result.deletes_pointers = deserializer.ReadProperty<vector<MetaBlockPointer>>(103, "delete_pointers");
	result.has_metadata_blocks = deserializer.ReadPropertyWithExplicitDefault<bool>(104, "has_metadata_blocks", false);
	result.extra_metadata_blocks = deserializer.ReadPropertyWithDefault<vector<idx_t>>(105, "extra_metadata_blocks");
	return result;
}

//===--------------------------------------------------------------------===//
// GetPartitionStats
//===--------------------------------------------------------------------===//
struct DuckDBPartitionRowGroup : public PartitionRowGroup {
	explicit DuckDBPartitionRowGroup(const RowGroup &row_group_p, bool is_exact_p)
	    : row_group(row_group_p), is_exact(is_exact_p) {
	}

	const RowGroup &row_group;
	const bool is_exact;

	unique_ptr<BaseStatistics> GetColumnStatistics(column_t column_id) override {
		return row_group.GetStatistics(column_id);
	}

	bool MinMaxIsExact(const BaseStatistics &stats) override {
		if (!is_exact) {
			return false;
		}
		if (stats.GetStatsType() == StatisticsType::STRING_STATS) {
			if (!StringStats::HasMaxStringLength(stats)) {
				return false;
			}
			const idx_t max_length = StringStats::MaxStringLength(stats);
			return max_length == StringStats::Max(stats).length() && max_length == StringStats::Min(stats).length();
		}
		return stats.GetStatsType() == StatisticsType::NUMERIC_STATS;
	}
};

PartitionStatistics RowGroup::GetPartitionStats(idx_t row_group_start) {
	PartitionStatistics result;
	result.row_start = row_group_start;
	result.count = count;
	if (HasUnloadedDeletes() || version_info.load().get()) {
		// we have version info - approx count
		result.count_type = CountType::COUNT_APPROXIMATE;
		result.partition_row_group = make_shared_ptr<DuckDBPartitionRowGroup>(*this, false);
	} else {
		result.count_type = CountType::COUNT_EXACT;
		result.partition_row_group = make_shared_ptr<DuckDBPartitionRowGroup>(*this, true);
	}

	return result;
}

Review comment (bug):
We also need to check if there are any updates, otherwise this does not work correctly. For example, here's a test that now fails:

```sql
load __TEST_DIR__/min_max_updates.db

statement ok
CREATE TABLE tbl(i INT);

statement ok
INSERT INTO tbl FROM range(10);

restart

statement ok con1
BEGIN

statement ok con1
UPDATE tbl SET i=i+100

query II
SELECT MIN(i), MAX(i) FROM tbl
----
0	9

query II con1
SELECT MIN(i), MAX(i) FROM tbl
----
100	109
```

Code after:
	result.deletes_pointers = deserializer.ReadProperty<vector<MetaBlockPointer>>(103, "delete_pointers");
	result.has_metadata_blocks = deserializer.ReadPropertyWithExplicitDefault<bool>(104, "has_metadata_blocks", false);
	result.extra_metadata_blocks = deserializer.ReadPropertyWithDefault<vector<idx_t>>(105, "extra_metadata_blocks");
	return result;
}

//===--------------------------------------------------------------------===//
// GetPartitionStats
//===--------------------------------------------------------------------===//
struct DuckDBPartitionRowGroup : public PartitionRowGroup {
	explicit DuckDBPartitionRowGroup(const RowGroup &row_group_p, bool is_exact_p)
	    : row_group(row_group_p), is_exact(is_exact_p) {
	}

	const RowGroup &row_group;
	const bool is_exact;

	unique_ptr<BaseStatistics> GetColumnStatistics(column_t column_id) override {
		return row_group.GetStatistics(column_id);
	}

	bool MinMaxIsExact(const BaseStatistics &stats) override {
		if (!is_exact || row_group.HasChanges()) {
			return false;
		}
		if (stats.GetStatsType() == StatisticsType::STRING_STATS) {
			if (!StringStats::HasMaxStringLength(stats)) {
				return false;
			}
			const idx_t max_length = StringStats::MaxStringLength(stats);
			return max_length == StringStats::Max(stats).length() && max_length == StringStats::Min(stats).length();
		}
		return stats.GetStatsType() == StatisticsType::NUMERIC_STATS;
	}
};

PartitionStatistics RowGroup::GetPartitionStats(idx_t row_group_start) {
	PartitionStatistics result;
	result.row_start = row_group_start;
	result.count = count;
	if (HasUnloadedDeletes() || version_info.load().get()) {
		// we have version info - approx count
		result.count_type = CountType::COUNT_APPROXIMATE;
		result.partition_row_group = make_shared_ptr<DuckDBPartitionRowGroup>(*this, false);
	} else {
		result.count_type = CountType::COUNT_EXACT;
		result.partition_row_group = make_shared_ptr<DuckDBPartitionRowGroup>(*this, true);
	}

	return result;
}