Skip to content

Commit e93056e

Browse files
lovasoacursoragent
andauthored
Add sqlpage.set_variable(name, value) function and update docs (#1124)
* feat: Add sqlpage.set_variable function Co-authored-by: contact <[email protected]> * Refactor: Fix set_variable serialization and update tests Co-authored-by: contact <[email protected]> * fix tests: no json_extract on mssql * Refactor: Update URLParameters handling in set_variable function - Replaced serde_json::Map with a custom URLParameters struct for better management of URL parameters. - Introduced methods for handling single and vector values in URLParameters. - Updated tests to reflect changes in the set_variable function's behavior. * cargo fmt * clippy * retsore set var test * remove redundant test * ensure set_variable only takes into account GET variables, not SET * factor url parameter setting code * v0.40 * sqlpage.set_variable links to "?" when no parameter is present - Renamed URLParameters module for clarity and removed the deprecated url_parameter_deserializer. - Updated the set_variable function to return parameters directly instead of appending to a URL. - Adjusted related function calls to reflect changes in URL parameter management. --------- Co-authored-by: Cursor Agent <[email protected]>
1 parent 8289bc6 commit e93056e

File tree

16 files changed

+321
-183
lines changed

16 files changed

+321
-183
lines changed

CHANGELOG.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
# CHANGELOG.md
22

3-
## unrelease
3+
## 0.40.0 (unreleased)
4+
- Fixed a bug in `sqlpage.link`: a link with no path (link to the current page) and no url parameter now works as expected. It used to keep the existing url parameters instead of removing them. `sqlpage.link('', '{}')` now returns `'?'` instead of the empty string.
5+
- **New Function**: `sqlpage.set_variable(name, value)`
6+
- Returns a URL with the specified variable set to the given value, preserving other existing variables.
7+
- This is a shorthand for `sqlpage.link(sqlpage.path(), json_patch(sqlpage.variables('get'), json_object(name, value)))`.
48
- **Variable System Improvements**: URL and POST parameters are now immutable, preventing accidental modification. User-defined variables created with `SET` remain mutable.
59
- **BREAKING**: `$variable` no longer accesses POST parameters. Use `:variable` instead.
610
- **What changed**: Previously, `$x` would return a POST parameter value if no GET parameter named `x` existed.
711
- **Fix**: Replace `$x` with `:x` when you need to access form field values.
812
- **Example**: Change `SELECT $username` to `SELECT :username` when reading form submissions.
9-
- **BREAKING**: `SET $name` no longer overwrites GET (URL) parameters when a URL parameter with the same name exists.
13+
- **BREAKING**: `SET $name` no longer makes GET (URL) parameters inaccessible when a URL parameter with the same name exists.
1014
- **What changed**: `SET $name = 'value'` would previously overwrite the URL parameter `$name`. Now it creates an independent SET variable that shadows the URL parameter.
1115
- **Fix**: This is generally the desired behavior. If you need to access the original URL parameter after setting a variable with the same name, extract it from the JSON returned by `sqlpage.variables('get')`.
1216
- **Example**: If your URL is `page.sql?name=john`, and you do `SET $name = 'modified'`, then:
1317
- `$name` will be `'modified'` (the SET variable)
1418
- The original URL parameter is still preserved and accessible:
15-
- PostgreSQL: `sqlpage.variables('get')->>'name'` returns `'john'`
16-
- SQLite: `json_extract(sqlpage.variables('get'), '$.name')` returns `'john'`
17-
- MySQL: `JSON_UNQUOTE(JSON_EXTRACT(sqlpage.variables('get'), '$.name'))` returns `'john'`
19+
- `sqlpage.variables('get')->>'name'` returns `'john'`
1820
- **New behavior**: Variable lookup now follows this precedence:
1921
- `$variable` checks SET variables first, then URL parameters
2022
- `:variable` checks SET variables first, then POST parameters

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "sqlpage"
3-
version = "0.39.1"
3+
version = "0.40.0"
44
edition = "2021"
55
description = "Build data user interfaces entirely in SQL. A web server that takes .sql files and formats the query result using pre-made configurable professional-looking components."
66
keywords = ["web", "sql", "framework"]

examples/official-site/component.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,6 @@ select
149149
select
150150
name as title,
151151
icon,
152-
sqlpage.link('component.sql', json_object('component', name)) as link
152+
sqlpage.set_variable('component', name) as link
153153
from component
154154
order by name;

examples/official-site/examples/layouts.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ For more information on how to use layouts, see the [shell component documentati
3030
select 'list' as component, 'Available SQLPage shell layouts' as title;
3131
select
3232
column1 as title,
33-
sqlpage.link('', json_object('layout', lower(column1), 'sidebar', $sidebar)) as link,
33+
sqlpage.set_variable('layout', lower(column1)) as link,
3434
$layout = lower(column1) as active,
3535
column3 as icon,
3636
column2 as description
@@ -43,7 +43,7 @@ from (VALUES
4343
select 'list' as component, 'Available Menu layouts' as title;
4444
select
4545
column1 as title,
46-
sqlpage.link('', json_object('layout', $layout, 'sidebar', column1 = 'Sidebar')) as link,
46+
sqlpage.set_variable('sidebar', column1 = 'Sidebar') as link,
4747
(column1 = 'Sidebar' AND $sidebar = 1) OR (column1 = 'Horizontal' AND $sidebar = 0) as active,
4848
column2 as description,
4949
column3 as icon

examples/official-site/functions.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ FROM example WHERE component = 'shell' LIMIT 1;
1111
select 'breadcrumb' as component;
1212
select 'SQLPage' as title, '/' as link, 'Home page' as description;
1313
select 'Functions' as title, '/functions.sql' as link, 'List of all functions' as description;
14-
select $function as title, sqlpage.link('functions.sql', json_object('function', $function)) as link where $function IS NOT NULL;
14+
select $function as title, sqlpage.set_variable('function', $function) as link where $function IS NOT NULL;
1515

1616
select 'text' as component, 'SQLPage built-in functions' as title where $function IS NULL;
1717
select '
@@ -60,7 +60,7 @@ select
6060
select
6161
name as title,
6262
icon,
63-
sqlpage.link('functions.sql', json_object('function', name)) as link
63+
sqlpage.set_variable('function', name) as link
6464
from sqlpage_functions
6565
where $function IS NOT NULL
6666
order by name;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
INSERT INTO
2+
sqlpage_functions (
3+
"name",
4+
"introduced_in_version",
5+
"icon",
6+
"description_md"
7+
)
8+
VALUES
9+
(
10+
'set_variable',
11+
'0.40.0',
12+
'variable',
13+
'Returns a URL that is the same as the current page''s URL, but with a variable set to a new value.
14+
15+
This function is useful when you want to create a link that changes a parameter on the current page, while preserving other parameters.
16+
17+
It is equivalent to `sqlpage.link(sqlpage.path(), json_patch(sqlpage.variables(''get''), json_object(name, value)))`.
18+
19+
### Example
20+
21+
Let''s say you have a list of products, and you want to filter them by category. You can use `sqlpage.set_variable` to create links that change the category filter, without losing other potential filters (like a search query or a sort order).
22+
23+
```sql
24+
select ''button'' as component, ''sm'' as size, ''center'' as justify;
25+
select
26+
category as title,
27+
sqlpage.set_variable(''category'', category) as link,
28+
case when $category = category then ''primary'' else ''secondary'' end as color
29+
from categories;
30+
```
31+
32+
### Parameters
33+
- `name` (TEXT): The name of the variable to set.
34+
- `value` (TEXT): The value to set the variable to. If `NULL` is passed, the variable is removed from the URL.
35+
'
36+
);
37+
38+
INSERT INTO
39+
sqlpage_function_parameters (
40+
"function",
41+
"index",
42+
"name",
43+
"description_md",
44+
"type"
45+
)
46+
VALUES
47+
(
48+
'set_variable',
49+
1,
50+
'name',
51+
'The name of the variable to set.',
52+
'TEXT'
53+
),
54+
(
55+
'set_variable',
56+
2,
57+
'value',
58+
'The value to set the variable to.',
59+
'TEXT'
60+
);

src/webserver/database/sqlpage_functions/functions.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use super::{ExecutionContext, RequestInfo};
22
use crate::webserver::{
33
database::{
44
blob_to_data_url::vec_to_data_uri_with_mime, execute_queries::DbConn,
5-
sqlpage_functions::url_parameter_deserializer::URLParameters,
5+
sqlpage_functions::url_parameters::URLParameters,
66
},
77
http_client::make_http_client,
88
request_variables::ParamMap,
@@ -46,6 +46,7 @@ super::function_definition_macro::sqlpage_functions! {
4646
read_file_as_text((&RequestInfo), file_path: Option<Cow<str>>);
4747
request_method((&RequestInfo));
4848
run_sql((&ExecutionContext, &mut DbConn), sql_file_path: Option<Cow<str>>, variables: Option<Cow<str>>);
49+
set_variable((&ExecutionContext), name: Cow<str>, value: Option<Cow<str>>);
4950

5051
uploaded_file_mime_type((&RequestInfo), upload_name: Cow<str>);
5152
uploaded_file_path((&RequestInfo), upload_name: Cow<str>);
@@ -376,11 +377,7 @@ async fn link<'a>(
376377
let encoded = serde_json::from_str::<URLParameters>(&parameters).with_context(|| {
377378
format!("link: invalid URL parameters: not a valid json object:\n{parameters}")
378379
})?;
379-
let encoded_str = encoded.get();
380-
if !encoded_str.is_empty() {
381-
url.push('?');
382-
url.push_str(encoded_str);
383-
}
380+
encoded.append_to_path(&mut url);
384381
}
385382
if let Some(hash) = hash {
386383
url.push('#');
@@ -612,6 +609,27 @@ async fn run_sql<'a>(
612609
Ok(Some(Cow::Owned(String::from_utf8(json_results_bytes)?)))
613610
}
614611

612+
async fn set_variable<'a>(
613+
context: &'a ExecutionContext,
614+
name: Cow<'a, str>,
615+
value: Option<Cow<'a, str>>,
616+
) -> anyhow::Result<String> {
617+
let mut params = URLParameters::new();
618+
619+
for (k, v) in &context.url_params {
620+
if k == &name {
621+
continue;
622+
}
623+
params.push_single_or_vec(k, v.clone());
624+
}
625+
626+
if let Some(value) = value {
627+
params.push_single_or_vec(&name, SingleOrVec::Single(value.into_owned()));
628+
}
629+
630+
Ok(params.with_empty_path())
631+
}
632+
615633
#[tokio::test]
616634
async fn test_hash_password() {
617635
let s = hash_password(Some("password".to_string()))

src/webserver/database/sqlpage_functions/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ mod function_definition_macro;
22
mod function_traits;
33
pub(super) mod functions;
44
mod http_fetch_request;
5-
mod url_parameter_deserializer;
5+
mod url_parameters;
66

77
use sqlparser::ast::FunctionArg;
88

src/webserver/database/sqlpage_functions/url_parameter_deserializer.rs

Lines changed: 0 additions & 138 deletions
This file was deleted.

0 commit comments

Comments
 (0)