#32595 closed Bug (fixed)
mysql DatabaseSchemaEditor can't `quote_value` on byte strings
Reported by: | Ryan Siemens | Owned by: | Mariusz Felisiak |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 3.1 |
Severity: | Normal | Keywords: | |
Cc: | Ryan Siemens, Claude Paroz | Triage Stage: | Ready for checkin |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Using the the django.db.backends.mysql
backend with mysqlclient
fails to render the schema migration with sqlmigrate
when using a BinaryField
with a binary string default value.
You can recreate this with a simple app using the following model or see the attached sample project (MD5 = 2be52a23a4001e90728d3b0f5276e087):
class TestModel(models.Model): ok = models.BinaryField(default=b'some-blob') # add this after the initial migration # not_ok = models.BinaryField(default=b'some-other-blob')
Then perform the following steps:
- run initial
manage.py makemigrations
- uncomment the
not_ok = models.BinaryField(default=b'some-other-blob')
- run
manage.py makemigrations
again to generate the alter table migration - run
manage.py sqlmigrate <app> 0002
- assert the following
TypeError
is raised
Traceback (most recent call last): File "manage.py", line 22, in <module> main() File "manage.py", line 18, in main execute_from_command_line(sys.argv) File "/Users/rsiemens/sandbox/quote_value_bug/.venv/lib/python3.8/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line utility.execute() File "/Users/rsiemens/sandbox/quote_value_bug/.venv/lib/python3.8/site-packages/django/core/management/__init__.py", line 395, in execute self.fetch_command(subcommand).run_from_argv(self.argv) File "/Users/rsiemens/sandbox/quote_value_bug/.venv/lib/python3.8/site-packages/django/core/management/base.py", line 330, in run_from_argv self.execute(*args, **cmd_options) File "/Users/rsiemens/sandbox/quote_value_bug/.venv/lib/python3.8/site-packages/django/core/management/commands/sqlmigrate.py", line 29, in execute return super().execute(*args, **options) File "/Users/rsiemens/sandbox/quote_value_bug/.venv/lib/python3.8/site-packages/django/core/management/base.py", line 371, in execute output = self.handle(*args, **options) File "/Users/rsiemens/sandbox/quote_value_bug/.venv/lib/python3.8/site-packages/django/core/management/commands/sqlmigrate.py", line 65, in handle sql_statements = loader.collect_sql(plan) File "/Users/rsiemens/sandbox/quote_value_bug/.venv/lib/python3.8/site-packages/django/db/migrations/loader.py", line 345, in collect_sql state = migration.apply(state, schema_editor, collect_sql=True) File "/Users/rsiemens/sandbox/quote_value_bug/.venv/lib/python3.8/site-packages/django/db/migrations/migration.py", line 124, in apply operation.database_forwards(self.app_label, schema_editor, old_state, project_state) File "/Users/rsiemens/sandbox/quote_value_bug/.venv/lib/python3.8/site-packages/django/db/migrations/operations/fields.py", line 104, in database_forwards schema_editor.add_field( File "/Users/rsiemens/sandbox/quote_value_bug/.venv/lib/python3.8/site-packages/django/db/backends/mysql/schema.py", line 89, in add_field super().add_field(model, field) File "/Users/rsiemens/sandbox/quote_value_bug/.venv/lib/python3.8/site-packages/django/db/backends/base/schema.py", line 487, in add_field self.execute(sql, params) File "/Users/rsiemens/sandbox/quote_value_bug/.venv/lib/python3.8/site-packages/django/db/backends/base/schema.py", line 137, in execute self.collected_sql.append((sql % tuple(map(self.quote_value, params))) + ending) File "/Users/rsiemens/sandbox/quote_value_bug/.venv/lib/python3.8/site-packages/django/db/backends/mysql/schema.py", line 55, in quote_value quoted = self.connection.connection.escape(value, self.connection.connection.encoders) TypeError: bytes() argument 2 must be str, not dict
The error seems to be raised from the mysqlclient connection.escape
, but interestingly this works if you alter the self.connection.connection.encoders
right before escaping (https://github.com/django/django/blob/main/django/db/backends/mysql/schema.py#L57) by doing self.connection.connection.encoders.pop(bytes)
. Looking at the default converters mysqlclient sets up (https://github.com/PyMySQL/mysqlclient/blob/v2.0.1/MySQLdb/converters.py#L106-L139) I don't see a bytes
one being added, so I'm guessing django adds it somewhere along the way.
Using:
Python 3.8.5
MySQL 8.0.23
Django==3.1.7
mysqlclient==2.0.3
Attachments (1)
Change History (12)
by , 4 years ago
Attachment: | example.tar.gz added |
---|
comment:1 by , 4 years ago
Cc: | added |
---|
comment:2 by , 4 years ago
Resolution: | → invalid |
---|---|
Status: | new → closed |
Thanks for this ticket, however that's an issue in mysqlclient (see also mysqlclient#306). I'm not sure but maybe they should use _bytes_literal()
. Please create a new issue in mysqlclient
and feel-free to ping me on it.
comment:3 by , 4 years ago
Thanks, Mariusz. I went ahead and opened https://github.com/PyMySQL/mysqlclient/issues/489
comment:4 by , 4 years ago
Cc: | added |
---|---|
Resolution: | invalid |
Status: | closed → new |
Triage Stage: | Unreviewed → Accepted |
There seems to be no easy way to fix this in mysqlclient
at least for now, see [discussion](https://github.com/PyMySQL/mysqlclient/issues/489). I'm going to add a workaround in the MySQL backend.
comment:5 by , 4 years ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
comment:7 by , 4 years ago
Triage Stage: | Accepted → Ready for checkin |
---|
example app to recreate bug