[gnome-documents] query, search: Add full-text search



commit ebcd5b6d3608a9f497c092ad5560dd05a05752f8
Author: Rafael Fonseca <r4f4rfs gmail com>
Date:   Wed Jan 6 15:26:05 2016 +0100

    query, search: Add full-text search
    
    https://bugzilla.gnome.org/show_bug.cgi?id=668728

 src/query.js  |   64 +++++++++++++++++++++++++++++++++++++++-----------------
 src/search.js |   32 +++++++++++++++++++++++++--
 2 files changed, 73 insertions(+), 23 deletions(-)
---
diff --git a/src/query.js b/src/query.js
index 3063871..91fea21 100644
--- a/src/query.js
+++ b/src/query.js
@@ -64,10 +64,11 @@ const QueryBuilder = new Lang.Class({
                  activeSource: this._context.sourceManager.getActiveItem() };
     },
 
-    _buildFilterString: function(currentType, flags) {
+    _buildFilterString: function(currentType, flags, isFtsEnabled) {
         let filters = [];
 
-        filters.push(this._context.searchMatchManager.getFilter(flags));
+        if (!isFtsEnabled)
+            filters.push(this._context.searchMatchManager.getFilter(flags));
         filters.push(this._context.sourceManager.getFilter(flags));
         filters.push(this._context.searchCategoryManager.getFilter(flags));
 
@@ -86,6 +87,27 @@ const QueryBuilder = new Lang.Class({
         return sparql;
     },
 
+    _addWhereClauses: function(partsList, global, flags, searchTypes, ftsQuery) {
+        // build an array of WHERE clauses; each clause maps to one
+        // type of resource we're looking for.
+        searchTypes.forEach(Lang.bind(this,
+            function(currentType) {
+                let part = '{ ' + currentType.getWhere() + ftsQuery;
+                part += this._buildOptional();
+
+                if ((flags & QueryFlags.UNFILTERED) == 0) {
+                    if (global)
+                        part += this._context.searchCategoryManager.getWhere() +
+                                this._context.documentManager.getWhere();
+
+                    part += this._buildFilterString(currentType, flags, ftsQuery.length > 0);
+                }
+
+                part += ' }';
+                partsList.push(part);
+            }));
+    },
+
     _buildWhere: function(global, flags) {
         let whereSparql = 'WHERE { ';
         let whereParts = [];
@@ -100,23 +122,23 @@ const QueryBuilder = new Lang.Class({
         else
             searchTypes = this._context.searchTypeManager.getAllTypes();
 
-        // build an array of WHERE clauses; each clause maps to one
-        // type of resource we're looking for.
-        searchTypes.forEach(Lang.bind(this,
-            function(currentType) {
-                let part = '{ ' + currentType.getWhere() + this._buildOptional();
+        let matchItem = this._context.searchMatchManager.getActiveItem();
 
-                if ((flags & QueryFlags.UNFILTERED) == 0) {
-                    if (global)
-                        part += this._context.searchCategoryManager.getWhere() +
-                                this._context.documentManager.getWhere();
+        // Skip matchTypes when only doing fts
+        if (matchItem.id != Search.SearchMatchStock.CONTENT) {
+            this._addWhereClauses(whereParts, global, flags, searchTypes, '');
+        }
 
-                    part += this._buildFilterString(currentType, flags);
-                }
+        if (flags & QueryFlags.SEARCH) {
+            let ftsWhere = this._context.searchMatchManager.getWhere();
 
-                part += ' }';
-                whereParts.push(part);
-            }));
+            // Need to repeat the searchTypes part to also include fts
+            // Note that the filter string needs to be slightly different for the
+            // fts to work properly
+            if (ftsWhere.length || matchItem.id == Search.SearchMatchStock.CONTENT) {
+                this._addWhereClauses(whereParts, global, flags, searchTypes, ftsWhere);
+            }
+        }
 
         // put all the clauses in an UNION
         whereSparql += whereParts.join(' UNION ');
@@ -134,6 +156,8 @@ const QueryBuilder = new Lang.Class({
             let offset = 0;
             let step = Search.OFFSET_STEP;
 
+            tailSparql += 'ORDER BY DESC(fts:rank(?urn)) ';
+
             if (offsetController) {
                 offset = offsetController.getOffset();
                 step = offsetController.getOffsetStep();
@@ -141,16 +165,16 @@ const QueryBuilder = new Lang.Class({
 
             switch (sortBy) {
             case Gd.MainColumns.PRIMARY_TEXT:
-                tailSparql += 'ORDER BY ASC(?title) ASC(?filename)';
+                tailSparql += 'ASC(?title) ASC(?filename)';
                 break;
             case Gd.MainColumns.SECONDARY_TEXT:
-                tailSparql += 'ORDER BY ASC(?author)';
+                tailSparql += 'ASC(?author)';
                 break;
             case Gd.MainColumns.MTIME:
-                tailSparql += 'ORDER BY DESC(?mtime)';
+                tailSparql += 'DESC(?mtime)';
                 break;
             default:
-                tailSparql += 'ORDER BY DESC(?mtime)';
+                tailSparql += 'DESC(?mtime)';
                 break;
             }
 
diff --git a/src/search.js b/src/search.js
index 4a672d0..c189ffa 100644
--- a/src/search.js
+++ b/src/search.js
@@ -281,7 +281,8 @@ const SearchTypeManager = new Lang.Class({
 const SearchMatchStock = {
     ALL: 'all',
     TITLE: 'title',
-    AUTHOR: 'author'
+    AUTHOR: 'author',
+    CONTENT: 'content'
 };
 
 const SearchMatch = new Lang.Class({
@@ -316,6 +317,8 @@ const SearchMatch = new Lang.Class({
                     '(tracker:case-fold' +
                     '(tracker:coalesce(nco:fullname(?creator), nco:fullname(?publisher))), ' +
                     '"%s")').format(this._term, this._term);
+        if (this.id == SearchMatchStock.CONTENT)
+            return '(false)';
         return '';
     }
 });
@@ -325,8 +328,9 @@ const SearchMatchManager = new Lang.Class({
     Extends: Manager.BaseManager,
 
     _init: function(context) {
-        // Translators: this is a verb that refers to "All", "Title" and "Author",
-        // as in "Match All", "Match Title" and "Match Author"
+        // Translators: this is a verb that refers to "All", "Title", "Author",
+        // and "Content" as in "Match All", "Match Title", "Match Author", and
+        // "Match Content"
         this.parent(_("Match"), 'search-match', context);
 
         this.addItem(new SearchMatch({ id: SearchMatchStock.ALL,
@@ -337,10 +341,32 @@ const SearchMatchManager = new Lang.Class({
         this.addItem(new SearchMatch({ id: SearchMatchStock.AUTHOR,
         //Translators: "Author" refers to "Match Author" when searching
                                        name: C_("Search Filter", "Author") }));
+        this.addItem(new SearchMatch({ id: SearchMatchStock.CONTENT,
+        //Translators: "Content" refers to "Match Content" when searching
+                                       name: C_("Search Filter", "Content") }));
 
         this.setActiveItemById(SearchMatchStock.ALL);
     },
 
+    getWhere: function() {
+        let item = this.getActiveItem();
+        if (item.id != SearchMatchStock.ALL &&
+            item.id != SearchMatchStock.CONTENT)
+            return '';
+
+        let terms = this.context.searchController.getTerms();
+        if (!terms.length)
+            return '';
+
+        let ftsterms = [];
+        for (let i = 0; i < terms.length; i++) {
+            if (terms[i].length > 0)
+                ftsterms.push(terms[i] + '*');
+        }
+
+        return '?urn fts:match \'%s\' . '.format(ftsterms.join(' '));
+    },
+
     getFilter: function(flags) {
         if ((flags & Query.QueryFlags.SEARCH) == 0)
             return '(true)';


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]