ironruby-dbi 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,195 @@
1
+ @class = Class.new(DBDConfig.testbase(DBDConfig.current_dbtype)) do
2
+
3
+ def test_empty_query
4
+ ["", " ", "\t"].each do |str|
5
+ [:do, :prepare, :execute, :select_one, :select_all].each do |call|
6
+ assert_raises(DBI::InterfaceError) do
7
+ @dbh.send(call, str)
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ def test_ping
14
+ assert @dbh.ping
15
+ # XXX if it isn't obvious, this should be tested better. Not sure what
16
+ # good behavior is yet.
17
+ end
18
+
19
+ def test_columns
20
+ assert_nothing_raised do
21
+ cols = @dbh.columns("precision_test")
22
+
23
+ assert(cols)
24
+ assert_kind_of(Array, cols)
25
+ assert_equal(4, cols.length)
26
+
27
+ # the first column should always be "text_field" and have the following
28
+ # properties:
29
+ assert_equal("text_field", cols[0]["name"])
30
+ assert(!cols[0]["nullable"])
31
+
32
+ assert_equal(20, cols[0]["precision"])
33
+ # scale can be either nil or 0 for character types.
34
+ case cols[0]["scale"]
35
+ when nil
36
+ assert_equal(nil, cols[0]["scale"])
37
+ when 0
38
+ assert_equal(0, cols[0]["scale"])
39
+ else
40
+ flunk "scale can be either 0 or nil for character types"
41
+ end
42
+
43
+ assert_equal(
44
+ DBI::Type::Varchar.object_id,
45
+ DBI::TypeUtil.type_name_to_module(cols[0]["type_name"]).object_id
46
+ )
47
+
48
+ # the second column should always be "integer_field" and have the following
49
+ # properties:
50
+ assert_equal("integer_field", cols[1]["name"])
51
+ assert(cols[1]["nullable"])
52
+ assert_equal(1, cols[2]["scale"])
53
+ assert_equal(2, cols[2]["precision"])
54
+
55
+ assert_equal(
56
+ DBI::Type::Integer.object_id,
57
+ DBI::TypeUtil.type_name_to_module(cols[1]["type_name"]).object_id
58
+ )
59
+
60
+ # the second column should always be "integer_field" and have the following
61
+ # properties:
62
+ assert_equal("decimal_field", cols[2]["name"])
63
+ assert(cols[2]["nullable"])
64
+ assert_equal(1, cols[2]["scale"])
65
+ assert_equal(2, cols[2]["precision"])
66
+ assert_equal(
67
+ DBI::Type::Decimal.object_id,
68
+ DBI::TypeUtil.type_name_to_module(cols[2]["type_name"]).object_id
69
+ )
70
+
71
+ # the second column should always be "numeric_field" and have the following
72
+ # properties:
73
+ assert_equal("numeric_field", cols[3]["name"])
74
+ assert(cols[3]["nullable"])
75
+ assert_equal(6, cols[3]["scale"])
76
+ assert_equal(30, cols[3]["precision"])
77
+ assert_equal(
78
+ DBI::Type::Decimal.object_id,
79
+ DBI::TypeUtil.type_name_to_module(cols[3]["type_name"]).object_id
80
+ )
81
+
82
+ # finally, we ensure that every column in the array is a ColumnInfo
83
+ # object
84
+ cols.each { |col| assert_kind_of(DBI::ColumnInfo, col) }
85
+ end
86
+ end
87
+
88
+ def test_prepare
89
+ @sth = @dbh.prepare('select * from names')
90
+
91
+ assert @sth
92
+ assert_kind_of DBI::StatementHandle, @sth
93
+
94
+ @sth.finish
95
+ end
96
+
97
+ def test_do
98
+ assert_equal 1, @dbh.do("insert into names (name, age) values (@name, @age)", {:name => "Billy", :age => 21})
99
+
100
+ @sth = @dbh.prepare("select * from names where name = @name")
101
+ @sth.execute :name => "Billy"
102
+
103
+ assert_equal ["Billy", 21], @sth.fetch
104
+ @sth.finish
105
+ end
106
+
107
+ def test_tables
108
+ tables = @dbh.tables.sort
109
+
110
+ # since this is a general test, let's prune the system tables
111
+ # FIXME not so sure if this should be a general test anymore.
112
+ if dbtype == "odbc"
113
+ tables -= [
114
+ "administrable_role_authorizations",
115
+ "applicable_roles",
116
+ "attributes",
117
+ "check_constraint_routine_usage",
118
+ "check_constraints",
119
+ "column_domain_usage",
120
+ "column_privileges",
121
+ "column_udt_usage",
122
+ "columns",
123
+ "constraint_column_usage",
124
+ "constraint_table_usage",
125
+ "data_type_privileges",
126
+ "domain_constraints",
127
+ "domain_udt_usage",
128
+ "domains",
129
+ "element_types",
130
+ "enabled_roles",
131
+ "information_schema_catalog_name",
132
+ "key_column_usage",
133
+ "parameters",
134
+ "referential_constraints",
135
+ "role_column_grants",
136
+ "role_routine_grants",
137
+ "role_table_grants",
138
+ "role_usage_grants",
139
+ "routine_privileges",
140
+ "routines",
141
+ "schemata",
142
+ "sequences",
143
+ "sql_features",
144
+ "sql_implementation_info",
145
+ "sql_languages",
146
+ "sql_packages",
147
+ "sql_parts",
148
+ "sql_sizing",
149
+ "sql_sizing_profiles",
150
+ "table_constraints",
151
+ "table_privileges",
152
+ "tables",
153
+ "triggered_update_columns",
154
+ "triggers",
155
+ "usage_privileges",
156
+ "view_column_usage",
157
+ "view_routine_usage",
158
+ "view_table_usage",
159
+ "views"
160
+ ]
161
+ end
162
+
163
+ case dbtype
164
+ when "postgresql"
165
+ tables.reject! { |x| x =~ /^pg_/ }
166
+ assert_equal %w(array_test bit_test blob_test boolean_test bytea_test db_specific_types_test enum_type_test field_types_test names precision_test time_test timestamp_test view_names), tables
167
+ when 'sqlite3'
168
+ assert_equal %w(bit_test blob_test boolean_test db_specific_types_test field_types_test names names_defined_with_spaces precision_test time_test timestamp_test view_names), tables
169
+ else
170
+ assert_equal %w(bit_test blob_test boolean_test db_specific_types_test field_types_test names precision_test time_test timestamp_test view_names), tables
171
+ end
172
+ end
173
+
174
+ def test_attrs
175
+ # test defaults
176
+ assert @dbh["AutoCommit"] # should be true
177
+
178
+ # test setting
179
+ assert !(@dbh["AutoCommit"] = false)
180
+ assert !@dbh["AutoCommit"]
181
+
182
+ # test committing an outstanding transaction
183
+ @sth = @dbh.prepare("insert into names (name, age) values (@name, @age)")
184
+ @sth.execute(:name => "Billy", :age => 22)
185
+ @sth.finish
186
+
187
+ assert @dbh["AutoCommit"] = true # should commit at this point
188
+
189
+ @sth = @dbh.prepare("select * from names where name = @name")
190
+ @sth.execute(:name => "Billy")
191
+ res = @sth.fetch
192
+ @sth.finish
193
+ assert_equal [ "Billy", 22 ], res
194
+ end
195
+ end