ydbi 0.5.0 → 0.5.1

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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog +4 -0
  3. data/build/Rakefile.dbi.rb +4 -4
  4. data/lib/dbi/version.rb +1 -1
  5. data/test/dbi/tc_dbi.rb +1 -1
  6. metadata +9 -124
  7. data/.gitignore +0 -6
  8. data/Gemfile +0 -4
  9. data/Rakefile +0 -8
  10. data/TODO +0 -44
  11. data/bench/bench.rb +0 -79
  12. data/build/rake_task_lib.rb +0 -187
  13. data/doc/DBD_SPEC.rdoc +0 -88
  14. data/doc/DBI_SPEC.rdoc +0 -157
  15. data/doc/homepage/contact.html +0 -62
  16. data/doc/homepage/development.html +0 -124
  17. data/doc/homepage/index.html +0 -83
  18. data/doc/homepage/ruby-dbi.css +0 -91
  19. data/lib/dbd/Mysql.rb +0 -137
  20. data/lib/dbd/ODBC.rb +0 -89
  21. data/lib/dbd/Pg.rb +0 -188
  22. data/lib/dbd/SQLite.rb +0 -97
  23. data/lib/dbd/SQLite3.rb +0 -124
  24. data/lib/dbd/mysql/database.rb +0 -405
  25. data/lib/dbd/mysql/driver.rb +0 -125
  26. data/lib/dbd/mysql/statement.rb +0 -188
  27. data/lib/dbd/odbc/database.rb +0 -128
  28. data/lib/dbd/odbc/driver.rb +0 -38
  29. data/lib/dbd/odbc/statement.rb +0 -137
  30. data/lib/dbd/pg/database.rb +0 -516
  31. data/lib/dbd/pg/exec.rb +0 -47
  32. data/lib/dbd/pg/statement.rb +0 -160
  33. data/lib/dbd/pg/tuples.rb +0 -121
  34. data/lib/dbd/pg/type.rb +0 -209
  35. data/lib/dbd/sqlite/database.rb +0 -151
  36. data/lib/dbd/sqlite/statement.rb +0 -125
  37. data/lib/dbd/sqlite3/database.rb +0 -201
  38. data/lib/dbd/sqlite3/statement.rb +0 -78
  39. data/prototypes/types2.rb +0 -237
  40. data/setup.rb +0 -1585
  41. data/test/DBD_TESTS +0 -50
  42. data/test/TESTING +0 -16
  43. data/test/dbd/general/test_database.rb +0 -206
  44. data/test/dbd/general/test_statement.rb +0 -326
  45. data/test/dbd/general/test_types.rb +0 -296
  46. data/test/dbd/mysql/base.rb +0 -26
  47. data/test/dbd/mysql/down.sql +0 -19
  48. data/test/dbd/mysql/test_blob.rb +0 -18
  49. data/test/dbd/mysql/test_new_methods.rb +0 -7
  50. data/test/dbd/mysql/test_patches.rb +0 -111
  51. data/test/dbd/mysql/up.sql +0 -28
  52. data/test/dbd/odbc/base.rb +0 -30
  53. data/test/dbd/odbc/down.sql +0 -19
  54. data/test/dbd/odbc/test_new_methods.rb +0 -12
  55. data/test/dbd/odbc/test_ping.rb +0 -10
  56. data/test/dbd/odbc/test_statement.rb +0 -44
  57. data/test/dbd/odbc/test_transactions.rb +0 -58
  58. data/test/dbd/odbc/up.sql +0 -33
  59. data/test/dbd/postgresql/base.rb +0 -31
  60. data/test/dbd/postgresql/down.sql +0 -31
  61. data/test/dbd/postgresql/test_arrays.rb +0 -179
  62. data/test/dbd/postgresql/test_async.rb +0 -121
  63. data/test/dbd/postgresql/test_blob.rb +0 -36
  64. data/test/dbd/postgresql/test_bytea.rb +0 -87
  65. data/test/dbd/postgresql/test_ping.rb +0 -10
  66. data/test/dbd/postgresql/test_timestamp.rb +0 -77
  67. data/test/dbd/postgresql/test_transactions.rb +0 -58
  68. data/test/dbd/postgresql/testdbipg.rb +0 -307
  69. data/test/dbd/postgresql/up.sql +0 -60
  70. data/test/dbd/sqlite/base.rb +0 -32
  71. data/test/dbd/sqlite/test_database.rb +0 -30
  72. data/test/dbd/sqlite/test_driver.rb +0 -68
  73. data/test/dbd/sqlite/test_statement.rb +0 -112
  74. data/test/dbd/sqlite/up.sql +0 -25
  75. data/test/dbd/sqlite3/base.rb +0 -32
  76. data/test/dbd/sqlite3/test_database.rb +0 -77
  77. data/test/dbd/sqlite3/test_driver.rb +0 -67
  78. data/test/dbd/sqlite3/test_statement.rb +0 -88
  79. data/test/dbd/sqlite3/up.sql +0 -33
  80. data/test/ts_dbd.rb +0 -131
  81. data/ydbi.gemspec +0 -24
