data_objects 0.9.11 → 0.9.12

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 (45) hide show
  1. data/Manifest.txt +19 -1
  2. data/Rakefile +6 -80
  3. data/lib/data_objects.rb +1 -6
  4. data/lib/data_objects/command.rb +51 -1
  5. data/lib/data_objects/connection.rb +13 -2
  6. data/lib/data_objects/logger.rb +40 -32
  7. data/lib/data_objects/quoting.rb +28 -32
  8. data/lib/data_objects/reader.rb +6 -5
  9. data/lib/data_objects/result.rb +7 -1
  10. data/lib/data_objects/spec/command_spec.rb +191 -0
  11. data/lib/data_objects/spec/connection_spec.rb +106 -0
  12. data/lib/data_objects/spec/encoding_spec.rb +31 -0
  13. data/lib/data_objects/spec/quoting_spec.rb +0 -0
  14. data/lib/data_objects/spec/reader_spec.rb +156 -0
  15. data/lib/data_objects/spec/result_spec.rb +58 -0
  16. data/lib/data_objects/spec/typecast/array_spec.rb +36 -0
  17. data/lib/data_objects/spec/typecast/bigdecimal_spec.rb +107 -0
  18. data/lib/data_objects/spec/typecast/boolean_spec.rb +107 -0
  19. data/lib/data_objects/spec/typecast/byte_array_spec.rb +86 -0
  20. data/lib/data_objects/spec/typecast/class_spec.rb +63 -0
  21. data/lib/data_objects/spec/typecast/date_spec.rb +108 -0
  22. data/lib/data_objects/spec/typecast/datetime_spec.rb +110 -0
  23. data/lib/data_objects/spec/typecast/float_spec.rb +111 -0
  24. data/lib/data_objects/spec/typecast/integer_spec.rb +86 -0
  25. data/lib/data_objects/spec/typecast/ipaddr_spec.rb +0 -0
  26. data/lib/data_objects/spec/typecast/nil_spec.rb +116 -0
  27. data/lib/data_objects/spec/typecast/range_spec.rb +36 -0
  28. data/lib/data_objects/spec/typecast/string_spec.rb +86 -0
  29. data/lib/data_objects/spec/typecast/time_spec.rb +64 -0
  30. data/lib/data_objects/transaction.rb +20 -13
  31. data/lib/data_objects/uri.rb +24 -2
  32. data/lib/data_objects/version.rb +2 -1
  33. data/spec/command_spec.rb +1 -17
  34. data/spec/connection_spec.rb +1 -23
  35. data/spec/lib/pending_helpers.rb +11 -0
  36. data/spec/lib/rspec_immediate_feedback_formatter.rb +53 -0
  37. data/spec/result_spec.rb +0 -3
  38. data/tasks/gem.rake +49 -0
  39. data/tasks/install.rake +13 -0
  40. data/tasks/release.rake +74 -0
  41. data/tasks/spec.rake +18 -0
  42. metadata +51 -30
  43. data/.gitignore +0 -2
  44. data/spec/dataobjects_spec.rb +0 -1
  45. data/spec/spec.opts +0 -2
data/Manifest.txt CHANGED
@@ -11,13 +11,31 @@ lib/data_objects/logger.rb
11
11
  lib/data_objects/quoting.rb
12
12
  lib/data_objects/reader.rb
13
13
  lib/data_objects/result.rb
