KWEBAPP(5) File Formats Manual KWEBAPP(5)

syntax for kwebapp configuration

The kwebapp configuration is used by the kwebapp(1) family to define how an application's data types; the data itself; how the data is created, modified, and deleted; and how the data is queried.
Configurations have the following rough structure:
config :== [ enum | bitfield | struct ]+ [ roles ]? 
roles :== "roles" "{" 
  [ "role" roledata ";" ]+ 
struct :== "struct" structname "{" 
  [ "comment" string_literal ";" ]? 
  [ "delete" deletedata ";" ]* 
  [ "field" fielddata ";" ]+ 
  [ "insert" ";" ]* 
  [ "iterate" searchdata ";" ]* 
  [ "list" searchdata ";" ]* 
  [ "roles" roledata ";" ]* 
  [ "search" searchdata ";" ]* 
  [ "unique" uniquedata ";" ]* 
  [ "update" updatedata ";" ]* 
enum :== "enum" enumname "{" 
  [ "comment" string_literal ";" ]? 
  [ "item" enumdata ";" ]+ 
bitfield :== "bits" bitsname "{" 
  [ "comment" string_literal ";" ]? 
  [ "item" bitsdata ";" ]+ 
  [ "isunset" label ";" ]+ 
  [ "isnull" label ";" ]+ 
In short, one or more structure definitions, zero or more enumerations and bitfields, and zero or one role definition.
Structures describe data structures and operations (queries, insertions, updates, deletions). Fields contained in each structure describe data fields, validation constraints, and so on.
Enumerations define fixed constants used in data field definitions. These are used only for validation and for formatting output data. Bitfields are similar to enumerations except that they describe bits set in a single value instead of independent values.
Roles stipulate the use of role-based access control on data operations.
Collectively, these are called a configuration's “objects”.

