From 098b27330a99da68a362eeb3cf30f44075ccbd91 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 1 Feb 2021 09:37:06 +0100 Subject: [PATCH] New home of odata2cqn --- odata/.npmrc | 1 + odata/.vscode/extensions.json | 15 + odata/etc/odata-url.abnf | 1189 +++++++++++++++++++++++++++++++++ odata/etc/odata-url.pegjs | 1162 ++++++++++++++++++++++++++++++++ odata/lib/odata2cqn.pegjs | 690 +++++-------------- odata/package.json | 6 +- odata/test/odata2cqn.test.js | 34 +- 7 files changed, 2548 insertions(+), 549 deletions(-) create mode 100644 odata/.npmrc create mode 100644 odata/.vscode/extensions.json create mode 100644 odata/etc/odata-url.abnf create mode 100644 odata/etc/odata-url.pegjs diff --git a/odata/.npmrc b/odata/.npmrc new file mode 100644 index 00000000..b71495a0 --- /dev/null +++ b/odata/.npmrc @@ -0,0 +1 @@ +@sap:registry=http://nexus.wdf.sap.corp:8081/nexus/repository/build.milestones.npm/ diff --git a/odata/.vscode/extensions.json b/odata/.vscode/extensions.json new file mode 100644 index 00000000..8f824cad --- /dev/null +++ b/odata/.vscode/extensions.json @@ -0,0 +1,15 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "SirTobi.pegjs-language", + "tamuratak.vscode-pegjs", + "joeandaverde.vscode-pegjs-live" + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [ + + ] +} \ No newline at end of file diff --git a/odata/etc/odata-url.abnf b/odata/etc/odata-url.abnf new file mode 100644 index 00000000..5e598756 --- /dev/null +++ b/odata/etc/odata-url.abnf @@ -0,0 +1,1189 @@ +;------------------------------------------------------------------------------ +; odata-abnf-construction-rules +;------------------------------------------------------------------------------ +; +; OData Version 4.0 Plus Errata 03 +; OASIS Standard incorporating Approved Errata 03 +; 02 June 2016 +; Copyright (c) OASIS Open 2016. All Rights Reserved. +; Source: http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/abnf/ +; Link to latest version of narrative specification: http://docs.oasis-open.org/odata/odata/v4.0/errata03/odata-v4.0-errata03-part1-protocol-complete.html +; +; Technical Committee: +; OASIS Open Data Protocol (OData) TC +; https://www.oasis-open.org/committees/odata +; +; Chairs: +; - Barbara Hartel (barbara.hartel@sap.com), SAP SE +; - Ram Jeyaraman (Ram.Jeyaraman@microsoft.com), Microsoft +; +; Editors: +; - Ralf Handl (ralf.handl@sap.com), SAP SE +; - Michael Pizzo (mikep@microsoft.com), Microsoft +; - Martin Zurmuehl (martin.zurmuehl@sap.com), SAP SE +; +; Additional artifacts: +; This grammar is one component of a Work Product which consists of: +; - OData Version 4.0 Part 1: Protocol +; - OData Version 4.0 Part 2: URL Conventions +; - OData Version 4.0 Part 3: Common Schema Definition Language (CSDL) +; - OData ABNF Construction Rules Version 4.0 (this document) +; - OData ABNF Test Cases +; - OData Core Vocabulary +; - OData Capabilities Vocabulary +; - OData Measures Vocabulary +; - OData Metadata Service Entity Model +; - OData EDMX XML Schema +; - OData EDM XML Schema +; +; Related work: +; This work product is related to the following two Work Products, each of +; which define alternate formats for OData payloads +; - OData Atom Format Version 4.0 +; - OData JSON Format Version 4.0 +; This specification replaces or supersedes: +; - None +; +; Declared XML namespaces: +; - http://docs.oasis-open.org/odata/ns/edmx +; - http://docs.oasis-open.org/odata/ns/edm +; +; 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 +; edited by Web clients using simple HTTP messages. This document defines +; 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 with one extension: literals +; enclosed in single quotes (e.g. '$metadata') are treated case-sensitive. +; +; The following rules assume that URIs have been percent-encoding normalized +; 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 +; set (see rule "unreserved" below) are plain literals and NOT +; percent-encoded. +; +; 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 +; a string literal is "encoded" as two consecutive single quotes in either +; literal or percent-encoded representation. +; +; Contents: +; 1. Resource Path +; 2. Query Options +; 3. Context URL Fragments +; 4. Expressions +; 5. JSON format for function parameters +; 6. Names and identifiers +; 7. Literal Data Values +; 8. Header values +; 9. Punctuation +; +; A. URI syntax [RFC3986] +; B. IRI syntax [RFC3986] +; C. ABNF core definitions [RFC5234] +; +;------------------------------------------------------------------------------ +dummyStartRule = odataUri / header / primitiveValue ; just to please the test parser +;------------------------------------------------------------------------------ + + +odataUri = serviceRoot [ odataRelativeUri ] + +serviceRoot = ( "https" / "http" ) ; Note: case-insensitive + "://" host [ ":" port ] + "/" *( segment-nz "/" ) + +odataRelativeUri = '$batch' ; Note: case-sensitive! + / '$entity' "?" entityOptions + / '$entity' "/" qualifiedEntityTypeName "?" entityCastOptions + / '$metadata' [ "?" format ] [ context ] + / resourcePath [ "?" queryOptions ] + + +;------------------------------------------------------------------------------ +; 1. Resource Path +;------------------------------------------------------------------------------ + +resourcePath = entitySetName [ collectionNavigation ] + / singletonEntity [ singleNavigation ] + / actionImportCall + / entityColFunctionImportCall [ collectionNavigation ] + / entityFunctionImportCall [ singleNavigation ] + / complexColFunctionImportCall [ complexColPath ] + / complexFunctionImportCall [ complexPath ] + / primitiveColFunctionImportCall [ primitiveColPath ] + / primitiveFunctionImportCall [ primitivePath ] + / crossjoin + / '$all' [ "/" qualifiedEntityTypeName ] + +collectionNavigation = [ "/" qualifiedEntityTypeName ] [ collectionNavPath ] +collectionNavPath = keyPredicate [ singleNavigation ] + / boundOperation + / count + / ref + +keyPredicate = simpleKey / compoundKey ;/ keyPathSegments +simpleKey = OPEN ( parameterAlias / keyPropertyValue ) CLOSE +compoundKey = OPEN keyValuePair *( COMMA keyValuePair ) CLOSE +keyValuePair = ( primitiveKeyProperty / keyPropertyAlias ) EQ ( parameterAlias / keyPropertyValue ) +keyPropertyValue = primitiveLiteral +keyPropertyAlias = odataIdentifier +;keyPathSegments = 1*( "/" keyPathLiteral ) +;keyPathLiteral = *pchar + +singleNavigation = [ "/" qualifiedEntityTypeName ] + [ "/" propertyPath + / boundOperation + / ref + / value ; request the media resource of a media entity + ] + +propertyPath = entityColNavigationProperty [ collectionNavigation ] + / entityNavigationProperty [ singleNavigation ] + / complexColProperty [ complexColPath ] + / complexProperty [ complexPath ] + / primitiveColProperty [ primitiveColPath ] + / primitiveProperty [ primitivePath ] + / streamProperty [ boundOperation ] + +primitiveColPath = count / boundOperation + +primitivePath = value / boundOperation + +complexColPath = [ "/" qualifiedComplexTypeName ] + [ count / boundOperation ] + +complexPath = [ "/" qualifiedComplexTypeName ] + [ "/" propertyPath + / boundOperation + ] + +count = '/$count' +ref = '/$ref' +value = '/$value' + +; 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 ] + / boundEntityFunctionCall [ singleNavigation ] + / boundComplexColFunctionCall [ complexColPath ] + / boundComplexFunctionCall [ complexPath ] + / boundPrimitiveColFunctionCall [ primitiveColPath ] + / boundPrimitiveFunctionCall [ primitivePath ] + ) + +actionImportCall = actionImport +boundActionCall = namespace "." action + ; with the added restriction that the binding parameter MUST be either an entity or collection of entities + ; 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 +; URI immediately preceding (to the left) of the boundXxxFunctionCall, and +; - the functionParameters MUST NOT include the bindingParameter. +boundEntityFunctionCall = namespace "." entityFunction functionParameters +boundEntityColFunctionCall = namespace "." entityColFunction functionParameters +boundComplexFunctionCall = namespace "." complexFunction functionParameters +boundComplexColFunctionCall = namespace "." complexColFunction functionParameters +boundPrimitiveFunctionCall = namespace "." primitiveFunction functionParameters +boundPrimitiveColFunctionCall = namespace "." primitiveColFunction functionParameters + +entityFunctionImportCall = entityFunctionImport functionParameters +entityColFunctionImportCall = entityColFunctionImport functionParameters +complexFunctionImportCall = complexFunctionImport functionParameters +complexColFunctionImportCall = complexColFunctionImport functionParameters +primitiveFunctionImportCall = primitiveFunctionImport functionParameters +primitiveColFunctionImportCall = primitiveColFunctionImport functionParameters + +functionParameters = OPEN [ functionParameter *( COMMA functionParameter ) ] CLOSE +functionParameter = parameterName EQ ( parameterAlias / primitiveLiteral ) +parameterName = odataIdentifier +parameterAlias = AT odataIdentifier + +crossjoin = '$crossjoin' OPEN + entitySetName *( COMMA entitySetName ) + CLOSE + + +;------------------------------------------------------------------------------ +; 2. Query Options +;------------------------------------------------------------------------------ + +queryOptions = queryOption *( "&" queryOption ) +queryOption = systemQueryOption + / aliasAndValue + / customQueryOption + +entityOptions = *( entityIdOption "&" ) id *( "&" entityIdOption ) +entityIdOption = format + / customQueryOption +entityCastOptions = *( entityCastOption "&" ) id *( "&" entityCastOption ) +entityCastOption = entityIdOption + / expand + / select + +id = '$id' EQ IRI-in-query + +systemQueryOption = deltatoken + / expand + / filter + / format + / id + / inlinecount + / orderby + / search + / select + / skip + / skiptoken + / top + +expand = '$expand' EQ expandItem *( COMMA expandItem ) +expandItem = STAR [ ref / OPEN levels CLOSE ] + / expandPath + [ ref [ OPEN expandRefOption *( SEMI expandRefOption ) CLOSE ] + / count [ OPEN expandCountOption *( SEMI expandCountOption ) CLOSE ] + / OPEN expandOption *( SEMI expandOption ) CLOSE + ] +expandPath = [ ( qualifiedEntityTypeName / qualifiedComplexTypeName ) "/" ] + *( ( complexProperty / complexColProperty ) "/" [ qualifiedComplexTypeName "/" ] ) + ( STAR / navigationProperty [ "/" qualifiedEntityTypeName ] ) +expandCountOption = filter + / search +expandRefOption = expandCountOption + / orderby + / skip + / top + / inlinecount +expandOption = expandRefOption + / select + / expand + / levels + +levels = '$levels' EQ ( oneToNine *DIGIT / 'max' ) + +filter = '$filter' EQ boolCommonExpr + +orderby = '$orderby' EQ orderbyItem *( COMMA orderbyItem ) +orderbyItem = commonExpr [ RWS ( 'asc' / 'desc' ) ] + +skip = '$skip' EQ 1*DIGIT +top = '$top' EQ 1*DIGIT + +format = '$format' EQ + ( "atom" + / "json" + / "xml" + / 1*pchar "/" 1*pchar ; or + ; + +inlinecount = '$count' EQ booleanValue + +search = '$search' EQ BWS searchExpr +searchExpr = ( OPEN BWS searchExpr BWS CLOSE + / searchTerm + ) [ searchOrExpr + / searchAndExpr + ] + +searchOrExpr = RWS 'OR' RWS searchExpr +searchAndExpr = RWS [ 'AND' RWS ] searchExpr + +searchTerm = [ 'NOT' RWS ] ( searchPhrase / searchWord ) +searchPhrase = quotation-mark 1*qchar-no-AMP-DQUOTE quotation-mark +searchWord = 1*ALPHA ; Actually: any character from the Unicode categories L or Nl, + ; but not the words AND, OR, and NOT + +select = '$select' EQ selectItem *( COMMA selectItem ) +selectItem = STAR + / allOperationsInSchema + / [ ( qualifiedEntityTypeName / qualifiedComplexTypeName ) "/" ] + ( selectProperty + / qualifiedActionName + / qualifiedFunctionName + ) +selectProperty = primitiveProperty + / primitiveColProperty + / navigationProperty + / selectPath [ "/" selectProperty ] +selectPath = ( complexProperty / complexColProperty ) [ "/" qualifiedComplexTypeName ] + + +allOperationsInSchema = namespace "." STAR + +; The parameterNames uniquely identify the bound function overload +; only if it has overloads. +qualifiedActionName = namespace "." action +qualifiedFunctionName = namespace "." function [ OPEN parameterNames CLOSE ] + +; The names of all non-binding parameters, separated by commas +parameterNames = parameterName *( COMMA parameterName ) + +deltatoken = '$deltatoken' EQ 1*( qchar-no-AMP ) + +skiptoken = '$skiptoken' EQ 1*( qchar-no-AMP ) + +aliasAndValue = parameterAlias EQ parameterValue + +parameterValue = arrayOrObject + / commonExpr + +customQueryOption = customName [ EQ customValue ] +customName = qchar-no-AMP-EQ-AT-DOLLAR *( qchar-no-AMP-EQ ) +customValue = *( qchar-no-AMP ) + + +;------------------------------------------------------------------------------ +; 3. Context URL Fragments +;------------------------------------------------------------------------------ + +context = "#" contextFragment +contextFragment = 'Collection($ref)' + / '$ref' + / 'Collection(Edm.EntityType)' + / 'Collection(Edm.ComplexType)' + / singletonEntity [ navigation *( containmentNavigation ) [ "/" qualifiedEntityTypeName ] ] [ selectList ] + / qualifiedTypeName [ selectList ] + / entitySet ( '/$deletedEntity' / '/$link' / '/$deletedLink' ) + / entitySet keyPredicate "/" contextPropertyPath [ selectList ] + / entitySet [ selectList ] [ '/$entity' / '/$delta' ] + +entitySet = entitySetName *( containmentNavigation ) [ "/" qualifiedEntityTypeName ] + +containmentNavigation = keyPredicate [ "/" qualifiedEntityTypeName ] navigation +navigation = *( "/" complexProperty [ "/" qualifiedComplexTypeName ] ) "/" navigationProperty + +selectList = OPEN selectListItem *( COMMA selectListItem ) CLOSE +selectListItem = STAR ; all structural properties + / allOperationsInSchema + / [ qualifiedEntityTypeName "/" ] + ( qualifiedActionName + / qualifiedFunctionName + / selectListProperty + ) +selectListProperty = primitiveProperty + / primitiveColProperty + / navigationProperty [ '+' ] [ selectList ] + / selectPath [ "/" selectListProperty ] + +contextPropertyPath = primitiveProperty + / primitiveColProperty + / complexColProperty + / complexProperty [ [ "/" qualifiedComplexTypeName ] "/" contextPropertyPath ] + + +;------------------------------------------------------------------------------ +; 4. Expressions +;------------------------------------------------------------------------------ + +; Note: a boolCommonExpr is also a commonExpr, e.g. sort by Boolean +commonExpr = ( primitiveLiteral + / parameterAlias + / arrayOrObject + / rootExpr + / firstMemberExpr + / functionExpr + / negateExpr + / methodCallExpr + / parenExpr + / castExpr + ) + [ addExpr + / subExpr + / mulExpr + / divExpr + / modExpr + ] + +boolCommonExpr = ( isofExpr + / boolMethodCallExpr + / notExpr + / commonExpr + [ eqExpr + / neExpr + / ltExpr + / leExpr + / gtExpr + / geExpr + / hasExpr + ] + / boolParenExpr + ) [ andExpr / orExpr ] + +rootExpr = '$root/' ( entitySetName keyPredicate / singletonEntity ) [ singleNavigationExpr ] + +firstMemberExpr = memberExpr + / inscopeVariableExpr [ "/" memberExpr ] + +memberExpr = [ qualifiedEntityTypeName "/" ] + ( propertyPathExpr + / boundFunctionExpr + ) + +propertyPathExpr = ( entityColNavigationProperty [ collectionNavigationExpr ] + / entityNavigationProperty [ singleNavigationExpr ] + / complexColProperty [ complexColPathExpr ] + / complexProperty [ complexPathExpr ] + / primitiveColProperty [ collectionPathExpr ] + / primitiveProperty [ primitivePathExpr ] + / streamProperty [ primitivePathExpr ] + ) + +inscopeVariableExpr = implicitVariableExpr + / lambdaVariableExpr ; only allowed inside a lambdaPredicateExpr +implicitVariableExpr = '$it' ; references the unnamed outer variable of the query +lambdaVariableExpr = odataIdentifier + +collectionNavigationExpr = [ "/" qualifiedEntityTypeName ] + [ keyPredicate [ singleNavigationExpr ] + / collectionPathExpr + ] + +singleNavigationExpr = "/" memberExpr + +complexColPathExpr = [ "/" qualifiedComplexTypeName ] + [ collectionPathExpr ] + +collectionPathExpr = count + / "/" boundFunctionExpr + / "/" anyExpr + / "/" allExpr + +complexPathExpr = [ "/" qualifiedComplexTypeName ] + [ "/" propertyPathExpr + / "/" boundFunctionExpr + ] + +primitivePathExpr = "/" boundFunctionExpr + +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 ] + / complexColFunction functionExprParameters [ complexColPathExpr ] + / complexFunction functionExprParameters [ complexPathExpr ] + / primitiveColFunction functionExprParameters [ collectionPathExpr ] + / primitiveFunction functionExprParameters [ primitivePathExpr ] + ) + +functionExprParameters = OPEN [ functionExprParameter *( COMMA functionExprParameter ) ] CLOSE +functionExprParameter = parameterName EQ ( parameterAlias / parameterValue ) + +anyExpr = 'any' OPEN BWS [ lambdaVariableExpr BWS COLON BWS lambdaPredicateExpr ] BWS CLOSE +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 + / yearMethodCallExpr + / monthMethodCallExpr + / dayMethodCallExpr + / hourMethodCallExpr + / minuteMethodCallExpr + / secondMethodCallExpr + / fractionalsecondsMethodCallExpr + / totalsecondsMethodCallExpr + / dateMethodCallExpr + / timeMethodCallExpr + / roundMethodCallExpr + / floorMethodCallExpr + / ceilingMethodCallExpr + / distanceMethodCallExpr + / geoLengthMethodCallExpr + / totalOffsetMinutesMethodCallExpr + / minDateTimeMethodCallExpr + / maxDateTimeMethodCallExpr + / nowMethodCallExpr + +boolMethodCallExpr = endsWithMethodCallExpr + / startsWithMethodCallExpr + / containsMethodCallExpr + / intersectsMethodCallExpr + +containsMethodCallExpr = 'contains' OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE +startsWithMethodCallExpr = 'startswith' OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE +endsWithMethodCallExpr = 'endswith' OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE +lengthMethodCallExpr = 'length' OPEN BWS commonExpr BWS CLOSE +indexOfMethodCallExpr = 'indexof' OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE +substringMethodCallExpr = 'substring' OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS [ COMMA BWS commonExpr BWS ] CLOSE +toLowerMethodCallExpr = 'tolower' OPEN BWS commonExpr BWS CLOSE +toUpperMethodCallExpr = 'toupper' OPEN BWS commonExpr BWS CLOSE +trimMethodCallExpr = 'trim' OPEN BWS commonExpr BWS CLOSE +concatMethodCallExpr = 'concat' OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE + +yearMethodCallExpr = 'year' OPEN BWS commonExpr BWS CLOSE +monthMethodCallExpr = 'month' OPEN BWS commonExpr BWS CLOSE +dayMethodCallExpr = 'day' OPEN BWS commonExpr BWS CLOSE +hourMethodCallExpr = 'hour' OPEN BWS commonExpr BWS CLOSE +minuteMethodCallExpr = 'minute' OPEN BWS commonExpr BWS CLOSE +secondMethodCallExpr = 'second' OPEN BWS commonExpr BWS CLOSE +fractionalsecondsMethodCallExpr = 'fractionalseconds' OPEN BWS commonExpr BWS CLOSE +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 + +minDateTimeMethodCallExpr = 'mindatetime' OPEN BWS CLOSE +maxDateTimeMethodCallExpr = 'maxdatetime' OPEN BWS CLOSE +nowMethodCallExpr = 'now' OPEN BWS CLOSE + +roundMethodCallExpr = 'round' OPEN BWS commonExpr BWS CLOSE +floorMethodCallExpr = 'floor' OPEN BWS commonExpr BWS CLOSE +ceilingMethodCallExpr = 'ceiling' OPEN BWS commonExpr BWS CLOSE + +distanceMethodCallExpr = 'geo.distance' OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE +geoLengthMethodCallExpr = 'geo.length' OPEN BWS commonExpr BWS CLOSE +intersectsMethodCallExpr = 'geo.intersects' OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE + +boolParenExpr = OPEN BWS boolCommonExpr BWS CLOSE +parenExpr = OPEN BWS commonExpr BWS CLOSE + +andExpr = RWS 'and' RWS boolCommonExpr +orExpr = RWS 'or' RWS boolCommonExpr + +eqExpr = RWS 'eq' RWS commonExpr +neExpr = RWS 'ne' RWS commonExpr +ltExpr = RWS 'lt' RWS commonExpr +leExpr = RWS 'le' RWS commonExpr +gtExpr = RWS 'gt' RWS commonExpr +geExpr = RWS 'ge' RWS commonExpr + +hasExpr = RWS 'has' RWS enum + +addExpr = RWS 'add' RWS commonExpr +subExpr = RWS 'sub' RWS commonExpr +mulExpr = RWS 'mul' RWS commonExpr +divExpr = RWS 'div' RWS commonExpr +modExpr = RWS 'mod' RWS commonExpr + +negateExpr = "-" BWS commonExpr + +notExpr = 'not' RWS boolCommonExpr + +isofExpr = 'isof' OPEN BWS [ commonExpr BWS COMMA BWS ] qualifiedTypeName BWS CLOSE +castExpr = 'cast' OPEN BWS [ commonExpr BWS COMMA BWS ] qualifiedTypeName BWS CLOSE + + +;------------------------------------------------------------------------------ +; 5. JSON format for function parameters +;------------------------------------------------------------------------------ +; Note: the query part of a URI needs to be partially percent-decoded before +; applying these rules, see comment at the top of this file +;------------------------------------------------------------------------------ + +arrayOrObject = complexColInUri + / complexInUri + / rootExprCol + / primitiveColInUri + +complexColInUri = begin-array + [ complexInUri *( value-separator complexInUri ) ] + end-array + +complexInUri = begin-object + [ ( annotationInUri + / primitivePropertyInUri + / complexPropertyInUri + / collectionPropertyInUri + / navigationPropertyInUri + ) + *( value-separator + ( annotationInUri + / primitivePropertyInUri + / complexPropertyInUri + / collectionPropertyInUri + / navigationPropertyInUri + ) + ) + ] + end-object + +collectionPropertyInUri = ( quotation-mark primitiveColProperty quotation-mark + name-separator + primitiveColInUri + ) + / ( quotation-mark complexColProperty quotation-mark + name-separator + complexColInUri + ) + +primitiveColInUri = begin-array + [ primitiveLiteralInJSON *( value-separator primitiveLiteralInJSON ) ] + end-array + +complexPropertyInUri = quotation-mark complexProperty quotation-mark + name-separator + complexInUri + +annotationInUri = quotation-mark AT namespace "." termName quotation-mark + name-separator + ( complexInUri / complexColInUri / primitiveLiteralInJSON / primitiveColInUri ) + +primitivePropertyInUri = quotation-mark primitiveProperty quotation-mark + name-separator + primitiveLiteralInJSON + +navigationPropertyInUri = singleNavPropInJSON + / collectionNavPropInJSON +singleNavPropInJSON = quotation-mark entityNavigationProperty quotation-mark + name-separator + rootExpr +collectionNavPropInJSON = quotation-mark entityColNavigationProperty quotation-mark + name-separator + rootExprCol + +rootExprCol = begin-array + [ rootExpr *( value-separator rootExpr ) ] + end-array + +; JSON syntax: adapted to URI restrictions from [RFC4627] +begin-object = BWS ( "{" / "%7B" ) BWS +end-object = BWS ( "}" / "%7D" ) BWS + +begin-array = BWS ( "[" / "%5B" ) BWS +end-array = BWS ( "]" / "%5D" ) BWS + +quotation-mark = DQUOTE / "%22" +name-separator = BWS COLON BWS +value-separator = BWS COMMA BWS + +primitiveLiteralInJSON = stringInJSON + / numberInJSON + / 'true' + / 'false' + / 'null' + +stringInJSON = quotation-mark *charInJSON 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 + / 'b' ; backspace U+0008 + / 'f' ; form feed U+000C + / 'n' ; line feed U+000A + / 'r' ; carriage return U+000D + / 't' ; tab U+0009 + / 'u' 4HEXDIG ; U+XXXX + ) + +qchar-JSON-special = SP / ":" / "{" / "}" / "[" / "]" ; some agents put these unencoded into the query part of a URL + +escape = "\" / "%5C" ; reverse solidus U+005C + +numberInJSON = [ "-" ] int [ frac ] [ exp ] +int = "0" / ( oneToNine *DIGIT ) +frac = "." 1*DIGIT +exp = "e" [ "-" / "+" ] 1*DIGIT + + +;------------------------------------------------------------------------------ +; 6. Names and identifiers +;------------------------------------------------------------------------------ + +singleQualifiedTypeName = qualifiedEntityTypeName + / qualifiedComplexTypeName + / qualifiedTypeDefinitionName + / qualifiedEnumTypeName + / primitiveTypeName + +qualifiedTypeName = singleQualifiedTypeName + / 'Collection' OPEN singleQualifiedTypeName CLOSE + +qualifiedEntityTypeName = namespace "." entityTypeName +qualifiedComplexTypeName = namespace "." complexTypeName +qualifiedTypeDefinitionName = namespace "." typeDefinitionName +qualifiedEnumTypeName = namespace "." enumerationTypeName + +; an alias is just a single-part namespace +namespace = namespacePart *( "." namespacePart ) +namespacePart = odataIdentifier + +entitySetName = odataIdentifier +singletonEntity = odataIdentifier +entityTypeName = odataIdentifier +complexTypeName = odataIdentifier +typeDefinitionName = odataIdentifier +enumerationTypeName = odataIdentifier +enumerationMember = odataIdentifier +termName = odataIdentifier + +; Note: this pattern is overly restrictive, the normative definition is type TSimpleIdentifier in OData EDM XML Schema +odataIdentifier = identifierLeadingCharacter *127identifierCharacter +identifierLeadingCharacter = ALPHA / "_" ; plus Unicode characters from the categories L or Nl +identifierCharacter = ALPHA / "_" / DIGIT ; plus Unicode characters from the categories L, Nl, Nd, Mn, Mc, Pc, or Cf + +primitiveTypeName = 'Edm.' ( 'Binary' + / 'Boolean' + / 'Byte' + / 'Date' + / 'DateTimeOffset' + / 'Decimal' + / 'Double' + / 'Duration' + / 'Guid' + / 'Int16' + / 'Int32' + / 'Int64' + / 'SByte' + / 'Single' + / 'Stream' + / 'String' + / 'TimeOfDay' + / abstractSpatialTypeName [ concreteSpatialTypeName ] + ) +abstractSpatialTypeName = 'Geography' + / 'Geometry' +concreteSpatialTypeName = 'Collection' + / 'LineString' + / 'MultiLineString' + / 'MultiPoint' + / 'MultiPolygon' + / 'Point' + / 'Polygon' + +primitiveProperty = primitiveKeyProperty / primitiveNonKeyProperty +primitiveKeyProperty = odataIdentifier +primitiveNonKeyProperty = odataIdentifier +primitiveColProperty = odataIdentifier +complexProperty = odataIdentifier +complexColProperty = odataIdentifier +streamProperty = odataIdentifier + +navigationProperty = entityNavigationProperty / entityColNavigationProperty +entityNavigationProperty = odataIdentifier +entityColNavigationProperty = odataIdentifier + +action = odataIdentifier +actionImport = odataIdentifier + +function = entityFunction + / entityColFunction + / complexFunction + / complexColFunction + / primitiveFunction + / primitiveColFunction + +entityFunction = odataIdentifier +entityColFunction = odataIdentifier +complexFunction = odataIdentifier +complexColFunction = odataIdentifier +primitiveFunction = odataIdentifier +primitiveColFunction = odataIdentifier + +entityFunctionImport = odataIdentifier +entityColFunctionImport = odataIdentifier +complexFunctionImport = odataIdentifier +complexColFunctionImport = odataIdentifier +primitiveFunctionImport = odataIdentifier +primitiveColFunctionImport = odataIdentifier + + +;------------------------------------------------------------------------------ +; 7. Literal Data Values +;------------------------------------------------------------------------------ + +; in URLs +primitiveLiteral = nullValue ; plain values up to int64Value + / booleanValue + / guidValue + / dateValue + / dateTimeOffsetValue + / timeOfDayValue + / decimalValue + / doubleValue + / singleValue + / sbyteValue + / byteValue + / int16Value + / int32Value + / int64Value + / string ; single-quoted + / duration ; all others are quoted and prefixed + / binary + / enum + / geographyCollection + / geographyLineString + / geographyMultiLineString + / geographyMultiPoint + / geographyMultiPolygon + / geographyPoint + / geographyPolygon + / geometryCollection + / geometryLineString + / geometryMultiLineString + / geometryMultiPoint + / geometryMultiPolygon + / geometryPoint + / geometryPolygon + +; in Atom and JSON message bodies and CSDL DefaultValue attributes +primitiveValue = booleanValue + / guidValue + / durationValue + / dateValue + / dateTimeOffsetValue + / timeOfDayValue + / enumValue + / fullCollectionLiteral + / fullLineStringLiteral + / fullMultiPointLiteral + / fullMultiLineStringLiteral + / fullMultiPolygonLiteral + / fullPointLiteral + / fullPolygonLiteral + / decimalValue + / doubleValue + / singleValue + / sbyteValue + / byteValue + / int16Value + / int32Value + / int64Value + / binaryValue + ; also valid are: + ; - any XML string for strings in Atom and CSDL documents + ; - any JSON string for JSON documents + +nullValue = 'null' + +; base64url encoding according to http://tools.ietf.org/html/rfc4648#section-5 +binary = "binary" SQUOTE binaryValue SQUOTE +binaryValue = *(4base64char) [ base64b16 / base64b8 ] +base64b16 = 2base64char ( 'A' / 'E' / 'I' / 'M' / 'Q' / 'U' / 'Y' / 'c' / 'g' / 'k' / 'o' / 's' / 'w' / '0' / '4' / '8' ) [ "=" ] +base64b8 = base64char ( 'A' / 'Q' / 'g' / 'w' ) [ "==" ] +base64char = ALPHA / DIGIT / "-" / "_" + +booleanValue = "true" / "false" + +decimalValue = [SIGN] 1*DIGIT ["." 1*DIGIT] + +doubleValue = decimalValue [ "e" [SIGN] 1*DIGIT ] / nanInfinity ; IEEE 754 binary64 floating-point number (15-17 decimal digits) +singleValue = doubleValue ; IEEE 754 binary32 floating-point number (6-9 decimal digits) +nanInfinity = 'NaN' / '-INF' / 'INF' + +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 +int32Value = [ sign ] 1*10DIGIT ; numbers in the range from -2147483648 to 2147483647 +int64Value = [ sign ] 1*19DIGIT ; numbers in the range from -9223372036854775808 to 9223372036854775807 + +string = SQUOTE *( SQUOTE-in-string / pchar-no-SQUOTE ) SQUOTE +SQUOTE-in-string = SQUOTE SQUOTE ; two consecutive single quotes represent one within a string literal + +dateValue = year "-" month "-" day + +dateTimeOffsetValue = year "-" month "-" day "T" hour ":" minute [ ":" second [ "." fractionalSeconds ] ] ( "Z" / sign hour ":" minute ) + +duration = "duration" SQUOTE durationValue SQUOTE +durationValue = [ sign ] "P" [ 1*DIGIT "D" ] [ "T" [ 1*DIGIT "H" ] [ 1*DIGIT "M" ] [ 1*DIGIT [ "." 1*DIGIT ] "S" ] ] + ; the above is an approximation of the rules for an xml dayTimeDuration. + ; see the lexical representation for dayTimeDuration in http://www.w3.org/TR/xmlschema11-2#dayTimeDuration for more information + +timeOfDayValue = hour ":" minute [ ":" second [ "." fractionalSeconds ] ] + +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 + / "1" ( "0" / "1" / "2" ) +day = "0" oneToNine + / ( "1" / "2" ) DIGIT + / "3" ( "0" / "1" ) +hour = ( "0" / "1" ) DIGIT + / "2" ( "0" / "1" / "2" / "3" ) +minute = zeroToFiftyNine +second = zeroToFiftyNine +fractionalSeconds = 1*12DIGIT + +enum = qualifiedEnumTypeName SQUOTE enumValue SQUOTE +enumValue = singleEnumValue *( COMMA singleEnumValue ) +singleEnumValue = enumerationMember / enumMemberValue +enumMemberValue = int64Value + +geographyCollection = geographyPrefix SQUOTE fullCollectionLiteral SQUOTE +fullCollectionLiteral = sridLiteral collectionLiteral +collectionLiteral = "Collection(" geoLiteral *( COMMA geoLiteral ) CLOSE +geoLiteral = collectionLiteral + / lineStringLiteral + / multiPointLiteral + / multiLineStringLiteral + / multiPolygonLiteral + / pointLiteral + / polygonLiteral + +geographyLineString = geographyPrefix SQUOTE fullLineStringLiteral SQUOTE +fullLineStringLiteral = sridLiteral lineStringLiteral +lineStringLiteral = "LineString" lineStringData +lineStringData = OPEN positionLiteral 1*( COMMA positionLiteral ) CLOSE + +geographyMultiLineString = geographyPrefix SQUOTE fullMultiLineStringLiteral SQUOTE +fullMultiLineStringLiteral = sridLiteral multiLineStringLiteral +multiLineStringLiteral = "MultiLineString(" [ lineStringData *( COMMA lineStringData ) ] CLOSE + +geographyMultiPoint = geographyPrefix SQUOTE fullMultiPointLiteral SQUOTE +fullMultiPointLiteral = sridLiteral multiPointLiteral +multiPointLiteral = "MultiPoint(" [ pointData *( COMMA pointData ) ] CLOSE + +geographyMultiPolygon = geographyPrefix SQUOTE fullMultiPolygonLiteral SQUOTE +fullMultiPolygonLiteral = sridLiteral multiPolygonLiteral +multiPolygonLiteral = "MultiPolygon(" [ polygonData *( COMMA polygonData ) ] CLOSE + +geographyPoint = geographyPrefix SQUOTE fullPointLiteral SQUOTE +fullPointLiteral = sridLiteral pointLiteral +sridLiteral = "SRID" EQ 1*5DIGIT SEMI +pointLiteral ="Point" pointData +pointData = OPEN positionLiteral CLOSE +positionLiteral = doubleValue SP doubleValue ; longitude, then latitude + +geographyPolygon = geographyPrefix SQUOTE fullPolygonLiteral SQUOTE +fullPolygonLiteral = sridLiteral polygonLiteral +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. + ; In order of traversal, points to the left side of the ring are interpreted as being in the polygon. + +geometryCollection = geometryPrefix SQUOTE fullCollectionLiteral SQUOTE +geometryLineString = geometryPrefix SQUOTE fullLineStringLiteral SQUOTE +geometryMultiLineString = geometryPrefix SQUOTE fullMultiLineStringLiteral SQUOTE +geometryMultiPoint = geometryPrefix SQUOTE fullMultiPointLiteral SQUOTE +geometryMultiPolygon = geometryPrefix SQUOTE fullMultiPolygonLiteral SQUOTE +geometryPoint = geometryPrefix SQUOTE fullPointLiteral SQUOTE +geometryPolygon = geometryPrefix SQUOTE fullPolygonLiteral SQUOTE + +geographyPrefix = "geography" +geometryPrefix = "geometry" + + +;------------------------------------------------------------------------------ +; 8. Header values +;------------------------------------------------------------------------------ + +header = content-id + / odata-entityid + / odata-isolation + / odata-maxversion + / odata-version + / prefer + +content-id = "Content-ID" ":" OWS 1*unreserved + +odata-entityid = "OData-EntityID" ":" OWS IRI-in-header +odata-isolation = "OData-Isolation" ":" OWS "snapshot" +odata-maxversion = "OData-MaxVersion" ":" OWS 1*DIGIT "." 1*DIGIT +odata-version = "OData-Version" ":" OWS "4.0" + +prefer = "Prefer" ":" OWS preference *( COMMA preference ) +preference = allowEntityReferencesPreference + / callbackPreference + / continueOnErrorPreference + / includeAnnotationsPreference + / maxpagesizePreference + / respondAsyncPreference + / returnPreference + / trackChangesPreference + / waitPreference + ; and everything allowed by http://tools.ietf.org/html/draft-snell-http-prefer-18 + ; / token [ EQ-h word ] *( OWS ";" [ OWS parameter ] ) + +allowEntityReferencesPreference = "odata.allow-entityreferences" + +callbackPreference = "odata.callback" OWS ";" OWS "url" EQ-h DQUOTE URI DQUOTE + +continueOnErrorPreference = "odata.continue-on-error" + +includeAnnotationsPreference = "odata.include-annotations" EQ-h DQUOTE annotationsList DQUOTE +annotationsList = annotationIdentifier *(COMMA annotationIdentifier) +annotationIdentifier = [ excludeOperator ] + ( STAR + / namespace "." ( termName / STAR ) + ) + [ "#" odataIdentifier ] +excludeOperator = "-" + +maxpagesizePreference = "odata.maxpagesize" EQ-h oneToNine *DIGIT + +respondAsyncPreference = "respond-async" + +returnPreference = "return" EQ-h ( 'representation' / 'minimal' ) + +trackChangesPreference = "odata.track-changes" + +waitPreference = "wait" EQ-h 1*DIGIT + +;parameter = token [ EQ-h word ] +;word = token / quoted-string +;token = 1*tchar +;tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" +; / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" +; / DIGIT / ALPHA +;quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE +;qdtext = %x21 / %x23-5B / %x5D-7E / obs-text / OWS +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 + + +;------------------------------------------------------------------------------ +; 9. Punctuation +;------------------------------------------------------------------------------ + +RWS = 1*( SP / HTAB / "%20" / "%09" ) ; "required" whitespace +BWS = *( SP / HTAB / "%20" / "%09" ) ; "bad" whitespace + +AT = "@" / "%40" +COLON = ":" / "%3A" +COMMA = "," / "%2C" +EQ = "=" +SIGN = "+" / "%2B" / "-" +SEMI = ";" / "%3B" +STAR = "*" / "%2A" +SQUOTE = "'" / "%27" + +OPEN = "(" / "%28" +CLOSE = ")" / "%29" + + +;------------------------------------------------------------------------------ +; A. URI syntax [RFC3986] +;------------------------------------------------------------------------------ + +URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] +hier-part = "//" authority path-abempty + / path-absolute + / path-rootless +; / path-empty +;URI-reference = URI / relative-ref +;absolute-URI = scheme ":" hier-part [ "?" query ] +;relative-ref = relative-part [ "?" query ] [ "#" fragment ] +;relative-part = "//" authority path-abempty +; / path-absolute +; / path-noscheme +; / path-empty +scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) +authority = [ userinfo "@" ] host [ ":" port ] +userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) +host = IP-literal / IPv4address / reg-name +port = *DIGIT +IP-literal = "[" ( IPv6address / IPvFuture ) "]" +IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) +IPv6address = 6( h16 ":" ) ls32 + / "::" 5( h16 ":" ) ls32 + / [ h16 ] "::" 4( h16 ":" ) ls32 + / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 + / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 + / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 + / [ *4( h16 ":" ) h16 ] "::" ls32 + / [ *5( h16 ":" ) h16 ] "::" h16 + / [ *6( h16 ":" ) h16 ] "::" +h16 = 1*4HEXDIG +ls32 = ( h16 ":" h16 ) / IPv4address +IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet +dec-octet = "1" 2DIGIT ; 100-199 + / "2" %x30-34 DIGIT ; 200-249 + / "25" %x30-35 ; 250-255 + / %x31-39 DIGIT ; 10-99 + / DIGIT ; 0-9 +reg-name = *( unreserved / pct-encoded / sub-delims ) +;path = path-abempty ; begins with "/" or is empty +; / path-absolute ; begins with "/" but not "//" +; / path-noscheme ; begins with a non-colon segment +; / path-rootless ; begins with a segment +; / path-empty ; zero characters +path-abempty = *( "/" segment ) +path-absolute = "/" [ segment-nz *( "/" segment ) ] +;path-noscheme = segment-nz-nc *( "/" segment ) +path-rootless = segment-nz *( "/" segment ) +;path-empty = "" +segment = *pchar +segment-nz = 1*pchar +;segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) ; non-zero-length segment without any colon ":" +pchar = unreserved / pct-encoded / sub-delims / ":" / "@" +query = *( pchar / "/" / "?" ) +fragment = *( pchar / "/" / "?" ) +pct-encoded = "%" HEXDIG HEXDIG +unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" +;reserved = gen-delims / sub-delims +;gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" +;sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" +sub-delims = "$" / "&" / "'" / "=" / other-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 + / "%" "2" ( "0" / "1" / "2" / "3" / "4" / "5" / "6" / "8" / "9" / A-to-F ) + +qchar-no-AMP = unreserved / pct-encoded / other-delims / ":" / "@" / "/" / "?" / "$" / "'" / "=" +qchar-no-AMP-EQ = unreserved / pct-encoded / other-delims / ":" / "@" / "/" / "?" / "$" / "'" +qchar-no-AMP-EQ-AT-DOLLAR = unreserved / pct-encoded / 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 ) + / "%" "5" ( DIGIT / "A" / "B" / "D" / "E" / "F" ) + +qchar-no-AMP-DQUOTE = qchar-unescaped + / escape ( escape / quotation-mark ) + + +;------------------------------------------------------------------------------ +; B. IRI syntax [RFC3987] +;------------------------------------------------------------------------------ +; Note: these are over-generous stubs, for the actual patterns refer to RFC3987 +;------------------------------------------------------------------------------ + +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 +HEXDIG = DIGIT / A-to-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 +;CHAR = %x01-7F +;LOCTET = %x00-FF +;CR = %x0D +;LF = %x0A +;CRLF = CR LF +;BIT = "0" / "1" + + +;------------------------------------------------------------------------------ +; End of odata-abnf-construction-rules +;------------------------------------------------------------------------------ \ No newline at end of file diff --git a/odata/etc/odata-url.pegjs b/odata/etc/odata-url.pegjs new file mode 100644 index 00000000..9bf55448 --- /dev/null +++ b/odata/etc/odata-url.pegjs @@ -0,0 +1,1162 @@ +//------------------------------------------------------------------------------ +// odata-abnf-construction-rules +//------------------------------------------------------------------------------ +// +// OData Version 4.0 Plus Errata 03 +// OASIS Standard incorporating Approved Errata 03 +// 02 June 2016 +// Copyright (c) OASIS Open 2016. All Rights Reserved. +// Source: http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/abnf/ +// Link to latest version of narrative specification: http://docs.oasis-open.org/odata/odata/v4.0/errata03/odata-v4.0-errata03-part1-protocol-complete.html +// +// Technical Committee: +// OASIS Open Data Protocol (OData) TC +// https://www.oasis-open.org/committees/odata +// +// Chairs: +// - Barbara Hartel (barbara.hartel@sap.com), SAP SE +// - Ram Jeyaraman (Ram.Jeyaraman@microsoft.com), Microsoft +// +// Editors: +// - Ralf Handl (ralf.handl@sap.com), SAP SE +// - Michael Pizzo (mikep@microsoft.com), Microsoft +// - Martin Zurmuehl (martin.zurmuehl@sap.com), SAP SE +// +// Additional artifacts: +// This grammar is one component of a Work Product which consists of: +// - OData Version 4.0 Part 1: Protocol +// - OData Version 4.0 Part 2: URL Conventions +// - OData Version 4.0 Part 3: Common Schema Definition Language (CSDL) +// - OData ABNF Construction Rules Version 4.0 (this document) +// - OData ABNF Test Cases +// - OData Core Vocabulary +// - OData Capabilities Vocabulary +// - OData Measures Vocabulary +// - OData Metadata Service Entity Model +// - OData EDMX XML Schema +// - OData EDM XML Schema +// +// Related work: +// This work product is related to the following two Work Products, each of +// which define alternate formats for OData payloads +// - OData Atom Format Version 4.0 +// - OData JSON Format Version 4.0 +// This specification replaces or supersedes: +// - None +// +// Declared XML namespaces: +// - http://docs.oasis-open.org/odata/ns/edmx +// - http://docs.oasis-open.org/odata/ns/edm +// +// 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 +// edited by Web clients using simple HTTP messages. This document defines +// 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 with one extension: literals +// enclosed in single quotes (e.g. '$metadata') are treated case-sensitive. +// +// The following rules assume that URIs have been percent-encoding normalized +// 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 +// set (see rule "unreserved" below) are plain literals and NOT +// percent-encoded. +// +// 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 +// a string literal is "encoded" as two consecutive single quotes in either +// literal or percent-encoded representation. +// +// Contents: +// 1. Resource Path +// 2. Query Options +// 3. Context URL Fragments +// 4. Expressions +// 5. JSON format for function parameters +// 6. Names and identifiers +// 7. Literal Data Values +// 8. Header values +// 9. Punctuation +// +// A. URI syntax [RFC3986] +// B. IRI syntax [RFC3986] +// C. ABNF core definitions [RFC5234] +// + +{ + const $=Object.assign + const stack=[] + let SELECT, columns + + const select = (col) => { + if (!columns) columns = SELECT.columns = [] + columns.push(col) + } + const expand = (col) => { + select (col) + stack.push (SELECT) + SELECT = col + columns = col.expand = [] + } + const end = () => { + if (columns.length === 0) columns.push('*') + SELECT = stack.pop() + columns = SELECT.columns || SELECT.expand + } + const limit = (o) => { + $(SELECT.limit || (SELECT.limit={}), o) + } + const functions = { + toupper: 'upper', + tolower: 'lower', + indexof: 'locate', + time: 'to_time', + } + const comparators = { + eq: '=', + ne: '!=', + lt: '<', + gt: '>', + le: '<=', + ge: '>=', + } + +} + +//------------------------------------------------------------------------------ +dummyStartRule = odataRelativeUri // just to please the test parser +//------------------------------------------------------------------------------ + + +// odataUri = serviceRoot ( odataRelativeUri )? + +// serviceRoot = ( "https" / "http" ) // Note: case-insensitive +// "://" [^:]+ ( ":" DIGIT+ )? +// "/" ( [^/]+ "/" )* + +odataRelativeUri = '$batch' // Note: case-sensitive! + / '$entity' "?" entityOptions + / '$entity' "/" qualifiedEntityTypeName "?" entityCastOptions + / '$metadata' ( "?" format )? ( context )? + / resourcePath ( "?" queryOptions )? + + +//------------------------------------------------------------------------------ +// 1. Resource Path +//------------------------------------------------------------------------------ + +resourcePath = entitySetName ( collectionNavigation )? + / singletonEntity ( singleNavigation )? + / actionImportCall + / entityColFunctionImportCall ( collectionNavigation )? + / entityFunctionImportCall ( singleNavigation )? + / complexColFunctionImportCall ( complexColPath )? + / complexFunctionImportCall ( complexPath )? + / primitiveColFunctionImportCall ( primitiveColPath )? + / primitiveFunctionImportCall ( primitivePath )? + / crossjoin + / '$all' ( "/" qualifiedEntityTypeName )? + +collectionNavigation = ( "/" qualifiedEntityTypeName )? ( collectionNavPath )? +collectionNavPath = keyPredicate ( singleNavigation )? + / boundOperation + / count + / ref + +keyPredicate = simpleKey / compoundKey // / keyPathSegments +simpleKey = OPEN ( parameterAlias / keyPropertyValue ) CLOSE +compoundKey = OPEN keyValuePair ( COMMA keyValuePair )* CLOSE +keyValuePair = ( primitiveKeyProperty / keyPropertyAlias ) EQ ( parameterAlias / keyPropertyValue ) +keyPropertyValue = primitiveLiteral +keyPropertyAlias = odataIdentifier +//keyPathSegments = ( "/" keyPathLiteral )+ +//keyPathLiteral = pchar* + +singleNavigation = ( "/" qualifiedEntityTypeName )? + ( "/" propertyPath + / boundOperation + / ref + / value // request the media resource of a media entity + )? + +propertyPath = entityColNavigationProperty ( collectionNavigation )? + / entityNavigationProperty ( singleNavigation )? + / complexColProperty ( complexColPath )? + / complexProperty ( complexPath )? + / primitiveColProperty ( primitiveColPath )? + / primitiveProperty ( primitivePath )? + / streamProperty ( boundOperation )? + +primitiveColPath = count / boundOperation + +primitivePath = value / boundOperation + +complexColPath = ( "/" qualifiedComplexTypeName )? + ( count / boundOperation )? + +complexPath = ( "/" qualifiedComplexTypeName )? + ( "/" propertyPath + / boundOperation + )? + +count = '/$count' +ref = '/$ref' +value = '/$value' + +// 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 )? + / boundEntityFunctionCall ( singleNavigation )? + / boundComplexColFunctionCall ( complexColPath )? + / boundComplexFunctionCall ( complexPath )? + / boundPrimitiveColFunctionCall ( primitiveColPath )? + / boundPrimitiveFunctionCall ( primitivePath )? + ) + +actionImportCall = actionImport +boundActionCall = namespace "." action + // with the added restriction that the binding parameter MUST be either an entity or collection of entities + // 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 +// URI immediately preceding (to the left) of the boundXxxFunctionCall, and +// - the functionParameters MUST NOT include the bindingParameter. +boundEntityFunctionCall = namespace "." entityFunction functionParameters +boundEntityColFunctionCall = namespace "." entityColFunction functionParameters +boundComplexFunctionCall = namespace "." complexFunction functionParameters +boundComplexColFunctionCall = namespace "." complexColFunction functionParameters +boundPrimitiveFunctionCall = namespace "." primitiveFunction functionParameters +boundPrimitiveColFunctionCall = namespace "." primitiveColFunction functionParameters + +entityFunctionImportCall = entityFunctionImport functionParameters +entityColFunctionImportCall = entityColFunctionImport functionParameters +complexFunctionImportCall = complexFunctionImport functionParameters +complexColFunctionImportCall = complexColFunctionImport functionParameters +primitiveFunctionImportCall = primitiveFunctionImport functionParameters +primitiveColFunctionImportCall = primitiveColFunctionImport functionParameters + +functionParameters = OPEN ( functionParameter ( COMMA functionParameter )* )? CLOSE +functionParameter = parameterName EQ ( parameterAlias / primitiveLiteral ) +parameterName = odataIdentifier +parameterAlias = AT odataIdentifier + +crossjoin = '$crossjoin' OPEN + entitySetName ( COMMA entitySetName )* + CLOSE + + +//------------------------------------------------------------------------------ +// 2. Query Options +//------------------------------------------------------------------------------ + +queryOptions = queryOption ( "&" queryOption )* +queryOption = systemQueryOption + / aliasAndValue + / customQueryOption + +entityOptions = ( entityIdOption "&" )* id ( "&" entityIdOption )* +entityIdOption = format + / customQueryOption +entityCastOptions = ( entityCastOption "&" )* id ( "&" entityCastOption )* +entityCastOption = entityIdOption + / expand + / select + +id = '$id' EQ $[^&]+ // was: IRI-in-query + +systemQueryOption = deltatoken + / expand + / filter + / format + / id + / inlinecount + / orderby + / search + / select + / skip + / skiptoken + / top + +expand = '$expand' EQ expandItem ( COMMA expandItem )* +expandItem = STAR ( ref / OPEN levels CLOSE )? + / expandPath + ( ref ( OPEN expandRefOption ( SEMI expandRefOption )* CLOSE )? + / count ( OPEN expandCountOption ( SEMI expandCountOption )* CLOSE )? + / OPEN expandOption ( SEMI expandOption )* CLOSE + )? +expandPath = ( ( qualifiedEntityTypeName / qualifiedComplexTypeName ) "/" )? + ( ( complexProperty / complexColProperty ) "/" ( qualifiedComplexTypeName "/" )? )* + ( STAR / navigationProperty ( "/" qualifiedEntityTypeName )? ) +expandCountOption = filter + / search +expandRefOption = expandCountOption + / orderby + / skip + / top + / inlinecount +expandOption = expandRefOption + / select + / expand + / levels + +levels = '$levels' EQ ( oneToNine *DIGIT / 'max' ) + +filter = '$filter' EQ boolCommonExpr + +orderby = '$orderby' EQ orderbyItem ( COMMA orderbyItem )* +orderbyItem = commonExpr ( RWS ( 'asc' / 'desc' ) )? + +skip = '$skip' EQ DIGIT* +top = '$top' EQ DIGIT* + +format = '$format' EQ + ( "atom" + / "json" + / "xml" + / pchar* "/" pchar* // or + // + +inlinecount = '$count' EQ booleanValue + +search = '$search' EQ BWS searchExpr +searchExpr = ( OPEN BWS searchExpr BWS CLOSE + / searchTerm + ) ( searchOrExpr + / searchAndExpr + )? + +searchOrExpr = RWS 'OR' RWS searchExpr +searchAndExpr = RWS ( 'AND' RWS )? searchExpr + +searchTerm = ( 'NOT' RWS )? ( searchPhrase / searchWord ) +searchPhrase = quotation_mark ( !DQUOTE qchar_no_AMP )+ quotation_mark +searchWord = ALPHA+ // Actually: any character from the Unicode categories L or Nl, + // but not the words AND, OR, and NOT + +select = '$select' EQ selectItem ( COMMA selectItem )* +selectItem = STAR + / allOperationsInSchema + / ( ( qualifiedEntityTypeName / qualifiedComplexTypeName ) "/" )? + ( selectProperty + / qualifiedActionName + / qualifiedFunctionName + ) +selectProperty = primitiveProperty + / primitiveColProperty + / navigationProperty + / selectPath ( "/" selectProperty )? +selectPath = ( complexProperty / complexColProperty ) ( "/" qualifiedComplexTypeName )? + + +allOperationsInSchema = namespace "." STAR + +// The parameterNames uniquely identify the bound function overload +// only if it has overloads. +qualifiedActionName = namespace "." action +qualifiedFunctionName = namespace "." function ( OPEN parameterNames CLOSE )? + +// The names of all non-binding parameters, separated by commas +parameterNames = parameterName ( COMMA parameterName )* + +deltatoken = '$deltatoken' EQ qchar_no_AMP+ + +skiptoken = '$skiptoken' EQ qchar_no_AMP+ + +aliasAndValue = parameterAlias EQ parameterValue + +parameterValue = arrayOrObject + / commonExpr + +customQueryOption = customName ( EQ customValue )? +customName = !EQ !AT !"$" qchar_no_AMP ( !EQ qchar_no_AMP )* +customValue = qchar_no_AMP* + + +//------------------------------------------------------------------------------ +// 3. Context URL Fragments +//------------------------------------------------------------------------------ + +context = "#" contextFragment +contextFragment = 'Collection($ref)' + / '$ref' + / 'Collection(Edm.EntityType)' + / 'Collection(Edm.ComplexType)' + / singletonEntity ( navigation ( containmentNavigation )* ( "/" qualifiedEntityTypeName )? )? ( selectList )? + / qualifiedTypeName ( selectList )? + / entitySet ( '/$deletedEntity' / '/$link' / '/$deletedLink' ) + / entitySet keyPredicate "/" contextPropertyPath ( selectList )? + / entitySet ( selectList )? ( '/$entity' / '/$delta' )? + +entitySet = entitySetName ( containmentNavigation )* ( "/" qualifiedEntityTypeName )? + +containmentNavigation = keyPredicate ( "/" qualifiedEntityTypeName )? navigation +navigation = ( "/" complexProperty ( "/" qualifiedComplexTypeName )? )* "/" navigationProperty + +selectList = OPEN selectListItem ( COMMA selectListItem )* CLOSE +selectListItem = STAR // all structural properties + / allOperationsInSchema + / ( qualifiedEntityTypeName "/" )? + ( qualifiedActionName + / qualifiedFunctionName + / selectListProperty + ) +selectListProperty = primitiveProperty + / primitiveColProperty + / navigationProperty ( '+' )? ( selectList )? + / selectPath ( "/" selectListProperty )? + +contextPropertyPath = primitiveProperty + / primitiveColProperty + / complexColProperty + / complexProperty ( ( "/" qualifiedComplexTypeName )? "/" contextPropertyPath )? + + +//------------------------------------------------------------------------------ +// 4. Expressions +//------------------------------------------------------------------------------ + +// Note: a boolCommonExpr is also a commonExpr, e.g. sort by Boolean +commonExpr = ( primitiveLiteral + / parameterAlias + / arrayOrObject + / rootExpr + / firstMemberExpr + / functionExpr + / negateExpr + / methodCallExpr + / parenExpr + / castExpr + ) + ( addExpr + / subExpr + / mulExpr + / divExpr + / modExpr + )? + +boolCommonExpr = ( isofExpr + / boolMethodCallExpr + / notExpr + / commonExpr + ( eqExpr + / neExpr + / ltExpr + / leExpr + / gtExpr + / geExpr + / hasExpr + )? + / boolParenExpr + ) ( andExpr / orExpr )? + +rootExpr = '$root/' ( entitySetName keyPredicate / singletonEntity ) ( singleNavigationExpr )? + +firstMemberExpr = memberExpr + / inscopeVariableExpr ( "/" memberExpr )? + +memberExpr = ( qualifiedEntityTypeName "/" )? + ( propertyPathExpr + / boundFunctionExpr + ) + +propertyPathExpr = ( entityColNavigationProperty ( collectionNavigationExpr )? + / entityNavigationProperty ( singleNavigationExpr )? + / complexColProperty ( complexColPathExpr )? + / complexProperty ( complexPathExpr )? + / primitiveColProperty ( collectionPathExpr )? + / primitiveProperty ( primitivePathExpr )? + / streamProperty ( primitivePathExpr )? + ) + +inscopeVariableExpr = implicitVariableExpr + / lambdaVariableExpr // only allowed inside a lambdaPredicateExpr +implicitVariableExpr = '$it' // references the unnamed outer variable of the query +lambdaVariableExpr = odataIdentifier + +collectionNavigationExpr = ( "/" qualifiedEntityTypeName )? + ( keyPredicate ( singleNavigationExpr )? + / collectionPathExpr + )? + +singleNavigationExpr = "/" memberExpr + +complexColPathExpr = ( "/" qualifiedComplexTypeName )? + ( collectionPathExpr )? + +collectionPathExpr = count + / "/" boundFunctionExpr + / "/" anyExpr + / "/" allExpr + +complexPathExpr = ( "/" qualifiedComplexTypeName )? + ( "/" propertyPathExpr + / "/" boundFunctionExpr + )? + +primitivePathExpr = "/" boundFunctionExpr + +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 )? + / complexColFunction functionExprParameters ( complexColPathExpr )? + / complexFunction functionExprParameters ( complexPathExpr )? + / primitiveColFunction functionExprParameters ( collectionPathExpr )? + / primitiveFunction functionExprParameters ( primitivePathExpr )? + ) + +functionExprParameters = OPEN ( functionExprParameter ( COMMA functionExprParameter )* )? CLOSE +functionExprParameter = parameterName EQ ( parameterAlias / parameterValue ) + +anyExpr = 'any' OPEN BWS ( lambdaVariableExpr BWS COLON BWS lambdaPredicateExpr )? BWS CLOSE +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 + / yearMethodCallExpr + / monthMethodCallExpr + / dayMethodCallExpr + / hourMethodCallExpr + / minuteMethodCallExpr + / secondMethodCallExpr + / fractionalsecondsMethodCallExpr + / totalsecondsMethodCallExpr + / dateMethodCallExpr + / timeMethodCallExpr + / roundMethodCallExpr + / floorMethodCallExpr + / ceilingMethodCallExpr + / distanceMethodCallExpr + / geoLengthMethodCallExpr + / totalOffsetMinutesMethodCallExpr + / minDateTimeMethodCallExpr + / maxDateTimeMethodCallExpr + / nowMethodCallExpr + +boolMethodCallExpr = endsWithMethodCallExpr + / startsWithMethodCallExpr + / containsMethodCallExpr + / intersectsMethodCallExpr + +containsMethodCallExpr = 'contains' OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE +startsWithMethodCallExpr = 'startswith' OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE +endsWithMethodCallExpr = 'endswith' OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE +lengthMethodCallExpr = 'length' OPEN BWS commonExpr BWS CLOSE +indexOfMethodCallExpr = 'indexof' OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE +substringMethodCallExpr = 'substring' OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS ( COMMA BWS commonExpr BWS )? CLOSE +toLowerMethodCallExpr = 'tolower' OPEN BWS commonExpr BWS CLOSE +toUpperMethodCallExpr = 'toupper' OPEN BWS commonExpr BWS CLOSE +trimMethodCallExpr = 'trim' OPEN BWS commonExpr BWS CLOSE +concatMethodCallExpr = 'concat' OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE + +yearMethodCallExpr = 'year' OPEN BWS commonExpr BWS CLOSE +monthMethodCallExpr = 'month' OPEN BWS commonExpr BWS CLOSE +dayMethodCallExpr = 'day' OPEN BWS commonExpr BWS CLOSE +hourMethodCallExpr = 'hour' OPEN BWS commonExpr BWS CLOSE +minuteMethodCallExpr = 'minute' OPEN BWS commonExpr BWS CLOSE +secondMethodCallExpr = 'second' OPEN BWS commonExpr BWS CLOSE +fractionalsecondsMethodCallExpr = 'fractionalseconds' OPEN BWS commonExpr BWS CLOSE +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 + +minDateTimeMethodCallExpr = 'mindatetime' OPEN BWS CLOSE +maxDateTimeMethodCallExpr = 'maxdatetime' OPEN BWS CLOSE +nowMethodCallExpr = 'now' OPEN BWS CLOSE + +roundMethodCallExpr = 'round' OPEN BWS commonExpr BWS CLOSE +floorMethodCallExpr = 'floor' OPEN BWS commonExpr BWS CLOSE +ceilingMethodCallExpr = 'ceiling' OPEN BWS commonExpr BWS CLOSE + +distanceMethodCallExpr = 'geo.distance' OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE +geoLengthMethodCallExpr = 'geo.length' OPEN BWS commonExpr BWS CLOSE +intersectsMethodCallExpr = 'geo.intersects' OPEN BWS commonExpr BWS COMMA BWS commonExpr BWS CLOSE + +boolParenExpr = OPEN BWS boolCommonExpr BWS CLOSE +parenExpr = OPEN BWS commonExpr BWS CLOSE + +andExpr = RWS 'and' RWS boolCommonExpr +orExpr = RWS 'or' RWS boolCommonExpr + +eqExpr = RWS 'eq' RWS commonExpr +neExpr = RWS 'ne' RWS commonExpr +ltExpr = RWS 'lt' RWS commonExpr +leExpr = RWS 'le' RWS commonExpr +gtExpr = RWS 'gt' RWS commonExpr +geExpr = RWS 'ge' RWS commonExpr + +hasExpr = RWS 'has' RWS enum + +addExpr = RWS 'add' RWS commonExpr +subExpr = RWS 'sub' RWS commonExpr +mulExpr = RWS 'mul' RWS commonExpr +divExpr = RWS 'div' RWS commonExpr +modExpr = RWS 'mod' RWS commonExpr + +negateExpr = "-" BWS commonExpr + +notExpr = 'not' RWS boolCommonExpr + +isofExpr = 'isof' OPEN BWS ( commonExpr BWS COMMA BWS )? qualifiedTypeName BWS CLOSE +castExpr = 'cast' OPEN BWS ( commonExpr BWS COMMA BWS )? qualifiedTypeName BWS CLOSE + + +//------------------------------------------------------------------------------ +// 5. JSON format for function parameters +//------------------------------------------------------------------------------ +// Note: the query part of a URI needs to be partially percent-decoded before +// applying these rules, see comment at the top of this file +//------------------------------------------------------------------------------ + +arrayOrObject = complexColInUri + / complexInUri + / rootExprCol + / primitiveColInUri + +complexColInUri = begin_array + ( complexInUri ( value_separator complexInUri )* )? + end_array + +complexInUri = begin_object + ( ( annotationInUri + / primitivePropertyInUri + / complexPropertyInUri + / collectionPropertyInUri + / navigationPropertyInUri + ) + ( value_separator + ( annotationInUri + / primitivePropertyInUri + / complexPropertyInUri + / collectionPropertyInUri + / navigationPropertyInUri + ) + )* + )? + end_object + +collectionPropertyInUri = ( quotation_mark primitiveColProperty quotation_mark + name_separator + primitiveColInUri + ) + / ( quotation_mark complexColProperty quotation_mark + name_separator + complexColInUri + ) + +primitiveColInUri = begin_array + ( primitiveLiteralInJSON ( value_separator primitiveLiteralInJSON )* )? + end_array + +complexPropertyInUri = quotation_mark complexProperty quotation_mark + name_separator + complexInUri + +annotationInUri = quotation_mark AT namespace "." termName quotation_mark + name_separator + ( complexInUri / complexColInUri / primitiveLiteralInJSON / primitiveColInUri ) + +primitivePropertyInUri = quotation_mark primitiveProperty quotation_mark + name_separator + primitiveLiteralInJSON + +navigationPropertyInUri = singleNavPropInJSON + / collectionNavPropInJSON +singleNavPropInJSON = quotation_mark entityNavigationProperty quotation_mark + name_separator + rootExpr +collectionNavPropInJSON = quotation_mark entityColNavigationProperty quotation_mark + name_separator + rootExprCol + +rootExprCol = begin_array + ( rootExpr ( value_separator rootExpr )* )? + end_array + +// JSON syntax: adapted to URI restrictions from (RFC4627)? +begin_object = BWS ( "{" / "%7B" ) BWS +end_object = BWS ( "}" / "%7D" ) BWS + +begin_array = BWS ( "(" / "%5B" ) BWS +end_array = BWS ( ")?" / "%5D" ) BWS + +quotation_mark = DQUOTE / "%22" +name_separator = BWS COLON BWS +value_separator = BWS COMMA BWS + +primitiveLiteralInJSON = stringInJSON + / numberInJSON + / 'true' + / 'false' + / 'null' + +stringInJSON = quotation_mark charInJSON* 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 + / 'b' // backspace U+0008 + / 'f' // form feed U+000C + / 'n' // line feed U+000A + / 'r' // carriage return U+000D + / 't' // tab U+0009 + / 'u' HEXDIG HEXDIG HEXDIG HEXDIG // U+XXXX + ) + +qchar_JSON_special = SP / ":" / "{" / "}" / "(" / ")" // some agents put these unencoded into the query part of a URL +qchar_unescaped = unreserved / pct_encoded_unescaped / other_delims / ":" / "@" / "/" / "?" / "$" / "'" / "=" + +escape = "\\" / "%5C" // reverse solidus U+005C +unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" +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" ) + +numberInJSON = "-"? int frac? exp? +int = "0" / ( oneToNine *DIGIT ) +frac = "." DIGIT+ +exp = "e" ( "-" / "+" )? DIGIT+ + + +//------------------------------------------------------------------------------ +// 6. Names and identifiers +//------------------------------------------------------------------------------ + +singleQualifiedTypeName = qualifiedEntityTypeName + / qualifiedComplexTypeName + / qualifiedTypeDefinitionName + / qualifiedEnumTypeName + / primitiveTypeName + +qualifiedTypeName = singleQualifiedTypeName + / 'Collection' OPEN singleQualifiedTypeName CLOSE + +qualifiedEntityTypeName = namespace "." entityTypeName +qualifiedComplexTypeName = namespace "." complexTypeName +qualifiedTypeDefinitionName = namespace "." typeDefinitionName +qualifiedEnumTypeName = namespace "." enumerationTypeName + +// an alias is just a single-part namespace +namespace = namespacePart ( "." namespacePart )* +namespacePart = odataIdentifier + +entitySetName = odataIdentifier +singletonEntity = odataIdentifier +entityTypeName = odataIdentifier +complexTypeName = odataIdentifier +typeDefinitionName = odataIdentifier +enumerationTypeName = odataIdentifier +enumerationMember = odataIdentifier +termName = odataIdentifier + +// Note: this pattern is overly restrictive, the normative definition is type TSimpleIdentifier in OData EDM XML Schema +odataIdentifier = $(identifierLeadingCharacter identifierCharacter*) +identifierLeadingCharacter = ALPHA / "_" // plus Unicode characters from the categories L or Nl +identifierCharacter = ALPHA / "_" / DIGIT // plus Unicode characters from the categories L, Nl, Nd, Mn, Mc, Pc, or Cf + +primitiveTypeName = 'Edm.' ( 'Binary' + / 'Boolean' + / 'Byte' + / 'Date' + / 'DateTimeOffset' + / 'Decimal' + / 'Double' + / 'Duration' + / 'Guid' + / 'Int16' + / 'Int32' + / 'Int64' + / 'SByte' + / 'Single' + / 'Stream' + / 'String' + / 'TimeOfDay' + / abstractSpatialTypeName ( concreteSpatialTypeName )? + ) +abstractSpatialTypeName = 'Geography' + / 'Geometry' +concreteSpatialTypeName = 'Collection' + / 'LineString' + / 'MultiLineString' + / 'MultiPoint' + / 'MultiPolygon' + / 'Point' + / 'Polygon' + +primitiveProperty = primitiveKeyProperty / primitiveNonKeyProperty +primitiveKeyProperty = odataIdentifier +primitiveNonKeyProperty = odataIdentifier +primitiveColProperty = odataIdentifier +complexProperty = odataIdentifier +complexColProperty = odataIdentifier +streamProperty = odataIdentifier + +navigationProperty = entityNavigationProperty / entityColNavigationProperty +entityNavigationProperty = odataIdentifier +entityColNavigationProperty = odataIdentifier + +action = odataIdentifier +actionImport = odataIdentifier + +function = entityFunction + / entityColFunction + / complexFunction + / complexColFunction + / primitiveFunction + / primitiveColFunction + +entityFunction = odataIdentifier +entityColFunction = odataIdentifier +complexFunction = odataIdentifier +complexColFunction = odataIdentifier +primitiveFunction = odataIdentifier +primitiveColFunction = odataIdentifier + +entityFunctionImport = odataIdentifier +entityColFunctionImport = odataIdentifier +complexFunctionImport = odataIdentifier +complexColFunctionImport = odataIdentifier +primitiveFunctionImport = odataIdentifier +primitiveColFunctionImport = odataIdentifier + + +//------------------------------------------------------------------------------ +// 7. Literal Data Values +//------------------------------------------------------------------------------ + +// in URLs +primitiveLiteral = nullValue // plain values up to int64Value + / booleanValue + / guidValue + / dateValue + / dateTimeOffsetValue + / timeOfDayValue + / decimalValue + / doubleValue + / singleValue + / sbyteValue + / byteValue + / int16Value + / int32Value + / int64Value + / string // single-quoted + / duration // all others are quoted and prefixed + / binary + / enum + / geographyCollection + / geographyLineString + / geographyMultiLineString + / geographyMultiPoint + / geographyMultiPolygon + / geographyPoint + / geographyPolygon + / geometryCollection + / geometryLineString + / geometryMultiLineString + / geometryMultiPoint + / geometryMultiPolygon + / geometryPoint + / geometryPolygon + +// in Atom and JSON message bodies and CSDL DefaultValue attributes +primitiveValue = booleanValue + / guidValue + / durationValue + / dateValue + / dateTimeOffsetValue + / timeOfDayValue + / enumValue + / fullCollectionLiteral + / fullLineStringLiteral + / fullMultiPointLiteral + / fullMultiLineStringLiteral + / fullMultiPolygonLiteral + / fullPointLiteral + / fullPolygonLiteral + / decimalValue + / doubleValue + / singleValue + / sbyteValue + / byteValue + / int16Value + / int32Value + / int64Value + / binaryValue + // also valid are: + // - any XML string for strings in Atom and CSDL documents + // - any JSON string for JSON documents + +nullValue = 'null' {return {val:null}} + +// base64url encoding according to http://tools.ietf.org/html/rfc4648#section-5 +binary = "binary" SQUOTE binaryValue SQUOTE +binaryValue = (base64char base64char base64char base64char)* ( base64b16 / base64b8 )? +base64b16 = base64char base64char ( 'A' / 'E' / 'I' / 'M' / 'Q' / 'U' / 'Y' / 'c' / 'g' / 'k' / 'o' / 's' / 'w' / '0' / '4' / '8' ) ( "=" )? +base64b8 = base64char ( 'A' / 'Q' / 'g' / 'w' ) ( "==" )? +base64char = ALPHA / DIGIT / "-" / "_" + +booleanValue = val:( true / false ) {return {val}} +true = "true" {return true} +false = "false" {return false} + +decimalValue = s:$((SIGN)? DIGIT+ ("." DIGIT+)?) +{return {val:Number(s)}} + +doubleValue = s:$(decimalValue ( "e" (SIGN)? DIGIT+ )? / nanInfinity) // IEEE 754 binary64 floating-point number (15-17 decimal digits) +{return {val:Number(s)}} + +singleValue = doubleValue // IEEE 754 binary32 floating-point number (6-9 decimal digits) +nanInfinity = 'NaN' / '-INF' / 'INF' + +guidValue = $( + /* 8 */ HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG + /* 4 */ "-" HEXDIG HEXDIG HEXDIG HEXDIG + /* 4 */ "-" HEXDIG HEXDIG HEXDIG HEXDIG + /* 4 */ "-" HEXDIG HEXDIG HEXDIG HEXDIG + /* 12 */ "-" HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG HEXDIG +) + +byteValue = ( DIGIT DIGIT DIGIT )+ // numbers in the range from 0 to 255 +sbyteValue = sign? byteValue // numbers in the range from -128 to 127 +int16Value = sign? ( DIGIT DIGIT DIGIT DIGIT DIGIT )+ // numbers in the range from -32768 to 32767 +int32Value = sign? ( DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT )+ // numbers in the range from -2147483648 to 2147483647 +int64Value = sign? ( DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT )+ // numbers in the range from -9223372036854775808 to 9223372036854775807 +sign = "+" / "-" + +string = SQUOTE ( SQUOTE_in_string / !SQUOTE pchar )* SQUOTE +SQUOTE_in_string = SQUOTE SQUOTE // two consecutive single quotes represent one within a string literal + +qchar_no_AMP = unreserved / pct_encoded / other_delims / ":" / "@" / "/" / "?" / "$" / "'" / "=" +pchar = unreserved / pct_encoded / sub_delims / ":" / "@" +pct_encoded = "%" HEXDIG HEXDIG +sub_delims = "$" / "&" / "'" / "=" / other_delims +other_delims = "!" / "(" / ")" / "*" / "+" / "," / ";" + +dateValue = year "-" month "-" day + +dateTimeOffsetValue = year "-" month "-" day "T" hour ":" minute ( ":" second ( "." fractionalSeconds )? )? ( "Z" / sign hour ":" minute ) + +duration = "duration" SQUOTE durationValue SQUOTE +durationValue = sign? "P" ( DIGIT+ "D" )? ( "T" ( DIGIT+ "H" )? ( DIGIT+ "M" )? ( DIGIT+ ( "." DIGIT+ )? "S" )? )? + // the above is an approximation of the rules for an xml dayTimeDuration. + // see the lexical representation for dayTimeDuration in http://www.w3.org/TR/xmlschema11-2#dayTimeDuration for more information + +timeOfDayValue = hour ":" minute ( ":" second ( "." fractionalSeconds )? )? + +oneToNine = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" +zeroToFiftyNine = ( "0" / "1" / "2" / "3" / "4" / "5" ) DIGIT +year = ( "-" )? ( "0" ( DIGIT DIGIT DIGIT ) / oneToNine DIGIT DIGIT DIGIT ) +month = "0" oneToNine + / "1" ( "0" / "1" / "2" ) +day = "0" oneToNine + / ( "1" / "2" ) DIGIT + / "3" ( "0" / "1" ) +hour = ( "0" / "1" ) DIGIT + / "2" ( "0" / "1" / "2" / "3" ) +minute = zeroToFiftyNine +second = zeroToFiftyNine +fractionalSeconds = DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT + +enum = qualifiedEnumTypeName SQUOTE enumValue SQUOTE +enumValue = singleEnumValue ( COMMA singleEnumValue )* +singleEnumValue = enumerationMember / enumMemberValue +enumMemberValue = int64Value + +geographyCollection = geographyPrefix SQUOTE fullCollectionLiteral SQUOTE +fullCollectionLiteral = sridLiteral collectionLiteral +collectionLiteral = "Collection(" geoLiteral ( COMMA geoLiteral )* CLOSE +geoLiteral = collectionLiteral + / lineStringLiteral + / multiPointLiteral + / multiLineStringLiteral + / multiPolygonLiteral + / pointLiteral + / polygonLiteral + +geographyLineString = geographyPrefix SQUOTE fullLineStringLiteral SQUOTE +fullLineStringLiteral = sridLiteral lineStringLiteral +lineStringLiteral = "LineString" lineStringData +lineStringData = OPEN positionLiteral ( COMMA positionLiteral )+ CLOSE + +geographyMultiLineString = geographyPrefix SQUOTE fullMultiLineStringLiteral SQUOTE +fullMultiLineStringLiteral = sridLiteral multiLineStringLiteral +multiLineStringLiteral = "MultiLineString(" ( lineStringData ( COMMA lineStringData )* )? CLOSE + +geographyMultiPoint = geographyPrefix SQUOTE fullMultiPointLiteral SQUOTE +fullMultiPointLiteral = sridLiteral multiPointLiteral +multiPointLiteral = "MultiPoint(" ( pointData ( COMMA pointData )* )? CLOSE + +geographyMultiPolygon = geographyPrefix SQUOTE fullMultiPolygonLiteral SQUOTE +fullMultiPolygonLiteral = sridLiteral multiPolygonLiteral +multiPolygonLiteral = "MultiPolygon(" ( polygonData ( COMMA polygonData )* )? CLOSE + +geographyPoint = geographyPrefix SQUOTE fullPointLiteral SQUOTE +fullPointLiteral = sridLiteral pointLiteral +sridLiteral = "SRID" EQ DIGIT DIGIT DIGIT DIGIT DIGIT SEMI +pointLiteral ="Point" pointData +pointData = OPEN positionLiteral CLOSE +positionLiteral = doubleValue SP doubleValue // longitude, then latitude + +geographyPolygon = geographyPrefix SQUOTE fullPolygonLiteral SQUOTE +fullPolygonLiteral = sridLiteral polygonLiteral +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. + // In order of traversal, points to the left side of the ring are interpreted as being in the polygon. + +geometryCollection = geometryPrefix SQUOTE fullCollectionLiteral SQUOTE +geometryLineString = geometryPrefix SQUOTE fullLineStringLiteral SQUOTE +geometryMultiLineString = geometryPrefix SQUOTE fullMultiLineStringLiteral SQUOTE +geometryMultiPoint = geometryPrefix SQUOTE fullMultiPointLiteral SQUOTE +geometryMultiPolygon = geometryPrefix SQUOTE fullMultiPolygonLiteral SQUOTE +geometryPoint = geometryPrefix SQUOTE fullPointLiteral SQUOTE +geometryPolygon = geometryPrefix SQUOTE fullPolygonLiteral SQUOTE + +geographyPrefix = "geography" +geometryPrefix = "geometry" + + +//------------------------------------------------------------------------------ +// 8. Header values +//------------------------------------------------------------------------------ + +header = content_id + / odata_entityid + / odata_isolation + / odata_maxversion + / odata_version + / prefer + +content_id = "Content-ID" ":" OWS unreserved+ + +odata_entityid = "OData-EntityID" ":" OWS ![^&]+ //was: IRI-in-header +odata_isolation = "OData-Isolation" ":" OWS "snapshot" +odata_maxversion = "OData-MaxVersion" ":" OWS DIGIT+ "." DIGIT+ +odata_version = "OData-Version" ":" OWS "4.0" + +prefer = "Prefer" ":" OWS preference ( COMMA preference )* +preference = allowEntityReferencesPreference + / callbackPreference + / continueOnErrorPreference + / includeAnnotationsPreference + / maxpagesizePreference + / respondAsyncPreference + / returnPreference + / trackChangesPreference + / waitPreference + // and everything allowed by http://tools.ietf.org/html/draft-snell-http-prefer-18 + // / token ( EQ_h word )? ( OWS ";" ( OWS parameter )? )* + +allowEntityReferencesPreference = "odata.allow-entityreferences" + +callbackPreference = "odata.callback" OWS ";" OWS "url" EQ_h DQUOTE [^"]* DQUOTE + +continueOnErrorPreference = "odata.continue-on-error" + +includeAnnotationsPreference = "odata.include-annotations" EQ_h DQUOTE annotationsList DQUOTE +annotationsList = annotationIdentifier (COMMA annotationIdentifier)* +annotationIdentifier = ( excludeOperator )? + ( STAR + / namespace "." ( termName / STAR ) + ) + ( "#" odataIdentifier )? +excludeOperator = "-" + +maxpagesizePreference = "odata.maxpagesize" EQ_h oneToNine *DIGIT + +respondAsyncPreference = "respond-async" + +returnPreference = "return" EQ_h ( 'representation' / 'minimal' ) + +trackChangesPreference = "odata.track-changes" + +waitPreference = "wait" EQ_h DIGIT+ + +//parameter = token ( EQ_h word )? +//word = token / quoted-string +//token = 1*tchar +//tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" +// / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" +// / DIGIT / ALPHA +//quoted-string = DQUOTE ( qdtext / quoted-pair )* DQUOTE +//qdtext = %x21 / %x23-5B / %x5D-7E / obs_text / OWS +//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 + + +//------------------------------------------------------------------------------ +// 9. Punctuation +//------------------------------------------------------------------------------ + +RWS = ( SP / HTAB / "%20" / "%09" )+ // "required" whitespace +BWS = ( SP / HTAB / "%20" / "%09" )* // "bad" whitespace + +AT = "@" / "%40" +COLON = ":" / "%3A" +COMMA = "," / "%2C" +EQ = "=" +SIGN = "+" / "%2B" / "-" +SEMI = ";" / "%3B" +STAR = "*" / "%2A" +SQUOTE = "'" / "%27" + +OPEN = "(" / "%28" +CLOSE = ")" / "%29" + + + +//------------------------------------------------------------------------------ +// C. ABNF core definitions [RFC5234] +//------------------------------------------------------------------------------ + +ALPHA = [A-Za-z] +DIGIT = [0-9] +HEXDIG = DIGIT / A_to_F +A_to_F = "A" / "B" / "C" / "D" / "E" / "F" +DQUOTE = '"' +SP = " " +HTAB = "\t" +//WSP = SP / HTAB +//LWSP = (WSP / CRLF WSP)* +VCHAR = [!-~] +//CHAR = %x01-7F +//LOCTET = %x00-FF +//CR = %x0D +//LF = %x0A +//CRLF = CR LF +//BIT = "0" / "1" + + +//------------------------------------------------------------------------------ +// End of odata-abnf-construction-rules +//------------------------------------------------------------------------------ \ No newline at end of file diff --git a/odata/lib/odata2cqn.pegjs b/odata/lib/odata2cqn.pegjs index e9fcf3f6..4727c88c 100644 --- a/odata/lib/odata2cqn.pegjs +++ b/odata/lib/odata2cqn.pegjs @@ -1,4 +1,4 @@ -/** +/** ------------------------------------------ * Odata Spec http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/abnf/odata-abnf-construction-rules.txt * Future test cases http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/abnf/odata-abnf-testcases.xml * @@ -12,546 +12,178 @@ * Books/201 * Books?$select=ID,title&$expand=author($select=name)&$filter=stock gt 1&$orderby=title */ -{ - const $=Object.assign - const SELECT={}; - const stack=[]; - let columns=[]; - let filterExpr; - const select = (col) => { - if (!SELECT.columns) SELECT.columns = columns - columns.push(col) - } - const expand = (col) => { - select (col) - stack.push (columns) - columns = col.expand = [] - } - const end = ()=> columns = stack.pop() - const compOperators = { - eq: '=', - ne: '!=', - lt: '<', - gt: '>', - le: '<=', - ge: '>=', - } - -} - -start = ODataRelativeURI - -ODataRelativeURI // Note: case-sensitive! - = (p:ref { SELECT.from = p }) ( o"?"o QueryOptions )? - {return { SELECT }} - -QueryOptions = ( - "$expand=" expand / - "$select=" select / - "$top=" top / - "$skip=" skip / - "$count=" count / - "$filter=" filter / - "$orderby=" orderby -)( o'&'o QueryOptions )? - - -filter - = (o { SELECT.where = [] }) - FilterExprSequence - - -// ---------- Grouped $filter expression ---------- -// ---------- - -FilterExprSequence = (Expr (SP logicalOperator SP Expr)*) -GroupedExpr = (startGroup FilterExprSequence closeGroup) -Expr = ( - (notOperator SP)? ( boolFunc / GroupedExpr ) - ) / ( commonExp ) -startGroup - = OPEN - { SELECT.where.push('(') } -closeGroup - = CLOSE - { SELECT.where.push(')') } - - -// ---------- Function expressions ---------- -// ---------- -commonExp = val:( - timeExpr / - secondExpr / - minuteExpr / - hourExpr / - dayExpr / - monthExpr / - yearExpr / - compStrExpr / - compareNumExpr - ) - { - const res = val.filter(cur => cur !== ' '); - SELECT.where.push(...res) - } - -compStrExpr - = firstArgObj:strArg - SP operatorVal:eqOperator SP - secondArgObj:strArg -compareNumExpr - = firstArgObj:numberArg - SP operatorVal:( eqOperator / numCompOperator ) SP - secondArgObj:numberArg -dayExpr - = firstArg:(dayFunc / dayVal) - SP operatorVal:( eqOperator / numCompOperator ) SP - secondArg:(dayFunc / dayVal) -hourExpr - = firstArg:(hourFunc / hourVal) - SP operatorVal:( eqOperator / numCompOperator ) SP - secondArg:(hourFunc / hourVal) -minuteExpr - = firstArg:( minuteFunc / minuteVal ) - SP operatorVal:( eqOperator / numCompOperator ) SP - secondArg:( minuteFunc / minuteVal ) -monthExpr - = firstArg:(monthFunc / monthVal) - SP operatorVal:( eqOperator / numCompOperator ) SP - secondArg:(monthFunc / monthVal) -secondExpr - = firstArg:(secondFunc / secondVal) - SP operatorVal:( eqOperator / numCompOperator ) SP - secondArg:(secondFunc / secondVal) -yearExpr - = firstArg:(yearFunc / yearVal) - SP operatorVal:( eqOperator / numCompOperator ) SP - secondArg:(yearFunc / yearVal) -timeExpr - = firstArg:(timeFunc / timeOfDayValue) - SP operatorVal:( numCompOperator / eqOperator ) SP - secondArg:(timeFunc / timeOfDayValue) - -strArg = ( - substringFunc / - tolowerFunc / - toupperFunc / - trimFunc / - concatFunc / - strLiteral / - field -) -numberArg = ( - lengthFunc / - indexofFunc / - roundFunc / - number / - field -) - -// ---------- Functions ---------- -// -// ---------- "contains" / "endswith" / "startswith" ---------- -boolFunc - = funcName:( "contains" / "endswith" / "startswith" ) - OPEN - fieldRef:strArg COMMA - value:strArg - CLOSE - { - const args = { - contains: [ "'%'", value, "'%'" ], - endswith: [ "'%'", value ], - startswith: [ value, "'%'" ] - }[funcName] - SELECT.where.push( - fieldRef, 'like', {func:'concat', args }, 'escape', "'^'" - ) - } -// ---------- "length" ---------- -lengthFunc - = "length" - OPEN - fieldRef:strArg - CLOSE - { - return { - func: 'length', - args: [ fieldRef ] - } - } -// ---------- "indexof" ---------- -indexofFunc - = "indexof" - OPEN - fieldRef:strArg COMMA - strArgVal:strArg - CLOSE - { - return { - func: 'locate', - args: [ fieldRef, strArgVal ] - } - } -// ---------- "substring" ---------- -substringFunc - = "substring" - OPEN - fieldRef:strArg COMMA - arg:( - (numberArg COMMA numberArg) / - numberArg - ) - CLOSE - { - const args = Array.isArray(arg) ? - [ - fieldRef, - ...arg.filter(cur => cur !== ',') - ] : - [fieldRef, arg]; - return { - func: 'substring', - args: args - } - } -// ---------- "tolower" ---------- -tolowerFunc - = "tolower" - OPEN - fieldRef:strArg - CLOSE - { - return { - func: 'lower', - args: [ fieldRef ] - } - } -// ---------- "toupper" ---------- -toupperFunc - = "toupper" - OPEN - fieldRef:strArg - CLOSE - { - return { - func: 'upper', - args: [ fieldRef ] - } - } -// ---------- "trim" ---------- -trimFunc - = "trim" - OPEN - fieldRef:strArg - CLOSE - { - return { - func: 'trim', - args: [ fieldRef ] - } - } -// ---------- "concat" ---------- -concatFunc - = "concat" - OPEN - fieldRef:strArg COMMA - strArgVal:strArg - CLOSE - { - return { - func: 'concat', - args: [ fieldRef, strArgVal ] - } - } -// ---------- "day" ---------- -dayFunc - = "day" - OPEN - fieldRef:(dateTimeOffsetValue / dateValue / field) - CLOSE - { - return { - func: 'dayofmonth', - args: [ fieldRef ] - } - } - -// ---------- "hour" ---------- -hourFunc - = "hour" - OPEN - fieldRef:(dateTimeOffsetValue / timeOfDayValue / field) - CLOSE - { - return { - func: 'hour', - args: [ fieldRef ] - } - } - -// ---------- "minute" ---------- -minuteFunc - = "minute" - OPEN - fieldRef:(dateTimeOffsetValue / timeOfDayValue / field) - CLOSE - { - return { - func: 'minute', - args: [ fieldRef ] - } - } - -// ---------- "month" ---------- -monthFunc - = "month" - OPEN - fieldRef:(dateTimeOffsetValue / dateValue / field) - CLOSE - { - return { - func: 'month', - args: [ fieldRef ] - } - } - -// ---------- "second" ---------- -secondFunc - = "second" - OPEN - fieldRef:(dateTimeOffsetValue / timeOfDayValue / field) - CLOSE - { - return { - func: 'second', - args: [ fieldRef ] - } - } - -// ---------- "year" ---------- -yearFunc - = "year" - OPEN - fieldRef:(dateTimeOffsetValue / dateValue / field) - CLOSE - { - return { - func: 'year', - args: [ fieldRef ] - } - } - -// ---------- "time" ---------- -timeFunc - = "time" - OPEN - fieldRef:(dateTimeOffsetValue / field) - CLOSE - { - return { - func: 'to_time', - args: [ fieldRef ] - } - } - -// ---------- "round" ---------- -roundFunc - = "round" - OPEN - fieldRef:(dateTimeOffsetValue / field) - CLOSE - { - return { - func: 'round', - args: [ fieldRef ] - } - } - - -expand - = ((c:ref {expand(c)}) (o'('o QueryOptions o')'o)? {end()}) - (o','o expand)? - -select - = (c:ref {select(c)}) - (o','o select)? - -top - = n:$[0-9]+ - { (SELECT.limit || (SELECT.limit={})).rows = { val: parseInt(n) } } - -skip - = n:$[0-9]+ - { (SELECT.limit || (SELECT.limit={})).offset = { val: parseInt(n) } } - -other "other query options" - = o:$([^=]+) "=" x:todo - { SELECT[o.slice(1)] = x } - -ref "a reference" - = p:$[^,?&()]+ { - const reffs = p.split('/'); - if(reffs.length === 2 && reffs[reffs.length-1] === '$count') { - SELECT.count = true; - } - return { ref: [reffs[0]] }; - } - -todo = $[^,?&]+ - -count = c:$[^,?&()]+ - { if(c === 'true') SELECT.count = true } - -orderby = o:$[^,?&()]+ +// ---------- JavaScript Helpers ------------- { - const matches = o.match(/\w+\s(asc|desc)/g); - if(!!matches) { - const out = matches[0].split(/\s/g); - SELECT.orderby = [ - { ref: [out[0]], sort: out[1] } - ] - } + const stack=[] + let SELECT } +// ---------- Entity Paths --------------- -// Primitive literals -// -// date -dateValue "Edm.Date" = val:$( year "-" month "-" day ) - { return { val } } -year = "-"? ( "0" DIGIT DIGIT DIGIT / oneToNine DIGIT DIGIT DIGIT ) -yearVal = val:$year { return { val } } -month = "0" oneToNine - / "1" ( "0" / "1" / "2" ) -monthVal = val:$month { return { val } } -day = "0" oneToNine - / ( "1" / "2" ) DIGIT - / "3" ( "0" / "1" ) -dayVal = val:$day { return { val } } -oneToNine = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" - -// datetime offset -dateTimeOffsetValue "Edm.DateTimeOffset" = val:$( year "-" month "-" day "T" hour ":" minute ( ":" second ( "." fractionalSeconds )? )? ( "Z" / SIGN hour ":" minute )) - { return { val: (new Date(val)).toISOString() } } -hour = ( "0" / "1" ) DIGIT - / "2" ( "0" / "1" / "2" / "3" ) -hourVal = val:$hour { return { val } } -minute = zeroToFiftyNine -minuteVal = val:$minute { return { val } } -second = zeroToFiftyNine -secondVal = val:$second { return { val: parseInt(val).toFixed(3) } } -zeroToFiftyNine = ( "0" / "1" / "2" / "3" / "4" / "5" ) DIGIT -fractionalSeconds = DIGIT DIGIT? DIGIT? DIGIT? - DIGIT? DIGIT? DIGIT? DIGIT? - DIGIT? DIGIT? DIGIT? DIGIT? - -// time of day value -timeOfDayValue "Edm.TimeOfDay" = val:$( hour ":" minute ( ":" second ( "." fractionalSeconds )? )? ) - { return { val } } - -// string -strLiteral "Edm.String" = SQUOTE strArgVal:strEntry SQUOTE - { return strArgVal; } -strEntry = symbols:( ( SQUOTEInString / pcharNoSQUOTE )* ) - { return { val: symbols.join('') }; } - -// field name -field "field name" - = field:$([a-zA-Z] [_a-zA-Z0-9]*) - { return { ref: [field] }; } - -// number -number = val:$( - doubleValue / - singleValue / - decimalValue / - int64Value / - int32Value / - int16Value - ) { - return { val: Number(val) }; + ODataRelativeURI // Note: case-sensitive! + = (p:path { SELECT = { from:p } }) + ( o"?"o QueryOption ( o'&'o QueryOption )* )? { + if (SELECT.expand) { + SELECT.columns = SELECT.expand + delete SELECT.expand + } + return { SELECT } } -// IEEE 754 binary64 floating-point number (15-17 decimal digits) -doubleValue "Edm.Double" = decimalValue ( "e" SIGN? DIGIT+ )? / nanInfinity -decimalValue "Edm.Decimal" = SIGN? DIGIT+ ("." DIGIT+)? -// IEEE 754 binary32 floating-point number (6-9 decimal digits) -singleValue "Edm.Single" = doubleValue -// numbers in the range from -9223372036854775808 to 9223372036854775807 -// contains 1-19 digits -int64Value "Edm.Int64" = SIGN? DIGIT DIGIT? DIGIT? DIGIT? DIGIT? - DIGIT? DIGIT? DIGIT? DIGIT? DIGIT? - DIGIT? DIGIT? DIGIT? DIGIT? DIGIT? - DIGIT? DIGIT? DIGIT? DIGIT? -// numbers in the range from -2147483648 to 2147483647 -// contains 1-15 digits -int32Value "Emd.Int32" - = val:(SIGN? DIGIT DIGIT? DIGIT? DIGIT? DIGIT? - DIGIT? DIGIT? DIGIT? DIGIT? DIGIT? - DIGIT? DIGIT? DIGIT? DIGIT? DIGIT?) - { return parseInt(val); } -// numbers in the range from -32768 to 32767 -// contains 1-5 digits -int16Value "Edm.Int16" = SIGN? DIGIT DIGIT? DIGIT? DIGIT? DIGIT? -nanInfinity = 'NaN' / '-INF' / 'INF' + path + = crv:$("$count"/"$ref"/"$value") {return {ref:[crv]}} + / head:identifier filter:(OPEN args CLOSE)? tail:( '/' p:path {return p} )? { + const ref = [ filter ? { id:head, where:filter[1] } : head ] + if (tail) ref.push (...tail.ref) + return {ref} + } + + args + = val:( number / integer / string ) {return [{val}]} + / ref:identifier o"="o val:( number / integer / string ) more:( COMMA args )? { + const args = [ {ref}, '=', {val} ] + if (more) args.push ('and', ...more[1]) + return args + } + + ref "a reference" + = head:identifier tail:( '/' identifier )* { + return { ref:[ head, ...tail ] } + } + + // +// ---------- Query Options ------------ + + QueryOption = ExpandOption + ExpandOption = + "$select=" o select ( COMMA select )* / + "$expand=" o expand ( COMMA expand )* / + "$filter=" o filter / + "$orderby=" o orderby / + "$top=" o top / + "$skip=" o skip / + "$search=" o search / + "$count=" o count + + select + = col:ref { + (SELECT.expand || (SELECT.expand = [])).push(col) + return col + } + + expand = + ( c:select {c.expand='*'} ) + ( // --- nested query options, if any + (OPEN { + stack.push (SELECT) + SELECT = SELECT.expand[SELECT.expand.length-1] + SELECT.expand = [] + }) + ExpandOption ( o";"o ExpandOption )* + (CLOSE { + SELECT = stack.pop() + }) + )? // --- end of nested query options + ( COMMA expand )? + + top + = val:integer { + (SELECT.limit || (SELECT.limit={})).rows = {val} + } + + skip + = val:integer { + (SELECT.limit || (SELECT.limit={})).offset = {val} + } + + search + = p:search_clause {SELECT.search = p} + search_clause = p:( n:NOT? {return n?[n]:[]} )( + OPEN xpr:search_clause CLOSE {p.push({xpr})} + / val:(identifier/string) {p.push({val})} + )( ao:(AND/OR) more:search_clause {p.push(ao,...more)} )* + {return p} + + filter + = p:where_clause {SELECT.where = p} + where_clause = p:( n:NOT? {return n?[n]:[]} )( + OPEN xpr:where_clause CLOSE {p.push({xpr})} + / comp:comparison {p.push(...comp)} + / func:boolish {p.push(func)} + )( ao:(AND/OR) more:where_clause {p.push(ao,...more)} )* + {return p} + + orderby + = ref:ref sort:( _ s:$("asc"/"desc") {return s})? { + SELECT.orderby = $(ref, sort && {sort}) + } + + count + = c:$[^,?&()]+ { SELECT.count = true } + + // +// ---------- Expressions ------------ -// ---------- URI sintax ---------- -// -otherDelims = "!" / "(" / ")" / "*" / "+" / "," / ";" -unreserved = [A-Za-z] / [0-9] / " " / "-" / "." / "_" / "~" -pcharNoSQUOTE = val:$(unreserved / otherDelims / "$" / "&" / "=" / ":" / "@") - { return val; } -SQUOTEInString = SQUOTE SQUOTE // two consecutive single quotes represent one within a string literal - { return "'"; } // convert double quotation mark to single like in current CAP implementaion + comparison "a comparison" + = a:operand _ o:$("eq"/"ne"/"lt"/"gt"/"le"/"ge") _ b:operand { + const op = { eq:'=', ne:'!=', lt:'<', gt:'>', le:'<=', ge:'>=' }[o]||o + return [ a, op, b ] + } + operand "an operand" + = val:number {return {val}} + / val:string {return {val}} + / function + / ref + + function "a function call" + = func:$[a-z]+ OPEN a:operand more:( COMMA o:operand {return o} )* CLOSE + { return { func, args:[a,...more] }} + + boolish "a boolean function" + = func:("contains"/"endswith"/"startswith") OPEN a:operand COMMA b:operand CLOSE + { return { func, args:[a,b] }} + + NOT = o "not"i _ {return 'not'} + AND = _ "and"i _ {return 'and'} + OR = _ "or"i _ {return 'or'} + + + // +// ---------- Literals ----------- + + string "Edm.String" + = "'" s:$("''"/[^'])* "'" + {return s.replace(/''/g,"'")} + + number + = x:$( [+-]? [0-9]+ ("."[0-9]+)? ("e"[0-9]+)? ) + {return Number(x)} + + integer + = x:$( [+-]? [0-9]+ ) + {return parseInt(x)} + + identifier + = $([a-zA-Z][_a-zA-Z0-9]*) + + + // // ---------- Punctuation ---------- -// -WS "whitespace" = ( SP / HTAB )* -AT = "@" -COLON = ":" -COMMA = "," -EQ = "=" -SIGN = "+" / "-" -SEMI = ";" -STAR = "*" -SQUOTE = "'" -OPEN = "(" -CLOSE = ")" + COLON = o":"o + COMMA = o","o + SEMI = o";"o + OPEN = o"("o + CLOSE = o")" -// ---------- Operators ---------- -// -eqOperator - = operatorVal:("eq" / "ne") - { return compOperators[operatorVal]; } -numCompOperator - = operatorVal:("lt" / "gt" / "le" / "ge") - { return compOperators[operatorVal]; } -logicalOperator = operator:$("and" / "or") - { SELECT.where.push(operator); } -notOperator - = "not" - { SELECT.where.push('not') } + // +// ---------- Whitespaces ----------- + o "optional whitespaces" = $[ \t\n]* + _ "mandatory whitespaces" = $[ \t\n]+ -// -// ---------- ABNF core definitions ---------- -// -ALPHA "letter" = [A-Za-z] -AtoF "A to F" = "A" / "B" / "C" / "D" / "E" / "F" -DIGIT "digit" = [0-9] -HEXDIG "hexing" = DIGIT / AtoF -DQUOTE "double quote" = '"' -SP "space" = ' ' -HTAB "horizontal tab" = ' ' -WSP "white space" = SP / HTAB -BIT = "0" / "1" -RWS = ( SP / HTAB )+ // "required" whitespace - - -//-- Whitespaces -o "optional whitespaces" = $[ \t\n]* -_ "mandatory whitespaces" = $[ \t\n]+ + // +// ------------------------------------ diff --git a/odata/package.json b/odata/package.json index bea51578..f22be73c 100644 --- a/odata/package.json +++ b/odata/package.json @@ -2,7 +2,11 @@ "name": "@sap/cds-odata", "version": "1.0.0", "dependencies": { - "@capire/bookshop": "*" + "@sap/cds-compiler": "latest", + "@sap/cds": "^4.4.10" + }, + "scripts": { + "postinstall": "rm -fr node_modules/@sap/cds/node_modules" }, "devDependencies": { "pegjs": "^0.10.0" diff --git a/odata/test/odata2cqn.test.js b/odata/test/odata2cqn.test.js index d499baee..7606d26f 100644 --- a/odata/test/odata2cqn.test.js +++ b/odata/test/odata2cqn.test.js @@ -2,19 +2,13 @@ const { parse:{cql:CQL}, test:{expect}} = require("@sap/cds/lib") const { parser:{parse:OData} } = require("../lib/odata2cqn") describe("$filter", () => { - // logger can be omitted - // afterEach(function () { - // logParserResult(newParserRes, `${this.currentTest.title}-new`); - // logParserResult(currentParserRes, `${this.currentTest.title}-cur`); - // }); describe("comparing expressions", () => { const types = { strings: "'some string'", - 'safe integers': 11, - 'unsafe integers': 2e53, - decimals: 0.99, + integers: 11, + // decimals: 0.99, //> REVISIT: wait for compiler v2.0.4 ? // ... } it.each(Object.keys(types))("should support expressions with %s", (t) => { @@ -41,20 +35,21 @@ describe("$filter", () => { describe("logical expressions", () => { it.each(['and','or'])("should support '%s'", (t) => { - expect (OData(`Foo?$filter=bar lt 11 and name eq 'some name'`)) - .to.eql (CQL(`SELECT from Foo where bar < 11 and name = 'some name'`)) + expect (OData(`Foo?$filter=bar lt 11 ${t} name eq 'some name'`)) + .to.eql (CQL(`SELECT from Foo where bar < 11 ${t} name = 'some name'`)) }) it("should support 'not'", () => { // REVISIT: We need to check with the Node.js team why they translated that to the equivalent of: // not name like concat('%','sunny','%') escape '^' expect (OData(`Foo?$filter= not contains(name,'sunny')`)) - .to.eql (CQL(`SELECT from Foo where not name like '%sunny%'`)) + .to.eql (CQL(`SELECT from Foo where not contains(name,'sunny')`)) }); + // REVISIT: wait for compiler v2 it("should support group expr", () => { - expect (OData(`Foo?$filter= (unitPrice lt 11 and length(name) eq 12) or name eq 'Restless and Wild'`)) - .to.eql (CQL(`SELECT from Foo where (unitPrice < 11 and length(name) = 12) or name = 'Restless and Wild'`)) + expect (OData(`Foo?$filter= (unitPrice gt 11 and length(name) eq 12) or name eq 'Restless and Wild'`)) + .to.eql (CQL(`SELECT from Foo where (unitPrice > 11 and length(name) = 12) or name = 'Restless and Wild'`)) }); }); @@ -62,17 +57,17 @@ describe("$filter", () => { it("should support contains", () => { expect (OData(`Foo?$filter= contains(name,'sunny')`)) - .to.eql (CQL(`SELECT from Foo where name like '%sunny%'`)) + .to.eql (CQL(`SELECT from Foo where contains(name,'sunny')`)) }); it("should support startswith", () => { expect (OData(`Foo?$filter= startswith(name,'sunny')`)) - .to.eql (CQL(`SELECT from Foo where name like 'sunny%'`)) + .to.eql (CQL(`SELECT from Foo where startswith(name,'sunny')`)) }); it("should support endswith", () => { expect (OData(`Foo?$filter= endswith(name,'sunny')`)) - .to.eql (CQL(`SELECT from Foo where name like '%sunny'`)) + .to.eql (CQL(`SELECT from Foo where endswith(name,'sunny')`)) }); it("should support length", () => { @@ -82,7 +77,7 @@ describe("$filter", () => { it("should support indexof", () => { expect (OData(`Foo?$filter= indexof(name,'x') eq 11`)) - .to.eql (CQL(`SELECT from Foo where locate(name,'x') = 11`)) + .to.eql (CQL(`SELECT from Foo where indexof(name,'x') = 11`)) }); it("should support substring", () => { @@ -92,12 +87,12 @@ describe("$filter", () => { it.each(['tolower','toupper','trim'])("should support '%s'", (fn) => { expect (OData(`Foo?$filter= ${fn}(name) eq 'foo'`)) - .to.eql (CQL(`SELECT from Foo where ${fn.replace(/^to/,'')}(name) = 'foo'`)) + .to.eql (CQL(`SELECT from Foo where ${fn}(name) = 'foo'`)) }); it("should support 'day'", () => { expect (OData(`Foo?$filter= day(name) eq 11`)) - .to.eql (CQL(`SELECT from Foo where dayofmonth(name) = 11`)) + .to.eql (CQL(`SELECT from Foo where day(name) = 11`)) }); it("should support concat", () => { @@ -106,4 +101,5 @@ describe("$filter", () => { }); }); + });