From 685d76c3137c25fb2e4ea39d1e26eabafdbe2e57 Mon Sep 17 00:00:00 2001 From: Marcelo Altmann Date: Tue, 16 Sep 2025 21:32:35 -0300 Subject: [PATCH 1/3] MySQL: Add support for INVISIBLE columns This commit adds support for INVISIBLE columns in MySQL dialect. --- src/ast/ddl.rs | 10 ++++++++++ src/ast/spans.rs | 1 + src/keywords.rs | 1 + src/parser/mod.rs | 5 +++++ tests/sqlparser_mysql.rs | 30 ++++++++++++++++++++++++++++++ 5 files changed, 47 insertions(+) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index a15474755..d8f309b17 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -1911,6 +1911,13 @@ pub enum ColumnOption { /// ``` /// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/creating-spatial-indexes.html Srid(Box), + /// MySQL specific: Column is invisible via SELECT * + /// Syntax: + /// ```sql + /// CREATE TABLE t (foo INT, bar INT INVISIBLE); + /// ``` + /// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/invisible-columns.html + Invisible, } impl fmt::Display for ColumnOption { @@ -2029,6 +2036,9 @@ impl fmt::Display for ColumnOption { Srid(srid) => { write!(f, "SRID {srid}") } + Invisible => { + write!(f, "INVISIBLE") + } } } } diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 4e15edd88..148070e54 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -918,6 +918,7 @@ impl Spanned for ColumnOption { ColumnOption::Policy(..) => Span::empty(), ColumnOption::Tags(..) => Span::empty(), ColumnOption::Srid(..) => Span::empty(), + ColumnOption::Invisible => Span::empty(), } } } diff --git a/src/keywords.rs b/src/keywords.rs index 3358e2845..e0590b69d 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -499,6 +499,7 @@ define_keywords!( INTERSECTION, INTERVAL, INTO, + INVISIBLE, INVOKER, IO, IS, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 819819f9d..d8e5aa2ab 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8144,6 +8144,11 @@ impl<'a> Parser<'a> { Keyword::REPLACE, ])?, ))) + } else if dialect_of!(self is MySqlDialect | GenericDialect) + && self.parse_keyword(Keyword::INVISIBLE) + { + // Support INVISIBLE for MySQL + Ok(Some(ColumnOption::Invisible)) } else { Ok(None) } diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 0857bae3b..6f5f4dc25 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -4247,3 +4247,33 @@ fn test_create_index_options() { "CREATE INDEX idx_name ON t(c1, c2) USING BTREE LOCK = EXCLUSIVE ALGORITHM = DEFAULT", ); } + +#[test] +fn parse_invisible_column() { + mysql().verified_stmt("CREATE TABLE t (foo INT, bar INT INVISIBLE)"); + mysql().verified_stmt("ALTER TABLE t ADD COLUMN bar INT INVISIBLE"); + let stmt = mysql().verified_stmt("CREATE TABLE t (foo INT, bar INT INVISIBLE)"); + match stmt { + Statement::CreateTable(CreateTable { columns, .. }) => { + assert_eq!( + columns, + vec![ + ColumnDef { + name: "foo".into(), + data_type: DataType::Int(None), + options: vec![], + }, + ColumnDef { + name: "bar".into(), + data_type: DataType::Int(None), + options: vec![ColumnOptionDef { + name: None, + option: ColumnOption::Invisible, + },], + } + ] + ); + } + _ => panic!("Unexpected statement {stmt}"), + } +} From cee711d33fc898c7e5006884b612f9b5a7ff2316 Mon Sep 17 00:00:00 2001 From: Marcelo Altmann Date: Wed, 17 Sep 2025 15:28:49 -0300 Subject: [PATCH 2/3] Make INVISIBLE keyword match all dialects Removed the MySQL dialect check and made it match all dialects. --- src/parser/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index d8e5aa2ab..bc6d3bef6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8144,10 +8144,7 @@ impl<'a> Parser<'a> { Keyword::REPLACE, ])?, ))) - } else if dialect_of!(self is MySqlDialect | GenericDialect) - && self.parse_keyword(Keyword::INVISIBLE) - { - // Support INVISIBLE for MySQL + } else if self.parse_keyword(Keyword::INVISIBLE) { Ok(Some(ColumnOption::Invisible)) } else { Ok(None) From 612c651213e7f3a103694ce0fbecef6436ad7d1a Mon Sep 17 00:00:00 2001 From: Marcelo Altmann Date: Fri, 19 Sep 2025 08:03:09 -0300 Subject: [PATCH 3/3] fix test case Moved test case to common test file. --- tests/sqlparser_common.rs | 55 ++++++++++++++++++++++++++++++++++++++- tests/sqlparser_mysql.rs | 30 --------------------- 2 files changed, 54 insertions(+), 31 deletions(-) diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 720c1e492..f46abc7d9 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -17138,7 +17138,7 @@ fn test_parse_semantic_view_table_factor() { } let ast_sql = r#"SELECT * FROM SEMANTIC_VIEW( - my_model + my_model DIMENSIONS DATE_PART('year', date_col), region_name METRICS orders.revenue, orders.count WHERE active = true @@ -17193,3 +17193,56 @@ fn parse_adjacent_string_literal_concatenation() { let sql = "SELECT * FROM t WHERE col = 'Hello' \n ' ' \t 'World!'"; dialects.one_statement_parses_to(sql, r"SELECT * FROM t WHERE col = 'Hello World!'"); } + +#[test] +fn parse_invisible_column() { + let sql = r#"CREATE TABLE t (foo INT, bar INT INVISIBLE)"#; + let stmt = verified_stmt(sql); + match stmt { + Statement::CreateTable(CreateTable { columns, .. }) => { + assert_eq!( + columns, + vec![ + ColumnDef { + name: "foo".into(), + data_type: DataType::Int(None), + options: vec![] + }, + ColumnDef { + name: "bar".into(), + data_type: DataType::Int(None), + options: vec![ColumnOptionDef { + name: None, + option: ColumnOption::Invisible + }] + } + ] + ); + } + _ => panic!("Unexpected statement {stmt}"), + } + + let sql = r#"ALTER TABLE t ADD COLUMN bar INT INVISIBLE"#; + let stmt = verified_stmt(sql); + match stmt { + Statement::AlterTable { operations, .. } => { + assert_eq!( + operations, + vec![AlterTableOperation::AddColumn { + column_keyword: true, + if_not_exists: false, + column_def: ColumnDef { + name: "bar".into(), + data_type: DataType::Int(None), + options: vec![ColumnOptionDef { + name: None, + option: ColumnOption::Invisible + }] + }, + column_position: None + }] + ); + } + _ => panic!("Unexpected statement {stmt}"), + } +} diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 6f5f4dc25..0857bae3b 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -4247,33 +4247,3 @@ fn test_create_index_options() { "CREATE INDEX idx_name ON t(c1, c2) USING BTREE LOCK = EXCLUSIVE ALGORITHM = DEFAULT", ); } - -#[test] -fn parse_invisible_column() { - mysql().verified_stmt("CREATE TABLE t (foo INT, bar INT INVISIBLE)"); - mysql().verified_stmt("ALTER TABLE t ADD COLUMN bar INT INVISIBLE"); - let stmt = mysql().verified_stmt("CREATE TABLE t (foo INT, bar INT INVISIBLE)"); - match stmt { - Statement::CreateTable(CreateTable { columns, .. }) => { - assert_eq!( - columns, - vec![ - ColumnDef { - name: "foo".into(), - data_type: DataType::Int(None), - options: vec![], - }, - ColumnDef { - name: "bar".into(), - data_type: DataType::Int(None), - options: vec![ColumnOptionDef { - name: None, - option: ColumnOption::Invisible, - },], - } - ] - ); - } - _ => panic!("Unexpected statement {stmt}"), - } -}