From 346b91c7f7b8813aa55cf8648871b6698b17c3a8 Mon Sep 17 00:00:00 2001 From: ez-plugins Date: Thu, 16 Apr 2026 11:36:15 +0200 Subject: [PATCH] docs: updated README.md with latest features --- README.md | 259 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 217 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 3e9e4af..ba45b64 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,16 @@ # JavaQueryBuilder -A lightweight, fluent Java library for building SQL queries and filtering in-memory data, no runtime dependencies required. +A lightweight, fluent Java library for building parameterized SQL queries and filtering in-memory data, no runtime dependencies required. ## Features -- Fluent, readable builder API -- Generates parameterized SQL `SELECT` statements (safe from SQL injection) -- Supports all common operators: `=`, `!=`, `>`, `<`, `LIKE`, `IN`, `BETWEEN`, `IS NOT NULL` -- Column selection, `GROUP BY`, `ORDER BY`, `LIMIT`, and `OFFSET` +- Fluent, readable builder API for SELECT, INSERT, UPDATE, DELETE, and CREATE TABLE +- All values are parameterized, safe from SQL injection by design +- Supports all common operators: `=`, `!=`, `>`, `>=`, `<`, `<=`, `LIKE`, `NOT LIKE`, `IN`, `NOT IN`, `BETWEEN`, `IS NULL`, `IS NOT NULL` +- Column selection, `DISTINCT`, `GROUP BY`, `ORDER BY`, `LIMIT`, and `OFFSET` +- SQL dialect support: Standard, MySQL, SQLite - In-memory filtering via `QueryableStorage` -- Zero runtime dependencies, pure Java 25 +- Zero runtime dependencies, pure Java 21+ ## Installation @@ -36,7 +37,7 @@ Add the repository of Jitpack to your `pom.xml`: ## Quick Start -### Generating SQL +### SELECT ```java SqlResult result = new QueryBuilder() @@ -49,14 +50,14 @@ SqlResult result = new QueryBuilder() .offset(50) .buildSql("users"); -String sql = result.getSql(); // the parameterized SQL string -List params = result.getParameters(); // the bound values +String sql = result.getSql(); // parameterized SQL string +List params = result.getParameters(); // bound values -// sql → "SELECT id, name, email FROM users WHERE status = ? AND name LIKE ? AND age > ? ORDER BY name ASC LIMIT 25 OFFSET 50" -// params → ["active", "%alice%", 18] +// sql → "SELECT id, name, email FROM users WHERE status = ? AND name LIKE ? AND age > ? ORDER BY name ASC LIMIT 25 OFFSET 50" +// params → ["active", "%alice%", 18] ``` -Pass `sql` and `params` directly to a JDBC `PreparedStatement`: +Pass directly to a JDBC `PreparedStatement`: ```java PreparedStatement stmt = connection.prepareStatement(result.getSql()); @@ -67,6 +68,58 @@ for (int i = 0; i < params.size(); i++) { ResultSet rs = stmt.executeQuery(); ``` +### INSERT + +```java +SqlResult result = QueryBuilder.insertInto("users") + .value("name", "Alice") + .value("email", "alice@example.com") + .value("age", 30) + .build(); + +// sql → "INSERT INTO users (name, email, age) VALUES (?, ?, ?)" +// params → ["Alice", "alice@example.com", 30] +``` + +### UPDATE + +```java +SqlResult result = QueryBuilder.update("users") + .set("status", "inactive") + .set("updated_at", "2026-04-16") + .whereEquals("id", 42) + .build(); + +// sql → "UPDATE users SET status = ?, updated_at = ? WHERE id = ?" +// params → ["inactive", "2026-04-16", 42] +``` + +### DELETE + +```java +SqlResult result = QueryBuilder.deleteFrom("sessions") + .whereEquals("user_id", 42) + .whereLessThan("expires_at", "2026-01-01") + .build(); + +// sql → "DELETE FROM sessions WHERE user_id = ? AND expires_at < ?" +// params → [42, "2026-01-01"] +``` + +### CREATE TABLE + +```java +SqlResult result = QueryBuilder.createTable("users") + .column("id", "INT") + .column("name", "VARCHAR(255)") + .column("email", "VARCHAR(255)") + .primaryKey("id") + .ifNotExists() + .build(); + +// sql → "CREATE TABLE IF NOT EXISTS users (id INT, name VARCHAR(255), email VARCHAR(255), PRIMARY KEY (id))" +``` + ### In-Memory Filtering Implement `QueryableStorage` on your storage class to enable query-based lookups without a database: @@ -88,36 +141,56 @@ public class UserStore implements QueryableStorage { ## All Operators -| Builder method | SQL clause | In-memory | +| Builder method | SQL clause | Available on | |---|---|---| -| `whereEquals(col, val)` | `col = ?` | `col.equals(val)` | -| `whereNotEquals(col, val)` | `col != ?` | `!col.equals(val)` | -| `whereGreaterThan(col, val)` | `col > ?` | `col > val` | -| `whereLessThan(col, val)` | `col < ?` | `col < val` | -| `whereLike(col, substr)` | `col LIKE ?` (value wrapped with `%`) | `col.contains(substr)` | -| `whereExists(col)` | `col IS NOT NULL` | `map.containsKey(col)` | -| `whereIn(col, collection)` | `col IN (?, ?, ...)` | `collection.contains(col)` | -| `whereBetween(col, a, b)` | `col BETWEEN ? AND ?` | `a <= col <= b` | - -## Full Builder Reference +| `whereEquals(col, val)` | `col = ?` | QueryBuilder, DeleteBuilder, UpdateBuilder, SelectBuilder | +| `orWhereEquals(col, val)` | `OR col = ?` | QueryBuilder, UpdateBuilder | +| `whereNotEquals(col, val)` | `col != ?` | QueryBuilder, DeleteBuilder | +| `whereGreaterThan(col, val)` | `col > ?` | QueryBuilder, DeleteBuilder | +| `whereGreaterThanOrEquals(col, val)` | `col >= ?` | QueryBuilder, DeleteBuilder, UpdateBuilder | +| `whereLessThan(col, val)` | `col < ?` | QueryBuilder, DeleteBuilder | +| `whereLessThanOrEquals(col, val)` | `col <= ?` | QueryBuilder, DeleteBuilder | +| `whereLike(col, substr)` | `col LIKE ?` (wrapped with `%`) | QueryBuilder, SelectBuilder | +| `whereNotLike(col, substr)` | `col NOT LIKE ?` (wrapped with `%`) | QueryBuilder | +| `whereExists(col)` | `col IS NOT NULL` | QueryBuilder | +| `whereNull(col)` | `col IS NULL` | QueryBuilder | +| `whereNotNull(col)` | `col IS NOT NULL` | QueryBuilder | +| `whereIn(col, list)` | `col IN (?, ?, ...)` | QueryBuilder, DeleteBuilder, SelectBuilder | +| `whereNotIn(col, list)` | `col NOT IN (?, ?, ...)` | QueryBuilder, DeleteBuilder | +| `whereBetween(col, a, b)` | `col BETWEEN ? AND ?` | QueryBuilder, DeleteBuilder | + +## Builder Reference + +### QueryBuilder (SELECT) + +`QueryBuilder` is the main entry point for SELECT queries. It also provides static factory methods for all other statement types. ```java new QueryBuilder() // --- column selection (optional, defaults to *) --- .select("id", "name", "created_at") - - // --- conditions --- - .whereEquals("status", "active") - .whereNotEquals("role", "banned") - .whereGreaterThan("age", 18) - .whereLessThan("score", 1000) - .whereLike("username", "john") // SQL: username LIKE '%john%' - .whereExists("email") // SQL: email IS NOT NULL - .whereIn("country", List.of("US", "CA", "GB")) - .whereBetween("created_at", startDate, endDate) + .distinct() // adds DISTINCT + + // --- conditions (all joined with AND unless or* variant used) --- + .whereEquals("status", "active") // status = ? + .orWhereEquals("status", "pending") // OR status = ? + .whereNotEquals("role", "banned") // role != ? + .whereGreaterThan("age", 18) // age > ? + .whereGreaterThanOrEquals("age", 18) // age >= ? + .whereLessThan("score", 1000) // score < ? + .whereLessThanOrEquals("score", 1000) // score <= ? + .whereLike("username", "john") // username LIKE '%john%' + .whereNotLike("email", "spam") // email NOT LIKE '%spam%' + .whereExists("verified_at") // verified_at IS NOT NULL + .whereNull("deleted_at") // deleted_at IS NULL + .whereNotNull("email") // email IS NOT NULL + .whereIn("country", List.of("US", "CA")) + .whereNotIn("role", List.of("bot", "banned")) + .whereBetween("created_at", from, to) // --- sorting and grouping --- .groupBy("department") + .havingRaw("COUNT(*) > 5") .orderBy("created_at", false) // false = DESC .orderBy("name", true) // true = ASC @@ -126,8 +199,101 @@ new QueryBuilder() .offset(50) // --- output --- + .from("users") // sets table for build() / no-arg buildSql() .build() // → Query (for in-memory use) - .buildSql("users"); // → SqlResult (for JDBC use) + .buildSql("users") // → SqlResult (for JDBC use) + .buildSql("users", SqlDialect.MYSQL); // → SqlResult with dialect quoting +``` + +### Static Factory Methods + +All DML/DDL builders are accessible from `QueryBuilder` without needing to import the individual classes: + +```java +// INSERT +QueryBuilder.insert() // new InsertBuilder +QueryBuilder.insertInto("tableName") // new InsertBuilder pre-configured with table + +// UPDATE +QueryBuilder.update() // new UpdateBuilder +QueryBuilder.update("tableName") // new UpdateBuilder pre-configured with table + +// DELETE +QueryBuilder.delete() // new DeleteBuilder +QueryBuilder.deleteFrom("tableName") // new DeleteBuilder pre-configured with table + +// CREATE TABLE +QueryBuilder.createTable() // new CreateBuilder +QueryBuilder.createTable("tableName") // new CreateBuilder pre-configured with table +``` + +### InsertBuilder + +```java +QueryBuilder.insertInto("users") + .value("name", "Alice") + .value("email", "alice@example.com") + .build(); // → SqlResult +``` + +### UpdateBuilder + +```java +QueryBuilder.update("users") + .set("name", "Alice") + .set("status", "active") + .whereEquals("id", 1) + .orWhereEquals("email", "alice@example.com") + .whereGreaterThanOrEquals("age", 18) + .build(); // → SqlResult +``` + +### DeleteBuilder + +```java +QueryBuilder.deleteFrom("users") + .whereEquals("id", 1) + .whereNotEquals("role", "admin") + .whereGreaterThan("age", 0) + .whereGreaterThanOrEquals("score", 10) + .whereLessThan("attempts", 5) + .whereLessThanOrEquals("balance", 0) + .whereIn("status", List.of("expired", "banned")) + .whereNotIn("plan", List.of("premium", "trial")) + .whereBetween("created_at", from, to) + .build(); // → SqlResult +``` + +### CreateBuilder + +```java +QueryBuilder.createTable("orders") + .column("id", "BIGINT") + .column("user_id", "INT") + .column("total", "DECIMAL(10,2)") + .column("created_at", "TIMESTAMP") + .primaryKey("id") + .ifNotExists() + .build(); // → SqlResult +``` + +### SelectBuilder + +`SelectBuilder` provides a standalone, self-contained SELECT builder (no `Query` object): + +```java +new SelectBuilder() + .from("users") + .select("id", "name") + .distinct() + .whereEquals("active", true) + .whereIn("role", List.of("admin", "editor")) + .whereLike("name", "alice") + .groupBy("department") + .orderBy("name", true) + .limit(10) + .offset(20) + .build(); // → SqlResult ``` ## SQL Dialects @@ -135,24 +301,33 @@ new QueryBuilder() By default, `buildSql(table)` uses `SqlDialect.STANDARD` (no identifier quoting). Pass a second argument to use a different dialect: ```java -// Standard SQL -SqlResult sql = new QueryBuilder() +// Standard SQL (default) +SqlResult result = new QueryBuilder() .whereEquals("status", "active") - .buildSql("users"); // SqlDialect.STANDARD + .buildSql("users"); +// → SELECT * FROM users WHERE status = ? + +// MySQL +SqlResult result = new QueryBuilder() + .select("id", "name") + .whereEquals("status", "active") + .buildSql("users", SqlDialect.MYSQL); +// → SELECT `id`, `name` FROM `users` WHERE `status` = ? // SQLite -SqlResult sql = new QueryBuilder() +SqlResult result = new QueryBuilder() .whereEquals("status", "active") .buildSql("users", SqlDialect.SQLITE); // → SELECT * FROM "users" WHERE "status" = ? ``` -| Dialect | `SqlDialect.STANDARD` | `SqlDialect.SQLITE` | +| Dialect | Identifier quoting | Boolean values | |---|---|---| -| Identifier quoting | none | double quotes `"col"` | -| Boolean values | `true` / `false` | `1` / `0` | +| `SqlDialect.STANDARD` | none | `true` / `false` | +| `SqlDialect.MYSQL` | back-ticks `` `col` `` | `true` / `false` | +| `SqlDialect.SQLITE` | double quotes `"col"` | `1` / `0` | -SQLite wraps every table and column name in double quotes, which safely handles reserved words and names with special characters. Java booleans are converted to `1`/`0` to match SQLite's integer-backed boolean storage. +MySQL back-tick quoting safely handles reserved words and case-sensitive identifiers. SQLite double-quote quoting serves the same purpose, and Java booleans are converted to `1`/`0` to match SQLite's integer-backed boolean storage. ## How SQL Generation Works