Current File : //proc/thread-self/root/usr/share/phpmyadmin/libraries/classes/Plugins/Export/ExportSql.php
<?php
/**
 * Set of functions used to build SQL dumps of tables
 */

declare(strict_types=1);

namespace PhpMyAdmin\Plugins\Export;

use PhpMyAdmin\Charsets;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\FieldMetadata;
use PhpMyAdmin\Plugins\ExportPlugin;
use PhpMyAdmin\Properties\Options\Groups\OptionsPropertyMainGroup;
use PhpMyAdmin\Properties\Options\Groups\OptionsPropertyRootGroup;
use PhpMyAdmin\Properties\Options\Groups\OptionsPropertySubgroup;
use PhpMyAdmin\Properties\Options\Items\BoolPropertyItem;
use PhpMyAdmin\Properties\Options\Items\MessageOnlyPropertyItem;
use PhpMyAdmin\Properties\Options\Items\NumberPropertyItem;
use PhpMyAdmin\Properties\Options\Items\RadioPropertyItem;
use PhpMyAdmin\Properties\Options\Items\SelectPropertyItem;
use PhpMyAdmin\Properties\Options\Items\TextPropertyItem;
use PhpMyAdmin\Properties\Plugins\ExportPluginProperties;
use PhpMyAdmin\SqlParser\Components\CreateDefinition;
use PhpMyAdmin\SqlParser\Context;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Statements\CreateStatement;
use PhpMyAdmin\SqlParser\Token;
use PhpMyAdmin\Util;
use PhpMyAdmin\Version;

use function __;
use function array_keys;
use function bin2hex;
use function count;
use function defined;
use function explode;
use function implode;
use function in_array;
use function intval;
use function is_array;
use function mb_strlen;
use function mb_strpos;
use function mb_substr;
use function preg_quote;
use function preg_replace;
use function preg_split;
use function sprintf;
use function str_repeat;
use function str_replace;
use function strtotime;
use function strtoupper;
use function trigger_error;

use const E_USER_ERROR;
use const PHP_VERSION;

/**
 * Handles the export for the SQL class
 */
class ExportSql extends ExportPlugin
{
    /**
     * Whether charset header was sent.
     *
     * @var bool
     */
    private $sentCharset = false;

    protected function init(): void
    {
        // Avoids undefined variables, use NULL so isset() returns false
        if (isset($GLOBALS['sql_backquotes'])) {
            return;
        }

        $GLOBALS['sql_backquotes'] = null;
    }

    /**
     * @psalm-return non-empty-lowercase-string
     */
    public function getName(): string
    {
        return 'sql';
    }

    protected function setProperties(): ExportPluginProperties
    {
        global $plugin_param, $dbi;

        $hideSql = false;
        $hideStructure = false;
        if ($plugin_param['export_type'] === 'table' && ! $plugin_param['single_table']) {
            $hideStructure = true;
            $hideSql = true;
        }

        // In case we have `raw_query` parameter set,
        // we initialize SQL option
        if (isset($_REQUEST['raw_query'])) {
            $hideStructure = false;
            $hideSql = false;
        }

        $exportPluginProperties = new ExportPluginProperties();
        $exportPluginProperties->setText('SQL');
        $exportPluginProperties->setExtension('sql');
        $exportPluginProperties->setMimeType('text/x-sql');

        if ($hideSql) {
            return $exportPluginProperties;
        }

        $exportPluginProperties->setOptionsText(__('Options'));

        // create the root group that will be the options field for
        // $exportPluginProperties
        // this will be shown as "Format specific options"
        $exportSpecificOptions = new OptionsPropertyRootGroup('Format Specific Options');

        // general options main group
        $generalOptions = new OptionsPropertyMainGroup('general_opts');

        // comments
        $subgroup = new OptionsPropertySubgroup('include_comments');
        $leaf = new BoolPropertyItem(
            'include_comments',
            __(
                'Display comments <i>(includes info such as export timestamp, PHP version, and server version)</i>'
            )
        );
        $subgroup->setSubgroupHeader($leaf);

        $leaf = new TextPropertyItem(
            'header_comment',
            __('Additional custom header comment (\n splits lines):')
        );
        $subgroup->addProperty($leaf);
        $leaf = new BoolPropertyItem(
            'dates',
            __(
                'Include a timestamp of when databases were created, last updated, and last checked'
            )
        );
        $subgroup->addProperty($leaf);
        $relationParameters = $this->relation->getRelationParameters();
        if ($relationParameters->relationFeature !== null) {
            $leaf = new BoolPropertyItem(
                'relation',
                __('Display foreign key relationships')
            );
            $subgroup->addProperty($leaf);
        }

        if ($relationParameters->browserTransformationFeature !== null) {
            $leaf = new BoolPropertyItem(
                'mime',
                __('Display media types')
            );
            $subgroup->addProperty($leaf);
        }

        $generalOptions->addProperty($subgroup);

        // enclose in a transaction
        $leaf = new BoolPropertyItem(
            'use_transaction',
            __('Enclose export in a transaction')
        );
        $leaf->setDoc(
            [
                'programs',
                'mysqldump',
                'option_mysqldump_single-transaction',
            ]
        );
        $generalOptions->addProperty($leaf);

        // disable foreign key checks
        $leaf = new BoolPropertyItem(
            'disable_fk',
            __('Disable foreign key checks')
        );
        $leaf->setDoc(
            [
                'manual_MySQL_Database_Administration',
                'server-system-variables',
                'sysvar_foreign_key_checks',
            ]
        );
        $generalOptions->addProperty($leaf);

        // export views as tables
        $leaf = new BoolPropertyItem(
            'views_as_tables',
            __('Export views as tables')
        );
        $generalOptions->addProperty($leaf);

        // export metadata
        $leaf = new BoolPropertyItem(
            'metadata',
            __('Export metadata')
        );
        $generalOptions->addProperty($leaf);

        // compatibility maximization
        $compats = $dbi->getCompatibilities();
        if (count($compats) > 0) {
            $values = [];
            foreach ($compats as $val) {
                $values[$val] = $val;
            }

            $leaf = new SelectPropertyItem(
                'compatibility',
                __(
                    'Database system or older MySQL server to maximize output compatibility with:'
                )
            );
            $leaf->setValues($values);
            $leaf->setDoc(
                [
                    'manual_MySQL_Database_Administration',
                    'Server_SQL_mode',
                ]
            );
            $generalOptions->addProperty($leaf);

            unset($values);
        }

        // what to dump (structure/data/both)
        $subgroup = new OptionsPropertySubgroup(
            'dump_table',
            __('Dump table')
        );
        $leaf = new RadioPropertyItem('structure_or_data');
        $leaf->setValues(
            [
                'structure' => __('structure'),
                'data' => __('data'),
                'structure_and_data' => __('structure and data'),
            ]
        );
        $subgroup->setSubgroupHeader($leaf);
        $generalOptions->addProperty($subgroup);

        // add the main group to the root group
        $exportSpecificOptions->addProperty($generalOptions);

        // structure options main group
        if (! $hideStructure) {
            $structureOptions = new OptionsPropertyMainGroup(
                'structure',
                __('Object creation options')
            );
            $structureOptions->setForce('data');

            // begin SQL Statements
            $subgroup = new OptionsPropertySubgroup();
            $leaf = new MessageOnlyPropertyItem(
                'add_statements',
                __('Add statements:')
            );
            $subgroup->setSubgroupHeader($leaf);

            // server export options
            if ($plugin_param['export_type'] === 'server') {
                $leaf = new BoolPropertyItem(
                    'drop_database',
                    sprintf(__('Add %s statement'), '<code>DROP DATABASE IF EXISTS</code>')
                );
                $subgroup->addProperty($leaf);
            }

            if ($plugin_param['export_type'] === 'database') {
                $createClause = '<code>CREATE DATABASE / USE</code>';
                $leaf = new BoolPropertyItem(
                    'create_database',
                    sprintf(__('Add %s statement'), $createClause)
                );
                $subgroup->addProperty($leaf);
            }

            if ($plugin_param['export_type'] === 'table') {
                $dropClause = $dbi->getTable($GLOBALS['db'], $GLOBALS['table'])->isView()
                    ? '<code>DROP VIEW</code>'
                    : '<code>DROP TABLE</code>';
            } else {
                $dropClause = '<code>DROP TABLE / VIEW / PROCEDURE / FUNCTION / EVENT</code>';
            }

            $dropClause .= '<code> / TRIGGER</code>';

            $leaf = new BoolPropertyItem(
                'drop_table',
                sprintf(__('Add %s statement'), $dropClause)
            );
            $subgroup->addProperty($leaf);

            $subgroupCreateTable = new OptionsPropertySubgroup();

            // Add table structure option
            $leaf = new BoolPropertyItem(
                'create_table',
                sprintf(__('Add %s statement'), '<code>CREATE TABLE</code>')
            );
            $subgroupCreateTable->setSubgroupHeader($leaf);

            $leaf = new BoolPropertyItem(
                'if_not_exists',
                '<code>IF NOT EXISTS</code> ' . __(
                    '(less efficient as indexes will be generated during table creation)'
                )
            );
            $subgroupCreateTable->addProperty($leaf);

            $leaf = new BoolPropertyItem(
                'auto_increment',
                sprintf(__('%s value'), '<code>AUTO_INCREMENT</code>')
            );
            $subgroupCreateTable->addProperty($leaf);

            $subgroup->addProperty($subgroupCreateTable);

            // Add view option
            $subgroupCreateView = new OptionsPropertySubgroup();
            $leaf = new BoolPropertyItem(
                'create_view',
                sprintf(__('Add %s statement'), '<code>CREATE VIEW</code>')
            );
            $subgroupCreateView->setSubgroupHeader($leaf);

            $leaf = new BoolPropertyItem(
                'simple_view_export',
                /* l10n: Allow simplifying exported view syntax to only "CREATE VIEW" */
                __('Use simple view export')
            );
            $subgroupCreateView->addProperty($leaf);

            $leaf = new BoolPropertyItem(
                'view_current_user',
                __('Exclude definition of current user')
            );
            $subgroupCreateView->addProperty($leaf);

            $leaf = new BoolPropertyItem(
                'or_replace_view',
                sprintf(__('%s view'), '<code>OR REPLACE</code>')
            );
            $subgroupCreateView->addProperty($leaf);

            $subgroup->addProperty($subgroupCreateView);

            $leaf = new BoolPropertyItem(
                'procedure_function',
                sprintf(
                    __('Add %s statement'),
                    '<code>CREATE PROCEDURE / FUNCTION / EVENT</code>'
                )
            );
            $subgroup->addProperty($leaf);

            // Add triggers option
            $leaf = new BoolPropertyItem(
                'create_trigger',
                sprintf(__('Add %s statement'), '<code>CREATE TRIGGER</code>')
            );
            $subgroup->addProperty($leaf);

            $structureOptions->addProperty($subgroup);

            $leaf = new BoolPropertyItem(
                'backquotes',
                __(
                    'Enclose table and column names with backquotes '
                    . '<i>(Protects column and table names formed with'
                    . ' special characters or keywords)</i>'
                )
            );

            $structureOptions->addProperty($leaf);

            // add the main group to the root group
            $exportSpecificOptions->addProperty($structureOptions);
        }

        // begin Data options
        $dataOptions = new OptionsPropertyMainGroup(
            'data',
            __('Data creation options')
        );
        $dataOptions->setForce('structure');
        $leaf = new BoolPropertyItem(
            'truncate',
            __('Truncate table before insert')
        );
        $dataOptions->addProperty($leaf);

        // begin SQL Statements
        $subgroup = new OptionsPropertySubgroup();
        $leaf = new MessageOnlyPropertyItem(
            __('Instead of <code>INSERT</code> statements, use:')
        );
        $subgroup->setSubgroupHeader($leaf);

        $leaf = new BoolPropertyItem(
            'delayed',
            __('<code>INSERT DELAYED</code> statements')
        );
        $leaf->setDoc(
            [
                'manual_MySQL_Database_Administration',
                'insert_delayed',
            ]
        );
        $subgroup->addProperty($leaf);

        $leaf = new BoolPropertyItem(
            'ignore',
            __('<code>INSERT IGNORE</code> statements')
        );
        $leaf->setDoc(
            [
                'manual_MySQL_Database_Administration',
                'insert',
            ]
        );
        $subgroup->addProperty($leaf);
        $dataOptions->addProperty($subgroup);

        // Function to use when dumping dat
        $leaf = new SelectPropertyItem(
            'type',
            __('Function to use when dumping data:')
        );
        $leaf->setValues(
            [
                'INSERT' => 'INSERT',
                'UPDATE' => 'UPDATE',
                'REPLACE' => 'REPLACE',
            ]
        );
        $dataOptions->addProperty($leaf);

        /* Syntax to use when inserting data */
        $subgroup = new OptionsPropertySubgroup();
        $leaf = new MessageOnlyPropertyItem(
            null,
            __('Syntax to use when inserting data:')
        );
        $subgroup->setSubgroupHeader($leaf);
        $leaf = new RadioPropertyItem(
            'insert_syntax',
            __('<code>INSERT IGNORE</code> statements')
        );
        $leaf->setValues(
            [
                'complete' => __(
                    'include column names in every <code>INSERT</code> statement'
                    . ' <br> &nbsp; &nbsp; &nbsp; Example: <code>INSERT INTO'
                    . ' tbl_name (col_A,col_B,col_C) VALUES (1,2,3)</code>'
                ),
                'extended' => __(
                    'insert multiple rows in every <code>INSERT</code> statement'
                    . '<br> &nbsp; &nbsp; &nbsp; Example: <code>INSERT INTO'
                    . ' tbl_name VALUES (1,2,3), (4,5,6), (7,8,9)</code>'
                ),
                'both' => __(
                    'both of the above<br> &nbsp; &nbsp; &nbsp; Example:'
                    . ' <code>INSERT INTO tbl_name (col_A,col_B,col_C) VALUES'
                    . ' (1,2,3), (4,5,6), (7,8,9)</code>'
                ),
                'none' => __(
                    'neither of the above<br> &nbsp; &nbsp; &nbsp; Example:'
                    . ' <code>INSERT INTO tbl_name VALUES (1,2,3)</code>'
                ),
            ]
        );
        $subgroup->addProperty($leaf);
        $dataOptions->addProperty($subgroup);

        // Max length of query
        $leaf = new NumberPropertyItem(
            'max_query_size',
            __('Maximal length of created query')
        );
        $dataOptions->addProperty($leaf);

        // Dump binary columns in hexadecimal
        $leaf = new BoolPropertyItem(
            'hex_for_binary',
            __(
                'Dump binary columns in hexadecimal notation <i>(for example, "abc" becomes 0x616263)</i>'
            )
        );
        $dataOptions->addProperty($leaf);

        // Dump time in UTC
        $leaf = new BoolPropertyItem(
            'utc_time',
            __(
                'Dump TIMESTAMP columns in UTC <i>(enables TIMESTAMP columns'
                . ' to be dumped and reloaded between servers in different'
                . ' time zones)</i>'
            )
        );
        $dataOptions->addProperty($leaf);

        // add the main group to the root group
        $exportSpecificOptions->addProperty($dataOptions);

        // set the options for the export plugin property item
        $exportPluginProperties->setOptions($exportSpecificOptions);

        return $exportPluginProperties;
    }

