Skip to content

Commit 65c366f

Browse files
committed
feat: add Amazon Aurora DSQL storage backend
Add Aurora DSQL as a supported storage backend for OpenFGA. DSQL is a PostgreSQL-compatible serverless database, so this implementation reuses the existing Postgres storage layer with DSQL-specific adaptations: - DSQL connection management via aurora-dsql-connectors/go (v0.2.0) with automatic IAM token refresh - Dedicated migration files (assets/migrations/dsql/) that avoid partial indexes and use ASYNC index creation per DSQL requirements - DSQL-specific SQL overrides in sqlcommon and postgres packages for queries that need adaptation (e.g. ReadPage ordering) - New --datastore-engine=dsql flag and configuration path - Integration test fixtures for DSQL The storage interface is unchanged; DSQL plugs in as a new engine option alongside postgres, mysql, and sqlite.
1 parent 54d090b commit 65c366f

24 files changed

Lines changed: 782 additions & 30 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
Try to keep listed changes to a concise bulleted list of simple explanations of changes. Aim for the amount of information needed so that readers can understand where they would look in the codebase to investigate the changes' implementation, or where they would look in the documentation to understand how to make use of the change in practice - better yet, link directly to the docs and provide detailed information there. Only elaborate if doing so is required to avoid breaking changes or experimental features from ruining someone's day.
88

