diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7eb315db..47625c5d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,7 +54,7 @@ jobs: db_url: "mssql://root:Password123!@127.0.0.1/sqlpage" - database: odbc container: postgres - db_url: "Driver=/usr/lib/x86_64-linux-gnu/odbc/psqlodbcw.so;Server=127.0.0.1;Port=5432;Database=sqlpage;UID=root;PWD=Password123!" + db_url: "Driver=PostgreSQL Unicode;Server=127.0.0.1;Port=5432;Database=sqlpage;UID=root;PWD=Password123!" setup_odbc: true steps: - uses: actions/checkout@v4 @@ -74,6 +74,8 @@ jobs: env: DATABASE_URL: ${{ matrix.db_url }} RUST_BACKTRACE: 1 + MALLOC_CHECK_: 3 + MALLOC_PERTURB_: 10 windows_test: runs-on: windows-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 66bdae84..ddc1a143 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # CHANGELOG.md +## v0.39.1 (unreleased) + - More precise server timing tracking to debug performance issues + - Fix missing server timing header in some cases + - Implement nice error messages for some header-related errors such as invalid header values. + - `compress_responses` is now set to `false` by default in the configuration. + - When response compression is enabled, additional buffering is needed. Users reported a better experience with pages that load more progressively, reducing the time before the pages' shell is rendered. + - When SQLPage is deployed behind a reverse proxy, compressing responses between sqlpage and the proxy is wasteful. + - In the table component, allow simple objects in custom_actions instead of requiring arrays of objects. + - Fatser icon loading. Previously, even a page containing a single icon required downloading and parsing a ~2MB file. This resulted in a delay where pages initially appeared with a blank space before icons appeared. Icons are now inlined inside pages and appear instantaneously. + - Updated tabler icons to 3.35 + +## v0.39.0 (2025-10-28) + - Ability to execute sql for URL paths with another extension. If you create sitemap.xml.sql, it will be executed for example.com/sitemap.xml + - Display source line info in errors even when the database does not return a precise error position. In this case, the entire problematic SQL statement is referenced. + - The shell with a vertical sidebar can now have "active" elements, just like the horizontal header bar. + - New `edit_url`, `delete_url`, and `custom_actions` properties in the [table](https://sql-page.com/component.sql?component=table) component to easily add nice icon buttons to a table. + - SQLPage now sets the [`Server-Timing` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Server-Timing) in development. So when you have a page that loads slowly, you can open your browser's network inspector, click on the slow request, then open the timing tab to understand where it's spending its time. + - image + - Fixed a memory corruption issue in the builtin odbc driver manager + - ODBC: fix using globally installed system drivers by their name in debian-based linux distributions. + - New [login](https://sql-page.com/component.sql?component=table) component. + + ## v0.38.0 - Added support for the Open Database Connectivity (ODBC) standard. - This makes SQLPage compatible with many new databases, including: @@ -33,7 +56,7 @@ - https://github.com/apache/datafusion-sqlparser-rs/blob/main/changelog/0.59.0.md ## v0.37.0 - - We now cryptographically sign the Windows app during releases, which proves the file hasn’t been tampered with. Once the production certificate is active, Windows will show a "verified publisher" and should stop showing screens saying "This app might harm your device", "Windows protected your PC" or "Are you sure you want to run this application ?". + - We now cryptographically sign the Windows app during releases, which proves the file hasn’t been tampered with. Once the production certificate is active, Windows will show a "verified publisher" and should stop showing screens saying "This app might harm your device", "Windows protected your PC" or "Are you sure you want to run this application ?". - Thanks to https://signpath.io for providing us with a windows signing certificate ! - Added a new parameter `encoding` to the [fetch](https://sql-page.com/functions.sql?function=fetch) function: - All [standard web encodings](https://encoding.spec.whatwg.org/#concept-encoding-get) are supported. @@ -59,7 +82,7 @@ - The form component now considers numbers and their string representation as equal when comparing the `value` parameter and the values from the `options` parameter in dropdowns. This makes it easier to use variables (which are always strings) in the value parameter in order to preserve a dropdown field value across page reloads. The following is now valid: - ```sql select 'form' as component; - select + select 'select' as type, true as create_new, true as dropdown, @@ -74,17 +97,17 @@ ## v0.36.0 - added support for the MONEY and SMALLMONEY types in MSSQL. - - include [math functions](https://sqlite.org/lang_mathfunc.html) in the builtin sqlite3 database. + - include [math functions](https://sqlite.org/lang_mathfunc.html) in the builtin sqlite3 database. - the sqlpage binary can now help you create new empty migration files from the command line: ``` ❯ ./sqlpage create-migration my_new_table Migration file created: sqlpage/migrations/20250627095944_my_new_table.sql - ``` + ``` - New [modal](https://sql-page.com/component.sql?component=modal) component - In bar charts: Sort chart categories by name instead of first appearance. This is useful when displaying cumulative bar charts with some series missing data for some x values. - Updated tabler to v1.4 https://github.com/tabler/tabler/releases/tag/%40tabler%2Fcore%401.4.0 - Updated tabler-icons to v3.34 (19 new icons) https://tabler.io/changelog#/changelog/tabler-icons-3.34 - - Added support for partially private sites when using OIDC single sign-on: + - Added support for partially private sites when using OIDC single sign-on: - The same SQLPage application can now have both publicly accessible and private pages accessible to users authenticated with SSO. - This allows easily creating a "log in page" that redirects to the OIDC provider. - See the [configuration](./configuration.md) for `oidc_protected_paths` @@ -168,84 +191,84 @@ ## v0.34 (2025-03-23) ### ✨ Top Features at a Glance -- **Safer deletion flows** in lists -- **Better table styling control** with CSS updates -- **Right-to-Left language support** -- **HTML-enhanced Markdown** in text components -- **Sticky table footers** for better data presentation +- **Safer deletion flows** in lists +- **Better table styling control** with CSS updates +- **Right-to-Left language support** +- **HTML-enhanced Markdown** in text components +- **Sticky table footers** for better data presentation ### 🔒 Security First -#### **POST-based Deletions** -List component's `delete_link` now uses secure POST requests: +#### **POST-based Deletions** +List component's `delete_link` now uses secure POST requests: ```sql SELECT 'list' AS component; SELECT 'Delete me' AS title, 'delete_item.sql?id=77' AS delete_link; ``` *Prevents accidental deletions by web crawlers and follows REST best practices* -#### **Protected Internal Files** -- Files/folders starting with `.` (e.g., `.utils/`) are now inaccessible +#### **Protected Internal Files** +- Files/folders starting with `.` (e.g., `.utils/`) are now inaccessible - Perfect for internal scripts used with `sqlpage.run_sql()` ### 🎨 UI & Component Upgrades -#### **Table Styling Revolution** +#### **Table Styling Revolution** ```css /* Before: .price | After: */ -._col_price { +._col_price { background: #f8f9fa; border-right: 2px solid #dee2e6; } ``` -- New CSS class pattern: `._col_{column_name}` -- Fixes [#830](https://github.com/sqlpage/SQLPage/issues/830) +- New CSS class pattern: `._col_{column_name}` +- Fixes [#830](https://github.com/sqlpage/SQLPage/issues/830) -#### **Column component** +#### **Column component** ```sql SELECT 'columns' AS component; SELECT 'View details' AS title; -- No button shown ``` -- Columns without button text now hide empty buttons +- Columns without button text now hide empty buttons - Cleaner interfaces by default -#### **Sticky Table Footers** +#### **Sticky Table Footers** ```sql -SELECT +SELECT 'table' AS component, true AS freeze_footers; -SELECT +SELECT 'Total' AS label, SUM(price) AS value, true AS _sqlpage_footer; ``` -- Keep summary rows visible during scroll +- Keep summary rows visible during scroll - Use `_sqlpage_footer` on your final data row ### 🌍 Internationalization -#### **Right-to-Left Support** +#### **Right-to-Left Support** ```sql SELECT 'shell' AS component, true AS rtl; ``` -- Enable RTL mode per page via shell component +- Enable RTL mode per page via shell component - Perfect for Arabic, Hebrew, and Persian content ### 📝 Content Handling -#### **Rich Text Power** +#### **Rich Text Power** ```sql SELECT 'text' AS component, '
**Important!** - + New *HTML-enhanced* content. -
' + ' AS unsafe_contents_md; ``` -- New `unsafe_contents_md` allows HTML+Markdown mixing +- New `unsafe_contents_md` allows HTML+Markdown mixing -#### **Base64 Image Support** +#### **Base64 Image Support** ```markdown ![Alt text](data:image/png;base64,iVBORw0KGg...) ``` -- Embed images directly in Markdown fields +- Embed images directly in Markdown fields ### ⚙️ Configuration Tweaks ```json @@ -257,18 +280,18 @@ SELECT 'text' AS component, - **Markdown safety controls** to change markdown rendering settings ### 🐛 Notable Fixes -- **SQL Server** - Fixed TINYINT handling crashes -- **Anchor Links** +- **SQL Server** + Fixed TINYINT handling crashes +- **Anchor Links** Corrected display in tables with fixed headers -- **Form Inputs** +- **Form Inputs** Proper handling of `0` values in number fields ### 💡 Upgrade Guide -1. **CSS Updates** +1. **CSS Updates** Search/replace `.your_column` → `._col_your_column` if you have custom css targetting tables. -2. **Deletion Flows** - Test list components using `delete_link`. +2. **Deletion Flows** + Test list components using `delete_link`. You can now add a check that the request method is POST if you want to forbid deletions by simply loading pages. [View full configuration options →](./configuration.md) @@ -300,8 +323,8 @@ SELECT 'text' AS component, ### 1. Routing & URL Enhancements 🔀 -#### **Clean URLs:** -Access your pages without the extra “.sql” suffix. For instance, if your file is `page.sql`, you can now use either: +#### **Clean URLs:** +Access your pages without the extra “.sql” suffix. For instance, if your file is `page.sql`, you can now use either: | Old URL | New URL | |---|---| @@ -309,7 +332,7 @@ Access your pages without the extra “.sql” suffix. For instance, if your fil Big thanks to [@guspower](https://github.com/guspower) for their contributions! -#### **Complete Routing Rewrite:** +#### **Complete Routing Rewrite:** We overhauled our request routing system for smoother, more predictable routing across every request. --- @@ -318,8 +341,8 @@ We overhauled our request routing system for smoother, more predictable routing #### **sqlpage.fetch (Calling External Services)** -- **HTTP Basic Authentication:** - SQLPage’s `sqlpage.fetch(request)` now supports HTTP Basic Auth. Easily call APIs requiring a username/password. For example: +- **HTTP Basic Authentication:** + SQLPage’s `sqlpage.fetch(request)` now supports HTTP Basic Auth. Easily call APIs requiring a username/password. For example: ```sql SET result = sqlpage.fetch(json_object( @@ -327,10 +350,10 @@ We overhauled our request routing system for smoother, more predictable routing 'username', 'user', 'password', 'pass' )); - ``` + ``` Check out the [[fetch documentation](https://sql-page.com/documentation.sql?component=fetch#component)](https://sql-page.com/documentation.sql?component=fetch#component) for more. -- **Smarter Fetch Errors & Headers Defaults:** +- **Smarter Fetch Errors & Headers Defaults:** Get clearer error messages if your HTTP request definition is off (unknown fields, etc.). Plus, if you omit the `headers` parameter, SQLPage now sends a default User‑Agent header that includes the SQLPage version. - New Functions: [`sqlpage.request_body`](https://sql-page.com/functions.sql?function=request_body) and [`sqlpage.request_body_base64`](https://sql-page.com/functions.sql?function=request_body_base64) @@ -345,86 +368,86 @@ We overhauled our request routing system for smoother, more predictable routing ); ``` -- **New Function: [sqlpage.headers](https://sql-page.com/functions.sql?function=headers):** +- **New Function: [sqlpage.headers](https://sql-page.com/functions.sql?function=headers):** Easily manage and inspect HTTP headers with the brand‑new [`sqlpage.headers`](https://sql-page.com/functions.sql?function=headers) function. ### 3. UI Component Enhancements 🎨 #### **Table & Card Components** -- **Table CSS Fixes:** +- **Table CSS Fixes:** We fixed a bug where table cells weren’t getting the right CSS classes—your tables now align perfectly. -- **Native Number Formatting:** - Numeric values in tables are now automatically formatted to your visitor’s locale with proper thousands separators and decimal points, and sorted numerically. - _Example:_ +- **Native Number Formatting:** + Numeric values in tables are now automatically formatted to your visitor’s locale with proper thousands separators and decimal points, and sorted numerically. + _Example:_ ![Number Formatting Example](https://github.com/user-attachments/assets/ba51a63f-b9ce-4ab2-a6dd-dfa8e22396de) -- **Enhanced Card Layouts:** - Customizing your `card` components is now easier: - - The `embed` property auto‑appends the `_sqlpage_embed` parameter for embeddable fragments. - - When rendering an embedded page, the `shell` component is replaced by `shell-empty` to avoid duplicate headers and metadata. +- **Enhanced Card Layouts:** + Customizing your `card` components is now easier: + - The `embed` property auto‑appends the `_sqlpage_embed` parameter for embeddable fragments. + - When rendering an embedded page, the `shell` component is replaced by `shell-empty` to avoid duplicate headers and metadata. ![Card Layout Example](https://github.com/user-attachments/assets/c5b58402-178a-441e-8966-fd8e341b02bc) #### **Form Component Boosts** -- **Auto‑Submit Forms:** - Set `auto_submit` to true and your form will instantly submit on any field change—ideal for dashboard filters. - *Example:* +- **Auto‑Submit Forms:** + Set `auto_submit` to true and your form will instantly submit on any field change—ideal for dashboard filters. + *Example:* ```sql SELECT 'form' AS component, 'Filter Results' AS title, true AS auto_submit; SELECT 'date' AS name; ``` -- **Dynamic Options for Dropdowns:** - Use `options_source` to load dropdown options dynamically from another SQL file. Perfect for autocomplete with large option sets. - *Example:* +- **Dynamic Options for Dropdowns:** + Use `options_source` to load dropdown options dynamically from another SQL file. Perfect for autocomplete with large option sets. + *Example:* ```sql SELECT 'form' AS component, 'Select Country' AS title, 'countries.sql' AS options_source; SELECT 'country' AS name; ``` -- **Markdown in Field Descriptions:** +- **Markdown in Field Descriptions:** With the new `description_md` property, render markdown in form field descriptions for improved guidance. -- **Improved Header Error Messages:** +- **Improved Header Error Messages:** Now you’ll get more helpful errors if header components (e.g., `json`, `cookie`) are used incorrectly. --- ### 4. Chart, Icons & CSS Updates 📊 -- **ApexCharts Upgrade:** +- **ApexCharts Upgrade:** We updated ApexCharts to [[v4.4.0](https://github.com/apexcharts/apexcharts.js/releases/tag/v4.4.0)](https://github.com/apexcharts/apexcharts.js/releases/tag/v4.4.0) for smoother charts and minor bug fixes. -- **Tabler Icons & CSS:** - Enjoy a refreshed look: - - Tabler Icons are now [[v3.30.0](https://tabler.io/changelog#/changelog/tabler-icons-3.30)](https://tabler.io/changelog#/changelog/tabler-icons-3.30) with many new icons. +- **Tabler Icons & CSS:** + Enjoy a refreshed look: + - Tabler Icons are now [[v3.30.0](https://tabler.io/changelog#/changelog/tabler-icons-3.30)](https://tabler.io/changelog#/changelog/tabler-icons-3.30) with many new icons. - The CSS framework has been upgraded to [[Tabler 1.0.0](https://github.com/tabler/tabler/releases/tag/v1.0.0)](https://github.com/tabler/tabler/releases/tag/v1.0.0) for improved consistency and a sleeker interface. --- ### 5. CSV Import & Error Handling 📥 -- **Enhanced CSV Error Messages:** +- **Enhanced CSV Error Messages:** More descriptive error messages when a CSV import fails (via `copy` and file upload). -- **Postgres CSV Bug Fix:** - A bug that caused subsequent requests to fail after a CSV import error on PostgreSQL is now fixed. +- **Postgres CSV Bug Fix:** + A bug that caused subsequent requests to fail after a CSV import error on PostgreSQL is now fixed. (See [Issue #788](https://github.com/sqlpage/SQLPage/issues/788) for details.) --- ### 6. SQL Parser & Advanced SQL Support 🔍 -**Upgraded SQL Parser ([v0.54](https://github.com/apache/datafusion-sqlparser-rs/blob/main/changelog/0.54.0.md)):** -Our sqlparser is now at [v0.54](https://github.com/apache/datafusion-sqlparser-rs/blob/main/changelog/0.54.0.md), with support for advanced SQL syntax: +**Upgraded SQL Parser ([v0.54](https://github.com/apache/datafusion-sqlparser-rs/blob/main/changelog/0.54.0.md)):** +Our sqlparser is now at [v0.54](https://github.com/apache/datafusion-sqlparser-rs/blob/main/changelog/0.54.0.md), with support for advanced SQL syntax: -- **INSERT...SELECT...RETURNING:** +- **INSERT...SELECT...RETURNING:** ```sql INSERT INTO users (name, email) SELECT :name, :email WHERE :name IS NOT NULL RETURNING 'redirect' AS component, 'user.sql?id=' || id AS link; ``` -- **PostgreSQL’s overlaps operator:** +- **PostgreSQL’s overlaps operator:** ```sql SELECT 'card' AS component, event_name AS title, @@ -435,7 +458,7 @@ Our sqlparser is now at [v0.54](https://github.com/apache/datafusion-sqlparser-r OVERLAPS ($start_filter::timestamp, $end_filter::timestamp); ``` -- **MySQL’s INSERT...SET syntax:** +- **MySQL’s INSERT...SET syntax:** ```sql INSERT INTO users SET name = :name, email = :email; @@ -597,7 +620,7 @@ This is a bugfix release. ### 🤖 Easy APIs - **Enhanced CSV Support**: The [CSV component](https://sql-page.com/component.sql?component=csv) can now create URLs that trigger a CSV download directly on page load. - This finally makes it possible to allow the download of large datasets as CSV - - This makes it possible to create an API that returns data as CSV and can be easily exposed to other software for interoperabily. + - This makes it possible to create an API that returns data as CSV and can be easily exposed to other software for interoperabily. - **Easy [json](https://sql-page.com/component.sql?component=json) APIs** - The json component now accepts a second sql query, and will return the results as a json array in a very resource-efficient manner. This makes it easier and faster than ever to build REST APIs entirely in SQL. - ```sql @@ -626,7 +649,7 @@ This is a bugfix release. ) as dropdown_item from performance; ``` - + ### 📈 Table & Search Improvements - **Initial Search Value**: Pre-fill the search bar with a default value in tables with `initial_search_value`, making it easier to set starting filters. - **Faster Sorting and Searching**: Table filtering and sorting has been entirely rewritten. @@ -655,9 +678,9 @@ This is a bugfix release. - ``` $ sqlpage --help 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. - + Usage: sqlpage [OPTIONS] - + Options: -w, --web-root The directory where the .sql files are located -d, --config-dir The directory where the sqlpage.json configuration, the templates, and the migrations are located @@ -690,7 +713,7 @@ This is a bugfix release. - Chart component: fix the labels of pie charts displaying too many decimal places. - ![pie chart](https://github.com/user-attachments/assets/6cc4a522-b9dd-4005-92bc-dc92b16c7293) - You can now create a `404.sql` file anywhere in your SQLPage project to handle requests to non-existing pages. This allows you to create custom 404 pages, or create [nice URLs](https://sql-page.com/your-first-sql-website/custom_urls.sql) that don't end with `.sql`. - - Create if `/folder/404.sql` exists, then it will be called for all URLs that start with `folder` and do not match an existing file. + - Create if `/folder/404.sql` exists, then it will be called for all URLs that start with `folder` and do not match an existing file. - Updated SQL parser to [v0.50.0](https://github.com/sqlparser-rs/sqlparser-rs/blob/main/CHANGELOG.md#0500-2024-08-15) - Support postgres String Constants with Unicode Escapes, like `U&'\2713'`. Fixes https://github.com/sqlpage/SQLPage/discussions/511 - New [big_number](https://sql-page.com/documentation.sql?component=big_number#component) component to display key statistics and indicators in a large, easy-to-read format. Useful for displaying KPIs, metrics, and other important numbers in dashboards and reports. @@ -709,7 +732,7 @@ This is a bugfix release. - Nicer inline code style in markdown. - Fixed `width` attribute in the card component not being respected when the specified width was < 6. - Fixed small inaccuracies in decimal numbers leading to unexpectedly long numbers in the output, such as `0.47000000000000003` instead of `0.47`. -- [chart component](https://sql-page.com/documentation.sql?component=chart#component) +- [chart component](https://sql-page.com/documentation.sql?component=chart#component) - TreeMap charts in the chart component allow you to visualize hierarchical data structures. - Timeline charts allow you to visualize time intervals. - Fixed multiple small display issues in the chart component. @@ -755,9 +778,9 @@ Allow loading javascript ESM modules in the shell component with the new `javasc Added `text` and `post_html` properties to the [html](https://sql-page.com/documentation.sql?component=html#component) component. This allows to include sanitized user-generated content in the middle of custom HTML. ```sql -select +select 'html' as component; -select +select 'Username: ' as html, 'username that will be safely escaped: <"& ' as text, '' as post_html; diff --git a/Cargo.lock b/Cargo.lock index 6ae80844..d65fa768 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "bytes", "futures-core", "futures-sink", @@ -31,14 +31,14 @@ dependencies = [ "actix-tls", "actix-utils", "base64 0.22.1", - "bitflags 2.9.4", + "bitflags 2.10.0", "brotli 8.0.2", "bytes", "bytestring", "derive_more 2.0.1", "encoding_rs", "flate2", - "foldhash", + "foldhash 0.1.5", "futures-core", "h2", "http 0.2.12", @@ -214,7 +214,7 @@ dependencies = [ "cookie", "derive_more 2.0.1", "encoding_rs", - "foldhash", + "foldhash 0.1.5", "futures-core", "futures-util", "impl-more", @@ -290,9 +290,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -325,7 +325,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.9.4", + "bitflags 2.10.0", "cc", "cesu8", "jni", @@ -699,9 +699,9 @@ checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bigdecimal" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a22f228ab7a1b23027ccc6c350b72868017af7ea8356fbdf19f8d991c690013" +checksum = "560f42649de9fa436b73517378a147ec21f6c997a546581df4b4b31677828934" dependencies = [ "autocfg", "libm", @@ -718,7 +718,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -740,11 +740,11 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -862,7 +862,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "log", "polling", "rustix 0.38.44", @@ -872,9 +872,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.41" +version = "1.2.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" dependencies = [ "find-msvc-tools", "jobserver", @@ -899,9 +899,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -936,9 +936,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.49" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f" +checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" dependencies = [ "clap_builder", "clap_derive", @@ -946,9 +946,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.49" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730" +checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" dependencies = [ "anstream", "anstyle", @@ -1233,9 +1233,9 @@ dependencies = [ [[package]] name = "csv-core" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" dependencies = [ "memchr", ] @@ -1382,9 +1382,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", "serde_core", @@ -1722,9 +1722,9 @@ checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" [[package]] name = "flate2" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "miniz_oxide", @@ -1753,6 +1753,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.5.0" @@ -1984,7 +1990,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.11.4", + "indexmap 2.12.0", "slab", "tokio", "tokio-util", @@ -2018,10 +2024,6 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] [[package]] name = "hashbrown" @@ -2029,7 +2031,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -2037,6 +2039,11 @@ name = "hashbrown" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] [[package]] name = "hashlink" @@ -2177,9 +2184,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", @@ -2190,9 +2197,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -2203,11 +2210,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -2218,42 +2224,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -2326,9 +2328,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.4" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown 0.16.0", @@ -2338,9 +2340,9 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -2424,9 +2426,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.81" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -2515,9 +2517,9 @@ checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libflate" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45d9dfdc14ea4ef0900c1cddbc8dcd553fbaacd8a4a282cf4018ae9dd04fb21e" +checksum = "249fa21ba2b59e8cbd69e722f5b31e1b466db96c937ae3de23e8b99ead0d1383" dependencies = [ "adler32", "core2", @@ -2528,12 +2530,12 @@ dependencies = [ [[package]] name = "libflate_lz77" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e0d73b369f386f1c44abd9c570d5318f55ccde816ff4b562fa452e5182863d" +checksum = "a599cb10a9cd92b1300debcef28da8f70b935ec937f44fcd1b70a7c986a11c5c" dependencies = [ "core2", - "hashbrown 0.14.5", + "hashbrown 0.16.0", "rle-decode-fast", ] @@ -2559,7 +2561,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "libc", "redox_syscall 0.5.18", ] @@ -2589,9 +2591,9 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "local-channel" @@ -2685,14 +2687,14 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", "log", "wasi", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2701,7 +2703,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "jni-sys", "log", "ndk-sys", @@ -2747,11 +2749,10 @@ dependencies = [ [[package]] name = "num-bigint-dig" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +checksum = "82c79c15c05d4bf82b6f5ef163104cc81a760d8e874d38ac50ab67c8877b647b" dependencies = [ - "byteorder", "lazy_static", "libm", "num-integer", @@ -2815,9 +2816,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", "rustversion", @@ -2825,9 +2826,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2876,7 +2877,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block2", "libc", "objc2", @@ -2892,7 +2893,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block2", "objc2", "objc2-core-location", @@ -2916,7 +2917,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block2", "objc2", "objc2-foundation", @@ -2958,7 +2959,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block2", "dispatch", "libc", @@ -2983,7 +2984,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block2", "objc2", "objc2-foundation", @@ -2995,7 +2996,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block2", "objc2", "objc2-foundation", @@ -3018,7 +3019,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block2", "objc2", "objc2-cloud-kit", @@ -3050,7 +3051,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block2", "objc2", "objc2-core-location", @@ -3073,8 +3074,9 @@ dependencies = [ [[package]] name = "odbc-sys" -version = "0.27.3" -source = "git+https://github.com/sqlpage/odbc-sys?branch=no-autotools#ae3e15446bb2c5c191f05e7c6affc37dfd6fcabe" +version = "0.27.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1896e52e97c2f0cf997cc627380f1af1ecb3f6c29ce6175047cd38adaadb46f5" dependencies = [ "unix-odbc", ] @@ -3096,9 +3098,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "openidconnect" @@ -3422,9 +3424,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] @@ -3474,9 +3476,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -3589,7 +3591,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", ] [[package]] @@ -3695,7 +3697,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64 0.21.7", - "bitflags 2.9.4", + "bitflags 2.10.0", "serde", "serde_derive", ] @@ -3760,7 +3762,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -3773,7 +3775,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.11.0", @@ -3782,9 +3784,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.32" +version = "0.23.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" dependencies = [ "aws-lc-rs", "log", @@ -3817,15 +3819,15 @@ dependencies = [ "serde", "serde_json", "thiserror 2.0.17", - "webpki-roots 1.0.3", + "webpki-roots 1.0.4", "x509-parser", ] [[package]] name = "rustls-native-certs" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -3844,18 +3846,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.7" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "aws-lc-rs", "ring", @@ -3907,9 +3909,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +checksum = "1317c3bf3e7df961da95b0a56a172a02abead31276215a0497241a7624b487ce" dependencies = [ "dyn-clone", "ref-cast", @@ -3943,7 +3945,7 @@ version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -4024,7 +4026,7 @@ version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.12.0", "itoa", "memchr", "ryu", @@ -4075,17 +4077,17 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.15.0" +version = "3.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093cd8c01b25262b84927e0f7151692158fab02d961e04c979d3903eba7ecc5" +checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.11.4", + "indexmap 2.12.0", "schemars 0.9.0", - "schemars 1.0.4", + "schemars 1.0.5", "serde_core", "serde_json", "serde_with_macros", @@ -4094,9 +4096,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.15.0" +version = "3.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7e6c180db0816026a61afa1cff5344fb7ebded7e4d3062772179f2501481c27" +checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955" dependencies = [ "darling 0.21.3", "proc-macro2", @@ -4219,7 +4221,7 @@ dependencies = [ [[package]] name = "sqlpage" -version = "0.38.0" +version = "0.39.1" dependencies = [ "actix-multipart", "actix-rt", @@ -4298,7 +4300,7 @@ dependencies = [ "atoi", "base64 0.22.1", "bigdecimal", - "bitflags 2.9.4", + "bitflags 2.10.0", "byteorder", "bytes", "chrono", @@ -4320,7 +4322,7 @@ dependencies = [ "hex", "hkdf", "hmac", - "indexmap 2.11.4", + "indexmap 2.12.0", "itoa", "libc", "libsqlite3-sys", @@ -4349,7 +4351,7 @@ dependencies = [ "tokio-util", "url", "uuid", - "webpki-roots 1.0.3", + "webpki-roots 1.0.4", "whoami", ] @@ -4425,9 +4427,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" dependencies = [ "proc-macro2", "quote", @@ -4540,9 +4542,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", @@ -4614,9 +4616,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.16" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" dependencies = [ "bytes", "futures-core", @@ -4654,7 +4656,7 @@ version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.12.0", "toml_datetime", "toml_parser", "winnow", @@ -4772,24 +4774,24 @@ checksum = "70ba288e709927c043cbe476718d37be306be53fb1fafecd0dbe36d072be2580" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-normalization" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] name = "unicode-segmentation" @@ -4805,8 +4807,9 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unix-odbc" -version = "0.1.2" -source = "git+https://github.com/sqlpage/odbc-sys?branch=no-autotools#ae3e15446bb2c5c191f05e7c6affc37dfd6fcabe" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8579f2e2aaba57c09f10990cf9ab50eef8c0155820ed8a72d962c1c05af4a8a" dependencies = [ "cc", ] @@ -4905,9 +4908,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", @@ -4916,25 +4919,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-futures" -version = "0.4.54" +version = "0.4.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" dependencies = [ "cfg-if", "js-sys", @@ -4945,9 +4934,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4955,31 +4944,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.81" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -5001,14 +4990,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.3", + "webpki-roots 1.0.4", ] [[package]] name = "webpki-roots" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" dependencies = [ "rustls-pki-types", ] @@ -5337,7 +5326,7 @@ checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" dependencies = [ "android-activity", "atomic-waker", - "bitflags 2.9.4", + "bitflags 2.10.0", "block2", "calloop", "cfg_aliases", @@ -5386,9 +5375,9 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "x509-parser" @@ -5413,7 +5402,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "dlib", "log", "once_cell", @@ -5448,11 +5437,10 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -5460,9 +5448,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", @@ -5519,9 +5507,9 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -5530,9 +5518,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -5541,9 +5529,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index c7248abd..a436f6bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sqlpage" -version = "0.38.0" +version = "0.39.1" 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"] @@ -79,7 +79,7 @@ clap = { version = "4.5.17", features = ["derive"] } tokio-util = "0.7.12" openidconnect = { version = "4.0.0", default-features = false } encoding_rs = "0.8.35" -odbc-sys = { version = "0.27.1", optional = true } +odbc-sys = { version = "0.27.4", optional = true } [features] @@ -87,10 +87,6 @@ default = [] odbc-static = ["odbc-sys", "odbc-sys/vendored-unix-odbc"] lambda-web = ["dep:lambda-web", "odbc-static"] - -[patch.crates-io] -odbc-sys = { git = "https://github.com/sqlpage/odbc-sys", branch = "no-autotools" } - [build-dependencies] awc = { version = "3", features = ["rustls-0_23-webpki-roots"] } rustls = "0.23" diff --git a/README.md b/README.md index c25c8867..d1692f5d 100644 --- a/README.md +++ b/README.md @@ -164,12 +164,12 @@ To run on a server, you can use [the docker image](https://hub.docker.com/r/lova custom components, and migrations (see [configuration.md](./configuration.md)) to `/etc/sqlpage` in the container. - For instance, you can use: - - `docker run -it --name sqlpage -p 8080:8080 --volume "$(pwd)/source:/var/www" --volume "$(pwd)/configuration:/etc/sqlpage:ro" --rm sqlpage/SQLPage` + - `docker run -it --name sqlpage -p 80:8080 --volume "$(pwd)/source:/var/www" --volume "$(pwd)/configuration:/etc/sqlpage:ro" --rm lovasoa/sqlpage` - And place your website in a folder named `source` and your `sqlpage.json` in a folder named `configuration`. - If you want to build your own docker image, taking the raw sqlpage image as a base is not recommended, since it is extremely stripped down and probably won't contain the dependencies you need. Instead, you can take debian as a base and simply copy the sqlpage binary from the official image to your own image: - ```Dockerfile FROM debian:stable-slim - COPY --from=sqlpage/SQLPage:main /usr/local/bin/sqlpage /usr/local/bin/sqlpage + COPY --from=lovasoa/sqlpage:main /usr/local/bin/sqlpage /usr/local/bin/sqlpage ``` We provide compiled binaries only for the x86_64 architecture, but provide docker images for other architectures, including arm64 and armv7. If you want to run SQLPage on a Raspberry Pi or @@ -186,15 +186,12 @@ An alternative for Mac OS users is to use [SQLPage's homebrew package](https://f ### ODBC Setup +SQLPage supports ODBC connections to connect to databases that don't have native drivers. You can skip this section if you want to use one of the built-in database drivers (SQLite, PostgreSQL, MySQL, Microsoft SQL Server). -SQLPage supports ODBC connections to connect to databases that don't have native drivers, such as Oracle, Snowflake, BigQuery, IBM DB2, and many others. - -On Linux, SQLPage supports both dynamic and static ODBC linking. The Docker image uses the system `unixODBC` (dynamic). -Linux and MacOS release binaries are built with a statically linked unixODBC. +Linux and MacOS release binaries conatain a built-in statically linked ODBC driver manager (unixODBC). You still need to install or provide the database-specific ODBC driver for the database you want to connect to. - #### Install your ODBC database driver - [DuckDB](https://duckdb.org/docs/stable/clients/odbc/overview.html) - [Snowflake](https://docs.snowflake.com/en/developer-guide/odbc/odbc) @@ -362,4 +359,4 @@ Check out our [Contributing Guide](./CONTRIBUTING.md) for detailed instructions Our windows binaries are digitally signed, so they should be recognized as safe by Windows. Free code signing provided by [SignPath.io](https://about.signpath.io/), certificate by [SignPath Foundation](https://signpath.org/). [Contributors](https://github.com/sqlpage/SQLPage/graphs/contributors), [Owners](https://github.com/orgs/sqlpage/people?query=role%3Aowner). -This program will not transfer any information to other networked systems unless specifically requested by the user or the person installing or operating it \ No newline at end of file +This program will not transfer any information to other networked systems unless specifically requested by the user or the person installing or operating it diff --git a/build.rs b/build.rs index a39ea959..3e594482 100644 --- a/build.rs +++ b/build.rs @@ -22,13 +22,17 @@ async fn main() { for h in [ spawn(download_deps(c.clone(), "sqlpage.js")), spawn(download_deps(c.clone(), "sqlpage.css")), - spawn(download_deps(c.clone(), "tabler-icons.svg")), + spawn(download_tabler_icons( + c.clone(), + "https://cdn.jsdelivr.net/npm/@tabler/icons-sprite@3.35.0/dist/tabler-sprite.svg", + )), spawn(download_deps(c.clone(), "apexcharts.js")), spawn(download_deps(c.clone(), "tomselect.js")), spawn(download_deps(c.clone(), "favicon.svg")), ] { h.await.unwrap(); } + set_odbc_rpath(); } fn make_client() -> awc::Client { @@ -171,3 +175,43 @@ fn make_url_path(url: &str) -> PathBuf { ); sqlpage_artefacts.join(filename) } + +async fn download_tabler_icons(client: Rc, sprite_url: &str) { + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); + let icon_map_path = out_dir.join("icons.rs"); + let mut sprite_content = Vec::with_capacity(3 * 1024 * 1024); + copy_url_to_opened_file(&client, sprite_url, &mut sprite_content).await; + let mut file = File::create(icon_map_path).unwrap(); + file.write_all(b"[").unwrap(); + extract_icons_from_sprite(&sprite_content, |name, content| { + writeln!(file, "({name:?}, r#\"{content}\"#),").unwrap(); + }); + file.write_all(b"]").unwrap(); +} + +fn extract_icons_from_sprite(sprite_content: &[u8], mut callback: impl FnMut(&str, &str)) { + let mut sprite_str = std::str::from_utf8(sprite_content).unwrap(); + fn take_between<'a>(s: &mut &'a str, start: &str, end: &str) -> Option<&'a str> { + let start_index = s.find(start)?; + let end_index = s[start_index + start.len()..].find(end)?; + let result = &s[start_index + start.len()..][..end_index]; + *s = &s[start_index + start.len() + end_index + end.len()..]; + Some(result) + } + while let Some(mut symbol_tag) = take_between(&mut sprite_str, "") { + let id = take_between(&mut symbol_tag, "id=\"tabler-", "\"").expect("id not found"); + let content_start = symbol_tag.find('>').unwrap() + 1; + callback(id, &symbol_tag[content_start..]); + } +} + +/// On debian-based linux distributions, odbc drivers are installed in /usr/lib/-linux-gnu/odbc +/// which is not in the default library search path. +fn set_odbc_rpath() { + if cfg!(all(target_os = "linux", feature = "odbc-static")) { + println!( + "cargo:rustc-link-arg=-Wl,-rpath,/usr/lib/{}-linux-gnu/odbc", + std::env::var("TARGET").unwrap().split('-').next().unwrap() + ); + } +} diff --git a/configuration.md b/configuration.md index 92589c42..faa15d79 100644 --- a/configuration.md +++ b/configuration.md @@ -33,7 +33,7 @@ Here are the available configuration options and their default values: | `oidc_scopes` | openid email profile | Space-separated list of [scopes](https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims) your app requests from the OIDC provider. | | `oidc_additional_trusted_audiences` | unset | A list of additional audiences that are allowed in JWT tokens, beyond the client ID. When empty or unset, any additional audience is accepted. For increased security, set to an empty list `[]` to only allow the client ID as audience. | | `max_pending_rows` | 256 | Maximum number of rendered rows that can be queued up in memory when a client is slow to receive them. | -| `compress_responses` | true | When the client supports it, compress the http response body. This can save bandwidth and speed up page loading on slow connections, but can also increase CPU usage and cause rendering delays on pages that take time to render (because streaming responses are buffered for longer than necessary). | +| `compress_responses` | false | When the client supports it, compress the http response body. This can save bandwidth and speed up page loading on slow connections, but can also increase CPU usage and cause rendering delays on pages that take time to render (because streaming responses are buffered for longer than necessary). | | `https_domain` | | Domain name to request a certificate for. Setting this parameter will automatically make SQLPage listen on port 443 and request an SSL certificate. The server will take a little bit longer to start the first time it has to request a certificate. | | `https_certificate_email` | contact@ | The email address to use when requesting a certificate. | | `https_certificate_cache_dir` | ./sqlpage/https | A writeable directory where to cache the certificates, so that SQLPage can serve https traffic immediately when it restarts. | @@ -144,7 +144,7 @@ Example configuration: - **Microsoft Entra ID** (formerly Azure AD) - Documentation: https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app - - Set *oidc_issuer_url* to `https://login.microsoftonline.com/{tenant}/v2.0` + - Set *oidc_issuer_url* to `https://login.microsoftonline.com/{tenant}/v2.0` - ([Find your tenant name](https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc#find-your-apps-openid-configuration-document-uri)) - **GitHub** @@ -193,7 +193,7 @@ For instance, if you want to create a custom `my_component` component, that disp ## Directories -SQLPage needs two important directories to work: the configuration directory, and the web root. +SQLPage needs two important directories to work: the configuration directory, and the web root. ### Configuration directory @@ -255,7 +255,7 @@ This can be useful to clean up temporary tables, rollback transactions that were left open, or other resources that were created during the request. -You can also use this script to close database connections that are +You can also use this script to close database connections that are in an undesirable state, such as being in a transaction that was left open. To close a connection, write a select statement that returns a single row with a single boolean column named `is_healthy`, and set it to false. diff --git a/examples/nginx/README.md b/examples/nginx/README.md index e1b838b2..46f38ee8 100644 --- a/examples/nginx/README.md +++ b/examples/nginx/README.md @@ -43,6 +43,26 @@ This service sets up a MySQL database with predefined credentials and a persiste The `nginx.conf` file contains the NGINX configuration: +### Streaming and compression + +SQLPage streams HTML as it is generated, so browsers can start rendering before the database finishes returning rows. NGINX enables `proxy_buffering` by default, which can delay those first bytes but stores responses for slow clients. Start with a modest buffer configuration and let the proxy handle compression: + +``` + proxy_buffering on; + proxy_buffer_size 16k; + proxy_buffers 4 16k; + + gzip on; + gzip_buffers 2 4k; + gzip_types text/html text/plain text/css application/javascript application/json; + + chunked_transfer_encoding on; +``` + +Keep buffering when you expect slow clients or longer SQLPage queries, increasing the buffer sizes only if responses overflow. When most users are on fast connections reading lightweight pages, consider reducing the buffer counts or flipping to `proxy_buffering off;` to minimise latency, accepting the extra load on SQLPage. See the [proxy buffering](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering), [gzip](https://nginx.org/en/docs/http/ngx_http_gzip_module.html), and [chunked transfer](https://nginx.org/en/docs/http/ngx_http_core_module.html#chunked_transfer_encoding) directives for more guidance. + +When SQLPage runs behind a reverse proxy, set `compress_responses` to `false` in its configuration (documented [here](https://github.com/sqlpage/SQLPage/blob/main/configuration.md)) so that NGINX can perform compression once at the edge. + ### Rate Limiting diff --git a/examples/official-site/component.sql b/examples/official-site/component.sql index f482d50e..05d4c30c 100644 --- a/examples/official-site/component.sql +++ b/examples/official-site/component.sql @@ -75,7 +75,14 @@ select group_concat( 'select ' || char(10) || ( - with t as (select * from json_tree(top.value)), + with t as ( + select *, + case type + when 'array' then json_array_length(value)>1 + else false + end as is_arr + from json_tree(top.value) + ), key_val as (select CASE t.type WHEN 'integer' THEN t.atom @@ -92,8 +99,8 @@ select ELSE parent.key END as key from t inner join t parent on parent.id = t.parent - where ((parent.fullkey = '$' and t.type != 'array') - or (parent.type = 'array' and parent.path = '$')) + where ((parent.fullkey = '$' and not t.is_arr) + or (parent.path = '$' and parent.is_arr)) ), key_val_padding as (select CASE diff --git a/examples/official-site/examples/authentication/create_session_token.sql b/examples/official-site/examples/authentication/create_session_token.sql index 645fedc4..8ea8cd19 100644 --- a/examples/official-site/examples/authentication/create_session_token.sql +++ b/examples/official-site/examples/authentication/create_session_token.sql @@ -4,12 +4,12 @@ delete from user_sessions where created_at < datetime('now', '-1 day'); -- check that the SELECT 'authentication' AS component, 'login.sql?failed' AS link, -- redirect to the login page on error - (SELECT password_hash FROM users WHERE username = :Username) AS password_hash, -- this is a hash of the password 'admin' - :Password AS password; -- this is the password that the user sent through our form in 'index.sql' + (SELECT password_hash FROM users WHERE username = :username) AS password_hash, -- this is a hash of the password 'admin' + :password AS password; -- this is the password that the user sent through our form in 'index.sql' -- if we haven't been redirected, then the password is correct -- create a new session -insert into user_sessions (session_token, username) values (sqlpage.random_string(32), :Username) +insert into user_sessions (session_token, username) values (sqlpage.random_string(32), :username) returning 'cookie' as component, 'session_token' as name, session_token as value; -- redirect to the authentication example home page diff --git a/examples/official-site/examples/authentication/login.sql b/examples/official-site/examples/authentication/login.sql index b634de30..b0a39f5b 100644 --- a/examples/official-site/examples/authentication/login.sql +++ b/examples/official-site/examples/authentication/login.sql @@ -1,10 +1,15 @@ select 'dynamic' as component, properties FROM example WHERE component = 'shell' LIMIT 1; -select 'form' as component, 'Authentication' as title, 'Log in' as validate, 'create_session_token.sql' as action; -select 'Username' as name, 'user' as prefix_icon, 'admin' as placeholder; -select 'Password' as name, 'lock' as prefix_icon, 'admin' as placeholder, 'password' as type; - -select 'alert' as component, 'danger' as color, 'Invalid username or password' as title where $failed is not null; +select + 'login' as component, + 'create_session_token.sql' as action, + '/assets/icon.webp' as image, + 'Demo Login Form' as title, + 'Username' as username, + 'Password' as password, + case when $failed is not null then 'Invalid username or password. In this demo, you can log in with admin / admin.' end as error_message, + 'In this demo, the username is "admin" and the password is "admin".' as footer_md, + 'Log in' as validate; select 'text' as component, ' @@ -12,7 +17,7 @@ select 'text' as component, ' This is a simple example of an authentication form. It uses - - the [`form`](/documentation.sql?component=form#component) component to create a login form + - the [`login`](/documentation.sql?component=login#component) component to create a login form - the [`authentication`](/documentation.sql?component=authentication#component) component to check the user password - the [`cookie`](/documentation.sql?component=cookie#component) component to store a unique session token in the user browser - the [`redirect`](/documentation.sql?component=redirect#component) component to redirect the user to the login page if they are not logged in diff --git a/examples/official-site/extensions-to-sql.md b/examples/official-site/extensions-to-sql.md index 81ee0902..88fa4067 100644 --- a/examples/official-site/extensions-to-sql.md +++ b/examples/official-site/extensions-to-sql.md @@ -1,257 +1,262 @@ -# Extensions to SQL +## How SQLPage runs your SQL -SQLPage makes some special treatment before executing your SQL queries. +SQLPage reads your SQL file and runs one statement at a time. For each statement, it -When executing your SQL file, SQLPage executes each query one at a time. -It doesn't send the whole file as-is to the database engine. +- decides whether to: + - handle it inside SQLPage, or + - prepare it as a (potentially slightly modified) sql statement on the database. +- extracts values from the request to pass them as prepared statements parameters +- runs [`sqlpage.*` functions](/functions) +- passes the database results to components -## Performance +This page explains every step of the process, +with examples and details about differences between how SQLPage understands SQL and how your database does. -See the [performance page](/performance.sql) for details on the optimizations -made to run your queries as fast as possible. +## What runs where -## Variables +### Handled locally by SQLPage -SQL doesn't have its own mechanism for variables. -SQLPage implements variables in the following way: +- Static simple selects (a tiny, fast subset of SELECT) +- Simple variable assignments that use only literals or variables + - All sqlpage functions + -### POST parameters +### Sent to your database -When sending a POST request, most often by sending a form with the -[form component](/component.sql?component=form), the form data is made -available as variables prefixed by a colon. +Everything else: joins, subqueries, arithmetic, database functions, `SELECT @@VERSION`, `CURRENT_TIMESTAMP`, `SELECT *`, expressions, `FROM`, `WHERE`, `GROUP BY`, `ORDER BY`, `LIMIT`/`FETCH`, `WITH`, `DISTINCT`, etc. -So when this form is sent: +### Mixed statements using `sqlpage.*` functions -`form.sql` -```sql -SELECT - 'form' AS component, - 'POST' AS method, -- form defaults to using the HTTP POST method - 'result.sql' AS action; +[`sqlpage.*` functions](/functions.sql) are executed by SQLPage; your database never sees them. They can run: -SELECT - 'age' AS name, - 'How old are you?' AS label, - 'number' AS type; -``` +- Before the query, when used as values inside conditions or parameters. +- After the query, when used as top-level selected columns (applied per row). + +Examples are shown below. + +## Static simple selects + +A *static simple select* is a very restricted `SELECT` that SQLPage can execute entirely by itself. This avoids back and forths between SQLPage and the database for trivial queries. + +To be static and simple, a statement must satisfy all of the following: + +- No `FROM`, `WHERE`, `GROUP BY`, `HAVING`, `ORDER BY`, `LIMIT`/`FETCH`, `WITH`, `DISTINCT`, `TOP`, windowing, locks, or other clauses. +- Each selected item is of the form `value AS alias`. +- Each `value` is either: + - a literal (single-quoted string, number, boolean, or `NULL`), or + - a variable (like `$name`, `:message`) + +That’s it. If any part is more complex, it is not a static simple select and will be sent to the database. -It will make a request to this page: +#### Examples that ARE static (executed by SQLPage) -`result.sql` ```sql -SELECT - 'text' AS component, - 'You are ' || :age || ' years old!' AS contents; +SELECT 'text' AS component, 'Hello' AS contents; +SELECT 'text' AS component, $name AS contents; ``` -`:age` will be substituted by the actual value of the POST parameter. +#### Examples that are NOT static (sent to the database) -### URL parameters +```sql +-- Has string concatenation +select 'from' as component, 'handle_form.sql?id=' || $id as action; + +-- Has WHERE +select 'text' as component, $alert_message as contents where $should_alert; + +-- Uses database functions or expressions +SELECT 1 + 1 AS two; +SELECT CURRENT_TIMESTAMP AS now; +SELECT @@VERSION AS version; -- SQL Server variables +-- Uses a subquery +SELECT (select 1) AS one; +``` -Likewise, URL parameters are available as variables prefixed by a dollar sign. +## Variables + +SQLPage communicates information about incoming HTTP requests to your SQL code through prepared statement variables. +You can use + - `$var` to reference a GET variable (an URL parameter), + - `:var` to reference a POST variable (a value filled by an user in a form field), + - `set var = ...` to set the value of `$var`. -> URL parameters are often called GET parameters because they can originate -> from a form with 'GET' as the method. +### POST parameters -So the previous example can be reworked to handle URL parameters: +Form fields sent with POST are available as `:name`. -`result.sql` ```sql SELECT - 'text' AS component, - 'You are ' || $age || ' years old!' AS contents; + 'form' AS component, + 'POST' AS method, + 'result.sql' AS action; + +SELECT 'age' AS name, 'How old are you?' AS label, 'number' AS type; ``` -By querying this page with this URL: `/request.sql?age=42` -we would get `You are 42 years old!` as a response. +```sql +-- result.sql +SELECT 'text' AS component, 'You are ' || :age || ' years old!' AS contents; +``` -### The `SET` command +### URL parameters -SQLPage overrides the behavior of `SET` statements in SQL to store variables in SQLPage itself instead of running the statement on the database. +Query-string parameters are available as `$name`. ```sql -SET coalesced_post_id = COALESCE($post_id, 0); +SELECT 'text' AS component, 'You are ' || $age || ' years old!' AS contents; +-- /result.sql?age=42 → You are 42 years old! ``` -`SET` statements are transformed into `SELECT` queries, and their result is stored in a `$`-variable: +When a URL parameter is not set, its value is `NULL`. -```sql -SELECT COALESCE($post_id, 0); -``` +### The SET command -We can override a previous `$`-variable: +`SET` stores a value in SQLPage (not in the database). Only strings and `NULL` are stored. ```sql +-- Give a default value to a variable SET post_id = COALESCE($post_id, 0); ``` -### Limitations +- If the right-hand side is purely literals/variables, SQLPage computes it directly. See the section about *static simple select* above. +- If it needs the database (for example, calls a database function), SQLPage runs an internal `SELECT` to compute it and stores the first column of the first row of results. -`$`-variables and `:`-variables are stored by SQLPage, not in the database. +Only a single textual value (**string or `NULL`**) is stored. +`set id = 1` will store the string `'1'`, not the number `1`. -They can only store a string, or null. +On databases with a strict type system, such as PostgreSQL, if you need a number, you will need to cast your variables: `select * from post where id = $id::int`. -As such, they're not designed to store table-valued results. -They will only store the first value of the first column: +Complex structures can be stored as json strings. -```sql -CREATE TABLE t(a, b); -INSERT INTO t(a, b) VALUES (1, 2), (3, 4); +For larger temporary results, prefer temporary tables on your database; do not send them to SQLPage at all. -SET var = (SELECT * FROM t); +## `sqlpage.*` functions --- now $var contains '1' -``` +Functions under the `sqlpage.` prefix run in SQLPage. See the [functions page](/functions.sql). -Temporary table-valued results can be stored in two ways. +They can run: -## Storing large datasets in the database with temporary tables +### Before sending the query (as input values) + +Used inside conditions or parameters, the function is evaluated first and its result is passed to the database. -This is the most efficient method to store large values. ```sql --- Database connections are reused and temporary tables are stored at the --- connection level, so we make sure the table doesn't exist already -DROP TABLE IF EXISTS my_temp_table; -CREATE TEMPORARY TABLE my_temp_table AS -SELECT a, b -FROM my_stored_table ... - --- Insert data from direct values -INSERT INTO my_temp_table(a, b) -VALUES (1, 2), (3, 4); +SELECT * +FROM blog +WHERE slug = sqlpage.path(); ``` -## Storing rich structured data in memory using JSON - -This can be more convenient, but should only be used for small values, because data -is copied from the database into SQLPage memory, and to the database again at each use. +### After receiving results (as top-level selected columns) -You can use the [JSON functions from your database](/blog.sql?post=JSON+in+SQL%3A+A+Comprehensive+Guide). +Used as top-level selected columns, the query is rewritten to first fetch the raw column, and the function is applied per row in SQLPage. -Here are some examples with SQLite: ```sql --- CREATE TABLE my_table(a, b); --- INSERT INTO my_table(a, b) --- VALUES (1, 2), (3, 4); - -SET my_json = ( - SELECT json_group_array(a) - FROM my_table -); --- [1, 3] - -SET my_json = json_array(1, 2, 3); --- [1, 2, 3] +SELECT sqlpage.read_file_as_text(file_path) AS contents +FROM blog_posts; ``` -## Functions - -Functions starting with `sqlpage.` are executed by SQLPage, not by your database engine. -See the [functions page](/functions.sql) for more details. +## Performance -They're either executed before or after the query is run in the database. +See the [performance page](/performance.sql) for details. In short: -### Executing functions *before* sending a query to the database +- Statements sent to the database are prepared and cached. +- Variables and pre-computed values are bound as parameters. +- This keeps queries fast and repeatable. -When they don't process results coming from the database: +## Working with larger temporary results -```sql -SELECT * FROM blog WHERE slug = sqlpage.path() -``` +### Temporary tables in your database -`sqlpage.path()` will get replaced by the result of the function. +When you reuse the same values multiple times in your page, +store them in a temporary table. -### Executing functions *after* receiving results from the database +```sql +DROP TABLE IF EXISTS filtered_posts; +CREATE TEMPORARY TABLE filtered_posts AS +SELECT * FROM posts where category = $category; -When they process results coming from the database: +select 'alert' as component, count(*) || 'results' as title +from filtered_posts; -```sql -SELECT sqlpage.read_file_as_text(blog_post_file) AS title -FROM blog; +select 'list' as component; +select name from filtered_posts; ``` -The query executed will be: +### Small JSON values in variables + +Useful for small datasets that you want to keep in memory. +See the [guide on JSON in SQL](/blog.sql?post=JSON+in+SQL%3A+A+Comprehensive+Guide). ```sql -SELECT blog_post_file AS title FROM blog; +set product = ( + select json_object('name', name, 'price', price) + from products where id = $product_id +); ``` -Then `sqlpage.read_file_as_text()` will be called on each row. - -## Implementation details of variables and functions +## CSV imports -All queries run by SQLPage in the database are first prepared, then executed. +When you write a compatible `COPY ... FROM 'field'` statement and upload a file with the matching form field name, SQLPage orchestrates the import: -Statements are prepared and cached the first time they're encountered by SQLPage. -Then those cached prepared statements are executed at each run, with parameter substitution. +- PostgreSQL: the file is streamed directly to the database using `COPY FROM STDIN`; the database performs the import. +- Other databases: SQLPage reads the CSV and inserts rows using a prepared `INSERT` statement. Options like delimiter, quote, header, escape, and a custom `NULL` string are supported. With a header row, column names are matched by name; otherwise, the order is used. -All variables and function results are cast as text, to let the -database query optimizer know only strings (or nulls) will be passed. - -Examples: +Example: ```sql --- Source query -SELECT * FROM blog WHERE slug = sqlpage.path(); - --- Prepared statement (SQLite syntax) -SELECT * FROM blog WHERE slug = CAST(?1 AS TEXT) +COPY my_table (col1, col2) +FROM 'my_csv' +(DELIMITER ';', HEADER); ``` -```sql --- Source query -SET post_id = COALESCE($post_id, 0); - --- Prepared statement (SQLite syntax) -SELECT COALESCE(CAST(?1 AS TEXT), 0) -``` +The uploaded file should be provided in a form field with `'file' as type, 'my_csv' as name`. -# Data types +## Data types -Each database has its own rich set of data types. -The data modal in SQLPage itself is simpler, mainly composed of text strings and json objects. +Each database has its own usually large set of data types. +SQLPage itself has a much more rudimentary type system. ### From the user to SQLPage -Form fields and URL parameters may contain arrays. These are converted to JSON strings before processing. +Form fields and URL parameters in HTTP are fundamentally untyped. +They are just sequences of bytes. SQLPage requires them to be valid utf8 strings. -For instance, Loading `users.sql?user[]=Tim&user[]=Tom` will result in a single variable `$user` with the textual value `["Tim", "Tom"]`. +SQLPage follows the convention that when a parameter name ends with `[]`, it represents an array. +Arrays in SQLPage are represented as JSON strings. + +Example: In `users.sql?user[]=Tim&user[]=Tom`, `$user` becomes `'["Tim", "Tom"]'` (a JSON string exploitable with your database's builtin json functions). ### From SQLPage to the database -SQLPage sends only text strings (`VARCHAR`) and `NULL`s to the database, since these are the only possible variable and function return values. +SQLPage sends only strings (`TEXT` or `VARCHAR`) and `NULL`s as parameters. ### From the database to SQLPage -Each row of data returned by a SQL query is converted to a JSON object before being passed to components. +Each row returned by the database becomes a JSON object +before its passed to components: -- Each column becomes a key in the json object. If a row has two columns of the same name, they become an array in the json object. -- Each value is converted to the closest JSON value - - all number types map to json numbers, booleans to booleans, and `NULL` to `null`, - - all text types map to json strings - - date and time types map to json strings containing ISO datetime values - - binary values (BLOBs) map to json strings containing [data URLs](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/data) +- Each column is a key. Duplicate column names turn into arrays. +- Numbers, booleans, text, and `NULL` map naturally. +- Dates/times become ISO strings. +- Binary data (BLOBs) becomes a data URL (with mime type auto-detection). #### Example -The following PostgreSQL query: - ```sql -select - 1 as one, - 'x' as my_array, 'y' as my_array, - now() as today, - ''::bytea as my_image; +SELECT + 1 AS one, + 'x' AS my_array, 'y' AS my_array, + now() AS today, + ''::bytea AS my_image; ``` -will result in the following JSON object being passed to components for rendering +Produces something like: ```json { - "one" : 1, - "my_array" : ["x","y"], - "today":"2025-08-30T06:40:13.894918+00:00", - "my_image":"data:image/svg+xml;base64,PHN2Zz48L3N2Zz4=" + "one": 1, + "my_array": ["x", "y"], + "today": "2025-08-30T06:40:13.894918+00:00", + "my_image": "data:image/svg+xml;base64,PHN2Zz48L3N2Zz4=" } ``` \ No newline at end of file diff --git a/examples/official-site/llms.txt.sql b/examples/official-site/llms.txt.sql new file mode 100644 index 00000000..a1f5cda5 --- /dev/null +++ b/examples/official-site/llms.txt.sql @@ -0,0 +1,145 @@ +select + 'http_header' as component, + 'text/markdown; charset=utf-8' as "Content-Type", + 'inline; filename="llms.txt"' as "Content-Disposition"; + +select + 'shell-empty' as component, + '# SQLPage + +> SQLPage is a SQL-only web application framework. It lets you build entire websites and web applications using nothing but SQL queries. Write `.sql` files, and SQLPage executes them, maps results to UI components (handlebars templates), and streams HTML to the browser. + +SQLPage is designed for developers who are comfortable with SQL but want to avoid the complexity of traditional web frameworks. It works with SQLite, PostgreSQL, MySQL, and Microsoft SQL Server, and through ODBC with any other database that has an ODBC driver installed. + +Key features: +- No backend code needed: Your SQL files are your backend +- Component-based UI: Built-in components for forms, tables, charts, maps, and more +- Database-first: Every HTTP request triggers a sequence of SQL queries from a .sql file, the results are rendered with built-in or custom components, defined as .handlebars files in the sqlpage/templates folder. +- Simple deployment: Single binary with no runtime dependencies +- Secure by default: Parameterized queries prevent SQL injection + +## Getting Started + +- [Introduction to SQLPage: installation, guiding principles, and a first example](/your-first-sql-website/tutorial.md): Complete beginner tutorial covering setup, database connections, forms, and deployment + +## Core Documentation + +- [Components reference](/documentation.sql): List of all ' || ( + select + count(*) + from + component + ) || ' built-in UI components with parameters and examples +- [Functions reference](/functions.sql): SQLPage built-in functions for handling requests, encoding data, and more +- [Configuration guide](https://github.com/sqlpage/SQLPage/blob/main/configuration.md): Complete list of configuration options in sqlpage.json + +## Components + +' || ( + select + group_concat ( + '### [' || c.name || '](/component.sql?component=' || c.name || ') + +' || c.description || ' + +' || ( + select + case when exists ( + select + 1 + from + parameter + where + component = c.name + and top_level + ) then '#### Top-level parameters + +' || group_concat ( + '- `' || name || '` (' || type || ')' || case when not optional then ' **REQUIRED**' else '' end || ': ' || description, + char(10) + ) + else + '' + end + from + parameter + where + component = c.name + and top_level + ) || ' + +' || ( + select + case when exists ( + select + 1 + from + parameter + where + component = c.name + and not top_level + ) then '#### Row-level parameters + +' || group_concat ( + '- `' || name || '` (' || type || ')' || case when not optional then ' **REQUIRED**' else '' end || ': ' || description, + char(10) + ) + else + '' + end + from + parameter + where + component = c.name + and not top_level + ) || ' + +', + '' + ) + from + component c + order by + c.name + ) || ' + +## Functions + +' || ( + select + group_concat ( + '### [sqlpage.' || name || '()](/functions.sql?function=' || name || ') +' || replace ( + replace ( + description_md, + char(10) || '#', + char(10) || '###' + ), + ' ', + ' ' + ), + char(10) + ) + from + sqlpage_functions + order by + name + ) || ' + +## Examples + +- [Authentication example](https://github.com/sqlpage/SQLPage/tree/main/examples/user-authentication): Complete user registration and login system +- [CRUD application](https://github.com/sqlpage/SQLPage/tree/main/examples/CRUD%20-%20Authentication): Create, read, update, delete with authentication +- [Image gallery](https://github.com/sqlpage/SQLPage/tree/main/examples/image%20gallery%20with%20user%20uploads): File upload and image display +- [Todo application](https://github.com/sqlpage/SQLPage/tree/main/examples/todo%20application): Simple CRUD app +- [Master-detail forms](https://github.com/sqlpage/SQLPage/tree/main/examples/master-detail-forms): Working with related data +- [Charts example](https://github.com/sqlpage/SQLPage/tree/main/examples/plots%20tables%20and%20forms): Data visualization + +## Optional + +- [Custom components guide](/custom_components.sql): Create your own handlebars components +- [Safety and security](/safety.sql): Understanding SQL injection prevention +- [Docker deployment](https://github.com/sqlpage/SQLPage#with-docker): Running SQLPage in containers +- [Systemd service](https://github.com/sqlpage/SQLPage/blob/main/sqlpage.service): Production deployment setup +- [Repository structure](https://github.com/sqlpage/SQLPage/blob/main/CONTRIBUTING.md): Project organization and contribution guide +' as html; \ No newline at end of file diff --git a/examples/official-site/sqlpage/migrations/01_documentation.sql b/examples/official-site/sqlpage/migrations/01_documentation.sql index ee2b8b51..bd4346ae 100644 --- a/examples/official-site/sqlpage/migrations/01_documentation.sql +++ b/examples/official-site/sqlpage/migrations/01_documentation.sql @@ -810,11 +810,15 @@ INSERT INTO parameter(component, name, description, type, top_level, optional) S ('money', 'Name of a numeric column whose values should be displayed as currency amounts, in the currency defined by the `currency` property. This argument can be repeated multiple times.', 'TEXT', TRUE, TRUE), ('currency', 'The ISO 4217 currency code (e.g., USD, EUR, GBP, etc.) to use when formatting monetary values.', 'TEXT', TRUE, TRUE), ('number_format_digits', 'Maximum number of decimal digits to display for numeric values.', 'INTEGER', TRUE, TRUE), + ('edit_url', 'If set, an edit button will be added to each row. The value of this property should be a URL, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row. Clicking the edit button will take the user to that URL. Added in v0.39.0', 'TEXT', TRUE, TRUE), + ('delete_url', 'If set, a delete button will be added to each row. The value of this property should be a URL, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row. Clicking the delete button will take the user to that URL. Added in v0.39.0', 'TEXT', TRUE, TRUE), + ('custom_actions', 'If set, a column of custom action buttons will be added to each row. The value of this property should be a JSON array of objects, each object defining a button with the following properties: `name` (the text to display on the button), `icon` (the tabler icon name or image link to display on the button), `link` (the URL to navigate to when the button is clicked, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row), and `tooltip` (optional text to display when hovering over the button). Added in v0.39.0', 'JSON', TRUE, TRUE), -- row level ('_sqlpage_css_class', 'For advanced users. Sets a css class on the table row. Added in v0.8.0.', 'TEXT', FALSE, TRUE), ('_sqlpage_color', 'Sets the background color of the row. Added in v0.8.0.', 'COLOR', FALSE, TRUE), ('_sqlpage_footer', 'Sets this row as the table footer. It is recommended that this parameter is applied to the last row. Added in v0.34.0.', 'BOOLEAN', FALSE, TRUE), - ('_sqlpage_id', 'Sets the id of the html tabler row element. Allows you to make links targeting a specific row in a table.', 'TEXT', FALSE, TRUE) + ('_sqlpage_id', 'Sets the id of the html tabler row element. Allows you to make links targeting a specific row in a table.', 'TEXT', FALSE, TRUE), + ('_sqlpage_actions', 'Sets custom action buttons for this specific row in addition to any defined at the table level, The value of this property should be a JSON array of objects, each object defining a button with the following properties: `name` (the text to display on the button), `icon` (the tabler icon name or image link to display on the button), `link` (the URL to navigate to when the button is clicked, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row), and `tooltip` (optional text to display when hovering over the button). Added in v0.39.0', 'JSON', FALSE, TRUE) ) x; INSERT INTO example(component, description, properties) VALUES @@ -994,7 +998,124 @@ GROUP BY This will generate a table with the stores in the first column, and the items in the following columns, with the quantity sold in each store for each item. ', NULL - ); + ), + ( + 'table', +'## Using Action Buttons in a table. + +### Preset Actions: `edit_url` & `delete_url` +Since edit and delete are common actions, the `table` component has dedicated `edit_url` and `delete_url` properties to add buttons for these actions. +The value of these properties should be a URL, containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row. + +### Column with fixed action buttons + +You may want to add custom action buttons to your table rows, for instance to view details, download a file, or perform a custom operation. +For this, the `table` component has a `custom_actions` top-level property that lets you define a column of buttons, each button defined by a name, an icon, a link, and an optional tooltip. + +### Column with variable action buttons + +The `table` component also supports the row level `_sqlpage_actions` column in your data table. +This is helpful if you want a more complex logic, for instance to disable a button on some rows, or to change the link or icon based on the row data. + +> WARNING! +> If the number of array items in `_sqlpage_actions` is not consistent across all rows, the table may not render correctly. +> You can leave blank spaces by including an object with only the `name` property. + +The table has a column of buttons, each button defined by the `_sqlpage_actions` column at the table level, and by the `_sqlpage_actions` property at the row level. + +### `custom_actions` & `_sqlpage_actions` JSON properties. + +Each button is defined by the following properties: +* `name`: sets the column header and the tooltip if no tooltip is provided, +* `tooltip`: text to display when hovering over the button, +* `link`: the URL to navigate to when the button is clicked, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row, +* `icon`: the tabler icon name or image link to display on the button + +### Example using all of the above +' + , + json('[ + { + "component": "table", + "edit_url": "/examples/show_variables.sql?action=edit&update_id={id}", + "delete_url": "/examples/show_variables.sql?action=delete&delete_id={id}", + "custom_actions": { + "name": "history", + "tooltip": "View Standard History", + "link": "/examples/show_variables.sql?action=history&standard_id={id}", + "icon": "history" + } + }, + { + "name": "CalStd", + "vendor": "PharmaCo", + "Product": "P1234", + "lot number": "T23523", + "status": "Available", + "expires on": "2026-10-13", + "_sqlpage_id": 32, + "_sqlpage_actions": [ + { + "name": "View PDF", + "tooltip": "View Presentation", + "link": "https://sql-page.com/pgconf/2024-sqlpage-badass.pdf", + "icon": "file-type-pdf" + }, + { + "name": "Action", + "tooltip": "Set In Use", + "link": "/examples/show_variables.sql?action=set_in_use&standard_id=32", + "icon": "caret-right" + } + ] + }, + { + "name": "CalStd", + "vendor": "PharmaCo", + "Product": "P1234", + "lot number": "T2352", + "status": "In Use", + "expires on": "2026-10-14", + "_sqlpage_id": 33, + "_sqlpage_actions": [ + { + "name": "View PDF", + "tooltip": "View Presentation", + "link": "https://sql-page.com/pgconf/2024-sqlpage-badass.pdf", + "icon": "file-type-pdf" + }, + { + "name": "Action", + "tooltip": "Retire Standard", + "link": "/examples/show_variables.sql?action=retire&standard_id=33", + "icon": "test-pipe-off" + } + ] + }, + { + "name": "CalStd", + "vendor": "PharmaCo", + "Product": "P1234", + "lot number": "A123", + "status": "Discarded", + "expires on": "2026-09-30", + "_sqlpage_id": 31, + "_sqlpage_actions": [ + { + "name": "View PDF", + "tooltip": "View Presentation", + "link": "https://sql-page.com/pgconf/2024-sqlpage-badass.pdf", + "icon": "file-type-pdf" + }, + { + "name": "Action" + } + ] + } +]' +) +); + INSERT INTO component(name, icon, description) VALUES diff --git a/examples/official-site/sqlpage/migrations/07_authentication.sql b/examples/official-site/sqlpage/migrations/07_authentication.sql index fd342fc6..c759dee8 100644 --- a/examples/official-site/sqlpage/migrations/07_authentication.sql +++ b/examples/official-site/sqlpage/migrations/07_authentication.sql @@ -14,7 +14,7 @@ you have two main options: - does not require any external service - gives you fine-grained control over - which pages and actions are protected - - the look of the login form + - the look of the [login form](?component=login) - the duration of the session - the permissions of each user 2. [**Single sign-on**](/sso) @@ -128,12 +128,10 @@ Then, in all the pages that require authentication, you check if the cookie is p You can check if the user has sent the correct password in a form, and if not, redirect them to a login page. -Create a login form in a file called `login.sql`: +Create a login form in a file called `login.sql` that uses the [login component](?component=login): ```sql -select ''form'' as component, ''Authentication'' as title, ''Log in'' as validate, ''create_session_token.sql'' as action; -select ''Username'' as name, ''admin'' as placeholder; -select ''Password'' as name, ''admin'' as placeholder, ''password'' as type; +select ''login'' as component; ``` And then, in `create_session_token.sql` : diff --git a/examples/official-site/sqlpage/migrations/68_login.sql b/examples/official-site/sqlpage/migrations/68_login.sql new file mode 100644 index 00000000..cce32fae --- /dev/null +++ b/examples/official-site/sqlpage/migrations/68_login.sql @@ -0,0 +1,66 @@ +INSERT INTO component(name, icon, description, introduced_in_version) VALUES + ('login', 'password-user', ' +The login component is an authentication form with numerous customization options. +It offers the main functionalities for this type of form. +The user can enter their username and password. +There are many optional attributes such as the use of icons on input fields, the insertion of a link to a page to reset the password, an option for the application to maintain the user''s identity via a cookie. +It is also possible to set the title of the form, display the company logo, or customize the appearance of the form submission button. + +This component should be used in conjunction with other components such as [authentication](component.sql?component=authentication) and [cookie](component.sql?component=cookie). +It does not implement any logic and simply collects the username and password to pass them to the code responsible for authentication. + +A few things to know : +- The form uses the POST method to transmit information to the destination page, +- The user''s username and password are entered into fields with the names `username` and `password`, +- To obtain the values of username and password, you must use the variables `:username` and `:password`, +- To know if the user wants their identity to be remembered, you must read the value of the variable `:remember`. +', '0.39.0'); + +INSERT INTO parameter(component, name, description, type, top_level, optional) SELECT 'login', * FROM (VALUES + ('title','Title of the authentication form.','TEXT',TRUE,TRUE), + ('enctype','Form data encoding.','TEXT',TRUE,TRUE), + ('action','An optional link to a target page that will handle the results of the form. ','TEXT',TRUE,TRUE), + ('error_message','An error message to display above the form, typically shown after a failed login attempt.','TEXT',TRUE,TRUE), + ('username','Label and placeholder for the user account identifier text field.','TEXT',TRUE,FALSE), + ('password','Label and placeholder for the password field.','TEXT',TRUE,FALSE), + ('username_icon','Icon to display on the left side of the input field, on the same line.','ICON',TRUE,TRUE), + ('password_icon','Icon to display on the left side of the input field, on the same line.','ICON',TRUE,TRUE), + ('image','The URL of an centered image displayed before the title.','URL',TRUE,TRUE), + ('forgot_password_text','A text for the link allowing the user to reset their password. If the text is empty, the link is not displayed.','TEXT',TRUE,TRUE), + ('forgot_password_link','The link to the page allowing the user to reset their password.','TEXT',TRUE,TRUE), + ('remember_me_text','A text for the option allowing the user to request the preservation of their identity. If the text is empty, the option is not displayed.','TEXT',TRUE,TRUE), + ('footer','A text placed at the bottom of the authentication form.','TEXT',TRUE,TRUE), + ('footer_md','A markdown text placed at the bottom of the authentication form. Useful for creating links to other pages (creating a new account, contacting technical support, etc.).','TEXT',TRUE,TRUE), + ('validate','The text to display in the button at the bottom of the form that submits the values.','TEXT',TRUE,TRUE), + ('validate_color','The color of the button at the bottom of the form that submits the values. Omit this property to use the default color.','COLOR',TRUE,TRUE), + ('validate_shape','The shape of the validation button.','TEXT',TRUE,TRUE), + ('validate_outline','A color to outline the validation button.','COLOR',TRUE,TRUE), + ('validate_size','The size of the validation button.','TEXT',TRUE,TRUE) +) x; + +-- Insert example(s) for the component +INSERT INTO example(component, description, properties) +VALUES ( + 'login', + 'Using the main options of the login component', + JSON( + '[ + { + "component": "login", + "action": "login.sql", + "image": "../assets/icon.webp", + "title": "Please login to your account", + "username": "Username", + "password": "Password", + "username_icon": "user", + "password_icon": "lock", + "forgot_password_text": "Forgot your password?", + "forgot_password_link": "reset_password.sql", + "remember_me_text": "Remember me", + "footer_md": "Don''t have an account? [Register here](register.sql)", + "validate": "Sign in" + } + ]' + ) + ), + ('login', 'Most basic login form', JSON('[{"component": "login"}]')); diff --git a/examples/official-site/sqlpage/migrations/99_shared_id_class_attributes.sql b/examples/official-site/sqlpage/migrations/99_shared_id_class_attributes.sql index 47e66576..df054541 100644 --- a/examples/official-site/sqlpage/migrations/99_shared_id_class_attributes.sql +++ b/examples/official-site/sqlpage/migrations/99_shared_id_class_attributes.sql @@ -18,7 +18,8 @@ FROM (VALUES ('title', TRUE), ('tracking', TRUE), ('text', TRUE), - ('carousel', TRUE) + ('carousel', TRUE), + ('login', TRUE) ); INSERT INTO parameter(component, top_level, name, description, type, optional) @@ -49,6 +50,7 @@ FROM (VALUES ('timeline', FALSE), ('title', TRUE), ('tracking', TRUE), - ('carousel', TRUE) + ('carousel', TRUE), + ('login', TRUE) ); diff --git a/examples/official-site/your-first-sql-website/nginx.md b/examples/official-site/your-first-sql-website/nginx.md index b910114a..9e156135 100644 --- a/examples/official-site/your-first-sql-website/nginx.md +++ b/examples/official-site/your-first-sql-website/nginx.md @@ -65,6 +65,23 @@ sudo systemctl reload nginx Your SQLPage instance is now hosted behind a reverse proxy using NGINX. You can access it by visiting `http://example.com`. + +### Streaming-friendly proxy settings + +SQLPage streams HTML by default so the browser can render results while the database is still sending rows. +If you have slow SQL queries (you shouldn't), you can add the following directive to your location block: + +```nginx +proxy_buffering off; +``` + +That will allow users to start seeing the top of your pages faster, +but will increase the load on your SQLPage server, and reduce the amount of users you can serve concurrently. + +Refer to the official documentation for [proxy buffering](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering), [gzip](https://nginx.org/en/docs/http/ngx_http_gzip_module.html), and [chunked transfer](https://nginx.org/en/docs/http/ngx_http_core_module.html#chunked_transfer_encoding) when tuning these values. + +When SQLPage sits behind a reverse proxy, set `compress_responses` to `false` [in `sqlpage.json`](https://github.com/sqlpage/SQLPage/blob/main/configuration.md) so that NGINX compresses once at the edge. + ### URL Rewriting URL rewriting is a powerful feature that allows you to manipulate URLs to make them more readable, search-engine-friendly, and easy to maintain. diff --git a/examples/user-authentication/docker-compose.yml b/examples/user-authentication/docker-compose.yml index 5108ac1e..96b78c0e 100644 --- a/examples/user-authentication/docker-compose.yml +++ b/examples/user-authentication/docker-compose.yml @@ -1,6 +1,8 @@ services: web: image: lovasoa/sqlpage:main # main is cutting edge, use sqlpage/SQLPage:latest for the latest stable version + build: + context: "../.." ports: - "8080:8080" volumes: diff --git a/examples/user-authentication/signin.sql b/examples/user-authentication/signin.sql index 4057e44d..bab0e883 100644 --- a/examples/user-authentication/signin.sql +++ b/examples/user-authentication/signin.sql @@ -1,14 +1,9 @@ -SELECT 'form' AS component, +SELECT 'login' AS component, + 'login.sql' AS action, 'Sign in' AS title, - 'Sign in' AS validate, - 'login.sql' AS action; - -SELECT 'username' AS name; -SELECT 'password' AS name, 'password' AS type; - -SELECT 'alert' as component, - 'Sorry' as title, - 'We could not authenticate you. Please log in or [create an account](signup.sql).' as description_md, - 'alert-circle' as icon, - 'red' as color -WHERE $error IS NOT NULL; \ No newline at end of file + 'Username' AS username, + 'Password' AS password, + 'user' AS username_icon, + 'lock' AS password_icon, + case when $error is not null then 'We could not authenticate you. Please log in or [create an account](signup.sql).' end as error_message_md, + 'Sign in' AS validate; \ No newline at end of file diff --git a/sqlpage/templates/login.handlebars b/sqlpage/templates/login.handlebars new file mode 100644 index 00000000..46014bfc --- /dev/null +++ b/sqlpage/templates/login.handlebars @@ -0,0 +1,82 @@ +
+
+
+
+ {{#if image}} +
+ +
+ {{/if}} + {{#if title}} +

{{title}}

+ {{/if}} + {{#if (or error_message error_message_md)}} + + {{/if}} + +
+ {{icon_img (default username_icon 'user-circle')}} + +
+ +
+ {{~icon_img (default password_icon 'key')~}} + +
+ {{#if remember_me_text}} + + {{/if}} +
+ +
+ {{#if (or footer footer_md)}} +
+
+ + {{#if footer}} + {{footer}} + {{else}} + {{#if footer_md}} + {{{markdown footer_md}}} + {{/if}} + {{/if}} + +
+ {{/if}} +
+
+
+
diff --git a/sqlpage/templates/shell.handlebars b/sqlpage/templates/shell.handlebars index 35f31b91..0e1c7a8e 100644 --- a/sqlpage/templates/shell.handlebars +++ b/sqlpage/templates/shell.handlebars @@ -95,7 +95,7 @@ {{~#with (parse_json this)}} {{#if (or (or this.title this.icon) this.image)}}