chore: import upstream snapshot with attribution
CI / compile_and_lint (push) Failing after 0s
CI / docker_build (linux/amd64, -linux-amd64-duckdb, duckdb) (push) Failing after 0s
CI / docker_build (linux/arm64, -linux-arm64, minimal) (push) Failing after 2s
CI / docker_build (linux/arm64, -linux-arm64-duckdb, duckdb) (push) Failing after 1s
CI / docker_build (linux/amd64, minimal) (push) Failing after 1s
CI / test (, sqlite, sqlite::memory:) (push) Has been skipped
CI / test (mssql, mssql, mssql://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / test (mysql, mysql, mysql://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / test (oracle, oracle, Driver=Oracle 21 ODBC driver;Dbq=//127.0.0.1:1521/FREEPDB1;Uid=root;Pwd=Password123!) (push) Has been skipped
CI / test (postgres, odbc, Driver=PostgreSQL Unicode;Server=127.0.0.1;Port=5432;Database=sqlpage;UID=root;PWD=Password123!, true) (push) Has been skipped
CI / test (postgres, postgres, postgres://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / playwright (push) Has been skipped
CI / docker_build (linux/arm/v7, -linux-arm-v7, minimal) (push) Failing after 0s
CI / hurl_examples (push) Failing after 8s
deploy website / deploy_official_site (push) Failing after 1s
CI / hurl (${{ matrix.example }}) (push) Has been skipped
CI / docker_push (duckdb) (push) Has been cancelled
CI / docker_push (minimal) (push) Has been cancelled
CI / windows_test (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:31:57 +08:00
commit d718c5a372
986 changed files with 74597 additions and 0 deletions
@@ -0,0 +1,40 @@
# Handling forms with a variable number of fields
This example shows how to handle forms with a number of fields
that is not known in advance, but depends on the contents of the database.
## The model
We have a database with a table
`products` that contains the products,
a table `orders` that contains the orders,
and a table `order_items` that contains the items of each order.
We want to display a form to create a new order, with a field for each product.
We cannot know in advance how many products there are,
so we cannot write a simple insert statement for `orders_items`,
like
```sql
INSERT INTO order_items (product_id, quantity)
VALUES (:product_1, :product_1_quantity);
```
Instead, we use a single POST variable of type array to store the
product IDs and quantities, like so:
```sql
SELECT
'product_quantity[]' AS name,
'Quantity of ' || name AS label
FROM products
```
And then we parse the array in SQLite with the [JSON_EACH](https://www.sqlite.org/json1.html#jeach) function.
## Screenshots
![](./screenshots/home.png)
![](./screenshots/order-form.png)
![](./screenshots/order.png)
@@ -0,0 +1,8 @@
services:
web:
image: lovasoa/sqlpage:main
ports:
- "8080:8080"
volumes:
- .:/var/www
- ./sqlpage:/etc/sqlpage
@@ -0,0 +1,15 @@
SELECT 'shell' AS component, 'Products' AS title;
SELECT 'list' AS component, 'Products' AS title;
SELECT 'Add a new product' AS title,
'red' AS color,
'new_product_form.sql' AS link,
TRUE AS active;
SELECT 'Pass an order' AS title,
'blue' AS color,
'order_form.sql' AS link,
TRUE AS active;
SELECT
name AS title,
price || '' AS description
FROM products;
@@ -0,0 +1,8 @@
SELECT 'shell' AS component, 'New product' AS title;
SELECT 'form' as component,
'New product' as title,
'Create product' as validate,
'new_product_insert.sql' as action;
SELECT 'Name' as name;
SELECT 'Price' as name, 'number' as type;
@@ -0,0 +1,6 @@
INSERT INTO products (name, price)
VALUES (:Name, :Price)
RETURNING
'redirect' AS component,
'index.sql' AS link
;
@@ -0,0 +1,18 @@
SELECT 'shell' AS component, 'Order' AS title;
SELECT 'form' as component,
'Pass an order' as title,
'Order' as validate,
'order_insert.sql' as action;
SELECT 'Name' as name, 'Your full name' AS placeholder;
SELECT 'Email' as name, 'Your email address' AS placeholder;
SELECT id AS name,
'Quantity of ' || name || '' AS label,
'Number of ' || name || ' you wish to order, for ' || price || ' € each.' AS description,
'number' AS type,
1 AS step,
0 as min,
0 as value
FROM products
ORDER BY id;
@@ -0,0 +1,12 @@
INSERT INTO orders(customer_name, customer_email)
VALUES (:Name, :Email);
INSERT INTO order_items(order_id, quantity, product_id)
SELECT
last_insert_rowid() AS order_id, -- The id of the order we just inserted. Requires SQLPage v0.9.0 or later.
CAST(value AS INTEGER) AS quantity,
CAST(key AS INTEGER) AS product_id -- extracts converts the field name into a number
FROM JSON_EACH(sqlpage.variables('post')) -- Iterates on all posted variables. Requires SQLPage v0.15.0 or later
WHERE product_id > 0 -- we used the product id as a variable name in the product quatntity form fields
and CAST(value AS INTEGER) > 0
RETURNING 'redirect' as component, 'orders.sql?id=' || order_id as link;
@@ -0,0 +1,28 @@
SELECT 'alert' as component,
format('Thanks %s!', customer_name) as title,
'analyze' as icon,
'teal' as color,
format('Your order is being processed.
You will shortly receive an email on %s with a receipt.',
customer_email) as description
from orders where id = $id;
SELECT 'index.sql' as link,
'Back to homepage' as title;
SELECT 'list' AS component,
'Order summary' AS title;
SELECT
quantity || ' x ' || name AS title,
'Subtotal: ' || quantity || ' x ' || price || ' € = ' || (quantity * price) || '' AS description
FROM order_items
INNER JOIN products ON products.id = order_items.product_id
WHERE order_id = $id;
SELECT
'Total: ' || SUM(quantity * price) || '' AS title,
'red' AS color,
TRUE AS active
FROM order_items
INNER JOIN products ON products.id = order_items.product_id
WHERE order_id = $id;
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

@@ -0,0 +1,21 @@
CREATE TABLE products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(255) NOT NULL,
price DECIMAL(10, 2) NOT NULL
);
CREATE TABLE orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
customer_name VARCHAR(255) NOT NULL,
customer_email VARCHAR(255) NOT NULL,
date_created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE order_items (
order_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders (id),
FOREIGN KEY (product_id) REFERENCES products (id),
PRIMARY KEY (order_id, product_id)
);
@@ -0,0 +1,6 @@
-- Insert statements to populate the 'products' table with unusual products
INSERT INTO products (name, price) VALUES ('Invisible Umbrella', 99.99);
INSERT INTO products (name, price) VALUES ('Silent Alarm Clock', 49.95);
INSERT INTO products (name, price) VALUES ('Pet Rock 2.0', 19.99);
INSERT INTO products (name, price) VALUES ('Chocolate Teapot', 39.99);
INSERT INTO products (name, price) VALUES ('Square Wheels for Your Bicycle', 29.99);
@@ -0,0 +1,34 @@
GET http://localhost:8080/order_form.sql
HTTP 200
[Asserts]
body contains "Pass an order"
body contains "Quantity of Invisible Umbrella"
body contains "Quantity of Silent Alarm Clock"
body contains "Quantity of Pet Rock 2.0"
body not contains "An error occurred"
POST http://localhost:8080/order_insert.sql
[FormParams]
Name: Ada Lovelace
Email: ada@example.com
1: 2
2: 1
3: 0
HTTP 302
[Captures]
location: header "Location"
[Asserts]
header "Location" matches "^orders\\.sql\\?id=\\d+$"
GET http://localhost:8080/{{location}}
HTTP 200
[Asserts]
body contains "Thanks Ada Lovelace!"
body contains "2 x Invisible Umbrella"
body contains "Subtotal: 2 x 99.99"
body contains "199.98"
body contains "1 x Silent Alarm Clock"
body contains "Subtotal: 1 x 49.95"
body contains "Total: 249.93 €"
body not contains "Pet Rock 2.0"
body not contains "An error occurred"