HNHacker News
TopNewBestAskShowJobs

RedCrowbar

438 karma · joined September 20, 2012

Working on hard problems @ Vercel.

Previously co-founder and CTO of Gel Data [1].

[1] https://geldata.com

[ my public key: https://keybase.io/elprans; my proof: https://keybase.io/elprans/sigs/p0bjVrvsPB2J-0pE_ZgpeT-gli6jsZ45wHgeBaMKNNY ]

submissionscomments
RedCrowbar··on Show HN: EdgeDB 1.0
GraphQL is already supported: https://www.edgedb.com/docs/graphql/index
RedCrowbar··on Show HN: EdgeDB 1.0
It's a matter of taste. We decided to do a "looks like SQL + GraphQL" style because that's what most people are familiar when they think about a query language.

That said, the functional variant is a likely way to represent EdgeQL in programming languages.

RedCrowbar··on Show HN: EdgeDB 1.0
It's built on Postgres, but it isn't a _thin_ wrapper. We lean hard into Postgres query machinery and type system in order to pull off EdgeQL and graph-relational efficiently.
RedCrowbar··on Show HN: EdgeDB 1.0
Data is stored relationally in Postgres in 3NF. References are indexed automatically, but you still need to index type properties if you use them in `filter`.
RedCrowbar··on Show HN: EdgeDB 1.0
(EdgeDB CTO here)

In a classic relational model everything is a tuple containing scalar values. Graph-relational extends the relational data model in three ways:

- every relation always has a global immutable key independent of data (explicit autoincrement keys aren't needed)

- this enables us to add a "reference type", which is essentially a pointer to some other record (i.e. a foreign key)

- attributes can be set-valued, so you can have nested collections in queries and in your data model.

This is what lets us do `Movie.actors.name` instead of a bunch of `JOINs`, because `actors` is declared as a set-valued reference type in the `Movie` relation.

RedCrowbar··on Building a production database in ten years or less
Github discussions are a good place for long form questions/proposals. We also just created a Discord server, everyone is welcome to join: https://www.edgedb.com/p/discord
RedCrowbar··on Building a production database in ten years or less
GraphQL is a lot more limited because it was never really intended to be used as a full-blown database query language. While EdgeDB supports GraphQL as a way to do queries, this support is currently experimental and is not intended to be used as an alternative for EdgeQL. Our position is that GraphQL should be used as originally intended: as an introspectable and more flexible replacement for REST APIs. There is work in progress to add a declarative way to define explicit API schemas as controlled mappings of EdgeDB schemas, (e.g. implementing GraphQL "resolvers" as EdgeQL functions or expression aliases, adding access control, etc). This would address the issues with the direct "Database-as-GraphQL" approach: 1) your API is not a direct reflection of your internal database schema, and 2) there is granular control over what and how things can be queried, which is important for performance and security.
RedCrowbar··on Building a production database in ten years or less
> One thing that separates EdgeDB from ORMs is the explicit design decision to be coupled with one specific database.

Yes, this is fundamental, and it is what enables us to drive the innovation in the query language. PostgreSQL's query engine is awesome and very powerful, but it is hopelessly obscured by SQL and its data model, and then further by the ORMs. EdgeDB and EdgeQL liberate that power and present it in a modernized way. EdgeQL wouldn't be possible or practical today without Postgres.

RedCrowbar··on Building a production database in ten years or less
> I both hope you have great success

Thank you!

> also that we get to have beer at a conference sometime to share war stories and trade-offs

Looking forward!

RedCrowbar··on Building a production database in ten years or less
> Love what you're doing.

Thanks!

> any plans to implement a full stored procedure language?

There are plans, and they have the word "Wasm" in them :-) You can also do a lot with pure-EdgeQL functions already.

> how well does it integrate with calls to postgres functions?

In theory all you need to do is write a function declaration with `USING SQL FUNCTION` [2], however we are currently limiting this functionality to the standard library, until we grow a well-defined user extension mechanism (a-la `CREATE EXTENSION` in Postgres).

[1] https://github.com/edgedb/edgedb/blob/6f87f44fbc9854ba060994...

RedCrowbar··on Building a production database in ten years or less
(EdgeDB CTO here)

> The schema you have designed helps a lot on the server-side. If this schema can be extensible (that is, people can make extensions by allowing extra information in the schema that the DBMS ignores, similar to comments) and available to an authorized user, people can create great tools for the client-side as well.

EdgeDB supports arbitrary JSON annotations on all schema objects, for example:

  type User {
    property name -> str {
      annotation render_as := '"textbox"';

      constraint expression on (len(.name) < 10) {
        annotation comment := '"needed for external reasons"';
      }
    }
    
    annotation data_for_app := "some-json-data"
  }

