dbi 0.4.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/build/Rakefile.dbi.rb +57 -0
  6. data/examples/test1.pl +39 -0
  7. data/examples/test1.rb +20 -0
  8. data/examples/xmltest.rb +8 -0
  9. data/lib/dbi.rb +323 -0
  10. data/lib/dbi/base_classes.rb +12 -0
  11. data/lib/dbi/base_classes/database.rb +135 -0
  12. data/lib/dbi/base_classes/driver.rb +47 -0
  13. data/lib/dbi/base_classes/statement.rb +167 -0
  14. data/lib/dbi/binary.rb +25 -0
  15. data/lib/dbi/columninfo.rb +106 -0
  16. data/lib/dbi/exceptions.rb +65 -0
  17. data/lib/dbi/handles.rb +49 -0
  18. data/lib/dbi/handles/database.rb +211 -0
  19. data/lib/dbi/handles/driver.rb +60 -0
  20. data/lib/dbi/handles/statement.rb +375 -0
  21. data/lib/dbi/row.rb +249 -0
  22. data/lib/dbi/sql.rb +23 -0
  23. data/lib/dbi/sql/preparedstatement.rb +115 -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 +158 -0
  27. data/lib/dbi/typeutil.rb +108 -0
  28. data/lib/dbi/utils.rb +60 -0
  29. data/lib/dbi/utils/date.rb +59 -0
  30. data/lib/dbi/utils/tableformatter.rb +112 -0
  31. data/lib/dbi/utils/time.rb +52 -0
  32. data/lib/dbi/utils/timestamp.rb +96 -0
  33. data/lib/dbi/utils/xmlformatter.rb +73 -0
  34. data/test/dbi/tc_columninfo.rb +94 -0
  35. data/test/dbi/tc_date.rb +88 -0
  36. data/test/dbi/tc_dbi.rb +178 -0
  37. data/test/dbi/tc_row.rb +256 -0
  38. data/test/dbi/tc_sqlbind.rb +168 -0
  39. data/test/dbi/tc_statementhandle.rb +16 -0
  40. data/test/dbi/tc_time.rb +77 -0
  41. data/test/dbi/tc_timestamp.rb +142 -0
  42. data/test/dbi/tc_types.rb +220 -0
  43. data/test/dbi/trace.rb +26 -0
  44. data/test/ts_dbi.rb +15 -0
  45. metadata +108 -0
