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

CBL-6207: Test multi-level unnest w/o index. #2137

Merged
merged 3 commits into from
Sep 18, 2024
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
12 changes: 12 additions & 0 deletions C/tests/c4QueryTest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,18 @@ N_WAY_TEST_CASE_METHOD(NestedQueryTest, "C4Query UNNEST objects", "[Query][C]")
WHERE: ['=', ['concat()', ['.shape.color'], ['to_string()',['.shape.size']]], 'red3']}"));
checkExplanation(withIndex);
CHECK(run() == (vector<string>{"3"}));

compileSelect(json5("{WHAT: [['.shape.color'], ['min()', ['.shape.size']]], \
FROM: [{as: 'doc'}, \
{as: 'shape', unnest: ['.doc.shapes']}],\
GROUP_BY: [['.shape.color']]}"));
checkExplanation(false); // even with index, this must do a scan
if ( withIndex )
CHECK(run2() == (vector<string>{"blue, 10", "cyan, 3", "green, 2", "red, 3", "white, 1", "yellow, 5"}));
else // Unnest without index is not working yet with group_by.
CHECK(run2()
== (vector<string>{"MISSING, MISSING", "MISSING, MISSING", "MISSING, MISSING", "MISSING, MISSING",
"MISSING, MISSING", "MISSING, MISSING"}));
}
}

Expand Down
218 changes: 218 additions & 0 deletions LiteCore/tests/QueryTest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2991,3 +2991,221 @@ TEST_CASE_METHOD(QueryTest, "Invalid collection names", "[Query]") {
}
}
}

namespace {
void AddDocWithJSON(KeyStore* store, slice docID, ExclusiveTransaction& t, const char* jsonBody) {
DataFileTestFixture::writeDoc(
*store, docID, DocumentFlags::kNone, t,
[=](Encoder& enc) {
impl::JSONConverter jc(enc);
if ( !jc.encodeJSON(slice(jsonBody)) ) {
enc.reset();
error(error::Fleece, jc.errorCode(), jc.errorMessage())._throw();
}
},
false);
t.commit();
}
} // anonymous namespace

N_WAY_TEST_CASE_METHOD(QueryTest, "Query with Unnest", "[Query]") {
const char* json = R"==(
{
"name":{"first":"Lue","last":"Laserna"},
"contacts":[
{
"type":"primary",
"address":{"street":"1 St","city":"San Pedro","state":"CA"},
"phones":[
{"type":"home","numbers":["310-123-4567","310-123-4568"]},
{"type":"mobile","numbers":["310-123-6789","310-222-1234"]}
],
"emails":["[email protected]","[email protected]"]
},
{
"type":"secondary",
"address":{"street":"5 St","city":"Santa Clara","state":"CA"},
"phones":[
{"type":"home","numbers":["650-123-4567","650-123-2120"]},
{"type":"mobile","numbers":["650-123-6789"]}
],
"emails":["[email protected]","[email protected]"]
}
],
"likes":["Soccer","Cat"]
}
)==";
{
ExclusiveTransaction t(store->dataFile());
AddDocWithJSON(store, "doc01"_sl, t, json);
}

// Case 1, Single level UNNEST
// ---------------------------
// SELECT name.first as name, c.address.city as city
// FROM profiles
// UNNEST contacts AS c

string queryStr = "{WHAT: [['AS', ['."s + collectionName + ".name.first'], 'name'],"
+ "['AS', ['.c.address.city'], 'city']], " + "FROM: [{COLLECTION: '" + collectionName + "'}, "
+ "{UNNEST: ['." + collectionName + ".contacts'], AS: 'c'}]}";
Retained<Query> query{store->compileQuery(json5(queryStr), QueryLanguage::kJSON)};
Retained<QueryEnumerator> e{query->createEnumerator()};
CHECK(e->next());
CHECK(e->columns()[0]->asString() == "Lue"_sl);
CHECK(e->columns()[1]->asString() == "San Pedro"_sl);
CHECK(e->next());
CHECK(e->columns()[0]->asString() == "Lue"_sl);
CHECK(e->columns()[1]->asString() == "Santa Clara"_sl);
CHECK(!e->next());

// Case 2, Multiple Single level UNNESTs
// -------------------------------------
// SELECT name.first as name, c.address.city as city, `like`
// FROM profiles
// UNNEST contacts AS c
// UNNEST likes AS `like`

