TypeORMError: alias was not found - postgresql

I'm trying to make API pagination for GET /authors.
I have bidirectional many to many relation between authors and books table.
I found that problem is when using creatingQueryBuilder() in combination with .leftJoinAndSelect() and .skip() I get TypeORMError: ""authors"" alias was not found. Maybe you forgot to join it?. But I'm not sure how to solve it.
My database look like this:
library=# select * from authors;
id | first_name | last_name | birth_date | created_at | updated_at
----+------------+-----------+------------+----------------------------+----------------------------
library=# select * from books;
id | title | isbn | pages | created_at | updated_at
----+------------------+-----------------------+-------+----------------------------+----------------------------
library=# select * from books_authors
books_id | authors_id
----------+------------
(4 rows)
Entities look like this:
import { Exclude } from 'class-transformer';
import { BookEntity } from 'src/book/entities/book.entity';
import {
Entity,
PrimaryGeneratedColumn,
Column,
BeforeInsert,
ManyToMany,
} from 'typeorm';
#Entity({ name: 'authors' })
export class AuthorEntity {
#Exclude()
#PrimaryGeneratedColumn()
id: number;
#Column()
firstName: string;
#Column()
lastName: string;
#Column({ type: 'date', nullable: true })
birthDate: Date | null;
#Exclude()
#Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;
#Exclude()
#Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
updatedAt: Date;
#ManyToMany(() => BookEntity, (book) => book.authors)
books: BookEntity[];
#BeforeInsert()
updateTimestamp() {
this.updatedAt = new Date();
}
}
import { Exclude } from 'class-transformer';
import { AuthorEntity } from 'src/author/entities/author.entity';
import {
Entity,
PrimaryGeneratedColumn,
Column,
BeforeInsert,
ManyToMany,
JoinTable,
} from 'typeorm';
#Entity({ name: 'books' })
export class BookEntity {
#Exclude()
#PrimaryGeneratedColumn()
id: number;
#Column()
title: string;
#Column()
isbn: string;
#Column()
pages: number;
#Exclude()
#Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;
#Exclude()
#Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
updatedAt: Date;
#ManyToMany(() => AuthorEntity, (author) => author.books)
#JoinTable({ name: 'books_authors' })
authors: AuthorEntity[];
#BeforeInsert()
updateTimestamp() {
this.updatedAt = new Date();
}
}
Service method looks like this:
async findAll(
pageOptionsDto: PageOptionsDto,
filterAuthorDto: FilterAuthorDto,
): Promise<PageDto<AuthorEntity>> {
const builder = this.dataSource
.getRepository(AuthorEntity)
.createQueryBuilder('authors');
if (filterAuthorDto?.firstName) {
builder.where('"authors"."first_name" LIKE :firstName', {
firstName: `%${filterAuthorDto.firstName}%`,
});
}
if (filterAuthorDto?.lastName) {
builder.andWhere('"authors"."last_name" LIKE :lastName', {
lastName: `%${filterAuthorDto.lastName}%`,
});
}
// This part of code is problematic
builder
.innerJoinAndSelect('authors.books', 'books')
.orderBy('"authors"."created_at"', pageOptionsDto.order)
.skip(pageOptionsDto.skip)
.take(pageOptionsDto.perPage);
const total = await builder.getCount();
const { entities } = await builder.getRawAndEntities();
const pageMetaDto = new PageMetaDto({ total, pageOptionsDto });
return new PageDto(entities, pageMetaDto);
}

Just remove the double quotation inside the string, it's redundant and makes typeorm get confused and couldn't find related defined alias.
...
.orderBy('authors.created_at', pageOptionsDto.order)
...

Related

Typeorm (Nestjs) update ManyToMany relation when using querybuilder violates not-null constraint

