First commit

This commit is contained in:
Peter Eisentraut 2015-02-17 23:23:18 -05:00
commit 67a513742a
10 changed files with 805 additions and 0 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.out -whitespace

25
Makefile Normal file
View File

@ -0,0 +1,25 @@
PG_CONFIG = pg_config
PKG_CONFIG = pkg-config
extension_version = 0
EXTENSION = pguri
MODULE_big = pguri
OBJS = pguri.o
DATA_built = pguri--$(extension_version).sql
ifeq (no,$(shell $(PKG_CONFIG) liburiparser || echo no))
$(warning liburiparser not registed with pkg-config, build might fail)
endif
PG_CPPFLAGS += $(shell $(PKG_CONFIG) --cflags-only-I liburiparser)
SHLIB_LINK += $(shell $(PKG_CONFIG) --libs liburiparser)
REGRESS = init test
REGRESS_OPTS = --inputdir=test
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
pguri--$(extension_version).sql: pguri.sql
cat $^ >$@

3
README.md Normal file
View File

@ -0,0 +1,3 @@
`uri` type for PostgreSQL
https://twitter.com/pvh/status/567395527357001728

365
pguri.c Normal file
View File

@ -0,0 +1,365 @@
#include <postgres.h>
#include <catalog/pg_type.h>
#include <fmgr.h>
#include <utils/array.h>
#include <utils/builtins.h>
#include <utils/inet.h>
#include <uriparser/Uri.h>
PG_MODULE_MAGIC;
typedef struct varlena uritype;
#define DatumGetUriP(X) ((uritype *) PG_DETOAST_DATUM(X))
#define UriPGetDatum(X) PointerGetDatum(X)
#define PG_GETARG_URI_P(n) DatumGetUriP(PG_GETARG_DATUM(n))
#define PG_RETURN_URI_P(x) PG_RETURN_POINTER(x)
static void
parse_uri(const char *s, UriUriA *urip)
{
UriParserStateA state;
state.uri = urip;
uriParseUriA(&state, s);
switch (state.errorCode)
{
case URI_SUCCESS:
return;
case URI_ERROR_SYNTAX:
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type uri at or near \"%s\"",
state.errorPos)));
default:
elog(ERROR, "liburiparser error code %d", state.errorCode);
}
}
PG_FUNCTION_INFO_V1(uri_in);
Datum
uri_in(PG_FUNCTION_ARGS)
{
char *s = PG_GETARG_CSTRING(0);
uritype *vardata;
UriUriA uri;
parse_uri(s, &uri);
uriFreeUriMembersA(&uri);
vardata = (uritype *) cstring_to_text(s);
PG_RETURN_URI_P(vardata);
}
PG_FUNCTION_INFO_V1(uri_out);
Datum
uri_out(PG_FUNCTION_ARGS)
{
Datum arg = PG_GETARG_DATUM(0);
PG_RETURN_CSTRING(TextDatumGetCString(arg));
}
static text *
uri_text_range_to_text(UriTextRangeA r)
{
if (!r.first || !r.afterLast)
return NULL;
return cstring_to_text_with_len(r.first, r.afterLast - r.first);
}
PG_FUNCTION_INFO_V1(uri_scheme);
Datum
uri_scheme(PG_FUNCTION_ARGS)
{
Datum arg = PG_GETARG_DATUM(0);
char *s = TextDatumGetCString(arg);
UriUriA uri;
text *result;
parse_uri(s, &uri);
result = uri_text_range_to_text(uri.scheme);
uriFreeUriMembersA(&uri);
if (result)
PG_RETURN_TEXT_P(result);
else
PG_RETURN_NULL();
}
PG_FUNCTION_INFO_V1(uri_userinfo);
Datum
uri_userinfo(PG_FUNCTION_ARGS)
{
Datum arg = PG_GETARG_DATUM(0);
char *s = TextDatumGetCString(arg);
UriUriA uri;
text *result;
parse_uri(s, &uri);
result = uri_text_range_to_text(uri.userInfo);
uriFreeUriMembersA(&uri);
if (result)
PG_RETURN_TEXT_P(result);
else
PG_RETURN_NULL();
}
PG_FUNCTION_INFO_V1(uri_host);
Datum
uri_host(PG_FUNCTION_ARGS)
{
Datum arg = PG_GETARG_DATUM(0);
char *s = TextDatumGetCString(arg);
UriUriA uri;
text *result;
parse_uri(s, &uri);
result = uri_text_range_to_text(uri.hostText);
uriFreeUriMembersA(&uri);
if (result)
PG_RETURN_TEXT_P(result);
else
PG_RETURN_NULL();
}
PG_FUNCTION_INFO_V1(uri_host_inet);
Datum
uri_host_inet(PG_FUNCTION_ARGS)
{
Datum arg = PG_GETARG_DATUM(0);
char *s = TextDatumGetCString(arg);
UriUriA uri;
text *result;
parse_uri(s, &uri);
if (uri.hostData.ip4)
{
unsigned char *data = uri.hostData.ip4;
char *tmp = psprintf("%u.%u.%u.%u", data[0], data[1], data[2], data[3]);
uriFreeUriMembersA(&uri);
PG_RETURN_INET_P(DirectFunctionCall1(inet_in, CStringGetDatum(tmp)));
}
else if (uri.hostData.ip6)
{
unsigned char *data = uri.hostData.ip6;
char *tmp = psprintf("%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
data[0], data[1], data[2], data[3],
data[4], data[5], data[6], data[7],
data[8], data[9], data[10], data[11],
data[12], data[13], data[14], data[15]);
uriFreeUriMembersA(&uri);
PG_RETURN_INET_P(DirectFunctionCall1(inet_in, CStringGetDatum(tmp)));
}
else
{
uriFreeUriMembersA(&uri);
PG_RETURN_NULL();
}
}
PG_FUNCTION_INFO_V1(uri_port);
Datum
uri_port(PG_FUNCTION_ARGS)
{
Datum arg = PG_GETARG_DATUM(0);
char *s = TextDatumGetCString(arg);
UriUriA uri;
text *result;
parse_uri(s, &uri);
result = uri_text_range_to_text(uri.portText);
uriFreeUriMembersA(&uri);
if (result)
PG_RETURN_TEXT_P(result);
else
PG_RETURN_NULL();
}
PG_FUNCTION_INFO_V1(uri_query);
Datum
uri_query(PG_FUNCTION_ARGS)
{
Datum arg = PG_GETARG_DATUM(0);
char *s = TextDatumGetCString(arg);
UriUriA uri;
text *result;
parse_uri(s, &uri);
result = uri_text_range_to_text(uri.query);
uriFreeUriMembersA(&uri);
if (result)
PG_RETURN_TEXT_P(result);
else
PG_RETURN_NULL();
}
PG_FUNCTION_INFO_V1(uri_fragment);
Datum
uri_fragment(PG_FUNCTION_ARGS)
{
Datum arg = PG_GETARG_DATUM(0);
char *s = TextDatumGetCString(arg);
UriUriA uri;
text *result;
parse_uri(s, &uri);
result = uri_text_range_to_text(uri.fragment);
uriFreeUriMembersA(&uri);
if (result)
PG_RETURN_TEXT_P(result);
else
PG_RETURN_NULL();
}
PG_FUNCTION_INFO_V1(uri_path);
Datum
uri_path(PG_FUNCTION_ARGS)
{
Datum arg = PG_GETARG_DATUM(0);
char *s = TextDatumGetCString(arg);
UriUriA uri;
ArrayBuildState *astate = initArrayResult(TEXTOID, CurrentMemoryContext);
UriPathSegmentA *pa;
parse_uri(s, &uri);
for (pa = uri.pathHead; pa; pa = pa->next)
{
text *piece = uri_text_range_to_text(pa->text);
astate = accumArrayResult(astate,
PointerGetDatum(piece),
!piece,
TEXTOID,
CurrentMemoryContext);
}
uriFreeUriMembersA(&uri);
if (astate)
PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate,
CurrentMemoryContext));
else
PG_RETURN_NULL();
}
static int
cmp_text_range(UriTextRangeA a, UriTextRangeA b)
{
if (!a.first || !a.afterLast)
{
if (!b.first || !b.afterLast)
return 0;
else
return -1;
}
else if (!b.first || !b.afterLast)
return 1;
else
{
int x = strncmp(a.first, b.first,
Min(a.afterLast - a.first, b.afterLast - b.first));
if (x == 0)
return (a.afterLast - a.first) - (b.afterLast - b.first);
return x;
}
}
static int
_uri_cmp(Datum a, Datum b)
{
const char *sa = TextDatumGetCString(a);
const char *sb = TextDatumGetCString(b);
UriUriA ua;
UriUriA ub;
int res = 0;
parse_uri(sa, &ua);
parse_uri(sa, &ub);
if (res == 0)
res = cmp_text_range(ua.scheme, ub.scheme);
if (res == 0)
res = cmp_text_range(ua.hostText, ub.hostText);
if (res == 0)
res = strcmp(sa, sb);
uriFreeUriMembersA(&ua);
uriFreeUriMembersA(&ub);
return res;
}
PG_FUNCTION_INFO_V1(uri_lt);
Datum
uri_lt(PG_FUNCTION_ARGS)
{
Datum *arg1 = PG_GETARG_DATUM(0);
Datum *arg2 = PG_GETARG_DATUM(1);
PG_RETURN_BOOL(_uri_cmp(arg1, arg2) < 0);
}
PG_FUNCTION_INFO_V1(uri_le);
Datum
uri_le(PG_FUNCTION_ARGS)
{
Datum *arg1 = PG_GETARG_DATUM(0);
Datum *arg2 = PG_GETARG_DATUM(1);
PG_RETURN_BOOL(_uri_cmp(arg1, arg2) <= 0);
}
PG_FUNCTION_INFO_V1(uri_eq);
Datum
uri_eq(PG_FUNCTION_ARGS)
{
Datum *arg1 = PG_GETARG_DATUM(0);
Datum *arg2 = PG_GETARG_DATUM(1);
PG_RETURN_BOOL(_uri_cmp(arg1, arg2) == 0);
}
PG_FUNCTION_INFO_V1(uri_ne);
Datum
uri_ne(PG_FUNCTION_ARGS)
{
Datum *arg1 = PG_GETARG_DATUM(0);
Datum *arg2 = PG_GETARG_DATUM(1);
PG_RETURN_BOOL(_uri_cmp(arg1, arg2) != 0);
}
PG_FUNCTION_INFO_V1(uri_ge);
Datum
uri_ge(PG_FUNCTION_ARGS)
{
Datum *arg1 = PG_GETARG_DATUM(0);
Datum *arg2 = PG_GETARG_DATUM(1);
PG_RETURN_BOOL(_uri_cmp(arg1, arg2) >= 0);
}
PG_FUNCTION_INFO_V1(uri_gt);
Datum
uri_gt(PG_FUNCTION_ARGS)
{
Datum *arg1 = PG_GETARG_DATUM(0);
Datum *arg2 = PG_GETARG_DATUM(1);
PG_RETURN_BOOL(_uri_cmp(arg1, arg2) > 0);
}
PG_FUNCTION_INFO_V1(uri_cmp);
Datum
uri_cmp(PG_FUNCTION_ARGS)
{
Datum *arg1 = PG_GETARG_DATUM(0);
Datum *arg2 = PG_GETARG_DATUM(1);
PG_RETURN_INT32(_uri_cmp(arg1, arg2));
}

