Skip to content

Commit 4f8a7f9

Browse files
authored
Merge branch 'julien-duponchelle:main' into main
2 parents 5141136 + b306dd7 commit 4f8a7f9

File tree

12 files changed

+105
-27
lines changed

12 files changed

+105
-27
lines changed

.github/workflows/release.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI
2+
3+
on: push
4+
5+
jobs:
6+
build-n-publish:
7+
name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI
8+
runs-on: ubuntu-latest
9+
steps:
10+
- uses: actions/checkout@v3
11+
- name: Set up Python
12+
uses: actions/setup-python@v4
13+
with:
14+
python-version: "3.x"
15+
- name: Install pypa/build
16+
run: >-
17+
python3 -m
18+
pip install
19+
build
20+
--user
21+
- name: Build a source tarball
22+
run: >-
23+
python3 -m
24+
build
25+
--sdist
26+
--outdir dist/
27+
- name: Publish distribution 📦 to PyPI
28+
if: startsWith(github.ref, 'refs/tags')
29+
uses: pypa/gh-action-pypi-publish@release/v1
30+
with:
31+
password: ${{ secrets.PYPI_API_TOKEN }}

.travis.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@ language: python
33
services:
44
- docker
55
python:
6-
- "2.7"
76
- "3.4"
8-
- "3.5"
9-
- "3.6"
7+
- "3.11"
108
- "pypy"
119
env:
1210
- DB=mysql57

CHANGELOG

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,15 @@
175175
* Handle null json
176176

177177
0.31 12/03/2023
178-
* Fix parse error for query_events with MariaDB
178+
* Fix parse error for query_events with MariaDB
179+
180+
0.40 07/05/2023
181+
* Drop support for Python 2.7
182+
* Gtid: remove __cmp__ due to python2 support dropped.
183+
* Mariadb 10.6.12: Mitigate corrupt binlog event bug
184+
185+
0.41 03/06/2023
186+
* Zero-pad fixed-length binary fields
187+
188+
0.42 25/06/2023
189+
* Add XAPrepareEvent, parse last_committed & sequence_number of GtidEvent

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ Project status
4444

4545
The project is test with:
4646
* MySQL 5.5, 5.6 and 5.7
47-
* Python >= 2.7
4847
* Python 3.3, 3.4, 3.5 and 3.6 (3.2 is not supported)
4948
* PyPy (really faster than the standard Python interpreter)
5049

