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.
@@ -0,0 +1,218 @@
1
+ # rubocop:disable Style/ConstantName
2
+ class PgQuery
3
+ # NODE TYPES
4
+
5
+ A_ARRAY_EXPR = 'A_ArrayExpr'.freeze
6
+ A_CONST = 'A_Const'.freeze
7
+ A_EXPR = 'A_Expr'.freeze
8
+ A_INDICES = 'A_Indices'.freeze
9
+ A_INDIRECTION = 'A_Indirection'.freeze
10
+ A_STAR = 'A_Star'.freeze
11
+ ACCESS_PRIV = 'AccessPriv'.freeze
12
+ ALIAS = 'Alias'.freeze
13
+ ALTER_TABLE_CMD = 'AlterTableCmd'.freeze
14
+ ALTER_TABLE_STMT = 'AlterTableStmt'.freeze
15
+ BIT_STRING = 'BitString'.freeze
16
+ BOOL_EXPR = 'BoolExpr'.freeze
17
+ CASE_EXPR = 'CaseExpr'.freeze
18
+ CASE_WHEN = 'CaseWhen'.freeze
19
+ CHECK_POINT_STMT = 'CheckPointStmt'.freeze
20
+ COALESCE_EXPR = 'CoalesceExpr'.freeze
21
+ COLUMN_DEF = 'ColumnDef'.freeze
22
+ COLUMN_REF = 'ColumnRef'.freeze
23
+ COMMON_TABLE_EXPR = 'CommonTableExpr'.freeze
24
+ CONSTRAINT = 'Constraint'.freeze
25
+ COPY_STMT = 'CopyStmt'.freeze
26
+ CREATE_FUNCTION_STMT = 'CreateFunctionStmt'.freeze
27
+ CREATE_SCHEMA_STMT = 'CreateSchemaStmt'.freeze
28
+ CREATE_STMT = 'CreateStmt'.freeze
29
+ CREATE_TABLE_AS_STMT = 'CreateTableAsStmt'.freeze
30
+ CREATE_TRIG_STMT = 'CreateTrigStmt'.freeze
31
+ DEF_ELEM = 'DefElem'.freeze
32
+ DELETE_STMT = 'DeleteStmt'.freeze
33
+ DROP_STMT = 'DropStmt'.freeze
34
+ EXPLAIN_STMT = 'ExplainStmt'.freeze
35
+ FLOAT = 'Float'.freeze
36
+ FUNC_CALL = 'FuncCall'.freeze
37
+ FUNCTION_PARAMETER = 'FunctionParameter'.freeze
38
+ GRANT_STMT = 'GrantStmt'.freeze
39
+ GRANT_ROLE_STMT = 'GrantRoleStmt'.freeze
40
+ INDEX_ELEM = 'IndexElem'.freeze
41
+ INDEX_STMT = 'IndexStmt'.freeze
42
+ INSERT_STMT = 'InsertStmt'.freeze
43
+ INTO_CLAUSE = 'IntoClause'.freeze
44
+ JOIN_EXPR = 'JoinExpr'.freeze
45
+ LOCK_STMT = 'LockStmt'.freeze
46
+ LOCKING_CLAUSE = 'LockingClause'.freeze
47
+ NULL_TEST = 'NullTest'.freeze
48
+ RANGE_FUNCTION = 'RangeFunction'.freeze
49
+ PARAM_REF = 'ParamRef'.freeze
50
+ RANGE_SUBSELECT = 'RangeSubselect'.freeze
51
+ RANGE_VAR = 'RangeVar'.freeze
52
+ REFRESH_MAT_VIEW_STMT = 'RefreshMatViewStmt'.freeze
53
+ RENAME_STMT = 'RenameStmt'.freeze
54
+ RES_TARGET = 'ResTarget'.freeze
55
+ ROW_EXPR = 'RowExpr'.freeze
56
+ RULE_STMT = 'RuleStmt'.freeze
57
+ ROLE_SPEC = 'RoleSpec'.freeze
58
+ SELECT_STMT = 'SelectStmt'.freeze
59
+ SORT_BY = 'SortBy'.freeze
60
+ SUB_LINK = 'SubLink'.freeze
61
+ TRANSACTION_STMT = 'TransactionStmt'.freeze
62
+ TRUNCATE_STMT = 'TruncateStmt'.freeze
63
+ TYPE_CAST = 'TypeCast'.freeze
64
+ TYPE_NAME = 'TypeName'.freeze
65
+ UPDATE_STMT = 'UpdateStmt'.freeze
66
+ VACUUM_STMT = 'VacuumStmt'.freeze
67
+ VARIABLE_SET_STMT = 'VariableSetStmt'.freeze
68
+ VARIABLE_SHOW_STMT = 'VariableShowStmt'.freeze
69
+ VIEW_STMT = 'ViewStmt'.freeze
70
+ WINDOW_DEF = 'WindowDef'.freeze
71
+ WITH_CLAUSE = 'WithClause'.freeze
72
+ STRING = 'String'.freeze
73
+ INTEGER = 'Integer'.freeze
74
+ SET_TO_DEFAULT = 'SetToDefault'.freeze
75
+ PREPARE_STMT = 'PrepareStmt'.freeze
76
+ EXECUTE_STMT = 'ExecuteStmt'.freeze
77
+ DEALLOCATE_STMT = 'DeallocateStmt'.freeze
78
+ NULL = 'Null'.freeze
79
+ INT_LIST = 'IntList'.freeze
80
+ OID_LIST = 'OidList'.freeze
81
+
82
+ # FIELDS
83
+
84
+ FROM_CLAUSE_FIELD = 'fromClause'.freeze
85
+ TARGET_LIST_FIELD = 'targetList'.freeze
86
+ COLS_FIELD = 'cols'.freeze
87
+ REXPR_FIELD = 'rexpr'.freeze
88
+
89
+ # ENUMS
90
+
91
+ CONSTR_TYPE_NULL = 0 # not standard SQL, but a lot of people expect it
92
+ CONSTR_TYPE_NOTNULL = 1
93
+ CONSTR_TYPE_DEFAULT = 2
94
+ CONSTR_TYPE_CHECK = 3
95
+ CONSTR_TYPE_PRIMARY = 4
96
+ CONSTR_TYPE_UNIQUE = 5
97
+ CONSTR_TYPE_EXCLUSION = 6
98
+ CONSTR_TYPE_FOREIGN = 7
99
+ CONSTR_TYPE_ATTR_DEFERRABLE = 8 # attributes for previous constraint node
100
+ CONSTR_TYPE_ATTR_NOT_DEFERRABLE = 9
101
+ CONSTR_TYPE_ATTR_DEFERRED = 10
102
+ CONSTR_TYPE_ATTR_IMMEDIATE = 11
103
+
104
+ OBJECT_TYPE_INDEX = 19
105
+ OBJECT_TYPE_RULE = 28
106
+ OBJECT_TYPE_SCHEMA = 29
107
+ OBJECT_TYPE_TABLE = 32
108
+ OBJECT_TYPE_TRIGGER = 35
109
+ OBJECT_TYPE_VIEW = 42
110
+
111
+ BOOL_EXPR_AND = 0
112
+ BOOL_EXPR_OR = 1
113
+ BOOL_EXPR_NOT = 2
114
+
115
+ AEXPR_OP = 0 # normal operator
116
+ AEXPR_OP_ANY = 1 # scalar op ANY (array)
117
+ AEXPR_OP_ALL = 2 # scalar op ALL (array)
118
+ AEXPR_DISTINCT = 3 # IS DISTINCT FROM - name must be "="
119
+ AEXPR_NULLIF = 4 # NULLIF - name must be "="
120
+ AEXPR_OF = 5 # IS [NOT] OF - name must be "=" or "<>"
121
+ AEXPR_IN = 6 # [NOT] IN - name must be "=" or "<>"
122
+ AEXPR_LIKE = 7 # [NOT] LIKE - name must be "~~" or "!~~"
123
+ AEXPR_ILIKE = 8 # [NOT] ILIKE - name must be "~~*" or "!~~*"
124
+ AEXPR_SIMILAR = 9 # [NOT] SIMILAR - name must be "~" or "!~"
125
+ AEXPR_BETWEEN = 10 # name must be "BETWEEN"
126
+ AEXPR_NOT_BETWEEN = 11 # name must be "NOT BETWEEN"
127
+ AEXPR_BETWEEN_SYM = 12 # name must be "BETWEEN SYMMETRIC"
128
+ AEXPR_NOT_BETWEEN_SYM = 13 # name must be "NOT BETWEEN SYMMETRIC"
129
+ AEXPR_PAREN = 14 # nameless dummy node for parentheses
130
+
131
+ TRANS_STMT_BEGIN = 0
132
+ TRANS_STMT_START = 1 # semantically identical to BEGIN
133
+ TRANS_STMT_COMMIT = 2
134
+ TRANS_STMT_ROLLBACK = 3
135
+ TRANS_STMT_SAVEPOINT = 4
136
+ TRANS_STMT_RELEASE = 5
137
+ TRANS_STMT_ROLLBACK_TO = 6
138
+ TRANS_STMT_PREPARE = 7
139
+ TRANS_STMT_COMMIT_PREPARED = 8
140
+ TRANS_STMT_ROLLBACK_PREPARED = 9
141
+
142
+ SUBLINK_TYPE_EXISTS = 0 # EXISTS(SELECT ...)
143
+ SUBLINK_TYPE_ALL = 1 # (lefthand) op ALL (SELECT ...)
144
+ SUBLINK_TYPE_ANY = 2 # (lefthand) op ANY (SELECT ...)
145
+ SUBLINK_TYPE_ROWCOMPARE = 3 # (lefthand) op (SELECT ...)
146
+ SUBLINK_TYPE_EXPR = 4 # (SELECT with single targetlist item ...)
147
+ SUBLINK_TYPE_MULTIEXPR = 5 # (SELECT with multiple targetlist items ...)
148
+ SUBLINK_TYPE_ARRAY = 6 # ARRAY(SELECT with single targetlist item ...)
149
+ SUBLINK_TYPE_CTE = 7 # WITH query (never actually part of an expression), for SubPlans only
150
+
151
+ LCS_NONE = 0 # no such clause - only used in PlanRowMark
152
+ LCS_FORKEYSHARE = 1 # FOR KEY SHARE
153
+ LCS_FORSHARE = 2 # FOR SHARE
154
+ LCS_FORNOKEYUPDATE = 3 # FOR NO KEY UPDATE
155
+ LCS_FORUPDATE = 4 # FOR UPDATE
156
+
157
+ AT_AddColumn = 0 # add column
158
+ AT_AddColumnRecurse = 1 # internal to commands/tablecmds.c
159
+ AT_AddColumnToView = 2 # implicitly via CREATE OR REPLACE VIEW
160
+ AT_ColumnDefault = 3 # alter column default
161
+ AT_DropNotNull = 4 # alter column drop not null
162
+ AT_SetNotNull = 5 # alter column set not null
163
+ AT_SetStatistics = 6 # alter column set statistics
164
+ AT_SetOptions = 7 # alter column set ( options )
165
+ AT_ResetOptions = 8 # alter column reset ( options )
166
+ AT_SetStorage = 9 # alter column set storage
167
+ AT_DropColumn = 10 # drop column
168
+ AT_DropColumnRecurse = 11 # internal to commands/tablecmds.c
169
+ AT_AddIndex = 12 # add index
170
+ AT_ReAddIndex = 13 # internal to commands/tablecmds.c
171
+ AT_AddConstraint = 14 # add constraint
172
+ AT_AddConstraintRecurse = 15 # internal to commands/tablecmds.c
173
+ AT_ReAddConstraint = 16 # internal to commands/tablecmds.c
174
+ AT_AlterConstraint = 17 # alter constraint
175
+ AT_ValidateConstraint = 18 # validate constraint
176
+ AT_ValidateConstraintRecurse = 19 # internal to commands/tablecmds.c
177
+ AT_ProcessedConstraint = 20 # pre-processed add constraint (local in parser/parse_utilcmd.c)
178
+ AT_AddIndexConstraint = 21 # add constraint using existing index
179
+ AT_DropConstraint = 22 # drop constraint
180
+ AT_DropConstraintRecurse = 23 # internal to commands/tablecmds.c
181
+ AT_ReAddComment = 24 # internal to commands/tablecmds.c
182
+ AT_AlterColumnType = 25 # alter column type
183
+ AT_AlterColumnGenericOptions = 26 # alter column OPTIONS (...)
184
+ AT_ChangeOwner = 27 # change owner
185
+ AT_ClusterOn = 28 # CLUSTER ON
186
+ AT_DropCluster = 29 # SET WITHOUT CLUSTER
187
+ AT_SetLogged = 30 # SET LOGGED
188
+ AT_SetUnLogged = 31 # SET UNLOGGED
189
+ AT_AddOids = 32 # SET WITH OIDS
190
+ AT_AddOidsRecurse = 33 # internal to commands/tablecmds.c
191
+ AT_DropOids = 34 # SET WITHOUT OIDS
192
+ AT_SetTableSpace = 35 # SET TABLESPACE
193
+ AT_SetRelOptions = 36 # SET (...) -- AM specific parameters
194
+ AT_ResetRelOptions = 37 # RESET (...) -- AM specific parameters
195
+ AT_ReplaceRelOptions = 38 # replace reloption list in its entirety
196
+ AT_EnableTrig = 39 # ENABLE TRIGGER name
197
+ AT_EnableAlwaysTrig = 40 # ENABLE ALWAYS TRIGGER name
198
+ AT_EnableReplicaTrig = 41 # ENABLE REPLICA TRIGGER name
199
+ AT_DisableTrig = 42 # DISABLE TRIGGER name
200
+ AT_EnableTrigAll = 43 # ENABLE TRIGGER ALL
201
+ AT_DisableTrigAll = 44 # DISABLE TRIGGER ALL
202
+ AT_EnableTrigUser = 45 # ENABLE TRIGGER USER
203
+ AT_DisableTrigUser = 46 # DISABLE TRIGGER USER
204
+ AT_EnableRule = 47 # ENABLE RULE name
205
+ AT_EnableAlwaysRule = 48 # ENABLE ALWAYS RULE name
206
+ AT_EnableReplicaRule = 49 # ENABLE REPLICA RULE name
207
+ AT_DisableRule = 50 # DISABLE RULE name
208
+ AT_AddInherit = 51 # INHERIT parent
209
+ AT_DropInherit = 52 # NO INHERIT parent
210
+ AT_AddOf = 53 # OF <type_name>
211
+ AT_DropOf = 54 # NOT OF
212
+ AT_ReplicaIdentity = 55 # REPLICA IDENTITY
213
+ AT_EnableRowSecurity = 56 # ENABLE ROW SECURITY
214
+ AT_DisableRowSecurity = 57 # DISABLE ROW SECURITY
215
+ AT_ForceRowSecurity = 58 # FORCE ROW SECURITY
216
+ AT_NoForceRowSecurity = 59 # NO FORCE ROW SECURITY
217
+ AT_GenericOptions = 60 # OPTIONS (...)
218
+ end
@@ -2,22 +2,22 @@ class PgQuery
2
2
  def param_refs # rubocop:disable Metrics/CyclomaticComplexity