4
pguri.control Normal file
View File

@ -0,0 +1,4 @@
comment = 'uri type'
default_version = 0
module_pathname = '$libdir/pguri'
relocatable = true

189
pguri.sql Normal file
View File

@ -0,0 +1,189 @@
SET client_min_messages = warning;
CREATE TYPE uri;
CREATE FUNCTION uri_in(cstring) RETURNS uri
IMMUTABLE
STRICT
LANGUAGE C
AS '$libdir/pguri';
CREATE FUNCTION uri_out(uri) RETURNS cstring
IMMUTABLE
STRICT
LANGUAGE C
AS '$libdir/pguri';
CREATE TYPE uri (
INTERNALLENGTH = -1,
INPUT = uri_in,
OUTPUT = uri_out
);
CREATE CAST (uri AS text) WITH INOUT AS ASSIGNMENT;
CREATE CAST (text AS uri) WITH INOUT AS ASSIGNMENT;
CREATE FUNCTION uri_scheme(uri) RETURNS text
IMMUTABLE
STRICT
LANGUAGE C
AS '$libdir/pguri';
CREATE FUNCTION uri_userinfo(uri) RETURNS text
IMMUTABLE
STRICT
LANGUAGE C
AS '$libdir/pguri';
CREATE FUNCTION uri_host(uri) RETURNS text
IMMUTABLE
STRICT
LANGUAGE C
AS '$libdir/pguri';
CREATE FUNCTION uri_host_inet(uri) RETURNS inet
IMMUTABLE
STRICT
LANGUAGE C
AS '$libdir/pguri';
CREATE FUNCTION uri_port(uri) RETURNS text
IMMUTABLE
STRICT
LANGUAGE C
AS '$libdir/pguri';
CREATE FUNCTION uri_query(uri) RETURNS text
IMMUTABLE
STRICT
LANGUAGE C
AS '$libdir/pguri';
CREATE FUNCTION uri_fragment(uri) RETURNS text
IMMUTABLE
STRICT
LANGUAGE C
AS '$libdir/pguri';
CREATE FUNCTION uri_path(uri) RETURNS text[]
IMMUTABLE
STRICT
LANGUAGE C
AS '$libdir/pguri';
CREATE FUNCTION uri_lt(uri, uri) RETURNS boolean
IMMUTABLE
STRICT
LANGUAGE C
AS '$libdir/pguri';
CREATE FUNCTION uri_le(uri, uri) RETURNS boolean
IMMUTABLE
STRICT
LANGUAGE C
AS '$libdir/pguri';
CREATE FUNCTION uri_eq(uri, uri) RETURNS boolean
IMMUTABLE
STRICT
LANGUAGE C
AS '$libdir/pguri';
CREATE FUNCTION uri_ne(uri, uri) RETURNS boolean
IMMUTABLE
STRICT
LANGUAGE C
AS '$libdir/pguri';
CREATE FUNCTION uri_ge(uri, uri) RETURNS boolean
IMMUTABLE
STRICT
LANGUAGE C
AS '$libdir/pguri';
CREATE FUNCTION uri_gt(uri, uri) RETURNS boolean
IMMUTABLE
STRICT
LANGUAGE C
AS '$libdir/pguri';
CREATE FUNCTION uri_cmp(uri, uri) RETURNS integer
IMMUTABLE
STRICT
LANGUAGE C
AS '$libdir/pguri';
CREATE OPERATOR < (
LEFTARG = uri,
RIGHTARG = uri,
COMMUTATOR = >,
NEGATOR = >=,
RESTRICT = scalarltsel,
JOIN = scalarltjoinsel,
PROCEDURE = uri_lt
);
CREATE OPERATOR <= (
LEFTARG = uri,
RIGHTARG = uri,
COMMUTATOR = >=,
NEGATOR = >,
RESTRICT = scalarltsel,
JOIN = scalarltjoinsel,
PROCEDURE = uri_le
);
CREATE OPERATOR = (
LEFTARG = uri,
RIGHTARG = uri,
COMMUTATOR = =,
NEGATOR = <>,
RESTRICT = eqsel,
JOIN = eqjoinsel,
HASHES,
MERGES,
PROCEDURE = uri_eq
);
CREATE OPERATOR <> (
LEFTARG = uri,
RIGHTARG = uri,
COMMUTATOR = <>,
NEGATOR = =,
RESTRICT = neqsel,
JOIN = neqjoinsel,
PROCEDURE = uri_ne
);
CREATE OPERATOR >= (
LEFTARG = uri,
RIGHTARG = uri,
COMMUTATOR = <=,
NEGATOR = <,
RESTRICT = scalargtsel,
JOIN = scalargtjoinsel,
PROCEDURE = uri_ge
);
CREATE OPERATOR > (
LEFTARG = uri,
RIGHTARG = uri,
COMMUTATOR = <,
NEGATOR = <=,
RESTRICT = scalargtsel,
JOIN = scalargtjoinsel,
PROCEDURE = uri_gt
);
CREATE OPERATOR CLASS uri_ops
DEFAULT FOR TYPE uri USING btree AS
OPERATOR 1 < ,
OPERATOR 2 <= ,
OPERATOR 3 = ,
OPERATOR 4 >= ,
OPERATOR 5 > ,
FUNCTION 1 uri_cmp(uri, uri);

