Skip to main content

ExternalDatabaseConfig

Struct ExternalDatabaseConfig 

Source
pub struct ExternalDatabaseConfig {
Show 27 fields pub kind: String, pub url_env: String, pub read_url_env: Option<String>, pub migration_url_env: Option<String>, pub compute: Option<String>, pub database: Option<String>, pub user: Option<String>, pub password_env: Option<String>, pub pool_max: Option<u32>, pub pool_min: Option<u32>, pub read_only: bool, pub allow_preview: bool, pub connect_timeout_secs: Option<u64>, pub image: Option<String>, pub volume_size_mib: Option<u32>, pub startup_grace_secs: Option<u32>, pub adopt_legacy_data_volume: bool, pub delta_capture: bool, pub restart_policy: DbRestartPolicy, pub tenant: TenantIsolation, pub tenant_scope: TenantScope, pub readiness_gate: ReadinessGate, pub rls_session: bool, pub tenant_guc: Option<String>, pub session_guc: Option<String>, pub tenant_all_marker: Option<String>, pub path: Option<PathBuf>,
}
Expand description

One external SQL database for the handler sql binding. Its source is one of two mutually-exclusive forms:

  • url_env — a bring-your-own database: the connection URL is a secret, named indirectly by an env var (never written in the config file).
  • compute — a database boatramp runs as a compute workload: boatramp derives the connection from the workload’s live endpoint (host:port) plus the database/user/password_env here, so there is no URL to hand-map and it follows the workload across restarts (PLAN-managed-compute-sql).

Fields§

§kind: String

Engine: postgres (aliases postgresql/pg), mysql (alias mariadb), or libsql (aliases sqlite/sqlite3) — the embedded SQLite-compatible engine (single-node file, or a remote sqld primary via url_env). A libsql entry needs neither compute nor an external URL for the single-node case: it names an on-disk file via path.

§url_env: String

