pg_query 1.0.1 → 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
- 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)