data_objects 0.9.11 → 0.9.12

Sign up to get free protection for your applications and to get access to all the features.
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