/* * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License, version 2.0, as published by the * Free Software Foundation. * * This program is also distributed with certain software (including but not * limited to OpenSSL) that is licensed under separate terms, as designated in a * particular file or component or in included license documentation. The * authors of MySQL hereby grant you an additional permission to link the * program and your derivative works with the separately licensed software that * they have included with MySQL. * * Without limiting anything contained in the foregoing, this file, which is * part of MySQL Connector/J, is also subject to the Universal FOSS Exception, * version 1.0, a copy of which can be found at * http://oss.oracle.com/licenses/universal-foss-exception. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, * for more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package com.mysql.cj.result; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; import com.mysql.cj.protocol.ColumnDefinition; /** * Protocol::ColumnDefinition41 object * */ public class DefaultColumnDefinition implements ColumnDefinition { protected Field[] fields; /** Map column names (and all of their permutations) to column indices */ private Map columnLabelToIndex = null; /** * The above map is a case-insensitive tree-map, it can be slow, this caches lookups into that map, because the other alternative is to create new * object instances for every call to findColumn().... */ private Map columnToIndexCache = new HashMap<>(); /** Map of fully-specified column names to column indices */ private Map fullColumnNameToIndex = null; /** Map column names (and all of their permutations) to column indices */ private Map columnNameToIndex = null; private boolean builtIndexMapping = false; public DefaultColumnDefinition() { } public DefaultColumnDefinition(Field[] fields) { this.fields = fields; } public Field[] getFields() { return this.fields; } public void setFields(Field[] fields) { this.fields = fields; } @Override /** * Builds a hash between column names and their indices for fast retrieval. */ public void buildIndexMapping() { int numFields = this.fields.length; this.columnLabelToIndex = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); this.fullColumnNameToIndex = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); this.columnNameToIndex = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); // We do this in reverse order, so that the 'first' column with a given name ends up as the final mapping in the hashtable... // // Quoting the JDBC Spec: // // "Column names used as input to getter methods are case insensitive. When a getter method is called with a column name and several columns have the // same name, the value of the first matching column will be returned. " // for (int i = numFields - 1; i >= 0; i--) { Integer index = Integer.valueOf(i); String columnName = this.fields[i].getOriginalName(); String columnLabel = this.fields[i].getName(); String fullColumnName = this.fields[i].getFullName(); if (columnLabel != null) { this.columnLabelToIndex.put(columnLabel, index); } if (fullColumnName != null) { this.fullColumnNameToIndex.put(fullColumnName, index); } if (columnName != null) { this.columnNameToIndex.put(columnName, index); } } // set the flag to prevent rebuilding... this.builtIndexMapping = true; } public boolean hasBuiltIndexMapping() { return this.builtIndexMapping; } public Map getColumnLabelToIndex() { return this.columnLabelToIndex; } public void setColumnLabelToIndex(Map columnLabelToIndex) { this.columnLabelToIndex = columnLabelToIndex; } public Map getFullColumnNameToIndex() { return this.fullColumnNameToIndex; } public void setFullColumnNameToIndex(Map fullColNameToIndex) { this.fullColumnNameToIndex = fullColNameToIndex; } public Map getColumnNameToIndex() { return this.columnNameToIndex; } public void setColumnNameToIndex(Map colNameToIndex) { this.columnNameToIndex = colNameToIndex; } public Map getColumnToIndexCache() { return this.columnToIndexCache; } public void setColumnToIndexCache(Map columnToIndexCache) { this.columnToIndexCache = columnToIndexCache; } public void initializeFrom(ColumnDefinition columnDefinition) { this.fields = columnDefinition.getFields(); this.columnLabelToIndex = columnDefinition.getColumnNameToIndex(); this.fullColumnNameToIndex = columnDefinition.getFullColumnNameToIndex(); this.builtIndexMapping = true; } public void exportTo(ColumnDefinition columnDefinition) { columnDefinition.setFields(this.fields); columnDefinition.setColumnNameToIndex(this.columnLabelToIndex); columnDefinition.setFullColumnNameToIndex(this.fullColumnNameToIndex); } public int findColumn(String columnName, boolean useColumnNamesInFindColumn, int indexBase) { Integer index; if (!hasBuiltIndexMapping()) { buildIndexMapping(); } index = this.columnToIndexCache.get(columnName); if (index != null) { return index.intValue() + indexBase; } index = this.columnLabelToIndex.get(columnName); if (index == null && useColumnNamesInFindColumn) { index = this.columnNameToIndex.get(columnName); } if (index == null) { index = this.fullColumnNameToIndex.get(columnName); } if (index != null) { this.columnToIndexCache.put(columnName, index); return index.intValue() + indexBase; } // Try this inefficient way, now for (int i = 0; i < this.fields.length; i++) { if (this.fields[i].getName().equalsIgnoreCase(columnName)) { return i + indexBase; } else if (this.fields[i].getFullName().equalsIgnoreCase(columnName)) { return i + indexBase; } } return -1; } /** * Check if fields with type BLOB, MEDIUMBLOB, LONGBLOB, TEXT, MEDIUMTEXT or LONGTEXT * exist in this ColumnDefinition. * This check is used for making a decision about whether we want to force a * buffer row (better for rows with large fields). */ public boolean hasLargeFields() { if (this.fields != null) { for (int i = 0; i < this.fields.length; i++) { switch (this.fields[i].getMysqlType()) { case BLOB: case MEDIUMBLOB: case LONGBLOB: case TEXT: case MEDIUMTEXT: case LONGTEXT: case JSON: return true; default: break; } } } return false; } }