pg_query 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +47 -61
- data/ext/pg_query/extconf.rb +15 -4
- data/ext/pg_query/pg_query_ruby.c +42 -0
- data/lib/pg_query.rb +4 -0
- data/lib/pg_query/deep_dup.rb +16 -0
- data/lib/pg_query/deparse.rb +166 -116
- data/lib/pg_query/deparse/alter_table.rb +21 -122
- data/lib/pg_query/filter_columns.rb +37 -35
- data/lib/pg_query/fingerprint.rb +95 -26
- data/lib/pg_query/legacy_parsetree.rb +128 -0
- data/lib/pg_query/node_types.rb +218 -0
- data/lib/pg_query/param_refs.rb +9 -9
- data/lib/pg_query/parse.rb +48 -50
- data/lib/pg_query/treewalker.rb +17 -11
- data/lib/pg_query/truncate.rb +10 -8
- data/lib/pg_query/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d5b51a8ab1816b19be70dbaae40055acb74bc8cd
|
4
|
+
data.tar.gz: 870d196421e8beb01a6dfbaa79c771673f8af3c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 41c49833febad40563a597e5ec17fbae6bad2678f8db466c4c459c744f4fda093cf13a4842678757b60a24168f5aa1bcd9b2098cf468b9fe257b7164fe5cae00
|
7
|
+
data.tar.gz: 4b7f26db07be2fdb025653007be7149442acdae475846341dbb917656d598ce393fd16c0728572f89c7c38dab34d48038cf98ab73bd4a9753b0d8cb872201d40
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.9.0 2016-04-17
|
4
|
+
|
5
|
+
* Based on PostgreSQL 9.5.2
|
6
|
+
* NOTE: Output format for the parse tree has changed (backwards incompatible!),
|
7
|
+
it is recommended you extensively test any direct reading/modification of
|
8
|
+
the tree data in your own code
|
9
|
+
* You can use the `.parsetree` translator method to ease the transition, note
|
10
|
+
however that there are still a few incompatible changes
|
11
|
+
* New `.fingerprint` method (backwards incompatible as well), see https://github.com/lfittl/libpg_query/wiki/Fingerprinting
|
12
|
+
* Removes PostgreSQL source and tarball after build process has finished, to reduce
|
13
|
+
diskspace requirements of the installed gem
|
14
|
+
|
15
|
+
|
3
16
|
## 0.8.0 2016-03-06
|
4
17
|
|
5
18
|
* Use fixed git version for libpg_query (PostgreSQL 9.4 based)
|
data/README.md
CHANGED
@@ -26,17 +26,14 @@ Due to compiling parts of PostgreSQL, installation might take a while on slower
|
|
26
26
|
PgQuery.parse("SELECT 1")
|
27
27
|
|
28
28
|
=> #<PgQuery:0x007fe92b27ea18
|
29
|
-
@
|
30
|
-
[{"
|
31
|
-
{"
|
32
|
-
|
33
|
-
|
34
|
-
[{"RESTARGET"=>
|
35
|
-
{"name"=>nil,
|
36
|
-
"indirection"=>nil,
|
37
|
-
"val"=>{"A_CONST"=>{"val"=>1, "location"=>7}},
|
29
|
+
@tree=
|
30
|
+
[{"SelectStmt"=>
|
31
|
+
{"targetList"=>
|
32
|
+
[{"ResTarget"=>
|
33
|
+
{"val"=>{"A_Const"=>{"val"=>{"Integer"=>{"ival"=>1}}, "location"=>7}},
|
38
34
|
"location"=>7}}],
|
39
|
-
|
35
|
+
"op"=>0,
|
36
|
+
}}],
|
40
37
|
@query="SELECT 1",
|
41
38
|
@warnings=[]>
|
42
39
|
```
|
@@ -47,36 +44,30 @@ PgQuery.parse("SELECT 1")
|
|
47
44
|
parsed_query = PgQuery.parse("SELECT * FROM users")
|
48
45
|
|
49
46
|
=> #<PgQuery:0x007ff3e956c8b0
|
50
|
-
@
|
51
|
-
[{"
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
"relname"=>"users",
|
66
|
-
"inhOpt"=>2,
|
67
|
-
"relpersistence"=>"p",
|
68
|
-
"alias"=>nil,
|
69
|
-
"location"=>14}}],
|
70
|
-
...}}],
|
47
|
+
@tree=
|
48
|
+
[{"SelectStmt"=>
|
49
|
+
{"targetList"=>
|
50
|
+
[{"ResTarget"=>
|
51
|
+
{"val"=>
|
52
|
+
{"ColumnRef"=> {"fields"=>[{"A_Star"=>{}}], "location"=>7}},
|
53
|
+
"location"=>7}
|
54
|
+
}],
|
55
|
+
"fromClause"=>
|
56
|
+
[{"RangeVar"=>
|
57
|
+
{"relname"=>"users",
|
58
|
+
"inhOpt"=>2,
|
59
|
+
"relpersistence"=>"p",
|
60
|
+
"location"=>14}}],
|
61
|
+
}}],
|
71
62
|
@query="SELECT * FROM users",
|
72
63
|
@warnings=[]>
|
73
64
|
|
74
65
|
# Modify the parse tree in some way
|
75
|
-
parsed_query.
|
66
|
+
parsed_query.tree[0]['SelectStmt']['fromClause'][0]['RangeVar']['relname'] = 'other_users'
|
76
67
|
|
77
68
|
# Turn it into SQL again
|
78
69
|
parsed_query.deparse
|
79
|
-
=> "SELECT * FROM other_users"
|
70
|
+
=> "SELECT * FROM \"other_users\""
|
80
71
|
```
|
81
72
|
|
82
73
|
Note: The deparsing feature is experimental and does not support outputting all SQL yet.
|
@@ -93,31 +84,26 @@ PgQuery.normalize("SELECT 1 FROM x WHERE y = 'foo'")
|
|
93
84
|
PgQuery.parse("SELECT ? FROM x WHERE y = ?")
|
94
85
|
|
95
86
|
=> #<PgQuery:0x007fb99455a438
|
96
|
-
@
|
97
|
-
[{"
|
98
|
-
{"
|
99
|
-
|
100
|
-
|
101
|
-
[{"RESTARGET"=>
|
102
|
-
{"name"=>nil,
|
103
|
-
"indirection"=>nil,
|
104
|
-
"val"=>{"PARAMREF"=>{"number"=>0, "location"=>7}},
|
87
|
+
@tree=
|
88
|
+
[{"SelectStmt"=>
|
89
|
+
{"targetList"=>
|
90
|
+
[{"ResTarget"=>
|
91
|
+
{"val"=>{"ParamRef"=>{"location"=>7}},
|
105
92
|
"location"=>7}}],
|
106
93
|
"fromClause"=>
|
107
|
-
[{"
|
108
|
-
{"
|
109
|
-
"relname"=>"x",
|
94
|
+
[{"RangeVar"=>
|
95
|
+
{"relname"=>"x",
|
110
96
|
"inhOpt"=>2,
|
111
97
|
"relpersistence"=>"p",
|
112
|
-
"alias"=>nil,
|
113
98
|
"location"=>14}}],
|
114
99
|
"whereClause"=>
|
115
|
-
{"
|
116
|
-
{"
|
117
|
-
"
|
118
|
-
"
|
100
|
+
{"A_Expr"=>
|
101
|
+
{"kind"=>0,
|
102
|
+
"name"=>[{"String"=>{"str"=>"="}}],
|
103
|
+
"lexpr"=>{"ColumnRef"=>{"fields"=>[{"String"=>{"str"=>"y"}}], "location"=>22}},
|
104
|
+
"rexpr"=>{"ParamRef"=>{"location"=>26}},
|
119
105
|
"location"=>24}},
|
120
|
-
|
106
|
+
}}],
|
121
107
|
@query="SELECT ? FROM x WHERE y = ?",
|
122
108
|
@warnings=[]>
|
123
109
|
```
|
@@ -143,23 +129,23 @@ PgQuery.parse("SELECT ? FROM x WHERE x.y = ? AND z = ?").filter_columns
|
|
143
129
|
```ruby
|
144
130
|
PgQuery.parse("SELECT 1").fingerprint
|
145
131
|
|
146
|
-
=> "
|
132
|
+
=> "8e1acac181c6d28f4a923392cf1c4eda49ee4cd2"
|
147
133
|
|
148
134
|
PgQuery.parse("SELECT 2; --- comment").fingerprint
|
149
135
|
|
150
|
-
=> "
|
151
|
-
```
|
136
|
+
=> "8e1acac181c6d28f4a923392cf1c4eda49ee4cd2"
|
152
137
|
|
153
|
-
|
138
|
+
# Faster fingerprint method that is implemented inside the native library
|
139
|
+
PgQuery.fingerprint("SELECT ?")
|
154
140
|
|
155
|
-
|
141
|
+
=> "8e1acac181c6d28f4a923392cf1c4eda49ee4cd2"
|
142
|
+
```
|
156
143
|
|
157
|
-
|
158
|
-
* **02_parse_replacement_char.patch:** Modify scan.l/gram.y to support parsing normalized queries
|
159
|
-
* Known regression: Removed support for custom operators containing "?" (doesn't affect hstore/JSON/geometric operators)
|
160
|
-
* **03_regenerate_bison_flex_files.patch:** Regenerate scan.c/gram.c to avoid bison/flex dependency on deployment
|
144
|
+
## Differences from Upstream PostgreSQL
|
161
145
|
|
162
|
-
|
146
|
+
This gem is based on [libpg_query](https://github.com/lfittl/libpg_query),
|
147
|
+
which uses the latest stable PostgreSQL version, but with a patch applied
|
148
|
+
to support parsing normalized queries containing `?` replacement characters.
|
163
149
|
|
164
150
|
|
165
151
|
## Original Author
|
data/ext/pg_query/extconf.rb
CHANGED
@@ -3,10 +3,12 @@
|
|
3
3
|
require 'mkmf'
|
4
4
|
require 'open-uri'
|
5
5
|
|
6
|
-
LIB_PG_QUERY_TAG = '9.
|
6
|
+
LIB_PG_QUERY_TAG = '9.5-1.1.0'
|
7
7
|
|
8
8
|
workdir = Dir.pwd
|
9
9
|
libdir = File.join(workdir, 'libpg_query-' + LIB_PG_QUERY_TAG)
|
10
|
+
gemdir = File.join(File.dirname(__FILE__), '../..')
|
11
|
+
libfile = libdir + '/libpg_query.a'
|
10
12
|
|
11
13
|
unless File.exist?("#{workdir}/libpg_query.tar.gz")
|
12
14
|
File.open("#{workdir}/libpg_query.tar.gz", 'wb') do |target_file|
|
@@ -20,14 +22,23 @@ unless Dir.exist?(libdir)
|
|
20
22
|
system("tar -xf #{workdir}/libpg_query.tar.gz") || fail('ERROR')
|
21
23
|
end
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
+
unless Dir.exist?(libfile)
|
26
|
+
# Build libpg_query (and parts of PostgreSQL)
|
27
|
+
system("cd #{libdir}; make DEBUG=0")
|
28
|
+
|
29
|
+
# Cleanup the Postgres install inside libpg_query to reduce the installed size
|
30
|
+
system("rm -rf #{libdir}/postgres")
|
31
|
+
system("rm -f #{libdir}/postgres.tar.bz2")
|
32
|
+
end
|
33
|
+
|
34
|
+
# Copy test files (this intentionally overwrites existing files!)
|
35
|
+
system("cp #{libdir}/testdata/* #{gemdir}/spec/files/")
|
25
36
|
|
26
37
|
$objs = ['pg_query_ruby.o']
|
27
38
|
|
28
39
|
$LOCAL_LIBS << '-lpg_query'
|
29
40
|
$LIBPATH << libdir
|
30
|
-
$CFLAGS << " -I #{libdir} -
|
41
|
+
$CFLAGS << " -I #{libdir} -O3 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv"
|
31
42
|
|
32
43
|
SYMFILE = File.join(File.dirname(__FILE__), 'pg_query_ruby.sym')
|
33
44
|
if RUBY_PLATFORM =~ /darwin/
|
@@ -2,8 +2,11 @@
|
|
2
2
|
|
3
3
|
void raise_ruby_parse_error(PgQueryParseResult result);
|
4
4
|
void raise_ruby_normalize_error(PgQueryNormalizeResult result);
|
5
|
+
void raise_ruby_fingerprint_error(PgQueryFingerprintResult result);
|
6
|
+
|
5
7
|
VALUE pg_query_ruby_parse(VALUE self, VALUE input);
|
6
8
|
VALUE pg_query_ruby_normalize(VALUE self, VALUE input);
|
9
|
+
VALUE pg_query_ruby_fingerprint(VALUE self, VALUE input);
|
7
10
|
|
8
11
|
void Init_pg_query(void)
|
9
12
|
{
|
@@ -15,6 +18,7 @@ void Init_pg_query(void)
|
|
15
18
|
|
16
19
|
rb_define_singleton_method(cPgQuery, "_raw_parse", pg_query_ruby_parse, 1);
|
17
20
|
rb_define_singleton_method(cPgQuery, "normalize", pg_query_ruby_normalize, 1);
|
21
|
+
rb_define_singleton_method(cPgQuery, "fingerprint", pg_query_ruby_fingerprint, 1);
|
18
22
|
}
|
19
23
|
|
20
24
|
void raise_ruby_parse_error(PgQueryParseResult result)
|
@@ -53,6 +57,24 @@ void raise_ruby_normalize_error(PgQueryNormalizeResult result)
|
|
53
57
|
rb_exc_raise(rb_class_new_instance(4, args, cParseError));
|
54
58
|
}
|
55
59
|
|
60
|
+
void raise_ruby_fingerprint_error(PgQueryFingerprintResult result)
|
61
|
+
{
|
62
|
+
VALUE cPgQuery, cParseError;
|
63
|
+
VALUE args[4];
|
64
|
+
|
65
|
+
cPgQuery = rb_const_get(rb_cObject, rb_intern("PgQuery"));
|
66
|
+
cParseError = rb_const_get_at(cPgQuery, rb_intern("ParseError"));
|
67
|
+
|
68
|
+
args[0] = rb_str_new2(result.error->message);
|
69
|
+
args[1] = rb_str_new2(result.error->filename);
|
70
|
+
args[2] = INT2NUM(result.error->lineno);
|
71
|
+
args[3] = INT2NUM(result.error->cursorpos);
|
72
|
+
|
73
|
+
pg_query_free_fingerprint_result(result);
|
74
|
+
|
75
|
+
rb_exc_raise(rb_class_new_instance(4, args, cParseError));
|
76
|
+
}
|
77
|
+
|
56
78
|
VALUE pg_query_ruby_parse(VALUE self, VALUE input)
|
57
79
|
{
|
58
80
|
Check_Type(input, T_STRING);
|
@@ -87,3 +109,23 @@ VALUE pg_query_ruby_normalize(VALUE self, VALUE input)
|
|
87
109
|
|
88
110
|
return output;
|
89
111
|
}
|
112
|
+
|
113
|
+
VALUE pg_query_ruby_fingerprint(VALUE self, VALUE input)
|
114
|
+
{
|
115
|
+
Check_Type(input, T_STRING);
|
116
|
+
|
117
|
+
VALUE output;
|
118
|
+
PgQueryFingerprintResult result = pg_query_fingerprint(StringValueCStr(input));
|
119
|
+
|
120
|
+
if (result.error) raise_ruby_fingerprint_error(result);
|
121
|
+
|
122
|
+
if (result.hexdigest) {
|
123
|
+
output = rb_str_new2(result.hexdigest);
|
124
|
+
} else {
|
125
|
+
output = Qnil;
|
126
|
+
}
|
127
|
+
|
128
|
+
pg_query_free_fingerprint_result(result);
|
129
|
+
|
130
|
+
return output;
|
131
|
+
}
|
data/lib/pg_query.rb
CHANGED
@@ -4,6 +4,10 @@ require 'pg_query/parse_error'
|
|
4
4
|
require 'pg_query/pg_query'
|
5
5
|
require 'pg_query/parse'
|
6
6
|
require 'pg_query/treewalker'
|
7
|
+
require 'pg_query/node_types'
|
8
|
+
require 'pg_query/deep_dup'
|
9
|
+
|
10
|
+
require 'pg_query/legacy_parsetree'
|
7
11
|
|
8
12
|
require 'pg_query/filter_columns'
|
9
13
|
require 'pg_query/fingerprint'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class PgQuery
|
2
|
+
def deep_dup(obj)
|
3
|
+
case obj
|
4
|
+
when Hash
|
5
|
+
obj.each_with_object(obj.dup) do |(key, value), hash|
|
6
|
+
hash[deep_dup(key)] = deep_dup(value)
|
7
|
+
end
|
8
|
+
when Array
|
9
|
+
obj.map { |it| deep_dup(it) }
|
10
|
+
when NilClass, FalseClass, TrueClass, Symbol, Numeric
|
11
|
+
obj # Can't be duplicated
|
12
|
+
else
|
13
|
+
obj.dup
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/pg_query/deparse.rb
CHANGED
@@ -2,7 +2,7 @@ require_relative 'deparse/interval'
|
|
2
2
|
require_relative 'deparse/alter_table'
|
3
3
|
class PgQuery
|
4
4
|
# Reconstruct all of the parsed queries into their original form
|
5
|
-
def deparse(tree = @
|
5
|
+
def deparse(tree = @tree)
|
6
6
|
tree.map do |item|
|
7
7
|
Deparse.from(item)
|
8
8
|
end.join('; ')
|
@@ -28,111 +28,133 @@ class PgQuery
|
|
28
28
|
node = item.values[0]
|
29
29
|
|
30
30
|
case type
|
31
|
-
when
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
when 'ALIAS'
|
31
|
+
when A_EXPR
|
32
|
+
case node['kind']
|
33
|
+
when AEXPR_OP
|
34
|
+
deparse_aexpr(node, context)
|
35
|
+
when AEXPR_OP_ANY
|
36
|
+
deparse_aexpr_any(node)
|
37
|
+
when AEXPR_IN
|
38
|
+
deparse_aexpr_in(node)
|
39
|
+
else
|
40
|
+
fail format("Can't deparse: %s: %s", type, node.inspect)
|
41
|
+
end
|
42
|
+
when ALIAS
|
44
43
|
deparse_alias(node)
|
45
|
-
when
|
44
|
+
when ALTER_TABLE_STMT
|
46
45
|
deparse_alter_table(node)
|
47
|
-
when
|
46
|
+
when ALTER_TABLE_CMD
|
48
47
|
deparse_alter_table_cmd(node)
|
49
|
-
when
|
48
|
+
when A_ARRAY_EXPR
|
50
49
|
deparse_a_arrayexp(node)
|
51
|
-
when
|
50
|
+
when A_CONST
|
52
51
|
deparse_a_const(node)
|
53
|
-
when
|
52
|
+
when A_INDICES
|
54
53
|
deparse_a_indices(node)
|
55
|
-
when
|
54
|
+
when A_INDIRECTION
|
56
55
|
deparse_a_indirection(node)
|
57
|
-
when
|
56
|
+
when A_STAR
|
58
57
|
deparse_a_star(node)
|
59
|
-
when
|
58
|
+
when A_TRUNCATED
|
60
59
|
'...' # pg_query internal
|
61
|
-
when
|
60
|
+
when BOOL_EXPR
|
61
|
+
case node['boolop']
|
62
|
+
when BOOL_EXPR_AND
|
63
|
+
deparse_bool_expr_and(node)
|
64
|
+
when BOOL_EXPR_OR
|
65
|
+
deparse_bool_expr_or(node)
|
66
|
+
when BOOL_EXPR_NOT
|
67
|
+
deparse_bool_expr_not(node)
|
68
|
+
end
|
69
|
+
when CASE_EXPR
|
62
70
|
deparse_case(node)
|
63
|
-
when
|
71
|
+
when COALESCE_EXPR
|
64
72
|
deparse_coalesce(node)
|
65
|
-
when
|
73
|
+
when COLUMN_DEF
|
66
74
|
deparse_columndef(node)
|
67
|
-
when
|
75
|
+
when COLUMN_REF
|
68
76
|
deparse_columnref(node)
|
69
|
-
when
|
77
|
+
when COMMON_TABLE_EXPR
|
70
78
|
deparse_cte(node)
|
71
|
-
when
|
79
|
+
when CONSTRAINT
|
72
80
|
deparse_constraint(node)
|
73
|
-
when
|
81
|
+
when CREATE_FUNCTION_STMT
|
74
82
|
deparse_create_function(node)
|
75
|
-
when
|
83
|
+
when CREATE_STMT
|
76
84
|
deparse_create_table(node)
|
77
|
-
when
|
85
|
+
when DEF_ELEM
|
78
86
|
deparse_defelem(node)
|
79
|
-
when
|
87
|
+
when DELETE_STMT
|
80
88
|
deparse_delete_from(node)
|
81
|
-
when
|
89
|
+
when DROP_STMT
|
82
90
|
deparse_drop(node)
|
83
|
-
when
|
91
|
+
when FUNC_CALL
|
84
92
|
deparse_funccall(node)
|
85
|
-
when
|
93
|
+
when FUNCTION_PARAMETER
|
86
94
|
deparse_functionparameter(node)
|
87
|
-
when
|
95
|
+
when INSERT_STMT
|
88
96
|
deparse_insert_into(node)
|
89
|
-
when
|
97
|
+
when JOIN_EXPR
|
90
98
|
deparse_joinexpr(node)
|
91
|
-
when
|
99
|
+
when LOCKING_CLAUSE
|
92
100
|
deparse_lockingclause(node)
|
93
|
-
when
|
101
|
+
when NULL_TEST
|
94
102
|
deparse_nulltest(node)
|
95
|
-
when
|
103
|
+
when PARAM_REF
|
96
104
|
deparse_paramref(node)
|
97
|
-
when
|
105
|
+
when RANGE_FUNCTION
|
98
106
|
deparse_range_function(node)
|
99
|
-
when
|
107
|
+
when RANGE_SUBSELECT
|
100
108
|
deparse_rangesubselect(node)
|
101
|
-
when
|
109
|
+
when RANGE_VAR
|
102
110
|
deparse_rangevar(node)
|
103
|
-
when
|
111
|
+
when RENAME_STMT
|
104
112
|
deparse_renamestmt(node)
|
105
|
-
when
|
113
|
+
when RES_TARGET
|
106
114
|
deparse_restarget(node, context)
|
107
|
-
when
|
115
|
+
when ROW_EXPR
|
108
116
|
deparse_row(node)
|
109
|
-
when
|
117
|
+
when SELECT_STMT
|
110
118
|
deparse_select(node)
|
111
|
-
when
|
119
|
+
when SORT_BY
|
112
120
|
deparse_sortby(node)
|
113
|
-
when
|
121
|
+
when SUB_LINK
|
114
122
|
deparse_sublink(node)
|
115
|
-
when
|
123
|
+
when TRANSACTION_STMT
|
116
124
|
deparse_transaction(node)
|
117
|
-
when
|
125
|
+
when TYPE_CAST
|
118
126
|
deparse_typecast(node)
|
119
|
-
when
|
127
|
+
when TYPE_NAME
|
120
128
|
deparse_typename(node)
|
121
|
-
when
|
129
|
+
when UPDATE_STMT
|
122
130
|
deparse_update(node)
|
123
|
-
when
|
131
|
+
when CASE_WHEN
|
124
132
|
deparse_when(node)
|
125
|
-
when
|
133
|
+
when WINDOW_DEF
|
126
134
|
deparse_windowdef(node)
|
127
|
-
when
|
135
|
+
when WITH_CLAUSE
|
128
136
|
deparse_with_clause(node)
|
129
|
-
when
|
137
|
+
when VIEW_STMT
|
130
138
|
deparse_viewstmt(node)
|
139
|
+
when STRING
|
140
|
+
if context == A_CONST
|
141
|
+
format("'%s'", node['str'].gsub("'", "''"))
|
142
|
+
elsif [FUNC_CALL, TYPE_NAME, :operator, :defname_as].include?(context)
|
143
|
+
node['str']
|
144
|
+
else
|
145
|
+
format('"%s"', node['str'].gsub('"', '""'))
|
146
|
+
end
|
147
|
+
when INTEGER
|
148
|
+
node['ival'].to_s
|
131
149
|
else
|
132
150
|
fail format("Can't deparse: %s: %s", type, node.inspect)
|
133
151
|
end
|
134
152
|
end
|
135
153
|
|
154
|
+
def deparse_item_list(nodes, context = nil)
|
155
|
+
nodes.map { |n| deparse_item(n, context) }
|
156
|
+
end
|
157
|
+
|
136
158
|
def deparse_rangevar(node)
|
137
159
|
output = []
|
138
160
|
output << 'ONLY' if node['inhOpt'] == 0
|
@@ -144,11 +166,13 @@ class PgQuery
|
|
144
166
|
def deparse_renamestmt(node)
|
145
167
|
output = []
|
146
168
|
|
147
|
-
if node['renameType'] ==
|
169
|
+
if node['renameType'] == OBJECT_TYPE_TABLE
|
148
170
|
output << 'ALTER TABLE'
|
149
171
|
output << deparse_item(node['relation'])
|
150
172
|
output << 'RENAME TO'
|
151
173
|
output << node['newname']
|
174
|
+
else
|
175
|
+
fail format("Can't deparse: %s", node.inspect)
|
152
176
|
end
|
153
177
|
|
154
178
|
output.join(' ')
|
@@ -167,7 +191,7 @@ class PgQuery
|
|
167
191
|
end
|
168
192
|
|
169
193
|
def deparse_a_const(node)
|
170
|
-
node['val']
|
194
|
+
deparse_item(node['val'], A_CONST)
|
171
195
|
end
|
172
196
|
|
173
197
|
def deparse_a_star(_node)
|
@@ -189,7 +213,7 @@ class PgQuery
|
|
189
213
|
def deparse_alias(node)
|
190
214
|
name = node['aliasname']
|
191
215
|
if node['colnames']
|
192
|
-
name + '(' + node['colnames'].join(', ') + ')'
|
216
|
+
name + '(' + deparse_item_list(node['colnames']).join(', ') + ')'
|
193
217
|
else
|
194
218
|
name
|
195
219
|
end
|
@@ -223,7 +247,7 @@ class PgQuery
|
|
223
247
|
end
|
224
248
|
|
225
249
|
def deparse_paramref(node)
|
226
|
-
if node['number']
|
250
|
+
if node['number'].nil?
|
227
251
|
'?'
|
228
252
|
else
|
229
253
|
format('$%d', node['number'])
|
@@ -250,7 +274,7 @@ class PgQuery
|
|
250
274
|
# COUNT(*)
|
251
275
|
args << '*' if node['agg_star']
|
252
276
|
|
253
|
-
name = (node['funcname'] - ['pg_catalog']).join('.')
|
277
|
+
name = (node['funcname'].map { |n| deparse_item(n, FUNC_CALL) } - ['pg_catalog']).join('.')
|
254
278
|
distinct = node['agg_distinct'] ? 'DISTINCT ' : ''
|
255
279
|
output << format('%s(%s%s)', name, distinct, args.join(', '))
|
256
280
|
output << format('OVER (%s)', deparse_item(node['over'])) if node['over']
|
@@ -284,12 +308,12 @@ class PgQuery
|
|
284
308
|
|
285
309
|
def deparse_aexpr_in(node)
|
286
310
|
rexpr = Array(node['rexpr']).map { |arg| deparse_item(arg) }
|
287
|
-
operator = node['name'] == ['='] ? 'IN' : 'NOT IN'
|
311
|
+
operator = node['name'].map { |n| deparse_item(n, :operator) } == ['='] ? 'IN' : 'NOT IN'
|
288
312
|
format('%s %s (%s)', deparse_item(node['lexpr']), operator, rexpr.join(', '))
|
289
313
|
end
|
290
314
|
|
291
|
-
def
|
292
|
-
format('NOT %s', deparse_item(node['
|
315
|
+
def deparse_bool_expr_not(node)
|
316
|
+
format('NOT %s', deparse_item(node['args'][0]))
|
293
317
|
end
|
294
318
|
|
295
319
|
def deparse_range_function(node)
|
@@ -304,7 +328,7 @@ class PgQuery
|
|
304
328
|
output = []
|
305
329
|
output << deparse_item(node['lexpr'], context || true)
|
306
330
|
output << deparse_item(node['rexpr'], context || true)
|
307
|
-
output = output.join(' ' + node['name'][0] + ' ')
|
331
|
+
output = output.join(' ' + deparse_item(node['name'][0], :operator) + ' ')
|
308
332
|
if context
|
309
333
|
# This is a nested expression, add parentheses.
|
310
334
|
output = '(' + output + ')'
|
@@ -312,25 +336,33 @@ class PgQuery
|
|
312
336
|
output
|
313
337
|
end
|
314
338
|
|
315
|
-
def
|
339
|
+
def deparse_bool_expr_and(node)
|
316
340
|
# Only put parantheses around OR nodes that are inside this one
|
317
|
-
|
318
|
-
|
319
|
-
|
341
|
+
node['args'].map do |arg|
|
342
|
+
if [BOOL_EXPR_OR].include?(arg.values[0]['boolop'])
|
343
|
+
format('(%s)', deparse_item(arg))
|
344
|
+
else
|
345
|
+
deparse_item(arg)
|
346
|
+
end
|
347
|
+
end.join(' AND ')
|
320
348
|
end
|
321
349
|
|
322
|
-
def
|
350
|
+
def deparse_bool_expr_or(node)
|
323
351
|
# Put parantheses around AND + OR nodes that are inside
|
324
|
-
|
325
|
-
|
326
|
-
|
352
|
+
node['args'].map do |arg|
|
353
|
+
if [BOOL_EXPR_AND, BOOL_EXPR_OR].include?(arg.values[0]['boolop'])
|
354
|
+
format('(%s)', deparse_item(arg))
|
355
|
+
else
|
356
|
+
deparse_item(arg)
|
357
|
+
end
|
358
|
+
end.join(' OR ')
|
327
359
|
end
|
328
360
|
|
329
361
|
def deparse_aexpr_any(node)
|
330
362
|
output = []
|
331
363
|
output << deparse_item(node['lexpr'])
|
332
364
|
output << format('ANY(%s)', deparse_item(node['rexpr']))
|
333
|
-
output.join(' ' + node['name'][0] + ' ')
|
365
|
+
output.join(' ' + deparse_item(node['name'][0], :operator) + ' ')
|
334
366
|
end
|
335
367
|
|
336
368
|
def deparse_joinexpr(node)
|
@@ -349,12 +381,12 @@ class PgQuery
|
|
349
381
|
output.join(' ')
|
350
382
|
end
|
351
383
|
|
352
|
-
LOCK_CLAUSE_STRENGTH =
|
353
|
-
'FOR KEY SHARE',
|
354
|
-
'FOR SHARE',
|
355
|
-
'FOR NO KEY UPDATE',
|
356
|
-
'FOR UPDATE'
|
357
|
-
|
384
|
+
LOCK_CLAUSE_STRENGTH = {
|
385
|
+
LCS_FORKEYSHARE => 'FOR KEY SHARE',
|
386
|
+
LCS_FORSHARE => 'FOR SHARE',
|
387
|
+
LCS_FORNOKEYUPDATE => 'FOR NO KEY UPDATE',
|
388
|
+
LCS_FORUPDATE => 'FOR UPDATE'
|
389
|
+
}
|
358
390
|
def deparse_lockingclause(node)
|
359
391
|
output = []
|
360
392
|
output << LOCK_CLAUSE_STRENGTH[node['strength']]
|
@@ -393,8 +425,8 @@ class PgQuery
|
|
393
425
|
output << persistence if persistence
|
394
426
|
|
395
427
|
output << 'VIEW'
|
396
|
-
output << node['view'][
|
397
|
-
output << format('(%s)', node['aliases'].join(', ')) if node['aliases']
|
428
|
+
output << node['view'][RANGE_VAR]['relname']
|
429
|
+
output << format('(%s)', deparse_item_list(node['aliases']).join(', ')) if node['aliases']
|
398
430
|
|
399
431
|
output << 'AS'
|
400
432
|
output << deparse_item(node['query'])
|
@@ -411,7 +443,7 @@ class PgQuery
|
|
411
443
|
def deparse_cte(node)
|
412
444
|
output = []
|
413
445
|
output << node['ctename']
|
414
|
-
output << format('(%s)', node['aliascolnames'].join(', ')) if node['aliascolnames']
|
446
|
+
output << format('(%s)', node['aliascolnames'].map { |n| deparse_item(n) }.join(', ')) if node['aliascolnames']
|
415
447
|
output << format('AS (%s)', deparse_item(node['ctequery']))
|
416
448
|
output.join(' ')
|
417
449
|
end
|
@@ -442,22 +474,38 @@ class PgQuery
|
|
442
474
|
output.compact.join(' ')
|
443
475
|
end
|
444
476
|
|
445
|
-
def deparse_constraint(node)
|
477
|
+
def deparse_constraint(node) # rubocop:disable Metrics/CyclomaticComplexity
|
446
478
|
output = []
|
447
479
|
if node['conname']
|
448
480
|
output << 'CONSTRAINT'
|
449
481
|
output << node['conname']
|
450
482
|
end
|
451
|
-
|
452
|
-
|
483
|
+
case node['contype']
|
484
|
+
when CONSTR_TYPE_NULL
|
485
|
+
output << 'NULL'
|
486
|
+
when CONSTR_TYPE_NOTNULL
|
487
|
+
output << 'NOT NULL'
|
488
|
+
when CONSTR_TYPE_DEFAULT
|
489
|
+
output << 'DEFAULT'
|
490
|
+
when CONSTR_TYPE_CHECK
|
491
|
+
output << 'CHECK'
|
492
|
+
when CONSTR_TYPE_PRIMARY
|
493
|
+
output << 'PRIMARY KEY'
|
494
|
+
when CONSTR_TYPE_UNIQUE
|
495
|
+
output << 'UNIQUE'
|
496
|
+
when CONSTR_TYPE_EXCLUSION
|
497
|
+
output << 'EXCLUSION'
|
498
|
+
when CONSTR_TYPE_FOREIGN
|
499
|
+
output << 'FOREIGN KEY'
|
500
|
+
end
|
453
501
|
|
454
502
|
if node['raw_expr']
|
455
503
|
expression = deparse_item(node['raw_expr'])
|
456
504
|
# Unless it's simple, put parentheses around it
|
457
|
-
expression = '(' + expression + ')' if node['raw_expr']
|
505
|
+
expression = '(' + expression + ')' if node['raw_expr'][A_EXPR] && node['raw_expr'][A_EXPR]['kind'] == AEXPR_OP
|
458
506
|
output << expression
|
459
507
|
end
|
460
|
-
output << '(' + node['keys'].join(', ') + ')' if node['keys']
|
508
|
+
output << '(' + deparse_item_list(node['keys']).join(', ') + ')' if node['keys']
|
461
509
|
output << "USING INDEX #{node['indexname']}" if node['indexname']
|
462
510
|
output.join(' ')
|
463
511
|
end
|
@@ -466,9 +514,9 @@ class PgQuery
|
|
466
514
|
output = []
|
467
515
|
output << 'CREATE FUNCTION'
|
468
516
|
|
469
|
-
arguments = node['parameters']
|
517
|
+
arguments = deparse_item_list(node['parameters']).join(', ')
|
470
518
|
|
471
|
-
output << node['funcname'].
|
519
|
+
output << deparse_item_list(node['funcname']).join('.') + '(' + arguments + ')'
|
472
520
|
|
473
521
|
output << 'RETURNS'
|
474
522
|
output << deparse_item(node['returnType'])
|
@@ -513,9 +561,9 @@ class PgQuery
|
|
513
561
|
end
|
514
562
|
|
515
563
|
def deparse_sublink(node)
|
516
|
-
if node['subLinkType'] ==
|
564
|
+
if node['subLinkType'] == SUBLINK_TYPE_ANY
|
517
565
|
return format('%s IN (%s)', deparse_item(node['testexpr']), deparse_item(node['subselect']))
|
518
|
-
elsif node['subLinkType'] ==
|
566
|
+
elsif node['subLinkType'] == SUBLINK_TYPE_EXISTS
|
519
567
|
return format('EXISTS(%s)', deparse_item(node['subselect']))
|
520
568
|
else
|
521
569
|
return format('(%s)', deparse_item(node['subselect']))
|
@@ -548,16 +596,16 @@ class PgQuery
|
|
548
596
|
|
549
597
|
output << deparse_item(node['withClause']) if node['withClause']
|
550
598
|
|
551
|
-
if node[
|
599
|
+
if node[TARGET_LIST_FIELD]
|
552
600
|
output << 'SELECT'
|
553
|
-
output << node[
|
601
|
+
output << node[TARGET_LIST_FIELD].map do |item|
|
554
602
|
deparse_item(item, :select)
|
555
603
|
end.join(', ')
|
556
604
|
end
|
557
605
|
|
558
|
-
if node[
|
606
|
+
if node[FROM_CLAUSE_FIELD]
|
559
607
|
output << 'FROM'
|
560
|
-
output << node[
|
608
|
+
output << node[FROM_CLAUSE_FIELD].map do |item|
|
561
609
|
deparse_item(item)
|
562
610
|
end.join(', ')
|
563
611
|
end
|
@@ -637,9 +685,9 @@ class PgQuery
|
|
637
685
|
output << 'UPDATE'
|
638
686
|
output << deparse_item(node['relation'])
|
639
687
|
|
640
|
-
if node[
|
688
|
+
if node[TARGET_LIST_FIELD]
|
641
689
|
output << 'SET'
|
642
|
-
node[
|
690
|
+
node[TARGET_LIST_FIELD].each do |item|
|
643
691
|
output << deparse_item(item, :update)
|
644
692
|
end
|
645
693
|
end
|
@@ -664,14 +712,16 @@ class PgQuery
|
|
664
712
|
if deparse_item(node['typeName']) == 'boolean'
|
665
713
|
deparse_item(node['arg']) == "'t'" ? 'true' : 'false'
|
666
714
|
else
|
667
|
-
deparse_item(node['arg']) + '::' + deparse_typename(node['typeName'][
|
715
|
+
deparse_item(node['arg']) + '::' + deparse_typename(node['typeName'][TYPE_NAME])
|
668
716
|
end
|
669
717
|
end
|
670
718
|
|
671
719
|
def deparse_typename(node)
|
720
|
+
names = node['names'].map { |n| deparse_item(n, TYPE_NAME) }
|
721
|
+
|
672
722
|
# Intervals are tricky and should be handled in a separate method because
|
673
723
|
# they require performing some bitmask operations.
|
674
|
-
return deparse_interval_type(node) if
|
724
|
+
return deparse_interval_type(node) if names == %w(pg_catalog interval)
|
675
725
|
|
676
726
|
output = []
|
677
727
|
output << 'SETOF' if node['setof']
|
@@ -681,7 +731,7 @@ class PgQuery
|
|
681
731
|
deparse_item(item)
|
682
732
|
end.join(', ')
|
683
733
|
end
|
684
|
-
output << deparse_typename_cast(
|
734
|
+
output << deparse_typename_cast(names, arguments)
|
685
735
|
|
686
736
|
output.join(' ')
|
687
737
|
end
|
@@ -758,12 +808,12 @@ class PgQuery
|
|
758
808
|
end
|
759
809
|
|
760
810
|
TRANSACTION_CMDS = {
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
811
|
+
TRANS_STMT_BEGIN => 'BEGIN',
|
812
|
+
TRANS_STMT_COMMIT => 'COMMIT',
|
813
|
+
TRANS_STMT_ROLLBACK => 'ROLLBACK',
|
814
|
+
TRANS_STMT_SAVEPOINT => 'SAVEPOINT',
|
815
|
+
TRANS_STMT_RELEASE => 'RELEASE',
|
816
|
+
TRANS_STMT_ROLLBACK_TO => 'ROLLBACK TO SAVEPOINT'
|
767
817
|
}
|
768
818
|
def deparse_transaction(node)
|
769
819
|
output = []
|
@@ -783,11 +833,11 @@ class PgQuery
|
|
783
833
|
def deparse_defelem(node)
|
784
834
|
case node['defname']
|
785
835
|
when 'as'
|
786
|
-
"AS $$#{node['arg'].join("\n")}$$"
|
836
|
+
"AS $$#{deparse_item_list(node['arg'], :defname_as).join("\n")}$$"
|
787
837
|
when 'language'
|
788
|
-
"language #{node['arg']}"
|
838
|
+
"language #{deparse_item(node['arg'])}"
|
789
839
|
else
|
790
|
-
node['arg']
|
840
|
+
deparse_item(node['arg'])
|
791
841
|
end
|
792
842
|
end
|
793
843
|
|
@@ -823,13 +873,13 @@ class PgQuery
|
|
823
873
|
|
824
874
|
def deparse_drop(node)
|
825
875
|
output = ['DROP']
|
826
|
-
output << 'TABLE' if node['removeType'] ==
|
876
|
+
output << 'TABLE' if node['removeType'] == OBJECT_TYPE_TABLE
|
827
877
|
output << 'CONCURRENTLY' if node['concurrent']
|
828
878
|
output << 'IF EXISTS' if node['missing_ok']
|
829
879
|
|
830
|
-
output << node['objects'].join(', ')
|
880
|
+
output << node['objects'].map { |list| list.map { |object| deparse_item(object) } }.join(', ')
|
831
881
|
|
832
|
-
output << 'CASCADE'
|
882
|
+
output << 'CASCADE' if node['behavior'] == 1
|
833
883
|
|
834
884
|
output.join(' ')
|
835
885
|
end
|
@@ -837,9 +887,9 @@ class PgQuery
|
|
837
887
|
# The PG parser adds several pieces of view data onto the RANGEVAR
|
838
888
|
# that need to be printed before deparse_rangevar is called.
|
839
889
|
def relpersistence(rangevar)
|
840
|
-
if rangevar[
|
890
|
+
if rangevar[RANGE_VAR]['relpersistence'] == 't'
|
841
891
|
'TEMPORARY'
|
842
|
-
elsif rangevar[
|
892
|
+
elsif rangevar[RANGE_VAR]['relpersistence'] == 'u'
|
843
893
|
'UNLOGGED'
|
844
894
|
end
|
845
895
|
end
|