sequel 2.3.0 → 2.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 (43) hide show
  1. data/CHANGELOG +16 -0
  2. data/README +4 -1
  3. data/Rakefile +17 -19
  4. data/doc/prepared_statements.rdoc +104 -0
  5. data/doc/sharding.rdoc +113 -0
  6. data/lib/sequel_core/adapters/ado.rb +24 -17
  7. data/lib/sequel_core/adapters/db2.rb +30 -33
  8. data/lib/sequel_core/adapters/dbi.rb +15 -13
  9. data/lib/sequel_core/adapters/informix.rb +13 -14
  10. data/lib/sequel_core/adapters/jdbc.rb +243 -60
  11. data/lib/sequel_core/adapters/jdbc/mysql.rb +32 -24
  12. data/lib/sequel_core/adapters/jdbc/postgresql.rb +32 -2
  13. data/lib/sequel_core/adapters/jdbc/sqlite.rb +16 -20
  14. data/lib/sequel_core/adapters/mysql.rb +164 -76
  15. data/lib/sequel_core/adapters/odbc.rb +21 -34
  16. data/lib/sequel_core/adapters/openbase.rb +10 -7
  17. data/lib/sequel_core/adapters/oracle.rb +17 -23
  18. data/lib/sequel_core/adapters/postgres.rb +246 -35
  19. data/lib/sequel_core/adapters/shared/mssql.rb +106 -0
  20. data/lib/sequel_core/adapters/shared/mysql.rb +34 -26
  21. data/lib/sequel_core/adapters/shared/postgres.rb +82 -38
  22. data/lib/sequel_core/adapters/shared/sqlite.rb +48 -16
  23. data/lib/sequel_core/adapters/sqlite.rb +141 -44
  24. data/lib/sequel_core/connection_pool.rb +85 -63
  25. data/lib/sequel_core/database.rb +46 -17
  26. data/lib/sequel_core/dataset.rb +21 -40
  27. data/lib/sequel_core/dataset/convenience.rb +3 -3
  28. data/lib/sequel_core/dataset/prepared_statements.rb +218 -0
  29. data/lib/sequel_core/exceptions.rb +0 -12
  30. data/lib/sequel_model/base.rb +1 -2
  31. data/lib/sequel_model/plugins.rb +1 -1
  32. data/spec/adapters/ado_spec.rb +32 -3
  33. data/spec/adapters/mysql_spec.rb +7 -8
  34. data/spec/integration/prepared_statement_test.rb +106 -0
  35. data/spec/sequel_core/connection_pool_spec.rb +105 -3
  36. data/spec/sequel_core/database_spec.rb +41 -3
  37. data/spec/sequel_core/dataset_spec.rb +117 -7
  38. data/spec/sequel_core/spec_helper.rb +2 -2
  39. data/spec/sequel_model/model_spec.rb +0 -6
  40. data/spec/sequel_model/spec_helper.rb +1 -1
  41. metadata +11 -6
  42. data/lib/sequel_core/adapters/adapter_skeleton.rb +0 -54
  43. data/lib/sequel_core/adapters/odbc_mssql.rb +0 -106
data/CHANGELOG CHANGED
@@ -1,3 +1,19 @@
1
+ === 2.4.0 (2008-08-06)
2
+
3
+ * Handle Java::JavaSql::Date type in the JDBC adapter (jeremyevans)
4
+
5
+ * Add support for read-only slave/writable master databases and database sharding (jeremyevans)
6
+
7
+ * Remove InvalidExpression, InvalidFilter, InvalidJoinType, and WorkerStop exceptions (jeremyevans)
8
+
9
+ * Add prepared statement/bound variable support (jeremyevans)
10
+
11
+ * Fix anonymous column names in the ADO adapter (nusco)
12
+
13
+ * Remove odbc_mssql adapter, use :db_type=>'mssql' option instead (jeremyevans)
14
+
15
+ * Split MSSQL specific syntax into separate file, usable by ADO and ODBC adapters (nusco, jeremyevans)
16
+
1
17
  === 2.3.0 (2008-07-25)
2
18
 
3
19
  * Enable almost full support for MySQL using JDBC (jeremyevans)
data/README CHANGED
@@ -6,6 +6,8 @@ Sequel is a lightweight database access toolkit for Ruby.
6
6
  for constructing database queries and table schemas.
