You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by lu...@apache.org on 2026/10/19 09:59:17 UTC
(datafusion-sqlparser-rs) branch upstream/colon-access-dialect-gate updated (7569dc717 -> afb5d83ca)
This is an automated email from the ASF dual-hosted git repository.
LucaCappelletti94 pushed a change to branch upstream/colon-access-dialect-gate
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
discard 7569dc717 Gate infix colon JSON access and literal/placeholder field access by dialect
add e790703ab Parser: fix exponential parse time on nested POSITION calls (#2619)
add dacdbc99c ClickHouse: Support GLOBAL IN / GLOBAL NOT IN (#2598)
add 5d1eaf352 Assert exact errors in negative tests (1/4): PostgreSQL tests (#2628)
add 5d653a257 ci: raise the merge queue status check timeout to 360 minutes (#2638)
add 7cac9e17a MySQL: Add support for `ZEROFILL` on numeric data types (#2636)
add fef1e355f Postgres: support OVERRIDING { SYSTEM | USER } VALUE in INSERT (#2626)
add 92e88502a ClickHouse: Support ON CLUSTER and SETTINGS in DELETE and UPDATE (#2617)
add a6f518bf7 Bump serde_json from 1.0.151 to 1.0.154 (#2637)
add 6434b5a22 Assert exact errors in negative tests (2/4): common and parser unit tests (#2629)
add f1854e47e Assert exact errors in negative tests (3/4): dialect tests (#2630)
add a80289f9f Assert exact errors in negative tests (4/4): deny clippy::assertions_on_result_states (#2631)
add de737560d Deny `clippy::manual_assert_eq` (#2644)
add f88f68507 Parser: Fix array suffixes after a `>>` that closes two nested types (#2643)
add 942f36afc SQLite: Support comma-separated WITHOUT ROWID and STRICT table options (#2575)
add c62f01d93 PostgreSQL: Remove unreachable empty op_types branch in operator FUNCTION items (#2632)
add 8338588e9 Commit the fuzz crate's Cargo.lock (#2647)
add 7d34c89b7 Deny `clippy::should_panic_without_expect` (#2648)
add 66d90faaa SetExpr: apply recursive-protection to Display (#2623)
add dcbc923dd MySQL: Bind && with the precedence of AND (#2576)
add c98351352 Postgres: Support aliases for JOIN USING (#2492)
add 0ab37b9e2 SQLite: Support `$` inside identifiers (#2554)
add 5a975d0f4 Deny `clippy::ignore_without_reason` (#2652)
add 7b5821dd4 Deny `clippy::allow_attributes` and `allow_attributes_without_reason` (#2653)
add 1aabd12d1 Add `Dialect::supports_cte_materialization` and enable it for DuckDB (#2651)
add 63015f54e ClickHouse, MySQL: Keep the quoting of the CREATE TABLE ENGINE name (#2655)
add 4ad878405 SQLite: Support a parenthesized table name in `FROM` (#2658)
add 66e0e8985 PostgreSQL: Reject string literals as aliases (#2668)
add 8977e96a9 Deny `clippy::dbg_macro and clippy::todo` (#2654)
add ada831442 DuckDB: Always render the macro argument list parentheses (#2656)
add d32825d6f replaces the hard-coded dialect list in the tokenizer with a `supports_hash_comment` dialect method (#2674)
add 9bf83ce06 SQLite: Reject colon-based semi-structured data traversal
add e30f8ffc7 Assert exact errors for the new deny lints
add 8ec5e9dc4 Merge main with SQLite colon traversal dependency
new afb5d83ca Gate infix colon JSON access and literal/placeholder field access by dialect
This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version. This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:
* -- * -- B -- O -- O -- O (7569dc717)
\
N -- N -- N refs/heads/upstream/colon-access-dialect-gate (afb5d83ca)
You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.
Any revisions marked "omit" are not gone; other references still
refer to them. Any revisions marked "discard" are gone forever.
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.asf.yaml | 37 +
.github/dependabot.yml | 5 +
Cargo.lock | 4 +-
Cargo.toml | 10 +
fuzz/.gitignore | 1 -
fuzz/Cargo.lock | 1139 +++++++++++++++++++++++++++
fuzz/fuzz_seeds/clickhouse_delete.sql | 5 +
fuzz/fuzz_seeds/clickhouse_global_in.sql | 63 ++
fuzz/fuzz_seeds/clickhouse_hash_comment.sql | 48 ++
fuzz/fuzz_seeds/clickhouse_update.sql | 5 +
src/ast/data_type.rs | 5 +
src/ast/ddl.rs | 62 +-
src/ast/dml.rs | 56 ++
src/ast/helpers/stmt_create_table.rs | 31 +-
src/ast/mod.rs | 46 +-
src/ast/query.rs | 7 +-
src/ast/spans.rs | 73 +-
src/dialect/bigquery.rs | 4 +
src/dialect/clickhouse.rs | 15 +
src/dialect/duckdb.rs | 4 +
src/dialect/generic.rs | 4 +
src/dialect/hive.rs | 4 +
src/dialect/mod.rs | 70 +-
src/dialect/mysql.rs | 20 +-
src/dialect/postgresql.rs | 10 +
src/dialect/snowflake.rs | 10 +-
src/dialect/sqlite.rs | 15 +-
src/keywords.rs | 7 +-
src/lib.rs | 9 +-
src/parser/mod.rs | 226 +++++-
src/tokenizer.rs | 20 +-
tests/no_std_recursion.rs | 14 +-
tests/sqlparser_bigquery.rs | 39 +-
tests/sqlparser_clickhouse.rs | 328 +++++++-
tests/sqlparser_common.rs | 744 ++++++++++++-----
tests/sqlparser_custom_dialect.rs | 22 +-
tests/sqlparser_databricks.rs | 39 +-
tests/sqlparser_derive_dialect.rs | 12 +-
tests/sqlparser_duckdb.rs | 52 +-
tests/sqlparser_mssql.rs | 87 +-
tests/sqlparser_mysql.rs | 219 +++--
tests/sqlparser_postgres.rs | 1022 +++++++++++++-----------
tests/sqlparser_redshift.rs | 10 +-
tests/sqlparser_regression.rs | 3 +-
tests/sqlparser_snowflake.rs | 172 ++--
tests/sqlparser_sqlite.rs | 186 ++++-
tests/sqlparser_teradata.rs | 10 +-
47 files changed, 3919 insertions(+), 1055 deletions(-)
create mode 100644 fuzz/Cargo.lock
create mode 100644 fuzz/fuzz_seeds/clickhouse_delete.sql
create mode 100644 fuzz/fuzz_seeds/clickhouse_global_in.sql
create mode 100644 fuzz/fuzz_seeds/clickhouse_hash_comment.sql
create mode 100644 fuzz/fuzz_seeds/clickhouse_update.sql
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org
(datafusion-sqlparser-rs) 01/01: Gate infix colon JSON access and literal/placeholder field access by dialect
Posted by lu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
LucaCappelletti94 pushed a commit to branch upstream/colon-access-dialect-gate
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
commit afb5d83cab4d36c6f8ad33dcb2bf5ca8b7f2da4e
Author: LucaCappelletti94 <ca...@gmail.com>
AuthorDate: Fri Oct 2 20:52:52 2026 +0200
Gate infix colon JSON access and literal/placeholder field access by dialect
---
derive/src/dialect.rs | 2 +-
src/dialect/databricks.rs | 4 ++++
src/dialect/generic.rs | 4 ++++
src/dialect/mod.rs | 28 ++++++++++++++++++++--------
src/dialect/postgresql.rs | 6 ++++++
src/dialect/snowflake.rs | 4 ++++
src/dialect/sqlite.rs | 7 ++++++-
src/parser/mod.rs | 11 ++++++++---
tests/sqlparser_common.rs | 27 +++++++++++++++++++++------
tests/sqlparser_postgres.rs | 27 +++++++++++++++++++++++++++
tests/sqlparser_sqlite.rs | 12 +++++++++++-
11 files changed, 112 insertions(+), 20 deletions(-)
diff --git a/derive/src/dialect.rs b/derive/src/dialect.rs
index af17067ac..0aad90ae8 100644
--- a/derive/src/dialect.rs
+++ b/derive/src/dialect.rs
@@ -229,7 +229,7 @@ fn generate_derived_dialect(input: &DeriveDialectInput, methods: &[DialectMethod
const _: () = {
use ::core::iter::Peekable;
use ::core::str::Chars;
- use sqlparser::ast::{ColumnOption, Expr, GranteesType, Ident, ObjectNamePart, Statement};
+ use sqlparser::ast::{ColumnOption, Expr, GranteesType, Ident, ObjectNamePart, Statement, Value};
use sqlparser::dialect::{Dialect, Precedence};
use sqlparser::keywords::Keyword;
use sqlparser::parser::{Parser, ParserError};
diff --git a/src/dialect/databricks.rs b/src/dialect/databricks.rs
index ca8610dd6..9efd03e85 100644
--- a/src/dialect/databricks.rs
+++ b/src/dialect/databricks.rs
@@ -48,6 +48,10 @@ impl Dialect for DatabricksDialect {
true
}
+ fn supports_semi_structured_data_traversal(&self) -> bool {
+ true
+ }
+
fn supports_filter_during_aggregation(&self) -> bool {
true
}
diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs
index 694eb8f19..09b1fe723 100644
--- a/src/dialect/generic.rs
+++ b/src/dialect/generic.rs
@@ -28,6 +28,10 @@ impl Dialect for GenericDialect {
true
}
+ fn supports_semi_structured_data_traversal(&self) -> bool {
+ true
+ }
+
fn is_delimited_identifier_start(&self, ch: char) -> bool {
ch == '"' || ch == '`'
}
diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs
index 69bbe355c..9dfc5a04e 100644
--- a/src/dialect/mod.rs
+++ b/src/dialect/mod.rs
@@ -132,7 +132,7 @@ pub use self::teradata::TeradataDialect;
#[cfg(feature = "derive-dialect")]
pub use sqlparser_derive::derive_dialect;
-use crate::ast::{ColumnOption, Expr, GranteesType, Ident, ObjectNamePart, Statement};
+use crate::ast::{ColumnOption, Expr, GranteesType, Ident, ObjectNamePart, Statement, Value};
pub use crate::keywords;
use crate::keywords::Keyword;
use crate::parser::{Parser, ParserError};
@@ -1002,13 +1002,15 @@ pub trait Dialect: Debug + Any {
Token::DoubleColon | Token::ExclamationMark | Token::LBracket | Token::CaretAt => {
Ok(p!(DoubleColon))
}
- Token::Colon => match &parser.peek_nth_token_ref(1).token {
- // When colon is followed by a string or a number, it's usually in MAP syntax.
- Token::SingleQuotedString(_) | Token::Number(_, _) => Ok(self.prec_unknown()),
- // In other cases, it's used in semi-structured data traversal like in variant or JSON
- // string columns. See `JsonAccess`.
- _ => Ok(p!(Colon)),
- },
+ Token::Colon if self.supports_semi_structured_data_traversal() => {
+ match &parser.peek_nth_token_ref(1).token {
+ // When colon is followed by a string or a number, it's usually in MAP syntax.
+ Token::SingleQuotedString(_) | Token::Number(_, _) => Ok(self.prec_unknown()),
+ // In other cases, it's used in semi-structured data traversal like in variant or JSON
+ // string columns. See `JsonAccess`.
+ _ => Ok(p!(Colon)),
+ }
+ }
Token::Arrow
| Token::LongArrow
| Token::HashArrow
@@ -1403,6 +1405,16 @@ pub trait Dialect: Debug + Any {
false
}
+ /// Returns true if the dialect supports traversing semi-structured data with the infix `:` operator, e.g. `a:b`.
+ fn supports_semi_structured_data_traversal(&self) -> bool {
+ false
+ }
+
+ /// Returns true if `value` may root a `.field` or `[index]` access chain under this dialect.
+ fn supports_value_access(&self, _value: &Value) -> bool {
+ true
+ }
+
/// Returns true if the dialect supports object-unpivot table factors in the FROM clause.
///
/// Syntax:
diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs
index e49417cc4..e7a9c47f1 100644
--- a/src/dialect/postgresql.rs
+++ b/src/dialect/postgresql.rs
@@ -28,6 +28,7 @@
// limitations under the License.
use log::debug;
+use crate::ast::Value;
use crate::dialect::{Dialect, Precedence};
use crate::keywords::Keyword;
use crate::parser::{Parser, ParserError};
@@ -378,4 +379,9 @@ impl Dialect for PostgreSqlDialect {
fn supports_comment_optimizer_hint(&self) -> bool {
true
}
+
+ /// `c_expr: PARAM opt_indirection` allows only a numbered parameter (`$1`).
+ fn supports_value_access(&self, value: &Value) -> bool {
+ matches!(value, Value::Placeholder(name) if name.starts_with('$'))
+ }
}
diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs
index ce4cde4b0..8888fd1d3 100644
--- a/src/dialect/snowflake.rs
+++ b/src/dialect/snowflake.rs
@@ -511,6 +511,10 @@ impl Dialect for SnowflakeDialect {
true
}
+ fn supports_semi_structured_data_traversal(&self) -> bool {
+ true
+ }
+
fn is_column_alias(&self, kw: &Keyword, parser: &mut Parser) -> bool {
match kw {
// The following keywords can be considered an alias as long as
diff --git a/src/dialect/sqlite.rs b/src/dialect/sqlite.rs
index f24407788..0a4bd2635 100644
--- a/src/dialect/sqlite.rs
+++ b/src/dialect/sqlite.rs
@@ -19,7 +19,7 @@
use alloc::boxed::Box;
use crate::ast::BinaryOperator;
-use crate::ast::{Expr, Statement};
+use crate::ast::{Expr, Statement, Value};
use crate::dialect::Dialect;
use crate::keywords::Keyword;
use crate::parser::{Parser, ParserError};
@@ -155,4 +155,9 @@ impl Dialect for SQLiteDialect {
fn supports_parens_around_table_factor(&self) -> bool {
true
}
+
+ /// SQLite's `nm ::= id | STRING` grammar takes a quoted string as a table qualifier.
+ fn supports_value_access(&self, value: &Value) -> bool {
+ matches!(value, Value::SingleQuotedString(_))
+ }
}
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index db54296fc..20302a7ad 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -2088,8 +2088,12 @@ impl<'a> Parser<'a> {
mut chain: Vec<AccessExpr>,
) -> Result<Expr, ParserError> {
let mut ending_wildcard: Option<TokenWithSpan> = None;
+ let root_value_access_allowed = match &root {
+ Expr::Value(v) => self.dialect.supports_value_access(&v.value),
+ _ => true,
+ };
loop {
- if self.consume_token(&Token::Period) {
+ if root_value_access_allowed && self.consume_token(&Token::Period) {
let next_token = self.peek_token_ref();
match &next_token.token {
Token::Mul => {
@@ -2189,7 +2193,8 @@ impl<'a> Parser<'a> {
}
}
}
- } else if !self.dialect.supports_partiql()
+ } else if root_value_access_allowed
+ && !self.dialect.supports_partiql()
&& self.peek_token_ref().token == Token::LBracket
{
self.parse_multi_dim_subscript(&mut chain)?;
@@ -4282,7 +4287,7 @@ impl<'a> Parser<'a> {
expr: Box::new(expr),
})
} else if Token::LBracket == *tok && self.dialect.supports_partiql()
- || (Token::Colon == *tok)
+ || (Token::Colon == *tok && self.dialect.supports_semi_structured_data_traversal())
{
self.prev_token();
self.parse_json_access(expr)
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index 4d5198953..ef2b5633d 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -20424,12 +20424,13 @@ fn parse_alter_table_column_position() {
#[test]
fn parse_compound_field_access_numeric_display() {
- all_dialects().verified_stmt("SELECT 1 . i");
- all_dialects().verified_stmt("SELECT (1 . i)");
- all_dialects().verified_stmt("SELECT 1 . 2");
- all_dialects().verified_stmt("SELECT (a . 1 . b)");
- all_dialects().verified_stmt("SELECT 1 . i.j");
- all_dialects().verified_stmt("SELECT 1 . 2 . 3");
+ let dialects = all_dialects_except(|d| d.is::<PostgreSqlDialect>() || d.is::<SQLiteDialect>());
+ dialects.verified_stmt("SELECT 1 . i");
+ dialects.verified_stmt("SELECT (1 . i)");
+ dialects.verified_stmt("SELECT 1 . 2");
+ dialects.verified_stmt("SELECT (a . 1 . b)");
+ dialects.verified_stmt("SELECT 1 . i.j");
+ dialects.verified_stmt("SELECT 1 . 2 . 3");
all_dialects().verified_stmt("SELECT a.b.c");
let err = all_dialects_where(|d| !d.supports_numeric_prefix())
@@ -20755,3 +20756,17 @@ fn parse_string_literal_alias() {
dialects.verified_stmt("SELECT 1 AS 'x'");
dialects.verified_stmt("SELECT a FROM t AS 'y'");
}
+
+#[test]
+fn parse_colon_json_access_requires_dialect_support() {
+ let unsupported = all_dialects_where(|d| !d.supports_semi_structured_data_traversal());
+ assert_eq!(
+ unsupported
+ .parse_sql_statements("SELECT a:b FROM t")
+ .unwrap_err(),
+ ParserError::ParserError("Expected: end of statement, found: :".to_string())
+ );
+
+ unsupported.verified_stmt("SELECT a::INT FROM t");
+ unsupported.verified_stmt("SELECT :name");
+}
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs
index 1e0028867..6927b2eca 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -10202,3 +10202,30 @@ fn parse_string_literal_alias_rejected() {
}
pg().verified_stmt(r#"SELECT a AS "x" FROM t AS "y""#);
}
+
+#[test]
+fn parse_param_indirection() {
+ pg().verified_stmt("SELECT $1.a");
+ pg().verified_stmt("SELECT $1[1]");
+ pg().verified_stmt("SELECT $1[1].a");
+}
+
+#[test]
+fn parse_indirection_rejects_non_param_value_roots() {
+ for sql in [
+ "SELECT 'abc'.foo",
+ "SELECT c = :NA.o FROM t",
+ "SELECT 2 .id FROM t",
+ ] {
+ assert_eq!(
+ pg().parse_sql_statements(sql).unwrap_err(),
+ ParserError::ParserError("Expected: end of statement, found: .".to_string()),
+ );
+ }
+ for sql in ["SELECT 'abc'[1]", "SELECT c = :NA[0] FROM t"] {
+ assert_eq!(
+ pg().parse_sql_statements(sql).unwrap_err(),
+ ParserError::ParserError("No infix parser for token LBracket".to_string()),
+ );
+ }
+}
diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs
index b1fe2fe58..9d3c6dff1 100644
--- a/tests/sqlparser_sqlite.rs
+++ b/tests/sqlparser_sqlite.rs
@@ -410,7 +410,17 @@ fn parse_create_table_with_strict() {
#[test]
fn parse_single_quoted_identified() {
sqlite().verified_only_select("SELECT 't'.*, t.'x' FROM 't'");
- // TODO: add support for select 't'.x
+ sqlite().verified_only_select("SELECT 't'.x FROM 't'");
+}
+
+#[test]
+fn parse_field_access_rejects_non_string_value_roots() {
+ for sql in ["SELECT c = :NA.o FROM t", "SELECT 2 .id FROM t"] {
+ assert_eq!(
+ sqlite().parse_sql_statements(sql).unwrap_err(),
+ ParserError::ParserError("Expected: end of statement, found: .".to_string())
+ );
+ }
}
#[test]
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org