14
+ lib/data_objects/spec/command_spec.rb
15
+ lib/data_objects/spec/connection_spec.rb
16
+ lib/data_objects/spec/encoding_spec.rb
17
+ lib/data_objects/spec/quoting_spec.rb
18
+ lib/data_objects/spec/reader_spec.rb
19
+ lib/data_objects/spec/result_spec.rb
20
+ lib/data_objects/spec/typecast/bigdecimal_spec.rb
21
+ lib/data_objects/spec/typecast/boolean_spec.rb
22
+ lib/data_objects/spec/typecast/byte_array_spec.rb
23
+ lib/data_objects/spec/typecast/class_spec.rb
24
+ lib/data_objects/spec/typecast/date_spec.rb
25
+ lib/data_objects/spec/typecast/datetime_spec.rb
26
+ lib/data_objects/spec/typecast/float_spec.rb
27
+ lib/data_objects/spec/typecast/integer_spec.rb
28
+ lib/data_objects/spec/typecast/ipaddr_spec.rb
29
+ lib/data_objects/spec/typecast/nil_spec.rb
30
+ lib/data_objects/spec/typecast/string_spec.rb
31
+ lib/data_objects/spec/typecast/time_spec.rb
14
32
  lib/data_objects/transaction.rb
15
33
  lib/data_objects/uri.rb
16
34
  lib/data_objects/version.rb
17
35
  spec/command_spec.rb
18
36
  spec/connection_spec.rb
19
- spec/dataobjects_spec.rb
20
37
  spec/do_mock.rb
38
+ spec/lib/pending_helpers.rb
21
39
  spec/reader_spec.rb
22
40
  spec/result_spec.rb
23
41
  spec/spec.opts
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
- require 'pathname'
2
1
  require 'rubygems'
3
- require 'spec/rake/spectask'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+
5
+ require 'pathname'
4
6
  require 'lib/data_objects/version'
5
7
 
6
8
  ROOT = Pathname(__FILE__).dirname.expand_path
@@ -8,82 +10,6 @@ JRUBY = RUBY_PLATFORM =~ /java/
8
10
  WINDOWS = Gem.win_platform?
9
11
  SUDO = (WINDOWS || JRUBY) ? '' : ('sudo' unless ENV['SUDOLESS'])
10
12
 
11
- AUTHOR = "Dirkjan Bussink"
12
- EMAIL = "d.bussink@gmail.com"
13
- GEM_NAME = "data_objects"
14
- GEM_VERSION = DataObjects::VERSION
15
- GEM_DEPENDENCIES = ["addressable", "~>2.0"], ["extlib", "~>0.9.9"]
16
- GEM_CLEAN = "{coverage,doc,log}/", "profile_results.*", "**/.*.sw?", "*.gem", ".config", "**/.DS_Store"
17
- GEM_EXTRAS = {}
18
-
19
- PROJECT_NAME = "dorb"
20
- PROJECT_URL = "http://rubyforge.org/projects/dorb"
21
- PROJECT_DESCRIPTION = PROJECT_SUMMARY = "The Core DataObjects class"
22
-
23
-
24
- # RCov is run by default, except on the JRuby platform, or if NO_RCOV env is true
25
- RUN_RCOV = JRUBY ? false : (ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true)
26
-
27
- if (tasks_dir = ROOT.parent + 'tasks').directory?
28
- require tasks_dir + 'hoe'
29
- end
30
-
31
- def sudo_gem(cmd)
32
- sh "#{SUDO} #{RUBY} -S gem #{cmd}", :verbose => false
33
- end
34
-
35
- # Installation
36
-
37
- desc "Install #{GEM_NAME} #{GEM_VERSION}"
38
- task :install => [ :package ] do
39
- sudo_gem "install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources"
40
- end
41
-
42
- desc "Uninstall #{GEM_NAME} #{GEM_VERSION}"
43
- task :uninstall => [ :clobber ] do
44
- sudo_gem "uninstall #{GEM_NAME} -v#{GEM_VERSION} -I -x"
45
- end
46
-
47
- # Specs
48
-
49
- Spec::Rake::SpecTask.new(:spec) do |t|
50
- t.spec_opts << '--format' << 'specdoc' << '--colour'
51
- t.spec_opts << '--loadby' << 'random'
52
- t.spec_files = Pathname.glob(ENV['FILES'] || 'spec/**/*_spec.rb').map { |f| f.to_s }
53
-
54
- begin
55
- t.rcov = RUN_RCOV
56
- t.rcov_opts << '--exclude' << 'spec'
57
- t.rcov_opts << '--text-summary'
58
- t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
59
- rescue Exception
60
- # rcov not installed
61
- end
62
- end
63
-
64
- namespace :ci do
65
-
66
- task :prepare do
67
- rm_rf ROOT + "ci"
68
- mkdir_p ROOT + "ci"
69
- mkdir_p ROOT + "ci/doc"
70
- mkdir_p ROOT + "ci/cyclomatic"
71
- mkdir_p ROOT + "ci/token"
72
- end
73
-
74
- task :publish do
75
- out = ENV['CC_BUILD_ARTIFACTS'] || "out"
76
- mkdir_p out unless File.directory? out
77
-
78
- mv "ci/rspec_report.html", "#{out}/rspec_report.html"
79
- mv "ci/coverage", "#{out}/coverage"
80
- end
81
-
82
- task :spec => :prepare do
83
- Rake::Task[:spec].invoke
84
- mv ROOT + "coverage", ROOT + "ci/coverage"
85
- end
86
-
87
- end
13
+ Dir['tasks/*.rake'].each { |f| import f }
88
14
 
