data_objects 0.10.0 → 0.10.1

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.
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