pg_query 1.0.1 → 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
- SHA1:
3
- metadata.gz: 4a5be9c84a9ac788a7845fc900696e4c7df77bb3
4
- data.tar.gz: e77d94ae2f8fb120c21efabf2faed451ad4e0966
2
+ SHA256:
3
+ metadata.gz: f3d7de9759711a5e173db293a6120a15ee6ab1f0f4c5804e7f68aaba99ec8ff8
4
+ data.tar.gz: 974e3fb0b709021a771803e52de2a8ca19309a679d81c1ee2a3deb105f297769
5
5
  SHA512:
6
- metadata.gz: 1f2f3fe149dfb181c7023fd243fa66f95f7613fffdb174c2968d947d37ade1d3dd15be599c27aa83ea67f8a289c8bff5e249c94902cb9fd61ca0306f61259927
7
- data.tar.gz: ae8f01f2afc0172fad220c3119fb48806893b8e49e5e35ab58448e2342161933487a5ba9cbae8d95c656c68f453537458b450529c72c12cc77119327494e7aa1
6
+ metadata.gz: 3c13b36c353e6de6aaaba2b6428be7e21c933bf8ef159212cdd64ee4a765f8a664defe735f2f3e94ef6441f3403d7be6fbf19ca4d6b98e07a6b42a68a635238a
7
+ data.tar.gz: cc742d80bdf934c89e1faa1dd6e6560764339ecabb6fe2af67fd219b2a71b4fb6756e649ddc237d995413a90c6b036d33fe8492eb91030d8a891f970ca10510a
@@ -1,5 +1,102 @@
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
+
30
+ ## 1.2.0 2019-11-10
31
+
32
+ * Reduce escaped keywords to Postgres-specific keywords, and ignore unreserved keywords
33
+ * This matches the behaviour of Postgres' quote_identifier function, and avoids problems
34
+ when doing text comparisons with output involving that function
35
+ * Note that this will lead to different output than in earlier pg_query versions,
36
+ in some cases
37
+
38
+
39
+ ## 1.1.1 2019-11-10
40
+
41
+ * Deparsing improvements by [@emin100](https://github.com/emin100)
42
+ * Deparse ILIKE, COLLATE and DISCARD (#133)
43
+ * CREATE CAST (#136)
44
+ * CREATE SCHEMA (#136)
45
+ * UNION, UNION ALL and EXCEPT in SELECT queries (#136)
46
+ * CREATE DOMAIN (#145)
47
+ * Subquery indirection (#157)
48
+ * Fix Type Cast Parentheses Problem (#152)
49
+ * SELECT INTO (#151)
50
+ * SET DEFAULT in INSERT INTO (#154)
51
+ * REVOKE (#155)
52
+ * PREPARE and EXECUTE (#148)
53
+ * INSERT INTO ... RETURNING (#153)
54
+ * Fix Alter .. RENAME SQL (#146)
55
+ * Deparsing improvements by [@herwinw](https://github.com/herwinw)
56
+ * Fix subquery in COPY in deparse (#112)
57
+ * Function call indirection (#116)
58
+ * Function without parameters (#117)
59
+ * CREATE AGGREGATE
60
+ * CREATE OPERATOR
61
+ * CREATE TYPE
62
+ * GRANT statements
63
+ * DROP SCHEMA
64
+ * Deparsing improvements by [@akiellor](https://github.com/akiellor)
65
+ * Named window functions (#150)
66
+ * Deparsing improvements by [@himanshu](https://github.com/himanshu)
67
+ * Arguments in custom types (#143)
68
+ * Use "double precision" instead of "double" type name (#139)
69
+ * Use explicit -z flag to support OpenBSD tar (#134) [@sirn](https://github.com/sirn)
70
+ * Add Ruby 2.6 to Travis tests
71
+ * Escape identifiers in more cases, if necessary
72
+
73
+
74
+ ## 1.1.0 2018-10-04
75
+
76
+ * Deparsing improvements by [@herwinw](https://github.com/herwinw)
77
+ * Add NULLS FIRST/LAST to ORDER BY [#95](https://github.com/lfittl/pg_query/pull/95)
78
+ * VACUUM [#97](https://github.com/lfittl/pg_query/pull/97)
79
+ * UPDATE with multiple columns [#99](https://github.com/lfittl/pg_query/pull/99)
80
+ * DISTINCT ON [#101](https://github.com/lfittl/pg_query/pull/101)
81
+ * CREATE TABLE AS [#102](https://github.com/lfittl/pg_query/pull/102)
82
+ * SQL value functions [#103](https://github.com/lfittl/pg_query/pull/103)
83
+ * LOCK [#105](https://github.com/lfittl/pg_query/pull/105)
84
+ * EXPLAIN [#107](https://github.com/lfittl/pg_query/pull/107)
85
+ * COPY [#108](https://github.com/lfittl/pg_query/pull/108)
86
+ * DO [#109](https://github.com/lfittl/pg_query/pull/109)
87
+ * Ignore pg_query.so in git checkout [#110](https://github.com/lfittl/pg_query/pull/110) [@herwinw](https://github.com/herwinw)
88
+ * Prefer __dir__ over File.dirname(__FILE__) [#110](https://github.com/lfittl/pg_query/pull/104) [@herwinw](https://github.com/herwinw)
89
+
90
+
91
+ ## 1.0.2 2018-04-11
92
+
93
+ * Deparsing improvements
94
+ * SELECT DISTINCT clause [#77](https://github.com/lfittl/pg_query/pull/77) [@Papierkorb](https://github.com/Papierkorb)
95
+ * "CASE expr WHEN ... END" clause [#78](https://github.com/lfittl/pg_query/pull/78) [@Papierkorb](https://github.com/Papierkorb)
96
+ * LEFT/RIGHT/FULL/NATURAL JOIN [#79](https://github.com/lfittl/pg_query/pull/79) [@Papierkorb](https://github.com/Papierkorb)
97
+ * SELECT that includes schema name [#80](https://github.com/lfittl/pg_query/pull/80) [@jcsjcs](https://github.com/jcsjcs)
98
+
99
+
3
100
  ## 1.0.1 2018-02-02
4
101
 
5
102
  * Parse CTEs and nested selects in INSERT/UPDATE [#76](https://github.com/lfittl/pg_query/pull/76) [@jcoleman](https://github.com/jcoleman)
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
 
data/Rakefile CHANGED
@@ -17,7 +17,7 @@ task test: :spec
17
17
  task lint: :rubocop
18
18
 
19
19
  task :clean do
20
- FileUtils.rm_rf File.join(File.dirname(__FILE__), 'tmp/')
21
- FileUtils.rm_f Dir.glob(File.join(File.dirname(__FILE__), 'ext/pg_query/*.o'))
22
- FileUtils.rm_f File.join(File.dirname(__FILE__), 'lib/pg_query/pg_query.bundle')
20
+ FileUtils.rm_rf File.join(__dir__, 'tmp/')
21
+ FileUtils.rm_f Dir.glob(File.join(__dir__, 'ext/pg_query/*.o'))
22
+ FileUtils.rm_f File.join(__dir__, 'lib/pg_query/pg_query.bundle')
23
23
  end
@@ -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
- gemdir = File.join(File.dirname(__FILE__), '../..')
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 -xf #{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!)
@@ -36,7 +45,7 @@ $LOCAL_LIBS << '-lpg_query'
36
45
  $LIBPATH << libdir
37
46
  $CFLAGS << " -I #{libdir} -O3 -Wall -fno-strict-aliasing -fwrapv -g"
38
47
 
39
- SYMFILE = File.join(File.dirname(__FILE__), 'pg_query_ruby.sym')
48
+ SYMFILE = File.join(__dir__, 'pg_query_ruby.sym')
40
49
  if RUBY_PLATFORM =~ /darwin/
41
50
  $DLDFLAGS << " -Wl,-exported_symbols_list #{SYMFILE}" unless defined?(::Rubinius)
42
51
  else
@@ -1,5 +1,8 @@
1
- require_relative 'deparse/interval'
2
1
  require_relative 'deparse/alter_table'
2
+ require_relative 'deparse/rename'
3
+ require_relative 'deparse/interval'
4
+ require_relative 'deparse/keywords'
5
+
3
6
  class PgQuery
4
7
  # Reconstruct all of the parsed queries into their original form
5
8
  def deparse(tree = @tree)
@@ -32,10 +35,14 @@ class PgQuery
32
35
  case node['kind']
33
36
  when AEXPR_OP
34
37
  deparse_aexpr(node, context)
38
+ when AEXPR_OP_ALL
39
+ deparse_aexpr_all(node)
35
40
  when AEXPR_OP_ANY
36
41
  deparse_aexpr_any(node)
37
42
  when AEXPR_IN
38
43
  deparse_aexpr_in(node)
44
+ when AEXPR_ILIKE
45
+ deparse_aexpr_ilike(node)
39
46
  when CONSTR_TYPE_FOREIGN
40
47
  deparse_aexpr_like(node)
41
48
  when AEXPR_BETWEEN, AEXPR_NOT_BETWEEN, AEXPR_BETWEEN_SYM, AEXPR_NOT_BETWEEN_SYM
@@ -45,6 +52,8 @@ class PgQuery
45
52
  else
46
53
  raise format("Can't deparse: %s: %s", type, node.inspect)
47
54
  end
55
+ when ACCESS_PRIV
56
+ deparse_access_priv(node)
48
57
  when ALIAS
49
58
  deparse_alias(node)
50
59
  when ALTER_TABLE_STMT
@@ -78,38 +87,82 @@ class PgQuery
78
87
  deparse_case(node)
79
88
  when COALESCE_EXPR
80
89
  deparse_coalesce(node)
90
+ when COLLATE_CLAUSE
91
+ deparse_collate(node)
81
92
  when COLUMN_DEF
82
93
  deparse_columndef(node)
83
94
  when COLUMN_REF
84
- deparse_columnref(node)
95
+ deparse_columnref(node, context)
85
96
  when COMMON_TABLE_EXPR
86
97
  deparse_cte(node)
98
+ when COMPOSITE_TYPE_STMT
99
+ deparse_composite_type(node)
87
100
  when CONSTRAINT
88
101
  deparse_constraint(node)
102
+ when COPY_STMT
103
+ deparse_copy(node)
104
+ when CREATE_CAST_STMT
105
+ deparse_create_cast(node)
106
+ when CREATE_DOMAIN_STMT
107
+ deparse_create_domain(node)
108
+ when CREATE_ENUM_STMT
109
+ deparse_create_enum(node)
89
110
  when CREATE_FUNCTION_STMT
90
111
  deparse_create_function(node)
112
+ when CREATE_RANGE_STMT
113
+ deparse_create_range(node)
114
+ when CREATE_SCHEMA_STMT
115
+ deparse_create_schema(node)
91
116
  when CREATE_STMT
92
117
  deparse_create_table(node)
118
+ when CREATE_TABLE_AS_STMT
119
+ deparse_create_table_as(node)
120
+ when INTO_CLAUSE
121
+ deparse_into_clause(node)
93
122
  when DEF_ELEM
94
123
  deparse_defelem(node)
124
+ when DEFINE_STMT
125
+ deparse_define_stmt(node)
95
126
  when DELETE_STMT
96
127
  deparse_delete_from(node)
128
+ when DISCARD_STMT
129
+ deparse_discard(node)
130
+ when DROP_ROLE
131
+ deparse_drop_role(node)
97
132
  when DROP_STMT
98
133
  deparse_drop(node)
134
+ when DROP_SUBSCRIPTION
135
+ deparse_drop_subscription(node)
136
+ when DROP_TABLESPACE
137
+ deparse_drop_tablespace(node)
138
+ when EXPLAIN_STMT
139
+ deparse_explain(node)
140
+ when EXECUTE_STMT
141
+ deparse_execute(node)
99
142
  when FUNC_CALL
100
143
  deparse_funccall(node)
101
144
  when FUNCTION_PARAMETER
102
145
  deparse_functionparameter(node)
146
+ when GRANT_ROLE_STMT
147
+ deparse_grant_role(node)
148
+ when GRANT_STMT
149
+ deparse_grant(node)
103
150
  when INSERT_STMT
104
151
  deparse_insert_into(node)
105
152
  when JOIN_EXPR
106
153
  deparse_joinexpr(node)
154
+ when LOCK_STMT
155
+ deparse_lock(node)
107
156
  when LOCKING_CLAUSE
108
157
  deparse_lockingclause(node)
109
158
  when NULL_TEST
110
159
  deparse_nulltest(node)
160
+ when OBJECT_WITH_ARGS
161
+ deparse_object_with_args(node)
111
162
  when PARAM_REF
112
163
  deparse_paramref(node)
164
+ when PREPARE_STMT
165
+ deparse_prepare(node)
113
166
  when RANGE_FUNCTION
114
167
  deparse_range_function(node)
115
168
  when RANGE_SUBSELECT
@@ -122,10 +175,14 @@ class PgQuery
122
175
  deparse_renamestmt(node)
123
176
  when RES_TARGET
124
177
  deparse_restarget(node, context)
178
+ when ROLE_SPEC
179
+ deparse_role_spec(node)
125
180
  when ROW_EXPR
126
181
  deparse_row(node)
127
182
  when SELECT_STMT
128
183
  deparse_select(node)
184
+ when SQL_VALUE_FUNCTION
185
+ deparse_sql_value_function(node)
129
186
  when SORT_BY
130
187
  deparse_sortby(node)
131
188
  when SUB_LINK
@@ -148,13 +205,21 @@ class PgQuery
148
205
  deparse_viewstmt(node)
149
206
  when VARIABLE_SET_STMT
150
207
  deparse_variable_set_stmt(node)
208
+ when VACUUM_STMT
209
+ deparse_vacuum_stmt(node)
210
+ when DO_STMT
211
+ deparse_do_stmt(node)
212
+ when SET_TO_DEFAULT
213
+ 'DEFAULT'
151
214
  when STRING
152
215
  if context == A_CONST
153
216
  format("'%s'", node['str'].gsub("'", "''"))
154
217
  elsif [FUNC_CALL, TYPE_NAME, :operator, :defname_as].include?(context)
155
218
  node['str']
219
+ elsif context == :excluded
220
+ node['str'].casecmp('EXCLUDED').zero? ? node['str'].upcase : deparse_identifier(node['str'], escape_always: true)
156
221
  else
157
- format('"%s"', node['str'].gsub('"', '""'))
222
+ deparse_identifier(node['str'], escape_always: true)
158
223
  end
159
224
  when INTEGER
160
225
  node['ival'].to_s
@@ -171,10 +236,20 @@ class PgQuery
171
236
  nodes.map { |n| deparse_item(n, context) }
172
237
  end
173
238
 
239
+ def deparse_identifier(ident, escape_always: false)
240
+ return if ident.nil?
241
+ if escape_always || !ident[/^\w+$/] || KEYWORDS.include?(ident.upcase)
242
+ format('"%s"', ident.gsub('"', '""'))
243
+ else
244
+ ident
245
+ end
246
+ end
247
+
174
248
  def deparse_rangevar(node)
175
249
  output = []
176
250
  output << 'ONLY' unless node['inh']
177
- output << '"' + node['relname'] + '"'
251
+ schema = node['schemaname'] ? '"' + node['schemaname'] + '".' : ''
252
+ output << schema + '"' + node['relname'] + '"'
178
253
  output << deparse_item(node['alias']) if node['alias']
179
254
  output.join(' ')
180
255
  end
@@ -183,30 +258,43 @@ class PgQuery
183
258
  deparse_item(node[STMT_FIELD])
184
259
  end
185
260
 
186
- def deparse_renamestmt(node)
187
- output = []
188
-
189
- case node['renameType']
190
- when OBJECT_TYPE_TABLE
191
- output << 'ALTER TABLE'
192
- output << deparse_item(node['relation'])
193
- output << 'RENAME TO'
194
- output << node['newname']
261
+ def deparse_renamestmt_decision(node, type)
262
+ if node[type]
263
+ if node[type].is_a?(String)
264
+ deparse_identifier(node[type])
265
+ elsif node[type].is_a?(Array)
266
+ deparse_item_list(node[type])
267
+ elsif node[type].is_a?(Object)
268
+ deparse_item(node[type])
269
+ end
195
270
  else
196
- raise format("Can't deparse: %s", node.inspect)
271
+ type
197
272
  end
273
+ end
274
+
275
+ def deparse_renamestmt(node)
276
+ output = []
277
+ output << 'ALTER'
278
+ command, type, options, type2, options2 = Rename.commands(node)
198
279
 
280
+ output << command if command
281
+ output << deparse_renamestmt_decision(node, type)
282
+ output << options if options
283
+ output << deparse_renamestmt_decision(node, type2) if type2
284
+ output << deparse_renamestmt_decision(node, options2) if options2
285
+ output << 'TO'
286
+ output << deparse_identifier(node['newname'], escape_always: true)
199
287
  output.join(' ')
200
288
  end
201
289
 
202
- def deparse_columnref(node)
290
+ def deparse_columnref(node, context = false)
203
291
  node['fields'].map do |field|
204
- field.is_a?(String) ? '"' + field + '"' : deparse_item(field)
292
+ field.is_a?(String) ? '"' + field + '"' : deparse_item(field, context)
205
293
  end.join('.')
206
294
  end
207
295
 
208
296
  def deparse_a_arrayexp(node)
209
- 'ARRAY[' + node['elements'].map do |element|
297
+ 'ARRAY[' + (node['elements'] || []).map do |element|
210
298
  deparse_item(element)
211
299
  end.join(', ') + ']'
212
300
  end
@@ -220,7 +308,17 @@ class PgQuery
220
308
  end
221
309
 
222
310
  def deparse_a_indirection(node)
223
- output = [deparse_item(node['arg'])]
311
+ output = []
312
+ arg = deparse_item(node['arg'])
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?)
318
+ "(#{arg})."
319
+ else
320
+ arg
321
+ end
224
322
  node['indirection'].each do |subnode|
225
323
  output << deparse_item(subnode)
226
324
  end
@@ -236,13 +334,16 @@ class PgQuery
236
334
  if node['colnames']
237
335
  name + '(' + deparse_item_list(node['colnames']).join(', ') + ')'
238
336
  else
239
- name
337
+ deparse_identifier(name)
240
338
  end
241
339
  end
242
340
 
243
341
  def deparse_alter_table(node)
244
342
  output = []
245
- output << 'ALTER TABLE'
343
+ output << 'ALTER'
344
+
345
+ output << 'TABLE' if node['relkind'] == OBJECT_TYPE_TABLE
346
+ output << 'VIEW' if node['relkind'] == OBJECT_TYPE_VIEW
246
347
 
247
348
  output << deparse_item(node['relation'])
248
349
 
@@ -267,6 +368,16 @@ class PgQuery
267
368
  output.compact.join(' ')
268
369
  end
269
370
 
371
+ def deparse_object_with_args(node)
372
+ output = []
373
+ output << deparse_item_list(node['objname']).join('.')
374
+ unless node['args_unspecified']
375
+ args = node.fetch('objargs', []).map(&method(:deparse_item)).join(', ')
376
+ output << "(#{args})"
377
+ end
378
+ output.join('')
379
+ end
380
+
270
381
  def deparse_paramref(node)
271
382
  if node['number'].nil?
272
383
  '?'
@@ -275,11 +386,22 @@ class PgQuery
275
386
  end
276
387
  end
277
388
 
389
+ def deparse_prepare(node)
390
+ output = ['PREPARE']
391
+ output << deparse_identifier(node['name'])
392
+ output << "(#{deparse_item_list(node['argtypes']).join(', ')})" if node['argtypes']
393
+ output << 'AS'
394
+ output << deparse_item(node['query'])
395
+ output.join(' ')
396
+ end
397
+
278
398
  def deparse_restarget(node, context)
279
399
  if context == :select
280
- [deparse_item(node['val']), node['name']].compact.join(' AS ')
400
+ [deparse_item(node['val']), deparse_identifier(node['name'])].compact.join(' AS ')
281
401
  elsif context == :update
282
- [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(' = ')
283
405
  elsif node['val'].nil?
284
406
  node['name']
285
407
  else
@@ -295,15 +417,24 @@ class PgQuery
295
417
  # COUNT(*)
296
418
  args << '*' if node['agg_star']
297
419
 
298
- name = (node['funcname'].map { |n| deparse_item(n, FUNC_CALL) } - ['pg_catalog']).join('.')
299
- distinct = node['agg_distinct'] ? 'DISTINCT ' : ''
300
- output << format('%s(%s%s)', name, distinct, args.join(', '))
301
- 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
302
431
 
303
432
  output.join(' ')
304
433
  end
305
434
 
306
435
  def deparse_windowdef(node)
436
+ return deparse_identifier(node['name']) if node['name']
437
+
307
438
  output = []
308
439
 
309
440
  if node['partitionClause']
@@ -320,13 +451,84 @@ class PgQuery
320
451
  end.join(', ')
321
452
  end
322
453
 
323
- output.join(' ')
454
+ format('(%s)', output.join(' '))
324
455
  end
325
456
 
326
457
  def deparse_functionparameter(node)
327
458
  deparse_item(node['argType'])
328
459
  end
329
460
 
461
+ def deparse_grant_role(node)
462
+ output = []
463
+ output << ['GRANT'] if node['is_grant']
464
+ output << ['REVOKE'] unless node['is_grant']
465
+ output << node['granted_roles'].map(&method(:deparse_item)).join(', ')
466
+ output << ['TO'] if node['is_grant']
467
+ output << ['FROM'] unless node['is_grant']
468
+ output << node['grantee_roles'].map(&method(:deparse_item)).join(', ')
469
+ output << 'WITH ADMIN OPTION' if node['admin_opt']
470
+ output.join(' ')
471
+ end
472
+
473
+ def deparse_grant(node) # rubocop:disable Metrics/CyclomaticComplexity
474
+ objtype, allow_all = deparse_grant_objtype(node)
475
+ output = []
476
+ output << ['GRANT'] if node['is_grant']
477
+ output << ['REVOKE'] unless node['is_grant']
478
+ output << if node.key?('privileges')
479
+ node['privileges'].map(&method(:deparse_item)).join(', ')
480
+ else
481
+ 'ALL'
482
+ end
483
+ output << 'ON'
484
+ objects = node['objects']
485
+ objects = objects[0] if %w[DOMAIN TYPE].include?(objtype)
486
+ objects = objects.map do |object|
487
+ deparsed = deparse_item(object)
488
+ if object.key?(RANGE_VAR) || object.key?(OBJECT_WITH_ARGS) || !allow_all
489
+ objtype == 'TABLE' ? deparsed : "#{objtype} #{deparsed}"
490
+ else
491
+ "ALL #{objtype}S IN SCHEMA #{deparsed}"
492
+ end
493
+ end
494
+ output << objects.join(', ')
495
+ output << ['TO'] if node['is_grant']
496
+ output << ['FROM'] unless node['is_grant']
497
+ output << node['grantees'].map(&method(:deparse_item)).join(', ')
498
+ output << 'WITH GRANT OPTION' if node['grant_option']
499
+ output.join(' ')
500
+ end
501
+
502
+ def deparse_grant_objtype(node)
503
+ {
504
+ 1 => ['TABLE', true],
505
+ 2 => ['SEQUENCE', true],
506
+ 3 => ['DATABASE', false],
507
+ 4 => ['DOMAIN', false],
508
+ 5 => ['FOREIGN DATA WRAPPER', false],
509
+ 6 => ['FOREIGN SERVER', false],
510
+ 7 => ['FUNCTION', true],
511
+ 8 => ['LANGUAGE', false],
512
+ 9 => ['LARGE OBJECT', false],
513
+ 10 => ['SCHEMA', false],
514
+ 11 => ['TABLESPACE', false],
515
+ 12 => ['TYPE', false]
516
+ }.fetch(node['objtype'])
517
+ end
518
+
519
+ def deparse_access_priv(node)
520
+ output = [node['priv_name']]
521
+ output << "(#{node['cols'].map(&method(:deparse_item)).join(', ')})" if node.key?('cols')
522
+ output.join(' ')
523
+ end
524
+
525
+ def deparse_role_spec(node)
526
+ return 'CURRENT_USER' if node['roletype'] == 1
527
+ return 'SESSION_USER' if node['roletype'] == 2
528
+ return 'PUBLIC' if node['roletype'] == 3
529
+ deparse_identifier(node['rolename'], escape_always: true)
530
+ end
531
+
330
532
  def deparse_aexpr_in(node)
331
533
  rexpr = Array(node['rexpr']).map { |arg| deparse_item(arg) }
332
534
  operator = node['name'].map { |n| deparse_item(n, :operator) } == ['='] ? 'IN' : 'NOT IN'
@@ -339,6 +541,12 @@ class PgQuery
339
541
  format('%s %s %s', deparse_item(node['lexpr']), operator, value)
340
542
  end
341
543
 
544
+ def deparse_aexpr_ilike(node)
545
+ value = deparse_item(node['rexpr'])
546
+ operator = node['name'][0]['String']['str'] == '~~*' ? 'ILIKE' : 'NOT ILIKE'
547
+ format('%s %s %s', deparse_item(node['lexpr']), operator, value)
548
+ end
549
+
342
550
  def deparse_bool_expr_not(node)
343
551
  format('NOT %s', deparse_item(node['args'][0]))
344
552
  end
@@ -405,6 +613,13 @@ class PgQuery
405
613
  output.join(' ' + deparse_item(node['name'][0], :operator) + ' ')
406
614
  end
407
615
 
616
+ def deparse_aexpr_all(node)
617
+ output = []
618
+ output << deparse_item(node['lexpr'])
619
+ output << format('ALL(%s)', deparse_item(node['rexpr']))
620
+ output.join(' ' + deparse_item(node['name'][0], :operator) + ' ')
621
+ end
622
+
408
623
  def deparse_aexpr_between(node)
409
624
  between = case node['kind']
410
625
  when AEXPR_BETWEEN
@@ -432,9 +647,17 @@ class PgQuery
432
647
  output << deparse_item(node['larg'])
433
648
  case node['jointype']
434
649
  when 0
435
- output << 'CROSS' if node['quals'].nil? && node['usingClause'].nil?
650
+ if node['isNatural']
651
+ output << 'NATURAL'
652
+ elsif node['quals'].nil? && node['usingClause'].nil?
653
+ output << 'CROSS'
654
+ end
436
655
  when 1
437
656
  output << 'LEFT'
657
+ when 2
658
+ output << 'FULL'
659
+ when 3
660
+ output << 'RIGHT'
438
661
  end
439
662
  output << 'JOIN'
440
663
  output << deparse_item(node['rarg'])
@@ -449,6 +672,14 @@ class PgQuery
449
672
  output.join(' ')
450
673
  end
451
674
 
675
+ def deparse_lock(node)
676
+ output = []
677
+ output << 'LOCK TABLE'
678
+ tables = node['relations'].map { |table| deparse_item(table) }
679
+ output << tables.join(', ')
680
+ output.join(' ')
681
+ end
682
+
452
683
  LOCK_CLAUSE_STRENGTH = {
453
684
  LCS_FORKEYSHARE => 'FOR KEY SHARE',
454
685
  LCS_FORSHARE => 'FOR SHARE',
@@ -472,6 +703,16 @@ class PgQuery
472
703
  output << deparse_item(node['node'])
473
704
  output << 'ASC' if node['sortby_dir'] == 1
474
705
  output << 'DESC' if node['sortby_dir'] == 2
706
+ output << 'NULLS FIRST' if node['sortby_nulls'] == 1
707
+ output << 'NULLS LAST' if node['sortby_nulls'] == 2
708
+ output.join(' ')
709
+ end
710
+
711
+ def deparse_collate(node)
712
+ output = []
713
+ output << deparse_item(node['arg'])
714
+ output << 'COLLATE'
715
+ output << deparse_item_list(node['collname'])
475
716
  output.join(' ')
476
717
  end
477
718
 
@@ -518,6 +759,35 @@ class PgQuery
518
759
  output.join(' ')
519
760
  end
520
761
 
762
+ def deparse_vacuum_stmt(node)
763
+ output = []
764
+ output << 'VACUUM'
765
+ output.concat(deparse_vacuum_options(node))
766
+ output << deparse_item(node['relation']) if node.key?('relation')
767
+ if node.key?('va_cols')
768
+ output << "(#{node['va_cols'].map(&method(:deparse_item)).join(', ')})"
769
+ end
770
+ output.join(' ')
771
+ end
772
+
773
+ def deparse_vacuum_options(node)
774
+ output = []
775
+ output << 'FULL' if node['options'][4] == 1
776
+ output << 'FREEZE' if node['options'][3] == 1
777
+ output << 'VERBOSE' if node['options'][2] == 1
778
+ output << 'ANALYZE' if node['options'][1] == 1
779
+ output
780
+ end
781
+
782
+ def deparse_do_stmt(node)
783
+ output = []
784
+ output << 'DO'
785
+ statement, *rest = node['args']
786
+ output << "$$#{statement['DefElem']['arg']['String']['str']}$$"
787
+ output += rest.map { |item| deparse_item(item) }
788
+ output.join(' ')
789
+ end
790
+
521
791
  def deparse_cte(node)
522
792
  output = []
523
793
  output << node['ctename']
@@ -528,6 +798,7 @@ class PgQuery
528
798
 
529
799
  def deparse_case(node)
530
800
  output = ['CASE']
801
+ output << deparse_item(node['arg']) if node['arg']
531
802
  output += node['args'].map { |arg| deparse_item(arg) }
532
803
  if node['defresult']
533
804
  output << 'ELSE'
@@ -549,9 +820,22 @@ class PgQuery
549
820
  deparse_item(item)
550
821
  end
551
822
  end
823
+ if node['collClause']
824
+ output << 'COLLATE'
825
+ output += node['collClause']['CollateClause']['collname'].map(&method(:deparse_item))
826
+ end
552
827
  output.compact.join(' ')
553
828
  end
554
829
 
830
+ def deparse_composite_type(node)
831
+ output = ['CREATE TYPE']
832
+ output << deparse_rangevar(node['typevar'][RANGE_VAR].merge('inh' => true))
833
+ output << 'AS'
834
+ coldeflist = node['coldeflist'].map(&method(:deparse_item))
835
+ output << "(#{coldeflist.join(', ')})"
836
+ output.join(' ')
837
+ end
838
+
555
839
  def deparse_constraint(node) # rubocop:disable Metrics/CyclomaticComplexity
556
840
  output = []
557
841
  if node['conname']
@@ -580,6 +864,7 @@ class PgQuery
580
864
  if node['raw_expr']
581
865
  expression = deparse_item(node['raw_expr'])
582
866
  # Unless it's simple, put parentheses around it
867
+ expression = '(' + expression + ')' if node['raw_expr'][BOOL_EXPR]
583
868
  expression = '(' + expression + ')' if node['raw_expr'][A_EXPR] && node['raw_expr'][A_EXPR]['kind'] == AEXPR_OP
584
869
  output << expression
585
870
  end
@@ -591,13 +876,75 @@ class PgQuery
591
876
  output.join(' ')
592
877
  end
593
878
 
879
+ def deparse_copy(node)
880
+ output = ['COPY']
881
+ if node.key?('relation')
882
+ output << deparse_item(node['relation'])
883
+ elsif node.key?('query')
884
+ output << "(#{deparse_item(node['query'])})"
885
+ end
886
+ columns = node.fetch('attlist', []).map { |column| deparse_item(column) }
887
+ output << "(#{columns.join(', ')})" unless columns.empty?
888
+ output << (node['is_from'] ? 'FROM' : 'TO')
889
+ output << 'PROGRAM' if node['is_program']
890
+ output << deparse_copy_output(node)
891
+ output.join(' ')
892
+ end
893
+
894
+ def deparse_copy_output(node)
895
+ return "'#{node['filename']}'" if node.key?('filename')
896
+ return 'STDIN' if node['is_from']
897
+ 'STDOUT'
898
+ end
899
+
900
+ def deparse_create_enum(node)
901
+ output = ['CREATE TYPE']
902
+ output << deparse_item(node['typeName'][0])
903
+ output << 'AS ENUM'
904
+ vals = node['vals'].map { |val| deparse_item(val, A_CONST) }
905
+ output << "(#{vals.join(', ')})"
906
+ output.join(' ')
907
+ end
908
+
909
+ def deparse_create_cast(node)
910
+ output = []
911
+ output << 'CREATE'
912
+ output << 'CAST'
913
+ output << format('(%s AS %s)', deparse_item(node['sourcetype']), deparse_item(node['targettype']))
914
+ output << if node['func']
915
+ function = node['func']['ObjectWithArgs']
916
+ name = deparse_item_list(function['objname']).join('.')
917
+ arguments = deparse_item_list(function['objargs']).join(', ')
918
+ format('WITH FUNCTION %s(%s)', name, arguments)
919
+ elsif node['inout']
920
+ 'WITH INOUT'
921
+ else
922
+ 'WITHOUT FUNCTION'
923
+ end
924
+ output << 'AS IMPLICIT' if (node['context']).zero?
925
+ output << 'AS ASSIGNMENT' if node['context'] == 1
926
+ output.join(' ')
927
+ end
928
+
929
+ def deparse_create_domain(node)
930
+ output = []
931
+ output << 'CREATE'
932
+ output << 'DOMAIN'
933
+ output << deparse_item_list(node['domainname']).join('.')
934
+ output << 'AS'
935
+ output << deparse_item(node['typeName']) if node['typeName']
936
+ output << deparse_item(node['collClause']) if node['collClause']
937
+ output << deparse_item_list(node['constraints'])
938
+ output.join(' ')
939
+ end
940
+
594
941
  def deparse_create_function(node)
595
942
  output = []
596
943
  output << 'CREATE'
597
944
  output << 'OR REPLACE' if node['replace']
598
945
  output << 'FUNCTION'
599
946
 
600
- arguments = deparse_item_list(node['parameters']).join(', ')
947
+ arguments = deparse_item_list(node.fetch('parameters', [])).join(', ')
601
948
 
602
949
  output << deparse_item_list(node['funcname']).join('.') + '(' + arguments + ')'
603
950
 
@@ -608,6 +955,30 @@ class PgQuery
608
955
  output.join(' ')
609
956
  end
610
957
 
958
+ def deparse_create_range(node)
959
+ output = ['CREATE TYPE']
960
+ output << deparse_item(node['typeName'][0])
961
+ output << 'AS RANGE'
962
+ params = node['params'].map do |param|
963
+ param_out = [param['DefElem']['defname']]
964
+ if param['DefElem'].key?('arg')
965
+ param_out << deparse_item(param['DefElem']['arg'])
966
+ end
967
+ param_out.join('=')
968
+ end
969
+ output << "(#{params.join(', ')})"
970
+ output.join(' ')
971
+ end
972
+
973
+ def deparse_create_schema(node)
974
+ output = ['CREATE SCHEMA']
975
+ output << 'IF NOT EXISTS' if node['if_not_exists']
976
+ output << deparse_identifier(node['schemaname']) if node.key?('schemaname')
977
+ output << format('AUTHORIZATION %s', deparse_item(node['authrole'])) if node.key?('authrole')
978
+ output << deparse_item_list(node['schemaElts']) if node.key?('schemaElts')
979
+ output.join(' ')
980
+ end
981
+
611
982
  def deparse_create_table(node)
612
983
  output = []
613
984
  output << 'CREATE'
@@ -635,6 +1006,40 @@ class PgQuery
635
1006
  output.join(' ')
636
1007
  end
637
1008
 
1009
+ def deparse_create_table_as(node)
1010
+ output = []
1011
+ output << 'CREATE'
1012
+
1013
+ into = node['into']['IntoClause']
1014
+ persistence = relpersistence(into['rel'])
1015
+ output << persistence if persistence
1016
+
1017
+ output << 'TABLE'
1018
+
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
+
1027
+ output << 'AS'
1028
+ output << deparse_item(node['query'])
1029
+ output.join(' ')
1030
+ end
1031
+
1032
+ def deparse_execute(node)
1033
+ output = ['EXECUTE']
1034
+ output << deparse_identifier(node['name'])
1035
+ output << "(#{deparse_item_list(node['params']).join(', ')})" if node['params']
1036
+ output.join(' ')
1037
+ end
1038
+
1039
+ def deparse_into_clause(node)
1040
+ deparse_item(node['rel'])
1041
+ end
1042
+
638
1043
  def deparse_when(node)
639
1044
  output = ['WHEN']
640
1045
  output << deparse_item(node['expr'])
@@ -646,15 +1051,21 @@ class PgQuery
646
1051
  def deparse_sublink(node)
647
1052
  if node['subLinkType'] == SUBLINK_TYPE_ANY
648
1053
  format('%s IN (%s)', deparse_item(node['testexpr']), deparse_item(node['subselect']))
1054
+ elsif node['subLinkType'] == SUBLINK_TYPE_ALL
1055
+ format('%s %s ALL (%s)', deparse_item(node['testexpr']), deparse_item(node['operName'][0], :operator), deparse_item(node['subselect']))
649
1056
  elsif node['subLinkType'] == SUBLINK_TYPE_EXISTS
650
1057
  format('EXISTS(%s)', deparse_item(node['subselect']))
1058
+ elsif node['subLinkType'] == SUBLINK_TYPE_ARRAY
1059
+ format('ARRAY(%s)', deparse_item(node['subselect']))
651
1060
  else
652
1061
  format('(%s)', deparse_item(node['subselect']))
653
1062
  end
654
1063
  end
655
1064
 
656
1065
  def deparse_rangesubselect(node)
657
- output = '(' + deparse_item(node['subquery']) + ')'
1066
+ output = ''
1067
+ output = 'LATERAL ' if node['lateral']
1068
+ output = output + '(' + deparse_item(node['subquery']) + ')'
658
1069
  if node['alias']
659
1070
  output + ' ' + deparse_item(node['alias'])
660
1071
  else
@@ -669,21 +1080,52 @@ class PgQuery
669
1080
  def deparse_select(node) # rubocop:disable Metrics/CyclomaticComplexity
670
1081
  output = []
671
1082
 
1083
+ output << deparse_item(node['withClause']) if node['withClause']
1084
+
672
1085
  if node['op'] == 1
1086
+ output << '(' if node['larg']['SelectStmt']['sortClause']
673
1087
  output << deparse_item(node['larg'])
1088
+ output << ')' if node['larg']['SelectStmt']['sortClause']
674
1089
  output << 'UNION'
675
1090
  output << 'ALL' if node['all']
1091
+ output << '(' if node['rarg']['SelectStmt']['sortClause']
676
1092
  output << deparse_item(node['rarg'])
677
- return output.join(' ')
1093
+ output << ')' if node['rarg']['SelectStmt']['sortClause']
1094
+ output.join(' ')
678
1095
  end
679
1096
 
680
- output << deparse_item(node['withClause']) if node['withClause']
1097
+ if node['op'] == 3
1098
+ output << deparse_item(node['larg'])
1099
+ output << 'EXCEPT'
1100
+ output << deparse_item(node['rarg'])
1101
+ output.join(' ')
1102
+ end
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]
681
1112
 
682
1113
  if node[TARGET_LIST_FIELD]
683
- output << 'SELECT'
1114
+ if node['distinctClause']
1115
+ output << 'DISTINCT'
1116
+ unless node['distinctClause'].compact.empty?
1117
+ columns = node['distinctClause'].map { |item| deparse_item(item, :select) }
1118
+ output << "ON (#{columns.join(', ')})"
1119
+ end
1120
+ end
684
1121
  output << node[TARGET_LIST_FIELD].map do |item|
685
1122
  deparse_item(item, :select)
686
1123
  end.join(', ')
1124
+
1125
+ if node['intoClause']
1126
+ output << 'INTO'
1127
+ output << deparse_item(node['intoClause'])
1128
+ end
687
1129
  end
688
1130
 
689
1131
  if node[FROM_CLAUSE_FIELD]
@@ -743,6 +1185,30 @@ class PgQuery
743
1185
  output.join(' ')
744
1186
  end
745
1187
 
1188
+ def deparse_sql_value_function(node)
1189
+ output = []
1190
+ lookup = [
1191
+ 'current_date',
1192
+ 'current_time',
1193
+ 'current_time', # with precision
1194
+ 'current_timestamp',
1195
+ 'current_timestamp', # with precision
1196
+ 'localtime',
1197
+ 'localtime', # with precision
1198
+ 'localtimestamp',
1199
+ 'localtimestamp', # with precision
1200
+ 'current_role',
1201
+ 'current_user',
1202
+ 'session_user',
1203
+ 'user',
1204
+ 'current_catalog',
1205
+ 'current_schema'
1206
+ ]
1207
+ output << lookup[node['op']]
1208
+ output << "(#{node['typmod']})" unless node.fetch('typmod', -1) == -1
1209
+ output.join('')
1210
+ end
1211
+
746
1212
  def deparse_insert_into(node)
747
1213
  output = []
748
1214
  output << deparse_item(node['withClause']) if node['withClause']
@@ -752,12 +1218,57 @@ class PgQuery
752
1218
 
753
1219
  if node['cols']
754
1220
  output << '(' + node['cols'].map do |column|
755
- deparse_item(column)
1221
+ deparse_item(column, :select)
756
1222
  end.join(', ') + ')'
757
1223
  end
758
1224
 
759
1225
  output << deparse_item(node['selectStmt'])
760
1226
 
1227
+ if node['onConflictClause']
1228
+ output << deparse_insert_onconflict(node['onConflictClause'][ON_CONFLICT_CLAUSE])
1229
+ end
1230
+
1231
+ if node['returningList']
1232
+ output << 'RETURNING'
1233
+ output << node['returningList'].map do |column|
1234
+ deparse_item(column, :select)
1235
+ end.join(', ')
1236
+ end
1237
+
1238
+ output.join(' ')
1239
+ end
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
+
761
1272
  output.join(' ')
762
1273
  end
763
1274
 
@@ -770,9 +1281,17 @@ class PgQuery
770
1281
 
771
1282
  if node[TARGET_LIST_FIELD]
772
1283
  output << 'SET'
773
- node[TARGET_LIST_FIELD].each do |item|
774
- output << deparse_item(item, :update)
1284
+ columns = node[TARGET_LIST_FIELD].map do |item|
1285
+ deparse_item(item, :update)
775
1286
  end
1287
+ output << columns.join(', ')
1288
+ end
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(', ')
776
1295
  end
777
1296
 
778
1297
  if node['whereClause']
@@ -791,12 +1310,34 @@ class PgQuery
791
1310
  output.join(' ')
792
1311
  end
793
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
+
794
1331
  def deparse_typecast(node)
795
- if deparse_item(node['typeName']) == 'boolean'
796
- deparse_item(node['arg']) == "'t'" ? 'true' : 'false'
797
- else
798
- deparse_item(node['arg']) + '::' + 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')
799
1337
  end
1338
+
1339
+ context = true if node['arg']['A_Expr']
1340
+ deparse_item(node['arg'], context) + '::' + deparse_typename(node['typeName'][TYPE_NAME])
800
1341
  end
801
1342
 
802
1343
  def deparse_typename(node)
@@ -825,7 +1366,7 @@ class PgQuery
825
1366
  # Just pass along any custom types.
826
1367
  # (The pg_catalog types are built-in Postgres system types and are
827
1368
  # handled in the case statement below)
828
- return names.join('.') if catalog != 'pg_catalog'
1369
+ return names.join('.') + (arguments.nil? ? '' : "(#{arguments})") if catalog != 'pg_catalog'
829
1370
 
830
1371
  case type
831
1372
  when 'bpchar'
@@ -847,7 +1388,7 @@ class PgQuery
847
1388
  when 'real', 'float4'
848
1389
  'real'
849
1390
  when 'float8'
850
- 'double'
1391
+ 'double precision'
851
1392
  when 'time'
852
1393
  'time'
853
1394
  when 'timetz'
@@ -930,6 +1471,57 @@ class PgQuery
930
1471
  end
931
1472
  end
932
1473
 
1474
+ def deparse_define_stmt(node)
1475
+ dispatch = {
1476
+ 1 => :deparse_create_aggregate,
1477
+ 25 => :deparse_create_operator,
1478
+ 45 => :deparse_create_type
1479
+ }
1480
+ method(dispatch.fetch(node['kind'])).call(node)
1481
+ end
1482
+
1483
+ def deparse_create_aggregate(node)
1484
+ output = ['CREATE AGGREGATE']
1485
+ output << node['defnames'].map(&method(:deparse_item))
1486
+ args = node['args'][0] || [{ A_STAR => nil }]
1487
+ output << "(#{args.map(&method(:deparse_item)).join(', ')})"
1488
+ definitions = node['definition'].map do |definition|
1489
+ definition_output = [definition['DefElem']['defname']]
1490
+ definition_output << definition['DefElem']['arg']['TypeName']['names'].map(&method(:deparse_item)).join(', ') if definition['DefElem'].key?('arg')
1491
+ definition_output.join('=')
1492
+ end
1493
+ output << "(#{definitions.join(', ')})"
1494
+ output.join(' ')
1495
+ end
1496
+
1497
+ def deparse_create_operator(node)
1498
+ output = ['CREATE OPERATOR']
1499
+ output << node['defnames'][0]['String']['str']
1500
+ definitions = node['definition'].map do |definition|
1501
+ definition_output = [definition['DefElem']['defname']]
1502
+ definition_output << definition['DefElem']['arg']['TypeName']['names'].map(&method(:deparse_item)).join(', ') if definition['DefElem'].key?('arg')
1503
+ definition_output.join('=')
1504
+ end
1505
+ output << "(#{definitions.join(', ')})"
1506
+ output.join(' ')
1507
+ end
1508
+
1509
+ def deparse_create_type(node)
1510
+ output = ['CREATE TYPE']
1511
+ output << node['defnames'].map(&method(:deparse_item))
1512
+ if node.key?('definition')
1513
+ definitions = node['definition'].map do |definition|
1514
+ definition_output = [definition['DefElem']['defname']]
1515
+ if definition['DefElem'].key?('arg')
1516
+ definition_output += definition['DefElem']['arg']['TypeName']['names'].map(&method(:deparse_item))
1517
+ end
1518
+ definition_output.join('=')
1519
+ end
1520
+ output << "(#{definitions.join(', ')})"
1521
+ end
1522
+ output.join(' ')
1523
+ end
1524
+
933
1525
  def deparse_delete_from(node)
934
1526
  output = []
935
1527
  output << deparse_item(node['withClause']) if node['withClause']
@@ -960,19 +1552,121 @@ class PgQuery
960
1552
  output.join(' ')
961
1553
  end
962
1554
 
963
- def deparse_drop(node)
1555
+ def deparse_discard(node)
1556
+ output = ['DISCARD']
1557
+ output << 'ALL' if (node['target']).zero?
1558
+ output << 'PLANS' if node['target'] == 1
1559
+ output << 'SEQUENCES' if node['target'] == 2
1560
+ output << 'TEMP' if node['target'] == 3
1561
+ output.join(' ')
1562
+ end
1563
+
1564
+ def deparse_drop(node) # rubocop:disable Metrics/CyclomaticComplexity
964
1565
  output = ['DROP']
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
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
965
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
+
966
1599
  output << 'CONCURRENTLY' if node['concurrent']
967
1600
  output << 'IF EXISTS' if node['missing_ok']
968
1601
 
969
- output << node['objects'].map { |list| list.map { |object| deparse_item(object) } }.join(', ')
1602
+ objects = node['objects']
1603
+ objects = [objects] unless objects[0].is_a?(Array)
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
970
1631
 
971
1632
  output << 'CASCADE' if node['behavior'] == 1
972
1633
 
973
1634
  output.join(' ')
974
1635
  end
975
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
+
1658
+ def deparse_explain(node)
1659
+ output = ['EXPLAIN']
1660
+ options = node.fetch('options', []).map { |option| option['DefElem']['defname'].upcase }
1661
+ if options.size == 1
1662
+ output.concat(options)
1663
+ elsif options.size > 1
1664
+ output << "(#{options.join(', ')})"
1665
+ end
1666
+ output << deparse_item(node['query'])
1667
+ output.join(' ')
1668
+ end
1669
+
976
1670
  # The PG parser adds several pieces of view data onto the RANGEVAR
977
1671
  # that need to be printed before deparse_rangevar is called.
978
1672
  def relpersistence(rangevar)