Skip to content

Commit c489e72

Browse files
authored
Merge pull request #43 from EzFramework/feature/postgresql-support
Updated documentation
2 parents e93ddf0 + 0df46f8 commit c489e72

21 files changed

Lines changed: 1063 additions & 439 deletions

‎docs/api-reference.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: API Reference
3-
nav_order: 11
3+
nav_order: 10
44
description: "Complete public method tables for every class and interface in JavaQueryBuilder"
55
---
66

‎docs/conditions.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Conditions
3-
nav_order: 5
3+
nav_order: 4
44
description: "Operators, Condition, ConditionEntry, Connector AND/OR, and the orWhere* pattern"
55
---
66

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

5456
---
5557

@@ -69,6 +71,8 @@ constant and the SQL it generates.
6971
| `whereLessThanOrEquals(col, val)` | `LTE` | `col <= ?` |
7072
| `whereLike(col, val)` | `LIKE` | `col LIKE ?` |
7173
| `whereNotLike(col, val)` | `NOT_LIKE` | `col NOT LIKE ?` |
74+
| `whereILike(col, val)` | `ILIKE` | `col ILIKE ?` (PostgreSQL only) |
75+
| `orWhereILike(col, val)` | `ILIKE` | `OR col ILIKE ?` (PostgreSQL only) |
7276
| `whereNull(col)` | `IS_NULL` | `col IS NULL` |
7377
| `whereNotNull(col)` | `IS_NOT_NULL` | `col IS NOT NULL` |
7478
| `whereExists(col)` | `EXISTS` | `col IS NOT NULL` |

‎docs/configuration.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Configuration
3-
nav_order: 8
3+
nav_order: 7
44
description: "QueryBuilderDefaults: global and per-query preset for dialect, columns, limit, offset, and LIKE wrapping"
55
---
66

