Line 1... |
Line 1... |
1 |
/*
|
1 |
/*
|
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
|
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
|
3 |
*
|
3 |
*
|
4 |
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
|
4 |
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
|
5 |
*
|
5 |
*
|
6 |
* The contents of this file are subject to the terms of the GNU General Public License Version 3
|
6 |
* The contents of this file are subject to the terms of the GNU General Public License Version 3
|
7 |
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
|
7 |
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
|
8 |
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
|
8 |
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
|
9 |
* language governing permissions and limitations under the License.
|
9 |
* language governing permissions and limitations under the License.
|
Line 18... |
Line 18... |
18 |
import org.openconcerto.sql.Log;
|
18 |
import org.openconcerto.sql.Log;
|
19 |
import org.openconcerto.sql.model.SQLSelect.ArchiveMode;
|
19 |
import org.openconcerto.sql.model.SQLSelect.ArchiveMode;
|
20 |
import org.openconcerto.sql.model.SQLSelect.LockStrength;
|
20 |
import org.openconcerto.sql.model.SQLSelect.LockStrength;
|
21 |
import org.openconcerto.sql.model.SQLSyntax.ConstraintType;
|
21 |
import org.openconcerto.sql.model.SQLSyntax.ConstraintType;
|
22 |
import org.openconcerto.sql.model.SQLTableEvent.Mode;
|
22 |
import org.openconcerto.sql.model.SQLTableEvent.Mode;
|
- |
|
23 |
import org.openconcerto.sql.model.Where.RowComparison;
|
23 |
import org.openconcerto.sql.model.graph.DatabaseGraph;
|
24 |
import org.openconcerto.sql.model.graph.DatabaseGraph;
|
24 |
import org.openconcerto.sql.model.graph.Link;
|
25 |
import org.openconcerto.sql.model.graph.Link;
|
25 |
import org.openconcerto.sql.model.graph.Link.Rule;
|
26 |
import org.openconcerto.sql.model.graph.Link.Rule;
|
26 |
import org.openconcerto.sql.model.graph.SQLKey;
|
27 |
import org.openconcerto.sql.model.graph.SQLKey;
|
27 |
import org.openconcerto.sql.model.graph.SQLKey.Type;
|
28 |
import org.openconcerto.sql.model.graph.SQLKey.Type;
|
28 |
import org.openconcerto.sql.model.graph.TablesMap;
|
29 |
import org.openconcerto.sql.model.graph.TablesMap;
|
29 |
import org.openconcerto.sql.request.UpdateBuilder;
|
30 |
import org.openconcerto.sql.request.UpdateBuilder;
|
30 |
import org.openconcerto.sql.utils.ChangeTable;
|
31 |
import org.openconcerto.sql.utils.ChangeTable;
|
31 |
import org.openconcerto.sql.utils.PartialUniqueTrigger;
|
32 |
import org.openconcerto.sql.utils.PartialUniqueTrigger;
|
32 |
import org.openconcerto.sql.utils.SQLCreateMoveableTable;
|
33 |
import org.openconcerto.sql.utils.SQLCreateMoveableTable;
|
- |
|
34 |
import org.openconcerto.sql.utils.SQLUtils;
|
33 |
import org.openconcerto.sql.utils.UniqueConstraintCreatorHelper;
|
35 |
import org.openconcerto.sql.utils.UniqueConstraintCreatorHelper;
|
34 |
import org.openconcerto.utils.CollectionUtils;
|
36 |
import org.openconcerto.utils.CollectionUtils;
|
35 |
import org.openconcerto.utils.CompareUtils;
|
37 |
import org.openconcerto.utils.CompareUtils;
|
36 |
import org.openconcerto.utils.ExceptionUtils;
|
38 |
import org.openconcerto.utils.ExceptionUtils;
|
37 |
import org.openconcerto.utils.ListMap;
|
39 |
import org.openconcerto.utils.ListMap;
|
Line 46... |
Line 48... |
46 |
|
48 |
|
47 |
import java.math.BigDecimal;
|
49 |
import java.math.BigDecimal;
|
48 |
import java.sql.DatabaseMetaData;
|
50 |
import java.sql.DatabaseMetaData;
|
49 |
import java.sql.ResultSet;
|
51 |
import java.sql.ResultSet;
|
50 |
import java.sql.SQLException;
|
52 |
import java.sql.SQLException;
|
- |
|
53 |
import java.sql.Statement;
|
- |
|
54 |
import java.sql.Types;
|
51 |
import java.util.ArrayList;
|
55 |
import java.util.ArrayList;
|
52 |
import java.util.Arrays;
|
56 |
import java.util.Arrays;
|
53 |
import java.util.Collection;
|
57 |
import java.util.Collection;
|
54 |
import java.util.Collections;
|
58 |
import java.util.Collections;
|
55 |
import java.util.EnumSet;
|
59 |
import java.util.EnumSet;
|
Line 61... |
Line 65... |
61 |
import java.util.LinkedList;
|
65 |
import java.util.LinkedList;
|
62 |
import java.util.List;
|
66 |
import java.util.List;
|
63 |
import java.util.ListIterator;
|
67 |
import java.util.ListIterator;
|
64 |
import java.util.Map;
|
68 |
import java.util.Map;
|
65 |
import java.util.Map.Entry;
|
69 |
import java.util.Map.Entry;
|
- |
|
70 |
import java.util.Objects;
|
66 |
import java.util.Set;
|
71 |
import java.util.Set;
|
67 |
import java.util.regex.Matcher;
|
72 |
import java.util.regex.Matcher;
|
68 |
import java.util.regex.Pattern;
|
73 |
import java.util.regex.Pattern;
|
69 |
|
74 |
|
70 |
import org.apache.commons.dbutils.ResultSetHandler;
|
75 |
import org.apache.commons.dbutils.ResultSetHandler;
|
Line 120... |
Line 125... |
120 |
return res;
|
125 |
return res;
|
121 |
}
|
126 |
}
|
122 |
};
|
127 |
};
|
123 |
|
128 |
|
124 |
@SuppressWarnings("unchecked")
|
129 |
@SuppressWarnings("unchecked")
|
125 |
public static final Map<String, Number> getUndefIDs(final SQLSchema schema) {
|
130 |
private static final Map<String, Number> getUndefIDs(final SQLSchema schema) {
|
126 |
assert Thread.holdsLock(UNDEFINED_IDs);
|
131 |
assert Thread.holdsLock(UNDEFINED_IDs);
|
127 |
if (!UNDEFINED_IDs.containsKey(schema)) {
|
132 |
if (!UNDEFINED_IDs.containsKey(schema)) {
|
128 |
final Map<String, Number> r;
|
133 |
final Map<String, Number> r;
|
129 |
if (schema.contains(undefTable)) {
|
134 |
if (schema.contains(undefTable)) {
|
130 |
final SQLBase b = schema.getBase();
|
135 |
final SQLBase b = schema.getBase();
|
Line 253... |
Line 258... |
253 |
schema.getDBSystemRoot().getDataSource().execute(update.asString());
|
258 |
schema.getDBSystemRoot().getDataSource().execute(update.asString());
|
254 |
}
|
259 |
}
|
255 |
}
|
260 |
}
|
256 |
final int res = toInsert.size() + toUpdate.size();
|
261 |
final int res = toInsert.size() + toUpdate.size();
|
257 |
if (res > 0) {
|
262 |
if (res > 0) {
|
258 |
undefT.fireTableModified(SQLRow.NONEXISTANT_ID);
|
263 |
undefT.fireTableModified();
|
259 |
}
|
264 |
}
|
260 |
return res;
|
265 |
return res;
|
261 |
}
|
266 |
}
|
262 |
}
|
267 |
}
|
263 |
|
268 |
|
- |
|
269 |
public static final boolean unsetUndefIDs(SQLSchema schema, Set<String> tableNames) throws SQLException {
|
- |
|
270 |
final boolean tableLoaded = schema.getTable(undefTable) != null;
|
- |
|
271 |
final boolean tableExists = unsetUndefIDs(schema.getDBSystemRoot(), schema.getDBRoot().getName(), tableNames);
|
- |
|
272 |
if (tableLoaded != tableExists)
|
- |
|
273 |
throw new IllegalStateException("Root not up to date, table loaded : " + tableLoaded + ", table exists : " + tableExists);
|
- |
|
274 |
return tableExists;
|
- |
|
275 |
}
|
- |
|
276 |
|
- |
|
277 |
public static final boolean unsetUndefIDs(final DBSystemRoot sysRoot, final String rootName, Set<String> tableNames) throws SQLException {
|
- |
|
278 |
final SQLName undefSQLName = new SQLName(Objects.requireNonNull(rootName, "Missing root name"), undefTable);
|
- |
|
279 |
final int deletedCount;
|
- |
|
280 |
try {
|
- |
|
281 |
// If already in a transaction, don't risk aborting it if a table doesn't exist.
|
- |
|
282 |
// (it's not strictly required for H2 and MySQL, since the transaction is *not*
|
- |
|
283 |
// aborted)
|
- |
|
284 |
deletedCount = SQLUtils.executeAtomic(sysRoot.getDataSource(), (ds) -> {
|
- |
|
285 |
try (final Statement stmt = ds.getConnection().createStatement()) {
|
- |
|
286 |
final int res = stmt.executeUpdate("DELETE FROM " + undefSQLName + " WHERE "
|
- |
|
287 |
+ Where.getCompareValuesClause(SQLBase.quoteIdentifier(UNDEF_TABLE_TABLENAME_FIELD), RowComparison.IN, tableNames, SQLType.getFromSyntax(sysRoot.getSyntax(), Types.VARCHAR, 250)));
|
- |
|
288 |
assert res >= 0;
|
- |
|
289 |
return res;
|
- |
|
290 |
}
|
- |
|
291 |
});
|
- |
|
292 |
} catch (SQLException e) {
|
- |
|
293 |
// nothing to unset
|
- |
|
294 |
if (sysRoot.getSyntax().isTableNotFoundException(e))
|
- |
|
295 |
return false;
|
- |
|
296 |
throw e;
|
- |
|
297 |
}
|
- |
|
298 |
if (deletedCount > 0) {
|
- |
|
299 |
// rootName might exist and thus the above query might succeed, but the root might not
|
- |
|
300 |
// be loaded or up to date.
|
- |
|
301 |
final SQLTable undefT = sysRoot.getDescLenient(undefSQLName, SQLTable.class);
|
- |
|
302 |
if (undefT != null)
|
- |
|
303 |
undefT.fireTableModified();
|
- |
|
304 |
}
|
- |
|
305 |
return true;
|
- |
|
306 |
}
|
- |
|
307 |
|
264 |
static private boolean AFTER_TX_DEFAULT = true;
|
308 |
static private boolean AFTER_TX_DEFAULT = true;
|
265 |
|
309 |
|
266 |
static public void setDefaultAfterTransaction(final boolean val) {
|
310 |
static public void setDefaultAfterTransaction(final boolean val) {
|
267 |
AFTER_TX_DEFAULT = val;
|
311 |
AFTER_TX_DEFAULT = val;
|
268 |
}
|
312 |
}
|
Line 797... |
Line 841... |
797 |
throw new IllegalStateException(this + " has more than 1 primary key: " + this.getPrimaryKeys());
|
841 |
throw new IllegalStateException(this + " has more than 1 primary key: " + this.getPrimaryKeys());
|
798 |
return this.primaryKey;
|
842 |
return this.primaryKey;
|
799 |
}
|
843 |
}
|
800 |
|
844 |
|
801 |
/**
|
845 |
/**
|
802 |
* Return the primary keys of this table.
|
846 |
* Return the fields of the primary key.
|
803 |
*
|
847 |
*
|
804 |
* @return the fields (SQLField) which are the keys of this table, can be empty.
|
848 |
* @return the fields of the primary key of this table, can be empty.
|
805 |
*/
|
849 |
*/
|
806 |
public synchronized Set<SQLField> getPrimaryKeys() {
|
850 |
public synchronized Set<SQLField> getPrimaryKeyFields() {
|
807 |
return this.primaryKeys;
|
851 |
return this.primaryKeys;
|
808 |
}
|
852 |
}
|
809 |
|
853 |
|
- |
|
854 |
@Deprecated
|
- |
|
855 |
public final Set<SQLField> getPrimaryKeys() {
|
- |
|
856 |
return this.getPrimaryKeyFields();
|
- |
|
857 |
}
|
- |
|
858 |
|
- |
|
859 |
public final List<String> getPKsNames() {
|
- |
|
860 |
return this.getPKsNames(new ArrayList<String>());
|
- |
|
861 |
}
|
- |
|
862 |
|
- |
|
863 |
public final <C extends Collection<String>> C getPKsNames(C pks) {
|
- |
|
864 |
for (final SQLField f : this.getPrimaryKeys()) {
|
- |
|
865 |
pks.add(f.getName());
|
- |
|
866 |
}
|
- |
|
867 |
return pks;
|
- |
|
868 |
}
|
- |
|
869 |
|
- |
|
870 |
public final RowRef createRowRef(final Object... pk) {
|
- |
|
871 |
return this.createRowRef(Arrays.asList(pk));
|
- |
|
872 |
}
|
- |
|
873 |
|
- |
|
874 |
public final RowRef createRowRef(final List<?> pk) {
|
- |
|
875 |
return new RowRef(this, pk);
|
- |
|
876 |
}
|
- |
|
877 |
|
- |
|
878 |
public final RowRef createRowRef(final Number id) {
|
- |
|
879 |
return new RowRef(this, id);
|
- |
|
880 |
}
|
- |
|
881 |
|
810 |
public final Set<Link> getForeignLinks() {
|
882 |
public final Set<Link> getForeignLinks() {
|
811 |
return this.getDBSystemRoot().getGraph().getForeignLinks(this);
|
883 |
return this.getDBSystemRoot().getGraph().getForeignLinks(this);
|
812 |
}
|
884 |
}
|
813 |
|
885 |
|
814 |
/**
|
886 |
/**
|
Line 1666... |
Line 1738... |
1666 |
|
1738 |
|
1667 |
public void removeTableListener(SQLTableListener l) {
|
1739 |
public void removeTableListener(SQLTableListener l) {
|
1668 |
this.removeTableModifiedListener(new BridgeListener(l));
|
1740 |
this.removeTableModifiedListener(new BridgeListener(l));
|
1669 |
}
|
1741 |
}
|
1670 |
|
1742 |
|
- |
|
1743 |
public final void fireTableModified() {
|
- |
|
1744 |
this.fireTableModified(SQLRow.NONEXISTANT_ID);
|
- |
|
1745 |
}
|
- |
|
1746 |
|
1671 |
/**
|
1747 |
/**
|
1672 |
* Previent tous les listeners de la table qu'il y a eu une modification ou ajout si modif de
|
1748 |
* Previent tous les listeners de la table qu'il y a eu une modification.
|
1673 |
* d'une ligne particuliere.
|
- |
|
1674 |
*
|
1749 |
*
|
1675 |
* @param id -1 signifie tout est modifié.
|
1750 |
* @param id which ID was modified, {@link SQLRow#NONEXISTANT_ID} meaning all rows.
|
1676 |
*/
|
1751 |
*/
|
1677 |
public void fireTableModified(final int id) {
|
1752 |
public void fireTableModified(final int id) {
|
1678 |
this.fire(Mode.ROW_UPDATED, id);
|
1753 |
this.fire(Mode.ROW_UPDATED, id);
|
1679 |
}
|
1754 |
}
|
1680 |
|
1755 |
|
Line 1913... |
Line 1988... |
1913 |
// equal)
|
1988 |
// equal)
|
1914 |
// if otherSystem isn't null, then this method is more lenient and return true if the two tables
|
1989 |
// if otherSystem isn't null, then this method is more lenient and return true if the two tables
|
1915 |
// are the closest possible. NOTE that otherSystem is not required to be the system of the other
|
1990 |
// are the closest possible. NOTE that otherSystem is not required to be the system of the other
|
1916 |
// table, it might be something else if the other table was loaded into a system different than
|
1991 |
// table, it might be something else if the other table was loaded into a system different than
|
1917 |
// the one which created the dump.
|
1992 |
// the one which created the dump.
|
1918 |
public synchronized String equalsDesc(SQLTable o, SQLSystem otherSystem, boolean compareName) {
|
1993 |
public synchronized String equalsDesc(SQLTable o, SQLSyntax otherSyntax, boolean compareName) {
|
1919 |
if (o == null)
|
1994 |
if (o == null)
|
1920 |
return "other table is null";
|
1995 |
return "other table is null";
|
1921 |
final boolean name = !compareName || this.getName().equals(o.getName());
|
1996 |
final boolean name = !compareName || this.getName().equals(o.getName());
|
1922 |
if (!name)
|
1997 |
if (!name)
|
1923 |
return "name unequal : " + this.getName() + " " + o.getName();
|
1998 |
return "name unequal : " + this.getName() + " " + o.getName();
|
Line 1928... |
Line 2003... |
1928 |
// return "triggers unequal : " + this.getTriggers() + " " + o.getTriggers();
|
2003 |
// return "triggers unequal : " + this.getTriggers() + " " + o.getTriggers();
|
1929 |
// } else {
|
2004 |
// } else {
|
1930 |
// if (!this.getTriggers().keySet().equals(o.getTriggers().keySet()))
|
2005 |
// if (!this.getTriggers().keySet().equals(o.getTriggers().keySet()))
|
1931 |
// return "triggers names unequal : " + this.getTriggers() + " " + o.getTriggers();
|
2006 |
// return "triggers names unequal : " + this.getTriggers() + " " + o.getTriggers();
|
1932 |
// }
|
2007 |
// }
|
1933 |
final boolean checkComment = otherSystem == null || this.getServer().getSQLSystem().isTablesCommentSupported() && otherSystem.isTablesCommentSupported();
|
2008 |
final boolean checkComment = otherSyntax == null || this.getServer().getSQLSystem().isTablesCommentSupported() && otherSyntax.getSystem().isTablesCommentSupported();
|
1934 |
if (checkComment && !CompareUtils.equals(this.getComment(), o.getComment()))
|
2009 |
if (checkComment && !CompareUtils.equals(this.getComment(), o.getComment()))
|
1935 |
return "comment unequal : " + SQLBase.quoteStringStd(this.getComment()) + " != " + SQLBase.quoteStringStd(o.getComment());
|
2010 |
return "comment unequal : " + SQLBase.quoteStringStd(this.getComment()) + " != " + SQLBase.quoteStringStd(o.getComment());
|
1936 |
return this.equalsChildren(o, otherSystem);
|
2011 |
return this.equalsChildren(o, otherSyntax);
|
1937 |
}
|
2012 |
}
|
1938 |
|
2013 |
|
1939 |
private synchronized String equalsChildren(SQLTable o, SQLSystem otherSystem) {
|
2014 |
private synchronized String equalsChildren(SQLTable o, SQLSyntax otherSyntax) {
|
1940 |
if (!this.getChildrenNames().equals(o.getChildrenNames()))
|
2015 |
if (!this.getChildrenNames().equals(o.getChildrenNames()))
|
1941 |
return "fields differences: " + this.getChildrenNames() + "\n" + o.getChildrenNames();
|
2016 |
return "fields differences: " + this.getChildrenNames() + "\n" + o.getChildrenNames();
|
1942 |
|
2017 |
|
1943 |
final String noLink = equalsChildrenNoLink(o, otherSystem);
|
2018 |
final String noLink = equalsChildrenNoLink(o, otherSyntax);
|
1944 |
if (noLink != null)
|
2019 |
if (noLink != null)
|
1945 |
return noLink;
|
2020 |
return noLink;
|
1946 |
|
2021 |
|
1947 |
// foreign keys
|
2022 |
// foreign keys
|
1948 |
final Set<Link> thisLinks = this.getForeignLinks();
|
2023 |
final Set<Link> thisLinks = this.getForeignLinks();
|
1949 |
final Set<Link> oLinks = o.getForeignLinks();
|
2024 |
final Set<Link> oLinks = o.getForeignLinks();
|
1950 |
if (thisLinks.size() != oLinks.size())
|
2025 |
if (thisLinks.size() != oLinks.size())
|
1951 |
return "different number of foreign keys " + thisLinks + " != " + oLinks;
|
2026 |
return "different number of foreign keys " + thisLinks + " != " + oLinks;
|
1952 |
final SQLSystem thisSystem = this.getServer().getSQLSystem();
|
2027 |
final SQLSystem thisSystem = this.getServer().getSQLSystem();
|
- |
|
2028 |
final SQLSystem otherSystem = otherSyntax == null ? null : otherSyntax.getSystem();
|
1953 |
for (final Link l : thisLinks) {
|
2029 |
for (final Link l : thisLinks) {
|
1954 |
final Link ol = o.getDBSystemRoot().getGraph().getForeignLink(o, l.getCols());
|
2030 |
final Link ol = o.getDBSystemRoot().getGraph().getForeignLink(o, l.getCols());
|
1955 |
if (ol == null)
|
2031 |
if (ol == null)
|
1956 |
return "no foreign key for " + l.getLabel();
|
2032 |
return "no foreign key for " + l.getLabel();
|
1957 |
final SQLName thisPath = l.getTarget().getContextualSQLName(this);
|
2033 |
final SQLName thisPath = l.getTarget().getContextualSQLName(this);
|
Line 2035... |
Line 2111... |
2035 |
*
|
2111 |
*
|
2036 |
* @param o the table to compare.
|
2112 |
* @param o the table to compare.
|
2037 |
* @param otherSystem the system <code>o</code> originates from, can be <code>null</code>.
|
2113 |
* @param otherSystem the system <code>o</code> originates from, can be <code>null</code>.
|
2038 |
* @return <code>null</code> if each fields of this exists in <code>o</code> and is equal to it.
|
2114 |
* @return <code>null</code> if each fields of this exists in <code>o</code> and is equal to it.
|
2039 |
*/
|
2115 |
*/
|
2040 |
public synchronized final String equalsChildrenNoLink(SQLTable o, SQLSystem otherSystem) {
|
2116 |
public synchronized final String equalsChildrenNoLink(SQLTable o, SQLSyntax otherSystem) {
|
2041 |
for (final SQLField f : this.getFields()) {
|
2117 |
for (final SQLField f : this.getFields()) {
|
2042 |
final SQLField oField = o.getField(f.getName());
|
2118 |
final SQLField oField = o.getField(f.getName());
|
2043 |
final boolean isPrimary = this.getPrimaryKeys().contains(f);
|
2119 |
final boolean isPrimary = this.getPrimaryKeys().contains(f);
|
2044 |
if (isPrimary != o.getPrimaryKeys().contains(oField))
|
2120 |
if (isPrimary != o.getPrimaryKeys().contains(oField))
|
2045 |
return f + " is a primary not in " + o.getPrimaryKeys();
|
2121 |
return f + " is a primary not in " + o.getPrimaryKeys();
|
Line 2104... |
Line 2180... |
2104 |
if (this.getComment() != null)
|
2180 |
if (this.getComment() != null)
|
2105 |
res.addOutsideClause(syntax.getSetTableComment(getComment()));
|
2181 |
res.addOutsideClause(syntax.getSetTableComment(getComment()));
|
2106 |
return res;
|
2182 |
return res;
|
2107 |
}
|
2183 |
}
|
2108 |
|
2184 |
|
2109 |
public final List<String> getPKsNames() {
|
- |
|
2110 |
return this.getPKsNames(new ArrayList<String>());
|
- |
|
2111 |
}
|
- |
|
2112 |
|
- |
|
2113 |
public synchronized final <C extends Collection<String>> C getPKsNames(C pks) {
|
- |
|
2114 |
for (final SQLField f : this.getPrimaryKeys()) {
|
- |
|
2115 |
pks.add(f.getName());
|
- |
|
2116 |
}
|
- |
|
2117 |
return pks;
|
- |
|
2118 |
}
|
- |
|
2119 |
|
- |
|
2120 |
public final String[] getPKsNamesArray() {
|
- |
|
2121 |
return getPKsNames().toArray(new String[0]);
|
- |
|
2122 |
}
|
- |
|
2123 |
|
- |
|
2124 |
/**
|
2185 |
/**
|
2125 |
* Return the indexes mapped by column names. Ie a key will have as value every index that
|
2186 |
* Return the indexes mapped by column names. Ie a key will have as value every index that
|
2126 |
* mentions it, and a multi-column index will be in several entries. Note: this is not robust
|
2187 |
* mentions it, and a multi-column index will be in several entries. Note: this is not robust
|
2127 |
* since {@link Index#getCols()} isn't.
|
2188 |
* since {@link Index#getCols()} isn't.
|
2128 |
*
|
2189 |
*
|