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
+37
View File
@@ -0,0 +1,37 @@
# Master-Detail Forms (Nested Forms)
This example shows how to handle inserting data into multiple tables
with a one-to-many relationship.
The example uses a **SQLite** database with the following schema:
| Database Schema | SQLPage Form |
| --- | --- |
| ![db schema](./screenshots/db-schema.png) | ![user addition](./screenshots//user-add-screenshot.png) |
A single master page features two forms based on two related tables or views.
Users insert data in the master form first to update information from the parent table.
Then, they can insert data in the detail forms to update information from the child tables.
This example application contains a main form to create users,
and a second form to create their addresses.
Once a user has been added, multiple addresses can be added to it.
See https://github.com/sqlpage/SQLPage/discussions/16 for more details.
The main idea is to create two separate forms.
In this example, we put both forms on the same page, in [`edit-user.sql`](./edit-user.sql).
The first one is an edition form for the already-existing user record,
and the second is a form to add an address to the user.
When you initially load the user creation form,
we do not display the address form.
Only when the user has been created,
you are redirected to the user edition form that contains the address form.
## Screenshots
![homepage](./screenshots/home-screenshot.png)
![user addition](./screenshots//user-add-screenshot.png)
@@ -0,0 +1,8 @@
services:
web:
image: lovasoa/sqlpage:main
ports:
- "8080:8080"
volumes:
- .:/var/www
- ./sqlpage:/etc/sqlpage
@@ -0,0 +1,25 @@
SELECT 'shell' AS component, 'User Management App' AS title, 'user' AS icon, '/' AS link;
SELECT 'form' AS component,
'Edit user' AS title,
'insert_user.sql' || COALESCE('?id=' || $id, '') AS action;
SELECT 'First name' AS name,
TRUE AS required,
(SELECT first_name FROM "user" WHERE id = CAST($id AS INT)) AS value;
SELECT 'Last name' AS name,
TRUE AS required,
(SELECT last_name FROM "user" WHERE id = CAST($id AS INT)) AS value;
SELECT 'Email' AS name,
'email' AS type,
(SELECT email FROM "user" WHERE id = CAST($id AS INT)) AS value;
SELECT 'list' AS component, 'Addresses' AS title WHERE $id IS NOT NULL;
SELECT street || ', ' || city || ', ' || country AS title FROM address WHERE user_id = CAST($id AS INT);
SELECT 'form' AS component, 'Add address' AS title, 'insert_address.sql?user_id=' || $id AS action WHERE $id IS NOT NULL;
SELECT 'Street' AS name, TRUE AS required WHERE $id IS NOT NULL;
SELECT 'City' AS name, TRUE AS required WHERE $id IS NOT NULL;
SELECT 'Country' AS name, TRUE AS required WHERE $id IS NOT NULL;
+10
View File
@@ -0,0 +1,10 @@
SELECT 'hero' as component,
'SQLPage Form Demo' as title,
'This application allows you to manage a list of users and their addresses' as description_md,
'https://upload.wikimedia.org/wikipedia/commons/thumb/e/e4/Lac_de_Zoug.jpg/640px-Lac_de_Zoug.jpg' as image,
'edit_user.sql' as link,
'Create a new user' as link_text;
SELECT 'list' AS component, 'Users' AS title;
SELECT first_name || ' ' || last_name AS title, email AS description, 'edit_user.sql?id=' || id AS link FROM "user";
SELECT 'Add a new user' AS title, 'edit_user.sql' AS link, TRUE AS active;
@@ -0,0 +1,4 @@
INSERT INTO address (user_id, street, city, country) VALUES ($user_id, :Street, :City, :Country)
RETURNING
'redirect' AS component,
'edit_user.sql?id=' || user_id AS link;
@@ -0,0 +1,6 @@
INSERT INTO user (id, first_name, last_name, email) VALUES (CAST($id AS INT), :"First name", :"Last name", :Email)
ON CONFLICT (id) DO -- this syntax is PostgreSQL-specific. In SQLite, use ON CONFLICT IGNORE.
UPDATE SET first_name = excluded.first_name, last_name = excluded.last_name, email = excluded.email
RETURNING
'redirect' AS component,
'edit_user.sql?id=' || id AS link;
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

@@ -0,0 +1,14 @@
CREATE TABLE "user"(
id INTEGER PRIMARY KEY,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
email TEXT
);
CREATE TABLE "address"(
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES "user"(id),
street TEXT NOT NULL,
city TEXT NOT NULL,
country TEXT NOT NULL
);
+65
View File
@@ -0,0 +1,65 @@
GET http://localhost:8080
HTTP 200
[Asserts]
header "Content-Type" contains "text/html"
body contains "SQLPage Form Demo"
body contains "Users"
body contains "Add a new user"
body not contains "An error occurred"
POST http://localhost:8080/insert_user.sql?id=987654
Content-Type: application/x-www-form-urlencoded
`First%20name=Hurl&Last%20name=Masterdetail&Email=hurl-masterdetail%40example.com`
HTTP 302
[Asserts]
header "Location" == "edit_user.sql?id=987654"
GET http://localhost:8080/edit_user.sql?id=987654
HTTP 200
[Asserts]
body contains "Edit user"
body contains "Hurl"
body contains "Masterdetail"
body contains "hurl-masterdetail@example.com"
body contains "Addresses"
body contains "Add address"
body not contains "An error occurred"
POST http://localhost:8080/insert_address.sql?user_id=987654
Content-Type: application/x-www-form-urlencoded
`Street=42%20Hurl%20Street&City=Testville&Country=Exampleland`
HTTP 302
[Asserts]
header "Location" == "edit_user.sql?id=987654"
GET http://localhost:8080/edit_user.sql?id=987654
HTTP 200
[Asserts]
body contains "Hurl"
body contains "Masterdetail"
body contains "42 Hurl Street, Testville, Exampleland"
body not contains "An error occurred"
POST http://localhost:8080/insert_user.sql?id=987654
Content-Type: application/x-www-form-urlencoded
`First%20name=Hurl&Last%20name=Updated&Email=hurl-updated-masterdetail%40example.com`
HTTP 302
[Asserts]
header "Location" == "edit_user.sql?id=987654"
GET http://localhost:8080/edit_user.sql?id=987654
HTTP 200
[Asserts]
body contains "Hurl"
body contains "Updated"
body contains "hurl-updated-masterdetail@example.com"
body contains "42 Hurl Street, Testville, Exampleland"
body not contains "Masterdetail"
body not contains "An error occurred"
GET http://localhost:8080
HTTP 200
[Asserts]
body contains "Hurl Updated"
body contains "hurl-updated-masterdetail@example.com"
body not contains "An error occurred"