changeset 544:c5a93767cc5c

lucene overhaul, untested
author Franklin Schmidt <fschmidt@gmail.com>
date Fri, 12 Jun 2015 19:11:44 -0600
parents 9767da72545b
children ddcd4296107a
files lucene/ext/lucene-queryparser-4.9.0.jar lucene/ext/sane-lucene-queryparser.jar lucene/src/luan/modules/lucene/Ab_testing.luan lucene/src/luan/modules/lucene/FieldTable.java lucene/src/luan/modules/lucene/LuanQueryNodeProcessor.java lucene/src/luan/modules/lucene/Lucene.luan lucene/src/luan/modules/lucene/LuceneDocument.java lucene/src/luan/modules/lucene/LuceneIndex.java lucene/src/luan/modules/lucene/LuceneSearcher.java lucene/src/luan/modules/lucene/LuceneWriter.java lucene/src/luan/modules/lucene/Web_search.luan scripts/test.luan
diffstat 12 files changed, 134 insertions(+), 356 deletions(-) [+]
line wrap: on
line diff
Binary file lucene/ext/lucene-queryparser-4.9.0.jar has changed
Binary file lucene/ext/sane-lucene-queryparser.jar has changed
--- a/lucene/src/luan/modules/lucene/Ab_testing.luan	Mon Jun 08 01:11:08 2015 -0400
+++ b/lucene/src/luan/modules/lucene/Ab_testing.luan	Fri Jun 12 19:11:44 2015 -0600
@@ -10,6 +10,7 @@
 local Io = require "luan:Io"
 local Http = require "luan:http/Http"
 local Logging = require "luan:logging/Logging"
+local Lucene = require "luan:lucene/Lucene"
 
 local M = {}
 
@@ -34,8 +35,8 @@
 		-- test.date_field is optional
 
 		local field = "ab_test_" .. test.name
-		index.fields[field] == nil or error("test "+test.name+" already defined")
-		index.fields[field] = field .. " index"
+		index.indexed_fields[field] == nil or error("test "+test.name+" already defined")
+		index.indexed_fields[field] = Lucene.type.string
 		test.field = field
 
 		-- returns map of event name to (map of value to result) and "start_date"
@@ -52,7 +53,7 @@
 					for name, factory in pairs(test.aggregator_factories) do
 						aggregators[name] = factory()
 					end