1
test/expected/init.out Normal file
View File

@ -0,0 +1 @@
CREATE EXTENSION pguri;

178
test/expected/test.out Normal file
View File

@ -0,0 +1,178 @@
\pset null _null_
CREATE TABLE test (a serial, b uri);
INSERT INTO test (b)
VALUES ('http://www.postgresql.org/'),
('http://www.postgresql.org/docs/devel/static/xfunc-sql.html#XFUNC-SQL-FUNCTION-ARGUMENTS'),
('https://duckduckgo.com/?q=postgresql&ia=about'),
('ftp://ftp.gnu.org/gnu/bison'),
('mailto:foo@example.com'),
('ssh://username@review.openstack.org:29418/openstack/nova.git'),
('http://admin:password@192.168.0.1'),
('http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html'),
('http://[1080::8:800:200C:417A]/foo'),
(''),
('foobar');
SELECT * FROM test;
a | b
----+-----------------------------------------------------------------------------------------
1 | http://www.postgresql.org/
2 | http://www.postgresql.org/docs/devel/static/xfunc-sql.html#XFUNC-SQL-FUNCTION-ARGUMENTS
3 | https://duckduckgo.com/?q=postgresql&ia=about
4 | ftp://ftp.gnu.org/gnu/bison
5 | mailto:foo@example.com
6 | ssh://username@review.openstack.org:29418/openstack/nova.git
7 | http://admin:password@192.168.0.1
8 | http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html
9 | http://[1080::8:800:200C:417A]/foo
10 |
11 | foobar
(11 rows)
-- error cases
SELECT uri ':';
ERROR: invalid input syntax for type uri at or near ":"
LINE 1: SELECT uri ':';
^
SELECT uri 'foo bar';
ERROR: invalid input syntax for type uri at or near " bar"
LINE 1: SELECT uri 'foo bar';
^
\x on
SELECT b AS uri,
uri_scheme(b),
uri_userinfo(b),
uri_host(b),
uri_host_inet(b),
uri_port(b),
uri_path(b),
uri_query(b),
uri_fragment(b)
FROM test;
-[ RECORD 1 ]-+----------------------------------------------------------------------------------------
uri | http://www.postgresql.org/
uri_scheme | http
uri_userinfo | _null_
uri_host | www.postgresql.org
uri_host_inet | _null_
uri_port | _null_
uri_path | {""}
uri_query | _null_
uri_fragment | _null_
-[ RECORD 2 ]-+----------------------------------------------------------------------------------------
uri | http://www.postgresql.org/docs/devel/static/xfunc-sql.html#XFUNC-SQL-FUNCTION-ARGUMENTS
uri_scheme | http
uri_userinfo | _null_
uri_host | www.postgresql.org
uri_host_inet | _null_
uri_port | _null_
uri_path | {docs,devel,static,xfunc-sql.html}
uri_query | _null_
uri_fragment | XFUNC-SQL-FUNCTION-ARGUMENTS
-[ RECORD 3 ]-+----------------------------------------------------------------------------------------
uri | https://duckduckgo.com/?q=postgresql&ia=about
uri_scheme | https
uri_userinfo | _null_
uri_host | duckduckgo.com
uri_host_inet | _null_
uri_port | _null_
uri_path | {""}
uri_query | q=postgresql&ia=about
uri_fragment | _null_
-[ RECORD 4 ]-+----------------------------------------------------------------------------------------
uri | ftp://ftp.gnu.org/gnu/bison
uri_scheme | ftp
uri_userinfo | _null_
uri_host | ftp.gnu.org
uri_host_inet | _null_
uri_port | _null_
uri_path | {gnu,bison}
uri_query | _null_
uri_fragment | _null_
-[ RECORD 5 ]-+----------------------------------------------------------------------------------------
uri | mailto:foo@example.com
uri_scheme | mailto
uri_userinfo | _null_
uri_host | _null_
uri_host_inet | _null_
uri_port | _null_
uri_path | {foo@example.com}
uri_query | _null_
uri_fragment | _null_
-[ RECORD 6 ]-+----------------------------------------------------------------------------------------
uri | ssh://username@review.openstack.org:29418/openstack/nova.git
uri_scheme | ssh
uri_userinfo | username
uri_host | review.openstack.org
uri_host_inet | _null_
uri_port | 29418
uri_path | {openstack,nova.git}
uri_query | _null_
uri_fragment | _null_
-[ RECORD 7 ]-+----------------------------------------------------------------------------------------
uri | http://admin:password@192.168.0.1
uri_scheme | http
uri_userinfo | admin:password
uri_host | 192.168.0.1
uri_host_inet | 192.168.0.1
uri_port | _null_
uri_path | {}
uri_query | _null_
uri_fragment | _null_
-[ RECORD 8 ]-+----------------------------------------------------------------------------------------
uri | http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html
uri_scheme | http
uri_userinfo | _null_
uri_host | FEDC:BA98:7654:3210:FEDC:BA98:7654:3210
uri_host_inet | fedc:ba98:7654:3210:fedc:ba98:7654:3210
uri_port | 80
uri_path | {index.html}
uri_query | _null_
uri_fragment | _null_
-[ RECORD 9 ]-+----------------------------------------------------------------------------------------
uri | http://[1080::8:800:200C:417A]/foo
uri_scheme | http
uri_userinfo | _null_
uri_host | 1080::8:800:200C:417A
uri_host_inet | 1080::8:800:200c:417a
uri_port | _null_
uri_path | {foo}
uri_query | _null_
uri_fragment | _null_
-[ RECORD 10 ]+----------------------------------------------------------------------------------------
uri |
uri_scheme | _null_
uri_userinfo | _null_
uri_host | _null_
uri_host_inet | _null_
uri_port | _null_
uri_path | {}
uri_query | _null_
uri_fragment | _null_
-[ RECORD 11 ]+----------------------------------------------------------------------------------------
uri | foobar
uri_scheme | _null_
uri_userinfo | _null_
uri_host | _null_
uri_host_inet | _null_
uri_port | _null_
uri_path | {foobar}
uri_query | _null_
uri_fragment | _null_
\x off
SELECT DISTINCT b FROM test ORDER BY b;
b
-----------------------------------------------------------------------------------------
foobar
ftp://ftp.gnu.org/gnu/bison
http://[1080::8:800:200C:417A]/foo
http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html
http://admin:password@192.168.0.1
http://www.postgresql.org/
http://www.postgresql.org/docs/devel/static/xfunc-sql.html#XFUNC-SQL-FUNCTION-ARGUMENTS
https://duckduckgo.com/?q=postgresql&ia=about
mailto:foo@example.com
ssh://username@review.openstack.org:29418/openstack/nova.git
(11 rows)

