dm-cutie 0.3.16
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 +352 -0
- data/Rakefile +103 -0
- data/lib/dm-cutie.rb +29 -0
- data/lib/dm-cutie/cutie.rb +283 -0
- data/lib/dm-cutie/models/executed_query.rb +49 -0
- data/lib/dm-cutie/models/generalized_query.rb +108 -0
- data/lib/dm-cutie/models/query_storage_link.rb +15 -0
- data/lib/dm-cutie/models/repository_storage.rb +67 -0
- data/lib/dm-cutie/tracker/data_objects.rb +16 -0
- data/lib/dm-cutie/tracker/data_objects/helper.rb +85 -0
- data/lib/dm-cutie/tracker/data_objects/overrides.rb +161 -0
- data/lib/dm-cutie/tracker/hook/abstract.rb +57 -0
- data/lib/dm-cutie/version.rb +5 -0
- metadata +100 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
# Link between an generalized query and a repository_storage, has boolean to track if it was the primary storage,
|
2
|
+
# ie, that of the model that generated the query
|
3
|
+
|
4
|
+
class QueryStorageLink
|
5
|
+
include DataMapper::Resource
|
6
|
+
|
7
|
+
belongs_to :repository_storage
|
8
|
+
belongs_to :generalized_query
|
9
|
+
|
10
|
+
property :repository_storage_id, Integer, :key => true
|
11
|
+
property :generalized_query_id, Integer, :key => true
|
12
|
+
|
13
|
+
property :primary_storage, Boolean, :default => true
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
# Storage information for a particular model & repository
|
4
|
+
# This table is a denormalized combination of Repositories and Models
|
5
|
+
class RepositoryStorage
|
6
|
+
include DataMapper::Resource
|
7
|
+
|
8
|
+
has n, :query_storage_links
|
9
|
+
|
10
|
+
after(:save){
|
11
|
+
DataMapper.logger.debug "Created RepositoryStorage ##{self.id}, #{self.signature}"
|
12
|
+
}
|
13
|
+
|
14
|
+
property :id, Serial
|
15
|
+
property :repo_name, String, :nullable => false
|
16
|
+
property :storage_name, String, :nullable => false
|
17
|
+
property :adapter_name, String, :nullable => false
|
18
|
+
property :model_name, String, :nullable => false
|
19
|
+
|
20
|
+
property :record_count, Integer, :nullable => false, :default => 0
|
21
|
+
|
22
|
+
property :signature, String, :length => 40, :nullable => false, :unique_index => true
|
23
|
+
|
24
|
+
def RepositoryStorage.find_or_make(r,a,s,m)
|
25
|
+
_signature = RepositoryStorage.names_to_signature(r,a,s,m)
|
26
|
+
|
27
|
+
_rs = RepositoryStorage.first(:signature => _signature) || RepositoryStorage.new
|
28
|
+
|
29
|
+
if _rs.new?
|
30
|
+
_rs.signature = _signature
|
31
|
+
_rs.repo_name = r
|
32
|
+
_rs.adapter_name = a
|
33
|
+
_rs.storage_name = s
|
34
|
+
_rs.model_name = m
|
35
|
+
_rs.save
|
36
|
+
end
|
37
|
+
_rs
|
38
|
+
end
|
39
|
+
|
40
|
+
def count_operation(op)
|
41
|
+
GeneralizedQuery.all(GeneralizedQuery.query_storage_links.repository_storage.id => self.id, :operation => op).length
|
42
|
+
end
|
43
|
+
|
44
|
+
def total_selects
|
45
|
+
@total_selects ||= count_operation(:select)
|
46
|
+
end
|
47
|
+
def total_inserts
|
48
|
+
@total_inserts ||= count_operation(:insert)
|
49
|
+
end
|
50
|
+
def total_deletes
|
51
|
+
@total_deletes ||= count_operation(:delete)
|
52
|
+
end
|
53
|
+
def total_updates
|
54
|
+
@total_updates ||= count_operation(:update)
|
55
|
+
end
|
56
|
+
def read_to_write_ratio
|
57
|
+
"#{total_selects}:#{total_inserts+total_deletes+total_updates}"
|
58
|
+
end
|
59
|
+
|
60
|
+
# @param r [~String] Repository name
|
61
|
+
# @param s [~String] Storage name
|
62
|
+
# @param a [~String] Adapter name
|
63
|
+
# @param m [~String] Model name
|
64
|
+
def RepositoryStorage.names_to_signature(r,a,s,m)
|
65
|
+
Digest::SHA1.hexdigest("#{r}>#{a}>#{s}>#{m}")
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
|
3
|
+
# module DataMapper
|
4
|
+
# module Cutie
|
5
|
+
# module Tracker
|
6
|
+
# module DataObjects
|
7
|
+
# end
|
8
|
+
# end
|
9
|
+
# end
|
10
|
+
# end
|
11
|
+
|
12
|
+
require DataMapper::Cutie.root / :tracker / :data_objects / :overrides
|
13
|
+
require DataMapper::Cutie.root / :tracker / :data_objects / :helper
|
14
|
+
|
15
|
+
# DataMapper::Adapters::DataObjectsAdapter.extend DataMapper::Cutie::Tracker::DataObjects::Overrides
|
16
|
+
# DataMapper::Adapters::DataObjectsAdapter.extend DataMapper::Cutie::Tracker::DataObjects::Helper
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
#
|
3
|
+
# @note: This adds a few helper methods to DataObjectsAdapter
|
4
|
+
#
|
5
|
+
class DataMapper::Adapters::DataObjectsAdapter
|
6
|
+
|
7
|
+
# This source method is 6 calls deep in apps started with a bin like merb, rails; otherwise, we are looking for the last line
|
8
|
+
# /Volumes/Workspace/dm-cutie-0.3.13/lib/dm-cutie/tracker/data_objects_adapter_helper.rb:38:in `read'
|
9
|
+
# /Volumes/Workspace/dm-core-0.10.1/lib/dm-core/adapters/data_objects_adapter.rb:266:in `with_connection'
|
10
|
+
# /Volumes/Workspace/dm-cutie-0.3.13/lib/dm-cutie/tracker/data_objects_adapter_override.rb:67:in `read'
|
11
|
+
# /Volumes/Workspace/dm-core-0.10.1/lib/dm-core/repository.rb:141:in `read'
|
12
|
+
# /Volumes/Workspace/dm-core-0.10.1/lib/dm-core/model.rb:303:in `first'
|
13
|
+
# /Volumes/Workspace/dm-core-0.10.1/lib/dm-core/model.rb:204:in `get'
|
14
|
+
# /Volumes/Workspace/dm-cutie-0.3.13/kickstart.rb:50
|
15
|
+
#
|
16
|
+
def _calls_deep;6;end;
|
17
|
+
|
18
|
+
# Accessor for query timers
|
19
|
+
def query_timers;(@__query_timers ||= {});end;
|
20
|
+
|
21
|
+
# - bind_statement: binds values to a SQL statement
|
22
|
+
#
|
23
|
+
# @param statement [~String] SQL Statement
|
24
|
+
# @param *bind_values [*splat] Values to bind
|
25
|
+
#
|
26
|
+
# @return [String] Bound SQL Statement
|
27
|
+
#
|
28
|
+
def bind_statement(statement, *bind_values)
|
29
|
+
_ret_stmnt = ''
|
30
|
+
with_connection do |connection|
|
31
|
+
command = connection.create_command(statement)
|
32
|
+
_ret_stmnt = command.send :escape_sql, *bind_values
|
33
|
+
end
|
34
|
+
_ret_stmnt
|
35
|
+
end
|
36
|
+
|
37
|
+
def start_tracking_timer(query)
|
38
|
+
if DataMapper::Cutie.tracking_query?(query)
|
39
|
+
query_timers[query.object_id] = Time.now
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def track_query(query, statement, bind_values)
|
44
|
+
if DataMapper::Cutie.tracking_query? query
|
45
|
+
query_start_time = query_timers.delete(query.object_id)
|
46
|
+
query_end_time = Time.now
|
47
|
+
|
48
|
+
_call_info = caller[_calls_deep] || caller.last
|
49
|
+
_call_info = _call_info.split(':')
|
50
|
+
|
51
|
+
DataMapper::Cutie.repo do
|
52
|
+
generalized_query = GeneralizedQuery.find_or_make query, statement
|
53
|
+
DataMapper.logger.debug "BEGAN PROFILING ~ #{query.model} : #{generalized_query.operation}"
|
54
|
+
executed_query = ExecutedQuery.new
|
55
|
+
|
56
|
+
executed_query.statement = bind_statement(statement, bind_values)
|
57
|
+
|
58
|
+
executed_query.repo_signature = Digest::SHA1.hexdigest("#{query.repository.name}: #{executed_query.statement}")
|
59
|
+
executed_query.signature = Digest::SHA1.hexdigest(executed_query.statement)
|
60
|
+
|
61
|
+
executed_query.elapsed_time = BigDecimal.new(query_end_time.to_f.to_s) - BigDecimal.new(query_start_time.to_f.to_s)
|
62
|
+
executed_query.executed_at = query_start_time
|
63
|
+
|
64
|
+
# Manhandle the data if its too big, should the fields just be blobs instead?
|
65
|
+
executed_query.caller_file = _call_info[0][ -255, _call_info[0].length ] if _call_info[0]
|
66
|
+
executed_query.caller_line = _call_info[1]
|
67
|
+
executed_query.caller_method = _call_info[2][ 0, 255 ] if _call_info[2]
|
68
|
+
|
69
|
+
executed_query.bind_values = bind_values
|
70
|
+
executed_query.generalized_query = generalized_query
|
71
|
+
|
72
|
+
executed_query.save
|
73
|
+
|
74
|
+
DataMapper::Cutie.process_tracker_hooks(
|
75
|
+
query.repository.adapter.options[:adapter], # The current adapter
|
76
|
+
generalized_query.operation, # The current operation
|
77
|
+
executed_query # The currently executed query
|
78
|
+
)
|
79
|
+
|
80
|
+
end # Finish DataMapper::Cutie.repo context
|
81
|
+
|
82
|
+
end #End if tracking_query?
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
class DataMapper::Adapters::DataObjectsAdapter
|
2
|
+
def create(resources)
|
3
|
+
resources.each do |resource|
|
4
|
+
model = resource.model
|
5
|
+
serial = model.serial(name)
|
6
|
+
attributes = resource.dirty_attributes
|
7
|
+
|
8
|
+
properties = []
|
9
|
+
bind_values = []
|
10
|
+
|
11
|
+
# make the order of the properties consistent
|
12
|
+
model.properties(name).each do |property|
|
13
|
+
next unless attributes.key?(property)
|
14
|
+
|
15
|
+
bind_value = attributes[property]
|
16
|
+
|
17
|
+
# skip insering NULL for columns that are serial or without a default
|
18
|
+
next if bind_value.nil? && (property.serial? || !property.default?)
|
19
|
+
|
20
|
+
# if serial is being set explicitly, do not set it again
|
21
|
+
if property.equal?(serial)
|
22
|
+
serial = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
properties << property
|
26
|
+
bind_values << bind_value
|
27
|
+
end
|
28
|
+
|
29
|
+
statement = insert_statement(model, properties, serial)
|
30
|
+
|
31
|
+
#### BEGIN DataMapper::Cutie ####
|
32
|
+
# @note: Need a Query object for the tracker or to rewrite what the tracker takes, it was easier to just
|
33
|
+
# make a BS Query object to pass in since the other CRUD methods have a query object.
|
34
|
+
query = DataMapper::Query.new(resource.repository, resource.model)
|
35
|
+
|
36
|
+
start_tracking_timer(query)
|
37
|
+
|
38
|
+
result = execute(statement, *bind_values)
|
39
|
+
|
40
|
+
#### END DataMapper::Cutie ####
|
41
|
+
track_query(query, statement, bind_values)
|
42
|
+
|
43
|
+
if result.to_i == 1 && serial
|
44
|
+
serial.set!(resource, result.insert_id)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Constructs and executes SELECT query, then instantiates
|
50
|
+
# one or many object from result set.
|
51
|
+
#
|
52
|
+
# @param [Query] query
|
53
|
+
# composition of the query to perform
|
54
|
+
#
|
55
|
+
# @return [Array]
|
56
|
+
# result set of the query
|
57
|
+
#
|
58
|
+
# @api semipublic
|
59
|
+
def read(query)
|
60
|
+
fields = query.fields
|
61
|
+
types = fields.map { |property| property.primitive }
|
62
|
+
|
63
|
+
statement, bind_values = select_statement(query)
|
64
|
+
|
65
|
+
records = []
|
66
|
+
|
67
|
+
with_connection do |connection|
|
68
|
+
command = connection.create_command(statement)
|
69
|
+
command.set_types(types)
|
70
|
+
|
71
|
+
#### BEGIN DataMapper::Cutie ####
|
72
|
+
start_tracking_timer(query)
|
73
|
+
|
74
|
+
reader = command.execute_reader(*bind_values)
|
75
|
+
|
76
|
+
track_query(query, statement, bind_values)
|
77
|
+
#### END DataMapper::Cutie ####
|
78
|
+
|
79
|
+
begin
|
80
|
+
while reader.next!
|
81
|
+
records << fields.zip(reader.values).to_hash
|
82
|
+
end
|
83
|
+
ensure
|
84
|
+
reader.close
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
records
|
89
|
+
end
|
90
|
+
|
91
|
+
# Constructs and executes UPDATE statement for given
|
92
|
+
# attributes and a query
|
93
|
+
#
|
94
|
+
# @param [Hash(Property => Object)] attributes
|
95
|
+
# hash of attribute values to set, keyed by Property
|
96
|
+
# @param [Collection] collection
|
97
|
+
# collection of records to be updated
|
98
|
+
#
|
99
|
+
# @return [Integer]
|
100
|
+
# the number of records updated
|
101
|
+
#
|
102
|
+
# @api semipublic
|
103
|
+
def update(attributes, collection)
|
104
|
+
query = collection.query
|
105
|
+
|
106
|
+
# TODO: if the query contains any links, a limit or an offset
|
107
|
+
# use a subselect to get the rows to be updated
|
108
|
+
|
109
|
+
properties = []
|
110
|
+
bind_values = []
|
111
|
+
|
112
|
+
# make the order of the properties consistent
|
113
|
+
query.model.properties(name).each do |property|
|
114
|
+
next unless attributes.key?(property)
|
115
|
+
properties << property
|
116
|
+
bind_values << attributes[property]
|
117
|
+
end
|
118
|
+
|
119
|
+
statement, conditions_bind_values = update_statement(properties, query)
|
120
|
+
|
121
|
+
bind_values.concat(conditions_bind_values)
|
122
|
+
|
123
|
+
#### BEGIN DataMapper::Cutie ####
|
124
|
+
start_tracking_timer(query)
|
125
|
+
|
126
|
+
result = execute(statement, *bind_values).to_i
|
127
|
+
|
128
|
+
#### END DataMapper::Cutie ####
|
129
|
+
track_query(query, statement, bind_values)
|
130
|
+
|
131
|
+
result
|
132
|
+
end
|
133
|
+
|
134
|
+
# Constructs and executes DELETE statement for given query
|
135
|
+
#
|
136
|
+
# @param [Collection] collection
|
137
|
+
# collection of records to be deleted
|
138
|
+
#
|
139
|
+
# @return [Integer]
|
140
|
+
# the number of records deleted
|
141
|
+
#
|
142
|
+
# @api semipublic
|
143
|
+
def delete(collection)
|
144
|
+
query = collection.query
|
145
|
+
|
146
|
+
# TODO: if the query contains any links, a limit or an offset
|
147
|
+
# use a subselect to get the rows to be deleted
|
148
|
+
|
149
|
+
statement, bind_values = delete_statement(query)
|
150
|
+
|
151
|
+
#### BEGIN DataMapper::Cutie ####
|
152
|
+
start_tracking_timer(query)
|
153
|
+
|
154
|
+
result = execute(statement, *bind_values).to_i
|
155
|
+
|
156
|
+
#### END DataMapper::Cutie ####
|
157
|
+
track_query(query, statement, bind_values)
|
158
|
+
|
159
|
+
result
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Cutie
|
3
|
+
module Tracker
|
4
|
+
module Hook
|
5
|
+
module Abstract
|
6
|
+
def self.included(model)
|
7
|
+
model.extend DataMapper::Cutie::Tracker::Hook::Abstract::ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
# - supported_statements - Statements supported by this tracker hook
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# [:delete]
|
15
|
+
#
|
16
|
+
# @return [Array[Symbol]]
|
17
|
+
#
|
18
|
+
def supported_statements
|
19
|
+
[:select, :insert, :update, :delete]
|
20
|
+
end
|
21
|
+
|
22
|
+
# - supported_adapters - List of adapters that will use this hook
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
# [ :mysql, :sqlite3 ]
|
26
|
+
#
|
27
|
+
# @return [Array[Symbol]]
|
28
|
+
#
|
29
|
+
def supported_adapters
|
30
|
+
raise Exception, "#{self.class}.supported_adapters was not implemented"
|
31
|
+
end
|
32
|
+
|
33
|
+
# - track - Factory method that is called to start your hook
|
34
|
+
#
|
35
|
+
# @param executed_query [ExecutedQuery] - the executed query
|
36
|
+
#
|
37
|
+
# return [NilClass]
|
38
|
+
#
|
39
|
+
def track( executed_query )
|
40
|
+
raise Exception, "#{self.class}.track was not implemented"
|
41
|
+
end
|
42
|
+
|
43
|
+
# - hook_name - The name of your hook
|
44
|
+
#
|
45
|
+
# @return [String]
|
46
|
+
#
|
47
|
+
def hook_name
|
48
|
+
raise Exception, "#{self.class}.hook_name was not implemented"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module InstanceMethods;end;
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dm-cutie
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.16
|
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: DataMapper's Query Tracker; She is super thorough and easy as hell, just the way you like it.
|
46
|
+
email: cutie@coryodaniel.com
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- README.markdown
|
53
|
+
- LICENSE
|
54
|
+
- History.txt
|
55
|
+
files:
|
56
|
+
- LICENSE
|
57
|
+
- README.markdown
|
58
|
+
- Rakefile
|
59
|
+
- lib/dm-cutie.rb
|
60
|
+
- lib/dm-cutie/cutie.rb
|
61
|
+
- lib/dm-cutie/version.rb
|
62
|
+
- lib/dm-cutie/models/executed_query.rb
|
63
|
+
- lib/dm-cutie/models/query_storage_link.rb
|
64
|
+
- lib/dm-cutie/models/repository_storage.rb
|
65
|
+
- lib/dm-cutie/models/generalized_query.rb
|
66
|
+
- lib/dm-cutie/tracker/data_objects.rb
|
67
|
+
- lib/dm-cutie/tracker/data_objects/helper.rb
|
68
|
+
- lib/dm-cutie/tracker/data_objects/overrides.rb
|
69
|
+
- lib/dm-cutie/tracker/hook/abstract.rb
|
70
|
+
- History.txt
|
71
|
+
has_rdoc: true
|
72
|
+
homepage: http://github.com/coryodaniel/dm-cutie
|
73
|
+
licenses: []
|
74
|
+
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: "0"
|
85
|
+
version:
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: "0"
|
91
|
+
version:
|
92
|
+
requirements: []
|
93
|
+
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 1.3.5
|
96
|
+
signing_key:
|
97
|
+
specification_version: 3
|
98
|
+
summary: DataMapper's Original Query Tracker'
|
99
|
+
test_files: []
|
100
|
+
|