-					local query = index.query.term{ [field] = value }
+					local query = field..":"..value
 					searcher.search(query, function(doc)
 						for _, aggregator in pairs(aggregators) do
 							aggregator.aggregate(doc)
--- a/lucene/src/luan/modules/lucene/FieldTable.java	Mon Jun 08 01:11:08 2015 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-package luan.modules.lucene;
-
-import java.util.Map;
-import java.util.HashMap;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.Iterator;
-import luan.LuanState;
-import luan.LuanTable;
-import luan.LuanMeta;
-
-
-class FieldTable extends LuanMeta {
-	final Map<String,String> map = new ConcurrentHashMap<String,String>();
-	final Map<String,String> reverseMap = new ConcurrentHashMap<String,String>();
-
-	FieldTable() {
-		put("type","type index");
-		put("id","id index");
-	}
-
-	@Override public boolean canNewindex() {
-		return true;
-	}
-
-	@Override public void __new_index(LuanState luan,LuanTable tbl,Object key,Object value) {
-		put(key,value);
-	}
-
-	private void put(Object key,Object value) {
-		if( !(key instanceof String) )
-			throw new UnsupportedOperationException("key must be string");
-		String name = (String)key;
-		if( value==null ) {  // delete
-			reverseMap.remove(map.remove(name));
-			return;
-		}
-		if( !(value instanceof String) )
-			throw new UnsupportedOperationException("value must be string");
-		String field = (String)value;
-		String oldField = map.put(name,field);
-		if( oldField != null )
-			reverseMap.remove(oldField);
-		String oldName = reverseMap.put(field,name);
-		if( oldName != null ) {
-			reverseMap.put(field,oldName);
-			map.remove(name);
-			throw new IllegalArgumentException("field '"+oldName+"' is already assigned to '"+field+"'");
-		}
-	}
-
-	@Override public Object __index(LuanState luan,LuanTable tbl,Object key) {
-//	@Override public final Object get(Object key) {
-		return map.get(key);
-	}
-
-	@Override public final Iterator keys(LuanTable tbl) {
-		return map.keySet().iterator();
-	}
-
-	@Override protected String type(LuanTable tbl) {
-		return "lucene-field";
-	}
-
-}
--- a/lucene/src/luan/modules/lucene/LuanQueryNodeProcessor.java	Mon Jun 08 01:11:08 2015 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-package luan.modules.lucene;
-
-import java.util.List;
-import org.apache.lucene.queryparser.flexible.core.processors.QueryNodeProcessor;
-import org.apache.lucene.queryparser.flexible.core.config.QueryConfigHandler;
-import org.apache.lucene.queryparser.flexible.core.nodes.QueryNode;
-import org.apache.lucene.queryparser.flexible.core.nodes.FieldQueryNode;
-import org.apache.lucene.queryparser.flexible.core.QueryNodeException;
-
-
-public class LuanQueryNodeProcessor implements QueryNodeProcessor {
-	private final LuceneIndex index;
-	private final QueryNodeProcessor qnp;
-
-	public LuanQueryNodeProcessor(LuceneIndex index,QueryNodeProcessor qnp) {
-		this.index = index;
-		this.qnp = qnp;
-	}
-
-	public QueryConfigHandler getQueryConfigHandler() {
-		return qnp.getQueryConfigHandler();
-	}
-
-	public void setQueryConfigHandler(QueryConfigHandler queryConfigHandler) {
-		qnp.setQueryConfigHandler(queryConfigHandler);
-	}
-
-	public QueryNode process(QueryNode queryTree) throws QueryNodeException {
-		fix(queryTree);
-//		System.out.println(queryTree);
-		return qnp.process(queryTree);
-	}
-
-	private void fix(QueryNode queryTree) {
-		if( queryTree instanceof FieldQueryNode ) {
-			FieldQueryNode fqn = (FieldQueryNode)queryTree;
-			CharSequence fldSeq = fqn.getField();
-			if( fldSeq == null )
-				throw new RuntimeException("missing field for value: "+fqn.getText());
-			String fld = fldSeq.toString();
-			fld = index.map_field_name(fld);
-//			System.out.println("field = "+fld);
-			fqn.setField(fld);
-		}
-		List<QueryNode> list = queryTree.getChildren();
-		if( list != null ) {
-			for( QueryNode qn : list ) {
-				fix(qn);
-			}
-		}
-	}
-}
--- a/lucene/src/luan/modules/lucene/Lucene.luan	Mon Jun 08 01:11:08 2015 -0400
+++ b/lucene/src/luan/modules/lucene/Lucene.luan	Fri Jun 12 19:11:44 2015 -0600
@@ -1,39 +1,28 @@
 java()
 local Luan = require "luan:Luan"
-local pairs = Luan.pairs
-local ipairs = Luan.ipairs
-local type = Luan.type
-local number_type = Luan.number_type
 local error = Luan.error
-local Number = require "luan:Number"
-local number_type = Number.number_type
-local Table = require "luan:Table"
-local unpack = Table.unpack
 local LuceneIndex = require "java:luan.modules.lucene.LuceneIndex"
-local Term = require "java:org.apache.lucene.index.Term"
-local TermQuery = require "java:org.apache.lucene.search.TermQuery"
-local TermRangeQuery = require "java:org.apache.lucene.search.TermRangeQuery"
-local NumericRangeQuery = require "java:org.apache.lucene.search.NumericRangeQuery"
-local MatchAllDocsQuery = require "java:org.apache.lucene.search.MatchAllDocsQuery"
-local BooleanQuery = require "java:org.apache.lucene.search.BooleanQuery"
-local BooleanClause = require "java:org.apache.lucene.search.BooleanClause"
-local BytesRef = require "java:org.apache.lucene.util.BytesRef"
-local NumericUtils = require "java:org.apache.lucene.util.NumericUtils"
-local Sort = require "java:org.apache.lucene.search.Sort"
-local SortField = require "java:org.apache.lucene.search.SortField"
+local NumberFieldParser = require "java:sane.lucene.queryparser.NumberFieldParser"
+
 
 local M = {}
 
+M.type = {
+	string = LuceneIndex.STRING_FIELD_PARSER;
+	integer = NumberFieldParser.INT;
+	long = NumberFieldParser.LONG;
+	double = NumberFieldParser.DOUBLE;
+}
+
 function M.index(indexDir)
 	local index = {}
 	local java_index = LuceneIndex.new(indexDir)
-	index.fields = java_index.fields.newTable()
+	index.indexed_fields = java_index.indexedFieldsMeta.newTable()
 	index.to_string = java_index.to_string
 	index.backup = java_index.backup
 	index.Writer = java_index.Writer
 	index.Searcher = java_index.Searcher
 	index.delete_all = java_index.delete_all
-	index.map_field_name = java_index.map_field_name
 	index.close = java_index.close
 
 	function index.save_document(doc)
@@ -70,120 +59,6 @@
 		end )
 	end
 
