KISS - Keep it simple, stupid!

Inserindo índices automaticamente nos relacionamentos

postado por Lucas Gameiro em 07/06/2011 11:27:01

Durante o desenvolvimento de uma aplicação legada, percebi que muitos gastos do banco de dados era devido a falta de índices nas tabelas do banco de dados.

Para não criar todos os índices na mão resolvi desenvolver um shell para o cakephp que os insira sempre que um relacionamento for detectado.

O script pode ser encontrado aqui no meu github.

Segue o código para quem não trabalha com git:

<?php
App::import('Model');
class InsertIndexShell extends Shell {
    public function main() {
        $models = $this->getModels();
        $this->insertIndexes($models);
    }
 
    private function getModels()
    {
        return App::objects('model');
    }
 
    private function insertIndexes($models)
    {
        foreach($models as $model)
        {
            App::import('Model',$model);
            $this->{$model} = new $model();
            $this->{$model}->recursive = -1;
            $this->fieldsToIndex($this->{$model});
        }
    }
 
    private function fieldsToIndex($modelObj)
    {
        foreach($modelObj->belongsTo as $relacionamento)
        {
            $field = $modelObj->_schema[$relacionamento['foreignKey']];
            if(!empty($field) && !array_key_exists('key',$field))
            {
                if($modelObj->query("ALTER TABLE `{$modelObj->tablePrefix}{$modelObj->table}` ADD INDEX (`{$relacionamento['foreignKey']}`)"))
                {
                    $this->out("Indice {$relacionamento['foreignKey']} adicionado em {$modelObj->table}");
                }
                else
                {
                    $this->out("impossivel adicionar indice {$relacionamento['foreignKey']} em {$modelObj->table}");
                }
            }
        }
    }
}
?>

Obs1.: Está específico para o MySQL.
Obs2.: Na próxima versão verificarei as colunas que estão sendo utilizadas nos conditions.

Tags: , , , , ,
Topo

Sem comentários

Logando queries no Shell do CakePHP

postado por Gabriel Gilini em 06/06/2011 14:59:53

Se você é como eu, adora criar shells em projetos CakePHP. São fáceis, herdam pouca estrutura e te dão acesso a quase tudo que precisa para tarefas de manutenção.

Apesar de todos os pontos positivos, passei por um problema sério recentemente: a incapacidade de se extender sua classe super. Todo shell deve herdar a classe Shell, que é declarada no core do CakePHP (cake/console/libs/shell.php). Este pequeno fato torna inviável qualquer mudança na classe Shell, já que alterar código terceiro em um projeto não é algo que eu considero inteligente.

A solução foi criar uma classe intermediária, assim como temos o AppController e o AppModel, podemos ter o AppShell.

Crie um arquivo app.php no caminho app/vendors/shells/app.php e crie sua classe AppShell:

<?php
class AppShell extends Shell
{
}

Ok, temos uma classe intermediária que podemos extender à vontade, é só fazer com que nossos shells extendam AppShell, com um pequeno porém: o Cake não vai incluir por padrão o arquivo de sua base class. E agora?

Simples! Vamos incluir o arquivo usando o método canivete suíço App::import.

<?php
App::import('Shell', 'App');
class SomeApplicationSpecificShell extends AppShell
{
    // Call some parent methods
}

E é só isso!

Mas o que você vai colocar na sua super classe? Eu tenho uma sugestão.

Sinto muita falta do element sql_dump, que apresenta um log de todas as transações ocorridas nos datasources durante o load da página. Abri o código do element e portei para nossa classe de shell:

<?php
class AppShell extends Shell
{
    public function getDatabaseLogs()
    {
        if (!class_exists('ConnectionManager') || Configure::read('debug') < 2) {
            return false;
        }
 
        $sources = ConnectionManager::sourceList();
        if (!isset($logs))
        {
            $logs = array();
            foreach ($sources as $source)
            {
                $db =& ConnectionManager::getDataSource($source);
                if (!$db->isInterfaceSupported('getLog'))
                {
                    continue;
                }
                $logs[$source] = $db->getLog();
            }
        }
 
        $out = array();
        $out[] = "Nr\tQuery\tError\tAffected\tNum. rows\tTook (ms)";
        foreach ($logs as $source => $logInfo)
        {
            $text = $logInfo['count'] > 1 ? 'queries' : 'query';
 
            $tmp = array();
            foreach ($logInfo['log'] as $k => $i)
            {
                $tmp[] = ($k + 1) . "\t" . h($i['query']) . "\t{$i['error']}\t{$i['affected']}\t{$i['numRows']}\t{$i['took']}";
            }
 
            $out[] = array(
                sprintf('(%s) %s %s took %s ms', $source, $logInfo['count'], $text, $logInfo['time']),
                $tmp
            );
        }
 
        return $out;
    }
}

Como vocês podem ver, simplesmente peguei o conteúdo do element sql_dump, removi a marcação, inseri tabulações para facilitar a visualização, joguei tudo num array e simplesmente retornei. Fica a cargo do desenvolvedor do projeto tratar e exibir a saída.

E você, que método é imprescindível em seus scripts shell?

Tags: , ,
Topo

Sem comentários

Creative Commons License
Sou Ágil: KISS em http://kiss.souagil.com.br está licenciado sobre
Creative Commons Attribution-Share Alike 2.5 Brazil License.

souÁgil