I have a simple ManyToMany setup with typeorm. According to the docs of typeORM on how to update/add a ManyToMany relation.
Link to the docs: https://typeorm.io/relational-query-builder
The error I get:
The query it produces, and here is the problem, it does not have 2 parameters and 'usersId' is empty. I think the user has to exist first before connecting them together, but maybe someone can confirm this? Why does it not create the user for me when adding a new user in enterprise?
query: SELECT "Enterprise"."id" AS "Enterprise_id", "Enterprise"."createdAt" AS "Enterprise_createdAt", "Enterprise"."updatedAt" AS "Enterprise_updatedAt", "Enterprise"."name" AS "Enterprise_name", "Enterprise"."erpCustomerCode" AS "Enterprise_erpCustomerCode", "Enterprise"."isActive" AS "Enterprise_isActive", "Enterprise"."street" AS "Enterprise_street", "Enterprise"."houseNumber" AS "Enterprise_houseNumber", "Enterprise"."city" AS "Enterprise_city", "Enterprise"."state" AS "Enterprise_state", "Enterprise"."country" AS "Enterprise_country", "Enterprise"."zip" AS "Enterprise_zip", "Enterprise"."geoLocation" AS "Enterprise_geoLocation", "Enterprise"."unitsSystem" AS "Enterprise_unitsSystem", "Enterprise"."weekNumberingSystem" AS "Enterprise_weekNumberingSystem", "Enterprise"."yearStart" AS "Enterprise_yearStart", "Enterprise"."weekStart" AS "Enterprise_weekStart" FROM "enterprises" "Enterprise" WHERE ("Enterprise"."id" = $1) LIMIT 1 -- PARAMETERS: ["d2f16db8-63be-4b09-a5ea-e04336069df4"]
User {}
query: INSERT INTO "enterprise_users"("enterprisesId", "usersId") VALUES ($1, DEFAULT) -- PARAMETERS: ["d2f16db8-63be-4b09-a5ea-e04336069df4"]
query failed: INSERT INTO "enterprise_users"("enterprisesId", "usersId") VALUES ($1, DEFAULT) -- PARAMETERS: ["d2f16db8-63be-4b09-a5ea-e04336069df4"]
I have the following setup:
enterprise.entity.ts
#Entity('enterprises')
export class Enterprise {
#PrimaryGeneratedColumn('uuid')
id: string;
#CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;
#UpdateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
updatedAt: Date;
#Column({ unique: true, length: 50 })
name: string;
...
#ManyToMany(() => User, (users) => users.enterprises, { cascade: true })
#JoinTable({ name: 'enterprise_users' })
users: User[];
}
user.entity.ts
#Entity('users')
export class User {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
name: string;
#ManyToMany(() => Enterprise, (enterprise) => enterprise.users)
enterprises: Enterprise[];
}
enterprise.service.ts
#Injectable()
export class EnterpriseService {
constructor(
#InjectRepository(Enterprise)
private enterpriseRepository: Repository<Enterprise>,
) {}
async update(id: string, enterprise: UpdateEnterpriseDto) {
// for testing
const user = new User();
user.name = 'test123';
// triggers error
await this.enterpriseRepository.createQueryBuilder().relation(Enterprise, 'users').of(id).add(user);
return this.enterpriseRepository.save({ id, ...enterprise });
}
}

Pass empty string to column datatype is enum postgres got error

