Skip to content

Commit daa6891

Browse files
committed
Added function protobuf_query_array
1 parent 1eda660 commit daa6891

8 files changed

+135
-7
lines changed

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44
# To release a new version, change these numbers and
55
# run ./build-and-test.sh to update the test cases.
66
EXT_VERSION_MAJOR = 0
7-
EXT_VERSION_MINOR = 1
8-
EXT_VERSION_PATCHLEVEL = 2
7+
EXT_VERSION_MINOR = 2
8+
EXT_VERSION_PATCHLEVEL = 0
99
EXT_VERSION = $(EXT_VERSION_MAJOR).$(EXT_VERSION_MINOR).$(EXT_VERSION_PATCHLEVEL)
1010

1111
PROTOBUF_ROOT=third_party/protobuf
1212
PROTOC=$(PROTOBUF_ROOT)/src/protoc
1313

1414
MODULE_big = postgres_protobuf
1515
EXTENSION = postgres_protobuf
16-
DATA = postgres_protobuf--0.1.sql
16+
DATA = postgres_protobuf--0.1.sql postgres_protobuf--0.1--0.2.sql
1717
DOCS = README.md
1818
REGRESS = postgres_protobuf
1919
OBJS=$(patsubst %.cpp, %.o, $(wildcard *.cpp))

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ Examples:
1313
```sql
1414
SELECT protobuf_query('MyProto:some_submessage.some_map[some_key]', my_proto_column) FROM ...;
1515

16-
-- or for multiple results
16+
-- or for multiple results as an array
17+
SELECT protobuf_query_array('MyProto:some_repeated_field[*].some_map[*].some_field', my_proto_column) FROM ...;
18+
-- or as rows
1719
SELECT protobuf_query_multi('MyProto:some_repeated_field[*].some_map[*].some_field', my_proto_column) FROM ...;
1820
```
1921

@@ -96,7 +98,8 @@ SELECT protobuf_query('path.to.Message:path.to.field', protobuf_as_byte_array) A
9698
The following functions are defined:
9799

98100
- `protobuf_query(query, protobuf)` returns the first matching field in the protobuf, or NULL if missing or proto3 default.
99-
- `protobuf_query_multi(query, protobuf)` returns all matching fields in the protobuf. Missing or proto3 default values are not returned.
101+
- `protobuf_query_array(query, protobuf)` returns all matching fields in the protobuf as a text array. Missing or proto3 default values are not returned.
102+
- `protobuf_query_multi(query, protobuf)` returns all matching fields in the protobuf as a set of rows. Missing or proto3 default values are not returned.
100103
- `protobuf_to_json_text(protobuf_type, protobuf)` converts the protobuf to a JSON string, assuming it's of the given type.
101104
- `protobuf_from_json_text(protobuf_type, json_str)` parses a protobuf from a JSON string, assuming it's of the given type.
102105
- `protobuf_extension_version()` returns the extension version `X.Y.Z` as a number `X*10000+Y*100+Z`.

