Exemplo n.º 1
0
Datum box2df_out(PG_FUNCTION_ARGS)
{
	ereport(ERROR,(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
	               errmsg("function box2df_out not implemented")));
	PG_RETURN_POINTER(NULL);
}
Exemplo n.º 2
0
Datum gserialized_gist_compress_2d(PG_FUNCTION_ARGS)
{
	GISTENTRY *entry_in = (GISTENTRY*)PG_GETARG_POINTER(0);
	GISTENTRY *entry_out = NULL;
	BOX2DF bbox_out;
	int result = LW_SUCCESS;

	POSTGIS_DEBUG(4, "[GIST] 'compress' function called");

	/*
	** Not a leaf key? There's nothing to do.
	** Return the input unchanged.
	*/
	if ( ! entry_in->leafkey )
	{
		POSTGIS_DEBUG(4, "[GIST] non-leafkey entry, returning input unaltered");
		PG_RETURN_POINTER(entry_in);
	}

	POSTGIS_DEBUG(4, "[GIST] processing leafkey input");
	entry_out = palloc(sizeof(GISTENTRY));

	/*
	** Null key? Make a copy of the input entry and
	** return.
	*/
	if ( DatumGetPointer(entry_in->key) == NULL )
	{
		POSTGIS_DEBUG(4, "[GIST] leafkey is null");
		gistentryinit(*entry_out, (Datum) 0, entry_in->rel,
		              entry_in->page, entry_in->offset, FALSE);
		POSTGIS_DEBUG(4, "[GIST] returning copy of input");
		PG_RETURN_POINTER(entry_out);
	}

	/* Extract our index key from the GiST entry. */
	result = gserialized_datum_get_box2df_p(entry_in->key, &bbox_out);

	/* Is the bounding box valid (non-empty, non-infinite)? If not, return input uncompressed. */
	if ( result == LW_FAILURE )
	{
		POSTGIS_DEBUG(4, "[GIST] empty geometry!");
		PG_RETURN_POINTER(entry_in);
	}

	POSTGIS_DEBUGF(4, "[GIST] got entry_in->key: %s", box2df_to_string(&bbox_out));

	/* Check all the dimensions for finite values */
	if ( ! finite(bbox_out.xmax) || ! finite(bbox_out.xmin) ||
	     ! finite(bbox_out.ymax) || ! finite(bbox_out.ymin) )
	{
		POSTGIS_DEBUG(4, "[GIST] infinite geometry!");
		PG_RETURN_POINTER(entry_in);
	}

	/* Enure bounding box has minimums below maximums. */
	box2df_validate(&bbox_out);

	/* Prepare GISTENTRY for return. */
	gistentryinit(*entry_out, PointerGetDatum(box2df_copy(&bbox_out)),
	              entry_in->rel, entry_in->page, entry_in->offset, FALSE);

	/* Return GISTENTRY. */
	POSTGIS_DEBUG(4, "[GIST] 'compress' function complete");
	PG_RETURN_POINTER(entry_out);
}
/*
 * in without morphology
 */
