dm-cutie-extras 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|