expected/postgres_protobuf.out

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
CREATE EXTENSION postgres_protobuf;
66
SELECT protobuf_extension_version() AS result;
77
result
8-
102
8+
200
99
(1 row)
1010
INSERT INTO protobuf_file_descriptor_sets (name, file_descriptor_set) VALUES ('default', '\x0afb0c0a196d61696e5f64657363726970746f725f7365742e70726f746f1209706770622e7465737422f7070a0e4578616d706c654d657373616765122c0a077363616c61727318012001280b32122e706770622e746573742e5363616c61727352077363616c61727312250a0e72657065617465645f696e743332180220032805520d7265706561746564496e74333212270a0f72657065617465645f737472696e67180320032809520e7265706561746564537472696e67123c0a05696e6e657218042001280b32262e706770622e746573742e4578616d706c654d6573736167652e496e6e65724d6573736167655205696e6e6572124d0a0e72657065617465645f696e6e657218052003280b32262e706770622e746573742e4578616d706c654d6573736167652e496e6e65724d657373616765520d7265706561746564496e6e6572122a0a07616e5f656e756d18062001280e32112e706770622e746573742e416e456e756d5206616e456e756d124a0a0b6d61705f7374723273747218072003280b32292e706770622e746573742e4578616d706c654d6573736167652e4d617053747232737472456e747279520a6d617053747232737472124a0a0b6d61705f696e743273747218082003280b32292e706770622e746573742e4578616d706c654d6573736167652e4d6170496e7432737472456e747279520a6d6170496e7432737472124a0a0b6d61705f696e7432696e7418092003280b32292e706770622e746573742e4578616d706c654d6573736167652e4d6170496e7432696e74456e747279520a6d6170496e7432696e7412500a0d6d61705f73747232696e6e6572180a2003280b322b2e706770622e746573742e4578616d706c654d6573736167652e4d617053747232696e6e6572456e747279520c6d617053747232696e6e65721a3d0a0f4d617053747232737472456e74727912100a036b657918012001280952036b657912140a0576616c7565180220012809520576616c75653a0238011a3d0a0f4d6170496e7432737472456e74727912100a036b657918012001280552036b657912140a0576616c7565180220012809520576616c75653a0238011a3d0a0f4d6170496e7432696e74456e74727912100a036b657918012001280552036b657912140a0576616c7565180220012805520576616c75653a0238011a670a114d617053747232696e6e6572456e74727912100a036b657918012001280952036b6579123c0a0576616c756518022001280b32262e706770622e746573742e4578616d706c654d6573736167652e496e6e65724d657373616765520576616c75653a0238011a520a0c496e6e65724d657373616765121b0a09696e6e65725f7374721801200128095208696e6e657253747212250a0e696e6e65725f7265706561746564180220032809520d696e6e657252657065617465642296040a075363616c61727312210a0c646f75626c655f6669656c64180120012801520b646f75626c654669656c64121f0a0b666c6f61745f6669656c64180220012802520a666c6f61744669656c64121f0a0b696e7433325f6669656c64180320012805520a696e7433324669656c64121f0a0b696e7436345f6669656c64180420012803520a696e7436344669656c6412210a0c75696e7433325f6669656c6418052001280d520b75696e7433324669656c6412210a0c75696e7436345f6669656c64180620012804520b75696e7436344669656c6412210a0c73696e7433325f6669656c64180720012811520b73696e7433324669656c6412210a0c73696e7436345f6669656c64180820012812520b73696e7436344669656c6412230a0d666978656433325f6669656c64180920012807520c666978656433324669656c6412230a0d666978656436345f6669656c64180a20012806520c666978656436344669656c6412250a0e73666978656433325f6669656c64180b2001280f520d73666978656433324669656c6412250a0e73666978656436345f6669656c64180c20012810520d73666978656436344669656c64121d0a0a626f6f6c5f6669656c64180d200128085209626f6f6c4669656c6412210a0c737472696e675f6669656c64180e20012809520b737472696e674669656c64121f0a0b62797465735f6669656c64180f2001280c520a62797465734669656c642a380a06416e456e756d120e0a0a456e756d56616c7565301000120e0a0a456e756d56616c7565311001120e0a0a456e756d56616c7565321002620670726f746f33'::BYTEA);
1111
INSERT INTO protobuf_file_descriptor_sets (name, file_descriptor_set) VALUES ('other', '\x0a6f0a1a6f746865725f64657363726970746f725f7365742e70726f746f120f706770622e746573742e6f7468657222380a154d657373616765496e4f7468657244657363536574121f0a0b696e7433325f6669656c64180120012805520a696e7433324669656c64620670726f746f33'::BYTEA);
@@ -424,6 +424,37 @@ result
424424
{"scalars":{"int32Field":123}}
425425
(1 row)
426426
--
427+
-- Array queries
428+
--
429+
SELECT protobuf_query_array('pgpb.test.ExampleMessage:repeated_int32[*]', '\x12037bc803'::BYTEA) AS result;
430+
result
431+
{123,456}
432+
(1 row)
433+
SELECT protobuf_query_array('pgpb.test.ExampleMessage:map_int2str[*]', '\x4207087b1203414141420808c8031203424242'::BYTEA) AS result;
434+
result
435+
{AAA,BBB}
436+
(1 row)
437+
SELECT protobuf_query_array('pgpb.test.ExampleMessage:map_int2str|keys', '\x4207087b1203414141420808c8031203424242'::BYTEA) AS result;
438+
result
439+
{123,456}
440+
(1 row)
441+
SELECT protobuf_query_array('pgpb.test.ExampleMessage:', '\x0a02187b'::BYTEA) AS result;
442+
result
443+
{"{\"scalars\":{\"int32Field\":123}}"}
444+
(1 row)
445+
SELECT protobuf_query_array('pgpb.test.ExampleMessage:scalars', '\x0a02187b'::BYTEA) AS result;
446+
result
447+
{"{\"int32Field\":123}"}
448+
(1 row)
449+
SELECT protobuf_query_array('pgpb.test.ExampleMessage:scalars.int32_field', '\x0a02187b'::BYTEA) AS result;
450+
result
451+
{123}
452+
(1 row)
453+
SELECT protobuf_query_array('pgpb.test.ExampleMessage:scalars.string_field', '\x0a02187b'::BYTEA) AS result;
454+
result
455+
{}
456+
(1 row)
457+
--
427458
-- Converting to JSON
428459
--
429460
SELECT protobuf_to_json_text('pgpb.test.ExampleMessage', '\x0a02187b'::BYTEA) AS result;

