diff --git a/sbroad-cartridge/test_app/test/integration/acl_test.lua b/sbroad-cartridge/test_app/test/integration/acl_test.lua index d88e37bf84674ecf18b1efa481a404efa0a8b03d..ddb295919bce358c2e89630dbdbe5518030626c5 100644 --- a/sbroad-cartridge/test_app/test/integration/acl_test.lua +++ b/sbroad-cartridge/test_app/test/integration/acl_test.lua @@ -22,7 +22,7 @@ g.test_drop_user = function() local _, err = api:call( "sbroad.execute", - { [[ DROP USER user ]], {} } + { [[ DROP USER user_name ]], {} } ) t.assert_equals( string.format("%s", err), diff --git a/sbroad-core/src/frontend/sql/ir/tests.rs b/sbroad-core/src/frontend/sql/ir/tests.rs index 0a5f4c4f33801220d51c8a1ab07b68d739610c93..28d4d481c3a9f8553de6978d976c85e7081f2451 100644 --- a/sbroad-core/src/frontend/sql/ir/tests.rs +++ b/sbroad-core/src/frontend/sql/ir/tests.rs @@ -405,17 +405,16 @@ vtable_max_rows = 5000 assert_eq!(expected_explain, plan.as_explain().unwrap()); } -// Check double angle quotation marks in the strings #[test] -fn front_sql20() { +fn front_sql_check_arbitrary_utf_in_single_quote_strings() { let input = r#"SELECT "identification_number" FROM "hash_testing" - WHERE "product_code" = '«123»'"#; + WHERE "product_code" = '«123Ȥ#*&%@/// / // \\ ƵǖḘỺʥ Í‘ Í‘ ͕ΆΨѮښ ۞ܤ'"#; let plan = sql_to_optimized_ir(input, vec![]); let expected_explain = String::from( r#"projection ("hash_testing"."identification_number"::integer -> "identification_number") - selection ROW("hash_testing"."product_code"::string) = ROW('«123»'::string) + selection ROW("hash_testing"."product_code"::string) = ROW('«123Ȥ#*&%@/// / // \\ ƵǖḘỺʥ Í‘ Í‘ ͕ΆΨѮښ ۞ܤ'::string) scan "hash_testing" execution options: sql_vdbe_max_steps = 45000 @@ -426,6 +425,26 @@ vtable_max_rows = 5000 assert_eq!(expected_explain, plan.as_explain().unwrap()); } +#[test] +fn front_sql_check_arbitraty_utf_in_identifiers() { + let input = r#"SELECT "id" "from", "id" as "select", "id" + "123»*&%ÚšÛž@Ƶǖselect.""''\\" + , "id" aц1&@$//Ƶǖ%^&*«»§*&%ÚšÛž@Ƶǖ FROM "test_space" *&%ÚšÛž@Ƶǖ"#; + + let plan = sql_to_optimized_ir(input, vec![]); + + let expected_explain = String::from( + r#"projection ("*&%ÚšÛž@ƵǕ"."id"::unsigned -> "from", "*&%ÚšÛž@ƵǕ"."id"::unsigned -> "select", "*&%ÚšÛž@ƵǕ"."id"::unsigned -> "123»*&%ÚšÛž@Ƶǖselect.""''\\", "*&%ÚšÛž@ƵǕ"."id"::unsigned -> "AЦ1&@$//ƵǕ%^&*«»§*&%ÚšÛž@ƵǕ") + scan "test_space" -> "*&%ÚšÛž@ƵǕ" +execution options: +sql_vdbe_max_steps = 45000 +vtable_max_rows = 5000 +"#, + ); + + assert_eq!(expected_explain, plan.as_explain().unwrap()); +} + #[test] fn front_projection_with_scan_specification_under_scan() { let input = r#"SELECT "hash_testing".* FROM "hash_testing""#; @@ -522,7 +541,7 @@ vtable_max_rows = 5000 #[test] fn track_shard_col_pos() { let input = r#" - select "e", "bucket_id", "f" + select "e", "bucket_id", "f" from "t2" where "e" + "f" = 3 "#; @@ -618,7 +637,7 @@ fn track_shard_col_pos() { fn front_sql_join_on_bucket_id1() { let input = r#"select * from "t2" join ( select "bucket_id" from "test_space" where "id" = 1 - ) as t_mv + ) as t_mv on t_mv."bucket_id" = "t2"."bucket_id"; "#; @@ -647,7 +666,7 @@ vtable_max_rows = 5000 fn front_sql_join_on_bucket_id2() { let input = r#"select * from "t2" join ( select "bucket_id" from "test_space" where "id" = 1 - ) as t_mv + ) as t_mv on t_mv."bucket_id" = "t2"."bucket_id" or "t2"."e" = "t2"."f"; "#; @@ -676,7 +695,7 @@ vtable_max_rows = 5000 #[test] fn front_sql_groupby_on_bucket_id() { let input = r#" - select b, count(*) from (select "bucket_id" as b from "t2") as t + select b, count(*) from (select "bucket_id" as b from "t2") as t group by b "#; @@ -700,7 +719,7 @@ vtable_max_rows = 5000 #[test] fn front_sql_sq_on_bucket_id() { let input = r#" - select b, e from (select "bucket_id" as b, "e" as e from "t2") as t + select b, e from (select "bucket_id" as b, "e" as e from "t2") as t where (b, e) in (select "bucket_id", "id" from "test_space") "#; diff --git a/sbroad-core/src/frontend/sql/query.pest b/sbroad-core/src/frontend/sql/query.pest index 2c6856bb0b5f3fc292d971b127781d2c785d6884..41745a956775f887d7ea34e55bbd5aaad6f84240 100644 --- a/sbroad-core/src/frontend/sql/query.pest +++ b/sbroad-core/src/frontend/sql/query.pest @@ -148,15 +148,33 @@ Query = { (SelectWithOptionalContinuation | Values | Insert | Update | Delete) ~ Delete = { ^"delete" ~ ^"from" ~ ScanTable ~ (^"where" ~ DeleteFilter)? } DeleteFilter = { Expr } -// Note: it's atomic to silence inner rules. -Identifier = @{ DoubleQuotedIdentifier | IdentifierInner } - DoubleQuotedIdentifier = @{ ("\"" ~ IdentifierInner ~ "\"") } - IdentifierInner = @{ !(Keyword ~ ("(" | WHITESPACE | "," | EOF)) ~ (IdentifierNonDigit ~ (IdentifierNonDigit | ASCII_DIGIT)*) } - IdentifierNonDigit = _{ ('a'..'z' | 'A' .. 'Z' | 'Ð' .. 'Я' | 'а' .. 'Ñ' | "-" | "_") } - Keyword = { ^"left" | ^"having" | ^"not" | ^"inner" | ^"group" - | ^"on" | ^"join" | ^"from" | ^"exists" | ^"except" - | ^"union" | ^"where" | ^"distinct" | ^"between" | ^"option" - | ^"values"} +Identifier = @{ DelimitedIdentifier | RegularIdentifier } + DelimitedIdentifier = @{ ("\"" ~ ((!("\"") ~ ANY) | "\"\"")* ~ "\"") } + RegularIdentifier = @{ !KeywordCoverage ~ + RegularIdentifierFirstApplicableSymbol ~ + RegularIdentifierApplicableSymbol* ~ + &IdentifierInapplicableSymbol } + RegularIdentifierFirstApplicableSymbol = { !(IdentifierInapplicableSymbol | ASCII_DIGIT) ~ ANY } + RegularIdentifierApplicableSymbol = { !IdentifierInapplicableSymbol ~ ANY } + IdentifierInapplicableSymbol = { WHITESPACE | "." | "," | "(" | EOF | ")" | "\"" } + KeywordCoverage = { Keyword ~ IdentifierInapplicableSymbol } + // Note: In case two keywords with the same prefix are met, shorter ones must go after longest. + // E.g. ^"in" must go after ^"insert" because keywords traversal stops on the first match. + // Please, try to keep the list in alphabetical order. + Keyword = { ^"all" | ^"and" | ^"any" | ^"array" | ^"as" + | ^"begin" | ^"between" | ^"boolean" | ^"bool"| ^"by" + | ^"cast" | ^"char" + | ^"decimal" | ^"distinct" | ^"double" + | ^"end" | ^"except" | ^"exists" + | ^"false" | ^"from" | ^"group" + | ^"having" | ^"inner" | ^"integer" | ^"into" | ^"int" | ^"in" | ^"is" + | ^"join" | ^"left" | ^"not" | ^"null" | ^"number" + | ^"on" | ^"option" | ^"or" | ^"outer" | ^"primary" + | ^"scalar" | ^"select" | ^"set" | ^"string" + | ^"table" | ^"text" | ^"to" | ^"true" + | ^"union" | ^"unsigned" | ^"using" | ^"uuid" + | ^"values" | ^"varchar" | ^"where" | ^"with" + } Expr = { ExprAtomValue ~ (ExprInfixOp ~ ExprAtomValue)* } ExprInfixOp = _{ Between | ArithInfixOp | CmpInfixOp | ConcatInfixOp | And | Or } @@ -189,9 +207,7 @@ Expr = { ExprAtomValue ~ (ExprInfixOp ~ ExprAtomValue)* } Double = @{ Integer ~ ("." ~ ASCII_DIGIT*)? ~ (^"e" ~ Integer) } Integer = @{ ("+" | "-")? ~ ASCII_DIGIT+ } Unsigned = @{ ASCII_DIGIT+ } - SingleQuotedString = @{ OnlyQuotesSequence | AnythingButQuotesSequence } - OnlyQuotesSequence = @{ ("'" ~ "'")+ } - AnythingButQuotesSequence = @{ "'" ~ (!("'") ~ ANY)* ~ "'" } + SingleQuotedString = @{ "'" ~ ((!("'") ~ ANY) | "''")* ~ "'" } Parameter = { PgParameter | TntParameter } TntParameter = @{ "?" } PgParameter = { "$" ~ Unsigned }