From 74a49bd0ca0f82c593525f4ad3621463e74ad6cf Mon Sep 17 00:00:00 2001 From: Jason Rogers Date: Thu, 5 Mar 2020 02:43:10 -0500 Subject: [PATCH 1/4] feat: implements encoding of floating point numbers as float64 --- src/encode.sql | 79 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 4 deletions(-) diff --git a/src/encode.sql b/src/encode.sql index e8d4f92..0f60687 100644 --- a/src/encode.sql +++ b/src/encode.sql @@ -1,3 +1,75 @@ +-- +-- float_to_bytea adapted from https://github.com/feross/ieee754 +-- +create or replace function float_to_bytea(value float8, isLittleEndian boolean) +returns bytea language plpgsql +as $function$ +declare + m bigint; + numeric_e bigint; + _pack bytea := E'\\313 '::bytea; + mlen int := 52; + elen int := 64 - mlen - 1; + emax int := (1 << elen) - 1; + ebias int := emax >> 1; + rt numeric := case when mlen = 23 then power(2, -24) - power(2, -77) else 0 end; + i int := case when isLittleEndian then 0 else 7 end; + d int := case when isLittleEndian then 1 else -1 end; + s int := case when value < 0 OR (value = 0 AND 1 / value < 0) then 1 else 0 end; + e int := floor(ln(value) / ln(2)); + c float8 := power(2, -e); +begin + if (value * c) < 1 then + e := e - 1; + c := c * 2; + end if; + + if (e + ebias) >= 1 then + value := value + (rt / c); + else + value := value + (rt * power(2, (1 - ebias))); + end if; + + if (value * c) >= 2 then + e := e + 1; + c := c / 2; + end if; + + if (e + ebias) >= emax then + m = 0; + e = emax; + elsif (e + ebias) >= 1 then + m = (((value * c) - 1) * power(2, mlen))::bigint; + e = e + ebias; + else + m = value * power(2, (eBias - 1)) * power(2, mlen); + e = 0; + end if; + + loop + exit when mlen < 8; + _pack := set_byte(_pack, 1 + i, (m::bigint & 255)::int); + i := i + d; + m := m / 256; + mlen := mlen - 8; + end loop; + + numeric_e = (e << mlen)::bigint | trunc(m)::bigint; + elen := elen + mlen; + + loop + exit when elen <= 0; + _pack := set_byte(_pack, 1 + i, (numeric_e & 255)::int); + i := i + d; + numeric_e := numeric_e / 256; + elen := elen - 8; + end loop; + + _pack := set_byte(_pack, 1 + i - d, get_byte(_pack, 1 + i - d) | trunc(s * 128)::int); + return _pack; +end; +$function$; + create or replace function msgpack_encode(_data jsonb) returns bytea language plpgsql as $function$ declare _size integer; @@ -61,10 +133,9 @@ begin when 'number' then _numeric = (_data#>>'{}')::numeric; if _numeric % 1 != 0 then - raise exception 'Float not implemented yet.'; - end if; - - if _numeric > 0 then + -- treat all floats as 64-bit floats + _pack = float_to_bytea(_numeric, false); + elsif _numeric > 0 then -- Integer if _numeric < 2 ^ 7 then _pack = set_byte(E' '::bytea, 0, _numeric::integer); From 6635905c12d3a527b8e359e0292ce172424a9215 Mon Sep 17 00:00:00 2001 From: Jason Rogers Date: Fri, 6 Mar 2020 18:07:46 -0500 Subject: [PATCH 2/4] fix: fixes integer encoding --- src/encode.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/encode.sql b/src/encode.sql index 0f60687..e3a9e5e 100644 --- a/src/encode.sql +++ b/src/encode.sql @@ -142,17 +142,17 @@ begin elsif _numeric < 2 ^ 8 then _pack = E'\\314'::bytea || set_byte(E' '::bytea, 0, _numeric::integer); - elsif _numeric < 2 ^ 16 then + elsif _numeric < 2 ^ 15 then _pack = E'\\315'::bytea || set_byte(E' '::bytea, 0, (_numeric::integer >> 8) & 255) || set_byte(E' '::bytea, 0, _numeric::integer & 255); - elsif _numeric < 2 ^ 32 then + elsif _numeric < 2 ^ 31 then _pack = E'\\316'::bytea || set_byte(E' '::bytea, 0, (_numeric::integer >> 24) & 255) || set_byte(E' '::bytea, 0, (_numeric::integer >> 16) & 255) || set_byte(E' '::bytea, 0, (_numeric::integer >> 8) & 255) || set_byte(E' '::bytea, 0, _numeric::integer & 255); - elsif _numeric < 2 ^ 64 then + elsif _numeric < 2 ^ 63 then _pack = E'\\317'::bytea || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 56) & 255)::integer) || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 48) & 255)::integer) From c825c1b7ebaf0cfb35d15135981425560c77d079 Mon Sep 17 00:00:00 2001 From: Jason Rogers Date: Fri, 6 Mar 2020 21:53:31 -0500 Subject: [PATCH 3/4] fix: fixes encoding neg. floats; adds support for NaN, Infinity, & -Infinity --- src/encode.sql | 372 ++++++++++++++++++++++++++----------------------- 1 file changed, 197 insertions(+), 175 deletions(-) diff --git a/src/encode.sql b/src/encode.sql index e3a9e5e..299b8ff 100644 --- a/src/encode.sql +++ b/src/encode.sql @@ -1,3 +1,14 @@ +create or replace function is_nan(float8) +returns boolean language sql immutable +as $function$ + select $1 = double precision 'NaN' +$function$; + +create or replace function is_negative_zero(float8) +returns boolean language sql immutable +as $function$ + select $1::text = '-0' +$function$; -- -- float_to_bytea adapted from https://github.com/feross/ieee754 -- @@ -15,35 +26,46 @@ declare rt numeric := case when mlen = 23 then power(2, -24) - power(2, -77) else 0 end; i int := case when isLittleEndian then 0 else 7 end; d int := case when isLittleEndian then 1 else -1 end; - s int := case when value < 0 OR (value = 0 AND 1 / value < 0) then 1 else 0 end; - e int := floor(ln(value) / ln(2)); - c float8 := power(2, -e); + s int := case when value < 0 OR is_negative_zero(value) then 1 else 0 end; + e int; + c float8; begin - if (value * c) < 1 then - e := e - 1; - c := c * 2; - end if; + value := abs(value); - if (e + ebias) >= 1 then - value := value + (rt / c); + if is_nan(value) OR value = 'infinity'::float8 + then + m := case when is_nan(value) then 1 else 0 end; + e := emax; else - value := value + (rt * power(2, (1 - ebias))); - end if; + e := floor(ln(value) / ln(2)); + c := power(2, -e); - if (value * c) >= 2 then - e := e + 1; - c := c / 2; - end if; + if (value * c) < 1 then + e := e - 1; + c := c * 2; + end if; - if (e + ebias) >= emax then - m = 0; - e = emax; - elsif (e + ebias) >= 1 then - m = (((value * c) - 1) * power(2, mlen))::bigint; - e = e + ebias; - else - m = value * power(2, (eBias - 1)) * power(2, mlen); - e = 0; + if (e + ebias) >= 1 then + value := value + (rt / c); + else + value := value + (rt * power(2, (1 - ebias))); + end if; + + if (value * c) >= 2 then + e := e + 1; + c := c / 2; + end if; + + if (e + ebias) >= emax then + m = 0; + e = emax; + elsif (e + ebias) >= 1 then + m = (((value * c) - 1) * power(2, mlen))::bigint; + e = e + ebias; + else + m = value * power(2, (eBias - 1)) * power(2, mlen); + e = 0; + end if; end if; loop @@ -80,157 +102,157 @@ declare _numeric numeric; declare _item jsonb; begin - case jsonb_typeof(_data) - when 'object' then - -- Get count of items in object - select count(jsonb_object_keys) into _size from jsonb_object_keys(_data); - - if _size < 16 then - _pack = set_byte(E' '::bytea, 0, (128::bit(8) | _size::bit(8))::integer); - elsif _size < 2 ^ 16 then - _pack = E'\\336'::bytea - || set_byte(E' '::bytea, 0, _size >> 8) - || set_byte(E' '::bytea, 0, _size); - elsif _size < 2 ^ 32 then - _pack = E'\\337'::bytea - || set_byte(E' '::bytea, 0, _size >> 24) - || set_byte(E' '::bytea, 0, _size >> 16) - || set_byte(E' '::bytea, 0, _size >> 8) - || set_byte(E' '::bytea, 0, _size); - else - raise exception 'Maximum number of keys exceeded.'; - end if; - - -- Process items - for _key in select jsonb_object_keys from jsonb_object_keys(_data) loop - _pack = _pack || public.msgpack_encode(to_jsonb(_key)) || public.msgpack_encode(_data->_key); - end loop; - - when 'array' then - select jsonb_array_length into _size from jsonb_array_length(_data); - - if _size < 16 then - _pack = set_byte(E' '::bytea, 0, (144::bit(8) | _size::bit(8))::integer); - elsif _size < 2 ^ 16 then - _pack = E'\\334'::bytea - || set_byte(E' '::bytea, 0, _size >> 8) - || set_byte(E' '::bytea, 0, _size); - elsif _size < 2 ^ 32 then - _pack = E'\\335'::bytea - || set_byte(E' '::bytea, 0, _size >> 24) - || set_byte(E' '::bytea, 0, _size >> 16) - || set_byte(E' '::bytea, 0, _size >> 8) - || set_byte(E' '::bytea, 0, _size); - else - raise exception 'Maximum number of items exceeded.'; - end if; - - -- Process items - for _item in select value from jsonb_array_elements(_data) loop - _pack = _pack || public.msgpack_encode(_item); - end loop; - - when 'number' then - _numeric = (_data#>>'{}')::numeric; - if _numeric % 1 != 0 then - -- treat all floats as 64-bit floats - _pack = float_to_bytea(_numeric, false); - elsif _numeric > 0 then - -- Integer - if _numeric < 2 ^ 7 then - _pack = set_byte(E' '::bytea, 0, _numeric::integer); - elsif _numeric < 2 ^ 8 then - _pack = E'\\314'::bytea - || set_byte(E' '::bytea, 0, _numeric::integer); - elsif _numeric < 2 ^ 15 then - _pack = E'\\315'::bytea - || set_byte(E' '::bytea, 0, (_numeric::integer >> 8) & 255) - || set_byte(E' '::bytea, 0, _numeric::integer & 255); - elsif _numeric < 2 ^ 31 then - _pack = E'\\316'::bytea - || set_byte(E' '::bytea, 0, (_numeric::integer >> 24) & 255) - || set_byte(E' '::bytea, 0, (_numeric::integer >> 16) & 255) - || set_byte(E' '::bytea, 0, (_numeric::integer >> 8) & 255) - || set_byte(E' '::bytea, 0, _numeric::integer & 255); - elsif _numeric < 2 ^ 63 then - _pack = E'\\317'::bytea - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 56) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 48) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 40) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 32) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 24) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 16) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 8) & 255)::integer) - || set_byte(E' '::bytea, 0, (_numeric::bigint & 255)::integer); - else - raise exception 'Integer out of range.'; - end if; - else - if _numeric >= -2 ^ 5 then - _pack = set_byte(E' '::bytea, 0, _numeric::integer); - elsif _numeric >= -2 ^ 7 then - _pack = E'\\320'::bytea - || set_byte(E' '::bytea, 0, _numeric::integer); - elsif _numeric >= -2 ^ 15 then - _pack = E'\\321'::bytea - || set_byte(E' '::bytea, 0, (_numeric::integer >> 8) & 255) - || set_byte(E' '::bytea, 0, _numeric::integer & 255); - elsif _numeric >= -2 ^ 31 then - _pack = E'\\322'::bytea - || set_byte(E' '::bytea, 0, (_numeric::integer >> 24) & 255) - || set_byte(E' '::bytea, 0, (_numeric::integer >> 16) & 255) - || set_byte(E' '::bytea, 0, (_numeric::integer >> 8) & 255) - || set_byte(E' '::bytea, 0, _numeric::integer & 255); - elsif _numeric >= -2 ^ 63 then - _pack = E'\\323'::bytea - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 56) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 48) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 40) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 32) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 24) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 16) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 8) & 255)::integer) - || set_byte(E' '::bytea, 0, (_numeric::bigint & 255)::integer); - else - raise exception 'Integer out of range.'; - end if; - end if; - - when 'string' then - _chunk = convert_to(_data#>>'{}', 'utf8'); - _size = octet_length(_chunk); - - if _size <= 31 then - _pack = set_byte(E' '::bytea, 0, ((160)::bit(8) | (_size)::bit(8))::integer); - elsif _size <= (2 ^ 8) - 1 then - _pack = E'\\331'::bytea || set_byte(E' '::bytea, 0, _size); - elsif _size <= (2 ^ 16) - 1 then - _pack = E'\\332'::bytea - || set_byte(E' '::bytea, 0, _size >> 8) - || set_byte(E' '::bytea, 0, _size); - elsif _size <= (2 ^ 32) - 1 then - _pack = E'\\333'::bytea - || set_byte(E' '::bytea, 0, _size >> 24) - || set_byte(E' '::bytea, 0, _size >> 16) - || set_byte(E' '::bytea, 0, _size >> 8) - || set_byte(E' '::bytea, 0, _size); - else - raise exception 'String is too long.'; - end if; - - _pack = _pack || _chunk; - - when 'boolean' then - _pack = case _data::text when 'false' then E'\\302'::bytea else E'\\303'::bytea end; - - when 'null' then - _pack = E'\\300'::bytea; - - else - raise exception '% not implemented yet', jsonb_typeof(_data); - end case; - - return _pack; + case jsonb_typeof(_data) + when 'object' then + -- Get count of items in object + select count(jsonb_object_keys) into _size from jsonb_object_keys(_data); + + if _size < 16 then + _pack = set_byte(E' '::bytea, 0, (128::bit(8) | _size::bit(8))::integer); + elsif _size < 2 ^ 16 then + _pack = E'\\336'::bytea + || set_byte(E' '::bytea, 0, _size >> 8) + || set_byte(E' '::bytea, 0, _size); + elsif _size < 2 ^ 32 then + _pack = E'\\337'::bytea + || set_byte(E' '::bytea, 0, _size >> 24) + || set_byte(E' '::bytea, 0, _size >> 16) + || set_byte(E' '::bytea, 0, _size >> 8) + || set_byte(E' '::bytea, 0, _size); + else + raise exception 'Maximum number of keys exceeded.'; + end if; + + -- Process items + for _key in select jsonb_object_keys from jsonb_object_keys(_data) loop + _pack = _pack || public.msgpack_encode(to_jsonb(_key)) || public.msgpack_encode(_data->_key); + end loop; + + when 'array' then + select jsonb_array_length into _size from jsonb_array_length(_data); + + if _size < 16 then + _pack = set_byte(E' '::bytea, 0, (144::bit(8) | _size::bit(8))::integer); + elsif _size < 2 ^ 16 then + _pack = E'\\334'::bytea + || set_byte(E' '::bytea, 0, _size >> 8) + || set_byte(E' '::bytea, 0, _size); + elsif _size < 2 ^ 32 then + _pack = E'\\335'::bytea + || set_byte(E' '::bytea, 0, _size >> 24) + || set_byte(E' '::bytea, 0, _size >> 16) + || set_byte(E' '::bytea, 0, _size >> 8) + || set_byte(E' '::bytea, 0, _size); + else + raise exception 'Maximum number of items exceeded.'; + end if; + + -- Process items + for _item in select value from jsonb_array_elements(_data) loop + _pack = _pack || public.msgpack_encode(_item); + end loop; + + when 'number' then + _numeric = (_data#>>'{}')::numeric; + if _numeric % 1 != 0 then + -- treat all floats as 64-bit floats + _pack = float_to_bytea(_numeric, false); + elsif _numeric > 0 then + -- Integer + if _numeric < 2 ^ 7 then + _pack = set_byte(E' '::bytea, 0, _numeric::integer); + elsif _numeric < 2 ^ 8 then + _pack = E'\\314'::bytea + || set_byte(E' '::bytea, 0, _numeric::integer); + elsif _numeric < 2 ^ 15 then + _pack = E'\\315'::bytea + || set_byte(E' '::bytea, 0, (_numeric::integer >> 8) & 255) + || set_byte(E' '::bytea, 0, _numeric::integer & 255); + elsif _numeric < 2 ^ 31 then + _pack = E'\\316'::bytea + || set_byte(E' '::bytea, 0, (_numeric::integer >> 24) & 255) + || set_byte(E' '::bytea, 0, (_numeric::integer >> 16) & 255) + || set_byte(E' '::bytea, 0, (_numeric::integer >> 8) & 255) + || set_byte(E' '::bytea, 0, _numeric::integer & 255); + elsif _numeric < 2 ^ 63 then + _pack = E'\\317'::bytea + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 56) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 48) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 40) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 32) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 24) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 16) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 8) & 255)::integer) + || set_byte(E' '::bytea, 0, (_numeric::bigint & 255)::integer); + else + raise exception 'Integer out of range.'; + end if; + else + if _numeric >= -2 ^ 5 then + _pack = set_byte(E' '::bytea, 0, _numeric::integer); + elsif _numeric >= -2 ^ 7 then + _pack = E'\\320'::bytea + || set_byte(E' '::bytea, 0, _numeric::integer); + elsif _numeric >= -2 ^ 15 then + _pack = E'\\321'::bytea + || set_byte(E' '::bytea, 0, (_numeric::integer >> 8) & 255) + || set_byte(E' '::bytea, 0, _numeric::integer & 255); + elsif _numeric >= -2 ^ 31 then + _pack = E'\\322'::bytea + || set_byte(E' '::bytea, 0, (_numeric::integer >> 24) & 255) + || set_byte(E' '::bytea, 0, (_numeric::integer >> 16) & 255) + || set_byte(E' '::bytea, 0, (_numeric::integer >> 8) & 255) + || set_byte(E' '::bytea, 0, _numeric::integer & 255); + elsif _numeric >= -2 ^ 63 then + _pack = E'\\323'::bytea + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 56) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 48) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 40) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 32) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 24) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 16) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 8) & 255)::integer) + || set_byte(E' '::bytea, 0, (_numeric::bigint & 255)::integer); + else + raise exception 'Integer out of range.'; + end if; + end if; + + when 'string' then + _chunk = convert_to(_data#>>'{}', 'utf8'); + _size = octet_length(_chunk); + + if _size <= 31 then + _pack = set_byte(E' '::bytea, 0, ((160)::bit(8) | (_size)::bit(8))::integer); + elsif _size <= (2 ^ 8) - 1 then + _pack = E'\\331'::bytea || set_byte(E' '::bytea, 0, _size); + elsif _size <= (2 ^ 16) - 1 then + _pack = E'\\332'::bytea + || set_byte(E' '::bytea, 0, _size >> 8) + || set_byte(E' '::bytea, 0, _size); + elsif _size <= (2 ^ 32) - 1 then + _pack = E'\\333'::bytea + || set_byte(E' '::bytea, 0, _size >> 24) + || set_byte(E' '::bytea, 0, _size >> 16) + || set_byte(E' '::bytea, 0, _size >> 8) + || set_byte(E' '::bytea, 0, _size); + else + raise exception 'String is too long.'; + end if; + + _pack = _pack || _chunk; + + when 'boolean' then + _pack = case _data::text when 'false' then E'\\302'::bytea else E'\\303'::bytea end; + + when 'null' then + _pack = E'\\300'::bytea; + + else + raise exception '% not implemented yet', jsonb_typeof(_data); + end case; + + return _pack; end; $function$ From 5968d176252376643cb253173995e06cf7f03b8b Mon Sep 17 00:00:00 2001 From: Jason Rogers Date: Mon, 9 Mar 2020 12:14:27 -0400 Subject: [PATCH 4/4] refactor: adds helper functions per JSONB type, removes generic helper functions (is_nan, is_negative_zero) --- src/encode.sql | 470 ++++++++++++++++++++++++++----------------------- 1 file changed, 248 insertions(+), 222 deletions(-) diff --git a/src/encode.sql b/src/encode.sql index 299b8ff..bc5c997 100644 --- a/src/encode.sql +++ b/src/encode.sql @@ -1,258 +1,284 @@ -create or replace function is_nan(float8) -returns boolean language sql immutable +create or replace function msgpack_array(_data jsonb) returns bytea language plpgsql as $function$ - select $1 = double precision 'NaN' +declare _size integer; +declare _item jsonb; +declare _pack bytea; +begin + -- Get count of items in object + select jsonb_array_length into _size from jsonb_array_length(_data); + + if _size < 16 then + _pack := set_byte(E' '::bytea, 0, (144::bit(8) | _size::bit(8))::integer); + elsif _size < 2 ^ 16 then + _pack := E'\\334'::bytea + || set_byte(E' '::bytea, 0, _size >> 8) + || set_byte(E' '::bytea, 0, _size); + elsif _size < 2 ^ 32 then + _pack := E'\\335'::bytea + || set_byte(E' '::bytea, 0, _size >> 24) + || set_byte(E' '::bytea, 0, _size >> 16) + || set_byte(E' '::bytea, 0, _size >> 8) + || set_byte(E' '::bytea, 0, _size); + else + raise exception 'Maximum number of items exceeded.'; + end if; + + -- Process items + for _item in select value from jsonb_array_elements(_data) loop + _pack := _pack || public.msgpack_encode(_item); + end loop; + + return _pack; +end; $function$; -create or replace function is_negative_zero(float8) -returns boolean language sql immutable +create or replace function msgpack_boolean(_data boolean) returns bytea language sql as $function$ - select $1::text = '-0' + SELECT case when _data then E'\\302'::bytea else E'\\303'::bytea end $function$; + -- --- float_to_bytea adapted from https://github.com/feross/ieee754 +-- msgpack_float adapted from https://github.com/feross/ieee754 -- -create or replace function float_to_bytea(value float8, isLittleEndian boolean) +create or replace function msgpack_float(value float8, isLittleEndian boolean) returns bytea language plpgsql as $function$ declare - m bigint; - numeric_e bigint; - _pack bytea := E'\\313 '::bytea; - mlen int := 52; - elen int := 64 - mlen - 1; - emax int := (1 << elen) - 1; - ebias int := emax >> 1; - rt numeric := case when mlen = 23 then power(2, -24) - power(2, -77) else 0 end; - i int := case when isLittleEndian then 0 else 7 end; - d int := case when isLittleEndian then 1 else -1 end; - s int := case when value < 0 OR is_negative_zero(value) then 1 else 0 end; - e int; - c float8; + m bigint; + numeric_e bigint; + _pack bytea := E'\\313 '::bytea; + mlen int := 52; + elen int := 64 - mlen - 1; + emax int := (1 << elen) - 1; + ebias int := emax >> 1; + rt numeric := case when mlen = 23 then power(2, -24) - power(2, -77) else 0 end; + i int := case when isLittleEndian then 0 else 7 end; + d int := case when isLittleEndian then 1 else -1 end; + s int := case when value < 0 OR (value::text = '-0') then 1 else 0 end; + e int; + c float8; begin - value := abs(value); + value := abs(value); - if is_nan(value) OR value = 'infinity'::float8 - then - m := case when is_nan(value) then 1 else 0 end; - e := emax; - else - e := floor(ln(value) / ln(2)); - c := power(2, -e); + if (value = double precision 'NaN') OR value = 'infinity'::float8 + then + m := case when (value = double precision 'NaN') then 1 else 0 end; + e := emax; + else + e := floor(ln(value) / ln(2)); + c := power(2, -e); - if (value * c) < 1 then - e := e - 1; - c := c * 2; - end if; + if (value * c) < 1 then + e := e - 1; + c := c * 2; + end if; - if (e + ebias) >= 1 then - value := value + (rt / c); - else - value := value + (rt * power(2, (1 - ebias))); - end if; + if (e + ebias) >= 1 then + value := value + (rt / c); + else + value := value + (rt * power(2, (1 - ebias))); + end if; - if (value * c) >= 2 then - e := e + 1; - c := c / 2; - end if; + if (value * c) >= 2 then + e := e + 1; + c := c / 2; + end if; - if (e + ebias) >= emax then - m = 0; - e = emax; - elsif (e + ebias) >= 1 then - m = (((value * c) - 1) * power(2, mlen))::bigint; - e = e + ebias; - else - m = value * power(2, (eBias - 1)) * power(2, mlen); - e = 0; - end if; - end if; + if (e + ebias) >= emax then + m = 0; + e = emax; + elsif (e + ebias) >= 1 then + m = (((value * c) - 1) * power(2, mlen))::bigint; + e = e + ebias; + else + m = value * power(2, (eBias - 1)) * power(2, mlen); + e = 0; + end if; + end if; - loop - exit when mlen < 8; - _pack := set_byte(_pack, 1 + i, (m::bigint & 255)::int); - i := i + d; - m := m / 256; - mlen := mlen - 8; - end loop; + loop + exit when mlen < 8; + _pack := set_byte(_pack, 1 + i, (m::bigint & 255)::int); + i := i + d; + m := m / 256; + mlen := mlen - 8; + end loop; - numeric_e = (e << mlen)::bigint | trunc(m)::bigint; - elen := elen + mlen; + numeric_e = (e << mlen)::bigint | trunc(m)::bigint; + elen := elen + mlen; - loop - exit when elen <= 0; - _pack := set_byte(_pack, 1 + i, (numeric_e & 255)::int); - i := i + d; - numeric_e := numeric_e / 256; - elen := elen - 8; - end loop; + loop + exit when elen <= 0; + _pack := set_byte(_pack, 1 + i, (numeric_e & 255)::int); + i := i + d; + numeric_e := numeric_e / 256; + elen := elen - 8; + end loop; - _pack := set_byte(_pack, 1 + i - d, get_byte(_pack, 1 + i - d) | trunc(s * 128)::int); - return _pack; + _pack := set_byte(_pack, 1 + i - d, get_byte(_pack, 1 + i - d) | trunc(s * 128)::int); + return _pack; end; $function$; -create or replace function msgpack_encode(_data jsonb) returns bytea language plpgsql +create or replace function msgpack_integer(_numeric numeric) returns bytea language plpgsql +as $function$ +begin + if _numeric > 0 then + if _numeric < 2 ^ 7 then + return set_byte(E' '::bytea, 0, _numeric::integer); + elsif _numeric < 2 ^ 8 then + return E'\\314'::bytea + || set_byte(E' '::bytea, 0, _numeric::integer); + elsif _numeric < 2 ^ 16 then + return E'\\315'::bytea + || set_byte(E' '::bytea, 0, (_numeric::integer >> 8) & 255) + || set_byte(E' '::bytea, 0, _numeric::integer & 255); + elsif _numeric < 2 ^ 32 then + return E'\\316'::bytea + || set_byte(E' '::bytea, 0, (_numeric::integer >> 24) & 255) + || set_byte(E' '::bytea, 0, (_numeric::integer >> 16) & 255) + || set_byte(E' '::bytea, 0, (_numeric::integer >> 8) & 255) + || set_byte(E' '::bytea, 0, _numeric::integer & 255); + elsif _numeric < 2 ^ 64 then + return E'\\317'::bytea + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 56) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 48) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 40) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 32) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 24) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 16) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 8) & 255)::integer) + || set_byte(E' '::bytea, 0, (_numeric::bigint & 255)::integer); + else + raise exception 'Integer out of range.'; + end if; + else + if _numeric >= -2 ^ 5 then + return set_byte(E' '::bytea, 0, _numeric::integer); + elsif _numeric >= -2 ^ 7 then + return E'\\320'::bytea + || set_byte(E' '::bytea, 0, _numeric::integer); + elsif _numeric >= -2 ^ 15 then + return E'\\321'::bytea + || set_byte(E' '::bytea, 0, (_numeric::integer >> 8) & 255) + || set_byte(E' '::bytea, 0, _numeric::integer & 255); + elsif _numeric >= -2 ^ 31 then + return E'\\322'::bytea + || set_byte(E' '::bytea, 0, (_numeric::integer >> 24) & 255) + || set_byte(E' '::bytea, 0, (_numeric::integer >> 16) & 255) + || set_byte(E' '::bytea, 0, (_numeric::integer >> 8) & 255) + || set_byte(E' '::bytea, 0, _numeric::integer & 255); + elsif _numeric >= -2 ^ 63 then + return E'\\323'::bytea + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 56) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 48) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 40) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 32) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 24) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 16) & 255)::integer) + || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 8) & 255)::integer) + || set_byte(E' '::bytea, 0, (_numeric::bigint & 255)::integer); + else + raise exception 'Integer out of range.'; + end if; + end if; +end; +$function$; + +create or replace function msgpack_object(_data jsonb) returns bytea language plpgsql as $function$ declare _size integer; declare _key text; declare _pack bytea; -declare _chunk bytea; -declare _numeric numeric; -declare _item jsonb; begin + -- Get count of items in object + select count(jsonb_object_keys) into _size from jsonb_object_keys(_data); + + if _size < 16 then + _pack := set_byte(E' '::bytea, 0, (128::bit(8) | _size::bit(8))::integer); + elsif _size < 2 ^ 16 then + _pack := E'\\336'::bytea + || set_byte(E' '::bytea, 0, _size >> 8) + || set_byte(E' '::bytea, 0, _size); + elsif _size < 2 ^ 32 then + _pack := E'\\337'::bytea + || set_byte(E' '::bytea, 0, _size >> 24) + || set_byte(E' '::bytea, 0, _size >> 16) + || set_byte(E' '::bytea, 0, _size >> 8) + || set_byte(E' '::bytea, 0, _size); + else + raise exception 'Maximum number of keys exceeded.'; + end if; - case jsonb_typeof(_data) - when 'object' then - -- Get count of items in object - select count(jsonb_object_keys) into _size from jsonb_object_keys(_data); + -- Process items + for _key in select jsonb_object_keys from jsonb_object_keys(_data) loop + _pack := _pack || public.msgpack_encode(to_jsonb(_key)) || public.msgpack_encode(_data->_key); + end loop; + + return _pack; +end; +$function$; - if _size < 16 then - _pack = set_byte(E' '::bytea, 0, (128::bit(8) | _size::bit(8))::integer); - elsif _size < 2 ^ 16 then - _pack = E'\\336'::bytea - || set_byte(E' '::bytea, 0, _size >> 8) - || set_byte(E' '::bytea, 0, _size); - elsif _size < 2 ^ 32 then - _pack = E'\\337'::bytea - || set_byte(E' '::bytea, 0, _size >> 24) - || set_byte(E' '::bytea, 0, _size >> 16) - || set_byte(E' '::bytea, 0, _size >> 8) - || set_byte(E' '::bytea, 0, _size); - else - raise exception 'Maximum number of keys exceeded.'; - end if; - - -- Process items - for _key in select jsonb_object_keys from jsonb_object_keys(_data) loop - _pack = _pack || public.msgpack_encode(to_jsonb(_key)) || public.msgpack_encode(_data->_key); - end loop; - - when 'array' then - select jsonb_array_length into _size from jsonb_array_length(_data); +create or replace function msgpack_text(_data jsonb) returns bytea language plpgsql +as $function$ +declare _size integer; +declare _pack bytea; +declare _chunk bytea; +begin + _chunk = convert_to(_data#>>'{}', 'utf8'); + _size = octet_length(_chunk); + + if _size <= 31 then + _pack := set_byte(E' '::bytea, 0, ((160)::bit(8) | (_size)::bit(8))::integer); + elsif _size <= (2 ^ 8) - 1 then + _pack := E'\\331'::bytea || set_byte(E' '::bytea, 0, _size); + elsif _size <= (2 ^ 16) - 1 then + _pack := E'\\332'::bytea + || set_byte(E' '::bytea, 0, _size >> 8) + || set_byte(E' '::bytea, 0, _size); + elsif _size <= (2 ^ 32) - 1 then + _pack := E'\\333'::bytea + || set_byte(E' '::bytea, 0, _size >> 24) + || set_byte(E' '::bytea, 0, _size >> 16) + || set_byte(E' '::bytea, 0, _size >> 8) + || set_byte(E' '::bytea, 0, _size); + else + raise exception 'String is too long.'; + end if; + + return _pack || _chunk; +end; +$function$; - if _size < 16 then - _pack = set_byte(E' '::bytea, 0, (144::bit(8) | _size::bit(8))::integer); - elsif _size < 2 ^ 16 then - _pack = E'\\334'::bytea - || set_byte(E' '::bytea, 0, _size >> 8) - || set_byte(E' '::bytea, 0, _size); - elsif _size < 2 ^ 32 then - _pack = E'\\335'::bytea - || set_byte(E' '::bytea, 0, _size >> 24) - || set_byte(E' '::bytea, 0, _size >> 16) - || set_byte(E' '::bytea, 0, _size >> 8) - || set_byte(E' '::bytea, 0, _size); - else - raise exception 'Maximum number of items exceeded.'; - end if; - - -- Process items - for _item in select value from jsonb_array_elements(_data) loop - _pack = _pack || public.msgpack_encode(_item); - end loop; - - when 'number' then - _numeric = (_data#>>'{}')::numeric; - if _numeric % 1 != 0 then - -- treat all floats as 64-bit floats - _pack = float_to_bytea(_numeric, false); - elsif _numeric > 0 then - -- Integer - if _numeric < 2 ^ 7 then - _pack = set_byte(E' '::bytea, 0, _numeric::integer); - elsif _numeric < 2 ^ 8 then - _pack = E'\\314'::bytea - || set_byte(E' '::bytea, 0, _numeric::integer); - elsif _numeric < 2 ^ 15 then - _pack = E'\\315'::bytea - || set_byte(E' '::bytea, 0, (_numeric::integer >> 8) & 255) - || set_byte(E' '::bytea, 0, _numeric::integer & 255); - elsif _numeric < 2 ^ 31 then - _pack = E'\\316'::bytea - || set_byte(E' '::bytea, 0, (_numeric::integer >> 24) & 255) - || set_byte(E' '::bytea, 0, (_numeric::integer >> 16) & 255) - || set_byte(E' '::bytea, 0, (_numeric::integer >> 8) & 255) - || set_byte(E' '::bytea, 0, _numeric::integer & 255); - elsif _numeric < 2 ^ 63 then - _pack = E'\\317'::bytea - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 56) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 48) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 40) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 32) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 24) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 16) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 8) & 255)::integer) - || set_byte(E' '::bytea, 0, (_numeric::bigint & 255)::integer); - else - raise exception 'Integer out of range.'; - end if; - else - if _numeric >= -2 ^ 5 then - _pack = set_byte(E' '::bytea, 0, _numeric::integer); - elsif _numeric >= -2 ^ 7 then - _pack = E'\\320'::bytea - || set_byte(E' '::bytea, 0, _numeric::integer); - elsif _numeric >= -2 ^ 15 then - _pack = E'\\321'::bytea - || set_byte(E' '::bytea, 0, (_numeric::integer >> 8) & 255) - || set_byte(E' '::bytea, 0, _numeric::integer & 255); - elsif _numeric >= -2 ^ 31 then - _pack = E'\\322'::bytea - || set_byte(E' '::bytea, 0, (_numeric::integer >> 24) & 255) - || set_byte(E' '::bytea, 0, (_numeric::integer >> 16) & 255) - || set_byte(E' '::bytea, 0, (_numeric::integer >> 8) & 255) - || set_byte(E' '::bytea, 0, _numeric::integer & 255); - elsif _numeric >= -2 ^ 63 then - _pack = E'\\323'::bytea - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 56) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 48) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 40) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 32) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 24) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 16) & 255)::integer) - || set_byte(E' '::bytea, 0, ((_numeric::bigint >> 8) & 255)::integer) - || set_byte(E' '::bytea, 0, (_numeric::bigint & 255)::integer); - else - raise exception 'Integer out of range.'; - end if; - end if; - - when 'string' then - _chunk = convert_to(_data#>>'{}', 'utf8'); - _size = octet_length(_chunk); - - if _size <= 31 then - _pack = set_byte(E' '::bytea, 0, ((160)::bit(8) | (_size)::bit(8))::integer); - elsif _size <= (2 ^ 8) - 1 then - _pack = E'\\331'::bytea || set_byte(E' '::bytea, 0, _size); - elsif _size <= (2 ^ 16) - 1 then - _pack = E'\\332'::bytea - || set_byte(E' '::bytea, 0, _size >> 8) - || set_byte(E' '::bytea, 0, _size); - elsif _size <= (2 ^ 32) - 1 then - _pack = E'\\333'::bytea - || set_byte(E' '::bytea, 0, _size >> 24) - || set_byte(E' '::bytea, 0, _size >> 16) - || set_byte(E' '::bytea, 0, _size >> 8) - || set_byte(E' '::bytea, 0, _size); - else - raise exception 'String is too long.'; - end if; - - _pack = _pack || _chunk; - - when 'boolean' then - _pack = case _data::text when 'false' then E'\\302'::bytea else E'\\303'::bytea end; +create or replace function msgpack_encode(_data jsonb) returns bytea language plpgsql +as $function$ +declare _pack bytea; +begin - when 'null' then - _pack = E'\\300'::bytea; - - else - raise exception '% not implemented yet', jsonb_typeof(_data); - end case; + case jsonb_typeof(_data) + when 'object' then + _pack := msgpack_object(_data); + when 'array' then + _pack := msgpack_array(_data); + when 'number' then + _numeric = (_data#>>'{}')::numeric; + if _numeric % 1 != 0 then + -- treat all floats as 64-bit floats + _pack := msgpack_float(_numeric, false); + else + _pack := msgpack_integer(_numeric); + end if; + when 'string' then + _pack := msgpack_text(_data); + when 'boolean' then + _pack := msgpack_boolean(_data::boolean); + when 'null' then + _pack := E'\\300'::bytea; + else + raise exception '% not implemented yet', jsonb_typeof(_data); + end case; - return _pack; + return _pack; end; $function$