Query by deep properties on relations - postgresql

I am trying to query the memberRepository on two deep relations
const memberRepository = connection.getRepository(Member);
const where = {
"contact": {
"user":{
"uuid": "3322ebc7-7327-4567-95a9-619d36b8e0a6"
}
},
"organization": {
"uuid": "014f2226-729f-4b9f-bf59-5a6e8b8da781",
}
};
const relations = ['contact', 'contact.user', 'organization']
const x = await memberRepository.findOne({ where, relations })
This isn't working, how can I query a deep relation with typeorm?

You should think of relations as SQL joins, it is quite simple with TypeOrm to define them on entities as you build query with QueryBuilder. As long as you have a link enabled in your entity definition you can define a join, or you can use a subselect query otherwise for more complex cases.
const result = await memberRepository.createQueryBuilder("member")
.leftJoinAndSelect("member.contact", "contact")
.leftJoinAndSelect("contact.user", "user")
.leftJoinAndSelect("member.organization", "organization")
.where("user.uuid = :userUuid", {
userUuid: "3322ebc7-7327-4567-95a9-619d36b8e0a6"
})
.andWhere("organization.uuid = :organizationUuid", {
organizationUuid: "014f2226-729f-4b9f-bf59-5a6e8b8da781"
})
.getOne();

Related

Is there any methods in Objection.js for Leftjoin and RightJoin apart from knex querybuilder?

I am writing get service in node js api . We are using Objectionjs as ORM module. We have two Table
'RoleDescription' and 'Features'.
Features is master table and RoleDescription is child table.
here how it is linked:
const { Model } = require('objection');
const BaseModel = require('./BaseModel')
const Features = require('./Features')
class RoleDescription extends BaseModel {
static get tableName() {
return 'RoleDescription';
}
static get relationMappings() {
return {
featureRelation: {
relation: Model.HasOneRelation,
modelClass: Features,
join: {
from: 'Features.Id',
to: 'RoleDescription.FeatureId'
}
},
}
}
}
module.exports = RoleDescription
Since, I want to get all data from features table in response . joinEager method does not solve the purpose beacuse i think its inner join.
so, i just want to know how to execute left join using objectionjs.
also wanted to know the difference between joinEager and Joins in objectionjs?

Strapi : Add element in chid collection

