Skip to content

Commit dcb80d4

Browse files
Merge branch 'main' into ci/cargo-audit
2 parents 586a0e8 + bf1741b commit dcb80d4

30 files changed

Lines changed: 2036 additions & 501 deletions

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ edition = "2021"
3636
name = "sqlparser"
3737
path = "src/lib.rs"
3838

39+
[lints.rust]
40+
unsafe_code = "forbid"
41+
3942
[features]
4043
default = ["std", "recursive-protection"]
4144
std = []

derive/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ edition = "2021"
3535
[lib]
3636
proc-macro = true
3737

38+
[lints.rust]
39+
unsafe_code = "forbid"
40+
3841
[dependencies]
3942
syn = { version = "2.0", default-features = false, features = ["full", "printing", "parsing", "derive", "proc-macro", "clone-impls"] }
4043
proc-macro2 = "1.0"

sqlparser_bench/benches/sqlparser_bench.rs

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
// under the License.
1717

1818
use criterion::{criterion_group, criterion_main, Criterion};
19-
use sqlparser::dialect::GenericDialect;
19+
use sqlparser::dialect::{GenericDialect, PostgreSqlDialect, SQLiteDialect};
2020
use sqlparser::keywords::Keyword;
2121
use sqlparser::parser::Parser;
2222
use sqlparser::tokenizer::{Span, Word};
@@ -177,11 +177,111 @@ fn parse_compound_chain(c: &mut Criterion) {
177177
group.finish();
178178
}
179179

180+
/// Benchmark parsing pathological compound chains with a reserved keyword in
181+
/// field position, like `SELECT x.not-b.not-b...`. The `.not-b` shape used to
182+
/// cause 2^N work in `parse_compound_expr` because `parse_prefix` descended
183+
/// into `parse_not` -> `parse_subexpr`, re-walking the remaining chain at
184+
/// every segment.
185+
fn parse_compound_keyword_chain(c: &mut Criterion) {
186+
let mut group = c.benchmark_group("parse_compound_keyword_chain");
187+
let dialect = GenericDialect {};
188+
189+
for &n in &[5usize, 10, 15] {
190+
let body = std::iter::repeat_n(".not-b", n).collect::<String>();
191+
let sql = format!("SELECT x{body}");
192+
193+
group.bench_function(format!("chain_{n}"), |b| {
194+
b.iter(|| {
195+
let _ = Parser::parse_sql(&dialect, std::hint::black_box(&sql));
196+
});
197+
});
198+
}
199+
200+
group.finish();
201+
}
202+
203+
/// Benchmark parsing pathological `IF(<keyword-fn>(<keyword-fn>(...x` chains
204+
/// that previously caused 2^N work in `parse_prefix`. Each nested
205+
/// `current_time(` segment used to be explored twice at every level (once via
206+
/// the speculative reserved-word arm, once via the unreserved-word fallback),
207+
/// doubling work per level. Post-fix the cost is linear in chain length.
208+
fn parse_prefix_keyword_call_chain(c: &mut Criterion) {
209+
let mut group = c.benchmark_group("parse_prefix_keyword_call_chain");
210+
let dialect = PostgreSqlDialect {};
211+
212+
for &n in &[10usize, 20, 30] {
213+
let sql = String::from("if(") + &"current_time(".repeat(n) + "x";
214+
215+
group.bench_function(format!("chain_{n}"), |b| {
216+
b.iter(|| {
217+
let _ = Parser::parse_sql(&dialect, std::hint::black_box(&sql));
218+
});
219+
});
220+
}
221+
222+
group.finish();
223+
}
224+
225+
/// Benchmark parsing pathological `case-case-case-...c` chains that
226+
/// previously caused 2^N work in `parse_prefix`. Each `case` token used to
227+
/// trigger a speculative `parse_case_expr` that recursively descends the
228+
/// chain, but the unreserved-word fallback returns `Identifier(case)` so the
229+
/// overall `parse_prefix` succeeds and the failure cache never fires.
230+
/// Post-fix the per-arm cache short-circuits the speculative descent.
231+
fn parse_prefix_case_chain(c: &mut Criterion) {
232+
let mut group = c.benchmark_group("parse_prefix_case_chain");
233+
let dialect = SQLiteDialect {};
234+
235+
for &n in &[10usize, 20, 30] {
236+
let sql = "case\t-".repeat(n) + "c";
237+
238+
group.bench_function(format!("chain_{n}"), |b| {
239+
b.iter(|| {
240+
let _ = Parser::parse_sql(&dialect, std::hint::black_box(&sql));
241+
});
242+
});
243+
}
244+
245+
group.finish();
246+
}
247+
248+
/// Benchmark parsing pathological paren chains that previously caused 2^N
249+
/// work in `parse_table_factor`. The input `SELECT 1 FROM ((((...` rejects
250+
/// at EOF, which used to force exponential backtracking through the chain.
251+
fn parse_table_factor_paren_chain(c: &mut Criterion) {
252+
let mut group = c.benchmark_group("parse_table_factor_paren_chain");
253+
let dialect = GenericDialect {};
254+
255+
for &n in &[10usize, 20, 30] {
256+
let mut sql = String::from("SELECT 1 ");
257+
for _ in 0..5 {
258+
sql.push_str("FROM ");
259+
sql.push_str(&"(".repeat(n));
260+
sql.push(' ');
261+
}
262+
263+
group.bench_function(format!("chain_{n}"), |b| {
264+
b.iter(|| {
265+
let _ = Parser::new(&dialect)
266+
.with_recursion_limit(256)
267+
.try_with_sql(std::hint::black_box(&sql))
268+
.and_then(|mut p| p.parse_statements());
269+
});
270+
});
271+
}
272+
273+
group.finish();
274+
}
275+
180276
criterion_group!(
181277
benches,
182278
basic_queries,
183279
word_to_ident,
184280
parse_many_identifiers,
185-
parse_compound_chain
281+
parse_compound_chain,
282+
parse_compound_keyword_chain,
283+
parse_prefix_keyword_call_chain,
284+
parse_prefix_case_chain,
285+
parse_table_factor_paren_chain
186286
);
187287
criterion_main!(benches);

