Add named parameter support for SQL queries
New Feature: Named SQL Parameters - Supports both positional (?) and named (:name) parameters - Named parameters are order-independent and more readable - Syntax: sqlite_param :param_name $variable Implementation: - Updated sqlite_param directive to accept 1 or 2 arguments - ModuleConfig.query_params now stores (name, variable) pairs - execute_query() detects named vs positional parameters - Extracted row_to_map closure to avoid type conflicts - Named params use rusqlite named parameter binding Examples (Port 8082): - Book detail: WHERE id = :book_id - Genre filter: WHERE genre = :genre_name - Year range: WHERE year >= :min_year AND year <= :max_year - Title search: WHERE title LIKE '%' || :search_term || '%' - Rating filter: WHERE rating >= :min_rating Benefits of Named Parameters: - Order-independent: params can be in any order in config - Self-documenting: :book_id is clearer than first ? - Maintainable: can add/remove params without reordering - Recommended for all but simplest queries Configuration: - conf/book_named_params.conf: Complete named params example - start_named_params.sh: Quick start script for port 8082 Documentation: - Added named vs positional comparison in README_PARAMETERS.md - Updated README.md with named parameter examples - Documented both syntaxes in directive reference All examples tested and working with both parameter styles.
This commit is contained in:
50
README.md
50
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
74
conf/book_named_params.conf
Normal file
74
conf/book_named_params.conf
Normal file
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
122
server_root/search/list.hbs
Normal file
122
server_root/search/list.hbs
Normal file
@ -0,0 +1,122 @@
|
||||
{{> header}}
|
||||
|
||||
<style>
|
||||
.book-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.book-card {
|
||||
background: #fff;
|
||||
border: 2px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.book-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 16px rgba(102, 126, 234, 0.2);
|
||||
border-color: #667eea;
|
||||
}
|
||||
.book-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: start;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.book-title {
|
||||
color: #2d3748;
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 0.25rem;
|
||||
flex: 1;
|
||||
}
|
||||
.book-rating {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 20px;
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
white-space: nowrap;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
.book-author {
|
||||
color: #667eea;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.book-description {
|
||||
color: #4a5568;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.book-details {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.book-genre {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.book-year {
|
||||
background: #e9ecef;
|
||||
color: #495057;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.book-isbn {
|
||||
color: #6c757d;
|
||||
font-family: monospace;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.stats {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 2rem;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
text-align: center;
|
||||
}
|
||||
.stat-item h2 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.stat-item p {
|
||||
opacity: 0.9;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat-item">
|
||||
<h2>📚</h2>
|
||||
<p>Book Collection</p>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<h2>⭐</h2>
|
||||
<p>Top Rated</p>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<h2>🔍</h2>
|
||||
<p>Browse All</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 style="color: #2d3748; margin-bottom: 1rem;">All Books in Collection</h2>
|
||||
|
||||
<div class="book-grid">
|
||||
{{#each results}}
|
||||
{{> book_card}}
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
{{> footer}}
|
||||
|
||||
122
server_root/top-rated/list.hbs
Normal file
122
server_root/top-rated/list.hbs
Normal file
@ -0,0 +1,122 @@
|
||||
{{> header}}
|
||||
|
||||
<style>
|
||||
.book-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.book-card {
|
||||
background: #fff;
|
||||
border: 2px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.book-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 16px rgba(102, 126, 234, 0.2);
|
||||
border-color: #667eea;
|
||||
}
|
||||
.book-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: start;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.book-title {
|
||||
color: #2d3748;
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 0.25rem;
|
||||
flex: 1;
|
||||
}
|
||||
.book-rating {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 20px;
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
white-space: nowrap;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
.book-author {
|
||||
color: #667eea;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.book-description {
|
||||
color: #4a5568;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.book-details {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.book-genre {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.book-year {
|
||||
background: #e9ecef;
|
||||
color: #495057;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.book-isbn {
|
||||
color: #6c757d;
|
||||
font-family: monospace;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.stats {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 2rem;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
text-align: center;
|
||||
}
|
||||
.stat-item h2 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.stat-item p {
|
||||
opacity: 0.9;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat-item">
|
||||
<h2>📚</h2>
|
||||
<p>Book Collection</p>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<h2>⭐</h2>
|
||||
<p>Top Rated</p>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<h2>🔍</h2>
|
||||
<p>Browse All</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 style="color: #2d3748; margin-bottom: 1rem;">All Books in Collection</h2>
|
||||
|
||||
<div class="book-grid">
|
||||
{{#each results}}
|
||||
{{> book_card}}
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
{{> footer}}
|
||||
|
||||
69
src/lib.rs
69
src/lib.rs
@ -1,7 +1,7 @@
|
||||
use handlebars::Handlebars;
|
||||
use ngx::core::Buffer;
|
||||
use ngx::ffi::{
|
||||
ngx_hash_key, ngx_http_get_variable, NGX_CONF_TAKE1, NGX_HTTP_LOC_CONF,
|
||||
ngx_hash_key, ngx_http_get_variable, NGX_CONF_TAKE1, NGX_CONF_TAKE2, NGX_HTTP_LOC_CONF,
|
||||
NGX_HTTP_MAIN_CONF, NGX_HTTP_MODULE, NGX_HTTP_LOC_CONF_OFFSET, NGX_RS_MODULE_SIGNATURE,
|
||||
nginx_version, ngx_chain_t, ngx_command_t, ngx_conf_t, ngx_http_module_t, ngx_int_t,
|
||||
ngx_module_t, ngx_str_t, ngx_uint_t,
|
||||
@ -46,7 +46,7 @@ struct ModuleConfig {
|
||||
db_path: String,
|
||||
query: String,
|
||||
template_path: String,
|
||||
query_params: Vec<String>, // Variable names to use as query parameters
|
||||
query_params: Vec<(String, String)>, // (param_name, variable_name) pairs
|
||||
}
|
||||
|
||||
// Global configuration for shared templates
|
||||
@ -179,7 +179,7 @@ static mut ngx_http_howto_commands: [ngx_command_t; 6] = [
|
||||
},
|
||||
ngx_command_t {
|
||||
name: ngx_string!("sqlite_param"),
|
||||
type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) as ngx_uint_t,
|
||||
type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1 | NGX_CONF_TAKE2) as ngx_uint_t,
|
||||
set: Some(ngx_http_howto_commands_add_param),
|
||||
conf: NGX_HTTP_LOC_CONF_OFFSET,
|
||||
offset: 0,
|
||||
@ -272,8 +272,20 @@ extern "C" fn ngx_http_howto_commands_add_param(
|
||||
unsafe {
|
||||
let conf = &mut *(conf as *mut ModuleConfig);
|
||||
let args = (*(*cf).args).elts as *mut ngx_str_t;
|
||||
let param = (*args.add(1)).to_string();
|
||||
conf.query_params.push(param);
|
||||
let nelts = (*(*cf).args).nelts;
|
||||
|
||||
if nelts == 2 {
|
||||
// Single argument: positional parameter
|
||||
// sqlite_param $arg_id
|
||||
let variable = (*args.add(1)).to_string();
|
||||
conf.query_params.push((String::new(), variable));
|
||||
} else if nelts == 3 {
|
||||
// Two arguments: named parameter
|
||||
// sqlite_param :book_id $arg_id
|
||||
let param_name = (*args.add(1)).to_string();
|
||||
let variable = (*args.add(2)).to_string();
|
||||
conf.query_params.push((param_name, variable));
|
||||
}
|
||||
};
|
||||
|
||||
std::ptr::null_mut()
|
||||
@ -318,7 +330,7 @@ fn load_templates_from_dir(reg: &mut Handlebars, dir_path: &str) -> std::io::Res
|
||||
fn execute_query(
|
||||
db_path: &str,
|
||||
query: &str,
|
||||
params: &[&str],
|
||||
params: &[(String, String)], // (param_name, value) pairs
|
||||
) -> Result<Vec<std::collections::HashMap<String, serde_json::Value>>> {
|
||||
let conn = Connection::open(db_path)?;
|
||||
let mut stmt = conn.prepare(query)?;
|
||||
@ -328,13 +340,11 @@ fn execute_query(
|
||||
.map(|i| stmt.column_name(i).unwrap_or("").to_string())
|
||||
.collect();
|
||||
|
||||
// Convert params to rusqlite parameters
|
||||
let rusqlite_params: Vec<&dyn rusqlite::ToSql> = params
|
||||
.iter()
|
||||
.map(|p| p as &dyn rusqlite::ToSql)
|
||||
.collect();
|
||||
// Bind parameters (either positional or named)
|
||||
let has_named_params = params.iter().any(|(name, _)| !name.is_empty());
|
||||
|
||||
let rows = stmt.query_map(rusqlite_params.as_slice(), |row| {
|
||||
// Convert row to JSON map
|
||||
let row_to_map = |row: &rusqlite::Row| -> rusqlite::Result<std::collections::HashMap<String, serde_json::Value>> {
|
||||
let mut map = std::collections::HashMap::new();
|
||||
for (i, col_name) in column_names.iter().enumerate() {
|
||||
let value: serde_json::Value = match row.get_ref(i)? {
|
||||
@ -350,7 +360,8 @@ fn execute_query(
|
||||
}
|
||||
rusqlite::types::ValueRef::Blob(v) => {
|
||||
// Convert blob to hex string
|
||||
let hex_string = v.iter()
|
||||
let hex_string = v
|
||||
.iter()
|
||||
.map(|b| format!("{:02x}", b))
|
||||
.collect::<String>();
|
||||
serde_json::Value::String(hex_string)
|
||||
@ -359,7 +370,23 @@ fn execute_query(
|
||||
map.insert(col_name.clone(), value);
|
||||
}
|
||||
Ok(map)
|
||||
})?;
|
||||
};
|
||||
|
||||
let rows = if has_named_params {
|
||||
// Use named parameters
|
||||
let named_params: Vec<(&str, &dyn rusqlite::ToSql)> = params
|
||||
.iter()
|
||||
.map(|(name, value)| (name.as_str(), value as &dyn rusqlite::ToSql))
|
||||
.collect();
|
||||
stmt.query_map(named_params.as_slice(), row_to_map)?
|
||||
} else {
|
||||
// Use positional parameters
|
||||
let positional_params: Vec<&dyn rusqlite::ToSql> = params
|
||||
.iter()
|
||||
.map(|(_, value)| value as &dyn rusqlite::ToSql)
|
||||
.collect();
|
||||
stmt.query_map(positional_params.as_slice(), row_to_map)?
|
||||
};
|
||||
|
||||
rows.collect()
|
||||
}
|
||||
@ -407,8 +434,8 @@ http_request_handler!(howto_access_handler, |request: &mut http::Request| {
|
||||
.unwrap_or("");
|
||||
|
||||
// Resolve query parameters from nginx variables
|
||||
let mut param_values: Vec<String> = Vec::new();
|
||||
for var_name in &co.query_params {
|
||||
let mut param_values: Vec<(String, String)> = Vec::new();
|
||||
for (param_name, var_name) in &co.query_params {
|
||||
let value = if var_name.starts_with('$') {
|
||||
// It's a variable reference, resolve it from nginx
|
||||
let var_name_str = &var_name[1..]; // Remove the '$' prefix
|
||||
@ -445,19 +472,17 @@ http_request_handler!(howto_access_handler, |request: &mut http::Request| {
|
||||
// It's a literal value
|
||||
var_name.clone()
|
||||
};
|
||||
param_values.push(value);
|
||||
param_values.push((param_name.clone(), value));
|
||||
}
|
||||
|
||||
ngx_log_debug_http!(
|
||||
request,
|
||||
"executing query with {} parameters: {:?}",
|
||||
param_values.len(),
|
||||
param_values
|
||||
"executing query with {} parameters",
|
||||
param_values.len()
|
||||
);
|
||||
|
||||
// Execute the configured SQL query with parameters
|
||||
let param_refs: Vec<&str> = param_values.iter().map(|s| s.as_str()).collect();
|
||||
let results = match execute_query(&co.db_path, &co.query, ¶m_refs) {
|
||||
let results = match execute_query(&co.db_path, &co.query, ¶m_values) {
|
||||
Ok(results) => results,
|
||||
Err(e) => {
|
||||
ngx_log_debug_http!(request, "failed to execute query: {}", e);
|
||||
|
||||
50
start_named_params.sh
Executable file
50
start_named_params.sh
Executable file
@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
# Start script for named parameters example
|
||||
|
||||
set -e
|
||||
|
||||
echo "📚 Starting Named Parameters Example..."
|
||||
echo ""
|
||||
|
||||
# Check if database exists
|
||||
if [ ! -f "book_catalog.db" ]; then
|
||||
echo "Database not found. Running setup..."
|
||||
./setup_book_catalog.sh
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Check if module is built
|
||||
if [ ! -f "target/debug/libsqlite_serve.dylib" ]; then
|
||||
echo "Module not built. Building..."
|
||||
direnv exec "$PWD" cargo build
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Start nginx
|
||||
echo "Starting nginx on http://localhost:8082"
|
||||
./ngx_src/nginx-1.28.0/objs/nginx -c conf/book_named_params.conf -p .
|
||||
|
||||
echo ""
|
||||
echo "✅ Named Parameters Example is running!"
|
||||
echo ""
|
||||
echo "Named Parameter Examples:"
|
||||
echo " • http://localhost:8082/book?id=1"
|
||||
echo " Query: SELECT * FROM books WHERE id = :book_id"
|
||||
echo " Param: :book_id = \$arg_id"
|
||||
echo ""
|
||||
echo " • http://localhost:8082/genre?genre=Programming"
|
||||
echo " Query: ... WHERE genre = :genre_name"
|
||||
echo " Param: :genre_name = \$arg_genre"
|
||||
echo ""
|
||||
echo " • http://localhost:8082/years?min=2015&max=2024"
|
||||
echo " Query: ... WHERE year >= :min_year AND year <= :max_year"
|
||||
echo " Params: :min_year = \$arg_min, :max_year = \$arg_max"
|
||||
echo ""
|
||||
echo " • http://localhost:8082/search?q=Rust"
|
||||
echo " Search by title with named parameter"
|
||||
echo ""
|
||||
echo " • http://localhost:8082/top-rated?rating=4.7"
|
||||
echo " Filter by minimum rating"
|
||||
echo ""
|
||||
echo "To stop: ./ngx_src/nginx-1.28.0/objs/nginx -s stop -c conf/book_named_params.conf -p ."
|
||||
|
||||
Reference in New Issue
Block a user