mirror of
https://github.com/quickwit-oss/tantivy.git
synced 2026-10-27 14:06:31 +00:00
Added serve command
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
extern crate tantivy;
|
||||
extern crate persistent;
|
||||
extern crate time;
|
||||
extern crate urlencoded;
|
||||
extern crate rustc_serialize;
|
||||
extern crate iron;
|
||||
extern crate staticfile;
|
||||
extern crate mount;
|
||||
|
||||
use tantivy::schema::Field;
|
||||
use tantivy::collector::CountCollector;
|
||||
use tantivy::Index;
|
||||
use tantivy::collector;
|
||||
use urlencoded::UrlEncodedQuery;
|
||||
use iron::status;
|
||||
use rustc_serialize::json::as_pretty_json;
|
||||
use std::path::Path;
|
||||
use staticfile::Static;
|
||||
use iron::mime::Mime;
|
||||
use mount::Mount;
|
||||
use tantivy::query::Query;
|
||||
use tantivy::query::QueryParser;
|
||||
use tantivy::Document;
|
||||
use tantivy::collector::TopCollector;
|
||||
use persistent::Read;
|
||||
use iron::typemap::Key;
|
||||
use iron::prelude::*;
|
||||
use std::io;
|
||||
|
||||
|
||||
#[derive(RustcDecodable, RustcEncodable)]
|
||||
struct Serp {
|
||||
q: String,
|
||||
num_hits: usize,
|
||||
hits: Vec<Hit>,
|
||||
timings: Vec<Timing>,
|
||||
}
|
||||
|
||||
#[derive(RustcDecodable, RustcEncodable)]
|
||||
struct Hit {
|
||||
title: String,
|
||||
body: String,
|
||||
}
|
||||
|
||||
#[derive(RustcDecodable, RustcEncodable)]
|
||||
struct Timing {
|
||||
name: String,
|
||||
duration: i64,
|
||||
}
|
||||
|
||||
struct IndexServer {
|
||||
index: Index,
|
||||
query_parser: QueryParser,
|
||||
body_field: Field,
|
||||
title_field: Field,
|
||||
}
|
||||
|
||||
impl IndexServer {
|
||||
|
||||
fn load(path: &Path) -> IndexServer {
|
||||
let index = Index::open(path).unwrap();
|
||||
let schema = index.schema();
|
||||
let body_field = schema.get_field("body").unwrap();
|
||||
let title_field = schema.get_field("title").unwrap();
|
||||
let query_parser = QueryParser::new(schema, vec!(body_field, title_field));
|
||||
IndexServer {
|
||||
index: index,
|
||||
query_parser: query_parser,
|
||||
title_field: title_field,
|
||||
body_field: body_field,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_hit(&self, doc: &Document) -> Hit {
|
||||
Hit {
|
||||
title: String::from(doc.get_first(self.title_field).unwrap().text()),
|
||||
body: String::from(doc.get_first(self.body_field).unwrap().text().clone()),
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fn search(&self, q: String) -> io::Result<Serp> {
|
||||
let query = self.query_parser.parse_query(&q).unwrap();
|
||||
let searcher = self.index.searcher().unwrap();
|
||||
let mut count_collector = CountCollector::new();
|
||||
let mut top_collector = TopCollector::with_limit(10);
|
||||
|
||||
{
|
||||
let mut chained_collector = collector::chain()
|
||||
.add(&mut top_collector)
|
||||
.add(&mut count_collector);
|
||||
try!(query.search(&searcher, &mut chained_collector));
|
||||
}
|
||||
let hits: Vec<Hit> = try!(
|
||||
top_collector.docs()
|
||||
.iter()
|
||||
.map(|doc_address| searcher.doc(doc_address))
|
||||
.map(|doc_result| doc_result.map(|doc| self.create_hit(&doc) ))
|
||||
.collect()
|
||||
);
|
||||
Ok(Serp {
|
||||
q: q,
|
||||
hits: hits,
|
||||
num_hits: count_collector.count(),
|
||||
timings: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
impl Key for IndexServer {
|
||||
type Value = IndexServer;
|
||||
}
|
||||
|
||||
|
||||
fn search(req: &mut Request) -> IronResult<Response> {
|
||||
let index_server = req.get::<Read<IndexServer>>().unwrap();
|
||||
match req.get_ref::<UrlEncodedQuery>() {
|
||||
Ok(ref qs_map) => {
|
||||
match qs_map.get("q") {
|
||||
Some(qs) => {
|
||||
let query = qs[0].clone();
|
||||
let serp = index_server.search(query).unwrap();
|
||||
let resp_json = as_pretty_json(&serp).indent(4);
|
||||
let content_type = "application/json".parse::<Mime>().unwrap();
|
||||
Ok(
|
||||
Response::with((content_type, status::Ok, format!("{}", resp_json)))
|
||||
)
|
||||
}
|
||||
None => {
|
||||
Ok(Response::with((status::BadRequest, "Query not defined")))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => Ok(Response::with((status::BadRequest, "Failed to parse query string")))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn main() {
|
||||
let mut mount = Mount::new();
|
||||
let server = IndexServer::load(&Path::new("/data/wiki-index/"));
|
||||
|
||||
mount.mount("/api", search);
|
||||
mount.mount("/", Static::new(Path::new("static/")));
|
||||
|
||||
let mut middleware = Chain::new(mount);
|
||||
middleware.link(Read::<IndexServer>::both(server));
|
||||
|
||||
let host = "127.0.0.1:3000";
|
||||
println!("listening on http://{}", host);
|
||||
Iron::new(middleware).http("127.0.0.1:3000").unwrap();
|
||||
}
|
||||
@@ -12,7 +12,6 @@ use postings::UnionPostings;
|
||||
use postings::ScoredDocSet;
|
||||
use postings::DocSet;
|
||||
use query::MultiTermScorer;
|
||||
use std::iter;
|
||||
use fastfield::U32FastFieldReader;
|
||||
use ScoredDoc;
|
||||
|
||||
@@ -74,10 +73,12 @@ impl MultiTermQuery {
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let query_coord = iter::repeat(1f32).take(self.terms.len()).collect();
|
||||
MultiTermScorer::new(query_coord, idfs)
|
||||
let query_coords = (0..self.terms.len() + 1)
|
||||
.map(|i| i as f32 / self.terms.len() as f32)
|
||||
.collect();
|
||||
MultiTermScorer::new(query_coords, idfs)
|
||||
}
|
||||
|
||||
|
||||
pub fn new(terms: Vec<Term>) -> MultiTermQuery {
|
||||
MultiTermQuery {
|
||||
terms: terms,
|
||||
@@ -96,7 +97,7 @@ impl MultiTermQuery {
|
||||
let field = term.get_field();
|
||||
fieldnorms_readers.push(reader.get_fieldnorms_reader(field).unwrap());
|
||||
segment_postings.push(postings);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
UnionPostings::new(fieldnorms_readers, segment_postings, multi_term_scorer)
|
||||
|
||||
Reference in New Issue
Block a user