‎docs/dialects/index.md‎

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
---
2+
title: SQL Dialects
3+
nav_order: 6
4+
has_children: true
5+
permalink: /sql-dialects/
6+
description: "SqlDialect, SqlResult, AbstractSqlDialect, and the dialect comparison matrix"
7+
---
8+
9+
# SQL Dialects
10+
{: .no_toc }
11+
12+
## Table of contents
13+
{: .no_toc .text-delta }
14+
15+
1. TOC
16+
{:toc}
17+
18+
---
19+
20+
## Overview
21+
22+
`SqlDialect` is a strategy interface that converts a `Query` object into a
23+
parameterized `SqlResult`. Four built-in dialects are provided as constants
24+
on the interface:
25+
26+
| Constant | Page | Identifier quoting | DELETE LIMIT | ILIKE | RETURNING |
27+
|----------|------|--------------------|--------------|-------|-----------|
28+
| `SqlDialect.STANDARD` | [STANDARD]({{ site.baseurl }}/sql-dialects/standard/) | None (ANSI) | No | No | No |
29+
| `SqlDialect.MYSQL` | [MySQL]({{ site.baseurl }}/sql-dialects/mysql/) | Back-tick `` ` `` | Yes | No | No |
30+
| `SqlDialect.SQLITE` | [SQLite]({{ site.baseurl }}/sql-dialects/sqlite/) | Double-quote `"` | Yes | No | No |
31+
| `SqlDialect.POSTGRESQL` | [PostgreSQL]({{ site.baseurl }}/sql-dialects/postgresql/) | Double-quote `"` | No | Yes | Yes (DELETE) |
32+
33+
---
34+
35+
## Dialect matrix
36+
37+
The same `Query` produces different SQL across dialects due to identifier
38+
quoting:
39+
40+
| Feature | STANDARD | MYSQL | SQLITE | POSTGRESQL |
41+
|---------|----------|-------|--------|------------|
42+
| Table quoting | `users` | `` `users` `` | `"users"` | `"users"` |
43+
| Column quoting | `id` | `` `id` `` | `"id"` | `"id"` |
44+
| DELETE LIMIT | No | Yes | Yes | No |
45+
| ILIKE / NOT ILIKE | No | No | No | Yes |
46+
| RETURNING on DELETE | No | No | No | Yes |
47+
| Parameter syntax | `?` | `?` | `?` | `?` |
48+
49+
---
50+
51+
## SqlResult
52+
53+
`SqlResult` is returned by every `build()` / `buildSql()` call. It carries the
54+
rendered SQL string and the ordered bind-parameter list.
55+
56+
| Method | Returns | Description |
57+
|--------|---------|-------------|
58+
| `getSql()` | `String` | The rendered SQL with `?` placeholders |
59+
| `getParameters()` | `List<Object>` | Bind parameters in the order they appear in the SQL |
60+
61+
```java
62+
SqlResult result = new QueryBuilder()
63+
.from("products")
64+
.whereEquals("active", true)
65+
.whereGreaterThan("stock", 0)
66+
.buildSql(SqlDialect.MYSQL);
67+
68+
String sql = result.getSql();
69+
// → SELECT * FROM `products` WHERE `active` = ? AND `stock` > ?
70+
71+
List<Object> params = result.getParameters();
72+
// → [true, 0]
73+
```
74+
75+
---
76+
77+
## AbstractSqlDialect
78+
79+
`AbstractSqlDialect` implements the shared rendering logic for SELECT and DELETE
80+
queries. It is the base class for `MySqlDialect`, `SqliteDialect`, and
81+
`PostgreSqlDialect`.
82+
83+
**Subquery parameter ordering**: parameters are collected depth-first in this
84+
order:
85+
86+
1. SELECT-list scalar subquery parameters (left to right)
87+
2. FROM subquery parameters
88+
3. JOIN subquery parameters (left to right)
89+
4. WHERE condition subquery parameters (top to bottom)
90+
91+
To create a fully custom dialect, extend `AbstractSqlDialect` and override any
92+
combination of `quoteIdentifier`, `supportsDeleteLimit`, `supportsReturning`,
93+
and `appendConditionFragment`.
94+
95+
---
96+
97+
## SqlDialect interface
98+
99+
| Member | Description |
100+
|--------|-------------|
101+
| `SqlDialect.STANDARD` | ANSI SQL constant instance |
102+
| `SqlDialect.MYSQL` | MySQL dialect constant instance |
103+
| `SqlDialect.SQLITE` | SQLite dialect constant instance |
104+
| `SqlDialect.POSTGRESQL` | PostgreSQL dialect constant instance |
105+
| `render(Query)` | Render a `SELECT` query to `SqlResult` |
106+
| `renderDelete(Query)` | Render a `DELETE` query to `SqlResult`; observes `LIMIT` and `RETURNING` on supporting dialects |

