Fixes #475, Quicksearch doesn't find child items in collections or saved searches

It ain't pretty, but (I think) it works.

Also:

- Fulltext content search should handle ANY/ALL modes better, but that needs some more testing.
- Tag selector now properly takes fulltext content search conditions into account when filtering to scope.
- Added Zotero.Search.hasPostSearchFilter(), since getSQL() isn't sufficient with post-search filters.
This commit is contained in:
Dan Stillman 2007-01-17 11:51:11 +00:00
parent 6e646b0455
commit ebe4ec7446
3 changed files with 154 additions and 59 deletions

View file

@ -700,11 +700,11 @@ Zotero.ItemGroup.prototype.getSearchObject = function() {
if (this.searchText){
if (this.isCollection())
{
s.addCondition('collectionID', 'is', this.ref.getID(), true);
s.addCondition('collectionID', 'is', this.ref.getID());
}
else if (this.isSearch())
{
s.addCondition('savedSearchID', 'is', this.ref['id'], true);
s.addCondition('savedSearchID', 'is', this.ref['id']);
}
s.addCondition('quicksearch', 'contains', this.searchText);
}
@ -728,7 +728,7 @@ Zotero.ItemGroup.prototype.getSearchObject = function() {
if (this.tags){
for (var tag in this.tags){
if (this.tags[tag]){
s.addCondition('tag', 'is', tag, true);
s.addCondition('tag', 'is', tag);
}
}
}

View file

@ -3212,12 +3212,24 @@ Zotero.Tags = new function(){
* Get all tags within the items of a Zotero.Search object
*/
function getAllWithinSearch(search) {
var searchSQL = search.getSQL();
var searchParams = search.getSQLParams();
// If search has post-search filters (e.g. fulltext content), run it
// and just include the ids
//
// DEBUG: it's possible there's a query length limit here
// or that this slows things down with large libraries
// -- should probably use a temporary table instead
if (search.hasPostSearchFilter()) {
var ids = search.search();
var sql = "SELECT DISTINCT tagID, tag FROM itemTags NATURAL JOIN tags "
+ "WHERE itemID IN (" + ids.join() + ") ORDER BY tag COLLATE NOCASE";
var tags = Zotero.DB.query(sql);
}
else {
var sql = "SELECT DISTINCT tagID, tag FROM itemTags NATURAL JOIN tags "
+ "WHERE itemID IN (" + search.getSQL() + ") ORDER BY tag COLLATE NOCASE";
var tags = Zotero.DB.query(sql, search.getSQLParams());
}
var sql = "SELECT DISTINCT tagID, tag FROM itemTags NATURAL JOIN tags "
+ "WHERE itemID IN (" + searchSQL + ") ORDER BY tag COLLATE NOCASE";
var tags = Zotero.DB.query(sql, searchParams);
var indexed = {};
for (var i in tags){
indexed[tags[i]['tagID']] = tags[i]['tag'];

View file

@ -174,8 +174,6 @@ Zotero.Search.prototype.addCondition = function(condition, operator, value, requ
// Shortcut to add a condition on every table -- does not return an id
if (condition=='quicksearch'){
this.addCondition('joinMode', 'any');
var parts = Zotero.SearchConditions.parseSearchString(value);
for each(var part in parts) {
@ -198,7 +196,6 @@ Zotero.Search.prototype.addCondition = function(condition, operator, value, requ
}
this.addCondition('blockEnd');
}
return false;
}
@ -272,6 +269,16 @@ Zotero.Search.prototype.getSearchConditions = function(){
}
Zotero.Search.prototype.hasPostSearchFilter = function() {
for each(var i in this._conditions){
if (i.condition == 'fulltextContent'){
return true;
}
}
return false;
}
/*
* Run the search and return an array of item ids for results
*/
@ -282,6 +289,9 @@ Zotero.Search.prototype.search = function(){
var ids = Zotero.DB.columnQuery(this._sql, this._sqlParams);
//Zotero.debug('IDs from main search: ');
//Zotero.debug(ids);
var joinMode = 'all';
for each(var condition in this._conditions) {
if (condition.condition == 'joinMode') {
@ -292,22 +302,57 @@ Zotero.Search.prototype.search = function(){
}
}
//Zotero.debug('Join mode: ' + joinMode);
for each(var condition in this._conditions) {
if (condition.condition == 'blockStart') {
var hasQuicksearch = true;
break;
}
}
// Filter results with fulltext search
//
// If join mode ANY, return the superset of the main search and
// (a separate fulltext word search filtered by fulltext content)
//
// If join mode ALL, return the (union of SQL and fulltext word search)
// If join mode ALL, return the (union of main and fulltext word search)
// filtered by fulltext content
//
// If join mode ANY or there's a quicksearch (which we assume
// fulltextContent is part of), return the superset of the main search and
// (a separate fulltext word search filtered by fulltext content)
for each(var condition in this._conditions){
if (condition['condition']=='fulltextContent'){
//Zotero.debug('Running subsearch against fulltext word index');
// Run a new search against the fulltext word index
// for words in this phrase
var s = new Zotero.Search();
var splits = Zotero.Fulltext.semanticSplitter(condition.value);
// Add any necessary conditions to the fulltext word search --
// those that are required in an ANY search and any outside the
// quicksearch in an ALL search
for each(var c in this._conditions) {
if (c.condition == 'blockStart') {
var inQS = true;
continue;
}
else if (c.condition == 'blockEnd') {
inQS = false;
continue;
}
else if (c.condition == 'fulltextContent' ||
c.condition == 'fulltextContent' ||
inQS) {
continue;
}
else if (joinMode == 'any' && !c.required) {
continue;
}
s.addCondition(c.condition, c.operator, c.value);
}
var splits = Zotero.Fulltext.semanticSplitter(condition.value);
for each(var split in splits){
s.addCondition('fulltextWord', condition.operator, split, false);
s.addCondition('fulltextWord', condition.operator, split);
}
var fulltextWordIDs = s.search();
@ -315,15 +360,20 @@ Zotero.Search.prototype.search = function(){
return hash[val] ?
(condition.operator == 'contains') :
(condition.operator == 'doesNotContain');
}
};
// If ALL mode, get union of main search and fulltext word index
if (joinMode == 'all') {
// If ALL mode, set union of main search and fulltext word index
// as the scope for the fulltext content search
if (joinMode == 'all' && !hasQuicksearch) {
var hash = {};
for each(var id in fulltextWordIDs){
hash[id] = true;
}
var scopeIDs = ids.filter(filter);
}
// If ANY mode, just use fulltext word index hits for content search,
// since the main results will be added in below
else {
var scopeIDs = fulltextWordIDs;
}
@ -331,16 +381,21 @@ Zotero.Search.prototype.search = function(){
var fulltextIDs = Zotero.Fulltext.findTextInItems(scopeIDs,
condition['value'], condition['mode']);
var hash = {};
for each(var val in fulltextIDs){
hash[val.id] = true;
if (scopeIDs) {
var hash = {};
for each(var val in fulltextIDs){
hash[val.id] = true;
}
var filteredIDs = scopeIDs.filter(filter);
}
else {
var filteredIDs = [];
}
var filteredIDs = scopeIDs ? scopeIDs.filter(filter) : [];
// If join mode ANY, add any new items from the fulltext content
// search to the main search results
if (joinMode == 'any' && ids) {
if ((joinMode == 'any' || hasQuicksearch) && ids) {
for each(var id in filteredIDs) {
if (ids.indexOf(id) == -1) {
ids.push(id);
@ -353,6 +408,9 @@ Zotero.Search.prototype.search = function(){
}
}
//Zotero.debug('Final result set');
//Zotero.debug(ids);
return ids;
}
@ -500,23 +558,23 @@ Zotero.Search.prototype._buildQuery = function(){
break;
case 'collectionID':
condSQL += "collectionID IN (?,";
condSQLParams.push({int:condition['value']});
var q = ['?'];
var p = [{int:condition['value']}];
// And descendents if recursive search
// Search descendent collections if recursive search
if (recursive){
var col = Zotero.Collections.get(condition['value']);
var descendents = col.getDescendents(false, 'collection');
if (descendents){
for (var k in descendents){
condSQL += '?,';
condSQLParams.push(descendents[k]['id']);
q.push('?');
p.push({int:descendents[k]['id']});
}
}
}
// Strip final comma
condSQL = condSQL.substring(0, condSQL.length-1) + ")";
condSQL += "collectionID IN (" + q.join() + ")";
condSQLParams = condSQLParams.concat(p);
skipOperators = true;
break;
@ -531,17 +589,16 @@ Zotero.Search.prototype._buildQuery = function(){
search.load(condition['value']);
// Check if there are any post-search filters
var subconds = search.getSearchConditions();
var hasFilter;
for each(var k in subconds){
if (k.condition == 'fulltextContent'){
hasFilter = true;
break;
}
}
var hasFilter = search.hasPostSearchFilter();
// This is an ugly and inefficient way of doing a
// subsearch, but it's necessary if there are any
// post-search filters (e.g. fulltext scanning)
// post-search filters (e.g. fulltext scanning) in the
// subsearch
//
// DEBUG: it's possible there's a query length limit here
// or that this slows things down with large libraries
// -- should probably use a temporary table instead
if (hasFilter){
var subids = search.search();
condSQL += subids.join();
@ -759,21 +816,33 @@ Zotero.Search.prototype._buildQuery = function(){
condSQL += ')';
}
// Little hack to support multiple quicksearch words
if (condition['name'] == 'blockStart') {
var inQS = true;
var qsSQL = '';
var qsParams = [];
continue;
}
else if (condition['name'] == 'blockEnd') {
inQS = false;
// Strip ' OR ' from last condition
qsSQL = qsSQL.substring(0, qsSQL.length-4);
// Add to existing quicksearch words
if (!quicksearchSQLSet) {
var quicksearchSQLSet = [];
var quicksearchParamsSet = [];
}
quicksearchSQLSet.push(qsSQL);
quicksearchParamsSet.push(qsParams);
}
else if (inQS) {
qsSQL += condSQL + ' OR ';
qsParams = qsParams.concat(condSQLParams);
}
// Keep non-required conditions separate if in ANY mode
if (!condition['required'] && joinMode=='ANY'){
// Little hack to allow multiple quicksearch words
if (condition['name'] == 'blockStart'){
condSQL += '(';
}
else if (condition['name'] == 'blockEnd'){
// Strip ' OR ' from last condition
anySQL = anySQL.substring(0, anySQL.length-4);
condSQL += ') AND ';
}
else {
condSQL += ' OR ';
}
anySQL += condSQL;
else if (!condition['required'] && joinMode == 'ANY') {
anySQL += condSQL + ' OR ';
anySQLParams = anySQLParams.concat(condSQLParams);
}
else {
@ -787,14 +856,28 @@ Zotero.Search.prototype._buildQuery = function(){
if (anySQL){
sql += '(' + anySQL;
sqlParams = sqlParams.concat(anySQLParams);
// If we ended with a block, remove ' AND ', otherwise ' OR '
var remlen = condition['name']=='blockEnd' ? 5 : 4;
sql = sql.substring(0, sql.length-remlen);
sql = sql.substring(0, sql.length-4); // remove last ' OR '
sql += ')';
}
else {
sql = sql.substring(0, sql.length-5); // remove last ' AND '
}
// Add on quicksearch conditions -- this requires searching against
// children, so we repeat the main query for note and attachment
// sourceItemIDs before ANDing the quicksearch block
if (quicksearchSQLSet) {
sql = "SELECT itemID FROM items WHERE itemID IN (" + sql + " UNION "
+ "SELECT itemID FROM itemNotes WHERE sourceItemID IN "
+ "(" + sql + ") UNION "
+ "SELECT itemID FROM itemAttachments WHERE sourceItemID IN "
+ "(" + sql + ")) AND ((" + quicksearchSQLSet.join(') AND (') + "))";
sqlParams = sqlParams.concat(sqlParams).concat(sqlParams);
for each(var p in quicksearchParamsSet) {
sqlParams = sqlParams.concat(p);
}
}
}
this._sql = sql;