Skip to main content

sea_query_hyperdb/
lib.rs

1// Copyright (c) 2026, Salesforce, Inc. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! `HyperDB` SQL dialect backend for [sea-query](https://crates.io/crates/sea-query).
5//!
6//! This crate provides [`HyperQueryBuilder`], a sea-query backend that generates SQL
7//! compatible with `HyperDB`'s SQL dialect. `HyperDB` is largely PostgreSQL-compatible, so
8//! this builder currently delegates to [`PostgresQueryBuilder`] for all operations.
9//!
10//! As Hyper's SQL dialect evolves and diverges from `PostgreSQL`, this builder will be
11//! updated to emit Hyper-specific syntax where needed (e.g., type names, function calls,
12//! or DDL extensions).
13//!
14//! # Usage
15//!
16//! `HyperQueryBuilder` implements all sea-query backend traits ([`QueryBuilder`],
17//! [`SchemaBuilder`], [`GenericBuilder`]), so it can be used anywhere a sea-query
18//! backend is accepted:
19//!
20//! ```rust
21//! use sea_query::{Query, Expr, ExprTrait, Iden, PostgresQueryBuilder};
22//! use sea_query_hyperdb::HyperQueryBuilder;
23//!
24//! #[derive(Iden)]
25//! enum Users {
26//!     Table,
27//!     Id,
28//!     Name,
29//! }
30//!
31//! let query = Query::select()
32//!     .column(Users::Id)
33//!     .column(Users::Name)
34//!     .from(Users::Table)
35//!     .and_where(Expr::col(Users::Id).gt(10))
36//!     .to_owned();
37//!
38//! // Build with parameter placeholders ($1, $2, ...)
39//! let (sql, values) = query.build(HyperQueryBuilder);
40//! assert!(sql.contains("SELECT"));
41//!
42//! // Or get the SQL string with values inlined
43//! let sql_string = query.to_string(HyperQueryBuilder);
44//! assert!(sql_string.contains("WHERE"));
45//! ```
46//!
47//! # With hyperdb-api
48//!
49//! ```rust,ignore
50//! use hyperdb_api::Connection;
51//! use sea_query::{Query, Expr, Iden};
52//! use sea_query_hyperdb::HyperQueryBuilder;
53//!
54//! #[derive(Iden)]
55//! enum Products {
56//!     Table,
57//!     Name,
58//!     Price,
59//! }
60//!
61//! let query = Query::select()
62//!     .column(Products::Name)
63//!     .column(Products::Price)
64//!     .from(Products::Table)
65//!     .and_where(Expr::col(Products::Price).gt(100))
66//!     .to_owned();
67//!
68//! let sql = query.to_string(HyperQueryBuilder);
69//! let result = conn.execute_query(&sql)?;
70//! ```
71
72use sea_query::backend::{
73    EscapeBuilder, ForeignKeyBuilder, GenericBuilder, IndexBuilder, OperLeftAssocDecider,
74    PrecedenceDecider, QueryBuilder, QuotedBuilder, SchemaBuilder, TableBuilder, TableRefBuilder,
75};
76use sea_query::{
77    BinOper, ColumnDef, ColumnType, ExplainStatement, Expr, ForeignKeyCreateStatement,
78    ForeignKeyDropStatement, IndexCreateStatement, IndexDropStatement, Oper, PostgresQueryBuilder,
79    Quote, SelectInto, SubQueryStatement, TableAlterStatement, TableRef, TableRenameStatement,
80    Value,
81};
82
83/// HyperDB-specific SQL dialect backend for sea-query.
84///
85/// Generates SQL compatible with `HyperDB`'s dialect, as documented at
86/// <https://developer.salesforce.com/docs/data/data-cloud-query-guide/references/dc-sql-reference/select.html>.
87///
88/// # Delegation Strategy
89///
90/// `HyperDB`'s SQL dialect is largely PostgreSQL-compatible — identifier quoting,
91/// placeholder syntax (`$1, $2, ...`), operator precedence, and DDL/DML syntax
92/// all match `PostgreSQL`. Rather than duplicating that logic, this builder
93/// delegates to [`PostgresQueryBuilder`] for all operations.
94///
95/// As Hyper's dialect evolves (e.g., Hyper-specific type names, functions, or
96/// DDL extensions), individual trait methods will be overridden to emit
97/// Hyper-specific syntax while the rest continues to delegate.
98///
99/// Notable `HyperDB` SQL features:
100/// - Optional `FROM` clause for simple expressions
101/// - `DISTINCT ON` extension
102/// - Standard `UNION`, `INTERSECT`, `EXCEPT` operations
103/// - Zero-column result tables
104///
105/// # Examples
106///
107/// ```rust
108/// use sea_query::{Query, Expr, ExprTrait, Iden};
109/// use sea_query_hyperdb::HyperQueryBuilder;
110///
111/// #[derive(Iden)]
112/// enum Users {
113///     Table,
114///     Name,
115///     Age,
116/// }
117///
118/// let sql = Query::select()
119///     .column(Users::Name)
120///     .from(Users::Table)
121///     .and_where(Expr::col(Users::Age).gt(18))
122///     .to_string(HyperQueryBuilder);
123///
124/// assert!(sql.contains(r#""users""#));
125/// ```
126#[derive(Debug, Clone, Copy, Default)]
127pub struct HyperQueryBuilder;
128
129impl QuotedBuilder for HyperQueryBuilder {
130    fn quote(&self) -> Quote {
131        PostgresQueryBuilder.quote()
132    }
133}
134
135impl EscapeBuilder for HyperQueryBuilder {}
136
137impl TableRefBuilder for HyperQueryBuilder {}
138
139impl OperLeftAssocDecider for HyperQueryBuilder {
140    fn well_known_left_associative(&self, op: &BinOper) -> bool {
141        PostgresQueryBuilder.well_known_left_associative(op)
142    }
143}
144
145impl PrecedenceDecider for HyperQueryBuilder {
146    fn inner_expr_well_known_greater_precedence(&self, inner: &Expr, outer_oper: &Oper) -> bool {
147        PostgresQueryBuilder.inner_expr_well_known_greater_precedence(inner, outer_oper)
148    }
149}
150
151impl QueryBuilder for HyperQueryBuilder {
152    fn prepare_query_statement(
153        &self,
154        query: &SubQueryStatement,
155        sql: &mut impl sea_query::SqlWriter,
156    ) {
157        PostgresQueryBuilder.prepare_query_statement(query, sql);
158    }
159
160    fn prepare_select_into(&self, into_table: &SelectInto, sql: &mut impl sea_query::SqlWriter) {
161        PostgresQueryBuilder.prepare_select_into(into_table, sql);
162    }
163
164    fn prepare_explain_statement(
165        &self,
166        explain: &ExplainStatement,
167        sql: &mut impl sea_query::SqlWriter,
168    ) {
169        PostgresQueryBuilder.prepare_explain_statement(explain, sql);
170    }
171
172    fn prepare_value(&self, value: Value, sql: &mut impl sea_query::SqlWriter) {
173        PostgresQueryBuilder.prepare_value(value, sql);
174    }
175
176    fn placeholder(&self) -> (&'static str, bool) {
177        PostgresQueryBuilder.placeholder()
178    }
179}
180
181impl ForeignKeyBuilder for HyperQueryBuilder {
182    fn prepare_table_ref_fk_stmt(&self, table_ref: &TableRef, sql: &mut impl sea_query::SqlWriter) {
183        PostgresQueryBuilder.prepare_table_ref_fk_stmt(table_ref, sql);
184    }
185
186    fn prepare_foreign_key_create_statement_internal(
187        &self,
188        create: &ForeignKeyCreateStatement,
189        sql: &mut impl sea_query::SqlWriter,
190        mode: sea_query::backend::Mode,
191    ) {
192        PostgresQueryBuilder.prepare_foreign_key_create_statement_internal(create, sql, mode);
193    }
194
195    fn prepare_foreign_key_drop_statement_internal(
196        &self,
197        drop: &ForeignKeyDropStatement,
198        sql: &mut impl sea_query::SqlWriter,
199        mode: sea_query::backend::Mode,
200    ) {
201        PostgresQueryBuilder.prepare_foreign_key_drop_statement_internal(drop, sql, mode);
202    }
203}
204
205impl IndexBuilder for HyperQueryBuilder {
206    fn prepare_index_create_statement(
207        &self,
208        create: &IndexCreateStatement,
209        sql: &mut impl sea_query::SqlWriter,
210    ) {
211        PostgresQueryBuilder.prepare_index_create_statement(create, sql);
212    }
213
214    fn prepare_table_ref_index_stmt(
215        &self,
216        table_ref: &TableRef,
217        sql: &mut impl sea_query::SqlWriter,
218    ) {
219        PostgresQueryBuilder.prepare_table_ref_index_stmt(table_ref, sql);
220    }
221
222    fn prepare_index_drop_statement(
223        &self,
224        drop: &IndexDropStatement,
225        sql: &mut impl sea_query::SqlWriter,
226    ) {
227        PostgresQueryBuilder.prepare_index_drop_statement(drop, sql);
228    }
229
230    fn prepare_index_prefix(
231        &self,
232        create: &IndexCreateStatement,
233        sql: &mut impl sea_query::SqlWriter,
234    ) {
235        PostgresQueryBuilder.prepare_index_prefix(create, sql);
236    }
237}
238
239impl TableBuilder for HyperQueryBuilder {
240    fn prepare_column_def(&self, column_def: &ColumnDef, sql: &mut impl sea_query::SqlWriter) {
241        PostgresQueryBuilder.prepare_column_def(column_def, sql);
242    }
243
244    fn prepare_column_type(&self, column_type: &ColumnType, sql: &mut impl sea_query::SqlWriter) {
245        PostgresQueryBuilder.prepare_column_type(column_type, sql);
246    }
247
248    fn column_spec_auto_increment_keyword(&self) -> &str {
249        PostgresQueryBuilder.column_spec_auto_increment_keyword()
250    }
251
252    fn prepare_table_alter_statement(
253        &self,
254        alter: &TableAlterStatement,
255        sql: &mut impl sea_query::SqlWriter,
256    ) {
257        PostgresQueryBuilder.prepare_table_alter_statement(alter, sql);
258    }
259
260    fn prepare_table_rename_statement(
261        &self,
262        rename: &TableRenameStatement,
263        sql: &mut impl sea_query::SqlWriter,
264    ) {
265        PostgresQueryBuilder.prepare_table_rename_statement(rename, sql);
266    }
267}
268
269impl SchemaBuilder for HyperQueryBuilder {}
270
271impl GenericBuilder for HyperQueryBuilder {}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276    use sea_query::{ExprTrait, Iden, Query};
277
278    #[derive(Iden)]
279    enum Users {
280        Table,
281        Id,
282        Name,
283        Age,
284    }
285
286    #[test]
287    fn select_generates_valid_sql() {
288        let query = Query::select()
289            .column(Users::Name)
290            .from(Users::Table)
291            .and_where(Expr::col(Users::Age).gt(18))
292            .to_owned();
293
294        let sql = query.to_string(HyperQueryBuilder);
295        assert_eq!(
296            sql,
297            query.to_string(PostgresQueryBuilder),
298            "HyperQueryBuilder should produce identical SQL to PostgresQueryBuilder"
299        );
300    }
301
302    #[test]
303    fn select_build_produces_placeholders() {
304        let query = Query::select()
305            .column(Users::Name)
306            .from(Users::Table)
307            .and_where(Expr::col(Users::Age).gt(18))
308            .to_owned();
309
310        let (sql, values) = query.build(HyperQueryBuilder);
311        let (pg_sql, pg_values) = query.build(PostgresQueryBuilder);
312        assert_eq!(sql, pg_sql);
313        assert_eq!(format!("{values:?}"), format!("{pg_values:?}"));
314    }
315
316    #[test]
317    fn insert_generates_valid_sql() {
318        let query = Query::insert()
319            .into_table(Users::Table)
320            .columns([Users::Name, Users::Age])
321            .values_panic(["Alice".into(), 30i32.into()])
322            .to_owned();
323
324        let sql = query.to_string(HyperQueryBuilder);
325        assert_eq!(sql, query.to_string(PostgresQueryBuilder));
326    }
327
328    #[test]
329    fn update_generates_valid_sql() {
330        let query = Query::update()
331            .table(Users::Table)
332            .value(Users::Name, "Bob")
333            .and_where(Expr::col(Users::Id).eq(1))
334            .to_owned();
335
336        let sql = query.to_string(HyperQueryBuilder);
337        assert_eq!(sql, query.to_string(PostgresQueryBuilder));
338    }
339
340    #[test]
341    fn delete_generates_valid_sql() {
342        let query = Query::delete()
343            .from_table(Users::Table)
344            .and_where(Expr::col(Users::Id).eq(1))
345            .to_owned();
346
347        let sql = query.to_string(HyperQueryBuilder);
348        assert_eq!(sql, query.to_string(PostgresQueryBuilder));
349    }
350
351    #[test]
352    fn create_table_generates_valid_sql() {
353        use sea_query::{ColumnDef, Table};
354
355        let stmt = Table::create()
356            .table(Users::Table)
357            .if_not_exists()
358            .col(ColumnDef::new(Users::Id).integer().not_null().primary_key())
359            .col(ColumnDef::new(Users::Name).string().not_null())
360            .col(ColumnDef::new(Users::Age).integer())
361            .to_owned();
362
363        let sql = stmt.to_string(HyperQueryBuilder);
364        assert_eq!(sql, stmt.to_string(PostgresQueryBuilder));
365    }
366
367    #[test]
368    fn default_trait() {
369        let builder = HyperQueryBuilder;
370        // Touch `builder` so the test body demonstrates the unit-struct is constructible.
371        let _ = builder;
372    }
373}