Skip to content

Commit 5b69cd5

Browse files
authored
Mila/MultiDB (#738)
1 parent 3ba328d commit 5b69cd5

File tree

6 files changed

+508
-46
lines changed

6 files changed

+508
-46
lines changed

docs/readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ Release Notes
9898
directory contains non-ANSI characters (Unicode above U+00FF).
9999
- Storage (Desktop): Fixed a crash on Windows when uploading files from a path
100100
containing non-ANSI characters (Unicode above U+00FF).
101+
- Firestore: Add multi-database support. ([#738](https://github.com/firebase/firebase-unity-sdk/pull/738)).
101102

102103
### 11.0.0
103104
- Changes

firestore/src/FirebaseFirestore.cs

Lines changed: 80 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,16 @@ public sealed class FirebaseFirestore {
4444
private readonly FirebaseFirestoreSettings _settings;
4545
private readonly TransactionManager _transactionManager;
4646

47-
private static readonly IDictionary<FirebaseApp, FirebaseFirestore> _instanceCache =
48-
new Dictionary<FirebaseApp, FirebaseFirestore>();
47+
private static readonly IDictionary<FirestoreInstanceCacheKey, FirebaseFirestore> _instanceCache =
48+
new Dictionary<FirestoreInstanceCacheKey, FirebaseFirestore>();
49+
50+
private const string DefaultDatabase = "(default)";
51+
52+
private string _databaseName;
4953

5054
// We rely on e.g. firestore.Document("a/b").Firestore returning the original Firestore
51-
// instance so it's important the constructor remains private and we only create one
52-
// FirebaseFirestore instance per FirebaseApp instance.
53-
private FirebaseFirestore(FirestoreProxy proxy, FirebaseApp app) {
55+
// instance so it's important the constructor remains private.
56+
private FirebaseFirestore(FirestoreProxy proxy, FirebaseApp app, string database) {
5457
_proxy = Util.NotNull(proxy);
5558
App = app;
5659
app.AppDisposed += OnAppDisposed;
@@ -63,6 +66,7 @@ private FirebaseFirestore(FirestoreProxy proxy, FirebaseApp app) {
6366

6467
_settings = new FirebaseFirestoreSettings(proxy);
6568
_transactionManager = new TransactionManager(this, proxy);
69+
_databaseName = database;
6670
}
6771

6872
/// <summary>
@@ -99,9 +103,11 @@ private void Dispose() {
99103
_isInCppInstanceCache = false;
100104
RemoveSelfFromInstanceCache();
101105
}
102-
106+
103107
_proxy = null;
104108
App = null;
109+
_databaseName = null;
110+
105111
} finally {
106112
_disposeLock.ReleaseWriterLock();
107113
}
@@ -115,41 +121,65 @@ private void Dispose() {
115121
public FirebaseApp App { get; private set; }
116122

117123
/// <summary>
118-
/// Gets the instance of <c>FirebaseFirestore</c> for the default <c>FirebaseApp</c>.
124+
/// Gets the instance of <c>FirebaseFirestore</c> for the default <c>FirebaseApp</c> with the default <c>database name</c>.
119125
/// </summary>
120126
/// <value>A <c>FirebaseFirestore</c> instance.</value>
121127
public static FirebaseFirestore DefaultInstance {
122128
get {
123129
FirebaseApp app = Util.NotNull(FirebaseApp.DefaultInstance);
124-
return GetInstance(app);
130+
return GetInstance(app, DefaultDatabase);
125131
}
126132
}
127133

128134
/// <summary>
129-
/// Gets an instance of <c>FirebaseFirestore</c> for a specific <c>FirebaseApp</c>.
135+
/// Gets an instance of <c>FirebaseFirestore</c> for a specific <c>FirebaseApp</c> with the default <c>database name</c>.
130136
/// </summary>
131137
/// <param name="app">The <c>FirebaseApp</c> for which to get a <c>FirebaseFirestore</c>
132138
/// instance.</param>
133139
/// <returns>A <c>FirebaseFirestore</c> instance.</returns>
134140
public static FirebaseFirestore GetInstance(FirebaseApp app) {
141+
return GetInstance(app, DefaultDatabase);
142+
}
143+
144+
145+
/// <summary>
146+
/// Gets an instance of <c>FirebaseFirestore</c> for the default <c>FirebaseApp</c> with a spesific <c>database name</c>.
147+
/// </summary>
148+
/// <param name="database">The customized name for the <c>database</c>.
149+
/// instance.</param>
150+
/// <returns>A <c>FirebaseFirestore</c> instance.</returns>
151+
public static FirebaseFirestore GetInstance(string database) {
152+
FirebaseApp app = Util.NotNull(FirebaseApp.DefaultInstance);
153+
return GetInstance(app, database);
154+
}
155+
156+
/// <summary>
157+
/// Gets an instance of <c>FirebaseFirestore</c> for a specific <c>FirebaseApp</c> with a spesific <c>database name</c>.
158+
/// </summary>
159+
/// <param name="app">The <c>FirebaseApp</c> for which to get a <c>FirebaseFirestore</c>
160+
/// <param name="database">The customized name for the <c>database</c>.
161+
/// instance.</param>
162+
/// <returns>A <c>FirebaseFirestore</c> instance.</returns>
163+
public static FirebaseFirestore GetInstance(FirebaseApp app, string database) {
135164
Preconditions.CheckNotNull(app, nameof(app));
165+
Preconditions.CheckNotNull(database, nameof(database));
136166

137167
while (true) {
138168
FirebaseFirestore firestore;
139-
140-
// Acquire the lock on `_instanceCache` to see if the given `FirebaseApp` is in
169+
FirestoreInstanceCacheKey key = new FirestoreInstanceCacheKey(app, database);
170+
// Acquire the lock on `_instanceCache` to see if the given `FirestoreInstanceCacheKey` is in
141171
// `_instanceCache`; if it isn't then create the `FirebaseFirestore` instance, put it in the
142172
// cache, and return it.
143173
lock (_instanceCache) {
144-
if (!_instanceCache.TryGetValue(app, out firestore)) {
174+
if (!_instanceCache.TryGetValue(key, out firestore)) {
145175
// NOTE: FirestoreProxy.GetInstance() returns an *owning* reference (see the %newobject
146176
// directive in firestore.SWIG); therefore, we must make sure that we *never* call
147177
// FirestoreProxy.GetInstance() when it would return a proxy for a C++ object that it
148178
// previously returned. Otherwise, if it did, then that C++ object would be deleted
149179
// twice, causing a crash.
150-
FirestoreProxy firestoreProxy = Util.NotNull(FirestoreProxy.GetInstance(app));
151-
firestore = new FirebaseFirestore(firestoreProxy, app);
152-
_instanceCache[app] = firestore;
180+
FirestoreProxy firestoreProxy = Util.NotNull(FirestoreProxy.GetInstance(app, database));
181+
firestore = new FirebaseFirestore(firestoreProxy, app, database);
182+
_instanceCache[key] = firestore;
153183
return firestore;
154184
}
155185
}
@@ -557,7 +587,7 @@ public Task WaitForPendingWritesAsync() {
557587
/// used. Calling any other method will result in an error.
558588
///
559589
/// To restart after termination, simply create a new instance of <c>FirebaseFirestore</c> with
560-
/// <c>GetInstance()</c> or <c>GetInstance(FirebaseApp)</c>.
590+
/// <c>GetInstance()</c> methods.
561591
///
562592
/// <c>Terminate()</c> does not cancel any pending writes, and any tasks that are awaiting a
563593
/// response from the server will not be resolved. The next time you start this instance, it
@@ -663,10 +693,42 @@ private void WithFirestoreProxy(Action<FirestoreProxy> action) {
663693
private void RemoveSelfFromInstanceCache() {
664694
lock (_instanceCache) {
665695
FirebaseFirestore cachedFirestore;
666-
if (_instanceCache.TryGetValue(App, out cachedFirestore) && cachedFirestore == this) {
667-
_instanceCache.Remove(App);
696+
FirestoreInstanceCacheKey key = new FirestoreInstanceCacheKey(App, _databaseName);
697+
if (_instanceCache.TryGetValue(key, out cachedFirestore) && cachedFirestore == this) {
698+
_instanceCache.Remove(key);
668699
}
669700
}
670701
}
702+
703+
private struct FirestoreInstanceCacheKey : IEquatable<FirestoreInstanceCacheKey> {
704+
public FirebaseApp App { get; }
705+
public string DatabaseName { get; }
706+
707+
public FirestoreInstanceCacheKey(FirebaseApp app, string databaseName)
708+
{
709+
App = app;
710+
DatabaseName = databaseName;
711+
}
712+
713+
public override int GetHashCode() {
714+
return App.Name.GetHashCode() + DatabaseName.GetHashCode();
715+
}
716+
public override bool Equals(object obj) {
717+
return obj is FirestoreInstanceCacheKey && Equals((FirestoreInstanceCacheKey)obj);
718+
}
719+
public bool Equals(FirestoreInstanceCacheKey other) {
720+
return App.Name == other.App.Name && DatabaseName == other.DatabaseName;
721+
}
722+
723+
public static bool operator ==(FirestoreInstanceCacheKey lhs, FirestoreInstanceCacheKey rhs) {
724+
return lhs.Equals(rhs);
725+
}
726+
public static bool operator !=(FirestoreInstanceCacheKey lhs, FirestoreInstanceCacheKey rhs) {
727+
return !lhs.Equals(rhs);
728+
}
729+
public override string ToString() {
730+
return String.Format("FirestoreInstanceKey: App = {0}, DatabaseName = {1}", App.Name, DatabaseName);
731+
}
732+
}
671733
}
672734
}

firestore/testapp/Assets/Firebase/Sample/Firestore/InvalidArgumentsTest.cs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,14 @@ public static InvalidArgumentsTestCase[] TestCases {
127127
action = FieldValue_ArrayRemove_NullArray },
128128
new InvalidArgumentsTestCase { name = "FieldValue_ArrayUnion_NullArray",
129129
action = FieldValue_ArrayUnion_NullArray },
130-
new InvalidArgumentsTestCase { name = "FirebaseFirestore_GetInstance_Null",
131-
action = FirebaseFirestore_GetInstance_Null },
130+
new InvalidArgumentsTestCase { name = "FirebaseFirestore_GetInstance_Null_App",
131+
action = FirebaseFirestore_GetInstance_Null_App },
132+
new InvalidArgumentsTestCase { name = "FirebaseFirestore_GetInstance_Null_Database_Name",
133+
action = FirebaseFirestore_GetInstance_Null_Database_Name },
134+
new InvalidArgumentsTestCase { name = "FirebaseFirestore_GetInstance_App_With_Null_Database_Name",
135+
action = FirebaseFirestore_GetInstance_App_With_Null_Database_Name},
136+
new InvalidArgumentsTestCase { name = "FirebaseFirestore_GetInstance_Null_App_With_Database_Name",
137+
action = FirebaseFirestore_GetInstance_Null_App_With_Database_Name },
132138
new InvalidArgumentsTestCase { name = "FirebaseFirestore_GetInstance_DisposedApp",
133139
action = FirebaseFirestore_GetInstance_DisposedApp },
134140
new InvalidArgumentsTestCase { name = "FirebaseFirestore_Collection_NullStringPath",
@@ -628,11 +634,26 @@ private static void FieldValue_ArrayUnion_NullArray(UIHandlerAutomated handler)
628634
handler.AssertException(typeof(ArgumentNullException), () => FieldValue.ArrayUnion(null));
629635
}
630636

631-
private static void FirebaseFirestore_GetInstance_Null(UIHandlerAutomated handler) {
637+
private static void FirebaseFirestore_GetInstance_Null_App(UIHandlerAutomated handler) {
632638
handler.AssertException(typeof(ArgumentNullException),
633-
() => FirebaseFirestore.GetInstance(null));
639+
() => FirebaseFirestore.GetInstance((FirebaseApp)null));
634640
}
635641

642+
private static void FirebaseFirestore_GetInstance_Null_Database_Name(UIHandlerAutomated handler) {
643+
handler.AssertException(typeof(ArgumentNullException),
644+
() => FirebaseFirestore.GetInstance((string)null));
645+
}
646+
private static void FirebaseFirestore_GetInstance_Null_App_With_Database_Name(UIHandlerAutomated handler) {
647+
handler.AssertException(typeof(ArgumentNullException),
648+
() => FirebaseFirestore.GetInstance((FirebaseApp)null, "a"));
649+
}
650+
private static void FirebaseFirestore_GetInstance_App_With_Null_Database_Name(UIHandlerAutomated handler)
651+
{
652+
FirebaseApp app = FirebaseApp.DefaultInstance;
653+
handler.AssertException(typeof(ArgumentNullException),
654+
() => FirebaseFirestore.GetInstance(app, (string)null));
655+
}
656+
636657
private static void FirebaseFirestore_GetInstance_DisposedApp(UIHandlerAutomated handler) {
637658
FirebaseApp disposedApp =
638659
FirebaseApp.Create(handler.db.App.Options, "test-getinstance-disposedapp");

firestore/testapp/Assets/Firebase/Sample/Firestore/UIHandler.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ namespace Firebase.Sample.Firestore {
3232
public class UIHandler : MonoBehaviour {
3333
private const int kMaxLogSize = 16382;
3434

35+
private const string DefaultDatabase = "(default)";
36+
3537
public GUISkin fb_GUISkin;
3638
private Vector2 controlsScrollViewVector = Vector2.zero;
3739
private string logText = "";
@@ -213,6 +215,18 @@ protected internal FirebaseFirestore TestFirestore(FirebaseApp app) {
213215
return firestore;
214216
}
215217

218+
protected internal FirebaseFirestore TestFirestore(string database) {
219+
FirebaseFirestore firestore = FirebaseFirestore.GetInstance(database);
220+
SetTargetBackend(firestore);
221+
return firestore;
222+
}
223+
224+
protected internal FirebaseFirestore TestFirestore(FirebaseApp app, string database) {
225+
FirebaseFirestore firestore = FirebaseFirestore.GetInstance(app, database);
226+
SetTargetBackend(firestore);
227+
return firestore;
228+
}
229+
216230
// Update the `Settings` of a Firestore instance to run tests against the production or
217231
// Firestore emulator backend.
218232
protected internal void SetTargetBackend(FirebaseFirestore db) {
@@ -652,4 +666,4 @@ void OnGUI() {
652666
GUILayout.EndArea();
653667
}
654668
}
655-
}
669+
}

0 commit comments

Comments
 (0)