queryStr = "{WHAT: [['AS', ['."s + collectionName + ".name.first'], 'name'],"
+ "['AS', ['.c.address.city'], 'city'], ['.like']], " + "FROM: [{COLLECTION: '" + collectionName + "'}, "
+ "{UNNEST: ['." + collectionName + ".contacts'], AS: 'c'}, " + "{UNNEST: ['." + collectionName
+ ".likes'], AS: 'like'}]}";
query = store->compileQuery(json5(queryStr), QueryLanguage::kJSON);
e = query->createEnumerator();
CHECK(e->next());
CHECK(e->columns()[0]->asString() == "Lue"_sl);
CHECK(e->columns()[1]->asString() == "San Pedro"_sl);
CHECK(e->columns()[2]->asString() == "Soccer"_sl);
CHECK(e->next());
CHECK(e->columns()[0]->asString() == "Lue"_sl);
CHECK(e->columns()[1]->asString() == "San Pedro"_sl);
CHECK(e->columns()[2]->asString() == "Cat"_sl);
CHECK(e->next());
CHECK(e->columns()[0]->asString() == "Lue"_sl);
CHECK(e->columns()[1]->asString() == "Santa Clara"_sl);
CHECK(e->columns()[2]->asString() == "Soccer"_sl);
CHECK(e->next());
CHECK(e->columns()[0]->asString() == "Lue"_sl);
CHECK(e->columns()[1]->asString() == "Santa Clara"_sl);
CHECK(e->columns()[2]->asString() == "Cat"_sl);
CHECK(!e->next());

// Case 3, 2-Level UNNEST
// ----------------------
// SELECT name.first as name, c.address.city as city, p.numbers as phone
// FROM profiles
// UNNEST contacts AS c
// UNNEST c.phones AS p

queryStr = "{WHAT: [['AS', ['."s + collectionName + ".name.first'], 'name'],"
+ "['AS', ['.c.address.city'], 'city'], ['AS', ['.p.numbers'], 'phone']], " + "FROM: [{COLLECTION: '"
+ collectionName + "'}, " + "{UNNEST: ['." + collectionName + ".contacts'], AS: 'c'}, "
+ "{UNNEST: ['.c.phones'], AS: 'p'}]}";
query = store->compileQuery(json5(queryStr), QueryLanguage::kJSON);
e = query->createEnumerator();
CHECK(e->next());
CHECK(e->columns()[0]->asString() == "Lue"_sl);
CHECK(e->columns()[1]->asString() == "San Pedro"_sl);
CHECK(e->columns()[2]->toJSONString() == "[\"310-123-4567\",\"310-123-4568\"]");
CHECK(e->next());
CHECK(e->columns()[0]->asString() == "Lue"_sl);
CHECK(e->columns()[1]->asString() == "San Pedro"_sl);
CHECK(e->columns()[2]->toJSONString() == "[\"310-123-6789\",\"310-222-1234\"]");
CHECK(e->next());
CHECK(e->columns()[0]->asString() == "Lue"_sl);
CHECK(e->columns()[1]->asString() == "Santa Clara"_sl);
CHECK(e->columns()[2]->toJSONString() == "[\"650-123-4567\",\"650-123-2120\"]");
CHECK(e->next());
CHECK(e->columns()[0]->asString() == "Lue"_sl);
CHECK(e->columns()[1]->asString() == "Santa Clara"_sl);
CHECK(e->columns()[2]->toJSONString() == "[\"650-123-6789\"]");
CHECK(!e->next());

// Case 4, 3-level UNNEST
// ----------------------
// SELECT name.first as name, c.address.city as city, p.type as `phone-type`, number
// FROM profiles
// UNNEST contacts AS c
// UNNEST c.phones AS p
// UNNEST p.numbers as number

