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 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