/*
* Copyright 2007 - 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.jailer.datamodel;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sf.jailer.configuration.DBMS;
import net.sf.jailer.database.Session;
import net.sf.jailer.util.SqlUtil;
/**
* Column of a table.
*
* @author Ralf Wisser
*/
public class Column {
/**
* The name.
*/
public final String name;
/**
* The type.
*/
public final String type;
/**
* The length (for VARCHAR, DECIMAL, ...) or <code>0</code> if type-length is not variable.
*/
public final int length;
/**
* The precision (for DECIMAL, NUMBER ...) or <code>-1</code> if precision is not variable.
*/
public final int precision;
/**
* <code>true</code> if column is identity column.
*/
public boolean isIdentityColumn = false;
/**
* <code>true</code> if column is virtual.
*/
public boolean isVirtual = false;
/**
* <code>true</code> if column is nullable.
*/
public boolean isNullable = false;
/**
* SQL Expression for server-side column data filtering.
*/
private Filter filter = null;
/**
* Constructor.
*
* @param name the name (upper-case)
* @param type the type (in SQL)
* @param length the length (for VARCHAR, DECIMAL, ...) or <code>0</code> if type-length is not variable
* @param precision the precision (for DECIMAL, NUMBER ...) or <code>-1</code> if precision is not variable
*/
public Column(String name, String type, int length, int precision) {
this.name = name;
this.type = type;
this.length = length;
this.precision = precision;
}
/**
* Gets SQL expression for server-side column data filtering.
* {@link Filter#OLD_VALUE_PROP} is replaced by column name.
*
* @return SQL expression for server-side column data filtering
* or <code>null</code>, if no filter is defined for this column
*/
public String getFilterExpression() {
if (filter == null) {
return null;
}
String expr = filter.getExpression().replaceAll(Filter.OLD_VALUE_PROP_RE, Matcher.quoteReplacement("T." + name)).trim();
if (expr.startsWith(Filter.LITERAL_PREFIX)) {
expr = expr.substring(Filter.LITERAL_PREFIX.length()).trim();
}
return expr;
}
/**
* Gets filter for server-side column data filtering.
*
* @return filter for server-side column data filtering
* or <code>null</code>, if no filter is defined for this column
*/
public Filter getFilter() {
return filter;
}
/**
* Sets filter for server-side column data filtering.
*
* @param filterExpression SQL expression for server-side column data filtering
* or <code>null</code>, if no filter is defined for this column
*/
public void setFilter(Filter filter) {
this.filter = filter;
}
private static Pattern typeWithSizeAndPrecision = Pattern.compile("([^ ]+) +([^ \\(]+) *\\( *([0-9]+) *, *([0-9]+) *\\)");
private static Pattern typeWithSize = Pattern.compile("([^ ]+) +([^ \\(]+) *\\( *([0-9]+) *\\)");
private static Pattern typeWithoutSize = Pattern.compile("([^ ]+) +([^ \\(]+)");
/**
* Parses a column declaration in SQL syntax.
*
* @param columnDeclaration the column declaration in SQL syntax
* @return the column
*/
public static Column parse(String columnDeclaration) {
columnDeclaration = columnDeclaration.trim();
// work-around for bug 2849047
String normalizedcolumnDeclaration = columnDeclaration.replaceFirst("(\\(\\))? +[iI][dD][eE][nN][tT][iI][tT][yY]", "");
if (!normalizedcolumnDeclaration.equals(columnDeclaration)) {
columnDeclaration = normalizedcolumnDeclaration + " identity";
}
normalizedcolumnDeclaration = columnDeclaration.replaceFirst("(\\(\\))? +[vV][iI][rR][tT][uU][aA][lL]", "");
if (!normalizedcolumnDeclaration.equals(columnDeclaration)) {
columnDeclaration = normalizedcolumnDeclaration + " virtual";
}
normalizedcolumnDeclaration = columnDeclaration.replaceFirst("(\\(\\))? +[nN][uU][lL][lL]", "");
if (!normalizedcolumnDeclaration.equals(columnDeclaration)) {
columnDeclaration = normalizedcolumnDeclaration + " null";
}
boolean isNullable = false;
if (columnDeclaration.toLowerCase().endsWith(" null")) {
isNullable = true;
columnDeclaration = columnDeclaration.substring(0, columnDeclaration.length() - 5).trim();
}
boolean isVirtual = false;
if (columnDeclaration.toLowerCase().endsWith(" virtual")) {
isVirtual = true;
columnDeclaration = columnDeclaration.substring(0, columnDeclaration.length() - 8).trim();
}
boolean isIdent = false;
if (columnDeclaration.toLowerCase().endsWith(" identity")) {
isIdent = true;
columnDeclaration = columnDeclaration.substring(0, columnDeclaration.length() - 9).trim();
}
Character quote = null;
if (columnDeclaration.length() > 0 && SqlUtil.LETTERS_AND_DIGITS.indexOf(columnDeclaration.charAt(0)) < 0) {
quote = columnDeclaration.charAt(0);
if (columnDeclaration.substring(1).indexOf(quote) >= 0) {
StringBuilder sb = new StringBuilder();
sb.append(quote);
boolean inScope = true;
for (int i = 1; i < columnDeclaration.length(); ++i) {
char c = columnDeclaration.charAt(i);
if (c == quote) {
inScope = false;
}
if (inScope && c == ' ') {
c = '\n';
}
sb.append(c);
}
columnDeclaration = sb.toString();
}
}
String name, type;
int size = 0;
int precision = -1;
Matcher matcher = typeWithSizeAndPrecision.matcher(columnDeclaration);
if (matcher.matches()) {
name = matcher.group(1);
type = matcher.group(2);
size = Integer.parseInt(matcher.group(3));
precision = Integer.parseInt(matcher.group(4));
} else {
matcher = typeWithSize.matcher(columnDeclaration);
if (matcher.matches()) {
name = matcher.group(1);
type = matcher.group(2);
size = Integer.parseInt(matcher.group(3));
} else {
matcher = typeWithoutSize.matcher(columnDeclaration);
if (matcher.matches()) {
name = matcher.group(1);
type = matcher.group(2);
} else {
throw new RuntimeException("can't parse primary-key: " + columnDeclaration);
}
}
}
if (quote != null) {
name = name.replace('\n', ' ');
}
Column column = new Column(name, type, size, precision);
column.isNullable = isNullable;
column.isIdentityColumn = isIdent;
column.isVirtual = isVirtual;
return column;
}
/**
* Compares two columns.
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof Column) {
return name.equals(((Column) obj).name) &&
type.equals(((Column) obj).type) &&
length == ((Column) obj).length;
}
return super.equals(obj);
}
@Override
public int hashCode() {
return toSQL(null).hashCode();
}
/**
* Returns the primary key in SQL syntax.
*
* @param columnPrefix an optional prefix for each PK-column
* @param typeReplacement column types replacements
*/
public String toSQL(String columnPrefix, Map<String, String> typeReplacement) {
String theType = type;
if (typeReplacement != null && typeReplacement.containsKey(theType)) {
theType = typeReplacement.get(theType);
}
return (columnPrefix == null? "": columnPrefix) + name + " " + theType + (length == 0? "" :
"(" + length + (precision >= 0? ", " + precision : "") + ")");
}
/**
* Returns the column definition in SQL syntax.
*
* @param columnPrefix an optional prefix for the column name
*/
public String toSQL(String columnPrefix) {
return toSQL(columnPrefix, null);
}
/**
* Returns a string representation of the column.
*/
@Override
public String toString() {
return toSQL(null);
}
/**
* Returns <code>true</code> iff this column cannot be updated.
*
* @param session the session
* @return <code>true</code> iff this column cannot be updated
*/
public boolean isVirtualOrBlocked(Session session) {
return isVirtual || session.dbms.getExportBlocks().contains(type);
}
}