generate_test_cases.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,22 @@ def pg_quote(s)
318318
end
319319
end
320320

321+
section "Array queries" do
322+
with_proto('repeated_int32: 123, repeated_int32: 456') do
323+
test_sql("SELECT protobuf_query_array('pgpb.test.ExampleMessage:repeated_int32[*]', #{pg_proto}) AS result;", ['{123,456}'])
324+
end
325+
with_proto('map_int2str: { key: 123, value: "AAA" }, map_int2str { key: 456, value: "BBB" }') do
326+
test_sql("SELECT protobuf_query_array('pgpb.test.ExampleMessage:map_int2str[*]', #{pg_proto}) AS result;", ['{AAA,BBB}'])
327+
test_sql("SELECT protobuf_query_array('pgpb.test.ExampleMessage:map_int2str|keys', #{pg_proto}) AS result;", ['{123,456}'])
328+
end
329+
with_proto('scalars { int32_field: 123 }') do
330+
test_sql("SELECT protobuf_query_array('pgpb.test.ExampleMessage:', #{pg_proto}) AS result;", ['{"{\"scalars\":{\"int32Field\":123}}"}'])
331+
test_sql("SELECT protobuf_query_array('pgpb.test.ExampleMessage:scalars', #{pg_proto}) AS result;", ['{"{\"int32Field\":123}"}'])
332+
test_sql("SELECT protobuf_query_array('pgpb.test.ExampleMessage:scalars.int32_field', #{pg_proto}) AS result;", ['{123}'])
333+
test_sql("SELECT protobuf_query_array('pgpb.test.ExampleMessage:scalars.string_field', #{pg_proto}) AS result;", ['{}'])
334+
end
335+
end
336+
321337
section "Converting to JSON" do
322338
with_proto('scalars { int32_field: 123 }') do
323339
test_sql("SELECT protobuf_to_json_text('pgpb.test.ExampleMessage', #{pg_proto}) AS result;", ['{"scalars":{"int32Field":123}}'])

postgres_protobuf--0.1--0.2.sql

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
2+
\echo Use "CREATE EXTENSION postgres_protobuf" to load this file. \quit
3+
4+
CREATE FUNCTION protobuf_query_array(
5+
IN TEXT, -- Query
6+
IN BYTEA -- Binary protobuf
7+
)
8+
RETURNS TEXT[]
9+
AS 'MODULE_PATHNAME'
10+
LANGUAGE C STRICT STABLE;

postgres_protobuf.control

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
comment = 'Protocol buffers for PostgreSQL'
2-
default_version = '0.1'
2+
default_version = '0.2'
33
module_pathname = '$libdir/postgres_protobuf'
44
relocatable = true

postgres_protobuf.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ extern "C" {
1313
#include <postgres.h>
1414

1515
#include <access/htup_details.h>
16+
#include <catalog/pg_type.h>
1617
#include <fmgr.h>
1718
#include <funcapi.h>
19+
#include <utils/lsyscache.h>
1820
} // extern "C"
1921

