Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
50 changes: 3 additions & 47 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import _ from 'lodash'
import queryBuilder from './query-builder'
import filterBuilder from './filter-builder'
import aggregationBuilder from './aggregation-builder'
import { sortMerge } from './utils'
import { sortMerge, build, buildV1 } from './utils'

/**
* **http://bodybuilder.js.org**
Expand Down Expand Up @@ -140,10 +140,10 @@ export default function bodybuilder () {
const aggregations = this.getAggregations()

if (version === 'v1') {
return _buildV1(body, queries, filters, aggregations)
return buildV1(body, queries, filters, aggregations)
}

return _build(body, queries, filters, aggregations)
return build(body, queries, filters, aggregations)
}

},
Expand All @@ -153,48 +153,4 @@ export default function bodybuilder () {
)
}

function _buildV1(body, queries, filters, aggregations) {
let clonedBody = _.cloneDeep(body)

if (!_.isEmpty(filters)) {
_.set(clonedBody, 'query.filtered.filter', filters)

if (!_.isEmpty(queries)) {
_.set(clonedBody, 'query.filtered.query', queries)
}

} else if (!_.isEmpty(queries)) {
_.set(clonedBody, 'query', queries)
}

if (!_.isEmpty(aggregations)) {
_.set(clonedBody, 'aggregations', aggregations)
}
return clonedBody
}

function _build(body, queries, filters, aggregations) {
let clonedBody = _.cloneDeep(body)

if (!_.isEmpty(filters)) {
let filterBody = {}
let queryBody = {}
_.set(filterBody, 'query.bool.filter', filters)
if (!_.isEmpty(queries.bool)) {
_.set(queryBody, 'query.bool', queries.bool)
} else if (!_.isEmpty(queries)) {
_.set(queryBody, 'query.bool.must', queries)
}
_.merge(clonedBody, filterBody, queryBody)
} else if (!_.isEmpty(queries)) {
_.set(clonedBody, 'query', queries)
}

if (!_.isEmpty(aggregations)) {
_.set(clonedBody, 'aggs', aggregations)
}

return clonedBody
}

module.exports = bodybuilder
71 changes: 65 additions & 6 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,24 +104,39 @@ function unwrap (arr) {
return arr.length > 1 ? arr : _.last(arr)
}

const nestedTypes = ['nested', 'has_parent', 'has_child']

export function pushQuery (existing, boolKey, type, ...args) {
const nested = {}
if (_.isFunction(_.last(args))) {
const isNestedType = _.includes(nestedTypes, _.snakeCase(type))
const nestedCallback = args.pop()
// It is illogical to add a query nested inside a filter, because its
// scoring won't be taken into account by elasticsearch. However we do need
// to provide the `query` methods in the context of joined queries for
// backwards compatability.
const nestedResult = nestedCallback(
Object.assign(
{},
filterBuilder({ isInFilterContext: this.isInFilterContext }),
this.isInFilterContext
(this.isInFilterContext && !isNestedType)
? {}
: queryBuilder({ isInFilterContext: this.isInFilterContext })
)
)
if (!this.isInFilterContext && nestedResult.hasQuery()) {
nested.query = nestedResult.getQuery()
}
if (nestedResult.hasFilter()) {
nested.filter = nestedResult.getFilter()
if (isNestedType) {
nested.query = build(
{},
nestedResult.getQuery(),
nestedResult.getFilter()
).query
} else {
if (!this.isInFilterContext && nestedResult.hasQuery()) {
nested.must = nestedResult.getQuery()
}
if (nestedResult.hasFilter()) {
nested.filter = nestedResult.getFilter()
}
}
}

Expand All @@ -141,3 +156,47 @@ export function pushQuery (existing, boolKey, type, ...args) {
)
}
}

export function buildV1(body, queries, filters, aggregations) {
let clonedBody = _.cloneDeep(body)

if (!_.isEmpty(filters)) {
_.set(clonedBody, 'query.filtered.filter', filters)

if (!_.isEmpty(queries)) {
_.set(clonedBody, 'query.filtered.query', queries)
}

} else if (!_.isEmpty(queries)) {
_.set(clonedBody, 'query', queries)
}

if (!_.isEmpty(aggregations)) {
_.set(clonedBody, 'aggregations', aggregations)
}
return clonedBody
}

export function build(body, queries, filters, aggregations) {
let clonedBody = _.cloneDeep(body)

if (!_.isEmpty(filters)) {
let filterBody = {}
let queryBody = {}
_.set(filterBody, 'query.bool.filter', filters)
if (!_.isEmpty(queries.bool)) {
_.set(queryBody, 'query.bool', queries.bool)
} else if (!_.isEmpty(queries)) {
_.set(queryBody, 'query.bool.must', queries)
}
_.merge(clonedBody, filterBody, queryBody)
} else if (!_.isEmpty(queries)) {
_.set(clonedBody, 'query', queries)
}

if (!_.isEmpty(aggregations)) {
_.set(clonedBody, 'aggs', aggregations)
}

return clonedBody
}
124 changes: 91 additions & 33 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ test('bodyBuilder should make this chained nested query', (t) => {
})

test('bodyBuilder should create this big-ass query', (t) => {
t.plan(1)
t.plan(4)

const result = bodyBuilder().query('constant_score', (q) => {
return q
Expand All @@ -352,47 +352,60 @@ test('bodyBuilder should create this big-ass query', (t) => {
})
})

t.deepEqual(result.getQuery(), {
const firstShould = {
term: {
'created_by.user_id': 'abc'
}
}

const secondShould = {
nested: {
path: 'doc_meta',
query: {
constant_score: {
filter: {
term: {
'doc_meta.user_id': 'abc'
}
}
}
}
}
}
const thirdShould = {
nested: {
path: 'tests',
query: {
constant_score: {
filter: {
term: {
'tests.created_by.user_id': 'abc'
}
}
}
}
}
}

const resultQuery = result.getQuery()

t.deepEqual(resultQuery, {
constant_score: {
filter: {
bool: {
should: [
{
term: {
'created_by.user_id': 'abc'
}
}, {
nested: {
path: 'doc_meta',
query: {
constant_score: {
filter: {
term: {
'doc_meta.user_id': 'abc'
}
}
}
}
}
}, {
nested: {
path: 'tests',
query: {
constant_score: {
filter: {
term: {
'tests.created_by.user_id': 'abc'
}
}
}
}
}
}
firstShould, secondShould, thirdShould
]
}
}
}
})

const resultShould = resultQuery.constant_score.filter.bool.should

t.deepEqual(resultShould[0], firstShould)
t.deepEqual(resultShould[1], secondShould)
t.deepEqual(resultShould[2], thirdShould)
})

test('bodyBuilder should combine queries, filters, aggregations', (t) => {
Expand Down Expand Up @@ -548,6 +561,51 @@ test('bodybuilder | dynamic filter', t => {
})
})

test('bodybuilder | dynamic query', t => {
t.plan(3)

const result = bodyBuilder()
.query('constant_score', f => f.query('term', 'user', 'kimchy'))
.query('term', 'message', 'this is a test')
.build()

t.deepEqual(result,
{
query: {
bool: {
must: [
{
constant_score: {
must: {
term: {
user: 'kimchy'
}
}
}
},
{ term: { message: 'this is a test' } }
]
}
}
})

t.deepEqual(result.query.bool.must[0],
{
constant_score: {
must: {
term: {
user: 'kimchy'
}
}
}
})

t.deepEqual(
result.query.bool.must[1],
{ term: { message: 'this is a test' } }
)
})

test('bodybuilder | complex dynamic filter', t => {
t.plan(3)

Expand Down
51 changes: 51 additions & 0 deletions test/query-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,57 @@ test('queryBuilder | has_parent', (t) => {
})
})

test('queryBuilder | hasParent (valid v2 syntax)', (t) => {
t.plan(1)

const result = queryBuilder().query('hasParent', 'parentTag', 'blog', (q) => {
return q.query('term', 'tag', 'something')
})

t.deepEqual(result.getQuery(), {
hasParent: {
parentTag: 'blog',
query: {
term: { tag: 'something' }
}
}
})
})

test('queryBuilder | has_parent filter v1 syntax', (t) => {
t.plan(1)

const result = queryBuilder().query('hasParent', 'parentTag', 'blog', (q) => {
return q.filter('term', 'tag', 'something')
})

t.deepEqual(result.getQuery('v1'), {
hasParent: {
parentTag: 'blog',
filter: {
term: { tag: 'something' }
}
}
})
})

test('queryBuilder | has_parent filter v1 syntax', (t) => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a typo here v1 syntax

t.plan(1)

const result = queryBuilder().query('hasParent', 'parentTag', 'blog', (q) => {
return q.filter('term', 'tag', 'something')
})

t.deepEqual(result.getQuery(), {
hasParent: {
parentTag: 'blog',
query: { bool: {filter: {
term: { tag: 'something' }
} } }
}
})
})

test('queryBuilder | geo_bounding_box', (t) => {
t.plan(1)

Expand Down