DROP DOMAIN hexdigit CASCADE;
DROP DOMAIN hexstr_int8 CASCADE;
DROP DOMAIN hexstr_int4 CASCADE;
DROP DOMAIN hexstr_int2 CASCADE;
DROP DOMAIN hexstr_internal CASCADE;
DROP DOMAIN hexstr CASCADE;

-- input validation for user-called hex2int[248]
CREATE DOMAIN hexstr AS text CONSTRAINT hexstr_valid_format
  CHECK (lower(VALUE) ~ $re$^\s*(?:0x)?([0-9a-f]+)\s*$$re$);

CREATE DOMAIN hexstr_internal AS text CONSTRAINT hexstr_preproc_assertion
  CHECK (VALUE ~ '^[0-9a-f]+$');

CREATE DOMAIN hexstr_int2 AS text CONSTRAINT int2_max_hex_digits
  CHECK (length(VALUE) <= 4);

CREATE DOMAIN hexstr_int4 AS text CONSTRAINT int4_max_hex_digits
  CHECK (length(VALUE) <= 8);

CREATE DOMAIN hexstr_int8 AS text CONSTRAINT int8_max_hex_digits
  CHECK (length(VALUE) <= 16);

CREATE DOMAIN hexdigit AS "char" CONSTRAINT hexdigit_format_assertion
  CHECK (VALUE BETWEEN '0' AND '9' OR VALUE BETWEEN 'a' AND 'f');

-- given a hex string from the user, permute it into a more strict internal
-- format
CREATE OR REPLACE FUNCTION hexstr_preproc(hexstr) RETURNS hexstr_internal AS
$func$
  SELECT CAST(regexp_replace(
      lower($1),
      $re$^\s*(?:0x)?([0-9a-f]+)\s*$$re$,
      E'\\1'
  ) AS hexstr_internal);
$func$ LANGUAGE sql IMMUTABLE STRICT;

-- split out for readability - given a single char [0-9a-f], convert to
-- integer of that hex digit (0 - 15)
CREATE OR REPLACE FUNCTION hexdigit_to_decimal(hexdigit) RETURNS int AS $$
  SELECT CASE
  WHEN $1 BETWEEN '0' AND '9'
    THEN CAST($1 AS int4) - CAST('0'::"char" AS int4)
    ELSE CAST($1 AS int4) - CAST('a'::"char" AS int4) + 10
  END;
$$ LANGUAGE sql IMMUTABLE STRICT;

-- given a hex string in internal format, return each hex digit and the number
-- of bits to left-shift it by to be in the proper position in the resulting
-- integer
CREATE OR REPLACE FUNCTION hexdigit_factor_pairs(hexstr_internal, length int, OUT hexdigit hexdigit, OUT shiftfactor int) RETURNS SETOF record AS $$
  SELECT CAST(substr($1, i, 1) AS hexdigit),
    -- times 4 because each hex digit is 4 bits
    4 * ($2 - i) FROM
    generate_series($2, 1, -1) AS series(i)
$$ LANGUAGE sql IMMUTABLE STRICT;

CREATE OR REPLACE FUNCTION hex2int8_internal(hexstr_int8) RETURNS int8 AS $$
  -- bitwise or each digit shifted to its proper place to get the answer
  SELECT bit_or(hexdigit_to_decimal(hexdigit)::int8 << shiftfactor) FROM
  hexdigit_factor_pairs($1, length($1))
$$ LANGUAGE sql IMMUTABLE STRICT;

CREATE OR REPLACE FUNCTION hex2int4_internal(hexstr_int4) RETURNS int4 AS $$
  -- bitwise or each digit shifted to its proper place to get the answer
  SELECT bit_or(hexdigit_to_decimal(hexdigit)::int4 << shiftfactor) FROM
  hexdigit_factor_pairs($1, length($1))
$$ LANGUAGE sql IMMUTABLE STRICT;

CREATE OR REPLACE FUNCTION hex2int2_internal(hexstr_int2) RETURNS int2 AS $$
  -- bitwise or each digit shifted to its proper place to get the answer
  SELECT bit_or(hexdigit_to_decimal(hexdigit)::int2 << shiftfactor) FROM
  hexdigit_factor_pairs($1, length($1))
$$ LANGUAGE sql IMMUTABLE STRICT;

CREATE OR REPLACE FUNCTION hex2int8(hexstr) RETURNS int8 AS $$
  SELECT hex2int8_internal(hexstr_preproc($1));
$$ LANGUAGE sql IMMUTABLE STRICT;

CREATE OR REPLACE FUNCTION hex2int4(hexstr) RETURNS int4 AS $$
  SELECT hex2int4_internal(hexstr_preproc($1));
$$ LANGUAGE sql IMMUTABLE STRICT;

CREATE OR REPLACE FUNCTION hex2int2(hexstr) RETURNS int2 AS $$
  SELECT hex2int2_internal(hexstr_preproc($1));
$$ LANGUAGE sql IMMUTABLE STRICT;


-- vim: set ft=psql ts=2 sw=2 expandtab :