Skip to content

Commit 13f5cd5

Browse files
committed
BUG#37774513: Inconsistent conversion to_sql for cext vs pure python
This patch fixes the inconsistency of data type conversion support between pure-python and c-extension based connector. Change-Id: I829a2871e6dc2ebe782be74cbe01fd60c8c2be5b
1 parent 9b86b29 commit 13f5cd5

File tree

7 files changed

+458
-118
lines changed

7 files changed

+458
-118
lines changed

CHANGES.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ v9.4.0
1515
- WL#16963: Update the OpenTelemetry version
1616
- WL#16962: Update the Python Protobuf version
1717
- BUG#37868219: RPM packages have incorrect copyright year in their metadata
18-
- BUG#37820231: Text based django ORM filters doesn't work with Connector/Python
1918
- BUG#37859771: mysql/connector python version 9.3.0 has a regression which cannot persist binary data with percent signs in it
19+
- BUG#37820231: Text based django ORM filters doesn't work with Connector/Python
2020
- BUG#37806057: Rename extra option (when installing wheel package) to install webauthn functionality dependencies
21+
- BUG#37774513: Inconsistent conversion to_sql for cext vs pure python
2122
- BUG#37642447: The license type is missing from RPM package
2223
- BUG#37627508: mysql/connector python fetchmany() has an off by one bug when argument given as 1
2324
- BUG#37047789: Python connector does not support Django enum

mysql-connector-python/lib/mysql/connector/constants.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
import warnings
3232

3333
from abc import ABC, ABCMeta
34+
from datetime import date, datetime, time, timedelta
35+
from decimal import Decimal
36+
from time import struct_time
3437
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union, ValuesView
3538

3639
from .charsets import MYSQL_CHARACTER_SETS, MYSQL_CHARACTER_SETS_57
@@ -284,6 +287,26 @@
284287
"TLSv1.3": TLSV1_3_CIPHER_SUITES.values(),
285288
}
286289

290+
NATIVE_SUPPORTED_CONVERSION_TYPES = {
291+
int: "int",
292+
float: "float",
293+
str: "str",
294+
bytes: "bytes",
295+
bytearray: "bytearray",
296+
bool: "bool",
297+
type(None): "nonetype",
298+
datetime: "datetime",
299+
date: "date",
300+
time: "time",
301+
struct_time: "struct_time",
302+
timedelta: "timedelta",
303+
Decimal: "decimal",
304+
}
305+
"""Dictionary of the supported data types of the default MySQLConverter class
306+
and the corresponding tag names used to map against their respective methods
307+
'_{tag_name}_to_mysql(...)'. These converter methods are then used to convert
308+
the matched data type to mysql recognizable type."""
309+
287310

