ydbi 0.5.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 (122) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/ChangeLog +3699 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +25 -0
  6. data/Rakefile +8 -0
  7. data/TODO +44 -0
  8. data/bench/bench.rb +79 -0
  9. data/bin/dbi +518 -0
  10. data/bin/test_broken_dbi +37 -0
  11. data/build/Rakefile.dbi.rb +60 -0
  12. data/build/rake_task_lib.rb +187 -0
  13. data/doc/DBD_SPEC.rdoc +88 -0
  14. data/doc/DBI_SPEC.rdoc +157 -0
  15. data/doc/homepage/contact.html +62 -0
  16. data/doc/homepage/development.html +124 -0
  17. data/doc/homepage/index.html +83 -0
  18. data/doc/homepage/ruby-dbi.css +91 -0
  19. data/examples/test1.pl +39 -0
  20. data/examples/test1.rb +20 -0
  21. data/examples/xmltest.rb +8 -0
  22. data/lib/dbd/Mysql.rb +137 -0
  23. data/lib/dbd/ODBC.rb +89 -0
  24. data/lib/dbd/Pg.rb +188 -0
  25. data/lib/dbd/SQLite.rb +97 -0
  26. data/lib/dbd/SQLite3.rb +124 -0
  27. data/lib/dbd/mysql/database.rb +405 -0
  28. data/lib/dbd/mysql/driver.rb +125 -0
  29. data/lib/dbd/mysql/statement.rb +188 -0
  30. data/lib/dbd/odbc/database.rb +128 -0
  31. data/lib/dbd/odbc/driver.rb +38 -0
  32. data/lib/dbd/odbc/statement.rb +137 -0
  33. data/lib/dbd/pg/database.rb +516 -0
  34. data/lib/dbd/pg/exec.rb +47 -0
  35. data/lib/dbd/pg/statement.rb +160 -0
  36. data/lib/dbd/pg/tuples.rb +121 -0
  37. data/lib/dbd/pg/type.rb +209 -0
  38. data/lib/dbd/sqlite/database.rb +151 -0
  39. data/lib/dbd/sqlite/statement.rb +125 -0
  40. data/lib/dbd/sqlite3/database.rb +201 -0
  41. data/lib/dbd/sqlite3/statement.rb +78 -0
  42. data/lib/dbi.rb +336 -0
  43. data/lib/dbi/base_classes.rb +12 -0
  44. data/lib/dbi/base_classes/database.rb +135 -0
  45. data/lib/dbi/base_classes/driver.rb +47 -0
  46. data/lib/dbi/base_classes/statement.rb +171 -0
  47. data/lib/dbi/binary.rb +25 -0
  48. data/lib/dbi/columninfo.rb +107 -0
  49. data/lib/dbi/exceptions.rb +65 -0
  50. data/lib/dbi/handles.rb +49 -0
  51. data/lib/dbi/handles/database.rb +241 -0
  52. data/lib/dbi/handles/driver.rb +60 -0
  53. data/lib/dbi/handles/statement.rb +408 -0
  54. data/lib/dbi/row.rb +269 -0
  55. data/lib/dbi/sql.rb +22 -0
  56. data/lib/dbi/sql/preparedstatement.rb +115 -0
  57. data/lib/dbi/sql_type_constants.rb +75 -0
  58. data/lib/dbi/trace.rb +91 -0
  59. data/lib/dbi/types.rb +218 -0
  60. data/lib/dbi/typeutil.rb +109 -0
  61. data/lib/dbi/utils.rb +60 -0
  62. data/lib/dbi/utils/date.rb +59 -0
  63. data/lib/dbi/utils/tableformatter.rb +112 -0
  64. data/lib/dbi/utils/time.rb +52 -0
  65. data/lib/dbi/utils/timestamp.rb +96 -0
  66. data/lib/dbi/utils/xmlformatter.rb +73 -0
  67. data/lib/dbi/version.rb +3 -0
  68. data/prototypes/types2.rb +237 -0
  69. data/readme.md +274 -0
  70. data/setup.rb +1585 -0
  71. data/test/DBD_TESTS +50 -0
  72. data/test/TESTING +16 -0
  73. data/test/dbd/general/test_database.rb +206 -0
  74. data/test/dbd/general/test_statement.rb +326 -0
  75. data/test/dbd/general/test_types.rb +296 -0
  76. data/test/dbd/mysql/base.rb +26 -0
  77. data/test/dbd/mysql/down.sql +19 -0
  78. data/test/dbd/mysql/test_blob.rb +18 -0
  79. data/test/dbd/mysql/test_new_methods.rb +7 -0
  80. data/test/dbd/mysql/test_patches.rb +111 -0
  81. data/test/dbd/mysql/up.sql +28 -0
  82. data/test/dbd/odbc/base.rb +30 -0
  83. data/test/dbd/odbc/down.sql +19 -0
  84. data/test/dbd/odbc/test_new_methods.rb +12 -0
  85. data/test/dbd/odbc/test_ping.rb +10 -0
  86. data/test/dbd/odbc/test_statement.rb +44 -0
  87. data/test/dbd/odbc/test_transactions.rb +58 -0
  88. data/test/dbd/odbc/up.sql +33 -0
  89. data/test/dbd/postgresql/base.rb +31 -0
  90. data/test/dbd/postgresql/down.sql +31 -0
  91. data/test/dbd/postgresql/test_arrays.rb +179 -0
  92. data/test/dbd/postgresql/test_async.rb +121 -0
  93. data/test/dbd/postgresql/test_blob.rb +36 -0
  94. data/test/dbd/postgresql/test_bytea.rb +87 -0
  95. data/test/dbd/postgresql/test_ping.rb +10 -0
  96. data/test/dbd/postgresql/test_timestamp.rb +77 -0
  97. data/test/dbd/postgresql/test_transactions.rb +58 -0
  98. data/test/dbd/postgresql/testdbipg.rb +307 -0
  99. data/test/dbd/postgresql/up.sql +60 -0
  100. data/test/dbd/sqlite/base.rb +32 -0
  101. data/test/dbd/sqlite/test_database.rb +30 -0
  102. data/test/dbd/sqlite/test_driver.rb +68 -0
  103. data/test/dbd/sqlite/test_statement.rb +112 -0
  104. data/test/dbd/sqlite/up.sql +25 -0
  105. data/test/dbd/sqlite3/base.rb +32 -0
  106. data/test/dbd/sqlite3/test_database.rb +77 -0
  107. data/test/dbd/sqlite3/test_driver.rb +67 -0
  108. data/test/dbd/sqlite3/test_statement.rb +88 -0
  109. data/test/dbd/sqlite3/up.sql +33 -0
  110. data/test/dbi/tc_columninfo.rb +94 -0
  111. data/test/dbi/tc_date.rb +88 -0
  112. data/test/dbi/tc_dbi.rb +184 -0
  113. data/test/dbi/tc_row.rb +256 -0
  114. data/test/dbi/tc_sqlbind.rb +168 -0
  115. data/test/dbi/tc_statementhandle.rb +29 -0
  116. data/test/dbi/tc_time.rb +77 -0
  117. data/test/dbi/tc_timestamp.rb +142 -0
  118. data/test/dbi/tc_types.rb +268 -0
  119. data/test/ts_dbd.rb +131 -0
  120. data/test/ts_dbi.rb +16 -0
  121. data/ydbi.gemspec +24 -0
  122. metadata +224 -0
