Our mission is to empower developers with a database to build software faster and with less effort.
See what is already implemented as well as of what to expect in the future.
The data model, EdgeQL, and all aspects of EdgeDB operation should be straightforward to learn and reason about, and the user experience should be a satisfying one.
EdgeQL features, language bindings, and tooling should be designed with high performance, low latency operation in mind.
Correctness should never be sacrificed in favor of ergonomics or performance. Nonsensical operations must always generate an error.
EdgeDB foundation: modern, type-safe, relational data model.
EdgeDB is built on top of PostgreSQL, inheriting all its core strengths: ACID compliance, performance, and reliability.
Object types, scalar types, arrays, tuples. Type composition and inheritance.
Support for polymorphic and generic functions and operators. Support for user-defined functions.
Support for arbitrary expressions in constraints. Multi-property constraints.
Complete schema introspection via EdgeQL and GraphQL queries.
Ability to declare insert/update/delete triggers on object types.
abstract type Shape;
type Rectangle extending Shape {
property width -> float64;
property height -> float64;
}
type Circle extending Shape {
property radius -> float64;
}
function area(shape: Rectangle)
-> float64
from edgeql $$
SELECT shape.width * shape.height
$$;
function area(shape: Circle)
-> float64
from edgeql $$
SELECT 3.1415 * shape.radius ^ 2
$$;
# A polymorphic EdgeQL query:
SELECT
Shape {
typename := .__type__.name,
area := area(Shape)
}
FILTER
Shape IS (Circle | Rectangle);
EdgeQL is the primary language of EdgeDB. It is used to define, mutate, and query data. Its main goals are to be readable, concise, and yet as powerful as SQL.
EdgeQL allows to fetch deep object hierarchies effortlessly, while applying filtering and sorting to the nested data.
Aggregate functions perform calculation on sets. EdgeDB supports many statistical and compositional aggregates.
Support for inserting a hierarchy of objects in a single atomic statement.
EdgeQL is a strongly typed functional language.
SELECT
Movie {
title,
actors: {
name,
email
} ORDER BY .name,
avg_review := math::mean(
.reviews.rating)
}
FILTER
datetime_get(.release_date,
'year')
IN {2018, 2019};
INSERT
Movie {
title := 'Dune',
actors := {
(INSERT Person {
name := 'Jason Momoa',
}),
(INSERT Person {
name := 'Rebecca Ferguson',
})
}
};
Errors should always be descriptive and besides just saying that something went wrong, they should suggest where exactly and how to fix that.
edgedb> ....... .......
SELECT Movie {
avg_review := math::mean(.reviews.rating),
} FILTER .release_date > <datetime>'2018-01-01';
InvalidValueError: invalid input to mean(): not enough elements in input set ### SELECT Movie { ### avg_review := math::mean(.reviews.rating), ### ^^^^^^^^^^ help: consider guarding with an IF expression ### } FILTER .release_date > <datetime>'2018-01-01';
Generalized partitioning and window functions.
WITH
Win AS WINDOW (WeeklyShowing
GROUP BY .movie
ORDER BY .week)
SELECT
WeeklyShowing {
movie,
week,
box_office,
weekly_change :=
(lag(.box_office) OVER Win) -
.box_office
}
FILTER
.movie.name ILIKE 'Avengers%';
GROUP
Movie {
release_year := datetime_get(
.release_date, 'year')
}
BY
Movie.genre, Movie.release_year
INTO
MG
UNION
(
genre := MG.genre,
release_year := MG.release_year,
avg_viewer_rating := math::mean(
MG.reviews.rating),
max_box_office := max(
MG.box_office)
);
Official language highlighting packages for Atom, Sublime Text, Visual Studio Code, and Vim.
Integrated LSP support allows code-completion and error highlighting for EdgeQL and EdgeDB SDL in IDEs.
Updatable views are an important mechanism for Database Views, GraphQL mutations and backwards-compatible schema migrations.
CREATE VIEW Comedy := (
SELECT Movie {
name,
}
FILTER
.genre = 'Comedy'
);
INSERT
Comedy {
name := 'Long Shot',
genre := 'Comedy'
};
EdgePL is the imperative language used to write more complex functions and triggers.
CREATE FUNCTION fibonacci(n: int64) -> int64 FROM EdgePL $$
let z := 0;
let i := 0;
let j := 1;
if n < 1 {
return 0;
}
while z <= n {
z := z + 1;
i, j := j, i + j;
}
return i;
$$;
EdgeDB ships with built-in GraphQL support.
Any EdgeDB object type can be queried and introspected via GraphQL.
GraphQL can query EdgeQL Aliases in situations where a complex condition or a function call is needed.
Access control and business logic rules specified at the schema level and transparently enforced for GraphQL queries.
Support for mutation of object types and EdgeQL updatable views.
Support for subscribing to a GraphQL endpoint to receive live updates.
fragment Groups on User {
groups {
name
}
}
query {
User(filter: {
name: {ilike: "anna%"},
age: {gt: 30}
}) {
name
age
settings(
order: {name: {dir: ASC}},
first: 5
) {
name
value
}
...Groups
}
}
The goal of the EdgeDB standard library is to include a large set of high-quality, consistent functions and operators.
Common numeric functions, operators and literals. Strict handling of numeric precision.
Common string functions and operators. Raw strings literals, regular expressions.
Strict, consistent handling of timezone-aware datetimes, local date, local time and durations.
Functions, operators and casts to traverse, extract and form JSON values.
Geometry and geography types and associated functions and operators.
Support for efficient numeric vector and matrix computations.
db>
SELECT (5 / 2, 5 // 2);
{2.5, 2}
db>
SELECT 1.5n + 1.0;
QueryError: operator '+' cannot be applied to operands of type 'std::decimal' and 'std::float64'
db>
SELECT <local_date>'2019-01-01';
{'2019-01-01'}
db>
SELECT <datetime>'2019-01-01';
InvalidValueError: missing required timezone specification
db>
SELECT <datetime>'2019-01-01 EST';
{'2019-01-01T05:00:00+00:00'}
db> ... ... ...
SELECT <json>Movie {
title,
year,
}
{'{"title": "Blade Runner", "year": 1982}', '{"title": "Dune", "year": 2020}'}
Support for declarative schema definition (SDL) and automatic DDL generation as migrations.
Support for migration rollbacks.
START MIGRATION TO {
module default {
# type Review { ... }
# type Person { ... }
type Movie {
required property title -> str;
required property year -> int64;
multi link reviews -> Review;
multi link directors -> Person;
multi link cast -> Person;
property avg_rating :=
math::mean(.reviews.rating);
}
}
};
POPULATE MIGRATION;
COMMIT MIGRATION;
edgedb migration
command line utility for interactive migration generation and integration with version control systems.
$
ls -l dbschema/
default.esdl
$
edgedb create-migration
Migration 0001-initial created.
$
ls -l dbschema/migrations/
0001-initial.edgeql
$
edgedb migrate
Applying migrations: - 0001-initial [OK]
$
sed -i 's/type Order/type Invoice/g' dbschema/default.esdl
$
edgedb create-migration
Detected schema changes: ALTER TYPE Order RENAME TO Invoice; Confirm migration chunk [y,n,a,d,e,?] y Migration 0002-order-rename created.
$
edgedb migrate
Applying migrations: - 0002-order-rename [OK]
Staggered update deployment and continuous integration require both the old and the newschema to be available at the same time. On large deployments there should be a way to migrate without blocking production traffic.
prod-02 $
env EDGEDB_SCHEMA_TAG=v99 run_server.py
### Meanwhile, on a devbox: ###
devbox $
edgedb migration create --tagged
Migration 0100-schema-updates created.
devbox $
deploy_to_prod.sh --to prod-01
prod-01) Applying migrations: prod-01) - 0100-schema-updates [OK] prod-01) Restarting server: prod-01) env EDGEDB_SCHEMA_TAG=v100 run_server.py
Data access control is one of the most ubiquitous types of business logic in applications. Supporting flexible access rules at the schema level benefits performance, security, and development productivity through the separation of concerns.
type Service {
property name -> str
};
type ServiceProvider extending User;
type ServiceManager extending User {
link service -> Service;
};
type Order {
link service -> Service;
link provider -> ServiceProvider;
property total -> decimal;
policy sp_access on SELECT {
WHEN
(GLOBAL user) IS ServiceProvider
CHECK
.provider = (GLOBAL user)
};
policy sm_access on SELECT {
WHEN
(GLOBAL user) IS ServiceManager
CHECK
.service = (GLOBAL user).service
}
}
db> ... ... ...
SET GLOBAL user := (
SELECT ServiceProvider
FILTER .name = 'Roofing inc.'
);
SET GLOBAL
db>
SELECT count(Order);
{10}
db> ... ... ...
SET GLOBAL user := (
SELECT ServiceManager
FILTER .name = 'Roof Manager'
);
SET GLOBAL
db>
SELECT count(Order);
{123}
Schema introspection has powerful applications in automatic or assisted generation of APIs, user interfaces and application bindings. However, there are security and API surface exposure concerns when it comes to exposing the database schema. Database views can help with that.
Combined with fine-grained data access control rules, this can significantly reduce the need to write backend code.
database view GraphQLView {
# Export all types from the
# "users" module:
type users::*;
# Export the "Order" type as
# "Invoice" with a limited
# set of properties:
type orders::Order as Invoice {
link user;
property subtotal;
property taxes;
property total :=
.subtotal + .taxes;
}
};
# Start a GraphQL endpoint
# exposing the "GraphQLView"
# view:
CONFIGURE SYSTEM INSERT Port {
protocol := "graphql+http",
database := "GraphQLView",
port := 80,
user := "graphql",
};
Stored computable properties and links with flexible invalidation policy:
type Movie {
link reviews -> Review;
property avg_rating {
expr := math::mean(.reviews.rating);
stored := true;
valid_for := <duration>'1 hour'
}
}
EdgeDB will provide native idiomatic drivers for all popular platforms. Drivers for Go, Rust, Ruby, Java, and .NET are on our list.
In any EdgeDB client library data can be requested either as high-level objects or JSON.
A fast Python driver supporting both blocking IO and async/await paradigms.
An idiomatic JavaScript driver with support for both async/await and callback APIs.
EdgeDB can expose both EdgeQL and GraphQL via an HTTP endpoint.
# Use "query()" to fetch data
# as rich Python objects.
#
# (If JSON is needed, just use
# the "query_json()" method - no
# need to change the query).
movies = await conn.fetchall('''
SELECT
Movie {
title,
actors: {
name,
email
} ORDER BY .name,
avg_review := math::mean(
.reviews.rating)
}
FILTER
datetime_get(.release_date,
'year')
IN {2018, 2019};
''')
print(movies)
EdgeDB bindings for all languages will have a query building API.
from edgedb import qb, connect
from my.app import esdl as schema
get_movies_query = qb.shape(schema.Movie, [
'title',
qb.shape(schema.Movie.actors, [
'name',
'email'
]).order_by('.name'),
qb.computable('avg_review', qb.math.mean(
schema.Movie.reviews.rating))
]).filter(
qb.IN(
qb.datetime_get(schema.Movie.release_date, 'year'),
{2018, 2018}
)
)
conn = connect()
data = conn.query_json(get_movies_query)
print(data)