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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d428caaf6d25e1f4d59a3e313989a5e161e1493b0f0953e16455a094343a35db
4
- data.tar.gz: 98e8480d007a6c9d42a973c27675856dd651d8bdc1b94bc722e86ca79ed7342a
3
+ metadata.gz: a8a2550f50ea38e6be5c716a282ce37173d0ac0190376917d80aa5135e724c66
4
+ data.tar.gz: c80ecea30fa21e3319fa62698cf774380b04a0bc4073082128d6aaf031b484d3
5
5
  SHA512:
6
- metadata.gz: d4833614a76bdbd7c37d1762eab7d972b2a706a4d1bdba10b12382f36f96a29bdc049160671e79c3b2d9e70c3db21cf8e6aa24fd47fd1a74937f0f9c290b9566
7
- data.tar.gz: d588e2f602ac8c07951694b70fcb6e33a8638f9203c374103e69f09c0be831bdf579ef1ee95aa3bba1f1f3f0fe754fd8cc5d26391db42db31500d4b2fdf6c2a6
6
+ metadata.gz: a380badccb229bd4ccc26c087aa8ce98ca32693679c9bc593cc56b3f0e79c5f71a4a1b08b7a9f254511c541fc10905b6f618b3fddca5d24f1ae277e77b1df564
7
+ data.tar.gz: 159e81fe1bd06e22576b2869318a12bfb4c1a2d181c11948ff1de8aac740733a0e2e8ca5047089bda30a7a9e55543f8db453faf3d75eba23efc598c98a9995fd
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # QueryRuleEngine
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/query_rule_engine`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ Moved to [query_police](https://github.com/strikeraryu/query_police).
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
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
- ## Usage
25
+ ## Get started
24
26
 
25
- TODO: Write usage instructions here
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module QueryRuleEngine
4
- VERSION = "0.1.0.beta"
4
+ VERSION = "0.1.1.beta"
5
5
  end
@@ -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.0.beta
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-23 00:00:00.000000000 Z
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