Skip to content

Commit

Permalink
ResultSetEnumerator -> ResultSet
Browse files Browse the repository at this point in the history
ResultSet is automatically closed just before yielding the last row
  • Loading branch information
junegunn committed Jun 17, 2013
1 parent 902e696 commit d7fbb82
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 146 deletions.
13 changes: 10 additions & 3 deletions CHANGELOG.md
@@ -1,9 +1,16 @@
### 0.8.0
* Refined TableWrapper interface

0.8.0 introduces a few backward-incompatible changes.

* `Connection#ResultSetEnumerator` is renamed to `Connection::ResultSet`
* `Connection#query` method will return a ResultSet object instead of an Array
* `Connection#enumerate` method is now retired, and just a synonym for query method
* Partially consumed ResultSet must be closed explicitly
* `ResultSet#each` method will return an enumerator when block is not given
* Refined TableWrapper interface with external [sql_helper](https://github.com/junegunn/sql_helper) gem
* The use of `JDBCHelper::SQL` is deprecated
* Added MariaDB connector
* Added SQLite connector
* Deprecated the use of `JDBCHelper::SQL`
* `ResultSetEnumerator#each` returns an enumerator when block is not given

### 0.7.7 / 2013/01/0?
* `PreparedStatment`s and `TableWrapper`s now inherit the fetch size of the connection
Expand Down
23 changes: 13 additions & 10 deletions README.md
Expand Up @@ -128,23 +128,26 @@ conn.query('SELECT a, b, c FROM T') do |row|
row.to_h # Row as a Hash
end

# Returns an array of rows when block is not given
rows = conn.query('SELECT b FROM T')
uniq_rows = rows.uniq

# You can even nest queries
conn.query('SELECT a FROM T') do |row1|
conn.query("SELECT * FROM T_#{row1.a}") do |row2|
# ...
end
end

# `enumerate' method returns an Enumerable object if block is not given.
# When the result set of the query is expected to be large and you wish to
# chain enumerators, `enumerate' is much preferred over `query'. (which returns the
# array of the entire rows)
conn.enumerate('SELECT * FROM LARGE_T').each_slice(1000) do |slice|
slice.each do | row |
# Connection::ResultSet object is returned when block is not given
# - ResultSet is automatically closed when entirely iterated
rows = conn.query('SELECT * FROM T')
uniq_rows = rows.to_a.uniq

# However, partially consumed ResultSet objects *must be closed* manually
rset = conn.query('SELECT * FROM T')
rows = rset.take(2)
rset.close

# Enumerator chain
conn.query('SELECT * FROM LARGE_T').each_slice(1000).with_index do |slice, idx|
slice.each do |row|
# ...
end
end
Expand Down
80 changes: 22 additions & 58 deletions lib/jdbc-helper/connection.rb
Expand Up @@ -8,7 +8,7 @@
require 'jdbc-helper/connection/prepared_statement'
require 'jdbc-helper/connection/callable_statement'
require 'jdbc-helper/connection/statement_pool'
require 'jdbc-helper/connection/result_set_enumerator'
require 'jdbc-helper/connection/result_set'
require 'jdbc-helper/connection/row'

require 'jdbc-helper/wrapper/object_wrapper'
Expand Down Expand Up @@ -241,18 +241,18 @@ def transaction
status == :committed
end

# Executes an SQL and returns the count of the update rows or a ResultSetEnumerator object
# Executes an SQL and returns the count of the update rows or a ResultSet object
# depending on the type of the given statement.
# If a ResultSetEnumerator is returned, it must be enumerated or closed.
# If a ResultSet is returned, it must be enumerated or closed.
# @param [String] qstr SQL string
# @return [Fixnum|ResultSetEnumerator]
# @return [Fixnum|ResultSet]
def execute(qstr)
check_closed

stmt = @spool.take
begin
if stmt.execute(qstr)
ResultSetEnumerator.send(:new, stmt.getResultSet) { @spool.give stmt }
ResultSet.send(:new, stmt.getResultSet) { @spool.give stmt }
else
rset = stmt.getUpdateCount
@spool.give stmt
Expand All @@ -277,57 +277,39 @@ def update(qstr)

# Executes a select query.
# When a code block is given, each row of the result is passed to the block one by one.
# If a code block not given, this method will return the array of the entire result rows.
# (which can be pretty inefficient when the result set is large. In such cases, use enumerate instead.)
#
# The concept of statement object of JDBC is encapsulated, so there's no need to do additional task,
# when you nest select queries, for example.
# If not given, ResultSet is returned, which can be used to enumerate through the result set.
# ResultSet is closed automatically when all the rows in the result set is consumed.
#
# @example Nested querying
# conn.query("SELECT a FROM T") do | trow |
# conn.query("SELECT * FROM U_#{trow.a}") do | urow |
# # ... and so on ...
# end
# conn.query("SELECT * FROM U_#{trow.a}").each_slice(10) do | urows |
# # ...
# end
# end
# @param [String] qstr SQL string
# @yield [JDBCHelper::Connection::Row]
# @return [Array]
def query(qstr, &blk)
check_closed

@spool.with do | stmt |
rset = stmt.execute_query(qstr)
process_and_close_rset(rset, &blk)
end
end

# Returns an enumerable object of the query result.
# "enumerate" method is preferable when dealing with a large result set,
# since it doesn't have to build a large array.
#
# The returned enumerator is automatically closed after enumeration.
#
# conn.enumerate('SELECT * FROM T').each_slice(10) do | slice |
# slice.each { | row | print row }
# puts
# end
#
# @param [String] qstr SQL string
# @yield [JDBCHelper::Connection::Row] Yields each record if block is given
# @return [JDBCHelper::Connection::ResultSetEnumerator] Returns an enumerator if block is not given
def enumerate(qstr, &blk)
return query(qstr, &blk) if block_given?

check_closed

stmt = @spool.take
begin
rset = stmt.execute_query(qstr)
return ResultSetEnumerator.send(:new, rset) { @spool.give stmt }
rescue Exception
rescue Exception => e
@spool.give stmt
raise
end

enum = ResultSet.send(:new, rset) { @spool.give stmt }
if block_given?
enum.each do |row|
yield row
end
else
enum
end
end
alias enumerate query

# Adds a statement to be executed in batch
# Adds to the batch
Expand Down Expand Up @@ -480,24 +462,6 @@ def create_statement # :nodoc:
stmt
end

def process_and_close_rset(rset) # :nodoc:
enum = ResultSetEnumerator.send :new, rset
rows = []

begin
enum.each do | row |
if block_given?
yield row
else
rows << row
end
end
block_given? ? nil : rows
ensure
enum.close
end
end

def close_pstmt pstmt
@pstmts.delete pstmt
end
Expand Down
25 changes: 11 additions & 14 deletions lib/jdbc-helper/connection/prepared_statement.rb
Expand Up @@ -27,13 +27,13 @@ def close
@java_obj = nil
end

# @return [Fixnum|ResultSetEnumerator]
# @return [Fixnum|ResultSet]
def execute(*params)
check_closed

set_params(params)
if @java_obj.execute
ResultSetEnumerator.new(@java_obj.getResultSet)
ResultSet.new(@java_obj.getResultSet)
else
@java_obj.getUpdateCount
end
Expand All @@ -52,19 +52,16 @@ def query(*params, &blk)
check_closed

set_params(params)
# sorry, ignoring privacy
@conn.send(:process_and_close_rset, @java_obj.execute_query, &blk)
end

# @return [JDBCHelper::Connection::ResultSetEnumerator]
def enumerate(*params, &blk)
check_closed

return query(*params, &blk) if block_given?

set_params(params)
ResultSetEnumerator.new(@java_obj.execute_query)
enum = ResultSet.new(@java_obj.execute_query)
if block_given?
enum.each do |row|
yield row
end
else
enum
end
end
alias enumerate query

# Adds to the batch
# @return [NilClass]
Expand Down
Expand Up @@ -7,49 +7,47 @@ module JDBCHelper
class Connection
# Class for enumerating query results.
# Automatically closed after used. When not used, you must close it explicitly by calling "close".
class ResultSetEnumerator
class ResultSet
include Enumerable

def each
return enum_for(:each) unless block_given?
return if closed?

count = -1
begin
while @rset.next
idx = 0
# Oracle returns numbers in NUMERIC type, which can be of any precision.
# So, we retrieve the numbers in String type not to lose their precision.
# This can be quite annoying when you're just working with integers,
# so I tried the following code to automatically convert integer string into integer
# when it's obvious. However, the performance drop is untolerable.
# Thus, commented out.
#
# if v && @cols_meta[i-1] == java.sql.Types::NUMERIC && v !~ /[\.e]/i
# v.to_i
# else
# v
# end
yield Connection::Row.new(
@col_labels,
@col_labels_d,
@getters.map { |gt|
case gt
when :getBigNum
v = @rset.getBigDecimal idx+=1
@rset.was_null ? nil : v.toPlainString.to_i
when :getBigDecimal
v = @rset.getBigDecimal idx+=1
@rset.was_null ? nil : BigDecimal.new(v.toPlainString)
else
v = @rset.send gt, idx+=1
@rset.was_null ? nil : v
end
}, count += 1)
end
ensure
close
while @nrow
idx = 0
# Oracle returns numbers in NUMERIC type, which can be of any precision.
# So, we retrieve the numbers in String type not to lose their precision.
# This can be quite annoying when you're just working with integers,
# so I tried the following code to automatically convert integer string into integer
# when it's obvious. However, the performance drop is untolerable.
# Thus, commented out.
#
# if v && @cols_meta[i-1] == java.sql.Types::NUMERIC && v !~ /[\.e]/i
# v.to_i
# else
# v
# end
row = Connection::Row.new(
@col_labels,
@col_labels_d,
@getters.map { |gt|
case gt
when :getBigNum
v = @rset.getBigDecimal idx+=1
@rset.was_null ? nil : v.toPlainString.to_i
when :getBigDecimal
v = @rset.getBigDecimal idx+=1
@rset.was_null ? nil : BigDecimal.new(v.toPlainString)
else
v = @rset.send gt, idx+=1
@rset.was_null ? nil : v
end
}, @rownum += 1)
close unless @nrow = @rset.next
yield row
end
close
end

def close
Expand Down Expand Up @@ -116,9 +114,11 @@ def initialize(rset, &close_callback) # :nodoc:

end

@rownum = -1
@nrow = @rset.next
@closed = false
end
end#ResultSetEnumerator
end#ResultSet
end#Connection
end#JDBCHelper

2 changes: 1 addition & 1 deletion lib/jdbc-helper/wrapper/function_wrapper.rb
Expand Up @@ -27,7 +27,7 @@ def initialize conn, name
def call(*args)
pstmt = @connection.prepare("select #{name}(#{args.map{'?'}.join ','})#{@suffix}")
begin
pstmt.query(*args)[0][0]
pstmt.query(*args).to_a[0][0]
ensure
pstmt.close
end
Expand Down
4 changes: 2 additions & 2 deletions lib/jdbc-helper/wrapper/sequence_wrapper.rb
Expand Up @@ -31,13 +31,13 @@ def initialize(conn, name)
# Increments the sequence and returns the value
# @return [Fixnum]
def nextval
@connection.query(@nextval_sql)[0][0].to_i
@connection.query(@nextval_sql).to_a[0][0].to_i
end

# Returns the incremented value of the sequence
# @return [Fixnum]
def currval
@connection.query(@currval_sql)[0][0].to_i
@connection.query(@currval_sql).to_a[0][0].to_i
end

# Recreates the sequence. Cannot be undone.
Expand Down
4 changes: 2 additions & 2 deletions lib/jdbc-helper/wrapper/table_wrapper.rb
Expand Up @@ -52,7 +52,7 @@ class TableWrapper < ObjectWrapper
def count *where
sql, *binds = SQLHelper.count :table => name, :where => @query_where + where, :prepared => true
pstmt = prepare :count, sql
pstmt.query(*binds)[0][0].to_i
pstmt.query(*binds).to_a[0][0].to_i
end

# Sees if the table is empty
Expand Down Expand Up @@ -222,7 +222,7 @@ def fetch_size fsz, &block

# Executes a select SQL for the table and returns an Enumerable object,
# or yields each row if block is given.
# @return [JDBCHelper::Connection::ResultSetEnumerator]
# @return [JDBCHelper::Connection::ResultSet]
# @since 0.4.0
def each &block
sql, *binds = SQLHelper.select(
Expand Down

0 comments on commit d7fbb82

Please sign in to comment.