1 /*
2
3 Copyright (C) 2012-2019 Olaf Till <i7tiol@t-online.de>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; If not, see <http://www.gnu.org/licenses/>.
17
18 */
19
20 #include "pq_connection.h"
21 #include "command.h"
22 #include "error-helpers.h"
23
24 DEFINE_OV_TYPEID_FUNCTIONS_AND_DATA (octave_pq_connection, "PGconn", "PGconn")
25
pq_basetype_prefix(void)26 std::string &pq_basetype_prefix (void)
27 {
28 static std::string prefix ("pg_catalog.");
29 return prefix;
30 }
31
32 const int pq_bpl = pq_basetype_prefix ().size ();
33
map_str_cmp(const char * c1,const char * c2)34 static bool map_str_cmp (const char *c1, const char *c2)
35 {
36 if (strcmp (c1, c2) < 0)
37 return true;
38 else
39 return false;
40 }
41
map_string_cmp(const std::string & s1,const std::string & s2)42 static bool map_string_cmp (const std::string &s1, const std::string &s2)
43 {
44 if (s1.compare (s2) < 0)
45 return true;
46 else
47 return false;
48 }
49
octave_pq_connection_rep(std::string & arg)50 octave_pq_connection_rep::octave_pq_connection_rep (std::string &arg)
51 : conv_map (), name_conv_map (&map_str_cmp), conn (NULL)
52 {
53 BEGIN_INTERRUPT_IMMEDIATELY_IN_FOREIGN_CODE;
54 conn = PQconnectdb (arg.c_str ());
55 END_INTERRUPT_IMMEDIATELY_IN_FOREIGN_CODE;
56
57 if (! conn || PQstatus (conn) == CONNECTION_BAD)
58 {
59 if (conn)
60 {
61 _p_error ("%s", PQerrorMessage (conn));
62
63 PGconn *t_conn = conn;
64
65 conn = NULL;
66
67 BEGIN_INTERRUPT_IMMEDIATELY_IN_FOREIGN_CODE;
68 PQfinish (t_conn);
69 END_INTERRUPT_IMMEDIATELY_IN_FOREIGN_CODE;
70 }
71
72 _p_error ("PQ connection attempt failed");
73 }
74 else
75 {
76 // init name converter-map (kind of "bootstrapping")
77 for (int i = 0; i < OCT_PQ_NUM_CONVERTERS; i++)
78 {
79 name_conv_map[conv_ptrs[i]->name.c_str ()] = conv_ptrs[i];
80
81 // unqualified name, may be replaced later with user-defined type
82 name_conv_map[conv_ptrs[i]->name.c_str () + pq_bpl] = conv_ptrs[i];
83 }
84
85 if (octave_pq_fill_base_types () ||
86 octave_pq_get_composite_types () ||
87 octave_pq_get_enum_types ())
88 {
89 PGconn *t_conn = conn;
90
91 conn = NULL;
92
93 BEGIN_INTERRUPT_IMMEDIATELY_IN_FOREIGN_CODE;
94 PQfinish (t_conn);
95 END_INTERRUPT_IMMEDIATELY_IN_FOREIGN_CODE;
96
97 _p_error ("could not read types");
98 }
99 else
100 {
101 if (strcmp (PQparameterStatus (conn, "integer_datetimes"), "on"))
102 integer_datetimes = false;
103 else
104 integer_datetimes = true;
105 }
106 }
107 }
108
~octave_pq_connection_rep(void)109 octave_pq_connection_rep::~octave_pq_connection_rep (void)
110 {
111 if (conn)
112 {
113 BEGIN_INTERRUPT_IMMEDIATELY_IN_FOREIGN_CODE;
114 PQfinish (conn);
115 END_INTERRUPT_IMMEDIATELY_IN_FOREIGN_CODE;
116
117 octave_pq_delete_non_constant_types ();
118 }
119 }
120
octave_pq_close(void)121 void octave_pq_connection_rep::octave_pq_close (void)
122 {
123 if (conn)
124 {
125 PGconn *t_conn = conn;
126
127 conn = NULL;
128
129 BEGIN_INTERRUPT_IMMEDIATELY_IN_FOREIGN_CODE;
130 PQfinish (t_conn);
131 END_INTERRUPT_IMMEDIATELY_IN_FOREIGN_CODE;
132
133 octave_pq_delete_non_constant_types ();
134 }
135 else
136 // deliberately left the 'error' call here, since
137 // 'octave_pq_close()' is only called by 'pq_close' immediately
138 // before returning
139 error ("PGconn object not open");
140 }
141
octave_pq_delete_non_constant_types(void)142 void octave_pq_connection_rep::octave_pq_delete_non_constant_types (void)
143 {
144 // In the first map, allocated types are usually referenced twice
145 // (by oid and aoid). Yet we need no refcount as long as we go
146 // through the name map as the last, since there (the same) types
147 // are only referenced once.
148
149 std::vector<oct_pq_conv_map_t::iterator> t_it_v;
150
151 for (oct_pq_conv_map_t::iterator it = conv_map.begin ();
152 it != conv_map.end ();
153 it++)
154 if (it->second->is_not_constant)
155 t_it_v.push_back (it);
156
157 for (std::vector<oct_pq_conv_map_t::iterator>::iterator it = t_it_v.begin ();
158 it != t_it_v.end (); it++)
159 conv_map.erase (*it);
160
161 std::vector<oct_pq_name_conv_map_t::iterator> t_name_it_v;
162
163 for (oct_pq_name_conv_map_t::iterator it = name_conv_map.begin ();
164 it != name_conv_map.end ();
165 it++)
166 {
167 oct_pq_conv_t *conv = it->second;
168
169 if (conv->is_not_constant)
170 {
171 t_name_it_v.push_back (it);
172
173 delete conv;
174 }
175 }
176
177 for (std::vector<oct_pq_name_conv_map_t::iterator>::iterator it =
178 t_name_it_v.begin ();
179 it != t_name_it_v.end (); it++)
180 name_conv_map.erase (*it);
181 }
182
octave_pq_fill_base_types(void)183 int octave_pq_connection_rep::octave_pq_fill_base_types (void)
184 {
185 Cell p (0, 0), pt (0, 0), rt (3, 1);
186 rt(0) = octave_value ("oid");
187 rt(1) = octave_value ("name");
188 rt(2) = octave_value ("oid");
189
190 std::string cmd ("select oid, typname, typarray from pg_type where (typtype = 'b' AND typarray != 0) OR typname = 'record' OR typname = 'unknown';"),
191 caller ("octave_pq_fill_base_types");
192
193 command c (*this, cmd, p, pt, rt, caller);
194 if (! c.good ())
195 {
196 _p_error ("octave_pq_fill_base_types: could not read pg_type");
197 return 1;
198 }
199
200 octave_value res = c.process_single_result ();
201 if (! c.good ())
202 return 1;
203
204 Cell tpls;
205 bool err;
206 SET_ERR (tpls = res.scalar_map_value ().contents ("data").cell_value (),
207 err);
208 if (err)
209 {
210 _p_error
211 ("octave_pq_fill_base_types: could not convert result data to cell");
212 return 1;
213 }
214
215 // make a temporary map of server base types (cell row numbers) for searching
216 typedef std::map<std::string, int, bool (*) (const std::string &,
217 const std::string &)>
218 bt_map_t;
219 bt_map_t bt_map (&map_string_cmp);
220 for (int i = 0; i < tpls.rows (); i++)
221 {
222 SET_ERR (bt_map[tpls(i, 1).string_value ()] = i, err);
223 if (err)
224 break;
225 }
226 if (err)
227 {
228 _p_error ("octave_pq_fill_base_types: could not read returned result");
229 return 1;
230 }
231
232 for (int i = 0; i < OCT_PQ_NUM_CONVERTERS; i++)
233 {
234 bt_map_t::iterator bt_it;
235 if ((bt_it = bt_map.find (conv_ptrs[i]->name.c_str () + pq_bpl)) ==
236 bt_map.end ())
237 {
238 _p_error ("octave_pq_fill_base_types: type %s not found in pg_type",
239 conv_ptrs[i]->name.c_str () + pq_bpl);
240 return 1;
241 }
242 // fill in oid and aoid into static records of converters
243 SET_ERR (conv_ptrs[i]->oid = tpls(bt_it->second, 0).int_value (), err);
244 if (! err)
245 {
246 SET_ERR (conv_ptrs[i]->aoid = tpls(bt_it->second, 2).int_value (),
247 err);
248 }
249 if (err)
250 break;
251
252 // fill in map of converters over oid with oid and, if not zero,
253 // also with aoid
254 conv_map[conv_ptrs[i]->oid] = conv_ptrs[i];
255 if (conv_ptrs[i]->aoid != 0)
256 conv_map[conv_ptrs[i]->aoid] = conv_ptrs[i];
257 }
258 if (err)
259 {
260 _p_error ("octave_pq_fill_base_types: could not read returned result");
261 return 1;
262 }
263
264 return 0;
265 }
266
octave_pq_get_composite_types(void)267 int octave_pq_connection_rep::octave_pq_get_composite_types (void)
268 {
269 Cell p, pt, rt;
270
271 std::string cmd ("select pg_type.oid, pg_type.typname, pg_type.typarray, pg_namespace.nspname, pg_type_is_visible(pg_type.oid) as visible, array_agg(pg_attribute.atttypid), array_agg(pg_attribute.attnum) from (pg_type join pg_namespace on pg_type.typnamespace = pg_namespace.oid) join pg_attribute on pg_type.typrelid = pg_attribute.attrelid where pg_type.typtype = 'c' and pg_attribute.attnum > 0 group by pg_type.oid, pg_type.typname, pg_type.typarray, pg_type.typrelid, pg_namespace.nspname, visible;"),
272 caller ("octave_pq_get_composite_types");
273
274 command c (*this, cmd, p, pt, rt, caller);
275 if (! c.good ())
276 {
277 _p_error ("octave_pq_get_composite_types: could not read pg_type");
278 return 1;
279 }
280
281 octave_value res = c.process_single_result ();
282 if (! c.good ())
283 return 1;
284
285 Cell tpls;
286 bool err;
287 SET_ERR (tpls = res.scalar_map_value ().contents ("data").cell_value (),
288 err)
289 if (err)
290 {
291 _p_error ("octave_pq_get_composite_types: could not convert result data to cell");
292 return 1;
293 }
294
295 for (int i = 0; i < tpls.rows (); i++)
296 {
297 Oid oid;
298 SET_ERR (oid = tpls(i, 0).uint_value (), err);
299 Oid aoid;
300 if (! err)
301 {
302 SET_ERR (aoid = tpls(i, 2).uint_value (), err);
303 }
304 std::string name;
305 if (! err)
306 {
307 SET_ERR (name = tpls(i, 1).string_value (), err);
308 }
309 std::string nspace;
310 if (! err)
311 {
312 SET_ERR (nspace = tpls(i, 3).string_value (), err);
313 }
314 bool visible = false;
315 if (! err)
316 {
317 SET_ERR (visible = tpls(i, 4).bool_value (), err);
318 }
319 Cell r_el_oids;
320 if (! err)
321 {
322 SET_ERR (r_el_oids = tpls(i, 5).scalar_map_value ()
323 .contents ("data").cell_value (), err);
324 }
325 Cell r_el_pos;
326 if (! err)
327 {
328 SET_ERR (r_el_pos = tpls(i, 6).scalar_map_value ()
329 .contents ("data").cell_value (), err);
330 }
331 if (err)
332 {
333 _p_error ("octave_pq_get_composite_types: could not read returned result");
334 return 1;
335 }
336 octave_idx_type nel = r_el_oids.numel ();
337 if (nel != r_el_pos.numel ())
338 {
339 _p_error ("octave_pq_get_composite_types: internal error, inconsistent content of pg_attribute?");
340
341 return 1;
342 }
343 oct_pq_el_oids_t el_oids;
344 el_oids.resize (nel);
345 oct_pq_conv_cache_t conv_cache;
346 conv_cache.resize (nel);
347 for (octave_idx_type i = 0; i < nel; i++)
348 {
349 octave_idx_type pos;
350 // "column" number (attnum) is one-based, so subtract 1
351 SET_ERR (pos = r_el_pos(i).idx_type_value () - 1, err);
352 if (! err)
353 {
354 if (pos >= nel)
355 {
356 _p_error ("octave_pq_get_composite_types: internal error (?system catalog erroneous?): column position %i greater than ncols %i for type %s, namespace %s",
357 pos, nel, name.c_str (), nspace.c_str ());
358 return 1;
359 }
360
361 SET_ERR (el_oids[pos] = r_el_oids(i).uint_value (), err);
362
363 conv_cache[pos] = NULL;
364 }
365 if (err)
366 {
367 _p_error ("octave_pq_get_composite_types: could not fill in element oids.");
368
369 return 1;
370 }
371 }
372
373 // must be allocated and filled before creating the name map
374 // entry, to get a remaining location for the c-string used as
375 // key
376 oct_pq_conv_t *t_conv = new oct_pq_conv_t;
377 t_conv->oid = oid;
378 t_conv->aoid = aoid;
379 t_conv->el_oids = el_oids;
380 t_conv->conv_cache = conv_cache;
381 t_conv->is_composite = true;
382 t_conv->is_enum = false;
383 t_conv->is_not_constant = true;
384 t_conv->name = nspace.append (".").append (name);
385 t_conv->to_octave_str = NULL;
386 t_conv->to_octave_bin = NULL;
387 t_conv->from_octave_str = NULL;
388 t_conv->from_octave_bin = NULL;
389
390 oct_pq_conv_t *&by_oid = conv_map[oid],
391 *&by_name = name_conv_map[t_conv->name.c_str ()];
392 if (by_oid || by_name)
393 {
394 _p_error ("octave_pq_get_composite_types: internal error, key already in typemap (by_oid: %u/%li, by name: %s/%li)",
395 oid, by_oid, t_conv->name.c_str (), by_name);
396 if (! by_oid) conv_map.erase (oid);
397 if (! by_name) name_conv_map.erase (t_conv->name.c_str ());
398 delete t_conv;
399 return 1;
400 }
401
402 by_oid = by_name = t_conv;
403
404 oct_pq_conv_t *t_conv_v = NULL; // silence inadequate warning by
405 // initializing it here
406
407 if (visible)
408 {
409 t_conv_v = new oct_pq_conv_t (*t_conv);
410
411 t_conv_v->el_oids = el_oids;
412
413 t_conv_v->conv_cache = conv_cache;
414
415 t_conv_v->name = name;
416
417 name_conv_map[t_conv_v->name.c_str ()] = t_conv_v;;
418 }
419
420 if (aoid)
421 {
422 oct_pq_conv_t *&by_aoid = conv_map[aoid];
423 if (by_aoid)
424 {
425 _p_error ("octave_pq_get_composite_types: internal error, aoid key %u already in typemap", aoid);
426 conv_map.erase (oid);
427 name_conv_map.erase (t_conv->name.c_str ());
428 delete t_conv;
429 if (visible)
430 {
431 name_conv_map.erase (t_conv_v->name.c_str ());
432 delete t_conv_v;
433 }
434 return 1;
435 }
436
437 by_aoid = by_oid;
438 }
439
440 }
441
442 return 0;
443 }
444
octave_pq_get_enum_types(void)445 int octave_pq_connection_rep::octave_pq_get_enum_types (void)
446 {
447 Cell p, pt, rt;
448
449 std::string cmd ("select pg_type.oid, pg_type.typname, pg_type.typarray, pg_namespace.nspname, pg_type_is_visible(pg_type.oid) from pg_type join pg_namespace on pg_type.typnamespace = pg_namespace.oid where pg_type.typtype = 'e';"),
450 caller ("octave_pq_get_enum_types");
451
452 command c (*this, cmd, p, pt, rt, caller);
453 if (! c.good ())
454 {
455 _p_error ("octave_pq_get_enum_types: could not read pg_type");
456 return 1;
457 }
458
459 octave_value res = c.process_single_result ();
460 if (! c.good ())
461 return 1;
462
463 Cell tpls;
464 bool err;
465 SET_ERR (tpls = res.scalar_map_value ().contents ("data").cell_value (),
466 err);
467 if (err)
468 {
469 _p_error ("octave_pq_get_enum_types: could not convert result data to cell");
470 return 1;
471 }
472
473 for (int i = 0; i < tpls.rows (); i++)
474 {
475 Oid oid;
476 SET_ERR (oid = tpls(i, 0).uint_value (), err);
477 Oid aoid;
478 if (! err)
479 {
480 SET_ERR (aoid = tpls(i, 2).uint_value (), err);
481 }
482 std::string name;
483 if (! err)
484 {
485 SET_ERR (name = tpls(i, 1).string_value (), err);
486 }
487 std::string nspace;
488 if (! err)
489 {
490 SET_ERR (nspace = tpls(i, 3).string_value (), err);
491 }
492 bool visible;
493 if (! err)
494 {
495 SET_ERR (visible = tpls(i, 4).bool_value (), err);
496 }
497 if (err)
498 {
499 _p_error ("octave_pq_get_enum_types: could not read returned result");
500 return 1;
501 }
502
503 // must be allocated and filled before creating the name map
504 // entry, to get a remaining location for the c-string used as
505 // key
506 oct_pq_conv_t *t_conv = new oct_pq_conv_t;
507 t_conv->oid = oid;
508 t_conv->aoid = aoid;
509 t_conv->el_oids = oct_pq_el_oids_t ();
510 t_conv->conv_cache = oct_pq_conv_cache_t ();
511 t_conv->is_composite = false;
512 t_conv->is_enum = true;
513 t_conv->is_not_constant = true;
514 t_conv->name = nspace.append (".").append (name);
515 t_conv->to_octave_str = &to_octave_str_text;
516 t_conv->to_octave_bin = &to_octave_bin_text;
517 t_conv->from_octave_str = &from_octave_str_text;
518 t_conv->from_octave_bin = &from_octave_bin_text;
519
520 // we trust there is always an array type in the table
521 oct_pq_conv_t *&by_oid = conv_map[oid], *&by_aoid = conv_map[aoid],
522 *&by_name = name_conv_map[t_conv->name.c_str ()];
523 if (by_oid || by_aoid || by_name)
524 {
525 _p_error ("octave_pq_get_enum_types: internal error, key already in typemap");
526 if (! by_oid) conv_map.erase (oid);
527 if (! by_aoid) conv_map.erase (aoid);
528 if (! by_name) name_conv_map.erase (t_conv->name.c_str ());
529 delete t_conv;
530 return 1;
531 }
532
533 by_oid = by_aoid = by_name = t_conv;
534
535 if (visible)
536 {
537 oct_pq_conv_t *t_conv_v = new oct_pq_conv_t (*t_conv);
538
539 t_conv_v->el_oids = oct_pq_el_oids_t ();
540
541 t_conv_v->conv_cache = oct_pq_conv_cache_t ();
542
543 t_conv_v->name = name;
544
545 name_conv_map[t_conv_v->name.c_str ()] = t_conv_v;
546 }
547 }
548
549 return 0;
550 }
551
octave_pq_refresh_types(void)552 int octave_pq_connection_rep::octave_pq_refresh_types (void)
553 {
554 octave_pq_delete_non_constant_types ();
555
556 // refresh unqualified base type names, may be replaced later with
557 // user-defined types
558 for (int i = 0; i < OCT_PQ_NUM_CONVERTERS; i++)
559 name_conv_map[conv_ptrs[i]->name.c_str () + pq_bpl] = conv_ptrs[i];
560
561 if (octave_pq_get_composite_types () || octave_pq_get_enum_types ())
562 {
563 PGconn *t_conn = conn;
564
565 conn = NULL;
566
567 if (t_conn)
568 {
569 BEGIN_INTERRUPT_IMMEDIATELY_IN_FOREIGN_CODE;
570 PQfinish (t_conn);
571 END_INTERRUPT_IMMEDIATELY_IN_FOREIGN_CODE;
572 }
573
574 _p_error ("octave_pq_refresh_types: could not read types");
575 return 1;
576 }
577 else
578 return 0;
579 }
580