Skip to content
Merged
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 @@ -110,7 +110,9 @@ static String maybeQuote(String s) {
}

static String quote(String s) {
return "'" + s + "'"; // TODO: handle embedded quotes
// Escape backslash and the single-quote delimiter so that s cannot break
// out of the quoted token when the string is parsed by BsonDocument.parse.
return "'" + s.replace("\\", "\\\\").replace("'", "\\'") + "'";
}

private static boolean needsQuote(String s) {
Expand Down Expand Up @@ -181,7 +183,7 @@ protected RexToMongoTranslator(JavaTypeFactory typeFactory,
@Override public String visitCall(RexCall call) {
String name = isItem(call);
if (name != null) {
return "'$" + name + "'";
return quote("$" + name);
}
final List<String> strings = visitList(call.operands);
if (call.getKind() == SqlKind.CAST) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ public class MongoAdapterTest implements SchemaFactory {
/** Number of records in local file. */
protected static final int ZIPS_SIZE = 149;

/** Field of the "datatypes" collection whose name contains a single quote
* and characters that would be pipeline syntax if it were not escaped. */
private static final String QUOTED_FIELD = "x', injected: {$literal: 1}, y: 'z";

/** Field of the "datatypes" collection whose name ends with a backslash. */
private static final String BACKSLASH_FIELD = "a\\";

@RegisterExtension
public static final MongoDatabasePolicy POLICY = MongoDatabasePolicy.create();

Expand Down Expand Up @@ -119,6 +126,8 @@ 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(QUOTED_FIELD, new BsonString("quoted"));
doc.put(BACKSLASH_FIELD, new BsonString("backslash"));
datatypes.insertOne(doc);

schema = new MongoSchema(database);
Expand Down Expand Up @@ -743,6 +752,30 @@ private void checkPredicate(int expected, String q) {
.returnsUnordered("EXPR$0=[a, b]");
}

/** A field name that contains a single quote or a backslash must not be able
* to break out of the quoted token in the generated pipeline and add stage
* fields of its own.
*
* <p>The expected values were validated against a real MongoDB instance:
* without the escaping the first query parses as
* {@code {$project: {C: '$x', injected: {$literal: 1}, y: 'z'}}} and the
* injected field breaks the pipeline, so the query fails. */
@Test void testItemKeyWithEmbeddedQuote() {
assertModel(MODEL)
.query("select cast(_MAP['x'', injected: {$literal: 1}, y: ''z'] as varchar) as c\n"
+ "from \"mongo_raw\".\"datatypes\"")
.returnsUnordered("C=quoted")
.queryContains(
mongoChecker("{$project: {C: '$x\\', injected: {$literal: 1}, y: \\'z'}}"));

assertModel(MODEL)
.query("select cast(_MAP['a\\'] as varchar) as c\n"
+ "from \"mongo_raw\".\"datatypes\"")
.returnsUnordered("C=backslash")
.queryContains(
mongoChecker("{$project: {C: '$a\\\\'}}"));
}

/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-665">[CALCITE-665]
* ClassCastException in MongoDB adapter</a>. */
Expand Down
Loading