summaryrefslogtreecommitdiffstats
path: root/src/interfaces/ecpg/ecpglib/connect.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/interfaces/ecpg/ecpglib/connect.c')
-rw-r--r--src/interfaces/ecpg/ecpglib/connect.c767
1 files changed, 767 insertions, 0 deletions
diff --git a/src/interfaces/ecpg/ecpglib/connect.c b/src/interfaces/ecpg/ecpglib/connect.c
new file mode 100644
index 0000000..056940c
--- /dev/null
+++ b/src/interfaces/ecpg/ecpglib/connect.c
@@ -0,0 +1,767 @@
+/* src/interfaces/ecpg/ecpglib/connect.c */
+
+#define POSTGRES_ECPG_INTERNAL
+#include "postgres_fe.h"
+
+#include "ecpg-pthread-win32.h"
+#include "ecpgerrno.h"
+#include "ecpglib.h"
+#include "ecpglib_extern.h"
+#include "ecpgtype.h"
+#include "sqlca.h"
+
+#ifdef HAVE_USELOCALE
+locale_t ecpg_clocale = (locale_t) 0;
+#endif
+
+#ifdef ENABLE_THREAD_SAFETY
+static pthread_mutex_t connections_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_key_t actual_connection_key;
+static pthread_once_t actual_connection_key_once = PTHREAD_ONCE_INIT;
+#endif
+static struct connection *actual_connection = NULL;
+static struct connection *all_connections = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+static void
+ecpg_actual_connection_init(void)
+{
+ pthread_key_create(&actual_connection_key, NULL);
+}
+
+void
+ecpg_pthreads_init(void)
+{
+ pthread_once(&actual_connection_key_once, ecpg_actual_connection_init);
+}
+#endif
+
+static struct connection *
+ecpg_get_connection_nr(const char *connection_name)
+{
+ struct connection *ret = NULL;
+
+ if ((connection_name == NULL) || (strcmp(connection_name, "CURRENT") == 0))
+ {
+#ifdef ENABLE_THREAD_SAFETY
+ ecpg_pthreads_init(); /* ensure actual_connection_key is valid */
+
+ ret = pthread_getspecific(actual_connection_key);
+
+ /*
+ * if no connection in TSD for this thread, get the global default
+ * connection and hope the user knows what they're doing (i.e. using
+ * their own mutex to protect that connection from concurrent accesses
+ */
+ if (ret == NULL)
+ /* no TSD connection, going for global */
+ ret = actual_connection;
+#else
+ ret = actual_connection;
+#endif
+ }
+ else
+ {
+ struct connection *con;
+
+ for (con = all_connections; con != NULL; con = con->next)
+ {
+ if (strcmp(connection_name, con->name) == 0)
+ break;
+ }
+ ret = con;
+ }
+
+ return ret;
+}
+
+struct connection *
+ecpg_get_connection(const char *connection_name)
+{
+ struct connection *ret = NULL;
+
+ if ((connection_name == NULL) || (strcmp(connection_name, "CURRENT") == 0))
+ {
+#ifdef ENABLE_THREAD_SAFETY
+ ecpg_pthreads_init(); /* ensure actual_connection_key is valid */
+
+ ret = pthread_getspecific(actual_connection_key);
+
+ /*
+ * if no connection in TSD for this thread, get the global default
+ * connection and hope the user knows what they're doing (i.e. using
+ * their own mutex to protect that connection from concurrent accesses
+ */
+ if (ret == NULL)
+ /* no TSD connection here either, using global */
+ ret = actual_connection;
+#else
+ ret = actual_connection;
+#endif
+ }
+ else
+ {
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_lock(&connections_mutex);
+#endif
+
+ ret = ecpg_get_connection_nr(connection_name);
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&connections_mutex);
+#endif
+ }
+
+ return ret;
+}
+
+static void
+ecpg_finish(struct connection *act)
+{
+ if (act != NULL)
+ {
+ struct ECPGtype_information_cache *cache,
+ *ptr;
+
+ ecpg_deallocate_all_conn(0, ECPG_COMPAT_PGSQL, act);
+ PQfinish(act->connection);
+
+ /*
+ * no need to lock connections_mutex - we're always called by
+ * ECPGdisconnect or ECPGconnect, which are holding the lock
+ */
+
+ /* remove act from the list */
+ if (act == all_connections)
+ all_connections = act->next;
+ else
+ {
+ struct connection *con;
+
+ for (con = all_connections; con->next && con->next != act; con = con->next);
+ if (con->next)
+ con->next = act->next;
+ }
+
+#ifdef ENABLE_THREAD_SAFETY
+ if (pthread_getspecific(actual_connection_key) == act)
+ pthread_setspecific(actual_connection_key, all_connections);
+#endif
+ if (actual_connection == act)
+ actual_connection = all_connections;
+
+ ecpg_log("ecpg_finish: connection %s closed\n", act->name ? act->name : "(null)");
+
+ for (cache = act->cache_head; cache; ptr = cache, cache = cache->next, ecpg_free(ptr));
+ ecpg_free(act->name);
+ ecpg_free(act);
+ /* delete cursor variables when last connection gets closed */
+ if (all_connections == NULL)
+ {
+ struct var_list *iv_ptr;
+
+ for (; ivlist; iv_ptr = ivlist, ivlist = ivlist->next, ecpg_free(iv_ptr));
+ }
+ }
+ else
+ ecpg_log("ecpg_finish: called an extra time\n");
+}
+
+bool
+ECPGsetcommit(int lineno, const char *mode, const char *connection_name)
+{
+ struct connection *con = ecpg_get_connection(connection_name);
+ PGresult *results;
+
+ if (!ecpg_init(con, connection_name, lineno))
+ return false;
+
+ ecpg_log("ECPGsetcommit on line %d: action \"%s\"; connection \"%s\"\n", lineno, mode, con->name);
+
+ if (con->autocommit && strncmp(mode, "off", strlen("off")) == 0)
+ {
+ if (PQtransactionStatus(con->connection) == PQTRANS_IDLE)
+ {
+ results = PQexec(con->connection, "begin transaction");
+ if (!ecpg_check_PQresult(results, lineno, con->connection, ECPG_COMPAT_PGSQL))
+ return false;
+ PQclear(results);
+ }
+ con->autocommit = false;
+ }
+ else if (!con->autocommit && strncmp(mode, "on", strlen("on")) == 0)
+ {
+ if (PQtransactionStatus(con->connection) != PQTRANS_IDLE)
+ {
+ results = PQexec(con->connection, "commit");
+ if (!ecpg_check_PQresult(results, lineno, con->connection, ECPG_COMPAT_PGSQL))
+ return false;
+ PQclear(results);
+ }
+ con->autocommit = true;
+ }
+
+ return true;
+}
+
+bool
+ECPGsetconn(int lineno, const char *connection_name)
+{
+ struct connection *con = ecpg_get_connection(connection_name);
+
+ if (!ecpg_init(con, connection_name, lineno))
+ return false;
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_setspecific(actual_connection_key, con);
+#else
+ actual_connection = con;
+#endif
+ return true;
+}
+
+
+static void
+ECPGnoticeReceiver(void *arg, const PGresult *result)
+{
+ char *sqlstate = PQresultErrorField(result, PG_DIAG_SQLSTATE);
+ char *message = PQresultErrorField(result, PG_DIAG_MESSAGE_PRIMARY);
+ struct sqlca_t *sqlca = ECPGget_sqlca();
+ int sqlcode;
+
+ if (sqlca == NULL)
+ {
+ ecpg_log("out of memory");
+ return;
+ }
+
+ (void) arg; /* keep the compiler quiet */
+ if (sqlstate == NULL)
+ sqlstate = ECPG_SQLSTATE_ECPG_INTERNAL_ERROR;
+
+ if (message == NULL) /* Shouldn't happen, but need to be sure */
+ message = ecpg_gettext("empty message text");
+
+ /* these are not warnings */
+ if (strncmp(sqlstate, "00", 2) == 0)
+ return;
+
+ ecpg_log("ECPGnoticeReceiver: %s\n", message);
+
+ /* map to SQLCODE for backward compatibility */
+ if (strcmp(sqlstate, ECPG_SQLSTATE_INVALID_CURSOR_NAME) == 0)
+ sqlcode = ECPG_WARNING_UNKNOWN_PORTAL;
+ else if (strcmp(sqlstate, ECPG_SQLSTATE_ACTIVE_SQL_TRANSACTION) == 0)
+ sqlcode = ECPG_WARNING_IN_TRANSACTION;
+ else if (strcmp(sqlstate, ECPG_SQLSTATE_NO_ACTIVE_SQL_TRANSACTION) == 0)
+ sqlcode = ECPG_WARNING_NO_TRANSACTION;
+ else if (strcmp(sqlstate, ECPG_SQLSTATE_DUPLICATE_CURSOR) == 0)
+ sqlcode = ECPG_WARNING_PORTAL_EXISTS;
+ else
+ sqlcode = 0;
+
+ strncpy(sqlca->sqlstate, sqlstate, sizeof(sqlca->sqlstate));
+ sqlca->sqlcode = sqlcode;
+ sqlca->sqlwarn[2] = 'W';
+ sqlca->sqlwarn[0] = 'W';
+
+ strncpy(sqlca->sqlerrm.sqlerrmc, message, sizeof(sqlca->sqlerrm.sqlerrmc));
+ sqlca->sqlerrm.sqlerrmc[sizeof(sqlca->sqlerrm.sqlerrmc) - 1] = 0;
+ sqlca->sqlerrm.sqlerrml = strlen(sqlca->sqlerrm.sqlerrmc);
+
+ ecpg_log("raising sqlcode %d\n", sqlcode);
+}
+
+/* this contains some quick hacks, needs to be cleaned up, but it works */
+bool
+ECPGconnect(int lineno, int c, const char *name, const char *user, const char *passwd, const char *connection_name, int autocommit)
+{
+ struct sqlca_t *sqlca = ECPGget_sqlca();
+ enum COMPAT_MODE compat = c;
+ struct connection *this;
+ int i,
+ connect_params = 0;
+ char *dbname = name ? ecpg_strdup(name, lineno) : NULL,
+ *host = NULL,
+ *tmp,
+ *port = NULL,
+ *realname = NULL,
+ *options = NULL;
+ const char **conn_keywords;
+ const char **conn_values;
+
+ if (sqlca == NULL)
+ {
+ ecpg_raise(lineno, ECPG_OUT_OF_MEMORY,
+ ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
+ ecpg_free(dbname);
+ return false;
+ }
+
+ ecpg_init_sqlca(sqlca);
+
+ /*
+ * clear auto_mem structure because some error handling functions might
+ * access it
+ */
+ ecpg_clear_auto_mem();
+
+ if (INFORMIX_MODE(compat))
+ {
+ char *envname;
+
+ /*
+ * Informix uses an environment variable DBPATH that overrides the
+ * connection parameters given here. We do the same with PG_DBPATH as
+ * the syntax is different.
+ */
+ envname = getenv("PG_DBPATH");
+ if (envname)
+ {
+ ecpg_free(dbname);
+ dbname = ecpg_strdup(envname, lineno);
+ }
+
+ }
+
+ if (dbname == NULL && connection_name == NULL)
+ connection_name = "DEFAULT";
+
+#if ENABLE_THREAD_SAFETY
+ ecpg_pthreads_init();
+#endif
+
+ /* check if the identifier is unique */
+ if (ecpg_get_connection(connection_name))
+ {
+ ecpg_free(dbname);
+ ecpg_log("ECPGconnect: connection identifier %s is already in use\n",
+ connection_name);
+ return false;
+ }
+
+ if ((this = (struct connection *) ecpg_alloc(sizeof(struct connection), lineno)) == NULL)
+ {
+ ecpg_free(dbname);
+ return false;
+ }
+
+ if (dbname != NULL)
+ {
+ /* get the detail information from dbname */
+ if (strncmp(dbname, "tcp:", 4) == 0 || strncmp(dbname, "unix:", 5) == 0)
+ {
+ int offset = 0;
+
+ /*
+ * only allow protocols tcp and unix
+ */
+ if (strncmp(dbname, "tcp:", 4) == 0)
+ offset = 4;
+ else if (strncmp(dbname, "unix:", 5) == 0)
+ offset = 5;
+
+ if (strncmp(dbname + offset, "postgresql://", strlen("postgresql://")) == 0)
+ {
+
+ /*------
+ * new style:
+ * <tcp|unix>:postgresql://server[:port][/db-name][?options]
+ *------
+ */
+ offset += strlen("postgresql://");
+
+ tmp = strrchr(dbname + offset, '?');
+ if (tmp != NULL) /* options given */
+ {
+ options = ecpg_strdup(tmp + 1, lineno);
+ *tmp = '\0';
+ }
+
+ tmp = last_dir_separator(dbname + offset);
+ if (tmp != NULL) /* database name given */
+ {
+ if (tmp[1] != '\0') /* non-empty database name */
+ {
+ realname = ecpg_strdup(tmp + 1, lineno);
+ connect_params++;
+ }
+ *tmp = '\0';
+ }
+
+ tmp = strrchr(dbname + offset, ':');
+ if (tmp != NULL) /* port number given */
+ {
+ *tmp = '\0';
+ port = ecpg_strdup(tmp + 1, lineno);
+ connect_params++;
+ }
+
+ if (strncmp(dbname, "unix:", 5) == 0)
+ {
+ /*
+ * The alternative of using "127.0.0.1" here is deprecated
+ * and undocumented; we'll keep it for backward
+ * compatibility's sake, but not extend it to allow IPv6.
+ */
+ if (strcmp(dbname + offset, "localhost") != 0 &&
+ strcmp(dbname + offset, "127.0.0.1") != 0)
+ {
+ ecpg_log("ECPGconnect: non-localhost access via sockets on line %d\n", lineno);
+ ecpg_raise(lineno, ECPG_CONNECT, ECPG_SQLSTATE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION, realname ? realname : ecpg_gettext("<DEFAULT>"));
+ if (host)
+ ecpg_free(host);
+ if (port)
+ ecpg_free(port);
+ if (options)
+ ecpg_free(options);
+ if (realname)
+ ecpg_free(realname);
+ if (dbname)
+ ecpg_free(dbname);
+ free(this);
+ return false;
+ }
+ }
+ else
+ {
+ if (*(dbname + offset) != '\0')
+ {
+ host = ecpg_strdup(dbname + offset, lineno);
+ connect_params++;
+ }
+ }
+ }
+ }
+ else
+ {
+ /* old style: dbname[@server][:port] */
+ tmp = strrchr(dbname, ':');
+ if (tmp != NULL) /* port number given */
+ {
+ port = ecpg_strdup(tmp + 1, lineno);
+ connect_params++;
+ *tmp = '\0';
+ }
+
+ tmp = strrchr(dbname, '@');
+ if (tmp != NULL) /* host name given */
+ {
+ host = ecpg_strdup(tmp + 1, lineno);
+ connect_params++;
+ *tmp = '\0';
+ }
+
+ if (strlen(dbname) > 0)
+ {
+ realname = ecpg_strdup(dbname, lineno);
+ connect_params++;
+ }
+ else
+ realname = NULL;
+ }
+ }
+ else
+ realname = NULL;
+
+ /*
+ * Count options for the allocation done below (this may produce an
+ * overestimate, it's ok).
+ */
+ if (options)
+ for (i = 0; options[i]; i++)
+ if (options[i] == '=')
+ connect_params++;
+
+ if (user && strlen(user) > 0)
+ connect_params++;
+ if (passwd && strlen(passwd) > 0)
+ connect_params++;
+
+ /*
+ * Allocate enough space for all connection parameters. These allocations
+ * are done before manipulating the list of connections to ease the error
+ * handling on failure.
+ */
+ conn_keywords = (const char **) ecpg_alloc((connect_params + 1) * sizeof(char *), lineno);
+ conn_values = (const char **) ecpg_alloc(connect_params * sizeof(char *), lineno);
+ if (conn_keywords == NULL || conn_values == NULL)
+ {
+ if (host)
+ ecpg_free(host);
+ if (port)
+ ecpg_free(port);
+ if (options)
+ ecpg_free(options);
+ if (realname)
+ ecpg_free(realname);
+ if (dbname)
+ ecpg_free(dbname);
+ if (conn_keywords)
+ ecpg_free(conn_keywords);
+ if (conn_values)
+ ecpg_free(conn_values);
+ free(this);
+ return false;
+ }
+
+ /* add connection to our list */
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_lock(&connections_mutex);
+#endif
+
+ /*
+ * ... but first, make certain we have created ecpg_clocale. Rely on
+ * holding connections_mutex to ensure this is done by only one thread.
+ */
+#ifdef HAVE_USELOCALE
+ if (!ecpg_clocale)
+ {
+ ecpg_clocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0);
+ if (!ecpg_clocale)
+ {
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&connections_mutex);
+#endif
+ ecpg_raise(lineno, ECPG_OUT_OF_MEMORY,
+ ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
+ if (host)
+ ecpg_free(host);
+ if (port)
+ ecpg_free(port);
+ if (options)
+ ecpg_free(options);
+ if (realname)
+ ecpg_free(realname);
+ if (dbname)
+ ecpg_free(dbname);
+ if (conn_keywords)
+ ecpg_free(conn_keywords);
+ if (conn_values)
+ ecpg_free(conn_values);
+ free(this);
+ return false;
+ }
+ }
+#endif
+
+ if (connection_name != NULL)
+ this->name = ecpg_strdup(connection_name, lineno);
+ else
+ this->name = ecpg_strdup(realname, lineno);
+
+ this->cache_head = NULL;
+ this->prep_stmts = NULL;
+
+ if (all_connections == NULL)
+ this->next = NULL;
+ else
+ this->next = all_connections;
+
+ all_connections = this;
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_setspecific(actual_connection_key, all_connections);
+#endif
+ actual_connection = all_connections;
+
+ ecpg_log("ECPGconnect: opening database %s on %s port %s %s%s %s%s\n",
+ realname ? realname : "<DEFAULT>",
+ host ? host : "<DEFAULT>",
+ port ? (ecpg_internal_regression_mode ? "<REGRESSION_PORT>" : port) : "<DEFAULT>",
+ options ? "with options " : "", options ? options : "",
+ (user && strlen(user) > 0) ? "for user " : "", user ? user : "");
+
+ i = 0;
+ if (realname)
+ {
+ conn_keywords[i] = "dbname";
+ conn_values[i] = realname;
+ i++;
+ }
+ if (host)
+ {
+ conn_keywords[i] = "host";
+ conn_values[i] = host;
+ i++;
+ }
+ if (port)
+ {
+ conn_keywords[i] = "port";
+ conn_values[i] = port;
+ i++;
+ }
+ if (user && strlen(user) > 0)
+ {
+ conn_keywords[i] = "user";
+ conn_values[i] = user;
+ i++;
+ }
+ if (passwd && strlen(passwd) > 0)
+ {
+ conn_keywords[i] = "password";
+ conn_values[i] = passwd;
+ i++;
+ }
+ if (options)
+ {
+ char *str;
+
+ /*
+ * The options string contains "keyword=value" pairs separated by
+ * '&'s. We must break this up into keywords and values to pass to
+ * libpq (it's okay to scribble on the options string). We ignore
+ * spaces just before each keyword or value.
+ */
+ for (str = options; *str;)
+ {
+ int e,
+ a;
+ char *token1,
+ *token2;
+
+ /* Skip spaces before keyword */
+ for (token1 = str; *token1 == ' '; token1++)
+ /* skip */ ;
+ /* Find end of keyword */
+ for (e = 0; token1[e] && token1[e] != '='; e++)
+ /* skip */ ;
+ if (token1[e]) /* found "=" */
+ {
+ token1[e] = '\0';
+ /* Skip spaces before value */
+ for (token2 = token1 + e + 1; *token2 == ' '; token2++)
+ /* skip */ ;
+ /* Find end of value */
+ for (a = 0; token2[a] && token2[a] != '&'; a++)
+ /* skip */ ;
+ if (token2[a]) /* found "&" => another option follows */
+ {
+ token2[a] = '\0';
+ str = token2 + a + 1;
+ }
+ else
+ str = token2 + a;
+
+ conn_keywords[i] = token1;
+ conn_values[i] = token2;
+ i++;
+ }
+ else
+ {
+ /* Bogus options syntax ... ignore trailing garbage */
+ str = token1 + e;
+ }
+ }
+ }
+
+ Assert(i <= connect_params);
+ conn_keywords[i] = NULL; /* terminator */
+
+ this->connection = PQconnectdbParams(conn_keywords, conn_values, 0);
+
+ if (host)
+ ecpg_free(host);
+ if (port)
+ ecpg_free(port);
+ if (options)
+ ecpg_free(options);
+ if (dbname)
+ ecpg_free(dbname);
+ ecpg_free(conn_values);
+ ecpg_free(conn_keywords);
+
+ if (PQstatus(this->connection) == CONNECTION_BAD)
+ {
+ const char *errmsg = PQerrorMessage(this->connection);
+ const char *db = realname ? realname : ecpg_gettext("<DEFAULT>");
+
+ /* PQerrorMessage's result already has a trailing newline */
+ ecpg_log("ECPGconnect: %s", errmsg);
+
+ ecpg_finish(this);
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&connections_mutex);
+#endif
+
+ ecpg_raise(lineno, ECPG_CONNECT, ECPG_SQLSTATE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION, db);
+ if (realname)
+ ecpg_free(realname);
+
+ return false;
+ }
+
+ if (realname)
+ ecpg_free(realname);
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&connections_mutex);
+#endif
+
+ this->autocommit = autocommit;
+
+ PQsetNoticeReceiver(this->connection, &ECPGnoticeReceiver, (void *) this);
+
+ return true;
+}
+
+bool
+ECPGdisconnect(int lineno, const char *connection_name)
+{
+ struct sqlca_t *sqlca = ECPGget_sqlca();
+ struct connection *con;
+
+ if (sqlca == NULL)
+ {
+ ecpg_raise(lineno, ECPG_OUT_OF_MEMORY,
+ ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
+ return false;
+ }
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_lock(&connections_mutex);
+#endif
+
+ if (strcmp(connection_name, "ALL") == 0)
+ {
+ ecpg_init_sqlca(sqlca);
+ for (con = all_connections; con;)
+ {
+ struct connection *f = con;
+
+ con = con->next;
+ ecpg_finish(f);
+ }
+ }
+ else
+ {
+ con = ecpg_get_connection_nr(connection_name);
+
+ if (!ecpg_init(con, connection_name, lineno))
+ {
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&connections_mutex);
+#endif
+ return false;
+ }
+ else
+ ecpg_finish(con);
+ }
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&connections_mutex);
+#endif
+
+ return true;
+}
+
+PGconn *
+ECPGget_PGconn(const char *connection_name)
+{
+ struct connection *con;
+
+ con = ecpg_get_connection(connection_name);
+ if (con == NULL)
+ return NULL;
+
+ return con->connection;
+}