Wed Jan 8 2020 09:49:42

Asterisk developer's documentation


cdr_pgsql.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2003 - 2012
5  *
6  * Matthew D. Hardeman <mhardemn@papersoft.com>
7  * Adapted from the MySQL CDR logger originally by James Sharp
8  *
9  * Modified September 2003
10  * Matthew D. Hardeman <mhardemn@papersoft.com>
11  *
12  * See http://www.asterisk.org for more information about
13  * the Asterisk project. Please do not directly contact
14  * any of the maintainers of this project for assistance;
15  * the project provides a web site, mailing lists and IRC
16  * channels for your use.
17  *
18  * This program is free software, distributed under the terms of
19  * the GNU General Public License Version 2. See the LICENSE file
20  * at the top of the source tree.
21  */
22 
23 /*!
24  * \file
25  * \brief PostgreSQL CDR logger
26  *
27  * \author Matthew D. Hardeman <mhardemn@papersoft.com>
28  * \extref PostgreSQL http://www.postgresql.org/
29  *
30  * See also
31  * \arg \ref Config_cdr
32  * \extref PostgreSQL http://www.postgresql.org/
33  * \ingroup cdr_drivers
34  */
35 
36 /*** MODULEINFO
37  <depend>pgsql</depend>
38  <support_level>extended</support_level>
39  ***/
40 
41 #include "asterisk.h"
42 
43 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 370642 $")
44 
45 #include <libpq-fe.h>
46 
47 #include "asterisk/config.h"
48 #include "asterisk/channel.h"
49 #include "asterisk/cdr.h"
50 #include "asterisk/module.h"
51 
52 #define DATE_FORMAT "'%Y-%m-%d %T'"
53 
54 static const char name[] = "pgsql";
55 static const char config[] = "cdr_pgsql.conf";
56 static char *pghostname = NULL, *pgdbname = NULL, *pgdbuser = NULL, *pgpassword = NULL, *pgdbport = NULL, *table = NULL, *encoding = NULL, *tz = NULL;
57 static int connected = 0;
58 static int maxsize = 512, maxsize2 = 512;
59 
61 
62 static PGconn *conn = NULL;
63 
64 struct columns {
65  char *name;
66  char *type;
67  int len;
68  unsigned int notnull:1;
69  unsigned int hasdefault:1;
71 };
72 
74 
75 #define LENGTHEN_BUF1(size) \
76  do { \
77  /* Lengthen buffer, if necessary */ \
78  if (ast_str_strlen(sql) + size + 1 > ast_str_size(sql)) { \
79  if (ast_str_make_space(&sql, ((ast_str_size(sql) + size + 3) / 512 + 1) * 512) != 0) { \
80  ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CDR failed.\n"); \
81  ast_free(sql); \
82  ast_free(sql2); \
83  AST_RWLIST_UNLOCK(&psql_columns); \
84  ast_mutex_unlock(&pgsql_lock); \
85  return -1; \
86  } \
87  } \
88  } while (0)
89 
90 #define LENGTHEN_BUF2(size) \
91  do { \
92  if (ast_str_strlen(sql2) + size + 1 > ast_str_size(sql2)) { \
93  if (ast_str_make_space(&sql2, ((ast_str_size(sql2) + size + 3) / 512 + 1) * 512) != 0) { \
94  ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CDR failed.\n"); \
95  ast_free(sql); \
96  ast_free(sql2); \
97  AST_RWLIST_UNLOCK(&psql_columns); \
98  ast_mutex_unlock(&pgsql_lock); \
99  return -1; \
100  } \
101  } \
102  } while (0)
103 
104 static int pgsql_log(struct ast_cdr *cdr)
105 {
106  struct ast_tm tm;
107  char *pgerror;
108  PGresult *result;
109 
111 
112  if ((!connected) && pghostname && pgdbuser && pgpassword && pgdbname) {
113  conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword);
114  if (PQstatus(conn) != CONNECTION_BAD) {
115  connected = 1;
116  if (PQsetClientEncoding(conn, encoding)) {
117 #ifdef HAVE_PGSQL_pg_encoding_to_char
118  ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default '%s'\n", encoding, pg_encoding_to_char(PQclientEncoding(conn)));
119 #else
120  ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default.\n", encoding);
121 #endif
122  }
123  } else {
124  pgerror = PQerrorMessage(conn);
125  ast_log(LOG_ERROR, "Unable to connect to database server %s. Calls will not be logged!\n", pghostname);
126  ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
127  PQfinish(conn);
128  conn = NULL;
129  }
130  }
131 
132  if (connected) {
133  struct columns *cur;
134  struct ast_str *sql = ast_str_create(maxsize), *sql2 = ast_str_create(maxsize2);
135  char buf[257], escapebuf[513], *value;
136  int first = 1;
137 
138  if (!sql || !sql2) {
139  ast_free(sql);
140  ast_free(sql2);
141  return -1;
142  }
143 
144  ast_str_set(&sql, 0, "INSERT INTO %s (", table);
145  ast_str_set(&sql2, 0, " VALUES (");
146 
147  AST_RWLIST_RDLOCK(&psql_columns);
148  AST_RWLIST_TRAVERSE(&psql_columns, cur, list) {
149  /* For fields not set, simply skip them */
150  ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
151  if (strcmp(cur->name, "calldate") == 0 && !value) {
152  ast_cdr_getvar(cdr, "start", &value, buf, sizeof(buf), 0, 0);
153  }
154  if (!value) {
155  if (cur->notnull && !cur->hasdefault) {
156  /* Field is NOT NULL (but no default), must include it anyway */
157  LENGTHEN_BUF1(strlen(cur->name) + 2);
158  ast_str_append(&sql, 0, "%s\"%s\"", first ? "" : ",", cur->name);
159  LENGTHEN_BUF2(3);
160  ast_str_append(&sql2, 0, "%s''", first ? "" : ",");
161  first = 0;
162  }
163  continue;
164  }
165 
166  LENGTHEN_BUF1(strlen(cur->name) + 2);
167  ast_str_append(&sql, 0, "%s\"%s\"", first ? "" : ",", cur->name);
168 
169  if (strcmp(cur->name, "start") == 0 || strcmp(cur->name, "calldate") == 0) {
170  if (strncmp(cur->type, "int", 3) == 0) {
171  LENGTHEN_BUF2(13);
172  ast_str_append(&sql2, 0, "%s%ld", first ? "" : ",", (long) cdr->start.tv_sec);
173  } else if (strncmp(cur->type, "float", 5) == 0) {
174  LENGTHEN_BUF2(31);
175  ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double)cdr->start.tv_sec + (double)cdr->start.tv_usec / 1000000.0);
176  } else {
177  /* char, hopefully */
178  LENGTHEN_BUF2(31);
179  ast_localtime(&cdr->start, &tm, tz);
180  ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
181  ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", buf);
182  }
183  } else if (strcmp(cur->name, "answer") == 0) {
184  if (strncmp(cur->type, "int", 3) == 0) {
185  LENGTHEN_BUF2(13);
186  ast_str_append(&sql2, 0, "%s%ld", first ? "" : ",", (long) cdr->answer.tv_sec);
187  } else if (strncmp(cur->type, "float", 5) == 0) {
188  LENGTHEN_BUF2(31);
189  ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double)cdr->answer.tv_sec + (double)cdr->answer.tv_usec / 1000000.0);
190  } else {
191  /* char, hopefully */
192  LENGTHEN_BUF2(31);
193  ast_localtime(&cdr->answer, &tm, tz);
194  ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
195  ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", buf);
196  }
197  } else if (strcmp(cur->name, "end") == 0) {
198  if (strncmp(cur->type, "int", 3) == 0) {
199  LENGTHEN_BUF2(13);
200  ast_str_append(&sql2, 0, "%s%ld", first ? "" : ",", (long) cdr->end.tv_sec);
201  } else if (strncmp(cur->type, "float", 5) == 0) {
202  LENGTHEN_BUF2(31);
203  ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double)cdr->end.tv_sec + (double)cdr->end.tv_usec / 1000000.0);
204  } else {
205  /* char, hopefully */
206  LENGTHEN_BUF2(31);
207  ast_localtime(&cdr->end, &tm, tz);
208  ast_strftime(buf, sizeof(buf), DATE_FORMAT, &tm);
209  ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", buf);
210  }
211  } else if (strcmp(cur->name, "duration") == 0 || strcmp(cur->name, "billsec") == 0) {
212  if (cur->type[0] == 'i') {
213  /* Get integer, no need to escape anything */
214  ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
215  LENGTHEN_BUF2(13);
216  ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", value);
217  } else if (strncmp(cur->type, "float", 5) == 0) {
218  struct timeval *when = cur->name[0] == 'd' ? &cdr->start : ast_tvzero(cdr->answer) ? &cdr->end : &cdr->answer;
219  LENGTHEN_BUF2(31);
220  ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double) (ast_tvdiff_us(cdr->end, *when) / 1000000.0));
221  } else {
222  /* Char field, probably */
223  struct timeval *when = cur->name[0] == 'd' ? &cdr->start : ast_tvzero(cdr->answer) ? &cdr->end : &cdr->answer;
224  LENGTHEN_BUF2(31);
225  ast_str_append(&sql2, 0, "%s'%f'", first ? "" : ",", (double) (ast_tvdiff_us(cdr->end, *when) / 1000000.0));
226  }
227  } else if (strcmp(cur->name, "disposition") == 0 || strcmp(cur->name, "amaflags") == 0) {
228  if (strncmp(cur->type, "int", 3) == 0) {
229  /* Integer, no need to escape anything */
230  ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 1);
231  LENGTHEN_BUF2(13);
232  ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", value);
233  } else {
234  /* Although this is a char field, there are no special characters in the values for these fields */
235  ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
236  LENGTHEN_BUF2(31);
237  ast_str_append(&sql2, 0, "%s'%s'", first ? "" : ",", value);
238  }
239  } else {
240  /* Arbitrary field, could be anything */
241  ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
242  if (strncmp(cur->type, "int", 3) == 0) {
243  long long whatever;
244  if (value && sscanf(value, "%30lld", &whatever) == 1) {
245  LENGTHEN_BUF2(26);
246  ast_str_append(&sql2, 0, "%s%lld", first ? "" : ",", whatever);
247  } else {
248  LENGTHEN_BUF2(2);
249  ast_str_append(&sql2, 0, "%s0", first ? "" : ",");
250  }
251  } else if (strncmp(cur->type, "float", 5) == 0) {
252  long double whatever;
253  if (value && sscanf(value, "%30Lf", &whatever) == 1) {
254  LENGTHEN_BUF2(51);
255  ast_str_append(&sql2, 0, "%s%30Lf", first ? "" : ",", whatever);
256  } else {
257  LENGTHEN_BUF2(2);
258  ast_str_append(&sql2, 0, "%s0", first ? "" : ",");
259  }
260  /* XXX Might want to handle dates, times, and other misc fields here XXX */
261  } else {
262  if (value)
263  PQescapeStringConn(conn, escapebuf, value, strlen(value), NULL);
264  else
265  escapebuf[0] = '\0';
266  LENGTHEN_BUF2(strlen(escapebuf) + 3);
267  ast_str_append(&sql2, 0, "%s'%s'", first ? "" : ",", escapebuf);
268  }
269  }
270  first = 0;
271  }
272 
273  LENGTHEN_BUF1(ast_str_strlen(sql2) + 2);
274  AST_RWLIST_UNLOCK(&psql_columns);
275  ast_str_append(&sql, 0, ")%s)", ast_str_buffer(sql2));
276  ast_verb(11, "[%s]\n", ast_str_buffer(sql));
277 
278  ast_debug(2, "inserting a CDR record.\n");
279 
280  /* Test to be sure we're still connected... */
281  /* If we're connected, and connection is working, good. */
282  /* Otherwise, attempt reconnect. If it fails... sorry... */
283  if (PQstatus(conn) == CONNECTION_OK) {
284  connected = 1;
285  } else {
286  ast_log(LOG_ERROR, "Connection was lost... attempting to reconnect.\n");
287  PQreset(conn);
288  if (PQstatus(conn) == CONNECTION_OK) {
289  ast_log(LOG_ERROR, "Connection reestablished.\n");
290  connected = 1;
291  } else {
292  pgerror = PQerrorMessage(conn);
293  ast_log(LOG_ERROR, "Unable to reconnect to database server %s. Calls will not be logged!\n", pghostname);
294  ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
295  PQfinish(conn);
296  conn = NULL;
297  connected = 0;
299  ast_free(sql);
300  ast_free(sql2);
301  return -1;
302  }
303  }
304  result = PQexec(conn, ast_str_buffer(sql));
305  if (PQresultStatus(result) != PGRES_COMMAND_OK) {
306  pgerror = PQresultErrorMessage(result);
307  ast_log(LOG_ERROR, "Failed to insert call detail record into database!\n");
308  ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
309  ast_log(LOG_ERROR, "Connection may have been lost... attempting to reconnect.\n");
310  PQreset(conn);
311  if (PQstatus(conn) == CONNECTION_OK) {
312  ast_log(LOG_ERROR, "Connection reestablished.\n");
313  connected = 1;
314  PQclear(result);
315  result = PQexec(conn, ast_str_buffer(sql));
316  if (PQresultStatus(result) != PGRES_COMMAND_OK) {
317  pgerror = PQresultErrorMessage(result);
318  ast_log(LOG_ERROR, "HARD ERROR! Attempted reconnection failed. DROPPING CALL RECORD!\n");
319  ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
320  }
321  }
323  PQclear(result);
324  ast_free(sql);
325  ast_free(sql2);
326  return -1;
327  }
328  PQclear(result);
329  ast_free(sql);
330  ast_free(sql2);
331  }
333  return 0;
334 }
335 
336 /* This function should be called without holding the pgsql_columns lock */
337 static void empty_columns(void)
338 {
339  struct columns *current;
340  AST_RWLIST_WRLOCK(&psql_columns);
341  while ((current = AST_RWLIST_REMOVE_HEAD(&psql_columns, list))) {
342  ast_free(current);
343  }
344  AST_RWLIST_UNLOCK(&psql_columns);
345 
346 }
347 
348 static int unload_module(void)
349 {
350  ast_cdr_unregister(name);
351 
352  PQfinish(conn);
353 
354  ast_free(pghostname);
355  ast_free(pgdbname);
356  ast_free(pgdbuser);
357  ast_free(pgpassword);
358  ast_free(pgdbport);
359  ast_free(table);
360  ast_free(encoding);
361  ast_free(tz);
362 
363  empty_columns();
364 
365  return 0;
366 }
367 
368 static int config_module(int reload)
369 {
370  struct ast_variable *var;
371  char *pgerror;
372  struct columns *cur;
373  PGresult *result;
374  const char *tmp;
375  struct ast_config *cfg;
376  struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
377 
378  if ((cfg = ast_config_load(config, config_flags)) == NULL || cfg == CONFIG_STATUS_FILEINVALID) {
379  ast_log(LOG_WARNING, "Unable to load config for PostgreSQL CDR's: %s\n", config);
380  return -1;
381  } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
382  return 0;
383  }
384 
386 
387  if (!(var = ast_variable_browse(cfg, "global"))) {
388  ast_config_destroy(cfg);
390  ast_log(LOG_NOTICE, "cdr_pgsql configuration contains no global section, skipping module %s.\n",
391  reload ? "reload" : "load");
392  return -1;
393  }
394 
395  if (!(tmp = ast_variable_retrieve(cfg, "global", "hostname"))) {
396  ast_log(LOG_WARNING, "PostgreSQL server hostname not specified. Assuming unix socket connection\n");
397  tmp = ""; /* connect via UNIX-socket by default */
398  }
399 
400  ast_free(pghostname);
401  if (!(pghostname = ast_strdup(tmp))) {
402  ast_config_destroy(cfg);
404  return -1;
405  }
406 
407  if (!(tmp = ast_variable_retrieve(cfg, "global", "dbname"))) {
408  ast_log(LOG_WARNING, "PostgreSQL database not specified. Assuming asterisk\n");
409  tmp = "asteriskcdrdb";
410  }
411 
412  ast_free(pgdbname);
413  if (!(pgdbname = ast_strdup(tmp))) {
414  ast_config_destroy(cfg);
416  return -1;
417  }
418 
419  if (!(tmp = ast_variable_retrieve(cfg, "global", "user"))) {
420  ast_log(LOG_WARNING, "PostgreSQL database user not specified. Assuming asterisk\n");
421  tmp = "asterisk";
422  }
423 
424  ast_free(pgdbuser);
425  if (!(pgdbuser = ast_strdup(tmp))) {
426  ast_config_destroy(cfg);
428  return -1;
429  }
430 
431  if (!(tmp = ast_variable_retrieve(cfg, "global", "password"))) {
432  ast_log(LOG_WARNING, "PostgreSQL database password not specified. Assuming blank\n");
433  tmp = "";
434  }
435 
436  ast_free(pgpassword);
437  if (!(pgpassword = ast_strdup(tmp))) {
438  ast_config_destroy(cfg);
440  return -1;
441  }
442 
443  if (!(tmp = ast_variable_retrieve(cfg, "global", "port"))) {
444  ast_log(LOG_WARNING, "PostgreSQL database port not specified. Using default 5432.\n");
445  tmp = "5432";
446  }
447 
448  ast_free(pgdbport);
449  if (!(pgdbport = ast_strdup(tmp))) {
450  ast_config_destroy(cfg);
452  return -1;
453  }
454 
455  if (!(tmp = ast_variable_retrieve(cfg, "global", "table"))) {
456  ast_log(LOG_WARNING, "CDR table not specified. Assuming cdr\n");
457  tmp = "cdr";
458  }
459 
460  ast_free(table);
461  if (!(table = ast_strdup(tmp))) {
462  ast_config_destroy(cfg);
464  return -1;
465  }
466 
467  if (!(tmp = ast_variable_retrieve(cfg, "global", "encoding"))) {
468  ast_log(LOG_WARNING, "Encoding not specified. Assuming LATIN9\n");
469  tmp = "LATIN9";
470  }
471 
472  ast_free(encoding);
473  if (!(encoding = ast_strdup(tmp))) {
474  ast_config_destroy(cfg);
476  return -1;
477  }
478 
479  if (!(tmp = ast_variable_retrieve(cfg, "global", "timezone"))) {
480  tmp = "";
481  }
482 
483  ast_free(tz);
484  tz = NULL;
485 
486  if (!ast_strlen_zero(tmp) && !(tz = ast_strdup(tmp))) {
487  ast_config_destroy(cfg);
489  return -1;
490  }
491 
492  if (option_debug) {
493  if (ast_strlen_zero(pghostname)) {
494  ast_debug(1, "using default unix socket\n");
495  } else {
496  ast_debug(1, "got hostname of %s\n", pghostname);
497  }
498  ast_debug(1, "got port of %s\n", pgdbport);
499  ast_debug(1, "got user of %s\n", pgdbuser);
500  ast_debug(1, "got dbname of %s\n", pgdbname);
501  ast_debug(1, "got password of %s\n", pgpassword);
502  ast_debug(1, "got sql table name of %s\n", table);
503  ast_debug(1, "got encoding of %s\n", encoding);
504  ast_debug(1, "got timezone of %s\n", tz);
505  }
506 
507  conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword);
508  if (PQstatus(conn) != CONNECTION_BAD) {
509  char sqlcmd[768];
510  char *fname, *ftype, *flen, *fnotnull, *fdef;
511  int i, rows, version;
512  ast_debug(1, "Successfully connected to PostgreSQL database.\n");
513  connected = 1;
514  if (PQsetClientEncoding(conn, encoding)) {
515 #ifdef HAVE_PGSQL_pg_encoding_to_char
516  ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default '%s'\n", encoding, pg_encoding_to_char(PQclientEncoding(conn)));
517 #else
518  ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default.\n", encoding);
519 #endif
520  }
521  version = PQserverVersion(conn);
522 
523  if (version >= 70300) {
524  char *schemaname, *tablename;
525  if (strchr(table, '.')) {
526  schemaname = ast_strdupa(table);
527  tablename = strchr(schemaname, '.');
528  *tablename++ = '\0';
529  } else {
530  schemaname = "";
531  tablename = table;
532  }
533 
534  /* Escape special characters in schemaname */
535  if (strchr(schemaname, '\\') || strchr(schemaname, '\'')) {
536  char *tmp = schemaname, *ptr;
537 
538  ptr = schemaname = ast_alloca(strlen(tmp) * 2 + 1);
539  for (; *tmp; tmp++) {
540  if (strchr("\\'", *tmp)) {
541  *ptr++ = *tmp;
542  }
543  *ptr++ = *tmp;
544  }
545  *ptr = '\0';
546  }
547  /* Escape special characters in tablename */
548  if (strchr(tablename, '\\') || strchr(tablename, '\'')) {
549  char *tmp = tablename, *ptr;
550 
551  ptr = tablename = ast_alloca(strlen(tmp) * 2 + 1);
552  for (; *tmp; tmp++) {
553  if (strchr("\\'", *tmp)) {
554  *ptr++ = *tmp;
555  }
556  *ptr++ = *tmp;
557  }
558  *ptr = '\0';
559  }
560 
561  snprintf(sqlcmd, sizeof(sqlcmd), "SELECT a.attname, t.typname, a.attlen, a.attnotnull, d.adsrc, a.atttypmod FROM (((pg_catalog.pg_class c INNER JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace AND c.relname = '%s' AND n.nspname = %s%s%s) INNER JOIN pg_catalog.pg_attribute a ON (NOT a.attisdropped) AND a.attnum > 0 AND a.attrelid = c.oid) INNER JOIN pg_catalog.pg_type t ON t.oid = a.atttypid) LEFT OUTER JOIN pg_attrdef d ON a.atthasdef AND d.adrelid = a.attrelid AND d.adnum = a.attnum ORDER BY n.nspname, c.relname, attnum",
562  tablename,
563  ast_strlen_zero(schemaname) ? "" : "'", ast_strlen_zero(schemaname) ? "current_schema()" : schemaname, ast_strlen_zero(schemaname) ? "" : "'");
564  } else {
565  snprintf(sqlcmd, sizeof(sqlcmd), "SELECT a.attname, t.typname, a.attlen, a.attnotnull, d.adsrc, a.atttypmod FROM pg_class c, pg_type t, pg_attribute a LEFT OUTER JOIN pg_attrdef d ON a.atthasdef AND d.adrelid = a.attrelid AND d.adnum = a.attnum WHERE c.oid = a.attrelid AND a.atttypid = t.oid AND (a.attnum > 0) AND c.relname = '%s' ORDER BY c.relname, attnum", table);
566  }
567  /* Query the columns */
568  result = PQexec(conn, sqlcmd);
569  if (PQresultStatus(result) != PGRES_TUPLES_OK) {
570  pgerror = PQresultErrorMessage(result);
571  ast_log(LOG_ERROR, "Failed to query database columns: %s\n", pgerror);
572  PQclear(result);
573  unload_module();
576  }
577 
578  rows = PQntuples(result);
579  if (rows == 0) {
580  ast_log(LOG_ERROR, "cdr_pgsql: Failed to query database columns. No columns found, does the table exist?\n");
581  PQclear(result);
582  unload_module();
585  }
586 
587  /* Clear out the columns list. */
588  empty_columns();
589 
590  for (i = 0; i < rows; i++) {
591  fname = PQgetvalue(result, i, 0);
592  ftype = PQgetvalue(result, i, 1);
593  flen = PQgetvalue(result, i, 2);
594  fnotnull = PQgetvalue(result, i, 3);
595  fdef = PQgetvalue(result, i, 4);
596  if (atoi(flen) == -1) {
597  /* For varchar columns, the maximum length is encoded in a different field */
598  flen = PQgetvalue(result, i, 5);
599  }
600  ast_verb(4, "Found column '%s' of type '%s'\n", fname, ftype);
601  cur = ast_calloc(1, sizeof(*cur) + strlen(fname) + strlen(ftype) + 2);
602  if (cur) {
603  sscanf(flen, "%30d", &cur->len);
604  cur->name = (char *)cur + sizeof(*cur);
605  cur->type = (char *)cur + sizeof(*cur) + strlen(fname) + 1;
606  strcpy(cur->name, fname);
607  strcpy(cur->type, ftype);
608  if (*fnotnull == 't') {
609  cur->notnull = 1;
610  } else {
611  cur->notnull = 0;
612  }
613  if (!ast_strlen_zero(fdef)) {
614  cur->hasdefault = 1;
615  } else {
616  cur->hasdefault = 0;
617  }
618  AST_RWLIST_WRLOCK(&psql_columns);
619  AST_RWLIST_INSERT_TAIL(&psql_columns, cur, list);
620  AST_RWLIST_UNLOCK(&psql_columns);
621  }
622  }
623  PQclear(result);
624  } else {
625  pgerror = PQerrorMessage(conn);
626  ast_log(LOG_ERROR, "Unable to connect to database server %s. CALLS WILL NOT BE LOGGED!!\n", pghostname);
627  ast_log(LOG_ERROR, "Reason: %s\n", pgerror);
628  connected = 0;
629  }
630 
631  ast_config_destroy(cfg);
632 
634  return 0;
635 }
636 
637 static int load_module(void)
638 {
639  if (config_module(0)) {
641  }
644 }
645 
646 static int reload(void)
647 {
648  return config_module(1);
649 }
650 
651 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PostgreSQL CDR Backend",
652  .load = load_module,
653  .unload = unload_module,
654  .reload = reload,
655  .load_pri = AST_MODPRI_CDR_DRIVER,
656  );
unsigned int hasdefault
Definition: cdr_pgsql.c:69
const char * description
Definition: module.h:234
static PGconn * conn
Definition: cdr_pgsql.c:62
uint32_t version
Asterisk main include file. File version handling, generic pbx functions.
static char * encoding
Definition: cdr_pgsql.c:56
const char * ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable)
Gets a variable.
Definition: config.c:625
#define AST_RWLIST_HEAD_STATIC(name, type)
Defines a structure to be used to hold a read/write list of specified type, statically initialized...
Definition: linkedlists.h:332
static const char config[]
Definition: cdr_pgsql.c:55
char * type
Definition: cdr_pgsql.c:66
#define ast_strdup(a)
Definition: astmm.h:109
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: utils.h:653
static char * pgdbuser
Definition: cdr_pgsql.c:56
int option_debug
Definition: asterisk.c:182
static ast_mutex_t pgsql_lock
Definition: cdr_pgsql.c:60
#define AST_RWLIST_WRLOCK(head)
Write locks a list.
Definition: linkedlists.h:51
static char * pgdbport
Definition: cdr_pgsql.c:56
#define LOG_WARNING
Definition: logger.h:144
struct ast_variable * ast_variable_browse(const struct ast_config *config, const char *category)
Goes through variables.
Definition: config.c:597
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:497
#define AST_RWLIST_UNLOCK(head)
Attempts to unlock a read/write based list.
Definition: linkedlists.h:150
struct ast_tm * ast_localtime(const struct timeval *timep, struct ast_tm *p_tm, const char *zone)
Timezone-independent version of localtime_r(3).
Definition: localtime.c:1570
Structure for variables, used for configurations and for channel variables.
Definition: config.h:75
#define var
Definition: ast_expr2f.c:606
int ast_tvzero(const struct timeval t)
Returns true if the argument is 0,0.
Definition: time.h:100
Configuration File Parser.
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
Definition: strings.h:900
struct ast_str * ast_str_create(size_t init_len)
Create a malloc&#39;ed dynamic length string.
Definition: strings.h:420
#define ast_mutex_lock(a)
Definition: lock.h:155
static char * tz
Definition: cdr_pgsql.c:56
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:374
int value
Definition: syslog.c:39
#define ast_verb(level,...)
Definition: logger.h:243
void ast_config_destroy(struct ast_config *config)
Destroys a config.
Definition: config.c:1037
SQLSMALLINT type
void ast_cdr_getvar(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int recur, int raw)
Definition: cdr.c:264
Call Detail Record API.
int ast_str_set(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Set a dynamic string using variable arguments.
Definition: strings.h:874
#define AST_RWLIST_RDLOCK(head)
Read locks a list.
Definition: linkedlists.h:77
static char * table
Definition: cdr_pgsql.c:56
#define ast_debug(level,...)
Log a DEBUG message.
Definition: logger.h:236
static char * pgdbname
Definition: cdr_pgsql.c:56
int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
Register a CDR handling engine.
Definition: cdr.c:130
General Asterisk PBX channel definitions.
static char * pgpassword
Definition: cdr_pgsql.c:56
#define ast_config_load(filename, flags)
Load a config file.
Definition: config.h:170
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:63
#define AST_RWLIST_TRAVERSE
Definition: linkedlists.h:493
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: utils.h:663
struct timeval answer
Definition: cdr.h:102
Responsible for call detail data.
Definition: cdr.h:82
struct columns::@71 list
#define LOG_ERROR
Definition: logger.h:155
The descriptor of a dynamic string XXX storage will be optimized later if needed We use the ts field ...
Definition: strings.h:364
static int reload(void)
Definition: cdr_pgsql.c:646
struct sla_ringing_trunk * first
Definition: app_meetme.c:965
void ast_log(int level, const char *file, int line, const char *function, const char *fmt,...)
Used for sending a log message This is the standard logger function. Probably the only way you will i...
Definition: logger.c:1207
#define LOG_NOTICE
Definition: logger.h:133
#define LENGTHEN_BUF1(size)
Definition: cdr_pgsql.c:75
struct timeval start
Definition: cdr.h:100
static int maxsize
Definition: cdr_pgsql.c:58
static const char name[]
#define ast_free(a)
Definition: astmm.h:97
#define AST_RWLIST_REMOVE_HEAD
Definition: linkedlists.h:829
int ast_strftime(char *buf, size_t len, const char *format, const struct ast_tm *tm)
Special version of strftime(3) that handles fractions of a second. Takes the same arguments as strfti...
Definition: localtime.c:2351
Structure used to handle boolean flags.
Definition: utils.h:200
#define AST_RWLIST_ENTRY
Definition: linkedlists.h:414
static char * pghostname
Definition: cdr_pgsql.c:56
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:471
#define AST_RWLIST_INSERT_TAIL
Definition: linkedlists.h:726
static int load_module(void)
Definition: cdr_pgsql.c:637
struct timeval end
Definition: cdr.h:104
unsigned int notnull
Definition: cdr_pgsql.c:68
int len
Definition: cdr_pgsql.c:67
static int pgsql_log(struct ast_cdr *cdr)
Definition: cdr_pgsql.c:104
#define ast_calloc(a, b)
Definition: astmm.h:82
static int connected
Definition: cdr_pgsql.c:57
int64_t ast_tvdiff_us(struct timeval end, struct timeval start)
Computes the difference (in microseconds) between two struct timeval instances.
Definition: time.h:70
static int unload_module(void)
Definition: cdr_pgsql.c:348
#define CONFIG_STATUS_FILEINVALID
Definition: config.h:52
static int config_module(int reload)
Definition: cdr_pgsql.c:368
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:38
Asterisk module definitions.
#define DATE_FORMAT
Definition: cdr_pgsql.c:52
static void empty_columns(void)
Definition: cdr_pgsql.c:337
#define AST_MUTEX_DEFINE_STATIC(mutex)
Definition: lock.h:526
void ast_cdr_unregister(const char *name)
Unregister a CDR handling engine.
Definition: cdr.c:165
#define ASTERISK_FILE_VERSION(file, version)
Register/unregister a source code file with the core.
Definition: asterisk.h:180
static int maxsize2
Definition: cdr_pgsql.c:58
#define CONFIG_STATUS_FILEUNCHANGED
Definition: config.h:51
#define ast_mutex_unlock(a)
Definition: lock.h:156
#define LENGTHEN_BUF2(size)
Definition: cdr_pgsql.c:90