Skip to content

Commit b387ac4

Browse files
committed
Merging 5.1 into master
2 parents 27db626 + 9f9dac2 commit b387ac4

22 files changed

+769
-157
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ vendor/*
1717
/phpbench.sh
1818
/xdebug/
1919
.php_cs.cache
20-
tdbm.lock.yml
20+
tdbm.lock.yml

composer.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
],
2020
"require" : {
2121
"php" : ">=7.1",
22-
"mouf/magic-query" : "^1.4.0",
22+
"mouf/magic-query" : "^1.4",
2323
"mouf/schema-analyzer": "^1.1.4",
2424
"doctrine/dbal": "^2.9.2",
2525
"psr/log": "~1.0",
@@ -32,20 +32,21 @@
3232
"mouf/utils.log.psr.multi-logger": "^1.0",
3333
"symfony/filesystem": "^2.7 || ^3 || ^4 || ^5",
3434
"ramsey/uuid": "^3.7",
35-
"doctrine/annotations": "^1.6",
35+
"doctrine/annotations": "^1.10",
3636
"zendframework/zend-code": "^3.4",
3737
"psr/container": "^1",
38+
"brain-diminished/schema-version-control": "^1.0.2",
3839
"ext-PDO": "*",
3940
"ext-json": "*",
4041
"ext-hash": "*",
4142
"ext-filter": "*",
42-
"brain-diminished/schema-version-control": "^1.0.2"
43+
"ext-intl": "*"
4344
},
4445
"require-dev" : {
4546
"phpunit/phpunit": "^7.4.4 || ^8.0.0",
4647
"php-coveralls/php-coveralls": "^2.1",
4748
"wa72/simplelogger" : "^1.0",
48-
"friendsofphp/php-cs-fixer": "^2.15.1",
49+
"friendsofphp/php-cs-fixer": "^2.16",
4950
"symfony/process": "^3 || ^4 || ^5",
5051
"thecodingmachine/tdbm-fluid-schema-builder": "^1.0.0",
5152
"phpstan/phpstan": "^0.11.5",

src/InnerResultArray.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ private function toIndex($offset): void
102102
}
103103
}
104104

105-
public function next()
105+
public function next(): void
106106
{
107107
// Let's overload the next() method to store the result.
108108
if (isset($this->results[$this->key + 1])) {

src/InnerResultIterator.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ protected function executeQuery(): void
106106

107107
$this->logger->debug('Running SQL request: '.$sql);
108108

109-
$this->statement = $this->tdbmService->getConnection()->executeQuery($sql, $this->parameters, DbalUtils::generateArrayTypes($this->parameters));
109+
$this->statement = $this->tdbmService->getConnection()->executeQuery($sql, $this->parameters, DbalUtils::generateTypes($this->parameters));
110110

111111
$this->fetchStarted = true;
112112
}
@@ -140,7 +140,7 @@ private function getRowCountViaSqlQuery(): int
140140

141141
$this->logger->debug('Running count SQL request: '.$countSql);
142142

143-
$this->count = (int) $this->tdbmService->getConnection()->fetchColumn($countSql, $this->parameters, 0, DbalUtils::generateArrayTypes($this->parameters));
143+
$this->count = (int) $this->tdbmService->getConnection()->fetchColumn($countSql, $this->parameters, 0, DbalUtils::generateTypes($this->parameters));
144144
return $this->count;
145145
}
146146

@@ -168,17 +168,21 @@ public function key()
168168
* Advances the cursor to the next result.
169169
* Casts the database result into one (or several) beans.
170170
*/
171-
public function next()
171+
public function next(): void
172172
{
173173
$row = $this->statement->fetch(\PDO::FETCH_ASSOC);
174174
if ($row) {
175175

176176
// array<tablegroup, array<table, array<column, value>>>
177177
$beansData = [];
178+
$allNull = true;
178179
foreach ($row as $key => $value) {
179180
if (!isset($this->columnDescriptors[$key])) {
180181
continue;
181182
}
183+
if ($allNull !== false && $value !== null) {
184+
$allNull = false;
185+
}
182186

183187
$columnDescriptor = $this->columnDescriptors[$key];
184188

@@ -192,6 +196,10 @@ public function next()
192196

193197
$beansData[$columnDescriptor['tableGroup']][$columnDescriptor['table']][$columnDescriptor['column']] = $value;
194198
}
199+
if ($allNull === true) {
200+
$this->next();
201+
return;
202+
}
195203

196204
$reflectionClassCache = [];
197205
$firstBean = true;

src/QueryFactory/AbstractQueryFactory.php

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,21 +181,35 @@ protected function getColumnsList(string $mainTable, array $additionalTablesFetc
181181
foreach ($allFetchedTables as $table) {
182182
foreach ($this->schema->getTable($table)->getColumns() as $column) {
183183
$columnName = $column->getName();
184-
$columnDescList[$table.'____'.$columnName] = [
185-
'as' => $table.'____'.$columnName,
184+
$alias = self::getColumnAlias($table, $columnName);
185+
$columnDescList[$alias] = [
186+
'as' => $alias,
186187
'table' => $table,
187188
'column' => $columnName,
188189
'type' => $column->getType(),
189190
'tableGroup' => $tableGroups[$table],
190191
];
191-
$columnsList[] = $mysqlPlatform->quoteIdentifier($table).'.'.$mysqlPlatform->quoteIdentifier($columnName).' as '.
192-
$connection->quoteIdentifier($table.'____'.$columnName);
192+
$columnsList[] = sprintf(
193+
'%s.%s as %s',
194+
$mysqlPlatform->quoteIdentifier($table),
195+
$mysqlPlatform->quoteIdentifier($columnName),
196+
$connection->quoteIdentifier($alias)
197+
);
193198
}
194199
}
195200

196201
return [$columnDescList, $columnsList, $reconstructedOrderBy];
197202
}
198203

204+
public static function getColumnAlias(string $tableName, string $columnName): string
205+
{
206+
$alias = $tableName.'____'.$columnName;
207+
if (strlen($alias) <= 30) { // Older oracle version had a limit of 30 characters for identifiers
208+
return $alias;
209+
}
210+
return substr($columnName, 0, 20) . crc32($tableName.'____'.$columnName);
211+
}
212+
199213
abstract protected function compute(): void;
200214

201215
/**

src/QueryFactory/FindObjectsFromRawSqlQueryFactory.php

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,16 @@ private function processParsedUnionQuery(array $parsedSql, ?string $sqlCount): a
144144
private function processParsedSelectQuery(array $parsedSql, ?string $sqlCount): array
145145
{
146146
// 1: let's reformat the SELECT and construct our columns
147-
list($select, $columnDescriptors) = $this->formatSelect($parsedSql['SELECT']);
147+
list($select, $countSelect, $columnDescriptors) = $this->formatSelect($parsedSql['SELECT']);
148148
$generator = new PHPSQLCreator();
149149
$parsedSql['SELECT'] = $select;
150150
$processedSql = $generator->create($parsedSql);
151151

152152
// 2: let's compute the count query if needed
153153
if ($sqlCount === null) {
154-
$parsedSqlCount = $this->generateParsedSqlCount($parsedSql);
154+
$parsedCountSql = $parsedSql;
155+
$parsedCountSql['SELECT'] = $countSelect;
156+
$parsedSqlCount = $this->generateParsedSqlCount($parsedCountSql);
155157
$processedSqlCount = $generator->create($parsedSqlCount);
156158
} else {
157159
$processedSqlCount = $sqlCount;
@@ -173,40 +175,46 @@ private function formatSelect(array $baseSelect): array
173175

174176
$connection = $this->tdbmService->getConnection();
175177
$formattedSelect = [];
178+
$formattedCountSelect = [];
176179
$columnDescriptors = [];
177180
$fetchedTables = [];
178181

179182
foreach ($baseSelect as $entry) {
180183
if ($entry['expr_type'] !== 'colref') {
181184
$formattedSelect[] = $entry;
185+
$formattedCountSelect[] = $entry;
182186
continue;
183187
}
184188

185189
$noQuotes = $entry['no_quotes'];
186190
if ($noQuotes['delim'] !== '.' || count($noQuotes['parts']) !== 2) {
187191
$formattedSelect[] = $entry;
192+
$formattedCountSelect[] = $entry;
188193
continue;
189194
}
190195

191196
$tableName = $noQuotes['parts'][0];
192197
if (!in_array($tableName, $relatedTables)) {
193198
$formattedSelect[] = $entry;
199+
$formattedCountSelect[] = $entry;
194200
continue;
195201
}
196202

197203
$columnName = $noQuotes['parts'][1];
198204
if ($columnName !== '*') {
199205
$formattedSelect[] = $entry;
206+
$formattedCountSelect[] = $entry;
200207
continue;
201208
}
202209

203210
$table = $this->schema->getTable($tableName);
211+
$pkColumns = $table->getPrimaryKeyColumns();
204212
foreach ($table->getColumns() as $column) {
205213
$columnName = $column->getName();
206-
$alias = "{$tableName}____{$columnName}";
207-
$formattedSelect[] = [
214+
$alias = AbstractQueryFactory::getColumnAlias($tableName, $columnName);
215+
$astColumn = [
208216
'expr_type' => 'colref',
209-
'base_expr' => $connection->quoteIdentifier($tableName).'.'.$connection->quoteIdentifier($columnName),
217+
'base_expr' => $connection->quoteIdentifier($tableName) . '.' . $connection->quoteIdentifier($columnName),
210218
'no_quotes' => [
211219
'delim' => '.',
212220
'parts' => [
@@ -219,7 +227,10 @@ private function formatSelect(array $baseSelect): array
219227
'name' => $alias,
220228
]
221229
];
222-
230+
$formattedSelect[] = $astColumn;
231+
if (in_array($columnName, $pkColumns)) {
232+
$formattedCountSelect[] = $astColumn;
233+
}
223234
$columnDescriptors[$alias] = [
224235
'as' => $alias,
225236
'table' => $tableName,
@@ -241,7 +252,13 @@ private function formatSelect(array $baseSelect): array
241252
$formattedSelect[$i]['delim'] = ',';
242253
}
243254
}
244-
return [$formattedSelect, $columnDescriptors];
255+
256+
for ($i = 0; $i < count($formattedCountSelect) - 1; $i++) {
257+
if (!isset($formattedCountSelect[$i]['delim'])) {
258+
$formattedCountSelect[$i]['delim'] = ',';
259+
}
260+
}
261+
return [$formattedSelect, $formattedCountSelect, $columnDescriptors];
245262
}
246263

247264
/**

src/QueryFactory/QueryFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public function getMagicSqlCount() : string;
3939
public function getMagicSqlSubQuery() : string;
4040

4141
/**
42-
* @return mixed[][] An array of column descriptors. The key is in the form "$tableName____$columnName". Value is an array with those keys: as, table, column, type, tableGroup
42+
* @return mixed[][] An array of column descriptors. The key might be in the form "$tableName____$columnName". Value is an array with those keys: as, table, column, type, tableGroup
4343
*/
4444
public function getColumnDescriptors() : array;
4545

src/ResultIterator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ protected function executeCountQuery(): void
112112
{
113113
$sql = $this->magicQuery->buildPreparedStatement($this->queryFactory->getMagicSqlCount(), $this->parameters);
114114
$this->logger->debug('Running count query: '.$sql);
115-
$this->totalCount = (int) $this->tdbmService->getConnection()->fetchColumn($sql, $this->parameters, 0, DbalUtils::generateArrayTypes($this->parameters));
115+
$this->totalCount = (int) $this->tdbmService->getConnection()->fetchColumn($sql, $this->parameters, 0, DbalUtils::generateTypes($this->parameters));
116116
}
117117

118118
/**

src/Utils/AbstractBeanPropertyDescriptor.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ public function getVariableName(): string
7575
return $this->namingStrategy->getVariableName($this);
7676
}
7777

78+
public function getSafeVariableName(): string
79+
{
80+
return '$' . StringUtils::getValidVariableName(ltrim($this->getVariableName(), '$'));
81+
}
82+
7883
public function getSetterName(): string
7984
{
8085
return $this->namingStrategy->getSetterName($this);
@@ -102,7 +107,7 @@ public function getConstructorAssignCode(): string
102107
{
103108
$str = '$this->%s(%s);';
104109

105-
return sprintf($str, $this->getSetterName(), $this->getVariableName());
110+
return sprintf($str, $this->getSetterName(), $this->getSafeVariableName());
106111
}
107112

108113
/**

src/Utils/BeanDescriptor.php

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Doctrine\DBAL\Schema\Schema;
99
use Doctrine\DBAL\Schema\Table;
1010
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
11+
use Doctrine\DBAL\Types\Type;
1112
use JsonSerializable;
1213
use Mouf\Database\SchemaAnalyzer\SchemaAnalyzer;
1314
use PhpParser\Comment\Doc;
@@ -348,7 +349,7 @@ private function getPropertiesForTable(Table $table): array
348349
foreach ($beanPropertyDescriptors as $beanDescriptor) {
349350
$name = $beanDescriptor->getGetterName();
350351
if (isset($names[$name])) {
351-
throw new TDBMException('Unsolvable name conflict while generating method name');
352+
throw new TDBMException('Unsolvable name conflict while generating method name "' . $name . '"');
352353
} else {
353354
$names[$name] = $beanDescriptor;
354355
}
@@ -376,7 +377,7 @@ private function generateBeanConstructor() : MethodGenerator
376377
$parentConstructorArguments = [];
377378

378379
foreach ($constructorProperties as $property) {
379-
$parameter = new ParameterGenerator(ltrim($property->getVariableName(), '$'));
380+
$parameter = new ParameterGenerator(ltrim($property->getSafeVariableName(), '$'));
380381
if ($property->isTypeHintable()) {
381382
$parameter->setType($property->getPhpType());
382383
}
@@ -387,7 +388,7 @@ private function generateBeanConstructor() : MethodGenerator
387388
if ($property->getTable()->getName() === $this->table->getName()) {
388389
$assigns[] = $property->getConstructorAssignCode()."\n";
389390
} else {
390-
$parentConstructorArguments[] = $property->getVariableName();
391+
$parentConstructorArguments[] = $property->getSafeVariableName();
391392
}
392393
}
393394

@@ -1296,7 +1297,7 @@ private function generateFindByDaoCodeForIndex(Index $index, string $beanNamespa
12961297
$first = true;
12971298
/** @var AbstractBeanPropertyDescriptor $element */
12981299
foreach ($elements as $element) {
1299-
$parameter = new ParameterGenerator(ltrim($element->getVariableName(), '$'));
1300+
$parameter = new ParameterGenerator(ltrim($element->getSafeVariableName(), '$'));
13001301
if (!$first && !($element->isCompulsory() && $index->isUnique())) {
13011302
$parameterType = '?';
13021303
//$functionParameter = '?';
@@ -1335,7 +1336,17 @@ private function generateFindByDaoCodeForIndex(Index $index, string $beanNamespa
13351336
foreach ($elements as $element) {
13361337
$params[] = $element->getParamAnnotation();
13371338
if ($element instanceof ScalarBeanPropertyDescriptor) {
1338-
$filterArrayCode .= ' '.var_export($element->getColumnName(), true).' => '.$element->getVariableName().",\n";
1339+
$typeName = $element->getDatabaseType()->getName();
1340+
if ($typeName === Type::DATETIME_IMMUTABLE) {
1341+
$filterArrayCode .= sprintf(
1342+
" %s => \$this->tdbmService->getConnection()->convertToDatabaseValue(%s, %s),\n",
1343+
var_export($element->getColumnName(), true),
1344+
$element->getSafeVariableName(),
1345+
var_export($typeName, true)
1346+
);
1347+
} else {
1348+
$filterArrayCode .= ' '.var_export($element->getColumnName(), true).' => '.$element->getSafeVariableName().",\n";
1349+
}
13391350
} elseif ($element instanceof ObjectBeanPropertyDescriptor) {
13401351
$foreignKey = $element->getForeignKey();
13411352
$columns = SafeFunctions::arrayCombine($foreignKey->getLocalColumns(), $foreignKey->getForeignColumns());
@@ -1346,14 +1357,14 @@ private function generateFindByDaoCodeForIndex(Index $index, string $beanNamespa
13461357
$targetedElement = new ScalarBeanPropertyDescriptor($foreignTable, $foreignTable->getColumn($foreignColumn), $this->namingStrategy, $this->annotationParser);
13471358
if ($first || $element->isCompulsory() && $index->isUnique()) {
13481359
// First parameter for index is not nullable
1349-
$filterArrayCode .= ' '.var_export($localColumn, true).' => '.$element->getVariableName().'->'.$targetedElement->getGetterName()."(),\n";
1360+
$filterArrayCode .= ' '.var_export($localColumn, true).' => '.$element->getSafeVariableName().'->'.$targetedElement->getGetterName()."(),\n";
13501361
} else {
13511362
// Other parameters for index is not nullable
1352-
$filterArrayCode .= ' '.var_export($localColumn, true).' => ('.$element->getVariableName().' !== null) ? '.$element->getVariableName().'->'.$targetedElement->getGetterName()."() : null,\n";
1363+
$filterArrayCode .= ' '.var_export($localColumn, true).' => ('.$element->getSafeVariableName().' !== null) ? '.$element->getSafeVariableName().'->'.$targetedElement->getGetterName()."() : null,\n";
13531364
}
13541365
}
13551366
}
1356-
$commentArguments[] = substr($element->getVariableName(), 1);
1367+
$commentArguments[] = substr($element->getSafeVariableName(), 1);
13571368
if ($first) {
13581369
$first = false;
13591370
}

src/Utils/DbalUtils.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
namespace TheCodingMachine\TDBM\Utils;
55

66
use Doctrine\DBAL\Connection;
7+
use Doctrine\DBAL\ParameterType;
78
use function is_array;
89
use function is_int;
910

@@ -14,11 +15,12 @@ class DbalUtils
1415
{
1516
/**
1617
* If a parameter is an array (used in a "IN" statement), we need to tell Doctrine about it.
18+
* If it is an integer we have to tell DBAL (default is string)
1719
* @see https://www.doctrine-project.org/projects/doctrine-dbal/en/2.9/reference/data-retrieval-and-manipulation.html#list-of-parameters-conversion
1820
* @param array<string, mixed> $parameters
1921
* @return array<string, int>
2022
*/
21-
public static function generateArrayTypes(array $parameters): array
23+
public static function generateTypes(array $parameters): array
2224
{
2325
$types = [];
2426
foreach ($parameters as $key => $value) {
@@ -30,6 +32,8 @@ public static function generateArrayTypes(array $parameters): array
3032
}
3133
}
3234
$types[$key] = Connection::PARAM_INT_ARRAY;
35+
} elseif (is_int($value)) {
36+
$types[$key] = ParameterType::INTEGER;
3337
}
3438
}
3539

0 commit comments

Comments
 (0)