Skip to content

Commit d169608

Browse files
committed
nom-sql: Parse escaped quotes in table column comments
We now handle doubled quotation marks, both single and double (MySQL only). We add several tests of the various combinations of these things, including of table comments, which were already working. Release-Note-Core: Correctly handle escaped quotes in table column comments. Fixes: REA-4446 Change-Id: I40d56e5b01880a182db1cc73b2e7e6fd6ff0ebfd Reviewed-on: https://gerrit.readyset.name/c/readyset/+/7626 Tested-by: Buildkite CI Reviewed-by: Marcelo Altmann <[email protected]>
1 parent 3701759 commit d169608

File tree

3 files changed

+56
-20
lines changed

3 files changed

+56
-20
lines changed

nom-sql/src/column.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,24 +292,29 @@ pub fn column_specification(
292292
dialect: Dialect,
293293
) -> impl Fn(LocatedSpan<&[u8]>) -> NomSqlResult<&[u8], ColumnSpecification> {
294294
move |i| {
295-
let (remaining_input, (column, field_type, constraints, comment)) = tuple((
295+
let (i, (column, field_type, constraints)) = tuple((
296296
column_identifier_no_alias(dialect),
297297
opt(delimited(
298298
whitespace1,
299299
type_identifier(dialect),
300300
whitespace0,
301301
)),
302302
many0(column_constraint(dialect)),
303-
opt(parse_comment),
304303
))(i)?;
305304

305+
let (i, comment) = if matches!(dialect, Dialect::MySQL) {
306+
opt(parse_comment(dialect))(i)?
307+
} else {
308+
(i, None)
309+
};
310+
306311
let sql_type = match field_type {
307312
None => SqlType::Text,
308313
Some(ref t) => t.clone(),
309314
};
310315

311316
Ok((
312-
remaining_input,
317+
i,
313318
ColumnSpecification {
314319
column,
315320
sql_type,

nom-sql/src/common.rs

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::str::FromStr;
66

77
use itertools::Itertools;
88
use nom::branch::alt;
9-
use nom::bytes::complete::{tag, tag_no_case, take_until};
9+
use nom::bytes::complete::{tag, tag_no_case};
1010
use nom::character::complete::{digit1, line_ending};
1111
use nom::combinator::{map, map_res, not, opt, peek};
1212
use nom::error::{ErrorKind, ParseError};
@@ -810,17 +810,18 @@ pub(crate) fn if_not_exists(i: LocatedSpan<&[u8]>) -> NomSqlResult<&[u8], bool>
810810
}
811811

812812
// Parse rule for a comment part.
813-
pub fn parse_comment(i: LocatedSpan<&[u8]>) -> NomSqlResult<&[u8], String> {
814-
map(
815-
preceded(
816-
delimited(whitespace0, tag_no_case("comment"), whitespace1),
817-
map_res(
818-
delimited(tag("'"), take_until("'"), tag("'")),
819-
|i: LocatedSpan<&[u8]>| str::from_utf8(&i),
813+
pub fn parse_comment(
814+
dialect: Dialect,
815+
) -> impl Fn(LocatedSpan<&[u8]>) -> NomSqlResult<&[u8], String> {
816+
move |i| {
817+
map(
818+
preceded(
819+
delimited(whitespace0, tag_no_case("comment"), whitespace1),
820+
map_res(move |i| dialect.string_literal()(i), String::from_utf8),
820821
),
821-
),
822-
String::from,
823-
)(i)
822+
String::from,
823+
)(i)
824+
}
824825
}
825826

826827
pub fn field_reference(
@@ -980,12 +981,6 @@ mod tests {
980981
);
981982
}
982983

983-
#[test]
984-
fn comment_data() {
985-
let res = parse_comment(LocatedSpan::new(b" COMMENT 'test'"));
986-
assert_eq!(res.unwrap().1, "test");
987-
}
988-
989984
#[test]
990985
fn terminated_by_semicolon() {
991986
let res = to_nom_result(statement_terminator(LocatedSpan::new(b" ; ")));
@@ -1204,6 +1199,22 @@ mod tests {
12041199
}
12051200
);
12061201
}
1202+
1203+
#[test]
1204+
fn table_column_comment() {
1205+
let res = test_parse!(parse_comment(Dialect::MySQL), b"comment 'foo'");
1206+
assert_eq!(res, "foo");
1207+
let res = test_parse!(parse_comment(Dialect::MySQL), b"comment \"foo\"");
1208+
assert_eq!(res, "foo");
1209+
let res = test_parse!(parse_comment(Dialect::MySQL), b"comment 'f''oo'");
1210+
assert_eq!(res, "f'oo");
1211+
let res = test_parse!(parse_comment(Dialect::MySQL), b"comment 'f\"\"oo'");
1212+
assert_eq!(res, "f\"\"oo");
1213+
let res = test_parse!(parse_comment(Dialect::MySQL), b"comment \"f\"\"oo\"");
1214+
assert_eq!(res, "f\"oo");
1215+
let res = test_parse!(parse_comment(Dialect::MySQL), b"comment \"f''oo\"");
1216+
assert_eq!(res, "f''oo");
1217+
}
12071218
}
12081219

12091220
mod postgres {

nom-sql/src/create_table_options.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,4 +421,24 @@ mod tests {
421421
vec![CreateTableOption::Comment("foobar".to_string())],
422422
);
423423
}
424+
425+
#[test]
426+
fn create_table_option_comment_escape() {
427+
should_parse_all(
428+
"COMMENT='foo''bar'",
429+
vec![CreateTableOption::Comment("foo'bar".to_string())],
430+
);
431+
should_parse_all(
432+
"COMMENT=\"foo\"\"bar\"",
433+
vec![CreateTableOption::Comment("foo\"bar".to_string())],
434+
);
435+
should_parse_all(
436+
"COMMENT='foo\"\"bar'",
437+
vec![CreateTableOption::Comment("foo\"\"bar".to_string())],
438+
);
439+
should_parse_all(
440+
"COMMENT=\"foo''bar\"",
441+
vec![CreateTableOption::Comment("foo''bar".to_string())],
442+
);
443+
}
424444
}

0 commit comments

Comments
 (0)