data_objects 0.10.0 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/ChangeLog.markdown +20 -0
  2. data/LICENSE +1 -29
  3. data/README.markdown +16 -2
  4. data/Rakefile +41 -7
  5. data/lib/data_objects.rb +3 -2
  6. data/lib/data_objects/byte_array.rb +6 -0
  7. data/lib/data_objects/connection.rb +21 -9
  8. data/lib/data_objects/logger.rb +15 -15
  9. data/lib/data_objects/pooling.rb +250 -0
  10. data/lib/data_objects/reader.rb +16 -0
  11. data/lib/data_objects/spec/bacon.rb +9 -0
  12. data/lib/data_objects/spec/command_spec.rb +54 -47
  13. data/lib/data_objects/spec/connection_spec.rb +119 -30
  14. data/lib/data_objects/spec/encoding_spec.rb +64 -6
  15. data/lib/data_objects/spec/helpers/immediate_red_green_output.rb +59 -0
  16. data/lib/data_objects/spec/helpers/pending.rb +22 -0
  17. data/lib/data_objects/spec/helpers/ssl.rb +21 -0
  18. data/lib/data_objects/spec/reader_spec.rb +47 -24
  19. data/lib/data_objects/spec/result_spec.rb +10 -19
  20. data/lib/data_objects/spec/typecast/array_spec.rb +16 -20
  21. data/lib/data_objects/spec/typecast/bigdecimal_spec.rb +16 -24
  22. data/lib/data_objects/spec/typecast/boolean_spec.rb +16 -24
  23. data/lib/data_objects/spec/typecast/byte_array_spec.rb +11 -15
  24. data/lib/data_objects/spec/typecast/class_spec.rb +7 -11
  25. data/lib/data_objects/spec/typecast/date_spec.rb +17 -25
  26. data/lib/data_objects/spec/typecast/datetime_spec.rb +18 -26
  27. data/lib/data_objects/spec/typecast/float_spec.rb +19 -27
  28. data/lib/data_objects/spec/typecast/integer_spec.rb +10 -14
  29. data/lib/data_objects/spec/typecast/nil_spec.rb +18 -30
  30. data/lib/data_objects/spec/typecast/other_spec.rb +45 -0
  31. data/lib/data_objects/spec/typecast/range_spec.rb +16 -20
  32. data/lib/data_objects/spec/typecast/string_spec.rb +72 -13
  33. data/lib/data_objects/spec/typecast/time_spec.rb +11 -15
  34. data/lib/data_objects/utilities.rb +18 -0
  35. data/lib/data_objects/version.rb +1 -2
  36. data/spec/command_spec.rb +2 -2
  37. data/spec/connection_spec.rb +7 -5
  38. data/spec/do_mock2.rb +31 -0
  39. data/spec/pooling_spec.rb +162 -0
  40. data/spec/reader_spec.rb +7 -4
  41. data/spec/result_spec.rb +2 -2
  42. data/spec/spec_helper.rb +26 -5
  43. data/spec/transaction_spec.rb +11 -9
  44. data/tasks/metrics.rake +36 -0
  45. data/tasks/release.rake +10 -70
  46. data/tasks/spec.rake +16 -14
  47. data/tasks/yard.rake +9 -0
  48. data/tasks/yardstick.rake +19 -0
  49. metadata +53 -27
  50. data/HISTORY.markdown +0 -7
  51. data/Manifest.txt +0 -44
  52. data/spec/lib/pending_helpers.rb +0 -11
  53. data/spec/lib/rspec_immediate_feedback_formatter.rb +0 -53
  54. data/spec/lib/ssl_helpers.rb +0 -20
  55. data/tasks/gem.rake +0 -8
  56. data/tasks/install.rake +0 -13
