diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7eb315db..20485478 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -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..ccf8bc1b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
# CHANGELOG.md
+## v0.39.0
+ - 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.
+ -
+ - Fixed a memory corruption issue in the builtin odbc driver manager
+
+
## v0.38.0
- Added support for the Open Database Connectivity (ODBC) standard.
- This makes SQLPage compatible with many new databases, including:
diff --git a/Cargo.lock b/Cargo.lock
index 6ae80844..b4b1df95 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",
@@ -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.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
+checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2"
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.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f"
+checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623"
dependencies = [
"clap_builder",
"clap_derive",
@@ -946,9 +946,9 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.5.49"
+version = "4.5.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730"
+checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0"
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"
@@ -2326,9 +2333,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 +2345,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"
@@ -2515,9 +2522,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 +2535,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 +2566,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",
]
@@ -2685,14 +2692,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 +2708,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",
@@ -2815,9 +2822,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 +2832,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 +2883,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 +2899,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 +2923,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 +2965,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 +2990,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 +3002,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 +3025,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 +3057,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 +3080,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 +3104,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"
@@ -3474,9 +3482,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 +3597,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 +3703,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 +3768,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 +3781,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 +3790,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",
@@ -3823,9 +3831,9 @@ dependencies = [
[[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",
@@ -3943,7 +3951,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 +4032,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,15 +4083,15 @@ 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",
"serde_core",
@@ -4094,9 +4102,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 +4227,7 @@ dependencies = [
[[package]]
name = "sqlpage"
-version = "0.38.0"
+version = "0.39.0"
dependencies = [
"actix-multipart",
"actix-rt",
@@ -4298,7 +4306,7 @@ dependencies = [
"atoi",
"base64 0.22.1",
"bigdecimal",
- "bitflags 2.9.4",
+ "bitflags 2.10.0",
"byteorder",
"bytes",
"chrono",
@@ -4320,7 +4328,7 @@ dependencies = [
"hex",
"hkdf",
"hmac",
- "indexmap 2.11.4",
+ "indexmap 2.12.0",
"itoa",
"libc",
"libsqlite3-sys",
@@ -4425,9 +4433,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",
@@ -4654,7 +4662,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,9 +4780,9 @@ checksum = "70ba288e709927c043cbe476718d37be306be53fb1fafecd0dbe36d072be2580"
[[package]]
name = "unicode-ident"
-version = "1.0.19"
+version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
+checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
[[package]]
name = "unicode-normalization"
@@ -4805,8 +4813,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",
]
@@ -5337,7 +5346,7 @@ checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732"
dependencies = [
"android-activity",
"atomic-waker",
- "bitflags 2.9.4",
+ "bitflags 2.10.0",
"block2",
"calloop",
"cfg_aliases",
@@ -5413,7 +5422,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",
diff --git a/Cargo.toml b/Cargo.toml
index c7248abd..465464e9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "sqlpage"
-version = "0.38.0"
+version = "0.39.0"
edition = "2021"
description = "Build data user interfaces entirely in SQL. A web server that takes .sql files and formats the query result using pre-made configurable professional-looking components."
keywords = ["web", "sql", "framework"]
@@ -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..b4c99c33 100644
--- a/README.md
+++ b/README.md
@@ -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)
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..dabd6d76 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/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)}}