Skip to content

Commit b332246

Browse files
committed
connection initialization scripts
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
1 parent 5d9e1ea commit b332246

File tree

4 files changed

+85
-3
lines changed

4 files changed

+85
-3
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525
select 'card' as component;
2626
select 'More...' as title, 'advanced_search.sql?query=' || sqlpage.url_encode($query)
2727
```
28+
- Add the ability to run a sql script on each database connection before it is used,
29+
by simply creating `sqlpage/on_connect.sql` file. This has many interesting use cases:
30+
- allows you to set up your database connection with custom settings, such as `PRAGMA` in SQLite
31+
- set a custom `search_path`, `application_name` or other variables in PostgreSQL
32+
- create temporary tables that will be available to all SQLPage queries but will not be persisted in the database
33+
- [`ATTACH`](https://www.sqlite.org/lang_attach.html) a database in SQLite to query multiple database files at once
2834

2935
## 0.11.0 (2023-09-17)
3036
- Support for **environment variables** ! You can now read environment variables from sql code using `sqlpage.environment_variable('VAR_NAME')`.

configuration.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,48 @@ The environment variable name can optionally be prefixed with `SQLPAGE_`.
3434
DATABASE_URL="sqlite:///path/to/my_database.db?mode=rwc"
3535
SQLITE_EXTENSIONS="mod_spatialite crypto define regexp"
3636
```
37+
38+
## Custom components
39+
40+
SQLPage allows you to create custom components in addition to or instead of the default ones.
41+
To create a custom component, create a [`.handlebars`](https://handlebarsjs.com/guide/expressions.html)
42+
file in the `sqlpage/templates` directory of your SQLPage installation.
43+
44+
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:
45+
46+
```handlebars
47+
<ul>
48+
{{#each_row}}
49+
<li>Value of my column: {{my_column}}</li>
50+
{{/each_row}}
51+
</ul>
52+
```
53+
54+
## Connection initialization scripts
55+
56+
SQLPage allows you to run a SQL script when a new database connection is opened,
57+
by simply creating a `sqlpage/on_connect.sql` file.
58+
59+
This can be useful to set up the database connection for your application.
60+
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).
61+
62+
```sql
63+
SET TIME ZONE 'UTC';
64+
SET search_path = my_schema;
65+
```
66+
67+
On SQLite, you can use this to [`ATTACH`](https://www.sqlite.org/lang_attach.html) additional databases.
68+
69+
```sql
70+
ATTACH DATABASE '/path/to/my_other_database.db' AS my_other_database;
71+
```
72+
73+
(and then, you can use `my_other_database.my_table` in your queries)
74+
75+
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.
76+
77+
```sql
78+
CREATE TEMPORARY TABLE my_temporary_table(
79+
my_temp_column TEXT
80+
);
81+
```

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use webserver::Database;
2121

2222
pub const TEMPLATES_DIR: &str = "sqlpage/templates";
2323
pub const MIGRATIONS_DIR: &str = "sqlpage/migrations";
24+
pub const ON_CONNECT_FILE: &str = "sqlpage/on_connect.sql";
2425

2526
pub struct AppState {
2627
pub db: Database,

src/webserver/database/mod.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub use crate::file_cache::FileCache;
1616

1717
use crate::webserver::database::sql_pseudofunctions::extract_req_param;
1818
use crate::webserver::http::{RequestInfo, SingleOrVec};
19-
use crate::MIGRATIONS_DIR;
19+
use crate::{MIGRATIONS_DIR, ON_CONNECT_FILE};
2020
pub use sql::make_placeholder;
2121
pub use sql::ParsedSqlFile;
2222
use sqlx::any::{
@@ -282,7 +282,7 @@ impl Database {
282282
}
283283

284284
fn create_pool_options(config: &AppConfig, db_kind: AnyKind) -> PoolOptions<Any> {
285-
PoolOptions::new()
285+
let mut pool_options = PoolOptions::new()
286286
.max_connections(if let Some(max) = config.max_database_pool_connections {
287287
max
288288
} else {
@@ -321,10 +321,40 @@ impl Database {
321321
)
322322
.acquire_timeout(Duration::from_secs_f64(
323323
config.database_connection_acquire_timeout_seconds,
324-
))
324+
));
325+
pool_options = add_on_connection_handler(pool_options);
326+
pool_options
325327
}
326328
}
327329

330+
fn add_on_connection_handler(pool_options: PoolOptions<Any>) -> PoolOptions<Any> {
331+
let on_connect_file = std::env::current_dir()
332+
.unwrap_or_default()
333+
.join(ON_CONNECT_FILE);
334+
if !on_connect_file.exists() {
335+
log::debug!("Not creating a custom SQL database connection handler because {on_connect_file:?} does not exist");
336+
return pool_options;
337+
}
338+
log::info!("Creating a custom SQL database connection handler from {on_connect_file:?}");
339+
let sql = match std::fs::read_to_string(&on_connect_file) {
340+
Ok(sql) => std::sync::Arc::new(sql),
341+
Err(e) => {
342+
log::error!("Unable to read the file {on_connect_file:?}: {e}");
343+
return pool_options;
344+
}
345+
};
346+
log::trace!("The custom SQL database connection handler is:\n{sql}");
347+
pool_options.after_connect(move |conn, _metadata| {
348+
log::debug!("Running {on_connect_file:?} on new connection");
349+
let sql = std::sync::Arc::clone(&sql);
350+
Box::pin(async move {
351+
let r = sqlx::query(&sql).execute(conn).await?;
352+
log::debug!("Finished running connection handler on new connection: {r:?}");
353+
Ok(())
354+
})
355+
})
356+
}
357+
328358
fn set_custom_connect_options(options: &mut AnyConnectOptions, config: &AppConfig) {
329359
if let Some(sqlite_options) = options.as_sqlite_mut() {
330360
for extension_name in &config.sqlite_extensions {

0 commit comments

Comments
 (0)