yf_db_installer_pgsql class

Fileplugins/db/classes/db/yf_db_installer_pgsql.class.php
Use_class('db_installer_pgsql', 'classes/db/')
Extendsyf_db_installer

Public properties

A site sets them in its config with $PROJECT_CONF['db_installer_pgsql']['NAME'] = ...;

PropertyDefaultDescription
$NO_REPAIR_TABLES
array
[]
$NUM_RETRIES
int
5

Inherited properties

from yf_db_installer: $LOCK_FILE_NAME, $LOCK_TIMEOUT, $OTHER_TABLES_DATAS, $OTHER_TABLES_STRUCTS, $RESTORE_FULLTEXT_INDEX, $SHARDING_BY_COUNTRY, $SHARDING_BY_DAY, $SHARDING_BY_LANG, $SHARDING_BY_MONTH, $SHARDING_BY_YEAR, $SYS_TABLES_DATAS, $SYS_TABLES_STRUCTS, $TABLES_DATA, $TABLES_SQL, $TABLES_SQL_PHP, $USE_CACHE, $USE_LOCKING, $USE_SQL_IF_NOT_EXISTS, $alter_table_post_callbacks, $alter_table_pre_callbacks, $create_table_post_callbacks, $create_table_pre_callbacks

Public methods 13

db_query_safe

public function db_query_safe($sql, $db)

Run the original query again, at most NUM_RETRIES times; db->query() starts the repair itself when it fails.

    public function db_query_safe($sql, $db)
    {
        if (isset($db->_repairs_by_sql[$sql]) && $db->_repairs_by_sql[$sql] >= $this->NUM_RETRIES) {
            return false;
        }
        @$db->_repairs_by_sql[$sql]++;
        return $db->query($sql);
    }
public function do_alter_table($full_table_name, $column_name, array $sql_php, $db)
  • $sql_php array
    public function do_alter_table($full_table_name, $column_name, array $sql_php, $db)
    {
        $column_data = (array) $sql_php['fields'][$column_name];
        $column_data['name'] = $column_name;
        $line = $db->utils()->_ddl_column_line($column_data);
        $table = $db->utils()->_escape_table_name($full_table_name);
        if ( ! $line || ! $table) {
            return false;
        }
        // Existing rows get the MySQL implicit default of a NOT NULL column without one
        if (empty($column_data['nullable']) && empty($column_data['auto_inc']) && ($column_data['default'] ?? null) === null) {
            $col = $db->escape_key(strtolower($column_name));
            return $db->query('ALTER TABLE ' . $table . ' ADD COLUMN ' . $line . ' DEFAULT ' . $db->utils()->_quote($this->_implicit_default($column_data)) . ';'
                . ' ALTER TABLE ' . $table . ' ALTER COLUMN ' . $col . ' DROP DEFAULT;');
        }
        return $db->query('ALTER TABLE ' . $table . ' ADD COLUMN ' . $line);
    }
public function do_create_table($full_table_name, array $sql_php, $db)
  • $sql_php array
    public function do_create_table($full_table_name, array $sql_php, $db)
    {
        if ( ! empty($this->NO_REPAIR_TABLES) && in_array($full_table_name, $this->NO_REPAIR_TABLES)) {
            return false;
        }
        $sql_php = $this->fix_sql_php($sql_php, $db);
        $sql_php['name'] = $full_table_name;
        $sql = $db->utils()->_ddl_create_sql($sql_php, $error);
        if ( ! $sql) {
            return false;
        }
        return $db->query($sql);
    }

fix_sql_php

public function fix_sql_php(array $sql_php, $db)
  • $sql_php array
    public function fix_sql_php(array $sql_php, $db)
    {
        if ($db->utils()->FULLTEXT_AS_GIN) {
            return $sql_php;
        }
        foreach ((array) ($sql_php['indexes'] ?? []) as $iname => $idx) {
            if (($idx['type'] ?? '') == 'fulltext') {
                unset($sql_php['indexes'][$iname]);
            }
        }
        return $sql_php;
    }