‎docs/dialects/mysql.md‎

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
---
2+
title: MySQL
3+
parent: SQL Dialects
4+
nav_order: 2
5+
permalink: /sql-dialects/mysql/
6+
description: "MySQL dialect — back-tick identifier quoting and DELETE LIMIT support"
7+
---
8+
9+
# MySQL Dialect
10+
{: .no_toc }
11+
12+
## Table of contents
13+
{: .no_toc .text-delta }
14+
15+
1. TOC
16+
{:toc}
17+
18+
---
19+
20+
## Overview
21+
22+
`SqlDialect.MYSQL` wraps all table and column identifiers in back-ticks
23+
(`` ` ``). It also supports the `LIMIT` clause on `DELETE` statements.
24+
Quoting is applied to SELECT and DELETE queries rendered through this dialect.
25+
26+
| Feature | Value |
27+
|---------|-------|
28+
| Identifier quoting | Back-tick `` ` `` |
29+
| DELETE LIMIT | Supported |
30+
| ILIKE | Not supported |
31+
| RETURNING on DELETE | Not supported |
32+
33+
---
34+
35+
## SELECT
36+
37+
```java
38+
SqlResult r = new QueryBuilder()
39+
.from("users")
40+
.select("id", "name", "email")
41+
.whereEquals("status", "active")
42+
.orderBy("name", true)
43+
.buildSql(SqlDialect.MYSQL);
44+
// → SELECT `id`, `name`, `email` FROM `users` WHERE `status` = ? ORDER BY `name` ASC
45+
// Parameters: ["active"]
46+
```
47+
48+
---
49+
50+
## DELETE with LIMIT
51+
52+
```java
53+
Query q = new QueryBuilder()
54+
.from("logs")
55+
.whereLessThan("created_at", "2025-01-01")
56+
.limit(1000)
57+
.build();
58+
59+
SqlResult result = SqlDialect.MYSQL.renderDelete(q);
60+
// → DELETE FROM `logs` WHERE `created_at` < ? LIMIT 1000
61+
// Parameters: ["2025-01-01"]
62+
```
63+
64+
Or via `DeleteBuilder`:
65+
66+
```java
67+
SqlResult result = QueryBuilder.deleteFrom("sessions")
68+
.whereEquals("expired", true)
69+
.build(SqlDialect.MYSQL);
70+
// → DELETE FROM `sessions` WHERE `expired` = ?
71+
```
72+
73+
---
74+
75+
## Identifier quoting coverage
76+
77+
Back-tick quoting is applied by the dialect to identifiers in SELECT and DELETE
78+
statements. INSERT and UPDATE builders render their own SQL and do not apply
79+
dialect quoting to column or table names.
80+
81+
---
82+
83+
## When to use
84+
85+
- MySQL and MariaDB
86+
- Any database that uses back-tick quoting convention
87+
- When `DELETE … LIMIT` batching is needed

‎docs/dialects/postgresql.md‎

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
---
2+
title: PostgreSQL
3+
parent: SQL Dialects
4+
nav_order: 4
5+
permalink: /sql-dialects/postgresql/
6+
description: "PostgreSQL dialect — double-quote quoting, ILIKE operators, and RETURNING on DELETE"
7+
---
8+
9+
# PostgreSQL Dialect
10+
{: .no_toc }
11+
12+
## Table of contents
13+
{: .no_toc .text-delta }
14+
15+
1. TOC
16+
{:toc}
17+
18+
---
19+
20+
## Overview
21+
22+
`SqlDialect.POSTGRESQL` wraps all table and column identifiers in double-quotes
23+
and adds two PostgreSQL-specific features: case-insensitive `ILIKE` / `NOT ILIKE`
24+
operators on SELECT queries, and a `RETURNING` clause on `DELETE` statements.
25+
26+
| Feature | Value |
27+
|---------|-------|
28+
| Identifier quoting | Double-quote `"` |
29+
| DELETE LIMIT | Not supported |
30+
| ILIKE / NOT ILIKE | Supported |
31+
| RETURNING on DELETE | Supported |
32+
33+
---
34+
35+
## SELECT with double-quote quoting
36+
37+
```java
38+
SqlResult r = new QueryBuilder()
39+
.from("users")
40+
.select("id", "name", "email")
41+
.whereEquals("status", "active")
42+
.orderBy("name", true)
43+
.buildSql(SqlDialect.POSTGRESQL);
44+
// → SELECT "id", "name", "email" FROM "users" WHERE "status" = ? ORDER BY "name" ASC
45+
// Parameters: ["active"]
46+
```
47+
48+
---
49+
50+
## ILIKE — case-insensitive LIKE
51+
52+
`ILIKE` is a PostgreSQL extension for case-insensitive pattern matching.
53+
Use `whereILike` / `orWhereILike` on `QueryBuilder` or `SelectBuilder`:
54+
55+
```java
56+
// Match email containing "alice" (any case)
57+
SqlResult r = new QueryBuilder()
58+
.from("users")
59+
.whereILike("email", "alice")
60+
.buildSql(SqlDialect.POSTGRESQL);
61+
// → SELECT * FROM "users" WHERE "email" ILIKE ?
62+
// Parameters: ["%alice%"]
63+
64+
// Combine with other conditions
65+
SqlResult r2 = new QueryBuilder()
66+
.from("articles")
67+
.whereEquals("published", true)
68+
.whereILike("title", "java")
69+
.buildSql(SqlDialect.POSTGRESQL);
70+
// → SELECT * FROM "articles" WHERE "published" = ? AND "title" ILIKE ?
71+
72+
// OR variant
73+
SqlResult r3 = new QueryBuilder()
74+
.from("users")
75+
.whereEquals("role", "admin")
76+
.orWhereILike("name", "bot")
77+
.buildSql(SqlDialect.POSTGRESQL);
78+
// → SELECT * FROM "users" WHERE "role" = ? OR "name" ILIKE ?
79+
```
80+
81+
{: .note }
82+
> `whereILike` and `orWhereILike` use the `ILIKE` operator which is only
83+
> rendered correctly by `SqlDialect.POSTGRESQL`. Passing a different dialect
84+
> will render the operator as-is without case-insensitive behaviour.
85+
86+
---
87+
88+
## DELETE with RETURNING
89+
90+
`RETURNING` on DELETE is rendered by the dialect — it is appended only when
91+
`SqlDialect.POSTGRESQL` is active:
92+
93+
```java
94+
SqlResult result = QueryBuilder.deleteFrom("users")
95+
.whereEquals("id", 99)
96+
.returning("id", "email")
97+
.build(SqlDialect.POSTGRESQL);
98+
99+
// → DELETE FROM "users" WHERE "id" = ? RETURNING id, email
100+
// Parameters: [99]
101+
```
102+
103+
Without the PostgreSQL dialect the `RETURNING` columns are ignored even if set:
104+
105+
```java
106+
// RETURNING is silently dropped for non-supporting dialects
107+
SqlResult result = QueryBuilder.deleteFrom("users")
108+
.whereEquals("id", 99)
109+
.returning("id", "email")
110+
.build(SqlDialect.STANDARD);
111+
// → DELETE FROM users WHERE id = ?
112+
```
113+
114+
---
115+
116+
## INSERT with RETURNING
117+
118+
For INSERT, `RETURNING` is appended inline regardless of dialect — the caller
119+
is responsible for using a PostgreSQL connection:
120+
121+
```java
122+
SqlResult result = QueryBuilder.insertInto("users")
123+
.value("name", "Alice")
124+
.value("email", "alice@example.com")
125+
.returning("id", "created_at")
126+
.build();
127+
// → INSERT INTO users (name, email) VALUES (?, ?) RETURNING id, created_at
128+
// Parameters: ["Alice", "alice@example.com"]
129+
```
130+
131+
---
132+
133+
## UPDATE with RETURNING
134+
135+
Same inline behaviour as INSERT:
136+
137+
```java
138+
SqlResult result = QueryBuilder.update("users")
139+
.set("status", "active")
140+
.whereEquals("id", 7)
141+
.returning("id", "updated_at")
142+
.build();
143+
// → UPDATE users SET status = ? WHERE id = ? RETURNING id, updated_at
144+
// Parameters: ["active", 7]
145+
```
146+
147+
---
148+
149+
## Identifier quoting coverage
150+
151+
Double-quote quoting is applied by the dialect to identifiers in SELECT and
152+
DELETE statements. INSERT and UPDATE builders render their own SQL and do not
153+
apply dialect quoting to column or table names.
154+
155+
---
156+
157+
## When to use
158+
159+
- PostgreSQL databases
160+
- When you need case-insensitive LIKE matching (`ILIKE`)
161+
- When you need to retrieve affected row values from a DELETE in a single
162+
round-trip (`RETURNING`)

0 commit comments

Comments
 (0)