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