@@ -0,0 +1,20 @@
1
+ ## 0.10.1 (unreleased, in git)
2
+
3
+ * Removal of Extlib dependency: Pooling and Utilities code moved to DataObjects.
4
+ * Switch to Jeweler for Gem building tasks (this change may be temporary).
5
+ * Switch to using Bacon for running specs: This should make specs friendlier to
6
+ new Ruby implementations that are not yet 100% MRI-compatible, and in turn,
7
+ prepared the road for our own IronRuby and MacRuby support.
8
+ * Make DataObjects::Reader Enumerable.
9
+
10
+ ## 0.10.0 2009-09-15
11
+
12
+ * No Changes since 0.9.11
13
+
14
+ ## 0.9.11 2009-01-19
15
+ * Fixes
16
+ * Use Extlib `Object.full_const_get` instead of custom code
17
+ * Remove Field as it was unused
18
+
19
+ ## 0.9.9 2008-11-27
20
+ * No Changes since 0.9.8
data/LICENSE CHANGED
@@ -1,32 +1,4 @@
1
- Copyright (c) 2007, 2008, 2009 Yehuda Katz, Dirkjan Bussink
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
-
22
- ---
23
- ---
24
-
25
- Some portions of tasks/ext_helper_java.rb are verbatim copies of software
26
- licensed under the MIT license. That license is included below:
27
-
28
- Copyright (c) 2006-2008 Nick Sieger <nick@nicksieger.com>
29
- Copyright (c) 2006-2008 Ola Bini <ola.bini@gmail.com>
1
+ Copyright (c) 2007 - 2010 Yehuda Katz, Dirkjan Bussink
30
2
 
31
3
  Permission is hereby granted, free of charge, to any person obtaining
