pg_query 0.8.0 → 0.9.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 +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
|