3
3
  results = []
4
4
 
5
- treewalker! parsetree do |_, _, v|
5
+ treewalker! @tree do |_, _, v|
6
6
  next unless v.is_a?(Hash)
7
7
 
8
- if v['PARAMREF']
9
- results << { 'location' => v['PARAMREF']['location'],
10
- 'length' => param_ref_length(v['PARAMREF']) }
11
- elsif v['TYPECAST']
12
- next unless v['TYPECAST']['arg'] && v['TYPECAST']['typeName']
8
+ if v[PARAM_REF]
9
+ results << { 'location' => v[PARAM_REF]['location'],
10
+ 'length' => param_ref_length(v[PARAM_REF]) }
11
+ elsif v[TYPE_CAST]
12
+ next unless v[TYPE_CAST]['arg'] && v[TYPE_CAST]['typeName']
13
13
 
14
- p = v['TYPECAST']['arg'].delete('PARAMREF')
15
- t = v['TYPECAST']['typeName'].delete('TYPENAME')
14
+ p = v[TYPE_CAST]['arg'].delete(PARAM_REF)
15
+ t = v[TYPE_CAST]['typeName'].delete(TYPE_NAME)
16
16
  next unless p && t
17
17
 
18
18
  location = p['location']
19
19
  typeloc = t['location']