@@ -332,14 +331,17 @@ Other contributors:
332331
* Dongwook Chan: Support for ZEROFILL, Correct timedelta value for negative MySQL TIME datatype, Fix parsing of row events for MySQL8 partitioned table, Parse status variables in query event, Parse status variables in query event , Fix parse errors with MariaDB (https://github.com/dongwook-chan)
333332
* Paul Vickers: Add support for specifying an end log_pos (https://github.com/paulvic)
334333
* Samira El Aabidi: Add support for MariaDB GTID (https://github.com/Samira-El)
335-
* Oliver Seemann: Handle large json, github actions (https://github.com/oseemann)
334+
* Oliver Seemann: Handle large json, github actions,
335+
Zero-pad fixed-length binary fields (https://github.com/oseemann)
336336
* Mahadir Ahmad: Handle null json payload (https://github.com/mahadirz)
337+
* Axel Viala: Removal of Python 2.7 (https://github.com/darnuria)
338+
* Etern: Add XAPrepareEvent, parse last_committed & sequence_number of GtidEvent (https://github.com/etern)
337339

338340
Thanks to GetResponse for their support
339341

340342
Licence
341343
=======
342-
Copyright 2012-2022 Julien Duponchelle
344+
Copyright 2012-2023 Julien Duponchelle
343345

344346
Licensed under the Apache License, Version 2.0 (the "License");
345347
you may not use this file except in compliance with the License.

docs/conf.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,16 @@
4141

4242
# General information about the project.
4343
project = u'Python MySQL Replication'
44-
copyright = u'2012-2021, Julien Duponchelle'
44+
copyright = u'2012-2023, Julien Duponchelle'
4545

4646
# The version info for the project you're documenting, acts as replacement for
4747
# |version| and |release|, also used in various other places throughout the
4848
# built documents.
4949
#
5050
# The short X.Y version.
51-
version = '0.31'
51+
version = '0.42'
5252
# The full version, including alpha/beta/rc tags.
53-
release = '0.31'
53+
release = '0.42'
5454

5555
# The language for content autogenerated by Sphinx. Refer to documentation
5656
# for a list of supported languages.

pymysqlreplication/binlogstream.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,14 @@ def __connect_to_stream(self):
319319
cur.execute("set @master_heartbeat_period= %d" % heartbeat)
320320
cur.close()
321321

322+
# When replicating from Mariadb 10.6.12 using binlog coordinates, a slave capability < 4 triggers a bug in
323+
# Mariadb, when it tries to replace GTID events with dummy ones. Given that this library understands GTID
324+
# events, setting the capability to 4 circumvents this error.
325+
# If the DB is mysql, this won't have any effect so no need to run this in a condition
326+
cur = self._stream_connection.cursor()
327+
cur.execute("SET @mariadb_slave_capability=4")
328+
cur.close()
329+
322330
self._register_slave()
323331

324332
if not self.auto_position:
@@ -352,8 +360,6 @@ def __connect_to_stream(self):
352360
if self.is_mariadb:
353361
# https://mariadb.com/kb/en/5-slave-registration/
354362
cur = self._stream_connection.cursor()
355-
356-
cur.execute("SET @mariadb_slave_capability=4")
357363
cur.execute("SET @slave_connect_state='%s'" % self.auto_position)
358364
cur.execute("SET @slave_gtid_strict_mode=1")
359365
cur.execute("SET @slave_gtid_ignore_duplicates=0")
@@ -610,7 +616,8 @@ def __get_table_information(self, schema, table):
610616
cur.execute("""
611617
SELECT
612618
COLUMN_NAME, COLLATION_NAME, CHARACTER_SET_NAME,
613-
COLUMN_COMMENT, COLUMN_TYPE, COLUMN_KEY, ORDINAL_POSITION
619+
COLUMN_COMMENT, COLUMN_TYPE, COLUMN_KEY, ORDINAL_POSITION,
620+
DATA_TYPE, CHARACTER_OCTET_LENGTH
614621
FROM
615622
information_schema.columns
616623
WHERE

pymysqlreplication/column.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ def __parse_column_definition(self, column_type, column_schema, packet):
2626
self.type_is_bool = False
2727
self.is_primary = column_schema["COLUMN_KEY"] == "PRI"
2828

29+
# Check for fixed-length binary type. When that's the case then we need
30+
# to zero-pad the values to full length at read time.
31+
self.fixed_binary_length = None
32+
if column_schema["DATA_TYPE"] == "binary":
33+
self.fixed_binary_length = column_schema["CHARACTER_OCTET_LENGTH"]
34+
2935
if self.type == FIELD_TYPE.VARCHAR:
3036
self.max_length = struct.unpack('<H', packet.read(2))[0]
3137
elif self.type == FIELD_TYPE.DOUBLE:

pymysqlreplication/gtid.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -281,11 +281,6 @@ def decode(cls, payload):
281281
else '%d' % x
282282
for x in intervals])))
283283

284-
def __cmp__(self, other):
285-
if other.sid != self.sid:
286-
return cmp(self.sid, other.sid)
287-
return cmp(self.intervals, other.intervals)
288-
289284
def __eq__(self, other):
290285
if other.sid != self.sid:
291286
return False

pymysqlreplication/row_event.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ def _read_column_data(self, cols_bitmap):
109109
name = self.table_map[self.table_id].columns[i].name
110110
unsigned = self.table_map[self.table_id].columns[i].unsigned
111111
zerofill = self.table_map[self.table_id].columns[i].zerofill
112+
fixed_binary_length = self.table_map[self.table_id].columns[i].fixed_binary_length
112113

113114
if BitGet(cols_bitmap, i) == 0:
114115
values[name] = None
@@ -154,6 +155,14 @@ def _read_column_data(self, cols_bitmap):
154155
values[name] = self.__read_string(2, column)
155156
else:
156157
values[name] = self.__read_string(1, column)
158+
159+
if fixed_binary_length and len(values[name]) < fixed_binary_length:
160+
# Fixed-length binary fields are stored in the binlog
161+
# without trailing zeros and must be padded with zeros up
162+
# to the specified length at read time.
163+
nr_pad = fixed_binary_length - len(values[name])
164+
values[name] += b'\x00' * nr_pad
165+
157166
elif column.type == FIELD_TYPE.NEWDECIMAL:
158167
values[name] = self.__read_new_decimal(column)
159168
elif column.type == FIELD_TYPE.BLOB:
@@ -640,6 +649,8 @@ def __init__(self, from_packet, event_size, table_map, ctl_connection, **kwargs)
640649
'COLUMN_NAME': '__dropped_col_{i}__'.format(i=i),
641650
'COLLATION_NAME': None,
642651
'CHARACTER_SET_NAME': None,
652+
'CHARACTER_OCTET_LENGTH': None,
653+
'DATA_TYPE': 'BLOB',
643654
'COLUMN_COMMENT': None,
644655
'COLUMN_TYPE': 'BLOB', # we don't know what it is, so let's not do anything with it.
645656
'COLUMN_KEY': '',

pymysqlreplication/tests/test_data_objects.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ def test_column_is_primary(self):
2222
{"COLUMN_NAME": "test",
2323
"COLLATION_NAME": "utf8_general_ci",
2424
"CHARACTER_SET_NAME": "UTF8",
25+
"CHARACTER_OCTET_LENGTH": None,
26+
"DATA_TYPE": "tinyint",
2527
"COLUMN_COMMENT": "",
2628
"COLUMN_TYPE": "tinyint(2)",
2729
"COLUMN_KEY": "PRI"},
@@ -33,6 +35,8 @@ def test_column_not_primary(self):
3335
{"COLUMN_NAME": "test",
3436
"COLLATION_NAME": "utf8_general_ci",
3537
"CHARACTER_SET_NAME": "UTF8",
38+
"CHARACTER_OCTET_LENGTH": None,
39+
"DATA_TYPE": "tinyint",
3640
"COLUMN_COMMENT": "",
3741
"COLUMN_TYPE": "tinyint(2)",
3842
"COLUMN_KEY": ""},
@@ -44,6 +48,8 @@ def test_column_serializable(self):
4448
{"COLUMN_NAME": "test",
4549
"COLLATION_NAME": "utf8_general_ci",
4650
"CHARACTER_SET_NAME": "UTF8",
51+
"CHARACTER_OCTET_LENGTH": None,
52+
"DATA_TYPE": "tinyint",
4753
"COLUMN_COMMENT": "",
4854
"COLUMN_TYPE": "tinyint(2)",
4955
"COLUMN_KEY": "PRI"},

pymysqlreplication/tests/test_data_type.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,18 @@ def create_and_get_tablemap_event(self, bit):
101101

102102
return event
103103

104+
def test_varbinary(self):
105+
create_query = "CREATE TABLE test(b VARBINARY(4))"
106+
insert_query = "INSERT INTO test VALUES(UNHEX('ff010000'))"
107+
event = self.create_and_insert_value(create_query, insert_query)
108+
self.assertEqual(event.rows[0]["values"]["b"], b'\xff\x01\x00\x00')
109+
110+
def test_fixed_length_binary(self):
111+
create_query = "CREATE TABLE test(b BINARY(4))"
112+
insert_query = "INSERT INTO test VALUES(UNHEX('ff010000'))"
113+
event = self.create_and_insert_value(create_query, insert_query)
114+
self.assertEqual(event.rows[0]["values"]["b"], b'\xff\x01\x00\x00')
115+
104116
def test_decimal(self):
105117
create_query = "CREATE TABLE test (test DECIMAL(2,1))"
106118
insert_query = "INSERT INTO test VALUES(4.2)"

setup.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,9 @@
66
except ImportError:
77
from distutils.core import setup, Command
88

9+
from pathlib import Path
910
import sys
1011

11-
tests_require = []
12-
13-
# add unittest2 to tests_require for python < 2.7
14-
if sys.version_info < (2, 7):
15-
tests_require.append("unittest2")
16-
1712

1813
class TestCommand(Command):
1914
user_options = []
@@ -34,21 +29,25 @@ def run(self):
3429
unittest.main(tests, argv=sys.argv[:1])
3530

3631

37-
version = "0.31"
32+
version = "0.42"
33+
34+
this_directory = Path(__file__).parent
35+
long_description = (this_directory / "README.md").read_text()
3836

3937
setup(
4038
name="mysql-replication",
4139
version=version,
42-
url="https://github.com/noplay/python-mysql-replication",
40+
url="https://github.com/julien-duponchelle/python-mysql-replication",
4341
author="Julien Duponchelle",
4442
author_email="[email protected]",
4543
description=("Pure Python Implementation of MySQL replication protocol "
4644
"build on top of PyMYSQL."),
45+
long_description=long_description,
46+
long_description_content_type='text/markdown',
4747
license="Apache 2",
4848
packages=["pymysqlreplication",
4949
"pymysqlreplication.constants",
5050
"pymysqlreplication.tests"],
5151
cmdclass={"test": TestCommand},
52-
extras_require={'test': tests_require},
5352
install_requires=['pymysql>=0.10'],
5453
)

0 commit comments

Comments
 (0)