> I'm throwing away Prisma integration of my company's project because they did not provide enough extension points, hooks and all the other ways to use the full power of the database. So as soon as you need something they don't support, you have to bypass the whole abstraction layer.

This is why we are investing so hard into EdgeQL. If you can't express something with EdgeQL that you can with SQL -- it's a bug. If whatever you do seems _harder_ (or slower) with EdgeQL than with SQL, it's also a bug. Same applies to generated database clients (we'll be releasing a JavaScript/TypeScript client generator / query builder alongside EdgeDB 1.0).

RedCrowbar··on PRQL – A proposal for a better SQL
EdgeQL is getting support for generic partitioning/aggregating `GROUP` very soon [1], so we are giving some love to the analytical side of things too :-)

We definitely need more collective effort put into "Better SQL", so PRQL is a welcome sight!

[1] https://github.com/edgedb/rfcs/blob/21e581a188715c6ff82944b6...

RedCrowbar··on Atlas – Terraform but for Database Migrations
Starting with an expressive declarative schema and building everything around it is exactly the approach we take in EdgeDB [1]. One of the key ideas is that you should be able to define almost anything not just statically, but as a result of some computation. Think views, functions, and computed columns, but without the traditional limitations and change-rigidity of their implementation in Postgres, because schema migrations are treated as a single logical change unit [2] rather than a bunch of independent DDL statements, so dependencies between schema objects and their changes are understood. The richness of the schema coupled with thorough introspection [3] then enables GraphQL schema derivation [4] and type-safe client generation without any loss in fidelity.

(disclaimer: I work on EdgeDB)

[1] https://www.edgedb.com/ [2] https://www.edgedb.com/docs/guides/migrations/index [3] https://www.edgedb.com/docs/guides/introspection/index [4] https://www.edgedb.com/docs/graphql/graphql

RedCrowbar··on EdgeDB: EdgeQL
edb server is mostly a development-mode proxy to edgedb-server, which is the actual server binary and supports the same options. The Docker image README [1] has some documentation on the supported options.

[1] https://github.com/edgedb/edgedb-docker#official-dockerfile-...

RedCrowbar··on EdgeDB: EdgeQL
You can. It is possible to point EdgeDB at an arbitrary Postgres cluster and have it coexist with other databases.
RedCrowbar··on EdgeDB: EdgeQL
There aren't any special "bulk" interfaces at the moment, you can load data via a small program using our language bindings (INSERT in a loop). We'll add support for batched `executemany()` soon to cater to very large loads specifically. A similar optimization in asyncpg [1] led to a 10x improvement in throughput (~100Krec/sec per connection), and we expect similar performance from EdgeDB once the protocol and the bindings are updated.

Loading arbitrary JSON by introspecting the data, and auto-generating the schema and the necessary DML statements is fairly straightforward too. I have a rough Python proof-of-concept, will need to find time to finish and publish it.

[1] https://github.com/MagicStack/asyncpg/pull/295

RedCrowbar··on EdgeDB: EdgeQL
There is a difference in how large a set `array_agg` would need to process, since `LEFT JOIN` may produce a very sparse relation and NULLs still have to be scanned over.
RedCrowbar··on EdgeDB: EdgeQL
If given a recursive schema like this:

    type Tree {
        property value -> str
        link parent -> Tree
    }
    
you'd traverse the link as usual:

    SELECT Tree {
        value,
        parent: {
            value
        }
    }
    FILTER .parent.parent.parent.value = 'foo'
    
If there's a need to self-join on an arbitrary property, then you could use a `WITH` clause to explicitly bind the two sets:

    WITH
        T1 := Tree,
        T2 := Tree
    SELECT
        T1 {
            similarly_valued := (SELECT T2 FILTER T1.value = T2.value)
        }
RedCrowbar··on EdgeDB: EdgeQL
EdgeQL lets you do arbitrary joins as well. Here's how you could compute salaries of employees by department even if you for some reason don't have a link between Employee and Department:

    SELECT Department {
        name,
        employees := (
            SELECT Employee { name, salary } 
            FILTER Employee.department_name = Department.name
        )
    }
   
Links in EdgeQL are merely an abstraction over the fact that most joins in a well-normalized schema are done over primary keys.
RedCrowbar··on We Can Do Better Than SQL
Both EdgeQL and SQL disregard the RM proscription about duplicate tuples for practical reasons:

1. Elimination of duplicates from every projection is prohibitively expensive.

2. Sometimes you actually _want_ duplicates to show up without injecting a synthetic key into every projection.

3. There's DISTINCT.

RedCrowbar··on We Can Do Better Than SQL
> Can you model this in EdgeQL?

