public static function getTableHash(
        \mysqli $mysqliConn, 
        string $tableName, 
        array $columns = array()
    ) : string
    {
        $tableHash = "";
        
        if (count($columns) == 0)
        {
            $columns = MysqliLib::getTableColumnNames($mysqliConn, $tableName);
            sort($columns);
        }
        
        $wrappedColumnList = array();
        
        # Using coalesce to prevent null values causing sync issues as raised in the
        # NullColumnTest test. E.g. [2, null, null] and [null, 2, null] would be considered equal
        # otherwise.
        foreach ($columns as $column)
        {
            $wrappedColumnList[] = "COALESCE(`" . $column . "`, 'NULL')";
        }
        
        # This fixes an issue with using group_concat on extremely large tables (num rows)
        # and allows for tables with up to 576,460,752,303,423,000 rows 
        # (18,446,744,073,709,547,520 / 33)
        $mysqliConn->query("SET group_concat_max_len = 18446744073709547520");
        
        $primaryKeyArray = MysqliLib::fetchPrimaryKey($mysqliConn, $tableName);
        $primaryKeyString = MysqliLib::convertPrimaryKeyArrayToString($primaryKeyArray);
        $orderByString = $primaryKeyString;
        
        $query =
            "SELECT MD5(GROUP_CONCAT(MD5(CONCAT_WS('#'," . implode(',', $wrappedColumnList) . ")))) " .
            "AS `hash` " .
            "FROM `{$tableName}` ORDER BY {$orderByString}";
        
        /* @var $result mysqli_result */
        $result = $mysqliConn->query($query);
        
        if ($result !== FALSE)
        {
            $row = $result->fetch_assoc();
            
            if ($row['hash'] === NULL)
            {
                // table has no data
                $tablehash = "";
            }
            else
            {
                $tableHash = $row['hash'];
            }
        }
        else
        {
            throw new \Exception("Failed to fetch table hash");
        }
        
        return $tableHash;
    }