diff --git a/src/Lunr/Gravity/Database/MySQL/MySQLCanonicalQuery.php b/src/Lunr/Gravity/Database/MySQL/MySQLCanonicalQuery.php
new file mode 100644
index 0000000000000000000000000000000000000000..587ea17abe46eb242171152e99ebebbfb182a643
--- /dev/null
+++ b/src/Lunr/Gravity/Database/MySQL/MySQLCanonicalQuery.php
@@ -0,0 +1,626 @@
+<?php
+
+/**
+ * MySQL canonical query class.
+ *
+ * @package    Lunr\Gravity\Database\MySQL
+ * @author     David Mendes <d.mendes@m2mobi.com>
+ * @copyright  2012-2018, M2Mobi BV, Amsterdam, The Netherlands
+ * @license    http://lunr.nl/LICENSE MIT License
+ */
+
+namespace Lunr\Gravity\Database\MySQL;
+
+/**
+ * This class returns the Canonicalized query.
+ */
+class MySQLCanonicalQuery
+{
+
+    /**
+     * Query to canonicalize.
+     * @var string
+     */
+    private $query;
+
+    /**
+     * Canonicalized query.
+     * @var string
+     */
+    private $canonical_query;
+
+    /**
+     * List of index ranges to ignore.
+     * @var array
+     */
+    private $ignore_positions;
+
+    /**
+     * Constructor.
+     *
+     * @param string $query Executed query
+     */
+    public function __construct(string $query)
+    {
+        $this->query           = $query;
+        $this->canonical_query = NULL;
+    }
+
+    /**
+     * __toString.
+     *
+     * @return string $returns The executed query canonicalized
+     */
+    public function __toString(): string
+    {
+        return $this->get_canonical_query();
+    }
+
+    /**
+     * Get the executed query canonicalized.
+     *
+     * @return string $canonical_query The executed query canonicalized
+     */
+    public function get_canonical_query(): string
+    {
+        if ($this->canonical_query !== NULL)
+        {
+            return $this->canonical_query;
+        }
+
+        $this->ignore_positions = [];
+
+        $tmp_query = $this->query;
+
+        $tmp_query = $this->replace_between($tmp_query, '#', PHP_EOL, '');
+        $tmp_query = $this->replace_between($tmp_query, '--', PHP_EOL, '');
+
+        $tmp_query = $this->remove_eol_blank_spaces($tmp_query);
+
+        $this->add_ignore_positions($this->find_positions($tmp_query, '/*M', '*/'));
+        $this->add_ignore_positions($this->find_positions($tmp_query, '/*!', '*/'));
+        $this->add_ignore_positions($this->find_positions($tmp_query, '`', '`'));
+        $this->add_ignore_positions($this->find_positions($tmp_query, '\\'));
+
+        $tmp_query = $this->replace_between($tmp_query, '/*', '*/', '');
+        $tmp_query = $this->replace_between($tmp_query, '"', '"', '"?"', TRUE);
+        $tmp_query = $this->replace_between($tmp_query, '\'', '\'', '\'?\'', TRUE);
+
+        $tmp_query = $this->replace_numeric($tmp_query, '?');
+
+        $tmp_query = $this->collapse_multirows($tmp_query);
+
+        $this->canonical_query = $this->remove_eol_blank_spaces($tmp_query);
+
+        return $this->canonical_query;
+    }
+
+    /**
+     * Replaces text in between two strings
+     *
+     * @param string  $string        Input string to replace
+     * @param string  $from          Input string to start replacing
+     * @param string  $to            Input string to end replacing
+     * @param string  $replace       Input string to replace with
+     * @param boolean $add_to_ignore Input bool to decide if add to ignore list
+     *
+     * @return string $string The provided string replaced
+     */
+    private function replace_between(string $string, string $from, string $to, string $replace, bool $add_to_ignore = FALSE): string
+    {
+        $offset = 0;
+
+        while ($offset < strlen($string))
+        {
+            $start_pos = strpos($string, $from, $offset);
+
+            if ($start_pos === FALSE)
+            {
+                break;
+            }
+
+            $offset = $this->jump_ignore($start_pos);
+            if ($offset > $start_pos)
+            {
+                continue;
+            }
+
+            $end_pos = strpos($string, $to, ($start_pos + 1));
+
+            if ($end_pos === FALSE)
+            {
+                $end_pos = strlen($string) - 1;
+            }
+
+            $len_to_remove = ($end_pos + strlen($to)) - $start_pos;
+
+            $string = substr_replace($string, $replace, $start_pos, $len_to_remove);
+
+            $this->ignore_positions = $this->update_positions($this->ignore_positions, $start_pos, $len_to_remove - strlen($replace));
+
+            if ($add_to_ignore === TRUE)
+            {
+                $this->add_ignore_positions([[$start_pos, $start_pos + strlen($replace) - 1]]);
+            }
+
+            $offset = $start_pos + strlen($replace);
+        }
+
+        return $string;
+    }
+
+    /**
+     * Search for all the index ranges in a string between two strings
+     * if $to is empty searches only for $from position
+     *
+     * @param string      $string Input string to search
+     * @param string      $from   Input string to search the start of range
+     * @param string|null $to     Input string to search the end of range
+     * @param integer     $offset Input int index of where to start the search
+     *
+     * @return array $positions All ranges between $to and $from,
+     *               if can't find $to returns empty,
+     *               if can't find $from returns the end of string,
+     *               if $to is empty returns all $from positions
+     */
+    private function find_positions(string $string, string $from, ?string $to = NULL, int $offset = 0): array
+    {
+        if (empty($from))
+        {
+            return [];
+        }
+
+        $positions = [];
+        $end       = strlen($string);
+        while ($offset < $end)
+        {
+            $tmp_position = [strpos($string, $from, $offset), NULL];
+            if ($tmp_position[0] === FALSE)
+            {
+                break;
+            }
+
+            $jump = $this->jump_ignore($tmp_position[0]);
+            if ($jump > $tmp_position[0])
+            {
+                $offset = $jump;
+                continue;
+            }
+
+            $position = $tmp_position;
+
+            if (is_null($to) || $to === '')
+            {
+                $position[1] = $position[0] + strlen($from) - 1;
+                $offset      = $position[1] + 1;
+                $positions[] = $position;
+                continue;
+            }
+
+            $position[1] = strpos($string, $to, ($position[0] + 1));
+
+            if ($position[1] === FALSE)
+            {
+                $position[1] = strlen($string) - 1;
+            }
+            else
+            {
+                $position[1] = $position[1] + strlen($to) - 1;
+            }
+
+            $offset      = $position[1] + 1;
+            $positions[] = $position;
+        }
+
+        return $positions;
+    }
+
+    /**
+     * Removes blank space and end of line characters from string
+     *
+     * @param string $string Input string remove
+     *
+     * @return string $returns The $string without blank spaces and end of line characters
+     */
+    private function remove_eol_blank_spaces(string $string): string
+    {
+        return trim(preg_replace('/\s+/', ' ', $string));
+    }
+
+    /**
+     * Add a range of indexes to $ignore_positions
+     *
+     * @param array $positions Input array with ranges of indexes
+     * @return void
+     */
+    private function add_ignore_positions(array $positions): void
+    {
+        $this->ignore_positions = array_merge($this->ignore_positions, $positions);
+        asort($this->ignore_positions);
+        $this->ignore_positions = array_values($this->ignore_positions);
+    }
+
+    /**
+     * Update all index ranges if start index
+     *
+     * @param array   $positions Input array with ranges of indexes
+     * @param integer $start     Input integer to start the update
+     * @param integer $offset    Input integer with the amount to offset positions
+     *
+     * @return array returns $positions updated
+     */
+    private function update_positions(array $positions, int $start, int $offset): array
+    {
+        foreach ($positions as $k => $position)
+        {
+            $positions[$k][0] = ($position[0] < $start) ? $position[0] : $position[0] - $offset;
+            $positions[$k][1] = ($position[1] < $start) ? $position[1] : $position[1] - $offset;
+        }
+
+        return $positions;
+    }
+
+    /**
+     * Get the index position of the next digit from offset
+     *
+     * @param string  $string Input string where to search for the next index position
+     * @param integer $offset Input integer where to start to search
+     *
+     * @return integer|null return Index of the next digit, NULL if not found
+     */
+    private function find_digit(string $string, int $offset): ?int
+    {
+        $end = strlen($string);
+        for ($i = $offset; $i < $end; ++$i)
+        {
+            $jump = $this->jump_ignore($i);
+            if ($jump > $i)
+            {
+                $i = $jump;
+                continue;
+            }
+
+            if (ctype_digit($string[$i]) && ($i == 0 || (!ctype_alnum(substr($string, $i - 1, 1)) && substr($string, $i - 1, 1) != '_')))
+            {
+                return $i;
+            }
+        }
+
+        return NULL;
+    }
+
+    /**
+     * Replaces numeric value (Integer, Decimal, Hexadecimal, Exponential) with string
+     *
+     * @param string $string  Input string to replace
+     * @param string $replace Input string to replace with
+     *
+     * @return string returns string with numeric values replaced
+     */
+    private function replace_numeric(string $string, string $replace): string
+    {
+        $end = strlen($string);
+        for ($i = 0; $i < $end; ++$i)
+        {
+            $pos = $this->find_digit($string, $i);
+            if (!isset($pos))
+            {
+                break;
+            }
+
+            $number_end = $this->is_numeric_value($string, $pos);
+
+            $i = $number_end[1];
+
+            if ($number_end[0] === FALSE)
+            {
+                continue;
+            }
+
+            if ($this->is_negative_number($string, $pos))
+            {
+                $pos--;
+            }
+
+            $replace_size           = strlen($replace);
+            $to_replace_length      = $number_end[1] - $pos + 1;
+            $string                 = substr_replace($string, $replace, $pos, $to_replace_length);
+            $this->ignore_positions = $this->update_positions($this->ignore_positions, $pos, $to_replace_length - $replace_size);
+
+            $i = $pos + $replace_size;
+        }
+
+        return $string;
+    }
+
+    /**
+     * Check if number is a negation
+     *
+     * @param string  $string Input string to check
+     * @param integer $i      Start index of the number position
+     *
+     * @return boolean returns TRUE if finds the negation character and is not a subtraction, FALSE otherwise
+     */
+    private function is_negative_number(string $string, int $i): bool
+    {
+        $is_negative_number = FALSE;
+        if ($i > 0 && $string[$i - 1] == '-')
+        {
+            $i--;
+            // Possibly a negative number
+            $is_negative_number = TRUE;
+            for ($j = $i - 1; $j >= 0; $j--)
+            {
+                if (!ctype_space($string[$j]))
+                {
+                    /** If we find a previously converted value, we know that it
+                     * is not a negation but a subtraction. */
+                    $is_negative_number = ($string[$j] != '?');
+                    break;
+                }
+            }
+        }
+
+        return $is_negative_number;
+    }
+
+    /**
+     * Check if digit is a number and get the position of last number digit
+     *
+     * @param string  $string Input string to check
+     * @param integer $i      Start position of digit index
+     *
+     * @return array $return the last index of number and is number result
+     */
+    private function is_numeric_value(string $string, int $i): array
+    {
+        $allow_hex      = FALSE;
+        $is_hexadecimal = (($string[$i] == '0'));
+
+        $i++; //first number we already know is numeric
+        $end       = strlen($string);
+        $is_number = TRUE;
+
+        while ($i < $end)
+        {
+            if (!(ctype_digit($string[$i]) || ($allow_hex && ctype_xdigit($string[$i]))))
+            {
+                if ($is_hexadecimal == TRUE && strtolower($string[$i]) == 'x')
+                {
+                    $is_hexadecimal = FALSE;
+                    $allow_hex      = TRUE;
+                }
+                elseif (strtolower($string[$i]) == 'e')
+                {
+                    $next = $i + 1;
+                    // Possible scientific notation number
+                    if ($next == $end || (!ctype_digit($string[$next]) && $string[$next] != '-'))
+                    {
+                        $i         = ++$next;
+                        $is_number = FALSE;
+                        break;
+                    }
+
+                    // Skip over the minus if we have one
+                    if ($string[$next] == '-')
+                    {
+                        $i++;
+                    }
+                }
+                elseif ($string[$i] == '.')
+                {
+                    $next = $i + 1;
+                    // Possible decimal number
+                    if ($next != $end && !ctype_digit($string[$next]))
+                    {
+                        /** The fractional part of a decimal is optional in MariaDB. */
+                        break;
+                    }
+                }
+                else
+                {
+                    // If we have a non-text character, we treat it as a number
+                    $is_number = !ctype_alpha($string[$i]);
+                    break;
+                }
+            }
+
+            $i++;
+        }
+
+        return [$is_number, --$i];
+    }
+
+    /**
+     * Checks if index is in a position to ignore, and returns next position
+     *
+     * @param integer $index Input integer to check if is between ranges
+     *
+     * @return integer returns the position after the range to ignore, if not in range returns the provided index
+     */
+    private function jump_ignore(int $index): int
+    {
+        foreach ($this->ignore_positions as $position)
+        {
+            if ($position[0] <= $index && $position[1] >= $index)
+            {
+                $index = $position[1] + 1;
+            }
+        }
+
+        return $index;
+    }
+
+    /**
+     * Collapses multi-row insert into one
+     *
+     * @param string $string Input string with canonical query to collapse
+     *
+     * @return string|null returns the string with canonical_query without multi-row values
+     */
+    private function collapse_multirows(string $string): ?string
+    {
+        if (stripos($string, 'INSERT INTO') === FALSE && stripos($string, 'REPLACE INTO') === FALSE)
+        {
+            return $string;
+        }
+
+        $end    = strlen($string);
+        $offset = $this->find_positions($string, 'VALUES');
+
+        if (count($offset) === 0)
+        {
+            return $string;
+        }
+
+        $first_row_position = $this->get_between_delimiter($string, '(', ')', $offset[0][1] + 1, [' ']);
+
+        if ($first_row_position === NULL)
+        {
+            return $string;
+        }
+
+        $first_row = substr($string, $first_row_position[0], $first_row_position[1] - $first_row_position[0] + 1);
+
+        $tmp_string = $string;
+        $i          = $first_row_position[1] + 1;
+        while ($i < $end)
+        {
+            $next_row_start = $this->find_next($tmp_string, ',', $first_row_position[1] + 1, [' ']);
+            if ($next_row_start === NULL)
+            {
+                break;
+            }
+
+            $row_position = $this->get_between_delimiter($tmp_string, '(', ')', $next_row_start + 1, [' ']);
+            if ($row_position === NULL)
+            {
+                return $string;
+            }
+
+            $row = substr($tmp_string, $row_position[0], $row_position[1] - $row_position[0] + 1);
+
+            if ($first_row != $row)
+            {
+                return $string;
+            }
+
+            $tmp_string = substr_replace($tmp_string, '', $first_row_position[1] + 1, $row_position[1] - $first_row_position[1]);
+
+            $end = strlen($tmp_string);
+            $i   = $first_row_position[1] + 1;
+        }
+
+        return $tmp_string;
+    }
+
+    /**
+     * Finds the next start and end index positions between two delimiters, ignoring delimiters in between
+     *
+     * @param string  $string    Input string to search
+     * @param string  $start_del Input string with start delimiter
+     * @param string  $end_del   Input string with end delimiter
+     * @param integer $offset    Input int with index to start the search
+     * @param array   $ignore    Input array with chars to ignore until start position is found
+     *
+     * @return array|null returns the range position of the start and end delimiters, if is not found returns null
+     */
+    private function get_between_delimiter(string $string, string $start_del, string $end_del, int $offset = 0, array $ignore): ?array
+    {
+        $end                   = strlen($string);
+        $delimiter_start_index = NULL;
+        $delimiter_end_index   = NULL;
+        for ($i = $offset; $i < $end; $i++)
+        {
+            if ($string[$i] === $start_del)
+            {
+                $delimiter_start_index = $i;
+                break;
+            }
+            elseif ($string[$i] == in_array($string[$i], $ignore))
+            {
+                continue;
+            }
+            elseif ($string[$i] != $start_del)
+            {
+                return NULL;
+            }
+        }
+
+        if ($delimiter_start_index === NULL)
+        {
+            return NULL;
+        }
+
+        $count_p = 0;
+        for ($i = $delimiter_start_index + 1; $i < $end; $i++)
+        {
+            if ($string[$i] === $start_del)
+            {
+                $count_p++;
+            }
+            elseif ($string[$i] === $end_del && $count_p > 0)
+            {
+                $count_p--;
+            }
+            elseif ($string[$i] === $end_del && $count_p === 0)
+            {
+                $delimiter_end_index = $i;
+                break;
+            }
+        }
+
+        if ($delimiter_end_index === NULL)
+        {
+            return NULL;
+        }
+
+        return [$delimiter_start_index, $delimiter_end_index];
+    }
+
+    /**
+     * Finds the next index position of a char
+     *
+     * @param string     $string      Input string to search
+     * @param string     $char        Input string with character to find
+     * @param integer    $offset      Input string start position
+     * @param array|null $ignore_char Input array with characters to ignore, if null ignores all chars
+     *
+     * @return integer|null returns the index position of the value found, if not found returns null
+     */
+    private function find_next(string $string, string $char, int $offset, ?array $ignore_char = NULL): ?int
+    {
+        $end = strlen($string);
+
+        while ($offset < $end)
+        {
+            $jump = $this->jump_ignore($offset);
+            if ($jump > $offset)
+            {
+                $offset = $jump;
+                continue;
+            }
+
+            if ($string[$offset] === $char)
+            {
+                return $offset;
+            }
+
+            if ($ignore_char !== NULL && $string[$offset] == in_array($string[$offset], $ignore_char))
+            {
+                $offset++;
+                continue;
+            }
+
+            if ($string[$offset] !== $char && $ignore_char !== NULL)
+            {
+                break;
+            }
+
+            $offset++;
+        }
+
+        return NULL;
+    }
+
+}
diff --git a/src/Lunr/Gravity/Database/MySQL/MySQLQueryResult.php b/src/Lunr/Gravity/Database/MySQL/MySQLQueryResult.php
index 16319e94cc26ccb1cf7ff952b89cde0e667a1a2b..edab863ec6576dd2599e1bbed57a649deb608d61 100644
--- a/src/Lunr/Gravity/Database/MySQL/MySQLQueryResult.php
+++ b/src/Lunr/Gravity/Database/MySQL/MySQLQueryResult.php
@@ -38,6 +38,12 @@ class MySQLQueryResult implements DatabaseQueryResultInterface
      */
     protected $query;
 
