Skip to content

Commit 969df14

Browse files
ALTER TABLE custom column to string (#45)
1 parent 92f878f commit 969df14

9 files changed

Lines changed: 252 additions & 5 deletions
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
CREATE TABLE t1 (
2+
id INT PRIMARY KEY,
3+
complex_val COMPLEX
4+
);
5+
INSERT INTO t1 VALUES (1, '(1.0,2.0)');
6+
INSERT INTO t1 VALUES (2, '(3.5,-1.2)');
7+
INSERT INTO t1 VALUES (3, NULL);
8+
SHOW CREATE TABLE t1;
9+
Table Create Table
10+
t1 CREATE TABLE `t1` (
11+
`id` int NOT NULL,
12+
`complex_val` vsql_complex.COMPLEX DEFAULT NULL,
13+
PRIMARY KEY (`id`)
14+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
15+
SELECT * FROM t1;
16+
id complex_val
17+
1 (1,2)
18+
2 (3.5,-1.2)
19+
3 NULL
20+
ALTER TABLE t1 CHANGE complex_val string_val VARCHAR(100);
21+
SHOW CREATE TABLE t1;
22+
Table Create Table
23+
t1 CREATE TABLE `t1` (
24+
`id` int NOT NULL,
25+
`string_val` varchar(100) DEFAULT NULL,
26+
PRIMARY KEY (`id`)
27+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
28+
SELECT * FROM t1;
29+
id string_val
30+
1 (1,2)
31+
2 (3.5,-1.2)
32+
3 NULL
33+
DROP TABLE t1;
34+
include/rpl/deprecated/show_binlog_events.inc
35+
Log_name Pos Event_type Server_id End_log_pos Info
36+
# # Query # # use `test`; CREATE TABLE `t1` (
37+
`id` int NOT NULL,
38+
`complex_val` vsql_complex.COMPLEX DEFAULT NULL,
39+
PRIMARY KEY (`id`)
40+
)
41+
# # Query # # BEGIN
42+
# # Table_map # # table_id: # (test.t1)
43+
# # Write_rows # # table_id: # flags: STMT_END_F
44+
# # Xid # # COMMIT /* XID */
45+
# # Query # # BEGIN
46+
# # Table_map # # table_id: # (test.t1)
47+
# # Write_rows # # table_id: # flags: STMT_END_F
48+
# # Xid # # COMMIT /* XID */
49+
# # Query # # BEGIN
50+
# # Table_map # # table_id: # (test.t1)
51+
# # Write_rows # # table_id: # flags: STMT_END_F
52+
# # Xid # # COMMIT /* XID */
53+
# # Query # # use `test`; ALTER TABLE t1 CHANGE complex_val string_val VARCHAR(100)
54+
# # Query # # use `test`; DROP TABLE `t1` /* generated by server */
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
CREATE TABLE t1 (
2+
id INT PRIMARY KEY,
3+
complex_val COMPLEX
4+
);
5+
INSERT INTO t1 VALUES (1, '(1.0,2.0)');
6+
INSERT INTO t1 VALUES (2, '(3.5,-1.2)');
7+
INSERT INTO t1 VALUES (3, NULL);
8+
SHOW CREATE TABLE t1;
9+
Table Create Table
10+
t1 CREATE TABLE `t1` (
11+
`id` int NOT NULL,
12+
`complex_val` vsql_complex.COMPLEX DEFAULT NULL,
13+
PRIMARY KEY (`id`)
14+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
15+
SELECT * FROM t1 ORDER BY id;
16+
id complex_val
17+
1 (1,2)
18+
2 (3.5,-1.2)
19+
3 NULL
20+
ALTER TABLE t1 CHANGE complex_val string_val VARCHAR(100) NOT NULL;
21+
ERROR 01000: Data truncated for column 'string_val' at row 3
22+
SHOW CREATE TABLE t1;
23+
Table Create Table
24+
t1 CREATE TABLE `t1` (
25+
`id` int NOT NULL,
26+
`complex_val` vsql_complex.COMPLEX DEFAULT NULL,
27+
PRIMARY KEY (`id`)
28+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
29+
SELECT * FROM t1 ORDER BY id;
30+
id complex_val
31+
1 (1,2)
32+
2 (3.5,-1.2)
33+
3 NULL
34+
UPDATE t1 SET complex_val='(2.0,-3.0)' WHERE id=3;
35+
ALTER TABLE t1 CHANGE complex_val string_val VARCHAR(99) NOT NULL;
36+
SHOW CREATE TABLE t1;
37+
Table Create Table
38+
t1 CREATE TABLE `t1` (
39+
`id` int NOT NULL,
40+
`string_val` varchar(99) NOT NULL,
41+
PRIMARY KEY (`id`)
42+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
43+
SELECT * FROM t1 ORDER BY id;
44+
id string_val
45+
1 (1,2)
46+
2 (3.5,-1.2)
47+
3 (2,-3)
48+
DROP TABLE t1;
49+
include/rpl/deprecated/show_binlog_events.inc
50+
Log_name Pos Event_type Server_id End_log_pos Info
51+
# # Query # # use `test`; CREATE TABLE `t1` (
52+
`id` int NOT NULL,
53+
`complex_val` vsql_complex.COMPLEX DEFAULT NULL,
54+
PRIMARY KEY (`id`)
55+
)
56+
# # Query # # BEGIN
57+
# # Table_map # # table_id: # (test.t1)
58+
# # Write_rows # # table_id: # flags: STMT_END_F
59+
# # Xid # # COMMIT /* XID */
60+
# # Query # # BEGIN
61+
# # Table_map # # table_id: # (test.t1)
62+
# # Write_rows # # table_id: # flags: STMT_END_F
63+
# # Xid # # COMMIT /* XID */
64+
# # Query # # BEGIN
65+
# # Table_map # # table_id: # (test.t1)
66+
# # Write_rows # # table_id: # flags: STMT_END_F
67+
# # Xid # # COMMIT /* XID */
68+
# # Query # # BEGIN
69+
# # Table_map # # table_id: # (test.t1)
70+
# # Update_rows # # table_id: # flags: STMT_END_F
71+
# # Xid # # COMMIT /* XID */
72+
# # Query # # use `test`; ALTER TABLE t1 CHANGE complex_val string_val VARCHAR(99) NOT NULL
73+
# # Query # # use `test`; DROP TABLE `t1` /* generated by server */
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
CREATE TABLE t1 (
2+
id INT PRIMARY KEY,
3+
value COMPLEX
4+
);
5+
SELECT * FROM villagesql.custom_columns;
6+
db_name table_name column_name extension_name extension_version type_name type_parameters
7+
test t1 value vsql_complex 0.0.1 COMPLEX {}
8+
INSERT INTO t1 VALUES (1, '(1.0,2.0)');
9+
INSERT INTO t1 VALUES (2, '(3.5,-1.2)');
10+
SHOW CREATE TABLE t1;
11+
Table Create Table
12+
t1 CREATE TABLE `t1` (
13+
`id` int NOT NULL,
14+
`value` vsql_complex.COMPLEX DEFAULT NULL,
15+
PRIMARY KEY (`id`)
16+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
17+
SELECT * FROM t1;
18+
id value
19+
1 (1,2)
20+
2 (3.5,-1.2)
21+
ALTER TABLE t1 MODIFY COLUMN value VARCHAR(100);
22+
SELECT * FROM villagesql.custom_columns;
23+
db_name table_name column_name extension_name extension_version type_name type_parameters
24+
SHOW CREATE TABLE t1;
25+
Table Create Table
26+
t1 CREATE TABLE `t1` (
27+
`id` int NOT NULL,
28+
`value` varchar(100) DEFAULT NULL,
29+
PRIMARY KEY (`id`)
30+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
31+
SELECT * FROM t1;
32+
id value
33+
1 (1,2)
34+
2 (3.5,-1.2)
35+
DROP TABLE t1;
36+
SELECT * FROM villagesql.custom_columns;
37+
db_name table_name column_name extension_name extension_version type_name type_parameters
38+
include/rpl/deprecated/show_binlog_events.inc
39+
Log_name Pos Event_type Server_id End_log_pos Info
40+
# # Query # # use `test`; CREATE TABLE `t1` (
41+
`id` int NOT NULL,
42+
`value` vsql_complex.COMPLEX DEFAULT NULL,
43+
PRIMARY KEY (`id`)
44+
)
45+
# # Query # # BEGIN
46+
# # Table_map # # table_id: # (test.t1)
47+
# # Write_rows # # table_id: # flags: STMT_END_F
48+
# # Xid # # COMMIT /* XID */
49+
# # Query # # BEGIN
50+
# # Table_map # # table_id: # (test.t1)
51+
# # Write_rows # # table_id: # flags: STMT_END_F
52+
# # Xid # # COMMIT /* XID */
53+
# # Query # # use `test`; ALTER TABLE t1 MODIFY COLUMN value VARCHAR(100)
54+
# # Query # # use `test`; DROP TABLE `t1` /* generated by server */

mysql-test/suite/villagesql/alter_table/t/alter_table_change_complex_column.test

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# Test ALTER TABLE CHANGE on existing COMPLEX column
22
# Grammar: ALTER TABLE table_name CHANGE complex_col new_name new_type
33

4-
--source include/villagesql/not_implemented_complex_type.inc
5-
64
--source include/villagesql/install_complex_extension.inc
75
--source include/villagesql/binlog_check_begin.inc
86

@@ -13,6 +11,7 @@ CREATE TABLE t1 (
1311

1412
INSERT INTO t1 VALUES (1, '(1.0,2.0)');
1513
INSERT INTO t1 VALUES (2, '(3.5,-1.2)');
14+
INSERT INTO t1 VALUES (3, NULL);
1615

1716
SHOW CREATE TABLE t1;
1817
SELECT * FROM t1;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Test ALTER TABLE CHANGE on existing COMPLEX column to NOT NULL column
2+
# Grammar: ALTER TABLE table_name CHANGE complex_col new_name new_type
3+
4+
--source include/villagesql/install_complex_extension.inc
5+
--source include/villagesql/binlog_check_begin.inc
6+
7+
CREATE TABLE t1 (
8+
id INT PRIMARY KEY,
9+
complex_val COMPLEX
10+
);
11+
12+
INSERT INTO t1 VALUES (1, '(1.0,2.0)');
13+
INSERT INTO t1 VALUES (2, '(3.5,-1.2)');
14+
INSERT INTO t1 VALUES (3, NULL);
15+
16+
SHOW CREATE TABLE t1;
17+
SELECT * FROM t1 ORDER BY id;
18+
19+
# Change COMPLEX column to different name and type - fails due to NULL row
20+
--error 1265
21+
ALTER TABLE t1 CHANGE complex_val string_val VARCHAR(100) NOT NULL;
22+
23+
SHOW CREATE TABLE t1;
24+
SELECT * FROM t1 ORDER BY id;
25+
26+
UPDATE t1 SET complex_val='(2.0,-3.0)' WHERE id=3;
27+
28+
ALTER TABLE t1 CHANGE complex_val string_val VARCHAR(99) NOT NULL;
29+
30+
SHOW CREATE TABLE t1;
31+
SELECT * FROM t1 ORDER BY id;
32+
33+
DROP TABLE t1;
34+
35+
--source include/villagesql/binlog_check_end.inc
36+
--source include/villagesql/uninstall_complex_extension.inc

mysql-test/suite/villagesql/alter_table/t/alter_table_modify_column_from_complex.test

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Test ALTER TABLE MODIFY COLUMN from COMPLEX to different type
22
# Grammar: ALTER TABLE table_name MODIFY COLUMN column_name type
33

4-
--source include/villagesql/not_implemented_complex_type.inc
54

65
--source include/villagesql/install_complex_extension.inc
76
--source include/villagesql/binlog_check_begin.inc

sql/field_conv.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/* Copyright (c) 2000, 2025, Oracle and/or its affiliates.
2+
Copyright (c) 2026 VillageSQL Contributors
23
34
This program is free software; you can redistribute it and/or modify
45
it under the terms of the GNU General Public License, version 2.0,
@@ -57,6 +58,7 @@
5758
#include "sql/table.h"
5859
#include "sql_string.h"
5960
#include "template_utils.h" // down_cast
61+
#include "villagesql/types/util.h"
6062

6163
/**
6264
Check if geometry type sub is a subtype of super.
@@ -322,6 +324,12 @@ static void do_field_string(Copy_field *, const Field *from_field,
322324
to_field->store(res.ptr(), res.length(), res.charset());
323325
}
324326

327+
// VillageSQL: Copy from a custom type field to string field.
328+
static void do_field_custom_to_string(Copy_field *, const Field *from_field,
329+
Field *to_field) {
330+
villagesql::CopyCustomToStringField(from_field, to_field);
331+
}
332+
325333
static void do_field_enum(Copy_field *copy, const Field *from_field,
326334
Field *to_field) {
327335
if (from_field->val_int() == 0) {
@@ -562,6 +570,15 @@ Copy_field::Copy_func *Copy_field::get_copy_func() {
562570
THD *thd = current_thd;
563571
if (m_to_field->is_array() && m_from_field->is_array()) return do_copy_blob;
564572

573+
// VillageSQL: Route to the appropriate custom type copy function.
574+
if (m_from_field->has_type_context()) {
575+
// TODO(villagesql) - should there be a do_field_custom_to_custom
576+
// instead of relying on varbinary field copy
577+
if (!m_to_field->has_type_context()) {
578+
return do_field_custom_to_string;
579+
}
580+
}
581+
565582
const bool compatible_db_low_byte_first =
566583
(m_to_field->table->s->db_low_byte_first ==
567584
m_from_field->table->s->db_low_byte_first);

villagesql/types/util.cc

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -589,8 +589,9 @@ bool TryCopyCustomTypeField(const Field *from, Field *to) {
589589

590590
// If target doesn't have a custom type, this is an incompatible conversion.
591591
if (!to->has_type_context()) {
592-
char buff[MAX_FIELD_WIDTH];
593-
String result(buff, sizeof(buff), from->charset());
592+
// TODO(villagesql-performance): evaluate something more performant
593+
StringBuffer<MAX_FIELD_WIDTH> result(from->charset());
594+
result.length(0U);
594595
from->val_external_str(&result);
595596

596597
THD *thd = current_thd;
@@ -634,6 +635,17 @@ bool TryCopyCustomTypeField(const Field *from, Field *to) {
634635
return false;
635636
}
636637

638+
void CopyCustomToStringField(const Field *from, Field *to) {
639+
assert(from->has_type_context());
640+
// Custom → non-custom string: decode to string representation.
641+
// NULL is handled outside this function
642+
// TODO(villagesql-performance): evaluate something more performant
643+
StringBuffer<MAX_FIELD_WIDTH> res(from->charset());
644+
res.length(0U);
645+
from->val_external_str(&res);
646+
to->store(res.ptr(), res.length(), res.charset());
647+
}
648+
637649
type_conversion_status TryEncodeStringFieldToCustom(Field *from_field,
638650
Field *to_field) {
639651
assert(!from_field->has_type_context());

villagesql/types/util.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,9 @@ extern bool ValidateAndReportCustomFieldStore(const Item *item,
287287
// If to does not have a custom type, generates an error with readable format.
288288
extern bool TryCopyCustomTypeField(const Field *from, Field *to);
289289

290+
// Copy from a custom type field to string type field.
291+
extern void CopyCustomToStringField(const Field *from, Field *to);
292+
290293
// Encode a string field value and store it in a custom type field.
291294
// This enables CTEs and subqueries with string values to work with custom
292295
// types:

0 commit comments

Comments
 (0)