Merge branch 'main' into feature/table-action-buttons

This commit is contained in:
lovasoa
2025-10-21 11:35:51 +02:00
9 changed files with 300 additions and 97 deletions
+5
View File
@@ -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
View File
@@ -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
View File
@@ -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"]
+2 -5
View File
@@ -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)
+145
View File
@@ -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;
+2 -2
View File
@@ -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')}}
+24 -18
View File
@@ -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,
+32 -7
View File
@@ -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;
+32 -7
View File
@@ -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 {