20
- typename = t['names'].join('.')
20
+ typename = t['names']
21
21
  length = param_ref_length(p)
22
22
 
23
23
  if typeloc < location
@@ -2,10 +2,10 @@ require 'json'
2
2
 
3
3
  class PgQuery
4
4
  def self.parse(query)
5
- parsetree, stderr = _raw_parse(query)
5
+ tree, stderr = _raw_parse(query)
6
6
 
7
7
  begin
8
- parsetree = JSON.parse(parsetree, max_nesting: 1000)
8
+ tree = JSON.parse(tree, max_nesting: 1000)
9
9
  rescue JSON::ParserError
10
10
  raise ParseError.new('Failed to parse JSON', __FILE__, __LINE__, -1)
11
11
  end
@@ -16,16 +16,16 @@ class PgQuery
16
16
  warnings << line.strip
17
17
  end
18
18
 
19
- PgQuery.new(query, parsetree, warnings)
19
+ PgQuery.new(query, tree, warnings)
20
20
  end
21
21
 
22
22
  attr_reader :query
23
- attr_reader :parsetree
23
+ attr_reader :tree
24
24
  attr_reader :warnings
25
25
 
26
- def initialize(query, parsetree, warnings = [])
26
+ def initialize(query, tree, warnings = [])
27
27
  @query = query
