PG-Promise - "Property doesn't exist" error with skip function - pg-promise

I am having trouble figuring out how to utilize skip to skip undefined/null values. I keep getting Error: Property 'vehicle_id' doesn't exist. Is skip within column set and skip of upsertReplaceQuery() somehow conflicting with each other? How can I get it to work?
const vehicleColumnSet = new pgp.helpers.ColumnSet(
[
{ name: 'user_id' },
{
name: 'vehicle_id',
skip: (c) => !c.exists,
},
{ name: 'model_id', def: null },
],
{ table: 'vehicle' }
);
const upsertReplaceQuery = (data, columnSet, conflictField) => {
return `${pgp.helpers.insert(
data,
columnSet
)} ON CONFLICT(${conflictField}) DO UPDATE SET ${columnSet.assignColumns({
from: 'EXCLUDED',
skip: conflictField,
})}`;
};
const vehicleUpsertQuery = upsertReplaceQuery(
{
user_id,
model_id: vehicle_model,
},
vehicleColumnSet,
'user_id'
);
await task.none(vehicleUpsertQuery);

PostgreSQL has no support for any skip logic within its multi-row insert syntax.
And pg-promise documentation also tells you within skip description:
An override for skipping columns dynamically.
Used by methods update (for a single object) and sets, ignored by methods insert and values.
It is also ignored when conditional flag cnd is set.
At most, you can add such logic against a single-row insert, as shown here.

Related

Prisma findFirstOrThrow does not throw

