pg-promise ColumnSet use Postgres functions with def property - javascript

I am using a ColumnSet and the helper.insert function for a multi row insert.
I have a table column where I want to use the Postgres Date/Time now() function.
const cs = new helpers.ColumnSet([
'lastname',
{
name: 'rental_date',
def: 'now()'
}
], { table: { table: 'book_rental', schema: 'public' } })
let rentals = [
{
lastname: 'Mueller'
},
{
lastname: 'Johnson'
}
]
let insert = helpers.insert(rentals, cs)
db.result(insert)
.then(data => res.json({ message: 'Ok!' }))
.catch(err => res.json({ message: 'Not ok!' }))
It seems to be working by using def: 'now()', but I want to make sure that I am using it the right way.
Edit:
Regarding the answer in the comment. I tried to do the insert manually and it looks like Postgres is converting the 'now()' string into the now() function.
INSERT INTO book_rental (lastname, rental_date) VALUES ('Mueller', 'now()');
To involve your answer, am I right that this should be the correct code then?
const cs = new helpers.ColumnSet([
'lastname',
{
name: 'rental_date',
mod: ':raw',
def: 'now()'
}
], { table: { table: 'book_rental', schema: 'public' } })

Your code doesn't look right, for the following reasons:
You want to use now() without any condition, but the def value is only used when the property doesn't exist in the source object (see Column). The init callback is what should be used instead to guarantee the right value override.
You return now() as an escaped string, while the query needs it as a raw-text string.
First, let's declare a reusable Raw Text string, as per Custom Type Formatting:
const rawText = text => ({toPostgres: () => text, rawType: true});
Then you can define the column like this:
{
name: 'rental_date',
init: () => rawText('now()')
}
And make sure you are using the latest version of pg-promise (v7.2.1 as of this writing).
Or alternatively, you can declare it like this:
{
name: 'rental_date',
mod: ':raw', // same as mode: '^'
init: () => 'now()'
}
This syntax however will work in all versions of the library, and perhaps is even simpler to use ;)

Related

How to group GraphQL query objects into namespaces?

How can I group my queries into namespaces in GraphQL? I have something like this right now:
const queryType = new g.GraphQLObjectType({
name: "Query",
fields: fields,
});
and in fields I have field -> object mappings and it works fine, but I'd like to group these mappings into two groups (live and historical). If I modify the above code to this however:
const queryType = new g.GraphQLObjectType({
name: "Query",
fields: {
historical: {
type: new g.GraphQLObjectType({
name: "historical",
fields: fields,
})
}
},
});
everything resolves to null. How can I write a resolver for this grouping? Is it possible at all?
so often people want namespaces for the sake of splitting up code, not sure if this is your end goal but you could achieve that this way aswell:
# in one file
type Mutation {
login(username: String, password: String): User
}
# in other file
extend type Mutation {
postX(title: String, message: String): X
}

Sequelize "object" does not have increment function

I am using generate sequelize tables from Django generated models with sequelize-auto in my project. so far so good. I wrote the code to update the rank if I see a url again.
const findLink = async (link) =>
Link.findOne({
where: {
url: link,
},
raw: true,
});
// eslint-disable-next-line no-use-before-define
const insertEvent = async (link, tweetText) => {
// Sync Database table before reading
await sequelize.sync();
findLink(link).then((url) => {
if (url) {
url.increment("rank", {by: 1}).then(()=>{
Event.create({
url_id: url.id,
tweet_text: tweetText,
created_at: new Date(),
updated_at: new Date(),
})
});
} else {
Link.create({
url: link,
rank: 0,
created_at: new Date(),
updated_at: new Date(),
}).then((newLink) => {
Event.create({
url_id: newLink.id,
tweet_text: tweetText,
created_at: new Date(),
updated_at: new Date(),
});
});
}
});
};
But the problem is that when It execute url.increment("rank", {by: 1}) it says that url does not have increment function.
But according to documentation it is clearly stated here. Please let me know if I am doing something wrong? I have searched the internet but I could not find any thing relative. I can update the value with duplicate look up but I am looking for a way If I could update the already found object instead of searching it again.
You are using raw queries
const findLink = async (link) =>
Link.findOne({
where: {
url: link,
},
raw: true,
});
It doesn't return an instance of the Model
See https://sequelize.org/master/manual/raw-queries.html
Edit:
By default the function will return two arguments - a results array, and an object containing metadata (such as amount of affected rows, etc).
A second option is the model. If you pass a model the returned data
will be instances of that model.
// Callee is the model definition. This allows you to easily map a query to a predefined model
const projects = await sequelize.query('SELECT * FROM projects', {
model: Projects,
mapToModel: true // pass true here if you have any mapped fields
});
// Each element of `projects` is now an instance of Project
So the mapToModel option might also work