89
- task :ci => ["ci:spec"]
15
+ CLEAN.include(%w[ pkg/ **/*.rbc ])
data/lib/data_objects.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'rubygems'
2
2
 
3
- gem 'extlib', '~>0.9.8'
3
+ gem 'extlib', '~>0.9.11'
4
4
  require 'extlib'
5
5
 
6
6
  require File.expand_path(File.join(File.dirname(__FILE__), 'data_objects', 'version'))
@@ -16,9 +16,4 @@ require File.expand_path(File.join(File.dirname(__FILE__), 'data_objects', 'quot
16
16
 
17
17
  module DataObjects
18
18
  class LengthMismatchError < StandardError; end
19
-
20
- def self.root
21
- @root ||= Pathname(__FILE__).dirname.parent.expand_path
22
- end
23
-
24
19
  end
@@ -1,30 +1,80 @@
1
1
  module DataObjects
2
+ # Abstract base class for adapter-specific Command subclasses
2
3
  class Command
3
4
 
5
+ # The Connection on which the command will be run
4
6
  attr_reader :connection
5
7
 
6
- # initialize creates a new Command object
8
+ # Create a new Command object on the specified connection
7
9
  def initialize(connection, text)
8
10
  raise ArgumentError.new("+connection+ must be a DataObjects::Connection") unless DataObjects::Connection === connection
9
11
  @connection, @text = connection, text
10
12
  end
11
13
 
14
+ # Execute this command and return no dataset
12
15
  def execute_non_query(*args)
13
16
  raise NotImplementedError.new
14
17
  end
15
18
 
19
+ # Execute this command and return a DataObjects::Reader for a dataset
16
20
  def execute_reader(*args)
17
21
  raise NotImplementedError.new
18
22
  end
19
23
 
24
+ # Assign an array of types for the columns to be returned by this command
20
25
  def set_types(column_types)
21
26
  raise NotImplementedError.new
22
27
  end
23
28
 
29
+ # Display the command text
24
30
  def to_s
25
31
  @text
26
32
  end
27
33
 
34
+ private
35
+
36
+ # Escape a string of SQL with a set of arguments.
37
+ # The first argument is assumed to be the SQL to escape,
38
+ # the remaining arguments (if any) are assumed to be
39
+ # values to escape and interpolate.
40
+ #
41
+ # ==== Examples
42
+ # escape_sql("SELECT * FROM zoos")
43
+ # # => "SELECT * FROM zoos"
44
+ #
45
+ # escape_sql("SELECT * FROM zoos WHERE name = ?", "Dallas")
46
+ # # => "SELECT * FROM zoos WHERE name = `Dallas`"
47
+ #
48
+ # escape_sql("SELECT * FROM zoos WHERE name = ? AND acreage > ?", "Dallas", 40)
49
+ # # => "SELECT * FROM zoos WHERE name = `Dallas` AND acreage > 40"
50
+ #
51
+ # ==== Warning
52
+ # This method is meant mostly for adapters that don't support
53
+ # bind-parameters.
54
+ def escape_sql(args)
55
+ sql = @text.dup
56
+ vars = args.dup
57
+
58
+ replacements = 0
59
+ mismatch = false
60
+
61
+ sql.gsub!(/\?/) do |x|
62
+ replacements += 1
63
+ if vars.empty?
64
+ mismatch = true
65
+ else
66
+ var = vars.shift
67
+ connection.quote_value(var)
68
+ end
69
+ end
70
+
71
+ if !vars.empty? || mismatch
72
+ raise ArgumentError, "Binding mismatch: #{args.size} for #{replacements}"
73
+ else
74
+ sql
75
+ end
76
+ end
77
+
28
78
  end
29
79
 
30
80
  end
@@ -6,8 +6,11 @@ rescue LoadError
6
6
  end
7
7
 
8
8
  module DataObjects
9
+ # An abstract connection to a DataObjects resource. The physical connection may be broken and re-established from time to time.
9
10
  class Connection
10
11
 
12
+ # Make a connection to the database using the DataObjects::URI given.
13
+ # Note that the physical connection may be delayed until the first command is issued, so success here doesn't necessarily mean you can connect.
11
14
  def self.new(uri_s)
12
15
  uri = DataObjects::URI::parse(uri_s)
13
16
 
@@ -40,9 +43,12 @@ module DataObjects
40
43
  DataObjects.const_get(driver_name.capitalize)::Connection.new(conn_uri)
41
44
  end
42
45
 
46
+ # Ensure that all Connection subclasses handle pooling and logging uniformly.
47
+ # See also Extlib::Pooling and DataObjects::Logger
43
48
  def self.inherited(target)
44
49
  target.class_eval do
45
50
 
51
+ # Allocate a Connection object from the pool, creating one if necessary. This method is active in Connection subclasses only.
46
52
  def self.new(*args)
47
53
  instance = allocate
48
54
  instance.send(:initialize, *args)
@@ -50,6 +56,8 @@ module DataObjects
50
56
  end
51
57
 
52
58
  include Extlib::Pooling
59
+ include Quoting
60
+
53
61
  alias close release
54
62
  end
55
63
 
@@ -72,18 +80,21 @@ module DataObjects
72
80
  #####################################################
73
81
  # Standard API Definition
74
82
  #####################################################
83
+
84
+ # Show the URI for this connection
75
85
  def to_s
76
86
  @uri.to_s
77
87
  end
78
88
 
79
- def initialize(uri)
89
+ def initialize(uri) #:nodoc:
80
90
  raise NotImplementedError.new
81
91
  end
82
92
 
83
- def dispose
93
+ def dispose #:nodoc:
84
94
  raise NotImplementedError.new
85
95
  end
86
96
 
97
+ # Create a Command object of the right subclass using the given text
87
98
  def create_command(text)
88
99
  concrete_command.new(self, text)
89
100
  end
@@ -1,47 +1,54 @@
1
1
  require "time" # httpdate
2
- # ==== Public DataObjects Logger API
3
- #
4
- # Logger taken from Merb :)
5
- #
6
- # To replace an existing logger with a new one:
7
- # DataObjects::Logger.set_log(log{String, IO},level{Symbol, String})
8
- #
9
- # Available logging levels are
10
- # DataObjects::Logger::{ Fatal, Error, Warn, Info, Debug }
11
- #
12
- # Logging via:
13
- # DataObjects.logger.fatal(message<String>)
14
- # DataObjects.logger.error(message<String>)
15
- # DataObjects.logger.warn(message<String>)
16
- # DataObjects.logger.info(message<String>)
17
- # DataObjects.logger.debug(message<String>)
18
- #
19
- # Flush the buffer to
20
- # DataObjects.logger.flush
21
- #
22
- # Remove the current log object
23
- # DataObjects.logger.close
24
- #
25
- # ==== Private DataObjects Logger API
26
- #
27
- # To initialize the logger you create a new object, proxies to set_log.
28
- # DataObjects::Logger.new(log{String, IO},level{Symbol, String})
29
- #
30
- # Logger will not create the file until something is actually logged
31
- # This avoids file creation on DataObjects init when it creates the
32
- # default logger.
2
+
33
3
  module DataObjects
34
4
 
35
- class << self #:nodoc:
5
+ class << self
6
+ # The global logger for DataObjects
36
7
  attr_accessor :logger
37
8
  end
38
9
 
10
+ # ==== Public DataObjects Logger API
11
+ #
12
+ # Logger taken from Merb :)
13
+ #
14
+ # To replace an existing logger with a new one:
15
+ # DataObjects::Logger.set_log(log{String, IO},level{Symbol, String})
16
+ #
17
+ # Available logging levels are
18
+ # DataObjects::Logger::{ Fatal, Error, Warn, Info, Debug }
19
+ #
20
+ # Logging via:
21
+ # DataObjects.logger.fatal(message<String>)
22
+ # DataObjects.logger.error(message<String>)
23
+ # DataObjects.logger.warn(message<String>)
24
+ # DataObjects.logger.info(message<String>)
25
+ # DataObjects.logger.debug(message<String>)
26
+ #
27
+ # Flush the buffer to
28
+ # DataObjects.logger.flush
29
+ #
30
+ # Remove the current log object
31
+ # DataObjects.logger.close
32
+ #
33
+ # ==== Private DataObjects Logger API
34
+ #
35
+ # To initialize the logger you create a new object, proxies to set_log.
36
+ # DataObjects::Logger.new(log{String, IO},level{Symbol, String})
37
+ #
38
+ # Logger will not create the file until something is actually logged
39
+ # This avoids file creation on DataObjects init when it creates the
40
+ # default logger.
39
41
  class Logger
40
42
 
43
+ # Use asynchronous I/O?
41
44
  attr_accessor :aio
45
+ # delimiter to use between message sections
42
46
  attr_accessor :delimiter
47
+ # a symbol representing the log level from {:off, :fatal, :error, :warn, :info, :debug}
43
48
  attr_reader :level
49
+ # Direct access to the buffer
44
50
  attr_reader :buffer
51
+ # The name of the log file
45
52
  attr_reader :log
46
53
 
47
54
  # @note
@@ -64,6 +71,7 @@ module DataObjects
64
71
  :debug => 0
65
72
  }
66
73
 
74
+ # Set the log level (use the level symbols as documented)
67
75
  def level=(new_level)
68
76
  @level = LEVELS[new_level.to_sym]
69
77
  reset_methods(:close)
@@ -1,43 +1,14 @@
1
1
  module DataObjects
2
2
 
3
3
  module Quoting
4
- # Escape a string of SQL with a set of arguments.
5
- # The first argument is assumed to be the SQL to escape,
6
- # the remaining arguments (if any) are assumed to be
7
- # values to escape and interpolate.
8
- #
9
- # ==== Examples
10
- # escape_sql("SELECT * FROM zoos")
11
- # # => "SELECT * FROM zoos"
12
- #
13
- # escape_sql("SELECT * FROM zoos WHERE name = ?", "Dallas")
14
- # # => "SELECT * FROM zoos WHERE name = `Dallas`"
15
- #
16
- # escape_sql("SELECT * FROM zoos WHERE name = ? AND acreage > ?", "Dallas", 40)
17
- # # => "SELECT * FROM zoos WHERE name = `Dallas` AND acreage > 40"
18
- #
19
- # ==== Warning
20
- # This method is meant mostly for adapters that don't support
21
- # bind-parameters.
22
- def escape_sql(args)
23
- sql = @text.dup
24
-
25
- unless args.empty?
26
- sql.gsub!(/\?/) do |x|
27
- quote_value(args.shift)
28
- end
29
- end
30
-
31
- sql
32
- end
33
4
 
5
+ # Quote a value of any of the recognised data types
34
6
  def quote_value(value)
35
7
  return 'NULL' if value.nil?
36
8
 
37
9
  case value
38
10
  when Numeric then quote_numeric(value)
39
11
  when String then quote_string(value)
40
- when Class then quote_class(value)
41
12
  when Time then quote_time(value)
42
13
  when DateTime then quote_datetime(value)
43
14
  when Date then quote_date(value)
@@ -46,58 +17,83 @@ module DataObjects
46
17
  when Range then quote_range(value)
47
18
  when Symbol then quote_symbol(value)
48
19
  when Regexp then quote_regexp(value)
20
+ when ::Extlib::ByteArray then quote_byte_array(value)
21
+ when Class then quote_class(value)
49
22
  else
50
23
  if value.respond_to?(:to_sql)
51
24
  value.to_sql
52
25
  else
53
- raise "Don't know how to quote #{value.inspect}"
26
+ raise "Don't know how to quote #{value.class} objects (#{value.inspect})"
54
27
  end
55
28
  end
56
29
  end
57
30
 
31
+ # Convert the Symbol to a String and quote that
58
32
  def quote_symbol(value)
59
33
  quote_string(value.to_s)
60
34
  end
61
35
 
36
+ # Convert the Numeric to a String and quote that
62
37
  def quote_numeric(value)
63
38
  value.to_s
64
39
  end
65
40
 
41
+ # Quote a String for SQL by doubling any embedded single-quote characters
66
42
  def quote_string(value)
67
43
  "'#{value.gsub("'", "''")}'"
68
44
  end
69
45
 
46
+ # Quote a class by quoting its name
70
47
  def quote_class(value)
71
48
  quote_string(value.name)
72
49
  end
73
50
 
51
+ # Convert a Time to standard YMDHMS format (with microseconds if necessary)
74
52
  def quote_time(value)
75
- "'#{value.strftime('%Y-%m-%d %H:%M:%S')}" + (value.usec > 0 ? ".#{value.usec.to_s.rjust(6, '0')}'" : "'")
53
+ offset = value.utc_offset
54
+ if offset >= 0
55
+ offset_string = "+#{sprintf("%02d", offset / 3600)}:#{sprintf("%02d", (offset % 3600) / 60)}"
56
+ elsif offset < 0
57
+ offset_string = "-#{sprintf("%02d", -offset / 3600)}:#{sprintf("%02d", (-offset % 3600) / 60)}"
58
+ end
59
+ "'#{value.strftime('%Y-%m-%dT%H:%M:%S')}" << (value.usec > 0 ? ".#{value.usec.to_s.rjust(6, '0')}" : "") << offset_string << "'"
76
60
  end
77
61
 
62
+ # Quote a DateTime by relying on it's own to_s conversion
78
63
  def quote_datetime(value)
79
64
  "'#{value.dup}'"
80
65
  end
81
66
 
67
+ # Convert a Date to standard YMD format
82
68
  def quote_date(value)
83
69
  "'#{value.strftime("%Y-%m-%d")}'"
84
70
  end
85
71
 
72
+ # Quote true, false as the strings TRUE, FALSE
86
73
  def quote_boolean(value)
87
74
  value.to_s.upcase
88
75
  end
89
76
 
77
+ # Quote an array as a list of quoted values
90
78
  def quote_array(value)
91
79
  "(#{value.map { |entry| quote_value(entry) }.join(', ')})"
92
80
  end
93
81
 
82
+ # Quote a range by joining the quoted end-point values with AND.
83
+ # It's not clear whether or when this is a useful or correct thing to do.
94
84
  def quote_range(value)
95
85
  "#{quote_value(value.first)} AND #{quote_value(value.last)}"
96
86
  end
97
87
 
88
+ # Quote a Regex using its string value. Note that there's no attempt to make a valid SQL "LIKE" string.
98
89
  def quote_regexp(value)
99
90
  quote_string(value.source)
100
91
  end
92
+
93
+ def quote_byte_array(value)
94
+ quote_string(value.source)
95
+ end
96
+
101
97
  end
102
98
 
103
99
  end