Datum
ltxtq_in(PG_FUNCTION_ARGS)
{
	PG_RETURN_POINTER(queryin((char *) PG_GETARG_POINTER(0)));
}
Exemplo n.º 4
0
Datum gserialized_gist_picksplit_2d(PG_FUNCTION_ARGS)
{
	GistEntryVector	*entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);

	GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1);
	OffsetNumber i;
	OffsetNumber *listL, *listR, *listB, *listT;
	BOX2DF *unionL, *unionR, *unionB, *unionT;
	int posL, posR, posB, posT;
	BOX2DF pageunion;
	BOX2DF *cur;
	char direction = ' ';
	bool allisequal = true;
	OffsetNumber maxoff;
	int nbytes;

	POSTGIS_DEBUG(3, "[GIST] 'picksplit' entered");

	posL = posR = posB = posT = 0;

	maxoff = entryvec->n - 1;
	cur = (BOX2DF*) DatumGetPointer(entryvec->vector[FirstOffsetNumber].key);

	memcpy((void *) &pageunion, (void *) cur, sizeof(BOX2DF));

	/* find MBR */
	for (i = OffsetNumberNext(FirstOffsetNumber); i <= maxoff; i = OffsetNumberNext(i))
	{
		cur = (BOX2DF *) DatumGetPointer(entryvec->vector[i].key);

		if ( allisequal == true &&  (
		            pageunion.xmax != cur->xmax ||
		            pageunion.ymax != cur->ymax ||
		            pageunion.xmin != cur->xmin ||
		            pageunion.ymin != cur->ymin
		        ) )
			allisequal = false;

		if (pageunion.xmax < cur->xmax)
			pageunion.xmax = cur->xmax;
		if (pageunion.xmin > cur->xmin)
			pageunion.xmin = cur->xmin;
		if (pageunion.ymax < cur->ymax)
			pageunion.ymax = cur->ymax;
		if (pageunion.ymin > cur->ymin)
			pageunion.ymin = cur->ymin;
	}

	POSTGIS_DEBUGF(4, "pageunion is %s", box2df_to_string(&pageunion));

	nbytes = (maxoff + 2) * sizeof(OffsetNumber);
	listL = (OffsetNumber *) palloc(nbytes);
	listR = (OffsetNumber *) palloc(nbytes);
	unionL = (BOX2DF *) palloc(sizeof(BOX2DF));
	unionR = (BOX2DF *) palloc(sizeof(BOX2DF));

	if (allisequal)
	{
		POSTGIS_DEBUG(4, " AllIsEqual!");

		cur = (BOX2DF*) DatumGetPointer(entryvec->vector[OffsetNumberNext(FirstOffsetNumber)].key);


		if (memcmp((void *) cur, (void *) &pageunion, sizeof(BOX2DF)) == 0)
		{
			v->spl_left = listL;
			v->spl_right = listR;
			v->spl_nleft = v->spl_nright = 0;
			memcpy((void *) unionL, (void *) &pageunion, sizeof(BOX2DF));
			memcpy((void *) unionR, (void *) &pageunion, sizeof(BOX2DF));

			for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
			{
				if (i <= (maxoff - FirstOffsetNumber + 1) / 2)
				{
					v->spl_left[v->spl_nleft] = i;
					v->spl_nleft++;
				}
				else
				{
					v->spl_right[v->spl_nright] = i;
					v->spl_nright++;
				}
			}
			v->spl_ldatum = PointerGetDatum(unionL);
			v->spl_rdatum = PointerGetDatum(unionR);

			PG_RETURN_POINTER(v);
		}
	}

	listB = (OffsetNumber *) palloc(nbytes);
	listT = (OffsetNumber *) palloc(nbytes);
	unionB = (BOX2DF *) palloc(sizeof(BOX2DF));
	unionT = (BOX2DF *) palloc(sizeof(BOX2DF));

