Tem algumas aplica??es que s?o ic?nicas pra gente – por diversas raz?es – no meu caso, alguns destes s?o winamp, mIRC, Macromedia Flash MX, Amarok, Kompare, MySQL Workbench e Gitlab.
Outro que conheci e me deixou admirado quando descobri foi o pgModeler, primeiro pela qualidade da aplica??o em si que é o mais próximo do Mysql Workbench que encontrei para PostgreSQL até hoje – e isso pra mim é um baita elogio. O segundo motivo foi descobrir que é um projeto de um brasileiro.
Dizendo de uma maneira bem resumida o pgModeler é uma aplica??o que permite desenhar seu banco de dados baseado em PostgreSQL usando uma interface visual bem completa, rápida e intuitiva. é possível gerar o DER da sua estrutura, exportar como imagem, gerar o SQL relacionado ao seu modelo, sincronizar em ambos os sentidos (de um banco de dados existentes para o modelo ou o contrário).
é um software livre e todo código está disponível no github do projeto, mas para ajudar a manter o desenvolvimento você pode fazer contribui??es financeiras ou comprar a vers?o pré-compilada a partir de USD $ 48,90. Se você pode, fa?a uma contribui??o financeira – pode acreditar que vale a pena, a aplica??o é excelente.
Mas caso você n?o tenha condi??o de contribuir financeiramente no momento a própria documenta??o do projeto tem um guia de como gerar seu binário. Minha inten??o aqui é registrar exatamente os comandos que eu precisei (e talvez você também precise) para compilar os binários no seu computador (se você também usa Kubuntu 21.04 como eu).
Sem mais delongas, o passo a passo é:
# First, download, extract and join the resource path
wget https://github.com/pgmodeler/pgmodeler/archive/refs/tags/v0.9.4.tar.gz -O pgmodeler-0.9.4.tar.gz
tar -zxvf pgmodeler-0.9.4.tar.gz
cd pgmodeler-0.9.4
# Get the plugins project using git
git clone https://github.com/pgmodeler/plugins.git
# Install all dependencies
sudo apt install make g++ qt5-qmake libxml2-dev libpq-dev pkg-config libqt5svg5-dev qt5 libqt5svg5 postgresql-server-dev-all qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev
# Prepare env
qmake pgmodeler.pro
# Compile
make
# Install
sudo make install
Se você está descobrindo agora o pgModeler, diga aí o que achou do projeto, mande um agradecimento lá no projeto para o Raphael que ele merece.
Essa é uma dica bem curta e realmente rápida pra registrar algo que precisei pesquisar algumas vezes nos últimos anos e sempre me esque?o.
Cena: você define uma tabela no seu projeto e gostaria de usar uma coluna com o tipo tsvector (como citei empostsrecentes) ou ent?o uuid. Você quer usar as fun??es do banco de dados para gerar o valor default para a coluna. Como fazer isso usando Migrations baseada no Phinx sem recorrer a SQL cru?
Literal pode ser utilizado tanto para definir valor default baseado em fun??es quanto para convers?o entre tipos para uma mesma coluna (fazer CAST do antigo formato para o novo).
Uso e trabalho com PHP há muitos anos e lá atrás as coisas eram bem complicadas – CMS era um palavr?o que ninguém entendia muito bem como funcionava, as op??es eram escassas e invariavelmente a gente caia no colo do phpnuke (ou aspnuke, dependendo do seu azar), que era terrível de customizar e estender. Até que um dia surge o WordPress, essa coisa linda que gerencia quase metade das páginas da internet ainda hoje.
Como todo gerenciador de conteúdo dinamico, exige certo processamento no servidor, e todo processamento toma tempo, por menor que seja.
Daí pulamos para 2021, com zilh?es de páginas espalhadas por aí, uma competindo com a outra por alguns segundos de aten??o e a gente com a vida corrida, picos e mais picos de ansiedade onde cada segundo de espera para abrir uma página gera frustra??o de desinteresse: a solu??o? otimizar a resposta da página para que carregue o mais rápido possível. Como fazer a página responder mais rápido? vamos fazer todo processamento antes do usuário acessar, e quando ele precisar, só enviamos o resultado que já está prontinho, e preferencialmente, em um servidor próximo.
Nessa dire??o comecei a estudar o Next.js, um framework baseado na biblioteca React (que por sua vez é baseado em JavaScript) que facilita a constru??o de páginas renderizadas estaticamente. Isso resolve a primeira parte do problema, retirando qualquer processamento no servidor no momento que alguém acessa a página.
A última parte, deixar as páginas o mais próximo possível do usuário, é feita pelo Vercel (que por sinal, é quem criou/mantém o Next.js). Ele abstrai a constru??o da página (com uma infraestrutura própria de CI/CD) e a sua distribui??o.
E onde entra o WordPress? Bom, essa solu??o n?o oferece uma interface rica para edi??o de conteúdo como o WP (existem outras integra??es para isso, mas o foco aqui é em quem já usa WP). A ideia é manter o WP para criar páginas/posts e usar o Next.js+Vercel para servir esse conteúdo de forma mais rápida.
Como fazer isso? Existem várias formas, mas uma das mais simples é instalar o plugin WP Graphql para expor seu conteúdo em uma API GraphQL estruturada, consumir essa API com o Next.js e aí ter sua vers?o estaticamente gerada.
Encerramos aqui essa breveintrodu??o sobre busca textual (fulltext search) com Postgres apresentando um plugin que pode te auxiliar (se você utiliza CakePHP + Postgres) na implementa??o da busca – caso n?o use, talvez sirva de inspira??o para um fork.
A história desse plugin vem lá de 2015 quando precisei incluir em uma busca que desconsidera-se pequenos erros de grafia e permitia o uso de sin?nimos. Tínhamos restri??es de hardware para implementar a busca – era uma aplica??o nova, com or?amento pequeno e dispunha de um servidor com apenas 2 GB de RAM. Usar Elasticsearch seria inviável.
Na época o CakePHP tinha recém chego a vers?o 3.0, com um ORM todo remodelado, muito mais flexível e extensível do que nas vers?es anteriores. Pensei: fácil, vou estender ele e adicionar suporte aos novos tipos de dados e índices (tsvector, GIN e GIST, como vimos anteriormente). Bom, na prática n?o era t?o fácil, a extensibilidade ainda era pequena, havia muitas dependências acopladas. A solu??o foi criar um shell que era invocado a cada X minutos e regerava a tabela de buscas com os tipos e índices apropriados usando SQL puro. Funcionou e foi o suficiente porque o sistema era novo e n?o tinha milh?es de registros – já prevíamos que escalando o banco de dados, precisaríamos de mais hardware para extrair a rotina de busca.
Mais de 6 anos se passaram, agora estamos com o CakePHP 4.2 e seu ORM muito mais flexível e desacoplado. E mais uma vez me foi dado o desafio de puxar uma busca que utilizava o Elasticsearch para dentro do banco de dados principal (Postgres). Assim nasceu o autopage/pg-search.
Considerando que você tenha uma instala??o do CakePHP > 4.2.2, a instala??o come?a com composer:
$ composer require autopage/pg-search
Em seguida, carregue o plugin na sua aplica??o:
$ bin/cake plugin load Autopage/PgSearch
Por último, precisamos configurar sua aplica??o para utilizar o driver Postgres fornecido. Para isso, edite seu arquivo de configura??o (ou variável de ambiente, se utilizar) config/app.php ou config/app_local.php:
// No ínicio do arquivo
use Autopage\PgSearch\Database\Driver\Postgres;
...
return [
...
// Dentro da configura??o dos datasources
...
'Datasources' => [
'default' => [
'driver' => Postgres::class,
...
Pronto, você já pode criar migrations (n?o depende disso), fixtures, Tables e querys com o CakePHP.
Uma migration poderia ser:
<?php
declare(strict_types=1);
use Migrations\AbstractMigration;
use Phinx\Util\Literal;
class CriaTabelaBuscas extends AbstractMigration
{
/**
* More information on this method is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
* @return void
*/
public function change()
{
$tabela = $this->table('buscas');
$tabela
->addColumn('original_id', 'integer', ['null' => false, 'default' => null])
->addColumn('nome', 'string', ['limit' => 255, 'null' => false, 'default' => null])
->addColumn('data', 'date', ['null' => false, 'default' => null])
->addColumn('conteudo', 'text', ['null' => false, 'default' => null])
->addColumn('conteudo_fts', Literal::from('tsvector'), ['null' => false, 'default' => null])
->addTimestamps('criado', 'modificado')
->create();
}
}
Para salvar registros, basta popular a entidade. Repare que criei duas colunas conteudo, uma text e outra tsvector. A ideia é que a primeira contenha a forma original que exibiremos ao usuário, enquanto a tsvector é preparada para execu??o da busca. Na hora de popular a entidade, atribua o mesmo valor em conteudo e conteudo_fts, o driver cuidará da convers?o necessária na hora de persistir.
Já uma consulta ao banco poderia ser feita com:
// N?o esque?a de sanitizar antes de passar na query abaixo
$termo = $this->request->getQuery('q');
$resultados = $this->Buscas->find()
->where(["conteudo_fts @@ phraseto_tsquery('{$termo}')"])
->order(['data' => 'desc'])
->all();
Ainda tem um behavior que adiciona um finder especial para busca, você deve vincular ele a tabela associada com a tabela de buscas – nesse exemplo da migration, o behavior seria ligado na tabela OriginalsTable. Veja as configura??es disponíveis na documenta??o do projeto.
é isso, com essa sequência de artigos espero ter mostrado que é possível oferecer uma busca boa o suficiente para a maioria das aplica??es sem precisar estourar or?amento com hardware ou servi?os externos. E claro, que essa implementa??o n?o precisa ser nenhum bicho de sete cabe?as.
Se tiver alguma dúvida ou encontrar algum problema, pode usar tanto a caixa de comentários abaixo quanto o github.
Gosto muito de ouvir música, já me aventurei em aprender um instrumento musical (saudades meu contrabaixo que ficou em Campo Grande) mas estou longe de ser considerado um audiófilo ou especialista em fones.
Dito isso, algumas experiências que tive com headphones e sistemas de sons me marcaram bastante. Uma vez testando um headphone BOSE com cancelamento de ruído ativo (nunca tinha usado um) – que experiência fantástica pra quem só tinha usado headphones de menos de R$ 150 (Sony, Philips, genéricos, AKG que vem junto com celular…). Em outra vez, me emprestaram um conjunto SoundSticks da Harman Kardon – nossa, eu queria re-ouvir todas as músicas que eu já tinha ouvido naquele aparelho, parece que instrumentos e notas surgiam em cada novo play. A última foi em parar em um stand de aeroporto com headphons da Sennheiser – testei alguns modelos e aquilo virou meu sonho de consumo. Um sonho distante, o mais barato custava mais de R$ 500, que pra mim era (ainda é) dinheiro demais.
Pula para 2021, trabalho remoto, enclausurado em casa e sem nenhum sistema de som – apenas headphones básico de celular e alto falante do notebook. Vi o anúncio: Sennheiser HD 350BT por “apenas” R$ 379. Passei duas semanas procurando review e opini?es mas achei pouca coisa. A maioria fala do irm?o mais velho, completo e caro HD 450BT. Mesmo assim resolvi arriscar e comprei. Quatro meses depois o que posso dizer: é bom, mas n?o memorável como minhas experiências citadas anteriormente.
A bateria dura bem depois que você atualizar o firmware – na vers?o original, mal durava 2 dias. Depois de atualizado, aguenta uma semana, dependendo do uso.
O som é limpo e equilibrado, n?o tem o baixo for?ado como alguns modelos da JBL e Philips gostam de se gabar. No app para mobile você consegue só atualizar o firmware e mexer um pouco na equaliza??o, mas sem grandes mudan?as.
Na parte de conex?o, ele tem bluetooth 5.0 com suporte a aptX para diminuir a latência, permite conectar dois aparelhos simultaneamente (um computador e seu celular, por exemplo) e alterna sozinho entre eles dependendo onde tem som saindo (uma liga??o, ou spotify transmitindo). Infelizmente ele n?o tem um conector analógico p2 ou p3, mas ele permite recarregar conectando o cabo USB-C enquanto você usa.
Sobre o tamanho, no meu caso ele n?o consegue cobrir completamente a orelha (está mais para “on-ear” do que “over-ear”) – as almofadas ficam sobre a “borda” da orelha, o que causa um desconforto considerável após poucas horas de uso. Acho que nunca consegui ficar mais de 3h seguidas com ele.
Além da ergonomia, outro ponto que me deixou desapontado foi o microfone. Uso ele conectado com um macbook air, e mesmo com a distancia maior, o microfone do macbook consegue captar minha voz muito mais alta e clara – acabo deixando configurado para usar o microfone embutido como entrada e o headphone só como saída.
Por fim, um ponto que é positivo é a garantia de 2 anos. Quer dizer, deve ser positivo porque li vários relatos de gente com dificuldade de acionar ela – espero n?o precisar.
Uma dica para um problema que tive: após atualizar o macOS, o headphone n?o queria se conectar de jeito nenhum ao macbook. Conectava no celular, na minha TV e em qualquer outra coisa, mas no macbook nada. O macbook por sua vez, se conectava a TV também, a um fone genérico bluetooth como se nada tivesse acontecido – mas com o HD 350BT nada. Passei 1 semana tentando limpar a lista de dispositivos bluetooth, os pareamentos, resetar o macOS e o headphone e nada. Resolvi deixar o headphone descarregar a bateria, limpei os pareamento do macOS e a conex?o voltou a funcionar.
Meu veredito? n?o me arrependo de ter comprado, nessa faixa de pre?o n?o conheci nada melhor. Mas se você pode gastar mais, talvez o irm?o mais velho dele (HD 450BT) ou um Sony na faixa de pre?o sejam mais interessantes.
Conhece alguma outra op??o interessante ou teve uma impress?o diferente? Comenta aí.
Continuando de onde paramos na parte 1, vamos ver um pouco como configurar o mínimo para ter uma busca textual através do Postgres. Se você quer se aprofundar mais nas possibilidades, recomendo os seguintes recursos:
Antes de tudo: você tem uma instala??o do Postgres funcionando né? N?o? Veja se esse artigo ajuda, quando terminar retome daqui.
Precisamos entender que a busca textual parte de um processamento do texto, fazendo uma análise estrutural e semantica e convertendo nosso texto em um formato padronizado. Esse processamento é dependente do idioma e portanto, uma configura??o feita para inglês é diferente de outra para português.
Felizmente o Postgres já vem com suporte básico a várias línguas, incluindo português, mas para uma busca mais flexível (pegando varia??es de plural/singular ou conjuga??es de verbo) precisamos incluir arquivos de dicionário.
Nesse gist eu disponibilizei os arquivos de dicionário para português do Brasil (pt-br). Eles foram extraídos do projeto Vero do LibreOffice e convertidos para utf-8. Sabendo da origem dos arquivos, vamos usá-los:
!/bin/bash
# Caminho das extens?es do Postgresql, varia de ambiente para ambiente. As vezes, de vers?o para vers?o.
# Para descobrir programaticamente, precisa instalar o pacote `postgresql-dev` e ent?o usar o `pg_config --sharedir`
# Lugares possíveis:
# Debian/Ubuntu: /usr/share/postgresql/VERSAO
# Alpine/docker: /usr/local/share/postgresql
PGSHARE=/usr/local/share/postgresql
mkdir -p $PGSHARE/tsearch_data
curl -sSL 'https://gist.github.com/CauanCabral/5ad952e0014c1cf21a87c3731397f078/raw/f525e8d21f6697c68303300713ddb28a4bccf384/pt_br.dict' -o $PGSHARE/tsearch_data/hunspell_pt_br.dict
curl -sSL'https://gist.github.com/CauanCabral/5ad952e0014c1cf21a87c3731397f078/raw/f525e8d21f6697c68303300713ddb28a4bccf384/pt_br.affix' -o $PGSHARE/tsearch_data/hunspell_pt_br.affix
Feito isso, você terá os arquivos de dicionário para pt-br onde o Postgres irá procurar.
O próximo passo é conectar ao banco de dados (pode ser via psql, via adminer ou pgAdmin, o que você preferir). Para evitar qualquer problema nesse primeiro teste, crie um banco de dados próprio para a gente e conecte a ele. Vou chamar aqui de busca_textual.
-- Habilitamos a extens?o 'unaccent' para usar na normaliza??o dos dados
CREATE EXTENSION IF NOT EXISTS unaccent SCHEMA public;
-- Criamos nosso dicionário, usando os arquivos que pegamos no gist
CREATE TEXT SEARCH DICTIONARY portuguese_hunspell (
TEMPLATE = ispell,
DictFile = hunspell_pt_br,
AffFile = hunspell_pt_br,
Stopwords = portuguese);
-- Criamos uma nova configura??o tomando como ponto de partida 'portuguese'
CREATE TEXT SEARCH CONFIGURATION pt_br ( COPY = portuguese );
-- Alteramos nossa nova configura??o, para usar o dicionário e unaccent
ALTER TEXT SEARCH CONFIGURATION pt_br
ALTER MAPPING FOR
asciiword, asciihword, hword_asciipart,
word, hword, hword_part
WITH portuguese_hunspell, unaccent, portuguese_stem;
Com esses comandos nós criamos uma configura??o chamada pt_br que instrui o Postgres a processar um texto fazendo:
Quebra do texto usando regras do português
Substitui??o da palavra pela sua forma n?o conjugada (por exemplo voto vira votar)
Remo??o dos acentos da palavra
Fundamental destacar que a identifica??o de uma palavra (token é o termo mais apropriado) só acontece uma vez. Ent?o se o token sendo processado existe no dicionário portuguese_hunspell, ele vai ser salvo e indexado da forma como esse dicionário trabalha ele (desconjugada a palavra). Se ela for acentuada, ela nem chegará a passar pelo filtro/dicionário de desacentua??o.
Por exemplo: a palavra raz?es ao passar por esse dicionário é substituída por raz?o, por que ela existe no dicionário. Já a palavra inventada jas?es passará despercebida nesse dicionário e cairá na regra de desacentua??o e será substituída por jasoes.
Ent?o a ordem dos dicionários/filtros na cria??o da configura??o s?o determinantes para o resultado da sua busca. Se colocar a desacentua??o antes do dicionário hunspell. nossos resultados anteriores seriam razoes e jasoes, respectivamente.
Tendo a configura??o, agora podemos ver como ela funciona em frases.
Lembra do parágrafo mencionado na parte 1? Execute a query abaixo para ver como ele seria processado em nossa configura??o:
SELECT * FROM ts_debug('pt_br', 'Algumas das principais raz?es da evas?o escolar no Brasil atual s?o a pobreza, a dificuldade de acesso à escola, a necessidade de trabalho e, principalmente, o desinteresse pelos estudos. Segundo o Programa das Na??es Unidas para o Desenvolvimento (PNUD), o país tem a terceira maior taxa de abandono escolar (24,3%) entre os 100 países com maior IDH (índice de Desenvolvimento Humano), só atrás da Bósnia e Herzegovina (26,8%) e das ilhas de S?o Cristov?o e Névis, no Caribe (26,5%). Na América Latina, só é superado pela Guatemala (35,2%) e pela Nicarágua (51,6%), n?o tendo sido divulgado o índice do Haiti.');
A saída deve ser tabular e conter algumas linhas como essas:
Alias
Desc.
Token
Dicionários
Dicionário
Lexema
word
Word, all letters
índice
{portuguese_hunspell,unaccent,portuguese_stem}
portuguese_hunspell
{índice}
blank
Space symbols
{}
NULL
NULL
asciiword
Word, all ASCII
de
{portuguese_hunspell,unaccent,portuguese_stem}
portuguese_hunspell
{}
blank
Space symbols
{}
NULL
NULL
asciiword
Word, all ASCII
Desenvolvimento
{portuguese_hunspell,unaccent,portuguese_stem}
portuguese_stem
{desenvolv}
blank
Space symbols
{}
NULL
NULL
asciiword
Word, all ASCII
Humano
{portuguese_hunspell,unaccent,portuguese_stem}
portuguese_hunspell
{humano,humanar}
blank
Space symbols
),
{}
NULL
NULL
word
Word, all letters
só
{portuguese_hunspell,unaccent,portuguese_stem}
portuguese_hunspell
{}
blank
Space symbols
{}
NULL
NULL
word
Word, all letters
atrás
{portuguese_hunspell,unaccent,portuguese_stem}
unaccent
{atras}
blank
Space symbols
{}
NULL
NULL
asciiword
Word, all ASCII
da
{portuguese_hunspell,unaccent,portuguese_stem}
portuguese_hunspell
{}
blank
Space symbols
{}
NULL
NULL
word
Word, all letters
Bósnia
{portuguese_hunspell,unaccent,portuguese_stem}
portuguese_hunspell
{bósnio}
blank
Space symbols
{}
NULL
NULL
asciiword
Word, all ASCII
e
{portuguese_hunspell,unaccent,portuguese_stem}
portuguese_hunspell
{}
blank
Space symbols
{}
NULL
NULL
asciiword
Word, all ASCII
Herzegovina
{portuguese_hunspell,unaccent,portuguese_stem}
portuguese_stem
{herzegovin}
blank
Space symbols
(
{}
NULL
NULL
uint
Unsigned integer
26
{simple}
simple
{26}
Cada linha representa um token identificado no parágrafo, como ele foi identificado, por qual regra e qual o resultado final após a aplica??o da regra.
Agora como salvamos no banco esse registro? Primeiro vamos criar uma tabela:
CREATE TABLE artigos (
id SERIAL PRIMARY KEY,
conteudo TEXT NOT NULL,
conteudo_fts TSVECTOR NOT NULL
);
Em seguida, vamos inserir o registro (repare que conteúdo é duplicado, mas na segunda vez, usamos a fun??o to_tsvector para converter ele):
INSERT INTO artigos (conteudo, conteudo_fts) VALUES (
'Algumas das principais raz?es da evas?o escolar no Brasil atual s?o a pobreza, a dificuldade de acesso à escola, a necessidade de trabalho e, principalmente, o desinteresse pelos estudos. Segundo o Programa das Na??es Unidas para o Desenvolvimento (PNUD), o país tem a terceira maior taxa de abandono escolar (24,3%) entre os 100 países com maior IDH (índice de Desenvolvimento Humano), só atrás da Bósnia e Herzegovina (26,8%) e das ilhas de S?o Cristov?o e Névis, no Caribe (26,5%). Na América Latina, só é superado pela Guatemala (35,2%) e pela Nicarágua (51,6%), n?o tendo sido divulgado o índice do Haiti.',
to_tsvector('pt_br', 'Algumas das principais raz?es da evas?o escolar no Brasil atual s?o a pobreza, a dificuldade de acesso à escola, a necessidade de trabalho e, principalmente, o desinteresse pelos estudos. Segundo o Programa das Na??es Unidas para o Desenvolvimento (PNUD), o país tem a terceira maior taxa de abandono escolar (24,3%) entre os 100 países com maior IDH (índice de Desenvolvimento Humano), só atrás da Bósnia e Herzegovina (26,8%) e das ilhas de S?o Cristov?o e Névis, no Caribe (26,5%). Na América Latina, só é superado pela Guatemala (35,2%) e pela Nicarágua (51,6%), n?o tendo sido divulgado o índice do Haiti.')
);
Agora sim podemos fazer uma consulta:
---- Bucansdo por 'na??o unidas'
SELECT id, conteudo FROM artigos WHERE conteudo_fts @@ plainto_tsquery('pt_br', 'na??o unidas');
---- A mesma busca da parte 1
SELECT id, conteudo FROM artigos WHERE conteudo_fts @@ plainto_tsquery('pt_br', 'evas?o escolar do brasil');
Ambas v?o encontrar nosso artigo.
Vale a pena estudar um pouco sobre as fun??es que o Postgres oferece na busca textual:
ts_debug(configuracao, texto): Ajuda a entender como sua configura??o interpreta um texto.
ts_lexize(dicionario, palavra): Aplica a regra do dicionário na palavra/token.
to_tsvector(configuracao, texto): Converte seu texto para um vetor de busca usando sua configura??o.
to_tsquery(configuracao, texto): Converte seu texto para uma query de busca de uma forma estrita. Palavras devem ser ligadas usando operadores explícitos (é uma jun??o? ou condicional?).
plainto_tsquery(configuracao, texto): Converte seu texto para uma query de busca usando uma linguagem natural, próxima ao do Google.
Da forma como está, você já consegue fazer uma busca textual básica, mas ela ainda é ineficiente porque n?o criamos nenhum índice. Colunas do tipo tsvector podem ser indexadas em índices dos tipos GIN e GIST – cada qual com sua vantagem. Em termos gerais, o GIN oferece uma tradeoff melhor entre tempo de escrita e otimiza??o de busca. O GIST possui um custo consideravelmente maior para escrita mas oferece uma velocidade melhor de leitura.
Hoje em dia qualquer aplica??o precisa de uma interface de busca, mas ainda é muito comum achar sistemas que dependem de uma entrada do usuário quase perfeita para conseguir encontrar um resultado. Internamente, essas aplica??es usam e abusam de operadores SQL como LIKE e ILIKE , por vezes contando com a ajuda do coringa %% para encontrar palavras ou express?es que contém a express?o pesquisada (uma substring em outras palavras).
Mas qual a diferen?a de usar um simples LIKE e uma busca textual? A diferen?a come?a pelo fato de um índice de busca textual armazenar os dados depois de um pré-processamento. Esse pré-processamento costuma envolver uma normaliza??o (tudo em caixa baixa, por exemplo), identifica??o de tokens (peda?os lógicos do texto) e descarte daquilo que n?o possui valor semantico (remove artigos e preposi??es que costumam aparecer muito sem ter valor real pra quem pesquisa, por exemplo), transforma??o das palavras encontradas em seus radicais básicos (processo chamado de stemming) e as vezes até incluindo ou substituindo essas palavras por sin?nimos. Parece complicado né? E é, mas você n?o precisa ser um pesquisador em NLP (processamento de linguagem natural) pra ter uma busca textual simples.
Em termos práticos, o que você ganha com uma busca textual?
Considere uma tabela chamada artigos e nela uma coluna conteudo do tipo TEXT. é um cenário comum onde se usa o operador LIKE. Agora vamos colocar uma segunda coluna conteudo_fulltext, essa do tipo TSVECTOR, que é um tipo próprio para busca textual.
Dentre os artigos salvos nessa tabela, temos um com o seguinte parágrafo:
Algumas das principais raz?es da evas?o escolar no Brasil atual s?o a pobreza, a dificuldade de acesso à escola, a necessidade de trabalho e, principalmente, o desinteresse pelos estudos. Segundo o Programa das Na??es Unidas para o Desenvolvimento (PNUD), o país tem a terceira maior taxa de abandono escolar (24,3%) entre os 100 países com maior IDH (índice de Desenvolvimento Humano), só atrás da Bósnia e Herzegovina (26,8%) e das ilhas de S?o Cristov?o e Névis, no Caribe (26,5%). Na América Latina, só é superado pela Guatemala (35,2%) e pela Nicarágua (51,6%), n?o tendo sido divulgado o índice do Haiti.
Imagine que alguém queira encontrar esse parágrafo na busca da nossa aplica??o. Esse alguém pesquisa: “evas?o escolar do brasil”.
Como isso seria tratado usando apenas um LIKE:
SELECT conteudo FROM artigos WHERE conteudo ILIKE '%evas?o escolar do brasil%';
E nosso alguém ficaria frustrado, sem nenhum resultado. Porque ele incluiu a preposi??o do que n?o faz parte do trecho contido no parágrafo (nele temos um no).
Agora, se nosso conteúdo está indexado para busca textual, a mesma consulta viraria:
SELECT conteudo FROM artigos WHERE conteudo_fts @@ to_tsquery('evas?o escolar do brasil');
E nesse caso, o alguém ficaria feliz com o resultado.
Talvez você esteja perguntando “por que ele n?o usa Elasticsearch/Sphinx?”. A pergunta é justa e a resposta t?o quanto: pra n?o complicar antes de precisar complicar. Ambos os projetos s?o excelentes e oferecem recursos avan?ados/otimizados para busca textual. Mas se sua aplica??o já utiliza Postgres e você n?o tem bilh?es de registros para indexar, n?o existe raz?o para otimizar prematuramente.
Vale mencionar ainda que em 2019 o Ifood apresentou sua migra??o da busca do aplicativo para usar esse mesmo mecanismo.
Agora que a gente viu uma vantagem em usar busca textual, precisamos aprender como realmente usar.
Mas vou deixar isso para o próximo artigo porque está ficando muito longo.
Antes de finalizar, só quero deixar aqui que o objetivo é ter mais 2 partes nessa sequência de posts. No último vou explicar como utilizar (ou usar como inspira??o) um plugin que desenvolvi para CakePHP que adiciona ao framework suporte a busca textual do Postgres: o Autopage\PgSearch.
Atualizado em 19/10/2021: aplicada a corre??o na query de exemplo conforme o apontamento feito pelo JC Bombardelli nos comentários. Muito obrigado.
Recentemente o core do PHP passou por uma situa??o, no mínimo, inusitada: 2 commits foram feitos em nome de personalidades da comunidade. Isso é possível porque quando registramos uma altera??o no git através de um commit, ele registra os dados que queremos que sejam registrados, incluindo o usuário e email que você disse ter (git config user.name "Cauan" e git config user.email "[email protected]"). Veja que você n?o precisa validar email nem nada.
Aplica??es que hospedam projetos em git geralmente pedem pra gente validar o email usado nos commits, assim s?o capazes de inferir que aquele código enviado é de quem disse ter enviado. Mas é apenas uma associa??o “lógica”. O commit diz que foi feito por fulana, e a fulana é uma usuária válida aqui no Github/Gitlab, ent?o vou exibir a foto e nome dessa fulana aqui na interface.
Se o git n?o garante que quem fez uma altera??o é quem diz ser, como a gente pode garantir? Assinando nossas mudan?as com uma chave criptográfica.
Felizmente o git permite que a gente especifique uma chave privada configurada no GPG e cuida de todo o processo de assinar nossas mudan?as. Na outra ponta, nós compartilhamos a chave pública com o Github/Gitlab para que valide o commit assinado e ent?o possam garantir que aquele código foi feito por quem diz ter feito.
O pessoal do PHP preparou um guia bem completo sobre como configurar o GPG e git para que o incidente n?o se repita. Vou colocar aqui o resumo do resumo para quem tiver alguma dificuldade com inglês, mas se tiver alguma dúvida, pode usar os comentários.
Aten??o: todos os comando devem ser executados com seu usuário normal. Quando necessário, o sudo vai estar explicitado.
O símbolo $ n?o faz parte do comando, é só pra diferenciar o que é um comando e o que é uma saída de comando. Sempre que tiver o $, você pode copiar o que está na frente e executar no seu terminal.
Primeiro, instale o GPG:
Sistema
Comando(s)
macOS com Homebrew
brew install gpg
Ubuntu, Debian, Mint, Kali
sudo apt install gnupg
CentOS, Fedora, RHEL
sudo yum install gnupg
Se você usa Zsh como shell, você precisa configurar um tty para que funcione corretamente os comandos interativos com o GPG:
Por último, precisamos informar ao Github/Gitlab nossa chave, para que ele possa verificar o autor das mudan?as:
$ gpg --armor --export "${GPG_KEYID}" | pbcopy
pbcopy é um comando do macOS para jogar informa??o na área de transferência (equivalente a um Ctrl+C). Substitua essa parte pelo comando equivalente no seu ambiente se for outro, ou simplesmente retire o trecho | pbcopy e a chave será exibe no terminal, daí basta selecionar e copiar.
Feito isso, todos os seus novos commits deve ser assinados e se tudo ocorreu bem, aparecer?o como verificados nos Git da vida:
Atualizado em 13/04/2021: eu segui esses passos do artigo original em um macOS sem o gpg previamente instalado e com o git relativamente atualizado. Caso seu ambiente já tenha gpg ou a vers?o do git seja muito antiga, verifique a seguinte resposta no StackOverflow. Agradecimento ao Elton Minetto pela dica nos comentários.
Atualizado em 01/05/2021: como alertou nos comentários o Adjamilton, depois da gente atualizar o conteúdo do arquivo .zshrc precisamos recarregar seus valores usando o comando source "${HOME}"/.zshrc para que as mudan?as surtam efeito. O trecho já está atualizado.
Quem utiliza o Macbook Air sabe como é (ou melhor, era) um bom custo-benefício para desenvolvimento e trabalho de escritório. A CPU é razoável, a bateria é excelente, a quantidade de memória é aceitável e o armazenamento é rápido.
Seria perfeito se pudéssemos melhorar a memória RAM e o armazenamento com o tempo. Infelizmente, só o segundo é possível. E n?o é barato.
Depois de pesquisar por muito tempo e a firma patrocinar o upgrade, estou redigindo isso após uma bem sucedida cirurgia de substitui??o do SSD PCI-e de 120GB por um de 480GB e o dobro da taxa de escrita/leitura.
Depois de ter o pendrive bootável, fa?a a troca do SSD (n?o tem mistério, só desparafusar, desencaixar, colocar o novo, parafusar novamente), insira o pendrive na porta USB e ligue o Macbook Air.
Embora tenha havido um grande esfor?o em manter o máximo de compatibilidade entre as duas recentes vers?es do framework, ao migrar uma aplica??o de complexidade razoável encontrei várias dificuldades n?o t?o bem documentadas.
Pra me poupar passar por esses problemas no futuro, e talvez te poupar de dor de cabe?a também, aqui vai um resumo do que observar:
Rotas e links: revisite todas as suas rotas e cria??o de links/redirects. Nomes de prefixos, plugins e controladores devem ser escritos como s?o, sem altera??o para caixa baixa (lowercase) e underscore. O que antes seria my_products agora deve ser MyProducts, como no nome da classe MyProductsController. No caso dos links, se antes ao usar o UrlHelper n?o havia problema de uma rota n?o ter sido mapeada, agora você terá uma exce??o.
Mocks: com a nova vers?o, veio também o uso do phpunit 8.5, que por sua vez, introduz algumas quebras de compatibilidade. Isso afeta em especial a cria??o de mocks em modelos ligados a behaviors (se você tentar usar o mock em um método do behavior). No pull-request que abri para o futuro CakePHP 4.1, é resolvida o problema de warnings gerados, porém, em alguns testes meus, é obrigatório incluir algum método concreto da Table/Model que está sendo mockada junto com o método de behavior na lista de métodos no mock – caso contrário, o warning volta a aparecer (esse problema acontecia por uma falha no pull-request que criei, mas fiz um segundo com uma implementa??o melhor e n?o há mais necessidade disso).
Tests: se em algum teste você faz um $this->loadPlugin('MyPlugin'); no seu setUp(), se esse plugin possuir rotas, as rotas da sua aplica??o n?o ser?o carregadas. Houve uma mudan?a na verifica??o de inicializa??o das rotas e necessidade de carregamento – antes, havia uma variável de controle que só era definida após carregar as rotas da aplica??o, agora, só é checado se já existe alguma rota declarada, e se houver, sup?e-se que todo procedimento já foi realizado com sucesso.
Destes problemas, o mais trabalhoso de corrigir é o com as rotas/links – os outros três a dificuldade foi realmente entender o que estava acontecendo.