@@ -0,0 +1,249 @@
1
+ require "delegate"
2
+
3
+ module DBI
4
+ # DBI::Row is the representation of a row in a result set returned by the
5
+ # database.
6
+ #
7
+ # It is responsible for containing and representing the result set, converting
8
+ # it to native Ruby types, and providing methods to sanely move through itself.
9
+ #
10
+ # The DBI::Row class is a delegate of Array, rather than a subclass, because
11
+ # there are times when it should act like an Array, and others when it should
12
+ # act like a Hash (and still others where it should act like String, Regexp,
13
+ # etc). It also needs to store metadata about the row, such as
14
+ # column data type and index information, that users can then access.
15
+ #
16
+ class Row < DelegateClass(Array)
17
+ attr_reader :column_names
18
+
19
+ # DBI::Row.new(columns, column_types, size_or_array=nil)
20
+ #
21
+ # Returns a new DBI::Row object using +columns+. The +size_or_array+
22
+ # argument may either be an Integer or an Array. If it is not provided,
23
+ # it defaults to the length of +columns+.
24
+ #
25
+ # Column types is a corresponding Array of Class/Module objects that
26
+ # conform to the DBI::Type interface. These will be used to convert types
27
+ # as they are returned.
28
+ #
29
+ # DBI::Row is a delegate of the Array class, so all of the Array
30
+ # instance methods are available to your DBI::Row object (keeping in
31
+ # mind that initialize, [], and []= have been explicitly overridden).
32
+ #
33
+ def initialize(columns, column_types, size_or_array=nil, convert_types=true)
34
+ @column_types = column_types
35
+ @convert_types = convert_types
36
+ size_or_array ||= columns.size
37
+
38
+ # The '@column_map' is used to map column names to integer values so
39
+ # that users can reference row values by name or number.
40
+
41
+ @column_map = {}
42
+ @column_names = columns
43
+ columns.each_with_index { |c,i| @column_map[c] = i }
44
+
45
+ case size_or_array
46
+ when Integer
47
+ super(@arr = Array.new(size_or_array))
48
+ when Array
49
+ super(@arr = size_or_array.dup)
50
+ set_values(size_or_array.dup)
51
+ else
52
+ raise TypeError, "parameter must be either Integer or Array"
53
+ end
54
+ end
55
+
56
+ # converts the types in the array to their specified representation
57
+ # from column types provided at construction time.
58
+ def convert_types(arr)
59
+ return arr.dup unless @convert_types
60
+
61
+ if arr.size != @column_types.size
62
+ raise TypeError, "Type mapping is not consistent with result"
63
+ end
64
+ new_arr = []
65
+ arr.each_with_index do |item, i|
66
+ new_arr.push((@column_types[i] || DBI::Type::Varchar).parse(item))
67
+ end
68
+
69
+ return new_arr
70
+ end
71
+
72
+ # Replaces the contents of the internal array with +new_values+.
73
+ # elements are type converted at this time.
74
+ def set_values(new_values)
75
+ @arr.replace(convert_types(new_values))
76
+ end
77
+
78
+ # Yields a column value by name (rather than index), along with the
79
+ # column name itself.
80
+ def each_with_name
81
+ @arr.each_with_index do |v, i|
82
+ yield v, @column_names[i]
83
+ end
84
+ end
85
+
86
+ # returns the underlying array (duplicated)
87
+ def to_a
88
+ @arr.dup
89
+ end
90
+
91
+ # Returns the Row object as a hash, created by #each_with_name.
92
+ def to_h
93
+ hash = {}
94
+ each_with_name{ |v, n| hash[n] = v}
95
+ hash
96
+ end
97
+
98
+ # Create a new row with 'new_values', reusing the field name hash.
99
+ # Initial cloning is done deeply, via Marshal.
100
+ def clone_with(new_values)
101
+ obj = Marshal.load(Marshal.dump(self))
102
+ obj.set_values(new_values)
103
+
104
+ return obj
105
+ end
106
+
107
+ alias field_names column_names
108
+
109
+ # Retrieve a value by index (rather than name).
110
+ #
111
+ # Deprecated. Since Row delegates to Array, just use Row#at.
112
+ def by_index(index)
113
+ @arr[index]
114
+ end
115
+
116
+ # Value of the field named +field_name+ or nil if not found.
117
+ def by_field(field_name)
118
+ begin
119
+ @arr[@column_map[field_name.to_s]]
120
+ rescue TypeError
121
+ nil
122
+ end
123
+ end
124
+
125
+ # Row#[]
126
+ #
127
+ # row[int]
128
+ # row[array]
129
+ # row[regexp]
130
+ # row[arg, arg]
131
+ # row[arg, arg, ...]
132
+ #
133
+ # Sample: Row.new(["first","last","age"], ["Daniel", "Berger", "36"])
134
+ #
135
+ # Retrieves row elements. Exactly what it retrieves depends on the
136
+ # kind and number of arguments used.
137
+ #
138
+ # Zero arguments will raise an ArgumentError.
139
+ #
140
+ # One argument will return a single result. This can be a String,
141
+ # Symbol, Integer, Range or Regexp and the appropriate result will
142
+ # be returned. Strings, Symbols and Regexps act like hash lookups,
143
+ # while Integers and Ranges act like Array index lookups.
144
+ #
145
+ # Two arguments will act like the second form of Array#[], i.e it takes
146
+ # two integers, with the first number the starting point and the second
147
+ # number the length, and returns an array of values.
148
+ #
149
+ # If three or more arguments are provided, an array of results is
150
+ # returned. The behavior for each argument is that of a single argument,
151
+ # i.e. Strings, Symbols, and Regexps act like hash lookups, while
152
+ # Integers and Ranges act like Array index lookups.
153
+ #
154
+ # If no results are found, or an unhandled type is passed, then nil
155
+ # (or a nil element) is returned.
156
+ #
157
+ def [](*args)
158
+ begin
159
+ case args.length
160
+ when 0
161
+ err = "wrong # of arguments(#{args.size} for at least 1)"
162
+ raise ArgumentError, err
163
+ when 1
164
+ case args[0]
165
+ when Array
166
+ args[0].collect { |e| self[e] }
167
+ when Regexp
168
+ self[@column_names.grep(args[0])]
169
+ else
170
+ @arr[conv_param(args[0])]
171
+ end
172
+ # We explicitly check for a length of 2 in order to properly
173
+ # simulate the second form of Array#[].
174
+ when 2
175
+ @arr[conv_param(args[0]), conv_param(args[1])]
176
+ else
177
+ results = []
178
+ args.flatten.each{ |arg|
179
+ case arg
180
+ when Integer
181
+ results.push(@arr[arg])
182
+ when Regexp
183
+ results.push(self[@column_names.grep(arg)])
184
+ else
185
+ results.push(self[conv_param(arg)])
186
+ end
187
+ }
188
+ results.flatten
189
+ end
190
+ rescue TypeError
191
+ nil
192
+ end
193
+ end
194
+
195
+ # Assign a value to a Row object by element. You can assign using
196
+ # a single element reference, or by using a start and length similar
197
+ # to the second form of Array#[]=.
198
+ #
199
+ # row[0] = "kirk"
200
+ # row[:last] = "haines"
201
+ # row[0, 2] = "test"
202
+ #
203
+ def []=(key, value_or_length, obj=nil)
204
+ if obj
205
+ @arr[conv_param(key), conv_param(value_or_length)] = obj
206
+ else
207
+ @arr[conv_param(key)] = value_or_length
208
+ end
209
+ end
210
+
211
+ #
212
+ # See Object#clone.
213
+ #
214
+ # #clone and #dup here, however, are both deep copies via Marshal.
215
+ #
216
+ def clone
217
+ Marshal.load(Marshal.dump(self))
218
+ end
219
+
220
+ alias dup clone
221
+
222
+ private
223
+
224
+ # Simple helper method to grab the proper value from @column_map
225
+ # NOTE this does something completely different than DBI::Utils::ConvParam
226
+ def conv_param(arg) # :nodoc:
227
+ case arg
228
+ when String, Symbol
229
+ @column_map[arg.to_s]
230
+ when Range
231
+ if arg.first.kind_of?(Symbol) || arg.first.kind_of?(String)
232
+ first = @column_map[arg.first.to_s]
233
+ last = @column_map[arg.last.to_s]
234
+ else
235
+ first = arg.first
236
+ last = arg.last
237
+ end
238
+
239
+ if arg.exclude_end?
240
+ (first...last)
241
+ else
242
+ (first..last)
243
+ end
244
+ else
245
+ arg
246
+ end
247
+ end
248
+ end # class Row
249
+ end # module DBI
@@ -0,0 +1,23 @@
1
+ #
2
+ # $Id: sql.rb,v 1.3 2006/03/27 20:25:02 francis Exp $
3
+ #
4
+ # parts extracted from Jim Weirichs DBD::Pg
5
+ #
6
+
7
+ require "dbi/utils"
8
+ require "parsedate"
9
+ require "time"
10
+
11
+ module DBI
12
+ # the SQL package contains assistance for DBDs and generally will not be
13
+ # needed outside of them.
14
+ module SQL
15
+ # Helper to determine if the statement is a query. Very crude and
16
+ # should not be relied on for accuracy.
17
+ def self.query?(sql)
18
+ sql =~ /^\s*select\b/i
19
+ end
20
+ end # module SQL
21
+ end # module DBI
22
+
23
+ require 'dbi/sql/preparedstatement'
@@ -0,0 +1,115 @@
1
+ module DBI
2
+ module SQL
3
+ #
4
+ # The PreparedStatement class attempts to provide binding functionality
5
+ # for database systems that do not have this built-in. This package
6
+ # emulates the whole concept of a statement.
7
+ #
8
+ class PreparedStatement
9
+ attr_accessor :unbound
10
+
11
+ # Convenience method for consumers that just need the tokens
12
+ # method.
13
+ def self.tokens(sql)
14
+ self.new(nil, sql).tokens
15
+ end
16
+
17
+ #
18
+ # "prepare" a statement.
19
+ #
20
+ # +quoter+ is deprecated and will eventually disappear, it is kept
21
+ # currently for compatibility. It is safe to pass nil to this parameter.
22
+ #
23
+ # +sql+ is the statement itself.
24
+ #
25
+ def initialize(quoter, sql)
26
+ @quoter, @sql = quoter, sql
27
+ prepare
28
+ end
29
+
30
+ # Break the sql string into parts.
31
+ #
32
+ # This is NOT a full lexer for SQL. It just breaks up the SQL
33
+ # string enough so that question marks, double question marks and
34
+ # quoted strings are separated. This is used when binding
35
+ # arguments to "?" in the SQL string.
36
+ #
37
+ # C-style (/* */) and Ada-style (--) comments are handled.
38
+ # Note:: Nested C-style comments are NOT handled!
39
+ #
40
+ def tokens
41
+ @sql.scan(%r{
42
+ (
43
+ -- .* (?# matches "--" style comments to the end of line or string )
44
+ | - (?# matches single "-" )
45
+ |
46
+ /[*] .*? [*]/ (?# matches C-style comments )
47
+ | / (?# matches single slash )
48
+ |
49
+ ' ( [^'\\] | '' | \\. )* ' (?# match strings surrounded by apostophes )
50
+ |
51
+ " ( [^"\\] | "" | \\. )* " (?# match strings surrounded by " )
52
+ |
53
+ \?\?? (?# match one or two question marks )
54
+ |
55
+ [^-/'"?]+ (?# match all characters except ' " ? - and / )
56
+
57
+ )}x).collect {|t| t.first}
58
+ end
59
+
60
+ # attempts to bind the arguments in +args+ to this statement.
61
+ # Will raise StandardError if there are any extents issues.
62
+ def bind(args)
63
+ if @arg_index < args.size
64
+ raise "Too many SQL parameters"
65
+ elsif @arg_index > args.size
66
+ raise "Not enough SQL parameters"
67
+ end
68
+
69
+ @unbound.each do |res_pos, arg_pos|
70
+ @result[res_pos] = args[arg_pos]
71
+ end
72
+
73
+ @result.join("")
74
+ end
75
+
76
+ private
77
+
78
+ # prepares the statement for binding. This is done by scanning the
79
+ # statement and itemizing what needs to be bound and what does not.
80
+ #
81
+ # This information will then be used by #bind to appropriately map
82
+ # parameters to the intended destinations.
83
+ def prepare
84
+ @result = []
85
+ @unbound = {}
86
+ pos = 0
87
+ @arg_index = 0
88
+
89
+ tokens.each { |part|
90
+ case part
91
+ when '?'
92
+ @result[pos] = nil
93
+ @unbound[pos] = @arg_index
94
+ pos += 1
95
+ @arg_index += 1
96
+ when '??'
97
+ if @result[pos-1] != nil
98
+ @result[pos-1] << "?"
99
+ else
100
+ @result[pos] = "?"
101
+ pos += 1
102
+ end
103
+ else
104
+ if @result[pos-1] != nil
105
+ @result[pos-1] << part
106
+ else
107
+ @result[pos] = part
108
+ pos += 1
109
+ end
110
+ end
111
+ }
112
+ end
113
+ end # PreparedStatement
114
+ end
115
+ end
@@ -0,0 +1,75 @@
1
+ module DBI
2
+ # Constants
3
+
4
+ # Constants for fetch_scroll
5
+ #
6
+ SQL_FETCH_NEXT = 1
7
+ SQL_FETCH_PRIOR = 2
8
+ SQL_FETCH_FIRST = 3
9
+ SQL_FETCH_LAST = 4
10
+ SQL_FETCH_ABSOLUTE = 5
11
+ SQL_FETCH_RELATIVE = 6
12
+
13
+ # SQL type constants
14
+ #
15
+ SQL_CHAR = 1
16
+ SQL_NUMERIC = 2
17
+ SQL_DECIMAL = 3
18
+ SQL_INTEGER = 4
19
+ SQL_SMALLINT = 5
20
+ SQL_FLOAT = 6
21
+ SQL_REAL = 7
22
+ SQL_DOUBLE = 8
23
+ SQL_DATE = 9 # 91
24
+ SQL_TIME = 10 # 92
25
+ SQL_TIMESTAMP = 11 # 93
26
+ SQL_VARCHAR = 12
27
+ SQL_BOOLEAN = 13
28
+
29
+ SQL_LONGVARCHAR = -1
30
+ SQL_BINARY = -2
31
+ SQL_VARBINARY = -3
32
+ SQL_LONGVARBINARY = -4
33
+ SQL_BIGINT = -5
34
+ SQL_TINYINT = -6
35
+ SQL_BIT = -7
36
+
37
+ # TODO
38
+ # Find types for these (XOPEN?)
39
+ #SQL_ARRAY =
40
+ SQL_BLOB = -10 # TODO
41
+ SQL_CLOB = -11 # TODO
42
+ #SQL_DISTINCT =
43
+ #SQL_OBJECT =
44
+ #SQL_NULL =
45
+ SQL_OTHER = 100
46
+ #SQL_REF =
47
+ #SQL_STRUCT =
48
+
49
+ SQL_TYPE_NAMES = {
50
+ SQL_BIT => 'BIT',
51
+ SQL_TINYINT => 'TINYINT',
52
+ SQL_SMALLINT => 'SMALLINT',
53
+ SQL_INTEGER => 'INTEGER',
54
+ SQL_BIGINT => 'BIGINT',
55
+ SQL_FLOAT => 'FLOAT',
56
+ SQL_REAL => 'REAL',
57
+ SQL_DOUBLE => 'DOUBLE',
58
+ SQL_NUMERIC => 'NUMERIC',
59
+ SQL_DECIMAL => 'DECIMAL',
60
+ SQL_CHAR => 'CHAR',
61
+ SQL_VARCHAR => 'VARCHAR',
62
+ SQL_LONGVARCHAR => 'LONG VARCHAR',
63
+ SQL_DATE => 'DATE',
64
+ SQL_TIME => 'TIME',
65
+ SQL_TIMESTAMP => 'TIMESTAMP',
66
+ SQL_BINARY => 'BINARY',
67
+ SQL_VARBINARY => 'VARBINARY',
68
+ SQL_LONGVARBINARY => 'LONG VARBINARY',
69
+ SQL_BLOB => 'BLOB',
70
+ SQL_CLOB => 'CLOB',
71
+ SQL_OTHER => nil,
72
+ SQL_BOOLEAN => 'BOOLEAN',
73
+
74
+ }
75
+ end