#define ADDLIST( list, unionD, pos, num ) do { \
	if ( pos ) { \
		if ( unionD->xmax < cur->xmax )    unionD->xmax	= cur->xmax; \
		if ( unionD->xmin	> cur->xmin  ) unionD->xmin	= cur->xmin; \
		if ( unionD->ymax < cur->ymax )    unionD->ymax	= cur->ymax; \
		if ( unionD->ymin	> cur->ymin  ) unionD->ymin	= cur->ymin; \
	} else { \
			memcpy( (void*)unionD, (void*) cur, sizeof( BOX2DF ) );  \
	} \
	list[pos] = num; \
	(pos)++; \
} while(0)

	for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
	{
		cur = (BOX2DF*) DatumGetPointer(entryvec->vector[i].key);

		if (cur->xmin - pageunion.xmin < pageunion.xmax - cur->xmax)
			ADDLIST(listL, unionL, posL,i);
		else
			ADDLIST(listR, unionR, posR,i);
		if (cur->ymin - pageunion.ymin < pageunion.ymax - cur->ymax)
			ADDLIST(listB, unionB, posB,i);
		else
			ADDLIST(listT, unionT, posT,i);
	}

	POSTGIS_DEBUGF(4, "unionL is %s", box2df_to_string(unionL));
	POSTGIS_DEBUGF(4, "unionR is %s", box2df_to_string(unionR));
	POSTGIS_DEBUGF(4, "unionT is %s", box2df_to_string(unionT));
	POSTGIS_DEBUGF(4, "unionB is %s", box2df_to_string(unionB));

	/* bad disposition, sort by ascending and resplit */
	if ( (posR==0 || posL==0) && (posT==0 || posB==0) )
	{
		KBsort *arr = (KBsort*)palloc( sizeof(KBsort) * maxoff );
		posL = posR = posB = posT = 0;
		for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
		{
			arr[i-1].key = (BOX2DF*) DatumGetPointer(entryvec->vector[i].key);
			arr[i-1].pos = i;
		}
		qsort( arr, maxoff, sizeof(KBsort), compare_KB );
		for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
		{
			cur = arr[i-1].key;
			if (cur->xmin - pageunion.xmin < pageunion.xmax - cur->xmax)
				ADDLIST(listL, unionL, posL,arr[i-1].pos);
			else if ( cur->xmin - pageunion.xmin == pageunion.xmax - cur->xmax )
			{
				if ( posL>posR )
					ADDLIST(listR, unionR, posR,arr[i-1].pos);
				else
					ADDLIST(listL, unionL, posL,arr[i-1].pos);
			}
			else
				ADDLIST(listR, unionR, posR,arr[i-1].pos);

			if (cur->ymin - pageunion.ymin < pageunion.ymax - cur->ymax)
				ADDLIST(listB, unionB, posB,arr[i-1].pos);
			else if ( cur->ymin - pageunion.ymin == pageunion.ymax - cur->ymax )
			{
				if ( posB>posT )
					ADDLIST(listT, unionT, posT,arr[i-1].pos);
				else
					ADDLIST(listB, unionB, posB,arr[i-1].pos);
			}
			else
				ADDLIST(listT, unionT, posT,arr[i-1].pos);
		}
		pfree(arr);
	}

	/* which split more optimal? */
	if (Max(posL, posR) < Max(posB, posT))
		direction = 'x';
	else if (Max(posL, posR) > Max(posB, posT))
		direction = 'y';
	else
	{
		float sizeLR, sizeBT;
		BOX2DF interLR, interBT;
		
		if ( box2df_intersection(unionL, unionR, &interLR) == FALSE )
			sizeLR = 0.0;
		else
			sizeLR = box2df_size(&interLR);

		if ( box2df_intersection(unionB, unionT, &interBT) == FALSE )
			sizeBT = 0.0;
		else
			sizeBT = box2df_size(&interBT);
		
		if (sizeLR < sizeBT)
			direction = 'x';
		else
			direction = 'y';
	}

	POSTGIS_DEBUGF(4, "split direction '%c'", direction);

	if (direction == 'x')
	{
		pfree(unionB);
		pfree(listB);
		pfree(unionT);
		pfree(listT);

		v->spl_left = listL;
		v->spl_right = listR;
		v->spl_nleft = posL;
		v->spl_nright = posR;
		v->spl_ldatum = PointerGetDatum(unionL);
		v->spl_rdatum = PointerGetDatum(unionR);
	}
	else
	{
		pfree(unionR);
		pfree(listR);
		pfree(unionL);
		pfree(listL);

		v->spl_left = listB;
		v->spl_right = listT;
		v->spl_nleft = posB;
		v->spl_nright = posT;
		v->spl_ldatum = PointerGetDatum(unionB);
		v->spl_rdatum = PointerGetDatum(unionT);
	}
	
	POSTGIS_DEBUG(4, "[GIST] 'picksplit' completed");
	
	PG_RETURN_POINTER(v);
}
Exemplo n.º 5
0
Datum LWGEOM_interiorringn_polygon(PG_FUNCTION_ARGS)
{
	GSERIALIZED *geom;
	int32 wanted_index;
	LWCURVEPOLY *curvepoly = NULL;
	LWPOLY *poly = NULL;
	POINTARRAY *ring;
	LWLINE *line;
	LWGEOM *lwgeom;
	GSERIALIZED *result;
	GBOX *bbox = NULL;
	int type;

	POSTGIS_DEBUG(2, "LWGEOM_interierringn_polygon called.");

	wanted_index = PG_GETARG_INT32(1);
	if ( wanted_index < 1 )
	{
		/* elog(ERROR, "InteriorRingN: ring number is 1-based"); */
		PG_RETURN_NULL(); /* index out of range */
	}

	geom = PG_GETARG_GSERIALIZED_P(0);
	type = gserialized_get_type(geom);

	if ( (type != POLYGONTYPE) && (type != CURVEPOLYTYPE) )
	{
		elog(ERROR, "InteriorRingN: geom is not a polygon");
		PG_FREE_IF_COPY(geom, 0);
		PG_RETURN_NULL();
	}
	
	lwgeom = lwgeom_from_gserialized(geom);
	if( lwgeom_is_empty(lwgeom) )
	{
		lwpoly_free(poly);
		PG_FREE_IF_COPY(geom, 0);
		PG_RETURN_NULL();
	}
	
	if ( type == POLYGONTYPE)
	{
		poly = lwgeom_as_lwpoly(lwgeom_from_gserialized(geom));

		/* Ok, now we have a polygon. Let's see if it has enough holes */
		if ( wanted_index >= poly->nrings )
		{
			lwpoly_free(poly);
			PG_FREE_IF_COPY(geom, 0);
			PG_RETURN_NULL();
		}

		ring = poly->rings[wanted_index];

		/* COMPUTE_BBOX==TAINTING */
		if ( poly->bbox ) 
		{
			bbox = lwalloc(sizeof(GBOX));
			ptarray_calculate_gbox_cartesian(ring, bbox);
		}

		/* This is a LWLINE constructed by interior ring POINTARRAY */
		line = lwline_construct(poly->srid, bbox, ring);


		result = geometry_serialize((LWGEOM *)line);
		lwline_release(line);
		lwpoly_free(poly);
	}
	else
	{
		curvepoly = lwgeom_as_lwcurvepoly(lwgeom_from_gserialized(geom));

		if (wanted_index >= curvepoly->nrings)
		{
			PG_FREE_IF_COPY(geom, 0);
			lwgeom_release((LWGEOM *)curvepoly);
			PG_RETURN_NULL();
		}

		result = geometry_serialize(curvepoly->rings[wanted_index]);
		lwgeom_free((LWGEOM*)curvepoly);
	}

	PG_FREE_IF_COPY(geom, 0);
	PG_RETURN_POINTER(result);
}
Exemplo n.º 6
0
Datum LWGEOM_exteriorring_polygon(PG_FUNCTION_ARGS)
{
	GSERIALIZED *geom = PG_GETARG_GSERIALIZED_P(0);
	GSERIALIZED *result;
	POINTARRAY *extring;
	LWGEOM *lwgeom;
	LWLINE *line;
	GBOX *bbox=NULL;
	int type = gserialized_get_type(geom);

	POSTGIS_DEBUG(2, "LWGEOM_exteriorring_polygon called.");

	if ( (type != POLYGONTYPE) &&
	     (type != CURVEPOLYTYPE) &&
	     (type != TRIANGLETYPE))
	{
		elog(ERROR, "ExteriorRing: geom is not a polygon");
		PG_RETURN_NULL();
	}
	
	lwgeom = lwgeom_from_gserialized(geom);
	
	if( lwgeom_is_empty(lwgeom) )
	{
		line = lwline_construct_empty(lwgeom->srid,
		                              lwgeom_has_z(lwgeom),
		                              lwgeom_has_m(lwgeom));
		result = geometry_serialize(lwline_as_lwgeom(line));
	}
	else if ( lwgeom->type == POLYGONTYPE )
	{
		LWPOLY *poly = lwgeom_as_lwpoly(lwgeom);

		/* Ok, now we have a polygon. Here is its exterior ring. */
		extring = poly->rings[0];

		/*
		* This is a LWLINE constructed by exterior ring POINTARRAY
		* If the input geom has a bbox, use it for
		* the output geom, as exterior ring makes it up !
		*/
		if ( poly->bbox ) 
			bbox = gbox_copy(poly->bbox);

		line = lwline_construct(poly->srid, bbox, extring);
		result = geometry_serialize((LWGEOM *)line);

		lwgeom_release((LWGEOM *)line);
	}
	else if ( lwgeom->type == TRIANGLETYPE )
	{
		LWTRIANGLE *triangle = lwgeom_as_lwtriangle(lwgeom);

		/*
		* This is a LWLINE constructed by exterior ring POINTARRAY
		* If the input geom has a bbox, use it for
		* the output geom, as exterior ring makes it up !
		*/
		if ( triangle->bbox ) 
			bbox = gbox_copy(triangle->bbox);
		line = lwline_construct(triangle->srid, bbox, triangle->points);

		result = geometry_serialize((LWGEOM *)line);

		lwgeom_release((LWGEOM *)line);
	}
	else
	{
		LWCURVEPOLY *curvepoly = lwgeom_as_lwcurvepoly(lwgeom);
		result = geometry_serialize(curvepoly->rings[0]);
	}

	lwgeom_free(lwgeom);
	PG_FREE_IF_COPY(geom, 0);
	PG_RETURN_POINTER(result);
}
Exemplo n.º 7
0
Datum
gtsquery_picksplit(PG_FUNCTION_ARGS)
{
	GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
	GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1);
	OffsetNumber maxoff = entryvec->n - 2;
	OffsetNumber k,
				j;
	TSQuerySign datum_l,
				datum_r;
	int32		size_alpha,
				size_beta;
	int32		size_waste,
				waste = -1;
	int32		nbytes;
	OffsetNumber seed_1 = 0,
				seed_2 = 0;
	OffsetNumber *left,
			   *right;

	SPLITCOST  *costvector;

	nbytes = (maxoff + 2) * sizeof(OffsetNumber);
	left = v->spl_left = (OffsetNumber *) palloc(nbytes);
	right = v->spl_right = (OffsetNumber *) palloc(nbytes);
	v->spl_nleft = v->spl_nright = 0;

	for (k = FirstOffsetNumber; k < maxoff; k = OffsetNumberNext(k))
		for (j = OffsetNumberNext(k); j <= maxoff; j = OffsetNumberNext(j))
		{
			size_waste = hemdist(GETENTRY(entryvec, j), GETENTRY(entryvec, k));
			if (size_waste > waste)
			{
				waste = size_waste;
				seed_1 = k;
				seed_2 = j;
			}
		}


	if (seed_1 == 0 || seed_2 == 0)
	{
		seed_1 = 1;
		seed_2 = 2;
	}

	datum_l = GETENTRY(entryvec, seed_1);
	datum_r = GETENTRY(entryvec, seed_2);

	maxoff = OffsetNumberNext(maxoff);
	costvector = (SPLITCOST *) palloc(sizeof(SPLITCOST) * maxoff);
	for (j = FirstOffsetNumber; j <= maxoff; j = OffsetNumberNext(j))
	{
		costvector[j - 1].pos = j;
		size_alpha = hemdist(GETENTRY(entryvec, seed_1), GETENTRY(entryvec, j));
		size_beta = hemdist(GETENTRY(entryvec, seed_2), GETENTRY(entryvec, j));
		costvector[j - 1].cost = abs(size_alpha - size_beta);
	}
	qsort((void *) costvector, maxoff, sizeof(SPLITCOST), comparecost);

	for (k = 0; k < maxoff; k++)
	{
		j = costvector[k].pos;
		if (j == seed_1)
		{
			*left++ = j;
			v->spl_nleft++;
			continue;
		}
		else if (j == seed_2)
		{
			*right++ = j;
			v->spl_nright++;
			continue;
		}
		size_alpha = hemdist(datum_l, GETENTRY(entryvec, j));
		size_beta = hemdist(datum_r, GETENTRY(entryvec, j));

		if (size_alpha < size_beta + WISH_F(v->spl_nleft, v->spl_nright, 0.05))
		{
			datum_l |= GETENTRY(entryvec, j);
			*left++ = j;
			v->spl_nleft++;
		}
		else
		{
			datum_r |= GETENTRY(entryvec, j);
			*right++ = j;
			v->spl_nright++;
		}
	}

	*right = *left = FirstOffsetNumber;
	v->spl_ldatum = TSQuerySignGetDatum(datum_l);
	v->spl_rdatum = TSQuerySignGetDatum(datum_r);

	PG_RETURN_POINTER(v);
}
Exemplo n.º 8
0
Datum
gin_extract_query_trgm(PG_FUNCTION_ARGS)
{
	text	   *val = (text *) PG_GETARG_TEXT_P(0);
	int32	   *nentries = (int32 *) PG_GETARG_POINTER(1);
	StrategyNumber strategy = PG_GETARG_UINT16(2);

	/* bool   **pmatch = (bool **) PG_GETARG_POINTER(3); */
	/* Pointer	  *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
	/* bool   **nullFlags = (bool **) PG_GETARG_POINTER(5); */
	int32	   *searchMode = (int32 *) PG_GETARG_POINTER(6);
	Datum	   *entries = NULL;
	TRGM	   *trg;
	int32		trglen;
	trgm	   *ptr;
	int32		i;

	switch (strategy)
	{
		case SimilarityStrategyNumber:
			trg = generate_trgm(VARDATA(val), VARSIZE(val) - VARHDRSZ);
			break;
		case ILikeStrategyNumber:
#ifndef IGNORECASE
			elog(ERROR, "cannot handle ~~* with case-sensitive trigrams");
#endif
			/* FALL THRU */
		case LikeStrategyNumber:

			/*
			 * For wildcard search we extract all the trigrams that every
			 * potentially-matching string must include.
			 */
			trg = generate_wildcard_trgm(VARDATA(val), VARSIZE(val) - VARHDRSZ);
			break;
		default:
			elog(ERROR, "unrecognized strategy number: %d", strategy);
			trg = NULL;			/* keep compiler quiet */
			break;
	}

	trglen = ARRNELEM(trg);
	*nentries = trglen;

	if (trglen > 0)
	{
		entries = (Datum *) palloc(sizeof(Datum) * trglen);
		ptr = GETARR(trg);
		for (i = 0; i < trglen; i++)
		{
			int32		item = trgm2int(ptr);

			entries[i] = Int32GetDatum(item);
			ptr++;
		}
	}

	/*
	 * If no trigram was extracted then we have to scan all the index.
	 */
	if (trglen == 0)
		*searchMode = GIN_SEARCH_MODE_ALL;

	PG_RETURN_POINTER(entries);
}
Exemplo n.º 9
0
Datum geometry_estimated_extent(PG_FUNCTION_ARGS)
{
	text *txnsp = NULL;
	text *txtbl = NULL;
	text *txcol = NULL;
	char *nsp = NULL;
	char *tbl = NULL;
	char *col = NULL;
	char *query;
	ArrayType *array = NULL;
	int SPIcode;
	SPITupleTable *tuptable;
	TupleDesc tupdesc ;
	HeapTuple tuple ;
	bool isnull;
	GBOX *box;
	size_t querysize;
	GEOM_STATS geomstats;
	float reltuples;
	Datum binval;

	if ( PG_NARGS() == 3 )
	{
		txnsp = PG_GETARG_TEXT_P(0);
		txtbl = PG_GETARG_TEXT_P(1);
		txcol = PG_GETARG_TEXT_P(2);
	}
	else if ( PG_NARGS() == 2 )
	{
		txtbl = PG_GETARG_TEXT_P(0);
		txcol = PG_GETARG_TEXT_P(1);
	}
	else
	{
		elog(ERROR, "estimated_extent() called with wrong number of arguments");
		PG_RETURN_NULL();
	}

	POSTGIS_DEBUG(2, "geomtery_estimated_extent called");

	/* Connect to SPI manager */
	SPIcode = SPI_connect();
	if (SPIcode != SPI_OK_CONNECT)
	{
		elog(ERROR, "geometry_estimated_extent: couldnt open a connection to SPI");
		PG_RETURN_NULL() ;
	}

	querysize = VARSIZE(txtbl)+VARSIZE(txcol)+516;

	if ( txnsp )
	{
		nsp = text2cstring(txnsp);
		querysize += VARSIZE(txnsp);
	}
	else
	{
		querysize += 32; /* current_schema() */
	}

	tbl = text2cstring(txtbl);
	col = text2cstring(txcol);

#if POSTGIS_DEBUG_LEVEL > 0
	if ( txnsp )
	{
		POSTGIS_DEBUGF(3, " schema:%s table:%s column:%s", nsp, tbl, col);
	}
	else
	{
		POSTGIS_DEBUGF(3, " schema:current_schema() table:%s column:%s",
		               tbl, col);
	}
#endif

	query = palloc(querysize);


	/* Security check: because we access information in the pg_statistic table, we must run as the database
	superuser (by marking the function as SECURITY DEFINER) and check permissions ourselves */
	if ( txnsp )
	{
		sprintf(query, "SELECT has_table_privilege((SELECT usesysid FROM pg_user WHERE usename = session_user), '\"%s\".\"%s\"', 'select')", nsp, tbl);
	}
	else
	{
		sprintf(query, "SELECT has_table_privilege((SELECT usesysid FROM pg_user WHERE usename = session_user), '\"%s\"', 'select')", tbl);
	}

	POSTGIS_DEBUGF(4, "permission check sql query is: %s", query);

	SPIcode = SPI_exec(query, 1);
	if (SPIcode != SPI_OK_SELECT)
	{
		elog(ERROR, "geometry_estimated_extent: couldn't execute permission check sql via SPI");
		SPI_finish();
		PG_RETURN_NULL();
	}

	tuptable = SPI_tuptable;
	tupdesc = SPI_tuptable->tupdesc;
	tuple = tuptable->vals[0];

	if (!DatumGetBool(SPI_getbinval(tuple, tupdesc, 1, &isnull)))
	{
		elog(ERROR, "geometry_estimated_extent: permission denied for relation %s", tbl);
		SPI_finish();
		PG_RETURN_NULL();
	}


	/* Return the stats data */
	if ( txnsp )
	{
	  sprintf(query, 
	    "SELECT s.stanumbers1[5:8], c.reltuples FROM pg_class c"
	    " LEFT OUTER JOIN pg_namespace n ON (n.oid = c.relnamespace)"
	    " LEFT OUTER JOIN pg_attribute a ON (a.attrelid = c.oid )"
	    " LEFT OUTER JOIN pg_statistic s ON (s.starelid = c.oid AND "
	                                        "s.staattnum = a.attnum )"
	    " WHERE c.relname = '%s' AND a.attname = '%s' "
	    " AND n.nspname = '%s';",
	    tbl, col, nsp);
	}
	else
	{
	  sprintf(query, 
	    "SELECT s.stanumbers1[5:8], c.reltuples FROM pg_class c"
	    " LEFT OUTER JOIN pg_namespace n ON (n.oid = c.relnamespace)"
	    " LEFT OUTER JOIN pg_attribute a ON (a.attrelid = c.oid )"
	    " LEFT OUTER JOIN pg_statistic s ON (s.starelid = c.oid AND "
	                                        "s.staattnum = a.attnum )"
	    " WHERE c.relname = '%s' AND a.attname = '%s' "
	    " AND n.nspname = current_schema();",
	    tbl, col);
	}

	POSTGIS_DEBUGF(4, " query: %s", query);

	SPIcode = SPI_exec(query, 1);
	if (SPIcode != SPI_OK_SELECT )
	{
		elog(ERROR,"geometry_estimated_extent: couldnt execute sql via SPI");
		SPI_finish();
		PG_RETURN_NULL();
	}
	if (SPI_processed != 1)
	{

		POSTGIS_DEBUGF(3, " %d stat rows", SPI_processed);

		elog(ERROR, "Unexistent field \"%s\".\"%s\".\"%s\"",
			( nsp ? nsp : "<current>" ), tbl, col);

		SPI_finish();
		PG_RETURN_NULL() ;
	}

	tuptable = SPI_tuptable;
	tupdesc = SPI_tuptable->tupdesc;
	tuple = tuptable->vals[0];

	/* Check if the table has zero rows first */
	binval = SPI_getbinval(tuple, tupdesc, 2, &isnull);
	if (isnull)
	{

		POSTGIS_DEBUG(3, " reltuples is NULL");

		elog(ERROR, "geometry_estimated_extent: null reltuples for table");

		SPI_finish();
		PG_RETURN_NULL();
	}
	reltuples = DatumGetFloat4(binval);
	if ( ! reltuples )
	{
		POSTGIS_DEBUG(3, "table has estimated zero rows");

		/* 
		 * TODO: distinguish between empty and not analyzed ?
		 */
		elog(NOTICE, "\"%s\".\"%s\".\"%s\" is empty or not analyzed",
			( nsp ? nsp : "<current>" ), tbl, col);

		SPI_finish();
		PG_RETURN_NULL();
	}

	binval = SPI_getbinval(tuple, tupdesc, 1, &isnull);
	if (isnull)
	{

		POSTGIS_DEBUG(3, " stats are NULL");

		elog(ERROR, "geometry_estimated_extent: null statistics for table");

		SPI_finish();
		PG_RETURN_NULL();
	}
	array = DatumGetArrayTypeP(binval);
	if ( ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array)) != 4 )
	{
		elog(ERROR, " corrupted histogram");
		PG_RETURN_NULL();
	}

	POSTGIS_DEBUGF(3, " stats array has %d elems", ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array)));

	/*
	 * Construct GBOX.
	 * Must allocate this in upper executor context
	 * to keep it alive after SPI_finish().
	 */
	box = SPI_palloc(sizeof(GBOX));
	FLAGS_SET_GEODETIC(box->flags, 0);
	FLAGS_SET_Z(box->flags, 0);
	FLAGS_SET_M(box->flags, 0);

	/* Construct the box */
	memcpy(&(geomstats.xmin), ARR_DATA_PTR(array), sizeof(float)*4);
	box->xmin = geomstats.xmin;
	box->xmax = geomstats.xmax;
	box->ymin = geomstats.ymin;
	box->ymax = geomstats.ymax;

	POSTGIS_DEBUGF(3, " histogram extent = %g %g, %g %g", box->xmin,
	               box->ymin, box->xmax, box->ymax);

	SPIcode = SPI_finish();
	if (SPIcode != SPI_OK_FINISH )
	{
		elog(ERROR, "geometry_estimated_extent: couldn't disconnect from SPI");
	}

	/* TODO: enlarge the box by some factor */

	PG_RETURN_POINTER(box);
}