sequel 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
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)