Tengo un campo char cuyo valor predeterminado debería ser un ksuid. ¿Cómo generar un ksuid en postgres?
Sugiero que su mejor opción es crear un DOMINIO de usuario. Luego defina una función para generar su ksuid. Luego modifique el dominio para usar esta función como predeterminada. Cuando sea necesario, defina su columna como ese tipo de DOMINIO.
-- setup domain and the generating function create domain ksuid character varying(27); create or replace function generate_ksuid() returns ksuid language sql as $$ select substring( replace(to_char(clock_timestamp(),'yyyymmddhh24missus') || (to_char(random()*1e9,'000000000') ),' ',''),1,27)::ksuid; $$; alter domain ksuid set default generate_ksuid();Vea el ejemplo completo, incluido el uso, aquí . Por supuesto, la función generar_ksuid deberá adaptarse a sus necesidades. El ejemplo solo se basa en clock_timestamp y un número aleatorio.
Esta función genera KSUID en PostgreSQL. Utiliza tipos de datos numeric para convertir el tiempo y la carga útil a base62.
Crea una carga útil pseudoaleatoria usando la MD5() nativa. Si desea utilizar pgcrypto , consulte el comentario de @ssz. ¡Gracias @ssz!
Los KSUID generados por la función cumplen con la implementación de referencia .
/** * Returns a Segment's KSUID. * * Reference implementation: https://github.com/segmentio/ksuid * Also read: https://segment.com/blog/a-brief-history-of-the-uuid/ */ create or replace function fn_ksuid() returns text as $$ declare v_time timestamp with time zone := null; v_seconds numeric := null; v_payload bytea := null; v_numeric numeric := null; v_base62 text := ''; v_epoch numeric = 1400000000; -- 2014-05-13T16:53:20Z v_alphabet char array[62] := array[ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']; i integer := 0; begin -- Get the current time v_time := clock_timestamp(); -- Extract seconds from the current time and apply epoch v_seconds := EXTRACT(EPOCH FROM v_time) - v_epoch; -- Generate a numeric value from the seconds v_numeric := v_seconds * pow(2::numeric, 128); -- Generate a pseudo-random payload -- v_payload := gen_random_bytes(16); -- to be used with `pgcrypto` v_payload := decode(md5(v_time::text || random()::text || random()::text), 'hex'); -- Add the payload to the numeric value while i < 16 loop i := i + 1; v_numeric := v_numeric + (get_byte(v_payload, i - 1) * pow(2::numeric, (16 - i) * 8)); end loop; -- Encode the numeric value to base62 while v_numeric <> 0 loop v_base62 := v_base62 || v_alphabet[mod(v_numeric, 62) + 1]; v_numeric := div(v_numeric, 62); end loop; v_base62 := reverse(v_base62); v_base62 := lpad(v_base62, 27, '0'); return v_base62; end $$ language plpgsql;Enlace a GitHub Gist .
Me gustaría sugerir un ligero cambio en la solución de fabiolimace. En cambio:
v_payload := decode(md5(v_time::text || random()::text || random()::text), 'hex'); use una función más fuerte gen_random_bytes() :
v_payload := gen_random_bytes(16); Requiere que la extensión pgcrypto esté habilitada:
CREATE EXTENSION pgcrypto;Actualización importante
En algunos casos raros, esta función devuelve NULL . Se produce un error en la línea:
v_base62 := v_base62 || v_alphabet[mod(v_numeric, 62) + 1];Ejemplo:
mod(v_numeric, 62) 'Let`s say this function returns 61.7977600000000000000000' mod(v_numeric, 62) + 1 '62.7977600000000000000000' v_alphabet[62.7977600000000000000000 => 63] => NULL '62.7977600000000000000000 rounds to 63, there is no element with index 63' v_base62 || NULL 'v_base62 becomes NULL because || operator always returns NULL if at least one of the operands is null'Cómo reproducir:
SELECT COUNT(*) FROM (SELECT fn_ksuid() AS id FROM GENERATE_SERIES(1, 1000)) q WHERE q.id IS NULL;Solución:
v_base62 := v_base62 || v_alphabet[floor(mod(v_numeric, 62)) + 1]; Siempre convierta el valor mod a la parte entera usando la función floor() .
En la vida real, el error ocurre cuando se inserta una gran cantidad de filas al mismo tiempo.