rdo 0.0.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.
data/lib/rdo/driver.rb ADDED
@@ -0,0 +1,120 @@
1
+ ##
2
+ # RDO: Ruby Data Objects.
3
+ # Copyright © 2012 Chris Corbyn.
4
+ #
5
+ # See LICENSE file for details.
6
+ ##
7
+
8
+ module RDO
9
+ # Abstract class that is subclassed by each specific driver.
10
+ #
11
+ # Driver developers should be able to subclass this, then write specs and
12
+ # override the behaviours they need to change.
13
+ #
14
+ # Ideally all instance method will be overridden by really robust drivers.
15
+ class Driver
16
+ # Options passed to initialize.
17
+ attr_reader :options
18
+
19
+ # Initialize the Driver with the given options.
20
+ #
21
+ # Drivers SHOULD call super if overriding.
22
+ #
23
+ # @param [Hash] options
24
+ # all options passed to the Driver, as a Symbol-keyed Hash.
25
+ def initialize(options = {})
26
+ @options = options.dup
27
+ end
28
+
29
+ # Open a connection to the RDBMS, if it is not already open.
30
+ #
31
+ # If it is not possible to open a connection, an RDO::Exception is raised.
32
+ #
33
+ # This is a no-op: subclasses MUST override this.
34
+ #
35
+ # @return [Boolean]
36
+ # true if a connection was opened or was already open, false if not.
37
+ def open
38
+ false
39
+ end
40
+
41
+ # Check if the connection is currently open or not.
42
+ #
43
+ # Drivers MUST override this.
44
+ #
45
+ # @return [Boolean]
46
+ # true if the connection is open, false otherwise
47
+ def open?
48
+ false
49
+ end
50
+
51
+ # Close the current connection, if it is open.
52
+ #
53
+ # Drivers MUST override this.
54
+ #
55
+ # @return [Boolean]
56
+ # true if the connection was closed or was already closed, false if not
57
+ def close
58
+ false
59
+ end
60
+
61
+ # Create a prepared statement to later be executed with some inputs.
62
+ #
63
+ # Not all drivers support this natively, but it is emulated by default.
64
+ #
65
+ # This is a default implementation for emulated prepared statements:
66
+ # drivers SHOULD override it if possible.
67
+ #
68
+ # @param [String] statement
69
+ # a string of SQL or DDL, with '?' placeholders for bind parameters
70
+ #
71
+ # @return [Statement]
72
+ # a prepared statement to later be executed
73
+ def prepare(statement)
74
+ Statement.new(emulated_statement_executor(statement))
75
+ end
76
+
77
+ # Execute a statement against the RDBMS.
78
+ #
79
+ # The statement can either by a read, or a write operation.
80
+ # Placeholders marked by '?' may be interpolated in the statement, so
81
+ # that bind parameters can be safely provided.
82
+ #
83
+ # Where the RDBMS natively support bind parameters, this functionality is
84
+ # used; otherwise, the values are quoted using #quote.
85
+ #
86
+ # Drivers MUST override this.
87
+ #
88
+ # @param [String] statement
89
+ # a string of SQL or DDL to be executed
90
+ #
91
+ # @param [Array] *bind_values
92
+ # a list of parameters to substitute in the statement
93
+ #
94
+ # @return [Result]
95
+ # the result of the query
96
+ def execute(statement, *bind_values)
97
+ Result.new([])
98
+ end
99
+
100
+ # Escape a given value for safe interpolation into a statement.
101
+ #
102
+ # This should be avoided where the driver natively supports bind parameters.
103
+ #
104
+ # Drivers MUST override this with a RDBMS-specific solution.
105
+ #
106
+ # @param [Object] value
107
+ # the value to quote
108
+ #
109
+ # @return [String]
110
+ # a safely escaped value
111
+ def quote(value)
112
+ end
113
+
114
+ private
115
+
116
+ def emulated_statement_executor(stmt)
117
+ EmulatedStatementExecutor.new(self, stmt)
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,36 @@
1
+ ##
2
+ # RDO: Ruby Data Objects.
3
+ # Copyright © 2012 Chris Corbyn.
4
+ #
5
+ # See LICENSE file for details.
6
+ ##
7
+
8
+ module RDO
9
+ # This StatementExecutor is used as a fallback for prepared statements.
10
+ #
11
+ # If a DBMS driver does not implement prepared statements, this is used instead.
12
+ # The #execute method simply delegates back to the connection object.
13
+ class EmulatedStatementExecutor
14
+ attr_reader :command
15
+
16
+ # Initialize a new statement executor for the given connection & command.
17
+ #
18
+ # @param [RDO::Connection] connection
19
+ # the connection on which #prepare was invoked
20
+ #
21
+ # @param [String] command
22
+ # a string of SQL/DDL to execute
23
+ def initialize(connection, command)
24
+ @connection = connection
25
+ @command = command
26
+ end
27
+
28
+ # Execute the command using the given bind values.
29
+ #
30
+ # @param [Object...] args
31
+ # bind parameters to use in place of '?'
32
+ def execute(*args)
33
+ @connection.execute(command, *args)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,11 @@
1
+ ##
2
+ # RDO: Ruby Data Objects.
3
+ # Copyright © 2012 Chris Corbyn.
4
+ #
5
+ # See LICENSE file for details.
6
+ ##
7
+
8
+ module RDO
9
+ # This is the only type of Exception raised by RDO.
10
+ class Exception < RuntimeError; end
11
+ end
data/lib/rdo/result.rb ADDED
@@ -0,0 +1,95 @@
1
+ ##
2
+ # RDO: Ruby Data Objects.
3
+ # Copyright © 2012 Chris Corbyn.
4
+ #
5
+ # See LICENSE file for details.
6
+ ##
7
+
8
+ module RDO
9
+ # The standard Result class returned by Connection#execute.
10
+ #
11
+ # Both read and write queries receive results in this format.
12
+ class Result
13
+ include Enumerable
14
+
15
+ # Initialize a new Result.
16
+ #
17
+ # @param [Enumerable] tuples
18
+ # a list of tuples, provided by the driver
19
+ #
20
+ # @param [Hash] info
21
+ # information about the result, including:
22
+ # - count
23
+ # - rows_affected
24
+ # - insert_id
25
+ # - execution_time
26
+ def initialize(tuples, info = {})
27
+ @info = info.dup
28
+ @tuples = tuples
29
+ end
30
+
31
+ # Get raw result info provided by the driver.
32
+ #
33
+ # @return [Hash]
34
+ # aribitrary information provided about the result
35
+ def info
36
+ @info
37
+ end
38
+
39
+ # Return the inserted row ID.
40
+ #
41
+ # For some drivers this requires that a RETURNING clause by used in SQL.
42
+ # It may be more desirable to simply check the rows in the result.
43
+ #
44
+ # @return [Object]
45
+ # the ID of the record just inserted, or nil
46
+ def insert_id
47
+ if info.key?(:insert_id)
48
+ info[:insert_id]
49
+ else
50
+ first_value
51
+ end
52
+ end
53
+
54
+ # If only one column and one row is expected in the result, fetch it.
55
+ #
56
+ # If no rows were returned, this method returns nil.
57
+ #
58
+ # @return [Object]
59
+ # a single value at the first column in the first row of the result
60
+ def first_value
61
+ if row = first
62
+ row.values.first
63
+ end
64
+ end
65
+
66
+ # Return the number of rows affected by the query.
67
+ #
68
+ # @return [Fixnum]
69
+ # the number of rows affected
70
+ def affected_rows
71
+ info[:affected_rows].to_i
72
+ end
73
+
74
+ # Get the number of rows in the result.
75
+ #
76
+ # Many drivers provide the count, otherwise it will be computed at runtime.
77
+ #
78
+ # @return Fixnum
79
+ # the number of rows in the Result
80
+ def count
81
+ if info[:count].nil? || block_given?
82
+ super
83
+ else
84
+ info[:count]
85
+ end
86
+ end
87
+
88
+ # Iterate over all rows returned by the connection.
89
+ #
90
+ # For each row, a Symbol-keyed Hash is yielded into the block.
91
+ def each(&block)
92
+ tap{ @tuples.each(&block) }
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,28 @@
1
+ ##
2
+ # RDO: Ruby Data Objects.
3
+ # Copyright © 2012 Chris Corbyn.
4
+ #
5
+ # See LICENSE file for details.
6
+ ##
7
+
8
+ require "forwardable"
9
+
10
+ module RDO
11
+ # Represents a prepared statement.
12
+ #
13
+ # This class actually just wraps a StatementExecutor,
14
+ # which only needs to conform to a duck-type
15
+ class Statement
16
+ extend Forwardable
17
+
18
+ def_delegators :@executor, :command, :execute
19
+
20
+ # Initialize a new Statement wrapping the given StatementExecutor.
21
+ #
22
+ # @param [Object] executor
23
+ # any object that responds to #execute, #connection and #command
24
+ def initialize(executor)
25
+ @executor = executor
26
+ end
27
+ end
28
+ end
data/lib/rdo/util.rb ADDED
@@ -0,0 +1,102 @@
1
+ ##
2
+ # RDO: Ruby Data Objects.
3
+ # Copyright © 2012 Chris Corbyn.
4
+ #
5
+ # See LICENSE file for details.
6
+ ##
7
+
8
+ require "date"
9
+ require "bigdecimal"
10
+
11
+ module RDO
12
+ # This file contains methods useful for drivers to convert complex types.
13
+ #
14
+ # Performing these operations in C would not be any cheaper, since the data
15
+ # must first be converted into Ruby types anyway.
16
+ module Util
17
+ class << self
18
+ # Convert a String to a Float, taking into account Infinity and NaN.
19
+ #
20
+ # @param [String] s
21
+ # a String that is formatted for a Float;
22
+ # or Infinity, -Infinity or NaN.
23
+ #
24
+ # @return [Float]
25
+ # a Float that is the same as the input
26
+ def float(s)
27
+ case s
28
+ when "Infinity"
29
+ Float::INFINITY
30
+ when "-Infinity"
31
+ -Float::INFINITY
32
+ when "NaN"
33
+ Float::NAN
34
+ else
35
+ Float(s)
36
+ end
37
+ end
38
+
39
+ # Convert a String to a BigDecimal.
40
+ #
41
+ # @param [String] s
42
+ # a String that is formatted as a decimal, or NaN
43
+ #
44
+ # @return [BigDecimal]
45
+ # the BigDecimal representation of this number
46
+ def decimal(s)
47
+ BigDecimal(s)
48
+ end
49
+
50
+ # Convert a date & time string, without a time zone, into a DateTime.
51
+ #
52
+ # This method will parse the DateTime using the system time zone.
53
+ #
54
+ # @param [String] s
55
+ # a date & time string
56
+ #
57
+ # @return [DateTime]
58
+ # a DateTime in the system time zone
59
+ def date_time_without_zone(s)
60
+ date_time_with_zone(s + system_time_zone)
61
+ end
62
+
63
+ # Convert a date & time string, with a time zone, into a DateTime.
64
+ #
65
+ # @param [String] s
66
+ # a date & time string, including a time zone
67
+ #
68
+ # @return [DateTime]
69
+ # a DateTime for this input
70
+ def date_time_with_zone(s)
71
+ DateTime.parse(s)
72
+ end
73
+
74
+ # Convert a date string into a Date.
75
+ #
76
+ # This method understands AD and BC.
77
+ #
78
+ # @param [String] s
79
+ # a string representing a date, possibly BC
80
+ #
81
+ # @return [Date]
82
+ # a Date for this input
83
+ def date(s)
84
+ Date.parse(s)
85
+ end
86
+
87
+ # Get the time zone of the local system.
88
+ #
89
+ # This is useful—in fact crucial—for ensuring times are represented
90
+ # correctly.
91
+ #
92
+ # Driver developers should use this, where possible, to notify the DBMS
93
+ # of the client's time zone.
94
+ #
95
+ # @return [String]
96
+ # a string of the form '+10:00', or '-09:30'
97
+ def system_time_zone
98
+ DateTime.now.zone
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,3 @@
1
+ module RDO
2
+ VERSION = "0.0.1"
3
+ end
data/rdo.gemspec ADDED
@@ -0,0 +1,52 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/rdo/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["d11wtq"]
6
+ gem.email = ["chris@w3style.co.uk"]
7
+
8
+ gem.description = <<-TEXT.strip.gsub(/^ {2}/, "")
9
+ == Ruby Data Objects
10
+
11
+ If you're building something in Ruby that needs access to a database, you may
12
+ opt to use an ORM like ActiveRecord, DataMapper or Sequel. But if your needs
13
+ don't fit well with an ORM (maybe you're even writing an ORM?) then you'll
14
+ need some other way of talking to your database.
15
+
16
+ RDO provides a common interface to a number of RDBMS backends, using a clean
17
+ Ruby syntax, while supporting all the functionality you'd expect from a robust
18
+ database connection library:
19
+
20
+ - Connect to different types of RDBMS in a consistent way
21
+ - Type casting
22
+ - Safe parameterization of queries
23
+ - Buffered query results
24
+ - Fetching meta data from executed commands
25
+ - Access RETURNING values just like any read query
26
+ - Prepared statements (emulated where no native support exists)
27
+ - Simple core ruby data types
28
+
29
+ === RDBMS Support
30
+
31
+ Support for each RDBMS is provided in separate gems, so as to minimize the
32
+ installation requirements. Many gems are maintained by separate users who
33
+ work more closely with those RDBMS's.
34
+
35
+ Due to the nature of this gem, most of the nitty-gritty code is actually
36
+ written in C.
37
+
38
+ See the official README for full details.
39
+ TEXT
40
+
41
+ gem.summary = "RDO—Ruby Data Objects—A robust RDBMS connection layer"
42
+ gem.homepage = "https://github.com/d11wtq/rdo"
43
+
44
+ gem.files = `git ls-files`.split($\)
45
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
46
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
47
+ gem.name = "rdo"
48
+ gem.require_paths = ["lib"]
49
+ gem.version = RDO::VERSION
50
+
51
+ gem.add_development_dependency "rspec"
52
+ end