Skip to content

Commit cbfd14c

Browse files
authored
Testing composite index queries against production (#7632)
1 parent cca4735 commit cbfd14c

File tree

9 files changed

+588
-126
lines changed

9 files changed

+588
-126
lines changed

.github/workflows/test-changed-firestore-integration.yml

+19-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,24 @@ jobs:
1313
with:
1414
# This makes Actions fetch all Git history so run-changed script can diff properly.
1515
fetch-depth: 0
16+
- uses: 'google-github-actions/auth@v0'
17+
with:
18+
credentials_json: '${{ secrets.JSSDK_ACTIONS_SA_KEY }}'
19+
# create composite indexes with Terraform
20+
- name: Setup Terraform
21+
uses: hashicorp/setup-terraform@v2
22+
- name: Terraform Init
23+
run: |
24+
cp config/ci.config.json config/project.json
25+
cd packages/firestore
26+
terraform init
27+
continue-on-error: true
28+
- name: Terraform Apply
29+
if: github.event_name == 'pull_request'
30+
run: |
31+
cd packages/firestore
32+
terraform apply -var-file=../../config/project.json -auto-approve
33+
continue-on-error: true
1634
- name: Set up Node (16)
1735
uses: actions/setup-node@v3
1836
with:
@@ -24,9 +42,7 @@ jobs:
2442
- name: Bump Node memory limit
2543
run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV
2644
- name: Test setup and yarn install
27-
run: |
28-
cp config/ci.config.json config/project.json
29-
yarn
45+
run: yarn
3046
- name: build
3147
run: yarn build:changed firestore-integration
3248
- name: Run tests if firestore or its dependencies has changed

.gitignore

+7-1
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,10 @@ tsdoc-metadata.json
9191
# generated html docs
9292
docs-rut/
9393
docs/
94-
toc/
94+
toc/
95+
96+
# generated Terraform docs
97+
.terraform/*
98+
.terraform.lock.hcl
99+
*.tfstate
100+
*.tfstate.*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
locals {
2+
indexes = {
3+
index1 = [
4+
{
5+
field_path = "testId"
6+
order = "ASCENDING"
7+
},
8+
{
9+
field_path = "a"
10+
order = "ASCENDING"
11+
},
12+
]
13+
index2 = [
14+
{
15+
field_path = "testId"
16+
order = "ASCENDING"
17+
},
18+
{
19+
field_path = "b"
20+
order = "ASCENDING"
21+
},
22+
]
23+
index3 = [
24+
{
25+
field_path = "testId"
26+
order = "ASCENDING"
27+
},
28+
{
29+
field_path = "b"
30+
order = "DESCENDING"
31+
},
32+
]
33+
index4 = [
34+
{
35+
field_path = "a"
36+
order = "ASCENDING"
37+
},
38+
{
39+
field_path = "testId"
40+
order = "ASCENDING"
41+
},
42+
{
43+
field_path = "b"
44+
order = "ASCENDING"
45+
},
46+
]
47+
index5 = [
48+
{
49+
field_path = "a"
50+
order = "ASCENDING"
51+
},
52+
{
53+
field_path = "testId"
54+
order = "ASCENDING"
55+
},
56+
{
57+
field_path = "b"
58+
order = "DESCENDING"
59+
},
60+
]
61+
index6 = [
62+
{
63+
field_path = "a"
64+
order = "ASCENDING"
65+
},
66+
{
67+
field_path = "testId"
68+
order = "ASCENDING"
69+
},
70+
{
71+
field_path = "a"
72+
order = "DESCENDING"
73+
},
74+
]
75+
index7 = [
76+
{
77+
field_path = "b"
78+
order = "ASCENDING"
79+
},
80+
{
81+
field_path = "testId"
82+
order = "ASCENDING"
83+
},
84+
{
85+
field_path = "a"
86+
order = "ASCENDING"
87+
},
88+
]
89+
index8 = [
90+
{
91+
field_path = "b"
92+
order = "ASCENDING"
93+
},
94+
{
95+
field_path = "testId"
96+
order = "ASCENDING"
97+
},
98+
{
99+
field_path = "a"
100+
order = "DESCENDING"
101+
},
102+
]
103+
}
104+
}

packages/firestore/main.tf

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
variable "projectId" {}
2+
3+
provider "google" {
4+
project = var.projectId
5+
}
6+
7+
resource "google_firestore_index" "default-db-index" {
8+
collection = "composite-index-test-collection"
9+
10+
for_each = local.indexes
11+
dynamic "fields" {
12+
for_each = distinct(flatten([for k, v in local.indexes : [
13+
for i in each.value : {
14+
field_path = i.field_path
15+
order = i.order
16+
}]]))
17+
content {
18+
field_path = lookup(fields.value, "field_path", null)
19+
order = lookup(fields.value, "order", null)
20+
}
21+
}
22+
23+
}
24+
25+
resource "google_firestore_index" "named-db-index" {
26+
collection = "composite-index-test-collection"
27+
database = "test-db"
28+
29+
for_each = local.indexes
30+
dynamic "fields" {
31+
for_each = distinct(flatten([for k, v in local.indexes : [
32+
for i in each.value : {
33+
field_path = i.field_path
34+
order = i.order
35+
}]]))
36+
content {
37+
field_path = lookup(fields.value, "field_path", null)
38+
order = lookup(fields.value, "order", null)
39+
}
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/**
2+
* @license
3+
* Copyright 2023 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { CompositeIndexTestHelper } from '../util/composite_index_test_helper';
19+
import {
20+
where,
21+
orderBy,
22+
limit,
23+
limitToLast,
24+
or
25+
} from '../util/firebase_export';
26+
import { apiDescribe } from '../util/helpers';
27+
28+
/*
29+
* Guidance for Creating Tests:
30+
* ----------------------------
31+
* When creating tests that require composite indexes, it is recommended to utilize the
32+
* "CompositeIndexTestHelper" class. This utility class provides methods for creating
33+
* and setting test documents and running queries with ease, ensuring proper data
34+
* isolation and query construction.
35+
*
36+
* Please remember to update the main index configuration file (firestore_index_config.tf)
37+
* with any new composite indexes needed for the tests. This ensures synchronization with
38+
* other testing environments, including CI. You can generate the required index link by
39+
* clicking on the Firebase console link in the error message while running tests locally.
40+
*/
41+
42+
apiDescribe('Composite Index Queries', persistence => {
43+
// OR Query tests only run when the SDK's local cache is configured to use
44+
// LRU garbage collection (rather than eager garbage collection) because
45+
// they validate that the result from server and cache match.
46+
// eslint-disable-next-line no-restricted-properties
47+
(persistence.gc === 'lru' ? describe : describe.skip)('OR Queries', () => {
48+
it('can use query overloads', () => {
49+
const testDocs = {
50+
doc1: { a: 1, b: 0 },
51+
doc2: { a: 2, b: 1 },
52+
doc3: { a: 3, b: 2 },
53+
doc4: { a: 1, b: 3 },
54+
doc5: { a: 1, b: 1 }
55+
};
56+
const testHelper = new CompositeIndexTestHelper();
57+
return testHelper.withTestDocs(persistence, testDocs, async coll => {
58+
// a == 1, limit 2, b - desc
59+
await testHelper.assertOnlineAndOfflineResultsMatch(
60+
testHelper.query(
61+
coll,
62+
where('a', '==', 1),
63+
limit(2),
64+
orderBy('b', 'desc')
65+
),
66+
'doc4',
67+
'doc5'
68+
);
69+
});
70+
});
71+
72+
it('can use or queries', () => {
73+
const testDocs = {
74+
doc1: { a: 1, b: 0 },
75+
doc2: { a: 2, b: 1 },
76+
doc3: { a: 3, b: 2 },
77+
doc4: { a: 1, b: 3 },
78+
doc5: { a: 1, b: 1 }
79+
};
80+
const testHelper = new CompositeIndexTestHelper();
81+
return testHelper.withTestDocs(persistence, testDocs, async coll => {
82+
// with one inequality: a>2 || b==1.
83+
await testHelper.assertOnlineAndOfflineResultsMatch(
84+
testHelper.compositeQuery(
85+
coll,
86+
or(where('a', '>', 2), where('b', '==', 1))
87+
),
88+
'doc5',
89+
'doc2',
90+
'doc3'
91+
);
92+
93+
// Test with limits (implicit order by ASC): (a==1) || (b > 0) LIMIT 2
94+
await testHelper.assertOnlineAndOfflineResultsMatch(
95+
testHelper.compositeQuery(
96+
coll,
97+
or(where('a', '==', 1), where('b', '>', 0)),
98+
limit(2)
99+
),
100+
'doc1',
101+
'doc2'
102+
);
103+
104+
// Test with limits (explicit order by): (a==1) || (b > 0) LIMIT_TO_LAST 2
105+
// Note: The public query API does not allow implicit ordering when limitToLast is used.
106+
await testHelper.assertOnlineAndOfflineResultsMatch(
107+
testHelper.compositeQuery(
108+
coll,
109+
or(where('a', '==', 1), where('b', '>', 0)),
110+
limitToLast(2),
111+
orderBy('b')
112+
),
113+
'doc3',
114+
'doc4'
115+
);
116+
117+
// Test with limits (explicit order by ASC): (a==2) || (b == 1) ORDER BY a LIMIT 1
118+
await testHelper.assertOnlineAndOfflineResultsMatch(
119+
testHelper.compositeQuery(
120+
coll,
121+
or(where('a', '==', 2), where('b', '==', 1)),
122+
limit(1),
123+
orderBy('a')
124+
),
125+
'doc5'
126+
);
127+
128+
// Test with limits (explicit order by DESC): (a==2) || (b == 1) ORDER BY a LIMIT_TO_LAST 1
129+
await testHelper.assertOnlineAndOfflineResultsMatch(
130+
testHelper.compositeQuery(
131+
coll,
132+
or(where('a', '==', 2), where('b', '==', 1)),
133+
limitToLast(1),
134+
orderBy('a')
135+
),
136+
'doc2'
137+
);
138+
});
139+
});
140+
});
141+
});

0 commit comments

Comments
 (0)