Skip to content

Commit 8692c99

Browse files
feat: Use database adapters for SQL generation in table.py (Phase 5)
Update table.py to use adapter methods for backend-agnostic SQL generation: - Add adapter property to Table class for easy access - Update full_table_name to use adapter.quote_identifier() - Update UPDATE statement to quote column names via adapter - Update INSERT (query mode) to quote field list via adapter - Update INSERT (batch mode) to quote field list via adapter - DELETE statement now backend-agnostic (via full_table_name) Known limitations (to be fixed in Phase 6): - REPLACE command is MySQL-specific - ON DUPLICATE KEY UPDATE is MySQL-specific - PostgreSQL users cannot use replace=True or skip_duplicates=True yet All existing tests pass. Fully backward compatible with MySQL backend. Part of multi-backend PostgreSQL support implementation. Related: #1338
1 parent b76a099 commit 8692c99

File tree

1 file changed

+34
-23
lines changed

1 file changed

+34
-23
lines changed

src/datajoint/table.py

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,12 @@ def full_table_name(self):
401401
f"Class {self.__class__.__name__} is not associated with a schema. "
402402
"Apply a schema decorator or use schema() to bind it."
403403
)
404-
return r"`{0:s}`.`{1:s}`".format(self.database, self.table_name)
404+
return f"{self.adapter.quote_identifier(self.database)}.{self.adapter.quote_identifier(self.table_name)}"
405+
406+
@property
407+
def adapter(self):
408+
"""Database adapter for backend-agnostic SQL generation."""
409+
return self.connection.adapter
405410

406411
def update1(self, row):
407412
"""
@@ -438,9 +443,10 @@ def update1(self, row):
438443
raise DataJointError("Update can only be applied to one existing entry.")
439444
# UPDATE query
440445
row = [self.__make_placeholder(k, v) for k, v in row.items() if k not in self.primary_key]
446+
assignments = ",".join(f"{self.adapter.quote_identifier(r[0])}={r[1]}" for r in row)
441447
query = "UPDATE {table} SET {assignments} WHERE {where}".format(
442448
table=self.full_table_name,
443-
assignments=",".join("`%s`=%s" % r[:2] for r in row),
449+
assignments=assignments,
444450
where=make_condition(self, key, set()),
445451
)
446452
self.connection.query(query, args=list(r[2] for r in row if r[2] is not None))
@@ -694,17 +700,17 @@ def insert(
694700
except StopIteration:
695701
pass
696702
fields = list(name for name in rows.heading if name in self.heading)
697-
query = "{command} INTO {table} ({fields}) {select}{duplicate}".format(
698-
command="REPLACE" if replace else "INSERT",
699-
fields="`" + "`,`".join(fields) + "`",
700-
table=self.full_table_name,
701-
select=rows.make_sql(fields),
702-
duplicate=(
703-
" ON DUPLICATE KEY UPDATE `{pk}`={table}.`{pk}`".format(table=self.full_table_name, pk=self.primary_key[0])
704-
if skip_duplicates
705-
else ""
706-
),
707-
)
703+
quoted_fields = ",".join(self.adapter.quote_identifier(f) for f in fields)
704+
705+
# Duplicate handling (MySQL-specific for Phase 5)
706+
if skip_duplicates:
707+
quoted_pk = self.adapter.quote_identifier(self.primary_key[0])
708+
duplicate = f" ON DUPLICATE KEY UPDATE {quoted_pk}={self.full_table_name}.{quoted_pk}"
709+
else:
710+
duplicate = ""
711+
712+
command = "REPLACE" if replace else "INSERT"
713+
query = f"{command} INTO {self.full_table_name} ({quoted_fields}) {rows.make_sql(fields)}{duplicate}"
708714
self.connection.query(query)
709715
return
710716

@@ -736,16 +742,21 @@ def _insert_rows(self, rows, replace, skip_duplicates, ignore_extra_fields):
736742
if rows:
737743
try:
738744
# Handle empty field_list (all-defaults insert)
739-
fields_clause = f"(`{'`,`'.join(field_list)}`)" if field_list else "()"
740-
query = "{command} INTO {destination}{fields} VALUES {placeholders}{duplicate}".format(
741-
command="REPLACE" if replace else "INSERT",
742-
destination=self.from_clause(),
743-
fields=fields_clause,
744-
placeholders=",".join("(" + ",".join(row["placeholders"]) + ")" for row in rows),
745-
duplicate=(
746-
" ON DUPLICATE KEY UPDATE `{pk}`=`{pk}`".format(pk=self.primary_key[0]) if skip_duplicates else ""
747-
),
748-
)
745+
if field_list:
746+
fields_clause = f"({','.join(self.adapter.quote_identifier(f) for f in field_list)})"
747+
else:
748+
fields_clause = "()"
749+
750+
# Build duplicate clause (MySQL-specific for Phase 5)
751+
if skip_duplicates:
752+
quoted_pk = self.adapter.quote_identifier(self.primary_key[0])
753+
duplicate = f" ON DUPLICATE KEY UPDATE {quoted_pk}=VALUES({quoted_pk})"
754+
else:
755+
duplicate = ""
756+
757+
command = "REPLACE" if replace else "INSERT"
758+
placeholders = ",".join("(" + ",".join(row["placeholders"]) + ")" for row in rows)
759+
query = f"{command} INTO {self.from_clause()}{fields_clause} VALUES {placeholders}{duplicate}"
749760
self.connection.query(
750761
query,
751762
args=list(itertools.chain.from_iterable((v for v in r["values"] if v is not None) for r in rows)),

0 commit comments

Comments
 (0)