Fix parsing of elided aliases in SQLite statements. Handle failure to parse views more gracefully.

This commit is contained in:
Sebastian Jeltsch
2025-07-15 10:19:59 +02:00
parent b371703f36
commit 1dbe73a8bd
4 changed files with 81 additions and 23 deletions

2
Cargo.lock generated
View File

@@ -6936,8 +6936,6 @@ dependencies = [
"maxminddb",
"mini-moka",
"parking_lot",
"rand 0.9.1",
"rand_core 0.9.3",
"regex",
"rusqlite",
"serde",

View File

@@ -275,11 +275,15 @@ pub async fn lookup_and_parse_all_view_schemas(
for row in rows.iter() {
let sql: String = row.get(0)?;
views.push({
let mut view: View = sqlite3_parse_view(&sql, tables)?;
view.name.database_schema = Some(db.name.clone());
view
});
match sqlite3_parse_view(&sql, tables) {
Ok(mut view) => {
view.name.database_schema = Some(db.name.clone());
views.push(view);
}
Err(err) => {
error!("Failed to parse VIEW definition '{sql}': {err}");
}
}
}
}

View File

@@ -13,15 +13,13 @@ crate-type=["cdylib", "rlib"]
[dependencies]
arc-swap = "1.7.1"
argon2 = { version = "^0.5.3", default-features = false, features = ["alloc", "password-hash", "rand"] }
argon2 = { version = "^0.5.3", default-features = false, features = ["alloc", "password-hash", "rand", "std"] }
base64 = { version = "0.22.1", default-features = false }
jsonschema = { version = "0.30.0", default-features = false }
log = "0.4.27"
maxminddb = "0.26.0"
mini-moka = "0.10.3"
parking_lot = { version = "0.12.3", default-features = false }
rand = "^0.9.0"
rand_core = "^0.9"
regex = "1.11.0"
rusqlite = { workspace = true }
serde = { version = "^1.0.203", features = ["derive"] }

View File

@@ -974,17 +974,15 @@ fn to_entry(
qn: AstQualifiedName,
alias: Option<sqlite3_parser::ast::As>,
) -> (String, QualifiedName) {
return (
alias
.and_then(|alias| {
if let sqlite3_parser::ast::As::As(name) = alias {
return Some(unquote_name(name));
}
None
})
.unwrap_or_else(|| qn.to_string()),
qn.into(),
);
let key = match alias {
// "FROM table_name AS alias"
Some(sqlite3_parser::ast::As::As(name)) => unquote_name(name),
// "FROM table_name alias"
Some(sqlite3_parser::ast::As::Elided(name)) => unquote_name(name),
_ => qn.to_string(),
};
return (key, qn.into());
}
#[derive(Clone, Debug)]
@@ -1151,11 +1149,11 @@ fn try_extract_column_mapping(
}
Expr::Qualified(qualifier, name) => {
let qualifier = unquote_name(qualifier);
let col_name = unquote_name(name);
let col_name = unquote_name(name.clone());
let Some(table_name) = table_names.get(&qualifier) else {
return Err(SchemaError::Precondition(
format!("Missing table with qualifier: {qualifier}").into(),
format!("Missing table: Qualified({qualifier}, {name})").into(),
));
};
@@ -1535,6 +1533,66 @@ mod tests {
#[test]
fn test_view_column_extraction() {
let tables = vec![Table {
name: QualifiedName {
name: "table_name".to_string(),
database_schema: None,
},
strict: true,
columns: vec![Column {
name: "column".to_string(),
data_type: ColumnDataType::Text,
options: vec![],
}],
foreign_keys: vec![],
unique: vec![],
checks: vec![],
virtual_table: false,
temporary: false,
}];
{
// No alias
let sql = "SELECT column FROM table_name";
let sqlite3_parser::ast::Stmt::Select(select) =
sqlite3_parse_into_statement(sql).unwrap().unwrap()
else {
panic!("Not a select");
};
let _mapping = try_extract_column_mapping(*select, &tables)
.unwrap()
.unwrap();
}
{
// With alias
let sql = "SELECT alias.column FROM table_name AS alias";
let sqlite3_parser::ast::Stmt::Select(select) =
sqlite3_parse_into_statement(sql).unwrap().unwrap()
else {
panic!("Not a select");
};
let _mapping = try_extract_column_mapping(*select, &tables)
.unwrap()
.unwrap();
}
{
// With "elided" alias
let sql = "SELECT alias.column FROM table_name alias";
let sqlite3_parser::ast::Stmt::Select(select) =
sqlite3_parse_into_statement(sql).unwrap().unwrap()
else {
panic!("Not a select");
};
let _mapping = try_extract_column_mapping(*select, &tables)
.unwrap()
.unwrap();
}
}
#[test]
fn test_view_column_extraction_join() {
let sql = "SELECT user, *, a.*, p.user AS foo FROM foo.articles AS a LEFT JOIN bar.profiles AS p ON p.user = a.author";
let sqlite3_parser::ast::Stmt::Select(select) =
sqlite3_parse_into_statement(sql).unwrap().unwrap()