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