Skip to content

Commit

Permalink
Merge pull request #230 from nextras/mssql
Browse files Browse the repository at this point in the history
add AppVeyor & MSSQL support
  • Loading branch information
hrach committed Sep 2, 2017
2 parents 548faa1 + 04fd29f commit 62e357c
Show file tree
Hide file tree
Showing 32 changed files with 496 additions and 124 deletions.
43 changes: 43 additions & 0 deletions .appveyor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
build: off
cache:
- c:\php -> appveyor.yml
- '%LOCALAPPDATA%\Composer\files -> appveyor.yml'

clone_folder: c:\projects\orm

services:
- mssql2016

init:
- SET PATH=c:\php;%PATH%
- SET PHP=1
- SET ANSICON=121x90 (121x90)

install:
# Install PHP
- IF EXIST c:\php (SET PHP=0) ELSE (mkdir c:\php)
- IF %PHP%==1 cd c:\php
- IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/releases/archives/php-7.1.0-Win32-VC14-x64.zip
- IF %PHP%==1 7z x php-7.1.0-Win32-VC14-x64.zip >nul
- IF %PHP%==1 echo extension_dir=ext >> php.ini
- IF %PHP%==1 echo extension=php_openssl.dll >> php.ini
- IF %PHP%==1 echo extension=php_sqlsrv_ts.dll >> php.ini
- IF %PHP%==1 appveyor DownloadFile https://github.com/Microsoft/msphpsql/releases/download/4.1.4-Windows/7.1.zip
- IF %PHP%==1 7z x 7.1.zip >nul
- IF %PHP%==1 copy 7.1\x64\php_sqlsrv_71_ts.dll ext\php_sqlsrv_ts.dll
- IF %PHP%==1 del /Q *.zip
- cd c:\projects\orm

# Install Nette Tester
- appveyor DownloadFile https://getcomposer.org/composer.phar
- php composer.phar install --prefer-dist --no-interaction --no-progress

# Create sections.ini, config.mssql.neon & php.ini
- copy tests\sections.appveyor.ini tests\sections.ini
- copy tests\config.array.sample.neon tests\config.array.neon
- copy tests\config.mssql.sample.neon tests\config.mssql.neon
- copy tests\php-win.ini tests\php.ini

