diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d7f884b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,302 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "clap" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rockql-ast" +version = "0.1.0" +dependencies = [ + "serde", +] + +[[package]] +name = "rockql-cli" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "rockql-ast", + "rockql-parser", + "rockql-sql", + "serde_json", +] + +[[package]] +name = "rockql-parser" +version = "0.1.0" +dependencies = [ + "rockql-ast", + "serde", +] + +[[package]] +name = "rockql-sql" +version = "0.1.0" +dependencies = [ + "rockql-ast", + "rockql-parser", + "thiserror", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/compiler/rockql-sql/src/lib.rs b/compiler/rockql-sql/src/lib.rs index 84f436c..34a76b9 100644 --- a/compiler/rockql-sql/src/lib.rs +++ b/compiler/rockql-sql/src/lib.rs @@ -152,72 +152,34 @@ fn compile_sort_item(item: &SortItem, dialect: Dialect) -> String { } fn normalize_expression(expression: &str, _dialect: Dialect) -> String { - let without_numeric_separators = remove_numeric_separators(expression); - let operators = without_numeric_separators - .replace("!=", "<>") - .replace("==", "="); - normalize_keywords(&operators) -} - -fn remove_numeric_separators(value: &str) -> String { - // ⚡ Bolt Optimization: Avoid converting the entire string to Vec. - // Operate on bytes and use a fast-path skip since digits/underscores are 1-byte ASCII. - let bytes = value.as_bytes(); - - if !bytes.contains(&b'_') { - return value.to_string(); - } - - let mut output = String::with_capacity(value.len()); - let mut start = 0; - - for (i, &b) in bytes.iter().enumerate() { - if b == b'_' - && i > 0 - && i + 1 < bytes.len() - && bytes[i - 1].is_ascii_digit() - && bytes[i + 1].is_ascii_digit() - { - output.push_str(&value[start..i]); - start = i + 1; - } - } - output.push_str(&value[start..]); - - output -} - -fn normalize_keywords(value: &str) -> String { - // ⚡ Bolt Optimization: Avoid repeatedly allocating strings for words and `to_ascii_lowercase()`. - // We track start/end indices directly into the input `value` and use `eq_ignore_ascii_case`. - let mut output = String::with_capacity(value.len()); + // ⚡ Bolt Optimization: Single-pass expression normalization. + // Avoids 3 intermediate String allocations (`remove_numeric_separators`, + // two `.replace()` calls) and consolidates string scanning. + let mut output = String::with_capacity(expression.len()); let mut word_start = None; let mut quote = None; - let flush_word = |output: &mut String, start: usize, end: usize, value: &str| { - if start == end { - return; - } - - let word = &value[start..end]; + let flush_word = |output: &mut String, start: usize| { + let word = &output[start..]; if word.eq_ignore_ascii_case("true") { - output.push_str("TRUE"); + output.replace_range(start.., "TRUE"); } else if word.eq_ignore_ascii_case("false") { - output.push_str("FALSE"); + output.replace_range(start.., "FALSE"); } else if word.eq_ignore_ascii_case("null") { - output.push_str("NULL"); + output.replace_range(start.., "NULL"); } else if word.eq_ignore_ascii_case("and") { - output.push_str("AND"); + output.replace_range(start.., "AND"); } else if word.eq_ignore_ascii_case("or") { - output.push_str("OR"); + output.replace_range(start.., "OR"); } else if word.eq_ignore_ascii_case("not") { - output.push_str("NOT"); - } else { - output.push_str(word); + output.replace_range(start.., "NOT"); } }; - for (i, character) in value.char_indices() { + let bytes = expression.as_bytes(); + let mut chars = expression.char_indices().peekable(); + + while let Some((i, character)) = chars.next() { if let Some(active_quote) = quote { output.push(character); if character == active_quote { @@ -228,27 +190,57 @@ fn normalize_keywords(value: &str) -> String { if character == '\'' || character == '"' { if let Some(start) = word_start { - flush_word(&mut output, start, i, value); + flush_word(&mut output, start); word_start = None; } output.push(character); quote = Some(character); } else if character.is_ascii_alphanumeric() || character == '_' { + // Handle numeric separators + if character == '_' + && i > 0 + && i + 1 < bytes.len() + && bytes[i - 1].is_ascii_digit() + && bytes[i + 1].is_ascii_digit() + { + continue; // Skip the underscore + } + if word_start.is_none() { - word_start = Some(i); + word_start = Some(output.len()); } + output.push(character); } else { if let Some(start) = word_start { - flush_word(&mut output, start, i, value); + flush_word(&mut output, start); word_start = None; } - output.push(character); + + // Handle operators != and == + if character == '!' { + if let Some(&(_, '=')) = chars.peek() { + output.push_str("<>"); + chars.next(); // Consume '=' + } else { + output.push(character); + } + } else if character == '=' { + if let Some(&(_, '=')) = chars.peek() { + output.push('='); + chars.next(); // Consume second '=' + } else { + output.push(character); + } + } else { + output.push(character); + } } } if let Some(start) = word_start { - flush_word(&mut output, start, value.len(), value); + flush_word(&mut output, start); } + output }