1 /***********************************************************************
2 * pc_pgsql.c
3 *
4 *  Utility functions to bind pc_api.h functions to PgSQL, including
5 *  memory management and serialization/deserializations.
6 *
7 *  PgSQL Pointcloud is free and open source software provided
8 *  by the Government of Canada
9 *  Copyright (c) 2013 Natural Resources Canada
10 *
11 ***********************************************************************/
12 
13 #include <assert.h>
14 #include "pc_pgsql.h"
15 #include "executor/spi.h"
16 #include "access/hash.h"
17 #include "utils/hsearch.h"
18 
19 PG_MODULE_MAGIC;
20 
21 /**********************************************************************************
22 * POSTGRESQL MEMORY MANAGEMENT HOOKS
23 */
24 
25 static void *
pgsql_alloc(size_t size)26 pgsql_alloc(size_t size)
27 {
28 	void * result;
29 	result = palloc(size);
30 
31 	if ( ! result )
32 	{
33 		ereport(ERROR,
34 			(errcode(ERRCODE_OUT_OF_MEMORY),
35 			errmsg("Out of virtual memory")));
36 	}
37 
38 	return result;
39 }
40 
41 static void *
pgsql_realloc(void * mem,size_t size)42 pgsql_realloc(void *mem, size_t size)
43 {
44 	void * result;
45 	result = repalloc(mem, size);
46 	if ( ! result )
47 	{
48 		ereport(ERROR,
49 				(errcode(ERRCODE_OUT_OF_MEMORY),
50 				 errmsg("Out of virtual memory")));
51 	}
52 	return result;
53 }
54 
55 static void
pgsql_free(void * ptr)56 pgsql_free(void *ptr)
57 {
58 	pfree(ptr);
59 }
60 
61 static void
62 pgsql_msg_handler(int sig, const char *fmt, va_list ap)
63 	__attribute__ (( format (printf, 2, 0) ));
64 
65 static void
pgsql_msg_handler(int sig,const char * fmt,va_list ap)66 pgsql_msg_handler(int sig, const char *fmt, va_list ap)
67 {
68 #define MSG_MAXLEN 1024
69 	char msg[MSG_MAXLEN] = {0};
70 	vsnprintf(msg, MSG_MAXLEN, fmt, ap);
71 	msg[MSG_MAXLEN-1] = '\0';
72 	ereport(sig, (errmsg_internal("%s", msg)));
73 }
74 
75 static void
76 pgsql_error(const char *fmt, va_list ap) __attribute__ (( format (printf, 1, 0) ));
77 
78 static void
pgsql_error(const char * fmt,va_list ap)79 pgsql_error(const char *fmt, va_list ap)
80 {
81 	pgsql_msg_handler(ERROR, fmt, ap);
82 }
83 
84 static void
85 pgsql_warn(const char *fmt, va_list ap) __attribute__ (( format (printf, 1, 0) ));
86 
87 static void
pgsql_warn(const char * fmt,va_list ap)88 pgsql_warn(const char *fmt, va_list ap)
89 {
90 	pgsql_msg_handler(WARNING, fmt, ap);
91 }
92 
93 static void
94 pgsql_info(const char *fmt, va_list ap) __attribute__ (( format (printf, 1, 0) ));
95 
96 static void
pgsql_info(const char * fmt,va_list ap)97 pgsql_info(const char *fmt, va_list ap)
98 {
99 	pgsql_msg_handler(NOTICE, fmt, ap);
100 }
101 
102 /**********************************************************************************
103 * POINTCLOUD START-UP/SHUT-DOWN CALLBACKS
104 */
105 
106 /**
107 * On module load we want to hook the message writing and memory allocation
108 * functions of libpc to the PostgreSQL ones.
109 * TODO: also hook the libxml2 hooks into PostgreSQL.
110 */
111 void _PG_init(void);
112 void
_PG_init(void)113 _PG_init(void)
114 {
115 	elog(LOG, "Pointcloud (%s) module loaded", POINTCLOUD_VERSION);
116 	pc_set_handlers(
117 		pgsql_alloc, pgsql_realloc,
118 		pgsql_free, pgsql_error,
119 		pgsql_info, pgsql_warn
120 	);
121 
122 }
123 
124 /* Module unload callback */
125 void _PG_fini(void);
126 void
_PG_fini(void)127 _PG_fini(void)
128 {
129 	elog(LOG, "Pointcloud (%s) module unloaded", POINTCLOUD_VERSION);
130 }
131 
132 /* Mask pcid from bottom of typmod */
pcid_from_typmod(const int32 typmod)133 uint32 pcid_from_typmod(const int32 typmod)
134 {
135 	if ( typmod == -1 )
136 		return 0;
137 	else
138 		return (typmod & 0x0000FFFF);
139 }
140 
141 /**********************************************************************************
142 * PCPOINT WKB Handling
143 */
144 
145 PCPOINT *
146 #if PGSQL_VERSION < 120
pc_point_from_hexwkb(const char * hexwkb,size_t hexlen,FunctionCallInfoData * fcinfo)147 pc_point_from_hexwkb(const char *hexwkb, size_t hexlen, FunctionCallInfoData *fcinfo)
148 #else
149 pc_point_from_hexwkb(const char *hexwkb, size_t hexlen, FunctionCallInfo fcinfo)
150 #endif
151 {
152 	PCPOINT *pt;
153 	PCSCHEMA *schema;
154 	uint32 pcid;
155 	uint8 *wkb = pc_bytes_from_hexbytes(hexwkb, hexlen);
156 	size_t wkblen = hexlen/2;
157 	pcid = pc_wkb_get_pcid(wkb);
158 	schema = pc_schema_from_pcid(pcid, fcinfo);
159 	pt = pc_point_from_wkb(schema, wkb, wkblen);
160 	pfree(wkb);
161 	return pt;
162 }
163 
164 char *
pc_point_to_hexwkb(const PCPOINT * pt)165 pc_point_to_hexwkb(const PCPOINT *pt)
166 {
167 	uint8 *wkb;
168 	size_t wkb_size;
169 	char *hexwkb;
170 
171 	wkb = pc_point_to_wkb(pt, &wkb_size);
172 	hexwkb = pc_hexbytes_from_bytes(wkb, wkb_size);
173 	pfree(wkb);
174 	return hexwkb;
175 }
176 
177 
178 /**********************************************************************************
179 * PCPATCH WKB Handling
180 */
181 
182 PCPATCH *
183 #if PGSQL_VERSION < 120
pc_patch_from_hexwkb(const char * hexwkb,size_t hexlen,FunctionCallInfoData * fcinfo)184 pc_patch_from_hexwkb(const char *hexwkb, size_t hexlen, FunctionCallInfoData *fcinfo)
185 #else
186 pc_patch_from_hexwkb(const char *hexwkb, size_t hexlen, FunctionCallInfo fcinfo)
187 #endif
188 {
189 	PCPATCH *patch;
190 	PCSCHEMA *schema;
191 	uint32 pcid;
192 	uint8 *wkb = pc_bytes_from_hexbytes(hexwkb, hexlen);
193 	size_t wkblen = hexlen/2;
194 	pcid = pc_wkb_get_pcid(wkb);
195 	if ( ! pcid )
196 		elog(ERROR, "%s: pcid is zero", __func__);
197 
198 	schema = pc_schema_from_pcid(pcid, fcinfo);
199 	if ( ! schema )
200 		elog(ERROR, "%s: unable to look up schema entry", __func__);
201 
202 	patch = pc_patch_from_wkb(schema, wkb, wkblen);
203 	pfree(wkb);
204 	return patch;
205 }
206 
207 char *
pc_patch_to_hexwkb(const PCPATCH * patch)208 pc_patch_to_hexwkb(const PCPATCH *patch)
209 {
210 	uint8 *wkb;
211 	size_t wkb_size;
212 	char *hexwkb;
213 
214 	wkb = pc_patch_to_wkb(patch, &wkb_size);
215 	hexwkb = pc_hexbytes_from_bytes(wkb, wkb_size);
216 	pfree(wkb);
217 	return hexwkb;
218 }
219 
220 
221 /**********************************************************************************
222 * PCID <=> PCSCHEMA translation via POINTCLOUD_FORMATS
223 */
224 
pcid_from_datum(Datum d)225 uint32 pcid_from_datum(Datum d)
226 {
227 	SERIALIZED_POINT *serpart;
228 	if ( ! d )
229 		return 0;
230 	/* Serializations are int32_t <size> uint32_t <pcid> == 8 bytes */
231 	/* Cast to SERIALIZED_POINT for convenience, SERIALIZED_PATCH shares same header */
232 	serpart = (SERIALIZED_POINT*)PG_DETOAST_DATUM_SLICE(d, 0, 8);
233 	return serpart->pcid;
234 }
235 
236 PCSCHEMA *
pc_schema_from_pcid_uncached(uint32 pcid)237 pc_schema_from_pcid_uncached(uint32 pcid)
238 {
239 	char sql[256];
240 	char *xml, *xml_spi, *srid_spi;
241 	int err, srid;
242 	size_t size;
243 	PCSCHEMA *schema;
244 
245 	if (SPI_OK_CONNECT != SPI_connect ())
246 	{
247 		SPI_finish();
248 		elog(ERROR, "%s: could not connect to SPI manager", __func__);
249 		return NULL;
250 	}
251 
252 	sprintf(sql, "select %s, %s from %s where pcid = %d",
253 		POINTCLOUD_FORMATS_XML, POINTCLOUD_FORMATS_SRID, POINTCLOUD_FORMATS, pcid);
254 	err = SPI_exec(sql, 1);
255 
256 	if ( err < 0 )
257 	{
258 		SPI_finish();
259 		elog(ERROR, "%s: error (%d) executing query: %s", __func__, err, sql);
260 		return NULL;
261 	}
262 
263 	/* No entry in POINTCLOUD_FORMATS */
264 	if (SPI_processed <= 0)
265 	{
266 		SPI_finish();
267 		elog(ERROR, "no entry in \"%s\" for pcid = %d", POINTCLOUD_FORMATS, pcid);
268 		return NULL;
269 	}
270 
271 	/* Result  */
272 	xml_spi = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1);
273 	srid_spi = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 2);
274 
275 	/* NULL result */
276 	if ( ! ( xml_spi && srid_spi ) )
277 	{
278 		SPI_finish();
279 		elog(ERROR, "unable to read row from \"%s\" for pcid = %d", POINTCLOUD_FORMATS, pcid);
280 		return NULL;
281 	}
282 
283 	/* Copy result to upper executor context */
284 	size = strlen(xml_spi) + 1;
285 	xml = SPI_palloc(size);
286 	memcpy(xml, xml_spi, size);
287 
288 	/* Parse the SRID string into the function stack */
289 	srid = atoi(srid_spi);
290 
291 	/* Disconnect from SPI, losing all our SPI-allocated memory now... */
292 	SPI_finish();
293 
294 	/* Build the schema object */
295 	schema = pc_schema_from_xml(xml);
296 
297 	if ( !schema )
298 	{
299 		ereport(ERROR,
300 			(errcode(ERRCODE_NOT_AN_XML_DOCUMENT),
301 			errmsg("unable to parse XML for pcid = %d in \"%s\"", pcid, POINTCLOUD_FORMATS)));
302 	}
303 
304 	schema->pcid = pcid;
305 	schema->srid = srid;
306 
307 	return schema;
308 }
309 
310 
311 /**
312 * Hold the schema references in a list.
313 * We'll just search them linearly, because
314 * usually we'll have only one per statement
315 */
316 #define SchemaCacheSize 16
317 
318 typedef struct
319 {
320 	int next_slot;
321 	int pcids[SchemaCacheSize];
322 	PCSCHEMA* schemas[SchemaCacheSize];
323 } SchemaCache;
324 
325 
326 /**
327 * Get the schema entry from the schema cache if one exists.
328 * If it doesn't exist, make a new empty one, cache it, and
329 * return it.
330 */
331 static SchemaCache *
332 #if PGSQL_VERSION < 120
GetSchemaCache(FunctionCallInfoData * fcinfo)333 GetSchemaCache(FunctionCallInfoData* fcinfo)
334 #else
335 GetSchemaCache(FunctionCallInfo fcinfo)
336 #endif
337 {
338 	SchemaCache *cache = fcinfo->flinfo->fn_extra;
339 	if ( ! cache )
340 	{
341 		cache = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(SchemaCache));
342 		memset(cache, 0, sizeof(SchemaCache));
343 		fcinfo->flinfo->fn_extra = cache;
344 	}
345 	return cache;
346 }
347 
348 
349 PCSCHEMA *
350 #if PGSQL_VERSION < 120
pc_schema_from_pcid(uint32 pcid,FunctionCallInfoData * fcinfo)351 pc_schema_from_pcid(uint32 pcid, FunctionCallInfoData *fcinfo)
352 #else
353 pc_schema_from_pcid(uint32 pcid, FunctionCallInfo fcinfo)
354 #endif
355 {
356 	SchemaCache *schema_cache = GetSchemaCache(fcinfo);
357 	int i;
358 	PCSCHEMA *schema;
359 	MemoryContext oldcontext;
360 
361 	/* Unable to find/make a schema cache? Odd. */
362 	if ( ! schema_cache )
363 	{
364 		ereport(ERROR,
365 			(errcode(ERRCODE_INTERNAL_ERROR),
366 			errmsg("unable to create/load statement level schema cache")));
367 	}
368 
369 	/* Find our PCID if it's in there (usually it will be first) */
370 	for ( i = 0; i < SchemaCacheSize; i++ )
371 	{
372 		if ( schema_cache->pcids[i] == pcid )
373 		{
374 			return schema_cache->schemas[i];
375 		}
376 	}
377 
378 	elog(DEBUG1, "schema cache miss, use pc_schema_from_pcid_uncached");
379 
380 	/* Not in there, load one the old-fashioned way. */
381 	oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
382 	schema = pc_schema_from_pcid_uncached(pcid);
383 	MemoryContextSwitchTo(oldcontext);
384 
385 	/* Failed to load the XML? Odd. */
386 	if ( ! schema )
387 	{
388 		ereport(ERROR,
389 			(errcode(ERRCODE_INTERNAL_ERROR),
390 			errmsg("unable to load schema for pcid %u", pcid)));
391 	}
392 
393 	/* Save the XML in the next unused slot */
394 	schema_cache->schemas[schema_cache->next_slot] = schema;
395 	schema_cache->pcids[schema_cache->next_slot] = pcid;
396 	schema_cache->next_slot = (schema_cache->next_slot + 1) % SchemaCacheSize;
397 	return schema;
398 }
399 
400 
401 
402 /**********************************************************************************
403 * SERIALIZATION/DESERIALIZATION UTILITIES
404 */
405 
406 SERIALIZED_POINT *
pc_point_serialize(const PCPOINT * pcpt)407 pc_point_serialize(const PCPOINT *pcpt)
408 {
409 	size_t serpt_size = sizeof(SERIALIZED_POINT) - 1 + pcpt->schema->size;
410 	SERIALIZED_POINT *serpt = palloc(serpt_size);
411 	serpt->pcid = pcpt->schema->pcid;
412 	memcpy(serpt->data, pcpt->data, pcpt->schema->size);
413 	SET_VARSIZE(serpt, serpt_size);
414 	return serpt;
415 }
416 
417 PCPOINT *
pc_point_deserialize(const SERIALIZED_POINT * serpt,const PCSCHEMA * schema)418 pc_point_deserialize(const SERIALIZED_POINT *serpt, const PCSCHEMA *schema)
419 {
420 	PCPOINT *pcpt;
421 	size_t pgsize = VARSIZE(serpt) + 1 - sizeof(SERIALIZED_POINT);
422 	/*
423 	* Big problem, the size on disk doesn't match what we expect,
424 	* so we cannot reliably interpret the contents.
425 	*/
426 	if ( schema->size != pgsize )
427 	{
428 		elog(ERROR, "schema size and disk size mismatch, repair the schema");
429 		return NULL;
430 	}
431 	pcpt = pc_point_from_data(schema, serpt->data);
432 	return pcpt;
433 }
434 
435 
436 size_t
pc_patch_serialized_size(const PCPATCH * patch)437 pc_patch_serialized_size(const PCPATCH *patch)
438 {
439 	size_t stats_size = pc_stats_size(patch->schema);
440 	size_t common_size = sizeof(SERIALIZED_PATCH) - 1;
441 	switch( patch->type )
442 	{
443 	case PC_NONE:
444 	{
445 		PCPATCH_UNCOMPRESSED *pu = (PCPATCH_UNCOMPRESSED*)patch;
446 		return common_size + stats_size + pu->datasize;
447 	}
448 	case PC_DIMENSIONAL:
449 	{
450 		return common_size + stats_size + pc_patch_dimensional_serialized_size((PCPATCH_DIMENSIONAL*)patch);
451 	}
452 	case PC_LAZPERF:
453 	{
454 		static size_t lazsize_size = 4;
455 		PCPATCH_LAZPERF *pg = (PCPATCH_LAZPERF*)patch;
456 		return common_size + stats_size + lazsize_size + pg->lazperfsize;
457 	}
458 	default:
459 	{
460 		pcerror("%s: unknown compresed %d", __func__, patch->type);
461 	}
462 	}
463 	return -1;
464 }
465 
466 static size_t
pc_patch_stats_serialize(uint8_t * buf,const PCSCHEMA * schema,const PCSTATS * stats)467 pc_patch_stats_serialize(uint8_t *buf, const PCSCHEMA *schema, const PCSTATS *stats)
468 {
469 	size_t sz = schema->size;
470 	/* Copy min */
471 	memcpy(buf, stats->min.data, sz);
472 	/* Copy max */
473 	memcpy(buf + sz, stats->max.data, sz);
474 	/* Copy avg */
475 	memcpy(buf + 2*sz, stats->avg.data, sz);
476 
477 	return sz*3;
478 }
479 
480 /**
481 * Stats are always three PCPOINT serializations in a row,
482 * min, max, avg. Their size is the uncompressed buffer size for
483 * a point, the schema->size.
484 */
485 PCSTATS *
pc_patch_stats_deserialize(const PCSCHEMA * schema,const uint8_t * buf)486 pc_patch_stats_deserialize(const PCSCHEMA *schema, const uint8_t *buf)
487 {
488 	size_t sz = schema->size;
489 	const uint8_t *buf_min = buf;
490 	const uint8_t *buf_max = buf + sz;
491 	const uint8_t *buf_avg = buf + 2*sz;
492 
493 	return pc_stats_new_from_data(schema, buf_min, buf_max, buf_avg);
494 }
495 
496 static SERIALIZED_PATCH *
pc_patch_dimensional_serialize(const PCPATCH * patch_in)497 pc_patch_dimensional_serialize(const PCPATCH *patch_in)
498 {
499 	//  uint32_t size;
500 	//  uint32_t pcid;
501 	//  uint32_t compression;
502 	//  uint32_t npoints;
503 	//  double xmin, xmax, ymin, ymax;
504 	//  data:
505 	//    pcpoint[3] stats;
506 	//    serialized_pcbytes[ndims] dimensions;
507 
508 	int i;
509 	uint8_t *buf;
510 	size_t serpch_size = pc_patch_serialized_size(patch_in);
511 	SERIALIZED_PATCH *serpch = pcalloc(serpch_size);
512 	const PCPATCH_DIMENSIONAL *patch = (PCPATCH_DIMENSIONAL*)patch_in;
513 
514 	assert(patch_in);
515 	assert(patch_in->type == PC_DIMENSIONAL);
516 
517 	/* Copy basics */
518 	serpch->pcid = patch->schema->pcid;
519 	serpch->npoints = patch->npoints;
520 	serpch->bounds = patch->bounds;
521 	serpch->compression = patch->type;
522 
523 	/* Get a pointer to the data area */
524 	buf = serpch->data;
525 
526 	/* Write stats into the buffer */
527 	if ( patch->stats )
528 	{
529 		buf += pc_patch_stats_serialize(buf, patch->schema, patch->stats);
530 	}
531 	else
532 	{
533 		pcerror("%s: stats missing!", __func__);
534 	}
535 
536 	/* Write each dimension in after the stats */
537 	for ( i = 0; i < patch->schema->ndims; i++ )
538 	{
539 		size_t bsize = 0;
540 		PCBYTES *pcb = &(patch->bytes[i]);
541 		pc_bytes_serialize(pcb, buf, &bsize);
542 		buf += bsize;
543 	}
544 
545 	SET_VARSIZE(serpch, serpch_size);
546 	return serpch;
547 }
548 
549 
550 static SERIALIZED_PATCH *
pc_patch_lazperf_serialize(const PCPATCH * patch_in)551 pc_patch_lazperf_serialize(const PCPATCH *patch_in)
552 {
553 	size_t serpch_size = pc_patch_serialized_size(patch_in);
554 	SERIALIZED_PATCH *serpch = pcalloc(serpch_size);
555 	const PCPATCH_LAZPERF *patch = (PCPATCH_LAZPERF*)patch_in;
556 	uint32_t lazsize = patch->lazperfsize;
557 	uint8_t *buf = serpch->data;
558 
559 	assert(patch);
560 	assert(patch->type == PC_LAZPERF);
561 
562 	/* Copy basics */
563 	serpch->pcid = patch->schema->pcid;
564 	serpch->npoints = patch->npoints;
565 	serpch->bounds = patch->bounds;
566 	serpch->compression = patch->type;
567 
568 	/* Write stats into the buffer first */
569 	if ( patch->stats )
570 	{
571 		buf += pc_patch_stats_serialize(buf, patch->schema, patch->stats);
572 	}
573 	else
574 	{
575 		pcerror("%s: stats missing!", __func__);
576 	}
577 
578 	/* Write buffer size */
579 	memcpy(buf, &(lazsize), 4);
580 	buf += 4;
581 
582 	/* Write buffer */
583 	memcpy(buf, patch->lazperf, patch->lazperfsize);
584 	SET_VARSIZE(serpch, serpch_size);
585 
586 	return serpch;
587 }
588 
589 static SERIALIZED_PATCH *
pc_patch_uncompressed_serialize(const PCPATCH * patch_in)590 pc_patch_uncompressed_serialize(const PCPATCH *patch_in)
591 {
592 	//  uint32_t size;
593 	//  uint32_t pcid;
594 	//  uint32_t compression;
595 	//  uint32_t npoints;
596 	//  double xmin, xmax, ymin, ymax;
597 	//  data:
598 	//    pcpoint [];
599 
600 	uint8_t *buf;
601 	size_t serpch_size;
602 	SERIALIZED_PATCH *serpch;
603 	const PCPATCH_UNCOMPRESSED *patch = (PCPATCH_UNCOMPRESSED *)patch_in;
604 
605 	serpch_size = pc_patch_serialized_size(patch_in);
606 	serpch = pcalloc(serpch_size);
607 
608 	/* Copy basic */
609 	serpch->compression = patch->type;
610 	serpch->pcid = patch->schema->pcid;
611 	serpch->npoints = patch->npoints;
612 	serpch->bounds = patch->bounds;
613 
614 	/* Write stats into the buffer first */
615 	buf = serpch->data;
616 	if ( patch->stats )
617 	{
618 		buf += pc_patch_stats_serialize(buf, patch->schema, patch->stats);
619 	}
620 	else
621 	{
622 		pcerror("%s: stats missing!", __func__);
623 	}
624 
625 	/* Copy point list into data buffer */
626 	memcpy(buf, patch->data, patch->datasize);
627 	SET_VARSIZE(serpch, serpch_size);
628 	return serpch;
629 }
630 
631 
632 /**
633 * Convert struct to byte array.
634 * Userdata is currently only PCDIMSTATS, hopefully updated across
635 * a number of iterations and saved.
636 */
637 SERIALIZED_PATCH *
pc_patch_serialize(const PCPATCH * patch_in,void * userdata)638 pc_patch_serialize(const PCPATCH *patch_in, void *userdata)
639 {
640 	PCPATCH *patch = (PCPATCH*)patch_in;
641 	SERIALIZED_PATCH *serpatch = NULL;
642 	/*
643 	* Ensure the patch has stats calculated before going on
644 	*/
645 	if ( ! patch->stats )
646 	{
647 		pcerror("%s: patch is missing stats", __func__);
648 		return NULL;
649 	}
650 	/*
651 	* Convert the patch to the final target compression,
652 	* which is the one in the schema.
653 	*/
654 	if ( patch->type != patch->schema->compression )
655 	{
656 		patch = pc_patch_compress(patch_in, userdata);
657 	}
658 
659 	switch( patch->type )
660 	{
661 	case PC_NONE:
662 	{
663 		serpatch = pc_patch_uncompressed_serialize(patch);
664 		break;
665 	}
666 	case PC_DIMENSIONAL:
667 	{
668 		serpatch = pc_patch_dimensional_serialize(patch);
669 		break;
670 	}
671 	case PC_LAZPERF:
672 	{
673 		serpatch = pc_patch_lazperf_serialize(patch);
674 		break;
675 	}
676 	default:
677 	{
678 		pcerror("%s: unsupported compression type %d", __func__, patch->type);
679 	}
680 	}
681 
682 	if ( patch != patch_in )
683 		pc_patch_free(patch);
684 
685 	return serpatch;
686 }
687 
688 
689 
690 
691 /**
692 * Convert struct to byte array.
693 * Userdata is currently only PCDIMSTATS, hopefully updated across
694 * a number of iterations and saved.
695 */
696 SERIALIZED_PATCH *
pc_patch_serialize_to_uncompressed(const PCPATCH * patch_in)697 pc_patch_serialize_to_uncompressed(const PCPATCH *patch_in)
698 {
699 	PCPATCH *patch = (PCPATCH*)patch_in;
700 	SERIALIZED_PATCH *serpatch;
701 
702 	/*  Convert the patch to uncompressed, if necessary */
703 	if ( patch->type != PC_NONE )
704 	{
705 		patch = pc_patch_uncompress(patch_in);
706 	}
707 
708 	serpatch = pc_patch_uncompressed_serialize(patch);
709 
710 	/* An uncompressed input won't result in a copy */
711 	if ( patch != patch_in )
712 		pc_patch_free(patch);
713 
714 	return serpatch;
715 }
716 
717 static PCPATCH *
pc_patch_uncompressed_deserialize(const SERIALIZED_PATCH * serpatch,const PCSCHEMA * schema)718 pc_patch_uncompressed_deserialize(const SERIALIZED_PATCH *serpatch, const PCSCHEMA *schema)
719 {
720 	// typedef struct
721 	// {
722 	//  uint32_t size;
723 	//  uint32_t pcid;
724 	//  uint32_t compression;
725 	//  uint32_t npoints;
726 	//  double xmin, xmax, ymin, ymax;
727 	//  data:
728 	//    pcpoint[3] pcstats(min, max, avg)
729 	//    pcpoint[npoints]
730 	// }
731 	// SERIALIZED_PATCH;
732 
733 	uint8_t *buf;
734 	size_t stats_size = pc_stats_size(schema); // 3 pcpoints worth of stats
735 	PCPATCH_UNCOMPRESSED *patch = pcalloc(sizeof(PCPATCH_UNCOMPRESSED));
736 
737 	/* Set up basic info */
738 	patch->type = serpatch->compression;
739 	patch->schema = schema;
740 	patch->readonly = true;
741 	patch->npoints = serpatch->npoints;
742 	patch->maxpoints = 0;
743 	patch->bounds = serpatch->bounds;
744 
745 	buf = (uint8_t*)serpatch->data;
746 
747 	/* Point into the stats area */
748 	patch->stats = pc_patch_stats_deserialize(schema, buf);
749 
750 	/* Advance data pointer past the stats serialization */
751 	patch->data = buf + stats_size;
752 
753 	/* Calculate the point data buffer size */
754 	patch->datasize = VARSIZE(serpatch) - sizeof(SERIALIZED_PATCH) + 1 - stats_size;
755 	if ( patch->datasize != patch->npoints * schema->size )
756 		pcerror("%s: calculated patch data sizes don't match (%d != %d)", __func__, patch->datasize, patch->npoints * schema->size);
757 
758 	return (PCPATCH*)patch;
759 }
760 
761 static PCPATCH *
pc_patch_dimensional_deserialize(const SERIALIZED_PATCH * serpatch,const PCSCHEMA * schema)762 pc_patch_dimensional_deserialize(const SERIALIZED_PATCH *serpatch, const PCSCHEMA *schema)
763 {
764 	// typedef struct
765 	// {
766 	//  uint32_t size;
767 	//  uint32_t pcid;
768 	//  uint32_t compression;
769 	//  uint32_t npoints;
770 	//  double xmin, xmax, ymin, ymax;
771 	//  data:
772 	//    pcpoint[3] pcstats(min, max, avg)
773 	//    pcbytes[ndims];
774 	// }
775 	// SERIALIZED_PATCH;
776 
777 	PCPATCH_DIMENSIONAL *patch;
778 	int i;
779 	const uint8_t *buf;
780 	int ndims = schema->ndims;
781 	int npoints = serpatch->npoints;
782 	size_t stats_size = pc_stats_size(schema); // 3 pcpoints worth of stats
783 
784 	/* Reference the external data */
785 	patch = pcalloc(sizeof(PCPATCH_DIMENSIONAL));
786 
787 	/* Set up basic info */
788 	patch->type = serpatch->compression;
789 	patch->schema = schema;
790 	patch->readonly = true;
791 	patch->npoints = npoints;
792 	patch->bounds = serpatch->bounds;
793 
794 	/* Point into the stats area */
795 	patch->stats = pc_patch_stats_deserialize(schema, serpatch->data);
796 
797 	/* Set up dimensions */
798 	patch->bytes = pcalloc(ndims * sizeof(PCBYTES));
799 	buf = serpatch->data + stats_size;
800 
801 	for ( i = 0; i < ndims; i++ )
802 	{
803 		PCBYTES *pcb = &(patch->bytes[i]);
804 		PCDIMENSION *dim = schema->dims[i];
805 		pc_bytes_deserialize(buf, dim, pcb, true /*readonly*/, false /*flipendian*/);
806 		pcb->npoints = npoints;
807 		buf += pc_bytes_serialized_size(pcb);
808 	}
809 
810 	return (PCPATCH*)patch;
811 }
812 
813 /*
814 * We don't do any radical deserialization here. Don't build out the tree, just
815 * set up pointers to the start of the buffer, so we can build it out later
816 * if necessary.
817 */
818 static PCPATCH *
pc_patch_lazperf_deserialize(const SERIALIZED_PATCH * serpatch,const PCSCHEMA * schema)819 pc_patch_lazperf_deserialize(const SERIALIZED_PATCH *serpatch, const PCSCHEMA *schema)
820 {
821 	PCPATCH_LAZPERF *patch;
822 	uint32_t lazperfsize;
823 	int npoints = serpatch->npoints;
824 	size_t stats_size = pc_stats_size(schema);
825 	uint8_t *buf = (uint8_t*)serpatch->data + stats_size;
826 
827 	/* Reference the external data */
828 	patch = pcalloc(sizeof(PCPATCH_LAZPERF));
829 
830 	/* Set up basic info */
831 	patch->type = serpatch->compression;
832 	patch->schema = schema;
833 	patch->readonly = true;
834 	patch->npoints = npoints;
835 	patch->bounds = serpatch->bounds;
836 
837 	/* Point into the stats area */
838 	patch->stats = pc_patch_stats_deserialize(schema, serpatch->data);
839 
840 	/* Set up buffer */
841 	memcpy(&lazperfsize, buf, 4);
842 	patch->lazperfsize = lazperfsize;
843 	buf += 4;
844 
845 	patch->lazperf = pcalloc( patch->lazperfsize );
846 	memcpy(patch->lazperf, buf, patch->lazperfsize);
847 
848 	return (PCPATCH*)patch;
849 }
850 
851 PCPATCH *
pc_patch_deserialize(const SERIALIZED_PATCH * serpatch,const PCSCHEMA * schema)852 pc_patch_deserialize(const SERIALIZED_PATCH *serpatch, const PCSCHEMA *schema)
853 {
854 	switch(serpatch->compression)
855 	{
856 	case PC_NONE:
857 		return pc_patch_uncompressed_deserialize(serpatch, schema);
858 	case PC_DIMENSIONAL:
859 		return pc_patch_dimensional_deserialize(serpatch, schema);
860 	case PC_LAZPERF:
861 		return pc_patch_lazperf_deserialize(serpatch, schema);
862 	}
863 	pcerror("%s: unsupported compression type", __func__);
864 	return NULL;
865 }
866 
867 
868 static uint8_t *
pc_patch_wkb_set_double(uint8_t * wkb,double d)869 pc_patch_wkb_set_double(uint8_t *wkb, double d)
870 {
871 	memcpy(wkb, &d, 8);
872 	wkb += 8;
873 	return wkb;
874 }
875 
876 static uint8_t *
pc_patch_wkb_set_int32(uint8_t * wkb,uint32_t i)877 pc_patch_wkb_set_int32(uint8_t *wkb, uint32_t i)
878 {
879 	memcpy(wkb, &i, 4);
880 	wkb += 4;
881 	return wkb;
882 }
883 
884 static uint8_t *
pc_patch_wkb_set_char(uint8_t * wkb,char c)885 pc_patch_wkb_set_char(uint8_t *wkb, char c)
886 {
887 	memcpy(wkb, &c, 1);
888 	wkb += 1;
889 	return wkb;
890 }
891 
892 /* 0 = xdr | big endian */
893 /* 1 = ndr | little endian */
894 static char
machine_endian(void)895 machine_endian(void)
896 {
897 	static int check_int = 1; /* dont modify this!!! */
898 	return *((char *) &check_int);
899 }
900 
901 uint8_t *
pc_patch_to_geometry_wkb_envelope(const SERIALIZED_PATCH * pa,const PCSCHEMA * schema,size_t * wkbsize)902 pc_patch_to_geometry_wkb_envelope(const SERIALIZED_PATCH *pa, const PCSCHEMA *schema, size_t *wkbsize)
903 {
904 	static uint32_t srid_mask = 0x20000000;
905 	static uint32_t nrings = 1;
906 	static uint32_t npoints = 5;
907 	uint32_t wkbtype = 3; /* WKB POLYGON */
908 	uint8_t *wkb, *ptr;
909 	int has_srid = false;
910 	size_t size = 1 + 4 + 4 + 4 + 2*npoints*8; /* endian + type + nrings + npoints + 5 dbl pts */
911 
912 	/* Bounds! */
913 	double xmin = pa->bounds.xmin;
914 	double ymin = pa->bounds.ymin;
915 	double xmax = pa->bounds.xmax;
916 	double ymax = pa->bounds.ymax;
917 
918 	/* Make sure they're slightly bigger than a point */
919 	if ( xmin == xmax ) xmax += xmax * 0.0000001;
920 	if ( ymin == ymax ) ymax += ymax * 0.0000001;
921 
922 	if ( schema->srid > 0 )
923 	{
924 		has_srid = true;
925 		wkbtype |= srid_mask;
926 		size += 4;
927 	}
928 
929 	wkb = palloc(size);
930 	ptr = wkb;
931 
932 	ptr = pc_patch_wkb_set_char(ptr, machine_endian()); /* Endian flag */
933 
934 	ptr = pc_patch_wkb_set_int32(ptr, wkbtype); /* TYPE = Polygon */
935 
936 	if ( has_srid )
937 	{
938 		ptr = pc_patch_wkb_set_int32(ptr, schema->srid); /* SRID */
939 	}
940 
941 	ptr = pc_patch_wkb_set_int32(ptr, nrings);  /* NRINGS = 1 */
942 	ptr = pc_patch_wkb_set_int32(ptr, npoints); /* NPOINTS = 5 */
943 
944 	/* Point 0 */
945 	ptr = pc_patch_wkb_set_double(ptr, pa->bounds.xmin);
946 	ptr = pc_patch_wkb_set_double(ptr, pa->bounds.ymin);
947 
948 	/* Point 1 */
949 	ptr = pc_patch_wkb_set_double(ptr, pa->bounds.xmin);
950 	ptr = pc_patch_wkb_set_double(ptr, pa->bounds.ymax);
951 
952 	/* Point 2 */
953 	ptr = pc_patch_wkb_set_double(ptr, pa->bounds.xmax);
954 	ptr = pc_patch_wkb_set_double(ptr, pa->bounds.ymax);
955 
956 	/* Point 3 */
957 	ptr = pc_patch_wkb_set_double(ptr, pa->bounds.xmax);
958 	ptr = pc_patch_wkb_set_double(ptr, pa->bounds.ymin);
959 
960 	/* Point 4 */
961 	ptr = pc_patch_wkb_set_double(ptr, pa->bounds.xmin);
962 	ptr = pc_patch_wkb_set_double(ptr, pa->bounds.ymin);
963 
964 	if ( wkbsize ) *wkbsize = size;
965 	return wkb;
966 }
967