28
- @parsetree = parsetree
28
+ @tree = tree
29
29
  @warnings = warnings
30
30
  end
31
31
 
@@ -45,7 +45,7 @@ class PgQuery
45
45
  @tables = []
46
46
  @aliases = {}
47
47
 
48
- statements = @parsetree.dup
48
+ statements = @tree.dup
49
49
  from_clause_items = []
50
50
  where_clause_items = []
51
51
 
@@ -53,44 +53,44 @@ class PgQuery
53
53
  statement = statements.shift
54
54
  if statement
55
55
  case statement.keys[0]
56
- when 'SELECT'
57
- if statement['SELECT']['op'] == 0
58
- (statement['SELECT']['fromClause'] || []).each do |item|
59
- if item['RANGESUBSELECT']
60
- statements << item['RANGESUBSELECT']['subquery']
56
+ when SELECT_STMT
57
+ if statement[SELECT_STMT]['op'] == 0
58
+ (statement[SELECT_STMT][FROM_CLAUSE_FIELD] || []).each do |item|
59
+ if item[RANGE_SUBSELECT]
60
+ statements << item[RANGE_SUBSELECT]['subquery']
61
61
  else
62
62
  from_clause_items << item
63
63
  end
64
64
  end
65
65
 
66
66
  # CTEs