    /**
     * Generates SQL for routines export
     *
     * @param string $db        Database
     * @param array  $aliases   Aliases of db/table/columns
     * @param string $type      Type of exported routine
     * @param string $name      Verbose name of exported routine
     * @param array  $routines  List of routines to export
     * @param string $delimiter Delimiter to use in SQL
     *
     * @return string SQL query
     */
    protected function exportRoutineSQL(
        $db,
        array $aliases,
        $type,
        $name,
        array $routines,
        $delimiter
    ) {
        global $crlf, $cfg, $dbi;

        $text = $this->exportComment()
            . $this->exportComment($name)
            . $this->exportComment();

        $usedAlias = false;
        $procQuery = '';

        foreach ($routines as $routine) {
            if (! empty($GLOBALS['sql_drop_table'])) {
                $procQuery .= 'DROP ' . $type . ' IF EXISTS '
                    . Util::backquote($routine)
                    . $delimiter . $crlf;
            }

            $createQuery = $this->replaceWithAliases(
                $dbi->getDefinition($db, $type, $routine),
                $aliases,
                $db,
                '',
                $flag
            );
            if (! empty($createQuery) && $cfg['Export']['remove_definer_from_definitions']) {
                // Remove definer clause from routine definitions
                $parser = new Parser($createQuery);
                $statement = $parser->statements[0];
                $statement->options->remove('DEFINER');
                $createQuery = $statement->build();
            }

            // One warning per database
            if ($flag) {
                $usedAlias = true;
            }

            $procQuery .= $createQuery . $delimiter . $crlf . $crlf;
        }

        if ($usedAlias) {
            $text .= $this->exportComment(
                __('It appears your database uses routines;')
            )
            . $this->exportComment(
                __('alias export may not work reliably in all cases.')
            )
            . $this->exportComment();
        }

        $text .= $procQuery;

        return $text;
    }

    /**
     * Exports routines (procedures and functions)
     *
     * @param string $db      Database
     * @param array  $aliases Aliases of db/table/columns
     */
    public function exportRoutines($db, array $aliases = []): bool
    {
        global $crlf, $dbi;

        $dbAlias = $db;
        $this->initAlias($aliases, $dbAlias);

        $text = '';
        $delimiter = '$$';

        $procedureNames = $dbi->getProceduresOrFunctions($db, 'PROCEDURE');
        $functionNames = $dbi->getProceduresOrFunctions($db, 'FUNCTION');

        if ($procedureNames || $functionNames) {
            $text .= $crlf
                . 'DELIMITER ' . $delimiter . $crlf;

            if ($procedureNames) {
                $text .= $this->exportRoutineSQL(
                    $db,
                    $aliases,
                    'PROCEDURE',
                    __('Procedures'),
                    $procedureNames,
                    $delimiter
                );
            }

            if ($functionNames) {
                $text .= $this->exportRoutineSQL(
                    $db,
                    $aliases,
                    'FUNCTION',
                    __('Functions'),
                    $functionNames,
                    $delimiter
                );
            }

            $text .= 'DELIMITER ;' . $crlf;
        }

        if (! empty($text)) {
            return $this->export->outputHandler($text);
        }

        return false;
    }

    /**
     * Possibly outputs comment
     *
     * @param string $text Text of comment
     *
     * @return string The formatted comment
     */
    private function exportComment($text = '')
    {
        if (isset($GLOBALS['sql_include_comments']) && $GLOBALS['sql_include_comments']) {
            // see https://dev.mysql.com/doc/refman/5.0/en/ansi-diff-comments.html
            if (empty($text)) {
                return '--' . $GLOBALS['crlf'];
            }

            $lines = preg_split("/\\r\\n|\\r|\\n/", $text);
            if ($lines === false) {
                return '--' . $GLOBALS['crlf'];
            }

            $result = [];
            foreach ($lines as $line) {
                $result[] = '-- ' . $line . $GLOBALS['crlf'];
            }

            return implode('', $result);
        }

        return '';
    }

    /**
     * Possibly outputs CRLF
     *
     * @return string crlf or nothing
     */
    private function possibleCRLF()
    {
        if (isset($GLOBALS['sql_include_comments']) && $GLOBALS['sql_include_comments']) {
            return $GLOBALS['crlf'];
        }

        return '';
    }

    /**
     * Outputs export footer
     */
    public function exportFooter(): bool
    {
        global $crlf, $dbi;

        $foot = '';

        if (isset($GLOBALS['sql_disable_fk'])) {
            $foot .= 'SET FOREIGN_KEY_CHECKS=1;' . $crlf;
        }

        if (isset($GLOBALS['sql_use_transaction'])) {
            $foot .= 'COMMIT;' . $crlf;
        }

        // restore connection settings
        if ($this->sentCharset) {
            $foot .= $crlf
                . '/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;'
                . $crlf
                . '/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;'
                . $crlf
                . '/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;'
                . $crlf;
            $this->sentCharset = false;
        }

        /* Restore timezone */
        if (isset($GLOBALS['sql_utc_time']) && $GLOBALS['sql_utc_time']) {
            $dbi->query('SET time_zone = "' . $GLOBALS['old_tz'] . '"');
        }

        return $this->export->outputHandler($foot);
    }

    /**
     * Outputs export header. It is the first method to be called, so all
     * the required variables are initialized here.
     */
    public function exportHeader(): bool
    {
        global $crlf, $cfg, $dbi;

        if (isset($GLOBALS['sql_compatibility'])) {
            $tmpCompat = $GLOBALS['sql_compatibility'];
            if ($tmpCompat === 'NONE') {
                $tmpCompat = '';
            }

            $dbi->tryQuery('SET SQL_MODE="' . $tmpCompat . '"');
            unset($tmpCompat);
        }

        $head = $this->exportComment('phpMyAdmin SQL Dump')
            . $this->exportComment('version ' . Version::VERSION)
            . $this->exportComment('https://www.phpmyadmin.net/')
            . $this->exportComment();
        $hostString = __('Host:') . ' ' . $cfg['Server']['host'];
        if (! empty($cfg['Server']['port'])) {
            $hostString .= ':' . $cfg['Server']['port'];
        }

        $head .= $this->exportComment($hostString);
        $head .= $this->exportComment(
            __('Generation Time:') . ' '
            . Util::localisedDate()
        )
        . $this->exportComment(
            __('Server version:') . ' ' . $dbi->getVersionString()
        )
        . $this->exportComment(__('PHP Version:') . ' ' . PHP_VERSION)
        . $this->possibleCRLF();

        if (isset($GLOBALS['sql_header_comment']) && ! empty($GLOBALS['sql_header_comment'])) {
            // '\n' is not a newline (like "\n" would be), it's the characters
            // backslash and n, as explained on the export interface
            $lines = explode('\n', $GLOBALS['sql_header_comment']);
            $head .= $this->exportComment();
            foreach ($lines as $oneLine) {
                $head .= $this->exportComment($oneLine);
            }

            $head .= $this->exportComment();
        }

        if (isset($GLOBALS['sql_disable_fk'])) {
            $head .= 'SET FOREIGN_KEY_CHECKS=0;' . $crlf;
        }

        // We want exported AUTO_INCREMENT columns to have still same value,
        // do this only for recent MySQL exports
        if (! isset($GLOBALS['sql_compatibility']) || $GLOBALS['sql_compatibility'] === 'NONE') {
            $head .= 'SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";' . $crlf;
        }

        if (isset($GLOBALS['sql_use_transaction'])) {
            $head .= 'START TRANSACTION;' . $crlf;
        }

        /* Change timezone if we should export timestamps in UTC */
        if (isset($GLOBALS['sql_utc_time']) && $GLOBALS['sql_utc_time']) {
            $head .= 'SET time_zone = "+00:00";' . $crlf;
            $GLOBALS['old_tz'] = $dbi
                ->fetchValue('SELECT @@session.time_zone');
            $dbi->query('SET time_zone = "+00:00"');
        }

        $head .= $this->possibleCRLF();

        if (! empty($GLOBALS['asfile'])) {
            // we are saving as file, therefore we provide charset information
            // so that a utility like the mysql client can interpret
            // the file correctly
            if (isset($GLOBALS['charset'], Charsets::$mysqlCharsetMap[$GLOBALS['charset']])) {
                // we got a charset from the export dialog
                $setNames = Charsets::$mysqlCharsetMap[$GLOBALS['charset']];
            } else {
                // by default we use the connection charset
                $setNames = Charsets::$mysqlCharsetMap['utf-8'];
            }

            if ($setNames === 'utf8' && $dbi->getVersion() > 50503) {
                $setNames = 'utf8mb4';
            }

            $head .= $crlf
                . '/*!40101 SET @OLD_CHARACTER_SET_CLIENT='
                . '@@CHARACTER_SET_CLIENT */;' . $crlf
                . '/*!40101 SET @OLD_CHARACTER_SET_RESULTS='
                . '@@CHARACTER_SET_RESULTS */;' . $crlf
                . '/*!40101 SET @OLD_COLLATION_CONNECTION='
                . '@@COLLATION_CONNECTION */;' . $crlf
                . '/*!40101 SET NAMES ' . $setNames . ' */;' . $crlf . $crlf;
            $this->sentCharset = true;
        }

        return $this->export->outputHandler($head);
    }