Using prisma I am trying to write some tests, however the findFirstOrThrow method (https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#findfirstorthrow) does not seem to throw when nothing should be found. Instead it is returning the first database record it finds.
The following piece of code is what I am testing
console.log('sessionId', this.ctx.session?.user?.id);
const author = await this.db.author.findFirstOrThrow({
where: { userId: this.ctx.session?.user?.id },
select: { id: true },
});
console.log({ author });
And in my test output I get the following logs
RERUN src/api/service.ts x31
stdout | src/api/service.test.ts > BlockService > block creation > without an author
sessionId undefined
stdout | src/api/service.test.ts > BlockService > block creation > without an author
{ author: { id: 'cle79pisg007hb2b8rhpii1ws' } }
So even though the this.ctx.session?.user?.id is undefined, prisma still returns the first author in the table.
What I've tried so far:
When not populating the authors table in the test it will throw.
When populating the authors table with a single author it will return this author
When giving an explicit undefined as the userId it will still returns the first record
edit: I use prisma ^4.8.0
This is the expected behaviour.
If you will pass undefined then it is equivalent to not passing any userId.
So, the query is equivalent to the following:
const author = await this.db.author.findFirstOrThrow({
where: { },
select: { id: true },
});
And this query would return the first record from database.
For reference, here is the section that defines this behaviour.

Prisma Typescript where clause inside include?

I am trying to query the database (Postgres) through Prisma. My query is
const products = await prisma.products.findMany({
where: { category: ProductsCategoryEnum[category] },
include: {
vehicles: {
include: {
manufacturers: { name: { in: { manufacturers.map(item => `"${item}"`) } } },
},
},
},
});
The error message is
Type '{ name: { in: { manufacturers: string; "": any; }; }; }' is not assignable to type 'boolean | manufacturersArgs'.
Object literal may only specify known properties, and 'name' does not exist in type 'manufacturersArgs'.ts(2322)
Manufacturers have the field name and it is unique; I am not sure why this is not working or how I can update this code to be able to query the database. It is like I should cast the values into Prisma arguments.
The TypeScript error is pretty self-explanatory: the name property does not exist in manufacturersArgs. The emitted Prisma Client does a great job of telling you what properties do and do not exist when filtering.
If you are trying to perform a nested filter, you need to use select instead of include.
Documentation: https://www.prisma.io/docs/concepts/components/prisma-client/relation-queries#filter-a-list-of-relations
Your query is going to look something like this:
const products = await prisma.products.findMany({
where: { category: ProductsCategoryEnum[category] },
select: {
// also need to select any other fields you need here
vehicles: {
// Updated this
select: { manufacturers: true },
// Updated this to add an explicit "where" clause
where: {
manufacturers: { name: { in: { manufacturers.map(item => `"${item}"`) } } },
},
},
},
});
The final code ultimately depends on your Prisma schema. If you are using an editor like VS Code, it should provide Intellisense into the Prisma Client's TypeScript definitions. You can use that to navigate the full Prisma Client and construct your query based on exactly what is and is not available. In VS Code, hold control [Windows] or command [macOS] and click on findMany in prisma.products.findMany. This lets you browse the full Prisma Client and construct your query!
The in keyword isn't working for me. I use hasSome to find items in an array. hasEvery is also available depending what the requirements are.
hasSome: manufacturers.map(item => `"${item}"`),
See https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#scalar-list-filters

KeystoneJs user-defined order for Relationship

I am using KeystoneJS with PostgreSQL as my backend and Apollo on the frontend for my app.
I have a schema that has a list that is linked to another list.
I want to be able to allow users to change the order of the second list.
This is a simplified version of my schema
keystone.createList(
'forms',
{
fields: {
name: {
type: Text,
isRequired: true,
},
buttons: {
type: Relationship,
ref: 'buttons.attached_forms',
many: true,
},
},
}
);
keystone.createList(
'buttons',
{
fields: {
name: {
type: Text,
isRequired: true,
},
attached_forms: {
type: Relationship,
ref: 'forms.buttons',
many: true,
},
},
}
);
So what I would like to do, is allow users to change the order of buttons so when I fetch them in the future from forms:
const QUERY = gql`
query getForms($formId: ID!) {
allforms(where: {
id: $formId,
}) {
id
name
buttons {
id
name
}
}
}
`;
The buttons should come back from the backend in a predefined order.
{
id: 1,
name: 'Form 1',
buttons: [
{
id: 1,
name: 'Button 1',
},
{
id: 3,
name: 'Button 3',
},
{
id: 2,
name: 'Button 2',
}
]
}
Or even just have some data on that returns with the query that will allow for sorting according to the user-defined sort order on the frontend.
The catch is that this relationship is many to many.
So it wouldn't be enough to add a column to the buttons schema as the ordering needs to be relationship-specific. In other words, if a user puts a particular button last on a particular form, it shouldn't change the order of that same button on other forms.
In a backend that I was creating myself, I would add something to the joining table, like a sortOrder field or similar and then change those values to change the order, or even order them on the frontend using that information.
Something like this answer here.
The many-to-many join table would have columns like formId, buttonId, sortOrder.
I have been diving into the docs for KeystoneJS and I can't figure out a way to make this work without getting into the weeds of overriding the KnexAdapter that we are using.
I am using:
{
"#keystonejs/adapter-knex": "^11.0.7",
"#keystonejs/app-admin-ui": "^7.3.11",
"#keystonejs/app-graphql": "^6.2.1",
"#keystonejs/fields": "^20.1.2",
"#keystonejs/keystone": "^17.1.2",
"#keystonejs/server-side-graphql-client": "^1.1.2",
}
Any thoughts on how I can achieve this?
One approach would be to have two "button" lists, one with a template for a button (buttonTemplate below) with common data such as name etc, and another (button below) which references one buttonTemplate and one form. This allows you to assign a formIndex property to each button, which dictates its position on the corresponding form.
(Untested) example code:
keystone.createList(
'Form',
{
fields: {
name: {
type: Text,
isRequired: true,
},
buttons: {
type: Relationship,
ref: 'Button.form',
many: true,
},
},
}
);
keystone.createList(
'Button',
{
fields: {
buttonTemplate: {
type: Relationship,
ref: 'ButtonTemplate.buttons',
many: false,
},
form: {
type: Relationship,
ref: 'Form.buttons',
many: false,
},
formIndex: {
type: Integer,
isRequired: true,
},
},
}
);
keystone.createList(
'ButtonTemplate',
{
fields: {
name: {
type: Text,
isRequired: true,
},
buttons: {
type: Relationship,
ref: 'Button.buttonTemplate',
many: true,
},
},
}
);
I think this is less likely to cause you headaches (which I'm sure you can see coming) down the line than your buttonOrder solution, e.g. users deleting buttons that are referenced by this field.
If you do decide to go with this approach, you can guard against such issues with the hook functionality in Keystone. E.g. before a button is deleted, go through all the forms and rewrite the buttonOrder field, removing any references to the deleted button.
I had a similar challenge once, so after some research and found this answer, I implemented a solution to a project using PostgreSQL TRIGGER.
So you can add a trigger where on an update, it should shift the buttonOrder.
Here is the SQL I had on me, this was the test code, I regex replaced the terms to fit your question :)
// Assign order
await knex.raw(`
do $$
DECLARE form_id text;
begin
CREATE SEQUENCE buttons_order_seq;
CREATE VIEW buttons_view AS SELECT * FROM "buttons" ORDER BY "createdAt" ASC, "formId";
CREATE RULE buttons_rule AS ON UPDATE TO buttons_view DO INSTEAD UPDATE buttons SET order = NEW.order WHERE id = NEW.id;
FOR form_id IN SELECT id FROM form LOOP
ALTER SEQUENCE buttons_order_seq RESTART;
UPDATE buttons_view SET order = nextval('buttons_order_seq') WHERE "formId" = form_id;
END LOOP;
DROP SEQUENCE buttons_order_seq;
DROP RULE buttons_rule ON buttons_view;
DROP VIEW buttons_view;
END; $$`);
// Create function that shifts orders
await knex.raw(`
CREATE FUNCTION shift_buttons_order()
RETURNS trigger AS
$$
BEGIN
IF NEW.order < OLD.order THEN
UPDATE buttons SET order = order + 1, "shiftOrderFlag" = NOT "shiftOrderFlag"
WHERE order >= NEW.order AND order < OLD.order AND "formId" = OLD."formId";
ELSE
UPDATE buttons SET order = order - 1, "shiftOrderFlag" = NOT "shiftOrderFlag"
WHERE order <= NEW.order AND order > OLD.order AND "formId" = OLD."formId";
END IF;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql'`);
// Create trigger to shift orders on update
await knex.raw(`
CREATE TRIGGER shift_buttons_order BEFORE UPDATE OF order ON buttons FOR EACH ROW
WHEN (OLD."shiftOrderFlag" = NEW."shiftOrderFlag" AND OLD.order <> NEW.order)
EXECUTE PROCEDURE shift_buttons_order()`);
One option that we came up with is to add the order to the form table.
keystone.createList(
'forms',
{
fields: {
name: {
type: Text,
isRequired: true,
},
buttonOrder: {
type: Text,
},
buttons: {
type: Relationship,
ref: 'buttons.attached_forms',
many: true,
},
},
}
);
This new field buttonOrder could contain a string representation of the order of the button Ids, like in a JSON stringified array.
The main issue with this is that it will be difficult to keep this field in-sync with the actual linked buttons.

pg-promise ColumnSet with nested object prop?

What is the proper syntax for a object child as a column prop?
const cs = new pgp.helpers.ColumnSet([
{
name: 'uid',
prop: 'id'
}, {
name: 'created_at'
prop: 'member.created_at // <-- error
}
])
Cant seem to get this to work.
While the regular pg-promise query formatting (using Named Parameters) supports nested properties, specifically helpers do not, due to certain templates-related complexity.
However, it is not necessary, because ColumnSet syntax for columns is very flexible (see type Column), and supports dynamic property resolution.
Simply update the column to use init, to get the value dynamically:
{
name: 'created_at',
init: c => c.source.member.created_at
}
For the field, we go to the source object, as per documentation, and take what we need.
Alternative syntax:
{
name: 'created_at',
init(c) {
// with this syntax, this = c.source
return this.member.created_at;
}
}

Feathersjs retrieve foreign key value through API

I am using PostgresSQL database with Feathersjs, connecting both through Knex. The database table (NewTable) was created with a foreign key, referencing to the users services.
Following describe the database table.
module.exports = function (app) {
const db = app.get('knexClient');
db.schema.createTableIfNotExists('NewTable', table => {
table.increments('id').primary();
table.integer('ownerId').references('users.id');
})
I will receive the foreign key value of ownerId (in integer) when I query it through GET.
export function load(id) {
return {
types: [LOAD, LOAD_SUCCESS, LOAD_FAIL],
promise: ({ app }) => app.service('NewTable').get(id)
};
}
What is the proper way to retrieve the first_name column of the user instead of userId through the GET api?
Do I need to run another GET api within the hook of NewTable service to get the first_name column, or there is a better/easier way to do it?
Create a function like below within the hook of feathersjs.
function populateUser() {
return populate({
schema: {
include: [{
nameAs: 'sentBy',
service: 'users',
parentField: 'ownerId',
childField: 'id'
}]
}
});
}
Use it within the hook to call it after the GET. The columns of user service will be included within the response.
after: {
all: [],
find: [],
get: [
populateUser(),
}
],
create: [],
update: [],
patch: [],
remove: []
},