Ir para conteúdo

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

Bruno Augusto

Select acessando Statement...

Recommended Posts

Então, queria melhorar um pouquinho meu conjunto de classes de acesso ao banco.

 

Fiz e funciona, mas fiquei chateado coma forma como foi feito.

 

Tenho as classes que constroem o SQL usando Fluent Interfaces:

 

$select = new Select( new Model );

$select -> select( array( 'field1', 'field1' ) )
       -> distinct()
-> from()

Do from() pra frente podem ser adicionados outros métodos à cadeia: where(), order(), limit() e etc.

 

Então eu tenho um método assemble() que monta a query, verificando quais métodos foram chamados e construindo o SQL propriamente dito.

 

Até aí sem problemas.

 

Até dois dias atrás só a PDO era usada, mas devido às discussões que rolaram aqui no fórum eu pensei "Por que não separar?" Se eu quiser usar a PDO eu uso, se eu preferir MySQLi, tudo bem também.

 

Daí eu reescrevi o conjunto de classes de DB. Criei adaptadores separados com a possibilidade de cada adaptador trabalhar com um driver diferente, cada BD passível de ser acessado tem um renderer próprio (para que os métodos chamados através da Select, sejam renderizados uniformemente, independente do banco escolhido) e etc.

 

Enfim... Dentre essas classes eu precisei criar um Statement próprio, para que todos os adaptadores compartilhassem uma mesma interface de uso, isto é, prepare(), execute(), fetchAll()...

 

Ocorre que, uma vez que a cadeia de métodos usados é um objeto Select, após o último método ser invocado não posso,em sequencia, chamar o fetchAll(), que pertence à Statement, que é uma classe separada.

 

Cada adaptador define sua própria classe Statement eu apenas faço $adapter -> getStatementClass(), porém ainda assim são classes diferentes.

 

Eu resolvi com um __call(). Eu intercepto o método que não existe em Select, construo a query (através do assemble()), preparo e executo essa query.

 

Dessa forma, o fluxo é direcionado da Select para a Statement e a cadeia continua, permitindo que eu atrele um fetchAll() ao código acima.

 

Mas eu queria saber outra forma, melhor, do que através do __call()

Compartilhar este post


Link para o post
Compartilhar em outros sites

Ocorre que, uma vez que a cadeia de métodos usados é um objeto Select, após o último método ser invocado não posso,em sequencia, chamar o fetchAll(), que pertence à Statement, que é uma classe separada.

que bom =)

 

 

 

permitindo que eu atrele um fetchAll() ao código acima.

putz.. e pra que isso ?

 

fazer FluentInterface é uma coisa, misturar conceitualmente, uma coisa que constroi a query, e outra que devolve um objeto dela, acho que é outra. (pelo menos eu acho isso)

e se a query falhar ? ou retornar 0 ? mesmo assim você faz o fetch() ?

Compartilhar este post


Link para o post
Compartilhar em outros sites

Então, eu invoco o fetchAll(), mas como o __call() exeuta o prepare() e o execute() e se ocorrer algum erro a Exception é disparada, com apenas um catch() eu manipulo o sucesso ou o erro.

 

Se uma Exception vier a ser gerada, o fetchAll() sequer será chamado pois a execução pararia no prepare() ou no execute() (dependendo do caso) através do __call().

 

O certo então seria quebrar essa cadeia? Se for, como deveria ser a forma correta de acessar o fetchAll() que é um método relativo ao Statement e não ao Table (ou Select)?

 

Também não vejo como certo criar "duplicatas" de tais métodos na Table e, através deles invocar os verdadeiros, na Statement.

 

P.S.: Não entendi o seu "que bom"

Compartilhar este post


Link para o post
Compartilhar em outros sites

Bom, relutantemente, depois de pensarem diversas possibilidades, umas mais assustadoras do que outras, acabei por adicionar métodos de acesso aos métodos dos diferentes e possíveis Statements.

 

