-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathFirestormCollectionReference.cs
249 lines (212 loc) · 8.82 KB
/
FirestormCollectionReference.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using E7.Firebase.LitJson;
using UnityEngine;
namespace E7.Firebase
{
public struct FirestormCollectionReference
{
internal StringBuilder stringBuilder;
private string parentDocument;
private string collectionName;
public FirestormCollectionReference(string name)
{
stringBuilder = new StringBuilder($"/{name}");
parentDocument = "";
collectionName = name;
}
public FirestormCollectionReference(FirestormDocumentReference sb, string name)
{
parentDocument = sb.stringBuilder.ToString();
collectionName = name;
this.stringBuilder = sb.stringBuilder;
this.stringBuilder.Append($"/{name}");
}
public FirestormDocumentReference Document(string name) => new FirestormDocumentReference(this, name);
/// <summary>
/// Get all documents in the collection.
/// Pagination, ordering, field masking, missing document, and transactions are not supported.
/// </summary>
public async Task<FirestormQuerySnapshot> GetSnapshotAsync()
{
var uwr = await FirestormConfig.Instance.UWRGet(stringBuilder.ToString());
Debug.Log($"Getting query snapshot : {uwr.downloadHandler.text} {uwr.error}");
return new FirestormQuerySnapshot(uwr.downloadHandler.text);
}
/// <typeparam name="string">A Firebase-generated new document ID</typeparam>
public async Task<string> AddAsync<T>(T documentData) where T : class, new()
{
string documentJson = FirestormUtility.ToJsonDocument(documentData, "");
byte[] postData = Encoding.UTF8.GetBytes(documentJson);
//The URL must NOT include document name
var uwr = await FirestormConfig.Instance.UWRPost(stringBuilder.ToString(), new (string, string)[]
{
}, postData);
return new FirestormDocumentSnapshot(uwr.downloadHandler.text).Name;
}
/// <summary>
/// The real C# API will be .WhereGreaterThan .WhereLessThan etc. methods. I am just too lazy to imitate them all.
/// Runs against only immediate descendant documents of this collection.
/// </summary>
/// <summary>
/// <param name="operationString">The same strig as in Javascript API such as "==", "<", "<=", ">", ">=", "array_contains".
/// They may add more than this in the future. "array_contains" was added later.</param>
public async Task<FirestormQuerySnapshot> GetSnapshotAsync(params (string fieldName, string operationString, object target)[] queries)
{
RunQuery rq = default;
var fieldFilters = new List<FieldFilter>();
for (int i = 0; i < queries.Length; i++)
{
var query = queries[i];
var filter = new FieldFilter();
filter.field = new FieldReference { fieldPath = query.fieldName };
filter.op = StringToOperator(query.operationString).ToString();
var formatted = FirestormUtility.FormatForValueJson(query.target);
filter.value = new Dictionary<string, object>
{
[formatted.typeString] = formatted.objectForJson,
};
fieldFilters.Add(filter);
}
//fieldFilters.Sort();
//rq.structuredQuery.orderBy = fieldFilters.Select(x => new Order { field = x.field }).ToArray();
if (queries.Length == 1)
{
rq.structuredQuery.where = new Dictionary<string, IFilter>
{
["fieldFilter"] = fieldFilters[0],
};
}
else if (queries.Length > 1)
{
CompositeFilter cf = new CompositeFilter
{
op = Operator.AND.ToString(),
filters = fieldFilters.Select(x => new FilterForFieldFilter { fieldFilter = x }).ToArray(),
};
rq.structuredQuery.where = new Dictionary<string, IFilter>
{
["compositeFilter"] = cf
};
}
else
{
throw new FirestormException($"Query length {queries.Length} invalid!");
}
//Apparently REST API can query from more than 1 collection at once!
//But since this class is a "CollectionReference", it should represent only this collection. So always 1 element in this array.
rq.structuredQuery.from = new CollectionSelector[]{
new CollectionSelector{
collectionId = collectionName,
allDescendants = false,
}
};
string postJson = JsonMapper.ToJson(rq);
//File.WriteAllText(Application.dataPath + $"/LITPOST{UnityEngine.Random.Range(0, 100)}.txt", postJson);
byte[] postData = Encoding.UTF8.GetBytes(postJson);
Debug.Log($"JPOST {postJson}");
//Path is the parent of this collection.
var uwr = await FirestormConfig.Instance.UWRPost($"{parentDocument}:runQuery", null, postData);
//File.WriteAllText(Application.dataPath + $"/{UnityEngine.Random.Range(0, 100)}.txt", uwr.downloadHandler.text);
//Make the format looks like the one came back from "list" REST API
JsonData jd = JsonMapper.ToObject(uwr.downloadHandler.text);
//First layer is json array
List<string> collect = new List<string>();
foreach (JsonData arrayItem in jd)
{
if (arrayItem.ContainsKey("document"))
{
collect.Add(arrayItem["document"].ToJson());
}
}
string newJo = "{ \"documents\" : [ " + string.Join(",", collect) + " ] }";
//Debug.Log($"this is new jo {newJo}");
return new FirestormQuerySnapshot(newJo);
}
private struct RunQuery
{
public StructuredQuery structuredQuery;
}
private struct StructuredQuery
{
// public Projection select;
//Only one of 3 types of filter allowed here
public CollectionSelector[] from;
public Dictionary<string, IFilter> where;
//public Order[] orderBy;
}
// private struct Order
// {
// public FieldReference field;
// }
private struct CollectionSelector
{
public string collectionId;
public bool allDescendants;
}
private struct CompositeFilter : IFilter
{
public string op;
public FilterForFieldFilter[] filters; //Composite to unary or to composite not supported
}
private struct FilterForFieldFilter
{
public FieldFilter fieldFilter;
}
private struct FieldFilter : IFilter, IComparable<FieldFilter>
{
public FieldReference field;
public string op;
public Dictionary<string, object> value;
public int CompareTo(FieldFilter other)
{
switch (op)
{
case ">":
case "<":
case ">=":
case "<=":
//Range filter have to come first = this instance is lesser
return -1;
default:
return this.field.fieldPath.CompareTo(other.field.fieldPath);
}
}
}
private struct FieldReference
{
public string fieldPath;
}
private Operator StringToOperator(string operatorString)
{
switch (operatorString)
{
case "==": return Operator.EQUAL;
case ">": return Operator.GREATER_THAN;
case "<": return Operator.LESS_THAN;
case ">=": return Operator.GREATER_THAN_OR_EQUAL;
case "<=": return Operator.LESS_THAN_OR_EQUAL;
case "array_contains": return Operator.ARRAY_CONTAINS;
}
throw new FirestormException($"Operator {operatorString} not supported!");
}
private enum Operator
{
OPERATOR_UNSPECIFIED,
AND, //for composite only
LESS_THAN,
LESS_THAN_OR_EQUAL,
GREATER_THAN,
GREATER_THAN_OR_EQUAL,
EQUAL,
ARRAY_CONTAINS,
IS_NAN, //not supported
IS_NULL, //not supported
}
private interface IFilter { }
}
}