Skip to content

Commit

Permalink
Merge pull request #82 from dolthub/taylor/postgres
Browse files Browse the repository at this point in the history
Postgres support for web
  • Loading branch information
tbantle22 authored Dec 19, 2023
2 parents 4aec1cb + 3b9c066 commit 1a92517
Show file tree
Hide file tree
Showing 141 changed files with 4,044 additions and 1,924 deletions.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# dolt-workbench

A modern, browser-based, open source SQL workbench for your MySQL-compatible database.
Use [Dolt](https://doltdb.com) to unlock powerful version control features.
A modern, browser-based, open source SQL workbench for your MySQL and PostgreSQL
compatible databases. Use [Dolt](https://doltdb.com) to unlock powerful version control
features.

Get started on [Docker Hub](https://hub.docker.com/r/dolthub/dolt-workbench).

## Features

Whether you decide to connect this workbench to a MySQL database or Dolt database, the
Whether you decide to connect this workbench to a MySQL, Dolt, or PostgreSQL database, the
Dolt Workbench has many features that make it the most modern and user-friendly web-based
workbench on the market.

Expand All @@ -17,7 +18,7 @@ Why is your SQL workbench stuck in 2003? The Dolt Workbench brings a modern brow
workbench features you know and love. It makes browsing table data and schemas more
intuitive and looks good doing it.

<img width="1357" alt="Modern, web based table-browser" src="https://github.com/dolthub/dolt-workbench/assets/29443194/8ee94e74-f185-4808-90ee-2551c4636749">
<img width="1357" alt="Modern, web based table-browser" src="https://www.dolthub.com/blog/static/3f1358cd506d7b8ed383ea0751b67446/4f2ef/table-browser.png">

### Auto-generate SQL queries

Expand Down Expand Up @@ -100,8 +101,8 @@ the [Docker image](https://hub.docker.com/r/dolthub/dolt-workbench).
% docker run -p 9002:9002 -p 3000:3000 dolthub/dolt-workbench:latest
```

Navigate to http://localhost:3000 to enter your database information. See instructions on
[Docker Hub](https://hub.docker.com/r/dolthub/dolt-workbench) for connecting to local and
Navigate to http://localhost:3000 to enter your database information. See instructions on
[Docker Hub](https://hub.docker.com/r/dolthub/dolt-workbench) for connecting to local and
Docker installed databases.

### Saving connection information between runs
Expand Down
18 changes: 14 additions & 4 deletions docker/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# dolt-workbench

A modern, browser-based, open source SQL workbench for your MySQL-compatible database. Unlock powerful version control features when you use [Dolt](https://doltdb.com).
A modern, browser-based, open source SQL workbench for your MySQL and PostgreSQL compatible databases. Unlock powerful version control features when you use [Dolt](https://doltdb.com).

<img src="https://www.dolthub.com/blog/static/3f1358cd506d7b8ed383ea0751b67446/4f2ef/table-browser.png" width="850px" alt="Dolt Workbench" />

Expand Down Expand Up @@ -47,11 +47,11 @@ If your database is already internet accessible (i.e. your database is hosted on

## Connecting to a locally installed database

If you'd like to connect to a MySQL-compatible server running on your local machine, there are some additional steps to accept connections from other hosts. Docker containers cannot use `localhost` or `127.0.0.1` to access services running on the host machine because they have their own local network. Instead, you can use `host.docker.internal` as the host IP which Docker resolves to the internal IP address used by the host machine.
If you'd like to connect to a database server running on your local machine, there are some additional steps to accept connections from other hosts. Docker containers cannot use `localhost` or `127.0.0.1` to access services running on the host machine because they have their own local network. Instead, you can use `host.docker.internal` as the host IP which Docker resolves to the internal IP address used by the host machine.

### MySQL

Set the `bind-address` directive in the MySQL configuration file (`my.cnf` or `my.ini`) to `0.0.0.0`.
Set the `bind-address` directive in the MySQL configuration file (`my.cnf` or `my.ini`) to `0.0.0.0`. Restart the server.

```
bind-address = 0.0.0.0
Expand All @@ -71,9 +71,19 @@ my-dolt-db % dolt sql-server -H 0.0.0.0

Use `host.docker.internal` as the host when entering your connection information from the UI.

### PostgreSQL

Locate your PostgreSQL configuration file (`postgresql.conf`) and change the `listen_addresses` directive from `localhost` to `*`. Restart the server.

```
listen_addresses = '*'
```

Use `host.docker.internal` as the host when entering your connection information from the UI.

## Connecting to a Docker installed database

You can use Docker container networking to allow containers to connect to and communicate with each other, or to non-Docker workloads. This allows us to run a local MySQL server in the same network as the workbench.
You can use Docker container networking to allow containers to connect to and communicate with each other, or to non-Docker workloads. This allows us to run a local server in the same network as the workbench.

First, create the network:

Expand Down
1 change: 1 addition & 0 deletions graphql-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"graphql-upload": "13",
"mysql2": "^3.6.5",
"pg": "^8.11.3",
"pg-copy-streams": "^6.0.6",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.5.4",
"timeago.js": "^4.0.2",
Expand Down
10 changes: 9 additions & 1 deletion graphql-server/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ type DatabaseConnection {
hideDoltFeatures: Boolean
useSSL: Boolean
type: DatabaseType
schema: String
}

enum DatabaseType {
Expand All @@ -113,6 +114,11 @@ type DoltDatabaseDetails {
type: DatabaseType!
}

type CurrentDatabaseState {
currentDatabase: String
currentSchema: String
}

type DiffStat {
rowsUnmodified: Float!
rowsAdded: Float!
Expand Down Expand Up @@ -282,6 +288,7 @@ type Query {
currentDatabase: String
storedConnections: [DatabaseConnection!]!
databases: [String!]!
schemas: [String!]!
doltDatabaseDetails: DoltDatabaseDetails!
diffStat(databaseName: String!, fromRefName: String!, toRefName: String!, refName: String, type: CommitDiffType, tableName: String): DiffStat!
diffSummaries(databaseName: String!, fromRefName: String!, toRefName: String!, refName: String, type: CommitDiffType, tableName: String): [DiffSummary!]!
Expand Down Expand Up @@ -331,9 +338,10 @@ enum DiffRowType {
type Mutation {
createBranch(databaseName: String!, newBranchName: String!, fromRefName: String!): String!
deleteBranch(databaseName: String!, branchName: String!): Boolean!
addDatabaseConnection(connectionUrl: String!, name: String!, hideDoltFeatures: Boolean, useSSL: Boolean, type: DatabaseType): String
addDatabaseConnection(connectionUrl: String!, name: String!, hideDoltFeatures: Boolean, useSSL: Boolean, type: DatabaseType, schema: String): CurrentDatabaseState!
removeDatabaseConnection(name: String!): Boolean!
createDatabase(databaseName: String!): Boolean!
createSchema(schemaName: String!): Boolean!
resetDatabase: Boolean!
loadDataFile(tableName: String!, refName: String!, databaseName: String!, importOp: ImportOperation!, fileType: FileType!, file: Upload!, modifier: LoadDataModifier): Boolean!
mergePull(databaseName: String!, fromBranchName: String!, toBranchName: String!): Boolean!
Expand Down
3 changes: 3 additions & 0 deletions graphql-server/src/connections/connection.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export class WorkbenchConfig {
useSSL: boolean;

type: DatabaseType;

schema?: string; // Postgres only
}

@Resolver()
Expand Down Expand Up @@ -65,6 +67,7 @@ export class ConnectionResolver {
type: config.type,
connectorPackage: config.type === "mysql" ? "mysql2" : undefined,
url: config.connectionUrl,
schema: config.schema,
ssl: config.useSSL
? {
rejectUnauthorized: false,
Expand Down
3 changes: 3 additions & 0 deletions graphql-server/src/databases/database.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ export class DatabaseConnection {

@Field(_type => DatabaseType, { nullable: true })
type?: DatabaseType;

@Field({ nullable: true })
schema?: string;
}
55 changes: 49 additions & 6 deletions graphql-server/src/databases/database.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from "@nestjs/graphql";
import { ConnectionResolver } from "../connections/connection.resolver";
import { FileStoreService } from "../fileStore/fileStore.service";
import { DBArgs } from "../utils/commonTypes";
import { DBArgs, SchemaArgs } from "../utils/commonTypes";
import { DatabaseType } from "./database.enum";
import { DatabaseConnection } from "./database.model";

Expand All @@ -29,6 +29,9 @@ class AddDatabaseConnectionArgs {

@Field(_type => DatabaseType, { nullable: true })
type?: DatabaseType;

@Field({ nullable: true })
schema?: string;
}

@ObjectType()
Expand All @@ -43,6 +46,16 @@ class DoltDatabaseDetails {
type: DatabaseType;
}

@ObjectType()
class CurrentDatabaseState {
@Field({ nullable: true })
currentDatabase?: string;

// Postgres only
@Field({ nullable: true })
currentSchema?: string;
}

@ArgsType()
class RemoveDatabaseConnectionArgs {
@Field()
Expand Down Expand Up @@ -80,6 +93,21 @@ export class DatabaseResolver {
);
}

@Query(_returns => [String])
async schemas(): Promise<string[]> {
const conn = this.conn.connection();
if (!conn.schemas) return [];
const db = await this.currentDatabase();
if (!db) return [];
const schemas = await conn.schemas({ databaseName: db });
return schemas.filter(
sch =>
sch !== "information_schema" &&
sch !== "pg_catalog" &&
sch !== "pg_toast",
);
}

@Query(_returns => DoltDatabaseDetails)
async doltDatabaseDetails(): Promise<DoltDatabaseDetails> {
const workbenchConfig = this.conn.getWorkbenchConfig();
Expand All @@ -91,15 +119,17 @@ export class DatabaseResolver {
};
}

@Mutation(_returns => String, { nullable: true })
@Mutation(_returns => CurrentDatabaseState)
async addDatabaseConnection(
@Args() args: AddDatabaseConnectionArgs,
): Promise<string | undefined> {
): Promise<CurrentDatabaseState> {
const type = args.type ?? DatabaseType.Mysql;
const workbenchConfig = {
connectionUrl: args.connectionUrl,
hideDoltFeatures: !!args.hideDoltFeatures,
useSSL: !!args.useSSL,
type: args.type ?? DatabaseType.Mysql,
type,
schema: args.schema,
};
await this.conn.addConnection(workbenchConfig);

Expand All @@ -109,8 +139,13 @@ export class DatabaseResolver {
});

const db = await this.currentDatabase();
if (!db) return undefined;
return db;
if (type === DatabaseType.Mysql) {
return { currentDatabase: db };
}
if (!db) {
throw new Error("Must provide database for Postgres connection");
}
return { currentDatabase: db, currentSchema: args.schema };
}

@Mutation(_returns => Boolean)
Expand All @@ -128,6 +163,14 @@ export class DatabaseResolver {
return true;
}

@Mutation(_returns => Boolean)
async createSchema(@Args() args: SchemaArgs): Promise<boolean> {
const conn = this.conn.connection();
if (!conn.createSchema) return false;
await conn.createSchema(args);
return true;
}

@Mutation(_returns => Boolean)
async resetDatabase(): Promise<boolean> {
await this.conn.resetDS();
Expand Down
4 changes: 4 additions & 0 deletions graphql-server/src/queryFactory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,12 @@ export declare class QueryFactory {

currentDatabase(): Promise<string | undefined>;

schemas?(args: t.DBArgs): Promise<string[]>;

createDatabase(args: t.DBArgs): Promise<void>;

createSchema?(args: t.SchemaArgs): Promise<void>;

getTableNames(
args: t.RefArgs,
filterSystemTables?: boolean,
Expand Down
11 changes: 11 additions & 0 deletions graphql-server/src/queryFactory/postgres/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,20 @@ export class PostgresQueryFactory

async databases(): Promise<string[]> {
const res: t.RawRows = await this.query(qh.databasesQuery, []);
return res.map(r => r.datname);
}

async schemas(args: t.DBArgs): Promise<string[]> {
const res: t.RawRows = await this.query(qh.schemasQuery, [
args.databaseName,
]);
return res.map(r => r.schema_name);
}

async createSchema(args: t.SchemaArgs): Promise<void> {
return this.handleAsyncQuery(async qr => qr.createSchema(args.schemaName));
}

async checkoutDatabase(qr: QueryRunner, dbName: string): Promise<void> {
await qr.query(qh.setSearchPath(dbName, this.isDolt));
}
Expand Down
6 changes: 4 additions & 2 deletions graphql-server/src/queryFactory/postgres/queries.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
export const setSearchPath = (dbName: string, _isDolt = false) =>
`SET SEARCH_PATH = '${dbName}'`;

export const listTablesQuery = `SELECT * FROM pg_catalog.pg_tables where schemaname=$1;`;
export const databasesQuery = `SELECT datname FROM pg_database`;

export const schemasQuery = `SELECT schema_name FROM information_schema.schemata WHERE catalog_name = $1`;

export const databasesQuery = `SELECT schema_name FROM information_schema.schemata;`;
export const listTablesQuery = `SELECT * FROM pg_catalog.pg_tables where schemaname=$1;`;

export const getViewsQuery = `SELECT table_name FROM INFORMATION_SCHEMA.views WHERE table_schema = $1`;

Expand Down
1 change: 1 addition & 0 deletions graphql-server/src/queryFactory/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DiffRowType } from "../rowDiffs/rowDiff.enums";

export type DBArgs = { databaseName: string };
export type SchemaArgs = { schemaName: string };
export type RefArgs = DBArgs & { refName: string };
export type BranchArgs = DBArgs & { branchName: string };
export type TagArgs = DBArgs & { tagName: string };
Expand Down
Loading

0 comments on commit 1a92517

Please sign in to comment.