How do I add custom validators in JOI 17?

I'm on JOI 14 and can't seem to find upgrade guides to move towards 17. I see people posting similar questions for JOI 16, but the last update was 3 months ago. It doesn't look like type was required back in 16 based on what I see in How to add custom validator function in Joi?.
I am looking at https://joi.dev/api/?v=17.3.0#extensions and the description of type is The type of schema. Can be a string, or a regular expression that matches multiple types..
I tried something like this:
const snakeAlpha = joi => {
return {
type: 'object',
name: 'snakeAlpha',
base: joi.string().regex(/^[a-z]+(_[a-z]+)*$/)
};
};
const customJoi = Joi.extend({
type: 'object',
rules: {
snakeAlpha
}
});
It gives me this error:
ValidationError: {
"type": "object",
"rules": {
"snakeAlpha" [1]: "[joi => {\n return {\n type: 'object',\n name: 'snakeAlpha',\n base: joi.string().regex(/^[a-z]+(_[a-z]+)*$/)\n };\n}]"
}
}
[1] "rules.snakeAlpha" must be of type object
I am confused since said object. I also tried string since that's what the base is, but it had same error message.
Update
I also realize the original example only covered one simple rule that isn't referencing joi (regex). I also have validators that reference other custom ones lke the below. Bonus points to solve this case too.
const arrayKebabAlpha = joi => {
return {
type: 'string',
name: 'arrayKebabAlpha',
base: joi.array().items(joi.kebabAlpha())
};
};
The documentation for Joi extensions is disappointingly lacklustre for such a useful feature. Fortunately a lot of Joi's core is written using extensions so a lot can be learned from looking at the source.
If I were to write your rule as an extension it'd be like this:
const customJoi = Joi.extend(joi => ({
type: 'string',
base: joi.string(),
messages: {
'string.snakeAlpha': '{{#label}} must be snake case'
},
rules: {
snakeAlpha: {
validate(value, helpers)
{
if (!/^[a-z]+(_[a-z]+)*$/.test(value))
{
return helpers.error('string.snakeAlpha', { value });
}
return value;
}
}
}
}));
Which can be used like:
customJoi.object().keys({
foo: customJoi.string().snakeAlpha()
});
UPDATE
Whether this is the correct way of working with dependant extensions, I'm not sure, but this is how I typically handle them...
I first define my extensions in an array ensuring dependant extensions are defined first. Then I'll iterate through the array re-using the previous customJoi instance so the next extension includes those defined before it. A simple working example will probably explain better than I can put into words!
(I've also simplified the extensions to be more inline with how you're used to using them)
const Joi = require('joi');
let customJoi = Joi;
const extensions = [
joi => ({
type: 'snakeAlpha',
base: joi.string().regex(/^[a-z]+(_[a-z]+)*$/)
}),
// this instance of 'joi' will include 'snakeAlpha'
joi => ({
type: 'kebabAlpha',
base: joi.string().regex(/^[a-z]+(-[a-z]+)*$/)
}),
// this instance of 'joi' will include 'snakeAlpha' and 'kebabAlpha'
joi => ({
type: 'arrayKebabAlpha',
base: joi.array().items(joi.kebabAlpha())
})
];
extensions.forEach(extension =>
customJoi = customJoi.extend(extension));
customJoi.assert([ 'hello-world' ], customJoi.arrayKebabAlpha());

Retrive some columns of relations in typeorm