+    /**
+     * The canonicalized query string that was executed.
+     * @var String
+     */
+    protected $canonical_query;
+
     /**
      * Return value from mysqli->query().
      * @var mixed
@@ -113,9 +119,10 @@ class MySQLQueryResult implements DatabaseQueryResultInterface
             $this->freed   = TRUE;
         }
 
-        $this->result = $result;
-        $this->mysqli = $mysqli;
-        $this->query  = $query;
+        $this->result          = $result;
+        $this->mysqli          = $mysqli;
+        $this->query           = $query;
+        $this->canonical_query = NULL;
 
         if ($async === FALSE)
         {
@@ -142,6 +149,7 @@ class MySQLQueryResult implements DatabaseQueryResultInterface
         unset($this->error_number);
         unset($this->insert_id);
         unset($this->query);
+        unset($this->canonical_query);
     }
 
     /**
@@ -335,6 +343,23 @@ class MySQLQueryResult implements DatabaseQueryResultInterface
         return isset($line[$column]) ? $line[$column] : NULL;
     }
 
+    /**
+     * Get the executed query canonicalized.
+     *
+     * @return string $canonicalized_query The executed query canonicalized
+     */
+    public function canonical_query(): string
+    {
+        if ($this->canonical_query !== NULL)
+        {
+            return $this->canonical_query;
+        }
+
+        $this->canonical_query = new MySQLCanonicalQuery($this->query());
+
+        return $this->canonical_query;
+    }
+
 }
 
 ?>
