MySQL 9.6.0
Source Code Documentation
boundary_set_binary_operation_iterator.h
Go to the documentation of this file.
1// Copyright (c) 2025, Oracle and/or its affiliates.
2//
3// This program is free software; you can redistribute it and/or modify
4// it under the terms of the GNU General Public License, version 2.0,
5// as published by the Free Software Foundation.
6//
7// This program is designed to work with certain software (including
8// but not limited to OpenSSL) that is licensed under separate terms,
9// as designated in a particular file or component or in included license
10// documentation. The authors of MySQL hereby grant you an additional
11// permission to link the program and your derivative works with the
12// separately licensed software that they have either included with
13// the program or referenced in the documentation.
14//
15// This program is distributed in the hope that it will be useful,
16// but WITHOUT ANY WARRANTY; without even the implied warranty of
17// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18// GNU General Public License, version 2.0, for more details.
19//
20// You should have received a copy of the GNU General Public License
21// along with this program; if not, write to the Free Software
22// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23
24#ifndef MYSQL_SETS_BOUNDARY_SET_BINARY_OPERATION_ITERATOR_H
25#define MYSQL_SETS_BOUNDARY_SET_BINARY_OPERATION_ITERATOR_H
26
27/// @file
28/// Experimental API header
29
30#include <cassert> // assert
31#include "mysql/iterators/iterator_interface.h" // Iterator_interface
32#include "mysql/sets/binary_operation.h" // Binary_operation
33#include "mysql/sets/boundary_set_meta.h" // Is_boundary_set
34#include "mysql/sets/optional_view_source_set.h" // Optional_view_source_set
35#include "mysql/sets/set_categories_and_traits.h" // Is_compatible_set
36
37/// @addtogroup GroupLibsMysqlSets
38/// @{
39
40namespace mysql::sets::detail {
41
42/// Forward iterator over the result of a binary set operation (union,
43/// intersection, or subtraction) over two boundary sets.
44template <Is_boundary_set Source1_tp, Is_boundary_set Source2_tp,
45 Binary_operation operation_tp>
46 requires Is_compatible_set<Source1_tp, Source2_tp>
49 Boundary_set_binary_operation_iterator<Source1_tp, Source2_tp,
50 operation_tp>> {
51 // This class holds one iterator to each source boundary set: m_pos1 to
52 // m_source1, and m_pos2 to m_source2. In this class implementation, we refer
53 // to objects of this class as output iterators, and its values as output
54 // boundaries.
55 //
56 // In general, a C++ iterator positioned at the end of the range, cannot be
57 // dereferenced. However, we will need to compare two iterators that can
58 // possibly be positioned at the end, treating end-positioned iterators as
59 // greater than iterators not positioned at the end. To make the notation
60 // convenient, we define (for exposition purposes) the "extended value" of an
61 // iterator as:
62 //
63 // V(iter) = *iter, when iter is not at the end; V(iter) = infinity, when
64 // iter is at the end.
65 //
66 // Here, infinity is an exposition-only value that is strictly greater than
67 // any value returned from *iter when iter does not point to the end.
68 //
69 // Define posA as the smaller-valued, and posB as the greater-valued iterator,
70 // comparing the extended values. If the extended values are equal, posA and
71 // posB may be defined arbitrarily. Let sourceA, sourceB be their
72 // corresponding sets.
73 //
74 // The current output boundary is *posA.
75 //
76 // The class maintains two invariants:
77 //
78 // I1. posB == source2.lower_bound(*posA)
79 //
80 // This invariant is important for two reasons: (1) it ensures that each
81 // output boundary has a unique representation. (2) the algorithm that
82 // steps to the next output boundary can use lower_bound to take long
83 // steps.
84 //
85 // I2. posA points to a boundary that is included in the output set. This is
86 // determined by case analysis, depending on the following things:
87 // - The operation (union/intersection/subtraction);
88 // - Whether posA==posB;
89 // - Whether posA/posB are endpoints;
90 // - For subtraction, whether sourceA refers to m_source1 or m_source2.
91 //
92 // The case analysis is as follows:
93 //
94 // - For union, posA is an output boundary if:
95 //
96 // V(posA) == V(posB)
97 // ? posA.is_endpoint() == posB.is_endpoint()
98 // : !posB.is_endpoint()
99 //
100 // In other words:
101 //
102 // - If the extended values are equal, posA is an output boundary if
103 // either both posA/posB are endpoints, or none is and endpoint. The
104 // reason is that, when two intervals from sourceA and sourceB begin
105 // or end at the same point, the union also has an interval that
106 // begins/ends at the same point; but when an interval from one source
107 // ends where an interval from the other source begins, the union
108 // contains those two intervals merged, where that point is not a
109 // boundary.
110 //
111 // Graphically, posA is included/excluded as follows:
112 // A A A A
113 // Included: ---] or [--- Excluded: ---] or [---
114 // ---] [--- [--- ---]
115 // B B B B
116 //
117 // - If the extended values are different, posA is an output boundary if
118 // posB is not an endpoint. The reason is that a boundary (posA) that
119 // is covered by an interval (ending at posB) cannot be a boundary in
120 // the union.
121 //
122 // Graphically, posA is included/excluded as follows:
123 // A A A A
124 // Included: ---] or [--- Excluded: ---] or [---
125 // [--- [--- ---] ---]
126 // B B B B
127 //
128 // In all cases where posA is an output boundary, the output boundary
129 // is an endpoint if and only if posA is an endpoint.
130 //
131 // - For intersection, posA is an output boundary if:
132 //
133 // V(posA) == V(posB)
134 // ? a.is_endpoint() == posB.is_endpoint()
135 // : posB.is_endpoint()
136 //
137 // In other words:
138 //
139 // - If the extended values are equal, posA is an output boundary if
140 // either both posA/posB are endpoints, or none is an endpoint. The
141 // reason is that, when two intervals from sourceA and sourceB begin
142 // or end at the same point, the intersection also has an interval
143 // that begins/ends at the same point; but when an interval from one
144 // source ends where an interval from the other source begins, the
145 // intersection does not contain any of those two intervals, so that
146 // point is not an output boundary.
147 //
148 // Graphically, posA is included/excluded as follows:
149 // A A A A
150 // Included: ---] or [--- Excluded: ---] or [---
151 // ---] [--- [--- ---]
152 // B B B B
153 //
154 // - If the extended values are different, posA is an output boundary if
155 // posB is an endpoint, because a boundary (posA) needs to be covered
156 // by an interval from the other source (ending at posB) in order
157 // to be a boundary in the intersection.
158 //
159 // Graphically, posA is included/excluded as follows:
160 // A A A A
161 // Included: ---] or [--- Excluded: [--- or ---]
162 // ---] ---] [--- [---
163 // B B B B
164 //
165 // When posA is an output boundary, the output boundary is an endpoint
166 // if and only if posA is an endpoint.
167 //
168 // - For subtraction, posA is an output boundary if:
169 //
170 // V(posA) == V(posB)
171 // ? posA.is_endpoint() != posB.is_endpoint()
172 // : (sourceA == m_source1) ? !posB.is_endpoint() : posB.is_endpoint()
173 //
174 // In other words:
175 //
176 // - If the extended values are equal, posA is an output boundary if
177 // either posA is an endpoint and posB is not, or posB is an endpoint
178 // and posA is not. The reason is that, when two intervals from
179 // sourceA and sourceB end at the same point, the one from m_source2
180 // "deletes" the one from m_source1, so that point is not an output
181 // boundary; but when one ends where the other begins, the one from
182 // m_source1 is preserved in the output set.
183 //
184 // Graphically, posA is included/excluded as follows:
185 // A A A A
186 // Included: ---] or [--- Excluded: ---] or [---
187 // [--- ---] ---] [---
188 // B B B B
189 //
190 // When posA is an output boundary, the output boundary is an endpoint
191 // if and only if m_pos1 is an endpoint.
192 //
193 // - If the extended values are different, the case depends on whether
194 // posA refers to m_pos1 or to m_pos2 (subtraction is not symmetric in
195 // the operands):
196 //
197 // - If sourceA refers to m_source1, posA is included if posB is not
198 // an endpoint. The reason is that posB==m_pos2 is then the endpoint
199 // of an interval from the subtracted set, so posA is subtracted.
200 //
201 // Graphically, posA==m_pos1 is included/excluded as follows:
202 // A A A A
203 // Included: ---] or [--- Excluded: ---] or [---
204 // [--- [--- ---] ---]
205 // B B B B
206 //
207 // When posA is an output boundary, the output boundary is an
208 // endpoint if and only if m_pos1 is an endpoint.
209 //
210 // - If sourceA refers to m_source2, posA is included if posB is an
211 // endpoint. The reason is that posA==m_pos1 is then the endpoint
212 // of an interval from which we remove posB's interval, so posA
213 // becomes the boundary of a new "gap" in the result of the
214 // subtraction.
215 //
216 // Graphically, posA==m_pos2 is included/excluded as follows:
217 // A A A A
218 // Included: ---] or [--- Excluded: ---] or [---
219 // ---] ---] [--- [---
220 // B B B B
221 //
222 // When posA is an output boundary, the output boundary is an
223 // endpoint if and only if m_pos1 is not an endpoint.
224 //
225 // These invariants are maintained as follows:
226 //
227 // - To advance the iterator one step:
228 //
229 // 1. If m_pos1==m_pos2, advance both iterators one step; otherwise advance
230 // posA. Note that in both cases, I1 is maintained.
231 //
232 // 2. If the current position is an output boundary, we are done.
233 // Otherwise, advance posA to the lower bound of posA, and go to step 1.
234 //
235 // The loop body of advance_to_boundary implements step 2 followed by step
236 // 1.
237 //
238 // The case analysis to determine if the current iterators represent an
239 // output boundary is implemented in the two is_output_boundary functions.
240 //
241 // - To initialize the 'begin' iterator, start with m_pos1=m_source1.begin()
242 // and m_pos2=m_source2.begin(). Note that I1 holds already. Then, execute
243 // (2) above.
244 public:
245 using Source1_t = Source1_tp;
246 using Source2_t = Source2_tp;
249 static constexpr auto operation = operation_tp;
250 using Set_traits_t = typename Source1_t::Set_traits_t;
251 using Element_t = typename Set_traits_t::Element_t;
252 using Less_t = typename Set_traits_t::Less_t;
253 using This_t =
257
258 // Default constructor. The resulting iterator is in a "pointless" state,
259 // where the only possible operations are assignment from another iterator,
260 // and comparison.
262
263 // Default rule-of-5 members
265 const Boundary_set_binary_operation_iterator &source) noexcept = default;
273
274 // Construct from two sources and one iterator into each of them.
276 const Source2_t *source2,
277 const Iterator1_t &pos1,
278 const Iterator2_t &pos2)
279 : m_source1(source1), m_source2(source2), m_pos1(pos1), m_pos2(pos2) {
281 }
282
283 /// @return The value that this iterator currently points to.
284 [[nodiscard]] Element_t get() const {
285 if (m_order <= 0)
286 return *m_pos1;
287 else
288 return *m_pos2;
289 }
290
291 /// @return true if this iterator equals other.
292 [[nodiscard]] bool is_equal(const This_t &other) const {
293 if (m_order <= 0)
294 return m_pos1 == other.m_pos1;
295 else
296 return m_pos2 == other.m_pos2;
297 }
298
299 /// @return true if this iterator is at an endpoint boundary.
300 [[nodiscard]] bool is_endpoint() const {
302 // For op_subtraction: when m_order < 0, m_pos2 is a non-endpoint, and the
303 // current boundary inherits endpointness from m_pos1. When m_order > 0,
304 // m_pos1 must be an endpoint, and the boundary inherits the *reversed*
305 // endpointness from m_pos2. When m_order == 0 and m_pos1 and m_pos2 are
306 // both non-end iterators, m_pos1 and m_pos2 have opposite endpointness,
307 // and we inherit the endpointness from m_pos1. When m_order == 0 and
308 // m_pos1 and m_pos2 are end iterators, they are both non-endpoints and we
309 // need to return a non-endpoint.
310 if (m_order <= 0)
311 return m_pos1.is_endpoint();
312 else
313 return !m_pos2.is_endpoint();
314 } else {
315 // For op_union/op_intersection: When m_order != 0, the current boundary
316 // inherits endpointness from the boundary of the underlying set. When
317 // m_order == 0, the boundary is in the set only when both underlying sets
318 // have the same endpointness.
319 if (m_order <= 0)
320 return m_pos1.is_endpoint();
321 else
322 return m_pos2.is_endpoint();
323 }
324 }
325
326 /// Move to the next iterator position.
327 void next() {
328 // if m_order == 0 then, yes, advance both!
329 if (m_order <= 0) ++m_pos1;
330 if (m_order >= 0) ++m_pos2;
331
333 }
334
335 /// Return the current iterator to the first source.
336 [[nodiscard]] Iterator1_t position1() const { return m_pos1; }
337
338 /// Return the current iterator to the second source.
339 [[nodiscard]] Iterator2_t position2() const { return m_pos2; }
340
341 private:
342 /// Set m_order to -1, 0, or 1, according to the relative order of m_pos1 and
343 /// m_pos2.
344 ///
345 /// -1 indicates that m_pos1 is smaller, 0 that they are equal, and 1 that
346 /// m_pos2 is smaller.
348 if (m_pos1 == m_source1.end()) {
349 if (m_pos2 == m_source2.end())
350 m_order = 0; // both are at the end
351 else
352 m_order = 1; // only pos1 is at the end
353 } else if (m_pos2 == m_source2.end()) {
354 m_order = -1; // only pos2 is at the end
355 } else {
356 if (lt(*m_pos1, *m_pos2))
357 m_order = -1; // *pos1 < *pos2
358 else if (*m_pos1 == *m_pos2)
359 m_order = 0; // *pos1 == *pos2
360 else
361 m_order = 1; // *pos1 > *pos2
362 }
363 }
364
365 /// Until the pair of iterators defines an output boundary, advance the
366 /// smallest-valued iterator past the greatest-valued one.
369 while (true) {
370 if (m_order < 0) {
372 m_pos2))
373 return;
374 } else if (m_order > 0) {
376 m_pos1))
377 return;
378 } else {
379 assert(m_order == 0);
380 if (advance_if_not_output_boundary()) return;
381 }
382 }
383 }
384
385 /// Assuming both iterators point to the same value: if they are not an output
386 /// boundary, advance both, and update m_order.
387 ///
388 /// @retval true if the position was at an output boundary.
389 ///
390 /// @retval false if the position was not at an output boundary, in which case
391 /// the position was advanced.
392 [[nodiscard]] bool advance_if_not_output_boundary() {
393 assert(m_order == 0);
394 if (is_output_boundary()) return true;
395 ++m_pos1;
396 ++m_pos2;
398 return false;
399 }
400
401 /// Assuming the iterators point to different values where pos_a < pos_b: if
402 /// pos_a is not at an output boundary, advance it to the lower bound of
403 /// pos_b, and update m_order.
404 ///
405 /// @param source_a the source boundary set that pos_a iterates over.
406 ///
407 /// @param pos_a the smallest-valued iterator.
408 ///
409 /// @param source_b the source boundary set that pos_b iterates over.
410 ///
411 /// @param pos_b the greatest-valued iterator.
412 ///
413 /// @retval true if the position was at an output boundary, or if advancing
414 /// pos_a made us reach the end output boundary.
415 ///
416 /// @retval false if the position was not at an output boundary, and pos_a was
417 /// advanced but not to the end output boundary.
418 [[nodiscard]] bool advance_if_not_output_boundary(const auto &source_a,
419 auto &pos_a,
420 const auto &source_b,
421 auto &pos_b) {
422 assert(source_a.has_object());
423 assert(m_order != 0);
424 assert(pos_a != source_a->end());
425 if (is_output_boundary(pos_b.is_endpoint())) return true;
426 if (pos_b == source_b.end()) {
427 pos_a = source_a->end();
428 m_order = 0;
429 return true;
430 } else {
431 pos_a = source_a->lower_bound(pos_a, *pos_b);
433 return false;
434 }
435 }
436
437 /// Assuming both iterators point to the same value, or both point to the end,
438 /// return true if that position defines an output boundary.
439 [[nodiscard]] constexpr bool is_output_boundary() {
440 assert(m_order == 0);
441 if (m_pos1 == m_source1.end()) return true;
443 return m_pos1.is_endpoint() != m_pos2.is_endpoint();
444 } else {
445 return m_pos1.is_endpoint() == m_pos2.is_endpoint();
446 }
447 }
448
449 /// Assuming the iterators point to different values, return true if the
450 /// smallest-valued iterator points to an output boundary.
451 ///
452 /// The decision whether a boundary should be included in the output depends
453 /// on the operation type, whether the next-greater boundary in the other
454 /// source is an endpoint or not, and whether the queried boundary is in
455 /// source1 or source2.
456 ///
457 /// @param pos_b_is_endpoint true if the next-greater boundary in the other
458 /// source (not the source containing the boundary we query for) is an
459 /// endpoint.
460 [[nodiscard]] constexpr bool is_output_boundary(bool pos_b_is_endpoint) {
461 assert(m_order != 0);
462 if constexpr (operation == Binary_operation::op_union) {
463 // pos_a is an output boundary if it is not covered by an interval ending
464 // in pos_b.
465 return !pos_b_is_endpoint;
466 } else if constexpr (operation == Binary_operation::op_intersection) {
467 // pos_a is an output boundary if it is covered by an interval ending in
468 // pos_b.
469 return pos_b_is_endpoint;
470 } else if constexpr (operation == Binary_operation::op_subtraction) {
471 // When m_pos1 < m_pos2 (m_order < 0), posA=m_pos1 is an output boundary
472 // if it is not covered by an interval ending in posB.
473 // When m_pos2 > m_pos2 (m_order > 0), posA=m_pos2 is an output boundary
474 // if it is covered by an interval ending in posB=m_pos1.
475 return pos_b_is_endpoint == (m_order > 0);
476 }
477 }
478
479 /// @return true if the first value is smaller than the second value,
480 /// according to the order defined by the Set traits.
481 [[nodiscard]] static constexpr bool lt(const Element_t &element1,
482 const Element_t &element2) {
483 return Set_traits_t::lt(element1, element2);
484 }
485
486 /// Pointer to the left-hand-side source.
488
489 /// Pointer to the right-hand-side source.
491
492 /// Iterator to current position in m_source1.
494
495 /// Iterator to current position in m_source2.
497
498 /// -1 if m_pos1 < m_pos2; 0 if m_pos1 == m_pos2; 1 if m_pos1 > m_pos2. (We
499 /// don't use std::strong_ordering because we want to be able to negate it.)
500 int m_order{0};
501}; // class Boundary_set_binary_operation_iterator
502
503} // namespace mysql::sets::detail
504
505// addtogroup GroupLibsMysqlSets
506/// @}
507
508#endif // ifndef MYSQL_SETS_BOUNDARY_SET_BINARY_OPERATION_ITERATOR_H
Experimental API header.
Experimental API header.
CRTP base class (mixin) that makes your class a standard-compliant iterator, given only a minimal set...
Definition: iterator_interface.h:370
auto end() const
Return a valid end iterator, even if !has_object().
Definition: view_sources.h:273
Forward iterator over the result of a binary set operation (union, intersection, or subtraction) over...
Definition: boundary_set_binary_operation_iterator.h:50
constexpr bool is_output_boundary()
Assuming both iterators point to the same value, or both point to the end, return true if that positi...
Definition: boundary_set_binary_operation_iterator.h:439
static constexpr bool lt(const Element_t &element1, const Element_t &element2)
Definition: boundary_set_binary_operation_iterator.h:481
Iterator1_t m_pos1
Iterator to current position in m_source1.
Definition: boundary_set_binary_operation_iterator.h:493
bool advance_if_not_output_boundary(const auto &source_a, auto &pos_a, const auto &source_b, auto &pos_b)
Assuming the iterators point to different values where pos_a < pos_b: if pos_a is not at an output bo...
Definition: boundary_set_binary_operation_iterator.h:418
static constexpr auto operation
Definition: boundary_set_binary_operation_iterator.h:249
typename Set_traits_t::Less_t Less_t
Definition: boundary_set_binary_operation_iterator.h:252
mysql::ranges::Range_const_iterator_type< Source1_t > Iterator1_t
Definition: boundary_set_binary_operation_iterator.h:255
Opt_source2_t m_source2
Pointer to the right-hand-side source.
Definition: boundary_set_binary_operation_iterator.h:490
void compute_order()
Set m_order to -1, 0, or 1, according to the relative order of m_pos1 and m_pos2.
Definition: boundary_set_binary_operation_iterator.h:347
constexpr bool is_output_boundary(bool pos_b_is_endpoint)
Assuming the iterators point to different values, return true if the smallest-valued iterator points ...
Definition: boundary_set_binary_operation_iterator.h:460
Source1_tp Source1_t
Definition: boundary_set_binary_operation_iterator.h:245
typename Set_traits_t::Element_t Element_t
Definition: boundary_set_binary_operation_iterator.h:251
bool is_endpoint() const
Definition: boundary_set_binary_operation_iterator.h:300
Source2_tp Source2_t
Definition: boundary_set_binary_operation_iterator.h:246
void next()
Move to the next iterator position.
Definition: boundary_set_binary_operation_iterator.h:327
Iterator2_t position2() const
Return the current iterator to the second source.
Definition: boundary_set_binary_operation_iterator.h:339
typename Source1_t::Set_traits_t Set_traits_t
Definition: boundary_set_binary_operation_iterator.h:250
bool is_equal(const This_t &other) const
Definition: boundary_set_binary_operation_iterator.h:292
mysql::ranges::Range_const_iterator_type< Source2_t > Iterator2_t
Definition: boundary_set_binary_operation_iterator.h:256
Opt_source1_t m_source1
Pointer to the left-hand-side source.
Definition: boundary_set_binary_operation_iterator.h:487
Element_t get() const
Definition: boundary_set_binary_operation_iterator.h:284
Boundary_set_binary_operation_iterator(Boundary_set_binary_operation_iterator &&) noexcept=default
int m_order
-1 if m_pos1 < m_pos2; 0 if m_pos1 == m_pos2; 1 if m_pos1 > m_pos2.
Definition: boundary_set_binary_operation_iterator.h:500
Iterator2_t m_pos2
Iterator to current position in m_source2.
Definition: boundary_set_binary_operation_iterator.h:496
void advance_to_boundary()
Until the pair of iterators defines an output boundary, advance the smallest-valued iterator past the...
Definition: boundary_set_binary_operation_iterator.h:367
Iterator1_t position1() const
Return the current iterator to the first source.
Definition: boundary_set_binary_operation_iterator.h:336
Boundary_set_binary_operation_iterator(const Boundary_set_binary_operation_iterator &source) noexcept=default
bool advance_if_not_output_boundary()
Assuming both iterators point to the same value: if they are not an output boundary,...
Definition: boundary_set_binary_operation_iterator.h:392
Experimental API header.
std::remove_cvref_t< decltype(std::declval< const Range_t >().begin())> Range_const_iterator_type
Gives the const_iterator type, deduced from the begin() const member.
Definition: meta.h:47
Definition: aliases.h:97
Binary_operation
Identifies the type of a binary operation.
Definition: binary_operation.h:37
noexcept
The return type for any call_and_catch(f, args...) call where f(args...) returns Type.
Definition: call_and_catch.h:76
Experimental API header.
repeated Source source
Definition: replication_asynchronous_connection_failover.proto:42
Experimental API header.