Skip to content

Commit

Permalink
connection initialization scripts
Browse files Browse the repository at this point in the history
add the ability to create a `sqlpage/on_connect.sql` file that will be run every time a new database connection is opened.

This has many interesting applications, see configuration.md

See #48
  • Loading branch information
lovasoa committed Sep 30, 2023
1 parent 5d9e1ea commit b332246
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 3 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
select 'card' as component;
select 'More...' as title, 'advanced_search.sql?query=' || sqlpage.url_encode($query)
```
- Add the ability to run a sql script on each database connection before it is used,
by simply creating `sqlpage/on_connect.sql` file. This has many interesting use cases:
- allows you to set up your database connection with custom settings, such as `PRAGMA` in SQLite
- set a custom `search_path`, `application_name` or other variables in PostgreSQL
- create temporary tables that will be available to all SQLPage queries but will not be persisted in the database
- [`ATTACH`](https://www.sqlite.org/lang_attach.html) a database in SQLite to query multiple database files at once

## 0.11.0 (2023-09-17)
- Support for **environment variables** ! You can now read environment variables from sql code using `sqlpage.environment_variable('VAR_NAME')`.
Expand Down
45 changes: 45 additions & 0 deletions configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,48 @@ The environment variable name can optionally be prefixed with `SQLPAGE_`.
DATABASE_URL="sqlite:///path/to/my_database.db?mode=rwc"
SQLITE_EXTENSIONS="mod_spatialite crypto define regexp"
```

## Custom components

SQLPage allows you to create custom components in addition to or instead of the default ones.
To create a custom component, create a [`.handlebars`](https://handlebarsjs.com/guide/expressions.html)
file in the `sqlpage/templates` directory of your SQLPage installation.

For instance, if you want to create a custom `my_component` component, that displays the value of the `my_column` column, create a `sqlpage/templates/my_component.handlebars` file with the following content:

```handlebars
<ul>
{{#each_row}}
<li>Value of my column: {{my_column}}</li>
{{/each_row}}
</ul>
```

## Connection initialization scripts

SQLPage allows you to run a SQL script when a new database connection is opened,
by simply creating a `sqlpage/on_connect.sql` file.

This can be useful to set up the database connection for your application.
For instance, on postgres, you can use this to [set the `search path` and various other connection options](https://www.postgresql.org/docs/current/sql-set.html).

```sql
SET TIME ZONE 'UTC';
SET search_path = my_schema;
```

On SQLite, you can use this to [`ATTACH`](https://www.sqlite.org/lang_attach.html) additional databases.

```sql
ATTACH DATABASE '/path/to/my_other_database.db' AS my_other_database;
```

(and then, you can use `my_other_database.my_table` in your queries)

You can also use this to create *temporary tables* to store intermediate results that are useful in your SQLPage application, but that you don't want to store permanently in the database.

```sql
CREATE TEMPORARY TABLE my_temporary_table(
my_temp_column TEXT
);
```
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use webserver::Database;

pub const TEMPLATES_DIR: &str = "sqlpage/templates";
pub const MIGRATIONS_DIR: &str = "sqlpage/migrations";
pub const ON_CONNECT_FILE: &str = "sqlpage/on_connect.sql";

pub struct AppState {
pub db: Database,
Expand Down
36 changes: 33 additions & 3 deletions src/webserver/database/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub use crate::file_cache::FileCache;

use crate::webserver::database::sql_pseudofunctions::extract_req_param;
use crate::webserver::http::{RequestInfo, SingleOrVec};
use crate::MIGRATIONS_DIR;
use crate::{MIGRATIONS_DIR, ON_CONNECT_FILE};
pub use sql::make_placeholder;
pub use sql::ParsedSqlFile;
use sqlx::any::{
Expand Down Expand Up @@ -282,7 +282,7 @@ impl Database {
}

fn create_pool_options(config: &AppConfig, db_kind: AnyKind) -> PoolOptions<Any> {
PoolOptions::new()
let mut pool_options = PoolOptions::new()
.max_connections(if let Some(max) = config.max_database_pool_connections {
max
} else {
Expand Down Expand Up @@ -321,10 +321,40 @@ impl Database {
)
.acquire_timeout(Duration::from_secs_f64(
config.database_connection_acquire_timeout_seconds,
))
));
pool_options = add_on_connection_handler(pool_options);
pool_options
}
}

fn add_on_connection_handler(pool_options: PoolOptions<Any>) -> PoolOptions<Any> {
let on_connect_file = std::env::current_dir()
.unwrap_or_default()
.join(ON_CONNECT_FILE);
if !on_connect_file.exists() {
log::debug!("Not creating a custom SQL database connection handler because {on_connect_file:?} does not exist");
return pool_options;
}
log::info!("Creating a custom SQL database connection handler from {on_connect_file:?}");
let sql = match std::fs::read_to_string(&on_connect_file) {
Ok(sql) => std::sync::Arc::new(sql),
Err(e) => {
log::error!("Unable to read the file {on_connect_file:?}: {e}");
return pool_options;
}
};
log::trace!("The custom SQL database connection handler is:\n{sql}");
pool_options.after_connect(move |conn, _metadata| {
log::debug!("Running {on_connect_file:?} on new connection");
let sql = std::sync::Arc::clone(&sql);
Box::pin(async move {
let r = sqlx::query(&sql).execute(conn).await?;
log::debug!("Finished running connection handler on new connection: {r:?}");
Ok(())
})
})
}

fn set_custom_connect_options(options: &mut AnyConnectOptions, config: &AppConfig) {
if let Some(sqlite_options) = options.as_sqlite_mut() {
for extension_name in &config.sqlite_extensions {
Expand Down

0 comments on commit b332246

Please sign in to comment.