Skip to content

Commit fae86c0

Browse files
Geert VanderkelenGeert Vanderkelen
Geert Vanderkelen
authored and
Geert Vanderkelen
committed
BUG19642249: Improve errors reporting invalid sharding keys
We improve the errors reported when invalid sharding keys are given for RANGE_STRING and RANGE_DATETIME types. We additionally improve performance by moving the sorting of partition keys when saving cache entries. Previously, sorting was done each time a partition was looked up. Unit test for BUG#19642249 was added in test_fabric module. (cherry picked from commit a95e9c6)
1 parent a1328a7 commit fae86c0

File tree

3 files changed

+112
-14
lines changed

3 files changed

+112
-14
lines changed

lib/mysql/connector/fabric/caching.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"""Implementing caching mechanisms for MySQL Fabric"""
2525

2626

27+
import bisect
2728
from datetime import datetime, timedelta
2829
from hashlib import sha1
2930
import logging
@@ -35,6 +36,24 @@
3536
_CACHE_TTL = 1 * 60 # 1 minute
3637

3738

39+
def insort_right_rev(a, x, lo=0, hi=None):
40+
"""Similar to bisect.insort_right but for reverse sorted lists
41+
42+
This code is similar to the Python code found in Lib/bisect.py.
43+
We simply change the comparison from 'less than' to 'greater than'.
44+
"""
45+
46+
if lo < 0:
47+
raise ValueError('lo must be non-negative')
48+
if hi is None:
49+
hi = len(a)
50+
while lo < hi:
51+
mid = (lo+hi)//2
52+
if x > a[mid]: hi = mid
53+
else: lo = mid+1
54+
a.insert(lo, x)
55+
56+
3857
class CacheEntry(object):
3958

4059
"""Base class for MySQL Fabric cache entries"""
@@ -83,6 +102,8 @@ def __init__(self, shard, version=None, fabric_uuid=None):
83102
fabric_uuid=fabric_uuid)
84103
self.partitioning = {}
85104
self._shard = shard
105+
self.keys = []
106+
self.keys_reversed = []
86107

87108
if shard.key and shard.group:
88109
self.add_partition(shard.key, shard.group)
@@ -115,6 +136,8 @@ def add_partition(self, key, group):
115136
'group': group,
116137
}
117138
self.reset_ttl()
139+
bisect.insort_right(self.keys, key)
140+
insort_right_rev(self.keys_reversed, key)
118141

119142
@classmethod
120143
def hash_index(cls, part1, part2=None):

lib/mysql/connector/fabric/connection.py

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -753,36 +753,43 @@ def get_shard_server(self, tables, key, scope=SCOPE_LOCAL, mode=None):
753753
return self.get_group_server(entry.global_group, mode=mode)
754754

755755
if entry.shard_type == 'RANGE':
756-
partitions = sorted(entry.partitioning.keys())
757-
index = partitions[bisect(partitions, int(key)) - 1]
756+
try:
757+
range_key = int(key)
758+
except ValueError:
759+
raise ValueError("Key must be an integer for RANGE")
760+
partitions = entry.keys
761+
index = partitions[bisect(partitions, range_key) - 1]
758762
partition = entry.partitioning[index]
759763
elif entry.shard_type == 'RANGE_DATETIME':
760764
if not isinstance(key, (datetime.date, datetime.datetime)):
761765
raise ValueError(
762766
"Key must be datetime.date or datetime.datetime for "
763767
"RANGE_DATETIME")
764-
partition_keys = sorted(entry.partitioning.keys(), reverse=True)
765-
for partkey in partition_keys:
768+
index = None
769+
for partkey in entry.keys_reversed:
766770
if key >= partkey:
767771
index = partkey
768772
break
769-
partition = entry.partitioning[index]
773+
try:
774+
partition = entry.partitioning[index]
775+
except KeyError:
776+
raise ValueError("Key invalid; was '{0}'".format(key))
770777
elif entry.shard_type == 'RANGE_STRING':
771778
if not isunicode(key):
772779
raise ValueError("Key must be a unicode value")
773-
partition_keys = sorted(entry.partitioning.keys(), reverse=True)
774-
for partkey in partition_keys:
775-
size = len(partkey)
776-
if key[0:size] >= partkey[0:size]:
780+
index = None
781+
for partkey in entry.keys_reversed:
782+
if key >= partkey:
777783
index = partkey
778784
break
779-
partition = entry.partitioning[index]
785+
try:
786+
partition = entry.partitioning[index]
787+
except KeyError:
788+
raise ValueError("Key invalid; was '{0}'".format(key))
780789
elif entry.shard_type == 'HASH':
781790
md5key = md5(str(key))
782-
partition_keys = sorted(
783-
entry.partitioning.keys(), reverse=True)
784-
index = partition_keys[-1]
785-
for partkey in partition_keys:
791+
index = entry.keys_reversed[-1]
792+
for partkey in entry.keys_reversed:
786793
if md5key.digest() >= b16decode(partkey):
787794
index = partkey
788795
break

