private static IList<int> SearchCore(SearchFilter searchFilter, out int totalHits)
        {
            if (!Directory.Exists(LuceneCommon.IndexDirectory))
            {
                totalHits = 0;
                return new int[0];
            }

            SortField sortField = GetSortField(searchFilter);
            int numRecords = searchFilter.Skip + searchFilter.Take;

            using (var directory = new LuceneFileSystem(LuceneCommon.IndexDirectory))
            {
                var searcher = new IndexSearcher(directory, readOnly: true);
                var query = ParseQuery(searchFilter);

                var filterTerm = searchFilter.IncludePrerelease ? "IsLatest" : "IsLatestStable";
                var termQuery = new TermQuery(new Term(filterTerm, Boolean.TrueString));
                Filter filter = new QueryWrapperFilter(termQuery);
                

                var results = searcher.Search(query, filter: filter, n: numRecords, sort: new Sort(sortField));
                var keys = results.scoreDocs.Skip(searchFilter.Skip)
                                            .Select(c => ParseKey(searcher.Doc(c.doc).Get("Key")))
                                            .ToList();

                totalHits = results.totalHits;
                searcher.Close();
                return keys;
            }
        }
        public static SearchFilter GetSearchFilter(string q, string sortOrder, int page, bool includePrerelease)
        {
            var searchFilter = new SearchFilter
            {
                SearchTerm = q,
                Skip = (page - 1) * Constants.DefaultPackageListPageSize, // pages are 1-based. 
                Take = Constants.DefaultPackageListPageSize,
                IncludePrerelease = includePrerelease
            };

            switch (sortOrder)
            {
                case Constants.AlphabeticSortOrder:
                    searchFilter.SortProperty = SortProperty.DisplayName;
                    searchFilter.SortDirection = SortDirection.Ascending;
                    break;

                case Constants.RecentSortOrder:
                    searchFilter.SortProperty = SortProperty.Recent;
                    break;

                case Constants.PopularitySortOrder:
                    searchFilter.SortProperty = SortProperty.DownloadCount;
                    break;

                default:
                    searchFilter.SortProperty = SortProperty.Relevance;
                    break;
            }

            return searchFilter;
        }
        private IQueryable<Package> SearchCore(SearchFilter searchFilter, out int totalHits)
        {
            int numRecords = searchFilter.Skip + searchFilter.Take;

            var searcher = new IndexSearcher(_directory, readOnly: true);
            var query = ParseQuery(searchFilter);

            // IF searching by relevance, boost scores by download count.
            if (searchFilter.SortProperty == SortProperty.Relevance)
            {
                var downloadCountBooster = new FieldScoreQuery("DownloadCount", FieldScoreQuery.Type.INT);
                query = new CustomScoreQuery(query, downloadCountBooster);
            }

            var filterTerm = searchFilter.IncludePrerelease ? "IsLatest" : "IsLatestStable";
            var termQuery = new TermQuery(new Term(filterTerm, Boolean.TrueString));
            var filter = new QueryWrapperFilter(termQuery);
            var results = searcher.Search(query, filter: filter, n: numRecords, sort: new Sort(GetSortField(searchFilter)));
            totalHits = results.totalHits;

            if (results.totalHits == 0 || searchFilter.CountOnly)
            {
                return Enumerable.Empty<Package>().AsQueryable();
            }

            var packages = results.scoreDocs
                                  .Skip(searchFilter.Skip)
                                  .Select(sd => PackageFromDoc(searcher.Doc(sd.doc)))
                                  .ToList();
            return packages.AsQueryable();
        }
        public static SearchFilter GetSearchFilter(string q, int page, string sortOrder, string context)
        {
            var searchFilter = new SearchFilter(context)
            {
                SearchTerm = q,
                Skip = (page - 1) * Constants.DefaultPackageListPageSize, // pages are 1-based. 
                Take = Constants.DefaultPackageListPageSize,
                IncludePrerelease = true
            };

            switch (sortOrder)
            {
                case Constants.AlphabeticSortOrder:
                    searchFilter.SortOrder = SortOrder.TitleAscending;
                    break;

                case Constants.RecentSortOrder:
                    searchFilter.SortOrder = SortOrder.Published;
                    break;

                default:
                    searchFilter.SortOrder = SortOrder.Relevance;
                    break;
            }

            return searchFilter;
        }
 private static SortField GetSortField(SearchFilter searchFilter)
 {
     switch (searchFilter.SortProperty)
     {
         case SortProperty.DisplayName:
             return new SortField("DisplayName", SortField.STRING, reverse: searchFilter.SortDirection == SortDirection.Descending);
         case SortProperty.DownloadCount:
             return new SortField("DownloadCount", SortField.INT, reverse: true);
         case SortProperty.Recent:
             return new SortField("PublishedDate", SortField.LONG, reverse: true);
     }
     return SortField.FIELD_SCORE;
 }
        public static async Task<IQueryable<Package>> GetResultsFromSearchService(ISearchService searchService, SearchFilter searchFilter)
        {
            var result = await searchService.Search(searchFilter);

            // For count queries, we can ask the SearchService to not filter the source results. This would avoid hitting the database and consequently make
            // it very fast.
            if (searchFilter.CountOnly)
            {
                // At this point, we already know what the total count is. We can have it return this value very quickly without doing any SQL.
                return result.Data.InterceptWith(new CountInterceptor(result.Hits));
            }

            // For relevance search, Lucene returns us a paged\sorted list. OData tries to apply default ordering and Take \ Skip on top of this.
            // We avoid it by yanking these expressions out of out the tree.
            return result.Data.InterceptWith(new DisregardODataInterceptor());
        }
        public IQueryable<Package> Search(IQueryable<Package> packages, SearchFilter searchFilter, out int totalHits)
        {
            if (packages == null)
            {
                throw new ArgumentNullException("packages");
            }

            if (searchFilter == null)
            {
                throw new ArgumentNullException("searchFilter");
            }

            if (String.IsNullOrEmpty(searchFilter.SearchTerm))
            {
                throw new ArgumentException("No term to search for.");
            }

            if (searchFilter.Skip < 0)
            {
                throw new ArgumentOutOfRangeException("skip");
            }

            if (searchFilter.Take < 0)
            {
                throw new ArgumentOutOfRangeException("take");
            }

            // For the given search term, find the keys that match.
            var keys = SearchCore(searchFilter);
            totalHits = keys.Count;
            if (keys.Count == 0 || searchFilter.CountOnly)
            {
                return Enumerable.Empty<Package>().AsQueryable();
            }

            // Query the source for each of the keys that need to be taken.
            var results = packages.Where(p => keys.Contains(p.Key));

            // When querying the database, these keys are returned in no particular order. We use the original order of queries
            // and retrieve each of the packages from the result in the same order.
            var lookup = results.ToDictionary(p => p.Key, p => p);

            return keys.Select(key => LookupPackage(lookup, key))
                       .Where(p => p != null)
                       .AsQueryable();
        }
        private SearchResults SearchCore(SearchFilter searchFilter)
        {
            // Get index timestamp
            DateTime timestamp = File.GetLastWriteTimeUtc(LuceneCommon.GetIndexMetadataPath());

            int numRecords = searchFilter.Skip + searchFilter.Take;

            var searcher = new IndexSearcher(_directory, readOnly: true);
            var query = ParseQuery(searchFilter);

            // IF searching by relevance, boost scores by download count.
            if (searchFilter.SortOrder == SortOrder.Relevance)
            {
                var downloadCountBooster = new FieldScoreQuery("DownloadCount", FieldScoreQuery.Type.INT);
                query = new CustomScoreQuery(query, downloadCountBooster);
            }

            var filterTerm = searchFilter.IncludePrerelease ? "IsLatest" : "IsLatestStable";
            Query filterQuery = new TermQuery(new Term(filterTerm, Boolean.TrueString));
            if (searchFilter.CuratedFeed != null)
            {
                var feedFilterQuery = new TermQuery(new Term("CuratedFeedKey", searchFilter.CuratedFeed.Key.ToString(CultureInfo.InvariantCulture)));
                BooleanQuery conjunctionQuery = new BooleanQuery();
                conjunctionQuery.Add(filterQuery, Occur.MUST);
                conjunctionQuery.Add(feedFilterQuery, Occur.MUST);
                filterQuery = conjunctionQuery;
            }

            Filter filter = new QueryWrapperFilter(filterQuery);
            var results = searcher.Search(query, filter: filter, n: numRecords, sort: new Sort(GetSortField(searchFilter)));
            
            if (results.TotalHits == 0 || searchFilter.CountOnly)
            {
                return new SearchResults(results.TotalHits, timestamp);
            }

            var packages = results.ScoreDocs
                                  .Skip(searchFilter.Skip)
                                  .Select(sd => PackageFromDoc(searcher.Doc(sd.Doc)))
                                  .ToList();
            return new SearchResults(
                results.TotalHits,
                timestamp,
                packages.AsQueryable());
        }
        public SearchResults Search(SearchFilter searchFilter)
        {
            if (searchFilter == null)
            {
                throw new ArgumentNullException("searchFilter");
            }

            if (searchFilter.Skip < 0)
            {
                throw new ArgumentOutOfRangeException("searchFilter");
            }

            if (searchFilter.Take < 0)
            {
                throw new ArgumentOutOfRangeException("searchFilter");
            }

            return SearchCore(searchFilter);
        }
        public Task<SearchResults> Search(SearchFilter searchFilter)
        {
            if (searchFilter == null)
            {
                throw new ArgumentNullException("searchFilter");
            }

            if (searchFilter.Skip < 0)
            {
                throw new ArgumentOutOfRangeException("searchFilter");
            }

            if (searchFilter.Take < 0)
            {
                throw new ArgumentOutOfRangeException("searchFilter");
            }

            return Task.FromResult(SearchCore(searchFilter));
        }
        public IQueryable<Package> Search(SearchFilter searchFilter, out int totalHits)
        {
            if (searchFilter == null)
            {
                throw new ArgumentNullException("searchFilter");
            }

            if (searchFilter.Skip < 0)
            {
                throw new ArgumentOutOfRangeException("searchFilter");
            }

            if (searchFilter.Take < 0)
            {
                throw new ArgumentOutOfRangeException("searchFilter");
            }

            return SearchCore(searchFilter, out totalHits);
        }
        private IQueryable<Package> SearchCore(SearchFilter searchFilter, out int totalHits)
        {
            int numRecords = searchFilter.Skip + searchFilter.Take;

            var searcher = new IndexSearcher(_directory, readOnly: true);
            var query = ParseQuery(searchFilter);

            // IF searching by relevance, boost scores by download count.
            if (searchFilter.SortProperty == SortProperty.Relevance)
            {
                var downloadCountBooster = new FieldScoreQuery("DownloadCount", FieldScoreQuery.Type.INT);
                query = new CustomScoreQuery(query, downloadCountBooster);
            }

            var filterTerm = searchFilter.IncludePrerelease ? "IsLatest" : "IsLatestStable";
            Query filterQuery = new TermQuery(new Term(filterTerm, Boolean.TrueString));
            if (searchFilter.CuratedFeedKey.HasValue)
            {
                var feedFilterQuery = new TermQuery(new Term("CuratedFeedKey", searchFilter.CuratedFeedKey.Value.ToString(CultureInfo.InvariantCulture)));
                BooleanQuery conjunctionQuery = new BooleanQuery();
                conjunctionQuery.Add(filterQuery, Occur.MUST);
                conjunctionQuery.Add(feedFilterQuery, Occur.MUST);
                filterQuery = conjunctionQuery;
            }

            Filter filter = new QueryWrapperFilter(filterQuery);
            var results = searcher.Search(query, filter: filter, n: numRecords, sort: new Sort(GetSortField(searchFilter)));
            totalHits = results.TotalHits;

            if (results.TotalHits == 0 || searchFilter.CountOnly)
            {
                return Enumerable.Empty<Package>().AsQueryable();
            }

            var packages = results.ScoreDocs
                                  .Skip(searchFilter.Skip)
                                  .Select(sd => PackageFromDoc(searcher.Doc(sd.Doc)))
                                  .ToList();
            return packages.AsQueryable();
        }
        private SearchResults SearchCore(SearchFilter searchFilter)
        {
            // Get index timestamp
            DateTime timestamp = File.GetLastWriteTimeUtc(LuceneCommon.IndexMetadataPath);

            int numRecords = searchFilter.Skip + searchFilter.Take;

            var searcher = new IndexSearcher(_directory, readOnly: true);
            var query = ParseQuery(searchFilter);

            // If searching by relevance, boost scores by download count.
            if (searchFilter.SortProperty == SortProperty.Relevance)
            {
                var downloadCountBooster = new FieldScoreQuery("DownloadCount", FieldScoreQuery.Type.INT);
                query = new CustomScoreQuery(query, downloadCountBooster);
            }

            var filterTerm = searchFilter.IncludePrerelease ? "IsLatest" : "IsLatestStable";
            Query filterQuery = new TermQuery(new Term(filterTerm, Boolean.TrueString));

            Filter filter = new QueryWrapperFilter(filterQuery);
            var results = searcher.Search(query, filter: filter, n: numRecords, sort: new Sort(GetSortField(searchFilter)));

            if (results.TotalHits == 0 || searchFilter.CountOnly)
            {
                return new SearchResults(results.TotalHits, timestamp);
            }

            var packages = results.ScoreDocs
                                  .Skip(searchFilter.Skip)
                                  .Select(sd => PackageFromDoc(searcher.Doc(sd.Doc)))
                                  .ToList();

            return new SearchResults(
                results.TotalHits,
                timestamp,
                packages.AsQueryable());
        }
