Сначала для удобства определим функцию для одной цифры.
=> CREATE FUNCTION digit(d text) RETURNS integer AS $$ SELECT ascii(d) - CASE WHEN d BETWEEN '0' AND '9' THEN ascii('0') ELSE ascii('A') - 10 END; $$ IMMUTABLE LANGUAGE sql;
CREATE FUNCTION
Теперь основная функция:
=> CREATE FUNCTION convert(hex text) RETURNS integer AS $$ WITH s(d,ord) AS ( SELECT * FROM regexp_split_to_table(reverse(upper(hex)),'') WITH ORDINALITY ) SELECT sum(digit(d) * 16^(ord-1))::integer FROM s; $$ IMMUTABLE LANGUAGE sql;
CREATE FUNCTION
=> SELECT convert('0FE'), convert('0FF'), convert('100');
convert | convert | convert ---------+---------+--------- 254 | 255 | 256 (1 row)
Предполагаем, что основание системы счисления от 2 до 36, то есть число записывается цифрами от 0 до 9, либо буквами от A до Z. В этом случае изменения минимальные.
=> DROP FUNCTION convert(text);
DROP FUNCTION
=> CREATE FUNCTION convert(num text, radix integer DEFAULT 16) RETURNS integer AS $$ WITH s(d,ord) AS ( SELECT * FROM regexp_split_to_table(reverse(upper(num)),'') WITH ORDINALITY ) SELECT sum(digit(d) * radix^(ord-1))::integer FROM s; $$ IMMUTABLE LANGUAGE sql;
CREATE FUNCTION
=> SELECT convert('0110',2), convert('0FF'), convert('Z',36);
convert | convert | convert ---------+---------+--------- 6 | 255 | 35 (1 row)
Сначала напишем вспомогательные функции, переводящие строку в числовое представление и обратно.
Первая очень похожа на функцию из предыдущего задания:
=> CREATE FUNCTION text2num(s text) RETURNS integer AS $$ WITH s(d,ord) AS ( SELECT * FROM regexp_split_to_table(reverse(s),'') WITH ORDINALITY ) SELECT sum( (ascii(d)-ascii('A')) * 26^(ord-1))::integer FROM s; $$ IMMUTABLE LANGUAGE sql;
CREATE FUNCTION
Обратную функцию напишем с помощью рекурсивного запроса:
=> CREATE FUNCTION num2text(n integer, digits integer) RETURNS text AS $$ WITH RECURSIVE r(num,txt, level) AS ( SELECT n/26, chr( n%26 + ascii('A') )::text, 1 UNION ALL SELECT r.num/26, chr( r.num%26 + ascii('A') ) || r.txt, r.level+1 FROM r WHERE r.level < digits ) SELECT r.txt FROM r WHERE r.level = digits; $$ IMMUTABLE LANGUAGE sql;
CREATE FUNCTION
=> SELECT num2text( text2num('ABC'), length('ABC') );
num2text ---------- ABC (1 row)
Теперь функцию generate_series для строк можно переписать, используя generate_series для целых чисел.
=> CREATE FUNCTION generate_series(start text, stop text) RETURNS SETOF text AS $$ SELECT num2text( g.n, length(start) ) FROM generate_series(text2num(start), text2num(stop)) g(n); $$ IMMUTABLE LANGUAGE sql;
CREATE FUNCTION
=> SELECT generate_series('AZ','BC');
generate_series ----------------- AZ BA BB BC (4 rows)