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 +5 -5
- data/CHANGELOG.md +97 -0
- data/README.md +8 -4
- data/Rakefile +3 -3
- data/ext/pg_query/extconf.rb +17 -8
- data/lib/pg_query/deparse.rb +738 -44
- data/lib/pg_query/deparse/keywords.rb +159 -0
- data/lib/pg_query/deparse/rename.rb +41 -0
- data/lib/pg_query/node_types.rb +15 -0
- data/lib/pg_query/parse.rb +3 -0
- data/lib/pg_query/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f3d7de9759711a5e173db293a6120a15ee6ab1f0f4c5804e7f68aaba99ec8ff8
|
4
|
+
data.tar.gz: 974e3fb0b709021a771803e52de2a8ca19309a679d81c1ee2a3deb105f297769
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3c13b36c353e6de6aaaba2b6428be7e21c933bf8ef159212cdd64ee4a765f8a664defe735f2f3e94ef6441f3403d7be6fbf19ca4d6b98e07a6b42a68a635238a
|
7
|
+
data.tar.gz: cc742d80bdf934c89e1faa1dd6e6560764339ecabb6fe2af67fd219b2a71b4fb6756e649ddc237d995413a90c6b036d33fe8492eb91030d8a891f970ca10510a
|
data/CHANGELOG.md
CHANGED
@@ -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
|
-
*
|
155
|
-
*
|
156
|
-
*
|
157
|
-
*
|
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(
|
21
|
-
FileUtils.rm_f Dir.glob(File.join(
|
22
|
-
FileUtils.rm_f File.join(
|
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
|
data/ext/pg_query/extconf.rb
CHANGED
@@ -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.
|
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(
|
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?(
|
14
|
-
File.open(
|
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 -
|
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
|
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(
|
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
|
data/lib/pg_query/deparse.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
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
|
-
|
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 = [
|
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
|
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) }
|
299
|
-
|
300
|
-
|
301
|
-
|
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
|
-
|
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
|
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 = '
|
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
|
-
|
1093
|
+
output << ')' if node['rarg']['SelectStmt']['sortClause']
|
1094
|
+
output.join(' ')
|
678
1095
|
end
|
679
1096
|
|
680
|
-
|
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
|
-
|
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].
|
774
|
-
|
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
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
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
|
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
|
-
|
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)
|