pg_query 1.2.0 → 1.3.0

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
  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