|
53 | 53 | import org.apache.calcite.util.TimeString;
|
54 | 54 | import org.apache.calcite.util.Util;
|
55 | 55 | import org.apache.calcite.util.trace.CalciteTrace;
|
| 56 | +import org.bson.BsonDocument; |
56 | 57 | import org.bson.BsonType;
|
| 58 | +import org.bson.BsonValue; |
57 | 59 | import org.checkerframework.checker.nullness.qual.NonNull;
|
58 | 60 | import org.slf4j.Logger;
|
59 | 61 | import software.amazon.documentdb.jdbc.common.utilities.SqlError;
|
@@ -204,7 +206,7 @@ private static boolean needsQuote(final String s) {
|
204 | 206 | * @param fieldName The non-normalized string
|
205 | 207 | * @return The input string with '$' replaced by '_'
|
206 | 208 | */
|
207 |
| - protected static String getNormalizedIdentifier(final String fieldName) { |
| 209 | + static String getNormalizedIdentifier(final String fieldName) { |
208 | 210 | return fieldName.startsWith("$") ? "_" + fieldName.substring(1) : fieldName;
|
209 | 211 | }
|
210 | 212 |
|
@@ -757,6 +759,8 @@ private static Operand reformatObjectIdLiteral(
|
757 | 759 |
|
758 | 760 | private static class DateFunctionTranslator {
|
759 | 761 |
|
| 762 | + private static final String CURRENT_DATE = "CURRENT_DATE"; |
| 763 | + private static final String CURRENT_TIMESTAMP = "CURRENT_TIMESTAMP"; |
760 | 764 | private static final Map<TimeUnitRange, String> DATE_PART_OPERATORS =
|
761 | 765 | new HashMap<>();
|
762 | 766 | private static final Instant FIRST_DAY_OF_WEEK_AFTER_EPOCH =
|
@@ -786,9 +790,54 @@ private static Operand translateCurrentTimestamp(final Instant currentTime) {
|
786 | 790 | @SneakyThrows
|
787 | 791 | private static Operand translateDateAdd(final RexCall call, final List<Operand> strings) {
|
788 | 792 | verifySupportedDateAddType(call.getOperands().get(1));
|
| 793 | + |
| 794 | + // Is date addition between literals (including CURRENT_DATE)? |
| 795 | + final boolean isLiteralCandidate = isDateLiteralCandidate(call, strings); |
| 796 | + if (isLiteralCandidate) { |
| 797 | + // Perform in-memory calculation before sending to server. |
| 798 | + return getDateAddLiteralOperand(strings); |
| 799 | + } |
| 800 | + // Otherwise, perform addition on server. |
789 | 801 | return new Operand("{ \"$add\":" + "[" + Util.commaList(strings) + "]}");
|
790 | 802 | }
|
791 | 803 |
|
| 804 | + private static boolean isDateLiteralCandidate(final RexCall call, final List<Operand> strings) { |
| 805 | + final boolean allLiterals = call.getOperands().stream() |
| 806 | + .allMatch(op -> { |
| 807 | + final SqlKind opKind = op.getKind(); |
| 808 | + final String opName = op.toString(); |
| 809 | + return opKind == SqlKind.LITERAL |
| 810 | + || opName.equalsIgnoreCase(CURRENT_DATE) |
| 811 | + || opName.equalsIgnoreCase(CURRENT_TIMESTAMP); |
| 812 | + }); |
| 813 | + final boolean allHaveQueryValue = strings.stream().allMatch(op -> op.getQueryValue() != null); |
| 814 | + return allLiterals && allHaveQueryValue; |
| 815 | + } |
| 816 | + |
| 817 | + private static Operand getDateAddLiteralOperand(final List<Operand> strings) { |
| 818 | + final String queryValue0 = strings.get(0).getQueryValue(); |
| 819 | + final String queryValue1 = strings.get(1).getQueryValue(); |
| 820 | + final BsonDocument document0 = BsonDocument.parse("{field: " + queryValue0 + "}"); |
| 821 | + final BsonDocument document1 = BsonDocument.parse("{field: " + queryValue1 + "}"); |
| 822 | + |
| 823 | + long sum = 0L; |
| 824 | + for (BsonValue v : new BsonValue[]{document0.get("field"), document1.get("field")}) { |
| 825 | + switch (v.getBsonType()) { |
| 826 | + case DATE_TIME: |
| 827 | + sum += v.asDateTime().getValue(); |
| 828 | + break; |
| 829 | + case INT64: |
| 830 | + sum += v.asInt64().getValue(); |
| 831 | + break; |
| 832 | + default: |
| 833 | + throw new UnsupportedOperationException( |
| 834 | + "Unsupported data type '" + v.getBsonType().name() + "'"); |
| 835 | + } |
| 836 | + } |
| 837 | + final String query = "{\"$date\": {\"$numberLong\": \"" + sum + "\"}}"; |
| 838 | + return new Operand(query, query, true); |
| 839 | + } |
| 840 | + |
792 | 841 | private static void verifySupportedDateAddType(final RexNode node)
|
793 | 842 | throws SQLFeatureNotSupportedException {
|
794 | 843 | if (node.getType().getSqlTypeName() == SqlTypeName.INTERVAL_MONTH
|
@@ -1169,7 +1218,7 @@ private static class StringFunctionTranslator {
|
1169 | 1218 | new HashMap<>();
|
1170 | 1219 |
|
1171 | 1220 | static {
|
1172 |
| - STRING_OPERATORS.put(SqlStdOperatorTable.CONCAT, "$concat");; |
| 1221 | + STRING_OPERATORS.put(SqlStdOperatorTable.CONCAT, "$concat"); |
1173 | 1222 | STRING_OPERATORS.put(SqlStdOperatorTable.LOWER, "$toLower");
|
1174 | 1223 | STRING_OPERATORS.put(SqlStdOperatorTable.UPPER, "$toUpper");
|
1175 | 1224 | STRING_OPERATORS.put(SqlStdOperatorTable.CHAR_LENGTH, "$strLenCP");
|
@@ -1215,15 +1264,15 @@ private static Operand getMongoAggregateForPositionStringOperator(
|
1215 | 1264 | args.add("{" + STRING_OPERATORS.get(SqlStdOperatorTable.LOWER) + ":" + strings.get(1) + "}");
|
1216 | 1265 | args.add("{" + STRING_OPERATORS.get(SqlStdOperatorTable.LOWER) + ":" + strings.get(0) + "}");
|
1217 | 1266 | // Check if either string is null.
|
1218 |
| - operand.append("{\"$cond\": [" + RexToMongoTranslator.getNullCheckExpr(strings) + ", "); |
| 1267 | + operand.append("{\"$cond\": [").append(RexToMongoTranslator.getNullCheckExpr(strings)).append(", "); |
1219 | 1268 | // Add starting index if any.
|
1220 | 1269 | if (strings.size() == 3) {
|
1221 | 1270 | args.add("{\"$subtract\": [" + strings.get(2) + ", 1]}"); // Convert to 0-based.
|
1222 |
| - operand.append("{\"$cond\": [{\"$lte\": [" + strings.get(2) + ", 0]}, 0, "); // Check if 1-based index > 0. |
| 1271 | + operand.append("{\"$cond\": [{\"$lte\": [").append(strings.get(2)).append(", 0]}, 0, "); // Check if 1-based index > 0. |
1223 | 1272 | finish.append("]}");
|
1224 | 1273 | }
|
1225 | 1274 | // Convert 0-based index to 1-based.
|
1226 |
| - operand.append("{\"$add\": [{" + STRING_OPERATORS.get(SqlStdOperatorTable.POSITION) + ": [" + Util.commaList(args) + "]}, 1]}"); |
| 1275 | + operand.append("{\"$add\": [{").append(STRING_OPERATORS.get(SqlStdOperatorTable.POSITION)).append(": [").append(Util.commaList(args)).append("]}, 1]}"); |
1227 | 1276 | operand.append(finish);
|
1228 | 1277 | operand.append(", null ]}");
|
1229 | 1278 | // Return 1-based index when string is found.
|
|
0 commit comments