Skip to content

Commit 3b29997

Browse files
committed
Deparser: Support pretty-printing and other deparser options
1 parent af3434b commit 3b29997

File tree

3 files changed

+168
-8
lines changed

3 files changed

+168
-8
lines changed

ext/pg_query/pg_query_ruby.c

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "pg_query.h"
2+
#include "postgres_deparse.h"
23
#include "xxhash/xxhash.h"
34
#include <ruby.h>
45

@@ -10,6 +11,8 @@ void raise_ruby_split_error(PgQuerySplitResult result);
1011

1112
VALUE pg_query_ruby_parse_protobuf(VALUE self, VALUE input);
1213
VALUE pg_query_ruby_deparse_protobuf(VALUE self, VALUE input);
14+
VALUE pg_query_ruby_deparse_protobuf_opts(VALUE self, VALUE input, VALUE pretty_print, VALUE comments, VALUE indent_size, VALUE max_line_length, VALUE trailing_newline, VALUE commas_start_of_line);
15+
VALUE pg_query_ruby_deparse_comments_for_query(VALUE self, VALUE input);
1316
VALUE pg_query_ruby_normalize(VALUE self, VALUE input);
1417
VALUE pg_query_ruby_fingerprint(VALUE self, VALUE input);
1518
VALUE pg_query_ruby_scan(VALUE self, VALUE input);
@@ -24,6 +27,8 @@ __attribute__((visibility ("default"))) void Init_pg_query(void)
2427

2528
rb_define_singleton_method(cPgQuery, "parse_protobuf", pg_query_ruby_parse_protobuf, 1);
2629
rb_define_singleton_method(cPgQuery, "deparse_protobuf", pg_query_ruby_deparse_protobuf, 1);
30+
rb_define_singleton_method(cPgQuery, "deparse_protobuf_opts", pg_query_ruby_deparse_protobuf_opts, 7);
31+
rb_define_singleton_method(cPgQuery, "deparse_comments_for_query", pg_query_ruby_deparse_comments_for_query, 1);
2732
rb_define_singleton_method(cPgQuery, "normalize", pg_query_ruby_normalize, 1);
2833
rb_define_singleton_method(cPgQuery, "fingerprint", pg_query_ruby_fingerprint, 1);
2934
rb_define_singleton_method(cPgQuery, "_raw_scan", pg_query_ruby_scan, 1);
@@ -70,6 +75,24 @@ void raise_ruby_deparse_error(PgQueryDeparseResult result)
7075
rb_exc_raise(rb_class_new_instance(4, args, cParseError));
7176
}
7277

78+
void raise_ruby_deparse_comments_error(PgQueryDeparseCommentsResult result)
79+
{
80+
VALUE cPgQuery, cParseError;
81+
VALUE args[4];
82+
83+
cPgQuery = rb_const_get(rb_cObject, rb_intern("PgQuery"));
84+
cParseError = rb_const_get_at(cPgQuery, rb_intern("ParseError"));
85+
86+
args[0] = rb_str_new2(result.error->message);
87+
args[1] = rb_str_new2(result.error->filename);
88+
args[2] = INT2NUM(result.error->lineno);
89+
args[3] = INT2NUM(result.error->cursorpos);
90+
91+
pg_query_free_deparse_comments_result(result);
92+
93+
rb_exc_raise(rb_class_new_instance(4, args, cParseError));
94+
}
95+
7396
void raise_ruby_normalize_error(PgQueryNormalizeResult result)
7497
{
7598
VALUE cPgQuery, cParseError;
@@ -182,6 +205,83 @@ VALUE pg_query_ruby_deparse_protobuf(VALUE self, VALUE input)
182205
return output;
183206
}
184207

