pg_ddm_sql_modifier 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ # pg_ddm_sql_modifier
@@ -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,3 @@
1
+ require 'pg_ddm_sql_modifier/version'
2
+ require 'pg_ddm_sql_modifier/parser'
3
+
@@ -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