Skip to content

ColumnExpression doesn't escape column name containing . #46

Closed

Description

@MarcoGorelli

What happens?

If a column name contains a '.' I can't select it with duckdb.ColumnExpression. Should ColumnExpression not do the escaping for us, so we can just pass the column name?

At least, that's what happens for column names that contains spaces

To Reproduce

import duckdb

rel = duckdb.sql("""select * from values (1, 2), (3, 4) df(a, "b.a")""")
rel.select(duckdb.ColumnExpression('b.a'))
---------------------------------------------------------------------------
BinderException                           Traceback (most recent call last)
Cell In[3], line 1
----> 1 rel.select(duckdb.ColumnExpression('b.a'))

BinderException: Binder Error: Referenced table "b" not found!
Candidate tables: "unnamed_relation_0ba05ea8514d33bd"

OS:

linux

DuckDB Package Version:

1.3.2

Python Version:

3.12

Full Name:

Marco Gorelli

Affiliation:

Quansight Labs

What is the latest build you tested with? If possible, we recommend testing with the latest nightly build.

I have tested with a nightly build

Did you include all relevant data sets for reproducing the issue?

No - Other reason (please specify in the issue body)

Did you include all code required to reproduce the issue?

  • Yes, I have

Did you include all relevant configuration to reproduce the issue?

  • Yes, I have

Activity

  1. Tishj commented on Sep 15, 2025

    @Tishj
    Member

    Ah.. that's kind of an oversight 🤔
    The original behavior was to parse the string for components, later we added args * overload where the components can be added separately, keeping the existing behavior for size==1 to be backwards compatible

    But that falls apart when your column reference has dots in the name..

    original behavior was this:

    shared_ptr<DuckDBPyExpression> DuckDBPyExpression::ColumnExpression(const string &column_name) {
    	if (column_name == "*") {
    		return StarExpression();
    	}
    
    	auto qualified_name = QualifiedName::Parse(column_name);
    	vector<string> column_names;
    	if (!qualified_name.catalog.empty()) {
    		column_names.push_back(qualified_name.catalog);
    	}
    	if (!qualified_name.schema.empty()) {
    		column_names.push_back(qualified_name.schema);
    	}
    	column_names.push_back(qualified_name.name);
    
    	return make_shared<DuckDBPyExpression>(make_uniq<duckdb::ColumnRefExpression>(std::move(column_names)));
    }

    Which also doesn't support this case

    One thing I can think of that would still be backwards compatible is adding a kwarg: *, parse_string: Optional[bool] = True

  2. Tishj commented on Sep 15, 2025

    @Tishj
    Member

    Though at that point, adding the quotes to your string is easier..

    import duckdb
    
    rel = duckdb.sql("""select * from values (1, 2), (3, 4) df(a, "b.a")""")
    rel.select(duckdb.ColumnExpression('"b.a"')).show()
    ┌───────┐
    │  b.a  │
    │ int32 │
    ├───────┤
    │     2 │
    │     4 │
    └───────┘
    
  3. evertlammerts commented on Oct 27, 2026

    @evertlammerts
    Member

    In duckdb 2.0.0 a string passed to ColumnExpression or duckdb.col is one column name, quoted whole and never split, so rel.select(duckdb.ColumnExpression('b.a')) now returns the b.a column. I verified that on 2.0.0 and on main. Two spellings changed meaning as a result: the pre-quoted '"b.a"' workaround above now looks for a column whose name includes the quotes, and a qualified column is written as ColumnExpression('t', 'x') instead of 't.x'. Closing this as fixed in 2.0.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions