fulltextlucene.java

来自「非常棒的java数据库」· Java 代码 · 共 608 行 · 第 1/2 页

JAVA
608
字号
/*
 * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
 * (http://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.fulltext;

//#ifdef JDK14
import java.io.IOException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.DateTools;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexModifier;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Searcher;
import org.h2.api.Trigger;
import org.h2.command.Parser;
import org.h2.engine.Session;
import org.h2.expression.ExpressionColumn;
import org.h2.jdbc.JdbcConnection;
import org.h2.store.fs.FileSystem;
import org.h2.tools.SimpleResultSet;
import org.h2.util.ByteUtils;
import org.h2.util.StringUtils;
//#endif

/**
 * This class implements the full text search based on Apache Lucene.
 */
public class FullTextLucene extends FullText
//#ifdef JDK14
implements Trigger
//#endif
{

//#ifdef JDK14
    private static HashMap indexers = new HashMap();
    private static final String FIELD_DATA = "DATA";
    private static final String FIELD_QUERY = "QUERY";
    private static final String FIELD_COLUMN_PREFIX = "_";
    private static final String TRIGGER_PREFIX = "FTL_";
    private static final String SCHEMA = "FTL";
    private String schemaName;
    private String tableName;
    private int[] keys;
    private int[] indexColumns;
    private String[] columnNames;
    private int[] dataTypes;
    private IndexModifier indexer;
//#endif

    /**
     * Create a new full text index for a table and column list. Each table may
     * only have one index at any time.
     * 
     * @param conn the connection
     * @param schema the schema name of the table
     * @param table the table name
     * @param columnList the column list (null for all columns)
     */
//#ifdef JDK14
    public static void createIndex(Connection conn, String schema, String table, String columnList) throws SQLException {
        init(conn);
        PreparedStatement prep = conn.prepareStatement("INSERT INTO "+SCHEMA+".INDEXES(SCHEMA, TABLE, COLUMNS) VALUES(?, ?, ?)");
        prep.setString(1, schema);
        prep.setString(2, table);
        prep.setString(3, columnList);
        prep.execute();
        createTrigger(conn, schema, table);
        indexExistingRows(conn, schema, table);
    }
//#endif

    /**
     * Re-creates the full text index for this database
     *
     * @param conn the connection
     */
//#ifdef JDK14
    public static void reindex(Connection conn) throws SQLException {
        init(conn);
        removeAllTriggers(conn);
        removeIndexFiles(conn);
        Statement stat = conn.createStatement();
        ResultSet rs = stat.executeQuery("SELECT * FROM "+SCHEMA+".INDEXES");
        while (rs.next()) {
            String schema = rs.getString("SCHEMA");
            String table = rs.getString("TABLE");
            createTrigger(conn, schema, table);
            indexExistingRows(conn, schema, table);
        }
    }
//#endif

    /**
     * Drops all full text indexes from the database.
     *
     * @param conn the connection
     */
//#ifdef JDK14
    public static void dropAll(Connection conn) throws SQLException {
        Statement stat = conn.createStatement();
        stat.execute("DROP SCHEMA IF EXISTS " + SCHEMA);
        removeAllTriggers(conn);
        removeIndexFiles(conn);
    }
//#endif

    /**
     * Initializes full text search functionality for this database. This adds
     * the following Java functions to the database:
     * <ul>
     * <li>FTL_CREATE_INDEX(schemaNameString, tableNameString,
     * columnListString) </li>
     * <li>FTL_SEARCH(queryString, limitInt, offsetInt): result set </li>
     * <li>FTL_REINDEX() </li>
     * <li>FTL_DROP_ALL() </li>
     * </ul>
     * It also adds a schema FTL to the database where bookkeeping information
     * is stored. This function may be called from a Java application, or by
     * using the SQL statements:
     * 
     * <pre>
     *  CREATE ALIAS IF NOT EXISTS FTL_INIT FOR 
     *      &quot;org.h2.fulltext.FullTextLucene.init&quot;;
     *  CALL FTL_INIT();
     * </pre>
     * 
     * @param conn
     */
//#ifdef JDK14
    public static void init(Connection conn) throws SQLException {
        Statement stat = conn.createStatement();
        stat.execute("CREATE SCHEMA IF NOT EXISTS " + SCHEMA);
        stat.execute("CREATE TABLE IF NOT EXISTS "+SCHEMA+".INDEXES(SCHEMA VARCHAR, TABLE VARCHAR, COLUMNS VARCHAR, PRIMARY KEY(SCHEMA, TABLE))");
        stat.execute("CREATE ALIAS IF NOT EXISTS FTL_CREATE_INDEX FOR \"" + FullTextLucene.class.getName() + ".createIndex\"");
        stat.execute("CREATE ALIAS IF NOT EXISTS FTL_SEARCH FOR \"" + FullTextLucene.class.getName() + ".search\"");
        stat.execute("CREATE ALIAS IF NOT EXISTS FTL_SEARCH_DATA FOR \"" + FullTextLucene.class.getName() + ".searchData\"");
        stat.execute("CREATE ALIAS IF NOT EXISTS FTL_REINDEX FOR \"" + FullTextLucene.class.getName() + ".reindex\"");
        stat.execute("CREATE ALIAS IF NOT EXISTS FTL_DROP_ALL FOR \"" + FullTextLucene.class.getName() + ".dropAll\"");
    }
//#endif

    /**
     * INTERNAL
     */
//#ifdef JDK14
    public void init(Connection conn, String schemaName, String triggerName, String tableName, boolean before, int type) throws SQLException {
        init(conn);
        this.schemaName = schemaName;
        this.tableName = tableName;
        this.indexer = getIndexModifier(conn);
        ArrayList keyList = new ArrayList();
        DatabaseMetaData meta = conn.getMetaData();
        ResultSet rs = meta.getColumns(null, schemaName, tableName, null);
        ArrayList columnList = new ArrayList();
        while (rs.next()) {
            columnList.add(rs.getString("COLUMN_NAME"));
        }
        dataTypes = new int[columnList.size()];
        columnNames = new String[columnList.size()];
        columnList.toArray(columnNames);
        rs = meta.getColumns(null, schemaName, tableName, null);
        for (int i = 0; rs.next(); i++) {
            dataTypes[i] = rs.getInt("DATA_TYPE");
        }
        if (keyList.size() == 0) {
            rs = meta.getPrimaryKeys(null, schemaName, tableName);
            while (rs.next()) {
                keyList.add(rs.getString("COLUMN_NAME"));
            }
        }
        if (keyList.size() == 0) {
            throw new SQLException("No primary key for table " + tableName);
        }
        ArrayList indexList = new ArrayList();
        PreparedStatement prep = conn.prepareStatement("SELECT COLUMNS FROM "+SCHEMA+".INDEXES WHERE SCHEMA=? AND TABLE=?");
        prep.setString(1, schemaName);
        prep.setString(2, tableName);
        rs = prep.executeQuery();
        if (rs.next()) {
            String columns = rs.getString(1);
            if (columns != null) {
                String[] list = StringUtils.arraySplit(columns, ',', true);
                for (int i = 0; i < list.length; i++) {
                    indexList.add(list[i]);
                }
            }
        }
        if (indexList.size() == 0) {
            indexList.addAll(columnList);
        }
        keys = new int[keyList.size()];
        setColumns(keys, keyList, columnList);
        indexColumns = new int[indexList.size()];
        setColumns(indexColumns, indexList, columnList);
    }
//#endif

    /**
     * INTERNAL
     */
//#ifdef JDK14
    public void fire(Connection conn, Object[] oldRow, Object[] newRow) throws SQLException {
        if (oldRow != null) {
            delete(oldRow);
        }
        if (newRow != null) {
            insert(newRow);
        }
    }
//#endif

    /**
     * Searches from the full text index for this database. The result contains
     * the primary key data as an array. The returned result set has the
     * following columns:
     * <ul>
     * <li>SCHEMA (varchar): The schema name. Example: PUBLIC </li>
     * <li>TABLE (varchar): The table name. Example: TEST </li>
     * <li>COLUMNS (array of varchar): Comma separated list of quoted column
     * names. The column names are quoted if necessary. Example: (ID) </li>
     * <li>KEYS (array of values): Comma separated list of values. Example: (1)
     * </li>
     * </ul>
     * 
     * @param conn the connection
     * @param text the search query
     * @param limit the maximum number of rows or 0 for no limit
     * @param offset the offset or 0 for no offset
     * @return the result set
     */
//#ifdef JDK14
    public static ResultSet searchData(Connection conn, String text, int limit, int offset) throws SQLException {
        return search(conn, text, limit, offset, true);
    }
//#endif
    
    /**
     * Searches from the full text index for this database.
     * The returned result set has the following column:
     * <ul><li>QUERY (varchar): The query to use to get the data.
     * The query does not include 'SELECT * FROM '. Example:
     * PUBLIC.TEST WHERE ID = 1
     * </li></ul>
     *
     * @param conn the connection
     * @param text the search query
     * @param limit the maximum number of rows or 0 for no limit
     * @param offset the offset or 0 for no offset
     * @return the result set
     */    
//#ifdef JDK14
    public static ResultSet search(Connection conn, String text, int limit, int offset) throws SQLException {
        return search(conn, text, limit, offset, false);
    }
    
    private static ResultSet search(Connection conn, String text, int limit, int offset, boolean data) throws SQLException {
        SimpleResultSet result = createResultSet(data);
        if (conn.getMetaData().getURL().startsWith("jdbc:columnlist:")) {
            // this is just to query the result set columns
            return result;
        }
        String path = getIndexPath(conn);
        try {
            IndexModifier indexer = getIndexModifier(conn);
            indexer.flush();
            IndexReader reader = IndexReader.open(path);
            Analyzer analyzer = new StandardAnalyzer();
            Searcher searcher = new IndexSearcher(reader);
            QueryParser parser = new QueryParser(FIELD_DATA, analyzer);
            Query query = parser.parse(text);
            Hits hits = searcher.search(query);
            int max = hits.length();
            if (limit == 0) {
                limit = max;
            }
            for (int i = 0; i < limit && i + offset < max; i++) {
                Document doc = hits.doc(i + offset);
                String q = doc.get(FIELD_QUERY);
                if (data) {
                    int idx = q.indexOf(" WHERE ");
                    JdbcConnection c = (JdbcConnection) conn;
                    Session session = (Session) c.getSession();
                    Parser p = new Parser(session);
                    String tab = q.substring(0, idx);
                    ExpressionColumn expr = (ExpressionColumn) p.parseExpression(tab);

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?