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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 24024aaa9e4b949d9029a29fcb87d070c7c5f21d
4
- data.tar.gz: cc1c05675ef8fe944a266eaec0f503ce9aac95ad
3
+ metadata.gz: d5b51a8ab1816b19be70dbaae40055acb74bc8cd
4
+ data.tar.gz: 870d196421e8beb01a6dfbaa79c771673f8af3c3
5
5
  SHA512:
6
- metadata.gz: 77ec3b2ac14043457998316f500671c1e16b9d9f70d18bd78f36c1262b22bc08668ad1d95d13ce5a308e0b1fc8e2863e9b09b883637cb9ab33defcb9b898413e
7
- data.tar.gz: d2ea1bb3fa351698fe311164a8648dad28bd29ba3cb0de8aab9f11f6e1ed7e84526aa3f10a5e98e8a2eed9781c3fe977675ed438da32d86f93974b7cb2530e05
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
- @parsetree=
30
- [{"SELECT"=>
31
- {"distinctClause"=>nil,
32
- "intoClause"=>nil,
33
- "targetList"=>
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
- @parsetree=
51
- [{"SELECT"=>{"distinctClause"=>nil,
52
- "intoClause"=>nil,
53
- "targetList"=>
54
- [{"RESTARGET"=>
55
- {"name"=>nil,
56
- "indirection"=>nil,
57
- "val"=>
58
- {"COLUMNREF"=>
59
- {"fields"=>[{"A_STAR"=>{}}],
60
- "location"=>7}},
61
- "location"=>7}}],
62
- "fromClause"=>
63
- [{"RANGEVAR"=>
64
- {"schemaname"=>nil,
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.parsetree[0]['SELECT']['fromClause'][0]['RANGEVAR']['relname'] = 'other_users'
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
- @parsetree=
97
- [{"SELECT"=>
98
- {"distinctClause"=>nil,
99
- "intoClause"=>nil,
100
- "targetList"=>
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
- [{"RANGEVAR"=>
108
- {"schemaname"=>nil,
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
- {"AEXPR"=>
116
- {"name"=>["="],
117
- "lexpr"=>{"COLUMNREF"=>{"fields"=>["y"], "location"=>22}},
118
- "rexpr"=>{"PARAMREF"=>{"number"=>0, "location"=>26}},
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
- => "db76551255b7861b99bd384cf8096a3dd5162ab3"
132
+ => "8e1acac181c6d28f4a923392cf1c4eda49ee4cd2"
147
133
 
148
134
  PgQuery.parse("SELECT 2; --- comment").fingerprint
149
135
 
150
- => "db76551255b7861b99bd384cf8096a3dd5162ab3"
151
- ```
136
+ => "8e1acac181c6d28f4a923392cf1c4eda49ee4cd2"
152
137
 
153
- ## Differences from Upstream PostgreSQL
138
+ # Faster fingerprint method that is implemented inside the native library
139
+ PgQuery.fingerprint("SELECT ?")
154
140
 
155
- **This gem is based on the latest stable PostgreSQL version, but applies a few [patches](https://github.com/lfittl/pg_query/tree/master/ext/pg_query/patches) to make this library work:**
141
+ => "8e1acac181c6d28f4a923392cf1c4eda49ee4cd2"
142
+ ```
156
143
 
157
- * **01_output_nodes_as_json.patch:** Auto-generated outfuncs that outputs a parsetree as JSON (called through nodeToJSONString)
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
- High-level unit tests for these patches are inside this library.
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
@@ -3,10 +3,12 @@
3
3
  require 'mkmf'
4
4
  require 'open-uri'
5
5
 
6
- LIB_PG_QUERY_TAG = '9.4-1.0.0'
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
- # Build libpg_query (and parts of PostgreSQL)
24
- system("cd #{libdir}; make")
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} -O2 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv"
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
@@ -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 = @parsetree)
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 'AEXPR AND'
32
- deparse_aexpr_and(node)
33
- when 'AEXPR ANY'
34
- deparse_aexpr_any(node)
35
- when 'AEXPR IN'
36
- deparse_aexpr_in(node)
37
- when 'AEXPR NOT'
38
- deparse_aexpr_not(node)
39
- when 'AEXPR OR'
40
- deparse_aexpr_or(node)
41
- when 'AEXPR'
42
- deparse_aexpr(node, context)
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 'ALTER TABLE'
44
+ when ALTER_TABLE_STMT
46
45
  deparse_alter_table(node)
47
- when 'ALTER TABLE CMD'
46
+ when ALTER_TABLE_CMD
48
47
  deparse_alter_table_cmd(node)
49
- when 'A_ARRAYEXPR'
48
+ when A_ARRAY_EXPR
50
49
  deparse_a_arrayexp(node)
51
- when 'A_CONST'
50
+ when A_CONST
52
51
  deparse_a_const(node)
53
- when 'A_INDICES'
52
+ when A_INDICES
54
53
  deparse_a_indices(node)
55
- when 'A_INDIRECTION'
54
+ when A_INDIRECTION
56
55
  deparse_a_indirection(node)
57
- when 'A_STAR'
56
+ when A_STAR
58
57
  deparse_a_star(node)
59
- when 'A_TRUNCATED'
58
+ when A_TRUNCATED
60
59
  '...' # pg_query internal
61
- when 'CASE'
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 'COALESCE'
71
+ when COALESCE_EXPR
64
72
  deparse_coalesce(node)
65
- when 'COLUMNDEF'
73
+ when COLUMN_DEF
66
74
  deparse_columndef(node)
67
- when 'COLUMNREF'
75
+ when COLUMN_REF
68
76
  deparse_columnref(node)
69
- when 'COMMONTABLEEXPR'
77
+ when COMMON_TABLE_EXPR
70
78
  deparse_cte(node)
71
- when 'CONSTRAINT'
79
+ when CONSTRAINT
72
80
  deparse_constraint(node)
73
- when 'CREATEFUNCTIONSTMT'
81
+ when CREATE_FUNCTION_STMT
74
82
  deparse_create_function(node)
75
- when 'CREATESTMT'
83
+ when CREATE_STMT
76
84
  deparse_create_table(node)
77
- when 'DEFELEM'
85
+ when DEF_ELEM
78
86
  deparse_defelem(node)
79
- when 'DELETE FROM'
87
+ when DELETE_STMT
80
88
  deparse_delete_from(node)
81
- when 'DROP'
89
+ when DROP_STMT
82
90
  deparse_drop(node)
83
- when 'FUNCCALL'
91
+ when FUNC_CALL
84
92
  deparse_funccall(node)
85
- when 'FUNCTIONPARAMETER'
93
+ when FUNCTION_PARAMETER
86
94
  deparse_functionparameter(node)
87
- when 'INSERT INTO'
95
+ when INSERT_STMT
88
96
  deparse_insert_into(node)
89
- when 'JOINEXPR'
97
+ when JOIN_EXPR
90
98
  deparse_joinexpr(node)
91
- when 'LOCKINGCLAUSE'
99
+ when LOCKING_CLAUSE
92
100
  deparse_lockingclause(node)
93
- when 'NULLTEST'
101
+ when NULL_TEST
94
102
  deparse_nulltest(node)
95
- when 'PARAMREF'
103
+ when PARAM_REF
96
104
  deparse_paramref(node)
97
- when 'RANGEFUNCTION'
105
+ when RANGE_FUNCTION
98
106
  deparse_range_function(node)
99
- when 'RANGESUBSELECT'
107
+ when RANGE_SUBSELECT
100
108
  deparse_rangesubselect(node)
101
- when 'RANGEVAR'
109
+ when RANGE_VAR
102
110
  deparse_rangevar(node)
103
- when 'RENAMESTMT'
111
+ when RENAME_STMT
104
112
  deparse_renamestmt(node)
105
- when 'RESTARGET'
113
+ when RES_TARGET
106
114
  deparse_restarget(node, context)
107
- when 'ROW'
115
+ when ROW_EXPR
108
116
  deparse_row(node)
109
- when 'SELECT'
117
+ when SELECT_STMT
110
118
  deparse_select(node)
111
- when 'SORTBY'
119
+ when SORT_BY
112
120
  deparse_sortby(node)
113
- when 'SUBLINK'
121
+ when SUB_LINK
114
122
  deparse_sublink(node)
115
- when 'TRANSACTION'
123
+ when TRANSACTION_STMT
116
124
  deparse_transaction(node)
117
- when 'TYPECAST'
125
+ when TYPE_CAST
118
126
  deparse_typecast(node)
119
- when 'TYPENAME'
127
+ when TYPE_NAME
120
128
  deparse_typename(node)
121
- when 'UPDATE'
129
+ when UPDATE_STMT
122
130
  deparse_update(node)
123
- when 'WHEN'
131
+ when CASE_WHEN
124
132
  deparse_when(node)
125
- when 'WINDOWDEF'
133
+ when WINDOW_DEF
126
134
  deparse_windowdef(node)
127
- when 'WITHCLAUSE'
135
+ when WITH_CLAUSE
128
136
  deparse_with_clause(node)
129
- when 'VIEWSTMT'
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'] == 26 # table
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'].inspect.gsub("'", "''").gsub('"', "'")
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'] == 0
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 deparse_aexpr_not(node)
292
- format('NOT %s', deparse_item(node['rexpr']))
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 deparse_aexpr_and(node)
339
+ def deparse_bool_expr_and(node)
316
340
  # Only put parantheses around OR nodes that are inside this one
317
- lexpr = format(['AEXPR OR'].include?(node['lexpr'].keys[0]) ? '(%s)' : '%s', deparse_item(node['lexpr']))
318
- rexpr = format(['AEXPR OR'].include?(node['rexpr'].keys[0]) ? '(%s)' : '%s', deparse_item(node['rexpr']))
319
- format('%s AND %s', lexpr, rexpr)
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 deparse_aexpr_or(node)
350
+ def deparse_bool_expr_or(node)
323
351
  # Put parantheses around AND + OR nodes that are inside
324
- lexpr = format(['AEXPR AND', 'AEXPR OR'].include?(node['lexpr'].keys[0]) ? '(%s)' : '%s', deparse_item(node['lexpr']))
325
- rexpr = format(['AEXPR AND', 'AEXPR OR'].include?(node['rexpr'].keys[0]) ? '(%s)' : '%s', deparse_item(node['rexpr']))
326
- format('%s OR %s', lexpr, rexpr)
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']['RANGEVAR']['relname']
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
- # NOT_NULL -> NOT NULL
452
- output << node['contype'].gsub('_', ' ')
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'].keys == ['AEXPR']
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'].map { |item| deparse_item(item) }.join(', ')
517
+ arguments = deparse_item_list(node['parameters']).join(', ')
470
518
 
471
- output << node['funcname'].first + '(' + arguments + ')'
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'] == 2 && node['operName'] == ['=']
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'] == 0
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['targetList']
599
+ if node[TARGET_LIST_FIELD]
552
600
  output << 'SELECT'
553
- output << node['targetList'].map do |item|
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['fromClause']
606
+ if node[FROM_CLAUSE_FIELD]
559
607
  output << 'FROM'
560
- output << node['fromClause'].map do |item|
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['targetList']
688
+ if node[TARGET_LIST_FIELD]
641
689
  output << 'SET'
642
- node['targetList'].each do |item|
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']['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 node['names'] == %w(pg_catalog interval)
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(node['names'], arguments)
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
- 0 => 'BEGIN',
762
- 2 => 'COMMIT',
763
- 3 => 'ROLLBACK',
764
- 4 => 'SAVEPOINT',
765
- 5 => 'RELEASE',
766
- 6 => 'ROLLBACK TO SAVEPOINT'
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'] == 26
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' if node['behavior'] == 1
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['RANGEVAR']['relpersistence'] == 't'
890
+ if rangevar[RANGE_VAR]['relpersistence'] == 't'
841
891
  'TEMPORARY'
842
- elsif rangevar['RANGEVAR']['relpersistence'] == 'u'
892
+ elsif rangevar[RANGE_VAR]['relpersistence'] == 'u'
843
893
  'UNLOGGED'
844
894
  end
845
895
  end