pg_con.c 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. /*
  2. * $Id$
  3. *
  4. * PostgreSQL Database Driver for SER
  5. *
  6. * Portions Copyright (C) 2001-2003 FhG FOKUS
  7. * Copyright (C) 2003 August.Net Services, LLC
  8. * Portions Copyright (C) 2005-2008 iptelorg GmbH
  9. *
  10. * This file is part of SER, a free SIP server.
  11. *
  12. * SER is free software; you can redistribute it and/or modify it under the
  13. * terms of the GNU General Public License as published by the Free Software
  14. * Foundation; either version 2 of the License, or (at your option) any later
  15. * version
  16. *
  17. * For a license to use the ser software under conditions other than those
  18. * described here, or to purchase support for this software, please contact
  19. * iptel.org by e-mail at the following addresses: [email protected]
  20. *
  21. * SER is distributed in the hope that it will be useful, but WITHOUT ANY
  22. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  23. * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  24. * details.
  25. *
  26. * You should have received a copy of the GNU General Public License along
  27. * with this program; if not, write to the Free Software Foundation, Inc., 59
  28. * Temple Place, Suite 330, Boston, MA 02111-1307 USA
  29. */
  30. /** \addtogroup postgres
  31. * @{
  32. */
  33. /** \file
  34. * Functions related to connections to PostgreSQL servers.
  35. */
  36. #include "pg_con.h"
  37. #include "pg_uri.h"
  38. #include "pg_sql.h"
  39. #include "pg_mod.h"
  40. #include "../../mem/mem.h"
  41. #include "../../dprint.h"
  42. #include "../../ut.h"
  43. #include <stdlib.h>
  44. #include <string.h>
  45. #include <netinet/in.h>
  46. #include <netinet/tcp.h>
  47. #include <time.h>
  48. /* Override the default notice processor to output the messages
  49. * using SER's output subsystem.
  50. */
  51. static void notice_processor(void* arg, const char* message)
  52. {
  53. LOG(L_NOTICE, "postgres: %s\n", message);
  54. }
  55. /** Determine the format of timestamps used by the server.
  56. * A PostgresSQL server can be configured to store timestamps either as 8-byte
  57. * integers or floating point numbers with double precision. This functions
  58. * sends a simple SQL query to the server and tries to determine the format of
  59. * timestamps from the reply. This function is executed once after connecting
  60. * to a PostgreSQL server and the result of the detection is then stored in
  61. * form of a flag in pg_con connection structure.
  62. * @param con A PostgreSQL connection handle
  63. * @retval 0 If the server stores timestamps as floating point numbers.
  64. * @retval 1 If the server stores timestamps as 8-byte integers.
  65. * @retval A negative number on error.
  66. */
  67. static int timestamp_format(PGconn* con)
  68. {
  69. unsigned long long offset;
  70. PGresult* res = 0;
  71. char* val;
  72. str sql;
  73. if (build_timestamp_format_sql(&sql) != 0) {
  74. ERR("postgres: Error while building SQL query to obtain timestamp format\n");
  75. return -1;
  76. }
  77. res = PQexecParams(con, sql.s, 0, 0, 0, 0, 0, 1);
  78. pkg_free(sql.s);
  79. if (PQfformat(res, 0) != 1) {
  80. ERR("postgres: Binary format expected but server sent text\n");
  81. goto error;
  82. }
  83. if (PQntuples(res) != 1) {
  84. ERR("postgres: Only one column expected, %d received\n", PQntuples(res));
  85. goto error;
  86. }
  87. if (PQnfields(res) != 1) {
  88. ERR("postgres: Only one row expected, %d received\n", PQnfields(res));
  89. goto error;
  90. }
  91. val = PQgetvalue(res, 0, 0);
  92. offset = ((unsigned long long)ntohl(((unsigned int*)val)[0]) << 32)
  93. + ntohl(((unsigned int*)val)[1]);
  94. PQclear(res);
  95. /* Server using int8 timestamps would return 1000000, because it stores
  96. * timestamps in microsecond resolution across the whole range. Server
  97. * using double timestamps would return 1 (encoded as double) here because
  98. * subsection fraction is stored as fractional part in the IEEE
  99. * representation. 1 stored as double would result in 4607182418800017408
  100. * when the memory location occupied by the variable is read as unsigned
  101. * long long.
  102. */
  103. if (offset == 1000000) {
  104. DBG("postgres: Server uses int8 format for timestamps.\n");
  105. return 1;
  106. } else {
  107. DBG("postgres: Server uses double format for timestamps.\n");
  108. return 0;
  109. }
  110. error:
  111. PQclear(res);
  112. return -1;
  113. }
  114. /** Retrieves a list of all supported field types from the server.
  115. * This function retrieves a list of all supported field types and their Oids
  116. * from system catalogs of the server. The list is then stored in pg_con
  117. * connection structure and it is used to map field type names, such as int2,
  118. * int4, float4, etc. to Oids. Every PostgreSQL server can map field types to
  119. * different Oids so we need to store the mapping array in the connection
  120. * structure.
  121. * @param con A structure representing connection to PostgreSQL server.
  122. * @retval 0 If executed successfully.
  123. * @retval A negative number on error.
  124. */
  125. static int get_oids(db_con_t* con)
  126. {
  127. struct pg_con* pcon;
  128. PGresult* res = NULL;
  129. str sql;
  130. pcon = DB_GET_PAYLOAD(con);
  131. if (build_select_oid_sql(&sql) < 0) goto error;
  132. res = PQexec(pcon->con, sql.s);
  133. pkg_free(sql.s);
  134. if (res == NULL || PQresultStatus(res) != PGRES_TUPLES_OK) goto error;
  135. pcon->oid = pg_new_oid_table(res);
  136. PQclear(res);
  137. if (pcon->oid == NULL) goto error;
  138. return 0;
  139. error:
  140. if (res) PQclear(res);
  141. return -1;
  142. }
  143. /** Free all memory allocated for a pg_con structure.
  144. * This function function frees all memory that is in use by
  145. * a pg_con structure.
  146. * @param con A generic db_con connection structure.
  147. * @param payload PostgreSQL specific payload to be freed.
  148. */
  149. static void pg_con_free(db_con_t* con, struct pg_con* payload)
  150. {
  151. if (!payload) return;
  152. /* Delete the structure only if there are no more references
  153. * to it in the connection pool
  154. */
  155. if (db_pool_remove((db_pool_entry_t*)payload) == 0) return;
  156. db_pool_entry_free(&payload->gen);
  157. pg_destroy_oid_table(payload->oid);
  158. if (payload->con) PQfinish(payload->con);
  159. pkg_free(payload);
  160. }
  161. int pg_con(db_con_t* con)
  162. {
  163. struct pg_con* pcon;
  164. /* First try to lookup the connection in the connection pool and
  165. * re-use it if a match is found
  166. */
  167. pcon = (struct pg_con*)db_pool_get(con->uri);
  168. if (pcon) {
  169. DBG("postgres: Connection to %.*s:%.*s found in connection pool\n",
  170. con->uri->scheme.len, ZSW(con->uri->scheme.s),
  171. con->uri->body.len, ZSW(con->uri->body.s));
  172. goto found;
  173. }
  174. pcon = (struct pg_con*)pkg_malloc(sizeof(struct pg_con));
  175. if (!pcon) {
  176. LOG(L_ERR, "postgres: No memory left\n");
  177. goto error;
  178. }
  179. memset(pcon, '\0', sizeof(struct pg_con));
  180. if (db_pool_entry_init(&pcon->gen, pg_con_free, con->uri) < 0) goto error;
  181. DBG("postgres: Preparing new connection to: %.*s:%.*s\n",
  182. con->uri->scheme.len, ZSW(con->uri->scheme.s),
  183. con->uri->body.len, ZSW(con->uri->body.s));
  184. /* Put the newly created postgres connection into the pool */
  185. db_pool_put((struct db_pool_entry*)pcon);
  186. DBG("postgres: Connection stored in connection pool\n");
  187. found:
  188. /* Attach driver payload to the db_con structure and set connect and
  189. * disconnect functions
  190. */
  191. DB_SET_PAYLOAD(con, pcon);
  192. con->connect = pg_con_connect;
  193. con->disconnect = pg_con_disconnect;
  194. return 0;
  195. error:
  196. if (pcon) {
  197. db_pool_entry_free(&pcon->gen);
  198. pkg_free(pcon);
  199. }
  200. return -1;
  201. }
  202. int pg_con_connect(db_con_t* con)
  203. {
  204. struct pg_con* pcon;
  205. struct pg_uri* puri;
  206. char* port_str;
  207. int ret, i = 0;
  208. const char *keywords[10], *values[10];
  209. char to[16];
  210. pcon = DB_GET_PAYLOAD(con);
  211. puri = DB_GET_PAYLOAD(con->uri);
  212. /* Do not reconnect already connected connections */
  213. if (pcon->flags & PG_CONNECTED) return 0;
  214. DBG("postgres: Connecting to %.*s:%.*s\n",
  215. con->uri->scheme.len, ZSW(con->uri->scheme.s),
  216. con->uri->body.len, ZSW(con->uri->body.s));
  217. if (puri->port > 0) {
  218. port_str = int2str(puri->port, 0);
  219. keywords[i] = "port";
  220. values[i++] = port_str;
  221. } else {
  222. port_str = NULL;
  223. }
  224. if (pcon->con) {
  225. PQfinish(pcon->con);
  226. pcon->con = NULL;
  227. }
  228. keywords[i] = "host";
  229. values[i++] = puri->host;
  230. keywords[i] = "dbname";
  231. values[i++] = puri->database;
  232. keywords[i] = "user";
  233. values[i++] = puri->username;
  234. keywords[i] = "password";
  235. values[i++] = puri->password;
  236. if (pg_timeout > 0) {
  237. snprintf(to, sizeof(to)-1, "%d", pg_timeout + 3);
  238. keywords[i] = "connect_timeout";
  239. values[i++] = to;
  240. }
  241. keywords[i] = values[i] = NULL;
  242. pcon->con = PQconnectdbParams(keywords, values, 1);
  243. if (pcon->con == NULL) {
  244. ERR("postgres: PQconnectdbParams ran out of memory\n");
  245. goto error;
  246. }
  247. if (PQstatus(pcon->con) != CONNECTION_OK) {
  248. ERR("postgres: %s\n", PQerrorMessage(pcon->con));
  249. goto error;
  250. }
  251. /* Override default notice processor */
  252. PQsetNoticeProcessor(pcon->con, notice_processor, 0);
  253. #ifdef HAVE_PGSERVERVERSION
  254. DBG("postgres: Connected. Protocol version=%d, Server version=%d\n",
  255. PQprotocolVersion(pcon->con), PQserverVersion(pcon->con));
  256. #else
  257. DBG("postgres: Connected. Protocol version=%d, Server version=%d\n",
  258. PQprotocolVersion(pcon->con), 0 );
  259. #endif
  260. #if defined(SO_KEEPALIVE) && defined(TCP_KEEPIDLE)
  261. if (pg_keepalive) {
  262. i = 1;
  263. setsockopt(PQsocket(pcon->con), SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i));
  264. setsockopt(PQsocket(pcon->con), IPPROTO_TCP, TCP_KEEPIDLE, &pg_keepalive, sizeof(pg_keepalive));
  265. }
  266. #endif
  267. ret = timestamp_format(pcon->con);
  268. if (ret == 1 || ret == -1) {
  269. /* Assume INT8 representation if detection fails */
  270. pcon->flags |= PG_INT8_TIMESTAMP;
  271. } else {
  272. pcon->flags &= ~PG_INT8_TIMESTAMP;
  273. }
  274. if (get_oids(con) < 0) goto error;
  275. pcon->flags |= PG_CONNECTED;
  276. return 0;
  277. error:
  278. if (pcon->con) PQfinish(pcon->con);
  279. pcon->con = NULL;
  280. return -1;
  281. }
  282. void pg_con_disconnect(db_con_t* con)
  283. {
  284. struct pg_con* pcon;
  285. pcon = DB_GET_PAYLOAD(con);
  286. if ((pcon->flags & PG_CONNECTED) == 0) return;
  287. DBG("postgres: Disconnecting from %.*s:%.*s\n",
  288. con->uri->scheme.len, ZSW(con->uri->scheme.s),
  289. con->uri->body.len, ZSW(con->uri->body.s));
  290. PQfinish(pcon->con);
  291. pcon->con = NULL;
  292. pcon->flags &= ~PG_CONNECTED;
  293. pcon->flags &= ~PG_INT8_TIMESTAMP;
  294. }
  295. /** @} */