Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/api-reference.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: API Reference
nav_order: 11
nav_order: 10
description: "Complete public method tables for every class and interface in JavaQueryBuilder"
---

Expand All @@ -24,7 +24,7 @@
**Static factory methods**

| Method | Returns | Description |
|--------|---------|-------------|

Check failure on line 27 in docs/api-reference.md

View workflow job for this annotation

GitHub Actions / Validate Markdown

Table column style

docs/api-reference.md:27:34 MD060/table-column-style Table column style [Table pipe is missing space to the left for style "compact"] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md060.md

Check failure on line 27 in docs/api-reference.md

View workflow job for this annotation

GitHub Actions / Validate Markdown

Table column style

docs/api-reference.md:27:20 MD060/table-column-style Table column style [Table pipe is missing space to the right for style "compact"] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md060.md

Check failure on line 27 in docs/api-reference.md

View workflow job for this annotation

GitHub Actions / Validate Markdown

Table column style

docs/api-reference.md:27:20 MD060/table-column-style Table column style [Table pipe is missing space to the left for style "compact"] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md060.md

Check failure on line 27 in docs/api-reference.md

View workflow job for this annotation

GitHub Actions / Validate Markdown

Table column style

docs/api-reference.md:27:10 MD060/table-column-style Table column style [Table pipe is missing space to the right for style "compact"] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md060.md

Check failure on line 27 in docs/api-reference.md

View workflow job for this annotation

GitHub Actions / Validate Markdown

Table column style

docs/api-reference.md:27:10 MD060/table-column-style Table column style [Table pipe is missing space to the left for style "compact"] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md060.md

Check failure on line 27 in docs/api-reference.md

View workflow job for this annotation

GitHub Actions / Validate Markdown

Table column style

docs/api-reference.md:27:1 MD060/table-column-style Table column style [Table pipe is missing space to the right for style "compact"] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md060.md
| `insert()` | `InsertBuilder` | New `InsertBuilder` |
| `insertInto(String table)` | `InsertBuilder` | New `InsertBuilder` pre-set to `table` |
| `update()` | `UpdateBuilder` | New `UpdateBuilder` |
Expand All @@ -37,7 +37,7 @@
**SELECT builder methods**

| Method | Returns | Description |
|--------|---------|-------------|

Check failure on line 40 in docs/api-reference.md

View workflow job for this annotation

GitHub Actions / Validate Markdown

Table column style

docs/api-reference.md:40:20 MD060/table-column-style Table column style [Table pipe is missing space to the left for style "compact"] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md060.md

Check failure on line 40 in docs/api-reference.md

View workflow job for this annotation

GitHub Actions / Validate Markdown

Table column style

docs/api-reference.md:40:10 MD060/table-column-style Table column style [Table pipe is missing space to the right for style "compact"] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md060.md

Check failure on line 40 in docs/api-reference.md

View workflow job for this annotation

GitHub Actions / Validate Markdown

Table column style

docs/api-reference.md:40:10 MD060/table-column-style Table column style [Table pipe is missing space to the left for style "compact"] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md060.md

Check failure on line 40 in docs/api-reference.md

View workflow job for this annotation

GitHub Actions / Validate Markdown

Table column style

docs/api-reference.md:40:1 MD060/table-column-style Table column style [Table pipe is missing space to the right for style "compact"] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md060.md
| `from(String table)` | `QueryBuilder` | Set source table |
| `select(String... columns)` | `QueryBuilder` | Add columns to SELECT clause; omit for `SELECT *` |
| `distinct()` | `QueryBuilder` | Add `DISTINCT` to SELECT |
Expand Down
6 changes: 5 additions & 1 deletion docs/conditions.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Conditions
nav_order: 5
nav_order: 4
description: "Operators, Condition, ConditionEntry, Connector AND/OR, and the orWhere* pattern"
---