32
4
  a copy of this software and associated documentation files (the
data/README.markdown CHANGED
@@ -1,4 +1,18 @@
1
- data_objects
2
- ============
1
+ # do_derby
2
+
3
+ * <http://dataobjects.info>
4
+
5
+ ## Description
3
6
 
4
7
  A unified Ruby API for popular databases.
8
+
9
+ ## License
10
+
11
+ Licensed under the MIT license. Please see the {file:LICENSE} for more information.
12
+
13
+ ## Contact
14
+
15
+ **IRC**: **Join us on IRC in #datamapper on irc.freenode.net!**<br/>
16
+ **Git**: <http://github.com/datamapper/do><br/>
17
+ **Author**: Dirkjan Bussink<br/>
18
+ **License**: MIT License
data/Rakefile CHANGED
@@ -1,15 +1,49 @@
1
+ require 'pathname'
1
2
  require 'rubygems'
2
3
  require 'rake'
3
4
  require 'rake/clean'
4
5
 
5
- require 'pathname'
6
- require 'lib/data_objects/version'
6
+ ROOT = Pathname(__FILE__).dirname.expand_path
7
7
 
8
- ROOT = Pathname(__FILE__).dirname.expand_path
9
- JRUBY = RUBY_PLATFORM =~ /java/
10
- WINDOWS = Gem.win_platform?
11
- SUDO = WINDOWS ? '' : ('sudo' unless ENV['SUDOLESS'])
8
+ require ROOT + 'lib/data_objects/version'
12
9
 
13
- Dir['tasks/*.rake'].sort.each { |f| import f }
10
+ JRUBY = RUBY_PLATFORM =~ /java/
11
+ IRONRUBY = defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ironruby'
12
+ WINDOWS = Gem.win_platform? || (JRUBY && ENV_JAVA['os.name'] =~ /windows/i)
13
+ SUDO = WINDOWS ? '' : ('sudo' unless ENV['SUDOLESS'])
14
14
 
15
15
  CLEAN.include(%w[ pkg/ **/*.rbc ])
16
+
17
+ begin
18
+ gem 'jeweler', '~> 1.4'
19
+ require 'jeweler'
20
+
21
+ Jeweler::Tasks.new do |gem|
22
+ gem.name = 'data_objects'
23
+ gem.version = DataObjects::VERSION
24
+ gem.summary = 'DataObjects basic API and shared driver specifications'
25
+ gem.description = 'Provide a standard and simplified API for communicating with RDBMS from Ruby'
26
+ gem.platform = Gem::Platform::RUBY
27
+ gem.files = FileList["lib/**/*.rb", "spec/**/*.rb", "tasks/**/*.rake",
28
+ "LICENSE", "Rakefile", "*.{markdown,rdoc,txt,yml}"]
29
+ gem.test_files = FileList['spec/**/*.rb']
30
+
31
+ gem.add_dependency 'addressable', '~>2.1'
32
+
33
+ gem.add_development_dependency 'bacon', '~>1.1'
34
+ gem.add_development_dependency 'mocha', '~>0.9'
35
+ gem.add_development_dependency 'yard', '~>0.5'
36
+
37
+ gem.rubyforge_project = 'dorb'
38
+
39
+ gem.authors = ['Dirkjan Bussink']
40
+ gem.email = 'd.bussink@gmail.com'
41
+ gem.homepage = 'http://github.com/datamapper/do'
42
+ end
43
+
44
+ Jeweler::GemcutterTasks.new
45
+
46
+ FileList['tasks/**/*.rake'].each { |task| import task }
47
+ rescue LoadError
48
+ puts 'Jeweler (or a dependency) not available. Install it with: gem install jeweler'
49
+ end
data/lib/data_objects.rb CHANGED
@@ -1,7 +1,8 @@
1
- require 'extlib'
2
-
3
1
  require 'data_objects/version'
2
+ require 'data_objects/utilities'
4
3
  require 'data_objects/logger'
4
+ require 'data_objects/byte_array'
5
+ require 'data_objects/pooling'
5
6
  require 'data_objects/connection'
6
7
  require 'data_objects/uri'
7
8
  require 'data_objects/transaction'
@@ -0,0 +1,6 @@
1
+ # This class has exists to represent binary data. This is mainly
2
+ # used by DataObjects. Binary data sometimes needs to be quoted differently
3
+ # than regular string data (even if the string is just plain ASCII).
4
+ module Extlib
5
+ class ByteArray < ::String; end
6
+ end
@@ -15,16 +15,18 @@ module DataObjects
15
15
  case uri.scheme.to_sym
16
16
  when :java
17
17
  warn 'JNDI URLs (connection strings) are only for use with JRuby' unless RUBY_PLATFORM =~ /java/
18
- # TODO: handle jndi connection strings
18
+ driver_name = uri.query.delete("scheme")
19
+ conn_uri = uri.to_s.gsub(/\?$/, '')
19
20
  when :jdbc
20
21
  warn 'JDBC URLs (connection strings) are only for use with JRuby' unless RUBY_PLATFORM =~ /java/
21
22
 
22
- driver_name = if uri.path.split(':').first == 'sqlite'
23
+ path = uri.path.sub(/jdbc:/, '')
24
+ driver_name = if path.split(':').first == 'sqlite'
23
25
  'sqlite3'
24
- elsif uri.path.split(':').first == 'postgresql'
26
+ elsif path.split(':').first == 'postgresql'
25
27
  'postgres'
26
28
  else
27
- uri.path.split(':').first
29
+ path.split(':').first
28
30
  end
29
31
 
30
32
  conn_uri = uri_s # NOTE: for now, do not reformat this JDBC connection
@@ -45,11 +47,24 @@ module DataObjects
45
47
  driver_name.capitalize
46
48
  end
47
49
 
48
- DataObjects.const_get(driver_class)::Connection.new(conn_uri)
50
+ clazz = DataObjects.const_get(driver_class)::Connection
51
+ unless clazz.method_defined? :close
52
+ if (uri.scheme.to_sym == :java)
53
+ clazz.class_eval do
54
+ alias close dispose
55
+ end
56
+ else
57
+ clazz.class_eval do
58
+ include Pooling
59
+ alias close release
60
+ end
61
+ end
62
+ end
63
+ clazz.new(conn_uri)
49
64
  end
50
65
 
51
66
  # Ensure that all Connection subclasses handle pooling and logging uniformly.
52
- # See also Extlib::Pooling and DataObjects::Logger
67
+ # See also DataObjects::Pooling and DataObjects::Logger
53
68
  def self.inherited(target)
54
69
  target.class_eval do
55
70
 
@@ -60,10 +75,7 @@ module DataObjects
60
75
  instance
61
76
  end
62
77
 
63
- include Extlib::Pooling
64
78
  include Quoting
65
-
66
- alias close release
67
79
  end
68
80
 
69
81
  if driver_module_name = target.name.split('::')[-2]
@@ -51,16 +51,16 @@ module DataObjects
51
51
  # The name of the log file
52
52
  attr_reader :log
53
53
 
54
- # @note
55
- # Ruby (standard) logger levels:
56
- # off: absolutely nothing
57
- # fatal: an unhandleable error that results in a program crash
58
- # error: a handleable error condition
59
- # warn: a warning
60
- # info: generic (useful) information about system operation
61
- # debug: low-level information for developers
62
54
  #
63
- # DataObjects::Logger::LEVELS[:off, :fatal, :error, :warn, :info, :debug]
55
+ # Ruby (standard) logger levels:
56
+ # off: absolutely nothing
57
+ # fatal: an unhandleable error that results in a program crash
58
+ # error: a handleable error condition
59
+ # warn: a warning
60
+ # info: generic (useful) information about system operation
61
+ # debug: low-level information for developers
62
+ #
63
+ # DataObjects::Logger::LEVELS[:off, :fatal, :error, :warn, :info, :debug]
64
64
  LEVELS =
65
65
  {
66
66
  :off => 99999,
@@ -201,12 +201,12 @@ module DataObjects
201
201
 
202
202
  # Appends a string and log level to logger's buffer.
203
203
 
204
- # @note
205
- # Note that the string is discarded if the string's log level less than the
206
- # logger's log level.
207
- # @note
208
- # Note that if the logger is aio capable then the logger will use
209
- # non-blocking asynchronous writes.
204
+ #
205
+ # Note that the string is discarded if the string's log level less than the
206
+ # logger's log level.
207
+ #
208
+ # Note that if the logger is aio capable then the logger will use
209
+ # non-blocking asynchronous writes.
210
210
  #
211
211
  # @param level<Fixnum> the logging level as an integer
212
212
  # @param string<String> the message string to be logged
@@ -0,0 +1,250 @@
1
+ require 'set'
2
+ require 'thread'
3
+
4
+ module DataObjects
5
+
6
+ def self.exiting= bool
7
+ if bool && DataObjects.const_defined?('Pooling')
8
+ if DataObjects::Pooling.scavenger?
9
+ DataObjects::Pooling.scavenger.wakeup
10
+ end
11
+ end
12
+ @exiting = true
13
+ end
14
+
15
+ def self.exiting
16
+ return @exiting if defined?(@exiting)
17
+ @exiting = false
18
+ end
19
+
20
+ # ==== Notes
21
+ # Provides pooling support to class it got included in.
22
+ #
23
+ # Pooling of objects is a faster way of aquiring instances
24
+ # of objects compared to regular allocation and initialization
25
+ # because instances are keeped in memory reused.
26
+ #
27
+ # Classes that include Pooling module have re-defined new
28
+ # method that returns instances acquired from pool.
29
+ #
30
+ # Term resource is used for any type of poolable objects
31
+ # and should NOT be thought as DataMapper Resource or
32
+ # ActiveResource resource and such.
33
+ #
34
+ # In Data Objects connections are pooled so that it is
35
+ # unnecessary to allocate and initialize connection object
36
+ # each time connection is needed, like per request in a
37
+ # web application.
38
+ #
39
+ # Pool obviously has to be thread safe because state of
40
+ # object is reset when it is released.
41
+ module Pooling
42
+
43
+ def self.scavenger?
44
+ defined?(@scavenger) && !@scavenger.nil? && @scavenger.alive?
45
+ end
46
+
47
+ def self.scavenger
48
+ unless scavenger?
49
+ @scavenger = Thread.new do
50
+ running = true
51
+ while running do
52
+ # Sleep before we actually start doing anything.
53
+ # Otherwise we might clean up something we just made
54
+ sleep(scavenger_interval)
55
+
56
+ lock.synchronize do
57
+ pools.each do |pool|
58
+ # This is a useful check, but non-essential, and right now it breaks lots of stuff.
59
+ # if pool.expired?
60
+ pool.lock.synchronize do
61
+ if pool.expired?
62
+ pool.dispose
63
+ end
64
+ end
65
+ # end
66
+ end
67
+
68
+ # The pool is empty, we stop the scavenger
69
+ # It wil be restarted if new resources are added again
70
+ if pools.empty?
71
+ running = false
72
+ end
73
+ end
74
+ end # loop
75
+ end
76
+ end
77
+
78
+ @scavenger.priority = -10
79
+ @scavenger
80
+ end
81
+
82
+ def self.pools
83
+ @pools ||= Set.new
84
+ end
85
+
86
+ def self.append_pool(pool)
87
+ lock.synchronize do
88
+ pools << pool
89
+ end
90
+ DataObjects::Pooling.scavenger
91
+ end
92
+
93
+ def self.lock
94
+ @lock ||= Mutex.new
95
+ end
96
+
97
+ class InvalidResourceError < StandardError
98
+ end
99
+
100
+ def self.included(target)
101
+ target.class_eval do
102
+ class << self
103
+ alias __new new
104
+ end
105
+
106
+ @__pools = {}
107
+ @__pool_lock = Mutex.new
108
+ @__pool_wait = ConditionVariable.new
109
+
110
+ def self.__pool_lock
111
+ @__pool_lock
112
+ end
113
+
114
+ def self.__pool_wait
115
+ @__pool_wait
116
+ end
117
+
118
+ def self.new(*args)
119
+ (@__pools[args] ||= __pool_lock.synchronize { Pool.new(self.pool_size, self, args) }).new
120
+ end
121
+
122
+ def self.__pools
123
+ @__pools
124
+ end
125
+
126
+ def self.pool_size
127
+ 8
128
+ end
129
+ end
130
+ end
131
+
132
+ def release
133
+ @__pool.release(self) unless @__pool.nil?
134
+ end
135
+
136
+ def detach
137
+ @__pool.delete(self) unless @__pool.nil?
138
+ end
139
+
140
+ class Pool
141
+ attr_reader :available
142
+ attr_reader :used
143
+
144
+ def initialize(max_size, resource, args)
145
+ raise ArgumentError.new("+max_size+ should be a Fixnum but was #{max_size.inspect}") unless Fixnum === max_size
146
+ raise ArgumentError.new("+resource+ should be a Class but was #{resource.inspect}") unless Class === resource
147
+
148
+ @max_size = max_size
149
+ @resource = resource
150
+ @args = args
151
+
152
+ @available = []
153
+ @used = {}
154
+ DataObjects::Pooling.append_pool(self)
155
+ end
156
+
157
+ def lock
158
+ @resource.__pool_lock
159
+ end
160
+
161
+ def wait
162
+ @resource.__pool_wait
163
+ end
164
+
165
+ def scavenge_interval
166
+ @resource.scavenge_interval
167
+ end
168
+
169
+ def new
170
+ instance = nil
171
+ begin
172
+ lock.synchronize do
173
+ if @available.size > 0
174
+ instance = @available.pop
175
+ @used[instance.object_id] = instance
176
+ elsif @used.size < @max_size
177
+ instance = @resource.__new(*@args)
178
+ raise InvalidResourceError.new("#{@resource} constructor created a nil object") if instance.nil?
179
+ raise InvalidResourceError.new("#{instance} is already part of the pool") if @used.include? instance
180
+ instance.instance_variable_set(:@__pool, self)
181
+ instance.instance_variable_set(:@__allocated_in_pool, Time.now)
182
+ @used[instance.object_id] = instance
183
+ else
184
+ # Wait for another thread to release an instance.
185
+ # If we exhaust the pool and don't release the active instance,
186
+ # we'll wait here forever, so it's *very* important to always
187
+ # release your services and *never* exhaust the pool within
188
+ # a single thread.
189
+ wait.wait(lock)
190
+ end
191
+ end
192
+ end until instance
193
+ instance
194
+ end
195
+
196
+ def release(instance)
197
+ lock.synchronize do
198
+ instance.instance_variable_set(:@__allocated_in_pool, Time.now)
199
+ @used.delete(instance.object_id)
200
+ @available.push(instance)
201
+ wait.signal
202
+ end
203
+ nil
204
+ end
205
+
206
+ def delete(instance)
207
+ lock.synchronize do
208
+ instance.instance_variable_set(:@__pool, nil)
209
+ @used.delete(instance.object_id)
210
+ wait.signal
211
+ end
212
+ nil
213
+ end
214
+
215
+ def size
216
+ @used.size + @available.size
217
+ end
218
+ alias length size
219
+
220
+ def inspect
221
+ "#<DataObjects::Pooling::Pool<#{@resource.name}> available=#{@available.size} used=#{@used.size} size=#{@max_size}>"
222
+ end
223
+
224
+ def flush!
225
+ @available.pop.dispose until @available.empty?
226
+ end
227
+
228
+ def dispose
229
+ flush!
230
+ @resource.__pools.delete(@args)
231
+ !DataObjects::Pooling.pools.delete?(self).nil?
232
+ end
233
+
234
+ def expired?
235
+ @available.each do |instance|
236
+ if DataObjects.exiting || instance.instance_variable_get(:@__allocated_in_pool) + DataObjects::Pooling.scavenger_interval <= (Time.now + 0.02)
237
+ instance.dispose
238
+ @available.delete(instance)
239
+ end
240
+ end
241
+ size == 0
242
+ end
243
+
244
+ end
245
+
246
+ def self.scavenger_interval
247
+ 60
248
+ end
249
+ end # module Pooling
250
+ end # module DataObjects