1 /* Copyright (C) 2000-2004 Thomas Bopp, Thorsten Hampel, Ludger Merkens
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 * $Id: searching.pike,v 1.9 2010/02/01 23:35:23 nicke Exp $
19 inherit "/kernel/db_searching";
22 #include <attributes.h>
25 class searching : public db_searching{
30 string get_identifier() { return "searching"; }
35 #define LOG_QUERY(a, b...) werror(a,b);
37 #define LOG_QUERY(a, b...)
51 void create(string store, string k, mixed v, string ao) {
55 value = "%" + v->get_object_id();
69 string compose_value_expression(SearchToken st)
72 if ( st->storage == STORE_ATTRIB )
73 query = " (ob_ident = 'attrib' and ob_attr='"+st->key+"' and ob_data "+
74 st->value[0]+" '"+st->value[1]+"') ";
75 else if ( st->storage == "doc_data" )
76 query = " (match(doc_data) against (\""+st->value[1]+"\") ";
78 query = " (ob_data "+ st->value[0]+" '" + st->value[1]+"') ";
82 string compose_value_expressions(array tokens)
86 SearchToken last = tokens[0];
87 string exp = compose_value_expression(last);
89 for ( int i = 1; i < sizeof(tokens); i++ ) {
90 SearchToken token = tokens[i];
91 query += exp + " or ";
92 exp = compose_value_expression(token);
99 string get_table_name() { return "ob_data"; }
106 void create(function func, mixed a) { f = func; args = a; }
108 void asyncResult(mixed id, mixed result) {
113 mapping results = ([ ]);
118 array eq(string|int value) {
120 intp(value) ? (string)value : fDb()->quote(value)
123 array gt(string|int value) {
124 return ({ ">", intp(value) ? (string)value : fDb()->quote(value)});
126 array lt(string|int value) {
127 return ({ "<", intp(value) ? (string)value : fDb()->quote(value)});
129 array like(string value) {
130 return ({ "like", intp(value) ? (string)value : fDb()->quote(value)});
132 array lte(string|int value) {
133 return ({ "<=", intp(value) ? (string)value : fDb()->quote(value)});
135 array gte(string|int value) {
136 return ({ ">=", intp(value) ? (string)value : fDb()->quote(value)});
138 array btw(string|int low, string|int up) {
139 return and(gte(low), lte(up));
141 array or(array a, array b) {
142 return ({ "or", ({a, b}) });
144 array and(array a, array b) {
145 return ({ "and", ({a, b}) });
148 array extends = ({ });
149 array limits = ({ });
150 array fulltext = ({ });
154 void create(int sid, array cl) {
157 service = get_module("ServiceManager");
160 void search_attribute(string key, string value) {
161 extends += ({ search(STORE_ATTRIB, key, like(value), "or") });
164 void first_query(string store, string key, mixed value, mixed filter) {
165 extends += ({ search(store, key, value, "or") });
168 void limit(string store, string key, mixed value) {
169 limits += ({ search(store, key, value, "and") });
171 void extend(string store, string key, mixed value) {
172 extends += ({ search(store, key, value, "or") });
175 void extend_ft(string pattern) {
176 fulltext += ({ SearchToken("doc_ft", "doc_data", pattern, "or") });
179 SearchToken search(string store, string key, mixed value, string andor) {
180 return SearchToken(store, key, value, andor);
182 mapping serialize_token(SearchToken s) {
185 void execute() { run(); }
187 if ( !service->is_service("search") ) {
188 FATAL("Unable to locate Search Service - running locally !");
189 string _query = "select distinct ob_id from ( ob_data ";
191 if ( arrayp(classes) && sizeof(classes) > 0 ) {
192 _query += "INNER JOIN ob_class on ob_class.ob_id=ob_data.ob_id and ("+
193 ( "ob_class = "+classes*" or ob_class=")+")";
198 _query += compose_value_expressions(extends);
199 LOG_QUERY("Query is: %s", _query);
200 array res = query(_query);
201 array sresult = ({ });
202 foreach(res, mixed r)
204 sresult += ({ r->ob_id });
206 handle_service(search_id, sresult);
210 service->call_service_async("search",
211 "search_id": search_id,
213 "extends": map(extends, serialize_token),
214 "limits": map(limits, serialize_token),
215 "fulltext": map(fulltext, serialize_token), ]));
218 result->vars = ([ "id": search_id, ]);
219 result->processFunc = handle_result;
220 result->resultFunc = results[search_id]->asyncResult;
224 if ( !service->is_service("search") )
225 steam_error("Unable to locate search service !");
227 service->call_service_async("search",
228 "search_id": search_id,
230 "extends": map(extends, serialize_token),
231 "limits": map(limits, serialize_token),
232 "fulltext": map(fulltext, serialize_token), ]));
236 array handle_result(array res)
238 int size = sizeof(res);
239 array result = ({ });
241 for (int i =0; i<size; i++) {
242 object o = find_object((int)res[i]);
243 if ( objectp(o) && o->status() >= 0 )
249 void handle_service(int id, array result)
251 result = handle_result(result);
252 Result r = results[id];
253 r->f(r->args, result);
256 Search searchQuery(function result_cb, mixed result_args, mixed ... params)
258 results[++searches] = Result(result_cb, result_args);
259 return Search(searches, @params);
262 object searchAsyncAttribute(string key, mixed val, mixed ... params)
264 Async.Return r = Async.Return();
265 results[++searches] = Result(r->asyncResult, 0);
266 Search s = Search(searches, @params);
267 s->search_attribute(key, val);
272 object searchAsync(array extends, array limits, array fulltext, void|int cBits)
274 array classlist = ({ });
276 while ( (cBits > 0) && (i < (1<<31)) )
278 if ( (cBits & i) == cBits)
279 classlist += ({ _Database->get_class_string(i) });
282 object aResult = get_module("ServiceManager")->call_service_async("search",
283 "search_id": searches,
284 "classes": classlist,
287 "fulltext": fulltext, ]));
288 aResult->processFunc = handle_result;
292 array search_simple(string searchTerm, void|int classBit)
294 object handle = _Database->get_db_handle();
295 string query = "SELECT distinct ob_id from ob_class WHERE obkeywords";
296 if (search(searchTerm, "%") >= 0)
297 query += " like '"+ handle->quote(searchTerm) + "'";
299 query += "='"+handle->quote(searchTerm) +"'";
302 query += " AND ob_class='"+_Database->get_class_string(classBit)+"'";
303 object result = handle->big_query(query);
304 array resultArr = allocate(result->num_rows());
305 for (int i=0; i < result->num_rows(); i++) {
306 mixed row = result->fetch_row();
307 resultArr[i] = find_object((int)row[0]);
312 string object_to_dc(object obj)
314 string rdf = "<rdf:RDF\n"+
315 "xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n"+
316 "xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n";
318 object creator = obj->get_creator() || USER("root");
320 rdf += sprintf("<rdf:Description rdf:about=\"%s\">\n"+
321 "<dc:creator>%s</dc:creator>\n"+
322 "<dc:title>%s</dc:title>\n"+
323 "<dc:description>%s</dc:description>\n"+
324 "<dc:subject>%s</dc:subject>\n"+
325 "<dc:source>%s</dc:source>\n"+
326 "<dc:date>%s</dc:date>\n"+
327 "<dc:type>%s</dc:type>\n"+
328 "<dc:identifier>%s</dc:identifier>\n"+
329 "</rdf:Description>\n",
330 get_module("filepath:tree")->object_to_filename(obj),
332 obj->get_identifier(),
333 obj->query_attribute(OBJ_DESC),
336 (string)obj->query_attribute(OBJ_CREATION_TIME),
337 obj->query_attribute(DOC_MIME_TYPE) || "",
338 (string)obj->get_object_id()
343 object searchKeyword(string keyword)
345 object aResult = get_module("ServiceManager")->call_service_async(
347 "search_id": searches,
349 "extends": (["keyword": keyword]),
351 "fulltext": ({ }), ]));
352 aResult->processFunc = handle_result;
359 * Filters an array of objects and returns those objects matching specified
360 * filter rules, sorted in a specified order and with optional pagination.
362 * The filter entries are applied in the order in which they are given. Each
363 * filter must be an array like this:
364 * ({ +/-, "class", class-bitmask })
365 * ({ +/-, "attribute", attribute-name, condition, value/values })
366 * ({ +/-, "function", function-name, condition, value/values, [params] })
367 * ({ +/-, "access", access-bitmask })
368 * If an object doesn't match any filter rule, it will be excluded by
369 * default, so if you would like to include any objects that didn't match any
370 * filter, append ({ "+", "class", CLASS_ALL }) to the end of your filter list.
371 * If the second parameter of a filter is prefixed by an exclamation mark, then
372 * the filter rule will match if the condition is not met. E.g.:
373 * ({ -, "!access", access-bitmask })
375 * * +/- must be either "+" (include) or "-" (exclude) as a string.
376 * * class-bitmask must be either a CLASS_* constant or a combination (binary
377 * OR) of CLASS_* constants (e.g. CLASS_CONTAINER|CLASS_DOCUMENT).
378 * * attribute-name must be the name (key) of an attribute, e.g. "OBJ_NAME".
379 * * condition must be one of the following strings: "==", "!=", "<",
380 * "<=", ">", ">=", "prefix", "suffix". Note that some conditions will only
381 * match for certain attribute types, e.g. int, float or string.
382 * * value/values must be either a simple value like int, float, string,
383 * object, or an array of simple values, in which case the condition will
384 * try to match at least one of these values.
385 * * params is optional and must be an array of parameters to pass to the
386 * function if specified.
387 * * access-bitmask must be either a SANCTION_* constant or a combination
388 * (binary OR) of SANCTION_* constants (e.g. SANCTION_READ|SANCTION_ANNOTATE).
390 * The sort entries are applied in the order in which they are given in case
391 * some entries are considered equal regarding the previous sort rule. Each
392 * sort entry must be an array like this:
393 * ({ >/<, "class", class-order })
394 * ({ >/<, "attribute", attribute-name })
396 * * >/< must be "<" (ascending) or ">" (descending)
397 * * class-order is optional and can be an array of CLASS_* constants. The
398 * result will be sorted in the specified order by the objects that match
399 * the specified classes. All classes that were not specified will be
400 * considered equal for this sort entry.
401 * * attribute-name must be the name (key) of an attribute, e.g. "OBJ_NAME".
404 * Return all documents and containers (no users) that the user can read,
405 * sorted by type and then
407 * filter_objects_array(
409 * ({ "-", "!access", SANCTION_READ }),
410 * ({ "-", "class", CLASS_USER }),
411 * ({ "+", "class", CLASS_DOCUMENT|CLASS_CONTAINER })
414 * ({ "<", "class", ({ CLASS_CONTAINER, CLASS_DOCUMENT }) }),
415 * ({ "<", "attribute", "OBJ_NAME" })
419 * Return all documents with keywords "urgent" or "important" that the user
420 * has read access to, that are no wikis and that have been changed in the
421 * last 24 hours, sort them by modification date (newest first) and return
422 * only the first 10 results:
423 * filter_objects_array(
425 * ({ "-", "!access", SANCTION_READ }),
426 * ({ "-", "attribute", "OBJ_TYPE", "prefix", "container_wiki" }),
427 * ({ "-", "attribute", "DOC_LAST_MODIFIED", "<", time()-86400 }),
428 * ({ "-", "attribute", "OBJ_KEYWORDS", "!=", ({ "urgent", "important" }) }),
429 * ({ "+", "class", CLASS_DOCUMENT })
432 * ({ ">", "attribute", "DOC_LAST_MODIFIED" })
435 * @param objects the array of which to retrieve a filtered selection
436 * @param filters (optional) an array of filters (each an array as described
437 * above) that specify which objects to return
438 * @param sort (optional) an array of sort entries (each an array as described
439 * above) that specify the order of the items (before pagination)
440 * @param offset (optional) only return the objects starting at (and including)
442 * @param length (optional) only return a maximum of this many objects
443 * @return a mapping ([ "objects":({...}), "total":nr, "length":nr,
444 * "start":nr, "page":nr ]), where the "objects" value is an array of
445 * objects that match the specified filters, sort order and pagination.
446 * The other indices contain pagination information ("total" is the total
447 * number of objects after filtering but before applying "length", "length"
448 * is the requested number of items to return (as in the parameter list),
449 * "start" is the start index of the result in the total number of objects,
450 * and "page" is the page number (starting with 1) of pages with "length"
451 * objects each, or 0 if invalid).
453 mapping paginate_object_array ( array objects, array|void filters, array|void sort, int|void offset, int|void length )
456 if ( !arrayp(filters) || sizeof(filters) == 0 )
461 foreach ( objects, object obj ) {
462 if ( !objectp(obj) ) continue;
463 foreach ( filters, mixed filter ) {
464 if ( !arrayp(filter) || sizeof(filter) < 2 )
465 THROW( sprintf( "Invalid filter: %O", filter ), E_ERROR );
468 string filter_type = filter[1];
469 if ( has_prefix( filter_type, "!" ) ) {
471 filter_type = filter_type[1..];
473 switch ( filter_type ) {
476 if ( sizeof(filter) < 3 )
477 THROW( sprintf( "Invalid filter: %O", filter ), E_ERROR );
478 int obj_class = obj->get_object_class();
479 if ( invert != ((filter[2] & obj_class) != 0) ) { // matches class
480 if ( filter[0] == "+" ) { // include class
484 else done = 1; // exclude class
489 if ( sizeof(filter) < 3 )
490 THROW( sprintf( "Invalid filter: %O", filter ), E_ERROR );
491 if ( invert != (get_module( "security" )->check_user_access( obj,
492 this_user(), filter[2], 0, false ) != 0) ) {
493 if ( filter[0] == "+" ) { // include
497 else done = 1; // exclude
503 if ( sizeof(filter) < 5 )
504 THROW( sprintf( "Invalid filter: %O", filter ), E_ERROR );
506 if ( filter_type == "function" ) {
507 mixed func_name = filter[2];
508 if ( sizeof(filter) > 5 ) {
509 mixed func_params = filter[5];
510 if ( !arrayp(func_params) ) func_params = ({ func_params });
511 catch( value = obj->find_function(func_name)( @func_params ) );
514 catch( value = obj->find_function(func_name)() );
518 value = obj->query_attribute( filter[2] );
519 if ( mappingp(value) )
520 break; // ignore mappings
521 mixed target_value = filter[4];
522 switch ( filter[3] ) { // condition:
524 if ( !arrayp(value) ) {
525 if ( invert != (value == target_value) ) done = 1;
528 foreach ( value, mixed subvalue ) {
529 if ( subvalue == target_value ) {
534 if ( invert ) done = !done;
537 if ( !arrayp(value) ) {
538 if ( invert != (value != target_value) ) done = 1;
542 foreach ( value, mixed subvalue ) {
543 if ( subvalue == target_value ) {
549 if ( invert ) done = !done;
552 if ( !arrayp(value) ) {
553 if ( invert != (value <= target_value) ) done = 1;
556 foreach ( value, mixed subvalue ) {
557 if ( subvalue <= target_value ) {
562 if ( invert ) done = !done;
565 if ( !arrayp(value) ) {
566 if ( invert != (value >= target_value) ) done = 1;
569 foreach ( value, mixed subvalue ) {
570 if ( subvalue >= target_value ) {
575 if ( invert ) done = !done;
578 if ( !arrayp(value) ) {
579 if ( invert != (value < target_value) ) done = 1;
582 foreach ( value, mixed subvalue ) {
583 if ( subvalue < target_value ) {
588 if ( invert ) done = !done;
591 if ( !arrayp(value) ) {
592 if ( invert != (value > target_value) ) done = 1;
595 foreach ( value, mixed subvalue ) {
596 if ( subvalue > target_value ) {
601 if ( invert ) done = !done;
604 if ( stringp(value) ) {
605 if ( invert != has_prefix( value, target_value ) ) done = 1;
608 if ( !arrayp(value) ) {
609 if ( invert ) done = 1;
612 foreach ( value, mixed subvalue ) {
613 if ( !stringp(subvalue) ) continue;
614 if ( invert != has_prefix( subvalue, target_value ) ) {
619 if ( invert ) done = !done;
622 if ( stringp(value) ) {
623 if ( invert != has_suffix( value, target_value ) ) done = 1;
626 if ( !arrayp(value) ) {
627 if ( invert ) done = 1;
630 foreach ( value, mixed subvalue ) {
631 if ( !stringp(subvalue) ) continue;
632 if ( has_suffix( subvalue, target_value ) ) {
637 if ( invert ) done = !done;
640 if ( done && filter[0] == "+" ) { // condition matches
642 } // otherwise "done" is set and the object will be excluded
652 if ( arrayp(sort) && sizeof(sort) > 0 ) {
653 result = Array.sort_array( result, sort_objects_filter, sort );
656 mapping info = ([ "total":sizeof(result), "length":length, "start":offset,
657 "page":0, "objects":({ }) ]);
658 if ( offset >= sizeof(result) ) return info;
659 if ( offset != 0 || ( length > 0 && length < sizeof(result) ) ) {
660 if ( length < 1 || (offset + length >= sizeof(result)) )
661 length = sizeof(result) - offset;
662 result = result[ offset .. (offset+length-1) ];
664 if ( result == objects )
665 result = copy_value( objects );
666 info["objects"] = result;
667 info["page"] = (int)ceil( (float)length / (float)info["total"] );
672 * Filters an object array according to filter rules, sorting, offset and
673 * length. This returns the same as the "objects" index in the result of
674 * paginate_object_array() and is here for compatibility reasons and ease of
675 * use (if you don't need pagination information).
677 * @see paginate_object_array
679 array filter_object_array ( array objects, array|void filters, array|void sort, int|void offset, int|void length )
681 return paginate_object_array( objects, filters, sort, offset, length )["objects"];
684 object paginate_search_async ( array|void filters, array|void sort, int|void offset, int|void length )
686 array limits = ({ });
687 array extends = ({ });
688 array fulltext = ({ });
691 array remaining_filters = ({ });
693 foreach ( filters, mixed filter ) {
694 if ( !arrayp(filter) || sizeof(filter) < 2 )
695 THROW( sprintf( "Invalid filter: %O", filter ), E_ERROR );
697 string filter_type = filter[1];
698 if ( has_prefix( filter_type, "!" ) ) {
699 // inverted filters are currently not handled in the search itself,
700 // they will be applied after the search.
701 remaining_filters += ({ filter });
705 switch ( filter_type ) {
708 if ( sizeof(filter) < 3 )
709 THROW( sprintf( "Invalid filter: %O", filter ), E_ERROR );
710 if ( filter[0] == "+" ) classes |= filter[2];
711 else classes_not |= filter[2];
715 if ( sizeof(filter) < 3 )
716 THROW( sprintf( "Invalid filter: %O", filter ), E_ERROR );
717 // access filters are currently applied after the search, they are not
718 // handled by the search itself
719 remaining_filters += ({ filter });
724 if ( sizeof(filter) < 5 )
725 THROW( sprintf( "Invalid filter: %O", filter ), E_ERROR );
726 // functions are currently not handled by the search itself, these
727 // filters will be applied after the search
728 remaining_filters += ({ filter });
733 if ( sizeof(filter) < 5 )
734 THROW( sprintf( "Invalid filter: %O", filter ), E_ERROR );
735 string attrib = filter[2];
736 string condition = filter[3];
737 mixed target_value = filter[4];
738 switch ( condition ) {
748 // condition is okay for search
753 // like doesn't check strictly enough, so use it for a first
754 // selection and then run the filter after the search
755 remaining_filters += ({ filter });
758 THROW( "Invalid condition for search: " + condition, E_ERROR );
760 if ( !arrayp( target_value ) ) target_value = ({ target_value });
761 foreach ( target_value, mixed value ) {
762 if ( filter[0] == "+" ) extends += ({ ([ "storage":"attrib",
763 "key":attrib, "value":({ condition, value }) ]) });
764 else limits += ({ ([ "storage":"attrib",
765 "key":attrib, "value":({ condition, value }) ]) });
770 if ( sizeof(filter) < 3 )
771 THROW( sprintf( "Invalid filter: %O", filter ), E_ERROR );
772 // limiting by content is not supported at the moment:
773 if ( filter[0] != "+" ) break;
774 fulltext += ({ ([ "storage":"doc_ft", "value":filter[2] ]) });
779 if ( classes == 0 ) classes = CLASS_ALL & (~classes_not);
780 array classlist = ({ });
781 if ( classes != CLASS_ALL ) {
784 mixed class_string = _Database->get_class_string(cBits);
785 if ( class_string != "/classes/Object" )
786 classlist += ({ class_string });
791 object async_result = get_module("ServiceManager")->call_service_async(
792 "extends":extends, "limits":limits, "fulltext":fulltext ])
794 async_result->processFunc = handle_paginate_search_result;
795 async_result->userData = ([ "filters":remaining_filters, "sort":sort,
796 "offset":offset, "length":length ]);
801 mapping handle_paginate_search_result ( array res, mapping data )
803 array objects = ({ });
804 foreach ( res, mixed obj ) {
805 if ( stringp(obj) ) obj = (int)obj;
806 if ( intp(obj) ) obj = find_object( obj );
807 if ( objectp(obj) ) objects += ({ obj });
809 return paginate_object_array( objects, data->filters, data->sort,
810 data->offset, data->length );
815 object filter_search_async ( array|void filters, array|void sort, int|void offset, int|void length )
817 object async_result = paginate_search_async( filters, sort, offset, length );
818 async_result->processFunc = handle_filter_search_result;
823 array handle_filter_search_result ( array res, mapping data )
825 array objects = ({ });
826 foreach ( res, mixed obj ) {
827 if ( stringp(obj) ) obj = (int)obj;
828 if ( intp(obj) ) obj = find_object( obj );
829 if ( objectp(obj) ) objects += ({ obj });
831 return filter_object_array( objects, data->filters, data->sort,
832 data->offset, data->length );
838 int sort_objects_filter ( object obj1, object obj2, array rules )
840 foreach ( rules, array rule ) {
842 if ( rule[0] == ">" ) reverse = 1;
845 int obj_class1 = obj1->get_object_class();
846 int obj_class2 = obj2->get_object_class();
847 if ( sizeof(rule) < 3 ) {
848 if ( obj_class1 > obj_class2 ) return 1 ^ reverse;
849 else if ( obj_class1 < obj_class2 ) return 0 ^ reverse;
855 foreach ( rule[2], int obj_class ) {
856 if ( obj_class & obj_class1 ) index1 = index;
857 if ( obj_class & obj_class2 ) index2 = index;
858 if ( index1 >= 0 && index2 >= 0 ) break;
861 if ( index1 > index2 ) return 1 ^ reverse;
862 else if ( index1 < index2 ) return 0 ^ reverse;
868 if ( arrayp( rule[2] ) ) {
869 foreach ( rule[2], mixed key ) {
870 if ( !value1 ) value1 = obj1->query_attribute( key );
871 if ( !value2 ) value2 = obj2->query_attribute( key );
875 value1 = obj1->query_attribute( rule[2] );
876 value2 = obj2->query_attribute( rule[2] );
878 if ( value1 == 0 && value2 == 0 ) continue;
879 if ( stringp(value1) && value2 == 0 ) return 1 ^ reverse;
880 else if ( value1 == 0 && stringp(value2) ) return 0 ^ reverse;
881 else if ( stringp(value1) && stringp(value2) ) {
882 if ( value1 > value2 ) return 1 ^ reverse;
883 else if ( value1 < value2 ) return 0 ^ reverse;
886 else if ( (intp(value1) || floatp(value1)) &&
887 (intp(value2) || floatp(value2)) ) {
888 if ( value1 > value2 ) return 1 ^ reverse;
889 else if ( value1 < value2 ) return 0 ^ reverse;
902 private array test_objects = ({ });
905 // first create some objects to search
906 object obj = get_factory(CLASS_DOCUMENT)->execute((["name": "document.doc", ]));
907 obj->set_attribute(OBJ_KEYWORDS, ({ "Mistel", "approach" }));
908 test_objects += ({ obj });
909 // test external search service
910 Test.add_test_function(test_search, 20, 1, ([ ]));
915 if ( arrayp(test_objects) ) {
916 foreach ( test_objects, object obj )
917 catch ( obj->delete() );
921 void search_test_finished(object result, array results)
923 if ( sizeof(results) == 0 ) {
924 Test.failed(result->userData->name,
925 "Search %s finished with %d results in %d ms",
926 result->userData->name,
928 get_time_millis()-result->userData->time);
931 Test.succeeded(result->userData->name,
932 "Search %s finished with %d results in %d ms",
933 result->userData->name,
935 get_time_millis()-result->userData->time);
937 result->userData->tests[result->userData->name] = 1;
941 void test_search(int nr_tries, mapping tests)
943 object serviceManager = get_module("ServiceManager");
945 if ( !Test.test("Service Manager",
946 objectp(serviceManager), "Failed to find ServiceManager!") )
949 if ( !serviceManager->is_service("search") ) {
951 Test.failed("search service",
952 "failed to locate search services after %d tries",
955 Test.add_test_function(test_search, 10, nr_tries+1, tests);
958 if ( sizeof(tests) != 0 ) {
959 foreach(values(tests), int i) {
961 werror("Waiting for tests to finish!");
962 Test.add_test_function(test_search, 10, nr_tries+1, tests);
968 // now search for common queries
969 object result, query;
972 tests["simple1"] = 0;
973 query = searchQuery(search_test_finished, ([ ]), ({ }));
974 query->extend(STORE_ATTRIB, OBJ_NAME, query->like("steam"));
975 result = query->run_async();
976 result->resultFunc = search_test_finished;
977 result->userData = ([ "name": "simple1",
979 "time":get_time_millis(),]);
981 tests["simple2"] = 0;
982 query = searchQuery(search_test_finished, ([ ]), ({ }));
983 query->extend(STORE_ATTRIB, OBJ_NAME, query->like("steam"));
984 query->extend(STORE_ATTRIB, OBJ_DESC, query->like("steam"));
985 query->extend(STORE_ATTRIB, OBJ_KEYWORDS, query->like("steam"));
986 result = query->run_async();
987 result->resultFunc = search_test_finished;
988 result->userData = ([ "name": "simple2",
990 "time":get_time_millis(),]);
993 tests["keywords"] = 0;
994 query = searchQuery(search_test_finished, ([ ]), ({ }));
995 query->extend(STORE_ATTRIB, OBJ_KEYWORDS, query->like("Mistel"));
996 result = query->run_async();
997 result->resultFunc = search_test_finished;
998 result->userData = ([ "name": "keywords",
1000 "time":get_time_millis(),]);
1002 tests["target room"] = 0;
1003 query = searchQuery(search_test_finished, ([ ]), ({ "\"/classes/Room\"" }));
1004 query->extend(STORE_ATTRIB, OBJ_NAME, query->like("coder%"));
1005 result = query->run_async();
1006 result->resultFunc = search_test_finished;
1007 result->userData = ([ "name": "target room",
1009 "time":get_time_millis(),]);
1011 tests["user 1"] = 0;
1012 query = searchQuery(search_test_finished, ([ ]), ({ "\"/classes/User\"" }));
1013 query->extend(STORE_ATTRIB, OBJ_NAME, query->like("service"));
1014 result = query->run_async();
1015 result->resultFunc = search_test_finished;
1016 result->userData = ([ "name": "user 1",
1018 "time":get_time_millis(),]);
1020 Test.add_test_function(test_search, 10, 0, tests);