Merge branch 'main' into feature/table-action-buttons
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
# CHANGELOG.md
|
||||
|
||||
## v0.38.1
|
||||
- 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.
|
||||
|
||||
## v0.38.0
|
||||
- Added support for the Open Database Connectivity (ODBC) standard.
|
||||
- This makes SQLPage compatible with many new databases, including:
|
||||
|
||||
Generated
+57
-57
@@ -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,7 +31,7 @@ dependencies = [
|
||||
"actix-tls",
|
||||
"actix-utils",
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.9.4",
|
||||
"bitflags 2.10.0",
|
||||
"brotli 8.0.2",
|
||||
"bytes",
|
||||
"bytestring",
|
||||
@@ -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",
|
||||
@@ -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",
|
||||
]
|
||||
@@ -1984,7 +1984,7 @@ dependencies = [
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap 2.11.4",
|
||||
"indexmap 2.12.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -2326,9 +2326,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",
|
||||
@@ -2559,7 +2559,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 +2685,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 +2701,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 +2815,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 +2825,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 +2876,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 +2892,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 +2916,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 +2958,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 +2983,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 +2995,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 +3018,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 +3050,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",
|
||||
@@ -3589,7 +3589,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 +3695,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 +3760,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 +3773,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 +3782,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.32"
|
||||
version = "0.23.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40"
|
||||
checksum = "751e04a496ca00bb97a5e043158d23d66b5aabf2e1d5aa2a0aaebb1aafe6f82c"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"log",
|
||||
@@ -3823,9 +3823,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 +3943,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 +4024,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",
|
||||
@@ -4083,7 +4083,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.11.4",
|
||||
"indexmap 2.12.0",
|
||||
"schemars 0.9.0",
|
||||
"schemars 1.0.4",
|
||||
"serde_core",
|
||||
@@ -4219,7 +4219,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlpage"
|
||||
version = "0.38.0"
|
||||
version = "0.38.1"
|
||||
dependencies = [
|
||||
"actix-multipart",
|
||||
"actix-rt",
|
||||
@@ -4298,7 +4298,7 @@ dependencies = [
|
||||
"atoi",
|
||||
"base64 0.22.1",
|
||||
"bigdecimal",
|
||||
"bitflags 2.9.4",
|
||||
"bitflags 2.10.0",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"chrono",
|
||||
@@ -4320,7 +4320,7 @@ dependencies = [
|
||||
"hex",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"indexmap 2.11.4",
|
||||
"indexmap 2.12.0",
|
||||
"itoa",
|
||||
"libc",
|
||||
"libsqlite3-sys",
|
||||
@@ -4425,9 +4425,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.106"
|
||||
version = "2.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||
checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4654,7 +4654,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",
|
||||
@@ -5337,7 +5337,7 @@ checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732"
|
||||
dependencies = [
|
||||
"android-activity",
|
||||
"atomic-waker",
|
||||
"bitflags 2.9.4",
|
||||
"bitflags 2.10.0",
|
||||
"block2",
|
||||
"calloop",
|
||||
"cfg_aliases",
|
||||
@@ -5413,7 +5413,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",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "sqlpage"
|
||||
version = "0.38.0"
|
||||
version = "0.38.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"]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
@@ -95,7 +95,7 @@
|
||||
{{~#with (parse_json this)}}
|
||||
{{#if (or (or this.title this.icon) this.image)}}
|
||||
<li class="nav-item{{#if this.submenu}} dropdown{{/if}}{{#if this.active}} active{{/if}}">
|
||||
<a class="nav-link {{#if this.submenu}}dropdown-toggle{{/if}}" href="{{#if this.link}}{{this.link}}{{else}}#{{/if}}"
|
||||
<a class="nav-link {{#if this.active}}active {{/if}}{{#if this.submenu}}dropdown-toggle{{/if}}" href="{{#if this.link}}{{this.link}}{{else}}#{{/if}}"
|
||||
{{~#if this.submenu}}
|
||||
data-bs-toggle="dropdown"
|
||||
{{!-- commented out until this is fixed: https://github.com/tabler/tabler/issues/2485
|
||||
@@ -125,7 +125,7 @@
|
||||
<div class="dropdown-menu dropdown-menu-end" data-bs-popper="static">
|
||||
{{~#each this.submenu~}}
|
||||
{{#if (or (or this.title this.icon) this.image)}}
|
||||
<a class="dropdown-item" href="{{this.link}}" {{#if this.target}}target="{{this.target}}"{{/if}}>
|
||||
<a class="dropdown-item{{#if this.active}} active{{/if}}" href="{{this.link}}" {{#if this.target}}target="{{this.target}}"{{/if}}>
|
||||
{{~#if this.image~}}
|
||||
<span {{~#if this.title}} class="me-1"{{/if}}>
|
||||
{{~#if (eq ../this.size 'sm')}}
|
||||
|
||||
@@ -27,41 +27,47 @@ impl std::fmt::Display for NiceDatabaseError {
|
||||
)?;
|
||||
if let sqlx::error::Error::Database(db_err) = &self.db_err {
|
||||
let Some(mut offset) = db_err.offset() else {
|
||||
return write!(f, "{}", self.query);
|
||||
write!(f, "{}", self.query)?;
|
||||
self.show_position_info(f)?;
|
||||
return Ok(());
|
||||
};
|
||||
for line in self.query.lines() {
|
||||
if offset > line.len() {
|
||||
offset -= line.len() + 1;
|
||||
} else {
|
||||
highlight_line_offset(f, line, offset);
|
||||
if let Some(query_position) = self.query_position {
|
||||
let start_line = query_position.start.line;
|
||||
let end_line = query_position.end.line;
|
||||
if start_line == end_line {
|
||||
write!(f, "{}: line {}", self.source_file.display(), start_line)?;
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{}: lines {} to {}",
|
||||
self.source_file.display(),
|
||||
start_line,
|
||||
end_line
|
||||
)?;
|
||||
}
|
||||
}
|
||||
self.show_position_info(f)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
write!(f, "{}", self.query)
|
||||
write!(f, "{}", self.query)?;
|
||||
self.show_position_info(f)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NiceDatabaseError {
|
||||
fn show_position_info(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(f, "\n{}", self.source_file.display())?;
|
||||
let _: () = if let Some(query_position) = self.query_position {
|
||||
let start_line = query_position.start.line;
|
||||
let end_line = query_position.end.line;
|
||||
if start_line == end_line {
|
||||
write!(f, ": line {start_line}")?;
|
||||
} else {
|
||||
write!(f, ": lines {start_line} to {end_line}")?;
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for NiceDatabaseError {}
|
||||
|
||||
/// Display a database error with a highlighted line and character offset.
|
||||
/// Display a database error without any position information
|
||||
#[must_use]
|
||||
pub fn display_db_error(
|
||||
source_file: &Path,
|
||||
|
||||
@@ -20,8 +20,9 @@ use sqlparser::dialect::{
|
||||
};
|
||||
use sqlparser::parser::{Parser, ParserError};
|
||||
use sqlparser::tokenizer::Token::{self, SemiColon, EOF};
|
||||
use sqlparser::tokenizer::{TokenWithSpan, Tokenizer};
|
||||
use sqlparser::tokenizer::{Location, Span, TokenWithSpan, Tokenizer};
|
||||
use sqlx::any::AnyKind;
|
||||
use std::fmt::Write;
|
||||
use std::ops::ControlFlow;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
@@ -241,11 +242,29 @@ fn extract_query_start(stmt: &impl Spanned) -> SourceSpan {
|
||||
}
|
||||
|
||||
fn syntax_error(err: ParserError, parser: &Parser, sql: &str) -> ParsedStatement {
|
||||
let location = parser.peek_token_no_skip().span;
|
||||
ParsedStatement::Error(anyhow::Error::from(err).context(format!(
|
||||
"Parsing failed: SQLPage couldn't understand the SQL file. Please check for syntax errors:\n\n{}",
|
||||
quote_source_with_highlight(sql, location.start.line, location.start.column)
|
||||
)))
|
||||
let Span {
|
||||
start: Location {
|
||||
line: start_line,
|
||||
column: start_column,
|
||||
},
|
||||
end: Location { line: end_line, .. },
|
||||
} = parser.peek_token_no_skip().span;
|
||||
|
||||
let mut msg = String::from(
|
||||
"Parsing failed: SQLPage couldn't understand the SQL file. Please check for syntax errors on ",
|
||||
);
|
||||
if start_line == end_line {
|
||||
write!(&mut msg, "line {start_line}:").unwrap();
|
||||
} else {
|
||||
write!(&mut msg, "lines {start_line} to {end_line}:").unwrap();
|
||||
}
|
||||
write!(
|
||||
&mut msg,
|
||||
"\n{}",
|
||||
quote_source_with_highlight(sql, start_line, start_column)
|
||||
)
|
||||
.unwrap();
|
||||
ParsedStatement::Error(anyhow::Error::from(err).context(msg))
|
||||
}
|
||||
|
||||
fn dialect_for_db(dbms: SupportedDatabase) -> Box<dyn Dialect> {
|
||||
@@ -923,7 +942,13 @@ impl VisitorMut for ParameterExtractor {
|
||||
Expr::Cast {
|
||||
kind: kind @ CastKind::DoubleColon,
|
||||
..
|
||||
} if self.db_info.database_type != SupportedDatabase::Postgres => {
|
||||
} if ![
|
||||
SupportedDatabase::Postgres,
|
||||
SupportedDatabase::Snowflake,
|
||||
SupportedDatabase::Generic,
|
||||
]
|
||||
.contains(&self.db_info.database_type) =>
|
||||
{
|
||||
log::warn!("Casting with '::' is not supported on your database. \
|
||||
For backwards compatibility with older SQLPage versions, we will transform it to CAST(... AS ...).");
|
||||
*kind = CastKind::Cast;
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
//!
|
||||
//! #### Paths with other extensions (assets):
|
||||
//! - If the file exists: **Serve** the static file
|
||||
//! - If not found: Look for custom 404 handlers (see Error Handling below)
|
||||
//! - If not found but `{path}.sql` exists: **Execute** the SQL file
|
||||
//! - If neither found: Look for custom 404 handlers (see Error Handling below)
|
||||
//!
|
||||
//! #### Paths without extension:
|
||||
//! - First, try to find `{path}.sql` and **Execute** if found
|
||||
@@ -66,6 +67,13 @@
|
||||
//! - If favicon.ico exists: Serve favicon.ico
|
||||
//! - Else if 404.sql exists: Execute 404.sql
|
||||
//! - Else: Default 404
|
||||
//!
|
||||
//! Request: GET /api/data.json
|
||||
//! - If api/data.json exists: Serve api/data.json
|
||||
//! - Else if api/data.json.sql exists: Execute api/data.json.sql
|
||||
//! - Else if api/404.sql exists: Execute api/404.sql
|
||||
//! - Else if 404.sql exists: Execute 404.sql
|
||||
//! - Else: Default 404
|
||||
//! ```
|
||||
|
||||
use crate::filesystem::FileSystem;
|
||||
@@ -140,12 +148,13 @@ where
|
||||
C: RoutingConfig,
|
||||
{
|
||||
let result = match check_path(path_and_query, config) {
|
||||
Ok(path) => match path.extension() {
|
||||
Ok(path) => match path.extension().and_then(|e| e.to_str()) {
|
||||
Some(SQL_EXTENSION) => find_file_or_not_found(&path, SQL_EXTENSION, store).await?,
|
||||
Some(extension) => match find_file(&path, extension, store).await? {
|
||||
Some(action) => action,
|
||||
None => calculate_route_without_extension(path_and_query, path, store).await?,
|
||||
},
|
||||
None => calculate_route_without_extension(path_and_query, path, store).await?,
|
||||
Some(extension) => {
|
||||
let ext = extension.to_str().unwrap_or_default();
|
||||
find_file_or_not_found(&path, ext, store).await?
|
||||
}
|
||||
},
|
||||
Err(action) => action,
|
||||
};
|
||||
@@ -189,7 +198,7 @@ where
|
||||
path.push(INDEX);
|
||||
find_file_or_not_found(&path, SQL_EXTENSION, store).await
|
||||
} else {
|
||||
let path_with_ext = path.with_extension(SQL_EXTENSION);
|
||||
let path_with_ext = PathBuf::from(format!("{}.{SQL_EXTENSION}", path.display()));
|
||||
match find_file_or_not_found(&path_with_ext, SQL_EXTENSION, store).await? {
|
||||
Execute(x) => Ok(Execute(x)),
|
||||
other_action => {
|
||||
@@ -340,6 +349,22 @@ mod tests {
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn path_with_non_sql_extension_executes_sql_file() {
|
||||
let actual = do_route("/abc.def", File("abc.def.sql"), None).await;
|
||||
let expected = execute("abc.def.sql");
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn path_with_non_sql_extension_and_site_prefix_executes_sql_file() {
|
||||
let actual = do_route("/prefix/abc.def", File("abc.def.sql"), Some("/prefix/")).await;
|
||||
let expected = execute("abc.def.sql");
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
mod custom_not_found {
|
||||
|
||||
Reference in New Issue
Block a user