@@ -5,7 +5,14 @@ cds.infer.target ??= q => q._target || q.target // instanceof cds.entity ? q._ta
55
66const infer = require ( './infer' )
77const { computeColumnsToBeSearched } = require ( './search' )
8- const { prettyPrintRef, isCalculatedOnRead, isCalculatedElement, getImplicitAlias, defineProperty, getModelUtils } = require ( './utils' )
8+ const {
9+ prettyPrintRef,
10+ isCalculatedOnRead,
11+ isCalculatedElement,
12+ getImplicitAlias,
13+ defineProperty,
14+ getModelUtils,
15+ } = require ( './utils' )
916
1017/**
1118 * For operators of <eqOps>, this is replaced by comparing all leaf elements with null, combined with and.
@@ -95,6 +102,7 @@ function cqn4sql(originalQuery, model) {
95102 const from = queryProp . from
96103
97104 const transformedProp = { __proto__ : queryProp } // IMPORTANT: don't lose anything you might not know of
105+ const queryNeedsJoins = inferred . joinTree && ! inferred . joinTree . isInitial
98106
99107 // Transform the existing where, prepend table aliases, and so on...
100108 if ( where ) {
@@ -104,7 +112,47 @@ function cqn4sql(originalQuery, model) {
104112 // Transform the from clause: association path steps turn into `WHERE EXISTS` subqueries.
105113 // The already transformed `where` clause is then glued together with the resulting subqueries.
106114 const { transformedWhere, transformedFrom } = getTransformedFrom ( from || entity , transformedProp . where )
107- const queryNeedsJoins = inferred . joinTree && ! inferred . joinTree . isInitial
115+
116+ // build a subquery for DELETE / UPDATE queries with path expressions and match the primary keys
117+ if ( queryNeedsJoins && ( inferred . UPDATE || inferred . DELETE ) ) {
118+ const prop = inferred . UPDATE ? 'UPDATE' : 'DELETE'
119+ const subquery = {
120+ SELECT : {
121+ from : { ...( from || entity ) } ,
122+ columns : [ ] , // primary keys of the query target will be added later
123+ where : [ ...where ] ,
124+ } ,
125+ }
126+ // The alias of the original query is now the alias for the subquery
127+ // so that potential references in the where clause to the alias match.
128+ // Hence, replace the alias of the original query with the next
129+ // available alias, so that each alias is unique.
130+ const uniqueSubqueryAlias = getNextAvailableTableAlias ( transformedFrom . as )
131+ transformedFrom . as = uniqueSubqueryAlias
132+
133+ // calculate the primary keys of the target entity, there is always exactly
134+ // one query source for UPDATE / DELETE
135+ const queryTarget = Object . values ( inferred . sources ) [ 0 ] . definition
136+ const primaryKey = { list : [ ] }
137+ for ( const k of Object . keys ( queryTarget . elements ) ) {
138+ const e = queryTarget . elements [ k ]
139+ if ( e . key === true && ! e . virtual && e . isAssociation !== true ) {
140+ subquery . SELECT . columns . push ( { ref : [ e . name ] } )
141+ primaryKey . list . push ( { ref : [ transformedFrom . as , e . name ] } )
142+ }
143+ }
144+
145+ const transformedSubquery = cqn4sql ( subquery , model )
146+
147+ // replace where condition of original query with the transformed subquery
148+ // correlate UPDATE / DELETE query with subquery by primary key matches
149+ transformedQuery [ prop ] . where = [ primaryKey , 'in' , transformedSubquery ]
150+
151+ if ( prop === 'UPDATE' ) transformedQuery . UPDATE . entity = transformedFrom
152+ else transformedQuery . DELETE . from = transformedFrom
153+
154+ return transformedQuery
155+ }
108156
109157 if ( inferred . SELECT ) {
110158 transformedQuery = transformSelectQuery ( queryProp , transformedFrom , transformedWhere , transformedQuery )
@@ -130,45 +178,7 @@ function cqn4sql(originalQuery, model) {
130178 }
131179
132180 if ( queryNeedsJoins ) {
133- if ( inferred . UPDATE || inferred . DELETE ) {
134- const prop = inferred . UPDATE ? 'UPDATE' : 'DELETE'
135- const subquery = {
136- SELECT : {
137- from : { ...transformedFrom } ,
138- columns : [ ] , // primary keys of the query target will be added later
139- where : [ ...transformedProp . where ] ,
140- } ,
141- }
142- // The alias of the original query is now the alias for the subquery
143- // so that potential references in the where clause to the alias match.
144- // Hence, replace the alias of the original query with the next
145- // available alias, so that each alias is unique.
146- const uniqueSubqueryAlias = getNextAvailableTableAlias ( transformedFrom . as )
147- transformedFrom . as = uniqueSubqueryAlias
148-
149- // calculate the primary keys of the target entity, there is always exactly
150- // one query source for UPDATE / DELETE
151- const queryTarget = Object . values ( inferred . sources ) [ 0 ] . definition
152- const primaryKey = { list : [ ] }
153- for ( const k of Object . keys ( queryTarget . elements ) ) {
154- const e = queryTarget . elements [ k ]
155- if ( e . key === true && ! e . virtual && e . isAssociation !== true ) {
156- subquery . SELECT . columns . push ( { ref : [ e . name ] } )
157- primaryKey . list . push ( { ref : [ transformedFrom . as , e . name ] } )
158- }
159- }
160-
161- const transformedSubquery = cqn4sql ( subquery , model )
162-
163- // replace where condition of original query with the transformed subquery
164- // correlate UPDATE / DELETE query with subquery by primary key matches
165- transformedQuery [ prop ] . where = [ primaryKey , 'in' , transformedSubquery ]
166-
167- if ( prop === 'UPDATE' ) transformedQuery . UPDATE . entity = transformedFrom
168- else transformedQuery . DELETE . from = transformedFrom
169- } else {
170- transformedQuery [ kind ] . from = translateAssocsToJoins ( transformedQuery [ kind ] . from )
171- }
181+ transformedQuery [ kind ] . from = translateAssocsToJoins ( transformedQuery [ kind ] . from )
172182 }
173183 }
174184
@@ -1668,8 +1678,7 @@ function cqn4sql(originalQuery, model) {
16681678 function getTransformedFrom ( from , existingWhere = [ ] ) {
16691679 const transformedWhere = [ ]
16701680 let transformedFrom = copy ( from ) // REVISIT: too expensive!
1671- if ( from . $refLinks )
1672- defineProperty ( transformedFrom , '$refLinks' , [ ...from . $refLinks ] )
1681+ if ( from . $refLinks ) defineProperty ( transformedFrom , '$refLinks' , [ ...from . $refLinks ] )
16731682 if ( from . args ) {
16741683 transformedFrom . args = [ ]
16751684 from . args . forEach ( arg => {
0 commit comments