Skip to content

Commit dc2da49

Browse files
committed
feat(gb-8838): postgres extension
1 parent 4e151aa commit dc2da49

153 files changed

Lines changed: 23249 additions & 151 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 1151 additions & 115 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[workspace]
22
resolver = "2"
3-
members = ["extensions/*", "publish-extensions", "test-matrix"]
3+
members = ["crates/*", "extensions/*", "cli/*", "publish-extensions", "test-matrix"]
44

55
[workspace.package]
66
edition = "2024"
@@ -52,16 +52,24 @@ unused-self = "allow"
5252
wildcard-imports = "allow"
5353

5454
[workspace.dependencies]
55+
Inflector = "0.11.4"
56+
anyhow = "1.0.98"
5557
async-nats = "0.40"
5658
base64 = "0.22.1"
5759
chrono = "0.4.40"
60+
clap = "4.5.36"
5861
duration-str = "0.16.1"
5962
futures = "0.3"
60-
grafbase-sdk = "0.12.0"
63+
grafbase-database-definition = { version = "0.1.0", path = "crates/database-definition" }
64+
grafbase-postgres-introspection = { version = "0.1.0", path = "crates/postgres-introspection" }
65+
grafbase-sdk = "0.14.0"
6166
http = "1.3"
67+
indexmap = "2.9.0"
6268
indoc = "2.0.6"
6369
insta = { version = "1.42.2", features = ["json"] }
70+
itertools = "0.14.0"
6471
jwt-compact = "0.8.0"
72+
names = "0.14.0"
6573
openidconnect = "4.0.0"
6674
ory-client = "=1.9.0"
6775
pem = "3.0.5"
@@ -72,6 +80,8 @@ serde = "1.0.219"
7280
serde_json = "1"
7381
serde_with = "3.12.0"
7482
sha2 = "0.10.8"
83+
sql-ast = { version = "0.1.0", path = "crates/sql-ast" }
84+
sqlx = { version = "0.8.4", default-features = false }
7585
strum = { version = "0.27.1", features = ["derive"] }
7686
tokio = { version = "1", features = ["rt-multi-thread", "macros", "test-util"] }
7787
toml = "0.8"

cli/postgres/Cargo.toml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
[package]
2+
name = "postgres-cli"
3+
version = "0.1.0"
4+
edition = "2024"
5+
license = "Apache-2.0"
6+
7+
[[bin]]
8+
name = "grafbase-postgres"
9+
path = "src/main.rs"
10+
11+
[dependencies]
12+
Inflector.workspace = true
13+
chrono.workspace = true
14+
clap = { workspace = true, features = ["derive", "env"] }
15+
indexmap = { workspace = true, features = ["serde"] }
16+
itertools.workspace = true
17+
serde = { workspace = true, features = ["derive"] }
18+
serde_json.workspace = true
19+
anyhow.workspace = true
20+
indoc.workspace = true
21+
tokio = { workspace = true, features = ["rt", "macros"] }
22+
grafbase-database-definition.workspace = true
23+
grafbase-postgres-introspection.workspace = true
24+
sqlx = { workspace = true, features = [
25+
"sqlx-postgres",
26+
"json",
27+
"runtime-tokio",
28+
"tls-rustls-aws-lc-rs",
29+
"tls-rustls-ring-native-roots",
30+
"postgres",
31+
] }
32+
semver = { version = "1.0.26", features = ["serde"] }
33+
url = { version = "2.5.4", features = ["serde"] }

cli/postgres/src/args.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use std::path::PathBuf;
2+
3+
use clap::{ArgGroup, Parser, Subcommand};
4+
use semver::Version;
5+
use url::Url;
6+
7+
#[derive(Parser, Debug)]
8+
#[command(name = "grafbase-postgres")]
9+
#[command(about = "Grafbase Postgres Extension")]
10+
pub struct Args {
11+
/// Connection string to the database
12+
#[arg(short, long, env = "DATABASE_URL")]
13+
pub database_url: String,
14+
15+
#[command(subcommand)]
16+
pub command: Commands,
17+
}
18+
19+
#[derive(Debug, Subcommand)]
20+
pub enum Commands {
21+
/// Introspect a PostgreSQL database
22+
#[command(name = "introspect")]
23+
Introspect(IntrospectCommand),
24+
}
25+
26+
#[derive(Parser, Debug)]
27+
#[command(group(
28+
ArgGroup::new("extension_identifier")
29+
.required(true)
30+
.args(["extension_url", "extension_version"]),
31+
))]
32+
pub struct IntrospectCommand {
33+
/// Output file path. If not provided, the SDL will be printed to stdout.
34+
#[arg(short, long)]
35+
pub output_file: Option<PathBuf>,
36+
/// The name of the database to be used in the GraphQL SDL
37+
#[arg(short, long, default_value = "default")]
38+
pub database_name: String,
39+
/// Default schema to be used in the GraphQL SDL (will be omitted from definitions)
40+
#[arg(short = 's', long, default_value = "public")]
41+
pub default_schema: String,
42+
/// URL to the extension
43+
#[arg(long, short = 'u')]
44+
pub extension_url: Option<Url>,
45+
/// Extension version following semver
46+
#[arg(long, short = 'v')]
47+
pub extension_version: Option<Version>,
48+
}
49+
50+
impl IntrospectCommand {
51+
pub fn extension_url(&self) -> String {
52+
match self.extension_version.as_ref() {
53+
Some(version) => format!("https://grafbase.com/extensions/postgres/{version}"),
54+
None => self.extension_url.as_ref().unwrap().to_string(),
55+
}
56+
}
57+
}
58+
59+
pub fn parse() -> Args {
60+
Args::parse()
61+
}

