Authoring a Language
Adding a language is the most involved extension point. This page outlines the
pieces involved; the provided language packages (@dortdb/lang-sql,
@dortdb/lang-cypher, @dortdb/lang-xquery) are the best worked references, and
a language is registered exactly like the provided ones: as mainLang or in
additionalLangs.
1. A parser that cooperates with language switching
The parser turns query text into an AST. Because any language can be nested
inside any other, a parser must handle language switches: when it encounters
a LANG <name> block it hands off to another parser, and it must know when to
stop, even with input remaining, either at an explicit LANG EXIT keyword or
when it would leave its own scope (for example a closing parenthesis).
The parser therefore returns both the parsed AST and any unparsed remainder, so an outer parser can resume. For example, parsing the nested block in:
SELECT t1.attr1 FROM (
LANG newlang
-- new language here...
) AS t1 WHERE attr1 > 3
the nested parser should stop at the ) and return the remaining
) AS t1 WHERE attr1 > 3 to the SQL parser.
2. Building the logical plan
From the AST, the language builds an initial logical plan.
If the unified algebra is not enough, the language may define new plan
operators, but it must then handle them in every visitor pass the engine
runs over the plan (dependency analysis, renaming, execution, and so on). The
full set of passes, what each one does, and default implementations are covered
in Plan Visitors. A language is described by a
descriptor whose visitors map provides these implementations:
export function MyLang(config?: MyLangConfig): MyLangLanguage {
return {
name: 'mylang',
// ...parser, serializer, data adapter...
visitors: {
attributeRenamer: MyLangAttributeRenamer, // extends the core AttributeRenamer
// ...one per core visitor...
},
};
}
3. Schema inference across scopes
When building a plan, a language receives an IdSet of identifiers defined in the
outer context. Some of those identifiers may end with the Symbol(toInfer)
token, meaning they belong to a source whose schema is still being inferred. If
the language references any such identifiers, it should record them and return
them alongside the plan, so schema inference can complete across the language
boundary.
Related building blocks
A language typically also provides a data adapter (how it reads registered values) and a serializer (how internal results become plain JavaScript values), and it may ship functions, index types, and optimizer rules for its operators.