I got an error when I pass an empty string to completed but when I pass empty string to delivery_name is work fine
const queryBuilder = this.deliveryRepository
.createQueryBuilder('delivery')
.select([
'delivery.delivery_id AS delivery_id',
'delivery.price AS price',
'delivery.delivery_date AS invoice_date',
'delivery.invoice_id AS invoice_id',
'delivery.completed AS completed',
'delivery.deliveryman_id AS deliveryman_id',
'deliveryman.delivery_name AS delivery_name',
'deliveryman.vehicle AS vehicle',
'deliveryman.phone AS phone',
]).orWhere('LOWER(deliveryman.delivery_name) LIKE LOWER(:delivery_name)', {
delivery_name: `%${option.filter}%`,
}).orWhere('delivery.completed = :completed', {
completed: `%${option.filter}%`,
})
DeliveryEntity
import { DeliveryManEntity, InvoiceEntity } from '#entity';
import { DeliveryStatus } from '../../../constant/delivery/delivery-status';
import {
Column,
Entity,
JoinColumn,
ManyToOne,
OneToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { ShareEntity } from '#share-entity';
#Entity({ name: 'delivery' })
export class DeliveryEntity extends ShareEntity {
#PrimaryGeneratedColumn()
delivery_id: number;
#Column({ nullable: true, default: 0 })
price: number;
#Column({ type: 'date', default: () => 'NOW()' })
delivery_date: Date;
#OneToOne(() => InvoiceEntity, (entity) => entity.delivery)
#JoinColumn({ name: 'invoice_id', referencedColumnName: 'invoice_id' })
invoice: InvoiceEntity;
#Column()
invoice_id: number;
#Column({
enum: DeliveryStatus,
type: 'enum',
default: DeliveryStatus.PENDING,
enumName: 'delivery_status',
name: 'completed',
})
completed: DeliveryStatus;
#ManyToOne(() => DeliveryManEntity, (entity) => entity.delivery)
#JoinColumn({
name: 'deliveryman_id',
referencedColumnName: 'deliveryman_id',
})
deliveryman: DeliveryManEntity;
#Column()
deliveryman_id: number;
}
Delivery Status
export enum DeliveryStatus {
PENDING = 'PENDING',
COMPLETED = 'COMPLETED',
}
I want to pass an empty string to completed or I can pass PENDING OR COMPLETED
any solution how to query with empty string to completed field
I'm using typeorm + Nestjs +Postgresql
Based on your comment, what you should be doing is something like below:
const queryBuilder = this.deliveryRepository
.createQueryBuilder('delivery')
.select([
'delivery.delivery_id AS delivery_id',
'delivery.price AS price',
'delivery.delivery_date AS invoice_date',
'delivery.invoice_id AS invoice_id',
'delivery.completed AS completed',
'delivery.deliveryman_id AS deliveryman_id',
'deliveryman.delivery_name AS delivery_name',
'deliveryman.vehicle AS vehicle',
'deliveryman.phone AS phone',
]).orWhere('LOWER(deliveryman.delivery_name) LIKE LOWER(:delivery_name)', {
delivery_name: `%${option.filter}%`,
});
const filter = `%${option.filter}%`;
if (filter === "") {
queryBuilder.orWhere('delivery.completed IN (...:completed)', {
completed: [DeliveryStatus.COMPLETED, DeliveryStatus.PENDING],
});
} else {
queryBuilder.orWhere('delivery.completed = :completed', {
completed: filter,
});
}

How to map joined table column to an entity's field in TypeORM

There are two entities as follow:
// user.entity.ts
#Entity()
export class User extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
#RelationId((user: User) => user.group)
groupId: number;
#Column()
fullName: string;
#Column()
email: string;
#Column()
passwordHash: string;
#ManyToOne(type => Group, group => group.users)
#JoinColumn()
group: Group;
isOwner: boolean;
}
// group.entity.ts
#Entity()
export class Group extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column({ default: false })
isOwner: boolean;
#OneToMany(type => User, user => user.group)
users: User[];
}
I'd like to map the isOwner value of Group to isOwner of User
I tried:
async findOneById(id: number): Promise<User> {
return await User.createQueryBuilder('user')
.leftJoinAndMapOne('user.isOwner', 'user.group', 'group')
.select(['user.id', 'user.fullName', 'user.email', 'group.isOwner'])
.getOne();
}
the result was:
It is possible to achieve that by using #AfterLoad() or with JS or with raw query.
BUT
Is it possible to implement that using the orm on the query level?
Something like that could be as a solution:
findOneById(id: number): Promise<User> {
return User.createQueryBuilder('user')
.leftJoinAndMapOne('user.isOwner', 'user.group', 'group')
.select(['user.id', 'user.fullName', 'user.email', 'group.isOwner AS user.isOwner']) // or probably 'group.isOwner AS user_isOwner'
.getOne();
}
And you could look at this answer, hope it would be helpful