Expand Down Expand Up @@ -50,6 +50,8 @@ conditions use `AND` by default; call the `orWhere*` variant to use `OR`.
| `BETWEEN` | `col BETWEEN ? AND ?` | Inclusive range; value is a two-element `List<?>` |
| `EXISTS_SUBQUERY` | `EXISTS (SELECT ...)` | Value is a `Query`; column is `null` |
| `NOT_EXISTS_SUBQUERY` | `NOT EXISTS (SELECT ...)` | Value is a `Query`; column is `null` |
| `ILIKE` | `col ILIKE ?` | Case-insensitive substring match — **PostgreSQL only** |
| `NOT_ILIKE` | `col NOT ILIKE ?` | Negated case-insensitive match — **PostgreSQL only** |

---

Expand All @@ -69,6 +71,8 @@ constant and the SQL it generates.
| `whereLessThanOrEquals(col, val)` | `LTE` | `col <= ?` |
| `whereLike(col, val)` | `LIKE` | `col LIKE ?` |
| `whereNotLike(col, val)` | `NOT_LIKE` | `col NOT LIKE ?` |
| `whereILike(col, val)` | `ILIKE` | `col ILIKE ?` (PostgreSQL only) |
| `orWhereILike(col, val)` | `ILIKE` | `OR col ILIKE ?` (PostgreSQL only) |
| `whereNull(col)` | `IS_NULL` | `col IS NULL` |
| `whereNotNull(col)` | `IS_NOT_NULL` | `col IS NOT NULL` |
| `whereExists(col)` | `EXISTS` | `col IS NOT NULL` |
Expand Down
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Configuration
nav_order: 8
nav_order: 7
description: "QueryBuilderDefaults: global and per-query preset for dialect, columns, limit, offset, and LIKE wrapping"
---

Expand Down
106 changes: 106 additions & 0 deletions docs/dialects/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
title: SQL Dialects
nav_order: 6
has_children: true
permalink: /sql-dialects/
description: "SqlDialect, SqlResult, AbstractSqlDialect, and the dialect comparison matrix"
---

# SQL Dialects
{: .no_toc }

## Table of contents
{: .no_toc .text-delta }

1. TOC
{:toc}

---

## Overview

`SqlDialect` is a strategy interface that converts a `Query` object into a
parameterized `SqlResult`. Four built-in dialects are provided as constants
on the interface:

| Constant | Page | Identifier quoting | DELETE LIMIT | ILIKE | RETURNING |
|----------|------|--------------------|--------------|-------|-----------|
| `SqlDialect.STANDARD` | [STANDARD]({{ site.baseurl }}/sql-dialects/standard/) | None (ANSI) | No | No | No |
| `SqlDialect.MYSQL` | [MySQL]({{ site.baseurl }}/sql-dialects/mysql/) | Back-tick `` ` `` | Yes | No | No |
| `SqlDialect.SQLITE` | [SQLite]({{ site.baseurl }}/sql-dialects/sqlite/) | Double-quote `"` | Yes | No | No |
| `SqlDialect.POSTGRESQL` | [PostgreSQL]({{ site.baseurl }}/sql-dialects/postgresql/) | Double-quote `"` | No | Yes | Yes (DELETE) |

---

## Dialect matrix

The same `Query` produces different SQL across dialects due to identifier
quoting:

| Feature | STANDARD | MYSQL | SQLITE | POSTGRESQL |
|---------|----------|-------|--------|------------|
| Table quoting | `users` | `` `users` `` | `"users"` | `"users"` |
| Column quoting | `id` | `` `id` `` | `"id"` | `"id"` |
| DELETE LIMIT | No | Yes | Yes | No |
| ILIKE / NOT ILIKE | No | No | No | Yes |
| RETURNING on DELETE | No | No | No | Yes |
| Parameter syntax | `?` | `?` | `?` | `?` |

---

## SqlResult

`SqlResult` is returned by every `build()` / `buildSql()` call. It carries the
rendered SQL string and the ordered bind-parameter list.

| Method | Returns | Description |
|--------|---------|-------------|
| `getSql()` | `String` | The rendered SQL with `?` placeholders |
| `getParameters()` | `List<Object>` | Bind parameters in the order they appear in the SQL |

```java
SqlResult result = new QueryBuilder()
.from("products")
.whereEquals("active", true)
.whereGreaterThan("stock", 0)
.buildSql(SqlDialect.MYSQL);

String sql = result.getSql();
// → SELECT * FROM `products` WHERE `active` = ? AND `stock` > ?

List<Object> params = result.getParameters();
// → [true, 0]
```

