Skip to content

Commit 2a88f6b

Browse files
committed
Merge branch 'release/8.0.26'
2 parents 11af087 + c4729e8 commit 2a88f6b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+15416
-94
lines changed

CHANGES.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,19 @@ Full release notes:
1111
v8.0.26
1212
=======
1313

14+
- WL#14634: Running unit tests against external server
1415
- WL#14542: Deprecate TLS 1.0 and 1.1
1516
- WL#14440: Support for authentication_kerberos_client authentication plugin
17+
- WL#14306: Integrate QA tests into Connector/Python test suite
1618
- WL#14237: Support query attributes
19+
- BUG#32947160: Remove MySQLdb module dependency from Django backend
20+
- BUG#32838010: Fix option files parsing with include directive
21+
- BUG#32789076: MSI is missing required libraries to use auth_ldap_client plugin
1722
- BUG#32778827: Raise an error if the _id is different when replacing a document
1823
- BUG#32740486: Fix typo in docstring
1924
- BUG#32623479: The X DevAPI returns str for binary types values
2025
- BUG#32585611: Fix broken links in X DevAPI reference documentation search
26+
- BUG#31528783: Fix number with ZEROFILL not handled by C extension
2127

2228
v8.0.25
2329
=======

cpydist/bdist_solaris.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,8 @@ def run(self):
311311
sun_root = os.path.join(build_base, "sun_pure")
312312
cmd_install = self.reinitialize_command("install",
313313
reinit_subcommands=1)
314-
cmd_install.compile = False
314+
cmd_install.byte_code_only = self.byte_code_only
315+
cmd_install.compile = self.byte_code_only
315316
cmd_install.distribution.metadata.name = metadata_name
316317
cmd_install.with_mysql_capi = None
317318
cmd_install.root = os.path.join(sun_root, self.dstroot)

examples/inserts.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
33

4-
# Copyright (c) 2009, 2017, Oracle and/or its affiliates. All rights reserved.
4+
# Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved.
55
#
66
# This program is free software; you can redistribute it and/or modify
77
# it under the terms of the GNU General Public License, version 2.0, as
@@ -53,7 +53,7 @@ def main(config):
5353
"CREATE TABLE names ("
5454
" id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, "
5555
" name VARCHAR(30) DEFAULT '' NOT NULL, "
56-
" info TEXT DEFAULT '', "
56+
" info TEXT, "
5757
" age TINYINT UNSIGNED DEFAULT '30', "
5858
"PRIMARY KEY (id))"
5959
)

examples/multi_resultsets.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
33

4-
# Copyright (c) 2009, 2017, Oracle and/or its affiliates. All rights reserved.
4+
# Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved.
55
#
66
# This program is free software; you can redistribute it and/or modify
77
# it under the terms of the GNU General Public License, version 2.0, as
@@ -52,7 +52,7 @@ def main(config):
5252
"CREATE TABLE names ("
5353
" id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, "
5454
" name VARCHAR(30) DEFAULT '' NOT NULL, "
55-
" info TEXT DEFAULT '', "
55+
" info TEXT, "
5656
" age TINYINT UNSIGNED DEFAULT '30', "
5757
" PRIMARY KEY (id))"
5858
)

lib/mysql/connector/django/base.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
from django.core.exceptions import ImproperlyConfigured
4949
from django.db import IntegrityError
5050
from django.db.backends.base.base import BaseDatabaseWrapper
51-
from django.db.backends.mysql.base import DatabaseWrapper as MySQLDatabaseWrapper
5251
from django.db import utils
5352
from django.utils.functional import cached_property
5453
from django.utils import dateparse, timezone
@@ -175,7 +174,7 @@ def __iter__(self):
175174
return iter(self.cursor)
176175

177176

178-
class DatabaseWrapper(MySQLDatabaseWrapper):
177+
class DatabaseWrapper(BaseDatabaseWrapper):
179178
vendor = 'mysql'
180179
# This dictionary maps Field objects to their associated MySQL column
181180
# types, as strings. Column-type strings can contain format strings; they'll
@@ -456,6 +455,30 @@ def data_type_check_constraints(self):
456455
return check_constraints
457456
return {}
458457