I have two collection type Organizations and Employees.
Its a one to many relationship between Organization and Employee.
I want to add new employee in organization through rest api call in strapi.
Strapi doesn't provide :
http://localhost:1337/organizations/{id}/employees
How to do the update?
There is no already built in solution to do it.
But in Strapi you can create your own controller to match that route and handle all the logic needed in there.
You can check the info about custom controllers here
As Christian said, you will have to create your own route in your Organization API.
Create your route.
Path — api/organization/config/routes.json
{
"routes": [
{
"method": "POST",
"path": "/organizations/:id/employees",
"handler": "organization.cusom",
"config": {
"policies": []
}
},
...
]
}
Create the controller function.
To do so, I will copy the function from the default create one:
https://strapi.io/documentation/3.0.0-beta.x/concepts/controllers.html#core-controllers
Path — `api/organization/controllers/organizaion.js``
const { parseMultipartData, sanitizeEntity } = require('strapi-utils');
module.exports = {
async custom(ctx) {
let entity;
if (ctx.is('multipart')) {
const { data, files } = parseMultipartData(ctx);
entity = await strapi.services.employee.create(data, { files });
} else {
entity = await strapi.services.employee.create(ctx.request.body);
}
return sanitizeEntity(entity, { model: strapi.models.employee });
},
};
And set employee for the creation cause we want to create employee.
Force to use the right organization ID
module.exports = {
async custom(ctx) {
// get the id params from the URL
const {id} = ctx.params;
// force the relation to this specific organisation
ctx.request.body.organization = id;
if (ctx.is('multipart')) {
const { data, files } = parseMultipartData(ctx);
entity = await strapi.services.employee.create(data, { files });
} else {
entity = await strapi.services.employee.create(ctx.request.body);
}
return sanitizeEntity(entity, { model: strapi.models.employee });
},
};

Creating Knex Migration Referencing the Same Table Twice

Trying to create a table Transaction in a postgresql database that references Buyers and Sellers s.t. both are objects from the Users table.
I think I have the migration working to look something like the following:
exports.up = function(knex, Promise) {
return knex.schema.createTable('likes', t => {
t.increments('id').primary()
t.integer('buyers_id').references('users.id').onDelete('CASCADE')
t.integer('sellers_id').references('users.id').onDelete('CASCADE')
...
t.datetime("created_at");
t.datetime("updated_at");
})
};
Next, I need to manage the association in the model, s.t the Transaction belongs to a Buyer and a Seller, which are both members of the User class.
To clarify the question, I am able to create the model with these attributes, but the association does not seem to be working.
here is my Transaction model:
const BaseModel = require("./BaseModel");
// const Password = require('objection-password')();
class Transaction extends BaseModel {
static get tableName() {
return "transactions";
}
static get relationMappings () {
const User = require('./User');
const Item = require('./Item')
return {
buyer: {
relation: BaseModel.BelongsToOneRelation,
modelClass: User,
join: {
from: 'transactions.buyers_id',
to: 'users.id'
}
},
seller: {
relation: BaseModel.BelongsToOneRelation,
modelClass: User,
join: {
from: 'transactions.sellers_id',
to: 'users.id'
}
},
books: {
relation: BaseModel.BelongsToOneRelation,
modelClass: Item,
join: {
from: 'transactions.items_id',
to: 'items.id'
}
}
}
}
}
module.exports = Transaction;
Here is the relevant route where I try to eager load the buyer:
let router = express.Router();
router.get('/', async (req, res) => {
const transactions = await Transaction
.query()
.eager(['buyer', 'items')
res.json(transactions);
});
I have figured this out. The above code works, using the aliases buyers and sellers and associating those two types of Users with Transactions.
For anyone who is interested... Consolidating the working solution above using Node/ExpressJS for the server, Postgresql for the db, KnexJS/ObjectiveJS to manage Models and queries.
Here is the migration that sets up columns for buyers and sellers both of which reference the same Users table:
exports.up = function(knex, Promise) {
return knex.schema.createTable('likes', t => {
t.increments('id').primary()
t.integer('buyers_id').references('users.id').onDelete('CASCADE')
t.integer('sellers_id').references('users.id').onDelete('CASCADE')
...
t.datetime("created_at");
t.datetime("updated_at");
})
};
HEre is the Transactions Model including associations s.t. a Transaction belongs to Item Buyer(user) and Seller(user) and Item:
const BaseModel = require("./BaseModel");
// const Password = require('objection-password')();
class Transaction extends BaseModel {
static get tableName() {
return "transactions";
}
static get relationMappings () {
const User = require('./User');
const Item = require('./Item')
return {
buyer: {
relation: BaseModel.BelongsToOneRelation,
modelClass: User,
join: {
from: 'transactions.buyers_id',
to: 'users.id'
}
},
seller: {
relation: BaseModel.BelongsToOneRelation,
modelClass: User,
join: {
from: 'transactions.sellers_id',
to: 'users.id'
}
},
books: {
relation: BaseModel.BelongsToOneRelation,
modelClass: Item,
join: {
from: 'transactions.items_id',
to: 'items.id'
}
}
}
}
}
module.exports = Transaction;
Lastly, here is the express route which returns all transactions including eager loading the associated models:
let router = express.Router();
router.get('/', async (req, res) => {
const transactions = await Transaction
.query()
.eager(['buyer', 'items')
res.json(transactions);
});

How to cast a mongoose aggregate result to a specific document schema type?

Consider the following aggretation:
let getUsersWithNoPersonsPromise = () => {
let pipeline = [
{
$lookup: {
from: "persons",
localField: "id",
foreignField: "person_id",
as: "persons_users"
}
},
{
$match: {
"persons_users:0": {
$exists: false
}
}
}
];
return User.aggregate(pipeline).exec();
}
How to I cast the $match result to be of type UserModel ? I´m getting plain javascript objects on it and I expect to receive mongoose User type objects.
You can use the result of the aggregation query and simply instantiate new objects by iterating through the array of results, like this (assuming you are using async await);
let people = await getUsersWithNoPersonsPromise()
people = people.map(p => new Person(p))
You can use this:
const { Query } = require('mongoose');
const mongooseQuery = new Query();
// pass the model and filter to mongoose to cast
// you can add this to pre aggregation middleware
mongooseQuery.cast(Model, filter);

apollostack/graphql-server - how to get the fields requested in a query from resolver

I am trying to figure out a clean way to work with queries and mongdb projections so I don't have to retrieve excessive information from the database.
So assuming I have:
// the query
type Query {
getUserByEmail(email: String!): User
}
And I have a User with an email and a username, to keep things simple. If I send a query and I only want to retrieve the email, I can do the following:
query { getUserByEmail(email: "test#test.com") { email } }
But in the resolver, my DB query still retrieves both username and email, but only one of those is passed back by apollo server as the query result.
I only want the DB to retrieve what the query asks for:
// the resolver
getUserByEmail(root, args, context, info) {
// check what fields the query requested
// create a projection to only request those fields
return db.collection('users').findOne({ email: args.email }, { /* projection */ });
}
Of course the problem is, getting information on what the client is requesting isn't so straightforward.
Assuming I pass in request as context - I considered using context.payload (hapi.js), which has the query string, and searching it through various .split()s, but that feels kind of dirty. As far as I can tell, info.fieldASTs[0].selectionSet.selections has the list of fields, and I could check for it's existence in there. I'm not sure how reliable this is. Especially when I start using more complex queries.
Is there a simpler way?
In case you don't use mongDB, a projection is an additional argument you pass in telling it explicitly what to retrieve:
// telling mongoDB to not retrieve _id
db.collection('users').findOne({ email: 'test#test.com' }, { _id: 0 })
As always, thanks to the amazing community.
2020-Jan answer
The current answer to getting the fields requested in a GraphQL query, is to use the graphql-parse-resolve-info library for parsing the info parameter.
The library is "a pretty complete solution and is actually used under the hood by postgraphile", and is recommended going forward by the author of the other top library for parsing the info field, graphql-fields.
Use graphql-fields
Apollo server example
const rootSchema = [`
type Person {
id: String!
name: String!
email: String!
picture: String!
type: Int!
status: Int!
createdAt: Float
updatedAt: Float
}
schema {
query: Query
mutation: Mutation
}
`];
const rootResolvers = {
Query: {
users(root, args, context, info) {
const topLevelFields = Object.keys(graphqlFields(info));
return fetch(`/api/user?fields=${topLevelFields.join(',')}`);
}
}
};
const schema = [...rootSchema];
const resolvers = Object.assign({}, rootResolvers);
// Create schema
const executableSchema = makeExecutableSchema({
typeDefs: schema,
resolvers,
});
Sure you can. This is actually the same functionality that is implemented on join-monster package for SQL based db's. There's a talk by their creator: https://www.youtube.com/watch?v=Y7AdMIuXOgs
Take a look on their info analysing code to get you started - https://github.com/stems/join-monster/blob/master/src/queryASTToSqlAST.js#L6-L30
Would love to see a projection-monster package for us mongo users :)
UPDATE:
There is a package that creates a projection object from info on npm: https://www.npmjs.com/package/graphql-mongodb-projection
You can generate MongoDB projection from info argument. Here is the sample code that you can follow
/**
* #description - Gets MongoDB projection from graphql query
*
* #return { object }
* #param { object } info
* #param { model } model - MongoDB model for referencing
*/
function getDBProjection(info, model) {
const {
schema: { obj }
} = model;
const keys = Object.keys(obj);
const projection = {};
const { selections } = info.fieldNodes[0].selectionSet;
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const isSelected = selections.some(
selection => selection.name.value === key
);
projection[key] = isSelected;
}
console.log(projection);
}
module.exports = getDBProjection;
With a few helper functions you can use it like this (typescript version):
import { parceGqlInfo, query } from "#backend";
import { GraphQLResolveInfo } from "graphql";
export const user = async (parent: unknown, args: unknown, ctx: unknown, info: GraphQLResolveInfo): Promise<User | null> => {
const { dbQueryStr } = parceGqlInfo(info, userFields, "id");
const [user] = await query(`SELECT ${dbQueryStr} FROM users WHERE id=$1;`, [1]);
return user;
};
Helper functions.
Few points:
gql_uid used as ID! string type from primary key to not change db types
required option is used for dataloaders (if field was not requested by user)
allowedFields used to filter additional fields from info like '__typename'
queryPrefix is used if you need to prefix selected fields like select u.id from users u
const userFields = [
"gql_uid",
"id",
"email"
]
// merge arrays and delete duplicates
export const mergeDedupe = <T>(arr: any[][]): T => {
// #ts-ignore
return ([...new Set([].concat(...arr))] as unknown) as T;
};
import { parse, simplify, ResolveTree } from "graphql-parse-resolve-info";
import { GraphQLResolveInfo } from "graphql";
export const getQueryFieldsFromInfo = <Required = string>(info: GraphQLResolveInfo, options: { required?: Required[] } = {}): string[] => {
const { fields } = simplify(parse(info) as ResolveTree, info.returnType) as { fields: { [key: string]: { name: string } } };
let astFields = Object.entries(fields).map(([, v]) => v.name);
if (options.required) {
astFields = mergeDedupe([astFields, options.required]);
}
return astFields;
};
export const onlyAllowedFields = <T extends string | number>(raw: T[] | readonly T[], allowed: T[] | readonly T[]): T[] => {
return allowed.filter((f) => raw.includes(f));
};
export const parceGqlInfo = (
info: GraphQLResolveInfo,
allowedFields: string[] | readonly string[],
gqlUidDbAlliasField: string,
options: { required?: string[]; queryPrefix?: string } = {}
): { pureDbFields: string[]; gqlUidRequested: boolean; dbQueryStr: string } => {
const fieldsWithGqlUid = onlyAllowedFields(getQueryFieldsFromInfo(info, options), allowedFields);
return {
pureDbFields: fieldsWithGqlUid.filter((i) => i !== "gql_uid"),
gqlUidRequested: fieldsWithGqlUid.includes("gql_uid"),
dbQueryStr: fieldsWithGqlUid
.map((f) => {
const dbQueryStrField = f === "gql_uid" ? `${gqlUidDbAlliasField}::Text AS gql_uid` : f;
return options.queryPrefix ? `${options.queryPrefix}.${dbQueryStrField}` : dbQueryStrField;
})
.join(),
};
};