Exemple #14
0
        static void TestSearch(string query, string expectedPackageId, int maxExpectedPosition)
        {
            var searchFilter = new SearchFilter
            {
                Skip = 0,
                Take = maxExpectedPosition,
                SearchTerm = query,
            };

            int totalHits;
            var results = luceneSearchService.Search(searchFilter, out totalHits).ToList();

            Assert.NotEqual(0, results.Count);
            Assert.Contains(expectedPackageId, results.Select(p => p.PackageRegistration.Id), StringComparer.InvariantCultureIgnoreCase);
            Assert.True(results.Count <= maxExpectedPosition);
        }
        protected virtual IQueryable <Package> SearchCore(IQueryable <Package> packages, string searchTerm, string targetFramework, bool includePrerelease, SearchFilter searchFilter, bool useCache)
        {
            // we don't allow an empty search for all versions.
            if (searchFilter.FilterInvalidReason == SearchFilterInvalidReason.DueToAllVersionsRequested && string.IsNullOrWhiteSpace(searchTerm))
            {
                searchFilter.IsValid = true;
            }

            // When the SearchFilter is valid, we can use Lucene
            // We can only use Lucene if the client queries for the latest versions (IsLatest \ IsLatestStable) versions of a package
            // and specific sort orders that we have in the index.
            if (searchFilter.IsValid)
            {
                searchFilter.SearchTerm        = searchTerm;
                searchFilter.IncludePrerelease = includePrerelease;

                return(GetResultsFromSearchService(searchFilter));
            }

            var invalidSearchFilterMessage = "Search filter was invalid ('{0}') Raw Url: '{1}' .".format_with(searchFilter.FilterInvalidReason, HttpContext.Request.RawUrl);

            Trace.WriteLine(invalidSearchFilterMessage);
            // raise this as an exception for even better visibility
            ErrorSignal.FromCurrentContext().Raise(new SystemException(invalidSearchFilterMessage));

            if (!includePrerelease)
            {
                packages = packages.Where(p => !p.IsPrerelease);
            }

            if (useCache)
            {
                return(NugetGallery.Cache.Get(
                           string.Format(
                               "V2Feed-Search-{0}-{1}-{2}",
                               searchTerm.to_lower(),
                               targetFramework.to_lower(),
                               includePrerelease
                               ),
                           DateTime.UtcNow.AddSeconds(3600),
                           () =>
                {
                    Trace.WriteLine("Database search results hit for API (caching results) Search term: '{0}' (prerelease? {1}).".format_with(searchTerm, includePrerelease));
                    return packages.Search(searchTerm, lowerCaseExpression: false).ToList();
                }).AsQueryable());
            }

            Trace.WriteLine("Database search results hit for API (not caching results) Search term: '{0}' (prerelease? {1}).".format_with(searchTerm, includePrerelease));


            if (searchFilter.Skip != 0)
            {
                packages = packages.Skip(searchFilter.Skip);
            }

            packages = packages.Take(MaxPageSize);
            if (searchFilter.Take != 0)
            {
                packages = packages.Take(searchFilter.Take);
            }

            switch (searchFilter.SortProperty)
            {
            case SortProperty.Relevance:
                //do not change the search order
                break;

            case SortProperty.DownloadCount:
                packages = packages.OrderByDescending(p => p.PackageRegistration.DownloadCount);
                break;

            case SortProperty.DisplayName:
                packages = searchFilter.SortDirection == SortDirection.Ascending ? packages.OrderBy(p => p.Title) : packages.OrderByDescending(p => p.Title);
                break;

            case SortProperty.Recent:
                packages = packages.OrderByDescending(p => p.Published);
                break;

            default:
                //do not change the search order
                break;
            }

            return(packages.Search(searchTerm, lowerCaseExpression: false));
        }
        private static Query ParseQuery(SearchFilter searchFilter)
        {
            // 1. parse the query into field clauses and general terms
            // We imagine that mostly, field clauses are meant to 'filter' results found searching for general terms.
            // The resulting clause collections may be empty.
            var queryParser = new NuGetQueryParser();
            var clauses = queryParser.Parse(searchFilter.SearchTerm).Select(StandardizeSearchTerms).ToList();
            var fieldSpecificTerms = clauses.Where(a => a.Field != null);
            var generalTerms = clauses.Where(a => a.Field == null);

            // Convert terms to appropriate Lucene Query objects
            var analyzer = new PerFieldAnalyzer();
            var fieldSpecificQueries = fieldSpecificTerms
                .Select(c => AnalysisHelper.GetFieldQuery(analyzer, c.Field, c.TermOrPhrase))
                .Where(q => !IsDegenerateQuery(q))
                .ToList();

            var generalQueries = generalTerms
                .Select(c => AnalysisHelper.GetMultiFieldQuery(analyzer, Fields, c.TermOrPhrase))
                .Where(q => !IsDegenerateQuery(q))
                .ToList();

            if (fieldSpecificQueries.Count == 0 &&
                generalQueries.Count == 0)
            {
                return new MatchAllDocsQuery();
            }

            // At this point we try to detect user intent...
            // a) General search? [foo bar]
            // b) Id-targeted search? [id:Foo bar]
            // c)  Other Field-targeted search? [author:Foo bar]
            bool doExactId = !fieldSpecificQueries.Any();
            Query generalQuery = BuildGeneralQuery(doExactId, searchFilter.SearchTerm, analyzer, generalTerms, generalQueries);

            // IF  field targeting is done, we should basically want to AND their field specific queries with all other query terms
            if (fieldSpecificQueries.Any())
            {
                var combinedQuery = new BooleanQuery();

                if (!IsDegenerateQuery(generalQuery))
                {
                    combinedQuery.Add(generalQuery, Occur.MUST);
                }

                foreach (var fieldQuery in fieldSpecificQueries)
                {
                    if (!IsDegenerateQuery(fieldQuery))
                    {
                        combinedQuery.Add(fieldQuery, Occur.MUST);
                    }
                }

                generalQuery = combinedQuery;
            }

            return generalQuery;
        }
        public IQueryable <V2FeedPackage> Search(string searchTerm, string targetFramework, bool includePrerelease)
        {
            var packages        = PackageRepo.GetAll().Where(p => p.StatusForDatabase == PackageStatusType.Submitted.GetDescriptionOrValue());
            var packageVersions = SearchCore(packages, searchTerm, targetFramework, includePrerelease, SearchFilter.Empty()).ToV2FeedPackageQuery(GetSiteRoot()).ToList();

            if (!includePrerelease)
            {
                return(packageVersions.Where(p => !p.IsPrerelease).AsQueryable());
            }

            return(packageVersions.AsQueryable());
        }
        protected virtual IQueryable <Package> SearchCore(IQueryable <Package> packages, string searchTerm, string targetFramework, bool includePrerelease, SearchFilter searchFilter)
        {
            // we don't allow an empty search for all versions.
            if (searchFilter.FilterInvalidReason == SearchFilterInvalidReason.DueToAllVersionsRequested && string.IsNullOrWhiteSpace(searchTerm))
            {
                searchFilter.IsValid = true;
            }

            // We can only use Lucene if the client queries for the latest versions (IsLatest \ IsLatestStable) versions of a package
            // and specific sort orders that we have in the index.
            if (searchFilter.IsValid)
            {
                searchFilter.SearchTerm        = searchTerm;
                searchFilter.IncludePrerelease = includePrerelease;

                return(GetResultsFromSearchService(searchFilter));
            }

            Trace.WriteLine("Database hit");

            if (!includePrerelease)
            {
                packages = packages.Where(p => !p.IsPrerelease);
            }

            return(packages.Search(searchTerm));
        }
        private static SortField GetSortField(SearchFilter searchFilter)
        {
            switch (searchFilter.SortOrder)
            {
                case SortOrder.TitleAscending:
                    return new SortField("DisplayName", SortField.STRING, reverse: false);
                case SortOrder.TitleDescending:
                    return new SortField("DisplayName", SortField.STRING, reverse: false);
                case SortOrder.Published:
                    return new SortField("PublishedDate", SortField.LONG, reverse: true);
                case SortOrder.LastEdited:
                    return new SortField("EditedDate", SortField.LONG, reverse: true);
            }

            return SortField.FIELD_SCORE;
        }
        protected virtual IQueryable <Package> SearchCore(IQueryable <Package> packages, string searchTerm, string targetFramework, bool includePrerelease, SearchFilter searchFilter, bool useCache)
        {
            // we don't allow an empty search for all versions.
            if (searchFilter.FilterInvalidReason == SearchFilterInvalidReason.DueToAllVersionsRequested && string.IsNullOrWhiteSpace(searchTerm))
            {
                searchFilter.IsValid = true;
            }

            // We can only use Lucene if the client queries for the latest versions (IsLatest \ IsLatestStable) versions of a package
            // and specific sort orders that we have in the index.
            if (searchFilter.IsValid)
            {
                searchFilter.SearchTerm        = searchTerm;
                searchFilter.IncludePrerelease = includePrerelease;

                return(GetResultsFromSearchService(searchFilter));
            }

            if (!includePrerelease)
            {
                packages = packages.Where(p => !p.IsPrerelease);
            }

            //if (searchFilter.Skip != 0)
            //{
            //    packages = packages.Skip(searchFilter.Skip);
            //}

            //packages = packages.Take(MaxPageSize);
            //if (searchFilter.Take != 0)
            //{
            //    packages = packages.Take(searchFilter.Take);
            //}

            //switch (searchFilter.SortProperty)
            //{
            //    case SortProperty.Relevance:
            //        //do not change the search order
            //        break;
            //    case SortProperty.DownloadCount:
            //        packages = packages.OrderByDescending(p => p.PackageRegistration.DownloadCount);
            //        break;
            //    case SortProperty.DisplayName:
            //        packages = searchFilter.SortDirection == SortDirection.Ascending ? packages.OrderBy(p => p.Title) : packages.OrderByDescending(p => p.Title);
            //        break;
            //    case SortProperty.Recent:
            //        packages = packages.OrderByDescending(p => p.Published);
            //        break;
            //    default:
            //        //do not change the search order
            //        break;
            //}

            if (useCache)
            {
                return(NugetGallery.Cache.Get(
                           string.Format(
                               "V2Feed-Search-{0}-{1}-{2}",
                               searchTerm.to_lower(),
                               targetFramework.to_lower(),
                               includePrerelease
                               ),
                           DateTime.UtcNow.AddSeconds(3600),
                           () =>
                {
                    Trace.WriteLine("Database search results hit for API (caching results) Search term: '{0}' (prerelease? {1}).".format_with(searchTerm, includePrerelease));
                    return packages.Search(searchTerm).ToList();
                }).AsQueryable());
            }

            Trace.WriteLine("Database search results hit for API (not caching results) Search term: '{0}' (prerelease? {1}).".format_with(searchTerm, includePrerelease));

            return(packages.Search(searchTerm));
        }
