added firebase and rudimentary leaderboard support
This commit is contained in:
255
addons/godot-firebase/firestore/firestore_query.gd
Normal file
255
addons/godot-firebase/firestore/firestore_query.gd
Normal file
@@ -0,0 +1,255 @@
|
||||
## @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
|
||||
Reference in New Issue
Block a user