1d9d3f137a
For example, a column with type Geometry(MultiPoint, 4326) will accept an insert of a Point, but the point will be converted to a single-entry MultiPoint on insert.
464 lines
13 KiB
C
464 lines
13 KiB
C
/**********************************************************************
|
|
*
|
|
* PostGIS - Spatial Types for PostgreSQL
|
|
* http://postgis.net
|
|
*
|
|
* PostGIS is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* PostGIS is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with PostGIS. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
**********************************************************************
|
|
*
|
|
* Copyright 2009 Paul Ramsey <pramsey@cleverelephant.ca>
|
|
*
|
|
**********************************************************************/
|
|
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "../postgis_config.h"
|
|
|
|
#include <math.h>
|
|
#include <float.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
|
|
#include "utils/elog.h"
|
|
#include "utils/array.h"
|
|
#include "utils/builtins.h" /* for cstring_to_text */
|
|
#include "lib/stringinfo.h" /* For binary input */
|
|
#include "catalog/pg_type.h" /* for CSTRINGOID */
|
|
|
|
#include "liblwgeom.h" /* For standard geometry types. */
|
|
#include "lwgeom_pg.h" /* For debugging macros. */
|
|
#include "geography.h" /* For utility functions. */
|
|
#include "lwgeom_transform.h" /* for srid_is_latlon */
|
|
|
|
|
|
Datum geography_typmod_in(PG_FUNCTION_ARGS);
|
|
Datum geometry_typmod_in(PG_FUNCTION_ARGS);
|
|
Datum postgis_typmod_out(PG_FUNCTION_ARGS);
|
|
Datum postgis_typmod_dims(PG_FUNCTION_ARGS);
|
|
Datum postgis_typmod_srid(PG_FUNCTION_ARGS);
|
|
Datum postgis_typmod_type(PG_FUNCTION_ARGS);
|
|
Datum geography_enforce_typmod(PG_FUNCTION_ARGS);
|
|
Datum geometry_enforce_typmod(PG_FUNCTION_ARGS);
|
|
|
|
|
|
/*
|
|
** postgis_typmod_out(int) returns cstring
|
|
*/
|
|
PG_FUNCTION_INFO_V1(postgis_typmod_out);
|
|
Datum postgis_typmod_out(PG_FUNCTION_ARGS)
|
|
{
|
|
StringInfoData si;
|
|
// char *s = (char*)palloc(64);
|
|
int32 typmod = PG_GETARG_INT32(0);
|
|
int32 srid = TYPMOD_GET_SRID(typmod);
|
|
int32 type = TYPMOD_GET_TYPE(typmod);
|
|
int32 hasz = TYPMOD_GET_Z(typmod);
|
|
int32 hasm = TYPMOD_GET_M(typmod);
|
|
|
|
POSTGIS_DEBUGF(3, "Got typmod(srid = %d, type = %d, hasz = %d, hasm = %d)", srid, type, hasz, hasm);
|
|
|
|
/* No SRID or type or dimensionality? Then no typmod at all. Return empty string. */
|
|
if (!(srid || type || hasz || hasm) || typmod < 0)
|
|
{
|
|
PG_RETURN_CSTRING(pstrdup(""));
|
|
}
|
|
|
|
/* Opening bracket. */
|
|
initStringInfo(&si);
|
|
appendStringInfoChar(&si, '(');
|
|
|
|
/* Has type? */
|
|
if (type)
|
|
appendStringInfo(&si, "%s", lwtype_name(type));
|
|
else if (srid || hasz || hasm)
|
|
appendStringInfoString(&si, "Geometry");
|
|
|
|
/* Has Z? */
|
|
if (hasz) appendStringInfoString(&si, "Z");
|
|
|
|
/* Has M? */
|
|
if (hasm) appendStringInfoString(&si, "M");
|
|
|
|
/* Has SRID? */
|
|
if (srid) appendStringInfo(&si, ",%d", srid);
|
|
|
|
/* Closing bracket. */
|
|
appendStringInfoChar(&si, ')');
|
|
|
|
PG_RETURN_CSTRING(si.data);
|
|
}
|
|
|
|
|
|
/**
|
|
* Check the consistency of the metadata we want to enforce in the typmod:
|
|
* srid, type and dimensionality. If things are inconsistent, shut down the query.
|
|
*/
|
|
GSERIALIZED* postgis_valid_typmod(GSERIALIZED *gser, int32_t typmod)
|
|
{
|
|
int32 geom_srid = gserialized_get_srid(gser);
|
|
int32 geom_type = gserialized_get_type(gser);
|
|
int32 geom_z = gserialized_has_z(gser);
|
|
int32 geom_m = gserialized_has_m(gser);
|
|
int32 typmod_srid = TYPMOD_GET_SRID(typmod);
|
|
int32 typmod_type = TYPMOD_GET_TYPE(typmod);
|
|
int32 typmod_z = TYPMOD_GET_Z(typmod);
|
|
int32 typmod_m = TYPMOD_GET_M(typmod);
|
|
|
|
POSTGIS_DEBUG(2, "Entered function");
|
|
|
|
/* No typmod (-1) => no preferences */
|
|
if (typmod < 0) return gser;
|
|
|
|
POSTGIS_DEBUGF(3, "Got geom(type = %d, srid = %d, hasz = %d, hasm = %d)", geom_type, geom_srid, geom_z, geom_m);
|
|
POSTGIS_DEBUGF(3, "Got typmod(type = %d, srid = %d, hasz = %d, hasm = %d)", typmod_type, typmod_srid, typmod_z, typmod_m);
|
|
|
|
/*
|
|
* #3031: If a user is handing us a MULTIPOINT EMPTY but trying to fit it into
|
|
* a POINT geometry column, there's a strong chance the reason she has
|
|
* a MULTIPOINT EMPTY because we gave it to her during data dump,
|
|
* converting the internal POINT EMPTY into a EWKB MULTIPOINT EMPTY
|
|
* (because EWKB doesn't have a clean way to represent POINT EMPTY).
|
|
* In such a case, it makes sense to turn the MULTIPOINT EMPTY back into a
|
|
* point EMPTY, rather than throwing an error.
|
|
*/
|
|
if ( typmod_type == POINTTYPE && geom_type == MULTIPOINTTYPE &&
|
|
gserialized_is_empty(gser) )
|
|
{
|
|
LWPOINT *empty_point = lwpoint_construct_empty(geom_srid, geom_z, geom_m);
|
|
geom_type = POINTTYPE;
|
|
pfree(gser);
|
|
if ( gserialized_is_geodetic(gser) )
|
|
gser = geography_serialize(lwpoint_as_lwgeom(empty_point));
|
|
else
|
|
gser = geometry_serialize(lwpoint_as_lwgeom(empty_point));
|
|
}
|
|
|
|
/* Typmod has a preference for SRID, but geometry does not? Harmonize the geometry SRID. */
|
|
if ( typmod_srid > 0 && geom_srid == 0 )
|
|
{
|
|
gserialized_set_srid(gser, typmod_srid);
|
|
geom_srid = typmod_srid;
|
|
}
|
|
|
|
/* Typmod has a preference for SRID? Geometry SRID had better match. */
|
|
if ( typmod_srid > 0 && typmod_srid != geom_srid )
|
|
{
|
|
ereport(ERROR, (
|
|
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("Geometry SRID (%d) does not match column SRID (%d)", geom_srid, typmod_srid) ));
|
|
}
|
|
|
|
/* Typmod has a preference for MULTI* geometry type */
|
|
/* and geometry is the singleton type. */
|
|
if ( typmod_type > 0 && typmod_type == lwtype_multitype(geom_type) )
|
|
{
|
|
/* Promote the singleton to equivalent multi */
|
|
LWGEOM *geom = lwgeom_from_gserialized(gser);
|
|
LWGEOM *mgeom = lwgeom_as_multi(geom);
|
|
GSERIALIZED *mgser = gserialized_is_geodetic(gser) ?
|
|
geography_serialize(mgeom) :
|
|
geometry_serialize(mgeom);
|
|
/* Count on caller memory context cleaning up dangling gserialized */
|
|
gser = mgser;
|
|
geom_type = gserialized_get_type(gser);
|
|
lwgeom_free(geom);
|
|
lwgeom_free(mgeom);
|
|
}
|
|
|
|
/* Typmod has a preference for geometry type. */
|
|
if ( typmod_type > 0 &&
|
|
/* GEOMETRYCOLLECTION column can hold any kind of collection */
|
|
((typmod_type == COLLECTIONTYPE && ! (geom_type == COLLECTIONTYPE ||
|
|
geom_type == MULTIPOLYGONTYPE ||
|
|
geom_type == MULTIPOINTTYPE ||
|
|
geom_type == MULTILINETYPE )) ||
|
|
/* Other types must be strictly equal. */
|
|
(typmod_type != geom_type)) )
|
|
{
|
|
ereport(ERROR, (
|
|
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("Geometry type (%s) does not match column type (%s)", lwtype_name(geom_type), lwtype_name(typmod_type)) ));
|
|
}
|
|
|
|
/* Mismatched Z dimensionality. */
|
|
if ( typmod_z && ! geom_z )
|
|
{
|
|
ereport(ERROR, (
|
|
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("Column has Z dimension but geometry does not" )));
|
|
}
|
|
|
|
/* Mismatched Z dimensionality (other way). */
|
|
if ( geom_z && ! typmod_z )
|
|
{
|
|
ereport(ERROR, (
|
|
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("Geometry has Z dimension but column does not" )));
|
|
}
|
|
|
|
/* Mismatched M dimensionality. */
|
|
if ( typmod_m && ! geom_m )
|
|
{
|
|
ereport(ERROR, (
|
|
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("Column has M dimension but geometry does not" )));
|
|
}
|
|
|
|
/* Mismatched M dimensionality (other way). */
|
|
if ( geom_m && ! typmod_m )
|
|
{
|
|
ereport(ERROR, (
|
|
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("Geometry has M dimension but column does not" )));
|
|
}
|
|
|
|
return gser;
|
|
|
|
}
|
|
|
|
|
|
static uint32 gserialized_typmod_in(ArrayType *arr, int is_geography)
|
|
{
|
|
int32 typmod = 0;
|
|
Datum *elem_values;
|
|
int n = 0;
|
|
int i = 0;
|
|
|
|
if (ARR_ELEMTYPE(arr) != CSTRINGOID)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
|
|
errmsg("typmod array must be type cstring[]")));
|
|
|
|
if (ARR_NDIM(arr) != 1)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
|
|
errmsg("typmod array must be one-dimensional")));
|
|
|
|
if (ARR_HASNULL(arr))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
errmsg("typmod array must not contain nulls")));
|
|
|
|
deconstruct_array(arr,
|
|
CSTRINGOID, -2, false, 'c', /* hardwire cstring representation details */
|
|
&elem_values, NULL, &n);
|
|
|
|
/* Set the SRID to the default value first */
|
|
if (is_geography)
|
|
TYPMOD_SET_SRID(typmod, SRID_DEFAULT);
|
|
else
|
|
TYPMOD_SET_SRID(typmod, SRID_UNKNOWN);
|
|
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
if ( i == 0 ) /* TYPE */
|
|
{
|
|
char *s = DatumGetCString(elem_values[i]);
|
|
uint8_t type = 0;
|
|
int z = 0;
|
|
int m = 0;
|
|
|
|
if ( geometry_type_from_string(s, &type, &z, &m) == LW_FAILURE )
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("Invalid geometry type modifier: %s", s)));
|
|
}
|
|
else
|
|
{
|
|
TYPMOD_SET_TYPE(typmod, type);
|
|
if ( z )
|
|
TYPMOD_SET_Z(typmod);
|
|
if ( m )
|
|
TYPMOD_SET_M(typmod);
|
|
}
|
|
}
|
|
if ( i == 1 ) /* SRID */
|
|
{
|
|
char *int_string = DatumGetCString(elem_values[i]);
|
|
char *endp;
|
|
long l;
|
|
int32_t srid;
|
|
|
|
errno = 0;
|
|
l = strtol(int_string, &endp, 10);
|
|
|
|
if (int_string == endp)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
errmsg("invalid input syntax for type %s: \"%s\"",
|
|
"integer", int_string)));
|
|
|
|
if (errno == ERANGE || l < INT_MIN || l > INT_MAX)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("value \"%s\" is out of range for type %s", int_string,
|
|
"integer")));
|
|
|
|
if (*endp != '\0')
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
errmsg("invalid input syntax for type %s: \"%s\"",
|
|
"integer", int_string)));
|
|
|
|
srid = clamp_srid(l);
|
|
POSTGIS_DEBUGF(3, "srid: %d", srid);
|
|
if ( srid != SRID_UNKNOWN )
|
|
{
|
|
TYPMOD_SET_SRID(typmod, srid);
|
|
}
|
|
}
|
|
}
|
|
|
|
pfree(elem_values);
|
|
|
|
return typmod;
|
|
}
|
|
|
|
/*
|
|
** geography_typmod_in(cstring[]) returns int32
|
|
**
|
|
** Modified from ArrayGetIntegerTypmods in PostgreSQL 8.3
|
|
*/
|
|
PG_FUNCTION_INFO_V1(geography_typmod_in);
|
|
Datum geography_typmod_in(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *arr = (ArrayType *) DatumGetPointer(PG_GETARG_DATUM(0));
|
|
int32 typmod = gserialized_typmod_in(arr, LW_TRUE);
|
|
int32_t srid = TYPMOD_GET_SRID(typmod);
|
|
/* Check the SRID is legal (geographic coordinates) */
|
|
srid_check_latlong(srid);
|
|
|
|
PG_RETURN_INT32(typmod);
|
|
}
|
|
|
|
/*
|
|
** geometry_typmod_in(cstring[]) returns int32
|
|
**
|
|
** Modified from ArrayGetIntegerTypmods in PostgreSQL 8.3
|
|
*/
|
|
PG_FUNCTION_INFO_V1(geometry_typmod_in);
|
|
Datum geometry_typmod_in(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *arr = (ArrayType *) DatumGetPointer(PG_GETARG_DATUM(0));
|
|
uint32 typmod = gserialized_typmod_in(arr, LW_FALSE); /* Not a geography */;
|
|
PG_RETURN_INT32(typmod);
|
|
}
|
|
|
|
/*
|
|
** geography_enforce_typmod(*GSERIALIZED, uint32) returns *GSERIALIZED
|
|
** Ensure that an incoming geometry conforms to typmod restrictions on
|
|
** type, dims and srid.
|
|
*/
|
|
PG_FUNCTION_INFO_V1(geography_enforce_typmod);
|
|
Datum geography_enforce_typmod(PG_FUNCTION_ARGS)
|
|
{
|
|
GSERIALIZED *arg = PG_GETARG_GSERIALIZED_P(0);
|
|
int32 typmod = PG_GETARG_INT32(1);
|
|
/* We don't need to have different behavior based on explicitness. */
|
|
/* bool isExplicit = PG_GETARG_BOOL(2); */
|
|
|
|
/* Check if geometry typmod is consistent with the supplied one. */
|
|
arg = postgis_valid_typmod(arg, typmod);
|
|
|
|
PG_RETURN_POINTER(arg);
|
|
}
|
|
|
|
/*
|
|
** geometry_enforce_typmod(*GSERIALIZED, uint32) returns *GSERIALIZED
|
|
** Ensure that an incoming geometry conforms to typmod restrictions on
|
|
** type, dims and srid.
|
|
*/
|
|
PG_FUNCTION_INFO_V1(geometry_enforce_typmod);
|
|
Datum geometry_enforce_typmod(PG_FUNCTION_ARGS)
|
|
{
|
|
GSERIALIZED *arg = PG_GETARG_GSERIALIZED_P(0);
|
|
int32 typmod = PG_GETARG_INT32(1);
|
|
/* We don't need to have different behavior based on explicitness. */
|
|
/* bool isExplicit = PG_GETARG_BOOL(2); */
|
|
|
|
/* Check if geometry typmod is consistent with the supplied one. */
|
|
arg = postgis_valid_typmod(arg, typmod);
|
|
|
|
PG_RETURN_POINTER(arg);
|
|
}
|
|
|
|
|
|
/*
|
|
** postgis_typmod_type(uint32) returns cstring
|
|
** Used for geometry_columns and other views on system tables
|
|
*/
|
|
PG_FUNCTION_INFO_V1(postgis_typmod_type);
|
|
Datum postgis_typmod_type(PG_FUNCTION_ARGS)
|
|
{
|
|
int32 typmod = PG_GETARG_INT32(0);
|
|
int32 type = TYPMOD_GET_TYPE(typmod);
|
|
char *s = (char*)palloc(64);
|
|
char *ptr = s;
|
|
text *stext;
|
|
|
|
/* Has type? */
|
|
if ( typmod < 0 || type == 0 )
|
|
ptr += sprintf(ptr, "Geometry");
|
|
else
|
|
ptr += sprintf(ptr, "%s", lwtype_name(type));
|
|
|
|
/* Has Z? */
|
|
if ( typmod >= 0 && TYPMOD_GET_Z(typmod) )
|
|
ptr += sprintf(ptr, "%s", "Z");
|
|
|
|
/* Has M? */
|
|
if ( typmod >= 0 && TYPMOD_GET_M(typmod) )
|
|
ptr += sprintf(ptr, "%s", "M");
|
|
|
|
stext = cstring_to_text(s);
|
|
pfree(s);
|
|
PG_RETURN_TEXT_P(stext);
|
|
}
|
|
|
|
/*
|
|
** postgis_typmod_dims(uint32) returns int
|
|
** Used for geometry_columns and other views on system tables
|
|
*/
|
|
PG_FUNCTION_INFO_V1(postgis_typmod_dims);
|
|
Datum postgis_typmod_dims(PG_FUNCTION_ARGS)
|
|
{
|
|
int32 typmod = PG_GETARG_INT32(0);
|
|
int32 dims = 2;
|
|
if ( typmod < 0 )
|
|
PG_RETURN_NULL(); /* unconstrained */
|
|
if ( TYPMOD_GET_Z(typmod) )
|
|
dims++;
|
|
if ( TYPMOD_GET_M(typmod) )
|
|
dims++;
|
|
PG_RETURN_INT32(dims);
|
|
}
|
|
|
|
/*
|
|
** postgis_typmod_srid(uint32) returns int
|
|
** Used for geometry_columns and other views on system tables
|
|
*/
|
|
PG_FUNCTION_INFO_V1(postgis_typmod_srid);
|
|
Datum postgis_typmod_srid(PG_FUNCTION_ARGS)
|
|
{
|
|
int32 typmod = PG_GETARG_INT32(0);
|
|
if ( typmod < 0 )
|
|
PG_RETURN_INT32(0);
|
|
PG_RETURN_INT32(TYPMOD_GET_SRID(typmod));
|
|
}
|
|
|