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
data/lib/dbi/types.rb ADDED
@@ -0,0 +1,218 @@
1
+ require 'time'
2
+ require 'bigdecimal'
3
+ require 'rational'
4
+
5
+ module DBI
6
+ #
7
+ # Interface to convert SQL result sets to native Ruby types.
8
+ #
9
+ # Type is used to convert result sets, which differ from bound variables
10
+ # (which generally go in the opposite direction). For those, see
11
+ # DBI::TypeUtil#convert and DBI::TypeUtil#register_conversion.
12
+ #
13
+ # Type objects have a simple interface: they implement a +parse+ method
14
+ # which takes the result from the DBD and attempts to convert it to the
15
+ # native type. In the event that they do not do this successfully, they are
16
+ # expected to return the object in its original form.
17
+ #
18
+ # As a result, many of the built-in Type classes fallback to simpler forms:
19
+ # Float falls back to Integer, Integer to Varchar, etc. It's questionable
20
+ # at this point if it's desirable to do this, but testing has so far proven
21
+ # it a non-issue.
22
+ #
23
+ # To reiterate, it is *never acceptable* to return +nil+ or some other
24
+ # placeholder when an object will not successfully parse. Return the object
25
+ # handed to you.
26
+ #
27
+ # Types must also handle +nil+ as a result to parse. In this case, the
28
+ # advisable solution is to just let the +nil+ pass through, as it's usually
29
+ # indicative of a SQL NULL result.
30
+ #
31
+ # DBI::Row handles delegation of these objects as a converter for the
32
+ # results. Typically, the type object is a class inferred from
33
+ # DBI::TypeUtil#type_name_to_module ran against the ColumnInfo field
34
+ # +type_name+. However, the the +dbi_type+ field can be used in its place
35
+ # to directly associate a Type object with the column in the DBD, and
36
+ # end-users can leverage StatementHandle#bind_coltype to manually tweak
37
+ # this transformation.
38
+ #
39
+ # As stated before, Type objects are objects. These objects may be Modules
40
+ # or Classes (and typically are), but there is no reason a traditional
41
+ # constructed object with a +parse+ method cannot be returned; in fact, it
42
+ # is used in a few spots to emulate complex types such as PostgreSQL
43
+ # arrays. Look into the +dbi_type+ ColumnInfo field to pass these types
44
+ # around.
45
+ #
46
+ module Type
47
+ #
48
+ # Represents a SQL NULL.
49
+ #
50
+ class Null
51
+ def self.parse(obj)
52
+ return nil if obj.to_s.match(/^null$/i)
53
+ return obj
54
+ end
55
+ end
56
+
57
+ #
58
+ # Represents a SQL char or varchar. General fallback class.
59
+ #
60
+ class Varchar
61
+ def self.parse(obj)
62
+ return obj unless obj
63
+ return obj.to_s if obj.respond_to? :to_s
64
+ return obj.to_str if obj.respond_to? :to_str
65
+ return obj
66
+ end
67
+ end
68
+
69
+ #
70
+ # Represents a whole number. Falls back to Varchar.
71
+ #
72
+ class Integer < Varchar
73
+ def self.parse(obj)
74
+ return nil if Null.parse(obj).nil?
75
+ return obj.to_i if obj.respond_to? :to_i
76
+ super
77
+ end
78
+ end
79
+
80
+ #
81
+ # Represents a decimal number with floating-point precision. Falls back
82
+ # to Integer.
83
+ #
84
+ class Float < Integer
85
+ def self.parse(obj)
86
+ return nil if Null.parse(obj).nil?
87
+ return obj.to_f if obj.respond_to? :to_f
88
+ super
89
+ end
90
+ end
91
+
92
+ #
93
+ # Represents a Decimal with real precision (BigDecimal). Falls back to
94
+ # Float.
95
+ #
96
+ class Decimal < Float
97
+ def self.parse(obj)
98
+ BigDecimal.new(obj) rescue super
99
+ end
100
+ end
101
+
102
+ #
103
+ # Represents a SQL TIMESTAMP and returns DateTime. Falls back to Null.
104
+ #
105
+ class Timestamp < Null
106
+ def self.create(year, month, day, hour, min, sec, usec=0, of=0)
107
+ # DateTime will remove leap and leap-leap seconds
108
+ sec = 59 if sec > 59
109
+ # store this before we modify it
110
+ civil = year, month, day
111
+ time = hour, min, sec, usec
112
+
113
+ date = ::DateTime.civil(year, month, day, hour, min, sec, of)
114
+ date += usec
115
+ #prefill_cache date, civil, time
116
+ date
117
+ end
118
+
119
+ # FIXME these methods are broken, I don't know why, and I don't really care right now.
120
+ # we shouldn't be playing in datetime's garden anyways.
121
+ if RUBY_VERSION =~ /^1\.8\./
122
+ def self.prefill_cache date, civil, time
123
+ time[3] /= 86400000000.0
124
+ date.instance_variable_set :"@__#{:civil.to_i}__", [civil]
125
+ date.instance_variable_set :"@__#{:time.to_i}__", [time]
126
+ end
127
+ else
128
+ def self.prefill_cache date, civil, time
129
+ time[3] /= 1000000.0
130
+ date.instance_variable_get(:@__ca__)[:civil.object_id] = civil
131
+ date.instance_variable_get(:@__ca__)[:time.object_id] = time
132
+ end
133
+ end
134
+
135
+ def self.parse_string str
136
+ # special casing the common formats here gives roughly an
137
+ # 8-fold speed boost over using Date._parse
138
+ case str
139
+ when /^(\d{4})-(\d{2})-(\d{2})(?: (\d{2}):(\d{2}):(\d{2})(\.\d+)?)?(?: ([+-]?\d{2}):?(\d{2}))?$/
140
+ parts = $~[1..-4].map { |s| s.to_i }
141
+ # i feel unclean. if we have fractional seconds, pad the number and then stuff it into a rational.
142
+ if $7
143
+ frac = $7.to_f * 10000000
144
+ parts << Rational(frac.to_i, 864000000000)
145
+ else
146
+ parts << 0
147
+ end
148
+ parts << Rational(($8 || 0).to_i * 60 + ($9 || 0).to_i, 1440)
149
+ else
150
+ parts = ::Date._parse(str).values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset)
151
+ # some defaults
152
+ today = nil
153
+ 8.times do |i|
154
+ next if parts[i]
155
+ today ||= ::Time.now.to_a.values_at(5, 4, 3) + [0, 0, 0, 0, 0]
156
+ parts[i] = today[i]
157
+ end
158
+ parts[6] = parts[6].kind_of?(Rational) ? parts[6] : Rational(parts[6], 1)
159
+ parts[6] *= Rational(1, 86400)
160
+ parts[7] = Rational(parts[7], 86400)
161
+ end
162
+ parts
163
+ end
164
+
165
+ def self.parse(obj)
166
+ case obj
167
+ when ::DateTime
168
+ return obj
169
+ when ::Date
170
+ return create(obj.year, obj.month, obj.day, 0, 0, 0)
171
+ when ::Time
172
+ return create(obj.year, obj.month, obj.day, obj.hour, obj.min, obj.sec, Rational(obj.usec, 86400000000), Rational(obj.utc_offset, 86400))
173
+ else
174
+ obj = super
175
+ return obj unless obj
176
+ return create(*parse_string(obj.to_s)) if obj.respond_to? :to_s
177
+ return create(*parse_string(obj.to_str)) if obj.respond_to? :to_str
178
+ return obj
179
+ end
180
+ end
181
+ end
182
+
183
+ #
184
+ # Represents a SQL BOOLEAN. Returns true/false. Falls back to Null.
185
+ #
186
+ class Boolean < Null
187
+ def self.parse(obj)
188
+ obj = super
189
+
190
+ return nil if obj.nil?
191
+
192
+ if obj == false or obj.kind_of? FalseClass
193
+ return false
194
+ elsif obj.kind_of? TrueClass
195
+ return true
196
+ else
197
+ case obj
198
+ when 't'
199
+ return true
200
+ when 'f'
201
+ return false
202
+ end
203
+
204
+ if obj.respond_to? :to_i
205
+ if obj.to_i == 0
206
+ return false
207
+ else
208
+ return true
209
+ end
210
+ else
211
+ # punt
212
+ return nil
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,109 @@
1
+ module DBI
2
+ #
3
+ # TypeUtil is a series of utility methods for type management.
4
+ #
5
+ class TypeUtil
6
+ @@conversions = { }
7
+
8
+ #
9
+ # Register a conversion for a DBD. This applies to bound parameters for
10
+ # outgoing statements; please look at DBI::Type for result sets.
11
+ #
12
+ # Conversions are given a driver_name, which is then used to look up
13
+ # the conversion to perform on the object. Please see #convert for more
14
+ # information. Driver names are typically provided by the DBD, but may
15
+ # be overridden at any stage temporarily by assigning to the
16
+ # +driver_name+ attribute for the various handles.
17
+ #
18
+ # A conversion block is normally a +case+ statement that identifies
19
+ # various native ruby types and converts them to string, but ultimately
20
+ # the result type is dependent on low-level driver. The resulting
21
+ # object will be fed to the query as the bound value.
22
+ #
23
+ # The result of the block is two arguments, the first being the result
24
+ # object, and the second being a +cascade+ flag, which if true
25
+ # instructs #convert to run the result through the +default+ conversion
26
+ # as well and use its result. This is advantageous when you just need
27
+ # to convert everything to string, and allow +default+ to properly escape
28
+ # it.
29
+ #
30
+ def self.register_conversion(driver_name, &block)
31
+ raise "Must provide a block" unless block_given?
32
+ @@conversions[driver_name] = block
33
+ end
34
+
35
+ #
36
+ # Convert object for +driver_name+. See #register_conversion for a
37
+ # complete explanation of how type conversion is performed.
38
+ #
39
+ # If the conversion is instructed to cascade, it will go to the special
40
+ # "default" conversion, which is a pre-defined common case (and
41
+ # mutable) ruleset for native types. Note that it will use the result
42
+ # from the first conversion, not what was originally passed. Be sure to
43
+ # leave the object untouched if that is your intent. E.g., if your DBD
44
+ # converts an Integer to String and tells it to cascade, the "default"
45
+ # conversion will get a String and quote it, not an Integer (which has
46
+ # different rules).
47
+ #
48
+ def self.convert(driver_name, obj)
49
+ if @@conversions[driver_name]
50
+ newobj, cascade = @@conversions[driver_name].call(obj)
51
+ if cascade
52
+ return @@conversions["default"].call(newobj)
53
+ end
54
+ return newobj
55
+ end
56
+
57
+ return @@conversions["default"].call(obj)
58
+ end
59
+
60
+ #
61
+ # Convenience method to match many SQL named types to DBI::Type classes. If
62
+ # none can be matched, returns DBI::Type::Varchar.
63
+ #
64
+ def self.type_name_to_module(type_name)
65
+ case type_name
66
+ when /^int(?:\d+|eger)?$/i
67
+ DBI::Type::Integer
68
+ when /^varchar$/i, /^character varying$/i
69
+ DBI::Type::Varchar
70
+ when /^(?:float|real)$/i
71
+ DBI::Type::Float
72
+ when /^bool(?:ean)?$/i, /^tinyint$/i
73
+ DBI::Type::Boolean
74
+ when /^time(?:stamp(?:tz)?)?$/i
75
+ DBI::Type::Timestamp
76
+ when /^(?:decimal|numeric)$/i
77
+ DBI::Type::Decimal
78
+ else
79
+ DBI::Type::Varchar
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ DBI::TypeUtil.register_conversion("default") do |obj|
86
+ case obj
87
+ when DBI::Binary # these need to be handled specially by the driver
88
+ obj
89
+ when ::NilClass
90
+ nil
91
+ when ::TrueClass
92
+ "'1'"
93
+ when ::FalseClass
94
+ "'0'"
95
+ when ::Time, ::Date, ::DateTime
96
+ "'#{::DateTime.parse(obj.to_s).strftime("%Y-%m-%dT%H:%M:%S")}'"
97
+ when ::String, ::Symbol
98
+ obj = obj.to_s
99
+ obj = obj.gsub(/\\/) { "\\\\" }
100
+ obj = obj.gsub(/'/) { "''" }
101
+ "'#{obj}'"
102
+ when ::BigDecimal
103
+ obj.to_s("F")
104
+ when ::Numeric
105
+ obj.to_s
106
+ else
107
+ "'#{obj.to_s}'"
108
+ end
109
+ end
@@ -0,0 +1,59 @@
1
+ module DBI
2
+ #
3
+ # Represents a Date.
4
+ #
5
+ # DEPRECATED: Please use a regular Date or DateTime object.
6
+ #
7
+ class Date
8
+ attr_accessor :year, :month, :day
9
+
10
+ # Aliases
11
+ alias :mon :month
12
+ alias :mon= :month=
13
+ alias :mday :day
14
+ alias :mday= :day=
15
+
16
+ # Returns a new Time object based on the year, month and day or, if a
17
+ # Time object was passed to the constructor, returns that object.
18
+ def to_time
19
+ @original_time || ::Time.local(@year, @month, @day, 0, 0, 0)
20
+ end
21
+
22
+ # Returns a new Date object based on the year, month and day or, if a
23
+ # Date object was passed to the constructor, returns that object.
24
+ def to_date
25
+ @original_date || ::Date.new(@year, @month, @day)
26
+ end
27
+
28
+ # Returns a DBI::Date object as a string in YYYY-MM-DD format.
29
+ def to_s
30
+ sprintf("%04d-%02d-%02d", @year, @month, @day)
31
+ end
32
+
33
+ private
34
+
35
+ # DBI::Date.new(year = 0, month = 0, day = 0)
36
+ # DBI::Date.new(Date)
37
+ # DBI::Date.new(Time)
38
+ #
39
+ # Creates and returns a new DBI::Date object. It's similar to the
40
+ # standard Date class' constructor except that it also accepts a
41
+ # Date or Time object.
42
+ def initialize(year=0, month=0, day=0)
43
+ case year
44
+ when ::Date
45
+ @year, @month, @day = year.year, year.month, year.day
46
+ @original_date = year
47
+ when ::Time
48
+ @year, @month, @day = year.year, year.month, year.day
49
+ @original_time = year
50
+ else
51
+ @year, @month, @day = year, month, day
52
+ end
53
+ end
54
+
55
+ public
56
+
57
+ deprecate :initialize, :public
58
+ end
59
+ end
@@ -0,0 +1,112 @@
1
+ module DBI
2
+ module Utils
3
+ # Formats a resultset in a textual table, suitable for printing.
4
+ module TableFormatter
5
+
6
+ def self.coerce(obj) # :nodoc:
7
+ # FIXME this is probably short-sighted.
8
+ obj = "NULL" if obj.nil?
9
+ obj = (obj.kind_of?(Array) or obj.kind_of?(Hash)) ? obj.inspect : obj.to_s
10
+ return obj
11
+ end
12
+
13
+ # Perform the formatting.
14
+ #
15
+ # * +header+: table headers, as you'd expect they correspond to each column in the row.
16
+ # * +rows+: array of array (or DBI::Row) which represent the data.
17
+ # * +header_orient+: jusification of the header. :left, :right, or :center.
18
+ # * +rows_orient+: justification of the rows. same as +header_orient+.
19
+ # * +indent+: number of spaces to indent each line in the output.
20
+ # * +cellspace+: number of spaces to pad the cell on the left and right.
21
+ # * +pagebreak_after+: introduce a pagebreak each +n+ rows.
22
+ # * +output+: object that responds to `<<` which will contain the output. Default is STDOUT.
23
+ #
24
+ # If a block is provided, +output+ will be yielded each row if
25
+ # +pagebreak+ is nil, otherwise it will be yielded when the output
26
+ # is complete.
27
+ #--
28
+ # TODO: add a nr-column where the number of the column is shown
29
+ #++
30
+ def self.ascii(header,
31
+ rows,
32
+ header_orient=:left,
33
+ rows_orient=:left,
34
+ indent=2,
35
+ cellspace=1,
36
+ pagebreak_after=nil,
37
+ output=STDOUT)
38
+
39
+ if rows.size == 0 or rows[0].size == 0
40
+ output.puts "No rows selected"
41
+ return
42
+ end
43
+
44
+ header_orient ||= :left
45
+ rows_orient ||= :left
46
+ indent ||= 2
47
+ cellspace ||= 1
48
+
49
+ # pagebreak_after n-rows (without counting header or split-lines)
50
+ # yield block with output as param after each pagebreak (not at the end)
51
+
52
+ col_lengths = (0...(header.size)).collect do |colnr|
53
+ [
54
+ (0...rows.size).collect { |rownr|
55
+ value = rows[rownr][colnr]
56
+ coerce(value).size
57
+ }.max,
58
+ header[colnr].size
59
+ ].max
60
+ end
61
+
62
+ indent = " " * indent
63
+
64
+ split_line = indent + "+"
65
+ col_lengths.each {|col| split_line << "-" * (col+cellspace*2) + "+" }
66
+
67
+ cellspace = " " * cellspace
68
+
69
+ output_row = proc {|row, orient|
70
+ output << indent + "|"
71
+ row.each_with_index {|c,i|
72
+ output << cellspace
73
+
74
+ str = coerce(c)
75
+
76
+ output << case orient
77
+ when :left then str.ljust(col_lengths[i])
78
+ when :right then str.rjust(col_lengths[i])
79
+ when :center then str.center(col_lengths[i])
80
+ end
81
+ output << cellspace
82
+ output << "|"
83
+ }
84
+ output << "\n"
85
+ }
86
+
87
+ rownr = 0
88
+
89
+ loop do
90
+ output << split_line + "\n"
91
+ output_row.call(header, header_orient)
92
+ output << split_line + "\n"
93
+ if pagebreak_after.nil?
94
+ rows.each {|ar| output_row.call(ar, rows_orient)}
95
+ output << split_line + "\n"
96
+ break
97
+ end
98
+
99
+ rows[rownr,pagebreak_after].each {|ar| output_row.call(ar, rows_orient)}
100
+ output << split_line + "\n"
101
+
102
+ rownr += pagebreak_after
103
+
104
+ break if rownr >= rows.size
105
+
106
+ yield output if block_given?
107
+ end
108
+
109
+ end
110
+ end # module TableFormatter
111
+ end
112
+ end
@@ -0,0 +1,52 @@
1
+ module DBI
2
+ #
3
+ # Represents a Time
4
+ #
5
+ # DEPRECATED: Please use a regular Time or DateTime object.
6
+ class Time
7
+ attr_accessor :hour, :minute, :second
8
+
9
+ private
10
+ # DBI::Time.new(hour = 0, minute = 0, second = 0)
11
+ # DBI::Time.new(Time)
12
+ #
13
+ # Creates and returns a new DBI::Time object. Unlike the Time object
14
+ # in the standard library, accepts an hour, minute and second, or a
15
+ # Time object.
16
+ def initialize(hour=0, minute=0, second=0)
17
+ case hour
18
+ when ::Time
19
+ @hour, @minute, @second = hour.hour, hour.min, hour.sec
20
+ @original_time = hour
21
+ else
22
+ @hour, @minute, @second = hour, minute, second
23
+ end
24
+ end
25
+
26
+ public
27
+
28
+ deprecate :initialize, :public
29
+
30
+ alias :min :minute
31
+ alias :min= :minute=
32
+ alias :sec :second
33
+ alias :sec= :second=
34
+
35
+ # Returns a new Time object based on the hour, minute and second, using
36
+ # the current year, month and day. If a Time object was passed to the
37
+ # constructor, returns that object instead.
38
+ def to_time
39
+ if @original_time
40
+ @original_time
41
+ else
42
+ t = ::Time.now
43
+ ::Time.local(t.year, t.month, t.day, @hour, @minute, @second)
44
+ end
45
+ end
46
+
47
+ # Returns a DBI::Time object as a string in HH:MM:SS format.
48
+ def to_s
49
+ sprintf("%02d:%02d:%02d", @hour, @minute, @second)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,96 @@
1
+ module DBI
2
+ #
3
+ # Represents a Timestamp.
4
+ #
5
+ # DEPRECATED: Please use a regular DateTime object.
6
+ #
7
+ class Timestamp
8
+ attr_accessor :year, :month, :day
9
+ attr_accessor :hour, :minute, :second
10
+ attr_writer :fraction
11
+
12
+ private
13
+ # DBI::Timestamp(year=0,month=0,day=0,hour=0,min=0,sec=0,fraction=nil)
14
+ # DBI::Timestamp(Time)
15
+ # DBI::Timestamp(Date)
16
+ #
17
+ # Creates and returns a new DBI::Timestamp object. This is similar to
18
+ # a Time object in the standard library, but it also contains fractional
19
+ # seconds, expressed in nanoseconds. In addition, the constructor
20
+ # accepts either a Date or Time object.
21
+ def initialize(year=0, month=0, day=0, hour=0, min=0, sec=0, fraction=nil)
22
+ case year
23
+ when ::Time
24
+ @year, @month, @day = year.year, year.month, year.day
25
+ @hour, @minute, @second, @fraction = year.hour, year.min, year.sec, nil
26
+ @original_time = year
27
+ when ::Date
28
+ @year, @month, @day = year.year, year.month, year.day
29
+ @hour, @minute, @second, @fraction = 0, 0, 0, nil
30
+ @original_date = year
31
+ else
32
+ @year, @month, @day = year, month, day
33
+ @hour, @minute, @second, @fraction = hour, min, sec, fraction
34
+ end
35
+ end
36
+
37
+ public
38
+
39
+ deprecate :initialize, :public
40
+
41
+ # Returns true if +timestamp+ has a year, month, day, hour, minute,
42
+ # second and fraction equal to the comparing object.
43
+ #
44
+ # Returns false if the comparison fails for any reason.
45
+ def ==(timestamp)
46
+ @year == timestamp.year and @month == timestamp.month and
47
+ @day == timestamp.day and @hour == timestamp.hour and
48
+ @minute == timestamp.minute and @second == timestamp.second and
49
+ (fraction() == timestamp.fraction)
50
+ rescue
51
+ false
52
+ end
53
+
54
+ # Returns fractional seconds, or 0 if not set.
55
+ def fraction
56
+ @fraction || 0
57
+ end
58
+
59
+ # Aliases
60
+ alias :mon :month
61
+ alias :mon= :month=
62
+ alias :mday :day
63
+ alias :mday= :day=
64
+ alias :min :minute
65
+ alias :min= :minute=
66
+ alias :sec :second
67
+ alias :sec= :second=
68
+
69
+ # Returns a DBI::Timestamp object as a string in YYYY-MM-DD HH:MM:SS
70
+ # format. If a fraction is present, then it is appended in ".FF" format.
71
+ def to_s
72
+ string = sprintf("%04d-%02d-%02d %02d:%02d:%02d",
73
+ @year, @month, @day, @hour, @minute, @second)
74
+
75
+ if @fraction
76
+ fraction = ("%.9f" % (@fraction.to_i / 1e9)).
77
+ to_s[1..-1].gsub(/0{1,8}$/, "")
78
+ string += fraction
79
+ end
80
+
81
+ string
82
+ end
83
+
84
+ # Returns a new Time object based on the year, month and day or, if a
85
+ # Time object was passed to the constructor, returns that object.
86
+ def to_time
87
+ @original_time || ::Time.local(@year, @month, @day, @hour, @minute, @second)
88
+ end
89
+
90
+ # Returns a new Date object based on the year, month and day or, if a
91
+ # Date object was passed to the constructor, returns that object.
92
+ def to_date
93
+ @original_date || ::Date.new(@year, @month, @day)
94
+ end
95
+ end
96
+ end