rdbi 0.9.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/.document +5 -0
- data/.gitignore +33 -0
- data/LICENSE +20 -0
- data/README.rdoc +194 -0
- data/Rakefile +109 -0
- data/VERSION +1 -0
- data/docs/external-api.pdf +0 -0
- data/docs/external-api.texi +365 -0
- data/lib/rdbi.rb +189 -0
- data/lib/rdbi/cursor.rb +90 -0
- data/lib/rdbi/database.rb +262 -0
- data/lib/rdbi/driver.rb +35 -0
- data/lib/rdbi/pool.rb +236 -0
- data/lib/rdbi/result.rb +402 -0
- data/lib/rdbi/schema.rb +121 -0
- data/lib/rdbi/statement.rb +204 -0
- data/lib/rdbi/types.rb +200 -0
- data/rdbi.gemspec +94 -0
- data/test/helper.rb +32 -0
- data/test/test_database.rb +246 -0
- data/test/test_pool.rb +181 -0
- data/test/test_rdbi.rb +85 -0
- data/test/test_result.rb +269 -0
- data/test/test_statement.rb +66 -0
- data/test/test_types.rb +114 -0
- data/test/test_util.rb +61 -0
- metadata +201 -0
data/lib/rdbi/schema.rb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
RDBI::Schema = Struct.new(
|
2
|
+
:columns,
|
3
|
+
:tables,
|
4
|
+
:type
|
5
|
+
)
|
6
|
+
|
7
|
+
RDBI::Column = Struct.new(
|
8
|
+
:name,
|
9
|
+
:type,
|
10
|
+
:ruby_type,
|
11
|
+
:precision,
|
12
|
+
:scale,
|
13
|
+
:nullable,
|
14
|
+
:metadata,
|
15
|
+
:default,
|
16
|
+
:table
|
17
|
+
)
|
18
|
+
|
19
|
+
#
|
20
|
+
# RDBI::Schema is the metadata representation of a single schema "object", such
|
21
|
+
# as the schema for a single table or the data queried against during
|
22
|
+
# RDBI::Statement execution.
|
23
|
+
#
|
24
|
+
# RDBI::Schema is the foundation for type management via RDBI::Type, and as a
|
25
|
+
# result an incomplete schema will lead to type inconsistency. As a result, it
|
26
|
+
# is *critical* that driver authors implement RDBI::Schema properly.
|
27
|
+
#
|
28
|
+
# RDBI::Schema is a core Struct underneath the hood and will respond accordingly.
|
29
|
+
#
|
30
|
+
class RDBI::Schema
|
31
|
+
##
|
32
|
+
# :attr_reader: columns
|
33
|
+
#
|
34
|
+
# Array of RDBI::Column objects associated with this schema.
|
35
|
+
#
|
36
|
+
|
37
|
+
##
|
38
|
+
# :attr_reader: tables
|
39
|
+
#
|
40
|
+
# Array of table names and views associated with this schema, represented as symbols.
|
41
|
+
|
42
|
+
##
|
43
|
+
# :attr_reader: type
|
44
|
+
#
|
45
|
+
# In some instances, the type (freeform, String) may be provided as an
|
46
|
+
# optional hint as to what kind of schema this is.
|
47
|
+
#
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# RDBI::Column is the metadata representation of a single table column. You
|
52
|
+
# will typically access this via RDBI::Schema.
|
53
|
+
#
|
54
|
+
# In tables, columns can represent the columns of the schema. In queries,
|
55
|
+
# columns can represent anything that identifies the column of a result set.
|
56
|
+
# This includes aggregates, other functions, dynamic queries, etc.
|
57
|
+
#
|
58
|
+
class RDBI::Column
|
59
|
+
##
|
60
|
+
# :attr_reader: name
|
61
|
+
#
|
62
|
+
# The name of the column, as symbol.
|
63
|
+
|
64
|
+
##
|
65
|
+
# :attr_reader: type
|
66
|
+
#
|
67
|
+
# The database-specific type, as symbol.
|
68
|
+
|
69
|
+
##
|
70
|
+
# :attr_reader: ruby_type
|
71
|
+
#
|
72
|
+
# The ruby target type, as symbol. This is used by RDBI::Type to convert data
|
73
|
+
# in rows.
|
74
|
+
|
75
|
+
##
|
76
|
+
# :attr_reader: precision
|
77
|
+
#
|
78
|
+
# The precision of the type. This is typically the first number in an
|
79
|
+
# extended type form, such as +NUMBER(1)+.
|
80
|
+
#
|
81
|
+
# Precisions are not always *really* precision and this depends on the type.
|
82
|
+
# Consult your database documentation for more information.
|
83
|
+
#
|
84
|
+
|
85
|
+
##
|
86
|
+
# :attr_reader: scale
|
87
|
+
#
|
88
|
+
# The scale of the type. This is typically the second number in an extended
|
89
|
+
# type form, such as +NUMBER(10,2)+.
|
90
|
+
#
|
91
|
+
# As with precision, this may not *really* be scale and it is recommended you
|
92
|
+
# consult your database documentation for specific, especially non-numeric,
|
93
|
+
# types.
|
94
|
+
#
|
95
|
+
|
96
|
+
##
|
97
|
+
# :attr_reader: nullable
|
98
|
+
#
|
99
|
+
# Boolean: does this column accept null?
|
100
|
+
|
101
|
+
##
|
102
|
+
# :attr_reader: metadata
|
103
|
+
#
|
104
|
+
# Free-form field for driver authors to provide data that lives outside of
|
105
|
+
# this specification.
|
106
|
+
|
107
|
+
##
|
108
|
+
# :attr_reader: default
|
109
|
+
#
|
110
|
+
# The value provided to the column when it is not specified but requested for
|
111
|
+
# use, such as in +INSERT+ statements.
|
112
|
+
#
|
113
|
+
|
114
|
+
##
|
115
|
+
# :attr_reader: table
|
116
|
+
#
|
117
|
+
# The table this column belongs to, if known, as symbol.
|
118
|
+
#
|
119
|
+
end
|
120
|
+
|
121
|
+
# vim: syntax=ruby ts=2 et sw=2 sts=2
|
@@ -0,0 +1,204 @@
|
|
1
|
+
#
|
2
|
+
# RDBI::Statement is the encapsulation of a single prepared statement (query).
|
3
|
+
# A statement can be executed with varying arguments multiple times through a
|
4
|
+
# facility called 'binding'.
|
5
|
+
#
|
6
|
+
# == About Binding
|
7
|
+
#
|
8
|
+
# Binding is the database term for facilitating placeholder replacement similar
|
9
|
+
# to formatters such as "sprintf()", but in a database-centric way:
|
10
|
+
#
|
11
|
+
# select * from my_table where some_column = ?
|
12
|
+
#
|
13
|
+
# The question mark is the placeholder here; upon execution, the user will be
|
14
|
+
# asked to provide values to fill that placeholder with.
|
15
|
+
#
|
16
|
+
# There are two major advantages to binding:
|
17
|
+
#
|
18
|
+
# * Multiple execution of the same statement with variable data
|
19
|
+
#
|
20
|
+
# For example, the above statement could be executed 12 times over an iterator:
|
21
|
+
#
|
22
|
+
# # RDBI::Database#prepare creates a prepared statement
|
23
|
+
#
|
24
|
+
# sth = dbh.prepare('select * from my_table where some_column = ?')
|
25
|
+
#
|
26
|
+
# # there is one placeholder here, so we'll use the iterator itself to feed
|
27
|
+
# # to the statement at execution time.
|
28
|
+
# #
|
29
|
+
# # This will send 12 copies of the select statement above, with 0 - 11 being
|
30
|
+
# # passed as the substitution for each placeholder. Use
|
31
|
+
# # RDBI::Database#preprocess_query to see what these queries would look
|
32
|
+
# # like.
|
33
|
+
#
|
34
|
+
# 12.times do |x|
|
35
|
+
# sth.execute(x)
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# * Protection against attacks such as SQL injection in a consistent way (see below).
|
39
|
+
#
|
40
|
+
# == Native client binding
|
41
|
+
#
|
42
|
+
# Binding is typically *not* just text replacement, it is a client-oriented
|
43
|
+
# operation that barely involves itself in the string at all. The query is
|
44
|
+
# parsed by the SQL engine, then the placeholders are requested; at this point,
|
45
|
+
# the client yields those to the database which then uses them in the
|
46
|
+
# *internal* representation of the query, which is why this is totally legal in
|
47
|
+
# a binding scenario:
|
48
|
+
#
|
49
|
+
# # RDBI::Database#execute is a way to prepare and execute statements immediately.
|
50
|
+
# dbh.execute("select * from my_table where some_column = ?", "; drop table my_table;")
|
51
|
+
#
|
52
|
+
# For purposes of instruction, this resolves to:
|
53
|
+
#
|
54
|
+
# select * from my_table where some_column = '; drop table my_table;'
|
55
|
+
#
|
56
|
+
# *BUT*, as mentioned above, the query is actually sent in two stages. It gets this:
|
57
|
+
#
|
58
|
+
# select * from my_table where some_column = ?
|
59
|
+
#
|
60
|
+
# Then a single element tuple is sent:
|
61
|
+
#
|
62
|
+
# ["; drop table my_table;"]
|
63
|
+
#
|
64
|
+
# These are combined with *post-parsing* to create a full, legal statement, so
|
65
|
+
# no grammar rules can be exploited.
|
66
|
+
#
|
67
|
+
# As a result, placeholder rules in this scenario are pretty rigid, only values
|
68
|
+
# can be used. For example, you cannot supply placeholders for:
|
69
|
+
#
|
70
|
+
# * table names
|
71
|
+
# * sql keywords and functions
|
72
|
+
# * other elements of syntax (punctuation, etc)
|
73
|
+
#
|
74
|
+
# == Preprocessing
|
75
|
+
#
|
76
|
+
# Preprocessing is a fallback mechanism we use when the underlying database
|
77
|
+
# does not support the above mechanism. It, unlike native client binding, is
|
78
|
+
# basically text replacement, so all those rules about what you can and cannot
|
79
|
+
# do go away.
|
80
|
+
#
|
81
|
+
# The downside is that if our replacement system (provided by the Epoxy class,
|
82
|
+
# which itself is provided by the epoxy gem) is unkempt, SQL injection attacks
|
83
|
+
# may be possible.
|
84
|
+
#
|
85
|
+
class RDBI::Statement
|
86
|
+
extend MethLab
|
87
|
+
|
88
|
+
# the RDBI::Database handle that created this statement.
|
89
|
+
attr_reader :dbh
|
90
|
+
# The query this statement was created for.
|
91
|
+
attr_reader :query
|
92
|
+
# A mutex for locked operations. Basically a cached copy of Mutex.new.
|
93
|
+
attr_reader :mutex
|
94
|
+
# The input type map provided during statement creation -- used for binding.
|
95
|
+
attr_reader :input_type_map
|
96
|
+
|
97
|
+
##
|
98
|
+
# :attr_reader: last_result
|
99
|
+
#
|
100
|
+
# The last RDBI::Result this statement yielded.
|
101
|
+
attr_threaded_accessor :last_result
|
102
|
+
|
103
|
+
##
|
104
|
+
# :attr_reader: finished
|
105
|
+
#
|
106
|
+
# Has this statement been finished?
|
107
|
+
|
108
|
+
##
|
109
|
+
# :attr_reader: finished?
|
110
|
+
#
|
111
|
+
# Has this statement been finished?
|
112
|
+
|
113
|
+
inline(:finished, :finished?) { @finished }
|
114
|
+
|
115
|
+
##
|
116
|
+
# :attr_reader: driver
|
117
|
+
#
|
118
|
+
# The RDBI::Driver object that this statement belongs to.
|
119
|
+
|
120
|
+
inline(:driver) { dbh.driver }
|
121
|
+
|
122
|
+
#
|
123
|
+
# Initialize a statement handle, given a text query and the RDBI::Database
|
124
|
+
# handle that created it.
|
125
|
+
#
|
126
|
+
def initialize(query, dbh)
|
127
|
+
@query = query
|
128
|
+
@dbh = dbh
|
129
|
+
@mutex = Mutex.new
|
130
|
+
@finished = false
|
131
|
+
@input_type_map = RDBI::Type.create_type_hash(RDBI::Type::In)
|
132
|
+
|
133
|
+
@dbh.open_statements.push(self)
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
# Execute the statement with the supplied binds.
|
138
|
+
#
|
139
|
+
def execute(*binds)
|
140
|
+
raise StandardError, "you may not execute a finished handle" if @finished
|
141
|
+
|
142
|
+
# XXX if we ever support some kind of hash type, this'll get ugly.
|
143
|
+
hashes, binds = binds.partition { |x| x.kind_of?(Hash) }
|
144
|
+
|
145
|
+
if hashes
|
146
|
+
hashes.collect! do |hash|
|
147
|
+
newhash = { }
|
148
|
+
|
149
|
+
hash.each do |key, value|
|
150
|
+
newhash[key] = RDBI::Type::In.convert(value, @input_type_map)
|
151
|
+
end
|
152
|
+
|
153
|
+
newhash
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
binds = (hashes || []) + binds.collect { |x| RDBI::Type::In.convert(x, @input_type_map) }
|
158
|
+
|
159
|
+
mutex.synchronize do
|
160
|
+
exec_args = *new_execution(*binds)
|
161
|
+
self.last_result = RDBI::Result.new(self, binds, *exec_args)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
#
|
166
|
+
# Deallocate any internal resources devoted to the statement. It will not be
|
167
|
+
# usable after this is called.
|
168
|
+
#
|
169
|
+
# Driver implementors will want to subclass this, do their thing and call
|
170
|
+
# 'super' as their last statement.
|
171
|
+
#
|
172
|
+
def finish
|
173
|
+
mutex.synchronize do
|
174
|
+
dbh.open_statements.reject! { |x| x.object_id == self.object_id }
|
175
|
+
@finished = true
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
##
|
180
|
+
# :method: new_execution
|
181
|
+
# :call-seq: new_execution(*binds)
|
182
|
+
#
|
183
|
+
# Database drivers will override this method in their respective RDBI::Statement
|
184
|
+
# subclasses. This method is called when RDBI::Statement#execute or
|
185
|
+
# RDBI::Database#execute is called.
|
186
|
+
#
|
187
|
+
# Implementations of this method must return, in order:
|
188
|
+
#
|
189
|
+
# * A RDBI::Cursor object which encapsulates the result
|
190
|
+
# * a RDBI::Schema struct which represents the kinds of data being queried
|
191
|
+
# * a +type_hash+ for on-fetch conversion which corresponds to the
|
192
|
+
# RDBI::Column information (see RDBI::Schema) and follows a structure similar
|
193
|
+
# to RDBI::Type::Out
|
194
|
+
#
|
195
|
+
# These return values are passed (along with this object and the binds passed
|
196
|
+
# to this call) to RDBI::Result.new.
|
197
|
+
#
|
198
|
+
|
199
|
+
inline(:new_execution) do |*args|
|
200
|
+
raise NoMethodError, "this method is not implemented in this driver"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# vim: syntax=ruby ts=2 et sw=2 sts=2
|
data/lib/rdbi/types.rb
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
require 'typelib'
|
2
|
+
require 'typelib/canned'
|
3
|
+
|
4
|
+
#
|
5
|
+
# == RDBI::Type -- manage types going to and coming from your database.
|
6
|
+
#
|
7
|
+
# RDBI::Type consists of:
|
8
|
+
#
|
9
|
+
# * Checks and Conversions (facilitated by TypeLib) for ruby -> database and
|
10
|
+
# database -> ruby
|
11
|
+
# * Mappings for Input (Ruby -> DB) and Output (DB -> Ruby) conversions based
|
12
|
+
# on type.
|
13
|
+
# * Convenience methods for TypeLib and creating new mappings.
|
14
|
+
#
|
15
|
+
# == How does it all work?
|
16
|
+
#
|
17
|
+
# RDBI::Type leverages +TypeLib+ which is a filter chaining system, one which
|
18
|
+
# you'll wish to read the documentation for to understand some of the concepts
|
19
|
+
# here.
|
20
|
+
#
|
21
|
+
# === A conversion follows these steps:
|
22
|
+
#
|
23
|
+
# * Metadata on the type (more below) is located and used to reference a
|
24
|
+
# TypeLib::FilterList which contains the TypeLib::Filters (which in turn
|
25
|
+
# consist of a +Check+ and +Conversion+ proc) which will process your data.
|
26
|
+
# * Data is passed to the FilterList and it is executed, following each filter
|
27
|
+
# in turn and following any conversion passing checks request. This may very
|
28
|
+
# well mean that no checks pass and therefore your original data is returned.
|
29
|
+
# * After processing, the data is yielded back to you for further processing
|
30
|
+
# (or a subsystem such as RDBI::Result#fetch and result drivers that take
|
31
|
+
# advantage of said data)
|
32
|
+
#
|
33
|
+
# === How is metadata located?
|
34
|
+
#
|
35
|
+
# It's important first to briefly describe how RDBI terms database I/O:
|
36
|
+
#
|
37
|
+
# * Binds going to the database proper are called 'input'.
|
38
|
+
# * Data coming from the database is called 'output'.
|
39
|
+
#
|
40
|
+
# Mappings are keyed by type metadata and thusly are typed as:
|
41
|
+
#
|
42
|
+
# * Input types are the native class of the type.
|
43
|
+
# * Output types are a symbol that represents the database type. These type
|
44
|
+
# names are provided by RDBI::Column via RDBI::Schema in the response from an
|
45
|
+
# execution. See RDBI::Statement#new_execution and RDBI::Column#ruby_type.
|
46
|
+
#
|
47
|
+
# Note that in the latter case these database types are effectively normalized,
|
48
|
+
# e.g., 'timestamp with timezone' in postgres is just +:timestamp+. You will
|
49
|
+
# want to read the mappings in the source to get a full listing of what's
|
50
|
+
# supported by default.
|
51
|
+
#
|
52
|
+
# Each map will also contain +:default+, which is what is used when a proper
|
53
|
+
# lookup fails, as a fallback.
|
54
|
+
#
|
55
|
+
# === Ok, so how do I use these maps?
|
56
|
+
#
|
57
|
+
# RDBI::Type.create_type_hash is a helper to duplicate the default maps and
|
58
|
+
# return them. If you don't wish to use the default maps at all, just a plain
|
59
|
+
# old +Hash+ following the semantics above will work.
|
60
|
+
#
|
61
|
+
# To perform conversions, look at RDBI::Type::In::convert and
|
62
|
+
# RDBI::Type::Out::convert.
|
63
|
+
#
|
64
|
+
module RDBI::Type
|
65
|
+
# A filter format to assist the conversions of DateTime objects.
|
66
|
+
DEFAULT_STRFTIME_FILTER = "%Y-%m-%d %H:%M:%S %z"
|
67
|
+
|
68
|
+
# Module for canned checks that are unique to RDBI. Includes the canned
|
69
|
+
# checks from TypeLib.
|
70
|
+
module Checks
|
71
|
+
include TypeLib::Canned::Checks
|
72
|
+
|
73
|
+
IS_NULL = proc { |obj| obj.nil? }
|
74
|
+
IS_BIGDECIMAL = proc { |obj| obj.kind_of?(BigDecimal) }
|
75
|
+
IS_DATETIME = proc { |obj| obj.kind_of?(DateTime) }
|
76
|
+
IS_BOOLEAN = proc { |obj| obj.kind_of?(TrueClass) or obj.kind_of?(FalseClass) }
|
77
|
+
|
78
|
+
STR_IS_BOOLEAN = proc { |obj| obj.kind_of?(String) and obj =~ /^(t(rue)?|f(alse)?|1|0)$/i }
|
79
|
+
end
|
80
|
+
|
81
|
+
# Module for canned conversions that are unique to RDBI. Includes the canned
|
82
|
+
# conversions from TypeLib.
|
83
|
+
module Conversions
|
84
|
+
include TypeLib::Canned::Conversions
|
85
|
+
|
86
|
+
TO_NULL = proc { |obj| nil }
|
87
|
+
TO_STRING_DECIMAL = proc { |obj| obj.to_s('F') }
|
88
|
+
TO_STRING_DATETIME = proc { |obj| obj.strftime(DEFAULT_STRFTIME_FILTER) }
|
89
|
+
TO_STRING_BOOLEAN = proc { |obj| obj ? 'TRUE' : 'FALSE' }
|
90
|
+
|
91
|
+
SQL_STR_TO_BOOLEAN = proc { |obj|
|
92
|
+
case obj
|
93
|
+
when /^(t(rue)?|1)$/i
|
94
|
+
true
|
95
|
+
when /^(f(alse)?|0)$/i
|
96
|
+
false
|
97
|
+
end
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
# Canned +TypeLib::Filter+ objects unique to RDBI to facilitate certain
|
102
|
+
# conversions. Includes TypeLib's canned filters.
|
103
|
+
module Filters
|
104
|
+
include TypeLib::Canned::Filters
|
105
|
+
|
106
|
+
NULL = TypeLib::Filter.new(Checks::IS_NULL, Conversions::TO_NULL)
|
107
|
+
FROM_INTEGER = TypeLib::Filter.new(Checks::IS_INTEGER, Conversions::TO_STRING)
|
108
|
+
FROM_NUMERIC = TypeLib::Filter.new(Checks::IS_NUMERIC, Conversions::TO_STRING)
|
109
|
+
FROM_DECIMAL = TypeLib::Filter.new(Checks::IS_BIGDECIMAL, Conversions::TO_STRING_DECIMAL)
|
110
|
+
FROM_DATETIME = TypeLib::Filter.new(Checks::IS_DATETIME, Conversions::TO_STRING_DATETIME)
|
111
|
+
FROM_BOOLEAN = TypeLib::Filter.new(Checks::IS_BOOLEAN, Conversions::TO_STRING_BOOLEAN)
|
112
|
+
|
113
|
+
TO_BOOLEAN = TypeLib::Filter.new(Checks::STR_IS_BOOLEAN, Conversions::SQL_STR_TO_BOOLEAN)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Shorthand for creating a new +TypeLib::FilterList+.
|
117
|
+
def self.filterlist(*ary)
|
118
|
+
TypeLib::FilterList.new([Filters::NULL, *ary])
|
119
|
+
end
|
120
|
+
|
121
|
+
# Shorthand to duplicate the +DEFAULTS+ hash from a module. Most frequently
|
122
|
+
# used to get a copy of the RDBI::Type::In and RDBI::Type::Out type maps.
|
123
|
+
def self.create_type_hash(klass)
|
124
|
+
hash = { }
|
125
|
+
klass::DEFAULTS.each do |key, value|
|
126
|
+
flist = filterlist()
|
127
|
+
value.each do |filter|
|
128
|
+
flist << filter
|
129
|
+
end
|
130
|
+
hash[key] = flist
|
131
|
+
end
|
132
|
+
|
133
|
+
return hash
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
# The default output type map. As explained in RDBI::Type, these are keyed by
|
138
|
+
# symbol and are loosely related to the type, and are compared against the
|
139
|
+
# proper RDBI::Column object to figure out which filter to call.
|
140
|
+
#
|
141
|
+
module Out
|
142
|
+
DEFAULTS = {
|
143
|
+
:integer => RDBI::Type.filterlist(Filters::STR_TO_INT),
|
144
|
+
:decimal => RDBI::Type.filterlist(Filters::STR_TO_DEC),
|
145
|
+
:datetime => RDBI::Type.filterlist(TypeLib::Canned.build_strptime_filter(DEFAULT_STRFTIME_FILTER)),
|
146
|
+
:timestamp => RDBI::Type.filterlist(TypeLib::Canned.build_strptime_filter(DEFAULT_STRFTIME_FILTER)),
|
147
|
+
:boolean => RDBI::Type.filterlist(Filters::TO_BOOLEAN),
|
148
|
+
:default => RDBI::Type.filterlist()
|
149
|
+
}
|
150
|
+
|
151
|
+
#
|
152
|
+
# Perform a conversion. Accepts the object to convert, a RDBI::Column
|
153
|
+
# object, and a type map (a +Hash+).
|
154
|
+
#
|
155
|
+
def self.convert(obj, column, type_hash)
|
156
|
+
fl = type_hash[column.ruby_type]
|
157
|
+
|
158
|
+
unless fl
|
159
|
+
fl = type_hash[:default]
|
160
|
+
end
|
161
|
+
|
162
|
+
fl.execute(obj)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
#
|
167
|
+
# The default input type map. As explained in RDBI::Type, these are keyed by
|
168
|
+
# the Ruby type with the exception of +:default+ which is a fallback
|
169
|
+
# conversion. RDBI::Statement subclassers will normally provide this object
|
170
|
+
# via +@input_type_map+ at construction time.
|
171
|
+
#
|
172
|
+
module In
|
173
|
+
DEFAULTS = {
|
174
|
+
Integer => RDBI::Type.filterlist(Filters::FROM_INTEGER),
|
175
|
+
Fixnum => RDBI::Type.filterlist(Filters::FROM_INTEGER),
|
176
|
+
Float => RDBI::Type.filterlist(Filters::FROM_NUMERIC),
|
177
|
+
BigDecimal => RDBI::Type.filterlist(Filters::FROM_DECIMAL),
|
178
|
+
DateTime => RDBI::Type.filterlist(Filters::FROM_DATETIME),
|
179
|
+
TrueClass => RDBI::Type.filterlist(Filters::FROM_BOOLEAN),
|
180
|
+
FalseClass => RDBI::Type.filterlist(Filters::FROM_BOOLEAN),
|
181
|
+
:default => RDBI::Type.filterlist()
|
182
|
+
}
|
183
|
+
|
184
|
+
#
|
185
|
+
# Perform a conversion. Accepts the object to convert and a type map (a
|
186
|
+
# +Hash+).
|
187
|
+
#
|
188
|
+
def self.convert(obj, type_hash)
|
189
|
+
fl = type_hash[obj.class]
|
190
|
+
|
191
|
+
unless fl
|
192
|
+
fl = type_hash[:default]
|
193
|
+
end
|
194
|
+
|
195
|
+
fl.execute(obj)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# vim: syntax=ruby ts=2 et sw=2 sts=2
|