Skip to content

Commit 7e0a05f

Browse files
authored
Merge pull request #57 from ProxySQL/fix/shard-planner-correctness
fix: stop silent wrong answers in distributed planner
2 parents a5bc6ea + ec89e65 commit 7e0a05f

21 files changed

Lines changed: 1052 additions & 60 deletions

include/sql_engine/distributed_planner.h

Lines changed: 291 additions & 40 deletions
Large diffs are not rendered by default.

include/sql_engine/distributed_txn.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,18 @@ class DistributedTransactionManager : public TransactionManager {
178178
return executor_.execute_dml(backend_name, sql);
179179
}
180180

181+
ResultSet route_query(const char* backend_name,
182+
sql_parser::StringRef sql) override {
183+
if (!active_) return executor_.execute(backend_name, sql);
184+
auto it = sessions_.find(backend_name);
185+
if (it != sessions_.end() && it->second) {
186+
return it->second->execute(sql);
187+
}
188+
return executor_.execute(backend_name, sql);
189+
}
190+
191+
bool route_query_supported() const override { return true; }
192+
181193
bool commit() override {
182194
if (!active_) return false;
183195
if (participants_.empty()) {

include/sql_engine/operators/aggregate_op.h

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#include <string>
1313
#include <cstring>
1414
#include <cmath>
15+
#include <unordered_set>
16+
#include "sql_parser/common.h"
1517

1618
namespace sql_engine {
1719

@@ -131,6 +133,8 @@ class AggregateOperator : public Operator {
131133
Value max_val{};
132134
bool has_value = false;
133135
bool count_star = false; // COUNT(*)
136+
bool distinct = false;
137+
std::unordered_set<std::string> seen;
134138
};
135139

136140
struct GroupState {
@@ -186,9 +190,9 @@ class AggregateOperator : public Operator {
186190

187191
if (expr->type == sql_parser::NodeType::NODE_FUNCTION_CALL) {
188192
sql_parser::StringRef name = expr->value();
193+
state.distinct = (expr->flags & sql_parser::FLAG_FUNC_DISTINCT) != 0;
189194
if (name.equals_ci("COUNT", 5)) {
190195
state.type = AggType::COUNT;
191-
// Check for COUNT(*)
192196
const sql_parser::AstNode* arg = expr->first_child;
193197
if (arg && arg->type == sql_parser::NodeType::NODE_ASTERISK) {
194198
state.count_star = true;
@@ -203,25 +207,32 @@ class AggregateOperator : public Operator {
203207
state.type = AggType::EXPR;
204208
}
205209

210+
static bool note_distinct(AggState& state, const sql_parser::AstNode* expr,
211+
const Value& v) {
212+
bool distinct = state.distinct ||
213+
(expr && (expr->flags & sql_parser::FLAG_FUNC_DISTINCT));
214+
if (!distinct) return true;
215+
return state.seen.insert(value_to_string(v)).second;
216+
}
217+
206218
void update_agg(AggState& state, const sql_parser::AstNode* expr,
207219
const std::function<Value(sql_parser::StringRef)>& resolver) {
208220
switch (state.type) {
209221
case AggType::COUNT: {
210222
if (state.count_star) {
211223
state.count++;
212224
} else {
213-
// COUNT(expr) - count non-null values
214225
const sql_parser::AstNode* arg = expr->first_child;
215226
Value v = evaluate_expression<D>(arg, resolver, functions_, arena_);
216-
if (!v.is_null()) state.count++;
227+
if (!v.is_null() && note_distinct(state, expr, v)) state.count++;
217228
}
218229
break;
219230
}
220231
case AggType::SUM:
221232
case AggType::AVG: {
222233
const sql_parser::AstNode* arg = expr->first_child;
223234
Value v = evaluate_expression<D>(arg, resolver, functions_, arena_);
224-
if (!v.is_null()) {
235+
if (!v.is_null() && note_distinct(state, expr, v)) {
225236
state.sum += v.to_double();
226237
state.count++;
227238
state.has_value = true;
@@ -231,7 +242,7 @@ class AggregateOperator : public Operator {
231242
case AggType::MIN: {
232243
const sql_parser::AstNode* arg = expr->first_child;
233244
Value v = evaluate_expression<D>(arg, resolver, functions_, arena_);
234-
if (!v.is_null()) {
245+
if (!v.is_null() && note_distinct(state, expr, v)) {
235246
if (!state.has_value || compare_values(v, state.min_val) < 0) {
236247
state.min_val = v;
237248
state.has_value = true;
@@ -242,7 +253,7 @@ class AggregateOperator : public Operator {
242253
case AggType::MAX: {
243254
const sql_parser::AstNode* arg = expr->first_child;
244255
Value v = evaluate_expression<D>(arg, resolver, functions_, arena_);
245-
if (!v.is_null()) {
256+
if (!v.is_null() && note_distinct(state, expr, v)) {
246257
if (!state.has_value || compare_values(v, state.max_val) > 0) {
247258
state.max_val = v;
248259
state.has_value = true;

include/sql_engine/plan_builder.h

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "sql_parser/common.h"
2626
#include "sql_parser/arena.h"
2727
#include <cstring>
28+
#include <cstdlib>
2829
#include <vector>
2930

3031
namespace sql_engine {
@@ -116,6 +117,55 @@ class PlanBuilder {
116117
return false;
117118
}
118119

120+
static const sql_parser::AstNode* select_item_expr(const sql_parser::AstNode* item) {
121+
return item ? item->first_child : nullptr;
122+
}
123+
124+
static sql_parser::StringRef select_item_alias(const sql_parser::AstNode* item) {
125+
if (!item) return {};
126+
for (const sql_parser::AstNode* c = item->first_child; c; c = c->next_sibling) {
127+
if (c->type == sql_parser::NodeType::NODE_ALIAS) return c->value();
128+
}
129+
return {};
130+
}
131+
132+
static const sql_parser::AstNode* resolve_order_key(
133+
const sql_parser::AstNode* key, const sql_parser::AstNode* select_items) {
134+
if (!key || !select_items) return key;
135+
uint16_t n = count_children(select_items);
136+
if (n == 0) return key;
137+
138+
if (key->type == sql_parser::NodeType::NODE_LITERAL_INT) {
139+
sql_parser::StringRef sv = key->value();
140+
if (!sv.ptr || sv.len == 0) return key;
141+
int64_t pos = std::strtoll(sv.ptr, nullptr, 10);
142+
if (pos < 1 || pos > static_cast<int64_t>(n)) return key;
143+
uint16_t idx = 0;
144+
for (const sql_parser::AstNode* item = select_items->first_child; item;
145+
item = item->next_sibling, ++idx) {
146+
if (idx + 1 == static_cast<uint16_t>(pos)) {
147+
const sql_parser::AstNode* expr = select_item_expr(item);
148+
return expr ? expr : key;
149+
}
150+
}
151+
return key;
152+
}
153+
154+
if (key->type == sql_parser::NodeType::NODE_COLUMN_REF ||
155+
key->type == sql_parser::NodeType::NODE_IDENTIFIER) {
156+
sql_parser::StringRef name = key->value();
157+
for (const sql_parser::AstNode* item = select_items->first_child; item;
158+
item = item->next_sibling) {
159+
sql_parser::StringRef alias = select_item_alias(item);
160+
if (alias.ptr && alias.equals_ci(name.ptr, name.len)) {
161+
const sql_parser::AstNode* expr = select_item_expr(item);
162+
return expr ? expr : key;
163+
}
164+
}
165+
}
166+
return key;
167+
}
168+
119169
// Check if an expression (or any descendant) contains an aggregate function call.
120170
// Does NOT recurse into subqueries -- aggregates inside subqueries belong
121171
// to the subquery's own aggregation, not the outer query.
@@ -279,10 +329,12 @@ class PlanBuilder {
279329
arena_.allocate(sizeof(sql_parser::AstNode*) * cnt));
280330
auto* dirs = static_cast<uint8_t*>(arena_.allocate(cnt));
281331

332+
const sql_parser::AstNode* select_items =
333+
find_child(select_ast, sql_parser::NodeType::NODE_SELECT_ITEM_LIST);
334+
282335
uint16_t idx = 0;
283336
for (const sql_parser::AstNode* item = order_by->first_child; item; item = item->next_sibling) {
284-
// First child is the key expression
285-
keys[idx] = item->first_child;
337+
keys[idx] = resolve_order_key(item->first_child, select_items);
286338
// Check for DESC direction (second child with "DESC" value)
287339
dirs[idx] = 0; // ASC by default
288340
const sql_parser::AstNode* dir_node = find_child(item, sql_parser::NodeType::NODE_IDENTIFIER);

include/sql_engine/plan_executor.h

Lines changed: 127 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
#include <string>
5757
#include <vector>
5858
#include <memory>
59+
#include <cstdlib>
60+
#include <cstring>
5961

6062
namespace sql_engine {
6163

@@ -846,15 +848,128 @@ class PlanExecutor {
846848
return ptr;
847849
}
848850

851+
static bool same_agg_call(const sql_parser::AstNode* a, const sql_parser::AstNode* b) {
852+
if (!a || !b) return false;
853+
if (a->type != sql_parser::NodeType::NODE_FUNCTION_CALL ||
854+
b->type != sql_parser::NodeType::NODE_FUNCTION_CALL) return false;
855+
return a->value().equals_ci(b->value().ptr, b->value().len);
856+
}
857+
858+
const sql_parser::AstNode* rewrite_having_expr(const sql_parser::AstNode* expr,
859+
PlanNode* agg_node) {
860+
if (!expr || !agg_node) return expr;
861+
uint16_t group_count = 0;
862+
uint16_t agg_count = 0;
863+
const sql_parser::AstNode** agg_exprs = nullptr;
864+
const sql_parser::AstNode** group_by = nullptr;
865+
if (agg_node->type == PlanNodeType::AGGREGATE) {
866+
group_count = agg_node->aggregate.group_count;
867+
agg_count = agg_node->aggregate.agg_count;
868+
agg_exprs = agg_node->aggregate.agg_exprs;
869+
group_by = agg_node->aggregate.group_by;
870+
} else if (agg_node->type == PlanNodeType::MERGE_AGGREGATE) {
871+
group_count = agg_node->merge_aggregate.group_key_count;
872+
if (agg_node->merge_aggregate.output_exprs &&
873+
agg_node->merge_aggregate.output_expr_count > group_count) {
874+
agg_exprs = agg_node->merge_aggregate.output_exprs + group_count;
875+
agg_count = static_cast<uint16_t>(
876+
agg_node->merge_aggregate.output_expr_count - group_count);
877+
group_by = agg_node->merge_aggregate.output_exprs;
878+
}
879+
} else {
880+
return expr;
881+
}
882+
883+
if (expr->type == sql_parser::NodeType::NODE_FUNCTION_CALL && agg_exprs) {
884+
for (uint16_t i = 0; i < agg_count; ++i) {
885+
if (same_agg_call(expr, agg_exprs[i])) {
886+
sql_parser::StringRef name = expr->value();
887+
return sql_parser::make_node(
888+
arena_, sql_parser::NodeType::NODE_IDENTIFIER, name);
889+
}
890+
}
891+
}
892+
893+
bool changed = false;
894+
sql_parser::AstNode* clone = sql_parser::make_node(
895+
arena_, expr->type, expr->value(), expr->flags);
896+
for (const sql_parser::AstNode* c = expr->first_child; c; c = c->next_sibling) {
897+
const sql_parser::AstNode* rw = rewrite_having_expr(c, agg_node);
898+
if (rw != c) changed = true;
899+
if (rw) clone->add_child(const_cast<sql_parser::AstNode*>(rw));
900+
}
901+
(void)group_count;
902+
(void)group_by;
903+
return changed ? clone : expr;
904+
}
905+
906+
const TableInfo* make_agg_output_table(PlanNode* agg_node) {
907+
if (!agg_node) return nullptr;
908+
uint16_t group_count = 0;
909+
uint16_t agg_count = 0;
910+
const sql_parser::AstNode** group_by = nullptr;
911+
const sql_parser::AstNode** agg_exprs = nullptr;
912+
if (agg_node->type == PlanNodeType::AGGREGATE) {
913+
group_count = agg_node->aggregate.group_count;
914+
agg_count = agg_node->aggregate.agg_count;
915+
group_by = agg_node->aggregate.group_by;
916+
agg_exprs = agg_node->aggregate.agg_exprs;
917+
} else if (agg_node->type == PlanNodeType::MERGE_AGGREGATE &&
918+
agg_node->merge_aggregate.output_exprs) {
919+
group_count = agg_node->merge_aggregate.group_key_count;
920+
group_by = agg_node->merge_aggregate.output_exprs;
921+
if (agg_node->merge_aggregate.output_expr_count > group_count) {
922+
agg_exprs = agg_node->merge_aggregate.output_exprs + group_count;
923+
agg_count = static_cast<uint16_t>(
924+
agg_node->merge_aggregate.output_expr_count - group_count);
925+
}
926+
} else {
927+
return nullptr;
928+
}
929+
930+
uint16_t n = static_cast<uint16_t>(group_count + agg_count);
931+
if (n == 0) return nullptr;
932+
auto* cols = static_cast<ColumnInfo*>(arena_.allocate(sizeof(ColumnInfo) * n));
933+
if (!cols) return nullptr;
934+
for (uint16_t i = 0; i < group_count; ++i) {
935+
cols[i].ordinal = i;
936+
cols[i].nullable = true;
937+
cols[i].type = SqlType::make_int();
938+
cols[i].name = (group_by && group_by[i]) ? group_by[i]->value()
939+
: sql_parser::StringRef{};
940+
}
941+
for (uint16_t i = 0; i < agg_count; ++i) {
942+
cols[group_count + i].ordinal = static_cast<uint16_t>(group_count + i);
943+
cols[group_count + i].nullable = true;
944+
cols[group_count + i].type = SqlType::make_int();
945+
cols[group_count + i].name = (agg_exprs && agg_exprs[i])
946+
? agg_exprs[i]->value() : sql_parser::StringRef{};
947+
}
948+
auto* ti = static_cast<TableInfo*>(arena_.allocate(sizeof(TableInfo)));
949+
if (!ti) return nullptr;
950+
std::memset(ti, 0, sizeof(TableInfo));
951+
ti->columns = cols;
952+
ti->column_count = n;
953+
return ti;
954+
}
955+
849956
Operator* build_filter(PlanNode* node) {
850957
Operator* child = build_operator(node->left);
851958
if (!child && node->left) return nullptr;
852959

853960
std::vector<const TableInfo*> tables;
854-
collect_tables(node->left, tables);
961+
const sql_parser::AstNode* expr = node->filter.expr;
962+
if (node->left && (node->left->type == PlanNodeType::AGGREGATE ||
963+
node->left->type == PlanNodeType::MERGE_AGGREGATE)) {
964+
expr = rewrite_having_expr(expr, node->left);
965+
const TableInfo* synth = make_agg_output_table(node->left);
966+
if (synth) tables.push_back(synth);
967+
} else {
968+
collect_tables(node->left, tables);
969+
}
855970

856971
auto op = std::make_unique<FilterOperator<D>>(
857-
child, node->filter.expr, catalog_, tables, functions_, arena_,
972+
child, expr, catalog_, tables, functions_, arena_,
858973
&subquery_exec_, outer_resolver_);
859974
Operator* ptr = op.get();
860975
operators_.push_back(std::move(op));
@@ -1170,12 +1285,21 @@ class PlanExecutor {
11701285

11711286
uint16_t resolve_column_index(const sql_parser::AstNode* key, const TableInfo* table) {
11721287
if (!key || !table) return 0;
1288+
if (key->type == sql_parser::NodeType::NODE_LITERAL_INT) {
1289+
sql_parser::StringRef sv = key->value();
1290+
if (!sv.ptr || sv.len == 0) return 0;
1291+
int64_t n = std::strtoll(sv.ptr, nullptr, 10);
1292+
if (n < 1) return 0;
1293+
if (n > static_cast<int64_t>(table->column_count)) {
1294+
return static_cast<uint16_t>(table->column_count - 1);
1295+
}
1296+
return static_cast<uint16_t>(n - 1);
1297+
}
11731298
sql_parser::StringRef col_name;
11741299
if (key->type == sql_parser::NodeType::NODE_COLUMN_REF ||
11751300
key->type == sql_parser::NodeType::NODE_IDENTIFIER) {
11761301
col_name = key->value();
11771302
} else if (key->type == sql_parser::NodeType::NODE_QUALIFIED_NAME) {
1178-
// table.column -- get the column part
11791303
const sql_parser::AstNode* c = key->first_child;
11801304
if (c && c->next_sibling) col_name = c->next_sibling->value();
11811305
else if (c) col_name = c->value();

include/sql_engine/plan_node.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ struct PlanNode {
101101
struct {
102102
const char* backend_name;
103103
const char* remote_sql;
104-
uint16_t remote_sql_len;
104+
uint32_t remote_sql_len;
105105
const TableInfo* table; // expected result schema (for SELECT *)
106106
// Optional projection expressions used to derive result column
107107
// names when the remote SQL is not a passthrough SELECT *. When

include/sql_engine/remote_query_builder.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,28 @@ class RemoteQueryBuilder {
9494
return sb.finish();
9595
}
9696

97+
sql_parser::StringRef build_select_join(
98+
const TableInfo* left,
99+
const TableInfo* right,
100+
const sql_parser::AstNode* on_expr,
101+
const sql_parser::AstNode* where_expr)
102+
{
103+
sql_parser::StringBuilder sb(arena_, 512);
104+
sb.append("SELECT * FROM ");
105+
if (left) sb.append(left->table_name.ptr, left->table_name.len);
106+
sb.append(" JOIN ");
107+
if (right) sb.append(right->table_name.ptr, right->table_name.len);
108+
if (on_expr) {
109+
sb.append(" ON ");
110+
emit_expr(on_expr, sb);
111+
}
112+
if (where_expr) {
113+
sb.append(" WHERE ");
114+
emit_expr(where_expr, sb);
115+
}
116+
return sb.finish();
117+
}
118+
97119
// Build an INSERT statement string.
98120
sql_parser::StringRef build_insert(
99121
const TableInfo* table,

0 commit comments

Comments
 (0)