понедельник, декабря 20, 2010

Scala/Lift/Mapper и SphinxQL 1.10-beta

Вчера возникла задача подключить Sphinx для полнотекстового поиска к проекту на Scala\Lift. Поскольку начиная с версии 0.0.9 Sphinx поддерживает работу по протоколу MySQL 10 версии (не забываем listen = localhost:3307:mysql41 в конфигурации Sphinx), то логичным было подключить его через стандартный jdbc-драйвер mysql MySQL Connector/J версии 5.1.14 и использовать в запросах SphinxQL. Как обычно бывает, сразу все не завелось даже в самом элементарном случае, но опыт других (раз, два), подсказал направление движения. Как стало ясно, дополнительные параметры characterEncoding и maxAllowedPacket в строке подключения "jdbc:mysql://127.0.0.1:3307?characterEncoding=utf8&maxAllowedPacket=512000" сделают свое дело. Однако попытка определения стандартного StandardDBVendor в Lift с такой строкой подключения неизменно приведет к Exception in thread "main" com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: MySQL Versions Older than 3.23.15 do not support transactions. Ковыряние в сырцах com.mysql.jdbc.Connection показало наличие в public void setAutoCommit(boolean autoCommit) кидания new SQLException("MySQL Versions Older than 3.23.15 "+"do not support transactions", "08003") при условии transactionsSupported!=true && autoCommit == false && this.relaxAutoCommit == false. Сырцы net.liftweb.mapper, в частности StandardDBVendor, ProtoDBVendor показали что имеется многократное setAutoCommit(false) в разных местах, как то: DB.newConnection, ProtoDBVendor.testConnection, ProtoDBVendor.newConnection и т.д. Переписывание классов показалось нецелесообразным и дополнительное исследование параметров jdbc-драйвера mysql показало наличие параметра relaxAutoCommit, которое дословно отвечает за "If the version of MySQL the driver connects to does not support transactions, still allow calls to commit(), rollback() and setAutoCommit() (true/false, defaults to 'false')?" и по умолчанию его значение равно false. Параметр работает, добавление relaxAutoCommit=true решает проблему.
Следующий тест должен проходить:

import java.sql.*;

public class TestSetAutoCommitWithSphinx {

      public static void main(String[] args) throws SQLException {
  Connection con = null;
  String driver = "com.mysql.jdbc.Driver";
  String user = "root";
  String pass = "";
  ResultSet rs = null;
  ResultSetMetaData rsmd = null;

  try {
   Class.forName(driver);
  } catch (Exception e) {
   e.printStackTrace();
  }
        // relaxAutoCommit important if you setAutoCommit(false) with SphinxQL
  con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3307?characterEncoding=utf8&maxAllowedPacket=512000&relaxAutoCommit=true", user, pass);

        // This throw com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: MySQL Versions Older than 3.23.15 do not support transactions
        // without relaxAutoCommit=true in connectionstring 
        con.setAutoCommit(false);

        Statement s = con.createStatement();
        rs =s.executeQuery("select @id as search_id, @weight as search_weight from phoneindex WHERE MATCH('John')");

  rsmd = rs.getMetaData();
  int cc = rsmd.getColumnCount();
  for (int i = 1; i <= cc; i ++)
   System.out.println(rsmd.getColumnName(i));

  while (rs.next()) {
   System.out.println("Id: " + rs.getInt(1) + ", Weight: " + rs.getInt(2));
  }
  rs.close();
 }
}
Cледующая проблема с интеграцией Lift/Mapper и SphinxQL - это реализация протокола mysql в SphinxQL, которая возвращает resultset с полями, имеющими sqltype=-2, то есть BINARY. При меппинге бинарных полей методами Lift/Mapper типа DB.exec и DB.runQuery вызывается метод resultSetTo(rs: ResultSet): (List[String], List[List[String]]), который вызовом DB.asString приводит все в строки. От приведения BINARY в строку через RecordSet.getObject ничего хорошего не получается. Единственные методы, которые возвращают честный ResultSet это def exec[T](db: SuperConnection, query: String)(f: (ResultSet) => T): T и def exec[T](statement: PreparedStatement)(f: (ResultSet) => T): T, поэтому имеется только две конструкция с использованием net.liftweb.mapper.DB, которые будут работать:
DB.use(EventTextSearchDB) { conn =>
          DB.exec(conn,"select * from eventindex WHERE MATCH('Это')") { rs =>
            while (rs.next) {
              // rs.getInt(1) for 32bit doc_id, rs.getLong(1) for 64bit doc_id
              println("Id: " + rs.getLong(1) + ", Weight: " + rs.getInt(2))
            }
          }
          DB.prepareStatement("select * from eventindex WHERE MATCH(?)",conn) { stmt =>
            stmt.setString(1,"Это")
            val rs=stmt.executeQuery
            while (rs.next) {
              println("Id: " + rs.getLong(1) + ", Weight: " + rs.getInt(2))
            }
          }
        }

Кажется все.

PS: Может быть поиск решения работы с Sphinx из Scala/Lift/Mapper и не стоил того - проще было прикрутить любой имеющийся connection pool и сделать свои обертки, но стандартизация требует жертв.

1 комментарий:

Анонимный комментирует...

Great information! I’ve been looking for something like this for a while now. Thanks!

Мой список блогов