diff --git a/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoProject.java b/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoProject.java index ee0721fe4a92..658d20a72ed4 100644 --- a/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoProject.java +++ b/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoProject.java @@ -78,7 +78,7 @@ public MongoProject(RelOptCluster cluster, RelTraitSet traitSet, for (Pair pair : getNamedProjects()) { final String name = pair.right; final String expr = pair.left.accept(translator); - items.add(expr.equals("'$" + name + "'") + items.add(expr.equals("\"$" + name + "\"") ? MongoRules.maybeQuote(name) + ": 1" : MongoRules.maybeQuote(name) + ": " + expr); } diff --git a/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoRules.java b/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoRules.java index fa10035746c2..c7e7d226c748 100644 --- a/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoRules.java +++ b/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoRules.java @@ -103,25 +103,13 @@ static List mongoFieldNames(final RelDataType rowType) { } static String maybeQuote(String s) { - if (!needsQuote(s)) { - return s; - } + // MongoDB Extended JSON conforms to the JSON RFC. It is safe to use double quotes for + // everything. return quote(s); } static String quote(String s) { - return "'" + s + "'"; // TODO: handle embedded quotes - } - - private static boolean needsQuote(String s) { - for (int i = 0, n = s.length(); i < n; i++) { - char c = s.charAt(i); - if (!Character.isJavaIdentifierPart(c) - || c == '$') { - return true; - } - } - return false; + return "\"" + s.replace("\"", "\\\"") + "\""; } /** Translator from {@link RexNode} to strings in MongoDB's expression @@ -178,7 +166,7 @@ protected RexToMongoTranslator(JavaTypeFactory typeFactory, @Override public String visitCall(RexCall call) { String name = isItem(call); if (name != null) { - return "'$" + name + "'"; + return "\"$" + name + "\""; } final List strings = visitList(call.operands); if (call.getKind() == SqlKind.CAST) { @@ -230,7 +218,7 @@ protected RexToMongoTranslator(JavaTypeFactory typeFactory, } private static String stripQuotes(String s) { - return s.startsWith("'") && s.endsWith("'") + return s.startsWith("\"") && s.endsWith("\"") ? s.substring(1, s.length() - 1) : s; } diff --git a/mongodb/src/test/java/org/apache/calcite/adapter/mongodb/MongoAdapterTest.java b/mongodb/src/test/java/org/apache/calcite/adapter/mongodb/MongoAdapterTest.java index 368340969398..949c2d24f919 100644 --- a/mongodb/src/test/java/org/apache/calcite/adapter/mongodb/MongoAdapterTest.java +++ b/mongodb/src/test/java/org/apache/calcite/adapter/mongodb/MongoAdapterTest.java @@ -119,6 +119,7 @@ public static void setUp() throws Exception { doc.put("ownerId", new BsonString("531e7789e4b0853ddb861313")); doc.put("arr", new BsonArray(Arrays.asList(new BsonString("a"), new BsonString("b")))); doc.put("binaryData", new BsonBinary("binaryData".getBytes(StandardCharsets.UTF_8))); + doc.put("1_minute_aggregation", new BsonInt32(10)); datatypes.insertOne(doc); schema = new MongoSchema(database); @@ -615,18 +616,22 @@ private CalciteAssert.AssertThat assertModel(URL url) { "{$limit: 5}")); } - @Disabled("broken; [CALCITE-2115] is logged to fix it") @Test void testProject() { assertModel(MODEL) .query("select state, city, 0 as zero from zips order by state, city") .limit(2) - .returns("STATE=AK; CITY=AKHIOK; ZERO=0\n" - + "STATE=AK; CITY=AKIACHAK; ZERO=0\n") + .returns("STATE=AK; CITY=ANCHORAGE; ZERO=0\n" + + "STATE=AK; CITY=FAIRBANKS; ZERO=0\n") .queryContains( mongoChecker( - "{$project: {CITY: '$city', STATE: '$state'}}", - "{$sort: {STATE: 1, CITY: 1}}", - "{$project: {STATE: 1, CITY: 1, ZERO: {$literal: 0}}}")); + "{$project: {STATE: '$state', CITY: '$city', ZERO: {$literal: 0}}}", + "{$sort: {STATE: 1, CITY: 1}}")); + + assertModel(MODEL) + .query("select cast(_MAP['1_minute_aggregation'] as INT) as \"1_minute_aggregation\" " + + "from \"mongo_raw\".\"datatypes\"") + .queryContains( + mongoChecker("{$project: {'1_minute_aggregation': 1}}")); } @Test void testFilter() {