pg_query 1.0.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +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)
|