diff --git a/src/Jfelder/OracleDB/OracleConnection.php b/src/Jfelder/OracleDB/OracleConnection.php index b42fd7f..1abcab0 100644 --- a/src/Jfelder/OracleDB/OracleConnection.php +++ b/src/Jfelder/OracleDB/OracleConnection.php @@ -92,10 +92,41 @@ public function bindValues($statement, $bindings) $statement->bindValue( $key, $value, - is_int($value) ? PDO::PARAM_INT : PDO::PARAM_STR + match (true) { + is_int($value) => PDO::PARAM_INT, + is_bool($value) => PDO::PARAM_BOOL, + is_null($value) => PDO::PARAM_NULL, + is_resource($value) => PDO::PARAM_LOB, + default => PDO::PARAM_STR + }, ); } } + /** + * Run an "insert get ID" statement against an oracle database. + * + * @param string $query + * @param array $bindings + * @return int + */ + public function oracleInsertGetId($query, $bindings = []) + { + return $this->run($query, $bindings, function ($query, $bindings) { + $last_insert_id = 0; + + $statement = $this->getPdo()->prepare($query); + $this->bindValues($statement, $this->prepareBindings($bindings)); + + // bind final param to a var to capture the id obtained by the query's "returning id into" clause + $statement->bindParam(count($bindings), $last_insert_id, PDO::PARAM_INT | PDO::PARAM_INPUT_OUTPUT, 8); + + $this->recordsHaveBeenModified(); + + $statement->execute(); + + return (int) $last_insert_id; + }); + } } diff --git a/src/Jfelder/OracleDB/Query/Processors/OracleProcessor.php b/src/Jfelder/OracleDB/Query/Processors/OracleProcessor.php index 02bf6e0..6e7f8ab 100644 --- a/src/Jfelder/OracleDB/Query/Processors/OracleProcessor.php +++ b/src/Jfelder/OracleDB/Query/Processors/OracleProcessor.php @@ -4,7 +4,6 @@ use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Processors\Processor as Processor; -use Jfelder\OracleDB\OCI_PDO\OCI; class OracleProcessor extends Processor { @@ -14,38 +13,12 @@ class OracleProcessor extends Processor * @param \Illuminate\Database\Query\Builder $query * @param string $sql * @param array $values - * @param string $sequence + * @param string $sequence no effect; only for method signature compatibility * @return int */ public function processInsertGetId(Builder $query, $sql, $values, $sequence = null) { - $counter = 0; - $last_insert_id = 0; - - // Get PDO object - $pdo = $query->getConnection()->getPdo(); - - // get PDO statment object - $stmt = $pdo->prepare($sql); - - // PDO driver params are 1-based so ++ has to be before bindValue - // OCI driver params are 0-based so no ++ before bindValue - if (get_class($pdo) != OCI::class) { - $counter++; - } - - // bind each parameter from the values array to their location in the - foreach ($values as $k => $v) { - $stmt->bindValue($counter++, $v, $this->bindType($v)); - } - - // bind output param for the returning clause - $stmt->bindParam($counter, $last_insert_id, \PDO::PARAM_INT | \PDO::PARAM_INPUT_OUTPUT, 8); - - // execute statement - $stmt->execute(); - - return (int) $last_insert_id; + return $query->getConnection()->oracleInsertGetId($sql, $values); } /** @@ -64,26 +37,4 @@ public function processColumnListing($results) return array_map($mapping, $results); } - - /* - * Determine parameter type passed in - * - * @param mixed $param - * @return \PDO::PARAM_* type - */ - - private function bindType($param) - { - if (is_int($param)) { - $param = \PDO::PARAM_INT; - } elseif (is_bool($param)) { - $param = \PDO::PARAM_BOOL; - } elseif (is_null($param)) { - $param = \PDO::PARAM_NULL; - } else { - $param = \PDO::PARAM_STR; - } - - return $param; - } } diff --git a/tests/OracleDBConnectionTest.php b/tests/OracleDBConnectionTest.php new file mode 100644 index 0000000..60475f3 --- /dev/null +++ b/tests/OracleDBConnectionTest.php @@ -0,0 +1,79 @@ +markTestSkipped('The oci8 extension is not available.'); + } + } + + protected function tearDown(): void + { + m::close(); + } + + public function testOracleInsertGetIdProperlyCallsPDO() + { + $pdo = $this->getMockBuilder(OracleDBConnectionTestMockPDO::class)->onlyMethods(['prepare'])->getMock(); + $statement = $this->getMockBuilder(OracleDBConnectionTestMockOCIStatement::class)->onlyMethods(['execute', 'bindValue', 'bindParam'])->getMock(); + $statement->expects($this->once())->method('bindValue')->with(0, 'bar', 2); + $statement->expects($this->once())->method('bindParam')->with(1, 0, PDO::PARAM_INT | PDO::PARAM_INPUT_OUTPUT, 8); + $statement->expects($this->once())->method('execute'); + $pdo->expects($this->once())->method('prepare')->with($this->equalTo('foo'))->willReturn($statement); + $mock = $this->getMockConnection(['prepareBindings'], $pdo); + $mock->expects($this->once())->method('prepareBindings')->with($this->equalTo(['bar']))->willReturn(['bar']); + $results = $mock->oracleInsertGetId('foo', ['bar']); + $this->assertSame(0, $results); + $log = $mock->getQueryLog(); + $this->assertSame('foo', $log[0]['query']); + $this->assertEquals(['bar'], $log[0]['bindings']); + $this->assertIsNumeric($log[0]['time']); + } + + protected function getMockConnection($methods = [], $pdo = null) + { + $pdo = $pdo ?: new OracleDBConnectionTestMockPDO; + $defaults = ['getDefaultQueryGrammar', 'getDefaultPostProcessor', 'getDefaultSchemaGrammar']; + $connection = $this->getMockBuilder(OracleConnection::class)->onlyMethods(array_merge($defaults, $methods))->setConstructorArgs([$pdo])->getMock(); + $connection->enableQueryLog(); + + return $connection; + } +} + +class OracleDBConnectionTestMockPDO extends OCI +{ + public function __construct() + { + // + } + + public function __destruct() + { + // + } +} + +class OracleDBConnectionTestMockOCIStatement extends OCIStatement +{ + public function __construct() + { + // + } + + public function __destruct() + { + // + } +} diff --git a/tests/OracleDBOCIProcessorTest.php b/tests/OracleDBOCIProcessorTest.php index ef0ef86..5ec1a4e 100644 --- a/tests/OracleDBOCIProcessorTest.php +++ b/tests/OracleDBOCIProcessorTest.php @@ -2,8 +2,6 @@ namespace Jfelder\OracleDB\Tests; -use ProcessorTestOCIStub; -use ProcessorTestOCIStatementStub; use Jfelder\OracleDB\OracleConnection; use Jfelder\OracleDB\Query\OracleBuilder; use Jfelder\OracleDB\Query\Processors\OracleProcessor; @@ -28,24 +26,15 @@ public function tearDown(): void public function testInsertGetIdProcessing() { - $stmt = m::mock(new ProcessorTestOCIStatementStub()); - $stmt->shouldReceive('bindValue')->times(4)->withAnyArgs(); - $stmt->shouldReceive('bindParam')->once()->with(5, 0, \PDO::PARAM_INT | \PDO::PARAM_INPUT_OUTPUT, 8); - $stmt->shouldReceive('execute')->once()->withNoArgs(); - - $pdo = m::mock(new ProcessorTestOCIStub()); - $pdo->shouldReceive('prepare')->once()->with('sql')->andReturn($stmt); - $connection = m::mock(OracleConnection::class); - $connection->shouldReceive('getPdo')->once()->andReturn($pdo); + $connection->shouldReceive('oracleInsertGetId')->once()->with('sql', [1, 'foo', true, null])->andReturn(1234); $builder = m::mock(OracleBuilder::class); $builder->shouldReceive('getConnection')->once()->andReturn($connection); $processor = new OracleProcessor; - - $result = $processor->processInsertGetId($builder, 'sql', [1, 'foo', true, null], 'id'); - $this->assertSame(0, $result); + $result = $processor->processInsertGetId($builder, 'sql', [1, 'foo', true, null]); + $this->assertSame(1234, $result); } public function testProcessColumnListing()