Skip to content

Commit 8df89f4

Browse files
committed
merge from main
2 parents 14b9832 + 84e82e6 commit 8df89f4

File tree

17 files changed

+263
-40
lines changed

17 files changed

+263
-40
lines changed

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,18 @@ name = "sqlparser"
3737
path = "src/lib.rs"
3838

3939
[features]
40-
default = ["std"]
40+
default = ["std", "recursive-protection"]
4141
std = []
42+
recursive-protection = ["std", "recursive"]
4243
# Enable JSON output in the `cli` example:
4344
json_example = ["serde_json", "serde"]
4445
visitor = ["sqlparser_derive"]
4546

4647
[dependencies]
4748
bigdecimal = { version = "0.4.1", features = ["serde"], optional = true }
4849
log = "0.4"
50+
recursive = { version = "0.1.1", optional = true}
51+
4952
serde = { version = "1.0", features = ["derive"], optional = true }
5053
# serde_json is only used in examples/cli, but we have to put it outside
5154
# of dev-dependencies because of

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ The following optional [crate features](https://doc.rust-lang.org/cargo/referen
6363

6464
* `serde`: Adds [Serde](https://serde.rs/) support by implementing `Serialize` and `Deserialize` for all AST nodes.
6565
* `visitor`: Adds a `Visitor` capable of recursively walking the AST tree.
66-
66+
* `recursive-protection` (enabled by default), uses [recursive](https://docs.rs/recursive/latest/recursive/) for stack overflow protection.
6767

6868
## Syntax vs Semantics
6969

derive/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,10 @@ fn derive_visit(input: proc_macro::TokenStream, visit_type: &VisitType) -> proc_
7878

7979
let expanded = quote! {
8080
// The generated impl.
81+
// Note that it uses [`recursive::recursive`] to protect from stack overflow.
82+
// See tests in https://github.com/apache/datafusion-sqlparser-rs/pull/1522/ for more info.
8183
impl #impl_generics sqlparser::ast::#visit_trait for #name #ty_generics #where_clause {
84+
#[cfg_attr(feature = "recursive-protection", recursive::recursive)]
8285
fn visit<V: sqlparser::ast::#visitor_trait>(
8386
&#modifier self,
8487
visitor: &mut V

sqlparser_bench/benches/sqlparser_bench.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,46 @@ fn basic_queries(c: &mut Criterion) {
4242
group.bench_function("sqlparser::with_select", |b| {
4343
b.iter(|| Parser::parse_sql(&dialect, with_query).unwrap());
4444
});
45+
46+
let large_statement = {
47+
let expressions = (0..1000)
48+
.map(|n| format!("FN_{}(COL_{})", n, n))
49+
.collect::<Vec<_>>()
50+
.join(", ");
51+
let tables = (0..1000)
52+
.map(|n| format!("TABLE_{}", n))
53+
.collect::<Vec<_>>()
54+
.join(" JOIN ");
55+
let where_condition = (0..1000)
56+
.map(|n| format!("COL_{} = {}", n, n))
57+
.collect::<Vec<_>>()
58+
.join(" OR ");
59+
let order_condition = (0..1000)
60+
.map(|n| format!("COL_{} DESC", n))
61+
.collect::<Vec<_>>()
62+
.join(", ");
63+
64+
format!(
65+
"SELECT {} FROM {} WHERE {} ORDER BY {}",
66+
expressions, tables, where_condition, order_condition
67+
)
68+
};
69+
70+
group.bench_function("parse_large_statement", |b| {
71+
b.iter(|| Parser::parse_sql(&dialect, criterion::black_box(large_statement.as_str())));
72+
});
73+
74+
let large_statement = Parser::parse_sql(&dialect, large_statement.as_str())
75+
.unwrap()
76+
.pop()
77+
.unwrap();
78+
79+
group.bench_function("format_large_statement", |b| {
80+
b.iter(|| {
81+
let formatted_query = large_statement.to_string();
82+
assert_eq!(formatted_query, large_statement);
83+
});
84+
});
4585
}
4686

4787
criterion_group!(benches, basic_queries);

src/ast/data_type.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ pub enum DataType {
254254
/// [postgresql]: https://www.postgresql.org/docs/15/datatype.html
255255
Float8,
256256
/// Double
257-
Double,
257+
Double(ExactNumberInfo),
258258
/// Double PRECISION e.g. [standard], [postgresql]
259259
///
260260
/// [standard]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#approximate-numeric-type
@@ -508,7 +508,7 @@ impl fmt::Display for DataType {
508508
DataType::Float4 => write!(f, "FLOAT4"),
509509
DataType::Float32 => write!(f, "Float32"),
510510
DataType::Float64 => write!(f, "FLOAT64"),
511-
DataType::Double => write!(f, "DOUBLE"),
511+
DataType::Double(info) => write!(f, "DOUBLE{info}"),
512512
DataType::Float8 => write!(f, "FLOAT8"),
513513
DataType::DoublePrecision => write!(f, "DOUBLE PRECISION"),
514514
DataType::Bool => write!(f, "BOOL"),

src/ast/ddl.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -885,12 +885,14 @@ impl fmt::Display for TableConstraint {
885885
} => {
886886
write!(
887887
f,
888-
"{}FOREIGN KEY ({}) REFERENCES {}({})",
888+
"{}FOREIGN KEY ({}) REFERENCES {}",
889889
display_constraint_name(name),
890890
display_comma_separated(columns),
891891
foreign_table,
892-
display_comma_separated(referred_columns),
893892
)?;
893+
if !referred_columns.is_empty() {
894+
write!(f, "({})", display_comma_separated(referred_columns))?;
895+
}
894896
if let Some(action) = on_delete {
895897
write!(f, " ON DELETE {action}")?;
896898
}

src/ast/mod.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1291,6 +1291,7 @@ impl fmt::Display for CastFormat {
12911291
}
12921292

12931293
impl fmt::Display for Expr {
1294+
#[cfg_attr(feature = "recursive-protection", recursive::recursive)]
12941295
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
12951296
match self {
12961297
Expr::Identifier(s) => write!(f, "{s}"),
@@ -2354,6 +2355,7 @@ pub enum Statement {
23542355
cache_metadata: bool,
23552356
noscan: bool,
23562357
compute_statistics: bool,
2358+
has_table_keyword: bool,
23572359
},
23582360
/// ```sql
23592361
/// TRUNCATE
@@ -3239,6 +3241,9 @@ pub enum Statement {
32393241
///
32403242
/// [SQLite](https://sqlite.org/lang_explain.html)
32413243
query_plan: bool,
3244+
/// `EXPLAIN ESTIMATE`
3245+
/// [Clickhouse](https://clickhouse.com/docs/en/sql-reference/statements/explain#explain-estimate)
3246+
estimate: bool,
32423247
/// A SQL query that specifies what to explain
32433248
statement: Box<Statement>,
32443249
/// Optional output format of explain
@@ -3471,6 +3476,7 @@ impl fmt::Display for Statement {
34713476
verbose,
34723477
analyze,
34733478
query_plan,
3479+
estimate,
34743480
statement,
34753481
format,
34763482
options,
@@ -3483,6 +3489,9 @@ impl fmt::Display for Statement {
34833489
if *analyze {
34843490
write!(f, "ANALYZE ")?;
34853491
}
3492+
if *estimate {
3493+
write!(f, "ESTIMATE ")?;
3494+
}
34863495

34873496
if *verbose {
34883497
write!(f, "VERBOSE ")?;
@@ -3644,8 +3653,13 @@ impl fmt::Display for Statement {
36443653
cache_metadata,
36453654
noscan,
36463655
compute_statistics,
3656+
has_table_keyword,
36473657
} => {
3648-
write!(f, "ANALYZE TABLE {table_name}")?;
3658+
write!(
3659+
f,
3660+
"ANALYZE{}{table_name}",
3661+
if *has_table_keyword { " TABLE " } else { " " }
3662+
)?;
36493663
if let Some(ref parts) = partitions {
36503664
if !parts.is_empty() {
36513665
write!(f, " PARTITION ({})", display_comma_separated(parts))?;

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ impl Spanned for Statement {
284284
cache_metadata: _,
285285
noscan: _,
286286
compute_statistics: _,
287+
has_table_keyword: _,
287288
} => union_spans(
288289
core::iter::once(table_name.span())
289290
.chain(partitions.iter().flat_map(|i| i.iter().map(|k| k.span())))

src/ast/visitor.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -894,4 +894,29 @@ mod tests {
894894
assert_eq!(actual, expected)
895895
}
896896
}
897+
898+
struct QuickVisitor; // [`TestVisitor`] is too slow to iterate over thousands of nodes
899+
900+
impl Visitor for QuickVisitor {
901+
type Break = ();
902+
}
903+
904+
#[test]
905+
fn overflow() {
906+
let cond = (0..1000)
907+
.map(|n| format!("X = {}", n))
908+
.collect::<Vec<_>>()
909+
.join(" OR ");
910+
let sql = format!("SELECT x where {0}", cond);
911+
912+
let dialect = GenericDialect {};
913+
let tokens = Tokenizer::new(&dialect, sql.as_str()).tokenize().unwrap();
914+
let s = Parser::new(&dialect)
915+
.with_tokens(tokens)
916+
.parse_statement()
917+
.unwrap();
918+
919+
let mut visitor = QuickVisitor {};
920+
s.visit(&mut visitor);
921+
}
897922
}

src/dialect/snowflake.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,10 @@ pub fn parse_create_table(
377377
parser.expect_token(&Token::RParen)?;
378378
builder = builder.with_tags(Some(tags));
379379
}
380+
Keyword::ON if parser.parse_keyword(Keyword::COMMIT) => {
381+
let on_commit = Some(parser.parse_create_table_on_commit()?);
382+
builder = builder.on_commit(on_commit);
383+
}
380384
_ => {
381385
return parser.expected("end of statement", next_token);
382386
}

0 commit comments

Comments
 (0)