// TODO: Does it really make sense to be merging in the backendSettings here? Shouldn't that happen automatically in some way for *every* backend, rather than just the PostgreSQL one specifically? As backend settings are a generic backend feature
// NOTE: We look up by alias, since this is an upsert - and so if the specified ID already exists as an alias, we should update the existing item instead of creating a new one with the specified (aliased) ID
returndb.Alias
.relatedQuery("item",tx)
.for(id);
}).then((existingItems)=>{
letexistingItem=existingItems[0];
letactualID=(existingItem!=null)
?existingItem.id
:id;
letexistingData=(existingItem!=null)
?existingItem.data
:{};
// Make sure to add a self:self alias
letallAliases=aliases.concat([actualID]);
letnewItem={
id:actualID,
data:update(existingData),
createdBy:parentID,
tags:tags.map((tag)=>({name:tag})),
aliases:allAliases.map((alias)=>({alias:alias})),
updatedAt:newDate()
};
returnPromise.try(()=>{
if(allowUpsert){
// NOTE: We *always* do upserts here, even if the user specified `data` rather than `update`, because tags and aliases should always be added even if the item itself already exists. We trust the user not to accidentally reuse IDs between different kinds of objects (which would break in various other ways anyway).
returndb.Item.query(tx).upsertGraph(newItem,{
insertMissing:true,
noDelete:true
});
}else{
returndb.Item.query(tx).insertGraph(newItem,{
insertMissing:true
});
}
}).tap(()=>{
// FIXME: We should probably move the metrics stuff to the wrapper instead, so that it works for *any* backend
metrics.storedItems.inc(1);
// TODO: This currently produces somewhat misleading metrics; it only counts *explicitly specified* tags. That will *mostly* correlate to amount of genuinely-new items, but not perfectly. In the future, we should probably refactor the insertion code such that it is aware of the *current* tags of an item that it is about to merge into - but maybe that should be delayed until the zapdb migration.
// TODO: Make stuff like `this.getItem` roundtrip through the backend abstraction instead of directly calling internal database methods, eg. by providing the backend itself as an argument to the method
returnPromise.all([
this.getItem(tx,{id:from,optional:true}),
this.getItem(tx,{id:into,optional:true}),
]).then(([fromObj,maybeIntoObj])=>{
// NOTE: If the source item does not exist, we silently ignore that. This is to ensure that opportunistic renaming of items (eg. after a naming convention change which only affects older items) in scraper configurations is possible.