@@ -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
@@ -0,0 +1,73 @@
1
+ module DBI
2
+ module Utils
3
+ # Formats results in XML.
4
+ module XMLFormatter
5
+ # Generate XML for a row. The column names will surround the the values as tags.
6
+ #
7
+ # * +dbrow+: the array of the result row.
8
+ # * +rowtag+: the name of the tag that encapsulates a row.
9
+ # * +output+: Object that responds to `<<`.
10
+ #
11
+ def self.row(dbrow, rowtag="row", output=STDOUT)
12
+ #XMLFormatter.extended_row(dbrow, "row", [],
13
+ output << "<#{rowtag}>\n"
14
+ dbrow.each_with_name do |val, name|
15
+ output << " <#{name}>" + textconv(val) + "</#{name}>\n"
16
+ end
17
+ output << "</#{rowtag}>\n"
18
+ end
19
+
20
+ # good lord, what a mess.
21
+ #
22
+ # nil in cols_as_tag, means "all columns expect those listed in cols_in_row_tag"
23
+ # add_row_tag_attrs are additional attributes which are inserted into the row-tag
24
+ def self.extended_row(dbrow, rowtag="row", cols_in_row_tag=[], cols_as_tag=nil, add_row_tag_attrs={}, output=STDOUT)
25
+ if cols_as_tag.nil?
26
+ cols_as_tag = dbrow.column_names - cols_in_row_tag
27
+ end
28
+
29
+ output << "<#{rowtag}"
30
+ add_row_tag_attrs.each do |key, val|
31
+ # TODO: use textconv ? " substitution?
32
+ output << %{ #{key}="#{textconv(val)}"}
33
+ end
34
+ cols_in_row_tag.each do |key|
35
+ # TODO: use textconv ? " substitution?
36
+ output << %{ #{key}="#{dbrow[key]}"}
37
+ end
38
+ output << ">\n"
39
+
40
+ cols_as_tag.each do |key|
41
+ output << " <#{key}>" + textconv(dbrow[key]) + "</#{key}>\n"
42
+ end
43
+ output << "</#{rowtag}>\n"
44
+ end
45
+
46
+ # generate a full XML representation of the table.
47
+ #
48
+ # Arguments and output are similar to #row, with the exception of
49
+ # +roottag+, which is a container for the individual row tags.
50
+ #
51
+ def self.table(rows, roottag = "rows", rowtag = "row", output=STDOUT)
52
+ output << '<?xml version="1.0" encoding="UTF-8" ?>'
53
+ output << "\n<#{roottag}>\n"
54
+ rows.each do |row|
55
+ row(row, rowtag, output)
56
+ end
57
+ output << "</#{roottag}>\n"
58
+ end
59
+
60
+ class << self
61
+ private
62
+ # Your standard XML entity conversions.
63
+ def textconv(str)
64
+ str = str.to_s.gsub('&', "&#38;")
65
+ str = str.gsub('\'', "&#39;")
66
+ str = str.gsub('"', "&#34;")
67
+ str = str.gsub('<', "&#60;")
68
+ str.gsub('>', "&#62;")
69
+ end
70
+ end # class self
71
+ end # module XMLFormatter
72
+ end
73
+ end
@@ -0,0 +1,3 @@
1
+ module DBI
2
+ VERSION = "0.5.0"
3
+ end
@@ -0,0 +1,237 @@
1
+ require 'date'
2
+ require 'time'
3
+
4
+ #
5
+ # General example, these would be types that would exist in DBI proper
6
+ #
7
+
8
+ module DBI
9
+ class DBI::Type
10
+ def self.parse(obj, type=nil, dbh=nil)
11
+ if type
12
+ sym = ( "to_" + type.to_s ).to_sym
13
+ begin
14
+ return self.__send__(sym, obj)
15
+ rescue ::NoMethodError
16
+ self.to_type(obj)
17
+ end
18
+ else
19
+ return self.to_type(obj)
20
+ end
21
+ end
22
+
23
+ def self.coerce(obj, type=nil, dbh=nil)
24
+ if type
25
+ sym = ( "from_" + type.to_s ).to_sym
26
+ begin
27
+ return self.__send__(sym, obj)
28
+ rescue ::NoMethodError
29
+ self.from_type(obj)
30
+ end
31
+ else
32
+ return self.from_type(obj)
33
+ end
34
+ end
35
+
36
+ def self.from_type(obj)
37
+ obj.to_s rescue obj.to_str rescue obj
38
+ end
39
+
40
+ def self.to_type(obj)
41
+ obj
42
+ end
43
+ end
44
+
45
+ class DBI::Type::Null < DBI::Type
46
+ def self.to_type(obj)
47
+ return obj unless obj
48
+ return nil if obj.to_s.match(/^null$/i)
49
+ return obj
50
+ end
51
+
52
+ def self.from_type(obj)
53
+ obj
54
+ end
55
+ end
56
+
57
+ class DBI::Type::Integer < DBI::Type::Null
58
+ def self.parse(obj)
59
+ obj = super
60
+ return obj unless obj
61
+ return obj.to_i if obj.respond_to? :to_i
62
+ return obj
63
+ end
64
+ end
65
+
66
+ class DBI::Type::Timestamp < DBI::Type::Null
67
+ def self.to_type(obj)
68
+ obj = super
69
+ return obj unless obj
70
+
71
+ case obj
72
+ when ::DateTime
73
+ return obj
74
+ when ::Date
75
+ return ::DateTime.strptime(obj.to_s, "%Y-%m-%d")
76
+ when ::Time
77
+ return ::DateTime.parse(obj.to_s)
78
+ when ::Integer
79
+ return ::DateTime.parse(::Time.at(obj).to_s)
80
+ else
81
+ return ::DateTime.parse(obj.to_s) if obj.respond_to? :to_s
82
+ return ::DateTime.parse(obj.to_str) if obj.respond_to? :to_str
83
+ return obj
84
+ end
85
+ end
86
+
87
+ def self.from_type(obj)
88
+ obj = super
89
+ return obj unless obj
90
+
91
+ case obj
92
+ when ::DateTime
93
+ return obj.to_s # produces ISO8601
94
+ when ::Time
95
+ return obj.iso8601
96
+ when ::Integer
97
+ return ::Time.at(obj).iso8601
98
+ else
99
+ return obj
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ module DBI::DBD
106
+ class Pg
107
+
108
+ #
109
+ # during connect time, after DatabaseHandle initialization, the hash
110
+ # that DatabaseHandle#type_map provides would be tweaked to take
111
+ # advantage of the available date formats.
112
+ #
113
+ # See 'PgDatabaseHandle' below for a mock.
114
+ #
115
+
116
+ class Type
117
+ class Timestamp < DBI::Type::Timestamp
118
+ def self.from_dmy(obj)
119
+ return obj if DBI::Type::Null.parse(obj).nil?
120
+
121
+ case obj
122
+ when ::DateTime, ::Time
123
+ obj.strftime("%d/%m/%Y %H:%M:%S")
124
+ when ::Integer
125
+ ::Time.at(obj).strftime("%d/%m/%Y %H:%M:%S")
126
+ else
127
+ # punt... this will actually try the baseline
128
+ # conversion at this point
129
+ raise "Crap!"
130
+ end
131
+ end
132
+
133
+ def self.to_dmy(obj)
134
+ return obj if DBI::Type::Null.parse(obj).nil?
135
+
136
+ # realistically all there needs to be is a check for the
137
+ # type ruby-pg typically returns and string, but to be
138
+ # complete I'm showing how it could be done if the type was
139
+ # less clear.
140
+
141
+ case obj
142
+ when ::DateTime
143
+ return obj
144
+ when ::Time
145
+ return ::DateTime.parse(obj.to_s)
146
+ else
147
+ return ::DateTime.strptime(obj, "%d/%m/%Y %H:%M:%S")
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ #
156
+ # this is just used to emulate the methods a DatabaseHandle would have to
157
+ # faciliate this.. certainly not a full (or correct) mirroring of the DBI API.
158
+ #
159
+ class DatabaseHandle
160
+
161
+ attr_accessor :columns
162
+
163
+ def outbound_type_map
164
+ {
165
+ 'timestamp' => [DBI::Type::Timestamp]
166
+ }
167
+ end
168
+
169
+ def inbound_type_map
170
+ {
171
+ ::DateTime => [DBI::Type::Timestamp],
172
+ ::Time => [DBI::Type::Timestamp]
173
+ }
174
+ end
175
+
176
+ # humor me while I completely break DBI for the sake of brevity..
177
+ def execute(*bindvars)
178
+ bindvars.collect do |var|
179
+ type_info = inbound_type_map[var.class]
180
+ type_info[0].coerce(var, type_info[1], self)
181
+ end
182
+ end
183
+
184
+ def fetch(*bindvars)
185
+ ret = []
186
+
187
+ bindvars.each_with_index do |var, i|
188
+ type_info = outbound_type_map[columns[i]]
189
+ ret.push type_info[0].parse(var, type_info[1], self)
190
+ end
191
+
192
+ return ret
193
+ end
194
+ end
195
+
196
+ class PgDatabaseHandle < DatabaseHandle
197
+ def outbound_type_map
198
+ {
199
+ 'timestamp' => [DBI::DBD::Pg::Type::Timestamp, :dmy]
200
+ }
201
+ end
202
+
203
+ def inbound_type_map
204
+ {
205
+ ::DateTime => [DBI::DBD::Pg::Type::Timestamp, :dmy],
206
+ ::Time => [DBI::DBD::Pg::Type::Timestamp, :dmy]
207
+ }
208
+ end
209
+ end
210
+
211
+ # ok! now for the functional example:
212
+
213
+ if __FILE__ == $0
214
+
215
+ dbh = DatabaseHandle.new
216
+ dbh.columns = %w(timestamp timestamp)
217
+ # this would go TO the database..
218
+ p dbh.execute(DateTime.now, Time.now)
219
+ # this would come FROM the database...
220
+ p dbh.fetch(Time.now.iso8601, DateTime.now.to_s)
221
+
222
+ # now the Pg example:
223
+ dbh = PgDatabaseHandle.new
224
+ dbh.columns = %w(timestamp timestamp)
225
+
226
+ # this would go TO the database..
227
+ p dbh.execute(DateTime.now, Time.now)
228
+ # this would come FROM the database...
229
+ p dbh.fetch(Time.now.strftime("%d/%m/%Y %H:%M:%S"), DateTime.now.strftime("%d/%m/%Y %H:%M:%S"))
230
+
231
+ # this should fail appropriately
232
+ begin
233
+ dbh.fetch(Time.now.iso8601, DateTime.now.to_s)
234
+ rescue Exception
235
+ puts "this failed like it should"
236
+ end
237
+ end