Search

Schema Notation (CSN)

CSN (pronounced as “Season”) is a notation for compact representations of data and service models — tailored to serve as an optimal format to share and interpret models with minimal footprint and dependencies.

It’s similar to JSON Schema but goes beyond JSON’s ability to capture full-blown Entity-Relationship Models and Extensions. This ability makes CSN models much more concise, closer to your conceptual thinking. Those CDS models are a perfect source to generate target models, such as OData/EDM or OpenAPI interfaces, as well as persistence models for SQL or NoSQL databases.

Content

Anatomy

A CSN model in JSON:

{
  "using": [],
  "definitions": {
    "some.type": {
      "type": "cds.String", "length": 11
    },
    "another.type": {
      "type": "some.type"
    },
    "structured.type": {
      "elements": {
        "foo": { "type": "cds.Integer" },
        "bar": { "type": "cds.String" }
      }
    }
  },
  "extensions": [],
}

The same model in YAML:

using: []
definitions:
  some.type: {type: cds.String, length: 11}
  another.type: {type: some.type }
  structured.type:
    elements:
      foo: {type: cds.Integer}
      bar: {type: cds.String}
extensions: []

The same model as a plain JavaScript object:

({
  using: [],
  definitions: {
    'some.type': { type:"cds.String", length:11 },
    'another.type': { type:"some.type" },
    'structured.type': { elements: {
      'foo': { type:"cds.Integer" },
      'bar': { type:"cds.String" }
    }}
  },
  extensions: [],
})

For the remainder of this spec, you see examples in plain JavaScript representation with the following conventions:

({property:...})   // a CSN-specified property name
({'name':...})     // a definition's declared name
"value"            // a string value, including referred names
11, true           // number and boolean literal values

Properties

Note: All properties are optional. For example, one model could contain a few definitions, while another one only contains some extensions.

References are case-sensitive. All references in properties like type, target, via use exactly the same notation regarding casing as their targets’ names. To avoid problems when translating models to case-insensitive environments like SQL databases, avoid case-significant names and references. For example, avoid two different definitions in the same scope whose names only differ in casing, such as foo and Foo.

Literals

There are several places where literals can show up in models, such as in SQL expressions, calculated fields, or annotations.

Standard literals are represented as in JSON:

Kind Example
Globals true, false, null
Numbers1 11 or 2.4
Strings "foo"
Dates2 "2016-11-24"
Times2 "16:11"
DateTimes2 "2016-11-24T16:11"
Records {"foo":<literal>, ...}
Arrays [<literal>, ...]

In addition, CSN specifies these special forms for references, expressions and enum symbols:.

Kind Example
References {"=":"foo.bar"}
Expressions {"=":"foo.bar < 9"}
Enum symbols3 {"#":"asc"}

Remarks

1 This is as in JSON and shares the same issues when decimals are mapped to doubles with potential rounding errors. The same applies to Integer64. Use strings to avoid that, if applicable.

2 Also, as in JSON, dates, and times are represented just as strings as specified in ISO 8601; consumers are assumed to know the types and handle the values correctly.

3 As enum symbols are equal to their values, it frequently suffices to just provide them as strings. Similar to time and dates in CSN and JSON, the consumers are assumed to know the types and handle the values correctly. The {"#":...} syntax option is to serve cases where you have to distinguish the kind only based on the provided value, for example, in untyped annotations.

Definitions

Each entry in the definitions dictionary is essentially a type definition. The name is the absolute, fully qualified name of the definition, and the value is a record with the definition details.

Example

({definitions:{
  'Name':     {type:"cds.String"},
  'Currency': {type:"cds.String", length:3},
  'USD':      {type:"Currency"},
  'Amount':   {elements:{
    'value':    {type:"cds.Decimal", precision:11, scale:3},
    'currency': {type:"Currency"},
  }},
  'SortOrder':{enum:{ 'asc':{}, 'desc':{} }}
}})

The name of a definition is its key in the enclosing dictionary, like in definitions for top-level entries or in elements for structured types and entities.

Names must

Properties