Exemple #21
0
        private static bool TryReadSearchFilter(bool allVersionsInIndex, string url, out SearchFilter searchFilter)
        {
            if (url == null)
            {
                searchFilter = null;
                return(false);
            }

            int indexOfQuestionMark = url.IndexOf('?');

            if (indexOfQuestionMark == -1)
            {
                searchFilter = null;
                return(false);
            }

            string path  = url.Substring(0, indexOfQuestionMark);
            string query = url.Substring(indexOfQuestionMark + 1);

            if (string.IsNullOrEmpty(query))
            {
                searchFilter = null;
                return(false);
            }

            searchFilter = new SearchFilter(SearchFilter.ODataSearchContext)
            {
                // The way the default paging works is WCF attempts to read up to the MaxPageSize elements. If it finds as many, it'll assume there
                // are more elements to be paged and generate a continuation link. Consequently we'll always ask to pull MaxPageSize elements so WCF generates the
                // link for us and then allow it to do a Take on the results. Further down, we'll also parse $skiptoken as a custom IDataServicePagingProvider
                // sneakily injects the Skip value in the continuation token.
                Take      = MaxPageSize,
                Skip      = 0,
                CountOnly = path.EndsWith("$count", StringComparison.Ordinal)
            };

            string[] props = query.Split('&');

            IDictionary <string, string> queryTerms = new Dictionary <string, string>();

            foreach (string prop in props)
            {
                string[] nameValue = prop.Split('=');
                if (nameValue.Length == 2)
                {
                    queryTerms[nameValue[0]] = nameValue[1];
                }
            }

            // We'll only use the index if we the query searches for latest \ latest-stable packages


            string filter;

            if (queryTerms.TryGetValue("$filter", out filter))
            {
                if (!(filter.Equals("IsLatestVersion", StringComparison.Ordinal) || filter.Equals("IsAbsoluteLatestVersion", StringComparison.Ordinal)))
                {
                    searchFilter = null;
                    return(false);
                }
            }
            else if (!allVersionsInIndex)
            {
                searchFilter = null;
                return(false);
            }

            string skipStr;

            if (queryTerms.TryGetValue("$skip", out skipStr))
            {
                int skip;
                if (int.TryParse(skipStr, out skip))
                {
                    searchFilter.Skip = skip;
                }
            }

            string topStr;

            if (queryTerms.TryGetValue("$top", out topStr))
            {
                int top;
                if (int.TryParse(topStr, out top))
                {
                    searchFilter.Take = Math.Min(top, MaxPageSize);
                }
            }

            string skipTokenStr;

            if (queryTerms.TryGetValue("$skiptoken", out skipTokenStr))
            {
                var skipTokenParts = skipTokenStr.Split(',');
                if (skipTokenParts.Length == 3) // this means our custom IDataServicePagingProvider did its magic by sneaking the Skip value into the SkipToken
                {
                    int skip;
                    if (int.TryParse(skipTokenParts[2], out skip))
                    {
                        searchFilter.Skip = skip;
                    }
                }
            }

            //  only certain orderBy clauses are supported from the Lucene search
            string orderBy;

            if (queryTerms.TryGetValue("$orderby", out orderBy))
            {
                if (string.IsNullOrEmpty(orderBy))
                {
                    searchFilter.SortOrder = SortOrder.Relevance;
                }
                else if (orderBy.StartsWith("DownloadCount", StringComparison.Ordinal))
                {
                    searchFilter.SortOrder = SortOrder.Relevance;
                }
                else if (orderBy.StartsWith("Published", StringComparison.Ordinal))
                {
                    searchFilter.SortOrder = SortOrder.Published;
                }
                else if (orderBy.StartsWith("LastEdited", StringComparison.Ordinal))
                {
                    searchFilter.SortOrder = SortOrder.LastEdited;
                }
                else if (orderBy.StartsWith("Id", StringComparison.Ordinal))
                {
                    searchFilter.SortOrder = SortOrder.TitleAscending;
                }
                else if (orderBy.StartsWith("concat", StringComparison.Ordinal))
                {
                    searchFilter.SortOrder = SortOrder.TitleAscending;

                    if (orderBy.Contains("%20desc"))
                    {
                        searchFilter.SortOrder = SortOrder.TitleDescending;
                    }
                }
                else
                {
                    searchFilter = null;
                    return(false);
                }
            }
            else
            {
                searchFilter.SortOrder = SortOrder.Relevance;
            }

            return(true);
        }