2022
namespace postgres_protobuf {
@@ -88,6 +90,7 @@ extern "C" {
8890
PG_FUNCTION_INFO_V1(protobuf_extension_version);
8991
PG_FUNCTION_INFO_V1(protobuf_query);
9092
PG_FUNCTION_INFO_V1(protobuf_query_multi);
93+
PG_FUNCTION_INFO_V1(protobuf_query_array);
9194
PG_FUNCTION_INFO_V1(protobuf_to_json_text);
9295
PG_FUNCTION_INFO_V1(protobuf_from_json_text);
9396

@@ -145,6 +148,61 @@ Datum protobuf_query(PG_FUNCTION_ARGS) {
145148
}
146149
}
147150

151+
Datum protobuf_query_array(PG_FUNCTION_ARGS) {
152+
using namespace querying;
153+
154+
assert(PG_NARGS() == 2);
155+
156+
try {
157+
text* query_text = PG_GETARG_TEXT_P(0);
158+
std::string query_str(VARDATA_ANY(query_text),
159+
VARSIZE_ANY_EXHDR(query_text));
160+
querying::Query query(query_str, std::nullopt);
161+
PGPROTO_DEBUG("Query parsed");
162+
163+
bytea* proto_bytea = PG_GETARG_BYTEA_P(1);
164+
const uint8* proto_data =
165+
reinterpret_cast<const uint8*>(VARDATA_ANY(proto_bytea));
166+
size_t proto_len = VARSIZE_ANY_EXHDR(proto_bytea);
167+
const auto rows = query.Run(proto_data, proto_len);
168+
PGPROTO_DEBUG("Query ran. Results: %lu", rows.size());
169+
Datum* elements = static_cast<Datum*>(palloc0_or_throw_bad_alloc(sizeof(Datum) * rows.size()));
170+
for (size_t i = 0; i < rows.size(); ++i) {
171+
const std::string& row = rows[i];
172+
size_t size = VARHDRSZ + row.size();
173+
bytea* p = static_cast<bytea*>(palloc0_or_throw_bad_alloc(size));
174+
SET_VARSIZE(p, size);
175+
memcpy(VARDATA(p), row.data(), row.size());
176+
elements[i] = reinterpret_cast<Datum>(p);
177+
}
178+
int16 typlen;
179+
bool typbyval;
180+
char typalign;
181+
get_typlenbyvalalign(TEXTOID, &typlen, &typbyval, &typalign);
182+
ArrayType* result = construct_array(elements, rows.size(), TEXTOID, typlen, typbyval, typalign);
183+
PG_RETURN_ARRAYTYPE_P(result);
184+
} catch (const std::bad_alloc& e) {
185+
ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory")));
186+
} catch (const BadProto& e) {
187+
// TODO: is this a good error code?
188+
ereport(ERROR, (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
189+
errmsg("invalid protobuf: %s", e.msg.c_str())));
190+
} catch (const BadQuery& e) {
191+
// TODO: is this a good error code?
192+
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
193+
errmsg("invalid query: %s", e.msg.c_str())));
194+
} catch (const RecursionDepthExceeded& e) {
195+
// TODO: is this a good error code?
196+
// TODO: make the limit configurable
197+
ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
198+
errmsg("protobuf recursion depth exceeded")));
199+
} catch (...) {
200+
ereport(ERROR,
201+
(errcode(ERRCODE_INTERNAL_ERROR),
202+
errmsg("unknown C++ exception in postgres_protobuf extension")));
203+
}
204+
}
205+
148206
Datum protobuf_query_multi(PG_FUNCTION_ARGS) {
149207
using namespace querying;
150208

sql/postgres_protobuf.sql

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,16 @@ SELECT protobuf_query('pgpb.test.ExampleMessage:an_enum', '\x3002'::BYTEA) AS re
249249
--
250250
SELECT protobuf_query('pgpb.test.ExampleMessage:', '\x0a02187b'::BYTEA) AS result;
251251
--
252+
-- Array queries
253+
--
254+
SELECT protobuf_query_array('pgpb.test.ExampleMessage:repeated_int32[*]', '\x12037bc803'::BYTEA) AS result;
255+
SELECT protobuf_query_array('pgpb.test.ExampleMessage:map_int2str[*]', '\x4207087b1203414141420808c8031203424242'::BYTEA) AS result;
256+
SELECT protobuf_query_array('pgpb.test.ExampleMessage:map_int2str|keys', '\x4207087b1203414141420808c8031203424242'::BYTEA) AS result;
257+
SELECT protobuf_query_array('pgpb.test.ExampleMessage:', '\x0a02187b'::BYTEA) AS result;
258+
SELECT protobuf_query_array('pgpb.test.ExampleMessage:scalars', '\x0a02187b'::BYTEA) AS result;
259+
SELECT protobuf_query_array('pgpb.test.ExampleMessage:scalars.int32_field', '\x0a02187b'::BYTEA) AS result;
260+
SELECT protobuf_query_array('pgpb.test.ExampleMessage:scalars.string_field', '\x0a02187b'::BYTEA) AS result;
261+
--
252262
-- Converting to JSON
253263
--
254264
SELECT protobuf_to_json_text('pgpb.test.ExampleMessage', '\x0a02187b'::BYTEA) AS result;

0 commit comments

Comments
 (0)