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

Mila/MultiDB #738

Merged
merged 19 commits into from
Jun 22, 2023
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
1 change: 1 addition & 0 deletions docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,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> {
tom-andersen marked this conversation as resolved.
Show resolved Hide resolved
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