1 /**********************************************************************
2  *
3  * PostGIS - Spatial Types for PostgreSQL
4  * http://postgis.net
5  *
6  * Copyright (C) 2015 Sandro Santilli <strk@kbt.io>
7  *
8  * This is free software; you can redistribute and/or modify it under
9  * the terms of the GNU General Public Licence. See the COPYING file.
10  *
11  **********************************************************************/
12 
13 #include "postgres.h"
14 #include "fmgr.h"
15 #include "c.h" /* for UINT64_FORMAT and uint64 */
16 #include "utils/builtins.h"
17 #include "utils/elog.h"
18 #include "utils/memutils.h" /* for TopMemoryContext */
19 #include "utils/array.h" /* for ArrayType */
20 #include "catalog/pg_type.h" /* for INT4OID */
21 #include "lib/stringinfo.h"
22 #include "access/xact.h" /* for RegisterXactCallback */
23 #include "funcapi.h" /* for FuncCallContext */
24 #include "executor/spi.h" /* this is what you need to work with SPI */
25 #include "inttypes.h" /* for PRId64 */
26 
27 #include "../postgis_config.h"
28 
29 #include "liblwgeom_internal.h" /* for gbox_clone */
30 #include "liblwgeom_topo.h"
31 
32 /*#define POSTGIS_DEBUG_LEVEL 1*/
33 #include "lwgeom_log.h"
34 #include "lwgeom_pg.h"
35 
36 #include <stdarg.h>
37 
38 #ifndef __GNUC__
39 # define __attribute__ (x)
40 #endif
41 
42 #define ABS(x) (x<0?-x:x)
43 
44 #ifdef WIN32
45 # define LWTFMT_ELEMID "lld"
46 #else
47 # define LWTFMT_ELEMID PRId64
48 #endif
49 
50 /*
51  * This is required for builds against pgsql
52  */
53 PG_MODULE_MAGIC;
54 
55 LWT_BE_IFACE* be_iface;
56 
57 /*
58  * Private data we'll use for this backend
59  */
60 #define MAXERRLEN 256
61 struct LWT_BE_DATA_T
62 {
63   char lastErrorMsg[MAXERRLEN];
64   /*
65    * This flag will need to be set to false
66    * at top-level function enter and set true
67    * whenever an callback changes the data
68    * in the database.
69    * It will be used by SPI_execute calls to
70    * make sure to see any data change occurring
71    * doring operations.
72    */
73   bool data_changed;
74 
75   int topoLoadFailMessageFlavor; /* 0:sql, 1:AddPoint */
76 };
77 
78 LWT_BE_DATA be_data;
79 
80 struct LWT_BE_TOPOLOGY_T
81 {
82   LWT_BE_DATA* be_data;
83   char *name;
84   int id;
85   int srid;
86   double precision;
87   int hasZ;
88   Oid geometryOID;
89 };
90 
91 /* utility funx */
92 
93 static void cberror(const LWT_BE_DATA* be, const char *fmt, ...)
94 __attribute__ (( format(printf, 2, 3) ));
95 
96 static void
cberror(const LWT_BE_DATA * be_in,const char * fmt,...)97 cberror(const LWT_BE_DATA* be_in, const char *fmt, ...)
98 {
99   LWT_BE_DATA *be = (LWT_BE_DATA*)be_in;/*const cast*/
100   va_list ap;
101 
102   va_start(ap, fmt);
103 
104   vsnprintf (be->lastErrorMsg, MAXERRLEN, fmt, ap);
105   be->lastErrorMsg[MAXERRLEN-1]='\0';
106 
107   va_end(ap);
108 }
109 
110 static void
_lwtype_upper_name(int type,char * buf,size_t buflen)111 _lwtype_upper_name(int type, char *buf, size_t buflen)
112 {
113   char *ptr;
114   snprintf(buf, buflen, "%s", lwtype_name(type));
115   buf[buflen-1] = '\0';
116   ptr = buf;
117   while (*ptr)
118   {
119     *ptr = toupper(*ptr);
120     ++ptr;
121   }
122 }
123 
124 /* Return an lwalloc'ed geometrical representation of the box */
125 static LWGEOM *
_box2d_to_lwgeom(const GBOX * bbox,int srid)126 _box2d_to_lwgeom(const GBOX *bbox, int srid)
127 {
128   POINTARRAY *pa = ptarray_construct(0, 0, 2);
129   POINT4D p;
130   LWLINE *line;
131 
132   p.x = bbox->xmin;
133   p.y = bbox->ymin;
134   ptarray_set_point4d(pa, 0, &p);
135   p.x = bbox->xmax;
136   p.y = bbox->ymax;
137   ptarray_set_point4d(pa, 1, &p);
138   line = lwline_construct(srid, NULL, pa);
139   return lwline_as_lwgeom(line);
140 }
141 
142 /* Return lwalloc'ed hexwkb representation for a GBOX */
143 static char *
_box2d_to_hexwkb(const GBOX * bbox,int srid)144 _box2d_to_hexwkb(const GBOX *bbox, int srid)
145 {
146   char *hex;
147   size_t sz;
148   LWGEOM *geom = _box2d_to_lwgeom(bbox, srid);
149   hex = lwgeom_to_hexwkb(geom, WKT_EXTENDED, &sz);
150   lwgeom_free(geom);
151   assert(hex[sz-1] == '\0');
152   return hex;
153 }
154 
155 /* Backend callbacks */
156 
157 static const char*
cb_lastErrorMessage(const LWT_BE_DATA * be)158 cb_lastErrorMessage(const LWT_BE_DATA* be)
159 {
160   return be->lastErrorMsg;
161 }
162 
163 static LWT_BE_TOPOLOGY*
cb_loadTopologyByName(const LWT_BE_DATA * be,const char * name)164 cb_loadTopologyByName(const LWT_BE_DATA* be, const char *name)
165 {
166   int spi_result;
167   const char *sql;
168   Datum dat;
169   bool isnull;
170   LWT_BE_TOPOLOGY *topo;
171   MemoryContext oldcontext = CurrentMemoryContext;
172   Datum values[1];
173   Oid argtypes[1];
174   static SPIPlanPtr plan = NULL;
175 
176   argtypes[0] = CSTRINGOID;
177   sql =
178     "SELECT id,srid,precision,null::geometry "
179     "FROM topology.topology WHERE name = $1::varchar";
180   if ( ! plan ) /* prepare on first call */
181   {
182     plan = SPI_prepare(sql, 1, argtypes);
183     if ( ! plan )
184     {
185       cberror(be, "unexpected return (%d) from query preparation: %s",
186               SPI_result, sql);
187       return NULL;
188     }
189     SPI_keepplan(plan);
190     /* SPI_freeplan to free, eventually */
191   }
192 
193   /* execute */
194   values[0] = CStringGetDatum(name);
195   spi_result = SPI_execute_plan(plan, values, NULL, !be->data_changed, 1);
196   MemoryContextSwitchTo( oldcontext ); /* switch back */
197   if ( spi_result != SPI_OK_SELECT )
198   {
199     cberror(be, "unexpected return (%d) from query execution: %s", spi_result, sql);
200     return NULL;
201   }
202   if ( ! SPI_processed )
203   {
204     if ( be->topoLoadFailMessageFlavor == 1 )
205     {
206       cberror(be, "No topology with name \"%s\" in topology.topology", name);
207     }
208     else
209     {
210       cberror(be, "SQL/MM Spatial exception - invalid topology name");
211     }
212     return NULL;
213   }
214   if ( SPI_processed > 1 )
215   {
216     cberror(be, "multiple topologies named '%s' were found", name);
217     return NULL;
218   }
219 
220   topo = palloc(sizeof(LWT_BE_TOPOLOGY));
221   topo->be_data = (LWT_BE_DATA *)be; /* const cast.. */
222   topo->name = pstrdup(name);
223 
224   dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
225   if ( isnull )
226   {
227     cberror(be, "Topology '%s' has null identifier", name);
228     SPI_freetuptable(SPI_tuptable);
229     return NULL;
230   }
231   topo->id = DatumGetInt32(dat);
232 
233   dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 2, &isnull);
234   if ( isnull )
235   {
236     cberror(be, "Topology '%s' has null SRID", name);
237     SPI_freetuptable(SPI_tuptable);
238     return NULL;
239   }
240   topo->srid = DatumGetInt32(dat);
241   if ( topo->srid < 0 )
242   {
243     lwnotice("Topology SRID value %d converted to "
244              "the officially unknown SRID value %d", topo->srid, SRID_UNKNOWN);
245     topo->srid = SRID_UNKNOWN;
246   }
247 
248   dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 3, &isnull);
249   if ( isnull )
250   {
251     lwnotice("Topology '%s' has null precision, taking as 0", name);
252     topo->precision = 0; /* TODO: should this be -1 instead ? */
253   }
254   else
255   {
256     topo->precision = DatumGetFloat8(dat);
257   }
258 
259   /* we're dynamically querying geometry type here */
260 #if POSTGIS_PGSQL_VERSION < 110
261   topo->geometryOID = SPI_tuptable->tupdesc->attrs[3]->atttypid;
262 #else
263   topo->geometryOID = SPI_tuptable->tupdesc->attrs[3].atttypid;
264 #endif
265 
266   POSTGIS_DEBUGF(1, "cb_loadTopologyByName: topo '%s' has "
267                  "id %d, srid %d, precision %g",
268                  name, topo->id, topo->srid, topo->precision);
269 
270   SPI_freetuptable(SPI_tuptable);
271 
272   return topo;
273 }
274 
275 static int
cb_topoGetSRID(const LWT_BE_TOPOLOGY * topo)276 cb_topoGetSRID(const LWT_BE_TOPOLOGY* topo)
277 {
278   return topo->srid;
279 }
280 
281 static int
cb_topoHasZ(const LWT_BE_TOPOLOGY * topo)282 cb_topoHasZ(const LWT_BE_TOPOLOGY* topo)
283 {
284   return topo->hasZ;
285 }
286 
287 static double
cb_topoGetPrecision(const LWT_BE_TOPOLOGY * topo)288 cb_topoGetPrecision(const LWT_BE_TOPOLOGY* topo)
289 {
290   return topo->precision;
291 }
292 
293 static int
cb_freeTopology(LWT_BE_TOPOLOGY * topo)294 cb_freeTopology(LWT_BE_TOPOLOGY* topo)
295 {
296   pfree(topo->name);
297   pfree(topo);
298   return 1;
299 }
300 
301 static void
addEdgeFields(StringInfo str,int fields,int fullEdgeData)302 addEdgeFields(StringInfo str, int fields, int fullEdgeData)
303 {
304   const char *sep = "";
305 
306   if ( fields & LWT_COL_EDGE_EDGE_ID )
307   {
308     appendStringInfoString(str, "edge_id");
309     sep = ",";
310   }
311   if ( fields & LWT_COL_EDGE_START_NODE )
312   {
313     appendStringInfo(str, "%sstart_node", sep);
314     sep = ",";
315   }
316   if ( fields & LWT_COL_EDGE_END_NODE )
317   {
318     appendStringInfo(str, "%send_node", sep);
319     sep = ",";
320   }
321   if ( fields & LWT_COL_EDGE_FACE_LEFT )
322   {
323     appendStringInfo(str, "%sleft_face", sep);
324     sep = ",";
325   }
326   if ( fields & LWT_COL_EDGE_FACE_RIGHT )
327   {
328     appendStringInfo(str, "%sright_face", sep);
329     sep = ",";
330   }
331   if ( fields & LWT_COL_EDGE_NEXT_LEFT )
332   {
333     appendStringInfo(str, "%snext_left_edge", sep);
334     if ( fullEdgeData ) appendStringInfoString(str, ", abs_next_left_edge");
335     sep = ",";
336   }
337   if ( fields & LWT_COL_EDGE_NEXT_RIGHT )
338   {
339     appendStringInfo(str, "%snext_right_edge", sep);
340     if ( fullEdgeData ) appendStringInfoString(str, ", abs_next_right_edge");
341     sep = ",";
342   }
343   if ( fields & LWT_COL_EDGE_GEOM )
344   {
345     appendStringInfo(str, "%sgeom", sep);
346   }
347 }
348 
349 /* Add edge values in text form, include the parens */
350 static void
addEdgeValues(StringInfo str,const LWT_ISO_EDGE * edge,int fields,int fullEdgeData)351 addEdgeValues(StringInfo str, const LWT_ISO_EDGE *edge, int fields, int fullEdgeData)
352 {
353   size_t hexewkb_size;
354   char *hexewkb;
355   const char *sep = "";
356 
357   appendStringInfoChar(str, '(');
358   if ( fields & LWT_COL_EDGE_EDGE_ID )
359   {
360     if ( edge->edge_id != -1 )
361       appendStringInfo(str, "%" LWTFMT_ELEMID, edge->edge_id);
362     else
363       appendStringInfoString(str, "DEFAULT");
364     sep = ",";
365   }
366   if ( fields & LWT_COL_EDGE_START_NODE )
367   {
368     appendStringInfo(str, "%s%" LWTFMT_ELEMID, sep, edge->start_node);
369     sep = ",";
370   }
371   if ( fields & LWT_COL_EDGE_END_NODE )
372   {
373     appendStringInfo(str, "%s%" LWTFMT_ELEMID, sep, edge->end_node);
374     sep = ",";
375   }
376   if ( fields & LWT_COL_EDGE_FACE_LEFT )
377   {
378     appendStringInfo(str, "%s%" LWTFMT_ELEMID, sep, edge->face_left);
379     sep = ",";
380   }
381   if ( fields & LWT_COL_EDGE_FACE_RIGHT )
382   {
383     appendStringInfo(str, "%s%" LWTFMT_ELEMID, sep, edge->face_right);
384     sep = ",";
385   }
386   if ( fields & LWT_COL_EDGE_NEXT_LEFT )
387   {
388     appendStringInfo(str, "%s%" LWTFMT_ELEMID, sep, edge->next_left);
389     if ( fullEdgeData )
390       appendStringInfo(str, ",%" LWTFMT_ELEMID, ABS(edge->next_left));
391     sep = ",";
392   }
393   if ( fields & LWT_COL_EDGE_NEXT_RIGHT )
394   {
395     appendStringInfo(str, "%s%" LWTFMT_ELEMID, sep, edge->next_right);
396     if ( fullEdgeData )
397       appendStringInfo(str, ",%" LWTFMT_ELEMID, ABS(edge->next_right));
398     sep = ",";
399   }
400   if ( fields & LWT_COL_EDGE_GEOM )
401   {
402     if ( edge->geom )
403     {
404       hexewkb = lwgeom_to_hexwkb(lwline_as_lwgeom(edge->geom),
405                                  WKB_EXTENDED, &hexewkb_size);
406       appendStringInfo(str, "%s'%s'::geometry", sep, hexewkb);
407       lwfree(hexewkb);
408     }
409     else
410     {
411       appendStringInfo(str, "%snull", sep);
412     }
413   }
414   appendStringInfoChar(str, ')');
415 }
416 
417 enum UpdateType
418 {
419   updSet,
420   updSel,
421   updNot
422 };
423 
424 static void
addEdgeUpdate(StringInfo str,const LWT_ISO_EDGE * edge,int fields,int fullEdgeData,enum UpdateType updType)425 addEdgeUpdate(StringInfo str, const LWT_ISO_EDGE* edge, int fields,
426               int fullEdgeData, enum UpdateType updType)
427 {
428   const char *sep = "";
429   const char *sep1;
430   const char *op;
431   size_t hexewkb_size;
432   char *hexewkb;
433 
434   switch (updType)
435   {
436   case updSet:
437     op = "=";
438     sep1 = ",";
439     break;
440   case updSel:
441     op = "=";
442     sep1 = " AND ";
443     break;
444   case updNot:
445   default:
446     op = "!=";
447     sep1 = " AND ";
448     break;
449   }
450 
451   if ( fields & LWT_COL_EDGE_EDGE_ID )
452   {
453     appendStringInfoString(str, "edge_id ");
454     appendStringInfo(str, "%s %" LWTFMT_ELEMID, op, edge->edge_id);
455     sep = sep1;
456   }
457   if ( fields & LWT_COL_EDGE_START_NODE )
458   {
459     appendStringInfo(str, "%sstart_node ", sep);
460     appendStringInfo(str, "%s %" LWTFMT_ELEMID, op, edge->start_node);
461     sep = sep1;
462   }
463   if ( fields & LWT_COL_EDGE_END_NODE )
464   {
465     appendStringInfo(str, "%send_node", sep);
466     appendStringInfo(str, "%s %" LWTFMT_ELEMID, op, edge->end_node);
467     sep = sep1;
468   }
469   if ( fields & LWT_COL_EDGE_FACE_LEFT )
470   {
471     appendStringInfo(str, "%sleft_face", sep);
472     appendStringInfo(str, "%s %" LWTFMT_ELEMID, op, edge->face_left);
473     sep = sep1;
474   }
475   if ( fields & LWT_COL_EDGE_FACE_RIGHT )
476   {
477     appendStringInfo(str, "%sright_face", sep);
478     appendStringInfo(str, "%s %" LWTFMT_ELEMID, op, edge->face_right);
479     sep = sep1;
480   }
481   if ( fields & LWT_COL_EDGE_NEXT_LEFT )
482   {
483     appendStringInfo(str, "%snext_left_edge", sep);
484     appendStringInfo(str, "%s %" LWTFMT_ELEMID, op, edge->next_left);
485     sep = sep1;
486     if ( fullEdgeData )
487     {
488       appendStringInfo(str, "%s abs_next_left_edge", sep);
489       appendStringInfo(str, "%s %" LWTFMT_ELEMID, op, ABS(edge->next_left));
490     }
491   }
492   if ( fields & LWT_COL_EDGE_NEXT_RIGHT )
493   {
494     appendStringInfo(str, "%snext_right_edge", sep);
495     appendStringInfo(str, "%s %" LWTFMT_ELEMID, op, edge->next_right);
496     sep = sep1;
497     if ( fullEdgeData )
498     {
499       appendStringInfo(str, "%s abs_next_right_edge", sep);
500       appendStringInfo(str, "%s %" LWTFMT_ELEMID, op, ABS(edge->next_right));
501     }
502   }
503   if ( fields & LWT_COL_EDGE_GEOM )
504   {
505     appendStringInfo(str, "%sgeom", sep);
506     hexewkb = lwgeom_to_hexwkb(lwline_as_lwgeom(edge->geom),
507                                WKB_EXTENDED, &hexewkb_size);
508     appendStringInfo(str, "%s'%s'::geometry", op, hexewkb);
509     lwfree(hexewkb);
510   }
511 }
512 
513 static void
addNodeUpdate(StringInfo str,const LWT_ISO_NODE * node,int fields,int fullNodeData,enum UpdateType updType)514 addNodeUpdate(StringInfo str, const LWT_ISO_NODE* node, int fields,
515               int fullNodeData, enum UpdateType updType)
516 {
517   const char *sep = "";
518   const char *sep1;
519   const char *op;
520   size_t hexewkb_size;
521   char *hexewkb;
522 
523   switch (updType)
524   {
525   case updSet:
526     op = "=";
527     sep1 = ",";
528     break;
529   case updSel:
530     op = "=";
531     sep1 = " AND ";
532     break;
533   case updNot:
534   default:
535     op = "!=";
536     sep1 = " AND ";
537     break;
538   }
539 
540   if ( fields & LWT_COL_NODE_NODE_ID )
541   {
542     appendStringInfoString(str, "node_id ");
543     appendStringInfo(str, "%s %" LWTFMT_ELEMID, op, node->node_id);
544     sep = sep1;
545   }
546   if ( fields & LWT_COL_NODE_CONTAINING_FACE )
547   {
548     appendStringInfo(str, "%scontaining_face %s", sep, op);
549     if ( node->containing_face != -1 )
550     {
551       appendStringInfo(str, "%" LWTFMT_ELEMID, node->containing_face);
552     }
553     else
554     {
555       appendStringInfoString(str, "null::int");
556     }
557     sep = sep1;
558   }
559   if ( fields & LWT_COL_NODE_GEOM )
560   {
561     appendStringInfo(str, "%sgeom", sep);
562     hexewkb = lwgeom_to_hexwkb(lwpoint_as_lwgeom(node->geom),
563                                WKB_EXTENDED, &hexewkb_size);
564     appendStringInfo(str, "%s'%s'::geometry", op, hexewkb);
565     lwfree(hexewkb);
566   }
567 }
568 
569 static void
addNodeFields(StringInfo str,int fields)570 addNodeFields(StringInfo str, int fields)
571 {
572   const char *sep = "";
573 
574   if ( fields & LWT_COL_NODE_NODE_ID )
575   {
576     appendStringInfoString(str, "node_id");
577     sep = ",";
578   }
579   if ( fields & LWT_COL_NODE_CONTAINING_FACE )
580   {
581     appendStringInfo(str, "%scontaining_face", sep);
582     sep = ",";
583   }
584   if ( fields & LWT_COL_NODE_GEOM )
585   {
586     appendStringInfo(str, "%sgeom", sep);
587   }
588 }
589 
590 static void
addFaceFields(StringInfo str,int fields)591 addFaceFields(StringInfo str, int fields)
592 {
593   const char *sep = "";
594 
595   if ( fields & LWT_COL_FACE_FACE_ID )
596   {
597     appendStringInfoString(str, "face_id");
598     sep = ",";
599   }
600   if ( fields & LWT_COL_FACE_MBR )
601   {
602     appendStringInfo(str, "%smbr", sep);
603     sep = ",";
604   }
605 }
606 
607 /* Add node values for an insert, in text form */
608 static void
addNodeValues(StringInfo str,const LWT_ISO_NODE * node,int fields)609 addNodeValues(StringInfo str, const LWT_ISO_NODE *node, int fields)
610 {
611   size_t hexewkb_size;
612   char *hexewkb;
613   const char *sep = "";
614 
615   appendStringInfoChar(str, '(');
616 
617   if ( fields & LWT_COL_NODE_NODE_ID )
618   {
619     if ( node->node_id != -1 )
620       appendStringInfo(str, "%" LWTFMT_ELEMID, node->node_id);
621     else
622       appendStringInfoString(str, "DEFAULT");
623     sep = ",";
624   }
625 
626   if ( fields & LWT_COL_NODE_CONTAINING_FACE )
627   {
628     if ( node->containing_face != -1 )
629       appendStringInfo(str, "%s%" LWTFMT_ELEMID, sep, node->containing_face);
630     else appendStringInfo(str, "%snull::int", sep);
631   }
632 
633   if ( fields & LWT_COL_NODE_GEOM )
634   {
635     if ( node->geom )
636     {
637       hexewkb = lwgeom_to_hexwkb(lwpoint_as_lwgeom(node->geom),
638                                  WKB_EXTENDED, &hexewkb_size);
639       appendStringInfo(str, "%s'%s'::geometry", sep, hexewkb);
640       lwfree(hexewkb);
641     }
642     else
643     {
644       appendStringInfo(str, "%snull::geometry", sep);
645     }
646   }
647 
648   appendStringInfoChar(str, ')');
649 }
650 
651 /* Add face values for an insert, in text form */
652 static void
addFaceValues(StringInfo str,LWT_ISO_FACE * face,int srid)653 addFaceValues(StringInfo str, LWT_ISO_FACE *face, int srid)
654 {
655   if ( face->face_id != -1 )
656     appendStringInfo(str, "(%" LWTFMT_ELEMID, face->face_id);
657   else
658     appendStringInfoString(str, "(DEFAULT");
659 
660   if ( face->mbr )
661   {
662     {
663       char *hexbox;
664       hexbox = _box2d_to_hexwkb(face->mbr, srid);
665       appendStringInfo(str, ",ST_Envelope('%s'::geometry))", hexbox);
666       lwfree(hexbox);
667     }
668   }
669   else
670   {
671     appendStringInfoString(str, ",null::geometry)");
672   }
673 }
674 
675 static void
fillEdgeFields(LWT_ISO_EDGE * edge,HeapTuple row,TupleDesc rowdesc,int fields)676 fillEdgeFields(LWT_ISO_EDGE* edge, HeapTuple row, TupleDesc rowdesc, int fields)
677 {
678   bool isnull;
679   Datum dat;
680   int val;
681   GSERIALIZED *geom;
682   LWGEOM *lwg;
683   int colno = 0;
684 
685   POSTGIS_DEBUGF(2, "fillEdgeFields: got %d atts and fields %x",
686                  rowdesc->natts, fields);
687 
688   if ( fields & LWT_COL_EDGE_EDGE_ID )
689   {
690     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
691     if ( isnull )
692     {
693       lwpgwarning("Found edge with NULL edge_id");
694       edge->edge_id = -1;
695     }
696     else
697     {
698       val = DatumGetInt32(dat);
699       POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (edge_id)"
700                      " has int32 val of %d",
701                      colno, val);
702       edge->edge_id = val;
703     }
704 
705   }
706   if ( fields & LWT_COL_EDGE_START_NODE )
707   {
708     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
709     if ( isnull )
710     {
711       lwpgwarning("Found edge with NULL start_node");
712       edge->start_node = -1;
713     }
714     else
715     {
716       val = DatumGetInt32(dat);
717       POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (start_node)"
718                      " has int32 val of %d", colno, val);
719       edge->start_node = val;
720     }
721   }
722   if ( fields & LWT_COL_EDGE_END_NODE )
723   {
724     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
725     if ( isnull )
726     {
727       lwpgwarning("Found edge with NULL end_node");
728       edge->end_node = -1;
729     }
730     else
731     {
732       val = DatumGetInt32(dat);
733       POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (end_node)"
734                      " has int32 val of %d", colno, val);
735       edge->end_node = val;
736     }
737   }
738   if ( fields & LWT_COL_EDGE_FACE_LEFT )
739   {
740     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
741     if ( isnull )
742     {
743       lwpgwarning("Found edge with NULL face_left");
744       edge->face_left = -1;
745     }
746     else
747     {
748       val = DatumGetInt32(dat);
749       POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (face_left)"
750                      " has int32 val of %d", colno, val);
751       edge->face_left = val;
752     }
753   }
754   if ( fields & LWT_COL_EDGE_FACE_RIGHT )
755   {
756     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
757     if ( isnull )
758     {
759       lwpgwarning("Found edge with NULL face_right");
760       edge->face_right = -1;
761     }
762     else
763     {
764       val = DatumGetInt32(dat);
765       POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (face_right)"
766                      " has int32 val of %d", colno, val);
767       edge->face_right = val;
768     }
769   }
770   if ( fields & LWT_COL_EDGE_NEXT_LEFT )
771   {
772     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
773     if ( isnull )
774     {
775       lwpgwarning("Found edge with NULL next_left");
776       edge->next_left = -1;
777     }
778     else
779     {
780       val = DatumGetInt32(dat);
781       POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (next_left)"
782                      " has int32 val of %d", colno, val);
783       edge->next_left = val;
784     }
785   }
786   if ( fields & LWT_COL_EDGE_NEXT_RIGHT )
787   {
788     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
789     if ( isnull )
790     {
791       lwpgwarning("Found edge with NULL next_right");
792       edge->next_right = -1;
793     }
794     else
795     {
796       val = DatumGetInt32(dat);
797       POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (next_right)"
798                      " has int32 val of %d", colno, val);
799       edge->next_right = val;
800     }
801   }
802   if ( fields & LWT_COL_EDGE_GEOM )
803   {
804     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
805     if ( ! isnull )
806     {
807       {
808         MemoryContext oldcontext = CurrentMemoryContext;
809         geom = (GSERIALIZED *)PG_DETOAST_DATUM(dat);
810         lwg = lwgeom_from_gserialized(geom);
811         MemoryContextSwitchTo( TopMemoryContext );
812         edge->geom = lwgeom_as_lwline(lwgeom_clone_deep(lwg));
813         MemoryContextSwitchTo( oldcontext ); /* switch back */
814         lwgeom_free(lwg);
815         if ( DatumGetPointer(dat) != (Pointer)geom ) pfree(geom); /* IF_COPY */
816       }
817     }
818     else
819     {
820       lwpgwarning("Found edge with NULL geometry !");
821       edge->geom = NULL;
822     }
823   }
824 }
825 
826 static void
fillNodeFields(LWT_ISO_NODE * node,HeapTuple row,TupleDesc rowdesc,int fields)827 fillNodeFields(LWT_ISO_NODE* node, HeapTuple row, TupleDesc rowdesc, int fields)
828 {
829   bool isnull;
830   Datum dat;
831   GSERIALIZED *geom;
832   LWGEOM *lwg;
833   int colno = 0;
834 
835   if ( fields & LWT_COL_NODE_NODE_ID )
836   {
837     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
838     node->node_id = DatumGetInt32(dat);
839   }
840   if ( fields & LWT_COL_NODE_CONTAINING_FACE )
841   {
842     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
843     if ( isnull ) node->containing_face = -1;
844     else node->containing_face = DatumGetInt32(dat);
845   }
846   if ( fields & LWT_COL_NODE_GEOM )
847   {
848     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
849     if ( ! isnull )
850     {
851       geom = (GSERIALIZED *)PG_DETOAST_DATUM(dat);
852       lwg = lwgeom_from_gserialized(geom);
853       node->geom = lwgeom_as_lwpoint(lwgeom_clone_deep(lwg));
854       lwgeom_free(lwg);
855       if ( DatumGetPointer(dat) != (Pointer)geom ) pfree(geom); /* IF_COPY */
856     }
857     else
858     {
859       lwpgnotice("Found node with NULL geometry !");
860       node->geom = NULL;
861     }
862   }
863 }
864 
865 static void
fillFaceFields(LWT_ISO_FACE * face,HeapTuple row,TupleDesc rowdesc,int fields)866 fillFaceFields(LWT_ISO_FACE* face, HeapTuple row, TupleDesc rowdesc, int fields)
867 {
868   bool isnull;
869   Datum dat;
870   GSERIALIZED *geom;
871   LWGEOM *g;
872   const GBOX *box;
873   int colno = 0;
874 
875   if ( fields & LWT_COL_FACE_FACE_ID )
876   {
877     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
878     face->face_id = DatumGetInt32(dat);
879   }
880   if ( fields & LWT_COL_FACE_MBR )
881   {
882     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
883     if ( ! isnull )
884     {
885       /* NOTE: this is a geometry of which we want to take (and clone) the BBOX */
886       geom = (GSERIALIZED *)PG_DETOAST_DATUM(dat);
887       g = lwgeom_from_gserialized(geom);
888       box = lwgeom_get_bbox(g);
889       if ( box )
890       {
891         face->mbr = gbox_clone(box);
892       }
893       else
894       {
895         lwpgnotice("Found face with EMPTY MBR !");
896         face->mbr = NULL;
897       }
898       lwgeom_free(g);
899       if ( DatumGetPointer(dat) != (Pointer)geom ) pfree(geom);
900     }
901     else
902     {
903       /* NOTE: perfectly fine for universe face */
904       POSTGIS_DEBUG(1, "Found face with NULL MBR");
905       face->mbr = NULL;
906     }
907   }
908 }
909 
910 /* return 0 on failure (null) 1 otherwise */
911 static int
getNotNullInt32(HeapTuple row,TupleDesc desc,int col,int32 * val)912 getNotNullInt32( HeapTuple row, TupleDesc desc, int col, int32 *val )
913 {
914   bool isnull;
915   Datum dat = SPI_getbinval( row, desc, col, &isnull );
916   if ( isnull ) return 0;
917   *val = DatumGetInt32(dat);
918   return 1;
919 }
920 
921 /* ----------------- Callbacks start here ------------------------ */
922 
923 static LWT_ISO_EDGE*
cb_getEdgeById(const LWT_BE_TOPOLOGY * topo,const LWT_ELEMID * ids,int * numelems,int fields)924 cb_getEdgeById(const LWT_BE_TOPOLOGY* topo,
925                const LWT_ELEMID* ids, int* numelems, int fields)
926 {
927   LWT_ISO_EDGE *edges;
928   int spi_result;
929   MemoryContext oldcontext = CurrentMemoryContext;
930   StringInfoData sqldata;
931   StringInfo sql = &sqldata;
932   int i;
933 
934   initStringInfo(sql);
935   appendStringInfoString(sql, "SELECT ");
936   addEdgeFields(sql, fields, 0);
937   appendStringInfo(sql, " FROM \"%s\".edge_data", topo->name);
938   appendStringInfoString(sql, " WHERE edge_id IN (");
939   // add all identifiers here
940   for (i=0; i<*numelems; ++i)
941   {
942     appendStringInfo(sql, "%s%" LWTFMT_ELEMID, (i?",":""), ids[i]);
943   }
944   appendStringInfoString(sql, ")");
945   POSTGIS_DEBUGF(1, "cb_getEdgeById query: %s", sql->data);
946 
947   spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, *numelems);
948   MemoryContextSwitchTo( oldcontext ); /* switch back */
949   if ( spi_result != SPI_OK_SELECT )
950   {
951     cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
952     pfree(sqldata.data);
953     *numelems = -1;
954     return NULL;
955   }
956   pfree(sqldata.data);
957 
958   POSTGIS_DEBUGF(1, "cb_getEdgeById: edge query returned %d rows", SPI_processed);
959   *numelems = SPI_processed;
960   if ( ! SPI_processed )
961   {
962     return NULL;
963   }
964 
965   edges = palloc( sizeof(LWT_ISO_EDGE) * *numelems );
966   for ( i=0; i<*numelems; ++i )
967   {
968     HeapTuple row = SPI_tuptable->vals[i];
969     fillEdgeFields(&edges[i], row, SPI_tuptable->tupdesc, fields);
970   }
971 
972   SPI_freetuptable(SPI_tuptable);
973 
974   return edges;
975 }
976 
977 static LWT_ISO_EDGE*
cb_getEdgeByNode(const LWT_BE_TOPOLOGY * topo,const LWT_ELEMID * ids,int * numelems,int fields)978 cb_getEdgeByNode(const LWT_BE_TOPOLOGY* topo,
979                  const LWT_ELEMID* ids, int* numelems, int fields)
980 {
981   LWT_ISO_EDGE *edges;
982   int spi_result;
983 
984   StringInfoData sqldata;
985   StringInfo sql = &sqldata;
986   int i;
987   MemoryContext oldcontext = CurrentMemoryContext;
988 
989   initStringInfo(sql);
990   appendStringInfoString(sql, "SELECT ");
991   addEdgeFields(sql, fields, 0);
992   appendStringInfo(sql, " FROM \"%s\".edge_data", topo->name);
993   appendStringInfoString(sql, " WHERE start_node IN (");
994   // add all identifiers here
995   for (i=0; i<*numelems; ++i)
996   {
997     appendStringInfo(sql, "%s%" LWTFMT_ELEMID, (i?",":""), ids[i]);
998   }
999   appendStringInfoString(sql, ") OR end_node IN (");
1000   // add all identifiers here
1001   for (i=0; i<*numelems; ++i)
1002   {
1003     appendStringInfo(sql, "%s%" LWTFMT_ELEMID, (i?",":""), ids[i]);
1004   }
1005   appendStringInfoString(sql, ")");
1006 
1007   POSTGIS_DEBUGF(1, "cb_getEdgeByNode query: %s", sql->data);
1008   POSTGIS_DEBUGF(1, "data_changed is %d", topo->be_data->data_changed);
1009 
1010   spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0);
1011   MemoryContextSwitchTo( oldcontext ); /* switch back */
1012   if ( spi_result != SPI_OK_SELECT )
1013   {
1014     cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
1015     pfree(sqldata.data);
1016     *numelems = -1;
1017     return NULL;
1018   }
1019   pfree(sqldata.data);
1020 
1021   POSTGIS_DEBUGF(1, "cb_getEdgeByNode: edge query returned %d rows", SPI_processed);
1022   *numelems = SPI_processed;
1023   if ( ! SPI_processed )
1024   {
1025     return NULL;
1026   }
1027 
1028   edges = palloc( sizeof(LWT_ISO_EDGE) * *numelems );
1029   for ( i=0; i<*numelems; ++i )
1030   {
1031     HeapTuple row = SPI_tuptable->vals[i];
1032     fillEdgeFields(&edges[i], row, SPI_tuptable->tupdesc, fields);
1033   }
1034 
1035   SPI_freetuptable(SPI_tuptable);
1036 
1037   return edges;
1038 }
1039 
1040 static LWT_ISO_EDGE*
cb_getEdgeByFace(const LWT_BE_TOPOLOGY * topo,const LWT_ELEMID * ids,int * numelems,int fields,const GBOX * box)1041 cb_getEdgeByFace(const LWT_BE_TOPOLOGY* topo,
1042                  const LWT_ELEMID* ids, int* numelems, int fields,
1043                  const GBOX *box)
1044 {
1045   LWT_ISO_EDGE *edges;
1046   int spi_result;
1047   MemoryContext oldcontext = CurrentMemoryContext;
1048   StringInfoData sqldata;
1049   StringInfo sql = &sqldata;
1050   int i;
1051   ArrayType *array_ids;
1052   Datum *datum_ids;
1053   Datum values[2];
1054   Oid argtypes[2];
1055   int nargs = 1;
1056   GSERIALIZED *gser = NULL;
1057 
1058   datum_ids = palloc(sizeof(Datum)*(*numelems));
1059   for (i=0; i<*numelems; ++i) datum_ids[i] = Int32GetDatum(ids[i]);
1060   array_ids = construct_array(datum_ids, *numelems, INT4OID, 4, true, 's');
1061 
1062   initStringInfo(sql);
1063   appendStringInfoString(sql, "SELECT ");
1064   addEdgeFields(sql, fields, 0);
1065   appendStringInfo(sql, " FROM \"%s\".edge_data"
1066                    " WHERE ( left_face = ANY($1) "
1067                    " OR right_face = ANY ($1) )",
1068                    topo->name);
1069 
1070   values[0] = PointerGetDatum(array_ids);
1071   argtypes[0] = INT4ARRAYOID;
1072 
1073   if ( box )
1074   {
1075     LWGEOM *g = _box2d_to_lwgeom(box, topo->srid);
1076     gser = geometry_serialize(g);
1077     lwgeom_free(g);
1078     appendStringInfo(sql, " AND geom && $2");
1079 
1080     values[1] = PointerGetDatum(gser);
1081     argtypes[1] = topo->geometryOID;
1082     ++nargs;
1083   }
1084 
1085   POSTGIS_DEBUGF(1, "cb_getEdgeByFace query: %s", sql->data);
1086   POSTGIS_DEBUGF(1, "data_changed is %d", topo->be_data->data_changed);
1087 
1088   spi_result = SPI_execute_with_args(sql->data, nargs, argtypes, values, NULL,
1089                                      !topo->be_data->data_changed, 0);
1090   pfree(array_ids); /* not needed anymore */
1091   if ( gser ) pfree(gser); /* not needed anymore */
1092   MemoryContextSwitchTo( oldcontext ); /* switch back */
1093   if ( spi_result != SPI_OK_SELECT )
1094   {
1095     cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
1096     pfree(sqldata.data);
1097     *numelems = -1;
1098     return NULL;
1099   }
1100   pfree(sqldata.data);
1101 
1102   POSTGIS_DEBUGF(1, "cb_getEdgeByFace: edge query returned %d rows", SPI_processed);
1103   *numelems = SPI_processed;
1104   if ( ! SPI_processed )
1105   {
1106     return NULL;
1107   }
1108 
1109   edges = palloc( sizeof(LWT_ISO_EDGE) * *numelems );
1110   for ( i=0; i<*numelems; ++i )
1111   {
1112     HeapTuple row = SPI_tuptable->vals[i];
1113     fillEdgeFields(&edges[i], row, SPI_tuptable->tupdesc, fields);
1114   }
1115 
1116   SPI_freetuptable(SPI_tuptable);
1117 
1118   return edges;
1119 }
1120 
1121 static LWT_ISO_FACE*
cb_getFacesById(const LWT_BE_TOPOLOGY * topo,const LWT_ELEMID * ids,int * numelems,int fields)1122 cb_getFacesById(const LWT_BE_TOPOLOGY* topo,
1123                 const LWT_ELEMID* ids, int* numelems, int fields)
1124 {
1125   LWT_ISO_FACE *faces;
1126   int spi_result;
1127   StringInfoData sqldata;
1128   StringInfo sql = &sqldata;
1129   int i;
1130   MemoryContext oldcontext = CurrentMemoryContext;
1131 
1132   initStringInfo(sql);
1133   appendStringInfoString(sql, "SELECT ");
1134   addFaceFields(sql, fields);
1135   appendStringInfo(sql, " FROM \"%s\".face", topo->name);
1136   appendStringInfoString(sql, " WHERE face_id IN (");
1137   // add all identifiers here
1138   for (i=0; i<*numelems; ++i)
1139   {
1140     appendStringInfo(sql, "%s%" LWTFMT_ELEMID, (i?",":""), ids[i]);
1141   }
1142   appendStringInfoString(sql, ")");
1143 
1144   POSTGIS_DEBUGF(1, "cb_getFaceById query: %s", sql->data);
1145   POSTGIS_DEBUGF(1, "data_changed is %d", topo->be_data->data_changed);
1146 
1147   spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0);
1148   MemoryContextSwitchTo( oldcontext ); /* switch back */
1149   if ( spi_result != SPI_OK_SELECT )
1150   {
1151     cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
1152     pfree(sqldata.data);
1153     *numelems = -1;
1154     return NULL;
1155   }
1156   pfree(sqldata.data);
1157 
1158   POSTGIS_DEBUGF(1, "cb_getFaceById: face query returned %d rows", SPI_processed);
1159   *numelems = SPI_processed;
1160   if ( ! SPI_processed )
1161   {
1162     return NULL;
1163   }
1164 
1165   faces = palloc( sizeof(LWT_ISO_EDGE) * *numelems );
1166   for ( i=0; i<*numelems; ++i )
1167   {
1168     HeapTuple row = SPI_tuptable->vals[i];
1169     fillFaceFields(&faces[i], row, SPI_tuptable->tupdesc, fields);
1170   }
1171 
1172   SPI_freetuptable(SPI_tuptable);
1173 
1174   return faces;
1175 }
1176 
1177 static LWT_ELEMID*
cb_getRingEdges(const LWT_BE_TOPOLOGY * topo,LWT_ELEMID edge,int * numelems,int limit)1178 cb_getRingEdges(const LWT_BE_TOPOLOGY* topo,
1179                 LWT_ELEMID edge, int* numelems, int limit)
1180 {
1181   LWT_ELEMID *edges;
1182   int spi_result;
1183   TupleDesc rowdesc;
1184   StringInfoData sqldata;
1185   StringInfo sql = &sqldata;
1186   int i;
1187   MemoryContext oldcontext = CurrentMemoryContext;
1188 
1189   initStringInfo(sql);
1190   appendStringInfo(sql, "WITH RECURSIVE edgering AS ( "
1191                    "SELECT %" LWTFMT_ELEMID
1192                    " as signed_edge_id, edge_id, next_left_edge, next_right_edge "
1193                    "FROM \"%s\".edge_data WHERE edge_id = %" LWTFMT_ELEMID " UNION "
1194                    "SELECT CASE WHEN "
1195                    "p.signed_edge_id < 0 THEN p.next_right_edge ELSE p.next_left_edge END, "
1196                    "e.edge_id, e.next_left_edge, e.next_right_edge "
1197                    "FROM \"%s\".edge_data e, edgering p WHERE "
1198                    "e.edge_id = CASE WHEN p.signed_edge_id < 0 THEN "
1199                    "abs(p.next_right_edge) ELSE abs(p.next_left_edge) END ) "
1200                    "SELECT * FROM edgering",
1201                    edge, topo->name, ABS(edge), topo->name);
1202   if ( limit )
1203   {
1204     ++limit; /* so we know if we hit it */
1205     appendStringInfo(sql, " LIMIT %d", limit);
1206   }
1207 
1208   POSTGIS_DEBUGF(1, "cb_getRingEdges query (limit %d): %s", limit, sql->data);
1209   spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, limit);
1210   MemoryContextSwitchTo( oldcontext ); /* switch back */
1211   if ( spi_result != SPI_OK_SELECT )
1212   {
1213     cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
1214     pfree(sqldata.data);
1215     *numelems = -1;
1216     return NULL;
1217   }
1218   pfree(sqldata.data);
1219 
1220   POSTGIS_DEBUGF(1, "cb_getRingEdges: edge query returned %d rows", SPI_processed);
1221   *numelems = SPI_processed;
1222   if ( ! SPI_processed )
1223   {
1224     return NULL;
1225   }
1226   if ( limit && *numelems == limit )
1227   {
1228     cberror(topo->be_data, "Max traversing limit hit: %d", limit-1);
1229     *numelems = -1;
1230     return NULL;
1231   }
1232 
1233   edges = palloc( sizeof(LWT_ELEMID) * *numelems );
1234   rowdesc = SPI_tuptable->tupdesc;
1235   for ( i=0; i<*numelems; ++i )
1236   {
1237     HeapTuple row = SPI_tuptable->vals[i];
1238     bool isnull;
1239     Datum dat;
1240     int32 val;
1241     dat = SPI_getbinval(row, rowdesc, 1, &isnull);
1242     if ( isnull )
1243     {
1244       lwfree(edges);
1245       cberror(topo->be_data, "Found edge with NULL edge_id");
1246       *numelems = -1;
1247       return NULL;
1248     }
1249     val = DatumGetInt32(dat);
1250     edges[i] = val;
1251     POSTGIS_DEBUGF(1, "Component " UINT64_FORMAT " in ring of edge %" LWTFMT_ELEMID " is edge %d", i, edge, val);
1252 
1253     /* For the last entry, check that we returned back to start
1254      * point, or complain about topology being corrupted */
1255     if ( i == *numelems - 1 )
1256     {
1257       int32 nextedge;
1258       int sidecol = val > 0 ? 3 : 4;
1259       const char *sidetext = val > 0 ? "left" : "right";
1260 
1261       dat = SPI_getbinval(row, rowdesc, sidecol, &isnull);
1262       if ( isnull )
1263       {
1264         lwfree(edges);
1265         cberror(topo->be_data, "Edge %d" /*LWTFMT_ELEMID*/
1266                                " has NULL next_%s_edge",
1267                                val, sidetext);
1268         *numelems = -1;
1269         return NULL;
1270       }
1271       nextedge = DatumGetInt32(dat);
1272       POSTGIS_DEBUGF(1, "Last component in ring of edge %"
1273                         LWTFMT_ELEMID " (%" LWTFMT_ELEMID ") has next_%s_edge %d",
1274                         edge, val, sidetext, nextedge);
1275       if ( nextedge != edge )
1276       {
1277         lwfree(edges);
1278         cberror(topo->be_data, "Corrupted topology: ring of edge %"
1279                                LWTFMT_ELEMID " is topologically non-closed",
1280                                edge);
1281         *numelems = -1;
1282         return NULL;
1283       }
1284     }
1285 
1286   }
1287 
1288   SPI_freetuptable(SPI_tuptable);
1289 
1290   return edges;
1291 }
1292 
1293 static LWT_ISO_NODE*
cb_getNodeById(const LWT_BE_TOPOLOGY * topo,const LWT_ELEMID * ids,int * numelems,int fields)1294 cb_getNodeById(const LWT_BE_TOPOLOGY* topo,
1295                const LWT_ELEMID* ids, int* numelems, int fields)
1296 {
1297   LWT_ISO_NODE *nodes;
1298   int spi_result;
1299 
1300   StringInfoData sqldata;
1301   StringInfo sql = &sqldata;
1302   int i;
1303   MemoryContext oldcontext = CurrentMemoryContext;
1304 
1305   initStringInfo(sql);
1306   appendStringInfoString(sql, "SELECT ");
1307   addNodeFields(sql, fields);
1308   appendStringInfo(sql, " FROM \"%s\".node", topo->name);
1309   appendStringInfoString(sql, " WHERE node_id IN (");
1310   // add all identifiers here
1311   for (i=0; i<*numelems; ++i)
1312   {
1313     appendStringInfo(sql, "%s%" LWTFMT_ELEMID, (i?",":""), ids[i]);
1314   }
1315   appendStringInfoString(sql, ")");
1316   POSTGIS_DEBUGF(1, "cb_getNodeById query: %s", sql->data);
1317   spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, *numelems);
1318   MemoryContextSwitchTo( oldcontext ); /* switch back */
1319   if ( spi_result != SPI_OK_SELECT )
1320   {
1321     cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
1322     pfree(sqldata.data);
1323     *numelems = -1;
1324     return NULL;
1325   }
1326   pfree(sqldata.data);
1327 
1328   POSTGIS_DEBUGF(1, "cb_getNodeById: edge query returned %d rows", SPI_processed);
1329   *numelems = SPI_processed;
1330   if ( ! SPI_processed )
1331   {
1332     return NULL;
1333   }
1334 
1335   nodes = palloc( sizeof(LWT_ISO_NODE) * *numelems );
1336   for ( i=0; i<*numelems; ++i )
1337   {
1338     HeapTuple row = SPI_tuptable->vals[i];
1339     fillNodeFields(&nodes[i], row, SPI_tuptable->tupdesc, fields);
1340   }
1341 
1342   SPI_freetuptable(SPI_tuptable);
1343 
1344   return nodes;
1345 }
1346 
1347 static LWT_ISO_NODE*
cb_getNodeByFace(const LWT_BE_TOPOLOGY * topo,const LWT_ELEMID * ids,int * numelems,int fields,const GBOX * box)1348 cb_getNodeByFace(const LWT_BE_TOPOLOGY* topo,
1349                  const LWT_ELEMID* ids, int* numelems, int fields,
1350                  const GBOX *box)
1351 {
1352   LWT_ISO_NODE *nodes;
1353   int spi_result;
1354   MemoryContext oldcontext = CurrentMemoryContext;
1355   StringInfoData sqldata;
1356   StringInfo sql = &sqldata;
1357   int i;
1358   char *hexbox;
1359 
1360   initStringInfo(sql);
1361   appendStringInfoString(sql, "SELECT ");
1362   addNodeFields(sql, fields);
1363   appendStringInfo(sql, " FROM \"%s\".node", topo->name);
1364   appendStringInfoString(sql, " WHERE containing_face IN (");
1365   // add all identifiers here
1366   for (i=0; i<*numelems; ++i)
1367   {
1368     appendStringInfo(sql, "%s%" LWTFMT_ELEMID, (i?",":""), ids[i]);
1369   }
1370   appendStringInfoString(sql, ")");
1371   if ( box )
1372   {
1373     hexbox = _box2d_to_hexwkb(box, topo->srid);
1374     appendStringInfo(sql, " AND geom && '%s'::geometry", hexbox);
1375     lwfree(hexbox);
1376   }
1377   POSTGIS_DEBUGF(1, "cb_getNodeByFace query: %s", sql->data);
1378   POSTGIS_DEBUGF(1, "data_changed is %d", topo->be_data->data_changed);
1379   spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0);
1380   MemoryContextSwitchTo( oldcontext ); /* switch back */
1381   if ( spi_result != SPI_OK_SELECT )
1382   {
1383     cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
1384     pfree(sqldata.data);
1385     *numelems = -1;
1386     return NULL;
1387   }
1388   pfree(sqldata.data);
1389 
1390   POSTGIS_DEBUGF(1, "cb_getNodeByFace: edge query returned %d rows", SPI_processed);
1391   *numelems = SPI_processed;
1392   if ( ! SPI_processed )
1393   {
1394     return NULL;
1395   }
1396 
1397   nodes = palloc( sizeof(LWT_ISO_NODE) * *numelems );
1398   for ( i=0; i<*numelems; ++i )
1399   {
1400     HeapTuple row = SPI_tuptable->vals[i];
1401     fillNodeFields(&nodes[i], row, SPI_tuptable->tupdesc, fields);
1402   }
1403 
1404   SPI_freetuptable(SPI_tuptable);
1405 
1406   return nodes;
1407 }
1408 
1409 static LWT_ISO_EDGE*
cb_getEdgeWithinDistance2D(const LWT_BE_TOPOLOGY * topo,const LWPOINT * pt,double dist,int * numelems,int fields,int limit)1410 cb_getEdgeWithinDistance2D(const LWT_BE_TOPOLOGY* topo,
1411                            const LWPOINT* pt, double dist, int* numelems,
1412                            int fields, int limit)
1413 {
1414   LWT_ISO_EDGE *edges;
1415   int spi_result;
1416   int elems_requested = limit;
1417   size_t hexewkb_size;
1418   char *hexewkb;
1419   MemoryContext oldcontext = CurrentMemoryContext;
1420   StringInfoData sqldata;
1421   StringInfo sql = &sqldata;
1422   int i;
1423 
1424   initStringInfo(sql);
1425   if ( elems_requested == -1 )
1426   {
1427     appendStringInfoString(sql, "SELECT EXISTS ( SELECT 1");
1428   }
1429   else
1430   {
1431     appendStringInfoString(sql, "SELECT ");
1432     addEdgeFields(sql, fields, 0);
1433   }
1434   appendStringInfo(sql, " FROM \"%s\".edge_data", topo->name);
1435   // TODO: use binary cursor here ?
1436   hexewkb = lwgeom_to_hexwkb(lwpoint_as_lwgeom(pt), WKB_EXTENDED, &hexewkb_size);
1437   if ( dist )
1438   {
1439     appendStringInfo(sql, " WHERE ST_DWithin('%s'::geometry, geom, %g)", hexewkb, dist);
1440   }
1441   else
1442   {
1443     appendStringInfo(sql, " WHERE ST_Within('%s'::geometry, geom)", hexewkb);
1444   }
1445   lwfree(hexewkb);
1446   if ( elems_requested == -1 )
1447   {
1448     appendStringInfoString(sql, ")");
1449   }
1450   else if ( elems_requested > 0 )
1451   {
1452     appendStringInfo(sql, " LIMIT %d", elems_requested);
1453   }
1454   POSTGIS_DEBUGF(1, "cb_getEdgeWithinDistance2D: query is: %s", sql->data);
1455   spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, limit >= 0 ? limit : 0);
1456   MemoryContextSwitchTo( oldcontext ); /* switch back */
1457   if ( spi_result != SPI_OK_SELECT )
1458   {
1459     cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
1460     pfree(sqldata.data);
1461     *numelems = -1;
1462     return NULL;
1463   }
1464   pfree(sqldata.data);
1465 
1466   POSTGIS_DEBUGF(1, "cb_getEdgeWithinDistance2D: edge query "
1467                  "(limited by %d) returned %d rows",
1468                  elems_requested, SPI_processed);
1469   *numelems = SPI_processed;
1470   if ( ! SPI_processed )
1471   {
1472     return NULL;
1473   }
1474 
1475   if ( elems_requested == -1 )
1476   {
1477     /* This was an EXISTS query */
1478     {
1479       Datum dat;
1480       bool isnull, exists;
1481       dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
1482       exists = DatumGetBool(dat);
1483       *numelems = exists ? 1 : 0;
1484       POSTGIS_DEBUGF(1, "cb_getEdgeWithinDistance2D: exists ? %d", *numelems);
1485     }
1486 
1487     SPI_freetuptable(SPI_tuptable);
1488 
1489     return NULL;
1490   }
1491 
1492   edges = palloc( sizeof(LWT_ISO_EDGE) * *numelems );
1493   for ( i=0; i<*numelems; ++i )
1494   {
1495     HeapTuple row = SPI_tuptable->vals[i];
1496     fillEdgeFields(&edges[i], row, SPI_tuptable->tupdesc, fields);
1497   }
1498 
1499   SPI_freetuptable(SPI_tuptable);
1500 
1501   return edges;
1502 }
1503 
1504 static LWT_ISO_NODE*
cb_getNodeWithinDistance2D(const LWT_BE_TOPOLOGY * topo,const LWPOINT * pt,double dist,int * numelems,int fields,int limit)1505 cb_getNodeWithinDistance2D(const LWT_BE_TOPOLOGY* topo,
1506                            const LWPOINT* pt, double dist, int* numelems,
1507                            int fields, int limit)
1508 {
1509   MemoryContext oldcontext = CurrentMemoryContext;
1510   LWT_ISO_NODE *nodes;
1511   int spi_result;
1512   size_t hexewkb_size;
1513   char *hexewkb;
1514   StringInfoData sqldata;
1515   StringInfo sql = &sqldata;
1516   int elems_requested = limit;
1517   int i;
1518 
1519   initStringInfo(sql);
1520   if ( elems_requested == -1 )
1521   {
1522     appendStringInfoString(sql, "SELECT EXISTS ( SELECT 1");
1523   }
1524   else
1525   {
1526     appendStringInfoString(sql, "SELECT ");
1527     if ( fields ) addNodeFields(sql, fields);
1528     else
1529     {
1530       lwpgwarning("liblwgeom-topo invoked 'getNodeWithinDistance2D' "
1531                   "backend callback with limit=%d and no fields",
1532                   elems_requested);
1533       appendStringInfo(sql, "*");
1534     }
1535   }
1536   appendStringInfo(sql, " FROM \"%s\".node", topo->name);
1537   // TODO: use binary cursor here ?
1538   hexewkb = lwgeom_to_hexwkb(lwpoint_as_lwgeom(pt), WKB_EXTENDED, &hexewkb_size);
1539   if ( dist )
1540   {
1541     appendStringInfo(sql, " WHERE ST_DWithin(geom, '%s'::geometry, %g)",
1542                      hexewkb, dist);
1543   }
1544   else
1545   {
1546     appendStringInfo(sql, " WHERE ST_Equals(geom, '%s'::geometry)", hexewkb);
1547   }
1548   lwfree(hexewkb);
1549   if ( elems_requested == -1 )
1550   {
1551     appendStringInfoString(sql, ")");
1552   }
1553   else if ( elems_requested > 0 )
1554   {
1555     appendStringInfo(sql, " LIMIT %d", elems_requested);
1556   }
1557   spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, limit >= 0 ? limit : 0);
1558   MemoryContextSwitchTo( oldcontext ); /* switch back */
1559   if ( spi_result != SPI_OK_SELECT )
1560   {
1561     cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
1562             spi_result, sql->data);
1563     pfree(sqldata.data);
1564     *numelems = -1;
1565     return NULL;
1566   }
1567   pfree(sqldata.data);
1568 
1569   POSTGIS_DEBUGF(1, "cb_getNodeWithinDistance2D: node query "
1570                  "(limited by %d) returned %d rows",
1571                  elems_requested, SPI_processed);
1572   if ( ! SPI_processed )
1573   {
1574     *numelems = 0;
1575     return NULL;
1576   }
1577 
1578   if ( elems_requested == -1 )
1579   {
1580     /* This was an EXISTS query */
1581     {
1582       Datum dat;
1583       bool isnull, exists;
1584       dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
1585       exists = DatumGetBool(dat);
1586       *numelems = exists ? 1 : 0;
1587     }
1588 
1589     SPI_freetuptable(SPI_tuptable);
1590 
1591     return NULL;
1592   }
1593   else
1594   {
1595     *numelems = SPI_processed;
1596     nodes = palloc( sizeof(LWT_ISO_EDGE) * *numelems );
1597     for ( i=0; i<*numelems; ++i )
1598     {
1599       HeapTuple row = SPI_tuptable->vals[i];
1600       fillNodeFields(&nodes[i], row, SPI_tuptable->tupdesc, fields);
1601     }
1602 
1603     SPI_freetuptable(SPI_tuptable);
1604 
1605     return nodes;
1606   }
1607 }
1608 
1609 static int
cb_insertNodes(const LWT_BE_TOPOLOGY * topo,LWT_ISO_NODE * nodes,int numelems)1610 cb_insertNodes( const LWT_BE_TOPOLOGY* topo,
1611                 LWT_ISO_NODE* nodes, int numelems )
1612 {
1613   MemoryContext oldcontext = CurrentMemoryContext;
1614   int spi_result;
1615   StringInfoData sqldata;
1616   StringInfo sql = &sqldata;
1617   int i;
1618 
1619   initStringInfo(sql);
1620   appendStringInfo(sql, "INSERT INTO \"%s\".node (", topo->name);
1621   addNodeFields(sql, LWT_COL_NODE_ALL);
1622   appendStringInfoString(sql, ") VALUES ");
1623   for ( i=0; i<numelems; ++i )
1624   {
1625     if ( i ) appendStringInfoString(sql, ",");
1626     // TODO: prepare and execute ?
1627     addNodeValues(sql, &nodes[i], LWT_COL_NODE_ALL);
1628   }
1629   appendStringInfoString(sql, " RETURNING node_id");
1630 
1631   POSTGIS_DEBUGF(1, "cb_insertNodes query: %s", sql->data);
1632 
1633   spi_result = SPI_execute(sql->data, false, numelems);
1634   MemoryContextSwitchTo( oldcontext ); /* switch back */
1635   if ( spi_result != SPI_OK_INSERT_RETURNING )
1636   {
1637     cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
1638             spi_result, sql->data);
1639     pfree(sqldata.data);
1640     return 0;
1641   }
1642   pfree(sqldata.data);
1643 
1644   if ( SPI_processed ) topo->be_data->data_changed = true;
1645 
1646   // TODO: Remove cast when numelems uses uint64 instead of int
1647   if ( SPI_processed != (uint64) numelems )
1648   {
1649     cberror(topo->be_data, "processed " UINT64_FORMAT " rows, expected %d",
1650             (uint64)SPI_processed, numelems);
1651     return 0;
1652   }
1653 
1654   /* Set node_id (could skip this if none had it set to -1) */
1655   /* TODO: check for -1 values in the first loop */
1656   for ( i=0; i<numelems; ++i )
1657   {
1658     if ( nodes[i].node_id != -1 ) continue;
1659     fillNodeFields(&nodes[i], SPI_tuptable->vals[i],
1660                    SPI_tuptable->tupdesc, LWT_COL_NODE_NODE_ID);
1661   }
1662 
1663   SPI_freetuptable(SPI_tuptable);
1664 
1665   return 1;
1666 }
1667 
1668 static int
cb_insertEdges(const LWT_BE_TOPOLOGY * topo,LWT_ISO_EDGE * edges,int numelems)1669 cb_insertEdges( const LWT_BE_TOPOLOGY* topo,
1670                 LWT_ISO_EDGE* edges, int numelems )
1671 {
1672   MemoryContext oldcontext = CurrentMemoryContext;
1673   int spi_result;
1674   StringInfoData sqldata;
1675   StringInfo sql = &sqldata;
1676   int i;
1677   int needsEdgeIdReturn = 0;
1678 
1679   initStringInfo(sql);
1680   /* NOTE: we insert into "edge", on which an insert rule is defined */
1681   appendStringInfo(sql, "INSERT INTO \"%s\".edge_data (", topo->name);
1682   addEdgeFields(sql, LWT_COL_EDGE_ALL, 1);
1683   appendStringInfoString(sql, ") VALUES ");
1684   for ( i=0; i<numelems; ++i )
1685   {
1686     if ( i ) appendStringInfoString(sql, ",");
1687     // TODO: prepare and execute ?
1688     addEdgeValues(sql, &edges[i], LWT_COL_EDGE_ALL, 1);
1689     if ( edges[i].edge_id == -1 ) needsEdgeIdReturn = 1;
1690   }
1691   if ( needsEdgeIdReturn ) appendStringInfoString(sql, " RETURNING edge_id");
1692 
1693   POSTGIS_DEBUGF(1, "cb_insertEdges query (%d elems): %s", numelems, sql->data);
1694   spi_result = SPI_execute(sql->data, false, numelems);
1695   MemoryContextSwitchTo( oldcontext ); /* switch back */
1696   if ( spi_result != ( needsEdgeIdReturn ? SPI_OK_INSERT_RETURNING : SPI_OK_INSERT ) )
1697   {
1698     cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
1699             spi_result, sql->data);
1700     pfree(sqldata.data);
1701     return -1;
1702   }
1703   pfree(sqldata.data);
1704   if ( SPI_processed ) topo->be_data->data_changed = true;
1705   POSTGIS_DEBUGF(1, "cb_insertEdges query processed %d rows", SPI_processed);
1706   if ( SPI_processed != (uint64) numelems )
1707   {
1708     cberror(topo->be_data, "processed " UINT64_FORMAT " rows, expected %d",
1709             (uint64)SPI_processed, numelems);
1710     return -1;
1711   }
1712 
1713   if ( needsEdgeIdReturn )
1714   {
1715     /* Set node_id for items that need it */
1716     for ( i=0; i<(int)SPI_processed; ++i )
1717     {
1718       if ( edges[i].edge_id != -1 ) continue;
1719       fillEdgeFields(&edges[i], SPI_tuptable->vals[i],
1720                      SPI_tuptable->tupdesc, LWT_COL_EDGE_EDGE_ID);
1721     }
1722   }
1723 
1724   SPI_freetuptable(SPI_tuptable);
1725 
1726   return SPI_processed;
1727 }
1728 
1729 static int
cb_insertFaces(const LWT_BE_TOPOLOGY * topo,LWT_ISO_FACE * faces,int numelems)1730 cb_insertFaces( const LWT_BE_TOPOLOGY* topo,
1731                 LWT_ISO_FACE* faces, int numelems )
1732 {
1733   MemoryContext oldcontext = CurrentMemoryContext;
1734   int spi_result;
1735   StringInfoData sqldata;
1736   StringInfo sql = &sqldata;
1737   int i;
1738   int needsFaceIdReturn = 0;
1739 
1740   initStringInfo(sql);
1741   appendStringInfo(sql, "INSERT INTO \"%s\".face (", topo->name);
1742   addFaceFields(sql, LWT_COL_FACE_ALL);
1743   appendStringInfoString(sql, ") VALUES ");
1744   for ( i=0; i<numelems; ++i )
1745   {
1746     if ( i ) appendStringInfoString(sql, ",");
1747     // TODO: prepare and execute ?
1748     addFaceValues(sql, &faces[i], topo->srid);
1749     if ( faces[i].face_id == -1 ) needsFaceIdReturn = 1;
1750   }
1751   if ( needsFaceIdReturn ) appendStringInfoString(sql, " RETURNING face_id");
1752 
1753   POSTGIS_DEBUGF(1, "cb_insertFaces query (%d elems): %s", numelems, sql->data);
1754   spi_result = SPI_execute(sql->data, false, numelems);
1755   MemoryContextSwitchTo( oldcontext ); /* switch back */
1756   if ( spi_result != ( needsFaceIdReturn ? SPI_OK_INSERT_RETURNING : SPI_OK_INSERT ) )
1757   {
1758     cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
1759             spi_result, sql->data);
1760     pfree(sqldata.data);
1761     return -1;
1762   }
1763   pfree(sqldata.data);
1764   if ( SPI_processed ) topo->be_data->data_changed = true;
1765   POSTGIS_DEBUGF(1, "cb_insertFaces query processed %d rows", SPI_processed);
1766   if ( SPI_processed != (uint64) numelems )
1767   {
1768     cberror(topo->be_data, "processed " UINT64_FORMAT " rows, expected %d",
1769             (uint64)SPI_processed, numelems);
1770     return -1;
1771   }
1772 
1773   if ( needsFaceIdReturn )
1774   {
1775     /* Set node_id for items that need it */
1776     for ( i=0; i<numelems; ++i )
1777     {
1778       if ( faces[i].face_id != -1 ) continue;
1779       fillFaceFields(&faces[i], SPI_tuptable->vals[i],
1780                      SPI_tuptable->tupdesc, LWT_COL_FACE_FACE_ID);
1781     }
1782   }
1783 
1784   SPI_freetuptable(SPI_tuptable);
1785 
1786   return SPI_processed;
1787 }
1788 
1789 static int
cb_updateEdges(const LWT_BE_TOPOLOGY * topo,const LWT_ISO_EDGE * sel_edge,int sel_fields,const LWT_ISO_EDGE * upd_edge,int upd_fields,const LWT_ISO_EDGE * exc_edge,int exc_fields)1790 cb_updateEdges( const LWT_BE_TOPOLOGY* topo,
1791                 const LWT_ISO_EDGE* sel_edge, int sel_fields,
1792                 const LWT_ISO_EDGE* upd_edge, int upd_fields,
1793                 const LWT_ISO_EDGE* exc_edge, int exc_fields )
1794 {
1795   MemoryContext oldcontext = CurrentMemoryContext;
1796   int spi_result;
1797   StringInfoData sqldata;
1798   StringInfo sql = &sqldata;
1799 
1800   initStringInfo(sql);
1801   appendStringInfo(sql, "UPDATE \"%s\".edge_data SET ", topo->name);
1802   addEdgeUpdate( sql, upd_edge, upd_fields, 1, updSet );
1803   if ( exc_edge || sel_edge ) appendStringInfoString(sql, " WHERE ");
1804   if ( sel_edge )
1805   {
1806     addEdgeUpdate( sql, sel_edge, sel_fields, 1, updSel );
1807     if ( exc_edge ) appendStringInfoString(sql, " AND ");
1808   }
1809   if ( exc_edge )
1810   {
1811     addEdgeUpdate( sql, exc_edge, exc_fields, 1, updNot );
1812   }
1813 
1814   POSTGIS_DEBUGF(1, "cb_updateEdges query: %s", sql->data);
1815 
1816   spi_result = SPI_execute( sql->data, false, 0 );
1817   MemoryContextSwitchTo( oldcontext ); /* switch back */
1818   if ( spi_result != SPI_OK_UPDATE )
1819   {
1820     cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
1821             spi_result, sql->data);
1822     pfree(sqldata.data);
1823     return -1;
1824   }
1825   pfree(sqldata.data);
1826 
1827   if ( SPI_processed ) topo->be_data->data_changed = true;
1828 
1829   POSTGIS_DEBUGF(1, "cb_updateEdges: update query processed %d rows", SPI_processed);
1830 
1831   return SPI_processed;
1832 }
1833 
1834 static int
cb_updateNodes(const LWT_BE_TOPOLOGY * topo,const LWT_ISO_NODE * sel_node,int sel_fields,const LWT_ISO_NODE * upd_node,int upd_fields,const LWT_ISO_NODE * exc_node,int exc_fields)1835 cb_updateNodes( const LWT_BE_TOPOLOGY* topo,
1836                 const LWT_ISO_NODE* sel_node, int sel_fields,
1837                 const LWT_ISO_NODE* upd_node, int upd_fields,
1838                 const LWT_ISO_NODE* exc_node, int exc_fields )
1839 {
1840   MemoryContext oldcontext = CurrentMemoryContext;
1841   int spi_result;
1842   StringInfoData sqldata;
1843   StringInfo sql = &sqldata;
1844 
1845   initStringInfo(sql);
1846   appendStringInfo(sql, "UPDATE \"%s\".node SET ", topo->name);
1847   addNodeUpdate( sql, upd_node, upd_fields, 1, updSet );
1848   if ( exc_node || sel_node ) appendStringInfoString(sql, " WHERE ");
1849   if ( sel_node )
1850   {
1851     addNodeUpdate( sql, sel_node, sel_fields, 1, updSel );
1852     if ( exc_node ) appendStringInfoString(sql, " AND ");
1853   }
1854   if ( exc_node )
1855   {
1856     addNodeUpdate( sql, exc_node, exc_fields, 1, updNot );
1857   }
1858 
1859   POSTGIS_DEBUGF(1, "cb_updateNodes: %s", sql->data);
1860 
1861   spi_result = SPI_execute( sql->data, false, 0 );
1862   MemoryContextSwitchTo( oldcontext ); /* switch back */
1863   if ( spi_result != SPI_OK_UPDATE )
1864   {
1865     cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
1866             spi_result, sql->data);
1867     pfree(sqldata.data);
1868     return -1;
1869   }
1870   pfree(sqldata.data);
1871 
1872   if ( SPI_processed ) topo->be_data->data_changed = true;
1873 
1874   POSTGIS_DEBUGF(1, "cb_updateNodes: update query processed %d rows", SPI_processed);
1875 
1876   return SPI_processed;
1877 }
1878 
1879 static int
cb_updateNodesById(const LWT_BE_TOPOLOGY * topo,const LWT_ISO_NODE * nodes,int numnodes,int fields)1880 cb_updateNodesById( const LWT_BE_TOPOLOGY* topo,
1881                     const LWT_ISO_NODE* nodes, int numnodes, int fields )
1882 {
1883   MemoryContext oldcontext = CurrentMemoryContext;
1884   int i;
1885   int spi_result;
1886   StringInfoData sqldata;
1887   StringInfo sql = &sqldata;
1888   const char *sep = "";
1889   const char *sep1 = ",";
1890 
1891   if ( ! fields )
1892   {
1893     cberror(topo->be_data,
1894             "updateNodesById callback called with no update fields!");
1895     return -1;
1896   }
1897 
1898   POSTGIS_DEBUGF(1, "cb_updateNodesById got %d nodes to update"
1899                  " (fields:%d)",
1900                  numnodes, fields);
1901 
1902   initStringInfo(sql);
1903   appendStringInfoString(sql, "WITH newnodes(node_id,");
1904   addNodeFields(sql, fields);
1905   appendStringInfoString(sql, ") AS ( VALUES ");
1906   for (i=0; i<numnodes; ++i)
1907   {
1908     const LWT_ISO_NODE* node = &(nodes[i]);
1909     if ( i ) appendStringInfoString(sql, ",");
1910     addNodeValues(sql, node, LWT_COL_NODE_NODE_ID|fields);
1911   }
1912   appendStringInfo(sql, " ) UPDATE \"%s\".node n SET ", topo->name);
1913 
1914   /* TODO: turn the following into a function */
1915   if ( fields & LWT_COL_NODE_NODE_ID )
1916   {
1917     appendStringInfo(sql, "%snode_id = o.node_id", sep);
1918     sep = sep1;
1919   }
1920   if ( fields & LWT_COL_NODE_CONTAINING_FACE )
1921   {
1922     appendStringInfo(sql, "%scontaining_face = o.containing_face", sep);
1923     sep = sep1;
1924   }
1925   if ( fields & LWT_COL_NODE_GEOM )
1926   {
1927     appendStringInfo(sql, "%sgeom = o.geom", sep);
1928   }
1929 
1930   appendStringInfo(sql, " FROM newnodes o WHERE n.node_id = o.node_id");
1931 
1932   POSTGIS_DEBUGF(1, "cb_updateNodesById query: %s", sql->data);
1933 
1934   spi_result = SPI_execute( sql->data, false, 0 );
1935   MemoryContextSwitchTo( oldcontext ); /* switch back */
1936   if ( spi_result != SPI_OK_UPDATE )
1937   {
1938     cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
1939             spi_result, sql->data);
1940     pfree(sqldata.data);
1941     return -1;
1942   }
1943   pfree(sqldata.data);
1944 
1945   if ( SPI_processed ) topo->be_data->data_changed = true;
1946 
1947   POSTGIS_DEBUGF(1, "cb_updateNodesById: update query processed %d rows", SPI_processed);
1948 
1949   return SPI_processed;
1950 }
1951 
1952 static int
cb_updateFacesById(const LWT_BE_TOPOLOGY * topo,const LWT_ISO_FACE * faces,int numfaces)1953 cb_updateFacesById( const LWT_BE_TOPOLOGY* topo,
1954                     const LWT_ISO_FACE* faces, int numfaces )
1955 {
1956   MemoryContext oldcontext = CurrentMemoryContext;
1957   int i;
1958   int spi_result;
1959   StringInfoData sqldata;
1960   StringInfo sql = &sqldata;
1961 
1962   initStringInfo(sql);
1963   appendStringInfoString(sql, "WITH newfaces(id,mbr) AS ( VALUES ");
1964   for (i=0; i<numfaces; ++i)
1965   {
1966     const LWT_ISO_FACE* face = &(faces[i]);
1967     char *hexbox = _box2d_to_hexwkb(face->mbr, topo->srid);
1968 
1969     if ( i ) appendStringInfoChar(sql, ',');
1970 
1971     appendStringInfo(sql, "(%" LWTFMT_ELEMID
1972                      ", ST_Envelope('%s'::geometry))",
1973                      face->face_id, hexbox);
1974     lwfree(hexbox);
1975   }
1976   appendStringInfo(sql, ") UPDATE \"%s\".face o SET mbr = i.mbr "
1977                    "FROM newfaces i WHERE o.face_id = i.id",
1978                    topo->name);
1979 
1980   POSTGIS_DEBUGF(1, "cb_updateFacesById query: %s", sql->data);
1981 
1982   spi_result = SPI_execute( sql->data, false, 0 );
1983   MemoryContextSwitchTo( oldcontext ); /* switch back */
1984   if ( spi_result != SPI_OK_UPDATE )
1985   {
1986     cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
1987             spi_result, sql->data);
1988     pfree(sqldata.data);
1989     return -1;
1990   }
1991   pfree(sqldata.data);
1992 
1993   if ( SPI_processed ) topo->be_data->data_changed = true;
1994 
1995   POSTGIS_DEBUGF(1, "cb_updateFacesById: update query processed %d rows", SPI_processed);
1996 
1997   return SPI_processed;
1998 }
1999 
2000 static int
cb_updateEdgesById(const LWT_BE_TOPOLOGY * topo,const LWT_ISO_EDGE * edges,int numedges,int fields)2001 cb_updateEdgesById( const LWT_BE_TOPOLOGY* topo,
2002                     const LWT_ISO_EDGE* edges, int numedges, int fields )
2003 {
2004   MemoryContext oldcontext = CurrentMemoryContext;
2005   int i;
2006   int spi_result;
2007   StringInfoData sqldata;
2008   StringInfo sql = &sqldata;
2009   const char *sep = "";
2010   const char *sep1 = ",";
2011 
2012   if ( ! fields )
2013   {
2014     cberror(topo->be_data,
2015             "updateEdgesById callback called with no update fields!");
2016     return -1;
2017   }
2018 
2019   initStringInfo(sql);
2020   appendStringInfoString(sql, "WITH newedges(edge_id,");
2021   addEdgeFields(sql, fields, 0);
2022   appendStringInfoString(sql, ") AS ( VALUES ");
2023   for (i=0; i<numedges; ++i)
2024   {
2025     const LWT_ISO_EDGE* edge = &(edges[i]);
2026     if ( i ) appendStringInfoString(sql, ",");
2027     addEdgeValues(sql, edge, fields|LWT_COL_EDGE_EDGE_ID, 0);
2028   }
2029   appendStringInfo(sql, ") UPDATE \"%s\".edge_data e SET ", topo->name);
2030 
2031   /* TODO: turn the following into a function */
2032   if ( fields & LWT_COL_EDGE_START_NODE )
2033   {
2034     appendStringInfo(sql, "%sstart_node = o.start_node", sep);
2035     sep = sep1;
2036   }
2037   if ( fields & LWT_COL_EDGE_END_NODE )
2038   {
2039     appendStringInfo(sql, "%send_node = o.end_node", sep);
2040     sep = sep1;
2041   }
2042   if ( fields & LWT_COL_EDGE_FACE_LEFT )
2043   {
2044     appendStringInfo(sql, "%sleft_face = o.left_face", sep);
2045     sep = sep1;
2046   }
2047   if ( fields & LWT_COL_EDGE_FACE_RIGHT )
2048   {
2049     appendStringInfo(sql, "%sright_face = o.right_face", sep);
2050     sep = sep1;
2051   }
2052   if ( fields & LWT_COL_EDGE_NEXT_LEFT )
2053   {
2054     appendStringInfo(sql,
2055                      "%snext_left_edge = o.next_left_edge, "
2056                      "abs_next_left_edge = abs(o.next_left_edge)", sep);
2057     sep = sep1;
2058   }
2059   if ( fields & LWT_COL_EDGE_NEXT_RIGHT )
2060   {
2061     appendStringInfo(sql,
2062                      "%snext_right_edge = o.next_right_edge, "
2063                      "abs_next_right_edge = abs(o.next_right_edge)", sep);
2064     sep = sep1;
2065   }
2066   if ( fields & LWT_COL_EDGE_GEOM )
2067   {
2068     appendStringInfo(sql, "%sgeom = o.geom", sep);
2069   }
2070 
2071   appendStringInfo(sql, " FROM newedges o WHERE e.edge_id = o.edge_id");
2072 
2073   POSTGIS_DEBUGF(1, "cb_updateEdgesById query: %s", sql->data);
2074 
2075   spi_result = SPI_execute( sql->data, false, 0 );
2076   MemoryContextSwitchTo( oldcontext ); /* switch back */
2077   if ( spi_result != SPI_OK_UPDATE )
2078   {
2079     cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2080             spi_result, sql->data);
2081     pfree(sqldata.data);
2082     return -1;
2083   }
2084   pfree(sqldata.data);
2085 
2086   if ( SPI_processed ) topo->be_data->data_changed = true;
2087 
2088   POSTGIS_DEBUGF(1, "cb_updateEdgesById: update query processed %d rows", SPI_processed);
2089 
2090   return SPI_processed;
2091 }
2092 
2093 static int
cb_deleteEdges(const LWT_BE_TOPOLOGY * topo,const LWT_ISO_EDGE * sel_edge,int sel_fields)2094 cb_deleteEdges( const LWT_BE_TOPOLOGY* topo,
2095                 const LWT_ISO_EDGE* sel_edge, int sel_fields )
2096 {
2097   MemoryContext oldcontext = CurrentMemoryContext;
2098   int spi_result;
2099   StringInfoData sqldata;
2100   StringInfo sql = &sqldata;
2101 
2102   initStringInfo(sql);
2103   appendStringInfo(sql, "DELETE FROM \"%s\".edge_data WHERE ", topo->name);
2104   addEdgeUpdate( sql, sel_edge, sel_fields, 0, updSel );
2105 
2106   POSTGIS_DEBUGF(1, "cb_deleteEdges: %s", sql->data);
2107 
2108   spi_result = SPI_execute( sql->data, false, 0 );
2109   MemoryContextSwitchTo( oldcontext ); /* switch back */
2110   if ( spi_result != SPI_OK_DELETE )
2111   {
2112     cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2113             spi_result, sql->data);
2114     pfree(sqldata.data);
2115     return -1;
2116   }
2117   pfree(sqldata.data);
2118 
2119   if ( SPI_processed ) topo->be_data->data_changed = true;
2120 
2121   POSTGIS_DEBUGF(1, "cb_deleteEdges: delete query processed %d rows", SPI_processed);
2122 
2123   return SPI_processed;
2124 }
2125 
2126 static LWT_ELEMID
cb_getNextEdgeId(const LWT_BE_TOPOLOGY * topo)2127 cb_getNextEdgeId( const LWT_BE_TOPOLOGY* topo )
2128 {
2129   MemoryContext oldcontext = CurrentMemoryContext;
2130   int spi_result;
2131   StringInfoData sqldata;
2132   StringInfo sql = &sqldata;
2133   bool isnull;
2134   Datum dat;
2135   LWT_ELEMID edge_id;
2136 
2137   initStringInfo(sql);
2138   appendStringInfo(sql, "SELECT nextval('\"%s\".edge_data_edge_id_seq')",
2139                    topo->name);
2140   spi_result = SPI_execute(sql->data, false, 0);
2141   MemoryContextSwitchTo( oldcontext ); /* switch back */
2142   if ( spi_result != SPI_OK_SELECT )
2143   {
2144     cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2145             spi_result, sql->data);
2146     pfree(sqldata.data);
2147     return -1;
2148   }
2149   pfree(sqldata.data);
2150 
2151   if ( SPI_processed ) topo->be_data->data_changed = true;
2152 
2153   if ( SPI_processed != 1 )
2154   {
2155     cberror(topo->be_data, "processed " UINT64_FORMAT " rows, expected 1",
2156             (uint64)SPI_processed);
2157     return -1;
2158   }
2159 
2160   dat = SPI_getbinval( SPI_tuptable->vals[0],
2161                        SPI_tuptable->tupdesc, 1, &isnull );
2162   if ( isnull )
2163   {
2164     cberror(topo->be_data, "nextval for edge_id returned null");
2165     return -1;
2166   }
2167   edge_id = DatumGetInt64(dat); /* sequences return 64bit integers */
2168 
2169   SPI_freetuptable(SPI_tuptable);
2170 
2171   return edge_id;
2172 }
2173 
2174 static int
cb_updateTopoGeomEdgeSplit(const LWT_BE_TOPOLOGY * topo,LWT_ELEMID split_edge,LWT_ELEMID new_edge1,LWT_ELEMID new_edge2)2175 cb_updateTopoGeomEdgeSplit ( const LWT_BE_TOPOLOGY* topo,
2176                              LWT_ELEMID split_edge, LWT_ELEMID new_edge1, LWT_ELEMID new_edge2 )
2177 {
2178   MemoryContext oldcontext = CurrentMemoryContext;
2179   int spi_result;
2180   StringInfoData sqldata;
2181   StringInfo sql = &sqldata;
2182   int i, ntopogeoms;
2183   const char *proj = "r.element_id, r.topogeo_id, r.layer_id, r.element_type";
2184 
2185   initStringInfo(sql);
2186   if ( new_edge2 == -1 )
2187   {
2188     appendStringInfo(sql, "SELECT %s", proj);
2189   }
2190   else
2191   {
2192     appendStringInfoString(sql, "DELETE");
2193   }
2194   appendStringInfo( sql, " FROM \"%s\".relation r %s topology.layer l WHERE "
2195                     "l.topology_id = %d AND l.level = 0 AND l.layer_id = r.layer_id "
2196                     "AND abs(r.element_id) = %" LWTFMT_ELEMID " AND r.element_type = 2",
2197                     topo->name, (new_edge2 == -1 ? "," : "USING" ), topo->id, split_edge );
2198   if ( new_edge2 != -1 )
2199   {
2200     appendStringInfo(sql, " RETURNING %s", proj);
2201   }
2202 
2203   POSTGIS_DEBUGF(1, "cb_updateTopoGeomEdgeSplit query: %s", sql->data);
2204 
2205   spi_result = SPI_execute(sql->data, new_edge2 == -1 ? !topo->be_data->data_changed : false, 0);
2206   MemoryContextSwitchTo( oldcontext ); /* switch back */
2207   if ( spi_result != ( new_edge2 == -1 ? SPI_OK_SELECT : SPI_OK_DELETE_RETURNING ) )
2208   {
2209     cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2210             spi_result, sql->data);
2211     pfree(sqldata.data);
2212     return 0;
2213   }
2214 
2215   if ( spi_result == SPI_OK_DELETE_RETURNING && SPI_processed )
2216   {
2217     POSTGIS_DEBUGF(1, "cb_updateTopoGeomEdgeSplit: deleted %d faces", SPI_processed);
2218     topo->be_data->data_changed = true;
2219   }
2220 
2221   ntopogeoms = SPI_processed;
2222   if ( ntopogeoms )
2223   {
2224     resetStringInfo(sql);
2225     appendStringInfo(sql, "INSERT INTO \"%s\".relation VALUES ", topo->name);
2226     for ( i=0; i<ntopogeoms; ++i )
2227     {
2228       HeapTuple row = SPI_tuptable->vals[i];
2229       TupleDesc tdesc = SPI_tuptable->tupdesc;
2230       int negate;
2231       int element_id;
2232       int topogeo_id;
2233       int layer_id;
2234       int element_type;
2235 
2236       if ( ! getNotNullInt32( row, tdesc, 1, &element_id ) )
2237       {
2238         cberror(topo->be_data,
2239                 "unexpected null element_id in \"%s\".relation",
2240                 topo->name);
2241         return 0;
2242       }
2243       negate = ( element_id < 0 );
2244 
2245       if ( ! getNotNullInt32( row, tdesc, 2, &topogeo_id ) )
2246       {
2247         cberror(topo->be_data,
2248                 "unexpected null topogeo_id in \"%s\".relation",
2249                 topo->name);
2250         return 0;
2251       }
2252 
2253       if ( ! getNotNullInt32( row, tdesc, 3, &layer_id ) )
2254       {
2255         cberror(topo->be_data,
2256                 "unexpected null layer_id in \"%s\".relation",
2257                 topo->name);
2258         return 0;
2259       }
2260 
2261       if ( ! getNotNullInt32( row, tdesc, 4, &element_type ) )
2262       {
2263         cberror(topo->be_data,
2264                 "unexpected null element_type in \"%s\".relation",
2265                 topo->name);
2266         return 0;
2267       }
2268 
2269       if ( i ) appendStringInfoChar(sql, ',');
2270       appendStringInfo(sql, "(%d,%d,%" LWTFMT_ELEMID ",%d)",
2271                        topogeo_id, layer_id, negate ? -new_edge1 : new_edge1, element_type);
2272       if ( new_edge2 != -1 )
2273       {
2274         resetStringInfo(sql);
2275         appendStringInfo(sql,
2276                          ",VALUES (%d,%d,%" LWTFMT_ELEMID ",%d",
2277                          topogeo_id, layer_id, negate ? -new_edge2 : new_edge2, element_type);
2278       }
2279     }
2280 
2281     SPI_freetuptable(SPI_tuptable);
2282 
2283     POSTGIS_DEBUGF(1, "cb_updateTopoGeomEdgeSplit query: %s", sql->data);
2284     spi_result = SPI_execute(sql->data, false, 0);
2285     MemoryContextSwitchTo( oldcontext ); /* switch back */
2286     if ( spi_result != SPI_OK_INSERT )
2287     {
2288       cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2289               spi_result, sql->data);
2290       pfree(sqldata.data);
2291       return 0;
2292     }
2293     if ( SPI_processed ) topo->be_data->data_changed = true;
2294   }
2295 
2296   POSTGIS_DEBUGF(1, "cb_updateTopoGeomEdgeSplit: updated %d topogeoms", ntopogeoms);
2297 
2298   pfree(sqldata.data);
2299   return 1;
2300 }
2301 
2302 static int
cb_updateTopoGeomFaceSplit(const LWT_BE_TOPOLOGY * topo,LWT_ELEMID split_face,LWT_ELEMID new_face1,LWT_ELEMID new_face2)2303 cb_updateTopoGeomFaceSplit ( const LWT_BE_TOPOLOGY* topo,
2304                              LWT_ELEMID split_face, LWT_ELEMID new_face1, LWT_ELEMID new_face2 )
2305 {
2306   MemoryContext oldcontext = CurrentMemoryContext;
2307   int spi_result;
2308   StringInfoData sqldata;
2309   StringInfo sql = &sqldata;
2310   int i, ntopogeoms;
2311   const char *proj = "r.element_id, r.topogeo_id, r.layer_id, r.element_type";
2312 
2313   POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceSplit signalled "
2314                  "split of face %" LWTFMT_ELEMID " into %"
2315                  LWTFMT_ELEMID " and %" LWTFMT_ELEMID,
2316                  split_face, new_face1, new_face2);
2317 
2318   initStringInfo(sql);
2319   if ( new_face2 == -1 )
2320   {
2321     appendStringInfo(sql, "SELECT %s", proj);
2322   }
2323   else
2324   {
2325     appendStringInfoString(sql, "DELETE");
2326   }
2327   appendStringInfo( sql, " FROM \"%s\".relation r %s topology.layer l WHERE "
2328                     "l.topology_id = %d AND l.level = 0 AND l.layer_id = r.layer_id "
2329                     "AND abs(r.element_id) = %" LWTFMT_ELEMID " AND r.element_type = 3",
2330                     topo->name, (new_face2 == -1 ? "," : "USING" ), topo->id, split_face );
2331   if ( new_face2 != -1 )
2332   {
2333     appendStringInfo(sql, " RETURNING %s", proj);
2334   }
2335 
2336   POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceSplit query: %s", sql->data);
2337 
2338   spi_result = SPI_execute(sql->data, new_face2 == -1 ? !topo->be_data->data_changed : false, 0);
2339   MemoryContextSwitchTo( oldcontext ); /* switch back */
2340   if ( spi_result != ( new_face2 == -1 ? SPI_OK_SELECT : SPI_OK_DELETE_RETURNING ) )
2341   {
2342     cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2343             spi_result, sql->data);
2344     pfree(sqldata.data);
2345     return 0;
2346   }
2347 
2348   if ( spi_result == SPI_OK_DELETE_RETURNING && SPI_processed )
2349   {
2350     topo->be_data->data_changed = true;
2351   }
2352 
2353   ntopogeoms = SPI_processed;
2354   if ( ntopogeoms )
2355   {
2356     resetStringInfo(sql);
2357     appendStringInfo(sql, "INSERT INTO \"%s\".relation VALUES ", topo->name);
2358     for ( i=0; i<ntopogeoms; ++i )
2359     {
2360       HeapTuple row = SPI_tuptable->vals[i];
2361       TupleDesc tdesc = SPI_tuptable->tupdesc;
2362       int negate;
2363       int element_id;
2364       int topogeo_id;
2365       int layer_id;
2366       int element_type;
2367 
2368       if ( ! getNotNullInt32( row, tdesc, 1, &element_id ) )
2369       {
2370         cberror(topo->be_data,
2371                 "unexpected null element_id in \"%s\".relation",
2372                 topo->name);
2373         return 0;
2374       }
2375       negate = ( element_id < 0 );
2376 
2377       if ( ! getNotNullInt32( row, tdesc, 2, &topogeo_id ) )
2378       {
2379         cberror(topo->be_data,
2380                 "unexpected null topogeo_id in \"%s\".relation",
2381                 topo->name);
2382         return 0;
2383       }
2384 
2385       if ( ! getNotNullInt32( row, tdesc, 3, &layer_id ) )
2386       {
2387         cberror(topo->be_data,
2388                 "unexpected null layer_id in \"%s\".relation",
2389                 topo->name);
2390         return 0;
2391       }
2392 
2393       if ( ! getNotNullInt32( row, tdesc, 4, &element_type ) )
2394       {
2395         cberror(topo->be_data,
2396                 "unexpected null element_type in \"%s\".relation",
2397                 topo->name);
2398         return 0;
2399       }
2400 
2401       if ( i ) appendStringInfoChar(sql, ',');
2402       appendStringInfo(sql,
2403                        "(%d,%d,%" LWTFMT_ELEMID ",%d)",
2404                        topogeo_id, layer_id, negate ? -new_face1 : new_face1, element_type);
2405 
2406       if ( new_face2 != -1 )
2407       {
2408         appendStringInfo(sql,
2409                          ",(%d,%d,%" LWTFMT_ELEMID ",%d)",
2410                          topogeo_id, layer_id, negate ? -new_face2 : new_face2, element_type);
2411       }
2412     }
2413 
2414     SPI_freetuptable(SPI_tuptable);
2415 
2416     POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceSplit query: %s", sql->data);
2417     spi_result = SPI_execute(sql->data, false, 0);
2418     MemoryContextSwitchTo( oldcontext ); /* switch back */
2419     if ( spi_result != SPI_OK_INSERT )
2420     {
2421       cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2422               spi_result, sql->data);
2423       pfree(sqldata.data);
2424       return 0;
2425     }
2426 
2427     if ( SPI_processed ) topo->be_data->data_changed = true;
2428   }
2429 
2430   POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceSplit: updated %d topogeoms", ntopogeoms);
2431 
2432   pfree(sqldata.data);
2433   return 1;
2434 }
2435 
2436 static int
cb_checkTopoGeomRemEdge(const LWT_BE_TOPOLOGY * topo,LWT_ELEMID rem_edge,LWT_ELEMID face_left,LWT_ELEMID face_right)2437 cb_checkTopoGeomRemEdge ( const LWT_BE_TOPOLOGY* topo,
2438                           LWT_ELEMID rem_edge, LWT_ELEMID face_left, LWT_ELEMID face_right )
2439 {
2440   MemoryContext oldcontext = CurrentMemoryContext;
2441   int spi_result;
2442   StringInfoData sqldata;
2443   StringInfo sql = &sqldata;
2444   const char *tg_id, *layer_id;
2445   const char *schema_name, *table_name, *col_name;
2446   HeapTuple row;
2447   TupleDesc tdesc;
2448 
2449   POSTGIS_DEBUG(1, "cb_checkTopoGeomRemEdge enter ");
2450 
2451   initStringInfo(sql);
2452   appendStringInfo( sql, "SELECT r.topogeo_id, r.layer_id, "
2453                     "l.schema_name, l.table_name, l.feature_column FROM "
2454                     "topology.layer l INNER JOIN \"%s\".relation r "
2455                     "ON (l.layer_id = r.layer_id) WHERE l.level = 0 AND "
2456                     "l.feature_type = 2 AND l.topology_id = %d"
2457                     " AND abs(r.element_id) = %" LWTFMT_ELEMID,
2458                     topo->name, topo->id, rem_edge );
2459 
2460   POSTGIS_DEBUGF(1, "cb_checkTopoGeomRemEdge query 1: %s", sql->data);
2461 
2462   spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0);
2463   MemoryContextSwitchTo( oldcontext ); /* switch back */
2464   if ( spi_result != SPI_OK_SELECT )
2465   {
2466     cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2467             spi_result, sql->data);
2468     pfree(sqldata.data);
2469     return 0;
2470   }
2471 
2472   if ( SPI_processed )
2473   {
2474     row = SPI_tuptable->vals[0];
2475     tdesc = SPI_tuptable->tupdesc;
2476 
2477     tg_id = SPI_getvalue(row, tdesc, 1);
2478     layer_id = SPI_getvalue(row, tdesc, 2);
2479     schema_name = SPI_getvalue(row, tdesc, 3);
2480     table_name = SPI_getvalue(row, tdesc, 4);
2481     col_name = SPI_getvalue(row, tdesc, 5);
2482 
2483     SPI_freetuptable(SPI_tuptable);
2484 
2485     cberror(topo->be_data, "TopoGeom %s in layer %s "
2486             "(%s.%s.%s) cannot be represented "
2487             "dropping edge %" LWTFMT_ELEMID,
2488             tg_id, layer_id, schema_name, table_name,
2489             col_name, rem_edge);
2490     return 0;
2491   }
2492 
2493 
2494   if ( face_left != face_right )
2495   {
2496     POSTGIS_DEBUGF(1, "Deletion of edge %" LWTFMT_ELEMID " joins faces %"
2497                    LWTFMT_ELEMID " and %" LWTFMT_ELEMID,
2498                    rem_edge, face_left, face_right);
2499     /*
2500       check if any topo_geom is defined only by one of the
2501       joined faces. In such case there would be no way to adapt
2502       the definition in case of healing, so we'd have to bail out
2503     */
2504     initStringInfo(sql);
2505     appendStringInfo( sql, "SELECT t.* FROM ( SELECT r.topogeo_id, "
2506                       "r.layer_id, l.schema_name, l.table_name, l.feature_column, "
2507                       "array_agg(r.element_id) as elems FROM topology.layer l "
2508                       " INNER JOIN \"%s\".relation r ON (l.layer_id = r.layer_id) "
2509                       "WHERE l.level = 0 and l.feature_type = 3 "
2510                       "AND l.topology_id = %d"
2511                       " AND r.element_id = ANY (ARRAY[%" LWTFMT_ELEMID ",%" LWTFMT_ELEMID
2512                       "]::int4[]) group by r.topogeo_id, r.layer_id, l.schema_name, "
2513                       "l.table_name, l.feature_column ) t WHERE NOT t.elems @> ARRAY[%"
2514                       LWTFMT_ELEMID ",%" LWTFMT_ELEMID "]::int4[]",
2515 
2516                       topo->name, topo->id,
2517                       face_left, face_right, face_left, face_right );
2518 
2519     POSTGIS_DEBUGF(1, "cb_checkTopoGeomRemEdge query 2: %s", sql->data);
2520     spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0);
2521     MemoryContextSwitchTo( oldcontext ); /* switch back */
2522 
2523     if ( spi_result != SPI_OK_SELECT )
2524     {
2525       cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2526               spi_result, sql->data);
2527       pfree(sqldata.data);
2528       return 0;
2529     }
2530 
2531     if ( SPI_processed )
2532     {
2533       row = SPI_tuptable->vals[0];
2534       tdesc = SPI_tuptable->tupdesc;
2535 
2536       tg_id = SPI_getvalue(row, tdesc, 1);
2537       layer_id = SPI_getvalue(row, tdesc, 2);
2538       schema_name = SPI_getvalue(row, tdesc, 3);
2539       table_name = SPI_getvalue(row, tdesc, 4);
2540       col_name = SPI_getvalue(row, tdesc, 5);
2541 
2542       SPI_freetuptable(SPI_tuptable);
2543 
2544       cberror(topo->be_data, "TopoGeom %s in layer %s "
2545               "(%s.%s.%s) cannot be represented "
2546               "healing faces %" LWTFMT_ELEMID
2547               " and %" LWTFMT_ELEMID,
2548               tg_id, layer_id, schema_name, table_name,
2549               col_name, face_right, face_left);
2550       return 0;
2551     }
2552   }
2553 
2554   return 1;
2555 }
2556 
2557 static int
cb_checkTopoGeomRemNode(const LWT_BE_TOPOLOGY * topo,LWT_ELEMID rem_node,LWT_ELEMID edge1,LWT_ELEMID edge2)2558 cb_checkTopoGeomRemNode ( const LWT_BE_TOPOLOGY* topo,
2559                           LWT_ELEMID rem_node, LWT_ELEMID edge1, LWT_ELEMID edge2 )
2560 {
2561   MemoryContext oldcontext = CurrentMemoryContext;
2562   int spi_result;
2563   StringInfoData sqldata;
2564   StringInfo sql = &sqldata;
2565   const char *tg_id, *layer_id;
2566   const char *schema_name, *table_name, *col_name;
2567   HeapTuple row;
2568   TupleDesc tdesc;
2569 
2570   initStringInfo(sql);
2571   appendStringInfo( sql, "SELECT t.* FROM ( SELECT r.topogeo_id, "
2572                     "r.layer_id, l.schema_name, l.table_name, l.feature_column, "
2573                     "array_agg(abs(r.element_id)) as elems FROM topology.layer l "
2574                     " INNER JOIN \"%s\".relation r ON (l.layer_id = r.layer_id) "
2575                     "WHERE l.level = 0 and l.feature_type = 2 "
2576                     "AND l.topology_id = %d"
2577                     " AND abs(r.element_id) = ANY (ARRAY[%" LWTFMT_ELEMID ",%" LWTFMT_ELEMID
2578                     "]::int4[]) group by r.topogeo_id, r.layer_id, l.schema_name, "
2579                     "l.table_name, l.feature_column ) t WHERE NOT t.elems @> ARRAY[%"
2580                     LWTFMT_ELEMID ",%" LWTFMT_ELEMID "]::int4[]",
2581                     topo->name, topo->id,
2582                     edge1, edge2, edge1, edge2 );
2583 
2584   POSTGIS_DEBUGF(1, "cb_checkTopoGeomRemNode query 1: %s", sql->data);
2585 
2586   spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0);
2587   MemoryContextSwitchTo( oldcontext ); /* switch back */
2588   if ( spi_result != SPI_OK_SELECT )
2589   {
2590     cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2591             spi_result, sql->data);
2592     pfree(sqldata.data);
2593     return 0;
2594   }
2595 
2596   if ( SPI_processed )
2597   {
2598     row = SPI_tuptable->vals[0];
2599     tdesc = SPI_tuptable->tupdesc;
2600 
2601     tg_id = SPI_getvalue(row, tdesc, 1);
2602     layer_id = SPI_getvalue(row, tdesc, 2);
2603     schema_name = SPI_getvalue(row, tdesc, 3);
2604     table_name = SPI_getvalue(row, tdesc, 4);
2605     col_name = SPI_getvalue(row, tdesc, 5);
2606 
2607     SPI_freetuptable(SPI_tuptable);
2608 
2609     cberror(topo->be_data, "TopoGeom %s in layer %s "
2610             "(%s.%s.%s) cannot be represented "
2611             "healing edges %" LWTFMT_ELEMID
2612             " and %" LWTFMT_ELEMID,
2613             tg_id, layer_id, schema_name, table_name,
2614             col_name, edge1, edge2);
2615     return 0;
2616   }
2617 
2618   /* TODO: check for TopoGeometry objects being defined by the common
2619    * node, see https://trac.osgeo.org/postgis/ticket/3239 */
2620 
2621   return 1;
2622 }
2623 
2624 static int
cb_updateTopoGeomFaceHeal(const LWT_BE_TOPOLOGY * topo,LWT_ELEMID face1,LWT_ELEMID face2,LWT_ELEMID newface)2625 cb_updateTopoGeomFaceHeal ( const LWT_BE_TOPOLOGY* topo,
2626                             LWT_ELEMID face1, LWT_ELEMID face2, LWT_ELEMID newface )
2627 {
2628   MemoryContext oldcontext = CurrentMemoryContext;
2629   int spi_result;
2630   StringInfoData sqldata;
2631   StringInfo sql = &sqldata;
2632 
2633   POSTGIS_DEBUG(1, "cb_updateTopoGeomFaceHeal enter ");
2634 
2635   /* delete oldfaces (not equal to newface) from the
2636    * set of primitives defining the TopoGeometries found before */
2637 
2638   if ( newface == face1 || newface == face2 )
2639   {
2640     initStringInfo(sql);
2641     /* this query can be optimized */
2642     appendStringInfo( sql, "DELETE FROM \"%s\".relation r "
2643                       "USING topology.layer l WHERE l.level = 0 AND l.feature_type = 3"
2644                       " AND l.topology_id = %d AND l.layer_id = r.layer_id "
2645                       " AND abs(r.element_id) IN ( %" LWTFMT_ELEMID ",%" LWTFMT_ELEMID ")"
2646                       " AND abs(r.element_id) != %" LWTFMT_ELEMID,
2647                       topo->name, topo->id, face1, face2, newface );
2648     POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceHeal query: %s", sql->data);
2649 
2650     spi_result = SPI_execute(sql->data, false, 0);
2651     MemoryContextSwitchTo( oldcontext ); /* switch back */
2652     if ( spi_result != SPI_OK_DELETE )
2653     {
2654       cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2655               spi_result, sql->data);
2656       pfree(sqldata.data);
2657       return 0;
2658     }
2659     if ( SPI_processed ) topo->be_data->data_changed = true;
2660   }
2661   else
2662   {
2663     initStringInfo(sql);
2664     /* delete face1 */
2665     appendStringInfo( sql, "DELETE FROM \"%s\".relation r "
2666                       "USING topology.layer l WHERE l.level = 0 AND l.feature_type = 3"
2667                       " AND l.topology_id = %d AND l.layer_id = r.layer_id "
2668                       " AND abs(r.element_id) = %" LWTFMT_ELEMID,
2669                       topo->name, topo->id, face1 );
2670     POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceHeal query 1: %s", sql->data);
2671 
2672     spi_result = SPI_execute(sql->data, false, 0);
2673     MemoryContextSwitchTo( oldcontext ); /* switch back */
2674     if ( spi_result != SPI_OK_DELETE )
2675     {
2676       cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2677               spi_result, sql->data);
2678       pfree(sqldata.data);
2679       return 0;
2680     }
2681     if ( SPI_processed ) topo->be_data->data_changed = true;
2682 
2683     initStringInfo(sql);
2684     /* update face2 to newface */
2685     appendStringInfo( sql, "UPDATE \"%s\".relation r "
2686                       "SET element_id = %" LWTFMT_ELEMID " FROM topology.layer l "
2687                       "WHERE l.level = 0 AND l.feature_type = 3 AND l.topology_id = %d"
2688                       " AND l.layer_id = r.layer_id AND r.element_id = %" LWTFMT_ELEMID,
2689                       topo->name, newface, topo->id, face2 );
2690     POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceHeal query 2: %s", sql->data);
2691 
2692     spi_result = SPI_execute(sql->data, false, 0);
2693     MemoryContextSwitchTo( oldcontext ); /* switch back */
2694     if ( spi_result != SPI_OK_UPDATE )
2695     {
2696       cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2697               spi_result, sql->data);
2698       pfree(sqldata.data);
2699       return 0;
2700     }
2701     if ( SPI_processed ) topo->be_data->data_changed = true;
2702   }
2703 
2704   return 1;
2705 }
2706 
2707 static int
cb_updateTopoGeomEdgeHeal(const LWT_BE_TOPOLOGY * topo,LWT_ELEMID edge1,LWT_ELEMID edge2,LWT_ELEMID newedge)2708 cb_updateTopoGeomEdgeHeal ( const LWT_BE_TOPOLOGY* topo,
2709                             LWT_ELEMID edge1, LWT_ELEMID edge2, LWT_ELEMID newedge )
2710 {
2711   MemoryContext oldcontext = CurrentMemoryContext;
2712   int spi_result;
2713   StringInfoData sqldata;
2714   StringInfo sql = &sqldata;
2715 
2716   /* delete old edges (not equal to new edge) from the
2717    * set of primitives defining the TopoGeometries found before */
2718 
2719   if ( newedge == edge1 || newedge == edge2 )
2720   {
2721     initStringInfo(sql);
2722     /* this query can be optimized */
2723     appendStringInfo( sql, "DELETE FROM \"%s\".relation r "
2724                       "USING topology.layer l WHERE l.level = 0 AND l.feature_type = 2"
2725                       " AND l.topology_id = %d AND l.layer_id = r.layer_id "
2726                       " AND abs(r.element_id) IN ( %" LWTFMT_ELEMID ",%" LWTFMT_ELEMID ")"
2727                       " AND abs(r.element_id) != %" LWTFMT_ELEMID,
2728                       topo->name, topo->id, edge1, edge2, newedge );
2729     POSTGIS_DEBUGF(1, "cb_updateTopoGeomEdgeHeal query: %s", sql->data);
2730 
2731     spi_result = SPI_execute(sql->data, false, 0);
2732     MemoryContextSwitchTo( oldcontext ); /* switch back */
2733     if ( spi_result != SPI_OK_DELETE )
2734     {
2735       cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2736               spi_result, sql->data);
2737       pfree(sqldata.data);
2738       return 0;
2739     }
2740     if ( SPI_processed ) topo->be_data->data_changed = true;
2741   }
2742   else
2743   {
2744     initStringInfo(sql);
2745     /* delete edge1 */
2746     appendStringInfo( sql, "DELETE FROM \"%s\".relation r "
2747                       "USING topology.layer l WHERE l.level = 0 AND l.feature_type = 2"
2748                       " AND l.topology_id = %d AND l.layer_id = r.layer_id "
2749                       " AND abs(r.element_id) = %" LWTFMT_ELEMID,
2750                       topo->name, topo->id, edge2 );
2751     POSTGIS_DEBUGF(1, "cb_updateTopoGeomEdgeHeal query 1: %s", sql->data);
2752 
2753     spi_result = SPI_execute(sql->data, false, 0);
2754     MemoryContextSwitchTo( oldcontext ); /* switch back */
2755     if ( spi_result != SPI_OK_DELETE )
2756     {
2757       cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2758               spi_result, sql->data);
2759       pfree(sqldata.data);
2760       return 0;
2761     }
2762     if ( SPI_processed ) topo->be_data->data_changed = true;
2763 
2764     initStringInfo(sql);
2765     /* update edge2 to newedge */
2766     appendStringInfo( sql, "UPDATE \"%s\".relation r "
2767                       "SET element_id = %" LWTFMT_ELEMID " *(element_id/%" LWTFMT_ELEMID
2768                       ") FROM topology.layer l "
2769                       "WHERE l.level = 0 AND l.feature_type = 2 AND l.topology_id = %d"
2770                       " AND l.layer_id = r.layer_id AND abs(r.element_id) = %" LWTFMT_ELEMID,
2771                       topo->name, newedge, edge1, topo->id, edge1 );
2772     POSTGIS_DEBUGF(1, "cb_updateTopoGeomEdgeHeal query 2: %s", sql->data);
2773 
2774     spi_result = SPI_execute(sql->data, false, 0);
2775     MemoryContextSwitchTo( oldcontext ); /* switch back */
2776     if ( spi_result != SPI_OK_UPDATE )
2777     {
2778       cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2779               spi_result, sql->data);
2780       pfree(sqldata.data);
2781       return 0;
2782     }
2783     if ( SPI_processed ) topo->be_data->data_changed = true;
2784   }
2785 
2786   return 1;
2787 }
2788 
2789 static LWT_ELEMID
cb_getFaceContainingPoint(const LWT_BE_TOPOLOGY * topo,const LWPOINT * pt)2790 cb_getFaceContainingPoint( const LWT_BE_TOPOLOGY* topo, const LWPOINT* pt )
2791 {
2792   MemoryContext oldcontext = CurrentMemoryContext;
2793   int spi_result;
2794   StringInfoData sqldata;
2795   StringInfo sql = &sqldata;
2796   bool isnull;
2797   Datum dat;
2798   LWT_ELEMID face_id;
2799   GSERIALIZED *pts;
2800   Datum values[1];
2801   Oid argtypes[1];
2802 
2803   initStringInfo(sql);
2804 
2805   pts = geometry_serialize(lwpoint_as_lwgeom(pt));
2806   if ( ! pts )
2807   {
2808     cberror(topo->be_data, "%s:%d: could not serialize query point",
2809             __FILE__, __LINE__);
2810     return -2;
2811   }
2812   /* TODO: call GetFaceGeometry internally, avoiding the round-trip to sql */
2813   appendStringInfo(sql,
2814                    "WITH faces AS ( SELECT face_id FROM \"%s\".face "
2815                    "WHERE mbr && $1 ORDER BY ST_Area(mbr) ASC ) "
2816                    "SELECT face_id FROM faces WHERE _ST_Contains("
2817                    "topology.ST_GetFaceGeometry('%s', face_id), $1)"
2818                    " LIMIT 1",
2819                    topo->name, topo->name);
2820 
2821   values[0] = PointerGetDatum(pts);
2822   argtypes[0] = topo->geometryOID;
2823   spi_result = SPI_execute_with_args(sql->data, 1, argtypes, values, NULL,
2824                                      !topo->be_data->data_changed, 1);
2825   MemoryContextSwitchTo( oldcontext ); /* switch back */
2826   pfree(pts); /* not needed anymore */
2827   if ( spi_result != SPI_OK_SELECT )
2828   {
2829     cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2830             spi_result, sql->data);
2831     pfree(sqldata.data);
2832     return -2;
2833   }
2834   pfree(sqldata.data);
2835 
2836   if ( SPI_processed != 1 )
2837   {
2838     return -1; /* none found */
2839   }
2840 
2841   dat = SPI_getbinval( SPI_tuptable->vals[0],
2842                        SPI_tuptable->tupdesc, 1, &isnull );
2843   if ( isnull )
2844   {
2845     SPI_freetuptable(SPI_tuptable);
2846     cberror(topo->be_data, "corrupted topology: face with NULL face_id");
2847     return -2;
2848   }
2849   face_id = DatumGetInt32(dat);
2850   SPI_freetuptable(SPI_tuptable);
2851   return face_id;
2852 }
2853 
2854 static int
cb_deleteFacesById(const LWT_BE_TOPOLOGY * topo,const LWT_ELEMID * ids,int numelems)2855 cb_deleteFacesById( const LWT_BE_TOPOLOGY* topo,
2856                     const LWT_ELEMID* ids, int numelems )
2857 {
2858   MemoryContext oldcontext = CurrentMemoryContext;
2859   int spi_result, i;
2860   StringInfoData sqldata;
2861   StringInfo sql = &sqldata;
2862 
2863   initStringInfo(sql);
2864   appendStringInfo(sql, "DELETE FROM \"%s\".face WHERE face_id IN (", topo->name);
2865   for (i=0; i<numelems; ++i)
2866   {
2867     appendStringInfo(sql, "%s%" LWTFMT_ELEMID, (i?",":""), ids[i]);
2868   }
2869   appendStringInfoString(sql, ")");
2870 
2871   POSTGIS_DEBUGF(1, "cb_deleteFacesById query: %s", sql->data);
2872 
2873   spi_result = SPI_execute( sql->data, false, 0 );
2874   MemoryContextSwitchTo( oldcontext ); /* switch back */
2875   if ( spi_result != SPI_OK_DELETE )
2876   {
2877     cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2878             spi_result, sql->data);
2879     pfree(sqldata.data);
2880     return -1;
2881   }
2882   pfree(sqldata.data);
2883 
2884   if ( SPI_processed ) topo->be_data->data_changed = true;
2885 
2886   POSTGIS_DEBUGF(1, "cb_deleteFacesById: delete query processed %d rows",
2887                  SPI_processed);
2888 
2889   return SPI_processed;
2890 }
2891 
2892 static int
cb_deleteNodesById(const LWT_BE_TOPOLOGY * topo,const LWT_ELEMID * ids,int numelems)2893 cb_deleteNodesById( const LWT_BE_TOPOLOGY* topo,
2894                     const LWT_ELEMID* ids, int numelems )
2895 {
2896   MemoryContext oldcontext = CurrentMemoryContext;
2897   int spi_result, i;
2898   StringInfoData sqldata;
2899   StringInfo sql = &sqldata;
2900 
2901   initStringInfo(sql);
2902   appendStringInfo(sql, "DELETE FROM \"%s\".node WHERE node_id IN (",
2903                    topo->name);
2904   for (i=0; i<numelems; ++i)
2905   {
2906     appendStringInfo(sql, "%s%" LWTFMT_ELEMID, (i?",":""), ids[i]);
2907   }
2908   appendStringInfoString(sql, ")");
2909 
2910   POSTGIS_DEBUGF(1, "cb_deleteNodesById query: %s", sql->data);
2911 
2912   spi_result = SPI_execute( sql->data, false, 0 );
2913   MemoryContextSwitchTo( oldcontext ); /* switch back */
2914   if ( spi_result != SPI_OK_DELETE )
2915   {
2916     cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2917             spi_result, sql->data);
2918     pfree(sqldata.data);
2919     return -1;
2920   }
2921   pfree(sqldata.data);
2922 
2923   if ( SPI_processed ) topo->be_data->data_changed = true;
2924 
2925   POSTGIS_DEBUGF(1, "cb_deleteNodesById: delete query processed %d rows",
2926                  SPI_processed);
2927 
2928   return SPI_processed;
2929 }
2930 
2931 static LWT_ISO_NODE*
cb_getNodeWithinBox2D(const LWT_BE_TOPOLOGY * topo,const GBOX * box,int * numelems,int fields,int limit)2932 cb_getNodeWithinBox2D ( const LWT_BE_TOPOLOGY* topo, const GBOX* box,
2933                         int* numelems, int fields, int limit )
2934 {
2935   MemoryContext oldcontext = CurrentMemoryContext;
2936   int spi_result;
2937   StringInfoData sqldata;
2938   StringInfo sql = &sqldata;
2939   int i;
2940   int elems_requested = limit;
2941   LWT_ISO_NODE* nodes;
2942   char *hexbox;
2943 
2944   initStringInfo(sql);
2945 
2946   if ( elems_requested == -1 )
2947   {
2948     appendStringInfoString(sql, "SELECT EXISTS ( SELECT 1");
2949   }
2950   else
2951   {
2952     appendStringInfoString(sql, "SELECT ");
2953     addNodeFields(sql, fields);
2954   }
2955   hexbox = _box2d_to_hexwkb(box, topo->srid);
2956   appendStringInfo(sql, " FROM \"%s\".node WHERE geom && '%s'::geometry",
2957                    topo->name, hexbox);
2958   lwfree(hexbox);
2959   if ( elems_requested == -1 )
2960   {
2961     appendStringInfoString(sql, ")");
2962   }
2963   else if ( elems_requested > 0 )
2964   {
2965     appendStringInfo(sql, " LIMIT %d", elems_requested);
2966   }
2967   POSTGIS_DEBUGF(1,"cb_getNodeWithinBox2D: query is: %s", sql->data);
2968   spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, limit >= 0 ? limit : 0);
2969   MemoryContextSwitchTo( oldcontext ); /* switch back */
2970   if ( spi_result != SPI_OK_SELECT )
2971   {
2972     cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
2973     pfree(sqldata.data);
2974     *numelems = -1;
2975     return NULL;
2976   }
2977   pfree(sqldata.data);
2978 
2979   POSTGIS_DEBUGF(1, "cb_getNodeWithinBox2D: edge query "
2980                  "(limited by %d) returned %d rows",
2981                  elems_requested, SPI_processed);
2982   *numelems = SPI_processed;
2983   if ( ! SPI_processed )
2984   {
2985     return NULL;
2986   }
2987 
2988   if ( elems_requested == -1 )
2989   {
2990     /* This was an EXISTS query */
2991     {
2992       Datum dat;
2993       bool isnull, exists;
2994       dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
2995       exists = DatumGetBool(dat);
2996       SPI_freetuptable(SPI_tuptable);
2997       *numelems = exists ? 1 : 0;
2998       POSTGIS_DEBUGF(1, "cb_getNodeWithinBox2D: exists ? %d", *numelems);
2999     }
3000     return NULL;
3001   }
3002 
3003   nodes = palloc( sizeof(LWT_ISO_EDGE) * *numelems );
3004   for ( i=0; i<*numelems; ++i )
3005   {
3006     HeapTuple row = SPI_tuptable->vals[i];
3007     fillNodeFields(&nodes[i], row, SPI_tuptable->tupdesc, fields);
3008   }
3009 
3010   SPI_freetuptable(SPI_tuptable);
3011 
3012   return nodes;
3013 }
3014 
3015 static LWT_ISO_EDGE*
cb_getEdgeWithinBox2D(const LWT_BE_TOPOLOGY * topo,const GBOX * box,int * numelems,int fields,int limit)3016 cb_getEdgeWithinBox2D ( const LWT_BE_TOPOLOGY* topo, const GBOX* box,
3017                         int* numelems, int fields, int limit )
3018 {
3019   MemoryContext oldcontext = CurrentMemoryContext;
3020   int spi_result;
3021   StringInfoData sqldata;
3022   StringInfo sql = &sqldata;
3023   int i;
3024   int elems_requested = limit;
3025   LWT_ISO_EDGE* edges;
3026   char *hexbox;
3027 
3028   initStringInfo(sql);
3029 
3030   if ( elems_requested == -1 )
3031   {
3032     appendStringInfoString(sql, "SELECT EXISTS ( SELECT 1");
3033   }
3034   else
3035   {
3036     appendStringInfoString(sql, "SELECT ");
3037     addEdgeFields(sql, fields, 0);
3038   }
3039   appendStringInfo(sql, " FROM \"%s\".edge", topo->name);
3040 
3041   if ( box )
3042   {
3043     hexbox = _box2d_to_hexwkb(box, topo->srid);
3044     appendStringInfo(sql, " WHERE geom && '%s'::geometry", hexbox);
3045     lwfree(hexbox);
3046   }
3047 
3048   if ( elems_requested == -1 )
3049   {
3050     appendStringInfoString(sql, ")");
3051   }
3052   else if ( elems_requested > 0 )
3053   {
3054     appendStringInfo(sql, " LIMIT %d", elems_requested);
3055   }
3056   POSTGIS_DEBUGF(1,"cb_getEdgeWithinBox2D: query is: %s", sql->data);
3057   spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, limit >= 0 ? limit : 0);
3058   MemoryContextSwitchTo( oldcontext ); /* switch back */
3059   if ( spi_result != SPI_OK_SELECT )
3060   {
3061     cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
3062     pfree(sqldata.data);
3063     *numelems = -1;
3064     return NULL;
3065   }
3066   pfree(sqldata.data);
3067 
3068   POSTGIS_DEBUGF(1, "cb_getEdgeWithinBox2D: edge query "
3069                  "(limited by %d) returned %d rows",
3070                  elems_requested, SPI_processed);
3071   *numelems = SPI_processed;
3072   if ( ! SPI_processed )
3073   {
3074     return NULL;
3075   }
3076 
3077   if ( elems_requested == -1 )
3078   {
3079     /* This was an EXISTS query */
3080     {
3081       Datum dat;
3082       bool isnull, exists;
3083       dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
3084       exists = DatumGetBool(dat);
3085       *numelems = exists ? 1 : 0;
3086       SPI_freetuptable(SPI_tuptable);
3087       POSTGIS_DEBUGF(1, "cb_getEdgeWithinBox2D: exists ? %d", *numelems);
3088     }
3089     return NULL;
3090   }
3091 
3092   edges = palloc( sizeof(LWT_ISO_EDGE) * *numelems );
3093   for ( i=0; i<*numelems; ++i )
3094   {
3095     HeapTuple row = SPI_tuptable->vals[i];
3096     fillEdgeFields(&edges[i], row, SPI_tuptable->tupdesc, fields);
3097   }
3098 
3099   SPI_freetuptable(SPI_tuptable);
3100 
3101   return edges;
3102 }
3103 
3104 static LWT_ISO_FACE*
cb_getFaceWithinBox2D(const LWT_BE_TOPOLOGY * topo,const GBOX * box,int * numelems,int fields,int limit)3105 cb_getFaceWithinBox2D ( const LWT_BE_TOPOLOGY* topo, const GBOX* box,
3106                         int* numelems, int fields, int limit )
3107 {
3108   MemoryContext oldcontext = CurrentMemoryContext;
3109   int spi_result;
3110   StringInfoData sqldata;
3111   StringInfo sql = &sqldata;
3112   int i;
3113   int elems_requested = limit;
3114   LWT_ISO_FACE* faces;
3115   char *hexbox;
3116 
3117   initStringInfo(sql);
3118 
3119   if ( elems_requested == -1 )
3120   {
3121     appendStringInfoString(sql, "SELECT EXISTS ( SELECT 1");
3122   }
3123   else
3124   {
3125     appendStringInfoString(sql, "SELECT ");
3126     addFaceFields(sql, fields);
3127   }
3128   hexbox = _box2d_to_hexwkb(box, topo->srid);
3129   appendStringInfo(sql, " FROM \"%s\".face WHERE mbr && '%s'::geometry",
3130                    topo->name, hexbox);
3131   lwfree(hexbox);
3132   if ( elems_requested == -1 )
3133   {
3134     appendStringInfoString(sql, ")");
3135   }
3136   else if ( elems_requested > 0 )
3137   {
3138     appendStringInfo(sql, " LIMIT %d", elems_requested);
3139   }
3140   POSTGIS_DEBUGF(1,"cb_getFaceWithinBox2D: query is: %s", sql->data);
3141   spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, limit >= 0 ? limit : 0);
3142   MemoryContextSwitchTo( oldcontext ); /* switch back */
3143   if ( spi_result != SPI_OK_SELECT )
3144   {
3145     cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
3146     pfree(sqldata.data);
3147     *numelems = -1;
3148     return NULL;
3149   }
3150   pfree(sqldata.data);
3151 
3152   POSTGIS_DEBUGF(1, "cb_getFaceWithinBox2D: face query "
3153                  "(limited by %d) returned %d rows",
3154                  elems_requested, SPI_processed);
3155   *numelems = SPI_processed;
3156   if ( ! SPI_processed )
3157   {
3158     return NULL;
3159   }
3160 
3161   if ( elems_requested == -1 )
3162   {
3163     /* This was an EXISTS query */
3164     {
3165       Datum dat;
3166       bool isnull, exists;
3167       dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
3168       exists = DatumGetBool(dat);
3169       *numelems = exists ? 1 : 0;
3170       POSTGIS_DEBUGF(1, "cb_getFaceWithinBox2D: exists ? %d", *numelems);
3171     }
3172 
3173     SPI_freetuptable(SPI_tuptable);
3174 
3175     return NULL;
3176   }
3177 
3178   faces = palloc( sizeof(LWT_ISO_EDGE) * *numelems );
3179   for ( i=0; i<*numelems; ++i )
3180   {
3181     HeapTuple row = SPI_tuptable->vals[i];
3182     fillFaceFields(&faces[i], row, SPI_tuptable->tupdesc, fields);
3183   }
3184 
3185   SPI_freetuptable(SPI_tuptable);
3186 
3187   return faces;
3188 }
3189 
3190 
3191 static LWT_BE_CALLBACKS be_callbacks =
3192 {
3193   cb_lastErrorMessage,
3194   NULL, /* createTopology */
3195   cb_loadTopologyByName,
3196   cb_freeTopology,
3197   cb_getNodeById,
3198   cb_getNodeWithinDistance2D,
3199   cb_insertNodes,
3200   cb_getEdgeById,
3201   cb_getEdgeWithinDistance2D,
3202   cb_getNextEdgeId,
3203   cb_insertEdges,
3204   cb_updateEdges,
3205   cb_getFacesById,
3206   cb_getFaceContainingPoint,
3207   cb_updateTopoGeomEdgeSplit,
3208   cb_deleteEdges,
3209   cb_getNodeWithinBox2D,
3210   cb_getEdgeWithinBox2D,
3211   cb_getEdgeByNode,
3212   cb_updateNodes,
3213   cb_updateTopoGeomFaceSplit,
3214   cb_insertFaces,
3215   cb_updateFacesById,
3216   cb_getRingEdges,
3217   cb_updateEdgesById,
3218   cb_getEdgeByFace,
3219   cb_getNodeByFace,
3220   cb_updateNodesById,
3221   cb_deleteFacesById,
3222   cb_topoGetSRID,
3223   cb_topoGetPrecision,
3224   cb_topoHasZ,
3225   cb_deleteNodesById,
3226   cb_checkTopoGeomRemEdge,
3227   cb_updateTopoGeomFaceHeal,
3228   cb_checkTopoGeomRemNode,
3229   cb_updateTopoGeomEdgeHeal,
3230   cb_getFaceWithinBox2D
3231 };
3232 
3233 static void
xact_callback(XactEvent event,void * arg)3234 xact_callback(XactEvent event, void *arg)
3235 {
3236   LWT_BE_DATA* data = (LWT_BE_DATA *)arg;
3237   POSTGIS_DEBUGF(1, "xact_callback called with event %d", event);
3238   data->data_changed = false;
3239 }
3240 
3241 
3242 /*
3243  * Module load callback
3244  */
3245 void _PG_init(void);
3246 void
_PG_init(void)3247 _PG_init(void)
3248 {
3249   MemoryContext old_context;
3250 
3251   /*
3252    * install PostgreSQL handlers for liblwgeom
3253    * NOTE: they may be already in place!
3254    */
3255   pg_install_lwgeom_handlers();
3256 
3257   /* Switch to the top memory context so that the backend interface
3258    * is valid for the whole backend lifetime */
3259   old_context = MemoryContextSwitchTo( TopMemoryContext );
3260 
3261   /* initialize backend data */
3262   be_data.data_changed = false;
3263   be_data.topoLoadFailMessageFlavor = 0;
3264 
3265   /* hook on transaction end to reset data_changed */
3266   RegisterXactCallback(xact_callback, &be_data);
3267 
3268   /* register callbacks against liblwgeom-topo */
3269   be_iface = lwt_CreateBackendIface(&be_data);
3270   lwt_BackendIfaceRegisterCallbacks(be_iface, &be_callbacks);
3271 
3272   /* Switch back to whatever memory context was in place
3273    * at time of _PG_init enter.
3274    * See http://www.postgresql.org/message-id/20150623114125.GD5835@localhost
3275    */
3276   MemoryContextSwitchTo(old_context);
3277 }
3278 
3279 /*
3280  * Module unload callback
3281  */
3282 void _PG_fini(void);
3283 void
_PG_fini(void)3284 _PG_fini(void)
3285 {
3286   elog(NOTICE, "Goodbye from PostGIS Topology %s", POSTGIS_VERSION);
3287 
3288   UnregisterXactCallback(xact_callback, &be_data);
3289   lwt_FreeBackendIface(be_iface);
3290 }
3291 
3292 /*  ST_ModEdgeSplit(atopology, anedge, apoint) */
3293 Datum ST_ModEdgeSplit(PG_FUNCTION_ARGS);
3294 PG_FUNCTION_INFO_V1(ST_ModEdgeSplit);
ST_ModEdgeSplit(PG_FUNCTION_ARGS)3295 Datum ST_ModEdgeSplit(PG_FUNCTION_ARGS)
3296 {
3297   text* toponame_text;
3298   char* toponame;
3299   LWT_ELEMID edge_id;
3300   LWT_ELEMID node_id;
3301   GSERIALIZED *geom;
3302   LWGEOM *lwgeom;
3303   LWPOINT *pt;
3304   LWT_TOPOLOGY *topo;
3305 
3306   if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) )
3307   {
3308     lwpgerror("SQL/MM Spatial exception - null argument");
3309     PG_RETURN_NULL();
3310   }
3311 
3312   toponame_text = PG_GETARG_TEXT_P(0);
3313   toponame = text_to_cstring(toponame_text);
3314   PG_FREE_IF_COPY(toponame_text, 0);
3315 
3316   edge_id = PG_GETARG_INT32(1) ;
3317 
3318   geom = PG_GETARG_GSERIALIZED_P(2);
3319   lwgeom = lwgeom_from_gserialized(geom);
3320   pt = lwgeom_as_lwpoint(lwgeom);
3321   if ( ! pt )
3322   {
3323     lwgeom_free(lwgeom);
3324     PG_FREE_IF_COPY(geom, 2);
3325     lwpgerror("ST_ModEdgeSplit third argument must be a point geometry");
3326     PG_RETURN_NULL();
3327   }
3328 
3329   if ( SPI_OK_CONNECT != SPI_connect() )
3330   {
3331     lwpgerror("Could not connect to SPI");
3332     PG_RETURN_NULL();
3333   }
3334 
3335   topo = lwt_LoadTopology(be_iface, toponame);
3336   pfree(toponame);
3337   if ( ! topo )
3338   {
3339     /* should never reach this point, as lwerror would raise an exception */
3340     SPI_finish();
3341     PG_RETURN_NULL();
3342   }
3343 
3344   POSTGIS_DEBUG(1, "Calling lwt_ModEdgeSplit");
3345   node_id = lwt_ModEdgeSplit(topo, edge_id, pt, 0);
3346   POSTGIS_DEBUG(1, "lwt_ModEdgeSplit returned");
3347   lwgeom_free(lwgeom);
3348   PG_FREE_IF_COPY(geom, 3);
3349   lwt_FreeTopology(topo);
3350 
3351   if ( node_id == -1 )
3352   {
3353     /* should never reach this point, as lwerror would raise an exception */
3354     SPI_finish();
3355     PG_RETURN_NULL();
3356   }
3357 
3358   SPI_finish();
3359   PG_RETURN_INT32(node_id);
3360 }
3361 
3362 /*  ST_NewEdgesSplit(atopology, anedge, apoint) */
3363 Datum ST_NewEdgesSplit(PG_FUNCTION_ARGS);
3364 PG_FUNCTION_INFO_V1(ST_NewEdgesSplit);
ST_NewEdgesSplit(PG_FUNCTION_ARGS)3365 Datum ST_NewEdgesSplit(PG_FUNCTION_ARGS)
3366 {
3367   text* toponame_text;
3368   char* toponame;
3369   LWT_ELEMID edge_id;
3370   LWT_ELEMID node_id;
3371   GSERIALIZED *geom;
3372   LWGEOM *lwgeom;
3373   LWPOINT *pt;
3374   LWT_TOPOLOGY *topo;
3375 
3376   if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) )
3377   {
3378     lwpgerror("SQL/MM Spatial exception - null argument");
3379     PG_RETURN_NULL();
3380   }
3381 
3382   toponame_text = PG_GETARG_TEXT_P(0);
3383   toponame = text_to_cstring(toponame_text);
3384   PG_FREE_IF_COPY(toponame_text, 0);
3385 
3386   edge_id = PG_GETARG_INT32(1) ;
3387 
3388   geom = PG_GETARG_GSERIALIZED_P(2);
3389   lwgeom = lwgeom_from_gserialized(geom);
3390   pt = lwgeom_as_lwpoint(lwgeom);
3391   if ( ! pt )
3392   {
3393     lwgeom_free(lwgeom);
3394     PG_FREE_IF_COPY(geom, 2);
3395     lwpgerror("ST_NewEdgesSplit third argument must be a point geometry");
3396     PG_RETURN_NULL();
3397   }
3398 
3399   if ( SPI_OK_CONNECT != SPI_connect() )
3400   {
3401     lwpgerror("Could not connect to SPI");
3402     PG_RETURN_NULL();
3403   }
3404 
3405   topo = lwt_LoadTopology(be_iface, toponame);
3406   pfree(toponame);
3407   if ( ! topo )
3408   {
3409     /* should never reach this point, as lwerror would raise an exception */
3410     SPI_finish();
3411     PG_RETURN_NULL();
3412   }
3413 
3414   POSTGIS_DEBUG(1, "Calling lwt_NewEdgesSplit");
3415   node_id = lwt_NewEdgesSplit(topo, edge_id, pt, 0);
3416   POSTGIS_DEBUG(1, "lwt_NewEdgesSplit returned");
3417   lwgeom_free(lwgeom);
3418   PG_FREE_IF_COPY(geom, 3);
3419   lwt_FreeTopology(topo);
3420 
3421   if ( node_id == -1 )
3422   {
3423     /* should never reach this point, as lwerror would raise an exception */
3424     SPI_finish();
3425     PG_RETURN_NULL();
3426   }
3427 
3428   SPI_finish();
3429   PG_RETURN_INT32(node_id);
3430 }
3431 
3432 /*  ST_AddIsoNode(atopology, aface, apoint) */
3433 Datum ST_AddIsoNode(PG_FUNCTION_ARGS);
3434 PG_FUNCTION_INFO_V1(ST_AddIsoNode);
ST_AddIsoNode(PG_FUNCTION_ARGS)3435 Datum ST_AddIsoNode(PG_FUNCTION_ARGS)
3436 {
3437   text* toponame_text;
3438   char* toponame;
3439   LWT_ELEMID containing_face;
3440   LWT_ELEMID node_id;
3441   GSERIALIZED *geom;
3442   LWGEOM *lwgeom;
3443   LWPOINT *pt;
3444   LWT_TOPOLOGY *topo;
3445 
3446   if ( PG_ARGISNULL(0) || PG_ARGISNULL(2) )
3447   {
3448     lwpgerror("SQL/MM Spatial exception - null argument");
3449     PG_RETURN_NULL();
3450   }
3451 
3452   toponame_text = PG_GETARG_TEXT_P(0);
3453   toponame = text_to_cstring(toponame_text);
3454   PG_FREE_IF_COPY(toponame_text, 0);
3455 
3456   if ( PG_ARGISNULL(1) ) containing_face = -1;
3457   else
3458   {
3459     containing_face = PG_GETARG_INT32(1);
3460     if ( containing_face < 0 )
3461     {
3462       lwpgerror("SQL/MM Spatial exception - not within face");
3463       PG_RETURN_NULL();
3464     }
3465   }
3466 
3467   geom = PG_GETARG_GSERIALIZED_P(2);
3468   lwgeom = lwgeom_from_gserialized(geom);
3469   pt = lwgeom_as_lwpoint(lwgeom);
3470   if ( ! pt )
3471   {
3472     lwgeom_free(lwgeom);
3473     PG_FREE_IF_COPY(geom, 2);
3474     lwpgerror("SQL/MM Spatial exception - invalid point");
3475     PG_RETURN_NULL();
3476   }
3477   if ( lwpoint_is_empty(pt) )
3478   {
3479     lwgeom_free(lwgeom);
3480     PG_FREE_IF_COPY(geom, 2);
3481     lwpgerror("SQL/MM Spatial exception - empty point");
3482     PG_RETURN_NULL();
3483   }
3484 
3485   if ( SPI_OK_CONNECT != SPI_connect() )
3486   {
3487     lwpgerror("Could not connect to SPI");
3488     PG_RETURN_NULL();
3489   }
3490 
3491   topo = lwt_LoadTopology(be_iface, toponame);
3492   pfree(toponame);
3493   if ( ! topo )
3494   {
3495     /* should never reach this point, as lwerror would raise an exception */
3496     SPI_finish();
3497     PG_RETURN_NULL();
3498   }
3499 
3500   POSTGIS_DEBUG(1, "Calling lwt_AddIsoNode");
3501   node_id = lwt_AddIsoNode(topo, containing_face, pt, 0);
3502   POSTGIS_DEBUG(1, "lwt_AddIsoNode returned");
3503   lwgeom_free(lwgeom);
3504   PG_FREE_IF_COPY(geom, 2);
3505   lwt_FreeTopology(topo);
3506 
3507   if ( node_id == -1 )
3508   {
3509     /* should never reach this point, as lwerror would raise an exception */
3510     SPI_finish();
3511     PG_RETURN_NULL();
3512   }
3513 
3514   SPI_finish();
3515   PG_RETURN_INT32(node_id);
3516 }
3517 
3518 /*  ST_AddIsoEdge(atopology, anode, anothernode, acurve) */
3519 Datum ST_AddIsoEdge(PG_FUNCTION_ARGS);
3520 PG_FUNCTION_INFO_V1(ST_AddIsoEdge);
ST_AddIsoEdge(PG_FUNCTION_ARGS)3521 Datum ST_AddIsoEdge(PG_FUNCTION_ARGS)
3522 {
3523   text* toponame_text;
3524   char* toponame;
3525   LWT_ELEMID edge_id;
3526   LWT_ELEMID start_node, end_node;
3527   GSERIALIZED *geom;
3528   LWGEOM *lwgeom;
3529   LWLINE *curve;
3530   LWT_TOPOLOGY *topo;
3531 
3532   if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) ||
3533        PG_ARGISNULL(2) || PG_ARGISNULL(3) )
3534   {
3535     lwpgerror("SQL/MM Spatial exception - null argument");
3536     PG_RETURN_NULL();
3537   }
3538 
3539   toponame_text = PG_GETARG_TEXT_P(0);
3540   toponame = text_to_cstring(toponame_text);
3541   PG_FREE_IF_COPY(toponame_text, 0);
3542 
3543   start_node = PG_GETARG_INT32(1);
3544   end_node = PG_GETARG_INT32(2);
3545 
3546   if ( start_node == end_node )
3547   {
3548     lwpgerror("Closed edges would not be isolated, try ST_AddEdgeNewFaces");
3549     PG_RETURN_NULL();
3550   }
3551 
3552   geom = PG_GETARG_GSERIALIZED_P(3);
3553   lwgeom = lwgeom_from_gserialized(geom);
3554   curve = lwgeom_as_lwline(lwgeom);
3555   if ( ! curve )
3556   {
3557     lwgeom_free(lwgeom);
3558     PG_FREE_IF_COPY(geom, 3);
3559     lwpgerror("SQL/MM Spatial exception - invalid curve");
3560     PG_RETURN_NULL();
3561   }
3562 
3563   if ( SPI_OK_CONNECT != SPI_connect() )
3564   {
3565     lwpgerror("Could not connect to SPI");
3566     PG_RETURN_NULL();
3567   }
3568 
3569   topo = lwt_LoadTopology(be_iface, toponame);
3570   pfree(toponame);
3571   if ( ! topo )
3572   {
3573     /* should never reach this point, as lwerror would raise an exception */
3574     SPI_finish();
3575     PG_RETURN_NULL();
3576   }
3577 
3578   POSTGIS_DEBUG(1, "Calling lwt_AddIsoEdge");
3579   edge_id = lwt_AddIsoEdge(topo, start_node, end_node, curve);
3580   POSTGIS_DEBUG(1, "lwt_AddIsoNode returned");
3581   lwgeom_free(lwgeom);
3582   PG_FREE_IF_COPY(geom, 3);
3583   lwt_FreeTopology(topo);
3584 
3585   if ( edge_id == -1 )
3586   {
3587     /* should never reach this point, as lwerror would raise an exception */
3588     SPI_finish();
3589     PG_RETURN_NULL();
3590   }
3591 
3592   SPI_finish();
3593   PG_RETURN_INT32(edge_id);
3594 }
3595 
3596 /*  ST_AddEdgeModFace(atopology, snode, enode, line) */
3597 Datum ST_AddEdgeModFace(PG_FUNCTION_ARGS);
3598 PG_FUNCTION_INFO_V1(ST_AddEdgeModFace);
ST_AddEdgeModFace(PG_FUNCTION_ARGS)3599 Datum ST_AddEdgeModFace(PG_FUNCTION_ARGS)
3600 {
3601   text* toponame_text;
3602   char* toponame;
3603   LWT_ELEMID startnode_id, endnode_id;
3604   int edge_id;
3605   GSERIALIZED *geom;
3606   LWGEOM *lwgeom;
3607   LWLINE *line;
3608   LWT_TOPOLOGY *topo;
3609 
3610   if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) || PG_ARGISNULL(3) )
3611   {
3612     lwpgerror("SQL/MM Spatial exception - null argument");
3613     PG_RETURN_NULL();
3614   }
3615 
3616   toponame_text = PG_GETARG_TEXT_P(0);
3617   toponame = text_to_cstring(toponame_text);
3618   PG_FREE_IF_COPY(toponame_text, 0);
3619 
3620   startnode_id = PG_GETARG_INT32(1) ;
3621   endnode_id = PG_GETARG_INT32(2) ;
3622 
3623   geom = PG_GETARG_GSERIALIZED_P(3);
3624   lwgeom = lwgeom_from_gserialized(geom);
3625   line = lwgeom_as_lwline(lwgeom);
3626   if ( ! line )
3627   {
3628     lwgeom_free(lwgeom);
3629     PG_FREE_IF_COPY(geom, 3);
3630     lwpgerror("ST_AddEdgeModFace fourth argument must be a line geometry");
3631     PG_RETURN_NULL();
3632   }
3633 
3634   if ( SPI_OK_CONNECT != SPI_connect() )
3635   {
3636     lwpgerror("Could not connect to SPI");
3637     PG_RETURN_NULL();
3638   }
3639 
3640   topo = lwt_LoadTopology(be_iface, toponame);
3641   pfree(toponame);
3642   if ( ! topo )
3643   {
3644     /* should never reach this point, as lwerror would raise an exception */
3645     SPI_finish();
3646     PG_RETURN_NULL();
3647   }
3648 
3649   POSTGIS_DEBUG(1, "Calling lwt_AddEdgeModFace");
3650   edge_id = lwt_AddEdgeModFace(topo, startnode_id, endnode_id, line, 0);
3651   POSTGIS_DEBUG(1, "lwt_AddEdgeModFace returned");
3652   lwgeom_free(lwgeom);
3653   PG_FREE_IF_COPY(geom, 3);
3654   lwt_FreeTopology(topo);
3655 
3656   if ( edge_id == -1 )
3657   {
3658     /* should never reach this point, as lwerror would raise an exception */
3659     SPI_finish();
3660     PG_RETURN_NULL();
3661   }
3662 
3663   SPI_finish();
3664   PG_RETURN_INT32(edge_id);
3665 }
3666 
3667 /*  ST_AddEdgeNewFaces(atopology, snode, enode, line) */
3668 Datum ST_AddEdgeNewFaces(PG_FUNCTION_ARGS);
3669 PG_FUNCTION_INFO_V1(ST_AddEdgeNewFaces);
ST_AddEdgeNewFaces(PG_FUNCTION_ARGS)3670 Datum ST_AddEdgeNewFaces(PG_FUNCTION_ARGS)
3671 {
3672   text* toponame_text;
3673   char* toponame;
3674   LWT_ELEMID startnode_id, endnode_id;
3675   int edge_id;
3676   GSERIALIZED *geom;
3677   LWGEOM *lwgeom;
3678   LWLINE *line;
3679   LWT_TOPOLOGY *topo;
3680 
3681   if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) || PG_ARGISNULL(3) )
3682   {
3683     lwpgerror("SQL/MM Spatial exception - null argument");
3684     PG_RETURN_NULL();
3685   }
3686 
3687   toponame_text = PG_GETARG_TEXT_P(0);
3688   toponame = text_to_cstring(toponame_text);
3689   PG_FREE_IF_COPY(toponame_text, 0);
3690 
3691   startnode_id = PG_GETARG_INT32(1) ;
3692   endnode_id = PG_GETARG_INT32(2) ;
3693 
3694   geom = PG_GETARG_GSERIALIZED_P(3);
3695   lwgeom = lwgeom_from_gserialized(geom);
3696   line = lwgeom_as_lwline(lwgeom);
3697   if ( ! line )
3698   {
3699     lwgeom_free(lwgeom);
3700     PG_FREE_IF_COPY(geom, 3);
3701     lwpgerror("ST_AddEdgeModFace fourth argument must be a line geometry");
3702     PG_RETURN_NULL();
3703   }
3704 
3705   if ( SPI_OK_CONNECT != SPI_connect() )
3706   {
3707     lwpgerror("Could not connect to SPI");
3708     PG_RETURN_NULL();
3709   }
3710 
3711   topo = lwt_LoadTopology(be_iface, toponame);
3712   pfree(toponame);
3713   if ( ! topo )
3714   {
3715     /* should never reach this point, as lwerror would raise an exception */
3716     SPI_finish();
3717     PG_RETURN_NULL();
3718   }
3719 
3720   POSTGIS_DEBUG(1, "Calling lwt_AddEdgeNewFaces");
3721   edge_id = lwt_AddEdgeNewFaces(topo, startnode_id, endnode_id, line, 0);
3722   POSTGIS_DEBUG(1, "lwt_AddEdgeNewFaces returned");
3723   lwgeom_free(lwgeom);
3724   PG_FREE_IF_COPY(geom, 3);
3725   lwt_FreeTopology(topo);
3726 
3727   if ( edge_id == -1 )
3728   {
3729     /* should never reach this point, as lwerror would raise an exception */
3730     SPI_finish();
3731     PG_RETURN_NULL();
3732   }
3733 
3734   SPI_finish();
3735   PG_RETURN_INT32(edge_id);
3736 }
3737 
3738 /* ST_GetFaceGeometry(atopology, aface) */
3739 Datum ST_GetFaceGeometry(PG_FUNCTION_ARGS);
3740 PG_FUNCTION_INFO_V1(ST_GetFaceGeometry);
ST_GetFaceGeometry(PG_FUNCTION_ARGS)3741 Datum ST_GetFaceGeometry(PG_FUNCTION_ARGS)
3742 {
3743   text* toponame_text;
3744   char* toponame;
3745   LWT_ELEMID face_id;
3746   LWGEOM *lwgeom;
3747   LWT_TOPOLOGY *topo;
3748   GSERIALIZED *geom;
3749   MemoryContext old_context;
3750 
3751   if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) )
3752   {
3753     lwpgerror("SQL/MM Spatial exception - null argument");
3754     PG_RETURN_NULL();
3755   }
3756 
3757   toponame_text = PG_GETARG_TEXT_P(0);
3758   toponame = text_to_cstring(toponame_text);
3759   PG_FREE_IF_COPY(toponame_text, 0);
3760 
3761   face_id = PG_GETARG_INT32(1) ;
3762 
3763   if ( SPI_OK_CONNECT != SPI_connect() )
3764   {
3765     lwpgerror("Could not connect to SPI");
3766     PG_RETURN_NULL();
3767   }
3768 
3769   topo = lwt_LoadTopology(be_iface, toponame);
3770   pfree(toponame);
3771   if ( ! topo )
3772   {
3773     /* should never reach this point, as lwerror would raise an exception */
3774     SPI_finish();
3775     PG_RETURN_NULL();
3776   }
3777 
3778   POSTGIS_DEBUG(1, "Calling lwt_GetFaceGeometry");
3779   lwgeom = lwt_GetFaceGeometry(topo, face_id);
3780   POSTGIS_DEBUG(1, "lwt_GetFaceGeometry returned");
3781   lwt_FreeTopology(topo);
3782 
3783   if ( lwgeom == NULL )
3784   {
3785     /* should never reach this point, as lwerror would raise an exception */
3786     SPI_finish();
3787     PG_RETURN_NULL();
3788   }
3789 
3790   /* Serialize in upper memory context (outside of SPI) */
3791   /* TODO: use a narrower context to switch to */
3792   old_context = MemoryContextSwitchTo( TopMemoryContext );
3793   geom = geometry_serialize(lwgeom);
3794   MemoryContextSwitchTo(old_context);
3795 
3796   SPI_finish();
3797 
3798   PG_RETURN_POINTER(geom);
3799 }
3800 
3801 typedef struct FACEEDGESSTATE
3802 {
3803   LWT_ELEMID *elems;
3804   int nelems;
3805   int curr;
3806 }
3807 FACEEDGESSTATE;
3808 
3809 /* ST_GetFaceEdges(atopology, aface) */
3810 Datum ST_GetFaceEdges(PG_FUNCTION_ARGS);
3811 PG_FUNCTION_INFO_V1(ST_GetFaceEdges);
ST_GetFaceEdges(PG_FUNCTION_ARGS)3812 Datum ST_GetFaceEdges(PG_FUNCTION_ARGS)
3813 {
3814   text* toponame_text;
3815   char* toponame;
3816   LWT_ELEMID face_id;
3817   int nelems;
3818   LWT_ELEMID *elems;
3819   LWT_TOPOLOGY *topo;
3820   FuncCallContext *funcctx;
3821   MemoryContext oldcontext, newcontext;
3822   TupleDesc tupdesc;
3823   HeapTuple tuple;
3824   AttInMetadata *attinmeta;
3825   FACEEDGESSTATE *state;
3826   char buf[64];
3827   char *values[2];
3828   Datum result;
3829 
3830   values[0] = buf;
3831   values[1] = &(buf[32]);
3832 
3833   if (SRF_IS_FIRSTCALL())
3834   {
3835 
3836     POSTGIS_DEBUG(1, "ST_GetFaceEdges first call");
3837     funcctx = SRF_FIRSTCALL_INIT();
3838     newcontext = funcctx->multi_call_memory_ctx;
3839 
3840     if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) )
3841     {
3842       lwpgerror("SQL/MM Spatial exception - null argument");
3843       PG_RETURN_NULL();
3844     }
3845 
3846     toponame_text = PG_GETARG_TEXT_P(0);
3847     toponame = text_to_cstring(toponame_text);
3848     PG_FREE_IF_COPY(toponame_text, 0);
3849 
3850     face_id = PG_GETARG_INT32(1) ;
3851 
3852     if ( SPI_OK_CONNECT != SPI_connect() )
3853     {
3854       lwpgerror("Could not connect to SPI");
3855       PG_RETURN_NULL();
3856     }
3857 
3858     topo = lwt_LoadTopology(be_iface, toponame);
3859     oldcontext = MemoryContextSwitchTo( newcontext );
3860     pfree(toponame);
3861     if ( ! topo )
3862     {
3863       /* should never reach this point, as lwerror would raise an exception */
3864       SPI_finish();
3865       PG_RETURN_NULL();
3866     }
3867 
3868     POSTGIS_DEBUG(1, "Calling lwt_GetFaceEdges");
3869     nelems = lwt_GetFaceEdges(topo, face_id, &elems);
3870     POSTGIS_DEBUGF(1, "lwt_GetFaceEdges returned %d", nelems);
3871     lwt_FreeTopology(topo);
3872 
3873     if ( nelems < 0 )
3874     {
3875       /* should never reach this point, as lwerror would raise an exception */
3876       SPI_finish();
3877       PG_RETURN_NULL();
3878     }
3879 
3880     state = lwalloc(sizeof(FACEEDGESSTATE));
3881     state->elems = elems;
3882     state->nelems = nelems;
3883     state->curr = 0;
3884     funcctx->user_fctx = state;
3885 
3886     /*
3887      * Build a tuple description for a
3888      * getfaceedges_returntype tuple
3889      */
3890     tupdesc = RelationNameGetTupleDesc("topology.getfaceedges_returntype");
3891 
3892     /*
3893      * generate attribute metadata needed later to produce
3894      * tuples from raw C strings
3895      */
3896     attinmeta = TupleDescGetAttInMetadata(tupdesc);
3897     funcctx->attinmeta = attinmeta;
3898 
3899     POSTGIS_DEBUG(1, "lwt_GetFaceEdges calling SPI_finish");
3900 
3901     MemoryContextSwitchTo(oldcontext);
3902 
3903     SPI_finish();
3904   }
3905 
3906   /* stuff done on every call of the function */
3907   funcctx = SRF_PERCALL_SETUP();
3908 
3909   /* get state */
3910   state = funcctx->user_fctx;
3911 
3912   if ( state->curr == state->nelems )
3913   {
3914     SRF_RETURN_DONE(funcctx);
3915   }
3916 
3917   if ( snprintf(values[0], 32, "%d", state->curr+1) >= 32 )
3918   {
3919     lwerror("Face edge sequence number does not fit 32 chars ?!: %d",
3920             state->curr+1);
3921   }
3922   if ( snprintf(values[1], 32, "%" LWTFMT_ELEMID,
3923                 state->elems[state->curr]) >= 32 )
3924   {
3925     lwerror("Signed edge identifier does not fit 32 chars ?!: %"
3926             LWTFMT_ELEMID, state->elems[state->curr]);
3927   }
3928 
3929   POSTGIS_DEBUGF(1, "ST_GetFaceEdges: cur:%d, val0:%s, val1:%s",
3930                  state->curr, values[0], values[1]);
3931 
3932   tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
3933   result = HeapTupleGetDatum(tuple);
3934   state->curr++;
3935 
3936   SRF_RETURN_NEXT(funcctx, result);
3937 }
3938 
3939 /*  ST_ChangeEdgeGeom(atopology, anedge, acurve) */
3940 Datum ST_ChangeEdgeGeom(PG_FUNCTION_ARGS);
3941 PG_FUNCTION_INFO_V1(ST_ChangeEdgeGeom);
ST_ChangeEdgeGeom(PG_FUNCTION_ARGS)3942 Datum ST_ChangeEdgeGeom(PG_FUNCTION_ARGS)
3943 {
3944   text* toponame_text;
3945   char buf[64];
3946   char* toponame;
3947   int ret;
3948   LWT_ELEMID edge_id;
3949   GSERIALIZED *geom;
3950   LWGEOM *lwgeom;
3951   LWLINE *line;
3952   LWT_TOPOLOGY *topo;
3953 
3954   if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) )
3955   {
3956     lwpgerror("SQL/MM Spatial exception - null argument");
3957     PG_RETURN_NULL();
3958   }
3959 
3960   toponame_text = PG_GETARG_TEXT_P(0);
3961   toponame = text_to_cstring(toponame_text);
3962   PG_FREE_IF_COPY(toponame_text, 0);
3963 
3964   edge_id = PG_GETARG_INT32(1) ;
3965 
3966   geom = PG_GETARG_GSERIALIZED_P(2);
3967   lwgeom = lwgeom_from_gserialized(geom);
3968   line = lwgeom_as_lwline(lwgeom);
3969   if ( ! line )
3970   {
3971     lwgeom_free(lwgeom);
3972     PG_FREE_IF_COPY(geom, 2);
3973     lwpgerror("ST_ChangeEdgeGeom third argument must be a line geometry");
3974     PG_RETURN_NULL();
3975   }
3976 
3977   if ( SPI_OK_CONNECT != SPI_connect() )
3978   {
3979     lwpgerror("Could not connect to SPI");
3980     PG_RETURN_NULL();
3981   }
3982 
3983   topo = lwt_LoadTopology(be_iface, toponame);
3984   pfree(toponame);
3985   if ( ! topo )
3986   {
3987     /* should never reach this point, as lwerror would raise an exception */
3988     SPI_finish();
3989     PG_RETURN_NULL();
3990   }
3991 
3992   POSTGIS_DEBUG(1, "Calling lwt_ChangeEdgeGeom");
3993   ret = lwt_ChangeEdgeGeom(topo, edge_id, line);
3994   POSTGIS_DEBUG(1, "lwt_ChangeEdgeGeom returned");
3995   lwgeom_free(lwgeom);
3996   PG_FREE_IF_COPY(geom, 2);
3997   lwt_FreeTopology(topo);
3998 
3999   if ( ret == -1 )
4000   {
4001     /* should never reach this point, as lwerror would raise an exception */
4002     SPI_finish();
4003     PG_RETURN_NULL();
4004   }
4005 
4006   SPI_finish();
4007 
4008   if ( snprintf(buf, 64, "Edge %" LWTFMT_ELEMID " changed", edge_id) >= 64 )
4009   {
4010     buf[63] = '\0';
4011   }
4012   PG_RETURN_TEXT_P(cstring_to_text(buf));
4013 }
4014 
4015 /*  ST_RemoveIsoNode(atopology, anode) */
4016 Datum ST_RemoveIsoNode(PG_FUNCTION_ARGS);
4017 PG_FUNCTION_INFO_V1(ST_RemoveIsoNode);
ST_RemoveIsoNode(PG_FUNCTION_ARGS)4018 Datum ST_RemoveIsoNode(PG_FUNCTION_ARGS)
4019 {
4020   text* toponame_text;
4021   char buf[64];
4022   char* toponame;
4023   int ret;
4024   LWT_ELEMID node_id;
4025   LWT_TOPOLOGY *topo;
4026 
4027   if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) )
4028   {
4029     lwpgerror("SQL/MM Spatial exception - null argument");
4030     PG_RETURN_NULL();
4031   }
4032 
4033   toponame_text = PG_GETARG_TEXT_P(0);
4034   toponame = text_to_cstring(toponame_text);
4035   PG_FREE_IF_COPY(toponame_text, 0);
4036 
4037   node_id = PG_GETARG_INT32(1) ;
4038 
4039   if ( SPI_OK_CONNECT != SPI_connect() )
4040   {
4041     lwpgerror("Could not connect to SPI");
4042     PG_RETURN_NULL();
4043   }
4044 
4045   topo = lwt_LoadTopology(be_iface, toponame);
4046   pfree(toponame);
4047   if ( ! topo )
4048   {
4049     /* should never reach this point, as lwerror would raise an exception */
4050     SPI_finish();
4051     PG_RETURN_NULL();
4052   }
4053 
4054   POSTGIS_DEBUG(1, "Calling lwt_RemoveIsoNode");
4055   ret = lwt_RemoveIsoNode(topo, node_id);
4056   POSTGIS_DEBUG(1, "lwt_RemoveIsoNode returned");
4057   lwt_FreeTopology(topo);
4058 
4059   if ( ret == -1 )
4060   {
4061     /* should never reach this point, as lwerror would raise an exception */
4062     SPI_finish();
4063     PG_RETURN_NULL();
4064   }
4065 
4066   /* TODO: check if any TopoGeometry exists including this point in
4067    * its definition ! */
4068 
4069   SPI_finish();
4070 
4071   if ( snprintf(buf, 64, "Isolated node %" LWTFMT_ELEMID
4072                 " removed", node_id) >= 64 )
4073   {
4074     buf[63] = '\0';
4075   }
4076   PG_RETURN_TEXT_P(cstring_to_text(buf));
4077 }
4078 
4079 /*  ST_RemIsoEdge(atopology, anedge) */
4080 Datum ST_RemIsoEdge(PG_FUNCTION_ARGS);
4081 PG_FUNCTION_INFO_V1(ST_RemIsoEdge);
ST_RemIsoEdge(PG_FUNCTION_ARGS)4082 Datum ST_RemIsoEdge(PG_FUNCTION_ARGS)
4083 {
4084   text* toponame_text;
4085   char buf[64];
4086   char* toponame;
4087   int ret;
4088   LWT_ELEMID node_id;
4089   LWT_TOPOLOGY *topo;
4090 
4091   if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) )
4092   {
4093     lwpgerror("SQL/MM Spatial exception - null argument");
4094     PG_RETURN_NULL();
4095   }
4096 
4097   toponame_text = PG_GETARG_TEXT_P(0);
4098   toponame = text_to_cstring(toponame_text);
4099   PG_FREE_IF_COPY(toponame_text, 0);
4100 
4101   node_id = PG_GETARG_INT32(1) ;
4102 
4103   if ( SPI_OK_CONNECT != SPI_connect() )
4104   {
4105     lwpgerror("Could not connect to SPI");
4106     PG_RETURN_NULL();
4107   }
4108 
4109   topo = lwt_LoadTopology(be_iface, toponame);
4110   pfree(toponame);
4111   if ( ! topo )
4112   {
4113     /* should never reach this point, as lwerror would raise an exception */
4114     SPI_finish();
4115     PG_RETURN_NULL();
4116   }
4117 
4118   POSTGIS_DEBUG(1, "Calling lwt_RemIsoEdge");
4119   ret = lwt_RemIsoEdge(topo, node_id);
4120   POSTGIS_DEBUG(1, "lwt_RemIsoEdge returned");
4121   lwt_FreeTopology(topo);
4122 
4123   if ( ret == -1 )
4124   {
4125     /* should never reach this point, as lwerror would raise an exception */
4126     SPI_finish();
4127     PG_RETURN_NULL();
4128   }
4129 
4130   /* TODO: check if any TopoGeometry exists including this point in
4131    * its definition ! */
4132 
4133   SPI_finish();
4134 
4135   if ( snprintf(buf, 64, "Isolated edge %" LWTFMT_ELEMID
4136                 " removed", node_id) >= 64 )
4137   {
4138     buf[63] = '\0';
4139   }
4140   PG_RETURN_TEXT_P(cstring_to_text(buf));
4141 }
4142 
4143 /*  ST_MoveIsoNode(atopology, anode, apoint) */
4144 Datum ST_MoveIsoNode(PG_FUNCTION_ARGS);
4145 PG_FUNCTION_INFO_V1(ST_MoveIsoNode);
ST_MoveIsoNode(PG_FUNCTION_ARGS)4146 Datum ST_MoveIsoNode(PG_FUNCTION_ARGS)
4147 {
4148   text* toponame_text;
4149   char buf[64];
4150   char* toponame;
4151   int ret;
4152   LWT_ELEMID node_id;
4153   GSERIALIZED *geom;
4154   LWGEOM *lwgeom;
4155   LWPOINT *pt;
4156   LWT_TOPOLOGY *topo;
4157   POINT2D p;
4158 
4159   if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) )
4160   {
4161     lwpgerror("SQL/MM Spatial exception - null argument");
4162     PG_RETURN_NULL();
4163   }
4164 
4165   toponame_text = PG_GETARG_TEXT_P(0);
4166   toponame = text_to_cstring(toponame_text);
4167   PG_FREE_IF_COPY(toponame_text, 0);
4168 
4169   node_id = PG_GETARG_INT32(1) ;
4170 
4171   geom = PG_GETARG_GSERIALIZED_P(2);
4172   lwgeom = lwgeom_from_gserialized(geom);
4173   pt = lwgeom_as_lwpoint(lwgeom);
4174   if ( ! pt )
4175   {
4176     lwgeom_free(lwgeom);
4177     PG_FREE_IF_COPY(geom, 2);
4178     lwpgerror("SQL/MM Spatial exception - invalid point");
4179     PG_RETURN_NULL();
4180   }
4181 
4182   if ( ! getPoint2d_p(pt->point, 0, &p) )
4183   {
4184     /* Do not let empty points in, see
4185      * https://trac.osgeo.org/postgis/ticket/3234
4186      */
4187     lwpgerror("SQL/MM Spatial exception - empty point");
4188     PG_RETURN_NULL();
4189   }
4190 
4191   /* TODO: check point for NaN values ? */
4192 
4193   if ( SPI_OK_CONNECT != SPI_connect() )
4194   {
4195     lwpgerror("Could not connect to SPI");
4196     PG_RETURN_NULL();
4197   }
4198 
4199   topo = lwt_LoadTopology(be_iface, toponame);
4200   pfree(toponame);
4201   if ( ! topo )
4202   {
4203     /* should never reach this point, as lwerror would raise an exception */
4204     SPI_finish();
4205     PG_RETURN_NULL();
4206   }
4207 
4208   POSTGIS_DEBUG(1, "Calling lwt_MoveIsoNode");
4209   ret = lwt_MoveIsoNode(topo, node_id, pt);
4210   POSTGIS_DEBUG(1, "lwt_MoveIsoNode returned");
4211   lwgeom_free(lwgeom);
4212   PG_FREE_IF_COPY(geom, 2);
4213   lwt_FreeTopology(topo);
4214 
4215   if ( ret == -1 )
4216   {
4217     /* should never reach this point, as lwerror would raise an exception */
4218     SPI_finish();
4219     PG_RETURN_NULL();
4220   }
4221 
4222   SPI_finish();
4223 
4224   if ( snprintf(buf, 64, "Isolated Node %" LWTFMT_ELEMID
4225                 " moved to location %g,%g",
4226                 node_id, p.x, p.y) >= 64 )
4227   {
4228     buf[63] = '\0';
4229   }
4230   PG_RETURN_TEXT_P(cstring_to_text(buf));
4231 }
4232 
4233 /*  ST_RemEdgeModFace(atopology, anedge) */
4234 Datum ST_RemEdgeModFace(PG_FUNCTION_ARGS);
4235 PG_FUNCTION_INFO_V1(ST_RemEdgeModFace);
ST_RemEdgeModFace(PG_FUNCTION_ARGS)4236 Datum ST_RemEdgeModFace(PG_FUNCTION_ARGS)
4237 {
4238   text* toponame_text;
4239   char* toponame;
4240   int ret;
4241   LWT_ELEMID node_id;
4242   LWT_TOPOLOGY *topo;
4243 
4244   if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) )
4245   {
4246     lwpgerror("SQL/MM Spatial exception - null argument");
4247     PG_RETURN_NULL();
4248   }
4249 
4250   toponame_text = PG_GETARG_TEXT_P(0);
4251   toponame = text_to_cstring(toponame_text);
4252   PG_FREE_IF_COPY(toponame_text, 0);
4253 
4254   node_id = PG_GETARG_INT32(1) ;
4255 
4256   if ( SPI_OK_CONNECT != SPI_connect() )
4257   {
4258     lwpgerror("Could not connect to SPI");
4259     PG_RETURN_NULL();
4260   }
4261 
4262   topo = lwt_LoadTopology(be_iface, toponame);
4263   pfree(toponame);
4264   if ( ! topo )
4265   {
4266     /* should never reach this point, as lwerror would raise an exception */
4267     SPI_finish();
4268     PG_RETURN_NULL();
4269   }
4270 
4271   POSTGIS_DEBUG(1, "Calling lwt_RemEdgeModFace");
4272   ret = lwt_RemEdgeModFace(topo, node_id);
4273   POSTGIS_DEBUG(1, "lwt_RemEdgeModFace returned");
4274   lwt_FreeTopology(topo);
4275 
4276   if ( ret == -1 )
4277   {
4278     /* should never reach this point, as lwerror would raise an exception */
4279     SPI_finish();
4280     PG_RETURN_NULL();
4281   }
4282 
4283   SPI_finish();
4284 
4285   PG_RETURN_INT32(ret);
4286 }
4287 
4288 /*  ST_RemEdgeNewFace(atopology, anedge) */
4289 Datum ST_RemEdgeNewFace(PG_FUNCTION_ARGS);
4290 PG_FUNCTION_INFO_V1(ST_RemEdgeNewFace);
ST_RemEdgeNewFace(PG_FUNCTION_ARGS)4291 Datum ST_RemEdgeNewFace(PG_FUNCTION_ARGS)
4292 {
4293   text* toponame_text;
4294   char* toponame;
4295   int ret;
4296   LWT_ELEMID node_id;
4297   LWT_TOPOLOGY *topo;
4298 
4299   if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) )
4300   {
4301     lwpgerror("SQL/MM Spatial exception - null argument");
4302     PG_RETURN_NULL();
4303   }
4304 
4305   toponame_text = PG_GETARG_TEXT_P(0);
4306   toponame = text_to_cstring(toponame_text);
4307   PG_FREE_IF_COPY(toponame_text, 0);
4308 
4309   node_id = PG_GETARG_INT32(1) ;
4310 
4311   if ( SPI_OK_CONNECT != SPI_connect() )
4312   {
4313     lwpgerror("Could not connect to SPI");
4314     PG_RETURN_NULL();
4315   }
4316 
4317   topo = lwt_LoadTopology(be_iface, toponame);
4318   pfree(toponame);
4319   if ( ! topo )
4320   {
4321     /* should never reach this point, as lwerror would raise an exception */
4322     SPI_finish();
4323     PG_RETURN_NULL();
4324   }
4325 
4326   POSTGIS_DEBUG(1, "Calling lwt_RemEdgeNewFace");
4327   ret = lwt_RemEdgeNewFace(topo, node_id);
4328   POSTGIS_DEBUG(1, "lwt_RemEdgeNewFace returned");
4329   lwt_FreeTopology(topo);
4330   SPI_finish();
4331 
4332   if ( ret <= 0 )
4333   {
4334     /* error or no face created */
4335     PG_RETURN_NULL();
4336   }
4337 
4338   PG_RETURN_INT32(ret);
4339 }
4340 
4341 /*  ST_ModEdgeHeal(atopology, anedge, anotheredge) */
4342 Datum ST_ModEdgeHeal(PG_FUNCTION_ARGS);
4343 PG_FUNCTION_INFO_V1(ST_ModEdgeHeal);
ST_ModEdgeHeal(PG_FUNCTION_ARGS)4344 Datum ST_ModEdgeHeal(PG_FUNCTION_ARGS)
4345 {
4346   text* toponame_text;
4347   char* toponame;
4348   int ret;
4349   LWT_ELEMID eid1, eid2;
4350   LWT_TOPOLOGY *topo;
4351 
4352   if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) )
4353   {
4354     lwpgerror("SQL/MM Spatial exception - null argument");
4355     PG_RETURN_NULL();
4356   }
4357 
4358   toponame_text = PG_GETARG_TEXT_P(0);
4359   toponame = text_to_cstring(toponame_text);
4360   PG_FREE_IF_COPY(toponame_text, 0);
4361 
4362   eid1 = PG_GETARG_INT32(1) ;
4363   eid2 = PG_GETARG_INT32(2) ;
4364 
4365   if ( SPI_OK_CONNECT != SPI_connect() )
4366   {
4367     lwpgerror("Could not connect to SPI");
4368     PG_RETURN_NULL();
4369   }
4370 
4371   topo = lwt_LoadTopology(be_iface, toponame);
4372   pfree(toponame);
4373   if ( ! topo )
4374   {
4375     /* should never reach this point, as lwerror would raise an exception */
4376     SPI_finish();
4377     PG_RETURN_NULL();
4378   }
4379 
4380   POSTGIS_DEBUG(1, "Calling lwt_ModEdgeHeal");
4381   ret = lwt_ModEdgeHeal(topo, eid1, eid2);
4382   POSTGIS_DEBUG(1, "lwt_ModEdgeHeal returned");
4383   lwt_FreeTopology(topo);
4384   SPI_finish();
4385 
4386   if ( ret <= 0 )
4387   {
4388     /* error, should have sent message already */
4389     PG_RETURN_NULL();
4390   }
4391 
4392   PG_RETURN_INT32(ret);
4393 }
4394 
4395 /*  ST_NewEdgeHeal(atopology, anedge, anotheredge) */
4396 Datum ST_NewEdgeHeal(PG_FUNCTION_ARGS);
4397 PG_FUNCTION_INFO_V1(ST_NewEdgeHeal);
ST_NewEdgeHeal(PG_FUNCTION_ARGS)4398 Datum ST_NewEdgeHeal(PG_FUNCTION_ARGS)
4399 {
4400   text* toponame_text;
4401   char* toponame;
4402   int ret;
4403   LWT_ELEMID eid1, eid2;
4404   LWT_TOPOLOGY *topo;
4405 
4406   if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) )
4407   {
4408     lwpgerror("SQL/MM Spatial exception - null argument");
4409     PG_RETURN_NULL();
4410   }
4411 
4412   toponame_text = PG_GETARG_TEXT_P(0);
4413   toponame = text_to_cstring(toponame_text);
4414   PG_FREE_IF_COPY(toponame_text, 0);
4415 
4416   eid1 = PG_GETARG_INT32(1) ;
4417   eid2 = PG_GETARG_INT32(2) ;
4418 
4419   if ( SPI_OK_CONNECT != SPI_connect() )
4420   {
4421     lwpgerror("Could not connect to SPI");
4422     PG_RETURN_NULL();
4423   }
4424 
4425   topo = lwt_LoadTopology(be_iface, toponame);
4426   pfree(toponame);
4427   if ( ! topo )
4428   {
4429     /* should never reach this point, as lwerror would raise an exception */
4430     SPI_finish();
4431     PG_RETURN_NULL();
4432   }
4433 
4434   POSTGIS_DEBUG(1, "Calling lwt_NewEdgeHeal");
4435   ret = lwt_NewEdgeHeal(topo, eid1, eid2);
4436   POSTGIS_DEBUG(1, "lwt_NewEdgeHeal returned");
4437   lwt_FreeTopology(topo);
4438   SPI_finish();
4439 
4440   if ( ret <= 0 )
4441   {
4442     /* error, should have sent message already */
4443     PG_RETURN_NULL();
4444   }
4445 
4446   PG_RETURN_INT32(ret);
4447 }
4448 
4449 /*  GetNodeByPoint(atopology, point, tolerance) */
4450 Datum GetNodeByPoint(PG_FUNCTION_ARGS);
4451 PG_FUNCTION_INFO_V1(GetNodeByPoint);
GetNodeByPoint(PG_FUNCTION_ARGS)4452 Datum GetNodeByPoint(PG_FUNCTION_ARGS)
4453 {
4454   text* toponame_text;
4455   char* toponame;
4456   double tol;
4457   LWT_ELEMID node_id;
4458   GSERIALIZED *geom;
4459   LWGEOM *lwgeom;
4460   LWPOINT *pt;
4461   LWT_TOPOLOGY *topo;
4462 
4463   toponame_text = PG_GETARG_TEXT_P(0);
4464   toponame = text_to_cstring(toponame_text);
4465   PG_FREE_IF_COPY(toponame_text, 0);
4466 
4467   geom = PG_GETARG_GSERIALIZED_P(1);
4468   lwgeom = lwgeom_from_gserialized(geom);
4469   pt = lwgeom_as_lwpoint(lwgeom);
4470   if ( ! pt )
4471   {
4472     lwgeom_free(lwgeom);
4473     PG_FREE_IF_COPY(geom, 1);
4474     lwpgerror("Node geometry must be a point");
4475     PG_RETURN_NULL();
4476   }
4477 
4478   tol = PG_GETARG_FLOAT8(2);
4479   if ( tol < 0 )
4480   {
4481     PG_FREE_IF_COPY(geom, 1);
4482     lwpgerror("Tolerance must be >=0");
4483     PG_RETURN_NULL();
4484   }
4485 
4486   if ( SPI_OK_CONNECT != SPI_connect() )
4487   {
4488     lwpgerror("Could not connect to SPI");
4489     PG_RETURN_NULL();
4490   }
4491 
4492   topo = lwt_LoadTopology(be_iface, toponame);
4493   pfree(toponame);
4494   if ( ! topo )
4495   {
4496     /* should never reach this point, as lwerror would raise an exception */
4497     SPI_finish();
4498     PG_RETURN_NULL();
4499   }
4500 
4501   POSTGIS_DEBUG(1, "Calling lwt_GetNodeByPoint");
4502   node_id = lwt_GetNodeByPoint(topo, pt, tol);
4503   POSTGIS_DEBUG(1, "lwt_GetNodeByPoint returned");
4504   lwgeom_free(lwgeom);
4505   PG_FREE_IF_COPY(geom, 1);
4506   lwt_FreeTopology(topo);
4507 
4508   if ( node_id == -1 )
4509   {
4510     /* should never reach this point, as lwerror would raise an exception */
4511     SPI_finish();
4512     PG_RETURN_NULL();
4513   }
4514 
4515   SPI_finish();
4516   PG_RETURN_INT32(node_id);
4517 }
4518 
4519 /*  GetEdgeByPoint(atopology, point, tolerance) */
4520 Datum GetEdgeByPoint(PG_FUNCTION_ARGS);
4521 PG_FUNCTION_INFO_V1(GetEdgeByPoint);
GetEdgeByPoint(PG_FUNCTION_ARGS)4522 Datum GetEdgeByPoint(PG_FUNCTION_ARGS)
4523 {
4524   text* toponame_text;
4525   char* toponame;
4526   double tol;
4527   LWT_ELEMID node_id;
4528   GSERIALIZED *geom;
4529   LWGEOM *lwgeom;
4530   LWPOINT *pt;
4531   LWT_TOPOLOGY *topo;
4532 
4533   toponame_text = PG_GETARG_TEXT_P(0);
4534   toponame = text_to_cstring(toponame_text);
4535   PG_FREE_IF_COPY(toponame_text, 0);
4536 
4537   geom = PG_GETARG_GSERIALIZED_P(1);
4538   lwgeom = lwgeom_from_gserialized(geom);
4539   pt = lwgeom_as_lwpoint(lwgeom);
4540   if ( ! pt )
4541   {
4542     lwgeom_free(lwgeom);
4543     PG_FREE_IF_COPY(geom, 1);
4544     lwpgerror("Node geometry must be a point");
4545     PG_RETURN_NULL();
4546   }
4547 
4548   tol = PG_GETARG_FLOAT8(2);
4549   if ( tol < 0 )
4550   {
4551     PG_FREE_IF_COPY(geom, 1);
4552     lwpgerror("Tolerance must be >=0");
4553     PG_RETURN_NULL();
4554   }
4555 
4556   if ( SPI_OK_CONNECT != SPI_connect() )
4557   {
4558     lwpgerror("Could not connect to SPI");
4559     PG_RETURN_NULL();
4560   }
4561 
4562   topo = lwt_LoadTopology(be_iface, toponame);
4563   pfree(toponame);
4564   if ( ! topo )
4565   {
4566     /* should never reach this point, as lwerror would raise an exception */
4567     SPI_finish();
4568     PG_RETURN_NULL();
4569   }
4570 
4571   POSTGIS_DEBUG(1, "Calling lwt_GetEdgeByPoint");
4572   node_id = lwt_GetEdgeByPoint(topo, pt, tol);
4573   POSTGIS_DEBUG(1, "lwt_GetEdgeByPoint returned");
4574   lwgeom_free(lwgeom);
4575   PG_FREE_IF_COPY(geom, 1);
4576   lwt_FreeTopology(topo);
4577 
4578   if ( node_id == -1 )
4579   {
4580     /* should never reach this point, as lwerror would raise an exception */
4581     SPI_finish();
4582     PG_RETURN_NULL();
4583   }
4584 
4585   SPI_finish();
4586   PG_RETURN_INT32(node_id);
4587 }
4588 
4589 /*  GetFaceByPoint(atopology, point, tolerance) */
4590 Datum GetFaceByPoint(PG_FUNCTION_ARGS);
4591 PG_FUNCTION_INFO_V1(GetFaceByPoint);
GetFaceByPoint(PG_FUNCTION_ARGS)4592 Datum GetFaceByPoint(PG_FUNCTION_ARGS)
4593 {
4594   text* toponame_text;
4595   char* toponame;
4596   double tol;
4597   LWT_ELEMID node_id;
4598   GSERIALIZED *geom;
4599   LWGEOM *lwgeom;
4600   LWPOINT *pt;
4601   LWT_TOPOLOGY *topo;
4602 
4603   toponame_text = PG_GETARG_TEXT_P(0);
4604   toponame = text_to_cstring(toponame_text);
4605   PG_FREE_IF_COPY(toponame_text, 0);
4606 
4607   geom = PG_GETARG_GSERIALIZED_P(1);
4608   lwgeom = lwgeom_from_gserialized(geom);
4609   pt = lwgeom_as_lwpoint(lwgeom);
4610   if ( ! pt )
4611   {
4612     lwgeom_free(lwgeom);
4613     PG_FREE_IF_COPY(geom, 1);
4614     lwpgerror("Node geometry must be a point");
4615     PG_RETURN_NULL();
4616   }
4617 
4618   tol = PG_GETARG_FLOAT8(2);
4619   if ( tol < 0 )
4620   {
4621     PG_FREE_IF_COPY(geom, 1);
4622     lwpgerror("Tolerance must be >=0");
4623     PG_RETURN_NULL();
4624   }
4625 
4626   if ( SPI_OK_CONNECT != SPI_connect() )
4627   {
4628     lwpgerror("Could not connect to SPI");
4629     PG_RETURN_NULL();
4630   }
4631 
4632   topo = lwt_LoadTopology(be_iface, toponame);
4633   pfree(toponame);
4634   if ( ! topo )
4635   {
4636     /* should never reach this point, as lwerror would raise an exception */
4637     SPI_finish();
4638     PG_RETURN_NULL();
4639   }
4640 
4641   POSTGIS_DEBUG(1, "Calling lwt_GetFaceByPoint");
4642   node_id = lwt_GetFaceByPoint(topo, pt, tol);
4643   POSTGIS_DEBUG(1, "lwt_GetFaceByPoint returned");
4644   lwgeom_free(lwgeom);
4645   PG_FREE_IF_COPY(geom, 1);
4646   lwt_FreeTopology(topo);
4647 
4648   if ( node_id == -1 )
4649   {
4650     /* should never reach this point, as lwerror would raise an exception */
4651     SPI_finish();
4652     PG_RETURN_NULL();
4653   }
4654 
4655   SPI_finish();
4656   PG_RETURN_INT32(node_id);
4657 }
4658 
4659 /*  TopoGeo_AddPoint(atopology, point, tolerance) */
4660 Datum TopoGeo_AddPoint(PG_FUNCTION_ARGS);
4661 PG_FUNCTION_INFO_V1(TopoGeo_AddPoint);
TopoGeo_AddPoint(PG_FUNCTION_ARGS)4662 Datum TopoGeo_AddPoint(PG_FUNCTION_ARGS)
4663 {
4664   text* toponame_text;
4665   char* toponame;
4666   double tol;
4667   LWT_ELEMID node_id;
4668   GSERIALIZED *geom;
4669   LWGEOM *lwgeom;
4670   LWPOINT *pt;
4671   LWT_TOPOLOGY *topo;
4672 
4673   if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) )
4674   {
4675     lwpgerror("SQL/MM Spatial exception - null argument");
4676     PG_RETURN_NULL();
4677   }
4678 
4679   toponame_text = PG_GETARG_TEXT_P(0);
4680   toponame = text_to_cstring(toponame_text);
4681   PG_FREE_IF_COPY(toponame_text, 0);
4682 
4683   geom = PG_GETARG_GSERIALIZED_P(1);
4684   lwgeom = lwgeom_from_gserialized(geom);
4685   pt = lwgeom_as_lwpoint(lwgeom);
4686   if ( ! pt )
4687   {
4688     {
4689       char buf[32];
4690       _lwtype_upper_name(lwgeom_get_type(lwgeom), buf, 32);
4691       lwgeom_free(lwgeom);
4692       PG_FREE_IF_COPY(geom, 1);
4693       lwpgerror("Invalid geometry type (%s) passed to TopoGeo_AddPoint"
4694                 ", expected POINT", buf );
4695       PG_RETURN_NULL();
4696     }
4697   }
4698 
4699   tol = PG_GETARG_FLOAT8(2);
4700   if ( tol < 0 )
4701   {
4702     PG_FREE_IF_COPY(geom, 1);
4703     lwpgerror("Tolerance must be >=0");
4704     PG_RETURN_NULL();
4705   }
4706 
4707   if ( SPI_OK_CONNECT != SPI_connect() )
4708   {
4709     lwpgerror("Could not connect to SPI");
4710     PG_RETURN_NULL();
4711   }
4712 
4713   {
4714     int pre = be_data.topoLoadFailMessageFlavor;
4715     be_data.topoLoadFailMessageFlavor = 1;
4716     topo = lwt_LoadTopology(be_iface, toponame);
4717     be_data.topoLoadFailMessageFlavor = pre;
4718   }
4719   pfree(toponame);
4720   if ( ! topo )
4721   {
4722     /* should never reach this point, as lwerror would raise an exception */
4723     SPI_finish();
4724     PG_RETURN_NULL();
4725   }
4726 
4727   POSTGIS_DEBUG(1, "Calling lwt_AddPoint");
4728   node_id = lwt_AddPoint(topo, pt, tol);
4729   POSTGIS_DEBUG(1, "lwt_AddPoint returned");
4730   lwgeom_free(lwgeom);
4731   PG_FREE_IF_COPY(geom, 1);
4732   lwt_FreeTopology(topo);
4733 
4734   if ( node_id == -1 )
4735   {
4736     /* should never reach this point, as lwerror would raise an exception */
4737     SPI_finish();
4738     PG_RETURN_NULL();
4739   }
4740 
4741   SPI_finish();
4742   PG_RETURN_INT32(node_id);
4743 }
4744 
4745 /*  TopoGeo_AddLinestring(atopology, point, tolerance) */
4746 Datum TopoGeo_AddLinestring(PG_FUNCTION_ARGS);
4747 PG_FUNCTION_INFO_V1(TopoGeo_AddLinestring);
TopoGeo_AddLinestring(PG_FUNCTION_ARGS)4748 Datum TopoGeo_AddLinestring(PG_FUNCTION_ARGS)
4749 {
4750   text* toponame_text;
4751   char* toponame;
4752   double tol;
4753   LWT_ELEMID *elems;
4754   int nelems;
4755   GSERIALIZED *geom;
4756   LWGEOM *lwgeom;
4757   LWLINE *ln;
4758   LWT_TOPOLOGY *topo;
4759   FuncCallContext *funcctx;
4760   MemoryContext oldcontext, newcontext;
4761   FACEEDGESSTATE *state;
4762   Datum result;
4763   LWT_ELEMID id;
4764 
4765   if (SRF_IS_FIRSTCALL())
4766   {
4767     POSTGIS_DEBUG(1, "TopoGeo_AddLinestring first call");
4768     funcctx = SRF_FIRSTCALL_INIT();
4769     newcontext = funcctx->multi_call_memory_ctx;
4770 
4771     if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) )
4772     {
4773       lwpgerror("SQL/MM Spatial exception - null argument");
4774       PG_RETURN_NULL();
4775     }
4776 
4777     toponame_text = PG_GETARG_TEXT_P(0);
4778     toponame = text_to_cstring(toponame_text);
4779     PG_FREE_IF_COPY(toponame_text, 0);
4780 
4781     geom = PG_GETARG_GSERIALIZED_P(1);
4782     lwgeom = lwgeom_from_gserialized(geom);
4783     ln = lwgeom_as_lwline(lwgeom);
4784     if ( ! ln )
4785     {
4786       {
4787         char buf[32];
4788         _lwtype_upper_name(lwgeom_get_type(lwgeom), buf, 32);
4789         lwgeom_free(lwgeom);
4790         PG_FREE_IF_COPY(geom, 1);
4791         lwpgerror("Invalid geometry type (%s) passed to "
4792                   "TopoGeo_AddLinestring, expected LINESTRING", buf);
4793         PG_RETURN_NULL();
4794       }
4795     }
4796 
4797     tol = PG_GETARG_FLOAT8(2);
4798     if ( tol < 0 )
4799     {
4800       PG_FREE_IF_COPY(geom, 1);
4801       lwpgerror("Tolerance must be >=0");
4802       PG_RETURN_NULL();
4803     }
4804 
4805     if ( SPI_OK_CONNECT != SPI_connect() )
4806     {
4807       lwpgerror("Could not connect to SPI");
4808       PG_RETURN_NULL();
4809     }
4810 
4811     {
4812       int pre = be_data.topoLoadFailMessageFlavor;
4813       be_data.topoLoadFailMessageFlavor = 1;
4814       topo = lwt_LoadTopology(be_iface, toponame);
4815       be_data.topoLoadFailMessageFlavor = pre;
4816     }
4817     oldcontext = MemoryContextSwitchTo( newcontext );
4818     pfree(toponame);
4819     if ( ! topo )
4820     {
4821       /* should never reach this point, as lwerror would raise an exception */
4822       SPI_finish();
4823       PG_RETURN_NULL();
4824     }
4825 
4826     POSTGIS_DEBUG(1, "Calling lwt_AddLine");
4827     elems = lwt_AddLine(topo, ln, tol, &nelems);
4828     POSTGIS_DEBUG(1, "lwt_AddLine returned");
4829     lwgeom_free(lwgeom);
4830     PG_FREE_IF_COPY(geom, 1);
4831     lwt_FreeTopology(topo);
4832 
4833     if ( nelems < 0 )
4834     {
4835       /* should never reach this point, as lwerror would raise an exception */
4836       SPI_finish();
4837       PG_RETURN_NULL();
4838     }
4839 
4840     state = lwalloc(sizeof(FACEEDGESSTATE));
4841     state->elems = elems;
4842     state->nelems = nelems;
4843     state->curr = 0;
4844     funcctx->user_fctx = state;
4845 
4846     POSTGIS_DEBUG(1, "TopoGeo_AddLinestring calling SPI_finish");
4847 
4848     MemoryContextSwitchTo(oldcontext);
4849 
4850     SPI_finish();
4851   }
4852 
4853   POSTGIS_DEBUG(1, "Per-call invocation");
4854 
4855   /* stuff done on every call of the function */
4856   funcctx = SRF_PERCALL_SETUP();
4857 
4858   /* get state */
4859   state = funcctx->user_fctx;
4860 
4861   if ( state->curr == state->nelems )
4862   {
4863     POSTGIS_DEBUG(1, "We're done, cleaning up all");
4864     SRF_RETURN_DONE(funcctx);
4865   }
4866 
4867   id = state->elems[state->curr++];
4868   POSTGIS_DEBUGF(1, "TopoGeo_AddLinestring: cur:%d, val:%" LWTFMT_ELEMID,
4869                  state->curr-1, id);
4870 
4871   result = Int32GetDatum((int32)id);
4872 
4873   SRF_RETURN_NEXT(funcctx, result);
4874 }
4875 
4876 /*  TopoGeo_AddPolygon(atopology, poly, tolerance) */
4877 Datum TopoGeo_AddPolygon(PG_FUNCTION_ARGS);
4878 PG_FUNCTION_INFO_V1(TopoGeo_AddPolygon);
TopoGeo_AddPolygon(PG_FUNCTION_ARGS)4879 Datum TopoGeo_AddPolygon(PG_FUNCTION_ARGS)
4880 {
4881   text* toponame_text;
4882   char* toponame;
4883   double tol;
4884   LWT_ELEMID *elems;
4885   int nelems;
4886   GSERIALIZED *geom;
4887   LWGEOM *lwgeom;
4888   LWPOLY *pol;
4889   LWT_TOPOLOGY *topo;
4890   FuncCallContext *funcctx;
4891   MemoryContext oldcontext, newcontext;
4892   FACEEDGESSTATE *state;
4893   Datum result;
4894   LWT_ELEMID id;
4895 
4896   if (SRF_IS_FIRSTCALL())
4897   {
4898     POSTGIS_DEBUG(1, "TopoGeo_AddPolygon first call");
4899     funcctx = SRF_FIRSTCALL_INIT();
4900     newcontext = funcctx->multi_call_memory_ctx;
4901 
4902     if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) )
4903     {
4904       lwpgerror("SQL/MM Spatial exception - null argument");
4905       PG_RETURN_NULL();
4906     }
4907 
4908     toponame_text = PG_GETARG_TEXT_P(0);
4909     toponame = text_to_cstring(toponame_text);
4910     PG_FREE_IF_COPY(toponame_text, 0);
4911 
4912     geom = PG_GETARG_GSERIALIZED_P(1);
4913     lwgeom = lwgeom_from_gserialized(geom);
4914     pol = lwgeom_as_lwpoly(lwgeom);
4915     if ( ! pol )
4916     {
4917       {
4918         char buf[32];
4919         _lwtype_upper_name(lwgeom_get_type(lwgeom), buf, 32);
4920         lwgeom_free(lwgeom);
4921         PG_FREE_IF_COPY(geom, 1);
4922         lwpgerror("Invalid geometry type (%s) passed to "
4923                   "TopoGeo_AddPolygon, expected POLYGON", buf);
4924         PG_RETURN_NULL();
4925       }
4926     }
4927 
4928     tol = PG_GETARG_FLOAT8(2);
4929     if ( tol < 0 )
4930     {
4931       PG_FREE_IF_COPY(geom, 1);
4932       lwpgerror("Tolerance must be >=0");
4933       PG_RETURN_NULL();
4934     }
4935 
4936     if ( SPI_OK_CONNECT != SPI_connect() )
4937     {
4938       lwpgerror("Could not connect to SPI");
4939       PG_RETURN_NULL();
4940     }
4941 
4942     {
4943       int pre = be_data.topoLoadFailMessageFlavor;
4944       be_data.topoLoadFailMessageFlavor = 1;
4945       topo = lwt_LoadTopology(be_iface, toponame);
4946       be_data.topoLoadFailMessageFlavor = pre;
4947     }
4948     oldcontext = MemoryContextSwitchTo( newcontext );
4949     pfree(toponame);
4950     if ( ! topo )
4951     {
4952       /* should never reach this point, as lwerror would raise an exception */
4953       SPI_finish();
4954       PG_RETURN_NULL();
4955     }
4956 
4957     POSTGIS_DEBUG(1, "Calling lwt_AddPolygon");
4958     elems = lwt_AddPolygon(topo, pol, tol, &nelems);
4959     POSTGIS_DEBUG(1, "lwt_AddPolygon returned");
4960     lwgeom_free(lwgeom);
4961     PG_FREE_IF_COPY(geom, 1);
4962     lwt_FreeTopology(topo);
4963 
4964     if ( nelems < 0 )
4965     {
4966       /* should never reach this point, as lwerror would raise an exception */
4967       SPI_finish();
4968       PG_RETURN_NULL();
4969     }
4970 
4971     state = lwalloc(sizeof(FACEEDGESSTATE));
4972     state->elems = elems;
4973     state->nelems = nelems;
4974     state->curr = 0;
4975     funcctx->user_fctx = state;
4976 
4977     POSTGIS_DEBUG(1, "TopoGeo_AddPolygon calling SPI_finish");
4978 
4979     MemoryContextSwitchTo(oldcontext);
4980 
4981     SPI_finish();
4982   }
4983 
4984   POSTGIS_DEBUG(1, "Per-call invocation");
4985 
4986   /* stuff done on every call of the function */
4987   funcctx = SRF_PERCALL_SETUP();
4988 
4989   /* get state */
4990   state = funcctx->user_fctx;
4991 
4992   if ( state->curr == state->nelems )
4993   {
4994     POSTGIS_DEBUG(1, "We're done, cleaning up all");
4995     SRF_RETURN_DONE(funcctx);
4996   }
4997 
4998   id = state->elems[state->curr++];
4999   POSTGIS_DEBUGF(1, "TopoGeo_AddPolygon: cur:%d, val:%" LWTFMT_ELEMID,
5000                  state->curr-1, id);
5001 
5002   result = Int32GetDatum((int32)id);
5003 
5004   SRF_RETURN_NEXT(funcctx, result);
5005 }
5006