提交 12082887 authored 作者: Noel Grandin's avatar Noel Grandin 提交者: GitHub

Merge pull request #640 from stumc/Issue#628

Issue#628 NPE in CTE 
......@@ -143,6 +143,7 @@ import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.table.TableFilter.TableFilterVisitor;
import org.h2.table.TableView;
import org.h2.util.ColumnNamer;
import org.h2.util.MathUtils;
import org.h2.util.New;
import org.h2.util.StatementBuilder;
......@@ -5268,23 +5269,16 @@ public class Parser {
// array of length 1 to receive extra 'output' field in addition to
// return value
querySQLOutput[0] = StringUtils.cache(theQuery.getPlanSQL());
ColumnNamer columnNamer = new ColumnNamer(theQuery.getSession());
ArrayList<Expression> withExpressions = theQuery.getExpressions();
for (int i = 0; i < withExpressions.size(); ++i) {
Expression columnExp = withExpressions.get(i);
/*
* Use the passed in column name if supplied, otherwise use alias
* (if found) otherwise use column name derived from column
* expression.
*/
String columnName;
if (cols != null) {
columnName = cols[i];
} else if (columnExp.getAlias() != null) {
columnName = columnExp.getAlias();
} else {
columnName = columnExp.getColumnName();
}
columnTemplateList.add(new Column(columnName, columnExp.getType()));
// use the passed in column name if supplied, otherwise use alias (if found) otherwise use column name
// derived from column expression
String columnName = columnNamer.getColumnName(columnExp,i,cols);
columnTemplateList.add(new Column(columnName,
columnExp.getType()));
}
return columnTemplateList;
}
......@@ -5300,6 +5294,7 @@ public class Parser {
parameters, columnTemplateList.toArray(new Column[0]), session,
allowRecursiveQueryDetection, false);
if (!view.isRecursiveQueryDetected() && allowRecursiveQueryDetection) {
session.removeLocalTempTable(view);
view = new TableView(schema, id, tempViewName, querySQL, parameters,
columnTemplateList.toArray(new Column[0]), session,
false/* recursive */, false);
......
......@@ -23,6 +23,7 @@ import org.h2.schema.Sequence;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.Table;
import org.h2.util.ColumnNamer;
import org.h2.util.New;
import org.h2.value.DataType;
import org.h2.value.Value;
......@@ -230,10 +231,11 @@ public class CreateTable extends SchemaCommand {
private void generateColumnsFromQuery() {
int columnCount = asQuery.getColumnCount();
ArrayList<Expression> expressions = asQuery.getExpressions();
ColumnNamer columnNamer= new ColumnNamer(session);
for (int i = 0; i < columnCount; i++) {
Expression expr = expressions.get(i);
int type = expr.getType();
String name = expr.getAlias();
String name = columnNamer.getColumnName(expr,i,expr.getAlias());
long precision = expr.getPrecision();
int displaySize = expr.getDisplaySize();
DataType dt = DataType.getDataType(type);
......
......@@ -16,6 +16,7 @@ import org.h2.engine.Constants;
import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.engine.SysProperties;
import org.h2.expression.Alias;
import org.h2.expression.Comparison;
import org.h2.expression.ConditionAndOr;
import org.h2.expression.Expression;
......@@ -40,6 +41,7 @@ import org.h2.table.JoinBatch;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.table.TableView;
import org.h2.util.ColumnNamer;
import org.h2.util.New;
import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils;
......@@ -838,8 +840,15 @@ public class Select extends Query {
sort = prepareOrder(orderList, expressions.size());
orderList = null;
}
ColumnNamer columnNamer= new ColumnNamer(session);
for (int i = 0; i < expressions.size(); i++) {
Expression e = expressions.get(i);
String proposedColumnName = e.getAlias();
String columnName = columnNamer.getColumnName(e,i,proposedColumnName);
// if the name changed, create an alias
if(!columnName.equals(proposedColumnName)){
e = new Alias(e,columnName,true);
}
expressions.set(i, e.optimize(session));
}
if (condition != null) {
......
......@@ -11,6 +11,7 @@ import org.h2.expression.ExpressionColumn;
import org.h2.table.Column;
import org.h2.table.ColumnResolver;
import org.h2.table.TableFilter;
import org.h2.util.ColumnNamer;
import org.h2.value.Value;
/**
......@@ -35,9 +36,11 @@ public class SelectListColumnResolver implements ColumnResolver {
columns = new Column[columnCount];
expressions = new Expression[columnCount];
ArrayList<Expression> columnList = select.getExpressions();
ColumnNamer columnNamer= new ColumnNamer(select.getSession());
for (int i = 0; i < columnCount; i++) {
Expression expr = columnList.get(i);
Column column = new Column(expr.getAlias(), Value.NULL);
String columnName = columnNamer.getColumnName(expr, i, expr.getAlias());
Column column = new Column(columnName, Value.NULL);
column.setTable(null, i);
columns[i] = column;
expressions[i] = expr.getNonAliasExpression();
......
......@@ -26,6 +26,7 @@ import org.h2.table.Column;
import org.h2.table.ColumnResolver;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.util.ColumnNamer;
import org.h2.util.New;
import org.h2.util.StringUtils;
import org.h2.value.Value;
......@@ -329,6 +330,7 @@ public class SelectUnion extends Query {
expressions = New.arrayList();
ArrayList<Expression> le = left.getExpressions();
ArrayList<Expression> re = right.getExpressions();
ColumnNamer columnNamer= new ColumnNamer(session);
for (int i = 0; i < len; i++) {
Expression l = le.get(i);
Expression r = re.get(i);
......@@ -336,7 +338,8 @@ public class SelectUnion extends Query {
long prec = Math.max(l.getPrecision(), r.getPrecision());
int scale = Math.max(l.getScale(), r.getScale());
int displaySize = Math.max(l.getDisplaySize(), r.getDisplaySize());
Column col = new Column(l.getAlias(), type, prec, scale, displaySize);
String columnName = columnNamer.getColumnName(l,i,l.getAlias());
Column col = new Column(columnName, type, prec, scale, displaySize);
Expression e = new ExpressionColumn(session.getDatabase(), col);
expressions.add(e);
}
......
......@@ -341,6 +341,7 @@ public class Set extends Prepared {
if (database.getMode() != mode) {
session.getUser().checkAdmin();
database.setMode(mode);
session.getColumnNamerConfiguration().configure(mode.getEnum());
}
break;
case SetTypes.MULTI_THREADED: {
......@@ -534,6 +535,13 @@ public class Set extends Prepared {
database.setAllowBuiltinAliasOverride(value == 1);
break;
}
case SetTypes.COLUMN_NAME_RULES: {
session.getUser().checkAdmin();
session.getColumnNamerConfiguration().configure(expression.getColumnName());
break;
}
default:
DbException.throwInternalError("type="+type);
}
......
......@@ -247,6 +247,11 @@ public class SetTypes {
* The type of SET BUILTIN_ALIAS_OVERRIDE statement.
*/
public static final int BUILTIN_ALIAS_OVERRIDE = 47;
/**
* The type of a SET COLUMN_NAME_RULES statement.
*/
public static final int COLUMN_NAME_RULES = 48;
private static final ArrayList<String> TYPES = New.arrayList();
......@@ -304,6 +309,8 @@ public class SetTypes {
list.add(FORCE_JOIN_ORDER, "FORCE_JOIN_ORDER");
list.add(LAZY_QUERY_EXECUTION, "LAZY_QUERY_EXECUTION");
list.add(BUILTIN_ALIAS_OVERRIDE, "BUILTIN_ALIAS_OVERRIDE");
list.add(COLUMN_NAME_RULES, "COLUMN_NAME_RULES");
}
/**
......
......@@ -17,11 +17,15 @@ import org.h2.util.StringUtils;
* PostgreSQL, MySQL). Each mode has different settings.
*/
public class Mode {
public enum ModeEnum {
REGULAR, DB2, Derby, MSSQLServer, HSQLDB, MySQL, Oracle, PostgreSQL, Ignite,
}
/**
* The name of the default mode.
*/
static final String REGULAR = "REGULAR";
static final String REGULAR = ModeEnum.REGULAR.name();
private static final HashMap<String, Mode> MODES = New.hashMap();
......@@ -180,12 +184,14 @@ public class Mode {
private final String name;
private ModeEnum modeEnum;
static {
Mode mode = new Mode(REGULAR);
Mode mode = new Mode(ModeEnum.REGULAR.name());
mode.nullConcatIsNull = true;
add(mode);
mode = new Mode("DB2");
mode = new Mode(ModeEnum.DB2.name());
mode.aliasColumnName = true;
mode.supportOffsetFetch = true;
mode.sysDummy1 = true;
......@@ -200,7 +206,7 @@ public class Mode {
mode.allowDB2TimestampFormat = true;
add(mode);
mode = new Mode("Derby");
mode = new Mode(ModeEnum.Derby.name());
mode.aliasColumnName = true;
mode.uniqueIndexSingleNull = true;
mode.supportOffsetFetch = true;
......@@ -210,7 +216,7 @@ public class Mode {
mode.supportedClientInfoPropertiesRegEx = null;
add(mode);
mode = new Mode("HSQLDB");
mode = new Mode(ModeEnum.HSQLDB.name());
mode.aliasColumnName = true;
mode.convertOnlyToSmallerScale = true;
mode.nullConcatIsNull = true;
......@@ -223,7 +229,7 @@ public class Mode {
mode.supportedClientInfoPropertiesRegEx = null;
add(mode);
mode = new Mode("MSSQLServer");
mode = new Mode(ModeEnum.MSSQLServer.name());
mode.aliasColumnName = true;
mode.squareBracketQuotedNames = true;
mode.uniqueIndexSingleNull = true;
......@@ -235,7 +241,7 @@ public class Mode {
mode.supportedClientInfoPropertiesRegEx = null;
add(mode);
mode = new Mode("MySQL");
mode = new Mode(ModeEnum.MySQL.name());
mode.convertInsertNullToZero = true;
mode.indexDefinitionInCreateTable = true;
mode.lowerCaseIdentifiers = true;
......@@ -249,7 +255,7 @@ public class Mode {
mode.prohibitEmptyInPredicate = true;
add(mode);
mode = new Mode("Oracle");
mode = new Mode(ModeEnum.Oracle.name());
mode.aliasColumnName = true;
mode.convertOnlyToSmallerScale = true;
mode.uniqueIndexSingleNullExceptAllColumnsAreNull = true;
......@@ -262,7 +268,7 @@ public class Mode {
mode.prohibitEmptyInPredicate = true;
add(mode);
mode = new Mode("PostgreSQL");
mode = new Mode(ModeEnum.PostgreSQL.name());
mode.aliasColumnName = true;
mode.nullConcatIsNull = true;
mode.supportOffsetFetch = true;
......@@ -285,7 +291,7 @@ public class Mode {
mode.disallowedTypes = disallowedTypes;
add(mode);
mode = new Mode("Ignite");
mode = new Mode(ModeEnum.Ignite.name());
mode.nullConcatIsNull = true;
mode.allowAffinityKey = true;
mode.indexDefinitionInCreateTable = true;
......@@ -294,6 +300,7 @@ public class Mode {
private Mode(String name) {
this.name = name;
this.modeEnum = ModeEnum.valueOf(name);
}
private static void add(Mode mode) {
......@@ -311,15 +318,19 @@ public class Mode {
}
public static Mode getMySQL() {
return getInstance("MySQL");
return getInstance(ModeEnum.MySQL.name());
}
public static Mode getOracle() {
return getInstance("Oracle");
return getInstance(ModeEnum.Oracle.name());
}
public String getName() {
return name;
}
public ModeEnum getEnum() {
return this.modeEnum;
}
}
......@@ -42,6 +42,7 @@ import org.h2.table.SubQueryInfo;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.table.TableType;
import org.h2.util.ColumnNamerConfiguration;
import org.h2.util.New;
import org.h2.util.SmallLRUCache;
import org.h2.value.Value;
......@@ -126,6 +127,7 @@ public class Session extends SessionWithState {
private boolean joinBatchEnabled;
private boolean forceJoinOrder;
private boolean lazyQueryExecution;
private ColumnNamerConfiguration columnNamerConfiguration;
/**
* Tables marked for ANALYZE after the current transaction is committed.
* Prevents us calling ANALYZE repeatedly in large transactions.
......@@ -162,6 +164,7 @@ public class Session extends SessionWithState {
this.lockTimeout = setting == null ?
Constants.INITIAL_LOCK_TIMEOUT : setting.getIntValue();
this.currentSchemaName = Constants.SCHEMA_MAIN;
this.columnNamerConfiguration = ColumnNamerConfiguration.getDefault();
}
public void setLazyQueryExecution(boolean lazyQueryExecution) {
......@@ -373,6 +376,8 @@ public class Session extends SessionWithState {
* @param table the table
*/
public void removeLocalTempTable(Table table) {
// Exception thrown in org.h2.engine.Database.removeMeta if line below is missing with TestGeneralCommonTableQueries
database.lockMeta(this);
modificationId++;
localTempTables.remove(table.getName());
synchronized (database) {
......@@ -974,6 +979,8 @@ public class Session extends SessionWithState {
modificationId++;
table.setModified();
it.remove();
// Exception thrown in org.h2.engine.Database.removeMeta if line below is missing with TestDeadlock
database.lockMeta(this);
table.removeChildrenAndResources(this);
if (closeSession) {
// need to commit, otherwise recovery might
......@@ -1747,4 +1754,12 @@ public class Session extends SessionWithState {
}
}
public ColumnNamerConfiguration getColumnNamerConfiguration() {
return columnNamerConfiguration;
}
public void setColumnNamerConfiguration(ColumnNamerConfiguration columnNamerConfiguration) {
this.columnNamerConfiguration = columnNamerConfiguration;
}
}
......@@ -30,6 +30,7 @@ import org.h2.result.ResultInterface;
import org.h2.result.Row;
import org.h2.result.SortOrder;
import org.h2.schema.Schema;
import org.h2.util.ColumnNamer;
import org.h2.util.New;
import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils;
......@@ -162,6 +163,7 @@ public class TableView extends Table {
tables = New.arrayList(query.getTables());
ArrayList<Expression> expressions = query.getExpressions();
ArrayList<Column> list = New.arrayList();
ColumnNamer columnNamer= new ColumnNamer(session);
for (int i = 0, count = query.getColumnCount(); i < count; i++) {
Expression expr = expressions.get(i);
String name = null;
......@@ -173,6 +175,7 @@ public class TableView extends Table {
if (name == null) {
name = expr.getAlias();
}
name = columnNamer.getColumnName(expr,i,name);
if (type == Value.UNKNOWN) {
type = expr.getType();
}
......@@ -441,7 +444,7 @@ public class TableView extends Table {
@Override
public String getSQL() {
if (isTemporary()) {
if (isTemporary() && querySQL!=null) {
return "(\n" + StringUtils.indent(querySQL) + ")";
}
return super.getSQL();
......
package org.h2.util;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import org.h2.engine.Session;
import org.h2.expression.Expression;
public class ColumnNamer {
private static final String DEFAULT_COLUMN_NAME = "DEFAULT";
private ColumnNamerConfiguration configuration;
private Session session;
private Set<String> existingColumnNames = new HashSet<String>();
public ColumnNamer(Session session) {
this.session = session;
if(this.session!=null && this.session.getColumnNamerConfiguration()!=null){
// use original from session
this.configuration = this.session.getColumnNamerConfiguration();
}
else{
// detached namer, create new
this.configuration = ColumnNamerConfiguration.getDefault();
if(session!=null){
session.setColumnNamerConfiguration(this.configuration);
}
}
}
/**
* Create a standardized column name that isn't null and doesn't have a CR/LF in it.
* @param columnExp the column expression
* @param indexOfColumn index of column in below array
* @return
*/
public String getColumnName(Expression expr, int indexOfColumn) {
return getColumnName(expr,indexOfColumn,(String) null);
}
/**
* Create a standardized column name that isn't null and doesn't have a CR/LF in it.
* @param columnExp the column expression
* @param indexOfColumn index of column in below array
* @param columnNameOverides array of overriding column names
* @return the new column name
*/
public String getColumnName(Expression columnExp, int indexOfColumn, String[] columnNameOverides){
String columnNameOverride = null;
if (columnNameOverides != null && columnNameOverides.length > indexOfColumn){
columnNameOverride = columnNameOverides[indexOfColumn];
}
return getColumnName(columnExp, indexOfColumn, columnNameOverride);
}
/**
* Create a standardized column name that isn't null and doesn't have a CR/LF in it.
* @param columnExp the column expression
* @param indexOfColumn index of column in below array
* @param columnNameOverride single overriding column name
* @return the new column name
*/
public String getColumnName(Expression columnExp, int indexOfColumn, String columnNameOverride) {
// try a name from the column name override
String columnName = null;
if (columnNameOverride != null){
columnName = columnNameOverride;
if(!isAllowableColumnName(columnName)){
columnName = fixColumnName(columnName);
}
if(!isAllowableColumnName(columnName)){
columnName = null;
}
}
// try a name from the column alias
if (columnName==null && columnExp.getAlias()!=null && !DEFAULT_COLUMN_NAME.equals(columnExp.getAlias())){
columnName = columnExp.getAlias();
if(!isAllowableColumnName(columnName)){
columnName = fixColumnName(columnName);
}
if(!isAllowableColumnName(columnName)){
columnName = null;
}
}
// try a name derived from the column expression SQL
if (columnName==null && columnExp.getColumnName()!=null && !DEFAULT_COLUMN_NAME.equals(columnExp.getColumnName())){
columnName = columnExp.getColumnName();
if(!isAllowableColumnName(columnName)){
columnName = fixColumnName(columnName);
}
if(!isAllowableColumnName(columnName)){
columnName = null;
}
}
// try a name derived from the column expression plan SQL
if (columnName==null && columnExp.getSQL()!=null && !DEFAULT_COLUMN_NAME.equals(columnExp.getSQL())){
columnName = columnExp.getSQL();
if(!isAllowableColumnName(columnName)){
columnName = fixColumnName(columnName);
}
if(!isAllowableColumnName(columnName)){
columnName = null;
}
}
// go with a innocuous default name pattern
if (columnName==null){
columnName = configuration.getDefaultColumnNamePattern().replace("$$", ""+(indexOfColumn+1));
}
if(existingColumnNames.contains(columnName) && configuration.isGenerateUniqueColumnNames()){
columnName = generateUniqueName(columnName);
}
existingColumnNames.add(columnName);
return columnName;
}
private String generateUniqueName(String columnName) {
String newColumnName = columnName;
int loopCount = 2;
while(existingColumnNames.contains(newColumnName)){
String loopCountString = "_"+loopCount;
newColumnName = columnName.substring(0,Math.min(columnName.length(), configuration.getMaxIdentiferLength()-loopCountString.length()))+loopCountString;
loopCount++;
}
return newColumnName;
}
public boolean isAllowableColumnName(String proposedName){
// check null
if (proposedName == null){
return false;
}
// check size limits
if (proposedName.length() > configuration.getMaxIdentiferLength() || proposedName.length()==0){
return false;
}
Matcher match = configuration.getCompiledRegularExpressionMatchAllowed().matcher(proposedName);
if(!match.matches()){
return false;
}
return true;
}
private String fixColumnName(String proposedName) {
Matcher match = configuration.getCompiledRegularExpressionMatchDisallowed().matcher(proposedName);
proposedName = match.replaceAll("");
// check size limits - then truncate
if (proposedName.length() > configuration.getMaxIdentiferLength()){
proposedName=proposedName.substring(0, configuration.getMaxIdentiferLength());
}
return proposedName;
}
public ColumnNamerConfiguration getConfiguration() {
return configuration;
}
}
package org.h2.util;
import java.util.regex.Pattern;
import org.h2.engine.Mode.ModeEnum;
import static org.h2.engine.Mode.ModeEnum.*;
import org.h2.message.DbException;
public class ColumnNamerConfiguration {
private static final String DEFAULT_COMMAND = "DEFAULT";
private static final String REGULAR_EXPRESSION_MATCH_DISALLOWED = "REGULAR_EXPRESSION_MATCH_DISALLOWED = ";
private static final String REGULAR_EXPRESSION_MATCH_ALLOWED = "REGULAR_EXPRESSION_MATCH_ALLOWED = ";
private static final String DEFAULT_COLUMN_NAME_PATTERN = "DEFAULT_COLUMN_NAME_PATTERN = ";
private static final String MAX_IDENTIFIER_LENGTH = "MAX_IDENTIFIER_LENGTH = ";
private static final String EMULATE_COMMAND = "EMULATE = ";
private static final String GENERATE_UNIQUE_COLUMN_NAMES = "GENERATE_UNIQUE_COLUMN_NAMES = ";
private int maxIdentiferLength;
private String regularExpressionMatchAllowed;
private String regularExpressionMatchDisallowed;
private String defaultColumnNamePattern;
private boolean generateUniqueColumnNames;
private Pattern compiledRegularExpressionMatchAllowed;
private Pattern compiledRegularExpressionMatchDisallowed;
public ColumnNamerConfiguration(int maxIdentiferLength, String regularExpressionMatchAllowed,
String regularExpressionMatchDisallowed, String defaultColumnNamePattern, boolean generateUniqueColumnNames) {
this.maxIdentiferLength = maxIdentiferLength;
this.regularExpressionMatchAllowed = regularExpressionMatchAllowed;
this.regularExpressionMatchDisallowed = regularExpressionMatchDisallowed;
this.defaultColumnNamePattern = defaultColumnNamePattern;
this.generateUniqueColumnNames = generateUniqueColumnNames;
compiledRegularExpressionMatchAllowed = Pattern.compile(regularExpressionMatchAllowed);
compiledRegularExpressionMatchDisallowed = Pattern.compile(regularExpressionMatchDisallowed);
}
public int getMaxIdentiferLength() {
return maxIdentiferLength;
}
public void setMaxIdentiferLength(int maxIdentiferLength) {
this.maxIdentiferLength = Math.max(30,maxIdentiferLength);
if(maxIdentiferLength!=getMaxIdentiferLength()){
throw DbException.getInvalidValueException("Illegal value (<30) in SET COLUMN_NAME_RULES","MAX_IDENTIFIER_LENGTH="+maxIdentiferLength);
}
}
public String getRegularExpressionMatchAllowed() {
return regularExpressionMatchAllowed;
}
public void setRegularExpressionMatchAllowed(String regularExpressionMatchAllowed) {
this.regularExpressionMatchAllowed = regularExpressionMatchAllowed;
}
public String getRegularExpressionMatchDisallowed() {
return regularExpressionMatchDisallowed;
}
public void setRegularExpressionMatchDisallowed(String regularExpressionMatchDisallowed) {
this.regularExpressionMatchDisallowed = regularExpressionMatchDisallowed;
}
public String getDefaultColumnNamePattern() {
return defaultColumnNamePattern;
}
public void setDefaultColumnNamePattern(String defaultColumnNamePattern) {
this.defaultColumnNamePattern = defaultColumnNamePattern;
}
public Pattern getCompiledRegularExpressionMatchAllowed() {
return compiledRegularExpressionMatchAllowed;
}
public void setCompiledRegularExpressionMatchAllowed(Pattern compiledRegularExpressionMatchAllowed) {
this.compiledRegularExpressionMatchAllowed = compiledRegularExpressionMatchAllowed;
}
public Pattern getCompiledRegularExpressionMatchDisallowed() {
return compiledRegularExpressionMatchDisallowed;
}
public void setCompiledRegularExpressionMatchDisallowed(Pattern compiledRegularExpressionMatchDisallowed) {
this.compiledRegularExpressionMatchDisallowed = compiledRegularExpressionMatchDisallowed;
}
public void configure(String stringValue) {
try{
if(stringValue.equalsIgnoreCase(DEFAULT_COMMAND)){
configure(REGULAR);
} else if(stringValue.startsWith(EMULATE_COMMAND)){
configure(ModeEnum.valueOf(unquoteString(stringValue.substring(EMULATE_COMMAND.length()))));
} else if(stringValue.startsWith(MAX_IDENTIFIER_LENGTH)){
int maxLength = Integer.parseInt(stringValue.substring(MAX_IDENTIFIER_LENGTH.length()));
setMaxIdentiferLength(maxLength);
} else if(stringValue.startsWith(GENERATE_UNIQUE_COLUMN_NAMES)){
setGenerateUniqueColumnNames(Integer.parseInt(stringValue.substring(GENERATE_UNIQUE_COLUMN_NAMES.length()))==1);
} else if(stringValue.startsWith(DEFAULT_COLUMN_NAME_PATTERN)){
setDefaultColumnNamePattern(unquoteString(stringValue.substring(DEFAULT_COLUMN_NAME_PATTERN.length())));
} else if(stringValue.startsWith(REGULAR_EXPRESSION_MATCH_ALLOWED)){
setRegularExpressionMatchAllowed(unquoteString(stringValue.substring(REGULAR_EXPRESSION_MATCH_ALLOWED.length())));
} else if(stringValue.startsWith(REGULAR_EXPRESSION_MATCH_DISALLOWED)){
setRegularExpressionMatchDisallowed(unquoteString(stringValue.substring(REGULAR_EXPRESSION_MATCH_DISALLOWED.length())));
}
else
{
throw DbException.getInvalidValueException("SET COLUMN_NAME_RULES: unknown id:"+stringValue,
stringValue);
}
recompilePatterns();
}
//Including NumberFormatException|PatternSyntaxException
catch(RuntimeException e){
throw DbException.getInvalidValueException("SET COLUMN_NAME_RULES:"+e.getMessage(),
stringValue);
}
}
private void recompilePatterns() {
try{
// recompile RE patterns
setCompiledRegularExpressionMatchAllowed(Pattern.compile(getRegularExpressionMatchAllowed()));
setCompiledRegularExpressionMatchDisallowed(Pattern.compile(getRegularExpressionMatchDisallowed()));
}
catch(Exception e){
configure(REGULAR);
throw e;
}
}
public static ColumnNamerConfiguration getDefault(){
return new ColumnNamerConfiguration(Integer.MAX_VALUE, "(?m)(?s).+", "(?m)(?s)[\\x00]", "_UNNAMED_$$",false);
}
private static String unquoteString(String s){
if(s.startsWith("'") && s.endsWith("'")){
s = s.substring(1, s.length()-1);
return s;
}
return s;
}
public boolean isGenerateUniqueColumnNames() {
return generateUniqueColumnNames;
}
public void setGenerateUniqueColumnNames(boolean generateUniqueColumnNames) {
this.generateUniqueColumnNames = generateUniqueColumnNames;
}
public void configure(ModeEnum modeEnum) {
switch(modeEnum){
case Oracle:
// Nonquoted identifiers can contain only alphanumeric characters
// from your database character set and the underscore (_), dollar sign ($), and pound sign (#).
setMaxIdentiferLength(128);
setRegularExpressionMatchAllowed("(?m)(?s)\"?[A-Za-z0-9_\\$#]+\"?");
setRegularExpressionMatchDisallowed("(?m)(?s)[^A-Za-z0-9_\"\\$#]");
setDefaultColumnNamePattern("_UNNAMED_$$");
setGenerateUniqueColumnNames(false);
break;
case MSSQLServer:
// https://docs.microsoft.com/en-us/sql/sql-server/maximum-capacity-specifications-for-sql-server
setMaxIdentiferLength(128);
setRegularExpressionMatchAllowed("(?m)(?s)[A-Za-z0-9_\\[\\]]+");// allows [] around names
setRegularExpressionMatchDisallowed("(?m)(?s)[^A-Za-z0-9_\\[\\]]");
setDefaultColumnNamePattern("_UNNAMED_$$");
setGenerateUniqueColumnNames(false);
break;
case PostgreSQL:
setMaxIdentiferLength(63);// this default can be changed to 128 by postgres config
setRegularExpressionMatchAllowed("(?m)(?s)[A-Za-z0-9_\\$]+");
setRegularExpressionMatchDisallowed("(?m)(?s)[^A-Za-z0-9_\\$]");
setDefaultColumnNamePattern("_UNNAMED_$$");
setGenerateUniqueColumnNames(false);
break;
case MySQL:
//https://dev.mysql.com/doc/refman/5.7/en/identifiers.html
setMaxIdentiferLength(64);
setRegularExpressionMatchAllowed("(?m)(?s)`?[A-Za-z0-9_`\\$]+`?");
setRegularExpressionMatchDisallowed("(?m)(?s)[^A-Za-z0-9_`\\$]");
setDefaultColumnNamePattern("_UNNAMED_$$");
setGenerateUniqueColumnNames(false);
break;
default:
case REGULAR:
case DB2:
case Derby:
case HSQLDB:
case Ignite:
setMaxIdentiferLength(Integer.MAX_VALUE);
setRegularExpressionMatchAllowed("(?m)(?s).+");
setRegularExpressionMatchDisallowed("(?m)(?s)[\\x00]");
setDefaultColumnNamePattern("_UNNAMED_$$");
setGenerateUniqueColumnNames(false);
break;
}
recompilePatterns();
}
}
\ No newline at end of file
......@@ -230,6 +230,7 @@ import org.h2.test.utils.SelfDestructor;
import org.h2.tools.DeleteDbFiles;
import org.h2.tools.Server;
import org.h2.util.AbbaLockingDetector;
import org.h2.util.TestColumnNamer;
import org.h2.util.New;
import org.h2.util.Profiler;
import org.h2.util.StringUtils;
......@@ -782,6 +783,8 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
addTest(new TestViewDropView());
addTest(new TestReplace());
addTest(new TestSynonymForTable());
addTest(new TestColumnNamer());
// jaqu
addTest(new AliasMapTest());
......
......@@ -9,7 +9,6 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import org.h2.jdbc.JdbcSQLException;
import org.h2.test.TestBase;
......@@ -42,6 +41,7 @@ public class TestGeneralCommonTableQueries extends TestBase {
testMerge();
testCreateTable();
testNestedSQL();
testRecursiveTable();
}
private void testSimpleSelect() throws Exception {
......@@ -469,4 +469,80 @@ public class TestGeneralCommonTableQueries extends TestBase {
conn.close();
deleteDb("commonTableExpressionQueries");
}
private void testRecursiveTable() throws Exception {
String[] expectedRowData =new String[]{"|meat|null","|fruit|3","|veg|2"};
String[] expectedColumnNames =new String[]{"VAL",
"SUM(SELECT\n X\nFROM PUBLIC.\"\" BB\n /* SELECT\n SUM(1) AS X,\n A\n FROM PUBLIC.B\n /++ PUBLIC.B.tableScan ++/\n /++ WHERE A IS ?1\n ++/\n /++ scanCount: 4 ++/\n INNER JOIN PUBLIC.C\n /++ PUBLIC.C.tableScan ++/\n ON 1=1\n WHERE (A IS ?1)\n AND (B.VAL = C.B)\n GROUP BY A: A IS A.VAL\n */\n /* scanCount: 1 */\nWHERE BB.A IS A.VAL)"};
deleteDb("commonTableExpressionQueries");
Connection conn = getConnection("commonTableExpressionQueries");
PreparedStatement prep;
ResultSet rs;
String SETUP_SQL =
"DROP TABLE IF EXISTS A; "
+"DROP TABLE IF EXISTS B; "
+"DROP TABLE IF EXISTS C; "
+"CREATE TABLE A(VAL VARCHAR(255)); "
+"CREATE TABLE B(A VARCHAR(255), VAL VARCHAR(255)); "
+"CREATE TABLE C(B VARCHAR(255), VAL VARCHAR(255)); "
+" "
+"INSERT INTO A VALUES('fruit'); "
+"INSERT INTO B VALUES('fruit','apple'); "
+"INSERT INTO B VALUES('fruit','banana'); "
+"INSERT INTO C VALUES('apple', 'golden delicious');"
+"INSERT INTO C VALUES('apple', 'granny smith'); "
+"INSERT INTO C VALUES('apple', 'pippin'); "
+"INSERT INTO A VALUES('veg'); "
+"INSERT INTO B VALUES('veg', 'carrot'); "
+"INSERT INTO C VALUES('carrot', 'nantes'); "
+"INSERT INTO C VALUES('carrot', 'imperator'); "
+"INSERT INTO C VALUES(null, 'banapple'); "
+"INSERT INTO A VALUES('meat'); "
;
String WITH_QUERY =
"WITH BB as (SELECT \n"
+"sum(1) as X, \n"
+"a \n"
+"FROM B \n"
+"JOIN C ON B.val=C.b \n"
+"GROUP BY a) \n"
+"SELECT \n"
+"A.val, \n"
+"sum(SELECT X FROM BB WHERE BB.a IS A.val)\n"//AS SUM_X
+"FROM A \n"
+"GROUP BY A.val";
for(int queryRunTries=1;queryRunTries<4;queryRunTries++){
Statement stat = conn.createStatement();
stat.execute(SETUP_SQL);
stat.close();
prep = conn.prepareStatement(WITH_QUERY);
rs = prep.executeQuery();
for(int columnIndex = 1; columnIndex <= rs.getMetaData().getColumnCount(); columnIndex++){
// previously the column label was null or had \n or \r in the string
assertTrue(rs.getMetaData().getColumnLabel(columnIndex)!=null);
assertEquals(expectedColumnNames[columnIndex-1],rs.getMetaData().getColumnLabel(columnIndex));
}
int rowNdx=0;
while (rs.next()) {
StringBuffer buf = new StringBuffer();
for(int columnIndex = 1; columnIndex <= rs.getMetaData().getColumnCount(); columnIndex++){
buf.append("|"+rs.getString(columnIndex));
}
assertEquals(expectedRowData[rowNdx], buf.toString());
rowNdx++;
}
assertEquals(3,rowNdx);
rs.close();
prep.close();
}
conn.close();
deleteDb("commonTableExpressionQueries");
}
}
\ No newline at end of file
......@@ -11,6 +11,8 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.PrintStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
......@@ -72,7 +74,7 @@ public class TestScript extends TestBase {
}
@Override
public void test() throws Exception {
public void test() throws Exception {
if (config.networked && config.big) {
return;
}
......
......@@ -2,3 +2,81 @@
-- and the EPL 1.0 (http://h2database.com/html/license.html).
-- Initial Developer: H2 Group
--
-- Try a custom column naming rules setup
SET COLUMN_NAME_RULES=MAX_IDENTIFIER_LENGTH = 30;
> ok
SET COLUMN_NAME_RULES=REGULAR_EXPRESSION_MATCH_ALLOWED = '[A-Za-z0-9_]+';
> ok
SET COLUMN_NAME_RULES=REGULAR_EXPRESSION_MATCH_DISALLOWED = '[^A-Za-z0-9_]+';
> ok
SET COLUMN_NAME_RULES=DEFAULT_COLUMN_NAME_PATTERN = 'noName$$';
> ok
SET COLUMN_NAME_RULES=GENERATE_UNIQUE_COLUMN_NAMES = 1;
> ok
SELECT 1 AS VERY_VERY_VERY_LONG_ID_VERY_VERY_VERY_LONG_ID, SUM(X)+1 AS _123456789012345, SUM(X)+1 , SUM(X)+1
+47, 'x' , '!!!' , '!!!!' FROM SYSTEM_RANGE(1,2);
> VERY_VERY_VERY_LONG_ID_VERY_VE _123456789012345 SUMX1 SUMX147 x noName6 noName7
> ------------------------------ ---------------- ----- ------- - ------- -------
> 1 4 4 51 x !!! !!!!
SET COLUMN_NAME_RULES=EMULATE='Oracle';
> ok
SELECT 1 AS VERY_VERY_VERY_LONG_ID, SUM(X)+1 AS _123456789012345, SUM(X)+1 , SUM(X)+1
+47, 'x' , '!!!' , '!!!!' FROM SYSTEM_RANGE(1,2);
> VERY_VERY_VERY_LONG_ID _123456789012345 SUMX1 SUMX147 x _UNNAMED_6 _UNNAMED_7
> ---------------------- ---------------- ----- ------- - ---------- ----------
> 1 4 4 51 x !!! !!!!
SET COLUMN_NAME_RULES=EMULATE='Oracle';
> ok
SELECT 1 AS VERY_VERY_VERY_LONG_ID, SUM(X)+1 AS _123456789012345, SUM(X)+1 , SUM(X)+1
+47, 'x' , '!!!' , '!!!!', 'Very Long' AS _23456789012345678901234567890XXX FROM SYSTEM_RANGE(1,2);
> VERY_VERY_VERY_LONG_ID _123456789012345 SUMX1 SUMX147 x _UNNAMED_6 _UNNAMED_7 _23456789012345678901234567890XXX
> ---------------------- ---------------- ----- ------- - ---------- ---------- ---------------------------------
> 1 4 4 51 x !!! !!!! Very Long
SET COLUMN_NAME_RULES=EMULATE='PostgreSQL';
> ok
SELECT 1 AS VERY_VERY_VERY_LONG_ID, SUM(X)+1 AS _123456789012345, SUM(X)+1 , SUM(X)+1
+47, 'x' , '!!!' , '!!!!', 999 AS "QuotedColumnId" FROM SYSTEM_RANGE(1,2);
> VERY_VERY_VERY_LONG_ID _123456789012345 SUMX1 SUMX147 x _UNNAMED_6 _UNNAMED_7 QuotedColumnId
> ---------------------- ---------------- ----- ------- - ---------- ---------- --------------
> 1 4 4 51 x !!! !!!! 999
SET COLUMN_NAME_RULES=DEFAULT;
> ok
-- Test all MODES of database:
-- DB2, Derby, MSSQLServer, HSQLDB, MySQL, Oracle, PostgreSQL, Ignite
SET COLUMN_NAME_RULES=EMULATE='DB2';
> ok
SET COLUMN_NAME_RULES=EMULATE='Derby';
> ok
SET COLUMN_NAME_RULES=EMULATE='MSSQLServer';
> ok
SET COLUMN_NAME_RULES=EMULATE='MySQL';
> ok
SET COLUMN_NAME_RULES=EMULATE='Oracle';
> ok
SET COLUMN_NAME_RULES=EMULATE='PostgreSQL';
> ok
SET COLUMN_NAME_RULES=EMULATE='Ignite';
> ok
SET COLUMN_NAME_RULES=EMULATE='REGULAR';
> ok
package org.h2.util;
import org.h2.expression.Expression;
import org.h2.expression.ValueExpression;
import org.h2.test.TestBase;
public class TestColumnNamer extends TestBase {
private String[] ids = new String[]{
"ABC"
,"123"
,"a\n2"
,"a$c%d#e@f!."
,null
,"VERYVERYVERYVERYVERYVERYLONGVERYVERYVERYVERYVERYVERYLONGVERYVERYVERYVERYVERYVERYLONG"
,"'!!!'"
,"'!!!!'"
,"3.1415"
,"\r"
,"col1"
,"col1"
,"col1"
,"col2col2col2col2col2col2col2col2col2col2col2col2col2col2col2col2col2col2col2col2col2col2col2col2"
,"col2col2col2col2col2col2col2col2col2col2col2col2col2col2col2col2col2col2col2col2col2col2col2col2"
};
private String[] expectedColumnName= {"ABC"
,"123"
,"a2"
,"acdef"
,"colName6"
,"VERYVERYVERYVERYVERYVERYLONGVE"
,"colName8"
,"colName9"
,"31415"
,"colName11"
,"col1"
,"col1_2"
,"col1_3"
,"col2col2col2col2col2col2col2co"
,"col2col2col2col2col2col2col2_2"};
public static void main(String[] args){
new TestColumnNamer().test();
}
public void test() {
ColumnNamer columnNamer = new ColumnNamer(null);
columnNamer.getConfiguration().configure("MAX_IDENTIFIER_LENGTH = 30");
columnNamer.getConfiguration().configure("REGULAR_EXPRESSION_MATCH_ALLOWED = '[A-Za-z0-9_]+'");
columnNamer.getConfiguration().configure("REGULAR_EXPRESSION_MATCH_DISALLOWED = '[^A-Za-z0-9_]+'");
columnNamer.getConfiguration().configure("DEFAULT_COLUMN_NAME_PATTERN = 'colName$$'");
columnNamer.getConfiguration().configure("GENERATE_UNIQUE_COLUMN_NAMES = 1");
int index =0;
for(String id : ids){
Expression columnExp = ValueExpression.getDefault();
String newColumnName = columnNamer.getColumnName(columnExp , index+1, id);
assertTrue(newColumnName!=null);
assertTrue(newColumnName.length()<=30);
assertTrue(newColumnName.length()>=1);
assertEquals(newColumnName,expectedColumnName[index]);
index++;
}
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论