    /**
     * Outputs CREATE DATABASE statement
     *
     * @param string $db         Database name
     * @param string $exportType 'server', 'database', 'table'
     * @param string $dbAlias    Aliases of db
     */
    public function exportDBCreate($db, $exportType, $dbAlias = ''): bool
    {
        global $crlf, $dbi;

        if (empty($dbAlias)) {
            $dbAlias = $db;
        }

        if (isset($GLOBALS['sql_compatibility'])) {
            $compat = $GLOBALS['sql_compatibility'];
        } else {
            $compat = 'NONE';
        }

        if (isset($GLOBALS['sql_drop_database'])) {
            if (
                ! $this->export->outputHandler(
                    'DROP DATABASE IF EXISTS '
                    . Util::backquoteCompat(
                        $dbAlias,
                        $compat,
                        isset($GLOBALS['sql_backquotes'])
                    )
                    . ';' . $crlf
                )
            ) {
                return false;
            }
        }

        if ($exportType === 'database' && ! isset($GLOBALS['sql_create_database'])) {
            return true;
        }

        $createQuery = 'CREATE DATABASE IF NOT EXISTS '
            . Util::backquoteCompat($dbAlias, $compat, isset($GLOBALS['sql_backquotes']));
        $collation = $dbi->getDbCollation($db);
        if (mb_strpos($collation, '_')) {
            $createQuery .= ' DEFAULT CHARACTER SET '
                . mb_substr(
                    $collation,
                    0,
                    (int) mb_strpos($collation, '_')
                )
                . ' COLLATE ' . $collation;
        } else {
            $createQuery .= ' DEFAULT CHARACTER SET ' . $collation;
        }

        $createQuery .= ';' . $crlf;
        if (! $this->export->outputHandler($createQuery)) {
            return false;
        }

        return $this->exportUseStatement($dbAlias, $compat);
    }

    /**
     * Outputs USE statement
     *
     * @param string $db     db to use
     * @param string $compat sql compatibility
     */
    private function exportUseStatement($db, $compat): bool
    {
        global $crlf;

        if (isset($GLOBALS['sql_compatibility']) && $GLOBALS['sql_compatibility'] === 'NONE') {
            $result = $this->export->outputHandler(
                'USE '
                . Util::backquoteCompat(
                    $db,
                    $compat,
                    isset($GLOBALS['sql_backquotes'])
                )
                . ';' . $crlf
            );
        } else {
            $result = $this->export->outputHandler('USE ' . $db . ';' . $crlf);
        }

        return $result;
    }

    /**
     * Outputs database header
     *
     * @param string $db      Database name
     * @param string $dbAlias Alias of db
     */
    public function exportDBHeader($db, $dbAlias = ''): bool
    {
        if (empty($dbAlias)) {
            $dbAlias = $db;
        }

        if (isset($GLOBALS['sql_compatibility'])) {
            $compat = $GLOBALS['sql_compatibility'];
        } else {
            $compat = 'NONE';
        }

        $head = $this->exportComment()
            . $this->exportComment(
                __('Database:') . ' '
                . Util::backquoteCompat(
                    $dbAlias,
                    $compat,
                    isset($GLOBALS['sql_backquotes'])
                )
            )
            . $this->exportComment();

        return $this->export->outputHandler($head);
    }

    /**
     * Outputs database footer
     *
     * @param string $db Database name
     */
    public function exportDBFooter($db): bool
    {
        global $crlf;

        $result = true;

        //add indexes to the sql dump file
        if (isset($GLOBALS['sql_indexes'])) {
            $result = $this->export->outputHandler($GLOBALS['sql_indexes']);
            unset($GLOBALS['sql_indexes']);
        }

        //add auto increments to the sql dump file
        if (isset($GLOBALS['sql_auto_increments'])) {
            $result = $this->export->outputHandler($GLOBALS['sql_auto_increments']);
            unset($GLOBALS['sql_auto_increments']);
        }

        //add constraints to the sql dump file
        if (isset($GLOBALS['sql_constraints'])) {
            $result = $this->export->outputHandler($GLOBALS['sql_constraints']);
            unset($GLOBALS['sql_constraints']);
        }

        return $result;
    }

    /**
     * Exports events
     *
     * @param string $db Database
     */
    public function exportEvents($db): bool
    {
        global $crlf, $cfg, $dbi;

        $text = '';
        $delimiter = '$$';

        $eventNames = $dbi->fetchResult(
            'SELECT EVENT_NAME FROM information_schema.EVENTS WHERE'
            . " EVENT_SCHEMA= '" . $dbi->escapeString($db)
            . "';"
        );

        if ($eventNames) {
            $text .= $crlf
                . 'DELIMITER ' . $delimiter . $crlf;

            $text .= $this->exportComment()
                . $this->exportComment(__('Events'))
                . $this->exportComment();

            foreach ($eventNames as $eventName) {
                if (! empty($GLOBALS['sql_drop_table'])) {
                    $text .= 'DROP EVENT IF EXISTS '
                        . Util::backquote($eventName)
                        . $delimiter . $crlf;
                }

                $eventDef = $dbi->getDefinition($db, 'EVENT', $eventName);
                if (! empty($eventDef) && $cfg['Export']['remove_definer_from_definitions']) {
                    // remove definer clause from the event definition
                    $parser = new Parser($eventDef);
                    $statement = $parser->statements[0];
                    $statement->options->remove('DEFINER');
                    $eventDef = $statement->build();
                }

                $text .= $eventDef . $delimiter . $crlf . $crlf;
            }

            $text .= 'DELIMITER ;' . $crlf;
        }

        if (! empty($text)) {
            return $this->export->outputHandler($text);
        }

        return false;
    }

    /**
     * Exports metadata from Configuration Storage
     *
     * @param string       $db            database being exported
     * @param string|array $tables        table(s) being exported
     * @param array        $metadataTypes types of metadata to export
     */
    public function exportMetadata(
        $db,
        $tables,
        array $metadataTypes
    ): bool {
        $relationParameters = $this->relation->getRelationParameters();
        if ($relationParameters->db === null) {
            return true;
        }

        $comment = $this->possibleCRLF()
            . $this->possibleCRLF()
            . $this->exportComment()
            . $this->exportComment(__('Metadata'))
            . $this->exportComment();
        if (! $this->export->outputHandler($comment)) {
            return false;
        }

        if (! $this->exportUseStatement((string) $relationParameters->db, $GLOBALS['sql_compatibility'])) {
            return false;
        }

        $r = 1;
        if (is_array($tables)) {
            // export metadata for each table
            foreach ($tables as $table) {
                $r &= (int) $this->exportConfigurationMetadata($db, $table, $metadataTypes);
            }

            // export metadata for the database
            $r &= (int) $this->exportConfigurationMetadata($db, null, $metadataTypes);
        } else {
            // export metadata for single table
            $r &= (int) $this->exportConfigurationMetadata($db, $tables, $metadataTypes);
        }

        return (bool) $r;
    }

    /**
     * Exports metadata from Configuration Storage
     *
     * @param string      $db            database being exported
     * @param string|null $table         table being exported
     * @param array       $metadataTypes types of metadata to export
     */
    private function exportConfigurationMetadata(
        $db,
        $table,
        array $metadataTypes
    ): bool {
        global $dbi;

        $relationParameters = $this->relation->getRelationParameters();
        $relationParams = $relationParameters->toArray();

        if (isset($table)) {
            $types = [
                'column_info' => 'db_name',
                'table_uiprefs' => 'db_name',
                'tracking' => 'db_name',
            ];
        } else {
            $types = [
                'bookmark' => 'dbase',
                'relation' => 'master_db',
                'pdf_pages' => 'db_name',
                'savedsearches' => 'db_name',
                'central_columns' => 'db_name',
            ];
        }

        $aliases = [];

        $comment = $this->possibleCRLF()
            . $this->exportComment();

        if (isset($table)) {
            $comment .= $this->exportComment(
                sprintf(
                    __('Metadata for table %s'),
                    $table
                )
            );
        } else {
            $comment .= $this->exportComment(
                sprintf(
                    __('Metadata for database %s'),
                    $db
                )
            );
        }

        $comment .= $this->exportComment();

        if (! $this->export->outputHandler($comment)) {
            return false;
        }

        foreach ($types as $type => $dbNameColumn) {
            if (! in_array($type, $metadataTypes) || ! isset($relationParams[$type])) {
                continue;
            }

            // special case, designer pages and their coordinates
            if ($type === 'pdf_pages') {
                if ($relationParameters->pdfFeature === null) {
                    continue;
                }

                $sqlQuery = 'SELECT `page_nr`, `page_descr` FROM '
                    . Util::backquote($relationParameters->pdfFeature->database)
                    . '.' . Util::backquote($relationParameters->pdfFeature->pdfPages)
                    . ' WHERE `db_name` = \'' . $dbi->escapeString($db) . "'";

                $result = $dbi->fetchResult($sqlQuery, 'page_nr', 'page_descr');

                foreach (array_keys($result) as $page) {
                    // insert row for pdf_page
                    $sqlQueryRow = 'SELECT `db_name`, `page_descr` FROM '
                        . Util::backquote($relationParameters->pdfFeature->database)
                        . '.' . Util::backquote($relationParameters->pdfFeature->pdfPages)
                        . ' WHERE `db_name` = \'' . $dbi->escapeString($db) . "'"
                        . " AND `page_nr` = '" . intval($page) . "'";

                    if (
                        ! $this->exportData(
                            $relationParameters->pdfFeature->database->getName(),
                            $relationParameters->pdfFeature->pdfPages->getName(),
                            $GLOBALS['crlf'],
                            '',
                            $sqlQueryRow,
                            $aliases
                        )
                    ) {
                        return false;
                    }

                    $lastPage = $GLOBALS['crlf']
                        . 'SET @LAST_PAGE = LAST_INSERT_ID();'
                        . $GLOBALS['crlf'];
                    if (! $this->export->outputHandler($lastPage)) {
                        return false;
                    }

                    $sqlQueryCoords = 'SELECT `db_name`, `table_name`, '
                        . "'@LAST_PAGE' AS `pdf_page_number`, `x`, `y` FROM "
                        . Util::backquote($relationParameters->pdfFeature->database)
                        . '.' . Util::backquote($relationParameters->pdfFeature->tableCoords)
                        . " WHERE `pdf_page_number` = '" . $page . "'";

                    $GLOBALS['exporting_metadata'] = true;
                    if (
                        ! $this->exportData(
                            $relationParameters->pdfFeature->database->getName(),
                            $relationParameters->pdfFeature->tableCoords->getName(),
                            $GLOBALS['crlf'],
                            '',
                            $sqlQueryCoords,
                            $aliases
                        )
                    ) {
                        $GLOBALS['exporting_metadata'] = false;

                        return false;
                    }

                    $GLOBALS['exporting_metadata'] = false;
                }

                continue;
            }

            // remove auto_incrementing id field for some tables
            if ($type === 'bookmark') {
                $sqlQuery = 'SELECT `dbase`, `user`, `label`, `query` FROM ';
            } elseif ($type === 'column_info') {
                $sqlQuery = 'SELECT `db_name`, `table_name`, `column_name`,'
                    . ' `comment`, `mimetype`, `transformation`,'
                    . ' `transformation_options`, `input_transformation`,'
                    . ' `input_transformation_options` FROM';
            } elseif ($type === 'savedsearches') {
                $sqlQuery = 'SELECT `username`, `db_name`, `search_name`, `search_data` FROM';
            } else {
                $sqlQuery = 'SELECT * FROM ';
            }

            $sqlQuery .= Util::backquote($relationParameters->db)
                . '.' . Util::backquote((string) $relationParams[$type])
                . ' WHERE ' . Util::backquote($dbNameColumn)
                . " = '" . $dbi->escapeString($db) . "'";
            if (isset($table)) {
                $sqlQuery .= " AND `table_name` = '"
                    . $dbi->escapeString($table) . "'";
            }

            if (
                ! $this->exportData(
                    (string) $relationParameters->db,
                    (string) $relationParams[$type],
                    $GLOBALS['crlf'],
                    '',
                    $sqlQuery,
                    $aliases
                )
            ) {
                return false;
            }
        }

        return true;
    }

