Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
# CHANGELOG.md

## unrelease
## 0.40.0 (unreleased)
- 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.
- **New Function**: `sqlpage.set_variable(name, value)`
- Returns a URL with the specified variable set to the given value, preserving other existing variables.
- This is a shorthand for `sqlpage.link(sqlpage.path(), json_patch(sqlpage.variables('get'), json_object(name, value)))`.
- **Variable System Improvements**: URL and POST parameters are now immutable, preventing accidental modification. User-defined variables created with `SET` remain mutable.
- **BREAKING**: `$variable` no longer accesses POST parameters. Use `:variable` instead.
- **What changed**: Previously, `$x` would return a POST parameter value if no GET parameter named `x` existed.
- **Fix**: Replace `$x` with `:x` when you need to access form field values.
- **Example**: Change `SELECT $username` to `SELECT :username` when reading form submissions.
- **BREAKING**: `SET $name` no longer overwrites GET (URL) parameters when a URL parameter with the same name exists.
- **BREAKING**: `SET $name` no longer makes GET (URL) parameters inaccessible when a URL parameter with the same name exists.
- **What changed**: `SET $name = 'value'` would previously overwrite the URL parameter `$name`. Now it creates an independent SET variable that shadows the URL parameter.
- **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')`.
- **Example**: If your URL is `page.sql?name=john`, and you do `SET $name = 'modified'`, then:
- `$name` will be `'modified'` (the SET variable)
- The original URL parameter is still preserved and accessible:
- PostgreSQL: `sqlpage.variables('get')->>'name'` returns `'john'`
- SQLite: `json_extract(sqlpage.variables('get'), '$.name')` returns `'john'`
- MySQL: `JSON_UNQUOTE(JSON_EXTRACT(sqlpage.variables('get'), '$.name'))` returns `'john'`
- `sqlpage.variables('get')->>'name'` returns `'john'`
- **New behavior**: Variable lookup now follows this precedence:
- `$variable` checks SET variables first, then URL parameters
- `:variable` checks SET variables first, then POST parameters
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sqlpage"
version = "0.39.1"
version = "0.40.0"
edition = "2021"
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."
keywords = ["web", "sql", "framework"]
Expand Down
2 changes: 1 addition & 1 deletion examples/official-site/component.sql
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,6 @@ select
select
name as title,
icon,
sqlpage.link('component.sql', json_object('component', name)) as link
sqlpage.set_variable('component', name) as link
from component
order by name;
4 changes: 2 additions & 2 deletions examples/official-site/examples/layouts.sql
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ For more information on how to use layouts, see the [shell component documentati
select 'list' as component, 'Available SQLPage shell layouts' as title;
select
column1 as title,
sqlpage.link('', json_object('layout', lower(column1), 'sidebar', $sidebar)) as link,
sqlpage.set_variable('layout', lower(column1)) as link,
$layout = lower(column1) as active,
column3 as icon,
column2 as description
Expand All @@ -43,7 +43,7 @@ from (VALUES
select 'list' as component, 'Available Menu layouts' as title;
select
column1 as title,
sqlpage.link('', json_object('layout', $layout, 'sidebar', column1 = 'Sidebar')) as link,
sqlpage.set_variable('sidebar', column1 = 'Sidebar') as link,
(column1 = 'Sidebar' AND $sidebar = 1) OR (column1 = 'Horizontal' AND $sidebar = 0) as active,
column2 as description,
column3 as icon
Expand Down
4 changes: 2 additions & 2 deletions examples/official-site/functions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ FROM example WHERE component = 'shell' LIMIT 1;
select 'breadcrumb' as component;
select 'SQLPage' as title, '/' as link, 'Home page' as description;
select 'Functions' as title, '/functions.sql' as link, 'List of all functions' as description;
select $function as title, sqlpage.link('functions.sql', json_object('function', $function)) as link where $function IS NOT NULL;
select $function as title, sqlpage.set_variable('function', $function) as link where $function IS NOT NULL;

select 'text' as component, 'SQLPage built-in functions' as title where $function IS NULL;
select '
Expand Down Expand Up @@ -60,7 +60,7 @@ select
select
name as title,
icon,
sqlpage.link('functions.sql', json_object('function', name)) as link
sqlpage.set_variable('function', name) as link
from sqlpage_functions
where $function IS NOT NULL
order by name;
60 changes: 60 additions & 0 deletions examples/official-site/sqlpage/migrations/72_set_variable.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
INSERT INTO
sqlpage_functions (
"name",
"introduced_in_version",
"icon",
"description_md"
)
VALUES
(
'set_variable',
'0.40.0',
'variable',
'Returns a URL that is the same as the current page''s URL, but with a variable set to a new value.

This function is useful when you want to create a link that changes a parameter on the current page, while preserving other parameters.

It is equivalent to `sqlpage.link(sqlpage.path(), json_patch(sqlpage.variables(''get''), json_object(name, value)))`.

### Example

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).

```sql
select ''button'' as component, ''sm'' as size, ''center'' as justify;
select
category as title,
sqlpage.set_variable(''category'', category) as link,
case when $category = category then ''primary'' else ''secondary'' end as color
from categories;
```

### Parameters
- `name` (TEXT): The name of the variable to set.
- `value` (TEXT): The value to set the variable to. If `NULL` is passed, the variable is removed from the URL.
'
);

INSERT INTO
sqlpage_function_parameters (
"function",
"index",
"name",
"description_md",
"type"
)
VALUES
(
'set_variable',
1,
'name',
'The name of the variable to set.',
'TEXT'
),
(
'set_variable',
2,
'value',
'The value to set the variable to.',
'TEXT'
);
30 changes: 24 additions & 6 deletions src/webserver/database/sqlpage_functions/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::{ExecutionContext, RequestInfo};
use crate::webserver::{
database::{
blob_to_data_url::vec_to_data_uri_with_mime, execute_queries::DbConn,
sqlpage_functions::url_parameter_deserializer::URLParameters,
sqlpage_functions::url_parameters::URLParameters,
},
http_client::make_http_client,
request_variables::ParamMap,
Expand Down Expand Up @@ -46,6 +46,7 @@ super::function_definition_macro::sqlpage_functions! {
read_file_as_text((&RequestInfo), file_path: Option<Cow<str>>);
request_method((&RequestInfo));
run_sql((&ExecutionContext, &mut DbConn), sql_file_path: Option<Cow<str>>, variables: Option<Cow<str>>);
set_variable((&ExecutionContext), name: Cow<str>, value: Option<Cow<str>>);

uploaded_file_mime_type((&RequestInfo), upload_name: Cow<str>);
uploaded_file_path((&RequestInfo), upload_name: Cow<str>);
Expand Down Expand Up @@ -376,11 +377,7 @@ async fn link<'a>(
let encoded = serde_json::from_str::<URLParameters>(&parameters).with_context(|| {
format!("link: invalid URL parameters: not a valid json object:\n{parameters}")
})?;
let encoded_str = encoded.get();
if !encoded_str.is_empty() {
url.push('?');
url.push_str(encoded_str);
}
encoded.append_to_path(&mut url);
}
if let Some(hash) = hash {
url.push('#');
Expand Down Expand Up @@ -612,6 +609,27 @@ async fn run_sql<'a>(
Ok(Some(Cow::Owned(String::from_utf8(json_results_bytes)?)))
}

async fn set_variable<'a>(
context: &'a ExecutionContext,
name: Cow<'a, str>,
value: Option<Cow<'a, str>>,
) -> anyhow::Result<String> {
let mut params = URLParameters::new();

for (k, v) in &context.url_params {
if k == &name {
continue;
}
params.push_single_or_vec(k, v.clone());
}

if let Some(value) = value {
params.push_single_or_vec(&name, SingleOrVec::Single(value.into_owned()));
}

Ok(params.with_empty_path())
}

#[tokio::test]
async fn test_hash_password() {
let s = hash_password(Some("password".to_string()))
Expand Down
2 changes: 1 addition & 1 deletion src/webserver/database/sqlpage_functions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ mod function_definition_macro;
mod function_traits;
pub(super) mod functions;
mod http_fetch_request;
mod url_parameter_deserializer;
mod url_parameters;

use sqlparser::ast::FunctionArg;

Expand Down
138 changes: 0 additions & 138 deletions src/webserver/database/sqlpage_functions/url_parameter_deserializer.rs

This file was deleted.

Loading