/*
pgsql.cpp
Qore Programming Language
Copyright 2003 - 2026 Qore Technologies, s.r.o.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "pgsql.h"
#include "QorePGConnection.h"
#include "QorePGMapper.h"
#if defined(QDBI_METHOD_SELECT_COLUMNAR) || defined(QDBI_METHOD_STMT_FETCH_COLUMNAR)
#include
#endif
#include
void init_pgsql_functions(QoreNamespace& ns);
void init_pgsql_constants(QoreNamespace& ns);
static void pgsql_module_init(QoreModuleInitContext& ctx, ExceptionSink& xsink);
static void pgsql_module_ns_init(QoreNamespace* rns, QoreNamespace* qns, ExceptionSink& xsink);
static void pgsql_module_delete();
extern "C" DLLEXPORT void pgsql_qore_module_desc(QoreModuleInfo& mod_info) {
mod_info.name = "pgsql";
mod_info.version = QORE_MODULE_PACKAGE_VERSION;
mod_info.desc = "PostgreSQL module";
mod_info.author = "David Nichols";
mod_info.url = "http://qore.org";
mod_info.api_major = QORE_MODULE_API_MAJOR;
mod_info.api_minor = QORE_MODULE_API_MINOR;
mod_info.init = pgsql_module_init;
mod_info.ns_init = pgsql_module_ns_init;
mod_info.del = pgsql_module_delete;
mod_info.license = QL_MIT;
mod_info.license_str = "MIT";
}
static QoreNamespace pgsql_ns("PGSQL");
static int pgsql_caps = DBI_CAP_TRANSACTION_MANAGEMENT
| DBI_CAP_CHARSET_SUPPORT
| DBI_CAP_STORED_PROCEDURES
| DBI_CAP_LOB_SUPPORT
| DBI_CAP_BIND_BY_VALUE
| DBI_CAP_HAS_EXECRAW
| DBI_CAP_TIME_ZONE_SUPPORT
| DBI_CAP_HAS_NUMBER_SUPPORT
| DBI_CAP_SERVER_TIME_ZONE
| DBI_CAP_AUTORECONNECT
| DBI_CAP_HAS_ARRAY_BIND
#ifdef QDBI_METHOD_SELECT_TYPED
| DBI_CAP_HAS_TYPED_SELECT
#endif
#ifdef QDBI_METHOD_SELECT_COLUMNAR
| DBI_CAP_HAS_COLUMNAR_SELECT
#endif
;
DBIDriver *DBID_PGSQL = nullptr;
static int qore_pgsql_commit(Datasource* ds, ExceptionSink* xsink) {
QorePGConnection *pc = (QorePGConnection *)ds->getPrivateData();
return pc->commit(xsink);
}
static int qore_pgsql_rollback(Datasource* ds, ExceptionSink* xsink) {
QorePGConnection *pc = (QorePGConnection *)ds->getPrivateData();
return pc->rollback(xsink);
}
static int qore_pgsql_begin_transaction(Datasource* ds, ExceptionSink* xsink) {
QorePGConnection *pc = (QorePGConnection *)ds->getPrivateData();
return pc->begin_transaction(xsink);
}
static QoreValue qore_pgsql_select_rows(Datasource* ds, const QoreString *qstr, const QoreListNode* args, ExceptionSink* xsink) {
QorePGConnection *pc = (QorePGConnection *)ds->getPrivateData();
return pc->selectRows(qstr, args, xsink);
}
#ifdef QDBI_METHOD_SELECT_TYPED
static QoreValue qore_pgsql_select_rows_typed(Datasource* ds, const QoreString* qstr, const QoreListNode* args,
ExceptionSink* xsink) {
QorePGConnection* pc = (QorePGConnection*)ds->getPrivateData();
return pc->selectRowsTyped(qstr, args, xsink);
}
#endif
static QoreHashNode* qore_pgsql_select_row(Datasource* ds, const QoreString *qstr, const QoreListNode* args, ExceptionSink* xsink) {
QorePGConnection *pc = (QorePGConnection *)ds->getPrivateData();
return pc->selectRow(qstr, args, xsink);
}
static QoreValue qore_pgsql_select(Datasource* ds, const QoreString *qstr, const QoreListNode* args, ExceptionSink* xsink) {
QorePGConnection *pc = (QorePGConnection *)ds->getPrivateData();
return pc->select(qstr, args, xsink);
}
#ifdef QDBI_METHOD_SELECT_TYPED
static QoreValue qore_pgsql_select_typed(Datasource* ds, const QoreString* qstr, const QoreListNode* args,
ExceptionSink* xsink) {
QorePGConnection* pc = (QorePGConnection*)ds->getPrivateData();
return pc->selectTyped(qstr, args, xsink);
}
#endif
#ifdef QDBI_METHOD_SELECT_COLUMNAR
static QoreColumnarResult* qore_pgsql_select_columnar(Datasource* ds, const QoreString* qstr,
const QoreListNode* args, ExceptionSink* xsink) {
QorePGConnection* pc = (QorePGConnection*)ds->getPrivateData();
return pc->selectColumnar(qstr, args, xsink);
}
#endif
static QoreValue qore_pgsql_exec(Datasource* ds, const QoreString *qstr, const QoreListNode* args, ExceptionSink* xsink) {
QorePGConnection *pc = (QorePGConnection *)ds->getPrivateData();
return pc->exec(qstr, args, xsink);
}
static QoreValue qore_pgsql_execRaw(Datasource* ds, const QoreString *qstr, ExceptionSink* xsink) {
QorePGConnection *pc = (QorePGConnection *)ds->getPrivateData();
return pc->execRaw(qstr, xsink);
}
static int qore_pgsql_open(Datasource* ds, ExceptionSink* xsink) {
printd(5, "qore_pgsql_open() datasource %08p for DB=%s\n", ds,
ds->getDBName() ? ds->getDBName() : "unknown");
// string for connection arguments
QoreString lstr;
if (ds->getUsername())
lstr.sprintf("user='%s' ", ds->getUsername());
if (ds->getPassword())
lstr.sprintf("password='%s' ", ds->getPassword());
if (ds->getDBName())
lstr.sprintf("dbname='%s' ", ds->getDBName());
if (ds->getHostName())
lstr.sprintf("host='%s' ", ds->getHostName());
if (ds->getPort())
lstr.sprintf("port=%d ", ds->getPort());
if (ds->getDBEncoding()) {
const QoreEncoding *enc = QorePGMapper::getQoreEncoding(ds->getDBEncoding());
ds->setQoreEncoding(enc);
} else {
char *enc = (char *)QorePGMapper::getPGEncoding(QCS_DEFAULT);
if (!enc) {
xsink->raiseException("DBI:PGSQL:UNKNOWN-CHARACTER-SET", "cannot find the PostgreSQL character encoding equivalent for '%s'", QCS_DEFAULT->getCode());
return -1;
}
ds->setDBEncoding(enc);
ds->setQoreEncoding(QCS_DEFAULT);
}
QorePGConnection* pc = new QorePGConnection(ds, lstr.c_str(), xsink);
if (*xsink) {
delete pc;
return -1;
}
ds->setPrivateData((void *)pc);
return 0;
}
static int qore_pgsql_close(Datasource* ds) {
QorePGConnection *pc = (QorePGConnection *)ds->getPrivateData();
delete pc;
ds->setPrivateData(nullptr);
return 0;
}
static QoreValue qore_pgsql_get_server_version(Datasource* ds, ExceptionSink* xsink) {
QorePGConnection *pc = (QorePGConnection *)ds->getPrivateData();
return pc->get_server_version();
}
static QoreValue qore_pgsql_get_client_version(const Datasource* ds, ExceptionSink* xsink) {
#ifdef HAVE_PQLIBVERSION
return PQlibVersion();
#else
xsink->raiseException("DBI:PGSQL-GET-CLIENT-VERSION-ERROR", "the version of the PostgreSQL client library that this module was compiled against did not support the PQlibVersion() function");
return QoreValue();
#endif
}
static int pgsql_stmt_prepare(SQLStatement* stmt, const QoreString& str, const QoreListNode* args, ExceptionSink* xsink) {
assert(!stmt->getPrivateData());
QorePgsqlPreparedStatement* bg = new QorePgsqlPreparedStatement(stmt->getDatasource());
stmt->setPrivateData(bg);
return bg->prepare(str, args, true, xsink);
}
static int pgsql_stmt_prepare_raw(SQLStatement* stmt, const QoreString& str, ExceptionSink* xsink) {
assert(!stmt->getPrivateData());
QorePgsqlPreparedStatement* bg = new QorePgsqlPreparedStatement(stmt->getDatasource());
stmt->setPrivateData(bg);
return bg->prepare(str, 0, false, xsink);
}
static int pgsql_stmt_bind(SQLStatement* stmt, const QoreListNode& l, ExceptionSink* xsink) {
QorePgsqlPreparedStatement* bg = (QorePgsqlPreparedStatement* )stmt->getPrivateData();
assert(bg);
return bg->bind(l, xsink);
}
static int pgsql_stmt_bind_placeholders(SQLStatement* stmt, const QoreListNode& l, ExceptionSink* xsink) {
xsink->raiseException("DBI:PGSQL-BIND-PLACEHHODERS-ERROR", "binding placeholders is not necessary or supported with the pgsql driver");
return -1;
}
static int pgsql_stmt_bind_values(SQLStatement* stmt, const QoreListNode& l, ExceptionSink* xsink) {
QorePgsqlPreparedStatement* bg = (QorePgsqlPreparedStatement* )stmt->getPrivateData();
assert(bg);
return bg->bind(l, xsink);
}
static int pgsql_stmt_exec(SQLStatement* stmt, ExceptionSink* xsink) {
QorePgsqlPreparedStatement* bg = (QorePgsqlPreparedStatement* )stmt->getPrivateData();
assert(bg);
return bg->exec(xsink);
}
static int pgsql_stmt_define(SQLStatement* stmt, ExceptionSink* xsink) {
// define is a noop in the pgsql driver
return 0;
}
static int pgsql_stmt_affected_rows(SQLStatement* stmt, ExceptionSink* xsink) {
QorePgsqlPreparedStatement* bg = (QorePgsqlPreparedStatement*)stmt->getPrivateData();
assert(bg);
return bg->rowsAffected();
}
static QoreHashNode* pgsql_stmt_get_output(SQLStatement* stmt, ExceptionSink* xsink) {
QorePgsqlPreparedStatement* bg = (QorePgsqlPreparedStatement*)stmt->getPrivateData();
assert(bg);
return bg->getOutputHash(xsink);
}
static QoreHashNode* pgsql_stmt_get_output_rows(SQLStatement* stmt, ExceptionSink* xsink) {
QorePgsqlPreparedStatement* bg = (QorePgsqlPreparedStatement*)stmt->getPrivateData();
assert(bg);
return bg->getOutputHash(xsink);
}
static QoreHashNode* pgsql_stmt_fetch_row(SQLStatement* stmt, ExceptionSink* xsink) {
QorePgsqlPreparedStatement* bg = (QorePgsqlPreparedStatement*)stmt->getPrivateData();
assert(bg);
return bg->fetchRow(xsink);
}
static QoreListNode* pgsql_stmt_fetch_rows(SQLStatement* stmt, int rows, ExceptionSink* xsink) {
QorePgsqlPreparedStatement* bg = (QorePgsqlPreparedStatement*)stmt->getPrivateData();
assert(bg);
return bg->fetchRows(rows, xsink);
}
static QoreHashNode* pgsql_stmt_fetch_columns(SQLStatement* stmt, int rows, ExceptionSink* xsink) {
QorePgsqlPreparedStatement* bg = (QorePgsqlPreparedStatement*)stmt->getPrivateData();
assert(bg);
return bg->fetchColumns(rows, xsink);
}
#ifdef QDBI_METHOD_STMT_FETCH_COLUMNAR
static QoreColumnarResult* pgsql_stmt_fetch_columnar(SQLStatement* stmt, int rows, ExceptionSink* xsink) {
QorePgsqlPreparedStatement* bg = (QorePgsqlPreparedStatement*)stmt->getPrivateData();
assert(bg);
return bg->fetchColumnar(rows, xsink);
}
#endif
static QoreHashNode* pgsql_stmt_describe(SQLStatement* stmt, ExceptionSink* xsink) {
QorePgsqlPreparedStatement* bg = (QorePgsqlPreparedStatement*)stmt->getPrivateData();
assert(bg);
return bg->describe(xsink);
}
static bool pgsql_stmt_next(SQLStatement* stmt, ExceptionSink* xsink) {
QorePgsqlPreparedStatement* bg = (QorePgsqlPreparedStatement*)stmt->getPrivateData();
assert(bg);
return bg->next();
}
static int pgsql_stmt_close(SQLStatement* stmt, ExceptionSink* xsink) {
QorePgsqlPreparedStatement* bg = (QorePgsqlPreparedStatement*)stmt->getPrivateData();
assert(bg);
bg->reset(xsink);
delete bg;
stmt->setPrivateData(0);
return *xsink ? -1 : 0;
}
static int pgsql_opt_set(Datasource* ds, const char* opt, const QoreValue val, ExceptionSink* xsink) {
QorePGConnection* pc = (QorePGConnection*)ds->getPrivateData();
return pc->setOption(opt, val, xsink);
}
static QoreValue pgsql_opt_get(const Datasource* ds, const char* opt) {
QorePGConnection* pc = (QorePGConnection*)ds->getPrivateData();
return pc->getOption(opt);
}
static void pgsql_module_init(QoreModuleInitContext& ctx, ExceptionSink& xsink) {
#ifdef HAVE_PQISTHREADSAFE
if (!PQisthreadsafe()) {
xsink.raiseException("MODULE-INIT-ERROR", "cannot load pgsql module; the PostgreSQL library on this system is not thread-safe");
return;
}
#endif
init_pgsql_functions(pgsql_ns);
init_pgsql_constants(pgsql_ns);
QorePGMapper::static_init();
QorePgsqlStatement::static_init();
// register database functions with DBI subsystem
qore_dbi_method_list methods;
methods.add(QDBI_METHOD_OPEN, qore_pgsql_open);
methods.add(QDBI_METHOD_CLOSE, qore_pgsql_close);
methods.add(QDBI_METHOD_SELECT, qore_pgsql_select);
methods.add(QDBI_METHOD_SELECT_ROWS, qore_pgsql_select_rows);
#ifdef QDBI_METHOD_SELECT_TYPED
methods.add(QDBI_METHOD_SELECT_TYPED, qore_pgsql_select_typed);
methods.add(QDBI_METHOD_SELECT_ROWS_TYPED, qore_pgsql_select_rows_typed);
#endif
#ifdef QDBI_METHOD_SELECT_COLUMNAR
methods.add(QDBI_METHOD_SELECT_COLUMNAR, qore_pgsql_select_columnar);
#endif
methods.add(QDBI_METHOD_SELECT_ROW, qore_pgsql_select_row);
methods.add(QDBI_METHOD_EXEC, qore_pgsql_exec);
methods.add(QDBI_METHOD_EXECRAW, qore_pgsql_execRaw);
methods.add(QDBI_METHOD_COMMIT, qore_pgsql_commit);
methods.add(QDBI_METHOD_ROLLBACK, qore_pgsql_rollback);
methods.add(QDBI_METHOD_BEGIN_TRANSACTION, qore_pgsql_begin_transaction);
methods.add(QDBI_METHOD_GET_SERVER_VERSION, qore_pgsql_get_server_version);
methods.add(QDBI_METHOD_GET_CLIENT_VERSION, qore_pgsql_get_client_version);
methods.add(QDBI_METHOD_STMT_PREPARE, pgsql_stmt_prepare);
methods.add(QDBI_METHOD_STMT_PREPARE_RAW, pgsql_stmt_prepare_raw);
methods.add(QDBI_METHOD_STMT_BIND, pgsql_stmt_bind);
methods.add(QDBI_METHOD_STMT_BIND_PLACEHOLDERS, pgsql_stmt_bind_placeholders);
methods.add(QDBI_METHOD_STMT_BIND_VALUES, pgsql_stmt_bind_values);
methods.add(QDBI_METHOD_STMT_EXEC, pgsql_stmt_exec);
methods.add(QDBI_METHOD_STMT_DEFINE, pgsql_stmt_define);
methods.add(QDBI_METHOD_STMT_FETCH_ROW, pgsql_stmt_fetch_row);
methods.add(QDBI_METHOD_STMT_FETCH_ROWS, pgsql_stmt_fetch_rows);
methods.add(QDBI_METHOD_STMT_FETCH_COLUMNS, pgsql_stmt_fetch_columns);
#ifdef QDBI_METHOD_STMT_FETCH_COLUMNAR
methods.add(QDBI_METHOD_STMT_FETCH_COLUMNAR, pgsql_stmt_fetch_columnar);
#endif
methods.add(QDBI_METHOD_STMT_DESCRIBE, pgsql_stmt_describe);
methods.add(QDBI_METHOD_STMT_NEXT, pgsql_stmt_next);
methods.add(QDBI_METHOD_STMT_CLOSE, pgsql_stmt_close);
methods.add(QDBI_METHOD_STMT_AFFECTED_ROWS, pgsql_stmt_affected_rows);
methods.add(QDBI_METHOD_STMT_GET_OUTPUT, pgsql_stmt_get_output);
methods.add(QDBI_METHOD_STMT_GET_OUTPUT_ROWS, pgsql_stmt_get_output_rows);
methods.add(QDBI_METHOD_OPT_SET, pgsql_opt_set);
methods.add(QDBI_METHOD_OPT_GET, pgsql_opt_get);
methods.registerOption(DBI_OPT_NUMBER_OPT, "when set, numeric/decimal values are returned as integers if possible, otherwise as arbitrary-precision number values; the argument is ignored; setting this option turns it on and turns off 'string-numbers' and 'numeric-numbers'");
methods.registerOption(DBI_OPT_NUMBER_STRING, "when set, numeric/decimal values are returned as strings for backwards-compatibility; the argument is ignored; setting this option turns it on and turns off 'optimal-numbers' and 'numeric-numbers'");
methods.registerOption(DBI_OPT_NUMBER_NUMERIC, "when set, numeric/decimal values are returned as arbitrary-precision number values; the argument is ignored; setting this option turns it on and turns off 'string-numbers' and 'optimal-numbers'");
methods.registerOption(DBI_OPT_TIMEZONE, "set the server-side timezone, value must be a string in the format accepted by Timezone::constructor() on the client (ie either a region name or a UTC offset like \"+01:00\"), if not set the server's time zone will be assumed to be the same as the client's", stringTypeInfo);
// libpq connection options, applied to the connection at connect time; TCP keepalives are enabled
// by default with an aggressive idle time so PostgreSQL promptly reaps backends orphaned by an
// unclean client exit (otherwise idle orphans persist until the OS keepalive default, often 2
// hours, and can exhaust max_connections)
methods.registerOption(PGSQL_OPT_KEEPALIVES, "controls whether client-side TCP keepalives are used on the connection (default: True); disabling this turns off all keepalive settings below", boolTypeInfo);
methods.registerOption(PGSQL_OPT_KEEPALIVES_IDLE, "seconds of idle time before the first TCP keepalive probe is sent (libpq keepalives_idle; default: 60); ignored when 'keepalives' is False", bigIntTypeInfo);
methods.registerOption(PGSQL_OPT_KEEPALIVES_INTERVAL, "seconds between TCP keepalive probes after the first (libpq keepalives_interval; default: 10); ignored when 'keepalives' is False", bigIntTypeInfo);
methods.registerOption(PGSQL_OPT_KEEPALIVES_COUNT, "number of unacknowledged TCP keepalive probes before the connection is considered dead (libpq keepalives_count; default: 3); ignored when 'keepalives' is False", bigIntTypeInfo);
methods.registerOption(PGSQL_OPT_CONNECT_TIMEOUT, "maximum time in seconds to wait when establishing a connection (libpq connect_timeout); 0 (the default) means use the libpq default (no client-side limit)", bigIntTypeInfo);
methods.registerOption(PGSQL_OPT_APPLICATION_NAME, "an application name reported to the server and shown in pg_stat_activity.application_name (libpq application_name); useful for identifying which client/pool owns each backend connection; unset by default (the libpq default applies)", stringTypeInfo);
DBID_PGSQL = DBI.registerDriver("pgsql", methods, pgsql_caps);
}
static void pgsql_module_ns_init(QoreNamespace* rns, QoreNamespace* qns, ExceptionSink& xsink) {
qns->addInitialNamespace(pgsql_ns.copy());
}
static void pgsql_module_delete() {
}