Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
e706db1
Prettier format
schwma Dec 9, 2022
7b7b9d4
Add tests for null and undefined values in args
schwma Dec 9, 2022
cea11c5
Ignore arguments with values of null
schwma Dec 9, 2022
fc3c345
Improve check if literal is a scalar to be parsed
schwma Dec 9, 2022
3f98c53
Replace variable null values with AST NullValues
schwma Dec 9, 2022
cb6ae61
Add convenience AST NullValue value
schwma Dec 9, 2022
588b1f9
Substitute undefined variables with undefined
schwma Dec 9, 2022
01702b2
Ignore arguments with undefined variable values
schwma Dec 9, 2022
6618244
Add changelog entries
schwma Dec 9, 2022
4f9cd84
Merge branch 'main' into null-and-undefined-values-in-arguments
schwma Jan 4, 2023
e945d45
Merge branch 'main' into null-and-undefined-values-in-arguments
schwma Jan 19, 2023
8833e07
Use gql tag in null/undefined tests
schwma Jan 19, 2023
b33986d
Merge branch 'main' into null-and-undefined-values-in-arguments
schwma Jan 23, 2023
9331fe2
Merge branch 'main' into null-and-undefined-values-in-arguments
schwma Jan 24, 2023
0bb2724
Merge branch 'main' into null-and-undefined-values-in-arguments
schwma Feb 27, 2023
dd24d16
Merge branch 'main' into null-and-undefined-values-in-arguments
schwma Feb 27, 2023
df9e5d5
Move changelog entries to newest release
schwma Feb 27, 2023
a894eb0
Merge branch 'main' into null-and-undefined-values-in-arguments
schwma Mar 2, 2023
8a47426
Ignore undefined properties in filter objects
schwma Mar 2, 2023
55840b6
Ignore incomplete filters like `{ ID: { stock: null } }`
schwma Mar 2, 2023
a81f49c
Don't check if parseLiteral function is defined
schwma Mar 2, 2023
66a5cc1
Add tests combining undefined/null and non-undefined/non-null variabl…
schwma Mar 3, 2023
78a3576
Prettier format
schwma Mar 6, 2023
06551f9
Merge branch 'main' into null-and-undefined-values-in-arguments
schwma Mar 10, 2023
a63b839
Create false _xpr for empty filter lists
schwma Mar 10, 2023
4bba695
Adjust tests so that incomplete filters return no results
schwma Mar 16, 2023
28ca365
Also adjust tests without connections
schwma Mar 16, 2023
e33f9a1
Empty filter objects return true _xprs
schwma Mar 17, 2023
83c69b5
Adjust tests so that incomplete filters are ignored
schwma Mar 20, 2023
c4d1339
Remove empty describe block
schwma Mar 20, 2023
7fcce28
Move up filter leaf guard clause
schwma Mar 20, 2023
2a9fd9e
Remove duplicated tests
schwma Mar 23, 2023
7d495b5
Remove insert from variables test
schwma Mar 23, 2023
3ff022f
Add tests for empty filter structures joined with equality filters
schwma Mar 23, 2023
69f229a
Add changelog entry
schwma Mar 23, 2023
741cf09
Split filter tests into two describe blocks
schwma Mar 23, 2023
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Support for filtering by `null` values

### Changed

- Improved handling of `null` and `undefined` values in query arguments
- Empty filter lists resolve to `false` and empty filter objects resolve to `true`

### Fixed

- Handling of GraphQL queries that are sent via `GET` requests using the `query` URL parameter if GraphiQL is enabled
Expand Down
5 changes: 4 additions & 1 deletion lib/resolvers/crud/read.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ module.exports = async (service, entity, selection) => {
query.columns(astToColumns(selection.selectionSet.selections))

const filter = getArgumentByName(args, ARGS.filter)
if (filter) query.where(astToWhere(filter))
if (filter) {
const where = astToWhere(filter)
if (where) query.where(where)
}

const orderBy = getArgumentByName(args, ARGS.orderBy)
if (orderBy) query.orderBy(astToOrderBy(orderBy))
Expand Down
7 changes: 6 additions & 1 deletion lib/resolvers/parse/ast/enrich.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ const _traverseListValue = (info, listValue, _fields) => {
}
}