Neles eu preparo e executo a query, e por fim retorno o método

 

No final, Select ganhou os métodos fetch() e fetchAll(), AbsractTable ganhou o rowCount() (já que é comm tanto à operações SELECT quanto à DELETE e UPDATE) e a Table ganhou o lastInsertId() (já que nela que insert() reside)

 

Nem de longe sei se é a melhor alternativa, mas li bastante a respeito, vi como outros sistema fazem e, modéstia à parte, posso até considerar o meu como mais organizado.

 

Agora basta criar a TableDataGateway e/ou RowDataGateway, muito embora eu não vejo a diferença (ou a utilidade) de ter dois que parecem fazer amesma coisa.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Então eu tenho um método assemble() que monta a query, verificando quais métodos foram chamados e construindo o SQL propriamente dito.

 

Eu faria um __toString. assim a classe só vai implementar métodos que são responsáveis pela construção do SQL, nada mais, nada menos !

Compartilhar este post


Link para o post
Compartilhar em outros sites

Pois é, agora que vi qeu que escrevi errado. É feito no __toString() mesmo. :P

 

Na verdade, a sentença correta seria "um método de assemble" (e sem os parênteses)

Compartilhar este post


Link para o post
Compartilhar em outros sites

A instância da Model sob com a qual Select trabalharia. Para a Select em si não é tão útil, já para Table sim, pois nela tinha o insert(), delete() e o update().

 

Tinha porque eu mudei (e ainda estou mudando) essa parte. A falta de material claro a respeito me atrapalha, já que não entendo muito bem os exemplos do Martin Fowler (que são em Java).

Compartilhar este post


Link para o post
Compartilhar em outros sites

Esse tópico é bem antigo, mas vendo a coisa dessa forma, semana passada esbarrei com um conteúdo a respeito de abstração em SQL, o pessoal falando a respeito das camadas INSERT , SELECT , DELETE ..

 

Afinal, essas são apenas palavras reservadas que por sua vez tem funcionalidades em vários aspectos na SQL, então, ter um conjunto de objetos para abstrair o SQL, é bacana, pra quem tem uma meta de desenvolvimento padronizada, é até util, pois você só vai trabalhar em cima daquilo ali, é o caso do seu Select, o motivo é o seguinte, pode vir vários casos em que você precisaria de um componente no qual seu objeto não oferece.

 

INSERT, a palavra INSERT no MySQL é reservada para inserções no banco de dados como todos conhecem, mas por sua vez ela pode ser usada em outros lugares, um caso 'insert at position' , como :

 

mysql> select insert ( 'andrey' , 6 , -1 , 'w' ) ;
+------------------------------------+
| insert ( 'andrey' , 6 , -1 , 'w' ) |
+------------------------------------+
| andrew                             |
+------------------------------------+
1 row in set (0.00 sec)

 

Dessa forma, não foi passado nenhuma outra keyword além dos parâmetros da insert, já o into por sua vez, ele pode ter parâmetros, como pode não ter, nesse caso, fica uma coisa meio que 'imprevisível' quando iremos usar estes recursos, acaba que neste caso, você teria que escrever a SQL manualmente, lógico que no PHP você pode fazer a mesma coisa do que a SQL acima fez, quero dizer no sentido de 'trabalhar com o banco', se podemos fazer no banco, fazemos no banco .. entendeu ?

 

Continuando .. a palavra SELECT, é outra também, ela é aplicável em N lugares, em subquerys, em exibição de string no próprio console .. muitas das vezes sem a formação das outras keywords, como 'from', 'where', 'case', 'when' ..

 

Então ficamos meio que 'limitado' à essas funcionalidades alternativas, claro que você está usando conforme pretende, que é gerar o select ( statement ) com suas propriedades, métodos que devem ser implementados em sua Model .. mas onde eu quero chegar, é o seguinte, e se você se depara em um caso no qual terá que usar um IF internamente no banco, por exemplo .. ? sua Select vai ser capaz de efetuar a demanda ?

 

