Replacing multiline with sed - sed

I want to replace
#Column("uuid", {
primary: true,
name: "id",
default: () => "uuid_generate_v4()",
})
with
#PrimaryGeneratedColumn
in a file with the following content:
export class SomeClass {
#Column("uuid", {
primary: true,
name: "id",
default: () => "uuid_generate_v4()",
})
id: string;
#Column("timestamp with time zone", {
name: "created_at",
default: () => "now()",
})
created_at: Date;
#Column("timestamp with time zone", {
name: "updated_at",
default: () => "now()",
})
updated_at: Date;
#Column("uuid", { name: "other_id", unique: true })
other_id: string;
}
This is my current solution:
sed -E -i '' -e '/#Column\(\"uuid\", \{/,/\}\)?/c #PrimaryGeneratedColumn\(\"uuid\"\)' fileName
It basically works, but it's too inaccurate and also replaces other lines that shouldn't be replaced. I tried to add uuid_generate_v4() but couldn't get it to work, probably because of the line breaks.

This might work for you (GNU sed):
sed '/#Column("uuid"/{:a;N;/^\s*})$/M!ba;/"uuid_generate_v4()/s/.*/#PrimaryGeneratedColum/}' file
Gather up lines between those containing #Column("uuid" and }) and if those lines also contain uid_generate_v4(), replace all them by PrimaryGeneratedColum.

Related

How to create or update within object of objects

I'm trying to access a database with the following difficult schema and want to update the file_name value if it exists, or create a new object with the file_name if it does not. As I have searched, the option { upsert: true, new: true, setDefaultsOnInsert: true }; will update do the second bit, but the problem is how do you access the file_name key nested deep within.
export type _ID = string;
export interface FileSchema {
[_id: _ID]: {
file_name: string;
creation_date: Date;
isPublished: boolean;
content: string;
};
}
export interface AccountSchema {
...
files: FileSchema;
}
const accountSchema = new Schema<AccountSchema>({
...
files: {
type: Map,
unique: true,
of: {
file_name: {
type: String,
required: true,
minlength: 4,
maxlength: 60,
unique: true,
},
creation_date: {
type: Date,
required: true,
},
isPublished: {
type: Boolean,
required: true,
},
content: {
type: String,
required: true,
},
},
},
});
Not familiar with Mongoose but you may be missing dot notation to access nested fields, look at
https://www.mongodb.com/docs/manual/tutorial/query-embedded-documents/#specify-equality-match-on-a-nested-field
I've searched a bit, and have come up with the solution below, although not pretty due to multiple queries. For nested objects with a dynamic key, you must use codes like [files.${file_id}.file_name] (with ``) to access the object within.
Reference 1
Reference 2
const accountSchema = new Schema<AccountSchema>({
...
files: {
type: Schema.Types.Map,
unique: true,
of: Object,
},
})
// checks if file exists, if exist then update file_name
const files: FileSchema | null = await DB_ACCOUNT.findOneAndUpdate(
{
_id,
username,
[`files.${file_id}`]: { $exists: true },
},
{
$set: {
[`files.${file_id}.file_name`]: file_name,
},
},
{ new: true, upsert: false, useFindAndModify: false }
);
const fn = genObjectId().toString();
// if file does not exist, then create a new file
const x = await DB_ACCOUNT.findOneAndUpdate(
{
_id,
username,
},
{
$set: {
[`files.${fn}`]: {
file_name,
creation_date: new Date(),
isPublished: false,
content: "",
},
},
},
{ new: true, upsert: true }
)
console.log("X: ", x);

Look up and create or update object inside array

