Skip to content

Commit

Permalink
Mila/MultiDB (#738)
Browse files Browse the repository at this point in the history
  • Loading branch information
milaGGL authored Jun 22, 2023
1 parent 3ba328d commit 5b69cd5
Show file tree
Hide file tree
Showing 6 changed files with 508 additions and 46 deletions.
1 change: 1 addition & 0 deletions docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ Release Notes
directory contains non-ANSI characters (Unicode above U+00FF).
- Storage (Desktop): Fixed a crash on Windows when uploading files from a path
containing non-ANSI characters (Unicode above U+00FF).
- Firestore: Add multi-database support. ([#738](https://github.com/firebase/firebase-unity-sdk/pull/738)).

### 11.0.0
- Changes
Expand Down
98 changes: 80 additions & 18 deletions firestore/src/FirebaseFirestore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,16 @@ public sealed class FirebaseFirestore {
private readonly FirebaseFirestoreSettings _settings;
private readonly TransactionManager _transactionManager;

private static readonly IDictionary<FirebaseApp, FirebaseFirestore> _instanceCache =
new Dictionary<FirebaseApp, FirebaseFirestore>();
private static readonly IDictionary<FirestoreInstanceCacheKey, FirebaseFirestore> _instanceCache =
new Dictionary<FirestoreInstanceCacheKey, FirebaseFirestore>();

private const string DefaultDatabase = "(default)";

private string _databaseName;

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

_settings = new FirebaseFirestoreSettings(proxy);
_transactionManager = new TransactionManager(this, proxy);
_databaseName = database;
}

/// <summary>
Expand Down Expand Up @@ -99,9 +103,11 @@ private void Dispose() {
_isInCppInstanceCache = false;
RemoveSelfFromInstanceCache();
}

_proxy = null;
App = null;
_databaseName = null;

} finally {
_disposeLock.ReleaseWriterLock();
}
Expand All @@ -115,41 +121,65 @@ private void Dispose() {
public FirebaseApp App { get; private set; }

/// <summary>
/// Gets the instance of <c>FirebaseFirestore</c> for the default <c>FirebaseApp</c>.
/// Gets the instance of <c>FirebaseFirestore</c> for the default <c>FirebaseApp</c> with the default <c>database name</c>.
/// </summary>
/// <value>A <c>FirebaseFirestore</c> instance.</value>
public static FirebaseFirestore DefaultInstance {
get {
FirebaseApp app = Util.NotNull(FirebaseApp.DefaultInstance);
return GetInstance(app);
return GetInstance(app, DefaultDatabase);
}
}

/// <summary>
/// Gets an instance of <c>FirebaseFirestore</c> for a specific <c>FirebaseApp</c>.
/// Gets an instance of <c>FirebaseFirestore</c> for a specific <c>FirebaseApp</c> with the default <c>database name</c>.
/// </summary>
/// <param name="app">The <c>FirebaseApp</c> for which to get a <c>FirebaseFirestore</c>
/// instance.</param>
/// <returns>A <c>FirebaseFirestore</c> instance.</returns>
public static FirebaseFirestore GetInstance(FirebaseApp app) {
return GetInstance(app, DefaultDatabase);
}


/// <summary>
/// Gets an instance of <c>FirebaseFirestore</c> for the default <c>FirebaseApp</c> with a spesific <c>database name</c>.
/// </summary>
/// <param name="database">The customized name for the <c>database</c>.
/// instance.</param>
/// <returns>A <c>FirebaseFirestore</c> instance.</returns>
public static FirebaseFirestore GetInstance(string database) {
FirebaseApp app = Util.NotNull(FirebaseApp.DefaultInstance);
return GetInstance(app, database);
}

/// <summary>
/// Gets an instance of <c>FirebaseFirestore</c> for a specific <c>FirebaseApp</c> with a spesific <c>database name</c>.
/// </summary>
/// <param name="app">The <c>FirebaseApp</c> for which to get a <c>FirebaseFirestore</c>
/// <param name="database">The customized name for the <c>database</c>.
/// instance.</param>
/// <returns>A <c>FirebaseFirestore</c> instance.</returns>
public static FirebaseFirestore GetInstance(FirebaseApp app, string database) {
Preconditions.CheckNotNull(app, nameof(app));
Preconditions.CheckNotNull(database, nameof(database));

while (true) {
FirebaseFirestore firestore;

// Acquire the lock on `_instanceCache` to see if the given `FirebaseApp` is in
FirestoreInstanceCacheKey key = new FirestoreInstanceCacheKey(app, database);
// Acquire the lock on `_instanceCache` to see if the given `FirestoreInstanceCacheKey` is in
// `_instanceCache`; if it isn't then create the `FirebaseFirestore` instance, put it in the
// cache, and return it.
lock (_instanceCache) {
if (!_instanceCache.TryGetValue(app, out firestore)) {
if (!_instanceCache.TryGetValue(key, out firestore)) {
// NOTE: FirestoreProxy.GetInstance() returns an *owning* reference (see the %newobject
// directive in firestore.SWIG); therefore, we must make sure that we *never* call
// FirestoreProxy.GetInstance() when it would return a proxy for a C++ object that it
// previously returned. Otherwise, if it did, then that C++ object would be deleted
// twice, causing a crash.
FirestoreProxy firestoreProxy = Util.NotNull(FirestoreProxy.GetInstance(app));
firestore = new FirebaseFirestore(firestoreProxy, app);
_instanceCache[app] = firestore;
FirestoreProxy firestoreProxy = Util.NotNull(FirestoreProxy.GetInstance(app, database));
firestore = new FirebaseFirestore(firestoreProxy, app, database);
_instanceCache[key] = firestore;
return firestore;
}
}
Expand Down Expand Up @@ -557,7 +587,7 @@ public Task WaitForPendingWritesAsync() {
/// used. Calling any other method will result in an error.
///
/// To restart after termination, simply create a new instance of <c>FirebaseFirestore</c> with
/// <c>GetInstance()</c> or <c>GetInstance(FirebaseApp)</c>.
/// <c>GetInstance()</c> methods.
///
/// <c>Terminate()</c> does not cancel any pending writes, and any tasks that are awaiting a
/// response from the server will not be resolved. The next time you start this instance, it
Expand Down Expand Up @@ -663,10 +693,42 @@ private void WithFirestoreProxy(Action<FirestoreProxy> action) {
private void RemoveSelfFromInstanceCache() {
lock (_instanceCache) {
FirebaseFirestore cachedFirestore;
if (_instanceCache.TryGetValue(App, out cachedFirestore) && cachedFirestore == this) {
_instanceCache.Remove(App);
FirestoreInstanceCacheKey key = new FirestoreInstanceCacheKey(App, _databaseName);
if (_instanceCache.TryGetValue(key, out cachedFirestore) && cachedFirestore == this) {
_instanceCache.Remove(key);
}
}
}

private struct FirestoreInstanceCacheKey : IEquatable<FirestoreInstanceCacheKey> {
public FirebaseApp App { get; }
public string DatabaseName { get; }

public FirestoreInstanceCacheKey(FirebaseApp app, string databaseName)
{
App = app;
DatabaseName = databaseName;
}

public override int GetHashCode() {
return App.Name.GetHashCode() + DatabaseName.GetHashCode();
}
public override bool Equals(object obj) {
return obj is FirestoreInstanceCacheKey && Equals((FirestoreInstanceCacheKey)obj);
}
public bool Equals(FirestoreInstanceCacheKey other) {
return App.Name == other.App.Name && DatabaseName == other.DatabaseName;
}

public static bool operator ==(FirestoreInstanceCacheKey lhs, FirestoreInstanceCacheKey rhs) {
return lhs.Equals(rhs);
}
public static bool operator !=(FirestoreInstanceCacheKey lhs, FirestoreInstanceCacheKey rhs) {
return !lhs.Equals(rhs);
}
public override string ToString() {
return String.Format("FirestoreInstanceKey: App = {0}, DatabaseName = {1}", App.Name, DatabaseName);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,14 @@ public static InvalidArgumentsTestCase[] TestCases {
action = FieldValue_ArrayRemove_NullArray },
new InvalidArgumentsTestCase { name = "FieldValue_ArrayUnion_NullArray",
action = FieldValue_ArrayUnion_NullArray },
new InvalidArgumentsTestCase { name = "FirebaseFirestore_GetInstance_Null",
action = FirebaseFirestore_GetInstance_Null },
new InvalidArgumentsTestCase { name = "FirebaseFirestore_GetInstance_Null_App",
action = FirebaseFirestore_GetInstance_Null_App },
new InvalidArgumentsTestCase { name = "FirebaseFirestore_GetInstance_Null_Database_Name",
action = FirebaseFirestore_GetInstance_Null_Database_Name },
new InvalidArgumentsTestCase { name = "FirebaseFirestore_GetInstance_App_With_Null_Database_Name",
action = FirebaseFirestore_GetInstance_App_With_Null_Database_Name},
new InvalidArgumentsTestCase { name = "FirebaseFirestore_GetInstance_Null_App_With_Database_Name",
action = FirebaseFirestore_GetInstance_Null_App_With_Database_Name },
new InvalidArgumentsTestCase { name = "FirebaseFirestore_GetInstance_DisposedApp",
action = FirebaseFirestore_GetInstance_DisposedApp },
new InvalidArgumentsTestCase { name = "FirebaseFirestore_Collection_NullStringPath",
Expand Down Expand Up @@ -628,11 +634,26 @@ private static void FieldValue_ArrayUnion_NullArray(UIHandlerAutomated handler)
handler.AssertException(typeof(ArgumentNullException), () => FieldValue.ArrayUnion(null));
}

private static void FirebaseFirestore_GetInstance_Null(UIHandlerAutomated handler) {
private static void FirebaseFirestore_GetInstance_Null_App(UIHandlerAutomated handler) {
handler.AssertException(typeof(ArgumentNullException),
() => FirebaseFirestore.GetInstance(null));
() => FirebaseFirestore.GetInstance((FirebaseApp)null));
}

private static void FirebaseFirestore_GetInstance_Null_Database_Name(UIHandlerAutomated handler) {
handler.AssertException(typeof(ArgumentNullException),
() => FirebaseFirestore.GetInstance((string)null));
}
private static void FirebaseFirestore_GetInstance_Null_App_With_Database_Name(UIHandlerAutomated handler) {
handler.AssertException(typeof(ArgumentNullException),
() => FirebaseFirestore.GetInstance((FirebaseApp)null, "a"));
}
private static void FirebaseFirestore_GetInstance_App_With_Null_Database_Name(UIHandlerAutomated handler)
{
FirebaseApp app = FirebaseApp.DefaultInstance;
handler.AssertException(typeof(ArgumentNullException),
() => FirebaseFirestore.GetInstance(app, (string)null));
}

private static void FirebaseFirestore_GetInstance_DisposedApp(UIHandlerAutomated handler) {
FirebaseApp disposedApp =
FirebaseApp.Create(handler.db.App.Options, "test-getinstance-disposedapp");
Expand Down
16 changes: 15 additions & 1 deletion firestore/testapp/Assets/Firebase/Sample/Firestore/UIHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ namespace Firebase.Sample.Firestore {
public class UIHandler : MonoBehaviour {
private const int kMaxLogSize = 16382;

private const string DefaultDatabase = "(default)";

public GUISkin fb_GUISkin;
private Vector2 controlsScrollViewVector = Vector2.zero;
private string logText = "";
Expand Down Expand Up @@ -213,6 +215,18 @@ protected internal FirebaseFirestore TestFirestore(FirebaseApp app) {
return firestore;
}

protected internal FirebaseFirestore TestFirestore(string database) {
FirebaseFirestore firestore = FirebaseFirestore.GetInstance(database);
SetTargetBackend(firestore);
return firestore;
}

protected internal FirebaseFirestore TestFirestore(FirebaseApp app, string database) {
FirebaseFirestore firestore = FirebaseFirestore.GetInstance(app, database);
SetTargetBackend(firestore);
return firestore;
}

// Update the `Settings` of a Firestore instance to run tests against the production or
// Firestore emulator backend.
protected internal void SetTargetBackend(FirebaseFirestore db) {
Expand Down Expand Up @@ -652,4 +666,4 @@ void OnGUI() {
GUILayout.EndArea();
}
}
}
}
Loading

0 comments on commit 5b69cd5

Please sign in to comment.