Exemple #22
0
        private static Query ParseQuery(SearchFilter searchFilter)
        {
            if (String.IsNullOrWhiteSpace(searchFilter.SearchTerm))
            {
                return(new MatchAllDocsQuery());
            }

            var fields = new[] { "Id", "Title", "Tags", "Description", "Author" };
            //var analyzer = new StandardAnalyzer(LuceneCommon.LuceneVersion);
            var analyzer    = new PerFieldAnalyzer();
            var queryParser = new MultiFieldQueryParser(LuceneCommon.LuceneVersion, fields, analyzer);

            // All terms in the multi-term query appear in at least one of the fields.
            var conjuctionQuery = new BooleanQuery();

            conjuctionQuery.Boost = 2.0f;

            // Some terms in the multi-term query appear in at least one of the fields.
            var disjunctionQuery = new BooleanQuery();

            disjunctionQuery.Boost = 0.1f;

            // Escape the entire term we use for exact searches.
            var escapedSearchTerm = Escape(searchFilter.SearchTerm).Replace("id\\:", string.Empty).Replace("author\\:", string.Empty).Replace("tag\\:", string.Empty);

            // Do not escape id when used against Id-Exact. The results will return incorrectly
            var idExactSearchTerm = searchFilter.SearchTerm.Replace("id:", string.Empty).Replace("author:", string.Empty).Replace("tag:", string.Empty);
            var exactIdQuery      = new TermQuery(new Term("Id-Exact", idExactSearchTerm));

            exactIdQuery.Boost = 7.0f;
            var relatedIdQuery = new WildcardQuery(new Term("Id-Exact", idExactSearchTerm + ".*"));

            relatedIdQuery.Boost = 6.5f;
            var startsIdQuery = new WildcardQuery(new Term("Id-Exact", idExactSearchTerm + "*"));

            startsIdQuery.Boost = 6.0f;
            var wildCardIdQuery = new WildcardQuery(new Term("Id-Exact", "*" + idExactSearchTerm + "*"));

            wildCardIdQuery.Boost = 3.0f;

            var exactTitleQuery = new TermQuery(new Term("Title-Exact", escapedSearchTerm));

            exactTitleQuery.Boost = 6.5f;
            var startsTitleQuery = new WildcardQuery(new Term("Title-Exact", escapedSearchTerm + "*"));

            startsTitleQuery.Boost = 5.5f;
            var wildCardTitleQuery = new WildcardQuery(new Term("Title-Exact", "*" + escapedSearchTerm + "*"));

            wildCardTitleQuery.Boost = 2.5f;

            // Suffix wildcard search e.g. jquer*
            var wildCardQuery = new BooleanQuery();

            wildCardQuery.Boost = 0.5f;

            // GetSearchTerms() escapes the search terms, so do not escape again
            var  terms               = GetSearchTerms(searchFilter.SearchTerm).ToList();
            bool onlySearchById      = searchFilter.ByIdOnly || searchFilter.ExactIdOnly || terms.AnySafe(t => t.StartsWith("id\\:"));
            bool onlySearchByExactId = searchFilter.ExactIdOnly;
            bool onlySearchByAuthor  = terms.AnySafe(t => t.StartsWith("author\\:"));
            bool onlySearchByTag     = terms.AnySafe(t => t.StartsWith("tag\\:"));
            bool searchLimiter       = onlySearchById || onlySearchByAuthor || onlySearchByTag;

            foreach (var term in terms)
            {
                var localTerm = term.Replace("id\\:", string.Empty).Replace("author\\:", string.Empty).Replace("tag\\:", string.Empty);
                var termQuery = queryParser.Parse(localTerm);
                conjuctionQuery.Add(termQuery, Occur.MUST);
                disjunctionQuery.Add(termQuery, Occur.SHOULD);

                foreach (var field in fields)
                {
                    if (onlySearchById && field != "Id")
                    {
                        continue;
                    }
                    if (onlySearchByAuthor && field != "Author")
                    {
                        continue;
                    }
                    if (onlySearchByTag && field != "Tags")
                    {
                        continue;
                    }

                    var wildCardTermQuery = new WildcardQuery(new Term(field, localTerm + "*"));
                    wildCardTermQuery.Boost = searchLimiter ? 7.0f : 0.7f;
                    wildCardQuery.Add(wildCardTermQuery, Occur.MUST);
                }
            }

            // Create an OR of all the queries that we have
            var combinedQuery = conjuctionQuery.Combine(new Query[] { exactIdQuery, relatedIdQuery, exactTitleQuery, startsIdQuery, startsTitleQuery, wildCardIdQuery, wildCardTitleQuery, conjuctionQuery, wildCardQuery });

            if (onlySearchByExactId)
            {
                combinedQuery = conjuctionQuery.Combine(new Query[] { exactIdQuery });
            }
            else if (onlySearchById)
            {
                combinedQuery = conjuctionQuery.Combine(new Query[] { exactIdQuery, relatedIdQuery, startsIdQuery, wildCardIdQuery, wildCardQuery });
            }
            else if (onlySearchByAuthor || onlySearchByTag)
            {
                combinedQuery = conjuctionQuery.Combine(new Query[] { wildCardQuery });
            }

            //if (searchFilter.SortProperty == SortProperty.Relevance)
            //{
            //    // If searching by relevance, boost scores by download count.
            //    var downloadCountBooster = new FieldScoreQuery("DownloadCount", FieldScoreQuery.Type.INT);
            //    return new CustomScoreQuery(combinedQuery, downloadCountBooster);
            //}

            return(combinedQuery);
        }
        private static Query ParseQuery(SearchFilter searchFilter)
        {
            if (String.IsNullOrWhiteSpace(searchFilter.SearchTerm))
            {
                return new MatchAllDocsQuery();
            }

            var fields = new[] { "Id", "Title", "Tags", "Description", "Author" };
            var analyzer = new StandardAnalyzer(LuceneCommon.LuceneVersion);
            var queryParser = new MultiFieldQueryParser(LuceneCommon.LuceneVersion, fields, analyzer);

            // All terms in the multi-term query appear in at least one of the fields.
            var conjuctionQuery = new BooleanQuery();
            conjuctionQuery.Boost = 2.0f;

            // Some terms in the multi-term query appear in at least one of the fields.
            var disjunctionQuery = new BooleanQuery();
            disjunctionQuery.Boost = 0.1f;

            // Suffix wildcard search e.g. jquer*
            var wildCardQuery = new BooleanQuery();
            wildCardQuery.Boost = 0.5f;

            // Escape the entire term we use for exact searches.
            var escapedSearchTerm = Escape(searchFilter.SearchTerm).Replace("id\\:", string.Empty).Replace("author\\:", string.Empty).Replace("tag\\:", string.Empty);

            bool searchLimiter = false;
            bool onlySearchById = false;
            bool onlySearchByAuthor = false;
            bool onlySearchByTag = false;

            var exactIdQuery = new TermQuery(new Term("Id-Exact", escapedSearchTerm));
            exactIdQuery.Boost = 2.5f;
            var wildCardIdQuery = new WildcardQuery(new Term("Id-Exact", "*" + escapedSearchTerm + "*"));

            foreach (var term in GetSearchTerms(searchFilter.SearchTerm))
            {
                var localTerm = term.to_lower_invariant();
                onlySearchById = localTerm.StartsWith("id\\:");
                onlySearchByAuthor = localTerm.StartsWith("author\\:");
                onlySearchByTag = localTerm.StartsWith("tag\\:");
                if (onlySearchById || onlySearchByAuthor || onlySearchByTag) searchLimiter = true; 
                
                localTerm = term.Replace("id\\:", string.Empty).Replace("author\\:", string.Empty).Replace("tag\\:", string.Empty);
                var termQuery = queryParser.Parse(localTerm);
                conjuctionQuery.Add(termQuery, Occur.MUST);
                disjunctionQuery.Add(termQuery, Occur.SHOULD);

                foreach (var field in fields)
                {
                    if (onlySearchById && field != "Id") continue;
                    if (onlySearchByAuthor && field != "Author") continue;
                    if (onlySearchByTag && field != "Tags") continue;

                    var wildCardTermQuery = new WildcardQuery(new Term(field, localTerm + "*"));
                    wildCardTermQuery.Boost = searchLimiter ? 2.5f : 0.7f;
                    wildCardQuery.Add(wildCardTermQuery, Occur.SHOULD);
                }
            }
            
            // Create an OR of all the queries that we have
            var combinedQuery = conjuctionQuery.Combine(new Query[] { exactIdQuery, wildCardIdQuery, conjuctionQuery, disjunctionQuery, wildCardQuery });

            if (onlySearchById)
            {
                combinedQuery = conjuctionQuery.Combine(new Query[] { exactIdQuery, wildCardIdQuery, wildCardQuery });
            } else if (onlySearchByAuthor || onlySearchByTag)
            {
                combinedQuery = conjuctionQuery.Combine(new Query[] { wildCardQuery });
            }
            
            if (searchFilter.SortProperty == SortProperty.Relevance)
            {
                // If searching by relevance, boost scores by download count.
                var downloadCountBooster = new FieldScoreQuery("DownloadCount", FieldScoreQuery.Type.INT);
                return new CustomScoreQuery(combinedQuery, downloadCountBooster);
            }

            return combinedQuery;
        }
        private static IList<int> SearchCore(SearchFilter searchFilter)
        {
            if (!Directory.Exists(LuceneCommon.IndexDirectory))
            {
                return new int[0];
            }

            SortField sortField = GetSortField(searchFilter);
            int numRecords = Math.Min((1 + searchFilter.Skip) * searchFilter.Take, MaximumRecordsToReturn);

            using (var directory = new LuceneFileSystem(LuceneCommon.IndexDirectory))
            {
                var searcher = new IndexSearcher(directory, readOnly: true);
                var query = ParseQuery(searchFilter);

                Filter filter = null;
                if (!searchFilter.IncludePrerelease)
                {
                    var isLatestStableQuery = new TermQuery(new Term("IsLatestStable", Boolean.TrueString));
                    filter = new QueryWrapperFilter(isLatestStableQuery);
                }

                var results = searcher.Search(query, filter: filter, n: numRecords, sort: new Sort(sortField));
                var keys = results.scoreDocs.Skip(searchFilter.Skip)
                                            .Select(c => ParseKey(searcher.Doc(c.doc).Get("Key")))
                                            .ToList();
                searcher.Close();
                return keys;
            }
        }
        private static Query ParseQuery(SearchFilter searchFilter)
        {
            if (String.IsNullOrWhiteSpace(searchFilter.SearchTerm))
            {
                return(new MatchAllDocsQuery());
            }

            var fields      = new[] { "Id", "Title", "Tags", "Description", "Author" };
            var analyzer    = new StandardAnalyzer(LuceneCommon.LuceneVersion);
            var queryParser = new MultiFieldQueryParser(LuceneCommon.LuceneVersion, fields, analyzer);

            // All terms in the multi-term query appear in at least one of the fields.
            var conjuctionQuery = new BooleanQuery();

            conjuctionQuery.Boost = 2.0f;

            // Some terms in the multi-term query appear in at least one of the fields.
            var disjunctionQuery = new BooleanQuery();

            disjunctionQuery.Boost = 0.1f;

            // Suffix wildcard search e.g. jquer*
            var wildCardQuery = new BooleanQuery();

            wildCardQuery.Boost = 0.5f;

            // Escape the entire term we use for exact searches.
            var escapedSearchTerm = Escape(searchFilter.SearchTerm).Replace("id\\:", string.Empty).Replace("author\\:", string.Empty).Replace("tag\\:", string.Empty);

            bool searchLimiter      = false;
            bool onlySearchById     = false;
            bool onlySearchByAuthor = false;
            bool onlySearchByTag    = false;

            var exactIdQuery = new TermQuery(new Term("Id-Exact", escapedSearchTerm));

            exactIdQuery.Boost = 2.5f;
            var wildCardIdQuery = new WildcardQuery(new Term("Id-Exact", "*" + escapedSearchTerm + "*"));

            foreach (var term in GetSearchTerms(searchFilter.SearchTerm))
            {
                var localTerm = term.to_lower_invariant();
                onlySearchById     = localTerm.StartsWith("id\\:");
                onlySearchByAuthor = localTerm.StartsWith("author\\:");
                onlySearchByTag    = localTerm.StartsWith("tag\\:");
                if (onlySearchById || onlySearchByAuthor || onlySearchByTag)
                {
                    searchLimiter = true;
                }

                localTerm = term.Replace("id\\:", string.Empty).Replace("author\\:", string.Empty).Replace("tag\\:", string.Empty);
                var termQuery = queryParser.Parse(localTerm);
                conjuctionQuery.Add(termQuery, Occur.MUST);
                disjunctionQuery.Add(termQuery, Occur.SHOULD);

                foreach (var field in fields)
                {
                    if (onlySearchById && field != "Id")
                    {
                        continue;
                    }
                    if (onlySearchByAuthor && field != "Author")
                    {
                        continue;
                    }
                    if (onlySearchByTag && field != "Tags")
                    {
                        continue;
                    }

                    var wildCardTermQuery = new WildcardQuery(new Term(field, localTerm + "*"));
                    wildCardTermQuery.Boost = searchLimiter ? 2.5f : 0.7f;
                    wildCardQuery.Add(wildCardTermQuery, Occur.SHOULD);
                }
            }

            // Create an OR of all the queries that we have
            var combinedQuery = conjuctionQuery.Combine(new Query[] { exactIdQuery, wildCardIdQuery, conjuctionQuery, disjunctionQuery, wildCardQuery });

            if (onlySearchById)
            {
                combinedQuery = conjuctionQuery.Combine(new Query[] { exactIdQuery, wildCardIdQuery, wildCardQuery });
            }
            else if (onlySearchByAuthor || onlySearchByTag)
            {
                combinedQuery = conjuctionQuery.Combine(new Query[] { wildCardQuery });
            }

            if (searchFilter.SortProperty == SortProperty.Relevance)
            {
                // If searching by relevance, boost scores by download count.
                var downloadCountBooster = new FieldScoreQuery("DownloadCount", FieldScoreQuery.Type.INT);
                return(new CustomScoreQuery(combinedQuery, downloadCountBooster));
            }

            return(combinedQuery);
        }
        private static Query ParseQuery(SearchFilter searchFilter)
        {
            var fields = new[] { "Id", "Title", "Tags", "Description", "Author" };
            var analyzer = new StandardAnalyzer(LuceneCommon.LuceneVersion);
            var queryParser = new MultiFieldQueryParser(LuceneCommon.LuceneVersion, fields, analyzer);

            // All terms in the multi-term query appear in at least one of the fields.
            var conjuctionQuery = new BooleanQuery();
            conjuctionQuery.SetBoost(2.0f);

            // Some terms in the multi-term query appear in at least one of the fields.
            var disjunctionQuery = new BooleanQuery();
            disjunctionQuery.SetBoost(0.1f);

            // Suffix wildcard search e.g. jquer*
            var wildCardQuery = new BooleanQuery();
            wildCardQuery.SetBoost(0.5f);

            // Escape the entire term we use for exact searches.
            var escapedSearchTerm = Escape(searchFilter.SearchTerm);
            var exactIdQuery = new TermQuery(new Term("Id-Exact", escapedSearchTerm));
            exactIdQuery.SetBoost(2.5f);
            var wildCardIdQuery = new WildcardQuery(new Term("Id-Exact", "*" + escapedSearchTerm + "*"));

            foreach (var term in GetSearchTerms(searchFilter.SearchTerm))
            {
                var termQuery = queryParser.Parse(term);
                conjuctionQuery.Add(termQuery, BooleanClause.Occur.MUST);
                disjunctionQuery.Add(termQuery, BooleanClause.Occur.SHOULD);

                foreach (var field in fields)
                {
                    var wildCardTermQuery = new WildcardQuery(new Term(field, term + "*"));
                    wildCardTermQuery.SetBoost(0.7f);
                    wildCardQuery.Add(wildCardTermQuery, BooleanClause.Occur.SHOULD);
                }
            }

            // Create an OR of all the queries that we have
            var combinedQuery = conjuctionQuery.Combine(new Query[] { exactIdQuery, wildCardIdQuery, conjuctionQuery, disjunctionQuery, wildCardQuery });

            if (searchFilter.SortProperty == SortProperty.Relevance)
            {
                // If searching by relevance, boost scores by download count.
                var downloadCountBooster = new FieldScoreQuery("DownloadCount", FieldScoreQuery.Type.INT);
                return new CustomScoreQuery(combinedQuery, downloadCountBooster);
            }
            return combinedQuery;
        }
        private static bool TryReadSearchFilter(HttpRequestBase request, string siteRoot, out SearchFilter searchFilter)
        {
            var odataQuery = SyntacticTree.ParseUri(new Uri(siteRoot + request.RawUrl), new Uri(siteRoot));

            var keywordPath = odataQuery.Path as KeywordSegmentQueryToken;
            searchFilter = new SearchFilter
            {
                // HACK: The way the default paging works is WCF attempts to read up to the MaxPageSize elements. If it finds as many, it'll assume there
                // are more elements to be paged and generate a continuation link. Consequently we'll always ask to pull MaxPageSize elements so WCF generates the
                // link for us and then allow it to do a Take on the results. The alternative to do is roll our IDataServicePagingProvider, but we run into
                // issues since we need to manage state over concurrent requests. This seems like an easier solution.
                Take = MaxPageSize,
                Skip = odataQuery.Skip ?? 0,
                CountOnly = keywordPath != null && keywordPath.Keyword == KeywordKind.Count,
                SortDirection = SortDirection.Ascending
            };

            var filterProperty = odataQuery.Filter as PropertyAccessQueryToken;
            if (filterProperty == null ||
                !(filterProperty.Name.Equals("IsLatestVersion", StringComparison.Ordinal) ||
                  filterProperty.Name.Equals("IsAbsoluteLatestVersion", StringComparison.Ordinal)))
            {
                // We'll only use the index if we the query searches for latest \ latest-stable packages
                return false;
            }

            var orderBy = odataQuery.OrderByTokens.FirstOrDefault();
            if (orderBy == null || orderBy.Expression == null)
            {
                searchFilter.SortProperty = SortProperty.Relevance;
            }
            else if (orderBy.Expression.Kind == QueryTokenKind.PropertyAccess)
            {
                var propertyAccess = (PropertyAccessQueryToken)orderBy.Expression;
                if (propertyAccess.Name.Equals("DownloadCount", StringComparison.Ordinal))
                {
                    searchFilter.SortProperty = SortProperty.DownloadCount;
                }
                else if (propertyAccess.Name.Equals("Published", StringComparison.Ordinal))
                {
                    searchFilter.SortProperty = SortProperty.Recent;
                }
                else if (propertyAccess.Name.Equals("Id", StringComparison.Ordinal))
                {
                    searchFilter.SortProperty = SortProperty.DisplayName;
                }
                else
                {
                    Debug.WriteLine("Order by clause {0} is unsupported", propertyAccess.Name);
                    return false;
                }
            }
            else if (orderBy.Expression.Kind == QueryTokenKind.FunctionCall)
            {
                var functionCall = (FunctionCallQueryToken)orderBy.Expression;
                if (functionCall.Name.Equals("concat", StringComparison.OrdinalIgnoreCase))
                {
                    // We'll assume this is concat(Title, Id)
                    searchFilter.SortProperty = SortProperty.DisplayName;
                    searchFilter.SortDirection = orderBy.Direction == OrderByDirection.Descending ? SortDirection.Descending : SortDirection.Ascending;
                }
                else
                {
                    Debug.WriteLine("Order by clause {0} is unsupported", functionCall.Name);
                    return false;
                }
            }
            else
            {
                Debug.WriteLine("Order by clause {0} is unsupported", orderBy.Expression.Kind);
                return false;
            }
            return true;
        }