    /**
     * Returns a stand-in CREATE definition to resolve view dependencies
     *
     * @param string $db      the database name
     * @param string $view    the view name
     * @param string $crlf    the end of line sequence
     * @param array  $aliases Aliases of db/table/columns
     *
     * @return string resulting definition
     */
    public function getTableDefStandIn($db, $view, $crlf, $aliases = [])
    {
        global $dbi;

        $dbAlias = $db;
        $viewAlias = $view;
        $this->initAlias($aliases, $dbAlias, $viewAlias);
        $createQuery = '';
        if (! empty($GLOBALS['sql_drop_table'])) {
            $createQuery .= 'DROP VIEW IF EXISTS '
                . Util::backquote($viewAlias)
                . ';' . $crlf;
        }

        $createQuery .= 'CREATE TABLE ';

        if (isset($GLOBALS['sql_if_not_exists']) && $GLOBALS['sql_if_not_exists']) {
            $createQuery .= 'IF NOT EXISTS ';
        }

        $createQuery .= Util::backquote($viewAlias) . ' (' . $crlf;
        $tmp = [];
        $columns = $dbi->getColumnsFull($db, $view);
        foreach ($columns as $columnName => $definition) {
            $colAlias = $columnName;
            if (! empty($aliases[$db]['tables'][$view]['columns'][$colAlias])) {
                $colAlias = $aliases[$db]['tables'][$view]['columns'][$colAlias];
            }

            $tmp[] = Util::backquote($colAlias) . ' ' .
                $definition['Type'] . $crlf;
        }

        return $createQuery . implode(',', $tmp) . ');' . $crlf;
    }

    /**
     * Returns CREATE definition that matches $view's structure
     *
     * @param string $db           the database name
     * @param string $view         the view name
     * @param string $crlf         the end of line sequence
     * @param bool   $addSemicolon whether to add semicolon and end-of-line at
     *                              the end
     * @param array  $aliases      Aliases of db/table/columns
     *
     * @return string resulting schema
     */
    private function getTableDefForView(
        $db,
        $view,
        $crlf,
        $addSemicolon = true,
        array $aliases = []
    ) {
        global $dbi;

        $dbAlias = $db;
        $viewAlias = $view;
        $this->initAlias($aliases, $dbAlias, $viewAlias);
        $createQuery = 'CREATE TABLE';
        if (isset($GLOBALS['sql_if_not_exists'])) {
            $createQuery .= ' IF NOT EXISTS ';
        }

        $createQuery .= Util::backquote($viewAlias) . '(' . $crlf;

        $columns = $dbi->getColumns($db, $view, true);

        $firstCol = true;
        foreach ($columns as $column) {
            $colAlias = $column['Field'];
            if (! empty($aliases[$db]['tables'][$view]['columns'][$colAlias])) {
                $colAlias = $aliases[$db]['tables'][$view]['columns'][$colAlias];
            }

            $extractedColumnspec = Util::extractColumnSpec($column['Type']);

            if (! $firstCol) {
                $createQuery .= ',' . $crlf;
            }

            $createQuery .= '    ' . Util::backquote($colAlias);
            $createQuery .= ' ' . $column['Type'];
            if ($extractedColumnspec['can_contain_collation'] && ! empty($column['Collation'])) {
                $createQuery .= ' COLLATE ' . $column['Collation'];
            }

            if ($column['Null'] === 'NO') {
                $createQuery .= ' NOT NULL';
            }

            if (isset($column['Default'])) {
                $createQuery .= " DEFAULT '"
                    . $dbi->escapeString($column['Default']) . "'";
            } else {
                if ($column['Null'] === 'YES') {
                    $createQuery .= ' DEFAULT NULL';
                }
            }

            if (! empty($column['Comment'])) {
                $createQuery .= " COMMENT '"
                    . $dbi->escapeString($column['Comment']) . "'";
            }

            $firstCol = false;
        }

        $createQuery .= $crlf . ')' . ($addSemicolon ? ';' : '') . $crlf;

        if (isset($GLOBALS['sql_compatibility'])) {
            $compat = $GLOBALS['sql_compatibility'];
        } else {
            $compat = 'NONE';
        }

        if ($compat === 'MSSQL') {
            $createQuery = $this->makeCreateTableMSSQLCompatible($createQuery);
        }

        return $createQuery;
    }

