/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you 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 org.eigenbase.sql;
import java.math.*;
import java.nio.charset.*;
import java.util.*;
import org.eigenbase.reltype.*;
import org.eigenbase.sql.fun.*;
import org.eigenbase.sql.parser.*;
import org.eigenbase.sql.type.*;
import org.eigenbase.sql.util.*;
import org.eigenbase.sql.validate.*;
import org.eigenbase.util.*;
import static org.eigenbase.util.Static.RESOURCE;
/**
* A <code>SqlLiteral</code> is a constant. It is, appropriately, immutable.
*
* <p>How is the value stored? In that respect, the class is somewhat of a black
* box. There is a {@link #getValue} method which returns the value as an
* object, but the type of that value is implementation detail, and it is best
* that your code does not depend upon that knowledge. It is better to use
* task-oriented methods such as {@link #toSqlString(SqlDialect)} and
* {@link #toValue}.</p>
*
* <p>If you really need to access the value directly, you should switch on the
* value of the {@link #typeName} field, rather than making assumptions about
* the runtime type of the {@link #value}.</p>
*
* <p>The allowable types and combinations are:
*
* <table>
* <caption>Allowable types for SqlLiteral</caption>
* <tr>
* <th>TypeName</th>
* <th>Meaing</th>
* <th>Value type</th>
* </tr>
* <tr>
* <td>{@link SqlTypeName#NULL}</td>
* <td>The null value. It has its own special type.</td>
* <td>null</td>
* </tr>
* <tr>
* <td>{@link SqlTypeName#BOOLEAN}</td>
* <td>Boolean, namely <code>TRUE</code>, <code>FALSE</code> or <code>
* UNKNOWN</code>.</td>
* <td>{@link Boolean}, or null represents the UNKNOWN value</td>
* </tr>
* <tr>
* <td>{@link SqlTypeName#DECIMAL}</td>
* <td>Exact number, for example <code>0</code>, <code>-.5</code>, <code>
* 12345</code>.</td>
* <td>{@link BigDecimal}</td>
* </tr>
* <tr>
* <td>{@link SqlTypeName#DOUBLE}</td>
* <td>Approximate number, for example <code>6.023E-23</code>.</td>
* <td>{@link BigDecimal}</td>
* </tr>
* <tr>
* <td>{@link SqlTypeName#DATE}</td>
* <td>Date, for example <code>DATE '1969-04'29'</code></td>
* <td>{@link Calendar}</td>
* </tr>
* <tr>
* <td>{@link SqlTypeName#TIME}</td>
* <td>Time, for example <code>TIME '18:37:42.567'</code></td>
* <td>{@link Calendar}</td>
* </tr>
* <tr>
* <td>{@link SqlTypeName#TIMESTAMP}</td>
* <td>Timestamp, for example <code>TIMESTAMP '1969-04-29
* 18:37:42.567'</code></td>
* <td>{@link Calendar}</td>
* </tr>
* <tr>
* <td>{@link SqlTypeName#CHAR}</td>
* <td>Character constant, for example <code>'Hello, world!'</code>, <code>
* ''</code>, <code>_N'Bonjour'</code>, <code>_ISO-8859-1'It''s superman!'
* COLLATE SHIFT_JIS$ja_JP$2</code>. These are always CHAR, never VARCHAR.</td>
* <td>{@link NlsString}</td>
* </tr>
* <tr>
* <td>{@link SqlTypeName#BINARY}</td>
* <td>Binary constant, for example <code>X'ABC'</code>, <code>X'7F'</code>.
* Note that strings with an odd number of hexits will later become values of
* the BIT datatype, because they have an incomplete number of bytes. But here,
* they are all binary constants, because that's how they were written. These
* constants are always BINARY, never VARBINARY.</td>
* <td>{@link BitString}</td>
* </tr>
* <tr>
* <td>{@link SqlTypeName#SYMBOL}</td>
* <td>A symbol is a special type used to make parsing easier; it is not part of
* the SQL standard, and is not exposed to end-users. It is used to hold a
* symbol, such as the LEADING flag in a call to the function <code>
* TRIM([LEADING|TRAILING|BOTH] chars FROM string)</code>.</td>
* <td>A class which implements the {@link SqlSymbol} interface</td>
* </tr>
* <tr>
* <td>{@link SqlTypeName#INTERVAL_DAY_TIME}</td>
* <td>Interval, for example <code>INTERVAL '1:34' HOUR</code>.</td>
* <td>{@link SqlIntervalLiteral.IntervalValue}.</td>
* </tr>
* </table>
*/
public class SqlLiteral extends SqlNode {
//~ Instance fields --------------------------------------------------------
/**
* The type with which this literal was declared. This type is very
* approximate: the literal may have a different type once validated. For
* example, all numeric literals have a type name of
* {@link SqlTypeName#DECIMAL}, but on validation may become
* {@link SqlTypeName#INTEGER}.
*/
private final SqlTypeName typeName;
/**
* The value of this literal. The type of the value must be appropriate for
* the typeName, as defined by the {@link #valueMatchesType} method.
*/
protected final Object value;
//~ Constructors -----------------------------------------------------------
/**
* Creates a <code>SqlLiteral</code>.
*/
protected SqlLiteral(
Object value,
SqlTypeName typeName,
SqlParserPos pos) {
super(pos);
this.value = value;
this.typeName = typeName;
assert typeName != null;
assert valueMatchesType(value, typeName);
}
//~ Methods ----------------------------------------------------------------
/**
* @return value of {@link #typeName}
*/
public SqlTypeName getTypeName() {
return typeName;
}
/**
* @return whether value is appropriate for its type (we have rules about
* these things)
*/
public static boolean valueMatchesType(
Object value,
SqlTypeName typeName) {
switch (typeName) {
case BOOLEAN:
return (value == null) || (value instanceof Boolean);
case NULL:
return value == null;
case DECIMAL:
case DOUBLE:
return value instanceof BigDecimal;
case DATE:
case TIME:
case TIMESTAMP:
return value instanceof Calendar;
case INTERVAL_DAY_TIME:
case INTERVAL_YEAR_MONTH:
return value instanceof SqlIntervalLiteral.IntervalValue;
case BINARY:
return value instanceof BitString;
case CHAR:
return value instanceof NlsString;
case SYMBOL:
return (value instanceof SqlSymbol)
|| (value instanceof SqlSampleSpec);
case MULTISET:
return true;
case INTEGER: // not allowed -- use Decimal
case VARCHAR: // not allowed -- use Char
case VARBINARY: // not allowed -- use Binary
default:
throw Util.unexpected(typeName);
}
}
public SqlNode clone(SqlParserPos pos) {
return new SqlLiteral(value, typeName, pos);
}
public SqlKind getKind() {
return SqlKind.LITERAL;
}
/**
* Returns the value of this literal.
*
* <p>Try not to use this method! There are so many different kinds of
* values, it's better to to let SqlLiteral do whatever it is you want to
* do.
*
* @see #booleanValue()
* @see #symbolValue()
*/
public Object getValue() {
return value;
}
/** Returns the value as a symbol. */
public <E extends SqlSymbol> E symbolValue() {
//noinspection unchecked
return (E) value;
}
/** Returns the value as a boolean. */
public boolean booleanValue() {
return (Boolean) value;
}
/**
* Extracts the {@link SqlSampleSpec} value from a symbol literal.
*
* @throws ClassCastException if the value is not a symbol literal
* @see #createSymbol(SqlSymbol, SqlParserPos)
*/
public static SqlSampleSpec sampleValue(SqlNode node) {
return (SqlSampleSpec) ((SqlLiteral) node).value;
}
/**
* Extracts the string value from a string literal, a chain of string
* literals, or a CAST of a string literal.
*/
public static String stringValue(SqlNode node) {
if (node instanceof SqlLiteral) {
SqlLiteral literal = (SqlLiteral) node;
assert SqlTypeUtil.inCharFamily(literal.getTypeName());
return literal.toValue();
} else if (SqlUtil.isLiteralChain(node)) {
final SqlLiteral literal =
SqlLiteralChainOperator.concatenateOperands((SqlCall) node);
assert SqlTypeUtil.inCharFamily(literal.getTypeName());
return literal.toValue();
} else if (node instanceof SqlCall
&& ((SqlCall) node).getOperator() == SqlStdOperatorTable.CAST) {
return stringValue(((SqlCall) node).operand(0));
} else {
throw Util.newInternal("invalid string literal: " + node);
}
}
/**
* Converts a chained string literals into regular literals; returns regular
* literals unchanged.
*/
public static SqlLiteral unchain(SqlNode node) {
if (node instanceof SqlLiteral) {
return (SqlLiteral) node;
} else if (SqlUtil.isLiteralChain(node)) {
return SqlLiteralChainOperator.concatenateOperands((SqlCall) node);
} else {
throw Util.newInternal("invalid literal: " + node);
}
}
/**
* For calc program builder - value may be different than {@link #unparse}
* Typical values:
*
* <ul>
* <li>Hello, world!</li>
* <li>12.34</li>
* <li>{null}</li>
* <li>1969-04-29</li>
* </ul>
*
* @return string representation of the value
*/
public String toValue() {
if (value == null) {
return null;
}
switch (typeName) {
case CHAR:
// We want 'It''s superman!', not _ISO-8859-1'It''s superman!'
return ((NlsString) value).getValue();
default:
return value.toString();
}
}
public void validate(SqlValidator validator, SqlValidatorScope scope) {
validator.validateLiteral(this);
}
public <R> R accept(SqlVisitor<R> visitor) {
return visitor.visit(this);
}
public boolean equalsDeep(SqlNode node, boolean fail) {
if (!(node instanceof SqlLiteral)) {
assert !fail : this + "!=" + node;
return false;
}
SqlLiteral that = (SqlLiteral) node;
if (!this.equals(that)) {
assert !fail : this + "!=" + node;
return false;
}
return true;
}
public SqlMonotonicity getMonotonicity(SqlValidatorScope scope) {
return SqlMonotonicity.CONSTANT;
}
/**
* Creates a NULL literal.
*
* <p>There's no singleton constant for a NULL literal. Instead, nulls must
* be instantiated via createNull(), because different instances have
* different context-dependent types.
*/
public static SqlLiteral createNull(SqlParserPos pos) {
return new SqlLiteral(null, SqlTypeName.NULL, pos);
}
/**
* Creates a boolean literal.
*/
public static SqlLiteral createBoolean(
boolean b,
SqlParserPos pos) {
return b ? new SqlLiteral(Boolean.TRUE, SqlTypeName.BOOLEAN, pos)
: new SqlLiteral(Boolean.FALSE, SqlTypeName.BOOLEAN, pos);
}
public static SqlLiteral createUnknown(SqlParserPos pos) {
return new SqlLiteral(null, SqlTypeName.BOOLEAN, pos);
}
/**
* Creates a literal which represents a parser symbol, for example the
* <code>TRAILING</code> keyword in the call <code>Trim(TRAILING 'x' FROM
* 'Hello world!')</code>.
*
* @see #symbolValue()
*/
public static SqlLiteral createSymbol(
SqlLiteral.SqlSymbol o,
SqlParserPos pos) {
return new SqlLiteral(o, SqlTypeName.SYMBOL, pos);
}
/**
* Creates a literal which represents a sample specification.
*/
public static SqlLiteral createSample(
SqlSampleSpec sampleSpec,
SqlParserPos pos) {
return new SqlLiteral(sampleSpec, SqlTypeName.SYMBOL, pos);
}
public boolean equals(Object obj) {
if (!(obj instanceof SqlLiteral)) {
return false;
}
SqlLiteral that = (SqlLiteral) obj;
return Util.equal(value, that.value);
}
public int hashCode() {
return (value == null) ? 0 : value.hashCode();
}
/**
* Returns the integer value of this literal.
*
* @param exact Whether the value has to be exact. If true, and the literal
* is a fraction (e.g. 3.14), throws. If false, discards the
* fractional part of the value.
* @return Integer value of this literal
*/
public int intValue(boolean exact) {
switch (typeName) {
case DECIMAL:
case DOUBLE:
BigDecimal bd = (BigDecimal) value;
if (exact) {
try {
return bd.intValueExact();
} catch (ArithmeticException e) {
throw SqlUtil.newContextException(getParserPosition(),
RESOURCE.numberLiteralOutOfRange(bd.toString()));
}
} else {
return bd.intValue();
}
default:
throw Util.unexpected(typeName);
}
}
/**
* Returns the long value of this literal.
*
* @param exact Whether the value has to be exact. If true, and the literal
* is a fraction (e.g. 3.14), throws. If false, discards the
* fractional part of the value.
* @return Long value of this literal
*/
public long longValue(boolean exact) {
switch (typeName) {
case DECIMAL:
case DOUBLE:
BigDecimal bd = (BigDecimal) value;
if (exact) {
try {
return bd.longValueExact();
} catch (ArithmeticException e) {
throw SqlUtil.newContextException(getParserPosition(),
RESOURCE.numberLiteralOutOfRange(bd.toString()));
}
} else {
return bd.longValue();
}
default:
throw Util.unexpected(typeName);
}
}
/**
* Returns sign of value.
*
* @return -1, 0 or 1
*/
public int signum() {
return bigDecimalValue().compareTo(
BigDecimal.ZERO);
}
/**
* Returns a numeric literal's value as a {@link BigDecimal}.
*/
public BigDecimal bigDecimalValue() {
switch (typeName) {
case DECIMAL:
case DOUBLE:
return (BigDecimal) value;
default:
throw Util.unexpected(typeName);
}
}
public String getStringValue() {
return ((NlsString) value).getValue();
}
public void unparse(
SqlWriter writer,
int leftPrec,
int rightPrec) {
switch (typeName) {
case BOOLEAN:
writer.keyword(
value == null ? "UNKNOWN" : (Boolean) value ? "TRUE" : "FALSE");
break;
case NULL:
writer.keyword("NULL");
break;
case CHAR:
case DECIMAL:
case DOUBLE:
case BINARY:
// should be handled in subtype
throw Util.unexpected(typeName);
case SYMBOL:
if (value instanceof Enum) {
Enum enumVal = (Enum) value;
writer.keyword(enumVal.toString());
} else {
writer.keyword(String.valueOf(value));
}
break;
default:
writer.literal(value.toString());
}
}
public RelDataType createSqlType(RelDataTypeFactory typeFactory) {
BitString bitString;
switch (typeName) {
case NULL:
case BOOLEAN:
RelDataType ret = typeFactory.createSqlType(typeName);
ret = typeFactory.createTypeWithNullability(ret, null == value);
return ret;
case BINARY:
bitString = (BitString) value;
int bitCount = bitString.getBitCount();
return typeFactory.createSqlType(SqlTypeName.BINARY, bitCount / 8);
case CHAR:
NlsString string = (NlsString) value;
Charset charset = string.getCharset();
if (null == charset) {
charset = typeFactory.getDefaultCharset();
}
SqlCollation collation = string.getCollation();
if (null == collation) {
collation = SqlCollation.COERCIBLE;
}
RelDataType type =
typeFactory.createSqlType(
SqlTypeName.CHAR,
string.getValue().length());
type =
typeFactory.createTypeWithCharsetAndCollation(
type,
charset,
collation);
return type;
case INTERVAL_YEAR_MONTH:
case INTERVAL_DAY_TIME:
SqlIntervalLiteral.IntervalValue intervalValue =
(SqlIntervalLiteral.IntervalValue) value;
return typeFactory.createSqlIntervalType(
intervalValue.getIntervalQualifier());
case SYMBOL:
return typeFactory.createSqlType(SqlTypeName.SYMBOL);
case INTEGER: // handled in derived class
case TIME: // handled in derived class
case VARCHAR: // should never happen
case VARBINARY: // should never happen
default:
throw Util.needToImplement(toString() + ", operand=" + value);
}
}
public static SqlDateLiteral createDate(
Calendar calendar,
SqlParserPos pos) {
return new SqlDateLiteral(calendar, pos);
}
public static SqlTimestampLiteral createTimestamp(
Calendar calendar,
int precision,
SqlParserPos pos) {
return new SqlTimestampLiteral(calendar, precision, false, pos);
}
public static SqlTimeLiteral createTime(
Calendar calendar,
int precision,
SqlParserPos pos) {
return new SqlTimeLiteral(calendar, precision, false, pos);
}
/**
* Creates an interval literal.
*
* @param intervalStr input string of '1:23:04'
* @param intervalQualifier describes the interval type and precision
* @param pos Parser position
*/
public static SqlIntervalLiteral createInterval(
int sign,
String intervalStr,
SqlIntervalQualifier intervalQualifier,
SqlParserPos pos) {
SqlTypeName typeName =
intervalQualifier.isYearMonth() ? SqlTypeName.INTERVAL_YEAR_MONTH
: SqlTypeName.INTERVAL_DAY_TIME;
return new SqlIntervalLiteral(
sign,
intervalStr,
intervalQualifier,
typeName,
pos);
}
public static SqlNumericLiteral createNegative(
SqlNumericLiteral num,
SqlParserPos pos) {
return new SqlNumericLiteral(
((BigDecimal) num.getValue()).negate(),
num.getPrec(),
num.getScale(),
num.isExact(),
pos);
}
public static SqlNumericLiteral createExactNumeric(
String s,
SqlParserPos pos) {
BigDecimal value;
int prec;
int scale;
int i = s.indexOf('.');
if ((i >= 0) && ((s.length() - 1) != i)) {
value = SqlParserUtil.parseDecimal(s);
scale = s.length() - i - 1;
assert scale == value.scale() : s;
prec = s.length() - 1;
} else if ((i >= 0) && ((s.length() - 1) == i)) {
value = SqlParserUtil.parseInteger(s.substring(0, i));
scale = 0;
prec = s.length() - 1;
} else {
value = SqlParserUtil.parseInteger(s);
scale = 0;
prec = s.length();
}
return new SqlNumericLiteral(
value,
prec,
scale,
true,
pos);
}
public static SqlNumericLiteral createApproxNumeric(
String s,
SqlParserPos pos) {
BigDecimal value = SqlParserUtil.parseDecimal(s);
return new SqlNumericLiteral(value, null, null, false, pos);
}
/**
* Creates a literal like X'ABAB'. Although it matters when we derive a type
* for this beastie, we don't care at this point whether the number of
* hexits is odd or even.
*/
public static SqlBinaryStringLiteral createBinaryString(
String s,
SqlParserPos pos) {
BitString bits;
try {
bits = BitString.createFromHexString(s);
} catch (NumberFormatException e) {
throw SqlUtil.newContextException(pos,
RESOURCE.binaryLiteralInvalid());
}
return new SqlBinaryStringLiteral(bits, pos);
}
/**
* Creates a literal like X'ABAB' from an array of bytes.
*
* @param bytes Contents of binary literal
* @param pos Parser position
* @return Binary string literal
*/
public static SqlBinaryStringLiteral createBinaryString(
byte[] bytes,
SqlParserPos pos) {
BitString bits;
try {
bits = BitString.createFromBytes(bytes);
} catch (NumberFormatException e) {
throw SqlUtil.newContextException(pos, RESOURCE.binaryLiteralInvalid());
}
return new SqlBinaryStringLiteral(bits, pos);
}
/**
* Creates a string literal in the system character set.
*
* @param s a string (without the sql single quotes)
* @param pos Parser position
*/
public static SqlCharStringLiteral createCharString(
String s,
SqlParserPos pos) {
// UnsupportedCharsetException not possible
return createCharString(s, null, pos);
}
/**
* Creates a string literal, with optional character-set.
*
* @param s a string (without the sql single quotes)
* @param charSet character set name, null means take system default
* @param pos Parser position
* @return A string literal
* @throws UnsupportedCharsetException if charSet is not null but there is
* no character set with that name in this
* environment
*/
public static SqlCharStringLiteral createCharString(
String s,
String charSet,
SqlParserPos pos) {
NlsString slit = new NlsString(s, charSet, null);
return new SqlCharStringLiteral(slit, pos);
}
/**
* Transforms this literal (which must be of type character) into a new one
* in which 4-digit Unicode escape sequences have been replaced with the
* corresponding Unicode characters.
*
* @param unicodeEscapeChar escape character (e.g. backslash) for Unicode
* numeric sequences; 0 implies no transformation
* @return transformed literal
*/
public SqlLiteral unescapeUnicode(char unicodeEscapeChar) {
if (unicodeEscapeChar == 0) {
return this;
}
assert SqlTypeUtil.inCharFamily(getTypeName());
NlsString ns = (NlsString) value;
String s = ns.getValue();
StringBuilder sb = new StringBuilder();
int n = s.length();
for (int i = 0; i < n; ++i) {
char c = s.charAt(i);
if (c == unicodeEscapeChar) {
if (n > (i + 1)) {
if (s.charAt(i + 1) == unicodeEscapeChar) {
sb.append(unicodeEscapeChar);
++i;
continue;
}
}
if ((i + 5) > n) {
throw SqlUtil.newContextException(getParserPosition(),
RESOURCE.unicodeEscapeMalformed(i));
}
final String u = s.substring(i + 1, i + 5);
final int v;
try {
v = Integer.parseInt(u, 16);
} catch (NumberFormatException ex) {
throw SqlUtil.newContextException(getParserPosition(),
RESOURCE.unicodeEscapeMalformed(i));
}
sb.append((char) (v & 0xFFFF));
// skip hexits
i += 4;
} else {
sb.append(c);
}
}
ns = new NlsString(
sb.toString(),
ns.getCharsetName(),
ns.getCollation());
return new SqlCharStringLiteral(ns, getParserPosition());
}
//~ Inner Interfaces -------------------------------------------------------
/**
* A value must implement this interface if it is to be embedded as a
* SqlLiteral of type SYMBOL. If the class is an {@link Enum} it trivially
* implements this interface.
*
* <p>The {@link #toString()} method should return how the symbol should be
* unparsed, which is sometimes not the same as the enumerated value's name
* (e.g. "UNBOUNDED PRECEDING" versus "UnboundedPreceeding").
*/
public interface SqlSymbol {
String name();
int ordinal();
}
}
// End SqlLiteral.java