Exemple #28
0
        private static bool TryReadSearchFilter(string url, out SearchFilter searchFilter)
        {
            if (url == null)
            {
                searchFilter = null;
                return(false);
            }

            int indexOfQuestionMark = url.IndexOf('?');

            if (indexOfQuestionMark == -1)
            {
                searchFilter = null;
                return(false);
            }

            string path  = url.Substring(0, indexOfQuestionMark);
            string query = url.Substring(indexOfQuestionMark + 1);

            if (string.IsNullOrEmpty(query))
            {
                searchFilter = null;
                return(false);
            }

            searchFilter = new SearchFilter
            {
                // The way the default paging works is WCF attempts to read up to the MaxPageSize elements. If it finds as many, it'll assume there
                // are more elements to be paged and generate a continuation link. Consequently we'll always ask to pull MaxPageSize elements so WCF generates the
                // link for us and then allow it to do a Take on the results. The alternative to do is roll our IDataServicePagingProvider, but we run into
                // issues since we need to manage state over concurrent requests. This seems like an easier solution.
                Take          = MaxPageSize,
                Skip          = 0,
                CountOnly     = path.EndsWith("$count", StringComparison.Ordinal),
                SortDirection = SortDirection.Ascending
            };

            string[] props = query.Split('&');

            IDictionary <string, string> queryTerms = new Dictionary <string, string>();

            foreach (string prop in props)
            {
                string[] nameValue = prop.Split('=');
                if (nameValue.Length == 2)
                {
                    queryTerms[nameValue[0]] = nameValue[1];
                }
            }

            // We'll only use the index if we the query searches for latest \ latest-stable packages

            string filter;

            if (queryTerms.TryGetValue("$filter", out filter))
            {
                if (!(filter.Equals("IsLatestVersion", StringComparison.Ordinal) || filter.Equals("IsAbsoluteLatestVersion", StringComparison.Ordinal)))
                {
                    searchFilter = null;
                    return(false);
                }
            }
            else
            {
                searchFilter = null;
                return(false);
            }

            string skip;

            if (queryTerms.TryGetValue("$skip", out skip))
            {
                int result;
                if (int.TryParse(skip, out result))
                {
                    searchFilter.Skip = result;
                }
            }

            //  only certain orderBy clauses are supported from the Lucene search

            string orderBy;

            if (queryTerms.TryGetValue("$orderby", out orderBy))
            {
                if (string.IsNullOrEmpty(orderBy))
                {
                    searchFilter.SortProperty = SortProperty.Relevance;
                }
                else if (orderBy.StartsWith("DownloadCount", StringComparison.Ordinal))
                {
                    searchFilter.SortProperty = SortProperty.DownloadCount;
                }
                else if (orderBy.StartsWith("Published", StringComparison.Ordinal))
                {
                    searchFilter.SortProperty = SortProperty.Recent;
                }
                else if (orderBy.StartsWith("LastEdited", StringComparison.Ordinal))
                {
                    searchFilter.SortProperty = SortProperty.RecentlyEdited;
                }
                else if (orderBy.StartsWith("Id", StringComparison.Ordinal))
                {
                    searchFilter.SortProperty = SortProperty.DisplayName;
                }
                else if (orderBy.StartsWith("concat", StringComparison.Ordinal))
                {
                    searchFilter.SortProperty = SortProperty.DisplayName;

                    if (orderBy.Contains("%20desc"))
                    {
                        searchFilter.SortDirection = SortDirection.Descending;
                    }
                }
                else
                {
                    searchFilter = null;
                    return(false);
                }
            }
            else
            {
                searchFilter.SortProperty = SortProperty.Relevance;
            }

            return(true);
        }
