2){ $port = $matches[2]; } $connectionString .= 'host=' . $host; if($port !== null){ $connectionString .= ';port=' . $port; } } else{ $connectionString .= 'host=' . $host; } if($defaultDatabase !== null){ $connectionString .= ';dbname=' . $defaultDatabase; } // Have to use ATTR_EMULATE_PREPARES = true, otherwise transactions don't work for some reason. $params = [\PDO::ATTR_EMULATE_PREPARES => true, \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, \PDO::ATTR_PERSISTENT => false]; if($forceUtf8){ $params[\PDO::MYSQL_ATTR_INIT_COMMAND] = 'set names utf8mb4 collate utf8mb4_unicode_ci;'; } // We can't use persistent connections (connection pooling) because we would have race condition problems with last_insert_id() $this->_link = new \PDO($connectionString, $user, $password, $params); $this->IsConnected = true; } catch(Exception $ex){ if(SITE_STATUS == SITE_STATUS_DEV){ var_dump($ex); } else{ Logger::WriteErrorLogEntry('Error connecting to ' . $connectionString . '. Exception: ' . vds($ex)); } if($require){ print("Something crazy happened in our database. Drop us a line and let us know exactly what you were doing and we'll take a look.\n
The more detail you can provide, the better!"); exit(); } } } // Inputs: string $sql = the SQL query to execute // array $params = an array of parameters to bind to the SQL statement // Returns: a resource record or null on error public function Query(string $sql, array $params = []){ if(!$this->IsConnected){ return; } $this->QueryCount++; $result = []; $preparedSql = $sql; $handle = $this->_link->prepare($preparedSql); if(!is_array($params)){ $params = [$params]; } $name = 0; foreach($params as $parameter){ $name++; if(is_a($parameter, 'DateTime')){ $parameter = $parameter->format('Y-m-d H:i:s'); } // MySQL strict mode requires 0 or 1 instead of true or false // Can't use PDO::PARAM_BOOL, it just doesn't work if(is_bool($parameter)){ if($parameter){ $parameter = 1; } else{ $parameter = 0; } } if(is_int($parameter)){ $handle->bindValue($name, $parameter, PDO::PARAM_INT); } else{ $handle->bindValue($name, $parameter); } } $deadlockRetries = 0; $done = false; while(!$done){ try{ $result = $this->ExecuteQuery($handle, $preparedSql, $params); $done = true; } catch(\PDOException $ex){ if($ex->errorInfo[1] == 1213 && $deadlockRetries < 3){ // InnoDB deadlock, this is normal and happens occasionally. All we have to do is retry the query. $deadlockRetries++; usleep(500000 * $deadlockRetries); // Give the deadlock some time to clear up. Start at .5 seconds } elseif(stripos($ex->getMessage(), '1064 offset out of bounds') !== false){ $done = true; // We reach here if Sphinx tries to get a record past its page limit. Just silently do nothing. } else{ $done = true; if(SITE_STATUS == SITE_STATUS_DEV){ print($sql); throw($ex); } else{ Logger::WriteErrorLogEntry($ex->getMessage()); Logger::WriteErrorLogEntry($preparedSql); Logger::WriteErrorLogEntry(vds($params)); } } } } // If only one rowset is returned, change the result object if(sizeof($result) == 1){ $result = $result[0]; } return $result; } private function ExecuteQuery($handle, string $preparedSql, array $params){ $handle->execute(); $result = []; do{ try{ if($handle->columnCount() > 0){ $result[] = $handle->fetchAll(\PDO::FETCH_OBJ); } } catch(\PDOException $ex){ // HY000 is thrown when there is no result set, e.g. for an update operation. // If anything besides that is thrown, then send it up the stack if($ex->errorInfo[0] != "HY000"){ throw $ex; } } }while($handle->nextRowset()); return $result; } // Gets the last AUTO-INCREMENT id public function GetLastInsertedId(){ $id = $this->_link->lastInsertId(); if($id == 0){ return null; } return $id; } }