pg_query 0.6.2 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
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