query_police 0.1.2.beta → 0.1.3.beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +9 -0
- data/README.md +106 -65
- data/lib/query_police/analyse.rb +100 -0
- data/lib/query_police/analysis/dynamic_message.rb +16 -7
- data/lib/query_police/analysis.rb +88 -44
- data/lib/query_police/rules.json +55 -16
- data/lib/query_police/transform.rb +31 -0
- data/lib/query_police/version.rb +1 -1
- data/lib/query_police.rb +9 -89
- metadata +50 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27f13db4425088a65a440e6f5bc0092898f45b3bda80e20a8f02c810153eca9b
|
4
|
+
data.tar.gz: 7a75948db5efee3b2a21b4b2f1479f0640c1f270b8b95cebe2d04079d43f9eb9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 37bf74b29105ed8a838019bda964395daadfe9241f4b14f91dee9ac306d5374c6b8faf08fb090df9fdcc7f52394c47d5973079c2f31a9ff4fc20434f9bff33df
|
7
|
+
data.tar.gz: b02df2bee3c473856e8a2edd0956cf39670d58d20b7f8f66f6b63d7e749e2fa01de82bd0f898b08ff223992ebb1936599765c3dc090423c7e974776e895e10de
|
data/.rubocop.yml
CHANGED
@@ -9,6 +9,15 @@ Style/StringLiteralsInInterpolation:
|
|
9
9
|
Enabled: true
|
10
10
|
EnforcedStyle: double_quotes
|
11
11
|
|
12
|
+
Style/HashTransformKeys:
|
13
|
+
Enabled: true
|
14
|
+
|
15
|
+
Style/HashTransformValues:
|
16
|
+
Enabled: true
|
17
|
+
|
18
|
+
Style/HashEachMethods:
|
19
|
+
Enabled: true
|
20
|
+
|
12
21
|
Metrics/MethodLength:
|
13
22
|
Max: 15
|
14
23
|
|
data/README.md
CHANGED
@@ -4,11 +4,11 @@ It is a rule-based engine with custom rules to Analyze Active-Record relations u
|
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
7
|
-
Install the gem and add to the application's Gemfile by executing:
|
7
|
+
Install the gem and add it to the application's Gemfile by executing:
|
8
8
|
|
9
9
|
$ bundle add query_police
|
10
10
|
|
11
|
-
If bundler is not being used to manage dependencies, install the gem by executing:
|
11
|
+
If the bundler is not being used to manage dependencies, install the gem by executing:
|
12
12
|
|
13
13
|
$ gem install query_police
|
14
14
|
|
@@ -28,8 +28,7 @@ puts analysis.pretty_analysis_for(<impact>)
|
|
28
28
|
**Eg.**
|
29
29
|
```
|
30
30
|
analysis = QueryPolice.analyse(
|
31
|
-
User.joins('join
|
32
|
-
.where('sessions.created_at < ?', Time.now - 5.months).order('sessions.created_at')
|
31
|
+
User.joins('join orders on orders.user_id = users.id')
|
33
32
|
)
|
34
33
|
puts analysis.pretty_analysis_for('negative')
|
35
34
|
# or
|
@@ -38,22 +37,45 @@ puts analysis.pretty_analysis({'negative' => true, 'positive' => true})
|
|
38
37
|
**Results**
|
39
38
|
|
40
39
|
```
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
40
|
+
query_score: 330.0
|
41
|
+
|
42
|
+
+----------------------------------------------------------------------------------------------------------------------------------+
|
43
|
+
| orders |
|
44
|
+
+------------+---------------------------------------------------------------------------------------------------------------------+
|
45
|
+
| score | 200.0 |
|
46
|
+
+------------+---------------------------------------------------------------------------------------------------------------------+
|
47
|
+
| column | type |
|
48
|
+
| impact | negative |
|
49
|
+
| tag_score | 100.0 |
|
50
|
+
| message | Entire orders table is scanned to find matching rows, you have 0 possible keys to use. |
|
51
|
+
| suggestion | Use index here. You can use index from possible key: absent or add new one to orders table as per the requirements. |
|
52
|
+
+------------+---------------------------------------------------------------------------------------------------------------------+
|
53
|
+
| column | possible_keys |
|
54
|
+
| impact | negative |
|
55
|
+
| tag_score | 50.0 |
|
56
|
+
| message | There are no possible keys for orders table to be used, can result into full scan |
|
57
|
+
| suggestion | Please add index keys for orders table |
|
58
|
+
+------------+---------------------------------------------------------------------------------------------------------------------+
|
59
|
+
| column | key |
|
60
|
+
| impact | negative |
|
61
|
+
| tag_score | 50.0 |
|
62
|
+
| message | There is no index key used for orders table, and can result into full scan of the orders table |
|
63
|
+
| suggestion | Please use index from possible_keys: absent or add new one to orders table as per the requirements. |
|
64
|
+
+------------+---------------------------------------------------------------------------------------------------------------------+
|
65
|
+
+------------------------------------------------------------------------------------+
|
66
|
+
| users |
|
67
|
+
+------------+-----------------------------------------------------------------------+
|
68
|
+
| score | 130.0 |
|
69
|
+
+------------+-----------------------------------------------------------------------+
|
70
|
+
| column | detailed#used_columns |
|
71
|
+
| impact | negative |
|
72
|
+
| tag_score | 130.0 |
|
73
|
+
| message | You have selected 18 columns, You should not select too many columns. |
|
74
|
+
| suggestion | Please only select required columns. |
|
75
|
+
+------------+-----------------------------------------------------------------------+
|
76
|
+
```
|
77
|
+
|
78
|
+
### Add a logger for every query
|
57
79
|
|
58
80
|
Add `QueryPolice.subscribe_logger` to your initial load file like `application.rb`
|
59
81
|
|
@@ -61,24 +83,24 @@ You can make logger silence of error using `QueryPolice.subscribe_logger silent:
|
|
61
83
|
|
62
84
|
You can change logger config using `QueryPolice logger_config: <config>`, default logger_config `{'negative' => true}`, options `positive: <Boolean>, caution: <Boolean>`.
|
63
85
|
|
64
|
-
|
86
|
+
---
|
65
87
|
|
66
88
|
## How it works?
|
67
89
|
|
68
|
-
1. Query police converts the relation into
|
90
|
+
1. Query police converts the relation into SQL query
|
69
91
|
|
70
|
-
2. Query police generates execution plan using EXPLAIN and EXPLAIN format=JSON based on the configuration.
|
92
|
+
2. Query police generates an execution plan using EXPLAIN and EXPLAIN format=JSON based on the configuration.
|
71
93
|
|
72
94
|
3. Query police load rules from the config file.
|
73
95
|
|
74
96
|
4. Query police apply rules on the execution plan and generate a new analysis object.
|
75
97
|
|
76
|
-
5. Analysis object
|
98
|
+
5. Analysis object provides different methods to print the analysis in a more descriptive format.
|
77
99
|
|
78
100
|
|
79
101
|
## Execution plan
|
80
102
|
|
81
|
-
We have 2 possible execution
|
103
|
+
We have 2 possible execution plans:-
|
82
104
|
|
83
105
|
Normal - using `EXPLAIN`
|
84
106
|
|
@@ -98,7 +120,7 @@ Generated using `EXPAIN <query>`
|
|
98
120
|
| 1 | SIMPLE | users | NULL | eq_ref | PRIMARY,index_users_on_id | PRIMARY | 4 | development.profile.user_id |1 | 100.00 | NULL |
|
99
121
|
|
100
122
|
|
101
|
-
|
123
|
+
The result for this is added as it is in the final execution plan
|
102
124
|
|
103
125
|
**Eg.**
|
104
126
|
|
@@ -172,7 +194,7 @@ Generated using `EXPAIN format=JSON <query>`
|
|
172
194
|
```
|
173
195
|
|
174
196
|
|
175
|
-
|
197
|
+
The result for this is added in the flattened form to the final execution plan, where the `detailed#` prefix is added before each key.
|
176
198
|
|
177
199
|
**Truncated Eg.**
|
178
200
|
|
@@ -200,27 +222,27 @@ Result for this is added in flatten form to final execution plan, where `detaile
|
|
200
222
|
|
201
223
|
## Analysis object
|
202
224
|
|
203
|
-
Analysis object stores a detailed analysis report of a relation inside `:tables :
|
225
|
+
Analysis object stores a detailed analysis report of a relation inside `:tables :summary attributes`.
|
204
226
|
|
205
227
|
#### Attributes
|
206
228
|
|
207
|
-
**table_count [Integer] - No. Tables used in the relation**
|
208
|
-
|
209
229
|
**tables [Hash] - detailed table analysis**
|
210
230
|
|
211
231
|
```
|
212
232
|
{
|
213
233
|
'users' => {
|
214
234
|
'id'=>1,
|
215
|
-
'name'=>'users',
|
216
|
-
'
|
217
|
-
|
235
|
+
'name' => 'users', # table alias user in the execution plan
|
236
|
+
'score' => <float> # score for the table
|
237
|
+
'analysis' => {
|
238
|
+
'type' => { # attribute name
|
218
239
|
'value' => <string>, # raw value of attribute in execution plan
|
219
240
|
'tags' => {
|
220
241
|
'all' => { # tag based on the value of a attribute
|
221
242
|
'impact'=> <string>, # negative, positive, cautions
|
222
243
|
'warning'=> <string>, # Eg. 'warning to represent the issue'
|
223
|
-
'suggestions'=> <string> # Eg. 'some follow
|
244
|
+
'suggestions'=> <string> # Eg. 'some follow-up suggestions'
|
245
|
+
'score' => <float> # score for the tag
|
224
246
|
}
|
225
247
|
}
|
226
248
|
}
|
@@ -232,10 +254,11 @@ Analysis object stores a detailed analysis report of a relation inside `:tables
|
|
232
254
|
|
233
255
|
```
|
234
256
|
{
|
235
|
-
'cardinality'=>{
|
236
|
-
'amount'=>10,
|
237
|
-
'warning'=>'warning to represent the issue',
|
238
|
-
'suggestions'=>'some follow up suggestions'
|
257
|
+
'cardinality' => {
|
258
|
+
'amount' => 10,
|
259
|
+
'warning' => 'warning to represent the issue',
|
260
|
+
'suggestions' => 'some follow up suggestions',
|
261
|
+
'score' => 100.0
|
239
262
|
}
|
240
263
|
}
|
241
264
|
```
|
@@ -261,7 +284,11 @@ A basic rule structure -
|
|
261
284
|
"amount": <integer>
|
262
285
|
"impact": <string>,
|
263
286
|
"message": <string>,
|
264
|
-
"suggestion": <string
|
287
|
+
"suggestion": <string>,
|
288
|
+
"score": {
|
289
|
+
"value": <integer>,
|
290
|
+
"type": <string>
|
291
|
+
}
|
265
292
|
}
|
266
293
|
}
|
267
294
|
}
|
@@ -278,19 +305,19 @@ A basic rule structure -
|
|
278
305
|
|
279
306
|
- `<tag>` - direct value match eg. ALL, SIMPLE
|
280
307
|
|
281
|
-
- `absent` - when value is missing
|
308
|
+
- `absent` - when the value is missing
|
282
309
|
|
283
310
|
- `threshold` - a greater than threshold check based on the amount set inside the rule.
|
284
311
|
|
285
|
-
- `amount` - amount of threshold
|
312
|
+
- `amount` - the amount of threshold that needs to check for
|
286
313
|
|
287
314
|
- length for string
|
288
315
|
|
289
316
|
- value for number
|
290
317
|
|
291
|
-
- size for array
|
318
|
+
- size for the array
|
292
319
|
|
293
|
-
- `impact` - impact
|
320
|
+
- `impact` - impact of the rule
|
294
321
|
|
295
322
|
- `negative`
|
296
323
|
|
@@ -298,23 +325,29 @@ A basic rule structure -
|
|
298
325
|
|
299
326
|
- `caution`
|
300
327
|
|
301
|
-
- `message` - message
|
328
|
+
- `message` - the message needs to provide the significance of the rule
|
302
329
|
|
303
330
|
- `suggestion` - suggestion on how we can fix the issue
|
331
|
+
- `score` - score-related config that will be affected to final query score
|
332
|
+
- `value` - value that will be added to the query score
|
333
|
+
- `type` - the type of scoring that will be added to the query score
|
334
|
+
- `base`- value
|
335
|
+
- `relative` - value * (amount for that column in query)
|
336
|
+
- `treshold_relative` - (value - (threshold amount)) * (amount for that column in query)
|
304
337
|
|
305
338
|
|
306
339
|
|
307
340
|
### Dynamic messages and suggestion
|
308
341
|
|
309
|
-
We can define dynamic messages and
|
342
|
+
We can define dynamic messages and suggestions with variables provided by the engine.
|
310
343
|
|
311
|
-
- `$amount` - amount of the value
|
344
|
+
- `$amount` - the amount of the value
|
312
345
|
|
313
346
|
- length for string
|
314
347
|
|
315
348
|
- value for number
|
316
349
|
|
317
|
-
- size for array
|
350
|
+
- size for the array
|
318
351
|
|
319
352
|
- `$column` - attribute name
|
320
353
|
|
@@ -326,7 +359,7 @@ We can define dynamic messages and suggestion with variables provided by the eng
|
|
326
359
|
|
327
360
|
- `$value` - original parsed value
|
328
361
|
|
329
|
-
- `$<column_name>` - value of that specific column in that table
|
362
|
+
- `$<column_name>` - the value of that specific column in that table
|
330
363
|
|
331
364
|
- `$amount_<column_name>` - amount of that specific column
|
332
365
|
|
@@ -349,15 +382,19 @@ We can define dynamic messages and suggestion with variables provided by the eng
|
|
349
382
|
"ALL": {
|
350
383
|
"impact": "negative",
|
351
384
|
"message": "Entire $table table is scanned to find matching rows, you have $amount_possible_keys possible keys to use.",
|
352
|
-
"suggestion": "Use index here. You can use index from possible key: $possible_keys or add new one to $table table as per the requirements."
|
385
|
+
"suggestion": "Use index here. You can use index from possible key: $possible_keys or add new one to $table table as per the requirements.",
|
386
|
+
"score": {
|
387
|
+
"value": 200,
|
388
|
+
"type": "base"
|
389
|
+
}
|
353
390
|
}
|
354
391
|
}
|
355
392
|
```
|
356
|
-
For above rule dynamic message will be generated as-
|
393
|
+
For the above rule, dynamic message will be generated as-
|
357
394
|
```
|
358
395
|
Entire users table is scanned to find matching rows, you have 1 possible keys to use
|
359
396
|
```
|
360
|
-
For above rule dynamic suggestion will be generated as-
|
397
|
+
For the above rule, dynamic suggestion will be generated as-
|
361
398
|
```
|
362
399
|
Use index here. You can use index from possible key: ["PRIMARY", "user_email"] or add new one to users table as per the requirements.
|
363
400
|
```
|
@@ -379,11 +416,11 @@ Use index here. You can use index from possible key: ["PRIMARY", "user_email"] o
|
|
379
416
|
}
|
380
417
|
```
|
381
418
|
|
382
|
-
For above rule dynamic message will be generated as-
|
419
|
+
For the above rule, dynamic message will be generated as-
|
383
420
|
```
|
384
421
|
There is no index key used for users table, and can result into full scan of the users table
|
385
422
|
```
|
386
|
-
For above rule dynamic suggestion will be generated as-
|
423
|
+
For the above rule, dynamic suggestion will be generated as-
|
387
424
|
```
|
388
425
|
Please use index from possible_keys: ["PRIMARY", "user_email"] or add new one to users table as per the requirements.
|
389
426
|
```
|
@@ -406,11 +443,11 @@ Please use index from possible_keys: ["PRIMARY", "user_email"] or add new one to
|
|
406
443
|
}
|
407
444
|
}
|
408
445
|
```
|
409
|
-
For above rule dynamic message will be generated as-
|
446
|
+
For the above rule, dynamic message will be generated as-
|
410
447
|
```
|
411
448
|
There are 10 possible keys for users table, having too many index keys can be unoptimal
|
412
449
|
```
|
413
|
-
For above rule dynamic suggestion will be generated as-
|
450
|
+
For the above rule, dynamic suggestion will be generated as-
|
414
451
|
```
|
415
452
|
Please check if there are extra indexes in users table.
|
416
453
|
```
|
@@ -427,16 +464,20 @@ Please check if there are extra indexes in users table.
|
|
427
464
|
"amount": 7,
|
428
465
|
"impact": "negative",
|
429
466
|
"message": "You have selected $amount columns, You should not select too many columns.",
|
430
|
-
"suggestion": "Please only select required columns."
|
467
|
+
"suggestion": "Please only select required columns.",
|
468
|
+
"score": {
|
469
|
+
"value": 10,
|
470
|
+
"type": "treshold_relative"
|
471
|
+
}
|
431
472
|
}
|
432
473
|
}
|
433
474
|
}
|
434
475
|
```
|
435
|
-
For above rule dynamic message will be generated as-
|
476
|
+
For the above rule, dynamic message will be generated as-
|
436
477
|
```
|
437
478
|
You have selected 10 columns, You should not select too many columns.
|
438
479
|
```
|
439
|
-
For above rule dynamic
|
480
|
+
For the above rule, dynamic suggestions will be generated as-
|
440
481
|
```
|
441
482
|
Please only select required columns.
|
442
483
|
```
|
@@ -444,24 +485,24 @@ Please only select required columns.
|
|
444
485
|
|
445
486
|
### Summary
|
446
487
|
|
447
|
-
You can define similar rules for summary. Current summary attribute supported -
|
488
|
+
You can define similar rules for the summary. Current summary attribute supported -
|
448
489
|
|
449
|
-
- `cardinality` - cardinality based on
|
490
|
+
- `cardinality` - cardinality based on all tables
|
450
491
|
|
451
|
-
**NOTE:** You can add custom summary attributes by defining how to calculate them in `QueryPolice.add_summary` for
|
492
|
+
**NOTE:** You can add custom summary attributes by defining how to calculate them in `QueryPolice.add_summary` for an attribute key.
|
452
493
|
|
453
494
|
|
454
495
|
|
455
496
|
### Attributes
|
456
497
|
|
457
|
-
There
|
498
|
+
There are a lot of attributes for you to use based on the final execution plan.
|
458
499
|
|
459
|
-
You can use normal execution plan attribute directly.
|
500
|
+
You can use the normal execution plan attribute directly.
|
460
501
|
Eg. `select_type, type, Extra, possible_keys`
|
461
502
|
|
462
503
|
To check more keys you can use `EXPLAIN <query>`
|
463
504
|
|
464
|
-
You can use detailed execution plan attribute can be used in
|
505
|
+
You can use the detailed execution plan attribute can be used in flattened form with the `detailed#` prefix.
|
465
506
|
Eg. `detailed#used_columns, detailed#cost_info#read_cost`
|
466
507
|
|
467
508
|
To check more keys you can use `EXPLAIN format=JSON <query>`
|
@@ -484,4 +525,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
484
525
|
|
485
526
|
## Code of Conduct
|
486
527
|
|
487
|
-
Everyone interacting in the QueryPolice project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/query_police/blob/master/CODE_OF_CONDUCT.md).
|
528
|
+
Everyone interacting in the QueryPolice project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/query_police/blob/master/CODE_OF_CONDUCT.md).
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# QueryPolice::Analyse
|
4
|
+
module QueryPolice
|
5
|
+
# This module define analyse methods for query police
|
6
|
+
module Analyse
|
7
|
+
def table(table, summary, rules_config)
|
8
|
+
table_analysis = {}
|
9
|
+
table_score = 0
|
10
|
+
|
11
|
+
table.each do |column, value|
|
12
|
+
summary = add_summary(summary, column, value)
|
13
|
+
next unless rules_config.dig(column).present?
|
14
|
+
|
15
|
+
table_analysis.merge!({ column => apply_rules(rules_config.dig(column), value) })
|
16
|
+
table_score += table_analysis.dig(column, "tags").map { |_, tag| tag.dig("score") }.sum.to_f
|
17
|
+
end
|
18
|
+
|
19
|
+
[table_analysis, summary, table_score]
|
20
|
+
end
|
21
|
+
|
22
|
+
def generate_summary(rules_config, summary)
|
23
|
+
summary_analysis = {}
|
24
|
+
summary_score = 0
|
25
|
+
|
26
|
+
summary.each do |column, value|
|
27
|
+
next unless rules_config.dig(column).present?
|
28
|
+
|
29
|
+
summary_analysis.merge!({ column => apply_rules(rules_config.dig(column), value) })
|
30
|
+
summary_score += summary_analysis.dig(column, "tags").map { |_, tag| tag.dig("score") }.sum.to_f
|
31
|
+
end
|
32
|
+
|
33
|
+
[summary_analysis, summary_score]
|
34
|
+
end
|
35
|
+
|
36
|
+
class << self
|
37
|
+
private
|
38
|
+
|
39
|
+
def add_summary(summary, column_name, value)
|
40
|
+
summary["cardinality"] = (summary.dig("cardinality") || 1) + value.to_f if column_name.eql?("rows")
|
41
|
+
|
42
|
+
summary
|
43
|
+
end
|
44
|
+
|
45
|
+
def apply_rules(column_rules, value)
|
46
|
+
column_rules = Constants::DEFAULT_COLUMN_RULES.merge(column_rules)
|
47
|
+
value = Transform.value(value, column_rules)
|
48
|
+
amount = Transform.amount(value, column_rules)
|
49
|
+
|
50
|
+
column_analyse = { "value" => value, "amount" => amount, "tags" => {} }
|
51
|
+
|
52
|
+
[*value].each do |tag|
|
53
|
+
tag_rule = column_rules.dig("rules", tag)
|
54
|
+
next unless tag_rule.present?
|
55
|
+
|
56
|
+
column_analyse["tags"].merge!(
|
57
|
+
{ tag => Transform.tag_rule(tag_rule).merge!({ "score" => generate_score(tag_rule, amount) }) }
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
column_analyse["tags"].merge!(apply_threshold_rule(column_rules, amount))
|
62
|
+
|
63
|
+
column_analyse
|
64
|
+
end
|
65
|
+
|
66
|
+
def apply_threshold_rule(column_rules, amount)
|
67
|
+
threshold_rule = column_rules.dig("rules", "threshold")
|
68
|
+
|
69
|
+
if threshold_rule.present? && amount >= threshold_rule.dig("amount")
|
70
|
+
return {
|
71
|
+
"threshold" => Transform.tag_rule(threshold_rule).merge(
|
72
|
+
{ "amount" => amount, "score" => generate_score(threshold_rule, amount) }
|
73
|
+
)
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
{}
|
78
|
+
end
|
79
|
+
|
80
|
+
def generate_score(tag_rule, amount)
|
81
|
+
score = tag_rule.dig("score", "value")
|
82
|
+
|
83
|
+
case tag_rule.dig("score", "type").to_s
|
84
|
+
when "base"
|
85
|
+
score.to_f
|
86
|
+
when "relative"
|
87
|
+
amount.to_f * score.to_f
|
88
|
+
when "treshold_relative"
|
89
|
+
(amount - tag_rule.dig("amount")).to_f * score.to_f
|
90
|
+
else
|
91
|
+
0
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
module_function :table, :generate_summary
|
97
|
+
end
|
98
|
+
|
99
|
+
private_constant :Analyse
|
100
|
+
end
|
@@ -14,13 +14,13 @@ module QueryPolice
|
|
14
14
|
# @return [String]
|
15
15
|
def dynamic_message(opts)
|
16
16
|
table, column, tag, type = opts.values_at("table", "column", "tag", "type")
|
17
|
-
message =
|
17
|
+
message = query_analytic.dig(table, "analysis", column, "tags", tag, type) || ""
|
18
18
|
|
19
19
|
variables = message.scan(/\$(\w+)/).uniq.map { |var| var[0] }
|
20
20
|
variables.each do |var|
|
21
21
|
value = dynamic_value_of(var, opts)
|
22
22
|
|
23
|
-
message.gsub
|
23
|
+
message = message.gsub(/\$#{var}/, value.to_s) unless value.nil?
|
24
24
|
end
|
25
25
|
|
26
26
|
message
|
@@ -32,14 +32,14 @@ module QueryPolice
|
|
32
32
|
|
33
33
|
def relative_value_of(var, table)
|
34
34
|
value_type = var.match(/amount_/).present? ? "amount" : "value"
|
35
|
-
|
35
|
+
query_analytic.dig(table, "analysis", var.gsub(/amount_/, ""), value_type)
|
36
36
|
end
|
37
37
|
|
38
38
|
# dynamic variable methods
|
39
39
|
def amount(opts)
|
40
40
|
table, column = opts.values_at("table", "column")
|
41
41
|
|
42
|
-
|
42
|
+
query_analytic.dig(table, "analysis", column, "amount")
|
43
43
|
end
|
44
44
|
|
45
45
|
def column(opts)
|
@@ -49,9 +49,18 @@ module QueryPolice
|
|
49
49
|
def impact(opts)
|
50
50
|
table, column, tag = opts.values_at("table", "column", "tag")
|
51
51
|
|
52
|
-
impact =
|
52
|
+
impact = query_analytic.dig(table, "analysis", column, "tags", tag, "impact")
|
53
53
|
|
54
|
-
opts.dig("colours").present? ? impact.send(IMPACTS
|
54
|
+
opts.dig("colours").present? ? impact.send(IMPACTS.dig(impact, "colour")) : impact
|
55
|
+
end
|
56
|
+
|
57
|
+
def score(opts)
|
58
|
+
table, column, tag = opts.values_at("table", "column", "tag")
|
59
|
+
|
60
|
+
impact = query_analytic.dig(table, "analysis", column, "tags", tag, "impact")
|
61
|
+
score = query_analytic.dig(table, "analysis", column, "tags", tag, "score")
|
62
|
+
|
63
|
+
opts.dig("colours").present? ? score.to_s.send(IMPACTS.dig(impact, "colour")) : score
|
55
64
|
end
|
56
65
|
|
57
66
|
def table(opts)
|
@@ -65,7 +74,7 @@ module QueryPolice
|
|
65
74
|
def value(opts)
|
66
75
|
table, column = opts.values_at("table", "column")
|
67
76
|
|
68
|
-
|
77
|
+
query_analytic.dig(table, "analysis", column, "value")
|
69
78
|
end
|
70
79
|
end
|
71
80
|
end
|
@@ -18,16 +18,18 @@ module QueryPolice
|
|
18
18
|
# Eg.
|
19
19
|
# {
|
20
20
|
# "users" => {
|
21
|
-
# "id"=>1,
|
22
|
-
# "name"=>"users",
|
23
|
-
# "
|
24
|
-
#
|
25
|
-
#
|
21
|
+
# "id" => 1,
|
22
|
+
# "name" => "users",
|
23
|
+
# "score" => 100.0,
|
24
|
+
# "analysis" => {
|
25
|
+
# "type" => {
|
26
|
+
# "value" => all",
|
26
27
|
# "tags" => {
|
27
28
|
# "all" => {
|
28
|
-
# "impact"=>"negative",
|
29
|
-
# "warning"=>"warning to represent the issue",
|
30
|
-
# "suggestions"=>"some follow up suggestions"
|
29
|
+
# "impact" => "negative",
|
30
|
+
# "warning" => "warning to represent the issue",
|
31
|
+
# "suggestions" => "some follow up suggestions",
|
32
|
+
# "score" => 100.0
|
31
33
|
# }
|
32
34
|
# }
|
33
35
|
# }
|
@@ -37,55 +39,65 @@ module QueryPolice
|
|
37
39
|
# summary [Hash] hash of analysis summary
|
38
40
|
# Eg.
|
39
41
|
# {
|
40
|
-
# "cardinality"=>{
|
41
|
-
# "amount"=>10,
|
42
|
-
# "warning"=>"warning to represent the issue",
|
43
|
-
# "suggestions"=>"some follow up suggestions"
|
42
|
+
# "cardinality" => {
|
43
|
+
# "amount" => 10,
|
44
|
+
# "warning" => "warning to represent the issue",
|
45
|
+
# "suggestions" => "some follow up suggestions",
|
46
|
+
# "score" => 100.0
|
44
47
|
# }
|
45
48
|
# }
|
46
49
|
def initialize
|
47
50
|
@table_count = 0
|
48
51
|
@tables = {}
|
52
|
+
@table_score = 0
|
49
53
|
@summary = {}
|
54
|
+
@summary_score = 0
|
50
55
|
end
|
51
56
|
|
52
|
-
attr_accessor :
|
57
|
+
attr_accessor :tables, :summary
|
53
58
|
|
54
59
|
# register a table analysis in analysis object
|
55
60
|
# @param name [String] name of the table
|
56
61
|
# @param table_analysis [Hash] analysis of a table
|
62
|
+
# @param score [Integer] score for that table
|
57
63
|
# Eg.
|
58
64
|
# {
|
59
|
-
# "id"=>1,
|
60
|
-
# "name"=>"users",
|
61
|
-
# "
|
62
|
-
#
|
65
|
+
# "id" => 1,
|
66
|
+
# "name" => "users",
|
67
|
+
# "score" => 100.0
|
68
|
+
# "analysis" => {
|
69
|
+
# "type" => [
|
63
70
|
# {
|
64
|
-
# "tag"=>"all",
|
65
|
-
# "impact"=>"negative",
|
66
|
-
# "warning"=>"warning to represent the issue",
|
67
|
-
# "suggestions"=>"some follow up suggestions"
|
71
|
+
# "tag" => "all",
|
72
|
+
# "impact" => "negative",
|
73
|
+
# "warning" => "warning to represent the issue",
|
74
|
+
# "suggestions" => "some follow up suggestions",
|
75
|
+
# "score" => 100.0
|
68
76
|
# }
|
69
77
|
# ]
|
70
78
|
# }
|
71
79
|
# }
|
72
|
-
def register_table(name, table_analysis)
|
73
|
-
|
80
|
+
def register_table(name, table_analysis, score)
|
81
|
+
@table_count += 1
|
74
82
|
tables.merge!(
|
75
83
|
{
|
76
84
|
name => {
|
77
|
-
"id" =>
|
85
|
+
"id" => @table_count,
|
78
86
|
"name" => name,
|
87
|
+
"score" => score,
|
79
88
|
"analysis" => table_analysis
|
80
89
|
}
|
81
90
|
}
|
82
91
|
)
|
92
|
+
|
93
|
+
@table_score += score
|
83
94
|
end
|
84
95
|
|
85
96
|
# register summary based in different attributes
|
86
97
|
# @param summary [Hash] hash of summary of analysis
|
87
|
-
def register_summary(summary)
|
98
|
+
def register_summary(summary, score)
|
88
99
|
self.summary.merge!(summary)
|
100
|
+
@summary_score += score
|
89
101
|
end
|
90
102
|
|
91
103
|
# to get analysis in pretty format with warnings and suggestions
|
@@ -93,6 +105,7 @@ module QueryPolice
|
|
93
105
|
# @return [String] pretty analysis
|
94
106
|
def pretty_analysis(opts)
|
95
107
|
final_message = ""
|
108
|
+
opts = opts.with_indifferent_access
|
96
109
|
|
97
110
|
opts.slice(*IMPACTS.keys).each do |impact, value|
|
98
111
|
final_message += pretty_analysis_for(impact) if value.present?
|
@@ -105,49 +118,80 @@ module QueryPolice
|
|
105
118
|
# @param impact [String]
|
106
119
|
# @return [String] pretty analysis
|
107
120
|
def pretty_analysis_for(impact)
|
108
|
-
final_message = ""
|
121
|
+
final_message = "query_score: #{query_score}\n\n"
|
109
122
|
|
110
|
-
|
111
|
-
table_message =
|
123
|
+
query_analytic.each_key do |table|
|
124
|
+
table_message = query_pretty_analysis(table, { impact => true })
|
112
125
|
|
113
|
-
final_message += "
|
126
|
+
final_message += "#{table_message}\n" if table_message.present?
|
114
127
|
end
|
115
128
|
|
116
129
|
final_message
|
117
130
|
end
|
118
131
|
|
132
|
+
# to get the final score
|
133
|
+
def query_score
|
134
|
+
@table_score + @summary_score
|
135
|
+
end
|
136
|
+
|
119
137
|
# to get analysis in pretty format with warnings and suggestions for a table
|
120
138
|
# @param table [String] - table name
|
121
139
|
# @param opts [Hash] - possible options [positive: <boolean>, negative: <boolean>, caution: <boolean>]
|
122
140
|
# @return [String] pretty analysis
|
123
|
-
def
|
124
|
-
|
141
|
+
def query_pretty_analysis(table, opts)
|
142
|
+
table_analytics = Terminal::Table.new(title: table)
|
143
|
+
table_analytics_present = false
|
144
|
+
table_analytics.add_row(["score", query_analytic.dig(table, "score")])
|
125
145
|
|
126
|
-
|
127
|
-
tags_message = ""
|
128
|
-
column_analysis.dig("tags").each do |tag, tag_analysis|
|
129
|
-
next unless opts.dig(tag_analysis.dig("impact")).present?
|
146
|
+
opts = opts.with_indifferent_access
|
130
147
|
|
131
|
-
|
132
|
-
|
148
|
+
query_analytic.dig(table, "analysis").each do |column, _|
|
149
|
+
column_analytics = column_analytic(table, column, opts)
|
150
|
+
next unless column_analytics.present?
|
133
151
|
|
134
|
-
|
152
|
+
table_analytics_present = true
|
153
|
+
table_analytics.add_separator
|
154
|
+
table_analytics.add_row(["column", column])
|
155
|
+
column_analytics.each { |row| table_analytics.add_row(row) }
|
135
156
|
end
|
136
157
|
|
137
|
-
|
158
|
+
table_analytics_present ? table_analytics : nil
|
138
159
|
end
|
139
160
|
|
140
161
|
private
|
141
162
|
|
142
|
-
def
|
143
|
-
|
163
|
+
def column_analytic(table, column, opts)
|
164
|
+
column_analytics = []
|
165
|
+
|
166
|
+
query_analytic.dig(table, "analysis", column, "tags").each do |tag, tag_analysis|
|
167
|
+
next unless opts.dig(tag_analysis.dig("impact")).present?
|
168
|
+
|
169
|
+
column_analytics += tag_analytic(table, column, tag)
|
170
|
+
end
|
171
|
+
|
172
|
+
column_analytics
|
173
|
+
end
|
174
|
+
|
175
|
+
def query_analytic
|
176
|
+
tables.merge(
|
177
|
+
"summary" => {
|
178
|
+
"name" => "summary",
|
179
|
+
"score" => @summary_score,
|
180
|
+
"analysis" => summary
|
181
|
+
}
|
182
|
+
)
|
183
|
+
end
|
184
|
+
|
185
|
+
def tag_analytic(table, column, tag)
|
186
|
+
tag_message = []
|
144
187
|
|
145
188
|
opts = { "table" => table, "column" => column, "tag" => tag }
|
146
189
|
message = dynamic_message(opts.merge({ "type" => "message" }))
|
147
190
|
suggestion = dynamic_message(opts.merge({ "type" => "suggestion" }))
|
148
|
-
tag_message
|
149
|
-
tag_message
|
150
|
-
tag_message
|
191
|
+
tag_message << ["impact", impact(opts.merge({ "colours" => true }))]
|
192
|
+
tag_message << ["tag_score", score(opts.merge({ "colours" => true }))]
|
193
|
+
tag_message << ["message", message]
|
194
|
+
tag_message << ["suggestion", suggestion] if suggestion.present?
|
151
195
|
|
152
196
|
tag_message
|
153
197
|
end
|
data/lib/query_police/rules.json
CHANGED
@@ -62,7 +62,11 @@
|
|
62
62
|
"ALL": {
|
63
63
|
"impact": "negative",
|
64
64
|
"message": "Entire $table table is scanned to find matching rows, you have $amount_possible_keys possible keys to use.",
|
65
|
-
"suggestion": "Use index here. You can use index from possible key: $possible_keys or add new one to $table table as per the requirements."
|
65
|
+
"suggestion": "Use index here. You can use index from possible key: $possible_keys or add new one to $table table as per the requirements.",
|
66
|
+
"score": {
|
67
|
+
"value": 100,
|
68
|
+
"type": "base"
|
69
|
+
}
|
66
70
|
}
|
67
71
|
}
|
68
72
|
},
|
@@ -86,13 +90,21 @@
|
|
86
90
|
"absent": {
|
87
91
|
"impact": "negative",
|
88
92
|
"message": "There are no possible keys for $table table to be used, can result into full scan",
|
89
|
-
"suggestion": "Please add index keys for $table table"
|
93
|
+
"suggestion": "Please add index keys for $table table",
|
94
|
+
"score": {
|
95
|
+
"value": 50,
|
96
|
+
"type": "base"
|
97
|
+
}
|
90
98
|
},
|
91
99
|
"threshold": {
|
92
100
|
"amount": 5,
|
93
101
|
"impact": "negative",
|
94
102
|
"message": "There are $amount possible keys for $table table, having too many index keys can be unoptimal",
|
95
|
-
"suggestion": "Please check if there are extra indexes in $table table."
|
103
|
+
"suggestion": "Please check if there are extra indexes in $table table.",
|
104
|
+
"score": {
|
105
|
+
"value": 20,
|
106
|
+
"type": "treshold_relative"
|
107
|
+
}
|
96
108
|
}
|
97
109
|
}
|
98
110
|
},
|
@@ -103,7 +115,11 @@
|
|
103
115
|
"absent": {
|
104
116
|
"impact": "negative",
|
105
117
|
"message": "There is no index key used for $table table, and can result into full scan of the $table table",
|
106
|
-
"suggestion": "Please use index from possible_keys: $possible_keys or add new one to $table table as per the requirements."
|
118
|
+
"suggestion": "Please use index from possible_keys: $possible_keys or add new one to $table table as per the requirements.",
|
119
|
+
"score": {
|
120
|
+
"value": 50,
|
121
|
+
"type": "base"
|
122
|
+
}
|
107
123
|
}
|
108
124
|
}
|
109
125
|
},
|
@@ -122,15 +138,14 @@
|
|
122
138
|
"value_type": "array",
|
123
139
|
"delimiter": ";",
|
124
140
|
"rules": {
|
125
|
-
"Using temporary": {
|
126
|
-
"impact": "",
|
127
|
-
"message": "",
|
128
|
-
"suggestion": ""
|
129
|
-
},
|
130
141
|
"Using filesort": {
|
131
142
|
"impact": "negative",
|
132
143
|
"message": "A file-based algorithm in being applied over your result, This can be inefficient and result into long query time.",
|
133
|
-
"suggestion": "Please ensure either result set is small or use proper index."
|
144
|
+
"suggestion": "Please ensure either result set is small or use proper index.",
|
145
|
+
"score": {
|
146
|
+
"value": 50,
|
147
|
+
"type": "base"
|
148
|
+
}
|
134
149
|
},
|
135
150
|
"Using join buffer": {
|
136
151
|
"impact": "",
|
@@ -145,26 +160,50 @@
|
|
145
160
|
}
|
146
161
|
},
|
147
162
|
"detailed#used_columns": {
|
148
|
-
"description": "",
|
163
|
+
"description": "number of column used to execute the query",
|
149
164
|
"value_type": "array",
|
150
165
|
"rules": {
|
151
166
|
"threshold": {
|
152
|
-
"amount":
|
167
|
+
"amount": 5,
|
153
168
|
"impact": "negative",
|
154
169
|
"message": "You have selected $amount columns, You should not select too many columns.",
|
155
|
-
"suggestion": "Please only select required columns."
|
170
|
+
"suggestion": "Please only select required columns.",
|
171
|
+
"score": {
|
172
|
+
"value": 10,
|
173
|
+
"type": "treshold_relative"
|
174
|
+
}
|
156
175
|
}
|
157
176
|
}
|
158
177
|
},
|
159
|
-
"
|
160
|
-
"description": "",
|
178
|
+
"detailed#cost_info#read_cost ": {
|
179
|
+
"description": "read cost to execute the query",
|
161
180
|
"value_type": "number",
|
162
181
|
"rules": {
|
163
182
|
"threshold": {
|
164
183
|
"amount": 100,
|
165
184
|
"impact": "negative",
|
185
|
+
"message": "The read cost of query is to high, most likely you are scanning to many rows",
|
186
|
+
"suggestion": "Please use proper index, query only requried data and ensure you are using proper joins.",
|
187
|
+
"score": {
|
188
|
+
"value": 0.01,
|
189
|
+
"type": "relative"
|
190
|
+
}
|
191
|
+
}
|
192
|
+
}
|
193
|
+
},
|
194
|
+
"cardinality": {
|
195
|
+
"description": "total cardinality of the query",
|
196
|
+
"value_type": "number",
|
197
|
+
"rules": {
|
198
|
+
"threshold": {
|
199
|
+
"amount": 500,
|
200
|
+
"impact": "negative",
|
166
201
|
"message": "The cardinality of table is $amount, and its too high.",
|
167
|
-
"suggestion": "Please use proper index, query only requried data and ensure you are using proper joins."
|
202
|
+
"suggestion": "Please use proper index, query only requried data and ensure you are using proper joins.",
|
203
|
+
"score": {
|
204
|
+
"value": 0.01,
|
205
|
+
"type": "relative"
|
206
|
+
}
|
168
207
|
}
|
169
208
|
}
|
170
209
|
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# QueryPolice::Transform
|
4
|
+
module QueryPolice
|
5
|
+
# This module define transformer methods for query police
|
6
|
+
module Transform
|
7
|
+
def amount(value, column_rules)
|
8
|
+
return 0 if value.eql?("absent")
|
9
|
+
|
10
|
+
column_rules.dig("value_type").eql?("number") ? value.to_f : value.size
|
11
|
+
end
|
12
|
+
|
13
|
+
def tag_rule(tag_rule)
|
14
|
+
tag_rule.slice("impact", "suggestion", "message")
|
15
|
+
end
|
16
|
+
|
17
|
+
def value(value, column_rules)
|
18
|
+
return "absent" if value.nil?
|
19
|
+
|
20
|
+
if column_rules.dig("value_type").eql?("array") && column_rules.dig("delimiter").present?
|
21
|
+
value = value.split(column_rules.dig("delimiter")).map(&:strip)
|
22
|
+
end
|
23
|
+
|
24
|
+
value
|
25
|
+
end
|
26
|
+
|
27
|
+
module_function :amount, :tag_rule, :value
|
28
|
+
end
|
29
|
+
|
30
|
+
private_constant :Transform
|
31
|
+
end
|
data/lib/query_police/version.rb
CHANGED
data/lib/query_police.rb
CHANGED
@@ -3,14 +3,18 @@
|
|
3
3
|
require "active_record"
|
4
4
|
require "active_support/notifications"
|
5
5
|
require "active_support/core_ext"
|
6
|
-
require "
|
6
|
+
require "colorize"
|
7
7
|
require "forwardable"
|
8
|
+
require "json"
|
9
|
+
require "terminal-table"
|
8
10
|
|
11
|
+
require_relative "query_police/analyse"
|
9
12
|
require_relative "query_police/analysis"
|
10
|
-
require_relative "query_police/constants"
|
11
13
|
require_relative "query_police/config"
|
14
|
+
require_relative "query_police/constants"
|
12
15
|
require_relative "query_police/explain"
|
13
16
|
require_relative "query_police/helper"
|
17
|
+
require_relative "query_police/transform"
|
14
18
|
require_relative "query_police/version"
|
15
19
|
|
16
20
|
# This module provides tools to analyse your queries based on custom rules
|
@@ -40,12 +44,12 @@ module QueryPolice
|
|
40
44
|
query_plan = Explain.full_explain(relation, config.detailed?)
|
41
45
|
|
42
46
|
query_plan.each do |table|
|
43
|
-
table_analysis, summary =
|
47
|
+
table_analysis, summary, table_score = Analyse.table(table, summary, rules_config)
|
44
48
|
|
45
|
-
analysis.register_table(table.dig("table"), table_analysis)
|
49
|
+
analysis.register_table(table.dig("table"), table_analysis, table_score)
|
46
50
|
end
|
47
51
|
|
48
|
-
analysis.register_summary(
|
52
|
+
analysis.register_summary(*Analyse.generate_summary(rules_config, summary))
|
49
53
|
|
50
54
|
analysis
|
51
55
|
end
|
@@ -71,90 +75,6 @@ module QueryPolice
|
|
71
75
|
|
72
76
|
class << self
|
73
77
|
attr_accessor :config
|
74
|
-
|
75
|
-
private
|
76
|
-
|
77
|
-
def add_summary(summary, column_name, value)
|
78
|
-
summary["cardinality"] = (summary.dig("cardinality") || 1) + value.to_f if column_name.eql?("rows")
|
79
|
-
|
80
|
-
summary
|
81
|
-
end
|
82
|
-
|
83
|
-
def analyse_table(table, summary, rules_config)
|
84
|
-
table_analysis = {}
|
85
|
-
|
86
|
-
table.each do |column, value|
|
87
|
-
summary = add_summary(summary, column, value)
|
88
|
-
next unless rules_config.dig(column).present?
|
89
|
-
|
90
|
-
table_analysis.merge!({ column => apply_rules(rules_config.dig(column), value) })
|
91
|
-
end
|
92
|
-
|
93
|
-
[table_analysis, summary]
|
94
|
-
end
|
95
|
-
|
96
|
-
def apply_rules(column_rules, value)
|
97
|
-
column_rules = Constants::DEFAULT_COLUMN_RULES.merge(column_rules)
|
98
|
-
value = transform_value(value, column_rules)
|
99
|
-
amount = transform_amount(value, column_rules)
|
100
|
-
|
101
|
-
column_analyse = { "value" => value, "amount" => amount, "tags" => {} }
|
102
|
-
|
103
|
-
[*value].each do |tag|
|
104
|
-
tag_rule = column_rules.dig("rules", tag)
|
105
|
-
next unless tag_rule.present?
|
106
|
-
|
107
|
-
column_analyse["tags"].merge!({ tag => transform_tag_rule(tag_rule) })
|
108
|
-
end
|
109
|
-
|
110
|
-
column_analyse["tags"].merge!(apply_threshold_rule(column_rules, amount))
|
111
|
-
|
112
|
-
column_analyse
|
113
|
-
end
|
114
|
-
|
115
|
-
def apply_threshold_rule(column_rules, amount)
|
116
|
-
threshold_rule = column_rules.dig("rules", "threshold")
|
117
|
-
|
118
|
-
if threshold_rule.present? && amount >= threshold_rule.dig("amount")
|
119
|
-
return {
|
120
|
-
"threshold" => transform_tag_rule(threshold_rule).merge(
|
121
|
-
{ "amount" => amount }
|
122
|
-
)
|
123
|
-
}
|
124
|
-
end
|
125
|
-
|
126
|
-
{}
|
127
|
-
end
|
128
|
-
|
129
|
-
def generate_summary_analysis(rules_config, summary)
|
130
|
-
summary_analysis = {}
|
131
|
-
|
132
|
-
summary.each do |column, value|
|
133
|
-
next unless rules_config.dig(column).present?
|
134
|
-
|
135
|
-
summary_analysis.merge!({ column => apply_rules(rules_config.dig(column), value) })
|
136
|
-
end
|
137
|
-
|
138
|
-
summary_analysis
|
139
|
-
end
|
140
|
-
|
141
|
-
def transform_amount(value, column_rules)
|
142
|
-
column_rules.dig("value_type").eql?("number") ? value.to_f : value.size
|
143
|
-
end
|
144
|
-
|
145
|
-
def transform_tag_rule(tag_rule)
|
146
|
-
tag_rule.slice("impact", "suggestion", "message")
|
147
|
-
end
|
148
|
-
|
149
|
-
def transform_value(value, column_rules)
|
150
|
-
value ||= "absent"
|
151
|
-
|
152
|
-
if column_rules.dig("value_type").eql?("array") && column_rules.dig("delimiter").present?
|
153
|
-
value = value.split(column_rules.dig("delimiter")).map(&:strip)
|
154
|
-
end
|
155
|
-
|
156
|
-
value
|
157
|
-
end
|
158
78
|
end
|
159
79
|
|
160
80
|
module_function :analyse, :subscribe_logger, *CONFIG_METHODS
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: query_police
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3.beta
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- strikeraryu
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-08-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -19,7 +19,7 @@ dependencies:
|
|
19
19
|
version: 3.0.0
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version:
|
22
|
+
version: 8.0.0
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -29,7 +29,7 @@ dependencies:
|
|
29
29
|
version: 3.0.0
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version:
|
32
|
+
version: 8.0.0
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: activesupport
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
version: 3.0.0
|
40
40
|
- - "<"
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version:
|
42
|
+
version: 8.0.0
|
43
43
|
type: :runtime
|
44
44
|
prerelease: false
|
45
45
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -49,7 +49,47 @@ dependencies:
|
|
49
49
|
version: 3.0.0
|
50
50
|
- - "<"
|
51
51
|
- !ruby/object:Gem::Version
|
52
|
-
version:
|
52
|
+
version: 8.0.0
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: colorize
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: 0.5.0
|
60
|
+
- - "<"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 0.8.1
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 0.5.0
|
70
|
+
- - "<"
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 0.8.1
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: terminal-table
|
75
|
+
requirement: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: 3.0.0
|
80
|
+
- - "<"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 3.0.2
|
83
|
+
type: :runtime
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 3.0.0
|
90
|
+
- - "<"
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 3.0.2
|
53
93
|
description:
|
54
94
|
email:
|
55
95
|
- striker.aryu56@gmail.com
|
@@ -66,6 +106,7 @@ files:
|
|
66
106
|
- README.md
|
67
107
|
- Rakefile
|
68
108
|
- lib/query_police.rb
|
109
|
+
- lib/query_police/analyse.rb
|
69
110
|
- lib/query_police/analysis.rb
|
70
111
|
- lib/query_police/analysis/dynamic_message.rb
|
71
112
|
- lib/query_police/config.rb
|
@@ -73,6 +114,7 @@ files:
|
|
73
114
|
- lib/query_police/explain.rb
|
74
115
|
- lib/query_police/helper.rb
|
75
116
|
- lib/query_police/rules.json
|
117
|
+
- lib/query_police/transform.rb
|
76
118
|
- lib/query_police/version.rb
|
77
119
|
- sig/query_police.rbs
|
78
120
|
homepage: https://github.com/strikeraryu/query_police.git
|
@@ -100,5 +142,6 @@ requirements: []
|
|
100
142
|
rubygems_version: 3.2.3
|
101
143
|
signing_key:
|
102
144
|
specification_version: 4
|
103
|
-
summary: This gem provides tools to analyze your queries based on custom rules
|
145
|
+
summary: This gem provides tools to analyze your queries based on custom rules and
|
146
|
+
detect bad queries.
|
104
147
|
test_files: []
|