Skip to content

Commit bd07788

Browse files
committed
Move partition definitions from tuples to explicit classes
1 parent 3576716 commit bd07788

File tree

3 files changed

+176
-43
lines changed

3 files changed

+176
-43
lines changed

partitionmanager/table_append_partition.py

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from partitionmanager.types import (
22
DuplicatePartitionException,
3+
MaxValuePartition,
34
MismatchedIdException,
5+
Partition,
6+
PositionPartition,
47
SqlInput,
58
TableInformationException,
69
UnexpectedPartitionException,
@@ -51,8 +54,12 @@ def get_current_positions(database, table_name, columns):
5154
columns_str = ", ".join([f"`{x}`" for x in columns])
5255
sql = f"SELECT {columns_str} FROM `{table_name}` ORDER BY {order_col} DESC LIMIT 1;"
5356
rows = database.run(sql)
54-
if len(rows) != 1:
55-
raise TableInformationException("Expected one result")
57+
if len(rows) > 1:
58+
raise TableInformationException(f"Expected one result from {table_name}")
59+
if len(rows) == 0:
60+
raise TableInformationException(
61+
f"Table {table_name} appears to be empty. (No results)"
62+
)
5663
ordered_positions = list()
5764
for c in columns:
5865
ordered_positions.append(rows[0][c])
@@ -71,9 +78,8 @@ def get_partition_map(database, table_name):
7178

7279
def parse_partition_map(rows):
7380
"""
74-
Read a partition statement from a table creation string and produce tuples
75-
for each partition with a max, and a single string for the partition using
76-
"maxvalue".
81+
Read a partition statement from a table creation string and produce Partition
82+
objets for each partition.
7783
"""
7884
partition_range = re.compile(r"[ ]*PARTITION BY RANGE \(([\w,` ]+)\)")
7985
partition_member = re.compile(
@@ -110,13 +116,17 @@ def parse_partition_map(rows):
110116
)
111117
raise MismatchedIdException("Partition columns mismatch")
112118

113-
partitions.append((part_name, part_vals))
119+
pos_part = PositionPartition(part_name)
120+
for v in part_vals:
121+
pos_part.add_position(v)
122+
123+
partitions.append(pos_part)
114124

115125
member_tail = partition_tail.match(l)
116126
if member_tail:
117127
part_name = member_tail.group(1)
118128
logging.debug(f"Found tail partition named {part_name}")
119-
partitions.append(part_name)
129+
partitions.append(MaxValuePartition(part_name, len(range_cols)))
120130

121131
return {"range_cols": range_cols, "partitions": partitions}
122132

@@ -130,31 +140,43 @@ def parition_name_now():
130140

131141
def reorganize_partition(partition_list, new_partition_name, partition_positions):
132142
"""
133-
From a partial partitions list (ending with a single value that indicates MAX VALUE),
134-
add a new partition at the partition_positions, which must be a list.
143+
From a partial partitions list of Partition types add a new partition at the
144+
partition_positions, which must be a list.
135145
"""
136146
if type(partition_positions) is not list:
137147
raise ValueError()
138148

139-
last_value = partition_list.pop()
140-
if type(last_value) is not str:
141-
raise UnexpectedPartitionException(last_value)
142-
if last_value == new_partition_name:
143-
raise DuplicatePartitionException(last_value)
144-
if type(partition_list[0][1]) is list:
145-
if len(partition_list[0][1]) != len(partition_positions):
146-
raise MismatchedIdException("Didn't get the same number of partition IDs")
147-
else:
148-
if len(partition_positions) != 1:
149-
raise MismatchedIdException("Expected only a single partition ID")
149+
num_partition_ids = partition_list[0].num_columns
150+
151+
tail_part = partition_list.pop()
152+
if not isinstance(tail_part, MaxValuePartition):
153+
raise UnexpectedPartitionException(tail_part)
154+
if tail_part.name == new_partition_name:
155+
raise DuplicatePartitionException(tail_part)
156+
157+
# Check any remaining partitions in the list after popping off the tail
158+
# to make sure each entry has the same number of partition IDs as the first
159+
# entry.
160+
for p in partition_list:
161+
if len(p.positions) != num_partition_ids:
162+
raise MismatchedIdException(
163+
"Didn't get the same number of partition IDs: "
164+
+ f"{p} has {len(p)} while expected {num_partition_ids}"
165+
)
166+
if len(partition_positions) != num_partition_ids:
167+
raise MismatchedIdException(
168+
f"Provided {len(partition_positions)} partition IDs,"
169+
+ f" but expected {num_partition_ids}"
170+
)
171+
172+
altered_partition = PositionPartition(tail_part.name)
173+
for p in partition_positions:
174+
altered_partition.add_position(p)
150175

151-
positions_str = ", ".join([str(x) for x in partition_positions])
152-
maxvalue_str = ", ".join(["MAXVALUE"] * len(partition_positions))
176+
new_partition = MaxValuePartition(new_partition_name, num_partition_ids)
153177

154-
reorganized_list = list()
155-
reorganized_list.append((last_value, f"({positions_str})"))
156-
reorganized_list.append((new_partition_name, maxvalue_str))
157-
return last_value, reorganized_list
178+
reorganized_list = [altered_partition, new_partition]
179+
return altered_partition.name, reorganized_list
158180

159181

160182
def format_sql_reorganize_partition_command(
@@ -166,9 +188,9 @@ def format_sql_reorganize_partition_command(
166188
"""
167189
partition_strings = list()
168190
for p in partition_list:
169-
if type(p) is not tuple:
191+
if not isinstance(p, Partition):
170192
raise UnexpectedPartitionException(p)
171-
partition_strings.append(f"PARTITION `{p[0]}` VALUES LESS THAN {p[1]}")
193+
partition_strings.append(f"PARTITION `{p.name}` VALUES LESS THAN {p.values()}")
172194
partition_update = ", ".join(partition_strings)
173195

174196
return (

partitionmanager/table_append_partition_test.py

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
from partitionmanager.types import (
66
DatabaseCommand,
77
DuplicatePartitionException,
8-
TableInformationException,
8+
MaxValuePartition,
99
MismatchedIdException,
10+
Partition,
11+
PositionPartition,
1012
SqlInput,
13+
TableInformationException,
1114
UnexpectedPartitionException,
1215
)
1316
from partitionmanager.table_append_partition import (
@@ -77,7 +80,7 @@ def test_single_partition(self):
7780
]
7881
results = parse_partition_map(create_stmt)
7982
self.assertEqual(len(results["partitions"]), 1)
80-
self.assertEqual(results["partitions"][0], "p_20201204")
83+
self.assertEqual(results["partitions"][0], mkTailPart("p_20201204"))
8184
self.assertEqual(results["range_cols"], ["id"])
8285

8386
def test_two_partitions(self):
@@ -96,8 +99,8 @@ def test_two_partitions(self):
9699
]
97100
results = parse_partition_map(create_stmt)
98101
self.assertEqual(len(results["partitions"]), 2)
99-
self.assertEqual(results["partitions"][0], ("before", [100]))
100-
self.assertEqual(results["partitions"][1], "p_20201204")
102+
self.assertEqual(results["partitions"][0], mkPPart("before", 100))
103+
self.assertEqual(results["partitions"][1], mkTailPart("p_20201204"))
101104
self.assertEqual(results["range_cols"], ["id"])
102105

103106
def test_dual_keys_single_partition(self):
@@ -115,7 +118,7 @@ def test_dual_keys_single_partition(self):
115118
]
116119
results = parse_partition_map(create_stmt)
117120
self.assertEqual(len(results["partitions"]), 1)
118-
self.assertEqual(results["partitions"][0], "p_start")
121+
self.assertEqual(results["partitions"][0], mkTailPart("p_start", count=2))
119122
self.assertEqual(results["range_cols"], ["firstID", "secondID"])
120123

121124
def test_dual_keys_multiple_partitions(self):
@@ -134,8 +137,8 @@ def test_dual_keys_multiple_partitions(self):
134137
]
135138
results = parse_partition_map(create_stmt)
136139
self.assertEqual(len(results["partitions"]), 2)
137-
self.assertEqual(results["partitions"][0], ("p_start", [255, 1234567890]))
138-
self.assertEqual(results["partitions"][1], "p_next")
140+
self.assertEqual(results["partitions"][0], mkPPart("p_start", 255, 1234567890))
141+
self.assertEqual(results["partitions"][1], mkTailPart("p_next", count=2))
139142
self.assertEqual(results["range_cols"], ["firstID", "secondID"])
140143

141144

@@ -153,35 +156,55 @@ def test_okay(self):
153156
SqlInput("zz-table")
154157

155158

159+
def mkPPart(name, *pos):
160+
p = PositionPartition(name)
161+
for x in pos:
162+
p.add_position(x)
163+
return p
164+
165+
166+
def mkTailPart(name, count=1):
167+
return MaxValuePartition(name, count)
168+
169+
156170
class TestReorganizePartitions(unittest.TestCase):
157171
def test_list_without_final_entry(self):
158172
with self.assertRaises(UnexpectedPartitionException):
159-
reorganize_partition([("a", 1), ("b", 2)], "new", [3])
173+
reorganize_partition([mkPPart("a", 1), mkPPart("b", 2)], "new", [3])
160174

161175
def test_reorganize_with_duplicate(self):
162176
with self.assertRaises(DuplicatePartitionException):
163-
reorganize_partition([("a", 1), "b"], "b", [3])
177+
reorganize_partition([mkPPart("a", 1), mkTailPart("b")], "b", [3])
178+
179+
def test_reorganize_single_partition(self):
180+
last_value, reorg_list = reorganize_partition([mkTailPart("a")], "b", [1])
181+
self.assertEqual(last_value, "a")
182+
self.assertEqual(reorg_list, [mkPPart("a", 1), mkTailPart("b")])
164183

165184
def test_reorganize(self):
166-
last_value, reorg_list = reorganize_partition([("a", 1), "b"], "c", [2])
185+
last_value, reorg_list = reorganize_partition(
186+
[mkPPart("a", 1), mkTailPart("b")], "c", [2]
187+
)
167188
self.assertEqual(last_value, "b")
168-
self.assertEqual(reorg_list, [("b", "(2)"), ("c", "MAXVALUE")])
189+
self.assertEqual(reorg_list, [mkPPart("b", 2), mkTailPart("c")])
169190

170191
def test_reorganize_too_many_partition_ids(self):
171192
with self.assertRaises(MismatchedIdException):
172-
reorganize_partition([("a", 1), "b"], "c", [2, 3, 4])
193+
reorganize_partition([mkPPart("a", 1), mkTailPart("b")], "c", [2, 3, 4])
173194

174195
def test_reorganize_too_few_partition_ids(self):
175196
with self.assertRaises(MismatchedIdException):
176-
reorganize_partition([("a", [1, 1, 1]), "b"], "c", [2, 3])
197+
reorganize_partition([mkPPart("a", 1, 1, 1), mkTailPart("b")], "c", [2, 3])
177198

178199
def test_reorganize_with_dual_keys(self):
179200
last_value, reorg_list = reorganize_partition(
180-
[("p_start", [255, 1234567890]), "p_next"], "new", [512, 2345678901]
201+
[mkPPart("p_start", 255, 1234567890), mkTailPart("p_next", count=2)],
202+
"new",
203+
[512, 2345678901],
181204
)
182205
self.assertEqual(last_value, "p_next")
183206
self.assertEqual(
184-
reorg_list, [("p_next", "(512, 2345678901)"), ("new", "MAXVALUE, MAXVALUE")]
207+
reorg_list, [mkPPart("p_next", 512, 2345678901), mkTailPart("new", count=2)]
185208
)
186209

187210

partitionmanager/types.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,94 @@ def run(self, sql):
3838
"""
3939

4040

41+
class Partition(abc.ABC):
42+
"""
43+
Represents a single SQL table partition.
44+
"""
45+
46+
@abc.abstractmethod
47+
def values(self):
48+
"""
49+
Return a SQL partition value string.
50+
"""
51+
52+
@property
53+
@abc.abstractmethod
54+
def name(self):
55+
"""
56+
Return the partition's name.
57+
"""
58+
59+
@property
60+
@abc.abstractmethod
61+
def num_columns(self):
62+
"""
63+
Return the number of columns this partition represents
64+
"""
65+
66+
def __repr__(self):
67+
return f"{type(self).__name__}<{str(self)}>"
68+
69+
def __str__(self):
70+
return f"{self.name}: {self.values()}"
71+
72+
73+
class PositionPartition(Partition):
74+
"""
75+
A partition that may have positions assocated with it.
76+
"""
77+
78+
def __init__(self, name):
79+
self._name = name
80+
self.positions = list()
81+
82+
@property
83+
def name(self):
84+
return self._name
85+
86+
def add_position(self, position):
87+
self.positions.append(int(position))
88+
89+
@property
90+
def num_columns(self):
91+
return len(self.positions)
92+
93+
def values(self):
94+
return "(" + ", ".join([str(x) for x in self.positions]) + ")"
95+
96+
def __eq__(self, other):
97+
if isinstance(other, PositionPartition):
98+
return self._name == other._name and self.positions == other.positions
99+
return False
100+
101+
102+
class MaxValuePartition(Partition):
103+
"""
104+
A partition that lives at the tail of a partition list, saying
105+
all remaining values belong in this partition.
106+
"""
107+
108+
def __init__(self, name, count):
109+
self._name = name
110+
self.count = count
111+
112+
@property
113+
def name(self):
114+
return self._name
115+
116+
@property
117+
def num_columns(self):
118+
return self.count
119+
120+
def values(self):
121+
return ", ".join(["MAXVALUE"] * self.count)
122+
123+
def __eq__(self, other):
124+
if isinstance(other, MaxValuePartition):
125+
return self._name == other._name and self.count == other.count
126+
return False
127+
128+
41129
class MismatchedIdException(Exception):
42130
"""
43131
Raised if the partition map doesn't use the primary key as its range id.

0 commit comments

Comments
 (0)