67
- if statement['SELECT']['withClause']
68
- statement['SELECT']['withClause']['WITHCLAUSE']['ctes'].each do |item|
69
- statements << item['COMMONTABLEEXPR']['ctequery'] if item['COMMONTABLEEXPR']
67
+ if statement[SELECT_STMT]['withClause']
68
+ statement[SELECT_STMT]['withClause'][WITH_CLAUSE]['ctes'].each do |item|
69
+ statements << item[COMMON_TABLE_EXPR]['ctequery'] if item[COMMON_TABLE_EXPR]
70
70
  end
71
71
  end
72
- elsif statement['SELECT']['op'] == 1
73
- statements << statement['SELECT']['larg'] if statement['SELECT']['larg']
74
- statements << statement['SELECT']['rarg'] if statement['SELECT']['rarg']
72
+ elsif statement[SELECT_STMT]['op'] == 1
73
+ statements << statement[SELECT_STMT]['larg'] if statement[SELECT_STMT]['larg']
74
+ statements << statement[SELECT_STMT]['rarg'] if statement[SELECT_STMT]['rarg']
75
75
  end
76
- when 'INSERT INTO', 'UPDATE', 'DELETE FROM', 'VACUUM', 'COPY', 'ALTER TABLE', 'CREATESTMT', 'INDEXSTMT', 'RULESTMT', 'CREATETRIGSTMT'
76
+ when INSERT_STMT, UPDATE_STMT, DELETE_STMT, VACUUM_STMT, COPY_STMT, ALTER_TABLE_STMT, CREATE_STMT, INDEX_STMT, RULE_STMT, CREATE_TRIG_STMT
77
77
  from_clause_items << statement.values[0]['relation']
