connection)) { return true; } @$this->connection = mysqli_connect( $this->connectionData->getHost(), $this->connectionData->getLogin(), $this->connectionData->getPassword(), $this->connectionData->getProperty('database'), $this->connectionData->getPort() ); if (mysqli_connect_errno()) { $this->lastError = "Failed to connect to MySQL: " . mysqli_connect_error(); $this->connection = NULL; return false; } mysqli_set_charset($this->connection, $this->connectionData->getProperty('encoding', 'UTF-8')); return true; } public function disconnect() { if(is_null($this->connection)) { return true; } mysqli_close($this->connection); return true; } public function getLastError(): string { $lastError = parent::getLastError(); if (empty($lastError)) { $lastError = mysqli_error($this->connection); } return $lastError; } private function getPreparedParams($values) { $sqlParams = array(''); foreach($values as $value) { $paramType = Parameter::parseType($value); switch($paramType) { case Parameter::TYPE_BOOLEAN: $value = $value ? 1 : 0; $sqlParams[0] .= 'i'; break; case Parameter::TYPE_INT: $sqlParams[0] .= 'i'; break; case Parameter::TYPE_FLOAT: $sqlParams[0] .= 'd'; break; case Parameter::TYPE_DATE: if ($value instanceof DateTime) { $value = $value->format('Y-m-d'); } $sqlParams[0] .= 's'; break; case Parameter::TYPE_TIME: if ($value instanceof DateTime) { $value = $value->format('H:i:s'); } $sqlParams[0] .= 's'; break; case Parameter::TYPE_DATE_TIME: if ($value instanceof DateTime) { $value = $value->format('Y-m-d H:i:s'); } $sqlParams[0] .= 's'; break; case Parameter::TYPE_ARRAY: $value = json_encode($value); $sqlParams[0] .= 's'; break; case Parameter::TYPE_EMAIL: default: $sqlParams[0] .= 's'; } $sqlParams[] = $value; } return $sqlParams; } protected function execute($query, $values = NULL, $returnValues = false) { $resultRows = array(); $this->lastError = ""; if (is_null($values) || empty($values)) { $res = mysqli_query($this->connection, $query); $success = $res !== FALSE; if ($success && $returnValues) { while($row = $res->fetch_assoc()) { $resultRows[] = $row; } $res->close(); } } else if($stmt = $this->connection->prepare($query)) { $success = false; $sqlParams = $this->getPreparedParams($values); $tmp = array(); foreach($sqlParams as $key => $value) $tmp[$key] = &$sqlParams[$key]; if(call_user_func_array(array($stmt, "bind_param"), $tmp)) { if($stmt->execute()) { if ($returnValues) { $res = $stmt->get_result(); if($res) { while($row = $res->fetch_assoc()) { $resultRows[] = $row; } $res->close(); $success = true; } else { $this->lastError = "PreparedStatement::get_result failed: $stmt->error ($stmt->errno)"; } } else { $success = true; } } else { $this->lastError = "PreparedStatement::execute failed: $stmt->error ($stmt->errno)"; } } else { $this->lastError = "PreparedStatement::prepare failed: $stmt->error ($stmt->errno)"; } $stmt->close(); } else { $success = false; } return ($success && $returnValues) ? $resultRows : $success; } public function getOnDuplicateStrategy(?Strategy $strategy, &$params): ?string { if (is_null($strategy)) { return ""; } else if ($strategy instanceof UpdateStrategy) { $updateValues = array(); foreach($strategy->getValues() as $key => $value) { $leftColumn = $this->columnName($key); if ($value instanceof Column) { $columnName = $this->columnName($value->getName()); $updateValues[] = "$leftColumn=VALUES($columnName)"; } else if($value instanceof Add) { $columnName = $this->columnName($value->getLHS()); $operator = $value->getOperator(); $value = $value->getValue(); $updateValues[] = "$leftColumn=$columnName$operator" . $this->addValue($value, $params); } else { $updateValues[] = "$leftColumn=" . $this->addValue($value, $params); } } return " ON DUPLICATE KEY UPDATE " . implode(",", $updateValues); } else { $strategyClass = get_class($strategy); $this->lastError = "ON DUPLICATE Strategy $strategyClass is not supported yet."; return null; } } protected function fetchReturning($res, string $returningCol) { $this->lastInsertId = mysqli_insert_id($this->connection); } public function getColumnType(Column $column): ?string { if ($column instanceof StringColumn) { $maxSize = $column->getMaxSize(); if ($maxSize) { return "VARCHAR($maxSize)"; } else { return "TEXT"; } } else if($column instanceof SerialColumn) { return "INTEGER AUTO_INCREMENT"; } else if($column instanceof IntColumn) { return "INTEGER"; } else if($column instanceof DateTimeColumn) { return "DATETIME"; } else if($column instanceof BoolColumn) { return "BOOLEAN"; } else if($column instanceof JsonColumn) { return "LONGTEXT"; # some maria db setups don't allow JSON hereā€¦ } else { $this->lastError = "Unsupported Column Type: " . get_class($column); return NULL; } } public function getColumnDefinition(Column $column): ?string { $columnName = $this->columnName($column->getName()); $defaultValue = $column->getDefaultValue(); $type = $this->getColumnType($column); if (!$type) { if ($column instanceof EnumColumn) { $values = array(); foreach($column->getValues() as $value) { $values[] = $this->getValueDefinition($value); } $values = implode(",", $values); $type = "ENUM($values)"; } else { return null; } } if ($type === "LONGTEXT") { $defaultValue = NULL; # must be null :( } $notNull = $column->notNull() ? " NOT NULL" : ""; if (!is_null($defaultValue) || !$column->notNull()) { $defaultValue = " DEFAULT " . $this->getValueDefinition($defaultValue); } else { $defaultValue = ""; } return "$columnName $type$notNull$defaultValue"; } public function getValueDefinition($value) { if (is_numeric($value)) { return $value; } else if(is_bool($value)) { return $value ? "TRUE" : "FALSE"; } else if(is_null($value)) { return "NULL"; } else if($value instanceof Keyword) { return $value->getValue(); } else if ($value instanceof CurrentTimeStamp) { return "CURRENT_TIMESTAMP"; } else { $str = addslashes($value); return "'$str'"; } } public function addValue($val, &$params = NULL) { if ($val instanceof Keyword) { return $val->getValue(); } else if ($val instanceof CurrentColumn) { return $val->getName(); } else if ($val instanceof Column) { return $this->columnName($val->getName()); } else if ($val instanceof Expression) { return $this->createExpression($val, $params); } else { $params[] = $val; return "?"; } } public function tableName($table): string { if (is_array($table)) { $tables = array(); foreach($table as $t) $tables[] = $this->tableName($t); return implode(",", $tables); } else { return "`$table`"; } } public function columnName($col): string { if ($col instanceof Keyword) { return $col->getValue(); } elseif(is_array($col)) { $columns = array(); foreach($col as $c) $columns[] = $this->columnName($c); return implode(",", $columns); } else { if (($index = strrpos($col, ".")) !== FALSE) { $tableName = $this->tableName(substr($col, 0, $index)); $columnName = $this->columnName(substr($col, $index + 1)); return "$tableName.$columnName"; } else if(($index = stripos($col, " as ")) !== FALSE) { $columnName = $this->columnName(trim(substr($col, 0, $index))); $alias = trim(substr($col, $index + 4)); return "$columnName as $alias"; } else { return "`$col`"; } } } public function getStatus() { return mysqli_stat($this->connection); } public function createTriggerBody(CreateTrigger $trigger): ?string { $values = array(); foreach ($trigger->getProcedure()->getParameters() as $param) { if ($param instanceof CurrentTable) { $values[] = $this->getUnsafeValue($trigger->getTable()); } else { $prefix = ($trigger->getEvent() !== "DELETE" ? "NEW." : "OLD."); $values[] = $this->columnName($prefix . $param->getName()); } } $procName = $trigger->getProcedure()->getName(); $procParameters = implode(",", $values); return "CALL $procName($procParameters)"; } private function getParameterDefinition(Column $parameter, bool $out = false): string { $out = ($out ? "OUT" : "IN"); $name = $parameter->getName(); $type = $this->getColumnType($parameter); return "$out $name $type"; } public function getProcedureHead(CreateProcedure $procedure): ?string { $name = $procedure->getName(); $returns = $procedure->getReturns(); $paramDefs = []; foreach ($procedure->getParameters() as $param) { if ($param instanceof Column) { $paramDefs[] = $this->getParameterDefinition($param); } else { $this->setLastError("PROCEDURE parameter type " . gettype($returns) . " is not implemented yet"); return null; } } if ($returns) { if ($returns instanceof Column) { $paramDefs[] = $this->getParameterDefinition($returns, true); } else if (!($returns instanceof Trigger)) { // mysql does not need to return triggers here $this->setLastError("PROCEDURE RETURN type " . gettype($returns) . " is not implemented yet"); return null; } } $paramDefs = implode(",", $paramDefs); return "CREATE PROCEDURE $name($paramDefs)"; } protected function buildUnsafe(Query $statement): string { $params = []; $query = $statement->build($params); foreach ($params as $value) { $query = preg_replace("?", $this->getUnsafeValue($value), $query, 1); } return $query; } protected function createExpression(Expression $exp, array &$params) { if ($exp instanceof DateAdd) { $lhs = $this->addValue($exp->getLHS(), $params); $rhs = $this->addValue($exp->getRHS(), $params); $unit = $exp->getUnit(); return "DATE_ADD($lhs, INTERVAL $rhs $unit)"; } else if ($exp instanceof CurrentTimeStamp) { return "NOW()"; } else { return parent::createExpression($exp, $params); } } }