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