78
- when 'VIEWSTMT'
79
- from_clause_items << statement['VIEWSTMT']['view']
80
- statements << statement['VIEWSTMT']['query']
81
- when 'REFRESHMATVIEWSTMT'
82
- from_clause_items << statement['REFRESHMATVIEWSTMT']['relation']
83
- when 'EXPLAIN'
84
- statements << statement['EXPLAIN']['query']
85
- when 'CREATE TABLE AS'
86
- if statement['CREATE TABLE AS']['into'] && statement['CREATE TABLE AS']['into']['INTOCLAUSE']['rel']
87
- from_clause_items << statement['CREATE TABLE AS']['into']['INTOCLAUSE']['rel']
78
+ when VIEW_STMT
79
+ from_clause_items << statement[VIEW_STMT]['view']
80
+ statements << statement[VIEW_STMT]['query']
81
+ when REFRESH_MAT_VIEW_STMT
82
+ from_clause_items << statement[REFRESH_MAT_VIEW_STMT]['relation']
83
+ when EXPLAIN_STMT
84
+ statements << statement[EXPLAIN_STMT]['query']
85
+ when CREATE_TABLE_AS_STMT
86
+ if statement[CREATE_TABLE_AS_STMT]['into'] && statement[CREATE_TABLE_AS_STMT]['into'][INTO_CLAUSE]['rel']
87
+ from_clause_items << statement[CREATE_TABLE_AS_STMT]['into'][INTO_CLAUSE]['rel']
88
88
  end
89
- when 'LOCK', 'TRUNCATE'
89
+ when LOCK_STMT, TRUNCATE_STMT
90
90
  from_clause_items += statement.values[0]['relations']
91
- when 'GRANTSTMT'
92
- objects = statement['GRANTSTMT']['objects']
93
- case statement['GRANTSTMT']['objtype']
91
+ when GRANT_STMT
92
+ objects = statement[GRANT_STMT]['objects']
93
+ case statement[GRANT_STMT]['objtype']
94
94
  when 0 # Column
95
95
  # FIXME
96
96
  when 1 # Table
@@ -98,14 +98,12 @@ class PgQuery
98
98
  when 2 # Sequence
99
99
  # FIXME
100
100
  end
