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.
- 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
|