diff --git a/README.md b/README.md index 083736d..846ae4c 100644 --- a/README.md +++ b/README.md @@ -48,17 +48,29 @@ A full-featured catalog with category browsing, global templates, and responsive **See:** `conf/book_catalog.conf` and `README_BOOK_CATALOG.md` -### Example 2: Parameterized Queries (Port 8081) +### Example 2: Positional Parameters (Port 8081) -Demonstrates dynamic SQL queries with nginx variables. +Demonstrates dynamic SQL queries with positional parameters. **Features:** -- Book detail pages by ID -- Genre filtering with query parameters -- Year range searches with multiple parameters -- Safe prepared statement parameter binding +- Query parameters with `?` placeholders +- Multiple positional parameters +- Safe prepared statement binding -**See:** `conf/book_detail.conf` and `README_PARAMETERS.md` +**See:** `conf/book_detail.conf` + +### Example 3: Named Parameters (Port 8082) - Recommended + +Demonstrates named SQL parameters for better readability. + +**Features:** +- Named parameters with `:name` syntax +- Order-independent parameter binding +- Title search with LIKE operator +- Rating filtering +- More maintainable configuration + +**See:** `conf/book_named_params.conf` and `README_PARAMETERS.md` ## Configuration Directives @@ -85,9 +97,14 @@ Specify the Handlebars template file (relative to location path). ### `sqlite_param` Add a parameter to the SQL query (can be used multiple times). -**Syntax:** `sqlite_param $variable_or_value;` +**Syntax:** +- Positional: `sqlite_param $variable_or_value;` +- Named: `sqlite_param :param_name $variable_or_value;` + **Context:** `location` -**Notes:** Order matches `?` placeholders in query +**Notes:** +- Positional parameters match `?` placeholders in order +- Named parameters match `:name` placeholders by name (recommended) ### `sqlite_global_templates` Set a directory for global template files (partials, layouts). @@ -112,13 +129,22 @@ http { sqlite_template "list.hbs"; } - # Parameterized query + # Parameterized query with named parameter (recommended) location = /book { sqlite_db "catalog.db"; - sqlite_query "SELECT * FROM books WHERE id = ?"; - sqlite_param $arg_id; + sqlite_query "SELECT * FROM books WHERE id = :book_id"; + sqlite_param :book_id $arg_id; sqlite_template "detail.hbs"; } + + # Positional parameters also supported + location = /search { + sqlite_db "catalog.db"; + sqlite_query "SELECT * FROM books WHERE year >= ? AND year <= ?"; + sqlite_param $arg_min; # First ? + sqlite_param $arg_max; # Second ? + sqlite_template "list.hbs"; + } } } ``` diff --git a/README_PARAMETERS.md b/README_PARAMETERS.md index 406d803..db5aa2e 100644 --- a/README_PARAMETERS.md +++ b/README_PARAMETERS.md @@ -8,13 +8,49 @@ The sqlite-serve module supports parameterized SQL queries using nginx variables Add parameters to SQL queries. Can be used multiple times to add multiple parameters. -**Syntax:** `sqlite_param variable_or_value;` +**Syntax:** +- Positional: `sqlite_param variable_or_value;` +- Named: `sqlite_param :param_name variable_or_value;` + **Context:** `location` -**Multiple:** Yes (order matches `?` placeholders in query) +**Multiple:** Yes + +**Note:** Positional parameters match `?` placeholders in order. Named parameters match `:name` placeholders by name. ## Usage -### Query Parameters (Most Common) +### Named Parameters (Recommended) + +Named parameters provide better readability and don't depend on order: + +```nginx +location = /book { + sqlite_db "book_catalog.db"; + sqlite_query "SELECT * FROM books WHERE id = :book_id"; + sqlite_param :book_id $arg_id; # Named parameter + sqlite_template "detail.hbs"; +} +``` + +**Request:** `http://localhost/book?id=5` +**SQL Executed:** `SELECT * FROM books WHERE id = '5'` + +### Multiple Named Parameters + +```nginx +location = /years { + sqlite_db "book_catalog.db"; + sqlite_query "SELECT * FROM books WHERE year >= :min AND year <= :max"; + sqlite_param :min $arg_min; # Order doesn't matter + sqlite_param :max $arg_max; # with named params + sqlite_template "list.hbs"; +} +``` + +**Request:** `http://localhost/years?min=2015&max=2024` +**SQL Executed:** `SELECT * FROM books WHERE year >= '2015' AND year <= '2024'` + +### Query Parameters (Positional) Use nginx's built-in `$arg_*` variables to access query parameters: @@ -267,10 +303,46 @@ Run it with: - All SQL placeholders must be `?` (positional parameters) - Parameters match placeholders in order of `sqlite_param` directives +## Named vs Positional Parameters + +### Named Parameters (`:name` syntax) - Recommended ✓ + +**Advantages:** +- Order-independent: Can rearrange `sqlite_param` directives without breaking queries +- Self-documenting: Parameter names explain their purpose +- Safer for maintenance: Adding/removing parameters less error-prone +- Better for complex queries with many parameters + +**Example:** +```nginx +sqlite_query "SELECT * FROM books WHERE author = :author AND year > :year"; +sqlite_param :year $arg_year; # Order doesn't matter! +sqlite_param :author $arg_author; +``` + +### Positional Parameters (`?` syntax) + +**Advantages:** +- Slightly more compact configuration +- Works well for simple 1-2 parameter queries + +**Disadvantages:** +- Order-dependent: Parameters must match `?` placeholders exactly +- Less readable with many parameters +- Error-prone when modifying queries + +**Example:** +```nginx +sqlite_query "SELECT * FROM books WHERE author = ? AND year > ?"; +sqlite_param $arg_author; # Must be first! +sqlite_param $arg_year; # Must be second! +``` + +**Recommendation:** Use named parameters (`:name`) for all but the simplest queries. + ## Limitations -- Only supports `?` positional parameters (not named parameters like `:name`) -- Parameters must be provided in the exact order they appear in the query - All parameter values are treated as strings (SQLite performs type coercion) - Complex SQL values (arrays, JSON) should be constructed in the query itself +- Cannot mix positional and named parameters in the same query diff --git a/conf/book_named_params.conf b/conf/book_named_params.conf new file mode 100644 index 0000000..ddee42e --- /dev/null +++ b/conf/book_named_params.conf @@ -0,0 +1,74 @@ +# Book Catalog with Named Parameters +# Demonstrates using named SQL parameters for better readability + +load_module target/debug/libsqlite_serve.dylib; + +worker_processes 1; +events {} + +error_log logs/error.log debug; + +http { + sqlite_global_templates "server_root/global_templates"; + + server { + listen 8082; + + root "server_root"; + + # Book detail with named parameter + location = /book { + add_header "Content-Type" "text/html; charset=utf-8"; + sqlite_db "book_catalog.db"; + sqlite_query "SELECT * FROM books WHERE id = :book_id"; + sqlite_param :book_id $arg_id; + sqlite_template "detail.hbs"; + } + + # Genre filter with named parameter + location = /genre { + add_header "Content-Type" "text/html; charset=utf-8"; + sqlite_db "book_catalog.db"; + sqlite_query "SELECT * FROM books WHERE genre = :genre_name ORDER BY rating DESC"; + sqlite_param :genre_name $arg_genre; + sqlite_template "genre.hbs"; + } + + # Year range with named parameters + location = /years { + add_header "Content-Type" "text/html; charset=utf-8"; + sqlite_db "book_catalog.db"; + sqlite_query "SELECT * FROM books WHERE year >= :min_year AND year <= :max_year ORDER BY year DESC, title"; + sqlite_param :min_year $arg_min; + sqlite_param :max_year $arg_max; + sqlite_template "list.hbs"; + } + + # Search by title with named parameter + location = /search { + add_header "Content-Type" "text/html; charset=utf-8"; + sqlite_db "book_catalog.db"; + sqlite_query "SELECT * FROM books WHERE title LIKE '%' || :search_term || '%' ORDER BY rating DESC, title"; + sqlite_param :search_term $arg_q; + sqlite_template "list.hbs"; + } + + # Filter by rating with named parameter + location = /top-rated { + add_header "Content-Type" "text/html; charset=utf-8"; + sqlite_db "book_catalog.db"; + sqlite_query "SELECT * FROM books WHERE rating >= :min_rating ORDER BY rating DESC, title"; + sqlite_param :min_rating $arg_rating; + sqlite_template "list.hbs"; + } + + # Fallback to all books + location / { + add_header "Content-Type" "text/html; charset=utf-8"; + sqlite_db "book_catalog.db"; + sqlite_query "SELECT * FROM books ORDER BY rating DESC, title"; + sqlite_template "list.hbs"; + } + } +} + diff --git a/server_root/search/list.hbs b/server_root/search/list.hbs new file mode 100644 index 0000000..9ee6ddb --- /dev/null +++ b/server_root/search/list.hbs @@ -0,0 +1,122 @@ +{{> header}} + + + +
Book Collection
+Top Rated
+Browse All
+Book Collection
+Top Rated
+Browse All
+