| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- using Invercargill.DataStructures;
- namespace InvercargillSql {
- /**
- * PostgreSQL implementation of ConnectionProvider.
- *
- * Handles connection strings in standard URI format:
- * - `postgresql://user:pass@host:port/database` - TCP connection
- * - `postgresql://user@/run/postgresql/dbname?host=/run/postgresql` - Unix socket dir
- * - `postgres://user@localhost:5432/db` - Short scheme alias
- *
- * The provider builds a libpq conninfo string from the parsed
- * ConnectionString components. Components that are empty are omitted
- * so libpq falls back to its own defaults (environment, ~/.pgpass,
- * compiled-in defaults) — "trust env" behaviour.
- *
- * Supported query options: any option key that is a known libpq
- * conninfo keyword (host, port, user, password, dbname, connect_timeout,
- * sslmode, application_name, ...) is forwarded verbatim; other options
- * are ignored.
- *
- * Note: This class is internal to the library. Use ConnectionFactory
- * to create connections.
- */
- internal class PostgresProvider : Object, ConnectionProvider {
- /**
- * The scheme this provider handles: "postgresql".
- */
- public string scheme {
- get { return "postgresql"; }
- }
- /**
- * Creates a new PostgreSQL connection from a connection string.
- *
- * @param cs The parsed connection string
- * @return A new PostgresConnection instance
- * @throws SqlError if connection creation fails
- */
- public Connection create_connection(ConnectionString cs) throws SqlError {
- string conninfo = build_conninfo(cs);
- return new PostgresConnection(conninfo, cs.database);
- }
- /**
- * Builds a libpq conninfo string from the parsed connection string.
- *
- * Empty components are omitted. A leading "/" on the host means a
- * Unix socket directory (libpq "host" accepts a directory path).
- *
- * @param cs The parsed connection string
- * @return conninfo string suitable for PQconnectdb
- * @throws SqlError.INVALID_CONNECTION_STRING if the database name is empty
- */
- internal string build_conninfo(ConnectionString cs) throws SqlError {
- if (cs.database.length == 0) {
- throw new SqlError.INVALID_CONNECTION_STRING(
- "PostgreSQL connection string missing database name"
- );
- }
- var parts = new StringBuilder();
- // Host/port may arrive either as URI components or as query
- // options (the common unix-socket form is
- // postgresql://user@/dbname?host=/run/postgresql). URI
- // components win; otherwise the options fill in.
- string host = cs.host;
- if (host.length == 0 && cs.has_option("host")) {
- host = cs.get_option("host");
- }
- append_part(parts, "host", host);
- uint16 port = cs.port;
- if (port == 0 && cs.has_option("port")) {
- port = (uint16)int64.parse(cs.get_option("port"));
- }
- if (port != 0) {
- append_part(parts, "port", port.to_string());
- }
- append_part(parts, "user", cs.user);
- append_part(parts, "password", cs.password);
- append_part(parts, "dbname", cs.database);
- // Forward recognized libpq conninfo keywords from query options.
- foreach (var kvp in cs.options) {
- string key = kvp.key.down();
- if (key == "user" || key == "password" || key == "dbname") {
- // Already covered by the parsed components above.
- continue;
- }
- if (key == "host" || key == "port") {
- // Handled above (URI component or fallback).
- continue;
- }
- switch (key) {
- case "connect_timeout":
- case "client_encoding":
- case "application_name":
- case "fallback_application_name":
- case "keepalives":
- case "keepalives_idle":
- case "keepalives_interval":
- case "keepalives_count":
- case "sslmode":
- case "sslcompression":
- case "sslcrl":
- case "sslrootcert":
- case "sslcert":
- case "sslkey":
- case "sslnegotiation":
- case "requiressl":
- case "krbsrvname":
- case "gsslib":
- case "gssdelegation":
- case "gssencmode":
- case "service":
- case "options":
- append_part(parts, key, kvp.value);
- break;
- default:
- // Unknown option: ignored, matching sqlite's
- // tolerance of unsupported options.
- break;
- }
- }
- return parts.str;
- }
- /**
- * Appends "key = 'value'" to the conninfo builder, escaping single
- * quotes in the value. Skips empty values.
- */
- private void append_part(StringBuilder parts, string key, string value) {
- if (value.length == 0) {
- return;
- }
- if (parts.len > 0) {
- parts.append(" ");
- }
- parts.append(key);
- parts.append(" = '");
- parts.append(value.replace("'", "\\'"));
- parts.append("'");
- }
- }
- }
|