Improve diagnostics of invalid executemany() input by elprans · Pull Request #848 · MagicStack/asyncpg · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion asyncpg/protocol/prepared_stmt.pxd
36 changes: 31 additions & 5 deletions asyncpg/protocol/prepared_stmt.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,25 @@ cdef class PreparedStatementState:
def mark_closed(self):
self.closed = True

cdef _encode_bind_msg(self, args):
cdef _encode_bind_msg(self, args, int seqno = -1):
cdef:
int idx
WriteBuffer writer
Codec codec

if not cpython.PySequence_Check(args):
if seqno >= 0:
raise exceptions.DataError(
f'invalid input in executemany() argument sequence '
f'element #{seqno}: expected a sequence, got '
f'{type(args).__name__}'
)
else:
# Non executemany() callers do not pass user input directly,
# so bad input is a bug.
raise exceptions.InternalClientError(
f'Bind: expected a sequence, got {type(args).__name__}')

if len(args) > 32767:
raise exceptions.InterfaceError(
'the number of query arguments cannot exceed 32767')
Expand Down Expand Up @@ -159,19 +172,32 @@ cdef class PreparedStatementState:
except exceptions.InterfaceError as e:
# This is already a descriptive error, but annotate
# with argument name for clarity.
pos = f'${idx + 1}'
if seqno >= 0:
pos = (
f'{pos} in element #{seqno} of'
f' executemany() sequence'
)
raise e.with_msg(
f'query argument ${idx + 1}: {e.args[0]}') from None
f'query argument {pos}: {e.args[0]}'
) from None
except Exception as e:
# Everything else is assumed to be an encoding error
# due to invalid input.
pos = f'${idx + 1}'
if seqno >= 0:
pos = (
f'{pos} in element #{seqno} of'
f' executemany() sequence'
)
value_repr = repr(arg)
if len(value_repr) > 40:
value_repr = value_repr[:40] + '...'

raise exceptions.DataError(
'invalid input for query argument'
' ${n}: {v} ({msg})'.format(
n=idx + 1, v=value_repr, msg=e)) from e
f'invalid input for query argument'
f' {pos}: {value_repr} ({e})'
) from e

if self.have_text_cols:
writer.write_int16(self.cols_num)
Expand Down
2 changes: 1 addition & 1 deletion asyncpg/protocol/protocol.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ cdef class BaseProtocol(CoreProtocol):
# Make sure the argument sequence is encoded lazily with
# this generator expression to keep the memory pressure under
# control.
data_gen = (state._encode_bind_msg(b) for b in args)
data_gen = (state._encode_bind_msg(b, i) for i, b in enumerate(args))
arg_bufs = iter(data_gen)

waiter = self._new_waiter(timeout)
Expand Down
29 changes: 24 additions & 5 deletions tests/test_execute.py