@@ -500,4 +500,269 @@ model Post extends Content {
500500 preview : 'This is the full body content of the post' ,
501501 } ) ;
502502 } ) ;
503+
504+ it ( 'works with update operations' , async ( ) => {
505+ const db = await createTestClient (
506+ `
507+ model User {
508+ id Int @id @default(autoincrement())
509+ firstName String
510+ lastName String
511+ fullName String @virtual
512+ }
513+ ` ,
514+ {
515+ virtualFields : {
516+ User : {
517+ fullName : ( row : any ) => `${ row . firstName } ${ row . lastName } ` ,
518+ } ,
519+ } ,
520+ } as any ,
521+ ) ;
522+
523+ await db . user . create ( {
524+ data : { id : 1 , firstName : 'Alex' , lastName : 'Smith' } ,
525+ } ) ;
526+
527+ // Update should return the virtual field
528+ const updated = await db . user . update ( {
529+ where : { id : 1 } ,
530+ data : { firstName : 'John' } ,
531+ } ) ;
532+
533+ expect ( updated . fullName ) . toBe ( 'John Smith' ) ;
534+ } ) ;
535+
536+ it ( 'works with upsert operations' , async ( ) => {
537+ const db = await createTestClient (
538+ `
539+ model User {
540+ id Int @id @default(autoincrement())
541+ firstName String
542+ lastName String
543+ fullName String @virtual
544+ }
545+ ` ,
546+ {
547+ virtualFields : {
548+ User : {
549+ fullName : ( row : any ) => `${ row . firstName } ${ row . lastName } ` ,
550+ } ,
551+ } ,
552+ } as any ,
553+ ) ;
554+
555+ // Upsert create path
556+ const created = await db . user . upsert ( {
557+ where : { id : 1 } ,
558+ create : { id : 1 , firstName : 'Alex' , lastName : 'Smith' } ,
559+ update : { firstName : 'John' } ,
560+ } ) ;
561+
562+ expect ( created . fullName ) . toBe ( 'Alex Smith' ) ;
563+
564+ // Upsert update path
565+ const updated = await db . user . upsert ( {
566+ where : { id : 1 } ,
567+ create : { id : 1 , firstName : 'Alex' , lastName : 'Smith' } ,
568+ update : { firstName : 'John' } ,
569+ } ) ;
570+
571+ expect ( updated . fullName ) . toBe ( 'John Smith' ) ;
572+ } ) ;
573+
574+ it ( 'works with multiple virtual fields on same model' , async ( ) => {
575+ const db = await createTestClient (
576+ `
577+ model User {
578+ id Int @id @default(autoincrement())
579+ firstName String
580+ lastName String
581+ email String
582+ fullName String @virtual
583+ displayEmail String @virtual
584+ initials String @virtual
585+ }
586+ ` ,
587+ {
588+ virtualFields : {
589+ User : {
590+ fullName : ( row : any ) => `${ row . firstName } ${ row . lastName } ` ,
591+ displayEmail : ( row : any ) => row . email . toLowerCase ( ) ,
592+ initials : ( row : any ) => `${ row . firstName [ 0 ] } ${ row . lastName [ 0 ] } ` . toUpperCase ( ) ,
593+ } ,
594+ } ,
595+ } as any ,
596+ ) ;
597+
598+ const user = await db . user . create ( {
599+ data : { id : 1 , firstName : 'Alex' , lastName : 'Smith' , email : 'ALEX@EXAMPLE.COM' } ,
600+ } ) ;
601+
602+ expect ( user . fullName ) . toBe ( 'Alex Smith' ) ;
603+ expect ( user . displayEmail ) . toBe ( 'alex@example.com' ) ;
604+ expect ( user . initials ) . toBe ( 'AS' ) ;
605+ } ) ;
606+
607+ it ( 'works with relations and virtual fields on MySQL (lateral join dialect)' , async ( ) => {
608+ // This test targets the lateral join dialect used by MySQL
609+ const db = await createTestClient (
610+ `
611+ model User {
612+ id Int @id @default(autoincrement())
613+ name String
614+ displayName String @virtual
615+ posts Post[]
616+ }
617+
618+ model Post {
619+ id Int @id @default(autoincrement())
620+ title String
621+ author User @relation(fields: [authorId], references: [id])
622+ authorId Int
623+ }
624+ ` ,
625+ {
626+ provider : 'mysql' ,
627+ virtualFields : {
628+ User : {
629+ displayName : ( row : any ) => `@${ row . name } ` ,
630+ } ,
631+ } ,
632+ } as any ,
633+ ) ;
634+
635+ await db . user . create ( {
636+ data : { id : 1 , name : 'alex' , posts : { create : { title : 'Post1' } } } ,
637+ } ) ;
638+
639+ await expect (
640+ db . post . findFirst ( {
641+ include : { author : true } ,
642+ } ) ,
643+ ) . resolves . toMatchObject ( {
644+ title : 'Post1' ,
645+ author : expect . objectContaining ( {
646+ name : 'alex' ,
647+ displayName : '@alex' ,
648+ } ) ,
649+ } ) ;
650+ } ) ;
651+
652+ it ( 'virtual field can access included relation data' , async ( ) => {
653+ const db = await createTestClient (
654+ `
655+ model User {
656+ id Int @id @default(autoincrement())
657+ name String
658+ posts Post[]
659+ }
660+
661+ model Post {
662+ id Int @id @default(autoincrement())
663+ title String
664+ author User @relation(fields: [authorId], references: [id])
665+ authorId Int
666+ authorDisplay String @virtual
667+ }
668+ ` ,
669+ {
670+ virtualFields : {
671+ Post : {
672+ authorDisplay : ( row : any ) => {
673+ // Virtual field can access included relation data
674+ if ( row . author ) {
675+ return `by ${ row . author . name } ` ;
676+ }
677+ return `by user #${ row . authorId } ` ;
678+ } ,
679+ } ,
680+ } ,
681+ } as any ,
682+ ) ;
683+
684+ await db . user . create ( {
685+ data : { id : 1 , name : 'Alex' , posts : { create : { title : 'My Post' } } } ,
686+ } ) ;
687+
688+ // Without including author
689+ const postWithoutAuthor = await db . post . findFirst ( ) ;
690+ expect ( postWithoutAuthor ?. authorDisplay ) . toBe ( 'by user #1' ) ;
691+
692+ // With including author
693+ const postWithAuthor = await db . post . findFirst ( {
694+ include : { author : true } ,
695+ } ) ;
696+ expect ( postWithAuthor ?. authorDisplay ) . toBe ( 'by Alex' ) ;
697+ } ) ;
698+
699+ it ( 'respects omit clause - skips virtual field computation' , async ( ) => {
700+ let virtualFieldCalled = false ;
701+
702+ const db = await createTestClient (
703+ `
704+ model User {
705+ id Int @id @default(autoincrement())
706+ name String
707+ displayName String @virtual
708+ }
709+ ` ,
710+ {
711+ virtualFields : {
712+ User : {
713+ displayName : ( row : any ) => {
714+ virtualFieldCalled = true ;
715+ return `@${ row . name } ` ;
716+ } ,
717+ } ,
718+ } ,
719+ } as any ,
720+ ) ;
721+
722+ await db . user . create ( {
723+ data : { id : 1 , name : 'Alex' } ,
724+ } ) ;
725+
726+ virtualFieldCalled = false ;
727+
728+ // When omitting the virtual field, it should NOT be computed
729+ const result = await db . user . findUnique ( {
730+ where : { id : 1 } ,
731+ omit : { displayName : true } ,
732+ } ) ;
733+
734+ expect ( result ) . toMatchObject ( { id : 1 , name : 'Alex' } ) ;
735+ expect ( result ) . not . toHaveProperty ( 'displayName' ) ;
736+ expect ( virtualFieldCalled ) . toBe ( false ) ;
737+ } ) ;
738+
739+ it ( 'propagates errors from virtual field functions' , async ( ) => {
740+ const db = await createTestClient (
741+ `
742+ model User {
743+ id Int @id @default(autoincrement())
744+ name String
745+ problematic String @virtual
746+ }
747+ ` ,
748+ {
749+ virtualFields : {
750+ User : {
751+ problematic : ( ) => {
752+ throw new Error ( 'Virtual field computation failed' ) ;
753+ } ,
754+ } ,
755+ } ,
756+ } as any ,
757+ ) ;
758+
759+ await db . user . create ( {
760+ data : { id : 1 , name : 'Alex' } ,
761+ } ) ;
762+
763+ // The error should propagate
764+ await expect ( db . user . findUnique ( { where : { id : 1 } } ) ) . rejects . toThrow (
765+ 'Virtual field computation failed' ,
766+ ) ;
767+ } ) ;
503768} ) ;
0 commit comments