rails-dbi 0.1.0

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/ChangeLog +3694 -0
  2. data/LICENSE +25 -0
  3. data/README +271 -0
  4. data/bin/dbi +518 -0
  5. data/bin/test_broken_dbi +37 -0
  6. data/build/Rakefile.dbi.rb +60 -0
  7. data/examples/test1.pl +39 -0
  8. data/examples/test1.rb +20 -0
  9. data/examples/xmltest.rb +8 -0
  10. data/lib/dbi/base_classes/database.rb +135 -0
  11. data/lib/dbi/base_classes/driver.rb +47 -0
  12. data/lib/dbi/base_classes/statement.rb +171 -0
  13. data/lib/dbi/base_classes.rb +12 -0
  14. data/lib/dbi/binary.rb +25 -0
  15. data/lib/dbi/columninfo.rb +107 -0
  16. data/lib/dbi/exceptions.rb +65 -0
  17. data/lib/dbi/handles/database.rb +241 -0
  18. data/lib/dbi/handles/driver.rb +60 -0
  19. data/lib/dbi/handles/statement.rb +408 -0
  20. data/lib/dbi/handles.rb +49 -0
  21. data/lib/dbi/row.rb +270 -0
  22. data/lib/dbi/sql/preparedstatement.rb +115 -0
  23. data/lib/dbi/sql.rb +22 -0
  24. data/lib/dbi/sql_type_constants.rb +75 -0
  25. data/lib/dbi/trace.rb +91 -0
  26. data/lib/dbi/types.rb +218 -0
  27. data/lib/dbi/typeutil.rb +109 -0
  28. data/lib/dbi/utils/date.rb +59 -0
  29. data/lib/dbi/utils/tableformatter.rb +112 -0
  30. data/lib/dbi/utils/time.rb +52 -0
  31. data/lib/dbi/utils/timestamp.rb +96 -0
  32. data/lib/dbi/utils/xmlformatter.rb +73 -0
  33. data/lib/dbi/utils.rb +60 -0
  34. data/lib/dbi.rb +337 -0
  35. data/test/dbi/tc_columninfo.rb +94 -0
  36. data/test/dbi/tc_date.rb +88 -0
  37. data/test/dbi/tc_dbi.rb +184 -0
  38. data/test/dbi/tc_row.rb +256 -0
  39. data/test/dbi/tc_sqlbind.rb +168 -0
  40. data/test/dbi/tc_statementhandle.rb +29 -0
  41. data/test/dbi/tc_time.rb +77 -0
  42. data/test/dbi/tc_timestamp.rb +142 -0
  43. data/test/dbi/tc_types.rb +268 -0
  44. data/test/ts_dbi.rb +15 -0
  45. metadata +132 -0
