Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,15 @@
# set by the user, and this is a good thing. If the user already set some
# LIBSQLITE3_FLAGS, he probably knows what he is doing.
LIBSQLITE3_FLAGS = "-DSQLITE_ENABLE_MATH_FUNCTIONS"

[build]
rustflags = []

[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-arg=-Wl,-rpath=$ORIGIN/sqlpage:$ORIGIN/lib"]

[target.aarch64-unknown-linux-gnu]
rustflags = ["-C", "link-arg=-Wl,-rpath=$ORIGIN/sqlpage:$ORIGIN/lib"]

[target.armv7-unknown-linux-gnueabihf]
rustflags = ["-C", "link-arg=-Wl,-rpath=$ORIGIN/sqlpage:$ORIGIN/lib"]
31 changes: 4 additions & 27 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,33 +32,10 @@ DATABASE_URL='mssql://root:Password123!@localhost/sqlpage' cargo test # all dbms
- Components: defined in `./sqlpage/templates/*.handlebars`
- Functions: `src/webserver/database/sqlpage_functions/functions.rs` registered with `make_function!`.
- Components and functions are documented in [official website](./examples/official-site/sqlpage/migrations/); one migration per component and per function.
- ```sql
CREATE TABLE component(
name TEXT PRIMARY KEY,
description TEXT NOT NULL,
icon TEXT, -- icon name from tabler icon
introduced_in_version TEXT
);

CREATE TABLE parameter_type(name TEXT PRIMARY KEY);
INSERT INTO parameter_type(name) VALUES ('BOOLEAN'), ('COLOR'), ('HTML'), ('ICON'), ('INTEGER'), ('JSON'), ('REAL'), ('TEXT'), ('TIMESTAMP'), ('URL');

CREATE TABLE parameter(
top_level BOOLEAN DEFAULT FALSE,
name TEXT,
component TEXT REFERENCES component(name) ON DELETE CASCADE,
description TEXT,
description_md TEXT,
type TEXT REFERENCES parameter_type(name) ON DELETE CASCADE,
optional BOOLEAN DEFAULT FALSE,
);

CREATE TABLE example(
component TEXT REFERENCES component(name) ON DELETE CASCADE,
description TEXT,
properties JSON,
);
```
- tables
- `component(name,description,icon,introduced_in_version)` -- icon name from tabler icon
- `parameter(top_level BOOLEAN, name, component REFERENCES component(name), description, description_md, type, optional BOOLEAN)` parameter types: BOOLEAN, COLOR, HTML, ICON, INTEGER, JSON, REAL, TEXT, TIMESTAMP, URL
- `example(component REFERENCES component(name), description, properties JSON)`
- [Configuration](./configuration.md): see [AppConfig](./src/app_config.rs)
- Routing: file-based in `src/webserver/routing.rs`; not found handled via `src/default_404.sql`.
- Follow patterns from similar modules before introducing new abstractions.
Expand Down
29 changes: 27 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,33 @@ cargo build --release

The resulting executable will be in `target/release/sqlpage`.

### ODBC build modes

SQLPage supports three additive build modes for ODBC. Choose the mode via Cargo features:

- Dynamic ODBC (default):
- Works on Linux, macOS and Windows (Windows has ODBC built-in).
- Command:
```bash
cargo build # or: cargo build --features odbc-dynamic
```
- Static ODBC (Linux only):
- Statically links the ODBC driver manager; simplifies distribution.
- Command:
```bash
cargo build --features odbc-static
```
- No ODBC:
- Disables ODBC support entirely.
- Command:
```bash
cargo build --no-default-features
```

Notes:
- When cross-compiling in Docker, headers come from the base image (e.g. `unixodbc-dev`).
- Runtime rpath on Linux includes `$ORIGIN/sqlpage:$ORIGIN/lib` so you can colocate drivers next to the binary when needed.

## Code Style and Linting

### Rust
Expand Down Expand Up @@ -199,8 +226,6 @@ git checkout -b feature/your-feature-name
- Run frontend linting with Biome
- Test against multiple databases (PostgreSQL, MySQL, MSSQL)

5. Wait for review and address any feedback

## Release Process

Releases are automated when pushing tags that match the pattern `v*` (e.g., `v1.0.0`). The CI pipeline will:
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ lto = "fat"
panic = "abort"
codegen-units = 2

[features]
# Build variants
# - default (odbc-dynamic): ODBC enabled, dynamically linked (works on Linux, macOS, Windows)
# - odbc-static: ODBC enabled, statically linked to the driver manager (Linux only)
# - disable ODBC entirely: use `--no-default-features`
default = ["odbc-dynamic"]
odbc = ["sqlx/odbc"]
odbc-dynamic = ["odbc"]
odbc-static = ["odbc", "odbc-sys/static"]

[dependencies]
sqlx = { package = "sqlx-oldapi", git = "https://github.com/sqlpage/sqlx-oldapi", version = "0.6.49-beta.6", default-features = false, features = [
"any",
Expand All @@ -26,7 +36,6 @@ sqlx = { package = "sqlx-oldapi", git = "https://github.com/sqlpage/sqlx-oldapi"
"postgres",
"mysql",
"mssql",
"odbc",
"chrono",
"bigdecimal",
"json",
Expand Down Expand Up @@ -75,6 +84,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", optional = true }

[build-dependencies]
awc = { version = "3", features = ["rustls-0_23-webpki-roots"] }
Expand Down
20 changes: 14 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,43 @@ RUN apt-get update && \
if [ "$TARGETARCH" = "$BUILDARCH" ]; then \
rustup target list --installed > TARGET && \
echo gcc > LINKER && \
apt-get install -y gcc libgcc-s1 cmake unixodbc-dev libodbc2 libltdl7 && \
LIBDIR="/lib/*"; \
USRLIBDIR="/usr/lib/*"; \
apt-get install -y gcc libgcc-s1 cmake unixodbc-dev libltdl-dev pkg-config && \
LIBMULTIARCH=$(gcc -print-multiarch); \
LIBDIR="/lib/$LIBMULTIARCH"; \
USRLIBDIR="/usr/lib/$LIBMULTIARCH"; \
HOST_TRIPLE=$(gcc -dumpmachine); \
elif [ "$TARGETARCH" = "arm64" ]; then \
echo aarch64-unknown-linux-gnu > TARGET && \
echo aarch64-linux-gnu-gcc > LINKER && \
dpkg --add-architecture arm64 && apt-get update && \
apt-get install -y gcc-aarch64-linux-gnu libgcc-s1-arm64-cross unixodbc-dev:arm64 libodbc2:arm64 libltdl7:arm64 && \
apt-get install -y gcc-aarch64-linux-gnu libgcc-s1-arm64-cross unixodbc-dev:arm64 libltdl-dev:arm64 pkg-config && \
LIBDIR="/lib/aarch64-linux-gnu"; \
USRLIBDIR="/usr/lib/aarch64-linux-gnu"; \
HOST_TRIPLE="aarch64-linux-gnu"; \
elif [ "$TARGETARCH" = "arm" ]; then \
echo armv7-unknown-linux-gnueabihf > TARGET && \
echo arm-linux-gnueabihf-gcc > LINKER && \
dpkg --add-architecture armhf && apt-get update && \
apt-get install -y gcc-arm-linux-gnueabihf libgcc-s1-armhf-cross cmake libclang1 clang unixodbc-dev:armhf libodbc2:armhf libltdl7:armhf && \
apt-get install -y gcc-arm-linux-gnueabihf libgcc-s1-armhf-cross cmake libclang1 clang unixodbc-dev:armhf libltdl-dev:armhf pkg-config && \
cargo install --force --locked bindgen-cli && \
SYSROOT=$(arm-linux-gnueabihf-gcc -print-sysroot); \
echo "--sysroot=$SYSROOT -I$SYSROOT/usr/include -I$SYSROOT/usr/include/arm-linux-gnueabihf" > BINDGEN_EXTRA_CLANG_ARGS; \
LIBDIR="/lib/arm-linux-gnueabihf"; \
USRLIBDIR="/usr/lib/arm-linux-gnueabihf"; \
HOST_TRIPLE="arm-linux-gnueabihf"; \
else \
echo "Unsupported cross compilation target: $TARGETARCH"; \
exit 1; \
fi && \
cp $LIBDIR/libgcc_s.so.1 $USRLIBDIR/libodbc.so.2 $USRLIBDIR/libltdl.so.7 /opt/sqlpage-libs/ && \
echo $USRLIBDIR > ODBC_LIBDIR && \
cp $LIBDIR/libgcc_s.so.1 /opt/sqlpage-libs/ && \
rustup target add $(cat TARGET) && \
cargo init .

# Build dependencies (creates a layer that avoids recompiling dependencies on every build)
COPY Cargo.toml Cargo.lock ./
RUN BINDGEN_EXTRA_CLANG_ARGS=$(cat BINDGEN_EXTRA_CLANG_ARGS || true) \
RS_ODBC_LINK_SEARCH=$(cat ODBC_LIBDIR) \
cargo build \
--target $(cat TARGET) \
--config target.$(cat TARGET).linker='"'$(cat LINKER)'"' \
Expand All @@ -46,6 +52,7 @@ RUN BINDGEN_EXTRA_CLANG_ARGS=$(cat BINDGEN_EXTRA_CLANG_ARGS || true) \
# Build the project
COPY . .
RUN touch src/main.rs && \
RS_ODBC_LINK_SEARCH=$(cat ODBC_LIBDIR) \
cargo build \
--target $(cat TARGET) \
--config target.$(cat TARGET).linker='"'$(cat LINKER)'"' \
Expand All @@ -62,6 +69,7 @@ ENV SQLPAGE_WEB_ROOT=/var/www
ENV SQLPAGE_CONFIGURATION_DIRECTORY=/etc/sqlpage
WORKDIR /var/www
COPY --from=builder /usr/src/sqlpage/sqlpage.bin /usr/local/bin/sqlpage
# Provide runtime helper libs in system lib directory for the glibc busybox base
COPY --from=builder /opt/sqlpage-libs/* /lib/
USER sqlpage
COPY --from=builder --chown=sqlpage:sqlpage /usr/src/sqlpage/sqlpage/sqlpage.db sqlpage/sqlpage.db
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,12 @@ You can skip this section if you want to use one of the built-in database driver

SQLPage supports ODBC connections to connect to databases that don't have native drivers, such as Oracle, Snowflake, BigQuery, IBM DB2, and many others.

ODBC support requires an ODBC driver manager and appropriate database drivers to be installed on your system.
On Linux, SQLPage supports both dynamic and static ODBC linking. The Docker image uses the system `unixODBC` (dynamic). Release binaries can be built with static ODBC linking by enabling the `odbc-static` feature. You still need to install or provide the database-specific ODBC driver for the database you want to connect to. SQLPage also searches for drivers in a `sqlpage/` directory placed next to the executable (rpath includes `$ORIGIN/sqlpage`).

#### Install ODBC

- On windows, it's installed by default.
- On linux: `sudo apt-get install -y unixodbc odbcinst unixodbc-common libodbcinst2`
- On linux, the driver manager is bundled with SQLPage; install only your database driver's package.
- On mac: `brew install unixodbc`


Expand Down
2 changes: 1 addition & 1 deletion configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ DATABASE_URL="Driver={Oracle ODBC Driver};Server=localhost:1521/XE;UID=hr;PWD=pa
DATABASE_URL="Driver={SnowflakeDSIIDriver};Server=account.snowflakecomputing.com;Database=mydb;UID=user;PWD=password"
```

ODBC drivers must be installed and configured on your system. On Linux, you typically need `unixodbc` and the appropriate database-specific ODBC driver.
ODBC drivers must be installed and configured on your system. On Linux, the `unixODBC` driver manager is statically linked into the SQLPage binary, so you usually only need to install and configure the database-specific ODBC driver for your target database (for example Snowflake, Oracle, DuckDB...).

If the `database_password` configuration parameter is set, it will override any password specified in the `database_url`.
It does not need to be percent-encoded.
Expand Down
11 changes: 8 additions & 3 deletions src/webserver/database/connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ use crate::{
};
use anyhow::Context;
use futures_util::future::BoxFuture;
#[cfg(feature = "odbc")]
use sqlx::odbc::OdbcConnectOptions;
use sqlx::{
any::{Any, AnyConnectOptions, AnyKind},
odbc::OdbcConnectOptions,
pool::PoolOptions,
sqlite::{Function, SqliteConnectOptions, SqliteFunctionCtx},
ConnectOptions, Connection, Executor,
Expand Down Expand Up @@ -209,8 +210,11 @@ fn set_custom_connect_options(options: &mut AnyConnectOptions, config: &AppConfi
if let Some(sqlite_options) = options.as_sqlite_mut() {
set_custom_connect_options_sqlite(sqlite_options, config);
}
if let Some(odbc_options) = options.as_odbc_mut() {
set_custom_connect_options_odbc(odbc_options, config);
#[cfg(feature = "odbc")]
{
if let Some(odbc_options) = options.as_odbc_mut() {
set_custom_connect_options_odbc(odbc_options, config);
}
}
}

Expand Down Expand Up @@ -239,6 +243,7 @@ fn make_sqlite_fun(name: &str, f: fn(&str) -> String) -> Function {
})
}

#[cfg(feature = "odbc")]
fn set_custom_connect_options_odbc(odbc_options: &mut OdbcConnectOptions, config: &AppConfig) {
// Allow fetching very large text fields when using ODBC by removing the max column size limit
let batch_size = config.max_pending_rows.clamp(1, 1024);
Expand Down
Loading