288311
def flag_is_set(flag: int, flags: int) -> bool:
289312
"""Checks if the flag is set

mysql-connector-python/lib/mysql/connector/conversion.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040

4141
from .constants import (
4242
MYSQL_VECTOR_TYPE_CODE,
43+
NATIVE_SUPPORTED_CONVERSION_TYPES,
4344
CharacterSet,
4445
FieldFlag,
4546
FieldType,
@@ -236,13 +237,26 @@ def to_mysql(self, value: MySQLConvertibleType) -> MySQLProducedType:
236237
"""Convert Python data type to MySQL"""
237238
if isinstance(value, Enum):
238239
value = value.value
239-
type_name = value.__class__.__name__.lower()
240+
# check if type of value object matches any one of the native supported conversion types
241+
# most of the types will match the condition below
242+
type_name: str = NATIVE_SUPPORTED_CONVERSION_TYPES.get(type(value), "")
243+
if not type_name:
244+
# check if the value object inherits from one of the native supported conversion types
245+
type_name = next(
246+
(
247+
name
248+
for data_type, name in NATIVE_SUPPORTED_CONVERSION_TYPES.items()
249+
if isinstance(value, data_type)
250+
),
251+
value.__class__.__name__.lower(),
252+
)
240253
try:
241254
converted: MySQLProducedType = getattr(self, f"_{type_name}_to_mysql")(
242255
value
243256
)
244257
return converted
245258
except AttributeError:
259+
# Value type is not a native one, nor a subclass of a native one
246260
if self.str_fallback:
247261
return str(value).encode()
248262
raise TypeError(

mysql-connector-python/src/include/mysql_capi_conversion.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2014, 2024, Oracle and/or its affiliates.
2+
* Copyright (c) 2014, 2025, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -33,6 +33,9 @@
3333

3434
#include <Python.h>
3535

36+
int
37+
is_decimal_instance(PyObject *obj);
38+
3639
PyObject *
3740
pytomy_date(PyObject *obj);
3841

mysql-connector-python/src/mysql_capi.c

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2014, 2024, Oracle and/or its affiliates.
2+
* Copyright (c) 2014, 2025, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -2010,16 +2010,16 @@ MySQL_convert_to_mysql(MySQL *self, PyObject *args)
20102010
// datetime is handled first
20112011
new_value = pytomy_datetime(value);
20122012
}
2013-
else if (PyDate_CheckExact(value)) {
2013+
else if (PyDate_Check(value)) {
20142014
new_value = pytomy_date(value);
20152015
}
20162016
else if (PyTime_Check(value)) {
20172017
new_value = pytomy_time(value);
20182018
}
2019-
else if (PyDelta_CheckExact(value)) {
2019+
else if (PyDelta_Check(value)) {
20202020
new_value = pytomy_timedelta(value);
20212021
}
2022-
else if (strcmp((value)->ob_type->tp_name, "decimal.Decimal") == 0) {
2022+
else if (is_decimal_instance(value)) {
20232023
new_value = pytomy_decimal(value);
20242024
}
20252025
else if (self->converter_str_fallback == Py_True) {
@@ -2194,7 +2194,7 @@ MySQL_query(MySQL *self, PyObject *args, PyObject *kwds)
21942194
continue;
21952195
}
21962196
/* DATE */
2197-
else if (PyDate_CheckExact(value)) {
2197+
else if (PyDate_Check(value)) {
21982198
MYSQL_TIME *date = &pbind->buffer.t;
21992199
date->year = PyDateTime_GET_YEAR(value);
22002200
date->month = PyDateTime_GET_MONTH(value);
@@ -2225,7 +2225,7 @@ MySQL_query(MySQL *self, PyObject *args, PyObject *kwds)
22252225
continue;
22262226
}
22272227
/* datetime.timedelta is TIME */
2228-
else if (PyDelta_CheckExact(value)) {
2228+
else if (PyDelta_Check(value)) {
22292229
MYSQL_TIME *time = &pbind->buffer.t;
22302230
time->hour = PyDateTime_TIME_GET_HOUR(value);
22312231
time->minute = PyDateTime_TIME_GET_MINUTE(value);
@@ -2244,7 +2244,7 @@ MySQL_query(MySQL *self, PyObject *args, PyObject *kwds)
22442244
continue;
22452245
}
22462246
/* DECIMAL */
2247-
else if (strcmp((value)->ob_type->tp_name, "decimal.Decimal") == 0) {
2247+
else if (is_decimal_instance(value)) {
22482248
pbind->str_value = pytomy_decimal(value);
22492249
mbind[i].buffer_type = MYSQL_TYPE_DECIMAL;
22502250
}
@@ -3383,7 +3383,7 @@ MySQLPrepStmt_execute(MySQLPrepStmt *self, PyObject *args, PyObject *kwds)
33833383
continue;
33843384
}
33853385
/* DATE */
3386-
else if (PyDate_CheckExact(value)) {
3386+
else if (PyDate_Check(value)) {
33873387
MYSQL_TIME *date = &pbind->buffer.t;
33883388
date->year = PyDateTime_GET_YEAR(value);
33893389
date->month = PyDateTime_GET_MONTH(value);
@@ -3414,7 +3414,7 @@ MySQLPrepStmt_execute(MySQLPrepStmt *self, PyObject *args, PyObject *kwds)
34143414
continue;
34153415
}
34163416
/* datetime.timedelta is TIME */
3417-
else if (PyDelta_CheckExact(value)) {
3417+
else if (PyDelta_Check(value)) {
34183418
MYSQL_TIME *time = &pbind->buffer.t;
34193419
time->hour = PyDateTime_TIME_GET_HOUR(value);
34203420
time->minute = PyDateTime_TIME_GET_MINUTE(value);
@@ -3433,7 +3433,7 @@ MySQLPrepStmt_execute(MySQLPrepStmt *self, PyObject *args, PyObject *kwds)
34333433
continue;
34343434
}
34353435
/* DECIMAL */
3436-
else if (strcmp((value)->ob_type->tp_name, "decimal.Decimal") == 0) {
3436+
else if (is_decimal_instance(value)) {
34373437
pbind->str_value = pytomy_decimal(value);
34383438
mbind[i].buffer_type = MYSQL_TYPE_DECIMAL;
34393439
}

mysql-connector-python/src/mysql_capi_conversion.c

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2014, 2024, Oracle and/or its affiliates.
2+
* Copyright (c) 2014, 2025, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -44,6 +44,34 @@
4444
#define MINYEAR 1
4545
#define MAXYEAR 9999
4646

47+
48+
// Decimal module needs to be loaded at runtime
49+
static PyObject *decimal_class = NULL;
50+
51+
/**
52+
Check whether the type of object is a decimal.
53+
54+
Check whether the object passed via parameter is an instance of decimal class.
55+
56+
@param obj the PyObject to be checked
57+
58+
@return 1 if obj is an instance of decimal class, 0 otherwise.
59+
@retval 1 Instance of the decimal class
60+
@retval 0 Not an instance of the decimal class
61+
*/
62+
int is_decimal_instance(PyObject *obj)
63+
{
64+
if (!decimal_class) {
65+
PyObject *decimal_module = PyImport_ImportModule("decimal");
66+
if (decimal_module) {
67+
decimal_class = PyObject_GetAttrString(decimal_module, "Decimal");
68+
}
69+
}
70+
return (decimal_class)
71+
? PyObject_IsInstance(obj, decimal_class)
72+
: strcmp((obj)->ob_type->tp_name, "decimal.Decimal") == 0;
73+
}
74+
4775
/**
4876
Check whether a year is a leap year.
4977

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy