<?xml version="1.0" encoding="utf-8"?>

<!-- The ADQL service and related data -->

<resource resdir="__system" schema="dc">
	<meta name="description">An endpoint for submitting ADQL queries
		to the data center and retrieving the result in various forms.</meta>
	<meta name="subject">virtual-observatories</meta>
	<meta name="subject">catalogs</meta>
	<meta name="creationDate">2008-09-20T12:00:00Z</meta>
	<meta name="schema-rank">10</meta>

	<meta name="_intro" format="rst"><![CDATA[
On this page, you can use
`ADQL <http://www.ivoa.net/Documents/latest/ADQL.html>`_ to query
\RSTservicelink{/__system__/dc_tables/list/form}{some of our tables}.
This is mainly for dabbling; use \RSTservicelink{/tap}{TAP} for larger
jobs (e.g., `using TAPHandle`_ within your browser).

To learn what ADQL is or for further information on this implementation, see the
\RSTservicelink{/__system__/adql/query/info}{service info}.

.. _using TAPHandle: http://saada.unistra.fr/taphandle/?url=\internallink{/tap}
]]>	
	</meta>
	<meta name="_bottominfo" format="rst">
		There is a fixed limit to 100000 rows on this service.  If this
		bugs you, use \RSTservicelink{tap}{TAP} (you should anyway).
	</meta>
	<meta name="_longdoc" format="rst"><![CDATA[

About this service
==================

To find out what tables are available for querying, see the
\RSTservicelink{__system__/dc_tables/list/form}{ADQL table list}.

Be sure to read `Standards Compliance`_ below.

About ADQL
==========

ADQL is the Astronomical Data Query Language, an extension of a subset of
the Standard Query Language `SQL <http://en.wikipedia.org/wiki/SQL>`_.  Its purpose is to give you a formal
language to specify what data you are interested in.

To get started using ADQL, try `our ADQL course`_ first.  There are plenty
of introductions SQL itself, which are perfectly useful for learning ADQL.
Check your local bookstore.  Online, `A Gentle Introduction to SQL`_ or chapter
three of `Practical PostgreSQL`_ might be useful; for the purposes
of learning ADQL, you can skip everything talking about "DDL" in general
introductions.

Finally, if you're serious about using ADQL, you should at least
briefly skim over the `ADQL specification`_.

Also have a look at the `TAP examples`_

.. _our ADQL course: http://docs.g-vo.org/adql
.. _Practical PostgreSQL: http://www.faqs.org/docs/ppbook/book1.htm
.. _A Gentle Introduction to SQL: http://sqlzoo.net/
.. _ADQL specification: http://www.ivoa.net/Documents/latest/ADQL.html
.. _TAP examples: \internallink{tap/examples}

Local guide
===========

Standards Compliance
''''''''''''''''''''

If you give no TOP clause, the system will automatically restrict your
matches to \getConfig{adql}{webDefaultLimit} rows.  This is mostly for
your own protection.  If you want more rows (make sure you don't throw
them at your browser, i.e., select VOTable output), use SELECT TOP 100000
or something along those lines.

In particular, when doing set operations (e.g., UNION) in STC-S,
no conforming of coordinate systems will be performed.

The output of ADQL geometries follows the TAP standard (simplified STC-S)
rather than the ADQL standard (something similarly messy).

ADQL defines coord_sys (the first argument to the geometry functions)
to be a string_value_expression; thus, you can have column references
or concatenations or basically anything there.  We only allow string literals
containing one of the defined coordinate systems there (these include at least
the empty string, GALACTIC, UNKNOWN, ICRS, FK4, FK5, and RELOCATABLE), or
NULL.

SELECT and SELECT ALL are not exactly the same thing.  The latter will add
an OFFSET 0 to the resulting postgresql query.  Use this when the query
planner messes up; see the `guide star example </tap/examples#Crossmatchforaguidestar>`_.

Date literals can be specified `as for Postgresql <http://www.postgresql.org/docs/8.3/static/datatype-datetime.html#DATATYPE-DATETIME-INPUT>`_;
you always need to use strings.  So, ``'2000-12-31'`` or ``'J2416642.5'``
are both valid date specifications.  See also the
`historic plates example <http://dc.g-vo.org/tap/examples#findingplatesbytimeandplace>`_.

The ADQL does not allow boolean columns.  Within the data
center, we do have boolean columns here and there, and it would be shame to
dumb them down to integers.  They show up as INTEGERs in TAP_SCHEMA, though.
When you try to compare integers to them, you get an error message to the
effect that an "operator does not exist: boolean = integer".  To query against
such columns (and have valid ADQL), use ``col='True'`` or ``col='False'``.


Usability
'''''''''

Hint: You can send off your query by typing control-return from within the
input text box.

Error messages for parse (and other) errors are not always as helpful as
we would like them to be.  Feel free to complain to us with concrete examples
of where we messed up, and we'll try to improve.

]]>
	</meta>
	<meta name="_related" title="Tables available for ADQL">/__system__/dc_tables/list/form</meta>

	<adqlCore id="qcore">
		<inputTable id="adqlInput">
			<inputKey name="query" tablehead="ADQL query" type="text"
				description="A query in the Astronomical Data Query Language"
				widgetFactory="widgetFactory(ScalingTextArea, rows=15)"
				required="True"/>
			<inputKey name="_TIMEOUT" type="integer" unit="s"
				tablehead="Timeout after"
				description="Seconds until the query is aborted.  If you find
					yourself having to raise this beyond 200 or so, please contact
					the site operators for hints on how to optimize your query">
				<values default="5"/>
			</inputKey>
		</inputTable>
	</adqlCore>

	<service id="query" core="qcore">
		<meta name="shortName">gavoadql</meta>
		<meta name="title">\getConfig{web}{sitename} ADQL Query</meta>
		<publish render="form" sets="local,ivo_managed"/>
	</service>

	<macDef name="containsBody">
		RETURNS INTEGER AS $func$
			SELECT CASE WHEN $1 @ $2
				THEN 1
				ELSE 0
			END
		$func$ LANGUAGE SQL;
	</macDef>

	<macDef name="intersectsBody">
		RETURNS INTEGER AS $func$
			SELECT CASE WHEN $1 &amp;&amp; $2
				THEN 1
				ELSE 0
			END
		$func$ LANGUAGE SQL;
	</macDef>


<!-- definition of user defined functions -->
	<data id="make_udfs">
		<!-- HACK: I need a table that's on disk for the script to run. -->
		<table id="empty" onDisk="True" temporary="True"/>
		<make table="empty">
			<script lang="AC_SQL" type="postCreation" name="create_user_functions">
				<![CDATA[

				-- ADQL geometry functions -- these are morphed into
				-- boolean expressions in the python ADQL adapter.  The
				-- guys here are for use in select lists.  Note that these
				-- do not do any system adaptation, and intersects does not
				-- decay to contains for points.  At least the latter could
				-- be fixed, but I consider this something fairly exotic in
				--  the first place.

				CREATE OR REPLACE FUNCTION contains(spoint, anyelement)
					\containsBody

				CREATE OR REPLACE FUNCTION contains(scircle, anyelement)
					\containsBody

				CREATE OR REPLACE FUNCTION contains(spoly, anyelement)
					\containsBody

				CREATE OR REPLACE FUNCTION intersects(spoint, anyelement)
					\containsBody

				CREATE OR REPLACE FUNCTION intersects(scircle, anyelement)
					\intersectsBody

				CREATE OR REPLACE FUNCTION intersects(spoly, anyelement)
					\intersectsBody


				-- text matching functions for the relational registry.
				-- The in-db functions do *not* profit from the indices, which is why
				-- they are replaced by the actual expressions when used in query
				-- expressions.  The functions here are fallbacks for when morphing
				-- does not take place for one reason or another

				CREATE OR REPLACE FUNCTION ivo_hasword(haystack TEXT, needle TEXT)
				RETURNS INTEGER AS $func$
					SELECT CASE WHEN to_tsvector('english', $1) @@
							plainto_tsquery('english', $2)
						THEN 1
						ELSE 0
					END
				$func$ LANGUAGE SQL STABLE PARALLEL SAFE;

				CREATE OR REPLACE FUNCTION ivo_hashlist_has(hashlist TEXT, item TEXT)
				RETURNS INTEGER AS $func$
					-- postgres can't RE-escape a user string; hence, we'll have
					-- to work on the hashlist (this assumes hashlist is already
					-- lowercased).
					SELECT CASE WHEN lower($2) = ANY(string_to_array(lower($1), '#'))
						THEN 1
						ELSE 0
					END
				$func$ LANGUAGE SQL STABLE PARALLEL SAFE;

				CREATE OR REPLACE FUNCTION ivo_nocasematch(value TEXT, pattern TEXT)
				RETURNS INTEGER AS $func$
					SELECT CASE WHEN $1 ILIKE $2
						THEN 1
						ELSE 0
					END
				$func$ LANGUAGE SQL STABLE PARALLEL SAFE;

				-- miscellaneous local functions

				CREATE OR REPLACE FUNCTION ts_to_mjd(value TIMESTAMP)
				RETURNS DOUBLE PRECISION AS $func$
					SELECT to_char($1, 'J')::double precision
  					+ to_char($1,'ssss')::double precision/86400
  					- 2400001
				$func$ LANGUAGE SQL STABLE PARALLEL SAFE;

				CREATE OR REPLACE FUNCTION ts_to_jd(value TIMESTAMP)
				RETURNS DOUBLE PRECISION AS $func$
					SELECT to_char($1, 'J')::double precision
  					+ to_char($1,'ssss')::double precision/86400-0.5
				$func$ LANGUAGE SQL STABLE PARALLEL SAFE;

				CREATE OR REPLACE FUNCTION hist_sfunc(
					state INTEGER[],
					val DOUBLE PRECISION,
					lower_bound DOUBLE PRECISION, upper_bound DOUBLE PRECISION,
					nbins INTEGER)
					RETURNS INTEGER [] AS $func$
					-- based on work by wiki.postgresql.org:Darius
					-- change: we store under- and overflows in arr[0] and arr[nbins+1]
					DECLARE
						bucket INTEGER;
						i INTEGER;
					BEGIN
						-- Init the array with the correct number of 0's so the
						-- caller doesn't see NULLs
						IF state[0] IS NULL THEN
							FOR i IN SELECT * FROM generate_series(0, nbins+1) LOOP
								state[i] := 0;
							END LOOP;
						END IF;

						IF val IS NOT NULL THEN
							bucket := width_bucket(val, lower_bound, upper_bound, nbins);
							state[bucket] = state[bucket] + 1;
						END IF;
						RETURN state;
					END;
				$func$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;

				DROP AGGREGATE IF EXISTS gavo_histogram (
					DOUBLE PRECISION, DOUBLE PRECISION, DOUBLE PRECISION, INTEGER);
				CREATE AGGREGATE gavo_histogram (
						DOUBLE PRECISION, DOUBLE PRECISION, DOUBLE PRECISION, INTEGER) (
					SFUNC = hist_sfunc,
					STYPE = INTEGER[]
				);

				CREATE OR REPLACE FUNCTION gavo_urlescape(
					string TEXT) RETURNS TEXT AS $func$
					-- replaces selected URI reserved characters with percent-escapes.
					-- This is not a general solution (which we had before and
					-- proved too slow).  As written here, it should be enough
					-- to quote pubdids, though.
					SELECT replace(replace(replace(replace(replace(replace(replace(
						string, '%', '%25'
							), '?', '%3F'
							), '#', '%23'
							), '&', '%26'
							), '+', '%2B'
							), ' ', '%20'
							), '=', '%3D')
				$func$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;

				CREATE OR REPLACE FUNCTION gavo_getauthority(
					ivoid TEXT) RETURNS TEXT AS $func$
					-- returns the authority part of an ivoid (and, actually,
					-- the host part of full URIs).
					SELECT a[1] FROM (
						SELECT regexp_matches(ivoid, '[^:]+://([^/]*)/') AS a) AS q
				$func$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;

				-- this will not normally be used, as it's being morphed as
				-- a service for the query planner; but we may want to
				-- use it SQL, too, so...
				CREATE OR REPLACE FUNCTION ivo_interval_overlaps(
					l1 NUMERIC, h1 NUMERIC, l2 NUMERIC, h2 NUMERIC)
					RETURNS BOOLEAN AS $func$
					SELECT h1>=l2 AND h2>=l1 AND l1<=h1 AND l2<=h2
				$func$ LANGUAGE SQL STABLE PARALLEL SAFE;

				CREATE OR REPLACE FUNCTION ivo_apply_pm(
					long DOUBLE PRECISION,
					lat DOUBLE PRECISION,
					pmlong DOUBLE PRECISION,
					pmlat DOUBLE PRECISION,
					epdist DOUBLE PRECISION) RETURNS spoint AS $func$
					-- "applies" proper motion, going through the tangential plane.
					-- legacy, non ESAC-compliant.  Drop this?
					SELECT gnomonic_inv(
						POINT(RADIANS(pmlong*epdist), RADIANS(pmlat*epdist)),
						spoint(RADIANS(long), RADIANS(lat)))
				$func$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;
			]]></script>

			<script lang="AC_SQL" type="postCreation" name="create_geometry_casts">
			<![CDATA[
CREATE OR REPLACE FUNCTION cast_to_point(dali_spec TEXT)
RETURNS spoint AS $func$
	-- cast a DALI-type string with two coordinates in degrees to an spoint
	DECLARE
		coo_lits TEXT[];
	BEGIN
		coo_lits := regexp_split_to_array(dali_spec, '\s+');
		IF array_length(coo_lits, 1) != 2 THEN
			RAISE EXCEPTION 'Not a DALI POINT literal: %', dali_spec;
		END IF;
		RETURN spoint(
			RADIANS(coo_lits[1]::DOUBLE PRECISION),
			RADIANS(coo_lits[2]::DOUBLE PRECISION));
	END
$func$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;

CREATE OR REPLACE FUNCTION cast_to_point(circ scircle)
-- more or less PoC for adding more casts: circle to point is just centroid
RETURNS spoint AS $func$
	SELECT center(circ);
$func$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;

CREATE OR REPLACE FUNCTION cast_to_circle(dali_spec TEXT)
RETURNS scircle AS $func$
	-- cast a DALI-type string with three angles in degrees to an scircle
	DECLARE
		coo_lits TEXT[];
	BEGIN
		coo_lits := regexp_split_to_array(dali_spec, '\s+');
		IF array_length(coo_lits, 1) != 3 THEN
			RAISE EXCEPTION 'Not a DALI CIRCLE literal: %', dali_spec;
		END IF;
		RETURN scircle(
			spoint(
				RADIANS(coo_lits[1]::DOUBLE PRECISION),
				RADIANS(coo_lits[2]::DOUBLE PRECISION)),
			RADIANS(coo_lits[3]::DOUBLE PRECISION));
	END
$func$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;

CREATE OR REPLACE FUNCTION cast_to_polygon(dali_spec TEXT)
RETURNS spoly AS $func$
	-- cast a DALI-type string with 2n angles in degrees to an spoly
	DECLARE
		coo_lits TEXT[];
		points spoint[];
		n_points INTEGER;
		ind INTEGER;
		res RECORD;
	BEGIN
		coo_lits := regexp_split_to_array(dali_spec, '\s+');

		IF array_length(coo_lits, 1) < 6
				OR array_length(coo_lits, 1)%2 = 1 THEN
			RAISE EXCEPTION 'Not a DALI POLYGON literal: %', dali_spec;
		END IF;
		
		n_points := array_length(coo_lits, 1)/2;
		FOR ind IN 1 .. n_points LOOP
			points[ind] = spoint(
				RADIANS(coo_lits[2*ind-1]::DOUBLE PRECISION),
				RADIANS(coo_lits[2*ind]::DOUBLE PRECISION));
		END LOOP;
		FOR res IN SELECT spoly(p) as geo FROM unnest(points) as p LOOP
			RETURN res.geo;
		END LOOP;
	END
$func$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;

			]]></script>

			<script lang="AC_SQL" type="postCreation" name="create_array_operators">
				<![CDATA[
CREATE OR REPLACE FUNCTION _array_add(arg1 ANYCOMPATIBLEARRAY, arg2 ANYCOMPATIBLEARRAY)
RETURNS ANYCOMPATIBLEARRAY AS $func$
	SELECT array_agg(COALESCE(a,'nan'::real)+COALESCE(b,'nan'::real))
	FROM unnest(arg1,arg2) as x(a,b)
$func$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;

DROP OPERATOR IF EXISTS + (ANYCOMPATIBLEARRAY, ANYCOMPATIBLEARRAY);
CREATE OPERATOR + (
   LEFTARG    = ANYCOMPATIBLEARRAY,
   RIGHTARG   = ANYCOMPATIBLEARRAY,
   PROCEDURE  = _array_add
);

CREATE OR REPLACE FUNCTION _array_sub(arg1 ANYCOMPATIBLEARRAY, arg2 ANYCOMPATIBLEARRAY)
RETURNS ANYCOMPATIBLEARRAY AS $func$
	SELECT array_agg(COALESCE(a,'nan'::real)-COALESCE(b,'nan'::real))
	FROM unnest(arg1,arg2) as x(a,b)
$func$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;

DROP OPERATOR IF EXISTS - (ANYCOMPATIBLEARRAY, ANYCOMPATIBLEARRAY);
CREATE OPERATOR - (
   LEFTARG    = ANYCOMPATIBLEARRAY,
   RIGHTARG   = ANYCOMPATIBLEARRAY,
   PROCEDURE  = _array_sub
);

-- That's component-wise multiplication.  See arr_dot for
-- the scalar product
CREATE OR REPLACE FUNCTION _array_mul(arg1 ANYCOMPATIBLEARRAY, arg2 ANYCOMPATIBLEARRAY)
RETURNS ANYCOMPATIBLEARRAY AS $func$
	SELECT array_agg(COALESCE(a,'nan'::real)*COALESCE(b,'nan'::real))
	FROM unnest(arg1,arg2) as x(a,b)
$func$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;

DROP OPERATOR IF EXISTS * (ANYCOMPATIBLEARRAY, ANYCOMPATIBLEARRAY);
CREATE OPERATOR * (
   LEFTARG    = ANYCOMPATIBLEARRAY,
   RIGHTARG   = ANYCOMPATIBLEARRAY,
   PROCEDURE  = _array_mul
);

CREATE OR REPLACE FUNCTION _array_div(arg1 ANYCOMPATIBLEARRAY, arg2 ANYCOMPATIBLEARRAY)
RETURNS ANYCOMPATIBLEARRAY AS $func$
	SELECT array_agg(COALESCE(a,'nan'::real)/COALESCE(b,'nan'::real))
	FROM unnest(arg1,arg2) as x(a,b)
$func$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;

DROP OPERATOR IF EXISTS / (ANYCOMPATIBLEARRAY, ANYCOMPATIBLEARRAY);
CREATE OPERATOR / (
   LEFTARG    = ANYCOMPATIBLEARRAY,
   RIGHTARG   = ANYCOMPATIBLEARRAY,
   PROCEDURE  = _array_div
);


CREATE OR REPLACE FUNCTION _array_leftscalarmul(
	arg1 ANYNONARRAY, arg2 ANYCOMPATIBLEARRAY)
RETURNS ANYCOMPATIBLEARRAY AS $func$
	SELECT array_agg(arg1*b) FROM unnest(arg2) as x(b)
$func$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;

CREATE OR REPLACE FUNCTION _array_rightscalarmul(
	arg1 ANYCOMPATIBLEARRAY, arg2 ANYNONARRAY)
RETURNS ANYCOMPATIBLEARRAY AS $func$
	SELECT array_agg(a*arg2) FROM unnest(arg1) as x(a)
$func$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;

DROP OPERATOR IF EXISTS * (ANYNONARRAY, ANYCOMPATIBLEARRAY);
DROP OPERATOR IF EXISTS * (ANYCOMPATIBLEARRAY, ANYNONARRAY);

CREATE OPERATOR * (
   LEFTARG    = ANYNONARRAY,
   RIGHTARG   = ANYCOMPATIBLEARRAY,
   PROCEDURE  = _array_leftscalarmul
);
CREATE OPERATOR * (
   LEFTARG    = ANYCOMPATIBLEARRAY,
   RIGHTARG   = ANYNONARRAY,
   PROCEDURE  = _array_rightscalarmul
);


CREATE OR REPLACE FUNCTION _array_rightscalardiv(
	arg1 ANYCOMPATIBLEARRAY, arg2 ANYNONARRAY)
RETURNS ANYCOMPATIBLEARRAY AS $func$
	SELECT array_agg(a/arg2) FROM unnest(arg1) as x(a)
$func$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;

DROP OPERATOR IF EXISTS / (ANYCOMPATIBLEARRAY, ANYNONARRAY);

CREATE OPERATOR / (
   LEFTARG    = ANYCOMPATIBLEARRAY,
   RIGHTARG   = ANYNONARRAY,
   PROCEDURE  = _array_rightscalardiv
);


CREATE OR REPLACE FUNCTION arr_min(arr ANYARRAY)
RETURNS ANYNONARRAY LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
$func$ SELECT min(x) FROM unnest(arr) x $func$;

CREATE OR REPLACE FUNCTION arr_max(arr ANYARRAY)
RETURNS ANYNONARRAY LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
$func$ SELECT max(x) FROM unnest(arr) x $func$;

CREATE OR REPLACE FUNCTION arr_avg(arr ANYARRAY)
RETURNS ANYNONARRAY LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
$func$ SELECT avg(x) FROM unnest(arr) x $func$;

CREATE OR REPLACE FUNCTION arr_stddev(arr ANYARRAY)
RETURNS ANYNONARRAY LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
$func$ SELECT sqrt((SELECT (avg(x^2)-avg(x)^2) FROM unnest(arr) x)
	*array_length(arr, 1)/(array_length(arr, 1)-1))
$func$;

CREATE OR REPLACE FUNCTION arr_count(arr ANYARRAY)
RETURNS ANYNONARRAY LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
$func$ SELECT array_length(arr, 1)
$func$;


CREATE OR REPLACE FUNCTION arr_sum(arr ANYARRAY)
RETURNS ANYNONARRAY LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
$func$ SELECT sum(x) FROM unnest(arr) x $func$;

CREATE OR REPLACE FUNCTION aggregate_array_sum(
	state ANYARRAY,
	val ANYARRAY)
	RETURNS ANYARRAY AS $func$
	BEGIN
		IF state[1] IS NULL THEN
			state = val;
		ELSE
			state = state+val;
		END IF;
		RETURN state;
	END;
$func$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;

DROP AGGREGATE IF EXISTS sum (ANYARRAY);
CREATE AGGREGATE sum (arr ANYARRAY) (
	SFUNC = aggregate_array_sum,
	STYPE = ANYARRAY
);


DROP TYPE IF EXISTS array_avg_state CASCADE;
CREATE TYPE array_avg_state AS (
	val double precision[], nitems int);

CREATE OR REPLACE FUNCTION aggregate_array_avg(
	state array_avg_state,
	val ANYARRAY)
	RETURNS array_avg_state AS $func$
	BEGIN
		IF state IS NULL THEN
			state.nitems = 1;
			state.val = val;
		ELSE
			state.nitems = state.nitems+1;
			state.val = state.val+val;
		END IF;
		RETURN state;
	END;
$func$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;

CREATE OR REPLACE FUNCTION aggregate_array_avgevaluate(
	state array_avg_state)
	RETURNS double precision[] AS $func$
	BEGIN
		RETURN state.val/state.nitems;
	END;
$func$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;

DROP AGGREGATE IF EXISTS avg (ANYARRAY);
CREATE AGGREGATE avg (arr ANYARRAY) (
	SFUNC = aggregate_array_avg,
	STYPE = array_avg_state,
	FINALFUNC = aggregate_array_avgevaluate
);


-- We use Welford's algorithm here for numerical stability
-- https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm

DROP TYPE IF EXISTS array_stddev_state CASCADE;
CREATE TYPE array_stddev_state AS (
	nitems int,
	v_mean double precision[],
	v_m2 double precision[]);

CREATE OR REPLACE FUNCTION aggregate_array_stddev(
	state array_stddev_state,
	val ANYARRAY)
	RETURNS array_stddev_state AS $func$
	DECLARE
		delta double precision[];
		delta2 double precision[];
	BEGIN
		IF state IS NULL THEN
			state.nitems = 1;
			state.v_mean = val;
			state.v_m2 = val-val;  -- all zeros with the right shape.
		ELSE
			state.nitems = state.nitems+1;
			delta = val-state.v_mean;
			state.v_mean = state.v_mean+delta/state.nitems;
			delta2 = val-state.v_mean;
			state.v_m2 = state.v_m2+delta*delta2;
		END IF;
		RETURN state;
	END;
$func$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;

CREATE OR REPLACE FUNCTION aggregate_array_stddevevaluate(
	state array_stddev_state)
	RETURNS double precision[] AS $func$
	BEGIN
		IF state.nitems<2 THEN
			RETURN NULL;
		END IF;
		RETURN (SELECT array_agg(SQRT(x/(state.nitems-1)))
			FROM unnest(state.v_m2) x);
	END;
$func$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;

DROP AGGREGATE IF EXISTS stddev (ANYARRAY);
CREATE AGGREGATE stddev (arr ANYARRAY) (
	SFUNC = aggregate_array_stddev,
	STYPE = array_stddev_state,
	FINALFUNC = aggregate_array_stddevevaluate
);


CREATE OR REPLACE FUNCTION arr_dot(arg1 ANYCOMPATIBLEARRAY, arg2 ANYCOMPATIBLEARRAY)
RETURNS DOUBLE PRECISION AS $func$
	SELECT sum(COALESCE(a, 'nan'::real)*COALESCE(b, 'nan'::real))
	FROM unnest(arg1,arg2) as x(a,b)
$func$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;

]]>
			</script>

			<!-- This stuff will (probably) be part of pgsphere 1.2.1
			and provides extra operators between normal geometries
			and MOCs.

			Remove this when we can rely on pgsphere 1.2.1 or later.
			-->
			<script lang="AC_SQL" type="postCreation" name="add_moc_casts"><![CDATA[
-- #################################
-- Cleanup

set client_min_messages = 'warning';
DROP OPERATOR
    <@ (smoc, scircle),
    <@ (scircle, smoc),
    <@ (smoc, spoly),
    <@ (spoly, smoc),
    !<@ (smoc, scircle),
    !<@ (scircle, smoc),
    !<@ (smoc, spoly),
    !<@ (spoly, smoc),
    @> (smoc, scircle),
    @> (scircle, smoc),
    @> (smoc, spoly),
    @> (spoly, smoc),
    !@> (smoc, scircle),
    !@> (scircle, smoc),
    !@> (smoc, spoly),
    !@> (spoly, smoc),
    && (scircle, smoc),
    && (spoly, smoc),
    && (smoc, scircle),
    && (smoc, spoly),
    !&& (scircle, smoc),
    !&& (spoly, smoc),
    !&& (smoc, scircle),
    !&& (smoc, spoly);
reset client_min_messages;

-- #################################
--  smoc/geo OVERLAPS
CREATE OR REPLACE FUNCTION scircle_subset_smoc(
  geo_arg scircle, a_moc smoc) RETURNS BOOL AS $body$
    SELECT smoc(max_order(a_moc), geo_arg) <@ a_moc
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR <@ (
  LEFTARG    = scircle,
  RIGHTARG   = smoc,
  PROCEDURE  = scircle_subset_smoc,
  COMMUTATOR = '@>',
  NEGATOR    = '!<@',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);

CREATE OR REPLACE FUNCTION smoc_subset_scircle(
  a_moc smoc, geo_arg scircle) RETURNS BOOL AS $body$
    SELECT a_moc <@ smoc(max_order(a_moc), geo_arg)
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR <@ (
  LEFTARG    = smoc,
  RIGHTARG   = scircle,
  PROCEDURE  = smoc_subset_scircle,
  COMMUTATOR = '@>',
  NEGATOR    = '!<@',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);

CREATE OR REPLACE FUNCTION spoly_subset_smoc(
  geo_arg spoly, a_moc smoc) RETURNS BOOL AS $body$
    SELECT smoc(max_order(a_moc), geo_arg) <@ a_moc
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR <@ (
  LEFTARG    = spoly,
  RIGHTARG   = smoc,
  PROCEDURE  = spoly_subset_smoc,
  COMMUTATOR = '@>',
  NEGATOR    = '!<@',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);

CREATE OR REPLACE FUNCTION smoc_subset_spoly(
  a_moc smoc, geo_arg spoly) RETURNS BOOL AS $body$
    SELECT a_moc <@ smoc(max_order(a_moc), geo_arg)
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR <@ (
  LEFTARG    = smoc,
  RIGHTARG   = spoly,
  PROCEDURE  = smoc_subset_spoly,
  COMMUTATOR = '@>',
  NEGATOR    = '!<@',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);


CREATE OR REPLACE FUNCTION scircle_not_subset_smoc(
  geo_arg scircle, a_moc smoc) RETURNS BOOL AS $body$
    SELECT smoc(max_order(a_moc), geo_arg) !<@ a_moc
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR !<@ (
  LEFTARG    = scircle,
  RIGHTARG   = smoc,
  PROCEDURE  = scircle_not_subset_smoc,
  COMMUTATOR = '!@>',
  NEGATOR    = '<@',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);

CREATE OR REPLACE FUNCTION smoc_not_subset_scircle(
  a_moc smoc, geo_arg scircle) RETURNS BOOL AS $body$
    SELECT a_moc !<@ smoc(max_order(a_moc), geo_arg)
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR !<@ (
  LEFTARG    = smoc,
  RIGHTARG   = scircle,
  PROCEDURE  = smoc_not_subset_scircle,
  COMMUTATOR = '!@>',
  NEGATOR    = '<@',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);

CREATE OR REPLACE FUNCTION spoly_not_subset_smoc(
  geo_arg spoly, a_moc smoc) RETURNS BOOL AS $body$
    SELECT smoc(max_order(a_moc), geo_arg) !<@ a_moc
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR !<@ (
  LEFTARG    = spoly,
  RIGHTARG   = smoc,
  PROCEDURE  = spoly_not_subset_smoc,
  COMMUTATOR = '!@>',
  NEGATOR    = '<@',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);

CREATE OR REPLACE FUNCTION smoc_not_subset_spoly(
  a_moc smoc, geo_arg spoly) RETURNS BOOL AS $body$
    SELECT a_moc !<@ smoc(max_order(a_moc), geo_arg)
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR !<@ (
  LEFTARG    = smoc,
  RIGHTARG   = spoly,
  PROCEDURE  = smoc_not_subset_spoly,
  COMMUTATOR = '!@>',
  NEGATOR    = '<@',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);


CREATE OR REPLACE FUNCTION scircle_superset_smoc(
  geo_arg scircle, a_moc smoc) RETURNS BOOL AS $body$
    SELECT smoc(max_order(a_moc), geo_arg) @> a_moc
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR @> (
  LEFTARG    = scircle,
  RIGHTARG   = smoc,
  PROCEDURE  = scircle_superset_smoc,
  COMMUTATOR = '<@',
  NEGATOR    = '!@>',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);

CREATE OR REPLACE FUNCTION smoc_superset_scircle(
  a_moc smoc, geo_arg scircle) RETURNS BOOL AS $body$
    SELECT a_moc @> smoc(max_order(a_moc), geo_arg)
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR @> (
  LEFTARG    = smoc,
  RIGHTARG   = scircle,
  PROCEDURE  = smoc_superset_scircle,
  COMMUTATOR = '<@',
  NEGATOR    = '!@>',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);

CREATE OR REPLACE FUNCTION spoly_superset_smoc(
  geo_arg spoly, a_moc smoc) RETURNS BOOL AS $body$
    SELECT smoc(max_order(a_moc), geo_arg) @> a_moc
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR @> (
  LEFTARG    = spoly,
  RIGHTARG   = smoc,
  PROCEDURE  = spoly_superset_smoc,
  COMMUTATOR = '<@',
  NEGATOR    = '!@>',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);

CREATE OR REPLACE FUNCTION smoc_superset_spoly(
  a_moc smoc, geo_arg spoly) RETURNS BOOL AS $body$
    SELECT a_moc @> smoc(max_order(a_moc), geo_arg)
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR @> (
  LEFTARG    = smoc,
  RIGHTARG   = spoly,
  PROCEDURE  = smoc_superset_spoly,
  COMMUTATOR = '<@',
  NEGATOR    = '!@>',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);


CREATE OR REPLACE FUNCTION scircle_not_superset_smoc(
  geo_arg scircle, a_moc smoc) RETURNS BOOL AS $body$
    SELECT smoc(max_order(a_moc), geo_arg) !@> a_moc
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR !@> (
  LEFTARG    = scircle,
  RIGHTARG   = smoc,
  PROCEDURE  = scircle_not_superset_smoc,
  COMMUTATOR = '!<@',
  NEGATOR    = '@>',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);

CREATE OR REPLACE FUNCTION smoc_not_superset_scircle(
  a_moc smoc, geo_arg scircle) RETURNS BOOL AS $body$
    SELECT a_moc !@> smoc(max_order(a_moc), geo_arg)
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR !@> (
  LEFTARG    = smoc,
  RIGHTARG   = scircle,
  PROCEDURE  = smoc_not_superset_scircle,
  COMMUTATOR = '!<@',
  NEGATOR    = '@>',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);

CREATE OR REPLACE FUNCTION spoly_not_superset_smoc(
  geo_arg spoly, a_moc smoc) RETURNS BOOL AS $body$
    SELECT smoc(max_order(a_moc), geo_arg) !@> a_moc
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR !@> (
  LEFTARG    = spoly,
  RIGHTARG   = smoc,
  PROCEDURE  = spoly_not_superset_smoc,
  COMMUTATOR = '!<@',
  NEGATOR    = '@>',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);

CREATE OR REPLACE FUNCTION smoc_not_superset_spoly(
  a_moc smoc, geo_arg spoly) RETURNS BOOL AS $body$
    SELECT a_moc !@> smoc(max_order(a_moc), geo_arg)
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR !@> (
  LEFTARG    = smoc,
  RIGHTARG   = spoly,
  PROCEDURE  = smoc_not_superset_spoly,
  COMMUTATOR = '!<@',
  NEGATOR    = '@>',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);



-- #################################
--  smoc/geo INTERSECTS
CREATE OR REPLACE FUNCTION scircle_intersect_smoc(
  geo_arg scircle, a_moc smoc) RETURNS BOOL AS $body$
    SELECT smoc(max_order(a_moc), geo_arg) && a_moc
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR && (
  LEFTARG    = scircle,
  RIGHTARG   = smoc,
  PROCEDURE  = scircle_intersect_smoc,
  COMMUTATOR = '&&',
  NEGATOR    = '!&&',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);

CREATE OR REPLACE FUNCTION smoc_intersect_scircle(
  a_moc smoc, geo_arg scircle) RETURNS BOOL AS $body$
    SELECT a_moc && smoc(max_order(a_moc), geo_arg)
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR && (
  LEFTARG    = smoc,
  RIGHTARG   = scircle,
  PROCEDURE  = smoc_intersect_scircle,
  COMMUTATOR = '&&',
  NEGATOR    = '!&&',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);

CREATE OR REPLACE FUNCTION spoly_intersect_smoc(
  geo_arg spoly, a_moc smoc) RETURNS BOOL AS $body$
    SELECT smoc(max_order(a_moc), geo_arg) && a_moc
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR && (
  LEFTARG    = spoly,
  RIGHTARG   = smoc,
  PROCEDURE  = spoly_intersect_smoc,
  COMMUTATOR = '&&',
  NEGATOR    = '!&&',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);

CREATE OR REPLACE FUNCTION smoc_intersect_spoly(
  a_moc smoc, geo_arg spoly) RETURNS BOOL AS $body$
    SELECT a_moc && smoc(max_order(a_moc), geo_arg)
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR && (
  LEFTARG    = smoc,
  RIGHTARG   = spoly,
  PROCEDURE  = smoc_intersect_spoly,
  COMMUTATOR = '&&',
  NEGATOR    = '!&&',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);


CREATE OR REPLACE FUNCTION scircle_not_intersect_smoc(
  geo_arg scircle, a_moc smoc) RETURNS BOOL AS $body$
    SELECT smoc(max_order(a_moc), geo_arg) !&& a_moc
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR !&& (
  LEFTARG    = scircle,
  RIGHTARG   = smoc,
  PROCEDURE  = scircle_not_intersect_smoc,
  COMMUTATOR = '!&&',
  NEGATOR    = '&&',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);

CREATE OR REPLACE FUNCTION smoc_not_intersect_scircle(
  a_moc smoc, geo_arg scircle) RETURNS BOOL AS $body$
    SELECT a_moc !&& smoc(max_order(a_moc), geo_arg)
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR !&& (
  LEFTARG    = smoc,
  RIGHTARG   = scircle,
  PROCEDURE  = smoc_not_intersect_scircle,
  COMMUTATOR = '!&&',
  NEGATOR    = '&&',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);

CREATE OR REPLACE FUNCTION spoly_not_intersect_smoc(
  geo_arg spoly, a_moc smoc) RETURNS BOOL AS $body$
    SELECT smoc(max_order(a_moc), geo_arg) !&& a_moc
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR !&& (
  LEFTARG    = spoly,
  RIGHTARG   = smoc,
  PROCEDURE  = spoly_not_intersect_smoc,
  COMMUTATOR = '!&&',
  NEGATOR    = '&&',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);

CREATE OR REPLACE FUNCTION smoc_not_intersect_spoly(
  a_moc smoc, geo_arg spoly) RETURNS BOOL AS $body$
    SELECT a_moc !&& smoc(max_order(a_moc), geo_arg)
  $body$ LANGUAGE SQL STABLE;
CREATE OPERATOR !&& (
  LEFTARG    = smoc,
  RIGHTARG   = spoly,
  PROCEDURE  = smoc_not_intersect_spoly,
  COMMUTATOR = '!&&',
  NEGATOR    = '&&',
  RESTRICT   = contsel,
  JOIN       = contjoinsel
);
			]]></script>
		</make>
	</data>
</resource>
