Skip to content

Commit 1a92517

Browse files
authored
Merge pull request #82 from dolthub/taylor/postgres
Postgres support for web
2 parents 4aec1cb + 3b9c066 commit 1a92517

File tree

141 files changed

+4044
-1924
lines changed

Some content is hidden

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

141 files changed

+4044
-1924
lines changed

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
# dolt-workbench
22

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

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

89
## Features
910

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

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

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

2223
### Auto-generate SQL queries
2324

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

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

107108
### Saving connection information between runs

docker/README.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# dolt-workbench
22

3-
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).
3+
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).
44

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

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

4848
## Connecting to a locally installed database
4949

50-
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.
50+
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.
5151

5252
### MySQL
5353

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

5656
```
5757
bind-address = 0.0.0.0
@@ -71,9 +71,19 @@ my-dolt-db % dolt sql-server -H 0.0.0.0
7171

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

74+
### PostgreSQL
75+
76+
Locate your PostgreSQL configuration file (`postgresql.conf`) and change the `listen_addresses` directive from `localhost` to `*`. Restart the server.
77+
78+
```
79+
listen_addresses = '*'
80+
```
81+
82+
Use `host.docker.internal` as the host when entering your connection information from the UI.
83+
7484
## Connecting to a Docker installed database
7585

76-
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.
86+
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.
7787

7888
First, create the network:
7989

graphql-server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"graphql-upload": "13",
4242
"mysql2": "^3.6.5",
4343
"pg": "^8.11.3",
44+
"pg-copy-streams": "^6.0.6",
4445
"reflect-metadata": "^0.1.13",
4546
"rxjs": "^7.5.4",
4647
"timeago.js": "^4.0.2",

graphql-server/schema.gql

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ type DatabaseConnection {
100100
hideDoltFeatures: Boolean
101101
useSSL: Boolean
102102
type: DatabaseType
103+
schema: String
103104
}
104105

105106
enum DatabaseType {
@@ -113,6 +114,11 @@ type DoltDatabaseDetails {
113114
type: DatabaseType!
114115
}
115116

117+
type CurrentDatabaseState {
118+
currentDatabase: String
119+
currentSchema: String
120+
}
121+
116122
type DiffStat {
117123
rowsUnmodified: Float!
118124
rowsAdded: Float!
@@ -282,6 +288,7 @@ type Query {
282288
currentDatabase: String
283289
storedConnections: [DatabaseConnection!]!
284290
databases: [String!]!
291+
schemas: [String!]!
285292
doltDatabaseDetails: DoltDatabaseDetails!
286293
diffStat(databaseName: String!, fromRefName: String!, toRefName: String!, refName: String, type: CommitDiffType, tableName: String): DiffStat!
287294
diffSummaries(databaseName: String!, fromRefName: String!, toRefName: String!, refName: String, type: CommitDiffType, tableName: String): [DiffSummary!]!
@@ -331,9 +338,10 @@ enum DiffRowType {
331338
type Mutation {
332339
createBranch(databaseName: String!, newBranchName: String!, fromRefName: String!): String!
333340
deleteBranch(databaseName: String!, branchName: String!): Boolean!
334-
addDatabaseConnection(connectionUrl: String!, name: String!, hideDoltFeatures: Boolean, useSSL: Boolean, type: DatabaseType): String
341+
addDatabaseConnection(connectionUrl: String!, name: String!, hideDoltFeatures: Boolean, useSSL: Boolean, type: DatabaseType, schema: String): CurrentDatabaseState!
335342
removeDatabaseConnection(name: String!): Boolean!
336343
createDatabase(databaseName: String!): Boolean!
344+
createSchema(schemaName: String!): Boolean!
337345
resetDatabase: Boolean!
338346
loadDataFile(tableName: String!, refName: String!, databaseName: String!, importOp: ImportOperation!, fileType: FileType!, file: Upload!, modifier: LoadDataModifier): Boolean!
339347
mergePull(databaseName: String!, fromBranchName: String!, toBranchName: String!): Boolean!

graphql-server/src/connections/connection.resolver.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export class WorkbenchConfig {
1515
useSSL: boolean;
1616

1717
type: DatabaseType;
18+
19+
schema?: string; // Postgres only
1820
}
1921

2022
@Resolver()
@@ -65,6 +67,7 @@ export class ConnectionResolver {
6567
type: config.type,
6668
connectorPackage: config.type === "mysql" ? "mysql2" : undefined,
6769
url: config.connectionUrl,
70+
schema: config.schema,
6871
ssl: config.useSSL
6972
? {
7073
rejectUnauthorized: false,

graphql-server/src/databases/database.model.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,7 @@ export class DatabaseConnection {
1717

1818
@Field(_type => DatabaseType, { nullable: true })
1919
type?: DatabaseType;
20+
21+
@Field({ nullable: true })
22+
schema?: string;
2023
}

graphql-server/src/databases/database.resolver.ts

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
} from "@nestjs/graphql";
1010
import { ConnectionResolver } from "../connections/connection.resolver";
1111
import { FileStoreService } from "../fileStore/fileStore.service";
12-
import { DBArgs } from "../utils/commonTypes";
12+
import { DBArgs, SchemaArgs } from "../utils/commonTypes";
1313
import { DatabaseType } from "./database.enum";
1414
import { DatabaseConnection } from "./database.model";
1515

@@ -29,6 +29,9 @@ class AddDatabaseConnectionArgs {
2929

3030
@Field(_type => DatabaseType, { nullable: true })
3131
type?: DatabaseType;
32+
33+
@Field({ nullable: true })
34+
schema?: string;
3235
}
3336

3437
@ObjectType()
@@ -43,6 +46,16 @@ class DoltDatabaseDetails {
4346
type: DatabaseType;
4447
}
4548

49+
@ObjectType()
50+
class CurrentDatabaseState {
51+
@Field({ nullable: true })
52+
currentDatabase?: string;
53+
54+
// Postgres only
55+
@Field({ nullable: true })
56+
currentSchema?: string;
57+
}
58+
4659
@ArgsType()
4760
class RemoveDatabaseConnectionArgs {
4861
@Field()
@@ -80,6 +93,21 @@ export class DatabaseResolver {
8093
);
8194
}
8295

96+
@Query(_returns => [String])
97+
async schemas(): Promise<string[]> {
98+
const conn = this.conn.connection();
99+
if (!conn.schemas) return [];
100+
const db = await this.currentDatabase();
101+
if (!db) return [];
102+
const schemas = await conn.schemas({ databaseName: db });
103+
return schemas.filter(
104+
sch =>
105+
sch !== "information_schema" &&
106+
sch !== "pg_catalog" &&
107+
sch !== "pg_toast",
108+
);
109+
}
110+
83111
@Query(_returns => DoltDatabaseDetails)
84112
async doltDatabaseDetails(): Promise<DoltDatabaseDetails> {
85113
const workbenchConfig = this.conn.getWorkbenchConfig();
@@ -91,15 +119,17 @@ export class DatabaseResolver {
91119
};
92120
}
93121

94-
@Mutation(_returns => String, { nullable: true })
122+
@Mutation(_returns => CurrentDatabaseState)
95123
async addDatabaseConnection(
96124
@Args() args: AddDatabaseConnectionArgs,
97-
): Promise<string | undefined> {
125+
): Promise<CurrentDatabaseState> {
126+
const type = args.type ?? DatabaseType.Mysql;
98127
const workbenchConfig = {
99128
connectionUrl: args.connectionUrl,
100129
hideDoltFeatures: !!args.hideDoltFeatures,
101130
useSSL: !!args.useSSL,
102-
type: args.type ?? DatabaseType.Mysql,
131+
type,
132+
schema: args.schema,
103133
};
104134
await this.conn.addConnection(workbenchConfig);
105135

@@ -109,8 +139,13 @@ export class DatabaseResolver {
109139
});
110140

111141
const db = await this.currentDatabase();
112-
if (!db) return undefined;
113-
return db;
142+
if (type === DatabaseType.Mysql) {
143+
return { currentDatabase: db };
144+
}
145+
if (!db) {
146+
throw new Error("Must provide database for Postgres connection");
147+
}
148+
return { currentDatabase: db, currentSchema: args.schema };
114149
}
115150

116151
@Mutation(_returns => Boolean)
@@ -128,6 +163,14 @@ export class DatabaseResolver {
128163
return true;
129164
}
130165

166+
@Mutation(_returns => Boolean)
167+
async createSchema(@Args() args: SchemaArgs): Promise<boolean> {
168+
const conn = this.conn.connection();
169+
if (!conn.createSchema) return false;
170+
await conn.createSchema(args);
171+
return true;
172+
}
173+
131174
@Mutation(_returns => Boolean)
132175
async resetDatabase(): Promise<boolean> {
133176
await this.conn.resetDS();

graphql-server/src/queryFactory/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,12 @@ export declare class QueryFactory {
5252

5353
currentDatabase(): Promise<string | undefined>;
5454

55+
schemas?(args: t.DBArgs): Promise<string[]>;
56+
5557
createDatabase(args: t.DBArgs): Promise<void>;
5658

59+
createSchema?(args: t.SchemaArgs): Promise<void>;
60+
5761
getTableNames(
5862
args: t.RefArgs,
5963
filterSystemTables?: boolean,

graphql-server/src/queryFactory/postgres/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,20 @@ export class PostgresQueryFactory
1616

1717
async databases(): Promise<string[]> {
1818
const res: t.RawRows = await this.query(qh.databasesQuery, []);
19+
return res.map(r => r.datname);
20+
}
21+
22+
async schemas(args: t.DBArgs): Promise<string[]> {
23+
const res: t.RawRows = await this.query(qh.schemasQuery, [
24+
args.databaseName,
25+
]);
1926
return res.map(r => r.schema_name);
2027
}
2128

29+
async createSchema(args: t.SchemaArgs): Promise<void> {
30+
return this.handleAsyncQuery(async qr => qr.createSchema(args.schemaName));
31+
}
32+
2233
async checkoutDatabase(qr: QueryRunner, dbName: string): Promise<void> {
2334
await qr.query(qh.setSearchPath(dbName, this.isDolt));
2435
}

graphql-server/src/queryFactory/postgres/queries.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
export const setSearchPath = (dbName: string, _isDolt = false) =>
22
`SET SEARCH_PATH = '${dbName}'`;
33

4-
export const listTablesQuery = `SELECT * FROM pg_catalog.pg_tables where schemaname=$1;`;
4+
export const databasesQuery = `SELECT datname FROM pg_database`;
5+
6+
export const schemasQuery = `SELECT schema_name FROM information_schema.schemata WHERE catalog_name = $1`;
57

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

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

0 commit comments

Comments
 (0)