1 /*
2 * This file and its contents are licensed under the Apache License 2.0.
3 * Please see the included NOTICE for copyright information and
4 * LICENSE-APACHE for a copy of the license.
5 */
6 #include <postgres.h>
7 #include <utils/jsonb.h>
8 #include <utils/numeric.h>
9
10 #include "export.h"
11 #include "hypercube.h"
12 #include "dimension_vector.h"
13
14 /*
15 * A hypercube represents the partition bounds of a hypertable chunk.
16 *
17 * A hypercube consists of N slices that each represent a range in a particular
18 * dimension that make up the hypercube. When a new tuple is inserted into a
19 * hypertable, and no chunk exists that can hold that tuple, we need to
20 * calculate a new hypercube that encloses the point corresponding to the
21 * tuple. When calculating the hypercube, we need to account for alignment
22 * requirements in dimensions marked as "aligned" and also ensure that there are
23 * no collisions with existing chunks. Alignment issues and collisions can occur
24 * when the partitioning configuration has changed (e.g., the time interval or
25 * number of partitions in a particular dimension changed).
26 */
27 Hypercube *
ts_hypercube_alloc(int16 num_dimensions)28 ts_hypercube_alloc(int16 num_dimensions)
29 {
30 Hypercube *hc = palloc0(HYPERCUBE_SIZE(num_dimensions));
31
32 hc->capacity = num_dimensions;
33 return hc;
34 }
35
36 void
ts_hypercube_free(Hypercube * hc)37 ts_hypercube_free(Hypercube *hc)
38 {
39 int i;
40
41 for (i = 0; i < hc->num_slices; i++)
42 ts_dimension_slice_free(hc->slices[i]);
43
44 pfree(hc);
45 }
46
47 #if defined(USE_ASSERT_CHECKING)
48 static inline bool
hypercube_is_sorted(const Hypercube * hc)49 hypercube_is_sorted(const Hypercube *hc)
50 {
51 int i;
52
53 if (hc->num_slices < 2)
54 return true;
55
56 for (i = 1; i < hc->num_slices; i++)
57 if (hc->slices[i]->fd.dimension_id < hc->slices[i - 1]->fd.dimension_id)
58 return false;
59
60 return true;
61 }
62 #endif
63
64 Hypercube *
ts_hypercube_copy(const Hypercube * hc)65 ts_hypercube_copy(const Hypercube *hc)
66 {
67 Hypercube *copy;
68 size_t nbytes = HYPERCUBE_SIZE(hc->capacity);
69 int i;
70
71 copy = palloc(nbytes);
72 memcpy(copy, hc, nbytes);
73
74 for (i = 0; i < hc->num_slices; i++)
75 copy->slices[i] = ts_dimension_slice_copy(hc->slices[i]);
76
77 return copy;
78 }
79
80 bool
ts_hypercube_equal(const Hypercube * hc1,const Hypercube * hc2)81 ts_hypercube_equal(const Hypercube *hc1, const Hypercube *hc2)
82 {
83 int i;
84
85 if (hc1->num_slices != hc2->num_slices)
86 return false;
87
88 for (i = 0; i < hc1->num_slices; i++)
89 if (ts_dimension_slice_cmp(hc1->slices[i], hc2->slices[i]) != 0)
90 return false;
91
92 return true;
93 }
94
95 static int
cmp_slices_by_dimension_id(const void * left,const void * right)96 cmp_slices_by_dimension_id(const void *left, const void *right)
97 {
98 const DimensionSlice *left_slice = *((DimensionSlice **) left);
99 const DimensionSlice *right_slice = *((DimensionSlice **) right);
100
101 if (left_slice->fd.dimension_id == right_slice->fd.dimension_id)
102 return 0;
103 if (left_slice->fd.dimension_id < right_slice->fd.dimension_id)
104 return -1;
105 return 1;
106 }
107
108 DimensionSlice *
ts_hypercube_add_slice_from_range(Hypercube * hc,int32 dimension_id,int64 start,int64 end)109 ts_hypercube_add_slice_from_range(Hypercube *hc, int32 dimension_id, int64 start, int64 end)
110 {
111 DimensionSlice *slice;
112
113 Assert(hc->capacity > hc->num_slices);
114
115 slice = ts_dimension_slice_create(dimension_id, start, end);
116 hc->slices[hc->num_slices++] = slice;
117
118 /* Check if we require a sort to maintain dimension order */
119 if (hc->num_slices > 1 &&
120 slice->fd.dimension_id < hc->slices[hc->num_slices - 2]->fd.dimension_id)
121 ts_hypercube_slice_sort(hc);
122
123 Assert(hypercube_is_sorted(hc));
124
125 return slice;
126 }
127
128 DimensionSlice *
ts_hypercube_add_slice(Hypercube * hc,const DimensionSlice * slice)129 ts_hypercube_add_slice(Hypercube *hc, const DimensionSlice *slice)
130 {
131 DimensionSlice *new_slice;
132
133 new_slice = ts_hypercube_add_slice_from_range(hc,
134 slice->fd.dimension_id,
135 slice->fd.range_start,
136 slice->fd.range_end);
137 new_slice->fd.id = slice->fd.id;
138
139 return new_slice;
140 }
141
142 /*
143 * Sort the hypercubes slices in ascending dimension ID order. This allows us to
144 * iterate slices in a consistent order.
145 */
146 void
ts_hypercube_slice_sort(Hypercube * hc)147 ts_hypercube_slice_sort(Hypercube *hc)
148 {
149 qsort(hc->slices, hc->num_slices, sizeof(DimensionSlice *), cmp_slices_by_dimension_id);
150 }
151
152 const DimensionSlice *
ts_hypercube_get_slice_by_dimension_id(const Hypercube * hc,int32 dimension_id)153 ts_hypercube_get_slice_by_dimension_id(const Hypercube *hc, int32 dimension_id)
154 {
155 DimensionSlice slice = {
156 .fd.dimension_id = dimension_id,
157 };
158 void *ptr = &slice;
159
160 if (hc->num_slices == 0)
161 return NULL;
162
163 Assert(hypercube_is_sorted(hc));
164
165 ptr = bsearch(&ptr,
166 hc->slices,
167 hc->num_slices,
168 sizeof(DimensionSlice *),
169 cmp_slices_by_dimension_id);
170
171 if (NULL == ptr)
172 return NULL;
173
174 return *((DimensionSlice **) ptr);
175 }
176
177 /*
178 * Given a set of constraints, build the corresponding hypercube.
179 */
180 Hypercube *
ts_hypercube_from_constraints(const ChunkConstraints * constraints,MemoryContext mctx)181 ts_hypercube_from_constraints(const ChunkConstraints *constraints, MemoryContext mctx)
182 {
183 Hypercube *hc;
184 int i;
185 MemoryContext old;
186
187 old = MemoryContextSwitchTo(mctx);
188 hc = ts_hypercube_alloc(constraints->num_dimension_constraints);
189 MemoryContextSwitchTo(old);
190
191 for (i = 0; i < constraints->num_constraints; i++)
192 {
193 ChunkConstraint *cc = chunk_constraints_get(constraints, i);
194 ScanTupLock tuplock = {
195 .lockmode = LockTupleKeyShare,
196 .waitpolicy = LockWaitBlock,
197 .lockflags = TUPLE_LOCK_FLAG_FIND_LAST_VERSION,
198 };
199
200 if (is_dimension_constraint(cc))
201 {
202 DimensionSlice *slice;
203 ScanTupLock *const tuplock_ptr = RecoveryInProgress() ? NULL : &tuplock;
204
205 Assert(hc->num_slices < constraints->num_dimension_constraints);
206
207 /* When building the hypercube, we reference the dimension slices
208 * to construct the hypercube.
209 *
210 * However, we cannot add a tuple lock when running in recovery
211 * mode since that prevents SELECT statements (which reach this
212 * point) from running on a read-only secondary (which runs in
213 * ephemeral recovery mode), so we only take the lock if we are not
214 * in recovery mode.
215 */
216 slice = ts_dimension_slice_scan_by_id_and_lock(cc->fd.dimension_slice_id,
217 tuplock_ptr,
218 mctx);
219 Assert(slice != NULL);
220 hc->slices[hc->num_slices++] = slice;
221 }
222 }
223
224 ts_hypercube_slice_sort(hc);
225
226 Assert(hypercube_is_sorted(hc));
227
228 return hc;
229 }
230
231 /*
232 * Find slices in the hypercube that already exists in metadata.
233 *
234 * If a slice exists in metadata, the slice ID will be filled in on the
235 * existing slice in the hypercube. Optionally, also lock the slice when
236 * found.
237 */
238 int
ts_hypercube_find_existing_slices(const Hypercube * cube,const ScanTupLock * tuplock)239 ts_hypercube_find_existing_slices(const Hypercube *cube, const ScanTupLock *tuplock)
240 {
241 int i;
242 int num_found = 0;
243
244 for (i = 0; i < cube->num_slices; i++)
245 {
246 /*
247 * Check if there's already an existing slice with the calculated
248 * range. If a slice already exists, use that slice's ID instead
249 * of a new one.
250 */
251 bool found = ts_dimension_slice_scan_for_existing(cube->slices[i], tuplock);
252
253 if (found)
254 num_found++;
255 }
256
257 return num_found;
258 }
259
260 /*
261 * Calculate the hypercube that encloses the given point.
262 *
263 * The hypercube's dimensions are calculated one by one, and depend on the
264 * current partitioning in each dimension of the N-dimensional hyperspace,
265 * including any alignment requirements.
266 *
267 * For non-aligned dimensions, we simply calculate the hypercube's slice range
268 * in that dimension given current partitioning configuration. If there is
269 * already an identical slice for that dimension, we will reuse it rather than
270 * creating a new one.
271 *
272 * For aligned dimensions, we first try to find an existing slice that covers
273 * the insertion point. If an existing slice is found, we reuse it or otherwise
274 * we calculate a new slice as described for non-aligned dimensions.
275 *
276 * If a hypercube has dimension slices that are not reused ones, we might need
277 * to cut them to ensure alignment and avoid collisions with other chunk
278 * hypercubes. This happens in a later step.
279 */
280 Hypercube *
ts_hypercube_calculate_from_point(const Hyperspace * hs,const Point * p,const ScanTupLock * tuplock)281 ts_hypercube_calculate_from_point(const Hyperspace *hs, const Point *p, const ScanTupLock *tuplock)
282 {
283 Hypercube *cube;
284 int i;
285
286 cube = ts_hypercube_alloc(hs->num_dimensions);
287
288 /* For each dimension, calculate the hypercube's slice in that dimension */
289 for (i = 0; i < hs->num_dimensions; i++)
290 {
291 const Dimension *dim = &hs->dimensions[i];
292 int64 value = p->coordinates[i];
293 bool found = false;
294
295 /* Assert that dimensions are in ascending order */
296 Assert(i == 0 || dim->fd.id > hs->dimensions[i - 1].fd.id);
297
298 /*
299 * If this is an aligned dimension, we'd like to reuse any existing
300 * slice that covers the coordinate in the dimension
301 */
302 if (dim->fd.aligned)
303 {
304 DimensionVec *vec;
305
306 vec = ts_dimension_slice_scan_limit(dim->fd.id, value, 1, tuplock);
307
308 if (vec->num_slices > 0)
309 {
310 cube->slices[i] = vec->slices[0];
311 found = true;
312 }
313 }
314
315 if (!found)
316 {
317 /*
318 * No existing slice found, or we are not aligning, so calculate
319 * the range of a new slice
320 */
321 cube->slices[i] = ts_dimension_calculate_default_slice(dim, value);
322
323 /*
324 * Check if there's already an existing slice with the calculated
325 * range. If a slice already exists, use that slice's ID instead
326 * of a new one.
327 */
328 ts_dimension_slice_scan_for_existing(cube->slices[i], tuplock);
329 }
330 }
331
332 cube->num_slices = hs->num_dimensions;
333
334 Assert(hypercube_is_sorted(cube));
335
336 return cube;
337 }
338
339 /*
340 * Check if two hypercubes collide (overlap).
341 *
342 * This is basically an axis-aligned bounding box collision detection,
343 * generalized to N dimensions. We check for dimension slice collisions in each
344 * dimension and only if all dimensions collide there is a hypercube collision.
345 */
346 bool
ts_hypercubes_collide(const Hypercube * cube1,const Hypercube * cube2)347 ts_hypercubes_collide(const Hypercube *cube1, const Hypercube *cube2)
348 {
349 int i;
350
351 Assert(cube1->num_slices == cube2->num_slices);
352
353 for (i = 0; i < cube1->num_slices; i++)
354 if (!ts_dimension_slices_collide(cube1->slices[i], cube2->slices[i]))
355 return false;
356
357 return true;
358 }
359