3 declare(strict_types
=1);
7 use PhpMyAdmin\SqlParser\Components\Expression
;
11 use function mb_strlen
;
12 use function preg_replace
;
13 use function strcasecmp
;
18 private string $whereClause;
19 private bool $clauseIsUnique = true;
20 /** @var array<string, string> */
21 private array $conditionArray = [];
24 * Function to generate unique condition for specified row.
26 * @param FieldMetadata[] $fieldsMeta meta information about fields
27 * @param array $row current row
28 * @param bool $forceUnique generate condition only on pk or unique
29 * @param string|bool $restrictToTable restrict the unique condition to this table or false if none
30 * @param Expression[] $expressions An array of Expression instances.
31 * @psalm-param array<int, mixed> $row
33 public function __construct(
36 bool $forceUnique = false,
37 string|
bool $restrictToTable = false,
38 array $expressions = [],
40 $fieldsCount = count($fieldsMeta);
43 $nonPrimaryCondition = '';
44 $preferredCondition = '';
45 $primaryKeyArray = [];
47 $nonPrimaryConditionArray = [];
49 foreach ($fieldsMeta as $i => $meta) {
50 // do not use a column alias in a condition
51 if ($meta->orgname
=== '') {
52 $meta->orgname
= $meta->name
;
54 foreach ($expressions as $expression) {
56 $expression->alias
=== null ||
$expression->alias
=== ''
57 ||
$expression->column
=== null ||
$expression->column
=== ''
62 if (strcasecmp($meta->name
, $expression->alias
) == 0) {
63 $meta->orgname
= $expression->column
;
69 // Do not use a table alias in a condition.
71 // select * from galerie x WHERE
72 //(select count(*) from galerie y where y.datum=x.datum)>1
74 // Also, do not use the original table name if we are dealing with
75 // a view because this view might be updatable.
76 // (The isView() verification should not be costly in most cases
77 // because there is some caching in the function).
79 $meta->table
!== $meta->orgtable
80 && ! DatabaseInterface
::getInstance()->getTable(Current
::$database, $meta->table
)->isView()
82 $meta->table
= $meta->orgtable
;
85 // If this field is not from the table which the unique clause needs
86 // to be restricted to.
87 if ($restrictToTable && $restrictToTable != $meta->table
) {
91 // to fix the bug where float fields (primary or not)
92 // can't be matched because of the imprecision of
93 // floating comparison, use CONCAT
94 // (also, the syntax "CONCAT(field) IS NULL"
95 // that we need on the next "if" will work)
96 if ($meta->isType(FieldMetadata
::TYPE_REAL
)) {
97 $conKey = 'CONCAT(' . Util
::backquote($meta->table
) . '.'
98 . Util
::backquote($meta->orgname
) . ')';
100 $conKey = Util
::backquote($meta->table
) . '.'
101 . Util
::backquote($meta->orgname
);
104 $condition = ' ' . $conKey . ' ';
106 [$conVal, $condition] = $this->getConditionValue(
114 if ($conVal === null) {
118 $condition .= $conVal . ' AND';
120 if ($meta->isPrimaryKey()) {
121 $primaryKey .= $condition;
122 $primaryKeyArray[$conKey] = $conVal;
123 } elseif ($meta->isUniqueKey()) {
124 $uniqueKey .= $condition;
125 $uniqueKeyArray[$conKey] = $conVal;
128 $nonPrimaryCondition .= $condition;
129 $nonPrimaryConditionArray[$conKey] = $conVal;
132 // Correction University of Virginia 19991216:
133 // prefer primary or unique keys for condition,
134 // but use conjunction of all values if no primary key
135 if ($primaryKey !== '') {
136 $preferredCondition = $primaryKey;
137 $this->conditionArray
= $primaryKeyArray;
138 } elseif ($uniqueKey !== '') {
139 $preferredCondition = $uniqueKey;
140 $this->conditionArray
= $uniqueKeyArray;
141 } elseif (! $forceUnique) {
142 $preferredCondition = $nonPrimaryCondition;
143 $this->conditionArray
= $nonPrimaryConditionArray;
144 $this->clauseIsUnique
= false;
147 $this->whereClause
= trim((string) preg_replace('|\s?AND$|', '', $preferredCondition));
150 public function getWhereClause(): string
152 return $this->whereClause
;
155 public function isClauseUnique(): bool
157 return $this->clauseIsUnique
;
160 /** @return array<string, string> */
161 public function getConditionArray(): array
163 return $this->conditionArray
;
167 * Build a condition and with a value
169 * @param string|int|float|null $row The row value
170 * @param FieldMetadata $meta The field metadata
171 * @param int $fieldsCount A number of fields
172 * @param string $conditionKey A key used for BINARY fields functions
173 * @param string $condition The condition
175 * @return array<int,string|null>
176 * @psalm-return array{string|null, string}
178 private function getConditionValue(
179 string|
int|
float|
null $row,
182 string $conditionKey,
186 return ['IS NULL', $condition];
189 $conditionValue = '';
190 $isBinaryString = $meta->isType(FieldMetadata
::TYPE_STRING
) && $meta->isBinary();
191 // 63 is the binary charset, see: https://dev.mysql.com/doc/internals/en/charsets.html
192 $isBlobAndIsBinaryCharset = $meta->isType(FieldMetadata
::TYPE_BLOB
) && $meta->charsetnr
=== 63;
193 if ($meta->isNumeric
) {
194 $conditionValue = '= ' . $row;
195 } elseif ($isBlobAndIsBinaryCharset ||
($row != 0 && $isBinaryString)) {
196 // hexify only if this is a true not empty BLOB or a BINARY
198 // do not waste memory building a too big condition
199 $rowLength = mb_strlen((string) $row);
200 if ($rowLength > 0 && $rowLength < 1000) {
201 // use a CAST if possible, to avoid problems
202 // if the field contains wildcard characters % or _
203 $conditionValue = '= CAST(0x' . bin2hex((string) $row) . ' AS BINARY)';
204 } elseif ($fieldsCount === 1) {
205 // when this blob is the only field present
206 // try settling with length comparison
207 $condition = ' CHAR_LENGTH(' . $conditionKey . ') ';
208 $conditionValue = ' = ' . $rowLength;
210 // this blob won't be part of the final condition
211 $conditionValue = null;
213 } elseif ($meta->isMappedTypeGeometry
&& $row != 0) {
214 // do not build a too big condition
215 if (mb_strlen((string) $row) < 5000) {
216 $condition .= '= CAST(0x' . bin2hex((string) $row) . ' AS BINARY)';
220 } elseif ($meta->isMappedTypeBit
) {
221 $conditionValue = "= b'" . Util
::printableBitValue((int) $row, $meta->length
) . "'";
223 $conditionValue = '= ' . DatabaseInterface
::getInstance()->quoteString((string) $row);
226 return [$conditionValue, $condition];