Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sample change to fix the issue of +TZ causing oracle sql exceptions #56

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Deque;
Expand All @@ -19,6 +19,7 @@
import com.force.formula.FormulaProperties;
import com.force.formula.FormulaRuntimeContext;
import com.force.formula.impl.FormulaAST;
import com.force.formula.impl.FormulaSqlHooks;
import com.force.formula.impl.IllegalArgumentTypeException;
import com.force.formula.impl.JsValue;
import com.force.formula.impl.TableAliasRegistry;
Expand Down Expand Up @@ -56,17 +57,19 @@ public SQLPair getSQL(FormulaAST node, FormulaContext context, String[] args, St
sql = args[0];
guard = SQLPair.generateGuard(guards, null);
} else {
sql = String.format(getSqlHooks(context).sqlToTimestampIso(), args[0]);
FormulaSqlHooks hooks = getSqlHooks(context);
sql = String.format(hooks.sqlToTimestampIso(), args[0]);

FormulaAST child = (FormulaAST)node.getFirstChild();
if (child != null && child.isLiteral() && child.getDataType() == String.class) {
if (OperatorDatetimeValueFormulaCommand.isValidDateTime(ConstantString.getStringValue(child, true))) {
boolean noExtraChars = !hooks.isPostgresStyle(); // Postgres works like JDK, so allow it.
if (OperatorDatetimeValueFormulaCommand.isValidDateTime(ConstantString.getStringValue(child, true), noExtraChars)) {
// no guard needed
guard = SQLPair.generateGuard(guards, null);
} else {
// we know it's false
guard = SQLPair.generateGuard(guards, "0=0");
sql = "NULL";
sql = hooks.sqlNullToDate();
}
} else {
// Guard protects against malformed dates as strings
Expand Down Expand Up @@ -133,7 +136,7 @@ public void execute(FormulaRuntimeContext context, Deque<Object> stack) throws F
value = new FormulaDateTime((Date)input);
} else {
try {
value = parseDateTime(checkStringType(input));
value = parseDateTime(checkStringType(input), false);
} catch (FormulaDateException ex) {
FormulaEngine.getHooks().handleFormulaDateException(ex);
}
Expand All @@ -143,30 +146,45 @@ public void execute(FormulaRuntimeContext context, Deque<Object> stack) throws F
stack.push(value);
}

protected static boolean isValidDateTime(String datetime) {
/**
* @return whether datetime is a valid value
* @param datetime the string of the format "yyyy-MM-dd HH:mm:ss"
* @param forSqlGeneration whether to be "strict" and only allow whitespace after parsing for sql generation
*/
protected static boolean isValidDateTime(String datetime, boolean forSqlGeneration) {
try {
parseDateTime(datetime);
parseDateTime(datetime, forSqlGeneration);
return true;
} catch (FormulaDateException x) {
return false;
}
}

private static Pattern DATE_PATTERN = Pattern.compile("\\d{4}-.*");
protected static FormulaDateTime parseDateTime(String input) throws FormulaDateException {
/**
* @param input the date time to parse
* @param forSqlGeneration means that only whitespace is allowed at the end of the date format string, as is required for oracle
* @return the DateTime of the parsed value
* @throws FormulaDateException
*/
protected static FormulaDateTime parseDateTime(String input, boolean forSqlGeneration) throws FormulaDateException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
dateFormat.setLenient(false);
dateFormat.setTimeZone(BaseLocalizer.GMT_TZ);
try {
// Do a pre-check for 4-digit year (setLenient does not require this)
if (!DATE_PATTERN.matcher(input).matches()) {
throw new FormulaDateException("Invalid year for DATEVALUE function");
}
return new FormulaDateTime(dateFormat.parse(input));

// Do a pre-check for 4-digit year (setLenient does not require this)
if (!DATE_PATTERN.matcher(input).matches()) {
throw new FormulaDateException("Invalid year for DATEVALUE function");
}
catch (ParseException x) {
throw new FormulaDateException(x);
ParsePosition p = new ParsePosition(0);
Date ret = dateFormat.parse(input, p);
// JDK's Date Format (and postgres) ignores characters past the date format, but Oracle and javascript do not.
// So for non-literal values, we don't allow extra characters, but for literal values we do, but only
// in Java. We don't know if we're generating SQL or JS at this point, so we allow it for backwards compatibilty.
if (ret == null || p.getErrorIndex() != -1 || (forSqlGeneration && !input.substring(p.getIndex()).isBlank())) {
throw new FormulaDateException("Invalid date format: " + input);
}
return new FormulaDateTime(ret);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
<testCase name="testDateTimeValueWithInvalidString">
<testInstance formula="DATETIMEVALUE(&quot;sample &quot;)" returntype="DateOnly">
<SqlOutput nullAsNull="true">
<Sql>NULL</Sql>
<Sql>CAST(NULL AS DATE)</Sql>
<Guard>0=0</Guard>
</SqlOutput>
<SqlOutput nullAsNull="false">
<Sql>NULL</Sql>
<Sql>CAST(NULL AS DATE)</Sql>
<Guard>0=0</Guard>
</SqlOutput>
<JsOutput highPrec="true" nullAsNull="false">new Date(&quot;sample &quot; + ' GMT')</JsOutput>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<testCase name="testDateTimeValueWithTimeZone">
<testInstance formula="DATETIMEVALUE(&quot;2021-08-12 03:26:09+1100&quot;)" returntype="DateOnly">
<SqlOutput nullAsNull="true">
<Sql>TO_TIMESTAMP('2021-08-12 03:26:09+1100', 'YYYY-MM-DD HH24:MI:SS')</Sql>
<Guard>null</Guard>
</SqlOutput>
<SqlOutput nullAsNull="false">
<Sql>TO_TIMESTAMP('2021-08-12 03:26:09+1100', 'YYYY-MM-DD HH24:MI:SS')</Sql>
<Guard>null</Guard>
</SqlOutput>
<JsOutput highPrec="true" nullAsNull="false">new Date(&quot;2021-08-12 03:26:09+1100&quot; + ' GMT')</JsOutput>
<JsOutput highPrec="true" nullAsNull="true">new Date(&quot;2021-08-12 03:26:09+1100&quot; + ' GMT')</JsOutput>
<JsOutput highPrec="false" nullAsNull="false">new Date(&quot;2021-08-12 03:26:09+1100&quot; + ' GMT')</JsOutput>
<JsOutput highPrec="false" nullAsNull="true">new Date(&quot;2021-08-12 03:26:09+1100&quot; + ' GMT')</JsOutput>
<result>
<!-- Test Case results don't match: Javascript had an error when no other did: should be Thu Aug 12 03:26:09 GMT 2021 but was Error: Cannot convert 'NaN'(language: Java, type: java.lang.Double) to Java type 'long' using Value.asLong(): Invalid or lossy primitive coercion. You can ensure that the value can be converted using Value.fitsInLong(). -->
<inputvalues>No data</inputvalues>
<formula>Thu Aug 12 03:26:09 GMT 2021</formula>
<sql>2021-08-12 03:26:09.0</sql>
<javascript>Error: Cannot convert 'NaN'(language: Java, type: java.lang.Double) to Java type 'long' using Value.asLong(): Invalid or lossy primitive coercion. You can ensure that the value can be converted using Value.fitsInLong().</javascript>
<javascriptLp>Error: Cannot convert 'NaN'(language: Java, type: java.lang.Double) to Java type 'long' using Value.asLong(): Invalid or lossy primitive coercion. You can ensure that the value can be converted using Value.fitsInLong().</javascriptLp>
<formulaNullAsNull>Thu Aug 12 03:26:09 GMT 2021</formulaNullAsNull>
<sqlNullAsNull>2021-08-12 03:26:09.0</sqlNullAsNull>
<javascriptNullAsNull>Error: Cannot convert 'NaN'(language: Java, type: java.lang.Double) to Java type 'long' using Value.asLong(): Invalid or lossy primitive coercion. You can ensure that the value can be converted using Value.fitsInLong().</javascriptNullAsNull>
<javascriptLpNullAsNull>Error: Cannot convert 'NaN'(language: Java, type: java.lang.Double) to Java type 'long' using Value.asLong(): Invalid or lossy primitive coercion. You can ensure that the value can be converted using Value.fitsInLong().</javascriptLpNullAsNull>
</result>
</testInstance>
</testCase>
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
<testCase name="testIfErrorDateTimeValueWithInvalidString">
<testInstance formula="IFERROR(DATETIMEVALUE(&quot;sample &quot;),DATETIMEVALUE(&quot;2005-11-15 17:00:00 &quot;))" returntype="DateOnly">
<SqlOutput nullAsNull="true">
<Sql>CASE WHEN 0=0 THEN TO_TIMESTAMP('2005-11-15 17:00:00 ', 'YYYY-MM-DD HH24:MI:SS') ELSE NULL END</Sql>
<Sql>CASE WHEN 0=0 THEN TO_TIMESTAMP('2005-11-15 17:00:00 ', 'YYYY-MM-DD HH24:MI:SS') ELSE CAST(NULL AS DATE) END</Sql>
<Guard>null</Guard>
</SqlOutput>
<SqlOutput nullAsNull="false">
<Sql>CASE WHEN 0=0 THEN TO_TIMESTAMP('2005-11-15 17:00:00 ', 'YYYY-MM-DD HH24:MI:SS') ELSE NULL END</Sql>
<Sql>CASE WHEN 0=0 THEN TO_TIMESTAMP('2005-11-15 17:00:00 ', 'YYYY-MM-DD HH24:MI:SS') ELSE CAST(NULL AS DATE) END</Sql>
<Guard>null</Guard>
</SqlOutput>
<JsOutput highPrec="true" nullAsNull="false">(Object.prototype.toString.call(new Date(&quot;sample &quot; + ' GMT')) !== '[object Date]') || isNaN(new Date(&quot;sample &quot; + ' GMT').getTime())?new Date(&quot;2005-11-15 17:00:00 &quot; + ' GMT'):new Date(&quot;sample &quot; + ' GMT')</JsOutput>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ author: Srikanth Yendluri/Doug Chasman
labelName="testDateTimeValueWithValidString" dataType="DateOnly" eval="formula,template"
scale="2" precision="12"
code="DATETIMEVALUE(&quot;2005-11-15 17:00:00 &quot;)">
</testcase>
<testcase name="testDateTimeValueWithTimeZone" devName="testDateTimeValueWithTimeZone"
whyIgnoreSql="oracle:Backwards Compatibility with extra characters in format,mysql: same,mssql: same"
whyIgnoreJs="Backwards Compatibility with extra characters in format"
labelName="testDateTimeValueWithTimeZone" dataType="DateOnly" eval="formula,template"
scale="2" precision="12"
code="DATETIMEVALUE(&quot;2021-08-12 03:26:09+1100&quot;)">
</testcase>
<testcase name="testDateTimeValueWithValidInValid" devName="testDateTimeValueWithValidInValid"
labelName="testDateTimeValueWithValidInValid" dataType="DateOnly" eval="formula,template" dataFile="functiondatevalue-dateandtime"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
<testCase name="testDateTimeValueWithInvalidString">
<testInstance formula="DATETIMEVALUE(&quot;sample &quot;)" returntype="DateOnly">
<SqlOutput nullAsNull="true">
<Sql>NULL</Sql>
<Sql>CAST(NULL AS DATE)</Sql>
<Guard>0=0</Guard>
</SqlOutput>
<SqlOutput nullAsNull="false">
<Sql>NULL</Sql>
<Sql>CAST(NULL AS DATE)</Sql>
<Guard>0=0</Guard>
</SqlOutput>
<result>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<testCase name="testDateTimeValueWithTimeZone">
<testInstance formula="DATETIMEVALUE(&quot;2021-08-12 03:26:09+1100&quot;)" returntype="DateOnly">
<SqlOutput nullAsNull="true">
<Sql>CAST(NULL AS DATE)</Sql>
<Guard>0=0</Guard>
</SqlOutput>
<SqlOutput nullAsNull="false">
<Sql>CAST(NULL AS DATE)</Sql>
<Guard>0=0</Guard>
</SqlOutput>
<result>
<inputvalues>No data</inputvalues>
<formula>Thu Aug 12 03:26:09 GMT 2021</formula>
<sql>null</sql>
<formulaNullAsNull>Thu Aug 12 03:26:09 GMT 2021</formulaNullAsNull>
<sqlNullAsNull>null</sqlNullAsNull>
</result>
</testInstance>
</testCase>
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
<testCase name="testIfErrorDateTimeValueWithInvalidString">
<testInstance formula="IFERROR(DATETIMEVALUE(&quot;sample &quot;),DATETIMEVALUE(&quot;2005-11-15 17:00:00 &quot;))" returntype="DateOnly">
<SqlOutput nullAsNull="true">
<Sql>CASE WHEN 0=0 THEN TIMESTAMP('2005-11-15 17:00:00 ') ELSE NULL END</Sql>
<Sql>CASE WHEN 0=0 THEN TIMESTAMP('2005-11-15 17:00:00 ') ELSE CAST(NULL AS DATE) END</Sql>
<Guard>null</Guard>
</SqlOutput>
<SqlOutput nullAsNull="false">
<Sql>CASE WHEN 0=0 THEN TIMESTAMP('2005-11-15 17:00:00 ') ELSE NULL END</Sql>
<Sql>CASE WHEN 0=0 THEN TIMESTAMP('2005-11-15 17:00:00 ') ELSE CAST(NULL AS DATE) END</Sql>
<Guard>null</Guard>
</SqlOutput>
<result>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
<testCase name="testDateTimeValueWithInvalidString">
<testInstance formula="DATETIMEVALUE(&quot;sample &quot;)" returntype="DateOnly">
<SqlOutput nullAsNull="true">
<Sql>NULL</Sql>
<Sql>TO_DATE(NULL)</Sql>
<Guard>0=0</Guard>
</SqlOutput>
<SqlOutput nullAsNull="false">
<Sql>NULL</Sql>
<Sql>TO_DATE(NULL)</Sql>
<Guard>0=0</Guard>
</SqlOutput>
<result>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<testCase name="testDateTimeValueWithTimeZone">
<testInstance formula="DATETIMEVALUE(&quot;2021-08-12 03:26:09+1100&quot;)" returntype="DateOnly">
<SqlOutput nullAsNull="true">
<Sql>TO_DATE(NULL)</Sql>
<Guard>0=0</Guard>
</SqlOutput>
<SqlOutput nullAsNull="false">
<Sql>TO_DATE(NULL)</Sql>
<Guard>0=0</Guard>
</SqlOutput>
<result>
<inputvalues>No data</inputvalues>
<formula>Thu Aug 12 03:26:09 GMT 2021</formula>
<sql>null</sql>
<formulaNullAsNull>Thu Aug 12 03:26:09 GMT 2021</formulaNullAsNull>
<sqlNullAsNull>null</sqlNullAsNull>
</result>
</testInstance>
</testCase>
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
<testCase name="testIfErrorDateTimeValueWithInvalidString">
<testInstance formula="IFERROR(DATETIMEVALUE(&quot;sample &quot;),DATETIMEVALUE(&quot;2005-11-15 17:00:00 &quot;))" returntype="DateOnly">
<SqlOutput nullAsNull="true">
<Sql>CASE WHEN 0=0 THEN TO_DATE('2005-11-15 17:00:00 ', 'YYYY-MM-DD HH24:MI:SS') ELSE NULL END</Sql>
<Sql>CASE WHEN 0=0 THEN TO_DATE('2005-11-15 17:00:00 ', 'YYYY-MM-DD HH24:MI:SS') ELSE TO_DATE(NULL) END</Sql>
<Guard>null</Guard>
</SqlOutput>
<SqlOutput nullAsNull="false">
<Sql>CASE WHEN 0=0 THEN TO_DATE('2005-11-15 17:00:00 ', 'YYYY-MM-DD HH24:MI:SS') ELSE NULL END</Sql>
<Sql>CASE WHEN 0=0 THEN TO_DATE('2005-11-15 17:00:00 ', 'YYYY-MM-DD HH24:MI:SS') ELSE TO_DATE(NULL) END</Sql>
<Guard>null</Guard>
</SqlOutput>
<result>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@
<testCase name="testDateTimeValueWithInvalidString">
<testInstance formula="DATETIMEVALUE(&quot;sample &quot;)" returntype="DateOnly">
<SqlOutput nullAsNull="true">
<Sql>NULL</Sql>
<Sql>CONVERT(DATETIME,NULL)</Sql>
<Guard>0=0</Guard>
</SqlOutput>
<SqlOutput nullAsNull="false">
<Sql>NULL</Sql>
<Sql>CONVERT(DATETIME,NULL)</Sql>
<Guard>0=0</Guard>
</SqlOutput>
<result>
<inputvalues>No data</inputvalues>
<formula>Error: com.force.formula.FormulaDateException</formula>
<sql>Error: At least one of the result expressions in a CASE specification must be an expression other than the NULL constant.</sql>
<sql>null</sql>
<formulaNullAsNull>Error: com.force.formula.FormulaDateException</formulaNullAsNull>
<sqlNullAsNull>Error: At least one of the result expressions in a CASE specification must be an expression other than the NULL constant.</sqlNullAsNull>
<sqlNullAsNull>null</sqlNullAsNull>
</result>
</testInstance>
</testCase>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<testCase name="testDateTimeValueWithTimeZone">
<testInstance formula="DATETIMEVALUE(&quot;2021-08-12 03:26:09+1100&quot;)" returntype="DateOnly">
<SqlOutput nullAsNull="true">
<Sql>CONVERT(DATETIME,NULL)</Sql>
<Guard>0=0</Guard>
</SqlOutput>
<SqlOutput nullAsNull="false">
<Sql>CONVERT(DATETIME,NULL)</Sql>
<Guard>0=0</Guard>
</SqlOutput>
<result>
<inputvalues>No data</inputvalues>
<formula>Thu Aug 12 03:26:09 GMT 2021</formula>
<sql>null</sql>
<formulaNullAsNull>Thu Aug 12 03:26:09 GMT 2021</formulaNullAsNull>
<sqlNullAsNull>null</sqlNullAsNull>
</result>
</testInstance>
</testCase>
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
<testCase name="testIfErrorDateTimeValueWithInvalidString">
<testInstance formula="IFERROR(DATETIMEVALUE(&quot;sample &quot;),DATETIMEVALUE(&quot;2005-11-15 17:00:00 &quot;))" returntype="DateOnly">
<SqlOutput nullAsNull="true">
<Sql>CASE WHEN 0=0 THEN CONVERT(DATETIME, '2005-11-15 17:00:00 ') ELSE NULL END</Sql>
<Sql>CASE WHEN 0=0 THEN CONVERT(DATETIME, '2005-11-15 17:00:00 ') ELSE CONVERT(DATETIME,NULL) END</Sql>
<Guard>null</Guard>
</SqlOutput>
<SqlOutput nullAsNull="false">
<Sql>CASE WHEN 0=0 THEN CONVERT(DATETIME, '2005-11-15 17:00:00 ') ELSE NULL END</Sql>
<Sql>CASE WHEN 0=0 THEN CONVERT(DATETIME, '2005-11-15 17:00:00 ') ELSE CONVERT(DATETIME,NULL) END</Sql>
<Guard>null</Guard>
</SqlOutput>
<result>
Expand Down