-
-
-	local queryTbl = {}
-	index.query = queryTbl
-
-	queryTbl.parse = java_index.parse
-
-	queryTbl.all_docs = MatchAllDocsQuery.new()
-	
-	function queryTbl.term(field,value)
-		if value==nil and type(field)=="table" then
-			local iter = pairs(field)
-			field, value = iter()
-			iter()==nil or error "only one term allowed"
-		end
-		field and value or error "missing term"
-		field = index.map_field_name(field)
-		local tp = type(value)
-		if tp == "string" then
-			return TermQuery.new(Term.new(field,value))
-		end
-		if tp == "number" then
-			local br = BytesRef.new()
-			tp = number_type(value)
-			if tp == "integer" then
-				NumericUtils.intToPrefixCoded(value,0,br)
-			elseif tp == "long" then
-				NumericUtils.longToPrefixCoded(value,0,br)
-			elseif tp == "double" then
-				value = NumericUtils.doubleToSortableLong(value)
-				NumericUtils.longToPrefixCoded(value,0,br)
-			else
-				error("invalid term value type: "..tp)
-			end
-			return TermQuery.new(Term.new(field,br))
-		end
-		error("invalid term value type: "..tp)
-	end
-
-	function queryTbl.range(field,min,max,include_min,include_max)
-		if include_min == nil then include_min = true end
-		if include_max == nil then include_max = true end
-		field = index.map_field_name(field)
-		local tp = type(min)
-		tp == type(max) or error "min and max types don't match"
-		if tp == "string" then
-			return TermRangeQuery.newStringRange(field,min,max,include_min,include_max)
-		end
-		if tp == "number" then
-			tp = number_type(min)
-			tp == number_type(max) or error "min and max number types don't match"
-			if tp == "integer" then
-				return NumericRangeQuery.newIntRange(field,min,max,include_min,include_max)
-			end
-			if tp == "long" then
-				return NumericRangeQuery.newLongRange(field,min,max,include_min,include_max)
-			end
-			if tp == "double" then
-				return NumericRangeQuery.newDoubleRange(field,min,max,include_min,include_max)
-			end
-			error("invalid term value type: "..tp)
-		end
-		error("invalid term value type: "..tp)
-	end
-
-	function queryTbl.boolean(t)
-		local boolean_query = BooleanQuery.new()
-		for query, occur_string in pairs(t) do
-			local occur = BooleanClause.Occur.valueOf( occur_string.upper() )
-			boolean_query.add( query, occur )
-		end
-		return boolean_query
-	end
-	
-	-- and list
-	function queryTbl.all(t)
-		local bt = {}
-		for key, query in pairs(t) do
-			if type(key)=="string" then
-				query = queryTbl.term{[key]=query}
-			end
-			bt[query] = "MUST"
-		end
-		return queryTbl.boolean(bt)
-	end
-	
-	-- or list
-	function queryTbl.any(t)
-		local bt = {}
-		for key, query in pairs(t) do
-			if type(key)=="string" then
-				query = queryTbl.term{[key]=query}
-			end
-			bt[query] = "SHOULD"
-		end
-		return queryTbl.boolean(bt)
-	end
-	
-	
-	function queryTbl.sort(fields)
-		#fields > 0 or error "list of sort fields expected"
-		local a = {}
-		for _, f in ipairs(fields) do
-			f.field or error "missing sort field"
-			f.type or error "missing sort type"
-			local field = index.map_field_name(f.field)
-			local type = SortField.Type.valueOf( f.type.upper() )
-			local reverse = f.reverse or false
-			a[#a+1] = SortField.new(field,type,reverse)
-		end
-		return Sort.new(unpack(a))
-	end
-
-
 	return index
 end
 
--- a/lucene/src/luan/modules/lucene/LuceneDocument.java	Mon Jun 08 01:11:08 2015 -0400
+++ b/lucene/src/luan/modules/lucene/LuceneDocument.java	Fri Jun 12 19:11:44 2015 -0600
@@ -21,12 +21,9 @@
 
 
 public class LuceneDocument {
-	// I assume there will be more flags later
-	public static final String INDEX = "index";
-
 	private LuceneDocument(String a) {}  // never
 
-	static Document toLucene(LuanState luan,LuanTable table,Map<String,String> nameMap) throws LuanException {
+	static Document toLucene(LuanState luan,LuanTable table,Set<String> indexed) throws LuanException {
 		Document doc = new Document();
 		for( Map.Entry<Object,Object> entry : table.iterable(luan) ) {
 			Object key = entry.getKey();
@@ -34,38 +31,30 @@
 				throw luan.exception("key must be string");
 			String name = (String)key;
 			Object value = entry.getValue();
-			String newName = nameMap.get(name);
-			if( newName != null )
-				name = newName;
-			Set<String> flags = new HashSet<String>();
-			String[] a = name.split(" +");
-			for( int i=1; i<a.length; i++ ) {
-				flags.add(a[i]);
-			}
 			if( value instanceof String ) {
 				String s = (String)value;
-				if( flags.remove(INDEX) ) {
+				if( indexed.contains(name) ) {
 					doc.add(new StringField(name, s, Field.Store.YES));
 				} else {
 					doc.add(new StoredField(name, s));
 				}
 			} else if( value instanceof Integer ) {
 				int i = (Integer)value;
-				if( flags.remove(INDEX) ) {
+				if( indexed.contains(name) ) {
 					doc.add(new IntField(name, i, Field.Store.YES));
 				} else {
 					doc.add(new StoredField(name, i));
 				}
 			} else if( value instanceof Long ) {
 				long i = (Long)value;
-				if( flags.remove(INDEX) ) {
+				if( indexed.contains(name) ) {
 					doc.add(new LongField(name, i, Field.Store.YES));
 				} else {
 					doc.add(new StoredField(name, i));
 				}
 			} else if( value instanceof Double ) {
 				double i = (Double)value;
-				if( flags.remove(INDEX) ) {
+				if( indexed.contains(name) ) {
 					doc.add(new DoubleField(name, i, Field.Store.YES));
 				} else {
 					doc.add(new StoredField(name, i));
@@ -75,21 +64,16 @@
 				doc.add(new StoredField(name, b));
 			} else
 				throw luan.exception("invalid value type "+value.getClass()+"' for '"+name+"'");
-			if( !flags.isEmpty() )
-				throw luan.exception("invalid flags "+flags+" in '"+name+"'");
 		}
 		return doc;
 	}
 
-	static LuanTable toTable(LuanState luan,Document doc,Map<String,String> nameMap) throws LuanException {
+	static LuanTable toTable(LuanState luan,Document doc) throws LuanException {
 		if( doc==null )
 			return null;
 		LuanTable table = new LuanTable();
 		for( IndexableField ifld : doc ) {
 			String name = ifld.name();
-			String newName = nameMap.get(name);
-			if( newName != null )
-				name = newName;
 			BytesRef br = ifld.binaryValue();
 			if( br != null ) {
 				table.rawPut(name,br.bytes);
--- a/lucene/src/luan/modules/lucene/LuceneIndex.java	Mon Jun 08 01:11:08 2015 -0400
+++ b/lucene/src/luan/modules/lucene/LuceneIndex.java	Fri Jun 12 19:11:44 2015 -0600
@@ -5,12 +5,13 @@
 import java.io.FileOutputStream;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.util.Iterator;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.zip.ZipOutputStream;
 import java.util.zip.ZipEntry;
 import org.apache.lucene.analysis.Analyzer;
-import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.analysis.core.KeywordAnalyzer;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.index.IndexWriterConfig;
@@ -23,8 +24,15 @@
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.search.TopDocs;
-import org.apache.lucene.queryparser.flexible.standard.StandardQueryParser;
-import org.apache.lucene.queryparser.flexible.core.QueryNodeException;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.IndexSearcher;
+import sane.lucene.queryparser.SaneQueryParser;
+import sane.lucene.queryparser.FieldParser;
+import sane.lucene.queryparser.MultiFieldParser;
+import sane.lucene.queryparser.StringFieldParser;
+import sane.lucene.queryparser.NumberFieldParser;
+import sane.lucene.queryparser.ParseException;
 import luan.modules.Utils;
 import luan.Luan;
 import luan.LuanState;
@@ -32,6 +40,7 @@
 import luan.LuanFunction;
 import luan.LuanJavaFunction;
 import luan.LuanException;
+import luan.LuanMeta;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -40,6 +49,8 @@
 	private static final Logger logger = LoggerFactory.getLogger(LuceneIndex.class);
 
 	private static final String FLD_NEXT_ID = "nextId";
+	private static final Analyzer analyzer = new KeywordAnalyzer();
+	public static final FieldParser STRING_FIELD_PARSER = new StringFieldParser(analyzer);
 
 	final Lock writeLock = new ReentrantLock();
 	private final File indexDir;
@@ -47,15 +58,16 @@
 	final IndexWriter writer;
 	private DirectoryReader reader;
 	private LuceneSearcher searcher;
-	public final FieldTable fields = new FieldTable();
 	private boolean isClosed = false;
+	private final MultiFieldParser mfp = new MultiFieldParser();
 
 	public LuceneIndex(LuanState luan,String indexDirStr) throws LuanException, IOException {
+		mfp.fields.put( "type", STRING_FIELD_PARSER );
+		mfp.fields.put( "id", NumberFieldParser.LONG );
 		File indexDir = new File(indexDirStr);
 		this.indexDir = indexDir;
 		Directory dir = FSDirectory.open(indexDir);
 		Version version = Version.LUCENE_4_9;
-		Analyzer analyzer = new StandardAnalyzer(version);
 		IndexWriterConfig conf = new IndexWriterConfig(version,analyzer);
 		snapshotDeletionPolicy = new SnapshotDeletionPolicy(conf.getIndexDeletionPolicy());
 		conf.setIndexDeletionPolicy(snapshotDeletionPolicy);
@@ -68,20 +80,7 @@
 	}
 
 	Document toLucene(LuanState luan,LuanTable table) throws LuanException {
-		return LuceneDocument.toLucene(luan,table,fields.map);
-	}
-
-	LuanTable toTable(LuanState luan,Document doc) throws LuanException {
-		return LuceneDocument.toTable(luan,doc,fields.reverseMap);
-	}
-
-	public String map_field_name(String fld) {
-		String s = fields.map.get(fld);
-		return s!=null ? s : fld;
-	}
-
-	Term newTerm(String fld,String text) {
-		return new Term(map_field_name(fld),text);
+		return LuceneDocument.toLucene(luan,table,mfp.fields.keySet());
 	}
 
 	public LuceneWriter openWriter() {
@@ -120,13 +119,13 @@
 	private final int idBatch = 10;
 
 	private void initId(LuanState luan) throws LuanException, IOException {
-		TopDocs td = searcher.search(new TermQuery(newTerm("type","next_id")),1);
+		IndexSearcher searcher = this.searcher.searcher;
+		TopDocs td = searcher.search(new TermQuery(new Term("type","next_id")),1);
 		switch(td.totalHits) {
 		case 0:
 			break;  // do nothing
 		case 1:
-			LuanTable doc = searcher.doc(luan,td.scoreDocs[0].doc);
-			idLim = (Long)doc.rawGet(FLD_NEXT_ID);
+			idLim = (Long)searcher.doc(td.scoreDocs[0].doc).getField(FLD_NEXT_ID).numericValue();
 			id = idLim;
 			break;
 		default:
@@ -134,16 +133,15 @@
 		}
 	}
 
-	synchronized String nextId(LuanState luan) throws LuanException, IOException {
-		String rtn = Long.toString(++id);
-		if( id > idLim ) {
+	synchronized long nextId(LuanState luan) throws LuanException, IOException {
+		if( ++id > idLim ) {
 			idLim += idBatch;
 			LuanTable doc = new LuanTable();
 			doc.rawPut( "type", "next_id" );
 			doc.rawPut( FLD_NEXT_ID, idLim );
-			writer.updateDocument(newTerm("type","next_id"),toLucene(luan,doc));
+			writer.updateDocument(new Term("type","next_id"),toLucene(luan,doc));
 		}
-		return rtn;
+		return id;
 	}
 
 
@@ -210,10 +208,47 @@
 	}
 
 
-	public Query parse(String s) throws QueryNodeException {
-		StandardQueryParser qp = new StandardQueryParser();
-		qp.setQueryNodeProcessor(new LuanQueryNodeProcessor(this,qp.getQueryNodeProcessor()));
-		return qp.parse(s,null);
+
+	public final LuanMeta indexedFieldsMeta = new LuanMeta() {
+
+		@Override public boolean canNewindex() {
+			return true;
+		}
+
+		@Override public Object __index(LuanState luan,LuanTable tbl,Object key) {
+			return mfp.fields.get(key);
+		}
+
+		@Override public void __new_index(LuanState luan,LuanTable tbl,Object key,Object value) throws LuanException {
+			if( !(key instanceof String) )
+				throw luan.exception("key must be string");
+			String field = (String)key;
+			if( value==null ) {  // delete
+				mfp.fields.remove(field);
+				return;
+			}
+			if( !(value instanceof FieldParser) )
+				throw luan.exception("value must be FieldParser like the values of Lucene.type");
+			FieldParser parser = (FieldParser)value;
+			mfp.fields.put( field, parser );
+		}
+
+		@Override public final Iterator keys(LuanTable tbl) {
+			return mfp.fields.keySet().iterator();
+		}
+
+		@Override protected String type(LuanTable tbl) {
+			return "lucene-indexed-fields";
+		}
+
+	};
+
+	public Query parseQuery(String s) throws ParseException {
+		return SaneQueryParser.parseQuery(mfp,s);
+	}
+
+	public Sort parseSort(String s) throws ParseException {
+		return SaneQueryParser.parseSort(mfp,s);
 	}
 
 }
--- a/lucene/src/luan/modules/lucene/LuceneSearcher.java	Mon Jun 08 01:11:08 2015 -0400
+++ b/lucene/src/luan/modules/lucene/LuceneSearcher.java	Fri Jun 12 19:11:44 2015 -0600
@@ -21,7 +21,7 @@
 import org.apache.lucene.search.Scorer;
 import org.apache.lucene.search.MatchAllDocsQuery;
 import org.apache.lucene.index.AtomicReaderContext;
-import org.apache.lucene.queryparser.flexible.core.QueryNodeException;
+import sane.lucene.queryparser.ParseException;
 import luan.Luan;
 import luan.LuanState;
 import luan.LuanTable;
@@ -30,11 +30,12 @@
 import luan.LuanException;
 import luan.LuanRuntimeException;
 import luan.LuanMethod;
+import luan.modules.Utils;
 
 
 public final class LuceneSearcher {
 	private final LuceneIndex index;
-	private final IndexSearcher searcher;
+	final IndexSearcher searcher;
 
 	LuceneSearcher(LuceneIndex index,IndexReader reader) {
 		this.index = index;
@@ -46,10 +47,10 @@
 		searcher.getIndexReader().decRef();
 	}
 
-	LuanTable doc(LuanState luan,int docID) throws LuanException, IOException {
-		return index.toTable(luan,searcher.doc(docID));
+	private LuanTable doc(LuanState luan,int docID) throws LuanException, IOException {
+		return LuceneDocument.toTable(luan,searcher.doc(docID));
 	}
-
+/*
 	TopDocs search(Query query,int n) throws IOException {
 		return searcher.search(query,n);
 	}
@@ -57,7 +58,7 @@
 	TopFieldDocs search(Query query,int n,Sort sort) throws IOException {
 		return searcher.search(query,n,sort);
 	}
-
+*/
 	// luan
 
 	private static final LuanFunction nothingFn = new LuanFunction() {
@@ -78,15 +79,9 @@
 		}
 	}
 
-	@LuanMethod public Object[] search( final LuanState luan, Object queryObj, Object nObj, Sort sort ) throws LuanException, IOException, QueryNodeException {
-		Query query;
-		if( queryObj instanceof Query ) {
-			query = (Query)queryObj;
-		} else if( queryObj instanceof String ) {
-			String s = (String)queryObj;
-			query = index.parse(s);
-		} else
-			throw luan.exception("bad argument #1 (string or Query expected, got "+Luan.type(queryObj)+")");
+	@LuanMethod public Object[] search( final LuanState luan, String queryStr, Object nObj, String sortStr ) throws LuanException, IOException, ParseException {
+		Utils.checkNotNull(luan,queryStr);
+		Query query = index.parseQuery(queryStr);
 		if( nObj instanceof LuanFunction ) {
 			final LuanFunction fn = (LuanFunction)nObj;
 			Collector col = new MyCollector() {
@@ -119,6 +114,7 @@
 			searcher.search(query,thcc);
 			return new Object[]{ nothingFn, 0, thcc.getTotalHits() };
 		}
+		Sort sort = sortStr==null ? null : index.parseSort(sortStr);
 		TopDocs td = sort==null ? searcher.search(query,n) : searcher.search(query,n,sort);
 		final ScoreDoc[] scoreDocs = td.scoreDocs;
 		LuanFunction results = new LuanFunction() {
@@ -145,7 +141,7 @@
 	LuanTable table() {
 		LuanTable tbl = new LuanTable();
 		try {
-			add( tbl, "search", LuanState.class, Object.class, Object.class, Sort.class );
+			add( tbl, "search", LuanState.class, String.class, Object.class, String.class );
 		} catch(NoSuchMethodException e) {
 			throw new RuntimeException(e);
 		}
--- a/lucene/src/luan/modules/lucene/LuceneWriter.java	Mon Jun 08 01:11:08 2015 -0400
+++ b/lucene/src/luan/modules/lucene/LuceneWriter.java	Fri Jun 12 19:11:44 2015 -0600
@@ -7,6 +7,8 @@
 import java.util.ArrayList;
 import org.apache.lucene.index.IndexableField;
 import org.apache.lucene.index.Term;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.NumericUtils;
 import luan.Luan;
 import luan.LuanState;
 import luan.LuanTable;
@@ -31,12 +33,30 @@
 		index.writer.commit();
 	}
 
-	void addDocument(LuanState luan,LuanTable doc) throws LuanException, IOException {
-		index.writer.addDocument(index.toLucene(luan,doc));
+	private Term term(String key,int value) {
+		BytesRef br = new BytesRef();
+		NumericUtils.intToPrefixCoded(value,0,br);
+		return new Term(key,br);
+	}
+
+	private Term term(String key,long value) {
+		BytesRef br = new BytesRef();
+		NumericUtils.longToPrefixCoded(value,0,br);
+		return new Term(key,br);
 	}
 
-	void updateDocument(LuanState luan,Term term,LuanTable doc) throws LuanException, IOException {
-		index.writer.updateDocument(term,index.toLucene(luan,doc));
+	private Term term(LuanState luan,String key,Object value) throws LuanException {
+		if( value instanceof String )
+			return new Term( key, (String)value );
+		if( value instanceof Integer )
+			return term( key, (Integer)value );
+		if( value instanceof Long )
+			return term( key, (Long)value );
+		if( value instanceof Float )
+			return term( key, NumericUtils.floatToSortableInt((Float)value) );
+		if( value instanceof Double )
+			return term( key, NumericUtils.doubleToSortableLong((Double)value) );
+		throw luan.exception("invalid value type '"+value.getClass().getSimpleName()+"' for key '"+key+"'");
 	}
 
 	public void delete_documents(LuanState luan,LuanTable tblTerms) throws LuanException, IOException {
@@ -46,27 +66,21 @@
 			Object value = entry.getValue();
 			if( !(key instanceof String) )
 				throw luan.exception("key must be a string but got "+key.getClass().getSimpleName());
-			if( !(value instanceof String) )
-				throw luan.exception("value must be a string but got "+value.getClass().getSimpleName());
-			list.add( index.newTerm( (String)key, (String)value ) );
+			list.add( term( luan, (String)key, value ) );
 		}
 		index.writer.deleteDocuments(list.toArray(new Term[list.size()]));
 	}
 
-	String nextId(LuanState luan) throws LuanException, IOException {
-		return index.nextId(luan);
-	}
-
 	public void save_document(LuanState luan,LuanTable doc) throws LuanException, IOException {
 		if( doc.get(luan,"type")==null )
 			throw luan.exception("missing 'type' field");
-		String id = (String)doc.get(luan,"id");
+		Long id = (Long)doc.get(luan,"id");
 		if( id == null ) {
-			id = nextId(luan);
+			id = index.nextId(luan);
 			doc.put(luan,"id",id);
-			addDocument(luan,doc);
+			index.writer.addDocument(index.toLucene(luan,doc));
 		} else {
-			updateDocument(luan,index.newTerm("id",id),doc);
+			index.writer.updateDocument( term("id",id), index.toLucene(luan,doc) );
 		}
 	}
 
--- a/lucene/src/luan/modules/lucene/Web_search.luan	Mon Jun 08 01:11:08 2015 -0400
+++ b/lucene/src/luan/modules/lucene/Web_search.luan	Fri Jun 12 19:11:44 2015 -0600
@@ -4,10 +4,6 @@
 local ipairs = Luan.ipairs
 local range = Luan.range
 local to_string = Luan.to_string
-local Number = require "luan:Number"
-local integer = Number.integer
-local long = Number.long
-local double = Number.double
 local Io = require "luan:Io"
 local Http = require "luan:http/Http"
 local String = require "luan:String"
@@ -31,8 +27,8 @@
 						<label>Query:</label>
 					</div>
 					<div colspan=10>
-						<input name="query" size="80" value="query.all_docs" autofocus />
-						<div textcolor="#888">Query examples: <i>query.term{ type = 'user' }</i> or <i>"type:user AND name:Joe"</i></div>
+						<input name="query" size="80" autofocus />
+						<div textcolor="#888">Query examples: <i>type:user</i> or <i>+type:user +name:Joe"</i></div>
 					</div>
 				</div>
 				<div row margin-top="1em">
@@ -49,7 +45,7 @@
 					</div>
 					<div colspan=10>
 						<input name="sort" size="60" />
-						<div textcolor="#888">Sort examples: sort{{ field = 'id', type='int' }}</div>
+						<div textcolor="#888">Sort examples: <i>name, id</i></div>
 					</div>
 				</div>
 				<div row margin-top="1em">
@@ -130,20 +126,13 @@
 
 	return function()
 		Io.stdout = Http.response.text_writer()
-		local query_string = Http.request.parameter.query
-		if query_string == nil then
+		local query = Http.request.parameter.query
+		if query == nil then
 			form()
 			return
 		end
-		local query_env = {
-			query=index.query;
-			integer=integer;
-			long=long;
-			double=double;
-		}
-		local query = load(query_string,"<query>",query_env,true)()
 		local rows = string_to_number(Http.request.parameter.rows)
-		local sort = load(Http.request.parameter.sort,"<sort>",{sort=index.query.sort},true)()
+		local sort = Http.request.parameter.sort
 		index.Searcher( function(searcher)
 			local results, length, total_hits = searcher.search(query,rows,sort)
 			local headers = {}
--- a/scripts/test.luan	Mon Jun 08 01:11:08 2015 -0400
+++ b/scripts/test.luan	Fri Jun 12 19:11:44 2015 -0600
@@ -83,7 +83,7 @@
 web_search()
 
 init()
-Http.request.parameter.query = "query.all_docs"
+Http.request.parameter.query = ""
 Http.request.parameter.rows = "100"
 Http.request.parameter.sort = ""
 web_search()