Mesmo ela sendo, vão existir casos e casos, como eu disse acima, fica imprevisível quando iremos usar estes recursos, e como faremos para implementar essas funcionalidades quando necessário .. lógico que seria uma gambiarra só, se não tivéssemos um padrão .. por exemplo, um dos códigos do cara lá, ele usava dessa forma

 

( Statement Object ) 
$Statement = new Statement( ); 

( InstanceOf Select Object ) 
$Select = $Statement->select( ); 

( InstanceOf Where Object in Select Object ) 
$Select->select( ) ->where( ); 

( Method equals in Where Object aggregated in Select Object ) 
$Select->select( )->where( 'field.name' )->equals( 'assignmentValue' );

Saída
SELECT * FROM ( [ get_class ( $this ) ] ) WHERE `field`.`name` = 'assignmentValue' ..

 

No caso o get_class( $this ) só foi aplicado, porque ele usou uma cláusula com uma expressão que será reconhecida como campo, então temos que estar selecionando o valor desse campo em algum lugar .. ou teríamos um error quando o código fosse executado e/ou a sql fosse executada ..

 

Mas aí a pergunta, veja como ele recebe cada o objeto e a query vai sendo construída .. é algo meio que 'doido', pois ele tinha uma implementação grande ( digo vários objetos ) como às camadas, as statements, é até um parser para o campo SQL, veja que no 'field.name' uma vez um ponto, ele é reconhecido como campo da tabela, a expressão não valida isso, pois o campo é quebrado, adicionado aos backticks e atrelado a query

 

Da mesma forma que ele agrega/compõe outros objetos na Insert, Delete, Update .. mesmo assim ele não tinha algo muito que completo, pois o SQL é o que chamamos de uma Chomsky hierarchy

 

Depois de entender o que é essa hierarquia, vai entender o porque 'de' ser complicado, fazer esse tipo de coisa, alias, complicado não é não, é quase impossível.

 

Veja o diagrama abaixo, não está totalmente certo, mas a brincadeira é bem mais embaixo do que isso aqui

SQL.png

 

No caso, pode-se observar que o LEFT, RIGHT, INNER, LEFT OUTER dependem do FROM ( ou não ) .. porque realmente eu nunca fiz um experimento com outras cláusulas na qual os relacionamentos de dados são aplicados, o DELETE por exemplo, pode ser usado um relacionamento, mas de qualquer forma um DELETE precisa do FROM, eles já estão relacionados de certa forma ..

 

Um JOIN é um relacionamento, por isso os outros tipos de JOIN sempre estarão relacionados ao relacionamento primário que no caso é o JOIN podemos estar selecionando da esquerda para a direita, mas de qualquer forma, estamos relacionando ..

 

Não estão todos os objetos aí, dei o exemplo do Between logo acima, veja que o Between depende de uma cláusula AND que a cláusula AND pode estar associada às expressões que são utilizadas no WHERE, da mesma forma que ela pode receber apenas inteiros, flutuantes, datas .. daí só vai complicando .. se o SQL fosse algo definitivo e totalmente reservado como o PHP .. seria mole mole ter uma implementação como essa ..

 

Afinal, esse tipo de implementação é 99.999999% impossível de se fazer em PHP, o custo benefício .. nem se fala .. só de pensar no fato em que às expressões de igualdade, divisão, multiplicação, maior que, maior .. forem separadas, o número de ( nested objects ) é muito maior .. e elas tem que ser separadas, pois são aplicadas em vários e vários casos ..

 

Aí pensa em uma subquery .. um select com relacionamentos, e você tem seu método lá 'addSubQuery ( Statement $Statement )' é vai mais uma queda no processamento .. algo que se você for parar pra pensar, o PHP já vai tar pedindo socorro e muita aspirina =)

 

E por aí vai .. acho que agora vou deixar por conta da sua imaginação, e o seu sorriso para codificação.

 

