/* * Copyright (C) 2012-2015 DataStax Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.datastax.driver.core; import java.util.*; /** * Metadata describing the columns returned in a {@link ResultSet} or a * {@link PreparedStatement}. * <p/> * A {@code columnDefinitions}} instance is mainly a list of * {@code ColumnsDefinitions.Definition}. The definitions or metadata for a column * can be accessed either by: * <ul> * <li>index (indexed from 0)</li> * <li>name</li> * </ul> * <p/> * When accessed by name, column selection is case insensitive. In case multiple * columns only differ by the case of their name, then the column returned with * be the first column that has been defined in CQL without forcing case sensitivity * (that is, it has either been defined without quotes or is fully lowercase). * If none of the columns have been defined in this manner, the first column matching * (with case insensitivity) is returned. You can force the case of a selection * by double quoting the name. * <p/> * For example: * <ul> * <li>If {@code cd} contains column {@code fOO}, then {@code cd.contains("foo")}, * {@code cd.contains("fOO")} and {@code cd.contains("Foo")} will return {@code true}.</li> * <li>If {@code cd} contains both {@code foo} and {@code FOO} then: * <ul> * <li>{@code cd.getType("foo")}, {@code cd.getType("fOO")} and {@code cd.getType("FOO")} * will all match column {@code foo}.</li> * <li>{@code cd.getType("\"FOO\"")} will match column {@code FOO}</li> * </ul> * </ul> * Note that the preceding rules mean that if a {@code ColumnDefinitions} object * contains multiple occurrences of the exact same name (be it the same column * multiple times or columns from different tables with the same name), you * will have to use selection by index to disambiguate. */ public class ColumnDefinitions implements Iterable<ColumnDefinitions.Definition> { static final ColumnDefinitions EMPTY = new ColumnDefinitions(new Definition[0], CodecRegistry.DEFAULT_INSTANCE); private final Definition[] byIdx; private final Map<String, int[]> byName; final CodecRegistry codecRegistry; ColumnDefinitions(Definition[] defs, CodecRegistry codecRegistry) { this.byIdx = defs; this.codecRegistry = codecRegistry; this.byName = new HashMap<String, int[]>(defs.length); for (int i = 0; i < defs.length; i++) { // Be optimistic, 99% of the time, previous will be null. int[] previous = this.byName.put(defs[i].name.toLowerCase(), new int[]{i}); if (previous != null) { int[] indexes = new int[previous.length + 1]; System.arraycopy(previous, 0, indexes, 0, previous.length); indexes[indexes.length - 1] = i; this.byName.put(defs[i].name.toLowerCase(), indexes); } } } /** * Returns the number of columns described by this {@code Columns} * instance. * * @return the number of columns described by this metadata. */ public int size() { return byIdx.length; } /** * Returns whether this metadata contains a given name. * * @param name the name to check. * @return {@code true} if this metadata contains the column named {@code name}, * {@code false} otherwise. */ public boolean contains(String name) { return findAllIdx(name) != null; } /** * The first index in this metadata of the provided name, if present. * * @param name the name of the column. * @return the index of the first occurrence of {@code name} in this metadata if * {@code contains(name)}, -1 otherwise. */ public int getIndexOf(String name) { return findFirstIdx(name); } /** * Returns an iterator over the {@link Definition} contained in this metadata. * <p/> * The order of the iterator will be the one of this metadata. * * @return an iterator over the {@link Definition} contained in this metadata. */ @Override public Iterator<Definition> iterator() { return Arrays.asList(byIdx).iterator(); } /** * Returns a list containing all the definitions of this metadata in order. * * @return a list of the {@link Definition} contained in this metadata. */ public List<Definition> asList() { return Arrays.asList(byIdx); } /** * Returns the name of the {@code i}th column in this metadata. * * @param i the index in this metadata. * @return the name of the {@code i}th column in this metadata. * @throws IndexOutOfBoundsException if {@code i < 0} or {@code i >= size()} */ public String getName(int i) { return byIdx[i].name; } /** * Returns the type of the {@code i}th column in this metadata. * <p> * Note that this method does not set the {@link DataType#isFrozen()} flag on the returned * object, it will always default to {@code false}. Use {@link Cluster#getMetadata()} to * determine if a column is frozen. * * @param i the index in this metadata. * @return the type of the {@code i}th column in this metadata. * @throws IndexOutOfBoundsException if {@code i < 0} or {@code i >= size()} */ public DataType getType(int i) { return byIdx[i].type; } /** * Returns the type of the first occurrence of {@code name} in this metadata. * <p> * Note that this method does not set the {@link DataType#isFrozen()} flag on the returned * object, it will always default to {@code false}. Use {@link Cluster#getMetadata()} to * determine if a column is frozen. * * @param name the name of the column. * @return the type of (the first occurrence of) {@code name} in this metadata. * @throws IllegalArgumentException if {@code name} is not in this metadata. */ public DataType getType(String name) { return getType(getFirstIdx(name)); } /** * Returns the keyspace of the {@code i}th column in this metadata. * * @param i the index in this metadata. * @return the keyspace of the {@code i}th column in this metadata. * @throws IndexOutOfBoundsException if {@code i < 0} or {@code i >= size()} */ public String getKeyspace(int i) { return byIdx[i].keyspace; } /** * Returns the keyspace of the first occurrence of {@code name} in this metadata. * * @param name the name of the column. * @return the keyspace of (the first occurrence of) column {@code name} in this metadata. * @throws IllegalArgumentException if {@code name} is not in this metadata. */ public String getKeyspace(String name) { return getKeyspace(getFirstIdx(name)); } /** * Returns the table of the {@code i}th column in this metadata. * * @param i the index in this metadata. * @return the table of the {@code i}th column in this metadata. * @throws IndexOutOfBoundsException if {@code i < 0} or {@code i >= size()} */ public String getTable(int i) { return byIdx[i].table; } /** * Returns the table of first occurrence of {@code name} in this metadata. * * @param name the name of the column. * @return the table of (the first occurrence of) column {@code name} in this metadata. * @throws IllegalArgumentException if {@code name} is not in this metadata. */ public String getTable(String name) { return getTable(getFirstIdx(name)); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Columns["); for (int i = 0; i < size(); i++) { if (i != 0) sb.append(", "); Definition def = byIdx[i]; sb.append(def.name).append('(').append(def.type).append(')'); } sb.append(']'); return sb.toString(); } int findFirstIdx(String name) { int[] indexes = findAllIdx(name); return indexes == null ? -1 : indexes[0]; } int[] findAllIdx(String name) { boolean caseSensitive = false; if (name.length() >= 2 && name.charAt(0) == '"' && name.charAt(name.length() - 1) == '"') { name = name.substring(1, name.length() - 1); caseSensitive = true; } int[] indexes = byName.get(name.toLowerCase()); if (!caseSensitive || indexes == null) return indexes; // First, optimistic and assume all are matching int nbMatch = 0; for (int i = 0; i < indexes.length; i++) if (name.equals(byIdx[indexes[i]].name)) nbMatch++; if (nbMatch == indexes.length) return indexes; int[] result = new int[nbMatch]; int j = 0; for (int i = 0; i < indexes.length; i++) { int idx = indexes[i]; if (name.equals(byIdx[idx].name)) result[j++] = idx; } return result; } int[] getAllIdx(String name) { int[] indexes = findAllIdx(name); if (indexes == null) throw new IllegalArgumentException(name + " is not a column defined in this metadata"); return indexes; } int getFirstIdx(String name) { return getAllIdx(name)[0]; } /** * A column definition. */ public static class Definition { private final String keyspace; private final String table; private final String name; private final DataType type; Definition(String keyspace, String table, String name, DataType type) { this.keyspace = keyspace; this.table = table; this.name = name; this.type = type; } /** * The name of the keyspace this column is part of. * * @return the name of the keyspace this column is part of. */ public String getKeyspace() { return keyspace; } /** * Returns the name of the table this column is part of. * * @return the name of the table this column is part of. */ public String getTable() { return table; } /** * Returns the name of the column. * * @return the name of the column. */ public String getName() { return name; } /** * Returns the type of the column. * * @return the type of the column. */ public DataType getType() { return type; } @Override public final int hashCode() { return Arrays.hashCode(new Object[]{keyspace, table, name, type}); } @Override public final boolean equals(Object o) { if (!(o instanceof Definition)) return false; Definition other = (Definition) o; return keyspace.equals(other.keyspace) && table.equals(other.table) && name.equals(other.name) && type.equals(other.type); } } }