const _isScalarKind = kind => kind === Kind.INT || kind === Kind.FLOAT || kind === Kind.STRING || kind === Kind.BOOLEAN

const _traverseArgumentOrObjectField = (info, argumentOrObjectField, _fieldOr_arg) => {
const value = argumentOrObjectField.value

const type = _getTypeFrom_fieldOr_arg(_fieldOr_arg)
if (type.parseLiteral && value.kind !== Kind.VARIABLE) value.value = type.parseLiteral(value)
if (_isScalarKind(value.kind)) value.value = type.parseLiteral(value)

switch (value.kind) {
case Kind.VARIABLE:
Expand All @@ -40,6 +42,9 @@ const _traverseArgumentOrObjectField = (info, argumentOrObjectField, _fieldOr_ar
_traverseObjectValue(info, value, type.getFields())
break
}

// Convenience value for both literal and variable values
if (argumentOrObjectField.value?.kind === Kind.NULL) argumentOrObjectField.value.value = null
}

const _traverseSelectionSet = (info, selectionSet, _fields) => {
Expand Down
6 changes: 6 additions & 0 deletions lib/resolvers/parse/ast/fromObject.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const { Kind } = require('graphql')
const { isPlainObject } = require('./util')

const _nullValue = { kind: Kind.NULL }

const _valueToGenericScalarValue = value => ({
kind: 'GenericScalarValue',
value
Expand Down Expand Up @@ -32,6 +34,10 @@ const _variableToValue = variable => {
return _arrayToListValue(variable)
} else if (isPlainObject(variable)) {
return _objectToObjectValue(variable)
} else if (variable === null) {
return _nullValue
} else if (variable === undefined) {
return undefined
}
return _valueToGenericScalarValue(variable)
}
Expand Down
5 changes: 4 additions & 1 deletion lib/resolvers/parse/ast2cqn/utils/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
const getArgumentByName = (args, name) => args.find(arg => arg.name.value === name)
const { Kind } = require('graphql')

const getArgumentByName = (args, name) =>
args.find(arg => arg.value && arg.name.value === name && arg.value.kind !== Kind.NULL)

module.exports = { getArgumentByName }
21 changes: 16 additions & 5 deletions lib/resolvers/parse/ast2cqn/where.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const _objectFieldTo_xpr = (objectField, columnName) => {
}

const _parseObjectField = (objectField, columnName) => {
if (columnName) return _objectFieldTo_xpr(objectField, columnName)

const value = objectField.value
const name = objectField.name.value
switch (value.kind) {
Expand All @@ -40,22 +42,31 @@ const _parseObjectField = (objectField, columnName) => {
case Kind.OBJECT:
return _parseObjectValue(value, name)
}
return _objectFieldTo_xpr(objectField, columnName)
}

const _arrayInsertBetweenFlat = (array, element) =>
array.flatMap((e, index) => (index === array.length - 1 ? [e] : [e, element])).flat()

const _joinedXprFrom_xprs = (_xprs, operator) => ({ xpr: _arrayInsertBetweenFlat(_xprs, operator) })

const _true_xpr = [{ val: 1 }, '=', { val: 1 }]

const _parseObjectValue = (objectValue, columnName) => {
const _xprs = objectValue.fields.map(field => _parseObjectField(field, columnName))
return _xprs.length === 1 ? _xprs[0] : _joinedXprFrom_xprs(_xprs, 'and')
const _xprs = objectValue.fields
.map(field => _parseObjectField(field, columnName))
.filter(field => field !== undefined)
if (_xprs.length === 0) return _true_xpr
else if (_xprs.length === 1) return _xprs[0]
return _joinedXprFrom_xprs(_xprs, 'and')
}

const _false_xpr = [{ val: 0 }, '=', { val: 1 }]

const _parseListValue = (listValue, columnName) => {
const _xprs = listValue.values.map(value => _parseObjectValue(value, columnName))
return _xprs.length === 1 ? _xprs[0] : _joinedXprFrom_xprs(_xprs, 'or')
const _xprs = listValue.values.map(value => _parseObjectValue(value, columnName)).filter(value => value !== undefined)
if (_xprs.length === 0) return _false_xpr
else if (_xprs.length === 1) return _xprs[0]
return _joinedXprFrom_xprs(_xprs, 'or')
}

const astToWhere = filterArg => {
Expand Down
Loading