Commit d7059c6c authored by Kubo Takehiro's avatar Kubo Takehiro
Browse files

Add OCI8::in_cond and OCI8:InCondBindHelper.

#115
parent 1967fe40
2016-04-24 Kubo Takehiro <kubo@jiubao.org>
* lib/oci8/oci8.rb: Add OCI8::in_cond and OCI8:InCondBindHelper.
* test/test_all.rb, test/test_bind_array.rb: Add test of OCI8::in_cond.
* README.md, docs/bind-array-to-in_cond.md: Add document of OCI8::in_cond.
2016-04-10 Kubo Takehiro <kubo@jiubao.org>
* lib/oci8/connection_pool.rb: update document.
......
......@@ -46,6 +46,7 @@ Other documents
* {file:docs/timeout-parameters.md Timeout Parameters}
* {file:docs/conflicts-local-connections-and-processes.md Conflicts between Local Connections and Child Process Handling on Unix}
* {file:docs/bind-array-to-in_cond.md Bind an Array to IN-condition}
License
=======
......
@ Bind an Array to IN-condition
Bind an Array to IN-condition
=============================
Binding an arbitrary-length array to IN-condition is not simple.
You need to create an SQL statement containing a comma-separated
list whose length is same with the input data.
Example:
ids = [ ... ] # an arbitrary-length array containing user IDs.
place_holder_string = Array.new(ids.length) {|index| ":id_#{index}"}.join(', ')
# place_holder_string is:
# ":id_0" if ids.length == 1
# ":id_0, :id_1" if ids.length == 2
# ...
cursor = conn.parse("select * from users where id in (#{place_holder_string})")
ids.each_with_index do |id, index|
cursor.bind_param("id#{index}", id) # bind each element
end
cursor.exec()
However this is awkward. So {OCI8.in_cond} was added in ruby-oci8 2.2.2.
The above code is rewritten as follows:
ids = [ ... ] # an arbitrary-length array containing user IDs.
in_cond = OCI8::in_cond(:id, ids)]
cursor = conn.exec("select * from users where id in (#{in_cond.names})", *in_cond.values)
or
ids = [ ... ] # an arbitrary-length array containing user IDs.
in_cond = OCI8::in_cond(:id, ids, Integer) # set the data type explicitly
cursor = conn.exec("select * from users where id in (#{in_cond.names})", *in_cond.values)
......@@ -447,6 +447,60 @@ class OCI8
end
end
# A helper class to bind an array to paramters in IN-condition.
#
# See {file:docs/bind-array-to-in_cond.md Bind an Array to IN-condition}
class InCondBindHelper
def initialize(bind_name_prefix, array, type = nil, length = nil)
bind_name_prefix = bind_name_prefix.to_s
if bind_name_prefix !~ /^\w+$/
raise ArgumentError, "The first argument doesn't consist of alphanumeric characters and underscores."
end
if array.empty?
# This doesn't match anything.
# However in-condition requires at least one value.
@bind_names = ":#{bind_name_prefix}_0"
@bind_values = [[nil, type.nil? ? String : type, length]]
else
@bind_names = Array.new(array.length) do |index|
":#{bind_name_prefix}_#{index}"
end.join(', ')
first_non_nil = array.find do |e|
!e.nil?
end
first_non_nil = '' if first_non_nil.nil?
@bind_values = array.collect do |elem|
if elem.nil? and type.nil?
[elem, first_non_nil.class]
else
[elem, type, length]
end
end
end
end
def names
@bind_names
end
def values
@bind_values
end
end
# Creates a helper object to bind an array to paramters in IN-condition.
#
# See {file:docs/bind-array-to-in_cond.md Bind an Array to IN-condition}
#
# @param [Symbol] bind_name_prefix prefix of the place holder name
# @param [Object] array an array of values to be bound.
# @param [Class] type data type. This is used as the third argument of {OCI8::Cursor#bind_param}.
# @param [Integer] length maximum bind length for string values. This is used as the fourth argument of {OCI8::Cursor#bind_param}.
# @return [OCI8::InCondBindHelper]
def self.in_cond(bind_name_prefix, array, type = nil, length = nil)
InCondBindHelper.new(bind_name_prefix, array, type, length)
end
private
# Converts the specified privilege name to the value passed to the
......
......@@ -5,6 +5,7 @@ require "#{srcdir}/config"
require "#{srcdir}/test_oradate"
require "#{srcdir}/test_oranumber"
require "#{srcdir}/test_bind_array.rb"
require "#{srcdir}/test_bind_string"
require "#{srcdir}/test_bind_time"
require "#{srcdir}/test_bind_raw"
......
require 'oci8'
require File.dirname(__FILE__) + '/config'
class TestBindArray < Minitest::Test
def test_bind_array_names
assert_equal(":id_0", OCI8::in_cond(:id, []).names)
assert_equal(":id_0", OCI8::in_cond(:id, [1]).names)
assert_equal(":id_0, :id_1", OCI8::in_cond(:id, [1, 2]).names)
assert_equal(":id_0, :id_1, :id_2", OCI8::in_cond(:id, [1, 2, 3]).names)
end
def test_bind_array_values
assert_equal([[nil, String, nil]], OCI8::in_cond(:id, []).values)
assert_equal([[1, nil, nil]], OCI8::in_cond(:id, [1]).values)
assert_equal([[1, nil, nil], [2, nil, nil]], OCI8::in_cond(:id, [1, 2]).values)
assert_equal([[1, nil, nil], [2, nil, nil], [3, nil, nil]], OCI8::in_cond(:id, [1, 2, 3]).values)
end
def test_bind_array_values_containg_nil
assert_equal([[nil, String]], OCI8::in_cond(:id, [nil]).values)
assert_equal([[nil, Fixnum], [2, nil, nil], [3, nil, nil]], OCI8::in_cond(:id, [nil, 2, 3]).values)
assert_equal([[1, nil, nil], [nil, Fixnum], [3, nil, nil]], OCI8::in_cond(:id, [1, nil, 3]).values)
end
def test_bind_array_values_with_type
assert_equal([[nil, Integer, nil]], OCI8::in_cond(:id, [], Integer).values)
assert_equal([[1, Integer, nil]], OCI8::in_cond(:id, [1], Integer).values)
assert_equal([[1, Integer, nil], [2, Integer, nil]], OCI8::in_cond(:id, [1, 2], Integer).values)
assert_equal([[1, Integer, nil], [2, Integer, nil], [3, Integer, nil]], OCI8::in_cond(:id, [1, 2, 3], Integer).values)
assert_equal([[nil, Integer, nil], [2, Integer, nil], [3, Integer, nil]], OCI8::in_cond(:id, [nil, 2, 3], Integer).values)
assert_equal([[1, Integer, nil], [nil, Integer, nil], [3, Integer, nil]], OCI8::in_cond(:id, [1, nil, 3], Integer).values)
end
def test_select
@conn = get_oci8_connection
begin
drop_table('test_table')
@conn.exec(<<EOS)
CREATE TABLE test_table (ID NUMBER(38))
EOS
cursor = @conn.parse('insert into test_table values(:1)')
cursor.bind_param(1, nil, Integer)
[1, 3, 5].each do |id|
cursor.exec(id)
end
cursor.close
[
[],
[1],
[1, 2],
[1, 2, 3],
[nil],
[nil, 2, 3],
[1, nil, 3],
].each do |ids|
in_cond = OCI8::in_cond(:id, ids)
cursor = @conn.exec("select * from test_table where id in (#{in_cond.names}) order by id", *in_cond.values)
([1, 3, 5] & ids).each do |id|
assert_equal(id, cursor.fetch[0])
end
end
drop_table('test_table')
ensure
@conn.logoff
end
end
end
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment