package marubinotto.h2.fulltext; import static marubinotto.h2.fulltext.InternalUtils.quoteSQL; import static marubinotto.h2.fulltext.InternalUtils.throwException; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import marubinotto.util.Assert; import org.h2.util.JdbcUtils; import org.h2.util.New; import org.h2.util.StatementBuilder; import org.h2.util.StringUtils; /** * To be created at Trigger.init */ public class IndexedTableInfo { /** * The index id. * (from FT_INDEXES.ID) */ public Integer id; /** * The schema name. * (from Trigger.init arg) */ public String schema; /** * The table name. * (from Trigger.init arg) */ public String table; /** * The column names. * (from DatabaseMetaData.getColumns["COLUMN_NAME"]) */ public List<String> columns; /** * from DatabaseMetaData.getColumns["DATA_TYPE"] */ public List<Integer> columnTypes; /** * The column indexes of the key columns. * (from DatabaseMetaData.getPrimaryKeys["COLUMN_NAME"]) */ public List<Integer> keys; /** * The column indexes of the index columns. * (from FT_INDEXES.COLUMNS) */ public List<Integer> indexColumns; /** * - the specified table must be indexed in "FT.INDEXES" */ public static IndexedTableInfo newInstance( Connection conn, String schemaName, String tableName) throws SQLException { IndexedTableInfo info = new IndexedTableInfo(); info.schema = schemaName; info.table = tableName; // column names and types DatabaseMetaData meta = conn.getMetaData(); ResultSet rs = meta.getColumns(null, JdbcUtils.escapeMetaDataPattern(schemaName), JdbcUtils.escapeMetaDataPattern(tableName), null); info.columns = New.arrayList(); info.columnTypes = New.arrayList(); while (rs.next()) { info.columns.add(rs.getString("COLUMN_NAME")); info.columnTypes.add(rs.getInt("DATA_TYPE")); } // primary keys List<String> keyNames = New.arrayList(); rs = meta.getPrimaryKeys(null, JdbcUtils.escapeMetaDataPattern(schemaName), tableName); while (rs.next()) { keyNames.add(rs.getString("COLUMN_NAME")); } if (keyNames.isEmpty()) { throw throwException("No primary key for table " + tableName); } info.keys = info.toColumnIndexes(keyNames); // indexed columns List<String> indexNames = New.arrayList(); PreparedStatement prep = conn.prepareStatement( "SELECT ID, COLUMNS FROM " + FullTextSearch.SCHEMA + ".INDEXES WHERE SCHEMA=? AND TABLE=?"); prep.setString(1, schemaName); prep.setString(2, tableName); rs = prep.executeQuery(); if (rs.next()) { info.id = rs.getInt(1); String columns = rs.getString(2); if (columns != null) { for (String s : StringUtils.arraySplit(columns, ',', true)) { indexNames.add(s); } } } if (info.id == null) throw throwException("Not to be indexed: " + tableName); if (indexNames.size() == 0) indexNames.addAll(info.columns); // all info.indexColumns = info.toColumnIndexes(indexNames); return info; } private List<Integer> toColumnIndexes(List<String> names) throws SQLException { Assert.Property.requireNotNull(columns, "columns"); List<Integer> indexes = New.arrayList(); for (String name : names) { int index = this.columns.indexOf(name); if (index < 0) throw throwException("Column not found: " + name); indexes.add(index); } return indexes; } /** * Check if a the indexed columns of a row probably have changed. It may * return true even if the change was minimal (for example from 0.0 to 0.00). */ public boolean haveIndexedColumnsChanged(Object[] oldRow, Object[] newRow) { for (int columnIndex : this.indexColumns) { Object o = oldRow[columnIndex], n = newRow[columnIndex]; if (o == null) { if (n != null) return true; } else if (!o.equals(n)) { return true; } } return false; } public String createConditionSqlWithKeys(Object[] row) throws SQLException { StatementBuilder buff = new StatementBuilder(); for (int columnIndex : this.keys) { buff.appendExceptFirst(" AND "); buff.append(StringUtils.quoteIdentifier(this.columns.get(columnIndex))); Object value = row[columnIndex]; if (value == null) { buff.append(" IS NULL"); } else { buff.append('=').append(quoteSQL(value, this.columnTypes.get(columnIndex))); } } return buff.toString(); } }