Agora, coisa que é totalmente impressionante, mas impressionante mesmo é como o core de um SGDB MySQL, Oracle, PgSQL trabalha, quebram toda SQL que foi feita milésimos de segundos ..

 

Isso porque ainda não inclui PL nessa conversa toda .. se não, você ia ficar meio 'afraid' quanto a isso tudo .. Você falou no tópico das permutações lá, sobre um outro desafio .. que tal esse ?

 

Também não estou falando em 're-criar o processador de sql' em PHP, se você pensar só nas partes básicas, como às do diagrama acima, vai ver o trabalho que dá, agora imagina as outras coisas, funções .. etc

 

Outro exemplo precedido do 'insert', é a keyword from ..

mysql> select substring( 'andrey' from 3 );
+------------------------------+
| substring( 'andrey' from 3 ) |
+------------------------------+
| drey                         |
+------------------------------+
1 row in set (0.00 sec)

mysql> select substring( 'andrey' from 3 for 2 );
+------------------------------------+
| substring( 'andrey' from 3 for 2 ) |
+------------------------------------+
| dr                                 |
+------------------------------------+
1 row in set (0.00 sec)

 

A keyword for também .. ou seja, não temos delimitadores nos parâmetros, isso já é outra gambiarra no código .. ou coisa que é imprevisível, podemos mandar ( 'andrey' , 3 , 2 ) como podemos mandar um from .. daí perdemos um padrão de envio de parâmetros, a não ser que fizéssemos um 'escape' em tudo que for 'from' e 'for' coisa que é horrível ..

 

;)

Compartilhar este post


Link para o post
Compartilhar em outros sites

Reescrever o interpretador SQL seria loucura, além de ineficaz...

Estou estudando Linguagens Formais e Autômatos na faculdade esse semestre e aprendi SQL é uma Linguagem Livre de Contexto. Apesar de ser o segundo tipo mais 'simples' da hierarquia de Chomsky, a complexidade mora no tamanho da linguagem.

 

Dá uma sacada na sintaxe do SELECT:

SELECT [sTRAIGHT_JOIN]        [sql_SMALL_RESULT] [sql_BIG_RESULT] [sql_BUFFER_RESULT]        [sql_CACHE | SQL_NO_CACHE] [sql_CALC_FOUND_ROWS] [HIGH_PRIORITY]        [DISTINCT | DISTINCTROW | ALL]     expressão_select,...     [iNTO {OUTFILE | DUMPFILE} 'nome_arquivo' opções_exportação]     [FROM tabelas_ref       [WHERE definição_where]       [GROUP BY {inteiro_sem_sinal | nome_col | formula} [ASC | DESC], ...         [WITH ROLLUP]]       [HAVING where_definition]       [ORDER BY {inteiro_sem_sinal | nome_coluna | formula} [ASC | DESC], ...]       [LIMIT [offset,] row_count | row_count OFFSET offset]       [PROCEDURE nome_procedimento(lista_argumentos)]       [FOR UPDATE | LOCK IN SHARE MODE]]

Oh meu Deus, é um monstro :o

 

Sei lá, acredito que o trabalho pra construir uma coisa dessas não compensa não.

Eu faço algo bem mais simples:

modelss.png

 

Omiti alguns métodos e as propriedades por simplicidade.

Pra ficar bem claro, eu só interajo com a camada DAO, essas outras ficam sempre uma camada abaixo.

Os métodos da classe AbstractQueryConstructor são todos abstratos, cabe à filha concreta implementá-los.

AbstractTableHeader é uma representação da estrutura da tabela, contém os campos que uma certa tabela possui

As classes que implementam DBConnector são a camada mais baixa, para lidar diretamente com o banco de dados.

 

Pra realizar uma query fica mais ou menos assim:

$dao = new DAO(new ConcreteTable1());
$dao->fetchAll(); //retorna todas as entradas da tabela
$dao->where('valor > 300')
    	->join(new ConcreteTable2(), 'c1.fk_id = c2.id')
    	->orderBy('c2.id', 'DESC')
    	->fetchOne(); //Não importa a ordem, desde que os métodos fetch* venham por último
