// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// Which server is answering on the PostgreSQL wire. The two speak the same protocol
/// and the same dialect, so nothing below this line can tell them apart; a query layer
/// above it has to, because a server that accepts `LISTEN` and one that answers
/// `syntax error at or near "listen"` fail in different places.
#derive(Eq)
pub(all) enum Server {
Postgres
Cockroach
}
///|
pub fn Server::name(self : Server) -> String {
match self {
Postgres => "postgres"
Cockroach => "cockroachdb"
}
}
///|
/// The server a `SELECT version()` answer names. CockroachDB reports itself as
/// `CockroachDB CCL v24.3.0 (x86_64-…, built …)`, PostgreSQL as `PostgreSQL 16.4 …`,
/// and a compatibility layer that borrows the string is still that layer.
pub fn Server::from_version(version : String) -> Server {
let lowered = version.to_lower()
if lowered.contains("cockroach") {
Cockroach
} else {
Postgres
}
}
///|
/// Connect the way [`@moonpostgres.PgConn::connect`] does — the protocol is the same
/// one, and there is nothing to translate.
pub async fn connect(
host : String,
port : Int32,
user : String,
password : String,
database : String,
tls? : @tls.Client,
) -> @moonpostgres.PgConn raise @moondb.DbError {
@moonpostgres.PgConn::connect(host, port, user, password, database, tls?)
}
///|
/// The server's own version sentence, as it answered.
pub async fn version(
conn : &@moondb.AsyncDriver,
) -> String raise @moondb.DbError {
let rows = conn.query("SELECT version() AS version", [])
guard rows.length() > 0 else {
raise @moondb.QueryError("SELECT version() returned no rows")
}
rows[0].text_by("version")
}
///|
/// Which server is behind this connection, asked rather than assumed.
pub async fn server_of(
conn : &@moondb.AsyncDriver,
) -> Server raise @moondb.DbError {
Server::from_version(version(conn))
}
///|
/// What *this server* can do, as opposed to what the driver implements.
/// [`@moondb.Capabilities::for_dialect`] describes the PostgreSQL implementation;
/// CockroachDB is not PostgreSQL, and the difference that matters to a caller is
/// `notifications`: `LISTEN` and `NOTIFY` do not exist there, so a query layer that
/// plans to wait for a notification has to poll instead.
///
/// The rest of the PostgreSQL surface does hold on CockroachDB — cursors, savepoints,
/// `COPY` in both directions, `RETURNING` — which is why this clears one flag and not
/// a row of them.
pub fn capabilities_for(server : Server) -> @moondb.Capabilities {
let base = @moondb.Capabilities::for_dialect(@moondb.Dialect::Postgres)
match server {
Postgres => base
Cockroach => { ..base, notifications: false, }
}
}
///|
/// Comparing two server names is the whole point of naming them.
pub extend Server with Eq::{not_equal, equal}