256 lines
7.4 KiB
GDScript
256 lines
7.4 KiB
GDScript
## @meta-authors Nicoló 'fenix' Santilio, Kyle Szklenski
|
|
## @meta-version 1.4
|
|
## A firestore query.
|
|
## Documentation TODO.
|
|
@tool
|
|
extends RefCounted
|
|
class_name FirestoreQuery
|
|
|
|
class Order:
|
|
var obj: Dictionary
|
|
|
|
class Cursor:
|
|
var values: Array
|
|
var before: bool
|
|
|
|
func _init(v : Array,b : bool):
|
|
values = v
|
|
before = b
|
|
|
|
signal query_result(query_result)
|
|
|
|
const TEMPLATE_QUERY: Dictionary = {
|
|
select = {},
|
|
from = [],
|
|
where = {},
|
|
orderBy = [],
|
|
startAt = {},
|
|
endAt = {},
|
|
offset = 0,
|
|
limit = 0
|
|
}
|
|
|
|
var query: Dictionary = {}
|
|
var aggregations: Array[Dictionary] = []
|
|
var sub_collection_path: String = ""
|
|
|
|
enum OPERATOR {
|
|
# Standard operators
|
|
OPERATOR_UNSPECIFIED,
|
|
LESS_THAN,
|
|
LESS_THAN_OR_EQUAL,
|
|
GREATER_THAN,
|
|
GREATER_THAN_OR_EQUAL,
|
|
EQUAL,
|
|
NOT_EQUAL,
|
|
ARRAY_CONTAINS,
|
|
ARRAY_CONTAINS_ANY,
|
|
IN,
|
|
NOT_IN,
|
|
|
|
# Unary operators
|
|
IS_NAN,
|
|
IS_NULL,
|
|
IS_NOT_NAN,
|
|
IS_NOT_NULL,
|
|
|
|
# Complex operators
|
|
AND,
|
|
OR
|
|
}
|
|
|
|
enum DIRECTION {
|
|
DIRECTION_UNSPECIFIED,
|
|
ASCENDING,
|
|
DESCENDING
|
|
}
|
|
|
|
|
|
# Select which fields you want to return as a reflection from your query.
|
|
# Fields must be added inside a list. Only a field is accepted inside the list
|
|
# Leave the Array empty if you want to return the whole document
|
|
func select(fields) -> FirestoreQuery:
|
|
match typeof(fields):
|
|
TYPE_STRING:
|
|
query["select"] = { fields = { fieldPath = fields } }
|
|
TYPE_ARRAY:
|
|
for field in fields:
|
|
field = ({ fieldPath = field })
|
|
query["select"] = { fields = fields }
|
|
_:
|
|
print("Type of 'fields' is not accepted.")
|
|
return self
|
|
|
|
|
|
|
|
# Select the collection you want to return the query result from
|
|
# if @all_descendants also sub-collections will be returned. If false, only documents will be returned
|
|
func from(collection_id : String, all_descendants : bool = true) -> FirestoreQuery:
|
|
query["from"] = [{collectionId = collection_id, allDescendants = all_descendants}]
|
|
return self
|
|
|
|
# @collections_array MUST be an Array of Arrays with this structure
|
|
# [ ["collection_id", true/false] ]
|
|
func from_many(collections_array : Array) -> FirestoreQuery:
|
|
var collections : Array = []
|
|
for collection in collections_array:
|
|
collections.append({collectionId = collection[0], allDescendants = collection[1]})
|
|
query["from"] = collections.duplicate(true)
|
|
return self
|
|
|
|
|
|
# Query the value of a field you want to match
|
|
# @field : the name of the field
|
|
# @operator : from FirestoreQuery.OPERATOR
|
|
# @value : can be any type - String, int, bool, float
|
|
# @chain : from FirestoreQuery.OPERATOR.[OR/AND], use it only if you want to chain "AND" or "OR" logic with futher where() calls
|
|
# eg. super.where("name", OPERATOR.EQUAL, "Matt", OPERATOR.AND).where("age", OPERATOR.LESS_THAN, 20)
|
|
func where(field : String, operator : int, value = null, chain : int = -1):
|
|
if operator in [OPERATOR.IS_NAN, OPERATOR.IS_NULL, OPERATOR.IS_NOT_NAN, OPERATOR.IS_NOT_NULL]:
|
|
if (chain in [OPERATOR.AND, OPERATOR.OR]) or (query.has("where") and query.where.has("compositeFilter")):
|
|
var filters : Array = []
|
|
if query.has("where") and query.where.has("compositeFilter"):
|
|
if chain == -1:
|
|
filters = query.where.compositeFilter.filters.duplicate(true)
|
|
chain = OPERATOR.get(query.where.compositeFilter.op)
|
|
else:
|
|
filters.append(query.where)
|
|
filters.append(create_unary_filter(field, operator))
|
|
query["where"] = create_composite_filter(chain, filters)
|
|
else:
|
|
query["where"] = create_unary_filter(field, operator)
|
|
else:
|
|
if value == null:
|
|
print("A value must be defined to match the field: {field}".format({field = field}))
|
|
else:
|
|
if (chain in [OPERATOR.AND, OPERATOR.OR]) or (query.has("where") and query.where.has("compositeFilter")):
|
|
var filters : Array = []
|
|
if query.has("where") and query.where.has("compositeFilter"):
|
|
if chain == -1:
|
|
filters = query.where.compositeFilter.filters.duplicate(true)
|
|
chain = OPERATOR.get(query.where.compositeFilter.op)
|
|
else:
|
|
filters.append(query.where)
|
|
filters.append(create_field_filter(field, operator, value))
|
|
query["where"] = create_composite_filter(chain, filters)
|
|
else:
|
|
query["where"] = create_field_filter(field, operator, value)
|
|
return self
|
|
|
|
|
|
# Order by a field, defining its name and the order direction
|
|
# default directoin = Ascending
|
|
func order_by(field : String, direction : int = DIRECTION.ASCENDING) -> FirestoreQuery:
|
|
query["orderBy"] = [_order_object(field, direction).obj]
|
|
return self
|
|
|
|
|
|
# Order by a set of fields and directions
|
|
# @order_list is an Array of Arrays with the following structure
|
|
# [@field_name , @DIRECTION.[direction]]
|
|
# else, order_object() can be called to return an already parsed Dictionary
|
|
func order_by_fields(order_field_list : Array) -> FirestoreQuery:
|
|
var order_list : Array = []
|
|
for order in order_field_list:
|
|
if order is Array:
|
|
order_list.append(_order_object(order[0], order[1]).obj)
|
|
elif order is Order:
|
|
order_list.append(order.obj)
|
|
query["orderBy"] = order_list
|
|
return self
|
|
|
|
func start_at(value, before : bool) -> FirestoreQuery:
|
|
var cursor : Cursor = _cursor_object(value, before)
|
|
query["startAt"] = { values = cursor.values, before = cursor.before }
|
|
print(query["startAt"])
|
|
return self
|
|
|
|
|
|
func end_at(value, before : bool) -> FirestoreQuery:
|
|
var cursor : Cursor = _cursor_object(value, before)
|
|
query["startAt"] = { values = cursor.values, before = cursor.before }
|
|
print(query["startAt"])
|
|
return self
|
|
|
|
|
|
func offset(offset : int) -> FirestoreQuery:
|
|
if offset < 0:
|
|
print("If specified, offset must be >= 0")
|
|
else:
|
|
query["offset"] = offset
|
|
return self
|
|
|
|
|
|
func limit(limit : int) -> FirestoreQuery:
|
|
if limit < 0:
|
|
print("If specified, offset must be >= 0")
|
|
else:
|
|
query["limit"] = limit
|
|
return self
|
|
|
|
|
|
func aggregate() -> FirestoreAggregation:
|
|
return FirestoreAggregation.new(self)
|
|
|
|
class FirestoreAggregation extends RefCounted:
|
|
var _query: FirestoreQuery
|
|
|
|
func _init(query: FirestoreQuery) -> void:
|
|
_query = query
|
|
|
|
func sum(field: String) -> FirestoreQuery:
|
|
_query.aggregations.push_back({ sum = { field = { fieldPath = field }}})
|
|
return _query
|
|
|
|
func count(up_to: int) -> FirestoreQuery:
|
|
_query.aggregations.push_back({ count = { upTo = up_to }})
|
|
return _query
|
|
|
|
func average(field: String) -> FirestoreQuery:
|
|
_query.aggregations.push_back({ avg = { field = { fieldPath = field }}})
|
|
return _query
|
|
|
|
# UTILITIES ----------------------------------------
|
|
|
|
static func _cursor_object(value, before : bool) -> Cursor:
|
|
var parse : Dictionary = Utilities.dict2fields({value = value}).fields.value
|
|
var cursor : Cursor = Cursor.new(parse.arrayValue.values if parse.has("arrayValue") else [parse], before)
|
|
return cursor
|
|
|
|
static func _order_object(field : String, direction : int) -> Order:
|
|
var order : Order = Order.new()
|
|
order.obj = { field = { fieldPath = field }, direction = DIRECTION.keys()[direction] }
|
|
return order
|
|
|
|
|
|
func create_field_filter(field : String, operator : int, value) -> Dictionary:
|
|
return {
|
|
fieldFilter = {
|
|
field = { fieldPath = field },
|
|
op = OPERATOR.keys()[operator],
|
|
value = Utilities.dict2fields({value = value}).fields.value
|
|
} }
|
|
|
|
func create_unary_filter(field : String, operator : int) -> Dictionary:
|
|
return {
|
|
unaryFilter = {
|
|
field = { fieldPath = field },
|
|
op = OPERATOR.keys()[operator],
|
|
} }
|
|
|
|
func create_composite_filter(operator : int, filters : Array) -> Dictionary:
|
|
return {
|
|
compositeFilter = {
|
|
op = OPERATOR.keys()[operator],
|
|
filters = filters
|
|
} }
|
|
|
|
func clean() -> void:
|
|
query = { }
|
|
|
|
func _to_string() -> String:
|
|
var pretty : String = "QUERY:\n"
|
|
for key in query.keys():
|
|
pretty += "- {key} = {value}\n".format({key = key, value = query.get(key)})
|
|
return pretty
|