query_rule_engine 0.1.0.beta → 0.1.1.beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +457 -4
- data/lib/query_rule_engine/version.rb +1 -1
- data/query_rule_engine.gemspec +6 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a8a2550f50ea38e6be5c716a282ce37173d0ac0190376917d80aa5135e724c66
|
4
|
+
data.tar.gz: c80ecea30fa21e3319fa62698cf774380b04a0bc4073082128d6aaf031b484d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a380badccb229bd4ccc26c087aa8ce98ca32693679c9bc593cc56b3f0e79c5f71a4a1b08b7a9f254511c541fc10905b6f618b3fddca5d24f1ae277e77b1df564
|
7
|
+
data.tar.gz: 159e81fe1bd06e22576b2869318a12bfb4c1a2d181c11948ff1de8aac740733a0e2e8ca5047089bda30a7a9e55543f8db453faf3d75eba23efc598c98a9995fd
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# QueryRuleEngine
|
2
2
|
|
3
|
-
|
3
|
+
Moved to [query_police](https://github.com/strikeraryu/query_police).
|
4
4
|
|
5
|
-
|
5
|
+
It is a rule-based engine with custom rules to Analyze Active-Record relations using explain results to detect bad queries.
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
@@ -19,10 +19,463 @@ And then execute:
|
|
19
19
|
Or install it yourself as:
|
20
20
|
|
21
21
|
$ gem install query_rule_engine
|
22
|
+
|
23
|
+
---
|
22
24
|
|
23
|
-
##
|
25
|
+
## Get started
|
24
26
|
|
25
|
-
|
27
|
+
### Basic Usage
|
28
|
+
|
29
|
+
Use `QueryRuleEngine.analyse` to generate an analysis object for your Active-Record relation or query and then you can use pretty print on the object.
|
30
|
+
|
31
|
+
```
|
32
|
+
analysis = QueryRuleEngine.analyse(<active_record_relation>)
|
33
|
+
puts analysis.pretty_analysis_for(<impact>)
|
34
|
+
```
|
35
|
+
|
36
|
+
**Eg.**
|
37
|
+
```
|
38
|
+
analysis = QueryRuleEngine.analyse(
|
39
|
+
User.joins('join sessions on sessions.user_email = users.email ')
|
40
|
+
.where('sessions.created_at < ?', Time.now - 5.months).order('sessions.created_at')
|
41
|
+
)
|
42
|
+
puts analysis.pretty_analysis_for('negative')
|
43
|
+
# or
|
44
|
+
puts analysis.pretty_analysis({'negative' => true, 'positive' => true})
|
45
|
+
```
|
46
|
+
**Results**
|
47
|
+
|
48
|
+
```
|
49
|
+
table: sessions
|
50
|
+
column: type
|
51
|
+
impact: negative
|
52
|
+
message: Entire sessions table is scanned to find matching rows, you have 1 possible keys to use.
|
53
|
+
suggestion: Use index here. You can use index from possible key: ["index_sessions_on_user_email"] or add new one to sessions table as per the requirements.
|
54
|
+
column: key
|
55
|
+
impact: negative
|
56
|
+
message: There is no index key used for sessions table, and can result into full scan of the sessions table
|
57
|
+
suggestion: Please use index from possible_keys: ["index_sessions_on_user_email"] or add new one to sessions table as per the requirements.
|
58
|
+
column: rows
|
59
|
+
impact: negative
|
60
|
+
message: 2982924 rows are being scanned per join for sessions table.
|
61
|
+
suggestion: Please see if it is possible to use index from ["index_sessions_on_user_email"] or add new one to sessions table as per the requirements to reduce the number of rows scanned.
|
62
|
+
```
|
63
|
+
|
64
|
+
### Add logger for every query
|
65
|
+
|
66
|
+
Add `QueryRuleEngine.subscribe_logger` to your initial load file like `application.rb`
|
67
|
+
|
68
|
+
You can make logger silence of error using `QueryRuleEngine.subscribe_logger silent: true`.
|
69
|
+
|
70
|
+
You can change logger config using `QueryRuleEngine logger_config: <config>`, default logger_config `{'negative' => true}`, options `positive: <Boolean>, caution: <Boolean>`.
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
## How it works?
|
75
|
+
|
76
|
+
1. Query rule engine converts the relation into sql query
|
77
|
+
|
78
|
+
2. Query rule engine generates execution plan using EXPLAIN and EXPLAIN format=JSON based on the configuration.
|
79
|
+
|
80
|
+
3. Query rule engine load rules from the config file.
|
81
|
+
|
82
|
+
4. Query rule engine apply rules on the execution plan and generate a new analysis object.
|
83
|
+
|
84
|
+
5. Analysis object provide different methods to print the analysis in more descriptive format.
|
85
|
+
|
86
|
+
|
87
|
+
## Execution plan
|
88
|
+
|
89
|
+
We have 2 possible execution plan:-
|
90
|
+
|
91
|
+
Normal - using `EXPLAIN`
|
92
|
+
|
93
|
+
Detailed - using `EXPLAIN format=JSON`
|
94
|
+
|
95
|
+
**NOTE:** By default Detailed execution plan is added in the final execution plan, you can remove that by `QueryRuleEngine.detailed=false`
|
96
|
+
|
97
|
+
### Normal execution plan
|
98
|
+
|
99
|
+
Generated using `EXPAIN <query>`
|
100
|
+
|
101
|
+
**Result**
|
102
|
+
|
103
|
+
| id | select_type | table | partitions | type. | possible_keys | key | key_len | ref | rows | filtered | Extra |
|
104
|
+
|----|-------------|---------|------------|--------|---------------------------|---------------------|---------|-----------------------------|------|----------|--------------------------|
|
105
|
+
| 1 | SIMPLE | profile | NULL. | index | fk_rails_249a7ebca1 | fk_rails_249a7ebca1 | 5 | NULL | 603 | 100.00 | Using where; Using index |
|
106
|
+
| 1 | SIMPLE | users | NULL | eq_ref | PRIMARY,index_users_on_id | PRIMARY | 4 | development.profile.user_id |1 | 100.00 | NULL |
|
107
|
+
|
108
|
+
|
109
|
+
Result for this is added as it is in the final execution plan
|
110
|
+
|
111
|
+
**Eg.**
|
112
|
+
|
113
|
+
```
|
114
|
+
{
|
115
|
+
"profile" => {
|
116
|
+
"id" => 1,
|
117
|
+
"select_type" => "SIMPLE",
|
118
|
+
"table" => "profile",
|
119
|
+
"partitions" => nil,
|
120
|
+
"type" => "index",
|
121
|
+
"possible_keys" => "fk_rails_249a7ebca1",
|
122
|
+
"key" => "fk_rails_249a7ebca1",
|
123
|
+
"key_len" => "5",
|
124
|
+
"ref" => nil,
|
125
|
+
"rows" => 603,
|
126
|
+
"filtered" => 100.0,
|
127
|
+
"Extra" => "Using where; Using index"
|
128
|
+
},
|
129
|
+
"users" => {
|
130
|
+
"id" => 1,
|
131
|
+
"select_type" => "SIMPLE",
|
132
|
+
"table" => "users",
|
133
|
+
"partitions" => nil,
|
134
|
+
"type" => "eq_ref",
|
135
|
+
"possible_keys" => "PRIMARY,index_users_on_id",
|
136
|
+
"key" => "PRIMARY",
|
137
|
+
"key_len" => "4",
|
138
|
+
"ref" => "development.profile.user_id",
|
139
|
+
"rows" => 1,
|
140
|
+
"filtered" => 100.0,
|
141
|
+
"Extra" => nil
|
142
|
+
}
|
143
|
+
}
|
144
|
+
```
|
145
|
+
|
146
|
+
### Detailed execution plan
|
147
|
+
|
148
|
+
Generated using `EXPAIN format=JSON <query>`
|
149
|
+
|
150
|
+
**Truncated Result**
|
151
|
+
|
152
|
+
```
|
153
|
+
{
|
154
|
+
"query_block": {
|
155
|
+
"select_id": 1,
|
156
|
+
"cost_info": {
|
157
|
+
"query_cost": "850.20"
|
158
|
+
},
|
159
|
+
"nested_loop": [
|
160
|
+
{
|
161
|
+
"table": {
|
162
|
+
"table_name": "profile",
|
163
|
+
"access_type": "index",
|
164
|
+
"key_length": "5",
|
165
|
+
"cost_info": {
|
166
|
+
"read_cost": "6.00",
|
167
|
+
"eval_cost": "120.60",
|
168
|
+
"prefix_cost": "126.60",
|
169
|
+
"data_read_per_join": "183K"
|
170
|
+
},
|
171
|
+
"used_columns": [
|
172
|
+
"user_id"
|
173
|
+
],
|
174
|
+
"attached_condition": "(`development`.`profile`.`user_id` is not null)"
|
175
|
+
}
|
176
|
+
}
|
177
|
+
]
|
178
|
+
}
|
179
|
+
}
|
180
|
+
```
|
181
|
+
|
182
|
+
|
183
|
+
Result for this is added in flatten form to final execution plan, where `detailed#` prefix is added before each key.
|
184
|
+
|
185
|
+
**Truncated Eg.**
|
186
|
+
|
187
|
+
```
|
188
|
+
{
|
189
|
+
"detailed#key_length" => "5",
|
190
|
+
"detailed#rows_examined_per_scan" => 603,
|
191
|
+
"detailed#rows_produced_per_join" => 603,
|
192
|
+
"detailed#filtered" => "100.00",
|
193
|
+
"detailed#using_index" => true,
|
194
|
+
"detailed#cost_info#read_cost" => "6.00",
|
195
|
+
"detailed#cost_info#eval_cost" => "120.60",
|
196
|
+
"detailed#cost_info#prefix_cost" => "126.60",
|
197
|
+
"detailed#cost_info#data_read_per_join" => "183K",
|
198
|
+
"detailed#used_columns" => ["user_id"]
|
199
|
+
...
|
200
|
+
```
|
201
|
+
|
202
|
+
|
203
|
+
|
204
|
+
##### Flatten
|
205
|
+
|
206
|
+
`{a: {b: 1}, c: 2}` is converted into `{a#b: 1, c: 2}`.
|
207
|
+
|
208
|
+
|
209
|
+
## Analysis object
|
210
|
+
|
211
|
+
Analysis object stores a detailed analysis report of a relation inside `:tables :table_count :summary attributes`.
|
212
|
+
|
213
|
+
#### Attributes
|
214
|
+
|
215
|
+
**table_count [Integer] - No. Tables used in the relation**
|
216
|
+
|
217
|
+
**tables [Hash] - detailed table analysis**
|
218
|
+
|
219
|
+
```
|
220
|
+
{
|
221
|
+
'users' => {
|
222
|
+
'id'=>1,
|
223
|
+
'name'=>'users', # table alias user in the execution plan
|
224
|
+
'analysis'=>{
|
225
|
+
'type'=>{ # attribute name
|
226
|
+
'value' => <string>, # raw value of attribute in execution plan
|
227
|
+
'tags' => {
|
228
|
+
'all' => { # tag based on the value of a attribute
|
229
|
+
'impact'=> <string>, # negative, positive, cautions
|
230
|
+
'warning'=> <string>, # Eg. 'warning to represent the issue'
|
231
|
+
'suggestions'=> <string> # Eg. 'some follow up suggestions'
|
232
|
+
}
|
233
|
+
}
|
234
|
+
}
|
235
|
+
}
|
236
|
+
}
|
237
|
+
}
|
238
|
+
```
|
239
|
+
**summary [Hash] - hash of analysis summary**
|
240
|
+
|
241
|
+
```
|
242
|
+
{
|
243
|
+
'cardinality'=>{
|
244
|
+
'amount'=>10,
|
245
|
+
'warning'=>'warning to represent the issue',
|
246
|
+
'suggestions'=>'some follow up suggestions'
|
247
|
+
}
|
248
|
+
}
|
249
|
+
```
|
250
|
+
|
251
|
+
|
252
|
+
|
253
|
+
## How to define rules?
|
254
|
+
|
255
|
+
Rules defined in the json file at config_path is applied to the execution plan. We have variety of option to define rules.
|
256
|
+
|
257
|
+
You can change this by `QueryRuleEngine.rules_path=<path>` and define your own rules
|
258
|
+
|
259
|
+
### Rule Structure
|
260
|
+
|
261
|
+
A basic rule structure -
|
262
|
+
```
|
263
|
+
"<column_name>": {
|
264
|
+
"description": <string>,
|
265
|
+
"value_type": <string>,
|
266
|
+
"delimiter": <string>,
|
267
|
+
"rules": {
|
268
|
+
"<rule>": {
|
269
|
+
"amount": <integer>
|
270
|
+
"impact": <string>,
|
271
|
+
"message": <string>,
|
272
|
+
"suggestion": <string>
|
273
|
+
}
|
274
|
+
}
|
275
|
+
}
|
276
|
+
```
|
277
|
+
- `<column_name>` - attribute name in the final execution plan.
|
278
|
+
|
279
|
+
- `description` - description of the attribute
|
280
|
+
|
281
|
+
- `value_type` - value type of the attribute
|
282
|
+
|
283
|
+
- `delimiter` - delimiter to parse array type attribute values, if no delimiter is passed engine will consider value is already in array form.
|
284
|
+
|
285
|
+
- `<rule>` - kind of rule for the attribute
|
286
|
+
|
287
|
+
- `<tag>` - direct value match eg. ALL, SIMPLE
|
288
|
+
|
289
|
+
- `absent` - when value is missing
|
290
|
+
|
291
|
+
- `threshold` - a greater than threshold check based on the amount set inside the rule.
|
292
|
+
|
293
|
+
- `amount` - amount of threshold need to check for
|
294
|
+
|
295
|
+
- length for string
|
296
|
+
|
297
|
+
- value for number
|
298
|
+
|
299
|
+
- size for array
|
300
|
+
|
301
|
+
- `impact` - impact for the rule
|
302
|
+
|
303
|
+
- `negative`
|
304
|
+
|
305
|
+
- `postive`
|
306
|
+
|
307
|
+
- `caution`
|
308
|
+
|
309
|
+
- `message` - message need to provide the significance of the rule
|
310
|
+
|
311
|
+
- `suggestion` - suggestion on how we can fix the issue
|
312
|
+
|
313
|
+
|
314
|
+
|
315
|
+
### Dynamic messages and suggestion
|
316
|
+
|
317
|
+
We can define dynamic messages and suggestion with variables provided by the engine.
|
318
|
+
|
319
|
+
- `$amount` - amount of the value
|
320
|
+
|
321
|
+
- length for string
|
322
|
+
|
323
|
+
- value for number
|
324
|
+
|
325
|
+
- size for array
|
326
|
+
|
327
|
+
- `$column` - attribute name
|
328
|
+
|
329
|
+
- `$impact` - impact for the rule
|
330
|
+
|
331
|
+
- `$table` - table alias used in the plan
|
332
|
+
|
333
|
+
- `$tag` - tag for which rule is applied
|
334
|
+
|
335
|
+
- `$value` - original parsed value
|
336
|
+
|
337
|
+
- `$<column_name>` - value of that specific column in that table
|
338
|
+
|
339
|
+
- `$amount_<column_name>` - amount of that specific column
|
340
|
+
|
341
|
+
|
342
|
+
|
343
|
+
### Rules Examples
|
344
|
+
|
345
|
+
#### Basic rule example
|
346
|
+
|
347
|
+
```
|
348
|
+
"type": {
|
349
|
+
"description": "Join used in the query for a specific table.",
|
350
|
+
"value_type": "string",
|
351
|
+
"rules": {
|
352
|
+
"system": {
|
353
|
+
"impact": "positive",
|
354
|
+
"message": "Table has zero or one row, no change required.",
|
355
|
+
"suggestion": ""
|
356
|
+
},
|
357
|
+
"ALL": {
|
358
|
+
"impact": "negative",
|
359
|
+
"message": "Entire $table table is scanned to find matching rows, you have $amount_possible_keys possible keys to use.",
|
360
|
+
"suggestion": "Use index here. You can use index from possible key: $possible_keys or add new one to $table table as per the requirements."
|
361
|
+
}
|
362
|
+
}
|
363
|
+
```
|
364
|
+
For above rule dynamic message will be generated as-
|
365
|
+
```
|
366
|
+
Entire users table is scanned to find matching rows, you have 1 possible keys to use
|
367
|
+
```
|
368
|
+
For above rule dynamic suggestion will be generated as-
|
369
|
+
```
|
370
|
+
Use index here. You can use index from possible key: ["PRIMARY", "user_email"] or add new one to users table as per the requirements.
|
371
|
+
```
|
372
|
+
|
373
|
+
|
374
|
+
#### Absent rule example
|
375
|
+
|
376
|
+
```
|
377
|
+
"key": {
|
378
|
+
"description": "index key used for the table",
|
379
|
+
"value_type": "string",
|
380
|
+
"rules": {
|
381
|
+
"absent": {
|
382
|
+
"impact": "negative",
|
383
|
+
"message": "There is no index key used for $table table, and can result into full scan of the $table table",
|
384
|
+
"suggestion": "Please use index from possible_keys: $possible_keys or add new one to $table table as per the requirements."
|
385
|
+
}
|
386
|
+
}
|
387
|
+
}
|
388
|
+
```
|
389
|
+
|
390
|
+
For above rule dynamic message will be generated as-
|
391
|
+
```
|
392
|
+
There is no index key used for users table, and can result into full scan of the users table
|
393
|
+
```
|
394
|
+
For above rule dynamic suggestion will be generated as-
|
395
|
+
```
|
396
|
+
Please use index from possible_keys: ["PRIMARY", "user_email"] or add new one to users table as per the requirements.
|
397
|
+
```
|
398
|
+
|
399
|
+
|
400
|
+
#### Threshold rule example
|
401
|
+
|
402
|
+
```
|
403
|
+
"possible_keys": {
|
404
|
+
"description": "Index keys possible for a specifc table",
|
405
|
+
"value_type": "array",
|
406
|
+
"delimiter": ",",
|
407
|
+
"rules": {
|
408
|
+
"threshold": {
|
409
|
+
"amount": 5,
|
410
|
+
"impact": "negative",
|
411
|
+
"message": "There are $amount possible keys for $table table, having too many index keys can be unoptimal",
|
412
|
+
"suggestion": "Please check if there are extra indexes in $table table."
|
413
|
+
}
|
414
|
+
}
|
415
|
+
}
|
416
|
+
```
|
417
|
+
For above rule dynamic message will be generated as-
|
418
|
+
```
|
419
|
+
There are 10 possible keys for users table, having too many index keys can be unoptimal
|
420
|
+
```
|
421
|
+
For above rule dynamic suggestion will be generated as-
|
422
|
+
```
|
423
|
+
Please check if there are extra indexes in users table.
|
424
|
+
```
|
425
|
+
|
426
|
+
|
427
|
+
#### Complex Detailed rule example
|
428
|
+
|
429
|
+
```
|
430
|
+
"detailed#used_columns": {
|
431
|
+
"description": "",
|
432
|
+
"value_type": "array",
|
433
|
+
"rules": {
|
434
|
+
"threshold": {
|
435
|
+
"amount": 7,
|
436
|
+
"impact": "negative",
|
437
|
+
"message": "You have selected $amount columns, You should not select too many columns.",
|
438
|
+
"suggestion": "Please only select required columns."
|
439
|
+
}
|
440
|
+
}
|
441
|
+
}
|
442
|
+
```
|
443
|
+
For above rule dynamic message will be generated as-
|
444
|
+
```
|
445
|
+
You have selected 10 columns, You should not select too many columns.
|
446
|
+
```
|
447
|
+
For above rule dynamic suggestion will be generated as-
|
448
|
+
```
|
449
|
+
Please only select required columns.
|
450
|
+
```
|
451
|
+
|
452
|
+
|
453
|
+
### Summary
|
454
|
+
|
455
|
+
You can define similar rules for summary. Current summary attribute supported -
|
456
|
+
|
457
|
+
- `cardinality` - cardinality based on the all tables
|
458
|
+
|
459
|
+
**NOTE:** You can add custom summary attributes by defining how to calculate them in `QueryRuleEngine.add_summary` for a attribute key.
|
460
|
+
|
461
|
+
|
462
|
+
|
463
|
+
### Attributes
|
464
|
+
|
465
|
+
There all lot of attributes for you to use based on the final execution plan.
|
466
|
+
|
467
|
+
You can use normal execution plan attribute directly.
|
468
|
+
Eg. `select_type, type, Extra, possible_keys`
|
469
|
+
|
470
|
+
To check more keys you can use `EXPLAIN <query>`
|
471
|
+
|
472
|
+
You can use detailed execution plan attribute can be used in flatten form with `detailed#` prefix.
|
473
|
+
Eg. `detailed#used_columns, detailed#cost_info#read_cost`
|
474
|
+
|
475
|
+
To check more keys you can use `EXPLAIN format=JSON <query>`
|
476
|
+
|
477
|
+
|
478
|
+
---
|
26
479
|
|
27
480
|
## Development
|
28
481
|
|
data/query_rule_engine.gemspec
CHANGED
@@ -36,4 +36,10 @@ Gem::Specification.new do |spec|
|
|
36
36
|
|
37
37
|
# For more information and examples about making a new gem, checkout our
|
38
38
|
# guide at: https://bundler.io/guides/creating_gem.html
|
39
|
+
|
40
|
+
spec.post_install_message = <<-MESSAGE
|
41
|
+
! The 'query_rule_engine' gem has been deprecated and has been replaced by 'query_police'.
|
42
|
+
! See: https://rubygems.org/gems/query_police
|
43
|
+
! And: https://github.com/strikeraryu/query_police
|
44
|
+
MESSAGE
|
39
45
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: query_rule_engine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1.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-04-
|
11
|
+
date: 2023-04-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -86,7 +86,10 @@ metadata:
|
|
86
86
|
homepage_uri: https://github.com/strikeraryu/query_rule_engine.git
|
87
87
|
source_code_uri: https://github.com/strikeraryu/query_rule_engine.git
|
88
88
|
changelog_uri: https://github.com/strikeraryu/tree/master/CHANGELOG.md
|
89
|
-
post_install_message:
|
89
|
+
post_install_message: |2
|
90
|
+
! The 'query_rule_engine' gem has been deprecated and has been replaced by 'query_police'.
|
91
|
+
! See: https://rubygems.org/gems/query_police
|
92
|
+
! And: https://github.com/strikeraryu/query_police
|
90
93
|
rdoc_options: []
|
91
94
|
require_paths:
|
92
95
|
- lib
|