Exemple #29
0
        public static IQueryable <Package> GetResultsFromSearchService(ISearchService searchService, SearchFilter searchFilter)
        {
            int totalHits;
            var result = searchService.Search(searchFilter, out totalHits);

            // For count queries, we can ask the SearchService to not filter the source results. This would avoid hitting the database and consequently make
            // it very fast.
            if (searchFilter.CountOnly)
            {
                // At this point, we already know what the total count is. We can have it return this value very quickly without doing any SQL.
                return(result.InterceptWith(new CountInterceptor(totalHits)));
            }

            // For relevance search, Lucene returns us a paged\sorted list. OData tries to apply default ordering and Take \ Skip on top of this.
            // We avoid it by yanking these expressions out of out the tree.
            return(result.InterceptWith(new DisregardODataInterceptor()));
        }
        private static bool TryReadSearchFilter(bool allVersionsInIndex, string url, out SearchFilter searchFilter)
        {
            if (url == null)
            {
                searchFilter = null;
                return false;
            }

            int indexOfQuestionMark = url.IndexOf('?');
            if (indexOfQuestionMark == -1)
            {
                searchFilter = null;
                return false;
            }

            string path = url.Substring(0, indexOfQuestionMark);
            string query = url.Substring(indexOfQuestionMark + 1);

            if (string.IsNullOrEmpty(query))
            {
                searchFilter = null;
                return false;
            }

            searchFilter = new SearchFilter(SearchFilter.ODataSearchContext)
            {
                // The way the default paging works is WCF attempts to read up to the MaxPageSize elements. If it finds as many, it'll assume there 
                // are more elements to be paged and generate a continuation link. Consequently we'll always ask to pull MaxPageSize elements so WCF generates the 
                // link for us and then allow it to do a Take on the results. The alternative to do is roll our IDataServicePagingProvider, but we run into 
                // issues since we need to manage state over concurrent requests. This seems like an easier solution.
                Take = MaxPageSize,
                Skip = 0,
                CountOnly = path.EndsWith("$count", StringComparison.Ordinal)
            };

            string[] props = query.Split('&');

            IDictionary<string, string> queryTerms = new Dictionary<string, string>();
            foreach (string prop in props)
            {
                string[] nameValue = prop.Split('=');
                if (nameValue.Length == 2)
                {
                    queryTerms[nameValue[0]] = nameValue[1];
                }
            }

            // We'll only use the index if we the query searches for latest \ latest-stable packages

            
            string filter;
            if (queryTerms.TryGetValue("$filter", out filter))
            {
                if (!(filter.Equals("IsLatestVersion", StringComparison.Ordinal) || filter.Equals("IsAbsoluteLatestVersion", StringComparison.Ordinal)))
                {
                    searchFilter = null;
                    return false;
                }
            }
            else if(!allVersionsInIndex)
            {
                searchFilter = null;
                return false;
            }

            string skipStr;
            if (queryTerms.TryGetValue("$skip", out skipStr))
            {
                int skip;
                if (int.TryParse(skipStr, out skip))
                {
                    searchFilter.Skip = skip;
                }
            }

            string topStr;
            if (queryTerms.TryGetValue("$top", out topStr))
            {
                int top;
                if(int.TryParse(topStr, out top))
                {
                    searchFilter.Take = Math.Min(top, SearchAdaptor.MaxPageSize);
                }
            }

            //  only certain orderBy clauses are supported from the Lucene search
            string orderBy;
            if (queryTerms.TryGetValue("$orderby", out orderBy))
            {
                if (string.IsNullOrEmpty(orderBy))
                {
                    searchFilter.SortOrder = SortOrder.Relevance;
                }
                else if (orderBy.StartsWith("DownloadCount", StringComparison.Ordinal))
                {
                    searchFilter.SortOrder = SortOrder.Relevance;
                }
                else if (orderBy.StartsWith("Published", StringComparison.Ordinal))
                {
                    searchFilter.SortOrder = SortOrder.Published;
                }
                else if (orderBy.StartsWith("LastEdited", StringComparison.Ordinal))
                {
                    searchFilter.SortOrder = SortOrder.LastEdited;
                }
                else if (orderBy.StartsWith("Id", StringComparison.Ordinal))
                {
                    searchFilter.SortOrder = SortOrder.TitleAscending;
                }
                else if (orderBy.StartsWith("concat", StringComparison.Ordinal))
                {
                    searchFilter.SortOrder = SortOrder.TitleAscending;

                    if (orderBy.Contains("%20desc"))
                    {
                        searchFilter.SortOrder = SortOrder.TitleDescending;
                    }
                }
                else
                {
                    searchFilter = null;
                    return false;
                }
            }
            else
            {
                searchFilter.SortOrder = SortOrder.Relevance;
            }

            return true;
        }
