flora-solr
Advanced tools
Comparing version 0.6.1 to 0.7.0
'use strict'; | ||
module.exports = function (grunt) { | ||
grunt.initConfig({ | ||
@@ -23,3 +22,3 @@ mochaTest: { | ||
'mocha_istanbul': { | ||
mocha_istanbul: { | ||
coverage: { | ||
@@ -35,3 +34,3 @@ src: 'test', | ||
eslint: { | ||
target: ['*.js'] | ||
target: ['index.js'] | ||
}, | ||
@@ -53,3 +52,2 @@ | ||
grunt.registerTask('test-cov', ['mocha_istanbul:coverage']); | ||
}; |
350
index.js
'use strict'; | ||
var http = require('http'); | ||
var url = require('url'); | ||
var querystring = require('querystring'); | ||
const http = require('http'); | ||
const url = require('url'); | ||
const querystring = require('querystring'); | ||
var _ = require('lodash'); | ||
var errors = require('flora-errors'); | ||
var ImplementationError = errors.ImplementationError; | ||
const _ = require('lodash'); | ||
const { ImplementationError } = require('flora-errors'); | ||
var SUPPORTED_FILTERS = ['equal', 'notEqual', 'lessOrEqual', 'greaterOrEqual', 'range']; | ||
var NO_LIMIT = 1000000; | ||
const SUPPORTED_FILTERS = [ | ||
'equal', | ||
'notEqual', | ||
'lessOrEqual', | ||
'greaterOrEqual', | ||
'range' | ||
]; | ||
/** | ||
* @constructor | ||
* @param {Api} api | ||
* @param {Object} config | ||
*/ | ||
var DataSource = module.exports = function (api, config) { | ||
this.options = config; | ||
}; | ||
const NO_LIMIT = 1000000; | ||
/** | ||
* @public | ||
* Return true if two filters on same attribute can be used as single range filter. | ||
* | ||
* @param {Array.<Object>} filters | ||
* @return {boolean} | ||
* @private | ||
*/ | ||
DataSource.prototype.prepare = function () {}; | ||
function filterRangeQueries(filters) { | ||
if (filters.length !== 2) return false; | ||
/** | ||
* @param {Object} request | ||
* @param {Function} callback | ||
*/ | ||
DataSource.prototype.process = function (request, callback) { | ||
var requestUrl, | ||
server = request.server || 'default', | ||
params = { wt: 'json' }, | ||
queryParts = []; | ||
const rangeOperators = ['lessOrEqual', 'greaterOrEqual']; | ||
const operator1 = filters[0].operator; | ||
const operator2 = filters[1].operator; | ||
if (!this.options.servers[server]) return callback(new Error('Server "' + server + '" not defined')); | ||
return rangeOperators.indexOf(operator1) !== -1 && rangeOperators.indexOf(operator2) !== -1; | ||
} | ||
requestUrl = this.options.servers[server].url + request.collection + '/select'; | ||
if (request.attributes) params.fl = request.attributes.join(','); | ||
if (request.order) params.sort= buildSolrOrderString(request.order); | ||
if (request.search) queryParts.push(escapeValueForSolr(request.search)); | ||
if (request.filter) { | ||
try { | ||
queryParts.push(buildSolrFilterString(request.filter)); | ||
} catch (e) { | ||
return callback(e); | ||
} | ||
} | ||
if (request.queryAddition) queryParts.push(prepareQueryAddition(request.queryAddition)); | ||
if (queryParts.length === 0) queryParts.push('*:*'); | ||
if (!request.limit) request.limit = NO_LIMIT; // overwrite SOLR default limit for sub-resource processing | ||
if (request.page) params.start = (request.page - 1) * request.limit; | ||
if (!request.limitPer) params.rows = request.limit; | ||
else { | ||
params = _.assign(params, { | ||
group: 'true', | ||
'group.format': 'simple', | ||
'group.main': 'true', | ||
'group.field': request.limitPer, | ||
'group.limit': request.limit, | ||
rows: NO_LIMIT // disable default limit because groups are returned as list | ||
}); | ||
} | ||
params.q = queryParts.join(' AND '); | ||
if (request._explain) { | ||
request._explain.url = requestUrl; | ||
request._explain.params = params; | ||
} | ||
querySolr(requestUrl, params, callback); | ||
}; | ||
/** | ||
* @param {Function} callback | ||
* Create range filter from a greatOrEqual and lessOrEqual filter. | ||
* | ||
* @param {Array.<string>} attributeFilters | ||
* @return {Object} | ||
* @private | ||
*/ | ||
DataSource.prototype.close = function (callback) { | ||
// TODO: implement | ||
if (callback) callback(); | ||
}; | ||
function createRangeFilter(attributeFilters) { | ||
const rangeFilter = { attribute: attributeFilters[0].attribute, operator: 'range' }; | ||
/** | ||
* @param {string} value | ||
*/ | ||
DataSource.prototype.escape = escapeValueForSolr; | ||
// make sure greaterOrEqual filter comes first | ||
attributeFilters.sort(filter1 => (filter1.operator === 'greaterOrEqual' ? -1 : 1)); | ||
function buildSolrFilterString(floraFilters) { | ||
var orConditions; | ||
orConditions = floraFilters.map(function (andFilters) { | ||
var conditions; | ||
if (andFilters.length > 1) andFilters = rangify(andFilters); | ||
conditions = andFilters.map(convertFilterToSolrSyntax); | ||
return '(' + conditions.join(' AND ') + ')'; | ||
}); | ||
if (orConditions.length > 1) return '(' + orConditions.join(' OR ') + ')'; | ||
return orConditions.join(''); | ||
rangeFilter.value = [attributeFilters[0].value, attributeFilters[1].value]; | ||
return rangeFilter; | ||
} | ||
@@ -119,15 +63,12 @@ | ||
function rangify(filters) { | ||
var groupedAttrs = _.groupBy(filters, 'attribute'), | ||
rangeQueries = _.filter(groupedAttrs, filterRangeQueries), | ||
rangeQueryAttrs; | ||
const groupedAttrs = _.groupBy(filters, 'attribute'); | ||
const rangeQueries = _.filter(groupedAttrs, filterRangeQueries); | ||
if (! rangeQueries.length) return filters; | ||
if (!rangeQueries.length) return filters; | ||
rangeQueryAttrs = rangeQueries.map(function (rangeQuery) { | ||
return rangeQuery[0].attribute; | ||
}); | ||
const rangeQueryAttrs = rangeQueries.map(rangeQuery => rangeQuery[0].attribute); | ||
return filters.filter(function (filter) { // copy non-range query attributes | ||
return rangeQueryAttrs.indexOf(filter.attribute) === -1; | ||
}) | ||
// copy non-range query attributes | ||
return filters | ||
.filter(filter => rangeQueryAttrs.indexOf(filter.attribute) === -1) | ||
.concat(rangeQueries.map(createRangeFilter)); | ||
@@ -137,36 +78,22 @@ } | ||
/** | ||
* Return true if two filters on same attribute can be used as single range filter. | ||
* | ||
* @param {Array.<Object>} filters | ||
* @return {boolean} | ||
* @param {string} value | ||
* @return {string} | ||
* @private | ||
*/ | ||
function filterRangeQueries(filters) { | ||
var rangeOperators, operator1, operator2; | ||
if (filters.length !== 2) return false; | ||
rangeOperators = ['lessOrEqual', 'greaterOrEqual']; | ||
operator1 = filters[0].operator; | ||
operator2 = filters[1].operator; | ||
return rangeOperators.indexOf(operator1) !== -1 && rangeOperators.indexOf(operator2) !== -1; | ||
function escapeSpecialChars(value) { | ||
const specialCharRegex = /(\\|\/|\+|-|&|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:)/g; | ||
return value.replace(specialCharRegex, '\\$1'); | ||
} | ||
/** | ||
* Create range filter from a greatOrEqual and lessOrEqual filter. | ||
* Escape strings and convert boolean values. | ||
* | ||
* @param {Array.<string>} attributeFilters | ||
* @return {Object} | ||
* @param {*} value | ||
* @return {*} | ||
* @private | ||
*/ | ||
function createRangeFilter(attributeFilters) { | ||
var rangeFilter = { attribute: attributeFilters[0].attribute, operator: 'range' }; | ||
attributeFilters.sort(function (filter1) { // make sure greaterOrEqual filter comes first | ||
return filter1.operator === 'greaterOrEqual' ? -1 : 1; | ||
}); | ||
rangeFilter.value = [attributeFilters[0].value, attributeFilters[1].value]; | ||
return rangeFilter; | ||
function escapeValueForSolr(value) { | ||
if (typeof value === 'string') value = escapeSpecialChars(value); | ||
else if (typeof value === 'boolean') value = value === false ? 0 : 1; | ||
return value; | ||
} | ||
@@ -180,7 +107,7 @@ | ||
function convertFilterToSolrSyntax(filter) { | ||
var value = filter.value, | ||
operator = filter.operator; | ||
let value = filter.value; | ||
const operator = filter.operator; | ||
if (SUPPORTED_FILTERS.indexOf(filter.operator) === -1) { | ||
throw new ImplementationError('DataSource "flora-solr" does not support "' + filter.operator + '" filters'); | ||
throw new ImplementationError(`DataSource "flora-solr" does not support "${filter.operator}" filters`); | ||
} | ||
@@ -199,39 +126,25 @@ | ||
return filter.attribute + ':' + value; | ||
} else { | ||
return '-' + filter.attribute + ':' + value; | ||
} | ||
} else { // convert composite keys to SOLR syntax | ||
return value | ||
.map(function (values) { | ||
var conditions = values.map(function (val, index) { | ||
return filter.attribute[index] + ':' + escapeValueForSolr(val); | ||
}); | ||
return '(' + conditions.join(' AND ') + ')'; | ||
}) | ||
.join(' OR '); | ||
return '-' + filter.attribute + ':' + value; | ||
} | ||
} | ||
/** | ||
* Escape strings and convert boolean values. | ||
* | ||
* @param {*} value | ||
* @return {*} | ||
* @private | ||
*/ | ||
function escapeValueForSolr(value) { | ||
if (typeof value === 'string') value = escapeSpecialChars(value); | ||
else if (typeof value === 'boolean') value = value === false ? 0 : 1; | ||
return value; | ||
// convert composite keys to SOLR syntax | ||
return value | ||
.map((values) => { | ||
const conditions = values.map((val, index) => filter.attribute[index] + ':' + escapeValueForSolr(val)); | ||
return '(' + conditions.join(' AND ') + ')'; | ||
}) | ||
.join(' OR '); | ||
} | ||
/** | ||
* | ||
* @param {string} value | ||
* @return {string} | ||
* @private | ||
*/ | ||
function escapeSpecialChars(value) { | ||
var specialCharRegex = /(\\|\/|\+|-|&|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:)/g; | ||
return value.replace(specialCharRegex, '\\$1'); | ||
function buildSolrFilterString(floraFilters) { | ||
const orConditions = floraFilters.map((andFilters) => { | ||
if (andFilters.length > 1) andFilters = rangify(andFilters); | ||
const conditions = andFilters.map(convertFilterToSolrSyntax); | ||
return '(' + conditions.join(' AND ') + ')'; | ||
}); | ||
if (orConditions.length > 1) return '(' + orConditions.join(' OR ') + ')'; | ||
return orConditions.join(''); | ||
} | ||
@@ -245,6 +158,5 @@ | ||
function buildSolrOrderString(floraOrders) { | ||
var orderCriteria = floraOrders.map(function (order) { | ||
return order.attribute + ' ' + order.direction; | ||
}); | ||
return orderCriteria.join(','); | ||
return floraOrders | ||
.map(order => (order.attribute + ' ' + order.direction)) | ||
.join(','); | ||
} | ||
@@ -275,3 +187,3 @@ | ||
function querySolr(requestUrl, params, callback) { | ||
var options = url.parse(requestUrl); | ||
const options = url.parse(requestUrl); | ||
options.method = 'POST'; | ||
@@ -282,14 +194,12 @@ options.headers = { | ||
var req = http.request(options, function processSolrReponse(res) { | ||
var chunks = []; | ||
const req = http.request(options, (res) => { | ||
const chunks = []; | ||
res.on('data', function (chunk) { | ||
chunks.push(chunk); | ||
}); | ||
res.on('data', chunk => chunks.push(chunk)); | ||
res.on('end', function () { | ||
var data = parseData(Buffer.concat(chunks).toString('utf8')); | ||
res.on('end', () => { | ||
const data = parseData(Buffer.concat(chunks).toString('utf8')); | ||
if (res.statusCode >= 400 || data instanceof Error) { | ||
var error = new Error('Solr error: ' + res.statusCode + ' ' + http.STATUS_CODES[res.statusCode]); | ||
const error = new Error('Solr error: ' + res.statusCode + ' ' + http.STATUS_CODES[res.statusCode]); | ||
if (data instanceof Error) { | ||
@@ -304,3 +214,3 @@ error.message += ': ' + data.message; | ||
callback(null, { totalCount: data.response.numFound, data: data.response.docs }); | ||
return callback(null, { totalCount: data.response.numFound, data: data.response.docs }); | ||
}); | ||
@@ -314,1 +224,87 @@ }); | ||
} | ||
class DataSource { | ||
/** | ||
* @param {Api} api | ||
* @param {Object} config | ||
*/ | ||
constructor(api, config) { | ||
this.options = config; | ||
} | ||
/** | ||
* @public | ||
*/ | ||
prepare() { | ||
} | ||
/** | ||
* @param {Object} request | ||
* @param {Function} callback | ||
*/ | ||
process(request, callback) { | ||
const server = request.server || 'default'; | ||
const queryParts = []; | ||
let params = { wt: 'json' }; | ||
if (!this.options.servers[server]) return callback(new Error(`Server "${server}" not defined`)); | ||
const requestUrl = this.options.servers[server].url + request.collection + '/select'; | ||
if (request.attributes) params.fl = request.attributes.join(','); | ||
if (request.order) params.sort = buildSolrOrderString(request.order); | ||
if (request.search) queryParts.push(escapeValueForSolr(request.search)); | ||
if (request.filter) { | ||
try { | ||
queryParts.push(buildSolrFilterString(request.filter)); | ||
} catch (e) { | ||
return callback(e); | ||
} | ||
} | ||
if (request.queryAddition) queryParts.push(prepareQueryAddition(request.queryAddition)); | ||
if (queryParts.length === 0) queryParts.push('*:*'); | ||
// overwrite SOLR default limit for sub-resource processing | ||
if (!request.limit) request.limit = NO_LIMIT; | ||
if (request.page) params.start = (request.page - 1) * request.limit; | ||
if (!request.limitPer) params.rows = request.limit; | ||
else { | ||
params = _.assign(params, { | ||
group: 'true', | ||
'group.format': 'simple', | ||
'group.main': 'true', | ||
'group.field': request.limitPer, | ||
'group.limit': request.limit, | ||
rows: NO_LIMIT // disable default limit because groups are returned as list | ||
}); | ||
} | ||
params.q = queryParts.join(' AND '); | ||
if (request._explain) { | ||
request._explain.url = requestUrl; | ||
request._explain.params = params; | ||
} | ||
return querySolr(requestUrl, params, callback); | ||
} | ||
/** | ||
* @param {Function} callback | ||
*/ | ||
close(callback) { | ||
// TODO: implement | ||
if (callback) callback(); | ||
} | ||
/** | ||
* @param {string} value | ||
*/ | ||
escape(...args) { | ||
return escapeValueForSolr(...args); | ||
} | ||
} | ||
module.exports = DataSource; |
{ | ||
"name": "flora-solr", | ||
"version": "0.6.1", | ||
"version": "0.7.0", | ||
"description": "Solr connection for Flora", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "grunt test" | ||
"test": "grunt test", | ||
"lint": "grunt lint" | ||
}, | ||
@@ -15,6 +16,3 @@ "author": { | ||
"license": "MIT", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/godmodelabs/flora-solr.git" | ||
}, | ||
"repository": "godmodelabs/flora-solr", | ||
"contributors": [ | ||
@@ -35,6 +33,6 @@ { | ||
"engines": { | ||
"node": ">=0.10.0" | ||
"node": ">=6" | ||
}, | ||
"dependencies": { | ||
"flora-errors": "^0.6.0", | ||
"flora-errors": "^0.7.0", | ||
"lodash": "^4.0.0" | ||
@@ -44,3 +42,5 @@ }, | ||
"chai": "^3.4.1", | ||
"eslint": "^3.2.2", | ||
"eslint": "^3.11.1", | ||
"eslint-config-airbnb-base": "^11.0.0", | ||
"eslint-plugin-import": "^2.2.0", | ||
"grunt": "^1.0.1", | ||
@@ -51,3 +51,3 @@ "grunt-cli": "^1.2.0", | ||
"grunt-mocha-istanbul": "^5.0.1", | ||
"grunt-mocha-test": "^0.12.7", | ||
"grunt-mocha-test": "^0.13.2", | ||
"istanbul": "^0.4.2", | ||
@@ -57,5 +57,4 @@ "load-grunt-tasks": "^3.4.0", | ||
"mocha-bamboo-reporter": "^1.1.0", | ||
"nock": "8.0.x", | ||
"when": "^3.7.7" | ||
"nock": "9.0.x" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
13337
15
294
1
2
+ Addedflora-errors@0.7.1(transitive)
+ Addedhas@1.0.4(transitive)
- Removedflora-errors@0.6.0(transitive)
Updatedflora-errors@^0.7.0