Absolutely!

  WITH
    april := <datetime>'2020-04-01T00:00+00',

    NewCustomers := (
      SELECT Customer
      FILTER
        NOT EXISTS (
          SELECT .orders
          FILTER .date < april
        )
    ),

    AprilCustomers := (
      SELECT Customer
      FILTER
        datetime_truncate(.orders.date, 'months') = april
    ),

    NewAprilCustomers := (
      SELECT AprilCustomers
      FILTER AprilCustomers IN NewCustomers
    )

  SELECT
    (count(NewAprilCustomers) / count(AprilCustomers)) * 100;
This assumes the following schema:

  type Order_ {
     property date -> datetime;
  }

  type Customer {
     multi link orders -> Order_;
  }
RedCrowbar··on We Can Do Better Than SQL
Take the CASE WHEN example from the blog. An equivalent EdgeQL expression is

  SELECT 'one' IF value = 1 ELSE 'not one'
If value is an empty set, then the result is _always_ an empty set, unlike SQL that pretends NULL values are actually boolean for the purposes of the condition. EdgeQL expressions are, essentially, set comprehensions, the above is equivalent to this Python expression:

  {'one' if v = 1 else 'not one' for v in value}
RedCrowbar··on We Can Do Better Than SQL
Alas, we don't support matviews yet, but it's on the TODO list.
RedCrowbar··on We Can Do Better Than SQL
> This is a reinvention of the the hierarchical database

It's not. Just because data can be fetched as a set of "tree-like" things does not make the model hierarchical in any way. But it makes lots of practical use cases easier.

> The fundamental idea that you have a highly structured data tree is what the relational model threw away.

And then ORMs came and reinstated the status quo. Truth is, many applications actually benefit from a highly structured data graph. That said, you don't have to use the "highly structured" bits of EdgeDB, it's perfectly fine if all your types are simple tuples.

> We compose things into other things, subtype things, and otherwise deal in things. And this turns out to be a disaster when lots of people need to extend and reuse hunks of the same database.

I would be very interested in reading about this. Do you have any references? Most cases of "disaster" in my experience had to do with extremely poorly defined and documented schemas that had barely any relation to the actual business model.

RedCrowbar··on We Can Do Better Than SQL
Empty set differs from NULL is that you get an empty set if you apply an element-wise function over it (which most operators and functions in EdgeQL are).

> Why not just use an Option/Maybe type?

We are considering adding algebraic types and a syntax to match them, but it ultimately boils down to taste and use case:

  SELECT value ?? "fallback in case of empty"
is not fundamentally different from (hypothetical):

  SELECT 
    MATCH value
    CASE Some(non_empty_val) THEN non_empty_val
    CASE Empty THEN "fallback in case of empty"
    END;
RedCrowbar··on We Can Do Better Than SQL
There's also HTTP/JSON support.
RedCrowbar··on We Can Do Better Than SQL
EdgeDB is not quite D if you read all the proscriptions to the letter, but it's very close :-)
RedCrowbar··on We Can Do Better Than SQL
The difference is that `+` is defined as a strict function (returns empty on empty input): plus(a, b), whereas sum() is an aggregate that is specifically defined as 0 on empty input.
RedCrowbar··on We Can Do Better Than SQL
> {value} OR {} = {value}, not {} (OR ≡ ∪), <bool>{} being {} instead of a true boolean value

This is because we define infix OR as

{a OR b | ∀ (a, b) ∈ (A x B)}

Same goes for the cast function.

You may argue that this is confusing, but if you think about everything as a set comprehension, it is way more consistent. For example the `CASE WHEN` example from the post would always return `{}` for empty input, making it obvious.

> but it's explicitly advertised as a feature of EdgeQL that it can return trees (json-like structures)

The tree-like return is not an aspect of a language system, it's a matter of output representation. For example:

    SELECT User { favorites: {name} }
still returns a flat set of User objects. The shape selector `{ ... }` is an annotation that defines how an object is serialized in the output.

> I would ultimately hope for something that can be expressed as a number of function calls and commonly used data structures

This is exactly where we are going. Orthogonality in the underlying language makes it much easier to achieve this.

> and is it as good as PosgreSQL, or MariaDB, or sqlite?

EdgeDB is based on Postgres, we actually transpile EdgeQL to SQL. EXPLAIN and query performance analysis are being worked on.

> in the real world I have to worry more about how performant and robust a thing is under load, than I can worry about programming convenience.

We have posted some benchmarks [1], and more are coming.

[1] https://edgedb.com/blog/edgedb-1-0-alpha-1/

RedCrowbar··on We Can Do Better Than SQL
Aggregate functions in EdgeDB have an "initial value", which, for `sum()` is defined as zero. Other aggregates, like `avg()` are not defined for empty sets (you cannot divide by 0), so an error is thrown in this case.
← PreviousPage 3 of 6Next →