public function get_table_struct_array_by_name($table_name, $db)

sql_php shape of the real table (the MySQL installer parses SHOW CREATE TABLE, which Postgres lacks).

    public function get_table_struct_array_by_name($table_name, $db)
    {
        $utils = $db->utils();
        return [
            'name' => $table_name,
            'fields' => $utils->list_columns($table_name),
            'indexes' => $utils->list_indexes($table_name),
            'foreign_keys' => $utils->list_foreign_keys($table_name),
            'options' => $utils->table_options($table_name),
        ];
    }

import_data

public function import_data($full_table_name, array $data, $db)

Upsert on the "id" primary key the query builder targets; tables keyed otherwise get insert-ignore.

  • $data array
    public function import_data($full_table_name, array $data, $db)
    {
        $name = $this->_strip_table_name($full_table_name, $db);
        $sql_php = $this->TABLES_SQL_PHP[$name] ?? $this->TABLES_SQL_PHP['sys_' . $name] ?? [];
        $data = $this->_fill_implicit_defaults($data, $sql_php);
        $first = reset($data);
        $cols = is_array($first) ? array_keys($first) : array_keys($data);
        // upsert on the primary key like REPLACE INTO; rows without the whole key can only be insert-ignore
        $pk_cols = $db->query_builder()->_primary_key_columns($full_table_name);
        $result = $pk_cols && ! array_diff($pk_cols, $cols)
            ? $db->insert_safe($full_table_name, $data, false, false, false, true)
            : $db->insert_safe($full_table_name, $data, false, false, true);
        if ($result && $sql_php) {
            $this->seed_table_post($full_table_name, $sql_php, $db);
        }
        return $result;
    }
public function innodb_has_fulltext($db)

Postgres has no InnoDB fulltext; a GIN index is built only when the utils ask for it.

    public function innodb_has_fulltext($db)
    {
        return false;
    }

repair

public function repair($sql, $db_error, $db)

Create the missing table (SQLSTATE 42P01) or the missing column (42703) the failed query needs and run it again.

    public function repair($sql, $db_error, $db)
    {
        $sql = trim($sql);
        $state = (string) ($db_error['sqlstate'] ?? '');
        $message = (string) ($db_error['message'] ?? '');
        if ( ! strlen($state)) {
            // Drivers without SQLSTATE: the text is English unless lc_messages was changed
            if (preg_match('~relation "[^"]+" does not exist~i', $message)) {
                $state = '42P01';
            } elseif (preg_match('~column [^ ]+ (of relation "[^"]+" )?does not exist~i', $message)) {
                $state = '42703';
            }
        }
        if ($state === '42P01') {
            if ( ! preg_match('~relation "([^"]+)" does not exist~i', $message, $m)) {
                return false;
            }
            $table = $this->_strip_table_name($m[1], $db);
            if ( ! strlen($table) || ! $this->create_table($table, $db)) {
                return false;
            }
            return $this->db_query_safe($sql, $db);
        }
        if ($state === '42703') {
            // column "x" does not exist / column "x" of relation "t" does not exist / column t.x does not exist
            if ( ! preg_match('~column ["]?(?:[a-z_0-9]+\.)?([a-z_0-9]+)["]? (?:of relation "([^"]+)" )?does not exist~i', $message, $m)) {
                return false;
            }
            $column = $m[1];
            $table = $m[2] ?? '';
            if ( ! strlen($table) && preg_match('~\b(?:UPDATE|FROM|INTO)\s+(?:ONLY\s+)?["`]?([a-z_0-9.]+)["`]?~i', $sql, $m2)) {
                $table = $m2[1];
            }
            $table = $this->_strip_table_name($table, $db);
            if ( ! strlen($table) || ! $this->alter_table($table, $column, $db)) {
                return false;
            }
            return $this->db_query_safe($sql, $db);
        }
        return false;
    }

