ironruby-dbi 0.1.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.
@@ -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