yajra / laravel-oci8

Oracle DB driver for Laravel via OCI8

Home Page:https://yajrabox.com/docs/laravel-oci8

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Binding driver options are ignored when calling addBindingsToStatement method

cwoodrgr opened this issue · comments

Summary of problem or feature request

While using the shortcut method to call a procedure: DB::executeProcedureWithCursor($procedureName, $bindings), I needed to pass options with the binding but found out that there does not appear to be a way to handle them currently.

Code snippet of problem

Current method ignores options:

public function addBindingsToStatement(PDOStatement $stmt, array $bindings)
    {
        foreach ($bindings as $key => &$binding) {
            $value = &$binding;
            $type = PDO::PARAM_STR;
            $length = -1;

            if (is_array($binding)) {
                $value = &$binding['value'];
                $type = array_key_exists('type', $binding) ? $binding['type'] : PDO::PARAM_STR;
                $length = array_key_exists('length', $binding) ? $binding['length'] : -1;
            }

            $stmt->bindParam(':'.$key, $value, $type, $length);
        }

        return $stmt;
    }

Updating the method to something like this appears to work:

public function addBindingsToStatement(PDOStatement $stmt, array $bindings)
    {
        foreach ($bindings as $key => &$binding) {
            $value = &$binding;
            $type = PDO::PARAM_STR;
            $length = -1;
            $options = null;

            if (is_array($binding)) {
                $value = &$binding['value'];
                $type = array_key_exists('type', $binding) ? $binding['type'] : PDO::PARAM_STR;
                $length = array_key_exists('length', $binding) ? $binding['length'] : -1;
                $options = array_key_exists('options', $binding) ? $binding['options'] : $options;
            }

            $stmt->bindParam(':'.$key, $value, $type, $length, $options);
        }

        return $stmt;
    }

System details

  • Operating System: Ubuntu 22.04
  • PHP Version: 8.1.13
  • Laravel Version: 9.52.9
  • Laravel-OCI8 Version: 9.5.0

Sorry, but can you provide some snippets for the use case?

Please do not hesitate to submit a PR, tagging for enhancement. Thanks!

A potential use case:

I have a procedure defined like this for testing purposes:

PROCEDURE Test_Array (p_array   IN     serial_numbers_t,
                          x_array      OUT rc_serial_numbers_out)
    IS
    BEGIN
        OPEN x_array FOR SELECT * FROM TABLE (p_array);
    END Test_Array;

serial_numbers_t is a named type:

create or replace type SERIAL_NUMBERS_T is table of VARCHAR2(30);

Calling the procedure manually like this works:

use Yajra\Pdo\Oci8\Statement;

$pdo = DB::connection('oracle_w')->getPdo();

$serialNumbers = ['1', '2'];

$collection = $pdo->getNewCollection('SERIAL_NUMBERS_T', 'APPS');

foreach ($serialNumbers as $value) {
    $collection->append($value);
}

$options = array("type_name" => "SERIAL_NUMBERS_T","schema" => "APPS");
$stmt_rec = $pdo->prepare("begin apps.test_pkg.test_array(:p_array, :x_array); end;");

$stmt_rec->bindParam(':p_array',  $collection, SQLT_NTY, -1, $options);

$cursor = null;
$stmt_rec->bindParam(':x_array', $cursor, PDO::PARAM_STMT);

$stmt_rec->execute();


$statement = new Statement($cursor, $pdo, $pdo->getOptions());
$statement->execute();
$results = $statement->fetchAll(PDO::FETCH_OBJ);
$statement->closeCursor();

dd($results);

Results:

array:2 [
  0 => {#4515
    +"column_value": "1"
  }
  1 => {#4504
    +"column_value": "2"
  }
]

Ideally I want to use the shortcut method you provide in this package.
First I tried just passing a basic array:

DB::connection('oracle_w')
  ->executeProcedureWithCursor('apps.test_pkg.test_array', ['p_array' => ['1', '2']]);

This did not work

Yajra\Pdo\Oci8\Exceptions\Oci8Exception 

Error Code    :   6550
Error Message : ORA-  06550: line   1, column   7:
PLS-  00306: wrong number or types of arguments in call to 'TEST_ARRAY'
ORA-  06550: line   1, column   7:
PL/SQL: Statement ignored

Then I tried defining the :p_array bind using an OCICollection:

$values = ['1', '2'];

$collection = DB::connection('oracle_w')
                ->getPdo()
                ->getNewCollection('SERIAL_NUMBERS_T', 'APPS');

foreach ($values as $value) {
  $collection->append($value);
}

$binds['p_array'] = [
  'value' => $collection,
  'type' => SQLT_NTY,
  'length' => -1,
  'options' => [
    'type_name' => 'SERIAL_NUMBERS_T',
    'schema' => 'APPS'
  ]
];

DB::connection('oracle_w')
  ->executeProcedureWithCursor('apps.test_pkg.test_array', $binds);

This resulted in the following exception:

 WARNING  oci_new_collection(): OCI-21560: argument 7 is null, invalid, or out of range in vendor/yajra/laravel-pdo-via-oci8/src/Pdo/Oci8.php on line 597.


   TYPE ERROR  Yajra\Pdo\Oci8::getNewCollection(): Return value must be of type OCICollection, bool returned in phar:///tmp/tinker.phar/vendor/psy/psysh/src/Exception/TypeErrorException.php on line 20.


Yajra\Pdo\Oci8::getNewCollection(): Return value must be of type OCICollection, bool returned
/vendor/yajra/laravel-pdo-via-oci8/src/Pdo/Oci8.php:21
 	/vendor/yajra/laravel-pdo-via-oci8/src/Pdo/Oci8/Statement.php:327
 	 	 in Yajra\Pdo\Oci8::getNewCollection()
 	/vendor/yajra/laravel-oci8/src/Oci8/Oci8Connection.php:422
 	 	 in Yajra\Pdo\Oci8\Statement::bindParam()
 	/vendor/yajra/laravel-oci8/src/Oci8/Oci8Connection.php:260
 	 	 in Yajra\Oci8\Oci8Connection::addBindingsToStatement()

I then tried modifying the addBindingsToStatement method in Oci8Connection.php to the following:

public function addBindingsToStatement(PDOStatement $stmt, array $bindings)
    {
        foreach ($bindings as $key => &$binding) {
            $value = &$binding;
            $type = PDO::PARAM_STR;
            $length = -1;
            $options = null;

            if (is_array($binding)) {
                $value = &$binding['value'];
                $type = array_key_exists('type', $binding) ? $binding['type'] : PDO::PARAM_STR;
                $length = array_key_exists('length', $binding) ? $binding['length'] : -1;
                $options = array_key_exists('options', $binding) ? $binding['options'] : $options;
            }

            $stmt->bindParam(':'.$key, $value, $type, $length, $options);
        }

        return $stmt;
    }

After making the change, I re-ran the code and got the expected result:

[
    {#4269
      +"column_value": "1",
    },
    {#4268
      +"column_value": "2",
    },
  ]

If this is acceptable, I can submit a PR.

How do you handle a PR that should be applied to multiple branches? (ex. 9.x, 10.x, etc...)

How do you handle a PR that should be applied to multiple branches? (ex. 9.x, 10.x, etc...)

Via cherry-pick if possible. I also accept a PR per branch but would take a lot of work.

The proposed changes are acceptable, looking forward to the PR. Thanks a lot for providing the details.

fixed via #814