99
## [Unreleased]
10+
### Added
11+
- Add Amazon Aurora DSQL as a supported storage backend.
12+
1013
### Changed
1114
- The ListObjects "pipeline" algorithm ditches its custom Pipe implementation and replaces it with Go native channels. [#2977](https://github.com/openfga/openfga/pull/2977)
1215
- Refactor tuple validation and manipulation functions for optimal performance. [#2984](https://github.com/openfga/openfga/pull/2984)

assets/assets.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const (
77
MySQLMigrationDir = "migrations/mysql"
88
PostgresMigrationDir = "migrations/postgres"
99
SqliteMigrationDir = "migrations/sqlite"
10+
DSQLMigrationDir = "migrations/dsql"
1011
)
1112

1213
// EmbedMigrations within the openfga binary.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
-- +goose Up
2+
-- +goose NO TRANSACTION
3+
-- DSQL schema: uses full indexes (no partial), ASYNC index creation, C collation by default
4+
5+
CREATE TABLE tuple (
6+
store TEXT NOT NULL,
7+
object_type TEXT NOT NULL,
8+
object_id TEXT NOT NULL,
9+
relation TEXT NOT NULL,
10+
_user TEXT NOT NULL,
11+
user_type TEXT NOT NULL,
12+
ulid TEXT NOT NULL,
13+
inserted_at TIMESTAMPTZ NOT NULL,
14+
PRIMARY KEY (store, object_type, object_id, relation, _user)
15+
);
16+
17+
CREATE INDEX ASYNC idx_tuple_user ON tuple (store, object_type, object_id, relation, _user, user_type);
18+
CREATE UNIQUE INDEX ASYNC idx_tuple_ulid ON tuple (ulid);
19+
20+
CREATE TABLE authorization_model (
21+
store TEXT NOT NULL,
22+
authorization_model_id TEXT NOT NULL,
23+
type TEXT NOT NULL,
24+
type_definition BYTEA,
25+
PRIMARY KEY (store, authorization_model_id, type)
26+
);
27+
28+
CREATE TABLE store (
29+
id TEXT PRIMARY KEY,
30+
name TEXT NOT NULL,
31+
created_at TIMESTAMPTZ NOT NULL,
32+
updated_at TIMESTAMPTZ,
33+
deleted_at TIMESTAMPTZ
34+
);
35+
36+
CREATE TABLE assertion (
37+
store TEXT NOT NULL,
38+
authorization_model_id TEXT NOT NULL,
39+
assertions BYTEA,
40+
PRIMARY KEY (store, authorization_model_id)
41+
);
42+
43+
CREATE TABLE changelog (
44+
store TEXT NOT NULL,
45+
object_type TEXT NOT NULL,
46+
object_id TEXT NOT NULL,
47+
relation TEXT NOT NULL,
48+
_user TEXT NOT NULL,
49+
operation INTEGER NOT NULL,
50+
ulid TEXT NOT NULL,
51+
inserted_at TIMESTAMPTZ NOT NULL,
52+
PRIMARY KEY (store, ulid, object_type)
53+
);
54+
55+
-- +goose Down
56+
DROP TABLE IF EXISTS tuple;
57+
DROP TABLE IF EXISTS authorization_model;
58+
DROP TABLE IF EXISTS store;
59+
DROP TABLE IF EXISTS assertion;
60+
DROP TABLE IF EXISTS changelog;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-- +goose Up
2+
-- +goose NO TRANSACTION
3+
-- DSQL: ADD COLUMN without DEFAULT, then UPDATE existing rows
4+
ALTER TABLE authorization_model ADD COLUMN schema_version TEXT;
5+
UPDATE authorization_model SET schema_version = '1.0' WHERE schema_version IS NULL;
6+
7+
-- +goose Down
8+
-- +goose NO TRANSACTION
9+
ALTER TABLE authorization_model DROP COLUMN schema_version;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- +goose Up
2+
-- +goose NO TRANSACTION
3+
CREATE INDEX ASYNC idx_reverse_lookup_user ON tuple (store, object_type, relation, _user);
4+
5+
-- +goose Down
6+
-- +goose NO TRANSACTION
7+
DROP INDEX IF EXISTS idx_reverse_lookup_user;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- +goose Up
2+
-- +goose NO TRANSACTION
3+
ALTER TABLE authorization_model ADD COLUMN serialized_protobuf BYTEA;
4+
5+
-- +goose Down
6+
-- +goose NO TRANSACTION
7+
ALTER TABLE authorization_model DROP COLUMN serialized_protobuf;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-- +goose Up
2+
-- +goose NO TRANSACTION
3+
-- DSQL: separate ALTER statements (one DDL per transaction)
4+
ALTER TABLE tuple ADD COLUMN condition_name TEXT;
5+
ALTER TABLE tuple ADD COLUMN condition_context BYTEA;
6+
ALTER TABLE changelog ADD COLUMN condition_name TEXT;
7+
ALTER TABLE changelog ADD COLUMN condition_context BYTEA;
8+
9+
-- +goose Down
10+
-- +goose NO TRANSACTION
11+
ALTER TABLE tuple DROP COLUMN condition_name;
12+
ALTER TABLE tuple DROP COLUMN condition_context;
13+
ALTER TABLE changelog DROP COLUMN condition_name;
14+
ALTER TABLE changelog DROP COLUMN condition_context;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-- +goose Up
2+
-- +goose NO TRANSACTION
3+
-- DSQL: uses C collation by default, ASYNC instead of CONCURRENTLY
4+
CREATE INDEX ASYNC idx_user_lookup ON tuple (
5+
store,
6+
_user,
7+
relation,
8+
object_type,
9+
object_id
10+
);
11+
12+
DROP INDEX IF EXISTS idx_reverse_lookup_user;
13+
14+
-- +goose Down
15+
-- +goose NO TRANSACTION
16+
DROP INDEX IF EXISTS idx_user_lookup;
17+
CREATE INDEX ASYNC idx_reverse_lookup_user ON tuple (store, object_type, relation, _user);

assets/migrations/dsql/README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# DSQL Migrations for OpenFGA
2+
3+
These migrations are adapted from the PostgreSQL migrations for Aurora DSQL compatibility.
4+
5+
## Key Differences from PostgreSQL Migrations
6+
7+
1. **ASYNC Indexes**: All `CREATE INDEX` statements use `CREATE INDEX ASYNC` (DSQL requirement)
8+
2. **NO TRANSACTION**: All migrations use `-- +goose NO TRANSACTION` as DSQL runs one DDL statement per transaction
9+
3. **BIGINT IDENTITY**: The goose version table uses `BIGINT GENERATED BY DEFAULT AS IDENTITY`, as required by DSQL
10+
4. **No Partial Indexes**: DSQL doesn't support partial indexes, so full indexes are used
11+
12+
## Migration Files
13+
14+
| File | Description |
15+
|------|-------------|
16+
| 001_initialize_schema.sql | Creates core tables (tuple, authorization_model, store, assertion, changelog) |
17+
| 002_add_authorization_model_version.sql | Adds schema_version column |
18+
| 003_add_reverse_lookup_index.sql | Adds reverse lookup index |
19+
| 004_add_authorization_model_serialized_protobuf.sql | Adds serialized_protobuf column |
20+
| 005_add_conditions_to_tuples.sql | Adds condition columns to tuple and changelog |
21+
| 006_add_collate_index.sql | Adds user lookup index with C collation |
22+
23+
## Future Consideration: Splitting Migrations
24+
25+
Per DSQL best practices, each migration should ideally contain only one DDL statement. Currently, migrations 002 and 005 contain multiple statements:
26+
27+
- **002**: ALTER TABLE + UPDATE (mixed DDL and DML)
28+
- **005**: Four ALTER TABLE statements
29+
30+
While these work correctly with `-- +goose NO TRANSACTION` (goose executes each statement separately), consider splitting them into individual migration files if you encounter OCC errors during migration. For example:
31+
32+
```
33+
005a_add_condition_name_to_tuple.sql
34+
005b_add_condition_context_to_tuple.sql
35+
005c_add_condition_name_to_changelog.sql
36+
005d_add_condition_context_to_changelog.sql
37+
```
38+
39+
See [DSQL Development Guide](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/) for more information on DDL best practices.

cmd/run/run.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,13 @@ func (s *ServerContext) datastoreConfig(config *serverconfig.Config) (storage.Op
508508
if err != nil {
509509
return nil, nil, fmt.Errorf("initialize sqlite datastore: %w", err)
510510
}
511+
case "dsql":
512+
// Aurora DSQL uses PostgreSQL wire protocol with IAM authentication
513+
// The postgres.New function handles dsql:// URIs automatically
514+
datastore, err = postgres.New(config.Datastore.URI, dsCfg)
515+
if err != nil {
516+
return nil, nil, fmt.Errorf("initialize dsql datastore: %w", err)
517+
}
511518
default:
512519
return nil, nil, fmt.Errorf("storage engine '%s' is unsupported", config.Datastore.Engine)
513520
}

0 commit comments

Comments
 (0)