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, 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, ForeignKeyCreateStatement, ForeignKeyDropStatement,
78    IndexCreateStatement, IndexDropStatement, Oper, PostgresQueryBuilder, Quote, SimpleExpr,
79    SubQueryStatement, TableAlterStatement, TableRef, TableRenameStatement, Value,
80};
81
82/// HyperDB-specific SQL dialect backend for sea-query.
83///
84/// Generates SQL compatible with `HyperDB`'s dialect, as documented at
85/// <https://developer.salesforce.com/docs/data/data-cloud-query-guide/references/dc-sql-reference/select.html>.
86///
87/// # Delegation Strategy
88///
89/// `HyperDB`'s SQL dialect is largely PostgreSQL-compatible — identifier quoting,
90/// placeholder syntax (`$1, $2, ...`), operator precedence, and DDL/DML syntax
91/// all match `PostgreSQL`. Rather than duplicating that logic, this builder
92/// delegates to [`PostgresQueryBuilder`] for all operations.
93///
94/// As Hyper's dialect evolves (e.g., Hyper-specific type names, functions, or
95/// DDL extensions), individual trait methods will be overridden to emit
96/// Hyper-specific syntax while the rest continues to delegate.
97///
98/// Notable `HyperDB` SQL features:
99/// - Optional `FROM` clause for simple expressions
100/// - `DISTINCT ON` extension
101/// - Standard `UNION`, `INTERSECT`, `EXCEPT` operations
102/// - Zero-column result tables
103///
104/// # Examples
105///
106/// ```rust
107/// use sea_query::{Query, Expr, Iden};
108/// use sea_query_hyperdb::HyperQueryBuilder;
109///
110/// #[derive(Iden)]
111/// enum Users {
112///     Table,
113///     Name,
114///     Age,
115/// }
116///
117/// let sql = Query::select()
118///     .column(Users::Name)
119///     .from(Users::Table)
120///     .and_where(Expr::col(Users::Age).gt(18))
121///     .to_string(HyperQueryBuilder);
122///
123/// assert!(sql.contains(r#""users""#));
124/// ```
125#[derive(Debug, Clone, Copy, Default)]
126pub struct HyperQueryBuilder;
127
128impl QuotedBuilder for HyperQueryBuilder {
129    fn quote(&self) -> Quote {
130        PostgresQueryBuilder.quote()
131    }
132}
133
134impl EscapeBuilder for HyperQueryBuilder {}
135
136impl TableRefBuilder for HyperQueryBuilder {}
137
138impl OperLeftAssocDecider for HyperQueryBuilder {
139    fn well_known_left_associative(&self, op: &BinOper) -> bool {
140        PostgresQueryBuilder.well_known_left_associative(op)
141    }
142}
143
144impl PrecedenceDecider for HyperQueryBuilder {
145    fn inner_expr_well_known_greater_precedence(
146        &self,
147        inner: &SimpleExpr,
148        outer_oper: &Oper,
149    ) -> bool {
150        PostgresQueryBuilder.inner_expr_well_known_greater_precedence(inner, outer_oper)
151    }
152}
153
154impl QueryBuilder for HyperQueryBuilder {
155    fn prepare_query_statement(
156        &self,
157        query: &SubQueryStatement,
158        sql: &mut dyn sea_query::SqlWriter,
159    ) {
160        PostgresQueryBuilder.prepare_query_statement(query, sql);
161    }
162
163    fn prepare_value(&self, value: &Value, sql: &mut dyn sea_query::SqlWriter) {
164        PostgresQueryBuilder.prepare_value(value, sql);
165    }
166
167    fn placeholder(&self) -> (&str, bool) {
168        PostgresQueryBuilder.placeholder()
169    }
170}
171
172impl ForeignKeyBuilder for HyperQueryBuilder {
173    fn prepare_table_ref_fk_stmt(&self, table_ref: &TableRef, sql: &mut dyn sea_query::SqlWriter) {
174        PostgresQueryBuilder.prepare_table_ref_fk_stmt(table_ref, sql);
175    }
176
177    fn prepare_foreign_key_create_statement_internal(
178        &self,
179        create: &ForeignKeyCreateStatement,
180        sql: &mut dyn sea_query::SqlWriter,
181        mode: sea_query::backend::Mode,
182    ) {
183        PostgresQueryBuilder.prepare_foreign_key_create_statement_internal(create, sql, mode);
184    }
185
186    fn prepare_foreign_key_drop_statement_internal(
187        &self,
188        drop: &ForeignKeyDropStatement,
189        sql: &mut dyn sea_query::SqlWriter,
190        mode: sea_query::backend::Mode,
191    ) {
192        PostgresQueryBuilder.prepare_foreign_key_drop_statement_internal(drop, sql, mode);
193    }
194}
195
196impl IndexBuilder for HyperQueryBuilder {
197    fn prepare_index_create_statement(
198        &self,
199        create: &IndexCreateStatement,
200        sql: &mut dyn sea_query::SqlWriter,
201    ) {
202        PostgresQueryBuilder.prepare_index_create_statement(create, sql);
203    }
204
205    fn prepare_table_ref_index_stmt(
206        &self,
207        table_ref: &TableRef,
208        sql: &mut dyn sea_query::SqlWriter,
209    ) {
210        PostgresQueryBuilder.prepare_table_ref_index_stmt(table_ref, sql);
211    }
212
213    fn prepare_index_drop_statement(
214        &self,
215        drop: &IndexDropStatement,
216        sql: &mut dyn sea_query::SqlWriter,
217    ) {
218        PostgresQueryBuilder.prepare_index_drop_statement(drop, sql);
219    }
220
221    fn prepare_index_prefix(
222        &self,
223        create: &IndexCreateStatement,
224        sql: &mut dyn sea_query::SqlWriter,
225    ) {
226        PostgresQueryBuilder.prepare_index_prefix(create, sql);
227    }
228}
229
230impl TableBuilder for HyperQueryBuilder {
231    fn prepare_column_def(&self, column_def: &ColumnDef, sql: &mut dyn sea_query::SqlWriter) {
232        PostgresQueryBuilder.prepare_column_def(column_def, sql);
233    }
234
235    fn prepare_column_type(&self, column_type: &ColumnType, sql: &mut dyn sea_query::SqlWriter) {
236        PostgresQueryBuilder.prepare_column_type(column_type, sql);
237    }
238
239    fn column_spec_auto_increment_keyword(&self) -> &str {
240        PostgresQueryBuilder.column_spec_auto_increment_keyword()
241    }
242
243    fn prepare_table_alter_statement(
244        &self,
245        alter: &TableAlterStatement,
246        sql: &mut dyn sea_query::SqlWriter,
247    ) {
248        PostgresQueryBuilder.prepare_table_alter_statement(alter, sql);
249    }
250
251    fn prepare_table_rename_statement(
252        &self,
253        rename: &TableRenameStatement,
254        sql: &mut dyn sea_query::SqlWriter,
255    ) {
256        PostgresQueryBuilder.prepare_table_rename_statement(rename, sql);
257    }
258}
259
260impl SchemaBuilder for HyperQueryBuilder {}
261
262impl GenericBuilder for HyperQueryBuilder {}
263
264#[cfg(test)]
265mod tests {
266    use super::*;
267    use sea_query::{Expr, Iden, Query};
268
269    #[derive(Iden)]
270    enum Users {
271        Table,
272        Id,
273        Name,
274        Age,
275    }
276
277    #[test]
278    fn select_generates_valid_sql() {
279        let query = Query::select()
280            .column(Users::Name)
281            .from(Users::Table)
282            .and_where(Expr::col(Users::Age).gt(18))
283            .to_owned();
284
285        let sql = query.to_string(HyperQueryBuilder);
286        assert_eq!(
287            sql,
288            query.to_string(PostgresQueryBuilder),
289            "HyperQueryBuilder should produce identical SQL to PostgresQueryBuilder"
290        );
291    }
292
293    #[test]
294    fn select_build_produces_placeholders() {
295        let query = Query::select()
296            .column(Users::Name)
297            .from(Users::Table)
298            .and_where(Expr::col(Users::Age).gt(18))
299            .to_owned();
300
301        let (sql, values) = query.build(HyperQueryBuilder);
302        let (pg_sql, pg_values) = query.build(PostgresQueryBuilder);
303        assert_eq!(sql, pg_sql);
304        assert_eq!(format!("{values:?}"), format!("{pg_values:?}"));
305    }
306
307    #[test]
308    fn insert_generates_valid_sql() {
309        let query = Query::insert()
310            .into_table(Users::Table)
311            .columns([Users::Name, Users::Age])
312            .values_panic(["Alice".into(), 30i32.into()])
313            .to_owned();
314
315        let sql = query.to_string(HyperQueryBuilder);
316        assert_eq!(sql, query.to_string(PostgresQueryBuilder));
317    }
318
319    #[test]
320    fn update_generates_valid_sql() {
321        let query = Query::update()
322            .table(Users::Table)
323            .value(Users::Name, "Bob")
324            .and_where(Expr::col(Users::Id).eq(1))
325            .to_owned();
326
327        let sql = query.to_string(HyperQueryBuilder);
328        assert_eq!(sql, query.to_string(PostgresQueryBuilder));
329    }
330
331    #[test]
332    fn delete_generates_valid_sql() {
333        let query = Query::delete()
334            .from_table(Users::Table)
335            .and_where(Expr::col(Users::Id).eq(1))
336            .to_owned();
337
338        let sql = query.to_string(HyperQueryBuilder);
339        assert_eq!(sql, query.to_string(PostgresQueryBuilder));
340    }
341
342    #[test]
343    fn create_table_generates_valid_sql() {
344        use sea_query::{ColumnDef, Table};
345
346        let stmt = Table::create()
347            .table(Users::Table)
348            .if_not_exists()
349            .col(ColumnDef::new(Users::Id).integer().not_null().primary_key())
350            .col(ColumnDef::new(Users::Name).string().not_null())
351            .col(ColumnDef::new(Users::Age).integer())
352            .to_owned();
353
354        let sql = stmt.to_string(HyperQueryBuilder);
355        assert_eq!(sql, stmt.to_string(PostgresQueryBuilder));
356    }
357
358    #[test]
359    fn default_trait() {
360        let builder = HyperQueryBuilder;
361        // Touch `builder` so the test body demonstrates the unit-struct is constructible.
362        let _ = builder;
363    }
364}