    /**
     * Returns $table's CREATE definition
     *
     * @param string $db                      the database name
     * @param string $table                   the table name
     * @param string $crlf                    the end of line sequence
     * @param string $errorUrl                the url to go back in case
     *                                         of error
     * @param bool   $showDates               whether to include creation/
     *                                         update/check dates
     * @param bool   $addSemicolon            whether to add semicolon and
     *                                         end-of-line at the end
     * @param bool   $view                    whether we're handling a view
     * @param bool   $updateIndexesIncrements whether we need to update
     *                                          two global variables
     * @param array  $aliases                 Aliases of db/table/columns
     *
     * @return string resulting schema
     */
    public function getTableDef(
        $db,
        $table,
        $crlf,
        $errorUrl,
        $showDates = false,
        $addSemicolon = true,
        $view = false,
        $updateIndexesIncrements = true,
        array $aliases = []
    ) {
        global $sql_drop_table, $sql_backquotes, $sql_constraints,
               $sql_constraints_query, $sql_indexes, $sql_indexes_query,
               $sql_auto_increments, $sql_drop_foreign_keys, $dbi, $cfg;

        $dbAlias = $db;
        $tableAlias = $table;
        $this->initAlias($aliases, $dbAlias, $tableAlias);

        $schemaCreate = '';
        $newCrlf = $crlf;

        if (isset($GLOBALS['sql_compatibility'])) {
            $compat = $GLOBALS['sql_compatibility'];
        } else {
            $compat = 'NONE';
        }

        $result = $dbi->tryQuery(
            'SHOW TABLE STATUS FROM ' . Util::backquote($db)
            . ' WHERE Name = \'' . $dbi->escapeString((string) $table) . '\''
        );
        if ($result != false) {
            if ($result->numRows() > 0) {
                $tmpres = $result->fetchAssoc();

                if ($showDates && isset($tmpres['Create_time']) && ! empty($tmpres['Create_time'])) {
                    $schemaCreate .= $this->exportComment(
                        __('Creation:') . ' '
                        . Util::localisedDate(
                            strtotime($tmpres['Create_time'])
                        )
                    );
                    $newCrlf = $this->exportComment() . $crlf;
                }

                if ($showDates && isset($tmpres['Update_time']) && ! empty($tmpres['Update_time'])) {
                    $schemaCreate .= $this->exportComment(
                        __('Last update:') . ' '
                        . Util::localisedDate(
                            strtotime($tmpres['Update_time'])
                        )
                    );
                    $newCrlf = $this->exportComment() . $crlf;
                }

                if ($showDates && isset($tmpres['Check_time']) && ! empty($tmpres['Check_time'])) {
                    $schemaCreate .= $this->exportComment(
                        __('Last check:') . ' '
                        . Util::localisedDate(
                            strtotime($tmpres['Check_time'])
                        )
                    );
                    $newCrlf = $this->exportComment() . $crlf;
                }
            }
        }

        $schemaCreate .= $newCrlf;

        if (! empty($sql_drop_table) && $dbi->getTable($db, $table)->isView()) {
            $schemaCreate .= 'DROP VIEW IF EXISTS '
                . Util::backquoteCompat($tableAlias, 'NONE', $sql_backquotes) . ';'
                . $crlf;
        }

        // no need to generate a DROP VIEW here, it was done earlier
        if (! empty($sql_drop_table) && ! $dbi->getTable($db, $table)->isView()) {
            $schemaCreate .= 'DROP TABLE IF EXISTS '
                . Util::backquoteCompat($tableAlias, 'NONE', $sql_backquotes) . ';'
                . $crlf;
        }

        // Complete table dump,
        // Whether to quote table and column names or not
        if ($sql_backquotes) {
            $dbi->query('SET SQL_QUOTE_SHOW_CREATE = 1');
        } else {
            $dbi->query('SET SQL_QUOTE_SHOW_CREATE = 0');
        }

        // I don't see the reason why this unbuffered query could cause problems,
        // because SHOW CREATE TABLE returns only one row, and we free the
        // results below. Nonetheless, we got 2 user reports about this
        // (see bug 1562533) so I removed the unbuffered mode.
        // $result = $dbi->query('SHOW CREATE TABLE ' . backquote($db)
        // . '.' . backquote($table), null, DatabaseInterface::QUERY_UNBUFFERED);
        //
        // Note: SHOW CREATE TABLE, at least in MySQL 5.1.23, does not
        // produce a displayable result for the default value of a BIT
        // column, nor does the mysqldump command. See MySQL bug 35796
        $dbi->tryQuery('USE ' . Util::backquote($db));
        $result = $dbi->tryQuery(
            'SHOW CREATE TABLE ' . Util::backquote($db) . '.'
            . Util::backquote($table)
        );
        // an error can happen, for example the table is crashed
        $tmpError = $dbi->getError();
        if ($tmpError) {
            $message = sprintf(__('Error reading structure for table %s:'), $db . '.' . $table);
            $message .= ' ' . $tmpError;
            if (! defined('TESTSUITE')) {
                trigger_error($message, E_USER_ERROR);
            }

            return $this->exportComment($message);
        }

        // Old mode is stored so it can be restored once exporting is done.
        $oldMode = Context::$MODE;

        $warning = '';

        $row = null;
        if ($result !== false) {
            $row = $result->fetchRow();
        }

        if ($row) {
            $createQuery = $row[1];
            unset($row);

            // Convert end of line chars to one that we want (note that MySQL
            // doesn't return query it will accept in all cases)
            if (mb_strpos($createQuery, "(\r\n ")) {
                $createQuery = str_replace("\r\n", $crlf, $createQuery);
            } elseif (mb_strpos($createQuery, "(\n ")) {
                $createQuery = str_replace("\n", $crlf, $createQuery);
            } elseif (mb_strpos($createQuery, "(\r ")) {
                $createQuery = str_replace("\r", $crlf, $createQuery);
            }

            /*
             * Drop database name from VIEW creation.
             *
             * This is a bit tricky, but we need to issue SHOW CREATE TABLE with
             * database name, but we don't want name to show up in CREATE VIEW
             * statement.
             */
            if ($view) {
                //TODO: use parser
                $createQuery = preg_replace(
                    '/' . preg_quote(Util::backquote($db), '/') . '\./',
                    '',
                    $createQuery
                );
                $parser = new Parser($createQuery);
                /**
                 * `CREATE TABLE` statement.
                 *
                 * @var CreateStatement
                 */
                $statement = $parser->statements[0];

                // exclude definition of current user
                if ($cfg['Export']['remove_definer_from_definitions'] || isset($GLOBALS['sql_view_current_user'])) {
                    $statement->options->remove('DEFINER');
                }

                if (isset($GLOBALS['sql_simple_view_export'])) {
                    $statement->options->remove('SQL SECURITY');
                    $statement->options->remove('INVOKER');
                    $statement->options->remove('ALGORITHM');
                    $statement->options->remove('DEFINER');
                }

                $createQuery = $statement->build();

                // whether to replace existing view or not
                if (isset($GLOBALS['sql_or_replace_view'])) {
                    $createQuery = preg_replace('/^CREATE/', 'CREATE OR REPLACE', $createQuery);
                }
            }

            // Substitute aliases in `CREATE` query.
            $createQuery = $this->replaceWithAliases($createQuery, $aliases, $db, $table, $flag);

            // One warning per view.
            if ($flag && $view) {
                $warning = $this->exportComment()
                    . $this->exportComment(
                        __('It appears your database uses views;')
                    )
                    . $this->exportComment(
                        __('alias export may not work reliably in all cases.')
                    )
                    . $this->exportComment();
            }

            // Adding IF NOT EXISTS, if required.
            if (isset($GLOBALS['sql_if_not_exists'])) {
                $createQuery = (string) preg_replace('/^CREATE TABLE/', 'CREATE TABLE IF NOT EXISTS', $createQuery);
            }

            // Making the query MSSQL compatible.
            if ($compat === 'MSSQL') {
                $createQuery = $this->makeCreateTableMSSQLCompatible($createQuery);
            }

            // Views have no constraints, indexes, etc. They do not require any
            // analysis.
            if (! $view) {
                if (empty($sql_backquotes)) {
                    // Option "Enclose table and column names with backquotes"
                    // was checked.
                    Context::$MODE |= Context::SQL_MODE_NO_ENCLOSING_QUOTES;
                }

                // Using appropriate quotes.
                if (($compat === 'MSSQL') || ($sql_backquotes === '"')) {
                    Context::$MODE |= Context::SQL_MODE_ANSI_QUOTES;
                }
            }

            /**
             * Parser used for analysis.
             */
            $parser = new Parser($createQuery);

            /**
             * `CREATE TABLE` statement.
             *
             * @var CreateStatement
             */
            $statement = $parser->statements[0];

            if (! empty($statement->entityOptions)) {
                $engine = $statement->entityOptions->has('ENGINE');
            } else {
                $engine = '';
            }

            /* Avoid operation on ARCHIVE tables as those can not be altered */
            if (
                (! empty($statement->fields) && is_array($statement->fields))
                && (empty($engine) || strtoupper($engine) !== 'ARCHIVE')
            ) {

                /**
                 * Fragments containing definition of each constraint.
                 *
                 * @var array
                 */
                $constraints = [];

                /**
                 * Fragments containing definition of each index.
                 *
                 * @var array
                 */
                $indexes = [];

                /**
                 * Fragments containing definition of each FULLTEXT index.
                 *
                 * @var array
                 */
                $indexesFulltext = [];

                /**
                 * Fragments containing definition of each foreign key that will
                 * be dropped.
                 *
                 * @var array
                 */
                $dropped = [];

                /**
                 * Fragment containing definition of the `AUTO_INCREMENT`.
                 *
                 * @var array
                 */
                $autoIncrement = [];

                // Scanning each field of the `CREATE` statement to fill the arrays
                // above.
                // If the field is used in any of the arrays above, it is removed
                // from the original definition.
                // Also, AUTO_INCREMENT attribute is removed.
                /** @var CreateDefinition $field */
                foreach ($statement->fields as $key => $field) {
                    if ($field->isConstraint) {
                        // Creating the parts that add constraints.
                        $constraints[] = $field::build($field);
                        unset($statement->fields[$key]);
                    } elseif (! empty($field->key)) {
                        // Creating the parts that add indexes (must not be
                        // constraints).
                        if ($field->key->type === 'FULLTEXT KEY') {
                            $indexesFulltext[] = $field::build($field);
                            unset($statement->fields[$key]);
                        } else {
                            if (empty($GLOBALS['sql_if_not_exists'])) {
                                $indexes[] = str_replace(
                                    'COMMENT=\'',
                                    'COMMENT \'',
                                    $field::build($field)
                                );
                                unset($statement->fields[$key]);
                            }
                        }
                    }

                    // Creating the parts that drop foreign keys.
                    if (! empty($field->key)) {
                        if ($field->key->type === 'FOREIGN KEY') {
                            $dropped[] = 'FOREIGN KEY ' . Context::escape($field->name);
                            unset($statement->fields[$key]);
                        }
                    }

                    // Dropping AUTO_INCREMENT.
                    if (empty($field->options)) {
                        continue;
                    }

                    if (! $field->options->has('AUTO_INCREMENT') || ! empty($GLOBALS['sql_if_not_exists'])) {
                        continue;
                    }

                    $autoIncrement[] = $field::build($field);
                    $field->options->remove('AUTO_INCREMENT');
                }

                /**
                 * The header of the `ALTER` statement (`ALTER TABLE tbl`).
                 *
                 * @var string
                 */
                $alterHeader = 'ALTER TABLE ' .
                    Util::backquoteCompat($tableAlias, $compat, $sql_backquotes);

                /**
                 * The footer of the `ALTER` statement (usually ';')
                 *
                 * @var string
                 */
                $alterFooter = ';' . $crlf;

                // Generating constraints-related query.
                if (! empty($constraints)) {
                    $sql_constraints_query = $alterHeader . $crlf . '  ADD '
                        . implode(',' . $crlf . '  ADD ', $constraints)
                        . $alterFooter;

                    $sql_constraints = $this->generateComment(
                        $crlf,
                        $sql_constraints,
                        __('Constraints for dumped tables'),
                        __('Constraints for table'),
                        $tableAlias,
                        $compat
                    ) . $sql_constraints_query;
                }

                // Generating indexes-related query.
                $sql_indexes_query = '';

                if (! empty($indexes)) {
                    $sql_indexes_query .= $alterHeader . $crlf . '  ADD '
                        . implode(',' . $crlf . '  ADD ', $indexes)
                        . $alterFooter;
                }

                if (! empty($indexesFulltext)) {
                    // InnoDB supports one FULLTEXT index creation at a time.
                    // So FULLTEXT indexes are created one-by-one after other
                    // indexes where created.
                    $sql_indexes_query .= $alterHeader .
                        ' ADD ' . implode($alterFooter . $alterHeader . ' ADD ', $indexesFulltext) . $alterFooter;
                }

                if (! empty($indexes) || ! empty($indexesFulltext)) {
                    $sql_indexes = $this->generateComment(
                        $crlf,
                        $sql_indexes,
                        __('Indexes for dumped tables'),
                        __('Indexes for table'),
                        $tableAlias,
                        $compat
                    ) . $sql_indexes_query;
                }

                // Generating drop foreign keys-related query.
                if (! empty($dropped)) {
                    $sql_drop_foreign_keys = $alterHeader . $crlf . '  DROP '
                        . implode(',' . $crlf . '  DROP ', $dropped)
                        . $alterFooter;
                }

                // Generating auto-increment-related query.
                if ($autoIncrement !== [] && $updateIndexesIncrements) {
                    $sqlAutoIncrementsQuery = $alterHeader . $crlf . '  MODIFY '
                        . implode(',' . $crlf . '  MODIFY ', $autoIncrement);
                    if (
                        isset($GLOBALS['sql_auto_increment'])
                        && ($statement->entityOptions->has('AUTO_INCREMENT') !== false)
                    ) {
                        if (
                            ! isset($GLOBALS['table_data'])
                            || (isset($GLOBALS['table_data'])
                            && in_array($table, $GLOBALS['table_data']))
                        ) {
                            $sqlAutoIncrementsQuery .= ', AUTO_INCREMENT='
                                . $statement->entityOptions->has('AUTO_INCREMENT');
                        }
                    }

                    $sqlAutoIncrementsQuery .= ';' . $crlf;

                    $sql_auto_increments = $this->generateComment(
                        $crlf,
                        $sql_auto_increments,
                        __('AUTO_INCREMENT for dumped tables'),
                        __('AUTO_INCREMENT for table'),
                        $tableAlias,
                        $compat
                    ) . $sqlAutoIncrementsQuery;
                }

                // Removing the `AUTO_INCREMENT` attribute from the `CREATE TABLE`
                // too.
                if (
                    ! empty($statement->entityOptions)
                    && (empty($GLOBALS['sql_if_not_exists'])
                    || empty($GLOBALS['sql_auto_increment']))
                ) {
                    $statement->entityOptions->remove('AUTO_INCREMENT');
                }

                // Rebuilding the query.
                $createQuery = $statement->build();
            }

            $schemaCreate .= $createQuery;
        }

        // Restoring old mode.
        Context::$MODE = $oldMode;

        return $warning . $schemaCreate . ($addSemicolon ? ';' . $crlf : '');
    }

    /**
     * Returns $table's comments, relations etc.
     *
     * @param string $db         database name
     * @param string $table      table name
     * @param bool   $doRelation whether to include relation comments
     * @param bool   $doMime     whether to include mime comments
     * @param array  $aliases    Aliases of db/table/columns
     *
     * @return string resulting comments
     */
    private function getTableComments(
        $db,
        $table,
        $doRelation = false,
        $doMime = false,
        array $aliases = []
    ) {
        global $sql_backquotes;

        $dbAlias = $db;
        $tableAlias = $table;
        $this->initAlias($aliases, $dbAlias, $tableAlias);

        $relationParameters = $this->relation->getRelationParameters();

        $schemaCreate = '';

        // Check if we can use Relations
        [$resRel, $haveRel] = $this->relation->getRelationsAndStatus(
            $doRelation && $relationParameters->relationFeature !== null,
            $db,
            $table
        );

        if ($doMime && $relationParameters->browserTransformationFeature !== null) {
            $mimeMap = $this->transformations->getMime($db, $table, true);
            if ($mimeMap === null) {
                unset($mimeMap);
            }
        }

        if (isset($mimeMap) && count($mimeMap) > 0) {
            $schemaCreate .= $this->possibleCRLF()
                . $this->exportComment()
                . $this->exportComment(
                    __('MEDIA TYPES FOR TABLE') . ' '
                    . Util::backquoteCompat($table, 'NONE', $sql_backquotes) . ':'
                );
            foreach ($mimeMap as $mimeField => $mime) {
                $schemaCreate .= $this->exportComment(
                    '  '
                    . Util::backquoteCompat($mimeField, 'NONE', $sql_backquotes)
                )
                . $this->exportComment(
                    '      '
                    . Util::backquoteCompat(
                        $mime['mimetype'],
                        'NONE',
                        $sql_backquotes
                    )
                );
            }

            $schemaCreate .= $this->exportComment();
        }

        if ($haveRel) {
            $schemaCreate .= $this->possibleCRLF()
                . $this->exportComment()
                . $this->exportComment(
                    __('RELATIONSHIPS FOR TABLE') . ' '
                    . Util::backquoteCompat($tableAlias, 'NONE', $sql_backquotes)
                    . ':'
                );

            foreach ($resRel as $relField => $rel) {
                if ($relField !== 'foreign_keys_data') {
                    $relFieldAlias = ! empty(
                        $aliases[$db]['tables'][$table]['columns'][$relField]
                    ) ? $aliases[$db]['tables'][$table]['columns'][$relField]
                        : $relField;
                    $schemaCreate .= $this->exportComment(
                        '  '
                        . Util::backquoteCompat(
                            $relFieldAlias,
                            'NONE',
                            $sql_backquotes
                        )
                    )
                    . $this->exportComment(
                        '      '
                        . Util::backquoteCompat(
                            $rel['foreign_table'],
                            'NONE',
                            $sql_backquotes
                        )
                        . ' -> '
                        . Util::backquoteCompat(
                            $rel['foreign_field'],
                            'NONE',
                            $sql_backquotes
                        )
                    );
                } else {
                    foreach ($rel as $oneKey) {
                        foreach ($oneKey['index_list'] as $index => $field) {
                            $relFieldAlias = ! empty(
                                $aliases[$db]['tables'][$table]['columns'][$field]
                            ) ? $aliases[$db]['tables'][$table]['columns'][$field]
                                : $field;
                            $schemaCreate .= $this->exportComment(
                                '  '
                                . Util::backquoteCompat(
                                    $relFieldAlias,
                                    'NONE',
                                    $sql_backquotes
                                )
                            )
                            . $this->exportComment(
                                '      '
                                . Util::backquoteCompat(
                                    $oneKey['ref_table_name'],
                                    'NONE',
                                    $sql_backquotes
                                )
                                . ' -> '
                                . Util::backquoteCompat(
                                    $oneKey['ref_index_list'][$index],
                                    'NONE',
                                    $sql_backquotes
                                )
                            );
                        }
                    }
                }
            }

            $schemaCreate .= $this->exportComment();
        }

        return $schemaCreate;
    }

