Unverified 提交 617709e7 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov 提交者: GitHub

Merge pull request #1745 from katzyn/lower

Add DATABASE_TO_LOWER and CASE_INSENSITIVE_IDENTIFIERS
......@@ -2797,7 +2797,7 @@ CURRENT ROW
"Other Grammar","Term","
value
| columnName
| column
| ?[ int ]
| NEXT VALUE FOR sequenceName
| function
......@@ -2806,15 +2806,22 @@ value
| select
| case
| caseWhen
| tableAlias.columnName
| userDefinedFunctionName
","
A value. Parameters can be indexed, for example ""?1"" meaning the first parameter.
Each table has a pseudo-column named ""_ROWID_"" that contains the unique row identifier.
","
'Hello'
"
"Other Grammar","Column","
[[schemaName.]tableAlias.] { columnName | _ROWID_ }
","
A column name with optional table alias and schema.
_ROWID_ can be used to access unique row identifier.
","
ID
"
"Other Grammar","Time","
TIME [ WITHOUT TIME ZONE ] 'hh:mm:ss[.nnnnnnnnn]'
","
......
......@@ -480,7 +480,7 @@ CURRENT_TIME, CURRENT_TIMESTAMP, CURRENT_USER, DISTINCT, EXCEPT,
EXISTS, FALSE, FETCH, FOR, FOREIGN, FROM, FULL, GROUP, HAVING,
IF, INNER, INTERSECT, INTERSECTS, INTERVAL, IS, JOIN, LIKE,
LIMIT, LOCALTIME, LOCALTIMESTAMP, MINUS, NATURAL, NOT, NULL,
OFFSET, ON, ORDER, PRIMARY, QUALIFY, ROW, ROWNUM, SELECT,
OFFSET, ON, ORDER, PRIMARY, QUALIFY, ROW, _ROWID_, ROWNUM, SELECT,
SYSDATE, SYSTIME, SYSTIMESTAMP, TABLE, TODAY, TOP, TRUE, UNION,
UNIQUE, VALUES, WHERE, WINDOW, WITH
</code>
......
......@@ -21,6 +21,18 @@ Change Log
<h2>Next Version (unreleased)</h2>
<ul>
<li>Issue #1739: Table and view names not case sensitive when using DATABASE_TO_UPPER=FALSE
</li>
<li>Issue #848: H2 PostgreSQL Compatibility Mode: lowercase metadata
</li>
<li>Issue #485: Problem is in invalid case for schema's IGNORECASE=true;DATABASE_TO_UPPER=false
</li>
<li>Issue #1742, PR #1743: Assorted small changes
</li>
<li>PR #1738: Reduce memory allocation in getSQL() methods
</li>
<li>PR #1737: more javadoc updates
</li>
<li>Issue #1735: Creating views with DATABASE_TO_UPPER=FALSE fails
</li>
<li>Issue #1732: source.html does not work
......
......@@ -107,7 +107,7 @@ public class AlterTableAddConstraint extends SchemaCommand {
}
throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, tableName);
}
if (getSchema().findConstraint(session, constraintName) != null) {
if (constraintName != null && getSchema().findConstraint(session, constraintName) != null) {
if (ifNotExists) {
return 0;
}
......
......@@ -69,7 +69,7 @@ public class CreateIndex extends SchemaCommand {
}
throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, tableName);
}
if (getSchema().findIndex(session, indexName) != null) {
if (indexName != null && getSchema().findIndex(session, indexName) != null) {
if (ifNotExists) {
return 0;
}
......
......@@ -459,7 +459,7 @@ public class Insert extends CommandWithValues implements ResultTarget {
for (Column column : indexedColumns) {
ExpressionColumn expr = new ExpressionColumn(session.getDatabase(),
table.getSchema().getName(), table.getName(),
column.getName());
column.getName(), false);
for (int i = 0; i < columns.length; i++) {
if (expr.getColumnName().equals(columns[i].getName())) {
if (condition == null) {
......
......@@ -23,6 +23,7 @@ import org.h2.message.DbException;
import org.h2.result.ResultInterface;
import org.h2.result.Row;
import org.h2.result.RowImpl;
import org.h2.table.Column;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.util.Utils;
......@@ -422,7 +423,7 @@ public class MergeUsing extends Prepared {
targetMatchQuery = new Select(session, null);
ArrayList<Expression> expressions = new ArrayList<>(1);
expressions.add(new ExpressionColumn(session.getDatabase(), targetTable.getSchema().getName(),
targetTableFilter.getTableAlias(), "_ROWID_"));
targetTableFilter.getTableAlias(), Column.ROWID, true));
targetMatchQuery.setExpressions(expressions);
targetMatchQuery.addTableFilter(targetTableFilter, true);
targetMatchQuery.addCondition(onCondition);
......
......@@ -1049,7 +1049,7 @@ public class Select extends Query {
}
String name = filter.getDerivedColumnName(c);
ExpressionColumn ec = new ExpressionColumn(
session.getDatabase(), null, alias, name != null ? name : c.getName());
session.getDatabase(), null, alias, name != null ? name : c.getName(), false);
expressions.add(index++, ec);
}
return index;
......
......@@ -401,6 +401,16 @@ public class Constants {
*/
public static final int SALT_LEN = 8;
/**
* The identity of INFORMATION_SCHEMA.
*/
public static final int INFORMATION_SCHEMA_ID = -1;
/**
* The identity of PUBLIC schema.
*/
public static final int MAIN_SCHEMA_ID = 0;
/**
* The name of the default schema.
*/
......
......@@ -82,7 +82,7 @@ import org.h2.util.TempFileDeleter;
import org.h2.util.Utils;
import org.h2.value.CaseInsensitiveMap;
import org.h2.value.CompareMode;
import org.h2.value.NullableKeyConcurrentMap;
import org.h2.value.CaseInsensitiveConcurrentMap;
import org.h2.value.Value;
import org.h2.value.ValueInt;
......@@ -660,7 +660,8 @@ public class Database implements DataHandler {
if (n == null || n.isEmpty()) {
n = "unnamed";
}
return dbSettings.databaseToUpper ? StringUtils.toUpperEnglish(n) : n;
return dbSettings.databaseToUpper ? StringUtils.toUpperEnglish(n)
: dbSettings.databaseToLower ? StringUtils.toLowerEnglish(n) : n;
}
private synchronized void open(int traceLevelFile, int traceLevelSystemOut, ConnectionInfo ci) {
......@@ -788,12 +789,14 @@ public class Database implements DataHandler {
store.getTransactionStore().init();
}
systemUser = new User(this, 0, SYSTEM_USER_NAME, true);
mainSchema = new Schema(this, 0, Constants.SCHEMA_MAIN, systemUser, true);
infoSchema = new Schema(this, -1, "INFORMATION_SCHEMA", systemUser, true);
mainSchema = new Schema(this, Constants.MAIN_SCHEMA_ID, sysIdentifier(Constants.SCHEMA_MAIN), systemUser,
true);
infoSchema = new Schema(this, Constants.INFORMATION_SCHEMA_ID, sysIdentifier("INFORMATION_SCHEMA"), systemUser,
true);
schemas.put(mainSchema.getName(), mainSchema);
schemas.put(infoSchema.getName(), infoSchema);
publicRole = new Role(this, 0, Constants.PUBLIC_ROLE_NAME, true);
roles.put(Constants.PUBLIC_ROLE_NAME, publicRole);
publicRole = new Role(this, 0, sysIdentifier(Constants.PUBLIC_ROLE_NAME), true);
roles.put(publicRole.getName(), publicRole);
systemUser.setAdmin(true);
systemSession = new Session(this, systemUser, ++nextSessionId);
lobSession = new Session(this, systemUser, ++nextSessionId);
......@@ -1687,6 +1690,15 @@ public class Database implements DataHandler {
return i;
}
/**
* Returns main schema (usually PUBLIC).
*
* @return main schema (usually PUBLIC)
*/
public Schema getMainSchema() {
return mainSchema;
}
public ArrayList<UserAggregate> getAllAggregates() {
return new ArrayList<>(aggregates.values());
}
......@@ -2760,8 +2772,8 @@ public class Database implements DataHandler {
continue;
}
// exclude the LOB_MAP that the Recover tool creates
if (table.getName().equals("LOB_BLOCKS") && table.getSchema()
.getName().equals("INFORMATION_SCHEMA")) {
if (table.getSchema().getId() == Constants.INFORMATION_SCHEMA_ID
&& table.getName().equalsIgnoreCase("LOB_BLOCKS")) {
continue;
}
return table;
......@@ -3041,9 +3053,7 @@ public class Database implements DataHandler {
* @return the hash map
*/
public <V> HashMap<String, V> newStringMap() {
return dbSettings.databaseToUpper ?
new HashMap<String, V>() :
new CaseInsensitiveMap<V>();
return dbSettings.caseInsensitiveIdentifiers ? new CaseInsensitiveMap<V>() : new HashMap<String, V>();
}
/**
......@@ -3054,7 +3064,8 @@ public class Database implements DataHandler {
* @return the hash map
*/
public <V> ConcurrentHashMap<String, V> newConcurrentStringMap() {
return new NullableKeyConcurrentMap<>(!dbSettings.databaseToUpper);
return dbSettings.caseInsensitiveIdentifiers ? new CaseInsensitiveConcurrentMap<V>()
: new ConcurrentHashMap<String, V>();
}
/**
......@@ -3066,7 +3077,33 @@ public class Database implements DataHandler {
* @return true if they match
*/
public boolean equalsIdentifiers(String a, String b) {
return a.equals(b) || (!dbSettings.databaseToUpper && a.equalsIgnoreCase(b));
return a.equals(b) || dbSettings.caseInsensitiveIdentifiers && a.equalsIgnoreCase(b);
}
/**
* Returns identifier in upper or lower case depending on database settings.
*
* @param upperName
* identifier in the upper case
* @return identifier in upper or lower case
*/
public String sysIdentifier(String upperName) {
assert isUpperSysIdentifier(upperName);
return dbSettings.databaseToLower ? StringUtils.toLowerEnglish(upperName) : upperName;
}
private static boolean isUpperSysIdentifier(String upperName) {
int l = upperName.length();
if (l == 0) {
return false;
}
for (int i = 0; i < l; i++) {
int ch = upperName.charAt(i);
if (ch < 'A' || ch > 'Z' && ch != '_') {
return false;
}
}
return true;
}
@Override
......
......@@ -6,6 +6,7 @@
package org.h2.engine;
import java.util.HashMap;
import org.h2.api.ErrorCode;
import org.h2.message.DbException;
import org.h2.util.Utils;
......@@ -55,15 +56,28 @@ public class DbSettings extends SettingsBase {
*/
public final int analyzeSample = get("ANALYZE_SAMPLE", 10_000);
/**
* Database setting <code>DATABASE_TO_LOWER</code> (default: false).<br />
* Database short names are converted to lowercase for the DATABASE()
* function, and in the CATALOG column of all database meta data methods.
* Setting this to "true" is experimental.
*/
public final boolean databaseToLower;
/**
* Database setting <code>DATABASE_TO_UPPER</code> (default: true).<br />
* Database short names are converted to uppercase for the DATABASE()
* function, and in the CATALOG column of all database meta data methods.
* Setting this to "false" is experimental. When set to false, all
* identifier names (table names, column names) are case sensitive (except
* aggregate, built-in functions, data types, and keywords).
*/
public final boolean databaseToUpper = get("DATABASE_TO_UPPER", true);
public final boolean databaseToUpper;
/**
* Database setting <code>CASE_INSENSITIVE_IDENTIFIERS</code> (default:
* false).<br />
* When set to true, all identifier names (table names, column names) are
* case insensitive. Setting this to "true" is experimental.
*/
public final boolean caseInsensitiveIdentifiers = get("CASE_INSENSITIVE_IDENTIFIERS", false);
/**
* Database setting <code>DB_CLOSE_ON_EXIT</code> (default: true).<br />
......@@ -284,12 +298,6 @@ public class DbSettings extends SettingsBase {
*/
public final boolean reuseSpace = get("REUSE_SPACE", true);
/**
* Database setting <code>ROWID</code> (default: true).<br />
* If set, each table has a pseudo-column _ROWID_.
*/
public final boolean rowId = get("ROWID", true);
/**
* Database setting <code>SHARE_LINKED_CONNECTIONS</code>
* (default: true).<br />
......@@ -337,6 +345,18 @@ public class DbSettings extends SettingsBase {
if (s.get("NESTED_JOINS") != null || Utils.getProperty("h2.nestedJoins", null) != null) {
throw DbException.getUnsupportedException("NESTED_JOINS setting is not available since 1.4.197");
}
boolean lower = get("DATABASE_TO_LOWER", false);
boolean upperSet = containsKey("DATABASE_TO_UPPER");
boolean upper = get("DATABASE_TO_UPPER", true);
if (lower && upper) {
if (upperSet) {
throw DbException.get(ErrorCode.UNSUPPORTED_SETTING_COMBINATION,
"DATABASE_TO_LOWER & DATABASE_TO_UPPER");
}
upper = false;
}
databaseToLower = lower;
databaseToUpper = upper;
}
/**
......
......@@ -205,8 +205,7 @@ public class FunctionAlias extends SchemaObjectBase {
@Override
public StringBuilder getSQL(StringBuilder builder) {
// TODO can remove this method once FUNCTIONS_IN_SCHEMA is enabled
if (database.getSettings().functionsInSchema ||
!getSchema().getName().equals(Constants.SCHEMA_MAIN)) {
if (database.getSettings().functionsInSchema || getSchema().getId() != Constants.MAIN_SCHEMA_ID) {
return super.getSQL(builder);
}
return Parser.quoteIdentifier(builder, getName());
......
......@@ -168,7 +168,6 @@ public class Session extends SessionWithState implements TransactionStore.Rollba
*/
private BitSet idsToRelease;
public Session(Database database, User user, int id) {
this.database = database;
this.queryTimeout = database.getSettings().maxQueryTimeout;
......@@ -176,7 +175,10 @@ public class Session extends SessionWithState implements TransactionStore.Rollba
this.user = user;
this.id = id;
this.lockTimeout = database.getLockTimeout();
this.currentSchemaName = Constants.SCHEMA_MAIN;
// PageStore creates a system session before initialization of the main schema
Schema mainSchema = database.getMainSchema();
this.currentSchemaName = mainSchema != null ? mainSchema.getName()
: database.sysIdentifier(Constants.SCHEMA_MAIN);
this.columnNamerConfiguration = ColumnNamerConfiguration.getDefault();
}
......
......@@ -36,6 +36,7 @@ public class ExpressionColumn extends Expression {
private final String schemaName;
private final String tableAlias;
private final String columnName;
private final boolean rowId;
private ColumnResolver columnResolver;
private int queryLevel;
private Column column;
......@@ -47,14 +48,16 @@ public class ExpressionColumn extends Expression {
this.schemaName = null;
this.tableAlias = null;
this.columnName = null;
this.rowId = column.isRowId();
}
public ExpressionColumn(Database database, String schemaName,
String tableAlias, String columnName) {
String tableAlias, String columnName, boolean rowId) {
this.database = database;
this.schemaName = schemaName;
this.tableAlias = tableAlias;
this.columnName = columnName;
this.rowId = rowId;
}
@Override
......@@ -71,6 +74,8 @@ public class ExpressionColumn extends Expression {
} else {
column.getSQL(builder);
}
} else if (rowId) {
builder.append(columnName);
} else {
Parser.quoteIdentifier(builder, columnName);
}
......@@ -91,6 +96,13 @@ public class ExpressionColumn extends Expression {
schemaName, resolver.getSchemaName())) {
return;
}
if (rowId) {
Column col = resolver.getRowIdColumn();
if (col != null) {
mapColumn(resolver, col, level);
}
return;
}
for (Column col : resolver.getColumns()) {
String n = resolver.getDerivedColumnName(col);
boolean derived;
......@@ -108,13 +120,6 @@ public class ExpressionColumn extends Expression {
return;
}
}
if (database.equalsIdentifiers(Column.ROWID, columnName)) {
Column col = resolver.getRowIdColumn();
if (col != null) {
mapColumn(resolver, col, level);
return;
}
}
Column[] columns = resolver.getSystemColumns();
for (int i = 0; columns != null && i < columns.length; i++) {
Column col = columns[i];
......
......@@ -84,7 +84,7 @@ public class JavaFunction extends Expression implements FunctionCall {
public StringBuilder getSQL(StringBuilder builder) {
// TODO always append the schema once FUNCTIONS_IN_SCHEMA is enabled
if (functionAlias.getDatabase().getSettings().functionsInSchema ||
!functionAlias.getSchema().getName().equals(Constants.SCHEMA_MAIN)) {
functionAlias.getSchema().getId() != Constants.MAIN_SCHEMA_ID) {
Parser.quoteIdentifier(builder, functionAlias.getSchema().getName()).append('.');
}
Parser.quoteIdentifier(builder, functionAlias.getName()).append('(');
......
......@@ -1551,7 +1551,7 @@ public class JdbcDatabaseMetaData extends TraceObject implements
* EXISTS, FALSE, FETCH, FOR, FOREIGN, FROM, FULL, GROUP, HAVING,
* IF, INNER, INTERSECT, INTERSECTS, INTERVAL, IS, JOIN, LIKE,
* LIMIT, LOCALTIME, LOCALTIMESTAMP, MINUS, NATURAL, NOT, NULL,
* OFFSET, ON, ORDER, PRIMARY, QUALIFY, ROW, ROWNUM, SELECT,
* OFFSET, ON, ORDER, PRIMARY, QUALIFY, ROW, _ROWID_, ROWNUM, SELECT,
* SYSDATE, SYSTIME, SYSTIMESTAMP, TABLE, TODAY, TOP, TRUE, UNION,
* UNIQUE, VALUES, WHERE, WINDOW, WITH
* </pre>
......@@ -1561,7 +1561,7 @@ public class JdbcDatabaseMetaData extends TraceObject implements
@Override
public String getSQLKeywords() {
debugCodeCall("getSQLKeywords");
return "IF,INTERSECTS,LIMIT,MINUS,OFFSET,QUALIFY,ROWNUM,SYSDATE,SYSTIME,SYSTIMESTAMP,TODAY,TOP";
return "IF,INTERSECTS,LIMIT,MINUS,OFFSET,QUALIFY,_ROWID_,ROWNUM,SYSDATE,SYSTIME,SYSTIMESTAMP,TODAY,TOP";
}
/**
......
......@@ -1640,7 +1640,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
* Add, replace or remove a key-value pair.
*
* @param key the key (may not be null)
* @param value new value, it may be null when removal is indended
* @param value new value, it may be null when removal is intended
* @param decisionMaker command object to make choices during transaction.
* @return previous value, if mapping for that key existed, or null otherwise
*/
......
......@@ -876,6 +876,7 @@ public class MVTable extends TableBase {
if (rowIdColumn == null) {
rowIdColumn = new Column(Column.ROWID, Value.LONG);
rowIdColumn.setTable(this, SearchRow.ROWID_INDEX);
rowIdColumn.setRowId(true);
}
return rowIdColumn;
}
......
......@@ -343,7 +343,6 @@ public class Schema extends DbObjectBase {
if (synonym != null) {
return synonym.getSynonymFor();
}
return null;
}
return table;
}
......
......@@ -84,6 +84,7 @@ public class Column {
private String comment;
private boolean primaryKey;
private boolean visible = true;
private boolean rowId;
private Domain domain;
/**
......@@ -288,11 +289,11 @@ public class Column {
}
public String getSQL() {
return Parser.quoteIdentifier(name);
return rowId ? name : Parser.quoteIdentifier(name);
}
public StringBuilder getSQL(StringBuilder builder) {
return Parser.quoteIdentifier(builder, name);
return rowId ? builder.append(name) : Parser.quoteIdentifier(builder, name);
}
public String getName() {
......@@ -323,6 +324,24 @@ public class Column {
this.domain = domain;
}
/**
* Returns whether this column is a row identity column.
*
* @return true for _ROWID_ column, false otherwise
*/
public boolean isRowId() {
return rowId;
}
/**
* Set row identity flag.
*
* @param rowId true _ROWID_ column, false otherwise
*/
public void setRowId(boolean rowId) {
this.rowId = rowId;
}
/**
* Validate the value, convert it if required, and update the sequence value
* if required. If the value is null, the default value (NULL if no default
......
......@@ -729,6 +729,7 @@ public class RegularTable extends TableBase {
if (rowIdColumn == null) {
rowIdColumn = new Column(Column.ROWID, Value.LONG);
rowIdColumn.setTable(this, -1);
rowIdColumn.setRowId(true);
}
return rowIdColumn;
}
......
......@@ -1053,11 +1053,8 @@ public class TableFilter implements ColumnResolver {
@Override
public Column getRowIdColumn() {
if (session.getDatabase().getSettings().rowId) {
return table.getRowIdColumn();
}
return null;
}
@Override
public Value getValue(Column column) {
......
......@@ -16,7 +16,6 @@ import org.h2.command.Prepared;
import org.h2.command.ddl.CreateTableData;
import org.h2.command.dml.AllColumnsForPlan;
import org.h2.command.dml.Query;
import org.h2.engine.Constants;
import org.h2.engine.Database;
import org.h2.engine.DbObject;
import org.h2.engine.Session;
......@@ -547,7 +546,7 @@ public class TableView extends Table {
*/
public static TableView createTempView(Session session, User owner,
String name, Query query, Query topQuery) {
Schema mainSchema = session.getDatabase().getSchema(Constants.SCHEMA_MAIN);
Schema mainSchema = session.getDatabase().getMainSchema();
String querySQL = query.getPlanSQL();
TableView v = new TableView(mainSchema, 0, name,
querySQL, query.getParameters(), null /* column templates */, session,
......
......@@ -227,10 +227,15 @@ public class ParserUtil {
*/
public static final int ROW = QUALIFY + 1;
/**
* The token "_ROWID_".
*/
public static final int _ROWID_ = ROW + 1;
/**
* The token "ROWNUM".
*/
public static final int ROWNUM = ROW + 1;
public static final int ROWNUM = _ROWID_ + 1;
/**
* The token "SELECT".
......@@ -352,10 +357,7 @@ public class ParserUtil {
*/
char c = s.charAt(start);
if (ignoreCase) {
/*
* Convert a-z to A-Z. This method is safe, because only A-Z
* characters are considered below.
*/
// Convert a-z to A-Z and 0x7f to _ (need special handling).
c &= 0xffdf;
}
switch (c) {
......@@ -537,6 +539,12 @@ public class ParserUtil {
return WITH;
}
return IDENTIFIER;
case '_':
// Cannot use eq() because 0x7f can be converted to '_' (0x5f)
if (end - start == 7 && "_ROWID_".regionMatches(ignoreCase, 0, s, start, 7)) {
return _ROWID_;
}
//$FALL-THROUGH$
default:
return IDENTIFIER;
}
......
......@@ -10,53 +10,32 @@ import java.util.concurrent.ConcurrentHashMap;
import org.h2.util.StringUtils;
/**
* A concurrent hash map with string keys that allows null keys.
* A concurrent hash map with case-insensitive string keys.
*
* @param <V> the value type
*/
public class NullableKeyConcurrentMap<V> extends ConcurrentHashMap<String, V> {
public class CaseInsensitiveConcurrentMap<V> extends ConcurrentHashMap<String, V> {
private static final long serialVersionUID = 1L;
private static final String NULL = new String();
private final boolean toUpper;
/**
* Create new instance of map.
*
* @param toUpper
* whether keys should be converted to upper case
*/
public NullableKeyConcurrentMap(boolean toUpper) {
this.toUpper = toUpper;
}
@Override
public V get(Object key) {
return super.get(toUpper(key));
return super.get(StringUtils.toUpperEnglish((String) key));
}
@Override
public V put(String key, V value) {
return super.put(toUpper(key), value);
return super.put(StringUtils.toUpperEnglish(key), value);
}
@Override
public boolean containsKey(Object key) {
return super.containsKey(toUpper(key));
return super.containsKey(StringUtils.toUpperEnglish((String) key));
}
@Override
public V remove(Object key) {
return super.remove(toUpper(key));
}
private String toUpper(Object key) {
if (key == null) {
return NULL;
}
String s = key.toString();
return toUpper ? StringUtils.toUpperEnglish(s) : s;
return super.remove(StringUtils.toUpperEnglish((String) key));
}
}
......@@ -19,26 +19,22 @@ public class CaseInsensitiveMap<V> extends HashMap<String, V> {
@Override
public V get(Object key) {
return super.get(toUpper(key));
return super.get(StringUtils.toUpperEnglish((String) key));
}
@Override
public V put(String key, V value) {
return super.put(toUpper(key), value);
return super.put(StringUtils.toUpperEnglish(key), value);
}
@Override
public boolean containsKey(Object key) {
return super.containsKey(toUpper(key));
return super.containsKey(StringUtils.toUpperEnglish((String) key));
}
@Override
public V remove(Object key) {
return super.remove(toUpper(key));
}
private static String toUpper(Object key) {
return key == null ? null : StringUtils.toUpperEnglish(key.toString());
return super.remove(StringUtils.toUpperEnglish((String) key));
}
}
......@@ -14,6 +14,7 @@ import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Locale;
import org.h2.api.ErrorCode;
import org.h2.test.TestBase;
import org.h2.test.TestDb;
......@@ -57,6 +58,7 @@ public class TestCompatibility extends TestDb {
testUnknownSet();
conn.close();
testIdentifiers();
deleteDb("compatibility");
testUnknownURL();
......@@ -71,7 +73,7 @@ public class TestCompatibility extends TestDb {
}
private void testCaseSensitiveIdentifiers() throws SQLException {
Connection c = getConnection("compatibility;DATABASE_TO_UPPER=FALSE");
Connection c = getConnection("compatibility;DATABASE_TO_UPPER=FALSE;CASE_INSENSITIVE_IDENTIFIERS=TRUE");
Statement stat = c.createStatement();
stat.execute("create table test(id int primary key, name varchar) " +
"as select 1, 'hello'");
......@@ -682,6 +684,62 @@ public class TestCompatibility extends TestDb {
assertThrows(ErrorCode.UNKNOWN_MODE_1, stat).execute("SET MODE Unknown");
}
private void testIdentifiers() throws SQLException {
deleteDb("compatibility");
testIdentifiers(false, false, false);
testIdentifiers(false, false, true);
testIdentifiers(true, false, false);
testIdentifiers(true, false, true);
testIdentifiers(false, true, false);
testIdentifiers(false, true, true);
}
private void testIdentifiers(boolean upper, boolean lower, boolean caseInsensitiveIdentifiers) throws SQLException
{
try (Connection conn = getConnection("compatibility;DATABASE_TO_UPPER=" + upper + ";DATABASE_TO_LOWER=" + lower
+ ";CASE_INSENSITIVE_IDENTIFIERS=" + caseInsensitiveIdentifiers)) {
Statement stat = conn.createStatement();
stat.execute("CREATE TABLE Test(Id INT) AS VALUES 2");
String schema = "PUBLIC", table = "Test", column = "Id";
if (upper) {
table = table.toUpperCase(Locale.ROOT);
column = column.toUpperCase(Locale.ROOT);
} else if (lower) {
schema = schema.toLowerCase(Locale.ROOT);
table = table.toLowerCase(Locale.ROOT);
column = column.toLowerCase(Locale.ROOT);
}
try (ResultSet rs = stat.executeQuery("SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME"
+ " FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME ILIKE 'Test'")) {
assertTrue(rs.next());
assertEquals(schema, rs.getString(1));
assertEquals(table, rs.getString(2));
assertEquals(column, rs.getString(3));
}
testIdentifiers(stat, "Test", "Id", true);
boolean ok = upper || lower || caseInsensitiveIdentifiers;
testIdentifiers(stat, "TEST", "ID", ok);
testIdentifiers(stat, "test", "id", ok);
testIdentifiers(stat, '"' + table + '"', '"' + column + '"', true);
testIdentifiers(stat, "\"TeSt\"", "\"iD\"", caseInsensitiveIdentifiers);
} finally {
deleteDb("compatibility");
}
}
private void testIdentifiers(Statement stat, String table, String column, boolean ok) throws SQLException {
String query = "SELECT _ROWID_, " + column + " FROM " + table;
if (ok) {
try (ResultSet rs = stat.executeQuery(query)) {
assertTrue(rs.next());
assertEquals(1L, rs.getLong(1));
assertEquals(2, rs.getInt(2));
}
} else {
assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, stat).executeQuery(query);
}
}
private void testUnknownURL() throws SQLException {
try {
getConnection("compatibility;MODE=Unknown").close();
......
......@@ -463,7 +463,7 @@ public class TestMetaData extends TestDb {
assertEquals("schema", meta.getSchemaTerm());
assertEquals("\\", meta.getSearchStringEscape());
assertEquals("IF,INTERSECTS,LIMIT,MINUS,OFFSET,QUALIFY,ROWNUM,SYSDATE,SYSTIME,SYSTIMESTAMP,TODAY,TOP",
assertEquals("IF,INTERSECTS,LIMIT,MINUS,OFFSET,QUALIFY,_ROWID_,ROWNUM,SYSDATE,SYSTIME,SYSTIMESTAMP,TODAY,TOP",
meta.getSQLKeywords());
assertTrue(meta.getURL().startsWith("jdbc:h2:"));
......
......@@ -28,3 +28,19 @@ TABLE TEST;
DROP TABLE TEST;
> ok
CREATE TABLE TEST(ID INT);
> ok
-- TODO Do we need _ROWID_ support here?
INSERT INTO TEST(_ROWID_, ID) VALUES (2, 3);
> update count: 1
SELECT _ROWID_, ID FROM TEST;
> _ROWID_ ID
> ------- --
> 2 3
> rows: 1
DROP TABLE TEST;
> ok
......@@ -640,3 +640,17 @@ SELECT * FROM
> ----- -
> 36 2
> rows: 1
CREATE TABLE TEST("_ROWID_" INT) AS VALUES 2;
> ok
SELECT _ROWID_ S1, TEST._ROWID_ S2, PUBLIC.TEST._ROWID_ S3, SCRIPT.PUBLIC.TEST._ROWID_ S4,
"_ROWID_" U1, TEST."_ROWID_" U2, PUBLIC.TEST."_ROWID_" U3, SCRIPT.PUBLIC.TEST."_ROWID_" U4
FROM TEST;
> S1 S2 S3 S4 U1 U2 U3 U4
> -- -- -- -- -- -- -- --
> 1 1 1 1 2 2 2 2
> rows: 1
DROP TABLE TEST;
> ok
......@@ -38,3 +38,28 @@ SELECT B FROM TEST;
DROP TABLE TEST;
> ok
CREATE TABLE TEST(ID INT) AS VALUES 100;
> ok
SELECT _ROWID_ FROM TEST;
>> 1
-- _ROWID_ modifications are ignored
UPDATE TEST SET _ROWID_ = 2 WHERE ID = 100;
> update count: 1
UPDATE TEST SET TEST._ROWID_ = 3 WHERE ID = 100;
> update count: 1
UPDATE TEST SET PUBLIC.TEST._ROWID_ = 4 WHERE ID = 100;
> update count: 1
UPDATE TEST SET SCRIPT.PUBLIC.TEST._ROWID_ = 5 WHERE ID = 100;
> update count: 1
SELECT _ROWID_ FROM TEST;
>> 1
DROP TABLE TEST;
> ok
......@@ -79,12 +79,10 @@ public class TestKeywords extends TestBase {
try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:keywords")) {
Statement stat = conn.createStatement();
for (String s : set) {
// _ROWID_ is a special virtual column
String column = s.equals("_ROWID_") ? "C" : s;
try {
stat.execute("CREATE TABLE " + s + '(' + column + " INT)");
stat.execute("INSERT INTO " + s + '(' + column + ") VALUES (10)");
try (ResultSet rs = stat.executeQuery("SELECT " + column + " FROM " + s)) {
stat.execute("CREATE TABLE " + s + '(' + s + " INT)");
stat.execute("INSERT INTO " + s + '(' + s + ") VALUES (10)");
try (ResultSet rs = stat.executeQuery("SELECT " + s + " FROM " + s)) {
assertTrue(rs.next());
assertEquals(10, rs.getInt(1));
assertFalse(rs.next());
......
......@@ -73,7 +73,7 @@ public class TestPgServer extends TestDb {
}
deleteDb("pgserver");
Connection conn = getConnection(
"mem:pgserver;DATABASE_TO_UPPER=false", "sa", "sa");
"mem:pgserver;DATABASE_TO_LOWER=true", "sa", "sa");
Statement stat = conn.createStatement();
stat.execute("create table test(id int, name varchar(255))");
Server server = createPgServer("-baseDir", getBaseDir(),
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论