@shelf/es-painless-fields
Version:
Helpers for bulk update Elasticsearch documents by query using Painless scripts
269 lines (262 loc) • 9.07 kB
JavaScript
import { flatten, unflatten } from 'flat';
const BRACKET_NOTATION_REGEX = /\.\[/gm;
const INLINE_SCRIPT_REGEX = /\n\s{1,}/g;
const BRACKETS_SPLIT_REGEX = /\['[^[\]]*'\]/gm;
const main = {
set(fieldsMap = {}) {
const source = Object.keys(fieldsMap)
.map(key => `ctx._source.${key} = params.${key};`)
.join(' ');
return {
lang: 'painless',
source,
params: unflatten(fieldsMap),
};
},
setNotFlattened(fieldsMap = {}, safe) {
const flatFieldsMap = flatten(fieldsMap, {
safe: true,
transformKey: key => `['${key}']`,
});
const brackets = Object.keys(flatFieldsMap).map(convertToBracketNotation);
let prefix = '';
if (safe) {
prefix = assertNullKeys(brackets);
}
const source = prefix + brackets.map(bracket => `ctx._source${bracket} = params${bracket};`).join(' ');
return {
lang: 'painless',
source,
params: fieldsMap,
};
},
unset(fields = []) {
const source = fields.map(key => `ctx._source.remove('${key}')`).join('; ');
return {
lang: 'painless',
source,
};
},
replace(fieldsReplacements = []) {
const source = fieldsReplacements
.map((replaceRule, i) => {
const sourceField = `ctx._source.${replaceRule.field}`;
const pattern = `params.patterns[${i}]`;
const substring = `params.substrings[${i}]`;
return `${sourceField} = ${sourceField}.replace(${pattern}, ${substring});`;
})
.join(' ');
return {
lang: 'painless',
source,
params: {
patterns: fieldsReplacements.map(i => i.pattern),
substrings: fieldsReplacements.map(i => i.substring),
},
};
},
replaceSubArray(fieldsReplacements = []) {
const source = fieldsReplacements
.map((replaceRule, i) => {
const sourceField = `ctx._source.${replaceRule.field}`;
const subArray = `params.subArrays[${i}]`;
const newArray = `params.newArrays[${i}]`;
return convertMultilineScriptToInline(`
for (int j=0;j<${subArray}.length;j++) {
if (${sourceField}.contains(${subArray}[j])) {
${sourceField}.remove(${sourceField}.indexOf(${subArray}[j]));
}
}
${sourceField}.addAll(${newArray}); ${sourceField} = ${sourceField}.stream().distinct().collect(Collectors.toList());`);
})
.join(' ');
return {
lang: 'painless',
source,
params: {
subArrays: fieldsReplacements.map(i => i.subArray),
newArrays: fieldsReplacements.map(i => i.newArray),
},
};
},
removeFromArray(fieldsReplacements = []) {
const source = fieldsReplacements
.map((replaceRule, i) => {
const sourceField = `ctx._source.${replaceRule.field}`;
const itemsToRemove = `params.itemsToRemoveArrays[${i}]`;
return convertMultilineScriptToInline(`
for (int j=0;j<${itemsToRemove}.length;j++) {
if (${sourceField}.contains(${itemsToRemove}[j])) {
${sourceField}.remove(${sourceField}.indexOf(${itemsToRemove}[j]));
}
}`);
})
.join(' ');
return {
lang: 'painless',
source,
params: {
itemsToRemoveArrays: fieldsReplacements.map(i => i.itemsToRemove),
},
};
},
increment(fieldsMap = {}) {
const source = Object.keys(fieldsMap)
.map(key => convertMultilineScriptToInline(`
if (ctx._source.${key} == null) {
ctx._source.${key} = params._inc.${key};
} else {
ctx._source.${key} += params._inc.${key};
}
`))
.join(' ');
return {
lang: 'painless',
source,
params: source ? { _inc: unflatten(fieldsMap) } : {},
};
},
decrement(fieldsMap = {}) {
const source = Object.keys(fieldsMap)
.map(key => convertMultilineScriptToInline(`
if (ctx._source.${key} == null) {
ctx._source.${key} = 0;
} else {
ctx._source.${key} -= params._dec.${key};
}
`))
.join(' ');
return {
lang: 'painless',
source,
params: source ? { _dec: unflatten(fieldsMap) } : {},
};
},
multiply(fieldsMap = {}) {
const source = Object.keys(fieldsMap)
.map(key => convertMultilineScriptToInline(`
if (ctx._source.${key} == null) {
ctx._source.${key} = 0;
} else {
ctx._source.${key} *= params._mul.${key};
}
`))
.join(' ');
return {
lang: 'painless',
source,
params: source ? { _mul: unflatten(fieldsMap) } : {},
};
},
divide(fieldsMap = {}) {
const source = Object.keys(fieldsMap)
.map(key => convertMultilineScriptToInline(`
if (ctx._source.${key} == null) {
ctx._source.${key} = 0;
} else {
ctx._source.${key} /= params._div.${key};
}
`))
.join(' ');
return {
lang: 'painless',
source,
params: source ? { _div: unflatten(fieldsMap) } : {},
};
},
updateObjectInArray(updateObjectInArrayParams) {
const { arrayFieldName, targetObject, fieldsToUpdate } = updateObjectInArrayParams;
const sourceArrayField = `ctx._source.${arrayFieldName}`;
const source = convertMultilineScriptToInline(`
if (${sourceArrayField} != null) {
def target = ${sourceArrayField}.find(objectInArray -> objectInArray[params.targetObject.fieldName] == params.targetObject.fieldValue);
if (target != null) {
for (key in params.fieldsToUpdate.keySet()) {
def value = params.fieldsToUpdate[key];
if (target[key] != null && target[key] != value) {
target[key] = value;
}
}
}
}
`);
return {
lang: 'painless',
source,
params: {
targetObject,
fieldsToUpdate,
},
};
},
upsertObjectInArray(upsertObjectInArrayParams) {
const { arrayFieldName, targetObject, fieldsToUpsert } = upsertObjectInArrayParams;
const sourceArrayField = `ctx._source.${arrayFieldName}`;
const source = convertMultilineScriptToInline(`
if (${sourceArrayField} == null) {
${sourceArrayField} = [];
}
def target = ${sourceArrayField}.find(objectInArray -> objectInArray[params.targetObject.fieldName] == params.targetObject.fieldValue);
if (target == null) {
${sourceArrayField}.add(params.fieldsToUpsert);
} else {
for (key in params.fieldsToUpsert.keySet()) {
def value = params.fieldsToUpsert[key];
if (target[key] != null && target[key] != value) {
target[key] = value;
}
}
}
`);
return {
lang: 'painless',
source,
params: {
targetObject,
fieldsToUpsert,
},
};
},
removeObjectFromArray(removeObjectFromArrayParams) {
const { arrayFieldName, targetObject } = removeObjectFromArrayParams;
const sourceArrayField = `ctx._source.${arrayFieldName}`;
const source = convertMultilineScriptToInline(`
if (${sourceArrayField} != null) {
${sourceArrayField}.removeIf(objectInArray -> objectInArray[params.targetObject.fieldName] == params.targetObject.fieldValue);
}
`);
return {
lang: 'painless',
source,
params: {
targetObject,
},
};
},
};
function assertNullKeys(brackets) {
let result = '';
for (const bracket of brackets) {
const match = bracket.match(BRACKETS_SPLIT_REGEX);
let assertKey = ``;
if (!match) {
continue;
}
for (let i = 0; i < match.length - 1; i++) {
const currentMatch = match[i];
assertKey += currentMatch;
result += `if (ctx._source${assertKey} == null) {
ctx._source${assertKey} = [:]
}
`;
}
}
return convertMultilineScriptToInline(result);
}
function convertMultilineScriptToInline(script) {
return script.replace(INLINE_SCRIPT_REGEX, ' ').trim();
}
function convertToBracketNotation(key) {
return key.replace(BRACKET_NOTATION_REGEX, '[');
}
export default main;