    /**
     * Outputs a raw query
     *
     * @param string      $errorUrl the url to go back in case of error
     * @param string|null $db       the database where the query is executed
     * @param string      $sqlQuery the rawquery to output
     * @param string      $crlf     the end of line sequence
     */
    public function exportRawQuery(string $errorUrl, ?string $db, string $sqlQuery, string $crlf): bool
    {
        global $dbi;

        if ($db !== null) {
            $dbi->selectDb($db);
        }

        return $this->exportData($db ?? '', '', $crlf, $errorUrl, $sqlQuery);
    }

    /**
     * Outputs table's structure
     *
     * @param string $db         database name
     * @param string $table      table name
     * @param string $crlf       the end of line sequence
     * @param string $errorUrl   the url to go back in case of error
     * @param string $exportMode 'create_table','triggers','create_view',
     *                            'stand_in'
     * @param string $exportType 'server', 'database', 'table'
     * @param bool   $relation   whether to include relation comments
     * @param bool   $comments   whether to include the pmadb-style column
     *                           comments as comments in the structure; this is
     *                           deprecated but the parameter is left here
     *                           because /export calls exportStructure()
     *                           also for other export types which use this
     *                           parameter
     * @param bool   $mime       whether to include mime comments
     * @param bool   $dates      whether to include creation/update/check dates
     * @param array  $aliases    Aliases of db/table/columns
     */
    public function exportStructure(
        $db,
        $table,
        $crlf,
        $errorUrl,
        $exportMode,
        $exportType,
        $relation = false,
        $comments = false,
        $mime = false,
        $dates = false,
        array $aliases = []
    ): bool {
        global $dbi;

        $dbAlias = $db;
        $tableAlias = $table;
        $this->initAlias($aliases, $dbAlias, $tableAlias);
        if (isset($GLOBALS['sql_compatibility'])) {
            $compat = $GLOBALS['sql_compatibility'];
        } else {
            $compat = 'NONE';
        }

        $formattedTableName = Util::backquoteCompat($tableAlias, $compat, isset($GLOBALS['sql_backquotes']));
        $dump = $this->possibleCRLF()
            . $this->exportComment(str_repeat('-', 56))
            . $this->possibleCRLF()
            . $this->exportComment();

        switch ($exportMode) {
            case 'create_table':
                $dump .= $this->exportComment(
                    __('Table structure for table') . ' ' . $formattedTableName
                );
                $dump .= $this->exportComment();
                $dump .= $this->getTableDef($db, $table, $crlf, $errorUrl, $dates, true, false, true, $aliases);
                $dump .= $this->getTableComments($db, $table, $relation, $mime, $aliases);
                break;
            case 'triggers':
                $dump = '';
                $delimiter = '$$';
                $triggers = $dbi->getTriggers($db, $table, $delimiter);
                if ($triggers) {
                    $dump .= $this->possibleCRLF()
                    . $this->exportComment()
                    . $this->exportComment(
                        __('Triggers') . ' ' . $formattedTableName
                    )
                        . $this->exportComment();
                    $usedAlias = false;
                    $triggerQuery = '';
                    foreach ($triggers as $trigger) {
                        if (! empty($GLOBALS['sql_drop_table'])) {
                            $triggerQuery .= $trigger['drop'] . ';' . $crlf;
                        }

                        $triggerQuery .= 'DELIMITER ' . $delimiter . $crlf;
                        $triggerQuery .= $this->replaceWithAliases($trigger['create'], $aliases, $db, $table, $flag);
                        if ($flag) {
                            $usedAlias = true;
                        }

                        $triggerQuery .= 'DELIMITER ;' . $crlf;
                    }

                    // One warning per table.
                    if ($usedAlias) {
                        $dump .= $this->exportComment(
                            __('It appears your table uses triggers;')
                        )
                        . $this->exportComment(
                            __('alias export may not work reliably in all cases.')
                        )
                        . $this->exportComment();
                    }

                    $dump .= $triggerQuery;
                }

                break;
            case 'create_view':
                if (empty($GLOBALS['sql_views_as_tables'])) {
                    $dump .= $this->exportComment(
                        __('Structure for view')
                        . ' '
                        . $formattedTableName
                    )
                    . $this->exportComment();
                    // delete the stand-in table previously created (if any)
                    if ($exportType !== 'table') {
                        $dump .= 'DROP TABLE IF EXISTS '
                            . Util::backquote($tableAlias) . ';' . $crlf;
                    }

                    $dump .= $this->getTableDef($db, $table, $crlf, $errorUrl, $dates, true, true, true, $aliases);
                } else {
                    $dump .= $this->exportComment(
                        sprintf(
                            __('Structure for view %s exported as a table'),
                            $formattedTableName
                        )
                    )
                    . $this->exportComment();
                    // delete the stand-in table previously created (if any)
                    if ($exportType !== 'table') {
                        $dump .= 'DROP TABLE IF EXISTS '
                        . Util::backquote($tableAlias) . ';' . $crlf;
                    }

                    $dump .= $this->getTableDefForView($db, $table, $crlf, true, $aliases);
                }

                break;
            case 'stand_in':
                $dump .= $this->exportComment(
                    __('Stand-in structure for view') . ' ' . $formattedTableName
                )
                    . $this->exportComment(
                        __('(See below for the actual view)')
                    )
                    . $this->exportComment();
                // export a stand-in definition to resolve view dependencies
                $dump .= $this->getTableDefStandIn($db, $table, $crlf, $aliases);
        }

        // this one is built by getTableDef() to use in table copy/move
        // but not in the case of export
        unset($GLOBALS['sql_constraints_query']);

        return $this->export->outputHandler($dump);
    }

