ydbi 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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