dbi 0.4.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/build/Rakefile.dbi.rb +57 -0
  6. data/examples/test1.pl +39 -0
  7. data/examples/test1.rb +20 -0
  8. data/examples/xmltest.rb +8 -0
  9. data/lib/dbi.rb +323 -0
  10. data/lib/dbi/base_classes.rb +12 -0
  11. data/lib/dbi/base_classes/database.rb +135 -0
  12. data/lib/dbi/base_classes/driver.rb +47 -0
  13. data/lib/dbi/base_classes/statement.rb +167 -0
  14. data/lib/dbi/binary.rb +25 -0
  15. data/lib/dbi/columninfo.rb +106 -0
  16. data/lib/dbi/exceptions.rb +65 -0
  17. data/lib/dbi/handles.rb +49 -0
  18. data/lib/dbi/handles/database.rb +211 -0
  19. data/lib/dbi/handles/driver.rb +60 -0
  20. data/lib/dbi/handles/statement.rb +375 -0
  21. data/lib/dbi/row.rb +249 -0
  22. data/lib/dbi/sql.rb +23 -0
  23. data/lib/dbi/sql/preparedstatement.rb +115 -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 +158 -0
  27. data/lib/dbi/typeutil.rb +108 -0
  28. data/lib/dbi/utils.rb +60 -0
  29. data/lib/dbi/utils/date.rb +59 -0
  30. data/lib/dbi/utils/tableformatter.rb +112 -0
  31. data/lib/dbi/utils/time.rb +52 -0
  32. data/lib/dbi/utils/timestamp.rb +96 -0
  33. data/lib/dbi/utils/xmlformatter.rb +73 -0
  34. data/test/dbi/tc_columninfo.rb +94 -0
  35. data/test/dbi/tc_date.rb +88 -0
  36. data/test/dbi/tc_dbi.rb +178 -0
  37. data/test/dbi/tc_row.rb +256 -0
  38. data/test/dbi/tc_sqlbind.rb +168 -0
  39. data/test/dbi/tc_statementhandle.rb +16 -0
  40. data/test/dbi/tc_time.rb +77 -0
  41. data/test/dbi/tc_timestamp.rb +142 -0
  42. data/test/dbi/tc_types.rb +220 -0
  43. data/test/dbi/trace.rb +26 -0
  44. data/test/ts_dbi.rb +15 -0
  45. metadata +108 -0
