Skip to content

Commit 25e77b1

Browse files
committed
Fix decimal normalization
1 parent 2338825 commit 25e77b1

2 files changed

Lines changed: 82 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
---
9+
## [1.3.8] – 2025-11-10
10+
### Fixed decimal normalization
11+
912
## [1.3.7] – 2025-10-28
1013
### Added
1114
- `whereRaw()` and `orWhereRaw()`:

src/PDOdb.php

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* @copyright Copyright (c) 2025 L. Fischer
99
* @license https://opensource.org/licenses/MIT MIT License
1010
* @link https://github.com/decMuc/PDOdb
11-
* @version 1.3.7
11+
* @version 1.3.8
1212
* @inspired-by https://github.com/ThingEngineer/PHP-MySQLi-Database-Class
1313
*/
1414

@@ -36,6 +36,7 @@
3636
final class PDOdb
3737
{
3838

39+
3940
// ==[ 01. Connection Management ]==
4041
protected static array $_instances = []; // All active DB instances
4142
protected array $connectionsSettings = []; // Connection config per instance
@@ -3631,6 +3632,7 @@ public function rawQuery(string $query, ?array $bindParams = null): mixed
36313632
if (!empty($bindParams)) {
36323633
foreach ($bindParams as $key => $value) {
36333634
$paramKey = is_int($key) ? $key + 1 : $key;
3635+
$value = $this->_normalizeDecimalStringSimple($value);
36343636
$stmt->bindValue($paramKey, $value);
36353637
}
36363638
}
@@ -3739,10 +3741,13 @@ public function rawQueryValue(string $query, ?array $bindParams = null): mixed
37393741
protected function _bindParam(mixed $value): void
37403742
{
37413743
if (is_array($value) && isset($value['value'], $value['type'])) {
3744+
$value['value'] = $this->_normalizeDecimalStringSimple($value['value']);
37423745
$this->_bindParams[] = $value;
37433746
return;
37443747
}
37453748

3749+
$value = $this->_normalizeDecimalStringSimple($value);
3750+
37463751
static $typeMap = [
37473752
'integer' => \PDO::PARAM_INT,
37483753
'boolean' => \PDO::PARAM_BOOL,
@@ -5439,7 +5444,12 @@ protected function _secureSanitizeWhere(array &$stack): void
54395444
$allowed = (strlen((string)$tmp) === strlen((string)$value));
54405445
}
54415446
elseif (in_array($type, ['float', 'double', 'decimal', 'real'], true)) {
5442-
$allowed = is_numeric($value);
5447+
$nv = $this->_normalizeDecimalStringSimple($value);
5448+
if (is_string($nv)) {
5449+
$nv = $this->_applyDecimalScale($nv, strtolower($meta['dbtype'] ?? ($meta['definition'] ?? ($meta['type_length'] ?? $type))));
5450+
}
5451+
$allowed = is_numeric($nv);
5452+
$value = $nv;
54435453
}
54445454
elseif (isset($meta['enumValues']) && is_array($meta['enumValues'])) {
54455455
$allowed = in_array((string) $value, $meta['enumValues'], true);
@@ -5573,6 +5583,7 @@ protected function _secureValidateInsertValues(array $insertData, string $contex
55735583
$validated = [];
55745584

55755585
foreach ($insertData as $col => $val) {
5586+
// 1) Spaltenname absichern
55765587
if (!$this->_secureIsSafeColumn($col)) {
55775588
$this->reset(true);
55785589
throw $this->handleException(
@@ -5581,13 +5592,13 @@ protected function _secureValidateInsertValues(array $insertData, string $contex
55815592
);
55825593
}
55835594

5584-
// Subquery object?
5595+
// 2) Subquery?
55855596
if (is_object($val) && method_exists($val, 'getSubQuery')) {
55865597
$validated[$col] = $val;
55875598
continue;
55885599
}
55895600

5590-
// Special values like [F], [I], [N]
5601+
// 3) [F], [I], [N]
55915602
if (is_array($val) && count($val) === 1) {
55925603
$key = array_key_first($val);
55935604

@@ -5612,18 +5623,21 @@ protected function _secureValidateInsertValues(array $insertData, string $contex
56125623
);
56135624
}
56145625

5615-
$validated[$col] = [$key => $inner];
5626+
$validated[$col] = $val;
56165627
continue;
56175628
}
56185629
}
56195630

5620-
// Allow only scalar values
5621-
if (!is_scalar($val) && $val !== null) {
5622-
$this->reset(true);
5623-
throw $this->handleException(
5624-
new \Exception("Invalid non-scalar value for column '{$col}' in {$context}()"),
5625-
$context
5626-
);
5631+
// Normalize decimals
5632+
$meta = $this->_secureGetColumnMeta($col);
5633+
$type = strtolower($meta['type'] ?? '');
5634+
$dbType = strtolower($meta['dbtype'] ?? ($meta['definition'] ?? ($meta['type_length'] ?? $type)));
5635+
5636+
if (in_array($type, ['decimal', 'float', 'double', 'real'], true)) {
5637+
$val = $this->_normalizeDecimalStringSimple($val);
5638+
if (is_string($val)) {
5639+
$val = $this->_applyDecimalScale($val, $dbType);
5640+
}
56275641
}
56285642

56295643
$validated[$col] = $val;
@@ -6172,4 +6186,57 @@ public function clearLastQuery(): void
61726186
$this->_lastQuery = '';
61736187
$this->_lastDebugQuery = '';
61746188
}
6189+
6190+
/**
6191+
* Simple and safe decimal normalization for comma/dot formats.
6192+
* Examples: "12,34"->"12.34", "1.234,56"->"1234.56", "1,234.56"->"1234.56".
6193+
* Returns SQL-safe string with '.' or original value if unsure.
6194+
*/
6195+
protected function _normalizeDecimalStringSimple(mixed $value): mixed
6196+
{
6197+
if ($value === null) return $value;
6198+
if (is_int($value) || is_float($value)) {
6199+
$s = rtrim(rtrim(sprintf('%.15F', (float)$value), '0'), '.');
6200+
return $s === '' ? '0' : $s;
6201+
}
6202+
if (!is_string($value)) return $value;
6203+
6204+
$s = trim($value);
6205+
if ($s === '') return $value;
6206+
6207+
$s = preg_replace('/[ \x{00A0}\x{202F}]+/u', '', $s);
6208+
6209+
$hasComma = strpos($s, ',') !== false;
6210+
$hasDot = strpos($s, '.') !== false;
6211+
6212+
if ($hasComma && !$hasDot) {
6213+
$s = str_replace(',', '.', $s);
6214+
} elseif ($hasComma && $hasDot) {
6215+
if (strrpos($s, ',') > strrpos($s, '.')) {
6216+
$s = str_replace('.', '', $s);
6217+
$s = str_replace(',', '.', $s);
6218+
} else {
6219+
$s = str_replace(',', '', $s);
6220+
}
6221+
} else {
6222+
if (preg_match('/^\d{1,3}(\.\d{3})+$/', $s)) {
6223+
$s = str_replace('.', '', $s);
6224+
}
6225+
}
6226+
6227+
if (!preg_match('/^-?\d+(\.\d+)?$/', $s)) return $value;
6228+
return $s;
6229+
}
6230+
/**
6231+
* Format normalized decimal string to column scale (DECIMAL(p,s)) to avoid 1265.
6232+
*/
6233+
protected function _applyDecimalScale(string $normalized, string $dbType): string
6234+
{
6235+
if (preg_match('/decimal\s*\(\s*\d+\s*,\s*(\d+)\s*\)/i', $dbType, $m)) {
6236+
$scale = (int)$m[1];
6237+
return number_format((float)$normalized, $scale, '.', '');
6238+
}
6239+
return $normalized;
6240+
}
6241+
61756242
}

0 commit comments

Comments
 (0)