Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ services:
links:
- mysql
- postgres
- sqlserver
environment:
- MYSQL_HOST=mysql
- POSTGRES_HOST=postgres
- MSSQL_HOST=sqlserver
mysql:
image: mysql:8.0-oracle
environment:
Expand All @@ -26,4 +28,12 @@ services:
POSTGRES_PASSWORD: rootpassword
POSTGRES_DB: picodb
ports:
- "5432:5432"
- "5432:5432"
sqlserver:
image: mcr.microsoft.com/mssql/server:2019-latest
environment:
ACCEPT_EULA: Y
MSSQL_PID: Developer
MSSQL_SA_PASSWORD: r00t_Password
ports:
- "1433:1433"
10 changes: 8 additions & 2 deletions docker/php/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
FROM php:8.3-cli

RUN apt-get update && apt-get install -y gnupg2
RUN curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/packages.microsoft.gpg
RUN curl https://packages.microsoft.com/config/ubuntu/20.04/prod.list > /etc/apt/sources.list.d/mssql-release.list
RUN apt-get update && \
apt-get install -y libpq-dev && \
apt-get install -y libpq-dev unixodbc-dev && \
ACCEPT_EULA=Y apt-get install -y msodbcsql18 && \
pecl install sqlsrv && \
pecl install pdo_sqlsrv && \
docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql && \
docker-php-ext-install pdo pgsql pdo_mysql pdo_pgsql mysqli && \
docker-php-ext-enable pdo pdo_mysql pdo_pgsql mysqli
docker-php-ext-enable pdo pdo_mysql pdo_pgsql mysqli sqlsrv pdo_sqlsrv
1 change: 1 addition & 0 deletions docker/php/php.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ extension=pdo
extension=pdo_mysql
extension=pdo_pgsql
extension=pdo_sqlite
extension=pdo_sqlsrv
20 changes: 20 additions & 0 deletions lib/PicoDb/Database.php
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,26 @@ public function execute($sql, array $values = array())
->execute();
}

/**
* Execute a prepared statement with named parameters
*
* @access public
* @param string $sql SQL query
* @param array $inputValues Named input parameters for the statement. Must be associative where the key is the parameter's name and value is the corresponding input.
* @param array $outputValues Output or In/Out parameters for the statement. Must be associative where the key is the parameter and each value is a reference to a defined variable where the output will be stored.
* @return \PDOStatement|false
*/
public function executeNamed($sql, array $inputValues = array(), array $outputValues = array())
{
$test = $this->statementHandler
->withSql($sql)
->withNamedParams($inputValues)
->withOutputParams($outputValues)
->execute($outputValues);

return $test;
}

/**
* Run a transaction
*
Expand Down
37 changes: 32 additions & 5 deletions lib/PicoDb/StatementHandler.php
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@ class StatementHandler
*/
protected $namedParams = array();

/**
* Output SQL parameters
*
* @access protected
* @var array
*/
protected $outputParams = array();

/**
* Flag to use named params
*
Expand Down Expand Up @@ -207,6 +215,20 @@ public function withNamedParams(array $params)
return $this;
}

/**
* Set output parameters
*
* @access public
* @param array $params
* @return $this
*/
public function withOutputParams(array &$params)
{
$this->outputParams = $params;
$this->useNamedParams = true;
return $this;
}

/**
* Bind large object parameter
*
Expand All @@ -217,7 +239,7 @@ public function withNamedParams(array $params)
*/
public function withLobParam($name, &$fp)
{
$this->lobParams[$name] =& $fp;
$this->lobParams[$name] = &$fp;
return $this;
}

Expand Down Expand Up @@ -286,6 +308,10 @@ protected function bindParams(PDOStatement $pdoStatement)
foreach ($this->namedParams as $name => $value) {
$pdoStatement->bindValue($name, $value, PDO::PARAM_STR);
}

foreach ($this->outputParams as $name => &$value) {
$pdoStatement->bindParam($name, $value, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 2048);
}
}

/**
Expand All @@ -308,7 +334,7 @@ protected function beforeExecute()
}, $sql);
} else {
$i = 0;
$sql = preg_replace_callback('/\?/', function($matches) use ($params, &$i) {
$sql = preg_replace_callback('/\?/', function ($matches) use ($params, &$i) {
$replacement = $params[$i] ?? '';
$i++;
return "'$replacement'";
Expand All @@ -333,8 +359,8 @@ protected function afterExecute()
if ($this->stopwatch) {
$duration = microtime(true) - $this->startTime;
$this->executionTime += $duration;
$this->db->setLogMessage('query_duration='.$duration);
$this->db->setLogMessage('total_execution_time='.$this->executionTime);
$this->db->setLogMessage('query_duration=' . $duration);
$this->db->setLogMessage('total_execution_time=' . $this->executionTime);
}

if ($this->explain) {
Expand All @@ -358,6 +384,7 @@ protected function cleanup()
$this->positionalParams = array();
$this->namedParams = array();
$this->lobParams = array();
$this->outputParams = array();
}

/**
Expand All @@ -373,6 +400,6 @@ public function handleSqlError(PDOException $e)
$this->db->cancelTransaction();
$this->db->setLogMessage($e->getMessage());

throw new SQLException('SQL Error: '.$e->getMessage());
throw new SQLException('SQL Error: ' . $e->getMessage());
}
}
39 changes: 39 additions & 0 deletions tests/MssqlProcedureTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

use PicoDb\Database;
use PicoDb\Table;

class MssqlProcedureTest extends \PHPUnit\Framework\TestCase
{
/**
* @var PicoDb\Database
*/
private $db;

public function setUp(): void
{
$this->db = new Database(['driver' => 'mssql', 'hostname' => getenv('MSSQL_HOST'), 'username' => 'sa', 'password' => 'r00t_Password', 'database' => 'master', 'trust_server_cert' => true]);
$this->db->getConnection()->exec("IF (SELECT db_id('picodb')) IS NULL CREATE DATABASE [picodb]");
$this->db->getConnection()->exec('DROP PROCEDURE IF EXISTS [usp_test1]');
$this->db->getConnection()->exec('DROP TABLE IF EXISTS [test1]');
}

public function testExecute()
{
$this->assertNotFalse($this->db->execute('CREATE TABLE [test1] (a INTEGER, b INTEGER )'));
$this->assertTrue($this->db->table('test1')->insert(array('a' => 2, 'b' => 3)));

$this->assertNotFalse($this->db->execute('CREATE OR ALTER PROCEDURE [dbo].[usp_test1] @ParamA INT = NULL AS BEGIN IF @ParamA IS NOT NULL SELECT @ParamA AS [input]; ELSE SELECT * FROM [test1]; END'));
$this->assertEquals(['a' => 2, 'b' => 3], $this->db->execute('EXEC [usp_test1]')->fetch(PDO::FETCH_ASSOC));
$this->assertEquals(['input' => 5], $this->db->executeNamed('EXEC [usp_test1] :ParamA', ['ParamA' => 5])->fetch(PDO::FETCH_ASSOC));
}

public function testOutputParams()
{
$this->assertNotFalse($this->db->execute('CREATE OR ALTER PROCEDURE [dbo].[usp_testMultiply] @ParamA INT, @ParamB INT, @ParamC INT OUTPUT AS BEGIN SET @ParamC = @ParamA * @ParamB; END'));

$output = 0;
$this->assertNotFalse($this->db->executeNamed('EXEC [usp_testMultiply] :ParamA, :ParamB, :ParamC', ['ParamA' => 5, 'ParamB' => 10], ['ParamC' => &$output]));
$this->assertEquals(50, $output);
}
}
Loading