Name of the env var holding the connection URL (e.g. postgres://user:pw@host/db). Required unless compute is set — or, for a single-node libsql database, unless path is set (an embedded file needs no URL/secret).

§read_url_env: Option<String>

Optional env var holding a read-replica connection URL. When set, open-read-only transactions route there; writes stay on url_env.

§migration_url_env: Option<String>

Optional env var holding a DDL/migration connection URL for the owner-role analog of the schema-migration surface — a login that is DDL-capable and distinct from the runtime binding user (user / url_env). Its whole purpose is to keep migration DDL off the runtime tenant identity, mirroring the Postgres owner-role split (Postgres mints a per-tenant non-superuser owner role automatically; MySQL has no such role model, so the DDL identity must be supplied explicitly).

MySQL: required to run schema migrations. Point it at an admin/DDL login (e.g. mysql://root:…@host/db or a purpose-made _migrate grant with CREATE, ALTER, DROP, INDEX, REFERENCES on the schema) that is not the runtime user. If it is unset (or resolves to the same identity as the runtime binding), migrate refuses fail-closed rather than run owner-DDL as the runtime tenant user — there is no owner/runtime split to fall back on. Ignored by the normal handler sql path; read only by the migration runner.

Postgres: ignored — the per-project non-superuser owner role + its sealed credential are minted automatically at provision, so no operator-supplied DDL URL is needed (a set value is harmless and unused).

§compute: Option<String>

The name of a compute workload (a Postgres/MySQL server boatramp runs) to source this database from, instead of url_env. boatramp resolves the workload’s live endpoint and builds the connection. Mutually exclusive with url_env.

Reserved prefix (#501 Stage B LOW-2): do NOT name a static workload with the bramp-db- prefix — it is reserved for the per-project managed-database servers boatramp derives for declarative databases: entries (boatramp_storage::tenant_provision::DERIVED_MANAGED_DB_PREFIX). A bramp-db-… value here is refused fail-closed at config load (ConfigError::InvalidDbName).

§database: Option<String>

The database name inside the compute-backed server (non-secret).

§user: Option<String>

The connecting user for the compute-backed server (non-secret).

§password_env: Option<String>

Env var holding the password for user on the compute-backed server. Omit to let boatramp fully manage the credential (PLAN-managed-compute-sql Phase 2): it generates a strong password once, seals it with the [secrets] envelope, injects it into the DB workload’s server-init env at launch, and connects the handler with it — the operator sets no DB secret at all. Set it only to bring your own password for the compute-backed server.

§pool_max: Option<u32>

Maximum pooled connections (default 8).

§pool_min: Option<u32>

Warm floor of pooled connections kept established while idle (default none). 0/unset lets an idle pool drain fully, so the next burst re-establishes every connection from cold — serially paying a TCP+TLS+auth handshake per connection (the proven public-storefront saturation). Set a small floor for a hot shared storefront pool so a burst acquires warm connections. NOTE: on a per-tenant managed binding this floor is held per tenant, so each tenant’s pool keeps that many idle DB sessions — set it only where that fan-out is acceptable. Clamped to pool_max.

§read_only: bool

Open every transaction READ ONLY (the engine rejects writes) — for a database functions should only read.

§allow_preview: bool

Permit preview deployments to reach this database. Default false: a preview is refused, so it can never touch the operator’s live external DB.

§connect_timeout_secs: Option<u64>

Connection/acquire timeout in seconds (default 10).

§image: Option<String>

The stock OCI image for a managed co-located database (compute set, no password_env). When omitted, boatramp auto-registers the workload from the engine’s default image (pgvector/pgvector:pg16 for postgres, mysql:8.0 for mysql). Ignored for a bring-your-own (url_env) database.

§volume_size_mib: Option<u32>

The persistent data-volume size in MiB for a managed co-located database (default 10240 = 10 GiB). Ignored for a bring-your-own database.

§startup_grace_secs: Option<u32>

Startup grace (seconds) for a managed co-located database: how long a freshly launched server has to finish its first initdb before the reconcile loop treats a still-unhealthy replica as a broken launch to stop + relaunch. When set it overrides the engine default the synthesizer picks (Postgres 60, MySQL 120). Omit to use that default. Ignored for a bring-your-own database.

§adopt_legacy_data_volume: bool

One-time migration designation (default false): mark THIS binding as the owner of the pre-v0.46 shared legacy data volume. Before co-located managed databases keyed their on-disk data directory per (project, workload), EVERY default-project / shared managed DB shared one bare <data_dir>/compute/volumes/data directory. On upgrade the container backend adopts that legacy directory into the owning workload’s per-workload scoped directory by an atomic, data-preserving rename. When exactly one managed DB is configured, it is the unambiguous owner and no flag is needed. When two or more co-located managed DBs exist, boatramp cannot prove from disk which one initialized the shared cluster, so it fails closed (refuses to launch them, leaving the data untouched) until an operator sets this flag on the ONE binding whose data lives there. Set it on at most one binding; setting it on two is itself ambiguous (fail closed). Ignored for a bring-your-own (url_env) database and once the volume is migrated.

Upgrade constraint (v0.46): the legacy-data owner is inferred from the managed databases bindings present at boot, so do NOT remove or rename a managed databases binding across the upgrade that first scopes volumes — if the removed/renamed binding owned the shared data cluster, the surviving binding could auto-adopt a cluster that is not its own (fail-closed in practice: a per-(project, workload) sealed credential will not authenticate against a foreign cluster’s roles, so the DB comes up unusable rather than leaking). If you must change bindings during that upgrade, set this flag on the ONE binding that owns the on-disk cluster so adoption is explicit.

§delta_capture: bool

Opt-in (default false): enable row-level change capture on this managed co-located database so it can source a delta export’s SQL true-delta (Postgres logical WAL / MySQL binlog). Flips wal_level=logical / --log-bin in the launch argv — a start-only server setting — so toggling it remints the content-addressed spec and requires a restart (governed by restart_policy). Off ⇒ no WAL/binlog overhead and a delta export of this DB falls back to a full dump. Ignored for a bring-your-own (url_env) database.

§restart_policy: DbRestartPolicy

How a restart-requiring config change on this managed co-located database (today: toggling delta_capture) is applied: manual (default — stage it and mark the DB restart-pending for an explicit boatramp db restart) or auto (apply + restart the DB immediately). Ignored for a bring-your-own database.

§tenant: TenantIsolation

Isolation mechanism for a compute-backed managed database (2×2 axis 1). single (default) — a dedicated database server (its own container) per tenant; shared — one server hosting a permission-separated database + role per tenant. Ignored for a bring-your-own (url_env) database.

§tenant_scope: TenantScope

Tenant grain for a compute-backed managed database (2×2 axis 2). project (default) — a tenant is a project; site — a tenant is a site. A tenant may hold several databases (one per binding that names it); it gets one login role and sealed credential per (tenant, server), granted on all its own databases and none of another tenant’s. The reserved default project uses the plain configured name, so a single-tenant install is just one ordinary database.

§readiness_gate: ReadinessGate

The per-surface readiness gate (v0.45.0): the readiness level each surface requires before it treats a managed-DB replica as usable. Default: every surface provisioned (strict — route/answer/migrate only once post-launch provisioning has completed). Relax a surface to service_ready to use a replica as soon as it answers SELECT 1, before provisioning finishes. Ignored for a bring-your-own (url_env) database.

§rls_session: bool

Opt-in (default false): inject the request’s boatramp.project / boatramp.site into the SQL session at each transaction start (Postgres set_config GUC, MySQL session var), so hand-written native RLS policies can key on them per-request. The GraphQL data connector’s row-level policy is claim-sourced and needs nothing here; this is for hand-rolled RLS on the plain sql.open path (Postgres — the engine with native row-level security).

§Trust model — read before relying on this for isolation

rls_session provides the request’s tenant to the SQL session for an app’s RLS to key on. It is not a general hostile-guest boundary:

  • The reserved keys (boatramp.* / @boatramp_*) are protected from guest override — a handler statement that tries to set_config('boatramp.…', …) / SET boatramp.… / SET @boatramp_… (or RESET/DISCARD them) is refused, so a guest cannot spoof its injected tenant.
  • But the real tenant-isolation boundary is the per-tenant database + role (tenant = single / shared), which a compromised handler cannot cross regardless of what it does in-session. rls_session is a convenience for app-authored RLS within a tenant’s own database, layered on top of that boundary — not a substitute for it.
  • For untrusted data, prefer claim-sourced enforcement (the GraphQL data connector’s row-level policy), which derives the tenant from the verified request, not from anything the handler’s SQL can influence.
§tenant_guc: Option<String>

The Postgres session GUC name an app’s RLS policies read for the host-resolved tenant (e.g. app.tenant_id), set per request/write on the rls_session path so RLS mirrors boatramp’s injected predicate as a defense-in-depth backstop (v0.4.20). Only honored when rls_session is on and the backend is Postgres. None ⇒ not exposed (today’s behavior). The guest can never set this GUC itself (its namespace becomes reserved).

§session_guc: Option<String>

The session GUC name for the anonymous session axis (e.g. app.session_id), the disjoint-column sibling of tenant_guc for TenantOrSession tables. None ⇒ not exposed.

§tenant_all_marker: Option<String>

A reserved sentinel value written to tenant_guc on an all-scoped READ (v0.4.21), so a table that opts in with USING (… OR current_setting(tenant_guc, true) = '<marker>') opens cross-tenant for the audited all twins. Pick a value that can never be a real tenant id (e.g. * or __all__). Only honored with tenant_guc set + rls_session on + Postgres. None/empty ⇒ all reads leave the GUC untouched (v0.4.20 fail-closed behavior). The guest can never set it — the tenant_guc namespace is already reserved against guest writes.

§path: Option<PathBuf>

The on-disk file path for a single-node libsql database (kind: "libsql"), e.g. /data/app.db. The parent directory is created if absent. Used only by the embedded engine; ignored for postgres/mysql and for a remote-sqld libsql binding (which uses url_env). A libsql entry sets exactly one of path (single-node file) or url_env (remote sqld).

Implementations§

Source§

impl ExternalDatabaseConfig

Source

pub fn validate(&self, name: &str) -> Result<(), String>

Validate the source is well-formed: exactly one of url_env / compute, and a compute-backed database has the connection details boatramp can’t infer (database + user). password_env is optional — omit it to let boatramp manage the credential (Phase 2). name is the binding name, for the error message.

Source

pub fn is_managed_credential(&self) -> bool

Whether this compute-backed database uses a boatramp-managed credential (Phase 2): compute is set and no password_env was supplied.

Trait Implementations§

Source§

impl Clone for ExternalDatabaseConfig

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ExternalDatabaseConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ExternalDatabaseConfig

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for ExternalDatabaseConfig

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Serialize for ExternalDatabaseConfig

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more