/** * Keep a collection of the columns to pass to update if a duplicate key happens, for MySQL-style * INSERT ... ON DUPLICATE KEY UPDATE .... * * @param column the column * @param expression the expression */ public void addAssignmentForDuplicate(Column column, Expression expression) { if (duplicateKeyAssignmentMap == null) { duplicateKeyAssignmentMap = New.hashMap(); } if (duplicateKeyAssignmentMap.containsKey(column)) { throw DbException.get(ErrorCode.DUPLICATE_COLUMN_NAME_1, column.getName()); } duplicateKeyAssignmentMap.put(column, expression); }
private void tryReadMetaData(Connection conn, String oCatalog, String oSchema, String tableName) throws SQLException { DatabaseMetaData meta = conn.getMetaData(); storesLowerCase = meta.storesLowerCaseIdentifiers(); storesMixedCase = meta.storesMixedCaseIdentifiers(); storesMixedCaseQuoted = meta.storesMixedCaseQuotedIdentifiers(); supportsMixedCaseIdentifiers = meta.supportsMixedCaseIdentifiers(); ResultSet rs = meta.getTables(oCatalog, oSchema, tableName, null); if (rs.next() && rs.next()) { throw DbException.get(ErrorCode.SCHEMA_NAME_MUST_MATCH, tableName); } rs.close(); rs = meta.getColumns(null, null, tableName, null); int i = 0; ArrayList<Column> columnList = New.arrayList(); HashMap<String, Column> columnMap = New.hashMap(); String catalog = null, schema = null; while (rs.next()) { String thisCatalog = rs.getString("TABLE_CAT"); if (catalog == null) { catalog = thisCatalog; } String thisSchema = rs.getString("TABLE_SCHEM"); if (schema == null) { schema = thisSchema; } if (!StringUtils.equals(catalog, thisCatalog) || !StringUtils.equals(schema, thisSchema)) { // if the table exists in multiple schemas or tables, // use the alternative solution columnMap.clear(); columnList.clear(); break; } String n = rs.getString("COLUMN_NAME"); n = convertColumnName(n); int sqlType = rs.getInt("DATA_TYPE"); long precision = rs.getInt("COLUMN_SIZE"); precision = convertPrecision(sqlType, precision); int scale = rs.getInt("DECIMAL_DIGITS"); scale = convertScale(sqlType, scale); int displaySize = MathUtils.convertLongToInt(precision); int type = DataType.convertSQLTypeToValueType(sqlType); Column col = new Column(n, type, precision, scale, displaySize); col.setTable(this, i++); columnList.add(col); columnMap.put(n, col); } rs.close(); // check if the table is accessible Statement stat = null; try { stat = conn.createStatement(); rs = stat.executeQuery("SELECT * FROM " + tableName + " T WHERE 1=0"); if (columnList.size() == 0) { // alternative solution ResultSetMetaData rsMeta = rs.getMetaData(); for (i = 0; i < rsMeta.getColumnCount(); ) { String n = rsMeta.getColumnName(i + 1); n = convertColumnName(n); int sqlType = rsMeta.getColumnType(i + 1); long precision = rsMeta.getPrecision(i + 1); precision = convertPrecision(sqlType, precision); int scale = rsMeta.getScale(i + 1); scale = convertScale(sqlType, scale); int displaySize = rsMeta.getColumnDisplaySize(i + 1); int type = DataType.getValueTypeFromResultSet(rsMeta, i + 1); Column col = new Column(n, type, precision, scale, displaySize); col.setTable(this, i++); columnList.add(col); columnMap.put(n, col); } } rs.close(); } catch (Exception e) { throw DbException.get( ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, e, tableName + "(" + e.toString() + ")"); } finally { JdbcUtils.closeSilently(stat); } Column[] cols = new Column[columnList.size()]; columnList.toArray(cols); setColumns(cols); // create scan index // load primary keys try { rs = meta.getPrimaryKeys(null, null, tableName); } catch (Exception e) { // Some ODBC bridge drivers don't support it: // some combinations of "DataDirect SequeLink(R) for JDBC" // http://www.datadirect.com/index.ssp rs = null; } String pkName = ""; ArrayList<Column> list; if (rs != null && rs.next()) { // the problem is, the rows are not sorted by KEY_SEQ list = New.arrayList(); do { int idx = rs.getInt("KEY_SEQ"); if (pkName == null) { pkName = rs.getString("PK_NAME"); } while (list.size() < idx) { list.add(null); } String col = rs.getString("COLUMN_NAME"); col = convertColumnName(col); Column column = columnMap.get(col); if (idx == 0) { // workaround for a bug in the SQLite JDBC driver list.add(column); } else { list.set(idx - 1, column); } } while (rs.next()); addIndex(pkName, list, IndexType.createPrimaryKey(false)); rs.close(); } try { rs = meta.getIndexInfo(null, null, tableName, false, true); } catch (Exception e) { // Oracle throws an exception if the table is not found or is a // SYNONYM rs = null; } String indexName = null; list = New.arrayList(); IndexType indexType = null; if (rs != null) { while (rs.next()) { if (rs.getShort("TYPE") == DatabaseMetaData.tableIndexStatistic) { // ignore index statistics continue; } String newIndex = rs.getString("INDEX_NAME"); if (pkName.equals(newIndex)) { continue; } if (indexName != null && !indexName.equals(newIndex)) { addIndex(indexName, list, indexType); indexName = null; } if (indexName == null) { indexName = newIndex; list.clear(); } boolean unique = !rs.getBoolean("NON_UNIQUE"); indexType = unique ? IndexType.createUnique(false) : IndexType.createNonUnique(); String col = rs.getString("COLUMN_NAME"); col = convertColumnName(col); Column column = columnMap.get(col); list.add(column); } rs.close(); } if (indexName != null) { addIndex(indexName, list, indexType); } shardingKeyIndex(); }
public class ConnectionHolder implements ConnectionProvider { private final Session session; private final HolderStrategy holderStrategy; private final ConnectionProvider target; private final Trace trace; private Map<String, Connection> connectionMap = New.hashMap(); private final Closer closer = new Closer(); public ConnectionHolder(Session session) { Database database = session.getDatabase(); JdbcRepository repository = (JdbcRepository) database.getRepository(); this.session = session; this.trace = database.getTrace(Trace.TRANSACTION); this.target = repository.getConnectionProvider(); String mode = database.getSettings().transactionMode; this.holderStrategy = transactionMode(mode); } public synchronized <T> List<T> foreach(Callback<T> callback) throws DbException { List<T> results = New.arrayList(); for (String name : connectionMap.keySet()) { try { Connection conn = connectionMap.get(name); results.add(callback.handle(name, conn)); } catch (SQLException e) { trace.error(e, "foreach {0} connection error", name); throw DbException.convert(e); } } return results; } public synchronized <T> List<T> foreach(Set<String> shards, Callback<T> callback) throws DbException { List<T> results = New.arrayList(); for (String name : connectionMap.keySet()) { if (shards.contains(name)) { try { Connection conn = connectionMap.get(name); results.add(callback.handle(name, conn)); } catch (SQLException e) { trace.error(e, "foreach {0} connection error", name); throw DbException.convert(e); } } } return results; } @Override public synchronized Connection getConnection(Options options) { Connection conn; if (session.getAutoCommit()) { conn = getRawConnection(options); } else { conn = getConnectionWithStrategy(options); } return conn; } @Override public synchronized void closeConnection(Connection connection, Options options) { if (!connectionMap.containsKey(options.shardName)) { target.closeConnection(connection, options); } } public synchronized boolean hasConnection() { return !connectionMap.isEmpty(); } public synchronized List<String> closeAndClear() { List<String> foreach = foreach(closer); connectionMap.clear(); return foreach; } /** * Each shard have a database connection, the worker thread may concurrently use a shard * connection executing SQL (such as executing ddl statement), If the JDBC driver is * spec-compliant, then technically yes, the object is thread-safe, MySQL Connector/J, all methods * to execute statements lock the connection with a synchronized block. * * @param options * @return */ protected Connection getConnectionWithStrategy(Options options) { String shardName = options.shardName; Connection conn; switch (holderStrategy) { case STRICTLY: if (connectionMap.isEmpty()) { conn = getRawConnection(options); connectionMap.put(shardName, conn); } else { conn = connectionMap.get(shardName); if (conn == null) { String lastTransactionNode = connectionMap.keySet().iterator().next(); throw DbException.get( ErrorCode.GENERAL_ERROR_1, "STRICTLY transaction mode not supported operation on multi-node, opend on " + lastTransactionNode + " and try on " + shardName); } } break; case ALLOW_CROSS_SHARD_READ: if (options.readOnly) { conn = connectionMap.get(shardName); if (conn == null) { conn = getRawConnectionForReadOnly(options); } } else { if (connectionMap.isEmpty()) { conn = getRawConnection(options); connectionMap.put(shardName, conn); } else { conn = connectionMap.get(shardName); if (conn == null) { String lastTransactionNode = connectionMap.keySet().iterator().next(); throw DbException.get( ErrorCode.GENERAL_ERROR_1, "ALLOW_READ_CROSS_DB transaction mode not supported writing operation on multi-node, opend on " + lastTransactionNode + " and try on " + shardName); } } } break; case BESTEFFORTS_1PC: conn = connectionMap.get(shardName); if (conn == null) { conn = getRawConnection(options); connectionMap.put(shardName, conn); } break; default: throw DbException.getInvalidValueException("transactionMode", holderStrategy); } return conn; } /** * @param options * @return * @throws SQLException */ private Connection getRawConnection(Options options) throws DbException { Connection conn = target.getConnection(options); try { if (conn.getAutoCommit() != session.getAutoCommit()) { conn.setAutoCommit(session.getAutoCommit()); } if (session.getTransactionIsolation() != 0) { if (conn.getTransactionIsolation() != session.getTransactionIsolation()) { conn.setTransactionIsolation(session.getTransactionIsolation()); } } if (conn.isReadOnly() != session.isReadOnly()) { conn.setReadOnly(session.isReadOnly()); } } catch (Exception e) { throw DbException.convert(e); } return conn; } private Connection getRawConnectionForReadOnly(Options options) { try { Connection conn = target.getConnection(options); conn.setAutoCommit(true); conn.setReadOnly(true); if (session.getTransactionIsolation() != 0) { if (conn.getTransactionIsolation() != session.getTransactionIsolation()) { conn.setTransactionIsolation(session.getTransactionIsolation()); } } return conn; } catch (SQLException e) { throw DbException.convert(e); } } private HolderStrategy transactionMode(String mode) { try { HolderStrategy holderStrategy = StringUtils.isNullOrEmpty(mode) ? HolderStrategy.BESTEFFORTS_1PC : HolderStrategy.valueOf(mode); return holderStrategy; } catch (Exception e) { throw DbException.getInvalidValueException("transactionMode", mode); } } public static interface Callback<T> { T handle(String shardName, Connection connection) throws SQLException; } private class Closer implements Callback<String> { @Override public String handle(String name, Connection connection) throws SQLException { try { target.closeConnection(connection, Options.build().shardName(name)); } catch (Exception e) { trace.error(e, "Close {0} connection error", name); // throw DbException.convert(e); } return name; } }; enum HolderStrategy { STRICTLY, ALLOW_CROSS_SHARD_READ, BESTEFFORTS_1PC } }