@@ -1,160 +0,0 @@
1
- #
2
- # See DBI::BaseStatement, and DBI::DBD::Pg::Tuples.
3
- #
4
- #--
5
- # Peculiar Statement responsibilities:
6
- # - Translate dbi params (?, ?, ...) to Pg params ($1, $2, ...)
7
- # - Translate DBI::Binary objects to Pg large objects (lo_*)
8
-
9
- class DBI::DBD::Pg::Statement < DBI::BaseStatement
10
-
11
- PG_STMT_NAME_PREFIX = 'ruby-dbi:Pg:'
12
-
13
- def initialize(db, sql)
14
- super(db)
15
- @db = db
16
- @sql = sql
17
- @stmt_name = PG_STMT_NAME_PREFIX + self.object_id.to_s + Time.now.to_f.to_s
18
- @result = nil
19
- @bindvars = []
20
- @prepared = false
21
- rescue PGError => err
22
- raise DBI::ProgrammingError.new(err.message)
23
- end
24
-
25
- def bind_param(index, value, options)
26
- @bindvars[index-1] = value
27
- end
28
-
29
- #
30
- # See DBI::BaseDatabase#execute.
31
- #
32
- # This method will make use of PostgreSQL's native BLOB support if
33
- # DBI::Binary objects are passed in.
34
- #
35
- def execute
36
- # replace DBI::Binary object by oid returned by lo_import
37
- @bindvars.collect! do |var|
38
- if var.is_a? DBI::Binary then
39
- oid = @db.__blob_create(PGconn::INV_WRITE)
40
- @db.__blob_write(oid, var.to_s)
41
- oid
42
- else
43
- var
44
- end
45
- end
46
-
47
- internal_prepare
48
-
49
- if not @db['AutoCommit'] then
50
- # if not SQL.query?(boundsql) and not @db['AutoCommit'] then
51
- @db.start_transaction unless @db.in_transaction?
52
- end
53
-
54
- if @db["pg_native_binding"]
55
- pg_result = @db._exec_prepared(@stmt_name, *@bindvars)
56
- else
57
- pg_result = @db._exec_prepared(@stmt_name)
58
- end
59
-
60
- @result = DBI::DBD::Pg::Tuples.new(@db, pg_result)
61
- rescue PGError, RuntimeError => err
62
- raise DBI::ProgrammingError.new(err.message)
63
- end
64
-
65
- def fetch
66
- @result.fetchrow
67
- end
68
-
69
- def fetch_scroll(direction, offset)
70
- @result.fetch_scroll(direction, offset)
71
- end
72
-
73
- def finish
74
- internal_finish
75
- @result = nil
76
- @db = nil
77
- end
78
-
79
- #
80
- # See DBI::DBD::Pg::Tuples#column_info.
81
- #
82
- def column_info
83
- @result.column_info
84
- end
85
-
86
- def rows
87
- if @result
88
- @result.rows_affected
89
- else
90
- nil
91
- end
92
- end
93
-
94
- #
95
- # Attributes:
96
- #
97
- # If +pg_row_count+ is requested and the statement has already executed,
98
- # postgres will return what it believes is the row count.
99
- #
100
- def [](attr)
101
- case attr
102
- when 'pg_row_count'
103
- if @result
104
- @result.row_count
105
- else
106
- nil
107
- end
108
- else
109
- @attr[attr]
110
- end
111
- end
112
-
113
- private
114
-
115
- #
116
- # A native binding helper.
117
- #
118
- class DummyQuoter
119
- # dummy to substitute ?-style parameter markers by :1 :2 etc.
120
- def quote(str)
121
- str
122
- end
123
- end
124
-
125
- # finish the statement at a lower level
126
- def internal_finish
127
- @result.finish if @result
128
- @db._exec("DEALLOCATE \"#{@stmt_name}\"") if @prepared rescue nil
129
- end
130
-
131
- # prepare the statement at a lower level.
132
- def internal_prepare
133
- if @db["pg_native_binding"]
134
- unless @prepared
135
- @stmt = @db._prepare(@stmt_name, translate_param_markers(@sql))
136
- end
137
- else
138
- internal_finish
139
- @stmt = @db._prepare(@stmt_name, DBI::SQL::PreparedStatement.new(DBI::DBD::Pg, @sql).bind(@bindvars))
140
- end
141
- @prepared = true
142
- end
143
-
144
- # Prepare the given SQL statement, returning its PostgreSQL string
145
- # handle. ?-style parameters are translated to $1, $2, etc.
146
- #--
147
- # TESTME do ?::TYPE qualifers work?
148
- # FIXME: DBI ought to supply a generic param converter, e.g.:
149
- # sql = DBI::Utils::convert_placeholders(sql) do |i|
150
- # '$' + i.to_s
151
- # end
152
- def translate_param_markers(sql)
153
- translator = DBI::SQL::PreparedStatement.new(DummyQuoter.new, sql)
154
- if translator.unbound.size > 0
155
- arr = (1..(translator.unbound.size)).collect{|i| "$#{i}"}
156
- sql = translator.bind( arr )
157
- end
158
- sql
159
- end
160
- end # Statement
data/lib/dbd/pg/tuples.rb DELETED
@@ -1,121 +0,0 @@
1
- #
2
- # Tuples is a class to represent result sets.
3
- #
4
- # Many of these methods are extremely similar to the methods that deal with
5
- # result sets in DBI::BaseStatement and are wrapped by the StatementHandle.
6
- # Unless you plan on working on this driver, these methods should never be
7
- # called directly.
8
- #
9
- class DBI::DBD::Pg::Tuples
10
-
11
- def initialize(db, pg_result)
12
- @db = db
13
- @pg_result = pg_result
14
- @index = -1
15
- @row = []
16
- end
17
-
18
- # See DBI::BaseStatement#column_info. Additional attributes:
19
- #
20
- # * array_of_type: True if this is actually an array of this type. In this
21
- # case, +dbi_type+ will be the type authority for conversion.
22
- #
23
- def column_info
24
- a = []
25
- 0.upto(@pg_result.num_fields-1) do |i|
26
- str = @pg_result.fname(i)
27
-
28
- typeinfo = nil
29
-
30
- begin
31
- typmod = @pg_result.fmod(i)
32
- rescue
33
- end
34
-
35
- if typmod and typ = @pg_result.ftype(i)
36
- res = @db._exec("select format_type(#{typ}, #{typmod})")
37
- typeinfo = DBI::DBD::Pg.parse_type(res[0].values[0])
38
- end
39
-
40
- map = @db.type_map[@pg_result.ftype(i)] || { }
41
- h = { "name" => str }.merge(map)
42
-
43
- if typeinfo
44
- h["precision"] = typeinfo[:size]
45
- h["scale"] = typeinfo[:decimal]
46
- h["type"] = typeinfo[:type]
47
- h["array_of_type"] = typeinfo[:array]
48
-
49
- if typeinfo[:array]
50
- h['dbi_type'] =
51
- DBI::DBD::Pg::Type::Array.new(
52
- DBI::TypeUtil.type_name_to_module(typeinfo[:type])
53
- )
54
- end
55
- end
56
-
57
- a.push h
58
- end
59
-
60
- return a
61
- end
62
-
63
- def fetchrow
64
- @index += 1
65
- if @index < @pg_result.num_tuples && @index >= 0
66
- @row = Array.new
67
- 0.upto(@pg_result.num_fields-1) do |x|
68
- @row.push(@pg_result.getvalue(@index, x))
69
- end
70
- @row
71
- else
72
- nil
73
- end
74
- end
75
-
76
- #
77
- # Just don't use this method. It'll be fixed soon.
78
- #
79
- def fetch_scroll(direction, offset)
80
- # Exact semantics aren't too closely defined. I attempted to follow the DBI:Mysql example.
81
- case direction
82
- when SQL_FETCH_NEXT
83
- # Nothing special to do, besides the fetchrow
84
- when SQL_FETCH_PRIOR
85
- @index -= 2
86
- when SQL_FETCH_FIRST
87
- @index = -1
88
- when SQL_FETCH_LAST
89
- @index = @pg_result.num_tuples - 2
90
- when SQL_FETCH_ABSOLUTE
91
- # Note: if you go "out of range", all fetches will give nil until you get back
92
- # into range, this doesn't raise an error.
93
- @index = offset-1
94
- when SQL_FETCH_RELATIVE
95
- # Note: if you go "out of range", all fetches will give nil until you get back
96
- # into range, this doesn't raise an error.
97
- @index += offset - 1
98
- else
99
- raise NotSupportedError
100
- end
101
- self.fetchrow
102
- end
103
-
104
- #
105
- # The number of rows returned.
106
- #
107
- def row_count
108
- @pg_result.num_tuples
109
- end
110
-
111
- #
112
- # The row processed count. This is analogue to DBI::StatementHandle#rows.
113
- #
114
- def rows_affected
115
- @pg_result.cmdtuples
116
- end
117
-
118
- def finish
119
- @pg_result.clear
120
- end
121
- end # Tuples
data/lib/dbd/pg/type.rb DELETED
@@ -1,209 +0,0 @@
1
- #
2
- # Type Management for PostgreSQL-specific types.
3
- #
4
- # See DBI::Type and DBI::TypeUtil for more information.
5
- #
6
- module DBI::DBD::Pg::Type
7
- #
8
- # ByteA is a special escaped form of binary data, suitable for inclusion in queries.
9
- #
10
- # This class is an attempt to abstract that type so you do not have to
11
- # concern yourself with the conversion issues.
12
- #
13
- class ByteA
14
-
15
- attr_reader :original
16
- attr_reader :escaped
17
-
18
- #
19
- # Build a new ByteA object.
20
- #
21
- # The data supplied is the unescaped binary data you wish to put in the
22
- # database.
23
- #
24
- def initialize(obj)
25
- @original = obj
26
- @escaped = escape_bytea(obj)
27
- @original.freeze
28
- @escaped.freeze
29
- end
30
-
31
- #
32
- # Escapes the supplied data. Has no effect on the object.
33
- #
34
- def escape_bytea(str)
35
- PGconn.escape_bytea(str)
36
- end
37
-
38
- #
39
- # Returns the original data.
40
- #
41
- def to_s
42
- return @original.dup
43
- end
44
-
45
- #
46
- # Class method to escape the data into ByteA format.
47
- #
48
- def self.escape_bytea(str)
49
- self.new(str).escaped
50
- end
51
-
52
- #
53
- # Class method to unescape the ByteA data and present it as a string.
54
- #
55
- def self.parse(obj)
56
-
57
- return nil if obj.nil?
58
-
59
- # FIXME there's a bug in the upstream 'pg' driver that does not
60
- # properly decode bytea, leaving in an extra slash for each decoded
61
- # character.
62
- #
63
- # Fix this for now, but beware that we'll have to unfix this as
64
- # soon as they fix their end.
65
- ret = PGconn.unescape_bytea(obj)
66
-
67
- # XXX
68
- # String#split does not properly create a full array if the the
69
- # string ENDS in the split regex, unless this oddball -1 argument is supplied.
70
- #
71
- # Another way of saying this:
72
- # if foo = "foo\\\\\" and foo.split(/\\\\/), the result will be
73
- # ["foo"]. You can add as many delimiters to the end of the string
74
- # as you'd like - the result is no different.
75
- #
76
-
77
- ret = ret.split(/\\\\/, -1).collect { |x| x.length > 0 ? x.gsub(/\\[0-7]{3}/) { |y| y[1..3].oct.chr } : "" }.join("\\")
78
- ret.gsub!(/''/, "'")
79
- return ret
80
- end
81
- end
82
-
83
- #
84
- # PostgreSQL arrays are simply a specification that sits on top of normal
85
- # types. They have a specialized string grammar and this class facilitates
86
- # converting that syntax and the types within those arrays.
87
- #
88
- class Array
89
-
90
- attr_reader :base_type
91
-
92
- #
93
- # +base_type+ is a DBI::Type that is used to parse the inner types when
94
- # a non-array one is found.
95
- #
96
- # For instance, if you had an array of integer, one would pass
97
- # DBI::Type::Integer here.
98
- #
99
- def initialize(base_type)
100
- @base_type = base_type
101
- end
102
-
103
- #
104
- # Object method. Please note that this is different than most DBI::Type
105
- # classes! One must initialize an Array object with an appropriate
106
- # DBI::Type used to convert the indices of the array before this method
107
- # can be called.
108
- #
109
- # Returns an appropriately converted array.
110
- #
111
- def parse(obj)
112
- if obj.nil?
113
- nil
114
- elsif obj.index('{') == 0 and obj.rindex('}') == (obj.length - 1)
115
- convert_array(obj)
116
- else
117
- raise "Not an array"
118
- end
119
- end
120
-
121
- #
122
- # Parse a PostgreSQL-Array output and convert into ruby array. This
123
- # does the real parsing work.
124
- #
125
- def convert_array(str)
126
-
127
- array_nesting = 0 # nesting level of the array
128
- in_string = false # currently inside a quoted string ?
129
- escaped = false # if the character is escaped
130
- sbuffer = '' # buffer for the current element
131
- result_array = ::Array.new # the resulting Array
132
-
133
- str.each_byte { |char| # parse character by character
134
- char = char.chr # we need the Character, not it's Integer
135
-
136
- if escaped then # if this character is escaped, just add it to the buffer
137
- sbuffer += char
138
- escaped = false
139
- next
140
- end
141
-
142
- case char # let's see what kind of character we have
143
- #------------- {: beginning of an array ----#
144
- when '{'
145
- if in_string then # ignore inside a string
146
- sbuffer += char
147
- next
148
- end
149
-
150
- if array_nesting >= 1 then # if it's an nested array, defer for recursion
151
- sbuffer += char
152
- end
153
- array_nesting += 1 # inside another array
154
-
155
- #------------- ": string deliminator --------#
156
- when '"'
157
- in_string = !in_string
158
-
159
- #------------- \: escape character, next is regular character #
160
- when "\\" # single \, must be extra escaped in Ruby
161
- if array_nesting > 1
162
- sbuffer += char
163
- else
164
- escaped = true
165
- end
166
-
167
- #------------- ,: element separator ---------#
168
- when ','
169
- if in_string or array_nesting > 1 then # don't care if inside string or
170
- sbuffer += char # nested array
171
- else
172
- if !sbuffer.is_a? ::Array then
173
- sbuffer = @base_type.parse(sbuffer)
174
- end
175
- result_array << sbuffer # otherwise, here ends an element
176
- sbuffer = ''
177
- end
178
-
179
- #------------- }: End of Array --------------#
180
- when '}'
181
- if in_string then # ignore if inside quoted string
182
- sbuffer += char
183
- next
184
- end
185
-
186
- array_nesting -=1 # decrease nesting level
187
-
188
- if array_nesting == 1 # must be the end of a nested array
189
- sbuffer += char
190
- sbuffer = convert_array( sbuffer ) # recurse, using the whole nested array
191
- elsif array_nesting > 1 # inside nested array, keep it for later
192
- sbuffer += char
193
- else # array_nesting = 0, must be the last }
194
- if !sbuffer.is_a? ::Array then
195
- sbuffer = @base_type.parse( sbuffer )
196
- end
197
-
198
- result_array << sbuffer unless sbuffer.nil? # upto here was the last element
199
- end
200
-
201
- #------------- all other characters ---------#
202
- else
203
- sbuffer += char # simply append
204
- end
205
- }
206
- return result_array
207
- end # convert_array()
208
- end
209
- end
@@ -1,151 +0,0 @@
1
- #
2
- # See DBI::BaseDatabase.
3
- #
4
- class DBI::DBD::SQLite::Database < DBI::BaseDatabase
5
- attr_reader :db
6
- attr_reader :attr_hash
7
- attr_accessor :open_handles
8
-
9
- #
10
- # Constructor. Valid attributes:
11
- #
12
- # * AutoCommit: Commit after every statement execution.
13
- #
14
- def initialize(dbname, user, auth, attr_hash)
15
- # FIXME why isn't this crap being done in DBI?
16
- unless dbname.kind_of? String
17
- raise DBI::InterfaceError, "Database Name must be a string"
18
- end
19
-
20
- unless dbname.length > 0
21
- raise DBI::InterfaceError, "Database Name needs to be length > 0"
22
- end
23
-
24
- unless attr_hash.kind_of? Hash
25
- raise DBI::InterfaceError, "Attributes should be a hash"
26
- end
27
-
28
- # FIXME handle busy_timeout in SQLite driver
29
- # FIXME handle SQLite pragmas in SQLite driver
30
- @attr_hash = attr_hash
31
- @open_handles = 0
32
-
33
- self["AutoCommit"] = true if self["AutoCommit"].nil?
34
-
35
- # open the database
36
- begin
37
- @db = ::SQLite::Database.new(dbname)
38
- rescue Exception => e
39
- raise DBI::OperationalError, "Couldn't open database #{dbname}: #{e.message}"
40
- end
41
- end
42
-
43
- def disconnect
44
- rollback rescue nil
45
- @db.close if @db and !@db.closed?
46
- @db = nil
47
- end
48
-
49
- def database_name
50
- st = DBI::DBD::SQLite::Statement.new('PRAGMA database_list', self)
51
- st.execute
52
- row = st.fetch
53
- st.finish
54
-
55
- return row[2]
56
- end
57
-
58
- def prepare(stmt)
59
- return DBI::DBD::SQLite::Statement.new(stmt, self)
60
- end
61
-
62
- def ping
63
- return !@db.closed?
64
- end
65
-
66
- def tables
67
- sth = prepare("select name from sqlite_master where type in ('table', 'view')")
68
- sth.execute
69
- tables = sth.fetch_all.flatten
70
- sth.finish
71
- return tables
72
- # FIXME does sqlite use views too? not sure, but they need to be included according to spec
73
- end
74
-
75
- def commit
76
- @db.commit if @db.transaction_active?
77
- end
78
-
79
- #
80
- # Rollback the transaction. SQLite has some issues with open statement
81
- # handles when this happens. If there are still open handles, a
82
- # DBI::Warning exception will be raised.
83
- #
84
- def rollback
85
- if @open_handles > 0
86
- raise DBI::Warning, "Leaving unfinished select statement handles while rolling back a transaction can corrupt your database or crash your program"
87
- end
88
-
89
- @db.rollback if @db.transaction_active?
90
- end
91
-
92
- def [](key)
93
- return @attr_hash[key]
94
- end
95
-
96
- #
97
- # See DBI::BaseDatabase#[]=.
98
- #
99
- # If AutoCommit is set to +true+ using this method, was previously +false+,
100
- # and we are currently in a transaction, The act of setting this will cause
101
- # an immediate commit.
102
- #
103
- def []=(key, value)
104
-
105
- old_value = @attr_hash[key]
106
-
107
- @attr_hash[key] = value
108
-
109
- # special handling of settings
110
- case key
111
- when "AutoCommit"
112
- # if the value being set is true and the previous value is false,
113
- # commit the current transaction (if any)
114
- # FIXME I still think this is a horrible way of handling this.
115
- if value and !old_value
116
- begin
117
- @dbh.commit
118
- rescue Exception => e
119
- end
120
- end
121
- end
122
-
123
- return @attr_hash[key]
124
- end
125
-
126
- def columns(tablename)
127
- return nil unless tablename and tablename.kind_of? String
128
-
129
- sth = prepare("PRAGMA table_info(?)")
130
- sth.bind_param(1, tablename)
131
- sth.execute
132
- columns = [ ]
133
- while row = sth.fetch
134
- column = { }
135
- column["name"] = row[1]
136
-
137
- m = DBI::DBD::SQLite.parse_type(row[2])
138
- column["type_name"] = m[1]
139
- column["precision"] = m[3].to_i if m[3]
140
- column["scale"] = m[5].to_i if m[5]
141
-
142
- column["nullable"] = row[3].to_i == 0
143
- column["default"] = row[4]
144
- columns.push column
145
- end
146
-
147
- sth.finish
148
- return columns
149
- # XXX it'd be nice if the spec was changed to do this k/v with the name as the key.
150
- end
151
- end