I am currently trying to setup a schema for custom Discord guild commands:
const GuildCommandsSchema = new mongoose.Schema({
_id: String,
commands: [
{
name: {
type: String,
unique: true,
required: true,
},
action: {
type: String,
required: true,
},
author: {
type: String,
required: true,
},
},
],
});
Is this ok, performancewise, or could I improve it?
I feel like Mongo would need to look through all commands, since it can't index any commands inside 'commands' even though 'name' is unique.
If that's fine, how can I access the values inside commands?
I would need to find the right command via 'name' if it exists, otherwise create it and add/update 'action' + 'author'.
I tried something like this:
const updatedCommand = await GuildCommands.findOneAndUpdate(
{ _id },
{
$set: {
[`commands.$[outer].name`]: name,
[`commands.$[outer].action`]: action,
[`commands.$[outer].author`]: author,
},
},
{
arrayFilters: [{ 'outer.name': name }],
}
);
Unfortunately that does not create commands if they don't exist.
Thanks for your help
aggregate
db.collection.update({},
{
$set: {
"commands.$[c].name": "1",
"commands.$[c].author": "1",
"commands.$[c].action": "1"
}
},
{
arrayFilters: [
{
"c.author": "34"
}
],
multi: true
})
mongoplayground
To answer my own question:
I changed my Schema to use Maps instead of Arrays for performance improvments and also better model management.
const GuildCommandsSchema = new mongoose.Schema(
{
_id: String,
commands: {
type: Map,
of: {
_id: false,
name: {
type: String,
required: true,
},
action: {
type: String,
required: true,
},
active: {
type: Boolean,
required: true,
default: true,
},
author: {
type: String,
required: true,
},
},
},
},
{ versionKey: false }
);
The new query to find and update/create a command is also better imo:
const findCommand = await GuildCommands.findOne({ _id });
if (!action) {
const getCommand = findCommand.commands.get(name);
if (getCommand) {
message.reply(getCommand.action);
} else {
message.reply(`Cannot find ${name}`);
}
} else {
findCommand.commands.set(name, {
name,
action,
author,
});
findCommand.save();
}

Mongoose findOneAndUpdate + upsert always replaces existing document

I have a collection I want to upsert with findOneAndUpdate. In addition to that I have two fields (isHandled, isNotADuplicate) that should be:
defaulted to 'false' upon insert
left untouched upon update (e.g. isHandled stays 'true')
I have however found that
isHandled, isNotADuplicate are always defaulted back to 'false'
_id is also regenerated upon every update (I use a compound key to query the doc, not _id)
My Model
export const QuickbrainFindingSchema = new Schema<QuickBrainFindingDocument>({
connectedApplicationType: { type: String, required: true, enum: ['jira'] },//e.g. jira
clientKey: { type: String, required: true },//e.g. 135eb702-156c-3b67-b9d0-a0c97548xxxx
//key
projectKey: { type: String, required: true },//e.g. AL
type: { type: String, required: true },
doc1key: { type: String, required: true },//e.g. AL-7
doc2key: { type: String, required: true },//e.g. AL-16
//data
calculationDate: { type: SchemaTypes.Date, default: Date.now },
direction: { type: String, required: true },
reasonAndMetric: { type: SchemaTypes.Mixed, reason: true },
scoreSummary: { type: String, reason: true },
isHandled: { type: SchemaTypes.Boolean, default: false },
isNotADuplicate: { type: SchemaTypes.Boolean, default: false },
similarityReference: { type: SchemaTypes.ObjectId, required: true, ref: "QuickbrainSimilarityMatrix" }
}, {
//options
});
QuickbrainFindingSchema.index(
{ connectedApplicationType: 1, clientKey: 1, project: 1, doc1key: 1, doc2key: 1, type: 1 },
{ unique: true, name: "compoundKey" }
);
export const QuickbrainFindingModel = model<QuickBrainFindingDocument>("QuickbrainFinding", QuickbrainFindingSchema);
My Code
public async addFinding(
projectKey: string,
doc1key: string,
doc2key: string,
type: ET_FindingType
, data: QuickbrainFindingData): Promise<QuickbrainFinding> {
let keyFull: QuickbrainFindingKey = {
connectedApplicationType: this.connectedApplicationType,
clientKey: this.clientKey,
projectKey: projectKey,
doc1key: doc1key,
doc2key: doc2key,
type: type
};
let insertObj: QuickbrainFinding = <QuickbrainFinding><unknown>{};
Object.assign(insert, keyFull);
Object.assign(insert, data);
delete (<any>insertObj).isHandled;
delete (<any>insertObj).isNotADuplicate;
return new Promise<QuickbrainFinding>(function (ok, nok) {
QuickbrainFindingModel.findOneAndUpdate(
keyFull, { $set: insertObj},
{
runValidators: true,
upsert: true,
setDefaultsOnInsert: true,
new: true,
omitUndefined: true,//I think only available for findAndReplace(..)
})
.lean().exec(function (err, result) {
if (err) {
nok(err);
}
else
ok(result)
});
});
}
Mongoose Debug Output
quickbrainfindings.findOneAndUpdate(
{
connectedApplicationType: 'jira',
clientKey: '135eb702-256c-3b67-b9d0-a0c975487af3',
projectKey: 'ITSMTEST',
doc1key: 'ITSMTEST-7',
doc2key: 'ITSMTEST-10',
type: 'Email'
},
{
'$setOnInsert':
{ __v: 0, isHandled: false, isNotADuplicate: false, _id: ObjectId("60789b02c094eb3ef07d2929") },
'$set': {
connectedApplicationType: 'jira',
clientKey: '135eb702-256c-3b67-b9d0-a0c975487af3', projectKey: 'ITSMTEST', doc1key: 'ITSMTEST-7', doc2key: 'ITSMTEST-10', type: 'Email',
calculationDate: new Date("Thu, 15 Apr 2021 19:58:58 GMT"),
direction: '2', scoreSummary: '100.0%',
similarityReference: ObjectId("60789b029df2079dfa8aa15a"),
reasonAndMetric: [{ reason: 'Title Substring', metricScore: '100%' },
{ reason: 'Title TokenSet', metricScore: '54%' }, { reason: 'Description TokenSet', metricScore: '100%' }]
}
},
{
runValidators: true, upsert: true, remove: false, projection: {},
returnOriginal: false
}
)
What happens
Existing documents are found, but when they are updated I'm confused that:
_id is regenerated
isHandled and isNotADuplicate are reset to 'false' (although insertObj does not contain them)
When looking at the debug output I can see that the new _id is the one fron $setOnInsert, which confuses the heck out of me, since the selector works
Notable
keyFull is used to query the existing document, it does not contain _id;
delete (<any>insertObj).isHandled <- the object used for $set does NOT contain isHandled
This is embarrasing to admit, but thanks to Joe I have found the problem.
Before every findOneAndUpdate / Upsert I had a delete statement removing the existing documents Pipeline:
Delete old documents
Calculate new documents
Upsert new documents -> always resulted in Insert
let matchAnyDoc = this.filterForDocKeyAny(projectKey, docKeyAny, findingType);
matchAnyDoc.forEach(async (condition) => {
QuickbrainFindingModel.deleteMany(condition).exec(function (err, res) {
if (err) {
nok(err);
} else {
ok();
}
});
}, this);

