blob: a37fd978bf5aabe494a40ee651924e06a841a132 [file] [log] [blame]
/* Copyright (c) 2001-2010, The HSQL Development Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the HSQL Development Group nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hsqldb.types;
import java.math.BigDecimal;
import org.hsqldb.Collation;
import org.hsqldb.OpTypes;
import org.hsqldb.Session;
import org.hsqldb.SessionInterface;
import org.hsqldb.Tokens;
import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.lib.ArrayUtil;
import org.hsqldb.lib.StringConverter;
import org.hsqldb.lib.StringUtil;
import org.hsqldb.lib.java.JavaSystem;
/**
* Type subclass for CHARACTER, VARCHAR, etc.<p>
*
* @author Fred Toussi (fredt@users dot sourceforge.net)
* @version 1.9.0
* @since 1.9.0
*/
public class CharacterType extends Type {
Collation collation;
Charset charset;
boolean isEqualIdentical;
final static int defaultCharPrecision = 32 * 1024;
static final long maxCharPrecision = Integer.MAX_VALUE;
public CharacterType(Collation collation, int type, long precision) {
super(Types.SQL_VARCHAR, type, precision, 0);
this.collation = collation;
this.charset = Charset.getDefaultInstance();
isEqualIdentical = this.collation.isEqualAlwaysIdentical()
&& type != Types.VARCHAR_IGNORECASE;
}
/**
* Always English collation
*/
public CharacterType(int type, long precision) {
super(Types.SQL_VARCHAR, type, precision, 0);
this.collation = Collation.getDefaultInstance();
this.charset = Charset.getDefaultInstance();
isEqualIdentical = type != Types.VARCHAR_IGNORECASE;
}
public int displaySize() {
return precision > Integer.MAX_VALUE ? Integer.MAX_VALUE
: (int) precision;
}
public int getJDBCTypeCode() {
switch (typeCode) {
case Types.SQL_CHAR :
return Types.CHAR;
case Types.SQL_VARCHAR :
case Types.VARCHAR_IGNORECASE :
return Types.VARCHAR;
case Types.SQL_CLOB :
return Types.CLOB;
default :
throw Error.runtimeError(ErrorCode.U_S0500, "CharacterType");
}
}
public Class getJDBCClass() {
return String.class;
}
public String getJDBCClassName() {
return "java.lang.String";
}
public int getSQLGenericTypeCode() {
return typeCode == Types.SQL_CHAR ? typeCode
: Types.SQL_VARCHAR;
}
public String getNameString() {
switch (typeCode) {
case Types.SQL_CHAR :
return Tokens.T_CHARACTER;
case Types.SQL_VARCHAR :
return Tokens.T_VARCHAR;
case Types.VARCHAR_IGNORECASE :
return Tokens.T_VARCHAR_IGNORECASE;
case Types.SQL_CLOB :
return Tokens.T_CLOB;
default :
throw Error.runtimeError(ErrorCode.U_S0500, "CharacterType");
}
}
public String getFullNameString() {
switch (typeCode) {
case Types.SQL_CHAR :
return Tokens.T_CHARACTER;
case Types.SQL_VARCHAR :
return "CHARACTER VARYING";
case Types.VARCHAR_IGNORECASE :
return Tokens.T_VARCHAR_IGNORECASE;
case Types.SQL_CLOB :
return "CHARACTER LARGE OBJECT";
default :
throw Error.runtimeError(ErrorCode.U_S0500, "CharacterType");
}
}
public String getDefinition() {
if (precision == 0) {
return getNameString();
}
StringBuffer sb = new StringBuffer(16);
sb.append(getNameString());
sb.append('(');
sb.append(precision);
sb.append(')');
return sb.toString();
}
public boolean isCharacterType() {
return true;
}
public long getMaxPrecision() {
return maxCharPrecision;
}
public boolean acceptsPrecision() {
return true;
}
public boolean requiresPrecision() {
return typeCode == Types.SQL_VARCHAR
|| typeCode == Types.VARCHAR_IGNORECASE;
}
public int precedenceDegree(Type other) {
if (other.typeCode == typeCode) {
return 0;
}
if (!other.isCharacterType()) {
return Integer.MIN_VALUE;
}
switch (typeCode) {
case Types.SQL_CHAR :
return other.typeCode == Types.SQL_CLOB ? 4
: 2;
case Types.SQL_VARCHAR :
case Types.VARCHAR_IGNORECASE :
if (other.typeCode == Types.SQL_VARCHAR
|| other.typeCode == Types.VARCHAR_IGNORECASE) {
return 0;
}
return other.typeCode == Types.SQL_CLOB ? 4
: 2;
case Types.SQL_CLOB :
return other.typeCode == Types.SQL_CHAR ? -4
: -2;
default :
throw Error.runtimeError(ErrorCode.U_S0500, "CharacterType");
}
}
public Type getAggregateType(Type other) {
if (typeCode == other.typeCode) {
return precision >= other.precision ? this
: other;
}
switch (other.typeCode) {
case Types.SQL_ALL_TYPES :
return this;
case Types.SQL_CHAR :
return precision >= other.precision ? this
: getCharacterType(
typeCode, other.precision);
case Types.SQL_VARCHAR :
if (typeCode == Types.SQL_CLOB
|| typeCode == Types.VARCHAR_IGNORECASE) {
return precision >= other.precision ? this
: getCharacterType(
typeCode,
other.precision);
} else {
return other.precision >= precision ? other
: getCharacterType(
other.typeCode,
precision);
}
case Types.VARCHAR_IGNORECASE :
if (typeCode == Types.SQL_CLOB) {
return precision >= other.precision ? this
: getCharacterType(
typeCode,
other.precision);
} else {
return other.precision >= precision ? other
: getCharacterType(
other.typeCode,
precision);
}
case Types.SQL_CLOB :
return other.precision >= precision ? other
: getCharacterType(
other.typeCode, precision);
case Types.SQL_BIT :
case Types.SQL_BIT_VARYING :
case Types.SQL_BLOB :
case Types.SQL_BINARY :
case Types.SQL_VARBINARY :
case Types.OTHER :
throw Error.error(ErrorCode.X_42562);
default :
/**
* @todo - this seems to be allowed in SQL-92 (is in NIST)
* but is disallowed in SQL:2003
* need to make dependent on a database property
*/
/*
int length = other.displaySize();
return getCharacterType(Types.SQL_VARCHAR,
length).getAggregateType(this);
*/
throw Error.error(ErrorCode.X_42562);
}
}
/**
* For concatenation
*/
public Type getCombinedType(Type other, int operation) {
if (operation != OpTypes.CONCAT) {
return getAggregateType(other);
}
Type newType;
long newPrecision = this.precision + other.precision;
switch (other.typeCode) {
case Types.SQL_ALL_TYPES :
return this;
case Types.SQL_CHAR :
newType = this;
break;
case Types.SQL_VARCHAR :
newType =
(typeCode == Types.SQL_CLOB || typeCode == Types
.VARCHAR_IGNORECASE) ? this
: other;
break;
case Types.VARCHAR_IGNORECASE :
newType = typeCode == Types.SQL_CLOB ? this
: other;
break;
case Types.SQL_CLOB :
newType = other;
break;
default :
throw Error.error(ErrorCode.X_42562);
}
if (newPrecision > maxCharPrecision) {
if (typeCode == Types.SQL_BINARY) {
// Standard disallows type length reduction
throw Error.error(ErrorCode.X_42570);
} else if (typeCode == Types.SQL_CHAR) {
newPrecision = maxCharPrecision;
}
}
return getCharacterType(newType.typeCode, precision + other.precision);
}
public int compare(Session session, Object a, Object b) {
if (a == b) {
return 0;
}
if (a == null) {
return -1;
}
if (b == null) {
return 1;
}
String as = (String) a;
String bs = (String) b;
int la = as.length();
int lb = bs.length();
if (la == lb) {}
else if (la > lb) {
char[] buffer = new char[la];
bs.getChars(0, lb, buffer, 0);
ArrayUtil.fillArray(buffer, lb, ' ');
bs = String.valueOf(buffer);
} else {
char[] buffer = new char[lb];
as.getChars(0, la, buffer, 0);
ArrayUtil.fillArray(buffer, la, ' ');
as = String.valueOf(buffer);
}
if (typeCode == Types.VARCHAR_IGNORECASE) {
return collation.compareIgnoreCase(as, bs);
} else {
return collation.compare(as, bs);
}
}
public Object convertToTypeLimits(SessionInterface session, Object a) {
if (a == null) {
return a;
}
if (precision == 0) {
return a;
}
switch (typeCode) {
case Types.SQL_CHAR : {
int slen = ((String) a).length();
if (slen == precision) {
return a;
}
if (slen > precision) {
if (getRightTrimSise((String) a, ' ') <= precision) {
return ((String) a).substring(0, (int) precision);
} else {
throw Error.error(ErrorCode.X_22001);
}
}
char[] b = new char[(int) precision];
((String) a).getChars(0, slen, b, 0);
for (int i = slen; i < precision; i++) {
b[i] = ' ';
}
return new String(b);
}
case Types.SQL_VARCHAR :
case Types.VARCHAR_IGNORECASE : {
int slen = ((String) a).length();
if (slen > precision) {
if (getRightTrimSise((String) a, ' ') <= precision) {
return ((String) a).substring(0, (int) precision);
} else {
throw Error.error(ErrorCode.X_22001);
}
}
return a;
}
case Types.SQL_CLOB :
/** @todo implement */
return a;
default :
throw Error.runtimeError(ErrorCode.U_S0500, "CharacterType");
}
}
public Object castToType(SessionInterface session, Object a,
Type otherType) {
if (a == null) {
return a;
}
return castOrConvertToType(session, a, otherType, true);
}
public Object castOrConvertToType(SessionInterface session, Object a,
Type otherType, boolean cast) {
switch (otherType.typeCode) {
case Types.SQL_CHAR :
case Types.SQL_VARCHAR :
case Types.VARCHAR_IGNORECASE : {
int length = ((String) a).length();
if (precision != 0 && length > precision) {
if (StringUtil.rightTrimSize((String) a) > precision) {
if (!cast) {
throw Error.error(ErrorCode.X_22001);
}
session.addWarning(Error.error(ErrorCode.W_01004));
}
a = ((String) a).substring(0, (int) precision);
}
switch (typeCode) {
case Types.SQL_CHAR :
return convertToTypeLimits(session, a);
case Types.SQL_VARCHAR :
case Types.VARCHAR_IGNORECASE :
return a;
case Types.SQL_CLOB : {
ClobData clob =
session.createClob(((String) a).length());
clob.setString(session, 0, (String) a);
return clob;
}
default :
throw Error.runtimeError(ErrorCode.U_S0500,
"CharacterType");
}
}
case Types.SQL_CLOB : {
long length = ((ClobData) a).length(session);
if (precision != 0 && length > precision) {
if (((ClobData) a).nonSpaceLength(session) > precision) {
if (!cast) {
throw Error.error(ErrorCode.X_22001);
}
session.addWarning(Error.error(ErrorCode.W_01004));
}
}
switch (typeCode) {
case Types.SQL_CHAR :
case Types.SQL_VARCHAR :
case Types.VARCHAR_IGNORECASE : {
if (length > maxCharPrecision) {
if (!cast) {
throw Error.error(ErrorCode.X_22001);
}
length = maxCharPrecision;
}
a = ((ClobData) a).getSubString(session, 0,
(int) length);
return convertToTypeLimits(session, a);
}
case Types.SQL_CLOB : {
if (precision != 0 && length > precision) {
return ((ClobData) a).getClob(session, 0,
precision);
}
return a;
}
default :
throw Error.runtimeError(ErrorCode.U_S0500,
"CharacterType");
}
}
case Types.OTHER :
throw Error.error(ErrorCode.X_42561);
case Types.SQL_BIT :
case Types.SQL_BIT_VARYING :
case Types.SQL_BLOB :
case Types.SQL_BINARY :
case Types.SQL_VARBINARY :
default :
String s = otherType.convertToString(a);
if (precision != 0 && s.length() > precision) {
throw Error.error(ErrorCode.X_22001);
}
a = s;
return convertToTypeLimits(session, a);
}
}
public Object convertToType(SessionInterface session, Object a,
Type otherType) {
if (a == null) {
return a;
}
return castOrConvertToType(session, a, otherType, false);
}
public Object convertToTypeJDBC(SessionInterface session, Object a,
Type otherType) {
if (a == null) {
return a;
}
if (otherType.typeCode == Types.SQL_BLOB) {
throw Error.error(ErrorCode.X_42561);
}
return convertToType(session, a, otherType);
}
/**
* Relaxes SQL parameter type enforcement, allowing long strings.
*/
public Object convertToDefaultType(SessionInterface session, Object a) {
if (a == null) {
return a;
}
String s;
if (a instanceof Boolean) {
s = a.toString();
} else if (a instanceof BigDecimal) {
s = JavaSystem.toString((BigDecimal) a);
} else if (a instanceof Number) {
s = a.toString(); // use shortcut
} else if (a instanceof String) {
s = (String) a;
} else if (a instanceof java.sql.Date) {
s = ((java.sql.Date) a).toString();
} else if (a instanceof java.sql.Time) {
s = ((java.sql.Time) a).toString();
} else if (a instanceof java.sql.Timestamp) {
s = ((java.sql.Timestamp) a).toString();
} else {
throw Error.error(ErrorCode.X_42561);
}
return s;
// return convertToType(session, a, Type.SQL_VARCHAR);
}
public String convertToString(Object a) {
if (a == null) {
return null;
}
switch (typeCode) {
case Types.SQL_CHAR : {
int slen = ((String) a).length();
if (precision == 0 || slen == precision) {
return (String) a;
}
char[] b = new char[(int) precision];
((String) a).getChars(0, slen, b, 0);
for (int i = slen; i < precision; i++) {
b[i] = ' ';
}
return new String(b);
}
case Types.SQL_VARCHAR :
case Types.VARCHAR_IGNORECASE : {
return (String) a;
}
default :
throw Error.runtimeError(ErrorCode.U_S0500, "CharacterType");
}
}
public String convertToSQLString(Object a) {
if (a == null) {
return Tokens.T_NULL;
}
String s = convertToString(a);
return StringConverter.toQuotedString(s, '\'', true);
}
public boolean canConvertFrom(Type otherType) {
return !otherType.isObjectType();
}
public Collation getCollation() {
return collation;
}
public Charset getCharacterSet() {
return charset;
}
public boolean isEqualIdentical() {
return isEqualIdentical;
}
public boolean isCaseInsensitive() {
return typeCode == Types.VARCHAR_IGNORECASE;
}
public long position(SessionInterface session, Object data,
Object otherData, Type otherType, long offset) {
if (data == null || otherData == null) {
return -1L;
}
if (otherType.typeCode == Types.SQL_CLOB) {
long otherLength = ((ClobData) data).length(session);
if (offset + otherLength > ((String) data).length()) {
return -1;
}
String otherString = ((ClobData) otherData).getSubString(session,
0, (int) otherLength);
return ((String) data).indexOf(otherString, (int) offset);
} else if (otherType.isCharacterType()) {
long otherLength = ((String) data).length();
if (offset + otherLength > ((String) data).length()) {
return -1;
}
return ((String) data).indexOf((String) otherData, (int) offset);
} else {
throw Error.runtimeError(ErrorCode.U_S0500, "CharacterType");
}
}
public Object substring(SessionInterface session, Object data,
long offset, long length, boolean hasLength,
boolean trailing) {
long end;
long dataLength = typeCode == Types.SQL_CLOB
? ((ClobData) data).length(session)
: ((String) data).length();
if (trailing) {
end = dataLength;
if (length > dataLength) {
offset = 0;
length = dataLength;
} else {
offset = dataLength - length;
}
} else if (hasLength) {
end = offset + length;
} else {
end = dataLength > offset ? dataLength
: offset;
}
if (end < offset) {
throw Error.error(ErrorCode.X_22011);
}
if (offset > end || end < 0) {
// return zero length data
offset = 0;
end = 0;
}
if (offset < 0) {
offset = 0;
}
if (end > dataLength) {
end = dataLength;
}
length = end - offset;
if (data instanceof String) {
return ((String) data).substring((int) offset,
(int) (offset + length));
} else if (data instanceof ClobData) {
ClobData clob = session.createClob(length);
/** @todo - change to support long strings */
String result = ((ClobData) data).getSubString(session, offset,
(int) length);
clob.setString(session, 0, result);
return clob;
} else {
throw Error.runtimeError(ErrorCode.U_S0500, "CharacterType");
}
}
/**
* Memory limits apply to Upper and Lower implementations with Clob data
*/
public Object upper(Session session, Object data) {
if (data == null) {
return null;
}
if (typeCode == Types.SQL_CLOB) {
String result = ((ClobData) data).getSubString(session, 0,
(int) ((ClobData) data).length(session));
result = collation.toUpperCase(result);
ClobData clob = session.createClob(result.length());
clob.setString(session, 0, result);
return clob;
}
return collation.toUpperCase((String) data);
}
public Object lower(Session session, Object data) {
if (data == null) {
return null;
}
if (typeCode == Types.SQL_CLOB) {
String result = ((ClobData) data).getSubString(session, 0,
(int) ((ClobData) data).length(session));
result = collation.toLowerCase(result);
ClobData clob = session.createClob(result.length());
clob.setString(session, 0, result);
return clob;
}
return collation.toLowerCase((String) data);
}
public Object trim(SessionInterface session, Object data, int trim,
boolean leading, boolean trailing) {
if (data == null) {
return null;
}
String s;
if (typeCode == Types.SQL_CLOB) {
s = ((ClobData) data).getSubString(
session, 0, (int) ((ClobData) data).length(session));
} else {
s = (String) data;
}
int endindex = s.length();
if (trailing) {
for (--endindex; endindex >= 0 && s.charAt(endindex) == trim;
endindex--) {}
endindex++;
}
int startindex = 0;
if (leading) {
while (startindex < endindex && s.charAt(startindex) == trim) {
startindex++;
}
}
/** @todo - change to support long strings */
if (startindex == 0 && endindex == s.length()) {}
else {
s = s.substring(startindex, endindex);
}
if (typeCode == Types.SQL_CLOB) {
ClobData clob = session.createClob(s.length());
clob.setString(session, 0, s);
return clob;
} else {
return s;
}
}
public Object overlay(SessionInterface session, Object data,
Object overlay, long offset, long length,
boolean hasLength) {
if (data == null || overlay == null) {
return null;
}
if (!hasLength) {
length = typeCode == Types.SQL_CLOB
? ((ClobData) overlay).length(session)
: ((String) overlay).length();
}
Object temp = concat(null,
substring(session, data, 0, offset, true, false),
overlay);
return concat(null, temp,
substring(session, data, offset + length, 0, false,
false));
}
public Object concat(Session session, Object a, Object b) {
if (a == null || b == null) {
return null;
}
String left;
String right;
if (a instanceof ClobData) {
left = ((ClobData) a).getSubString(
session, 0, (int) ((ClobData) a).length(session));
} else {
left = (String) a;
}
if (b instanceof ClobData) {
right = ((ClobData) b).getSubString(
session, 0, (int) ((ClobData) b).length(session));
} else {
right = (String) b;
}
if (typeCode == Types.SQL_CLOB) {
ClobData clob = session.createClob(left.length() + right.length());
clob.setString(session, 0, left);
clob.setString(session, left.length(), right);
return clob;
} else {
return left + right;
}
}
public long size(SessionInterface session, Object data) {
if (typeCode == Types.SQL_CLOB) {
return ((ClobData) data).length(session);
}
return ((String) data).length();
}
/*
public static Object concat(Object a, Object b) {
if (a == null || b == null) {
return null;
}
return a.toString() + b.toString();
}
*/
public static int getRightTrimSise(String s, char trim) {
int endindex = s.length();
for (--endindex; endindex >= 0 && s.charAt(endindex) == trim;
endindex--) {}
endindex++;
return endindex;
}
public static Type getCharacterType(int type, long precision) {
switch (type) {
case Types.SQL_VARCHAR :
case Types.SQL_CHAR :
case Types.VARCHAR_IGNORECASE :
return new CharacterType(type, (int) precision);
case Types.SQL_CLOB :
return new ClobType(precision);
default :
throw Error.runtimeError(ErrorCode.U_S0500, "CharacterType");
}
}
}