Execute SQL statement. For INSERT statements without an explicit RETURNING clause, we try to append ``RETURNING id`` so legacy callers can read ``cursor.lastrowid``. But not every table has an ``id`` column (e.g. ``qd_oauth_states`` uses ``state`` as PK). In that ca
(self, query: str, args: Any = None)
| 454 | return query |
| 455 | |
| 456 | def execute(self, query: str, args: Any = None): |
| 457 | """Execute SQL statement. |
| 458 | |
| 459 | For INSERT statements without an explicit RETURNING clause, we try to |
| 460 | append ``RETURNING id`` so legacy callers can read ``cursor.lastrowid``. |
| 461 | But not every table has an ``id`` column (e.g. ``qd_oauth_states`` |
| 462 | uses ``state`` as PK). In that case psycopg2 raises |
| 463 | ``UndefinedColumn`` and aborts the whole transaction, which can |
| 464 | cascade into "column \"id\" does not exist" errors across the app. |
| 465 | |
| 466 | To stay safe, wrap the RETURNING-id variant in a SAVEPOINT. If it |
| 467 | fails with UndefinedColumn, roll back to the savepoint and retry the |
| 468 | plain INSERT without RETURNING. The outer transaction is preserved. |
| 469 | """ |
| 470 | query = self._convert_placeholders(query) |
| 471 | if args is not None and not isinstance(args, (tuple, list)): |
| 472 | args = (args,) |
| 473 | |
| 474 | self._buffered_row = None |
| 475 | |
| 476 | is_insert = query.strip().upper().startswith('INSERT') |
| 477 | has_returning = 'RETURNING' in query.upper() |
| 478 | |
| 479 | if is_insert and not has_returning: |
| 480 | q_with_id = query.rstrip(';').rstrip() + ' RETURNING id' |
| 481 | savepoint = '_pg_ins_ret_id' |
| 482 | try: |
| 483 | # Constant savepoint name; avoid SQL formatting. |
| 484 | self._cursor.execute("SAVEPOINT _pg_ins_ret_id") |
| 485 | except Exception: |
| 486 | savepoint = None |
| 487 | |
| 488 | try: |
| 489 | if args: |
| 490 | result = self._cursor.execute(q_with_id, args) |
| 491 | else: |
| 492 | result = self._cursor.execute(q_with_id) |
| 493 | try: |
| 494 | row = self._cursor.fetchone() |
| 495 | if row and 'id' in row: |
| 496 | self._last_insert_id = row['id'] |
| 497 | except Exception: |
| 498 | pass |
| 499 | if savepoint: |
| 500 | try: |
| 501 | self._cursor.execute("RELEASE SAVEPOINT _pg_ins_ret_id") |
| 502 | except Exception: |
| 503 | pass |
| 504 | return result |
| 505 | except Exception as e: |
| 506 | # If the error is about missing id column, fall back. Other |
| 507 | # errors (unique violation, NOT NULL, FK, ...) must propagate. |
| 508 | msg = str(e).lower() |
| 509 | is_missing_id = ( |
| 510 | 'column "id" does not exist' in msg |
| 511 | or 'undefinedcolumn' in e.__class__.__name__.lower() |
| 512 | and '"id"' in msg |
| 513 | ) |