    /**
     * Outputs the content of a table in SQL format
     *
     * @param string $db       database name
     * @param string $table    table name
     * @param string $crlf     the end of line sequence
     * @param string $errorUrl the url to go back in case of error
     * @param string $sqlQuery SQL query for obtaining data
     * @param array  $aliases  Aliases of db/table/columns
     */
    public function exportData(
        $db,
        $table,
        $crlf,
        $errorUrl,
        $sqlQuery,
        array $aliases = []
    ): bool {
        global $current_row, $sql_backquotes, $dbi;

        // Do not export data for merge tables
        if ($dbi->getTable($db, $table)->isMerge()) {
            return true;
        }

        $dbAlias = $db;
        $tableAlias = $table;
        $this->initAlias($aliases, $dbAlias, $tableAlias);

        if (isset($GLOBALS['sql_compatibility'])) {
            $compat = $GLOBALS['sql_compatibility'];
        } else {
            $compat = 'NONE';
        }

        $formattedTableName = Util::backquoteCompat($tableAlias, $compat, $sql_backquotes);

        // Do not export data for a VIEW, unless asked to export the view as a table
        // (For a VIEW, this is called only when exporting a single VIEW)
        if ($dbi->getTable($db, $table)->isView() && empty($GLOBALS['sql_views_as_tables'])) {
            $head = $this->possibleCRLF()
                . $this->exportComment()
                . $this->exportComment('VIEW ' . $formattedTableName)
                . $this->exportComment(__('Data:') . ' ' . __('None'))
                . $this->exportComment()
                . $this->possibleCRLF();

            return $this->export->outputHandler($head);
        }

        $result = $dbi->tryQuery($sqlQuery, DatabaseInterface::CONNECT_USER, DatabaseInterface::QUERY_UNBUFFERED);
        // a possible error: the table has crashed
        $tmpError = $dbi->getError();
        if ($tmpError) {
            $message = sprintf(__('Error reading data for table %s:'), $db . '.' . $table);
            $message .= ' ' . $tmpError;
            if (! defined('TESTSUITE')) {
                trigger_error($message, E_USER_ERROR);
            }

            return $this->export->outputHandler(
                $this->exportComment($message)
            );
        }

        if ($result === false) {
            return true;
        }

        $fieldsCnt = $result->numFields();

        // Get field information
        /** @var FieldMetadata[] $fieldsMeta */
        $fieldsMeta = $dbi->getFieldsMeta($result);

        $fieldSet = [];
        for ($j = 0; $j < $fieldsCnt; $j++) {
            $colAs = $fieldsMeta[$j]->name;
            if (! empty($aliases[$db]['tables'][$table]['columns'][$colAs])) {
                $colAs = $aliases[$db]['tables'][$table]['columns'][$colAs];
            }

            $fieldSet[$j] = Util::backquoteCompat($colAs, $compat, $sql_backquotes);
        }

        if (isset($GLOBALS['sql_type']) && $GLOBALS['sql_type'] === 'UPDATE') {
            // update
            $schemaInsert = 'UPDATE ';
            if (isset($GLOBALS['sql_ignore'])) {
                $schemaInsert .= 'IGNORE ';
            }

            // avoid EOL blank
            $schemaInsert .= Util::backquoteCompat($tableAlias, $compat, $sql_backquotes) . ' SET';
        } else {
            // insert or replace
            if (isset($GLOBALS['sql_type']) && $GLOBALS['sql_type'] === 'REPLACE') {
                $sqlCommand = 'REPLACE';
            } else {
                $sqlCommand = 'INSERT';
            }

            // delayed inserts?
            if (isset($GLOBALS['sql_delayed'])) {
                $insertDelayed = ' DELAYED';
            } else {
                $insertDelayed = '';
            }

            // insert ignore?
            if (isset($GLOBALS['sql_type'], $GLOBALS['sql_ignore']) && $GLOBALS['sql_type'] === 'INSERT') {
                $insertDelayed .= ' IGNORE';
            }

            //truncate table before insert
            if (isset($GLOBALS['sql_truncate']) && $GLOBALS['sql_truncate'] && $sqlCommand === 'INSERT') {
                $truncate = 'TRUNCATE TABLE '
                    . Util::backquoteCompat($tableAlias, $compat, $sql_backquotes) . ';';
                $truncatehead = $this->possibleCRLF()
                    . $this->exportComment()
                    . $this->exportComment(
                        __('Truncate table before insert') . ' '
                        . $formattedTableName
                    )
                    . $this->exportComment()
                    . $crlf;
                $this->export->outputHandler($truncatehead);
                $this->export->outputHandler($truncate);
            }

            // scheme for inserting fields
            if ($GLOBALS['sql_insert_syntax'] === 'complete' || $GLOBALS['sql_insert_syntax'] === 'both') {
                $fields = implode(', ', $fieldSet);
                $schemaInsert = $sqlCommand . $insertDelayed . ' INTO '
                    . Util::backquoteCompat($tableAlias, $compat, $sql_backquotes)
                    // avoid EOL blank
                    . ' (' . $fields . ') VALUES';
            } else {
                $schemaInsert = $sqlCommand . $insertDelayed . ' INTO '
                    . Util::backquoteCompat($tableAlias, $compat, $sql_backquotes)
                    . ' VALUES';
            }
        }

        //\x08\\x09, not required
        $current_row = 0;
        $querySize = 0;
        if (
            ($GLOBALS['sql_insert_syntax'] === 'extended'
            || $GLOBALS['sql_insert_syntax'] === 'both')
            && (! isset($GLOBALS['sql_type'])
            || $GLOBALS['sql_type'] !== 'UPDATE')
        ) {
            $separator = ',';
            $schemaInsert .= $crlf;
        } else {
            $separator = ';';
        }

        while ($row = $result->fetchRow()) {
            if ($current_row == 0) {
                $head = $this->possibleCRLF()
                    . $this->exportComment()
                    . $this->exportComment(
                        __('Dumping data for table') . ' '
                        . $formattedTableName
                    )
                    . $this->exportComment()
                    . $crlf;
                if (! $this->export->outputHandler($head)) {
                    return false;
                }
            }

            // We need to SET IDENTITY_INSERT ON for MSSQL
            if (
                isset($GLOBALS['sql_compatibility'])
                && $GLOBALS['sql_compatibility'] === 'MSSQL'
                && $current_row == 0
            ) {
                if (
                    ! $this->export->outputHandler(
                        'SET IDENTITY_INSERT '
                        . Util::backquoteCompat(
                            $tableAlias,
                            $compat,
                            $sql_backquotes
                        )
                        . ' ON ;' . $crlf
                    )
                ) {
                    return false;
                }
            }

            $current_row++;
            $values = [];
            for ($j = 0; $j < $fieldsCnt; $j++) {
                // NULL
                if (! isset($row[$j])) {
                    $values[] = 'NULL';
                } elseif (
                    $fieldsMeta[$j]->isNumeric
                ) {
                    // a number
                    $values[] = $row[$j];
                } elseif ($fieldsMeta[$j]->isBinary && isset($GLOBALS['sql_hex_for_binary'])) {
                    // a true BLOB
                    // - mysqldump only generates hex data when the --hex-blob
                    //   option is used, for fields having the binary attribute
                    //   no hex is generated
                    // - a TEXT field returns type blob but a real blob
                    //   returns also the 'binary' flag

                    // empty blobs need to be different, but '0' is also empty
                    // :-(
                    if (empty($row[$j]) && $row[$j] != '0') {
                        $values[] = '\'\'';
                    } else {
                        $values[] = '0x' . bin2hex($row[$j]);
                    }
                } elseif ($fieldsMeta[$j]->isMappedTypeBit) {
                    // detection of 'bit' works only on mysqli extension
                    $values[] = "b'" . Util::printableBitValue(
                        (int) $row[$j],
                        (int) $fieldsMeta[$j]->length
                    ) . "'";
                } elseif ($fieldsMeta[$j]->isMappedTypeGeometry) {
                    // export GIS types as hex
                    $values[] = '0x' . bin2hex($row[$j]);
                } elseif (! empty($GLOBALS['exporting_metadata']) && $row[$j] === '@LAST_PAGE') {
                    $values[] = '@LAST_PAGE';
                } elseif ($row[$j] === '') {
                    $values[] = "''";
                } else {
                    // something else -> treat as a string
                    $values[] = '\'' . $dbi->escapeString($row[$j]) . '\'';
                }
            }

            // should we make update?
            if (isset($GLOBALS['sql_type']) && $GLOBALS['sql_type'] === 'UPDATE') {
                $insertLine = $schemaInsert;
                for ($i = 0; $i < $fieldsCnt; $i++) {
                    if ($i == 0) {
                        $insertLine .= ' ';
                    }

                    if ($i > 0) {
                        // avoid EOL blank
                        $insertLine .= ',';
                    }

                    $insertLine .= $fieldSet[$i] . ' = ' . $values[$i];
                }

                [$tmpUniqueCondition, $tmpClauseIsUnique] = Util::getUniqueCondition(
                    $fieldsCnt,
                    $fieldsMeta,
                    $row
                );
                $insertLine .= ' WHERE ' . $tmpUniqueCondition;
                unset($tmpUniqueCondition, $tmpClauseIsUnique);
            } else {
                // Extended inserts case
                if ($GLOBALS['sql_insert_syntax'] === 'extended' || $GLOBALS['sql_insert_syntax'] === 'both') {
                    if ($current_row == 1) {
                        $insertLine = $schemaInsert . '('
                            . implode(', ', $values) . ')';
                    } else {
                        $insertLine = '(' . implode(', ', $values) . ')';
                        $insertLineSize = mb_strlen($insertLine);
                        $sqlMaxSize = $GLOBALS['sql_max_query_size'];
                        if (isset($sqlMaxSize) && $sqlMaxSize > 0 && $querySize + $insertLineSize > $sqlMaxSize) {
                            if (! $this->export->outputHandler(';' . $crlf)) {
                                return false;
                            }

                            $querySize = 0;
                            $current_row = 1;
                            $insertLine = $schemaInsert . $insertLine;
                        }
                    }

                    $querySize += mb_strlen($insertLine);
                    // Other inserts case
                } else {
                    $insertLine = $schemaInsert
                        . '(' . implode(', ', $values) . ')';
                }
            }

            unset($values);

            if (! $this->export->outputHandler(($current_row == 1 ? '' : $separator . $crlf) . $insertLine)) {
                return false;
            }
        }

        if ($current_row > 0) {
            if (! $this->export->outputHandler(';' . $crlf)) {
                return false;
            }
        }

        // We need to SET IDENTITY_INSERT OFF for MSSQL
        if (isset($GLOBALS['sql_compatibility']) && $GLOBALS['sql_compatibility'] === 'MSSQL' && $current_row > 0) {
            $outputSucceeded = $this->export->outputHandler(
                $crlf . 'SET IDENTITY_INSERT '
                . Util::backquoteCompat(
                    $tableAlias,
                    $compat,
                    $sql_backquotes
                )
                . ' OFF;' . $crlf
            );
            if (! $outputSucceeded) {
                return false;
            }
        }

        return true;
    }

    /**
     * Make a create table statement compatible with MSSQL
     *
     * @param string $createQuery MySQL create table statement
     *
     * @return string MSSQL compatible create table statement
     */
    private function makeCreateTableMSSQLCompatible($createQuery)
    {
        // In MSSQL
        // 1. No 'IF NOT EXISTS' in CREATE TABLE
        // 2. DATE field doesn't exists, we will use DATETIME instead
        // 3. UNSIGNED attribute doesn't exist
        // 4. No length on INT, TINYINT, SMALLINT, BIGINT and no precision on
        //    FLOAT fields
        // 5. No KEY and INDEX inside CREATE TABLE
        // 6. DOUBLE field doesn't exists, we will use FLOAT instead

        $createQuery = (string) preg_replace('/^CREATE TABLE IF NOT EXISTS/', 'CREATE TABLE', (string) $createQuery);
        // first we need  to replace all lines ended with '" DATE ...,\n'
        // last preg_replace preserve us from situation with date text
        // inside DEFAULT field value
        $createQuery = (string) preg_replace(
            "/\" date DEFAULT NULL(,)?\n/",
            '" datetime DEFAULT NULL$1' . "\n",
            $createQuery
        );
        $createQuery = (string) preg_replace("/\" date NOT NULL(,)?\n/", '" datetime NOT NULL$1' . "\n", $createQuery);
        $createQuery = (string) preg_replace(
            '/" date NOT NULL DEFAULT \'([^\'])/',
            '" datetime NOT NULL DEFAULT \'$1',
            $createQuery
        );

        // next we need to replace all lines ended with ') UNSIGNED ...,'
        // last preg_replace preserve us from situation with unsigned text
        // inside DEFAULT field value
        $createQuery = (string) preg_replace("/\) unsigned NOT NULL(,)?\n/", ') NOT NULL$1' . "\n", $createQuery);
        $createQuery = (string) preg_replace(
            "/\) unsigned DEFAULT NULL(,)?\n/",
            ') DEFAULT NULL$1' . "\n",
            $createQuery
        );
        $createQuery = (string) preg_replace(
            '/\) unsigned NOT NULL DEFAULT \'([^\'])/',
            ') NOT NULL DEFAULT \'$1',
            $createQuery
        );

        // we need to replace all lines ended with
        // '" INT|TINYINT([0-9]{1,}) ...,' last preg_replace preserve us
        // from situation with int([0-9]{1,}) text inside DEFAULT field
        // value
        $createQuery = (string) preg_replace(
            '/" (int|tinyint|smallint|bigint)\([0-9]+\) DEFAULT NULL(,)?\n/',
            '" $1 DEFAULT NULL$2' . "\n",
            $createQuery
        );
        $createQuery = (string) preg_replace(
            '/" (int|tinyint|smallint|bigint)\([0-9]+\) NOT NULL(,)?\n/',
            '" $1 NOT NULL$2' . "\n",
            $createQuery
        );
        $createQuery = (string) preg_replace(
            '/" (int|tinyint|smallint|bigint)\([0-9]+\) NOT NULL DEFAULT \'([^\'])/',
            '" $1 NOT NULL DEFAULT \'$2',
            $createQuery
        );

        // we need to replace all lines ended with
        // '" FLOAT|DOUBLE([0-9,]{1,}) ...,'
        // last preg_replace preserve us from situation with
        // float([0-9,]{1,}) text inside DEFAULT field value
        $createQuery = (string) preg_replace(
            '/" (float|double)(\([0-9]+,[0-9,]+\))? DEFAULT NULL(,)?\n/',
            '" float DEFAULT NULL$3' . "\n",
            $createQuery
        );
        $createQuery = (string) preg_replace(
            '/" (float|double)(\([0-9,]+,[0-9,]+\))? NOT NULL(,)?\n/',
            '" float NOT NULL$3' . "\n",
            $createQuery
        );

        return (string) preg_replace(
            '/" (float|double)(\([0-9,]+,[0-9,]+\))? NOT NULL DEFAULT \'([^\'])/',
            '" float NOT NULL DEFAULT \'$3',
            $createQuery
        );

        // @todo remove indexes from CREATE TABLE
    }