458+
@cached_property
459+
def mysql_server_data(self):
460+
with self.temporary_connection() as cursor:
461+
# Select some server variables and test if the time zone
462+
# definitions are installed. CONVERT_TZ returns NULL if 'UTC'
463+
# timezone isn't loaded into the mysql.time_zone table.
464+
cursor.execute("""
465+
SELECT VERSION(),
466+
@@sql_mode,
467+
@@default_storage_engine,
468+
@@sql_auto_is_null,
469+
@@lower_case_table_names,
470+
CONVERT_TZ('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL
471+
""")
472+
row = cursor.fetchone()
473+
return {
474+
'version': row[0],
475+
'sql_mode': row[1],
476+
'default_storage_engine': row[2],
477+
'sql_auto_is_null': bool(row[3]),
478+
'lower_case_table_names': bool(row[4]),
479+
'has_zoneinfo_database': bool(row[5]),
480+
}
481+
459482
@cached_property
460483
def mysql_server_info(self):
461484
with self.temporary_connection() as cursor:
@@ -479,7 +502,6 @@ def sql_mode(self):
479502
@property
480503
def use_pure(self):
481504
return self._use_pure
482-
# return not HAVE_CEXT or self._use_pure
483505

484506

485507
class DjangoMySQLConverter(MySQLConverter):

lib/mysql/connector/django/introspection.py

Lines changed: 99 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@
3636
from django.db.backends.base.introspection import (
3737
BaseDatabaseIntrospection, FieldInfo as BaseFieldInfo, TableInfo,
3838
)
39-
from django.db.backends.mysql.introspection import (
40-
DatabaseIntrospection as MySQLDatabaseIntrospection,
41-
)
4239
from django.db.models import Index
4340
from django.utils.datastructures import OrderedSet
4441

@@ -60,7 +57,57 @@
6057
)
6158

6259

63-
class DatabaseIntrospection(MySQLDatabaseIntrospection):
60+
class DatabaseIntrospection(BaseDatabaseIntrospection):
61+
62+
data_types_reverse = {
63+
FieldType.BLOB: 'TextField',
64+
FieldType.DECIMAL: 'DecimalField',
65+
FieldType.NEWDECIMAL: 'DecimalField',
66+
FieldType.DATE: 'DateField',
67+
FieldType.DATETIME: 'DateTimeField',
68+
FieldType.DOUBLE: 'FloatField',
69+
FieldType.FLOAT: 'FloatField',
70+
FieldType.INT24: 'IntegerField',
71+
FieldType.LONG: 'IntegerField',
72+
FieldType.LONGLONG: 'BigIntegerField',
73+
FieldType.SHORT: 'SmallIntegerField',
74+
FieldType.STRING: 'CharField',
75+
FieldType.TIME: 'TimeField',
76+
FieldType.TIMESTAMP: 'DateTimeField',
77+
FieldType.TINY: 'IntegerField',
78+
FieldType.TINY_BLOB: 'TextField',
79+
FieldType.MEDIUM_BLOB: 'TextField',
80+
FieldType.LONG_BLOB: 'TextField',
81+
FieldType.VAR_STRING: 'CharField',
82+
}
83+
84+
def get_field_type(self, data_type, description):
85+
field_type = super().get_field_type(data_type, description)
86+
if 'auto_increment' in description.extra:
87+
if field_type == 'IntegerField':
88+
return 'AutoField'
89+
elif field_type == 'BigIntegerField':
90+
return 'BigAutoField'
91+
elif field_type == 'SmallIntegerField':
92+
return 'SmallAutoField'
93+
if description.is_unsigned:
94+
if field_type == 'BigIntegerField':
95+
return 'PositiveBigIntegerField'
96+
elif field_type == 'IntegerField':
97+
return 'PositiveIntegerField'
98+
elif field_type == 'SmallIntegerField':
99+
return 'PositiveSmallIntegerField'
100+
# JSON data type is an alias for LONGTEXT in MariaDB, use check
101+
# constraints clauses to introspect JSONField.
102+
if description.has_json_constraint:
103+
return 'JSONField'
104+
return field_type
105+
106+
def get_table_list(self, cursor):
107+
"""Return a list of table and view names in the current database."""
108+
cursor.execute("SHOW FULL TABLES")
109+
return [TableInfo(row[0], {'BASE TABLE': 't', 'VIEW': 'v'}.get(row[1]))
110+
for row in cursor.fetchall()]
64111

