PostgreSQL 中的 Base58 编码器函数

Base58 Encoder function in PostgreSQL

我为 Base58 Encoder in a Github Gist 找到了这个 MySQL 函数。

DELIMITER $$

CREATE FUNCTION base58_encode (num int) RETURNS varchar(255)
  DETERMINISTIC

BEGIN
  DECLARE alphabet varchar(255);
  DECLARE base_count int DEFAULT 0;
  DECLARE encoded varchar(255);
  DECLARE divisor DECIMAL(10,4);
  DECLARE mode int DEFAULT 0;

  SET alphabet = '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ';
  SET base_count = CHAR_LENGTH(alphabet);
  SET encoded = "";

  WHILE num  >= base_count DO
    SET divisor = num / base_count;
    SET mode = (num - (base_count* TRUNCATE(divisor,0)));
    SET encoded = CONCAT(SUBSTRING(alphabet FROM mode+1 FOR 1), encoded);
    SET num = TRUNCATE(divisor,0);
  END WHILE;

  SET encoded = CONCAT(SUBSTRING(alphabet FROM num+1 FOR 1), encoded);

  RETURN (encoded);

END

我是 PostgreSQL 的新手,很难将上述函数转换为 PostgreSQL 函数。

上述 SQL 片段的等价 PostgreSQL 函数如何用于 Base58 编码器?

我想出的 PostgreSQL 中的等效函数如下。

CREATE FUNCTION base58_encode(num INT)
  RETURNS VARCHAR(255) AS $encoded$

DECLARE
  alphabet   VARCHAR(255);
  base_count INT DEFAULT 0;
  encoded    VARCHAR(255);
  divisor    DECIMAL(10, 4);
  mod        INT DEFAULT 0;

BEGIN
  alphabet := '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ';
  base_count := char_length(alphabet);
  encoded := '';

  WHILE num >= base_count LOOP
    divisor := num / base_count;
    mod := (num - (base_count * trunc(divisor, 0)));
    encoded := concat(substring(alphabet FROM mod + 1 FOR 1), encoded);
    num := trunc(divisor, 0);
  END LOOP;

  encoded = concat(substring(alphabet FROM num + 1 FOR 1), encoded);

  RETURN (encoded);

END; $encoded$
LANGUAGE PLPGSQL;

Postgres 9.x

CREATE OR REPLACE FUNCTION base58_encode (num bigint)
RETURNS text AS
$body$
declare
  --alphabet text = '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ';
  alphabet text[] = array[
    '1','2','3','4','5','6','7','8','9', 
    'a','b','c','d','e','f','g','h','i','j','k','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
    'A','B','C','D','E','F','G','H','J','K','L','M','N','P','Q','R','S','T','U','V','W','X','Y','Z'
  ];
  cnt integer = 58;
  dst text = '';
  mod integer;
begin
  while (num >= cnt) loop
    num = num / cnt;
    mod = num % cnt + 1;
    dst = alphabet[mod] || dst;
  end loop;

  return alphabet[num] || dst;
end;
$body$
LANGUAGE 'plpgsql'
IMMUTABLE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 100;

为了完整起见,这里有一个快速而肮脏的反 base58_decode() 函数:

CREATE OR REPLACE FUNCTION base58_decode(str VARCHAR(255))
  RETURNS BIGINT AS $$
DECLARE
  alphabet VARCHAR(255);
  c CHAR(1);
  p INT;
  v BIGINT;
BEGIN
  alphabet := '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ';
  v := 0;
  FOR i IN 1..char_length(str) LOOP
    c := substring(str FROM i FOR 1);
    -- This is probably wildly inefficient, but we're just using this function for diagnostics...
    p := position(c IN alphabet);
    IF p = 0 THEN
      RAISE 'Illegal base58 character ''%'' in ''%''', c, str;
    END IF;
    v := (v * 58) + (p - 1);
  END LOOP;
  RETURN v;
END;$$
LANGUAGE PLPGSQL;

为了继续完整性,如果您需要解码为 UUID (Int16),我会根据 @mike-blackwell 的答案进行解码。 Gist

我希望得到一些反馈和改进。我不得不想象从 128 位数字到 UUID

有更好的方法

代码:

CREATE OR REPLACE FUNCTION base58_decode(encoded_id VARCHAR(22))
    RETURNS UUID AS $$
DECLARE
    -- Bitcoin base58 alphabet
    alphabet CHAR(58) := '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
    c CHAR(1) := null;
    p INT := null;
    raw_num NUMERIC := 0;
    uuid_str VARCHAR(32);
BEGIN
    /*
    Parses a UUID encoded with the Bitcoin base58 standard
    Use sparingly, any application connecting to the database should handle decoding the ID itself
    */

    -- Decode id to numeric
    FOR i IN 1..CHAR_LENGTH(encoded_id) LOOP
        c = SUBSTRING(encoded_id FROM i FOR 1);
        p = POSITION(c IN alphabet);
        raw_num = (raw_num * 58) + (p - 1);
    END LOOP;

    -- Parse NUMERIC into bytes
    -- There must be a better way to go from a NUMERIC -> UUID
    uuid_str := '';
    FOR i IN 0..31 LOOP
        uuid_str = CONCAT(uuid_str, TO_HEX(MOD(raw_num, 16)::INT));
        raw_num = DIV(raw_num, 16);
    END LOOP;

    return REVERSE(uuid_str)::UUID;
END;$$
LANGUAGE PLPGSQL;