mirror of
https://github.com/postgres/postgres.git
synced 2026-10-23 13:29:09 +03:00
Use hex_decode_safe() to speed up UUID input.
Previously, string_to_uuid() decoded one byte at a time, calling
isxdigit() twice and strtoul() once for every pair of hexadecimal
digits. Commit ec8719ccbf made hex_decode_safe() decode a run of
hexadecimal digits in bulk, so add a fast path for the two common
shapes: 32 contiguous hexadecimal digits, and the canonical
8x-4x-4x-4x-12x form, each optionally wrapped in braces.
Anything else, and any decoding error, falls back to the
byte-at-a-time parser, so the accepted grammar and the error messages
don't change. hex_decode_safe() also skips whitespace, which the UUID
grammar forbids, so we treat a short decode as an error too. Rejecting
an input that has a fast-path shape therefore costs a second
parse. But this only happens for syntax-error cases, so it's not a
problem in practice.
Reviewed-by: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Reviewed-by: Haibo Yan <tristan.yim@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Reviewed-by: John Naylor <johncnaylorls@gmail.com>
Discussion: https://postgr.es/m/CAD21AoCqeR4UQU77Q_yOMNNzJ7AVeiO5QZT+4HnzPm4Wm-e02Q@mail.gmail.com
This commit is contained in:
@@ -19,7 +19,9 @@
|
||||
#include "common/hashfn.h"
|
||||
#include "lib/hyperloglog.h"
|
||||
#include "libpq/pqformat.h"
|
||||
#include "nodes/miscnodes.h"
|
||||
#include "port/pg_bswap.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/fmgrprotos.h"
|
||||
#include "utils/guc.h"
|
||||
#include "utils/skipsupport.h"
|
||||
@@ -139,13 +141,13 @@ uuid_out(PG_FUNCTION_ARGS)
|
||||
}
|
||||
|
||||
/*
|
||||
* We allow UUIDs as a series of 32 hexadecimal digits with an optional dash
|
||||
* after each group of 4 hexadecimal digits, and optionally surrounded by {}.
|
||||
* (The canonical format 8x-4x-4x-4x-12x, where "nx" means n hexadecimal
|
||||
* digits, is the only one used for output.)
|
||||
* Reference implementation of the UUID grammar, parsing one byte at a time.
|
||||
* string_to_uuid() recognizes the common shapes more cheaply and defers to
|
||||
* this function for everything else, so this is also the only place that
|
||||
* reports a syntax error.
|
||||
*/
|
||||
static void
|
||||
string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
|
||||
string_to_uuid_scalar(const char *source, pg_uuid_t *uuid, Node *escontext)
|
||||
{
|
||||
const char *src = source;
|
||||
bool braces = false;
|
||||
@@ -194,6 +196,95 @@ syntax_error:
|
||||
"uuid", source)));
|
||||
}
|
||||
|
||||
/*
|
||||
* We allow UUIDs as a series of 32 hexadecimal digits with an optional dash
|
||||
* after each group of 4 hexadecimal digits, and optionally surrounded by {}.
|
||||
* (The canonical format 8x-4x-4x-4x-12x, where "nx" means n hexadecimal
|
||||
* digits, is the only one used for output.)
|
||||
*
|
||||
* The two common shapes -- a bare string of 32 hexadecimal digits and the
|
||||
* canonical form, each optionally wrapped in braces -- are compacted into 32
|
||||
* contiguous hex digits and decoded with hex_decode_safe(), which is much
|
||||
* faster than the byte-at-a-time loop. Any other shape, or any decoding
|
||||
* error, is handed off to string_to_uuid_scalar() so that the accepted
|
||||
* grammar and the error messages are unchanged.
|
||||
*/
|
||||
static void
|
||||
string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
|
||||
{
|
||||
const char *body = source;
|
||||
const char *hexsrc = NULL;
|
||||
char hexbuf[32];
|
||||
uint64 written;
|
||||
size_t len;
|
||||
ErrorSaveContext private_escontext = {T_ErrorSaveContext};
|
||||
|
||||
/*
|
||||
* Measure the input only far enough to classify its shape. The bound must
|
||||
* exceed the longest shape handled here, the braced canonical form at 38
|
||||
* characters: strnlen() returns the bound for anything at least that
|
||||
* long, so stopping at an accepted length would accept a longer string
|
||||
* that merely starts with a valid UUID.
|
||||
*/
|
||||
len = strnlen(source, 64);
|
||||
|
||||
/* Strip one optional surrounding brace pair */
|
||||
if (len >= 2 && source[0] == '{' && source[len - 1] == '}')
|
||||
{
|
||||
body = source + 1;
|
||||
len -= 2;
|
||||
}
|
||||
|
||||
if (len == 32)
|
||||
{
|
||||
/*
|
||||
* Body is already 32 contiguous hex digits -- decode straight from
|
||||
* the input. hex_decode_safe() reads exactly body[0..31], so it never
|
||||
* touches the trailing NUL or '}'.
|
||||
*/
|
||||
hexsrc = body;
|
||||
}
|
||||
else if (len == 36 && body[8] == '-' && body[13] == '-' &&
|
||||
body[18] == '-' && body[23] == '-')
|
||||
{
|
||||
/*
|
||||
* Canonical 8x-4x-4x-4x-12x form; compact them into hexbuf with
|
||||
* fixed-offset copies, dropping the dashes.
|
||||
*/
|
||||
memcpy(&hexbuf[0], &body[0], 8);
|
||||
memcpy(&hexbuf[8], &body[9], 4);
|
||||
memcpy(&hexbuf[12], &body[14], 4);
|
||||
memcpy(&hexbuf[16], &body[19], 4);
|
||||
memcpy(&hexbuf[20], &body[24], 12);
|
||||
hexsrc = hexbuf;
|
||||
}
|
||||
|
||||
if (hexsrc == NULL)
|
||||
{
|
||||
/* Uncommon shape; let the general parse handle it */
|
||||
string_to_uuid_scalar(source, uuid, escontext);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* The shape matched, so the decode is expected to succeed. Any error is
|
||||
* routed into a private context and discarded, leaving
|
||||
* string_to_uuid_scalar() to parse the input again and report the syntax
|
||||
* error, so that the message does not depend on which path rejected the
|
||||
* input.
|
||||
*/
|
||||
written = hex_decode_safe(hexsrc, 32, (char *) uuid->data,
|
||||
(Node *) &private_escontext);
|
||||
|
||||
/*
|
||||
* A short result must be rejected as well as an error: hex_decode_safe()
|
||||
* skips whitespace, so it can succeed yet write fewer than UUID_LEN
|
||||
* bytes, whereas the UUID grammar forbids whitespace.
|
||||
*/
|
||||
if (private_escontext.error_occurred || written != UUID_LEN)
|
||||
string_to_uuid_scalar(source, uuid, escontext);
|
||||
}
|
||||
|
||||
Datum
|
||||
uuid_recv(PG_FUNCTION_ARGS)
|
||||
{
|
||||
|
||||
@@ -375,5 +375,79 @@ SELECT v = v::bytea::uuid as matched FROM gen_random_uuid() v;
|
||||
t
|
||||
(1 row)
|
||||
|
||||
-- Test the UUID shapes for which the parser uses the fast path.
|
||||
SELECT '5b35380a-7143-4912-9b55-f322699c6770'::uuid;
|
||||
uuid
|
||||
--------------------------------------
|
||||
5b35380a-7143-4912-9b55-f322699c6770
|
||||
(1 row)
|
||||
|
||||
SELECT '{5b35380a-7143-4912-9b55-f322699c6770}'::uuid;
|
||||
uuid
|
||||
--------------------------------------
|
||||
5b35380a-7143-4912-9b55-f322699c6770
|
||||
(1 row)
|
||||
|
||||
SELECT '5b35380a714349129b55f322699c6770'::uuid;
|
||||
uuid
|
||||
--------------------------------------
|
||||
5b35380a-7143-4912-9b55-f322699c6770
|
||||
(1 row)
|
||||
|
||||
SELECT '{5b35380a714349129b55f322699c6770}'::uuid;
|
||||
uuid
|
||||
--------------------------------------
|
||||
5b35380a-7143-4912-9b55-f322699c6770
|
||||
(1 row)
|
||||
|
||||
-- Test that the fast path correctly rejects invalid UUID strings.
|
||||
SELECT '5b35380a714349129b55f32 99c6770'::uuid;
|
||||
ERROR: invalid input syntax for type uuid: "5b35380a714349129b55f32 99c6770"
|
||||
LINE 1: SELECT '5b35380a714349129b55f32 99c6770'::uuid;
|
||||
^
|
||||
SELECT '5b35380a-7143-4912-9b55-f322699c67 '::uuid;
|
||||
ERROR: invalid input syntax for type uuid: "5b35380a-7143-4912-9b55-f322699c67 "
|
||||
LINE 1: SELECT '5b35380a-7143-4912-9b55-f322699c67 '::uuid;
|
||||
^
|
||||
SELECT ' 35380a-7143-4912-9b55-f322699c6770'::uuid;
|
||||
ERROR: invalid input syntax for type uuid: " 35380a-7143-4912-9b55-f322699c6770"
|
||||
LINE 1: SELECT ' 35380a-7143-4912-9b55-f322699c6770'::uuid;
|
||||
^
|
||||
SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
|
||||
ERROR: invalid input syntax for type uuid: "AZ35380a-7143-4912-9b55-f322699c6770"
|
||||
LINE 1: SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
|
||||
^
|
||||
SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
|
||||
ERROR: invalid input syntax for type uuid: "{AZ35380a-7143-4912-9b55-f322699c6770}"
|
||||
LINE 1: SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
|
||||
^
|
||||
SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
|
||||
ERROR: invalid input syntax for type uuid: "{AZ35380a714349129b55f322699c6770}"
|
||||
LINE 1: SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
|
||||
^
|
||||
SELECT '{AZ35380a714349129b55f322699c67 }'::uuid;
|
||||
ERROR: invalid input syntax for type uuid: "{AZ35380a714349129b55f322699c67 }"
|
||||
LINE 1: SELECT '{AZ35380a714349129b55f322699c67 }'::uuid;
|
||||
^
|
||||
-- The parser only measures the input far enough to classify its shape. If it
|
||||
-- stopped measuring at one of the accepted lengths, a longer string that
|
||||
-- merely starts with a valid UUID would look like that UUID and be accepted
|
||||
-- with the rest silently ignored, so check that trailing data is rejected.
|
||||
SELECT '5b35380a714349129b55f322699c6770TRAILING'::uuid;
|
||||
ERROR: invalid input syntax for type uuid: "5b35380a714349129b55f322699c6770TRAILING"
|
||||
LINE 1: SELECT '5b35380a714349129b55f322699c6770TRAILING'::uuid;
|
||||
^
|
||||
SELECT '{5b35380a714349129b55f322699c6770}TRAILING'::uuid;
|
||||
ERROR: invalid input syntax for type uuid: "{5b35380a714349129b55f322699c6770}TRAILING"
|
||||
LINE 1: SELECT '{5b35380a714349129b55f322699c6770}TRAILING'::uuid;
|
||||
^
|
||||
SELECT '5b35380a-7143-4912-9b55-f322699c6770TRAILING'::uuid;
|
||||
ERROR: invalid input syntax for type uuid: "5b35380a-7143-4912-9b55-f322699c6770TRAILING"
|
||||
LINE 1: SELECT '5b35380a-7143-4912-9b55-f322699c6770TRAILING'::uuid;
|
||||
^
|
||||
SELECT '{5b35380a-7143-4912-9b55-f322699c6770}TRAILING'::uuid;
|
||||
ERROR: invalid input syntax for type uuid: "{5b35380a-7143-4912-9b55-f322699c6770}TRAILING"
|
||||
LINE 1: SELECT '{5b35380a-7143-4912-9b55-f322699c6770}TRAILING'::uui...
|
||||
^
|
||||
-- clean up
|
||||
DROP TABLE guid1, guid2, guid3 CASCADE;
|
||||
|
||||
@@ -178,5 +178,29 @@ SELECT '\x019a2f859ced7225b99d9c55044a2563'::bytea::uuid;
|
||||
SELECT '\x1234567890abcdef'::bytea::uuid; -- error
|
||||
SELECT v = v::bytea::uuid as matched FROM gen_random_uuid() v;
|
||||
|
||||
-- Test the UUID shapes for which the parser uses the fast path.
|
||||
SELECT '5b35380a-7143-4912-9b55-f322699c6770'::uuid;
|
||||
SELECT '{5b35380a-7143-4912-9b55-f322699c6770}'::uuid;
|
||||
SELECT '5b35380a714349129b55f322699c6770'::uuid;
|
||||
SELECT '{5b35380a714349129b55f322699c6770}'::uuid;
|
||||
|
||||
-- Test that the fast path correctly rejects invalid UUID strings.
|
||||
SELECT '5b35380a714349129b55f32 99c6770'::uuid;
|
||||
SELECT '5b35380a-7143-4912-9b55-f322699c67 '::uuid;
|
||||
SELECT ' 35380a-7143-4912-9b55-f322699c6770'::uuid;
|
||||
SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
|
||||
SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
|
||||
SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
|
||||
SELECT '{AZ35380a714349129b55f322699c67 }'::uuid;
|
||||
|
||||
-- The parser only measures the input far enough to classify its shape. If it
|
||||
-- stopped measuring at one of the accepted lengths, a longer string that
|
||||
-- merely starts with a valid UUID would look like that UUID and be accepted
|
||||
-- with the rest silently ignored, so check that trailing data is rejected.
|
||||
SELECT '5b35380a714349129b55f322699c6770TRAILING'::uuid;
|
||||
SELECT '{5b35380a714349129b55f322699c6770}TRAILING'::uuid;
|
||||
SELECT '5b35380a-7143-4912-9b55-f322699c6770TRAILING'::uuid;
|
||||
SELECT '{5b35380a-7143-4912-9b55-f322699c6770}TRAILING'::uuid;
|
||||
|
||||
-- clean up
|
||||
DROP TABLE guid1, guid2, guid3 CASCADE;
|
||||
|
||||
Reference in New Issue
Block a user