Property kind is always omitted for elements and can be omitted for top-level type definitions. These examples (in yaml) are semantically equivalent:

Foo1 = { type:"cds.String" }
Foo2 = { type:"cds.String", kind:"type" }

Type Definitions

Custom-defined types are entries in definitions with an optional property kind="type" and

property for
type Scalar Types and Associations
elements Structured Types
items Arrayed Types
enum Enumeration Types

Example

({definitions: {
  'scalar.type': {type:"cds.String", length:3 },
  'struct.type': {elements:{ 'foo': {type:"cds.Integer"}}},
  'arrayd.type': {items:{type:"cds.Integer"}},
  'enum.type':   {enum:{ 'asc':{}, 'desc':{} }}
}})

Properties

Scalar Types

Scalar types always have property type specified, plus optional type-specific parameter properties.

({definitions:{
  'scalar.type': {type:"cds.String", length:3 },
}})

See the CDL reference docs for an overview of CDS’ built-in types.

Note: While in CDS sources you can refer to these types without prefix, they always have to be specified with their fully qualified names in CSN, for example:

({definitions: {
  'Foo': { type:"cds.Integer" },
  'Bar': { type:"cds.Decimal", precision:11, scale:3 },
}})

Structured Types

Structured types are signified by the presence of an elements property. The value of elements, is a dictionary of elements. The name is the local name of the element and the values in turn are Type Definitions.

({definitions:{
  'structured.type': {elements:{
    'foo': {type:"cds.Integer"},
    'bar': {type:"cds.String"}
  }}
}})

Arrayed Types

Arrayed types are signified by the presence of a property items. The value of which is in turn a type definition that specifies the arrayed items’ type.

({definitions:{
  'arrayed.type': {items:{type:"cds.Integer"}}
}})

Enumeration Types

The enum property is a dictionary of enum member elements with the name being the enum symbol and the value being a CQN literal value expression. The literal expression optionally specifies a constant val as a literal plus optional annotations. An enumeration type can specify an explicit type (for example, Decimal) but can also omit it and refer from given enumeration values, or String as default.

({definitions:{
  'Gender': {enum:{
    'male':{},
    'female':{}
  }},
  'Status': {enum:{
    'submitted': {val:1},
    'fulfilled': {val:2}
  }},
  'Rating': {type:"cds.Decimal", enum:{
    'low':    {val:0},
    'medium': {val:50},
    'high':   {val:100}
  }}
}})

Entity Definitions

Entities are structured types with kind='entity'. In addition, one or more elements usually have property key set to true, to flag the entity’s primary key.

Example

({definitions:{
  'Products': {kind:"entity", elements:{
    'ID':     {type:"cds.Integer", key:true, unique:true},
    'title':  {type:"cds.String", notNull:true},
    'price':  {type:"Amount", virtual:true},
  }}
}})

Properties

Elements in entity definitions may optionally be equipped with one or more of these properties (all of type boolean):

View Definitions

Views are entities defined as projections on underlying entities, with the projections being expressed as nested CQN expressions in property query. In effect, views are recognized by the existence of the property query.

Example

({definitions:{
  'a.simple.view': {kind:"entity",
    query: {
      SELECT:{
        from: {ref:['Products']},
        columns: [ {ref:['title']}, {ref:['price']} ]
      }
    },
  }
}})

Properties

Views with Declared Signatures

Views with declared signatures have the additional property elements filled in as in entities:

({definitions:{
  'with.declared.signature': {kind:"entity",
    elements: {
      'title': {type:"cds.String"},
      'price': {type:"Amount"}
    },
    query: { SELECT:{...} },
  }
}})

Views with Parameters

Views with parameters have an additional property params — an optional dictionary of parameter type definitions:

({definitions:{
  'with.params': {kind:"entity",
    params: { 'ID': { type: 'cds.Integer' } },
    query: { SELECT:{...} },
  }
}})

Associations

Associations are like scalar type definitions with type being cds.Association or cds.Composition plus additional properties specifying the association’s target and optional information like on conditions or foreign keys.

