diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml new file mode 100644 index 000000000..00abb989e --- /dev/null +++ b/.github/workflows/coding-style.yml @@ -0,0 +1,31 @@ +name: Coding Style + +on: [push, pull_request] + +jobs: + nette_cc: + name: Nette Code Checker + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: shivammathur/setup-php@v2 + with: + php-version: 8.0 + coverage: none + + - run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress + - run: php temp/code-checker/code-checker --strict-types --no-progress --ignore "tests/*/fixtures" + + + nette_cs: + name: Nette Coding Standard + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: shivammathur/setup-php@v2 + with: + php-version: 8.0 + coverage: none + + - run: composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress --ignore-platform-reqs + - run: php temp/coding-standard/ecs check diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 000000000..f985b0526 --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,21 @@ +name: Static Analysis (only informative) + +on: + push: + branches: + - master + +jobs: + phpstan: + name: PHPStan + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: shivammathur/setup-php@v2 + with: + php-version: 8.0 + coverage: none + + - run: composer install --no-progress --prefer-dist + - run: composer phpstan -- --no-progress + continue-on-error: true # is only informative diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..b81c281de --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,121 @@ +name: Tests + +on: [push, pull_request] + +env: + php-extensions: mbstring, intl, mysqli, pgsql, sqlsrv-5.9.0preview1, pdo_sqlsrv-5.9.0preview1 + php-tools: "composer:v2, pecl" + +jobs: + tests: + runs-on: ubuntu-latest + strategy: + matrix: + php: ['7.2', '7.3', '7.4', '8.0'] + + fail-fast: false + + name: PHP ${{ matrix.php }} tests + + services: + mysql57: + image: mysql:5.7 + env: + MYSQL_DATABASE: dibi_test + MYSQL_ROOT_PASSWORD: root + ports: + - 3306:3306 + options: >- + --health-cmd "mysqladmin ping -ppass" + --health-interval 10s + --health-start-period 10s + --health-timeout 5s + --health-retries 10 + + mysql80: + image: mysql:8.0 + ports: + - 3307:3306 + options: >- + --health-cmd="mysqladmin ping -ppass" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + -e MYSQL_ROOT_PASSWORD=root + -e MYSQL_DATABASE=dibi_test + --entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password" + + postgres96: + image: postgres:9.6 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: dibi_test + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + postgres13: + image: postgres:13 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: dibi_test + ports: + - 5433:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + mssql: + image: mcr.microsoft.com/mssql/server:latest + env: + ACCEPT_EULA: Y + SA_PASSWORD: YourStrong!Passw0rd + MSSQL_PID: Developer + ports: + - 1433:1433 + options: >- + --name=mssql + --health-cmd "/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'SELECT 1'" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v2 + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: ${{ env.php-extensions }} + tools: ${{ env.php-tools }} + coverage: none + + - name: Create databases.ini + run: cp ./tests/databases.github.ini ./tests/databases.ini + + - name: Create MS SQL Database + run: docker exec -i mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'CREATE DATABASE dibi_test' + + - run: composer install --no-progress --prefer-dist + - run: vendor/bin/tester -p phpdbg tests -s -C --coverage ./coverage.xml --coverage-src ./src + - if: failure() + uses: actions/upload-artifact@v2 + with: + name: output + path: tests/**/output + + + - name: Save Code Coverage + if: ${{ matrix.php == '8.0' }} + env: + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar + php php-coveralls.phar --verbose --config tests/.coveralls.yml diff --git a/.gitignore b/.gitignore index de4a392c3..ba0d2a307 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /vendor /composer.lock +/.vscode \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index a4330dcf6..ace08b6e6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,8 +6,9 @@ cache: clone_folder: c:\projects\dibi services: - - mssql2012sp1 +# - mssql2012sp1 # - mssql2014 + - mssql2016 - mysql init: @@ -15,23 +16,27 @@ init: - SET ANSICON=121x90 (121x90) install: - # Install PHP 7.2 + # Install PHP 7.4 - IF EXIST c:\php7 (SET PHP=0) ELSE (SET PHP=1) - IF %PHP%==1 mkdir c:\php7 - IF %PHP%==1 cd c:\php7 - - IF %PHP%==1 curl https://windows.php.net/downloads/releases/archives/php-7.2.18-Win32-VC15-x64.zip --output php.zip + - IF %PHP%==1 curl https://windows.php.net/downloads/releases/archives/php-7.4.19-Win32-vc15-x64.zip --output php.zip - IF %PHP%==1 7z x php.zip >nul - IF %PHP%==1 echo extension_dir=ext >> php.ini - IF %PHP%==1 echo extension=php_openssl.dll >> php.ini - - IF %PHP%==1 curl https://github.com/microsoft/msphpsql/releases/download/v5.8.0/Windows-7.2.zip -L --output sqlsrv.zip + - IF %PHP%==1 curl https://github.com/microsoft/msphpsql/releases/download/v5.9.0/Windows-7.4.zip -L --output sqlsrv.zip - IF %PHP%==1 7z x sqlsrv.zip >nul - - IF %PHP%==1 copy Windows-7.2\x64\php_sqlsrv_72_ts.dll ext\php_sqlsrv_ts.dll + - IF %PHP%==1 copy Windows-7.4\x64\php_sqlsrv_74_ts.dll ext\php_sqlsrv_ts.dll - IF %PHP%==1 del /Q *.zip # Install Microsoft Access Database Engine x64 - IF %PHP%==1 curl https://download.microsoft.com/download/2/4/3/24375141-E08D-4803-AB0E-10F2E3A07AAA/AccessDatabaseEngine_X64.exe --output AccessDatabaseEngine_X64.exe - cmd /c start /wait AccessDatabaseEngine_X64.exe /passive + # Install ODBC Driver for SQL Server x64 Version 17.7.2 + - IF %PHP%==1 curl --silent --location --output msodbcsql.msi https://go.microsoft.com/fwlink/?linkid=2156851 + - cmd /c start /wait msiexec /qn /passive /i msodbcsql.msi + # Install Nette Tester - cd c:\projects\dibi - appveyor DownloadFile https://getcomposer.org/composer.phar diff --git a/composer.json b/composer.json index d94135ceb..c66c6b1cb 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ "tracy/tracy": "~2.2", "nette/tester": "~2.0", "nette/di": "^3.0", - "phpstan/phpstan": "^0.12" + "phpstan/phpstan": "^0.12", + "nette/code-checker": "^3.2" }, "replace": { "dg/dibi": "*" diff --git a/readme.md b/readme.md index 997b3c415..edc764df1 100644 --- a/readme.md +++ b/readme.md @@ -176,6 +176,7 @@ In addition to the `?` wildcard char, we can also use modifiers: | %ex | SQL expression or array of expressions | %lmt | special - adds LIMIT to the query | %ofs | special - adds OFFSET to the query +| %pq | special - sends the parameterized query down to the underlying DB driver for binding Example: @@ -202,6 +203,15 @@ $result = $database->query('SELECT * FROM %n WHERE %n = ?', $table, $column, $va // SELECT * FROM `blog`.`users` WHERE `name` = 'Jim' ``` +The modifier `%pq` inserts a `?` into the query sent to the underlying database driver and allows the caller to bind the parameterized query values to the positional modifiers. + +Example: + +```php +$result = $database->query('SELECT * FROM users WHERE name = %pq', $database->getDriver()->bindText($name)); +// SELECT * FROM `users` WHERE `name` = ? +``` + Three special modifiers are available for LIKE: | modifier | description diff --git a/src/Dibi/Drivers/DummyDriver.php b/src/Dibi/Drivers/DummyDriver.php index 523179a46..f1e331297 100644 --- a/src/Dibi/Drivers/DummyDriver.php +++ b/src/Dibi/Drivers/DummyDriver.php @@ -18,6 +18,7 @@ class DummyDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector { use Dibi\Strict; + use NoParameterizedQueries; public function disconnect(): void { @@ -129,7 +130,6 @@ public function escapeLike(string $value, int $pos): string return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'"); } - /** * Injects LIMIT/OFFSET to the SQL query. */ diff --git a/src/Dibi/Drivers/FirebirdDriver.php b/src/Dibi/Drivers/FirebirdDriver.php index e1f8c69fc..793a4df69 100644 --- a/src/Dibi/Drivers/FirebirdDriver.php +++ b/src/Dibi/Drivers/FirebirdDriver.php @@ -27,6 +27,7 @@ class FirebirdDriver implements Dibi\Driver { use Dibi\Strict; + use NoParameterizedQueries; public const ERROR_EXCEPTION_THROWN = -836; diff --git a/src/Dibi/Drivers/MySqliDriver.php b/src/Dibi/Drivers/MySqliDriver.php index d9dede68b..75a952342 100644 --- a/src/Dibi/Drivers/MySqliDriver.php +++ b/src/Dibi/Drivers/MySqliDriver.php @@ -33,6 +33,7 @@ class MySqliDriver implements Dibi\Driver { use Dibi\Strict; + use NoParameterizedQueries; public const ERROR_ACCESS_DENIED = 1045; diff --git a/src/Dibi/Drivers/NoParameterizedQueries.php b/src/Dibi/Drivers/NoParameterizedQueries.php new file mode 100644 index 000000000..c379aa099 --- /dev/null +++ b/src/Dibi/Drivers/NoParameterizedQueries.php @@ -0,0 +1,71 @@ +connection->query($sql); + if (count($this->paramValues) > 0) { + $res = $this->connection->prepare($sql); + if ($res) { + $count = count($this->paramValues); + for ($index = 0; $index < $count && $res; $index++) { + $success = $res->bindParam($index+1, $this->paramValues[$index]['var'], $this->paramValues[$index]['type']); + if (!$success) { + $res = false; + } + } + if ($res) { + $success = $res->execute(); + if (!$success) { + $res = false; + } + } + } + $this->paramValues = []; + } else { + $res = $this->connection->query($sql); + } + if ($res) { $this->affectedRows = $res->rowCount(); return $res->columnCount() ? $this->createResultDriver($res) : null; @@ -349,6 +373,53 @@ public function escapeLike(string $value, int $pos): string } } + public function addParameter(QueryParameter $param) : void { + // https://www.php.net/manual/en/sqlsrv.constants.php + $this->paramValues[] = [ + 'var' => $param->value, + 'type' => $param->sqlType + ]; + } + + function bindAsciiText(?string $value, ?string $length = null, ?string $encoding = null): QueryParameter { + // return new QueryParameter($value, \SQLSRV_SQLTYPE_VARCHAR($length ?? 'max'), \SQLSRV_PHPTYPE_STRING($encoding ?? 'UTF-8')); + return new QueryParameter($value, PDO::PARAM_STR, null); + } + + function bindText(?string $value, ?string $length = null, ?string $encoding = null): QueryParameter { + // return new QueryParameter($value, \SQLSRV_SQLTYPE_NVARCHAR($length ?? 'max'), \SQLSRV_PHPTYPE_STRING($encoding ?? 'UTF-8')); + return new QueryParameter($value, PDO::PARAM_STR, null); + } + + function bindIdentifier(?string $value): QueryParameter { + // return new QueryParameter($value, \SQLSRV_SQLTYPE_UNIQUEIDENTIFIER, \SQLSRV_PHPTYPE_STRING('UTF-8')); + return new QueryParameter($value, PDO::PARAM_STR, null); + } + + function bindInt(?int $value): QueryParameter { + // return new QueryParameter($value, \SQLSRV_SQLTYPE_INT, \SQLSRV_PHPTYPE_INT); + return new QueryParameter($value, PDO::PARAM_INT, null); + } + + function bindNumeric(?float $value, string $precision, string $scale): QueryParameter { + // return new QueryParameter($value, \SQLSRV_SQLTYPE_NUMERIC($precision, $scale), \SQLSRV_PHPTYPE_FLOAT); + return new QueryParameter($value, PDO::PARAM_STR, null); + } + + function bindDate(?\DateTimeInterface $value): QueryParameter { + // return new QueryParameter($value, \SQLSRV_SQLTYPE_DATE, \SQLSRV_PHPTYPE_DATETIME); + return new QueryParameter($value, PDO::PARAM_STR, null); + } + + function bindDateTime(?\DateTimeInterface $value): QueryParameter { + // return new QueryParameter($value, \SQLSRV_SQLTYPE_DATETIME2, \SQLSRV_PHPTYPE_DATETIME); + return new QueryParameter($value, PDO::PARAM_STR, null); + } + + function bindDateInterval(?\DateInterval $value): QueryParameter { + // return new QueryParameter($value, \SQLSRV_SQLTYPE_DATETIMEOFFSET, \SQLSRV_PHPTYPE_STRING('UTF-8')); + return new QueryParameter($value, PDO::PARAM_STR, null); + } /** * Injects LIMIT/OFFSET to the SQL query. diff --git a/src/Dibi/Drivers/PostgreDriver.php b/src/Dibi/Drivers/PostgreDriver.php index f020876f6..8188e77d2 100644 --- a/src/Dibi/Drivers/PostgreDriver.php +++ b/src/Dibi/Drivers/PostgreDriver.php @@ -28,6 +28,7 @@ class PostgreDriver implements Dibi\Driver { use Dibi\Strict; + use NoParameterizedQueries; /** @var resource */ private $connection; diff --git a/src/Dibi/Drivers/SqliteDriver.php b/src/Dibi/Drivers/SqliteDriver.php index fe0b216f6..3cfce01c2 100644 --- a/src/Dibi/Drivers/SqliteDriver.php +++ b/src/Dibi/Drivers/SqliteDriver.php @@ -26,6 +26,7 @@ class SqliteDriver implements Dibi\Driver { use Dibi\Strict; + use NoParameterizedQueries; /** @var SQLite3 */ private $connection; diff --git a/src/Dibi/Drivers/SqlsrvDriver.php b/src/Dibi/Drivers/SqlsrvDriver.php index 03ae091d8..214725b03 100644 --- a/src/Dibi/Drivers/SqlsrvDriver.php +++ b/src/Dibi/Drivers/SqlsrvDriver.php @@ -11,7 +11,7 @@ use Dibi; use Dibi\Helpers; - +use Dibi\QueryParameter; /** * The driver for Microsoft SQL Server and SQL Azure databases. @@ -29,6 +29,9 @@ class SqlsrvDriver implements Dibi\Driver { use Dibi\Strict; + // https://docs.microsoft.com/en-us/sql/t-sql/data-types/int-bigint-smallint-and-tinyint-transact-sql?view=sql-server-ver15 + private const SQLSERVER_MAX_INT = 2147483647; + /** @var resource */ private $connection; @@ -38,6 +41,8 @@ class SqlsrvDriver implements Dibi\Driver /** @var string */ private $version = ''; + /** @var array */ + private $paramValues = []; /** @throws Dibi\NotSupportedException */ public function __construct(array $config) @@ -84,7 +89,6 @@ public function disconnect(): void @sqlsrv_close($this->connection); // @ - connection can be already disconnected } - /** * Executes the SQL query. * @throws Dibi\DriverException @@ -92,7 +96,8 @@ public function disconnect(): void public function query(string $sql): ?Dibi\ResultDriver { $this->affectedRows = null; - $res = sqlsrv_query($this->connection, $sql); + $res = sqlsrv_query($this->connection, $sql, $this->paramValues); // TODO Change this to include bound parameters + $this->paramValues = []; if ($res === false) { $info = sqlsrv_errors(); @@ -248,6 +253,59 @@ public function escapeLike(string $value, int $pos): string return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'"); } + public function addParameter(QueryParameter $param) : void { + // https://www.php.net/manual/en/sqlsrv.constants.php + $this->paramValues[] = [ + $param->value, + \SQLSRV_PARAM_IN, + $param->phpType, + $param->sqlType, + ]; + } + + function bindAsciiText(?string $value, ?string $length = null, ?string $encoding = null): QueryParameter { + $encoding = $encoding ?: 'UTF-8'; + $length = $length ?: 'max'; + $phpType = \SQLSRV_PHPTYPE_STRING($encoding); + $sqlType = \SQLSRV_SQLTYPE_VARCHAR($length); + return new QueryParameter($value, $sqlType, $phpType); + } + + function bindText(?string $value, ?string $length = null, ?string $encoding = null): QueryParameter { + $encoding = $encoding ?: 'UTF-8'; + $length = $length ?: 'max'; + $phpType = \SQLSRV_PHPTYPE_STRING($encoding); + $sqlType = \SQLSRV_SQLTYPE_NVARCHAR($length); + return new QueryParameter($value, $sqlType, $phpType); + } + + function bindIdentifier(?string $value): QueryParameter { + $phpType = \SQLSRV_PHPTYPE_STRING('UTF-8'); + return new QueryParameter($value, \SQLSRV_SQLTYPE_UNIQUEIDENTIFIER, $phpType); + } + + function bindInt(?int $value): QueryParameter { + $sqlType = ($value == null || $value <= self::SQLSERVER_MAX_INT) ? \SQLSRV_SQLTYPE_INT : \SQLSRV_SQLTYPE_BIGINT; + return new QueryParameter($value, $sqlType, \SQLSRV_PHPTYPE_INT); + } + + function bindNumeric(?float $value, string $precision, string $scale): QueryParameter { + $sqlType = \SQLSRV_SQLTYPE_NUMERIC($precision, $scale); + return new QueryParameter($value, $sqlType, \SQLSRV_PHPTYPE_FLOAT); + } + + function bindDate(?\DateTimeInterface $value): QueryParameter { + return new QueryParameter($value, \SQLSRV_SQLTYPE_DATE, \SQLSRV_PHPTYPE_DATETIME); + } + + function bindDateTime(?\DateTimeInterface $value): QueryParameter { + return new QueryParameter($value, \SQLSRV_SQLTYPE_DATETIME2, \SQLSRV_PHPTYPE_DATETIME); + } + + function bindDateInterval(?\DateInterval $value): QueryParameter { + $phpType = \SQLSRV_PHPTYPE_STRING('UTF-8'); + return new QueryParameter($value, \SQLSRV_SQLTYPE_DATETIMEOFFSET, $phpType); + } /** * Injects LIMIT/OFFSET to the SQL query. diff --git a/src/Dibi/QueryParameter.php b/src/Dibi/QueryParameter.php new file mode 100644 index 000000000..eeb84d20f --- /dev/null +++ b/src/Dibi/QueryParameter.php @@ -0,0 +1,30 @@ +sqlType = $sqlType; + $this->phpType = $phpType; + $this->value = $value; + } + + public function __toString() : string + { + return "QueryParameter Value={$this->value} SqlType:{$this->sqlType} PhpType:{$this->phpType}"; + } +} diff --git a/src/Dibi/Translator.php b/src/Dibi/Translator.php index 1a7852e0a..a7d026cac 100644 --- a/src/Dibi/Translator.php +++ b/src/Dibi/Translator.php @@ -128,6 +128,11 @@ public function translate(array $args): string $arg = iterator_to_array($arg); } + if ($arg instanceof QueryParameter) { + $this->driver->addParameter($arg); + continue; + } + if (is_array($arg) && is_string(key($arg))) { // associative array -> autoselect between SET or VALUES & LIST if ($commandIns === null) { @@ -598,6 +603,11 @@ private function cb(array $matches): string } return ''; + } elseif ($mod === 'pq') { // parameterized query + if ($this->comment) { + return "parameterized query"; + } + return '?'; } else { // default processing $cursor++; return $this->formatValue($this->args[$cursor - 1], $mod); diff --git a/src/Dibi/interfaces.php b/src/Dibi/interfaces.php index 9f6b21223..14887add7 100644 --- a/src/Dibi/interfaces.php +++ b/src/Dibi/interfaces.php @@ -98,6 +98,24 @@ function escapeDateInterval(\DateInterval $value): string; */ function escapeLike(string $value, int $pos): string; + function addParameter(QueryParameter $param) : void; + + function bindText(?string $value, ?string $length = null, ?string $encoding = null): QueryParameter; + + function bindAsciiText(?string $value, ?string $length = null, ?string $encoding = null): QueryParameter; + + function bindIdentifier(?string $value): QueryParameter; + + function bindInt(?int $value): QueryParameter; + + function bindNumeric(?float $value, string $precision, string $scale): QueryParameter; + + function bindDate(?\DateTimeInterface $value): QueryParameter; + + function bindDateTime(?\DateTimeInterface $value): QueryParameter; + + function bindDateInterval(?\DateInterval $value): QueryParameter; + /** * Injects LIMIT/OFFSET to the SQL query. */ diff --git a/tests/databases.appveyor.ini b/tests/databases.appveyor.ini index 0e8c92437..cd2c46066 100644 --- a/tests/databases.appveyor.ini +++ b/tests/databases.appveyor.ini @@ -50,13 +50,13 @@ system = odbc ;password = "Password12!" ;system = sqlsrv -[sqlsrv 2012] -driver = sqlsrv -host = "(local)\SQL2012SP1" -database = master -username = sa -password = "Password12!" -system = sqlsrv +; [sqlsrv 2012] +; driver = sqlsrv +; host = "(local)\SQL2012SP1" +; database = master +; username = sa +; password = "Password12!" +; system = sqlsrv ;[sqlsrv 2012-pdo] ;driver = pdo @@ -79,3 +79,11 @@ system = sqlsrv ;username = sa ;password = "Password12!" ;system = sqlsrv + +[sqlsrv 2016] +driver = sqlsrv +host = "(local)\SQL2016" +database = master +username = sa +password = "Password12!" +system = sqlsrv diff --git a/tests/databases.github.ini b/tests/databases.github.ini new file mode 100644 index 000000000..40aeb4641 --- /dev/null +++ b/tests/databases.github.ini @@ -0,0 +1,89 @@ +[sqlite] ; default +driver = sqlite +database = :memory: +system = sqlite + +[sqlite pdo] +driver = pdo +dsn = "sqlite::memory:" +system = sqlite + +[mysql 5.7] +driver = mysqli +host = "127.0.0.1" +database = dibi_test +username = root +password = root +port = 3306 +system = mysql + +[mysql 5.7pdo] +driver = pdo +dsn = "mysql:host=127.0.0.1;port=3306;dbname=dibi_test" +user = root +password = root +system = mysql + +[mysql 8.0] +driver = mysqli +host = "127.0.0.1" +database = dibi_test +username = root +password = root +port = 3307 +system = mysql + +[mysql 8.0pdo] +driver = pdo +dsn = "mysql:host=127.0.0.1;port=3307;dbname=dibi_test" +user = root +password = root +system = mysql + +[postgre 9.6] +driver = postgre +host = "127.0.0.1" +database = dibi_test +username = postgres +password = postgres +port = 5432 +system = postgre + +[postgre 9.6pdo] +driver = pdo +dsn = "pgsql:host=127.0.0.1;port=5432;dbname=dibi_test" +user = postgres +password = postgres +system = postgre + +[postgre 13] +driver = postgre +host = "127.0.0.1" +database = dibi_test +username = postgres +password = postgres +port = 5433 +system = postgre + +[postgre 13pdo] +driver = pdo +dsn = "pgsql:host=127.0.0.1;port=5433;dbname=dibi_test" +user = postgres +password = postgres +system = postgre + +[sqlsrv] +driver = sqlsrv +host = "localhost" +username = SA +password = "YourStrong!Passw0rd" +database = dibi_test +port = 1433 +system = sqlsrv + +;[sqlsrv pdo] +;driver = pdo +;dsn = "sqlsrv:Server=localhost,1433;Database=dibi_test" +;user = SA +;password = "YourStrong!Passw0rd" +;system = sqlsrv diff --git a/tests/dibi/Connection.connect.phpt b/tests/dibi/Connection.connect.phpt index fbc7501a1..2977c8b17 100644 --- a/tests/dibi/Connection.connect.phpt +++ b/tests/dibi/Connection.connect.phpt @@ -62,6 +62,28 @@ test('', function () use ($config) { Assert::equal('hello', $conn->query('SELECT %s', 'hello')->fetchSingle()); }); +test('Binding to int Query Parameters', function () use ($config) { + $conn = new Connection($config); + $driver = $conn->getDriver(); + try { + Assert::equal(PHP_INT_MAX, intval($conn->query('SELECT %pq', $driver->bindInt(PHP_INT_MAX))->fetchSingle())); + Assert::equal(42, intval($conn->query("SELECT %pq;", $driver->bindInt(42))->fetchSingle())); // Some PDO drivers will convert to string types even when bound to non string values + Assert::null($conn->query('SELECT %pq', $driver->bindInt(null))->fetchSingle()); + } catch (Dibi\NotSupportedException $_) { + } +}); + +test('Binding to Text Query Parameters', function () use ($config) { + $conn = new Connection($config); + $driver = $conn->getDriver(); + try { + Assert::equal('hello', $conn->query('SELECT %pq', $driver->bindAsciiText('hello'))->fetchSingle()); + Assert::equal('hello', $conn->query('SELECT %pq', $driver->bindText('hello'))->fetchSingle()); + Assert::null($conn->query('SELECT %pq', $driver->bindText(null))->fetchSingle()); + Assert::null($conn->query('SELECT %pq', $driver->bindAsciiText(null))->fetchSingle()); + } catch (Dibi\NotSupportedException $_) { + } +}); test('', function () use ($config) { Assert::exception(function () use ($config) { diff --git a/tests/dibi/DateTime.phpt b/tests/dibi/DateTime.phpt index 5621c9b0f..df1f11661 100644 --- a/tests/dibi/DateTime.phpt +++ b/tests/dibi/DateTime.phpt @@ -7,7 +7,6 @@ use Tester\Assert; require __DIR__ . '/bootstrap.php'; - date_default_timezone_set('Europe/Prague'); Assert::same('1978-01-23 11:40:00.000000', (string) new DateTime(254400000)); @@ -15,7 +14,9 @@ Assert::same('1978-01-23 11:40:00.000000', (string) (new DateTime)->setTimestamp Assert::same(254400000, (new DateTime(254400000))->getTimestamp()); Assert::same('2050-08-13 11:40:00.000000', (string) new DateTime(2544000000)); -Assert::same('2050-08-13 11:40:00.000000', (string) (new DateTime)->setTimestamp(2544000000)); -Assert::same(is_int(2544000000) ? 2544000000 : '2544000000', (new DateTime(2544000000))->getTimestamp()); // 64 bit +if (is_int(2544000000)) { + Assert::same('2050-08-13 11:40:00.000000', (string) (new DateTime)->setTimestamp(2544000000)); // 64 bit only +} +Assert::same(is_int(2544000000) ? 2544000000 : false, (new DateTime(2544000000))->getTimestamp()); // 64 bit Assert::same('1978-05-05 00:00:00.000000', (string) new DateTime('1978-05-05')); diff --git a/tests/dibi/Sqlsrv.params.phpt b/tests/dibi/Sqlsrv.params.phpt new file mode 100644 index 000000000..4802feb8f --- /dev/null +++ b/tests/dibi/Sqlsrv.params.phpt @@ -0,0 +1,41 @@ +getDriver(); + + Assert::equal('hello', $conn->query('SELECT %pq', $driver->bindAsciiText('hello', '20'))->fetchSingle()); + + Assert::equal(1, $conn->query('INSERT INTO Customers ([name]) VALUES (%pq)', $driver->bindText('❤️‍🔥'))->getRowCount()); + Assert::equal('❤️‍🔥', $conn->fetchSingle('SELECT [name] FROM Customers WHERE [name] = %pq', $driver->bindText('❤️‍🔥'))); + + $param = $driver->bindText('testing', '20', 'UTF-8'); + $expectedSqlType = \SQLSRV_SQLTYPE_NVARCHAR('20'); + $expectedPhpType = \SQLSRV_PHPTYPE_STRING('UTF-8'); + Assert::equal($expectedSqlType, $param->sqlType); + Assert::equal('testing', $param->value); + Assert::equal($expectedPhpType, $param->phpType); + + Assert::equal('?????', $conn->query('SELECT %pq', $driver->bindAsciiText('❤️‍🔥'))->fetchSingle()); + + Assert::equal('❤️‍🔥', $conn->query('SELECT %pq', $driver->bindText('❤️‍🔥'))->fetchSingle()); + + Assert::equal(42, $conn->query('SELECT %pq', $driver->bindInt(42))->fetchSingle()); + + Assert::equal(null, $conn->query('SELECT %pq', $driver->bindText(null))->fetchSingle()); +}; + +$conn = new Dibi\Connection($config); +$conn->loadFile(__DIR__ . "/data/$config[system].sql"); + +$tests($conn); diff --git a/tests/dibi/data/sqlsrv.sql b/tests/dibi/data/sqlsrv.sql index 9be70d6ee..71f4df428 100644 --- a/tests/dibi/data/sqlsrv.sql +++ b/tests/dibi/data/sqlsrv.sql @@ -18,7 +18,7 @@ SET IDENTITY_INSERT products OFF; CREATE TABLE customers ( customer_id int NOT NULL IDENTITY(11,1), - name varchar(50) NOT NULL, + name nvarchar(50) NOT NULL, PRIMARY KEY(customer_id) );