sqlx_paginated/paginated_query_as/builders/
paginated_query_builder.rs1use crate::paginated_query_as::examples::postgres_examples::build_query_with_safe_defaults;
2use crate::paginated_query_as::internal::quote_identifier;
3use crate::paginated_query_as::models::QuerySortDirection;
4use crate::{FlatQueryParams, PaginatedResponse, QueryParams};
5use serde::Serialize;
6use sqlx::postgres::PgArguments;
7use sqlx::{postgres::Postgres, query::QueryAs, Execute, FromRow, IntoArguments, Pool};
8
9pub struct PaginatedQueryBuilder<'q, T, A>
10where
11 T: for<'r> FromRow<'r, <Postgres as sqlx::Database>::Row> + Send + Unpin,
12{
13 query: QueryAs<'q, Postgres, T, A>,
14 params: QueryParams<'q, T>,
15 totals_count_enabled: bool,
16 build_query_fn: fn(&QueryParams<T>) -> (Vec<String>, PgArguments),
17}
18
19impl<'q, T, A> PaginatedQueryBuilder<'q, T, A>
37where
38 T: for<'r> FromRow<'r, <Postgres as sqlx::Database>::Row> + Send + Unpin + Serialize + Default,
39 A: 'q + IntoArguments<'q, Postgres> + Send,
40{
41 pub fn new(query: QueryAs<'q, Postgres, T, A>) -> Self {
68 Self {
69 query,
70 params: FlatQueryParams::default().into(),
71 totals_count_enabled: true,
72 build_query_fn: |params| build_query_with_safe_defaults::<T, Postgres>(params),
73 }
74 }
75
76 pub fn with_query_builder(
77 self,
78 build_query_fn: fn(&QueryParams<T>) -> (Vec<String>, PgArguments),
79 ) -> Self {
80 Self {
81 build_query_fn,
82 ..self
83 }
84 }
85
86 pub fn with_params(mut self, params: impl Into<QueryParams<'q, T>>) -> Self {
87 self.params = params.into();
88 self
89 }
90
91 pub fn disable_totals_count(mut self) -> Self {
101 self.totals_count_enabled = false;
102 self
103 }
104
105 pub async fn fetch_paginated(
122 self,
123 pool: &Pool<Postgres>,
124 ) -> Result<PaginatedResponse<T>, sqlx::Error> {
125 let base_sql = self.build_base_query();
126 let (conditions, main_arguments) = (self.build_query_fn)(&self.params);
127 let where_clause = self.build_where_clause(&conditions);
128
129 let (total, total_pages, pagination) = if self.totals_count_enabled {
130 let (_, count_arguments) = (self.build_query_fn)(&self.params);
131 let pagination_arguments = self.params.pagination.clone();
132
133 let count_sql = format!(
134 "{} SELECT COUNT(*) FROM base_query{}",
135 base_sql, where_clause
136 );
137 let count: i64 = sqlx::query_scalar_with(&count_sql, count_arguments)
138 .fetch_one(pool)
139 .await?;
140
141 let available_pages = match count {
142 0 => 0,
143 _ => (count + pagination_arguments.page_size - 1) / pagination_arguments.page_size,
144 };
145
146 (
147 Some(count),
148 Some(available_pages),
149 Some(pagination_arguments),
150 )
151 } else {
152 (None, None, None)
153 };
154
155 let mut main_sql = format!("{} SELECT * FROM base_query{}", base_sql, where_clause);
156
157 main_sql.push_str(&self.build_order_clause());
158 main_sql.push_str(&self.build_limit_offset_clause());
159
160 let records = sqlx::query_as_with::<Postgres, T, _>(&main_sql, main_arguments)
161 .fetch_all(pool)
162 .await?;
163
164 Ok(PaginatedResponse {
165 records,
166 pagination,
167 total,
168 total_pages,
169 })
170 }
171
172 fn build_base_query(&self) -> String {
178 format!("WITH base_query AS ({})", self.query.sql())
179 }
180
181 fn build_where_clause(&self, conditions: &[String]) -> String {
191 if conditions.is_empty() {
192 String::new()
193 } else {
194 format!(" WHERE {}", conditions.join(" AND "))
195 }
196 }
197
198 fn build_order_clause(&self) -> String {
204 let order = match self.params.sort.sort_direction {
205 QuerySortDirection::Ascending => "ASC",
206 QuerySortDirection::Descending => "DESC",
207 };
208 let column_name = quote_identifier(&self.params.sort.sort_column);
209
210 format!(" ORDER BY {} {}", column_name, order)
211 }
212
213 fn build_limit_offset_clause(&self) -> String {
214 let pagination = &self.params.pagination;
215 let offset = (pagination.page - 1) * pagination.page_size;
216
217 format!(" LIMIT {} OFFSET {}", pagination.page_size, offset)
218 }
219}