cli/postgres/src/main.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use args::IntrospectCommand;
2+
use grafbase_postgres_introspection::IntrospectionOptions;
3+
use sqlx::{Connection, PgConnection};
4+
5+
mod args;
6+
7+
#[tokio::main(flavor = "current_thread")]
8+
async fn main() -> anyhow::Result<()> {
9+
let args = args::parse();
10+
11+
let mut conn = PgConnection::connect(&args.database_url).await?;
12+
13+
match args.command {
14+
args::Commands::Introspect(introspect_cmd) => {
15+
introspect(&mut conn, introspect_cmd).await?;
16+
}
17+
}
18+
19+
Ok(())
20+
}
21+
22+
async fn introspect(conn: &mut PgConnection, cmd: IntrospectCommand) -> anyhow::Result<()> {
23+
let opts = IntrospectionOptions {
24+
database_name: &cmd.database_name,
25+
extension_url: &cmd.extension_url(),
26+
default_schema: &cmd.default_schema,
27+
};
28+
29+
let sdl = grafbase_postgres_introspection::introspect(conn, opts).await?;
30+
31+
match cmd.output_file {
32+
Some(path) => std::fs::write(path, sdl)?,
33+
None => println!("{sdl}"),
34+
}
35+
36+
Ok(())
37+
}

compose.yaml

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ services:
77
hydra:
88
image: oryd/hydra:v2.2.0
99
ports:
10-
- '4444:4444' # Public port
11-
- '4445:4445' # Admin port
10+
- "4444:4444" # Public port
11+
- "4445:4445" # Admin port
1212
command: serve -c /etc/config/hydra/hydra.yml all --dev
1313
volumes:
1414
- hydra-sqlite:/var/lib/sqlite:Z
1515
- ./docker/hydra-config:/etc/config/hydra:Z
1616
environment:
17-
DSN: 'sqlite:///var/lib/sqlite/db.sqlite?_fk=true'
18-
URLS_SELF_ISSUER: 'http://127.0.0.1:4444'
17+
DSN: "sqlite:///var/lib/sqlite/db.sqlite?_fk=true"
18+
URLS_SELF_ISSUER: "http://127.0.0.1:4444"
1919
restart: unless-stopped
2020
depends_on:
2121
- hydra-migrate
@@ -25,7 +25,7 @@ services:
2525
hydra-migrate:
2626
image: oryd/hydra:v2.2.0
2727
environment:
28-
DSN: 'sqlite:///var/lib/sqlite/db.sqlite?_fk=true'
28+
DSN: "sqlite:///var/lib/sqlite/db.sqlite?_fk=true"
2929
command: migrate -c /etc/config/hydra/hydra.yml sql -e --yes
3030
volumes:
3131
- hydra-sqlite:/var/lib/sqlite:Z
@@ -38,17 +38,17 @@ services:
3838
hydra-2:
3939
image: oryd/hydra:v2.2.0
4040
ports:
41-
- '4454:4454' # Public port
42-
- '4455:4455' # Admin port
41+
- "4454:4454" # Public port
42+
- "4455:4455" # Admin port
4343
command: serve -c /etc/config/hydra/hydra.yml all --dev
4444
volumes:
4545
- hydra-2-sqlite:/var/lib/sqlite:Z
4646
- ./docker/hydra-config:/etc/config/hydra:Z
4747
environment:
48-
DSN: 'sqlite:///var/lib/sqlite/db.sqlite?_fk=true'
49-
URLS_SELF_ISSUER: 'http://127.0.0.1:4454'
50-
SERVE_PUBLIC_PORT: '4454'
51-
SERVE_ADMIN_PORT: '4455'
48+
DSN: "sqlite:///var/lib/sqlite/db.sqlite?_fk=true"
49+
URLS_SELF_ISSUER: "http://127.0.0.1:4454"
50+
SERVE_PUBLIC_PORT: "4454"
51+
SERVE_ADMIN_PORT: "4455"
5252
restart: unless-stopped
5353
depends_on:
5454
- hydra-migrate
@@ -58,7 +58,7 @@ services:
5858
hydra-2-migrate:
5959
image: oryd/hydra:v2.2.0
6060
environment:
61-
DSN: 'sqlite:///var/lib/sqlite/db.sqlite?_fk=true'
61+
DSN: "sqlite:///var/lib/sqlite/db.sqlite?_fk=true"
6262
command: migrate -c /etc/config/hydra/hydra.yml sql -e --yes
6363
volumes:
6464
- hydra-2-sqlite:/var/lib/sqlite:Z
@@ -70,20 +70,32 @@ services:
7070
nats:
7171
image: nats
7272
ports:
73-
- '4222:4222'
74-
- '8222:8222'
73+
- "4222:4222"
74+
- "8222:8222"
7575
command: >
7676
--jetstream
7777
--http_port=8222
7878
--user=grafbase
7979
--pass=grafbase
80-
networks: ['nats']
80+
networks: ["nats"]
81+
82+
postgres:
83+
image: postgres:17
84+
environment:
85+
POSTGRES_USER: postgres
86+
POSTGRES_PASSWORD: grafbase
87+
ports:
88+
- "5432:5432"
89+
networks: ["postgres"]
90+
restart: unless-stopped
8191