7
7
  * Sequel also includes a lightweight but comprehensive ORM layer for
8
8
  mapping records to Ruby objects and handling associated records.
9
+ * Sequel supports advanced database features such as prepared statements,
10
+ bound variables, master/slave configurations, and database sharding.
9
11
  * Sequel makes it easy to deal with multiple records without having
10
12
  to break your teeth on SQL.
11
13
  * Sequel currently has adapters for ADO, DB2, DBI, Informix, JDBC,
@@ -13,10 +15,11 @@ Sequel is a lightweight database access toolkit for Ruby.
13
15
 
14
16
  == Resources
15
17
 
18
+ * {Website}[http://sequel.rubyforge.org]
16
19
  * {Source code}[http://github.com/jeremyevans/sequel]
17
20
  * {Bug tracking}[http://code.google.com/p/ruby-sequel/issues/list]
18
21
  * {Google group}[http://groups.google.com/group/sequel-talk]
19
- * {RDoc}[http://sequel.rubyforge.org]
22
+ * {RDoc}[http://sequel.rubyforge.org/rdoc]
20
23
 
21
24
  To check out the source code:
22
25
 
data/Rakefile CHANGED
@@ -8,14 +8,13 @@ require "spec/rake/spectask"
8
8
  include FileUtils
9
9
 
10
10
  NAME = 'sequel'
11
- VERS = '2.3.0'
12
- CLEAN.include ["**/.*.sw?", "pkg", ".config", "rdoc", "coverage"]
11
+ VERS = '2.4.0'
12
+ CLEAN.include ["**/.*.sw?", "pkg", ".config", "rdoc", "coverage", "www/public/*.html"]
13
13
  RDOC_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', \
14
14
  'Sequel: The Database Toolkit for Ruby', '--main', 'README']
15
15
 
16
- ##############################################################################
17
- # gem packaging and release
18
- ##############################################################################
16
+ # Gem Packaging and Release
17
+
19
18
  desc "Packages sequel"
20
19
  task :package=>[:clean]
21
20
  spec = Gem::Specification.new do |s|
@@ -64,25 +63,26 @@ task :release=>[:package] do
64
63
  sh %{rubyforge add_file sequel #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.gem}
65
64
  end
66
65
 
67
- ##############################################################################
68
- # rdoc
69
- ##############################################################################
66
+ ### RDoc
67
+
70
68
  Rake::RDocTask.new do |rdoc|
71
69
  rdoc.rdoc_dir = "rdoc"
72
70
  rdoc.options += RDOC_OPTS
73
71
  rdoc.rdoc_files.add %w"README CHANGELOG COPYING lib/**/*.rb doc/*.rdoc"
74
72
  end
75
73
 
76
- desc "Update docs and upload to rubyforge.org"
77
- task :doc_rforge => [:rdoc]
78
- task :doc_rforge do
79
- sh %{chmod -R g+w rdoc/*}
80
- sh %{scp -rp rdoc/* rubyforge.org:/var/www/gforge-projects/sequel}
74
+ ### Website
75
+
76
+ desc "Update sequel.rubyforge.org"
77
+ task :website => [:rdoc]
78
+ task :website do
79
+ sh %{www/make_www.rb}
80
+ sh %{scp -r www/public/* rubyforge.org:/var/www/gforge-projects/sequel/}
81
+ sh %{scp -r rdoc/* rubyforge.org:/var/www/gforge-projects/sequel/rdoc/}
81
82
  end
82
83
 
83
- ##############################################################################
84
- # specs
85
- ##############################################################################
84
+ ### Specs
85
+
86
86
  lib_dir = File.join(File.dirname(__FILE__), 'lib')
87
87
  fixRUBYLIB = Proc.new{ENV['RUBYLIB'] ? (ENV['RUBYLIB'] += ":#{lib_dir}") : (ENV['RUBYLIB'] = lib_dir)}
88
88
  sequel_core_specs = "spec/sequel_core/*_spec.rb"
@@ -141,9 +141,7 @@ task :dcov do
141
141
  sh %{find lib -name '*.rb' | xargs dcov}
142
142
  end
143
143
 
144
- ##############################################################################
145
- # Statistics
146
- ##############################################################################
144
+ ### Statistics
147
145
 
148
146
  STATS_DIRECTORIES = [
149
147
  %w(Code lib/),
@@ -0,0 +1,104 @@
1
+ = Prepared Statements and Bound Variables
2
+
3
+ Starting with version 2.4.0, Sequel has support for prepared statements and
4
+ bound variables. No matter which database you are using, the Sequel prepared
5
+ statement/bound variable API remains exactly the same. There is native support
6
+ for prepared statements/bound variables on the following databases:
7
+
8
+ * PostgreSQL (using the pg driver, requires type specifiers)
9
+ * MySQL (prepared statements only, as the ruby mysql driver doesn't support
10
+ bound variables)
11
+ * SQLite (a new native prepared statement is used for each call, though)
12
+ * JDBC (using the postgresql, mysql, or sqlite databases, and possibly others)
13
+
14
+ Support on other databases is emulated via the usual string interpolation.
15
+
16
+ == Placeholders
17
+
18
+ Generally, when using prepared statements (and certainly when using bound
19
+ variables), you need to put placeholders in your SQL to indicate where you
20
+ want your bound arguments to appear. Database support and syntax vary
21
+ significantly for placeholders (e.g. :name, $1, ?). Sequel abstracts all of
22
+ that and allows you to specify placeholders by using the :$name format for
23
+ placeholders, e.g.:
24
+
25
+ ds = DB[:items].filter(:name=>:$name)
26
+
27
+ == Bound Variables
28
+
29
+ Using bound variables for this query is simple:
30
+
31
+ ds.call(:select, :name=>'Jim')
32
+
33
+ This will do the equivalent of selecting records that have the name 'Jim'. It
34
+ returns all records, and can take a block that is passed to Dataset#all.
35
+
36
+ Deleting or returning the first record works similarly:
37
+
38
+ ds.call(:first, :name=>'Jim') # First record with name 'Jim'
39
+ ds.call(:delete, :name=>'Jim') # Delete records with name 'Jim'
40
+
41
+ For inserting/updating records, you should also specify a value hash, which
42
+ may itself contain placeholders:
43
+
44
+ # Insert record with 'Jim', note that the previous filter is ignored
45
+ ds.call(:insert, {:name=>'Jim'}, :name=>:$name)
46
+ # Change name to 'Bob' for all records with name of 'Jim'
47
+ ds.call(:update, {:name=>'Jim', :new_name=>'Bob'}, :name=>$:new_name)
48
+
49
+ == Prepared Statements
50
+
51
+ Prepared statement support is similar to bound variable support, but you
52
+ use Dataset#prepare with a name, and Dataset#call later with the values:
53
+
54
+ ds = DB[:items].filter(:name=>:$name)
55
+ ps = ds.prepare(:select, :select_by_name)
56
+ ps.call(:name=>'Jim')
57
+ DB.call(:select_by_name, :name=>'Jim') # same as above
58
+
59
+ The Dataset#prepare method returns a prepared statement, and also stores a
60
+ copy of the prepared statement in the database for later use. For insert
61
+ and update queries, the hash to insert/update is passed to prepare:
62
+
63
+ ps1 = DB[:items].prepare(:insert, :insert_with_name, :name=>:$name)
64
+ ps1.call(:name=>'Jim')
65
+ DB.call(:insert_with_name, :name=>'Jim') # same as above
66
+ ds = DB[:items].filter(:name=>:$name)
67
+ ps2 = ds.prepare(:update, :update_name, :name=>:$new_name)
68
+ ps2.call(:name=>'Jim', :new_name=>'Bob')
69
+ DB.call(:update_name, :name=>'Jim', :new_name=>'Bob') # same as above
70
+
71
+ == Database support
72
+
73
+ === PostgreSQL
74
+
75
+ If you are using the ruby-postgres or postgres-pr driver, PostgreSQL uses the
76
+ default emulated support. If you are using ruby-pg, there is native support,
77
+ but it requires type specifiers most of the time. This is easy if you have
78
+ direct control over the SQL string, but since Sequel abstracts that, the types
79
+ have to be specified another way. This is done by adding a __* suffix to the
80
+ placeholder symbol (e.g. :$name__text, which will be compiled to "$1::text"
81
+ in the SQL). Prepared statements are always server side.
82
+
83
+ === SQLite
84
+
85
+ SQLite supports bound variables and prepared statements exactly the same, since
86
+ a new native prepared statement is created and executed for each call.
87
+
88
+ === MySQL
89
+
90
+ The MySQL ruby driver does not support bound variables, so the the bound
91
+ variable methods fall back to string interpolation. It uses server side
92
+ prepared statements.
93
+
94
+ === JDBC
95
+
96
+ JDBC supports both prepared statements and bound variables. Whether these
97
+ are server side or client side depends on the JDBC driver. For PostgreSQL
98
+ over JDBC, you can add the prepareThreshold=N parameter to the connection
99
+ string, which will use a server side prepared statement after N calls to
100
+ the prepared statement.
101
+
102
+ === All Others
103
+
104
+ Support is emulated using interpolation.
data/doc/sharding.rdoc ADDED
@@ -0,0 +1,113 @@
1
+ = Read-Only Slaves/Writable Master and Database Sharding
2
+
3
+ Starting with version 2.4.0, Sequel has support for read only slave databases
4
+ with a writable master database, as well as database sharding (where you can
5
+ pick a database connection to use for a given dataset). Support for both
6
+ features is database independent, and should work for all database adapters
7
+ included with Sequel.
8
+
9
+ == The :servers Database option
10
+
11
+ Both features use the new :servers Database option. The :servers option should
12
+ be a hash with symbol keys and values that are either hashes or procs that
13
+ return hashes. Note that all servers should have the same schema, unless you
14
+ really know what you are doing.
15
+
16
+ == Master and Slave Database Configurations
17
+
18
+ === Single Read-Only Slave, Single Master
19
+
20
+ To use a single, read-only slave that handles SELECT queries, the following
21
+ is the simplest configuration:
22
+
23
+ DB=Sequel.connect('postgres://master_server/database', \
24
+ :servers=>{:read_only=>{:host=>'slave_server'}})
25
+
26
+ This will use the host slave_server for SELECT queries and master_server for
27
+ other queries.
28
+
29
+ === Multiple Read-Only Slaves, Single Master
30
+
31
+ Let's say you have 4 slave database servers with names slave_server0,
32
+ slave_server1, slave_server2, and slave_server3.
33
+
34
+ DB=Sequel.connect('postgres://master_server/database', \
35
+ :servers=>{:read_only=>proc{|db| :host=>db.get_slave_host}})
36
+ def DB.get_slave_host
37
+ @current_host ||= -1
38
+ "slave_server#{(@current_host+=1)%4}"
39
+ end
40
+
41
+ This will use one of the slave servers for SELECT queries and use the
42
+ master_server for other queries. It's also possible to pick a random host
43
+ instead of using the round robin approach presented above, but that can result
44
+ in less optimal resource usage.
45
+
46
+ === Multiple Read-Only Slaves, Multiple Masters
47
+
48
+ This involves the same basic idea as the multiple slaves, single master, but
49
+ it shows that the master database is named :default. So for 4 masters and
50
+ 4 slaves:
51
+
52
+ DB=Sequel.connect('postgres://master_server/database', \
53
+ :servers=>{:read_only=>proc{|db| :host=>db.get_slave_host}, \
54
+ :default=>proc{|db| :host=>db.get_master_host}})
55
+ def DB.get_slave_host
56
+ @current_slave_host ||= -1
57
+ "slave_server#{(@current_slave_host+=1)%4}"
58
+ end
59
+ def DB.get_master_host
60
+ @current_master_host ||= -1
61
+ "master_server#{(@current_master_host+=1)%4}"
62
+ end
63
+
64
+ == Sharding
65
+
66
+ There is specific support in Sequel for handling master/slave database
67
+ combinations, with the only necessary setup being the database configuration.
68
+ However, since sharding is always going to be implementation dependent, Sequel
69
+ supplies the basic infrastructure, but you have to tell it which server to use
70
+ for each dataset. Let's assume the simple scenario, a distributed rainbow
71
+ table for SHA-1 hashes, sharding based on the first hex character (for a total
72
+ of 16 shards). First, you need to configure the database:
73
+
74
+ servers = {}
75
+ (('0'..'9').to_a + ('a'..'f').to_a).each do |hex|
76
+ servers[hex.to_sym] = {:host=>"hash_host_#{hex}"}
77
+ end
78
+ DB=Sequel.connect('postgres://hash_host/hashes', :servers=>servers)
79
+
80
+ This configures 17 servers, the 16 shard servers (/hash_host_[0-9a-f]/), and 1
81
+ default server which will be used if no shard is specified ("hash_host"). If
82
+ you want the default server to be one of the shard servers (e.g. hash_host_a),
83
+ it's easiest to do:
84
+
85
+ DB=Sequel.connect('postgres://hash_host_a/hashes', :servers=>servers)
86
+
87
+ That will still set up a second pool of connections for the default server,
88
+ since it considers the default server and shard servers independent. Note that
89
+ if you always set the shard on a dataset before using it in queries, it will
90
+ not attempt to connect to the default server. Sequel may use the default
91
+ server in queries it generates itself, such as to get column names or table
92
+ schemas, so it is always good to have a default server that works.
93
+
94
+ To set the shard for a given query, you use the Dataset#server method:
95
+
96
+ DB[:hashes].server(:a).filter(:hash=>/31337/)
97
+
98
+ That will return all matching rows on the hash_host_a shard that have a hash
99
+ column that contains 31337.
100
+
101
+ Rainbow tables are generally used to find specific hashes, so to save some
102
+ work, you might want to add a method to the dataset that automatically sets
103
+ the shard to use. This is fairly easy using a Sequel::Model:
104
+
105
+ class Rainbow < Sequel::Model(:hashes)
106
+ def_dataset_method(:plaintext_for_hash) do |hash|
107
+ raise(ArgumentError, 'Invalid SHA-1 Hash') unless /\A[0-9a-f]{40}\z/.match(hash)
108
+ row = self.server(hash[0...1].to_sym).first(:hash=>hash)
109
+ row[:plaintext] if row
110
+ end
111
+ end
112
+
113
+ Rainbow.plaintext_for_hash("e580726d31f6e1ad216ffd87279e536d1f74e606")
@@ -13,15 +13,16 @@ module Sequel
13
13
  module ADO
14
14
  class Database < Sequel::Database
15
15
  set_adapter_scheme :ado
16
-
17
- AUTO_INCREMENT = 'IDENTITY(1,1)'.freeze
18
-
19
- def auto_increment_sql
20
- AUTO_INCREMENT
21
- end
22
-
23
- def connect
24
- s = "driver=#{@opts[:driver] || 'SQL Server'};server=#{@opts[:host]};database=#{@opts[:database]}#{";uid=#{@opts[:user]};pwd=#{@opts[:password]}" if @opts[:user]}"
16
+
17
+ def connect(server)
18
+ opts = server_opts(server)
19
+ opts[:driver] ||= 'SQL Server'
20
+ case opts[:driver]
21
+ when 'SQL Server'
22
+ require 'sequel_core/adapters/shared/mssql'
23
+ extend Sequel::MSSQL::DatabaseMethods
24
+ end
25
+ s = "driver=#{opts[:driver]};server=#{opts[:host]};database=#{opts[:database]}#{";uid=#{opts[:user]};pwd=#{opts[:password]}" if opts[:user]}"
25
26
  handle = WIN32OLE.new('ADODB.Connection')
26
27
  handle.Open(s)
27
28
  handle
@@ -35,11 +36,14 @@ module Sequel
35
36
  ADO::Dataset.new(self, opts)
36
37
  end
37
38
 
38
- def execute(sql)
39
+ def execute(sql, opts={})
39
40
  log_info(sql)
40
- @pool.hold {|conn| conn.Execute(sql)}
41
+ synchronize(opts[:server]) do |conn|
42
+ r = conn.Execute(sql)
43
+ yield(r) if block_given?
44
+ r
45
+ end
41
46
  end
42
-
43
47
  alias_method :do, :execute
44
48
  end
45
49
 
@@ -55,11 +59,12 @@ module Sequel
55
59
  end
56
60
  end
57
61
 
58
- def fetch_rows(sql, &block)
59
- @db.synchronize do
60
- s = @db.execute sql
61
-
62
- @columns = s.Fields.extend(Enumerable).map {|x| x.Name.to_sym}
62
+ def fetch_rows(sql)
63
+ execute(sql) do |s|
64
+ @columns = s.Fields.extend(Enumerable).map do |column|
65
+ name = column.Name.empty? ? '(no column name)' : column.Name
66
+ name.to_sym
67
+ end
63
68
 
64
69
  unless s.eof
65
70
  s.moveFirst
@@ -69,6 +74,8 @@ module Sequel
69
74
  self
70
75
  end
71
76
 
77
+ private
78
+
72
79
  def hash_row(row)
73
80
  @columns.inject({}) do |m, c|
74
81
  m[c] = row.shift
@@ -6,29 +6,15 @@ module Sequel
6
6
  set_adapter_scheme :db2
7
7
  include DB2CLI
8
8
 
9
- # AUTO_INCREMENT = 'IDENTITY(1,1)'.freeze
10
- #
11
- # def auto_increment_sql
12
- # AUTO_INCREMENT
13
- # end
14
-
15
- def check_error(rc, msg)
16
- case rc
17
- when SQL_SUCCESS, SQL_SUCCESS_WITH_INFO
18
- nil
19
- else
20
- raise Error, msg
21
- end
22
- end
23
-
24
9
  rc, @@env = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE)
25
- check_error(rc, "Could not allocate DB2 environment")
10
+ #check_error(rc, "Could not allocate DB2 environment")
26
11
 
27
- def connect
12
+ def connect(server)
13
+ opts = server_opts(server)
28
14
  rc, dbc = SQLAllocHandle(SQL_HANDLE_DBC, @@env)
29
15
  check_error(rc, "Could not allocate database connection")
30
16
 
31
- rc = SQLConnect(dbc, @opts[:database], @opts[:user], @opts[:password])
17
+ rc = SQLConnect(dbc, opts[:database], opts[:user], opts[:password])
32
18
  check_error(rc, "Could not connect to database")
33
19
 
34
20
  dbc
@@ -44,8 +30,8 @@ module Sequel
44
30
  end
45
31
  end
46
32
 
47
- def test_connection
48
- @pool.hold {|conn|}
33
+ def test_connection(server=nil)
34
+ synchronize(server){|conn|}
49
35
  true
50
36
  end
51
37
 
@@ -53,9 +39,9 @@ module Sequel
53
39
  DB2::Dataset.new(self, opts)
54
40
  end
55
41
 
56
- def execute(sql, &block)
42
+ def execute(sql, opts={})
57
43
  log_info(sql)
58
- @pool.hold do |conn|
44
+ synchronize(opts[:server]) do |conn|
59
45
  rc, sth = SQLAllocHandle(SQL_HANDLE_STMT, @handle)
60
46
  check_error(rc, "Could not allocate statement")
61
47
 
@@ -63,7 +49,7 @@ module Sequel
63
49
  rc = SQLExecDirect(sth, sql)
64
50
  check_error(rc, "Could not execute statement")
65
51
 
66
- block[sth] if block
52
+ yield(sth) if block_given?
67
53
 
68
54
  rc, rpc = SQLRowCount(sth)
69
55
  check_error(rc, "Could not get RPC")
@@ -75,9 +61,22 @@ module Sequel
75
61
  end
76
62
  end
77
63
  alias_method :do, :execute
64
+
65
+ private
66
+
67
+ def check_error(rc, msg)
68
+ case rc
69
+ when SQL_SUCCESS, SQL_SUCCESS_WITH_INFO
70
+ nil
71
+ else
72
+ raise Error, msg
73
+ end
74
+ end
78
75
  end
79
76
 
80
77
  class Dataset < Sequel::Dataset
78
+ MAX_COL_SIZE = 256
79
+
81
80
  def literal(v)
82
81
  case v
83
82
  when Time
@@ -89,21 +88,19 @@ module Sequel
89
88
  end
90
89
  end
91
90
 
92
- def fetch_rows(sql, &block)
93
- @db.synchronize do
94
- @db.execute(sql) do |sth|
95
- @column_info = get_column_info(sth)
96
- @columns = @column_info.map {|c| c[:name]}
97
- while (rc = SQLFetch(@handle)) != SQL_NO_DATA_FOUND
98
- @db.check_error(rc, "Could not fetch row")
99
- yield hash_row(sth)
100
- end
91
+ def fetch_rows(sql)
92
+ execute(sql) do |sth|
93
+ @column_info = get_column_info(sth)
94
+ @columns = @column_info.map {|c| c[:name]}
95
+ while (rc = SQLFetch(@handle)) != SQL_NO_DATA_FOUND
96
+ @db.check_error(rc, "Could not fetch row")
97
+ yield hash_row(sth)
101
98
  end
102
99
  end
103
100
  self
104
101
  end
105
102
 
106
- MAX_COL_SIZE = 256
103
+ private
107
104
 
108
105
  def get_column_info(sth)
109
106
  rc, column_count = SQLNumResultCols(sth)