Skip to content

Commit 71ed352

Browse files
tomas-villagesqlmjamaloney
authored andcommitted
Add GROUP_CONCAT support for custom types (#464)
GitOrigin-RevId: 8b58a2336b2bec40d8da4f8cda0f45e61ac854b2
1 parent 1a9a52f commit 71ed352

9 files changed

Lines changed: 90 additions & 33 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
CREATE TABLE complex_test (
2+
id INT PRIMARY KEY,
3+
value vsql_complex.COMPLEX
4+
);
5+
INSERT INTO complex_test VALUES
6+
(1, '(3.0, 4.0)'),
7+
(2, '(1.0, 2.0)'),
8+
(3, '(5.0, 1.0)'),
9+
(4, '(2.0, 3.0)'),
10+
(5, '(1.0, 2.0)');
11+
# Basic GROUP_CONCAT (no ORDER BY, no GROUP BY)
12+
SELECT GROUP_CONCAT(value) FROM complex_test;
13+
GROUP_CONCAT(value)
14+
(3,4),(1,2),(5,1),(2,3),(1,2)
15+
# GROUP_CONCAT with constant (tests const_item branch)
16+
SELECT GROUP_CONCAT('(1.0, 2.0)');
17+
GROUP_CONCAT('(1.0, 2.0)')
18+
(1.0, 2.0)
19+
# GROUP_CONCAT with DISTINCT (tests field path with uniqueness)
20+
SELECT GROUP_CONCAT(DISTINCT value) FROM complex_test;
21+
GROUP_CONCAT(DISTINCT value)
22+
(1,2),(2,3),(3,4),(5,1)
23+
# GROUP_CONCAT with const-folded function call (tests const_item with custom type)
24+
SELECT GROUP_CONCAT(complex_from_string('(6.0, 7.0)'));
25+
GROUP_CONCAT(complex_from_string('(6.0, 7.0)'))
26+
(6,7)
27+
DROP TABLE complex_test;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Test GROUP_CONCAT() on COMPLEX custom type
2+
3+
--source include/villagesql/install_complex_extension.inc
4+
5+
CREATE TABLE complex_test (
6+
id INT PRIMARY KEY,
7+
value vsql_complex.COMPLEX
8+
);
9+
10+
INSERT INTO complex_test VALUES
11+
(1, '(3.0, 4.0)'),
12+
(2, '(1.0, 2.0)'),
13+
(3, '(5.0, 1.0)'),
14+
(4, '(2.0, 3.0)'),
15+
(5, '(1.0, 2.0)');
16+
17+
--echo # Basic GROUP_CONCAT (no ORDER BY, no GROUP BY)
18+
SELECT GROUP_CONCAT(value) FROM complex_test;
19+
20+
--echo # GROUP_CONCAT with constant (tests const_item branch)
21+
SELECT GROUP_CONCAT('(1.0, 2.0)');
22+
23+
--echo # GROUP_CONCAT with DISTINCT (tests field path with uniqueness)
24+
SELECT GROUP_CONCAT(DISTINCT value) FROM complex_test;
25+
26+
--echo # GROUP_CONCAT with const-folded function call (tests const_item with custom type)
27+
SELECT GROUP_CONCAT(complex_from_string('(6.0, 7.0)'));
28+
29+
DROP TABLE complex_test;
30+
31+
--source include/villagesql/uninstall_complex_extension.inc

mysql-test/suite/villagesql/implicit_cast/disallowed/r/aggregate_group_concat_complex.result

Lines changed: 0 additions & 8 deletions
This file was deleted.

mysql-test/suite/villagesql/implicit_cast/disallowed/t/aggregate_group_concat_complex.test

Lines changed: 0 additions & 17 deletions
This file was deleted.

sql/field.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,6 +1029,13 @@ class Field {
10291029
// types always decode binary data to a new string (never returning existing
10301030
// string data), so the two-buffer optimization doesn't apply.
10311031
String *val_custom_str(String *buf) const;
1032+
String *val_custom_str(String *str, uchar *new_ptr) {
1033+
uchar *old_ptr = ptr;
1034+
ptr = new_ptr;
1035+
String *result = val_custom_str(str);
1036+
ptr = old_ptr;
1037+
return result;
1038+
}
10321039
String *val_int_as_str(String *val_buffer, bool unsigned_flag) const;
10331040
/*
10341041
str_needs_quotes() returns true if the value returned by val_str() needs

sql/item.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ String *Item::val_str_ascii(String *str) {
287287
String *Item::val_custom_str(String *str) {
288288
// Get binary data from val_str()
289289
String *binary_data = val_str(str);
290-
if (!binary_data || null_value) return binary_data;
290+
if (!has_type_context() || null_value) return binary_data;
291291

292292
// Decode using TypeContext's to_string function
293293
bool is_valid = true;

sql/item_sum.cc

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,16 @@ Field *Item_sum::create_tmp_field(bool, TABLE *table) {
721721
return field;
722722
}
723723

724+
Field *Item_sum::make_string_field(TABLE *table) const {
725+
Field *field = Item::make_string_field(table);
726+
// VillageSQL: Propagate TypeContext from aggregated argument to result field.
727+
// This allows GROUP_CONCAT to decode custom type values when building output.
728+
if (field && arg_count > 0 && args[0]->has_type_context()) {
729+
field->set_type_context(args[0]->get_type_context());
730+
}
731+
return field;
732+
}
733+
724734
bool Item_sum::collect_grouped_aggregates(uchar *arg) {
725735
auto *info = pointer_cast<Collect_grouped_aggregate_info *>(arg);
726736

@@ -4170,17 +4180,20 @@ int dump_leaf_key(void *key_arg, element_count count [[maybe_unused]],
41704180
We also can't use table->field array to access the fields
41714181
because it contains both order and arg list fields.
41724182
*/
4173-
if ((*arg)->const_item())
4174-
res = (*arg)->val_str(&tmp);
4175-
else {
4183+
// VillageSQL: Use val_custom_str throughout to decode custom types.
4184+
// Const items can have custom types (e.g., const-folded function calls).
4185+
if ((*arg)->const_item()) {
4186+
res = (*arg)->val_custom_str(&tmp);
4187+
} else {
41764188
Field *field = (*arg)->get_tmp_table_field();
41774189
if (field) {
41784190
const uint offset =
41794191
(field->offset(field->table->record[0]) - table->s->null_bytes);
41804192
assert(offset < table->s->reclength);
4181-
res = field->val_str(&tmp, key + offset);
4182-
} else
4183-
res = (*arg)->val_str(&tmp);
4193+
res = field->val_custom_str(&tmp, key + offset);
4194+
} else {
4195+
res = (*arg)->val_custom_str(&tmp);
4196+
}
41844197
}
41854198
if (res) result->append(*res);
41864199
}

sql/item_sum.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,8 @@ class Item_sum : public Item_func {
616616
}
617617
virtual void make_unique() { force_copy_fields = true; }
618618
virtual Field *create_tmp_field(bool group, TABLE *table);
619+
// VillageSQL: Override to propagate TypeContext from aggregated argument
620+
Field *make_string_field(TABLE *table) const override;
619621

620622
/// argument used by walk method collect_grouped_aggregates ("cga")
621623
struct Collect_grouped_aggregate_info {

villagesql/types/util.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -863,11 +863,13 @@ bool CheckCustomTypeUsage(Item *item, THD *thd) {
863863
// Check if any arg is custom type
864864
for (uint i = 0; i < sum_func->arg_count; i++) {
865865
if (sum_func->get_arg(i)->has_type_context()) {
866-
// Some aggregates work automatically via the type's compare function
866+
// Some aggregates work automatically via the type's compare/decode
867+
// functions
867868
switch (sum_func->sum_func()) {
868869
case Item_sum::MIN_FUNC:
869870
case Item_sum::MAX_FUNC:
870871
case Item_sum::COUNT_DISTINCT_FUNC:
872+
case Item_sum::GROUP_CONCAT_FUNC:
871873
continue; // Allow these aggregates on custom types
872874
default:
873875
// Block all other aggregate functions on custom types

0 commit comments

Comments
 (0)