/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.persistence;
import java.io.Serializable;
import java.math.BigDecimal;
import java.rmi.RemoteException;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.DecimalFormat;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import com.servoy.base.persistence.BaseColumn;
import com.servoy.j2db.IServiceProvider;
import com.servoy.j2db.J2DBGlobals;
import com.servoy.j2db.Messages;
import com.servoy.j2db.dataprocessing.IDataServer;
import com.servoy.j2db.dataprocessing.ValueFactory.DbIdentValue;
import com.servoy.j2db.dataprocessing.ValueFactory.NullValue;
import com.servoy.j2db.query.ColumnType;
import com.servoy.j2db.util.AliasKeyMap.ISupportAlias;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.TimezoneUtils;
import com.servoy.j2db.util.UUID;
import com.servoy.j2db.util.Utils;
import com.servoy.j2db.util.keyword.Ident;
import com.servoy.j2db.util.keyword.SQLKeywords;
/**
* A database column , this information is not stored inside the repository but recreated each time<br>
* Only the ColumnInfo is stored in the database
*
* @author jblok
*/
public class Column extends BaseColumn implements Serializable, IColumn, ISupportHTMLToolTipText, ISupportAlias<String>
{
public static final long serialVersionUID = -2730015162348120893L;
public static final int MAX_SQL_OBJECT_NAME_LENGTH = 30; // max length of table names, column names, etc; 30 seen by oracle, 31 seen by firebird
/*
* _____________________________________________________________ Declaration of attributes
*/
public static final int[] allDefinedTypes = new int[] { TEXT, INTEGER, NUMBER, DATETIME, MEDIA };
// column flags
public static final int NORMAL_COLUMN = 0;
public static final int PK_COLUMN = 1;
public static final int USER_ROWID_COLUMN = 2;
public static final int UUID_COLUMN = 4;
public static final int EXCLUDED_COLUMN = 8;
public static final int IDENT_COLUMNS = PK_COLUMN + USER_ROWID_COLUMN;
public static final int NON_IDENT_COLUMNS = ~IDENT_COLUMNS;
public static final int[] allDefinedRowIdents = new int[] { NORMAL_COLUMN, PK_COLUMN, USER_ROWID_COLUMN };
public static final int[] allDefinedOtherFlags = new int[] { UUID_COLUMN, EXCLUDED_COLUMN };
private final Table table;
private String plainSQLName;
private ColumnType columnType; // as returned by current database, columnInfo holds column type as configured by developer
private boolean existInDB;
private boolean dbPK = false; // please only use this if column exists in database (use existInDB to find out)
private String databaseDefaultValue = null;
private boolean allowNull = true;
private ColumnInfo columnInfo;
/*
* _____________________________________________________________ Declaration and definition of constructors
*/
public Column(Table db, String theSQLName, int type, int length, int scale, boolean existInDB)
{
table = db;
this.plainSQLName = theSQLName;
this.existInDB = existInDB;
updateColumnType(type, length, scale);
}
public String toHTML()
{
StringBuilder sb = new StringBuilder();
sb.append("<html>"); //$NON-NLS-1$
sb.append("<b>"); //$NON-NLS-1$
sb.append(getSQLName());
sb.append("</b> "); //$NON-NLS-1$
sb.append(getDisplayTypeString(mapToDefaultType(getType())));
if (getLength() > 0)
{
sb.append(" length: "); //$NON-NLS-1$
sb.append(getLength());
}
if (!getAllowNull())
{
sb.append("<br>"); //$NON-NLS-1$
sb.append("<font color=\"red\">NOT NULL</font>"); //$NON-NLS-1$
}
sb.append("</html>"); //$NON-NLS-1$
return sb.toString();
}
public String getTextualPropertyInfo()
{
if (columnInfo != null)
{
return columnInfo.getTextualPropertyInfo(false);
}
return null;
}
public static String getDisplayTypeString(int atype)
{
switch (mapToDefaultType(atype))
{
case DATETIME :
return "DATETIME"; //$NON-NLS-1$
case TEXT :
return "TEXT"; //$NON-NLS-1$
case NUMBER :
return "NUMBER"; //$NON-NLS-1$
case INTEGER :
return "INTEGER"; //$NON-NLS-1$
case MEDIA :
return "MEDIA"; //$NON-NLS-1$
default :
return "UNKNOWN TYPE#" + atype; //$NON-NLS-1$
}
}
public static int mapToDefaultType(int atype)
{
switch (atype)
{
case Types.DATE :
case Types.TIME :
case Types.TIMESTAMP :
case 11 ://date?? fix for 'odbc-bridge' and 'inet driver'
return DATETIME;
case Types.CHAR :
case Types.NCHAR :
case Types.VARCHAR :
case Types.LONGVARCHAR :
case Types.LONGNVARCHAR :
case Types.CLOB :
case Types.NCLOB :
case Types.ROWID : //nchar fix for 'odbc-bridge' and 'inet driver'
case Types.NVARCHAR : //nvarchar fix for 'odbc-bridge' and 'inet driver'
case -10 ://ntext fix for 'odbc-bridge' and 'inet driver'
case -11 ://UID text fix M$ driver -sql server
case Types.JAVA_OBJECT : //postgres uuid
return TEXT;
case Types.FLOAT :
case Types.DOUBLE :
case Types.DECIMAL :
case Types.REAL :
case Types.NUMERIC :
return NUMBER;
case Types.TINYINT :
case Types.SMALLINT :
case Types.INTEGER :
case Types.BIGINT :
case Types.BIT :
case Types.BOOLEAN :
return INTEGER;
case Types.VARBINARY :
case Types.BINARY :
case Types.LONGVARBINARY :
case Types.BLOB :
case Types.SQLXML :
case Types.NULL :
return MEDIA;
case Types.OTHER :
default :
return atype;
}
}
public static Object getAsRightType(int type, int flags, Object obj, String format, int l, TimeZone timeZone, boolean throwOnFail)
{
if (obj == null) return null;
if (obj instanceof DbIdentValue || obj instanceof NullValue) return obj;
if (format == null) return getAsRightType(type, flags, obj, l, throwOnFail);//can't do anything else
try
{
if (obj instanceof String)
{
String str = ((String)obj).trim();
ParsePosition pos = new ParsePosition(0);
switch (mapToDefaultType(type))
{
case DATETIME :
SimpleDateFormat dformatter = new SimpleDateFormat(format);
if (timeZone != null)
{
dformatter.setTimeZone(timeZone);
}
Date date = dformatter.parse(str, pos);
return getAsRightType(type, flags, date, l, throwOnFail);
case NUMBER :
DecimalFormat nformatter = new DecimalFormat(format);
{
String pos_prefix = nformatter.getPositivePrefix();
if (pos_prefix == null) pos_prefix = ""; //$NON-NLS-1$
String neg_prefix = nformatter.getNegativePrefix();
if (neg_prefix == null) neg_prefix = "-"; //$NON-NLS-1$
if (!str.startsWith(pos_prefix) && !str.startsWith(neg_prefix))
{
nformatter.setPositivePrefix(""); //$NON-NLS-1$
nformatter.setNegativePrefix("-"); //$NON-NLS-1$
}
}
{
String pos_suffix = nformatter.getPositiveSuffix();
if (pos_suffix == null) pos_suffix = ""; //$NON-NLS-1$
String neg_suffix = nformatter.getNegativeSuffix();
if (neg_suffix == null) neg_suffix = ""; //$NON-NLS-1$
if (!str.endsWith(pos_suffix) && !str.endsWith(neg_suffix))
{
nformatter.setPositiveSuffix(""); //$NON-NLS-1$
nformatter.setNegativeSuffix(""); //$NON-NLS-1$
}
}
return getAsRightType(type, flags, nformatter.parse(str, pos), l, throwOnFail);
case INTEGER :
DecimalFormat iformatter = new DecimalFormat(format);
{
String pos_prefix = iformatter.getPositivePrefix();
if (pos_prefix == null) pos_prefix = ""; //$NON-NLS-1$
String neg_prefix = iformatter.getNegativePrefix();
if (neg_prefix == null) neg_prefix = "-"; //$NON-NLS-1$
if (!str.startsWith(pos_prefix) && !str.startsWith(neg_prefix))
{
iformatter.setPositivePrefix(""); //$NON-NLS-1$
iformatter.setNegativePrefix("-"); //$NON-NLS-1$
}
}
{
String pos_suffix = iformatter.getPositiveSuffix();
if (pos_suffix == null) pos_suffix = ""; //$NON-NLS-1$
String neg_suffix = iformatter.getNegativeSuffix();
if (neg_suffix == null) neg_suffix = ""; //$NON-NLS-1$
if (!str.endsWith(pos_suffix) && !str.endsWith(neg_suffix))
{
iformatter.setPositiveSuffix(""); //$NON-NLS-1$
iformatter.setNegativeSuffix(""); //$NON-NLS-1$
}
}
return getAsRightType(type, flags, iformatter.parse(str, pos), l, throwOnFail);
case TEXT :
if (l > 0 && str.length() >= l)
{
obj = str.substring(0, l);
}
return obj;
case MEDIA :
if (obj instanceof byte[])
{
return obj;
}
if (throwOnFail)
{
throw new RuntimeException(Messages.getString("servoy.conversion.error.media", new Object[] { obj })); //$NON-NLS-1$
}
return null;
default :
return obj.toString();
}
}
else
{
switch (mapToDefaultType(type))
{
case DATETIME :
if (obj instanceof Date)
{
return getAsRightType(type, flags, obj, l, throwOnFail);
}
if (obj instanceof Number)
{
return getAsRightType(type, flags, new Date(((Number)obj).longValue()), l, throwOnFail);
}
return getAsRightType(type, flags, obj.toString(), format, l, timeZone, throwOnFail);
case NUMBER :
if (obj instanceof Number)
{
return obj;
}
return getAsRightType(type, flags, obj.toString(), format, l, timeZone, throwOnFail);
case INTEGER :
if (obj instanceof Number)
{
if (obj instanceof Integer || obj instanceof Long)
{
return obj;
}
return new Long(((Number)obj).longValue());
}
return getAsRightType(type, flags, obj.toString(), format, l, timeZone, throwOnFail);
case TEXT :
String str = obj.toString();
if (l > 0 && str.length() >= l)
{
str = str.substring(0, l);
}
return str;
case MEDIA :
if (obj instanceof byte[])
{
return obj;
}
if (throwOnFail)
{
throw new RuntimeException(Messages.getString("servoy.conversion.error.media", new Object[] { obj })); //$NON-NLS-1$
}
return null;
default :
return obj.toString();
}
}
}
catch (RuntimeException e)
{
if (throwOnFail) throw e;
Debug.log(e);
}
return null;
}
public static Object getAsRightType(int type, int flags, Object obj, int l, boolean throwOnFail)
{
if (obj == null) return null;
if (obj instanceof DbIdentValue || obj instanceof NullValue) return obj;
if ((flags & UUID_COLUMN) != 0 || obj instanceof UUID)
{
UUID uuid = Utils.getAsUUID(obj, throwOnFail);
if (uuid == null)
{
return null;
}
switch (mapToDefaultType(type))
{
case TEXT :
return uuid.toString();
case MEDIA :
return uuid.toBytes();
}
}
try
{
switch (type)
{
case Types.NULL : //Type.NULL == 0 means untyped, just return the object as it is
return obj;
case Types.DATE :
if (obj instanceof java.util.Date)
{
return new java.sql.Date(((java.util.Date)obj).getTime());
}
if (obj instanceof Number)
{
return new java.sql.Date(((Number)obj).longValue());
}
if (throwOnFail)
{
throw new RuntimeException(Messages.getString("servoy.conversion.error.date", new Object[] { obj })); //$NON-NLS-1$
}
return null;
case Types.TIME :
if (obj instanceof java.util.Date)
{
return new java.sql.Time(((java.util.Date)obj).getTime());
}
if (throwOnFail)
{
throw new RuntimeException(Messages.getString("servoy.conversion.error.date", new Object[] { obj })); //$NON-NLS-1$
}
return null;
case Types.TIMESTAMP :
case 11 : //date?? fix for 'odbc-bridge' and 'inet driver'
if (obj instanceof org.mozilla.javascript.NativeDate)
{
return new Timestamp(((java.util.Date)((org.mozilla.javascript.NativeDate)obj).unwrap()).getTime());
}
if (obj instanceof java.util.Date)
{
return new Timestamp(((java.util.Date)obj).getTime());
}
if (obj instanceof Number)
{
return new Timestamp(((Number)obj).longValue());
}
if (throwOnFail)
{
throw new RuntimeException(Messages.getString("servoy.conversion.error.date", new Object[] { obj })); //$NON-NLS-1$
}
return null;
}
switch (mapToDefaultType(type))
{
case NUMBER :
if (obj instanceof Double || obj instanceof BigDecimal) return obj;
Double retValue = new Double(Utils.getAsDouble(obj, throwOnFail));
if (obj instanceof Long)
{
// long could hold a bigger integer number then a double can hold in its precision/mantissa
if (((Long)obj).longValue() != retValue.longValue())
{
return new BigDecimal(((Long)obj).longValue());
}
}
return retValue;
case INTEGER :
if (obj instanceof Integer) return obj;
if (obj instanceof Long) return obj;
return new Long(Utils.getAsLong(obj, throwOnFail));
case TEXT :
String str = obj.toString();
if (l > 0 && str.length() > l)
{
if (Debug.tracing())
{
Debug.trace("String trimmed to length: " + l + ", " + str); //$NON-NLS-1$ //$NON-NLS-2$
}
str = str.substring(0, l);
}
return str;
case MEDIA :
if (obj instanceof byte[])
{
return obj;
}
if (throwOnFail)
{
throw new RuntimeException(Messages.getString("servoy.conversion.error.media", new Object[] { obj })); //$NON-NLS-1$
}
return null;
default :
return obj.toString();
}
}
catch (RuntimeException e)
{
if (throwOnFail) throw e;
Debug.log(e);
}
return null;
}
public Object getAsRightType(Object obj, String format)
{
return getAsRightType(getType(), getFlags(), obj, format, columnType.getLength(), null, false);
}
public Object getAsRightType(Object obj, String format, TimeZone timeZone)
{
return getAsRightType(getType(), getFlags(), obj, format, columnType.getLength(), timeZone, false);
}
public Object getAsRightType(Object obj)
{
return getAsRightType(getType(), getFlags(), obj, columnType.getLength(), false);
}
public Object getAsRightType(Object obj, boolean throwOnFail)
{
return getAsRightType(getType(), getFlags(), obj, columnType.getLength(), throwOnFail);
}
public boolean isAggregate()
{
return false;
}
public Object getModificationValue(IServiceProvider application) throws RemoteException
{
ColumnInfo ci = getColumnInfo();
if (ci != null)
{
int autoentertype = ci.getAutoEnterType();
switch (autoentertype)
{
case ColumnInfo.SYSTEM_VALUE_AUTO_ENTER :
int systemValueType = ci.getAutoEnterSubType();
switch (systemValueType)
{
case ColumnInfo.SYSTEM_VALUE_MODIFICATION_SERVER_DATETIME :
return new Timestamp(application.getClientHost().getServerTime(application.getClientID()).getTime());
case ColumnInfo.SYSTEM_VALUE_MODIFICATION_DATETIME :
return new Timestamp(TimezoneUtils.getClientDate(application).getTime());
case ColumnInfo.SYSTEM_VALUE_MODIFICATION_USERNAME :
return application.getUserName();
case ColumnInfo.SYSTEM_VALUE_MODIFICATION_USERUID :
String user_uid = application.getUserUID();
if (user_uid == null) user_uid = ""; //$NON-NLS-1$
switch (getDataProviderType())
{
case NUMBER :
return new Double(Utils.getAsDouble(user_uid));
case INTEGER :
return new Integer(Utils.getAsInteger(user_uid));
case TEXT :
default :
return user_uid;
}
default :
return null;
}
default :
return null;
}
}
return null;
}
//NOTE also called for duplicate
public Object getNewRecordValue(IServiceProvider application) throws Exception
{
ColumnInfo ci = getColumnInfo();
if (ci != null)
{
int autoEnterType = ci.getAutoEnterType();
int autoEnterSubType = ci.getAutoEnterSubType();
switch (autoEnterType)
{
case ColumnInfo.SYSTEM_VALUE_AUTO_ENTER :
switch (autoEnterSubType)
{
case ColumnInfo.SYSTEM_VALUE_CREATION_SERVER_DATETIME :
return new Timestamp(application.getClientHost().getServerTime(application.getClientID()).getTime());
case ColumnInfo.SYSTEM_VALUE_CREATION_DATETIME :
// case ColumnInfo.SYSTEM_VALUE_MODIFICATION_DATETIME://makes it possible to search for non modified records
return new Timestamp(TimezoneUtils.getClientDate(application).getTime());
case ColumnInfo.SYSTEM_VALUE_CREATION_USERNAME :
// case ColumnInfo.SYSTEM_VALUE_MODIFICATION_NAME://makes it possible to search for non modified records
return application.getUserName();
case ColumnInfo.SYSTEM_VALUE_CREATION_USERUID :
// case ColumnInfo.SYSTEM_VALUE_MODIFICATION_USERID://makes it possible to search for non modified records
{
String user_uid = application.getUserUID();
if (user_uid == null) user_uid = ""; //$NON-NLS-1$
switch (getDataProviderType())
{
case NUMBER :
return new Double(Utils.getAsDouble(user_uid));
case INTEGER :
return new Integer(Utils.getAsInteger(user_uid));
case TEXT :
default :
return user_uid;
}
}
default :
return null;
}
case ColumnInfo.SEQUENCE_AUTO_ENTER :
if (autoEnterSubType != ColumnInfo.NO_SEQUENCE_SELECTED)
{
IDataServer ds = application.getDataServer();
if (ds != null)
{
return ds.getNextSequence(getTable().getServerName(), getTable().getName(), getName(), ci.getID());
}
return Integer.valueOf(0);
}
//$FALL-THROUGH$
case ColumnInfo.CUSTOM_VALUE_AUTO_ENTER :
String val = ci.getDefaultValue();
switch (getDataProviderType())
{
case NUMBER :
return new Double(Utils.getAsDouble(val));
case INTEGER :
return new Integer(Utils.getAsInteger(val));
case TEXT :
return val;
}
return val;
// case ColumnInfo.CALCULATION_VALUE_AUTO_ENTER:
// return "<todo impl>";
// case ColumnInfo.LOOKUP_VALUE_AUTO_ENTER:
// return "<todo impl>";
case ColumnInfo.NO_AUTO_ENTER :
// if (!getAllowNull())//not wanted by Servoy developers, they will fillin/use the autoenter they say
// {
// switch(mapToDefaultType(type))
// {
// case TEXT:
// return ""; //$NON-NLS-1$
// case NUMBER:
// return new Double(0);
// case INTEGER:
// return new Integer(0);
// case DATETIME:
// return new Date();
// }
// }
default :
return null;
}
}
return null;
}
/**
* Get the column type as defined by the db.
*/
public ColumnType getColumnType()
{
return columnType;
}
@Override
public int getType()
{
return columnType.getSqlType();
}
public String getTypeAsString()
{
return getDisplayTypeString(getType());
}
/*
* _____________________________________________________________ The methods below override methods from superclass <classname>
*/
@Override
public boolean equals(Object o)
{
if (o instanceof Column)
{
Column other = (Column)o;
if ((other.table.equals(table)) && (other.plainSQLName.equalsIgnoreCase(plainSQLName)))
{
return true;
}
}
return false;
}
@Override
public int hashCode()
{
return ((table.hashCode() / 2) + (plainSQLName.hashCode() / 2));
}
/*
* _____________________________________________________________ The methods below belong to interface <interfacename>
*/
@Override
public String getSQLName()//can be camelcasing
{
return plainSQLName;
}
void setSQLName(String name)
{
plainSQLName = name;
hasBadName = null; // clear notify, so checks are run again
normalizedName = null; // should be recalculated
}
private transient String normalizedName = null;//temp var
private transient String dataProviderID = null;//temp var
public String getDataProviderID()//get the id
{
if (columnInfo != null && columnInfo.getDataProviderID() != null)
{
return columnInfo.getDataProviderID();
}
if (dataProviderID != null)
{
return dataProviderID;
}
return getName();
}
public String getAlias()
{
return getDataProviderID();
}
public void setDataProviderID(String dataProviderID)
{
String oldDataProviderID = getDataProviderID();
if (columnInfo != null)
{
columnInfo.setDataProviderID(getName().equals(dataProviderID) ? null : dataProviderID);
this.dataProviderID = null;
columnInfo.flagChanged();
}
else
{
this.dataProviderID = dataProviderID;
}
table.columnDataProviderIDChanged(oldDataProviderID);
}
public int getDataProviderType()
{
return mapToDefaultType(getType());
}
public ColumnWrapper getColumnWrapper()
{
return new ColumnWrapper(this);
}
public boolean isEditable()
{
return getRowIdentType() == NORMAL_COLUMN;
}
/*
* _____________________________________________________________ The methods below belong to this class
*/
@Override
public int getID()
{
if (columnInfo == null)
{
return -1;
}
else
{
return columnInfo.getID();
}
}
public Table getTable()
{
return table;
}
// Used to update database type and length after table creation to make sure
// they hibernate dialect's type choice matches out type.
public void updateColumnType(int type, int length, int scale)
{
this.columnType = checkColumnType(ColumnType.getInstance(type, length, scale));
table.fireIColumnChanged(this);
}
public static ColumnType checkColumnType(ColumnType columnType)
{
if (columnType == null)
{
return null;
}
int defType = Column.mapToDefaultType(columnType.getSqlType());
return ColumnType.getInstance(columnType.getSqlType(), (defType == IColumnTypes.INTEGER || defType == IColumnTypes.DATETIME) ? 0 /* length irrelevant */
: columnType.getLength(), defType == IColumnTypes.NUMBER ? columnType.getScale() : 0);
}
public String getName()
{
if (normalizedName == null)
{
normalizedName = Ident.generateNormalizedNonKeywordName(plainSQLName);
}
return normalizedName;
}
public String getTitle()
{
ColumnInfo ci = getColumnInfo();
String title = (ci != null && ci.getTitleText() != null && ci.getTitleText().trim().length() != 0 ? ci.getTitleText() : null);
if (title != null && title.startsWith("i18n")) //$NON-NLS-1$
{
title = J2DBGlobals.getServiceProvider().getI18NMessage(title);
}
return (title == null ? getName() : title);
}
public void updateName(IValidateName validator, String name) throws RepositoryException
{
if (!existInDB)
{
String newName = name.substring(0, Math.min(name.length(), MAX_SQL_OBJECT_NAME_LENGTH));//limit name for column info table
String oldSQLName = plainSQLName;
try
{
table.columnNameChange(validator, oldSQLName, newName);
}
catch (RepositoryException e)
{
setSQLName(oldSQLName);
throw e;
}
}
}
public void updateDataProviderID(IValidateName validator, String dpid) throws RepositoryException
{
String ndpid = Ident.generateNormalizedName(dpid);
Column other = table.getColumn(ndpid);
if (other != null && other != this)
{
throw new RepositoryException("A column on table " + table.getName() + " with name/dataProviderID " + ndpid + " already exists"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
validator.checkName(ndpid, -1, new ValidatorSearchContext(this, IRepository.COLUMNS), false);
setDataProviderID(ndpid);
table.fireIColumnChanged(this);
}
@Override
public int getScale()
{
return columnType.getScale();
}
public int getLength()
{
return columnType.getLength();
}
@Override
public String toString()
{
return getName();
}
public void setExistInDB(boolean b)
{
existInDB = b;
}
public boolean getExistInDB()
{
return existInDB;
}
//return if this this column a primary key
public int getRowIdentType()
{
return getFlags() & IDENT_COLUMNS;
}
/**
* set this column to be a primary key type must be NORMAL_COLUMN,PK_COLUMN and USER_ROW_ID_COLUMN.
*/
public void setRowIdentType(int type)
{
setFlags(type | (getFlags() & NON_IDENT_COLUMNS));
}
public void setFlags(int f)
{
if (f < 0)
{
// -1 value is only for internal class use; all external setFlags calls should have f >= 0
Debug.error("Set flags called with " + f + ". This is not a valid value for column flags."); //$NON-NLS-1$//$NON-NLS-2$
return;
}
// dbPK dictates the value of the PK_COLUMN flag and can disable USER_ROWID_COLUMN
int colIdentFlags;
if (existInDB)
{
if ((f & IDENT_COLUMNS) == USER_ROWID_COLUMN && !dbPK)
{
colIdentFlags = USER_ROWID_COLUMN; // only set user row ident if it is not already pk
}
else
{
colIdentFlags = dbPK ? PK_COLUMN : NORMAL_COLUMN;
}
}
else
{
colIdentFlags = f & IDENT_COLUMNS;
}
// use computed identity flags combined with other flags from columnInfo
int newFlags = (f & NON_IDENT_COLUMNS) | colIdentFlags;
updateTableIdentColumns(newFlags);
if (columnInfo != null)
{
if (columnInfo.getFlags() != newFlags)
{
columnInfo.setFlags(newFlags);
columnInfo.flagChanged();
}
this.flags = -1; // clear local
}
else
{
// dbPK = ((newFlags & PK_COLUMN) != 0);
this.flags = newFlags;
}
}
protected void updateTableIdentColumns(int newFlags)
{
if ((newFlags & IDENT_COLUMNS) != NORMAL_COLUMN)
{
table.addRowIdentColumn(this);
if (!existInDB) allowNull = false;
}
else
{
table.removeRowIdentColumn(this);
}
}
public int getFlags()
{
if (columnInfo == null)
{
if (flags != -1)
{
return flags;
}
return dbPK ? Column.PK_COLUMN : 0;
}
return columnInfo.getFlags();
}
public void setDatabasePK(boolean pk)
{
dbPK = pk;
if (columnInfo == null && flags == -1)
{
updateTableIdentColumns(dbPK ? PK_COLUMN : NORMAL_COLUMN);
}
else
{
setFlags(getFlags()); // update flags/table ident columns to reflect new dbPK status
}
}
public boolean isDatabasePK()
{
if (!existInDB)
{
return (getFlags() & PK_COLUMN) != 0;
}
else
{
return dbPK;
}
}
/**
* Set or clear a flag.
*
* @param flag
* @param set
*/
public void setFlag(int flag, boolean set)
{
setFlags(set ? (getFlags() | flag) : (getFlags() & ~flag));
}
/**
* @param flag
*/
public boolean hasFlag(int flag)
{
return (getFlags() & flag) != 0;
}
public void setDatabaseDefaultValue(String value)
{
databaseDefaultValue = value;
if (columnInfo != null)
{
// Don't flag the column info as changed, since the default value is dynamic (i.e., not saved).
columnInfo.setDatabaseDefaultValue(value);
}
}
public String getDatabaseDefaultValue()
{
return columnInfo != null ? columnInfo.getDatabaseDefaultValue() : databaseDefaultValue;
}
/**
* @return column type as configured by developer, fall back to db type when configured is not available
*/
public ColumnType getConfiguredColumnType()
{
if (columnInfo != null && columnInfo.getConfiguredColumnType() != null)
{
return columnInfo.getConfiguredColumnType();
}
// default to db-defined column type
return columnType;
}
public void setColumnInfo(ColumnInfo ci)
{
if (ci == null) throw new NullPointerException("Column info cannot be set null"); //$NON-NLS-1$
String oldDataProviderID = getDataProviderID();
if (!ci.isStoredPersistently() && getColumnType().getScale() > 0 && ci.getCompatibleColumnTypes() == null) // if this is a default in-memory column info, it's type should be compatible with the actual column type
{
// if table definition has a scale, add it to the compatible list, because the default type won't store scale.
ci.addCompatibleColumnType(getColumnType());
}
columnInfo = ci;
if (sequenceType != ColumnInfo.NO_SEQUENCE_SELECTED) //delegate
{
setSequenceType(sequenceType);
if (databaseSequenceName != null)
{
setDatabaseSequenceName(databaseSequenceName);
}
}
if (dataProviderID != null)
{
setDataProviderID(dataProviderID);
}
if (flags != -1)
{
setFlags(flags); // use the flags (meant for the use-case when you want to create a column marked as UUID - before actually creating it in DB, see commit for revision 4340)
}
else
{
// re-apply column-info flags to column to adjust them if necessary as setFlags() allows
setFlags(ci.getFlags());
}
// The database default value only gets set once, via the column itself and never via the column info.
// Thus the version in the column is always the correct one and should override anything in column info.
setDatabaseDefaultValue(databaseDefaultValue);
table.columnDataProviderIDChanged(oldDataProviderID);
}
public void removeColumnInfo()//only called when column is deleted...
{
columnInfo = null;
}
/**
* Is only present after Server.createTableInDB if creating new
*/
public ColumnInfo getColumnInfo()
{
return columnInfo;
}
/**
* Returns the allowNull.
*
* @return boolean
*/
public boolean getAllowNull()
{
return allowNull;
}
/**
* Sets the allowNull.
*
* @param allowNull The allowNull to set
*/
public void setAllowNull(boolean allowNull)
{
this.allowNull = allowNull;
}
private transient String note;//used to show temp tooltip text when hovering over
public String getNote()
{
if (note == null && columnInfo != null)
{
// plain text
return columnInfo.getTextualPropertyInfo(false);
}
return note;
}
/**
* @param string
*/
public void setNote(String string)
{
note = string;
}
public int getSequenceType()
{
if (columnInfo != null && columnInfo.getAutoEnterType() == ColumnInfo.SEQUENCE_AUTO_ENTER)
{
return columnInfo.getAutoEnterSubType();
}
return sequenceType;
}
/*
* For temp_xxx tables columninfo is not set but identity column may be used
*/
public boolean isDBIdentity()
{
return getSequenceType() == ColumnInfo.DATABASE_IDENTITY;
}
private transient int sequenceType = ColumnInfo.NO_SEQUENCE_SELECTED;
private transient String databaseSequenceName;
private transient int flags = -1;
public void setSequenceType(int i)
{
if (columnInfo != null)
{
columnInfo.setAutoEnterType(ColumnInfo.SEQUENCE_AUTO_ENTER);
columnInfo.setAutoEnterSubType(i);
columnInfo.flagChanged();
sequenceType = ColumnInfo.NO_SEQUENCE_SELECTED; //clear local
}
else
{
sequenceType = i;
}
}
public void setDatabaseSequenceName(String databaseSequenceName)
{
if (columnInfo != null)
{
columnInfo.setDatabaseSequenceName(databaseSequenceName);
columnInfo.flagChanged();
this.databaseSequenceName = null; // clear local
}
else
{
this.databaseSequenceName = databaseSequenceName;
}
}
/**
* @param flags
*/
public static String getFlagsString(int flags)
{
StringBuilder sb = new StringBuilder();
if ((flags & USER_ROWID_COLUMN) != 0) sb.append(" row_ident"); //$NON-NLS-1$
if ((flags & PK_COLUMN) != 0) sb.append(" pk"); //$NON-NLS-1$
if ((flags & UUID_COLUMN) != 0) sb.append(" uuid"); //$NON-NLS-1$
if ((flags & EXCLUDED_COLUMN) != 0) sb.append(" excluded"); //$NON-NLS-1$
return sb.toString().trim();
}
/**
* It can happen when developers load existing table/columns from the db, that they contain reserved word for other dbs
*/
private transient Boolean hasBadName = null;
public boolean hasBadNaming(boolean isMobile)
{
if (hasBadName == null)
{
List<String> notes = new ArrayList<String>();
if (Ident.checkIfKeyword(getName()) || SQLKeywords.checkIfKeyword(getName()))
{
notes.add("'" + getName() + "' is an reserved word!");
}
if (isMobile && Ident.checkIfReservedBrowserWindowObjectWord(getName()))
{
notes.add("'" + getName() + "' is an reserved browser window object word!");
}
if (getName().length() > MAX_SQL_OBJECT_NAME_LENGTH)
{
notes.add("Column namnes longer than " + MAX_SQL_OBJECT_NAME_LENGTH + " are not supported by some databases!");
}
if (notes.size() > 0)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < notes.size(); i++)
{
sb.append(notes.get(i));
}
note = sb.toString();
}
hasBadName = Boolean.valueOf(notes.size() > 0);
}
return hasBadName.booleanValue();
}
/**
* Determines the length of an object, mainly string and byte[]
*
* @param value the object
* @param type of the object
* @return 0 if irrelevant, Integer.MAX_VALUE if it does not know
*/
public static int getObjectSize(Object value, int type)
{
if (value == null) return 0;//length irrelevant for null values
switch (mapToDefaultType(type))
{
case NUMBER :
case INTEGER :
case DATETIME :
return 0; //irrelevant, db makes it fit
case TEXT :
if (value instanceof String)
{
return ((String)value).length();
}
break;
case MEDIA :
if (value instanceof byte[])
{
return ((byte[])value).length;
}
break;
}
return Integer.MAX_VALUE;
}
public void flagColumnInfoChanged()
{
if (columnInfo != null) columnInfo.flagChanged();
if (table != null) table.fireIColumnChanged(this);
}
/**
* Check if db column type is compatible with external column type (like from import)
*/
public static boolean isColumnInfoCompatible(ColumnType dbColumnType, ColumnType externalColumnType, boolean checkLength)
{
if (dbColumnType == null && externalColumnType == null)
{
return true;
}
if (dbColumnType == null)
{
return false;
}
if (dbColumnType.equals(externalColumnType))
{
return true;
}
int dbtype = mapToDefaultType(dbColumnType.getSqlType());
int exttype = mapToDefaultType(externalColumnType.getSqlType());
if (dbtype == exttype)
{
if (checkLength && dbtype == IColumnTypes.TEXT && dbColumnType.getLength() != 0 && externalColumnType.getLength() != 0 &&
dbColumnType.getLength() != externalColumnType.getLength())
{
// different length
return false;
}
return true;
}
// different type
if (dbtype == IColumnTypes.NUMBER && exttype == IColumnTypes.INTEGER)
{
return true;
}
return false;
}
}