| d23464c0 Andy McCall ↶ |
1 |
|
| 3b1bc27f Andy McCall |
2 |
|
| 3 |
|
| d23464c0 Andy McCall ↶ |
4 |
|
| 19bb31ff Andy McCall ↶ |
5 |
pub mod accounts; |
| 25ab8c73 Andy McCall ↶ |
6 |
pub mod activity; |
| 42fa9554 Andy McCall ↶ |
7 |
pub mod flow; |
| b64e2537 Andy McCall ↶ |
8 |
pub mod hooks; |
| 18e381ea Andy McCall ↶ |
9 |
pub mod issues; |
| 10 |
pub mod notify; |
| e1ec9fbf Andy McCall ↶ |
11 |
pub mod projects; |
| de505df5 Andy McCall ↶ |
12 |
pub mod proposals; |
| 3f4b0ea6 Andy McCall ↶ |
13 |
pub mod releases; |
| d23464c0 Andy McCall ↶ |
14 |
pub mod repos; |
| b6b7e4cf Andy McCall ↶ |
15 |
pub mod runners; |
| 0024ccb1 Andy McCall ↶ |
16 |
pub mod sessions; |
| 965b950f Andy McCall ↶ |
17 |
pub mod tlog; |
| d23464c0 Andy McCall ↶ |
18 |
|
| 3b1bc27f Andy McCall |
19 |
#[cfg(not(feature = "postgres"))] |
| 20 |
pub use sqlite::{begin_write, open, Db}; |
| d23464c0 Andy McCall ↶ |
21 |
|
| 3b1bc27f Andy McCall |
22 |
#[cfg(feature = "postgres")] |
| 23 |
pub use postgres::{begin_write, open, Db}; |
| d23464c0 Andy McCall ↶ |
24 |
|
| 3b1bc27f Andy McCall |
25 |
pub type Pool = sqlx::Pool<Db>; |
| eae4a974 Andy McCall ↶ |
26 |
|
| 3b1bc27f Andy McCall |
27 |
#[cfg(not(feature = "postgres"))] |
| 28 |
mod sqlite { |
| 29 |
use super::Pool; |
| 30 |
use crate::config::Config; |
| 31 |
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions, SqliteSynchronous}; |
| 32 |
|
| 33 |
pub type Db = sqlx::Sqlite; |
| 34 |
|
| 35 |
|
| 36 |
|
| 37 |
|
| 38 |
|
| 39 |
const BUSY_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30); |
| 40 |
|
| 41 |
|
| 42 |
pub async fn open(config: &Config) -> anyhow::Result<Pool> { |
| 43 |
let options = SqliteConnectOptions::new() |
| 44 |
.filename(config.db_path()) |
| 45 |
.create_if_missing(true) |
| 46 |
.journal_mode(SqliteJournalMode::Wal) |
| 47 |
.synchronous(SqliteSynchronous::Normal) |
| 48 |
.foreign_keys(true) |
| 49 |
.busy_timeout(BUSY_TIMEOUT); |
| 50 |
migrate(options.clone()).await?; |
| 51 |
let pool = SqlitePoolOptions::new().max_connections(8).connect_with(options).await?; |
| 52 |
Ok(pool) |
| 53 |
} |
| 54 |
|
| 55 |
|
| 56 |
|
| 57 |
|
| 58 |
|
| 59 |
async fn migrate(options: SqliteConnectOptions) -> anyhow::Result<()> { |
| 60 |
use sqlx::Connection; |
| 61 |
let mut conn = sqlx::SqliteConnection::connect_with(&options.foreign_keys(false)).await?; |
| 62 |
sqlx::migrate!("./migrations").run(&mut conn).await?; |
| 63 |
let broken: Vec<(String,)> = sqlx::query_as("SELECT \"table\" FROM pragma_foreign_key_check").fetch_all(&mut conn).await?; |
| 64 |
if let Some((table,)) = broken.first() { |
| 65 |
anyhow::bail!("after the migrations, {} rows break a foreign key (the first in {table})", broken.len()); |
| 66 |
} |
| 67 |
conn.close().await?; |
| 68 |
Ok(()) |
| 69 |
} |
| 70 |
|
| 71 |
|
| 72 |
|
| 73 |
|
| 74 |
|
| 75 |
pub async fn begin_write(pool: &Pool) -> sqlx::Result<sqlx::Transaction<'static, Db>> { |
| 76 |
pool.begin_with("BEGIN IMMEDIATE").await |
| 77 |
} |
| d23464c0 Andy McCall ↶ |
78 |
} |
| 3b1bc27f Andy McCall |
79 |
|
| 80 |
|
| 81 |
|
| 82 |
|
| 83 |
#[cfg(feature = "postgres")] |
| 84 |
mod postgres { |
| 85 |
use super::Pool; |
| 86 |
use crate::config::Config; |
| 87 |
use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; |
| 88 |
use sqlx::Executor; |
| d23464c0 Andy McCall ↶ |
89 |
|
| 3b1bc27f Andy McCall |
90 |
pub type Db = sqlx::Postgres; |
| 91 |
|
| 92 |
|
| 93 |
pub async fn open(config: &Config) -> anyhow::Result<Pool> { |
| 94 |
if config.database.url.is_empty() { |
| 95 |
anyhow::bail!("this hub is built for PostgreSQL: set [database] url (or OXYHUB_DATABASE_URL)"); |
| 96 |
} |
| 97 |
let options: PgConnectOptions = config.database.url.parse()?; |
| 98 |
let schema = schema_name(config); |
| 99 |
if let Some(schema) = &schema { |
| 100 |
let pool = PgPoolOptions::new().max_connections(1).connect_with(options.clone()).await?; |
| 101 |
pool.execute(format!("CREATE SCHEMA IF NOT EXISTS \"{schema}\"").as_str()).await?; |
| 102 |
pool.close().await; |
| 103 |
} |
| 104 |
let path = match &schema { |
| 105 |
|
| 106 |
Some(s) => format!("{s},public"), |
| 107 |
None => "public".to_string(), |
| 108 |
}; |
| 109 |
let options = options.options([("search_path", path.as_str())]); |
| 110 |
let pool = PgPoolOptions::new().max_connections(16).connect_with(options).await?; |
| 111 |
extension(&pool).await?; |
| 112 |
sqlx::migrate!("./migrations-pg").run(&pool).await?; |
| 113 |
Ok(pool) |
| 745d225d Andy McCall ↶ |
114 |
} |
| 3b1bc27f Andy McCall |
115 |
|
| 116 |
|
| 117 |
|
| 118 |
async fn extension(pool: &Pool) -> anyhow::Result<()> { |
| 119 |
let made = pool.execute("CREATE EXTENSION IF NOT EXISTS citext WITH SCHEMA public").await; |
| 120 |
if let Err(e) = made { |
| 121 |
let there: bool = |
| 122 |
sqlx::query_scalar("SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'citext')").fetch_one(pool).await?; |
| 123 |
if !there { |
| 124 |
return Err(e.into()); |
| 125 |
} |
| 126 |
} |
| 127 |
Ok(()) |
| 128 |
} |
| 129 |
|
| 130 |
|
| 131 |
|
| 132 |
fn schema_name(config: &Config) -> Option<String> { |
| 133 |
let s = match config.database.schema.as_str() { |
| 134 |
"" | "public" => return None, |
| 135 |
"*" => { |
| 136 |
let dir = config.data.file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_default(); |
| 137 |
format!("hub_{dir}") |
| 138 |
} |
| 139 |
s => s.to_string(), |
| 140 |
}; |
| 141 |
Some(s.chars().map(|c| if c.is_ascii_alphanumeric() { c.to_ascii_lowercase() } else { '_' }).collect()) |
| 142 |
} |
| 143 |
|
| 144 |
|
| 145 |
|
| 146 |
|
| 147 |
pub async fn begin_write(pool: &Pool) -> sqlx::Result<sqlx::Transaction<'static, Db>> { |
| 148 |
let mut tx = pool.begin().await?; |
| 149 |
sqlx::query("SELECT pg_advisory_xact_lock(7300211)").execute(&mut *tx).await?; |
| 150 |
Ok(tx) |
| 151 |
} |
| 745d225d Andy McCall ↶ |
152 |
} |
| 153 |
|
| 3b1bc27f Andy McCall |
154 |
|
| 155 |
|
| 156 |
pub fn json_text(col: &str, key: &str) -> String { |
| 157 |
if cfg!(feature = "postgres") { |
| 158 |
format!("(CAST(NULLIF({col}, '') AS json) ->> '{key}')") |
| 159 |
} else { |
| 160 |
format!("json_extract({col}, '$.{key}')") |
| 161 |
} |
| 162 |
} |
| 163 |
|
| d23464c0 Andy McCall ↶ |
164 |
|
| 165 |
pub fn now_ms() -> i64 { |
| 166 |
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_millis() as i64).unwrap_or(0) |
| 941abdd6 Andy McCall ↶ |
167 |
} |