src/ast/comments.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ impl Comments {
3333
/// last accepted comment. In other words, this method will skip the
3434
/// comment if its comming out of order (as encountered in the parsed
3535
/// source code.)
36-
pub(crate) fn offer(&mut self, comment: CommentWithSpan) {
36+
pub fn offer(&mut self, comment: CommentWithSpan) {
3737
if self
3838
.0
3939
.last()
@@ -71,7 +71,7 @@ impl Comments {
7171
/// // all comments appearing before line seven, i.e. before the first statement itself
7272
/// assert_eq!(
7373
/// &comments.find(..Location::new(7, 1)).map(|c| c.as_str()).collect::<Vec<_>>(),
74-
/// &["\n header comment ...\n ... spanning multiple lines\n", " first statement\n"]);
74+
/// &["\n header comment ...\n ... spanning multiple lines\n", " first statement"]);
7575
///
7676
/// // all comments appearing within the first statement
7777
/// assert_eq!(
@@ -81,7 +81,7 @@ impl Comments {
8181
/// // all comments appearing within or after the first statement
8282
/// assert_eq!(
8383
/// &comments.find(Location::new(7, 1)..).map(|c| c.as_str()).collect::<Vec<_>>(),
84-
/// &[" world ", " second statement\n", " trailing comment\n"]);
84+
/// &[" world ", " second statement", " trailing comment"]);
8585
/// ```
8686
///
8787
/// The [Spanned](crate::ast::Spanned) trait allows you to access location

src/ast/ddl.rs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5632,6 +5632,161 @@ impl Spanned for AlterFunction {
56325632
}
56335633
}
56345634

5635+
/// Text search object kind.
5636+
///
5637+
/// See [PostgreSQL](https://www.postgresql.org/docs/current/textsearch-intro.html).
5638+
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
5639+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
5640+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
5641+
pub enum TextSearchObjectType {
5642+
/// `DICTIONARY`
5643+
Dictionary,
5644+
/// `CONFIGURATION`
5645+
Configuration,
5646+
/// `TEMPLATE`
5647+
Template,
5648+
/// `PARSER`
5649+
Parser,
5650+
}
5651+
5652+
impl fmt::Display for TextSearchObjectType {
5653+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
5654+
match self {
5655+
TextSearchObjectType::Dictionary => write!(f, "DICTIONARY"),
5656+
TextSearchObjectType::Configuration => write!(f, "CONFIGURATION"),
5657+
TextSearchObjectType::Template => write!(f, "TEMPLATE"),
5658+
TextSearchObjectType::Parser => write!(f, "PARSER"),
5659+
}
5660+
}
5661+
}
5662+
5663+
/// `CREATE TEXT SEARCH ...` statement.
5664+
///
5665+
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createtsdictionary.html).
5666+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
5667+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
5668+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
5669+
pub struct CreateTextSearch {
5670+
/// The specific text search object type.
5671+
pub object_type: TextSearchObjectType,
5672+
/// Object name.
5673+
pub name: ObjectName,
5674+
/// Parenthesized options.
5675+
pub options: Vec<SqlOption>,
5676+
}
5677+
5678+
impl fmt::Display for CreateTextSearch {
5679+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
5680+
write!(
5681+
f,
5682+
"CREATE TEXT SEARCH {} {} ({})",
5683+
self.object_type,
5684+
self.name,
5685+
display_comma_separated(&self.options)
5686+
)
5687+
}
5688+
}
5689+
5690+
impl Spanned for CreateTextSearch {
5691+
fn span(&self) -> Span {
5692+
Span::empty()
5693+
}
5694+
}
5695+
5696+
/// Option assignment used by `ALTER TEXT SEARCH ... ( ... )`.
5697+
///
5698+
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-altertsdictionary.html).
5699+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
5700+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
5701+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
5702+
pub struct AlterTextSearchOption {
5703+
/// Option name.
5704+
pub key: Ident,
5705+
/// Optional value (`option [= value]`).
5706+
pub value: Option<Expr>,
5707+
}
5708+
5709+
impl fmt::Display for AlterTextSearchOption {
5710+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
5711+
match &self.value {
5712+
Some(value) => write!(f, "{} = {}", self.key, value),
5713+
None => write!(f, "{}", self.key),
5714+
}
5715+
}
5716+
}
5717+
5718+
/// Operation for `ALTER TEXT SEARCH ...`.
5719+
///
5720+
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-altertsdictionary.html).
5721+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
5722+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
5723+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
5724+
pub enum AlterTextSearchOperation {
5725+
/// `RENAME TO new_name`
5726+
RenameTo {
5727+
/// New name.
5728+
new_name: Ident,
5729+
},
5730+
/// `OWNER TO ...`
5731+
OwnerTo(Owner),
5732+
/// `SET SCHEMA schema_name`
5733+
SetSchema {
5734+
/// Target schema.
5735+
schema_name: ObjectName,
5736+
},
5737+
/// `( option [= value] [, ...] )`
5738+
SetOptions {
5739+
/// Text search options to apply.
5740+
options: Vec<AlterTextSearchOption>,
5741+
},
5742+
}
5743+
5744+
impl fmt::Display for AlterTextSearchOperation {
5745+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
5746+
match self {
5747+
AlterTextSearchOperation::RenameTo { new_name } => write!(f, "RENAME TO {new_name}"),
5748+
AlterTextSearchOperation::OwnerTo(owner) => write!(f, "OWNER TO {owner}"),
5749+
AlterTextSearchOperation::SetSchema { schema_name } => {
5750+
write!(f, "SET SCHEMA {schema_name}")
5751+
}
5752+
AlterTextSearchOperation::SetOptions { options } => {
5753+
write!(f, "({})", display_comma_separated(options))
5754+
}
5755+
}
5756+
}
5757+
}
5758+
5759+
/// `ALTER TEXT SEARCH ...` statement.
5760+
///
5761+
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-altertsdictionary.html).
5762+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
5763+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
5764+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
5765+
pub struct AlterTextSearch {
5766+
/// The specific text search object type.
5767+
pub object_type: TextSearchObjectType,
5768+
/// Object name.
5769+
pub name: ObjectName,
5770+
/// Operation to apply.
5771+
pub operation: AlterTextSearchOperation,
5772+
}
5773+
5774+
impl fmt::Display for AlterTextSearch {
5775+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
5776+
write!(
5777+
f,
5778+
"ALTER TEXT SEARCH {} {} {}",
5779+
self.object_type, self.name, self.operation
5780+
)
5781+
}
5782+
}
5783+
5784+
impl Spanned for AlterTextSearch {
5785+
fn span(&self) -> Span {
5786+
Span::empty()
5787+
}
5788+
}
5789+
56355790
/// CREATE POLICY statement.
56365791
///
56375792
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createpolicy.html)

0 commit comments

Comments
 (0)