. * */ namespace Vvveb\System\Db; define('SQL_VAR_REGEX', '/:([a-zA-Z0-9\[][\.\'a-zA-Z0-9\[\]_-]+)/ms'); class DBDriver { protected $quote; public function _filter($data, $columns, $addMissingDefaults = false) { //remove fields that are not table columns, $colums is returned by sqlp->getColumnsMeta() foreach ($data as $key => $name) { if (! isset($columns[$key])) { unset($data[$key]); } else { if ($data[$key] === NULL) { //set default value is empty value provided if (isset($columns[$key]['d'])) { $data[$key] = $columns[$key]['d']; } } } } if ($addMissingDefaults) { foreach ($columns as $name => $options) { $options['t'] = strtolower($options['t']); if (isset($options['e']) && $options['e'] == 'auto_increment') { continue; } //todo: validate based on data type (t) //if there is no data for column and column is not nullable set default if (! isset($data[$name]) && $options['n'] == false) { if ($options['d']) { $data[$name] = $options['d']; } else { if ($options['d'] == NULL) { $data[$name] = ''; } if ($options['t'] == 'int' || $options['t'] == 'decimal' || $options['t'] == 'tinyint') { $data[$name] = 0; } if ($options['t'] == 'datetime') { $data[$name] = date('Y-m-d H:i:s'); } if ($options['t'] == 'date') { $data[$name] = date('Y-m-d'); } } } } } return $data; } public function varType($var) { //$type = gettype($var); if (is_array($var)) { $type = 'a'; } else { //if (is_float($var)) { if ($var == (string)(float)$var) { $type = 'd'; } else { if (is_numeric($var)) { $type = 'i'; } else { $type = 's'; } } } return $type; } public function filter($data, $columns, $addMissingDefaults = false) { //check if collection of rows or individual row reset($data); if (is_numeric(key($data))) { //rows foreach ($data as $key => $row) { $return[$key] = $this->_filter($row, $columns, $addMissingDefaults); } return $return; } else { //row return $this->_filter($data, $columns, $addMissingDefaults); } } public function validate($data, $params, $table = '', $ignoreMissing = false) { $errors = []; foreach ($params as $name => $type) { if (! isset($data[$name])) { if (! $ignoreMissing) { $errors[] = sprintf('%s missing!', $name); } continue; } if (is_array($type)) { if (isset($data[$name]) && is_array($data[$name])) { //check if collection if (is_numeric(key($data[$name]))) { $arr = $data[$name][0] ?? []; } else { $arr = $data[$name]; } foreach ($type as $col => $attr) { //skip auto increment if ((isset($attr['e']) && $attr['e'] == 'auto_increment') || ($table && $col == $table . '_id')) { continue; } if (! isset($arr[$col])) { //skip columns that have default values if (($attr['d'] === 'NULL' || $attr['d'] === NULL) && ! $ignoreMissing) { $errors[] = sprintf('%s missing!', $name . '[' . $col . ']'); } } else { if (($attr['t'] == 'int' || $attr['t'] == 'decimal' || $attr['t'] == 'tinyint') && ! is_numeric($arr[$col])) { $errors[] = sprintf('%s is not an integer!', $name . '[' . $col . ']'); } if (isset($attr['l']) && $attr['l'] && (strlen($arr[$col]) > $attr['l'])) { $errors[] = sprintf('%s is longer than %s!', $name . '[' . $col . ']', $attr['l']); } } } } else { $errors[] = sprintf('%s is not an array!', $name); } } else { switch ($type) { case 'a': if (! isset($data[$name]) || ! is_array($data[$name])) { $errors[] = sprintf('%s is not an array!', $name); } else { if (substr_compare($name, '_id', -3 ,3) === 0 && ! empty($data[$name]) && ! is_numeric($data[$name][0])) { $errors[] = sprintf('%s is not an integer!', $name . '[0]'); } } break; case 'i': if (! isset($data[$name]) || ! is_numeric($data[$name])) { $errors[] = sprintf('%s is not an integer!', $name); } break; case 's': if (! isset($data[$name]) || ! is_string($data[$name])) { $errors[] = sprintf('%s is not a string!', $name); } break; } } } //var_dump($data); //var_dump($params); //var_dump($errors); return $errors; } /* * Expands arrays a = ['param' => 'value', 'second' => value]; to a[param], a[second] */ public function expandArray($array, $arrayName) { $first = true; $sql = ''; $parameters = []; if (is_array($array)) { foreach ($array as $key => $value) { if (! $first) { $sql .= ','; } $arrayKey = "['" . $arrayName . "']['" . $key . "']"; $sql .= ':' . $arrayKey; $type = $this->varType($value); $parameters[$arrayKey] = $type; $first = false; } } return [$sql, $parameters]; } /** * Generate a SQL list used for inserts * input ['var1' => 1, 'var2'=> 2, 'var3'=> 3] output var1 = 1, var2 = 2, var3 = 3. * @param mixed $array * @param mixed $arrayName * * @return mixed */ public function expandList($array, $arrayName) { $first = true; $sql = ''; $parameters = []; $quote = $this->quote; foreach ($array as $key => $value) { if (! $first) { $sql .= ','; } $arrayKey = "['$arrayName']['$key']"; $sql .= "$quote$key$quote = :$arrayKey"; $type = $this->varType($value); $parameters[$arrayKey] = $type; $first = false; } return [$sql, $parameters]; } public function sqlCount($query, $column, $table) { //remove limit //pgsql $query = preg_replace('/LIMIT\s+(\d+|:\w+)\s+OFFSET\s*(\d+|:\w+)\s*;?$/', '', $query); //mysql $query = preg_replace('/LIMIT\s+(\d+|:\w+),\s*(\d+|:\w+)\s*;?$/', '', $query); //remove order by $query = preg_replace('/ORDER BY\s+([a-z\._\-]+|,|\s+|DESC|ASC)+/', '', $query); $query = preg_replace("/^\s*SELECT .*?\s*FROM\s*$table\s/ms", "SELECT $column FROM $table ", $query); return $query; } /* * Convert array dot notation to php notation * Ex: my.array.key to ['my']['array']['key'] */ function sqlPhpArrayKey($key) { return '[\'' . str_replace('.', '\'][\'', $key) . '\']'; } /* * Replace :named_params with ? */ public function paramsToQmark(&$sql, &$params = [], &$paramTypes = [], $placeholder = '?') { $parameters = []; $index = 1; $sql = preg_replace_callback( SQL_VAR_REGEX, function ($matches) use (&$params, &$parameters, &$types, $paramTypes, &$index, $placeholder) { $varName = $matches[1]; if (strpos($varName, '.') !== false) { $varName = $this->sqlPhpArrayKey($varName); } //if parameters is array element if ($varName[0] == '[') { if (preg_match_all('/[\w_-]+/', $varName, $arrayKeys)) { $type = $paramTypes[$varName] ?? 's'; $types .= $type; $key1 = $arrayKeys[0][0]; $key2 = $arrayKeys[0][1]; $parameter = &$params[$key1][$key2]; //if (strpos($parameter, ')') && ) $parameters[] = $parameter; } } else { if (isset($params[$varName])) { $parameter = &$params[$varName]; if (isset($paramTypes[$varName])) { $type = $paramTypes[$varName]; } else { $type = $this->varType($parameter); } if ($type == 'a') { $return = false; foreach ($parameter as $key => $value) { $parameters[] = $value; if ($placeholder == '$') { $holder = '$' . $index; } else { $holder = '?'; } if ($return) { $return .= ",$holder"; } else { $return = $holder; } $type = $this->varType($value); $types .= $type; $index++; } return $return; } else { if (! $type) { $type = $this->varType($parameter); } $types .= $type; $parameters[] = $parameter; } } else { return 'null'; } } if ($placeholder == '$') { $holder = '$' . $index; } else { $holder = '?'; } $return = $holder; $index++; return $return; }, $sql); return [$parameters, $types]; } public function debugSql($sql, $params = [], $paramTypes = [], $placeholder = '?') { list($parameters, $types) = $this->paramsToQmark($sql, $params, $paramTypes, $placeholder); if ($placeholder == '$') { $regex = '\$\d+'; } else { $regex = "\\$placeholder"; } $index = 0; $sql = preg_replace_callback( "/$regex/", function ($matches) use (&$parameters, &$sql, &$index) { $value = $parameters[$index++]; if (is_array($value)) { $value = var_export($value, 1); } else { if (! is_numeric($value)) { $value = "'$value'"; } } return $value; }, $sql); return $sql; $sql = preg_replace_callback( SQL_VAR_REGEX, function ($matches) use (&$params, &$types, $paramTypes) { //if parameters is array element $varName = $matches[1]; $value = $params[$varName] ?? ''; if (strpos($varName, '.') !== false) { $varName = $this->sqlPhpArrayKey($varName); } if ($varName[0] == '[') { if (preg_match_all('/[\w_-]+/', $varName, $arrayKeys)) { $type = $paramTypes[$varName] ?? 's'; $types .= $type; $key1 = $arrayKeys[0][0]; $key2 = $arrayKeys[0][1]; $parameter = &$params[$key1][$key2]; //if (strpos($parameter, ')') && ) if ($type == 's') { $parameter = '"' . (string)$parameter . '"'; } return $parameter; } } else { if (isset($params[$varName])) { $parameter = &$params[$varName]; if (isset($paramTypes[$varName])) { $type = $paramTypes[$varName]; } else { $type = $this->varType($parameter); } if ($type == 'a') { $return = false; if ($parameter) { foreach ($parameter as $key => $value) { if ($return) { $return .= ','; } if ($value) { $return .= '"' . $value . '"'; } } } return $return; } else { if (! $type) { $type = 's'; } $types .= $type; } if (! $type) { $type = 's'; } $types .= $type; if ($type == 's') { $parameter = '"' . $parameter . '"'; } return $parameter; } else { return 'null'; } } return '?'; }, $sql); return $sql; } }