Initial SFERA platform baseline
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "query-parser"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
@@ -0,0 +1,143 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ParsedQueryText {
|
||||
pub tables: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn parse_query_text(query_text: &str) -> ParsedQueryText {
|
||||
ParsedQueryText {
|
||||
tables: extract_tables(query_text),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_tables(query_text: &str) -> Vec<String> {
|
||||
let mut tables = Vec::new();
|
||||
let lines: Vec<String> = query_text.lines().map(clean_query_line).collect();
|
||||
|
||||
for (index, line) in lines.iter().enumerate() {
|
||||
if let Some(table) = table_after_from(line) {
|
||||
push_unique(&mut tables, table);
|
||||
} else if is_standalone_from(line) {
|
||||
if let Some(next_line) = lines.get(index + 1) {
|
||||
if let Some(table) = first_table_token(next_line) {
|
||||
push_unique(&mut tables, table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(table) = table_after_join(line) {
|
||||
push_unique(&mut tables, table);
|
||||
}
|
||||
}
|
||||
|
||||
tables
|
||||
}
|
||||
|
||||
fn clean_query_line(line: &str) -> String {
|
||||
let mut value = line
|
||||
.trim()
|
||||
.trim_end_matches(';')
|
||||
.trim()
|
||||
.trim_matches('"')
|
||||
.trim();
|
||||
if let Some(stripped) = value.strip_prefix('|') {
|
||||
value = stripped.trim();
|
||||
}
|
||||
value.trim_matches('"').trim().to_string()
|
||||
}
|
||||
|
||||
fn is_standalone_from(line: &str) -> bool {
|
||||
line.eq_ignore_ascii_case("ИЗ") || line.eq_ignore_ascii_case("FROM")
|
||||
}
|
||||
|
||||
fn table_after_from(line: &str) -> Option<String> {
|
||||
let trimmed = line.trim();
|
||||
let upper = trimmed.to_uppercase();
|
||||
let rest = if upper.starts_with("ИЗ ") {
|
||||
&trimmed["ИЗ".len()..]
|
||||
} else if upper.starts_with("FROM ") {
|
||||
&trimmed["FROM".len()..]
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
first_table_token(rest)
|
||||
}
|
||||
|
||||
fn table_after_join(line: &str) -> Option<String> {
|
||||
let normalized = line.replace('\t', " ");
|
||||
let parts: Vec<&str> = normalized.split_whitespace().collect();
|
||||
let index = parts.iter().position(|part| {
|
||||
part.eq_ignore_ascii_case("JOIN") || part.eq_ignore_ascii_case("СОЕДИНЕНИЕ")
|
||||
})?;
|
||||
let table = parts.get(index + 1)?.trim_matches(',');
|
||||
if table.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(table.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn first_table_token(line: &str) -> Option<String> {
|
||||
let table = line.trim().split_whitespace().next()?.trim_matches(',');
|
||||
if table.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(table.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn push_unique(values: &mut Vec<String>, value: String) {
|
||||
if !values.iter().any(|existing| existing == &value) {
|
||||
values.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{extract_tables, parse_query_text};
|
||||
|
||||
#[test]
|
||||
fn extracts_table_after_standalone_from() {
|
||||
let tables = extract_tables(
|
||||
r#"
|
||||
ВЫБРАТЬ
|
||||
Остатки.Номенклатура
|
||||
ИЗ
|
||||
РегистрНакопления.ОстаткиТоваров КАК Остатки
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_eq!(tables, vec!["РегистрНакопления.ОстаткиТоваров"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extracts_inline_from_and_join_tables() {
|
||||
let query = parse_query_text(
|
||||
r#"
|
||||
SELECT
|
||||
Orders.Ref,
|
||||
Customers.Description
|
||||
FROM Document.CustomerOrder AS Orders
|
||||
LEFT JOIN Catalog.Customers AS Customers
|
||||
ON Orders.Customer = Customers.Ref
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
query.tables,
|
||||
vec!["Document.CustomerOrder", "Catalog.Customers"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cleans_pipe_prefixed_1c_query_lines() {
|
||||
let tables = extract_tables(
|
||||
r#"
|
||||
|ВЫБРАТЬ
|
||||
|Контрагенты.Ссылка
|
||||
|ИЗ Справочник.Контрагенты КАК Контрагенты
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_eq!(tables, vec!["Справочник.Контрагенты"]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user