1 /***********************************************************************
2 * pc_access.c
3 *
4 * Accessor/aggregate functions for points and patches in PgSQL.
5 *
6 * PgSQL Pointcloud is free and open source software provided
7 * by the Government of Canada
8 * Copyright (c) 2013 Natural Resources Canada
9 *
10 ***********************************************************************/
11
12 #include "pc_pgsql.h" /* Common PgSQL support for our type */
13 #include "utils/numeric.h"
14 #include "funcapi.h"
15 #include "lib/stringinfo.h"
16 #include "pc_api_internal.h" /* for pcpatch_summary */
17
18 /* cstring array utility functions */
19 const char **array_to_cstring_array(ArrayType *array, int *size);
20 void pc_cstring_array_free(const char **array, int nelems);
21
22 /* General SQL functions */
23 Datum pcpoint_get_value(PG_FUNCTION_ARGS);
24 Datum pcpoint_get_values(PG_FUNCTION_ARGS);
25 Datum pcpatch_from_pcpoint_array(PG_FUNCTION_ARGS);
26 Datum pcpatch_from_float_array(PG_FUNCTION_ARGS);
27 Datum pcpatch_from_pcpatch_array(PG_FUNCTION_ARGS);
28 Datum pcpatch_uncompress(PG_FUNCTION_ARGS);
29 Datum pcpatch_compress(PG_FUNCTION_ARGS);
30 Datum pcpatch_numpoints(PG_FUNCTION_ARGS);
31 Datum pcpatch_pointn(PG_FUNCTION_ARGS);
32 Datum pcpatch_range(PG_FUNCTION_ARGS);
33 Datum pcpatch_pcid(PG_FUNCTION_ARGS);
34 Datum pcpatch_summary(PG_FUNCTION_ARGS);
35 Datum pcpatch_compression(PG_FUNCTION_ARGS);
36 Datum pcpatch_intersects(PG_FUNCTION_ARGS);
37 Datum pcpatch_get_stat(PG_FUNCTION_ARGS);
38 Datum pcpatch_filter(PG_FUNCTION_ARGS);
39 Datum pcpatch_sort(PG_FUNCTION_ARGS);
40 Datum pcpatch_is_sorted(PG_FUNCTION_ARGS);
41 Datum pcpatch_size(PG_FUNCTION_ARGS);
42 Datum pcpoint_size(PG_FUNCTION_ARGS);
43 Datum pcpoint_pcid(PG_FUNCTION_ARGS);
44 Datum pc_version(PG_FUNCTION_ARGS);
45 Datum pc_pgsql_version(PG_FUNCTION_ARGS);
46 Datum pc_libxml2_version(PG_FUNCTION_ARGS);
47 Datum pc_lazperf_enabled(PG_FUNCTION_ARGS);
48
49 /* Generic aggregation functions */
50 Datum pointcloud_agg_transfn(PG_FUNCTION_ARGS);
51 Datum pointcloud_abs_in(PG_FUNCTION_ARGS);
52 Datum pointcloud_abs_out(PG_FUNCTION_ARGS);
53
54 /* Point finalizers */
55 Datum pcpoint_agg_final_pcpatch(PG_FUNCTION_ARGS);
56 Datum pcpoint_agg_final_array(PG_FUNCTION_ARGS);
57
58 /* Patch finalizers */
59 Datum pcpatch_agg_final_array(PG_FUNCTION_ARGS);
60 Datum pcpatch_agg_final_pcpatch(PG_FUNCTION_ARGS);
61
62 /* Deaggregation functions */
63 Datum pcpatch_unnest(PG_FUNCTION_ARGS);
64
65 /**
66 * Read a named dimension from a PCPOINT
67 * PC_Get(point pcpoint, dimname text) returns Numeric
68 */
69 PG_FUNCTION_INFO_V1(pcpoint_get_value);
pcpoint_get_value(PG_FUNCTION_ARGS)70 Datum pcpoint_get_value(PG_FUNCTION_ARGS)
71 {
72 SERIALIZED_POINT *serpt = PG_GETARG_SERPOINT_P(0);
73 text *dim_name = PG_GETARG_TEXT_P(1);
74 char *dim_str;
75 float8 double_result;
76
77 PCSCHEMA *schema = pc_schema_from_pcid(serpt->pcid, fcinfo);
78 PCPOINT *pt = pc_point_deserialize(serpt, schema);
79 if ( ! pt )
80 PG_RETURN_NULL();
81
82 dim_str = text_to_cstring(dim_name);
83 if ( ! pc_point_get_double_by_name(pt, dim_str, &double_result) )
84 {
85 pc_point_free(pt);
86 elog(ERROR, "dimension \"%s\" does not exist in schema", dim_str);
87 }
88 pfree(dim_str);
89 pc_point_free(pt);
90 PG_RETURN_DATUM(DirectFunctionCall1(float8_numeric, Float8GetDatum(double_result)));
91 }
92
93 /**
94 * Returns all the values of a point as a double precision array
95 * PC_Get(point pcpoint) returns Float8[]
96 */
97 PG_FUNCTION_INFO_V1(pcpoint_get_values);
pcpoint_get_values(PG_FUNCTION_ARGS)98 Datum pcpoint_get_values(PG_FUNCTION_ARGS)
99 {
100 SERIALIZED_POINT *serpt;
101 ArrayType *result;
102 PCSCHEMA *schema;
103 PCPOINT *pt;
104 Datum *elems;
105 int i;
106 double *vals;
107
108 serpt = PG_GETARG_SERPOINT_P(0);
109 schema = pc_schema_from_pcid(serpt->pcid, fcinfo);
110 pt = pc_point_deserialize(serpt, schema);
111 if ( ! pt ) PG_RETURN_NULL();
112
113 elems = (Datum * )palloc(schema->ndims * sizeof(Datum) );
114 vals = pc_point_to_double_array(pt);
115 i = schema->ndims;
116 while (i--) elems[i] = Float8GetDatum(vals[i]);
117 pcfree(vals);
118 result = construct_array(elems, schema->ndims, FLOAT8OID,
119 sizeof(float8), FLOAT8PASSBYVAL, 'd');
120
121 pc_point_free(pt);
122 PG_RETURN_ARRAYTYPE_P(result);
123 }
124
125
126 static inline bool
array_get_isnull(const bits8 * nullbitmap,int offset)127 array_get_isnull(const bits8 *nullbitmap, int offset)
128 {
129 if (nullbitmap == NULL)
130 {
131 return false; /* assume not null */
132 }
133 if (nullbitmap[offset / 8] & (1 << (offset % 8)))
134 {
135 return false; /* not null */
136 }
137 return true;
138 }
139
140 static PCPATCH *
141 #if PGSQL_VERSION < 120
pcpatch_from_point_array(ArrayType * array,FunctionCallInfoData * fcinfo)142 pcpatch_from_point_array(ArrayType *array, FunctionCallInfoData *fcinfo)
143 #else
144 pcpatch_from_point_array(ArrayType *array, FunctionCallInfo fcinfo)
145 #endif
146 {
147 int nelems;
148 bits8 *bitmap;
149 size_t offset = 0;
150 int i;
151 uint32 pcid = 0;
152 PCPATCH *pa;
153 PCPOINTLIST *pl;
154 PCSCHEMA *schema = 0;
155
156 /* How many things in our array? */
157 nelems = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array));
158
159 /* PgSQL supplies a bitmap of which array entries are null */
160 bitmap = ARR_NULLBITMAP(array);
161
162 /* Empty array? Null return */
163 if ( nelems == 0 )
164 return NULL;
165
166 /* Make our holder */
167 pl = pc_pointlist_make(nelems);
168
169 offset = 0;
170 bitmap = ARR_NULLBITMAP(array);
171 for ( i = 0; i < nelems; i++ )
172 {
173 /* Only work on non-NULL entries in the array */
174 if ( ! array_get_isnull(bitmap, i) )
175 {
176 SERIALIZED_POINT *serpt = (SERIALIZED_POINT *)(ARR_DATA_PTR(array)+offset);
177 PCPOINT *pt;
178
179 if ( ! schema )
180 {
181 schema = pc_schema_from_pcid(serpt->pcid, fcinfo);
182 }
183
184 if ( ! pcid )
185 {
186 pcid = serpt->pcid;
187 }
188 else if ( pcid != serpt->pcid )
189 {
190 elog(ERROR, "pcpatch_from_point_array: pcid mismatch (%d != %d)", serpt->pcid, pcid);
191 }
192
193 pt = pc_point_deserialize(serpt, schema);
194 if ( ! pt )
195 {
196 elog(ERROR, "pcpatch_from_point_array: point deserialization failed");
197 }
198
199 pc_pointlist_add_point(pl, pt);
200
201 offset += INTALIGN(VARSIZE(serpt));
202 }
203
204 }
205
206 if ( pl->npoints == 0 )
207 return NULL;
208
209 pa = pc_patch_from_pointlist(pl);
210 pc_pointlist_free(pl);
211 return pa;
212 }
213
214
215 static PCPATCH *
216 #if PGSQL_VERSION < 120
pcpatch_from_patch_array(ArrayType * array,FunctionCallInfoData * fcinfo)217 pcpatch_from_patch_array(ArrayType *array, FunctionCallInfoData *fcinfo)
218 #else
219 pcpatch_from_patch_array(ArrayType *array, FunctionCallInfo fcinfo)
220 #endif
221 {
222 int nelems;
223 bits8 *bitmap;
224 size_t offset = 0;
225 int i;
226 uint32 pcid = 0;
227 PCPATCH *pa;
228 PCPATCH **palist;
229 int numpatches = 0;
230 PCSCHEMA *schema = 0;
231
232 /* How many things in our array? */
233 nelems = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array));
234
235 /* PgSQL supplies a bitmap of which array entries are null */
236 bitmap = ARR_NULLBITMAP(array);
237
238 /* Empty array? Null return */
239 if ( nelems == 0 )
240 return NULL;
241
242 /* Make our temporary list of patches */
243 palist = pcalloc(nelems*sizeof(PCPATCH*));
244
245 /* Read the patches out of the array and deserialize */
246 offset = 0;
247 bitmap = ARR_NULLBITMAP(array);
248 for ( i = 0; i < nelems; i++ )
249 {
250 /* Only work on non-NULL entries in the array */
251 if ( ! array_get_isnull(bitmap, i) )
252 {
253 SERIALIZED_PATCH *serpatch = (SERIALIZED_PATCH *)(ARR_DATA_PTR(array)+offset);
254
255 if ( ! schema )
256 {
257 schema = pc_schema_from_pcid(serpatch->pcid, fcinfo);
258 }
259
260 if ( ! pcid )
261 {
262 pcid = serpatch->pcid;
263 }
264 else if ( pcid != serpatch->pcid )
265 {
266 elog(ERROR, "pcpatch_from_patch_array: pcid mismatch (%d != %d)", serpatch->pcid, pcid);
267 }
268
269 pa = pc_patch_deserialize(serpatch, schema);
270 if ( ! pa )
271 {
272 elog(ERROR, "pcpatch_from_patch_array: patch deserialization failed");
273 }
274
275 palist[numpatches++] = pa;
276
277 offset += INTALIGN(VARSIZE(serpatch));
278 }
279
280 }
281
282 /* Can't do anything w/ NULL */
283 if ( numpatches == 0 )
284 return NULL;
285
286 /* Pass to the lib to build the output patch from the list */
287 pa = pc_patch_from_patchlist(palist, numpatches);
288
289 /* Free the temporary patch list */
290 for ( i = 0; i < numpatches; i++ )
291 {
292 pc_patch_free(palist[i]);
293 }
294 pcfree(palist);
295
296 return pa;
297 }
298
299
300 PG_FUNCTION_INFO_V1(pcpatch_from_pcpatch_array);
pcpatch_from_pcpatch_array(PG_FUNCTION_ARGS)301 Datum pcpatch_from_pcpatch_array(PG_FUNCTION_ARGS)
302 {
303 ArrayType *array;
304 PCPATCH *pa;
305 SERIALIZED_PATCH *serpa;
306
307 if ( PG_ARGISNULL(0) )
308 PG_RETURN_NULL();
309
310 array = DatumGetArrayTypeP(PG_GETARG_DATUM(0));
311 pa = pcpatch_from_patch_array(array, fcinfo);
312 if ( ! pa )
313 PG_RETURN_NULL();
314
315 serpa = pc_patch_serialize(pa, NULL);
316 pc_patch_free(pa);
317 PG_RETURN_POINTER(serpa);
318 }
319
320 PG_FUNCTION_INFO_V1(pcpatch_from_pcpoint_array);
pcpatch_from_pcpoint_array(PG_FUNCTION_ARGS)321 Datum pcpatch_from_pcpoint_array(PG_FUNCTION_ARGS)
322 {
323 ArrayType *array;
324 PCPATCH *pa;
325 SERIALIZED_PATCH *serpa;
326
327 if ( PG_ARGISNULL(0) )
328 PG_RETURN_NULL();
329
330 array = DatumGetArrayTypeP(PG_GETARG_DATUM(0));
331 pa = pcpatch_from_point_array(array, fcinfo);
332 if ( ! pa )
333 PG_RETURN_NULL();
334
335 serpa = pc_patch_serialize(pa, NULL);
336 pc_patch_free(pa);
337 PG_RETURN_POINTER(serpa);
338 }
339
340
341 PG_FUNCTION_INFO_V1(pcpatch_from_float_array);
pcpatch_from_float_array(PG_FUNCTION_ARGS)342 Datum pcpatch_from_float_array(PG_FUNCTION_ARGS)
343 {
344 int i, ndims, nelems, npoints;
345 float8 *vals;
346 PCPATCH *pa;
347 PCPOINTLIST *pl;
348 SERIALIZED_PATCH *serpa;
349 uint32 pcid = PG_GETARG_INT32(0);
350 ArrayType *arrptr = PG_GETARG_ARRAYTYPE_P(1);
351 PCSCHEMA *schema = pc_schema_from_pcid(pcid, fcinfo);
352
353 if ( ! schema )
354 elog(ERROR, "unable to load schema for pcid = %d", pcid);
355
356 if ( ARR_ELEMTYPE(arrptr) != FLOAT8OID )
357 elog(ERROR, "array must be of float8[]");
358
359 if ( ARR_NDIM(arrptr) != 1 )
360 elog(ERROR, "float8[] must have one dimension");
361
362 if ( ARR_HASNULL(arrptr) )
363 elog(ERROR, "float8[] must not have null elements");
364
365 ndims = schema->ndims;
366 nelems = ARR_DIMS(arrptr)[0];
367
368 if ( nelems % ndims != 0 ) {
369 elog(ERROR, "array dimensions do not match schema dimensions of pcid = %d", pcid);
370 }
371
372 npoints = nelems / ndims;
373
374 vals = (float8*) ARR_DATA_PTR(arrptr);
375 pl = pc_pointlist_make(nelems);
376
377 for ( i = 0; i < npoints; ++i ) {
378
379 PCPOINT* pt = pc_point_from_double_array(schema, vals, i * ndims, ndims);
380 pc_pointlist_add_point(pl, pt);
381 }
382
383 pa = pc_patch_from_pointlist(pl);
384 pc_pointlist_free(pl);
385 if ( ! pa )
386 PG_RETURN_NULL();
387
388 serpa = pc_patch_serialize(pa, NULL);
389
390 pc_patch_free(pa);
391 PG_RETURN_POINTER(serpa);
392 }
393
394 typedef struct
395 {
396 ArrayBuildState *s;
397 } abs_trans;
398
399 PG_FUNCTION_INFO_V1(pointcloud_abs_in);
pointcloud_abs_in(PG_FUNCTION_ARGS)400 Datum pointcloud_abs_in(PG_FUNCTION_ARGS)
401 {
402 ereport(ERROR,(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
403 errmsg("function pointcloud_abs_in not implemented")));
404 PG_RETURN_POINTER(NULL);
405 }
406
407 PG_FUNCTION_INFO_V1(pointcloud_abs_out);
pointcloud_abs_out(PG_FUNCTION_ARGS)408 Datum pointcloud_abs_out(PG_FUNCTION_ARGS)
409 {
410 ereport(ERROR,(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
411 errmsg("function pointcloud_abs_out not implemented")));
412 PG_RETURN_POINTER(NULL);
413 }
414
415
416 PG_FUNCTION_INFO_V1(pointcloud_agg_transfn);
pointcloud_agg_transfn(PG_FUNCTION_ARGS)417 Datum pointcloud_agg_transfn(PG_FUNCTION_ARGS)
418 {
419 Oid arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
420 MemoryContext aggcontext;
421 abs_trans *a;
422 ArrayBuildState *state;
423 Datum elem;
424
425 if (arg1_typeid == InvalidOid)
426 ereport(ERROR,
427 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
428 errmsg("could not determine input data type")));
429
430 if ( ! AggCheckCallContext(fcinfo, &aggcontext) )
431 {
432 /* cannot be called directly because of dummy-type argument */
433 elog(ERROR, "pointcloud_agg_transfn called in non-aggregate context");
434 aggcontext = NULL; /* keep compiler quiet */
435 }
436
437 if ( PG_ARGISNULL(0) )
438 {
439 a = (abs_trans*) palloc(sizeof(abs_trans));
440 a->s = NULL;
441 }
442 else
443 {
444 a = (abs_trans*) PG_GETARG_POINTER(0);
445 }
446 state = a->s;
447 elem = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1);
448 state = accumArrayResult(state,
449 elem,
450 PG_ARGISNULL(1),
451 arg1_typeid,
452 aggcontext);
453 a->s = state;
454
455 PG_RETURN_POINTER(a);
456 }
457
458
459
460
461 static Datum
pointcloud_agg_final(abs_trans * a,MemoryContext mctx,FunctionCallInfo fcinfo)462 pointcloud_agg_final(abs_trans *a, MemoryContext mctx, FunctionCallInfo fcinfo)
463 {
464 ArrayBuildState *state;
465 int dims[1];
466 int lbs[1];
467 state = a->s;
468 dims[0] = state->nelems;
469 lbs[0] = 1;
470 return makeMdArrayResult(state, 1, dims, lbs, mctx, false);
471 }
472
473 PG_FUNCTION_INFO_V1(pcpoint_agg_final_array);
pcpoint_agg_final_array(PG_FUNCTION_ARGS)474 Datum pcpoint_agg_final_array(PG_FUNCTION_ARGS)
475 {
476 abs_trans *a;
477 Datum result = 0;
478
479 if (PG_ARGISNULL(0))
480 PG_RETURN_NULL(); /* returns null iff no input values */
481
482 a = (abs_trans*) PG_GETARG_POINTER(0);
483
484 result = pointcloud_agg_final(a, CurrentMemoryContext, fcinfo);
485 PG_RETURN_DATUM(result);
486 }
487
488
489 PG_FUNCTION_INFO_V1(pcpatch_agg_final_array);
pcpatch_agg_final_array(PG_FUNCTION_ARGS)490 Datum pcpatch_agg_final_array(PG_FUNCTION_ARGS)
491 {
492 abs_trans *a;
493 Datum result = 0;
494
495 if (PG_ARGISNULL(0))
496 PG_RETURN_NULL(); /* returns null iff no input values */
497
498 a = (abs_trans*) PG_GETARG_POINTER(0);
499
500 result = pointcloud_agg_final(a, CurrentMemoryContext, fcinfo);
501 PG_RETURN_DATUM(result);
502 }
503
504
505 PG_FUNCTION_INFO_V1(pcpoint_agg_final_pcpatch);
pcpoint_agg_final_pcpatch(PG_FUNCTION_ARGS)506 Datum pcpoint_agg_final_pcpatch(PG_FUNCTION_ARGS)
507 {
508 ArrayType *array;
509 abs_trans *a;
510 PCPATCH *pa;
511 SERIALIZED_PATCH *serpa;
512
513 if (PG_ARGISNULL(0))
514 PG_RETURN_NULL(); /* returns null iff no input values */
515
516 a = (abs_trans*) PG_GETARG_POINTER(0);
517
518 array = DatumGetArrayTypeP(pointcloud_agg_final(a, CurrentMemoryContext, fcinfo));
519 pa = pcpatch_from_point_array(array, fcinfo);
520 if ( ! pa )
521 PG_RETURN_NULL();
522
523 serpa = pc_patch_serialize(pa, NULL);
524 pc_patch_free(pa);
525 PG_RETURN_POINTER(serpa);
526 }
527
528
529 PG_FUNCTION_INFO_V1(pcpatch_agg_final_pcpatch);
pcpatch_agg_final_pcpatch(PG_FUNCTION_ARGS)530 Datum pcpatch_agg_final_pcpatch(PG_FUNCTION_ARGS)
531 {
532 ArrayType *array;
533 abs_trans *a;
534 PCPATCH *pa;
535 SERIALIZED_PATCH *serpa;
536
537 if (PG_ARGISNULL(0))
538 PG_RETURN_NULL(); /* returns null iff no input values */
539
540 a = (abs_trans*) PG_GETARG_POINTER(0);
541
542 array = DatumGetArrayTypeP(pointcloud_agg_final(a, CurrentMemoryContext, fcinfo));
543 pa = pcpatch_from_patch_array(array, fcinfo);
544 if ( ! pa )
545 PG_RETURN_NULL();
546
547 serpa = pc_patch_serialize(pa, NULL);
548 pc_patch_free(pa);
549 PG_RETURN_POINTER(serpa);
550 }
551
552
553 PG_FUNCTION_INFO_V1(pcpatch_unnest);
pcpatch_unnest(PG_FUNCTION_ARGS)554 Datum pcpatch_unnest(PG_FUNCTION_ARGS)
555 {
556 typedef struct
557 {
558 int nextelem;
559 int numelems;
560 PCPOINTLIST *pointlist;
561 } pcpatch_unnest_fctx;
562
563 FuncCallContext *funcctx;
564 pcpatch_unnest_fctx *fctx;
565 MemoryContext oldcontext;
566
567 /* stuff done only on the first call of the function */
568 if (SRF_IS_FIRSTCALL())
569 {
570 PCPATCH *patch;
571 SERIALIZED_PATCH *serpatch;
572
573 /* create a function context for cross-call persistence */
574 funcctx = SRF_FIRSTCALL_INIT();
575
576 /*
577 * switch to memory context appropriate for multiple function calls
578 */
579 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
580
581 /*
582 * Get the patch value and detoast if needed. We can't do this
583 * earlier because if we have to detoast, we want the detoasted copy
584 * to be in multi_call_memory_ctx, so it will go away when we're done
585 * and not before. (If no detoast happens, we assume the originally
586 * passed array will stick around till then.)
587 */
588 serpatch = PG_GETARG_SERPATCH_P(0);
589 patch = pc_patch_deserialize(serpatch, pc_schema_from_pcid_uncached(serpatch->pcid));
590
591 /* allocate memory for user context */
592 fctx = (pcpatch_unnest_fctx *) palloc(sizeof(pcpatch_unnest_fctx));
593
594 /* initialize state */
595 fctx->nextelem = 0;
596 fctx->numelems = patch->npoints;
597 fctx->pointlist = pc_pointlist_from_patch(patch);
598
599 /* save user context, switch back to function context */
600 funcctx->user_fctx = fctx;
601 MemoryContextSwitchTo(oldcontext);
602 }
603
604 /* stuff done on every call of the function */
605 funcctx = SRF_PERCALL_SETUP();
606 fctx = funcctx->user_fctx;
607
608 if (fctx->nextelem < fctx->numelems)
609 {
610 Datum elem;
611 PCPOINT *pt = pc_pointlist_get_point(fctx->pointlist, fctx->nextelem);
612 SERIALIZED_POINT *serpt = pc_point_serialize(pt);
613 fctx->nextelem++;
614 elem = PointerGetDatum(serpt);
615 SRF_RETURN_NEXT(funcctx, elem);
616 }
617 else
618 {
619 /* do when there is no more left */
620 SRF_RETURN_DONE(funcctx);
621 }
622 }
623
624 PG_FUNCTION_INFO_V1(pcpatch_uncompress);
pcpatch_uncompress(PG_FUNCTION_ARGS)625 Datum pcpatch_uncompress(PG_FUNCTION_ARGS)
626 {
627 SERIALIZED_PATCH *serpa = PG_GETARG_SERPATCH_P(0);
628 PCSCHEMA *schema = pc_schema_from_pcid(serpa->pcid, fcinfo);
629 PCPATCH *patch = pc_patch_deserialize(serpa, schema);
630 SERIALIZED_PATCH *serpa_out = pc_patch_serialize_to_uncompressed(patch);
631 pc_patch_free(patch);
632 PG_RETURN_POINTER(serpa_out);
633 }
634
635 PG_FUNCTION_INFO_V1(pcpatch_compress);
pcpatch_compress(PG_FUNCTION_ARGS)636 Datum pcpatch_compress(PG_FUNCTION_ARGS)
637 {
638 SERIALIZED_PATCH *serpa = PG_GETARG_SERPATCH_P(0);
639 text *compr_in_text = PG_GETARG_TEXT_P(1);
640 char *compr_in = text_to_cstring(compr_in_text);
641 text *config_in_text = PG_GETARG_TEXT_P(2);
642 char *config_in = text_to_cstring(config_in_text);
643 PCSCHEMA *schema = pc_schema_from_pcid(serpa->pcid, fcinfo);
644 PCPATCH *patch_in = pc_patch_deserialize(serpa, schema);
645 PCPATCH *pa = patch_in;
646 SERIALIZED_PATCH *serpa_out;
647 PCDIMSTATS *stats = NULL;
648 int i;
649
650 /* Uncompress first */
651 if ( patch_in->type != PC_NONE ) {
652 pa = pc_patch_uncompress(patch_in);
653 }
654
655 schema = pc_schema_clone(schema); /* we're going to modify it */
656
657 /* Set compression scheme */
658 if ( *compr_in == '\0' || strcasecmp(compr_in, "auto") == 0 ) {
659 /* keep schema defined compression */
660 }
661 else if ( strcmp(compr_in, "dimensional") == 0 ) {{
662 char *ptr = config_in;
663 PCPATCH_DIMENSIONAL *pdl = pc_patch_dimensional_from_uncompressed((PCPATCH_UNCOMPRESSED*)pa);
664 schema->compression = PC_DIMENSIONAL;
665 stats = pc_dimstats_make(schema);
666 pc_dimstats_update(stats, pdl);
667 /* make sure to avoid stat updates (not sure if needed) */
668 stats->total_points = PCDIMSTATS_MIN_SAMPLE+1;
669
670
671 /* Fill in per-dimension compression */
672 if ( *ptr )
673 for (i=0; i<stats->ndims; ++i) {
674 PCDIMSTAT *stat = &(stats->stats[i]);
675 /*pcinfo("ptr: %s", ptr);*/
676 if ( *ptr == ',' || strncmp(ptr, "auto", strlen("auto")) == 0 ) {
677 /* leave auto-determined compression */
678 }
679 else if ( strncmp(ptr, "rle", strlen("rle")) == 0 ) {
680 stat->recommended_compression = PC_DIM_RLE;
681 }
682 else if ( strncmp(ptr, "sigbits", strlen("sigbits")) == 0 ) {
683 stat->recommended_compression = PC_DIM_SIGBITS;
684 }
685 else if ( strncmp(ptr, "zlib", strlen("zlib")) == 0 ) {
686 stat->recommended_compression = PC_DIM_ZLIB;
687 }
688 else {
689 elog(ERROR, "Unrecognized dimensional compression '%s'. Please specify 'auto', 'rle', 'sigbits' or 'zlib'", ptr);
690 }
691 while (*ptr && *ptr != ',') ++ptr;
692 if ( ! *ptr ) break;
693 else ++ptr;
694 }
695
696 if ( pa != patch_in ) pc_patch_free(pa);
697 pa = (PCPATCH*)pc_patch_dimensional_compress(pdl, stats);
698 pc_patch_dimensional_free(pdl);
699 }}
700 else if ( strcmp(compr_in, "laz") == 0 ) {
701 schema->compression = PC_LAZPERF;
702 }
703 else {
704 elog(ERROR, "Unrecognized compression '%s'. Please specify 'auto','dimensional' or 'laz'", compr_in);
705 }
706
707 pa->schema = schema; /* install overridden schema */
708 serpa_out = pc_patch_serialize(pa, stats);
709
710 if ( pa != patch_in )
711 pc_patch_free(pa);
712 pc_patch_free(patch_in);
713 pc_schema_free(schema);
714
715 PG_RETURN_POINTER(serpa_out);
716 }
717
718 PG_FUNCTION_INFO_V1(pcpatch_numpoints);
pcpatch_numpoints(PG_FUNCTION_ARGS)719 Datum pcpatch_numpoints(PG_FUNCTION_ARGS)
720 {
721 SERIALIZED_PATCH *serpa = PG_GETHEADER_SERPATCH_P(0);
722 PG_RETURN_INT32(serpa->npoints);
723 }
724
725 PG_FUNCTION_INFO_V1(pcpatch_pointn);
pcpatch_pointn(PG_FUNCTION_ARGS)726 Datum pcpatch_pointn(PG_FUNCTION_ARGS)
727 {
728 SERIALIZED_POINT *serpt;
729 SERIALIZED_PATCH *serpa = PG_GETARG_SERPATCH_P(0);
730 int32 n = PG_GETARG_INT32(1);
731 PCSCHEMA *schema = pc_schema_from_pcid(serpa->pcid, fcinfo);
732 PCPATCH *patch = pc_patch_deserialize(serpa, schema);
733 PCPOINT *pt = NULL;
734 if(patch) {
735 pt = pc_patch_pointn(patch,n);
736 pc_patch_free(patch);
737 }
738 if(!pt) PG_RETURN_NULL();
739 serpt = pc_point_serialize(pt);
740 pc_point_free(pt);
741 PG_RETURN_POINTER(serpt);
742 }
743
744 PG_FUNCTION_INFO_V1(pcpatch_range);
pcpatch_range(PG_FUNCTION_ARGS)745 Datum pcpatch_range(PG_FUNCTION_ARGS)
746 {
747 SERIALIZED_PATCH *serpaout;
748 SERIALIZED_PATCH *serpa = PG_GETARG_SERPATCH_P(0);
749 int32 first = PG_GETARG_INT32(1);
750 int32 count = PG_GETARG_INT32(2);
751 PCSCHEMA *schema = pc_schema_from_pcid(serpa->pcid, fcinfo);
752 PCPATCH *patch = pc_patch_deserialize(serpa, schema);
753 PCPATCH *patchout = NULL;
754 if ( patch )
755 {
756 patchout = pc_patch_range(patch, first, count);
757 if ( patchout != patch )
758 pc_patch_free(patch);
759 }
760 if ( !patchout )
761 PG_RETURN_NULL();
762 serpaout = pc_patch_serialize(patchout, NULL);
763 pc_patch_free(patchout);
764 PG_RETURN_POINTER(serpaout);
765 }
766
767 PG_FUNCTION_INFO_V1(pcpatch_pcid);
pcpatch_pcid(PG_FUNCTION_ARGS)768 Datum pcpatch_pcid(PG_FUNCTION_ARGS)
769 {
770 SERIALIZED_PATCH *serpa = PG_GETHEADER_SERPATCH_P(0);
771 PG_RETURN_INT32(serpa->pcid);
772 }
773
774 PG_FUNCTION_INFO_V1(pcpatch_summary);
pcpatch_summary(PG_FUNCTION_ARGS)775 Datum pcpatch_summary(PG_FUNCTION_ARGS)
776 {
777 const int stats_size_guess = 400;
778 SERIALIZED_PATCH *serpa;
779 PCSCHEMA *schema;
780 PCSTATS *stats;
781 PCPATCH *patch = NULL;
782 StringInfoData strdata;
783 text *ret;
784 const char *comma = "";
785 int i;
786
787 serpa = PG_GETHEADERX_SERPATCH_P(0, stats_size_guess);
788 schema = pc_schema_from_pcid(serpa->pcid, fcinfo);
789 if ( serpa->compression == PC_DIMENSIONAL )
790 {
791 /* need full data to inspect per-dimension compression */
792 /* NOTE: memory usage could be optimized to only fetch slices
793 * at specific offsets, but doesn't seem worth at this time
794 * See https://github.com/pgpointcloud/pointcloud/pull/51#issuecomment-83592363
795 */
796 serpa = PG_GETARG_SERPATCH_P(0);
797 patch = pc_patch_deserialize(serpa, schema);
798 }
799 else if ( stats_size_guess < pc_stats_size(schema) )
800 {
801 /* only need stats here */
802 serpa = PG_GETHEADERX_SERPATCH_P(0, pc_stats_size(schema));
803 }
804 stats = pc_patch_stats_deserialize(schema, serpa->data);
805
806 initStringInfo(&strdata);
807 /* Make space for VARSIZ, see SET_VARSIZE below */
808 appendStringInfoSpaces(&strdata, VARHDRSZ);
809
810 appendStringInfo(&strdata, "{"
811 "\"pcid\":%d, \"npts\":%d, \"srid\":%d, "
812 "\"compr\":\"%s\",\"dims\":[",
813 serpa->pcid, serpa->npoints, schema->srid,
814 pc_compression_name(serpa->compression));
815
816 for (i=0; i<schema->ndims; ++i)
817 {
818 PCDIMENSION *dim = schema->dims[i];
819 PCBYTES bytes;
820 double val;
821 appendStringInfo(&strdata,
822 "%s{\"pos\":%d,\"name\":\"%s\",\"size\":%d"
823 ",\"type\":\"%s\"",
824 comma, dim->position, dim->name, dim->size,
825 pc_interpretation_string(dim->interpretation));
826
827 /* Print per-dimension compression (if dimensional) */
828 if ( serpa->compression == PC_DIMENSIONAL )
829 {
830 bytes = ((PCPATCH_DIMENSIONAL*)patch)->bytes[i];
831 switch ( bytes.compression )
832 {
833 case PC_DIM_RLE:
834 appendStringInfoString(&strdata,",\"compr\":\"rle\"");
835 break;
836 case PC_DIM_SIGBITS:
837 appendStringInfoString(&strdata,",\"compr\":\"sigbits\"");
838 break;
839 case PC_DIM_ZLIB:
840 appendStringInfoString(&strdata,",\"compr\":\"zlib\"");
841 break;
842 case PC_DIM_NONE:
843 appendStringInfoString(&strdata,",\"compr\":\"none\"");
844 break;
845 default:
846 appendStringInfo(&strdata,",\"compr\":\"unknown(%d)\"",
847 bytes.compression);
848 break;
849 }
850 }
851
852 if ( stats )
853 {
854 pc_point_get_double_by_name(&(stats->min), dim->name, &val);
855 appendStringInfo(&strdata,",\"stats\":{\"min\":%g", val);
856 pc_point_get_double_by_name(&(stats->max), dim->name, &val);
857 appendStringInfo(&strdata,",\"max\":%g", val);
858 pc_point_get_double_by_name(&(stats->avg), dim->name, &val);
859 appendStringInfo(&strdata,",\"avg\":%g}", val);
860 }
861 appendStringInfoString(&strdata, "}");
862 comma = ",";
863 }
864
865 appendStringInfoString(&strdata, "]}");
866
867 ret = (text*)strdata.data;
868 SET_VARSIZE(ret, strdata.len);
869 PG_RETURN_TEXT_P(ret);
870 }
871
872 PG_FUNCTION_INFO_V1(pcpatch_compression);
pcpatch_compression(PG_FUNCTION_ARGS)873 Datum pcpatch_compression(PG_FUNCTION_ARGS)
874 {
875 SERIALIZED_PATCH *serpa = PG_GETHEADER_SERPATCH_P(0);
876 PG_RETURN_INT32(serpa->compression);
877 }
878
879 PG_FUNCTION_INFO_V1(pcpatch_intersects);
pcpatch_intersects(PG_FUNCTION_ARGS)880 Datum pcpatch_intersects(PG_FUNCTION_ARGS)
881 {
882 SERIALIZED_PATCH *serpa1 = PG_GETHEADER_SERPATCH_P(0);
883 SERIALIZED_PATCH *serpa2 = PG_GETHEADER_SERPATCH_P(1);
884
885 if ( serpa1->pcid != serpa2->pcid )
886 elog(ERROR, "%s: pcid mismatch (%d != %d)", __func__, serpa1->pcid, serpa2->pcid);
887
888 if ( pc_bounds_intersects(&(serpa1->bounds), &(serpa2->bounds)) )
889 {
890 PG_RETURN_BOOL(true);
891 }
892 PG_RETURN_BOOL(false);
893 }
894
895 PG_FUNCTION_INFO_V1(pcpatch_size);
pcpatch_size(PG_FUNCTION_ARGS)896 Datum pcpatch_size(PG_FUNCTION_ARGS)
897 {
898 SERIALIZED_PATCH *serpa = PG_GETARG_SERPATCH_P(0);
899 PG_RETURN_INT32(VARSIZE(serpa));
900 }
901
902 PG_FUNCTION_INFO_V1(pcpoint_size);
pcpoint_size(PG_FUNCTION_ARGS)903 Datum pcpoint_size(PG_FUNCTION_ARGS)
904 {
905 SERIALIZED_POINT *serpt = PG_GETARG_SERPOINT_P(0);
906 PG_RETURN_INT32(VARSIZE(serpt));
907 }
908
909 PG_FUNCTION_INFO_V1(pcpoint_pcid);
pcpoint_pcid(PG_FUNCTION_ARGS)910 Datum pcpoint_pcid(PG_FUNCTION_ARGS)
911 {
912 SERIALIZED_POINT *serpt = PG_GETARG_SERPOINT_P(0);
913 PG_RETURN_INT32(serpt->pcid);
914 }
915
916 PG_FUNCTION_INFO_V1(pc_version);
pc_version(PG_FUNCTION_ARGS)917 Datum pc_version(PG_FUNCTION_ARGS)
918 {
919 text *version_text;
920 char version[64];
921 snprintf(version, 64, "%s", POINTCLOUD_VERSION);
922 version_text = cstring_to_text(version);
923 PG_RETURN_TEXT_P(version_text);
924 }
925
926 PG_FUNCTION_INFO_V1(pc_pgsql_version);
pc_pgsql_version(PG_FUNCTION_ARGS)927 Datum pc_pgsql_version(PG_FUNCTION_ARGS)
928 {
929 text *version_text;
930 char version[12];
931 snprintf(version, 12, "%d", PGSQL_VERSION);
932 version_text = cstring_to_text(version);
933 PG_RETURN_TEXT_P(version_text);
934 }
935
936 PG_FUNCTION_INFO_V1(pc_libxml2_version);
pc_libxml2_version(PG_FUNCTION_ARGS)937 Datum pc_libxml2_version(PG_FUNCTION_ARGS)
938 {
939 text *version_text;
940 char version[64];
941 snprintf(version, 64, "%s", LIBXML2_VERSION);
942 version_text = cstring_to_text(version);
943 PG_RETURN_TEXT_P(version_text);
944 }
945
946 PG_FUNCTION_INFO_V1(pc_lazperf_enabled);
pc_lazperf_enabled(PG_FUNCTION_ARGS)947 Datum pc_lazperf_enabled(PG_FUNCTION_ARGS)
948 {
949 #ifdef HAVE_LAZPERF
950 PG_RETURN_BOOL(true);
951 #else
952 PG_RETURN_BOOL(false);
953 #endif
954 }
955
956 /**
957 * Read a named dimension statistic from a PCPATCH
958 * PC_PatchMax(patch pcpatch, dimname text) returns Numeric
959 * PC_PatchMin(patch pcpatch, dimname text) returns Numeric
960 * PC_PatchAvg(patch pcpatch, dimname text) returns Numeric
961 * PC_PatchMax(patch pcpatch) returns PcPoint
962 * PC_PatchMin(patch pcpatch) returns PcPoint
963 * PC_PatchAvg(patch pcpatch) returns PcPoint
964 */
965 PG_FUNCTION_INFO_V1(pcpatch_get_stat);
pcpatch_get_stat(PG_FUNCTION_ARGS)966 Datum pcpatch_get_stat(PG_FUNCTION_ARGS)
967 {
968 static int stats_size_guess = 400;
969 SERIALIZED_PATCH *serpa = PG_GETHEADERX_SERPATCH_P(0, stats_size_guess);
970 PCSCHEMA *schema = pc_schema_from_pcid(serpa->pcid, fcinfo);
971 int32 statno = PG_GETARG_INT32(1);
972 char *dim_str = 0;
973 PCSTATS *stats;
974 const PCPOINT *pt;
975 SERIALIZED_POINT *serpt = NULL;
976 float8 double_result;
977 int rv = 1;
978
979
980 if ( PG_NARGS() > 2 ) {
981 /* TODO: only get small slice ? */
982 dim_str = text_to_cstring(PG_GETARG_TEXT_P(2));
983 }
984
985 if ( stats_size_guess < pc_stats_size(schema) )
986 {
987 serpa = PG_GETHEADERX_SERPATCH_P(0, pc_stats_size(schema) );
988 }
989
990 stats = pc_patch_stats_deserialize(schema, serpa->data);
991
992 if ( ! stats )
993 PG_RETURN_NULL();
994
995 /* Min */
996 if ( 0 == statno )
997 pt = &(stats->min);
998 /* Max */
999 else if ( 1 == statno )
1000 pt = &(stats->max);
1001 /* Avg */
1002 else if ( 2 == statno )
1003 pt = &(stats->avg);
1004 /* Unsupported */
1005 else
1006 elog(ERROR, "stat number \"%d\" is not supported", statno);
1007
1008 /* empty dim string means we want the whole point */
1009 if ( ! dim_str )
1010 {
1011 serpt = pc_point_serialize(pt);
1012 pc_stats_free(stats);
1013 PG_RETURN_POINTER(serpt);
1014 }
1015 else
1016 {
1017 rv = pc_point_get_double_by_name(pt, dim_str, &double_result);
1018 pc_stats_free(stats);
1019 if ( ! rv )
1020 {
1021 elog(ERROR, "dimension \"%s\" does not exist in schema", dim_str);
1022 PG_RETURN_NULL();
1023 }
1024 pfree(dim_str);
1025 PG_RETURN_DATUM(DirectFunctionCall1(float8_numeric, Float8GetDatum(double_result)));
1026 }
1027 }
1028
1029 /**
1030 * PC_FilterLessThan(patch pcpatch, dimname text, value) returns PcPatch
1031 * PC_FilterGreaterThan(patch pcpatch, dimname text, value) returns PcPatch
1032 * PC_FilterEquals(patch pcpatch, dimname text, value) returns PcPatch
1033 * PC_FilterBetween(patch pcpatch, dimname text, value1, value2) returns PcPatch
1034 */
1035 PG_FUNCTION_INFO_V1(pcpatch_filter);
pcpatch_filter(PG_FUNCTION_ARGS)1036 Datum pcpatch_filter(PG_FUNCTION_ARGS)
1037 {
1038 SERIALIZED_PATCH *serpatch = PG_GETARG_SERPATCH_P(0);
1039 PCSCHEMA *schema = pc_schema_from_pcid(serpatch->pcid, fcinfo);
1040 char *dim_name = text_to_cstring(PG_GETARG_TEXT_P(1));
1041 float8 value1 = PG_GETARG_FLOAT8(2);
1042 float8 value2 = PG_GETARG_FLOAT8(3);
1043 int32 mode = PG_GETARG_INT32(4);
1044 PCPATCH *patch;
1045 PCPATCH *patch_filtered = NULL;
1046 SERIALIZED_PATCH *serpatch_filtered;
1047
1048 patch = pc_patch_deserialize(serpatch, schema);
1049 if ( ! patch )
1050 {
1051 elog(ERROR, "failed to deserialize patch");
1052 PG_RETURN_NULL();
1053 }
1054
1055 switch ( mode )
1056 {
1057 case 0:
1058 patch_filtered = pc_patch_filter_lt_by_name(patch, dim_name, value1);
1059 break;
1060 case 1:
1061 patch_filtered = pc_patch_filter_gt_by_name(patch, dim_name, value1);
1062 break;
1063 case 2:
1064 patch_filtered = pc_patch_filter_equal_by_name(patch, dim_name, value1);
1065 break;
1066 case 3:
1067 patch_filtered = pc_patch_filter_between_by_name(patch, dim_name, value1, value2);
1068 break;
1069 default:
1070 elog(ERROR, "unknown mode \"%d\"", mode);
1071 }
1072
1073 pc_patch_free(patch);
1074 PG_FREE_IF_COPY(serpatch, 0);
1075
1076 if ( ! patch_filtered )
1077 {
1078 elog(ERROR, "dimension \"%s\" does not exist", dim_name);
1079 }
1080 pfree(dim_name);
1081
1082 /* Always treat zero-point patches as SQL NULL */
1083 if ( patch_filtered->npoints <= 0 )
1084 {
1085 pc_patch_free(patch_filtered);
1086 PG_RETURN_NULL();
1087 }
1088
1089 serpatch_filtered = pc_patch_serialize(patch_filtered, NULL);
1090 pc_patch_free(patch_filtered);
1091
1092 PG_RETURN_POINTER(serpatch_filtered);
1093 }
1094
array_to_cstring_array(ArrayType * array,int * size)1095 const char **array_to_cstring_array(ArrayType *array, int *size)
1096 {
1097 int i, j, offset = 0;
1098 int nelems = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array));
1099 const char **cstring = nelems ? pcalloc(nelems * sizeof(char*)) : NULL;
1100 bits8 *bitmap = ARR_NULLBITMAP(array);
1101 for(i=j=0; i<nelems; ++i)
1102 {
1103 text *array_text;
1104 if(array_get_isnull(bitmap,i)) continue;
1105
1106 array_text = (text *)(ARR_DATA_PTR(array)+offset);
1107 cstring[j++] = text_to_cstring(array_text);
1108 offset += INTALIGN(VARSIZE(array_text));
1109 }
1110 if(size) *size = j;
1111 return cstring;
1112 }
1113
pc_cstring_array_free(const char ** array,int nelems)1114 void pc_cstring_array_free(const char **array, int nelems)
1115 {
1116 int i;
1117 if(!array) return;
1118 for(i=0;i<nelems;++i) pfree((void *)array[i]);
1119 pcfree((void *)array);
1120 }
1121
1122 /**
1123 * PC_Sort(patch pcpatch, dimname text[]) returns PcPatch
1124 */
1125 PG_FUNCTION_INFO_V1(pcpatch_sort);
pcpatch_sort(PG_FUNCTION_ARGS)1126 Datum pcpatch_sort(PG_FUNCTION_ARGS)
1127 {
1128 SERIALIZED_PATCH *serpatch = PG_GETARG_SERPATCH_P(0);
1129 ArrayType *array = PG_GETARG_ARRAYTYPE_P(1);
1130 PCSCHEMA *schema = NULL;
1131 PCPATCH *patch = NULL;
1132 PCPATCH *patch_sorted = NULL;
1133 SERIALIZED_PATCH *serpatch_sorted = NULL;
1134
1135 int ndims;
1136 const char **dim_name = array_to_cstring_array(array,&ndims);
1137 if(!ndims)
1138 {
1139 pc_cstring_array_free(dim_name,ndims);
1140 PG_RETURN_POINTER(serpatch);
1141 }
1142
1143 schema = pc_schema_from_pcid(serpatch->pcid, fcinfo);
1144
1145 patch = pc_patch_deserialize(serpatch, schema);
1146 if(patch) patch_sorted = pc_patch_sort(patch,dim_name,ndims);
1147
1148 pc_cstring_array_free(dim_name,ndims);
1149 if(patch) pc_patch_free(patch);
1150 PG_FREE_IF_COPY(serpatch, 0);
1151
1152 if(!patch_sorted) PG_RETURN_NULL();
1153
1154 serpatch_sorted = pc_patch_serialize(patch_sorted,NULL);
1155 pc_patch_free(patch_sorted);
1156 PG_RETURN_POINTER(serpatch_sorted);
1157 }
1158
1159 /** True/false if the patch is sorted on dimension */
1160 PG_FUNCTION_INFO_V1(pcpatch_is_sorted);
pcpatch_is_sorted(PG_FUNCTION_ARGS)1161 Datum pcpatch_is_sorted(PG_FUNCTION_ARGS)
1162 {
1163 ArrayType *array = PG_GETARG_ARRAYTYPE_P(1);
1164 bool strict = PG_GETARG_BOOL(2);
1165 PCSCHEMA *schema = NULL;
1166 SERIALIZED_PATCH *serpatch = NULL;
1167 PCPATCH *patch = NULL;
1168 int ndims;
1169 uint32_t res;
1170 const char **dim_name = array_to_cstring_array(array,&ndims);
1171 if(!ndims)
1172 {
1173 pc_cstring_array_free(dim_name,ndims);
1174 PG_RETURN_BOOL(PC_TRUE);
1175 }
1176 serpatch = PG_GETARG_SERPATCH_P(0);
1177 schema = pc_schema_from_pcid(serpatch->pcid, fcinfo);
1178 patch = pc_patch_deserialize(serpatch, schema);
1179
1180 res = pc_patch_is_sorted(patch,dim_name,ndims,strict);
1181
1182 pc_cstring_array_free(dim_name,ndims);
1183 pc_patch_free(patch);
1184
1185 if(res == PC_FAILURE-1)
1186 elog(ERROR, "PC_IsSorted failed");
1187
1188 PG_RETURN_BOOL(res);
1189 }
1190
1191