diff --git a/src/Lunr/Gravity/Database/MySQL/Tests/MySQLCanonicalQueryBaseTest.php b/src/Lunr/Gravity/Database/MySQL/Tests/MySQLCanonicalQueryBaseTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..55f3cb670beac16eb2e746344cc88147e43bf9be
--- /dev/null
+++ b/src/Lunr/Gravity/Database/MySQL/Tests/MySQLCanonicalQueryBaseTest.php
@@ -0,0 +1,349 @@
+<?php
+
+/**
+ * This file contains the MySQLConnectionEscapeTest class.
+ *
+ * @package    Lunr\Gravity\Database\MySQL
+ * @author     Heinz Wiesinger <heinz@m2mobi.com>
+ * @copyright  2012-2018, M2Mobi BV, Amsterdam, The Netherlands
+ * @license    http://lunr.nl/LICENSE MIT License
+ */
+
+namespace Lunr\Gravity\Database\MySQL\Tests;
+
+/**
+ * This class contains unit tests for MySQLCanonicalQuery.
+ *
+ * @covers Lunr\Gravity\Database\MySQL\MySQLCanonicalQuery
+ */
+class MySQLCanonicalQueryBaseTest extends MySQLCanonicalQueryTest
+{
+
+    /**
+     * Test that find_positions() returns the correct positions.
+     *
+     * @param array $data     Data values
+     * @param array $expected Modified data values
+     *
+     * @dataProvider findPositionsDataProvider
+     * @covers       Lunr\Gravity\Database\MySQL\MySQLCanonicalQuery::find_positions
+     */
+    public function testFindPositions(array $data, array $expected): void
+    {
+        $property = $this->get_accessible_reflection_property('ignore_positions');
+        $property->setValue($this->class, ($data[1] ?? []));
+
+        $method = $this->get_accessible_reflection_method('find_positions');
+
+        $result = $method->invokeArgs($this->class, $data[0] );
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
+     * Test that remove_eol_blank_spaces() returns string without double blank spaces and end of line characters.
+     *
+     * @param string $data     Data values
+     * @param string $expected Modified values
+     *
+     * @dataProvider removeEolBlankSpacesDataProvider
+     * @covers       Lunr\Gravity\Database\MySQL\MySQLCanonicalQuery::remove_eol_blank_spaces
+     */
+    public function testRemoveEolBlankSpaces(string $data, string $expected): void
+    {
+        $method = $this->get_accessible_reflection_method('remove_eol_blank_spaces');
+
+        $result = $method->invokeArgs($this->class, [$data]);
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
+     * Test that escape_string() properly escapes the given string.
+     *
+     * @param array $data     Data values
+     * @param array $expected Modified values
+     *
+     * @dataProvider updatePositionsDataProvider
+     * @covers       Lunr\Gravity\Database\MySQL\MySQLCanonicalQuery::update_positions
+     */
+    public function testUpdatePositions(array $data, array $expected): void
+    {
+        $method = $this->get_accessible_reflection_method('update_positions');
+
+        $result = $method->invokeArgs($this->class, $data);
+
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
+     * Test that find_digit() finds next digit if exists
+     *
+     * @param array $data     Data values
+     * @param mixed $expected Modified values
+     *
+     * @dataProvider findDigitDataProvider
+     * @covers       Lunr\Gravity\Database\MySQL\MySQLCanonicalQuery::find_digit
+     */
+    public function testFindDigit(array $data, $expected): void
+    {
+        $property = $this->get_accessible_reflection_property('ignore_positions');
+        $property->setValue($this->class, ($data[1] ?? []));
+
+        $method = $this->get_accessible_reflection_method('find_digit');
+
+        $result = $method->invokeArgs($this->class, $data[0]);
+
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
+     * Test that jump_ignore() returns the index after positions to ignore or the same index case is not between ranges
+     *
+     * @param array   $data     Data values
+     * @param integer $expected Modified values
+     *
+     * @dataProvider jumpIgnoreDataProvider
+     * @covers       Lunr\Gravity\Database\MySQL\MySQLCanonicalQuery::jump_ignore
+     */
+    public function testJumpIgnore(array $data, int $expected): void
+    {
+        $property = $this->get_accessible_reflection_property('ignore_positions');
+        $property->setValue($this->class, ($data[1] ?? []));
+
+        $method = $this->get_accessible_reflection_method('jump_ignore');
+
+        $result = $method->invokeArgs($this->class, [$data[0]]);
+
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
+     * Test that is_numeric_value() returns true and the end position of the numeric value, and false if its not numeric
+     *
+     * @param array $data     Data values
+     * @param array $expected Modified data values
+     *
+     * @dataProvider isNumericValueDataProvider
+     * @covers       Lunr\Gravity\Database\MySQL\MySQLCanonicalQuery::is_numeric_value
+     */
+    public function testIsNumericValue(array $data, array $expected): void
+    {
+        $method = $this->get_accessible_reflection_method('is_numeric_value');
+
+        $result = $method->invokeArgs($this->class, $data);
+
+        $this->assertIsBool($result[0]);
+        $this->assertIsInt($result[1]);
+
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
+     * Test that replace_between() returns replaced string correctly.
+     *
+     * @param array  $data     Data values
+     * @param string $expected Modified data values
+     *
+     * @dataProvider replaceBetweenDataProvider
+     * @covers       Lunr\Gravity\Database\MySQL\MySQLCanonicalQuery::replace_between
+     */
+    public function testReplaceBetween(array $data, string $expected): void
+    {
+        $property = $this->get_accessible_reflection_property('ignore_positions');
+        $property->setValue($this->class, ($data[1] ?? []));
+
+        $method = $this->get_accessible_reflection_method('replace_between');
+
+        $result = $method->invokeArgs($this->class, $data[0]);
+
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
+     * Test that add_ignore_positions() returns true and the end position of the numeric value, and false if its not numeric
+     *
+     * @covers Lunr\Gravity\Database\MySQL\MySQLCanonicalQuery::add_ignore_positions
+     */
+    public function testAddIgnorePositions(): void
+    {
+        $property = $this->get_accessible_reflection_property('ignore_positions');
+        $property->setValue($this->class, []);
+
+        $method = $this->get_accessible_reflection_method('add_ignore_positions');
+        $method->invokeArgs($this->class, [[[40,50]]]);
+        $method->invokeArgs($this->class, [[[3,10],[25,30]]]);
+
+        $result = $property->getValue($this->class);
+
+        $this->assertIsArray($result);
+        $this->assertEquals([[3,10],[25,30],[40,50]], $result);
+    }
+
+    /**
+     * Test that is_negative_number()
+     *
+     * @param array   $data     Data values
+     * @param boolean $expected Modified data values
+     *
+     * @dataProvider isNegativeNumberDataProvider
+     * @covers       Lunr\Gravity\Database\MySQL\MySQLCanonicalQuery::is_negative_number
+     */
+    public function testIsNegativeNumber(array $data, bool $expected): void
+    {
+        $method = $this->get_accessible_reflection_method('is_negative_number');
+
+        $result = $method->invokeArgs($this->class, $data);
+        $this->assertIsBool($result);
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
+     * Test that replace_numeric() returns the query string without numeric values and does not remove numeric non values,
+     * numeric values includes numeric, decimal, exponential, hexadecimal, negative
+     *
+     * @param array  $data     Data values
+     * @param string $expected Modified data values
+     *
+     * @dataProvider replaceNumericDataProvider
+     * @covers       Lunr\Gravity\Database\MySQL\MySQLCanonicalQuery::replace_numeric
+     */
+    public function testReplaceNumeric(array $data, string $expected): void
+    {
+        $property = $this->get_accessible_reflection_property('ignore_positions');
+        $property->setValue($this->class, ($data[1] ?? []));
+
+        $method = $this->get_accessible_reflection_method('replace_numeric');
+
+        $result = $method->invokeArgs($this->class, $data[0]);
+
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
+     * Test that get_canonical_query() returns the canonical query.
+     *
+     * @param string $data     Data values
+     * @param string $expected Modified data values
+     *
+     * @dataProvider canonicalQueryDataProvider
+     * @covers       Lunr\Gravity\Database\MySQL\MySQLCanonicalQuery::get_canonical_query
+     */
+    public function testCanonicalQuery(string $data, string $expected): void
+    {
+        if (!file_exists($data))
+        {
+            $this->markTestSkipped($data . ' does not exist.');
+            return;
+        }
+
+        if (!file_exists($expected))
+        {
+            $this->markTestSkipped($expected . ' does not exist.');
+            return;
+        }
+
+        $property = $this->get_accessible_reflection_property('query');
+        $input    = file_get_contents($data);
+        $output   = file_get_contents($expected);
+
+        if ($input === FALSE)
+        {
+            $this->markTestSkipped("File \"$data\" could not be read!");
+        }
+
+        if ($output === FALSE)
+        {
+            $this->markTestSkipped("File \"$expected\" could not be read!");
+        }
+
+        $property->setValue($this->class, $input);
+        $value = $this->class->get_canonical_query();
+
+        $this->assertEquals($output, $value);
+    }
+
+    /**
+     * Test that get_between_delimiter() returns the range of index between the next start and end delimiters provided
+     *
+     * @param array       $data     Data values
+     * @param string|null $expected Modified data values
+     *
+     * @dataProvider getBetweenDelimiterDataProvider
+     * @covers       Lunr\Gravity\Database\MySQL\MySQLCanonicalQuery::get_between_delimiter
+     */
+    public function testGetBetweenDelimiter(array $data, ?array $expected): void
+    {
+        $method = $this->get_accessible_reflection_method('get_between_delimiter');
+
+        $result = $method->invokeArgs($this->class, $data);
+
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
+     * Test that find_next() returns the index of the string provided,
+     *
+     * @param array        $data     Data values
+     * @param integer|null $expected Modified data values
+     *
+     * @dataProvider findNextDataProvider
+     * @covers       Lunr\Gravity\Database\MySQL\MySQLCanonicalQuery::find_next
+     */
+    public function testFindNext(array $data, ?int $expected): void
+    {
+        $property = $this->get_accessible_reflection_property('ignore_positions');
+        $property->setValue($this->class, ($data[1] ?? []));
+
+        $method = $this->get_accessible_reflection_method('find_next');
+
+        $result = $method->invokeArgs($this->class, $data[0]);
+
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
+     * Test that collapse_multi_rows() returns the canonical query collapsed.
+     *
+     * @param string $data     Data values
+     * @param string $expected Modified data values
+     *
+     * @dataProvider collapseMultiRowInsertsDataProvider
+     * @covers       Lunr\Gravity\Database\MySQL\MySQLCanonicalQuery::collapse_multirows
+     */
+    public function testCollapseMultiRows(string $data, string $expected): void
+    {
+        if (!file_exists($data))
+        {
+            $this->markTestSkipped($data . ' does not exist.');
+            return;
+        }
+
+        if (!file_exists($expected))
+        {
+            $this->markTestSkipped($expected . ' does not exist.');
+            return;
+        }
+
+        $input  = file_get_contents($data);
+        $output = file_get_contents($expected);
+
+        if ($input === FALSE)
+        {
+            $this->markTestSkipped("File \"$data\" could not be read!");
+        }
+
+        if ($output === FALSE)
+        {
+            $this->markTestSkipped("File \"$expected\" could not be read!");
+        }
+
+        $property = $this->get_accessible_reflection_property('ignore_positions');
+        $property->setValue($this->class, []);
+
+        $method = $this->get_accessible_reflection_method('collapse_multirows');
+
+        $result = $method->invokeArgs($this->class, [$input]);
+
+        $this->assertEquals($output, $result);
+    }
+
+}
diff --git a/src/Lunr/Gravity/Database/MySQL/Tests/MySQLCanonicalQueryTest.php b/src/Lunr/Gravity/Database/MySQL/Tests/MySQLCanonicalQueryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..972f8f41ccd579d19e55eca0fcff1c96de360292
--- /dev/null
+++ b/src/Lunr/Gravity/Database/MySQL/Tests/MySQLCanonicalQueryTest.php
@@ -0,0 +1,274 @@
+<?php
+
+/**
+ * This file contains the MySQLConnectionEscapeTest class.
+ *
+ * @package    Lunr\Gravity\Database\MySQL
+ * @author     Heinz Wiesinger <heinz@m2mobi.com>
+ * @copyright  2012-2018, M2Mobi BV, Amsterdam, The Netherlands
+ * @license    http://lunr.nl/LICENSE MIT License
+ */
+
+namespace Lunr\Gravity\Database\MySQL\Tests;
+
+use Lunr\Gravity\Database\MySQL\MySQLCanonicalQuery;
+use Lunr\Halo\LunrBaseTest;
+use ReflectionClass;
+
+/**
+ * This class contains unit tests for MySQLCanonicalQuery.
+ *
+ * @covers Lunr\Gravity\Database\MySQL\MySQLCanonicalQuery
+ */
+class MySQLCanonicalQueryTest extends LunrBaseTest
+{
+
+    /**
+     * TestCase Constructor.
+     */
+    public function setUp(): void
+    {
+         $this->reflection = new ReflectionClass('Lunr\Gravity\Database\MySQL\MySQLCanonicalQuery');
+         $this->class      = new MySQLCanonicalQuery('');
+    }
+
+    /**
+     * TestCase Destructor.
+     */
+    public function tearDown(): void
+    {
+        parent::tearDown();
+    }
+
+    /**
+     * Unit Test Data Provider.
+     *
+     * @return array $data_provider Array of data values.
+     */
+    public function findPositionsDataProvider(): array
+    {
+        $data_provider                               = [];
+        $data_provider['first argument not string']  = [[['013456789013456789','23','56'],[]], []];
+        $data_provider['second argument not string'] = [[['012346789012346789','23','56'],[]], [[2,17]]];
+        $data_provider['both arguments string']      = [[['01234567890123456789','23','56'],[]], [[2,6],[12,16]]];
+        $data_provider['both arguments not string']  = [[['01234567890123456789','bb','cc'],[]], []];
+        $data_provider['no second argument']         = [[['01234567890123456789','56',''],[]], [[5,6],[15,16]]];
+        $data_provider['no first argument']          = [[['01234567890123456789','','56'],[]], []];
+        $data_provider['single no second arg']       = [[['01234567890123456789','5',''],[]], [[5,5],[15,15]]];
+        $data_provider['ignore positions']           = [[['01234567890123456789','23','56'],[[1,8]]], [[12,16]]];
+
+        return $data_provider;
+    }
+
+    /**
+     * Unit Test Data Provider.
+     *
+     * @return array $data_provider Array of data values.
+     */
+    public function removeEolBlankSpacesDataProvider(): array
+    {
+        $data_provider   = [];
+        $data_provider[] = ['  SELECT     *   FROM `table`   ','SELECT * FROM `table`'];
+        $data_provider[] = ["SELECT * \nFROM `table`",'SELECT * FROM `table`'];
+        $data_provider[] = ["SELECT * \r\nFROM `table`",'SELECT * FROM `table`'];
+        $data_provider[] = ["SELECT * \rFROM `table`",'SELECT * FROM `table`'];
+
+        return $data_provider;
+    }
+
+    /**
+     * Unit Test Data Provider.
+     *
+     * @return array $data_provider Array of data values.
+     */
+    public function isNumericValueDataProvider(): array
+    {
+        $data_provider   = [];
+        $data_provider[] = [['value=234567 AND',6],[TRUE,11]];
+        $data_provider[] = [['value=0x47 AND',6],[TRUE,9]];
+        $data_provider[] = [['value=1.245 AND',6],[TRUE,10]];
+        $data_provider[] = [['value=3.82384E-11 AND',6],[TRUE,16]];
+
+        return $data_provider;
+    }
+
+    /**
+     * Unit Test Data Provider.
+     *
+     * @return array $data_provider Array of data values.
+     */
+    public function updatePositionsDataProvider(): array
+    {
+        $data_provider   = [];
+        $data_provider[] = [[[[3,10],[25,30],[40,50]],15,5],[[3,10],[20,25],[35,45]]];
+
+        return $data_provider;
+    }
+
+    /**
+     * Unit Test Data Provider.
+     *
+     * @return array $data_provider Array of data values.
+     */
+    public function findDigitDataProvider(): array
+    {
+        $data_provider   = [];
+        $data_provider[] = [[['SELECT * FROM `table1` WHERE `value1`="teste" AND `value2`=12', 22],[[29,36],[50,57]]],59];
+        $data_provider[] = [[['SELECT * FROM `table1` WHERE `value1`="teste" AND `value2`="A"', 22],[[29,36],[50,57]]],NULL];
+
+        return $data_provider;
+    }
+
+    /**
+     * Unit Test Data Provider.
+     *
+     * @return array $data_provider Array of data values.
+     */
+    public function jumpIgnoreDataProvider(): array
+    {
+        $data_provider   = [];
+        $data_provider[] = [[0,[[0,10],[10,100],[102,110]]], 101];
+
+        return $data_provider;
+    }
+
+    /**
+     * Unit Test Data Provider.
+     *
+     * @return array $data_provider Array of data values.
+     */
+    public function isNegativeNumberDataProvider(): array
+    {
+        $data_provider   = [];
+        $data_provider[] = [['value=123',6], FALSE];
+        $data_provider[] = [['value=-123',7], TRUE];
+        $data_provider[] = [['value=?-123',8], FALSE];
+
+        return $data_provider;
+    }
+
+    /**
+     * Unit Test Data Provider.
+     *
+     * @return array $data_provider Array of data values.
+     */
+    public function replaceNumericDataProvider(): array
+    {
+        $data_provider   = [];
+        $data_provider[] = [[['value=12 and','?'],NULL],'value=? and'];
+        $data_provider[] = [[['value=0x24 and','?'],NULL],'value=? and'];
+        $data_provider[] = [[['value=-12 and','?'],NULL],'value=? and'];
+        $data_provider[] = [[['value=1.24 and','?'],NULL],'value=? and'];
+        $data_provider[] = [[['value=-1.24 and','?'],NULL],'value=? and'];
+        $data_provider[] = [[['value=3.8E-11 and','?'],NULL],'value=? and'];
+        $data_provider[] = [[['value1=123456 and value2=123456 /*! 123 */','?'],[[32,41]]],'value1=? and value2=? /*! 123 */'];
+
+        return $data_provider;
+    }
+
+    /**
+     * Unit Test Data Provider.
+     *
+     * @return array $data_provider Array of data values.
+     */
+    public function replaceBetweenDataProvider(): array
+    {
+        $data_provider                       = [];
+        $data_provider['replace jump'][0]    = [['SELECT * FROM value1="ignore" AND value2="B"', '"', '"', '"?"',TRUE],[[21,28]]];
+        $data_provider['replace jump'][1]    = 'SELECT * FROM value1="ignore" AND value2="?"';
+        $data_provider['replace no jump'][0] = [['SELECT * FROM value1="ignore" AND value2="B"', '"', '"', '"?"',TRUE],NULL];
+        $data_provider['replace no jump'][1] = 'SELECT * FROM value1="?" AND value2="?"';
+
+        return $data_provider;
+    }
+
+    /**
+     * Unit Test Data Provider.
+     *
+     * @return array $data_provider Array of data values.
+     */
+    public function canonicalQueryDataProvider(): array
+    {
+        $path = TEST_STATICS . '/Gravity/Database/MySQL/';
+
+        $data_provider['select']                            = [$path . 'input_select.sql', $path . 'output_select.sql'];
+        $data_provider['update']                            = [$path . 'input_update.sql', $path . 'output_update.sql'];
+        $data_provider['create']                            = [$path . 'input_create.sql', $path . 'output_create.sql'];
+        $data_provider['insert single row']                 = [$path . 'input_insert_single_row.sql', $path . 'output_insert_single_row.sql'];
+        $data_provider['insert multi-rows']                 = [$path . 'input_insert_multi_rows.sql', $path . 'output_insert_multi_rows.sql'];
+        $data_provider['insert different rows']             = [$path . 'input_insert_different_rows.sql', $path . 'output_insert_different_rows.sql'];
+        $data_provider['insert no rows']                    = [$path . 'input_insert_no_rows.sql', $path . 'output_insert_no_rows.sql'];
+        $data_provider['insert value or null multi-rows']   = [$path . 'input_insert_value_or_null_multirows.sql', $path . 'output_insert_value_or_null_multirows.sql'];
+        $data_provider['insert null diff. case multi-rows'] = [$path . 'input_insert_null_diff_case_multi_rows.sql', $path . 'output_insert_null_diff_case_multi_rows.sql'];
+        $data_provider['replace single row']                = [$path . 'input_replace_single_row.sql', $path . 'output_replace_single_row.sql'];
+        $data_provider['replace multi-rows']                = [$path . 'input_replace_multi_rows.sql', $path . 'output_replace_multi_rows.sql'];
+        $data_provider['upserts single row']                = [$path . 'input_upserts_single_row.sql', $path . 'output_upserts_single_row.sql'];
+        $data_provider['upserts multi-rows']                = [$path . 'input_upserts_multi_rows.sql', $path . 'output_upserts_multi_rows.sql'];
+        $data_provider['upserts function multi-rows']       = [$path . 'input_upserts_function_multi_rows.sql', $path . 'output_upserts_function_multi_rows.sql'];
+        $data_provider['maxscalehints']                     = [$path . 'input_maxscalehints.sql', $path . 'output_maxscalehints.sql'];
+        $data_provider['cte']                               = [$path . 'input_cte.sql', $path . 'output_cte.sql'];
+        $data_provider['unix_lf']                           = [$path . 'input_unix_lf.sql', $path . 'output_unix_lf.sql'];
+        $data_provider['win_crlf']                          = [$path . 'input_win_crlf.sql', $path . 'output_win_crlf.sql'];
+        $data_provider['mac_cr']                            = [$path . 'input_mac_cr.sql', $path . 'output_mac_cr.sql'];
+
+        return $data_provider;
+    }
+
+    /**
+     * Unit Test Data Provider.
+     *
+     * @return array $data_provider Array of data values.
+     */
+    public function findNextDataProvider(): array
+    {
+        $data_provider                      = [];
+        $data_provider['find']              = [[['VALUES (?) , (?)',',',4,NULL],[]],11];
+        $data_provider['find ignore char']  = [[[' ,(?),(?)',',',0,[' ']],[]],1];
+        $data_provider['found first index'] = [[[',(?),(?)',',',0,[' ']],[]],0];
+        $data_provider['offset']            = [[[',(?) ,(?)',',',4,[' ']],[]],5];
+        $data_provider['find not ignore']   = [[['VALUES (?) , (?)',',',4,[' ']],[]],NULL];
+        $data_provider['not found']         = [[['(?,?) ON ',',',4,NULL],[]],NULL];
+
+        return $data_provider;
+    }
+
+    /**
+     * Unit Test Data Provider.
+     *
+     * @return array $data_provider Array of data values.
+     */
+    public function getBetweenDelimiterDataProvider(): array
+    {
+        $data_provider                      = [];
+        $data_provider['simple']            = [[' (?,?,"?") , (?,?,"?") ','(',')',0,[' ']],[1,9]];
+        $data_provider['offset']            = [['values (?,?,"?") , (?,?,"?") ','(',')',18,[' ']],[19,27]];
+        $data_provider['delimiters inside'] = [['values (COALESCE(?,"?"),?,"?") ','(',')',6,[' ']],[7,29]];
+        $data_provider['not found']         = [['values (?,?,"?") , (?,?,"?") ','{','}',6,[' ']],NULL];
+
+        return $data_provider;
+    }
+
+    /**
+     * Unit Test Data Provider.
+     *
+     * @return array $data_provider Array of data values.
+     */
+    public function collapseMultiRowInsertsDataProvider(): array
+    {
+        $path = TEST_STATICS . '/Gravity/Database/MySQL/';
+
+        $data_provider['insert single row']    = [$path . 'input_collapse_insert_single_row.sql', $path . 'output_collapse_insert_single_row.sql'];
+        $data_provider['insert multi-rows']    = [$path . 'input_collapse_insert_multi_rows.sql', $path . 'output_collapse_insert_multi_rows.sql'];
+        $data_provider['insert no rows']       = [$path . 'input_collapse_insert_no_rows.sql', $path . 'output_collapse_insert_no_rows.sql'];
+        $data_provider['insert different row'] = [$path . 'input_collapse_insert_different_row.sql', $path . 'output_collapse_insert_different_row.sql'];
+        $data_provider['insert function']      = [$path . 'input_collapse_insert_function_multi_rows.sql', $path . 'output_collapse_insert_function_multi_rows.sql'];
+        $data_provider['replace']              = [$path . 'input_collapse_replace_multi_rows.sql', $path . 'output_collapse_replace_multi_rows.sql'];
+        $data_provider['upserts single row']   = [$path . 'input_collapse_upserts_single_row.sql', $path . 'output_collapse_upserts_single_row.sql'];
+        $data_provider['upserts multi-rows']   = [$path . 'input_collapse_upserts_multi_rows.sql', $path . 'output_collapse_upserts_multi_rows.sql'];
+        $data_provider['upserts function']     = [$path . 'input_collapse_upserts_function.sql', $path . 'output_collapse_upserts_function.sql'];
+        $data_provider['select']               = [$path . 'input_collapse_select.sql', $path . 'output_collapse_select.sql'];
+
+        return $data_provider;
+    }
+
+}
diff --git a/src/Lunr/Gravity/Database/MySQL/Tests/MySQLQueryResultBaseTest.php b/src/Lunr/Gravity/Database/MySQL/Tests/MySQLQueryResultBaseTest.php
index 58652170e73bb71819568489a13f6a7ef16da759..517035412d91a3ce59f2a8f045aa6ed9ab49f8af 100644
--- a/src/Lunr/Gravity/Database/MySQL/Tests/MySQLQueryResultBaseTest.php
+++ b/src/Lunr/Gravity/Database/MySQL/Tests/MySQLQueryResultBaseTest.php
@@ -205,6 +205,29 @@ class MySQLQueryResultBaseTest extends MySQLQueryResultTest
         $this->assertSame($this->mysqli, $value);
     }
 
+    /**
+     * Test that canonical_query() returns a string.
+     *
+     * @covers Lunr\Gravity\Database\MySQL\MySQLQueryResult::canonical_query
+     */
+    public function testCanonicalQuery(): void
+    {
+        $class = $this->result_reflection->getProperty('query');
+        $class->setAccessible(TRUE);
+        $class->setValue($this->result, 'SELECT * FROM table1 WHERE value="test"');
+
+        $value = $this->result->canonical_query();
+        $this->assertIsString($value);
+        $this->assertEquals('SELECT * FROM table1 WHERE value="?"', $value);
+
+        $class = $this->result_reflection->getProperty('canonical_query');
+        $class->setAccessible(TRUE);
+        $class->setValue($this->result, 'SELECT * FROM table2 WHERE value=?');
+        $value = $this->result->canonical_query();
+        $this->assertIsString($value);
+        $this->assertEquals('SELECT * FROM table2 WHERE value=?', $value);
+    }
+
 }
 
 ?>
diff --git a/src/Lunr/Gravity/Filesystem/Tests/PhysicalFilesystemAccessObjectDirectoryTest.php b/src/Lunr/Gravity/Filesystem/Tests/PhysicalFilesystemAccessObjectDirectoryTest.php
index 8610f394e1f07e02c3b04c5d17c1a75ce51fa672..08b34e95d02b145906577f095bbb87544c6d70a9 100644
--- a/src/Lunr/Gravity/Filesystem/Tests/PhysicalFilesystemAccessObjectDirectoryTest.php
+++ b/src/Lunr/Gravity/Filesystem/Tests/PhysicalFilesystemAccessObjectDirectoryTest.php
@@ -30,7 +30,7 @@ class PhysicalFilesystemAccessObjectDirectoryTest extends PhysicalFilesystemAcce
      */
     public function testGetListingOfAccessibleDirectory(): void
     {
-        $expected = [ 'file1', 'file2', 'file3', 'folder1', 'folder2' ];
+        $expected = [ 'Database', 'file1', 'file2', 'file3', 'folder1', 'folder2' ];
 
         $value = $this->class->get_directory_listing($this->find_location);
 
diff --git a/src/Lunr/Gravity/Filesystem/Tests/PhysicalFilesystemAccessObjectListDirectoriesTest.php b/src/Lunr/Gravity/Filesystem/Tests/PhysicalFilesystemAccessObjectListDirectoriesTest.php
index 4eb2c32f57a6ca6809d379a869159472597dfb6c..09583971fba2c6a736cf1a181856335257fc4d68 100644
--- a/src/Lunr/Gravity/Filesystem/Tests/PhysicalFilesystemAccessObjectListDirectoriesTest.php
+++ b/src/Lunr/Gravity/Filesystem/Tests/PhysicalFilesystemAccessObjectListDirectoriesTest.php
@@ -29,7 +29,7 @@ class PhysicalFilesystemAccessObjectListDirectoriesTest extends PhysicalFilesyst
      */
     public function testGetListOfDirectoriesInAccessibleDirectory(): void
     {
-        $expected = [ 'folder1', 'folder2' ];
+        $expected = [ 'Database', 'folder1', 'folder2' ];
 
         $value = $this->class->get_list_of_directories($this->find_location);
 
diff --git a/tests/phpunit.xml b/tests/phpunit.xml
index 5b6ebc7c724fe07eb344f72fdd01c6a4c29b8a51..dd199b9dc7cc27c9205515d838493b339b5c769b 100644
--- a/tests/phpunit.xml
+++ b/tests/phpunit.xml
@@ -145,6 +145,7 @@
             <file>../src/Lunr/Gravity/Database/MySQL/Tests/MySQLSimpleDMLQueryBuilderWriteTest.php</file>
             <file>../src/Lunr/Gravity/Database/MySQL/Tests/MySQLSimpleDMLQueryBuilderFluidInterfaceTest.php</file>
             <file>../src/Lunr/Gravity/Database/MySQL/Tests/MySQLSimpleDMLQueryBuilderUsingTest.php</file>
+            <file>../src/Lunr/Gravity/Database/MySQL/Tests/MySQLCanonicalQueryBaseTest.php</file>
             <file>../src/Lunr/Gravity/Database/Tests/DatabaseConnectionBaseTest.php</file>
             <file>../src/Lunr/Gravity/Database/MySQL/Tests/MySQLConnectionBaseTest.php</file>
             <file>../src/Lunr/Gravity/Database/MySQL/Tests/MySQLConnectionSetTest.php</file>
diff --git a/tests/statics/Gravity/Database/MySQL/input_collapse_insert_different_row.sql b/tests/statics/Gravity/Database/MySQL/input_collapse_insert_different_row.sql
new file mode 100644
index 0000000000000000000000000000000000000000..7b08ed15299b54d9ac7a1908b92b249d8a4bdaf1
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_collapse_insert_different_row.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) VALUES (COALESCE("?","?",?), ?, '?'), (?, ?, '?'), (?, ?, '?'), (COALESCE("?","?",?), ?, '?')
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_collapse_insert_function_multi_rows.sql b/tests/statics/Gravity/Database/MySQL/input_collapse_insert_function_multi_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..9b28e779366392e09633599ac5b8d684bbb7efd0
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_collapse_insert_function_multi_rows.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) VALUES (COALESCE("?","?",?), ?, '?'), (COALESCE("?","?",?), ?, '?'), (COALESCE("?","?",?), ?, '?'), (COALESCE("?","?",?), ?, '?'), (COALESCE("?","?",?), ?, '?');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_collapse_insert_multi_rows.sql b/tests/statics/Gravity/Database/MySQL/input_collapse_insert_multi_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..501af761915e0d70f9046cd11f7df7d205bfe4de
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_collapse_insert_multi_rows.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) VALUES (?, '?', '?') , (?, '?', '?') , (?, '?', '?') , (?, '?', '?') , (?, '?', '?');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_collapse_insert_no_rows.sql b/tests/statics/Gravity/Database/MySQL/input_collapse_insert_no_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..b0398bb01f3e2f1e727b59a15111aa83114abac1
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_collapse_insert_no_rows.sql
@@ -0,0 +1 @@
+INSERT INTO `table1` (`param1`, `param2`) SELECT `param1`,`param2` FROM `table2` WHERE `table2`.`param3` > ?;
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_collapse_insert_single_row.sql b/tests/statics/Gravity/Database/MySQL/input_collapse_insert_single_row.sql
new file mode 100644
index 0000000000000000000000000000000000000000..5db1365fe30c6cbeb3745207db9854890b6a3400
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_collapse_insert_single_row.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) VALUES (?, '?', '?')
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_collapse_replace_multi_rows.sql b/tests/statics/Gravity/Database/MySQL/input_collapse_replace_multi_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..05efb0457dddb2750f16b487fb7186857b725e62
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_collapse_replace_multi_rows.sql
@@ -0,0 +1 @@
+REPLACE INTO `database`.`table` (`param1`, `param2`, `param3`) VALUES (?, '?', '?') , (?, '?', '?') , (?, '?', '?') , (?, '?', '?') , (?, '?', '?');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_collapse_select.sql b/tests/statics/Gravity/Database/MySQL/input_collapse_select.sql
new file mode 100644
index 0000000000000000000000000000000000000000..333aa7f130999c85cdfe798d5f3fe59fb81835ff
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_collapse_select.sql
@@ -0,0 +1 @@
+SELECT `c`.`identifier`,`c`.`language`,`c`.`language`,`fb`.`identifier`,COALESCE(NULL,?) as "?" from `table` as `c` left join `bookmarks` fb on `c`.`identifier` = `fb`.`identifier` where `c`.`identifier` = "?";
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_collapse_upserts_function.sql b/tests/statics/Gravity/Database/MySQL/input_collapse_upserts_function.sql
new file mode 100644
index 0000000000000000000000000000000000000000..c1da306825639a1da6838cd1d937a0b33cb2b690
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_collapse_upserts_function.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`identifier`, `language`, `content`) VALUES (COALESCE("?","?",?),"?","?") ,(COALESCE("?","?",?),"?","?") ,(COALESCE("?","?",?),"?","?") ,(COALESCE("?","?",?),"?","?") ,(COALESCE("?","?",?),"?","?") ON DUPLICATE KEY UPDATE `content`="?";
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_collapse_upserts_multi_rows.sql b/tests/statics/Gravity/Database/MySQL/input_collapse_upserts_multi_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..63d224b8c2f368257973a6f12f3ec0b125fee900
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_collapse_upserts_multi_rows.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`identifier`, `language`, `content`) VALUES ("?","?","?"),("?","?","?"),("?","?","?"),("?","?","?"),("?","?","?") ON DUPLICATE KEY UPDATE `content`="?";
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_collapse_upserts_single_row.sql b/tests/statics/Gravity/Database/MySQL/input_collapse_upserts_single_row.sql
new file mode 100644
index 0000000000000000000000000000000000000000..94c8703bee15cc736da0edc18eb3a3d5fe9e4b8a
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_collapse_upserts_single_row.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`identifier`, `language`, `content`) VALUES ("?","?","?") ON DUPLICATE KEY UPDATE `content`="?";
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_create.sql b/tests/statics/Gravity/Database/MySQL/input_create.sql
new file mode 100644
index 0000000000000000000000000000000000000000..55c312f2167600fc0443c610eb9aa418dd189f31
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_create.sql
@@ -0,0 +1,17 @@
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `table` (
+   `param1` varchar(255) NOT NULL,
+   `param2` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`changedData`)),
+   `param3` int(11) NOT NULL,
+   `param4` bigint(20) NOT NULL,
+   `param5` enum('added','changed','deleted') NOT NULL,
+   `param6` varchar(8) GENERATED ALWAYS AS (json_value(from_base64(`id`),'$.codeShareFlightNumber')) STORED,
+   `param7` tinyint(1) GENERATED ALWAYS AS (if(json_value(from_base64(`id`),'$.arrival') = '1',1,0)) STORED,
+   `param8` date GENERATED ALWAYS AS (json_value(from_base64(`id`),'$.scheduledDate')) STORED,
+   `param9` char(3) GENERATED ALWAYS AS (json_value(from_base64(`id`),'$.departureAirport')) STORED,
+   `param10` char(3) GENERATED ALWAYS AS (json_value(from_base64(`id`),'$.arrivalAirport')) STORED,
+   `param11` char(3) GENERATED ALWAYS AS (json_value(from_base64(`id`),'$.viaAirport')) STORED,
+   PRIMARY KEY (`param1`,`param2`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 `PAGE_COMPRESSED`=1;
+/*!40101 SET character_set_client = @saved_cs_client */;
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_cte.sql b/tests/statics/Gravity/Database/MySQL/input_cte.sql
new file mode 100644
index 0000000000000000000000000000000000000000..b873a1e2a8c1114d92baf7ee2f64f14d97651a34
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_cte.sql
@@ -0,0 +1,21 @@
+WITH `teste` AS (
+    SELECT
+        `c`.`language` as `lang`,
+        RAND() as `num`
+    FROM
+        `table1` as `c`
+            LEFT JOIN
+        `table2` as `fb` ON `c`.`id`=`fb`.`id`
+    WHERE
+            COALESCE(NULL,1) = 1
+      AND `c`.`id` = 1
+    GROUP BY `lang`
+    ORDER BY `num` DESC
+    LIMIT 100 OFFSET 5
+    )
+SELECT
+    `table1`.*
+FROM
+    `table1`
+        JOIN
+    `teste`;
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_insert_different_rows.sql b/tests/statics/Gravity/Database/MySQL/input_insert_different_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..669259d70d26568e0e1b4e2bcdd225784d00ecf5
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_insert_different_rows.sql
@@ -0,0 +1,9 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) 
+VALUES 
+(CONCAT(COALESCE(`param3`,null),`param4`), "x", 'Lorem'), 
+("id_value1", "x1", ?), 
+("id_value2", 'x12', '?'), 
+(CONCAT(COALESCE(`param3`,null),"_",`param4`), "x123", 'Lorem'), 
+(CONCAT(COALESCE(`param3`,null),"_",`param4`), "x1234", 'Lorem'), 
+(CONCAT(COALESCE(`param3`,null),"_",`param4`), "x12345", 'Lorem'), 
+(CONCAT(COALESCE(`param3`,null),"_",`param4`), "x123456", 'Lorem');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_insert_multi_rows.sql b/tests/statics/Gravity/Database/MySQL/input_insert_multi_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..0f99af422c964e895eccb1f5580836f68d8c6d8f
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_insert_multi_rows.sql
@@ -0,0 +1,6 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) VALUES
+    (1, 'en-US', 'Lorem ipsum dolor sit amet'),
+    (2, 'en-US', 'consectetur adipiscing elit'),
+    (3, 'en-US', 'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'),
+    (4, 'en-US', 'Ut enim ad minim veniam'),
+    (5, 'en-US', 'quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_insert_no_rows.sql b/tests/statics/Gravity/Database/MySQL/input_insert_no_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..f85dad25d27dfa9a3d80418d4a3799aad23913c8
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_insert_no_rows.sql
@@ -0,0 +1 @@
+INSERT INTO `table1` (`param1`, `param2`) SELECT `param1`,`param2` FROM `table2` WHERE `table2`.`param3` > 100;
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_insert_null_diff_case_multi_rows.sql b/tests/statics/Gravity/Database/MySQL/input_insert_null_diff_case_multi_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..31ebfbda8f57552fbfe7c36a32f28c23832c1e52
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_insert_null_diff_case_multi_rows.sql
@@ -0,0 +1,5 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) 
+VALUES 
+(CONCAT(COALESCE(`param3`,null),"_",`param4`), "x123", 'Lorem'), 
+(CONCAT(COALESCE(`param3`,NULL),"_",`param4`), "x12345", 'Lorem'), 
+(CONCAT(COALESCE(`param3`,null),"_",`param4`), "x123456", 'Lorem');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_insert_single_row.sql b/tests/statics/Gravity/Database/MySQL/input_insert_single_row.sql
new file mode 100644
index 0000000000000000000000000000000000000000..2b3f88bb737a7607d3c2ca83b9a8bcecc285e4ac
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_insert_single_row.sql
@@ -0,0 +1,3 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) 
+VALUES
+    (1, 'en-US', 'Lorem ipsum dolor sit amet');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_insert_value_or_null_multirows.sql b/tests/statics/Gravity/Database/MySQL/input_insert_value_or_null_multirows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..797bbf262fe0ab01bc4a2ee7af68f2182e4d5c3f
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_insert_value_or_null_multirows.sql
@@ -0,0 +1,7 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) 
+VALUES 
+(CONCAT(COALESCE(`param3`,"Unknown"),`param4`), "x", 'Lorem'), 
+(CONCAT(COALESCE(`param3`,null),"_",`param4`), "x123", 'Lorem'), 
+(CONCAT(COALESCE(`param3`,"WW"),"_",`param4`), "x1234", 'Lorem'), 
+(CONCAT(COALESCE(`param3`,null),"_",`param4`), "x12345", 'Lorem'), 
+(CONCAT(COALESCE(`param3`,null),"_",`param4`), "x123456", 'Lorem');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_mac_cr.sql b/tests/statics/Gravity/Database/MySQL/input_mac_cr.sql
new file mode 100644
index 0000000000000000000000000000000000000000..92b5af4419d8d89b0424079216e3ca741e63834a
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_mac_cr.sql
@@ -0,0 +1 @@
+INSERT 
INTO `database`.`table` (`param1`, `param2`, `param3`) 
	VALUES 
		(1, 'Lorem', 'Lorem') ,
	 (2, 'Lorem', 'Lorem') , (3, 'Lorem', 'Lorem') , (4, 'Lorem', 'Lorem') , 
		(5, 'Lorem', 'Lorem');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_maxscalehints.sql b/tests/statics/Gravity/Database/MySQL/input_maxscalehints.sql
