rdo 0.0.1

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