mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
fix: empty objects being hydrated when eager loading relations that have a @VirtualColumn (#10927)
This commit is contained in:
parent
8429e8f9cc
commit
ae96f87923
@ -381,12 +381,12 @@ Special column that is never saved to the database and thus acts as a readonly p
|
||||
Each time you call `find` or `findOne` from the entity manager, the value is recalculated based on the query function that was provided in the VirtualColumn Decorator. The alias argument passed to the query references the exact entity alias of the generated query behind the scenes.
|
||||
|
||||
```typescript
|
||||
@Entity({ name: "companies", alias: "COMP" })
|
||||
export class Company extends BaseEntity {
|
||||
@Entity({ name: "companies" })
|
||||
export class Company {
|
||||
@PrimaryColumn("varchar", { length: 50 })
|
||||
name: string;
|
||||
|
||||
@VirtualColumn({ query: (alias) => `SELECT COUNT("name") FROM "employees" WHERE "companyName" = ${alias}.name` })
|
||||
@VirtualColumn({ query: (alias) => `SELECT COUNT("name") FROM "employees" WHERE "companyName" = ${alias}."name"` })
|
||||
totalEmployeesCount: number;
|
||||
|
||||
@OneToMany((type) => Employee, (employee) => employee.company)
|
||||
@ -394,7 +394,7 @@ export class Company extends BaseEntity {
|
||||
}
|
||||
|
||||
@Entity({ name: "employees" })
|
||||
export class Employee extends BaseEntity {
|
||||
export class Employee {
|
||||
@PrimaryColumn("varchar", { length: 50 })
|
||||
name: string;
|
||||
|
||||
|
||||
@ -5,7 +5,8 @@ import { SelectQueryBuilder } from "../../query-builder/SelectQueryBuilder"
|
||||
/**
|
||||
* Holds a number of children in the closure table of the column.
|
||||
*
|
||||
* @deprecated Do not use this decorator, it may be removed in the future versions
|
||||
* @deprecated This decorator will removed in the future versions.
|
||||
* Use {@link VirtualColumn} to calculate the count instead.
|
||||
*/
|
||||
export function RelationCount<T>(
|
||||
relation: string | ((object: T) => any),
|
||||
|
||||
@ -2882,11 +2882,6 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
|
||||
})
|
||||
})
|
||||
} else {
|
||||
if (column.isVirtualProperty) {
|
||||
// Do not add unselected virtual properties to final select
|
||||
return
|
||||
}
|
||||
|
||||
finalSelects.push({
|
||||
selection: selectionPath,
|
||||
aliasName: DriverUtils.buildAlias(
|
||||
@ -4240,8 +4235,8 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
|
||||
.join(" OR ")
|
||||
}
|
||||
} else {
|
||||
let andConditions: string[] = []
|
||||
for (let key in where) {
|
||||
const andConditions: string[] = []
|
||||
for (const key in where) {
|
||||
if (where[key] === undefined || where[key] === null) continue
|
||||
|
||||
const propertyPath = embedPrefix ? embedPrefix + "." + key : key
|
||||
@ -4261,7 +4256,7 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
|
||||
if (column) {
|
||||
let aliasPath = `${alias}.${propertyPath}`
|
||||
if (column.isVirtualProperty && column.query) {
|
||||
aliasPath = `(${column.query(alias)})`
|
||||
aliasPath = `(${column.query(this.escape(alias))})`
|
||||
}
|
||||
// const parameterName = alias + "_" + propertyPath.split(".").join("_") + "_" + parameterIndex;
|
||||
|
||||
@ -4271,13 +4266,14 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
|
||||
parameterValue = where[key].value
|
||||
}
|
||||
if (column.transformer) {
|
||||
parameterValue instanceof FindOperator
|
||||
? parameterValue.transformValue(column.transformer)
|
||||
: (parameterValue =
|
||||
ApplyValueTransformers.transformTo(
|
||||
column.transformer,
|
||||
parameterValue,
|
||||
))
|
||||
if (parameterValue instanceof FindOperator) {
|
||||
parameterValue.transformValue(column.transformer)
|
||||
} else {
|
||||
parameterValue = ApplyValueTransformers.transformTo(
|
||||
column.transformer,
|
||||
parameterValue,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// if (parameterValue === null) {
|
||||
|
||||
@ -239,7 +239,7 @@ export class RawSqlResultsToEntityTransformer {
|
||||
|
||||
if (value === undefined) continue
|
||||
// we don't mark it as has data because if we will have all nulls in our object - we don't need such object
|
||||
else if (value !== null) hasData = true
|
||||
else if (value !== null && !column.isVirtualProperty) hasData = true
|
||||
|
||||
column.setEntityValue(
|
||||
entity,
|
||||
|
||||
@ -1,20 +1,19 @@
|
||||
import {
|
||||
ManyToOne,
|
||||
Entity,
|
||||
BaseEntity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
} from "../../../../src"
|
||||
import TimeSheet from "./TimeSheet"
|
||||
Entity,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
} from "../../../../../src"
|
||||
import { TimeSheet } from "./TimeSheet"
|
||||
|
||||
@Entity({ name: "activities" })
|
||||
export default class Activity extends BaseEntity {
|
||||
export class Activity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number
|
||||
|
||||
@Column("int")
|
||||
hours: number
|
||||
|
||||
@ManyToOne((type) => TimeSheet, (timesheet) => timesheet.activities)
|
||||
@ManyToOne(() => TimeSheet, (timesheet) => timesheet.activities)
|
||||
timesheet: TimeSheet
|
||||
}
|
||||
@ -3,21 +3,20 @@ import {
|
||||
OneToMany,
|
||||
PrimaryColumn,
|
||||
VirtualColumn,
|
||||
BaseEntity,
|
||||
} from "../../../../src"
|
||||
import Employee from "./Employee"
|
||||
} from "../../../../../src"
|
||||
import { Employee } from "./Employee"
|
||||
|
||||
@Entity({ name: "companies" })
|
||||
export default class Company extends BaseEntity {
|
||||
export class Company {
|
||||
@PrimaryColumn("varchar", { length: 50 })
|
||||
name: string
|
||||
|
||||
@VirtualColumn({
|
||||
query: (alias) =>
|
||||
`SELECT COUNT("name") FROM "employees" WHERE "companyName" = ${alias}.name`,
|
||||
`SELECT COUNT("name") FROM "employees" WHERE "companyName" = ${alias}."name"`,
|
||||
})
|
||||
totalEmployeesCount: number
|
||||
|
||||
@OneToMany((type) => Employee, (employee) => employee.company)
|
||||
@OneToMany(() => Employee, (employee) => employee.company)
|
||||
employees: Employee[]
|
||||
}
|
||||
20
test/functional/columns/virtual-columns/entity/Employee.ts
Normal file
20
test/functional/columns/virtual-columns/entity/Employee.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import {
|
||||
Entity,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryColumn
|
||||
} from "../../../../../src"
|
||||
import { Company } from "./Company"
|
||||
import { TimeSheet } from "./TimeSheet"
|
||||
|
||||
@Entity({ name: "employees" })
|
||||
export class Employee {
|
||||
@PrimaryColumn("varchar", { length: 50 })
|
||||
name: string
|
||||
|
||||
@ManyToOne(() => Company, (company) => company.employees)
|
||||
company: Company
|
||||
|
||||
@OneToMany(() => TimeSheet, (timesheet) => timesheet.employee)
|
||||
timesheets: TimeSheet[]
|
||||
}
|
||||
26
test/functional/columns/virtual-columns/entity/TimeSheet.ts
Normal file
26
test/functional/columns/virtual-columns/entity/TimeSheet.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import {
|
||||
Entity,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
VirtualColumn,
|
||||
} from "../../../../../src"
|
||||
import { Activity } from "./Activity"
|
||||
import { Employee } from "./Employee"
|
||||
|
||||
@Entity({ name: "timesheets" })
|
||||
export class TimeSheet {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number
|
||||
|
||||
@VirtualColumn({
|
||||
query: (alias) =>
|
||||
`SELECT SUM("hours") FROM "activities" WHERE "timesheetId" = ${alias}."id"`,
|
||||
})
|
||||
totalActivityHours: number
|
||||
|
||||
@ManyToOne(() => Activity, (activity) => activity.timesheet)
|
||||
activities: Activity[]
|
||||
|
||||
@ManyToOne(() => Employee, (employee) => employee.timesheets)
|
||||
employee: Employee
|
||||
}
|
||||
@ -4,35 +4,57 @@ import {
|
||||
DataSource,
|
||||
FindManyOptions,
|
||||
FindOneOptions,
|
||||
FindOptionsUtils,
|
||||
MoreThan,
|
||||
} from "../../../src"
|
||||
} from "../../../../src"
|
||||
import { DriverUtils } from "../../../../src/driver/DriverUtils"
|
||||
import {
|
||||
closeTestingConnections,
|
||||
createTestingConnections,
|
||||
} from "../../utils/test-utils"
|
||||
import Activity from "./entity/Activity"
|
||||
import Company from "./entity/Company"
|
||||
import Employee from "./entity/Employee"
|
||||
import TimeSheet from "./entity/TimeSheet"
|
||||
} from "../../../utils/test-utils"
|
||||
import { Activity } from "./entity/Activity"
|
||||
import { Company } from "./entity/Company"
|
||||
import { Employee } from "./entity/Employee"
|
||||
import { TimeSheet } from "./entity/TimeSheet"
|
||||
|
||||
describe("github issues > #9323 Add new VirtualColumn decorator feature", () => {
|
||||
describe("column > virtual columns", () => {
|
||||
let connections: DataSource[]
|
||||
before(
|
||||
async () =>
|
||||
(connections = await createTestingConnections({
|
||||
enabledDrivers: ["postgres"],
|
||||
schemaCreate: true,
|
||||
dropSchema: true,
|
||||
entities: [Company, Employee, TimeSheet, Activity],
|
||||
})),
|
||||
)
|
||||
before(async () => {
|
||||
connections = await createTestingConnections({
|
||||
schemaCreate: true,
|
||||
dropSchema: true,
|
||||
entities: [Company, Employee, TimeSheet, Activity],
|
||||
})
|
||||
|
||||
for (const connection of connections) {
|
||||
// By default, MySQL uses backticks instead of quotes for identifiers
|
||||
if (DriverUtils.isMySQLFamily(connection.driver)) {
|
||||
const totalEmployeesCountMetadata = connection
|
||||
.getMetadata(Company)
|
||||
.columns.find(
|
||||
(columnMetadata) =>
|
||||
columnMetadata.propertyName ===
|
||||
"totalEmployeesCount",
|
||||
)!
|
||||
totalEmployeesCountMetadata.query = (alias) =>
|
||||
`SELECT COUNT(\`name\`) FROM \`employees\` WHERE \`companyName\` = ${alias}.\`name\``
|
||||
|
||||
const totalActivityHoursMetadata = connection
|
||||
.getMetadata(TimeSheet)
|
||||
.columns.find(
|
||||
(columnMetadata) =>
|
||||
columnMetadata.propertyName ===
|
||||
"totalActivityHours",
|
||||
)!
|
||||
totalActivityHoursMetadata.query = (alias) =>
|
||||
`SELECT SUM(\`hours\`) FROM \`activities\` WHERE \`timesheetId\` = ${alias}.\`id\``
|
||||
}
|
||||
}
|
||||
})
|
||||
after(() => closeTestingConnections(connections))
|
||||
|
||||
it("should generate expected sub-select & select statement", () =>
|
||||
Promise.all(
|
||||
connections.map((connection) => {
|
||||
const metadata = connection.getMetadata(Company)
|
||||
const options1: FindManyOptions<Company> = {
|
||||
select: {
|
||||
name: true,
|
||||
@ -41,32 +63,28 @@ describe("github issues > #9323 Add new VirtualColumn decorator feature", () =>
|
||||
}
|
||||
|
||||
const query1 = connection
|
||||
.createQueryBuilder(
|
||||
Company,
|
||||
FindOptionsUtils.extractFindManyOptionsAlias(
|
||||
options1,
|
||||
) || metadata.name,
|
||||
)
|
||||
.setFindOptions(options1 || {})
|
||||
.createQueryBuilder(Company, "Company")
|
||||
.setFindOptions(options1)
|
||||
.getSql()
|
||||
|
||||
expect(query1).to.eq(
|
||||
`SELECT "Company"."name" AS "Company_name", (SELECT COUNT("name") FROM "employees" WHERE "companyName" = "Company".name) AS "Company_totalEmployeesCount" FROM "companies" "Company"`,
|
||||
)
|
||||
let expectedQuery = `SELECT "Company"."name" AS "Company_name", (SELECT COUNT("name") FROM "employees" WHERE "companyName" = "Company"."name") AS "Company_totalEmployeesCount" FROM "companies" "Company"`
|
||||
if (DriverUtils.isMySQLFamily(connection.driver)) {
|
||||
expectedQuery = expectedQuery.replaceAll('"', "`")
|
||||
}
|
||||
expect(query1).to.eq(expectedQuery)
|
||||
}),
|
||||
))
|
||||
|
||||
it("should generate expected sub-select & nested-subselect statement", () =>
|
||||
Promise.all(
|
||||
connections.map((connection) => {
|
||||
const metadata = connection.getMetadata(Company)
|
||||
const options1: FindManyOptions<Company> = {
|
||||
const findOptions: FindManyOptions<Company> = {
|
||||
select: {
|
||||
name: true,
|
||||
totalEmployeesCount: true,
|
||||
employees: {
|
||||
timesheets: {
|
||||
totalActvityHours: true,
|
||||
totalActivityHours: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -77,29 +95,25 @@ describe("github issues > #9323 Add new VirtualColumn decorator feature", () =>
|
||||
},
|
||||
}
|
||||
|
||||
const query1 = connection
|
||||
.createQueryBuilder(
|
||||
Company,
|
||||
FindOptionsUtils.extractFindManyOptionsAlias(
|
||||
options1,
|
||||
) || metadata.name,
|
||||
)
|
||||
.setFindOptions(options1 || {})
|
||||
const query = connection
|
||||
.createQueryBuilder(Company, "Company")
|
||||
.setFindOptions(findOptions)
|
||||
.getSql()
|
||||
|
||||
expect(query1).to.include(
|
||||
`SELECT "Company"."name" AS "Company_name"`,
|
||||
)
|
||||
expect(query1).to.include(
|
||||
`(SELECT COUNT("name") FROM "employees" WHERE "companyName" = "Company".name) AS "Company_totalEmployeesCount", (SELECT SUM("hours") FROM "activities" WHERE "timesheetId" =`,
|
||||
)
|
||||
let expectedQuery1 = `SELECT "Company"."name" AS "Company_name"`
|
||||
let expectedQuery2 = `(SELECT COUNT("name") FROM "employees" WHERE "companyName" = "Company"."name") AS "Company_totalEmployeesCount", (SELECT SUM("hours") FROM "activities" WHERE "timesheetId" =`
|
||||
if (DriverUtils.isMySQLFamily(connection.driver)) {
|
||||
expectedQuery1 = expectedQuery1.replaceAll('"', "`")
|
||||
expectedQuery2 = expectedQuery2.replaceAll('"', "`")
|
||||
}
|
||||
expect(query).to.include(expectedQuery1)
|
||||
expect(query).to.include(expectedQuery2)
|
||||
}),
|
||||
))
|
||||
|
||||
it("should not generate sub-select if column is not selected", () =>
|
||||
Promise.all(
|
||||
connections.map((connection) => {
|
||||
const metadata = connection.getMetadata(Company)
|
||||
const options: FindManyOptions<Company> = {
|
||||
select: {
|
||||
name: true,
|
||||
@ -107,69 +121,73 @@ describe("github issues > #9323 Add new VirtualColumn decorator feature", () =>
|
||||
},
|
||||
}
|
||||
const query = connection
|
||||
.createQueryBuilder(
|
||||
Company,
|
||||
FindOptionsUtils.extractFindManyOptionsAlias(options) ||
|
||||
metadata.name,
|
||||
)
|
||||
.setFindOptions(options || {})
|
||||
.createQueryBuilder(Company, "Company")
|
||||
.setFindOptions(options)
|
||||
.getSql()
|
||||
|
||||
expect(query).to.eq(
|
||||
`SELECT "Company"."name" AS "Company_name" FROM "companies" "Company"`,
|
||||
)
|
||||
let expectedQuery = `SELECT "Company"."name" AS "Company_name" FROM "companies" "Company"`
|
||||
if (DriverUtils.isMySQLFamily(connection.driver)) {
|
||||
expectedQuery = expectedQuery.replaceAll('"', "`")
|
||||
}
|
||||
expect(query).to.eq(expectedQuery)
|
||||
}),
|
||||
))
|
||||
|
||||
it("should be able to save and find sub-select data in the database", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
const companyName = "My Company 1"
|
||||
const company = Company.create({ name: companyName } as Company)
|
||||
await company.save()
|
||||
const activityRepository = connection.getRepository(Activity)
|
||||
const companyRepository = connection.getRepository(Company)
|
||||
const employeeRepository = connection.getRepository(Employee)
|
||||
const timesheetRepository = connection.getRepository(TimeSheet)
|
||||
|
||||
const employee1 = Employee.create({
|
||||
const companyName = "My Company 1"
|
||||
const company = companyRepository.create({ name: companyName })
|
||||
await companyRepository.save(company)
|
||||
|
||||
const employee1 = employeeRepository.create({
|
||||
name: "Collin 1",
|
||||
company: company,
|
||||
})
|
||||
const employee2 = Employee.create({
|
||||
const employee2 = employeeRepository.create({
|
||||
name: "John 1",
|
||||
company: company,
|
||||
})
|
||||
const employee3 = Employee.create({
|
||||
const employee3 = employeeRepository.create({
|
||||
name: "Cory 1",
|
||||
company: company,
|
||||
})
|
||||
const employee4 = Employee.create({
|
||||
const employee4 = employeeRepository.create({
|
||||
name: "Kevin 1",
|
||||
company: company,
|
||||
})
|
||||
await Employee.save([
|
||||
await employeeRepository.save([
|
||||
employee1,
|
||||
employee2,
|
||||
employee3,
|
||||
employee4,
|
||||
])
|
||||
|
||||
const employee1TimeSheet = TimeSheet.create({
|
||||
const employee1TimeSheet = timesheetRepository.create({
|
||||
employee: employee1,
|
||||
})
|
||||
await employee1TimeSheet.save()
|
||||
const employee1Activities: Activity[] = [
|
||||
Activity.create({
|
||||
await timesheetRepository.save(employee1TimeSheet)
|
||||
|
||||
const employee1Activities = activityRepository.create([
|
||||
{
|
||||
hours: 2,
|
||||
timesheet: employee1TimeSheet,
|
||||
}),
|
||||
Activity.create({
|
||||
},
|
||||
{
|
||||
hours: 2,
|
||||
timesheet: employee1TimeSheet,
|
||||
}),
|
||||
Activity.create({
|
||||
},
|
||||
{
|
||||
hours: 2,
|
||||
timesheet: employee1TimeSheet,
|
||||
}),
|
||||
]
|
||||
await Activity.save(employee1Activities)
|
||||
},
|
||||
])
|
||||
await activityRepository.save(employee1Activities)
|
||||
|
||||
const findOneOptions: FindOneOptions<Company> = {
|
||||
select: {
|
||||
@ -179,7 +197,7 @@ describe("github issues > #9323 Add new VirtualColumn decorator feature", () =>
|
||||
name: true,
|
||||
timesheets: {
|
||||
id: true,
|
||||
totalActvityHours: true,
|
||||
totalActivityHours: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -193,7 +211,7 @@ describe("github issues > #9323 Add new VirtualColumn decorator feature", () =>
|
||||
totalEmployeesCount: MoreThan(2),
|
||||
employees: {
|
||||
timesheets: {
|
||||
totalActvityHours: MoreThan(0),
|
||||
totalActivityHours: MoreThan(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -201,20 +219,24 @@ describe("github issues > #9323 Add new VirtualColumn decorator feature", () =>
|
||||
employees: {
|
||||
timesheets: {
|
||||
id: "DESC",
|
||||
totalActvityHours: "ASC",
|
||||
totalActivityHours: "ASC",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const usersUnderCompany = await Company.findOne(findOneOptions)
|
||||
const usersUnderCompany = await companyRepository.findOne(
|
||||
findOneOptions,
|
||||
)
|
||||
expect(usersUnderCompany?.totalEmployeesCount).to.eq(4)
|
||||
const employee1TimesheetFound = usersUnderCompany?.employees
|
||||
.find((e) => e.name === employee1.name)
|
||||
?.timesheets.find((ts) => ts.id === employee1TimeSheet.id)
|
||||
expect(employee1TimesheetFound?.totalActvityHours).to.eq(6)
|
||||
expect(employee1TimesheetFound?.totalActivityHours).to.eq(6)
|
||||
|
||||
const usersUnderCompanyList = await Company.find(findOneOptions)
|
||||
const usersUnderCompanyList = await companyRepository.find(
|
||||
findOneOptions,
|
||||
)
|
||||
const usersUnderCompanyListOne = usersUnderCompanyList[0]
|
||||
expect(usersUnderCompanyListOne?.totalEmployeesCount).to.eq(4)
|
||||
const employee1TimesheetListOneFound =
|
||||
@ -223,52 +245,57 @@ describe("github issues > #9323 Add new VirtualColumn decorator feature", () =>
|
||||
?.timesheets.find(
|
||||
(ts) => ts.id === employee1TimeSheet.id,
|
||||
)
|
||||
expect(employee1TimesheetListOneFound?.totalActvityHours).to.eq(
|
||||
6,
|
||||
)
|
||||
expect(
|
||||
employee1TimesheetListOneFound?.totalActivityHours,
|
||||
).to.eq(6)
|
||||
}),
|
||||
))
|
||||
|
||||
it("should be able to save and find sub-select data in the database (with query builder)", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
const companyName = "My Company 2"
|
||||
const company = Company.create({ name: companyName } as Company)
|
||||
await company.save()
|
||||
const activityRepository = connection.getRepository(Activity)
|
||||
const companyRepository = connection.getRepository(Company)
|
||||
const employeeRepository = connection.getRepository(Employee)
|
||||
const timesheetRepository = connection.getRepository(TimeSheet)
|
||||
|
||||
const employee1 = Employee.create({
|
||||
const companyName = "My Company 2"
|
||||
const company = companyRepository.create({ name: companyName })
|
||||
await companyRepository.save(company)
|
||||
|
||||
const employee1 = employeeRepository.create({
|
||||
name: "Collin 2",
|
||||
company: company,
|
||||
})
|
||||
const employee2 = Employee.create({
|
||||
const employee2 = employeeRepository.create({
|
||||
name: "John 2",
|
||||
company: company,
|
||||
})
|
||||
const employee3 = Employee.create({
|
||||
const employee3 = employeeRepository.create({
|
||||
name: "Cory 2",
|
||||
company: company,
|
||||
})
|
||||
await Employee.save([employee1, employee2, employee3])
|
||||
await employeeRepository.save([employee1, employee2, employee3])
|
||||
|
||||
const employee1TimeSheet = TimeSheet.create({
|
||||
const employee1TimeSheet = timesheetRepository.create({
|
||||
employee: employee1,
|
||||
})
|
||||
await employee1TimeSheet.save()
|
||||
const employee1Activities: Activity[] = [
|
||||
Activity.create({
|
||||
await timesheetRepository.save(employee1TimeSheet)
|
||||
const employee1Activities = activityRepository.create([
|
||||
{
|
||||
hours: 2,
|
||||
timesheet: employee1TimeSheet,
|
||||
}),
|
||||
Activity.create({
|
||||
},
|
||||
{
|
||||
hours: 2,
|
||||
timesheet: employee1TimeSheet,
|
||||
}),
|
||||
Activity.create({
|
||||
},
|
||||
{
|
||||
hours: 2,
|
||||
timesheet: employee1TimeSheet,
|
||||
}),
|
||||
]
|
||||
await Activity.save(employee1Activities)
|
||||
},
|
||||
])
|
||||
await activityRepository.save(employee1Activities)
|
||||
|
||||
const companyQueryData = await connection
|
||||
.createQueryBuilder(Company, "company")
|
||||
@ -277,7 +304,7 @@ describe("github issues > #9323 Add new VirtualColumn decorator feature", () =>
|
||||
"company.totalEmployeesCount",
|
||||
"employee.name",
|
||||
"timesheet.id",
|
||||
"timesheet.totalActvityHours",
|
||||
"timesheet.totalActivityHours",
|
||||
])
|
||||
.leftJoin("company.employees", "employee")
|
||||
.leftJoin("employee.timesheets", "timesheet")
|
||||
@ -286,7 +313,7 @@ describe("github issues > #9323 Add new VirtualColumn decorator feature", () =>
|
||||
//.andWhere("company.totalEmployeesCount > 2")
|
||||
//.orderBy({
|
||||
// "employees.timesheets.id": "DESC",
|
||||
// //"employees.timesheets.totalActvityHours": "ASC",
|
||||
// //"employees.timesheets.totalActivityHours": "ASC",
|
||||
//})
|
||||
.getOne()
|
||||
|
||||
@ -297,7 +324,7 @@ describe("github issues > #9323 Add new VirtualColumn decorator feature", () =>
|
||||
(t) => t.id === employee1TimeSheet.id,
|
||||
)
|
||||
|
||||
expect(foundEmployeeTimeSheet?.totalActvityHours).to.eq(6)
|
||||
expect(foundEmployeeTimeSheet?.totalActivityHours).to.eq(6)
|
||||
}),
|
||||
))
|
||||
})
|
||||
@ -14,7 +14,7 @@ export class Category {
|
||||
|
||||
@VirtualColumn({
|
||||
query: (alias) =>
|
||||
`SELECT COUNT(*) FROM category WHERE id = ${alias}.id`,
|
||||
`SELECT COUNT(*) FROM "category" WHERE "id" = ${alias}."id"`,
|
||||
})
|
||||
randomVirtualColumn: number
|
||||
|
||||
|
||||
@ -1,25 +1,40 @@
|
||||
import "reflect-metadata"
|
||||
import {
|
||||
createTestingConnections,
|
||||
closeTestingConnections,
|
||||
reloadTestingDatabases,
|
||||
} from "../../utils/test-utils"
|
||||
import { DataSource } from "../../../src"
|
||||
import { expect } from "chai"
|
||||
import "reflect-metadata"
|
||||
import { DataSource } from "../../../src"
|
||||
import {
|
||||
closeTestingConnections,
|
||||
createTestingConnections,
|
||||
} from "../../utils/test-utils"
|
||||
|
||||
import { DriverUtils } from "../../../src/driver/DriverUtils"
|
||||
import { Category, Product } from "./entity"
|
||||
|
||||
describe("github issues > #10431 When requesting nested relations on foreign key primary entities, relation becomes empty entity rather than null", () => {
|
||||
let connections: DataSource[]
|
||||
before(
|
||||
async () =>
|
||||
(connections = await createTestingConnections({
|
||||
entities: [Category, Product],
|
||||
schemaCreate: true,
|
||||
dropSchema: true,
|
||||
})),
|
||||
)
|
||||
beforeEach(() => reloadTestingDatabases(connections))
|
||||
|
||||
before(async () => {
|
||||
connections = await createTestingConnections({
|
||||
entities: [Category, Product],
|
||||
schemaCreate: true,
|
||||
dropSchema: true,
|
||||
})
|
||||
|
||||
for (const connection of connections) {
|
||||
// By default, MySQL uses backticks instead of quotes for identifiers
|
||||
if (DriverUtils.isMySQLFamily(connection.driver)) {
|
||||
const randomVirtualColumnMetadata = connection
|
||||
.getMetadata(Category)
|
||||
.columns.find(
|
||||
(columnMetadata) =>
|
||||
columnMetadata.propertyName ===
|
||||
"randomVirtualColumn",
|
||||
)!
|
||||
|
||||
randomVirtualColumnMetadata.query = (alias) =>
|
||||
`SELECT COUNT(*) FROM \`category\` WHERE \`id\` = ${alias}.\`id\``
|
||||
}
|
||||
}
|
||||
})
|
||||
after(() => closeTestingConnections(connections))
|
||||
|
||||
it("should return [] when requested nested relations are empty on ManyToMany relation with @VirtualColumn definitions", () =>
|
||||
@ -28,13 +43,16 @@ describe("github issues > #10431 When requesting nested relations on foreign key
|
||||
const productRepo = connection.getRepository(Product)
|
||||
const testProduct = new Product()
|
||||
testProduct.name = "foo"
|
||||
|
||||
await productRepo.save(testProduct)
|
||||
|
||||
const foundProduct = await productRepo.findOne({
|
||||
where: {
|
||||
id: testProduct.id,
|
||||
},
|
||||
relations: { categories: true },
|
||||
})
|
||||
|
||||
expect(foundProduct?.name).eq("foo")
|
||||
expect(foundProduct?.categories).eql([])
|
||||
}),
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
import {
|
||||
ManyToOne,
|
||||
Entity,
|
||||
PrimaryColumn,
|
||||
BaseEntity,
|
||||
OneToMany,
|
||||
} from "../../../../src"
|
||||
import TimeSheet from "./TimeSheet"
|
||||
import Company from "./Company"
|
||||
|
||||
@Entity({ name: "employees" })
|
||||
export default class Employee extends BaseEntity {
|
||||
@PrimaryColumn("varchar", { length: 50 })
|
||||
name: string
|
||||
|
||||
@ManyToOne((type) => Company, (company) => company.employees)
|
||||
company: Company
|
||||
|
||||
@OneToMany((type) => TimeSheet, (timesheet) => timesheet.employee)
|
||||
timesheets: TimeSheet[]
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
import {
|
||||
ManyToOne,
|
||||
Entity,
|
||||
BaseEntity,
|
||||
PrimaryGeneratedColumn,
|
||||
VirtualColumn,
|
||||
} from "../../../../src"
|
||||
import Activity from "./Activity"
|
||||
import Employee from "./Employee"
|
||||
|
||||
@Entity({ name: "timesheets" })
|
||||
export default class TimeSheet extends BaseEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number
|
||||
|
||||
@VirtualColumn({
|
||||
query: (alias) =>
|
||||
`SELECT SUM("hours") FROM "activities" WHERE "timesheetId" = ${alias}.id`,
|
||||
})
|
||||
totalActvityHours: number
|
||||
|
||||
@ManyToOne((type) => Activity, (activity) => activity.timesheet)
|
||||
activities: Activity[]
|
||||
|
||||
@ManyToOne((type) => Employee, (employee) => employee.timesheets)
|
||||
employee: Employee
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user