dbi 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +3694 -0
- data/LICENSE +25 -0
- data/README +271 -0
- data/bin/dbi +518 -0
- data/build/Rakefile.dbi.rb +57 -0
- data/examples/test1.pl +39 -0
- data/examples/test1.rb +20 -0
- data/examples/xmltest.rb +8 -0
- data/lib/dbi.rb +323 -0
- data/lib/dbi/base_classes.rb +12 -0
- data/lib/dbi/base_classes/database.rb +135 -0
- data/lib/dbi/base_classes/driver.rb +47 -0
- data/lib/dbi/base_classes/statement.rb +167 -0
- data/lib/dbi/binary.rb +25 -0
- data/lib/dbi/columninfo.rb +106 -0
- data/lib/dbi/exceptions.rb +65 -0
- data/lib/dbi/handles.rb +49 -0
- data/lib/dbi/handles/database.rb +211 -0
- data/lib/dbi/handles/driver.rb +60 -0
- data/lib/dbi/handles/statement.rb +375 -0
- data/lib/dbi/row.rb +249 -0
- data/lib/dbi/sql.rb +23 -0
- data/lib/dbi/sql/preparedstatement.rb +115 -0
- data/lib/dbi/sql_type_constants.rb +75 -0
- data/lib/dbi/trace.rb +91 -0
- data/lib/dbi/types.rb +158 -0
- data/lib/dbi/typeutil.rb +108 -0
- data/lib/dbi/utils.rb +60 -0
- data/lib/dbi/utils/date.rb +59 -0
- data/lib/dbi/utils/tableformatter.rb +112 -0
- data/lib/dbi/utils/time.rb +52 -0
- data/lib/dbi/utils/timestamp.rb +96 -0
- data/lib/dbi/utils/xmlformatter.rb +73 -0
- data/test/dbi/tc_columninfo.rb +94 -0
- data/test/dbi/tc_date.rb +88 -0
- data/test/dbi/tc_dbi.rb +178 -0
- data/test/dbi/tc_row.rb +256 -0
- data/test/dbi/tc_sqlbind.rb +168 -0
- data/test/dbi/tc_statementhandle.rb +16 -0
- data/test/dbi/tc_time.rb +77 -0
- data/test/dbi/tc_timestamp.rb +142 -0
- data/test/dbi/tc_types.rb +220 -0
- data/test/dbi/trace.rb +26 -0
- data/test/ts_dbi.rb +15 -0
- metadata +108 -0
data/lib/dbi/row.rb
ADDED
@@ -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
|
data/lib/dbi/sql.rb
ADDED
@@ -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
|