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