101
- when 'DROP'
102
- objects = statement['DROP']['objects']
103
- case statement['DROP']['removeType']
104
- when 26 # Table
101
+ when DROP_STMT
102
+ objects = statement[DROP_STMT]['objects'].map { |list| list.map { |obj| obj['String']['str'] } }
103
+ case statement[DROP_STMT]['removeType']
104
+ when OBJECT_TYPE_TABLE
105
105
  @tables += objects.map { |r| r.join('.') }
106
- when 23 # Rule
107
- @tables += objects.map { |r| r[0..-2].join('.') }
108
- when 28 # Trigger
106
+ when OBJECT_TYPE_RULE, OBJECT_TYPE_TRIGGER
109
107
  @tables += objects.map { |r| r[0..-2].join('.') }
110
108
  end
111
109
  end
@@ -117,7 +115,7 @@ class PgQuery
117
115
  next_item = where_clause_items.shift
118
116
  if next_item
119
117
  case next_item.keys[0]
120
- when /^AEXPR/, 'ANY'
118
+ when A_EXPR
121
119
  %w(lexpr rexpr).each do |side|
122
120
  elem = next_item.values[0][side]
123
121
  next unless elem
@@ -127,8 +125,8 @@ class PgQuery
127
125
  where_clause_items << elem
128
126
  end
129
127
  end
130
- when 'SUBLINK'
131
- statements << next_item['SUBLINK']['subselect']
128
+ when SUB_LINK
129
+ statements << next_item[SUB_LINK]['subselect']
132
130
  end
133
131
  end
134
132
 
@@ -140,17 +138,17 @@ class PgQuery
140
138
  break unless next_item
141
139
 
142
140
  case next_item.keys[0]
143
- when 'JOINEXPR'
141
+ when JOIN_EXPR
144
142
  %w(larg rarg).each do |side|
145
- from_clause_items << next_item['JOINEXPR'][side]
143
+ from_clause_items << next_item[JOIN_EXPR][side]
146
144
  end
147
- when 'ROW'
148
- from_clause_items += next_item['ROW']['args']
149
- when 'RANGEVAR'
150
- rangevar = next_item['RANGEVAR']
145
+ when ROW_EXPR
146
+ from_clause_items += next_item[ROW_EXPR]['args']
147
+ when RANGE_VAR
148
+ rangevar = next_item[RANGE_VAR]
151
149
  table = [rangevar['schemaname'], rangevar['relname']].compact.join('.')
152
150
  @tables << table
153
- @aliases[rangevar['alias']['ALIAS']['aliasname']] = table if rangevar['alias']
151
+ @aliases[rangevar['alias'][ALIAS]['aliasname']] = table if rangevar['alias']
154
152
  end
155
153
  end
156
154
 
@@ -30,18 +30,24 @@ class PgQuery
30
30
  end
31
31
  end
32
32
 
33
- def deep_dup(obj)
34
- case obj
35
- when Hash
36
- obj.each_with_object(obj.dup) do |(key, value), hash|
37
- hash[deep_dup(key)] = deep_dup(value)
33
+ def transform_nodes!(parsetree, &block)
34
+ result = deep_dup(parsetree)
35
+ exprs = result.dup
36
+
37
+ loop do
38
+ expr = exprs.shift
39
+
40
+ if expr.is_a?(Hash)
41
+ block.call(expr) if expr.size == 1 && expr.keys[0][/^[A-Z]+/]
42
+
43
+ exprs += expr.values.compact
44
+ elsif expr.is_a?(Array)
45
+ exprs += expr
38
46
  end
39
- when Array
40
- obj.map { |it| deep_dup(it) }
41
- when NilClass, FalseClass, TrueClass, Symbol, Numeric
42
- obj # Can't be duplicated
43
- else
44
- obj.dup
47
+
48
+ break if exprs.empty?
45
49
  end
50
+
51
+ result
46
52
  end
47
53
  end