Skip to content

Commit c795c3a

Browse files
fix: use adapter for identifier quoting in SQL generation
- heading.as_sql() now accepts optional adapter parameter - Pass adapter from connection to all as_sql() calls in expression.py - Changed fallback from MySQL backticks to ANSI double quotes This ensures proper identifier quoting for PostgreSQL queries. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent aa78497 commit c795c3a

File tree

2 files changed

+12
-8
lines changed

2 files changed

+12
-8
lines changed

src/datajoint/expression.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def make_sql(self, fields=None):
153153
"""
154154
return "SELECT {distinct}{fields} FROM {from_}{where}{sorting}".format(
155155
distinct="DISTINCT " if self._distinct else "",
156-
fields=self.heading.as_sql(fields or self.heading.names),
156+
fields=self.heading.as_sql(fields or self.heading.names, adapter=self.connection.adapter),
157157
from_=self.from_clause(),
158158
where=self.where_clause(),
159159
sorting=self.sorting_clauses(),
@@ -881,17 +881,18 @@ def __len__(self):
881881
has_left_join = any(is_left for is_left, _ in result._joins)
882882

883883
# Build COUNT query - PostgreSQL requires different syntax for multi-column DISTINCT
884+
adapter = result.connection.adapter
884885
if has_left_join or len(result.primary_key) > 1:
885886
# Use subquery with DISTINCT for multi-column primary keys (backend-agnostic)
886-
fields = result.heading.as_sql(result.primary_key, include_aliases=False)
887+
fields = result.heading.as_sql(result.primary_key, include_aliases=False, adapter=adapter)
887888
query = (
888889
f"SELECT count(*) FROM ("
889890
f"SELECT DISTINCT {fields} FROM {result.from_clause()}{result.where_clause()}"
890891
f") AS distinct_count"
891892
)
892893
else:
893894
# Single column - can use count(DISTINCT col) directly
894-
fields = result.heading.as_sql(result.primary_key, include_aliases=False)
895+
fields = result.heading.as_sql(result.primary_key, include_aliases=False, adapter=adapter)
895896
query = f"SELECT count(DISTINCT {fields}) FROM {result.from_clause()}{result.where_clause()}"
896897

897898
return result.connection.query(query).fetchone()[0]
@@ -1018,7 +1019,7 @@ def where_clause(self):
10181019
return "" if not self._left_restrict else " WHERE (%s)" % ")AND(".join(str(s) for s in self._left_restrict)
10191020

10201021
def make_sql(self, fields=None):
1021-
fields = self.heading.as_sql(fields or self.heading.names)
1022+
fields = self.heading.as_sql(fields or self.heading.names, adapter=self.connection.adapter)
10221023
assert self._grouping_attributes or not self.restriction
10231024
distinct = set(self.heading.names) == set(self.primary_key)
10241025
return "SELECT {distinct}{fields} FROM {from_}{where}{group_by}{sorting}".format(

src/datajoint/heading.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ def as_dtype(self) -> np.dtype:
322322
"""
323323
return np.dtype(dict(names=self.names, formats=[v.dtype for v in self.attributes.values()]))
324324

325-
def as_sql(self, fields: list[str], include_aliases: bool = True) -> str:
325+
def as_sql(self, fields: list[str], include_aliases: bool = True, adapter=None) -> str:
326326
"""
327327
Generate SQL SELECT clause for specified fields.
328328
@@ -332,19 +332,22 @@ def as_sql(self, fields: list[str], include_aliases: bool = True) -> str:
332332
Attribute names to include.
333333
include_aliases : bool, optional
334334
Include AS clauses for computed attributes. Default True.
335+
adapter : DatabaseAdapter, optional
336+
Database adapter for identifier quoting. If not provided, attempts
337+
to get from table_info connection.
335338
336339
Returns
337340
-------
338341
str
339342
Comma-separated SQL field list.
340343
"""
341344
# Get adapter for proper identifier quoting
342-
adapter = None
343-
if self.table_info and "conn" in self.table_info and self.table_info["conn"]:
345+
if adapter is None and self.table_info and "conn" in self.table_info and self.table_info["conn"]:
344346
adapter = self.table_info["conn"].adapter
345347

346348
def quote(name):
347-
return adapter.quote_identifier(name) if adapter else f"`{name}`"
349+
# Use adapter if available, otherwise use ANSI SQL double quotes (not backticks)
350+
return adapter.quote_identifier(name) if adapter else f'"{name}"'
348351

349352
return ",".join(
350353
(

0 commit comments

Comments
 (0)