dm-cutie-extras 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +0 -0
- data/LICENSE +19 -0
- data/README.markdown +62 -0
- data/Rakefile +94 -0
- data/TODO.markdown +60 -0
- data/lib/dm-cutie-extras.rb +28 -0
- data/lib/dm-cutie-extras/hooks/mysql_execution_step.rb +63 -0
- data/lib/dm-cutie-extras/hooks/mysql_index.rb +132 -0
- data/lib/dm-cutie-extras/hooks/mysql_warning.rb +48 -0
- data/lib/dm-cutie-extras/hooks/sqlite3_execution_step.rb +55 -0
- data/lib/dm-cutie-extras/version.rb +7 -0
- data/lib/dm-cutie-extras/views/_template_.erb +47 -0
- data/lib/dm-cutie-extras/views/mysql_execution_steps.erb +71 -0
- data/lib/dm-cutie-extras/views/mysql_indexes.erb +45 -0
- data/lib/dm-cutie-extras/views/mysql_warnings.erb +35 -0
- data/lib/dm-cutie-extras/views/sqlite3_execution_steps.erb +29 -0
- metadata +102 -0
data/History.txt
ADDED
File without changes
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (C) 2009, Cory ODaniel
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
4
|
+
copy of this software and associated documentation files (the "Software"),
|
5
|
+
to deal in the Software without restriction, including without limitation
|
6
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
7
|
+
and/or sell copies of the Software, and to permit persons to whom the
|
8
|
+
Software is furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
16
|
+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
18
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
19
|
+
DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
Description
|
2
|
+
=============
|
3
|
+
dm-cutie-extras is a pack of extra stuff for dm-cutie and dm-cutie-ui.
|
4
|
+
|
5
|
+
|
6
|
+
Usage
|
7
|
+
============
|
8
|
+
require 'dm-core'
|
9
|
+
require 'dm-cutie'
|
10
|
+
require 'dm-cutie-extras'
|
11
|
+
|
12
|
+
DataMapper::Cutie::Extras.load :mysql_query_plan
|
13
|
+
|
14
|
+
DataMapper::Cutie.start(true)
|
15
|
+
|
16
|
+
|
17
|
+
Creating Views
|
18
|
+
==============
|
19
|
+
If your hook includes a DataMapper model and you are writing a view, the view name should
|
20
|
+
be the plural of your model name
|
21
|
+
|
22
|
+
|
23
|
+
Writing Tracker Hooks
|
24
|
+
=====================
|
25
|
+
See documentation @ http://github.com/coryodaniel/dm-cutie/
|
26
|
+
|
27
|
+
|
28
|
+
Adding Custom Columns to Views
|
29
|
+
==============================
|
30
|
+
The partial 'grid' takes three keys to create its local variables: :columns, :records, :adhoc_columns (discussed next)
|
31
|
+
|
32
|
+
Columns should be respond to #each, it will generally be a DataMapper PropertySet. Each element in columns will be used in a #send method call on each of the elements in records. That means instead of doing the standard
|
33
|
+
partial :grid, :locals => { :columns => MyModel.properties, :records => @my_collection}
|
34
|
+
|
35
|
+
You can add any other columns you want as long as there is a method that a 'record' will respond to. For example, every object has a 'class' method, so you could do
|
36
|
+
partial :grid,
|
37
|
+
:locals => {
|
38
|
+
:columns => MyModel.properties.to_a + [:class],
|
39
|
+
:records => @my_collection
|
40
|
+
}
|
41
|
+
|
42
|
+
This would cause the class name to be output in a column (pretty useless), but you can add any method you want to the class and simply add its name to the set of elements being passed to :columns
|
43
|
+
|
44
|
+
|
45
|
+
As an alternative method you can pass a column name to a proc in the :adhoc_columns parameter
|
46
|
+
partial :grid,
|
47
|
+
:locals => {
|
48
|
+
:columns => MyModel.properties,
|
49
|
+
:records => @my_collection,
|
50
|
+
:adhoc_columns => {
|
51
|
+
:my_cool_class_name => lambda{|my_record| my_record.class }
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
This would result in a column named 'my_cool_class_name' with a value of 'MyModel'. Obviously you can be more creative :)
|
56
|
+
|
57
|
+
Note: the proc should accept one parameter, the specific record from the collection of items passed to :records
|
58
|
+
|
59
|
+
IMPORTANT NOTE:
|
60
|
+
Remember, if you are adding additional columns to the grid, make sure you pass the column names to the 'menu' partial if you want the ability to hide that column.
|
61
|
+
|
62
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rubygems/specification'
|
4
|
+
require 'date'
|
5
|
+
require "extlib"
|
6
|
+
|
7
|
+
require 'rake'
|
8
|
+
require 'rake/clean'
|
9
|
+
require 'spec/rake/spectask'
|
10
|
+
require "spec"
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
require 'rake/gempackagetask'
|
13
|
+
|
14
|
+
ROOT = Pathname(__FILE__).dirname.expand_path
|
15
|
+
require ROOT + 'lib/dm-cutie-extras/version'
|
16
|
+
|
17
|
+
@spec = Gem::Specification.new do |s|
|
18
|
+
s.name = %q{dm-cutie-extras}
|
19
|
+
s.version = DataMapper::Cutie::Extras::VERSION
|
20
|
+
|
21
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
22
|
+
s.authors = ["Cory ODaniel"]
|
23
|
+
s.date = %q{2009-10-15}
|
24
|
+
s.summary = %q{DM Cutie's Extras}
|
25
|
+
s.description = %q{Cool additional hooks for DM Cutie.}
|
26
|
+
|
27
|
+
s.email = %q{cutie@coryodaniel.com}
|
28
|
+
|
29
|
+
s.extra_rdoc_files = ["README.markdown", "LICENSE", "History.txt", "TODO.markdown"]
|
30
|
+
s.files = ["LICENSE", "README.markdown", "Rakefile", "TODO.markdown",
|
31
|
+
"lib/dm-cutie-extras.rb",
|
32
|
+
"lib/dm-cutie-extras/version.rb",
|
33
|
+
]
|
34
|
+
s.files += Dir[ "lib/dm-cutie-extras/views/*"]
|
35
|
+
s.files += Dir[ "lib/dm-cutie-extras/hooks/*"]
|
36
|
+
|
37
|
+
s.add_dependency "extlib",">=0.9.12"
|
38
|
+
s.add_dependency "dm-core", '>=0.10.0'
|
39
|
+
s.add_dependency "dm-types", '>=0.10.0'
|
40
|
+
|
41
|
+
s.has_rdoc = true
|
42
|
+
s.homepage = "http://github.com/coryodaniel/dm-cutie-extras"
|
43
|
+
s.require_paths = ["lib"]
|
44
|
+
s.rubygems_version = %q{1.2.0}
|
45
|
+
end
|
46
|
+
|
47
|
+
[ ROOT, ROOT.parent ].each do |dir|
|
48
|
+
Pathname.glob(dir.join('tasks/**/*.rb').to_s).each { |f| require f }
|
49
|
+
end
|
50
|
+
|
51
|
+
NAME = @spec.name
|
52
|
+
GEM_VERSION = DataMapper::Cutie::Extras::VERSION
|
53
|
+
|
54
|
+
Rake::GemPackageTask.new(@spec) do |pkg|
|
55
|
+
pkg.gem_spec = @spec
|
56
|
+
end
|
57
|
+
|
58
|
+
desc "install the plugin locally"
|
59
|
+
task :install => [:package] do
|
60
|
+
sh %{sudo gem install #{install_home} pkg/#{NAME}-#{GEM_VERSION} --no-update-sources}
|
61
|
+
end
|
62
|
+
|
63
|
+
desc "create a gemspec file"
|
64
|
+
task :make_spec do
|
65
|
+
File.open("#{NAME}.gemspec", "w") do |file|
|
66
|
+
file.puts @spec.to_ruby
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
Rake::RDocTask.new do |rdoc|
|
71
|
+
files = ["README.markdown", "History.txt", "LICENSE", "lib/**/*.rb"]
|
72
|
+
rdoc.rdoc_files.add(files)
|
73
|
+
rdoc.main = "README.markdown"
|
74
|
+
rdoc.title = "DM Cutie Extras"
|
75
|
+
|
76
|
+
rdoc.rdoc_dir = "doc/rdoc"
|
77
|
+
rdoc.options << "--line-numbers" << "--inline-source"
|
78
|
+
end
|
79
|
+
|
80
|
+
Spec::Rake::SpecTask.new do |t|
|
81
|
+
t.spec_files = Dir["./spec/**/*_spec.rb"]
|
82
|
+
t.spec_files.unshift './spec/spec_helper.rb'
|
83
|
+
|
84
|
+
t.libs = ['lib']
|
85
|
+
t.spec_opts << "--color" << "--format" << "specdoc" #"progress"
|
86
|
+
|
87
|
+
if ENV['RCOV']
|
88
|
+
t.rcov = true
|
89
|
+
t.rcov_opts << '--exclude' << 'pkg,spec,interactive.rb,install_test_suite.rb,lib/gems,' + Gem.path.join(',')
|
90
|
+
t.rcov_opts << '--text-summary'
|
91
|
+
t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
|
92
|
+
t.rcov_opts << '--only-uncovered'
|
93
|
+
end
|
94
|
+
end
|
data/TODO.markdown
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
MysqlIndex
|
2
|
+
=============
|
3
|
+
* Percent of times used (Need a way to set dependencies between hooks)
|
4
|
+
* mysql_index count times used queryplan.keys.split(',').member? index.key_name
|
5
|
+
* mysql_index count times used queryplan.possible_keys_.split(',').member? index.key_name
|
6
|
+
|
7
|
+
* link MysqlIndex to executed OR generalized query ?
|
8
|
+
* Offer DROP INDEX on mysql_index
|
9
|
+
* redundant index detection http://www.paragon-cs.com/presentations/querytuning.pdf
|
10
|
+
|
11
|
+
|
12
|
+
MysqlOptimizations (Probably highly correlated to MysqlExecutionStep...)
|
13
|
+
===================
|
14
|
+
* Offer ADD INDEX on generalized_query (Optimize Queries like this)
|
15
|
+
|
16
|
+
|
17
|
+
Amazon SimpleDB
|
18
|
+
===============
|
19
|
+
* Cost calculation
|
20
|
+
|
21
|
+
|
22
|
+
MysqlExecutionStep
|
23
|
+
==================
|
24
|
+
* full table scans causes:
|
25
|
+
* no WHERE clause
|
26
|
+
* no index on any field in WHERE clause
|
27
|
+
* poor selectivity on an indexed field
|
28
|
+
* too many records meet WHERE conditions
|
29
|
+
* MySQL version less than 5.0 and using OR in a WHERE clause
|
30
|
+
* using SELECT * FROM
|
31
|
+
* select_type optimizers
|
32
|
+
* if MysqlExecutionStep.select_type =~ /DEPENDENT/i # Correlated subquery, consider rewriting as join
|
33
|
+
* type optimizers
|
34
|
+
* extra optimizers
|
35
|
+
* distinct
|
36
|
+
* full scan on NULL key
|
37
|
+
* Impossible WHERE noticed after reading const table
|
38
|
+
* No tables
|
39
|
+
* Not exists
|
40
|
+
* Range checked for each record (index map: N)
|
41
|
+
* Select tables optimized away
|
42
|
+
* Using filesort
|
43
|
+
* Using index
|
44
|
+
* Using index for group-by
|
45
|
+
* Using sort_union(...), Using union(...), Using intersect(...)
|
46
|
+
* Using temporary
|
47
|
+
* Using where
|
48
|
+
* Using where with pushed condition
|
49
|
+
* covering index useful
|
50
|
+
* When you have large tables
|
51
|
+
* When you have long rows (BLOBS for example)
|
52
|
+
* When extra columns do not increase key length significantly
|
53
|
+
* When you have a large join with a number of secondary table lookups
|
54
|
+
* When a lot of rows match the same key value
|
55
|
+
|
56
|
+
Analyze joins
|
57
|
+
=============
|
58
|
+
* Repository Storage 'strongest' relationships (storage that a given storage is most frequently joined with)
|
59
|
+
* Repository Storage 'weakest' relationship
|
60
|
+
* Repository Storage w/ no relationship
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Cutie
|
3
|
+
class Extras
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def view_path
|
7
|
+
@_view_paths ||={}
|
8
|
+
end
|
9
|
+
|
10
|
+
def root
|
11
|
+
@_root_path ||= File.expand_path(File.dirname(__FILE__)) / 'dm-cutie-extras'
|
12
|
+
end
|
13
|
+
|
14
|
+
def load(*_hooks)
|
15
|
+
_hooks.each do |_hook|
|
16
|
+
require DataMapper::Cutie::Extras.root / 'hooks' / _hook
|
17
|
+
hook_view_path = DataMapper::Cutie::Extras.root / 'views' / "#{_hook.to_s.pluralize}.erb"
|
18
|
+
|
19
|
+
if File.exist? hook_view_path
|
20
|
+
DataMapper::Cutie::Extras.view_path[_hook] = hook_view_path
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Tracker Hook for recording mysql query plans
|
2
|
+
#
|
3
|
+
class MysqlExecutionStep
|
4
|
+
include DataMapper::Cutie::Tracker::Hook::Abstract
|
5
|
+
include DataMapper::Resource
|
6
|
+
|
7
|
+
STATEMENT = "EXPLAIN EXTENDED %s"
|
8
|
+
|
9
|
+
belongs_to :executed_query
|
10
|
+
property :id, Serial
|
11
|
+
|
12
|
+
property :order, Integer, :nullable => false #the order the step was executed in
|
13
|
+
property :type, String
|
14
|
+
property :select_type, String
|
15
|
+
property :table, String #denormalized, duplicate, you can get this through self.generalized_query.query_storage_links.first(:primary_storage => true).repository_storage.table_name
|
16
|
+
property :possible_keys,String
|
17
|
+
property :used_key, String # maps to: OpenStruct#key
|
18
|
+
property :used_key_len, String # maps to: OpenStruct#key_len
|
19
|
+
property :ref, String
|
20
|
+
property :rows, Integer
|
21
|
+
property :filtered, Float
|
22
|
+
property :extra, String
|
23
|
+
|
24
|
+
def self.hook_name; "MySQL Execution Steps" end;
|
25
|
+
def self.supported_statements; [:select]; end;
|
26
|
+
def self.supported_adapters; [:mysql]; end;
|
27
|
+
|
28
|
+
def self.execute(target_repo, query_str)
|
29
|
+
repository(target_repo.to_sym).adapter.query MysqlExecutionStep::STATEMENT % query_str
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.track( executed_query )
|
33
|
+
# Perform the EXPLAIN against the original target repo
|
34
|
+
raw_query_plan = MysqlExecutionStep.execute(
|
35
|
+
executed_query.generalized_query.primary_storage.repo_name,
|
36
|
+
executed_query.statement
|
37
|
+
)
|
38
|
+
|
39
|
+
#Store the MysqlExecutionSteps in the DM Cutie repo
|
40
|
+
DataMapper::Cutie.repo do
|
41
|
+
raw_query_plan.each_with_index do |raw_query_step, idx|
|
42
|
+
attribs = raw_query_step.attributes
|
43
|
+
attribs.delete(:id) #get rid of the rowID from MySQL
|
44
|
+
attribs[:used_key] = attribs.delete(:key)
|
45
|
+
attribs[:used_key_len] = attribs.delete(:key_len)
|
46
|
+
query_step = MysqlExecutionStep.new(attribs)
|
47
|
+
query_step.order = idx + 1
|
48
|
+
query_step.executed_query = executed_query
|
49
|
+
|
50
|
+
query_step.save
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end #end track
|
54
|
+
end
|
55
|
+
|
56
|
+
# Let executed query know it just got some mysql_execution_steps
|
57
|
+
ExecutedQuery.send :has, (1/0.to_f), :mysql_execution_steps
|
58
|
+
|
59
|
+
# Let Cutie know she needs to treat this as an internal model
|
60
|
+
DataMapper::Cutie.add_internal_model :mysql_execution_step
|
61
|
+
|
62
|
+
# Let cutie know she has a new hook
|
63
|
+
DataMapper::Cutie.add_tracker_hook :mysql_execution_step
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# This isn't a Tracker::Hook, its actually and After Create hook for RepositoryStorage and ExecutedQuery
|
2
|
+
#
|
3
|
+
# In calculating the selectivity, since the number of records can change, I do the average selectivity
|
4
|
+
# and the final selectivity (on stopping Dm Cutie)
|
5
|
+
#
|
6
|
+
class MysqlIndex
|
7
|
+
include DataMapper::Resource
|
8
|
+
|
9
|
+
STATEMENT = "SHOW INDEX FROM %s"
|
10
|
+
|
11
|
+
belongs_to :repository_storage
|
12
|
+
property :id, Serial
|
13
|
+
property :non_unique, Boolean
|
14
|
+
property :key_name, String
|
15
|
+
property :sequence_in_index, Integer # maps to: OpenStruct#Seq_in_index
|
16
|
+
property :table, String #denormalized, accessible through repository_storage
|
17
|
+
property :column_name, String
|
18
|
+
property :collation, String
|
19
|
+
property :cardinality, Integer
|
20
|
+
property :sub_part, Integer
|
21
|
+
property :packed, String
|
22
|
+
property :null, String
|
23
|
+
property :index_type, String
|
24
|
+
|
25
|
+
######### SELECTIVITY #########
|
26
|
+
# The distinctness of the values in the column
|
27
|
+
property :distinct_count, Integer, :default => 0, :nullable => false
|
28
|
+
|
29
|
+
# denormalized, this is the count of 'executed_queries' that are related to this index
|
30
|
+
# @note there is no direction relationship (belongs_to/has) to executed_query in this model
|
31
|
+
property :execution_count, Integer, :default => 0, :nullable => false
|
32
|
+
|
33
|
+
# methods for selectivity
|
34
|
+
# selectivity is calculated by count(*) / count(distinct(COLUMN_NAME)) the closer to 1.0
|
35
|
+
# the better the index
|
36
|
+
def selectivity
|
37
|
+
( distinct_count.to_f / self.repository_storage.record_count.to_f )
|
38
|
+
end
|
39
|
+
|
40
|
+
# Shortcut method to R:W of repo
|
41
|
+
def table_read_to_write_ratio
|
42
|
+
self.repository_storage.read_to_write_ratio
|
43
|
+
end
|
44
|
+
|
45
|
+
# TODO Row length could be a useful addition to calculating this
|
46
|
+
# TODO Potential Uses vs Actual Uses could be a useful addition to calculating this
|
47
|
+
#
|
48
|
+
def usefulness
|
49
|
+
# Number of records, read to write ratio, selectivity
|
50
|
+
num_records = self.repository_storage.record_count
|
51
|
+
num_reads = self.repository_storage.total_selects
|
52
|
+
num_writes = self.repository_storage.total_inserts +
|
53
|
+
self.repository_storage.total_deletes +
|
54
|
+
self.repository_storage.total_updates
|
55
|
+
|
56
|
+
records_processed = num_records * execution_count
|
57
|
+
total_operations = (num_reads + num_writes).to_f
|
58
|
+
read_percentage = (num_reads / total_operations)
|
59
|
+
# write_percentage = (num_writes / total_operations)
|
60
|
+
return(
|
61
|
+
(total_operations * read_percentage * selectivity) / total_operations
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
# detect duplicate indexes
|
66
|
+
def duplicate
|
67
|
+
MysqlIndex.all(
|
68
|
+
:id.not => self.id,
|
69
|
+
:table => self.table,
|
70
|
+
:index_type => self.index_type,
|
71
|
+
:column_name => self.column_name
|
72
|
+
).count != 0
|
73
|
+
end
|
74
|
+
|
75
|
+
######### CLASS METHODS #########
|
76
|
+
def self.execute( target_repo, storage_name )
|
77
|
+
repository(target_repo.to_sym).adapter.query MysqlIndex::STATEMENT % storage_name
|
78
|
+
end
|
79
|
+
|
80
|
+
# Track the selectivity of an index
|
81
|
+
def self.process_executed_query( executed_query )
|
82
|
+
executing_repo = executed_query.generalized_query.primary_storage
|
83
|
+
|
84
|
+
MysqlIndex.all(MysqlIndex.repository_storage.id => executing_repo.id).each do |mysql_index|
|
85
|
+
# Get to the repo BEING tracked for distinct count
|
86
|
+
distinct_column_count = DataMapper.repository(executing_repo.repo_name.to_sym) do |exec_repo|
|
87
|
+
exec_repo.adapter.query(
|
88
|
+
"SELECT COUNT(DISTINCT(`#{mysql_index.column_name}`)) FROM `#{mysql_index.table}`"
|
89
|
+
).first
|
90
|
+
end
|
91
|
+
|
92
|
+
DataMapper::Transaction.new.link do
|
93
|
+
mysql_index.distinct_count = distinct_column_count
|
94
|
+
mysql_index.execution_count += 1
|
95
|
+
mysql_index.save
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
# Track the indexes on a Repository Storage
|
103
|
+
def self.process_repository_storage( repository_storage )
|
104
|
+
# Perform the SHOW index on the repos storage
|
105
|
+
raw_index_list = MysqlIndex.execute( repository_storage.repo_name, repository_storage.storage_name )
|
106
|
+
|
107
|
+
#Store the MysqlIndex in the DM Cutie repo
|
108
|
+
DataMapper::Cutie.repo do
|
109
|
+
raw_index_list.each_with_index do |shown_index, idx|
|
110
|
+
attribs = shown_index.attributes
|
111
|
+
attribs.delete(:comment)
|
112
|
+
attribs[:sequence_in_index] = attribs.delete(:seq_in_index)
|
113
|
+
mysql_index = MysqlIndex.new(attribs)
|
114
|
+
mysql_index.repository_storage = repository_storage
|
115
|
+
|
116
|
+
mysql_index.save
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end #end process_repository_storage
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
# Let executed query know it just got some mysql_execution_steps
|
124
|
+
RepositoryStorage.send :has, (1/0.to_f), :mysql_indexes
|
125
|
+
|
126
|
+
# Let Cutie know she needs to treat this as an internal model
|
127
|
+
DataMapper::Cutie.add_internal_model :mysql_index
|
128
|
+
|
129
|
+
# Add this to the after(:create) for RepositoryStorage
|
130
|
+
RepositoryStorage.send(:after,:create){ MysqlIndex.process_repository_storage self }
|
131
|
+
|
132
|
+
ExecutedQuery.send(:after, :create){ MysqlIndex.process_executed_query self }
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# Tracker Hook for recording mysql warnings
|
2
|
+
#
|
3
|
+
class MysqlWarning
|
4
|
+
include DataMapper::Cutie::Tracker::Hook::Abstract
|
5
|
+
include DataMapper::Resource
|
6
|
+
|
7
|
+
STATEMENT = "SHOW WARNINGS"
|
8
|
+
|
9
|
+
belongs_to :executed_query
|
10
|
+
property :id, Serial
|
11
|
+
|
12
|
+
property :level, String
|
13
|
+
property :code, Integer
|
14
|
+
property :message, Text
|
15
|
+
|
16
|
+
def self.hook_name; "MySQL Warnings" end;
|
17
|
+
def self.supported_statements; [:select, :insert, :update, :delete]; end;
|
18
|
+
def self.supported_adapters; [:mysql]; end;
|
19
|
+
|
20
|
+
def self.track( executed_query )
|
21
|
+
# Perform the show warings
|
22
|
+
raw_warnings = repository(
|
23
|
+
executed_query.generalized_query.primary_storage.repo_name.to_sym
|
24
|
+
).adapter.query MysqlWarning::STATEMENT % executed_query.statement
|
25
|
+
|
26
|
+
#Store the MysqlWarning in the DM Cutie repo
|
27
|
+
DataMapper::Cutie.repo do
|
28
|
+
raw_warnings.each_with_index do |raw_warning, idx|
|
29
|
+
next if raw_warning.attributes[:level] =~ /^note$/i
|
30
|
+
attribs = raw_warning.attributes
|
31
|
+
mysql_warning = MysqlWarning.new(attribs)
|
32
|
+
mysql_warning.executed_query = executed_query
|
33
|
+
mysql_warning.save
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end #end track
|
37
|
+
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
# Let executed query know it just got some mysql_warnings
|
42
|
+
ExecutedQuery.send :has, (1/0.to_f), :mysql_warnings
|
43
|
+
|
44
|
+
# Let Cutie know she needs to treat this as an internal model
|
45
|
+
DataMapper::Cutie.add_internal_model :mysql_warning
|
46
|
+
|
47
|
+
# Let cutie know she has a new hook
|
48
|
+
DataMapper::Cutie.add_tracker_hook :mysql_warning
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Tracker Hook for recording sqlite3 query plans
|
2
|
+
#
|
3
|
+
class Sqlite3ExecutionStep
|
4
|
+
include DataMapper::Cutie::Tracker::Hook::Abstract
|
5
|
+
include DataMapper::Resource
|
6
|
+
|
7
|
+
STATEMENT = "EXPLAIN QUERY PLAN %s"
|
8
|
+
|
9
|
+
belongs_to :executed_query
|
10
|
+
property :id, Serial
|
11
|
+
|
12
|
+
property :order, Integer, :nullable => false #the order the step was executed in
|
13
|
+
property :from, Integer, :nullable => false #the table the step was performed against
|
14
|
+
property :detail, Text # "TABLE people WITH INDEX index_people_person_name_idx"
|
15
|
+
|
16
|
+
def self.hook_name
|
17
|
+
"Sqlite3 Execution Steps"
|
18
|
+
end
|
19
|
+
def self.supported_statements
|
20
|
+
[:select, :insert, :update, :delete]
|
21
|
+
end
|
22
|
+
def self.supported_adapters
|
23
|
+
[:sqlite3]
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.track( executed_query )
|
27
|
+
# Perform the EXPLAIN against the original target repo
|
28
|
+
raw_query_plan = repository(
|
29
|
+
executed_query.generalized_query.primary_storage.repo_name.to_sym
|
30
|
+
).adapter.query Sqlite3ExecutionStep::STATEMENT % executed_query.statement
|
31
|
+
|
32
|
+
#Store the Sqlite3ExecutionStep in the DM Cutie repo
|
33
|
+
DataMapper::Cutie.repo do
|
34
|
+
raw_query_plan.each_with_index do |raw_query_step, idx|
|
35
|
+
attribs = raw_query_step.attributes
|
36
|
+
query_step = Sqlite3ExecutionStep.new(attribs)
|
37
|
+
query_step.order = attribs[:order]
|
38
|
+
query_step.from = attribs[:from]
|
39
|
+
query_step.detail = attribs[:detail]
|
40
|
+
query_step.executed_query = executed_query
|
41
|
+
query_step.save
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
# Tell executed query it has sqlite3 execution steps
|
49
|
+
ExecutedQuery.send :has, (1/0.to_f), :sqlite3_execution_steps
|
50
|
+
|
51
|
+
# Let Cutie know she needs to treat this as an internal model
|
52
|
+
DataMapper::Cutie.add_internal_model :sqlite3_execution_step
|
53
|
+
|
54
|
+
# Let cutie know she has a new hook
|
55
|
+
DataMapper::Cutie.add_tracker_hook :sqlite3_execution_step
|
@@ -0,0 +1,47 @@
|
|
1
|
+
<%
|
2
|
+
# This should be an array of DM Properties or symbols, essentially things
|
3
|
+
# that can be 'sent' to each of your records
|
4
|
+
# @my_record.send(_element_in_columns_array_)
|
5
|
+
|
6
|
+
@columns = YOUR_MODEL.properties
|
7
|
+
%>
|
8
|
+
|
9
|
+
<%= partial :menu, :locals => {:columns => @columns} %>
|
10
|
+
|
11
|
+
<%
|
12
|
+
query = {}
|
13
|
+
|
14
|
+
# @example: ExecutedQuery.generalized_query.query_storage_links.repository_storage
|
15
|
+
# @note: Yours will likeley by YOUR_MODEL.executed_query.generalized_query.query_storage_links.repository_storage
|
16
|
+
#
|
17
|
+
# The point in this is that DM CutieUI allows you to filter result sets by Repository, Adapter and Model
|
18
|
+
# This builds the join statement for narrowing that down. You just have to set up how to find the relationshps
|
19
|
+
# between this model and the RepositoryStorage model
|
20
|
+
relationship_path_to_repo_storage_model = YOUR_MODEL.relationship.path.to.repository_storage
|
21
|
+
|
22
|
+
unless ram_filters[:repository].blank?
|
23
|
+
query[relationship_path_to_repo_storage_model.repo_name]= ram_filters[:repository]
|
24
|
+
end
|
25
|
+
|
26
|
+
unless ram_filters[:adapter].blank?
|
27
|
+
query[relationship_path_to_repo_storage_model.adapter_name]= ram_filters[:adapter]
|
28
|
+
end
|
29
|
+
|
30
|
+
unless ram_filters[:model].blank?
|
31
|
+
query[relationship_path_to_repo_storage_model.model_name]= ram_filters[:model]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Additionally parameters may come in with specific values to filter from the result set
|
35
|
+
# query = parse_params(YOUR_MODEL).merge(query)
|
36
|
+
|
37
|
+
@records = YOUR_MODEL.all(query)
|
38
|
+
%>
|
39
|
+
|
40
|
+
<%= partial :grid,
|
41
|
+
:locals => {
|
42
|
+
:columns => @columns,
|
43
|
+
:records => @records,
|
44
|
+
:adhoc_columns => {},
|
45
|
+
:relationships => YOUR_MODEL.relationships
|
46
|
+
}
|
47
|
+
%>
|
@@ -0,0 +1,71 @@
|
|
1
|
+
<% help_link %>
|
2
|
+
<% @columns = MysqlExecutionStep.properties %>
|
3
|
+
<%= partial :menu, :locals => {:columns => @columns } %>
|
4
|
+
|
5
|
+
<%
|
6
|
+
query = {}
|
7
|
+
path_to_repo_storage = MysqlExecutionStep.executed_query.generalized_query.query_storage_links.repository_storage
|
8
|
+
unless ram_filters[:repository].blank?
|
9
|
+
query[path_to_repo_storage.repo_name]= ram_filters[:repository]
|
10
|
+
end
|
11
|
+
|
12
|
+
unless ram_filters[:adapter].blank?
|
13
|
+
query[path_to_repo_storage.adapter_name]= ram_filters[:adapter]
|
14
|
+
end
|
15
|
+
|
16
|
+
unless ram_filters[:model].blank?
|
17
|
+
query[path_to_repo_storage.model_name]= ram_filters[:model]
|
18
|
+
end
|
19
|
+
|
20
|
+
query = parse_params(MysqlExecutionStep).merge(query)
|
21
|
+
@records = MysqlExecutionStep.all(query)
|
22
|
+
%>
|
23
|
+
|
24
|
+
<%= partial :grid,
|
25
|
+
:locals => {
|
26
|
+
:columns => @columns,
|
27
|
+
:records => @records,
|
28
|
+
:relationships => MysqlExecutionStep.relationships
|
29
|
+
}
|
30
|
+
%>
|
31
|
+
|
32
|
+
<a name="help">
|
33
|
+
<div class="help_container">
|
34
|
+
<p>
|
35
|
+
<a href="http://dev.mysql.com/doc/refman/5.0/en/using-explain.html">
|
36
|
+
Details on MySQL's explain statement can be read up on here.
|
37
|
+
</a>
|
38
|
+
</p>
|
39
|
+
<p>
|
40
|
+
Below is a list of column explanations
|
41
|
+
<ul>
|
42
|
+
<li>select_type - type of SELECT</li>
|
43
|
+
<li>table - the name of the table or alias</li>
|
44
|
+
<li>type - the type of join used for the query</li>
|
45
|
+
<li>possible_keys - indexes MySQL could possible use</li>
|
46
|
+
<li>used_key - indexes MySQL actually used</li>
|
47
|
+
<li>used_key_len - the total length of keys used</li>
|
48
|
+
<li>ref - any columns used with key to retrieve results</li>
|
49
|
+
<li>rows - estimated number of rows returned</li>
|
50
|
+
<li>extra - additional details</li>
|
51
|
+
</ul>
|
52
|
+
|
53
|
+
Below is a list of the MySQL Execution Plan step 'types'. They are listed in order from best to worst.
|
54
|
+
<ul>
|
55
|
+
<li>system</li>
|
56
|
+
<li>const</li>
|
57
|
+
<li>eq_ref</li>
|
58
|
+
<li>ref</li>
|
59
|
+
<li>fulltext</li>
|
60
|
+
<li>ref_or_null</li>
|
61
|
+
<li>index_merge</li>
|
62
|
+
<li>unique_subquery</li>
|
63
|
+
<li>index_subquery</li>
|
64
|
+
<li>range</li>
|
65
|
+
<li>index</li>
|
66
|
+
<li>all</li>
|
67
|
+
</ul>
|
68
|
+
</p>
|
69
|
+
|
70
|
+
</div>
|
71
|
+
</a>
|
@@ -0,0 +1,45 @@
|
|
1
|
+
<% @columns = MysqlIndex.properties.to_a + [:usefulness, :selectivity, :table_read_to_write_ratio, :usefulness, :duplicate] %>
|
2
|
+
|
3
|
+
<%= partial :menu, :locals => {:columns => @columns} %>
|
4
|
+
|
5
|
+
<%
|
6
|
+
query = {}
|
7
|
+
|
8
|
+
unless ram_filters[:repository].blank?
|
9
|
+
query[MysqlIndex.repository_storage.repo_name]= ram_filters[:repository]
|
10
|
+
end
|
11
|
+
|
12
|
+
unless ram_filters[:adapter].blank?
|
13
|
+
query[MysqlIndex.repository_storage.adapter_name]= ram_filters[:adapter]
|
14
|
+
end
|
15
|
+
|
16
|
+
unless ram_filters[:model].blank?
|
17
|
+
query[MysqlIndex.repository_storage.model_name]= ram_filters[:model]
|
18
|
+
end
|
19
|
+
query = parse_params(MysqlIndex).merge(query)
|
20
|
+
@records = MysqlIndex.all(query)
|
21
|
+
%>
|
22
|
+
|
23
|
+
<%= partial :grid,
|
24
|
+
:locals => {
|
25
|
+
:columns => @columns,
|
26
|
+
:records => @records,
|
27
|
+
:adhoc_columns => {},
|
28
|
+
:relationships => MysqlIndex.relationships
|
29
|
+
}
|
30
|
+
%>
|
31
|
+
|
32
|
+
<a name="help">
|
33
|
+
<div class="help_container">
|
34
|
+
<p>
|
35
|
+
<em>Selectivity</em> measures the selectiveness of an index. The closer to '1' the better, the closer it is to '0' the MySQL optimizer will probably not use it.
|
36
|
+
Unused indexes should be dropped because they still slow down write performance.
|
37
|
+
</p>
|
38
|
+
<p>
|
39
|
+
<em>Duplicate</em> determines whether a duplicate (same index_type) index is on the table/column. Duplicate indexes require <em>n</em> times the index management, and should be avoided
|
40
|
+
</p>
|
41
|
+
<p>
|
42
|
+
<em>Usefulness</em> is just a weight to compare indexes on the same table to determine how useful an index may be. This calculation will change over time as this mysql_index plugin gets more refined. From 0 to 1, the closer to 1 the better.
|
43
|
+
</p>
|
44
|
+
</div>
|
45
|
+
</a>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<%
|
2
|
+
@columns = MysqlWarning.properties
|
3
|
+
%>
|
4
|
+
|
5
|
+
<%= partial :menu, :locals => {:columns => @columns} %>
|
6
|
+
|
7
|
+
<%
|
8
|
+
query = {}
|
9
|
+
|
10
|
+
relationship_path_to_repo_storage_model = MysqlWarning.executed_query.generalized_query.query_storage_links.repository_storage
|
11
|
+
|
12
|
+
unless ram_filters[:repository].blank?
|
13
|
+
query[relationship_path_to_repo_storage_model.repo_name]= ram_filters[:repository]
|
14
|
+
end
|
15
|
+
|
16
|
+
unless ram_filters[:adapter].blank?
|
17
|
+
query[relationship_path_to_repo_storage_model.adapter_name]= ram_filters[:adapter]
|
18
|
+
end
|
19
|
+
|
20
|
+
unless ram_filters[:model].blank?
|
21
|
+
query[relationship_path_to_repo_storage_model.model_name]= ram_filters[:model]
|
22
|
+
end
|
23
|
+
|
24
|
+
query = parse_params(MysqlWarning).merge(query)
|
25
|
+
@records = MysqlWarning.all(query)
|
26
|
+
%>
|
27
|
+
|
28
|
+
<%= partial :grid,
|
29
|
+
:locals => {
|
30
|
+
:columns => @columns,
|
31
|
+
:records => @records,
|
32
|
+
:adhoc_columns => {},
|
33
|
+
:relationships => MysqlWarning.relationships
|
34
|
+
}
|
35
|
+
%>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<% @columns = Sqlite3ExecutionStep.properties %>
|
2
|
+
<%= partial :menu, :locals => {:columns => @columns} %>
|
3
|
+
|
4
|
+
<%
|
5
|
+
query = {}
|
6
|
+
path_to_repo_storage = Sqlite3ExecutionStep.executed_query.generalized_query.query_storage_links.repository_storage
|
7
|
+
unless ram_filters[:repository].blank?
|
8
|
+
query[path_to_repo_storage.repo_name]= ram_filters[:repository]
|
9
|
+
end
|
10
|
+
|
11
|
+
unless ram_filters[:adapter].blank?
|
12
|
+
query[path_to_repo_storage.adapter_name]= ram_filters[:adapter]
|
13
|
+
end
|
14
|
+
|
15
|
+
unless ram_filters[:model].blank?
|
16
|
+
query[path_to_repo_storage.model_name]= ram_filters[:model]
|
17
|
+
end
|
18
|
+
|
19
|
+
query = parse_params(Sqlite3ExecutionStep).merge(query)
|
20
|
+
@records = Sqlite3ExecutionStep.all(query)
|
21
|
+
%>
|
22
|
+
|
23
|
+
<%= partial :grid,
|
24
|
+
:locals => {
|
25
|
+
:columns => @columns,
|
26
|
+
:records => @records,
|
27
|
+
:relationships => Sqlite3ExecutionStep.relationships
|
28
|
+
}
|
29
|
+
%>
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dm-cutie-extras
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Cory ODaniel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-15 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: extlib
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.9.12
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: dm-core
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.10.0
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: dm-types
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.10.0
|
44
|
+
version:
|
45
|
+
description: Cool additional hooks for DM Cutie.
|
46
|
+
email: cutie@coryodaniel.com
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- README.markdown
|
53
|
+
- LICENSE
|
54
|
+
- History.txt
|
55
|
+
- TODO.markdown
|
56
|
+
files:
|
57
|
+
- LICENSE
|
58
|
+
- README.markdown
|
59
|
+
- Rakefile
|
60
|
+
- TODO.markdown
|
61
|
+
- lib/dm-cutie-extras.rb
|
62
|
+
- lib/dm-cutie-extras/version.rb
|
63
|
+
- History.txt
|
64
|
+
- lib/dm-cutie-extras/views/_template_.erb
|
65
|
+
- lib/dm-cutie-extras/views/mysql_execution_steps.erb
|
66
|
+
- lib/dm-cutie-extras/views/mysql_indexes.erb
|
67
|
+
- lib/dm-cutie-extras/views/mysql_warnings.erb
|
68
|
+
- lib/dm-cutie-extras/views/sqlite3_execution_steps.erb
|
69
|
+
- lib/dm-cutie-extras/hooks/mysql_execution_step.rb
|
70
|
+
- lib/dm-cutie-extras/hooks/mysql_index.rb
|
71
|
+
- lib/dm-cutie-extras/hooks/mysql_warning.rb
|
72
|
+
- lib/dm-cutie-extras/hooks/sqlite3_execution_step.rb
|
73
|
+
has_rdoc: true
|
74
|
+
homepage: http://github.com/coryodaniel/dm-cutie-extras
|
75
|
+
licenses: []
|
76
|
+
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: "0"
|
87
|
+
version:
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: "0"
|
93
|
+
version:
|
94
|
+
requirements: []
|
95
|
+
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 1.3.5
|
98
|
+
signing_key:
|
99
|
+
specification_version: 3
|
100
|
+
summary: DM Cutie's Extras
|
101
|
+
test_files: []
|
102
|
+
|