Cyclic dependency with Postgres

I have two entities called User and Ad and the relation is 1:M, when I need to create a new Ad, I need to pass the announcer_id together.
Ad.ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
JoinColumn,
} from 'typeorm';
import User from './User';
#Entity('ads')
class Ad {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
announcer_id: string;
#ManyToOne(() => User)
#JoinColumn({ name: 'announcer_id' })
announcer: User;
}
export default Ad;
User.ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
#Entity('users')
class Ad {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
name: string;
#Column()
email: string;
#Column()
password: string;
#CreateDateColumn()
created_at: Date;
#UpdateDateColumn()
updated_at: Date;
}
export default Ad;
CreateAdService.ts
import { getCustomRepository } from 'typeorm';
import Ad from '../models/Ad';
import AdsRepository from '../repositories/AdsRepository';
interface IRequest {
announcer_id: string;
title: string;
description: string;
price: number;
n_room: number;
n_bathroom: number;
garage: boolean;
}
class CreateAdService {
public async execute({
announcer_id,
title,
description,
price,
n_room,
n_bathroom,
garage,
}: IRequest): Promise<Ad> {
const adsRepository = getCustomRepository(AdsRepository);
const priceNegative = adsRepository.isNegative(price);
if (priceNegative) {
throw new Error("You can't create an announcement with negative price.");
}
const ad = adsRepository.create({
announcer_id,
title,
description,
price,
n_room,
n_bathroom,
garage,
});
await adsRepository.save(ad);
return ad;
}
}
export default CreateAdService;
The Error
{
"error": "Cyclic dependency: \"Ad\""
}

How can I create a query on nested documents?

I am trying to find a document inside nested collection, but I do not know how should I create query for this. In TypeORM documentation there is only one simple example how to use find/findOne on not nested documents.
I created this query:
const result: BlogEntity = await this.blogRepository.findOne({posts: {_id: article._id}});
but when I am trying to build my project i am getting this error:
error TS2345: Argument of type '{ posts: { _id: ObjectID; }; }' is not assignable to parameter of type 'string | number | Date | ObjectID | FindOneOptions | Partial'.
After this i also tried :
const result: BlogEntity = await this.blogRepository.findOne({posts: {_id: article._id}});
const result: BlogEntity = await this.blogRepository.findOne({'posts._id': article._id});
const result: BlogEntity = await this.blogRepository.findOne({where: {posts: { _id: article._id}}});
const result: BlogEntity = await this.blogRepository.findOne({where: {'posts._id': _id: article._id}});
const result: ArticleEntity = await this.mongoManager.findOne(ArticleEntity, {_id: article._id});
But none of them is working
So the question is how should i correctly create this find query. BlogEntity and ArticleEntity code bellow
BlogEntity
#Entity()
#ObjectType()
export class BlogEntity {
#Field(() => ID)
#ObjectIdColumn()
_id!: ObjectID;
#Field(() => [ArticleEntity])
#Column()
posts!: ArticleEntity[];
#Field(() => [ArticleEntity])
#Column()
projects!: ArticleEntity[];
#Field(() => [ArticleEntity])
#Column()
tutorials!: ArticleEntity[];
}
ArticleEntity
#Entity()
#ObjectType()
export class ArticleEntity {
#Field(() => ID)
#ObjectIdColumn()
_id!: ObjectID;
#Field()
#Column()
title!: string;
#Field(() => [String])
#Column()
tags!: string[];
#Field()
#Column()
release!: Date;
#Field(() => [SectionEntity])
#Column()
sections!: SectionEntity[];
#Field({ nullable: true })
#Column()
tutorial?: string;
#Field({ nullable: true })
#Column()
app?: string;
}
If you need anything else ping me in the comment section.
When you are working with nested object's you need to use (.) operator
await this.blogRepository.findOne({'posts._id': article._id});
So it is impossible to achive this in TypeORM. The only one solution which I see there is to swich to Typegoose or Mongoose.
If you are more interested in this issue you can read more here: Issue: Query an Array of Embedded Documents #2483