EdgeDB is a new kind of database
that takes the best parts of
relational databases, graph
databases, and ORMs. We call it
a graph-relational database.
🧩 Types, not tables 🧩
Schema is the foundation of your application. It should be something you can
read, write, and understand.
Forget foreign keys; tabular data modeling is a relic of an older age, and it
isn't compatible
with modern languages. Instead, EdgeDB thinks about schema the same way you do:
as object types containing properties connected by links.
type Person{
required name: str;
}type Movie{
required title: str;
multi actors:Person;
}
This example is intentionally simple, but EdgeDB supports everything you'd
expect from your database: a strict type system, indexes, constraints, computed
properties, stored procedures...the list goes on. Plus it gives you some shiny
new features too: link properties, schema mixins, and best-in-class JSON
support. Read the schema docs
for details.
🌳 Objects, not rows 🌳
EdgeDB's super-powered query language EdgeQL is designed as a ground-up
redesign of SQL. EdgeQL queries produce rich, structured objects, not flat
lists of rows. Deeply fetching related objects is painless...bye, bye, JOINs.
select Movie{
title,
actors:{
name
}}filter .title ="The Matrix"
EdgeQL queries are also composable; you can use one EdgeQL query as an
expression inside another. This property makes things like subqueries and
nested mutations a breeze.
insert Movie{
title :="The Matrix Resurrections",
actors :=(
select Person
filter .name in {
'KeanuReeves',
'Carrie-AnneMoss',
'LaurenceFishburne'
})}
There's a lot more to EdgeQL: a comprehensive standard library, computed
properties, polymorphic queries, with blocks, transactions, and much more.
Read the EdgeQL docs for the full
picture.
🦋 More than a mapper 🦋
While EdgeDB solves the same problems as ORM libraries, it's so much more. It's
a full-fledged database with a
powerful and elegant query language, a
migrations system, a
suite of client libraries in
different languages, a
command line tool, and—coming soon—a
cloud hosting platform. The goal is to rethink every aspect of how developers
model, migrate, manage, and query their database.
Here's a taste-test of EdgeDB's next-level developer experience: you can
install our CLI, spin up an instance, and open an interactive EdgeQL shell with
just three commands.
Windows users: use this Powershell command to install the CLI.
PS> iwr https://ps1.edgedb.com -useb | iex
Get started
To start learning about EdgeDB, check out the following resources:
The quickstart. If
you're just starting out, the 10-minute quickstart guide is the fastest way
to get up and running.
EdgeDB Cloud 🌤️. The best
most effortless way to host your EdgeDB database in the cloud.
The interactive tutorial. For a
structured deep-dive into the EdgeQL query language, try the web-based
tutorial— no need to install anything.
The e-book. For the most
comprehensive walkthrough of EdgeDB concepts, check out our illustrated
e-book Easy EdgeDB. It's designed to
walk a total beginner through EdgeDB in its entirety, from the basics through
advanced concepts.
Currently, schema::Constraint.args is declared as map<std::str, std::str>, yet Constraint.args field is defined as ExpressionText (and is stored as such). It should be possible to introspect args separately. The best approach, IMO, is to create schema::Argument as such:
CREATE CONCEPT schema::Argument {
CREATE REQUIRED LINK schema::param TO schema::Parameter;
CREATE REQUIRED LINK schema::val TO std::str;
};
1. asyncpg.exceptions.RaiseError: resolve_type_name: unknown type: "288c3ba6-6741-11e7-8575-ab3c1ce9c654"
---------------------------------------- Traceback -----------------------------------------
/Users/yury/dev/edge/edgedb/edgedb/server/protocol.py, line 217, in _on_edge_connect
> self.backend = fut.result()
/Users/yury/dev/edge/edgedb/edgedb/server/pgsql/backend.py, line 1571, in open_database
> await bk.getschema()
/Users/yury/dev/edge/edgedb/edgedb/server/pgsql/backend.py, line 427, in getschema
> self.schema = await self.readschema()
/Users/yury/dev/edge/edgedb/edgedb/server/pgsql/backend.py, line 410, in readschema
> await self.read_constraints(schema)
/Users/yury/dev/edge/edgedb/edgedb/server/pgsql/backend.py, line 791, in read_constraints
> self.connection)
/Users/yury/dev/edge/edgedb/edgedb/server/pgsql/datasources/schema/constraints.py, line 42, in fetch
> """, name)
/Users/yury/dev/venvs/edgedb/lib/python3.6/site-packages/asyncpg/connection.py, line 314, in fetch
> return await self._execute(query, args, 0, timeout)
/Users/yury/dev/venvs/edgedb/lib/python3.6/site-packages/asyncpg/connection.py, line 997, in _execute
> return await self._do_execute(query, executor, timeout)
/Users/yury/dev/venvs/edgedb/lib/python3.6/site-packages/asyncpg/connection.py, line 1018, in _do_execute
> result = await executor(stmt, None)
asyncpg/protocol/protocol.pyx, line 189, in bind_execute (asyncpg/protocol/protocol.c:60713)
asyncpg.exceptions.RaiseError: resolve_type_name: unknown type: "288c3ba6-6741-11e7-8575-ab3c1ce9c654"
PostgreSQL functions have the CALLED ON NULL INPUT and RETURNS NULL ON NULL INPUT attributes that determine whether the function is called when one of the arguments is NULL.
We should have the ability to specify the same attribute on EdgeQL functions. Arguably, the RETURNS NULL ON NULL INPUT should be the default, as that's the assumed behaviour of most EdgeDB functions.
PostgreSQL has the STRICT shorthand for RETURNS NULL..., but no short counterpart. I feel like the standard blah ON NULL INPUT is too verbose.
While working with @vpetrovykh on #8, we realized that $param in constraints isn't strictly defined, which causes problems when you try to use function calls in your constraints' exprs (in fact, everything seems to be working by accident right now).
So when we changed
CREATE CONSTRAINT std::enum INHERITING std::constraint {
SET errmessage := '{subject} must be one of: {param}.';
SET expr := (subject IN $param);
};
to
CREATE CONSTRAINT std::enum INHERITING std::constraint {
SET errmessage := '{subject} must be one of: {param}.';
SET expr := (array_contains($param, subject));
};
the compiler could no longer compile the expr expression, as the type of $param is unknown.
We propose to add function-like signatures to constraints, as in:
CREATE CONSTRAINT std::enum($param: array<any>) INHERITING std::constraint {
SET errmessage := '{subject} must be one of: {param}.';
SET expr := (array_contains($param, subject));
};
which would at least make inference possible for parameters of constraints.
There's still an open issue of how to determine the type of the subject link though.
Currently EdgeQL strings are very simple and don't even have line continuation. The original rationale was that explicit string concatenation solves the problem without inventing new syntax. It also allows us to treat $$-strings as essentially raw strings, where everything is exactly as written, including -escapes.
Line continuation is somewhat desirable for the cases when logically there should be no newlines, but the amount of text is large (e.g. descriptions).
Schema aggravates this problem by being white-space sensitive. Currently schema parser will automagically dedent raw strings and handle line continuation symbol before handing the strings off to the EdgeQL parser. The case for needing nice line continuation in the Schema is stronger.
concept Foo:
description :="This is all one"+"string without newlines."
v.s. something like
concept Foo:
description :="This is all one \ string without newlines."# dedent will get rid of the whitespace before the word "string"
There's a :> operator in the schema that attempts to streamline strings, but because it lacks a terminal symbol it is difficult to highlight the string after it with regular expressions. This operator needs to be removed.
An option to consider is adding additional string types for $$-string. From lower ascii set only alphabetic characters, digits and "_" are allowed in the $foo_1$ marker. This can be used to add a special separator for different string types. We seem to need strings that automatically dedent themselves, so examples of syntax for that could be:
$d:$ ...content... $$
$d:foo_1$ ...content... $foo_1$
$:<$ ...content... $$
$foo_1:<$ ...content... $foo_1$
TODO summary:
remove :>
remove very exceptional line continuation rule from eschema
add more string literal types to EdgeQL
add line continuation to some/all string literals in EdgeQL
Currently EdgeQL doesn't recognize any escape sequences in string literals (like plain vanilla strings in SQL). This may be an issue since we like to position Edge as a modern DB.
It's worthwhile comparing how different popular modern languages handle character escaping.
GROUP and FOR constructs seem to share a lot of similarities. Additionally, GROUP has certain features w.r.t. scoping that are a bit confusing as is. The proposal is to merge FOR and GROUP syntax into one general top-level statement and stop treating FOR as a clause of other statements.
The proposed syntax is as follows:
Simple iterator
FOR (X IN expr1)
expr2
FILTER ...
ORDER BY ...
OFFSET ... LIMIT ...;
Group iterator
FOR (
X, Y1, ..., Yn IN
GROUP Foo BY
Foo.a1,
Foo.a2,
...
Foo.an
)
expr2
FILTER ...
ORDER BY ...
OFFSET ... LIMIT ...;
The fundamental idea is that this allows to clearly disambiguate when expr2 is referring to the grouped subset or to one of the grouping parameters vs. the general sets. This syntax also make more clear that clauses such as FILTER and LIMIT are applied to the result of FOR.
Currently the documentation uses several disparate schemas for examples. This is sub-optimal as it makes it harder to follow. We need to consolidate all the documentation examples into a single schema (potentially with 2 modules).
The schema theme could be "GitHub" as opposed to a more generic and flavorless "issue tracker".
Float literals with omitted leading or trailing zero are slightly error prone (they look nearly identical to an int, but are actually of a different type, which may matter in a few cases).
Additionally, the grammar for parsing tuple indexes is simplified if floats always have digits around the ..
function foo(..) -> EDGEQL
link foo:name to EDGEQL
Ideally, we want to replace eschema.ast.ObjectName with edgeql.ast.TypeName or edgeql.ast.ClassRef. The idea is to use the same types as a corresponding EdgeQL syntax uses.
When given the following expression, the parser generates incorrect AST. It works as if {spam := 1} is a sub-shape of the Foo shape, whereas, in fact, it is an independent shape of a SELECT Bar expression.
CREATE CONSTRAINT std::enum(array<std::any>) {
SET errmessage := '{subject} must be one of: {$0}.';
SET expr := array_contains($0, subject);
};
should become
CREATE CONSTRAINT std::enum(array<std::any>) {
SET errmessage := '{subject} must be one of: {$0}.';
FROM edgeql $$
SELECT array_contains($0, subject);
$$;
};
Here's a list of commits that require some amount of documentation updates:
lexical.rst has a bunch of empty sections.
graphql: Implement reflection of ObjectTypes. (commit 0d8d5a7)
ObjectTypes get reflected as type INTERFACE into GraphQL. Additionally,
non-abstract ObjectTypes get reflected as type OBJECT, but with the
"Type" appended at the end of their name.
This allows the return type of fields/functions to be specified in terms
of INTERFACE types and the actual object __typename to reflect the
OBJECT type of the specific instance.
When a computable expression contains a path, use the trailing
link or property as a base for the computable in shape.
For example, in Spam { alias := Foo.bar }, Spam.alias
is derived from Foo.bar inheriting its properties.
edgeql: Add re_replace function to std. (commit 9c20ef1)
graphql: Use a filter argument for more flexible filtering. (commit aa1c201)
Rather than taking arguments corresponding to the individual fields, use
a single filter argument to specify the filtering conditions. This is
more generic and easier to add more functionality to in the future.
All fields that return a list of objects can now be ordered by one or
more of the scalar fields on that object.
Disallow overriding the 'id' property (commit e502f4c)
The value of the id property of an object coresponds to object's identity
(and value). It is both unsafe and unintuitive to allow arbitrary
re-definition of id.
SELECT Foo ORDER BY Foo;
SELECT Foo ORDER BY Foo.id;
The above queries are equivalent by default, but would stop being equivalent
if id was re-defined in a view.
Add after, first, before and last arguments to anything that
returns an object list. The arguemnts have the same semantics as in
Relay Connections regardless of whether the field returns a Connection
or a plain List.
edgeql: Update the type specification syntax. (commit 03b3d52)
Introduce type expressions - syntactical constructs denoting types and
type composition.
Operator | produces a union type that is a duck type having the
intersection of all of the links and properties of the component types.
Semantically any object that is an instance of at least one of the
component types is also an instance of the union type.
Operator & produces an intersection type that is a duck type having
the union of all of the links and properties of the component types.
Semantically only an object that is an instance of all of the component
types is also an instance of the intersection type.
Syntactically type expressions are general and can be used anywhere
where a type can be used. There may be some exceptions, notably only
base types can be used in extending specification.
The [] indexing operator is also available for JSON. This operator is
strict and will raise an exception if it is used with a non-existent
index value (this is true for JSON, strings and arrays).
There's a new array_get function that doesn't perform the boundary
check on the index value and returns an empty set if there is no such
index.
Add support for casting objects and tuples to std::json (commit 8b7a72e)
<json>Object now always returns a std::json value containing
the output representation of the object. That is, the result is
the same as the output of SELECT Object in JSON mode, including
the type shape.
Now, casting a non-named tuple to std::json returns a JSON array,
and casting a named tuple to std::json returns a JSON object.
Casting non-scalar JSON values to scalars is now an error, as well as
casting a non-matching JSON scalar value.
Prohibit VARIADIC parameters from having default values (commit 952be2f)
Implement named and variadic arguments; fix type system. (commit 57a24bf)
Now we support named arguments and variadic parameters:
SELECT foo(1, 2, 3, $arg := 'val')
Type system was fixed to correctly handle implicit casts for
ints and floats.
Add support for on target delete clause for links (commit ef120bd)
The new on target delete clause of concrete links is a mechanism
to specify what happens when a target object of a link instance
is deleted.
Currently implemented modes are:
RESTRICT -- produce an error indicating that deletion cannot be
done due to an existing link. This is the default.
DEFERRED RESTRICT -- same as the above, but the check is postponed
until the end of the current transaction, if there is no transaction,
equivalent to RESTRICT.
SET EMPTY -- remove the link, can only be used with links that are
not required.
DELETE SOURCE -- remove the link and the source of the link
(cascaded deletion).
Update function declaration and call syntax (commit 6377f93)
No longer use '$' as a prefix for parameter names in function
signatures and call expressions. This is to declutter the syntax
in expressions like the following:
func($foo, $default := $something)
which now becomes
func($foo, default:=$something)
It's now immediately clear what refers to a variable and what sets a
value for a named argument.
edgeql: Alter the precedence rules. (commit c9a3164)
Move shapes {} to a higher precedence, same group as [].
Move DETACHED in between :: and . in precedence.
Update existing tests and add more tests on interactions between
precedence, shapes, scopes, and DETACHED.
Enforce lexical structure in string literals; support escape seqs (commit 094fc18)
Allowed escape codes in string literals:
\n, \t, \r
\, ', "
\xXX, \uXXXX, \UXXXXXXXX
Otherwise string literals allow any unicode characters and new lines.
Add support for raw string literals; tweak lexer/ast. (commit 399cb02)
Add raw strings.
$$ ... $$ strings are now raw strings.
New syntax: r'...' and r"..." -- raw string literals.
Raw strings do not process any escapes, meaning that \ symbol
acts just as any other one.
Tweak string literals.
Regular strings no longer accept \xhh codes over 0x7F. This is
modeled after Rust strings [1], quoting:
"Higher values are not permitted because it is ambiguous whether
they mean Unicode code points or byte values."
New AST nodes.
EdgeQL and IR implementations got two new nodes: StringConstant and RawStringConstant. SQL has also a new node: EscapedStringConstant
(compiles to an E'...' string).
schema: Add support for spaces + tabs indents. (commit af76bc2)
Indentation must be consistent, so if a mix of tabs and spaces is used,
tabs must come first and shallow indentation must be an exact prefix of
deeper indentation.
It is illegal to use a tab after a space in indentation.
It is illegal to use a line continuation immediately after indentation.
Some other content must appear first on that line.
Whitespace is still ignored in parentheses or in the middle of a line
even if it is extended via line continuation.
Implement support for polymorphic functions (commit a8424aa)
Update re_* functions to have "pattern" as a first parameter (commit c69770b)
Make regular division return floats and add a floor division operator (commit d0b572e)
In another break with tradition, / now always performs a true division:
even if the operands are integers, the result is a floating point
approximation of the division result. The new // operator allows
pefforming floor division explicitly.
In this change we largely follow the motivation of Python's PEP 238.
Classic behavior of integer division makes it hard to write polymorphic
functions that work with arbitrary numeric types, especially if one
considers numbers beyond the real plane, as blind casting of one of the
operands may not work as expected.
Allow indexing named tuples by position; fix named tuples serialization (commit 3e0832c)
Named tuples can now be indexed by position. The below two queries are
equivalent:
db> SELECT (a:=1).a;
db> SELECT (a:=1).0;
Named tuples will now be correctly serialized to JSON in more cases,
for example:
db> SELECT [(a:=1)][0];
{"a": 1}
Serialization of nested named tuples is still broken in some cases
though:
We want to add a "std::any()" function and it seems that renaming
"any" to "anytype" is a better option than specialcasing any()
production in the grammar. Also "anytype" plays nice with "anyscalar"
and "anyint".
Rework the way relationship cardinality constraints are expressed (commit f77fe8c)
We currently represent relationship cardinality as the cardinality
attribute in concrete link and property definitions. The accepted
values are '11' (one-to-one), '1*' (one-to-many), '*1' (may-to-one),
and '**' (many-to-many). While this is accepted parlour, the
interpretation of the constraints, together with the separate required
qualifier produces a fair amount of cognitive load.
In reality, the current monolithic relationship cardinality actually
represents two distinct properties: the cardinality of the target set
when traversing a pointer from a given source (the second '1' or ''),
and the exclusivity constraint on the target (the first '1' or '').
The first property needs to be specified far more often than the second,
so it makes sense to separate them.
Now, a concrete link or property declaration can include the new target
set cardinality qualifier in the form of the new single and multi
keywords. For example:
type User:
link friends -> User:
cardinality: '**'
becomes:
type User:
multi link friends -> User
The single qualifier is the default, but can still be specified:
type Issue:
single link status -> Status
The required qualifier can be combined with the single/multi
qualifier to form a precise target set cardinality requirement: single means zero-or-one, required single means exactly one, multi means zero-or-more, required multi means one-or-more.
The second relationship cardinality property is target exclusivity, i.e.
how many sources can relate to a given target, is now represented by the exclusive constraint, which is the current unique constraint, but with
a new, more appropriate name. It is more natural to say that a given
relationship is exclusive, rather than unique. For example, in the
relationship Person -> owns -> Chair, "this chair is exclusive to this
person" is better than "the Person/Chair relationship is unique":
type Person:
single link owns -> Chair:
constraint exclusive
So, to summarize:
Former '11' is now single + constraint exclusive.
Former '1*' is now multi + constraint exclusive.
Former '*1' is now just single (this is the default).
Former '**' is now just multi.
The new qualifier syntax is also significantly more natural to use with
computable links and properties when an explicit cardinality qualifier
needs to be given to override the inferred value:
type Person:
multi link items := {'item'}
This now extends to ad-hoc computables in queries as well, so:
The WITH CARDINALITY '*' syntax is now entirely unnecessary and has
been removed.
The new syntax also allows expressing the non-zero cardinality
requirement with the required qualifier on the computable, but that is
left for a subsequent commit (see #217).
edgeql: Update how time and date is handled. (commit a3457f3)
The time and date scalars are:
datetime
naivedatetime
naivedate
naivetime
timedelta
There are 3 different functions for obtaining "current" datetime:
datetime_now
datetime_of_statement
datetime_of_transaction
There are also a suite of functions that extract specific date or time
parts:
datetime_get
date_get
time_get
timedelta_get
edgeql: Add converter functions for multiple data types. (commit 19e6a23)
The to_str function is meant to be like a cast when called
without the format parameter.
to_str usage with json scalars is not equivalent to casting, it is
producing a string representation of JSON. The inverse function to_json
is therefore parsing a string, not casting either:
str_to_json becomes to_json
json_to_str becomes to_str
Add various date and time converter functions.
Restrict usage of abstract scalars and anytype. (commit a23af17)
Casting into an abstract scalar or anytype is now forbidden.
Technically anytype is a valid type that is the supertype for both
anyscalar and Object. However, it is currently illegal to use it
anywhere outside of a function signature definition.
Anytype is also special in the fact that it doesn't have a corresponding
entry in the schema module (no Type object).
edgeql: Make casts more consistent. (commit fe0e8cb)
Forbid casting bytes into anything other than bytes (this makes sense
for scalar types derived from bytes).
Make JSON casts symmetric. For any type that allows a cast into json, it
should also allow to cast back without additional intermediate steps
(such as casting into str first).
Formally implement fundamental operators as part of stdlib (commit f406f99)
This declares currently supported binary and prefix operators
on standard scalar types using the new CREATE OPERATOR DDL.
Expression type inference has been switched over from a
hardcoded table to looking up operators from the schema.
This also allows removing a bunch of hardcoded logic from the
compiler, since we can now consistently rely on the operators
properly reflected in the backend.
This commit also features correct implementation of the floor division
(//) and division remainder (%) operators, which now truncate toward
negative infinity, like Python does.
Implement array, string and byte concatenation via the ++ operator (commit de73ddf)
The current + operator is a poor choice for concatenation, especially
in the array case, since the latter may be interpreted as an
element-wise vector addition. Instead, use ++ consistently as the
concatenation operator for all data types that support it.
Schema attributes are named values associated with schema items and are
designed to hold arbitrary user-defined schema-level metadata.
There are several shortcomings with the current implementation that
need tweaking.
First issue: attributes can currently be of any primitive type, which
makes it really hard to represent the attribute collection as a
coherent whole without resorting to some string serialization, and that
can go sideways quickly if the attribute type is reasonably complex.
Thus, we stop pretending to support arbitrary types in attributes, and
only support std::str. Complex structures can be stored as JSON and
coerced to a specific EdgeQL type explicitly, so this change has no
practical usability loss. This also means that abstract attribute
declaration does not require a type specification anymore, although we
may revisit this in the future and allow std::str sub-types to be used,
to, for example, allow specifying constraints on attribute values.
Abstract attribute declarations continue to be required for
disambiguation purposes.
Second issue: standard schema item aspects, like link cardinality, are
represented as attributes in the introspection schema. There is no
reason to do it, and this creates confusing duality, since some aspects
are represented as properties and links of the appropriate schema::Object subtype. We stop doing this, and only reserve schema::Object.attributes to things that are true annotations and bear
no effect on the core schema behavior. Currently the only attributes
that satisfy this requirement are "title" and "description".
In DDL, the SET <attribute-name> := <attribute-value> is changed to SET ATTRIBUTE <attribute-name> := <attribute-value>.
In schema DSL we allow setting attribute values on foreign schema items
using the regular declaration syntax with a qualified name:
Make attributes non-inheritable by default (commit 9798846)
Automatic inheritance of attributes is sometimes inconvenient or
undesirable. A "description" attribute, for example, is unlikely to be
useful when inherited. Thus, we make all attributes non-inheritable by
default, with an option to opt-in to automatic inheritance with the new INHERITABLE qualifier for CREATE ATTRIBUTE:
Since we now can use {} for denoting sets, let's add this.
Without set literals it's hard to even write edgedb documentation: we'll have to explain the meaning of 1 UNION 2 or teach what array_unpack() does way too early.
@vpetrovykh Could you please open a PR to add set literals to the grammar/parser?
To avoid having to deal with the questions of set distinctness for INTERSECT and EXCEPT, let's just remove them and suggest to use SELECT A WHERE A [NOT] IN B instead.
DISTINCT and ALL modifiers need to be dropped from aggregate function calls. This is strongly related to the addition of UNION ALL operator, so it makes sense to do at the same time. This is something that may require input from @elprans in order to make sure that what the parser is generating aligns reasonably with the rest of the processing pipeline.
Currently we have a mechanism to define "abstract constraints", they apply to the derived concepts. This way we can have a base Dictionary class with a unique constraint on its key link which will be enforced on all concepts inherited from Dictionary.
One idea is to rename "abstract constraints" to "deferred constraints".
concept AbstractConstraintParent:
link name to str:
abstract constraint unique
Using up to 6 processes to run tests.
unittest/loader.py E
Progress: 1/1 tests.
==============================================================================================================================================
ERROR: tests/test_edgeql_syntax (unittest.loader._FailedTest)
----------------------------------------------------------------------------------------------------------------------------------------------
ImportError: Failed to import test module: tests/test_edgeql_syntax
Traceback (most recent call last):
File "/opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/loader.py", line 153, in loadTestsFromName
module = __import__(module_name)
ModuleNotFoundError: No module named 'tests/test_edgeql_syntax'
FAILURE
tests ran: 1
errors: 1
Running times:
tests: 00:00:00.1
The notion of some language-independent operators is a rather odd one. The operators belong to the specific language where they are defined. The only thing worth salvaging and having in common is probably the Operator class itself.
In particular, operators should be transpiled from one language to another.
Typos and outdated syntax can creep into documentation examples. We need a tool to test them together with the rest of the test suite. In particular that means that we will need to make the examples use module names in a self-consistent and sane manner.