Repository: duckdb/duckdb
File: src/optimizer/filter_combiner.cpp (C++)

Code before:
FilterPushdownResult FilterCombiner::TryPushdownPrefixFilter(TableFilterSet &table_filters,
                                                             const vector<ColumnIndex> &column_ids, Expression &expr) {
	if (expr.GetExpressionClass() != ExpressionClass::BOUND_FUNCTION) {
		return FilterPushdownResult::NO_PUSHDOWN;
	}
	auto &func = expr.Cast<BoundFunctionExpression>();
	if (func.function.name != "prefix") {
		return FilterPushdownResult::NO_PUSHDOWN;
	}
	if (func.children[0]->GetExpressionClass() != ExpressionClass::BOUND_COLUMN_REF ||
	    func.children[1]->GetExpressionType() != ExpressionType::VALUE_CONSTANT) {
		// we need prefix(col, 'literal') in order to push this down
		return FilterPushdownResult::NO_PUSHDOWN;
	}
	auto &column_ref = func.children[0]->Cast<BoundColumnRefExpression>();
	auto &constant_value_expr = func.children[1]->Cast<BoundConstantExpression>();
	auto prefix_string = StringValue::Get(constant_value_expr.value);
	if (prefix_string.empty()) {
		// empty prefix - skip
		return FilterPushdownResult::NO_PUSHDOWN;
	}
	auto &column_index = column_ids[column_ref.binding.column_index];
	//! Replace prefix with a set of comparisons
	auto lower_bound = make_uniq<ConstantFilter>(ExpressionType::COMPARE_GREATERTHANOREQUALTO, Value(prefix_string));
	table_filters.PushFilter(column_index, std::move(lower_bound));
	prefix_string[prefix_string.size() - 1]++;
	if (Utf8Proc::Analyze(prefix_string.c_str(), prefix_string.size()) != UnicodeType::INVALID) {
		auto upper_bound = make_uniq<ConstantFilter>(ExpressionType::COMPARE_LESSTHAN, Value(prefix_string));
		table_filters.PushFilter(column_index, std::move(upper_bound));
	}
	return FilterPushdownResult::PUSHED_DOWN_FULLY;
}

FilterPushdownResult FilterCombiner::TryPushdownLikeFilter(TableFilterSet &table_filters,
                                                           const vector<ColumnIndex> &column_ids, Expression &expr) {
	if (expr.GetExpressionClass() != ExpressionClass::BOUND_FUNCTION) {
		return FilterPushdownResult::NO_PUSHDOWN;
	}
	auto &func = expr.Cast<BoundFunctionExpression>();
	if (func.function.name != "~~") {
		return FilterPushdownResult::NO_PUSHDOWN;
	}
	if (func.children[0]->GetExpressionClass() != ExpressionClass::BOUND_COLUMN_REF ||
	    func.children[1]->GetExpressionType() != ExpressionType::VALUE_CONSTANT) {
		// we need col LIKE 'literal' in order to generate extra filters
		return FilterPushdownResult::NO_PUSHDOWN;
	}

	//! This is a like function.
	auto &column_ref = func.children[0]->Cast<BoundColumnRefExpression>();
	auto &constant_value_expr = func.children[1]->Cast<BoundConstantExpression>();

Review comment (suggestion):
Instead of just incrementing the last byte (which might result in invalid UTF8) - could we find the next UTF8 codepoint?

This would look something like this:

```cpp
// find the start of the last codepoint
idx_t last_codepoint_start;
for(last_codepoint_start = prefix_string.size(); last_codepoint_start > 0; last_codepoint_start--) {
    if (IsCharacter(prefix_string[last_codepoint_start - 1])) {
        break;
    }
}
D_ASSERT(last_codepoint_start > 0);
last_codepoint_start--;
int codepoint_size;
auto codepoint = Utf8Proc ::UTF8ToCodepoint(prefix_string.c_str() + last_codepoint_start, codepoint_size);
auto next_codepoint = codepoint + 1;
if (next_codepoint >= 0xD800 && next_codepoint <= 0xDFFF) {
    // next codepoint falls within surrogate range increment to next valid character
    next_codepoint = 0xE000;
}
char next_codepoint_text[4];
int next_codepoint_size;
if (!Utf8Proc::CodepointToUtf8(codepoint + 1, next_codepoint_size, next_codepoint_text)) {
        // invalid codepoint
		return FilterPushdownResult::NO_PUSHDOWN;
}
string upper_bound = prefix_string.substr(0, last_codepoint_start) + string(next_codepoint_text, next_codepoint_size);
```

Code after:
		return FilterPushdownResult::NO_PUSHDOWN;
	}
	// extract the column bindings from this expression
	vector<ColumnBinding> bindings;
	ColumnLifetimeAnalyzer::ExtractColumnBindings(expr, bindings);
	if (bindings.empty()) {
		return FilterPushdownResult::NO_PUSHDOWN;
	}
	// we can only pushdown expressions that refer to exactly one column
	for (idx_t i = 1; i < bindings.size(); i++) {
		if (bindings[i] != bindings[0]) {
			return FilterPushdownResult::NO_PUSHDOWN;
		}
	}
	if (!get.function.pushdown_expression(context, get, expr)) {
		// the scan does not support pushing down THIS expression
		return FilterPushdownResult::NO_PUSHDOWN;
	}
	// replace the BoundColumnRefExpression with a BoundReference
	auto filter_expr = expr.Copy();
	ReplaceWithBoundReference(filter_expr);

	// push the expression filter
	auto &column_ids = get.GetColumnIds();
	auto expr_filter = make_uniq<ExpressionFilter>(std::move(filter_expr));
	auto &column_index = column_ids[bindings[0].column_index];
	get.table_filters.PushFilter(column_index, std::move(expr_filter));
	return FilterPushdownResult::PUSHED_DOWN_FULLY;
}

FilterPushdownResult FilterCombiner::TryPushdownPrefixFilter(TableFilterSet &table_filters,
                                                             const vector<ColumnIndex> &column_ids, Expression &expr) {
	if (expr.GetExpressionClass() != ExpressionClass::BOUND_FUNCTION) {
		return FilterPushdownResult::NO_PUSHDOWN;
	}
	auto &func = expr.Cast<BoundFunctionExpression>();
	if (func.function.name != "prefix") {
		return FilterPushdownResult::NO_PUSHDOWN;
	}
	if (func.children[0]->GetExpressionClass() != ExpressionClass::BOUND_COLUMN_REF ||
	    func.children[1]->GetExpressionType() != ExpressionType::VALUE_CONSTANT) {
		// we need prefix(col, 'literal') in order to push this down
		return FilterPushdownResult::NO_PUSHDOWN;
	}
	auto &column_ref = func.children[0]->Cast<BoundColumnRefExpression>();
	auto &constant_value_expr = func.children[1]->Cast<BoundConstantExpression>();
	auto prefix_string = StringValue::Get(constant_value_expr.value);
	if (prefix_string.empty()) {
		// empty prefix - skip
		return FilterPushdownResult::NO_PUSHDOWN;
	}