提交 7585c39a authored 作者: Thomas Mueller's avatar Thomas Mueller

Issue 326: improved support for case sensitive (mixed case) identifiers without…

Issue 326: improved support for case sensitive (mixed case) identifiers without quotes when using DATABASE_TO_UPPER=FALSE.
上级 4bef1ee5
......@@ -19,7 +19,10 @@ Change Log
<h1>Change Log</h1>
<h2>Next Version (unreleased)</h2>
<ul><li>-
<ul><li>The optimization for "group by" was not working correctly if the group by column
was aliased in the select list.
</li><li>Issue 326: improved support for case sensitive (mixed case) identifiers
without quotes when using DATABASE_TO_UPPER=FALSE.
</li></ul>
<h2>Version 1.3.161 (2011-10-28)</h2>
......
......@@ -97,6 +97,7 @@ spread the word, and translated this project. Also many thanks to the donors:
</li><li><a href="http://skycash.com">SkyCash, Poland</a>
</li><li><a href="http://lumber-mill.co.jp">Lumber-mill, Inc., Japan</a>
</li><li><a href="http://www.stockmarketeye.com">StockMarketEye, USA</a>
</li><li>Alessio Jacopo D'Adamo, Italy</a>
</li><li>Martin Wildam, Austria
</li><li>Ashwin Jayaprakash, USA
</li><li>Donald Bleyl, USA
......
......@@ -26,7 +26,6 @@ import org.h2.table.ColumnResolver;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.util.New;
import org.h2.util.StringUtils;
import org.h2.value.Value;
import org.h2.value.ValueInt;
import org.h2.value.ValueNull;
......@@ -311,6 +310,7 @@ public abstract class Query extends Prepared {
/**
* Initialize the order by list. This call may extend the expressions list.
*
* @param session the session
* @param expressions the select list expressions
* @param expressionSQL the select list SQL snippets
* @param orderList the order by list
......@@ -318,12 +318,14 @@ public abstract class Query extends Prepared {
* @param mustBeInResult all order by expressions must be in the select list
* @param filters the table filters
*/
static void initOrder(ArrayList<Expression> expressions,
static void initOrder(Session session,
ArrayList<Expression> expressions,
ArrayList<String> expressionSQL,
ArrayList<SelectOrderBy> orderList,
int visible,
boolean mustBeInResult,
ArrayList<TableFilter> filters) {
Database db = session.getDatabase();
for (SelectOrderBy o : orderList) {
Expression e = o.expression;
if (e == null) {
......@@ -346,7 +348,7 @@ public abstract class Query extends Prepared {
if (ec instanceof ExpressionColumn) {
// select expression
ExpressionColumn c = (ExpressionColumn) ec;
found = col.equals(c.getColumnName());
found = db.equalsIdentifiers(col, c.getColumnName());
if (found && tableAlias != null) {
String ca = c.getOriginalTableAliasName();
if (ca == null) {
......@@ -354,18 +356,18 @@ public abstract class Query extends Prepared {
// select id from test order by test.id
for (int i = 0, size = filters.size(); i < size; i++) {
TableFilter f = filters.get(i);
if (f.getTableAlias().equals(tableAlias)) {
if (db.equalsIdentifiers(f.getTableAlias(), tableAlias)) {
found = true;
break;
}
}
} else {
found = ca.equals(tableAlias);
found = db.equalsIdentifiers(ca, tableAlias);
}
}
} else if (!(ec instanceof Alias)) {
continue;
} else if (tableAlias == null && col.equals(ec.getAlias())) {
} else if (tableAlias == null && db.equalsIdentifiers(col, ec.getAlias())) {
found = true;
} else {
Expression ec2 = ec.getNonAliasExpression();
......@@ -375,8 +377,9 @@ public abstract class Query extends Prepared {
// exprCol.getTableAlias();
String tb = c2.getSQL();
// getTableAlias();
found = col.equals(c2.getColumnName());
if (!StringUtils.equals(ta, tb)) {
String s2 = c2.getColumnName();
found = db.equalsIdentifiers(col, s2);
if (!db.equalsIdentifiers(ta, tb)) {
found = false;
}
}
......@@ -392,7 +395,7 @@ public abstract class Query extends Prepared {
if (expressionSQL != null) {
for (int j = 0, size = expressionSQL.size(); j < size; j++) {
String s2 = expressionSQL.get(j);
if (s2.equals(s)) {
if (db.equalsIdentifiers(s2, s)) {
idx = j;
isAlias = true;
break;
......
......@@ -286,7 +286,7 @@ public class SelectUnion extends Query {
expressions.add(e);
}
if (orderList != null) {
initOrder(expressions, null, orderList, getColumnCount(), true, null);
initOrder(session, expressions, null, orderList, getColumnCount(), true, null);
sort = prepareOrder(orderList, expressions.size());
orderList = null;
}
......
......@@ -61,6 +61,7 @@ import org.h2.util.SourceCompiler;
import org.h2.util.StringUtils;
import org.h2.util.TempFileDeleter;
import org.h2.util.Utils;
import org.h2.value.CaseInsensitiveMap;
import org.h2.value.CompareMode;
import org.h2.value.Value;
import org.h2.value.ValueInt;
......@@ -2343,4 +2344,20 @@ public class Database implements DataHandler {
return dbSettings;
}
public <V> HashMap<String, V> newStringMap() {
return dbSettings.databaseToUpper ?
new HashMap<String, V>() :
new CaseInsensitiveMap<V>();
}
public boolean equalsIdentifiers(String a, String b) {
if (a == b || a.equals(b)) {
return true;
}
if (!dbSettings.databaseToUpper && a.equalsIgnoreCase(b)) {
return true;
}
return false;
}
}
......@@ -124,7 +124,7 @@ public class Session extends SessionWithState {
private void initVariables() {
if (variables == null) {
variables = New.hashMap();
variables = database.newStringMap();
}
}
......@@ -209,7 +209,7 @@ public class Session extends SessionWithState {
*/
public void addLocalTempTable(Table table) {
if (localTempTables == null) {
localTempTables = New.hashMap();
localTempTables = database.newStringMap();
}
if (localTempTables.get(table.getName()) != null) {
throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, table.getSQL());
......@@ -260,7 +260,7 @@ public class Session extends SessionWithState {
*/
public void addLocalTempTableIndex(Index index) {
if (localTempTableIndexes == null) {
localTempTableIndexes = New.hashMap();
localTempTableIndexes = database.newStringMap();
}
if (localTempTableIndexes.get(index.getName()) != null) {
throw DbException.get(ErrorCode.INDEX_ALREADY_EXISTS_1, index.getSQL());
......@@ -298,7 +298,7 @@ public class Session extends SessionWithState {
/**
* Get the map of constraints for all constraints on local, temporary
* tables, if any. The map's keys are the constraints' names.
* tables, if any. The map's keys are the constraints' names.
*
* @return the map of constraints, or null
*/
......@@ -317,7 +317,7 @@ public class Session extends SessionWithState {
*/
public void addLocalTempTableConstraint(Constraint constraint) {
if (localTempTableConstraints == null) {
localTempTableConstraints = New.hashMap();
localTempTableConstraints = database.newStringMap();
}
String name = constraint.getName();
if (localTempTableConstraints.get(name) != null) {
......@@ -753,7 +753,7 @@ public class Session extends SessionWithState {
*/
public void addSavepoint(String name) {
if (savepoints == null) {
savepoints = New.hashMap();
savepoints = database.newStringMap();
}
savepoints.put(name, getLogId());
}
......@@ -989,7 +989,7 @@ public class Session extends SessionWithState {
*/
public void addProcedure(Procedure procedure) {
if (procedures == null) {
procedures = New.hashMap();
procedures = database.newStringMap();
}
procedures.put(procedure.getName(), procedure);
}
......@@ -1093,6 +1093,10 @@ public class Session extends SessionWithState {
if (exclusive == null || exclusive == this) {
break;
}
if (Thread.holdsLock(exclusive)) {
// if another connection is used within the connection
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
......
......@@ -52,16 +52,19 @@ public class ExpressionColumn extends Expression {
public String getSQL() {
String sql;
boolean quote = database.getSettings().databaseToUpper;
if (column != null) {
sql = column.getSQL();
} else {
sql = Parser.quoteIdentifier(columnName);
sql = quote ? Parser.quoteIdentifier(columnName) : columnName;
}
if (tableAlias != null) {
sql = Parser.quoteIdentifier(tableAlias) + "." + sql;
String a = quote ? Parser.quoteIdentifier(tableAlias) : tableAlias;
sql = a + "." + sql;
}
if (schemaName != null) {
sql = Parser.quoteIdentifier(schemaName) + "." + sql;
String s = quote ? Parser.quoteIdentifier(schemaName) : schemaName;
sql = s + "." + sql;
}
return sql;
}
......@@ -71,19 +74,20 @@ public class ExpressionColumn extends Expression {
}
public void mapColumns(ColumnResolver resolver, int level) {
if (tableAlias != null && !tableAlias.equals(resolver.getTableAlias())) {
if (tableAlias != null && !database.equalsIdentifiers(tableAlias, resolver.getTableAlias())) {
return;
}
if (schemaName != null && !schemaName.equals(resolver.getSchemaName())) {
if (schemaName != null && !database.equalsIdentifiers(schemaName, resolver.getSchemaName())) {
return;
}
for (Column col : resolver.getColumns()) {
if (columnName.equals(col.getName())) {
String n = col.getName();
if (database.equalsIdentifiers(columnName, n)) {
mapColumn(resolver, col, level);
return;
}
}
if (Column.ROWID.equals(columnName)) {
if (database.equalsIdentifiers(Column.ROWID, columnName)) {
Column col = resolver.getRowIdColumn();
if (col != null) {
mapColumn(resolver, col, level);
......@@ -93,7 +97,7 @@ public class ExpressionColumn extends Expression {
Column[] columns = resolver.getSystemColumns();
for (int i = 0; columns != null && i < columns.length; i++) {
Column col = columns[i];
if (columnName.equals(col.getName())) {
if (database.equalsIdentifiers(columnName, col.getName())) {
mapColumn(resolver, col, level);
return;
}
......
......@@ -23,8 +23,8 @@ import org.h2.engine.User;
import org.h2.index.Index;
import org.h2.message.DbException;
import org.h2.message.Trace;
import org.h2.table.Table;
import org.h2.table.RegularTable;
import org.h2.table.Table;
import org.h2.table.TableLink;
import org.h2.util.New;
import org.h2.util.Utils;
......@@ -36,22 +36,22 @@ import org.h2.util.Utils;
public class Schema extends DbObjectBase {
private User owner;
private boolean system;
private final boolean system;
private HashMap<String, Table> tablesAndViews = New.hashMap();
private HashMap<String, Index> indexes = New.hashMap();
private HashMap<String, Sequence> sequences = New.hashMap();
private HashMap<String, TriggerObject> triggers = New.hashMap();
private HashMap<String, Constraint> constraints = New.hashMap();
private HashMap<String, Constant> constants = New.hashMap();
private HashMap<String, FunctionAlias> functions = New.hashMap();
private final HashMap<String, Table> tablesAndViews;
private final HashMap<String, Index> indexes;
private final HashMap<String, Sequence> sequences;
private final HashMap<String, TriggerObject> triggers;
private final HashMap<String, Constraint> constraints;
private final HashMap<String, Constant> constants;
private final HashMap<String, FunctionAlias> functions;
/**
* The set of returned unique names that are not yet stored. It is used to
* avoid returning the same unique name twice when multiple threads
* concurrently create objects.
*/
private HashSet<String> temporaryUniqueNames = New.hashSet();
private final HashSet<String> temporaryUniqueNames = New.hashSet();
/**
* Create a new schema object.
......@@ -64,6 +64,13 @@ public class Schema extends DbObjectBase {
* dropped)
*/
public Schema(Database database, int id, String schemaName, User owner, boolean system) {
tablesAndViews = database.newStringMap();
indexes = database.newStringMap();
sequences = database.newStringMap();
triggers = database.newStringMap();
constraints = database.newStringMap();
constants = database.newStringMap();
functions = database.newStringMap();
initDbObjectBase(database, id, schemaName, Trace.SCHEMA);
this.owner = owner;
this.system = system;
......@@ -571,4 +578,8 @@ public class Schema extends DbObjectBase {
}
}
public <V> HashMap<String, V> newStringMap() {
return database.newStringMap();
}
}
......@@ -37,10 +37,10 @@ import org.h2.jdbc.JdbcStatement;
import org.h2.message.DbException;
import org.h2.util.IOUtils;
import org.h2.util.JdbcUtils;
import org.h2.util.New;
import org.h2.util.ScriptReader;
import org.h2.util.StringUtils;
import org.h2.util.Utils;
import org.h2.value.CaseInsensitiveMap;
/**
* One server thread is opened for each client.
......@@ -63,8 +63,8 @@ public class PgServerThread implements Runnable {
private int processId;
private String clientEncoding = SysProperties.PG_DEFAULT_CLIENT_ENCODING;
private String dateStyle = "ISO";
private HashMap<String, Prepared> prepared = New.hashMap();
private HashMap<String, Portal> portals = New.hashMap();
private HashMap<String, Prepared> prepared = new CaseInsensitiveMap<Prepared>();
private HashMap<String, Portal> portals = new CaseInsensitiveMap<Portal>();
PgServerThread(Socket socket, PgServer server) {
this.server = server;
......
......@@ -29,7 +29,6 @@ public class PageFreeList extends Page {
private Data data;
private PageFreeList(PageStore store, int pageId) {
int todoKeptInTwoPlaces;
// kept in cache, and array list in page store
setPos(pageId);
this.store = store;
......
......@@ -113,7 +113,10 @@ public class Column {
if (table == null || other.table == null || name == null || other.name == null) {
return false;
}
return table == other.table && name.equals(other.name);
if (table != other.table) {
return false;
}
return name.equals(other.name);
}
public int hashCode() {
......
......@@ -95,7 +95,7 @@ public abstract class Table extends SchemaObjectBase {
*/
protected boolean isHidden;
private final HashMap<String, Column> columnMap = New.hashMap();
private final HashMap<String, Column> columnMap;
private boolean persistIndexes;
private boolean persistData;
private ArrayList<TriggerObject> triggers;
......@@ -107,6 +107,7 @@ public abstract class Table extends SchemaObjectBase {
private Row nullRow;
public Table(Schema schema, int id, String name, boolean persistIndexes, boolean persistData) {
columnMap = schema.newStringMap();
initSchemaObjectBase(schema, id, name, Trace.TABLE);
this.persistIndexes = persistIndexes;
this.persistData = persistData;
......
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.value;
import java.util.HashMap;
import org.h2.util.StringUtils;
/**
* A hash map with a case-insensitive string key.
*
* @param <V> the value type
*/
public class CaseInsensitiveMap<V> extends HashMap<String, V> {
private static final long serialVersionUID = 1L;
public V get(Object key) {
return super.get(toUpper(key));
}
public V put(String key, V value) {
return super.put(toUpper(key), value);
}
public boolean containsKey(Object key) {
return super.containsKey(toUpper(key));
}
public V remove(Object key) {
return super.remove(toUpper(key));
}
private String toUpper(Object key) {
return key == null ? null : StringUtils.toUpperEnglish(key.toString());
}
}
......@@ -7,6 +7,7 @@
package org.h2.test.db;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
......@@ -34,8 +35,8 @@ public class TestCompatibility extends TestBase {
public void test() throws SQLException {
deleteDb("compatibility");
testKeyAsColumnInMySQLMode();
testCaseSensitiveIdentifiers();
testKeyAsColumnInMySQLMode();
conn = getConnection("compatibility");
testDomain();
......@@ -64,14 +65,40 @@ public class TestCompatibility extends TestBase {
Connection c = getConnection("compatibility;DATABASE_TO_UPPER=FALSE");
Statement stat = c.createStatement();
stat.execute("create table test(id int primary key, name varchar) as select 1, 'hello'");
assertThrows(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, stat).
execute("create table test(id int primary key, name varchar)");
assertThrows(ErrorCode.DUPLICATE_COLUMN_NAME_1, stat).
execute("alter table test add column Name varchar");
ResultSet rs;
rs = stat.executeQuery("select * from test");
DatabaseMetaData meta = c.getMetaData();
rs = meta.getTables(null, null, "test", null);
assertTrue(rs.next());
assertEquals("test", rs.getString("TABLE_NAME"));
rs = stat.executeQuery("select id, name from test");
assertEquals("id", rs.getMetaData().getColumnLabel(1));
assertEquals("name", rs.getMetaData().getColumnLabel(2));
rs = stat.executeQuery("select Id, Name from Test");
assertEquals("id", rs.getMetaData().getColumnLabel(1));
assertEquals("name", rs.getMetaData().getColumnLabel(2));
assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, stat).
execute("select * from TEST");
rs = stat.executeQuery("select ID, NAME from TEST");
assertEquals("id", rs.getMetaData().getColumnLabel(1));
assertEquals("name", rs.getMetaData().getColumnLabel(2));
stat.execute("select COUNT(*), count(*), Count(*), Sum(id) from test");
stat.execute("select LENGTH(name), length(name), Length(name) from test");
stat.execute("select t.id from test t group by t.id");
stat.execute("select id from test t group by t.id");
stat.execute("select id from test group by ID");
stat.execute("select id as c from test group by c");
stat.execute("select t.id from test t group by T.ID");
stat.execute("select id from test t group by T.ID");
stat.execute("drop table test");
c.close();
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论