@@ -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,94 @@
1
+ ############################################################
2
+ # tc_columninfo.rb
3
+ #
4
+ # Test case for the DBI::ColumnInfo class.
5
+ ############################################################
6
+ $LOAD_PATH.unshift(Dir.pwd)
7
+ $LOAD_PATH.unshift(File.dirname(Dir.pwd))
8
+ $LOAD_PATH.unshift("../../lib")
9
+ $LOAD_PATH.unshift("../../lib/dbi")
10
+ $LOAD_PATH.unshift("lib")
11
+
12
+ require "dbi/columninfo"
13
+ require "test/unit"
14
+
15
+ class TC_DBI_ColumnInfo < Test::Unit::TestCase
16
+ def setup
17
+ @colinfo = DBI::ColumnInfo.new(
18
+ "name" => "test",
19
+ "sql_type" => "numeric",
20
+ "type_name" => "test_type_name",
21
+ "precision" => 2,
22
+ "scale" => 2,
23
+ "default" => 100.00,
24
+ "nullable" => false,
25
+ "indexed" => true,
26
+ "primary" => true,
27
+ "unique" => false
28
+ )
29
+ @keys = %w/name sql_type type_name precision scale default nullable
30
+ indexed primary unique
31
+ /
32
+ end
33
+
34
+ def test_constructor
35
+ assert_nothing_raised{ DBI::ColumnInfo.new }
36
+
37
+ assert_nothing_raised do
38
+ DBI::ColumnInfo.new({"foo" => "bar", "baz" => "quux"})
39
+ DBI::ColumnInfo.new({:foo => "bar", :baz => "quux"})
40
+ end
41
+
42
+ assert_raise(TypeError) do
43
+ DBI::ColumnInfo.new({"foo" => "bar", :foo => "quux"})
44
+ end
45
+ end
46
+
47
+ def test_accessors
48
+ assert_nothing_raised do
49
+ @keys.each do |x|
50
+ assert_equal(@colinfo[x], @colinfo[x.to_sym])
51
+ assert_equal(@colinfo.send(x.to_sym), @colinfo[x.to_sym])
52
+ @colinfo[x] = "poop"
53
+ assert_equal("poop", @colinfo[x])
54
+ assert_equal("poop", @colinfo[x.to_sym])
55
+ end
56
+ end
57
+ end
58
+
59
+ def test_precision_basic
60
+ assert_respond_to(@colinfo, :size)
61
+ assert_respond_to(@colinfo, :size=)
62
+ assert_respond_to(@colinfo, :length)
63
+ assert_respond_to(@colinfo, :length=)
64
+ end
65
+
66
+ def test_scale_basic
67
+ assert_respond_to(@colinfo, :decimal_digits)
68
+ assert_respond_to(@colinfo, :decimal_digits=)
69
+ end
70
+
71
+ def test_default_value_basic
72
+ assert_respond_to(@colinfo, :default_value)
73
+ assert_respond_to(@colinfo, :default_value=)
74
+ end
75
+
76
+ def test_unique_basic
77
+ assert_respond_to(@colinfo, :is_unique)
78
+ end
79
+
80
+ def test_keys
81
+ assert_respond_to(@colinfo, :keys)
82
+ assert_equal(@keys.sort, @colinfo.keys.collect { |x| x.to_s }.sort)
83
+ end
84
+
85
+ def test_respond_to_hash_methods
86
+ assert_respond_to(@colinfo, :each)
87
+ assert_respond_to(@colinfo, :empty?)
88
+ assert_respond_to(@colinfo, :has_key?)
89
+ end
90
+
91
+ def teardown
92
+ @colinfo = nil
93
+ end
94
+ end
@@ -0,0 +1,88 @@
1
+ ##############################################################################
2
+ # tc_date.rb
3
+ #
4
+ # Test case for the DBI::Date class (currently) located in the utils.rb file.
5
+ ##############################################################################
6
+ $LOAD_PATH.unshift(Dir.pwd)
7
+ $LOAD_PATH.unshift(File.dirname(Dir.pwd))
8
+ $LOAD_PATH.unshift("../../lib")
9
+ $LOAD_PATH.unshift("../../lib/dbi")
10
+ $LOAD_PATH.unshift("lib")
11
+
12
+ require 'date'
13
+ require 'dbi'
14
+ require 'test/unit'
15
+
16
+ Deprecate.set_action(proc { })
17
+
18
+ class TC_DBI_Date < Test::Unit::TestCase
19
+ def setup
20
+ @date = Date.new
21
+ @time = Time.now
22
+ @dbi_date = DBI::Date.new
23
+ end
24
+
25
+ def test_constructor
26
+ assert_nothing_raised{ DBI::Date.new(2006) }
27
+ assert_nothing_raised{ DBI::Date.new(2006, 1) }
28
+ assert_nothing_raised{ DBI::Date.new(2006, 1, 20) }
29
+ assert_nothing_raised{ DBI::Date.new(Date.new) }
30
+ assert_nothing_raised{ DBI::Date.new(Time.now) }
31
+ end
32
+
33
+ def test_year
34
+ assert_respond_to(@dbi_date, :year)
35
+ assert_respond_to(@dbi_date, :year=)
36
+ assert_equal(0, @dbi_date.year)
37
+ end
38
+
39
+ def test_month
40
+ assert_respond_to(@dbi_date, :month)
41
+ assert_respond_to(@dbi_date, :month=)
42
+ end
43
+
44
+ # An alias for :month, :month=
45
+ def test_mon
46
+ assert_respond_to(@dbi_date, :mon)
47
+ assert_respond_to(@dbi_date, :mon=)
48
+ assert_equal(0, @dbi_date.mon)
49
+ end
50
+
51
+ def test_day
52
+ assert_respond_to(@dbi_date, :day)
53
+ assert_respond_to(@dbi_date, :day=)
54
+ assert_equal(0, @dbi_date.day)
55
+ end
56
+
57
+ # An alias for :day, :day=
58
+ def test_mday
59
+ assert_respond_to(@dbi_date, :mday)
60
+ assert_respond_to(@dbi_date, :mday=)
61
+ end
62
+
63
+ def test_to_time
64
+ assert_respond_to(@dbi_date, :to_time)
65
+ assert_equal(@time, DBI::Date.new(@time).to_time)
66
+ assert_equal(@time.object_id, DBI::Date.new(@time).to_time.object_id)
67
+ end
68
+
69
+ def test_to_date
70
+ assert_respond_to(@dbi_date, :to_date)
71
+ assert_equal(@date, DBI::Date.new(@date).to_date)
72
+ assert_equal(@date.object_id, DBI::Date.new(@date).to_date.object_id)
73
+ end
74
+
75
+ # We test .to_s because it has an explicit implementation
76
+ def test_to_s
77
+ assert_respond_to(@dbi_date, :to_s)
78
+ assert_nothing_raised{ @dbi_date.to_s }
79
+ assert_kind_of(String, @dbi_date.to_s)
80
+ assert_equal("0000-00-00", @dbi_date.to_s)
81
+ end
82
+
83
+ def teardown
84
+ @date = nil
85
+ @time = nil
86
+ @dbi_date = nil
87
+ end
88
+ end