$dao->select('SUM(campo) AS total')
    	->groupBy('outro_campo')
    	->having('total > 10')
    	->orderBy('c2.id', 'DESC')
    	->fetchAll();

 

Só não é completamente portável pelo fato de eu inserir 'à mão' funções como SUM. Nunca trabalhei com outro SGBD a não ser MySQL, mas creio que seja diferente para outros SGBD's.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Isso aí Henrique .. o fato é que não é 'reescrever' o interpretador de SQL ou o construtor .. é mais pela complexidade do negócio, ter um bom conjunto de objetos para abstrair somente aquele padrão, não é ruim, mas ficamos limitados ao que podemos usar no banco .. foi exatamente isso que eu quis mostrar.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Ah! Mas eu nem sonhando pretendo abstrair TODO o SQL, seria doentio.

 

Eu por exemplo não tenho nem métodos para fazer JOIN porque já penso nelas como algo não tão básico que deveriam ser escrita na unha, até mesmo por questões de performance.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Não cara, join dá pra fazer de boa. Olha meu DC ali em cima... Basta você informar a tabela e a cláusula ON.

No QueryConstructor, para cada parte do statement eu guardo os dados. Eu armazeno a tabela e a cláusula em um array hash.

Na hora de montar o statement, eu só verifico se existem valores naquele array, se houver, os incluo:

Parte do método (abstrato) AbstractQueryConstructor::generateStatement() implementado pela filha MySQLQueryConstructor:

case self::SELECT:
			$statement .= 'SELECT ';
			if(!$this->count){
				if(count($this->distinct_fields) > 0)
					$statement .= 'DISTINCT('.join(', ', $this->distinct_fields).'), ';

				$prefix = $this->table->getAlias() != null ? $this->table->getAlias() : $this->table->getName();
				$statement .= $this->serializeFields($this->fields, $prefix);

				foreach($this->join_fields as $table => $fields)
					$statement .= ', '.$this->serializeFields($fields, $table);
			} else {
				$statement .= 'COUNT('.$this->count.')';
				if($this->countAlias !== null)
					$statement .= ' AS '.$this->countAlias;
			}

			if($this->subquery != null)
				$statement .= ', '.$this->subquery;

			$statement .= ' FROM '.$this->table->getName();
			if(($alias = $this->table->getAlias()) != null)
					$statement .= ' AS '.$alias;

			$statement .= $this->generateJoin('left');
			$statement .= $this->generateJoin('right');
			$statement .= $this->generateJoin('inner');

			if($this->where != null)
				$statement .= ' WHERE '.$this->where;

			if($this->group != null)
				$statement .= ' GROUP BY '.$this->group;

			if($this->having != null)
				$statement .= ' HAVING '.$this->having;

			if($this->order != null)
				$statement .= ' ORDER BY '.$this->order;

			if($this->limit != null)
				$statement .= ' LIMIT '.$this->limit;

			break;

...

Note que não coloquei OUTER JOIN, nem CROSS JOIN pq nunca precisei. Se e quando precisar, bastaria incluí-los...

Compartilhar este post


Link para o post
Compartilhar em outros sites

Essa sua classe, faz JOIN "aninhado" também? Tipo, entre várias tabelas?

 

Sei lá, de repente podia me dar uma idéia de como fazer...

Compartilhar este post


Link para o post
Compartilhar em outros sites

Seria legal trabalhar isso em grupo, fazer um conjunto de objetos para abstrair SQL, por partes, mantemos um código atualizado e apto aos casos mais conhecidos.

 

Rick, acho que esse seu método aí tem muita responsabilidade .. como eu disse, a refatoração para esse tipo de coisa chega as extremidades de cada coisa que será atribuída a query, como o JOIN pode entrar no UPDATE, DELETE .. nesse caso, seu código não se torna reutilizável

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.