65112
def get_table_description(self, cursor, table_name):
66113
"""
@@ -181,6 +228,54 @@ def get_primary_key_column(self, cursor, table_name):
181228
return column[0]
182229
return None
183230

231+
def get_sequences(self, cursor, table_name, table_fields=()):
232+
for field_info in self.get_table_description(cursor, table_name):
233+
if 'auto_increment' in field_info.extra:
234+
# MySQL allows only one auto-increment column per table.
235+
return [{'table': table_name, 'column': field_info.name}]
236+
return []
237+
238+
def get_relations(self, cursor, table_name):
239+
"""
240+
Return a dictionary of {field_name: (field_name_other_table, other_table)}
241+
representing all relationships to the given table.
242+
"""
243+
constraints = self.get_key_columns(cursor, table_name)
244+
relations = {}
245+
for my_fieldname, other_table, other_field in constraints:
246+
relations[my_fieldname] = (other_field, other_table)
247+
return relations
248+
249+
def get_key_columns(self, cursor, table_name):
250+
"""
251+
Return a list of (column_name, referenced_table_name, referenced_column_name)
252+
for all key columns in the given table.
253+
"""
254+
key_columns = []
255+
cursor.execute("""
256+
SELECT column_name, referenced_table_name, referenced_column_name
257+
FROM information_schema.key_column_usage
258+
WHERE table_name = %s
259+
AND table_schema = DATABASE()
260+
AND referenced_table_name IS NOT NULL
261+
AND referenced_column_name IS NOT NULL""", [table_name])
262+
key_columns.extend(cursor.fetchall())
263+
return key_columns
264+
265+
def get_storage_engine(self, cursor, table_name):
266+
"""
267+
Retrieve the storage engine for a given table. Return the default
268+
storage engine if the table doesn't exist.
269+
"""
270+
cursor.execute(
271+
"SELECT engine "
272+
"FROM information_schema.tables "
273+
"WHERE table_name = %s", [table_name])
274+
result = cursor.fetchone()
275+
if not result:
276+
return self.connection.features._mysql_storage_engine
277+
return result[0]
278+
184279
def get_constraints(self, cursor, table_name):
185280
"""
186281
Retrieve any constraints or keys (unique, pk, fk, check, index) across

lib/mysql/connector/optionfiles.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2014, 2020, Oracle and/or its affiliates.
1+
# Copyright (c) 2014, 2021, Oracle and/or its affiliates.
22
#
33
# This program is free software; you can redistribute it and/or modify
44
# it under the terms of the GNU General Public License, version 2.0, as
@@ -166,12 +166,14 @@ def _parse_options(self, files):
166166
Raises ValueError if any of the included or file given in arguments
167167
is not readable.
168168
"""
169+
initial_files = files[:]
170+
files = []
169171
index = 0
170172
err_msg = "Option file '{0}' being included again in file '{1}'"
171173

172-
for file_ in files:
174+
for file_ in initial_files:
173175
try:
174-
if file_ in files[index+1:]:
176+
if file_ in initial_files[index+1:]:
175177
raise ValueError("Same option file '{0}' occurring more "
176178
"than once in the list".format(file_))
177179
with open(file_, 'r') as op_file:
@@ -186,18 +188,18 @@ def _parse_options(self, files):
186188
entry, file_))
187189
if (os.path.isfile(entry) and
188190
entry.endswith(self.default_extension)):
189-
files.insert(index+1, entry)
191+
files.append(entry)
190192

191193
elif line.startswith('!include'):
192194
_, filename = line.split(None, 1)
193195
filename = filename.strip()
194196
if filename in files:
195197
raise ValueError(err_msg.format(
196198
filename, file_))
197-
files.insert(index+1, filename)
199+
files.append(filename)
198200

199201
index += 1
200-
202+
files.append(file_)
201203
except (IOError, OSError) as exc:
202204
raise ValueError("Failed reading file '{0}': {1}".format(
203205
file_, str(exc)))
@@ -224,6 +226,10 @@ def read(self, filenames): # pylint: disable=W0221
224226
out_file = io.StringIO()
225227
for line in codecs.open(filename, encoding='utf-8'):
226228
line = line.strip()
229+
# Skip lines that begin with "!includedir" or "!include"
230+
if line.startswith('!include'):
231+
continue
232+
227233
match_obj = self.OPTCRE.match(line)
228234
if not self.SECTCRE.match(line) and match_obj:
229235
optname, delimiter, optval = match_obj.group('option',

src/mysql_capi.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2669,7 +2669,13 @@ MySQL_fetch_row(MySQL *self)
26692669
field_type == MYSQL_TYPE_LONGLONG ||
26702670
field_type == MYSQL_TYPE_INT24 ||
26712671
field_type == MYSQL_TYPE_YEAR) {
2672-
PyTuple_SET_ITEM(result_row, i, PyLong_FromString(row[i], NULL, 0));
2672+
if (field_flags && ZEROFILL_FLAG) {
2673+
PyTuple_SET_ITEM(result_row, i, PyLong_FromString(row[i], NULL, 10));
2674+
}
2675+
else
2676+
{
2677+
PyTuple_SET_ITEM(result_row, i, PyLong_FromString(row[i], NULL, 0));
2678+
}
26732679
}
26742680
else if (field_type == MYSQL_TYPE_DATETIME ||
26752681
field_type == MYSQL_TYPE_TIMESTAMP)

0 commit comments

Comments
 (0)