@sap/cds-compiler 5.7.4 → 5.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -2
- package/bin/cdsse.js +13 -1
- package/doc/CHANGELOG_BETA.md +7 -0
- package/lib/api/options.js +2 -1
- package/lib/api/validate.js +9 -0
- package/lib/base/message-registry.js +55 -20
- package/lib/base/messages.js +5 -2
- package/lib/base/model.js +4 -1
- package/lib/checks/assocOutsideService.js +40 -0
- package/lib/checks/featureFlags.js +4 -1
- package/lib/checks/types.js +7 -4
- package/lib/checks/validator.js +3 -0
- package/lib/compiler/assert-consistency.js +11 -5
- package/lib/compiler/checks.js +79 -17
- package/lib/compiler/define.js +57 -3
- package/lib/compiler/extend.js +1 -2
- package/lib/compiler/generate.js +1 -1
- package/lib/compiler/populate.js +17 -6
- package/lib/compiler/propagator.js +1 -1
- package/lib/compiler/resolve.js +181 -150
- package/lib/compiler/shared.js +276 -22
- package/lib/compiler/tweak-assocs.js +15 -4
- package/lib/compiler/xpr-rewrite.js +76 -50
- package/lib/edm/annotations/edmJson.js +1 -1
- package/lib/edm/annotations/genericTranslation.js +2 -2
- package/lib/edm/csn2edm.js +2 -2
- package/lib/edm/edmPreprocessor.js +15 -9
- package/lib/edm/edmUtils.js +12 -5
- package/lib/gen/CdlGrammar.checksum +1 -0
- package/lib/gen/CdlParser.js +2234 -2233
- package/lib/gen/Dictionary.json +55 -8
- package/lib/json/from-csn.js +37 -17
- package/lib/json/to-csn.js +4 -0
- package/lib/language/genericAntlrParser.js +7 -0
- package/lib/main.d.ts +5 -0
- package/lib/model/cloneCsn.js +1 -0
- package/lib/model/csnRefs.js +1 -0
- package/lib/model/csnUtils.js +0 -5
- package/lib/modelCompare/utils/filter.js +2 -2
- package/lib/optionProcessor.js +2 -0
- package/lib/parsers/AstBuildingParser.js +47 -17
- package/lib/parsers/CdlGrammar.g4 +10 -12
- package/lib/parsers/XprTree.js +206 -0
- package/lib/render/toCdl.js +61 -89
- package/lib/render/toSql.js +59 -29
- package/lib/render/utils/standardDatabaseFunctions.js +252 -15
- package/lib/transform/addTenantFields.js +9 -3
- package/lib/transform/db/assocsToQueries/transformExists.js +3 -0
- package/lib/transform/db/assocsToQueries/utils.js +10 -3
- package/lib/transform/db/expansion.js +3 -1
- package/lib/transform/db/flattening.js +7 -3
- package/lib/transform/db/killAnnotations.js +1 -0
- package/lib/transform/db/processSqlServices.js +70 -17
- package/lib/transform/draft/db.js +8 -3
- package/lib/transform/draft/odata.js +27 -4
- package/lib/transform/effective/main.js +37 -10
- package/lib/transform/effective/misc.js +4 -9
- package/lib/transform/effective/service.js +34 -0
- package/lib/transform/effective/types.js +28 -17
- package/lib/transform/forOdata.js +36 -10
- package/lib/transform/forRelationalDB.js +30 -18
- package/lib/transform/odata/adaptAnnotationRefs.js +37 -21
- package/lib/transform/odata/createForeignKeys.js +121 -117
- package/lib/transform/odata/flattening.js +12 -9
- package/lib/transform/transformUtils.js +58 -25
- package/lib/transform/translateAssocsToJoins.js +10 -6
- package/lib/transform/universalCsn/coreComputed.js +5 -1
- package/package.json +1 -1
- package/share/messages/message-explanations.json +1 -0
- package/share/messages/rewrite-not-supported.md +5 -0
- package/share/messages/rewrite-undefined-key.md +94 -0
|
@@ -99,7 +99,7 @@ const oDataFunctions = {
|
|
|
99
99
|
const { args } = signature;
|
|
100
100
|
checkArgs.call(this, 'date', args, 1);
|
|
101
101
|
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
102
|
-
return `date(${x})
|
|
102
|
+
return `date(${x})`;
|
|
103
103
|
},
|
|
104
104
|
// this could also be a negative number
|
|
105
105
|
// also, parts of the EDM.duration are optional which complicates
|
|
@@ -199,19 +199,19 @@ const oDataFunctions = {
|
|
|
199
199
|
const { args } = signature;
|
|
200
200
|
checkArgs.call(this, 'fractionalseconds', args, 1);
|
|
201
201
|
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
202
|
-
return `
|
|
202
|
+
return `cast(date_part('second', ${x}) - floor(date_part('second', ${x})) AS DECIMAL(3,3))`;
|
|
203
203
|
},
|
|
204
204
|
time(signature) {
|
|
205
205
|
const { args } = signature;
|
|
206
206
|
checkArgs.call(this, 'time', args, 1);
|
|
207
207
|
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
208
|
-
return `to_char(${x}, 'HH24:MI:SS')`;
|
|
208
|
+
return `to_char(${x}, 'HH24:MI:SS')::TIME`;
|
|
209
209
|
},
|
|
210
210
|
date(signature) {
|
|
211
211
|
const { args } = signature;
|
|
212
212
|
checkArgs.call(this, 'date', args, 1);
|
|
213
213
|
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
214
|
-
return
|
|
214
|
+
return `${x}::DATE`;
|
|
215
215
|
},
|
|
216
216
|
},
|
|
217
217
|
// https://help.sap.com/docs/HANA_SERVICE_CF/7c78579ce9b14a669c1f3295b0d8ca16/f12b86a6284c4aeeb449e57eb5dd3ebd.html?locale=en-US
|
|
@@ -309,13 +309,13 @@ const oDataFunctions = {
|
|
|
309
309
|
const { args } = signature;
|
|
310
310
|
checkArgs.call(this, 'time', args, 1);
|
|
311
311
|
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
312
|
-
return `
|
|
312
|
+
return `to_time(${x})`;
|
|
313
313
|
},
|
|
314
314
|
date(signature) {
|
|
315
315
|
const { args } = signature;
|
|
316
316
|
checkArgs.call(this, 'date', args, 1);
|
|
317
317
|
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
318
|
-
return `
|
|
318
|
+
return `to_date(${x})`;
|
|
319
319
|
},
|
|
320
320
|
},
|
|
321
321
|
// https://www.h2database.com/html/functions.html
|
|
@@ -429,13 +429,13 @@ const oDataFunctions = {
|
|
|
429
429
|
const { args } = signature;
|
|
430
430
|
checkArgs.call(this, 'time', args, 1);
|
|
431
431
|
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
432
|
-
return `cast(
|
|
432
|
+
return `cast(${x} AS TIME)`;
|
|
433
433
|
},
|
|
434
434
|
date(signature) {
|
|
435
435
|
const { args } = signature;
|
|
436
436
|
checkArgs.call(this, 'date', args, 1);
|
|
437
437
|
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
438
|
-
return `cast(
|
|
438
|
+
return `cast(${x} AS DATE)`;
|
|
439
439
|
},
|
|
440
440
|
},
|
|
441
441
|
common: {
|
|
@@ -544,18 +544,255 @@ const oDataFunctions = {
|
|
|
544
544
|
},
|
|
545
545
|
};
|
|
546
546
|
|
|
547
|
-
// TODO: add support for the common SAP HANA Functions
|
|
548
547
|
const hanaFunctions = {
|
|
549
|
-
sqlite: {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
548
|
+
sqlite: {
|
|
549
|
+
/**
|
|
550
|
+
* SQLite relies on floating-point arithmetic for date/time calculations, which can introduce
|
|
551
|
+
* slight imprecisions due to the use of the `julianday` function. The `julianday` function
|
|
552
|
+
* computes the difference between two timestamps as a floating-point value in days, which
|
|
553
|
+
* is then scaled to nano100 units (0.1 microseconds). While this approach is efficient,
|
|
554
|
+
* the inherent precision limits of floating-point arithmetic can result in small deviations
|
|
555
|
+
* (e.g., off by a few nano100 units).
|
|
556
|
+
*
|
|
557
|
+
* @param {Object} signature - The function signature containing arguments.
|
|
558
|
+
* @returns {string} - SQL expression to calculate the nano100 difference in SQLite.
|
|
559
|
+
*/
|
|
560
|
+
nano100_between(signature) {
|
|
561
|
+
const { args } = signature;
|
|
562
|
+
checkArgs.call(this, 'nano100_between', args, 2);
|
|
563
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
564
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
565
|
+
// 1 day = 24h*60m*60s*10'000'000 = 864'000'000'000 nano100
|
|
566
|
+
return `CAST(((julianday(${y}) - julianday(${x})) * 864000000000) as INTEGER)`;
|
|
567
|
+
},
|
|
568
|
+
seconds_between(signature) {
|
|
569
|
+
const { args } = signature;
|
|
570
|
+
checkArgs.call(this, 'seconds_between', args, 2);
|
|
571
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
572
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
573
|
+
|
|
574
|
+
return `CAST(strftime('%s', ${y}) - strftime('%s', ${x}) AS INTEGER)`;
|
|
575
|
+
},
|
|
576
|
+
days_between(signature) {
|
|
577
|
+
const { args } = signature;
|
|
578
|
+
checkArgs.call(this, 'days_between', args, 2);
|
|
579
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
580
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
581
|
+
|
|
582
|
+
return `(CASE WHEN (strftime('%s', ${y}) - strftime('%s', ${x})) < 86400 AND (strftime('%s', ${y}) - strftime('%s', ${x})) > -86400 THEN 0 ELSE CAST((strftime('%s', ${y}) - strftime('%s', ${x})) / 86400 AS INTEGER) END)`;
|
|
583
|
+
},
|
|
584
|
+
/**
|
|
585
|
+
* Calculates the difference in months between two dates, `x` and `y`, with a correction for partial months.
|
|
586
|
+
*
|
|
587
|
+
* The computation consists of:
|
|
588
|
+
*
|
|
589
|
+
* 1. Year/Month Difference:
|
|
590
|
+
* - Extracts the year and month parts from both dates and computes a raw difference:
|
|
591
|
+
* (year(y) - year(x)) * 12 + (month(y) - month(x)).
|
|
592
|
+
*
|
|
593
|
+
* 2. Partial-Month Correction:
|
|
594
|
+
* - Generates a composite value of day and time components from each date using:
|
|
595
|
+
* strftime('%d%H%M%S%f0000', date)
|
|
596
|
+
* This zero-padded composite includes day, hour, minute, second, and fractional seconds.
|
|
597
|
+
* - For a forward interval (when y is after or equal to x):
|
|
598
|
+
* If the composite for y is less than that for x, then the final month is incomplete, so subtract 1.
|
|
599
|
+
* - For a backward interval (when y is before x):
|
|
600
|
+
* If the composite for y is greater than that for x, then the final month is incomplete, so add 1.
|
|
601
|
+
*
|
|
602
|
+
* 3. Leap-Year Adjustment:
|
|
603
|
+
* - The composite value inherently captures all day/time details (including the leap day, Feb 29),
|
|
604
|
+
* so the extra day in a leap year is automatically accounted for in the partial-month correction.
|
|
605
|
+
*
|
|
606
|
+
* @param {object} signature - Contains the function arguments.
|
|
607
|
+
* @returns {string} A SQL expression that calculates the adjusted month difference.
|
|
608
|
+
*/
|
|
609
|
+
months_between(signature) {
|
|
610
|
+
// Ensure exactly two arguments (startDate, endDate)
|
|
611
|
+
checkArgs.call(this, 'months_between', signature.args, 2);
|
|
612
|
+
|
|
613
|
+
// Render the arguments as SQL expressions.
|
|
614
|
+
const x = this.renderArgs({ ...signature, args: [ signature.args[0] ] });
|
|
615
|
+
const y = this.renderArgs({ ...signature, args: [ signature.args[1] ] });
|
|
616
|
+
|
|
617
|
+
// Construct the SQL expression:
|
|
618
|
+
// 1. Base month difference from the year and month components.
|
|
619
|
+
// 2. Partial-month correction using a composite integer of day and time.
|
|
620
|
+
const res = `
|
|
621
|
+
(
|
|
622
|
+
(
|
|
623
|
+
(CAST(strftime('%Y', ${y}) AS Integer) - CAST(strftime('%Y', ${x}) AS Integer)) * 12
|
|
624
|
+
)
|
|
625
|
+
+
|
|
626
|
+
(
|
|
627
|
+
CAST(strftime('%m', ${y}) AS Integer) - CAST(strftime('%m', ${x}) AS Integer)
|
|
628
|
+
)
|
|
629
|
+
+
|
|
630
|
+
(
|
|
631
|
+
CASE
|
|
632
|
+
/* For backward intervals: if the composite (day + time) of y is greater than x, add 1. */
|
|
633
|
+
WHEN CAST(strftime('%Y%m', ${y}) AS Integer) < CAST(strftime('%Y%m', ${x}) AS Integer)
|
|
634
|
+
THEN (CAST(strftime('%d%H%M%S%f0000', ${y}) AS Integer) > CAST(strftime('%d%H%M%S%f0000', ${x}) AS Integer))
|
|
635
|
+
/* For forward intervals: if the composite of y is less than x, subtract 1. */
|
|
636
|
+
ELSE (CAST(strftime('%d%H%M%S%f0000', ${y}) AS Integer) < CAST(strftime('%d%H%M%S%f0000', ${x}) AS Integer)) * -1
|
|
637
|
+
END
|
|
638
|
+
)
|
|
639
|
+
)
|
|
640
|
+
`;
|
|
641
|
+
// Remove extra whitespace and return the single-line SQL expression.
|
|
642
|
+
return res.replace(/\s+/g, ' ');
|
|
643
|
+
},
|
|
644
|
+
years_between(signature) {
|
|
645
|
+
const { args } = signature;
|
|
646
|
+
checkArgs.call(this, 'years_between', args, 2);
|
|
647
|
+
return `floor((${hanaFunctions.sqlite.months_between.call(this, signature)}) / 12)`;
|
|
648
|
+
},
|
|
649
|
+
},
|
|
650
|
+
postgres: {
|
|
651
|
+
nano100_between(signature) {
|
|
652
|
+
const { args } = signature;
|
|
653
|
+
checkArgs.call(this, 'nano100_between', args, 2);
|
|
654
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
655
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
656
|
+
// make sure to cast NUMERIC to BIGINT (corresponds to cds.Int64)
|
|
657
|
+
return `(EXTRACT(EPOCH FROM (${y}) - (${x})) * 10000000)::BIGINT`;
|
|
658
|
+
},
|
|
659
|
+
seconds_between(signature) {
|
|
660
|
+
const { args } = signature;
|
|
661
|
+
checkArgs.call(this, 'seconds_between', args, 2);
|
|
662
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
663
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
664
|
+
|
|
665
|
+
return `EXTRACT(EPOCH FROM (${y}) - (${x}))::BIGINT`;
|
|
666
|
+
},
|
|
667
|
+
days_between(signature) {
|
|
668
|
+
const { args } = signature;
|
|
669
|
+
checkArgs.call(this, 'days_between', args, 2);
|
|
670
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
671
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
672
|
+
return `EXTRACT(DAY FROM ${y}::timestamp - ${x}::timestamp)::integer`;
|
|
673
|
+
},
|
|
674
|
+
months_between(signature) {
|
|
675
|
+
const { args } = signature;
|
|
676
|
+
checkArgs.call(this, 'months_between', args, 2);
|
|
677
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
678
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
679
|
+
|
|
680
|
+
return `(EXTRACT(YEAR FROM AGE(${y}, ${x})) * 12 + EXTRACT(MONTH FROM AGE(${y}, ${x})))::INTEGER`;
|
|
681
|
+
},
|
|
682
|
+
years_between(signature) {
|
|
683
|
+
const { args } = signature;
|
|
684
|
+
checkArgs.call(this, 'years_between', args, 2);
|
|
685
|
+
return `floor((${hanaFunctions.postgres.months_between.call(this, signature)}) / 12)::INTEGER`;
|
|
686
|
+
},
|
|
687
|
+
},
|
|
688
|
+
h2: {
|
|
689
|
+
nano100_between(signature) {
|
|
690
|
+
const { args } = signature;
|
|
691
|
+
checkArgs.call(this, 'nano100_between', args, 2);
|
|
692
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
693
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
694
|
+
|
|
695
|
+
return `CAST(DATEDIFF('MICROSECOND', ${x}, ${y}) * 10 AS BIGINT)`;
|
|
696
|
+
},
|
|
697
|
+
seconds_between(signature) {
|
|
698
|
+
const { args } = signature;
|
|
699
|
+
checkArgs.call(this, 'seconds_between', args, 2);
|
|
700
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
701
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
702
|
+
|
|
703
|
+
return `CAST(DATEDIFF('SECOND', ${x}, ${y}) AS BIGINT)`;
|
|
704
|
+
},
|
|
705
|
+
days_between(signature) {
|
|
706
|
+
const { args } = signature;
|
|
707
|
+
checkArgs.call(this, 'days_between', args, 2);
|
|
708
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
709
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
710
|
+
return `CASE WHEN ABS(DATEDIFF('SECOND', ${x}, ${y})) < 86400 THEN 0 ELSE CAST(FLOOR(DATEDIFF('SECOND', ${x}, ${y}) / 86400) AS INTEGER) END`;
|
|
711
|
+
},
|
|
712
|
+
/**
|
|
713
|
+
* Uses DATEDIFF('MONTH') and then applies a partial-month correction for day-of-month boundaries in both
|
|
714
|
+
* forward and backward (negative) scenarios.
|
|
715
|
+
*/
|
|
716
|
+
months_between(signature) {
|
|
717
|
+
const { args } = signature;
|
|
718
|
+
checkArgs.call(this, 'months_between', args, 2);
|
|
719
|
+
|
|
720
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
721
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
722
|
+
|
|
723
|
+
const res = `
|
|
724
|
+
CAST(
|
|
725
|
+
DATEDIFF('MONTH', ${x}, ${y})
|
|
726
|
+
+ CASE
|
|
727
|
+
WHEN DATEDIFF('DAY', ${x}, ${y}) >= 0
|
|
728
|
+
AND EXTRACT(DAY FROM ${y}) < EXTRACT(DAY FROM ${x})
|
|
729
|
+
THEN -1
|
|
730
|
+
|
|
731
|
+
WHEN DATEDIFF('DAY', ${x}, ${y}) < 0
|
|
732
|
+
AND EXTRACT(DAY FROM ${y}) > EXTRACT(DAY FROM ${x})
|
|
733
|
+
THEN 1
|
|
734
|
+
|
|
735
|
+
ELSE 0
|
|
736
|
+
END
|
|
737
|
+
AS INTEGER
|
|
738
|
+
)
|
|
739
|
+
`;
|
|
740
|
+
return res.replace(/\s+/g, ' ');
|
|
741
|
+
},
|
|
742
|
+
years_between(signature) {
|
|
743
|
+
const { args } = signature;
|
|
744
|
+
checkArgs.call(this, 'years_between', args, 2);
|
|
745
|
+
return `floor((${hanaFunctions.h2.months_between.call(this, signature)}) / 12)`;
|
|
746
|
+
},
|
|
747
|
+
},
|
|
553
748
|
common: {},
|
|
749
|
+
// identity functions + argument check
|
|
750
|
+
hana: {
|
|
751
|
+
nano100_between(signature) {
|
|
752
|
+
const { args } = signature;
|
|
753
|
+
checkArgs.call(this, 'nano100_between', args, 2);
|
|
754
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
755
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
756
|
+
|
|
757
|
+
return `nano100_between(${x}, ${y})`;
|
|
758
|
+
},
|
|
759
|
+
seconds_between(signature) {
|
|
760
|
+
const { args } = signature;
|
|
761
|
+
checkArgs.call(this, 'seconds_between', args, 2);
|
|
762
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
763
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
764
|
+
|
|
765
|
+
return `seconds_between(${x}, ${y})`;
|
|
766
|
+
},
|
|
767
|
+
days_between(signature) {
|
|
768
|
+
const { args } = signature;
|
|
769
|
+
checkArgs.call(this, 'days_between', args, 2);
|
|
770
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
771
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
772
|
+
return `days_between(${x}, ${y})`;
|
|
773
|
+
},
|
|
774
|
+
months_between(signature) {
|
|
775
|
+
const { args } = signature;
|
|
776
|
+
checkArgs.call(this, 'months_between', args, 2);
|
|
777
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
778
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
779
|
+
|
|
780
|
+
return `months_between(${x}, ${y})`;
|
|
781
|
+
},
|
|
782
|
+
years_between(signature) {
|
|
783
|
+
const { args } = signature;
|
|
784
|
+
checkArgs.call(this, 'years_between', args, 2);
|
|
785
|
+
return `years_between(${this.renderArgs(signature)})`;
|
|
786
|
+
},
|
|
787
|
+
},
|
|
554
788
|
};
|
|
555
789
|
|
|
556
790
|
function checkArgs( funcName, receivedArgs, expectedLength, alternativeLength = null ) {
|
|
557
791
|
const expectedMismatch = receivedArgs.length < expectedLength;
|
|
558
|
-
const alternativeMismatch
|
|
792
|
+
const alternativeMismatch
|
|
793
|
+
= expectedMismatch &&
|
|
794
|
+
(!alternativeLength ||
|
|
795
|
+
(alternativeLength && receivedArgs.length < alternativeLength));
|
|
559
796
|
if (expectedMismatch && alternativeMismatch) {
|
|
560
797
|
this.error('def-missing-argument', [ ...this.path, 'args' ], {
|
|
561
798
|
'#': alternativeLength ? 'alternative' : 'std',
|
|
@@ -565,7 +802,7 @@ function checkArgs( funcName, receivedArgs, expectedLength, alternativeLength =
|
|
|
565
802
|
name: funcName,
|
|
566
803
|
});
|
|
567
804
|
}
|
|
568
|
-
}
|
|
805
|
+
}
|
|
569
806
|
|
|
570
807
|
module.exports.standardDatabaseFunctions = {
|
|
571
808
|
sqlite: { ...oDataFunctions.sqlite, ...hanaFunctions.sqlite },
|
|
@@ -18,7 +18,12 @@
|
|
|
18
18
|
'use strict';
|
|
19
19
|
|
|
20
20
|
const { createMessageFunctions } = require( '../base/messages' );
|
|
21
|
-
const {
|
|
21
|
+
const {
|
|
22
|
+
csnRefs,
|
|
23
|
+
traverseQuery,
|
|
24
|
+
implicitAs,
|
|
25
|
+
pathId,
|
|
26
|
+
} = require( '../model/csnRefs' );
|
|
22
27
|
|
|
23
28
|
const annoTenantIndep = '@cds.tenant.independent';
|
|
24
29
|
|
|
@@ -85,7 +90,7 @@ function addTenantFields( csn, options, messageFunctions ) {
|
|
|
85
90
|
independent = art.kind; // might be used for message variant
|
|
86
91
|
checkIncludes( art ); // recompile should work
|
|
87
92
|
}
|
|
88
|
-
else if (projection) { // events - TODO: mention in doc
|
|
93
|
+
else if (projection) { // events, types - TODO: mention in doc
|
|
89
94
|
independent = art.kind; // might be used for message variant
|
|
90
95
|
// recompile should work: no new `tenant` source element for `select *`
|
|
91
96
|
traverseQuery( projection, null, null, handleQuery );
|
|
@@ -187,12 +192,13 @@ function addTenantFields( csn, options, messageFunctions ) {
|
|
|
187
192
|
|
|
188
193
|
function handleQuerySource( query ) {
|
|
189
194
|
if (independent) {
|
|
190
|
-
const art = query.ref[0]; // yes, the base
|
|
195
|
+
const art = pathId(query.ref[0]); // yes, the base
|
|
191
196
|
if (csn.definitions[art][annoTenantIndep])
|
|
192
197
|
return true;
|
|
193
198
|
error( 'tenant-invalid-query-source', msgLocations( csnPath ), { art, '#': independent }, {
|
|
194
199
|
std: 'Can\'t use a tenant-dependent query source $(ART) in a tenant-independent entity',
|
|
195
200
|
event: 'Can\'t use a tenant-dependent query source $(ART) in an event',
|
|
201
|
+
type: 'Can\'t use a tenant-dependent query source $(ART) in a type definition',
|
|
196
202
|
} );
|
|
197
203
|
return true;
|
|
198
204
|
}
|
|
@@ -346,6 +346,9 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
|
|
|
346
346
|
},
|
|
347
347
|
};
|
|
348
348
|
|
|
349
|
+
if (assocRef.args) // copy named arguments
|
|
350
|
+
subselect.SELECT.from.ref = [ { id: target, args: assocRef.args } ];
|
|
351
|
+
|
|
349
352
|
setProp(subselect.SELECT.from, '_art', csn.definitions[target]);
|
|
350
353
|
setProp(subselect.SELECT.from, '_links', [ { idx: 0, art: csn.definitions[target] } ]);
|
|
351
354
|
|
|
@@ -106,14 +106,21 @@ function getHelpers( csn, inspectRef, error ) {
|
|
|
106
106
|
function getFirstAssoc( xprPart, path ) {
|
|
107
107
|
const { links, art } = getLinksAndArt({}, path);
|
|
108
108
|
for (let i = 0; i < xprPart.ref.length - 1; i++) {
|
|
109
|
-
if (links[i].art
|
|
109
|
+
if (links[i].art?.target) {
|
|
110
110
|
return {
|
|
111
|
-
head: (i === 0 ? [] : xprPart.ref.slice(0, i)),
|
|
111
|
+
head: (i === 0 ? [] : xprPart.ref.slice(0, i)),
|
|
112
|
+
root: links[i].art,
|
|
113
|
+
ref: xprPart.ref[i],
|
|
114
|
+
tail: xprPart.ref.slice(i + 1),
|
|
112
115
|
};
|
|
113
116
|
}
|
|
114
117
|
}
|
|
118
|
+
const { ref } = xprPart;
|
|
115
119
|
return {
|
|
116
|
-
head: (
|
|
120
|
+
head: (ref.length === 1 ? [] : ref.slice(0, ref.length - 1)),
|
|
121
|
+
root: art,
|
|
122
|
+
ref: ref.at(-1),
|
|
123
|
+
tail: [],
|
|
117
124
|
};
|
|
118
125
|
}
|
|
119
126
|
|
|
@@ -652,8 +652,10 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
652
652
|
if (typeof root.$env === 'string' && (isComplexOrNestedQuery || options.transformation !== 'odata' || root.$env === pathId(obj.ref[0])))
|
|
653
653
|
obj.ref = [ root.$env, ...obj.ref ];
|
|
654
654
|
|
|
655
|
-
if (iterateOptions.keepKeysOrigin)
|
|
655
|
+
if (iterateOptions.keepKeysOrigin) {
|
|
656
656
|
setProp(obj, '$originalKeyRef', { ref: root.ref, as: root.as });
|
|
657
|
+
setProp(obj, '$path', root.$path);
|
|
658
|
+
}
|
|
657
659
|
|
|
658
660
|
return obj;
|
|
659
661
|
});
|
|
@@ -15,7 +15,7 @@ const { forEach } = require('../../utils/objectUtils');
|
|
|
15
15
|
const { transformExpression } = require('./applyTransformations');
|
|
16
16
|
const { cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
17
17
|
const { EdmTypeFacetNames } = require('../../edm/EdmPrimitiveTypeDefinitions');
|
|
18
|
-
const adaptAnnotationsRefs = require('../odata/adaptAnnotationRefs');
|
|
18
|
+
const { adaptAnnotationsRefs } = require('../odata/adaptAnnotationRefs');
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Strip off leading $self from refs where applicable.
|
|
@@ -220,7 +220,8 @@ function getStructStepsFlattener( csn, options, messageFunctions, resolved, path
|
|
|
220
220
|
// full path into target, uncomment this line and
|
|
221
221
|
// comment/remove setProp in expansion.js
|
|
222
222
|
// setProp(parent, '$structRef', parent.ref);
|
|
223
|
-
|
|
223
|
+
const flattenParameters = false; // structured parameters remain structured
|
|
224
|
+
[ parent.ref, refChanged ] = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes, suspend, suspendPos, parent.$bparam, flattenParameters);
|
|
224
225
|
resolved.set(parent, { links, art, scope });
|
|
225
226
|
// Explicitly set implicit alias for things that are now flattened - but only in columns
|
|
226
227
|
// TODO: Can this be done elegantly during expand phase already?
|
|
@@ -649,8 +650,11 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
|
|
|
649
650
|
delete element.default;
|
|
650
651
|
}
|
|
651
652
|
|
|
652
|
-
if (options.transformation === 'effective')
|
|
653
|
+
if (options.transformation === 'effective') {
|
|
653
654
|
adaptAnnotationsRefs(fks, csnUtils, messageFunctions, eltPath);
|
|
655
|
+
const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && findAnnotationExpression(element, pn));
|
|
656
|
+
fks.forEach(fk => copyAnnotations(element, fk[1], false, {}, validAnnoNames));
|
|
657
|
+
}
|
|
654
658
|
orderedElements.push(...fks);
|
|
655
659
|
});
|
|
656
660
|
|
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { setProp } = require('../../base/model');
|
|
3
|
+
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
4
4
|
|
|
5
5
|
const sqlServiceAnnotation = '@protocol';
|
|
6
|
-
|
|
7
|
-
// const sqlServiceEntities = Symbol.for('SQL Service enabled entities');
|
|
6
|
+
|
|
8
7
|
/**
|
|
9
8
|
* Find all entities in SQL services and mark them with an annotation and
|
|
10
9
|
* remember them in a symbol property for easier processing in toSql-rendering.
|
|
11
10
|
*
|
|
12
11
|
* @param {CSN.Model} csn
|
|
12
|
+
* @param {CSN.Options} options
|
|
13
13
|
* @returns {Function}
|
|
14
14
|
*/
|
|
15
|
-
function processSqlServices(csn) {
|
|
15
|
+
function processSqlServices(csn, options) {
|
|
16
16
|
setProp(csn, '$sqlServiceEntities', Object.create(null));
|
|
17
|
+
setProp(csn, '$dummyServiceEntities', Object.create(null));
|
|
17
18
|
return function findAndMarkSqlServiceArtifacts(artifact, artifactName) {
|
|
18
|
-
const sqlServiceName = isEntityInSqlService(artifact, artifactName, csn);
|
|
19
|
+
const { sqlServiceName, dummyServiceName } = isEntityInSqlService(artifact, artifactName, csn, options);
|
|
19
20
|
if (sqlServiceName?.length > 0)
|
|
20
21
|
setProp(artifact, '$sqlService', sqlServiceName);
|
|
22
|
+
if (dummyServiceName?.length > 0)
|
|
23
|
+
setProp(artifact, '$dummyService', dummyServiceName);
|
|
21
24
|
};
|
|
22
25
|
}
|
|
23
26
|
|
|
@@ -29,35 +32,85 @@ function processSqlServices(csn) {
|
|
|
29
32
|
function isSqlService(artifact) {
|
|
30
33
|
return artifact.kind === 'service' && artifact[sqlServiceAnnotation] === 'sql';
|
|
31
34
|
}
|
|
35
|
+
|
|
32
36
|
/**
|
|
37
|
+
* Checks if the given artifact is an external ABAP SQL service.
|
|
33
38
|
*
|
|
34
|
-
* @param {
|
|
35
|
-
* @param {
|
|
36
|
-
* @
|
|
37
|
-
|
|
39
|
+
* @param {object} artifact - The artifact to check.
|
|
40
|
+
* @param {CSN.Options} options
|
|
41
|
+
* @returns {boolean} - Returns true if the artifact is an external ABAP SQL service, otherwise false.
|
|
42
|
+
*/
|
|
43
|
+
function isDummyService(artifact, options) {
|
|
44
|
+
return isBetaEnabled(options, 'sqlServiceDummies') && artifact.kind === 'service' && artifact['@cds.external'] && artifact[sqlServiceAnnotation] === 'dummy';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Determines if an artifact is part of a SQL service or an external ABAP SQL service.
|
|
49
|
+
*
|
|
50
|
+
* @param {object} artifact - The artifact to check.
|
|
51
|
+
* @param {string} artifactName - The name of the artifact.
|
|
52
|
+
* @param {object} csn - The CSN (Core Schema Notation) object containing definitions.
|
|
53
|
+
* @param {CSN.Options} options
|
|
54
|
+
* @returns {object} An object containing the names of the SQL service and external ABAP SQL service, if found.
|
|
38
55
|
*/
|
|
39
|
-
function isEntityInSqlService(artifact, artifactName, csn) {
|
|
40
|
-
|
|
41
|
-
|
|
56
|
+
function isEntityInSqlService(artifact, artifactName, csn, options) {
|
|
57
|
+
const result = { sqlServiceName: undefined, dummyServiceName: undefined };
|
|
58
|
+
if (artifact.kind !== 'entity' || !artifactName.includes('.') || artifact['@cds.persistence.skip'] === true)
|
|
59
|
+
return result;
|
|
42
60
|
|
|
43
61
|
const nameParts = artifactName.split('.');
|
|
44
|
-
for (let i = nameParts.length; i >= 0; i--) {
|
|
62
|
+
for (let i = nameParts.length - 1; i >= 0; i--) {
|
|
45
63
|
const possibleServiceName = nameParts.slice(0, i).join('.');
|
|
46
64
|
if (!csn.definitions[possibleServiceName])
|
|
47
65
|
continue;
|
|
48
66
|
|
|
49
67
|
const definition = csn.definitions[possibleServiceName];
|
|
50
68
|
if (isSqlService(definition))
|
|
51
|
-
|
|
69
|
+
result.sqlServiceName = possibleServiceName;
|
|
70
|
+
|
|
71
|
+
if (isDummyService(definition, options))
|
|
72
|
+
result.dummyServiceName = possibleServiceName;
|
|
52
73
|
|
|
53
74
|
// We don't allow nested services/contexts - if we find one, we don't need to keep searching
|
|
54
75
|
if (definition.kind === 'service' || definition.kind === 'context')
|
|
55
|
-
return
|
|
76
|
+
return result;
|
|
56
77
|
}
|
|
57
78
|
|
|
58
|
-
return
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Creates a dummy ABAP SQL service for the given artifact if it is marked as an external ABAP SQL service.
|
|
84
|
+
* The dummy service is a copy of the original artifact with certain properties removed.
|
|
85
|
+
* The dummy service is then added to the CSN (Core Schema Notation) definitions.
|
|
86
|
+
*
|
|
87
|
+
* @param {object} artifact - The artifact to create a dummy service for.
|
|
88
|
+
* @param {string} artifactName - The name of the artifact.
|
|
89
|
+
* @param {object} csn - The Core Schema Notation (CSN) object where the dummy service will be added.
|
|
90
|
+
* @param {object} messageFunctions
|
|
91
|
+
* @param {Function} messageFunctions.error
|
|
92
|
+
*/
|
|
93
|
+
function createServiceDummy(artifact, artifactName, csn, { error }) {
|
|
94
|
+
if (!artifact.$dummyService)
|
|
95
|
+
return;
|
|
96
|
+
|
|
97
|
+
artifact['@cds.persistence.exists'] = true;
|
|
98
|
+
artifact.$ignore = true;
|
|
99
|
+
|
|
100
|
+
const dummy = { ...artifact };
|
|
101
|
+
delete dummy['@cds.persistence.exists'];
|
|
102
|
+
delete dummy.$ignore;
|
|
103
|
+
|
|
104
|
+
if (csn.definitions[`dummy.${artifactName}`])
|
|
105
|
+
error(null, [ 'definitions', artifactName ], { name: `dummy.${artifactName}` }, 'Generated artifact name $(NAME) conflicts with existing entity');
|
|
106
|
+
else
|
|
107
|
+
csn.definitions[`dummy.${artifactName}`] = dummy;
|
|
59
108
|
}
|
|
60
109
|
|
|
61
110
|
module.exports = {
|
|
62
|
-
processSqlServices,
|
|
111
|
+
processSqlServices,
|
|
112
|
+
isSqlService,
|
|
113
|
+
isDummyService,
|
|
114
|
+
sqlServiceAnnotation,
|
|
115
|
+
createServiceDummy,
|
|
63
116
|
};
|
|
@@ -4,7 +4,7 @@ const {
|
|
|
4
4
|
hasAnnotationValue, getServiceNames, forEachDefinition,
|
|
5
5
|
getResultingName, forEachMemberRecursively, applyAnnotationsFromExtensions,
|
|
6
6
|
} = require('../../model/csnUtils');
|
|
7
|
-
const { setProp, isDeprecatedEnabled } = require('../../base/model');
|
|
7
|
+
const { setProp, isDeprecatedEnabled, isBetaEnabled } = require('../../base/model');
|
|
8
8
|
const { getTransformers } = require('../transformUtils');
|
|
9
9
|
const { ModelError } = require('../../base/error');
|
|
10
10
|
const { forEach } = require('../../utils/objectUtils');
|
|
@@ -25,8 +25,8 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
25
25
|
const allServices = getServiceNames(csn);
|
|
26
26
|
const draftRoots = new WeakMap();
|
|
27
27
|
const {
|
|
28
|
-
createAndAddDraftAdminDataProjection, createScalarElement,
|
|
29
|
-
addElement, copyAndAddElement, createAssociationPathComparison, csnUtils,
|
|
28
|
+
createAndAddDraftAdminDataProjection, createScalarElement,
|
|
29
|
+
createAssociationElement, addElement, copyAndAddElement, createAssociationPathComparison, csnUtils,
|
|
30
30
|
} = getTransformers(csn, options, messageFunctions, pathDelimiter);
|
|
31
31
|
const { getCsnDef, isComposition } = csnUtils;
|
|
32
32
|
const { error, warning } = messageFunctions;
|
|
@@ -231,6 +231,11 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
231
231
|
};
|
|
232
232
|
draftAdministrativeData.DraftAdministrativeData.notNull = true;
|
|
233
233
|
addElement(draftAdministrativeData, draftsArtifact, artifactName);
|
|
234
|
+
|
|
235
|
+
if (isBetaEnabled(options, 'draftMessages')) {
|
|
236
|
+
const draftMessages = { DraftMessages: { '@Core.Computed': true, virtual: true, items: { type: 'DRAFT.DraftAdministrativeData_DraftMessage' } } };
|
|
237
|
+
addElement(draftMessages, draftsArtifact, artifactName);
|
|
238
|
+
}
|
|
234
239
|
// Note that we may need to do the HANA transformation steps for managed associations
|
|
235
240
|
// (foreign key field generation, generatedFieldName, creating ON-condition) by hand,
|
|
236
241
|
// because the corresponding transformation steps have already been done on all artifacts
|