-
Notifications
You must be signed in to change notification settings - Fork 245
[RFC 0002] MVP #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
bb422ae
a65274a
920a3e2
3316639
9994d54
b217cff
f4f36db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
--- | ||
feature: mvp | ||
start-date: 2020-01-28 | ||
author: kiwicopple | ||
co-authors: steve-chavez | ||
related-issues: (will contain links to implementation PRs) | ||
--- | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
We would like to create a basic CLI for Supabase. At this stage we can assume that the user won't need to create orgs/projects from the CLI, but they will need to: | ||
|
||
- develop locally | ||
- push database migrations to their Postgres database | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
- Make it easy to run Supabase locally | ||
- Make it easy to manage database migrations | ||
- Make it easy to do "database branching", where each git branch can have it's own data to prevent conflicts. This is especially important for working with platforms like Vercel/Netlify | ||
|
||
|
||
# Detailed design | ||
[design]: #detailed-design | ||
|
||
|
||
### Initializing | ||
|
||
```sh | ||
supa init # creates a .supabase folder with relevant config, connection strings etc. Also scaffolds out a database folder. | ||
supa start # Starts Supabase (by running docker-compose), and pulls up the database | ||
supa eject # See the .supabase/docker-compose.yml details below | ||
``` | ||
This will likely need: | ||
|
||
- `supabase.js` - a config file with the Supabase Project details | ||
- `.supabase/config.txt` - CLI config and details | ||
- `.supabase/docker-compose.yml` - for running Supabase locally. We may not even need this, as we can just run it directly from the `npm_modules` folder, which would prevent conflicts when we add upgrades or make changes. If we do generate a file, this should be stored in the `.supabase` folder so that it doesn't conflict with the user's docker-compose.yml file. When the user runs `supa eject`, it can move the file out to their root folder (similar to a `cra eject` model), and then the user can manage this themselves. | ||
|
||
|
||
For the database scaffold, [this](https://gitlab.com/delibrium/delibrium-postgrest/-/tree/master/database) format is very nice, although I think we can make it a bit clearer. Here is a suggested format: | ||
|
||
```sh | ||
├── _init.sql | ||
├── types.sql # Types: custom database types all in a single file | ||
│ # Data: database fixed/seed data. | ||
├── data | ||
│ ├── some_data.{sql|csv|json} | ||
│ # Models: Tables and Views | ||
├── models | ||
│ ├── public.table # Personal preference to explicitly add the schema, and we should prefer dot notation over nesting. | ||
│ │ ├── data.{sql|csv|json} # For convenience, table specific data can be stored here too. | ||
│ │ ├── model.sql # This is the core. We should also figure out a nice, readable format for SQL files. Should include constraints and indexes and table/column comments. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did a small mock of this to figure out how to structure the file. Some initial thoughts here: |
||
│ │ ├── permissions.sql # Grants and Policies. | ||
│ │ ├── test.sql # PGTAP tests. For MVP: generate some simple pgtap has_column tests by default. https://pgtap.org/documentation.html#has_column | ||
│ │ └── triggers.sql # table triggers | ||
│ # Functions/RPC: SQL only for MVP | ||
├── functions | ||
│ ├── public.func1.sql | ||
│ ├── public.func2.sql│ | ||
│ # Postgres roles and grants. | ||
└── roles | ||
├── role_name.sql | ||
└── role_name.sql | ||
``` | ||
|
||
|
||
### Branching | ||
|
||
This is going to be a key part of Supabase development. Since so many of our users are on Vercel/Netlify, we should have the ability to support [branch/preview-development](https://vercel.com/docs/platform/deployments). | ||
|
||
We can do this by connecting to a different database, an idea that I got from [pgsh](https://github.com/sastraxi/pgsh). For example: | ||
|
||
- `postgresql://postgres@localhost:5432/master` | ||
- `postgresql://postgres@localhost:5432/develop` | ||
- `postgresql://postgres@localhost:5432/chore_add_types` | ||
Comment on lines
+76
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On these different envs, would the users also get different realtime/postgrest instances? For preview workflows, the users would like to interact with the full stack right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This completely slipped my mind. Yes they are going to need it. Hmm, how will we keep the costs down? Perhaps it will have to be "local only" for now? It should be manage-able with the docker files There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
A temporary postgrest with
Yes, local-only it's a good start. Other envs can be done in a later RFC - they need more thought. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can also take advantage of systemd socket activation, which essentially lets a service sleep until a request comes. That coupled with a systemd timer to stop the service after some idle time(so question), would make resource usage minimum. |
||
|
||
This is the same from CI (like github actions), except that instead of running against a local database, it is running against the supabase database. We will need a way inside our config to match branch names to database names too. For example | ||
|
||
|
||
### Dump | ||
|
||
```sh | ||
supa db dump # dumps the currently active (local) database to disk | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I used I'm not entirely sure if it's the right approach. On the one hand, we have full control to manage the layout/structure of the dump. On the other hand, we will need to cover all the nuances of Postgres. Perhaps it's better rely on Option 1:
or Option 2:
I'm leaning towards option 2, as it also give the develop better control. For example they might want to dump just their "functions". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
AFAICT, So if we go the above way, we'd need a combination of 1 and 2 Maybe there's another way. If you look at the Schema Diff output image in #2 (comment), you'll notice that it also gives the source DDL. And all the database objects are neatly separated. The Flask endpoint of the pgadmin schema diff process gives this big json as its response. The diff related to the above image is located here. Notice that the ddl output also has COMMENTs included and it's pretty printed(has newlines). Checking the big json, all the different types of db objects(event triggers, tables, functions, etc) are there. If we could (ab)use that output, we'd have most of what we need already organized. |
||
supa db restore # restores the (local) database from disk | ||
supa migrate # generate the migrations file | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm still not sure how we will handle going from a declarative structure, into a set of migration scripts. There are tools like migra which work on a schema comparison - this might be the way to do it as long as the limitations aren't too restrictive. eg: "custom types/domains Basic support (drop-and-create only, no alter)". ref There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Ideally the developer should not look at migrations scripts and instead it should focus on declarative* SQL. So perhaps we could keep the migrations off git?(perhaps on a table)
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. metagration also has an interesting approach. It allows generating up/down migration scripts with stored procedures. It also has its own table for tracking applied migrations. Maybe we can use metagration plus migra for providing a template migration that can be edited. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. metagration also creates a restore point for down migrations, so it's possible to recover dropped columns for example(no need for a restore from a backup). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK cool - metagration looks interersting. I think it's the declarative structure -> up/down scripts that's the tricky part here though. The tools I found:
Or we can of course code this directly into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately they're not open to provide a CLI mode: https://redmine.postgresql.org/issues/6304 (you'll need to sign in to see this). They mention that it would take a lot of work. I still think the pgadmin diff is the best choice. It's already the most complete and has dedicated support. It would provide the best DX. I've already managed to run it from source. It's a Flask application plus a Backbone.js frontend(seems they're migrating to React). The functionality for the schema diff was started here: pgadmin-org/pgadmin4@45f2e35. That gives me an idea of the modules involved for making it work. How about if I try isolating that python code from the GUI and create a package of our own? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Succeeded doing that. Code here: https://gist.github.com/steve-chavez/8c6825abee788394cb5424380c06487c. Now I would need to wrap the code in a CLI. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice job Steve - that's an awesome effort.
Yeah, it would be very cool if we can use it here somehow. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have the CLI ready on this branch: https://github.com/steve-chavez/pgadmin4/blob/cli/web/cli.py. I'm now wrapping it on a docker container so it can be tried without building the whole pgadmin4 package. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Docker container here: https://hub.docker.com/r/supabase/pgadmin-schema-diff/ Usage: docker run supabase/pgadmin-schema-diff \
'postgres://postgres@host:5432/diff_source' \
'postgres://postgres@host:5432/diff_target' \
> diff_db.sql |
||
supa up # runs the "up" migrations on Prod | ||
supa down # runs the "down" migrations on Prod | ||
``` | ||
|
||
The `supa migrate` task is the trickiest part here. See [#migration-questions](#migration-questions) below. | ||
|
||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
- Supabase is about "time to value", and making things as simple/easy as possible. A CLI can be complicated, so we should make sure that the experience is as amazing as our hosted platform | ||
|
||
# Alternatives | ||
[alternatives]: #alternatives | ||
|
||
The hosted platform provides much of this functionality. We could continue to build this functionality into the platform however it will be a great dev experience if they can develop locally and push their changes later. | ||
|
||
# Unresolved questions | ||
[unresolved]: #unresolved-questions | ||
|
||
|
||
### Migration questions | ||
[migration-questions]: #migration-questions | ||
|
||
The `supa migrate` task is the trickiest part. How do we determine what migrations should run on our database, since the database is created declaratively? We would need to compare the databases somehow, and figure out the `up/down` migrations. Alternatively we could do "stateless" migrations, where we just run `restore` on a brand new database, then re-route traffic, but then how do we get the user's prod data into the new database. Bear in mind that enterprise users might have terrabytes of data, and so copying it across will be slow. | ||
|
||
|
||
### Other questions | ||
- what is the prefix? should it be `supabase` (clearer) or `supa` (might have a few people calling it "supper/suppa") | ||
- Which CLI tool/framework/libraries should we use? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we use golang (static binaries, easy to distribute), https://github.com/spf13/cobra is used by most (all?) of the nicer modern CLIs (e.g. kubectl, I think also flyctl under the wraps) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks great, although I suspect we will need to use Unfortunately it creates a dependency on NodeJS There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://github.com/vadimdemedes/ink looks good, apparently Prisma used to use it but I couldn't figure out why they migrated away from it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @soedirgo It had issues on Windows for us (Prisma) and we simplified things a lot so it was not needed anymore. |
||
- `supa start` requires docker-compose. Is it reasonable to expect that everyone will have docker set up on their computer? Can we make this easier? | ||
- How much functionality should we push to postgres-meta? | ||
|
||
|
||
|
||
# Future work | ||
[future]: #future-work | ||
|
||
|
||
- Functions: Add support for `plv8` and other languages | ||
|
||
### Supabase platform | ||
|
||
```sh | ||
supa deploy # Deploy a local app to prod. On the first time this is run, it should generate a `./supabase.js` with relevant Supabase config. | ||
supa orgs # Manage orgs | ||
supa project # Manage projects | ||
supa logs # Fetch logs | ||
supa scale # Scale the current database | ||
...etc | ||
``` | ||
|
||
|
||
### Generators (bonus) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a fantastic idea that I think will help a lot of people! For some inspiration you can take a look at the fantastic GraphQL Code Generator project! |
||
|
||
Since I have been using typescript in one of our repos, I have discovered a lot of limitations around our [Typescript Generators](https://supabase.io/docs/client/generating-types). This is because Open API spec doesn't have all the tables (if they aren't exposed to `postgres` role), and sometimes it expects some inputs where the database already has defaults (for example our OpenAPI for `created_at` expects a string, when it is actually auto-generated). | ||
|
||
I propose here a few ideas for generators. While they are exposed through the CLI, I think we should actually build these into [pg-api](https://github.com/supabase/pg-api), so that anyone can use them without the supabase CLI. (I also think this should be the case with the "dump" functionality). | ||
|
||
```sh | ||
supa gen json api # Dumps the JSON Schema for the API | ||
supa gen json db # generate AJV schemas for input validation | ||
|
||
supa gen typescript api # generate typescript interfaces for api | ||
supa gen typescript db # generate typescript interfaces for database. Note that these types should have an interface for each table with "insert types" and "select types". These are actually different because the database can generate default values, so some fields aren't "required" | ||
|
||
|
||
# Models: | ||
# This is my suggestion for a "local first" database which has all of the rules/relationships we need to build a a realtime user store with automatic synchronization via our Realtime engine. | ||
# A bit like https://github.com/mobxjs/mst-gql (but hopefully much simpler) | ||
supa gen typescript store | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @joshnuss 's ActiveRecord library could be a good starting place for JS: |
||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding branching, this would allow us to just rely on different hardcoded master data for test/staging/preview environments right?
It does seem easier compared to something like snapshotting the db with ZFS. Much more lighter on resources as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah exactly - this would give "reproducible builds". It's not as powerful as ZFS cloning, but it is less "magic"
ZFS cloning might solve a different use-case in the future for managing terrabyte-scale databases