MongoDB - Search with special characters

MongoDB è un database non relazionale a schema libero, molto usato per storicizzare big data, performante e scalabile.

Le table presenti nei database relazionali, qui prendono il nome di collection; i document sono il contenuto delle collection.

È possibile storicizzare qualsiasi tipo di dato all'interno di un document di un database MongoDB, ma oggi si soffermeremo sul tipo più semplice: il tipo stringa.

Ipotizziamo di avere una collection author che contenga i seguenti valori:

> db.authors.find().pretty()
{
        "_id" : ObjectId("54b7d8b78c1d469a5fcab81c"),
        "name" : "Joao",
        "surname" : "Guimarães Rosa"
}
{
        "_id" : ObjectId("54b7d910c27fa617ff510e45"),
        "name" : "Jo",
        "surname" : "Nesbø"
}
{
        "_id" : ObjectId("54b7d96cc27fa617ff510e46"),
        "name" : "Håkan",
        "surname" : "Nesser"
}
{
        "_id" : ObjectId("54b7d9a0c27fa617ff510e47"),
        "name" : "Åsa",
        "surname" : "Larsson"
}
{
        "_id" : ObjectId("54b7d9e1c27fa617ff510e48"),
        "name" : "Camilla",
        "surname" : "Läckberg"
}
{
        "_id" : ObjectId("54b7da0fc27fa617ff510e49"),
        "name" : "James",
        "surname" : "Joyce"
}

Se volessimo eseguire una ricerca che ritorni il documento relativo a Jo Nesbø, dovremmo scrivere una query del genere:

> db.authors.find({surname:/nesbø/i}).pretty()
{
        "_id" : ObjectId("54b7d910c27fa617ff510e45"),
        "name" : "Jo",
        "surname" : "Nesbø"
}

NB: /nesbø/ indica che il testo è ricercato come regular expression, il comportamento è identico alla like in SQL. Il flag i indica che l'espressione è case insensitive.

Una cosa analoga avverrebbe per quasi tutti gli altri autori presenti nella nostra collaction.
Questo vuol dire che se volessimo cercare la stringa nesbo, non troveremmo risultati.

Esistono numerose soluzioni per risolvere questa problematica:

  • introdurre un campo aggiuntivo al nostro documento che contenga il valore ricercabile pulito da caratteri speciali;
  • utilizzare un sistema di ricerca indicizzato e avanzato (Elasticsearch, Lucene, ecc..);
  • usare una regular expression per effettuare la ricerca;

La soluzione che ho scelto, che si basa sulla semplicità e velocità di sviluppo, riguarda l'utilizzo appunto di una regular expression per ricercare il valore nella collection.
Ho creato un piccolo metodo che esegue il parsing della stringa e esegue la sostituzione dei caratteri con un'espressione regolare, di seguito il codice:

function StringUtil(){
    var self = this;
    self.ACCENT_STRINGS = 'ŠŒŽšœžŸ¥µÀÁÂÃÄÅÆÇÈÉÊËẼÌÍÎÏĨÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëẽìíîïĩðñòóôõöøùúûüýÿ';
    self.NO_ACCENT_STRINGS = 'SOZsozYYuAAAAAAACEEEEEIIIIIDNOOOOOOUUUUYsaaaaaaaceeeeeiiiiionoooooouuuuyy';

    self.accentToRegex = function(text){
        var from = self.ACCENT_STRINGS.split(''),
            to = self.NO_ACCENT_STRINGS.toLowerCase().split(''),
            text = text,
            regex = [],
            value = '';

        for(var key in to){
            if (to.hasOwnProperty(key)) {
                value = to[key];
                if(typeof  regex[value] != 'undefined'){
                    regex[value] += from[key];
                }
                else{
                    regex[value] = value;
                }
            }
        }

        for(var key in regex){
            if (regex.hasOwnProperty(key)) {
                var re = new RegExp(key,"g");
               text = text.replace(re, "["+regex[key]+"]");
            }
        }

        return text;
    };
};

Quindi la nostra chiamata tramite Node.js al database di MongoDB.

Come operazione preliminare occorre installare il driver Node.js di MongoDB con il comando npm install mongodb.

var MongoClient = require('mongodb').MongoClient,
    strUtil = new StringUtil();

MongoClient.connect('mongodb://localhost:27017/test', function(err, db) {
    if(err) throw err;

    var text = "nesbo";
    var reg = new RegExp(strUtil.accentToRegex(text), "i");

    db.collection('authors').find({surname:reg}).toArray(function(err, docs) {
        console.dir(docs);
    });

});

Il testo decodificato avrà il formato [nñ][eèéêëẽ][sŠß]b[oðñòóôõöø], che permettera a MongoDB di eseguire una query tramite una regular expression e ricercare i caratteri speciali.

Il nostro autore, risultato della ricerca, verrà infine mostrato in output nel nostro terminale.