From e8a7788d00102e311e4341f5040586e461acd3e7 Mon Sep 17 00:00:00 2001 From: Paul Ramsey Date: Fri, 18 Jan 2013 16:15:39 -0800 Subject: [PATCH] Add in XML function to pgsql Set up XML validation tests --- libpc/Makefile | 1 - libpc/cunit/data/simple-schema.xml | 45 +++++++++++++++++ libpc/pc_api.h | 3 ++ libpc/pc_api_internal.h | 2 + libpc/pc_core.c | 12 ----- libpc/pc_mem.c | 10 +++- libpc/pc_schema.c | 56 +++++++++++++++------ pgsql/pc_inout.c | 12 +++-- pgsql/pc_pgsql.c | 79 ++++++++++++++++++++++++++++++ pgsql/pointcloud--1.0.sql | 10 +++- 10 files changed, 198 insertions(+), 32 deletions(-) create mode 100644 libpc/cunit/data/simple-schema.xml delete mode 100644 libpc/pc_core.c diff --git a/libpc/Makefile b/libpc/Makefile index 2543cac..c6a2461 100644 --- a/libpc/Makefile +++ b/libpc/Makefile @@ -5,7 +5,6 @@ CPPFLAGS = $(XML2_CPPFLAGS) LDFLAGS = $(XML2_LDFLAGS) OBJS = \ - pc_core.o \ pc_mem.o \ pc_patch.o \ pc_point.o \ diff --git a/libpc/cunit/data/simple-schema.xml b/libpc/cunit/data/simple-schema.xml new file mode 100644 index 0000000..26a55a1 --- /dev/null +++ b/libpc/cunit/data/simple-schema.xml @@ -0,0 +1,45 @@ + + + + 1 + 4 + X coordinate as a long integer. You must use the scale and offset information of the header to determine the double value. + X + int32_t + 0.01 + + + 2 + 4 + Y coordinate as a long integer. You must use the scale and offset information of the header to determine the double value. + Y + int32_t + 0.01 + + + 2 + 4 + Z coordinate as a long integer. You must use the scale and offset information of the header to determine the double value. + Z + int32_t + 0.01 + + + 4 + 2 + The intensity value is the integer representation of the pulse return magnitude. This value is optional and system specific. However, it should always be included if available. + Intensity + uint16_t + 1 + + + + + + + + + + 4326 + + diff --git a/libpc/pc_api.h b/libpc/pc_api.h index 470fb41..833ad23 100644 --- a/libpc/pc_api.h +++ b/libpc/pc_api.h @@ -14,6 +14,7 @@ #include #include #include +#include #include "hashtable.h" @@ -21,6 +22,8 @@ * DATA STRUCTURES */ +#define POINTCLOUD_VERSION "1.0" + /** * Compression types for PCPOINTS in a PCPATCH */ diff --git a/libpc/pc_api_internal.h b/libpc/pc_api_internal.h index 2cb008f..a15cc7b 100644 --- a/libpc/pc_api_internal.h +++ b/libpc/pc_api_internal.h @@ -87,6 +87,8 @@ uint8_t* bytes_from_hexbytes(const char *hexbuf, size_t hexsize); /** Turn a binary buffer into a hex string */ char* hexbytes_from_bytes(const uint8_t *bytebuf, size_t bytesize); +/** Copy a string within the global memory management context */ +char* pcstrdup(const char *str); #endif /* _PC_API_INTERNAL_H */ \ No newline at end of file diff --git a/libpc/pc_core.c b/libpc/pc_core.c deleted file mode 100644 index 94a6f1a..0000000 --- a/libpc/pc_core.c +++ /dev/null @@ -1,12 +0,0 @@ -/*********************************************************************** -* pc_core.c -* -* Core routines for point clouds -* -* Portions Copyright (c) 2012, OpenGeo -* -***********************************************************************/ - -#include "pc_api_internal.h" - - diff --git a/libpc/pc_mem.c b/libpc/pc_mem.c index e33f308..32394f1 100644 --- a/libpc/pc_mem.c +++ b/libpc/pc_mem.c @@ -62,7 +62,7 @@ default_msg_handler(const char *label, const char *fmt, va_list ap) static void default_info_handler(const char *fmt, va_list ap) { - // default_msg_handler("INFO: ", fmt, ap); + default_msg_handler("INFO: ", fmt, ap); } static void @@ -111,6 +111,14 @@ pcalloc(size_t size) return mem; } +char * +pcstrdup(const char *str) +{ + size_t len = strlen(str); + char *newstr = pcalloc(len + 1); + memcpy(newstr, str, len + 1); + return newstr; +} void * pcrealloc(void * mem, size_t size) diff --git a/libpc/pc_schema.c b/libpc/pc_schema.c index 7f82da9..1f2344e 100644 --- a/libpc/pc_schema.c +++ b/libpc/pc_schema.c @@ -206,6 +206,7 @@ PCSCHEMA* pc_schema_from_xml(const char *xml_str) xmlXPathContextPtr xpath_ctx; xmlXPathObjectPtr xpath_obj; xmlNodeSetPtr nodes; + PCSCHEMA *s = NULL; size_t xml_size = strlen(xml_str); static xmlChar *xpath_str = "/pc:PointCloudSchema/pc:dimension"; @@ -214,7 +215,10 @@ PCSCHEMA* pc_schema_from_xml(const char *xml_str) xmlInitParser(); xml_doc = xmlReadMemory(xml_str, xml_size, NULL, NULL, 0); if ( ! xml_doc ) - pcerror("Unable to parse XML"); + { + xmlCleanupParser(); + pcerror("unable to parse schema XML"); + } /* Capture the namespace */ xml_root = xmlDocGetRootElement(xml_doc); @@ -224,7 +228,11 @@ PCSCHEMA* pc_schema_from_xml(const char *xml_str) /* Create xpath evaluation context */ xpath_ctx = xmlXPathNewContext(xml_doc); if( ! xpath_ctx ) - pcerror("Unable to create new XPath context"); + { + xmlFreeDoc(xml_doc); + xmlCleanupParser(); + pcerror("unable to create new XPath context to read schema XML"); + } /* Register the root namespace if there is one */ if ( xml_ns ) @@ -233,14 +241,19 @@ PCSCHEMA* pc_schema_from_xml(const char *xml_str) /* Evaluate xpath expression */ xpath_obj = xmlXPathEvalExpression(xpath_str, xpath_ctx); if( ! xpath_obj ) - pcerror("Unable to evaluate xpath expression \"%s\"", xpath_str); + { + xmlXPathFreeContext(xpath_ctx); + xmlFreeDoc(xml_doc); + xmlCleanupParser(); + pcerror("unable to evaluate xpath expression \"%s\" against schema XML", xpath_str); + } /* Iterate on the dimensions we found */ if ( nodes = xpath_obj->nodesetval ) { int ndims = nodes->nodeNr; int i; - PCSCHEMA *s = pc_schema_new(ndims); + s = pc_schema_new(ndims); for ( i = 0; i < ndims; i++ ) { @@ -249,7 +262,6 @@ PCSCHEMA* pc_schema_from_xml(const char *xml_str) { xmlNodePtr cur = nodes->nodeTab[i]; xmlNodePtr child; - int position = -1; PCDIMENSION *d = pc_dimension_new(); int is_x = 0, is_y = 0; @@ -272,10 +284,10 @@ PCSCHEMA* pc_schema_from_xml(const char *xml_str) { is_y = 1; } - d->name = strdup(child->children->content); + d->name = pcstrdup(child->children->content); } else if ( strcmp(child->name, "description") == 0 ) - d->description = strdup(child->children->content); + d->description = pcstrdup(child->children->content); else if ( strcmp(child->name, "size") == 0 ) d->size = atoi(child->children->content); else if ( strcmp(child->name, "active") == 0 ) @@ -287,22 +299,32 @@ PCSCHEMA* pc_schema_from_xml(const char *xml_str) else if ( strcmp(child->name, "scale") == 0 ) d->scale = atof(child->children->content); else - pcinfo("Unhandled schema type element \"%s\" encountered", child->name); + pcinfo("unhandled schema type element \"%s\" encountered", child->name); } } /* Store the dimension in the schema */ if ( d->position >= 0 && d->position < ndims ) { + if ( s->dims[d->position] ) + { + xmlXPathFreeObject(xpath_obj); + xmlXPathFreeContext(xpath_ctx); + xmlFreeDoc(xml_doc); + xmlCleanupParser(); + pcerror("schema dimension at position \"%d\" is declared twice", d->position + 1, ndims); + } s->dims[d->position] = d; d->size = pc_interpretation_size(d->interpretation); s->size += d->size; if ( is_x ) { s->x_position = d->position; } if ( is_y ) { s->y_position = d->position; } - hashtable_insert(s->namehash, strdup(d->name), d); + hashtable_insert(s->namehash, pcstrdup(d->name), d); } else - pcwarn("Dimension at position \"%d\" discarded", position); + { + pcwarn("schema dimension states position \"%d\", but number of XML dimensions is \"%d\"", d->position + 1, ndims); + } } } @@ -310,7 +332,13 @@ PCSCHEMA* pc_schema_from_xml(const char *xml_str) pc_schema_calculate_byteoffsets(s); } - return NULL; + + xmlXPathFreeObject(xpath_obj); + xmlXPathFreeContext(xpath_ctx); + xmlFreeDoc(xml_doc); + xmlCleanupParser(); + + return s; } uint32_t @@ -320,13 +348,13 @@ pc_schema_is_valid(const PCSCHEMA *s) if ( s->x_position < 0 ) { - pcwarn("schema does not include X coordinate"); + pcwarn("schema does not include an X coordinate"); return PC_FALSE; } if ( s->y_position < 0 ) { - pcwarn("schema does not include Y coordinate"); + pcwarn("schema does not include a Y coordinate"); return PC_FALSE; } @@ -340,7 +368,7 @@ pc_schema_is_valid(const PCSCHEMA *s) { if ( ! s->dims[i] ) { - pcwarn("schema has null dimension at position %d", i); + pcwarn("schema is missing a dimension at position %d", i); return PC_FALSE; } } diff --git a/pgsql/pc_inout.c b/pgsql/pc_inout.c index a17b206..f3022d6 100644 --- a/pgsql/pc_inout.c +++ b/pgsql/pc_inout.c @@ -1,9 +1,12 @@ #include "pc_pgsql.h" /* Common PgSQL support for our type */ +/* In/out functions */ Datum pcpoint_in(PG_FUNCTION_ARGS); Datum pcpoint_out(PG_FUNCTION_ARGS); -Datum pcschema_is_valid(PG_FUNCTION_ARGS); + +/* Other SQL functions */ +Datum PC_SchemaIsValid(PG_FUNCTION_ARGS); @@ -39,8 +42,8 @@ Datum pcschema_is_valid(PG_FUNCTION_ARGS); // } // -PG_FUNCTION_INFO_V1(pcschema_is_valid); -Datum pcschema_is_valid(PG_FUNCTION_ARGS) +PG_FUNCTION_INFO_V1(PC_SchemaIsValid); +Datum PC_SchemaIsValid(PG_FUNCTION_ARGS) { bool valid; text *xml = PG_GETARG_TEXT_P(0); @@ -49,7 +52,10 @@ Datum pcschema_is_valid(PG_FUNCTION_ARGS) pfree(xmlstr); if ( ! schema ) + { + elog(NOTICE, "pc_schema_from_xml returned NULL"); PG_RETURN_BOOL(FALSE); + } valid = pc_schema_is_valid(schema); PG_RETURN_BOOL(valid); diff --git a/pgsql/pc_pgsql.c b/pgsql/pc_pgsql.c index 1b22966..91e32e2 100644 --- a/pgsql/pc_pgsql.c +++ b/pgsql/pc_pgsql.c @@ -2,6 +2,85 @@ #include "pc_pgsql.h" #include "executor/spi.h" +PG_MODULE_MAGIC; + + +static void * +pgsql_alloc(size_t size) +{ + void * result; + result = palloc(size); + + if ( ! result ) + { + ereport(ERROR, (errmsg_internal("Out of virtual memory"))); + return NULL; + } + return result; +} + +static void * +pgsql_realloc(void *mem, size_t size) +{ + void * result; + result = repalloc(mem, size); + return result; +} + +static void +pgsql_free(void *ptr) +{ + pfree(ptr); +} + +static void +pgsql_msg_handler(int sig, const char *fmt, va_list ap) +{ +#define MSG_MAXLEN 1024 + char msg[MSG_MAXLEN] = {0}; + vsnprintf(msg, MSG_MAXLEN, fmt, ap); + msg[MSG_MAXLEN-1] = '\0'; + ereport(sig, (errmsg_internal("%s", msg))); +} + +static void +pgsql_error(const char *fmt, va_list ap) +{ + pgsql_msg_handler(ERROR, fmt, ap); +} + +static void +pgsql_warn(const char *fmt, va_list ap) +{ + pgsql_msg_handler(WARNING, fmt, ap); +} + +static void +pgsql_info(const char *fmt, va_list ap) +{ + pgsql_msg_handler(NOTICE, fmt, ap); +} + +/* Module loading callback */ +void _PG_init(void); +void +_PG_init(void) +{ + elog(LOG, "Pointcloud (%s) module loaded", POINTCLOUD_VERSION); + pc_set_handlers(pgsql_alloc, pgsql_realloc, + pgsql_free, pgsql_error, + pgsql_info, pgsql_warn); +} + +/* Module unload callback */ +void _PG_fini(void); +void +_PG_fini(void) +{ + elog(LOG, "Pointcloud (%s) module unloaded", POINTCLOUD_VERSION); +} + + /** * TODO: Back this routine with a statement level memory cache. */ diff --git a/pgsql/pointcloud--1.0.sql b/pgsql/pointcloud--1.0.sql index af45ab1..381dffb 100644 --- a/pgsql/pointcloud--1.0.sql +++ b/pgsql/pointcloud--1.0.sql @@ -2,11 +2,19 @@ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pointcloud" to load this file. \quit + +-- Confirm the XML representation of a schema has everything we need +CREATE OR REPLACE FUNCTION PC_SchemaIsValid(xml text) + RETURNS boolean AS 'MODULE_PATHNAME','PC_SchemaIsValid' + LANGUAGE 'c' IMMUTABLE STRICT; + -- Metadata table describing contents of pcpoints CREATE TABLE pointcloud_formats ( pcid INTEGER PRIMARY KEY, srid INTEGER, -- REFERENCES spatial_ref_sys(srid) - schema XML + schema TEXT + CHECK ( PC_SchemaIsValid(schema) ) ); + -- Register pointcloud_formats table so the contents are included in pg_dump output SELECT pg_catalog.pg_extension_config_dump('pointcloud_formats', ''); \ No newline at end of file