Example

({definitions:{
  'Currency': {type:"cds.Association",
    //> an association type-def
    target:"Currencies"
  },
  'Books': {kind:"entity", elements:{
    'title':  {type:"cds.String"},
    'author': {type:"cds.Association",
      // a to-one association
      target:"Authors", cardinality:{max:1}
    },
    'genre': {type:"cds.Association",
      // a to-one association with controlled foreign keys
      target:"Genres", keys:[{ref:"name"}]
    },
  }}
  'Authors': {kind:"entity", elements:{
    'name':  {type:"cds.String"},
    'books': {type:"cds.Association",
      // a to-many association
      target:"Books", cardinality:{max:"*"},
      on: [{ref:['books', 'author']}, '=', {ref:['$self']}]
    },
  }}
}})

Properties

Managed to-one associations automatically derive foreign keys from the target’s primary key elements. You can overrule this behavior by choosing them explicitly in the keys property, which follows the format and mechanisms of CQN projections:

({..., keys:[ //one or more of...
  {ref:['<names of element in target>'], as:'<local name>'}
]})

Many-to-many Relationships are realized using the via property, which can either be (a) a string referring to an existing link table entity definition, or (b) an inline struct definition with kind="inline".

Annotations

Annotations are represented as properties, prefixed with @. This format applies to type/entity-level annotations as well as to element-level ones.

Example

({definitions:{
  'Employees': {kind:"entity",
    '@title':"Mitarbeiter",
    '@readonly':true,
    elements:{
      'firstname': {type:"cds.String", '@title':"Vorname"},
      'surname':   {type:"cds.String", '@title':"Nachname"},
    }
  },
}})

Annotations are used to add custom information to definitions, the prefixed @ acts as a protection against conflicts with built-in/standard properties. They’re flat lists of key-value pairs, with keys being fully qualified property names and values being represented as introduced in the section Literals and Expressions.

Aspects

In contrast to definitions, extensions and annotations are unnamed. They’re kept in the top-level array property extensions.

Example

({extensions:[
  // extend Employees with @foo { ..., salary: Decimal; }
  {kind:"extend",target:"Employees",
    '@foo':true,
    elements:{
      'surname': {kind:"annotate", '@title': "Familienname"},
      'salary':  {type:"cds.Decimal"},  //> new field
    }
  },
  // annotate Employees with @foo { ... }
  // equivalent to the above, except for the new field
  {kind:"annotate", target:"Employees",
    '@foo':true,
    elements:{
      'surname': {'@title':"Familienname"},
    }
  },
]})

The internal structure of extensions is the same as definitions with some restrictions:

Kind Source
extend extend Foo …
annotate extend Foo …
extend … { extend foo … }
annotate … { annotate foo … }
define … { [add/define] foo … }

In the example, the first entry is an extend case, which adds a new element salary and overrides annotations for element surname. The second entry is a pure annotation.

Services

Services are definitions with kind='service':

({definitions:{
  'MyOrders': {kind:"service"}
}})

Actions / Functions

Service definitions (for unbound actions/functions) as well as entity definitions (for bound actions/functions) can have an additional property actions. The keys of these actions are the (local) names of actions/functions.

Example

({definitions:{
  'MyOrders': {kind:"service", actions:{
    'cancelOrder': {kind:"action",
      params:{
        'orderID': {type:"cds.Integer"},
        'reason':  {type:"cds.String"},
      },
      returns: {elements:{
        'ack': {enum:{ 'succeeded':{}, 'failed':{} }},
        'msg': {type:"cds.String"},
      }}
    }
  }}
}})

Properties

Note: The definition of the response can be a reference to a declared type or the inline definition of a new (struct) type.

Imports

The using directive allows you to import definitions from other models and thus compose compound models out of smaller individual ones. Using directives work exactly like the import statements in ES6.

Example

({using:[
  {name:"some.external.name", as:"alias", from:"some/model"},
  {name:"some.external.name", from:"some/model"},
  {name:"some.other.name"},
  {from:"some/model"},
],...})

Properties