Skip to content

Commit 8ab2ef6

Browse files
committed
[CALCITE-6509] MongoAdapter throws 'Invalid JSON Number' exception if first character of field name is number
1 parent 9b4eff0 commit 8ab2ef6

File tree

3 files changed

+17
-24
lines changed

3 files changed

+17
-24
lines changed

mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoProject.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public MongoProject(RelOptCluster cluster, RelTraitSet traitSet,
7878
for (Pair<RexNode, String> pair : getNamedProjects()) {
7979
final String name = pair.right;
8080
final String expr = pair.left.accept(translator);
81-
items.add(expr.equals("'$" + name + "'")
81+
items.add(expr.equals("\"$" + name + "\"")
8282
? MongoRules.maybeQuote(name) + ": 1"
8383
: MongoRules.maybeQuote(name) + ": " + expr);
8484
}

mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoRules.java

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -103,25 +103,13 @@ static List<String> mongoFieldNames(final RelDataType rowType) {
103103
}
104104

105105
static String maybeQuote(String s) {
106-
if (!needsQuote(s)) {
107-
return s;
108-
}
106+
// MongoDB Extended JSON conforms to the JSON RFC. It is safe to use double quotes for
107+
// everything.
109108
return quote(s);
110109
}
111110

112111
static String quote(String s) {
113-
return "'" + s + "'"; // TODO: handle embedded quotes
114-
}
115-
116-
private static boolean needsQuote(String s) {
117-
for (int i = 0, n = s.length(); i < n; i++) {
118-
char c = s.charAt(i);
119-
if (!Character.isJavaIdentifierPart(c)
120-
|| c == '$') {
121-
return true;
122-
}
123-
}
124-
return false;
112+
return "\"" + s.replace("\"", "\\\"") + "\"";
125113
}
126114

127115
/** Translator from {@link RexNode} to strings in MongoDB's expression
@@ -178,7 +166,7 @@ protected RexToMongoTranslator(JavaTypeFactory typeFactory,
178166
@Override public String visitCall(RexCall call) {
179167
String name = isItem(call);
180168
if (name != null) {
181-
return "'$" + name + "'";
169+
return "\"$" + name + "\"";
182170
}
183171
final List<String> strings = visitList(call.operands);
184172
if (call.getKind() == SqlKind.CAST) {
@@ -230,7 +218,7 @@ protected RexToMongoTranslator(JavaTypeFactory typeFactory,
230218
}
231219

232220
private static String stripQuotes(String s) {
233-
return s.startsWith("'") && s.endsWith("'")
221+
return s.startsWith("\"") && s.endsWith("\"")
234222
? s.substring(1, s.length() - 1)
235223
: s;
236224
}

mongodb/src/test/java/org/apache/calcite/adapter/mongodb/MongoAdapterTest.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ public static void setUp() throws Exception {
119119
doc.put("ownerId", new BsonString("531e7789e4b0853ddb861313"));
120120
doc.put("arr", new BsonArray(Arrays.asList(new BsonString("a"), new BsonString("b"))));
121121
doc.put("binaryData", new BsonBinary("binaryData".getBytes(StandardCharsets.UTF_8)));
122+
doc.put("1_minute_aggregation", new BsonInt32(10));
122123
datatypes.insertOne(doc);
123124

124125
schema = new MongoSchema(database);
@@ -615,18 +616,22 @@ private CalciteAssert.AssertThat assertModel(URL url) {
615616
"{$limit: 5}"));
616617
}
617618

618-
@Disabled("broken; [CALCITE-2115] is logged to fix it")
619619
@Test void testProject() {
620620
assertModel(MODEL)
621621
.query("select state, city, 0 as zero from zips order by state, city")
622622
.limit(2)
623-
.returns("STATE=AK; CITY=AKHIOK; ZERO=0\n"
624-
+ "STATE=AK; CITY=AKIACHAK; ZERO=0\n")
623+
.returns("STATE=AK; CITY=ANCHORAGE; ZERO=0\n"
624+
+ "STATE=AK; CITY=FAIRBANKS; ZERO=0\n")
625625
.queryContains(
626626
mongoChecker(
627-
"{$project: {CITY: '$city', STATE: '$state'}}",
628-
"{$sort: {STATE: 1, CITY: 1}}",
629-
"{$project: {STATE: 1, CITY: 1, ZERO: {$literal: 0}}}"));
627+
"{$project: {STATE: '$state', CITY: '$city', ZERO: {$literal: 0}}}",
628+
"{$sort: {STATE: 1, CITY: 1}}"));
629+
630+
assertModel(MODEL)
631+
.query("select cast(_MAP['1_minute_aggregation'] as INT) as \"1_minute_aggregation\" "
632+
+ "from \"mongo_raw\".\"datatypes\"")
633+
.queryContains(
634+
mongoChecker("{$project: {'1_minute_aggregation': 1}}"));
630635
}
631636

632637
@Test void testFilter() {

0 commit comments

Comments
 (0)