| /* 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" |
| #include "common/mdb_locale.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; |
| } |