208+
VALUE pg_query_ruby_deparse_protobuf_opts(VALUE self, VALUE input, VALUE pretty_print, VALUE comments, VALUE indent_size, VALUE max_line_length, VALUE trailing_newline, VALUE commas_start_of_line)
209+
{
210+
Check_Type(input, T_STRING);
211+
Check_Type(comments, T_ARRAY);
212+
Check_Type(indent_size, T_FIXNUM);
213+
Check_Type(max_line_length, T_FIXNUM);
214+
215+
VALUE output;
216+
PgQueryProtobuf pbuf = {0};
217+
PgQueryDeparseResult result = {0};
218+
PostgresDeparseOpts deparse_opts = {0};
219+
deparse_opts.pretty_print = RTEST(pretty_print);
220+
deparse_opts.indent_size = NUM2INT(indent_size);
221+
deparse_opts.max_line_length = NUM2INT(max_line_length);
222+
deparse_opts.trailing_newline = RTEST(trailing_newline);
223+
deparse_opts.commas_start_of_line = RTEST(commas_start_of_line);
224+
deparse_opts.comments = malloc(RARRAY_LEN(comments) * sizeof(PostgresDeparseComment *));
225+
deparse_opts.comment_count = RARRAY_LEN(comments);
226+
VALUE *comments_arr = RARRAY_PTR(comments);
227+
228+
for (int i = 0; i < RARRAY_LEN(comments); i++)
229+
{
230+
PostgresDeparseComment* comment = malloc(sizeof(PostgresDeparseComment));
231+
VALUE str_ref = rb_ivar_get(comments_arr[i], rb_intern("@str"));
232+
comment->match_location = NUM2INT(rb_ivar_get(comments_arr[i], rb_intern("@match_location")));
233+
comment->newlines_before_comment = NUM2INT(rb_ivar_get(comments_arr[i], rb_intern("@newlines_before_comment")));
234+
comment->newlines_after_comment = NUM2INT(rb_ivar_get(comments_arr[i], rb_intern("@newlines_after_comment")));
235+
comment->str = StringValueCStr(str_ref);
236+
237+
deparse_opts.comments[i] = comment;
238+
}
239+
240+
pbuf.data = StringValuePtr(input);
241+
pbuf.len = RSTRING_LEN(input);
242+
result = pg_query_deparse_protobuf_opts(pbuf, deparse_opts);
243+
244+
if (result.error) raise_ruby_deparse_error(result);
245+
246+
output = rb_str_new2(result.query);
247+
248+
pg_query_free_deparse_result(result);
249+
for (int i = 0; i < deparse_opts.comment_count; i++)
250+
free(deparse_opts.comments[i]);
251+
free(deparse_opts.comments);
252+
253+
return output;
254+
}
255+
256+
VALUE pg_query_ruby_deparse_comments_for_query(VALUE self, VALUE input)
257+
{
258+
Check_Type(input, T_STRING);
259+
260+
VALUE cPgQuery, cDeparseComment;
261+
262+
cPgQuery = rb_const_get(rb_cObject, rb_intern("PgQuery"));
263+
cDeparseComment = rb_const_get_at(cPgQuery, rb_intern("DeparseComment"));
264+
265+
VALUE output = rb_ary_new();
266+
PgQueryDeparseCommentsResult result = pg_query_deparse_comments_for_query(StringValueCStr(input));
267+
if (result.error) raise_ruby_deparse_comments_error(result);
268+
269+
for (int i = 0; i < result.comment_count; i++)
270+
{
271+
PostgresDeparseComment* comment = result.comments[i];
272+
VALUE c = rb_class_new_instance(0, NULL, cDeparseComment);
273+
rb_ivar_set(c, rb_intern("@str"), rb_str_new2(comment->str));
274+
rb_ivar_set(c, rb_intern("@match_location"), INT2NUM(comment->match_location));
275+
rb_ivar_set(c, rb_intern("@newlines_before_comment"), INT2NUM(comment->newlines_before_comment));
276+
rb_ivar_set(c, rb_intern("@newlines_after_comment"), INT2NUM(comment->newlines_after_comment));
277+
rb_ary_push(output, c);
278+
}
279+
280+
pg_query_free_deparse_comments_result(result);
281+
282+
return output;
283+
}
284+
185285
VALUE pg_query_ruby_normalize(VALUE self, VALUE input)
186286
{
187287
Check_Type(input, T_STRING);

lib/pg_query/deparse.rb

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,39 @@
11
module PgQuery
22
class ParserResult
3-
def deparse
4-
PgQuery.deparse(@tree)
3+
def deparse(opts: nil)
4+
PgQuery.deparse(@tree, opts: opts)
55
end
66
end
77

8+
class DeparseComment
9+
attr_accessor :match_location, :newlines_before_comment, :newlines_after_comment, :str
10+
end
11+
12+
DeparseOpts = Struct.new(:pretty_print, :comments, :indent_size, :max_line_length,
13+
:trailing_newline, :commas_start_of_line, keyword_init: true)
14+
815
# Reconstruct all of the parsed queries into their original form
9-
def self.deparse(tree)
10-
if PgQuery::ParseResult.method(:encode).arity == 1
11-
PgQuery.deparse_protobuf(PgQuery::ParseResult.encode(tree)).force_encoding('UTF-8')
12-
elsif PgQuery::ParseResult.method(:encode).arity == -1
13-
PgQuery.deparse_protobuf(PgQuery::ParseResult.encode(tree, recursion_limit: 1_000)).force_encoding('UTF-8')
16+
def self.deparse(tree, opts: nil)
17+
protobuf_encoded = if PgQuery::ParseResult.method(:encode).arity == 1
18+
PgQuery::ParseResult.encode(tree)
19+
elsif PgQuery::ParseResult.method(:encode).arity == -1
20+
PgQuery::ParseResult.encode(tree, recursion_limit: 1_000)
21+
else
22+
raise ArgumentError, 'Unsupported protobuf Ruby API'
23+
end
24+
25+
if opts
26+
PgQuery.deparse_protobuf_opts(
27+
protobuf_encoded,
28+
opts.pretty_print,
29+
opts.comments || [],
30+
opts.indent_size || 0,
31+
opts.max_line_length || 0,
32+
opts.trailing_newline,
33+
opts.commas_start_of_line
34+
).force_encoding('UTF-8')
1435
else
15-
raise ArgumentError, 'Unsupported protobuf Ruby API'
36+
PgQuery.deparse_protobuf(protobuf_encoded).force_encoding('UTF-8')
1637
end
1738
end
1839

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
require 'spec_helper'
2+
3+
describe PgQuery do
4+
describe '.deparse' do
5+
subject { PgQuery.parse(query).deparse(opts: PgQuery::DeparseOpts.new(pretty_print: true, comments: PgQuery.deparse_comments_for_query(query))) }
6+
7+
context 'SELECT' do
8+
context 'basic statement' do
9+
let(:query) do
10+
<<~Q
11+
SELECT a AS b
12+
FROM x
13+
WHERE
14+
y = 5
15+
AND z = y
16+
Q
17+
end
18+
19+
it { is_expected.to eq query.strip }
20+
end
21+
end
22+
23+
context 'comments' do
24+
context 'multi-line' do
25+
let(:query) do
26+
<<~Q
27+
--
28+
-- Name: EXTENSION btree_gist; Type: COMMENT; Schema: -; Owner: -
29+
--
30+
31+
COMMENT ON EXTENSION btree_gist IS 'support for indexing common datatypes in GiST'
32+
Q
33+
end
34+
35+
it { is_expected.to eq query.strip }
36+
end
37+
end
38+
end
39+
end

0 commit comments

Comments
 (0)