SQLon test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 95.1% 39 / 0 / 41
Functions: 100.0% 14 / 0 / 14
Branches: 86.7% 13 / 0 / 15

include/sqlon/window.h
Line Branch Exec Source
1 /******************************************************************************
2 **
3 ** Copyright (C) 2026 Ivan Pinezhaninov <ivan.pinezhaninov@gmail.com>
4 **
5 ** This file is part of the SQLon - which can be found at
6 ** https://github.com/IvanPinezhaninov/sqlon/.
7 **
8 ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
9 ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
10 ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
11 ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
12 ** DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
13 ** OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
14 ** THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 **
16 ******************************************************************************/
17
18 #ifndef SQLON_WINDOW_H
19 #define SQLON_WINDOW_H
20
21 #include <sqlon/export.h>
22 #include <sqlon/order.h>
23
24 #include <cstdint>
25 #include <memory>
26 #include <type_traits>
27 #include <utility>
28
29 namespace sqlon {
30
31 namespace detail {
32 struct window_access;
33 }
34
35 /** @brief Boundary of a SQL window frame. */
36 class window_frame_bound final {
37 private:
38 friend struct detail::window_access;
39
40 28 explicit window_frame_bound(detail::window_frame_bound_node node)
41 28 : m_node(node)
42 28 {}
43
44 detail::window_frame_bound_node m_node;
45 };
46
47 /** @brief Creates an UNBOUNDED PRECEDING boundary. */
48 SQLON_API window_frame_bound unbounded_preceding();
49
50 /** @brief Creates an offset PRECEDING boundary. */
51 SQLON_API window_frame_bound preceding(std::uint64_t offset);
52
53 /** @brief Creates a CURRENT ROW boundary. */
54 SQLON_API window_frame_bound current_row();
55
56 /** @brief Creates an offset FOLLOWING boundary. */
57 SQLON_API window_frame_bound following(std::uint64_t offset);
58
59 /** @brief Creates an UNBOUNDED FOLLOWING boundary. */
60 SQLON_API window_frame_bound unbounded_following();
61
62 /** @brief Complete SQL window-frame specification. */
63 class window_frame final {
64 private:
65 friend struct detail::window_access;
66
67 11 explicit window_frame(detail::window_frame_node node)
68 11 : m_node(std::move(node))
69 11 {}
70
71 detail::window_frame_node m_node;
72 };
73
74 /** @brief Builder for ROWS, RANGE, or GROUPS window frames. */
75 class window_frame_builder final {
76 public:
77 /** @brief Creates a frame between two boundaries. */
78 SQLON_API window_frame between(window_frame_bound start, window_frame_bound end) const;
79
80 /** @brief Creates a frame with one boundary. */
81 SQLON_API window_frame bound(window_frame_bound value) const;
82
83 /** @brief Creates a frame starting at UNBOUNDED PRECEDING. */
84 SQLON_API window_frame unbounded_preceding() const;
85
86 /** @brief Creates a frame starting at an offset PRECEDING boundary. */
87 SQLON_API window_frame preceding(std::uint64_t offset) const;
88
89 /** @brief Creates a frame starting at CURRENT ROW. */
90 SQLON_API window_frame current_row() const;
91
92 /** @brief Rejects an invalid single-bound FOLLOWING frame. */
93 SQLON_API window_frame following(std::uint64_t offset) const;
94
95 private:
96 friend struct detail::window_access;
97
98 SQLON_API explicit window_frame_builder(detail::window_frame_mode mode);
99
100 static int bound_rank(detail::window_frame_bound_kind kind) noexcept;
101
102 detail::window_frame_mode m_mode;
103 };
104
105 /** @brief Starts a ROWS window-frame builder. */
106 SQLON_API window_frame_builder rows();
107
108 /** @brief Starts a RANGE window-frame builder. */
109 SQLON_API window_frame_builder range();
110
111 /** @brief Starts a GROUPS window-frame builder. */
112 SQLON_API window_frame_builder groups();
113
114 /** @brief PARTITION BY, ORDER BY, and frame definition for a window expression. */
115 class window_spec final {
116 public:
117 /** @brief Creates an empty window specification. */
118 SQLON_API window_spec();
119
120 /** @brief Appends expressions to PARTITION BY. */
121 template<typename... Expressions>
122 2 window_spec partition_by(const Expressions&... expressions) const
123 {
124 2 window_spec copy = *this;
125
1/1
✓ Branch 3 taken 2 times.
2 (copy.m_node.partition_by.push_back(detail::expression_access::node(to_expression(expressions))), ...);
126 2 return copy;
127 ✗ }
128
129 /** @brief Appends orderings to the window ORDER BY clause. */
130 template<typename... Orders>
131 2 window_spec order_by(const Orders&... orders) const
132 {
133 static_assert((std::is_same_v<std::decay_t<Orders>, ordering> && ...),
134 "window order_by expects asc()/desc() values");
135 2 window_spec copy = *this;
136
1/1
✓ Branch 2 taken 2 times.
2 (copy.m_node.order_by.push_back(detail::ordering_access::node(orders)), ...);
137 2 return copy;
138 ✗ }
139
140 /** @brief Sets the window frame. */
141 SQLON_API window_spec frame(window_frame value) const;
142
143 private:
144 friend struct detail::window_access;
145
146 detail::window_spec_node m_node;
147 };
148
149 namespace detail {
150
151 struct window_access final {
152 28 static window_frame_bound make_bound(window_frame_bound_node node)
153 {
154 28 return window_frame_bound{node};
155 }
156
157 28 static const window_frame_bound_node& node(const window_frame_bound& value) noexcept
158 {
159 28 return value.m_node;
160 }
161
162 11 static window_frame make_frame(window_frame_node node)
163 {
164 11 return window_frame{std::move(node)};
165 }
166
167 8 static const window_frame_node& node(const window_frame& value) noexcept
168 {
169 8 return value.m_node;
170 }
171
172 18 static window_frame_builder make_builder(window_frame_mode mode)
173 {
174 18 return window_frame_builder{mode};
175 }
176
177 14 static const window_spec_node& node(const window_spec& value) noexcept
178 {
179 14 return value.m_node;
180 }
181 };
182
183 } // namespace detail
184
185 /** @brief Creates an empty window specification. */
186 SQLON_API window_spec window();
187
188 /** @brief Creates a window specification with PARTITION BY expressions. */
189 template<typename... Expressions>
190 1 window_spec partition_by(const Expressions&... expressions)
191 {
192
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
1 return window().partition_by(expressions...);
193 }
194
195 template<typename T>
196 14 expression<T> expression<T>::over(const window_spec& value) const
197 {
198
1/1
✓ Branch 1 taken 14 times.
14 std::shared_ptr<detail::expression_node> node = std::make_shared<detail::expression_node>();
199 14 node->kind = detail::expression_kind::window;
200
3/5
✓ Branch 1 taken 14 times.
✓ Branch 3 taken 14 times.
✓ Branch 4 taken 14 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
28 node->operands = {m_node};
201
1/1
✓ Branch 3 taken 14 times.
14 node->window = detail::window_access::node(value);
202 28 return detail::expression_access::make<T>(std::move(node));
203 28 }
204
205 /** @brief Creates a ROW_NUMBER window function. */
206 SQLON_API expression<std::int64_t> row_number();
207
208 /** @brief Creates a RANK window function. */
209 SQLON_API expression<std::int64_t> rank();
210
211 /** @brief Creates a DENSE_RANK window function. */
212 SQLON_API expression<std::int64_t> dense_rank();
213
214 /** @brief Creates a LAG window function. */
215 template<typename T>
216 expression<T> lag(const expression<T>& value)
217 {
218 return function<T>("lag", value);
219 }
220
221 /** @brief Creates a LAG window function with an offset. */
222 template<typename T, typename Offset>
223 expression<T> lag(const expression<T>& value, Offset&& offset)
224 {
225 using offset_type = detail::expression_value_type_t<Offset>;
226 static_assert(std::is_integral_v<offset_type> && !std::is_same_v<offset_type, bool>,
227 "LAG offsets must be integral expressions");
228 return function<T>("lag", value, std::forward<Offset>(offset));
229 }
230
231 /** @brief Creates a LAG window function with an offset and default value. */
232 template<typename T, typename Offset, typename Default>
233 2 expression<T> lag(const expression<T>& value, Offset&& offset, Default&& default_value)
234 {
235 using offset_type = detail::expression_value_type_t<Offset>;
236 static_assert(std::is_integral_v<offset_type> && !std::is_same_v<offset_type, bool>,
237 "LAG offsets must be integral expressions");
238 static_assert(detail::compatible_expression_types<T, detail::expression_value_type_t<Default>>,
239 "LAG default values must be compatible with the source expression");
240
4/4
sqlon::expression<long> sqlon::lag<long, int, int>(sqlon::expression<long> const&, int&&, int&&):
✓ Branch 3 taken 1 time.
✓ Branch 6 taken 1 time.
sqlon::expression<long> sqlon::lag<long, sqlon::parameter_slot<unsigned long> const&, sqlon::parameter_slot<long> const&>(sqlon::expression<long> const&, sqlon::parameter_slot<unsigned long> const&, sqlon::parameter_slot<long> const&):
✓ Branch 3 taken 1 time.
✓ Branch 6 taken 1 time.
6 return function<T>("lag", value, std::forward<Offset>(offset), std::forward<Default>(default_value));
241 }
242
243 /** @brief Creates a LEAD window function. */
244 template<typename T>
245 expression<T> lead(const expression<T>& value)
246 {
247 return function<T>("lead", value);
248 }
249
250 /** @brief Creates a LEAD window function with an offset. */
251 template<typename T, typename Offset>
252 expression<T> lead(const expression<T>& value, Offset&& offset)
253 {
254 using offset_type = detail::expression_value_type_t<Offset>;
255 static_assert(std::is_integral_v<offset_type> && !std::is_same_v<offset_type, bool>,
256 "LEAD offsets must be integral expressions");
257 return function<T>("lead", value, std::forward<Offset>(offset));
258 }
259
260 /** @brief Creates a LEAD window function with an offset and default value. */
261 template<typename T, typename Offset, typename Default>
262 expression<T> lead(const expression<T>& value, Offset&& offset, Default&& default_value)
263 {
264 using offset_type = detail::expression_value_type_t<Offset>;
265 static_assert(std::is_integral_v<offset_type> && !std::is_same_v<offset_type, bool>,
266 "LEAD offsets must be integral expressions");
267 static_assert(detail::compatible_expression_types<T, detail::expression_value_type_t<Default>>,
268 "LEAD default values must be compatible with the source expression");
269 return function<T>("lead", value, std::forward<Offset>(offset), std::forward<Default>(default_value));
270 }
271
272 } // namespace sqlon
273
274 #endif // SQLON_WINDOW_H
275