Skip to content

Commit d79ba4c

Browse files
CopilotBohuTANG
andcommitted
Add validation to prevent ALTER COLUMN NOT NULL with NULL values
Co-authored-by: BohuTANG <[email protected]>
1 parent b8be9bc commit d79ba4c

File tree

2 files changed

+114
-6
lines changed

2 files changed

+114
-6
lines changed

src/query/service/src/interpreters/interpreter_table_modify_column.rs

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,33 @@ impl ModifyTableColumnInterpreter {
162162
let schema = table.schema().as_ref().clone();
163163
let table_info = table.get_table_info();
164164
let mut new_schema = schema.clone();
165+
166+
// Validate that columns being changed to NOT NULL don't contain NULL values
167+
let fuse_table = FuseTable::try_from_table(table.as_ref())?;
168+
let base_snapshot = fuse_table.read_table_snapshot().await?;
169+
if let Some(snapshot) = base_snapshot.as_ref() {
170+
for (field, _comment) in field_and_comments {
171+
if let Some((_, old_field)) = schema.column_with_name(&field.name) {
172+
// Check if we're changing from nullable to NOT NULL
173+
let is_removing_nullable =
174+
old_field.data_type.is_nullable() && !field.data_type.is_nullable();
175+
176+
if is_removing_nullable {
177+
// Check if column contains NULL values by examining statistics
178+
let column_id = old_field.column_id;
179+
if let Some(col_stats) = snapshot.summary.col_stats.get(&column_id) {
180+
if col_stats.null_count > 0 {
181+
return Err(ErrorCode::BadArguments(format!(
182+
"Cannot change column '{}' to NOT NULL: column contains {} NULL values",
183+
field.name, col_stats.null_count
184+
)));
185+
}
186+
}
187+
}
188+
}
189+
}
190+
}
191+
165192
// first check default expr before lock table
166193
for (field, _comment) in field_and_comments {
167194
if let Some((i, old_field)) = schema.column_with_name(&field.name) {
@@ -455,12 +482,10 @@ impl ModifyTableColumnInterpreter {
455482
(_, _) => {
456483
if need_remove_nullable {
457484
// If the column is being changed from NULLABLE to NOT NULL,
458-
// wrap it with `coalesce()` to replace NULL values with the default,
459-
// and `remove_nullable()` to mark the resulting expression as non-nullable.
460-
format!(
461-
"remove_nullable(coalesce(`{}`, {}))",
462-
field.name, default_scalar
463-
)
485+
// use `remove_nullable()` to mark the resulting expression as non-nullable.
486+
// Note: We validate earlier that the column doesn't contain NULL values,
487+
// so we don't need to use coalesce() to replace NULLs.
488+
format!("remove_nullable(`{}`)", field.name)
464489
} else {
465490
format!("`{}`", field.name)
466491
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
statement ok
2+
USE default
3+
4+
statement ok
5+
DROP TABLE IF EXISTS test_alter_not_null
6+
7+
# Test 1: Cannot alter column to NOT NULL when it contains NULL values
8+
statement ok
9+
CREATE OR REPLACE TABLE test_alter_not_null(a STRING NULL, b INT NULL);
10+
11+
statement ok
12+
INSERT INTO test_alter_not_null VALUES ('a',1), ('b',NULL), (NULL,3), ('d',4);
13+
14+
query IT
15+
SELECT a, b FROM test_alter_not_null ORDER BY b;
16+
----
17+
a 1
18+
b NULL
19+
NULL 3
20+
d 4
21+
22+
# Should fail: column 'a' contains NULL values
23+
statement error 1065
24+
ALTER TABLE test_alter_not_null MODIFY COLUMN a STRING NOT NULL;
25+
26+
# Should fail: column 'b' contains NULL values
27+
statement error 1065
28+
ALTER TABLE test_alter_not_null MODIFY COLUMN b INT NOT NULL;
29+
30+
statement ok
31+
DROP TABLE test_alter_not_null
32+
33+
# Test 2: Can alter column to NOT NULL when it doesn't contain NULL values
34+
statement ok
35+
CREATE OR REPLACE TABLE test_alter_not_null(a STRING NULL, b INT NULL);
36+
37+
statement ok
38+
INSERT INTO test_alter_not_null VALUES ('a',1), ('b',2), ('c',3), ('d',4);
39+
40+
query IT
41+
SELECT a, b FROM test_alter_not_null ORDER BY b;
42+
----
43+
a 1
44+
b 2
45+
c 3
46+
d 4
47+
48+
# Should succeed: column 'a' doesn't contain NULL values
49+
statement ok
50+
ALTER TABLE test_alter_not_null MODIFY COLUMN a STRING NOT NULL;
51+
52+
# Should succeed: column 'b' doesn't contain NULL values
53+
statement ok
54+
ALTER TABLE test_alter_not_null MODIFY COLUMN b INT NOT NULL;
55+
56+
query TT
57+
DESC test_alter_not_null;
58+
----
59+
a VARCHAR NO NULL (empty)
60+
b INT NO NULL (empty)
61+
62+
statement ok
63+
DROP TABLE test_alter_not_null
64+
65+
# Test 3: Empty table should allow NOT NULL
66+
statement ok
67+
CREATE OR REPLACE TABLE test_alter_not_null(a STRING NULL, b INT NULL);
68+
69+
# Should succeed: table is empty
70+
statement ok
71+
ALTER TABLE test_alter_not_null MODIFY COLUMN a STRING NOT NULL;
72+
73+
statement ok
74+
ALTER TABLE test_alter_not_null MODIFY COLUMN b INT NOT NULL;
75+
76+
query TT
77+
DESC test_alter_not_null;
78+
----
79+
a VARCHAR NO NULL (empty)
80+
b INT NO NULL (empty)
81+
82+
statement ok
83+
DROP TABLE test_alter_not_null

0 commit comments

Comments
 (0)