pg_query 1.2.0 → 1.3.0

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
  SHA256:
3
- metadata.gz: 7e8912d6c8962c7e999e1a14d4258f0553cdf6e538e20e6374cb4b362118ec84
4
- data.tar.gz: e27779fc7d4e15c76d8a38195541240c2b48508d2d07fe17eef715ac00e91a06
3
+ metadata.gz: f3d7de9759711a5e173db293a6120a15ee6ab1f0f4c5804e7f68aaba99ec8ff8
4
+ data.tar.gz: 974e3fb0b709021a771803e52de2a8ca19309a679d81c1ee2a3deb105f297769
5
5
  SHA512:
6
- metadata.gz: 8eaa86d4571c15b3ea496bf5f85a4e2b1c69f5b1c2f2ad179862d020ca00d6fac160f0a1296707d0bee802f5b0e7cf29b466deb4c04927ec6e6987e31d2347f7
7
- data.tar.gz: d766dc39d5b3ec3ee9fe1809ece77ca8a83c78290e4d72893e86cff37e4d47eac18b2dc18437807e2f5b4a4c50ccfe518831a9dae9e7bdd7cfd778eed86c9415
6
+ metadata.gz: 3c13b36c353e6de6aaaba2b6428be7e21c933bf8ef159212cdd64ee4a765f8a664defe735f2f3e94ef6441f3403d7be6fbf19ca4d6b98e07a6b42a68a635238a
7
+ data.tar.gz: cc742d80bdf934c89e1faa1dd6e6560764339ecabb6fe2af67fd219b2a71b4fb6756e649ddc237d995413a90c6b036d33fe8492eb91030d8a891f970ca10510a
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.3.0 2020-12-28
4
+
5
+ * Incorporate newer libpg_query updates in 10-1.0.3 and 10-1.0.4
6
+ * Adds support for running on ARM
7
+ * Fixes asprintf warning during builds
8
+ * Updates to newer Postgres 10 patch release (10.15)
9
+ * Deparsing improvements by [@emin100](https://github.com/emin100)
10
+ * Add support for additional DROP statements (#147)
11
+ * Fix CREATE TABLE AS - Support without TEMP, Add ON COMMIT (#149)
12
+ * Empty target list support (#156)
13
+ * UNION parentheses (#158)
14
+ * OVERLAY keyword function (#161)
15
+ * Array indirection (#162)
16
+ * ARRAY functions (#163)
17
+ * Correctly handle column names that need escaping in INSERT and UPDATE statements (#164)
18
+ * INSERT INTO ON CONFLICT (#166)
19
+ * LATERAL JOIN (#168)
20
+ * UPDATE FROM clause (#170)
21
+ * SELECT aggregate FILTER (#175)
22
+ * INTERSECT operator (#176)
23
+ * Deparsing: Improve handling of boolean type casts [@himanshu-pro](https://github.com/himanshu-pro) & [@emin100](https://github.com/emin100)
24
+ * `tables` method: Find tables in the subquery of CREATE TABLE AS (#172) [@Tassosb](https://github.com/Tassosb)
25
+ * Support Ruby 3.0, verify SHA256 checksum of downloaded libpg_query (#178) [@stanhu](https://github.com/stanhu)
26
+ * Verify SHA256 checksum to guard against any malicious attempts to change the archive
27
+ * Use `URI.open` to fix Ruby 3.0 support
28
+
29
+
3
30
  ## 1.2.0 2019-11-10
4
31
 
5
32
  * Reduce escaped keywords to Postgres-specific keywords, and ignore unreserved keywords
@@ -8,6 +35,7 @@
8
35
  * Note that this will lead to different output than in earlier pg_query versions,
9
36
  in some cases
10
37
 
38
+
11
39
  ## 1.1.1 2019-11-10
12
40
 
13
41
  * Deparsing improvements by [@emin100](https://github.com/emin100)
data/README.md CHANGED
@@ -151,11 +151,15 @@ to support parsing normalized queries containing `?` replacement characters.
151
151
 
152
152
  Currently tested and officially supported Ruby versions:
153
153
 
154
- * MRI 2.1
155
- * MRI 2.2
156
- * MRI 2.3
157
- * MRI 2.4
154
+ * CRuby 2.5
155
+ * CRuby 2.6
156
+ * CRuby 2.7
157
+ * CRuby 3.0
158
158
 
159
+ Not supported:
160
+
161
+ * JRuby: `pg_query` relies on a C extension, which is discouraged / not properly supported for JRuby
162
+ * TruffleRuby: GraalVM [does not support sigjmp](https://www.graalvm.org/reference-manual/llvm/NativeExecution/), which is used by the Postgres error handling code (`pg_query` uses a copy of the Postgres parser & error handling code)
159
163
 
160
164
  ## Resources
161
165
 
@@ -1,30 +1,39 @@
1
1
  # rubocop:disable Style/GlobalVars
2
2
 
3
+ require 'digest'
3
4
  require 'mkmf'
4
5
  require 'open-uri'
5
6
 
6
- LIB_PG_QUERY_TAG = '10-1.0.1'.freeze
7
+ LIB_PG_QUERY_TAG = '10-1.0.4'.freeze
8
+ LIB_PG_QUERY_SHA256SUM = '88cc90296e5fcaaebd0b360c46698b7c5badddf86f120e249ef682a820d41338'.freeze
7
9
 
8
10
  workdir = Dir.pwd
9
11
  libdir = File.join(workdir, 'libpg_query-' + LIB_PG_QUERY_TAG)
10
12
  gemdir = File.join(__dir__, '../..')
11
13
  libfile = libdir + '/libpg_query.a'
14
+ filename = File.join(workdir, 'libpg_query-' + LIB_PG_QUERY_TAG + '.tar.gz')
12
15
 
13
- unless File.exist?("#{workdir}/libpg_query.tar.gz")
14
- File.open("#{workdir}/libpg_query.tar.gz", 'wb') do |target_file|
15
- open('https://codeload.github.com/lfittl/libpg_query/tar.gz/' + LIB_PG_QUERY_TAG, 'rb') do |read_file|
16
+ unless File.exist?(filename)
17
+ File.open(filename, 'wb') do |target_file|
18
+ URI.open('https://codeload.github.com/lfittl/libpg_query/tar.gz/' + LIB_PG_QUERY_TAG, 'rb') do |read_file|
16
19
  target_file.write(read_file.read)
17
20
  end
18
21
  end
22
+
23
+ checksum = Digest::SHA256.hexdigest(File.read(filename))
24
+
25
+ if checksum != LIB_PG_QUERY_SHA256SUM
26
+ raise "SHA256 of #{filename} does not match: got #{checksum}, expected #{expected_sha256}"
27
+ end
19
28
  end
20
29
 
21
30
  unless Dir.exist?(libdir)
22
- system("tar -xzf #{workdir}/libpg_query.tar.gz") || raise('ERROR')
31
+ system("tar -xzf #{filename}") || raise('ERROR')
23
32
  end
24
33
 
25
34
  unless Dir.exist?(libfile)
26
35
  # Build libpg_query (and parts of PostgreSQL)
27
- system("cd #{libdir}; #{ENV['MAKE'] || (RUBY_PLATFORM =~ /bsd/ ? 'gmake' : 'make')} build")
36
+ system(format("cd %s; %s build", libdir, ENV['MAKE'] || (RUBY_PLATFORM =~ /bsd/ ? 'gmake' : 'make')))
28
37
  end
29
38
 
30
39
  # Copy test files (this intentionally overwrites existing files!)
@@ -92,7 +92,7 @@ class PgQuery
92
92
  when COLUMN_DEF
93
93
  deparse_columndef(node)
94
94
  when COLUMN_REF
95
- deparse_columnref(node)
95
+ deparse_columnref(node, context)
96
96
  when COMMON_TABLE_EXPR
97
97
  deparse_cte(node)
98
98
  when COMPOSITE_TYPE_STMT
@@ -127,8 +127,14 @@ class PgQuery
127
127
  deparse_delete_from(node)
128
128
  when DISCARD_STMT
129
129
  deparse_discard(node)
130
+ when DROP_ROLE
131
+ deparse_drop_role(node)
130
132
  when DROP_STMT
131
133
  deparse_drop(node)
134
+ when DROP_SUBSCRIPTION
135
+ deparse_drop_subscription(node)
136
+ when DROP_TABLESPACE
137
+ deparse_drop_tablespace(node)
132
138
  when EXPLAIN_STMT
133
139
  deparse_explain(node)
134
140
  when EXECUTE_STMT
@@ -210,6 +216,8 @@ class PgQuery
210
216
  format("'%s'", node['str'].gsub("'", "''"))
211
217
  elsif [FUNC_CALL, TYPE_NAME, :operator, :defname_as].include?(context)
212
218
  node['str']
219
+ elsif context == :excluded
220
+ node['str'].casecmp('EXCLUDED').zero? ? node['str'].upcase : deparse_identifier(node['str'], escape_always: true)
213
221
  else
214
222
  deparse_identifier(node['str'], escape_always: true)
215
223
  end
@@ -279,9 +287,9 @@ class PgQuery
279
287
  output.join(' ')
280
288
  end
281
289
 
282
- def deparse_columnref(node)
290
+ def deparse_columnref(node, context = false)
283
291
  node['fields'].map do |field|
284
- field.is_a?(String) ? '"' + field + '"' : deparse_item(field)
292
+ field.is_a?(String) ? '"' + field + '"' : deparse_item(field, context)
285
293
  end.join('.')
286
294
  end
287
295
 
@@ -302,7 +310,11 @@ class PgQuery
302
310
  def deparse_a_indirection(node)
303
311
  output = []
304
312
  arg = deparse_item(node['arg'])
305
- output << if node['arg'].key?(FUNC_CALL) || node['arg'].key?(SUB_LINK)
313
+ array_indirection = []
314
+ if node['indirection']
315
+ array_indirection = node['indirection'].select { |a| a.key?(A_INDICES) }
316
+ end
317
+ output << if node['arg'].key?(FUNC_CALL) || node['arg'].key?(A_EXPR) || (node['arg'].key?(SUB_LINK) && array_indirection.count.zero?)
306
318
  "(#{arg})."
307
319
  else
308
320
  arg
@@ -358,7 +370,7 @@ class PgQuery
358
370
 
359
371
  def deparse_object_with_args(node)
360
372
  output = []
361
- output += node['objname'].map(&method(:deparse_item))
373
+ output << deparse_item_list(node['objname']).join('.')
362
374
  unless node['args_unspecified']
363
375
  args = node.fetch('objargs', []).map(&method(:deparse_item)).join(', ')
364
376
  output << "(#{args})"
@@ -387,7 +399,9 @@ class PgQuery
387
399
  if context == :select
388
400
  [deparse_item(node['val']), deparse_identifier(node['name'])].compact.join(' AS ')
389
401
  elsif context == :update
390
- [node['name'], deparse_item(node['val'])].compact.join(' = ')
402
+ [deparse_identifier(node['name']), deparse_item(node['val'])].compact.join(' = ')
403
+ elsif context == :excluded
404
+ [deparse_identifier(node['name'], escape_always: true), deparse_item(node['val'], context)].compact.join(' = ')
391
405
  elsif node['val'].nil?
392
406
  node['name']
393
407
  else
@@ -403,10 +417,17 @@ class PgQuery
403
417
  # COUNT(*)
404
418
  args << '*' if node['agg_star']
405
419
 
406
- name = (node['funcname'].map { |n| deparse_item(n, FUNC_CALL) } - ['pg_catalog']).join('.')
407
- distinct = node['agg_distinct'] ? 'DISTINCT ' : ''
408
- output << format('%s(%s%s)', name, distinct, args.join(', '))
409
- output << format('OVER %s', deparse_item(node['over'])) if node['over']
420
+ name = (node['funcname'].map { |n| deparse_item(n, FUNC_CALL) }).join('.')
421
+ if name == 'pg_catalog.overlay'
422
+ # Note that this is a bit odd, but "OVERLAY" is a keyword on its own merit, and only accepts the
423
+ # keyword parameter style when its called as a keyword, not as a regular function (i.e. pg_catalog.overlay)
424
+ output << format('OVERLAY(%s PLACING %s FROM %s FOR %s)', args[0], args[1], args[2], args[3])
425
+ else
426
+ distinct = node['agg_distinct'] ? 'DISTINCT ' : ''
427
+ output << format('%s(%s%s)', name, distinct, args.join(', '))
428
+ output << format('FILTER (WHERE %s)', deparse_item(node['agg_filter'])) if node['agg_filter']
429
+ output << format('OVER %s', deparse_item(node['over'])) if node['over']
430
+ end
410
431
 
411
432
  output.join(' ')
412
433
  end
@@ -987,8 +1008,22 @@ class PgQuery
987
1008
 
988
1009
  def deparse_create_table_as(node)
989
1010
  output = []
990
- output << 'CREATE TEMPORARY TABLE'
1011
+ output << 'CREATE'
1012
+
1013
+ into = node['into']['IntoClause']
1014
+ persistence = relpersistence(into['rel'])
1015
+ output << persistence if persistence
1016
+
1017
+ output << 'TABLE'
1018
+
991
1019
  output << deparse_item(node['into'])
1020
+
1021
+ if into['onCommit'] > 0
1022
+ output << 'ON COMMIT'
1023
+ output << 'DELETE ROWS' if into['onCommit'] == 2
1024
+ output << 'DROP' if into['onCommit'] == 3
1025
+ end
1026
+
992
1027
  output << 'AS'
993
1028
  output << deparse_item(node['query'])
994
1029
  output.join(' ')
@@ -1020,13 +1055,17 @@ class PgQuery
1020
1055
  format('%s %s ALL (%s)', deparse_item(node['testexpr']), deparse_item(node['operName'][0], :operator), deparse_item(node['subselect']))
1021
1056
  elsif node['subLinkType'] == SUBLINK_TYPE_EXISTS
1022
1057
  format('EXISTS(%s)', deparse_item(node['subselect']))
1058
+ elsif node['subLinkType'] == SUBLINK_TYPE_ARRAY
1059
+ format('ARRAY(%s)', deparse_item(node['subselect']))
1023
1060
  else
1024
1061
  format('(%s)', deparse_item(node['subselect']))
1025
1062
  end
1026
1063
  end
1027
1064
 
1028
1065
  def deparse_rangesubselect(node)
1029
- output = '(' + deparse_item(node['subquery']) + ')'
1066
+ output = ''
1067
+ output = 'LATERAL ' if node['lateral']
1068
+ output = output + '(' + deparse_item(node['subquery']) + ')'
1030
1069
  if node['alias']
1031
1070
  output + ' ' + deparse_item(node['alias'])
1032
1071
  else
@@ -1044,10 +1083,14 @@ class PgQuery
1044
1083
  output << deparse_item(node['withClause']) if node['withClause']
1045
1084
 
1046
1085
  if node['op'] == 1
1086
+ output << '(' if node['larg']['SelectStmt']['sortClause']
1047
1087
  output << deparse_item(node['larg'])
1088
+ output << ')' if node['larg']['SelectStmt']['sortClause']
1048
1089
  output << 'UNION'
1049
1090
  output << 'ALL' if node['all']
1091
+ output << '(' if node['rarg']['SelectStmt']['sortClause']
1050
1092
  output << deparse_item(node['rarg'])
1093
+ output << ')' if node['rarg']['SelectStmt']['sortClause']
1051
1094
  output.join(' ')
1052
1095
  end
1053
1096
 
@@ -1058,8 +1101,16 @@ class PgQuery
1058
1101
  output.join(' ')
1059
1102
  end
1060
1103
 
1104
+ if node['op'] == 2
1105
+ output << deparse_item(node['larg'])
1106
+ output << 'INTERSECT'
1107
+ output << deparse_item(node['rarg'])
1108
+ output.join(' ')
1109
+ end
1110
+
1111
+ output << 'SELECT' if node[FROM_CLAUSE_FIELD] || node[TARGET_LIST_FIELD]
1112
+
1061
1113
  if node[TARGET_LIST_FIELD]
1062
- output << 'SELECT'
1063
1114
  if node['distinctClause']
1064
1115
  output << 'DISTINCT'
1065
1116
  unless node['distinctClause'].compact.empty?
@@ -1167,12 +1218,16 @@ class PgQuery
1167
1218
 
1168
1219
  if node['cols']
1169
1220
  output << '(' + node['cols'].map do |column|
1170
- deparse_item(column)
1221
+ deparse_item(column, :select)
1171
1222
  end.join(', ') + ')'
1172
1223
  end
1173
1224
 
1174
1225
  output << deparse_item(node['selectStmt'])
1175
1226
 
1227
+ if node['onConflictClause']
1228
+ output << deparse_insert_onconflict(node['onConflictClause'][ON_CONFLICT_CLAUSE])
1229
+ end
1230
+
1176
1231
  if node['returningList']
1177
1232
  output << 'RETURNING'
1178
1233
  output << node['returningList'].map do |column|
@@ -1183,6 +1238,40 @@ class PgQuery
1183
1238
  output.join(' ')
1184
1239
  end
1185
1240
 
1241
+ def deparse_insert_onconflict(node) # rubocop:disable Metrics/CyclomaticComplexity
1242
+ output = []
1243
+ output << 'ON CONFLICT'
1244
+
1245
+ infer = node['infer']['InferClause']
1246
+ if infer['indexElems']
1247
+ output << '(' + infer['indexElems'].map do |column|
1248
+ '"' + column['IndexElem']['name'] + '"'
1249
+ end.join(', ') + ')'
1250
+ end
1251
+
1252
+ if infer['conname']
1253
+ output << 'ON CONSTRAINT'
1254
+ output << deparse_identifier(infer['conname'], escape_always: true)
1255
+ end
1256
+
1257
+ output << 'DO UPDATE' if node['action'] == 2
1258
+
1259
+ if node[TARGET_LIST_FIELD]
1260
+ output << 'SET ' + node[TARGET_LIST_FIELD].map do |column|
1261
+ deparse_item(column, :excluded)
1262
+ end.join(', ')
1263
+ end
1264
+
1265
+ if infer['whereClause']
1266
+ output << 'WHERE'
1267
+ output << deparse_item(infer['whereClause'])
1268
+ end
1269
+
1270
+ output << 'DO NOTHING' if node['action'] == 1
1271
+
1272
+ output.join(' ')
1273
+ end
1274
+
1186
1275
  def deparse_update(node)
1187
1276
  output = []
1188
1277
  output << deparse_item(node['withClause']) if node['withClause']
@@ -1198,6 +1287,13 @@ class PgQuery
1198
1287
  output << columns.join(', ')
1199
1288
  end
1200
1289
 
1290
+ if node[FROM_CLAUSE_FIELD]
1291
+ output << 'FROM'
1292
+ output << node[FROM_CLAUSE_FIELD].map do |item|
1293
+ deparse_item(item)
1294
+ end.join(', ')
1295
+ end
1296
+
1201
1297
  if node['whereClause']
1202
1298
  output << 'WHERE'
1203
1299
  output << deparse_item(node['whereClause'])
@@ -1214,13 +1310,34 @@ class PgQuery
1214
1310
  output.join(' ')
1215
1311
  end
1216
1312
 
1313
+ # Builds a properly-qualified reference to a built-in Postgres type.
1314
+ #
1315
+ # Inspired by SystemTypeName in Postgres' gram.y, but without node name
1316
+ # and locations, to simplify comparison.
1317
+ def make_system_type_name(name)
1318
+ {
1319
+ 'names' => [
1320
+ { 'String' => { 'str' => 'pg_catalog' } },
1321
+ { 'String' => { 'str' => name } }
1322
+ ],
1323
+ 'typemod' => -1
1324
+ }
1325
+ end
1326
+
1327
+ def make_string(str)
1328
+ { 'String' => { 'str' => str } }
1329
+ end
1330
+
1217
1331
  def deparse_typecast(node)
1218
- if deparse_item(node['typeName']) == 'boolean'
1219
- deparse_item(node['arg']) == "'t'" ? 'true' : 'false'
1220
- else
1221
- context = true if node['arg']['A_Expr']
1222
- deparse_item(node['arg'], context) + '::' + deparse_typename(node['typeName'][TYPE_NAME])
1332
+ # Handle "bool" or "false" in the statement, which is represented as a typecast
1333
+ # (other boolean casts should be represented as a cast, i.e. don't need special handling)
1334
+ if node['arg'][A_CONST] && node['typeName'][TYPE_NAME].slice('names', 'typemod') == make_system_type_name('bool')
1335
+ return 'true' if node['arg'][A_CONST]['val'] == make_string('t')
1336
+ return 'false' if node['arg'][A_CONST]['val'] == make_string('f')
1223
1337
  end
1338
+
1339
+ context = true if node['arg']['A_Expr']
1340
+ deparse_item(node['arg'], context) + '::' + deparse_typename(node['typeName'][TYPE_NAME])
1224
1341
  end
1225
1342
 
1226
1343
  def deparse_typename(node)
@@ -1446,20 +1563,98 @@ class PgQuery
1446
1563
 
1447
1564
  def deparse_drop(node) # rubocop:disable Metrics/CyclomaticComplexity
1448
1565
  output = ['DROP']
1449
- output << 'TABLE' if node['removeType'] == OBJECT_TYPE_TABLE
1566
+
1567
+ output << 'ACCESS METHOD' if node['removeType'] == OBJECT_TYPE_ACCESS_METHOD
1568
+ output << 'AGGREGATE' if node['removeType'] == OBJECT_TYPE_AGGREGATE
1569
+ output << 'CAST' if node['removeType'] == OBJECT_TYPE_CAST
1570
+ output << 'COLLATION' if node['removeType'] == OBJECT_TYPE_COLLATION
1571
+ output << 'DOMAIN' if node['removeType'] == OBJECT_TYPE_DOMAIN
1572
+ output << 'CONVERSION' if node['removeType'] == OBJECT_TYPE_CONVERSION
1573
+ output << 'EVENT TRIGGER' if node['removeType'] == OBJECT_TYPE_EVENT_TRIGGER
1574
+ output << 'EXTENSION' if node['removeType'] == OBJECT_TYPE_EXTENSION
1575
+ output << 'FOREIGN DATA WRAPPER' if node['removeType'] == OBJECT_TYPE_FDW
1576
+ output << 'FOREIGN TABLE' if node['removeType'] == OBJECT_TYPE_FOREIGN_TABLE
1577
+ output << 'FUNCTION' if node['removeType'] == OBJECT_TYPE_FUNCTION
1578
+ output << 'INDEX' if node['removeType'] == OBJECT_TYPE_INDEX
1579
+ output << 'MATERIALIZED VIEW' if node['removeType'] == OBJECT_TYPE_MATVIEW
1580
+ output << 'OPERATOR CLASS' if node['removeType'] == OBJECT_TYPE_OPCLASS
1581
+ output << 'OPERATOR FAMILY' if node['removeType'] == OBJECT_TYPE_OPFAMILY
1582
+ output << 'POLICY' if node['removeType'] == OBJECT_TYPE_POLICY
1583
+ output << 'PUBLICATION' if node['removeType'] == OBJECT_TYPE_PUBLICATION
1584
+ output << 'RULE' if node['removeType'] == OBJECT_TYPE_RULE
1450
1585
  output << 'SCHEMA' if node['removeType'] == OBJECT_TYPE_SCHEMA
1586
+ output << 'SERVER' if node['removeType'] == OBJECT_TYPE_FOREIGN_SERVER
1587
+ output << 'SEQUENCE' if node['removeType'] == OBJECT_TYPE_SEQUENCE
1588
+ output << 'STATISTICS' if node['removeType'] == OBJECT_TYPE_STATISTIC_EXT
1589
+ output << 'TABLE' if node['removeType'] == OBJECT_TYPE_TABLE
1590
+ output << 'TRANSFORM' if node['removeType'] == OBJECT_TYPE_TRANSFORM
1591
+ output << 'TRIGGER' if node['removeType'] == OBJECT_TYPE_TRIGGER
1592
+ output << 'TEXT SEARCH CONFIGURATION' if node['removeType'] == OBJECT_TYPE_TSCONFIGURATION
1593
+ output << 'TEXT SEARCH DICTIONARY' if node['removeType'] == OBJECT_TYPE_TSDICTIONARY
1594
+ output << 'TEXT SEARCH PARSER' if node['removeType'] == OBJECT_TYPE_TSPARSER
1595
+ output << 'TEXT SEARCH TEMPLATE' if node['removeType'] == OBJECT_TYPE_TSTEMPLATE
1596
+ output << 'TYPE' if node['removeType'] == OBJECT_TYPE_TYPE
1597
+ output << 'VIEW' if node['removeType'] == OBJECT_TYPE_VIEW
1598
+
1451
1599
  output << 'CONCURRENTLY' if node['concurrent']
1452
1600
  output << 'IF EXISTS' if node['missing_ok']
1453
1601
 
1454
1602
  objects = node['objects']
1455
1603
  objects = [objects] unless objects[0].is_a?(Array)
1456
- output << objects.map { |list| list.map { |object| deparse_item(object) } }.join(', ')
1604
+ case node['removeType']
1605
+ when OBJECT_TYPE_CAST
1606
+ object = objects[0]
1607
+ output << format('(%s)', deparse_item_list(object).join(' AS '))
1608
+ when OBJECT_TYPE_FUNCTION, OBJECT_TYPE_AGGREGATE, OBJECT_TYPE_SCHEMA, OBJECT_TYPE_EXTENSION
1609
+ output << objects.map { |list| list.map { |object_line| deparse_item(object_line) } }.join(', ')
1610
+ when OBJECT_TYPE_OPFAMILY, OBJECT_TYPE_OPCLASS
1611
+ object = objects[0]
1612
+ output << deparse_item(object[1]) if object.length == 2
1613
+ output << deparse_item_list(object[1..-1]).join('.') if object.length == 3
1614
+ output << 'USING'
1615
+ output << deparse_item(object[0])
1616
+ when OBJECT_TYPE_TRIGGER, OBJECT_TYPE_RULE, OBJECT_TYPE_POLICY
1617
+ object = objects[0]
1618
+ output << deparse_item(object[-1])
1619
+ output << 'ON'
1620
+ output << deparse_item(object[0]) if object.length == 2
1621
+ output << deparse_item_list(object[0..1]).join('.') if object.length == 3
1622
+ when OBJECT_TYPE_TRANSFORM
1623
+ object = objects[0]
1624
+ output << 'FOR'
1625
+ output << deparse_item(object[0])
1626
+ output << 'LANGUAGE'
1627
+ output << deparse_item(object[1])
1628
+ else
1629
+ output << objects.map { |list| list.map { |object_line| deparse_item(object_line) }.join('.') }.join(', ')
1630
+ end
1457
1631
 
1458
1632
  output << 'CASCADE' if node['behavior'] == 1
1459
1633
 
1460
1634
  output.join(' ')
1461
1635
  end
1462
1636
 
1637
+ def deparse_drop_role(node)
1638
+ output = ['DROP ROLE']
1639
+ output << 'IF EXISTS' if node['missing_ok']
1640
+ output << node['roles'].map { |role| deparse_identifier(role['RoleSpec']['rolename']) }.join(', ')
1641
+ output.join(' ')
1642
+ end
1643
+
1644
+ def deparse_drop_subscription(node)
1645
+ output = ['DROP SUBSCRIPTION']
1646
+ output << 'IF EXISTS' if node['missing_ok']
1647
+ output << deparse_identifier(node['subname'])
1648
+ output.join(' ')
1649
+ end
1650
+
1651
+ def deparse_drop_tablespace(node)
1652
+ output = ['DROP TABLESPACE']
1653
+ output << 'IF EXISTS' if node['missing_ok']
1654
+ output << node['tablespacename']
1655
+ output.join(' ')
1656
+ end
1657
+
1463
1658
  def deparse_explain(node)
1464
1659
  output = ['EXPLAIN']
1465
1660
  options = node.fetch('options', []).map { |option| option['DefElem']['defname'].upcase }
@@ -44,6 +44,9 @@ class PgQuery
44
44
  DISCARD_STMT = 'DiscardStmt'.freeze
45
45
  DO_STMT = 'DoStmt'.freeze
46
46
  DROP_STMT = 'DropStmt'.freeze
47
+ DROP_SUBSCRIPTION = 'DropSubscriptionStmt'.freeze
48
+ DROP_TABLESPACE = 'DropTableSpaceStmt'.freeze
49
+ DROP_ROLE = 'DropRoleStmt'.freeze
47
50
  EXECUTE_STMT = 'ExecuteStmt'.freeze
48
51
  EXPLAIN_STMT = 'ExplainStmt'.freeze
49
52
  FETCH_STMT = 'FetchStmt'.freeze
@@ -65,6 +68,7 @@ class PgQuery
65
68
  NULL_TEST = 'NullTest'.freeze
66
69
  OBJECT_WITH_ARGS = 'ObjectWithArgs'.freeze
67
70
  OID_LIST = 'OidList'.freeze
71
+ ON_CONFLICT_CLAUSE = 'OnConflictClause'.freeze
68
72
  PARAM_REF = 'ParamRef'.freeze
69
73
  PREPARE_STMT = 'PrepareStmt'.freeze
70
74
  RANGE_FUNCTION = 'RangeFunction'.freeze
@@ -124,6 +124,9 @@ class PgQuery
124
124
  if statement[CREATE_TABLE_AS_STMT]['into'] && statement[CREATE_TABLE_AS_STMT]['into'][INTO_CLAUSE]['rel']
125
125
  from_clause_items << { item: statement[CREATE_TABLE_AS_STMT]['into'][INTO_CLAUSE]['rel'], type: :ddl }
126
126
  end
127
+ if statement[CREATE_TABLE_AS_STMT]['query']
128
+ statements << statement[CREATE_TABLE_AS_STMT]['query']
129
+ end
127
130
  when TRUNCATE_STMT
128
131
  from_clause_items += statement.values[0]['relations'].map { |r| { item: r, type: :ddl } }
129
132
  when VIEW_STMT
@@ -1,3 +1,3 @@
1
1
  class PgQuery
2
- VERSION = '1.2.0'.freeze
2
+ VERSION = '1.3.0'.freeze
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: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lukas Fittl
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-11 00:00:00.000000000 Z
11
+ date: 2020-12-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -123,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
125
  requirements: []
126
- rubygems_version: 3.0.6
126
+ rubygems_version: 3.0.3
127
127
  signing_key:
128
128
  specification_version: 4
129
129
  summary: PostgreSQL query parsing and normalization library