From ca475a20dfe785f46b74bb68cf4ad84e12790a0e Mon Sep 17 00:00:00 2001 From: Christoph Berg Date: Fri, 18 Jan 2019 15:50:14 +0100 Subject: [PATCH] WIP: Support typmods --- Makefile | 5 +- expected/typmod.out | 0 sql/typmod.sql | 17 + unit--7--8.sql.in | 24 ++ unit--8.sql.in | 739 ++++++++++++++++++++++++++++++++++++++++++++ unit.c | 140 ++++++++- unit.control | 2 +- 7 files changed, 923 insertions(+), 4 deletions(-) create mode 100644 expected/typmod.out create mode 100644 sql/typmod.sql create mode 100644 unit--7--8.sql.in create mode 100644 unit--8.sql.in diff --git a/Makefile b/Makefile index ee6ab08..2da6804 100644 --- a/Makefile +++ b/Makefile @@ -8,8 +8,9 @@ DATA_built = unit--2--3.sql unit--3.sql \ unit--3--4.sql unit--4.sql \ unit--4--5.sql unit--5.sql \ unit--5--6.sql unit--6.sql \ - unit--6--7.sql unit--7.sql -REGRESS = extension tables unit binary unicode prefix units time temperature functions round derived compare aggregate iec custom + unit--6--7.sql unit--7.sql \ + unit--7--8.sql unit--8.sql +REGRESS = extension tables unit typmod binary unicode prefix units time temperature functions round derived compare aggregate iec custom # Jessie's and trusty's bison (3.0.2) is buggy, ship pregenerated .tab files EXTRA_CLEAN = unitparse.yy.* powers powers.o unit-*.dump # unitparse.tab.* diff --git a/expected/typmod.out b/expected/typmod.out new file mode 100644 index 0000000..e69de29 diff --git a/sql/typmod.sql b/sql/typmod.sql new file mode 100644 index 0000000..69163a2 --- /dev/null +++ b/sql/typmod.sql @@ -0,0 +1,17 @@ +SET client_min_messages = warning; +DROP TABLE IF EXISTS unittypmod; +RESET client_min_messages; +CREATE TABLE unittypmod ( + --one unit(1), + length unit('LENGTH'), + area unit('AREA') +); + +\d unittypmod + +SELECT 'foo'::unit(''); +SELECT 'foo'::unit(1); +SELECT 'foo'::unit(LENGTH); +SELECT 'foo'::unit(AREA); +SELECT 'foo'::unit(foo); +SELECT 'foo'::unit(4); diff --git a/unit--7--8.sql.in b/unit--7--8.sql.in new file mode 100644 index 0000000..10d614f --- /dev/null +++ b/unit--7--8.sql.in @@ -0,0 +1,24 @@ +-- add typmod functions + +CREATE FUNCTION unit_typmod_in(cstring[]) + RETURNS integer + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION unit_typmod_out(integer) + RETURNS cstring + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +UPDATE pg_type SET typmodin = 'unit_typmod_in', typmodout = 'unit_typmod_out' + WHERE typname = 'unit' AND typnamespace = '@extschema@'::regnamespace; + +INSERT INTO pg_depend (classid, objid, objsubid, refclassid, refobjid, refobjsubid, deptype) + VALUES + ('pg_type'::regclass, 'unit'::regtype, 0, 'pg_proc'::regclass, 'unit_typmod_in'::regproc, 0, 'n'), + ('pg_type'::regclass, 'unit[]'::regtype, 0, 'pg_proc'::regclass, 'unit_typmod_in'::regproc, 0, 'n'), + ('pg_type'::regclass, 'unit'::regtype, 0, 'pg_proc'::regclass, 'unit_typmod_out'::regproc, 0, 'n'), + ('pg_type'::regclass, 'unit[]'::regtype, 0, 'pg_proc'::regclass, 'unit_typmod_out'::regproc, 0, 'n'); + +-- load prefixes and units tables +--SELECT unit_load(); diff --git a/unit--8.sql.in b/unit--8.sql.in new file mode 100644 index 0000000..01f575a --- /dev/null +++ b/unit--8.sql.in @@ -0,0 +1,739 @@ +/* +Copyright (C) 2016-2019 Christoph Berg + +This program 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. + +This program 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. +*/ + +-- type definition + +CREATE TYPE unit; + +CREATE FUNCTION unit_typmod_in(cstring[]) + RETURNS integer + SET search_path = @extschema@ + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION unit_typmod_out(integer) + RETURNS cstring + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION unit_in(cstring) + RETURNS unit + SET search_path = @extschema@ + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION unit_out(unit) + RETURNS cstring + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION unit_recv(internal) + RETURNS unit + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION unit_send(unit) + RETURNS bytea + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE unit ( + internallength = 16, + typmod_in = unit_typmod_in, + typmod_out = unit_typmod_out, + input = unit_in, + output = unit_out, + receive = unit_recv, + send = unit_send, + alignment = double +); + +-- prefix/unit definition tables + +CREATE TABLE unit_prefixes ( + prefix varchar(32) PRIMARY KEY, + factor double precision NOT NULL, + definition text, -- original definition, informational + dump boolean DEFAULT true +); + +SELECT pg_catalog.pg_extension_config_dump('unit_prefixes', 'WHERE dump'); + +CREATE TABLE unit_units ( + name varchar(32) PRIMARY KEY, + unit unit NOT NULL, + shift double precision, -- NULL means 0.0 here + definition text, -- original definition, informational + dump boolean DEFAULT true +); + +SELECT pg_catalog.pg_extension_config_dump('unit_units', 'WHERE dump'); + +GRANT SELECT ON unit_prefixes, unit_units TO PUBLIC; + +CREATE OR REPLACE FUNCTION unit_load() + RETURNS VOID + LANGUAGE plpgsql + AS $$BEGIN +DELETE FROM unit_prefixes WHERE dump IS NOT TRUE; +CREATE TEMP TABLE tmp_prefixes (LIKE unit_prefixes) ON COMMIT DROP; +COPY tmp_prefixes (prefix, factor, definition, dump) FROM '@MODULEDIR@/unit_prefixes.data'; +INSERT INTO unit_prefixes + SELECT * FROM tmp_prefixes t WHERE NOT EXISTS + (SELECT prefix FROM unit_prefixes u WHERE u.prefix = t.prefix); +DROP TABLE tmp_prefixes; + +DELETE FROM unit_units WHERE dump IS NOT TRUE; +CREATE TEMP TABLE tmp_units (LIKE unit_units) ON COMMIT DROP; +COPY tmp_units (name, unit, shift, definition, dump) FROM '@MODULEDIR@/unit_units.data'; +INSERT INTO unit_units + SELECT * FROM tmp_units t WHERE NOT EXISTS + (SELECT name FROM unit_units u WHERE u.name = t.name); +DROP TABLE tmp_units; +END;$$; + +COMMENT ON FUNCTION unit_load() IS 'Loads/upgrades the unit_units and unit_prefixes contents from the data files on disk'; + +SELECT unit_load(); + +-- constructors + +CREATE FUNCTION unit(double precision DEFAULT 1.0) + RETURNS unit + AS '$libdir/unit', 'dbl2unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION meter(double precision DEFAULT 1.0) + RETURNS unit + AS '$libdir/unit', 'unit_meter' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION kilogram(double precision DEFAULT 1.0) + RETURNS unit + AS '$libdir/unit', 'unit_kilogram' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION second(double precision DEFAULT 1.0) + RETURNS unit + AS '$libdir/unit', 'unit_second' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION ampere(double precision DEFAULT 1.0) + RETURNS unit + AS '$libdir/unit', 'unit_ampere' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION kelvin(double precision DEFAULT 1.0) + RETURNS unit + AS '$libdir/unit', 'unit_kelvin' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION mole(double precision DEFAULT 1.0) + RETURNS unit + AS '$libdir/unit', 'unit_mole' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION candela(double precision DEFAULT 1.0) + RETURNS unit + AS '$libdir/unit', 'unit_candela' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION byte(double precision DEFAULT 1.0) + RETURNS unit + AS '$libdir/unit', 'unit_byte' + LANGUAGE C IMMUTABLE STRICT; + +-- functions without operators + +CREATE FUNCTION value(unit) + RETURNS double precision + AS '$libdir/unit', 'unit_value' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION dimension(unit) + RETURNS unit + AS '$libdir/unit', 'unit_dimension' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION round(unit) + RETURNS unit + AS '$libdir/unit', 'unit_round' + LANGUAGE C IMMUTABLE STRICT; + +-- operators + +CREATE FUNCTION unit_add(unit, unit) + RETURNS unit + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR + ( + leftarg = unit, + rightarg = unit, + procedure = unit_add, + commutator = + +); + +CREATE FUNCTION unit_sub(unit, unit) + RETURNS unit + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR - ( + leftarg = unit, + rightarg = unit, + procedure = unit_sub +); + +CREATE FUNCTION unit_neg(unit) + RETURNS unit + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR - ( + rightarg = unit, + procedure = unit_neg +); + +CREATE FUNCTION unit_mul(unit, unit) + RETURNS unit + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR * ( + leftarg = unit, + rightarg = unit, + procedure = unit_mul, + commutator = * +); + +CREATE FUNCTION dbl_unit_mul(double precision, unit) + RETURNS unit + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR * ( + leftarg = double precision, + rightarg = unit, + procedure = dbl_unit_mul, + commutator = * +); + +CREATE FUNCTION unit_dbl_mul(unit, double precision) + RETURNS unit + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR * ( + leftarg = unit, + rightarg = double precision, + procedure = unit_dbl_mul, + commutator = * +); + +CREATE FUNCTION unit_div(unit, unit) + RETURNS unit + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR / ( + leftarg = unit, + rightarg = unit, + procedure = unit_div +); + +CREATE FUNCTION dbl_unit_div(double precision, unit) + RETURNS unit + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR / ( + leftarg = double precision, + rightarg = unit, + procedure = dbl_unit_div +); + +CREATE FUNCTION unit_dbl_div(unit, double precision) + RETURNS unit + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR / ( + leftarg = unit, + rightarg = double precision, + procedure = unit_dbl_div +); + +CREATE FUNCTION unit_pow(unit, int) + RETURNS unit + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR ^ ( + leftarg = unit, + rightarg = int, + procedure = unit_pow +); + +CREATE FUNCTION sqrt(unit) + RETURNS unit + AS '$libdir/unit', 'unit_sqrt' + LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR |/ ( + rightarg = unit, + procedure = sqrt +); + +CREATE FUNCTION cbrt(unit) + RETURNS unit + AS '$libdir/unit', 'unit_cbrt' + LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR ||/ ( + rightarg = unit, + procedure = cbrt +); + +CREATE FUNCTION unit_at(unit, text) + RETURNS text + SET search_path = @extschema@ + AS '$libdir/unit', 'unit_at_text2' + LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR @ ( + leftarg = unit, + rightarg = text, + procedure = unit_at +); + +CREATE FUNCTION unit_at_double(unit, text) + RETURNS double precision + SET search_path = @extschema@ + AS '$libdir/unit', 'unit_at_double' + LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR @@ ( + leftarg = unit, + rightarg = text, + procedure = unit_at_double +); + +-- derived units + +CREATE FUNCTION radian (double precision DEFAULT 1.0) -- m·m^-1 + RETURNS unit + AS $$SELECT meter($1) / meter()$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION steradian (double precision DEFAULT 1.0) -- m^2·m^-2 + RETURNS unit + AS $$SELECT meter($1) * meter() / meter()^2$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION hertz (double precision DEFAULT 1.0) -- s^-1 + RETURNS unit + AS $$SELECT $1 / second()$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION newton (double precision DEFAULT 1.0) -- kg·m·s^-2 + RETURNS unit + AS $$SELECT kilogram($1) * meter() / second()^2$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION pascal (double precision DEFAULT 1.0) -- N/m^2 + RETURNS unit + AS $$SELECT newton($1) / meter()^2 $$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION joule (double precision DEFAULT 1.0) -- N·m + RETURNS unit + AS $$SELECT newton($1) * meter()$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION watt (double precision DEFAULT 1.0) -- J/s + RETURNS unit + AS $$SELECT joule($1) / second()$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION coulomb (double precision DEFAULT 1.0) -- A·s + RETURNS unit + AS $$SELECT ampere($1) * second()$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION volt (double precision DEFAULT 1.0) -- W/A + RETURNS unit + AS $$SELECT watt($1) / ampere() $$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION farad (double precision DEFAULT 1.0) -- C/V + RETURNS unit + AS $$SELECT coulomb($1) / volt() $$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION ohm (double precision DEFAULT 1.0) -- V/A + RETURNS unit + AS $$SELECT volt($1) / ampere() $$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION siemens (double precision DEFAULT 1.0) -- A/V + RETURNS unit + AS $$SELECT ampere($1) / volt()$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION weber (double precision DEFAULT 1.0) -- V·s + RETURNS unit + AS $$SELECT volt($1) * second()$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION tesla (double precision DEFAULT 1.0) -- Wb/m^2 + RETURNS unit + AS $$SELECT weber($1) / meter()^2$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION henry (double precision DEFAULT 1.0) -- Wb/A + RETURNS unit + AS $$SELECT weber($1) / ampere()$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION celsius (double precision DEFAULT 0.0) -- K relative to 273.15, default to 0°C + RETURNS unit + AS $$SELECT kelvin($1 + 273.15)$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION lumen (double precision DEFAULT 1.0) -- cd·sr + RETURNS unit + AS $$SELECT candela($1) * steradian()$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION lux (double precision DEFAULT 1.0) -- lm/m^2 + RETURNS unit + AS $$SELECT lumen($1) / meter()^2$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION becquerel (double precision DEFAULT 1.0) -- s^-1 + RETURNS unit + AS $$SELECT $1 / second()$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION gray (double precision DEFAULT 1.0) -- J/kg + RETURNS unit + AS $$SELECT joule($1) / kilogram()$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION sievert (double precision DEFAULT 1.0) -- J/kg + RETURNS unit + AS $$SELECT joule($1)/ kilogram()$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION katal (double precision DEFAULT 1.0) -- mol·s^-1 + RETURNS unit + AS $$SELECT mole($1) / second()$$ + LANGUAGE SQL IMMUTABLE STRICT; + +-- Non-SI units accepted for use with the SI +--minute, hour, day, degree of arc, minute of arc, second of arc, hectare, litre, tonne, astronomical unit and [deci]bel + +CREATE FUNCTION minute (double precision DEFAULT 1.0) + RETURNS unit + AS $$SELECT second($1 * 60)$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION hour (double precision DEFAULT 1.0) + RETURNS unit + AS $$SELECT second($1 * 3600)$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION day (double precision DEFAULT 1.0) + RETURNS unit + AS $$SELECT second($1 * 86400)$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION degree_arc(double precision DEFAULT 1.0) + RETURNS unit + AS $$SELECT $1 * '1'::unit * pi() / 180$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION minute_arc(double precision DEFAULT 1.0) + RETURNS unit + AS $$SELECT $1 * '1'::unit * pi() / 180 / 60$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION second_arc(double precision DEFAULT 1.0) + RETURNS unit + AS $$SELECT $1 * '1'::unit * pi() / 180 / 3600$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION hectare (double precision DEFAULT 1.0) + RETURNS unit + AS $$SELECT $1 * meter(100)^2$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION liter (double precision DEFAULT 1.0) + RETURNS unit + AS $$SELECT $1 * meter(0.1)^3$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION tonne (double precision DEFAULT 1.0) + RETURNS unit + AS $$SELECT kilogram($1 * 1000)$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION au (double precision DEFAULT 1.0) + RETURNS unit + AS $$SELECT meter($1 * 149597870700)$$ + LANGUAGE SQL IMMUTABLE STRICT; +CREATE FUNCTION decibel (double precision DEFAULT 0.0) + RETURNS double precision + AS $$SELECT 10.0^($1 / 10.0)$$ + LANGUAGE SQL IMMUTABLE STRICT; + +-- comparisons + +CREATE FUNCTION unit_lt(unit, unit) RETURNS bool + AS '$libdir/unit' LANGUAGE C IMMUTABLE STRICT; +CREATE FUNCTION unit_le(unit, unit) RETURNS bool + AS '$libdir/unit' LANGUAGE C IMMUTABLE STRICT; +CREATE FUNCTION unit_eq(unit, unit) RETURNS bool + AS '$libdir/unit' LANGUAGE C IMMUTABLE STRICT; +CREATE FUNCTION unit_ne(unit, unit) RETURNS bool + AS '$libdir/unit' LANGUAGE C IMMUTABLE STRICT; +CREATE FUNCTION unit_ge(unit, unit) RETURNS bool + AS '$libdir/unit' LANGUAGE C IMMUTABLE STRICT; +CREATE FUNCTION unit_gt(unit, unit) RETURNS bool + AS '$libdir/unit' LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR < ( + leftarg = unit, rightarg = unit, procedure = unit_lt, + commutator = > , negator = >= , + restrict = scalarltsel, join = scalarltjoinsel +); +CREATE OPERATOR <= ( + leftarg = unit, rightarg = unit, procedure = unit_le, + commutator = >= , negator = > , + restrict = scalarltsel, join = scalarltjoinsel +); +CREATE OPERATOR = ( + leftarg = unit, rightarg = unit, procedure = unit_eq, + commutator = = , negator = <> , + restrict = eqsel, join = eqjoinsel +); +CREATE OPERATOR <> ( + leftarg = unit, rightarg = unit, procedure = unit_ne, + commutator = <> , negator = = , + restrict = neqsel, join = neqjoinsel +); +CREATE OPERATOR >= ( + leftarg = unit, rightarg = unit, procedure = unit_ge, + commutator = <= , negator = < , + restrict = scalargtsel, join = scalargtjoinsel +); +CREATE OPERATOR > ( + leftarg = unit, rightarg = unit, procedure = unit_gt, + commutator = < , negator = <= , + restrict = scalargtsel, join = scalargtjoinsel +); + +CREATE FUNCTION unit_cmp(unit, unit) + RETURNS int4 + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR CLASS unit_ops + DEFAULT FOR TYPE unit USING btree AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 unit_cmp(unit, unit); + +-- strict comparisons + +CREATE FUNCTION unit_strict_lt(unit, unit) RETURNS bool + AS '$libdir/unit' LANGUAGE C IMMUTABLE STRICT; +CREATE FUNCTION unit_strict_le(unit, unit) RETURNS bool + AS '$libdir/unit' LANGUAGE C IMMUTABLE STRICT; +CREATE FUNCTION unit_strict_eq(unit, unit) RETURNS bool + AS '$libdir/unit' LANGUAGE C IMMUTABLE STRICT; +CREATE FUNCTION unit_strict_ne(unit, unit) RETURNS bool + AS '$libdir/unit' LANGUAGE C IMMUTABLE STRICT; +CREATE FUNCTION unit_strict_ge(unit, unit) RETURNS bool + AS '$libdir/unit' LANGUAGE C IMMUTABLE STRICT; +CREATE FUNCTION unit_strict_gt(unit, unit) RETURNS bool + AS '$libdir/unit' LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR << ( + leftarg = unit, rightarg = unit, procedure = unit_strict_lt, + commutator = >> , negator = >>= , + restrict = scalarltsel, join = scalarltjoinsel +); +CREATE OPERATOR <<= ( + leftarg = unit, rightarg = unit, procedure = unit_strict_le, + commutator = >>= , negator = >> , + restrict = scalarltsel, join = scalarltjoinsel +); +CREATE OPERATOR == ( + leftarg = unit, rightarg = unit, procedure = unit_strict_eq, + commutator = == , negator = <<>> , + restrict = eqsel, join = eqjoinsel +); +CREATE OPERATOR <<>> ( + leftarg = unit, rightarg = unit, procedure = unit_strict_ne, + commutator = <<>> , negator = == , + restrict = neqsel, join = neqjoinsel +); +CREATE OPERATOR >>= ( + leftarg = unit, rightarg = unit, procedure = unit_strict_ge, + commutator = <<= , negator = << , + restrict = scalargtsel, join = scalargtjoinsel +); +CREATE OPERATOR >> ( + leftarg = unit, rightarg = unit, procedure = unit_strict_gt, + commutator = << , negator = <<= , + restrict = scalargtsel, join = scalargtjoinsel +); + +CREATE FUNCTION unit_strict_cmp(unit, unit) + RETURNS int4 + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR CLASS unit_strict_ops + FOR TYPE unit USING btree AS + OPERATOR 1 << , + OPERATOR 2 <<= , + OPERATOR 3 == , + OPERATOR 4 >>= , + OPERATOR 5 >> , + FUNCTION 1 unit_strict_cmp(unit, unit); + +-- range type + +CREATE FUNCTION unit_diff(unit, unit) + RETURNS float8 + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +COMMENT ON FUNCTION unit_diff(unit, unit) IS 'returns difference of two units as float8 for use in the unitrange type'; + +CREATE TYPE unitrange AS RANGE ( + SUBTYPE = unit, + SUBTYPE_OPCLASS = unit_strict_ops, + SUBTYPE_DIFF = unit_diff +); + +-- aggregates + +CREATE AGGREGATE sum(unit) +( + sfunc = unit_add, + stype = unit +); + +CREATE FUNCTION unit_least(unit, unit) + RETURNS unit + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE AGGREGATE min(unit) +( + sfunc = unit_least, + stype = unit +); + +CREATE FUNCTION unit_greatest(unit, unit) + RETURNS unit + AS '$libdir/unit' + LANGUAGE C IMMUTABLE STRICT; + +CREATE AGGREGATE max(unit) +( + sfunc = unit_greatest, + stype = unit +); + +CREATE TYPE unit_accum_t AS ( + s unit, + squares double precision, + n bigint +); + +CREATE FUNCTION unit_accum(a unit_accum_t, u unit) + RETURNS unit_accum_t + AS $$SELECT (CASE WHEN a.s = '0'::unit THEN u ELSE a.s + u END, a.squares + value(u)^2, a.n + 1)::unit_accum_t$$ + LANGUAGE SQL IMMUTABLE STRICT; + +CREATE FUNCTION unit_avg(a unit_accum_t) + RETURNS unit + AS $$SELECT CASE WHEN a.n > 0 THEN a.s / a.n ELSE NULL END$$ + LANGUAGE SQL IMMUTABLE STRICT; + +CREATE AGGREGATE avg(unit) +( + sfunc = unit_accum, + stype = unit_accum_t, + finalfunc = unit_avg, + initcond = '(0,0,0)' +); + +CREATE FUNCTION unit_var_pop(a unit_accum_t) + RETURNS double precision + AS $$SELECT CASE WHEN a.n > 0 THEN (a.squares - value(a.s)^2 / a.n) / a.n ELSE NULL END$$ + LANGUAGE SQL IMMUTABLE STRICT; + +CREATE AGGREGATE var_pop(unit) +( + sfunc = unit_accum, + stype = unit_accum_t, + finalfunc = unit_var_pop, + initcond = '(0,0,0)' +); + +CREATE FUNCTION unit_var_samp(a unit_accum_t) + RETURNS double precision + AS $$SELECT CASE WHEN a.n > 1 THEN (a.squares - value(a.s)^2 / a.n) / (a.n - 1) WHEN a.n = 1 THEN 0 ELSE NULL END$$ + LANGUAGE SQL IMMUTABLE STRICT; + +CREATE AGGREGATE var_samp(unit) +( + sfunc = unit_accum, + stype = unit_accum_t, + finalfunc = unit_var_samp, + initcond = '(0,0,0)' +); + +CREATE AGGREGATE variance(unit) +( + sfunc = unit_accum, + stype = unit_accum_t, + finalfunc = unit_var_samp, + initcond = '(0,0,0)' +); + +CREATE FUNCTION unit_stddev_pop(a unit_accum_t) + RETURNS unit + AS $$SELECT CASE WHEN a.n > 0 THEN sqrt((a.squares - value(a.s)^2 / a.n) / a.n) * dimension(a.s) ELSE NULL END$$ + LANGUAGE SQL IMMUTABLE STRICT; + +CREATE AGGREGATE stddev_pop(unit) +( + sfunc = unit_accum, + stype = unit_accum_t, + finalfunc = unit_stddev_pop, + initcond = '(0,0,0)' +); + +CREATE FUNCTION unit_stddev_samp(a unit_accum_t) + RETURNS unit + AS $$SELECT CASE WHEN a.n > 1 THEN sqrt((a.squares - value(a.s)^2 / a.n) / (a.n - 1)) * dimension(a.s) WHEN a.n = 1 THEN 0 * dimension(a.s) ELSE NULL END$$ + LANGUAGE SQL IMMUTABLE STRICT; + +CREATE AGGREGATE stddev_samp(unit) +( + sfunc = unit_accum, + stype = unit_accum_t, + finalfunc = unit_stddev_samp, + initcond = '(0,0,0)' +); + +CREATE AGGREGATE stddev(unit) +( + sfunc = unit_accum, + stype = unit_accum_t, + finalfunc = unit_stddev_samp, + initcond = '(0,0,0)' +); + +-- internal functions + +CREATE FUNCTION unit_is_hashed(cstring) + RETURNS bool + AS '$libdir/unit' + LANGUAGE C VOLATILE STRICT; + +CREATE FUNCTION unit_reset() + RETURNS void + AS '$libdir/unit' + LANGUAGE C VOLATILE STRICT; diff --git a/unit.c b/unit.c index 62e443d..6fa4252 100644 --- a/unit.c +++ b/unit.c @@ -1,5 +1,5 @@ /* -Copyright (C) 2016-2018 Christoph Berg +Copyright (C) 2016-2019 Christoph Berg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -14,6 +14,8 @@ GNU General Public License for more details. #include "postgres.h" #include "fmgr.h" +#include "catalog/pg_type.h" /* for CSTRINGOID */ +#include "executor/spi.h" #include "libpq/pqformat.h" /* send/recv */ #include "utils/builtins.h" /* cstring_to_text (needed on 9.5) */ #include "utils/guc.h" @@ -45,6 +47,7 @@ static HTAB *unit_dimensions; /* unit definitions */ +/* create hash tables and populate them with base data */ void unit_get_definitions(void); void @@ -503,6 +506,141 @@ unit_cstring (Unit *unit) return output; } +/* type mod */ + +static int +unit_get_typmod(char *typmodstr) +{ + unit_names_t *name; + int ret; + Oid argtypes[1]; + Datum values[1]; + Unit *unitp; + + /* Check if it's a predefined or previously seen unit */ + name = hash_search(unit_names, typmodstr, HASH_FIND, NULL); + if (name) + { + elog(DEBUG1, "unit %s found in unit_names hash table", name->name); + if (name->unit_shift.unit.value != 1.0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("unit \"%s\" cannot be used as type modifier", typmodstr), + errdetail("unit value is not one in base units"))); + if (name->unit_shift.shift != 0.0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("unit \"%s\" cannot be used as type modifier", typmodstr), + errdetail("unit is a shifted unit"))); + unitp = &name->unit_shift.unit; + goto found; + } + + SPI_connect(); + + argtypes[0] = TEXTOID; + values[0] = CStringGetTextDatum(typmodstr); + + /* look up unit definition without prefix */ + ret = SPI_execute_with_args("SELECT unit, shift " + "FROM unit_units WHERE " + "name = $1", + 1, /* nargs */ + argtypes, + values, + NULL, /* nulls */ + true, /* read only */ + 0); /* limit */ + if (ret != SPI_OK_SELECT) + elog(ERROR, "internal error determining definition of unit \"%s\"", typmodstr); + + if (SPI_processed != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("unit \"%s\" is not known", typmodstr))); + + { + bool is_null; + + unitp = (Unit *) DatumGetPointer(SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &is_null)); + if (is_null) + elog(ERROR, "unit \"%s\" definition is NULL", typmodstr); + if (unitp->value != 1.0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("unit \"%s\" cannot be used as type modifier", typmodstr), + errdetail("unit value is not one in base units"))); + + /* result unused */ SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 2, &is_null); + if (!is_null) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("unit \"%s\" cannot be used as type modifier", typmodstr), + errdetail("unit is a shifted unit"))); + } + + SPI_finish(); + +found: + return unitp->units[UNIT_m]; /* TODO */ +} + +PG_FUNCTION_INFO_V1 (unit_typmod_in); + +Datum +unit_typmod_in (PG_FUNCTION_ARGS) +{ + ArrayType *arr = (ArrayType *) DatumGetPointer(PG_GETARG_DATUM(0)); + 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); + + if (n > 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("typmod array must exactly one element"))); + + typmod = unit_get_typmod(DatumGetCString(elem_values[i])); + + pfree(elem_values); + + return typmod; +} + +PG_FUNCTION_INFO_V1 (unit_typmod_out); + +Datum +unit_typmod_out (PG_FUNCTION_ARGS) +{ + int typmod = PG_GETARG_INT32(0); + switch (typmod) { + case 0: PG_RETURN_CSTRING("(1)"); break; + case 1: PG_RETURN_CSTRING("(LENGTH)"); break; + case 2: PG_RETURN_CSTRING("(AREA)"); break; + } + PG_RETURN_CSTRING(psprintf("(%d)", typmod)); +} + /* input and output */ char *yyerrstr; /* copy of error catched by yyuniterror() */ diff --git a/unit.control b/unit.control index 9a2b8a0..9c4d190 100644 --- a/unit.control +++ b/unit.control @@ -1,4 +1,4 @@ -default_version = '7' +default_version = '8' comment = 'SI units extension' # the unit_prefixes/unit_units tables can be installed in an arbitrary schema, # but can not be relocated later: