pg_query 0.6.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 648434f9fd8c17f87a1797af0a71295d9fe7a2bf
4
- data.tar.gz: a4c16fed44f8930d6c1218e964372d37e79ff5ff
3
+ metadata.gz: f75ea93d3521a2b9f912943357d6b16e3598bbea
4
+ data.tar.gz: f320d5689ceda9ba637d751bf10e4f09d8dddaae
5
5
  SHA512:
6
- metadata.gz: 0c31153f12613c9e90db0ff1fa4024c073d6f4b0f24cbd0194b35c8d00706e3622d46c913239c94990eab7649c753c2427cbff252baa0afe3dda1f62c75fa690
7
- data.tar.gz: 2a83a4e86349133ba7760ba2d7a546bff22b431798326021aa94ec287c7b0871fed0b51a8dd9188917ae9d709e23025406074bec90043e0750994b5b2d4f7f21
6
+ metadata.gz: 2007ca5cea466541823b11818ba6ba9b86cdf447e3f449024a790b8ebfbf8a9762dfefc82e0e9abb6e96c42e5a3602c4c7bf7ed479aa431666f078f503911a7f
7
+ data.tar.gz: 687b67153015d9e3e36053a6ffc4e7c558cec3f4ca7eac3860188c0d88a72b41a38d57dcae0e02deb11535f3b8e2f499789fc1ad9281e1da8365c7d9a421e308
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.4 UNRELEASED
4
+
5
+ * ...
6
+
7
+
8
+ ## 0.6.3 2015-08-20
9
+
10
+ * Deparsing
11
+ * COUNT(*) [@JackDanger](https://github.com/JackDanger)
12
+ * Window clauses [Chris Martin](https://github.com/cmrtn)
13
+ * CREATE TABLE/VIEW/FUNCTION [@JackDanger](https://github.com/JackDanger)
14
+ * Return exact location for parser errors [@JackDanger](https://github.com/JackDanger)
15
+
16
+
3
17
  ## 0.6.2 2015-08-06
4
18
 
5
19
  * Speed up gem install by not generating rdoc/ri for the Postgres source
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # pg_query [ ![](https://img.shields.io/gem/v/pg_query.svg)](https://rubygems.org/gems/pg_query) [ ![](https://img.shields.io/gem/dt/pg_query.svg)](https://rubygems.org/gems/pg_query) [ ![Codeship Status for lfittl/dblint](https://img.shields.io/codeship/584524e0-ed17-0131-838b-4216c01ccc74.svg)](https://codeship.com/projects/26651)
1
+ # pg_query [ ![](https://img.shields.io/gem/v/pg_query.svg)](https://rubygems.org/gems/pg_query) [ ![](https://img.shields.io/gem/dt/pg_query.svg)](https://rubygems.org/gems/pg_query) [ ![](https://travis-ci.org/lfittl/pg_query.svg?branch=master)](https://travis-ci.org/lfittl/pg_query)
2
2
 
3
3
  This Ruby extension uses the actual PostgreSQL server source to parse SQL queries and return the internal PostgreSQL parsetree.
4
4
 
@@ -157,6 +157,7 @@ PgQuery.parse("SELECT 2; --- comment").fingerprint
157
157
  **This gem uses a [patched version of the latest PostgreSQL stable](https://github.com/pganalyze/postgres/compare/REL9_4_STABLE...pg_query).**
158
158
 
159
159
  Changes:
160
+
160
161
  * **scan.l/gram.y:** Modified to support parsing normalized queries
161
162
  * Known regression: Removed support for custom operators containing "?" (doesn't affect hstore/JSON/geometric operators)
162
163
  * **outfuncs_json.c:** Auto-generated outfuncs that outputs a parsetree as JSON (called through nodeToJSONString)
@@ -164,11 +165,16 @@ Changes:
164
165
  Unit tests for these patches are inside this library - the tests will break if run against upstream.
165
166
 
166
167
 
167
- ## Authors
168
+ ## Original Author
168
169
 
169
170
  - [Lukas Fittl](mailto:lukas@fittl.com)
170
171
 
171
172
 
173
+ ## Special Thanks to
174
+
175
+ - [Jack Danger Canty](https://github.com/JackDanger), for significantly improving deparsing
176
+
177
+
172
178
  ## License
173
179
 
174
180
  Copyright (c) 2015, pganalyze Team <team@pganalyze.com><br>
@@ -10,15 +10,21 @@
10
10
  VALUE new_parse_error(ErrorData* error)
11
11
  {
12
12
  VALUE cPgQuery, cParseError;
13
- VALUE args[2];
13
+ VALUE args[4];
14
14
 
15
15
  cPgQuery = rb_const_get(rb_cObject, rb_intern("PgQuery"));
16
16
  cParseError = rb_const_get_at(cPgQuery, rb_intern("ParseError"));
17
17
 
18
+ // exception message
18
19
  args[0] = rb_str_new2(error->message);
19
- args[1] = INT2NUM(error->cursorpos);
20
-
21
- return rb_class_new_instance(2, args, cParseError);
20
+ // source of exception (e.g. parse.l)
21
+ args[1] = rb_str_new2(error->filename);
22
+ // source of exception (e.g. 104)
23
+ args[2] = INT2NUM(error->lineno);
24
+ // char in query at which exception occurred
25
+ args[3] = INT2NUM(error->cursorpos);
26
+
27
+ return rb_class_new_instance(4, args, cParseError);
22
28
  }
23
29
 
24
30
  VALUE pg_query_raw_parse(VALUE self, VALUE input)
@@ -22,80 +22,96 @@ class PgQuery
22
22
  node = item.values[0]
23
23
 
24
24
  case type
25
- when 'RANGEVAR'
26
- deparse_rangevar(node)
25
+ when 'AEXPR AND'
26
+ deparse_aexpr_and(node)
27
+ when 'AEXPR ANY'
28
+ deparse_aexpr_any(node)
29
+ when 'AEXPR IN'
30
+ deparse_aexpr_in(node)
31
+ when 'AEXPR NOT'
32
+ deparse_aexpr_not(node)
33
+ when 'AEXPR OR'
34
+ deparse_aexpr_or(node)
27
35
  when 'AEXPR'
28
36
  deparse_aexpr(node)
29
- when 'COLUMNREF'
30
- deparse_columnref(node)
37
+ when 'ALIAS'
38
+ deparse_alias(node)
31
39
  when 'A_ARRAYEXPR'
32
40
  deparse_a_arrayexp(node)
33
41
  when 'A_CONST'
34
42
  deparse_a_const(node)
35
- when 'A_STAR'
36
- deparse_a_star(node)
37
- when 'A_INDIRECTION'
38
- deparse_a_indirection(node)
39
43
  when 'A_INDICES'
40
44
  deparse_a_indices(node)
41
- when 'ALIAS'
42
- deparse_alias(node)
43
- when 'PARAMREF'
44
- deparse_paramref(node)
45
- when 'RESTARGET'
46
- deparse_restarget(node, context)
45
+ when 'A_INDIRECTION'
46
+ deparse_a_indirection(node)
47
+ when 'A_STAR'
48
+ deparse_a_star(node)
49
+ when 'A_TRUNCATED'
50
+ '...' # pg_query internal
51
+ when 'CASE'
52
+ deparse_case(node)
53
+ when 'COALESCE'
54
+ deparse_coalesce(node)
55
+ when 'COLUMNDEF'
56
+ deparse_columndef(node)
57
+ when 'COLUMNREF'
58
+ deparse_columnref(node)
59
+ when 'COMMONTABLEEXPR'
60
+ deparse_cte(node)
61
+ when 'CONSTRAINT'
62
+ deparse_constraint(node)
63
+ when 'CREATEFUNCTIONSTMT'
64
+ deparse_create_function(node)
65
+ when 'CREATESTMT'
66
+ deparse_create_table(node)
67
+ when 'DEFELEM'
68
+ deparse_defelem(node)
69
+ when 'DELETE FROM'
70
+ deparse_delete_from(node)
47
71
  when 'FUNCCALL'
48
72
  deparse_funccall(node)
49
- when 'RANGEFUNCTION'
50
- deparse_range_function(node)
51
- when 'AEXPR AND'
52
- deparse_aexpr_and(node)
73
+ when 'FUNCTIONPARAMETER'
74
+ deparse_functionparameter(node)
75
+ when 'INSERT INTO'
76
+ deparse_insert_into(node)
53
77
  when 'JOINEXPR'
54
78
  deparse_joinexpr(node)
55
- when 'SORTBY'
56
- deparse_sortby(node)
79
+ when 'NULLTEST'
80
+ deparse_nulltest(node)
81
+ when 'PARAMREF'
82
+ deparse_paramref(node)
83
+ when 'RANGEFUNCTION'
84
+ deparse_range_function(node)
85
+ when 'RANGESUBSELECT'
86
+ deparse_rangesubselect(node)
87
+ when 'RANGEVAR'
88
+ deparse_rangevar(node)
89
+ when 'RESTARGET'
90
+ deparse_restarget(node, context)
91
+ when 'ROW'
92
+ deparse_row(node)
57
93
  when 'SELECT'
58
94
  deparse_select(node)
59
- when 'WITHCLAUSE'
60
- deparse_with_clause(node)
61
- when 'COMMONTABLEEXPR'
62
- deparse_cte(node)
63
- when 'INSERT INTO'
64
- deparse_insert_into(node)
65
- when 'UPDATE'
66
- deparse_update(node)
95
+ when 'SORTBY'
96
+ deparse_sortby(node)
97
+ when 'SUBLINK'
98
+ deparse_sublink(node)
99
+ when 'TRANSACTION'
100
+ deparse_transaction(node)
67
101
  when 'TYPECAST'
68
102
  deparse_typecast(node)
69
103
  when 'TYPENAME'
70
104
  deparse_typename(node)
71
- when 'CASE'
72
- deparse_case(node)
105
+ when 'UPDATE'
106
+ deparse_update(node)
73
107
  when 'WHEN'
74
108
  deparse_when(node)
75
- when 'SUBLINK'
76
- deparse_sublink(node)
77
- when 'RANGESUBSELECT'
78
- deparse_rangesubselect(node)
79
- when 'ROW'
80
- deparse_row(node)
81
- when 'AEXPR IN'
82
- deparse_aexpr_in(node)
83
- when 'AEXPR NOT'
84
- deparse_aexpr_not(node)
85
- when 'AEXPR OR'
86
- deparse_aexpr_or(node)
87
- when 'AEXPR ANY'
88
- deparse_aexpr_any(node)
89
- when 'NULLTEST'
90
- deparse_nulltest(node)
91
- when 'TRANSACTION'
92
- deparse_transaction(node)
93
- when 'COALESCE'
94
- deparse_coalesce(node)
95
- when 'DELETE FROM'
96
- deparse_delete_from(node)
97
- when 'A_TRUNCATED'
98
- '...' # pg_query internal
109
+ when 'WINDOWDEF'
110
+ deparse_windowdef(node)
111
+ when 'WITHCLAUSE'
112
+ deparse_with_clause(node)
113
+ when 'VIEWSTMT'
114
+ deparse_viewstmt(node)
99
115
  else
100
116
  fail format("Can't deparse: %s: %s", type, node.inspect)
101
117
  end
@@ -122,7 +138,7 @@ class PgQuery
122
138
  end
123
139
 
124
140
  def deparse_a_const(node)
125
- node['val'].inspect.gsub('"', '\'')
141
+ node['val'].inspect.gsub("'", "''").gsub('"', "'")
126
142
  end
127
143
 
128
144
  def deparse_a_star(_node)
@@ -142,7 +158,12 @@ class PgQuery
142
158
  end
143
159
 
144
160
  def deparse_alias(node)
145
- node['aliasname']
161
+ name = node['aliasname']
162
+ if node['colnames']
163
+ name + '(' + node['colnames'].join(', ') + ')'
164
+ else
165
+ name
166
+ end
146
167
  end
147
168
 
148
169
  def deparse_paramref(node)
@@ -166,8 +187,41 @@ class PgQuery
166
187
  end
167
188
 
168
189
  def deparse_funccall(node)
190
+ output = []
191
+
192
+ # SUM(a, b)
169
193
  args = Array(node['args']).map { |arg| deparse_item(arg) }
170
- format('%s(%s)', node['funcname'].join('.'), args.join(', '))
194
+ # COUNT(*)
195
+ args << '*' if node['agg_star']
196
+
197
+ output << format('%s(%s)', node['funcname'].join('.'), args.join(', '))
198
+ output << format('OVER (%s)', deparse_item(node['over'])) if node['over']
199
+
200
+ output.join(' ')
201
+ end
202
+
203
+ def deparse_windowdef(node)
204
+ output = []
205
+
206
+ if node['partitionClause']
207
+ output << 'PARTITION BY'
208
+ output << node['partitionClause'].map do |item|
209
+ deparse_item(item)
210
+ end.join(', ')
211
+ end
212
+
213
+ if node['orderClause']
214
+ output << 'ORDER BY'
215
+ output << node['orderClause'].map do |item|
216
+ deparse_item(item)
217
+ end.join(', ')
218
+ end
219
+
220
+ output.join(' ')
221
+ end
222
+
223
+ def deparse_functionparameter(node)
224
+ deparse_item(node['argType'])
171
225
  end
172
226
 
173
227
  def deparse_aexpr_in(node)
@@ -234,6 +288,7 @@ class PgQuery
234
288
  output = []
235
289
  output << deparse_item(node['node'])
236
290
  output << 'ASC' if node['sortby_dir'] == 1
291
+ output << 'DESC' if node['sortby_dir'] == 2
237
292
  output.join(' ')
238
293
  end
239
294
 
@@ -246,12 +301,36 @@ class PgQuery
246
301
  output.join(' ')
247
302
  end
248
303
 
304
+ def deparse_viewstmt(node)
305
+ output = []
306
+ output << 'CREATE'
307
+ output << 'OR REPLACE' if node['replace']
308
+
309
+ persistence = relpersistence(node['view'])
310
+ output << persistence if persistence
311
+
312
+ output << 'VIEW'
313
+ output << node['view']['RANGEVAR']['relname']
314
+ output << format('(%s)', node['aliases'].join(', ')) if node['aliases']
315
+
316
+ output << 'AS'
317
+ output << deparse_item(node['query'])
318
+
319
+ case node['withCheckOption']
320
+ when 1
321
+ output << 'WITH CHECK OPTION'
322
+ when 2
323
+ output << 'WITH CASCADED CHECK OPTION'
324
+ end
325
+ output.join(' ')
326
+ end
327
+
249
328
  def deparse_cte(node)
250
- output = ''
251
- output += node['ctename']
252
- output += format('(%s)', node['aliascolnames'].join(', ')) if node['aliascolnames']
253
- output += format(' AS (%s)', deparse_item(node['ctequery']))
254
- output
329
+ output = []
330
+ output << node['ctename']
331
+ output << format('(%s)', node['aliascolnames'].join(', ')) if node['aliascolnames']
332
+ output << format('AS (%s)', deparse_item(node['ctequery']))
333
+ output.join(' ')
255
334
  end
256
335
 
257
336
  def deparse_case(node)
@@ -265,6 +344,65 @@ class PgQuery
265
344
  output.join(' ')
266
345
  end
267
346
 
347
+ def deparse_columndef(node)
348
+ output = [node['colname']]
349
+ output << deparse_item(node['typeName'])
350
+ if node['constraints']
351
+ output += node['constraints'].map do |item|
352
+ deparse_item(item)
353
+ end
354
+ end
355
+ output.join(' ')
356
+ end
357
+
358
+ def deparse_constraint(node)
359
+ output = []
360
+ # NOT_NULL -> NOT NULL
361
+ output << node['contype'].gsub('_', ' ')
362
+ output << deparse_item(node['raw_expr']) if node['raw_expr']
363
+ output.join(' ')
364
+ end
365
+
366
+ def deparse_create_function(node)
367
+ output = []
368
+ output << 'CREATE FUNCTION'
369
+
370
+ arguments = node['parameters'].map { |item| deparse_item(item) }.join(', ')
371
+
372
+ output << node['funcname'].first + '(' + arguments + ')'
373
+
374
+ output << 'RETURNS'
375
+ output << deparse_item(node['returnType'])
376
+ output += node['options'].map { |item| deparse_item(item) }
377
+
378
+ output.join(' ')
379
+ end
380
+
381
+ def deparse_create_table(node)
382
+ output = []
383
+ output << 'CREATE'
384
+
385
+ persistence = relpersistence(node['relation'])
386
+ output << persistence if persistence
387
+
388
+ output << 'TABLE'
389
+
390
+ output << deparse_item(node['relation'])
391
+
392
+ output << '(' + node['tableElts'].map do |item|
393
+ deparse_item(item)
394
+ end.join(', ') + ')'
395
+
396
+ if node['inhRelations']
397
+ output << 'INHERITS'
398
+ output << '(' + node['inhRelations'].map do |relation|
399
+ deparse_item(relation)
400
+ end.join(', ') + ')'
401
+ end
402
+
403
+ output.join(' ')
404
+ end
405
+
268
406
  def deparse_when(node)
269
407
  output = ['WHEN']
270
408
  output << deparse_item(node['expr'])
@@ -284,11 +422,12 @@ class PgQuery
284
422
  end
285
423
 
286
424
  def deparse_rangesubselect(node)
287
- output = '('
288
- output += deparse_item(node['subquery'])
289
- output += ')'
290
- output += ' ' + node['alias']['ALIAS']['aliasname'] if node['alias']
291
- output
425
+ output = '(' + deparse_item(node['subquery']) + ')'
426
+ if node['alias']
427
+ output + ' ' + deparse_item(node['alias'])
428
+ else
429
+ output
430
+ end
292
431
  end
293
432
 
294
433
  def deparse_row(node)
@@ -400,7 +539,7 @@ class PgQuery
400
539
  end
401
540
 
402
541
  def deparse_typecast(node)
403
- if deparse_item(node['typeName']) == :boolean
542
+ if deparse_item(node['typeName']) == 'boolean'
404
543
  deparse_item(node['arg']) == "'t'" ? 'true' : 'false'
405
544
  else
406
545
  deparse_item(node['arg']) + '::' + deparse_typename(node['typeName']['TYPENAME'])
@@ -408,10 +547,50 @@ class PgQuery
408
547
  end
409
548
 
410
549
  def deparse_typename(node)
411
- if node['names'] == %w(pg_catalog bool)
412
- :boolean
550
+ output = []
551
+ output << 'SETOF' if node['setof']
552
+
553
+ if node['typmods']
554
+ arguments = node['typmods'].map do |item|
555
+ deparse_item(item)
556
+ end.join(', ')
557
+ end
558
+
559
+ output << deparse_typename_cast(node['names'], arguments)
560
+
561
+ output.join(' ')
562
+ end
563
+
564
+ def deparse_typename_cast(names, arguments) # rubocop:disable Metrics/CyclomaticComplexity
565
+ catalog, type = names
566
+ # Just pass along any custom types.
567
+ # (The pg_catalog types are built-in Postgres system types and are
568
+ # handled in the case statement below)
569
+ return names.join('.') if catalog != 'pg_catalog'
570
+
571
+ case type
572
+ when 'bpchar'
573
+ # char(2) or char(9)
574
+ "char(#{arguments})"
575
+ when 'varchar'
576
+ "varchar(#{arguments})"
577
+ when 'numeric'
578
+ # numeric(3, 5)
579
+ "numeric(#{arguments})"
580
+ when 'bool'
581
+ 'boolean'
582
+ when 'int2'
583
+ 'smallint'
584
+ when 'int4'
585
+ 'int'
586
+ when 'int8'
587
+ 'bigint'
588
+ when 'real', 'float4'
589
+ 'real'
590
+ when 'float8'
591
+ 'double'
413
592
  else
414
- node['names'].join('.')
593
+ fail format("Can't deparse type: %s", type)
415
594
  end
416
595
  end
417
596
 
@@ -437,8 +616,8 @@ class PgQuery
437
616
  output = []
438
617
  output << TRANSACTION_CMDS[node['kind']] || fail(format("Can't deparse TRANSACTION %s", node.inspect))
439
618
 
440
- if node['options'] && node['options'][0]['DEFELEM']
441
- output << node['options'][0]['DEFELEM']['arg']
619
+ if node['options']
620
+ output += node['options'].map { |item| deparse_item(item) }
442
621
  end
443
622
 
444
623
  output.join(' ')
@@ -448,6 +627,17 @@ class PgQuery
448
627
  format('COALESCE(%s)', node['args'].map { |a| deparse_item(a) }.join(', '))
449
628
  end
450
629
 
630
+ def deparse_defelem(node)
631
+ case node['defname']
632
+ when 'as'
633
+ "AS $$#{node['arg'].join("\n")}$$"
634
+ when 'language'
635
+ "language #{node['arg']}"
636
+ else
637
+ node['arg']
638
+ end
639
+ end
640
+
451
641
  def deparse_delete_from(node)
452
642
  output = []
453
643
  output << deparse_item(node['withClause']) if node['withClause']
@@ -477,5 +667,15 @@ class PgQuery
477
667
 
478
668
  output.join(' ')
479
669
  end
670
+
671
+ # The PG parser adds several pieces of view data onto the RANGEVAR
672
+ # that need to be printed before deparse_rangevar is called.
673
+ def relpersistence(rangevar)
674
+ if rangevar['RANGEVAR']['relpersistence'] == 't'
675
+ 'TEMPORARY'
676
+ elsif rangevar['RANGEVAR']['relpersistence'] == 'u'
677
+ 'UNLOGGED'
678
+ end
679
+ end
480
680
  end
481
681
  end
@@ -7,7 +7,7 @@ class PgQuery
7
7
  begin
8
8
  parsetree = JSON.parse(parsetree, max_nesting: 1000)
9
9
  rescue JSON::ParserError
10
- raise ParseError.new('Failed to parse JSON', -1)
10
+ raise ParseError.new('Failed to parse JSON', __FILE__, __LINE__, -1)
11
11
  end
12
12
 
13
13
  warnings = []
@@ -1,8 +1,8 @@
1
1
  class PgQuery
2
2
  class ParseError < ArgumentError
3
3
  attr_reader :location
4
- def initialize(message, location)
5
- super(message)
4
+ def initialize(message, source_file, source_line, location)
5
+ super("#{message} (#{source_file}:#{source_line})")
6
6
  @location = location
7
7
  end
8
8
  end
@@ -1,3 +1,3 @@
1
1
  class PgQuery
2
- VERSION = '0.6.2'
2
+ VERSION = '0.6.3'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_query
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.6.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lukas Fittl
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-06 00:00:00.000000000 Z
11
+ date: 2015-08-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler