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