Skip to main content

reinhardt_db/orm/
window.rs

1/// Window functions similar to Django's window functions
2use serde::{Deserialize, Serialize};
3
4/// Window frame type
5#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
6pub enum FrameType {
7	/// Range variant.
8	Range,
9	/// Rows variant.
10	Rows,
11	/// Groups variant.
12	Groups,
13}
14
15impl FrameType {
16	/// Convert frame type to SQL keyword
17	///
18	/// # Examples
19	///
20	/// ```
21	/// use reinhardt_db::orm::window::FrameType;
22	///
23	/// assert_eq!(FrameType::Range.to_sql(), "RANGE");
24	/// assert_eq!(FrameType::Rows.to_sql(), "ROWS");
25	/// assert_eq!(FrameType::Groups.to_sql(), "GROUPS");
26	/// ```
27	pub fn to_sql(&self) -> &'static str {
28		match self {
29			FrameType::Range => "RANGE",
30			FrameType::Rows => "ROWS",
31			FrameType::Groups => "GROUPS",
32		}
33	}
34}
35
36/// Frame boundary
37#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
38pub enum FrameBoundary {
39	/// UnboundedPreceding variant.
40	UnboundedPreceding,
41	/// Preceding variant.
42	Preceding(i64),
43	/// CurrentRow variant.
44	CurrentRow,
45	/// Following variant.
46	Following(i64),
47	/// UnboundedFollowing variant.
48	UnboundedFollowing,
49}
50
51impl FrameBoundary {
52	/// Convert frame boundary to SQL expression
53	///
54	/// # Examples
55	///
56	/// ```
57	/// use reinhardt_db::orm::window::FrameBoundary;
58	///
59	/// assert_eq!(FrameBoundary::UnboundedPreceding.to_sql(), "UNBOUNDED PRECEDING");
60	/// assert_eq!(FrameBoundary::Preceding(5).to_sql(), "5 PRECEDING");
61	/// assert_eq!(FrameBoundary::CurrentRow.to_sql(), "CURRENT ROW");
62	/// assert_eq!(FrameBoundary::Following(3).to_sql(), "3 FOLLOWING");
63	/// assert_eq!(FrameBoundary::UnboundedFollowing.to_sql(), "UNBOUNDED FOLLOWING");
64	/// ```
65	pub fn to_sql(&self) -> String {
66		match self {
67			FrameBoundary::UnboundedPreceding => "UNBOUNDED PRECEDING".to_string(),
68			FrameBoundary::Preceding(n) => format!("{} PRECEDING", n),
69			FrameBoundary::CurrentRow => "CURRENT ROW".to_string(),
70			FrameBoundary::Following(n) => format!("{} FOLLOWING", n),
71			FrameBoundary::UnboundedFollowing => "UNBOUNDED FOLLOWING".to_string(),
72		}
73	}
74}
75
76/// Window frame specification
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct Frame {
79	/// The frame type.
80	pub frame_type: FrameType,
81	/// The start.
82	pub start: FrameBoundary,
83	/// The end.
84	pub end: Option<FrameBoundary>,
85}
86
87impl Frame {
88	/// Create a ROWS frame specification
89	///
90	/// # Examples
91	///
92	/// ```
93	/// use reinhardt_db::orm::window::{Frame, FrameBoundary};
94	///
95	/// let frame = Frame::rows(
96	///     FrameBoundary::UnboundedPreceding,
97	///     Some(FrameBoundary::CurrentRow)
98	/// );
99	/// assert_eq!(frame.to_sql(), "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW");
100	/// ```
101	pub fn rows(start: FrameBoundary, end: Option<FrameBoundary>) -> Self {
102		Self {
103			frame_type: FrameType::Rows,
104			start,
105			end,
106		}
107	}
108	/// Create a RANGE frame specification
109	///
110	/// # Examples
111	///
112	/// ```
113	/// use reinhardt_db::orm::window::{Frame, FrameBoundary};
114	///
115	/// let frame = Frame::range(
116	///     FrameBoundary::Preceding(3),
117	///     Some(FrameBoundary::Following(3))
118	/// );
119	/// assert_eq!(frame.to_sql(), "RANGE BETWEEN 3 PRECEDING AND 3 FOLLOWING");
120	/// ```
121	pub fn range(start: FrameBoundary, end: Option<FrameBoundary>) -> Self {
122		Self {
123			frame_type: FrameType::Range,
124			start,
125			end,
126		}
127	}
128	/// Create a GROUPS frame specification
129	///
130	/// # Examples
131	///
132	/// ```
133	/// use reinhardt_db::orm::window::{Frame, FrameBoundary};
134	///
135	/// let frame = Frame::groups(
136	///     FrameBoundary::CurrentRow,
137	///     Some(FrameBoundary::UnboundedFollowing)
138	/// );
139	/// assert_eq!(frame.to_sql(), "GROUPS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING");
140	/// ```
141	pub fn groups(start: FrameBoundary, end: Option<FrameBoundary>) -> Self {
142		Self {
143			frame_type: FrameType::Groups,
144			start,
145			end,
146		}
147	}
148	/// Convert frame to SQL BETWEEN clause
149	///
150	/// # Examples
151	///
152	/// ```
153	/// use reinhardt_db::orm::window::{Frame, FrameBoundary};
154	///
155	/// let frame = Frame::rows(FrameBoundary::Preceding(1), None);
156	/// assert_eq!(frame.to_sql(), "ROWS BETWEEN 1 PRECEDING AND CURRENT ROW");
157	/// ```
158	pub fn to_sql(&self) -> String {
159		let mut sql = format!(
160			"{} BETWEEN {}",
161			self.frame_type.to_sql(),
162			self.start.to_sql()
163		);
164
165		if let Some(ref end) = self.end {
166			sql.push_str(&format!(" AND {}", end.to_sql()));
167		} else {
168			sql.push_str(" AND CURRENT ROW");
169		}
170
171		sql
172	}
173}
174
175/// Window specification
176#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct Window {
178	/// The partition by.
179	pub partition_by: Vec<String>,
180	/// The order by.
181	pub order_by: Vec<String>,
182	/// The frame.
183	pub frame: Option<Frame>,
184}
185
186impl Window {
187	/// Create a new empty window specification
188	///
189	/// # Examples
190	///
191	/// ```
192	/// use reinhardt_db::orm::window::Window;
193	///
194	/// let window = Window::new();
195	/// assert_eq!(window.to_sql(), "");
196	/// ```
197	pub fn new() -> Self {
198		Self {
199			partition_by: Vec::new(),
200			order_by: Vec::new(),
201			frame: None,
202		}
203	}
204	/// Add a PARTITION BY clause
205	///
206	/// # Examples
207	///
208	/// ```
209	/// use reinhardt_db::orm::window::Window;
210	///
211	/// let window = Window::new().partition_by("department");
212	/// assert_eq!(window.to_sql(), "PARTITION BY department");
213	/// ```
214	pub fn partition_by(mut self, field: impl Into<String>) -> Self {
215		self.partition_by.push(field.into());
216		self
217	}
218	/// Add an ORDER BY clause
219	///
220	/// # Examples
221	///
222	/// ```
223	/// use reinhardt_db::orm::window::Window;
224	///
225	/// let window = Window::new().order_by("salary DESC");
226	/// assert_eq!(window.to_sql(), "ORDER BY salary DESC");
227	/// ```
228	pub fn order_by(mut self, field: impl Into<String>) -> Self {
229		self.order_by.push(field.into());
230		self
231	}
232	/// Add a frame specification
233	///
234	/// # Examples
235	///
236	/// ```
237	/// use reinhardt_db::orm::window::{Window, Frame, FrameBoundary};
238	///
239	/// let frame = Frame::rows(
240	///     FrameBoundary::UnboundedPreceding,
241	///     Some(FrameBoundary::CurrentRow)
242	/// );
243	/// let window = Window::new().order_by("date").frame(frame);
244	/// assert_eq!(window.to_sql(), "ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW");
245	/// ```
246	pub fn frame(mut self, frame: Frame) -> Self {
247		self.frame = Some(frame);
248		self
249	}
250	/// Convert window specification to SQL
251	///
252	/// # Examples
253	///
254	/// ```
255	/// use reinhardt_db::orm::window::Window;
256	///
257	/// let window = Window::new()
258	///     .partition_by("department")
259	///     .order_by("salary DESC");
260	/// assert_eq!(window.to_sql(), "PARTITION BY department ORDER BY salary DESC");
261	/// ```
262	pub fn to_sql(&self) -> String {
263		let mut parts = Vec::new();
264
265		if !self.partition_by.is_empty() {
266			parts.push(format!("PARTITION BY {}", self.partition_by.join(", ")));
267		}
268
269		if !self.order_by.is_empty() {
270			parts.push(format!("ORDER BY {}", self.order_by.join(", ")));
271		}
272
273		if let Some(ref frame) = self.frame {
274			parts.push(frame.to_sql());
275		}
276
277		parts.join(" ")
278	}
279}
280
281impl Default for Window {
282	fn default() -> Self {
283		Self::new()
284	}
285}
286
287/// Base trait for window functions
288pub trait WindowFunction {
289	/// Converts the window function to SQL using the given window definition.
290	fn to_sql(&self, window: &Window) -> String;
291}
292
293/// ROW_NUMBER window function
294#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct RowNumber;
296
297impl RowNumber {
298	/// Create a ROW_NUMBER window function
299	///
300	/// # Examples
301	///
302	/// ```
303	/// use reinhardt_db::orm::window::{RowNumber, Window, WindowFunction};
304	///
305	/// let window = Window::new()
306	///     .partition_by("department")
307	///     .order_by("hire_date");
308	/// let row_num = RowNumber::new();
309	/// assert_eq!(
310	///     row_num.to_sql(&window),
311	///     "ROW_NUMBER() OVER (PARTITION BY department ORDER BY hire_date)"
312	/// );
313	/// ```
314	pub fn new() -> Self {
315		Self
316	}
317}
318
319impl Default for RowNumber {
320	fn default() -> Self {
321		Self::new()
322	}
323}
324
325impl WindowFunction for RowNumber {
326	fn to_sql(&self, window: &Window) -> String {
327		format!("ROW_NUMBER() OVER ({})", window.to_sql())
328	}
329}
330
331/// RANK window function
332#[derive(Debug, Clone, Serialize, Deserialize)]
333pub struct Rank;
334
335impl Rank {
336	/// Create a RANK window function
337	///
338	/// # Examples
339	///
340	/// ```
341	/// use reinhardt_db::orm::window::{Rank, Window, WindowFunction};
342	///
343	/// let window = Window::new()
344	///     .partition_by("department")
345	///     .order_by("salary DESC");
346	/// let rank = Rank::new();
347	/// assert_eq!(
348	///     rank.to_sql(&window),
349	///     "RANK() OVER (PARTITION BY department ORDER BY salary DESC)"
350	/// );
351	/// ```
352	pub fn new() -> Self {
353		Self
354	}
355}
356
357impl Default for Rank {
358	fn default() -> Self {
359		Self::new()
360	}
361}
362
363impl WindowFunction for Rank {
364	fn to_sql(&self, window: &Window) -> String {
365		format!("RANK() OVER ({})", window.to_sql())
366	}
367}
368
369/// DENSE_RANK window function
370#[derive(Debug, Clone, Serialize, Deserialize)]
371pub struct DenseRank;
372
373impl DenseRank {
374	/// Create a DENSE_RANK window function
375	///
376	/// # Examples
377	///
378	/// ```
379	/// use reinhardt_db::orm::window::{DenseRank, Window, WindowFunction};
380	///
381	/// let window = Window::new().order_by("score DESC");
382	/// let dense_rank = DenseRank::new();
383	/// assert_eq!(
384	///     dense_rank.to_sql(&window),
385	///     "DENSE_RANK() OVER (ORDER BY score DESC)"
386	/// );
387	/// ```
388	pub fn new() -> Self {
389		Self
390	}
391}
392
393impl Default for DenseRank {
394	fn default() -> Self {
395		Self::new()
396	}
397}
398
399impl WindowFunction for DenseRank {
400	fn to_sql(&self, window: &Window) -> String {
401		format!("DENSE_RANK() OVER ({})", window.to_sql())
402	}
403}
404
405/// NTILE window function
406#[derive(Debug, Clone, Serialize, Deserialize)]
407pub struct NTile {
408	/// The num buckets.
409	pub num_buckets: i64,
410}
411
412impl NTile {
413	/// Create an NTILE window function to divide rows into buckets
414	///
415	/// # Examples
416	///
417	/// ```
418	/// use reinhardt_db::orm::window::{NTile, Window, WindowFunction};
419	///
420	/// let window = Window::new().order_by("salary");
421	/// let ntile = NTile::new(4);
422	/// assert_eq!(ntile.to_sql(&window), "NTILE(4) OVER (ORDER BY salary)");
423	/// ```
424	pub fn new(num_buckets: i64) -> Self {
425		Self { num_buckets }
426	}
427}
428
429impl WindowFunction for NTile {
430	fn to_sql(&self, window: &Window) -> String {
431		format!("NTILE({}) OVER ({})", self.num_buckets, window.to_sql())
432	}
433}
434
435/// LEAD window function
436#[derive(Debug, Clone, Serialize, Deserialize)]
437pub struct Lead {
438	/// The expression.
439	pub expression: String,
440	/// The offset.
441	pub offset: i64,
442	/// The default.
443	pub default: Option<String>,
444}
445
446impl Lead {
447	/// Create a LEAD window function to access following rows
448	///
449	/// # Examples
450	///
451	/// ```
452	/// use reinhardt_db::orm::window::{Lead, Window, WindowFunction};
453	///
454	/// let window = Window::new().order_by("date");
455	/// let lead = Lead::new("value");
456	/// assert_eq!(lead.to_sql(&window), "LEAD(value, 1) OVER (ORDER BY date)");
457	/// ```
458	pub fn new(expression: impl Into<String>) -> Self {
459		Self {
460			expression: expression.into(),
461			offset: 1,
462			default: None,
463		}
464	}
465	/// Set the offset for how many rows to look ahead
466	///
467	/// # Examples
468	///
469	/// ```
470	/// use reinhardt_db::orm::window::{Lead, Window, WindowFunction};
471	///
472	/// let window = Window::new().order_by("date");
473	/// let lead = Lead::new("value").offset(2);
474	/// assert_eq!(lead.to_sql(&window), "LEAD(value, 2) OVER (ORDER BY date)");
475	/// ```
476	pub fn offset(mut self, offset: i64) -> Self {
477		self.offset = offset;
478		self
479	}
480	/// Set the default value when no following row exists
481	///
482	/// # Examples
483	///
484	/// ```
485	/// use reinhardt_db::orm::window::{Lead, Window, WindowFunction};
486	///
487	/// let window = Window::new().order_by("date");
488	/// let lead = Lead::new("value").offset(2).default("0");
489	/// assert_eq!(lead.to_sql(&window), "LEAD(value, 2, 0) OVER (ORDER BY date)");
490	/// ```
491	pub fn default(mut self, default: impl Into<String>) -> Self {
492		self.default = Some(default.into());
493		self
494	}
495}
496
497impl WindowFunction for Lead {
498	fn to_sql(&self, window: &Window) -> String {
499		let mut args = vec![self.expression.clone(), self.offset.to_string()];
500		if let Some(ref default) = self.default {
501			args.push(default.clone());
502		}
503		format!("LEAD({}) OVER ({})", args.join(", "), window.to_sql())
504	}
505}
506
507/// LAG window function
508#[derive(Debug, Clone, Serialize, Deserialize)]
509pub struct Lag {
510	/// The expression.
511	pub expression: String,
512	/// The offset.
513	pub offset: i64,
514	/// The default.
515	pub default: Option<String>,
516}
517
518impl Lag {
519	/// Create a LAG window function to access preceding rows
520	///
521	/// # Examples
522	///
523	/// ```
524	/// use reinhardt_db::orm::window::{Lag, Window, WindowFunction};
525	///
526	/// let window = Window::new().order_by("date");
527	/// let lag = Lag::new("value");
528	/// assert_eq!(lag.to_sql(&window), "LAG(value, 1) OVER (ORDER BY date)");
529	/// ```
530	pub fn new(expression: impl Into<String>) -> Self {
531		Self {
532			expression: expression.into(),
533			offset: 1,
534			default: None,
535		}
536	}
537	/// Set the offset for how many rows to look back
538	///
539	/// # Examples
540	///
541	/// ```
542	/// use reinhardt_db::orm::window::{Lag, Window, WindowFunction};
543	///
544	/// let window = Window::new().order_by("date");
545	/// let lag = Lag::new("value").offset(3);
546	/// assert_eq!(lag.to_sql(&window), "LAG(value, 3) OVER (ORDER BY date)");
547	/// ```
548	pub fn offset(mut self, offset: i64) -> Self {
549		self.offset = offset;
550		self
551	}
552	/// Set the default value when no preceding row exists
553	///
554	/// # Examples
555	///
556	/// ```
557	/// use reinhardt_db::orm::window::{Lag, Window, WindowFunction};
558	///
559	/// let window = Window::new().order_by("date");
560	/// let lag = Lag::new("value").default("0");
561	/// assert_eq!(lag.to_sql(&window), "LAG(value, 1, 0) OVER (ORDER BY date)");
562	/// ```
563	pub fn default(mut self, default: impl Into<String>) -> Self {
564		self.default = Some(default.into());
565		self
566	}
567}
568
569impl WindowFunction for Lag {
570	fn to_sql(&self, window: &Window) -> String {
571		let mut args = vec![self.expression.clone(), self.offset.to_string()];
572		if let Some(ref default) = self.default {
573			args.push(default.clone());
574		}
575		format!("LAG({}) OVER ({})", args.join(", "), window.to_sql())
576	}
577}
578
579/// FIRST_VALUE window function
580#[derive(Debug, Clone, Serialize, Deserialize)]
581pub struct FirstValue {
582	/// The expression.
583	pub expression: String,
584}
585
586impl FirstValue {
587	/// Create a FIRST_VALUE window function
588	///
589	/// # Examples
590	///
591	/// ```
592	/// use reinhardt_db::orm::window::{FirstValue, Window, WindowFunction};
593	///
594	/// let window = Window::new()
595	///     .partition_by("department")
596	///     .order_by("salary DESC");
597	/// let first_val = FirstValue::new("salary");
598	/// assert_eq!(
599	///     first_val.to_sql(&window),
600	///     "FIRST_VALUE(salary) OVER (PARTITION BY department ORDER BY salary DESC)"
601	/// );
602	/// ```
603	pub fn new(expression: impl Into<String>) -> Self {
604		Self {
605			expression: expression.into(),
606		}
607	}
608}
609
610impl WindowFunction for FirstValue {
611	fn to_sql(&self, window: &Window) -> String {
612		format!(
613			"FIRST_VALUE({}) OVER ({})",
614			self.expression,
615			window.to_sql()
616		)
617	}
618}
619
620/// LAST_VALUE window function
621#[derive(Debug, Clone, Serialize, Deserialize)]
622pub struct LastValue {
623	/// The expression.
624	pub expression: String,
625}
626
627impl LastValue {
628	/// Create a LAST_VALUE window function
629	///
630	/// # Examples
631	///
632	/// ```
633	/// use reinhardt_db::orm::window::{LastValue, Window, WindowFunction};
634	///
635	/// let window = Window::new()
636	///     .partition_by("department")
637	///     .order_by("salary DESC");
638	/// let last_val = LastValue::new("salary");
639	/// assert_eq!(
640	///     last_val.to_sql(&window),
641	///     "LAST_VALUE(salary) OVER (PARTITION BY department ORDER BY salary DESC)"
642	/// );
643	/// ```
644	pub fn new(expression: impl Into<String>) -> Self {
645		Self {
646			expression: expression.into(),
647		}
648	}
649}
650
651impl WindowFunction for LastValue {
652	fn to_sql(&self, window: &Window) -> String {
653		format!("LAST_VALUE({}) OVER ({})", self.expression, window.to_sql())
654	}
655}
656
657/// NTH_VALUE window function
658#[derive(Debug, Clone, Serialize, Deserialize)]
659pub struct NthValue {
660	/// The expression.
661	pub expression: String,
662	/// The n.
663	pub n: i64,
664}
665
666impl NthValue {
667	/// Create an NTH_VALUE window function
668	///
669	/// # Examples
670	///
671	/// ```
672	/// use reinhardt_db::orm::window::{NthValue, Window, WindowFunction};
673	///
674	/// let window = Window::new().order_by("salary DESC");
675	/// let nth_val = NthValue::new("salary", 2);
676	/// assert_eq!(
677	///     nth_val.to_sql(&window),
678	///     "NTH_VALUE(salary, 2) OVER (ORDER BY salary DESC)"
679	/// );
680	/// ```
681	pub fn new(expression: impl Into<String>, n: i64) -> Self {
682		Self {
683			expression: expression.into(),
684			n,
685		}
686	}
687}
688
689impl WindowFunction for NthValue {
690	fn to_sql(&self, window: &Window) -> String {
691		format!(
692			"NTH_VALUE({}, {}) OVER ({})",
693			self.expression,
694			self.n,
695			window.to_sql()
696		)
697	}
698}
699
700#[cfg(test)]
701mod tests {
702	use super::*;
703
704	#[test]
705	fn test_window_partition_by() {
706		let window = Window::new().partition_by("department");
707		assert_eq!(window.to_sql(), "PARTITION BY department");
708	}
709
710	#[test]
711	fn test_window_order_by() {
712		let window = Window::new().order_by("salary DESC");
713		assert_eq!(window.to_sql(), "ORDER BY salary DESC");
714	}
715
716	#[test]
717	fn test_window_partition_and_order() {
718		let window = Window::new()
719			.partition_by("department")
720			.order_by("salary DESC");
721		assert_eq!(
722			window.to_sql(),
723			"PARTITION BY department ORDER BY salary DESC"
724		);
725	}
726
727	#[test]
728	fn test_frame_rows() {
729		let frame = Frame::rows(
730			FrameBoundary::UnboundedPreceding,
731			Some(FrameBoundary::CurrentRow),
732		);
733		assert_eq!(
734			frame.to_sql(),
735			"ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
736		);
737	}
738
739	#[test]
740	fn test_frame_range() {
741		let frame = Frame::range(
742			FrameBoundary::Preceding(3),
743			Some(FrameBoundary::Following(3)),
744		);
745		assert_eq!(frame.to_sql(), "RANGE BETWEEN 3 PRECEDING AND 3 FOLLOWING");
746	}
747
748	#[test]
749	fn test_row_number() {
750		let window = Window::new()
751			.partition_by("department")
752			.order_by("hire_date");
753		let row_num = RowNumber::new();
754		assert_eq!(
755			row_num.to_sql(&window),
756			"ROW_NUMBER() OVER (PARTITION BY department ORDER BY hire_date)"
757		);
758	}
759
760	#[test]
761	fn test_rank() {
762		let window = Window::new()
763			.partition_by("department")
764			.order_by("salary DESC");
765		let rank = Rank::new();
766		assert_eq!(
767			rank.to_sql(&window),
768			"RANK() OVER (PARTITION BY department ORDER BY salary DESC)"
769		);
770	}
771
772	#[test]
773	fn test_dense_rank() {
774		let window = Window::new().order_by("score DESC");
775		let dense_rank = DenseRank::new();
776		assert_eq!(
777			dense_rank.to_sql(&window),
778			"DENSE_RANK() OVER (ORDER BY score DESC)"
779		);
780	}
781
782	#[test]
783	fn test_ntile() {
784		let window = Window::new().order_by("salary");
785		let ntile = NTile::new(4);
786		assert_eq!(ntile.to_sql(&window), "NTILE(4) OVER (ORDER BY salary)");
787	}
788
789	#[test]
790	fn test_lead() {
791		let window = Window::new().order_by("date");
792		let lead = Lead::new("value");
793		assert_eq!(lead.to_sql(&window), "LEAD(value, 1) OVER (ORDER BY date)");
794	}
795
796	#[test]
797	fn test_lead_with_offset_and_default() {
798		let window = Window::new().order_by("date");
799		let lead = Lead::new("value").offset(2).default("0");
800		assert_eq!(
801			lead.to_sql(&window),
802			"LEAD(value, 2, 0) OVER (ORDER BY date)"
803		);
804	}
805
806	#[test]
807	fn test_lag() {
808		let window = Window::new().order_by("date");
809		let lag = Lag::new("value");
810		assert_eq!(lag.to_sql(&window), "LAG(value, 1) OVER (ORDER BY date)");
811	}
812
813	#[test]
814	fn test_lag_with_offset() {
815		let window = Window::new().order_by("date");
816		let lag = Lag::new("value").offset(3);
817		assert_eq!(lag.to_sql(&window), "LAG(value, 3) OVER (ORDER BY date)");
818	}
819
820	#[test]
821	fn test_first_value() {
822		let window = Window::new()
823			.partition_by("department")
824			.order_by("salary DESC");
825		let first_val = FirstValue::new("salary");
826		assert_eq!(
827			first_val.to_sql(&window),
828			"FIRST_VALUE(salary) OVER (PARTITION BY department ORDER BY salary DESC)"
829		);
830	}
831
832	#[test]
833	fn test_last_value() {
834		let window = Window::new()
835			.partition_by("department")
836			.order_by("salary DESC");
837		let last_val = LastValue::new("salary");
838		assert_eq!(
839			last_val.to_sql(&window),
840			"LAST_VALUE(salary) OVER (PARTITION BY department ORDER BY salary DESC)"
841		);
842	}
843
844	#[test]
845	fn test_nth_value() {
846		let window = Window::new().order_by("salary DESC");
847		let nth_val = NthValue::new("salary", 2);
848		assert_eq!(
849			nth_val.to_sql(&window),
850			"NTH_VALUE(salary, 2) OVER (ORDER BY salary DESC)"
851		);
852	}
853
854	#[test]
855	fn test_window_with_frame() {
856		let frame = Frame::rows(
857			FrameBoundary::Preceding(1),
858			Some(FrameBoundary::Following(1)),
859		);
860		let window = Window::new()
861			.partition_by("department")
862			.order_by("date")
863			.frame(frame);
864		assert_eq!(
865			window.to_sql(),
866			"PARTITION BY department ORDER BY date ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING"
867		);
868	}
869}