new file mode 100644
index 0000000000000000000000000000000000000000..077442f6ca44d9e44581da555b0fabe38d21bd27
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_maxscalehints.sql
@@ -0,0 +1 @@
+SELECT * FROM table1 where tnumber=999 and ttext="Lorem Ipsum";
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_multirows_insert_different_value_with_null_ulcase.sql b/tests/statics/Gravity/Database/MySQL/input_multirows_insert_different_value_with_null_ulcase.sql
new file mode 100644
index 0000000000000000000000000000000000000000..31ebfbda8f57552fbfe7c36a32f28c23832c1e52
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_multirows_insert_different_value_with_null_ulcase.sql
@@ -0,0 +1,5 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) 
+VALUES 
+(CONCAT(COALESCE(`param3`,null),"_",`param4`), "x123", 'Lorem'), 
+(CONCAT(COALESCE(`param3`,NULL),"_",`param4`), "x12345", 'Lorem'), 
+(CONCAT(COALESCE(`param3`,null),"_",`param4`), "x123456", 'Lorem');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_replace_multi_rows.sql b/tests/statics/Gravity/Database/MySQL/input_replace_multi_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..6b8dc0d4a3077732b316438b97645ede1cf8b572
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_replace_multi_rows.sql
@@ -0,0 +1,19 @@
+REPLACE
+INTO `db`.`table`
+VALUES 
+(1, 'Old', '2014-01-20 22:47:00'),
+(2, 'New', '2021-08-20 13:36:00'),
+(3, 'New', '2021-02-20 18:47:00'),
+(4, 'Old', '2014-08-20 16:02:00'),
+(5, 'Old', '2014-08-16 18:47:00'),
+(6, 'New', '2021-03-20 18:36:00'),
+(7, 'Old', '2014-08-20 10:47:00'),
+(8, 'New', '2021-08-15 18:36:00'),
+(9, 'Old', '2014-04-20 11:02:00'),
+(10, 'Old', '2014-08-20 18:47:00'), 
+(11, 'New', '2021-08-02 18:35:00'),
+(12, 'Old', '2015-08-20 09:47:00'),
+(13, 'Old', '2014-05-20 18:01:00'),
+(14, 'New', '2021-08-30 18:02:00'),
+(15, 'New', '2021-06-01 01:17:00'),
+(16, 'Old', '2016-08-21 18:47:00');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_replace_single_row.sql b/tests/statics/Gravity/Database/MySQL/input_replace_single_row.sql
new file mode 100644
index 0000000000000000000000000000000000000000..3f5441ab293e5a0400d0594f203461dc61a6160c
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_replace_single_row.sql
@@ -0,0 +1,3 @@
+REPLACE
+INTO db.table
+VALUES (1, 'Old', '2014-08-20 18:47:00');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_select.sql b/tests/statics/Gravity/Database/MySQL/input_select.sql
new file mode 100644
index 0000000000000000000000000000000000000000..892c63eeccf97ea707667a4a3a4d6d65a22686d8
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_select.sql
@@ -0,0 +1,4 @@
+SELECT `c`.`identifier`,`c`.`language`,`c`.`language`,`fb`.`identifier`,COALESCE(NULL,1) as "Teste"
+from `table` as `c`
+         left join `bookmarks` fb on `c`.`identifier` = `fb`.`identifier`
+where `c`.`identifier` = "NOTIFICATION_UPDATE_ARRIVAL";
diff --git a/tests/statics/Gravity/Database/MySQL/input_unix_lf.sql b/tests/statics/Gravity/Database/MySQL/input_unix_lf.sql
new file mode 100644
index 0000000000000000000000000000000000000000..5512f2fea7b4eab9091d52ec056b6d732d39eb86
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_unix_lf.sql
@@ -0,0 +1,6 @@
+INSERT 
+INTO `database`.`table` (`param1`, `param2`, `param3`) 
+	VALUES 
+		(1, 'Lorem', 'Lorem') ,
+	 (2, 'Lorem', 'Lorem') , (3, 'Lorem', 'Lorem') , (4, 'Lorem', 'Lorem') , 
+		(5, 'Lorem', 'Lorem');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_update.sql b/tests/statics/Gravity/Database/MySQL/input_update.sql
new file mode 100644
index 0000000000000000000000000000000000000000..f6f89132738df647ad21ea3b6896644de1f36313
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_update.sql
@@ -0,0 +1,3 @@
+UPDATE `database`.`table`
+SET `content` = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit ..', `language` = 'li-LI'
+    WHERE `identifier` = 'LOREM_IPSUM' AND `id` = 2;
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_upserts_function_multi_rows.sql b/tests/statics/Gravity/Database/MySQL/input_upserts_function_multi_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..3be06daa949b6e6a0be83357266cd98f20ff25d0
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_upserts_function_multi_rows.sql
@@ -0,0 +1,6 @@
+INSERT INTO `database`.`table` (`identifier`, `language`, `content`) 
+VALUES 
+  (COALESCE(`param1`,`param2`,0),"en_US","What is Lorem Ipsum?") ,
+  (COALESCE(`param1`,`param2`,0),"zh_CN","什么是Lorem Ipsum?") ,
+  (COALESCE(`param1`,`param2`,0),"nl_NL","Wat is Lorem Ipsum?")
+ON DUPLICATE KEY UPDATE `content`="What is Lorem Ipsum?";
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_upserts_multi_rows.sql b/tests/statics/Gravity/Database/MySQL/input_upserts_multi_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..e53017b20c9ba6c1c5a03bac8de107d3fd358330
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_upserts_multi_rows.sql
@@ -0,0 +1,10 @@
+INSERT INTO `database`.`table` (`identifier`, `language`, `content`)
+VALUES
+    ("LOREM_IPSUM","pt-PT","Lorem ipsum dolor sit amet .."),
+	("LOREM_IPSUM","zh-CN","Lorem ipsum dolor sit amet .."),
+	("LOREM_IPSUM","nl-NL","Lorem ipsum dolor sit amet .."),
+	("LOREM_IPSUM","pt-BR","Lorem ipsum dolor sit amet .."),
+	("LOREM_IPSUM","en-EN","Lorem ipsum dolor sit amet .."),
+	("LOREM_IPSUM","en-US","Lorem ipsum dolor sit amet .."),
+	("LOREM_IPSUM","li-LI","Lorem ipsum dolor sit amet ..")
+        ON DUPLICATE KEY UPDATE `content`="Lorem ipsum dolor sit amet ..";
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_upserts_single_row.sql b/tests/statics/Gravity/Database/MySQL/input_upserts_single_row.sql
new file mode 100644
index 0000000000000000000000000000000000000000..f87b974961172986d4981e5d7ba4071d4f768ab1
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_upserts_single_row.sql
@@ -0,0 +1,4 @@
+INSERT INTO `database`.`table` (`identifier`, `language`, `content`)
+VALUES
+    ("LOREM_IPSUM","li-LI","Lorem ipsum dolor sit amet ..")
+        ON DUPLICATE KEY UPDATE `content`="Lorem ipsum dolor sit amet ..";
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/input_win_crlf.sql b/tests/statics/Gravity/Database/MySQL/input_win_crlf.sql
new file mode 100644
index 0000000000000000000000000000000000000000..0494096daf1c851244f3029616cb85d0972018e4
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/input_win_crlf.sql
@@ -0,0 +1,6 @@
+INSERT 
+INTO `database`.`table` (`param1`, `param2`, `param3`) 
+	VALUES 
+		(1, 'Lorem', 'Lorem') ,
+	 (2, 'Lorem', 'Lorem') , (3, 'Lorem', 'Lorem') , (4, 'Lorem', 'Lorem') , 
+		(5, 'Lorem', 'Lorem');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_collapse_insert_different_row.sql b/tests/statics/Gravity/Database/MySQL/output_collapse_insert_different_row.sql
new file mode 100644
index 0000000000000000000000000000000000000000..7b08ed15299b54d9ac7a1908b92b249d8a4bdaf1
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_collapse_insert_different_row.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) VALUES (COALESCE("?","?",?), ?, '?'), (?, ?, '?'), (?, ?, '?'), (COALESCE("?","?",?), ?, '?')
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_collapse_insert_function_multi_rows.sql b/tests/statics/Gravity/Database/MySQL/output_collapse_insert_function_multi_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..c1ef308cfc23c6c76b57f63a156915a38412a576
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_collapse_insert_function_multi_rows.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) VALUES (COALESCE("?","?",?), ?, '?');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_collapse_insert_multi_rows.sql b/tests/statics/Gravity/Database/MySQL/output_collapse_insert_multi_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..d6e0a65242116e78a392185c2b5abb5c3d09b97a
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_collapse_insert_multi_rows.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) VALUES (?, '?', '?');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_collapse_insert_no_rows.sql b/tests/statics/Gravity/Database/MySQL/output_collapse_insert_no_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..b0398bb01f3e2f1e727b59a15111aa83114abac1
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_collapse_insert_no_rows.sql
@@ -0,0 +1 @@
+INSERT INTO `table1` (`param1`, `param2`) SELECT `param1`,`param2` FROM `table2` WHERE `table2`.`param3` > ?;
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_collapse_insert_single_row.sql b/tests/statics/Gravity/Database/MySQL/output_collapse_insert_single_row.sql
new file mode 100644
index 0000000000000000000000000000000000000000..5db1365fe30c6cbeb3745207db9854890b6a3400
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_collapse_insert_single_row.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) VALUES (?, '?', '?')
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_collapse_replace_multi_rows.sql b/tests/statics/Gravity/Database/MySQL/output_collapse_replace_multi_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..3a7f428c434d4cd57c53842df262880921fcdb0d
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_collapse_replace_multi_rows.sql
@@ -0,0 +1 @@
+REPLACE INTO `database`.`table` (`param1`, `param2`, `param3`) VALUES (?, '?', '?');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_collapse_select.sql b/tests/statics/Gravity/Database/MySQL/output_collapse_select.sql
new file mode 100644
index 0000000000000000000000000000000000000000..333aa7f130999c85cdfe798d5f3fe59fb81835ff
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_collapse_select.sql
@@ -0,0 +1 @@
+SELECT `c`.`identifier`,`c`.`language`,`c`.`language`,`fb`.`identifier`,COALESCE(NULL,?) as "?" from `table` as `c` left join `bookmarks` fb on `c`.`identifier` = `fb`.`identifier` where `c`.`identifier` = "?";
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_collapse_upserts_function.sql b/tests/statics/Gravity/Database/MySQL/output_collapse_upserts_function.sql
new file mode 100644
index 0000000000000000000000000000000000000000..8823a16655e5524702f589a1d5192b314572d061
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_collapse_upserts_function.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`identifier`, `language`, `content`) VALUES (COALESCE("?","?",?),"?","?") ON DUPLICATE KEY UPDATE `content`="?";
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_collapse_upserts_multi_rows.sql b/tests/statics/Gravity/Database/MySQL/output_collapse_upserts_multi_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..94c8703bee15cc736da0edc18eb3a3d5fe9e4b8a
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_collapse_upserts_multi_rows.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`identifier`, `language`, `content`) VALUES ("?","?","?") ON DUPLICATE KEY UPDATE `content`="?";
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_collapse_upserts_single_row.sql b/tests/statics/Gravity/Database/MySQL/output_collapse_upserts_single_row.sql
new file mode 100644
index 0000000000000000000000000000000000000000..94c8703bee15cc736da0edc18eb3a3d5fe9e4b8a
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_collapse_upserts_single_row.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`identifier`, `language`, `content`) VALUES ("?","?","?") ON DUPLICATE KEY UPDATE `content`="?";
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_create.sql b/tests/statics/Gravity/Database/MySQL/output_create.sql
new file mode 100644
index 0000000000000000000000000000000000000000..10b3de199f493de3cb6f34c21ecbfc959eba1e54
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_create.sql
@@ -0,0 +1 @@
+/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `table` ( `param1` varchar(?) NOT NULL, `param2` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`changedData`)), `param3` int(?) NOT NULL, `param4` bigint(?) NOT NULL, `param5` enum('?','?','?') NOT NULL, `param6` varchar(?) GENERATED ALWAYS AS (json_value(from_base64(`id`),'?')) STORED, `param7` tinyint(?) GENERATED ALWAYS AS (if(json_value(from_base64(`id`),'?') = '?',?,?)) STORED, `param8` date GENERATED ALWAYS AS (json_value(from_base64(`id`),'?')) STORED, `param9` char(?) GENERATED ALWAYS AS (json_value(from_base64(`id`),'?')) STORED, `param10` char(?) GENERATED ALWAYS AS (json_value(from_base64(`id`),'?')) STORED, `param11` char(?) GENERATED ALWAYS AS (json_value(from_base64(`id`),'?')) STORED, PRIMARY KEY (`param1`,`param2`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 `PAGE_COMPRESSED`=?; /*!40101 SET character_set_client = @saved_cs_client */;
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_cte.sql b/tests/statics/Gravity/Database/MySQL/output_cte.sql
new file mode 100644
index 0000000000000000000000000000000000000000..87bd19d42a484826a1b8dbf5f68e9397cbd7222f
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_cte.sql
@@ -0,0 +1 @@
+WITH `teste` AS ( SELECT `c`.`language` as `lang`, RAND() as `num` FROM `table1` as `c` LEFT JOIN `table2` as `fb` ON `c`.`id`=`fb`.`id` WHERE COALESCE(NULL,?) = ? AND `c`.`id` = ? GROUP BY `lang` ORDER BY `num` DESC LIMIT ? OFFSET ? ) SELECT `table1`.* FROM `table1` JOIN `teste`;
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_insert_different_rows.sql b/tests/statics/Gravity/Database/MySQL/output_insert_different_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..e51a3e7b62b2fc9208be20d22f10a1e14775615a
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_insert_different_rows.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) VALUES (CONCAT(COALESCE(`param3`,null),`param4`), "?", '?'), ("?", "?", ?), ("?", '?', '?'), (CONCAT(COALESCE(`param3`,null),"?",`param4`), "?", '?'), (CONCAT(COALESCE(`param3`,null),"?",`param4`), "?", '?'), (CONCAT(COALESCE(`param3`,null),"?",`param4`), "?", '?'), (CONCAT(COALESCE(`param3`,null),"?",`param4`), "?", '?');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_insert_multi_rows.sql b/tests/statics/Gravity/Database/MySQL/output_insert_multi_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..d6e0a65242116e78a392185c2b5abb5c3d09b97a
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_insert_multi_rows.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) VALUES (?, '?', '?');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_insert_no_rows.sql b/tests/statics/Gravity/Database/MySQL/output_insert_no_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..b0398bb01f3e2f1e727b59a15111aa83114abac1
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_insert_no_rows.sql
@@ -0,0 +1 @@
+INSERT INTO `table1` (`param1`, `param2`) SELECT `param1`,`param2` FROM `table2` WHERE `table2`.`param3` > ?;
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_insert_null_diff_case_multi_rows.sql b/tests/statics/Gravity/Database/MySQL/output_insert_null_diff_case_multi_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..3d759cccb79228e5f88b78fd98767fcfa07f64cf
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_insert_null_diff_case_multi_rows.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) VALUES (CONCAT(COALESCE(`param3`,null),"?",`param4`), "?", '?'), (CONCAT(COALESCE(`param3`,NULL),"?",`param4`), "?", '?'), (CONCAT(COALESCE(`param3`,null),"?",`param4`), "?", '?');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_insert_single_row.sql b/tests/statics/Gravity/Database/MySQL/output_insert_single_row.sql
new file mode 100644
index 0000000000000000000000000000000000000000..d6e0a65242116e78a392185c2b5abb5c3d09b97a
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_insert_single_row.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) VALUES (?, '?', '?');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_insert_value_or_null_multirows.sql b/tests/statics/Gravity/Database/MySQL/output_insert_value_or_null_multirows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..32c6d5afbc52d8d29e5079b6c428705271cb6908
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_insert_value_or_null_multirows.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) VALUES (CONCAT(COALESCE(`param3`,"?"),`param4`), "?", '?'), (CONCAT(COALESCE(`param3`,null),"?",`param4`), "?", '?'), (CONCAT(COALESCE(`param3`,"?"),"?",`param4`), "?", '?'), (CONCAT(COALESCE(`param3`,null),"?",`param4`), "?", '?'), (CONCAT(COALESCE(`param3`,null),"?",`param4`), "?", '?');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_mac_cr.sql b/tests/statics/Gravity/Database/MySQL/output_mac_cr.sql
new file mode 100644
index 0000000000000000000000000000000000000000..d6e0a65242116e78a392185c2b5abb5c3d09b97a
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_mac_cr.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) VALUES (?, '?', '?');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_maxscalehints.sql b/tests/statics/Gravity/Database/MySQL/output_maxscalehints.sql
new file mode 100644
index 0000000000000000000000000000000000000000..2cffdd4ea2546fc98eda69f6f830692b13c13df8
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_maxscalehints.sql
@@ -0,0 +1 @@
+SELECT * FROM table1 where tnumber=? and ttext="?";
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_replace_multi_rows.sql b/tests/statics/Gravity/Database/MySQL/output_replace_multi_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..c7da48a8a193dbd3a1122e7fc6a8568c38eac836
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_replace_multi_rows.sql
@@ -0,0 +1 @@
+REPLACE INTO `db`.`table` VALUES (?, '?', '?');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_replace_single_row.sql b/tests/statics/Gravity/Database/MySQL/output_replace_single_row.sql
new file mode 100644
index 0000000000000000000000000000000000000000..6666020019b6b3e263b6e88c6d6d130a39373d67
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_replace_single_row.sql
@@ -0,0 +1 @@
+REPLACE INTO db.table VALUES (?, '?', '?');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_select.sql b/tests/statics/Gravity/Database/MySQL/output_select.sql
new file mode 100644
index 0000000000000000000000000000000000000000..333aa7f130999c85cdfe798d5f3fe59fb81835ff
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_select.sql
@@ -0,0 +1 @@
+SELECT `c`.`identifier`,`c`.`language`,`c`.`language`,`fb`.`identifier`,COALESCE(NULL,?) as "?" from `table` as `c` left join `bookmarks` fb on `c`.`identifier` = `fb`.`identifier` where `c`.`identifier` = "?";
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_unix_lf.sql b/tests/statics/Gravity/Database/MySQL/output_unix_lf.sql
new file mode 100644
index 0000000000000000000000000000000000000000..d6e0a65242116e78a392185c2b5abb5c3d09b97a
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_unix_lf.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) VALUES (?, '?', '?');
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_update.sql b/tests/statics/Gravity/Database/MySQL/output_update.sql
new file mode 100644
index 0000000000000000000000000000000000000000..f87088ece9ace351d15f2fd5031bdb580effc913
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_update.sql
@@ -0,0 +1 @@
+UPDATE `database`.`table` SET `content` = '?', `language` = '?' WHERE `identifier` = '?' AND `id` = ?;
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_upserts_function_multi_rows.sql b/tests/statics/Gravity/Database/MySQL/output_upserts_function_multi_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..e6ae6d4305678a6b55b1d4323836b42540c3b8b9
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_upserts_function_multi_rows.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`identifier`, `language`, `content`) VALUES (COALESCE(`param1`,`param2`,?),"?","?") ON DUPLICATE KEY UPDATE `content`="?";
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_upserts_multi_rows.sql b/tests/statics/Gravity/Database/MySQL/output_upserts_multi_rows.sql
new file mode 100644
index 0000000000000000000000000000000000000000..94c8703bee15cc736da0edc18eb3a3d5fe9e4b8a
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_upserts_multi_rows.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`identifier`, `language`, `content`) VALUES ("?","?","?") ON DUPLICATE KEY UPDATE `content`="?";
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_upserts_single_row.sql b/tests/statics/Gravity/Database/MySQL/output_upserts_single_row.sql
new file mode 100644
index 0000000000000000000000000000000000000000..94c8703bee15cc736da0edc18eb3a3d5fe9e4b8a
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_upserts_single_row.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`identifier`, `language`, `content`) VALUES ("?","?","?") ON DUPLICATE KEY UPDATE `content`="?";
\ No newline at end of file
diff --git a/tests/statics/Gravity/Database/MySQL/output_win_crlf.sql b/tests/statics/Gravity/Database/MySQL/output_win_crlf.sql
new file mode 100644
index 0000000000000000000000000000000000000000..d6e0a65242116e78a392185c2b5abb5c3d09b97a
--- /dev/null
+++ b/tests/statics/Gravity/Database/MySQL/output_win_crlf.sql
@@ -0,0 +1 @@
+INSERT INTO `database`.`table` (`param1`, `param2`, `param3`) VALUES (?, '?', '?');
\ No newline at end of file