---

## AbstractSqlDialect

`AbstractSqlDialect` implements the shared rendering logic for SELECT and DELETE
queries. It is the base class for `MySqlDialect`, `SqliteDialect`, and
`PostgreSqlDialect`.

**Subquery parameter ordering**: parameters are collected depth-first in this
order:

1. SELECT-list scalar subquery parameters (left to right)
2. FROM subquery parameters
3. JOIN subquery parameters (left to right)
4. WHERE condition subquery parameters (top to bottom)

To create a fully custom dialect, extend `AbstractSqlDialect` and override any
combination of `quoteIdentifier`, `supportsDeleteLimit`, `supportsReturning`,
and `appendConditionFragment`.

---

## SqlDialect interface

| Member | Description |
|--------|-------------|
| `SqlDialect.STANDARD` | ANSI SQL constant instance |
| `SqlDialect.MYSQL` | MySQL dialect constant instance |
| `SqlDialect.SQLITE` | SQLite dialect constant instance |
| `SqlDialect.POSTGRESQL` | PostgreSQL dialect constant instance |
| `render(Query)` | Render a `SELECT` query to `SqlResult` |
| `renderDelete(Query)` | Render a `DELETE` query to `SqlResult`; observes `LIMIT` and `RETURNING` on supporting dialects |
87 changes: 87 additions & 0 deletions docs/dialects/mysql.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
title: MySQL
parent: SQL Dialects
nav_order: 2
permalink: /sql-dialects/mysql/
description: "MySQL dialect — back-tick identifier quoting and DELETE LIMIT support"
---

# MySQL Dialect
{: .no_toc }

## Table of contents
{: .no_toc .text-delta }

1. TOC
{:toc}

---

## Overview

