Skip to content

Commit ebf5aa8

Browse files
committed
Fix: check ambiguous column reference
1 parent 6590ea3 commit ebf5aa8

File tree

4 files changed

+77
-12
lines changed

4 files changed

+77
-12
lines changed

datafusion/common/src/dfschema.rs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -419,10 +419,7 @@ impl DFSchema {
419419
name: &str,
420420
) -> Result<(Option<&TableReference>, &Field)> {
421421
if let Some(qualifier) = qualifier {
422-
let idx = self
423-
.index_of_column_by_name(Some(qualifier), name)
424-
.ok_or_else(|| field_not_found(Some(qualifier.clone()), name, self))?;
425-
Ok((self.field_qualifiers[idx].as_ref(), self.field(idx)))
422+
self.qualified_field_with_qualified_name(qualifier, name)
426423
} else {
427424
self.qualified_field_with_unqualified_name(name)
428425
}
@@ -467,6 +464,36 @@ impl DFSchema {
467464
.collect()
468465
}
469466

467+
/// Find all fields that match the given name with qualifier and return them with their qualifier
468+
pub fn qualified_fields_with_qualified_name(
469+
&self,
470+
qualifier: &TableReference,
471+
name: &str,
472+
) -> Vec<(Option<&TableReference>, &Field)> {
473+
self.iter()
474+
.filter(|(q, f)| match (qualifier, q) {
475+
// field to lookup is qualified.
476+
// current field is qualified and not shared between relations, compare both
477+
// qualifier and name.
478+
(q, Some(field_q)) => q.resolved_eq(field_q) && f.name() == name,
479+
// field to lookup is qualified but current field is unqualified.
480+
(qq, None) => {
481+
// the original field may now be aliased with a name that matches the
482+
// original qualified name
483+
let column = Column::from_qualified_name(f.name());
484+
match column {
485+
Column {
486+
relation: Some(r),
487+
name: column_name,
488+
} => &r == qq && column_name == name,
489+
_ => false,
490+
}
491+
}
492+
})
493+
.map(|(qualifier, field)| (qualifier, field.as_ref()))
494+
.collect()
495+
}
496+
470497
/// Find all fields that match the given name and convert to column
471498
pub fn columns_with_unqualified_name(&self, name: &str) -> Vec<Column> {
472499
self.iter()
@@ -519,6 +546,25 @@ impl DFSchema {
519546
}
520547
}
521548

549+
/// Find the qualified field with the given qualified name
550+
pub fn qualified_field_with_qualified_name(
551+
&self,
552+
qualifier: &TableReference,
553+
name: &str,
554+
) -> Result<(Option<&TableReference>, &Field)> {
555+
let matches = self.qualified_fields_with_qualified_name(qualifier, name);
556+
match matches.len() {
557+
0 => Err(field_not_found(Some(qualifier.clone()), name, self)),
558+
1 => Ok((matches[0].0, (matches[0].1))),
559+
_ => _schema_err!(SchemaError::AmbiguousReference {
560+
field: Column {
561+
relation: None,
562+
name: name.to_string(),
563+
},
564+
}),
565+
}
566+
}
567+
522568
/// Find the field with the given name
523569
pub fn field_with_unqualified_name(&self, name: &str) -> Result<&Field> {
524570
self.qualified_field_with_unqualified_name(name)

datafusion/core/src/datasource/physical_plan/csv.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,15 +1329,15 @@ mod tests {
13291329

13301330
let df = ctx.sql(r#"select * from t1"#).await?.collect().await?;
13311331
let expected = [
1332-
"+-------+-----------------------------+",
1333-
"| col1 | col2 |",
1334-
"+-------+-----------------------------+",
1335-
"| 1 | hello\rworld |",
1336-
"| 2 | something\relse |",
1332+
"+-------+------------------------+",
1333+
"| col1 | col2 |",
1334+
"+-------+------------------------+",
1335+
"| 1 | hello\rworld |",
1336+
"| 2 | something\relse |",
13371337
"| 3 | \rmany\rlines\rmake\rgood test\r |",
1338-
"| 4 | unquoted |",
1339-
"| value | end |",
1340-
"+-------+-----------------------------+",
1338+
"| 4 | unquoted |",
1339+
"| value | end |",
1340+
"+-------+------------------------+",
13411341
];
13421342
crate::assert_batches_eq!(expected, &df);
13431343
Ok(())

datafusion/sql/src/expr/identifier.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,11 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> {
186186
let s = &ids[0..ids.len()];
187187
// safe unwrap as s can never be empty or exceed the bounds
188188
let (relation, column_name) = form_identifier(s).unwrap();
189+
// sanity check on column
190+
schema.qualified_field_with_name(
191+
relation.as_ref(),
192+
column_name,
193+
)?;
189194
Ok(Expr::Column(Column::new(relation, column_name)))
190195
}
191196
}

datafusion/sqllogictest/test_files/join.slt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,3 +1209,17 @@ drop table t1;
12091209

12101210
statement ok
12111211
drop table t2;
1212+
1213+
# Test SQLancer issue: https://github.com/apache/datafusion/issues/12337
1214+
statement ok
1215+
create table t1(v1 int);
1216+
1217+
## Query with Ambiguous column reference
1218+
query error DataFusion error: Schema error: Ambiguous reference to unqualified field v1
1219+
select count(*)
1220+
from t1
1221+
right outer join t1
1222+
on t1.v1 > 0;
1223+
1224+
statement ok
1225+
drop table t1;

0 commit comments

Comments
 (0)