1
test/sql/init.sql Normal file
View File

@ -0,0 +1 @@
CREATE EXTENSION pguri;

38
test/sql/test.sql Normal file
View File

@ -0,0 +1,38 @@
\pset null _null_
CREATE TABLE test (a serial, b uri);
INSERT INTO test (b)
VALUES ('http://www.postgresql.org/'),
('http://www.postgresql.org/docs/devel/static/xfunc-sql.html#XFUNC-SQL-FUNCTION-ARGUMENTS'),
('https://duckduckgo.com/?q=postgresql&ia=about'),
('ftp://ftp.gnu.org/gnu/bison'),
('mailto:foo@example.com'),
('ssh://username@review.openstack.org:29418/openstack/nova.git'),
('http://admin:password@192.168.0.1'),
('http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html'),
('http://[1080::8:800:200C:417A]/foo'),
(''),
('foobar');
SELECT * FROM test;
-- error cases
SELECT uri ':';
SELECT uri 'foo bar';
\x on
SELECT b AS uri,
uri_scheme(b),
uri_userinfo(b),
uri_host(b),
uri_host_inet(b),
uri_port(b),
uri_path(b),
uri_query(b),
uri_fragment(b)
FROM test;
\x off
SELECT DISTINCT b FROM test ORDER BY b;