queryStr = "{WHAT: [['AS', ['."s + collectionName + ".name.first'], 'name'], "
+ "['AS', ['.c.address.city'], 'city'], ['AS', ['.p.type'], 'phone-type'], " + "['.number']], "
+ "FROM: [{COLLECTION: '" + collectionName + "'}, " + "{UNNEST: ['." + collectionName
+ ".contacts'], AS: 'c'}, " + "{UNNEST: ['.c.phones'], AS: 'p'}, "
+ "{UNNEST: ['.p.numbers'], AS: 'number'}]}";
query = store->compileQuery(json5(queryStr), QueryLanguage::kJSON);
e = query->createEnumerator();
CHECK(e->next());
CHECK(e->columns()[0]->asString() == "Lue"_sl);
CHECK(e->columns()[1]->asString() == "San Pedro"_sl);
CHECK(e->columns()[2]->asString() == "home"_sl);
CHECK(e->columns()[3]->asString() == "310-123-4567"_sl);
CHECK(e->next());
CHECK(e->columns()[0]->asString() == "Lue"_sl);
CHECK(e->columns()[1]->asString() == "San Pedro"_sl);
CHECK(e->columns()[2]->asString() == "home"_sl);
CHECK(e->columns()[3]->asString() == "310-123-4568"_sl);
CHECK(e->next());
CHECK(e->columns()[0]->asString() == "Lue"_sl);
CHECK(e->columns()[1]->asString() == "San Pedro"_sl);
CHECK(e->columns()[2]->asString() == "mobile"_sl);
CHECK(e->columns()[3]->asString() == "310-123-6789"_sl);
CHECK(e->next());
CHECK(e->columns()[0]->asString() == "Lue"_sl);
CHECK(e->columns()[1]->asString() == "San Pedro"_sl);
CHECK(e->columns()[2]->asString() == "mobile"_sl);
CHECK(e->columns()[3]->asString() == "310-222-1234"_sl);
CHECK(e->next());
CHECK(e->columns()[0]->asString() == "Lue"_sl);
CHECK(e->columns()[1]->asString() == "Santa Clara"_sl);
CHECK(e->columns()[2]->asString() == "home"_sl);
CHECK(e->columns()[3]->asString() == "650-123-4567"_sl);
CHECK(e->next());
CHECK(e->columns()[0]->asString() == "Lue"_sl);
CHECK(e->columns()[1]->asString() == "Santa Clara"_sl);
CHECK(e->columns()[2]->asString() == "home"_sl);
CHECK(e->columns()[3]->asString() == "650-123-2120"_sl);
CHECK(e->next());
CHECK(e->columns()[0]->asString() == "Lue"_sl);
CHECK(e->columns()[1]->asString() == "Santa Clara"_sl);
CHECK(e->columns()[2]->asString() == "mobile"_sl);
CHECK(e->columns()[3]->asString() == "650-123-6789"_sl);
CHECK(!e->next());

// Test 5, Unnest with Where & order by
// -------------------------
// SELECT name.first as name, c.address.city as city, p.numbers[0] as phone
// FROM profiles
// UNNEST contacts AS c
// UNNEST c.phones AS p
// WHERE p.type = "mobile"
// ORDER BY c.address.city DESC

queryStr = "{WHAT: [['AS', ['."s + collectionName + ".name.first'], 'name'],"
+ "['AS', ['.c.address.city'], 'city'], ['AS', ['.p.numbers[0]'], 'phone']], " + "FROM: [{COLLECTION: '"
+ collectionName + "'}, " + "{UNNEST: ['." + collectionName + ".contacts'], AS: 'c'}, "
+ "{UNNEST: ['.c.phones'], AS: 'p'}], " + "WHERE: ['=', ['.p.type'], 'mobile'], "
+ "ORDER_BY: [['DESC', ['.c.address.city']]]}";
query = store->compileQuery(json5(queryStr), QueryLanguage::kJSON);
e = query->createEnumerator();
CHECK(e->next());
CHECK(e->columns()[0]->asString() == "Lue"_sl);
CHECK(e->columns()[1]->asString() == "Santa Clara"_sl);
CHECK(e->columns()[2]->asString() == "650-123-6789");
CHECK(e->next());
CHECK(e->columns()[0]->asString() == "Lue"_sl);
CHECK(e->columns()[1]->asString() == "San Pedro"_sl);
CHECK(e->columns()[2]->asString() == "310-123-6789");
CHECK(!e->next());

// Test 6, Unnest with Group-by
// ----------------------------
// SELECT DISTINCT c.address.state as state, count(c.address.city) as num
// FROM profiles
// UNNEST contacts AS c
// GROUP BY c.address.state, c.address.city
//
// "Group By" does not work yet with pure virtual table, fl_each.
// c.f. test "C4Query UNNEST objects" in C4QueryTest.cc
}
Loading