Search

    Introspecting CQN Queries

    API to introspect CDS QL statements in Java

    Content

    Introduction

    The CDS Query Introspection API allows to analyze CQN statements and extract values and information on the CDS entities in references.

    The CqnAnalyzer can be constructed from a CDS model:

    import com.sap.cds.ql.cqn.CqnAnalyzer;
    
    CdsModel cdsModel = context.getModel();
    CqnAnalyzer cqnAnalyzer = CqnAnalyzer.create(cdsModel);
    

    Furthermore the static isCountQuery(cqn) method can be used to check if a CQN statement only returns a single count:

    // cqn: Select.from("Books").columns(CQL.count().as("bookCount"));
    boolean isCount = CqnAnalyzer.isCountQuery(cqn);  // true
    

    Usage

    Given the following CDS model and CQL query:

    entity Orders {
      key OrderNo : String;
      Items       : Composition of many OrderItems on Items.parent = $self;
      ...
    }
    entity OrderItems {
      key ID : Integer;
      book   : Association to Books;
      ...
    }
    

    Find this source also in cap/samples.

    --CQL query
    SELECT from Orders[OrderNo = '42'].items[ID = 1]
    

    the corresponding CQN statement can be analyzed using the analyze method of the CqnAnalyzer:

    CqnStatement cqn = context.getCqn();
    
    AnalysisResult result = cqnAnalyzer.analyze(cqn.ref());
    

    Resolving CDS Entities

    Based on the AnalysisResult, information on the CDS entities can be accessed through the Reflection API:

    CdsEntity order = result.rootEntity();   // Orders
    CdsEntity item  = result.targetEntity(); // OrderItems
    

    Extracting Filter Values

    Extracting filter values from CQN statements is supported for noncomplex predicates where the values can be unambiguously determined.

    The key values of the entities can be extracted as a map using the rootKeys and targetKeys method of the AnalysisResult object:

    Map<String, Object> rootKeys = result.rootKeys();
    String orderNo = (String) rootKeys.get("OrderNo"); // 42
    
    Map<String, Object> targetKeys  = result.targetKeys();
    Integer itemId = (Integer) targetKeys.get("ID");   // 1
    

    To extract all filter values of the target entity including nonkey values, the targetValues method can be used:

    Map<String, Object> filterValues = result.targetValues();
    

    For CqnSelect, CqnUpdate, and CqnDelete, values can also be extracted from the statement’s where condition:

    --CQL query
    SELECT from Orders[OrderNo = '42'].items where ID = 3 and status = 'open'
    
    CqnSelect select = context.getCqn();
    AnalysisResult result = cqnAnalyzer.analyze(select);
    
    Map<String, Object> targetKeys = result.targetKeys();
    Integer itemId = (Integer) targetKeys.get("ID");   // 3
    
    Map<String, Object> filterValues = result.targetValues();
    String status = (String) filterValues.get("status");   // 'open'
    

    Using the Iterator

    The methods prefixed with root and target access the first resp. last segment of the CQN statement’s ref. If the ref has more than two segments, such as:

    --CQL query
    SELECT from Orders[OrderNo = '42'].items[ID = 1].book
    

    the segment items can be analyzed using an iterator:

    Iterator<ResolvedSegment> iterator = result.iterator();
    CdsEntity order = iterator.next().entity();
    CdsEntity item  = iterator.next().entity();
    CdsEntity book  = iterator.next().entity();
    

    or a reverse iterator starting from the last segment:

    Iterator<ResolvedSegment> iterator = result.reverse();
    CdsEntity book  = iterator.next().entity();
    CdsEntity item  = iterator.next().entity();
    CdsEntity order = iterator.next().entity();
    

    In the same way, also the filter values for each segment can be extracted using the values and keys method instead of the entity method.

    Application and Limitations

    Although the CDS Query Introspection API allows analysis and extraction of element values for most of the queries, nevertheless it comes with some limitations. The main rule here is:

    The value of an element reference in a where and filter predicate must be unambiguously identified.

    This implies the following:

    • The operator of comparison predicate must be either eq or is:
    Select.from("bookshop.Book").where(b -> b.get("ID").eq(42));
    
    • The conjunction and is used in predicates:
    Select.from("bookshop.Book")
    	.where(b -> b.get("ID").eq(42).and(b.get("title").is("Capire"));
    

    This rule applies to all segments of all references of the query, be it simple query or the one with path expression:

    Select.from("bookshop.Book", 
    	b -> b.filter(b.get("ID").eq(41))
    		.to("author").filter(a -> a.get("Id").eq(1)));
    

    In general, CDS Query Introspection API returns the values for elements, which are:

    • Unambiguously identified by: eq or is operator of comparison predicate or used in byId or matching clause of the query;
    • Used in conjunction (and) predicates

    CDS Query Introspection API doesn’t return the values for elements, which are:

    • Compared with lt, gt, le, ge, ne, isNot operator
    • Used within in
    • Negated with not
    • Used in search
    • Used in functions
    • Used in sub-queries
    • Referencing elements of an associated entity
    Show/Hide Beta Features