8292
networks:
8393
hydra:
8494
hydra-2:
8595
nats:
96+
postgres:
8697

8798
volumes:
8899
hydra-sqlite:
89100
hydra-2-sqlite:
101+
postgres-data:
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "grafbase-database-definition"
3+
version = "0.1.0"
4+
edition = "2024"
5+
license = "Apache-2.0"
6+
7+
[dependencies]
8+
Inflector.workspace = true
9+
grafbase-sdk.workspace = true
10+
serde.workspace = true
11+
itertools.workspace = true
12+
indexmap.workspace = true
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use inflector::Inflector;
2+
3+
use super::{SchemaId, names::StringId};
4+
5+
#[derive(Debug, Clone)]
6+
pub struct Enum<T> {
7+
pub(super) schema_id: SchemaId,
8+
pub(super) database_name: T,
9+
pub(super) client_name: T,
10+
}
11+
12+
impl<T> Enum<T> {
13+
pub(crate) fn schema_id(&self) -> SchemaId {
14+
self.schema_id
15+
}
16+
17+
pub(crate) fn set_client_name(&mut self, client_name: T) {
18+
self.client_name = client_name;
19+
}
20+
}
21+
22+
impl Enum<String> {
23+
pub fn new(schema_id: SchemaId, database_name: String, client_name: Option<String>) -> Self {
24+
let client_name = client_name.unwrap_or_else(|| database_name.to_pascal_case());
25+
26+
Self {
27+
schema_id,
28+
database_name,
29+
client_name,
30+
}
31+
}
32+
33+
pub fn database_name(&self) -> &str {
34+
&self.database_name
35+
}
36+
37+
pub fn client_name(&self) -> &str {
38+
&self.client_name
39+
}
40+
}
41+
42+
impl Enum<StringId> {
43+
pub fn database_name(&self) -> StringId {
44+
self.database_name
45+
}
46+
47+
pub fn client_name(&self) -> StringId {
48+
self.client_name
49+
}
50+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use inflector::Inflector;
2+
3+
use super::{EnumId, names::StringId};
4+
5+
#[derive(Debug, Clone)]
6+
pub struct EnumVariant<T> {
7+
pub(super) enum_id: EnumId,
8+
pub(super) database_name: T,
9+
pub(super) client_name: T,
10+
}
11+
12+
impl<T> EnumVariant<T> {
13+
pub(crate) fn enum_id(&self) -> EnumId {
14+
self.enum_id
15+
}
16+
}
17+
18+
impl EnumVariant<String> {
19+
pub fn new(enum_id: EnumId, database_name: String, client_name: Option<String>) -> Self {
20+
let client_name = client_name.unwrap_or_else(|| database_name.to_screaming_snake_case());
21+
22+
Self {
23+
enum_id,
24+
database_name,
25+
client_name,
26+
}
27+
}
28+
29+
pub(crate) fn database_name(&self) -> &str {
30+
&self.database_name
31+
}
32+
33+
pub(crate) fn client_name(&self) -> &str {
34+
&self.client_name
35+
}
36+
}
37+
38+
impl EnumVariant<StringId> {
39+
pub(crate) fn database_name(&self) -> StringId {
40+
self.database_name
41+
}
42+
43+
pub(crate) fn client_name(&self) -> StringId {
44+
self.client_name
45+
}
46+
}

0 commit comments

Comments
 (0)