Open sakhmedbayev opened 6 years ago
Search is a very expensive operation especially when it is triggered sitewide. We must carefully plan how and what to show in the results.
If u use Dgraph as standard, u can take advantage of the search system built into their database. It has all sorts of indexing and etc. And it is easy to reconcile with this kit. You can index gigantic texts (Like blog) completely and it handles this with extreme ease.
This might help...
export default function filterBuilder(queryBuilder, args) {
let { filters } = args;
console.log("FILTERS", filters)
// add filter conditions
if (filters) {
let first = true;
for (let filter of filters) {
// Pre Filters Recursion
if (filter.prefilters) {
if (first) {
first = false
queryBuilder.where( function() {
filterBuilder(this, { filters: filter.prefilters })
})
} else {
if (filter.filterBool === 'and') {
queryBuilder.andWhere( function() {
filterBuilder(this, { filters: filter.prefilters })
})
} else if (filter.filterBool === 'or') {
queryBuilder.orWhere( function() {
filterBuilder(this, { filters: filter.prefilters })
})
} else {
// Default to OR
queryBuilder.orWhere( function() {
filterBuilder(this, { filters: filter.prefilters })
})
}
}
}
// This Filter Visitation
if (filter.field) {
let column = filter.field;
if (filter.table) {
column = filter.table + "." + column
}
column = decamelize(column)
let compare = '='
if (filter.compare) {
compare = filter.compare
}
let value = filter.value ? filter.value : filter.values
if(!value) {
value = filter.timeValue ? filter.timeValue : filter.timeValues
if(!value) {
value = filter.intValue ? filter.intValue : filter.intValues
}
if(!value) {
value = filter.floatValue ? filter.floatValue : filter.floatValues
}
if(!value) {
value = filter.boolValue ? filter.boolValue : filter.boolValues
}
}
if (first) {
first = false
queryBuilder.where(column, compare, value);
} else {
if (filter.bool === 'and') {
queryBuilder.andWhere(column, compare, value);
} else if (filter.bool === 'or') {
queryBuilder.orWhere(column, compare, value);
} else {
// Default to OR
queryBuilder.orWhere(column, compare, value);
}
}
}
// Post Filters Recursion
if (filter.postfilters) {
if (first) {
first = false
queryBuilder.where( function() {
filterBuilder(this, { filters: filter.postfilters })
})
} else {
if (filter.filterBool === 'and') {
queryBuilder.andWhere( function() {
filterBuilder(this, { filters: filter.postfilters })
})
} else if (filter.filterBool === 'or') {
queryBuilder.orWhere( function() {
filterBuilder(this, { filters: filter.postfilters })
})
} else {
// Default to OR
queryBuilder.orWhere( function() {
filterBuilder(this, { filters: filter.postfilters })
})
}
}
}
}
}
return queryBuilder
}
export default class User {
async list(args, trx) {
try {
let queryBuilder = knex
.select(...selectFields)
.from('users AS u')
.leftJoin('user_profile AS p', 'p.user_id', 'u.id');
// add filter conditions
queryBuilder = filterBuilder(queryBuilder, args)
// paging and ordering
queryBuilder = paging(queryBuilder, args)
queryBuilder = ordering(queryBuilder, args)
if (trx) {
queryBuilder.transacting(trx);
}
let rows = await queryBuilder;
return camelizeKeys(rows);
} catch (e) {
log.error('Error in User.list', e);
throw e;
}
}
...
}
input FilterInput {
### Whoa deja vue thinking about subfilters while looking at...
#
# http://knexjs.org/#Builder-where -- Grouped Chain
# and the "filterBuilder" I was working on
# This should happen first
prefilters: [FilterInput]
# search by username, email, or any column
type: String
bool: String
table: String
field: String
compare: String
value: String
values: [String]
intValue: Int
intValues: [Int]
floatValue: Float
floatValues: [Float]
boolValue: Boolean
boolValues: [Boolean]
# This should happen last
postfilters: [FilterInput]
}
{
users(limit:10 filters:[
{
prefilters:[
{
table:"u"
field:"email"
compare:"like"
value:"%admin%"
},
{
bool: "or"
table:"p"
field:"displayName"
compare:"like"
value:"%Boss%"
}
]
},
{
bool:"and"
table:"u"
field:"createdAt"
compare:"between"
values: ["2017-12-21 05:00:00","2017-12-21 05:00:54"]
}
]){
email
createdAt
profile{
displayName
}
}
}
export default function paging(queryBuilder, args) {
const { offset, limit } = args;
if (offset) {
queryBuilder.offset(offset);
}
if (limit) {
queryBuilder.limit(limit);
}
return queryBuilder
}
import { decamelize } from 'humps';
export default function ordering(queryBuilder, args) {
let { orderBys } = args;
// add order by
if (orderBys) {
for (let orderBy of orderBys) {
if (orderBy && orderBy.column) {
let column = orderBy.column;
if (orderBy.table) {
column = orderBy.table + "." + column
}
column = decamelize(column)
let order = 'asc';
if (orderBy.order) {
order = orderBy.order;
}
queryBuilder.orderBy(column, order);
}
}
}
return queryBuilder;
}
Thank you for sharing @verdverm. Is there any way I can look the final product of this implementation?
I'd say it's more of an adapter for resolvers. The filterBuilder above is a more general filtering scheme based on the way the orderBy was set up. If you look at the current User.list
resolver, this is basically the same code. I just went nuts on the filter to make it more flexible and pulled the code out into functions we can use elsewhere.
As of now, you can use the filter adapter to combine filters in interesting ways, depending on the context the user is searching from. A site wide search could have its own resolver, which uses the several filtered loaders. Then you have to organize and rank results somehow. That's the Google secret sauce.
As far as something you can just use out of the box, there probably isn't something yet. I've been working from the graphiql interface, so the frontend of my auth-upgrades
branch hasn't caught up. I'm starting to break up my work so that we can merge it in smaller chunks. The above is one of those pieces. (A set of sql helpers) I imagine we will have a similar adapter for other storage engines. It would be cool to work towards some search input type that can be used against multiple storage engines. Something like this: https://github.com/GraphQLGuide/all-the-databases/
This is not an issue rather an enhancement proposal. It would be nice if the kit features some kind of site search solution. I am currently researching over Algolia. I will see if I will manage to implement something that is worthy of a PR.