@@ -0,0 +1,135 @@
1
+ module DBI
2
+ # Provides the core-level functionality for DatabaseHandles.
3
+ #
4
+ # If the method description says "DBD Required", it's the DBD's
5
+ # responsibility to create this method.
6
+ #
7
+ # Required methods unimplemented by the DBD will raise
8
+ # DBD::NotImplementedError.
9
+ #
10
+ # "DBD Optional" methods are methods that do not have a default
11
+ # implementation but are optional due to the fact that many databases may
12
+ # not support these features (and emulating them would be prohibitive).
13
+ #
14
+ # These methods raise DBI::NotSupportedError.
15
+ #
16
+ # Otherwise, DBI will provide a general alternative which should meet the
17
+ # expectations of the documentation. However, DBDs can override every
18
+ # method in this class.
19
+ #
20
+ class BaseDatabase < Base
21
+ def initialize(handle, attr)
22
+ @handle = handle
23
+ @attr = {}
24
+ attr.each {|k,v| self[k] = v}
25
+ end
26
+
27
+ # Disconnect from the database. DBD Required.
28
+ def disconnect
29
+ raise NotImplementedError
30
+ end
31
+
32
+ # Ping the database to ensure the connection is still alive. Boolean
33
+ # return, true for success. DBD Required.
34
+ def ping
35
+ raise NotImplementedError
36
+ end
37
+
38
+ # Prepare a cached statement, returning a StatementHandle. DBD
39
+ # Required.
40
+ def prepare(statement)
41
+ raise NotImplementedError
42
+ end
43
+
44
+ #
45
+ # Return a map of the columns that exist in the provided table name.
46
+ # DBD Required.
47
+ #
48
+ # The result should be an array of DBI::ColumnInfo objects which have,
49
+ # at minimum, the following fields:
50
+ #
51
+ # * name:: the name of the column.
52
+ # * type:: This is not a field name in itself. You have two options:
53
+ # * type_name:: The name of the type as returned by the database
54
+ # * dbi_type:: A DBI::Type-conforming class that can be used to convert to a native type.
55
+ # * precision:: the precision (generally length) of the column
56
+ # * scale:: the scale (generally a secondary attribute to precision
57
+ # that helps indicate length) of the column
58
+ #
59
+ def columns(table)
60
+ raise NotImplementedError
61
+ end
62
+
63
+ #============================================
64
+ # OPTIONAL
65
+ #============================================
66
+
67
+ # Schedule a commit to the database immediately. DBD Optional.
68
+ def commit
69
+ raise NotSupportedError
70
+ end
71
+
72
+ # Schedule a rollback to the database immediately. DBD Optional.
73
+ def rollback
74
+ raise NotSupportedError
75
+ end
76
+
77
+ # Return the tables available to the database connection.
78
+ #
79
+ # Note:: the basic implementation returns an empty array.
80
+ def tables
81
+ []
82
+ end
83
+
84
+ #
85
+ # Execute a statement with the binds provided. Returns the statement
86
+ # handle unfinished.
87
+ #
88
+ # This is roughly equivalent to:
89
+ #
90
+ # sth = dbh.prepare("my statement")
91
+ # sth.execute(my, bind, vars)
92
+ #
93
+ def execute(statement, *bindvars)
94
+ stmt = prepare(statement)
95
+ stmt.bind_params(*bindvars)
96
+ stmt.execute
97
+ stmt
98
+ end
99
+
100
+ #
101
+ # Execute and complete the statement with the binds provided. Returns
102
+ # the row modified count (via BaseStatement#rows). Finishes the
103
+ # statement handle for you.
104
+ #
105
+ # Roughly equivalent to:
106
+ #
107
+ # sth = dbh.prepare("my statement")
108
+ # sth.execute(my, bind, vars)
109
+ # result = sth.rows
110
+ # sth.finish
111
+ #
112
+ # Returning the value stored in `result`.
113
+ def do(statement, *bindvars)
114
+ stmt = execute(statement, *bindvars)
115
+ res = stmt.rows
116
+ stmt.finish
117
+ return res
118
+ end
119
+
120
+ #
121
+ # Get an attribute from the DatabaseHandle. These are DBD specific and
122
+ # embody things like Auto-Commit support for transactional databases.
123
+ #
124
+ # DBD Authors:: This messes with @attr directly.
125
+ #
126
+ def [](attr)
127
+ @attr[attr]
128
+ end
129
+
130
+ # Set an attribute on the DatabaseHandle. DBD Optional.
131
+ def []=(attr, value)
132
+ raise NotSupportedError
133
+ end
134
+ end # class BaseDatabase
135
+ end
@@ -0,0 +1,47 @@
1
+ module DBI
2
+
3
+ # Implements the basic functionality that constitutes a Driver
4
+ #
5
+ # Drivers do not have a direct interface exposed to the user; these methods
6
+ # are mostly for DBD authors.
7
+ #
8
+ # As with DBI::BaseDatabase, "DBD Required" and "DBD Optional" will be used
9
+ # to explain the same requirements.
10
+ #
11
+ class BaseDriver < Base
12
+ def initialize(dbi_version)
13
+ major, minor = dbi_version.split(".").collect { |x| x.to_i }
14
+ dbi_major, dbi_minor = DBI::VERSION.split(".").collect { |x| x.to_i }
15
+ unless major == dbi_major and minor == dbi_minor
16
+ raise InterfaceError, "Wrong DBD API version used"
17
+ end
18
+ end
19
+
20
+ # Connect to the database. DBD Required.
21
+ def connect(dbname, user, auth, attr)
22
+ raise NotImplementedError
23
+ end
24
+
25
+ # Default u/p information in an array.
26
+ def default_user
27
+ ['', '']
28
+ end
29
+
30
+ # Default attributes to set on the DatabaseHandle.
31
+ def default_attributes
32
+ {}
33
+ end
34
+
35
+ # Return the data sources available to this driver. Returns an empty
36
+ # array per default.
37
+ def data_sources
38
+ []
39
+ end
40
+
41
+ # Disconnect all DatabaseHandles. DBD Required.
42
+ def disconnect_all
43
+ raise NotImplementedError
44
+ end
45
+
46
+ end # class BaseDriver
47
+ end
@@ -0,0 +1,171 @@
1
+ module DBI
2
+ #
3
+ # StatementHandles are used to encapsulate the process of managing a
4
+ # statement (DDL or DML) and its parameters, sending it to the database,
5
+ # and gathering any results from the execution of that statement.
6
+ #
7
+ # As with the other `Base` classes, the terms "DBD Required" and "DBD
8
+ # Optional" are defined in DBI::BaseDatabase.
9
+ #
10
+ class BaseStatement < Base
11
+
12
+ attr_accessor :raise_error
13
+
14
+ def initialize(attr=nil)
15
+ @attr = attr || {}
16
+ end
17
+
18
+ #
19
+ # Bind a parameter to the statement. DBD Required.
20
+ #
21
+ # The parameter number is numeric and indexes starting at 1. This
22
+ # corresponds to the question marks (?) in the statement from the
23
+ # left-most part of the statement moving forward.
24
+ #
25
+ # The value may be any ruby type. How these are handled is
26
+ # DBD-dependent, but the vast majority of DBDs will convert these to
27
+ # string inside the query.
28
+ #
29
+ def bind_param(param, value, attribs)
30
+ raise NotImplementedError
31
+ end
32
+
33
+ #
34
+ # Execute the statement with the known binds. DBD Required.
35
+ #
36
+ def execute
37
+ raise NotImplementedError
38
+ end
39
+
40
+ #
41
+ # Close the statement and any result cursors. DBD Required.
42
+ #
43
+ # Note:: Most implementations will fail miserably if you forget to
44
+ # finish your statement handles.
45
+ def finish
46
+ raise NotImplementedError
47
+ end
48
+
49
+ #
50
+ # Fetch the next row in the result set. DBD Required.
51
+ #
52
+ # DBI::Row is responsible for formatting the data the DBD provides it.
53
+ #
54
+ def fetch
55
+ raise NotImplementedError
56
+ end
57
+
58
+ ##
59
+ # returns result-set column information as array
60
+ # of hashs, where each hash represents one column. See
61
+ # BaseDatabase#columns. DBD Required.
62
+ #
63
+ def column_info
64
+ raise NotImplementedError
65
+ end
66
+
67
+ #============================================
68
+ # OPTIONAL
69
+ #============================================
70
+
71
+ #
72
+ # Take a list of bind variables and bind them successively using bind_param.
73
+ #
74
+ def bind_params(*bindvars)
75
+ bindvars.each_with_index {|val,i| bind_param(i+1, val, nil) }
76
+ self
77
+ end
78
+
79
+ #
80
+ # Cancel any result cursors. DBD Optional, but intentionally does not
81
+ # raise any exception as it's used internally to maintain consistency.
82
+ #
83
+ def cancel
84
+ end
85
+
86
+ #
87
+ # fetch_scroll is provided with a direction and offset and works
88
+ # similar to how seek() is used on files.
89
+ #
90
+ # The constants available for direction are as follows:
91
+ #
92
+ # * SQL_FETCH_NEXT: fetch the next result.
93
+ # * SQL_FETCH_LAST: fetch the last result, period.
94
+ # * SQL_FETCH_RELATIVE: fetch the result at the offset.
95
+ #
96
+ # Other constants can be used, but if this method is not supplied by
97
+ # the driver, they will result in a raise of DBI::NotSupportedError.
98
+ #
99
+
100
+ def fetch_scroll(direction, offset)
101
+ case direction
102
+ when SQL_FETCH_NEXT
103
+ return fetch
104
+ when SQL_FETCH_LAST
105
+ last_row = nil
106
+ while (row=fetch) != nil
107
+ last_row = row
108
+ end
109
+ return last_row
110
+ when SQL_FETCH_RELATIVE
111
+ raise NotSupportedError if offset <= 0
112
+ row = nil
113
+ offset.times { row = fetch; break if row.nil? }
114
+ return row
115
+ else
116
+ raise NotSupportedError
117
+ end
118
+ end
119
+
120
+ #
121
+ # fetch x rows. The result is Array of DBI::Row.
122
+ #
123
+ def fetch_many(cnt)
124
+ rows = []
125
+ cnt.times do
126
+ row = fetch
127
+ break if row.nil?
128
+ rows << row.dup
129
+ end
130
+
131
+ if rows.empty?
132
+ nil
133
+ else
134
+ rows
135
+ end
136
+ end
137
+
138
+ #
139
+ # Fetch all available rows. Result is Array of DBI::Row.
140
+ #
141
+ def fetch_all
142
+ rows = []
143
+ loop do
144
+ row = fetch
145
+ break if row.nil?
146
+ rows << row.dup
147
+ end
148
+
149
+ if rows.empty?
150
+ nil
151
+ else
152
+ rows
153
+ end
154
+ end
155
+
156
+ #
157
+ # Get statement attributes.
158
+ #
159
+ def [](attr)
160
+ @attr ||= { }
161
+ @attr[attr]
162
+ end
163
+
164
+ #
165
+ # Set statement attributes. DBD Optional.
166
+ #
167
+ def []=(attr, value)
168
+ raise NotSupportedError
169
+ end
170
+ end # class BaseStatement
171
+ end
@@ -0,0 +1,12 @@
1
+ #--
2
+ # Fallback classes for default behavior of DBD driver
3
+ # must be inherited by the DBD driver classes
4
+ #++
5
+ module DBI
6
+ class Base #:nodoc:
7
+ end
8
+ end
9
+
10
+ require 'dbi/base_classes/driver'
11
+ require 'dbi/base_classes/database'
12
+ require 'dbi/base_classes/statement'
data/lib/dbi/binary.rb ADDED
@@ -0,0 +1,25 @@
1
+ module DBI
2
+ #
3
+ # Encapsulates the concept of a CLOB/BLOB, which can then be passed as a
4
+ # bind via BaseStatement#bind_param.
5
+ #
6
+ # This is similar to a DBI::Type class and will eventually find its way
7
+ # there.
8
+ #
9
+ # See #new for usage.
10
+ class Binary
11
+ attr_accessor :data
12
+
13
+ # Construct a new DBI::Binary object with the data supplied as string.
14
+ # This object can then be used in bound variables to represent a CLOB
15
+ # or BLOB type.
16
+ def initialize(data)
17
+ @data = data
18
+ end
19
+
20
+ # Return the string representation of the DBI::Binary object.
21
+ def to_s
22
+ @data
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,107 @@
1
+ require 'delegate'
2
+
3
+ begin
4
+ require 'rubygems'
5
+ gem 'deprecated', "= 2.0.1"
6
+ rescue LoadError => e
7
+ end
8
+
9
+ require 'deprecated'
10
+
11
+ module DBI
12
+ # This represents metadata for columns within a given table, such as the
13
+ # data type, whether or not the the column is a primary key, etc.
14
+ #
15
+ # ColumnInfo is a delegate of Hash, but represents its keys indifferently,
16
+ # coercing all strings to symbols. It also has ostruct-like features, f.e.:
17
+ #
18
+ # h = ColumnInfo.new({ "foo" => "bar" })
19
+ # h[:foo] => "bar"
20
+ # h["foo"] => "bar"
21
+ # h.foo => "bar"
22
+ #
23
+ # All of these forms have assignment forms as well.
24
+ #
25
+ class ColumnInfo < DelegateClass(Hash)
26
+
27
+ # Create a new ColumnInfo object.
28
+ #
29
+ # If no Hash is provided, one will be created for you. The hash will be
30
+ # shallow cloned for storage inside the object, and an attempt will be
31
+ # made to convert all string keys to symbols.
32
+ #
33
+ # In the event that both string and symbol keys are provided in the
34
+ # initial hash, we cannot safely route around collisions and therefore
35
+ # a TypeError is raised.
36
+ #
37
+ def initialize(hash=nil)
38
+ @hash = hash.dup rescue nil
39
+ @hash ||= Hash.new
40
+
41
+ # coerce all strings to symbols
42
+ @hash.each_key do |x|
43
+ if x.kind_of? String
44
+ sym = x.to_sym
45
+ if @hash.has_key? sym
46
+ raise ::TypeError,
47
+ "#{self.class.name} may construct from a hash keyed with strings or symbols, but not both"
48
+ end
49
+ @hash[sym] = @hash[x]
50
+ @hash.delete(x)
51
+ end
52
+ end
53
+
54
+ super(@hash)
55
+ end
56
+
57
+ def [](key)
58
+ @hash[key.to_sym]
59
+ end
60
+
61
+ def []=(key, value)
62
+ @hash[key.to_sym] = value
63
+ end
64
+
65
+ def default() # :nodoc; XXX hack to get around Hash#default
66
+ method_missing(:default)
67
+ end
68
+
69
+ def method_missing(sym, value=nil)
70
+ if sym.to_s =~ /=$/
71
+ sym = sym.to_s.sub(/=$/, '').to_sym
72
+ @hash[sym] = value
73
+ elsif sym.to_s =~ /\?$/
74
+ sym = sym.to_s.sub(/\?$/, '').to_sym
75
+ @hash[sym]
76
+ else
77
+ @hash[sym]
78
+ end
79
+ end
80
+
81
+ # Aliases - XXX soon to be deprecated
82
+ def self.deprecated_alias(target, source) # :nodoc:
83
+ define_method(target) { |*args| method_missing(source, *args) }
84
+ deprecate target
85
+ end
86
+
87
+ deprecated_alias :is_nullable?, :nullable
88
+ deprecated_alias :can_be_null?, :nullable
89
+
90
+ deprecated_alias :is_indexed?, :indexed
91
+
92
+ deprecated_alias :is_primary?, :primary
93
+
94
+ deprecated_alias :is_unique, :unique
95
+
96
+ deprecated_alias :size, :precision
97
+ deprecated_alias :size=, :precision=
98
+ deprecated_alias :length, :precision
99
+ deprecated_alias :length=, :precision=
100
+
101
+ deprecated_alias :decimal_digits, :scale
102
+ deprecated_alias :decimal_digits=, :scale=
103
+
104
+ deprecated_alias :default_value, :default
105
+ deprecated_alias :default_value=, :default=
106
+ end
107
+ end
@@ -0,0 +1,65 @@
1
+ module DBI
2
+ # Exceptions (borrowed by Python API 2.0)
3
+
4
+ # Base class of all other error exceptions. Use this to catch all DBI
5
+ # errors.
6
+ class Error < RuntimeError
7
+ end
8
+
9
+ # For important warnings like data truncation, etc.
10
+ class Warning < RuntimeError
11
+ end
12
+
13
+ # Exception for errors related to the DBI interface rather than the
14
+ # database itself.
15
+ class InterfaceError < Error
16
+ end
17
+
18
+ # Exception raised if the DBD driver has not specified a mandatory method.
19
+ class NotImplementedError < InterfaceError
20
+ end
21
+
22
+ # Exception for errors related to the database.
23
+ class DatabaseError < Error
24
+ attr_reader :err, :errstr, :state
25
+
26
+ def initialize(errstr="", err=nil, state=nil)
27
+ super(errstr)
28
+ @err, @errstr, @state = err, errstr, state
29
+ end
30
+ end
31
+
32
+ # Exception for errors due to problems with the processed
33
+ # data such as division by zero, numeric value out of range, etc.
34
+ class DataError < DatabaseError
35
+ end
36
+
37
+ # Exception for errors related to the database's operation which are not
38
+ # necessarily under the control of the programmer. This includes such
39
+ # things as unexpected disconnect, datasource name not found, transaction
40
+ # could not be processed, a memory allocation error occured during
41
+ # processing, etc.
42
+ class OperationalError < DatabaseError
43
+ end
44
+
45
+ # Exception raised when the relational integrity of the database
46
+ # is affected, e.g. a foreign key check fails.
47
+ class IntegrityError < DatabaseError
48
+ end
49
+
50
+ # Exception raised when the database encounters an internal error,
51
+ # e.g. the cursor is not valid anymore, the transaction is out of sync.
52
+ class InternalError < DatabaseError
53
+ end
54
+
55
+ # Exception raised for programming errors, e.g. table not found
56
+ # or already exists, syntax error in SQL statement, wrong number
57
+ # of parameters specified, etc.
58
+ class ProgrammingError < DatabaseError
59
+ end
60
+
61
+ # Exception raised if e.g. commit() is called for a database which do not
62
+ # support transactions.
63
+ class NotSupportedError < DatabaseError
64
+ end
65
+ end