tests/test_fabric.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,43 @@ def _truncate(self, cur, table):
424424
cur.execute("SELECT @@global.gtid_executed")
425425
return cur.fetchone()[0]
426426

427+
def test_range(self):
428+
self.assertTrue(self._check_table("employees.employees_range", 'RANGE'))
429+
tbl_name = "employees_range"
430+
431+
tables = ["employees.{0}".format(tbl_name)]
432+
433+
self.cnx.set_property(tables=tables,
434+
scope=fabric.SCOPE_GLOBAL,
435+
mode=fabric.MODE_READWRITE)
436+
cur = self.cnx.cursor()
437+
gtid_executed = self._truncate(cur, tbl_name)
438+
self.cnx.commit()
439+
440+
insert = ("INSERT INTO {0} "
441+
"VALUES (%s, %s, %s, %s, %s, %s)").format(tbl_name)
442+
443+
self._populate(self.cnx, gtid_executed, tbl_name, insert,
444+
self.emp_data[1985] + self.emp_data[2000], 0)
445+
446+
time.sleep(2)
447+
448+
# Year is key of self.emp_data, second value is emp_no for RANGE key
449+
exp_keys = [(1985, 10002), (2000, 47291)]
450+
for year, emp_no in exp_keys:
451+
self.cnx.set_property(tables=tables,
452+
scope=fabric.SCOPE_LOCAL,
453+
key=emp_no, mode=fabric.MODE_READONLY)
454+
cur = self.cnx.cursor()
455+
cur.execute("SELECT * FROM {0}".format(tbl_name))
456+
rows = cur.fetchall()
457+
self.assertEqual(rows, self.emp_data[year])
458+
459+
self.cnx.set_property(tables=tables,
460+
key='spam', mode=fabric.MODE_READONLY)
461+
self.assertRaises(ValueError, self.cnx.cursor)
462+
463+
427464
def test_range_datetime(self):
428465
self.assertTrue(self._check_table(
429466
"employees.employees_range_datetime", 'RANGE_DATETIME'))
@@ -510,3 +547,34 @@ def test_range_string(self):
510547
key='not unicode str',
511548
mode=fabric.MODE_READONLY)
512549
self.assertRaises(ValueError, self.cnx.cursor)
550+
551+
def test_bug19642249(self):
552+
self.assertTrue(self._check_table(
553+
"employees.employees_range_string", 'RANGE_STRING'))
554+
555+
# Invalid key for RANGE_STRING
556+
tbl_name = "employees_range_string"
557+
tables = ["employees.{0}".format(tbl_name)]
558+
559+
self.cnx.set_property(tables=tables,
560+
key=u'1', mode=fabric.MODE_READONLY)
561+
try:
562+
cur = self.cnx.cursor()
563+
except ValueError as exc:
564+
self.assertEqual("Key invalid; was '1'", str(exc))
565+
else:
566+
self.fail("ValueError not raised")
567+
568+
# Invalid key for RANGE_DATETIME
569+
tbl_name = "employees_range_datetime"
570+
tables = ["employees.{0}".format(tbl_name)]
571+
572+
self.cnx.set_property(tables=tables,
573+
key=datetime.date(1977, 1, 1),
574+
mode=fabric.MODE_READONLY)
575+
try:
576+
cur = self.cnx.cursor()
577+
except ValueError as exc:
578+
self.assertEqual("Key invalid; was '1977-01-01'", str(exc))
579+
else:
580+
self.fail("ValueError not raised")

0 commit comments

Comments
 (0)