Jest toMatchObject with MongoDB confusing

I have a test on my mongoose model and while one models' tests are running completly fine, another one which is basically a copy - does not work. And honestly I don't understand the problem. I tried to remove some of the properties or add hardcoded values to really match 100% exactly - but somehow i always get a similar error.
The error. What drives me crazy is that I tried to remove/add the "_id" and the "createdOn" field but at least the "_id" always appear in the error. As said above in another model the test does not complain about the "_id" because I do not validate it there...I just don't get it.
insert › Should save a channel
expect(received).toMatchObject(expected)
- Expected - 1
+ Received + 2
## -1,8 +1,9 ##
Object {
+ "_id": "5e962f1dc133d8b92891ddaf",
"createdBy": "5e962f1dc133d8b92891ddae",
- "createdOn": Anything,
+ "createdOn": 2020-04-14T21:46:05.907Z,
"hasAcceptedLegal": false,
"isActive": true,
"isPublic": false,
"members": Array [
"5e962f1dc133d8b92891ddae",
48 | expect(spy).toHaveBeenCalled();
49 |
> 50 | expect(channel).toMatchObject({
| ^
51 | name: mockName,
52 | createdBy: mockCreatedById,
53 | createdOn: expect.anything(),
at Object.it (test/model/channel.model.test.ts:50:21)
The respective model
import mongoose, { Schema, Document } from "mongoose";
import { IUser } from "./user.model";
export interface IChannel extends Document {
name: string;
createdBy: IUser["id"];
createdOn: Date;
isActive: boolean;
isPublic: boolean;
hasAcceptedLegal: boolean;
members: [IUser["id"]];
}
const ChannelSchema: Schema = new Schema({
name: { type: String, required: true },
createdBy: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
createdOn: { type: Date },
isActive: { type: Boolean, default: true },
isPublic: { type: Boolean, default: false },
hasAcceptedLegal: { type: Boolean, default: false },
members: [
{
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
],
});
export default mongoose.model<IChannel>("Channel", ChannelSchema);
The test:
it("Should save a channel", async () => {
expect.assertions(2);
let mockName = Faker.company.companyName();
let mockCreatedById = Types.ObjectId();
let mockCreatedOn = Date.now();
const channel: IChannel = new Channel({
name: mockName,
createdBy: mockCreatedById,
createdOn: mockCreatedOn,
members: [mockCreatedById],
});
const spy = jest.spyOn(channel, "save");
channel.save();
expect(spy).toHaveBeenCalled();
expect(channel).toMatchObject({
name: mockName,
createdBy: mockCreatedById,
createdOn: expect.anything(),
isActive: true,
isPublic: false,
hasAcceptedLegal: false,
members: [mockCreatedById],
});
});
just for reference, I found sth. that the returned mongoose object should be converted with ".toJSON()" and then it works, but even then it was having a problem with the "createdOn" field as it seems to be a formatted Date or sth. like this (no string, parenthesis missing).
What I finally did now was the following and now it works:
it("Should save a channel (required+defaults)", async () => {
expect.assertions(2);
let mockName = Faker.company.companyName();
let mockCreatedById = Types.ObjectId();
let mockCreatedOn = new Date();
const channel: IChannel = new Channel({
name: mockName,
createdBy: mockCreatedById,
createdOn: mockCreatedOn,
members: [mockCreatedById],
});
const spy = jest.spyOn(channel, "save");
channel.save();
expect(spy).toHaveBeenCalled();
expect(channel.toJSON()).toMatchObject({
_id: expect.anything(),
name: mockName,
createdBy: mockCreatedById,
createdOn: expect.anything(),
isActive: true,
isPublic: false,
hasAcceptedLegal: false,
members: expect.arrayContaining([mockCreatedById]),
});
});

Meteor Mongo update ($set object; $push array)

Good Day.
I'm having difficulty figuring out why my mongo isn't updating. Objects and Arrays are not being updated. I've
been trying many avenues. Besides the code below, I've tried having the key already in the document (but empty, so
the array would be keyName:{} , as well as the key not in the document). I've also tried putting the keys within quotes, etc.
I've taken the console output of the update and pasted into Robo3T and ran the query and it updates the document just fine.
meteor 1.6
mongo 3.2.15
Server code running
const dbQuery = {owner: uid.user_id, paymentToken: uid.paymtToken}
exFile = {
$set: {
agreement_id: responseData.id, // string
selfUrl: responseData.links[0].href, // string
agreementDetails: responseData.agreement_details, // object
membershipLevel: 'premium', // string
ppOwnerInfo: responseData.payer, // object
},
};
let subsReturn = MonthlySubs.update(dbQuery, exFile, {multi:false, upsert:false} );
console.log('subsReturn: ', subsReturn); // outputs: 1
This will result in the document being update with everything except the objects. I know that exFile is valid:
console.dir(exFile, {depth: null});
results in:
{ '$set':
{ agreement_id: 'I-SW0AL8YJS',
selfUrl: 'https://api.sandbox.paypal.com/v1/payments/billing-agreements/I-SW0AL',
agreementDetails:
{ outstanding_balance: { value: '0.00' },
cycles_remaining: '1',
cycles_completed: '0',
next_billing_date: '2018-05-05T10:00:00Z',
final_payment_date: '1970-01-01T00:00:00Z',
failed_payment_count: '0' },
membershipLevel: 'premium',
ppOwnerInfo:
{ payment_method: 'paypal',
status: 'verified',
payer_info:
{ email: 'paypal-buyer#tion.com',
first_name: 'test',
last_name: 'buyer',
payer_id: '99CEFGB6L',
shipping_address:
{ recipient_name: 'test buyer',
line1: '1 Main St',
city: 'San Jose',
state: 'CA',
postal_code: '95131',
country_code: 'US' } } } } }
Also having issues with updating an array.
let pushFile = {
$push: {
links: {href: responseData.links[0].href, rel: 'self', method: 'GET', }
}
};
console.dir(pushFile, {depth: null});
subsReturn = MonthlySubs.update(dbQuery, pushFile, {multi:false, upsert:false} );
console.log('subsReturn: ', subsReturn); // outputs: 1
Here's the pushFile contents from the console output:
{ '$push':
{ links:
{ href: 'https://api.sandbox.paypal.com/v1/payments/billing-agreements/I-S45A17AV',
rel: 'self',
method: 'GET' } } }
This too works well in Robo3T.
I'm using simpl-schema:
[snip]
selfUrl: {
type: String,
label: 'Link for details about subscription on Paypal.',
optional: true,
},
ppOwnerInfo: {
type: Object,
label: 'Subscriber name, address, etc. from Paypal. Populates after subscription executed. (payer)',
optional: true,
},
links: {
type: Array,
label: 'Holds various Paypal endpoints. Optional because not all inserts/updates have these.',
optional: true,
},
[/snip]
In your schema you need to set blackbox: true for ppOwnerInfo. Likely also for agreementDetails see docs