`SqlDialect.MYSQL` wraps all table and column identifiers in back-ticks
(`` ` ``). It also supports the `LIMIT` clause on `DELETE` statements.
Quoting is applied to SELECT and DELETE queries rendered through this dialect.

| Feature | Value |
|---------|-------|
| Identifier quoting | Back-tick `` ` `` |
| DELETE LIMIT | Supported |
| ILIKE | Not supported |
| RETURNING on DELETE | Not supported |

---

## SELECT

```java
SqlResult r = new QueryBuilder()
.from("users")
.select("id", "name", "email")
.whereEquals("status", "active")
.orderBy("name", true)
.buildSql(SqlDialect.MYSQL);
// → SELECT `id`, `name`, `email` FROM `users` WHERE `status` = ? ORDER BY `name` ASC
// Parameters: ["active"]
```

---

## DELETE with LIMIT

```java
Query q = new QueryBuilder()
.from("logs")
.whereLessThan("created_at", "2025-01-01")
.limit(1000)
.build();

SqlResult result = SqlDialect.MYSQL.renderDelete(q);
// → DELETE FROM `logs` WHERE `created_at` < ? LIMIT 1000
// Parameters: ["2025-01-01"]
```

Or via `DeleteBuilder`:

```java
SqlResult result = QueryBuilder.deleteFrom("sessions")
.whereEquals("expired", true)
.build(SqlDialect.MYSQL);
// → DELETE FROM `sessions` WHERE `expired` = ?
```

---

## Identifier quoting coverage

Back-tick quoting is applied by the dialect to identifiers in SELECT and DELETE
statements. INSERT and UPDATE builders render their own SQL and do not apply
dialect quoting to column or table names.

---

## When to use

- MySQL and MariaDB
- Any database that uses back-tick quoting convention
- When `DELETE … LIMIT` batching is needed
162 changes: 162 additions & 0 deletions docs/dialects/postgresql.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
---
title: PostgreSQL
parent: SQL Dialects
nav_order: 4
permalink: /sql-dialects/postgresql/
description: "PostgreSQL dialect — double-quote quoting, ILIKE operators, and RETURNING on DELETE"
---

# PostgreSQL Dialect
{: .no_toc }

## Table of contents
{: .no_toc .text-delta }

1. TOC
{:toc}

---

## Overview

`SqlDialect.POSTGRESQL` wraps all table and column identifiers in double-quotes
and adds two PostgreSQL-specific features: case-insensitive `ILIKE` / `NOT ILIKE`
operators on SELECT queries, and a `RETURNING` clause on `DELETE` statements.

| Feature | Value |
|---------|-------|
| Identifier quoting | Double-quote `"` |
| DELETE LIMIT | Not supported |
| ILIKE / NOT ILIKE | Supported |
| RETURNING on DELETE | Supported |

---

## SELECT with double-quote quoting

```java
SqlResult r = new QueryBuilder()
.from("users")
.select("id", "name", "email")
.whereEquals("status", "active")
.orderBy("name", true)
.buildSql(SqlDialect.POSTGRESQL);
// → SELECT "id", "name", "email" FROM "users" WHERE "status" = ? ORDER BY "name" ASC
// Parameters: ["active"]
```

---

## ILIKE — case-insensitive LIKE

`ILIKE` is a PostgreSQL extension for case-insensitive pattern matching.
Use `whereILike` / `orWhereILike` on `QueryBuilder` or `SelectBuilder`:

```java
// Match email containing "alice" (any case)
SqlResult r = new QueryBuilder()
.from("users")
.whereILike("email", "alice")
.buildSql(SqlDialect.POSTGRESQL);
// → SELECT * FROM "users" WHERE "email" ILIKE ?
// Parameters: ["%alice%"]

// Combine with other conditions
SqlResult r2 = new QueryBuilder()
.from("articles")
.whereEquals("published", true)
.whereILike("title", "java")
.buildSql(SqlDialect.POSTGRESQL);
// → SELECT * FROM "articles" WHERE "published" = ? AND "title" ILIKE ?

// OR variant
SqlResult r3 = new QueryBuilder()
.from("users")
.whereEquals("role", "admin")
.orWhereILike("name", "bot")
.buildSql(SqlDialect.POSTGRESQL);
// → SELECT * FROM "users" WHERE "role" = ? OR "name" ILIKE ?
```

{: .note }
> `whereILike` and `orWhereILike` use the `ILIKE` operator which is only
> rendered correctly by `SqlDialect.POSTGRESQL`. Passing a different dialect
> will render the operator as-is without case-insensitive behaviour.

---

## DELETE with RETURNING

`RETURNING` on DELETE is rendered by the dialect — it is appended only when
`SqlDialect.POSTGRESQL` is active:

```java
SqlResult result = QueryBuilder.deleteFrom("users")
.whereEquals("id", 99)
.returning("id", "email")
.build(SqlDialect.POSTGRESQL);

// → DELETE FROM "users" WHERE "id" = ? RETURNING id, email
// Parameters: [99]
```

Without the PostgreSQL dialect the `RETURNING` columns are ignored even if set:

```java
// RETURNING is silently dropped for non-supporting dialects
SqlResult result = QueryBuilder.deleteFrom("users")
.whereEquals("id", 99)
.returning("id", "email")
.build(SqlDialect.STANDARD);
// → DELETE FROM users WHERE id = ?
```

---

## INSERT with RETURNING

For INSERT, `RETURNING` is appended inline regardless of dialect — the caller
is responsible for using a PostgreSQL connection:

```java
SqlResult result = QueryBuilder.insertInto("users")
.value("name", "Alice")
.value("email", "alice@example.com")
.returning("id", "created_at")
.build();
// → INSERT INTO users (name, email) VALUES (?, ?) RETURNING id, created_at
// Parameters: ["Alice", "alice@example.com"]
```

---

## UPDATE with RETURNING

Same inline behaviour as INSERT:

```java
SqlResult result = QueryBuilder.update("users")
.set("status", "active")
.whereEquals("id", 7)
.returning("id", "updated_at")
.build();
// → UPDATE users SET status = ? WHERE id = ? RETURNING id, updated_at
// Parameters: ["active", 7]
```

---

## Identifier quoting coverage

Double-quote quoting is applied by the dialect to identifiers in SELECT and
DELETE statements. INSERT and UPDATE builders render their own SQL and do not
apply dialect quoting to column or table names.

---

## When to use

- PostgreSQL databases
- When you need case-insensitive LIKE matching (`ILIKE`)
- When you need to retrieve affected row values from a DELETE in a single
round-trip (`RETURNING`)
Loading
Loading