Exemple #31
0
        private static bool TryReadSearchFilter(HttpRequestBase request, string siteRoot, out SearchFilter searchFilter)
        {
            var odataQuery = SyntacticTree.ParseUri(new Uri(siteRoot + request.RawUrl), new Uri(siteRoot));

            var keywordPath = odataQuery.Path as KeywordSegmentQueryToken;
            searchFilter = new SearchFilter
            {
                // HACK: The way the default paging works is WCF attempts to read up to the MaxPageSize elements. If it finds as many, it'll assume there 
                // are more elements to be paged and generate a continuation link. Consequently we'll always ask to pull MaxPageSize elements so WCF generates the 
                // link for us and then allow it to do a Take on the results. The alternative to do is roll our IDataServicePagingProvider, but we run into 
                // issues since we need to manage state over concurrent requests. This seems like an easier solution.
                Take = MaxPageSize,
                Skip = odataQuery.Skip ?? 0,
                CountOnly = keywordPath != null && keywordPath.Keyword == KeywordKind.Count,
                SortDirection = SortDirection.Ascending
            };

            var filterProperty = odataQuery.Filter as PropertyAccessQueryToken;
            if (filterProperty == null ||
                !(filterProperty.Name.Equals("IsLatestVersion", StringComparison.Ordinal) ||
                  filterProperty.Name.Equals("IsAbsoluteLatestVersion", StringComparison.Ordinal)))
            {
                // We'll only use the index if we the query searches for latest \ latest-stable packages
                return false;
            }

            var orderBy = odataQuery.OrderByTokens.FirstOrDefault();
            if (orderBy == null || orderBy.Expression == null)
            {
                searchFilter.SortProperty = SortProperty.Relevance;
            }
            else if (orderBy.Expression.Kind == QueryTokenKind.PropertyAccess)
            {
                var propertyAccess = (PropertyAccessQueryToken)orderBy.Expression;
                if (propertyAccess.Name.Equals("DownloadCount", StringComparison.Ordinal))
                {
                    searchFilter.SortProperty = SortProperty.DownloadCount;
                }
                else if (propertyAccess.Name.Equals("Published", StringComparison.Ordinal))
                {
                    searchFilter.SortProperty = SortProperty.Recent;
                }
                else if (propertyAccess.Name.Equals("Id", StringComparison.Ordinal))
                {
                    searchFilter.SortProperty = SortProperty.DisplayName;
                }
                else
                {
                    Debug.WriteLine("Order by clause {0} is unsupported", propertyAccess.Name);
                    return false;
                }
            }
            else if (orderBy.Expression.Kind == QueryTokenKind.FunctionCall)
            {
                var functionCall = (FunctionCallQueryToken)orderBy.Expression;
                if (functionCall.Name.Equals("concat", StringComparison.OrdinalIgnoreCase))
                {
                    // We'll assume this is concat(Title, Id)
                    searchFilter.SortProperty = SortProperty.DisplayName;
                    searchFilter.SortDirection = orderBy.Direction == OrderByDirection.Descending ? SortDirection.Descending : SortDirection.Ascending;
                }
                else
                {
                    Debug.WriteLine("Order by clause {0} is unsupported", functionCall.Name);
                    return false;
                }
            }
            else
            {
                Debug.WriteLine("Order by clause {0} is unsupported", orderBy.Expression.Kind);
                return false;
            }
            return true;
        }