    /**
     * replaces db/table/column names with their aliases
     *
     * @param string $sqlQuery SQL query in which aliases are to be substituted
     * @param array  $aliases  Alias information for db/table/column
     * @param string $db       the database name
     * @param string $table    the tablename
     * @param string $flag     the flag denoting whether any replacement was done
     *
     * @return string query replaced with aliases
     */
    public function replaceWithAliases(
        $sqlQuery,
        array $aliases,
        $db,
        $table = '',
        &$flag = null
    ) {
        $flag = false;

        /**
         * The parser of this query.
         */
        $parser = new Parser($sqlQuery);

        if (empty($parser->statements[0])) {
            return $sqlQuery;
        }

        /**
         * The statement that represents the query.
         *
         * @var CreateStatement $statement
         */
        $statement = $parser->statements[0];

        /**
         * Old database name.
         */
        $oldDatabase = $db;

        // Replacing aliases in `CREATE TABLE` statement.
        if ($statement->options->has('TABLE')) {
            // Extracting the name of the old database and table from the
            // statement to make sure the parameters are correct.
            if (! empty($statement->name->database)) {
                $oldDatabase = $statement->name->database;
            }

            /**
             * Old table name.
             */
            $oldTable = $statement->name->table;

            // Finding the aliased database name.
            // The database might be empty so we have to add a few checks.
            $newDatabase = null;
            if (! empty($statement->name->database)) {
                $newDatabase = $statement->name->database;
                if (! empty($aliases[$oldDatabase]['alias'])) {
                    $newDatabase = $aliases[$oldDatabase]['alias'];
                }
            }

            // Finding the aliases table name.
            $newTable = $oldTable;
            if (! empty($aliases[$oldDatabase]['tables'][$oldTable]['alias'])) {
                $newTable = $aliases[$oldDatabase]['tables'][$oldTable]['alias'];
            }

            // Replacing new values.
            if (($statement->name->database !== $newDatabase) || ($statement->name->table !== $newTable)) {
                $statement->name->database = $newDatabase;
                $statement->name->table = $newTable;
                $statement->name->expr = ''; // Force rebuild.
                $flag = true;
            }

            /** @var CreateDefinition[] $fields */
            $fields = $statement->fields;
            foreach ($fields as $field) {
                // Column name.
                if (! empty($field->type)) {
                    if (! empty($aliases[$oldDatabase]['tables'][$oldTable]['columns'][$field->name])) {
                        $field->name = $aliases[$oldDatabase]['tables'][$oldTable]['columns'][$field->name];
                        $flag = true;
                    }
                }

                // Key's columns.
                if (! empty($field->key)) {
                    foreach ($field->key->columns as $key => $column) {
                        if (! isset($column['name'])) {
                            // In case the column has no name field
                            continue;
                        }

                        if (empty($aliases[$oldDatabase]['tables'][$oldTable]['columns'][$column['name']])) {
                            continue;
                        }

                        $columnAliases = $aliases[$oldDatabase]['tables'][$oldTable]['columns'];
                        $field->key->columns[$key]['name'] = $columnAliases[$column['name']];
                        $flag = true;
                    }
                }

                // References.
                if (empty($field->references)) {
                    continue;
                }

                $refTable = $field->references->table->table;
                // Replacing table.
                if (! empty($aliases[$oldDatabase]['tables'][$refTable]['alias'])) {
                    $field->references->table->table = $aliases[$oldDatabase]['tables'][$refTable]['alias'];
                    $field->references->table->expr = '';
                    $flag = true;
                }

                // Replacing column names.
                foreach ($field->references->columns as $key => $column) {
                    if (empty($aliases[$oldDatabase]['tables'][$refTable]['columns'][$column])) {
                        continue;
                    }

                    $field->references->columns[$key] = $aliases[$oldDatabase]['tables'][$refTable]['columns'][$column];
                    $flag = true;
                }
            }
        } elseif ($statement->options->has('TRIGGER')) {
            // Extracting the name of the old database and table from the
            // statement to make sure the parameters are correct.
            if (! empty($statement->table->database)) {
                $oldDatabase = $statement->table->database;
            }

            /**
             * Old table name.
             */
            $oldTable = $statement->table->table;

            if (! empty($aliases[$oldDatabase]['tables'][$oldTable]['alias'])) {
                $statement->table->table = $aliases[$oldDatabase]['tables'][$oldTable]['alias'];
                $statement->table->expr = ''; // Force rebuild.
                $flag = true;
            }
        }

        if (
            $statement->options->has('TRIGGER')
            || $statement->options->has('PROCEDURE')
            || $statement->options->has('FUNCTION')
            || $statement->options->has('VIEW')
        ) {
            // Replacing the body.
            for ($i = 0, $count = count((array) $statement->body); $i < $count; ++$i) {

                /**
                 * Token parsed at this moment.
                 *
                 * @var Token $token
                 */
                $token = $statement->body[$i];

                // Replacing only symbols (that are not variables) and unknown
                // identifiers.
                $isSymbol = $token->type === Token::TYPE_SYMBOL;
                $isKeyword = $token->type === Token::TYPE_KEYWORD;
                $isNone = $token->type === Token::TYPE_NONE;
                $replaceToken = $isSymbol
                    && (! ($token->flags & Token::FLAG_SYMBOL_VARIABLE))
                    || ($isKeyword
                    && (! ($token->flags & Token::FLAG_KEYWORD_RESERVED))
                    || $isNone);

                if (! $replaceToken) {
                    continue;
                }

                $alias = $this->getAlias($aliases, $token->value);
                if (empty($alias)) {
                    continue;
                }

                // Replacing the token.
                $token->token = Context::escape($alias);
                $flag = true;
            }
        }

        return $statement->build();
    }

    /**
     * Generate comment
     *
     * @param string      $crlf         Carriage return character
     * @param string|null $sqlStatement SQL statement
     * @param string      $comment1     Comment for dumped table
     * @param string      $comment2     Comment for current table
     * @param string      $tableAlias   Table alias
     * @param string      $compat       Compatibility mode
     *
     * @return string
     */
    protected function generateComment(
        $crlf,
        ?string $sqlStatement,
        $comment1,
        $comment2,
        $tableAlias,
        $compat
    ) {
        if (! isset($sqlStatement)) {
            if (isset($GLOBALS['no_constraints_comments'])) {
                $sqlStatement = '';
            } else {
                $sqlStatement = $crlf
                    . $this->exportComment()
                    . $this->exportComment($comment1)
                    . $this->exportComment();
            }
        }

        // comments for current table
        if (! isset($GLOBALS['no_constraints_comments'])) {
            $sqlStatement .= $crlf
                . $this->exportComment()
                . $this->exportComment(
                    $comment2 . ' ' . Util::backquoteCompat(
                        $tableAlias,
                        $compat,
                        isset($GLOBALS['sql_backquotes'])
                    )
                )
                . $this->exportComment();
        }

        return $sqlStatement;
    }
}
¿Qué es la limpieza dental de perros? - Clínica veterinaria


Es la eliminación del sarro y la placa adherida a la superficie de los dientes mediante un equipo de ultrasonidos que garantiza la integridad de las piezas dentales a la vez que elimina en profundidad cualquier resto de suciedad.

A continuación se procede al pulido de los dientes mediante una fresa especial que elimina la placa bacteriana y devuelve a los dientes el aspecto sano que deben tener.

Una vez terminado todo el proceso, se mantiene al perro en observación hasta que se despierta de la anestesia, bajo la atenta supervisión de un veterinario.

¿Cada cuánto tiempo tengo que hacerle una limpieza dental a mi perro?

A partir de cierta edad, los perros pueden necesitar una limpieza dental anual o bianual. Depende de cada caso. En líneas generales, puede decirse que los perros de razas pequeñas suelen acumular más sarro y suelen necesitar una atención mayor en cuanto a higiene dental.


Riesgos de una mala higiene


Los riesgos más evidentes de una mala higiene dental en los perros son los siguientes:

  • Cuando la acumulación de sarro no se trata, se puede producir una inflamación y retracción de las encías que puede descalzar el diente y provocar caídas.
  • Mal aliento (halitosis).
  • Sarro perros
  • Puede ir a más
  • Las bacterias de la placa pueden trasladarse a través del torrente circulatorio a órganos vitales como el corazón ocasionando problemas de endocarditis en las válvulas. Las bacterias pueden incluso acantonarse en huesos (La osteomielitis es la infección ósea, tanto cortical como medular) provocando mucho dolor y una artritis séptica).

¿Cómo se forma el sarro?

El sarro es la calcificación de la placa dental. Los restos de alimentos, junto con las bacterias presentes en la boca, van a formar la placa bacteriana o placa dental. Si la placa no se retira, al mezclarse con la saliva y los minerales presentes en ella, reaccionará formando una costra. La placa se calcifica y se forma el sarro.

El sarro, cuando se forma, es de color blanquecino pero a medida que pasa el tiempo se va poniendo amarillo y luego marrón.

Síntomas de una pobre higiene dental
La señal más obvia de una mala salud dental canina es el mal aliento.

Sin embargo, a veces no es tan fácil de detectar
Y hay perros que no se dejan abrir la boca por su dueño. Por ejemplo…

Recientemente nos trajeron a la clínica a un perro que parpadeaba de un ojo y decía su dueño que le picaba un lado de la cara. Tenía molestias y dificultad para comer, lo que había llevado a sus dueños a comprarle comida blanda (que suele ser un poco más cara y llevar más contenido en grasa) durante medio año. Después de una exploración oftalmológica, nos dimos cuenta de que el ojo tenía una úlcera en la córnea probablemente de rascarse . Además, el canto lateral del ojo estaba inflamado. Tenía lo que en humanos llamamos flemón pero como era un perro de pelo largo, no se le notaba a simple vista. Al abrirle la boca nos llamó la atención el ver una muela llena de sarro. Le realizamos una radiografía y encontramos una fístula que llegaba hasta la parte inferior del ojo.

Le tuvimos que extraer la muela. Tras esto, el ojo se curó completamente con unos colirios y una lentilla protectora de úlcera. Afortunadamente, la úlcera no profundizó y no perforó el ojo. Ahora el perro come perfectamente a pesar de haber perdido una muela.

¿Cómo mantener la higiene dental de tu perro?
Hay varias maneras de prevenir problemas derivados de la salud dental de tu perro.

Limpiezas de dientes en casa
Es recomendable limpiar los dientes de tu perro semanal o diariamente si se puede. Existe una gran variedad de productos que se pueden utilizar:

Pastas de dientes.
Cepillos de dientes o dedales para el dedo índice, que hacen más fácil la limpieza.
Colutorios para echar en agua de bebida o directamente sobre el diente en líquido o en spray.

En la Clínica Tus Veterinarios enseñamos a nuestros clientes a tomar el hábito de limpiar los dientes de sus perros desde que son cachorros. Esto responde a nuestro compromiso con la prevención de enfermedades caninas.

Hoy en día tenemos muchos clientes que limpian los dientes todos los días a su mascota, y como resultado, se ahorran el dinero de hacer limpiezas dentales profesionales y consiguen una mejor salud de su perro.


Limpiezas dentales profesionales de perros y gatos

Recomendamos hacer una limpieza dental especializada anualmente. La realizamos con un aparato de ultrasonidos que utiliza agua para quitar el sarro. Después, procedemos a pulir los dientes con un cepillo de alta velocidad y una pasta especial. Hacemos esto para proteger el esmalte.

La frecuencia de limpiezas dentales necesaria varía mucho entre razas. En general, las razas grandes tienen buena calidad de esmalte, por lo que no necesitan hacerlo tan a menudo e incluso pueden pasarse la vida sin requerir una limpieza. Sin embargo, razas pequeñas como el Yorkshire o el Maltés, deben hacérselas todos los años desde cachorros si se quiere conservar sus piezas dentales.

Otro factor fundamental es la calidad del pienso. Algunas marcas han diseñado croquetas que limpian la superficie del diente y de la muela al masticarse.

Ultrasonido para perros

¿Se necesita anestesia para las limpiezas dentales de perros y gatos?

La limpieza dental en perros no es una técnica que pueda practicarse sin anestesia general , aunque hay veces que los propietarios no quieren anestesiar y si tiene poco sarro y el perro es muy bueno se puede intentar…… , pero no se va a poder pulir ni acceder a todas la zona de la boca …. Además los limpiadores dentales van a irrigar agua y hay riesgo de aspiración a vías respiratorias si no se realiza una anestesia correcta con intubación traqueal . En resumen , sin anestesia no se va hacer una correcta limpieza dental.

Tampoco sirve la sedación ya que necesitamos que el animal esté totalmente quieto, y el veterinario tenga un acceso completo a todas sus piezas dentales y encías.

Alimentos para la limpieza dental

Hay que tener cierto cuidado a la hora de comprar determinados alimentos porque no todos son saludables. Algunos tienen demasiado contenido graso, que en exceso puede causar problemas cardiovasculares y obesidad.

Los mejores alimentos para los dientes son aquellos que están elaborados por empresas farmacéuticas y llevan componentes químicos con tratamientos específicos para el diente del perro. Esto implica no solo limpieza a través de la acción mecánica de morder sino también un tratamiento antibacteriano para prevenir el sarro.

Conclusión

Si eres como la mayoría de dueños, por falta de tiempo , es probable que no estés prestando la suficiente atención a la limpieza dental de tu perro. Por eso te animamos a que comiences a limpiar los dientes de tu perro y consideres atender a su higiene bucal con frecuencia.

Estas simples medidas pueden conllevar a que tu perro tenga una vida más larga y mucho más saludable.

Si te resulta imposible introducir un cepillo de dientes a tu perro en la boca, pásate con él por clínica Tus Veterinarios y te explicamos cómo hacerlo.

Necesitas hacer una limpieza dental profesional a tu mascota?
Llámanos al 622575274 o contacta con nosotros

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

¡Hola!