In kwebapp, white-space serves only to separate tokens: it is discarded except as found within quoted literals. Thus, the following are identical except for the name:
struct foo { 
  field id int rowid; 
struct bar{field id int rowid;};
Except for the content of string literals, a kwebapp configuration only recognises ASCII characters.

Objects are generally named by an identifier. These are always case-insensitive alphanumeric non-empty string beginning with a letter. There are many disallowed or reserved identifiers. There are also unique name constraints to consider (e.g., no two structures can have the same name, etc.).
An example identifier:
enum foobar { ... };
A non-confirming identifier may be:
enum foo_bar { ... };

Another comment syntactic element is the string literal: a double-quoted string where internal double quotes may be escaped by a single preceding backslash.
struct ident { field id int comment "\"Literal\"."; };

Document comments are begun by the hash mark (“#”) and extend to the end of the line.
# This is my structure. 
struct ident { 
  field id int comment "\"Literal\"."; # End of line. 
They are always discarded and not considered part of the parsed configuration file. They shouldn't be confused with Comments possible for individual statements.

Both decimal and integral numbers are recognised. Integral numbers are signed and limited to 64 bits and formatted as [-]?[0-9]+. Decimal numbers are similarly formatted as [-]?[0-9]+.[0-9]*. The difference between the two is the existence of the decimal.

Every kwebapp document consists of a set of top-level objects. These objects describe data types (Enumeration and Bitfields), data and operations (Structures and their fields), and access control (Roles).
There are no ordering constraints on objects: all linkage between components (e.g., referenced fields, roles, types, etc.) occurs after parsing the document.

A structure object is the foundational type of kwebapp. It is similar to a class and consists primarily of data definitions and operations. A structure is represented as a struct in a C API along with functions operating on that struct. In SQL, it is a table.
Its definition begins with the word struct, then the unique identifier of the structure, then elements within the curly braces.
"struct" structname "{" 
  [ "comment" string_literal ";" ]? 
  [ "delete" deletedata ";" ]* 
  [ "field" fielddata ";" ]+ 
  [ "insert" ";" ]? 
  [ "iterate" searchdata ";" ]* 
  [ "list" searchdata ";" ]* 
  [ "roles" roledata ";" ]* 
  [ "search" searchdata ";" ]* 
  [ "unique" uniquedata ";" ]* 
  [ "update" updatedata ";" ]* 
The elements may consist of one or more field describing data fields (see Fields); optionally a comment for describing the structure (see Comments); zero or more update, delete, or insert statements that define data modifiers (see Updates); zero or more unique statements that create unique constraints on multiple fields (see Uniques); and zero or more list, iterate, or search statements that define data queries (see Queries). Lastly, the roles statement describes modifiers and queries available to roles (see Roles).

A field object defines the data contained within a structure. In the C API, a field is a struct member. In SQL, it is a column specification. Each field consists of the word field followed by an identifier name and, optionally, a type then additional information.
"field" name[":" target]? [type [typeinfo]*]? ";"
The name may either be a standalone identifier or a “foreign key” referencing a field in another structure by the structure and field name. In this case, the referenced field must be a rowid or unique and have the same type.
The type, if specified, may be one of the following:
Native integer constained to 64-bit bit index (that is, 0–64). The bit indices start from 1 in order to represent a zero value (no bits to set). Each non-zero value is usually merged into a bit-field by setting 1u << (value - 1) for some input or output value. For entire bitfields, see bits.
Native type mapping into a 64-bit integer, constrained to valid bitfield name. See Bitfields for more documentation.
Native type mapping into a fixed-size binary buffer.
Native type mapping into a nil-terminated character string, constrained to e-mail address format.
Native type mapping into a 64-bit integer, constrained to valid enumeration values of name. See Enumerations for more documentation.
Native type mapping into a 64-bit integer, int64_t.
Native type mapping into a nil-terminated character string. This field is special in that it converts an input password into a hash before insertion into the database. It also can properly search for password hashes by running the hash verification after extraction. Thus, there is a difference between a password field that is being inserted or updated (as a password, which is hashed) and extracted using a search (as a hash).
Native type mapping into a double-precision float.
Native type mapping into a 64-bit integer, constrained to valid time_t values and similarly represented in the C API. The date alias is also available, which is the same but using a date (ISO 8601) sequence input validator.
A non-native type filled in by joining the given field with its referent structure. This type is “non-native” because it is not represented in the database schema, and is instead filled in with the referenced row. In the C API, this is represented by a struct name of the referent structure. The field may be marked with null, but this involves a not-inconsiderable performance hit when querying (directly or indirectly) on the structure.
Native type mapping into a nil-terminated character string, usually encoded in UTF-8.
The typeinfo provides further information (or operations) regarding the field, and may consist of the following:
Like actup but on deletion of the field in the database.
SQL actions taken when the field is updated. May be one of none (do nothing), restrict, (disallow if having child referents), nullify (set child referents to null), cascade, (propogate operation to referents), or default (set child referents to their default values). This is only available on foreign key references.
Documents the field using the quoted string.
Set a default value for the column that's used only when adding columns to the SQL schema via kwebapp-sqldiff(1). It's only valid for numeric, date, or string literal (email, text) field types. Dates must be in yyyy-mm-dd format.
limit_op limit_val
Used when generating validation functions. Only available for native types. The limit_op argument consists of an operator the limit_val is checked against. Available operators are ge, le, gt, lt, and eq. Respectively, these mean the field should be greater than or equal to, less than or equal to, greater than, less than, or equal to the given value. If the field type is text, email, password, or blob, this refers to the string (or binary) length in bytes. For numeric types, it's the value itself. The given value must match the field type: an integer (which may be signed) for integers, integer or real-valued for real, or a positive integer for lengths.
Never exported using the JSON interface. This is useful for sensitive internal information. Fields with type password are never exported by default.
Accepts null SQL or C values, and is only available for native types. A rowid field may not also be null.
The field is an SQL primary key. This is only available for the int type and may only appear for one field in a given structure.
Has a unique SQL column value. See Uniques for multi-field unique constraints.
A field declaration may consist of any number of typeinfo statements.

Enumerations constrain an int field type to a specific set of constant values. They are defined as follows:
"enum" enumname "{" 
  [ "comment" string_literal ";" ]? 
  [ "item" name [value]? [parms]* ";" ]+ 
For example,
enum enumname { 
  item "val1" 1 jslabel "Value one"; 
The enumeration name must be unique among all enumerations, bitfields, and structures.
Items define enumeration item names, their constant values (if set), and documentation. Each item's name must be unique within an enumeration. The value is the named constant's value expressed as an integer. It must also be unique within the enumeration object. If not specified, it is assigned as one more than the maximum of the assigned values or zero, whichever is larger. Automatic assignment is linear and in the order specified in the configuration. Parameters may be any of the following:
"comment" string_literal 
The item's comment is used to document the field, while its label (see Labels) is used only for formatting output.
The above enumeration would be used in an example field definition as follows:
field foo enum enumname;
This would constrain validation routines to only recognise values defined for the enumeration.

Like enumerations, bitfields constrain an int field type to a bit-wise mask of constant values. They are defined as follows:
"bits" bitsname "{" 
  [ "comment" string_literal ";" ]? 
  [ "item" name bitidx [parms]* ";" ]+ 
  [ "unset" label ";" ]+ 
For example,
bits bitsname { 
  item "bit1" 0 jslabel "Bit one"; 
  isunset jslabel "No bits"; 
The name must be unique among all enumerations, structures, and other bitfields.
Items define bits, their values, and documentation. Each item's name must be unique within a bitfield. The value is the named constant's bit index from zero, so a value of zero refers to the first bit, one to the second bit, and so on. Each must be unique within the bitfield. Parameters may be any of the following:
"comment" string_literal 
The item's comment is used to document the field, while its label (see Labels) is used only for formatting output.
The above bitfield would be used in an example field definition as follows:
field foo bits bitsname;
The bitfield's comment is passed into the output media, and the isunset statement serves to provide a label (see Labels) for when no bits are set (i.e., the field evaluates to zero).

There are three types of searches that may be defined to produce searching functions on structures: search for an individual row (i.e., on a unique column or with a limit of 1) and store the result in memory, store a list of retrieved results in memory, or iterate a function for each retrieved result in an active query.
Queries are always by field, and may be followed by parameters:
"struct" name "{" 
  [ "search" [term ["," term]*]? [":" [parms]* ]? ";" ]* 
  [ "list" [term ["," term]*]? [":" [parms]* ]? ";" ]* 
  [ "iterate" [term ["," term]*]? [":" [parms]* ]? ";" ]* 
Here, term consists of the possibly-nested field names to search for and an optional operator. (Searchers of type search require at least one field.) Nested fields are in dotted-notation:
[structure "."]*field [operator]?
This would produce functions searching the field “field” within the struct structures as listed. The following operators may be used:
, or
Logical AND (&) and logical OR (|), respectively.
, neq
Equality or non-equality binary operator. The eq operator is the default.
, gt
Less than or greater than binary operators. For text, the comparison is lexical; otherwise, it is by value.
, ge
Less than/equality or greater than/equality binary operators. For text, the comparison is lexical; otherwise, it is by value.
The LIKE SQL operator. This only applies to text and email fields.
, notnull
Unary operator to check whether the field is null or not null.
The password field does not accept any operator but the default check for equality.
The parms search parameters are a series of key-value pairs:
"comment" string_literal 
"distinct" ["." | [field ["." field]*]] 
"limit" limitval["," offsetval]? 
"name" searchname 
"order" term [type]? ["," term [type]?]*
The available parameter keys are as follows:
String literal included in the API comments for the function.
A value >0 that limits the number of returned results. By default, there is no limit. This can be used in a search singleton result statement as a way to limit non-unique results to a single result. If followed by a comma, the next term is used to offset the query. This is usually used to page through results.
Return only distinct rows. If only a period (“.”) follows the keyword, then the distinct applies to the entire returned structure. If a nested structure follows the keyword, only distinct rows of the given structure are returned by the search operation. This does not work with null structures; and moreover, disallows searching by fields of type password.
A unique identifier used in the C API for the search function.
Result ordering. The term, like the search term, consists of order fields with periods indicating fields nested within structures. Each term may be followed by an order direction: asc for ascending and desc for descending. Result ordering is applied from left-to-right.
If you're searching (in any way) on a password field, the field is omitted from the initial search, then hash-verified after being extracted from the database. Thus, this doesn't have the same performance as a normal search.

While individual fields may be marked unique on a per-column basis, multiple-column unique constraints may be specified with the unique structure-level keyword. The syntax is as follows:
"unique" field ["," field]+ ";"
Each field must be in the local structure, and must be native types. There must be at least two fields in the statement. There can be only one unique statement per combination of fields (in any order).
For example, consider a request for something involving two parties, where the pair requesting must be unique.
struct request { 
  field int; 
  field int; 
  unique userid, ownerid; 
This stipulates that adding the same pair will result in a constraint failure.

Update statements (update, delete, insert) define how the database will be modified. By default, there are no update, delete, or insert functions defined. The syntax is as follows:
"struct" name "{" 
  [ "update" [mflds]* [":" [cflds]* [":" [parms]* ]? ]? ";" ]* 
  [ "delete" [cflds]* [":" [parms]* ]? ";" ]* 
  [ "insert" ";" ]? 
Both mflds and cflds are a sequence of comma-separated native-type fields in the current structure. The former refers to the fields that will be modified; the latter refers to fields that will act as constraints. In other words, modify fields constraint by cflds to contain the cflds. Usually, the latter will be the rowid and the former will be any other fields.
The delete statement obviously does not accept fields to modify (mflds). If the update statement does not have mflds, it's taken to mean that all fields will be modified using the default operator.
The fields in mflds may accept a modifier type that modifies the existing field instead of setting it externally. This is only available for numeric types. The fields in cflds may accept an optional operator type as described for Queries.
mfld [modifier]? 
cfld [operator]?
The following augment operations are available, but only to numeric types:
Increment the current field by a given value (x = x + ?).
Decrement the current field by a given value (x = x - ?).
Default behaviour of setting to a value (x = ?).
The parms search parameters are a series of key-value pairs:
"comment" string_literal 
"name" name
The name sets a unique name for the generated function, while comment is used for the API comments.
Fields of type password are not allowed as cflds since they are not stored directly as comparable strings, but hashed with a unique salt.

Full role-based access control is available in kwebapp when a top-level roles block is defined.
"roles" "{" 
   [ "role" name [parms] ["{" "role" name... ";" "}"]* ";" ]* 
This nested structure defines the role tree. Roles descendent of roles are called sub-roles.
By defining roles, even if left empty, the system will switch into default-deny access control mode, and each function in Structures must be associated with one or more roles to be used.
There are three reserved roles: default, none, and all. These need not be specified in the roles statement. The first may be used for the initial state of the system (before a role has been explicitly assigned), the second refers to the empty role that can do nothing, and the third contains all explicitly-defined roles.
Each role may be associated with parameters limited to:
"role" name ["comment" quoted_string]?
The comment field is only produced for role documentation.
Within Structures, roles are defined as follows:
"struct" name "{" 
  [ "roles" role ["," role]* "{" roletype [name]? "};" ]* 
The role is a list of roles as defined in the top-level block, or one of the reserved roles but for none, which can never be assigned. The role may be one of the following types:
A special type referring to all function types.
The named delete operation.
The insert operation.
The named iterate operation.
The named list operation.
Do not export the field name via the JSON export routines. If no name is given, don't export any fields.
The named search operation.
The name update operation.
To refer to an operation, use its name. The only way to refer to un-named operations is to use all, which refers to all operations (i.e., all types but noexport).
If, during run-time, the current role is not a subtype (inclusive) of the given role for an operation, the application is immediately terminated.

Comments attached to statements are used to document the output and serve no other purpose. For the time being, there is no syntax attached to comments: they should be considered opaque text for the time being, and copied as-is into the output documentation.

Labels specify how bits and enum types and their items may be described by a front-end formatter such as kwebapp-javascript(1). That is, while the string value of a struct item describes itself, an enum maps to a numeric value that needs to be translated into a meaningful format. Labels export string representations of the internal numeric value to the front-end formatters.
The syntax is as follows:
"jslabel" ["." lang]? quoted_string
The lang token is usually an ISO 639-1 code, but may be any identifier. If the lang is not specified, the label is considered to be the default label.
If a label is not specified for a given language, it inherits the default label. If the default label is not provided, it is an empty string.

A trivial example is as follows:
struct user { 
  field name text; 
  field id int rowid; 
  comment "A regular user."; 
struct session { 
  field user struct userid; 
  field comment "Associated user."; 
  field token int comment "Random cookie."; 
  field ctime epoch comment "Creation time."; 
  field id int rowid; 
  comment "Authenticated session."; 
This generates two C structures, user and session, consisting of the given fields. The session structure contains a struct user as well; thus, there is a declarative order that kwebapp(1) enforces when writing out structures.
The SQL interface, when fetching a struct session, will employ an INNER JOIN over the user identifier and session userid field.

November 2, 2018 OpenBSD 6.3