seed_data

public function seed_data($full_table_name, array $data, $sql_php, $db)

Seed rows leave NOT NULL columns without default out and rely on the MySQL implicit default, Postgres refuses it.

  • $data array
    public function seed_data($full_table_name, array $data, $sql_php, $db)
    {
        return $db->insert_safe($full_table_name, $this->_fill_implicit_defaults($data, $sql_php));
    }
public function seed_table_post($full_table_name, $sql_php, $db)

Rows with explicit ids do not move the serial sequences: set them past the imported values.

    public function seed_table_post($full_table_name, $sql_php, $db)
    {
        $utils = $db->utils();
        $table = $utils->_escape_table_name($full_table_name);
        if ( ! $table) {
            return false;
        }
        foreach ((array) $sql_php['fields'] as $name => $field) {
            if (empty($field['auto_inc'])) {
                continue;
            }
            $col = $db->escape_key(strtolower($field['name'] ?? $name));
            $db->query('SELECT setval(pg_get_serial_sequence(' . $utils->_quote($table) . ', ' . $utils->_quote(strtolower($field['name'] ?? $name)) . '), COALESCE(MAX(' . $col . '), 1), MAX(' . $col . ') IS NOT NULL) FROM ' . $table);
        }
        return true;
    }

Methods starting with _ hooks and helpers; not routable as page actions

public function _fill_implicit_defaults(array $data, $sql_php)

What MySQL stores for a missing NOT NULL column without default: 0, '' or the first enum value.

  • $data array one row or a list of rows
    public function _fill_implicit_defaults(array $data, $sql_php)
    {
        $first = reset($data);
        $rows = is_array($first) ? $data : [$data];
        foreach ((array) ($sql_php['fields'] ?? []) as $name => $field) {
            if ( ! empty($field['nullable']) || ! empty($field['auto_inc']) || ($field['default'] ?? null) !== null) {
                continue;
            }
            $value = $this->_implicit_default($field);
            $col = $field['name'] ?? $name;
            foreach ($rows as $i => $row) {
                if ( ! array_key_exists($col, $row)) {
                    $rows[$i][$col] = $value;
                }
            }
        }
        return is_array($first) ? $rows : $rows[0];
    }
public function _implicit_default(array $field)
  • $field array
    public function _implicit_default(array $field)
    {
        $type = strtolower($field['type'] ?? '');
        if (in_array($type, ['int', 'tinyint', 'smallint', 'mediumint', 'bigint', 'float', 'double', 'decimal', 'numeric', 'bit', 'year'])) {
            return 0;
        } elseif ($type === 'date') {
            return '1970-01-01';
        } elseif (in_array($type, ['datetime', 'timestamp'])) {
            return '1970-01-01 00:00:00';
        } elseif ($type === 'time') {
            return '00:00:00';
        } elseif ($type === 'enum' || $type === 'set') {
            return (string) (array_values((array) ($field['values'] ?? []))[0] ?? '');
        }
        return '';
    }
public function _strip_table_name($name, $db)

Name without database part and connection prefix, as the installer structures are keyed.

    public function _strip_table_name($name, $db)
    {
        $name = trim(str_replace(['"', '`'], '', (string) $name));
        $dot = strrpos($name, '.');
        if ($dot !== false) {
            $name = substr($name, $dot + 1);
        }
        $plen = strlen($db->DB_PREFIX);
        if ($plen && substr($name, 0, $plen) === $db->DB_PREFIX) {
            $name = substr($name, $plen);
        }
        // the mysql installer strips the same test-table marker
        return str_replace('dbt_', '', $name);
    }

Inherited methods

from yf_db_installer: __call(), _init(), alter_table(), alter_table_post_hook(), alter_table_pre_hook(), create_table(), create_table_php_to_sql(), create_table_post_hook(), create_table_pre_hook(), create_table_sql_to_php(), db_table_struct_into_array(), get_all_struct_array(), get_shard_table_name(), load_data()