forked from grahamearley/FirestoreGoogleAppsScript
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathQuery.js
More file actions
189 lines (174 loc) · 6.08 KB
/
Query.js
File metadata and controls
189 lines (174 loc) · 6.08 KB
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
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "_" }] */
/* globals isNumeric_, wrapValue_ */
/**
* This callback type is called `queryCallback`.
* Callback should utilize the Query parameter to send a request and return the response.
*
* @callback queryCallback
* @param {object} query the Structured Query to utilize in the query request {@link FirestoreQuery_}
* @returns [object] response of the sent query
*/
/**
* An object that acts as a Query to be a structured query.
* Chain methods to update query. Must call .execute to send request.
*
* @constructor
* @private
* @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1beta1/StructuredQuery Firestore Structured Query}
* @param {string} from the base collection to query
* @param {queryCallback} callback the function that is executed with the internally compiled query
*/
var FirestoreQuery_ = function (from, callback) {
const this_ = this
// @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1beta1/StructuredQuery#Operator_1 FieldFilter Operator}
const fieldOps = {
'==': 'EQUAL',
'===': 'EQUAL',
'<': 'LESS_THAN',
'<=': 'LESS_THAN_OR_EQUAL',
'>': 'GREATER_THAN',
'>=': 'GREATER_THAN_OR_EQUAL'
}
// @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1beta1/StructuredQuery#Operator_2 FieldFilter Operator}
const unaryOps = {
'nan': 'IS_NAN',
'null': 'IS_NULL'
}
// @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1beta1/StructuredQuery#FieldReference Field Reference}
const fieldRef = function (field) {
return {fieldPath: field}
}
const filter = function (field, operator, value) {
// @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1beta1/StructuredQuery#FieldFilter Field Filter}
if (operator in fieldOps) {
if (value == null) { // Covers null and undefined values
operator = 'null'
} else if (isNaN(value)) { // Covers NaN
operator = 'nan'
} else {
return {
fieldFilter: {
field: fieldRef(field),
op: fieldOps[operator],
value: wrapValue_(value)
}
}
}
}
// @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1beta1/StructuredQuery#UnaryFilter Unary Filter}
if (operator.toLowerCase() in unaryOps) {
return {
unaryFilter: {
field: fieldRef(field),
op: unaryOps[operator]
}
}
}
throw new Error('Invalid Operator given ' + operator)
}
const query = {}
if (from) {
query.from = [{collectionId: from}]
}
/**
* Select Query which can narrow which fields to return.
* Can be repeated if multiple fields are needed in the response.
*
* @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1beta1/StructuredQuery#Projection Select}
* @param {string} field The field to narrow down (if empty, returns name of document)
* @returns {object} this query object for chaining
*/
this.select = function (field) {
if (!query.select) {
query.select = {fields: []}
}
if (!field || !field.trim()) { // Catch undefined or blank strings
field = '__name__'
}
query.select.fields.push(fieldRef(field))
return this_
}
/**
* Filter Query by a given field and operator (or additionally a value).
* Can be repeated if multiple filters required.
* Results must satisfy all filters.
*
* @param {string} field The field to reference for filtering
* @param {string} operator The operator to filter by. {@link fieldOps} {@link unaryOps}
* @param {*} [value] Object to set the field value to. Null if using a unary operator.
* @returns {object} this query object for chaining
*/
this.where = function (field, operator, value) {
if (query.where) {
if (!query.where.compositeFilter) {
query.where = {
compositeFilter: {
op: 'AND', // Currently "OR" is unsupported
filters: [
query.where
]
}
}
}
query.where.compositeFilter.filters.push(filter(field, operator, value))
} else {
query.where = filter(field, operator, value)
}
return this_
}
/**
* Orders the Query results based on a field and specific direction.
* Can be repeated if additional ordering is needed.
*
* @see {@link https://firebase.google.com/docs/firestore/reference/rest/v1beta1/StructuredQuery#Projection Select}
* @param {string} field The field to order by.
* @param {string} dir The direction to order the field by. Should be one of "asc" or "desc". Defaults to Ascending.
* @returns {object} this query object for chaining
*/
this.orderBy = function (field, dir) {
if (!query.orderBy) {
query.orderBy = []
}
const isDesc = dir && (dir.substr(0, 3).toUpperCase() === 'DEC' || dir.substr(0, 4).toUpperCase() === 'DESC')
query.orderBy.push({
field: fieldRef(field),
direction: isDesc ? 'DESCENDING' : 'ASCENDING'
})
return this_
}
/**
* Offsets the Query results by a given number of documents.
*
* @param {number} offset Number of results to skip
* @returns {object} this query object for chaining
*/
this.offset = function (offset) {
if (!isNumeric_(offset)) {
throw new TypeError('Offset is not a valid number!')
}
query.offset = offset
return this_
}
/**
* Limits the amount Query results returned.
*
* @param {number} limit Number of results limit
* @returns {object} this query object for chaining
*/
this.limit = function (limit) {
if (!isNumeric_(limit)) {
throw new TypeError('Limit is not a valid number!')
}
query.limit = limit
return this_
}
/**
* Executes the query with the given callback method and the generated query.
* Must be used at the end of any query for execution.
*
* @returns {object} The query results from the execution
*/
this.execute = function () {
return callback(query) // Not using callback.bind due to debugging limitations of GAS
}
}