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.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/ChangeLog +3699 -0
- data/Gemfile +4 -0
- data/LICENSE +25 -0
- data/Rakefile +8 -0
- data/TODO +44 -0
- data/bench/bench.rb +79 -0
- data/bin/dbi +518 -0
- data/bin/test_broken_dbi +37 -0
- data/build/Rakefile.dbi.rb +60 -0
- data/build/rake_task_lib.rb +187 -0
- data/doc/DBD_SPEC.rdoc +88 -0
- data/doc/DBI_SPEC.rdoc +157 -0
- data/doc/homepage/contact.html +62 -0
- data/doc/homepage/development.html +124 -0
- data/doc/homepage/index.html +83 -0
- data/doc/homepage/ruby-dbi.css +91 -0
- data/examples/test1.pl +39 -0
- data/examples/test1.rb +20 -0
- data/examples/xmltest.rb +8 -0
- data/lib/dbd/Mysql.rb +137 -0
- data/lib/dbd/ODBC.rb +89 -0
- data/lib/dbd/Pg.rb +188 -0
- data/lib/dbd/SQLite.rb +97 -0
- data/lib/dbd/SQLite3.rb +124 -0
- data/lib/dbd/mysql/database.rb +405 -0
- data/lib/dbd/mysql/driver.rb +125 -0
- data/lib/dbd/mysql/statement.rb +188 -0
- data/lib/dbd/odbc/database.rb +128 -0
- data/lib/dbd/odbc/driver.rb +38 -0
- data/lib/dbd/odbc/statement.rb +137 -0
- data/lib/dbd/pg/database.rb +516 -0
- data/lib/dbd/pg/exec.rb +47 -0
- data/lib/dbd/pg/statement.rb +160 -0
- data/lib/dbd/pg/tuples.rb +121 -0
- data/lib/dbd/pg/type.rb +209 -0
- data/lib/dbd/sqlite/database.rb +151 -0
- data/lib/dbd/sqlite/statement.rb +125 -0
- data/lib/dbd/sqlite3/database.rb +201 -0
- data/lib/dbd/sqlite3/statement.rb +78 -0
- data/lib/dbi.rb +336 -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 +171 -0
- data/lib/dbi/binary.rb +25 -0
- data/lib/dbi/columninfo.rb +107 -0
- data/lib/dbi/exceptions.rb +65 -0
- data/lib/dbi/handles.rb +49 -0
- data/lib/dbi/handles/database.rb +241 -0
- data/lib/dbi/handles/driver.rb +60 -0
- data/lib/dbi/handles/statement.rb +408 -0
- data/lib/dbi/row.rb +269 -0
- data/lib/dbi/sql.rb +22 -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 +218 -0
- data/lib/dbi/typeutil.rb +109 -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/lib/dbi/version.rb +3 -0
- data/prototypes/types2.rb +237 -0
- data/readme.md +274 -0
- data/setup.rb +1585 -0
- data/test/DBD_TESTS +50 -0
- data/test/TESTING +16 -0
- data/test/dbd/general/test_database.rb +206 -0
- data/test/dbd/general/test_statement.rb +326 -0
- data/test/dbd/general/test_types.rb +296 -0
- data/test/dbd/mysql/base.rb +26 -0
- data/test/dbd/mysql/down.sql +19 -0
- data/test/dbd/mysql/test_blob.rb +18 -0
- data/test/dbd/mysql/test_new_methods.rb +7 -0
- data/test/dbd/mysql/test_patches.rb +111 -0
- data/test/dbd/mysql/up.sql +28 -0
- data/test/dbd/odbc/base.rb +30 -0
- data/test/dbd/odbc/down.sql +19 -0
- data/test/dbd/odbc/test_new_methods.rb +12 -0
- data/test/dbd/odbc/test_ping.rb +10 -0
- data/test/dbd/odbc/test_statement.rb +44 -0
- data/test/dbd/odbc/test_transactions.rb +58 -0
- data/test/dbd/odbc/up.sql +33 -0
- data/test/dbd/postgresql/base.rb +31 -0
- data/test/dbd/postgresql/down.sql +31 -0
- data/test/dbd/postgresql/test_arrays.rb +179 -0
- data/test/dbd/postgresql/test_async.rb +121 -0
- data/test/dbd/postgresql/test_blob.rb +36 -0
- data/test/dbd/postgresql/test_bytea.rb +87 -0
- data/test/dbd/postgresql/test_ping.rb +10 -0
- data/test/dbd/postgresql/test_timestamp.rb +77 -0
- data/test/dbd/postgresql/test_transactions.rb +58 -0
- data/test/dbd/postgresql/testdbipg.rb +307 -0
- data/test/dbd/postgresql/up.sql +60 -0
- data/test/dbd/sqlite/base.rb +32 -0
- data/test/dbd/sqlite/test_database.rb +30 -0
- data/test/dbd/sqlite/test_driver.rb +68 -0
- data/test/dbd/sqlite/test_statement.rb +112 -0
- data/test/dbd/sqlite/up.sql +25 -0
- data/test/dbd/sqlite3/base.rb +32 -0
- data/test/dbd/sqlite3/test_database.rb +77 -0
- data/test/dbd/sqlite3/test_driver.rb +67 -0
- data/test/dbd/sqlite3/test_statement.rb +88 -0
- data/test/dbd/sqlite3/up.sql +33 -0
- data/test/dbi/tc_columninfo.rb +94 -0
- data/test/dbi/tc_date.rb +88 -0
- data/test/dbi/tc_dbi.rb +184 -0
- data/test/dbi/tc_row.rb +256 -0
- data/test/dbi/tc_sqlbind.rb +168 -0
- data/test/dbi/tc_statementhandle.rb +29 -0
- data/test/dbi/tc_time.rb +77 -0
- data/test/dbi/tc_timestamp.rb +142 -0
- data/test/dbi/tc_types.rb +268 -0
- data/test/ts_dbd.rb +131 -0
- data/test/ts_dbi.rb +16 -0
- data/ydbi.gemspec +24 -0
- 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('&', "&")
|
|
65
|
+
str = str.gsub('\'', "'")
|
|
66
|
+
str = str.gsub('"', """)
|
|
67
|
+
str = str.gsub('<', "<")
|
|
68
|
+
str.gsub('>', ">")
|
|
69
|
+
end
|
|
70
|
+
end # class self
|
|
71
|
+
end # module XMLFormatter
|
|
72
|
+
end
|
|
73
|
+
end
|
data/lib/dbi/version.rb
ADDED
|
@@ -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
|