Skip to content
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

Postgres support for web #82

Merged
merged 23 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 Expand Up @@ -29,7 +30,7 @@
filterByRowType?: DiffRowType;
};

export type RawRow = Record<string, any>;

Check warning on line 33 in graphql-server/src/queryFactory/types.ts

View workflow job for this annotation

GitHub Actions / ci

Unexpected any. Specify a different type
export type RawRows = RawRow[];
export type PR = Promise<RawRows>;
export type SPR = Promise<RawRow>;
Expand Down
Loading
Loading