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