pg_ddm_sql_modifier 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +0 -0
- data/LICENSE +674 -0
- data/README.md +1 -0
- data/Rakefile +23 -0
- data/lib/pg_ddm_sql_modifier.rb +3 -0
- data/lib/pg_ddm_sql_modifier/parser.rb +498 -0
- data/lib/pg_ddm_sql_modifier/version.rb +4 -0
- metadata +153 -0
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# pg_ddm_sql_modifier
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/extensiontask'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
require 'rubocop/rake_task'
|
5
|
+
|
6
|
+
Rake::ExtensionTask.new 'pg_ddm_sql_modifier' do |ext|
|
7
|
+
ext.lib_dir = 'lib/pg_ddm_sql_modifier'
|
8
|
+
end
|
9
|
+
|
10
|
+
RSpec::Core::RakeTask.new
|
11
|
+
RuboCop::RakeTask.new
|
12
|
+
|
13
|
+
task spec: :compile
|
14
|
+
|
15
|
+
task default: %i[lint spec]
|
16
|
+
task test: :spec
|
17
|
+
task lint: :rubocop
|
18
|
+
|
19
|
+
task :clean do
|
20
|
+
FileUtils.rm_rf File.join(__dir__, 'tmp/')
|
21
|
+
FileUtils.rm_f Dir.glob(File.join(__dir__, 'ext/pg_query/*.o'))
|
22
|
+
FileUtils.rm_f File.join(__dir__, 'lib/pg_query/pg_ddm_sql_modifier.bundle')
|
23
|
+
end
|
@@ -0,0 +1,498 @@
|
|
1
|
+
require 'pg_query'
|
2
|
+
require 'json'
|
3
|
+
require 'etcdv3'
|
4
|
+
require 'hashie'
|
5
|
+
|
6
|
+
class PgQueryOpt
|
7
|
+
@sql = nil
|
8
|
+
@query_parser = nil
|
9
|
+
@table_list = []
|
10
|
+
@remove_ref = 0
|
11
|
+
@etcd = nil
|
12
|
+
@etcd_host = 'localhost'
|
13
|
+
@etcd_port = '2379'
|
14
|
+
@etcd_user = nil
|
15
|
+
@etcd_passwd = nil
|
16
|
+
@pass_tag = ''
|
17
|
+
@tag_regex = ''
|
18
|
+
@data_in_etcd = {}
|
19
|
+
@col_list = {}
|
20
|
+
@ref = nil
|
21
|
+
@groups = {}
|
22
|
+
@return_column_ref = {}
|
23
|
+
@in_function = 0
|
24
|
+
@name = nil
|
25
|
+
@mask = true
|
26
|
+
@change_name = nil
|
27
|
+
@mode = 'select'
|
28
|
+
|
29
|
+
def self.hi
|
30
|
+
puts "Hello world!"
|
31
|
+
end
|
32
|
+
|
33
|
+
def properties(sql, username, db, etcd_host, etcd_port, etcd_user, etcd_passwd, user_regex, tag_regex, default_scheme, tag_users)
|
34
|
+
|
35
|
+
@sql = sql
|
36
|
+
@username = username
|
37
|
+
@db = db
|
38
|
+
@etcd_host = etcd_host
|
39
|
+
@etcd_port = etcd_port
|
40
|
+
@etcd_user = etcd_user
|
41
|
+
@etcd_passwd = etcd_passwd
|
42
|
+
@user_regex = user_regex
|
43
|
+
@tag_regex = tag_regex
|
44
|
+
@data_in_etcd = {}
|
45
|
+
@default_scheme = default_scheme
|
46
|
+
@in_function = 0
|
47
|
+
@name = nil
|
48
|
+
@mask = true
|
49
|
+
@change_name = nil
|
50
|
+
@mode = 'select'
|
51
|
+
|
52
|
+
|
53
|
+
tag_users = tag_users.delete(' ').split(',')
|
54
|
+
if tag_users.include?(username)
|
55
|
+
@pass_tag = /#{@tag_regex}/.match(@sql)
|
56
|
+
return @sql if @pass_tag
|
57
|
+
end
|
58
|
+
|
59
|
+
conn_etcd
|
60
|
+
|
61
|
+
@query_parser = PgQuery.parse(@sql)
|
62
|
+
@sql = @sql.strip
|
63
|
+
|
64
|
+
@tag_sql = %r{(?<=^/\*)([^\*]*)(?=\*/)}.match(@sql)
|
65
|
+
@tag_sql = @tag_sql ? '/* ' + @tag_sql[1].strip + ' */' : ''
|
66
|
+
|
67
|
+
if @user_id.nil?
|
68
|
+
@user_id = /#{@user_regex}/.match(@sql)
|
69
|
+
|
70
|
+
@user_id = @user_id[1].strip if @user_id
|
71
|
+
end
|
72
|
+
|
73
|
+
user_to_group
|
74
|
+
|
75
|
+
return @sql if @groups.empty?
|
76
|
+
|
77
|
+
i = 0
|
78
|
+
@query_parser.tree.each do |parse_item|
|
79
|
+
@query_parser.tree[i] = parse(parse_item)
|
80
|
+
i += 1
|
81
|
+
end
|
82
|
+
@tag_sql + @query_parser.deparse
|
83
|
+
rescue StandardError => e
|
84
|
+
puts e
|
85
|
+
puts e.backtrace.to_s
|
86
|
+
@sql
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
def get_role(sql)
|
91
|
+
parser = if @sql
|
92
|
+
@query_parser
|
93
|
+
else
|
94
|
+
PgQuery.parse(sql)
|
95
|
+
end
|
96
|
+
tree = parser.tree
|
97
|
+
tree.extend Hashie::Extensions::DeepFind
|
98
|
+
keys = tree.deep_find_all('FuncCall')
|
99
|
+
keys2 = tree.deep_find_all('TransactionStmt')
|
100
|
+
keys3 = tree.deep_find_all('SelectStmt')
|
101
|
+
|
102
|
+
|
103
|
+
if keys.nil? && keys2.nil? && !keys3.nil?
|
104
|
+
'read'
|
105
|
+
else
|
106
|
+
'master'
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def conn_etcd
|
111
|
+
return unless @etcd.nil?
|
112
|
+
|
113
|
+
@etcd = if @etcd_user.empty?
|
114
|
+
Etcdv3.new(endpoints: 'http://' + @etcd_host + ':' + @etcd_port, command_timeout: 5)
|
115
|
+
else
|
116
|
+
Etcdv3.new(endpoints: 'http://' + @etcd_host + ':' + @etcd_port, command_timeout: 5, user: @etcd_user, password: @etcd_passwd)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def user_to_group
|
121
|
+
data = []
|
122
|
+
@groups = {}
|
123
|
+
key_replace = []
|
124
|
+
|
125
|
+
unless @username.nil?
|
126
|
+
filter = '/dbuser/' + @username.strip
|
127
|
+
key_replace.push(filter)
|
128
|
+
data += @etcd.get(filter, range_end: filter + '0').kvs
|
129
|
+
end
|
130
|
+
|
131
|
+
unless @user_id.nil?
|
132
|
+
filter = '/users/' + @user_id.strip
|
133
|
+
key_replace.push(filter)
|
134
|
+
data += @etcd.get(filter, range_end: filter + '0').kvs
|
135
|
+
end
|
136
|
+
|
137
|
+
filter = '/users/*'
|
138
|
+
key_replace.push(filter)
|
139
|
+
data += @etcd.get(filter, range_end: filter + '0').kvs
|
140
|
+
|
141
|
+
filter = '/dbuser/*'
|
142
|
+
key_replace.push(filter)
|
143
|
+
data += @etcd.get(filter, range_end: filter + '0').kvs
|
144
|
+
|
145
|
+
|
146
|
+
i = 0
|
147
|
+
data.each do |val|
|
148
|
+
val_obj = JSON.parse(val.value)
|
149
|
+
if val_obj['enabled'].to_s == 'false'
|
150
|
+
data.delete_at(i)
|
151
|
+
else
|
152
|
+
key = val.key.dup
|
153
|
+
key_replace.each { |key_data| key.gsub! key_data, '' }
|
154
|
+
@groups[key] = {}
|
155
|
+
end
|
156
|
+
i += 1
|
157
|
+
end
|
158
|
+
|
159
|
+
data
|
160
|
+
end
|
161
|
+
|
162
|
+
def etcd_data(filter_id)
|
163
|
+
if @data_in_etcd[filter_id].nil?
|
164
|
+
data = @etcd.get(filter_id, range_end: filter_id + '0')
|
165
|
+
@data_in_etcd[filter_id] = data
|
166
|
+
end
|
167
|
+
return {} if @data_in_etcd[filter_id].kvs.count.zero?
|
168
|
+
|
169
|
+
JSON.parse(@data_in_etcd[filter_id].kvs.first.value)
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
def get_string(node)
|
174
|
+
return node if node.is_a?(String)
|
175
|
+
return node['String']['str'] unless node['String'].nil?
|
176
|
+
return nil unless node['A_Star'].nil?
|
177
|
+
|
178
|
+
node.to_s
|
179
|
+
end
|
180
|
+
|
181
|
+
def get_alias(node)
|
182
|
+
if node.is_a?(Array)
|
183
|
+
alias_array = []
|
184
|
+
node.each do |col|
|
185
|
+
alias_array.push(get_string(col))
|
186
|
+
end
|
187
|
+
alias_array.join('.')
|
188
|
+
else
|
189
|
+
get_string(node)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def get_col_with_table(schema, table)
|
194
|
+
etcd_data('/' + @db + '/' + schema + '/' + table)
|
195
|
+
end
|
196
|
+
|
197
|
+
# @param [Object] ref
|
198
|
+
# @param [Object] table_list
|
199
|
+
# @return [Hash{null->null}]
|
200
|
+
def mask(ref, table_list)
|
201
|
+
return_column_ref = {}
|
202
|
+
if ref.count == 1
|
203
|
+
table_list.each do |alias_name, table|
|
204
|
+
next unless table['columns'].find { |col| !col.key(get_string(ref[-1])).nil? }
|
205
|
+
|
206
|
+
ref = alias_name.split('.') + [get_string(ref[-1])]
|
207
|
+
break
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
tab = table_list[get_alias(ref.first(ref.count - 1))] unless table_list.empty?
|
212
|
+
unless tab.nil?
|
213
|
+
filter = '/rules/' + @db + '/' + tab['schema'] + '/' + tab['table']
|
214
|
+
data = {}
|
215
|
+
@groups.each do |key, value|
|
216
|
+
group = etcd_data(key)
|
217
|
+
next if group['enabled'].to_s == 'false'
|
218
|
+
|
219
|
+
data_temp = etcd_data(filter + '/' + get_string(ref[-1]) + key)
|
220
|
+
next if data_temp['enabled'].to_s == 'false'
|
221
|
+
|
222
|
+
if data_temp.empty?
|
223
|
+
data = {}
|
224
|
+
else
|
225
|
+
data = data_temp
|
226
|
+
break
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
unless data.empty?
|
231
|
+
name = if @name.nil?
|
232
|
+
get_string(ref[-1])
|
233
|
+
elsif @name.is_a?(String)
|
234
|
+
@name
|
235
|
+
end
|
236
|
+
|
237
|
+
if data['rule'] == 'send_null'
|
238
|
+
return_column_ref = { 'ResTarget' => { 'name' => name, 'val' => { 'A_Const' => { 'val' => { 'Null' => {} } } } } }
|
239
|
+
elsif data['rule'] == 'delete_col'
|
240
|
+
return_column_ref = { 'del' => 1 }
|
241
|
+
else
|
242
|
+
change_colname = JSON.parse(data['prop'].gsub('%col%', { 'ColumnRef' => { 'fields' => ref } }.to_json))
|
243
|
+
# TODO: Schema is not dynamic
|
244
|
+
func = { 'funcname' => [{ 'String' => { 'str' => 'mask' } }, { 'String' => { 'str' => data['rule'] } }], 'args' => change_colname }
|
245
|
+
|
246
|
+
return_column_ref = { 'ResTarget' => { 'name' => name, 'val' => { 'FuncCall' => func } } }
|
247
|
+
end
|
248
|
+
if data['filter'] != ''
|
249
|
+
filter_tables = {}
|
250
|
+
filter_tables[tab['alias']] = tab
|
251
|
+
|
252
|
+
filter_where = get_filters(filter_tables, data)
|
253
|
+
return_column_ref = { 'ResTarget' => { 'name' => name, 'val' => { 'CaseExpr' => { 'args' => [{ 'CaseWhen' => { 'expr' => filter_where[0], 'result' => return_column_ref['ResTarget']['val'] } }], 'defresult' => { 'ColumnRef' => { 'fields' => ref } } } } } }
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
return_column_ref
|
258
|
+
end
|
259
|
+
|
260
|
+
# @param [Object] table_list
|
261
|
+
# @param [Object] where_part
|
262
|
+
def get_filters(table_list, where_part = nil)
|
263
|
+
filter_w = []
|
264
|
+
table_list.each do |alias_name, table|
|
265
|
+
filter_list = []
|
266
|
+
next if table['schema'].nil?
|
267
|
+
|
268
|
+
if where_part.nil?
|
269
|
+
@groups.each do |key, value|
|
270
|
+
filter_temp = etcd_data('/sqlfilter/' + @db + '/' + table['schema'] + '/' + table['table'] + key)
|
271
|
+
next if filter_temp.empty?
|
272
|
+
next if filter_temp['enabled'].to_s == 'false'
|
273
|
+
|
274
|
+
filter_list.push(filter_temp)
|
275
|
+
end
|
276
|
+
filter_temp = etcd_data('/sqlfilter/' + @db + '/' + table['schema'] + '/' + table['table'] + '/*')
|
277
|
+
|
278
|
+
unless filter_temp.empty?
|
279
|
+
filter_list.push(filter_temp) if filter_temp['enabled'].to_s == 'true'
|
280
|
+
end
|
281
|
+
else
|
282
|
+
next unless (@db + '.' + table['schema'] + '.' + table['table']) == where_part['table_column'].split('.')[0...-1].join('.')
|
283
|
+
|
284
|
+
filter_list.push(where_part)
|
285
|
+
end
|
286
|
+
next if filter_list.count.zero?
|
287
|
+
|
288
|
+
filter_list.each do |filter|
|
289
|
+
where_condition = filter['filter'].gsub(table['schema'] + '.' + table['table'], alias_name)
|
290
|
+
where_query = PgQuery.parse('SELECT WHERE ' + where_condition)
|
291
|
+
filter_w.push(where_query.tree[0]['RawStmt']['stmt']['SelectStmt']['whereClause'])
|
292
|
+
end
|
293
|
+
end
|
294
|
+
filter_w
|
295
|
+
end
|
296
|
+
|
297
|
+
# @param [Object] items
|
298
|
+
# @param [Array] table_list
|
299
|
+
def parse(items, table_list = [], masked = true)
|
300
|
+
old_table_list = []
|
301
|
+
return if items.nil?
|
302
|
+
|
303
|
+
if items.is_a?(Hash)
|
304
|
+
if items.keys.is_a?(Array)
|
305
|
+
items.keys.each do |item|
|
306
|
+
case item
|
307
|
+
when 'fields'
|
308
|
+
@ref = items[item]
|
309
|
+
@return_column_ref = mask(@ref, table_list) if @ref[-1]['A_Star'].nil? && masked == true && @mode == 'select'
|
310
|
+
@name = nil
|
311
|
+
when 'ResTarget'
|
312
|
+
if @mode == 'update' && masked == true
|
313
|
+
masked_field = mask([items[item]['name']], table_list)
|
314
|
+
items[item] = { "name" => masked_field[item]['name'], "val" => { "CaseExpr" => { "args" => [{ "CaseWhen" => { "expr" => { "A_Expr" => { "kind" => 0, "name" => [{ "String" => { "str" => "=" } }], "lexpr" => masked_field[item]['val'], "rexpr" => items[item]['val'] } }, "result" => { "ColumnRef" => { "fields" => [items[item]['name']] } } } }], "defresult" => items[item]['val'] } } } unless masked_field.empty?
|
315
|
+
end
|
316
|
+
when 'name'
|
317
|
+
@name = items[item]
|
318
|
+
when 'ColumnRef', 'args'
|
319
|
+
@remove_ref = 3
|
320
|
+
when 'funcname'
|
321
|
+
@in_function = 1 if get_string(items[item][0]) == 'count'
|
322
|
+
when 'A_Star'
|
323
|
+
if @in_function.zero?
|
324
|
+
@col_list = {}
|
325
|
+
case @ref.count
|
326
|
+
when 1
|
327
|
+
table_list.each do |alias_name, table|
|
328
|
+
@col_list[alias_name] = table['columns']
|
329
|
+
end
|
330
|
+
when 2, 3
|
331
|
+
alias_name = get_alias(@ref.first(@ref.count - 1))
|
332
|
+
@col_list[alias_name] = table_list[alias_name]['columns'] unless table_list[alias_name].nil?
|
333
|
+
else
|
334
|
+
raise 'SQL exception check your alias names (' + get_alias(@ref) + ')'
|
335
|
+
end
|
336
|
+
@remove_ref = 1
|
337
|
+
else
|
338
|
+
@in_function = 0
|
339
|
+
end
|
340
|
+
when 'SelectStmt', 'UpdateStmt'
|
341
|
+
@mode = if item == 'UpdateStmt'
|
342
|
+
'update'
|
343
|
+
else
|
344
|
+
'select'
|
345
|
+
end
|
346
|
+
old_table_list = table_list
|
347
|
+
table_list = find_table_list(items[item])
|
348
|
+
filters = get_filters(table_list)
|
349
|
+
when 'InsertStmt'
|
350
|
+
return items
|
351
|
+
when 'A_Expr'
|
352
|
+
if items[item].is_a?(Hash)
|
353
|
+
if items[item].include?('name')
|
354
|
+
masked = false unless get_string(items[item]['name'][0]) == '||'
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
|
360
|
+
parse(items[item], table_list, masked)
|
361
|
+
|
362
|
+
masked = true if item == 'A_Expr'
|
363
|
+
@name = nil if item == 'name'
|
364
|
+
|
365
|
+
@remove_ref = 2 if item == 'ResTarget' && @remove_ref == 1
|
366
|
+
|
367
|
+
if item == 'ResTarget'
|
368
|
+
unless items[item].include?('name')
|
369
|
+
items[item]['name'] = @change_name unless @change_name.nil?
|
370
|
+
end
|
371
|
+
@change_name = nil
|
372
|
+
end
|
373
|
+
|
374
|
+
if item == 'fields' && @remove_ref == 3 && !@return_column_ref.nil? && @return_column_ref['del'].nil?
|
375
|
+
unless @return_column_ref.empty?
|
376
|
+
items.delete(item)
|
377
|
+
items[item] = [@return_column_ref['ResTarget']['val']]
|
378
|
+
@change_name = @return_column_ref['ResTarget']['name'] if @return_column_ref['ResTarget'].include?('name')
|
379
|
+
end
|
380
|
+
@return_column_ref = {}
|
381
|
+
end
|
382
|
+
|
383
|
+
if item == 'ResTarget' && !@return_column_ref.nil?
|
384
|
+
unless @return_column_ref.empty?
|
385
|
+
if @return_column_ref['del'].nil?
|
386
|
+
items[item] = @return_column_ref['ResTarget']
|
387
|
+
else
|
388
|
+
items.delete(item)
|
389
|
+
end
|
390
|
+
end
|
391
|
+
@return_column_ref = {}
|
392
|
+
end
|
393
|
+
|
394
|
+
if !filters.nil? && item == 'SelectStmt'
|
395
|
+
if filters.count > 0
|
396
|
+
filters.push(items['SelectStmt']['whereClause']) unless items['SelectStmt']['whereClause'].nil?
|
397
|
+
items['SelectStmt']['whereClause'] = { 'BoolExpr' => { 'boolop' => 0, 'args' => filters } }
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
table_list = old_table_list if %w[SelectStmt UpdateStmt].include?(item)
|
402
|
+
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
if items.is_a?(Array)
|
408
|
+
i = 0
|
409
|
+
items.each do |item|
|
410
|
+
parse(item, table_list, masked)
|
411
|
+
|
412
|
+
reparse = 0
|
413
|
+
unless item.nil?
|
414
|
+
if item.empty?
|
415
|
+
items.delete_at(i)
|
416
|
+
reparse = 1
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
if @remove_ref == 2
|
421
|
+
k = 1
|
422
|
+
|
423
|
+
@col_list.each do |alias_name, alias_table|
|
424
|
+
next if alias_table.nil?
|
425
|
+
|
426
|
+
if alias_table.count > 0 && reparse.zero?
|
427
|
+
reparse = 1
|
428
|
+
items.delete_at(i)
|
429
|
+
k -= 1
|
430
|
+
end
|
431
|
+
alias_table.each do |col|
|
432
|
+
fields = alias_name.split('.').push(col['column_name'])
|
433
|
+
items.insert(i + k, 'ResTarget' => { 'val' => { 'ColumnRef' => { 'fields' => fields } } })
|
434
|
+
k += 1
|
435
|
+
end
|
436
|
+
end
|
437
|
+
@col_list = {}
|
438
|
+
@remove_ref = 0
|
439
|
+
|
440
|
+
end
|
441
|
+
parse(items[i], table_list, masked) if reparse == 1
|
442
|
+
unless items[i].nil?
|
443
|
+
if items[i].empty?
|
444
|
+
items.delete_at(i)
|
445
|
+
end
|
446
|
+
end
|
447
|
+
i += 1
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
items
|
452
|
+
end
|
453
|
+
|
454
|
+
def find_table_list(tree, table_list = {})
|
455
|
+
key_list = %w[SelectStmt UpdateStmt relation fromClause JoinExpr larg rarg]
|
456
|
+
if tree.is_a?(Array)
|
457
|
+
tree.each do |k|
|
458
|
+
find_table_list(k, table_list)
|
459
|
+
end
|
460
|
+
else
|
461
|
+
tree.keys.each do |key|
|
462
|
+
find_table_list(tree[key], table_list) if key_list.include?(key)
|
463
|
+
end
|
464
|
+
|
465
|
+
unless tree['RangeVar'].nil?
|
466
|
+
table = {}
|
467
|
+
table['table'] = tree['RangeVar']['relname']
|
468
|
+
if tree['RangeVar']['schemaname'].nil?
|
469
|
+
unless @default_scheme.nil?
|
470
|
+
@default_scheme.split(',').each do |scheme|
|
471
|
+
scheme = scheme.strip
|
472
|
+
table_defination = get_col_with_table(scheme, table['table'])
|
473
|
+
next if table_defination.empty?
|
474
|
+
|
475
|
+
table['schema'] = scheme
|
476
|
+
break
|
477
|
+
end
|
478
|
+
end
|
479
|
+
else
|
480
|
+
table['schema'] = tree['RangeVar']['schemaname']
|
481
|
+
end
|
482
|
+
table['alias'] = if tree['RangeVar']['alias'].nil?
|
483
|
+
if tree['RangeVar']['schemaname'].nil?
|
484
|
+
table['table']
|
485
|
+
else
|
486
|
+
table['schema'] + '.' + table['table']
|
487
|
+
end
|
488
|
+
else
|
489
|
+
tree['RangeVar']['alias']['Alias']['aliasname']
|
490
|
+
end
|
491
|
+
|
492
|
+
table['columns'] = get_col_with_table(table['schema'], table['table']) unless table['schema'].nil?
|
493
|
+
table_list[table['alias']] = table
|
494
|
+
end
|
495
|
+
end
|
496
|
+
table_list
|
497
|
+
end
|
498
|
+
end
|