I need to retrieve just some columns of relations in typeorm query.
I have an entity Environment that has an relation with Document, I want select environment with just url of document, how to do this in typeorm findOne/findAndCount methods?
To do that you have to use a querybuilder, here's an example:
return this.createQueryBuilder('environment') // use this if the query used inside of your entity's repository or getRepository(Environment)...
.select(["environment.id","environment.xx","environment.xx","document.url"])
.leftJoin("environment.document", "document")
.where("environment.id = :id ", { id: id })
.getOne();
Sorry I can't add comment to post above. If you by not parsed data mean something like "environment.id" instead of "id"
try this:
return this.createQueryBuilder("environment")
.getRepository(Environment)
.select([
"environment.id AS id",
"environment.xx AS xx",
"document.url AS url",
])
.leftJoin("environment.document", "document")
.where("environment.id = :id ", { id: id })
.getRawOne();
Here is the code that works for me, and it doesn't require using the QueryBuilder. I'm using the EntityManager approach, so assuming you have one of those from an existing DataSource, try this:
const environment = await this.entityManager.findOne(Environment, {
select: {
document: {
url: true,
}
},
relations: {
document: true
},
where: {
id: environmentId
},
});
Even though the Environment attributes are not specified in the select clause, my experience is that they are all returned in the results, along with document.url.
In one of the applications that I'm working on, I have the need to bring back attributes from doubled-nested relationships, and I've gotten that to work in a similar way, shown below.
Assuming an object model where an Episode has many CareTeamMembers, and each CareTeamMember has a User, something like the code below will fetch all episodes (all attributes) along with the first and last name of the associated Users:
const episodes = await this.entityManager.find(Episode, {
select: {
careTeamMembers: {
id: true, // Required for this to work
user: {
id: true,
firstName: true,
lastName: true,
},
}
},
relations: {
careTeamMembers: {
user: true,
}
},
where: {
deleted: false,
},
});
For some reason, I have to include at least one attribute from the CareTeamMembers entity itself (I'm using the id) for this approach to work.

Sequelize wrap column with " in where condition

I have a JSONB column in DB.
I'd like to have request to DB where I can check if some value in this JSON it true or false:
SELECT *
FROM table
WHERE ("json_column"->'data'->>'data2')::boolean = true AND id = '00000000-1111-2222-3333-456789abcdef'
LIMIT 1
So, my sequelize request:
const someVariableWithColumnName = 'data2';
Model.findOne({
where: {
[`$("json_column"->'data'->>'${someVariableWithColumnName}')::boolean$`]: true,
id: someIdVariable,
},
order: [/* some order, doesn't matter */],
})
And sequelize generate bad result like:
SELECT *
FROM table
WHERE "(json_column"."->'data'->>'data2')::boolean" = true AND id = '00000000-1111-2222-3333-456789abcdef'
LIMIT 1
Split my column by . and add " to every element.
Any idea how to get rid of adding " to the column in where condition?
Edit:
Here is my query with sequelize.literal():
const someVariableWithColumnName = 'data2';
Model.findOne({
where: {
[sequelize.literal(`$("json_column"->'data'->>'${someVariableWithColumnName}')::boolean$`)]: true,
id: someIdVariable,
},
order: [/* some order, doesn't matter */],
})
You can use Sequelize.literal() to avoid spurious quotes. IMHO, wrapping the json handling in a db function might also be helpful.
I just came across a similar use case.
I believe you can use the static sequelize.where method in combination with sequelize.literal.
Here is the corresponding documentation in sequelize API reference: https://sequelize.org/master/class/lib/sequelize.js~Sequelize.html#static-method-where
And here is an example (although I will admit hard to find) in the regular documentation:
https://sequelize.org/master/manual/model-querying-basics.html#advanced-queries-with-functions--not-just-columns-
In the end for your specific sit try something like this:
const someVariableWithColumnName = 'data2';
Model.findOne({
where: {
[Op.and]: [
// We provide the virtual column sql as the first argument of sequelize.where with sequelize.literal.
// We provide the matching condition as the second argument of sequelize.where, with the usual sequelize syntax.
sequelize.where(sequelize.literal(`$("json_column"->'data'->>'${someVariableWithColumnName}')::boolean$`), { [Op.eq]: true }),
{ id: someIdVariable }
]
})

Categories