/**
  * Allows the display and benchmarking of queries as they are being run
  *
  * @param string $sql Query to run, and single parameter to callback
  * @param callable $callback Callback to execute code
  * @return mixed Result of query
  */
 protected function benchmarkQuery($sql, $callback, $parameters = array())
 {
     $starttime = microtime(true);
     $startmemory = memory_get_usage(true);
     if (is_array($sql)) {
         $parameters = $sql[1];
         $sql = $sql[0];
     }
     if ($this->showQueries && Director::isDev()) {
         $starttime = microtime(true);
         $result = $callback($sql);
         $endtime = round(microtime(true) - $starttime, 4);
         $formattedSql = JdornSqlFormatter::format($sql);
         $rows = $result->numRecords();
         echo '<pre>The following query took <b>' . $endtime . '</b>s an returned <b>' . $rows . "</b> row(s) \n";
         echo 'Triggered by: <i>' . $this->findSource() . '</i></pre>';
         echo $formattedSql;
         $results = iterator_to_array($result);
         if ($rows > 0) {
             if ($rows == 1) {
                 dump($results[0]);
             } else {
                 $linearValues = count($results[0]);
                 if ($linearValues) {
                     dump(implode(',', array_map(function ($item) {
                         return $item[key($item)];
                     }, $results)));
                 } else {
                     if ($rows < 20) {
                         dump($results);
                     } else {
                         dump("Too many results to display");
                     }
                 }
             }
         }
         echo '<hr/>';
         $handle = $result;
         $handle->rewind();
         // Rewind the results
     } else {
         /* @var $handle PDOQuery */
         $handle = $callback($sql);
     }
     $endtime = microtime(true);
     $endmemory = memory_get_usage(true);
     $rawsql = $sql;
     $select = null;
     // Prepared query are not so readable
     if (!empty($parameters)) {
         foreach ($parameters as $param) {
             $pos = strpos($sql, '?');
             if ($pos !== false) {
                 $param = '"' . $param . '"';
                 $sql = substr_replace($sql, $param, $pos, 1);
             }
         }
     }
     // Sometimes, ugly spaces are there
     $sql = preg_replace('/[[:blank:]]+/', ' ', trim($sql));
     $shortsql = $sql;
     // Sometimes, the select statement can be very long and unreadable
     $matches = null;
     preg_match_all('/SELECT(.+?) FROM/is', $sql, $matches);
     $select = empty($matches[1]) ? null : trim($matches[1][0]);
     if (strlen($select) > 100) {
         $shortsql = str_replace($select, '"ClickToShowFields"', $sql);
     } else {
         $select = null;
     }
     $this->queries[] = ['raw_query' => $rawsql, 'short_query' => $shortsql, 'select' => $select, 'query' => $sql, 'start_time' => $starttime, 'end_time' => $endtime, 'duration' => $endtime - $starttime, 'memory' => $endmemory - $startmemory, 'rows' => $handle ? $handle->numRecords() : null, 'success' => $handle ? true : false, 'database' => $this->getSelectedDatabase(), 'source' => $this->findSource ? $this->findSource() : null];
     return $handle;
 }
 /**
  * Stuff that only needs to be done once.  Builds regular expressions and sorts the reserved words.
  */
 protected static function init()
 {
     if (self::$init) {
         return;
     }
     // Sort reserved word list from longest word to shortest, 3x faster than usort
     $reservedMap = array_combine(self::$reserved, array_map('strlen', self::$reserved));
     arsort($reservedMap);
     self::$reserved = array_keys($reservedMap);
     // Set up regular expressions
     self::$regex_boundaries = '(' . implode('|', array_map(array(__CLASS__, 'quote_regex'), self::$boundaries)) . ')';
     self::$regex_reserved = '(' . implode('|', array_map(array(__CLASS__, 'quote_regex'), self::$reserved)) . ')';
     self::$regex_reserved_toplevel = str_replace(' ', '\\s+', '(' . implode('|', array_map(array(__CLASS__, 'quote_regex'), self::$reserved_toplevel)) . ')');
     self::$regex_reserved_newline = str_replace(' ', '\\s+', '(' . implode('|', array_map(array(__CLASS__, 'quote_regex'), self::$reserved_newline)) . ')');
     self::$regex_function = '(' . implode('|', array_map(array(__CLASS__, 'quote_regex'), self::$functions)) . ')';
     self::$init = true;
 }
 /**
  * Helpful debugging helper. Pass as many arguments as you need.
  * Keep the call on one line to be able to output arguments names
  * Without arguments, it will display all object instances in the backtrace
  * 
  * @return void
  */
 function d()
 {
     // Clean buffer that may be in the way
     if (ob_get_contents()) {
         ob_end_clean();
     }
     $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT);
     // Where is d called is the first element of the backtrace
     $line = $bt[0]['line'];
     $file = $bt[0]['file'];
     // Caller
     $caller_function = isset($bt[1]['function']) ? $bt[1]['function'] : null;
     $caller_class = isset($bt[1]['class']) ? $bt[1]['class'] : null;
     $caller = $caller_function;
     if ($caller_class) {
         $caller = $caller_class . '::' . $caller_function;
     }
     // Probably best to avoid using this in live websites...
     if (Director::isLive()) {
         SS_Log::log("Please remove call to d() in {$file}:{$line}", SS_Log::WARN);
         return;
     }
     // Arguments passed to the function are stored in matches
     $src = file($file);
     $src_line = $src[$line - 1];
     preg_match("/d\\((.+)\\)/", $src_line, $matches);
     // Find all arguments, ignore variables within parenthesis
     $arguments_name = [];
     if (!empty($matches[1])) {
         $arguments_name = array_map('trim', preg_split("/(?![^(]*\\)),/", $matches[1]));
     }
     $isAjax = Director::is_ajax();
     // Display data nicely according to context
     $print = function () use($isAjax) {
         $args = func_get_args();
         if (!$isAjax) {
             echo '<pre>';
         }
         foreach ($args as $arg) {
             if (!$arg) {
                 continue;
             }
             if (is_string($arg)) {
                 echo $arg;
             } else {
                 print_r($arg);
             }
             echo "\n";
         }
         if (!$isAjax) {
             echo '</pre>';
         }
     };
     // Display caller info
     $print("{$file}:{$line} ({$caller})");
     // Display data in a friendly manner
     $args = func_get_args();
     if (empty($args)) {
         $arguments_name = [];
         foreach ($bt as $trace) {
             if (!empty($trace['object'])) {
                 $line = isset($trace['line']) ? $trace['line'] : 0;
                 $function = isset($trace['function']) ? $trace['function'] : 'unknown function';
                 $arguments_name[] = $function . ':' . $line;
                 $args[] = $trace['object'];
             }
         }
     }
     $i = 0;
     foreach ($args as $arg) {
         // Echo name of the variable
         $len = 20;
         $varname = isset($arguments_name[$i]) ? $arguments_name[$i] : null;
         if ($varname) {
             $print('Value for: ' . $varname);
             $len = strlen($varname);
         }
         // For ajax requests, a good old print_r is much better
         if ($isAjax || !function_exists('dump')) {
             $print($arg);
             // Make a nice line between variables for readability
             if (count($args) > 1) {
                 $print(str_repeat('-', $len));
             }
         } else {
             if ($varname && is_string($arg) && strpos($varname, 'sql') !== false) {
                 echo JdornSqlFormatter::format($arg);
             } else {
                 dump($arg);
             }
         }
         $i++;
     }
     exit;
 }