From 24234aab174f16b73ab963f5b440644faadf06b5 Mon Sep 17 00:00:00 2001 From: Ralf Handl Date: Wed, 18 Oct 2023 17:15:44 +0200 Subject: [PATCH 1/2] Data Aggregation CS03 Release Candidate (#71) * ODATA-1442 - Update Aggregation ABNF with changes from previous issues (#35) * ODATA-1384: Node value property path in hierarchical transformations (#72) * ODATA-1468: Paths in aggregate in analogy with groupby (#75) * ODATA-1391 - Enable usage of $this and $root in filter transformation (#34) * ODATA-1533: Generalize 4th parameter of ancestors/descendants (#80) * countdistinct and custom aggregation methods can aggregate non-primitive values (#81) * `from` multiple dimensions (#82) * ODATA-1532: OData-Aggr WD05 sections 3.10.2.2 and 3.22 (#79) * Walkthrough OData-Aggr 3.2 (#85) * Incomplete searchExpr in $apply=search (#84) * Allow aggregation with paths ending in a type-cast (#91) * $count after single-valued primitive segment (#92) * Abolish transformnested (#95) * Allow lambda variables in aggregate function (#93) * "@odata.context": "$metadata#EntitySet(@Core.AnyStructure)" (#94) * Optional parameters for rolluprecursive (#86) * Allow `commonExpr` in aggregate expressions of type 2 (#98) * Remove optional parameter from `ancestors` and `descendants` (#100) --- .github/workflows/nodejs.yml | 2 +- abnf/odata-aggregation-abnf.txt | 256 +++-- abnf/odata-aggregation-testcases.yaml | 940 +++++++++++++++--- obsolete/odata-aggregation-testcases.xml | 1140 ++++++++++++---------- package.json | 2 +- 5 files changed, 1643 insertions(+), 697 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 3f293f3..5763a60 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: - node-version: [18.x,20.x] + node-version: [20.x] steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it diff --git a/abnf/odata-aggregation-abnf.txt b/abnf/odata-aggregation-abnf.txt index b81424e..581ffb7 100644 --- a/abnf/odata-aggregation-abnf.txt +++ b/abnf/odata-aggregation-abnf.txt @@ -1,9 +1,7 @@ ;------------------------------------------------------------------------------ ; OData Aggregation ABNF Construction Rules Version 4.0 ;------------------------------------------------------------------------------ -; 04 November 2015 -; -; Latest version: https://github.com/oasis-tcs/odata-abnf/blob/main/abnf/odata-aggregation-abnf.txt +; 19 September 2023 ;------------------------------------------------------------------------------ ; ; Technical Committee: @@ -11,32 +9,33 @@ ; https://www.oasis-open.org/committees/odata ; ; Chairs: -; - Barbara Hartel (barbara.hartel@sap.com), SAP AG -; - Ram Jeyaraman (Ram.Jeyaraman@microsoft.com), Microsoft +; - Ralf Handl (ralf.handl@sap.com), SAP SE +; - Michael Pizzo (mikep@microsoft.com), Microsoft ; ; Editors: -; - Ralf Handl (ralf.handl@sap.com), SAP AG +; - Ralf Handl (ralf.handl@sap.com), SAP SE ; - Hubert Heijkers (hubert.heijkers@nl.ibm.com), IBM -; - Gerald Krause (gerald.krause@sap.com), SAP AG +; - Gerald Krause (gerald.krause@sap.com), SAP SE ; - Michael Pizzo (mikep@microsoft.com), Microsoft -; - Martin Zurmuehl (martin.zurmuehl@sap.com), SAP AG +; - Martin Zurmuehl (martin.zurmuehl@sap.com), SAP SE +; - Heiko Theissen (heiko.theissen@sap.com), SAP SE ; ; Additional artifacts: ; This grammar is one component of a Work Product which consists of: ; - OData Extension for Data Aggregation Version 4.0 -; - OData Aggregation ABNF Construction Rules Version 4.0 -; - OData Aggregation ABNF Test Cases ; - OData Aggregation Vocabulary +; - OData Aggregation ABNF Construction Rules Version 4.0 (this document) +; - OData Aggregation ABNF Test Cases Version 4.0 ; ; Related work: ; This specification is related to: -; - OData Version 4.0 Part 1: Protocol -; - OData Version 4.0 Part 2: URL Conventions -; - OData Version 4.0 Part 3: CSDL -; - OData ABNF Construction Rules Version 4.0 -; - OData Core Vocabulary -; - OData Measures Vocabulary -; - OData JSON Format Version 4.0 +; - OData Version 4.01 Part 1: Protocol +; - OData Version 4.01 Part 2: URL Conventions +; - OData ABNF Construction Rules Version 4.01 +; - OData ABNF Test Cases Version 4.01 +; - OData Common Schema Definition Language (CSDL) JSON Representation Version 4.01 +; - OData Common Schema Definition Language (CSDL) XML Representation Version 4.01 +; - OData JSON Format Version 4.01 ; This specification replaces or supersedes: ; - None ; @@ -44,14 +43,14 @@ ; - None ; ; Abstract: -; This specification adds basic grouping and aggregation functionality (e.g. +; This specification adds basic grouping and aggregation functionality (such as ; sum, min, and max) to the Open Data Protocol (OData) without changing any ; of the base principles of OData. ; ; Overview: ; This grammar uses the ABNF defined in RFC5234 and RFC7405. ; -; It extends the OData ABNF Construction Rules Version 4.0 +; It extends the OData ABNF Construction Rules Version 4.01 ; ; Contents: ; 1. New alternatives for OData ABNF Construction Rules @@ -70,8 +69,11 @@ expandOption =/ apply boolMethodCallExpr =/ isdefinedExpr -primitiveProperty =/ expressionAlias / customAggregate +primitiveProperty =/ customAggregate + +firstMemberExpr =/ currCollectionExpr +collectionPathExpr =/ %s"/aggregate" OPEN BWS aggregateFunctionExpr BWS CLOSE ;------------------------------------------------------------------------------ ; 2. System Query Option $apply @@ -80,96 +82,180 @@ primitiveProperty =/ expressionAlias / customAggregate apply = ( "$apply" / "apply" ) EQ applyExpr applyExpr = applyTrafo *( "/" applyTrafo ) applyTrafo = aggregateTrafo - / bottomcountTrafo - / bottompercentTrafo - / bottomsumTrafo / computeTrafo / concatTrafo - / expandTrafo - / filterTrafo / groupbyTrafo - / identityTrafo - / searchTrafo - / topcountTrafo - / toppercentTrafo - / topsumTrafo - / customFunction - -aggregateTrafo = %s"aggregate" OPEN BWS aggregateItem *( BWS COMMA BWS aggregateItem ) BWS CLOSE -aggregateItem = %s"$count" asAlias - / aggregateExpr -aggregateExpr = commonExpr aggregateWith [ aggregateFrom ] asAlias - / pathPrefix primitiveProperty aggregateWith [ aggregateFrom ] asAlias - / pathPrefix customAggregate [ customFrom asAlias ] - / pathPrefix pathSegment OPEN aggregateExpr CLOSE -aggregateWith = RWS %s"with" RWS aggregateMethod -aggregateFrom = RWS %s"from" RWS groupingProperty aggregateWith [ aggregateFrom ] -customFrom = RWS %s"from" RWS groupingProperty [ aggregateWith ] [ customFrom ] -aggregateMethod = %s"sum" - / %s"min" - / %s"max" - / %s"average" - / %s"countdistinct" - / namespace "." odataIdentifier + / joinTrafo + / nestTrafo + / outerjoinTrafo + / preservingTrafo +preservingTrafo = bottomcountTrafo + / bottompercentTrafo + / bottomsumTrafo + / filterTrafo + / identityTrafo + / orderbyTrafo + / searchTrafo + / skipTrafo + / topTrafo + / topcountTrafo + / toppercentTrafo + / topsumTrafo + / ancestorsTrafo + / descendantsTrafo + / traverseTrafo + / customFunction ; custom functions could be preserving, hence are allowed in preservingTrafos +preservingTrafos = preservingTrafo *( "/" preservingTrafo ) + +aggregateTrafo = %s"aggregate" OPEN BWS aggregateExpr *( BWS COMMA BWS aggregateExpr ) BWS CLOSE +aggregateExpr = ( aggrPathPrefix / aggrCastPath ) nonprimAggWith [ aggregateFrom ] asAlias + / aggregatableExpW [ aggregateFrom ] asAlias + / aggregateCount [ aggregateFrom ] asAlias + / aggregateCustom [ [ customFrom ] asAlias ] +aggregatableExpr = commonExpr ; resulting in an aggregatable value +aggregatableExpW = aggregatableExpr aggregateWith + / [ aggrCastPath "/" ] aggrPrimPath aggregateWith +aggrPathPrefix = [ aggrCastPath "/" ] aggrPropPath +aggregateWith = RWS %s"with" RWS aggregateMethod +nonprimAggWith = RWS %s"with" RWS nonprimAggMethod +aggregateFrom = RWS %s"from" RWS groupingProperties aggregateWith [ aggregateFrom ] +customFrom = RWS %s"from" RWS groupingProperties [ aggregateWith ] [ customFrom ] +aggregateMethod = %s"sum" + / %s"min" + / %s"max" + / %s"average" + / nonprimAggMethod +nonprimAggMethod = %s"countdistinct" + / namespace "." odataIdentifier ; custom aggregation methods may work on non-primitive values +aggregateCount = %s"$count" + / [ aggrCastPath "/" ] aggrPrimPath count + / ( aggrPathPrefix / aggrCastPath ) count + +aggregateCustom = [ ( aggrPathPrefix / aggrCastPath ) "/" ] customAggregate asAlias = RWS %s"as" RWS expressionAlias expressionAlias = odataIdentifier customAggregate = odataIdentifier -groupingProperty = pathPrefix - ( entityNavigationProperty [ "/" qualifiedEntityTypeName ] - / primitiveProperty - / complexProperty - ) -pathPrefix = [ ( qualifiedEntityTypeName / qualifiedComplexTypeName ) "/" ] *( pathSegment "/" ) -pathSegment = ( complexProperty / complexColProperty ) [ "/" qualifiedComplexTypeName ] - / navigationProperty [ "/" qualifiedEntityTypeName ] - -computeTrafo = %s"compute" OPEN BWS computeExpr *( BWS COMMA BWS computeExpr ) BWS CLOSE -computeExpr = commonExpr asAlias +; Three flavors of data aggregation paths are defined now: +; - one for use in aggregate, whose segments can be single- or collection-valued (rules with prefix aggr) +; - one for use in groupby, whose segments must be single-valued (rules with prefix sngl) +; - one for use in addnested, without entity-valued segments (rule with prefix nest) +; Term casts are not allowed in data aggregation paths. +aggrPropStep = ( complexProperty / complexColProperty / entityNavigationProperty / entityColNavigationProperty ) + [ "/" aggrCastPath ] +aggrPropPath = aggrPropStep [ "/" aggrPropPath ] +aggrPrimPath = aggrPropStep "/" aggrPrimPath + / primitiveProperty / primitiveColProperty / streamProperty +aggrCastPath = optionallyQualifiedComplexTypeName / optionallyQualifiedEntityTypeName + +nestPropPath = ( complexProperty / complexColProperty ) [ [ "/" optionallyQualifiedComplexTypeName ] "/" nestPropPath ] + +snglPropPath = ( complexProperty / entityNavigationProperty ) [ [ "/" aggrCastPath ] "/" snglPropPath ] +snglPrimPath = ( complexProperty / entityNavigationProperty ) [ "/" aggrCastPath ] "/" snglPrimPath + / primitiveProperty / streamProperty + +groupingProperty = [ aggrCastPath "/" ] ( snglPrimPath / snglPropPath ) +groupingProperties = groupingProperty *( BWS COMMA BWS groupingProperty ) + +; Expressions evaluable on a collection +collectionExpr = commonExpr ; but where every firstMemberExpr must be a currCollectionExpr +currCollectionExpr = %s"$these" collectionPathExpr + +computeTrafo = %s"compute" OPEN BWS computeExpr *( BWS COMMA BWS computeExpr ) BWS CLOSE +computeExpr = commonExpr asAlias -bottomcountTrafo = %s"bottomcount" OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE -bottompercentTrafo = %s"bottompercent" OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE -bottomsumTrafo = %s"bottomsum" OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE +bottomcountTrafo = %s"bottomcount" OPEN BWS collectionExpr BWS COMMA BWS commonExpr BWS CLOSE +bottompercentTrafo = %s"bottompercent" OPEN BWS collectionExpr BWS COMMA BWS commonExpr BWS CLOSE +bottomsumTrafo = %s"bottomsum" OPEN BWS collectionExpr BWS COMMA BWS commonExpr BWS CLOSE + +concatTrafo = %s"concat" OPEN BWS applyExpr 1*( BWS COMMA BWS applyExpr ) BWS CLOSE + +nestTrafo = %s"nest" OPEN BWS nestApplyExpr BWS CLOSE + / %s"addnested" OPEN BWS nestPath BWS COMMA BWS nestApplyExpr BWS CLOSE +nestPath = [ aggrCastPath "/" ] + ( [ nestPropPath "/" ] navigationProperty [ "/" optionallyQualifiedEntityTypeName ] + / nestPropPath + ) +nestApplyExpr = applyExpr asAlias *( BWS COMMA BWS applyExpr asAlias ) + +joinTrafo = %s"join" OPEN BWS joinProperty asAlias [ BWS COMMA BWS applyExpr ] BWS CLOSE +outerjoinTrafo = %s"outerjoin" OPEN BWS joinProperty asAlias [ BWS COMMA BWS applyExpr ] BWS CLOSE +joinProperty = ( complexColProperty + / complexAnnotationInQuery ; must be collection-valued + / entityColNavigationProperty [ "/" optionallyQualifiedEntityTypeName ] + / entityAnnotationInQuery ; must be collection-valued + ) + +ancestorsTrafo = %s"ancestors" OPEN BWS + recHierReference BWS + COMMA BWS preservingTrafos BWS + [ COMMA BWS 1*DIGIT BWS ] + [ COMMA BWS %s"keep start" BWS ] + CLOSE + +descendantsTrafo = %s"descendants" OPEN BWS + recHierReference BWS + COMMA BWS preservingTrafos BWS + [ COMMA BWS 1*DIGIT BWS ] + [ COMMA BWS %s"keep start" BWS ] + CLOSE + +traverseTrafo = %s"traverse" OPEN BWS + recHierReference BWS + COMMA BWS ( %s"preorder" / %s"postorder" ) BWS + [ COMMA BWS preservingTrafos BWS ] + [ COMMA BWS orderbyItem *( BWS COMMA BWS orderbyItem ) BWS ] + CLOSE + +recHierReference = rootExpr ; must have type Collection(Edm.EntityType) + BWS COMMA BWS recHierQualifier + BWS COMMA BWS recHierPropertyPath +recHierQualifier = odataIdentifier +recHierPropertyPath = [ aggrCastPath "/" ] aggrPrimPath + +filterTrafo = %s"filter" OPEN BWS boolCommonExpr BWS CLOSE + +searchTrafo = %s"search" OPEN BWS ( searchExpr / searchExpr-incomplete ) BWS CLOSE -concatTrafo = %s"concat" OPEN BWS applyExpr 1*( BWS COMMA BWS applyExpr ) BWS CLOSE +groupbyTrafo = %s"groupby" OPEN BWS groupbyList [ BWS COMMA BWS applyExpr ] BWS CLOSE +groupbyList = OPEN BWS groupbyElement *( BWS COMMA BWS groupbyElement ) BWS CLOSE +groupbyElement = groupingProperty / rollupLevels / rollupRecursive -expandTrafo = %s"expand" OPEN BWS expandNavPath BWS COMMA BWS - ( expandTrafo *( BWS COMMA BWS expandTrafo ) - / filterTrafo *( BWS COMMA BWS expandTrafo ) - ) BWS CLOSE -expandNavPath = [ ( qualifiedEntityTypeName / qualifiedComplexTypeName ) "/" ] - *( ( complexProperty / complexColProperty ) "/" [ qualifiedComplexTypeName "/" ] ) - navigationProperty [ "/" qualifiedEntityTypeName ] +rollupLevels = %s"rollup" OPEN BWS ( rollupUnnamedHier / rollupNamedHier ) BWS CLOSE +rollupRecursive = %s"rolluprecursive" OPEN BWS + recHierReference BWS + [ COMMA BWS preservingTrafos BWS ] + CLOSE -filterTrafo = %s"filter" OPEN BWS boolCommonExpr BWS CLOSE +rollupUnnamedHier = groupingProperty 1*( BWS COMMA BWS groupingProperty ) +rollupNamedHier = odataIdentifier ; qualifier of Aggregation.LeveledHierarchy annotation -searchTrafo = %s"search" OPEN BWS searchExpr BWS CLOSE +identityTrafo = %s"identity" -groupbyTrafo = %s"groupby" OPEN BWS groupbyList [ BWS COMMA BWS applyExpr ] BWS CLOSE -groupbyList = OPEN BWS groupbyElement *( BWS COMMA BWS groupbyElement ) BWS CLOSE -groupbyElement = groupingProperty / rollupSpec -rollupSpec = %s"rollup" OPEN BWS - ( %s"$all" / groupingProperty ) - 1*( BWS COMMA BWS groupingProperty ) - BWS CLOSE +topcountTrafo = %s"topcount" OPEN BWS collectionExpr BWS COMMA BWS commonExpr BWS CLOSE +toppercentTrafo = %s"toppercent" OPEN BWS collectionExpr BWS COMMA BWS commonExpr BWS CLOSE +topsumTrafo = %s"topsum" OPEN BWS collectionExpr BWS COMMA BWS commonExpr BWS CLOSE -identityTrafo = %s"identity" +topTrafo = %s"top" OPEN BWS 1*DIGIT BWS CLOSE +skipTrafo = %s"skip" OPEN BWS 1*DIGIT BWS CLOSE -topcountTrafo = %s"topcount" OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE -toppercentTrafo = %s"toppercent" OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE -topsumTrafo = %s"topsum" OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE +orderbyTrafo = %s"orderby" OPEN orderbyItem *( BWS COMMA BWS orderbyItem ) CLOSE -customFunction = namespace "." ( entityColFunction / complexColFunction / primitiveColFunction ) functionExprParameters +customFunction = namespace "." ( entityColFunction / complexColFunction / primitiveColFunction ) functionExprParameters ;------------------------------------------------------------------------------ -; 3. Extensions to $filter +; 3. New functions ;------------------------------------------------------------------------------ isdefinedExpr = %s"isdefined" OPEN BWS ( firstMemberExpr ) BWS CLOSE +aggregateFunctionExpr = aggregatableExpW [ aggregateFrom ] + / aggrPathPrefix nonprimAggWith [ aggregateFrom ] + / aggregateCount [ aggregateFrom ] + / aggregateCustom [ customFrom ] ;------------------------------------------------------------------------------ ; End of odata-aggregation-abnf diff --git a/abnf/odata-aggregation-testcases.yaml b/abnf/odata-aggregation-testcases.yaml index bd69624..a7e6e85 100644 --- a/abnf/odata-aggregation-testcases.yaml +++ b/abnf/odata-aggregation-testcases.yaml @@ -1,111 +1,150 @@ -# OData Aggregation ABNF Test Cases Version 4.0 +--- +#------------------------------------------------------------------------------ +# OData Aggregation ABNF Test Cases Version 4.0 +#------------------------------------------------------------------------------ +# 19 September 2023 +#------------------------------------------------------------------------------ # -# 04 November 2015 +# Technical Committee: +# OASIS Open Data Protocol (OData) TC +# https://www.oasis-open.org/committees/odata # -# Latest version: https://github.com/oasis-tcs/odata-abnf/blob/main/abnf/odata-aggregation-testcases.yaml +# Chairs: +# - Ralf Handl (ralf.handl@sap.com), SAP SE +# - Michael Pizzo (mikep@microsoft.com), Microsoft # -# Technical Committee: -# OASIS Open Data Protocol (OData) TC -# https://www.oasis-open.org/committees/odata +# Editors: +# - Ralf Handl (ralf.handl@sap.com), SAP SE +# - Hubert Heijkers (hubert.heijkers@nl.ibm.com), IBM +# - Gerald Krause (gerald.krause@sap.com), SAP SE +# - Michael Pizzo (mikep@microsoft.com), Microsoft +# - Martin Zurmuehl (martin.zurmuehl@sap.com), SAP SE +# - Heiko Theissen (heiko.theissen@sap.com), SAP SE # -# Chairs: -# - Barbara Hartel (barbara.hartel@sap.com), SAP AG -# - Ram Jeyaraman (Ram.Jeyaraman@microsoft.com), Microsoft +# Additional artifacts: +# This test case document is one component of a Work Product which consists of: +# - OData Extension for Data Aggregation Version 4.0 +# - OData Aggregation Vocabulary +# - OData Aggregation ABNF Construction Rules Version 4.0 +# - OData Aggregation ABNF Test Cases (this document) # -# Editors: -# - Ralf Handl (ralf.handl@sap.com), SAP AG -# - Hubert Heijkers (hubert.heijkers@nl.ibm.com), IBM -# - Gerald Krause (gerald.krause@sap.com), SAP AG -# - Michael Pizzo (mikep@microsoft.com), Microsoft -# - Martin Zurmuehl (martin.zurmuehl@sap.com), SAP AG +# Related work: +# This specification is related to: +# - OData Version 4.01 Part 1: Protocol +# - OData Version 4.01 Part 2: URL Conventions +# - OData ABNF Construction Rules Version 4.01 +# - OData ABNF Test Cases Version 4.01 +# - OData Common Schema Definition Language (CSDL) JSON Representation Version 4.01 +# - OData Common Schema Definition Language (CSDL) XML Representation Version 4.01 +# - OData JSON Format Version 4.01 +# This specification replaces or supersedes: +# - None # -# Additional artifacts: -# This test case document is one component of a Work Product which consists of: -# - OData Extension for Data Aggregation Version 4.0 -# - OData Aggregation ABNF Construction Rules Version 4.0 -# - OData Aggregation ABNF Test Cases -# - OData Aggregation Vocabulary +# Declared XML namespaces: +# - None # -# Related work: -# This specification is related to: -# - OData Version 4.0 Part 1: Protocol -# - OData Version 4.0 Part 2: URL Conventions -# - OData Version 4.0 Part 3: CSDL -# - OData ABNF Construction Rules Version 4.0 -# - OData Core Vocabulary -# - OData Measures Vocabulary -# - OData JSON Format Version 4.0 -# This specification replaces or supersedes: -# - None +# Abstract: +# This specification adds basic grouping and aggregation functionality (such as +# sum, min, and max) to the Open Data Protocol (OData) without changing any +# of the base principles of OData. # -# Declared XML namespaces: -# - None # -# Abstract: -# This specification adds basic grouping and aggregation functionality (e.g. -# sum, min, and max) to the Open Data Protocol (OData) without changing any -# of the base principles of OData. +# Overview: +# This document contains positive and negative test cases for the +# OData Aggregation ABNF Construction Rules Version 4.0. +# Positive test cases consist of the rule to test, the input string to parse, +# and a description of the test case. +# Negative test cases in addition state the character position at which the +# invalid portion of input text starts, 0 meaning the whole input string. # +# These test cases can be automatically executed with the ABNF test tool +# available at https://github.com/oasis-tcs/odata-abnf/tree/main/lib. +# This tool is neither required for interpreting the test cases, nor is it +# part of the OData work product. # -# Overview: -# This document contains positive and negative test cases for the -# OData Aggregation ABNF Construction Rules Version 4.0. -# Positive test cases consist of the rule to test, the input string to parse, -# and a description of the test case. -# Negative test cases in addition state the character position at which the -# invalid portion of input text starts, 0 meaning the whole input string. -# -# These test cases can be automatically executed with the ABNF test tool -# available at https://github.com/oasis-tcs/odata-abnf/tree/main/lib. -# This tool is neither required for interpreting the test cases, nor is it -# part of the OData work product. +# ------------------------------------------------------------------------------ Constraints: action: [] actionImport: [] expressionAlias: - Actual + - AmountExcl - AverageAmount - AveragePrice - AvgAmt - CountryPopulation + - CustomerAmount - CustomerCountryAverage + - CustomerYearAmount + - CustomerIDs - DailyAverage + - Detail - DistinctProducts + - FilteredSales + - FilteredSuppliers + - FirstLetter - ItemAmount - MaxAmount - MinAmount + - MinCurrency - MonthlyAverage + - NumberOfLeaves - ProductNames + - ProductIDs - Profit + - Profits + - RelativeAmount + - RelativeOrderSize + - RegionAmount + - RevenueContribution + - RevenueTrend + - Sale - SalesCount + - SomeCustomers + - Stuff + - PaperSalesCount + - SalesOrgCount - Tax - Total + - TotalAmount + - TotalAmountExcl - TotalPlannedRevenue - TotalPopulation + - TotalSales + - WeightedAmount - WeekDay + complexAnnotationInQuery: + - '@Core.GeometryFeature' complexColProperty: - Details - complexColFunction: [] + complexColFunction: + - condense complexColFunctionImport: [] complexFunction: [] complexFunctionImport: [] - complexProperty: [] + complexProperty: + - ShipTo complexTypeName: [] customAggregate: - Budget - Forecast + entityAnnotationInQuery: [] entityColFunction: - TopCountAndBalance entityColFunctionImport: [] entityColNavigationProperty: - Items + - Nodes - Orders - Products + - ProductCategories + - Profits # dynamic - Sales - SalesPlan - Suppliers - entityFunction: [] + entityFunction: + - TopProduct entityFunctionImport: [] entityNavigationProperty: - Category @@ -117,35 +156,52 @@ Constraints: - Products_cj - ProductGroup - Sales_cj + - SalesOrganization - Time + - TotalSales entitySetName: - Categories - Customers - Products - Products_cj + - ProductCategories - Sales - Sales_cj - SalesOrganizations + - SalesOrgHierarchies - Time - entityTypeName: [] + entityTypeName: + - DigitalProduct enumerationMember: [] enumerationTypeName: [] keyPathLiteral: [] keyPropertyAlias: [] - lambdaVariableExpr: [] + lambdaVariableExpr: + - p + - s namespacePart: - Aggregation + - Core - Custom + - Measures - Self + primitiveAnnotationInQuery: + - '@Core.MediaType' + - '@Measures.ISOCurrency' primitiveColFunction: [] primitiveColFunctionImport: [] - primitiveColProperty: [] + primitiveColProperty: + - Discounts primitiveFunction: - isroot - isleaf - isancestor - issibling - isdescendant + - rollupnode + - Rating + - sqrt + - Weight primitiveFunctionImport: [] primitiveKeyProperty: - Code @@ -153,24 +209,42 @@ Constraints: - ID primitiveNonKeyProperty: - Amount + - AmountExcl # dynamic - City - Cost - CountryCode + - CountryPopulation # dynamic + - CustomerAmount # dynamic + - CustomerYearAmount # dynamic + - FirstLetter # dynamic + - Region - Month - Name - PlannedRevenue - Population - Price - ProductID + - Profit # dynamic - Quantity + - RegionAmount # dynamic - Revenue - SalesArea + - SalesCount # dynamic + - SalesNumber - Shipped - Status - Street - TaxRate + - Total # dynamic + - WeightedAmount # dynamic - Year - streamProperty: [] + streamProperty: + - Image + termName: + - AnyStructure + - ISOCurrency + - GeometryFeature + - MediaType TestCases: - Name: aggregate - no parameters @@ -182,10 +256,50 @@ TestCases: Rule: queryOptions Input: $apply=aggregate(Amount with sum as Total) + - Name: aggregate - arithmetic expression + Rule: queryOptions + Input: $apply=aggregate((Amount sub Cost) mul TaxRate with sum as Tax) + + - Name: aggregate - arithmetic expression with literals + Rule: queryOptions + Input: $apply=aggregate((TaxRate sub 1) mul 'P1D' with average as Stuff) + + - Name: aggregate - forbidden arithmetic on collection + Rule: queryOptions + FailAt: 30 + Input: $apply=aggregate(Sales/Amount sub Sales/Cost with sum as TotalAmount) + + - Name: aggregate - arithmetic with $it + Rule: queryOptions + Input: $apply=aggregate(Amount sub $it/Cost with sum as TotalAmount) + + - Name: aggregate - allowed arithmetic on collection + Rule: queryOptions + Input: $apply=addnested(Sales,compute(Amount sub Cost as Profit) as + Profits)/aggregate(Profits/Profit with sum as TotalAmount) + + - Name: aggregate - forbidden arithmetic + Rule: queryOptions + Input: $apply=aggregate(Amount sub Discounts with sum as TotalAmount) + Expect: + - aggregatableExpr:Amount sub Discounts # but actually it is not + + - Name: aggregate - functional expression + Rule: queryOptions + Input: $apply=aggregate(Self.sqrt(Number=Amount mul Amount sub Cost mul Cost) with sum as TotalAmount) + + - Name: aggregate - functional expression on entity + Rule: queryOptions + Input: $apply=aggregate(Custom.Rating(Article=Product) with average as Stuff) + - Name: aggregate - alias - context URL Rule: odataRelativeUri Input: $metadata#Sales(Total) + - Name: aggregate - alias - context URL after concat + Rule: odataRelativeUri + Input: $metadata#Sales(@Core.AnyStructure) + - Name: aggregate - property requires alias Rule: queryOptions FailAt: 32 @@ -196,11 +310,6 @@ TestCases: FailAt: 24 Input: $apply=aggregate(Amount as Total) - - Name: aggregate - property requires alias - Rule: queryOptions - FailAt: 32 - Input: $apply=aggregate(Amount with sum) - - Name: aggregate - property requires method and alias Rule: queryOptions FailAt: 23 @@ -210,13 +319,20 @@ TestCases: Rule: queryOptions Input: $apply=aggregate(Amount mul Product/TaxRate with sum as Tax) - - Name: aggregate - expression after path + - Name: aggregate - no expression after path - this feature from CS02 has been removed Rule: queryOptions + FailAt: 29 Input: $apply=aggregate(Sales(Amount mul Product/TaxRate with sum as Tax)) + - Name: aggregate - aggregate annotations + Rule: queryOptions + Input: $apply=aggregate(Price/@Measures.ISOCurrency with min as MinCurrency) + Expect: + - annotationExpr:@Measures.ISOCurrency # no term casts in data aggregation paths + - Name: aggregate - alias - context URL Rule: odataRelativeUri - Input: $metadata#Products(Sales(Tax)) + Input: $metadata#Products(Sales(TaxRate)) - Name: aggregate - min Rule: queryOptions @@ -234,7 +350,35 @@ TestCases: Rule: queryOptions Input: $apply=aggregate(Product/Name with Custom.concat as ProductNames) + - Name: aggregate - custom aggregation method with type cast + Rule: queryOptions + Input: $apply=aggregate(Products/Self.DigitalProduct with Custom.discount as Stuff) + Expect: + - aggrPathPrefix:Products/Self.DigitalProduct + + - Name: aggregate - type cast after single-valued segment + Rule: queryOptions + Input: $apply=aggregate(Product/Self.DigitalProduct with Custom.discount as Stuff) + Expect: + - aggrPathPrefix:Product/Self.DigitalProduct + + - Name: aggregate - custom aggregation method with only type cast + Rule: queryOptions + Input: $apply=aggregate(Self.DigitalProduct with Custom.discount as Stuff) + Expect: + - aggrCastPath:Self.DigitalProduct + + - Name: aggregate - custom aggregation method with complex property + Rule: queryOptions + Input: $apply=aggregate(Product/ShipTo with Custom.TSP as Stuff) + Expect: + - aggrPathPrefix:Product/ShipTo + - Name: aggregate - countdistinct + Rule: queryOptions + Input: $apply=aggregate(Product/Name with countdistinct as DistinctProducts) + + - Name: aggregate - countdistinct with navigation property Rule: queryOptions Input: $apply=aggregate(Product with countdistinct as DistinctProducts) @@ -248,29 +392,86 @@ TestCases: $apply=aggregate(Amount with sum from Time with average from Product/Name with max as DailyAverage) + - Name: aggregate - from twice in one step + Rule: queryOptions + Input: + $apply=aggregate(Amount with average from Time,Product/Name + with max as DailyAverage) + + - Name: aggregate - from twice in one step wrong + Rule: queryOptions + FailAt: 47 + Input: + $apply=aggregate(Amount with average from Time from Product/Name + with max as DailyAverage) + - Name: aggregate - from requires as Rule: queryOptions FailAt: 55 Input: $apply=aggregate(Amount with sum from Time with average) - - Name: aggregate - property from requires with + - Name: aggregate - property from requires with 1 + Rule: queryOptions + FailAt: 47 + Input: $apply=aggregate(Amount with average from Time as DailyAverage) + + - Name: aggregate - property from requires with 2 Rule: queryOptions FailAt: 24 Input: $apply=aggregate(Amount from Time with average as DailyAverage) + - Name: aggregate - $count from + Rule: queryOptions + Input: $apply=aggregate(Sales/$count from Time with average as DailyAverage) + + - Name: aggregate - custom aggregate reached via path + Rule: queryOptions + Input: $apply=aggregate(Sales/Forecast) + Expect: + - aggrPathPrefix:Sales + + - Name: aggregate - custom aggregate reached via type cast + Rule: queryOptions + Input: $apply=aggregate(Products/Self.DigitalProduct/Forecast) + Expect: + - aggrPathPrefix:Products/Self.DigitalProduct + + - Name: aggregate - custom aggregate reached via type cast only + Rule: queryOptions + Input: $apply=aggregate(Self.DigitalProduct/Forecast) + Expect: + - aggrCastPath:Self.DigitalProduct + - Name: aggregate - custom aggregate and from Rule: queryOptions Input: $apply=aggregate(Forecast from Time with average as DailyAverage) + - Name: aggregate - custom aggregate and multiple from, missing alias + Rule: queryOptions + FailAt: 66 + Input: $apply=aggregate(Forecast from Time from Product/Name with average) + + - Name: aggregate - custom aggregate and from multiple + Rule: queryOptions + Input: $apply=aggregate(Forecast from Time,Product/Name with average as DailyAverage) + + - Name: aggregate - custom aggregate and multiple from with + Rule: queryOptions + Input: $apply=aggregate(Forecast from Time with average from Product/Name with max as DailyAverage) + + - Name: aggregate - custom aggregate, with, custom aggregate again + Rule: queryOptions + Input: $apply=aggregate(Forecast from Time with average from Product/Name as DailyAverage) + + - Name: aggregate - custom aggregate and from without with + Rule: queryOptions + Input: $apply=aggregate(Forecast from Time as Stuff) + - Name: aggregate - path with key segment Rule: queryOptions Input: $apply=aggregate(Product/SalesPlan('2015')/PlannedRevenue with sum as TotalPlannedRevenue) - - Name: Aggregation - collection of complex type - Rule: queryOptions - Input: $apply=groupby((Product),aggregate(Details(Amount with sum as Total))) - - Name: aggregate - $count Rule: queryOptions Input: $apply=aggregate($count as SalesCount) @@ -280,9 +481,41 @@ TestCases: FailAt: 24 Input: $apply=aggregate($count with sum as SalesCount) + - Name: aggregate - $count with primitive property + Rule: queryOptions + Input: $apply=aggregate(Discounts/$count as SalesCount) + + - Name: aggregate - $count with type cast + Rule: queryOptions + Input: $apply=aggregate(Products/Self.DigitalProduct/$count as Stuff) + Expect: + - aggrPropPath:Products/Self.DigitalProduct + + - Name: aggregate - $count with type cast + Rule: queryOptions + Input: $apply=aggregate(Self.DigitalProduct/$count as Stuff) + + - Name: aggregate - $count after single-valued property + Rule: queryOptions + Input: $apply=aggregate(Product/$count as Stuff) + + - Name: aggregate - $count with type-cast after single-valued property + Rule: queryOptions + Input: $apply=aggregate(Product/Self.DigitalProduct/$count as Stuff) + Expect: + - aggrPropPath:Product/Self.DigitalProduct + - Name: aggregate - topcount Rule: queryOptions Input: $apply=topcount(2,Amount) + Expect: + - collectionExpr:2 + + - Name: aggregate - topcount with $count + Rule: queryOptions + Input: $apply=topcount($these/$count div 10,Amount) + Expect: + - currCollectionExpr:$these/$count - Name: aggregate - topsum Rule: queryOptions @@ -322,44 +555,185 @@ TestCases: Rule: queryOptions Input: $apply=groupby((Product/Name,Amount)) - - Name: aggregate - groupby rollup + - Name: aggregate - groupby stream property + Rule: queryOptions + Input: $apply=groupby((Product/Image)) + Expect: + - snglPrimPath:Product/Image + + - Name: aggregate - groupby complex property + Rule: queryOptions + Input: $apply=groupby((Product/ShipTo)) + Expect: + - snglPropPath:Product/ShipTo + + - Name: aggregate - groupby annotations + Rule: queryOptions + FailAt: 29 # no term casts in data aggregation paths + Input: $apply=groupby((Product/Price/@Measures.ISOCurrency)) + + - Name: aggregate - groupby no two consecutive primitive properties + Rule: queryOptions + FailAt: 29 + Input: $apply=groupby((Product/Price/Quantity)) + + - Name: aggregate - groupby with type cast + Rule: queryOptions + Input: $apply=groupby((Product/Self.DigitalProduct/Region)) + + - Name: aggregate - groupby final type cast + Rule: queryOptions + FailAt: 43 + Input: $apply=groupby((Product/Self.DigitalProduct)) + + - Name: aggregate - groupby only derived property + Rule: queryOptions + Input: $apply=groupby((Self.DigitalProduct/Region)) + + - Name: aggregate - groupby context with type cast + Rule: odataRelativeUri + Input: $metadata#Products(ID,Self.DigitalProduct/Region) + Expect: + - qualifiedEntityTypeName:Self.DigitalProduct + + - Name: aggregate - groupby complex annotations + Rule: queryOptions + FailAt: 24 # no term casts in data aggregation paths + Input: $apply=groupby((Country/@Core.GeometryFeature)) +# Expect: +# - grpPropAnnoSegment:@Core.GeometryFeature + + - Name: aggregate - groupby with custom aggregates calculated from aggregated + instances + Rule: queryOptions + Input: $apply=groupby((Country),aggregate(Budget))/filter(Budget gt + 1000)/aggregate(Budget) + + - Name: aggregate - groupby rollup leveled hierarchy Rule: queryOptions Input: $apply=groupby((rollup(Customer/Country,Customer/Name),rollup(Product/ProductGroup/Name,Product/Name),Currency/Code),aggregate(Amount with sum as Total)) + - Name: aggregate - groupby rollup named leveled hierarchy + Rule: queryOptions + Input: + $apply=groupby((rollup(CustomerHierarchy),rollup(Product/ProductGroup/Name,Product/Name),Currency/Code),aggregate(Amount + with sum as Total)) + Expect: + - rollupNamedHier:CustomerHierarchy + + - Name: aggregate - groupby rollup recursive hierarchy + Rule: queryOptions + Input: $apply=groupby((rolluprecursive($root/SalesOrganizations,SalesOrgHierarchy,ID)),aggregate($count as + SalesOrgCount)) + + - Name: aggregate - groupby rollup recursive sub-hierarchy + Rule: queryOptions + Input: $apply=groupby((rolluprecursive( + $root/SalesOrganizations,SalesOrgHierarchy,SalesOrganization/ID, + descendants($root/SalesOrganizations,SalesOrgHierarchy,ID,filter(ID eq 'EMEA')))), + aggregate(Amount with sum as Total))/traverse($root/SalesOrganizations,SalesOrgHierarchy,SalesOrganization/ID, + preorder,Name desc,ID) + Expect: + - orderbyItem:Name desc + - orderbyItem:ID + + - Name: aggregate - groupby rollup recursive sub-hierarchy with filter + Rule: queryOptions + Input: $apply=groupby((rolluprecursive( + $root/SalesOrganizations,SalesOrgHierarchy,SalesOrganization/ID, + descendants($root/SalesOrganizations,SalesOrgHierarchy,ID,filter(ID eq 'EMEA'),2,keep start)/ancestors( + $root/SalesOrganizations,SalesOrgHierarchy,ID,filter(contains(Name, 'Central')),keep start))), + aggregate(Amount with sum as Total))/orderby(SalesOrganization/Name)/traverse( + $root/SalesOrganizations,SalesOrgHierarchy,SalesOrganization/ID,preorder) + - Name: aggregate - filter Rule: queryOptions Input: $apply=filter(Amount gt 3) - - Name: aggregate - expand + - Name: aggregate - search Rule: queryOptions - Input: $apply=expand(Sales,filter(Amount gt 3)) + Input: $apply=search(coffee) - - Name: aggregate - multi-level expand + - Name: aggregate - search with quoted argument Rule: queryOptions - Input: $apply=expand(Products,expand(Sales,filter(Amount gt 3))) + Input: $apply=search(')/top(1')/top(1) + Expect: + - searchExpr-incomplete:')/top(1' - - Name: aggregate - multi-level expand with branches + - Name: aggregate - addnested Rule: queryOptions - Input: $apply=expand(Products,expand(Sales,filter(Amount gt - 3)),expand(Suppliers,expand(Products,filter(true)))) + Input: $apply=addnested(Sales,filter(Amount gt 3) as FilteredSales) - - Name: aggregate - multi-level expand with branches + - Name: aggregate - addnested with filter on $this Rule: queryOptions - FailAt: 102 - Input: $apply=expand(Products,expand(Sales,filter(Amount gt - 3)),expand(Suppliers,filter(true),expand(Products))) + Input: $apply=addnested(Sales,filter($this/Customer/City eq ShipTo/City) as FilteredSales) - - Name: aggregate - multi-level expand with branches + - Name: aggregate - filter on $root Rule: queryOptions - FailAt: 89 - Input: $apply=expand(Products,expand(Sales,filter(Amount gt - 3)),expand(Suppliers,expand(Products))) + Input: $apply=filter(Name eq $root/Products('P2')/Name) - - Name: aggregate - search + - Name: aggregate - nesting complex property Rule: queryOptions - Input: $apply=search(coffee) + Input: $apply=addnested(ShipTo,identity as Stuff) + + - Name: aggregate - nesting collection-valued complex property + Rule: queryOptions + Input: $apply=addnested(Details,filter(Cost gt 1000) as Stuff) + + - Name: aggregate - multi-level nest + Rule: queryOptions + Input: $apply=addnested(Products,addnested(Sales,filter(Amount gt 3) as FilteredSales) as FilteredSales) + + - Name: aggregate - multi-level nest with branches + Rule: queryOptions + Input: + $apply=addnested(Products,addnested(Sales,filter(Amount gt 3) as FilteredSales) as + FilteredSales,addnested(Suppliers,addnested(Products,filter(true) as Stuff) as Stuff) as + FilteredSuppliers) + + - Name: aggregate - multi-level nest with branches + Rule: queryOptions + FailAt: 122 + Input: + $apply=addnested(Products,addnested(Sales,filter(Amount gt 3) as FilteredSales) as + Stuff,addnested(Suppliers,nest(Products))) + + - Name: aggregate - nest with filter on $this + Rule: queryOptions + Input: $apply=addnested(Sales,filter($this/Customer/City eq ShipTo/City) as FilteredSales) + + - Name: aggregate - nest without path + Rule: queryOptions + Input: + $apply=nest(groupby((Customer/ID)) as CustomerIDs,groupby((Product/ID)) + as ProductIDs) + + - Name: aggregate - join + Rule: queryOptions + Input: $apply=join(Sales as Sale) + + - Name: aggregate - join + Rule: queryOptions + Input: $apply=join(Sales as Sale,filter(Customer/Country eq 'US')) + + - Name: aggregate - join with collection-valued complex property + Rule: queryOptions + Input: $apply=join(Details as Detail,filter(Cost gt 1000)) + + - Name: aggregate - join with single-valued complex property + Rule: queryOptions + FailAt: 18 + Input: $apply=join(ShipTo as Detail,identity) + + - Name: aggregate - join + Rule: queryOptions + Input: $apply=outerjoin(Sales as Sale) + + - Name: aggregate - join + Rule: queryOptions + Input: $apply=outerjoin(Sales as Sale,filter(Customer/Country eq 'FR')) - Name: aggregate - isdefined Rule: queryOptions @@ -370,6 +744,119 @@ TestCases: Rule: queryOptions Input: $orderby=isdefined(Product) desc,Product asc + - Name: $count after navigation path + Rule: queryOptions + Input: $filter=Sales/$count gt 3 + Expect: + - collectionPathExpr:/$count + + - Name: aggregate function - prefix required + Rule: queryOptions + FailAt: 17 + Input: $filter=aggregate(Amount with sum) gt 5 + + - Name: aggregate function - arithmetic expression + Rule: queryOptions + Input: $filter=Sales/aggregate($it/TaxRate mul Amount with sum) gt 5 + Expect: + - aggregatableExpr:$it/TaxRate mul Amount + + - Name: aggregate function - $compute + Rule: queryOptions + Input: $compute=Sales/aggregate(Amount with sum) as Total + + - Name: aggregate function - lambda operators + Rule: queryOptions + Input: $filter=Products/all(p:p/Sales/any(s:s/Amount gt p/Sales/aggregate(Amount with average) mul 2)) + + - Name: aggregate function after function + Rule: commonExpr + Input: Self.TopProduct()/Sales/aggregate(Amount with sum) + + - Name: aggregate function - function expression + Rule: queryOptions + Input: $filter=Sales/aggregate(Custom.Rating(Article=Product) with average) gt 5 + + - Name: aggregate function - function expression on current collection + Rule: queryOptions + Input: $filter=$these/aggregate(Custom.Rating(Article=Product) with average) gt 5 + + - Name: aggregate function - after grouping + Rule: queryOptions + Input: $apply=groupby((Customer),aggregate(Amount with sum as + CustomerAmount))/compute(CustomerAmount div $these/aggregate(CustomerAmount with + sum) as RevenueContribution) + + - Name: aggregate function - within a group + Rule: queryOptions + Input: $apply=groupby((Customer,Year),aggregate(Amount with sum as + CustomerYearAmount))/groupby((Customer),compute(CustomerYearAmount div + $these/aggregate(CustomerYearAmount with sum) as RevenueTrend)) + + - Name: aggregate function - across input set + Rule: queryOptions + Input: $apply=compute(Amount div $these/aggregate(Amount with average) as + RelativeOrderSize) + + - Name: aggregate function - across input set with custom aggregate + Rule: queryOptions + Input: $apply=compute(Amount div $these/aggregate(Budget) as RelativeAmount) + + - Name: aggregate function - across input set with related custom aggregate + Rule: queryOptions + Input: $apply=compute(Amount div $these/aggregate(Product/Budget) as + RelativeAmount) + + - Name: aggregate function - across input set with from + Rule: queryOptions + Input: + $apply=compute(Amount div $these/aggregate(Amount with sum from Time + with average) as RelativeAmount) + + - Name: aggregate function - across input set with $count and from + Rule: queryOptions + Input: $apply=compute($these/aggregate($count from Product with max) as SalesCount) + + - Name: aggregate function - across input set with $count + Rule: queryOptions + Input: $apply=groupby((Customer),compute($these/aggregate($count) as + SalesCount))/filter(SalesCount ge 2)/aggregate(Amount with sum as + TotalAmount) + + - Name: aggregate function - within a group - ... + Rule: queryOptions + Input: $apply=groupby((Region),compute($these/aggregate(SalesNumber with + average) as RegionAmount))/filter(RegionAmount gt + 150)/concat(groupby((Region),aggregate(SalesNumber with average as + RegionAmount)),aggregate(SalesNumber with average as TotalAmount)) + + - Name: aggregate function - in $compute option + Rule: queryOptions + Input: $compute=Amount div $these/aggregate(Amount with sum) as RelativeAmount + + - Name: aggregate function - in $compute option with custom aggregate + Rule: queryOptions + Input: $compute=Amount div $these/aggregate(Budget) as RelativeAmount + + - Name: aggregate function - within filter after navigation property + Rule: queryOptions + Input: $filter=Products/aggregate(Sales/Amount with sum) gt 3 + + - Name: aggregate function - within filter within groupby + Rule: odataRelativeUri + Input: Products?$apply=groupby((Category), + filter($these/aggregate(Sales/Amount with sum) gt 3)) + Expect: + - currCollectionExpr:$these/aggregate(Sales/Amount with sum) + + - Name: aggregate function - aggregate within any + Rule: queryOptions + Input: $filter=Products/any(p:p/Sales/aggregate(Amount with sum) gt 10) + + - Name: aggregate function - countdistinct with navigation property + Rule: queryOptions + Input: $filter=$these/aggregate(Products/Sales with countdistinct) gt 3 + - Name: aggregate - crossjoin Rule: odataRelativeUri Input: $crossjoin(Products_cj,Sales_cj)?$apply=filter(Products_cj/ID eq @@ -386,7 +873,7 @@ TestCases: - Name: custom aggregates - entity set Rule: odataRelativeUri - Input: Sales?$apply=groupby((Time/Month),aggregate(Forecast)) + Input: Sales?$apply=groupby((Time/Month),aggregate(Forecast as Stuff)) - Name: custom aggregates - crossjoin Rule: odataRelativeUri @@ -394,31 +881,113 @@ TestCases: - Name: hierarchy functions - isroot Rule: odataRelativeUri - Input: SalesOrganizations?$filter=$it/Aggregation.isroot(Hierarchy='SalesOrgHierarchy') + Input: SalesOrganizations?$filter=Aggregation.isroot( + HierarchyNodes=$root/SalesOrganizations, + HierarchyQualifier='SalesOrgHierarchy', + Node=ID) - Name: hierarchy functions - isdescendant Rule: odataRelativeUri - Input: SalesOrganizations?$filter=$it/Aggregation.isdescendant(Hierarchy='SalesOrgHierarchy',Node='EMEA') + Input: SalesOrganizations?$filter=Aggregation.isdescendant( + HierarchyNodes=$root/SalesOrganizations, + HierarchyQualifier='SalesOrgHierarchy', + Node=ID, + Ancestor='EMEA') - Name: hierarchy functions - isdescendant Rule: odataRelativeUri - Input: SalesOrganizations?$filter=$it/Aggregation.isdescendant(Hierarchy='SalesOrgHierarchy',Node='EMEA',MaxDistance=1) + Input: SalesOrganizations?$filter=Aggregation.isdescendant( + HierarchyNodes=$root/SalesOrganizations, + HierarchyQualifier='SalesOrgHierarchy', + Node=ID, + Ancestor='EMEA', + MaxDistance=1) - Name: hierarchy functions - isancestor Rule: odataRelativeUri - Input: SalesOrganizations?$filter=$it/Aggregation.isancestor(Hierarchy='SalesOrgHierarchy',Node='EMEA') + Input: SalesOrganizations?$filter=Aggregation.isancestor( + HierarchyNodes=$root/SalesOrganizations, + HierarchyQualifier='SalesOrgHierarchy', + Node=ID, + Descendant='EMEA') - Name: hierarchy functions - isancestor Rule: odataRelativeUri - Input: SalesOrganizations?$filter=$it/Aggregation.isancestor(Hierarchy='SalesOrgHierarchy',Node='EMEA',MaxDistance=1) + Input: SalesOrganizations?$filter=Aggregation.isancestor( + HierarchyNodes=$root/SalesOrganizations, + HierarchyQualifier='SalesOrgHierarchy', + Node=ID, + Descendant='EMEA', + MaxDistance=1) - Name: hierarchy functions - issibling Rule: odataRelativeUri - Input: SalesOrganizations?$filter=$it/Aggregation.issibling(Hierarchy='SalesOrgHierarchy',Node='EMEA') + Input: SalesOrganizations?$filter=Aggregation.issibling( + HierarchyNodes=$root/SalesOrganizations, + HierarchyQualifier='SalesOrgHierarchy', + Node=ID, + Other='EMEA') - Name: hierarchy functions - isleaf Rule: odataRelativeUri - Input: SalesOrganizations?$filter=$it/Aggregation.isleaf(Hierarchy='SalesOrgHierarchy') + Input: SalesOrganizations?$filter=Aggregation.isleaf( + HierarchyNodes=$root/SalesOrganizations, + HierarchyQualifier='SalesOrgHierarchy', + Node=ID) + + - Name: hierarchy transformations - ancestors with forbidden node property path + Rule: queryOptions + FailAt: 65 + Input: $apply=ancestors($root/SalesOrganizations,SalesOrgHierarchy,Sales(4711)/ID,identity) + + - Name: hierarchy transformations - ancestors of search result + Rule: queryOptions + Input: $apply=ancestors($root/SalesOrganizations,SalesOrgHierarchy,ID,search(East)/top(3)) + Expect: + - preservingTrafos:search(East)/top(3) + + - Name: hierarchy transformations - ancestors + Rule: queryOptions + Input: $apply=ancestors($root/SalesOrganizations,SalesOrgHierarchy,ID,filter(contains(Name,'East') or + contains(Name,'Central'))) + + - Name: hierarchy transformations - ancestors max distance + Rule: queryOptions + Input: $apply=ancestors($root/SalesOrganizations,SalesOrgHierarchy,ID,filter(contains(Name,'East') or + contains(Name,'Central')), 2) + + - Name: hierarchy transformations - ancestors with forbidden fifth parameter + Rule: queryOptions + FailAt: 94 + Input: $apply=ancestors($root/SalesOrganizations,SalesOrgHierarchy,ID,filter(contains(Name,'East')), + filter(contains(Name,'Central')), 2) + + - Name: hierarchy transformations - ancestors hierarchy directory + Rule: queryOptions + Input: $apply=ancestors($root/SalesOrgHierarchies('DEFAULT')/Nodes,SalesOrgHierarchy,ID,filter(contains(Name,'East') or + contains(Name,'Central')), keep start) + + - Name: hierarchy transformations - ancestors processing non-hierarchical input set; node values via navigation property + Rule: odataRelativeUri + Input: Sales?$apply=ancestors($root/SalesOrganizations,SalesOrgHierarchy,SalesOrganization/ID, + filter(contains(SalesOrganization/Name,'East') or + contains(SalesOrganization/Name,'Central')), 2, keep start) + + - Name: hierarchy transformations - descendants + Rule: queryOptions + Input: $apply=descendants($root/SalesOrganizations,SalesOrgHierarchy,ID,filter(Name eq 'US')) + + - Name: hierarchy transformations - descendants max distance + Rule: queryOptions + Input: $apply=descendants($root/SalesOrganizations,SalesOrgHierarchy,ID,filter(Name eq 'US'), 3) + + - Name: hierarchy transformations - descendants keep start + Rule: queryOptions + Input: $apply=descendants($root/SalesOrganizations,SalesOrgHierarchy,ID,filter(Name eq 'US'),3,keep start) + + - Name: hierarchy transformations - traverse + Rule: queryOptions + Input: $apply=traverse($root/SalesOrganizations,SalesOrgHierarchy,ID,preorder) - Name: distinct values - no aggregate Rule: odataRelativeUri @@ -438,17 +1007,20 @@ TestCases: - Name: aggregation methods Rule: queryOptions - Input: $apply=groupby((Name),aggregate(Sales(Amount with sum as Total))) + Input: $apply=groupby((Name),aggregate(Amount with sum as Total)) - Name: aggregation methods - multiple grouping Rule: queryOptions - Input: - $apply=groupby((Name,Sales/Currency/Code),aggregate(Sales(Amount with sum - as Total))) + Input: $apply=groupby((Country,Name),aggregate(Amount with sum as Total)) - Name: aggregation methods - multiple grouping - distinct Rule: queryOptions - Input: $apply=groupby((Country,Sales/Product/Name)) + Input: $apply=groupby((Customer,Product/Name)) + + - Name: aggregation methods - collection-valued navigation property + Rule: queryOptions + FailAt: 21 + Input: $apply=groupby((Sales/Product/Name)) - Name: aggregation methods - average Rule: queryOptions @@ -457,21 +1029,23 @@ TestCases: - Name: aggregation methods - $count segment Rule: queryOptions - Input: $apply=groupby((Name),aggregate(Sales/$count with sum as SalesCount)) + Input: $apply=groupby((Name),aggregate(Sales/$count as SalesCount)) + + - Name: aggregation methods - $count segment after single-valued + Rule: queryOptions + Input: $apply=groupby((Name),aggregate(Sales/Amount/$count as SalesCount)) - Name: aggregation methods - $count segment and sum Rule: queryOptions - Input: $apply=groupby((Name),aggregate(Sales/$count with sum as - SalesCount,Sales(Amount with sum as Total))) + Input: $apply=groupby((Name),aggregate(Sales/$count as + SalesCount,Sales/Amount with sum as Total)) - Name: aggregation methods - $count only allowed on top level, not nested within path Rule: queryOptions FailAt: 38 - Input: - $apply=groupby((Name),aggregate(Sales($count as SalesCount),Sales(Amount - with sum as Total))) + Input: $apply=groupby((Name),aggregate(Sales($count as SalesCount))) - Name: collection-valued path Rule: queryOptions @@ -482,21 +1056,15 @@ TestCases: Input: $apply=groupby((Customer/Country),aggregate(Amount with sum as Actual,Forecast)) - - Name: custom aggregate - with path + - Name: custom aggregate reached via path Rule: queryOptions - Input: $apply=groupby((Name),aggregate(Sales(Amount with sum as - Actual),Sales/Forecast)) + Input: $apply=groupby((Name),aggregate(Sales/Forecast)) - Name: aliasing - aggregate with two parameters Rule: queryOptions Input: $apply=groupby((Customer/Country),aggregate(Amount with sum as Total,Amount with average as AvgAmt)) - - Name: aliasing - two parameters with path - Rule: queryOptions - Input: $apply=groupby((Name),aggregate(Sales(Amount with sum as - Total),Sales(Amount with average as AvgAmt))) - - Name: aliasing - group by and aggregate same property Rule: queryOptions Input: $apply=groupby((Amount),aggregate(Amount with sum as Total)) @@ -515,6 +1083,20 @@ TestCases: $apply=groupby((Customer/Country,Product/Name,Currency/Code),topcount(2,Amount)/aggregate(Amount with sum as Total)) + - Name: combining transformations - number of leaves + Rule: queryOptions + Input: $apply=groupby((CountryCode),concat( + groupby((Customer))/aggregate($count as NumberOfLeaves), + aggregate(Budget,Forecast) + )/Custom.condense())/orderby(Budget desc)/top(4) + + - Name: combining transformations - number of leaves + Rule: queryOptions + Input: $apply=groupby((CountryCode),concat( + groupby((Customer))/aggregate($count as NumberOfLeaves), + aggregate(Budget,Forecast) + )/Custom.condense())/orderby(Budget desc)/top(4) + - Name: compute Rule: queryOptions Input: $apply=compute(Amount mul Product/TaxRate as Tax) @@ -524,6 +1106,18 @@ TestCases: Input: $apply=compute(Amount mul Product/TaxRate as Tax, day(Time/Date) as WeekDay) + - Name: top + Rule: queryOptions + Input: $apply=top(5) + + - Name: skip + Rule: queryOptions + Input: $apply=skip(5) + + - Name: orderby + Rule: queryOptions + Input: $apply=orderby(Country asc,Name desc) + - Name: model functions as set transformations Rule: queryOptions Input: @@ -547,7 +1141,7 @@ TestCases: - Name: controlling aggregation - rollup and from Rule: queryOptions Input: - $apply=groupby((rollup($all,Customer/Country,Customer/ID),Currency/Code),aggregate(Amount + $apply=groupby((rollup(Customer/Country,Customer/ID),Currency/Code),aggregate(Amount with sum from Customer/ID with average from Customer/Country with average as CustomerCountryAverage)) @@ -565,6 +1159,11 @@ TestCases: Input: $apply=groupby((Time),aggregate(Amount with sum as Total))/aggregate(Total with average as DailyAverage) + - Name: transformation sequences - compute, groupby, and nest + Rule: queryOptions + Input: $apply=compute(substring(Name,0,1) as + FirstLetter)/groupby((FirstLetter),nest(identity as SomeCustomers)) + - Name: transformation sequences - simple Rule: queryOptions Input: @@ -601,11 +1200,114 @@ TestCases: 10000000),groupby((Continent/Name),aggregate(CountryPopulation with sum as TotalPopulation))) - - Name: transformation sequences - filter and expand + - Name: transformation sequences - filter and nest Rule: queryOptions - Input: $apply=filter(Status eq 'incomplete')/expand(Items,filter(not - Shipped))/groupby((Customer/Country),aggregate(Items(Amount with sum as - ItemAmount))) + Input: + $apply=filter(Status eq 'incomplete')/addnested(Items,filter(not + Shipped) as Stuff)/groupby((Customer/Country),aggregate(Amount with sum as + ItemAmount)) + + - Name: transformation sequences - paging with skip and top + Rule: queryOptions + Input: $apply=groupby((Customer/City),aggregate(Amount with sum as + Total))/skip(10)/top(5) + + - Name: transformation sequences - sorting + Rule: queryOptions + Input: $apply=groupby((Product/Name),aggregate(Amount with sum as + Total))/orderby(Total desc) + + - Name: transformation sequences - joining related instances + Rule: queryOptions + Input: $apply=join(Sales as TotalSales,aggregate(Amount with sum as + Total))/groupby((Name,TotalSales/Total)) + + - Name: transformation sequences - joining related instances keeping unrelated ones + Rule: queryOptions + Input: $apply=outerjoin(Sales as TotalSales,aggregate(Amount with sum as + Total))/groupby((Name,TotalSales/Total)) + + - Name: transformation sequences - sub-hierarchy selection + Rule: queryOptions + Input: $apply=descendants($root/SalesOrganizations,SalesOrgHierarchy,ID,filter(Name eq 'US'),keep + start)/ancestors($root/SalesOrganizations,SalesOrgHierarchy,ID,filter(contains(Name,'East')),keep + start)/traverse($root/SalesOrganizations,SalesOrgHierarchy,ID,preorder) + + - Name: transformation sequences - aggregation along hierarchy + Rule: queryOptions + Input: $apply=addnested(Sales,filter(Product/Name eq + 'Paper') as FilteredSales)/groupby((rolluprecursive($root/SalesOrganizations,SalesOrgHierarchy,ID)), + aggregate(Sales/$count as PaperSalesCount)) + + - Name: two hierarchies in one transformation sequence + Rule: queryOptions + Input: $apply=groupby((rolluprecursive( + $root/SalesOrganizations,SalesOrgHierarchy, + SalesOrganization/ID, + ancestors( + $root/SalesOrganizations,SalesOrgHierarchy, + ID, + traverse( + $root/ProductCategories, + ProductCategoryHierarchy, + ProductCategories/ID, + preorder, + filter(Name eq 'Cereals'))) + )), + aggregate(Amount with sum as Total)) + + - Name: two hierarchies in one transformation sequence - alternative + Rule: queryOptions + Input: $apply=groupby((rolluprecursive( + $root/SalesOrganizations,SalesOrgHierarchy, + SalesOrganization/ID, + ancestors( + $root/SalesOrganizations,SalesOrgHierarchy, + ID, + descendants( + $root/ProductCategories,ProductCategoryHierarchy, + ProductCategories/ID, + filter(ProductCategories/any(p:p/Name eq 'Cereals')), + keep start)) + )), + aggregate(Amount with sum as Total)) + + - Name: multi-parent hierarchy with weighted edges + (ns.SalesOrganization@Aggregation.RecursiveHierarchy#SalesOrgHierarchy/ParentPropertyPath = Edges/Parent) + Rule: queryOptions + Input: $apply=groupby( + (rolluprecursive( + $root/SalesOrganizations, + SalesOrgHierarchy, + SalesOrganization/ID)), + compute(Amount mul SalesOrganization/Self.Weight(Ancestor=Aggregation.rollupnode()) as + WeightedAmount)/aggregate(WeightedAmount with sum as TotalAmount)) + + - Name: aggregation with 1:n hierarchy assignment + Rule: queryOptions + Input: $apply=groupby((rolluprecursive($root/SalesOrganizations,SalesOrgHierarchy,Suppliers/SalesOrganization/ID)), + aggregate(Amount with sum as Total)) + Expect: + - recHierPropertyPath:Suppliers/SalesOrganization/ID + + - Name: traverserecursive + Rule: queryOptions + Input: $apply=groupby((rolluprecursive($root/SalesOrganizations,SalesOrgHierarchy,SalesOrganization/ID)), + filter(SalesOrganization eq Aggregation.rollupnode())) + + - Name: restricted hierarchical measure + Rule: queryOptions + Input: $apply=groupby( + (rolluprecursive( + $root/SalesOrganizations, + SalesOrgHierarchy, + SalesOrganization/ID, + descendants($root/SalesOrganizations, + SalesOrgHierarchy, + ID, filter(ID eq 'US'), keep start))), + compute(case(SalesOrganization eq Aggregation.rollupnode():Amount) as AmountExcl)/aggregate( + Amount with sum as TotalAmount, + AmountExcl with sum as TotalAmountExcl)) - Name: aggregate in $expand Rule: odataRelativeUri diff --git a/obsolete/odata-aggregation-testcases.xml b/obsolete/odata-aggregation-testcases.xml index b539b4b..385d11b 100644 --- a/obsolete/odata-aggregation-testcases.xml +++ b/obsolete/odata-aggregation-testcases.xml @@ -1,491 +1,649 @@ - - - - - - - - - - - - - - - - - - Actual - AverageAmount - AveragePrice - AvgAmt - CountryPopulation - CustomerCountryAverage - DailyAverage - DistinctProducts - ItemAmount - MaxAmount - MinAmount - MonthlyAverage - ProductNames - Profit - SalesCount - Tax - Total - TotalPlannedRevenue - TotalPopulation - WeekDay - - - Details - - - - - - - - - Budget - Forecast - - - TopCountAndBalance - - - - Items - Orders - Products - Sales - SalesPlan - Suppliers - - - - - - - Category - Continent - Country - Currency - Customer - Product - Products_cj - ProductGroup - Sales_cj - Time - - - Categories - Customers - Products - Products_cj - Sales - Sales_cj - SalesOrganizations - Time - - - - - - - - - Aggregation - Custom - Self - - - - - - isroot - isleaf - isancestor - issibling - isdescendant - - - - Code - Date - ID - - - Amount - City - Cost - CountryCode - Month - Name - PlannedRevenue - Population - Price - ProductID - Quantity - Revenue - SalesArea - Shipped - Status - Street - TaxRate - Year - - - - - $apply=aggregate() - - - $apply=aggregate(Amount with sum as Total) - - - $metadata#Sales(Total) - - - $apply=aggregate(Amount with sum) - - - $apply=aggregate(Amount as Total) - - - $apply=aggregate(Amount with sum) - - - $apply=aggregate(Amount) - - - $apply=aggregate(Amount mul Product/TaxRate with sum as Tax) - - - $apply=aggregate(Sales(Amount mul Product/TaxRate with sum as Tax)) - - - $metadata#Products(Sales(Tax)) - - - $apply=aggregate(Amount with max as MaxAmount) - - - $apply=aggregate(Amount with max as MaxAmount) - - - $apply=aggregate(Amount with average as AverageAmount) - - - $apply=aggregate(Product/Name with Custom.concat as ProductNames) - - - $apply=aggregate(Product with countdistinct as DistinctProducts) - - - $apply=aggregate(Amount with sum from Time with average as DailyAverage) - - - $apply=aggregate(Amount with sum from Time with average from Product/Name with max as DailyAverage) - - - $apply=aggregate(Amount with sum from Time with average) - - - $apply=aggregate(Amount from Time with average as DailyAverage) - - - $apply=aggregate(Forecast from Time with average as DailyAverage) - - - $apply=aggregate(Product/SalesPlan('2015')/PlannedRevenue with sum as TotalPlannedRevenue) - - - $apply=groupby((Product),aggregate(Details(Amount with sum as Total))) - - - $apply=aggregate($count as SalesCount) - - - $apply=aggregate($count with sum as SalesCount) - - - $apply=topcount(2,Amount) - - - $apply=topsum(15,Amount) - - - $apply=toppercent(50,Amount) - - - $apply=bottomcount(2,Amount) - - - $apply=bottomsum(15,Amount) - - - $apply=bottompercent(50,Amount) - - - $apply=identity - - - $apply=concat(topcount(2,Amount),bottomcount(2,Amount)) - - - $apply=groupby((Customer/Country,Product/Name),aggregate(Amount with sum as Total)) - - - $apply=groupby((Product/Name,Amount)) - - - $apply=groupby((rollup(Customer/Country,Customer/Name),rollup(Product/ProductGroup/Name,Product/Name),Currency/Code),aggregate(Amount with sum as Total)) - - - $apply=filter(Amount gt 3) - - - $apply=expand(Sales,filter(Amount gt 3)) - - - $apply=expand(Products,expand(Sales,filter(Amount gt 3))) - - - $apply=expand(Products,expand(Sales,filter(Amount gt 3)),expand(Suppliers,expand(Products,filter(true)))) - - - $apply=expand(Products,expand(Sales,filter(Amount gt 3)),expand(Suppliers,filter(true),expand(Products))) - - - $apply=expand(Products,expand(Sales,filter(Amount gt 3)),expand(Suppliers,expand(Products))) - - - $apply=search(coffee) - - - $filter=isdefined(Product) and isdefined(Customer/Name) and isdefined(Forecast) - - - $orderby=isdefined(Product) desc,Product asc - - - - $crossjoin(Products_cj,Sales_cj)?$apply=filter(Products_cj/ID eq Sales_cj/ProductID)/groupby((Products_cj/Name),aggregate(Sales_cj/Amount with sum as Total)) - - - - $crossjoin(Products_cj,Time,Sales_cj)?$apply=groupby((Products_cj/Name,Time/Date),aggregate(Budget)) - - - $crossjoin(Products_cj,Time,Sales_cj)?$apply=groupby((Products_cj/Name,Time/Date),aggregate(Sales_cj/Forecast)) - - - Sales?$apply=groupby((Time/Month),aggregate(Forecast)) - - - $crossjoin(Time)?$apply=groupby((Time/Year),aggregate(Budget)) - - - SalesOrganizations?$filter=$it/Aggregation.isroot(Hierarchy='SalesOrgHierarchy') - - - SalesOrganizations?$filter=$it/Aggregation.isdescendant(Hierarchy='SalesOrgHierarchy',Node='EMEA') - - - SalesOrganizations?$filter=$it/Aggregation.isdescendant(Hierarchy='SalesOrgHierarchy',Node='EMEA',MaxDistance=1) - - - SalesOrganizations?$filter=$it/Aggregation.isancestor(Hierarchy='SalesOrgHierarchy',Node='EMEA') - - - SalesOrganizations?$filter=$it/Aggregation.isancestor(Hierarchy='SalesOrgHierarchy',Node='EMEA',MaxDistance=1) - - - SalesOrganizations?$filter=$it/Aggregation.issibling(Hierarchy='SalesOrgHierarchy',Node='EMEA') - - - SalesOrganizations?$filter=$it/Aggregation.isleaf(Hierarchy='SalesOrgHierarchy') - - - Customers?$apply=groupby((Name)) - - - $apply=groupby((Customer/Name)) - - - $apply=groupby((Customer/Name,Customer/ID)) - - - $apply=groupby((Customer/Name,Customer/ID,Product/Name)) - - - $apply=groupby((Name),aggregate(Sales(Amount with sum as Total))) - - - $apply=groupby((Name,Sales/Currency/Code),aggregate(Sales(Amount with sum as Total))) - - - $apply=groupby((Country,Sales/Product/Name)) - - - $apply=groupby((Customer/Country),aggregate(Amount with average as AverageAmount)) - - - $apply=groupby((Name),aggregate(Sales/$count with sum as SalesCount)) - - - $apply=groupby((Name),aggregate(Sales/$count with sum as SalesCount,Sales(Amount with sum as Total))) - - - $apply=groupby((Name),aggregate(Sales($count as SalesCount),Sales(Amount with sum as Total))) - - - $apply=groupby((Name),aggregate(Sales/Amount with sum as Total)) - - - $apply=groupby((Customer/Country),aggregate(Amount with sum as Actual,Forecast)) - - - $apply=groupby((Name),aggregate(Sales(Amount with sum as Actual),Sales/Forecast)) - - - $apply=groupby((Customer/Country),aggregate(Amount with sum as Total,Amount with average as AvgAmt)) - - - $apply=groupby((Name),aggregate(Sales(Amount with sum as Total),Sales(Amount with average as AvgAmt))) - - - $apply=groupby((Amount),aggregate(Amount with sum as Total)) - - - $apply=concat(groupby((Customer/Country,Product/Name,Currency/Code),aggregate(Amount with sum as Total))/groupby((Customer/Country,Currency/Code),topcount(1,Total)),groupby((Customer/Country,Currency/Code),aggregate(Amount with sum as Total))) - - - $apply=groupby((Customer/Country,Product/Name,Currency/Code),topcount(2,Amount)/aggregate(Amount with sum as Total)) - - - $apply=compute(Amount mul Product/TaxRate as Tax) - - - $apply=compute(Amount mul Product/TaxRate as Tax, day(Time/Date) as WeekDay) - - - $apply=groupby((Customer/Country,Product/Name),aggregate(Amount with sum as Total))/groupby((Customer/Country),Self.TopCountAndBalance(Count=1,Property='Total')) - - - $apply=groupby((Product/ID,Product/Name,Time/Month),aggregate(Amount with sum as Total))/groupby((Product/ID,Product/Name),aggregate(Total with average as AverageAmount)) - - - $apply=groupby((Product/ID,Product/Name),aggregate(Amount with sum from Time/Month with average as MonthlyAverage)) - - - $apply=groupby((rollup($all,Customer/Country,Customer/ID),Currency/Code),aggregate(Amount with sum from Customer/ID with average from Customer/Country with average as CustomerCountryAverage)) - - - $apply=filter(Amount le 1)/aggregate(Amount with sum as Total) - - - $apply=filter(Amount le 2)/groupby((Product/Name),aggregate(Forecast))&$filter=Total ge 4 - - - $apply=groupby((Time),aggregate(Amount with sum as Total))/aggregate(Total with average as DailyAverage) - - - $apply=groupby((Continent/Name,Country/Name),aggregate(Population with sum as TotalPopulation)) - - - $apply=filter(Population ge 10000000)/groupby((Continent/Name,Country/Name),aggregate(Population with sum as TotalPopulation)) - - - $apply=groupby((Continent/Name,Country/Name),aggregate(Population with sum as CountryPopulation))/filter(CountryPopulation ge 10000000)/concat(identity,groupby((Continent/Name),aggregate(CountryPopulation with sum as TotalPopulation))) - - - $apply=groupby((Continent/Name,Country/Name),aggregate(Population with sum as CountryPopulation))/filter(CountryPopulation ge 10000000)/groupby((rollup(Continent/Name,Country/Name)),aggregate(CountryPopulation with sum as TotalPopulation)) - - - $apply=groupby((Continent/Name,Country/Name),aggregate(Population with sum as CountryPopulation))/concat(filter(CountryPopulation ge 10000000),groupby((Continent/Name),aggregate(CountryPopulation with sum as TotalPopulation))) - - - $apply=filter(Status eq 'incomplete')/expand(Items,filter(not Shipped))/groupby((Customer/Country),aggregate(Items(Amount with sum as ItemAmount))) - - - Categories?$expand=Products($apply=aggregate(Price with average as AveragePrice)) - - + + + + + + + + + + + + + + + + + + Actual + AverageAmount + AveragePrice + AvgAmt + CountryPopulation + CustomerAmount + CustomerCountryAverage + CustomerYearAmount + CustomerIDs + DailyAverage + Detail + DistinctProducts + FilteredSales + FirstLetter + ItemAmount + MaxAmount + MinAmount + MonthlyAverage + ProductNames + ProductIDs + Profit + RelativeAmount + RelativeOrderSize + RegionAmount + RevenueContribution + RevenueTrend + Sale + SalesCount + SomeCustomers + Stuff + PaperSalesCount + SalesOrgCount + Tax + Total + TotalAmount + TotalPlannedRevenue + TotalPopulation + TotalSales + WeekDay + + + Details + + + + + + + ShipTo + + + + Budget + Forecast + + + TopCountAndBalance + + + + Items + Orders + Products + Sales + SalesPlan + Suppliers + + + + + + + Category + Continent + Country + Currency + Customer + Product + Products_cj + ProductGroup + Sales_cj + Time + TotalSales + + + Categories + Customers + Products + Products_cj + Sales + Sales_cj + SalesOrganizations + Time + + + + + + + + p + + + Aggregation + Custom + Self + + + + + + isroot + isleaf + isancestor + issibling + isdescendant + + + + Code + Date + ID + + + Amount + City + Cost + CountryCode + Region + Month + Name + PlannedRevenue + Population + Price + ProductID + Quantity + Revenue + SalesArea + SalesNumber + Shipped + Status + Street + TaxRate + Year + + + + + $apply=aggregate() + + + $apply=aggregate(Amount with sum as Total) + + + $metadata#Sales(Total) + + + $apply=aggregate(Amount with sum) + + + $apply=aggregate(Amount as Total) + + + $apply=aggregate(Amount with sum) + + + $apply=aggregate(Amount) + + + $apply=aggregate(Amount mul Product/TaxRate with sum as Tax) + + + $apply=aggregate(Sales(Amount mul Product/TaxRate with sum as Tax)) + + + $metadata#Products(Sales(Tax)) + + + $apply=aggregate(Amount with max as MaxAmount) + + + $apply=aggregate(Amount with max as MaxAmount) + + + $apply=aggregate(Amount with average as AverageAmount) + + + $apply=aggregate(Product/Name with Custom.concat as ProductNames) + + + $apply=aggregate(Product with countdistinct as DistinctProducts) + + + $apply=aggregate(Amount with sum from Time with average as DailyAverage) + + + $apply=aggregate(Amount with sum from Time with average from Product/Name with max as DailyAverage) + + + $apply=aggregate(Amount with sum from Time with average) + + + $apply=aggregate(Amount from Time with average as DailyAverage) + + + $apply=aggregate(Sales/Forecast) + + + $apply=aggregate(Forecast from Time with average as DailyAverage) + + + $apply=aggregate(Product/SalesPlan('2015')/PlannedRevenue with sum as TotalPlannedRevenue) + + + $apply=groupby((Product),aggregate(Details(Amount with sum as Total))) + + + $apply=aggregate($count as SalesCount) + + + $apply=aggregate($count with sum as SalesCount) + + + $apply=topcount(2,Amount) + + + $apply=topsum(15,Amount) + + + $apply=toppercent(50,Amount) + + + $apply=bottomcount(2,Amount) + + + $apply=bottomsum(15,Amount) + + + $apply=bottompercent(50,Amount) + + + $apply=identity + + + $apply=concat(topcount(2,Amount),bottomcount(2,Amount)) + + + $apply=groupby((Customer/Country,Product/Name),aggregate(Amount with sum as Total)) + + + $apply=groupby((Product/Name,Amount)) + + + $apply=groupby((Country),aggregate(Budget))/filter(Budget gt 1000)/aggregate(Budget) + + + $apply=groupby((rollup(Customer/Country,Customer/Name),rollup(Product/ProductGroup/Name,Product/Name),Currency/Code),aggregate(Amount with sum as Total)) + + + $apply=groupby((rollup(SalesOrgHierarchy)),aggregate($count as SalesOrgCount)) + + + $apply=filter(Amount gt 3) + + + $apply=filter(Name eq $root/Products('P2')/Name) + + + $apply=search(coffee) + + + $apply=nest(Sales,filter(Amount gt 3)) + + + $apply=nest(Sales,filter(Amount gt 3) as FilteredSales) + + + $apply=nest(ShipTo,identity) + + + $apply=nest(Details,filter(Cost gt 1000)) + + + $apply=nest(Products,nest(Sales,filter(Amount gt 3))) + + + $apply=nest(Products,nest(Sales,filter(Amount gt 3)) as FilteredSales,nest(Suppliers,nest(Products,filter(true)))) + + + $apply=nest(Products,nest(Sales,filter(Amount gt 3)),nest(Suppliers,filter(true),nest(Products))) + + + $apply=nest(Products,nest(Sales,filter(Amount gt 3)) as FilteredSales,nest(Suppliers,nest(Products))) + + + $apply=nest(Sales,filter($this/Customer/City eq ShipTo/City)) + + + $apply=nest(groupby((Customer/ID)) as CustomerIDs,groupby((Product/ID)) as ProductIDs) + + + $apply=join(Sales as Sale) + + + $apply=join(Sales as Sale,filter(Customer/Country eq 'US')) + + + $apply=join(Details as Detail,filter(Cost gt 1000)) + + + $apply=join(ShipTo as Detail,identity) + + + $apply=outerjoin(Sales as Sale) + + + $apply=outerjoin(Sales as Sale,filter(Customer/Country eq 'FR')) + + + $filter=isdefined(Product) and isdefined(Customer/Name) and isdefined(Forecast) + + + $orderby=isdefined(Product) desc,Product asc + + + $apply=groupby((Customer),aggregate(Amount with sum as CustomerAmount))/compute(CustomerAmount div aggregate(CustomerAmount with sum) as RevenueContribution) + + + $apply=groupby((Customer,Year),aggregate(Amount with sum as CustomerYearAmount))/groupby((Customer),compute(CustomerYearAmount div aggregate(CustomerYearAmount with sum) as RevenueTrend)) + + + $apply=compute(Amount div aggregate(Amount with average) as RelativeOrderSize) + + + $apply=compute(Amount div aggregate(Budget) as RelativeAmount) + + + $apply=compute(Amount div aggregate(Product/Budget) as RelativeAmount) + + + $apply=compute(Amount div aggregate(Amount with sum from Time with average) as RelativeAmount) + + + $apply=compute(aggregate($count) as SalesCount) + + + $apply=groupby((Customer),compute(aggregate($count) as SalesCount))/filter(SalesCount ge 2)/aggregate(Amount with sum as TotalAmount) + + + $apply=groupby((Region),compute(aggregate(SalesNumber with average) as RegionAmount))/filter(RegionAmount gt 150)/concat(groupby((Region),aggregate(SalesNumber with average as RegionAmount)),aggregate(SalesNumber with average as TotalAmount)) + + + $filter=Products/any(p:aggregate(p/Sales/Amount with sum) gt 3) + + + + $crossjoin(Products_cj,Sales_cj)?$apply=filter(Products_cj/ID eq Sales_cj/ProductID)/groupby((Products_cj/Name),aggregate(Sales_cj/Amount with sum as Total)) + + + + $crossjoin(Products_cj,Time,Sales_cj)?$apply=groupby((Products_cj/Name,Time/Date),aggregate(Budget)) + + + $crossjoin(Products_cj,Time,Sales_cj)?$apply=groupby((Products_cj/Name,Time/Date),aggregate(Sales_cj/Forecast)) + + + Sales?$apply=groupby((Time/Month),aggregate(Forecast)) + + + $crossjoin(Time)?$apply=groupby((Time/Year),aggregate(Budget)) + + + SalesOrganizations?$filter=$it/Aggregation.isroot(Hierarchy='SalesOrgHierarchy') + + + SalesOrganizations?$filter=$it/Aggregation.isdescendant(Hierarchy='SalesOrgHierarchy',Node='EMEA') + + + SalesOrganizations?$filter=$it/Aggregation.isdescendant(Hierarchy='SalesOrgHierarchy',Node='EMEA',MaxDistance=1) + + + SalesOrganizations?$filter=$it/Aggregation.isancestor(Hierarchy='SalesOrgHierarchy',Node='EMEA') + + + SalesOrganizations?$filter=$it/Aggregation.isancestor(Hierarchy='SalesOrgHierarchy',Node='EMEA',MaxDistance=1) + + + SalesOrganizations?$filter=$it/Aggregation.issibling(Hierarchy='SalesOrgHierarchy',Node='EMEA') + + + SalesOrganizations?$filter=$it/Aggregation.isleaf(Hierarchy='SalesOrgHierarchy') + + + $apply=ancestors(SalesOrgHierarchy,filter(contains(Name,'East') or contains(Name,'Central'))) + + + $apply=ancestors(SalesOrgHierarchy,filter(contains(Name,'East') or contains(Name,'Central')), 2) + + + $apply=ancestors(SalesOrgHierarchy,filter(contains(Name,'East') or contains(Name,'Central')), keep start) + + + $apply=ancestors(SalesOrgHierarchy,filter(contains(Name,'East') or contains(Name,'Central')), 2, keep start) + + + $apply=descendants(SalesOrgHierarchy,filter(Name eq 'US')) + + + $apply=descendants(SalesOrgHierarchy,filter(Name eq 'US'), 3) + + + $apply=descendants(SalesOrgHierarchy,filter(Name eq 'US'),3,keep start) + + + $apply=traverse(SalesOrgHierarchy,preorder) + + + Customers?$apply=groupby((Name)) + + + $apply=groupby((Customer/Name)) + + + $apply=groupby((Customer/Name,Customer/ID)) + + + $apply=groupby((Customer/Name,Customer/ID,Product/Name)) + + + $apply=groupby((Name),aggregate(Sales(Amount with sum as Total))) + + + $apply=groupby((Country,Name),aggregate(Sales(Amount with sum as Total))) + + + $apply=groupby((Customer,Product/Name)) + + + $apply=groupby((Sales/Product/Name)) + + + $apply=groupby((Customer/Country),aggregate(Amount with average as AverageAmount)) + + + $apply=groupby((Name),aggregate(Sales/$count with sum as SalesCount)) + + + $apply=groupby((Name),aggregate(Sales/$count with sum as SalesCount,Sales(Amount with sum as Total))) + + + $apply=groupby((Name),aggregate(Sales($count as SalesCount),Sales(Amount with sum as Total))) + + + $apply=groupby((Name),aggregate(Sales/Amount with sum as Total)) + + + $apply=groupby((Customer/Country),aggregate(Amount with sum as Actual,Forecast)) + + + $apply=groupby((Name),aggregate(Sales(Amount with sum as Actual),Sales/Forecast)) + + + $apply=groupby((Customer/Country),aggregate(Amount with sum as Total,Amount with average as AvgAmt)) + + + $apply=groupby((Name),aggregate(Sales(Amount with sum as Total),Sales(Amount with average as AvgAmt))) + + + $apply=groupby((Amount),aggregate(Amount with sum as Total)) + + + $apply=concat(groupby((Customer/Country,Product/Name,Currency/Code),aggregate(Amount with sum as Total))/groupby((Customer/Country,Currency/Code),topcount(1,Total)),groupby((Customer/Country,Currency/Code),aggregate(Amount with sum as Total))) + + + $apply=groupby((Customer/Country,Product/Name,Currency/Code),topcount(2,Amount)/aggregate(Amount with sum as Total)) + + + $apply=compute(Amount mul Product/TaxRate as Tax) + + + $apply=compute(Amount mul Product/TaxRate as Tax, day(Time/Date) as WeekDay) + + + $apply=top(5) + + + $apply=skip(5) + + + $apply=orderby(Country asc,Name desc) + + + $apply=groupby((Customer/Country,Product/Name),aggregate(Amount with sum as Total))/groupby((Customer/Country),Self.TopCountAndBalance(Count=1,Property='Total')) + + + $apply=groupby((Product/ID,Product/Name,Time/Month),aggregate(Amount with sum as Total))/groupby((Product/ID,Product/Name),aggregate(Total with average as AverageAmount)) + + + $apply=groupby((Product/ID,Product/Name),aggregate(Amount with sum from Time/Month with average as MonthlyAverage)) + + + $apply=groupby((rollupall(Customer/Country,Customer/ID),Currency/Code),aggregate(Amount with sum from Customer/ID with average from Customer/Country with average as CustomerCountryAverage)) + + + $apply=filter(Amount le 1)/aggregate(Amount with sum as Total) + + + $apply=filter(Amount le 2)/groupby((Product/Name),aggregate(Forecast))&$filter=Total ge 4 + + + $apply=groupby((Time),aggregate(Amount with sum as Total))/aggregate(Total with average as DailyAverage) + + + $apply=compute(substring(Name,0,1) as FirstLetter)/groupby((FirstLetter),nest(identity as SomeCustomers)) + + + $apply=groupby((Continent/Name,Country/Name),aggregate(Population with sum as TotalPopulation)) + + + $apply=filter(Population ge 10000000)/groupby((Continent/Name,Country/Name),aggregate(Population with sum as TotalPopulation)) + + + $apply=groupby((Continent/Name,Country/Name),aggregate(Population with sum as CountryPopulation))/filter(CountryPopulation ge 10000000)/concat(identity,groupby((Continent/Name),aggregate(CountryPopulation with sum as TotalPopulation))) + + + $apply=groupby((Continent/Name,Country/Name),aggregate(Population with sum as CountryPopulation))/filter(CountryPopulation ge 10000000)/groupby((rollup(Continent/Name,Country/Name)),aggregate(CountryPopulation with sum as TotalPopulation)) + + + $apply=groupby((Continent/Name,Country/Name),aggregate(Population with sum as CountryPopulation))/concat(filter(CountryPopulation ge 10000000),groupby((Continent/Name),aggregate(CountryPopulation with sum as TotalPopulation))) + + + $apply=filter(Status eq 'incomplete')/nest(Items,filter(not Shipped))/groupby((Customer/Country),aggregate(Items(Amount with sum as ItemAmount))) + + + $apply=groupby((Customer/City),aggregate(Amount with sum as Total))/skip(10)/top(5) + + + $apply=groupby((Product/Name),aggregate(Amount with sum as Total))/orderby(Total desc) + + + $apply=join(Sales as TotalSales,aggregate(Amount with sum as Total))/groupby((Name,TotalSales/Total)) + + + $apply=outerjoin(Sales as TotalSales,aggregate(Amount with sum as Total))/groupby((Name,TotalSales/Total)) + + + $apply=descendants(SalesOrgHierarchy,filter(Name eq 'US'),keep start)/ancestors(SalesOrgHierarchy,filter(contains(Name,'East')),keep start)/traverse(SalesOrgHierarchy,preorder) + + + $apply=nest(Sales,filter(Product/Name eq 'Paper'))/groupby((rollup(SalesOrgHierarchy)),aggregate(Sales/$count with sum as PaperSalesCount)) + + + Categories?$expand=Products($apply=aggregate(Price with average as AveragePrice)) + + diff --git a/package.json b/package.json index 6ede9da..525268d 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "ABNF" ], "author": "", - "license": "ISC", + "license": "SEE LICENSE IN LICENSE.md", "bugs": { "url": "https://github.com/oasis-tcs/odata-abnf/issues" }, From 9d42aa71b7644f015ca7efcf59c260aedc1a3052 Mon Sep 17 00:00:00 2001 From: Ralf Handl Date: Fri, 27 Oct 2023 13:15:50 +0200 Subject: [PATCH 2/2] No trailing whitespace in ABNF files (#107) --- abnf/odata-abnf-construction-rules.txt | 556 ++++++++++++------------- abnf/odata-aggregation-abnf.txt | 18 +- abnf/odata-temporal-abnf.txt | 16 +- 3 files changed, 295 insertions(+), 295 deletions(-) diff --git a/abnf/odata-abnf-construction-rules.txt b/abnf/odata-abnf-construction-rules.txt index 992c43f..a29639e 100644 --- a/abnf/odata-abnf-construction-rules.txt +++ b/abnf/odata-abnf-construction-rules.txt @@ -19,7 +19,7 @@ ; - Michael Pizzo (mikep@microsoft.com), Microsoft ; - Martin Zurmuehl (martin.zurmuehl@sap.com), SAP SE ; -; Additional artifacts: +; Additional artifacts: ; This grammar is one component of a Work Product which consists of: ; - OData Version 4.01 Part 1: Protocol ; - OData Version 4.01 Part 2: URL Conventions @@ -37,27 +37,27 @@ ; Abstract: ; The Open Data Protocol (OData) enables the creation of REST-based data ; services, which allow resources, identified using Uniform Resource -; Identifiers (URLs) and defined in a data model, to be published and +; Identifiers (URLs) and defined in a data model, to be published and ; edited by Web clients using simple HTTP messages. This document defines -; the URL syntax for requests and the serialization format for primitive +; the URL syntax for requests and the serialization format for primitive ; literals in request and response payloads. ; ; Overview: -; This grammar uses the ABNF defined in RFC5234 and RFC7405. +; This grammar uses the ABNF defined in RFC5234 and RFC7405. ; ; The following rules assume that URIs have been percent-encoding normalized -; as described in section 6.2.2.2 of RFC3986 +; as described in section 6.2.2.2 of RFC3986 ; (http://tools.ietf.org/html/rfc3986#section-6.2.2.2) -; before applying the grammar to them, i.e. all characters in the unreserved +; before applying the grammar to them, i.e. all characters in the unreserved ; set (see rule "unreserved" below) are plain literals and NOT -; percent-encoded. +; percent-encoded. ; -; For characters outside the unreserved set the rules explicitly state +; For characters outside the unreserved set the rules explicitly state ; whether the percent-encoded representation is treated identical to the ; plain literal representation. -; -; One prominent example is the single quote that delimits OData primitive -; type literals: %27 and ' are treated identically, so a single quote within +; +; One prominent example is the single quote that delimits OData primitive +; type literals: %27 and ' are treated identically, so a single quote within ; a string literal is "encoded" as two consecutive single quotes in either ; literal or percent-encoded representation. ; @@ -78,16 +78,16 @@ ; ;------------------------------------------------------------------------------ -odataUri = serviceRoot [ odataRelativeUri ] +odataUri = serviceRoot [ odataRelativeUri ] -serviceRoot = ( "https" / "http" ) ; Note: case-insensitive +serviceRoot = ( "https" / "http" ) ; Note: case-insensitive "://" host [ ":" port ] "/" *( segment-nz "/" ) ; Note: dollar-prefixed path segments are case-sensitive! odataRelativeUri = %s"$batch" [ "?" batchOptions ] - / %s"$entity" "?" entityOptions - / %s"$entity" "/" optionallyQualifiedEntityTypeName "?" entityCastOptions + / %s"$entity" "?" entityOptions + / %s"$entity" "/" optionallyQualifiedEntityTypeName "?" entityCastOptions / %s"$metadata" [ "?" metadataOptions ] [ context ] / resourcePath [ "?" [ queryOptions ] ] @@ -96,15 +96,15 @@ odataRelativeUri = %s"$batch" [ "?" batchOptions ] ; 1. Resource Path ;------------------------------------------------------------------------------ -resourcePath = entitySetName [ collectionNavigation ] +resourcePath = entitySetName [ collectionNavigation ] / singletonEntity [ singleNavigation ] - / actionImportCall - / entityColFunctionImportCall [ collectionNavigation ] - / entityFunctionImportCall [ singleNavigation ] - / complexColFunctionImportCall [ complexColPath ] - / complexFunctionImportCall [ complexPath ] - / primitiveColFunctionImportCall [ collectionPath ] - / primitiveFunctionImportCall [ primitivePath ] + / actionImportCall + / entityColFunctionImportCall [ collectionNavigation ] + / entityFunctionImportCall [ singleNavigation ] + / complexColFunctionImportCall [ complexColPath ] + / complexFunctionImportCall [ complexPath ] + / primitiveColFunctionImportCall [ collectionPath ] + / primitiveFunctionImportCall [ primitivePath ] / functionImportCallNoParens [ querySegment ] / crossjoin [ querySegment ] / %s"$all" [ "/" optionallyQualifiedEntityTypeName ] @@ -134,8 +134,8 @@ singleNavigation = singleNavPath singleNavPath = "/" propertyPath / boundOperation - / ref - / value ; request the media resource of a media entity + / ref + / value ; request the media resource of a media entity / querySegment propertyPath = entityColNavigationProperty [ collectionNavigation ] @@ -150,16 +150,16 @@ collectionPath = count / boundOperation / ordinalIndex / querySegment primitivePath = value / boundOperation / querySegment -complexColPath = collectionPath +complexColPath = collectionPath / "/" optionallyQualifiedComplexTypeName [ collectionPath ] -complexPath = complexNavPath +complexPath = complexNavPath / "/" optionallyQualifiedComplexTypeName [ complexNavPath ] -complexNavPath = "/" propertyPath +complexNavPath = "/" propertyPath / boundOperation / querySegment - + filterInPath = %s"/$filter" OPEN boolCommonExpr CLOSE each = %s"/$each" @@ -171,16 +171,16 @@ querySegment = %s"/$query" ordinalIndex = "/" [ "-" ] 1*DIGIT -; boundOperation segments can only be composed if the type of the previous segment +; boundOperation segments can only be composed if the type of the previous segment ; matches the type of the first parameter of the action or function being called. ; Note that the rule name reflects the return type of the function. boundOperation = "/" ( boundActionCall - / boundEntityColFunctionCall [ collectionNavigation ] + / boundEntityColFunctionCall [ collectionNavigation ] / boundEntityFunctionCall [ singleNavigation ] - / boundComplexColFunctionCall [ complexColPath ] + / boundComplexColFunctionCall [ complexColPath ] / boundComplexFunctionCall [ complexPath ] - / boundPrimitiveColFunctionCall [ collectionPath ] - / boundPrimitiveFunctionCall [ primitivePath ] + / boundPrimitiveColFunctionCall [ collectionPath ] + / boundPrimitiveFunctionCall [ primitivePath ] / boundFunctionCallNoParens [ querySegment ] ) @@ -190,8 +190,8 @@ boundActionCall = [ namespace "." ] action ; and is specified by reference using the URI immediately preceding (to the left) of the boundActionCall ; The following boundXxxFunctionCall rules have the added restrictions that -; - the function MUST support binding, and -; - the binding parameter type MUST match the type of resource identified by the +; - the function MUST support binding, and +; - the binding parameter type MUST match the type of resource identified by the ; URI immediately preceding (to the left) of the boundXxxFunctionCall, and ; - the functionParameters MUST NOT include the bindingParameter. boundEntityFunctionCall = [ namespace "." ] entityFunction functionParameters @@ -225,7 +225,7 @@ functionImportCallNoParens = entityFunctionImport functionParameters = OPEN [ BWS functionParameter *( BWS COMMA BWS functionParameter ) ] BWS CLOSE functionParameter = parameterName EQ ( parameterAlias / primitiveLiteral ) parameterName = odataIdentifier -parameterAlias = AT odataIdentifier +parameterAlias = AT odataIdentifier crossjoin = %s"$crossjoin" OPEN entitySetName *( COMMA entitySetName ) @@ -236,77 +236,77 @@ crossjoin = %s"$crossjoin" OPEN ; 2. Query Options ;------------------------------------------------------------------------------ -queryOptions = queryOption *( "&" queryOption ) -queryOption = systemQueryOption - / aliasAndValue +queryOptions = queryOption *( "&" queryOption ) +queryOption = systemQueryOption + / aliasAndValue / nameAndValue - / customQueryOption + / customQueryOption -batchOptions = batchOption *( "&" batchOption ) +batchOptions = batchOption *( "&" batchOption ) batchOption = format - / customQueryOption - -metadataOptions = metadataOption *( "&" metadataOption ) + / customQueryOption + +metadataOptions = metadataOption *( "&" metadataOption ) metadataOption = format - / customQueryOption + / customQueryOption entityOptions = *( entityIdOption "&" ) id *( "&" entityIdOption ) entityIdOption = format / customQueryOption entityCastOptions = *( entityCastOption "&" ) id *( "&" entityCastOption ) entityCastOption = entityIdOption - / expand + / expand / select -id = ( "$id" / "id" ) EQ IRI-in-query +id = ( "$id" / "id" ) EQ IRI-in-query systemQueryOption = compute / deltatoken - / expand - / filter - / format + / expand + / filter + / format / id - / inlinecount - / orderby + / inlinecount + / orderby / schemaversion / search - / select - / skip + / select + / skip / skiptoken - / top + / top / index compute = ( "$compute" / "compute" ) EQ computeItem *( COMMA computeItem ) computeItem = commonExpr RWS "as" RWS computedProperty -computedProperty = odataIdentifier +computedProperty = odataIdentifier expand = ( "$expand" / "expand" ) EQ expandItem *( COMMA expandItem ) expandItem = "$value" / expandPath / optionallyQualifiedEntityTypeName "/" expandPath expandPath = ( STAR [ ref / OPEN levels CLOSE ] - / ( navigationProperty / entityAnnotationInQuery ) [ "/" optionallyQualifiedEntityTypeName ] + / ( navigationProperty / entityAnnotationInQuery ) [ "/" optionallyQualifiedEntityTypeName ] [ ref [ OPEN expandRefOption *( SEMI expandRefOption ) CLOSE ] / count [ OPEN expandCountOption *( SEMI expandCountOption ) CLOSE ] - / OPEN expandOption *( SEMI expandOption ) CLOSE - ] - / ( complexProperty / complexColProperty / optionallyQualifiedComplexTypeName / complexAnnotationInQuery ) "/" expandPath - / streamProperty + / OPEN expandOption *( SEMI expandOption ) CLOSE + ] + / ( complexProperty / complexColProperty / optionallyQualifiedComplexTypeName / complexAnnotationInQuery ) "/" expandPath + / streamProperty ) expandCountOption = filter / search expandRefOption = expandCountOption / orderby - / skip - / top + / skip + / top / inlinecount expandOption = expandRefOption - / select + / select / expand / compute / levels / aliasAndValue - + levels = ( "$levels" / "levels" ) EQ ( oneToNine *DIGIT / "max" ) filter = ( "$filter" / "filter" ) EQ boolCommonExpr @@ -321,12 +321,12 @@ index = ( "$index" / "index" ) EQ [ "-" ] 1*DIGIT format = ( "$format" / "format" ) EQ ( "atom" - / "json" + / "json" / "xml" / 1*pchar "/" 1*pchar ; or ; - + inlinecount = ( "$count" / "count" ) EQ booleanValue schemaversion = ( "$schemaversion" / "schemaversion" ) EQ ( STAR / 1*unreserved ) @@ -336,7 +336,7 @@ search = ( "$search" / "search" ) EQ BWS ( searchExpr / searchExpr-incomplet searchExpr = ( searchParenExpr / searchNegateExpr / searchPhrase - / searchWord + / searchWord ) [ searchOrExpr / searchAndExpr ] @@ -351,7 +351,7 @@ searchAndExpr = RWS [ %s"AND" RWS ] searchExpr searchPhrase = quotation-mark 1*( qchar-no-AMP-DQUOTE / SP ) quotation-mark -; A searchWord is a sequence of one or more non-whitespace characters, excluding +; A searchWord is a sequence of one or more non-whitespace characters, excluding ; - literal or percent-encoded parentheses "(", "%28", "%29", ")" ; - literal or percent-encoded double-quotes '"' and "%22" ; - the semicolon ";" @@ -366,34 +366,34 @@ searchExpr-incomplete = SQUOTE *( SQUOTE-in-string / qchar-no-AMP-SQUOTE / quota select = ( "$select" / "select" ) EQ selectItem *( COMMA selectItem ) selectItem = STAR - / allOperationsInSchema + / allOperationsInSchema / selectProperty - / optionallyQualifiedActionName - / optionallyQualifiedFunctionName - / ( optionallyQualifiedEntityTypeName / optionallyQualifiedComplexTypeName ) + / optionallyQualifiedActionName + / optionallyQualifiedFunctionName + / ( optionallyQualifiedEntityTypeName / optionallyQualifiedComplexTypeName ) "/" ( selectProperty - / optionallyQualifiedActionName - / optionallyQualifiedFunctionName + / optionallyQualifiedActionName + / optionallyQualifiedFunctionName ) selectProperty = primitiveProperty / primitiveAnnotationInQuery / ( primitiveColProperty / primitiveColAnnotationInQuery ) [ OPEN selectOptionPC *( SEMI selectOptionPC ) CLOSE ] / navigationProperty / selectPath [ OPEN selectOption *( SEMI selectOption ) CLOSE - / "/" selectProperty + / "/" selectProperty ] -selectPath = ( complexProperty / complexColProperty / complexAnnotationInQuery ) [ "/" optionallyQualifiedComplexTypeName ] -selectOptionPC = filter / search / inlinecount / orderby / skip / top +selectPath = ( complexProperty / complexColProperty / complexAnnotationInQuery ) [ "/" optionallyQualifiedComplexTypeName ] +selectOptionPC = filter / search / inlinecount / orderby / skip / top selectOption = selectOptionPC / compute / select / aliasAndValue -allOperationsInSchema = namespace "." STAR +allOperationsInSchema = namespace "." STAR ; The parameterNames uniquely identify the bound function overload ; Necessary only if it has overloads optionallyQualifiedActionName = [ namespace "." ] action optionallyQualifiedFunctionName = [ namespace "." ] function [ OPEN parameterNames CLOSE ] -; The names of all non-binding parameters, separated by commas +; The names of all non-binding parameters, separated by commas parameterNames = parameterName *( COMMA parameterName ) deltatoken = "$deltatoken" EQ 1*( qchar-no-AMP ) @@ -408,7 +408,7 @@ parameterValue = arrayOrObject / commonExpr customQueryOption = customName [ EQ customValue ] -customName = qchar-no-AMP-EQ-AT-DOLLAR *( qchar-no-AMP-EQ ) +customName = qchar-no-AMP-EQ-AT-DOLLAR *( qchar-no-AMP-EQ ) customValue = *( qchar-no-AMP ) complexAnnotationInQuery = annotationInQuery ; complex-valued annotation @@ -416,7 +416,7 @@ entityAnnotationInQuery = annotationInQuery ; entity-valued annotation primitiveAnnotationInQuery = annotationInQuery ; primitive-valued annotation primitiveColAnnotationInQuery = annotationInQuery ; primitive collection-valued annotation - + ;------------------------------------------------------------------------------ ; 3. Context URL Fragments ;------------------------------------------------------------------------------ @@ -433,20 +433,20 @@ contextFragment = %s"Collection($ref)" / entitySet [ selectList ] [ %s"/$entity" / %s"/$delta" ] entitySet = entitySetName *( containmentNavigation ) [ "/" qualifiedEntityTypeName ] - + containmentNavigation = keyPredicate [ "/" qualifiedEntityTypeName ] navigation -navigation = *( "/" complexProperty [ "/" qualifiedComplexTypeName ] ) "/" navigationProperty +navigation = *( "/" complexProperty [ "/" qualifiedComplexTypeName ] ) "/" navigationProperty selectList = OPEN [ selectListItem *( COMMA selectListItem ) ] CLOSE selectListItem = STAR ; all structural properties - / allOperationsInSchema - / [ ( qualifiedEntityTypeName / qualifiedComplexTypeName ) "/" ] + / allOperationsInSchema + / [ ( qualifiedEntityTypeName / qualifiedComplexTypeName ) "/" ] ( qualifiedActionName - / qualifiedFunctionName + / qualifiedFunctionName / selectListProperty ) -selectListProperty = primitiveProperty - / primitiveColProperty +selectListProperty = primitiveProperty + / primitiveColProperty / ( navigationProperty / entityAnnotationInFragment ) [ "+" ] [ selectList ] / ( complexProperty / complexColProperty / complexAnnotationInFragment ) [ "/" qualifiedComplexTypeName ] [ "/" selectListProperty ] @@ -457,7 +457,7 @@ contextPropertyPath = primitiveProperty qualifiedActionName = namespace "." action qualifiedFunctionName = namespace "." function [ OPEN parameterNames CLOSE ] - + complexAnnotationInFragment = annotationInFragment ; complex-valued annotation entityAnnotationInFragment = annotationInFragment ; entity-valued annotation @@ -465,38 +465,38 @@ entityAnnotationInFragment = annotationInFragment ; entity-valued annotation ; 4. Expressions ;------------------------------------------------------------------------------ -; Note: a boolCommonExpr is also a commonExpr, e.g. sort by Boolean +; Note: a boolCommonExpr is also a commonExpr, e.g. sort by Boolean commonExpr = ( primitiveLiteral / arrayOrObject / rootExpr / functionExpr - / negateExpr - / methodCallExpr - / parenExpr - / castExpr + / negateExpr + / methodCallExpr + / parenExpr + / castExpr / isofExpr / notExpr / firstMemberExpr - ) - [ addExpr - / subExpr - / mulExpr + ) + [ addExpr + / subExpr + / mulExpr / divExpr - / divbyExpr + / divbyExpr / modExpr - ] - [ eqExpr - / neExpr - / ltExpr - / leExpr - / gtExpr - / geExpr - / hasExpr - / inExpr ] - [ andExpr - / orExpr - ] + [ eqExpr + / neExpr + / ltExpr + / leExpr + / gtExpr + / geExpr + / hasExpr + / inExpr + ] + [ andExpr + / orExpr + ] boolCommonExpr = commonExpr ; resulting in a Boolean @@ -515,32 +515,32 @@ firstMemberExpr = memberExpr memberExpr = directMemberExpr / ( optionallyQualifiedEntityTypeName / optionallyQualifiedComplexTypeName ) "/" directMemberExpr - + directMemberExpr = propertyPathExpr - / boundFunctionExpr + / boundFunctionExpr / annotationExpr - -propertyPathExpr = ( entityColNavigationProperty [ collectionNavigationExpr ] - / entityNavigationProperty [ singleNavigationExpr ] + +propertyPathExpr = ( entityColNavigationProperty [ collectionNavigationExpr ] + / entityNavigationProperty [ singleNavigationExpr ] / complexColProperty [ complexColPathExpr ] - / complexProperty [ complexPathExpr ] + / complexProperty [ complexPathExpr ] / primitiveColProperty [ collectionPathExpr ] / primitiveProperty [ primitivePathExpr ] / streamProperty [ primitivePathExpr ] ) - + annotationExpr = annotationInQuery [ collectionPathExpr / singleNavigationExpr / complexPathExpr / primitivePathExpr ] - + annotationInQuery = AT [ namespace "." ] termName [ HASH annotationQualifier ] annotationInFragment = AT [ namespace "." ] termName [ "#" annotationQualifier ] -annotationQualifier = odataIdentifier - -inscopeVariableExpr = implicitVariableExpr +annotationQualifier = odataIdentifier + +inscopeVariableExpr = implicitVariableExpr / parameterAlias / lambdaVariableExpr ; only allowed inside a lambdaPredicateExpr implicitVariableExpr = %s"$it" ; the current instance of the resource identified by the resource path @@ -560,29 +560,29 @@ filterExpr = %s"/$filter" OPEN boolCommonExpr CLOSE complexColPathExpr = collectionPathExpr / "/" optionallyQualifiedComplexTypeName [ collectionPathExpr ] - + collectionPathExpr = count [ OPEN expandCountOption *( SEMI expandCountOption ) CLOSE ] / filterExpr [ collectionPathExpr ] / "/" anyExpr / "/" allExpr / "/" boundFunctionExpr / "/" annotationExpr - + complexPathExpr = "/" directMemberExpr / "/" optionallyQualifiedComplexTypeName [ "/" directMemberExpr ] primitivePathExpr = "/" [ annotationExpr / boundFunctionExpr ] -boundFunctionExpr = functionExpr ; boundFunction segments can only be composed if the type of the +boundFunctionExpr = functionExpr ; boundFunction segments can only be composed if the type of the ; previous segment matches the type of the first function parameter - + functionExpr = [ namespace "." ] - ( entityColFunction functionExprParameters [ collectionNavigationExpr ] - / entityFunction functionExprParameters [ singleNavigationExpr ] + ( entityColFunction functionExprParameters [ collectionNavigationExpr ] + / entityFunction functionExprParameters [ singleNavigationExpr ] / complexColFunction functionExprParameters [ complexColPathExpr ] - / complexFunction functionExprParameters [ complexPathExpr ] - / primitiveColFunction functionExprParameters [ collectionPathExpr ] - / primitiveFunction functionExprParameters [ primitivePathExpr ] + / complexFunction functionExprParameters [ complexPathExpr ] + / primitiveColFunction functionExprParameters [ collectionPathExpr ] + / primitiveFunction functionExprParameters [ primitivePathExpr ] ) functionExprParameters = OPEN [ BWS functionExprParameter *( BWS COMMA BWS functionExprParameter ) ] BWS CLOSE @@ -592,29 +592,29 @@ anyExpr = "any" OPEN BWS [ lambdaVariableExpr BWS COLON BWS lambdaPredicateExpr allExpr = "all" OPEN BWS lambdaVariableExpr BWS COLON BWS lambdaPredicateExpr BWS CLOSE lambdaPredicateExpr = boolCommonExpr ; containing at least one lambdaVariableExpr -methodCallExpr = indexOfMethodCallExpr - / toLowerMethodCallExpr - / toUpperMethodCallExpr - / trimMethodCallExpr - / substringMethodCallExpr - / concatMethodCallExpr - / lengthMethodCallExpr +methodCallExpr = indexOfMethodCallExpr + / toLowerMethodCallExpr + / toUpperMethodCallExpr + / trimMethodCallExpr + / substringMethodCallExpr + / concatMethodCallExpr + / lengthMethodCallExpr / matchesPatternMethodCallExpr - / yearMethodCallExpr - / monthMethodCallExpr - / dayMethodCallExpr - / hourMethodCallExpr - / minuteMethodCallExpr - / secondMethodCallExpr + / yearMethodCallExpr + / monthMethodCallExpr + / dayMethodCallExpr + / hourMethodCallExpr + / minuteMethodCallExpr + / secondMethodCallExpr / fractionalsecondsMethodCallExpr / totalsecondsMethodCallExpr - / dateMethodCallExpr - / timeMethodCallExpr - / roundMethodCallExpr - / floorMethodCallExpr - / ceilingMethodCallExpr - / distanceMethodCallExpr - / geoLengthMethodCallExpr + / dateMethodCallExpr + / timeMethodCallExpr + / roundMethodCallExpr + / floorMethodCallExpr + / ceilingMethodCallExpr + / distanceMethodCallExpr + / geoLengthMethodCallExpr / totalOffsetMinutesMethodCallExpr / minDateTimeMethodCallExpr / maxDateTimeMethodCallExpr @@ -622,10 +622,10 @@ methodCallExpr = indexOfMethodCallExpr / caseMethodCallExpr / boolMethodCallExpr -boolMethodCallExpr = endsWithMethodCallExpr - / startsWithMethodCallExpr - / containsMethodCallExpr - / intersectsMethodCallExpr +boolMethodCallExpr = endsWithMethodCallExpr + / startsWithMethodCallExpr + / containsMethodCallExpr + / intersectsMethodCallExpr / hasSubsetMethodCallExpr / hasSubsequenceMethodCallExpr @@ -651,7 +651,7 @@ fractionalsecondsMethodCallExpr = "fractionalseconds" OPEN BWS commonExpr BWS totalsecondsMethodCallExpr = "totalseconds" OPEN BWS commonExpr BWS CLOSE dateMethodCallExpr = "date" OPEN BWS commonExpr BWS CLOSE timeMethodCallExpr = "time" OPEN BWS commonExpr BWS CLOSE -totalOffsetMinutesMethodCallExpr = "totaloffsetminutes" OPEN BWS commonExpr BWS CLOSE +totalOffsetMinutesMethodCallExpr = "totaloffsetminutes" OPEN BWS commonExpr BWS CLOSE minDateTimeMethodCallExpr = "mindatetime" OPEN BWS CLOSE maxDateTimeMethodCallExpr = "maxdatetime" OPEN BWS CLOSE @@ -668,7 +668,7 @@ intersectsMethodCallExpr = "geo.intersects" OPEN BWS commonExpr BWS COMMA BWS co hasSubsetMethodCallExpr = "hassubset" OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE hasSubsequenceMethodCallExpr = "hassubsequence" OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE -caseMethodCallExpr = "case" OPEN BWS boolCommonExpr BWS COLON BWS commonExpr BWS +caseMethodCallExpr = "case" OPEN BWS boolCommonExpr BWS COLON BWS commonExpr BWS *( COMMA BWS boolCommonExpr BWS COLON BWS commonExpr BWS ) CLOSE parenExpr = OPEN BWS commonExpr BWS CLOSE @@ -677,7 +677,7 @@ listExpr = OPEN BWS [ primitiveLiteral BWS *( COMMA BWS primitiveLiteral BWS ) andExpr = RWS "and" RWS boolCommonExpr orExpr = RWS "or" RWS boolCommonExpr -eqExpr = RWS "eq" RWS commonExpr +eqExpr = RWS "eq" RWS commonExpr neExpr = RWS "ne" RWS commonExpr ltExpr = RWS "lt" RWS commonExpr leExpr = RWS "le" RWS commonExpr @@ -712,8 +712,8 @@ castExpr = "cast" OPEN BWS [ commonExpr BWS COMMA BWS ] optionallyQualifiedTypeN arrayOrObject = array / object -array = begin-array - [ valueInUrl *( value-separator valueInUrl ) ] +array = begin-array + [ valueInUrl *( value-separator valueInUrl ) ] end-array object = begin-object @@ -723,13 +723,13 @@ object = begin-object member = stringInUrl name-separator valueInUrl valueInUrl = stringInUrl - / commonExpr + / commonExpr -; JSON syntax: adapted to URI restrictions from [RFC8259] +; JSON syntax: adapted to URI restrictions from [RFC8259] begin-object = BWS ( "{" / "%7B" ) BWS end-object = BWS ( "}" / "%7D" ) -begin-array = BWS ( "[" / "%5B" ) BWS +begin-array = BWS ( "[" / "%5B" ) BWS end-array = BWS ( "]" / "%5D" ) quotation-mark = DQUOTE / "%22" @@ -738,12 +738,12 @@ value-separator = BWS COMMA BWS stringInUrl = quotation-mark *charInJSON quotation-mark -charInJSON = qchar-unescaped - / qchar-JSON-special - / escape ( quotation-mark +charInJSON = qchar-unescaped + / qchar-JSON-special + / escape ( quotation-mark / escape / ( "/" / "%2F" ) ; solidus U+002F - literal form is allowed in the query part of a URL - / %s"b" ; backspace U+0008 + / %s"b" ; backspace U+0008 / %s"f" ; form feed U+000C / %s"n" ; line feed U+000A / %s"r" ; carriage return U+000D @@ -760,28 +760,28 @@ escape = "\" / "%5C" ; reverse solidus U+005C ; 6. Names and identifiers ;------------------------------------------------------------------------------ -qualifiedTypeName = singleQualifiedTypeName +qualifiedTypeName = singleQualifiedTypeName / %s"Collection" OPEN singleQualifiedTypeName CLOSE -optionallyQualifiedTypeName = singleQualifiedTypeName +optionallyQualifiedTypeName = singleQualifiedTypeName / %s"Collection" OPEN singleQualifiedTypeName CLOSE / singleTypeName / %s"Collection" OPEN singleTypeName CLOSE -singleQualifiedTypeName = qualifiedEntityTypeName +singleQualifiedTypeName = qualifiedEntityTypeName / qualifiedComplexTypeName / qualifiedTypeDefinitionName / qualifiedEnumTypeName - / primitiveTypeName - -singleTypeName = entityTypeName - / complexTypeName - / typeDefinitionName - / enumerationTypeName - + / primitiveTypeName + +singleTypeName = entityTypeName + / complexTypeName + / typeDefinitionName + / enumerationTypeName + qualifiedEntityTypeName = namespace "." entityTypeName qualifiedComplexTypeName = namespace "." complexTypeName -qualifiedTypeDefinitionName = namespace "." typeDefinitionName +qualifiedTypeDefinitionName = namespace "." typeDefinitionName qualifiedEnumTypeName = namespace "." enumerationTypeName optionallyQualifiedEntityTypeName = [ namespace "." ] entityTypeName @@ -792,10 +792,10 @@ namespace = namespacePart *( "." namespacePart ) namespacePart = odataIdentifier entitySetName = odataIdentifier -singletonEntity = odataIdentifier +singletonEntity = odataIdentifier entityTypeName = odataIdentifier complexTypeName = odataIdentifier -typeDefinitionName = odataIdentifier +typeDefinitionName = odataIdentifier enumerationTypeName = odataIdentifier enumerationMember = odataIdentifier termName = odataIdentifier @@ -808,12 +808,12 @@ identifierCharacter = ALPHA / "_" / DIGIT ; plus percent-encoded Unicode primitiveTypeName = %s"Edm." ( %s"Binary" / %s"Boolean" / %s"Byte" - / %s"Date" + / %s"Date" / %s"DateTimeOffset" / %s"Decimal" / %s"Double" - / %s"Duration" - / %s"Guid" + / %s"Duration" + / %s"Guid" / %s"Int16" / %s"Int32" / %s"Int64" @@ -822,7 +822,7 @@ primitiveTypeName = %s"Edm." ( %s"Binary" / %s"Stream" / %s"String" / %s"TimeOfDay" - / abstractSpatialTypeName [ concreteSpatialTypeName ] + / abstractSpatialTypeName [ concreteSpatialTypeName ] ) abstractSpatialTypeName = %s"Geography" / %s"Geometry" @@ -842,20 +842,20 @@ complexProperty = odataIdentifier complexColProperty = odataIdentifier streamProperty = odataIdentifier -navigationProperty = entityNavigationProperty / entityColNavigationProperty +navigationProperty = entityNavigationProperty / entityColNavigationProperty entityNavigationProperty = odataIdentifier entityColNavigationProperty = odataIdentifier action = odataIdentifier actionImport = odataIdentifier -function = entityFunction - / entityColFunction - / complexFunction - / complexColFunction - / primitiveFunction +function = entityFunction + / entityColFunction + / complexFunction + / complexColFunction + / primitiveFunction / primitiveColFunction - + entityFunction = odataIdentifier entityColFunction = odataIdentifier complexFunction = odataIdentifier @@ -877,43 +877,43 @@ primitiveColFunctionImport = odataIdentifier ; in URLs primitiveLiteral = nullValue ; plain values up to int64Value - / booleanValue - / guidValue - / dateTimeOffsetValueInUrl + / booleanValue + / guidValue + / dateTimeOffsetValueInUrl / dateValue / timeOfDayValueInUrl - / decimalValue - / doubleValue - / singleValue - / sbyteValue + / decimalValue + / doubleValue + / singleValue + / sbyteValue / byteValue - / int16Value - / int32Value - / int64Value + / int16Value + / int32Value + / int64Value / string ; single-quoted / duration / enum - / binary ; all others are quoted and prefixed - / geographyCollection - / geographyLineString - / geographyMultiLineString - / geographyMultiPoint - / geographyMultiPolygon - / geographyPoint - / geographyPolygon - / geometryCollection - / geometryLineString - / geometryMultiLineString - / geometryMultiPoint - / geometryMultiPolygon - / geometryPoint + / binary ; all others are quoted and prefixed + / geographyCollection + / geographyLineString + / geographyMultiLineString + / geographyMultiPoint + / geographyMultiPolygon + / geographyPoint + / geographyPolygon + / geometryCollection + / geometryLineString + / geometryMultiLineString + / geometryMultiPoint + / geometryMultiPolygon + / geometryPoint / geometryPolygon - -; in Atom and JSON message bodies and CSDL DefaultValue attributes + +; in Atom and JSON message bodies and CSDL DefaultValue attributes primitiveValue = booleanValue / guidValue / durationValue - / dateTimeOffsetValue + / dateTimeOffsetValue / dateValue / timeOfDayValue / enumValue @@ -924,22 +924,22 @@ primitiveValue = booleanValue / fullMultiPolygonLiteral / fullPointLiteral / fullPolygonLiteral - / decimalValue - / doubleValue - / singleValue - / sbyteValue + / decimalValue + / doubleValue + / singleValue + / sbyteValue / byteValue - / int16Value - / int32Value - / int64Value - / binaryValue + / int16Value + / int32Value + / int64Value + / binaryValue ; also valid are: ; - any XML string for strings in Atom and CSDL documents - ; - any JSON string for JSON documents + ; - any JSON string for JSON documents -nullValue = %s"null" +nullValue = %s"null" -; base64url encoding according to http://tools.ietf.org/html/rfc4648#section-5 +; base64url encoding according to http://tools.ietf.org/html/rfc4648#section-5 binary = "binary" SQUOTE binaryValue SQUOTE binaryValue = *(4base64char) [ base64b16 / base64b8 ] base64b16 = 2base64char ( %s"A" / %s"E" / %s"I" / %s"M" / %s"Q" / %s"U" / %s"Y" / %s"c" / %s"g" / %s"k" / %s"o" / %s"s" / %s"w" / %s"0" / %s"4" / %s"8" ) [ "=" ] @@ -953,11 +953,11 @@ doubleValue = decimalValue ; IEEE 754 binary64 floating-point number (15-17 dec singleValue = decimalValue ; IEEE 754 binary32 floating-point number (6-9 decimal digits) nanInfinity = %s"NaN" / %s"-INF" / %s"INF" -guidValue = 8HEXDIG "-" 4HEXDIG "-" 4HEXDIG "-" 4HEXDIG "-" 12HEXDIG +guidValue = 8HEXDIG "-" 4HEXDIG "-" 4HEXDIG "-" 4HEXDIG "-" 12HEXDIG byteValue = 1*3DIGIT ; numbers in the range from 0 to 255 sbyteValue = [ SIGN ] 1*3DIGIT ; numbers in the range from -128 to 127 -int16Value = [ SIGN ] 1*5DIGIT ; numbers in the range from -32768 to 32767 +int16Value = [ SIGN ] 1*5DIGIT ; numbers in the range from -32768 to 32767 int32Value = [ SIGN ] 1*10DIGIT ; numbers in the range from -2147483648 to 2147483647 int64Value = [ SIGN ] 1*19DIGIT ; numbers in the range from -9223372036854775808 to 9223372036854775807 @@ -976,8 +976,8 @@ durationValue = [ SIGN ] "P" [ 1*DIGIT "D" ] [ "T" [ 1*DIGIT "H" ] [ 1*DIGIT "M" timeOfDayValue = hour ":" minute [ ":" second [ "." fractionalSeconds ] ] timeOfDayValueInUrl = hour COLON minute [ COLON second [ "." fractionalSeconds ] ] - -oneToNine = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" + +oneToNine = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" zeroToFiftyNine = ( "0" / "1" / "2" / "3" / "4" / "5" ) DIGIT year = [ "-" ] ( "0" 3DIGIT / oneToNine 3*DIGIT ) month = "0" oneToNine @@ -986,7 +986,7 @@ day = "0" oneToNine / ( "1" / "2" ) DIGIT / "3" ( "0" / "1" ) hour = ( "0" / "1" ) DIGIT - / "2" ( "0" / "1" / "2" / "3" ) + / "2" ( "0" / "1" / "2" / "3" ) minute = zeroToFiftyNine second = zeroToFiftyNine / "60" ; for leap seconds fractionalSeconds = 1*12DIGIT @@ -1037,7 +1037,7 @@ polygonLiteral = "Polygon" polygonData polygonData = OPEN ringLiteral *( COMMA ringLiteral ) CLOSE ringLiteral = OPEN positionLiteral *( COMMA positionLiteral ) CLOSE ; Within each ringLiteral, the first and last positionLiteral elements MUST be an exact syntactic match to each other. - ; Within the polygonData, the ringLiterals MUST specify their points in appropriate winding order. + ; Within the polygonData, the ringLiterals MUST specify their points in appropriate winding order. ; In order of traversal, points to the left side of the ring are interpreted as being in the polygon. geometryCollection = geometryPrefix SQUOTE fullCollectionLiteral SQUOTE @@ -1049,7 +1049,7 @@ geometryPoint = geometryPrefix SQUOTE fullPointLiteral SQUOT geometryPolygon = geometryPrefix SQUOTE fullPolygonLiteral SQUOTE geographyPrefix = "geography" -geometryPrefix = "geometry" +geometryPrefix = "geometry" ;------------------------------------------------------------------------------ @@ -1059,26 +1059,26 @@ geometryPrefix = "geometry" header = asyncresult / content-id / isolation - / odata-entityid + / odata-entityid / odata-error / odata-maxversion / odata-version / prefer -asyncresult = "AsyncResult" ":" OWS 3DIGIT +asyncresult = "AsyncResult" ":" OWS 3DIGIT content-id = "Content-ID" ":" OWS request-id isolation = [ "OData-" ] "Isolation" ":" OWS "snapshot" request-id = 1*unreserved - + odata-entityid = "OData-EntityID" ":" OWS IRI-in-header ; Note: the header value is a JSON object restricted to characters allowed in a header odata-error = "OData-Error" ":" OWS "{" DQUOTE %s"code" DQUOTE ":" *( VCHAR / SP ) -odata-maxversion = "OData-MaxVersion" ":" OWS 1*DIGIT "." 1*DIGIT +odata-maxversion = "OData-MaxVersion" ":" OWS 1*DIGIT "." 1*DIGIT odata-version = "OData-Version" ":" OWS "4.0" [ oneToNine ] - -prefer = "Prefer" ":" OWS preference *( OWS "," OWS preference ) + +prefer = "Prefer" ":" OWS preference *( OWS "," OWS preference ) preference = allowEntityReferencesPreference / callbackPreference / continueOnErrorPreference @@ -1091,25 +1091,25 @@ preference = allowEntityReferencesPreference / waitPreference ; and everything allowed by https://tools.ietf.org/html/rfc7240 ; / ( parameter / token ) *( OWS ";" [ OWS ( parameter / token ) ] ) - + allowEntityReferencesPreference = [ "odata." ] "allow-entityreferences" callbackPreference = [ "odata." ] "callback" OWS ";" OWS "url" EQ-h DQUOTE URI DQUOTE -continueOnErrorPreference = [ "odata." ] "continue-on-error" [ EQ-h booleanValue ] - +continueOnErrorPreference = [ "odata." ] "continue-on-error" [ EQ-h booleanValue ] + includeAnnotationsPreference = [ "odata." ] "include-annotations" EQ-h DQUOTE annotationsList DQUOTE annotationsList = annotationIdentifier *("," annotationIdentifier) annotationIdentifier = [ excludeOperator ] - ( STAR - / namespace "." ( termName / STAR ) - ) + ( STAR + / namespace "." ( termName / STAR ) + ) [ "#" odataIdentifier ] excludeOperator = "-" - -maxpagesizePreference = [ "odata." ] "maxpagesize" EQ-h oneToNine *DIGIT -omitValuesPreference = "omit-values" EQ-h ( "nulls" / "defaults" ) +maxpagesizePreference = [ "odata." ] "maxpagesize" EQ-h oneToNine *DIGIT + +omitValuesPreference = "omit-values" EQ-h ( "nulls" / "defaults" ) respondAsyncPreference = "respond-async" @@ -1129,17 +1129,17 @@ waitPreference = "wait" EQ-h 1*DIGIT obs-text = %x80-FF ;quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) -OWS = *( SP / HTAB ) ; "optional" whitespace -BWS-h = *( SP / HTAB ) ; "bad" whitespace in header values -EQ-h = BWS-h EQ BWS-h +OWS = *( SP / HTAB ) ; "optional" whitespace +BWS-h = *( SP / HTAB ) ; "bad" whitespace in header values +EQ-h = BWS-h EQ BWS-h ;------------------------------------------------------------------------------ ; 9. Punctuation ;------------------------------------------------------------------------------ -RWS = 1*( SP / HTAB / "%20" / "%09" ) ; "required" whitespace -BWS = *( SP / HTAB / "%20" / "%09" ) ; "bad" whitespace +RWS = 1*( SP / HTAB / "%20" / "%09" ) ; "required" whitespace +BWS = *( SP / HTAB / "%20" / "%09" ) ; "bad" whitespace AT = "@" / "%40" COLON = ":" / "%3A" @@ -1221,7 +1221,7 @@ sub-delims = "$" / "&" / "'" / "=" other-delims = "!" / "(" / ")" / "*" / "+" / "," / ";" pchar-no-SQUOTE = unreserved / pct-encoded-no-SQUOTE / other-delims / "$" / "&" / "=" / ":" / "@" -pct-encoded-no-SQUOTE = "%" ( "0" / "1" / "3" / "4" / "5" / "6" / "8" / "9" / A-to-F ) HEXDIG +pct-encoded-no-SQUOTE = "%" ( "0" / "1" / "3" / "4" / "5" / "6" / "8" / "9" / A-to-F ) HEXDIG / "%" "2" ( "0" / "1" / "2" / "3" / "4" / "5" / "6" / "8" / "9" / A-to-F ) qchar-no-AMP = unreserved / pct-encoded / other-delims / ":" / "@" / "/" / "?" / "$" / "'" / "=" @@ -1231,14 +1231,14 @@ qchar-no-AMP-SQUOTE = unreserved / pct-encoded / other-delims / qchar-no-AMP-DQUOTE = unreserved / pct-encoded-no-DQUOTE / other-delims / ":" / "@" / "/" / "?" / "$" / "'" / "=" qchar-unescaped = unreserved / pct-encoded-unescaped / other-delims / ":" / "@" / "/" / "?" / "$" / "'" / "=" -pct-encoded-unescaped = "%" ( "0" / "1" / "3" / "4" / "6" / "7" / "8" / "9" / A-to-F ) HEXDIG - / "%" "2" ( "0" / "1" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / A-to-F ) +pct-encoded-unescaped = "%" ( "0" / "1" / "3" / "4" / "6" / "7" / "8" / "9" / A-to-F ) HEXDIG + / "%" "2" ( "0" / "1" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / A-to-F ) / "%" "5" ( DIGIT / "A" / "B" / "D" / "E" / "F" ) -pct-encoded-no-DQUOTE = "%" ( "0" / "1" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / A-to-F ) HEXDIG - / "%" "2" ( "0" / "1" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / A-to-F ) - - +pct-encoded-no-DQUOTE = "%" ( "0" / "1" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / A-to-F ) HEXDIG + / "%" "2" ( "0" / "1" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / A-to-F ) + + ;------------------------------------------------------------------------------ ; B. IRI syntax [RFC3987] ;------------------------------------------------------------------------------ @@ -1248,27 +1248,27 @@ pct-encoded-no-DQUOTE = "%" ( "0" / "1" / "3" / "4" / "5" / "6" / "7" / "8" / IRI-in-header = 1*( VCHAR / obs-text ) IRI-in-query = 1*qchar-no-AMP - + ;------------------------------------------------------------------------------ ; C. ABNF core definitions [RFC5234] ;------------------------------------------------------------------------------ -ALPHA = %x41-5A / %x61-7A -DIGIT = %x30-39 +ALPHA = %x41-5A / %x61-7A +DIGIT = %x30-39 HEXDIG = DIGIT / A-to-F -A-to-F = "A" / "B" / "C" / "D" / "E" / "F" +A-to-F = "A" / "B" / "C" / "D" / "E" / "F" DQUOTE = %x22 -SP = %x20 -HTAB = %x09 -;WSP = SP / HTAB -;LWSP = *(WSP / CRLF WSP) -VCHAR = %x21-7E +SP = %x20 +HTAB = %x09 +;WSP = SP / HTAB +;LWSP = *(WSP / CRLF WSP) +VCHAR = %x21-7E ;CHAR = %x01-7F -;LOCTET = %x00-FF -;CR = %x0D -;LF = %x0A +;LOCTET = %x00-FF +;CR = %x0D +;LF = %x0A ;CRLF = CR LF -;BIT = "0" / "1" +;BIT = "0" / "1" ;------------------------------------------------------------------------------ diff --git a/abnf/odata-aggregation-abnf.txt b/abnf/odata-aggregation-abnf.txt index 581ffb7..2d9598e 100644 --- a/abnf/odata-aggregation-abnf.txt +++ b/abnf/odata-aggregation-abnf.txt @@ -20,7 +20,7 @@ ; - Martin Zurmuehl (martin.zurmuehl@sap.com), SAP SE ; - Heiko Theissen (heiko.theissen@sap.com), SAP SE ; -; Additional artifacts: +; Additional artifacts: ; This grammar is one component of a Work Product which consists of: ; - OData Extension for Data Aggregation Version 4.0 ; - OData Aggregation Vocabulary @@ -48,14 +48,14 @@ ; of the base principles of OData. ; ; Overview: -; This grammar uses the ABNF defined in RFC5234 and RFC7405. +; This grammar uses the ABNF defined in RFC5234 and RFC7405. ; ; It extends the OData ABNF Construction Rules Version 4.01 ; ; Contents: ; 1. New alternatives for OData ABNF Construction Rules ; 2. System Query Option $apply -; 3. Extensions to $filter +; 3. Extensions to $filter ; ;------------------------------------------------------------------------------ @@ -135,9 +135,9 @@ aggregateCustom = [ ( aggrPathPrefix / aggrCastPath ) "/" ] customAggregate asAlias = RWS %s"as" RWS expressionAlias expressionAlias = odataIdentifier - + customAggregate = odataIdentifier - + ; Three flavors of data aggregation paths are defined now: ; - one for use in aggregate, whose segments can be single- or collection-valued (rules with prefix aggr) ; - one for use in groupby, whose segments must be single-valued (rules with prefix sngl) @@ -164,8 +164,8 @@ collectionExpr = commonExpr ; but where every firstMemberExpr must be a curr currCollectionExpr = %s"$these" collectionPathExpr computeTrafo = %s"compute" OPEN BWS computeExpr *( BWS COMMA BWS computeExpr ) BWS CLOSE -computeExpr = commonExpr asAlias - +computeExpr = commonExpr asAlias + bottomcountTrafo = %s"bottomcount" OPEN BWS collectionExpr BWS COMMA BWS commonExpr BWS CLOSE bottompercentTrafo = %s"bottompercent" OPEN BWS collectionExpr BWS COMMA BWS commonExpr BWS CLOSE bottomsumTrafo = %s"bottomsum" OPEN BWS collectionExpr BWS COMMA BWS commonExpr BWS CLOSE @@ -182,8 +182,8 @@ nestApplyExpr = applyExpr asAlias *( BWS COMMA BWS applyExpr asAlias ) joinTrafo = %s"join" OPEN BWS joinProperty asAlias [ BWS COMMA BWS applyExpr ] BWS CLOSE outerjoinTrafo = %s"outerjoin" OPEN BWS joinProperty asAlias [ BWS COMMA BWS applyExpr ] BWS CLOSE -joinProperty = ( complexColProperty - / complexAnnotationInQuery ; must be collection-valued +joinProperty = ( complexColProperty + / complexAnnotationInQuery ; must be collection-valued / entityColNavigationProperty [ "/" optionallyQualifiedEntityTypeName ] / entityAnnotationInQuery ; must be collection-valued ) diff --git a/abnf/odata-temporal-abnf.txt b/abnf/odata-temporal-abnf.txt index e863460..03181a3 100644 --- a/abnf/odata-temporal-abnf.txt +++ b/abnf/odata-temporal-abnf.txt @@ -20,7 +20,7 @@ ; - Michael Pizzo (mikep@microsoft.com), Microsoft ; - Martin Zurmuehl (martin.zurmuehl@sap.com), SAP SE ; -; Additional artifacts: +; Additional artifacts: ; This grammar is one component of a Work Product which consists of: ; - OData Extension for Temporal Data Version 4.0 ; - OData Temporal ABNF Construction Rules Version 4.0 @@ -48,13 +48,13 @@ ; using the Open Data Protocol (OData). ; ; Overview: -; This grammar uses the ABNF defined in RFC5234 and RFC7405. +; This grammar uses the ABNF defined in RFC5234 and RFC7405. ; ; It extends the OData ABNF Construction Rules Version 4.01 ; ; Contents: ; 1. New alternatives for OData ABNF Construction Rules -; 2. Temporal System Query Options +; 2. Temporal System Query Options ; ;------------------------------------------------------------------------------ @@ -67,8 +67,8 @@ systemQueryOption =/ temporalOption expandOption =/ temporalOption temporalOption = at-option - / from - / to + / from + / to / toInclusive @@ -81,9 +81,9 @@ from = "$from" EQ temporalExpr to = "$to" EQ temporalExpr toInclusive = "$toInclusive" EQ temporalExpr -temporalExpr = "min" - / "max" - / commonExpr ; evaluating to a dateValue or dateTimeOffsetValue +temporalExpr = "min" + / "max" + / commonExpr ; evaluating to a dateValue or dateTimeOffsetValue ;------------------------------------------------------------------------------