diff --git a/goDB/Exceptions/SubDataInvalidFormat.php b/goDB/Exceptions/SubDataInvalidFormat.php new file mode 100644 index 0000000..9923c90 --- /dev/null +++ b/goDB/Exceptions/SubDataInvalidFormat.php @@ -0,0 +1,20 @@ +query; } $query = preg_replace_callback('~{(.*?)}~', array($this, 'tableClb'), $this->pattern); - $pattern = '~\?([a-z\?-]+)?(:([a-z0-9_-]*))?;?~i'; + $pattern = '~\?([a-z\?-]+)?(:([a-z0-9_-]*))?(\[[?:\s\w,-]+\])?;?~i'; $callback = array($this, 'placeholderClb'); $query = preg_replace_callback($pattern, $callback, $query); if ((!$this->named) && (count($this->data) > $this->counter)) { @@ -97,17 +99,76 @@ private function tableClb($matches) protected function placeholderClb($matches) { $placeholder = isset($matches[1]) ? $matches[1] : ''; + if ($placeholder == '?') { // "??" for question mark + return '?'; + } + + $parser = $this->getParser($matches); + $dataKey = $this->currentName; + if (!array_key_exists($dataKey, $this->data)) { + if ($this->named) { + throw new DataNamed($dataKey); + } else { + throw new DataNotEnough(count($this->data), $dataKey); + } + } + $value = $this->data[$dataKey]; + + if (isset($matches[4])) { + $elementModifiers = $this->getElementModifiers($placeholder, $matches[4]); + } else { + $elementModifiers = []; + } + $method = 'replacement' . strtoupper($parser->getType()); + + return $this->$method($value, $parser->getModifiers(), $elementModifiers); + } + + /** + * @param string $placeholder + * @param string $subPattern + * @return array + */ + protected function getElementModifiers($placeholder, $subPattern) + { + $subTemplate = clone $this; + $subTemplate->named = false; + $subTemplate->counter = 0; + $pattern = '~\?([a-z\?-]+)?(:([a-z0-9_-]*))?;?~i'; + $elementModifiers = []; + preg_match_all($pattern, $subPattern, $subMatches, PREG_SET_ORDER); + foreach ($subMatches as $match) { + try { + $subParser = $subTemplate->getParser($match); + } catch (Logic $ex) { + throw new SubDataInvalidFormat($placeholder, $ex->getMessage(), $ex); + } + if ($subType = $subParser->getType()) { + throw new SubDataInvalidFormat($placeholder, 'Only modifiers can be used. Found type: ' . $subType); + } + $elementModifiers[$subTemplate->currentName] = $subParser->getModifiers(); + } + + return $elementModifiers; + } + + /** + * Get parser object + * + * @param array $matches + * @return ParserPH + * @throws \go\DB\Exceptions\Templater + */ + protected function getParser($matches) + { if (isset($matches[3])) { $name = $matches[3]; - if (empty($name)) { + if (empty($name) && $matches[2] == ':') { /* There is a named placeholder without name ("?set:") */ throw new UnknownPlaceholder($matches[0]); } } else { $name = null; - if ($placeholder == '?') { // "??" for question mark - return '?'; - } } if ($name) { if ($this->counter == 0) { @@ -116,26 +177,16 @@ protected function placeholderClb($matches) /* There is a named placeholder although already used regular */ throw new MixedPlaceholder($matches[0]); } - if (!array_key_exists($name, $this->data)) { - throw new DataNamed($name); - } - $value = $this->data[$name]; + $this->currentName = $name; } elseif ($this->named) { /* There is a regular placeholder although already used named */ throw new MixedPlaceholder($matches[0]); } else { - if (!array_key_exists($this->counter, $this->data)) { - /* Data for regular placeholders is ended */ - throw new DataNotEnough(count($this->data), $this->counter); - } - $value = $this->data[$this->counter]; + $this->currentName = $this->counter; } $this->counter++; - $parser = new ParserPH($placeholder); - $type = $parser->getType(); - $modifiers = $parser->getModifiers(); - $method = 'replacement'.strtoupper($type); - return $this->$method($value, $modifiers); + + return new ParserPH(isset($matches[1]) ? $matches[1] : ''); } /** @@ -198,9 +249,10 @@ protected function replacement($value, array $modifiers) * * @param array $value * @param array $modifiers + * @param array $elementModifiers * @return string */ - protected function replacementL($value, array $modifiers) + protected function replacementL($value, array $modifiers, array $elementModifiers = []) { if (!is_array($value)) { throw new DataInvalidFormat('list', 'required array (list of values)'); @@ -210,7 +262,15 @@ protected function replacementL($value, array $modifiers) if (is_array($element)) { throw new DataInvalidFormat('list', 'required scalar in item #'.$k); } - $values[] = $this->valueModification($element, $modifiers); + if (!empty($elementModifiers)) { + if (!isset($elementModifiers[$k])) { + throw new DataInvalidFormat('list', 'No modifier for key: ' . $k); + } + $elementMod = $elementModifiers[$k]; + } else { + $elementMod = $modifiers; + } + $values[] = $this->valueModification($element, $elementMod); } return implode(', ', $values); } @@ -220,9 +280,10 @@ protected function replacementL($value, array $modifiers) * * @param array $value * @param array $modifiers + * @param array $elementModifiers * @return string */ - protected function replacementS($value, array $modifiers) + protected function replacementS($value, array $modifiers, array $elementModifiers = []) { if (!is_array($value)) { throw new DataInvalidFormat('set', 'required array (column => value)'); @@ -237,14 +298,22 @@ protected function replacementS($value, array $modifiers) $element = $this->replacementC($element, $modifiers); } } else { - if (is_int($element)) { - $element = $this->implementation->reprInt($this->connection, $element); + if (!empty($elementModifiers)) { + if (!isset($elementModifiers[$col])) { + throw new DataInvalidFormat('set', 'No modifier for col: ' . $col); + } + $element = $this->valueModification($element, $elementModifiers[$col]); } else { - $element = $this->valueModification($element, $modifiers); + if (is_int($element)) { + $element = $this->implementation->reprInt($this->connection, $element); + } else { + $element = $this->valueModification($element, $modifiers); + } } } $set[] = $key.'='.$element; } + return implode(', ', $set); } @@ -253,16 +322,17 @@ protected function replacementS($value, array $modifiers) * * @param array $value * @param array $modifiers + * @param array $elementModifiers * @return string */ - private function replacementV($value, array $modifiers) + private function replacementV($value, array $modifiers, array $elementModifiers = []) { if (!is_array($value)) { throw new DataInvalidFormat('values', 'required array of arrays'); } $values = array(); foreach ($value as $v) { - $values[] = '('.$this->replacementL($v, $modifiers).')'; + $values[] = '('.$this->replacementL($v, $modifiers, $elementModifiers).')'; } return implode(', ', $values); } @@ -541,6 +611,11 @@ private function whereGroup($value, array $modifiers, $sep = 'AND') */ protected $data; + /** + * @var string + */ + protected $currentName = ''; + /** * @var string */ diff --git a/tests/Helpers/Templater/ExceptionsTest.php b/tests/Helpers/Templater/ExceptionsTest.php index 97db6a7..ab82567 100644 --- a/tests/Helpers/Templater/ExceptionsTest.php +++ b/tests/Helpers/Templater/ExceptionsTest.php @@ -236,4 +236,62 @@ public function providerExceptionDataInvalidFormat() ], ]; } + + /** + * @dataProvider providerExceptionSubDataInvalidFormat + * @param string $placeholder + * @param mixed $data + * @param string $exceptionClass + * @param string $message + */ + public function testExceptionSubDataInvalidFormat($placeholder, $data, $exceptionClass, $message) + { + $pattern = '?'.$placeholder; + $data = [$data]; + $templater = $this->createTemplater($pattern, $data); + $this->setExpectedException($exceptionClass, $message); + $templater->parse(); + } + + public function providerExceptionSubDataInvalidFormat() + { + return [ + 'type' => [ + 'v[?i, ?set-int]', + [1, 2], + 'go\DB\Exceptions\SubDataInvalidFormat', + 'Only modifiers can be used. Found type: s', + ], + 'internal' => [ + 'l[?i, ?wtf]', + [1, 2], + 'go\DB\Exceptions\SubDataInvalidFormat', + 'Invalid sub data for ?l: Unknown placeholder "wtf"', + ], + 'mixedSubPlaceholder' => [ + 'l[?i, ?i:foo]', + [1, 2], + 'go\DB\Exceptions\SubDataInvalidFormat', + 'Invalid sub data for ?l: Mixed placeholder "?i:foo"', + ], + 'mixedSubPlaceholder2' => [ + 'v[?i:bar, ?i]', + [1, 2], + 'go\DB\Exceptions\SubDataInvalidFormat', + 'Invalid sub data for ?v: Mixed placeholder "?i"', + ], + 'listElementModifierNotSet' => [ + 'l[?i, ?i]', + [1, 2, 3], + 'go\DB\Exceptions\DataInvalidFormat', + 'Data for ?list has invalid format: "No modifier for key: 2"', + ], + 'setElementModifierNotSet' => [ + 's[?i:foo, ?i:baZ]', + ['foo' => 1, 'bar' => 2], + 'go\DB\Exceptions\DataInvalidFormat', + 'Data for ?set has invalid format: "No modifier for col: bar"', + ], + ]; + } } diff --git a/tests/Helpers/Templater/ListTest.php b/tests/Helpers/Templater/ListTest.php index f7ba104..394d49f 100644 --- a/tests/Helpers/Templater/ListTest.php +++ b/tests/Helpers/Templater/ListTest.php @@ -49,6 +49,11 @@ public function providerTemplater() [$list], 'INSERT INTO `table` VALUES (0, 1, NULL, 3)', ], + 'subtype' => [ + 'INSERT INTO `table` VALUES (?l[?int, ?string, ?n, ?int])', + [$list], + 'INSERT INTO `table` VALUES (0, "1", NULL, 3)', + ], ]; } } diff --git a/tests/Helpers/Templater/SetTest.php b/tests/Helpers/Templater/SetTest.php index 6cbf6e0..d655968 100644 --- a/tests/Helpers/Templater/SetTest.php +++ b/tests/Helpers/Templater/SetTest.php @@ -28,6 +28,11 @@ public function providerTemplater() [$set], 'INSERT INTO `table` SET `s`="str\"ing", `d`="3.5", `n`=NULL', ], + 'subset' => [ + 'INSERT INTO `table` SET ?s[?string:s, ?int:d, ?null:n]', + [$set], + 'INSERT INTO `table` SET `s`="str\"ing", `d`=3, `n`=NULL', + ], 'null' => [ 'INSERT INTO `table` SET ?set-null', [$set], diff --git a/tests/Helpers/Templater/ValuesTest.php b/tests/Helpers/Templater/ValuesTest.php index 1f09c40..54a84ae 100644 --- a/tests/Helpers/Templater/ValuesTest.php +++ b/tests/Helpers/Templater/ValuesTest.php @@ -27,6 +27,11 @@ public function providerTemplater() [$values], 'INSERT INTO `table` VALUES (0, 1, 2), ("one", NULL, "three")', ], + 'subset' => [ + 'INSERT INTO `table` VALUES ?values[?string, ?int-null, ?i]', + [$values], + 'INSERT INTO `table` VALUES ("0", 1, 2), ("one", NULL, 0)', + ], 'null' => [ 'INSERT INTO `table` VALUES ?vn', [$values],