Skip to content
Open
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 @@ -8,6 +8,8 @@
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_ACCOUNT;
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_BANK;
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_BANK_WITH_NULL_VALUES;
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_CASCADED_NESTED;
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_DEEP_NESTED;
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_LOGS;
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_NESTED_SIMPLE;
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_OTEL_LOGS;
Expand All @@ -24,6 +26,7 @@
import org.junit.Ignore;
import org.junit.Test;
import org.opensearch.sql.common.setting.Settings;
import org.opensearch.sql.common.utils.StringUtils;
import org.opensearch.sql.ppl.ExplainIT;

public class CalciteExplainIT extends ExplainIT {
Expand All @@ -42,6 +45,8 @@ public void init() throws Exception {
loadIndex(Index.WORKER);
loadIndex(Index.WORK_INFORMATION);
loadIndex(Index.WEBLOG);
loadIndex(Index.DEEP_NESTED);
loadIndex(Index.CASCADED_NESTED);
}

@Override
Expand Down Expand Up @@ -1953,4 +1958,67 @@ public void testDedupTextTypeNotPushdown() throws IOException {
assertYamlEqualsIgnoreId(
expected, explainQueryYaml(String.format("source=%s | dedup email", TEST_INDEX_BANK)));
}

@Test
public void testFilterOnComputedNestedFields() throws IOException {
assertYamlEqualsIgnoreId(
loadExpectedPlan("filter_computed_nested.yaml"),
explainQueryYaml(
StringUtils.format(
"source=%s | eval proj_name_len=length(projects.name) | fields projects.name,"
+ " proj_name_len | where proj_name_len > 29",
TEST_INDEX_DEEP_NESTED)));
}

@Test
public void testFilterOnNestedAndRootFields() throws IOException {
assertYamlEqualsIgnoreId(
loadExpectedPlan("filter_root_and_nested.yaml"),
// city is not in a nested object
explainQueryYaml(
StringUtils.format(
"source=%s | where city.name = 'Seattle' and length(projects.name) > 29",
TEST_INDEX_DEEP_NESTED)));
}

@Test
public void testFilterOnNestedFields() throws IOException {
assertYamlEqualsIgnoreId(
loadExpectedPlan("filter_nested_term.yaml"),
// address is a nested object
explainQueryYaml(
StringUtils.format(
"source=%s | where address.city = 'New york city'", TEST_INDEX_NESTED_SIMPLE)));

assertYamlEqualsIgnoreId(
loadExpectedPlan("filter_nested_terms.yaml"),
explainQueryYaml(
StringUtils.format(
"source=%s | where address.city in ('Miami', 'san diego')",
TEST_INDEX_NESTED_SIMPLE)));
}

@Test
public void testFilterOnMultipleCascadedNestedFields() throws IOException {
// 1. Access two different hierarchies of nested fields, one at author.books.reviews, another at
// author.books
// 2. One is pushed as nested range query, another is pushed as nested filter query.
assertYamlEqualsIgnoreId(
loadExpectedPlan("filter_multiple_nested_cascaded_range.yaml"),
explainQueryYaml(
StringUtils.format(
"source=%s | where author.books.reviews.rating >=4 and author.books.reviews.rating"
+ " < 6 and author.books.title = 'The Shining'",
TEST_INDEX_CASCADED_NESTED)));
}

@Test
public void testAggFilterOnNestedFields() throws IOException {
assertYamlEqualsIgnoreId(
loadExpectedPlan("agg_filter_nested.yaml"),
explainQueryYaml(
StringUtils.format(
"source=%s | stats count(eval(author.name < 'K')) as george_and_jk",
TEST_INDEX_CASCADED_NESTED)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,151 @@

package org.opensearch.sql.calcite.remote;

import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_CASCADED_NESTED;
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_DEEP_NESTED;
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_NESTED_SIMPLE;
import static org.opensearch.sql.util.MatcherUtils.rows;
import static org.opensearch.sql.util.MatcherUtils.schema;
import static org.opensearch.sql.util.MatcherUtils.verifyDataRows;
import static org.opensearch.sql.util.MatcherUtils.verifyErrorMessageContains;
import static org.opensearch.sql.util.MatcherUtils.verifySchema;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.json.JSONObject;
import org.junit.Test;
import org.opensearch.sql.common.utils.StringUtils;
import org.opensearch.sql.ppl.WhereCommandIT;

public class CalciteWhereCommandIT extends WhereCommandIT {
@Override
public void init() throws Exception {
super.init();
enableCalcite();
loadIndex(Index.NESTED_SIMPLE);
loadIndex(Index.DEEP_NESTED);
loadIndex(Index.CASCADED_NESTED);
}

@Override
protected String getIncompatibleTypeErrMsg() {
return "In expression types are incompatible: fields type LONG, values type [INTEGER, INTEGER,"
+ " STRING]";
}

@Test
public void testFilterOnComputedNestedFields() throws IOException {
JSONObject result =
executeQuery(
String.format(
"source=%s | eval proj_name_len=length(projects.name) | fields projects.name,"
+ " proj_name_len | where proj_name_len > 29",
TEST_INDEX_DEEP_NESTED));
verifySchema(result, schema("projects.name", "string"), schema("proj_name_len", "int"));
verifyDataRows(result, rows("AWS Redshift Spectrum querying", 30));
}

@Test
public void testFilterOnNestedAndRootFields() throws IOException {
JSONObject result =
executeQuery(
String.format(
"source=%s | where city.name = 'Seattle' and length(projects.name) > 29 | fields"
+ " city.name, projects.name",
TEST_INDEX_DEEP_NESTED));
verifySchema(result, schema("city.name", "string"), schema("projects.name", "string"));
verifyDataRows(result, rows("Seattle", "AWS Redshift Spectrum querying"));
}

@Test
public void testFilterOnNestedFields() throws IOException {
// address is a nested object
JSONObject result1 =
executeQuery(
String.format(
"source=%s | where address.city = 'New york city' | fields address.city",
TEST_INDEX_NESTED_SIMPLE));
verifySchema(result1, schema("address.city", "string"));
verifyDataRows(result1, rows("New york city"));

JSONObject result2 =
executeQuery(
String.format(
"source=%s | where address.city in ('Miami', 'san diego') | fields address.city",
TEST_INDEX_NESTED_SIMPLE));
verifyDataRows(result2, rows("Miami"), rows("san diego"));
}

@Test
public void testFilterOnMultipleCascadedNestedFields() throws IOException {
// SQL's static type system does not allow returning list[int] in place of int
enabledOnlyWhenPushdownIsEnabled();
JSONObject result =
executeQuery(
String.format(
"source=%s | where author.books.reviews.rating >=4 and author.books.reviews.rating"
+ " < 6 and author.books.title = 'The Shining' | fields author.books",
TEST_INDEX_CASCADED_NESTED));
verifySchema(result, schema("author.books", "array"));
verifyDataRows(
result,
rows(
List.of(
Map.of(
"title",
"The Shining",
"reviews",
List.of(
Map.of(
"review_date",
"2022-09-03",
"rating",
3,
"comment",
"Brilliant but terrifying"),
Map.of(
"review_date",
"2023-04-12",
"rating",
4,
"comment",
"Psychological horror at its best"),
Map.of(
"review_date",
"2023-10-28",
"rating",
2,
"comment",
"Too slow in places"))))));
}

@Test
public void testScriptFilterOnDifferentNestedHierarchyShouldThrow() throws IOException {
enabledOnlyWhenPushdownIsEnabled();
Throwable t =
assertThrows(
Exception.class,
() ->
executeQuery(
String.format(
"source=%s | where author.books.reviews.rating + length(author.books.title)"
+ " > 10",
TEST_INDEX_CASCADED_NESTED)));
verifyErrorMessageContains(
t,
"Accessing multiple nested fields under different hierarchies in script is not supported:"
+ " [author.books.reviews, author.books]");
}

@Test
public void testAggFilterOnNestedFields() throws IOException {
JSONObject result =
executeQuery(
StringUtils.format(
"source=%s | stats count(eval(author.name < 'K')) as george_and_jk",
TEST_INDEX_CASCADED_NESTED));
verifySchema(result, schema("george_and_jk", "bigint"));
verifyDataRows(result, rows(2));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,11 @@ public enum Index {
"_doc",
getDeepNestedIndexMapping(),
"src/test/resources/deep_nested_index_data.json"),
CASCADED_NESTED(
TestsConstants.TEST_INDEX_CASCADED_NESTED,
"_doc",
getMappingFile("cascaded_nested_index_mapping.json"),
"src/test/resources/cascaded_nested.json"),
TELEMETRY(
TestsConstants.TEST_INDEX_TELEMETRY,
"_doc",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public class TestsConstants {
public static final String TEST_INDEX_TIME_DATA = TEST_INDEX + "_time_data";

public static final String TEST_INDEX_DEEP_NESTED = TEST_INDEX + "_deep_nested";
public static final String TEST_INDEX_CASCADED_NESTED = TEST_INDEX + "_cascaded_nested";
public static final String TEST_INDEX_TELEMETRY = TEST_INDEX + "_telemetry";
public static final String TEST_INDEX_STRINGS = TEST_INDEX + "_strings";
public static final String TEST_INDEX_DATATYPE_NUMERIC = TEST_INDEX + "_datatypes_numeric";
Expand Down
6 changes: 6 additions & 0 deletions integ-test/src/test/resources/cascaded_nested.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{"index": {"_id": "1"}}
{"author": {"name": "J.K. Rowling", "books": [{"title": "Harry Potter and the Sorcerer's Stone", "reviews": [{"rating": 5, "comment": "Magical and enchanting!", "review_date": "2023-01-15"}, {"rating": 4, "comment": "Great for kids and adults", "review_date": "2023-06-22"}]}, {"title": "Harry Potter and the Chamber of Secrets", "reviews": [{"rating": 5, "comment": "Even better than the first", "review_date": "2023-02-10"}, {"rating": 4, "comment": "Darker tone emerging", "review_date": "2023-07-18"}]}]}}
{"index": {"_id": "2"}}
{"author": {"name": "George R.R. Martin", "books": [{"title": "A Game of Thrones", "reviews": [{"rating": 4, "comment": "Epic fantasy masterpiece", "review_date": "2022-11-05"}, {"rating": 3, "comment": "Too many characters to track", "review_date": "2023-03-20"}]}, {"title": "A Clash of Kings", "reviews": [{"rating": 2, "comment": "Incredible plot twists", "review_date": "2023-08-14"}]}]}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Verify the review sentiment consistency.

The review for "A Clash of Kings" has a rating of 2 (out of 5) but the comment says "Incredible plot twists", which typically conveys positive sentiment. This inconsistency may be intentional for testing edge cases, but if not, consider aligning the rating with the comment (e.g., rating: 5 or comment: "Confusing plot twists").

🧰 Tools
🪛 Biome (2.1.2)

[error] 3-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-5: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)

🤖 Prompt for AI Agents
In integ-test/src/test/resources/cascaded_nested.json around line 4, the "A
Clash of Kings" review shows a numeric rating of 2 but a positive comment
"Incredible plot twists", creating a sentiment mismatch; update the test data to
make rating and comment consistent by either raising the rating (e.g., to 5) to
match the positive comment or changing the comment to reflect a negative
sentiment (e.g., "Confusing plot twists"), and ensure the chosen change aligns
with any intended edge-case testing elsewhere in the suite.

{"index": {"_id": "3"}}
{"author": {"name": "Stephen King", "books": [{"title": "The Shining", "reviews": [{"rating": 3, "comment": "Brilliant but terrifying", "review_date": "2022-09-03"}, {"rating": 4, "comment": "Psychological horror at its best", "review_date": "2023-04-12"}, {"rating": 2, "comment": "Too slow in places", "review_date": "2023-10-28"}]}]}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
calcite:
logical: |
LogicalSystemLimit(fetch=[10000], type=[QUERY_SIZE_LIMIT])
LogicalAggregate(group=[{}], george_and_jk=[COUNT($0)])
LogicalProject($f1=[CASE(<($7, 'K'), 1, null:NULL)])
CalciteLogicalIndexScan(table=[[OpenSearch, opensearch-sql_test_index_cascaded_nested]])
physical: |
EnumerableLimit(fetch=[10000])
CalciteEnumerableIndexScan(table=[[OpenSearch, opensearch-sql_test_index_cascaded_nested]], PushDownContext=[[AGGREGATION->rel#:LogicalAggregate.NONE.[](input=RelSubset#,group={},george_and_jk=COUNT() FILTER $0)], OpenSearchRequestBuilder(sourceBuilder={"from":0,"size":0,"timeout":"1m","aggregations":{"george_and_jk":{"filter":{"nested":{"query":{"range":{"author.name":{"from":null,"to":"K","include_lower":true,"include_upper":false,"boost":1.0}}},"path":"author","ignore_unmapped":false,"score_mode":"none","boost":1.0}},"aggregations":{"george_and_jk":{"value_count":{"field":"_index"}}}}}}, requestedTotalSize=2147483647, pageSize=null, startFrom=0)])
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
calcite:
logical: |
LogicalSystemLimit(fetch=[10000], type=[QUERY_SIZE_LIMIT])
LogicalFilter(condition=[>($1, 29)])
LogicalProject(projects.name=[$3], proj_name_len=[CHAR_LENGTH($3)])
CalciteLogicalIndexScan(table=[[OpenSearch, opensearch-sql_test_index_deep_nested]])
physical: |
EnumerableCalc(expr#0=[{inputs}], expr#1=[CHAR_LENGTH($t0)], proj#0..1=[{exprs}])
CalciteEnumerableIndexScan(table=[[OpenSearch, opensearch-sql_test_index_deep_nested]], PushDownContext=[[PROJECT->[projects.name], SCRIPT->>(CHAR_LENGTH($0), 29), LIMIT->10000], OpenSearchRequestBuilder(sourceBuilder={"from":0,"size":10000,"timeout":"1m","query":{"nested":{"query":{"script":{"script":{"source":"{\"langType\":\"calcite\",\"script\":\"rO0ABXQCIXsKICAib3AiOiB7CiAgICAibmFtZSI6ICI+IiwKICAgICJraW5kIjogIkdSRUFURVJfVEhBTiIsCiAgICAic3ludGF4IjogIkJJTkFSWSIKICB9LAogICJvcGVyYW5kcyI6IFsKICAgIHsKICAgICAgIm9wIjogewogICAgICAgICJuYW1lIjogIkNIQVJfTEVOR1RIIiwKICAgICAgICAia2luZCI6ICJDSEFSX0xFTkdUSCIsCiAgICAgICAgInN5bnRheCI6ICJGVU5DVElPTiIKICAgICAgfSwKICAgICAgIm9wZXJhbmRzIjogWwogICAgICAgIHsKICAgICAgICAgICJkeW5hbWljUGFyYW0iOiAwLAogICAgICAgICAgInR5cGUiOiB7CiAgICAgICAgICAgICJ0eXBlIjogIlZBUkNIQVIiLAogICAgICAgICAgICAibnVsbGFibGUiOiB0cnVlLAogICAgICAgICAgICAicHJlY2lzaW9uIjogLTEKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIF0KICAgIH0sCiAgICB7CiAgICAgICJkeW5hbWljUGFyYW0iOiAxLAogICAgICAidHlwZSI6IHsKICAgICAgICAidHlwZSI6ICJJTlRFR0VSIiwKICAgICAgICAibnVsbGFibGUiOiBmYWxzZQogICAgICB9CiAgICB9CiAgXQp9\"}","lang":"opensearch_compounded_script","params":{"utcTimestamp": 0,"SOURCES":[0,2],"DIGESTS":["projects.name",29]}},"boost":1.0}},"path":"projects","ignore_unmapped":false,"score_mode":"none","boost":1.0}},"_source":{"includes":["projects.name"],"excludes":[]}}, requestedTotalSize=10000, pageSize=null, startFrom=0)])
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
calcite:
logical: |
LogicalSystemLimit(fetch=[10000], type=[QUERY_SIZE_LIMIT])
LogicalProject(author=[$0])
LogicalFilter(condition=[AND(SEARCH($4, Sarg[[4..6)]), =($6, 'The Shining'))])
CalciteLogicalIndexScan(table=[[OpenSearch, opensearch-sql_test_index_cascaded_nested]])
physical: |
CalciteEnumerableIndexScan(table=[[OpenSearch, opensearch-sql_test_index_cascaded_nested]], PushDownContext=[[PROJECT->[author, author.books.reviews.rating, author.books.title], FILTER->AND(SEARCH($1, Sarg[[4..6)]), =($2, 'The Shining')), PROJECT->[author], LIMIT->10000], OpenSearchRequestBuilder(sourceBuilder={"from":0,"size":10000,"timeout":"1m","query":{"bool":{"must":[{"nested":{"query":{"range":{"author.books.reviews.rating":{"from":4.0,"to":6.0,"include_lower":true,"include_upper":false,"boost":1.0}}},"path":"author.books.reviews","ignore_unmapped":false,"score_mode":"none","boost":1.0}},{"nested":{"query":{"term":{"author.books.title":{"value":"The Shining","boost":1.0}}},"path":"author.books","ignore_unmapped":false,"score_mode":"none","boost":1.0}}],"adjust_pure_negative":true,"boost":1.0}},"_source":{"includes":["author"],"excludes":[]}}, requestedTotalSize=10000, pageSize=null, startFrom=0)])
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
calcite:
logical: |
LogicalSystemLimit(fetch=[10000], type=[QUERY_SIZE_LIMIT])
LogicalProject(name=[$0], address=[$1], id=[$6], age=[$7])
LogicalFilter(condition=[=($2, 'New york city')])
CalciteLogicalIndexScan(table=[[OpenSearch, opensearch-sql_test_index_nested_simple]])
physical: |
CalciteEnumerableIndexScan(table=[[OpenSearch, opensearch-sql_test_index_nested_simple]], PushDownContext=[[PROJECT->[name, address, address.city, id, age], FILTER->=($2, 'New york city'), PROJECT->[name, address, id, age], LIMIT->10000], OpenSearchRequestBuilder(sourceBuilder={"from":0,"size":10000,"timeout":"1m","query":{"nested":{"query":{"term":{"address.city.keyword":{"value":"New york city","boost":1.0}}},"path":"address","ignore_unmapped":false,"score_mode":"none","boost":1.0}},"_source":{"includes":["name","address","id","age"],"excludes":[]}}, requestedTotalSize=10000, pageSize=null, startFrom=0)])
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
calcite:
logical: |
LogicalSystemLimit(fetch=[10000], type=[QUERY_SIZE_LIMIT])
LogicalProject(name=[$0], address=[$1], id=[$6], age=[$7])
LogicalFilter(condition=[SEARCH($2, Sarg['Miami':VARCHAR, 'san diego':VARCHAR]:VARCHAR)])
CalciteLogicalIndexScan(table=[[OpenSearch, opensearch-sql_test_index_nested_simple]])
physical: |
CalciteEnumerableIndexScan(table=[[OpenSearch, opensearch-sql_test_index_nested_simple]], PushDownContext=[[PROJECT->[name, address, address.city, id, age], FILTER->SEARCH($2, Sarg['Miami':VARCHAR, 'san diego':VARCHAR]:VARCHAR), PROJECT->[name, address, id, age], LIMIT->10000], OpenSearchRequestBuilder(sourceBuilder={"from":0,"size":10000,"timeout":"1m","query":{"nested":{"query":{"terms":{"address.city.keyword":["Miami","san diego"],"boost":1.0}},"path":"address","ignore_unmapped":false,"score_mode":"none","boost":1.0}},"_source":{"includes":["name","address","id","age"],"excludes":[]}}, requestedTotalSize=10000, pageSize=null, startFrom=0)])
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
calcite:
logical: |
LogicalSystemLimit(fetch=[10000], type=[QUERY_SIZE_LIMIT])
LogicalProject(accounts=[$0], projects=[$2], city=[$4], account=[$8])
LogicalFilter(condition=[AND(=($7, 'Seattle'), >(CHAR_LENGTH($3), 29))])
CalciteLogicalIndexScan(table=[[OpenSearch, opensearch-sql_test_index_deep_nested]])
physical: |
CalciteEnumerableIndexScan(table=[[OpenSearch, opensearch-sql_test_index_deep_nested]], PushDownContext=[[PROJECT->[accounts, projects, projects.name, city, city.name, account], SCRIPT->AND(=($4, 'Seattle'), >(CHAR_LENGTH($2), 29)), PROJECT->[accounts, projects, city, account], LIMIT->10000], OpenSearchRequestBuilder(sourceBuilder={"from":0,"size":10000,"timeout":"1m","query":{"bool":{"must":[{"term":{"city.name":{"value":"Seattle","boost":1.0}}},{"nested":{"query":{"script":{"script":{"source":"{\"langType\":\"calcite\",\"script\":\"rO0ABXQCIXsKICAib3AiOiB7CiAgICAibmFtZSI6ICI+IiwKICAgICJraW5kIjogIkdSRUFURVJfVEhBTiIsCiAgICAic3ludGF4IjogIkJJTkFSWSIKICB9LAogICJvcGVyYW5kcyI6IFsKICAgIHsKICAgICAgIm9wIjogewogICAgICAgICJuYW1lIjogIkNIQVJfTEVOR1RIIiwKICAgICAgICAia2luZCI6ICJDSEFSX0xFTkdUSCIsCiAgICAgICAgInN5bnRheCI6ICJGVU5DVElPTiIKICAgICAgfSwKICAgICAgIm9wZXJhbmRzIjogWwogICAgICAgIHsKICAgICAgICAgICJkeW5hbWljUGFyYW0iOiAwLAogICAgICAgICAgInR5cGUiOiB7CiAgICAgICAgICAgICJ0eXBlIjogIlZBUkNIQVIiLAogICAgICAgICAgICAibnVsbGFibGUiOiB0cnVlLAogICAgICAgICAgICAicHJlY2lzaW9uIjogLTEKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIF0KICAgIH0sCiAgICB7CiAgICAgICJkeW5hbWljUGFyYW0iOiAxLAogICAgICAidHlwZSI6IHsKICAgICAgICAidHlwZSI6ICJJTlRFR0VSIiwKICAgICAgICAibnVsbGFibGUiOiBmYWxzZQogICAgICB9CiAgICB9CiAgXQp9\"}","lang":"opensearch_compounded_script","params":{"utcTimestamp": 0,"SOURCES":[0,2],"DIGESTS":["projects.name",29]}},"boost":1.0}},"path":"projects","ignore_unmapped":false,"score_mode":"none","boost":1.0}}],"adjust_pure_negative":true,"boost":1.0}},"_source":{"includes":["accounts","projects","city","account"],"excludes":[]}}, requestedTotalSize=10000, pageSize=null, startFrom=0)])
Loading
Loading