A versão 2.4.0 do MongoDB vai ser lançada em breve e terá suporte a busca textual (Full Text Search – FTS) nativa! Esse recurso estava sendo solicitado desde as primeiras versões do MongoDB, consta no ticket SERVER-380 do JIRA, aberto em 2009.
Esse recurso permite realizar busca textual simples e mais complexas, incluindo uso de stemming, stopwords, negação, busca por frases e suporte a multi idiomas, incluindo o português. Atenção, esse recurso ainda está em desenvolvimento, não use em produção!
Eu fiz um teste, utilizando a versão 2.4.0-rc1 que foi lançada dia 25/02/2013 e pode ser encontrada na página de download do MongoDB.
Como habilitar a busca textual
Se você estiver usando a versão 2.4.0-rc ou superior, poderá habilitar o suporte a busca textual com o seguinte comando:
$ ./mongod --setParameter textSearchEnabled=true
Com o servidor sendo executado com o comando acima, os exemplos a seguir foram executados dentro do shell do próprio MongoDB.
O comando abaixo cria o índice no banco para a língua portuguesa:
> db.teste.ensureIndex( {txt: "text"}, {default_language: "portuguese"} )
Para verificar se o índice foi criado com sucesso, basta rodar o comando:
> db.teste.getIndices()
A saída deverá ser algo do tipo:
> db.teste.getIndices() [ { "v" : 1, "key" : { "_id" : 1 }, "ns" : "test.teste", "name" : "_id_" }, { "v" : 1, "key" : { "_fts" : "text", "_ftsx" : 1 }, "ns" : "test.teste", "name" : "txt_text", "default_language" : "portuguese", "weights" : { "txt" : 1 }, "language_override" : "language", "textIndexVersion" : 1 } ]
Repare que na linha 19, o parâmetro “default_language” confirma que o índice foi criado respeitando os padrões do idioma português.
Agora é hora de indexar alguma coisa:
> db.teste.insert( {txt: "O rato roeu a roupa do rei de Roma"} )
Abaixo, um exemplo da busca, a palavra pesquisada é apenas “roma“:
> db.teste.runCommand( "text", { search : "roma"}) { "queryDebugString" : "rom||||||", "language" : "portuguese", "results" : [ { "score" : 0.6, "obj" : { "_id" : ObjectId("512e28809d5aac13c2980cc9"), "txt" : "O rato roeu a roupa do rei de Roma" } } ], "stats" : { "nscanned" : 1, "nscannedObjects" : 0, "n" : 1, "nfound" : 1, "timeMicros" : 95 }, "ok" : 1 }
Repare que o registro com o termo “roma” foi encontrado, junto com seu score.
Podemos fazer uma brincadeira para testar o stemming, vou realizar uma busca com a frase “o rato roeu a roupa da rainha de roma”. Troquei o termo “rei” por “rainha”, vejam:
> db.teste.runCommand( "text", { search : "o rato roeu a roupa da rainha de roma"}) { "queryDebugString" : "rainh|rat|roeu|rom|roup||||||", "language" : "portuguese", "results" : [ { "score" : 5.4, "obj" : { "_id" : ObjectId("512e28809d5aac13c2980cc9"), "txt" : "O rato roeu a roupa do rei de Roma" } } ], "stats" : { "nscanned" : 4, "nscannedObjects" : 0, "n" : 1, "nfound" : 1, "timeMicros" : 221 }, "ok" : 1 }
O score dessa busca ficou mais alto que o da primeira. Quanto mais baixo o score, maior é a precisão da busca. Mesmo assim a busca retornou um resultado válido, encontrando o registro que contém esse termo.
Vamos agora adicionar mais algumas frases:
> db.teste.insert( {txt: "Batatinha quando nasce, espalha rama pelo chão"} ) > db.teste.insert( {txt: "Escolhe um trabalho de que gostes, e não terás que trabalhar nem um dia na tua vida."} ) > db.teste.insert( {txt: "Enquanto houver vontade de lutar, haverá esperança de vencer"} ) > db.teste.insert( {txt: "A vida só pode ser compreendida, olhando-se para trás; mas só pode ser vivida, olhando-se para frente"} )
Indexei algumas frases aleatórias encontradas na internet, sendo que duas frases possuem a palavra “vida”.
Se fizermos uma busca com o termo “vida”, teremos o seguinte resultado:
> db.teste.runCommand( "text", { search : "vida"}) { "queryDebugString" : "vid||||||", "language" : "portuguese", "results" : [ { "score" : 0.5625, "obj" : { "_id" : ObjectId("512e2ad69d5aac13c2980ccb"), "txt" : "Escolhe um trabalho de que gostes, e não terás que trabalhar nem um dia na tua vida." } }, { "score" : 0.5454545454545454, "obj" : { "_id" : ObjectId("512e2b6c9d5aac13c2980ccd"), "txt" : "A vida só pode ser compreendida, olhando-se para trás; mas só pode ser vivida, olhando-se para frente" } } ], "stats" : { "nscanned" : 2, "nscannedObjects" : 0, "n" : 2, "nfound" : 2, "timeMicros" : 113 }, "ok" : 1 }
Agora, vejam o stemming em ação, vou fazer uma busca com o termo “vencendo”. Repare que essa palavra não está indexada, mas temos um registro com a palavra “vencer”, que é da mesma raiz da busca em questão.
> db.teste.runCommand( "text", { search : "vencendo"}) { "queryDebugString" : "venc||||||", "language" : "portuguese", "results" : [ { "score" : 0.5714285714285714, "obj" : { "_id" : ObjectId("512e2b2d9d5aac13c2980ccc"), "txt" : "Enquanto houver vontade de lutar, haverá esperança de vencer" } } ], "stats" : { "nscanned" : 1, "nscannedObjects" : 0, "n" : 1, "nfound" : 1, "timeMicros" : 98 }, "ok" : 1 }
Outro exemplo, com o verbo “nascer”, que também não existe, mas uma palavra com a mesma raiz foi indexada: “nasce”:
> db.teste.runCommand( "text", { search : "nascer"}) { "queryDebugString" : "nasc||||||", "language" : "portuguese", "results" : [ { "score" : 0.5714285714285714, "obj" : { "_id" : ObjectId("512e2a929d5aac13c2980cca"), "txt" : "Batatinha quando nasce, espalha rama pelo chão" } } ], "stats" : { "nscanned" : 1, "nscannedObjects" : 0, "n" : 1, "nfound" : 1, "timeMicros" : 96 }, "ok" : 1 }
Para finalizar, vamos fazer uma busca com negação. Nesse caso quero todos os registros que contenham a palavra “vida” mas que não tenha a palavra “escolhe”, fazemos assim:
> db.teste.runCommand( "text", { search : "vida -escolhe"}) { "queryDebugString" : "vid||escolh||||", "language" : "portuguese", "results" : [ { "score" : 0.5454545454545454, "obj" : { "_id" : ObjectId("512e2b6c9d5aac13c2980ccd"), "txt" : "A vida só pode ser compreendida, olhando-se para trás; mas só pode ser vivida, olhando-se para frente" } } ], "stats" : { "nscanned" : 2, "nscannedObjects" : 0, "n" : 1, "nfound" : 1, "timeMicros" : 192 }, "ok" : 1 }
O registro que tinha o termo “escolhe” foi ignorado do resultado, como esperado.
São exemplos simples, mas mostram que a busca textual do MongoDB possui um grande potencial.
Estou fazendo alguns testes de realizar buscas dentro de uma aplicação desenvolvida em Python (utilizando o PyMongo). Assim que tiver material suficiente, vou escrever outro post.