test_script:
- sqlcmd -S ".\SQL2016" -U sa -P Password12! -Q "CREATE DATABASE nextras_orm_test" -d "master"
- vendor\bin\tester -p php -c tests\php.ini --setup tests\inc\setup.php tests\cases
6 changes: 3 additions & 3 deletions src/Mapper/Dbal/DbalCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public function __construct(DbalMapper $mapper, Connection $connection, QueryBui

public function getBy(array $where)
{
return $this->findBy($where)->limitBy(1)->fetch();
return $this->findBy($where)->fetch();
}


Expand Down Expand Up @@ -269,11 +269,11 @@ protected function getIteratorCount(): int
foreach ($primary as $column) {
$builder->addSelect('%table.%column', $builder->getFromAlias(), $column);
}
$sql = 'SELECT COUNT(*) FROM (' . $builder->getQuerySql() . ') temp';
$sql = 'SELECT COUNT(*) AS count FROM (' . $builder->getQuerySql() . ') temp';
$args = $builder->getQueryParameters();

} else {
$builder->select('COUNT(*)');
$builder->select('COUNT(*) AS count');
$builder->orderBy(null);
$sql = $builder->getQuerySql();
$args = $builder->getQueryParameters();
Expand Down
10 changes: 6 additions & 4 deletions src/Mapper/Dbal/DbalMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use Nextras\Orm\Mapper\BaseMapper;
use Nextras\Orm\Mapper\IMapper;
use Nextras\Orm\Mapper\IRelationshipMapper;
use Nextras\Orm\NotSupportedException;
use Nextras\Orm\StorageReflection\IStorageReflection;


Expand Down Expand Up @@ -102,7 +103,6 @@ public function toCollection($data): ICollection
public function toEntity($data)
{
if ($data instanceof QueryBuilder) {
$data->limitBy(1);
$data = $this->connection->queryByQueryBuilder($data);
}
if ($data instanceof Result) {
Expand Down Expand Up @@ -296,11 +296,13 @@ protected function processUpdate(IEntity $entity, $data, $primary)

protected function processAutoupdate(IEntity $entity, array $args)
{
$platform = $this->connection->getPlatform();
if ($platform instanceof PostgreSqlPlatform) {
$platform = $this->connection->getPlatform()->getName();
if ($platform === 'pgsql') {
$this->processPostgreAutoupdate($entity, $args);
} else {
} elseif ($platform === 'mysql') {
$this->processMySQLAutoupdate($entity, $args);
} else {
throw new NotSupportedException();
}
}

Expand Down
94 changes: 66 additions & 28 deletions src/Mapper/Dbal/RelationshipMapperManyHasMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,17 +127,8 @@ private function fetchByTwoPassStrategy(QueryBuilder $builder, array $values): M
$builder->addSelect('%column', "$targetTable.$this->primaryKeyTo");
$builder->addSelect('%column', "$targetTable.$this->primaryKeyFrom");

if ($builder->hasLimitOffsetClause()) { // todo !== 1
$sqls = $args = [];
foreach ($values as $value) {
$builderPart = clone $builder;
$builderPart->andWhere('%column = %any', "$targetTable.$this->primaryKeyFrom", $value);
$sqls[] = $builderPart->getQuerySql();
$args = array_merge($args, $builderPart->getQueryParameters());
}

$query = '(' . implode(') UNION ALL (', $sqls) . ')';
$result = $this->connection->queryArgs($query, $args);
if ($builder->hasLimitOffsetClause()) {
$result = $this->processMultiResult($builder, $values, $targetTable);

} else {
$builder->andWhere('%column IN %any', "$targetTable.$this->primaryKeyFrom", $values);
Expand Down Expand Up @@ -213,25 +204,13 @@ private function fetchCounts(QueryBuilder $builder, array $values)
"{$sourceTable}." . $this->targetMapper->getStorageReflection()->getStoragePrimaryKey()[0]
);
$builder->addSelect('%column', "$targetTable.$this->primaryKeyFrom");
$builder->orderBy(null);

if ($builder->hasLimitOffsetClause()) {
$sqls = [];
$args = [];
foreach ($values as $value) {
$build = clone $builder;
$build->andWhere("%column = %any", $this->primaryKeyFrom, $value);
$sqls[] = "SELECT %any AS %column, COUNT(*) AS [count] FROM (" . $build->getQuerySql() . ') [temp]';
$args[] = $value;
$args[] = $this->primaryKeyFrom;
$args = array_merge($args, $build->getQueryParameters());
}

$sql = '(' . implode(') UNION ALL (', $sqls) . ')';
$result = $this->connection->queryArgs($sql, $args);
$result = $this->processMultiCountResult($builder, $values);

} else {
$builder->addSelect('COUNT(%column) as count', $this->primaryKeyTo);
$builder->orderBy(null);
$builder->andWhere('%column IN %any', $this->primaryKeyFrom, $values);
$builder->groupBy('%column', $this->primaryKeyFrom);
$result = $this->connection->queryArgs($builder->getQuerySql(), $builder->getQueryParameters());
Expand Down Expand Up @@ -269,10 +248,9 @@ public function remove(IEntity $parent, array $remove)
$this->mapperCoordinator->beginTransaction();
$list = $this->buildList($parent, $remove);
$this->connection->query(
'DELETE FROM %table WHERE (%column[]) IN %any',
'DELETE FROM %table WHERE %multiOr',
$this->joinTable,
array_keys(reset($list)),
array_map('array_values', $list)
$list
);
}

Expand All @@ -296,6 +274,66 @@ protected function buildList(IEntity $parent, array $entries): array
}


protected function processMultiResult(QueryBuilder $builder, array $values, string $targetTable)
{
if ($this->connection->getPlatform()->getName() === 'mssql') {
$result = [];
foreach ($values as $value) {
$builderPart = clone $builder;
$builderPart->andWhere('%column = %any', "$targetTable.$this->primaryKeyFrom", $value);
$result = array_merge($this->connection->queryByQueryBuilder($builderPart)->fetchAll(), $result);
}
return $result;

} else {
$sqls = $args = [];
foreach ($values as $value) {
$builderPart = clone $builder;
$builderPart->andWhere('%column = %any', "$targetTable.$this->primaryKeyFrom", $value);
$sqls[] = $builderPart->getQuerySql();
$args = array_merge($args, $builderPart->getQueryParameters());
}

$query = '(' . implode(') UNION ALL (', $sqls) . ')';
return $this->connection->queryArgs($query, $args);
}
}


protected function processMultiCountResult(QueryBuilder $builder, array $values)
{
if ($this->connection->getPlatform()->getName() === 'mssql') {
$result = [];
foreach ($values as $value) {
$builderPart = clone $builder;
$builderPart->andWhere("%column = %any", $this->primaryKeyFrom, $value);
$result = array_merge($this->connection->queryArgs(
"SELECT %any AS %column, COUNT(*) AS [count] FROM (" . $builderPart->getQuerySql() . ') [temp]',
array_merge([$value, $this->primaryKeyFrom], $builderPart->getQueryParameters())
)->fetchAll(), $result);
}
return $result;

} else {
$sqls = [];
$args = [];
$builder->orderBy(null);
foreach ($values as $value) {
$builderPart = clone $builder;
$builderPart->andWhere("%column = %any", $this->primaryKeyFrom, $value);
$sqls[] = "SELECT %any AS %column, COUNT(*) AS [count] FROM (" . $builderPart->getQuerySql() . ') [temp]';
$args[] = $value;
$args[] = $this->primaryKeyFrom;
$args = array_merge($args, $builderPart->getQueryParameters());
}

$sql = '(' . implode(') UNION ALL (', $sqls) . ')';
$result = $this->connection->queryArgs($sql, $args);
return $result;
}
}


protected function calculateCacheKey(QueryBuilder $builder, array $values): string
{
return md5($builder->getQuerySql() . json_encode($builder->getQueryParameters()) . json_encode($values));
Expand Down
94 changes: 67 additions & 27 deletions src/Mapper/Dbal/RelationshipMapperOneHasMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,24 +123,14 @@ protected function fetchByTwoPassStrategy(QueryBuilder $builder, array $values):
$builder->addSelect("[$key]");
}

$sqls = $args = [];
foreach ($values as $primaryValue) {
$builderPart = clone $builder;
$builderPart->andWhere("%column = %any", $this->joinStorageKey, $primaryValue);

$sqls[] = $builderPart->getQuerySql();
$args = array_merge($args, $builderPart->getQueryParameters());
}

$query = '(' . implode(') UNION ALL (', $sqls) . ')';
$result = $this->connection->queryArgs($query, $args);
$result = $this->processMultiResult($builder, $values);

$map = $ids = [];
if ($isComposite) {
foreach ($result as $row) {
$id = [];
foreach ($targetPrimaryKey as $key) {
$id[] = $row->{$key};
$id[$key] = $row->{$key};
}

$ids[] = $id;
Expand All @@ -161,7 +151,7 @@ protected function fetchByTwoPassStrategy(QueryBuilder $builder, array $values):

if ($isComposite) {
$builder = $this->targetMapper->builder();
$builder->andWhere('(%column[]) IN %any', $targetPrimaryKey, $ids);
$builder->andWhere('%multiOr', $ids);

$entitiesResult = [];
$collection = $this->targetMapper->toCollection($builder);
Expand Down Expand Up @@ -222,24 +212,12 @@ private function fetchCounts(QueryBuilder $builder, array $values)

$builder = clone $builder;
$builder->addSelect('%column', "{$sourceTable}.{$this->joinStorageKey}");
$builder->orderBy(null);

if ($builder->hasLimitOffsetClause()) {
$sqls = [];
$args = [];
foreach ($values as $value) {
$build = clone $builder;
$build->andWhere('%column = %any', "{$sourceTable}.{$this->joinStorageKey}", $value);
$sqls[] = "SELECT %any AS %column, COUNT(*) AS [count] FROM (" . $build->getQuerySql() . ') [temp]';
$args[] = $value;
$args[] = $this->joinStorageKey;
$args = array_merge($args, $build->getQueryParameters());
}

$sql = '(' . implode(') UNION ALL (', $sqls) . ')';
$result = $this->connection->queryArgs($sql, $args);
$result = $this->processMultiCountResult($builder, $values);

} else {
$builder->orderBy(null);
$builder->addSelect('COUNT(%column) AS [count]', "{$sourceTable}.{$targetStoragePrimaryKey}");
$builder->andWhere('%column IN %any', "{$sourceTable}.{$this->joinStorageKey}", $values);
$builder->groupBy('%column', "{$sourceTable}.{$this->joinStorageKey}");
Expand All @@ -254,6 +232,68 @@ private function fetchCounts(QueryBuilder $builder, array $values)
}


protected function processMultiResult(QueryBuilder $builder, array $values)
{
if ($this->connection->getPlatform()->getName() === 'mssql') {
$result = [];
foreach ($values as $primaryValue) {
$builderPart = clone $builder;
$builderPart->andWhere("%column = %any", $this->joinStorageKey, $primaryValue);
$result = array_merge($this->connection->queryByQueryBuilder($builderPart)->fetchAll(), $result);
}
return $result;

} else {
$sqls = $args = [];
foreach ($values as $primaryValue) {
$builderPart = clone $builder;
$builderPart->andWhere("%column = %any", $this->joinStorageKey, $primaryValue);
$sqls[] = $builderPart->getQuerySql();
$args = array_merge($args, $builderPart->getQueryParameters());
}

$query = '(' . implode(') UNION ALL (', $sqls) . ')';
return $this->connection->queryArgs($query, $args);
}
}


protected function processMultiCountResult(QueryBuilder $builder, array $values)
{
$sourceTable = $builder->getFromAlias();

if ($this->connection->getPlatform()->getName() === 'mssql') {
$result = [];
foreach ($values as $value) {
$builderPart = clone $builder;
$builderPart->andWhere('%column = %any', "{$sourceTable}.{$this->joinStorageKey}", $value);
$result = array_merge($this->connection->queryArgs(
"SELECT %any AS %column, COUNT(*) AS [count] FROM (" . $builderPart->getQuerySql() . ') [temp]',
array_merge([$value, $this->joinStorageKey], $builderPart->getQueryParameters())
)->fetchAll(), $result);
}
return $result;

} else {
$sqls = [];
$args = [];
$builder->orderBy(null);
foreach ($values as $value) {
$builderPart = clone $builder;
$builderPart->andWhere('%column = %any', "{$sourceTable}.{$this->joinStorageKey}", $value);
$sqls[] = "SELECT %any AS %column, COUNT(*) AS [count] FROM (" . $builderPart->getQuerySql() . ') [temp]';
$args[] = $value;
$args[] = $this->joinStorageKey;
$args = array_merge($args, $builderPart->getQueryParameters());
}

$sql = '(' . implode(') UNION ALL (', $sqls) . ')';
$result = $this->connection->queryArgs($sql, $args);
return $result;
}
}


protected function calculateCacheKey(QueryBuilder $builder, array $values): string
{
return md5($builder->getQuerySql() . json_encode($builder->getQueryParameters()) . json_encode($values));
Expand Down
4 changes: 4 additions & 0 deletions src/Mapper/Dbal/StorageReflection/StorageReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,10 @@ protected function getDefaultModifiers(): array
$types = ['DATETIME' => true];
break;

case 'mssql':
$types = ['TIMESTAMP' => true];
break;

default:
throw new NotSupportedException();
}
Expand Down
10 changes: 8 additions & 2 deletions src/Repository/Functions/ValueOperatorFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,10 @@ private function qbEqualOperator($column, $value)
if (is_array($value)) {
if ($value) {
if (is_array($column)) {
return ['(%column[]) IN %any', $column, $value];
$value = array_map(function ($value) use ($column) {
return array_combine($column, $value);
}, $value);
return ['%multiOr', $value];
} else {
return ['%column IN %any', $column, $value];
}
Expand All @@ -118,7 +121,10 @@ private function qbNotEqualOperator($column, $value)
if (is_array($value)) {
if ($value) {
if (is_array($column)) {
return ['(%column[]) NOT IN %any', $column, $value];
$value = array_map(function ($value) use ($column) {
return array_combine($column, $value);
}, $value);
return ['NOT (%multiOr)', $value];
} else {
return ['%column NOT IN %any', $column, $value];
}
Expand Down
Loading

0 comments on commit 62e357c

Please sign in to comment.